Skip to content
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

ClassDef checker error after linking when only using the class data of a non-native JS class #4850

Closed
futile opened this issue Apr 11, 2023 · 9 comments · Fixed by #4862
Closed
Assignees
Labels
bug Confirmed bug. Needs to be fixed.
Milestone

Comments

@futile
Copy link

futile commented Apr 11, 2023

In a project (non-public :( ) that uses tapir, compilation with 1.13.0 worked fine, while the upgrade to 1.13.1 throws the following error:

[error] file:/home/runner/work/tapir/tapir/serverless/aws/lambda/src/main/scalajs/sttp/tapir/serverless/aws/lambda/js/AwsJsRequest.scala(29:7:ClassDef): JS classes and module classes must have a constructor
[error] java.util.concurrent.ExecutionException: Boxed Error
[error] 	at scala.concurrent.impl.Promise$.resolver(Promise.scala:87)
[error] 	at scala.concurrent.impl.Promise$.scala$concurrent$impl$Promise$$resolveTry(Promise.scala:79)
[error] 	at scala.concurrent.impl.Promise$DefaultPromise.tryComplete(Promise.scala:284)
[error] 	at scala.concurrent.Promise.complete(Promise.scala:53)
[error] 	at scala.concurrent.Promise.complete$(Promise.scala:52)
[error] 	at scala.concurrent.impl.Promise$DefaultPromise.complete(Promise.scala:187)
[error] 	at scala.concurrent.impl.Promise.$anonfun$transform$1(Promise.scala:33)
[error] 	at scala.concurrent.impl.CallbackRunnable.run(Promise.scala:64)
[error] 	at java.base/java.util.concurrent.ForkJoinTask$RunnableExecuteAction.exec(ForkJoinTask.java:1395)
[error] 	at java.base/java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:373)
[error] 	at java.base/java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(ForkJoinPool.java:1182)
[error] 	at java.base/java.util.concurrent.ForkJoinPool.scan(ForkJoinPool.java:1655)
[error] 	at java.base/java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1622)
[error] 	at java.base/java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:165)
[error] Caused by: java.lang.AssertionError: There were 1 ClassDef checking errors after optimizing. Please report this as a bug.
[error] 	at org.scalajs.linker.frontend.Refiner$ClassInfoCache.update(Refiner.scala:161)
[error] 	at org.scalajs.linker.frontend.Refiner$ClassInfoCache.$anonfun$loadInfo$2(Refiner.scala:147)
[error] 	at scala.concurrent.Future$.$anonfun$apply$1(Future.scala:659)
[error] 	at scala.util.Success.$anonfun$map$1(Try.scala:255)
[error] 	at scala.util.Success.map(Try.scala:213)
[error] 	at scala.concurrent.Future.$anonfun$map$1(Future.scala:292)
[error] 	at scala.concurrent.impl.Promise.liftedTree1$1(Promise.scala:33)
[error] 	at scala.concurrent.impl.Promise.$anonfun$transform$1(Promise.scala:33)
[error] 	at scala.concurrent.impl.CallbackRunnable.run(Promise.scala:64)
[error] 	at java.base/java.util.concurrent.ForkJoinTask$RunnableExecuteAction.exec(ForkJoinTask.java:1395)
[error] 	at java.base/java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:373)
[error] 	at java.base/java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(ForkJoinPool.java:1182)
[error] 	at java.base/java.util.concurrent.ForkJoinPool.scan(ForkJoinPool.java:1655)
[error] 	at java.base/java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1622)
[error] 	at java.base/java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:165)

The offending line seems to be this one:
https://github.com/softwaremill/tapir/blob/master/serverless/aws/lambda/src/main/scalajs/sttp/tapir/serverless/aws/lambda/js/AwsJsRequest.scala#L29

No idea how to minimize this, but since it's a public library, it should be possible to reproduce. This bug occurs when compiling tapir 1.2.12, which is also the most recent release, released last week (at the time of writing).

Please let me know if/what further information can help, or to test a possible fix! Really looking forward to those incremental-comp performance upgrades in 1.13.1 :) Thanks!

@sjrd
Copy link
Member

sjrd commented Apr 11, 2023

Hum, interesting. FWIW, the bug probably existed before 1.13.1, but did not do ClassDef checks after the optimizer before.

You should be able to work around the issue by disabling IR checks (.withCheckIR(false) on scalaJSLinkerConfig). IIIUC it's the default, so you should already have a withCheckIR somewhere in your build that makes it true.

@sjrd sjrd added the bug Confirmed bug. Needs to be fixed. label Apr 11, 2023
@sjrd sjrd added this to the v1.13.2 milestone Apr 11, 2023
@sjrd
Copy link
Member

sjrd commented Apr 11, 2023

Alternative workaround: add a

scala.scalajs.js.constructorOf[sttp.tapir.serverless.aws.lambda.js.AwsJsRequestContext]

somewhere in your code (not in dead code), such as in your main method. That should force the optimizer to preserve the constructor of that class.

@futile
Copy link
Author

futile commented Apr 11, 2023

Thanks for the quick reply!

From a quick grep for withCheckIR through the source of my project, as well as tapir itself, nothing came up. I took a look at where withCheckIR is used in scalajs, and found this line:

.withCheckIR(true) // for safety, fullOpt is slow anyways.

It seems that the IR-checks are enabled for fullOptJS by default? And this assertion happens in my project when building fullOptJS, so that would match up.

I'll test out the workaround and report back as well.

