8000 Linker not noticing instance test changes · Issue #5131 · scala-js/scala-js · GitHub
[go: up one dir, main page]

Skip to content

Linker not noticing instance test changes #5131

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
VelizarHristov opened this issue Feb 18, 2025 · 0 comments 8000 · Fixed by #5140
Closed

Linker not noticing instance test changes #5131

VelizarHristov opened this issue Feb 18, 2025 · 0 comments · Fixed by #5140
Assignees
Labels
bug Confirmed bug. Needs to be fixed.
Milestone

Comments

@VelizarHristov
Copy link

In some cases, the linker fails to update a file and this can result in Javascript errors which always go away after clean and re-linking. Here's one example where this might happen:

Make sure to use small modules in the build:

scalaJSLinkerConfig ~= {
    _.withModuleKind(ModuleKind.ESModule)
      .withModuleSplitStyle(
        ModuleSplitStyle.SmallModulesFor(List("hello_world")))
  }

Use whichever version of Scala and SBT (I've reproduced it on many versions), and the latest ScalaJS.

Declare the following in a file:

package hello_world
trait A
case class B() extends A
case class C() extends A

And this is your main method in a different file:

package hello_world
@main
def main(): Unit =
    val ls = Seq(new B(), new C())
    println(ls.collect { case a: B => a })

Now link it by running fastLinkJS from sbt - so far everything will work;
Then change B to C in the last line, the code should look like this: println(ls.collect { case a: C => a })
Then run fastLinkJS again.
At this point the resulting JS is broken. It no longer prints a console log message, and it instead shows a JS error.
What's broken specifically is in the generated C.js code - it is missing the function starting with $as_, which is expected from the file which I will abbreviate as $$anon$1.js.

(The bug didn't reproduce when I declared the trait and classes in the same file as the main method; however, it reproduces when all of A, B, C are classes (B, C must extend A), even with parameters)

The problem can be narrowed down to the code here: https://github.com/scala-js/scala-js/blob/main/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/Emitter.scala#L696-L701
This code is not updating changed in the case where linkedClass.hasInstanceTests changes, while nothing in the class itself changes. Note that we're not modifying the implementation of the case class C, we're only modifying its caller, so the linker incorrectly assumes that the class has not changed and doesn't check anything referencing it. I couldn't find a specific part where the code was wrong, it's more that this check needs to be added to prevent this bug, and I don't know what is the best way to do this.

A seemingly much less important case of the same bug: If I'm doing an instance test, then I remove it without making changes to the file declaring the referenced instance, then a different js file will be constructed by the emitter, but changed will be false so it won't replace the old file. This doesn't break anything, it only leaves in unused code. To reproduce follow the same steps as above, but check the generated JS code for B, and you'll see that it has instance check functions that are unused and that will go away if cleaned and re-linked.

Also I am not sure whether classEmitter.needInstanceTests can toggle in other situations with changed remaining false - I don't know what .isAncestorOfHijackedClass will be because I don't know what a hijacked class is, and I did not dig into it, it just seemed suspicious that this might possibly happen. Food for thought for someone more familiar with the codebase than I am.

@sjrd sjrd self-assigned this Feb 18, 2025
@sjrd sjrd added the bug Confirmed bug. Needs to be fixed. label Feb 18, 2025
@sjrd sjrd added this to the v1.19.0 milestone Feb 18, 2025
sjrd added a commit to sjrd/scala-js that referenced this issue Mar 11, 2025
… flag.

That flag is used to decide if a module needs to be rewritten.
Previously, we failed to notice cases where *only* one of the
`Analysis` flags changed (`hasInstances`, `hasRuntimeTypeInfo` and
`hasInstanceTests`). In those cases, all the *cached trees* are
kept. The only thing that changes is the subset thereof that is
actullay included. That does not require invalidation of the
caches, but it does require rewriting the module.
sjrd added a commit to sjrd/scala-js that referenced this issue Mar 17, 2025
… flag.

That flag is used to decide if a module needs to be rewritten.
Previously, we failed to notice cases where *only* one of the
`Analysis` flags changed (`hasInstances`, `hasRuntimeTypeInfo` and
`hasInstanceTests`). In those cases, all the *cached trees* are
kept. The only thing that changes is the subset thereof that is
actullay included. That does not require invalidation of the
caches, but it does require rewriting the module.
sjrd added a commit to sjrd/scala-js that referenced this issue Mar 24, 2025
…Class.

Previously, we were missing a number of cases where we should
have set the `changed` flag to true.

That flag is used to decide if a module needs to be rewritten.
Previously, we failed to notice cases where *only* one of the
`Analysis` flags changed (`hasInstances`, `hasRuntimeTypeInfo`
and `hasInstanceTests`). In those cases, all the *cached trees*
are kept. The only thing that changes is the subset thereof that
is actullay included. That does not require invalidation of the
caches, but it does require rewriting the module.

Overall, it had become quite tricky to follow what we are
allowed to use where. This commit overhauls how we track
everything.

First, we include the `kind` of the class in its cache identity,
because too many things depend on it. If it changes, it is
likely that everything will be invalidated anyway.

Other than that, dangerous variables, whose uses must be
justified, are clearly marked with `foo_!` names.

Boolean decisions whose outcome is never cached are tracked
separately; if any of them changes, we marked `changed` to `true`.
This particular change is what fixes scala-js#5131.
sjrd added a commit to sjrd/scala-js that referenced this issue Apr 6, 2025
…Class.

Previously, we were missing a number of cases where we should
have set the `changed` flag to true.

That flag is used to decide if a module needs to be rewritten.
Previously, we failed to notice cases where *only* one of the
`Analysis` flags changed (`hasInstances`, `hasRuntimeTypeInfo`
and `hasInstanceTests`). In those cases, all the *cached trees*
are kept. The only thing that changes is the subset thereof that
is actullay included. That does not require invalidation of the
caches, but it does require rewriting the module.

Likewise, we failed to invalidate the module if only the *set*
of static-like methods retained by the analysis changed.

Overall, it had become quite tricky to follow what we are
allowed to use where. This commit overhauls how we track
everything.

First, we include the `kind` of the class in its cache identity,
because too many things depend on it. If it changes, it is
likely that everything will be invalidated anyway.

Other than that, dangerous variables, whose uses must be
justified, are clearly marked with `foo_!` names.

Boolean decisions whose outcome is never cached are tracked
separately; if any of them changes, we marked `changed` to `true`.
This particular change is what fixes scala-js#5131.

Finally, we add dedicated tracking of the set of generated
static-like methods, like we do for instance methods.
@sjrd sjrd closed this as completed in #5140 Apr 6, 2025
sjrd added a commit that referenced this issue Apr 6, 2025
…h-modules

Fix #5131: Overhaul justifications for caching in Emitter.genClass.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Confirmed bug. Needs to be fixed.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants
0