@futile
Copy link
Author

futile commented Apr 11, 2023

I can confirm two things already:

  • The assertion only occurs when compiling fullOptJS, while fastOptJS compiles fine.
  • Using the constructorOf[...] workaround, the build works again, so the workaround works

I didn't manage to find how I enable checkIR through sbt. .settings(fullOptJS / checkIR := false) didn't work, maybe you can tell me what the easiest way to disable checkIR is?

@sjrd
Copy link
Member

sjrd commented Apr 11, 2023

Ah yes, it's true by default in fullLink. I had forgotten about that.

The correct setting is:

.settings(Compile / fullLinkJS / scalaJSLinkerConfig ~= { _.withCheckIR(false) }

@futile
Copy link
Author

futile commented Apr 11, 2023

Thanks, then I can confirm that disabling the IR check that way also works around the issue!

Just to have an idea of how long a workaround would be in our codebase until a fix is released in scala-js: Will this bug be reason for a quick release of 1.13.2 since it's (technically) a regression, or will it simply be left as-is since it might not affect many projects and has an easy workaround?

Again, thanks for the quick replies! :)

@sjrd
Copy link
Member

sjrd commented Apr 11, 2023

It's unlikely that we'll make a quick release for this, unless a number of other people experience the same issue. We normally stick to our 2-month schedule.

@futile
Copy link
Author

futile commented Apr 11, 2023

Alright, thanks for letting me know, and for the quick replies again!

sjrd added a commit to sjrd/scala-js that referenced this issue Apr 16, 2023
…their data.

This underlying issue was already fixed in
ae17b93.
sjrd added a commit to sjrd/scala-js that referenced this issue Apr 16, 2023
…their data.

The underlying issue was already fixed in
ae17b93.
@sjrd sjrd self-assigned this Apr 16, 2023
@sjrd
Copy link
Member

sjrd commented Apr 16, 2023

It turns out this was already fixed in ae17b93. Consolidating is good. :p PR to enshrine it in tests open at #4852.

sjrd added a commit to sjrd/scala-js that referenced this issue Apr 16, 2023
…their data.

The underlying issue was already fixed in
ae17b93.
sjrd added a commit to sjrd/scala-js that referenced this issue Apr 17, 2023
…d JS types.

When a JS type is not instantiated, it loses its constructor, which
makes it invalid from the ClassDef checker's perspective. We now
turn such JS types into `AbstractJSType`s, which do not need any
constructor.

For native JS types, this also means that they should get rid of
their `jsNativeLoadSpec`, since abstract JS types must not have
one.
sjrd added a commit to sjrd/scala-js that referenced this issue Apr 18, 2023
…d JS types.

When a JS type is not instantiated, it loses its constructor, which
makes it invalid from the ClassDef checker's perspective. We now
turn such JS types into `NativeJSClass`es that do not have a
native load spec.
@sjrd sjrd changed the title Bug: AssertionError when compiling tapir 1.2.12 with scalajs 1.13.1 ClassDef checker error after linking when only using the class data of a non-native JS class Apr 18, 2023
sjrd added a commit to sjrd/scala-js that referenced this issue May 16, 2023
There were several interleaved issues with JS classes (native or
not, module or not) that survived the base linker only for their
class data:

- Non-native classes lose their `jsConstructorDef` (which is what
  caused the symptoms in the issue).
- Non-native non-module classes generate a `Class.isInstance` test
  that tries to access a never-declared `$a_C()` accessor.
- Module classes lose their module status, triggering the wrong
  conditions for the `Class.isInstance` tests later on.

Some related issues about module accessors were previously handled
by patching `ClassKind`s. For module classes whose module accessor
was not used, we patched the kind to a corresponding kind without
module accessor. This was problematic as well: it caused the third
issue above, as well as making the kind unstable over incremental
runs (although we tend to assume that the kind is covered by the
class version).

We solve all these issues at once, by taking a much more principled
approach:

- We never change the kind of classes.
- After base linking, we tolerate `ClassDef`s without constructors
  even if they are module classes and/or non-native JS classes.
- In the emitter, we adjust the generation of module accessors and
  `Class.isInstance` tests to whether the class effectively has
  instances.
@sjrd sjrd linked a pull request May 17, 2023 that will close this issue
sjrd added a commit to sjrd/scala-js that referenced this issue May 22, 2023
There were several interleaved issues with JS classes (native or
not, module or not) that survived the base linker only for their
class data:

- Non-native classes lose their `jsConstructorDef` (which is what
  caused the symptoms in the issue).
- Non-native non-module classes generate a `Class.isInstance` test
  that tries to access a never-declared `$a_C()` accessor.
- Module classes lose their module status, triggering the wrong
  conditions for the `Class.isInstance` tests later on.

Some related issues about module accessors were previously handled
by patching `ClassKind`s. For module classes whose module accessor
was not used, we patched the kind to a corresponding kind without
module accessor. This was problematic as well: it caused the third
issue above, as well as making the kind unstable over incremental
runs (although we tend to assume that the kind is covered by the
class version).

We solve all these issues at once, by taking a much more principled
approach:

- We never change the kind of classes.
- After base linking, we tolerate `ClassDef`s without constructors
  even if they are module classes and/or non-native JS classes.
- In the emitter, we adjust the generation of module accessors and
  `Class.isInstance` tests to whether the class effectively has
  instances.
gzm0 added a commit that referenced this issue May 23, 2023
…lass-data-2

Fix #4850: Handle JS classes that survive only for their data.
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