From ddee368309fbbb7a7d716af9e13ce295ac0ddd43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Mon, 2 Feb 2026 13:10:43 +0100 Subject: [PATCH 01/32] Tigten IR checking of NewLambda. --- .../main/scala/org/scalajs/linker/checker/IRChecker.scala | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/linker/shared/src/main/scala/org/scalajs/linker/checker/IRChecker.scala b/linker/shared/src/main/scala/org/scalajs/linker/checker/IRChecker.scala index 441759130a..5758ee515d 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/checker/IRChecker.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/checker/IRChecker.scala @@ -515,6 +515,14 @@ private final class IRChecker(linkTimeProperties: LinkTimeProperties, val closureType = ClosureType(descriptor.paramTypes, descriptor.resultType, nullable = false) typecheckExpect(fun, env, closureType) + val possibleTypes = (descriptor.superClass :: descriptor.interfaces).map { parent => + ClassType(parent, nullable = false, exact = false) + } + if (!possibleTypes.exists(isSubtype(_, tree.tpe))) { + val possibleTypesStr = possibleTypes.map(_.show()).mkString(", ") + reportError(i"illegal type for NewLambda: expected a supertype of one of $possibleTypesStr; got ${tree.tpe}") + } + case UnaryOp(UnaryOp.CheckNotNull, lhs) => // CheckNotNull accepts any closure type in addition to `AnyType` lhs.tpe match { From 4cb8c94d415d783c10d45084026cbeb898210c7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Mon, 16 Feb 2026 10:57:23 +0100 Subject: [PATCH 02/32] Fix #5145: Add {ClassDef,IR}Checker tests for NewLambda. --- .../org/scalajs/linker/IRCheckerTest.scala | 54 +++++++++++++++++++ .../linker/checker/ClassDefCheckerTest.scala | 34 ++++++++++++ 2 files changed, 88 insertions(+) diff --git a/linker/shared/src/test/scala/org/scalajs/linker/IRCheckerTest.scala b/linker/shared/src/test/scala/org/scalajs/linker/IRCheckerTest.scala index 58ae17e255..aed20585c6 100644 --- a/linker/shared/src/test/scala/org/scalajs/linker/IRCheckerTest.scala +++ b/linker/shared/src/test/scala/org/scalajs/linker/IRCheckerTest.scala @@ -389,6 +389,60 @@ class IRCheckerTest { testLinkNoIRError(classDefs, MainTestModuleInitializers, postOptimizer = true) } + @Test + def newLambda(): AsyncResult = await { + val ComparableClass = ClassName("java.lang.Comparable") + val ComparableType = ClassType(ComparableClass, nullable = false, exact = false) + + val descriptor = NewLambda.Descriptor( + ObjectClass, List(ComparableClass), m("compareTo", List(O), I), List(AnyType), IntType) + val closure = + Closure(ClosureFlags.typed, Nil, List(paramDef("that", AnyType)), None, IntType, int(0), Nil) + + val allowedTypeMsg = "a supertype of one of java.lang.Object!, java.lang.Comparable!" + + val result1 = for { + log <- testLinkIRErrors( + Seq(mainTestClassDef( + NewLambda(descriptor, closure.copy(resultType = AnyType))(ComparableType) + )), + MainTestModuleInitializers) + } yield { + log.assertContainsError("((any) => int)! expected but ((any) => any)! found") + } + + val result2 = for { + log <- testLinkIRErrors( + Seq(mainTestClassDef( + NewLambda(descriptor, closure)(ClassType(CloneableClass, nullable = false, exact = false)) + )), + MainTestModuleInitializers) + } yield { + log.assertContainsError( + s"illegal type for NewLambda: expected $allowedTypeMsg; got java.lang.Cloneable!") + } + + val result3 = for { + log <- testLinkIRErrors( + Seq(mainTestClassDef( + NewLambda(descriptor, closure)(ComparableType.copy(exact = true)) + )), + MainTestModuleInitializers) + } yield { + log.assertContainsError( + s"illegal type for NewLambda: expected $allowedTypeMsg; got =java.lang.Comparable!") + } + + // Using any! as super type is fine + val result4 = testLinkNoIRError( + Seq(mainTestClassDef( + NewLambda(descriptor, closure)(AnyNotNullType) + )), + MainTestModuleInitializers) + + Future.sequence(Seq(result1, result2, result3)) + } + @Test def badRecordSelect(): AsyncResult = await { val recordValue = RecordValue( diff --git a/linker/shared/src/test/scala/org/scalajs/linker/checker/ClassDefCheckerTest.scala b/linker/shared/src/test/scala/org/scalajs/linker/checker/ClassDefCheckerTest.scala index ea59be84aa..257cb1a41f 100644 --- a/linker/shared/src/test/scala/org/scalajs/linker/checker/ClassDefCheckerTest.scala +++ b/linker/shared/src/test/scala/org/scalajs/linker/checker/ClassDefCheckerTest.scala @@ -870,6 +870,40 @@ class ClassDefCheckerTest { previousPhase = CheckingPhase.Optimizer) } + @Test + def newLambdaTest(): Unit = { + val ComparableClass = ClassName("java.lang.Comparable") + val ComparableType = ClassType(ComparableClass, nullable = false, exact = false) + + val descriptor = NewLambda.Descriptor( + ObjectClass, List(ComparableClass), m("compareTo", List(O), I), List(AnyType), IntType) + val closure = + Closure(ClosureFlags.typed, Nil, List(paramDef("that", AnyType)), None, IntType, int(0), Nil) + + // Wrong Closure flags + assertError( + mainTestClassDef(NewLambda(descriptor, + closure.copy(flags = ClosureFlags.arrow, resultType = AnyType))(ComparableType)), + "The argument to a NewLambda must be a typed closure") + + // Not a closure + assertError( + mainTestClassDef(NewLambda(descriptor, int(0))(ComparableType)), + "The argument to a NewLambda must be a typed closure") + + // Something wrong in the Closure itself (smoke test to check that we check the Closure as a whole) + assertError( + mainTestClassDef(NewLambda(descriptor, + closure.copy(captureValues = List(int(0))))(ComparableType)), + "Mismatched size for captures: 0 params vs 1 values") + + // NewLambda is rejected after desugaring + assertError( + mainTestClassDef(NewLambda(descriptor, closure)(ComparableType)), + "Illegal NewLambda after desugaring", + previousPhase = CheckingPhase.Optimizer) + } + @Test def linkTimePropertyTest(): Unit = { // Test that some illegal types are rejected From 41e6063d22024502e2dfc2b8ffada79d22a34104 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Mon, 16 Feb 2026 11:43:39 +0100 Subject: [PATCH 03/32] Close #5311: Deprecate support for JDK < 17. We display a warning in two places: * when loading an sbt build with sbt-scalajs, and * when calling the `link` method of a `StandardLinkerImpl`. --- .../linker/standard/StandardLinkerImpl.scala | 26 +++++++++++++++++++ .../scala/tools/nsc/MainGenericRunner.scala | 2 +- .../org/scalajs/sbtplugin/ScalaJSPlugin.scala | 15 +++++++++++ 3 files changed, 42 insertions(+), 1 deletion(-) diff --git a/linker/shared/src/main/scala/org/scalajs/linker/standard/StandardLinkerImpl.scala b/linker/shared/src/main/scala/org/scalajs/linker/standard/StandardLinkerImpl.scala index 97771f1df0..7664d1c58f 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/standard/StandardLinkerImpl.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/standard/StandardLinkerImpl.scala @@ -26,6 +26,8 @@ private final class StandardLinkerImpl private ( frontend: LinkerFrontend, backend: LinkerBackend) extends LinkerImpl { + import StandardLinkerImpl._ + require(frontend.coreSpec == backend.coreSpec, "Frontend and backend must implement the same core specification") @@ -40,6 +42,8 @@ private final class StandardLinkerImpl private ( throw new IllegalStateException("Linker used concurrently") } + checkJDKVersion(logger) + checkValid() .flatMap { _ => frontend.link( @@ -64,4 +68,26 @@ private final class StandardLinkerImpl private ( object StandardLinkerImpl { def apply(frontend: LinkerFrontend, backend: LinkerBackend): Linker = new StandardLinkerImpl(frontend, backend) + + private lazy val deprecatedJDKVersion: Option[Int] = { + if (1.0.toString() == "1") { + // We're running on JS + None + } else { + val fullVersion = System.getProperty("java.version") + val v = fullVersion.stripPrefix("1.").takeWhile(_.isDigit).toInt + if (v < 17) + Some(v) + else + None + } + } + + private def checkJDKVersion(logger: Logger): Unit = { + for (v <- deprecatedJDKVersion) { + logger.warn( + s"Using the Scala.js Linker API on JDK $v is deprecated. " + + "A future minor version will require at least JDK 17.") + } + } } diff --git a/partest/src/main/scala/scala/tools/nsc/MainGenericRunner.scala b/partest/src/main/scala/scala/tools/nsc/MainGenericRunner.scala index 69e2ab3f69..492c992fe4 100644 --- a/partest/src/main/scala/scala/tools/nsc/MainGenericRunner.scala +++ b/partest/src/main/scala/scala/tools/nsc/MainGenericRunner.scala @@ -86,7 +86,7 @@ class MainGenericRunner { if (command.howToRun != AsObject) return errorFn("Scala.js runner can only run an object") - val logger = new ScalaConsoleLogger(Level.Warn) + val logger = new ScalaConsoleLogger(Level.Error) val semantics0 = readSemantics() val semantics = if (optMode == FullOpt) semantics0.optimized else semantics0 diff --git a/sbt-plugin/src/main/scala/org/scalajs/sbtplugin/ScalaJSPlugin.scala b/sbt-plugin/src/main/scala/org/scalajs/sbtplugin/ScalaJSPlugin.scala index 0f6fa25c00..0a6ff26882 100644 --- a/sbt-plugin/src/main/scala/org/scalajs/sbtplugin/ScalaJSPlugin.scala +++ b/sbt-plugin/src/main/scala/org/scalajs/sbtplugin/ScalaJSPlugin.scala @@ -372,6 +372,21 @@ object ScalaJSPlugin extends AutoPlugin { scalaJSLoggerFactory := Loggers.sbtLogger2ToolsLogger _, + // Show a deprecation message if we load the build on JDK < 17 + onLoad := { + onLoad.value.andThen { state => + val fullVersion = System.getProperty("java.version") + val v = fullVersion.stripPrefix("1.").takeWhile(_.isDigit).toInt + if (v < 17) { + sLog.value.warn( + s"Using sbt-scalajs on JDK $v is deprecated. " + + "A future minor version will require at least JDK 17." + ) + } + state + } + }, + // Clear the IR cache stats every time a sequence of tasks ends onComplete := { val prev = onComplete.value From 21a2387ef7424bcedc0aefe04c64df00f6a27d1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Thu, 19 Feb 2026 16:20:59 +0100 Subject: [PATCH 04/32] Add LinkingInfo.moduleKind as a link-time property. This can be used for code that must adapt to the module kind in a way that would not link otherwise. --- .../src/main/scala/org/scalajs/ir/Trees.scala | 1 + .../scala/scala/scalajs/LinkingInfo.scala | 31 +++++++++++++++++++ .../linker/frontend/LinkTimeProperties.scala | 29 ++++++++++------- .../testsuite/library/LinkTimeIfTest.scala | 5 +++ .../testsuite/library/LinkingInfoTest.scala | 18 ++++++++++- 5 files changed, 71 insertions(+), 13 deletions(-) diff --git a/ir/shared/src/main/scala/org/scalajs/ir/Trees.scala b/ir/shared/src/main/scala/org/scalajs/ir/Trees.scala index 81b43b57cc..dd24231856 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/Trees.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/Trees.scala @@ -1319,6 +1319,7 @@ object Trees { final val ProductionMode = "core/productionMode" final val ESVersion = "core/esVersion" final val UseECMAScript2015Semantics = "core/useECMAScript2015Semantics" + final val ModuleKind = "core/moduleKind" final val IsWebAssembly = "core/isWebAssembly" final val LinkerVersion = "core/linkerVersion" } diff --git a/library/src/main/scala/scala/scalajs/LinkingInfo.scala b/library/src/main/scala/scala/scalajs/LinkingInfo.scala index 94c075ac98..6b72515f15 100644 --- a/library/src/main/scala/scala/scalajs/LinkingInfo.scala +++ b/library/src/main/scala/scala/scalajs/LinkingInfo.scala @@ -224,6 +224,11 @@ object LinkingInfo { def useECMAScript2015Semantics: Boolean = linkTimePropertyBoolean("core/useECMAScript2015Semantics") + /** Kind of module structure emitted for the Scala.js output. */ + @inline @linkTimeProperty("core/moduleKind") + def moduleKind: Int = + linkTimePropertyInt("core/moduleKind") + /** Whether we are linking to WebAssembly. * * This property can be used to delegate to different code paths optimized @@ -368,6 +373,32 @@ object LinkingInfo { final val ES2021 = 12 } + /** Constants for the value of `moduleKind`. */ + object ModuleKind { + + /** No module structure. + * + * With this module kind, exports are stored on the global object. + * + * Imports are not supported. + */ + final val NoModule = 1 + + /** An ECMAScript 2015 module. + * + * Scala.js imports and exports directly map to `import` and `export` + * clauses in the ES module. + */ + final val ESModule = 2 + + /** A CommonJS module (notably used by Node.js). + * + * Imported modules are fetched with `require`. Exports go to the `exports` + * module-global variable. + */ + final val CommonJSModule = 3 + } + private[scalajs] def linkTimePropertyInt(name: String): Int = throw new java.lang.Error("stub") diff --git a/linker/shared/src/main/scala/org/scalajs/linker/frontend/LinkTimeProperties.scala b/linker/shared/src/main/scala/org/scalajs/linker/frontend/LinkTimeProperties.scala index a6b44fc707..02fbdd76c4 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/frontend/LinkTimeProperties.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/frontend/LinkTimeProperties.scala @@ -16,27 +16,24 @@ import org.scalajs.ir.Trees.LinkTimeProperty._ import org.scalajs.ir.Types._ import org.scalajs.ir.ScalaJSVersions -import org.scalajs.linker.interface.{ESVersion => _, _} +import org.scalajs.linker.interface.{ESVersion => _, ModuleKind => _, _} import org.scalajs.linker.standard.CoreSpec final class LinkTimeProperties private ( semantics: Semantics, esFeatures: ESFeatures, + moduleKindInt: Int, targetIsWebAssembly: Boolean ) { import LinkTimeProperties._ private val linkTimeProperties: Map[String, LinkTimeValue] = Map( - ESVersion -> - LinkTimeInt(esFeatures.esVersion.edition), - UseECMAScript2015Semantics -> - LinkTimeBoolean(esFeatures.useECMAScript2015Semantics), - IsWebAssembly -> - LinkTimeBoolean(targetIsWebAssembly), - ProductionMode -> - LinkTimeBoolean(semantics.productionMode), - LinkerVersion -> - LinkTimeString(ScalaJSVersions.current) + ESVersion -> LinkTimeInt(esFeatures.esVersion.edition), + UseECMAScript2015Semantics -> LinkTimeBoolean(esFeatures.useECMAScript2015Semantics), + ModuleKind -> LinkTimeInt(moduleKindInt), + IsWebAssembly -> LinkTimeBoolean(targetIsWebAssembly), + ProductionMode -> LinkTimeBoolean(semantics.productionMode), + LinkerVersion -> LinkTimeString(ScalaJSVersions.current) ) def get(name: String): Option[LinkTimeValue] = @@ -56,7 +53,15 @@ object LinkTimeProperties { } def fromCoreSpec(coreSpec: CoreSpec): LinkTimeProperties = { + // These magic constants are mandated by the values in `scala.scalajs.LinkingInfo.ModuleKind`. + import org.scalajs.linker.interface.ModuleKind._ + val moduleKindInt = coreSpec.moduleKind match { + case NoModule => 1 + case ESModule => 2 + case CommonJSModule => 3 + } + new LinkTimeProperties(coreSpec.semantics, coreSpec.esFeatures, - coreSpec.targetIsWebAssembly) + moduleKindInt, coreSpec.targetIsWebAssembly) } } diff --git a/test-suite/js/src/test/scala/org/scalajs/testsuite/library/LinkTimeIfTest.scala b/test-suite/js/src/test/scala/org/scalajs/testsuite/library/LinkTimeIfTest.scala index d7e1771765..ab54554a32 100644 --- a/test-suite/js/src/test/scala/org/scalajs/testsuite/library/LinkTimeIfTest.scala +++ b/test-suite/js/src/test/scala/org/scalajs/testsuite/library/LinkTimeIfTest.scala @@ -50,6 +50,11 @@ class LinkTimeIfTest { val cond = !(Platform.assumedESVersion < ESVersion.ES2015) assertEquals(cond, linkTimeIf(!(esVersion < ESVersion.ES2015))(true)(false)) } + + locally { + val hasModules = !Platform.isNoModule + assertEquals(hasModules, linkTimeIf(moduleKind == ModuleKind.NoModule)(false)(true)) + } } @Test def linkTimeIfNested(): Unit = { diff --git a/test-suite/js/src/test/scala/org/scalajs/testsuite/library/LinkingInfoTest.scala b/test-suite/js/src/test/scala/org/scalajs/testsuite/library/LinkingInfoTest.scala index 50fc1fe8af..d724f5eaa3 100644 --- a/test-suite/js/src/test/scala/org/scalajs/testsuite/library/LinkingInfoTest.scala +++ b/test-suite/js/src/test/scala/org/scalajs/testsuite/library/LinkingInfoTest.scala @@ -13,7 +13,7 @@ package org.scalajs.testsuite.library import scala.scalajs.LinkingInfo -import scala.scalajs.LinkingInfo.ESVersion +import scala.scalajs.LinkingInfo.{ESVersion, ModuleKind} import org.junit.Assert._ import org.junit.Assume._ @@ -37,6 +37,15 @@ class LinkingInfoTest { @Test def useECMAScript2015Semantics(): Unit = assertEquals(Platform.useECMAScript2015Semantics, LinkingInfo.useECMAScript2015Semantics) + @Test def moduleKind(): Unit = { + if (Platform.isNoModule) + assertEquals(ModuleKind.NoModule, LinkingInfo.moduleKind) + else if (Platform.isESModule) + assertEquals(ModuleKind.ESModule, LinkingInfo.moduleKind) + else + assertEquals(ModuleKind.CommonJSModule, LinkingInfo.moduleKind) + } + @Test def isWebAssembly(): Unit = assertEquals(Platform.executingInWebAssembly, LinkingInfo.isWebAssembly) @@ -52,6 +61,13 @@ class LinkingInfoTest { assertEquals(12, ESVersion.ES2021) } + @Test def moduleKindConstants(): Unit = { + // The numeric values behind the constants should stay stable forever, so we test them. + assertEquals(1, ModuleKind.NoModule) + assertEquals(2, ModuleKind.ESModule) + assertEquals(3, ModuleKind.CommonJSModule) + } + @Test def isolatedJSLinkingInfo(): Unit = { val linkingInfo = scala.scalajs.runtime.linkingInfo assertEquals(Platform.isInProductionMode, linkingInfo.productionMode) From e133016f020bddab23f0279ebef663b3d35a5b57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Thu, 19 Feb 2026 16:38:09 +0100 Subject: [PATCH 05/32] Use `LinkingInfo.moduleKind` instead of config-dependent `ExportLoopback`. --- project/Build.scala | 6 +---- .../require-commonjs/ExportLoopback.scala | 25 ------------------- .../testsuite/jsinterop/ExportLoopback.scala | 22 ---------------- .../testsuite/jsinterop/ExportLoopback.scala | 22 ---------------- .../jsinterop/TopLevelExportsTest.scala | 21 ++++++++++++++-- 5 files changed, 20 insertions(+), 76 deletions(-) delete mode 100644 test-suite/js/src/test/require-commonjs/ExportLoopback.scala delete mode 100644 test-suite/js/src/test/require-esmodule/org/scalajs/testsuite/jsinterop/ExportLoopback.scala delete mode 100644 test-suite/js/src/test/require-no-modules/org/scalajs/testsuite/jsinterop/ExportLoopback.scala diff --git a/project/Build.scala b/project/Build.scala index 7c8cf70ab0..5d9fbf97b6 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -2287,16 +2287,12 @@ object Build { esVersion >= ESVersion.ES2017 && isWebAssembly) ::: includeIf(testDir / "require-modules", hasModules) ::: - includeIf(testDir / "require-no-modules", - !hasModules) ::: includeIf(testDir / "require-multi-modules", hasModules && !linkerConfig.closureCompiler && !isWebAssembly) ::: includeIf(testDir / "require-dynamic-import", moduleKind == ModuleKind.ESModule) ::: includeIf(testDir / "require-esmodule", - moduleKind == ModuleKind.ESModule) ::: - includeIf(testDir / "require-commonjs", - moduleKind == ModuleKind.CommonJSModule) + moduleKind == ModuleKind.ESModule) }, Test / unmanagedResourceDirectories ++= { diff --git a/test-suite/js/src/test/require-commonjs/ExportLoopback.scala b/test-suite/js/src/test/require-commonjs/ExportLoopback.scala deleted file mode 100644 index aeca2e8864..0000000000 --- a/test-suite/js/src/test/require-commonjs/ExportLoopback.scala +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Scala.js (https://www.scala-js.org/) - * - * Copyright EPFL. - * - * Licensed under Apache License 2.0 - * (https://www.apache.org/licenses/LICENSE-2.0). - * - * See the NOTICE file distributed with this work for - * additional information regarding copyright ownership. - */ - -package org.scalajs.testsuite.jsinterop - -import scala.scalajs.js - -import scala.concurrent.Future - -object ExportLoopback { - val exportsNamespace: Future[js.Dynamic] = { - js.Promise.resolve[Unit](()) - .`then`[js.Dynamic](_ => js.Dynamic.global.require("./main.js")) - .toFuture - } -} diff --git a/test-suite/js/src/test/require-esmodule/org/scalajs/testsuite/jsinterop/ExportLoopback.scala b/test-suite/js/src/test/require-esmodule/org/scalajs/testsuite/jsinterop/ExportLoopback.scala deleted file mode 100644 index b91a1bdbf8..0000000000 --- a/test-suite/js/src/test/require-esmodule/org/scalajs/testsuite/jsinterop/ExportLoopback.scala +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Scala.js (https://www.scala-js.org/) - * - * Copyright EPFL. - * - * Licensed under Apache License 2.0 - * (https://www.apache.org/licenses/LICENSE-2.0). - * - * See the NOTICE file distributed with this work for - * additional information regarding copyright ownership. - */ - -package org.scalajs.testsuite.jsinterop - -import scala.scalajs.js - -import scala.concurrent.Future - -object ExportLoopback { - val exportsNamespace: Future[js.Dynamic] = - js.`import`("./main.js").toFuture -} diff --git a/test-suite/js/src/test/require-no-modules/org/scalajs/testsuite/jsinterop/ExportLoopback.scala b/test-suite/js/src/test/require-no-modules/org/scalajs/testsuite/jsinterop/ExportLoopback.scala deleted file mode 100644 index 7a76610937..0000000000 --- a/test-suite/js/src/test/require-no-modules/org/scalajs/testsuite/jsinterop/ExportLoopback.scala +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Scala.js (https://www.scala-js.org/) - * - * Copyright EPFL. - * - * Licensed under Apache License 2.0 - * (https://www.apache.org/licenses/LICENSE-2.0). - * - * See the NOTICE file distributed with this work for - * additional information regarding copyright ownership. - */ - -package org.scalajs.testsuite.jsinterop - -import scala.scalajs.js - -import scala.concurrent.Future - -object ExportLoopback { - def exportsNamespace: Future[js.Dynamic] = - throw new AssertionError("attempted to get exportsNamsepace in NoModule mode") -} diff --git a/test-suite/js/src/test/scala/org/scalajs/testsuite/jsinterop/TopLevelExportsTest.scala b/test-suite/js/src/test/scala/org/scalajs/testsuite/jsinterop/TopLevelExportsTest.scala index 66ae05a678..41c9179d81 100644 --- a/test-suite/js/src/test/scala/org/scalajs/testsuite/jsinterop/TopLevelExportsTest.scala +++ b/test-suite/js/src/test/scala/org/scalajs/testsuite/jsinterop/TopLevelExportsTest.scala @@ -35,8 +35,25 @@ import org.scalajs.junit.async._ class TopLevelExportsTest { /** The namespace in which top-level exports are stored. */ - private lazy val exportsNamespace: Future[js.Dynamic] = - ExportLoopback.exportsNamespace + private lazy val exportsNamespace: Future[js.Dynamic] = { + import scala.scalajs.LinkingInfo._ + + linkTimeIf[Future[js.Dynamic]](moduleKind == ModuleKind.NoModule) { + throw new AssertionError("attempted to get exportsNamsepace in NoModule mode") + } { + linkTimeIf[Future[js.Dynamic]](moduleKind == ModuleKind.ESModule) { + js.`import`("./main.js").toFuture + } { + linkTimeIf[Future[js.Dynamic]](moduleKind == ModuleKind.CommonJSModule) { + js.Promise.resolve[Unit](()) + .`then`[js.Dynamic](_ => js.Dynamic.global.require("./main.js")) + .toFuture + } { + throw new AssertionError(s"unknown module kind: ${moduleKind}") + } + } + } + } def witnessOf(obj: Any): Any = { assertTrue("" + obj.getClass(), obj.isInstanceOf[WitnessInterface]) From 186650bc4d96e51123001802647a656111fdf55e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 4 Mar 2026 21:48:25 +0000 Subject: [PATCH 06/32] Bump @tootallnate/once and jsdom Removes [@tootallnate/once](https://github.com/TooTallNate/once). It's no longer used after updating ancestor dependency [jsdom](https://github.com/jsdom/jsdom). These dependencies need to be updated together. Removes `@tootallnate/once` Updates `jsdom` from 16.7.0 to 28.1.0 - [Release notes](https://github.com/jsdom/jsdom/releases) - [Changelog](https://github.com/jsdom/jsdom/blob/main/Changelog.md) - [Commits](https://github.com/jsdom/jsdom/compare/16.7.0...28.1.0) --- updated-dependencies: - dependency-name: "@tootallnate/once" dependency-version: dependency-type: indirect - dependency-name: jsdom dependency-version: 28.1.0 dependency-type: direct:development ... Signed-off-by: dependabot[bot] --- package-lock.json | 1578 +++++++++++++++++++-------------------------- package.json | 2 +- 2 files changed, 656 insertions(+), 924 deletions(-) diff --git a/package-lock.json b/package-lock.json index c2f0693ef2..c1ab759c5f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,116 +6,228 @@ "": { "devDependencies": { "express": "4.22.1", - "jsdom": "16.7.0", + "jsdom": "28.1.0", "jszip": "3.8.0", "source-map-support": "0.5.19" } }, - "node_modules/@tootallnate/once": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", - "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", + "node_modules/@acemir/cssom": { + "version": "0.9.31", + "resolved": "https://registry.npmjs.org/@acemir/cssom/-/cssom-0.9.31.tgz", + "integrity": "sha512-ZnR3GSaH+/vJ0YlHau21FjfLYjMpYVIzTD8M8vIEQvIGxeOXyXdzCI140rrCY862p/C/BbzWsjc1dgnM9mkoTA==", + "dev": true + }, + "node_modules/@asamuzakjp/css-color": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-5.0.1.tgz", + "integrity": "sha512-2SZFvqMyvboVV1d15lMf7XiI3m7SDqXUuKaTymJYLN6dSGadqp+fVojqJlVoMlbZnlTmu3S0TLwLTJpvBMO1Aw==", "dev": true, + "dependencies": { + "@csstools/css-calc": "^3.1.1", + "@csstools/css-color-parser": "^4.0.2", + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0", + "lru-cache": "^11.2.6" + }, "engines": { - "node": ">= 6" + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" } }, - "node_modules/abab": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", - "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==", + "node_modules/@asamuzakjp/dom-selector": { + "version": "6.8.1", + "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-6.8.1.tgz", + "integrity": "sha512-MvRz1nCqW0fsy8Qz4dnLIvhOlMzqDVBabZx6lH+YywFDdjXhMY37SmpV1XFX3JzG5GWHn63j6HX6QPr3lZXHvQ==", + "dev": true, + "dependencies": { + "@asamuzakjp/nwsapi": "^2.3.9", + "bidi-js": "^1.0.3", + "css-tree": "^3.1.0", + "is-potential-custom-element-name": "^1.0.1", + "lru-cache": "^11.2.6" + } + }, + "node_modules/@asamuzakjp/nwsapi": { + "version": "2.3.9", + "resolved": "https://registry.npmjs.org/@asamuzakjp/nwsapi/-/nwsapi-2.3.9.tgz", + "integrity": "sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==", "dev": true }, - "node_modules/accepts": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "node_modules/@bramus/specificity": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/@bramus/specificity/-/specificity-2.4.2.tgz", + "integrity": "sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw==", "dev": true, "dependencies": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" + "css-tree": "^3.0.0" }, - "engines": { - "node": ">= 0.6" + "bin": { + "specificity": "bin/cli.js" } }, - "node_modules/acorn": { - "version": "8.8.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", - "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", + "node_modules/@csstools/color-helpers": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-6.0.2.tgz", + "integrity": "sha512-LMGQLS9EuADloEFkcTBR3BwV/CGHV7zyDxVRtVDTwdI2Ca4it0CCVTT9wCkxSgokjE5Ho41hEPgb8OEUwoXr6Q==", "dev": true, - "bin": { - "acorn": "bin/acorn" - }, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], "engines": { - "node": ">=0.4.0" + "node": ">=20.19.0" } }, - "node_modules/acorn-globals": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-6.0.0.tgz", - "integrity": "sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg==", + "node_modules/@csstools/css-calc": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-3.1.1.tgz", + "integrity": "sha512-HJ26Z/vmsZQqs/o3a6bgKslXGFAungXGbinULZO3eMsOyNJHeBBZfup5FiZInOghgoM4Hwnmw+OgbJCNg1wwUQ==", "dev": true, - "dependencies": { - "acorn": "^7.1.1", - "acorn-walk": "^7.1.1" + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0" } }, - "node_modules/acorn-globals/node_modules/acorn": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", - "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "node_modules/@csstools/css-color-parser": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-4.0.2.tgz", + "integrity": "sha512-0GEfbBLmTFf0dJlpsNU7zwxRIH0/BGEMuXLTCvFYxuL1tNhqzTbtnFICyJLTNK4a+RechKP75e7w42ClXSnJQw==", "dev": true, - "bin": { - "acorn": "bin/acorn" + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "dependencies": { + "@csstools/color-helpers": "^6.0.2", + "@csstools/css-calc": "^3.1.1" }, "engines": { - "node": ">=0.4.0" + "node": ">=20.19.0" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0" } }, - "node_modules/acorn-walk": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", - "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==", + "node_modules/@csstools/css-parser-algorithms": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-4.0.0.tgz", + "integrity": "sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w==", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], "engines": { - "node": ">=0.4.0" + "node": ">=20.19.0" + }, + "peerDependencies": { + "@csstools/css-tokenizer": "^4.0.0" } }, - "node_modules/agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "node_modules/@csstools/css-syntax-patches-for-csstree": { + "version": "1.0.29", + "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.0.29.tgz", + "integrity": "sha512-jx9GjkkP5YHuTmko2eWAvpPnb0mB4mGRr2U7XwVNwevm8nlpobZEVk+GNmiYMk2VuA75v+plfXWyroWKmICZXg==", "dev": true, - "dependencies": { - "debug": "4" - }, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ] + }, + "node_modules/@csstools/css-tokenizer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-4.0.0.tgz", + "integrity": "sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], "engines": { - "node": ">= 6.0.0" + "node": ">=20.19.0" } }, - "node_modules/agent-base/node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "node_modules/@exodus/bytes": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/@exodus/bytes/-/bytes-1.15.0.tgz", + "integrity": "sha512-UY0nlA+feH81UGSHv92sLEPLCeZFjXOuHhrIo0HQydScuQc8s0A7kL/UdgwgDq8g8ilksmuoF35YVTNphV2aBQ==", "dev": true, - "dependencies": { - "ms": "2.1.2" - }, "engines": { - "node": ">=6.0" + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + }, + "peerDependencies": { + "@noble/hashes": "^1.8.0 || ^2.0.0" }, "peerDependenciesMeta": { - "supports-color": { + "@noble/hashes": { "optional": true } } }, - "node_modules/agent-base/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dev": true, + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "dev": true, + "engines": { + "node": ">= 14" + } }, "node_modules/array-flatten": { "version": "1.1.1", @@ -123,11 +235,14 @@ "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", "dev": true }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "dev": true + "node_modules/bidi-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz", + "integrity": "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==", + "dev": true, + "dependencies": { + "require-from-string": "^2.0.2" + } }, "node_modules/body-parser": { "version": "1.20.3", @@ -153,12 +268,6 @@ "npm": "1.2.8000 || >= 1.4.16" } }, - "node_modules/browser-process-hrtime": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", - "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==", - "dev": true - }, "node_modules/buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", @@ -203,18 +312,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, "node_modules/content-disposition": { "version": "0.5.4", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", @@ -277,42 +374,45 @@ "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", "dev": true }, - "node_modules/cssom": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.4.4.tgz", - "integrity": "sha512-p3pvU7r1MyyqbTk+WbNJIgJjG2VmTIaB10rI93LzVPrmDJKkzKYMtxxyAvQXR/NS6otuzveI7+7BBq3SjBS2mw==", - "dev": true + "node_modules/css-tree": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.1.0.tgz", + "integrity": "sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==", + "dev": true, + "dependencies": { + "mdn-data": "2.12.2", + "source-map-js": "^1.0.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + } }, "node_modules/cssstyle": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz", - "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==", + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-6.2.0.tgz", + "integrity": "sha512-Fm5NvhYathRnXNVndkUsCCuR63DCLVVwGOOwQw782coXFi5HhkXdu289l59HlXZBawsyNccXfWRYvLzcDCdDig==", "dev": true, "dependencies": { - "cssom": "~0.3.6" + "@asamuzakjp/css-color": "^5.0.1", + "@csstools/css-syntax-patches-for-csstree": "^1.0.28", + "css-tree": "^3.1.0", + "lru-cache": "^11.2.6" }, "engines": { - "node": ">=8" + "node": ">=20" } }, - "node_modules/cssstyle/node_modules/cssom": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", - "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==", - "dev": true - }, "node_modules/data-urls": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-2.0.0.tgz", - "integrity": "sha512-X5eWTSXO/BJmpdIKCRuKUgSCgAN0OwliVK3yPKbwIWU1Tdw5BRajxlzMidvh+gwko9AfQ9zIj52pzF91Q3YAvQ==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-7.0.0.tgz", + "integrity": "sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA==", "dev": true, "dependencies": { - "abab": "^2.0.3", - "whatwg-mimetype": "^2.3.0", - "whatwg-url": "^8.0.0" + "whatwg-mimetype": "^5.0.0", + "whatwg-url": "^16.0.0" }, "engines": { - "node": ">=10" + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" } }, "node_modules/debug": { @@ -325,26 +425,11 @@ } }, "node_modules/decimal.js": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz", - "integrity": "sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==", + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", + "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", "dev": true }, - "node_modules/deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true - }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "dev": true, - "engines": { - "node": ">=0.4.0" - } - }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -364,27 +449,6 @@ "npm": "1.2.8000 || >= 1.4.16" } }, - "node_modules/domexception": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/domexception/-/domexception-2.0.1.tgz", - "integrity": "sha512-yxJ2mFy/sibVQlu5qHjOkf9J3K6zgmCxgJ94u2EdvDOV09H+32LtRswEcUsmUWN72pVLOEnTSRaIVVzVQgS0dg==", - "dev": true, - "dependencies": { - "webidl-conversions": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/domexception/node_modules/webidl-conversions": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-5.0.0.tgz", - "integrity": "sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -414,6 +478,18 @@ "node": ">= 0.8" } }, + "node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "dev": true, + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/es-define-property": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", @@ -444,80 +520,12 @@ "node": ">= 0.4" } }, - "node_modules/es-set-tostringtag": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", - "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", - "dev": true, - "dependencies": { - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.6", - "has-tostringtag": "^1.0.2", - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, "node_modules/escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", "dev": true }, - "node_modules/escodegen": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.0.0.tgz", - "integrity": "sha512-mmHKys/C8BFUGI+MAWNcSYoORYLMdPzjrknd2Vc+bUsjN5bXcr8EhrNB+UTqfL1y3I9c4fw2ihgtMPQLBRiQxw==", - "dev": true, - "dependencies": { - "esprima": "^4.0.1", - "estraverse": "^5.2.0", - "esutils": "^2.0.2", - "optionator": "^0.8.1" - }, - "bin": { - "escodegen": "bin/escodegen.js", - "esgenerate": "bin/esgenerate.js" - }, - "engines": { - "node": ">=6.0" - }, - "optionalDependencies": { - "source-map": "~0.6.1" - } - }, - "node_modules/esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true, - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/etag": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", @@ -608,12 +616,6 @@ } ] }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true - }, "node_modules/finalhandler": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", @@ -632,22 +634,6 @@ "node": ">= 0.8" } }, - "node_modules/form-data": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.4.tgz", - "integrity": "sha512-f0cRzm6dkyVYV3nPoooP8XlccPQukegwhAnpoLcXy+X+A8KfpGOoXwDr9FLZd3wzgLaBGQBE3lY93Zm/i1JvIQ==", - "dev": true, - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "es-set-tostringtag": "^2.1.0", - "hasown": "^2.0.2", - "mime-types": "^2.1.35" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -736,21 +722,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/has-tostringtag": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", - "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", - "dev": true, - "dependencies": { - "has-symbols": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/hasown": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", @@ -764,15 +735,15 @@ } }, "node_modules/html-encoding-sniffer": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz", - "integrity": "sha512-D5JbOMBIR/TVZkubHT+OyT2705QvogUW4IBn6nHd756OwieSF9aDYFj4dv6HHEVGYbHaLETa3WggZYWWMyy3ZQ==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-6.0.0.tgz", + "integrity": "sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==", "dev": true, "dependencies": { - "whatwg-encoding": "^1.0.5" + "@exodus/bytes": "^1.6.0" }, "engines": { - "node": ">=10" + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" } }, "node_modules/http-errors": { @@ -792,26 +763,25 @@ } }, "node_modules/http-proxy-agent": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", - "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", "dev": true, "dependencies": { - "@tootallnate/once": "1", - "agent-base": "6", - "debug": "4" + "agent-base": "^7.1.0", + "debug": "^4.3.4" }, "engines": { - "node": ">= 6" + "node": ">= 14" } }, "node_modules/http-proxy-agent/node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "dev": true, "dependencies": { - "ms": "2.1.2" + "ms": "^2.1.3" }, "engines": { "node": ">=6.0" @@ -823,31 +793,31 @@ } }, "node_modules/http-proxy-agent/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true }, "node_modules/https-proxy-agent": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", - "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", "dev": true, "dependencies": { - "agent-base": "6", + "agent-base": "^7.1.2", "debug": "4" }, "engines": { - "node": ">= 6" + "node": ">= 14" } }, "node_modules/https-proxy-agent/node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "dev": true, "dependencies": { - "ms": "2.1.2" + "ms": "^2.1.3" }, "engines": { "node": ">=6.0" @@ -859,9 +829,9 @@ } }, "node_modules/https-proxy-agent/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true }, "node_modules/iconv-lite": { @@ -910,44 +880,38 @@ "dev": true }, "node_modules/jsdom": { - "version": "16.7.0", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-16.7.0.tgz", - "integrity": "sha512-u9Smc2G1USStM+s/x1ru5Sxrl6mPYCbByG1U/hUmqaVsm4tbNyS7CicOSRyuGQYZhTu0h84qkZZQ/I+dzizSVw==", - "dev": true, - "dependencies": { - "abab": "^2.0.5", - "acorn": "^8.2.4", - "acorn-globals": "^6.0.0", - "cssom": "^0.4.4", - "cssstyle": "^2.3.0", - "data-urls": "^2.0.0", - "decimal.js": "^10.2.1", - "domexception": "^2.0.1", - "escodegen": "^2.0.0", - "form-data": "^3.0.0", - "html-encoding-sniffer": "^2.0.1", - "http-proxy-agent": "^4.0.1", - "https-proxy-agent": "^5.0.0", + "version": "28.1.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-28.1.0.tgz", + "integrity": "sha512-0+MoQNYyr2rBHqO1xilltfDjV9G7ymYGlAUazgcDLQaUf8JDHbuGwsxN6U9qWaElZ4w1B2r7yEGIL3GdeW3Rug==", + "dev": true, + "dependencies": { + "@acemir/cssom": "^0.9.31", + "@asamuzakjp/dom-selector": "^6.8.1", + "@bramus/specificity": "^2.4.2", + "@exodus/bytes": "^1.11.0", + "cssstyle": "^6.0.1", + "data-urls": "^7.0.0", + "decimal.js": "^10.6.0", + "html-encoding-sniffer": "^6.0.0", + "http-proxy-agent": "^7.0.2", + "https-proxy-agent": "^7.0.6", "is-potential-custom-element-name": "^1.0.1", - "nwsapi": "^2.2.0", - "parse5": "6.0.1", - "saxes": "^5.0.1", + "parse5": "^8.0.0", + "saxes": "^6.0.0", "symbol-tree": "^3.2.4", - "tough-cookie": "^4.0.0", - "w3c-hr-time": "^1.0.2", - "w3c-xmlserializer": "^2.0.0", - "webidl-conversions": "^6.1.0", - "whatwg-encoding": "^1.0.5", - "whatwg-mimetype": "^2.3.0", - "whatwg-url": "^8.5.0", - "ws": "^7.4.6", - "xml-name-validator": "^3.0.0" + "tough-cookie": "^6.0.0", + "undici": "^7.21.0", + "w3c-xmlserializer": "^5.0.0", + "webidl-conversions": "^8.0.1", + "whatwg-mimetype": "^5.0.0", + "whatwg-url": "^16.0.0", + "xml-name-validator": "^5.0.0" }, "engines": { - "node": ">=10" + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" }, "peerDependencies": { - "canvas": "^2.5.0" + "canvas": "^3.0.0" }, "peerDependenciesMeta": { "canvas": { @@ -967,19 +931,6 @@ "set-immediate-shim": "~1.0.1" } }, - "node_modules/levn": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==", - "dev": true, - "dependencies": { - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, "node_modules/lie": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", @@ -989,11 +940,14 @@ "immediate": "~3.0.5" } }, - "node_modules/lodash": { - "version": "4.17.23", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", - "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", - "dev": true + "node_modules/lru-cache": { + "version": "11.2.6", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.6.tgz", + "integrity": "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ==", + "dev": true, + "engines": { + "node": "20 || >=22" + } }, "node_modules/math-intrinsics": { "version": "1.1.0", @@ -1004,6 +958,12 @@ "node": ">= 0.4" } }, + "node_modules/mdn-data": { + "version": "2.12.2", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.12.2.tgz", + "integrity": "sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==", + "dev": true + }, "node_modules/media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -1079,12 +1039,6 @@ "node": ">= 0.6" } }, - "node_modules/nwsapi": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.2.tgz", - "integrity": "sha512-90yv+6538zuvUMnN+zCr8LuV6bPFdq50304114vJYJ8RDyK8D5O9Phpbd6SZWgI7PwzmmfN1upeOJlvybDSgCw==", - "dev": true - }, "node_modules/object-inspect": { "version": "1.13.4", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", @@ -1109,23 +1063,6 @@ "node": ">= 0.8" } }, - "node_modules/optionator": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", - "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", - "dev": true, - "dependencies": { - "deep-is": "~0.1.3", - "fast-levenshtein": "~2.0.6", - "levn": "~0.3.0", - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2", - "word-wrap": "~1.2.3" - }, - "engines": { - "node": ">= 0.8.0" - } - }, "node_modules/pako": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", @@ -1133,10 +1070,16 @@ "dev": true }, "node_modules/parse5": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", - "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", - "dev": true + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.0.tgz", + "integrity": "sha512-9m4m5GSgXjL4AjumKzq1Fgfp3Z8rsvjRNbnkVwfu2ImRqE5D0LnY2QfDen18FSY9C573YU5XxSapdHZTZ2WolA==", + "dev": true, + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } }, "node_modules/parseurl": { "version": "1.3.3", @@ -1153,15 +1096,6 @@ "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", "dev": true }, - "node_modules/prelude-ls": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==", - "dev": true, - "engines": { - "node": ">= 0.8.0" - } - }, "node_modules/process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", @@ -1181,16 +1115,10 @@ "node": ">= 0.10" } }, - "node_modules/psl": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", - "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==", - "dev": true - }, "node_modules/punycode": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", - "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", "dev": true, "engines": { "node": ">=6" @@ -1211,12 +1139,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/querystringify": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", - "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", - "dev": true - }, "node_modules/range-parser": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", @@ -1256,11 +1178,14 @@ "util-deprecate": "~1.0.1" } }, - "node_modules/requires-port": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", - "dev": true + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } }, "node_modules/safe-buffer": { "version": "5.1.2", @@ -1275,15 +1200,15 @@ "dev": true }, "node_modules/saxes": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz", - "integrity": "sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", "dev": true, "dependencies": { "xmlchars": "^2.2.0" }, "engines": { - "node": ">=10" + "node": ">=v12.22.7" } }, "node_modules/send": { @@ -1436,6 +1361,15 @@ "node": ">=0.10.0" } }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/source-map-support": { "version": "0.5.19", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz", @@ -1461,13 +1395,31 @@ "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "dependencies": { - "safe-buffer": "~5.1.0" + "safe-buffer": "~5.1.0" + } + }, + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "dev": true + }, + "node_modules/tldts": { + "version": "7.0.24", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.0.24.tgz", + "integrity": "sha512-1r6vQTTt1rUiJkI5vX7KG8PR342Ru/5Oh13kEQP2SMbRSZpOey9SrBe27IDxkoWulx8ShWu4K6C0BkctP8Z1bQ==", + "dev": true, + "dependencies": { + "tldts-core": "^7.0.24" + }, + "bin": { + "tldts": "bin/cli.js" } }, - "node_modules/symbol-tree": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", - "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "node_modules/tldts-core": { + "version": "7.0.24", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.0.24.tgz", + "integrity": "sha512-pj7yygNMoMRqG7ML2SDQ0xNIOfN3IBDUcPVM2Sg6hP96oFNN2nqnzHreT3z9xLq85IWJyNTvD38O002DdOrPMw==", "dev": true }, "node_modules/toidentifier": { @@ -1480,42 +1432,27 @@ } }, "node_modules/tough-cookie": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz", - "integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.0.tgz", + "integrity": "sha512-kXuRi1mtaKMrsLUxz3sQYvVl37B0Ns6MzfrtV5DvJceE9bPyspOqk9xxv7XbZWcfLWbFmm997vl83qUWVJA64w==", "dev": true, "dependencies": { - "psl": "^1.1.33", - "punycode": "^2.1.1", - "universalify": "^0.2.0", - "url-parse": "^1.5.3" + "tldts": "^7.0.5" }, "engines": { - "node": ">=6" + "node": ">=16" } }, "node_modules/tr46": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-2.1.0.tgz", - "integrity": "sha512-15Ih7phfcdP5YxqiB+iDtLoaTz4Nd35+IiAv0kQ5FNKHzXgdWqPoTIqEDDJmXceQt4JZk6lVPT8lnDlPpGDppw==", - "dev": true, - "dependencies": { - "punycode": "^2.1.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/type-check": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", - "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-6.0.0.tgz", + "integrity": "sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==", "dev": true, "dependencies": { - "prelude-ls": "~1.1.2" + "punycode": "^2.3.1" }, "engines": { - "node": ">= 0.8.0" + "node": ">=20" } }, "node_modules/type-is": { @@ -1531,13 +1468,13 @@ "node": ">= 0.6" } }, - "node_modules/universalify": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", - "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", + "node_modules/undici": { + "version": "7.22.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.22.0.tgz", + "integrity": "sha512-RqslV2Us5BrllB+JeiZnK4peryVTndy9Dnqq62S3yYRRTj0tFQCwEniUy2167skdGOy3vqRzEvl1Dm4sV2ReDg==", "dev": true, "engines": { - "node": ">= 4.0.0" + "node": ">=20.18.1" } }, "node_modules/unpipe": { @@ -1549,16 +1486,6 @@ "node": ">= 0.8" } }, - "node_modules/url-parse": { - "version": "1.5.10", - "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", - "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", - "dev": true, - "dependencies": { - "querystringify": "^2.1.1", - "requires-port": "^1.0.0" - } - }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -1583,102 +1510,59 @@ "node": ">= 0.8" } }, - "node_modules/w3c-hr-time": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", - "integrity": "sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==", - "deprecated": "Use your platform's native performance.now() and performance.timeOrigin.", - "dev": true, - "dependencies": { - "browser-process-hrtime": "^1.0.0" - } - }, "node_modules/w3c-xmlserializer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-2.0.0.tgz", - "integrity": "sha512-4tzD0mF8iSiMiNs30BiLO3EpfGLZUT2MSX/G+o7ZywDzliWQ3OPtTZ0PTC3B3ca1UAf4cJMHB+2Bf56EriJuRA==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", + "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", "dev": true, "dependencies": { - "xml-name-validator": "^3.0.0" + "xml-name-validator": "^5.0.0" }, "engines": { - "node": ">=10" + "node": ">=18" } }, "node_modules/webidl-conversions": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-6.1.0.tgz", - "integrity": "sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.1.tgz", + "integrity": "sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==", "dev": true, "engines": { - "node": ">=10.4" + "node": ">=20" } }, - "node_modules/whatwg-encoding": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz", - "integrity": "sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==", + "node_modules/whatwg-mimetype": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-5.0.0.tgz", + "integrity": "sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==", "dev": true, - "dependencies": { - "iconv-lite": "0.4.24" + "engines": { + "node": ">=20" } }, - "node_modules/whatwg-mimetype": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz", - "integrity": "sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==", - "dev": true - }, "node_modules/whatwg-url": { - "version": "8.7.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-8.7.0.tgz", - "integrity": "sha512-gAojqb/m9Q8a5IV96E3fHJM70AzCkgt4uXYX2O7EmuyOnLrViCQlsEBmF9UQIu3/aeAIp2U17rtbpZWNntQqdg==", + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-16.0.1.tgz", + "integrity": "sha512-1to4zXBxmXHV3IiSSEInrreIlu02vUOvrhxJJH5vcxYTBDAx51cqZiKdyTxlecdKNSjj8EcxGBxNf6Vg+945gw==", "dev": true, "dependencies": { - "lodash": "^4.7.0", - "tr46": "^2.1.0", - "webidl-conversions": "^6.1.0" + "@exodus/bytes": "^1.11.0", + "tr46": "^6.0.0", + "webidl-conversions": "^8.0.1" }, "engines": { - "node": ">=10" - } - }, - "node_modules/word-wrap": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.4.tgz", - "integrity": "sha512-2V81OA4ugVo5pRo46hAoD2ivUJx8jXmWXfUkY4KFNw0hEptvN0QfH3K4nHiwzGeKl5rFKedV48QVoqYavy4YpA==", - "dev": true, - "engines": { - "node": ">=0.10.0" + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" } }, - "node_modules/ws": { - "version": "7.5.10", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", - "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", + "node_modules/xml-name-validator": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", + "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", "dev": true, "engines": { - "node": ">=8.3.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } + "node": ">=18" } }, - "node_modules/xml-name-validator": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz", - "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==", - "dev": true - }, "node_modules/xmlchars": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", @@ -1687,95 +1571,132 @@ } }, "dependencies": { - "@tootallnate/once": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", - "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", + "@acemir/cssom": { + "version": "0.9.31", + "resolved": "https://registry.npmjs.org/@acemir/cssom/-/cssom-0.9.31.tgz", + "integrity": "sha512-ZnR3GSaH+/vJ0YlHau21FjfLYjMpYVIzTD8M8vIEQvIGxeOXyXdzCI140rrCY862p/C/BbzWsjc1dgnM9mkoTA==", "dev": true }, - "abab": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", - "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==", + "@asamuzakjp/css-color": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-5.0.1.tgz", + "integrity": "sha512-2SZFvqMyvboVV1d15lMf7XiI3m7SDqXUuKaTymJYLN6dSGadqp+fVojqJlVoMlbZnlTmu3S0TLwLTJpvBMO1Aw==", + "dev": true, + "requires": { + "@csstools/css-calc": "^3.1.1", + "@csstools/css-color-parser": "^4.0.2", + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0", + "lru-cache": "^11.2.6" + } + }, + "@asamuzakjp/dom-selector": { + "version": "6.8.1", + "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-6.8.1.tgz", + "integrity": "sha512-MvRz1nCqW0fsy8Qz4dnLIvhOlMzqDVBabZx6lH+YywFDdjXhMY37SmpV1XFX3JzG5GWHn63j6HX6QPr3lZXHvQ==", + "dev": true, + "requires": { + "@asamuzakjp/nwsapi": "^2.3.9", + "bidi-js": "^1.0.3", + "css-tree": "^3.1.0", + "is-potential-custom-element-name": "^1.0.1", + "lru-cache": "^11.2.6" + } + }, + "@asamuzakjp/nwsapi": { + "version": "2.3.9", + "resolved": "https://registry.npmjs.org/@asamuzakjp/nwsapi/-/nwsapi-2.3.9.tgz", + "integrity": "sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==", "dev": true }, - "accepts": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "@bramus/specificity": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/@bramus/specificity/-/specificity-2.4.2.tgz", + "integrity": "sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw==", "dev": true, "requires": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" + "css-tree": "^3.0.0" } }, - "acorn": { - "version": "8.8.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", - "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", + "@csstools/color-helpers": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-6.0.2.tgz", + "integrity": "sha512-LMGQLS9EuADloEFkcTBR3BwV/CGHV7zyDxVRtVDTwdI2Ca4it0CCVTT9wCkxSgokjE5Ho41hEPgb8OEUwoXr6Q==", "dev": true }, - "acorn-globals": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-6.0.0.tgz", - "integrity": "sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg==", + "@csstools/css-calc": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-3.1.1.tgz", + "integrity": "sha512-HJ26Z/vmsZQqs/o3a6bgKslXGFAungXGbinULZO3eMsOyNJHeBBZfup5FiZInOghgoM4Hwnmw+OgbJCNg1wwUQ==", + "dev": true, + "requires": {} + }, + "@csstools/css-color-parser": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-4.0.2.tgz", + "integrity": "sha512-0GEfbBLmTFf0dJlpsNU7zwxRIH0/BGEMuXLTCvFYxuL1tNhqzTbtnFICyJLTNK4a+RechKP75e7w42ClXSnJQw==", "dev": true, "requires": { - "acorn": "^7.1.1", - "acorn-walk": "^7.1.1" - }, - "dependencies": { - "acorn": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", - "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", - "dev": true - } + "@csstools/color-helpers": "^6.0.2", + "@csstools/css-calc": "^3.1.1" } }, - "acorn-walk": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", - "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==", + "@csstools/css-parser-algorithms": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-4.0.0.tgz", + "integrity": "sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w==", + "dev": true, + "requires": {} + }, + "@csstools/css-syntax-patches-for-csstree": { + "version": "1.0.29", + "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.0.29.tgz", + "integrity": "sha512-jx9GjkkP5YHuTmko2eWAvpPnb0mB4mGRr2U7XwVNwevm8nlpobZEVk+GNmiYMk2VuA75v+plfXWyroWKmICZXg==", "dev": true }, - "agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "@csstools/css-tokenizer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-4.0.0.tgz", + "integrity": "sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==", + "dev": true + }, + "@exodus/bytes": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/@exodus/bytes/-/bytes-1.15.0.tgz", + "integrity": "sha512-UY0nlA+feH81UGSHv92sLEPLCeZFjXOuHhrIo0HQydScuQc8s0A7kL/UdgwgDq8g8ilksmuoF35YVTNphV2aBQ==", + "dev": true, + "requires": {} + }, + "accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", "dev": true, "requires": { - "debug": "4" - }, - "dependencies": { - "debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "requires": { - "ms": "2.1.2" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - } + "mime-types": "~2.1.34", + "negotiator": "0.6.3" } }, + "agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "dev": true + }, "array-flatten": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", "dev": true }, - "asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "dev": true + "bidi-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz", + "integrity": "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==", + "dev": true, + "requires": { + "require-from-string": "^2.0.2" + } }, "body-parser": { "version": "1.20.3", @@ -1797,12 +1718,6 @@ "unpipe": "1.0.0" } }, - "browser-process-hrtime": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", - "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==", - "dev": true - }, "buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", @@ -1835,15 +1750,6 @@ "get-intrinsic": "^1.3.0" } }, - "combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, - "requires": { - "delayed-stream": "~1.0.0" - } - }, "content-disposition": { "version": "0.5.4", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", @@ -1885,38 +1791,36 @@ "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", "dev": true }, - "cssom": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.4.4.tgz", - "integrity": "sha512-p3pvU7r1MyyqbTk+WbNJIgJjG2VmTIaB10rI93LzVPrmDJKkzKYMtxxyAvQXR/NS6otuzveI7+7BBq3SjBS2mw==", - "dev": true + "css-tree": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.1.0.tgz", + "integrity": "sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==", + "dev": true, + "requires": { + "mdn-data": "2.12.2", + "source-map-js": "^1.0.1" + } }, "cssstyle": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz", - "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==", + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-6.2.0.tgz", + "integrity": "sha512-Fm5NvhYathRnXNVndkUsCCuR63DCLVVwGOOwQw782coXFi5HhkXdu289l59HlXZBawsyNccXfWRYvLzcDCdDig==", "dev": true, "requires": { - "cssom": "~0.3.6" - }, - "dependencies": { - "cssom": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", - "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==", - "dev": true - } + "@asamuzakjp/css-color": "^5.0.1", + "@csstools/css-syntax-patches-for-csstree": "^1.0.28", + "css-tree": "^3.1.0", + "lru-cache": "^11.2.6" } }, "data-urls": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-2.0.0.tgz", - "integrity": "sha512-X5eWTSXO/BJmpdIKCRuKUgSCgAN0OwliVK3yPKbwIWU1Tdw5BRajxlzMidvh+gwko9AfQ9zIj52pzF91Q3YAvQ==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-7.0.0.tgz", + "integrity": "sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA==", "dev": true, "requires": { - "abab": "^2.0.3", - "whatwg-mimetype": "^2.3.0", - "whatwg-url": "^8.0.0" + "whatwg-mimetype": "^5.0.0", + "whatwg-url": "^16.0.0" } }, "debug": { @@ -1929,21 +1833,9 @@ } }, "decimal.js": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz", - "integrity": "sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==", - "dev": true - }, - "deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true - }, - "delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", + "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", "dev": true }, "depd": { @@ -1958,23 +1850,6 @@ "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", "dev": true }, - "domexception": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/domexception/-/domexception-2.0.1.tgz", - "integrity": "sha512-yxJ2mFy/sibVQlu5qHjOkf9J3K6zgmCxgJ94u2EdvDOV09H+32LtRswEcUsmUWN72pVLOEnTSRaIVVzVQgS0dg==", - "dev": true, - "requires": { - "webidl-conversions": "^5.0.0" - }, - "dependencies": { - "webidl-conversions": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-5.0.0.tgz", - "integrity": "sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA==", - "dev": true - } - } - }, "dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -1998,6 +1873,12 @@ "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", "dev": true }, + "entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "dev": true + }, "es-define-property": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", @@ -2019,55 +1900,12 @@ "es-errors": "^1.3.0" } }, - "es-set-tostringtag": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", - "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", - "dev": true, - "requires": { - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.6", - "has-tostringtag": "^1.0.2", - "hasown": "^2.0.2" - } - }, "escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", "dev": true }, - "escodegen": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.0.0.tgz", - "integrity": "sha512-mmHKys/C8BFUGI+MAWNcSYoORYLMdPzjrknd2Vc+bUsjN5bXcr8EhrNB+UTqfL1y3I9c4fw2ihgtMPQLBRiQxw==", - "dev": true, - "requires": { - "esprima": "^4.0.1", - "estraverse": "^5.2.0", - "esutils": "^2.0.2", - "optionator": "^0.8.1", - "source-map": "~0.6.1" - } - }, - "esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true - }, - "estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true - }, - "esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true - }, "etag": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", @@ -2130,12 +1968,6 @@ } } }, - "fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true - }, "finalhandler": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", @@ -2151,19 +1983,6 @@ "unpipe": "~1.0.0" } }, - "form-data": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.4.tgz", - "integrity": "sha512-f0cRzm6dkyVYV3nPoooP8XlccPQukegwhAnpoLcXy+X+A8KfpGOoXwDr9FLZd3wzgLaBGQBE3lY93Zm/i1JvIQ==", - "dev": true, - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "es-set-tostringtag": "^2.1.0", - "hasown": "^2.0.2", - "mime-types": "^2.1.35" - } - }, "forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -2222,15 +2041,6 @@ "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", "dev": true }, - "has-tostringtag": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", - "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", - "dev": true, - "requires": { - "has-symbols": "^1.0.3" - } - }, "hasown": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", @@ -2241,12 +2051,12 @@ } }, "html-encoding-sniffer": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz", - "integrity": "sha512-D5JbOMBIR/TVZkubHT+OyT2705QvogUW4IBn6nHd756OwieSF9aDYFj4dv6HHEVGYbHaLETa3WggZYWWMyy3ZQ==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-6.0.0.tgz", + "integrity": "sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==", "dev": true, "requires": { - "whatwg-encoding": "^1.0.5" + "@exodus/bytes": "^1.6.0" } }, "http-errors": { @@ -2263,56 +2073,55 @@ } }, "http-proxy-agent": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", - "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", "dev": true, "requires": { - "@tootallnate/once": "1", - "agent-base": "6", - "debug": "4" + "agent-base": "^7.1.0", + "debug": "^4.3.4" }, "dependencies": { "debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "dev": true, "requires": { - "ms": "2.1.2" + "ms": "^2.1.3" } }, "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true } } }, "https-proxy-agent": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", - "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", "dev": true, "requires": { - "agent-base": "6", + "agent-base": "^7.1.2", "debug": "4" }, "dependencies": { "debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "dev": true, "requires": { - "ms": "2.1.2" + "ms": "^2.1.3" } }, "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true } } @@ -2357,38 +2166,32 @@ "dev": true }, "jsdom": { - "version": "16.7.0", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-16.7.0.tgz", - "integrity": "sha512-u9Smc2G1USStM+s/x1ru5Sxrl6mPYCbByG1U/hUmqaVsm4tbNyS7CicOSRyuGQYZhTu0h84qkZZQ/I+dzizSVw==", + "version": "28.1.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-28.1.0.tgz", + "integrity": "sha512-0+MoQNYyr2rBHqO1xilltfDjV9G7ymYGlAUazgcDLQaUf8JDHbuGwsxN6U9qWaElZ4w1B2r7yEGIL3GdeW3Rug==", "dev": true, "requires": { - "abab": "^2.0.5", - "acorn": "^8.2.4", - "acorn-globals": "^6.0.0", - "cssom": "^0.4.4", - "cssstyle": "^2.3.0", - "data-urls": "^2.0.0", - "decimal.js": "^10.2.1", - "domexception": "^2.0.1", - "escodegen": "^2.0.0", - "form-data": "^3.0.0", - "html-encoding-sniffer": "^2.0.1", - "http-proxy-agent": "^4.0.1", - "https-proxy-agent": "^5.0.0", + "@acemir/cssom": "^0.9.31", + "@asamuzakjp/dom-selector": "^6.8.1", + "@bramus/specificity": "^2.4.2", + "@exodus/bytes": "^1.11.0", + "cssstyle": "^6.0.1", + "data-urls": "^7.0.0", + "decimal.js": "^10.6.0", + "html-encoding-sniffer": "^6.0.0", + "http-proxy-agent": "^7.0.2", + "https-proxy-agent": "^7.0.6", "is-potential-custom-element-name": "^1.0.1", - "nwsapi": "^2.2.0", - "parse5": "6.0.1", - "saxes": "^5.0.1", + "parse5": "^8.0.0", + "saxes": "^6.0.0", "symbol-tree": "^3.2.4", - "tough-cookie": "^4.0.0", - "w3c-hr-time": "^1.0.2", - "w3c-xmlserializer": "^2.0.0", - "webidl-conversions": "^6.1.0", - "whatwg-encoding": "^1.0.5", - "whatwg-mimetype": "^2.3.0", - "whatwg-url": "^8.5.0", - "ws": "^7.4.6", - "xml-name-validator": "^3.0.0" + "tough-cookie": "^6.0.0", + "undici": "^7.21.0", + "w3c-xmlserializer": "^5.0.0", + "webidl-conversions": "^8.0.1", + "whatwg-mimetype": "^5.0.0", + "whatwg-url": "^16.0.0", + "xml-name-validator": "^5.0.0" } }, "jszip": { @@ -2403,16 +2206,6 @@ "set-immediate-shim": "~1.0.1" } }, - "levn": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==", - "dev": true, - "requires": { - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2" - } - }, "lie": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", @@ -2422,10 +2215,10 @@ "immediate": "~3.0.5" } }, - "lodash": { - "version": "4.17.23", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", - "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", + "lru-cache": { + "version": "11.2.6", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.6.tgz", + "integrity": "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ==", "dev": true }, "math-intrinsics": { @@ -2434,6 +2227,12 @@ "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", "dev": true }, + "mdn-data": { + "version": "2.12.2", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.12.2.tgz", + "integrity": "sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==", + "dev": true + }, "media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -2485,12 +2284,6 @@ "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", "dev": true }, - "nwsapi": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.2.tgz", - "integrity": "sha512-90yv+6538zuvUMnN+zCr8LuV6bPFdq50304114vJYJ8RDyK8D5O9Phpbd6SZWgI7PwzmmfN1upeOJlvybDSgCw==", - "dev": true - }, "object-inspect": { "version": "1.13.4", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", @@ -2506,20 +2299,6 @@ "ee-first": "1.1.1" } }, - "optionator": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", - "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", - "dev": true, - "requires": { - "deep-is": "~0.1.3", - "fast-levenshtein": "~2.0.6", - "levn": "~0.3.0", - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2", - "word-wrap": "~1.2.3" - } - }, "pako": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", @@ -2527,10 +2306,13 @@ "dev": true }, "parse5": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", - "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", - "dev": true + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.0.tgz", + "integrity": "sha512-9m4m5GSgXjL4AjumKzq1Fgfp3Z8rsvjRNbnkVwfu2ImRqE5D0LnY2QfDen18FSY9C573YU5XxSapdHZTZ2WolA==", + "dev": true, + "requires": { + "entities": "^6.0.0" + } }, "parseurl": { "version": "1.3.3", @@ -2544,12 +2326,6 @@ "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", "dev": true }, - "prelude-ls": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==", - "dev": true - }, "process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", @@ -2566,16 +2342,10 @@ "ipaddr.js": "1.9.1" } }, - "psl": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", - "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==", - "dev": true - }, "punycode": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", - "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", "dev": true }, "qs": { @@ -2587,12 +2357,6 @@ "side-channel": "^1.0.6" } }, - "querystringify": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", - "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", - "dev": true - }, "range-parser": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", @@ -2626,10 +2390,10 @@ "util-deprecate": "~1.0.1" } }, - "requires-port": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", "dev": true }, "safe-buffer": { @@ -2645,9 +2409,9 @@ "dev": true }, "saxes": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz", - "integrity": "sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", "dev": true, "requires": { "xmlchars": "^2.2.0" @@ -2766,6 +2530,12 @@ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true }, + "source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true + }, "source-map-support": { "version": "0.5.19", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz", @@ -2797,6 +2567,21 @@ "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", "dev": true }, + "tldts": { + "version": "7.0.24", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.0.24.tgz", + "integrity": "sha512-1r6vQTTt1rUiJkI5vX7KG8PR342Ru/5Oh13kEQP2SMbRSZpOey9SrBe27IDxkoWulx8ShWu4K6C0BkctP8Z1bQ==", + "dev": true, + "requires": { + "tldts-core": "^7.0.24" + } + }, + "tldts-core": { + "version": "7.0.24", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.0.24.tgz", + "integrity": "sha512-pj7yygNMoMRqG7ML2SDQ0xNIOfN3IBDUcPVM2Sg6hP96oFNN2nqnzHreT3z9xLq85IWJyNTvD38O002DdOrPMw==", + "dev": true + }, "toidentifier": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", @@ -2804,33 +2589,21 @@ "dev": true }, "tough-cookie": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz", - "integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.0.tgz", + "integrity": "sha512-kXuRi1mtaKMrsLUxz3sQYvVl37B0Ns6MzfrtV5DvJceE9bPyspOqk9xxv7XbZWcfLWbFmm997vl83qUWVJA64w==", "dev": true, "requires": { - "psl": "^1.1.33", - "punycode": "^2.1.1", - "universalify": "^0.2.0", - "url-parse": "^1.5.3" + "tldts": "^7.0.5" } }, "tr46": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-2.1.0.tgz", - "integrity": "sha512-15Ih7phfcdP5YxqiB+iDtLoaTz4Nd35+IiAv0kQ5FNKHzXgdWqPoTIqEDDJmXceQt4JZk6lVPT8lnDlPpGDppw==", - "dev": true, - "requires": { - "punycode": "^2.1.1" - } - }, - "type-check": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", - "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-6.0.0.tgz", + "integrity": "sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==", "dev": true, "requires": { - "prelude-ls": "~1.1.2" + "punycode": "^2.3.1" } }, "type-is": { @@ -2843,10 +2616,10 @@ "mime-types": "~2.1.24" } }, - "universalify": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", - "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", + "undici": { + "version": "7.22.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.22.0.tgz", + "integrity": "sha512-RqslV2Us5BrllB+JeiZnK4peryVTndy9Dnqq62S3yYRRTj0tFQCwEniUy2167skdGOy3vqRzEvl1Dm4sV2ReDg==", "dev": true }, "unpipe": { @@ -2855,16 +2628,6 @@ "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", "dev": true }, - "url-parse": { - "version": "1.5.10", - "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", - "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", - "dev": true, - "requires": { - "querystringify": "^2.1.1", - "requires-port": "^1.0.0" - } - }, "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -2883,73 +2646,42 @@ "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", "dev": true }, - "w3c-hr-time": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", - "integrity": "sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==", - "dev": true, - "requires": { - "browser-process-hrtime": "^1.0.0" - } - }, "w3c-xmlserializer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-2.0.0.tgz", - "integrity": "sha512-4tzD0mF8iSiMiNs30BiLO3EpfGLZUT2MSX/G+o7ZywDzliWQ3OPtTZ0PTC3B3ca1UAf4cJMHB+2Bf56EriJuRA==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", + "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", "dev": true, "requires": { - "xml-name-validator": "^3.0.0" + "xml-name-validator": "^5.0.0" } }, "webidl-conversions": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-6.1.0.tgz", - "integrity": "sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.1.tgz", + "integrity": "sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==", "dev": true }, - "whatwg-encoding": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz", - "integrity": "sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==", - "dev": true, - "requires": { - "iconv-lite": "0.4.24" - } - }, "whatwg-mimetype": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz", - "integrity": "sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-5.0.0.tgz", + "integrity": "sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==", "dev": true }, "whatwg-url": { - "version": "8.7.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-8.7.0.tgz", - "integrity": "sha512-gAojqb/m9Q8a5IV96E3fHJM70AzCkgt4uXYX2O7EmuyOnLrViCQlsEBmF9UQIu3/aeAIp2U17rtbpZWNntQqdg==", + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-16.0.1.tgz", + "integrity": "sha512-1to4zXBxmXHV3IiSSEInrreIlu02vUOvrhxJJH5vcxYTBDAx51cqZiKdyTxlecdKNSjj8EcxGBxNf6Vg+945gw==", "dev": true, "requires": { - "lodash": "^4.7.0", - "tr46": "^2.1.0", - "webidl-conversions": "^6.1.0" + "@exodus/bytes": "^1.11.0", + "tr46": "^6.0.0", + "webidl-conversions": "^8.0.1" } }, - "word-wrap": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.4.tgz", - "integrity": "sha512-2V81OA4ugVo5pRo46hAoD2ivUJx8jXmWXfUkY4KFNw0hEptvN0QfH3K4nHiwzGeKl5rFKedV48QVoqYavy4YpA==", - "dev": true - }, - "ws": { - "version": "7.5.10", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", - "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", - "dev": true, - "requires": {} - }, "xml-name-validator": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz", - "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", + "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", "dev": true }, "xmlchars": { diff --git a/package.json b/package.json index 09c1bf4ac4..d3d1dc7b6a 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "private": true, "devDependencies": { "express": "4.22.1", - "jsdom": "16.7.0", + "jsdom": "28.1.0", "jszip": "3.8.0", "source-map-support": "0.5.19" } From 3ff16444469894817278a35b90e05cabeec1a7b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Sun, 8 Mar 2026 12:49:31 +0100 Subject: [PATCH 07/32] Fix #5331: Transform the body of a void typed closure as a statement. --- .../linker/frontend/optimizer/OptimizerCore.scala | 9 +++++---- .../scalajs/testsuite/compiler/RegressionTest.scala | 13 +++++++++++++ 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala b/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala index 055988ff18..428d5b685b 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala @@ -834,7 +834,7 @@ private[optimizer] abstract class OptimizerCore( .withLocalDefs(paramLocalDefs) .withLocalDefs(restParamLocalDef.toList) - transformCapturingBody(captureParams, tcaptureValues, body, innerEnv) { + transformCapturingBody(captureParams, tcaptureValues, resultType, body, innerEnv) { (newCaptureParams, newCaptureValues, newBody) => val newClosure = { Closure(flags, newCaptureParams, newParams, newRestParam, resultType, @@ -845,7 +845,7 @@ private[optimizer] abstract class OptimizerCore( } private def transformCapturingBody(captureParams: List[ParamDef], - tcaptureValues: List[PreTransform], body: Tree, innerEnv: OptEnv)( + tcaptureValues: List[PreTransform], resultType: Type, body: Tree, innerEnv: OptEnv)( inner: (List[ParamDef], List[Tree], Tree) => PreTransTree)(cont: PreTransCont)( implicit scope: Scope, pos: Position): TailRec[Tree] = { /* Process captures. @@ -913,7 +913,7 @@ private[optimizer] abstract class OptimizerCore( val innerScope = scope.withEnv(innerEnv.withLocalDefs(captureParamLocalDefs.result())) - val newBody = transformExpr(body)(innerScope) + val newBody = transform(body, isStat = resultType == VoidType)(innerScope) withNewLocalDefs(captureValueBindings.result()) { (localDefs, cont1) => val (finalCaptureParams, finalCaptureValues) = (for { @@ -2540,7 +2540,8 @@ private[optimizer] abstract class OptimizerCore( val methodDef = getMethodBody(targetMethod) - transformCapturingBody(methodDef.args, targs, methodDef.body.get, OptEnv.Empty) { + transformCapturingBody(methodDef.args, targs, AnyType, + methodDef.body.get, OptEnv.Empty) { (newCaptureParams, newCaptureValues, newBody) => if (!importReplacement.used.value.isUsed) cancelFun() diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/compiler/RegressionTest.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/compiler/RegressionTest.scala index defc7ba819..57a7314c48 100644 --- a/test-suite/shared/src/test/scala/org/scalajs/testsuite/compiler/RegressionTest.scala +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/compiler/RegressionTest.scala @@ -982,6 +982,19 @@ class RegressionTest { assertEquals("g", hide(b.bar)) } + @Test + def typedClosureWithStatementBodyMustBeTransformedAsStat_Issue5331(): Unit = { + final class Box(var x: Int) + + @noinline + def escapeAndCall(body: Runnable): Unit = body.run() + + val box = new Box(5) + val task: Runnable = () => box.x = 6 // SAM for Runnable + escapeAndCall(task) + assertEquals(6, box.x) + } + } object RegressionTest { From 218e64908e64566cf7d3029bf0f28f9a92955b30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Mon, 16 Feb 2026 14:22:30 +0100 Subject: [PATCH 08/32] Fix #5144: More direct hashing of method names for lambda class names. --- .../linker/frontend/LambdaSynthesizer.scala | 53 ++++++++--- .../frontend/LambdaSynthesizerTest.scala | 93 +++++++++++++++++++ 2 files changed, 135 insertions(+), 11 deletions(-) create mode 100644 linker/shared/src/test/scala/org/scalajs/linker/frontend/LambdaSynthesizerTest.scala diff --git a/linker/shared/src/main/scala/org/scalajs/linker/frontend/LambdaSynthesizer.scala b/linker/shared/src/main/scala/org/scalajs/linker/frontend/LambdaSynthesizer.scala index 92ff3d20a5..8029f00cfb 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/frontend/LambdaSynthesizer.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/frontend/LambdaSynthesizer.scala @@ -43,17 +43,7 @@ private[linker] object LambdaSynthesizer { descriptor.superClass } - val digestBuilder = new SHA1.DigestBuilder() - digestBuilder.updateUTF8String(descriptor.superClass.encoded) - for (intf <- descriptor.interfaces) - digestBuilder.updateUTF8String(intf.encoded) - - // FIXME This is not efficient - digestBuilder.updateUTF8String(UTF8String(descriptor.methodName.nameString)) - - // No need the hash the paramTypes and resultType because they derive from the method name - - val digest = digestBuilder.finalizeDigest() + val digest = hashDescriptor(descriptor) /* The "$$Lambda" segment is meant to match the way LambdaMetaFactory * names generated classes. This is mostly for test compatibility @@ -69,6 +59,47 @@ private[linker] object LambdaSynthesizer { ClassName(baseClassName.encoded ++ UTF8String(suffixBuilder.toString())) } + private def hashDescriptor(descriptor: NewLambda.Descriptor): Array[Byte] = { + val digestBuilder = new SHA1.DigestBuilder() + + def updateInt(x: Int): Unit = { + digestBuilder.update((x >>> 24).toByte) + digestBuilder.update((x >>> 16).toByte) + digestBuilder.update((x >>> 8).toByte) + digestBuilder.update(x.toByte) + } + + def updateTypeRef(typeRef: TypeRef): Unit = typeRef match { + case typeRef: PrimRef => + digestBuilder.update(typeRef.charCode.toByte) + case ClassRef(className) => + digestBuilder.update('L'.toByte) + digestBuilder.updateUTF8String(className.encoded) + case ArrayTypeRef(base, dimensions) => + digestBuilder.update('['.toByte) + updateTypeRef(base) + updateInt(dimensions) + case TransientTypeRef(name) => + digestBuilder.update('t'.toByte) + digestBuilder.updateUTF8String(name.encoded) + } + + digestBuilder.updateUTF8String(descriptor.superClass.encoded) + updateInt(descriptor.interfaces.size) + for (intf <- descriptor.interfaces) + digestBuilder.updateUTF8String(intf.encoded) + + val methodName = descriptor.methodName + digestBuilder.updateUTF8String(methodName.simpleName.encoded) + updateInt(methodName.paramTypeRefs.size) + methodName.paramTypeRefs.foreach(updateTypeRef(_)) + updateTypeRef(methodName.resultTypeRef) + + // No need to hash the paramTypes and resultType because they derive from the method name + + digestBuilder.finalizeDigest() + } + /** Computes the constructor name for the lambda class of a descriptor. */ def makeConstructorName(descriptor: NewLambda.Descriptor): MethodName = { val closureTypeNonNull = diff --git a/linker/shared/src/test/scala/org/scalajs/linker/frontend/LambdaSynthesizerTest.scala b/linker/shared/src/test/scala/org/scalajs/linker/frontend/LambdaSynthesizerTest.scala new file mode 100644 index 0000000000..6719eeb30f --- /dev/null +++ b/linker/shared/src/test/scala/org/scalajs/linker/frontend/LambdaSynthesizerTest.scala @@ -0,0 +1,93 @@ +/* + * Scala.js (https://www.scala-js.org/) + * + * Copyright EPFL. + * + * Licensed under Apache License 2.0 + * (https://www.apache.org/licenses/LICENSE-2.0). + * + * See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + */ + +package org.scalajs.linker.frontend + +import org.junit.Test +import org.junit.Assert._ + +import org.scalajs.ir.Names._ +import org.scalajs.ir.Trees.NewLambda.Descriptor +import org.scalajs.ir.Types._ +import org.scalajs.ir.WellKnownNames._ + +import org.scalajs.linker.testutils.TestIRBuilder._ + +class LambdaSynthesizerTest { + private def makeDesc(superClass: ClassName, interfaces: List[ClassName], + methodName: String, paramTypeRefs: List[TypeRef], resultTypeRef: TypeRef): Descriptor = { + + // Only for tests; would not work for JS class types + def typeRefToType(typeRef: TypeRef): Type = typeRef match { + case typeRef: PrimRef => typeRef.tpe + case ClassRef(className) => ClassType(className, nullable = true, exact = false) + case typeRef: ArrayTypeRef => ArrayType(typeRef, nullable = true, exact = false) + case typeRef: TransientTypeRef => typeRef.tpe + } + + Descriptor(superClass, interfaces, + MethodName(SimpleMethodName(methodName), paramTypeRefs, resultTypeRef), + paramTypeRefs.map(typeRefToType(_)), typeRefToType(resultTypeRef)) + } + + private def makeClassName(superClass: ClassName, interfaces: List[ClassName], + methodName: String, paramTypeRefs: List[TypeRef], resultTypeRef: TypeRef): String = { + val desc = makeDesc(superClass, interfaces, methodName, paramTypeRefs, resultTypeRef) + LambdaSynthesizer.makeClassName(desc).nameString + } + + @Test def testMakeClassNameBasicShape(): Unit = { + assertEquals( + "java.lang.Comparable.$$Lambda$fa13d0f5607243329b6dbf6698569d230ec3ead0", + makeClassName(ObjectClass, List("java.lang.Comparable"), "compareTo", List(O), I)) + + assertEquals( + "scala.runtime.AbstractFunction1.$$Lambda$7afc3dd0acc1681fb022ef921c83979087aaa919", + makeClassName("scala.runtime.AbstractFunction1", Nil, "apply", List(O), O)) + } + + @Test def testMakeClassNameEveryBitMatters(): Unit = { + val IClass = ClassRef("I") + val CClass = ClassRef("C") + + val descs: Vector[Descriptor] = Vector( + makeDesc(ObjectClass, List("I"), "foo", List(IClass), I), + makeDesc("A", List("I"), "foo", List(IClass), I), + makeDesc(ObjectClass, List("I"), "foo", List(IClass, CharRef), I), + makeDesc(ObjectClass, List("J"), "foo", List(IClass), I), + makeDesc(ObjectClass, List("I"), "bar", List(IClass), I), + makeDesc(ObjectClass, List("I"), "foo", List(CClass), I), + makeDesc(ObjectClass, List("I"), "foo", List(IClass), Z), + makeDesc(ObjectClass, List("I"), "foo", List(IClass), IClass), + makeDesc(ObjectClass, List("I"), "foo", List(CClass), IClass), + makeDesc(ObjectClass, List("I"), "foo", List(IClass), V), + makeDesc(ObjectClass, List("I"), "foo", List(IClass), ArrayTypeRef(I, 1)), + makeDesc(ObjectClass, List("I"), "foo", List(IClass), ArrayTypeRef(IClass, 1)), + makeDesc(ObjectClass, List("I"), "foo", List(IClass), ArrayTypeRef(I, 3)), + makeDesc(ObjectClass, List("I"), "foo", List(IClass), ArrayTypeRef(IClass, 3)), + makeDesc(ObjectClass, List("I"), "foo", List(IClass), TransientTypeRef(LabelName("I"))(IntType)) + ) + + val classNames = descs.map(LambdaSynthesizer.makeClassName(_)) + + for { + i <- 0 until descs.size + j <- i + 1 until descs.size + } { + if (classNames(i) == classNames(j)) { + fail( + "Two descriptors hashed to the same class name:\n" + + s"${descs(i)}\n${descs(j)}\n${classNames(i).nameString}") + } + } + } +} From 00b2d3f7a8e54fb501cef076bda9849585336e23 Mon Sep 17 00:00:00 2001 From: Rikito Taniguchi Date: Mon, 2 Mar 2026 23:51:14 +0900 Subject: [PATCH 09/32] Grow linear memory in malloc if required --- .../backend/wasmemitter/CoreWasmLib.scala | 36 ++++++++++++++++++- .../backend/webassembly/BinaryWriter.scala | 4 +++ .../backend/webassembly/Instructions.scala | 2 ++ .../backend/webassembly/TextWriter.scala | 4 +++ 4 files changed, 45 insertions(+), 1 deletion(-) diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/CoreWasmLib.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/CoreWasmLib.scala index 9e946dad1b..faec5326a1 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/CoreWasmLib.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/CoreWasmLib.scala @@ -973,6 +973,9 @@ final class CoreWasmLib(coreSpec: CoreSpec, globalInfo: LinkedGlobalInfo) { val alignment = 8 // max alignment? val base = fb.addLocal("base", Int32) + val requiredEnd = fb.addLocal("requiredEnd", Int32) + val currentMemEnd = fb.addLocal("currentMemEnd", Int32) + val pagesToGrow = fb.addLocal("pagesToGrow", Int32) fb += GlobalGet(genGlobalID.stackPointer) @@ -988,9 +991,40 @@ final class CoreWasmLib(coreSpec: CoreSpec, globalInfo: LinkedGlobalInfo) { fb += I32Mul fb += LocalTee(base) - fb += LocalGet(nbytes) // newPtr = stackPointer + nbytes + fb += LocalGet(nbytes) fb += I32Add + fb += LocalSet(requiredEnd) + // Grow linear memory on demand. + fb += MemorySize(genMemoryID.memory) + fb += I32Const(16) + fb += I32Shl // pages -> bytes + fb += LocalSet(currentMemEnd) + + fb += LocalGet(requiredEnd) + fb += LocalGet(currentMemEnd) + fb += I32GtU + fb.ifThen() { + // ceil((requiredEnd - currentMemEnd) / 65536) + fb += LocalGet(requiredEnd) + fb += LocalGet(currentMemEnd) + fb += I32Sub + fb += I32Const(65535) + fb += I32Add + fb += I32Const(65536) + fb += I32DivU + fb += LocalSet(pagesToGrow) + + fb += LocalGet(pagesToGrow) + fb += MemoryGrow(genMemoryID.memory) + fb += I32Const(-1) + fb += I32Eq + fb.ifThen() { + fb += Unreachable + } + } + + fb += LocalGet(requiredEnd) fb += GlobalSet(genGlobalID.stackPointer) fb += LocalGet(base) diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/webassembly/BinaryWriter.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/webassembly/BinaryWriter.scala index 23caf512cd..c60ee185b0 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/webassembly/BinaryWriter.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/webassembly/BinaryWriter.scala @@ -579,6 +579,10 @@ private sealed class BinaryWriter(module: Module, emitDebugInfo: Boolean) { case MemoryCopy(src, dst) => writeMemoryIdx(src) writeMemoryIdx(dst) + case MemorySize(memory) => + writeMemoryIdx(memory) + case MemoryGrow(memory) => + writeMemoryIdx(memory) case PositionMark(pos) => throw new AssertionError(s"Unexpected $instr") diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/webassembly/Instructions.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/webassembly/Instructions.scala index a8aa657774..f92ab32b24 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/webassembly/Instructions.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/webassembly/Instructions.scala @@ -228,6 +228,8 @@ object Instructions { case class I64tore8(arg: MemoryArg = MemoryArg()) extends LoadStoreInstr("i64.store8", 0x3c, arg) case class I64tore16(arg: MemoryArg = MemoryArg()) extends LoadStoreInstr("i64.store16", 0x3d, arg) case class I64tore32(arg: MemoryArg = MemoryArg()) extends LoadStoreInstr("i64.store32", 0x3e, arg) + final case class MemorySize(i: MemoryID) extends Instr("memory.size", 0x3f) + final case class MemoryGrow(i: MemoryID) extends Instr("memory.grow", 0x40) // Literals of primitive numeric types diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/webassembly/TextWriter.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/webassembly/TextWriter.scala index 4e96a9f72c..475631c51a 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/webassembly/TextWriter.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/webassembly/TextWriter.scala @@ -537,6 +537,10 @@ private class TextWriter(module: Module) { case MemoryCopy(src, dst) => appendName(src) appendName(dst) + case MemorySize(memory) => + appendName(memory) + case MemoryGrow(memory) => + appendName(memory) case PositionMark(_) => throw new AssertionError(s"Unexpected $instr") From 9dd12daa0b32ebba742d88b66a981e927df33d83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Thu, 12 Mar 2026 14:47:15 +0100 Subject: [PATCH 10/32] Fix the buffer growing logic in InputStream.readNBytes. The comparison was the wrong way around. On the first resize, we jumped straight to a buffer of size `min(Int.MaxValue, len)`. That was not too bad for `readNBytes` per se, but devastating for `readAllBytes`, which calls `readNBytes` with `len = Int.MaxValue`. --- javalib/src/main/scala/java/io/InputStream.scala | 2 +- .../scalajs/testsuite/javalib/io/InputStreamTestOnJDK11.scala | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/javalib/src/main/scala/java/io/InputStream.scala b/javalib/src/main/scala/java/io/InputStream.scala index 1d62ddb5f5..e494bf967e 100644 --- a/javalib/src/main/scala/java/io/InputStream.scala +++ b/javalib/src/main/scala/java/io/InputStream.scala @@ -75,7 +75,7 @@ abstract class InputStream extends Closeable { * - len <= Integer.MAX_VALUE (because of its type) */ val newLen = - if (Integer.MAX_VALUE / 2 > buf.length) Integer.MAX_VALUE + if (buf.length > Integer.MAX_VALUE / 2) Integer.MAX_VALUE else buf.length * 2 buf = Arrays.copyOf(buf, Math.min(len, newLen)) } diff --git a/test-suite/shared/src/test/require-jdk11/org/scalajs/testsuite/javalib/io/InputStreamTestOnJDK11.scala b/test-suite/shared/src/test/require-jdk11/org/scalajs/testsuite/javalib/io/InputStreamTestOnJDK11.scala index 3a588e79d2..94fb666f14 100644 --- a/test-suite/shared/src/test/require-jdk11/org/scalajs/testsuite/javalib/io/InputStreamTestOnJDK11.scala +++ b/test-suite/shared/src/test/require-jdk11/org/scalajs/testsuite/javalib/io/InputStreamTestOnJDK11.scala @@ -42,6 +42,7 @@ class InputStreamTestOnJDK11 { @Test def readAllBytes(): Unit = { assertBytesEqual(0 until 100, chunkedStream(10, 0 until 100).readAllBytes()) + assertBytesEqual(0 until 4000, chunkedStream(100, 0 until 4000).readAllBytes()) assertBytesEqual(Nil, emptyStream().readAllBytes()) } @@ -53,6 +54,7 @@ class InputStreamTestOnJDK11 { // test buffer growing assertBytesEqual(0 until 2000, chunkedStream(200, 0 until 2000).readNBytes(2000)) + assertBytesEqual(0 until 20000, chunkedStream(2000, 0 until 20000).readNBytes(20000)) assertThrows(classOf[IllegalArgumentException], emptyStream().readNBytes(-1)) } From 7c7d6d30ef2d02431b7524e36092e7d980c9d8ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Fri, 13 Mar 2026 16:19:42 +0100 Subject: [PATCH 11/32] Use dedicated ModuleKind's for Wasm without a JS environment. Instead of boolean configs in `WasmFeatures`. It makes more sense to use a `ModuleKind`, because it affects how the produced artifacts look like to the external world, which is exactly what a `ModuleKind` specifies. --- .github/workflows/ci.yml | 2 +- README.md | 2 +- .../scala/org/scalajs/ir/Serializers.scala | 8 +- .../src/main/scala/org/scalajs/ir/Trees.scala | 1 - .../src/main/scala/java/lang/Character.scala | 9 +- javalib/src/main/scala/java/lang/Double.scala | 9 +- javalib/src/main/scala/java/lang/Float.scala | 6 +- .../src/main/scala/java/lang/Integer.scala | 5 +- javalib/src/main/scala/java/lang/Math.scala | 39 +-- javalib/src/main/scala/java/lang/System.scala | 11 +- .../src/main/scala/java/lang/Throwables.scala | 14 +- .../src/main/scala/java/lang/_String.scala | 38 +-- .../src/main/scala/java/nio/ByteOrder.scala | 4 +- .../scala/java/nio/charset/CoderResult.scala | 16 +- .../src/main/scala/java/util/Formatter.scala | 4 +- .../main/scala/java/util/regex/Engine.scala | 4 +- .../main/scala/java/util/regex/Pattern.scala | 4 +- .../java/util/regex/PatternCompiler.scala | 13 +- .../scala/java/util/regex/RegExpImpl.scala | 7 +- .../src/main/scala/org/junit/Assert.scala | 15 +- .../scala/scala/scalajs/LinkingInfo.scala | 10 +- .../concurrent/QueueExecutionContext.scala | 5 +- .../scalajs/linker/interface/ModuleKind.scala | 23 +- .../org/scalajs/linker/interface/Report.scala | 10 +- .../linker/interface/WasmFeatures.scala | 26 +- .../scalajs/linker/analyzer/Analysis.scala | 2 +- .../scalajs/linker/analyzer/Analyzer.scala | 12 +- .../scalajs/linker/analyzer/InfoLoader.scala | 4 +- .../org/scalajs/linker/analyzer/Infos.scala | 18 +- .../backend/WebAssemblyLinkerBackend.scala | 7 +- .../backend/wasmemitter/ClassEmitter.scala | 50 ++-- .../backend/wasmemitter/CoreWasmLib.scala | 277 ++++++++++-------- .../backend/wasmemitter/DerivedClasses.scala | 4 +- .../linker/backend/wasmemitter/Emitter.scala | 13 +- .../backend/wasmemitter/FunctionEmitter.scala | 170 +++++------ .../backend/wasmemitter/LoaderContent.scala | 6 +- .../linker/backend/wasmemitter/SWasmGen.scala | 27 +- .../backend/wasmemitter/SpecialNames.scala | 1 - .../backend/wasmemitter/TypeTransformer.scala | 4 +- .../linker/backend/wasmemitter/VarGen.scala | 15 +- .../backend/wasmemitter/WasmContext.scala | 9 +- .../linker/frontend/LinkTimeProperties.scala | 11 +- .../frontend/optimizer/OptimizerCore.scala | 13 +- project/Build.scala | 69 +++-- project/JavalibIRCleaner.scala | 4 - .../sbtplugin/ScalaJSPluginInternal.scala | 6 +- .../scala/collection/mutable/Buffer.scala | 2 +- scalalib/overrides-2.13/scala/Symbol.scala | 9 +- .../scala/collection/mutable/Buffer.scala | 4 +- scalalib/overrides/scala/Symbol.scala | 9 +- .../scala/runtime/BoxesRunTime.scala | 5 +- .../org/scalajs/testing/bridge/Bridge.scala | 19 +- .../org/scalajs/testing/bridge/JSRPC.scala | 8 +- .../scalajs/testsuite/utils/BuildInfo.scala | 2 + .../scalajs/testsuite/utils/Platform.scala | 11 +- .../testsuite/library/LinkTimeIfTest.scala | 11 +- .../scala/scala/scalajs/LinkingInfo.scala | 7 +- .../testsuite/compiler/RegressionTest.scala | 4 +- .../testsuite/javalib/lang/LongTest.scala | 4 +- .../testsuite/javalib/lang/MathTest.scala | 10 +- .../testsuite/javalib/lang/ThreadTest.scala | 4 +- .../testsuite/javalib/util/BitSetTest.scala | 4 +- 62 files changed, 604 insertions(+), 516 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a0b4eb9bcb..9636d8bef6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -81,7 +81,7 @@ jobs: esVersion: [ES2015, ES2021] # Some javalib features depend on the target ES version eh-support: [true, false] env: - SBT_SET_COMMAND: 'set Seq(Global/enableWasmEverywhere := true, ThisBuild/scalaJSLinkerConfig ~= (_.withESFeatures(_.withESVersion(ESVersion.${{ matrix.esVersion }})).withWasmFeatures(_.withTargetPureWasm(true).withExceptionHandling(${{ matrix.eh-support }}))))' + SBT_SET_COMMAND: 'set Seq(Global/enableWasmEverywhere := true, ThisBuild/scalaJSLinkerConfig ~= (_.withESFeatures(_.withESVersion(ESVersion.${{ matrix.esVersion }})).withModuleKind(ModuleKind.MinimalWasmModule).withWasmFeatures(_.withExceptionHandling(${{ matrix.eh-support }}))))' steps: - uses: actions/checkout@v4 - uses: actions/setup-java@v4 diff --git a/README.md b/README.md index d0ed6fd58a..1fb3836265 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ This is a friendly fork of Scala.js, targeting stand-alone Wasm runtimes such as ### `test-suite` ```sh sbt:Scala.js> set Global/enableWasmEverywhere := true -sbt:Scala.js> set scalaJSLinkerConfig in testSuite.v2_12 ~= (_.withWasmFeatures(_.withTargetPureWasm(true))) +sbt:Scala.js> set scalaJSLinkerConfig in testSuite.v2_12 ~= (_.withModuleKind(ModuleKind.MinimalWasmModule)) sbt:Scala.js> testSuite2_12/test ``` diff --git a/ir/shared/src/main/scala/org/scalajs/ir/Serializers.scala b/ir/shared/src/main/scala/org/scalajs/ir/Serializers.scala index d632f06f95..b78f8c366e 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/Serializers.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/Serializers.scala @@ -30,8 +30,7 @@ import LinkTimeProperty.{ ESVersion, UseECMAScript2015Semantics, IsWebAssembly, - LinkerVersion, - TargetPureWasm + LinkerVersion } import Types._ import Tags._ @@ -1590,8 +1589,6 @@ object Serializers { LinkTimeProperty(LinkerVersion)(StringType) case StringLiteral("fileLevelThis") => JSGlobalRef(JSGlobalRef.FileLevelThis) - case StringLiteral("targetPureWasm") => - LinkTimeProperty(TargetPureWasm)(BooleanType) case otherItem => JSSelect(jsLinkingInfo, otherItem) } @@ -1628,8 +1625,7 @@ object Serializers { LinkTimeProperty(UseECMAScript2015Semantics)(BooleanType)), (StringLiteral("isWebAssembly"), LinkTimeProperty(IsWebAssembly)(BooleanType)), (StringLiteral("linkerVersion"), LinkTimeProperty(LinkerVersion)(StringType)), - (StringLiteral("fileLevelThis"), JSGlobalRef(JSGlobalRef.FileLevelThis)), - (StringLiteral("targetPureWasm"), LinkTimeProperty(TargetPureWasm)(BooleanType)) + (StringLiteral("fileLevelThis"), JSGlobalRef(JSGlobalRef.FileLevelThis)) )) } else { throw new IOException( diff --git a/ir/shared/src/main/scala/org/scalajs/ir/Trees.scala b/ir/shared/src/main/scala/org/scalajs/ir/Trees.scala index 7f9e45fbb7..440d8ec4b9 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/Trees.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/Trees.scala @@ -1327,7 +1327,6 @@ object Trees { final val ModuleKind = "core/moduleKind" final val IsWebAssembly = "core/isWebAssembly" final val LinkerVersion = "core/linkerVersion" - final val TargetPureWasm = "core/targetPureWasm" } // Atomic expressions diff --git a/javalib/src/main/scala/java/lang/Character.scala b/javalib/src/main/scala/java/lang/Character.scala index 4d040ac9dc..639a5f98ef 100644 --- a/javalib/src/main/scala/java/lang/Character.scala +++ b/javalib/src/main/scala/java/lang/Character.scala @@ -21,7 +21,8 @@ import scala.annotation.{tailrec, switch} import scala.scalajs.js import scala.scalajs.LinkingInfo -import scala.scalajs.LinkingInfo.ESVersion +import scala.scalajs.LinkingInfo.{ESVersion, moduleKind} +import scala.scalajs.LinkingInfo.ModuleKind.{MinimalWasmModule, WasmComponent} import java.lang.constant.Constable import java.util.{ArrayList, Arrays, HashMap} @@ -132,7 +133,7 @@ object Character { if (!isValidCodePoint(codePoint)) throw new IllegalArgumentException() - LinkingInfo.linkTimeIf(LinkingInfo.targetPureWasm) { + LinkingInfo.linkTimeIf(moduleKind == MinimalWasmModule || moduleKind == WasmComponent) { if (isBmpCodePoint(codePoint)) { Character.toString(codePoint.toChar) } else { @@ -723,7 +724,7 @@ object Character { case _ => // In WASI implementation, we cannot use String#toUpperCase // since it uses Character#toUpperCase. - LinkingInfo.linkTimeIf(LinkingInfo.targetPureWasm) { + LinkingInfo.linkTimeIf(moduleKind == MinimalWasmModule || moduleKind == WasmComponent) { import CaseUtil._ toCase(codePoint, a, z, lowerBeta, lowerRanges, lowerDeltas, lowerSteps) } { @@ -752,7 +753,7 @@ object Character { case 0x0130 => 0x0069 // İ => i case _ => - LinkingInfo.linkTimeIf(LinkingInfo.targetPureWasm) { + LinkingInfo.linkTimeIf(moduleKind == MinimalWasmModule || moduleKind == WasmComponent) { // in pure Wasm implementation, we cannot use String#toLowerCase // since it uses Character$toLowerCase import CaseUtil._ diff --git a/javalib/src/main/scala/java/lang/Double.scala b/javalib/src/main/scala/java/lang/Double.scala index b3713a667d..9483076671 100644 --- a/javalib/src/main/scala/java/lang/Double.scala +++ b/javalib/src/main/scala/java/lang/Double.scala @@ -17,7 +17,8 @@ import java.util.regex.RegExpImpl import scala.scalajs.js import scala.scalajs.LinkingInfo -import scala.scalajs.LinkingInfo.linkTimeIf +import scala.scalajs.LinkingInfo.{linkTimeIf, moduleKind} +import scala.scalajs.LinkingInfo.ModuleKind.{MinimalWasmModule, WasmComponent} import Utils._ @@ -127,7 +128,7 @@ object Double { import RegExpImpl.impl val groups = impl.exec(doubleStrPat, s) if (impl.matches(groups)) { - linkTimeIf(LinkingInfo.targetPureWasm) { + linkTimeIf(moduleKind == MinimalWasmModule || moduleKind == WasmComponent) { parseDoubleWasm(s, groups) } { js.Dynamic.global.parseFloat(impl.get(groups, 1)).asInstanceOf[scala.Double] @@ -289,7 +290,7 @@ object Double { @inline def nativeParseInt(s: String, radix: Int): scala.Double = js.Dynamic.global.parseInt(s, radix).asInstanceOf[scala.Double] - val mantissa = linkTimeIf(LinkingInfo.targetPureWasm) { + val mantissa = linkTimeIf(moduleKind == MinimalWasmModule || moduleKind == WasmComponent) { val mantissaLong = Long.parseUnsignedLong(truncatedMantissaStr, 16) // convert unsigned long to double (mantissaLong >>> 32).toDouble * (1L << 32) + (mantissaLong & 0xffffffffL).toDouble @@ -298,7 +299,7 @@ object Double { } // Assert: mantissa != 0.0 && mantissa != scala.Double.PositiveInfinity - val binaryExp = linkTimeIf(LinkingInfo.targetPureWasm) { + val binaryExp = linkTimeIf(moduleKind == MinimalWasmModule || moduleKind == WasmComponent) { if (binaryExpStr.length() > 11) { if (binaryExpStr.charAt(0) == '-') Int.MinValue else Int.MaxValue diff --git a/javalib/src/main/scala/java/lang/Float.scala b/javalib/src/main/scala/java/lang/Float.scala index 33cb1be8f1..ae0ee5281c 100644 --- a/javalib/src/main/scala/java/lang/Float.scala +++ b/javalib/src/main/scala/java/lang/Float.scala @@ -16,7 +16,9 @@ import java.lang.constant.{Constable, ConstantDesc} import java.util.regex.{Matcher, Pattern, RegExpImpl} import scala.scalajs.js -import scala.scalajs.LinkingInfo._ +import scala.scalajs.LinkingInfo +import scala.scalajs.LinkingInfo.{ESVersion, esVersion, linkTimeIf, moduleKind} +import scala.scalajs.LinkingInfo.ModuleKind.{MinimalWasmModule, WasmComponent} /* This is a hijacked class. Its instances are primitive numbers. * Constructors are not emitted. @@ -150,7 +152,7 @@ object Float { private def parseFloatDecimal(fullNumberStr: String, integralPartStr: String, fractionalPartStr: String, exponentStr: String): scala.Float = { - linkTimeIf[scala.Float](targetPureWasm) { + linkTimeIf[scala.Float](moduleKind == MinimalWasmModule || moduleKind == WasmComponent) { parseFloatDecimalWasm(fullNumberStr, integralPartStr, fractionalPartStr, exponentStr) } { parseFloatDecimalJS(fullNumberStr, integralPartStr, fractionalPartStr, exponentStr) diff --git a/javalib/src/main/scala/java/lang/Integer.scala b/javalib/src/main/scala/java/lang/Integer.scala index 791548f230..1742bb65a6 100644 --- a/javalib/src/main/scala/java/lang/Integer.scala +++ b/javalib/src/main/scala/java/lang/Integer.scala @@ -17,7 +17,8 @@ import java.util.function._ import scala.scalajs.js import scala.scalajs.LinkingInfo -import scala.scalajs.LinkingInfo.ESVersion +import scala.scalajs.LinkingInfo.{ESVersion, moduleKind} +import scala.scalajs.LinkingInfo.ModuleKind.{MinimalWasmModule, WasmComponent} /* This is a hijacked class. Its instances are primitive numbers. * Constructors are not emitted. @@ -339,7 +340,7 @@ object Integer { @inline def min(a: Int, b: Int): Int = Math.min(a, b) @inline private[this] def toStringBase(i: scala.Int, base: scala.Int): String = { - LinkingInfo.linkTimeIf(LinkingInfo.targetPureWasm) { + LinkingInfo.linkTimeIf(moduleKind == MinimalWasmModule || moduleKind == WasmComponent) { toStringWasmGenericImpl(i, base, false) } { import js.JSNumberOps.enableJSNumberOps diff --git a/javalib/src/main/scala/java/lang/Math.scala b/javalib/src/main/scala/java/lang/Math.scala index 15e80307e2..5be460731b 100644 --- a/javalib/src/main/scala/java/lang/Math.scala +++ b/javalib/src/main/scala/java/lang/Math.scala @@ -29,7 +29,8 @@ import scala.scalajs.js import js.Dynamic.{global => g} import scala.scalajs.LinkingInfo -import scala.scalajs.LinkingInfo.{ESVersion, linkTimeIf} +import scala.scalajs.LinkingInfo.{ESVersion, linkTimeIf, moduleKind} +import scala.scalajs.LinkingInfo.ModuleKind.{MinimalWasmModule, WasmComponent} object Math { final val E = 2.718281828459045 @@ -53,7 +54,7 @@ object Math { // Wasm intrinsics @inline def abs(a: scala.Float): scala.Float = { - linkTimeIf(LinkingInfo.targetPureWasm) { + linkTimeIf(LinkingInfo.isWebAssembly) { Float.intBitsToFloat(Float.floatToIntBits(a) & ~Int.MinValue) } { js.Math.abs(a).toFloat @@ -61,7 +62,7 @@ object Math { } @inline def abs(a: scala.Double): scala.Double = { - linkTimeIf(LinkingInfo.targetPureWasm) { + linkTimeIf(LinkingInfo.isWebAssembly) { Double.longBitsToDouble(Double.doubleToLongBits(a) & ~scala.Long.MinValue) } { js.Math.abs(a) @@ -73,7 +74,7 @@ object Math { // Wasm intrinsics @inline def max(a: scala.Float, b: scala.Float): scala.Float = { - linkTimeIf(LinkingInfo.targetPureWasm) { + linkTimeIf(moduleKind == MinimalWasmModule || moduleKind == WasmComponent) { if (a != a || b != b) { Float.NaN } else if (a == 0.0f && b == 0.0f) { @@ -90,7 +91,7 @@ object Math { } @inline def max(a: scala.Double, b: scala.Double): scala.Double = { - linkTimeIf(LinkingInfo.targetPureWasm) { + linkTimeIf(moduleKind == MinimalWasmModule || moduleKind == WasmComponent) { if (a != a || b != b) { Double.NaN } else if (a == 0.0 && b == 0.0) { @@ -111,7 +112,7 @@ object Math { // Wasm intrinsics @inline def min(a: scala.Float, b: scala.Float): scala.Float = { - linkTimeIf(LinkingInfo.targetPureWasm) { + linkTimeIf(moduleKind == MinimalWasmModule || moduleKind == WasmComponent) { if (a != a || b != b) { Float.NaN } else if (a == 0.0f && b == 0.0f) { @@ -128,7 +129,7 @@ object Math { } @inline def min(a: scala.Double, b: scala.Double): scala.Double = { - linkTimeIf(LinkingInfo.targetPureWasm) { + linkTimeIf(moduleKind == MinimalWasmModule || moduleKind == WasmComponent) { if (a != a || b != b) { Double.NaN } else if (a == 0.0 && b == 0.0) { @@ -189,7 +190,7 @@ object Math { // Wasm intrinsics @inline def ceil(a: scala.Double): scala.Double = { - linkTimeIf(LinkingInfo.targetPureWasm) { + linkTimeIf(moduleKind == MinimalWasmModule || moduleKind == WasmComponent) { -floor(-a) } { js.Math.ceil(a) @@ -197,7 +198,7 @@ object Math { } @inline def floor(a: scala.Double): scala.Double = { - linkTimeIf(LinkingInfo.targetPureWasm) { + linkTimeIf(moduleKind == MinimalWasmModule || moduleKind == WasmComponent) { floorWasm(a) } { js.Math.floor(a) @@ -278,7 +279,7 @@ object Math { } @inline def round(a: scala.Float): scala.Int = { - linkTimeIf(LinkingInfo.targetPureWasm) { + linkTimeIf(moduleKind == MinimalWasmModule || moduleKind == WasmComponent) { if (Float.isNaN(a)) { 0 } else if (a <= Int.MinValue.toFloat) { @@ -294,7 +295,7 @@ object Math { } @inline def round(a: scala.Double): scala.Long = { - linkTimeIf(LinkingInfo.targetPureWasm) { + linkTimeIf(moduleKind == MinimalWasmModule || moduleKind == WasmComponent) { if (Double.isNaN(a)) { 0L } else if (a <= scala.Long.MinValue.toDouble) { @@ -311,7 +312,7 @@ object Math { // Wasm intrinsic @inline def sqrt(a: scala.Double): scala.Double = { - linkTimeIf(LinkingInfo.targetPureWasm) { + linkTimeIf(moduleKind == MinimalWasmModule || moduleKind == WasmComponent) { StrictMath.sqrt(a) } { js.Math.sqrt(a) @@ -319,7 +320,7 @@ object Math { } @inline def pow(a: scala.Double, b: scala.Double): scala.Double = { - linkTimeIf(LinkingInfo.targetPureWasm) { + linkTimeIf(moduleKind == MinimalWasmModule || moduleKind == WasmComponent) { StrictMath.pow(a, b) } { js.Math.pow(a, b) @@ -329,7 +330,7 @@ object Math { @inline def exp(a: scala.Double): scala.Double = js.Math.exp(a) @inline def log(a: scala.Double): scala.Double = { - linkTimeIf(LinkingInfo.targetPureWasm) { + linkTimeIf(moduleKind == MinimalWasmModule || moduleKind == WasmComponent) { StrictMath.log(a) } { js.Math.log(a) @@ -337,7 +338,7 @@ object Math { } @inline def log10(a: scala.Double): scala.Double = { - linkTimeIf(LinkingInfo.targetPureWasm) { + linkTimeIf(moduleKind == MinimalWasmModule || moduleKind == WasmComponent) { StrictMath.log10(a) } { if (assumingES6 || !Utils.isUndefined(g.Math.log10)) @@ -348,7 +349,7 @@ object Math { } @inline def log1p(a: scala.Double): scala.Double = { - linkTimeIf(LinkingInfo.targetPureWasm) { + linkTimeIf(moduleKind == MinimalWasmModule || moduleKind == WasmComponent) { StrictMath.log1p(a) } { if (assumingES6 || !Utils.isUndefined(g.Math.log1p)) @@ -367,7 +368,7 @@ object Math { @inline def atan2(y: scala.Double, x: scala.Double): scala.Double = js.Math.atan2(y, x) @inline def random(): scala.Double = { - linkTimeIf(LinkingInfo.targetPureWasm) { + linkTimeIf(moduleKind == MinimalWasmModule || moduleKind == WasmComponent) { WasmSystem.random() } { js.Math.random() @@ -405,7 +406,7 @@ object Math { } def cbrt(a: scala.Double): scala.Double = { - linkTimeIf(LinkingInfo.targetPureWasm) { + linkTimeIf(moduleKind == MinimalWasmModule || moduleKind == WasmComponent) { cbrtImpl(a) } { if (assumingES6 || !Utils.isUndefined(g.Math.cbrt)) { @@ -676,7 +677,7 @@ object Math { } def hypot(a: scala.Double, b: scala.Double): scala.Double = { - linkTimeIf(LinkingInfo.targetPureWasm) { + linkTimeIf(moduleKind == MinimalWasmModule || moduleKind == WasmComponent) { hypotImpl(a, b) } { if (assumingES6 || !Utils.isUndefined(g.Math.hypot)) { diff --git a/javalib/src/main/scala/java/lang/System.scala b/javalib/src/main/scala/java/lang/System.scala index 4498e25cbb..a8627c0d83 100644 --- a/javalib/src/main/scala/java/lang/System.scala +++ b/javalib/src/main/scala/java/lang/System.scala @@ -17,6 +17,8 @@ import java.io._ import scala.scalajs.js import scala.scalajs.js.Dynamic.global import scala.scalajs.LinkingInfo +import scala.scalajs.LinkingInfo.{ESVersion, moduleKind} +import scala.scalajs.LinkingInfo.ModuleKind.{MinimalWasmModule, WasmComponent} import java.{util => ju} import java.util.function._ @@ -68,7 +70,7 @@ object System { @inline def currentTimeMillis(): scala.Long = { - LinkingInfo.linkTimeIf(LinkingInfo.targetPureWasm) { + LinkingInfo.linkTimeIf(moduleKind == MinimalWasmModule || moduleKind == WasmComponent) { WasmSystem.currentTimeMillis() } { js.Date.now().toLong @@ -86,7 +88,7 @@ object System { @inline def nanoTime(): scala.Long = { - LinkingInfo.linkTimeIf(LinkingInfo.targetPureWasm) { + LinkingInfo.linkTimeIf(moduleKind == MinimalWasmModule || moduleKind == WasmComponent) { WasmSystem.nanoTime() } { (NanoTime.highPrecisionTimer.now().asInstanceOf[scala.Double] * 1000000).toLong @@ -192,7 +194,8 @@ object System { private object SystemProperties { private val storageImpl: StorageImpl = { - LinkingInfo.linkTimeIf[StorageImpl](LinkingInfo.targetPureWasm) { + LinkingInfo.linkTimeIf[StorageImpl]( + moduleKind == MinimalWasmModule || moduleKind == WasmComponent) { StorageImpl.HashMapStorageImpl } { StorageImpl.DictStorageImpl @@ -460,7 +463,7 @@ private final class JSConsoleBasedPrintStream(isErr: scala.Boolean) override def close(): Unit = () private def doWriteLine(line: String): Unit = { - LinkingInfo.linkTimeIf(LinkingInfo.targetPureWasm) { + LinkingInfo.linkTimeIf(moduleKind == MinimalWasmModule || moduleKind == WasmComponent) { WasmSystem.print(line) } { import js.DynamicImplicits.truthValue diff --git a/javalib/src/main/scala/java/lang/Throwables.scala b/javalib/src/main/scala/java/lang/Throwables.scala index 316c4dcb1d..e10612d431 100644 --- a/javalib/src/main/scala/java/lang/Throwables.scala +++ b/javalib/src/main/scala/java/lang/Throwables.scala @@ -16,6 +16,8 @@ import java.util.function._ import scala.scalajs.js.annotation.JSExport import scala.scalajs.LinkingInfo +import scala.scalajs.LinkingInfo.moduleKind +import scala.scalajs.LinkingInfo.ModuleKind.{MinimalWasmModule, WasmComponent} class Throwable protected (s: String, private var e: Throwable, enableSuppression: scala.Boolean, writableStackTrace: scala.Boolean) @@ -47,17 +49,17 @@ class Throwable protected (s: String, private var e: Throwable, def getLocalizedMessage(): String = getMessage() def fillInStackTrace(): Throwable = { - LinkingInfo.linkTimeIf(!LinkingInfo.targetPureWasm) { - jsErrorForStackTrace = StackTrace.captureJSError(this) + LinkingInfo.linkTimeIf(moduleKind == MinimalWasmModule || moduleKind == WasmComponent) { this } { + jsErrorForStackTrace = StackTrace.captureJSError(this) this } } def getStackTrace(): Array[StackTraceElement] = { if (stackTrace eq null) { - LinkingInfo.linkTimeIf(LinkingInfo.targetPureWasm) { + LinkingInfo.linkTimeIf(moduleKind == MinimalWasmModule || moduleKind == WasmComponent) { stackTrace = new Array[StackTraceElement](0) } { if (writableStackTrace) @@ -70,7 +72,9 @@ class Throwable protected (s: String, private var e: Throwable, } def setStackTrace(stackTrace: Array[StackTraceElement]): Unit = { - LinkingInfo.linkTimeIf(!LinkingInfo.targetPureWasm) { + LinkingInfo.linkTimeIf(moduleKind == MinimalWasmModule || moduleKind == WasmComponent) { + // do nothing + } { if (writableStackTrace) { var i = 0 while (i < stackTrace.length) { @@ -81,7 +85,7 @@ class Throwable protected (s: String, private var e: Throwable, this.stackTrace = stackTrace.clone() } - } {} + } } def printStackTrace(): Unit = printStackTrace(System.err) diff --git a/javalib/src/main/scala/java/lang/_String.scala b/javalib/src/main/scala/java/lang/_String.scala index 932fc68228..170c706a9f 100644 --- a/javalib/src/main/scala/java/lang/_String.scala +++ b/javalib/src/main/scala/java/lang/_String.scala @@ -20,7 +20,8 @@ import scala.scalajs.js import scala.scalajs.js.annotation._ import scala.scalajs.js.JSStringOps.enableJSStringOps import scala.scalajs.LinkingInfo -import scala.scalajs.LinkingInfo.ESVersion +import scala.scalajs.LinkingInfo.{ESVersion, moduleKind} +import scala.scalajs.LinkingInfo.ModuleKind.{MinimalWasmModule, WasmComponent} import java.lang.constant.{Constable, ConstantDesc} import java.nio.ByteBuffer @@ -59,7 +60,7 @@ final class _String private () // scalastyle:ignore // Wasm intrinsic def codePointAt(index: Int): Int = { - LinkingInfo.linkTimeIf(LinkingInfo.targetPureWasm) { + LinkingInfo.linkTimeIf(moduleKind == MinimalWasmModule || moduleKind == WasmComponent) { Character.codePointAtImpl(this, index) } { if (LinkingInfo.esVersion >= ESVersion.ES2015) { @@ -201,7 +202,7 @@ final class _String private () // scalastyle:ignore @inline def endsWith(suffix: String): scala.Boolean = { - LinkingInfo.linkTimeIf(LinkingInfo.targetPureWasm) { + LinkingInfo.linkTimeIf(moduleKind == MinimalWasmModule || moduleKind == WasmComponent) { regionMatches(thisString.length() - suffix.length, suffix, 0, suffix.length) } { if (LinkingInfo.esVersion >= ESVersion.ES2015) { @@ -246,7 +247,7 @@ final class _String private () // scalastyle:ignore indexOf(Character.toString(ch), fromIndex) def indexOf(str: String): Int = { - LinkingInfo.linkTimeIf(LinkingInfo.targetPureWasm) { + LinkingInfo.linkTimeIf(moduleKind == MinimalWasmModule || moduleKind == WasmComponent) { indexOf(str, 0) } { thisString.jsIndexOf(str) @@ -254,7 +255,7 @@ final class _String private () // scalastyle:ignore } def indexOf(str: String, fromIndex: Int): Int = { - LinkingInfo.linkTimeIf(LinkingInfo.targetPureWasm) { + LinkingInfo.linkTimeIf(moduleKind == MinimalWasmModule || moduleKind == WasmComponent) { val thisLen = thisString.length() val strLen = str.length() @@ -303,7 +304,7 @@ final class _String private () // scalastyle:ignore @inline def lastIndexOf(str: String): Int = { - LinkingInfo.linkTimeIf(LinkingInfo.targetPureWasm) { + LinkingInfo.linkTimeIf(moduleKind == MinimalWasmModule || moduleKind == WasmComponent) { val thisLen = thisString.length() lastIndexOf(str, thisLen) } { @@ -316,7 +317,7 @@ final class _String private () // scalastyle:ignore def lastIndexOf(str: String, fromIndex: Int): Int = { if (fromIndex < 0) -1 else { - LinkingInfo.linkTimeIf(LinkingInfo.targetPureWasm) { + LinkingInfo.linkTimeIf(moduleKind == MinimalWasmModule || moduleKind == WasmComponent) { val thisLen = thisString.length() val strLen = str.length() @@ -390,7 +391,8 @@ final class _String private () // scalastyle:ignore throw new IllegalArgumentException } else { LinkingInfo.linkTimeIf( - LinkingInfo.esVersion >= ESVersion.ES2015 && !LinkingInfo.targetPureWasm) { + LinkingInfo.esVersion >= ESVersion.ES2015 && + moduleKind != MinimalWasmModule && moduleKind != WasmComponent) { /* This will throw a `js.RangeError` if `count` is too large, instead of * an `OutOfMemoryError`. That's fine because the behavior of `repeat` is * not specified for `count` too large. @@ -409,7 +411,7 @@ final class _String private () // scalastyle:ignore str += str remainingIters -= 1 } - LinkingInfo.linkTimeIf(LinkingInfo.targetPureWasm) { + LinkingInfo.linkTimeIf(moduleKind == MinimalWasmModule || moduleKind == WasmComponent) { str += str.substring(0, resultLength - str.length) } { str += str.jsSubstring(0, resultLength - str.length) @@ -426,10 +428,10 @@ final class _String private () // scalastyle:ignore @inline def replace(target: CharSequence, replacement: CharSequence): String = { - LinkingInfo.linkTimeIf(!LinkingInfo.targetPureWasm) { - thisString.jsSplit(target.toString).join(replacement.toString) - } { + LinkingInfo.linkTimeIf(moduleKind == MinimalWasmModule || moduleKind == WasmComponent) { replaceInternal(target, replacement) + } { + thisString.jsSplit(target.toString).join(replacement.toString) } } @@ -476,7 +478,7 @@ final class _String private () // scalastyle:ignore @inline def startsWith(prefix: String): scala.Boolean = { - LinkingInfo.linkTimeIf(LinkingInfo.targetPureWasm) { + LinkingInfo.linkTimeIf(moduleKind == MinimalWasmModule || moduleKind == WasmComponent) { regionMatches(0, prefix, 0, prefix.length()) } { if (LinkingInfo.esVersion >= ESVersion.ES2015) { @@ -490,7 +492,7 @@ final class _String private () // scalastyle:ignore @inline def startsWith(prefix: String, toffset: Int): scala.Boolean = { - LinkingInfo.linkTimeIf(LinkingInfo.targetPureWasm) { + LinkingInfo.linkTimeIf(moduleKind == MinimalWasmModule || moduleKind == WasmComponent) { regionMatches(toffset, prefix, 0, prefix.length()) } { if (LinkingInfo.esVersion >= ESVersion.ES2015) { @@ -515,7 +517,7 @@ final class _String private () // scalastyle:ignore if (beginIndex < 0 || beginIndex > length()) charAt(beginIndex) - LinkingInfo.linkTimeIf(LinkingInfo.targetPureWasm) { + LinkingInfo.linkTimeIf(moduleKind == MinimalWasmModule || moduleKind == WasmComponent) { this.substring(beginIndex, thisString.length) } { thisString.jsSubstring(beginIndex) @@ -533,7 +535,7 @@ final class _String private () // scalastyle:ignore if (endIndex < beginIndex) charAt(-1) - LinkingInfo.linkTimeIf(LinkingInfo.targetPureWasm) { + LinkingInfo.linkTimeIf(moduleKind == MinimalWasmModule || moduleKind == WasmComponent) { val length = thisString.length val builder = new StringBuilder(endIndex - beginIndex) var i = beginIndex @@ -725,7 +727,7 @@ final class _String private () // scalastyle:ignore @inline def toLowerCase(): String = { - LinkingInfo.linkTimeIf(LinkingInfo.targetPureWasm) { + LinkingInfo.linkTimeIf(moduleKind == MinimalWasmModule || moduleKind == WasmComponent) { this.asInstanceOf[_String].toLowerCaseImpl() } { this.asInstanceOf[js.Dynamic].toLowerCase().asInstanceOf[String] @@ -825,7 +827,7 @@ for (cp <- 0 to Character.MAX_CODE_POINT) { @inline def toUpperCase(): String = { - LinkingInfo.linkTimeIf(LinkingInfo.targetPureWasm) { + LinkingInfo.linkTimeIf(moduleKind == MinimalWasmModule || moduleKind == WasmComponent) { replaceCharsAtIndex { i => val c = this.charAt(i) if (c < 0x80) null // fast-forward ASCII characters diff --git a/javalib/src/main/scala/java/nio/ByteOrder.scala b/javalib/src/main/scala/java/nio/ByteOrder.scala index 296978ae13..611c036333 100644 --- a/javalib/src/main/scala/java/nio/ByteOrder.scala +++ b/javalib/src/main/scala/java/nio/ByteOrder.scala @@ -16,6 +16,8 @@ import scala.scalajs.js import scala.scalajs.js.typedarray._ import scala.scalajs.LinkingInfo +import scala.scalajs.LinkingInfo.moduleKind +import scala.scalajs.LinkingInfo.ModuleKind.{MinimalWasmModule, WasmComponent} final class ByteOrder private (name: String) { override def toString(): String = name @@ -26,7 +28,7 @@ object ByteOrder { val LITTLE_ENDIAN: ByteOrder = new ByteOrder("LITTLE_ENDIAN") private[nio] val areTypedArraysBigEndian = { - LinkingInfo.linkTimeIf(LinkingInfo.targetPureWasm) { + LinkingInfo.linkTimeIf(moduleKind == MinimalWasmModule || moduleKind == WasmComponent) { true } { if (js.typeOf(js.Dynamic.global.Int32Array) != "undefined") { diff --git a/javalib/src/main/scala/java/nio/charset/CoderResult.scala b/javalib/src/main/scala/java/nio/charset/CoderResult.scala index cd150bcdc7..868af138aa 100644 --- a/javalib/src/main/scala/java/nio/charset/CoderResult.scala +++ b/javalib/src/main/scala/java/nio/charset/CoderResult.scala @@ -18,7 +18,9 @@ import java.lang.Utils._ import java.nio._ import scala.scalajs.js -import scala.scalajs.LinkingInfo.{targetPureWasm, linkTimeIf} +import scala.scalajs.LinkingInfo +import scala.scalajs.LinkingInfo.{ESVersion, linkTimeIf, moduleKind} +import scala.scalajs.LinkingInfo.ModuleKind.{MinimalWasmModule, WasmComponent} class CoderResult private (kind: Int, _length: Int) { import CoderResult._ @@ -61,7 +63,7 @@ object CoderResult { // This is a sparse array private val uniqueMalformedJS = { - linkTimeIf(!targetPureWasm) { + linkTimeIf(moduleKind != MinimalWasmModule && moduleKind != WasmComponent) { js.Array[js.UndefOr[CoderResult]]() } { null @@ -69,7 +71,7 @@ object CoderResult { } private val uniqueMalformedWasm = { - linkTimeIf(targetPureWasm) { + linkTimeIf(moduleKind == MinimalWasmModule || moduleKind == WasmComponent) { new java.util.HashMap[Int, CoderResult]() } { null @@ -83,7 +85,7 @@ object CoderResult { // This is a sparse array private val uniqueUnmappableJS = { - linkTimeIf(!targetPureWasm) { + linkTimeIf(moduleKind != MinimalWasmModule && moduleKind != WasmComponent) { js.Array[js.UndefOr[CoderResult]]() } { null @@ -91,7 +93,7 @@ object CoderResult { } private val uniqueUnmappableWasm = { - linkTimeIf(targetPureWasm) { + linkTimeIf(moduleKind == MinimalWasmModule || moduleKind == WasmComponent) { new java.util.HashMap[Int, CoderResult]() } { null @@ -107,7 +109,7 @@ object CoderResult { } private def malformedForLengthImpl(length: Int): CoderResult = { - linkTimeIf(targetPureWasm) { + linkTimeIf(moduleKind == MinimalWasmModule || moduleKind == WasmComponent) { uniqueMalformedWasm.computeIfAbsent(length, _ => new CoderResult(Malformed, length)) } { undefOrFold(uniqueMalformedJS(length)) { () => @@ -129,7 +131,7 @@ object CoderResult { } private def unmappableForLengthImpl(length: Int): CoderResult = { - linkTimeIf(targetPureWasm) { + linkTimeIf(moduleKind == MinimalWasmModule || moduleKind == WasmComponent) { uniqueUnmappableWasm.computeIfAbsent(length, _ => new CoderResult(Unmappable, length)) } { undefOrFold(uniqueUnmappableJS(length)) { () => diff --git a/javalib/src/main/scala/java/util/Formatter.scala b/javalib/src/main/scala/java/util/Formatter.scala index 1a1d1bd0c7..8dcfefd0e2 100644 --- a/javalib/src/main/scala/java/util/Formatter.scala +++ b/javalib/src/main/scala/java/util/Formatter.scala @@ -15,6 +15,8 @@ package java.util import scala.annotation.switch import scala.scalajs.js import scala.scalajs.LinkingInfo +import scala.scalajs.LinkingInfo.moduleKind +import scala.scalajs.LinkingInfo.ModuleKind.{MinimalWasmModule, WasmComponent} import java.lang.{Double => JDouble} import java.lang.Utils._ @@ -1107,7 +1109,7 @@ object Formatter { if (ePos < 0) { 0 } else { - LinkingInfo.linkTimeIf(LinkingInfo.targetPureWasm) { + LinkingInfo.linkTimeIf(moduleKind == MinimalWasmModule || moduleKind == WasmComponent) { java.lang.Integer.parseInt(s.substring(ePos + 1)) } { js.Dynamic.global.parseInt(s.substring(ePos + 1)).asInstanceOf[Int] diff --git a/javalib/src/main/scala/java/util/regex/Engine.scala b/javalib/src/main/scala/java/util/regex/Engine.scala index 39a3b85347..1a6b6c266d 100644 --- a/javalib/src/main/scala/java/util/regex/Engine.scala +++ b/javalib/src/main/scala/java/util/regex/Engine.scala @@ -17,6 +17,8 @@ import scala.language.higherKinds import java.util.function.Supplier import scala.scalajs.LinkingInfo +import scala.scalajs.LinkingInfo.moduleKind +import scala.scalajs.LinkingInfo.ModuleKind.{MinimalWasmModule, WasmComponent} /** Underlying engine used by `Pattern`. * @@ -109,7 +111,7 @@ private[regex] abstract class Engine { private[regex] object Engine { val engine: Engine = { - LinkingInfo.linkTimeIf[Engine](LinkingInfo.targetPureWasm) { + LinkingInfo.linkTimeIf[Engine](moduleKind == MinimalWasmModule || moduleKind == WasmComponent) { WasmEngine } { JSEngine diff --git a/javalib/src/main/scala/java/util/regex/Pattern.scala b/javalib/src/main/scala/java/util/regex/Pattern.scala index 0397bb48b5..53e6591a85 100644 --- a/javalib/src/main/scala/java/util/regex/Pattern.scala +++ b/javalib/src/main/scala/java/util/regex/Pattern.scala @@ -17,6 +17,8 @@ import scala.annotation.tailrec import java.util.ScalaOps._ import scala.scalajs.LinkingInfo +import scala.scalajs.LinkingInfo.moduleKind +import scala.scalajs.LinkingInfo.ModuleKind.{MinimalWasmModule, WasmComponent} import PatternCompiler.Support._ @@ -140,7 +142,7 @@ final class Pattern private[regex] ( private[regex] def getIndices(lastMatch: engine.ExecResult, forMatches: Boolean): engine.IndicesArray = { - LinkingInfo.linkTimeIf(LinkingInfo.targetPureWasm) { + LinkingInfo.linkTimeIf(moduleKind == MinimalWasmModule || moduleKind == WasmComponent) { val indices = engine.getIndices(lastMatch) if (indices == null) throw new AssertionError("Unreachable; WasmEngine always supports and produces indices") diff --git a/javalib/src/main/scala/java/util/regex/PatternCompiler.scala b/javalib/src/main/scala/java/util/regex/PatternCompiler.scala index f6a2f605fb..c91209491c 100644 --- a/javalib/src/main/scala/java/util/regex/PatternCompiler.scala +++ b/javalib/src/main/scala/java/util/regex/PatternCompiler.scala @@ -30,7 +30,8 @@ import java.util.ScalaOps._ import scala.scalajs.js import scala.scalajs.LinkingInfo -import scala.scalajs.LinkingInfo.ESVersion +import scala.scalajs.LinkingInfo.{ESVersion, moduleKind} +import scala.scalajs.LinkingInfo.ModuleKind.{MinimalWasmModule, WasmComponent} /** Compiler from Java regular expressions to JavaScript regular expressions. * @@ -50,7 +51,7 @@ private[regex] object PatternCompiler { /** RegExp to renumber backreferences (used for possessive quantifiers). * - * Lazy because we cannot link it under targetPureWasm. + * Lazy because we cannot link it under Wasm without JS interop. */ private lazy val renumberingRegExp = new js.RegExp("(\\\\+)(\\d+)", "g") @@ -1209,7 +1210,7 @@ private final class PatternCompiler(private val pattern: String, private var fla private def buildPossessiveQuantifier(compiledGroupCountBeforeThisToken: Int, compiledToken: String, baseRepeater: String): String = { - LinkingInfo.linkTimeIf(LinkingInfo.targetPureWasm) { + LinkingInfo.linkTimeIf(moduleKind == MinimalWasmModule || moduleKind == WasmComponent) { /* Our WasmEngine supports possessive quantifiers. * We use them so that we don't need renumbering, which we otherwise * use a js.RegExp for. @@ -1910,7 +1911,7 @@ private final class PatternCompiler(private val pattern: String, private var fla } else if (c1 == '>') { // Atomic group pIndex = start + 3 - LinkingInfo.linkTimeIf(LinkingInfo.targetPureWasm) { + LinkingInfo.linkTimeIf(moduleKind == MinimalWasmModule || moduleKind == WasmComponent) { // Our WasmEngine directly supports atomic groups, so use them. s"(?>${compileInsideGroup()})" } { @@ -1945,7 +1946,7 @@ private final class PatternCompiler(private val pattern: String, private var fla @inline def ucSubstring(s: String, start: Int): String = { LinkingInfo.linkTimeIf(LinkingInfo.isWebAssembly) { - // use s.substring anyway, for the intrinsic or the targetPureWasm implementation + // use s.substring anyway, for the intrinsic or the Wasm-only implementation s.substring(start) } { // avoid range checks @@ -1958,7 +1959,7 @@ private final class PatternCompiler(private val pattern: String, private var fla @inline def ucSubstring(s: String, start: Int, end: Int): String = { LinkingInfo.linkTimeIf(LinkingInfo.isWebAssembly) { - // use s.substring anyway, for the intrinsic or the targetPureWasm implementation + // use s.substring anyway, for the intrinsic or the Wasm-only implementation s.substring(start, end) } { // avoid range checks diff --git a/javalib/src/main/scala/java/util/regex/RegExpImpl.scala b/javalib/src/main/scala/java/util/regex/RegExpImpl.scala index 505dab722c..75bc392dba 100644 --- a/javalib/src/main/scala/java/util/regex/RegExpImpl.scala +++ b/javalib/src/main/scala/java/util/regex/RegExpImpl.scala @@ -12,7 +12,9 @@ package java.util.regex -import scala.scalajs.LinkingInfo._ +import scala.scalajs.LinkingInfo +import scala.scalajs.LinkingInfo.moduleKind +import scala.scalajs.LinkingInfo.ModuleKind.{MinimalWasmModule, WasmComponent} /** A wrapper class to select regex implementation across different platforms. * @@ -38,7 +40,8 @@ private[java] sealed abstract class RegExpImpl { } private[java] object RegExpImpl { - val impl = linkTimeIf[RegExpImpl](targetPureWasm) { + val impl = LinkingInfo.linkTimeIf[RegExpImpl]( + moduleKind == MinimalWasmModule || moduleKind == WasmComponent) { JavaRegExpImpl } { JSRegExpImpl diff --git a/junit-runtime/src/main/scala/org/junit/Assert.scala b/junit-runtime/src/main/scala/org/junit/Assert.scala index 2278965622..7dd7b8a6ed 100644 --- a/junit-runtime/src/main/scala/org/junit/Assert.scala +++ b/junit-runtime/src/main/scala/org/junit/Assert.scala @@ -10,7 +10,6 @@ import org.junit.internal.InexactComparisonCriteria import org.junit.internal.ExactComparisonCriteria import org.hamcrest.Matcher import org.hamcrest.MatcherAssert -import scala.scalajs.LinkingInfo object Assert { @noinline @@ -411,17 +410,9 @@ object Assert { actualThrown) } - LinkingInfo.linkTimeIf(LinkingInfo.targetPureWasm) { - throw new AssertionError( - buildPrefix + - "expecte " + formatClass(expectedThrowable) + " to be thrown, but nothing was thrown" - ) - } { - throw new AssertionError( - buildPrefix + - String.format( - "expected %s to be thrown, but nothing was thrown", formatClass(expectedThrowable))) - } + throw new AssertionError( + s"${buildPrefix}expected ${formatClass(expectedThrowable)} " + + "to be thrown, but nothing was thrown") // scalastyle:on return } diff --git a/library/src/main/scala/scala/scalajs/LinkingInfo.scala b/library/src/main/scala/scala/scalajs/LinkingInfo.scala index 550f0232cf..7558fda2ef 100644 --- a/library/src/main/scala/scala/scalajs/LinkingInfo.scala +++ b/library/src/main/scala/scala/scalajs/LinkingInfo.scala @@ -263,10 +263,6 @@ object LinkingInfo { def isWebAssembly: Boolean = linkTimePropertyBoolean("core/isWebAssembly") - @inline @linkTimeProperty("core/targetPureWasm") - def targetPureWasm: Boolean = - linkTimePropertyBoolean("core/targetPureWasm") - /** Version of the linker. */ @inline @linkTimeProperty("core/linkerVersion") def linkerVersion: String = @@ -401,6 +397,12 @@ object LinkingInfo { * module-global variable. */ final val CommonJSModule = 3 + + /** A minimal Wasm module. */ + final val MinimalWasmModule = 4 + + /** A Wasm Component in the Component Model. */ + final val WasmComponent = 5 } private[scalajs] def linkTimePropertyInt(name: String): Int = diff --git a/library/src/main/scala/scala/scalajs/concurrent/QueueExecutionContext.scala b/library/src/main/scala/scala/scalajs/concurrent/QueueExecutionContext.scala index 3a462174fd..b98e4b199b 100644 --- a/library/src/main/scala/scala/scalajs/concurrent/QueueExecutionContext.scala +++ b/library/src/main/scala/scala/scalajs/concurrent/QueueExecutionContext.scala @@ -19,7 +19,8 @@ import java.util.concurrent.Executor import scala.scalajs.js import scala.scalajs.js.| import scala.scalajs.LinkingInfo -import scala.scalajs.LinkingInfo.linkTimeIf +import scala.scalajs.LinkingInfo.{linkTimeIf, moduleKind} +import scala.scalajs.LinkingInfo.ModuleKind.{MinimalWasmModule, WasmComponent} object QueueExecutionContext { def timeouts(): ExecutionContextExecutor = @@ -32,7 +33,7 @@ object QueueExecutionContext { new SingleThreadedExecutionContext def apply(): ExecutionContextExecutor = { - linkTimeIf(LinkingInfo.targetPureWasm) { + linkTimeIf(moduleKind == MinimalWasmModule || moduleKind == WasmComponent) { single() } { if (js.typeOf(js.Dynamic.global.Promise) == "undefined") timeouts() diff --git a/linker-interface/shared/src/main/scala/org/scalajs/linker/interface/ModuleKind.scala b/linker-interface/shared/src/main/scala/org/scalajs/linker/interface/ModuleKind.scala index bc708445ac..45a4999131 100644 --- a/linker-interface/shared/src/main/scala/org/scalajs/linker/interface/ModuleKind.scala +++ b/linker-interface/shared/src/main/scala/org/scalajs/linker/interface/ModuleKind.scala @@ -23,9 +23,12 @@ object ModuleKind { * whoever maintains the back-ends. */ val All: List[ModuleKind] = List( - NoModule, - ESModule, - CommonJSModule) + NoModule, + ESModule, + MinimalWasmModule, + WasmComponent, + CommonJSModule + ) /** No module structure. * @@ -42,6 +45,12 @@ object ModuleKind { */ case object ESModule extends ModuleKind + /** A minimal Wasm module. */ + case object MinimalWasmModule extends ModuleKind + + /** A Wasm Component in the Component Model. */ + case object WasmComponent extends ModuleKind + /** A CommonJS module (notably used by Node.js). * * Imported modules are fetched with `require`. Exports go to the `exports` @@ -53,9 +62,11 @@ object ModuleKind { override def fingerprint(moduleKind: ModuleKind): String = { moduleKind match { - case NoModule => "NoModule" - case ESModule => "ESModule" - case CommonJSModule => "CommonJSModule" + case NoModule => "NoModule" + case ESModule => "ESModule" + case MinimalWasmModule => "MinimalWasmModule" + case WasmComponent => "WasmComponent" + case CommonJSModule => "CommonJSModule" } } } diff --git a/linker-interface/shared/src/main/scala/org/scalajs/linker/interface/Report.scala b/linker-interface/shared/src/main/scala/org/scalajs/linker/interface/Report.scala index 23ce4c3583..476538aaba 100644 --- a/linker-interface/shared/src/main/scala/org/scalajs/linker/interface/Report.scala +++ b/linker-interface/shared/src/main/scala/org/scalajs/linker/interface/Report.scala @@ -114,9 +114,11 @@ object Report { private def writeModuleKind(kind: ModuleKind): Unit = { val i = kind match { - case ModuleKind.NoModule => 0 - case ModuleKind.ESModule => 1 - case ModuleKind.CommonJSModule => 2 + case ModuleKind.NoModule => 0 + case ModuleKind.ESModule => 1 + case ModuleKind.CommonJSModule => 2 + case ModuleKind.MinimalWasmModule => 3 + case ModuleKind.WasmComponent => 4 } writeByte(i) } @@ -153,6 +155,8 @@ object Report { case 0 => ModuleKind.NoModule case 1 => ModuleKind.ESModule case 2 => ModuleKind.CommonJSModule + case 3 => ModuleKind.MinimalWasmModule + case 4 => ModuleKind.WasmComponent case v => throw new IllegalArgumentException(s"unknown module byte: $v") } } diff --git a/linker-interface/shared/src/main/scala/org/scalajs/linker/interface/WasmFeatures.scala b/linker-interface/shared/src/main/scala/org/scalajs/linker/interface/WasmFeatures.scala index 4bc39d775d..bcdb9dc7b1 100644 --- a/linker-interface/shared/src/main/scala/org/scalajs/linker/interface/WasmFeatures.scala +++ b/linker-interface/shared/src/main/scala/org/scalajs/linker/interface/WasmFeatures.scala @@ -4,8 +4,6 @@ import Fingerprint.FingerprintBuilder final class WasmFeatures private ( _exceptionHandling: Boolean, - _targetPureWasm: Boolean, - _componentModel: Boolean, _witDirectory: Option[String], _witWorld: Option[String], _autoIncludeWasiImports: Boolean @@ -15,8 +13,6 @@ final class WasmFeatures private ( private def this() = { this( _exceptionHandling = true, - _targetPureWasm = false, - _componentModel = false, _witDirectory = None, _witWorld = None, _autoIncludeWasiImports = true @@ -24,8 +20,6 @@ final class WasmFeatures private ( } val exceptionHandling = _exceptionHandling - val targetPureWasm = _targetPureWasm - val componentModel = _componentModel val witDirectory = _witDirectory val witWorld = _witWorld val autoIncludeWasiImports = _autoIncludeWasiImports @@ -33,12 +27,6 @@ final class WasmFeatures private ( def withExceptionHandling(exceptionHandling: Boolean): WasmFeatures = copy(exceptionHandling = exceptionHandling) - def withTargetPureWasm(targetPureWasm: Boolean): WasmFeatures = - copy(targetPureWasm = targetPureWasm) - - def withComponentModel(componentModel: Boolean): WasmFeatures = - copy(componentModel = componentModel) - def withWitDirectory(witDirectory: Option[String]): WasmFeatures = copy(witDirectory = witDirectory) @@ -51,8 +39,6 @@ final class WasmFeatures private ( override def equals(that: Any): Boolean = that match { case that: WasmFeatures => this.exceptionHandling == that.exceptionHandling && - this.targetPureWasm == that.targetPureWasm && - this.componentModel == that.componentModel && this.witDirectory == that.witDirectory && this.witWorld == that.witWorld && this.autoIncludeWasiImports == that.autoIncludeWasiImports @@ -64,19 +50,15 @@ final class WasmFeatures private ( import scala.util.hashing.MurmurHash3._ var acc = HashSeed acc = mix(acc, exceptionHandling.##) - acc = mix(acc, targetPureWasm.##) - acc = mix(acc, componentModel.##) acc = mix(acc, witDirectory.##) acc = mix(acc, witWorld.##) acc = mixLast(acc, autoIncludeWasiImports.##) - finalizeHash(acc, 6) + finalizeHash(acc, 4) } override def toString(): String = { s"""WasmFeatures( | exceptionHandling = $exceptionHandling, - | targetPureWasm = $targetPureWasm, - | componentModel = $componentModel, | witDirectory = $witDirectory, | witWorld = $witWorld, | autoIncludeWasiImports = $autoIncludeWasiImports @@ -85,16 +67,12 @@ final class WasmFeatures private ( private def copy( exceptionHandling: Boolean = this.exceptionHandling, - targetPureWasm: Boolean = this.targetPureWasm, - componentModel: Boolean = this.componentModel, witDirectory: Option[String] = this.witDirectory, witWorld: Option[String] = this.witWorld, autoIncludeWasiImports: Boolean = this.autoIncludeWasiImports ): WasmFeatures = { new WasmFeatures( _exceptionHandling = exceptionHandling, - _targetPureWasm = targetPureWasm, - _componentModel = componentModel, _witDirectory = witDirectory, _witWorld = witWorld, _autoIncludeWasiImports = autoIncludeWasiImports @@ -113,8 +91,6 @@ object WasmFeatures { override def fingerprint(wasmFeatures: WasmFeatures): String = { new FingerprintBuilder("WasmFeatures") .addField("exceptionHandling", wasmFeatures.exceptionHandling) - .addField("targetPureWasm", wasmFeatures.targetPureWasm) - .addField("componentModel", wasmFeatures.componentModel) .addField("witDirectory", wasmFeatures.witDirectory) .addField("witWorld", wasmFeatures.witWorld) .addField("autoIncludeWasiImports", wasmFeatures.autoIncludeWasiImports) diff --git a/linker/shared/src/main/scala/org/scalajs/linker/analyzer/Analysis.scala b/linker/shared/src/main/scala/org/scalajs/linker/analyzer/Analysis.scala index 5fa8a96224..d0222db65a 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/analyzer/Analysis.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/analyzer/Analysis.scala @@ -312,7 +312,7 @@ object Analysis { val usages = jsInteropUsages.map { case (pos, irStr) => s" at ${pos.source}:${pos.line + 1}:${pos.column + 1}: $irStr" }.mkString("\n") - s"Uses JS interop with targetPureWasm = true:\n$usages" + s"Uses JS interop with with a Wasm-only module kind:\n$usages" } logger.log(level, headMsg) diff --git a/linker/shared/src/main/scala/org/scalajs/linker/analyzer/Analyzer.scala b/linker/shared/src/main/scala/org/scalajs/linker/analyzer/Analyzer.scala index 628117856b..5f20ea3839 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/analyzer/Analyzer.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/analyzer/Analyzer.scala @@ -56,8 +56,16 @@ final class Analyzer(config: CommonPhaseConfig, initial: Boolean, private val linkTimeProperties = LinkTimeProperties.fromCoreSpec(config.coreSpec) private val infoLoader: InfoLoader = { - new InfoLoader(irLoader, checkIRFor, linkTimeProperties, - config.coreSpec.wasmFeatures.targetPureWasm) + /* We only ask the InfoLoader to register JS interop if we actually need to + * report errors for them. Otherwise, it is too expensive. + */ + val registerJSInterop = config.coreSpec.moduleKind match { + case ModuleKind.MinimalWasmModule => true + case ModuleKind.WasmComponent => true + case _ => false + } + + new InfoLoader(irLoader, checkIRFor, linkTimeProperties, registerJSInterop) } def computeReachability(moduleInitializers: Seq[ModuleInitializer], diff --git a/linker/shared/src/main/scala/org/scalajs/linker/analyzer/InfoLoader.scala b/linker/shared/src/main/scala/org/scalajs/linker/analyzer/InfoLoader.scala index a8c21799fd..ba9b2ad5e8 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/analyzer/InfoLoader.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/analyzer/InfoLoader.scala @@ -31,9 +31,9 @@ import Platform.emptyThreadSafeMap private[analyzer] final class InfoLoader(irLoader: IRLoader, checkIRFor: Option[CheckingPhase], linkTimeProperties: LinkTimeProperties, - targetPureWasm: Boolean) { + registerJSInterop: Boolean) { - private val generator = new Infos.InfoGenerator(linkTimeProperties, targetPureWasm) + private val generator = new Infos.InfoGenerator(linkTimeProperties, registerJSInterop) private var logger: Logger = _ private val cache = emptyThreadSafeMap[ClassName, InfoLoader.ClassInfoCache] diff --git a/linker/shared/src/main/scala/org/scalajs/linker/analyzer/Infos.scala b/linker/shared/src/main/scala/org/scalajs/linker/analyzer/Infos.scala index 4767e7b0cf..4481bd17df 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/analyzer/Infos.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/analyzer/Infos.scala @@ -569,7 +569,7 @@ object Infos { } final class InfoGenerator(linkTimeProperties: LinkTimeProperties, - targetPureWasm: Boolean) { + registerJSInterop: Boolean) { def genReferencedFieldClasses(fields: List[AnyFieldDef]): Map[FieldName, ClassName] = { val builder = Map.newBuilder[FieldName, ClassName] @@ -595,7 +595,7 @@ object Infos { * [[org.scalajs.ir.Trees.MethodDef Trees.MethodDef]]. */ def generateMethodInfo(methodDef: MethodDef): MethodInfo = { - new GenInfoTraverser(methodDef.version, linkTimeProperties, targetPureWasm) + new GenInfoTraverser(methodDef.version, linkTimeProperties, registerJSInterop) .generateMethodInfo(methodDef) } @@ -603,7 +603,7 @@ object Infos { * [[org.scalajs.ir.Trees.JSConstructorDef Trees.JSConstructorDef]]. */ def generateJSConstructorInfo(ctorDef: JSConstructorDef): ReachabilityInfo = { - new GenInfoTraverser(ctorDef.version, linkTimeProperties, targetPureWasm) + new GenInfoTraverser(ctorDef.version, linkTimeProperties, registerJSInterop) .generateJSConstructorInfo(ctorDef) } @@ -611,7 +611,7 @@ object Infos { * [[org.scalajs.ir.Trees.JSMethodDef Trees.JSMethodDef]]. */ def generateJSMethodInfo(methodDef: JSMethodDef): ReachabilityInfo = { - new GenInfoTraverser(methodDef.version, linkTimeProperties, targetPureWasm) + new GenInfoTraverser(methodDef.version, linkTimeProperties, registerJSInterop) .generateJSMethodInfo(methodDef) } @@ -619,7 +619,7 @@ object Infos { * [[org.scalajs.ir.Trees.JSPropertyDef Trees.JSPropertyDef]]. */ def generateJSPropertyInfo(propertyDef: JSPropertyDef): ReachabilityInfo = { - new GenInfoTraverser(propertyDef.version, linkTimeProperties, targetPureWasm) + new GenInfoTraverser(propertyDef.version, linkTimeProperties, registerJSInterop) .generateJSPropertyInfo(propertyDef) } @@ -631,7 +631,7 @@ object Infos { /** Generates the [[MethodInfo]] for the top-level exports. */ def generateTopLevelExportInfo(enclosingClass: ClassName, topLevelExportDef: TopLevelExportDef): TopLevelExportInfo = { - val info = new GenInfoTraverser(Version.Unversioned, linkTimeProperties, targetPureWasm) + val info = new GenInfoTraverser(Version.Unversioned, linkTimeProperties, registerJSInterop) .generateTopLevelExportInfo(enclosingClass, topLevelExportDef) new TopLevelExportInfo(info, ModuleID(topLevelExportDef.moduleID), @@ -639,13 +639,13 @@ object Infos { } def generateWitNativeMember(member: WitNativeMemberDef): MethodInfo = { - new GenInfoTraverser(Version.Unversioned, linkTimeProperties, targetPureWasm) + new GenInfoTraverser(Version.Unversioned, linkTimeProperties, registerJSInterop) .generateWitNativeMember(member) } } private final class GenInfoTraverser(version: Version, - linkTimeProperties: LinkTimeProperties, targetPureWasm: Boolean) + linkTimeProperties: LinkTimeProperties, registerJSInterop: Boolean) extends Traverser { private val builder = new ReachabilityInfoBuilder(version) @@ -835,7 +835,7 @@ object Infos { override def traverse(tree: Tree): Unit = { builder.maybeAddReferencedClass(tree.tpe) - if (targetPureWasm) + if (registerJSInterop) checkJSInterop(tree) tree match { diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/WebAssemblyLinkerBackend.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/WebAssemblyLinkerBackend.scala index 75c54d3841..8fa162bb01 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/WebAssemblyLinkerBackend.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/WebAssemblyLinkerBackend.scala @@ -33,8 +33,9 @@ final class WebAssemblyLinkerBackend(config: LinkerBackendImpl.Config) extends LinkerBackendImpl(config) { require( - coreSpec.moduleKind == ModuleKind.ESModule, - s"The WebAssembly backend only supports ES modules; was ${coreSpec.moduleKind}." + Set[ModuleKind](ModuleKind.ESModule, ModuleKind.MinimalWasmModule, ModuleKind.WasmComponent) + .contains(coreSpec.moduleKind), + s"The WebAssembly backend does not support the module kind ${coreSpec.moduleKind}." ) require( coreSpec.esFeatures.useECMAScript2015Semantics, @@ -139,7 +140,7 @@ final class WebAssemblyLinkerBackend(config: LinkerBackendImpl.Config) val binaryOutput = BinaryWriter.write(wasmModule, emitDebugInfo) outputImpl.writeFull(wasmFileName, binaryOutput) }).flatMap { _ => - if (!coreSpec.wasmFeatures.componentModel) { + if (coreSpec.moduleKind != ModuleKind.WasmComponent) { Future.unit } else { processComponentModel() diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/ClassEmitter.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/ClassEmitter.scala index c0d7d49d5f..f5e532b5db 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/ClassEmitter.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/ClassEmitter.scala @@ -41,11 +41,11 @@ import VarGen._ import TypeTransformer._ import WasmContext._ import _root_.org.scalajs.linker.backend.wasmemitter.canonicalabi.CABIToScalaJS +import org.scalajs.linker.interface.ModuleKind class ClassEmitter(coreSpec: CoreSpec) { import ClassEmitter._ import coreSpec.semantics - import coreSpec.wasmFeatures.targetPureWasm def genClassDef(clazz: LinkedClass)(implicit ctx: WasmContext): Unit = { val classInfo = ctx.getClassInfo(clazz.className) @@ -78,8 +78,7 @@ class ClassEmitter(coreSpec: CoreSpec) { genMethod(clazz, method) } - if (coreSpec.wasmFeatures.targetPureWasm && - coreSpec.wasmFeatures.componentModel) { + if (coreSpec.moduleKind == ModuleKind.WasmComponent) { for (member <- clazz.witNativeMembers) { canonicalabi.InteropEmitter.genComponentNativeInterop(clazz, member) } @@ -149,7 +148,7 @@ class ClassEmitter(coreSpec: CoreSpec) { def genTopLevelExport(topLevelExport: LinkedTopLevelExport)( implicit ctx: WasmContext): Unit = { topLevelExport.tree match { - case d: WitExportDef if ctx.coreSpec.wasmFeatures.targetPureWasm => + case d: WitExportDef if !ctx.hasJSInterop => canonicalabi.InteropEmitter.genWitExportDef(topLevelExport.owningClass, d) case d: TopLevelMethodExportDef => genTopLevelExportSetter(topLevelExport.exportName) @@ -221,11 +220,11 @@ class ClassEmitter(coreSpec: CoreSpec) { val classInfo = ctx.getClassInfo(className) val nameStr = runtimeClassNameOf(className) - val nameValue = { - if (targetPureWasm) { - ctx.stringPool.getConstantStringDataInstr(nameStr) :+ - wa.RefNull(watpe.HeapType(genTypeID.wasmString)) - } else ctx.stringPool.getConstantStringDataInstr(nameStr) + val nameValue = if (ctx.hasJSInterop) { + ctx.stringPool.getConstantStringDataInstr(nameStr) + } else { + ctx.stringPool.getConstantStringDataInstr(nameStr) :+ + wa.RefNull(watpe.HeapType(genTypeID.wasmString)) } val kind = className match { @@ -400,14 +399,16 @@ class ClassEmitter(coreSpec: CoreSpec) { watpe.RefType(vtableTypeID), isMutable = false ) - val idHashCodeField = if (targetPureWasm) { + val idHashCodeField = if (ctx.hasJSInterop) { + None + } else { Some(watpe.StructField( genFieldID.objStruct.idHashCode, OriginalName(genFieldID.objStruct.idHashCode.toString()), watpe.Int32, isMutable = true )) - } else None + } val fields = idHashCodeField.toList ::: classInfo.allFieldDefs.map { field => watpe.StructField( @@ -505,7 +506,7 @@ class ClassEmitter(coreSpec: CoreSpec) { isMutable = false ) - val idHashCodeFieldOpt = if (targetPureWasm) { + val idHashCodeFieldOpt = if (!ctx.hasJSInterop) { Some(watpe.StructField( genFieldID.objStruct.idHashCode, OriginalName(genFieldID.objStruct.idHashCode.toString()), @@ -578,11 +579,11 @@ class ClassEmitter(coreSpec: CoreSpec) { case ClassRef(className) => "[L" + runtimeClassNameOf(className) + ";" case WitResourceTypeRef(className) => "[W" + runtimeClassNameOf(className) + ";" } - val nameValue = { - if (targetPureWasm) { - ctx.stringPool.getConstantStringDataInstr(nameStr) :+ - wa.RefNull(watpe.HeapType(genTypeID.wasmString)) - } else ctx.stringPool.getConstantStringDataInstr(nameStr) + val nameValue = if (ctx.hasJSInterop) { + ctx.stringPool.getConstantStringDataInstr(nameStr) + } else { + ctx.stringPool.getConstantStringDataInstr(nameStr) :+ + wa.RefNull(watpe.HeapType(genTypeID.wasmString)) } val vtableInit: List[wa.Instr] = nameValue ::: List( // name @@ -729,8 +730,10 @@ class ClassEmitter(coreSpec: CoreSpec) { // Load 1 << jsValueType(expr) fb += wa.I32Const(1) fb += wa.LocalGet(exprNonNullLocal) - if (targetPureWasm) fb += wa.Call(genFunctionID.scalaValueType) - else fb += wa.Call(genFunctionID.jsValueType) + if (ctx.hasJSInterop) + fb += wa.Call(genFunctionID.jsValueType) + else + fb += wa.Call(genFunctionID.scalaValueType) fb += wa.I32Shl // return (... & specialInstanceTypes) != 0 @@ -815,7 +818,8 @@ class ClassEmitter(coreSpec: CoreSpec) { fb += wa.GlobalGet(genGlobalID.forVTable(className)) // idHashCode - if (targetPureWasm) fb += wa.I32Const(0) + if (!ctx.hasJSInterop) + fb += wa.I32Const(0) classInfo.allFieldDefs.foreach { f => fb ++= genZeroOf(f.ftpe) @@ -861,7 +865,9 @@ class ClassEmitter(coreSpec: CoreSpec) { // Push the vtable on the stack fb += wa.GlobalGet(genGlobalID.forVTable(className)) - if (targetPureWasm) fb += wa.I32Const(0) + // idHashCode + if (!ctx.hasJSInterop) + fb += wa.I32Const(0) // Push every field of `fromTyped` on the stack info.allFieldDefs.foreach { field => @@ -1540,7 +1546,7 @@ class ClassEmitter(coreSpec: CoreSpec) { val body = method.body.getOrElse(throw new Exception("abstract method cannot be transformed")) // Emit the function - if (!ctx.coreSpec.wasmFeatures.componentModel && + if (coreSpec.moduleKind == ModuleKind.MinimalWasmModule && (className == SpecialNames.WasmSystemClass || className == SpecialNames.WasmScalajsComClass) && namespace == MemberNamespace.Public && !methodName.isReflectiveProxy) { emitSpecialMethod( diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/CoreWasmLib.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/CoreWasmLib.scala index faec5326a1..4312b21f2e 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/CoreWasmLib.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/CoreWasmLib.scala @@ -19,7 +19,7 @@ import org.scalajs.ir.Types.{Type => _, ArrayType => _, _} import org.scalajs.ir.{OriginalName, Position, Types => irtpe} import org.scalajs.ir.WellKnownNames._ -import org.scalajs.linker.interface.CheckedBehavior +import org.scalajs.linker.interface.{CheckedBehavior, ModuleKind} import org.scalajs.linker.standard.{CoreSpec, LinkedGlobalInfo} import org.scalajs.linker.backend.webassembly._ @@ -51,13 +51,14 @@ final class CoreWasmLib(coreSpec: CoreSpec, globalInfo: LinkedGlobalInfo) { import CoreWasmLib._ import RefType.anyref import coreSpec.semantics - import coreSpec.wasmFeatures.targetPureWasm - val stringType = if (targetPureWasm) RefType(genTypeID.wasmString) else RefType.extern + private val hasJSInterop = coreSpec.moduleKind == ModuleKind.ESModule - val nullableStringType = - if (targetPureWasm) RefType.nullable(genTypeID.wasmString) - else RefType.externref + private val stringType: RefType = + if (hasJSInterop) RefType.extern + else RefType(genTypeID.wasmString) + + private val nullableStringType: RefType = stringType.toNullable private implicit val noPos: Position = Position.NoPosition @@ -94,15 +95,15 @@ final class CoreWasmLib(coreSpec: CoreSpec, globalInfo: LinkedGlobalInfo) { def make(id: FieldID, tpe: Type, isMutable: Boolean): StructField = StructField(id, OriginalName(id.toString()), tpe, isMutable) - val nameFields = { - if (targetPureWasm) { - List( - make(nameOffset, Int32, isMutable = false), - make(nameSize, Int32, isMutable = false), - make(nameStringIndex, Int32, isMutable = false), - make(name, RefType.nullable(genTypeID.wasmString), isMutable = true) - ) - } else List(make(name, RefType.externref, isMutable = true)) + val nameFields = if (hasJSInterop) { + List(make(name, RefType.externref, isMutable = true)) + } else { + List( + make(nameOffset, Int32, isMutable = false), + make(nameSize, Int32, isMutable = false), + make(nameStringIndex, Int32, isMutable = false), + make(name, RefType.nullable(genTypeID.wasmString), isMutable = true) + ) } nameFields ::: @@ -136,7 +137,7 @@ final class CoreWasmLib(coreSpec: CoreSpec, globalInfo: LinkedGlobalInfo) { * with the enclosing function's result type. */ private def genThrow(fb: FunctionBuilder, fakeResult: List[Instr]): Unit = { - if (!targetPureWasm) + if (hasJSInterop) fb += ExternConvertAny if (coreSpec.wasmFeatures.exceptionHandling) { @@ -178,9 +179,10 @@ final class CoreWasmLib(coreSpec: CoreSpec, globalInfo: LinkedGlobalInfo) { ctx.moduleBuilder.addRecTypeBuilder(ctx.mainRecType) genCoreTypesInRecType() - if (!targetPureWasm) genImports() - else { - if (!coreSpec.wasmFeatures.componentModel) + if (hasJSInterop) { + genImports() + } else { + if (coreSpec.moduleKind == ModuleKind.MinimalWasmModule) genWasmEssentialsImports() if (coreSpec.wasmFeatures.exceptionHandling) { @@ -231,7 +233,7 @@ final class CoreWasmLib(coreSpec: CoreSpec, globalInfo: LinkedGlobalInfo) { def genPostClasses()(implicit ctx: WasmContext): Unit = { genBoxedZeroGlobals() - if (targetPureWasm) { + if (!hasJSInterop) { genUndefinedAndIsUndef() genNaiveFmod() genLtoa() @@ -316,7 +318,7 @@ final class CoreWasmLib(coreSpec: CoreSpec, globalInfo: LinkedGlobalInfo) { ) ) - if (targetPureWasm) { + if (!hasJSInterop) { genCoreType( genTypeID.wasmString, StructType( @@ -348,7 +350,7 @@ final class CoreWasmLib(coreSpec: CoreSpec, globalInfo: LinkedGlobalInfo) { // --- Imports --- private def genImports()(implicit ctx: WasmContext): Unit = { - assert(!targetPureWasm) + assert(hasJSInterop) if (ctx.coreSpec.wasmFeatures.exceptionHandling) genTagImports() genGlobalImports() genStringBuiltinImports() @@ -720,11 +722,11 @@ final class CoreWasmLib(coreSpec: CoreSpec, globalInfo: LinkedGlobalInfo) { ) for ((primRef, kind) <- primRefsWithKinds) { - val nameValue = { - if (targetPureWasm) { - ctx.stringPool.getConstantStringDataInstr(primRef.displayName) :+ - RefNull(HeapType(genTypeID.wasmString)) - } else ctx.stringPool.getConstantStringDataInstr(primRef.displayName) + val nameValue = if (hasJSInterop) { + ctx.stringPool.getConstantStringDataInstr(primRef.displayName) + } else { + ctx.stringPool.getConstantStringDataInstr(primRef.displayName) :+ + RefNull(HeapType(genTypeID.wasmString)) } val instrs: List[Instr] = { @@ -748,19 +750,24 @@ final class CoreWasmLib(coreSpec: CoreSpec, globalInfo: LinkedGlobalInfo) { val primTypesWithBoxClasses: List[(GlobalID, ClassName, Instr)] = List( (genGlobalID.bZeroChar, SpecialNames.CharBoxClass, I32Const(0)), (genGlobalID.bZeroLong, SpecialNames.LongBoxClass, I64Const(0)) - ) ++ (if (targetPureWasm) List( - (genGlobalID.bZeroInteger, SpecialNames.IntegerBoxClass, I32Const(0)), - (genGlobalID.bZeroFloat, SpecialNames.DoubleBoxClass, F64Const(0)), - (genGlobalID.bZeroDouble, SpecialNames.DoubleBoxClass, F64Const(0)) - ) - else Nil) + ) ++ { + if (hasJSInterop) { + Nil + } else { + List( + (genGlobalID.bZeroInteger, SpecialNames.IntegerBoxClass, I32Const(0)), + (genGlobalID.bZeroFloat, SpecialNames.DoubleBoxClass, F64Const(0)), + (genGlobalID.bZeroDouble, SpecialNames.DoubleBoxClass, F64Const(0)) + ) + } + } for ((globalID, boxClassName, zeroValueInstr) <- primTypesWithBoxClasses) { val boxStruct = genTypeID.forClass(boxClassName) val instrs: List[Instr] = List( GlobalGet(genGlobalID.forVTable(boxClassName)) ) ::: - (if (targetPureWasm) List(I32Const(0)) else Nil) ::: + (if (hasJSInterop) Nil else List(I32Const(0))) ::: List( zeroValueInstr, StructNew(boxStruct) @@ -789,7 +796,8 @@ final class CoreWasmLib(coreSpec: CoreSpec, globalInfo: LinkedGlobalInfo) { genUnboxByteOrShort(ShortRef) genTestByteOrShort(ByteRef, I32Extend8S) genTestByteOrShort(ShortRef, I32Extend16S) - if (targetPureWasm) genStringLiteral() + if (!hasJSInterop) + genStringLiteral() genTypeDataName() genCreateClassOf() genGetClassOf() @@ -822,19 +830,17 @@ final class CoreWasmLib(coreSpec: CoreSpec, globalInfo: LinkedGlobalInfo) { } if (semantics.stringIndexOutOfBounds != CheckedBehavior.Unchecked) { - genCheckedStringCharAtOrCodePointAt( - genFunctionID.checkedStringCharAt, - if (targetPureWasm) { - genFunctionID.wasmString.charCodeAt - } else { - genFunctionID.stringBuiltins.charCodeAt - } - ) - if (!targetPureWasm) { // In WASI, Optimizer won't transform substring method + if (hasJSInterop) { + genCheckedStringCharAtOrCodePointAt( + genFunctionID.checkedStringCharAt, genFunctionID.stringBuiltins.charCodeAt) genCheckedStringCharAtOrCodePointAt( genFunctionID.checkedStringCodePointAt, genFunctionID.stringBuiltins.codePointAt) genCheckedSubstringStart() genCheckedSubstringStartEnd() + } else { + // With Wasm-defined strings, the optimizer won't transform the substring method + genCheckedStringCharAtOrCodePointAt( + genFunctionID.checkedStringCharAt, genFunctionID.wasmString.charCodeAt) } } @@ -862,8 +868,8 @@ final class CoreWasmLib(coreSpec: CoreSpec, globalInfo: LinkedGlobalInfo) { genArrayCloneFunctions() genArrayCopyFunctions() - // WASI - if (targetPureWasm) { + // Wasm-defined strings + if (!hasJSInterop) { genStringConcat() genStringEquals() genGetWholeChars() @@ -1183,7 +1189,7 @@ final class CoreWasmLib(coreSpec: CoreSpec, globalInfo: LinkedGlobalInfo) { } private def genBoxBoolean()(implicit ctx: WasmContext): Unit = { - if (targetPureWasm) { + if (!hasJSInterop) { genBox(genFunctionID.box(BooleanRef), BooleanType) } else { val fb = newFunctionBuilder(genFunctionID.box(BooleanRef)) @@ -1252,7 +1258,7 @@ final class CoreWasmLib(coreSpec: CoreSpec, globalInfo: LinkedGlobalInfo) { private def genTypeTest(functionID: FunctionID, targetTpe: PrimType)( implicit ctx: WasmContext): Unit = { - assert(targetPureWasm) + assert(!hasJSInterop) val fb = newFunctionBuilder(functionID) val xParam = fb.addParam("x", RefType.anyref) @@ -1268,7 +1274,7 @@ final class CoreWasmLib(coreSpec: CoreSpec, globalInfo: LinkedGlobalInfo) { } private def genBox(functionID: FunctionID, targetTpe: PrimType)(implicit ctx: WasmContext): Unit = { - assert(targetPureWasm) + assert(!hasJSInterop) val wasmType = transformPrimType(targetTpe) val fb = newFunctionBuilder(functionID) @@ -1286,7 +1292,7 @@ final class CoreWasmLib(coreSpec: CoreSpec, globalInfo: LinkedGlobalInfo) { } fb += GlobalGet(genGlobalID.forVTable(boxClass)) - if (targetPureWasm) fb += I32Const(0) + fb += I32Const(0) // idHashCode fb += LocalGet(xParam) if (targetTpe == FloatType) fb += F64PromoteF32 fb += StructNew(genTypeID.forClass(boxClass)) @@ -1295,7 +1301,8 @@ final class CoreWasmLib(coreSpec: CoreSpec, globalInfo: LinkedGlobalInfo) { private def genUnbox(functionID: FunctionID, targetTpe: PrimType)( implicit ctx: WasmContext): Unit = { - assert(targetPureWasm) + assert(!hasJSInterop) + val fb = newFunctionBuilder(functionID) val xParam = fb.addParam("x", RefType.anyref) val resultType = transformPrimType(targetTpe) @@ -1329,7 +1336,8 @@ final class CoreWasmLib(coreSpec: CoreSpec, globalInfo: LinkedGlobalInfo) { } private def genUnboxFloat()(implicit ctx: WasmContext): Unit = { - assert(targetPureWasm) + assert(!hasJSInterop) + val fb = newFunctionBuilder(genFunctionID.unbox(FloatRef)) val xParam = fb.addParam("x", RefType.anyref) val resultType = transformPrimType(FloatType) @@ -1366,7 +1374,8 @@ final class CoreWasmLib(coreSpec: CoreSpec, globalInfo: LinkedGlobalInfo) { } private def genUnboxDouble()(implicit ctx: WasmContext): Unit = { - assert(targetPureWasm) + assert(!hasJSInterop) + val fb = newFunctionBuilder(genFunctionID.unbox(DoubleRef)) val xParam = fb.addParam("x", RefType.anyref) val resultType = transformPrimType(DoubleType) @@ -1470,7 +1479,8 @@ final class CoreWasmLib(coreSpec: CoreSpec, globalInfo: LinkedGlobalInfo) { } private def genTestFloat()(implicit ctx: WasmContext): Unit = { - assert(targetPureWasm) + assert(!hasJSInterop) + val fb = newFunctionBuilder(genFunctionID.typeTest(FloatRef)) val xParam = fb.addParam("x", RefType.anyref) fb.setResultType(Int32) @@ -1504,7 +1514,7 @@ final class CoreWasmLib(coreSpec: CoreSpec, globalInfo: LinkedGlobalInfo) { } private def genTestInteger()(implicit ctx: WasmContext): Unit = { - assert(targetPureWasm) + assert(!hasJSInterop) val fb = newFunctionBuilder(genFunctionID.typeTest(IntRef)) val xParam = fb.addParam("x", RefType.anyref) @@ -1559,7 +1569,7 @@ final class CoreWasmLib(coreSpec: CoreSpec, globalInfo: LinkedGlobalInfo) { } private def genTestDouble()(implicit ctx: WasmContext): Unit = { - assert(targetPureWasm) + assert(!hasJSInterop) val fb = newFunctionBuilder(genFunctionID.typeTest(DoubleRef)) val xParam = fb.addParam("x", RefType.anyref) @@ -1588,7 +1598,8 @@ final class CoreWasmLib(coreSpec: CoreSpec, globalInfo: LinkedGlobalInfo) { } private def genStringLiteral()(implicit ctx: WasmContext): Unit = { - assert(targetPureWasm, "genStringLiteral should be generated only for Wasm only target") + assert(!hasJSInterop, "genStringLiteral should be generated only for Wasm only target") + val fb = newFunctionBuilder(genFunctionID.stringLiteral) val offsetParam = fb.addParam("offset", Int32) val sizeParam = fb.addParam("size", Int32) @@ -1696,7 +1707,7 @@ final class CoreWasmLib(coreSpec: CoreSpec, globalInfo: LinkedGlobalInfo) { // for the StructSet typeData.name near the end fb += LocalGet(typeDataParam) - if (!targetPureWasm) { + if (hasJSInterop) { /* if it was null, the typeData must represent an array type, and its * component type cannot be a primitive type; * compute its name from the component type name. @@ -1816,7 +1827,7 @@ final class CoreWasmLib(coreSpec: CoreSpec, globalInfo: LinkedGlobalInfo) { val doubleValueLocal = fb.addLocal("doubleValue", Float64) fb.setResultType(stringType) - if (targetPureWasm) { + if (!hasJSInterop) { val doubleBoxType = RefType(genTypeID.forClass(SpecialNames.DoubleBoxClass)) fb.block(anyref) { notOurObjectLabel => fb.block(objectType) { isCharLabel => @@ -1901,7 +1912,7 @@ final class CoreWasmLib(coreSpec: CoreSpec, globalInfo: LinkedGlobalInfo) { } } - if (targetPureWasm) { + if (!hasJSInterop) { // shouldn't reach here fb += Drop fb ++= ctx.stringPool.getConstantStringInstr("TODO") @@ -2067,13 +2078,13 @@ final class CoreWasmLib(coreSpec: CoreSpec, globalInfo: LinkedGlobalInfo) { case UndefType => fb += Call(genFunctionID.isUndef) case StringType => - if (targetPureWasm) { + if (!hasJSInterop) { fb += RefTest(RefType(genTypeID.wasmString)) } else { fb += ExternConvertAny fb += Call(genFunctionID.stringBuiltins.test) } - case ByteType | ShortType | IntType | FloatType | DoubleType if targetPureWasm => + case ByteType | ShortType | IntType | FloatType | DoubleType if !hasJSInterop => fb += Call(genFunctionID.typeTest(DoubleRef)) case primType: PrimTypeWithRef => fb += Call(genFunctionID.typeTest(primType.primRef)) @@ -2086,18 +2097,18 @@ final class CoreWasmLib(coreSpec: CoreSpec, globalInfo: LinkedGlobalInfo) { fb += GlobalGet(genGlobalID.undef) case StringType => fb += LocalGet(objParam) - if (targetPureWasm) { + if (!hasJSInterop) { fb += RefCast(RefType(genTypeID.wasmString)) } else { fb += ExternConvertAny fb += RefAsNonNull } - case ByteType | ShortType | IntType if targetPureWasm => + case ByteType | ShortType | IntType if !hasJSInterop => fb += LocalGet(objParam) fb += Call(genFunctionID.unbox(DoubleRef)) fb += I32TruncF64S - case FloatType if targetPureWasm => + case FloatType if !hasJSInterop => fb += LocalGet(objParam) fb += Call(genFunctionID.unbox(DoubleRef)) fb += F32DemoteF64 @@ -2110,7 +2121,7 @@ final class CoreWasmLib(coreSpec: CoreSpec, globalInfo: LinkedGlobalInfo) { fb += LocalGet(objParam) primType match { case StringType => - if (targetPureWasm) { + if (!hasJSInterop) { fb += RefCast(RefType.nullable(genTypeID.wasmString)) } else { fb += ExternConvertAny @@ -2118,19 +2129,20 @@ final class CoreWasmLib(coreSpec: CoreSpec, globalInfo: LinkedGlobalInfo) { } // subtype of DoubleType (Byte, Short, Int, Float, and Double) - case ByteType | ShortType | IntType if targetPureWasm => + case ByteType | ShortType | IntType if !hasJSInterop => fb += Call(genFunctionID.unbox(DoubleRef)) fb += I32TruncF64S fb += Call(genFunctionID.box(primType.asInstanceOf[PrimTypeWithRef].primRef)) - case FloatType if targetPureWasm => + case FloatType if !hasJSInterop => fb += Call(genFunctionID.unbox(DoubleRef)) fb += F32DemoteF64 fb += Call(genFunctionID.box(FloatRef)) - case p: PrimTypeWithRef if targetPureWasm => + case p: PrimTypeWithRef if !hasJSInterop => fb += Call(genFunctionID.unbox(p.primRef)) fb += Call(genFunctionID.box(p.primRef)) + case _ => } @@ -2266,13 +2278,11 @@ final class CoreWasmLib(coreSpec: CoreSpec, globalInfo: LinkedGlobalInfo) { maybeWrapInUBE(fb, semantics.arrayIndexOutOfBounds) { genNewScalaClass(fb, ArrayIndexOutOfBoundsExceptionClass, SpecialNames.StringArgConstructorName) { - if (targetPureWasm) { - fb += LocalGet(indexParam) - fb += Call(genFunctionID.itoa) - } else { - fb += LocalGet(indexParam) + fb += LocalGet(indexParam) + if (hasJSInterop) fb += Call(genFunctionID.intToString) - } + else + fb += Call(genFunctionID.itoa) } } genThrow(fb, fakeResult = Nil) @@ -2294,13 +2304,11 @@ final class CoreWasmLib(coreSpec: CoreSpec, globalInfo: LinkedGlobalInfo) { maybeWrapInUBE(fb, semantics.negativeArraySizes) { genNewScalaClass(fb, NegativeArraySizeExceptionClass, SpecialNames.StringArgConstructorName) { - if (targetPureWasm) { - fb += LocalGet(sizeParam) - fb += Call(genFunctionID.itoa) - } else { - fb += LocalGet(sizeParam) + fb += LocalGet(sizeParam) + if (hasJSInterop) fb += Call(genFunctionID.intToString) - } + else + fb += Call(genFunctionID.itoa) } } genThrow(fb, fakeResult = Nil) @@ -2529,7 +2537,7 @@ final class CoreWasmLib(coreSpec: CoreSpec, globalInfo: LinkedGlobalInfo) { fb += LocalGet(typeDataParam) // typeData := new typeData(...) - if (targetPureWasm) { + if (!hasJSInterop) { fb += I32Const(0) // nameOffset fb += I32Const(0) // nameSize fb += I32Const(0) // nameStringIndex @@ -2619,11 +2627,10 @@ final class CoreWasmLib(coreSpec: CoreSpec, globalInfo: LinkedGlobalInfo) { // if index unsigned_>= str.length fb += LocalGet(indexParam) fb += LocalGet(strParam) - if (targetPureWasm) { - fb += StructGet(genTypeID.wasmString, genFieldID.wasmString.length) - } else { + if (hasJSInterop) fb += Call(genFunctionID.stringBuiltins.length) - } + else + fb += StructGet(genTypeID.wasmString, genFieldID.wasmString.length) fb += I32GeU // unsigned comparison makes negative values of index larger than the length fb.ifThen() { // then, throw a StringIndexOutOfBoundsException @@ -2834,7 +2841,7 @@ final class CoreWasmLib(coreSpec: CoreSpec, globalInfo: LinkedGlobalInfo) { }, List(KindBoxedString) -> { () => fb += LocalGet(valueParam) - if (targetPureWasm) { + if (!hasJSInterop) { fb += RefTest(RefType(genTypeID.wasmString)) } else { fb += ExternConvertAny @@ -2843,7 +2850,7 @@ final class CoreWasmLib(coreSpec: CoreSpec, globalInfo: LinkedGlobalInfo) { }, // case KindJSType | KindJSTypeWithSuperClass => call typeData.isJSClassInstance(value) or throw if it is null List(KindJSType, KindJSTypeWithSuperClass) -> { () => - if (targetPureWasm) { + if (!hasJSInterop) { fb += Unreachable // shouldn't reach here } else { fb.block(RefType.anyref) { isJSClassInstanceIsNull => @@ -2911,8 +2918,10 @@ final class CoreWasmLib(coreSpec: CoreSpec, globalInfo: LinkedGlobalInfo) { // Load (1 << jsValueType(valueNonNull)) fb += I32Const(1) fb += LocalGet(valueNonNullLocal) - if (targetPureWasm) fb += Call(genFunctionID.scalaValueType) - else fb += Call(genFunctionID.jsValueType) + if (hasJSInterop) + fb += Call(genFunctionID.jsValueType) + else + fb += Call(genFunctionID.scalaValueType) fb += I32Shl // if ((... & specialInstanceTypes) != 0) @@ -3283,7 +3292,7 @@ final class CoreWasmLib(coreSpec: CoreSpec, globalInfo: LinkedGlobalInfo) { } else { val arrayTypeRef = ArrayTypeRef(baseRef, 1) fb += GlobalGet(genGlobalID.forArrayVTable(baseRef)) - if (targetPureWasm) { + if (!hasJSInterop) { fb += I32Const(0) // idHashCode } fb += LocalGet(lengthParam) @@ -3301,7 +3310,8 @@ final class CoreWasmLib(coreSpec: CoreSpec, globalInfo: LinkedGlobalInfo) { fb += LocalGet(componentTypeDataLocal) fb += I32Const(1) fb += Call(genFunctionID.specificArrayTypeData) - if (targetPureWasm) fb += I32Const(0) // idHashCode + if (!hasJSInterop) + fb += I32Const(0) // idHashCode fb += LocalGet(lengthParam) fb += ArrayNewDefault(genTypeID.underlyingOf(arrayTypeRef)) @@ -3395,8 +3405,10 @@ final class CoreWasmLib(coreSpec: CoreSpec, globalInfo: LinkedGlobalInfo) { fb.switch() { () => // scrutinee fb += LocalGet(valueParam) - if (targetPureWasm) fb += Call(genFunctionID.scalaValueType) - else fb += Call(genFunctionID.jsValueType) + if (hasJSInterop) + fb += Call(genFunctionID.jsValueType) + else + fb += Call(genFunctionID.scalaValueType) }( // case JSValueTypeFalse, JSValueTypeTrue => typeDataOf[jl.Boolean] List(JSValueTypeFalse, JSValueTypeTrue) -> { () => @@ -3511,7 +3523,7 @@ final class CoreWasmLib(coreSpec: CoreSpec, globalInfo: LinkedGlobalInfo) { fb.ifThenElse(typeDataType) { fb += getHijackedClassTypeDataInstr(BoxedLongClass) } { - if (targetPureWasm) { + if (!hasJSInterop) { fb += LocalGet(ourObjectLocal) fb += RefTest(RefType(genTypeID.forClass(SpecialNames.IntegerBoxClass))) fb.ifThenElse(typeDataType) { @@ -3583,7 +3595,7 @@ final class CoreWasmLib(coreSpec: CoreSpec, globalInfo: LinkedGlobalInfo) { fb += Return } - if (targetPureWasm) { + if (!hasJSInterop) { // In pure Wasm, the boxed primitives are our objects, and // the scalaValueType tests are needed. fb += LocalSet(objNonNullLocal) @@ -3597,8 +3609,10 @@ final class CoreWasmLib(coreSpec: CoreSpec, globalInfo: LinkedGlobalInfo) { fb.ifThen() { fb.switch() { () => fb += LocalGet(objNonNullLocal) - if (targetPureWasm) fb += Call(genFunctionID.scalaValueType) - else fb += Call(genFunctionID.jsValueType) + if (hasJSInterop) + fb += Call(genFunctionID.jsValueType) + else + fb += Call(genFunctionID.scalaValueType) }( Seq( List(JSValueTypeFalse) -> { () => @@ -3611,11 +3625,11 @@ final class CoreWasmLib(coreSpec: CoreSpec, globalInfo: LinkedGlobalInfo) { }, List(JSValueTypeString) -> { () => fb += LocalGet(objNonNullLocal) - if (!targetPureWasm) fb += ExternConvertAny - else fb += RefCast(RefType(genTypeID.wasmString)) - fb += Call( - genFunctionID.forMethod(Public, BoxedStringClass, hashCodeMethodName) - ) + if (hasJSInterop) + fb += ExternConvertAny + else + fb += RefCast(RefType(genTypeID.wasmString)) + fb += Call(genFunctionID.forMethod(Public, BoxedStringClass, hashCodeMethodName)) fb += Return }, List(JSValueTypeNumber) -> { () => @@ -3630,7 +3644,7 @@ final class CoreWasmLib(coreSpec: CoreSpec, globalInfo: LinkedGlobalInfo) { fb += I32Const(0) // specified by jl.Void.hashCode(), Scala.js only fb += Return } - ) ++ (if (!targetPureWasm) { + ) ++ (if (hasJSInterop) { List( List(JSValueTypeBigInt) -> { () => fb += LocalGet(objNonNullLocal) @@ -3660,7 +3674,7 @@ final class CoreWasmLib(coreSpec: CoreSpec, globalInfo: LinkedGlobalInfo) { // If we get here, use the idHashCodeMap - if (!targetPureWasm) { + if (hasJSInterop) { // Read the existing idHashCode, if one exists fb += GlobalGet(genGlobalID.idHashCodeMap) fb += LocalGet(objNonNullLocal) @@ -3822,7 +3836,7 @@ final class CoreWasmLib(coreSpec: CoreSpec, globalInfo: LinkedGlobalInfo) { // If we get here, we did not find the method; throw. - if (targetPureWasm) { + if (!hasJSInterop) { genNewScalaClass(fb, ArrayIndexOutOfBoundsExceptionClass, SpecialNames.StringArgConstructorName) { fb ++= ctx.stringPool.getConstantStringInstr("Method not found") @@ -3891,7 +3905,8 @@ final class CoreWasmLib(coreSpec: CoreSpec, globalInfo: LinkedGlobalInfo) { // Build the result arrayStruct fb += LocalGet(fromLocal) fb += StructGet(arrayStructTypeID, genFieldID.objStruct.vtable) // vtable - if (targetPureWasm) fb += I32Const(0) // idHashCode + if (!hasJSInterop) + fb += I32Const(0) // idHashCode fb += LocalGet(resultUnderlyingLocal) fb += StructNew(arrayStructTypeID) @@ -3961,10 +3976,10 @@ final class CoreWasmLib(coreSpec: CoreSpec, globalInfo: LinkedGlobalInfo) { maybeWrapInUBE(fb, semantics.arrayIndexOutOfBounds) { genNewScalaClass(fb, ArrayIndexOutOfBoundsExceptionClass, SpecialNames.StringArgConstructorName) { - if (targetPureWasm) - fb += RefNull(HeapType(genTypeID.wasmString)) - else + if (hasJSInterop) fb += RefNull(HeapType.NoExtern) + else + fb += RefNull(HeapType(genTypeID.wasmString)) } } genThrow(fb, fakeResult = Nil) @@ -4147,10 +4162,10 @@ final class CoreWasmLib(coreSpec: CoreSpec, globalInfo: LinkedGlobalInfo) { maybeWrapInUBE(fb, semantics.arrayStores) { genNewScalaClass(fb, ArrayStoreExceptionClass, SpecialNames.StringArgConstructorName) { - if (targetPureWasm) - fb += RefNull(HeapType(genTypeID.wasmString)) - else + if (hasJSInterop) fb += RefNull(HeapType.NoExtern) + else + fb += RefNull(HeapType(genTypeID.wasmString)) } } genThrow(fb, fakeResult = Nil) @@ -4160,7 +4175,8 @@ final class CoreWasmLib(coreSpec: CoreSpec, globalInfo: LinkedGlobalInfo) { } private def genStringConcat()(implicit ctx: WasmContext): Unit = { - assert(targetPureWasm, "stringConcat should be generated only for Wasm only target.") + assert(!hasJSInterop, "stringConcat should be generated only for Wasm only target.") + val fb = newFunctionBuilder(genFunctionID.wasmString.stringConcat) val str1 = fb.addParam("str1", stringType) @@ -4184,7 +4200,8 @@ final class CoreWasmLib(coreSpec: CoreSpec, globalInfo: LinkedGlobalInfo) { } private def genStringEquals()(implicit ctx: WasmContext): Unit = { - assert(targetPureWasm, "stringEquals should be generated only for Wasm only target.") + assert(!hasJSInterop, "stringEquals should be generated only for Wasm only target.") + val fb = newFunctionBuilder(genFunctionID.wasmString.stringEquals) val str1 = fb.addParam("str1", nullableStringType) val str2 = fb.addParam("str2", nullableStringType) @@ -4277,7 +4294,8 @@ final class CoreWasmLib(coreSpec: CoreSpec, globalInfo: LinkedGlobalInfo) { } private def genCharCodeAt()(implicit ctx: WasmContext): Unit = { - assert(targetPureWasm, "charCodeAt should be generated only for Wasm only target.") + assert(!hasJSInterop, "charCodeAt should be generated only for Wasm only target.") + val fb = newFunctionBuilder(genFunctionID.wasmString.charCodeAt) val strParam = fb.addParam("str", RefType(genTypeID.wasmString)) @@ -4294,7 +4312,8 @@ final class CoreWasmLib(coreSpec: CoreSpec, globalInfo: LinkedGlobalInfo) { } private def genGetWholeChars()(implicit ctx: WasmContext): Unit = { - assert(targetPureWasm, "getWholeChars should be generated only for Wasm only target.") + assert(!hasJSInterop, "getWholeChars should be generated only for Wasm only target.") + val fb = newFunctionBuilder(genFunctionID.wasmString.getWholeChars) val strParam = fb.addParam("str", RefType(genTypeID.wasmString)) @@ -4316,7 +4335,8 @@ final class CoreWasmLib(coreSpec: CoreSpec, globalInfo: LinkedGlobalInfo) { } private def genCollapseString()(implicit ctx: WasmContext): Unit = { - assert(targetPureWasm, "collapseString should be generated only for Wasm only target.") + assert(!hasJSInterop, "collapseString should be generated only for Wasm only target.") + val fb = newFunctionBuilder(genFunctionID.wasmString.collapseString) val strParam = fb.addParam("str", RefType(genTypeID.wasmString)) @@ -4383,7 +4403,7 @@ final class CoreWasmLib(coreSpec: CoreSpec, globalInfo: LinkedGlobalInfo) { } private def genUndefinedAndIsUndef()(implicit ctx: WasmContext): Unit = { - assert(targetPureWasm) + assert(!hasJSInterop) ctx.mainRecType.addSubType( genTypeID.undefined, @@ -4410,7 +4430,8 @@ final class CoreWasmLib(coreSpec: CoreSpec, globalInfo: LinkedGlobalInfo) { // TODO: https://262.ecma-international.org/#sec-numeric-types-number-remainder private def genNaiveFmod()(implicit ctx: WasmContext): Unit = { - assert(targetPureWasm) + assert(!hasJSInterop) + // f32.fmod locally { val fb = newFunctionBuilder(genFunctionID.f32Fmod) @@ -4465,11 +4486,10 @@ final class CoreWasmLib(coreSpec: CoreSpec, globalInfo: LinkedGlobalInfo) { fb += I64Eqz fb.ifThen() { fb += I32Const('0'.toInt) - if (targetPureWasm) { - SWasmGen.genWasmStringFromCharCode(fb) - } else { + if (hasJSInterop) fb += Call(genFunctionID.stringBuiltins.fromCharCode) - } + else + SWasmGen.genWasmStringFromCharCode(fb) fb += Return } @@ -4616,7 +4636,8 @@ final class CoreWasmLib(coreSpec: CoreSpec, globalInfo: LinkedGlobalInfo) { * the return value should be compatible with jsValueType. */ private def genScalaValueType()(implicit ctx: WasmContext): Unit = { - assert(targetPureWasm) + assert(!hasJSInterop) + val fb = newFunctionBuilder(genFunctionID.scalaValueType) val xParam = fb.addParam("x", RefType.any) fb.setResultType(Int32) @@ -4759,8 +4780,10 @@ final class CoreWasmLib(coreSpec: CoreSpec, globalInfo: LinkedGlobalInfo) { } private def genStringConcat(fb: FunctionBuilder): Unit = { - if (targetPureWasm) fb += Call(genFunctionID.wasmString.stringConcat) - else fb += Call(genFunctionID.stringBuiltins.concat) + if (hasJSInterop) + fb += Call(genFunctionID.stringBuiltins.concat) + else + fb += Call(genFunctionID.wasmString.stringConcat) } } diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/DerivedClasses.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/DerivedClasses.scala index c4b86e0d5c..5de8da302e 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/DerivedClasses.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/DerivedClasses.scala @@ -24,7 +24,7 @@ import org.scalajs.ir.Types._ import org.scalajs.ir.WellKnownNames._ import org.scalajs.ir.{EntryPointsInfo, Version} -import org.scalajs.linker.interface.IRFile +import org.scalajs.linker.interface.{IRFile, ModuleKind} import org.scalajs.linker.interface.unstable.IRFileImpl import org.scalajs.linker.standard.{LinkedClass, CoreSpec} @@ -34,7 +34,7 @@ import SpecialNames._ /** Derives `CharacterBox` and `LongBox` from `jl.Character` and `jl.Long`. */ object DerivedClasses { def deriveClasses(classes: List[LinkedClass], coreSpec: CoreSpec): List[LinkedClass] = { - if (coreSpec.wasmFeatures.targetPureWasm) { + if (coreSpec.moduleKind != ModuleKind.ESModule) { classes.collect { case clazz if clazz.className == BoxedCharacterClass || diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/Emitter.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/Emitter.scala index 02593ceebf..8c31b6f1c1 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/Emitter.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/Emitter.scala @@ -62,8 +62,8 @@ final class Emitter(config: Emitter.Config) { def emit(module: ModuleSet.Module, globalInfo: LinkedGlobalInfo, logger: Logger): Result = { val (wasmModule, jsFileContentInfo) = emitWasmModule(module, globalInfo) val loaderContent = - if (coreSpec.wasmFeatures.targetPureWasm) LoaderContent.pureWasmBytesContent - else LoaderContent.bytesContent + if (coreSpec.moduleKind == ModuleKind.ESModule) LoaderContent.bytesContent + else LoaderContent.noJSInteropBytesContent val jsFileContent = buildJSFileContent(module, jsFileContentInfo) new Result(wasmModule, loaderContent, jsFileContent) @@ -201,11 +201,10 @@ final class Emitter(config: Emitter.Config) { ModuleInitializerImpl.fromInitializer(init) match { case ModuleInitializerImpl.MainMethodWithArgs(className, encodedMainMethodName, args) => val stringArrayTypeRef = ArrayTypeRef(ClassRef(BoxedStringClass), 1) - SWasmGen.genArrayValue( - fb, stringArrayTypeRef, args.size, coreSpec.wasmFeatures.targetPureWasm) { + SWasmGen.genArrayValue(fb, stringArrayTypeRef, args.size) { for (arg <- args) { fb ++= ctx.stringPool.getConstantStringInstr(arg) - if (!coreSpec.wasmFeatures.targetPureWasm) + if (ctx.hasJSInterop) fb += wa.AnyConvertExtern } } @@ -228,7 +227,7 @@ final class Emitter(config: Emitter.Config) { fb += wa.I32Const(c.toInt) fb += wa.ArrayNewFixed(genTypeID.i16Array, message.length()) - if (coreSpec.wasmFeatures.componentModel) + if (coreSpec.moduleKind == ModuleKind.WasmComponent) fb += wa.Drop // TODO else fb += wa.Call(genFunctionID.wasmEssentials.print) @@ -594,7 +593,7 @@ object Emitter { callStaticMethod(WasmRuntimeClass, fmodfMethodName), callStaticMethod(WasmRuntimeClass, fmoddMethodName), - cond(coreSpec.wasmFeatures.targetPureWasm) { + cond(coreSpec.moduleKind != ModuleKind.ESModule) { callStaticMethod(RyuDoubleClass, doubleToStringMethodName) } ) diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/FunctionEmitter.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/FunctionEmitter.scala index 99f58b24f9..ec808ce83a 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/FunctionEmitter.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/FunctionEmitter.scala @@ -328,7 +328,6 @@ private class FunctionEmitter private ( private val coreSpec = ctx.coreSpec import coreSpec.semantics - import coreSpec.wasmFeatures.targetPureWasm private var currentExceptionHandler: Option[wanme.LabelID] = null private var currentNPELabel: Option[wanme.LabelID] = null @@ -691,24 +690,23 @@ private class FunctionEmitter private ( case t: Skip => VoidType // JavaScript expressions - case t: JSNew if !targetPureWasm => genJSNew(t) - case t: JSSelect if !targetPureWasm => genJSSelect(t, castTo = expectedType) - case t: JSFunctionApply if !targetPureWasm => genJSFunctionApply(t, castTo = expectedType) - case t: JSMethodApply if !targetPureWasm => genJSMethodApply(t, castTo = expectedType) - case t: JSImportCall if !targetPureWasm => genJSImportCall(t) - case t: JSImportMeta if !targetPureWasm => genJSImportMeta(t) - case t: LoadJSConstructor if !targetPureWasm => genLoadJSConstructor(t) - case t: LoadJSModule if !targetPureWasm => genLoadJSModule(t) - case t: SelectJSNativeMember if !targetPureWasm => - genSelectJSNativeMember(t, castTo = expectedType) - case t: JSDelete if !targetPureWasm => genJSDelete(t) - case t: JSUnaryOp if !targetPureWasm => genJSUnaryOp(t, castTo = expectedType) - case t: JSBinaryOp if !targetPureWasm => genJSBinaryOp(t, castTo = expectedType) - case t: JSArrayConstr if !targetPureWasm => genJSArrayConstr(t) - case t: JSObjectConstr if !targetPureWasm => genJSObjectConstr(t) - case t: JSGlobalRef if !targetPureWasm => genJSGlobalRef(t, castTo = expectedType) - case t: JSTypeOfGlobalRef if !targetPureWasm => genJSTypeOfGlobalRef(t, castTo = expectedType) - case t: Closure => genClosure(t) + case t: JSNew => genJSNew(t) + case t: JSSelect => genJSSelect(t, castTo = expectedType) + case t: JSFunctionApply => genJSFunctionApply(t, castTo = expectedType) + case t: JSMethodApply => genJSMethodApply(t, castTo = expectedType) + case t: JSImportCall => genJSImportCall(t) + case t: JSImportMeta => genJSImportMeta(t) + case t: LoadJSConstructor => genLoadJSConstructor(t) + case t: LoadJSModule => genLoadJSModule(t) + case t: SelectJSNativeMember => genSelectJSNativeMember(t, castTo = expectedType) + case t: JSDelete => genJSDelete(t) + case t: JSUnaryOp => genJSUnaryOp(t, castTo = expectedType) + case t: JSBinaryOp => genJSBinaryOp(t, castTo = expectedType) + case t: JSArrayConstr => genJSArrayConstr(t) + case t: JSObjectConstr => genJSObjectConstr(t) + case t: JSGlobalRef => genJSGlobalRef(t, castTo = expectedType) + case t: JSTypeOfGlobalRef => genJSTypeOfGlobalRef(t, castTo = expectedType) + case t: Closure => genClosure(t) // array case t: NewArray => genNewArray(t) @@ -716,11 +714,11 @@ private class FunctionEmitter private ( case t: ArrayValue => genArrayValue(t) // Non-native JS classes - case t: CreateJSClass if !targetPureWasm => genCreateJSClass(t) - case t: JSPrivateSelect if !targetPureWasm => genJSPrivateSelect(t) - case t: JSSuperSelect if !targetPureWasm => genJSSuperSelect(t) - case t: JSSuperMethodCall if !targetPureWasm => genJSSuperMethodCall(t, castTo = expectedType) - case t: JSNewTarget if !targetPureWasm => genJSNewTarget(t) + case t: CreateJSClass => genCreateJSClass(t) + case t: JSPrivateSelect => genJSPrivateSelect(t) + case t: JSSuperSelect => genJSSuperSelect(t) + case t: JSSuperMethodCall => genJSSuperMethodCall(t, castTo = expectedType) + case t: JSNewTarget => genJSNewTarget(t) // Records (only generated by the optimizer) case t: RecordSelect => genRecordSelect(t) @@ -761,7 +759,7 @@ private class FunctionEmitter private ( primType match { case NullType => expectedType match { - case ClassType(BoxedStringClass, true, _) if !targetPureWasm => + case ClassType(BoxedStringClass, true, _) if ctx.hasJSInterop => fb += wa.ExternConvertAny case _ => () } @@ -789,7 +787,7 @@ private class FunctionEmitter private ( } case (StringType | ClassType(BoxedStringClass, _, _), _) => - if (!targetPureWasm) { + if (ctx.hasJSInterop) { expectedType match { case ClassType(BoxedStringClass, _, _) => () case _ => fb += wa.AnyConvertExtern @@ -872,7 +870,7 @@ private class FunctionEmitter private ( def genRhs(): Unit = { genTree(rhs, lhs.tpe) lhs.tpe match { - case ClassType(BoxedStringClass, _, _) if !targetPureWasm => + case ClassType(BoxedStringClass, _, _) if ctx.hasJSInterop => fb += wa.AnyConvertExtern case _ => () } @@ -914,19 +912,19 @@ private class FunctionEmitter private ( s"ArraySelect.array must be an array type, but has type ${array.tpe}") } - case JSPrivateSelect(qualifier, field) if !targetPureWasm => + case JSPrivateSelect(qualifier, field) => genTree(qualifier, AnyType) genTree(rhs, AnyType) markPosition(tree) fb += wa.Call(genFunctionID.forPrivateJSFieldSetter(field.name)) - case JSSelect(qualifier, item) if !targetPureWasm => + case JSSelect(qualifier, item) => genThroughCustomJSHelper(List(qualifier, item, rhs), VoidType) { allJSArgs => val List(jsQualifier, jsItem, jsRhs) = allJSArgs js.Assign(js.BracketSelect.makeOptimized(jsQualifier, jsItem), jsRhs) } - case JSSuperSelect(superClass, receiver, item) if !targetPureWasm => + case JSSuperSelect(superClass, receiver, item) => genTree(superClass, AnyType) genTree(receiver, AnyType) genTree(item, AnyType) @@ -934,7 +932,7 @@ private class FunctionEmitter private ( markPosition(tree) fb += wa.Call(genFunctionID.jsSuperSelectSet) - case lhs @ JSGlobalRef(name) if !targetPureWasm => + case lhs @ JSGlobalRef(name) => val builder = new CustomJSHelperBuilderWithTreeSupport() val rhsRef = builder.addInput(rhs) val helperID = builder.build(VoidType) { @@ -1248,7 +1246,7 @@ private class FunctionEmitter private ( if (methodName == toStringMethodName) { // By spec, toString() is special assert(argsLocals.isEmpty) - if (targetPureWasm) + if (!ctx.hasJSInterop) fb += wa.Call(genFunctionID.hijackedValueToString) else fb += wa.Call(genFunctionID.jsValueToString) @@ -1259,8 +1257,10 @@ private class FunctionEmitter private ( genHijackedClassCall(BoxedDoubleClass) } else if (receiverClassName == CharSequenceClass) { // the value must be a `string` - if (!targetPureWasm) fb += wa.ExternConvertAny - else fb += wa.RefCast(watpe.RefType(genTypeID.wasmString)) + if (ctx.hasJSInterop) + fb += wa.ExternConvertAny + else + fb += wa.RefCast(watpe.RefType(genTypeID.wasmString)) pushArgs(argsLocals) genHijackedClassCall(BoxedStringClass) } else if (methodName == compareToMethodName) { @@ -1273,7 +1273,7 @@ private class FunctionEmitter private ( val receiverLocal = addSyntheticLocal(watpe.RefType.any) fb += wa.LocalTee(receiverLocal) - if (!targetPureWasm) { + if (ctx.hasJSInterop) { val jsValueTypeLocal = addSyntheticLocal(watpe.Int32) fb += wa.Call(genFunctionID.jsValueType) fb += wa.LocalTee(jsValueTypeLocal) @@ -1440,7 +1440,7 @@ private class FunctionEmitter private ( case Some(primReceiverType) => if (receiver.tpe == primReceiverType) { genTreeAuto(receiver) - } else if (targetPureWasm && + } else if (!ctx.hasJSInterop && receiver.tpe == ClassType(BoxedStringClass, false, false) && primReceiverType == StringType) { genTreeAuto(receiver) @@ -1535,14 +1535,13 @@ private class FunctionEmitter private ( * type in the IR but they get a `void` expected type. */ expectedType - } else if (tree.isInstanceOf[Null] && + } else if (tree.isInstanceOf[Null] && ctx.hasJSInterop && expectedType == ClassType(BoxedStringClass, true, false)) { /* Directly emit a `ref.null noextern` instead of requiring an * `extern.convert_from_any` in `genAdapt`. */ markPosition(tree) - if (targetPureWasm) fb += wa.RefNull(watpe.HeapType(genTypeID.wasmString)) - else fb += wa.RefNull(watpe.HeapType.NoExtern) + fb += wa.RefNull(watpe.HeapType.NoExtern) expectedType } else { markPosition(tree) @@ -1699,8 +1698,10 @@ private class FunctionEmitter private ( // String.length case String_length => - if (targetPureWasm) fb += wa.StructGet(genTypeID.wasmString, genFieldID.wasmString.length) - else fb += wa.Call(genFunctionID.stringBuiltins.length) + if (ctx.hasJSInterop) + fb += wa.Call(genFunctionID.stringBuiltins.length) + else + fb += wa.StructGet(genTypeID.wasmString, genFieldID.wasmString.length) // Null check case CheckNotNull => @@ -1860,7 +1861,7 @@ private class FunctionEmitter private ( */ private def genThrow(): Unit = { if (ctx.coreSpec.wasmFeatures.exceptionHandling) { - if (!targetPureWasm) + if (ctx.hasJSInterop) fb += wa.ExternConvertAny fb += wa.Throw(genTagID.exception) } else { @@ -1957,11 +1958,10 @@ private class FunctionEmitter private ( genTree(rhs, IntType) markPosition(tree) if (semantics.stringIndexOutOfBounds == CheckedBehavior.Unchecked) { - if (targetPureWasm) { - fb += wa.Call(genFunctionID.wasmString.charCodeAt) - } else { + if (ctx.hasJSInterop) fb += wa.Call(genFunctionID.stringBuiltins.charCodeAt) - } + else + fb += wa.Call(genFunctionID.wasmString.charCodeAt) } else { fb += wa.Call(genFunctionID.checkedStringCharAt) genForwardThrow() @@ -2071,7 +2071,7 @@ private class FunctionEmitter private ( fb += wa.RefIsNull maybeGenInvert() BooleanType - } else if (targetPureWasm && isStringType(lhsType) && isStringType(rhsType)) { + } else if (isStringType(lhsType) && isStringType(rhsType)) { genTreeAuto(lhs) genTreeAuto(rhs) markPosition(tree) @@ -2191,8 +2191,10 @@ private class FunctionEmitter private ( genToStringForConcat(lhs) genToStringForConcat(rhs) markPosition(tree) - if (targetPureWasm) fb += wa.Call(genFunctionID.wasmString.stringConcat) - else fb += wa.Call(genFunctionID.stringBuiltins.concat) + if (ctx.hasJSInterop) + fb += wa.Call(genFunctionID.stringBuiltins.concat) + else + fb += wa.Call(genFunctionID.wasmString.stringConcat) } StringType @@ -2200,7 +2202,9 @@ private class FunctionEmitter private ( private def genToStringForConcat(tree: Tree): Unit = { val stringType = - if (targetPureWasm) watpe.RefType(genTypeID.wasmString) else watpe.RefType.extern + if (ctx.hasJSInterop) watpe.RefType.extern + else watpe.RefType(genTypeID.wasmString) + def genWithDispatch(needHijackedClassDispatch: Boolean): Unit = { // TODO Better codegen when non-nullable @@ -2283,10 +2287,10 @@ private class FunctionEmitter private ( } // end block labelNotOurObject // Now we have a value that is not one of our objects; the anyref is still on the stack - if (targetPureWasm) - fb += wa.Call(genFunctionID.hijackedValueToString) - else + if (ctx.hasJSInterop) fb += wa.Call(genFunctionID.jsValueToStringForConcat) + else + fb += wa.Call(genFunctionID.hijackedValueToString) } // end block labelDone } } @@ -2301,7 +2305,7 @@ private class FunctionEmitter private ( case StringType => () // no-op case BooleanType => - if (targetPureWasm) { + if (!ctx.hasJSInterop) { fb += wa.I32Eqz fb.ifThenElse(stringType) { fb ++= ctx.stringPool.getConstantStringInstr("false") @@ -2312,7 +2316,7 @@ private class FunctionEmitter private ( fb += wa.Call(genFunctionID.booleanToString) } case CharType => - if (targetPureWasm) { + if (!ctx.hasJSInterop) { fb += wa.ArrayNewFixed(genTypeID.i16Array, 1) fb += wa.I32Const(1) fb += wa.RefNull(watpe.HeapType(genTypeID.wasmString)) @@ -2321,20 +2325,20 @@ private class FunctionEmitter private ( fb += wa.Call(genFunctionID.stringBuiltins.fromCharCode) } case ByteType | ShortType | IntType => - if (targetPureWasm) { + if (!ctx.hasJSInterop) { fb += wa.Call(genFunctionID.itoa) } else { fb += wa.Call(genFunctionID.intToString) } case LongType => - if (targetPureWasm) { + if (!ctx.hasJSInterop) { fb += wa.Call(genFunctionID.ltoa) } else { fb += wa.Call(genFunctionID.longToString) } case FloatType => fb += wa.F64PromoteF32 - if (targetPureWasm) { + if (!ctx.hasJSInterop) { fb += wa.Call(genFunctionID.forMethod(MemberNamespace.PublicStatic, SpecialNames.RyuDoubleClass, SpecialNames.doubleToStringMethodName)) fb += wa.RefAsNonNull @@ -2342,22 +2346,19 @@ private class FunctionEmitter private ( fb += wa.Call(genFunctionID.doubleToString) } case DoubleType => - if (targetPureWasm) { + if (!ctx.hasJSInterop) { fb += wa.Call(genFunctionID.forMethod(MemberNamespace.PublicStatic, SpecialNames.RyuDoubleClass, SpecialNames.doubleToStringMethodName)) fb += wa.RefAsNonNull } else { fb += wa.Call(genFunctionID.doubleToString) } - case NullType | UndefType => - if (targetPureWasm) { - fb += wa.Drop - fb ++= - (if (primType == NullType) ctx.stringPool.getConstantStringInstr("null") - else ctx.stringPool.getConstantStringInstr("undefined")) - } else { - fb += wa.Call(genFunctionID.jsValueToStringForConcat) - } + case NullType => + fb += wa.Drop + fb ++= ctx.stringPool.getConstantStringInstr("null") + case UndefType => + fb += wa.Drop + fb ++= ctx.stringPool.getConstantStringInstr("undefined") case NothingType => () // unreachable case VoidType => @@ -2509,7 +2510,7 @@ private class FunctionEmitter private ( case UndefType => fb += wa.Call(genFunctionID.isUndef) case StringType => - if (targetPureWasm) { + if (!ctx.hasJSInterop) { fb += wa.RefTest(watpe.RefType(genTypeID.wasmString)) } else { fb += wa.ExternConvertAny @@ -2759,7 +2760,7 @@ private class FunctionEmitter private ( fb += wa.GlobalGet(genGlobalID.undef) case StringType => - if (targetPureWasm) { + if (!ctx.hasJSInterop) { fb += wa.RefCast(watpe.RefType.nullable(genTypeID.wasmString)) val sig = watpe.FunctionType(List(watpe.RefType.nullable(genTypeID.wasmString)), List(watpe.RefType(genTypeID.wasmString))) @@ -2929,8 +2930,8 @@ private class FunctionEmitter private ( markPosition(tree) val exceptionType = - if (targetPureWasm) watpe.RefType.anyref - else watpe.RefType.externref + if (ctx.hasJSInterop) watpe.RefType.externref + else watpe.RefType.anyref fb.block(resultType) { doneLabel => fb.block(exceptionType) { catchLabel => @@ -2951,7 +2952,8 @@ private class FunctionEmitter private ( } } // end block $catch withNewLocal(errVarName, errVarOrigName, watpe.RefType.anyref) { exceptionLocal => - if (!targetPureWasm) fb += wa.AnyConvertExtern + if (ctx.hasJSInterop) + fb += wa.AnyConvertExtern fb += wa.LocalSet(exceptionLocal) genTree(handler, expectedType) } @@ -3093,7 +3095,8 @@ private class FunctionEmitter private ( fb += wa.LocalSet(primLocal) fb += wa.GlobalGet(genGlobalID.forVTable(boxClassName)) - if (targetPureWasm) fb += wa.I32Const(0) // idHashCode + if (!ctx.hasJSInterop) + fb += wa.I32Const(0) // idHashCode fb += wa.LocalGet(primLocal) fb += wa.StructNew(genTypeID.forClass(boxClassName)) } @@ -3361,7 +3364,8 @@ private class FunctionEmitter private ( markPosition(tree) genLoadArrayTypeData(fb, arrayTypeRef) // vtable - if (targetPureWasm) fb += wa.I32Const(0) + if (!ctx.hasJSInterop) + fb += wa.I32Const(0) // idHashCode // Create the underlying array genTree(length, IntType) @@ -3521,7 +3525,7 @@ private class FunctionEmitter private ( throw new AssertionError(s"Invalid array type $arrayTypeRef at ${tree.pos}") } - SWasmGen.genArrayValueFromUnderlying(fb, arrayTypeRef, targetPureWasm) { + SWasmGen.genArrayValueFromUnderlying(fb, arrayTypeRef) { fb += wa.I32Const(offset) fb += wa.I32Const(length) fb += wa.ArrayNewData(genTypeID.underlyingOf(arrayTypeRef), dataID) @@ -3533,7 +3537,7 @@ private class FunctionEmitter private ( case _ => AnyType } - SWasmGen.genArrayValue(fb, arrayTypeRef, elems.size, targetPureWasm) { + SWasmGen.genArrayValue(fb, arrayTypeRef, elems.size) { // Create the underlying array elems.foreach(genTree(_, expectedElemType)) @@ -3548,10 +3552,8 @@ private class FunctionEmitter private ( private def genClosure(tree: Closure): Type = { if (tree.flags.typed) genTypedClosure(tree) - else if (!targetPureWasm) - genJSClosure(tree) else - throw new AssertionError(s"JavaScript closure isn't supported when targetPureWasm = true.") + genJSClosure(tree) } private def genTypedClosure(tree: Closure): Type = { @@ -3594,6 +3596,8 @@ private class FunctionEmitter private ( implicit val pos = tree.pos + assert(ctx.hasJSInterop, s"Unexpected JS Closure at $pos") + val dataStructTypeID = ctx.getClosureDataStructType(captureParams.map(_.ptpe)) // Define the function where captures are reified as a `__captureData` argument. @@ -4016,8 +4020,10 @@ private class FunctionEmitter private ( } private def genStringEquals(): Unit = { - if (targetPureWasm) fb += wa.Call(genFunctionID.wasmString.stringEquals) - else fb += wa.Call(genFunctionID.stringBuiltins.equals) + if (ctx.hasJSInterop) + fb += wa.Call(genFunctionID.stringBuiltins.equals) + else + fb += wa.Call(genFunctionID.wasmString.stringEquals) } /** Generates code with a custom JS helper with direct access to the builder. */ diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/LoaderContent.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/LoaderContent.scala index ea4253f56c..df65f1f2c7 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/LoaderContent.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/LoaderContent.scala @@ -23,8 +23,8 @@ object LoaderContent { val bytesContent: Array[Byte] = stringContent.getBytes(StandardCharsets.UTF_8) - val pureWasmBytesContent: Array[Byte] = - pureWasmStringContent.getBytes(StandardCharsets.UTF_8) + val noJSInteropBytesContent: Array[Byte] = + noJSInteropStringContent.getBytes(StandardCharsets.UTF_8) private def stringContent: String = { raw""" @@ -225,7 +225,7 @@ export async function load(wasmFileURL, exportSetters, privateJSFieldGetters, """ } - private def pureWasmStringContent: String = { + private def noJSInteropStringContent: String = { import org.scalajs.ir.OriginalName.NoOriginalName import org.scalajs.ir.OriginalName import org.scalajs.ir.Position.{NoPosition => NoPos} diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/SWasmGen.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/SWasmGen.scala index 7b9f242ee7..0ef02034a9 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/SWasmGen.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/SWasmGen.scala @@ -46,15 +46,11 @@ object SWasmGen { case LongType => I64Const(0L) case FloatType => F32Const(0.0f) case DoubleType => F64Const(0.0) - case StringType => - if (ctx.coreSpec.wasmFeatures.targetPureWasm) - GlobalGet(genGlobalID.emptyStringArray) - else - ctx.stringPool.getEmptyStringInstr() - case UndefType => GlobalGet(genGlobalID.undef) + case StringType => ctx.stringPool.getEmptyStringInstr() + case UndefType => GlobalGet(genGlobalID.undef) case ClassType(BoxedStringClass, true, _) => - if (ctx.coreSpec.wasmFeatures.targetPureWasm) + if (!ctx.hasJSInterop) RefNull(HeapType(genTypeID.wasmString)) else RefNull(Types.HeapType.NoExtern) @@ -109,21 +105,22 @@ object SWasmGen { } } - def genArrayValue(fb: FunctionBuilder, arrayTypeRef: ArrayTypeRef, - length: Int, targetPureWasm: Boolean)( - genElems: => Unit): Unit = { - genArrayValueFromUnderlying(fb, arrayTypeRef, targetPureWasm) { + def genArrayValue(fb: FunctionBuilder, arrayTypeRef: ArrayTypeRef, length: Int)( + genElems: => Unit)( + implicit ctx: WasmContext): Unit = { + genArrayValueFromUnderlying(fb, arrayTypeRef) { // Create the underlying array genElems fb += ArrayNewFixed(genTypeID.underlyingOf(arrayTypeRef), length) } } - def genArrayValueFromUnderlying(fb: FunctionBuilder, arrayTypeRef: ArrayTypeRef, - targetPureWasm: Boolean)( - genUnderlying: => Unit): Unit = { + def genArrayValueFromUnderlying(fb: FunctionBuilder, arrayTypeRef: ArrayTypeRef)( + genUnderlying: => Unit)( + implicit ctx: WasmContext): Unit = { genLoadArrayTypeData(fb, arrayTypeRef) // vtable - if (targetPureWasm) fb += I32Const(0) + if (!ctx.hasJSInterop) + fb += I32Const(0) // idHashCode genUnderlying fb += StructNew(genTypeID.forArrayClass(arrayTypeRef)) } diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/SpecialNames.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/SpecialNames.scala index c56da80124..5ac6dc2176 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/SpecialNames.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/SpecialNames.scala @@ -26,7 +26,6 @@ object SpecialNames { val CharBoxClass = BoxedCharacterClass.withSuffix("Box") val LongBoxClass = BoxedLongClass.withSuffix("Box") - // targetPureWasm val BooleanBoxClass = BoxedBooleanClass.withSuffix("Box") val IntegerBoxClass = BoxedIntegerClass.withSuffix("Box") val DoubleBoxClass = BoxedDoubleClass.withSuffix("Box") diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/TypeTransformer.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/TypeTransformer.scala index 092ef1d803..b4ec3c30c6 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/TypeTransformer.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/TypeTransformer.scala @@ -110,7 +110,7 @@ object TypeTransformer { val heapType: watpe.HeapType = ctx.getClassInfoOption(className) match { case Some(info) => if (className == BoxedStringClass) { - if (ctx.coreSpec.wasmFeatures.targetPureWasm) + if (!ctx.hasJSInterop) watpe.HeapType(genTypeID.wasmString) else watpe.HeapType.Extern // for all the JS string builtin functions @@ -153,7 +153,7 @@ object TypeTransformer { case FloatType => watpe.Float32 case DoubleType => watpe.Float64 case StringType => - if (ctx.coreSpec.wasmFeatures.targetPureWasm) + if (!ctx.hasJSInterop) watpe.RefType(genTypeID.wasmString) else watpe.RefType.extern diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/VarGen.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/VarGen.scala index 0e130966e7..cb389fe7e7 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/VarGen.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/VarGen.scala @@ -57,7 +57,7 @@ object VarGen { case object bZeroLong extends GlobalID case object lastIDHashCode extends GlobalID - // targetPureWasm + // Wasm-only, without JS interop case object stringLiteralCache extends GlobalID case object bZeroBoolean extends GlobalID case object bZeroInteger extends GlobalID @@ -264,7 +264,8 @@ object VarGen { case object equals extends JSHelperFunctionID } - object wasmString { // targetPureWasm + // Wasm-only, without JS interop + object wasmString { // case object stringFromCharCode extends FunctionID case object stringConcat extends FunctionID case object stringEquals extends FunctionID @@ -426,7 +427,7 @@ object VarGen { */ case object reflectiveProxies extends FieldID - /** The name data as the 3 arguments to `stringLiteral`. (targetPureWasm only) + /** The name data as the 3 arguments to `stringLiteral` (only without JS interop). * * It is only meaningful for primitives and for classes. For array types, they are all 0, as * array types compute their `name` from the `name` of their component type. @@ -457,7 +458,9 @@ object VarGen { case object fun extends FieldID } - object wasmString { // targetPureWasm + // Wasm-only, without JS interop + object wasmString { + /** Internal i16Array storage for the characters of the string. * * For concatenation optimizations, this array may initially contain @@ -542,8 +545,8 @@ object VarGen { case object typeDataArray extends TypeID case object reflectiveProxies extends TypeID - case object undefined extends TypeID // targetPureWasm - case object wasmString extends TypeID // targetPureWasm + case object undefined extends TypeID // Wasm-only, without JS interop + case object wasmString extends TypeID // Wasm-only, without JS interop // primitive array types, underlying the Array[T] classes case object i8Array extends TypeID diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/WasmContext.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/WasmContext.scala index 69438b45a8..ac9667394f 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/WasmContext.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/WasmContext.scala @@ -25,8 +25,7 @@ import org.scalajs.ir.Trees.{FieldDef, ParamDef, JSNativeLoadSpec} import org.scalajs.ir.Types._ import org.scalajs.ir.WellKnownNames._ -import org.scalajs.linker.interface.ModuleInitializer -import org.scalajs.linker.interface.unstable.ModuleInitializerImpl +import org.scalajs.linker.interface.ModuleKind import org.scalajs.linker.standard.{CoreSpec, LinkedClass, LinkedTopLevelExport} import org.scalajs.linker.backend.emitter.{NameGen => JSNameGen} @@ -52,6 +51,8 @@ final class WasmContext( ) { import WasmContext._ + val hasJSInterop = coreSpec.moduleKind == ModuleKind.ESModule + private val functionTypes = LinkedHashMap.empty[watpe.FunctionType, wanme.TypeID] private val tableFunctionTypes = mutable.HashMap.empty[MethodName, wanme.TypeID] private val closureDataTypes = LinkedHashMap.empty[List[Type], wanme.TypeID] @@ -99,8 +100,8 @@ final class WasmContext( new mutable.LinkedHashSet() val stringPool: StringPool = - if (coreSpec.wasmFeatures.targetPureWasm) new DataStringPool - else new JSStringPool + if (hasJSInterop) new JSStringPool + else new DataStringPool val constantArrayPool: ConstantArrayPool = new ConstantArrayPool diff --git a/linker/shared/src/main/scala/org/scalajs/linker/frontend/LinkTimeProperties.scala b/linker/shared/src/main/scala/org/scalajs/linker/frontend/LinkTimeProperties.scala index 04e30ecb0e..1510169c30 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/frontend/LinkTimeProperties.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/frontend/LinkTimeProperties.scala @@ -34,8 +34,7 @@ final class LinkTimeProperties private ( ModuleKind -> LinkTimeInt(moduleKindInt), IsWebAssembly -> LinkTimeBoolean(targetIsWebAssembly), ProductionMode -> LinkTimeBoolean(semantics.productionMode), - LinkerVersion -> LinkTimeString(ScalaJSVersions.current), - TargetPureWasm -> LinkTimeBoolean(wasmFeatures.targetPureWasm) + LinkerVersion -> LinkTimeString(ScalaJSVersions.current) ) def get(name: String): Option[LinkTimeValue] = @@ -58,9 +57,11 @@ object LinkTimeProperties { // These magic constants are mandated by the values in `scala.scalajs.LinkingInfo.ModuleKind`. import org.scalajs.linker.interface.ModuleKind._ val moduleKindInt = coreSpec.moduleKind match { - case NoModule => 1 - case ESModule => 2 - case CommonJSModule => 3 + case NoModule => 1 + case ESModule => 2 + case CommonJSModule => 3 + case MinimalWasmModule => 4 + case WasmComponent => 5 } new LinkTimeProperties(coreSpec.semantics, coreSpec.esFeatures, diff --git a/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala b/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala index 02d56533b9..7e1bafb9dc 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala @@ -54,8 +54,6 @@ private[optimizer] abstract class OptimizerCore( private val isWasm: Boolean = config.coreSpec.targetIsWebAssembly - private val targetPureWasm: Boolean = config.coreSpec.wasmFeatures.targetPureWasm - // Uncomment and adapt to print debug messages only during one method // lazy val debugThisMethod: Boolean = // debugID == "java.lang.FloatingPointBits$.numberHashCode;D;I" @@ -146,7 +144,7 @@ private[optimizer] abstract class OptimizerCore( !config.coreSpec.esFeatures.allowBigIntsForLongs && !isWasm private val intrinsics = - Intrinsics.buildIntrinsics(config.coreSpec.esFeatures, isWasm, targetPureWasm) + Intrinsics.buildIntrinsics(config.coreSpec) private val integerDivisions = new IntegerDivisions(useRuntimeLong) @@ -7499,14 +7497,13 @@ private[optimizer] object OptimizerCore { ) // scalafmt: {} - def buildIntrinsics(esFeatures: ESFeatures, isWasm: Boolean, - targetPureWasm: Boolean): Intrinsics = { - val allIntrinsics = if (isWasm) { + def buildIntrinsics(coreSpec: CoreSpec): Intrinsics = { + val allIntrinsics = if (coreSpec.targetIsWebAssembly) { commonIntrinsics ::: wasmIntrinsics ::: - (if (targetPureWasm) Nil else wasmJSStringIntrinsics) + (if (coreSpec.moduleKind == ModuleKind.ESModule) wasmJSStringIntrinsics else Nil) } else { val baseIntrinsics = commonIntrinsics ::: baseJSIntrinsics - if (esFeatures.allowBigIntsForLongs) baseIntrinsics + if (coreSpec.esFeatures.allowBigIntsForLongs) baseIntrinsics else baseIntrinsics ++ runtimeLongIntrinsics } diff --git a/project/Build.scala b/project/Build.scala index a7751a3b16..2eb45b82fa 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -242,7 +242,7 @@ object MyScalaJSPlugin extends AutoPlugin { val baseConfig = NodeJSEnv.Config().withSourceMap(wantSourceMaps.value) val config = if (enableWasmEverywhere.value) { val linkerConfig = scalaJSLinkerConfig.value - val additionWasmArgs = if (!linkerConfig.wasmFeatures.targetPureWasm) { + val additionWasmArgs = if (linkerConfig.moduleKind == ModuleKind.ESModule) { List( "--experimental-wasm-exnref", "--experimental-wasm-imported-strings", // for JS string builtins @@ -275,6 +275,10 @@ object MyScalaJSPlugin extends AutoPlugin { case ModuleKind.NoModule => "commonjs" case ModuleKind.CommonJSModule => "commonjs" case ModuleKind.ESModule => "module" + + case ModuleKind.MinimalWasmModule | ModuleKind.WasmComponent => + // Nonsensical, but we need to emit something + "module" } val path = target.value / "package.json" @@ -2085,11 +2089,9 @@ object Build { scalaJSLinkerConfig.value .withPrettyPrint(true) .withExperimentalUseWebAssembly(true) - .withModuleKind(ModuleKind.ESModule) + .withModuleKind(ModuleKind.WasmComponent) .withWasmFeatures { prevFeatures => prevFeatures - .withTargetPureWasm(true) - .withComponentModel(true) .withWitDirectory(Some(witDir.getAbsolutePath)) .withWitWorld(witWorld) } @@ -2108,8 +2110,7 @@ object Build { scalaJSLinkerConfig ~= { _.withPrettyPrint(true) .withExperimentalUseWebAssembly(true) - .withModuleKind(ModuleKind.ESModule) - .withWasmFeatures(_.withTargetPureWasm(true)) + .withModuleKind(ModuleKind.MinimalWasmModule) }, jsEnv := { val config = NodeJSEnv.Config().withArgs(List( @@ -2138,11 +2139,9 @@ object Build { scalaJSLinkerConfig.value .withPrettyPrint(true) .withExperimentalUseWebAssembly(true) - .withModuleKind(ModuleKind.ESModule) + .withModuleKind(ModuleKind.WasmComponent) .withWasmFeatures { prevFeatures => prevFeatures - .withTargetPureWasm(true) - .withComponentModel(true) .withWitDirectory(Some(witDir.getAbsolutePath)) .withWitWorld(witWorld) } @@ -2166,11 +2165,9 @@ object Build { scalaJSLinkerConfig.value .withPrettyPrint(true) .withExperimentalUseWebAssembly(true) - .withModuleKind(ModuleKind.ESModule) + .withModuleKind(ModuleKind.WasmComponent) .withWasmFeatures { prevFeatures => prevFeatures - .withTargetPureWasm(true) - .withComponentModel(true) .withWitDirectory(Some(witDir.getAbsolutePath)) .withWitWorld(witWorld) } @@ -2193,12 +2190,10 @@ object Build { val witWorld = scalaJSWitWorld.value scalaJSLinkerConfig.value .withPrettyPrint(true) - .withModuleKind(ModuleKind.ESModule) + .withModuleKind(ModuleKind.WasmComponent) .withExperimentalUseWebAssembly(true) .withWasmFeatures { prevFeatures => prevFeatures - .withTargetPureWasm(true) - .withComponentModel(true) .withWitDirectory(Some(witDir.getAbsolutePath)) .withWitWorld(witWorld) } @@ -2309,7 +2304,11 @@ object Build { Test / unmanagedSourceDirectories ++= { val config = (Test / scalaJSLinkerConfig).value - val targetPureWasm = config.wasmFeatures.targetPureWasm + + val isWasmNoJS = config.moduleKind match { + case ModuleKind.MinimalWasmModule | ModuleKind.WasmComponent => true + case _ => false + } val testDir = (Test / sourceDirectory).value val sharedTestDir = @@ -2320,7 +2319,7 @@ object Build { val javaV = javaVersion.value val scalaV = scalaVersion.value - if (targetPureWasm) { + if (isWasmNoJS) { List( sharedTestDir / "scala", jsTestDir, // run only a few tests (filtered out in sources) @@ -2339,7 +2338,11 @@ object Build { Test / sources := { val config = (Test / scalaJSLinkerConfig).value - val targetPureWasm = config.wasmFeatures.targetPureWasm + + val isWasmNoJS = config.moduleKind match { + case ModuleKind.MinimalWasmModule | ModuleKind.WasmComponent => true + case _ => false + } def endsWith(f: File, suffix: String): Boolean = f.getPath().replace('\\', '/').endsWith(suffix) @@ -2348,8 +2351,9 @@ object Build { f.getPath().replace('\\', '/').contains(substr) val originalSources = (Test / sources).value - if (!targetPureWasm) originalSources - else { + if (!isWasmNoJS) { + originalSources + } else { originalSources .filter(f => contains(f, "/shared/src/test/scala-old-collections/") || @@ -2489,9 +2493,13 @@ object Build { val moduleKind = linkerConfig.moduleKind val hasModules = moduleKind != ModuleKind.NoModule val isWebAssembly = linkerConfig.experimentalUseWebAssembly - val targetPureWasm = linkerConfig.wasmFeatures.targetPureWasm - if (targetPureWasm) Nil + val isWasmNoJS = linkerConfig.moduleKind match { + case ModuleKind.MinimalWasmModule | ModuleKind.WasmComponent => true + case _ => false + } + + if (isWasmNoJS) Nil else { collectionsEraDependentDirectory(scalaV, testDir) :: includeIf(testDir / "require-new-target", @@ -2515,15 +2523,13 @@ object Build { Test / unmanagedResourceDirectories ++= { val testDir = (Test / sourceDirectory).value - val targetPureWasm = scalaJSLinkerConfig.value.wasmFeatures.targetPureWasm - if (targetPureWasm) Nil - else { - scalaJSLinkerConfig.value.moduleKind match { - case ModuleKind.NoModule => Nil - case ModuleKind.CommonJSModule => Seq(testDir / "resources-commonjs") - case ModuleKind.ESModule => Seq(testDir / "resources-esmodule") - } + scalaJSLinkerConfig.value.moduleKind match { + case ModuleKind.NoModule => Nil + case ModuleKind.CommonJSModule => Seq(testDir / "resources-commonjs") + case ModuleKind.ESModule => Seq(testDir / "resources-esmodule") + case ModuleKind.MinimalWasmModule => Nil + case ModuleKind.WasmComponent => Nil } }, @@ -2566,6 +2572,8 @@ object Build { "isNoModule" -> (moduleKind == ModuleKind.NoModule), "isESModule" -> (moduleKind == ModuleKind.ESModule), "isCommonJSModule" -> (moduleKind == ModuleKind.CommonJSModule), + "isMinimalWasmModule" -> (moduleKind == ModuleKind.MinimalWasmModule), + "isWasmComponent" -> (moduleKind == ModuleKind.WasmComponent), "usesClosureCompiler" -> linkerConfig.closureCompiler, "hasMinifiedNames" -> (linkerConfig.closureCompiler || linkerConfig.minify), "compliantAsInstanceOfs" -> (sems.asInstanceOfs == CheckedBehavior.Compliant), @@ -2579,7 +2587,6 @@ object Build { "esVersion" -> linkerConfig.esFeatures.esVersion.edition, "useECMAScript2015Semantics" -> linkerConfig.esFeatures.useECMAScript2015Semantics, "isWebAssembly" -> linkerConfig.experimentalUseWebAssembly, - "targetPureWasm" -> linkerConfig.wasmFeatures.targetPureWasm, ) }, diff --git a/project/JavalibIRCleaner.scala b/project/JavalibIRCleaner.scala index 131e161891..e24bd72b76 100644 --- a/project/JavalibIRCleaner.scala +++ b/project/JavalibIRCleaner.scala @@ -407,9 +407,6 @@ final class JavalibIRCleaner(baseDirectoryURI: URI) { case IntrinsicCall(LinkingInfoClass, `linkerVersionMethodName`, Nil) => LinkTimeProperty(LinkTimeProperty.LinkerVersion)(StringType) - case IntrinsicCall(LinkingInfoClass, `targetPureWasmMethodName`, Nil) => - LinkTimeProperty(LinkTimeProperty.TargetPureWasm)(BooleanType) - case _ => tree } @@ -723,7 +720,6 @@ object JavalibIRCleaner { private val esVersionMethodName = MethodName("esVersion", Nil, IntRef) private val isWebAssemblyMethodName = MethodName("isWebAssembly", Nil, BooleanRef) private val linkerVersionMethodName = MethodName("linkerVersion", Nil, ClassRef(BoxedStringClass)) - private val targetPureWasmMethodName = MethodName("targetPureWasm", Nil, BooleanRef) private val ClassNameSubstitutions: Map[ClassName, ClassName] = { val refBaseNames = diff --git a/sbt-plugin/src/main/scala/org/scalajs/sbtplugin/ScalaJSPluginInternal.scala b/sbt-plugin/src/main/scala/org/scalajs/sbtplugin/ScalaJSPluginInternal.scala index 8e51b8105b..cda08bcc9b 100644 --- a/sbt-plugin/src/main/scala/org/scalajs/sbtplugin/ScalaJSPluginInternal.scala +++ b/sbt-plugin/src/main/scala/org/scalajs/sbtplugin/ScalaJSPluginInternal.scala @@ -563,6 +563,10 @@ private[sbtplugin] object ScalaJSPluginInternal { case ModuleKind.NoModule => Input.Script(path) case ModuleKind.ESModule => Input.ESModule(path) case ModuleKind.CommonJSModule => Input.CommonJSModule(path) + + case ModuleKind.MinimalWasmModule | ModuleKind.WasmComponent => + // Pretend that we are an ES module for now + Input.ESModule(path) } }, @@ -686,7 +690,7 @@ private[sbtplugin] object ScalaJSPluginInternal { val log = s.log // Only run when component model is enabled - if (!linkerConfig.wasmFeatures.componentModel) { + if (linkerConfig.moduleKind != ModuleKind.WasmComponent) { Seq.empty[File] } else if (!witDir.exists()) { log.debug(s"WIT directory $witDir does not exist, skipping wit-bindgen") diff --git a/scalalib/overrides-2.12/scala/collection/mutable/Buffer.scala b/scalalib/overrides-2.12/scala/collection/mutable/Buffer.scala index 4c7e44a475..530a66b6ef 100644 --- a/scalalib/overrides-2.12/scala/collection/mutable/Buffer.scala +++ b/scalalib/overrides-2.12/scala/collection/mutable/Buffer.scala @@ -47,7 +47,7 @@ trait Buffer[A] extends Seq[A] object Buffer extends SeqFactory[Buffer] { implicit def canBuildFrom[A]: CanBuildFrom[Coll, A, Buffer[A]] = ReusableCBF.asInstanceOf[GenericCanBuildFrom[A]] def newBuilder[A]: Builder[A, Buffer[A]] = - linkTimeIf[Builder[A, Buffer[A]]](LinkingInfo.targetPureWasm) { + linkTimeIf[Builder[A, Buffer[A]]](LinkingInfo.isWebAssembly) { ArrayBuffer.newBuilder[A] } { new js.WrappedArray[A] diff --git a/scalalib/overrides-2.13/scala/Symbol.scala b/scalalib/overrides-2.13/scala/Symbol.scala index 64f3371e34..bc70f427c8 100644 --- a/scalalib/overrides-2.13/scala/Symbol.scala +++ b/scalalib/overrides-2.13/scala/Symbol.scala @@ -13,7 +13,8 @@ package scala import scala.scalajs.js -import scala.scalajs.LinkingInfo.{linkTimeIf, targetPureWasm} +import scala.scalajs.LinkingInfo.{linkTimeIf, moduleKind} +import scala.scalajs.LinkingInfo.ModuleKind.{MinimalWasmModule, WasmComponent} /** This class provides a simple way to get unique objects for equal strings. * Since symbols are interned, they can be compared using reference equality. @@ -48,7 +49,7 @@ object Symbol extends JSUniquenessCache[Symbol] { private[scala] abstract class JSUniquenessCache[V] { private val cacheJS = { - linkTimeIf(!targetPureWasm) { + linkTimeIf(moduleKind != MinimalWasmModule && moduleKind != WasmComponent) { js.Dictionary.empty[V] } { null @@ -56,7 +57,7 @@ private[scala] abstract class JSUniquenessCache[V] } private val cacheWasm = { - linkTimeIf(targetPureWasm) { + linkTimeIf(moduleKind == MinimalWasmModule || moduleKind == WasmComponent) { new java.util.HashMap[String, V]() } { null @@ -67,7 +68,7 @@ private[scala] abstract class JSUniquenessCache[V] protected def keyFromValue(v: V): Option[String] def apply(name: String): V = { - linkTimeIf(targetPureWasm) { + linkTimeIf(moduleKind == MinimalWasmModule || moduleKind == WasmComponent) { cacheWasm.computeIfAbsent(name, _ => valueFromKey(name)) } { cacheJS.getOrElseUpdate(name, valueFromKey(name)) diff --git a/scalalib/overrides-2.13/scala/collection/mutable/Buffer.scala b/scalalib/overrides-2.13/scala/collection/mutable/Buffer.scala index 9d02bcde99..79b83dd776 100644 --- a/scalalib/overrides-2.13/scala/collection/mutable/Buffer.scala +++ b/scalalib/overrides-2.13/scala/collection/mutable/Buffer.scala @@ -226,7 +226,7 @@ trait IndexedBuffer[A] extends IndexedSeq[A] @SerialVersionUID(3L) object Buffer extends SeqFactory.Delegate[Buffer]( - linkTimeIf[SeqFactory[Buffer]](LinkingInfo.targetPureWasm) { + linkTimeIf[SeqFactory[Buffer]](LinkingInfo.isWebAssembly) { ArrayBuffer } { js.WrappedArray @@ -235,7 +235,7 @@ object Buffer extends SeqFactory.Delegate[Buffer]( @SerialVersionUID(3L) object IndexedBuffer extends SeqFactory.Delegate[IndexedBuffer]( - linkTimeIf[SeqFactory[IndexedBuffer]](LinkingInfo.targetPureWasm) { + linkTimeIf[SeqFactory[IndexedBuffer]](LinkingInfo.isWebAssembly) { ArrayBuffer } { js.WrappedArray diff --git a/scalalib/overrides/scala/Symbol.scala b/scalalib/overrides/scala/Symbol.scala index deab491add..55a0df31e7 100644 --- a/scalalib/overrides/scala/Symbol.scala +++ b/scalalib/overrides/scala/Symbol.scala @@ -13,7 +13,8 @@ package scala import scala.scalajs.js -import scala.scalajs.LinkingInfo.{linkTimeIf, targetPureWasm} +import scala.scalajs.LinkingInfo.{linkTimeIf, moduleKind} +import scala.scalajs.LinkingInfo.ModuleKind.{MinimalWasmModule, WasmComponent} /** This class provides a simple way to get unique objects for equal strings. * Since symbols are interned, they can be compared using reference equality. @@ -48,7 +49,7 @@ object Symbol extends JSUniquenessCache[Symbol] { private[scala] abstract class JSUniquenessCache[V] { private val cacheJS = { - linkTimeIf(!targetPureWasm) { + linkTimeIf(moduleKind != MinimalWasmModule && moduleKind != WasmComponent) { js.Dictionary.empty[V] } { null @@ -56,7 +57,7 @@ private[scala] abstract class JSUniquenessCache[V] } private val cacheWasm = { - linkTimeIf(targetPureWasm) { + linkTimeIf(moduleKind == MinimalWasmModule || moduleKind == WasmComponent) { new java.util.HashMap[String, V]() } { null @@ -67,7 +68,7 @@ private[scala] abstract class JSUniquenessCache[V] protected def keyFromValue(v: V): Option[String] def apply(name: String): V = { - linkTimeIf(targetPureWasm) { + linkTimeIf(moduleKind == MinimalWasmModule || moduleKind == WasmComponent) { cacheWasm.computeIfAbsent(name, _ => valueFromKey(name)) } { cacheJS.getOrElseUpdate(name, valueFromKey(name)) diff --git a/scalalib/overrides/scala/runtime/BoxesRunTime.scala b/scalalib/overrides/scala/runtime/BoxesRunTime.scala index 9e92186249..18fa33a89b 100644 --- a/scalalib/overrides/scala/runtime/BoxesRunTime.scala +++ b/scalalib/overrides/scala/runtime/BoxesRunTime.scala @@ -3,7 +3,8 @@ package scala.runtime import scala.math.ScalaNumber import scala.scalajs.LinkingInfo -import scala.scalajs.LinkingInfo.linkTimeIf +import scala.scalajs.LinkingInfo.{linkTimeIf, moduleKind} +import scala.scalajs.LinkingInfo.ModuleKind.{MinimalWasmModule, WasmComponent} /* The declaration of the class is only to make the JVM back-end happy when * compiling the scalalib. @@ -52,7 +53,7 @@ object BoxesRunTime { def unboxToDouble(d: Any): Double = d.asInstanceOf[Double] def equals(x: Object, y: Object): Boolean = - linkTimeIf(LinkingInfo.targetPureWasm) { + linkTimeIf(moduleKind == MinimalWasmModule || moduleKind == WasmComponent) { if (x eq y) { x match { case x: java.lang.Double => x == x // rejects NaN diff --git a/test-bridge/src/main/scala/org/scalajs/testing/bridge/Bridge.scala b/test-bridge/src/main/scala/org/scalajs/testing/bridge/Bridge.scala index 84524ff7c1..4a30738129 100644 --- a/test-bridge/src/main/scala/org/scalajs/testing/bridge/Bridge.scala +++ b/test-bridge/src/main/scala/org/scalajs/testing/bridge/Bridge.scala @@ -14,25 +14,30 @@ package org.scalajs.testing.bridge import scala.scalajs.js import scala.scalajs.js.annotation.{JSGlobalScope, JSName} -import scala.scalajs.LinkingInfo.linkTimeIf -import scala.scalajs.LinkingInfo +import scala.scalajs.LinkingInfo.{linkTimeIf, moduleKind} +import scala.scalajs.LinkingInfo.ModuleKind.{MinimalWasmModule, WasmComponent} import org.scalajs.testing.common._ private[bridge] object Bridge { // Called via org.scalajs.testing.adapter.testAdapterInitializer def start(): Unit = mode match { - case TestBridgeMode.FullBridge => TestAdapterBridge.start() + case TestBridgeMode.FullBridge => + TestAdapterBridge.start() + case TestBridgeMode.HTMLRunner(tests) => - linkTimeIf(LinkingInfo.targetPureWasm) { // shouldn't reach here + linkTimeIf(moduleKind == MinimalWasmModule || moduleKind == WasmComponent) { + // shouldn't reach here throw new AssertionError("The HTML runner is not supported in pure Wasm.") } { HTMLRunner.start(tests) } } - private def mode = { - linkTimeIf(!LinkingInfo.targetPureWasm) { + private def mode: TestBridgeMode = { + linkTimeIf[TestBridgeMode](moduleKind == MinimalWasmModule || moduleKind == WasmComponent) { + TestBridgeMode.FullBridge + } { if (js.typeOf(js.Dynamic.global.__ScalaJSTestBridgeMode) == "undefined") { TestBridgeMode.FullBridge } else { @@ -40,8 +45,6 @@ private[bridge] object Bridge { js.Dynamic.global.__ScalaJSTestBridgeMode.asInstanceOf[String] Serializer.deserialize[TestBridgeMode](modeStr) } - } { - TestBridgeMode.FullBridge } } } diff --git a/test-bridge/src/main/scala/org/scalajs/testing/bridge/JSRPC.scala b/test-bridge/src/main/scala/org/scalajs/testing/bridge/JSRPC.scala index 58ce8e1c0b..fd641de077 100644 --- a/test-bridge/src/main/scala/org/scalajs/testing/bridge/JSRPC.scala +++ b/test-bridge/src/main/scala/org/scalajs/testing/bridge/JSRPC.scala @@ -14,8 +14,8 @@ package org.scalajs.testing.bridge import scala.scalajs.js import scala.scalajs.js.annotation._ -import scala.scalajs.LinkingInfo -import scala.scalajs.LinkingInfo.linkTimeIf +import scala.scalajs.LinkingInfo.{linkTimeIf, moduleKind} +import scala.scalajs.LinkingInfo.ModuleKind.{MinimalWasmModule, WasmComponent} /* Use the queue execution context (based on JS promises) explicitly: * We do not have anything better at our disposal and it is accceptable in @@ -31,7 +31,7 @@ import org.scalajs.testing.common.RPCCore /** JS RPC Core. Uses `scalajsCom`. */ private[bridge] final object JSRPC extends RPCCore { - linkTimeIf(LinkingInfo.targetPureWasm) { + linkTimeIf(moduleKind == MinimalWasmModule || moduleKind == WasmComponent) { ScalajsCom.init() } { Com.init(handleMessage _) @@ -46,7 +46,7 @@ private[bridge] final object JSRPC extends RPCCore { handleMessage(msg) override protected def send(msg: String): Unit = { - linkTimeIf(LinkingInfo.targetPureWasm) { + linkTimeIf(moduleKind == MinimalWasmModule || moduleKind == WasmComponent) { ScalajsCom.send(msg) } { Com.send(msg) diff --git a/test-suite/js/src/main/scala-ide-stubs/org/scalajs/testsuite/utils/BuildInfo.scala b/test-suite/js/src/main/scala-ide-stubs/org/scalajs/testsuite/utils/BuildInfo.scala index 8356a0da7c..7ce0e5e6be 100644 --- a/test-suite/js/src/main/scala-ide-stubs/org/scalajs/testsuite/utils/BuildInfo.scala +++ b/test-suite/js/src/main/scala-ide-stubs/org/scalajs/testsuite/utils/BuildInfo.scala @@ -22,6 +22,8 @@ private[utils] object BuildInfo { final val isNoModule = false final val isESModule = false final val isCommonJSModule = false + final val isMinimalWasmModule = false + final val isWasmComponent = false final val usesClosureCompiler = false final val hasMinifiedNames = false final val compliantAsInstanceOfs = false diff --git a/test-suite/js/src/main/scala/org/scalajs/testsuite/utils/Platform.scala b/test-suite/js/src/main/scala/org/scalajs/testsuite/utils/Platform.scala index efc8f45b73..e9361f3f06 100644 --- a/test-suite/js/src/main/scala/org/scalajs/testsuite/utils/Platform.scala +++ b/test-suite/js/src/main/scala/org/scalajs/testsuite/utils/Platform.scala @@ -13,7 +13,8 @@ package org.scalajs.testsuite.utils import scala.scalajs.js -import scala.scalajs.LinkingInfo.{ESVersion, targetPureWasm, linkTimeIf} +import scala.scalajs.LinkingInfo.{ESVersion, linkTimeIf, moduleKind} +import scala.scalajs.LinkingInfo.ModuleKind.{MinimalWasmModule, WasmComponent} import org.scalajs.testsuite.utils.{BuildInfo => ScalaJSBuildInfo} @@ -34,8 +35,6 @@ object Platform { def executingInWebAssembly: Boolean = BuildInfo.isWebAssembly - def executingInPureWebAssembly: Boolean = BuildInfo.targetPureWasm - def executingInNodeJS: Boolean = { js.typeOf(js.Dynamic.global.process) != "undefined" && !js.isUndefined(js.Dynamic.global.process.release) && @@ -93,7 +92,7 @@ object Platform { def hasCompliantModuleInit: Boolean = BuildInfo.compliantModuleInit def hasDirectBuffers: Boolean = - linkTimeIf(targetPureWasm)(false)(typedArrays) + linkTimeIf(moduleKind == MinimalWasmModule || moduleKind == WasmComponent)(false)(typedArrays) def regexSupportsUnicodeCase: Boolean = assumedESVersion >= ESVersion.ES2015 @@ -107,6 +106,10 @@ object Platform { def isNoModule: Boolean = BuildInfo.isNoModule def isESModule: Boolean = BuildInfo.isESModule def isCommonJSModule: Boolean = BuildInfo.isCommonJSModule + def isMinimalWasmModule: Boolean = BuildInfo.isMinimalWasmModule + def isWasmComponent: Boolean = BuildInfo.isWasmComponent + + def executingInPureWebAssembly: Boolean = isMinimalWasmModule || isWasmComponent /** Runs the specified piece of code in the global context. * diff --git a/test-suite/js/src/test/scala/org/scalajs/testsuite/library/LinkTimeIfTest.scala b/test-suite/js/src/test/scala/org/scalajs/testsuite/library/LinkTimeIfTest.scala index c82eb10cfc..55e1d48129 100644 --- a/test-suite/js/src/test/scala/org/scalajs/testsuite/library/LinkTimeIfTest.scala +++ b/test-suite/js/src/test/scala/org/scalajs/testsuite/library/LinkTimeIfTest.scala @@ -14,6 +14,7 @@ package org.scalajs.testsuite.library import scala.scalajs.js import scala.scalajs.LinkingInfo._ +import scala.scalajs.LinkingInfo.ModuleKind.{MinimalWasmModule, WasmComponent} import org.junit.Test import org.junit.Assert._ @@ -85,13 +86,17 @@ class LinkTimeIfTest { @Test def exponentOp(): Unit = { def pow(x: Double, y: Double): Double = { - linkTimeIf(esVersion >= ESVersion.ES2016 && !targetPureWasm) { + linkTimeIf( + esVersion >= ESVersion.ES2016 && + moduleKind != MinimalWasmModule && moduleKind != WasmComponent) { assertTrue("Took the wrong branch of linkTimeIf when linking for ES 2016+", - esVersion >= ESVersion.ES2016 && !targetPureWasm) + esVersion >= ESVersion.ES2016 && + moduleKind != MinimalWasmModule && moduleKind != WasmComponent) (x.asInstanceOf[js.Dynamic] ** y.asInstanceOf[js.Dynamic]).asInstanceOf[Double] } { assertFalse("Took the wrong branch of linkTimeIf when linking for ES 2015-", - esVersion >= ESVersion.ES2016 && !targetPureWasm) + esVersion >= ESVersion.ES2016 && + moduleKind != MinimalWasmModule && moduleKind != WasmComponent) Math.pow(x, y) } } diff --git a/test-suite/jvm/src/main/scala/scala/scalajs/LinkingInfo.scala b/test-suite/jvm/src/main/scala/scala/scalajs/LinkingInfo.scala index 5dda47fd00..418809c8b2 100644 --- a/test-suite/jvm/src/main/scala/scala/scalajs/LinkingInfo.scala +++ b/test-suite/jvm/src/main/scala/scala/scalajs/LinkingInfo.scala @@ -19,8 +19,13 @@ package scala.scalajs * Once all tests link to pure Wasm and linkTimeIf removed, this shim will no longer be needed. */ object LinkingInfo { - final val targetPureWasm = false + val moduleKind = -1 def linkTimeIf[T](cond: Boolean)(thenp: T)(elsep: T): T = if (cond) thenp else elsep + + object ModuleKind { + final val MinimalWasmModule = 4 + final val WasmComponent = 5 + } } diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/compiler/RegressionTest.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/compiler/RegressionTest.scala index 1a03e1df41..4171e26097 100644 --- a/test-suite/shared/src/test/scala/org/scalajs/testsuite/compiler/RegressionTest.scala +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/compiler/RegressionTest.scala @@ -23,6 +23,8 @@ import org.scalajs.testsuite.utils.AssertThrows.{assertThrows, _} import org.scalajs.testsuite.utils.Platform._ import scala.scalajs.LinkingInfo +import scala.scalajs.LinkingInfo.moduleKind +import scala.scalajs.LinkingInfo.ModuleKind.{MinimalWasmModule, WasmComponent} class RegressionTest { import RegressionTest._ @@ -110,7 +112,7 @@ class RegressionTest { assumeFalse("TODO: mutable.Buffer doesn't link in pure Wasm, it uses typedarray", executingInPureWebAssembly) - LinkingInfo.linkTimeIf(!LinkingInfo.targetPureWasm) { + LinkingInfo.linkTimeIf(moduleKind != MinimalWasmModule && moduleKind != WasmComponent) { val a = scala.collection.mutable.Buffer.empty[Int] a.insert(0, 0) a.remove(0) diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/lang/LongTest.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/lang/LongTest.scala index 351a7af110..4f3f793f20 100644 --- a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/lang/LongTest.scala +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/lang/LongTest.scala @@ -22,6 +22,8 @@ import org.scalajs.testsuite.utils.AssertThrows.assertThrows import org.scalajs.testsuite.utils.Platform.executingInPureWebAssembly import scala.scalajs.LinkingInfo +import scala.scalajs.LinkingInfo.moduleKind +import scala.scalajs.LinkingInfo.ModuleKind.{MinimalWasmModule, WasmComponent} /** Tests the implementation of the java standard library Long * requires jsinterop/LongTest to work to make sense @@ -192,7 +194,7 @@ class LongTest { @Test def parseStringBase2To36(): Unit = { assumeFalse("Doesn't link StringRadixInfos", executingInPureWebAssembly) - LinkingInfo.linkTimeIf(!LinkingInfo.targetPureWasm) { + LinkingInfo.linkTimeIf(moduleKind != MinimalWasmModule && moduleKind != WasmComponent) { def test(radix: Int, s: String, v: Long): Unit = { assertEquals(v, JLong.parseLong(s, radix)) assertEquals(v, JLong.valueOf(s, radix).longValue()) diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/lang/MathTest.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/lang/MathTest.scala index be4dd36654..d01bd641a3 100644 --- a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/lang/MathTest.scala +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/lang/MathTest.scala @@ -27,6 +27,8 @@ import org.scalajs.testsuite.utils.AssertThrows.assertThrows import org.scalajs.testsuite.utils.Platform._ import scala.scalajs.LinkingInfo +import scala.scalajs.LinkingInfo.moduleKind +import scala.scalajs.LinkingInfo.ModuleKind.{MinimalWasmModule, WasmComponent} class MathTest { @@ -532,7 +534,7 @@ class MathTest { @Test def expm1(): Unit = { assumeFalse(executingInPureWebAssembly) - LinkingInfo.linkTimeIf(!LinkingInfo.targetPureWasm) { + LinkingInfo.linkTimeIf(moduleKind != MinimalWasmModule && moduleKind != WasmComponent) { assertTrue(1 / Math.expm1(-0.0) < 0) assertTrue(1 / Math.expm1(0.0) > 0) assertSameDouble(-0.0, Math.expm1(-0.0)) @@ -549,7 +551,7 @@ class MathTest { @Test def sinh(): Unit = { assumeFalse(executingInPureWebAssembly) - LinkingInfo.linkTimeIf(!LinkingInfo.targetPureWasm) { + LinkingInfo.linkTimeIf(moduleKind != MinimalWasmModule && moduleKind != WasmComponent) { assertEquals(Double.NegativeInfinity, Math.sinh(-1234.56), 0.0) assertEquals(Double.PositiveInfinity, Math.sinh(1234.56), 0.0) assertSameDouble(0.0, Math.sinh(0.0)) @@ -562,7 +564,7 @@ class MathTest { @Test def cosh(): Unit = { assumeFalse(executingInPureWebAssembly) - LinkingInfo.linkTimeIf(!LinkingInfo.targetPureWasm) { + LinkingInfo.linkTimeIf(moduleKind != MinimalWasmModule && moduleKind != WasmComponent) { assertEquals(Double.PositiveInfinity, Math.cosh(-1234.56), 0.0) assertEquals(Double.PositiveInfinity, Math.cosh(1234.56), 0.0) assertEquals(1.0, Math.cosh(-0.0), 0.01) @@ -575,7 +577,7 @@ class MathTest { @Test def tanh(): Unit = { assumeFalse(executingInPureWebAssembly) - LinkingInfo.linkTimeIf(!LinkingInfo.targetPureWasm) { + LinkingInfo.linkTimeIf(moduleKind != MinimalWasmModule && moduleKind != WasmComponent) { assertEquals(-1.0, Math.tanh(-1234.56), 0.01) assertEquals(-1.0, Math.tanh(-120.56), 0.01) assertEquals(1.0, Math.tanh(1234.56), 0.01) diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/lang/ThreadTest.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/lang/ThreadTest.scala index 211d29a702..eb83713ce5 100644 --- a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/lang/ThreadTest.scala +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/lang/ThreadTest.scala @@ -19,6 +19,8 @@ import org.junit.Assume._ import org.scalajs.testsuite.utils.Platform.{executingInJVM, executingInPureWebAssembly} import scala.scalajs.LinkingInfo +import scala.scalajs.LinkingInfo.moduleKind +import scala.scalajs.LinkingInfo.ModuleKind.{MinimalWasmModule, WasmComponent} class ThreadTest { @@ -38,7 +40,7 @@ class ThreadTest { @Test def currentThreadGetStackTrace(): Unit = { assumeFalse("Doesn't link in pure Wasm", executingInPureWebAssembly) - LinkingInfo.linkTimeIf(!LinkingInfo.targetPureWasm) { + LinkingInfo.linkTimeIf(moduleKind != MinimalWasmModule && moduleKind != WasmComponent) { val _ = Thread.currentThread().getStackTrace() } {} } diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/BitSetTest.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/BitSetTest.scala index 6ad19b7a0f..4d51355d08 100644 --- a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/BitSetTest.scala +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/BitSetTest.scala @@ -21,6 +21,8 @@ import org.scalajs.testsuite.utils.AssertThrows._ import org.scalajs.testsuite.utils.Platform._ import scala.scalajs.LinkingInfo +import scala.scalajs.LinkingInfo.moduleKind +import scala.scalajs.LinkingInfo.ModuleKind.{MinimalWasmModule, WasmComponent} class BitSetTest { @Test def test_Constructor_empty(): Unit = { @@ -1499,7 +1501,7 @@ class BitSetTest { @Test def valueOf_ByteBuffer_typedArrays(): Unit = { assumeFalse("requires support for direct Buffers, which isn't available in pure Wasm", executingInPureWebAssembly) - LinkingInfo.linkTimeIf(!LinkingInfo.targetPureWasm) { + LinkingInfo.linkTimeIf(moduleKind != MinimalWasmModule && moduleKind != WasmComponent) { assumeTrue("requires support for direct Buffers", hasDirectBuffers) val eightBS = makeEightBS() From 22d48c03c882566aa824b65bc9d1e9f43a2fe157 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Fri, 13 Mar 2026 10:29:29 +0100 Subject: [PATCH 12/32] Fix #5335: Throw a user-friendly exception on inconsistent config. --- .../linker/frontend/LinkerFrontendImpl.scala | 5 ++ .../linker/IncompatibleConfigTest.scala | 55 +++++++++++++++++++ 2 files changed, 60 insertions(+) create mode 100644 linker/shared/src/test/scala/org/scalajs/linker/IncompatibleConfigTest.scala diff --git a/linker/shared/src/main/scala/org/scalajs/linker/frontend/LinkerFrontendImpl.scala b/linker/shared/src/main/scala/org/scalajs/linker/frontend/LinkerFrontendImpl.scala index 1dc406b45a..7f6adf9575 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/frontend/LinkerFrontendImpl.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/frontend/LinkerFrontendImpl.scala @@ -36,6 +36,11 @@ final class LinkerFrontendImpl private (config: LinkerFrontendImpl.Config) exten /** Core specification that this linker frontend implements. */ val coreSpec = config.commonConfig.coreSpec + require( + coreSpec.moduleKind != ModuleKind.NoModule || + config.moduleSplitStyle == ModuleSplitStyle.FewestModules, + s"NoModule requires ModuleSplitStyle.FewestModules; was ${config.moduleSplitStyle}.") + private[this] val linker: BaseLinker = new BaseLinker(config.commonConfig, config.checkIR) diff --git a/linker/shared/src/test/scala/org/scalajs/linker/IncompatibleConfigTest.scala b/linker/shared/src/test/scala/org/scalajs/linker/IncompatibleConfigTest.scala new file mode 100644 index 0000000000..9ac2786fb6 --- /dev/null +++ b/linker/shared/src/test/scala/org/scalajs/linker/IncompatibleConfigTest.scala @@ -0,0 +1,55 @@ +/* + * Scala.js (https://www.scala-js.org/) + * + * Copyright EPFL. + * + * Licensed under Apache License 2.0 + * (https://www.apache.org/licenses/LICENSE-2.0). + * + * See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + */ + +package org.scalajs.linker + +import org.junit.Test +import org.junit.Assert._ + +import org.scalajs.linker.interface._ + +/** Tests for the error messages that we get when trying to use invalid configurations. */ +class IncompatibleConfigTest { + private val baseC = StandardConfig() + + private def test(expectedMessage: String, config: StandardConfig): Unit = { + val e = assertThrows(classOf[IllegalArgumentException], + () => StandardImpl.linker(config)) + assertEquals(expectedMessage, e.getMessage().stripPrefix("requirement failed: ")) + } + + @Test def inconsistentConfigTest(): Unit = { + // #5335 NoModule requires ModuleSplitStyle.FewestModules + test("NoModule requires ModuleSplitStyle.FewestModules; was SmallestModules.", + baseC.withModuleSplitStyle(ModuleSplitStyle.SmallestModules)) + test("NoModule requires ModuleSplitStyle.FewestModules; was SmallModulesFor(List(foo)).", + baseC.withModuleSplitStyle(ModuleSplitStyle.SmallModulesFor(List("foo")))) + } + + @deprecated("tests deprecated APIs", since = "forever") + @Test def configNotSupportedByWasmBackend(): Unit = { + val wasmC = baseC + .withExperimentalUseWebAssembly(true) + .withModuleKind(ModuleKind.ESModule) + + // Unsupported ModuleKind + val supportedModuleKinds = Set[ModuleKind](ModuleKind.ESModule) + for (moduleKind <- ModuleKind.All if !supportedModuleKinds.contains(moduleKind)) { + test(s"The WebAssembly backend only supports ES modules; was $moduleKind.", + wasmC.withModuleKind(moduleKind)) + } + + // ES 5.1 + test("The WebAssembly backend only supports the ECMAScript 2015 semantics.", + wasmC.withESFeatures(_.withESVersion(ESVersion.ES5_1))) + } +} From 20f9f18645e55f4585a2c03bc0b0ab810c1d1bdf Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 13 Mar 2026 23:13:55 +0000 Subject: [PATCH 13/32] Bump undici from 7.22.0 to 7.24.1 Bumps [undici](https://github.com/nodejs/undici) from 7.22.0 to 7.24.1. - [Release notes](https://github.com/nodejs/undici/releases) - [Commits](https://github.com/nodejs/undici/compare/v7.22.0...v7.24.1) --- updated-dependencies: - dependency-name: undici dependency-version: 7.24.1 dependency-type: indirect ... Signed-off-by: dependabot[bot] --- package-lock.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index c1ab759c5f..1a93c3c75a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1469,9 +1469,9 @@ } }, "node_modules/undici": { - "version": "7.22.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-7.22.0.tgz", - "integrity": "sha512-RqslV2Us5BrllB+JeiZnK4peryVTndy9Dnqq62S3yYRRTj0tFQCwEniUy2167skdGOy3vqRzEvl1Dm4sV2ReDg==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.24.1.tgz", + "integrity": "sha512-5xoBibbmnjlcR3jdqtY2Lnx7WbrD/tHlT01TmvqZUFVc9Q1w4+j5hbnapTqbcXITMH1ovjq/W7BkqBilHiVAaA==", "dev": true, "engines": { "node": ">=20.18.1" @@ -2617,9 +2617,9 @@ } }, "undici": { - "version": "7.22.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-7.22.0.tgz", - "integrity": "sha512-RqslV2Us5BrllB+JeiZnK4peryVTndy9Dnqq62S3yYRRTj0tFQCwEniUy2167skdGOy3vqRzEvl1Dm4sV2ReDg==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.24.1.tgz", + "integrity": "sha512-5xoBibbmnjlcR3jdqtY2Lnx7WbrD/tHlT01TmvqZUFVc9Q1w4+j5hbnapTqbcXITMH1ovjq/W7BkqBilHiVAaA==", "dev": true }, "unpipe": { From 0cce77cce4e334837e68241af5d7f80ff99eb22d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Wed, 4 Mar 2026 15:38:49 +0100 Subject: [PATCH 14/32] Fix: Return 0 in CharArrayReader(_, _, 0) when past the end. Previously, we were returning -1. --- javalib/src/main/scala/java/io/CharArrayReader.scala | 4 +++- .../testsuite/javalib/io/CharArrayReaderTest.scala | 11 +++++++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/javalib/src/main/scala/java/io/CharArrayReader.scala b/javalib/src/main/scala/java/io/CharArrayReader.scala index 627f0613dd..3fe5723158 100644 --- a/javalib/src/main/scala/java/io/CharArrayReader.scala +++ b/javalib/src/main/scala/java/io/CharArrayReader.scala @@ -55,7 +55,9 @@ class CharArrayReader(protected var buf: Array[Char], offset: Int, length: Int) ensureOpen() - if (this.pos < this.count) { + if (len == 0) { + 0 + } else if (this.pos < this.count) { val bytesRead = Math.min(len, this.count - this.pos) System.arraycopy(this.buf, this.pos, buffer, offset, bytesRead) this.pos += bytesRead diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/io/CharArrayReaderTest.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/io/CharArrayReaderTest.scala index 871e425ffa..0a7c0ea775 100644 --- a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/io/CharArrayReaderTest.scala +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/io/CharArrayReaderTest.scala @@ -87,8 +87,15 @@ class CharArrayReaderTest { @Test def readCharArray(): Unit = { withClose(new CharArrayReader(hw)) { cr => val c = new Array[Char](11) - cr.read(c, 1, 10) - assertArrayEquals(Array(0.toChar) ++ hw, c) + assertEquals(4, cr.read(c, 1, 4)) + assertEquals("\u0000Hell" + "\u0000" * 6, String.valueOf(c)) + assertEquals(0, cr.read(c, 3, 0)) + assertThrows(classOf[IndexOutOfBoundsException], cr.read(c, 3, 10)) + assertEquals("\u0000Hell" + "\u0000" * 6, String.valueOf(c)) + assertEquals(6, cr.read(c, 3, 8)) + assertEquals("\u0000HeoWorld" + "\u0000" * 2, String.valueOf(c)) + assertEquals(-1, cr.read(c, 3, 1)) + assertEquals(0, cr.read(c, 3, 0)) } } From dbf2ef584c8df5f07b97b4604b3d898c145f1e7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Wed, 4 Mar 2026 12:05:52 +0100 Subject: [PATCH 15/32] In StringBuilder, rely on the underlying string's StringIOOBEs. Strengthen the tests when in compliant mode. --- .../main/scala/java/lang/StringBuilder.scala | 90 ++++++++---- .../javalib/lang/StringBufferTest.scala | 131 ++++++----------- .../javalib/lang/StringBuilderTest.scala | 134 ++++++------------ .../testsuite/utils/AssertThrows.scala | 5 + 4 files changed, 157 insertions(+), 203 deletions(-) diff --git a/javalib/src/main/scala/java/lang/StringBuilder.scala b/javalib/src/main/scala/java/lang/StringBuilder.scala index a27e93c8fc..d3fb54138f 100644 --- a/javalib/src/main/scala/java/lang/StringBuilder.scala +++ b/javalib/src/main/scala/java/lang/StringBuilder.scala @@ -48,14 +48,23 @@ class StringBuilder extends AnyRef with CharSequence with Appendable with java.i def append(s: CharSequence): StringBuilder = append(s: AnyRef) - def append(s: CharSequence, start: Int, end: Int): StringBuilder = - append((if (s == null) "null" else s).subSequence(start, end)) + def append(s: CharSequence, start: Int, end: Int): StringBuilder = { + val s2 = if (s == null) "null" else s + checkStartEnd(start, end, s2.length()) + append(s2.subSequence(start, end).toString()) + } def append(str: Array[scala.Char]): StringBuilder = append(String.valueOf(str)) - def append(str: Array[scala.Char], offset: Int, len: Int): StringBuilder = + def append(str: Array[scala.Char], offset: Int, len: Int): StringBuilder = { + /* Since we check offset >= 0, if offset + len can only overflow from the + * positives into the negatives, in which case offset + len < offset, which + * is reported as well. + */ + checkStartEnd(offset, offset + len, str.length) append(String.valueOf(str, offset, len)) + } def append(b: scala.Boolean): StringBuilder = append(b.toString()) def append(c: scala.Char): StringBuilder = append(c.toString()) @@ -73,10 +82,10 @@ class StringBuilder extends AnyRef with CharSequence with Appendable with java.i def deleteCharAt(index: Int): StringBuilder = { /* This is not equivalent to `delete(index, index + 1)` when * `index == length`. + * + * The two calls to substring imply the required bounds checks. */ val oldContent = content - if (index < 0 || index >= oldContent.length) - throw new StringIndexOutOfBoundsException(index) content = oldContent.substring(0, index) + oldContent.substring(index + 1) this } @@ -84,9 +93,12 @@ class StringBuilder extends AnyRef with CharSequence with Appendable with java.i def replace(start: Int, end: Int, str: String): StringBuilder = { val oldContent = content val length = oldContent.length - if (start < 0 || start > length || start > end) - throw new StringIndexOutOfBoundsException(start) + + // The call to substring implies the bounds checks for 0 <= start <= length val firstPart = oldContent.substring(0, start) + str + if (end < start) + oldContent.charAt(-1) + content = if (end >= length) firstPart else firstPart + oldContent.substring(end) @@ -95,6 +107,7 @@ class StringBuilder extends AnyRef with CharSequence with Appendable with java.i def insert(index: Int, str: Array[scala.Char], offset: Int, len: Int): StringBuilder = { + // Surprisingly, this overload specifies a StringIOOBE. insert(index, String.valueOf(str, offset, len)) } @@ -102,9 +115,8 @@ class StringBuilder extends AnyRef with CharSequence with Appendable with java.i insert(offset, String.valueOf(obj)) def insert(offset: Int, str: String): StringBuilder = { + // The first call to substring implies the required bounds checks val oldContent = content - if (offset < 0 || offset > oldContent.length) - throw new StringIndexOutOfBoundsException(offset) content = oldContent.substring(0, offset) + str + oldContent.substring(offset) this @@ -113,19 +125,26 @@ class StringBuilder extends AnyRef with CharSequence with Appendable with java.i def insert(offset: Int, str: Array[scala.Char]): StringBuilder = insert(offset, String.valueOf(str)) - def insert(dstOffset: Int, s: CharSequence): StringBuilder = - insert(dstOffset, s: AnyRef) + def insert(dstOffset: Int, s: CharSequence): StringBuilder = { + checkInsertOffset(dstOffset) + insert(dstOffset, String.valueOf(s)) + } def insert(dstOffset: Int, s: CharSequence, start: Int, end: Int): StringBuilder = { - insert(dstOffset, (if (s == null) "null" else s).subSequence(start, end)) + checkInsertOffset(dstOffset) + val s2 = if (s == null) "null" else s + checkStartEnd(start, end, s2.length()) + insert(dstOffset, s2.subSequence(start, end).toString()) } def insert(offset: Int, b: scala.Boolean): StringBuilder = insert(offset, b.toString) - def insert(offset: Int, c: scala.Char): StringBuilder = + def insert(offset: Int, c: scala.Char): StringBuilder = { + checkInsertOffset(offset) insert(offset, c.toString) + } def insert(offset: Int, i: scala.Int): StringBuilder = insert(offset, i.toString) @@ -139,6 +158,28 @@ class StringBuilder extends AnyRef with CharSequence with Appendable with java.i def insert(offset: Int, d: scala.Double): StringBuilder = insert(offset, d.toString) + /** Explicitly checks an insertion offset. + * + * Used by the overloads of insert() that specify IOOBE, instead of UB + * StringIOOBE. + */ + @inline + private def checkInsertOffset(offset: Int): Unit = { + if (offset < 0 || offset > content.length) + throw new IndexOutOfBoundsException(offset) + } + + /** Explicitly checks start and end indices. + * + * Used by the overloads of append() and insert() that specify IOOBE, + * instead of UB StringIOOBE. + */ + @inline + private def checkStartEnd(start: Int, end: Int, length: Int): Unit = { + if (start < 0 || start > end || end > length) + throw new IndexOutOfBoundsException() + } + def indexOf(str: String): Int = content.indexOf(str) def indexOf(str: String, fromIndex: Int): Int = @@ -186,20 +227,20 @@ class StringBuilder extends AnyRef with CharSequence with Appendable with java.i def trimToSize(): Unit = () def setLength(newLength: Int): Unit = { - if (newLength < 0) - throw new StringIndexOutOfBoundsException(newLength) var newContent = content - val additional = newLength - newContent.length // cannot overflow - if (additional < 0) { - newContent = newContent.substring(0, newLength) - } else { - var i = 0 - while (i != additional) { + var currentLength = newContent.length() + + if (newLength >= currentLength) { + // Implies newLength >= 0, so bounds are OK + while (currentLength != newLength) { newContent += "\u0000" - i += 1 + currentLength += 1 } + content = newContent + } else { + // The call to substring implies the required bounds check + content = newContent.substring(0, newLength) } - content = newContent } def charAt(index: Int): Char = content.charAt(index) @@ -220,9 +261,8 @@ class StringBuilder extends AnyRef with CharSequence with Appendable with java.i } def setCharAt(index: Int, ch: scala.Char): Unit = { + // The two calls to substring imply the required bounds checks. val oldContent = content - if (index < 0 || index >= oldContent.length) - throw new StringIndexOutOfBoundsException(index) content = oldContent.substring(0, index) + ch + oldContent.substring(index + 1) } diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/lang/StringBufferTest.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/lang/StringBufferTest.scala index dee8b07824..bb4f1146e1 100644 --- a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/lang/StringBufferTest.scala +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/lang/StringBufferTest.scala @@ -15,7 +15,7 @@ package org.scalajs.testsuite.javalib.lang import org.junit.Test import org.junit.Assert._ -import org.scalajs.testsuite.utils.AssertThrows.assertThrows +import org.scalajs.testsuite.utils.AssertThrows.{assertThrows, _} import org.scalajs.testsuite.utils.Platform.executingInJVM import WrappedStringCharSequence.charSequence @@ -167,10 +167,8 @@ class StringBufferTest { assertEquals("hello", resultFor("hello", 5, 5)) assertEquals("hel", resultFor("hello", 3, 8)) - assertThrows(classOf[StringIndexOutOfBoundsException], - resultFor("hello", -1, 2)) - assertThrows(classOf[StringIndexOutOfBoundsException], - resultFor("hello", 3, 2)) + assertThrowsStringIIOBEIfCompliant(resultFor("hello", -1, 2)) + assertThrowsStringIIOBEIfCompliant(resultFor("hello", 3, 2)) } @Test def deleteCharAt(): Unit = { @@ -181,10 +179,8 @@ class StringBufferTest { assertEquals("123", resultFor("0123", 0)) assertEquals("012", resultFor("0123", 3)) - assertThrows(classOf[StringIndexOutOfBoundsException], - resultFor("0123", -1)) - assertThrows(classOf[StringIndexOutOfBoundsException], - resultFor("0123", 4)) + assertThrowsStringIIOBEIfCompliant(resultFor("0123", -1)) + assertThrowsStringIIOBEIfCompliant(resultFor("0123", 4)) } @Test def replace(): Unit = { @@ -199,12 +195,9 @@ class StringBufferTest { assertEquals("0xxxx123", resultFor("0123", 1, 1, "xxxx")) assertEquals("0123x", resultFor("0123", 4, 5, "x")) - assertThrows(classOf[StringIndexOutOfBoundsException], - resultFor("0123", -1, 3, "x")) - assertThrows(classOf[StringIndexOutOfBoundsException], - resultFor("0123", 4, 3, "x")) - assertThrows(classOf[StringIndexOutOfBoundsException], - resultFor("0123", 5, 8, "x")) + assertThrowsStringIIOBEIfCompliant(resultFor("0123", -1, 3, "x")) + assertThrowsStringIIOBEIfCompliant(resultFor("0123", 4, 3, "x")) + assertThrowsStringIIOBEIfCompliant(resultFor("0123", 5, 8, "x")) if (executingInJVM) assertThrows(classOf[NullPointerException], resultFor("0123", 1, 3, null)) @@ -221,16 +214,12 @@ class StringBufferTest { assertEquals("0bc12", resultFor("012", 1, arr, 1, 2)) assertEquals("abcdef", resultFor("abef", 2, arr, 2, 2)) - assertThrows(classOf[StringIndexOutOfBoundsException], - resultFor("1234", -1, arr, 1, 2)) - assertThrows(classOf[StringIndexOutOfBoundsException], - resultFor("1234", 6, arr, 1, 2)) - assertThrows(classOf[StringIndexOutOfBoundsException], - resultFor("1234", 1, arr, -1, 2)) - assertThrows(classOf[StringIndexOutOfBoundsException], - resultFor("1234", 1, arr, 1, -2)) - assertThrows(classOf[StringIndexOutOfBoundsException], - resultFor("1234", 1, arr, 4, 3)) + // Surprisingly, this overload specifies a StringIOOBE. + assertThrowsStringIIOBEIfCompliant(resultFor("1234", -1, arr, 1, 2)) + assertThrowsStringIIOBEIfCompliant(resultFor("1234", 6, arr, 1, 2)) + assertThrowsStringIIOBEIfCompliant(resultFor("1234", 1, arr, -1, 2)) + assertThrowsStringIIOBEIfCompliant(resultFor("1234", 1, arr, 1, -2)) + assertThrowsStringIIOBEIfCompliant(resultFor("1234", 1, arr, 4, 3)) if (executingInJVM) { assertThrows(classOf[NullPointerException], @@ -247,10 +236,8 @@ class StringBufferTest { assertEquals("01hello234", resultFor("01234", 2, "hello")) assertEquals("01foobar234", resultFor("01234", 2, charSequence("foobar"))) - assertThrows(classOf[StringIndexOutOfBoundsException], - resultFor("1234", -1, "foo")) - assertThrows(classOf[StringIndexOutOfBoundsException], - resultFor("1234", 6, "foo")) + assertThrowsStringIIOBEIfCompliant(resultFor("1234", -1, "foo")) + assertThrowsStringIIOBEIfCompliant(resultFor("1234", 6, "foo")) } @Test def insertString(): Unit = { @@ -260,10 +247,8 @@ class StringBufferTest { assertEquals("01null234", resultFor("01234", 2, null)) assertEquals("01hello234", resultFor("01234", 2, "hello")) - assertThrows(classOf[StringIndexOutOfBoundsException], - resultFor("1234", -1, "foo")) - assertThrows(classOf[StringIndexOutOfBoundsException], - resultFor("1234", 6, "foo")) + assertThrowsStringIIOBEIfCompliant(resultFor("1234", -1, "foo")) + assertThrowsStringIIOBEIfCompliant(resultFor("1234", 6, "foo")) } @Test def insertCharArray(): Unit = { @@ -275,10 +260,8 @@ class StringBufferTest { assertEquals("0abcde12", resultFor("012", 1, arr)) assertEquals("ababcdeef", resultFor("abef", 2, arr)) - assertThrows(classOf[StringIndexOutOfBoundsException], - resultFor("1234", -1, arr)) - assertThrows(classOf[StringIndexOutOfBoundsException], - resultFor("1234", 6, arr)) + assertThrowsStringIIOBEIfCompliant(resultFor("1234", -1, arr)) + assertThrowsStringIIOBEIfCompliant(resultFor("1234", 6, arr)) if (executingInJVM) assertThrows(classOf[NullPointerException], resultFor("1234", 1, null)) @@ -337,10 +320,8 @@ class StringBufferTest { assertEquals("a4bcd", initBuffer("abcd").insert(1, 4.toByte).toString) assertEquals("a304bcd", initBuffer("abcd").insert(1, 304.toShort).toString) - assertThrows(classOf[StringIndexOutOfBoundsException], - initBuffer("abcd").insert(5, 56)) - assertThrows(classOf[StringIndexOutOfBoundsException], - initBuffer("abcd").insert(-1, 56)) + assertThrowsStringIIOBEIfCompliant(initBuffer("abcd").insert(5, 56)) + assertThrowsStringIIOBEIfCompliant(initBuffer("abcd").insert(-1, 56)) } @Test def indexOfString(): Unit = { @@ -419,7 +400,7 @@ class StringBufferTest { @Test def setLength(): Unit = { val b = initBuffer("foobar") - assertThrows(classOf[StringIndexOutOfBoundsException], b.setLength(-3)) + assertThrowsStringIIOBEIfCompliant(b.setLength(-3)) b.setLength(3) assertEquals("foo", b.toString) @@ -435,11 +416,9 @@ class StringBufferTest { assertEquals('\ud801', resultFor("ab\ud801\udc02cd", 2)) assertEquals('\udc02', resultFor("ab\ud801\udc02cd", 3)) - if (executingInJVM) { - assertThrows(classOf[IndexOutOfBoundsException], resultFor("hello", -1)) - assertThrows(classOf[IndexOutOfBoundsException], resultFor("hello", 5)) - assertThrows(classOf[IndexOutOfBoundsException], resultFor("hello", 6)) - } + assertThrowsStringIIOBEIfCompliant(resultFor("hello", -1)) + assertThrowsStringIIOBEIfCompliant(resultFor("hello", 5)) + assertThrowsStringIIOBEIfCompliant(resultFor("hello", 6)) } @Test def codePointAt(): Unit = { @@ -456,12 +435,8 @@ class StringBufferTest { assertEquals(0xdf06, resultFor("\udf06abc", 0)) assertEquals(0xd834, resultFor("abc\ud834", 3)) - if (executingInJVM) { - assertThrows(classOf[IndexOutOfBoundsException], - resultFor("abc\ud834\udf06def", -1)) - assertThrows(classOf[IndexOutOfBoundsException], - resultFor("abc\ud834\udf06def", 15)) - } + assertThrowsStringIIOBEIfCompliant(resultFor("abc\ud834\udf06def", -1)) + assertThrowsStringIIOBEIfCompliant(resultFor("abc\ud834\udf06def", 15)) } @Test def codePointBefore(): Unit = { @@ -477,12 +452,8 @@ class StringBufferTest { assertEquals(0xd834, resultFor("\ud834abc", 1)) assertEquals(0xdf06, resultFor("\udf06abc", 1)) - if (executingInJVM) { - assertThrows(classOf[IndexOutOfBoundsException], - resultFor("abc\ud834\udf06def", 0)) - assertThrows(classOf[IndexOutOfBoundsException], - resultFor("abc\ud834\udf06def", 15)) - } + assertThrowsStringIIOBEIfCompliant(resultFor("abc\ud834\udf06def", 0)) + assertThrowsStringIIOBEIfCompliant(resultFor("abc\ud834\udf06def", 15)) } @Test def codePointCount(): Unit = { @@ -568,10 +539,8 @@ class StringBufferTest { assertEquals("foxbar", resultFor("foobar", 2, 'x')) assertEquals("foobah", resultFor("foobar", 5, 'h')) - assertThrows(classOf[StringIndexOutOfBoundsException], - resultFor("foobar", -1, 'h')) - assertThrows(classOf[StringIndexOutOfBoundsException], - resultFor("foobar", 6, 'h')) + assertThrowsStringIIOBEIfCompliant(resultFor("foobar", -1, 'h')) + assertThrowsStringIIOBEIfCompliant(resultFor("foobar", 6, 'h')) } @Test def substringStart(): Unit = { @@ -581,12 +550,8 @@ class StringBufferTest { assertEquals("llo", resultFor("hello", 2)) assertEquals("", resultFor("hello", 5)) - if (executingInJVM) { - assertThrows(classOf[StringIndexOutOfBoundsException], - resultFor("hello", -1)) - assertThrows(classOf[StringIndexOutOfBoundsException], - resultFor("hello", 8)) - } + assertThrowsStringIIOBEIfCompliant(resultFor("hello", -1)) + assertThrowsStringIIOBEIfCompliant(resultFor("hello", 8)) } @Test def subSequence(): Unit = { @@ -601,16 +566,10 @@ class StringBufferTest { assertEquals("", resultFor("hello", 5, 5)) assertEquals("hel", resultFor("hello", 0, 3)) - if (executingInJVM) { - assertThrows(classOf[StringIndexOutOfBoundsException], - resultFor("hello", -1, 3)) - assertThrows(classOf[StringIndexOutOfBoundsException], - resultFor("hello", 8, 8)) - assertThrows(classOf[StringIndexOutOfBoundsException], - resultFor("hello", 3, 2)) - assertThrows(classOf[StringIndexOutOfBoundsException], - resultFor("hello", 3, 8)) - } + assertThrowsStringIIOBEIfCompliant(resultFor("hello", -1, 3)) + assertThrowsStringIIOBEIfCompliant(resultFor("hello", 8, 8)) + assertThrowsStringIIOBEIfCompliant(resultFor("hello", 3, 2)) + assertThrowsStringIIOBEIfCompliant(resultFor("hello", 3, 8)) } @Test def substringStartEnd(): Unit = { @@ -621,15 +580,9 @@ class StringBufferTest { assertEquals("", resultFor("hello", 5, 5)) assertEquals("hel", resultFor("hello", 0, 3)) - if (executingInJVM) { - assertThrows(classOf[StringIndexOutOfBoundsException], - resultFor("hello", -1, 3)) - assertThrows(classOf[StringIndexOutOfBoundsException], - resultFor("hello", 8, 8)) - assertThrows(classOf[StringIndexOutOfBoundsException], - resultFor("hello", 3, 2)) - assertThrows(classOf[StringIndexOutOfBoundsException], - resultFor("hello", 3, 8)) - } + assertThrowsStringIIOBEIfCompliant(resultFor("hello", -1, 3)) + assertThrowsStringIIOBEIfCompliant(resultFor("hello", 8, 8)) + assertThrowsStringIIOBEIfCompliant(resultFor("hello", 3, 2)) + assertThrowsStringIIOBEIfCompliant(resultFor("hello", 3, 8)) } } diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/lang/StringBuilderTest.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/lang/StringBuilderTest.scala index 0a703cd535..dccff37c9f 100644 --- a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/lang/StringBuilderTest.scala +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/lang/StringBuilderTest.scala @@ -17,7 +17,7 @@ import java.lang.StringBuilder import org.junit.Test import org.junit.Assert._ -import org.scalajs.testsuite.utils.AssertThrows.assertThrows +import org.scalajs.testsuite.utils.AssertThrows.{assertThrows, _} import org.scalajs.testsuite.utils.Platform.executingInJVM import WrappedStringCharSequence.charSequence @@ -169,10 +169,8 @@ class StringBuilderTest { assertEquals("hello", resultFor("hello", 5, 5)) assertEquals("hel", resultFor("hello", 3, 8)) - assertThrows(classOf[StringIndexOutOfBoundsException], - resultFor("hello", -1, 2)) - assertThrows(classOf[StringIndexOutOfBoundsException], - resultFor("hello", 3, 2)) + assertThrowsStringIIOBEIfCompliant(resultFor("hello", -1, 2)) + assertThrowsStringIIOBEIfCompliant(resultFor("hello", 3, 2)) } @Test def deleteCharAt(): Unit = { @@ -183,10 +181,8 @@ class StringBuilderTest { assertEquals("123", resultFor("0123", 0)) assertEquals("012", resultFor("0123", 3)) - assertThrows(classOf[StringIndexOutOfBoundsException], - resultFor("0123", -1)) - assertThrows(classOf[StringIndexOutOfBoundsException], - resultFor("0123", 4)) + assertThrowsStringIIOBEIfCompliant(resultFor("0123", -1)) + assertThrowsStringIIOBEIfCompliant(resultFor("0123", 4)) } @Test def replace(): Unit = { @@ -201,12 +197,9 @@ class StringBuilderTest { assertEquals("0xxxx123", resultFor("0123", 1, 1, "xxxx")) assertEquals("0123x", resultFor("0123", 4, 5, "x")) - assertThrows(classOf[StringIndexOutOfBoundsException], - resultFor("0123", -1, 3, "x")) - assertThrows(classOf[StringIndexOutOfBoundsException], - resultFor("0123", 4, 3, "x")) - assertThrows(classOf[StringIndexOutOfBoundsException], - resultFor("0123", 5, 8, "x")) + assertThrowsStringIIOBEIfCompliant(resultFor("0123", -1, 3, "x")) + assertThrowsStringIIOBEIfCompliant(resultFor("0123", 4, 3, "x")) + assertThrowsStringIIOBEIfCompliant(resultFor("0123", 5, 8, "x")) if (executingInJVM) assertThrows(classOf[NullPointerException], resultFor("0123", 1, 3, null)) @@ -223,16 +216,12 @@ class StringBuilderTest { assertEquals("0bc12", resultFor("012", 1, arr, 1, 2)) assertEquals("abcdef", resultFor("abef", 2, arr, 2, 2)) - assertThrows(classOf[StringIndexOutOfBoundsException], - resultFor("1234", -1, arr, 1, 2)) - assertThrows(classOf[StringIndexOutOfBoundsException], - resultFor("1234", 6, arr, 1, 2)) - assertThrows(classOf[StringIndexOutOfBoundsException], - resultFor("1234", 1, arr, -1, 2)) - assertThrows(classOf[StringIndexOutOfBoundsException], - resultFor("1234", 1, arr, 1, -2)) - assertThrows(classOf[StringIndexOutOfBoundsException], - resultFor("1234", 1, arr, 4, 3)) + // Surprisingly, this overload specifies a StringIOOBE. + assertThrowsStringIIOBEIfCompliant(resultFor("1234", -1, arr, 1, 2)) + assertThrowsStringIIOBEIfCompliant(resultFor("1234", 6, arr, 1, 2)) + assertThrowsStringIIOBEIfCompliant(resultFor("1234", 1, arr, -1, 2)) + assertThrowsStringIIOBEIfCompliant(resultFor("1234", 1, arr, 1, -2)) + assertThrowsStringIIOBEIfCompliant(resultFor("1234", 1, arr, 4, 3)) if (executingInJVM) { assertThrows(classOf[NullPointerException], @@ -249,10 +238,8 @@ class StringBuilderTest { assertEquals("01hello234", resultFor("01234", 2, "hello")) assertEquals("01foobar234", resultFor("01234", 2, charSequence("foobar"))) - assertThrows(classOf[StringIndexOutOfBoundsException], - resultFor("1234", -1, "foo")) - assertThrows(classOf[StringIndexOutOfBoundsException], - resultFor("1234", 6, "foo")) + assertThrowsStringIIOBEIfCompliant(resultFor("1234", -1, "foo")) + assertThrowsStringIIOBEIfCompliant(resultFor("1234", 6, "foo")) } @Test def insertString(): Unit = { @@ -262,10 +249,8 @@ class StringBuilderTest { assertEquals("01null234", resultFor("01234", 2, null)) assertEquals("01hello234", resultFor("01234", 2, "hello")) - assertThrows(classOf[StringIndexOutOfBoundsException], - resultFor("1234", -1, "foo")) - assertThrows(classOf[StringIndexOutOfBoundsException], - resultFor("1234", 6, "foo")) + assertThrowsStringIIOBEIfCompliant(resultFor("1234", -1, "foo")) + assertThrowsStringIIOBEIfCompliant(resultFor("1234", 6, "foo")) } @Test def insertCharArray(): Unit = { @@ -277,10 +262,8 @@ class StringBuilderTest { assertEquals("0abcde12", resultFor("012", 1, arr)) assertEquals("ababcdeef", resultFor("abef", 2, arr)) - assertThrows(classOf[StringIndexOutOfBoundsException], - resultFor("1234", -1, arr)) - assertThrows(classOf[StringIndexOutOfBoundsException], - resultFor("1234", 6, arr)) + assertThrowsStringIIOBEIfCompliant(resultFor("1234", -1, arr)) + assertThrowsStringIIOBEIfCompliant(resultFor("1234", 6, arr)) if (executingInJVM) assertThrows(classOf[NullPointerException], resultFor("1234", 1, null)) @@ -339,10 +322,8 @@ class StringBuilderTest { assertEquals("a4bcd", initBuilder("abcd").insert(1, 4.toByte).toString) assertEquals("a304bcd", initBuilder("abcd").insert(1, 304.toShort).toString) - assertThrows(classOf[StringIndexOutOfBoundsException], - initBuilder("abcd").insert(5, 56)) - assertThrows(classOf[StringIndexOutOfBoundsException], - initBuilder("abcd").insert(-1, 56)) + assertThrowsStringIIOBEIfCompliant(initBuilder("abcd").insert(5, 56)) + assertThrowsStringIIOBEIfCompliant(initBuilder("abcd").insert(-1, 56)) } @Test def indexOfString(): Unit = { @@ -421,7 +402,7 @@ class StringBuilderTest { @Test def setLength(): Unit = { val b = initBuilder("foobar") - assertThrows(classOf[StringIndexOutOfBoundsException], b.setLength(-3)) + assertThrowsStringIIOBEIfCompliant(b.setLength(-3)) b.setLength(3) assertEquals("foo", b.toString) @@ -437,11 +418,9 @@ class StringBuilderTest { assertEquals('\ud801', resultFor("ab\ud801\udc02cd", 2)) assertEquals('\udc02', resultFor("ab\ud801\udc02cd", 3)) - if (executingInJVM) { - assertThrows(classOf[IndexOutOfBoundsException], resultFor("hello", -1)) - assertThrows(classOf[IndexOutOfBoundsException], resultFor("hello", 5)) - assertThrows(classOf[IndexOutOfBoundsException], resultFor("hello", 6)) - } + assertThrowsStringIIOBEIfCompliant(resultFor("hello", -1)) + assertThrowsStringIIOBEIfCompliant(resultFor("hello", 5)) + assertThrowsStringIIOBEIfCompliant(resultFor("hello", 6)) } @Test def codePointAt(): Unit = { @@ -458,12 +437,8 @@ class StringBuilderTest { assertEquals(0xdf06, resultFor("\udf06abc", 0)) assertEquals(0xd834, resultFor("abc\ud834", 3)) - if (executingInJVM) { - assertThrows(classOf[IndexOutOfBoundsException], - resultFor("abc\ud834\udf06def", -1)) - assertThrows(classOf[IndexOutOfBoundsException], - resultFor("abc\ud834\udf06def", 15)) - } + assertThrowsStringIIOBEIfCompliant(resultFor("abc\ud834\udf06def", -1)) + assertThrowsStringIIOBEIfCompliant(resultFor("abc\ud834\udf06def", 15)) } @Test def codePointBefore(): Unit = { @@ -479,12 +454,8 @@ class StringBuilderTest { assertEquals(0xd834, resultFor("\ud834abc", 1)) assertEquals(0xdf06, resultFor("\udf06abc", 1)) - if (executingInJVM) { - assertThrows(classOf[IndexOutOfBoundsException], - resultFor("abc\ud834\udf06def", 0)) - assertThrows(classOf[IndexOutOfBoundsException], - resultFor("abc\ud834\udf06def", 15)) - } + assertThrowsStringIIOBEIfCompliant(resultFor("abc\ud834\udf06def", 0)) + assertThrowsStringIIOBEIfCompliant(resultFor("abc\ud834\udf06def", 15)) } @Test def codePointCount(): Unit = { @@ -505,6 +476,7 @@ class StringBuilderTest { assertEquals(0, sb.codePointCount(sb.length - 1, sb.length - 1)) assertEquals(0, sb.codePointCount(sb.length, sb.length)) + // Actually mandated IOOBE, not StringIIOBE assertThrows(classOf[IndexOutOfBoundsException], sb.codePointCount(-3, 4)) assertThrows(classOf[IndexOutOfBoundsException], sb.codePointCount(6, 2)) assertThrows(classOf[IndexOutOfBoundsException], sb.codePointCount(10, 30)) @@ -527,6 +499,7 @@ class StringBuilderTest { assertEquals(sb.length - 1, sb.offsetByCodePoints(sb.length - 1, 0)) assertEquals(sb.length, sb.offsetByCodePoints(sb.length, 0)) + // Actually mandated IOOBE, not StringIIOBE assertThrows(classOf[IndexOutOfBoundsException], sb.offsetByCodePoints(-3, 4)) assertThrows(classOf[IndexOutOfBoundsException], sb.offsetByCodePoints(6, 18)) assertThrows(classOf[IndexOutOfBoundsException], sb.offsetByCodePoints(30, 2)) @@ -549,6 +522,7 @@ class StringBuilderTest { assertEquals(sb.length - 1, sb.offsetByCodePoints(sb.length - 1, -0)) assertEquals(sb.length, sb.offsetByCodePoints(sb.length, -0)) + // Actually mandated IOOBE, not StringIIOBE assertThrows(classOf[IndexOutOfBoundsException], sb.offsetByCodePoints(-3, 4)) assertThrows(classOf[IndexOutOfBoundsException], sb.offsetByCodePoints(6, 18)) assertThrows(classOf[IndexOutOfBoundsException], sb.offsetByCodePoints(30, 2)) @@ -570,10 +544,8 @@ class StringBuilderTest { assertEquals("foxbar", resultFor("foobar", 2, 'x')) assertEquals("foobah", resultFor("foobar", 5, 'h')) - assertThrows(classOf[StringIndexOutOfBoundsException], - resultFor("foobar", -1, 'h')) - assertThrows(classOf[StringIndexOutOfBoundsException], - resultFor("foobar", 6, 'h')) + assertThrowsStringIIOBEIfCompliant(resultFor("foobar", -1, 'h')) + assertThrowsStringIIOBEIfCompliant(resultFor("foobar", 6, 'h')) } @Test def substringStart(): Unit = { @@ -583,12 +555,8 @@ class StringBuilderTest { assertEquals("llo", resultFor("hello", 2)) assertEquals("", resultFor("hello", 5)) - if (executingInJVM) { - assertThrows(classOf[StringIndexOutOfBoundsException], - resultFor("hello", -1)) - assertThrows(classOf[StringIndexOutOfBoundsException], - resultFor("hello", 8)) - } + assertThrowsStringIIOBEIfCompliant(resultFor("hello", -1)) + assertThrowsStringIIOBEIfCompliant(resultFor("hello", 8)) } @Test def subSequence(): Unit = { @@ -603,16 +571,10 @@ class StringBuilderTest { assertEquals("", resultFor("hello", 5, 5)) assertEquals("hel", resultFor("hello", 0, 3)) - if (executingInJVM) { - assertThrows(classOf[StringIndexOutOfBoundsException], - resultFor("hello", -1, 3)) - assertThrows(classOf[StringIndexOutOfBoundsException], - resultFor("hello", 8, 8)) - assertThrows(classOf[StringIndexOutOfBoundsException], - resultFor("hello", 3, 2)) - assertThrows(classOf[StringIndexOutOfBoundsException], - resultFor("hello", 3, 8)) - } + assertThrowsStringIIOBEIfCompliant(resultFor("hello", -1, 3)) + assertThrowsStringIIOBEIfCompliant(resultFor("hello", 8, 8)) + assertThrowsStringIIOBEIfCompliant(resultFor("hello", 3, 2)) + assertThrowsStringIIOBEIfCompliant(resultFor("hello", 3, 8)) } @Test def substringStartEnd(): Unit = { @@ -623,16 +585,10 @@ class StringBuilderTest { assertEquals("", resultFor("hello", 5, 5)) assertEquals("hel", resultFor("hello", 0, 3)) - if (executingInJVM) { - assertThrows(classOf[StringIndexOutOfBoundsException], - resultFor("hello", -1, 3)) - assertThrows(classOf[StringIndexOutOfBoundsException], - resultFor("hello", 8, 8)) - assertThrows(classOf[StringIndexOutOfBoundsException], - resultFor("hello", 3, 2)) - assertThrows(classOf[StringIndexOutOfBoundsException], - resultFor("hello", 3, 8)) - } + assertThrowsStringIIOBEIfCompliant(resultFor("hello", -1, 3)) + assertThrowsStringIIOBEIfCompliant(resultFor("hello", 8, 8)) + assertThrowsStringIIOBEIfCompliant(resultFor("hello", 3, 2)) + assertThrowsStringIIOBEIfCompliant(resultFor("hello", 3, 8)) } @Test def stringInterpolationToSurviveNullAndUndefined(): Unit = { diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/utils/AssertThrows.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/utils/AssertThrows.scala index df343e91d8..87fefaf548 100644 --- a/test-suite/shared/src/test/scala/org/scalajs/testsuite/utils/AssertThrows.scala +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/utils/AssertThrows.scala @@ -26,4 +26,9 @@ object AssertThrows { if (Platform.hasCompliantNullPointers) assertThrows(classOf[NullPointerException], code) } + + def assertThrowsStringIIOBEIfCompliant(code: => Unit): Unit = { + if (Platform.hasCompliantStringIndexOutOfBounds) + assertThrows(classOf[StringIndexOutOfBoundsException], code) + } } From bfed364036fe4a4662f1691fb39698e34d848350 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Fri, 6 Mar 2026 11:31:46 +0100 Subject: [PATCH 16/32] Throw a specified IndexOutOfBoundsException in CharArrayWriter. Instead of a StringIOOBE, which is subject to UB. --- javalib/src/main/scala/java/io/CharArrayWriter.scala | 2 +- .../scalajs/testsuite/javalib/io/CharArrayWriterTest.scala | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/javalib/src/main/scala/java/io/CharArrayWriter.scala b/javalib/src/main/scala/java/io/CharArrayWriter.scala index 3c9dfb3490..4367acc92a 100644 --- a/javalib/src/main/scala/java/io/CharArrayWriter.scala +++ b/javalib/src/main/scala/java/io/CharArrayWriter.scala @@ -58,7 +58,7 @@ class CharArrayWriter(initialSize: Int) extends Writer { override def write(str: String, offset: Int, len: Int): Unit = { if (offset < 0 || offset > str.length || len < 0 || len > str.length - offset) - throw new StringIndexOutOfBoundsException + throw new IndexOutOfBoundsException ensureCapacity(len) str.getChars(offset, offset + len, this.buf, this.count) diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/io/CharArrayWriterTest.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/io/CharArrayWriterTest.scala index 4bc7fb0391..a4b3fcc48e 100644 --- a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/io/CharArrayWriterTest.scala +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/io/CharArrayWriterTest.scala @@ -116,6 +116,11 @@ class CharArrayWriterTest { cw.write("HelloWorld", 5, 5) assertEquals("World", cw.toString) assertArrayEquals("World".toCharArray, cw.toCharArray) + + assertThrows(classOf[IndexOutOfBoundsException], cw.write("foo", 1, 3)) + assertThrows(classOf[IndexOutOfBoundsException], cw.write("foo", -1, 3)) + assertThrows(classOf[IndexOutOfBoundsException], cw.write("foo", 2, -1)) + assertArrayEquals("World".toCharArray, cw.toCharArray) } } From 7b06e2c7a9563ea73f22a55c0acf2fc31f1b47c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Fri, 6 Mar 2026 11:32:36 +0100 Subject: [PATCH 17/32] Trigger UB for StringIOOBEs in all jl.String methods. --- .../src/main/scala/java/lang/_String.scala | 47 +++++++++++++++---- project/Build.scala | 4 +- 2 files changed, 39 insertions(+), 12 deletions(-) diff --git a/javalib/src/main/scala/java/lang/_String.scala b/javalib/src/main/scala/java/lang/_String.scala index 19d78774e6..16f1b6a56d 100644 --- a/javalib/src/main/scala/java/lang/_String.scala +++ b/javalib/src/main/scala/java/lang/_String.scala @@ -216,10 +216,26 @@ final class _String private () // scalastyle:ignore res } - def getChars(srcBegin: Int, srcEnd: Int, dst: Array[Char], - dstBegin: Int): Unit = { - if (srcEnd > length() || srcBegin < 0 || srcEnd < 0 || srcBegin > srcEnd) - throw new StringIndexOutOfBoundsException("Index out of Bound") + def getChars(srcBegin: Int, srcEnd: Int, dst: Array[Char], dstBegin: Int): Unit = { + val dstLength = dst.length // implies null check + + // Bounds checks on the source + if (srcEnd > length() || srcBegin < 0 || srcEnd < 0 || srcBegin > srcEnd) { + if (srcBegin < 0) + charAt(srcBegin) + if (srcEnd > length()) + charAt(srcEnd) + charAt(-1) + } + + /* Bounds checks on the destination. + * Intuitively, they should throw ArrayIOOBE. However, in practice, the JVM + * throws throws a StringIOOBE. We follow that behavior. + */ + if (dstBegin < 0) + "".charAt(dstBegin) + if (dstBegin > dstLength - (srcEnd - srcBegin)) + "".charAt(dstBegin + (srcEnd - srcBegin)) val offset = dstBegin - srcBegin var i = srcBegin @@ -967,10 +983,8 @@ object _String { // scalastyle:ignore `new`(value, 0, value.length) def `new`(value: Array[Char], offset: Int, count: Int): String = { + checkBoundsForNewFromArray(offset, count, value.length) val end = offset + count - if (offset < 0 || end < offset || end > value.length) - throw new StringIndexOutOfBoundsException - var result = "" var i = offset while (i != end) { @@ -1003,10 +1017,8 @@ object _String { // scalastyle:ignore } def `new`(codePoints: Array[Int], offset: Int, count: Int): String = { + checkBoundsForNewFromArray(offset, count, codePoints.length) val end = offset + count - if (offset < 0 || end < offset || end > codePoints.length) - throw new StringIndexOutOfBoundsException - var result = "" var i = offset while (i != end) { @@ -1028,6 +1040,21 @@ object _String { // scalastyle:ignore def `new`(builder: java.lang.StringBuilder): String = builder.toString + @inline + private def checkBoundsForNewFromArray(offset: Int, count: Int, arrayLength: Int): Unit = { + /* Publicly specified as throwing an IndexOutOfBoundsException. + * Intuitively, should throw an ArrayIOOBE. However, in practice, the JVM + * throws a StringIOOBE. We replicate that behavior. + */ + if (offset < 0 || count < 0 || offset > arrayLength - count) { + if (offset < 0 || offset >= arrayLength) + "".charAt(offset) + if (count < 0) + "".charAt(count) + "".charAt(offset + count - 1) + } + } + // Static methods (aka methods on the companion object) def valueOf(b: scala.Boolean): String = b.toString() diff --git a/project/Build.scala b/project/Build.scala index 5d9fbf97b6..7afdb5849f 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -2092,14 +2092,14 @@ object Build { if (!useMinifySizes) { Some(ExpectedSizes( fastLink = 438000 to 439000, - fullLink = 263000 to 264000, + fullLink = 262000 to 263000, fastLinkGz = 57000 to 58000, fullLinkGz = 43000 to 44000, )) } else { Some(ExpectedSizes( fastLink = 304000 to 305000, - fullLink = 263000 to 264000, + fullLink = 262000 to 263000, fastLinkGz = 48000 to 49000, fullLinkGz = 43000 to 44000, )) From 23bf595559c80031e92f6dade2b73ec7eae33836 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Wed, 4 Mar 2026 15:40:06 +0100 Subject: [PATCH 18/32] Throw IOOBE instead of ArrayIOOBE in CharArrayReader.read. It is specified as the more general exception, and in practice that is what the JVM throws. This was the only place where we explicitly threw an ArrayIOOBE not subject to UB. --- javalib/src/main/scala/java/io/CharArrayReader.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/javalib/src/main/scala/java/io/CharArrayReader.scala b/javalib/src/main/scala/java/io/CharArrayReader.scala index 3fe5723158..c4ac09dcbe 100644 --- a/javalib/src/main/scala/java/io/CharArrayReader.scala +++ b/javalib/src/main/scala/java/io/CharArrayReader.scala @@ -48,10 +48,10 @@ class CharArrayReader(protected var buf: Array[Char], offset: Int, length: Int) override def read(buffer: Array[Char], offset: Int, len: Int): Int = { if (offset < 0 || offset > buffer.length) - throw new ArrayIndexOutOfBoundsException("Offset out of bounds : " + offset) + throw new IndexOutOfBoundsException("Offset out of bounds : " + offset) if (len < 0 || len > buffer.length - offset) - throw new ArrayIndexOutOfBoundsException("Length out of bounds : " + len) + throw new IndexOutOfBoundsException("Length out of bounds : " + len) ensureOpen() From edd74b6f38b888f91004c522c5df210e19a45882 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Wed, 4 Mar 2026 15:50:32 +0100 Subject: [PATCH 19/32] Trigger a UB `ArrayStoreException` in `System.arraycopy`. This was the only place where we explicitly threw an `ArrayStoreException`, as opposed to a UBE. --- javalib/src/main/scala/java/lang/System.scala | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/javalib/src/main/scala/java/lang/System.scala b/javalib/src/main/scala/java/lang/System.scala index 4ca1a42342..fb0f7baaba 100644 --- a/javalib/src/main/scala/java/lang/System.scala +++ b/javalib/src/main/scala/java/lang/System.scala @@ -91,8 +91,10 @@ object System { import scala.{Boolean, Char, Byte, Short, Int, Long, Float, Double} - def mismatch(): Nothing = - throw new ArrayStoreException("Incompatible array types") + def mismatch(): Unit = { + // Trigger an ArrayStoreException subject to UB. + new Array[String](1).asInstanceOf[Array[Object]](0) = Integer.valueOf(0) + } def impl(srcLen: Int, destLen: Int, f: BiConsumer[Int, Int]): Unit = { /* Perform dummy swaps to trigger an ArrayIndexOutOfBoundsException or From dd7be5a0741a5e428901805e44c38cc673148bb6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Wed, 4 Mar 2026 17:11:59 +0100 Subject: [PATCH 20/32] Replace explicit NegativeArraySizeException's by UB ones. The trick we use is to allocate an array of the requested length and discard it in statement position. We enhance the optimizer to recognize that pattern, so that we do not actually allocate an array. This is similar to what we do with `x.getClass()` to perform a null pointer check. --- .../main/scala/java/lang/StringBuilder.scala | 3 +-- javalib/src/main/scala/java/util/Arrays.scala | 6 ----- javalib/src/main/scala/java/util/BitSet.scala | 5 +--- .../backend/emitter/FunctionEmitter.scala | 15 ++++++++++- .../linker/backend/emitter/Transients.scala | 25 +++++++++++++++++++ .../backend/wasmemitter/FunctionEmitter.scala | 20 +++++++++++++++ .../frontend/optimizer/OptimizerCore.scala | 11 +++++--- project/Build.scala | 8 +++--- .../javalib/lang/StringBufferTest.scala | 5 +++- .../javalib/lang/StringBuilderTest.scala | 5 +++- .../testsuite/javalib/util/ArraysTest.scala | 14 +++++++++++ .../testsuite/javalib/util/BitSetTest.scala | 3 +-- .../testsuite/utils/AssertThrows.scala | 5 ++++ 13 files changed, 100 insertions(+), 25 deletions(-) diff --git a/javalib/src/main/scala/java/lang/StringBuilder.scala b/javalib/src/main/scala/java/lang/StringBuilder.scala index d3fb54138f..1f560f1d54 100644 --- a/javalib/src/main/scala/java/lang/StringBuilder.scala +++ b/javalib/src/main/scala/java/lang/StringBuilder.scala @@ -25,8 +25,7 @@ class StringBuilder extends AnyRef with CharSequence with Appendable with java.i def this(initialCapacity: Int) = { this() - if (initialCapacity < 0) - throw new NegativeArraySizeException() + new Array[Char](initialCapacity) // NegativeArraySize check } def this(seq: CharSequence) = this(seq.toString) diff --git a/javalib/src/main/scala/java/util/Arrays.scala b/javalib/src/main/scala/java/util/Arrays.scala index 886a43ff80..49a0de9ed1 100644 --- a/javalib/src/main/scala/java/util/Arrays.scala +++ b/javalib/src/main/scala/java/util/Arrays.scala @@ -456,7 +456,6 @@ object Arrays { @inline private def copyOfImpl[U, T](original: Array[U], newLength: Int)( implicit uops: ArrayOps[U], tops: ArrayCreateOps[T]): Array[T] = { - checkArrayLength(newLength) val copyLength = Math.min(newLength, uops.length(original)) val ret = tops.create(newLength) System.arraycopy(original, 0, ret, 0, copyLength) @@ -512,11 +511,6 @@ object Arrays { ret } - @inline private def checkArrayLength(len: Int): Unit = { - if (len < 0) - throw new NegativeArraySizeException - } - @noinline def asList[T <: AnyRef](a: Array[T]): List[T] = { new AbstractList[T] with RandomAccess { def size(): Int = diff --git a/javalib/src/main/scala/java/util/BitSet.scala b/javalib/src/main/scala/java/util/BitSet.scala index e43966defa..0b35758ff8 100644 --- a/javalib/src/main/scala/java/util/BitSet.scala +++ b/javalib/src/main/scala/java/util/BitSet.scala @@ -70,11 +70,8 @@ class BitSet private (private var bits: Array[Int]) extends Serializable with Cl def this(nbits: Int) = { this( bits = { - if (nbits < 0) - throw new NegativeArraySizeException - + new Array[Int](nbits) // NegativeArraySize check val length = (nbits + BitSet.RightBits) >> BitSet.AddressBitsPerWord - new Array[Int](length) } ) diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/FunctionEmitter.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/FunctionEmitter.scala index c6946460c5..8dad9d3940 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/FunctionEmitter.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/FunctionEmitter.scala @@ -938,6 +938,19 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { case Return(expr, label) => pushLhsInto(Lhs.Return(label), expr, tailPosLabels) + case Transient(CheckArrayLength(length)) => + unnest(length) { (newLength, env0) => + implicit val env = env0 + val jsLength = transformExprNoChar(newLength) + + if (semantics.negativeArraySizes != CheckedBehavior.Unchecked) { + js.If(jsLength < js.IntLiteral(0), + genCallHelper(VarField.throwNegativeArraySizeException)) + } else { + jsLength + } + } + case Transient(SystemArrayCopy(src, srcPos, dest, destPos, length)) => unnest(List(src, srcPos, dest, destPos, length)) { (newArgs, env0) => implicit val env = env0 @@ -2377,7 +2390,7 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) { case _:Skip | _:VarDef | _:Assign | _:While | _:Debugger | _:JSSuperConstructorCall | _:JSDelete | _:StoreModule | - Transient(_: SystemArrayCopy) => + Transient(_:CheckArrayLength | _:SystemArrayCopy) => /* Go "back" to transformStat() after having dived into * expression statements. This can only happen for Lhs.Discard and * for Lhs.Return's whose target is a statement. diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/Transients.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/Transients.scala index df1356271d..b649e5b668 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/Transients.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/Transients.scala @@ -96,6 +96,31 @@ object Transients { } } + /** Check an array length as a statement. + * + * This node is produced by the optimizer when a NewArray ends up in + * statement position. We then only need to check the array length, but not + * actually allocate the array. + * + * This is important because we intentionally use `new Array[x](n)` in + * statement position to trigger NegativeArraySizeException's subject to UB. + */ + final case class CheckArrayLength(length: Tree) extends Transient.Value { + val tpe: Type = VoidType + + def traverse(traverser: Traverser): Unit = + traverser.traverse(length) + + def transform(t: Transformer)(implicit pos: Position): Tree = + Transient(CheckArrayLength(t.transform(length))) + + def printIR(out: IRTreePrinter): Unit = { + out.print("$checkArrayLength(") + out.print(length) + out.print(")") + } + } + /** Intrinsic for `System.arraycopy`. * * This node *assumes* that `src` and `dest` are non-null. It is the diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/FunctionEmitter.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/FunctionEmitter.scala index c6429253ba..81f81b43af 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/FunctionEmitter.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/FunctionEmitter.scala @@ -3571,6 +3571,26 @@ private class FunctionEmitter private ( case Transients.Cast(expr, tpe) => genCast(expr, tpe, tree.pos) + case Transients.CheckArrayLength(length) => + if (semantics.negativeArraySizes == CheckedBehavior.Unchecked) { + genTree(length, VoidType) + } else { + // if length < 0 + genTree(length, IntType) + markPosition(tree) + val lengthLocal = addSyntheticLocal(watpe.Int32) + fb += wa.LocalTee(lengthLocal) + fb += wa.I32Const(0) + fb += wa.I32LtS + fb.ifThen() { + // then throw NegativeArraySizeException + fb += wa.LocalGet(lengthLocal) + fb += wa.Call(genFunctionID.throwNegativeArraySizeException) + fb += wa.Unreachable + } + } + VoidType + case value: Transients.SystemArrayCopy => genSystemArrayCopy(tree, value) diff --git a/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala b/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala index 428d5b685b..e52c4afc67 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala @@ -1844,10 +1844,13 @@ private[optimizer] abstract class OptimizerCore( case LoadModule(moduleClassName) => if (hasElidableConstructors(moduleClassName)) Skip()(stat.pos) else stat - case NewArray(_, length) if isNonNegativeIntLiteral(length) => - Skip()(stat.pos) - case NewArray(_, length) if semantics.negativeArraySizes == CheckedBehavior.Unchecked => - keepOnlySideEffects(length) + case NewArray(_, length) => + if (isNonNegativeIntLiteral(length)) + Skip()(stat.pos) + else if (semantics.negativeArraySizes == CheckedBehavior.Unchecked) + keepOnlySideEffects(length) + else + Transient(CheckArrayLength(length))(stat.pos) case ArrayValue(_, elems) => Block(elems.map(keepOnlySideEffects(_)))(stat.pos) case ArraySelect(array, index) diff --git a/project/Build.scala b/project/Build.scala index 7afdb5849f..9b2398beb4 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -2074,15 +2074,15 @@ object Build { case `default212Version` => if (!useMinifySizes) { Some(ExpectedSizes( - fastLink = 620000 to 621000, - fullLink = 284000 to 285000, - fastLinkGz = 75000 to 79000, + fastLink = 619000 to 620000, + fullLink = 283000 to 284000, + fastLinkGz = 75000 to 76000, fullLinkGz = 43000 to 44000, )) } else { Some(ExpectedSizes( fastLink = 425000 to 426000, - fullLink = 284000 to 285000, + fullLink = 283000 to 284000, fastLinkGz = 61000 to 62000, fullLinkGz = 43000 to 44000, )) diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/lang/StringBufferTest.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/lang/StringBufferTest.scala index bb4f1146e1..565be213ac 100644 --- a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/lang/StringBufferTest.scala +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/lang/StringBufferTest.scala @@ -34,9 +34,12 @@ class StringBufferTest { @Test def init(): Unit = assertEquals("", new StringBuffer().toString()) - @Test def initInt(): Unit = + @Test def initInt(): Unit = { assertEquals("", new StringBuffer(5).toString()) + assertThrowsNegArraySizeIfCompliant(new StringBuffer(-3)) + } + @Test def initString(): Unit = { assertEquals("hello", new StringBuffer("hello").toString()) diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/lang/StringBuilderTest.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/lang/StringBuilderTest.scala index dccff37c9f..b3b08d38f0 100644 --- a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/lang/StringBuilderTest.scala +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/lang/StringBuilderTest.scala @@ -36,9 +36,12 @@ class StringBuilderTest { @Test def init(): Unit = assertEquals("", new StringBuilder().toString()) - @Test def initInt(): Unit = + @Test def initInt(): Unit = { assertEquals("", new StringBuilder(5).toString()) + assertThrowsNegArraySizeIfCompliant(new StringBuilder(-3)) + } + @Test def initString(): Unit = { assertEquals("hello", new StringBuilder("hello").toString()) diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/ArraysTest.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/ArraysTest.scala index 15e0e8941b..12c5dc66d5 100644 --- a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/ArraysTest.scala +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/ArraysTest.scala @@ -685,6 +685,20 @@ class ArraysTest { assertArrayEquals(Array[A](B(1), B(2), B(3), null, null), bscopyAsA) } + @Test def copyOfNegativeNewSize(): Unit = { + assumeTrue("requires compliant NegativeArraySizeException's", hasCompliantNegativeArraySizes) + + assertThrows(classOf[NegativeArraySizeException], Arrays.copyOf(new Array[Boolean](3), -3)) + assertThrows(classOf[NegativeArraySizeException], Arrays.copyOf(new Array[Char](3), -3)) + assertThrows(classOf[NegativeArraySizeException], Arrays.copyOf(new Array[Byte](3), -3)) + assertThrows(classOf[NegativeArraySizeException], Arrays.copyOf(new Array[Short](3), -3)) + assertThrows(classOf[NegativeArraySizeException], Arrays.copyOf(new Array[Int](3), -3)) + assertThrows(classOf[NegativeArraySizeException], Arrays.copyOf(new Array[Long](3), -3)) + assertThrows(classOf[NegativeArraySizeException], Arrays.copyOf(new Array[Float](3), -3)) + assertThrows(classOf[NegativeArraySizeException], Arrays.copyOf(new Array[Double](3), -3)) + assertThrows(classOf[NegativeArraySizeException], Arrays.copyOf(new Array[Object](3), -3)) + } + @Test def copyOfRangeAnyRef(): Unit = { val anyrefs: Array[AnyRef] = Array("a", "b", "c", "d", "e") val anyrefscopy = Arrays.copyOfRange(anyrefs, 2, 4) diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/BitSetTest.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/BitSetTest.scala index f5f05898fa..d32ef61fed 100644 --- a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/BitSetTest.scala +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/BitSetTest.scala @@ -43,8 +43,7 @@ class BitSetTest { assertEquals("Failed to round BitSet element size", 96, bs.size()) } - // "Failed to throw exception when creating a new BitSet with negative element value" - assertThrows(classOf[NegativeArraySizeException], new BitSet(-9)) + assertThrowsNegArraySizeIfCompliant(new BitSet(-9)) } @Test def test_clone(): Unit = { diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/utils/AssertThrows.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/utils/AssertThrows.scala index 87fefaf548..de5947ede5 100644 --- a/test-suite/shared/src/test/scala/org/scalajs/testsuite/utils/AssertThrows.scala +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/utils/AssertThrows.scala @@ -31,4 +31,9 @@ object AssertThrows { if (Platform.hasCompliantStringIndexOutOfBounds) assertThrows(classOf[StringIndexOutOfBoundsException], code) } + + def assertThrowsNegArraySizeIfCompliant(code: => Unit): Unit = { + if (Platform.hasCompliantNegativeArraySizes) + assertThrows(classOf[NegativeArraySizeException], code) + } } From cb6b2955d413b83f93ff7094a7df90dee01d94cc Mon Sep 17 00:00:00 2001 From: "v.karamyshev" Date: Mon, 9 Mar 2026 13:01:31 +0500 Subject: [PATCH 21/32] Don't use js.Map in ClassValue when targeting pure Wasm --- .../src/main/scala/java/lang/ClassValue.scala | 82 ++++++++++++------- project/Build.scala | 3 - 2 files changed, 53 insertions(+), 32 deletions(-) diff --git a/javalib/src/main/scala/java/lang/ClassValue.scala b/javalib/src/main/scala/java/lang/ClassValue.scala index 0ab92d37cb..0b06ed675d 100644 --- a/javalib/src/main/scala/java/lang/ClassValue.scala +++ b/javalib/src/main/scala/java/lang/ClassValue.scala @@ -17,16 +17,24 @@ import java.util.HashMap import scala.scalajs.js import scala.scalajs.js.annotation._ import scala.scalajs.LinkingInfo -import scala.scalajs.LinkingInfo.ESVersion +import scala.scalajs.LinkingInfo.{ESVersion, moduleKind} +import scala.scalajs.LinkingInfo.ModuleKind.{MinimalWasmModule, WasmComponent} import Utils._ abstract class ClassValue[T] protected () { private val jsMap: js.Map[Class[_], T] = { - if (LinkingInfo.esVersion >= ESVersion.ES2015 || js.typeOf(js.Dynamic.global.Map) != "undefined") - new js.Map() - else + LinkingInfo.linkTimeIf[js.Map[Class[_], T]]( + moduleKind == MinimalWasmModule || moduleKind == WasmComponent) { null + } { + if (LinkingInfo.esVersion >= ESVersion.ES2015 || + js.typeOf(js.Dynamic.global.Map) != "undefined") { + new js.Map() + } else { + null + } + } } @inline @@ -35,7 +43,11 @@ abstract class ClassValue[T] protected () { * emitting ES 2015 code, which allows to dead-code-eliminate the branches * using `HashMap`s, and therefore `HashMap` itself. */ - LinkingInfo.esVersion >= ESVersion.ES2015 || jsMap != null + LinkingInfo.linkTimeIf(moduleKind == MinimalWasmModule || moduleKind == WasmComponent) { + false + } { + LinkingInfo.esVersion >= ESVersion.ES2015 || jsMap != null + } } /* We use a HashMap instead of an IdentityHashMap because the latter is @@ -49,35 +61,47 @@ abstract class ClassValue[T] protected () { protected def computeValue(`type`: Class[_]): T def get(`type`: Class[_]): T = { - if (useJSMap) { - mapGetOrElseUpdate(jsMap, `type`)(() => computeValue(`type`)) - } else { - /* We first perform `get`, and if the result is null, we use - * `containsKey` to disambiguate a present null from an absent key. - * Since the purpose of ClassValue is to be used a cache indexed by Class - * values, the expected use case will have more hits than misses, and so - * this ordering should be faster on average than first performing `has` - * then `get`. - */ - javaMap.get(`type`) match { - case null => - if (javaMap.containsKey(`type`)) { - null.asInstanceOf[T] - } else { - val newValue = computeValue(`type`) - javaMap.put(`type`, newValue) - newValue - } - case value => - value + LinkingInfo.linkTimeIf(moduleKind == MinimalWasmModule || moduleKind == WasmComponent) { + getJavaMap(`type`) + } { + if (useJSMap) { + mapGetOrElseUpdate(jsMap, `type`)(() => computeValue(`type`)) + } else { + getJavaMap(`type`) } } } + private def getJavaMap(`type`: Class[_]): T = { + /* We first perform `get`, and if the result is null, we use + * `containsKey` to disambiguate a present null from an absent key. + * Since the purpose of ClassValue is to be used a cache indexed by Class + * values, the expected use case will have more hits than misses, and so + * this ordering should be faster on average than first performing `has` + * then `get`. + */ + javaMap.get(`type`) match { + case null => + if (javaMap.containsKey(`type`)) { + null.asInstanceOf[T] + } else { + val newValue = computeValue(`type`) + javaMap.put(`type`, newValue) + newValue + } + case value => + value + } + } + def remove(`type`: Class[_]): Unit = { - if (useJSMap) - jsMap.delete(`type`) - else + LinkingInfo.linkTimeIf[Unit](moduleKind == MinimalWasmModule || moduleKind == WasmComponent) { javaMap.remove(`type`) + } { + if (useJSMap) + jsMap.delete(`type`) + else + javaMap.remove(`type`) + } } } diff --git a/project/Build.scala b/project/Build.scala index e33c99838f..7ec885d02b 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -2359,9 +2359,6 @@ object Build { contains(f, "/shared/src/test/scala-old-collections/") || contains(f, "/shared/src/test/require-scala2/") || contains(f, "/shared/src/test/scala/org/scalajs/testsuite/") && ( - // javalib/lang - !endsWith(f, "/lang/ClassValueTest.scala") && // js.Map in ClassValue - // javalib/util !endsWith(f, "/DateTest.scala") && // js.Date !endsWith(f, "/PropertiesTest.scala") && // Date.toString From a5d576e5c45d1b0402bbab41597978c394621ae2 Mon Sep 17 00:00:00 2001 From: Rikito Taniguchi Date: Tue, 24 Mar 2026 23:13:01 +0100 Subject: [PATCH 22/32] Fix array of resource Fix the remaining special-case handling needed for `Array[resource]` in the Wasm backend so the component-model example can allocate and use arrays of resource values. This is a kind of monkey patch. `WitResourceTypeRef` still does not behave enough like a normal class type, so we need a resource-specific branch in the array subtype / type-data path for now. The real fix is to make resource types behave more like regular classes. (make resourcs `final class` with some restrictions in class hierarchy, and make it `ClassType` and `ClassRef`). Once we do that, this special-case logic should become unnecessary and can be removed. --- .../main/scala/componentmodel/TestImportsImpl.scala | 11 +++++++++++ ir/shared/src/main/scala/org/scalajs/ir/Types.scala | 2 ++ .../linker/backend/wasmemitter/ClassEmitter.scala | 2 +- .../scalajs/linker/backend/wasmemitter/SWasmGen.scala | 10 +++++++++- 4 files changed, 23 insertions(+), 2 deletions(-) diff --git a/examples/test-component-model/src/main/scala/componentmodel/TestImportsImpl.scala b/examples/test-component-model/src/main/scala/componentmodel/TestImportsImpl.scala index dfbd4f81fd..a30ddec81e 100644 --- a/examples/test-component-model/src/main/scala/componentmodel/TestImportsImpl.scala +++ b/examples/test-component-model/src/main/scala/componentmodel/TestImportsImpl.scala @@ -17,6 +17,8 @@ import java.util.Optional @WitImplementation object TestImportsImpl extends TestImports { override def run(): Unit = { + def newCounterArray(size: Int): Array[Counter] = + new Array[Counter](size) val start = System.currentTimeMillis() @@ -62,6 +64,15 @@ object TestImportsImpl extends TestImports { val arr3 = Array[C1](C1.A(0), C1.B(3)) assert(arr3.sameElements(roundtripListVariant(arr3))) + val counter1 = Counter(1) + val counter2 = Counter(2) + val counterArr = newCounterArray(2) + assert(counterArr.length == 2) + counterArr(0) = counter1 + counterArr(1) = counter2 + assert(counterArr(0).valueOf() == 1) + assert(counterArr(1).valueOf() == 2) + assert("foo" == roundtripString("foo")) assert("" == roundtripString("")) assert(Point(0, 5) == roundtripPoint(Point(0, 5))) diff --git a/ir/shared/src/main/scala/org/scalajs/ir/Types.scala b/ir/shared/src/main/scala/org/scalajs/ir/Types.scala index 31fbc7668b..2e2e9368ff 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/Types.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/Types.scala @@ -548,6 +548,8 @@ object Types { * Object in their ancestors. */ rhsBaseName == ObjectClass || isSubclass(lhsBaseName, rhsBaseName) + case (WitResourceTypeRef(lhsBaseName), WitResourceTypeRef(rhsBaseName)) => + lhsBaseName == rhsBaseName case _ => lhsBase eq rhsBase } diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/ClassEmitter.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/ClassEmitter.scala index f5e532b5db..610cfef8f1 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/ClassEmitter.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/ClassEmitter.scala @@ -592,7 +592,7 @@ class ClassEmitter(coreSpec: CoreSpec) { ) ::: ( strictAncestorsTypeData // strictAncestors ) ::: List( - wa.GlobalGet(genGlobalID.forVTable(baseTypeRef)), // componentType + wa.GlobalGet(nonArrayTypeDataGlobalID(baseTypeRef)), // componentType wa.RefNull(watpe.HeapType.None), // classOf wa.RefNull(watpe.HeapType.None), // arrayOf wa.RefFunc(genFunctionID.cloneArray(baseTypeRef)), // clone diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/SWasmGen.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/SWasmGen.scala index 0ef02034a9..4e4d599544 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/SWasmGen.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/SWasmGen.scala @@ -25,6 +25,14 @@ import org.scalajs.linker.backend.webassembly.Identitities.LocalID /** Scala.js-specific Wasm generators that are used across the board. */ object SWasmGen { + def nonArrayTypeDataGlobalID(typeRef: NonArrayTypeRef) = typeRef match { + case WitResourceTypeRef(className) => + // Resource classes currently share the class-backed typeData/vtable global. + genGlobalID.forVTable(className) + case _ => + genGlobalID.forVTable(typeRef) + } + def genZeroOf(tpe: Type)(implicit ctx: WasmContext): List[Instr] = { tpe match { case WitResourceType(className) => @@ -83,7 +91,7 @@ object SWasmGen { } def genLoadNonArrayTypeData(fb: FunctionBuilder, typeRef: NonArrayTypeRef): Unit = - fb += GlobalGet(genGlobalID.forVTable(typeRef)) + fb += GlobalGet(nonArrayTypeDataGlobalID(typeRef)) def genLoadArrayTypeData(fb: FunctionBuilder, arrayTypeRef: ArrayTypeRef): Unit = { val ArrayTypeRef(base, dimensions) = arrayTypeRef From d845e8126ae5c33588ae4a8ae9cb23df66314e15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Fri, 13 Mar 2026 18:24:20 +0100 Subject: [PATCH 23/32] Allow to allocate direct byte buffers without typed arrays. They are backed by arrays, which is allowed by the spec. The motivation is not so much about ES 5.1 (which is deprecated anyway), but about future support of Wasm without a JS host. In Wasm without JS host, we should be able to allocate a direct `ByteBuffer` even if there are no JS typed arrays. --- .../src/main/scala/java/nio/ByteBuffer.scala | 17 ++++++- .../main/scala/java/nio/GenHeapBuffer.scala | 17 ++++--- .../scala/java/nio/GenHeapBufferView.scala | 11 ++--- .../main/scala/java/nio/HeapByteBuffer.scala | 15 +++++-- .../java/nio/HeapByteBufferCharView.scala | 11 ++--- .../java/nio/HeapByteBufferDoubleView.scala | 9 ++-- .../java/nio/HeapByteBufferFloatView.scala | 9 ++-- .../java/nio/HeapByteBufferIntView.scala | 9 ++-- .../java/nio/HeapByteBufferLongView.scala | 9 ++-- .../java/nio/HeapByteBufferShortView.scala | 9 ++-- .../main/scala/java/nio/HeapCharBuffer.scala | 5 ++- .../scala/java/nio/HeapDoubleBuffer.scala | 5 ++- .../main/scala/java/nio/HeapFloatBuffer.scala | 5 ++- .../main/scala/java/nio/HeapIntBuffer.scala | 5 ++- .../main/scala/java/nio/HeapLongBuffer.scala | 5 ++- .../main/scala/java/nio/HeapShortBuffer.scala | 5 ++- .../scalajs/testsuite/utils/Platform.scala | 2 - .../niobuffer/ByteBufferJSTest.scala | 14 ------ .../niobuffer/CharBufferJSTest.scala | 43 ------------------ .../niobuffer/DoubleBufferJSTest.scala | 44 ------------------- .../niobuffer/FloatBufferJSTest.scala | 43 ------------------ .../testsuite/niobuffer/IntBufferJSTest.scala | 43 ------------------ .../niobuffer/LongBufferJSTest.scala | 43 ------------------ .../niobuffer/ShortBufferJSTest.scala | 43 ------------------ .../scalajs/testsuite/utils/Platform.scala | 1 - .../testsuite/javalib/util/BitSetTest.scala | 4 +- .../testsuite/niobuffer/BaseBufferTest.scala | 9 ++++ .../testsuite/niobuffer/BufferFactory.scala | 10 +++++ .../niobuffer/ByteBufferFactories.scala | 2 + .../testsuite/niobuffer/ByteBufferTest.scala | 10 +++++ .../testsuite/niobuffer/CharBufferTest.scala | 34 +++++++++++++- .../niobuffer/DoubleBufferTest.scala | 35 ++++++++++++++- .../testsuite/niobuffer/FloatBufferTest.scala | 34 +++++++++++++- .../testsuite/niobuffer/IntBufferTest.scala | 33 +++++++++++++- .../testsuite/niobuffer/LongBufferTest.scala | 34 +++++++++++++- .../testsuite/niobuffer/ShortBufferTest.scala | 34 +++++++++++++- 36 files changed, 324 insertions(+), 337 deletions(-) diff --git a/javalib/src/main/scala/java/nio/ByteBuffer.scala b/javalib/src/main/scala/java/nio/ByteBuffer.scala index c84d3335a7..7e0c4baaef 100644 --- a/javalib/src/main/scala/java/nio/ByteBuffer.scala +++ b/javalib/src/main/scala/java/nio/ByteBuffer.scala @@ -12,8 +12,12 @@ package java.nio +import scala.scalajs.js import scala.scalajs.js.typedarray._ +import scala.scalajs.LinkingInfo +import scala.scalajs.LinkingInfo.ESVersion + object ByteBuffer { private final val HashSeed = -547316498 // "java.nio.ByteBuffer".## @@ -24,7 +28,18 @@ object ByteBuffer { def allocateDirect(capacity: Int): ByteBuffer = { GenBuffer.validateAllocateCapacity(capacity) - TypedArrayByteBuffer.allocate(capacity) + + if (LinkingInfo.esVersion >= ESVersion.ES2015 || + js.typeOf(js.Dynamic.global.Int8Array) != "undefined") { + TypedArrayByteBuffer.allocate(capacity) + } else { + /* Create a direct ByteBuffer that is actually backed by a regular Array. + * We can do this because the JavaDoc explicitly leaves it unspecified + * whether direct buffers are actually backed by an array or not. + * They only need to return `true` from `isDirect()`. + */ + HeapByteBuffer.allocateDirect(capacity) + } } def wrap(array: Array[Byte], offset: Int, length: Int): ByteBuffer = diff --git a/javalib/src/main/scala/java/nio/GenHeapBuffer.scala b/javalib/src/main/scala/java/nio/GenHeapBuffer.scala index 3ec0341523..88539ab1ec 100644 --- a/javalib/src/main/scala/java/nio/GenHeapBuffer.scala +++ b/javalib/src/main/scala/java/nio/GenHeapBuffer.scala @@ -19,8 +19,15 @@ private[nio] object GenHeapBuffer { new GenHeapBuffer(self) trait NewHeapBuffer[BufferType <: Buffer, ElementType] { + + /** Creates a new HeapBuffer with the given parameters. + * + * `direct` is only supported if `BufferType <: ByteBuffer`. For other + * buffer types, if `direct` is false, throws an `AssertionError`. + */ def apply(capacity: Int, array: Array[ElementType], arrayOffset: Int, - initialPosition: Int, initialLimit: Int, readOnly: Boolean): BufferType + initialPosition: Int, initialLimit: Int, readOnly: Boolean, + direct: Boolean): BufferType } @inline @@ -35,7 +42,7 @@ private[nio] object GenHeapBuffer { if (initialPosition < 0 || initialLength < 0 || initialLimit > capacity) throw new IndexOutOfBoundsException newHeapBuffer(capacity, array, arrayOffset, - initialPosition, initialLimit, isReadOnly) + initialPosition, initialLimit, isReadOnly, false) } } @@ -54,14 +61,14 @@ private[nio] final class GenHeapBuffer[B <: Buffer] private (val self: B) extend implicit newHeapBuffer: NewThisHeapBuffer): BufferType = { val newCapacity = remaining() newHeapBuffer(newCapacity, _array, _arrayOffset + position(), - 0, newCapacity, isReadOnly()) + 0, newCapacity, isReadOnly(), isDirect()) } @inline def generic_duplicate()( implicit newHeapBuffer: NewThisHeapBuffer): BufferType = { val result = newHeapBuffer(capacity(), _array, _arrayOffset, - position(), limit(), isReadOnly()) + position(), limit(), isReadOnly(), isDirect()) result._mark = _mark result } @@ -70,7 +77,7 @@ private[nio] final class GenHeapBuffer[B <: Buffer] private (val self: B) extend def generic_asReadOnlyBuffer()( implicit newHeapBuffer: NewThisHeapBuffer): BufferType = { val result = newHeapBuffer(capacity(), _array, _arrayOffset, - position(), limit(), true) + position(), limit(), true, isDirect()) result._mark = _mark result } diff --git a/javalib/src/main/scala/java/nio/GenHeapBufferView.scala b/javalib/src/main/scala/java/nio/GenHeapBufferView.scala index d39aaedc0f..0584b446da 100644 --- a/javalib/src/main/scala/java/nio/GenHeapBufferView.scala +++ b/javalib/src/main/scala/java/nio/GenHeapBufferView.scala @@ -21,7 +21,7 @@ private[nio] object GenHeapBufferView { def apply(capacity: Int, byteArray: Array[Byte], byteArrayOffset: Int, initialPosition: Int, initialLimit: Int, readOnly: Boolean, - isBigEndian: Boolean): BufferType + isDirect: Boolean, isBigEndian: Boolean): BufferType } @inline @@ -33,7 +33,8 @@ private[nio] object GenHeapBufferView { (byteBuffer.limit() - byteBufferPos) / newHeapBufferView.bytesPerElem newHeapBufferView(viewCapacity, byteBuffer._array, byteBuffer._arrayOffset + byteBufferPos, - 0, viewCapacity, byteBuffer.isReadOnly(), byteBuffer.isBigEndian) + 0, viewCapacity, byteBuffer.isReadOnly(), byteBuffer.isDirect(), + byteBuffer.isBigEndian) } } @@ -53,14 +54,14 @@ private[nio] final class GenHeapBufferView[B <: Buffer] private (val self: B) ex val bytesPerElem = newHeapBufferView.bytesPerElem newHeapBufferView(newCapacity, _byteArray, _byteArrayOffset + bytesPerElem * position(), - 0, newCapacity, isReadOnly(), isBigEndian) + 0, newCapacity, isReadOnly(), isDirect(), isBigEndian) } @inline def generic_duplicate()( implicit newHeapBufferView: NewThisHeapBufferView): BufferType = { val result = newHeapBufferView(capacity(), _byteArray, _byteArrayOffset, - position(), limit(), isReadOnly(), isBigEndian) + position(), limit(), isReadOnly(), isDirect(), isBigEndian) result._mark = _mark result } @@ -69,7 +70,7 @@ private[nio] final class GenHeapBufferView[B <: Buffer] private (val self: B) ex def generic_asReadOnlyBuffer()( implicit newHeapBufferView: NewThisHeapBufferView): BufferType = { val result = newHeapBufferView(capacity(), _byteArray, _byteArrayOffset, - position(), limit(), true, isBigEndian) + position(), limit(), true, isDirect(), isBigEndian) result._mark = _mark result } diff --git a/javalib/src/main/scala/java/nio/HeapByteBuffer.scala b/javalib/src/main/scala/java/nio/HeapByteBuffer.scala index c4deee4f8b..f0beeb0e36 100644 --- a/javalib/src/main/scala/java/nio/HeapByteBuffer.scala +++ b/javalib/src/main/scala/java/nio/HeapByteBuffer.scala @@ -14,7 +14,8 @@ package java.nio private[nio] final class HeapByteBuffer private ( _capacity: Int, _array0: Array[Byte], _arrayOffset0: Int, - _initialPosition: Int, _initialLimit: Int, _readOnly: Boolean) + _initialPosition: Int, _initialLimit: Int, _readOnly: Boolean, + _isDirect: Boolean) extends ByteBuffer(_capacity, _array0, _arrayOffset0) { position(_initialPosition) @@ -24,7 +25,7 @@ private[nio] final class HeapByteBuffer private ( def isReadOnly(): Boolean = _readOnly - def isDirect(): Boolean = false + def isDirect(): Boolean = _isDirect @noinline def slice(): ByteBuffer = @@ -199,11 +200,12 @@ private[nio] final class HeapByteBuffer private ( private[nio] object HeapByteBuffer { private[nio] implicit object NewHeapByteBuffer extends GenHeapBuffer.NewHeapBuffer[ByteBuffer, Byte] { + @inline def apply(capacity: Int, array: Array[Byte], arrayOffset: Int, initialPosition: Int, initialLimit: Int, - readOnly: Boolean): ByteBuffer = { + readOnly: Boolean, direct: Boolean): ByteBuffer = { new HeapByteBuffer(capacity, array, arrayOffset, - initialPosition, initialLimit, readOnly) + initialPosition, initialLimit, readOnly, direct) } } @@ -215,4 +217,9 @@ private[nio] object HeapByteBuffer { array, arrayOffset, capacity, initialPosition, initialLength, isReadOnly) } + + private[nio] def allocateDirect(capacity: Int): ByteBuffer = { + new HeapByteBuffer(capacity, new Array[Byte](capacity), + 0, 0, capacity, false, true) + } } diff --git a/javalib/src/main/scala/java/nio/HeapByteBufferCharView.scala b/javalib/src/main/scala/java/nio/HeapByteBufferCharView.scala index cef615e30a..bbddfe53ad 100644 --- a/javalib/src/main/scala/java/nio/HeapByteBufferCharView.scala +++ b/javalib/src/main/scala/java/nio/HeapByteBufferCharView.scala @@ -17,7 +17,8 @@ private[nio] final class HeapByteBufferCharView private ( override private[nio] val _byteArray: Array[Byte], override private[nio] val _byteArrayOffset: Int, _initialPosition: Int, _initialLimit: Int, - _readOnly: Boolean, override private[nio] val isBigEndian: Boolean) + _readOnly: Boolean, _isDirect: Boolean, + override private[nio] val isBigEndian: Boolean) extends CharBuffer(_capacity, null, -1) { position(_initialPosition) @@ -28,7 +29,7 @@ private[nio] final class HeapByteBufferCharView private ( def isReadOnly(): Boolean = _readOnly - def isDirect(): Boolean = false + def isDirect(): Boolean = _isDirect @noinline def slice(): CharBuffer = @@ -46,7 +47,7 @@ private[nio] final class HeapByteBufferCharView private ( if (start < 0 || end < start || end > remaining()) throw new IndexOutOfBoundsException new HeapByteBufferCharView(capacity(), _byteArray, _byteArrayOffset, - position() + start, position() + end, isReadOnly(), isBigEndian) + position() + start, position() + end, isReadOnly(), isDirect(), isBigEndian) } @noinline @@ -99,9 +100,9 @@ private[nio] object HeapByteBufferCharView { def apply(capacity: Int, byteArray: Array[Byte], byteArrayOffset: Int, initialPosition: Int, initialLimit: Int, readOnly: Boolean, - isBigEndian: Boolean): CharBuffer = { + isDirect: Boolean, isBigEndian: Boolean): CharBuffer = { new HeapByteBufferCharView(capacity, byteArray, byteArrayOffset, - initialPosition, initialLimit, readOnly, isBigEndian) + initialPosition, initialLimit, readOnly, isDirect, isBigEndian) } } diff --git a/javalib/src/main/scala/java/nio/HeapByteBufferDoubleView.scala b/javalib/src/main/scala/java/nio/HeapByteBufferDoubleView.scala index fd1cd29cb2..81dc29483e 100644 --- a/javalib/src/main/scala/java/nio/HeapByteBufferDoubleView.scala +++ b/javalib/src/main/scala/java/nio/HeapByteBufferDoubleView.scala @@ -17,7 +17,8 @@ private[nio] final class HeapByteBufferDoubleView private ( override private[nio] val _byteArray: Array[Byte], override private[nio] val _byteArrayOffset: Int, _initialPosition: Int, _initialLimit: Int, - _readOnly: Boolean, override private[nio] val isBigEndian: Boolean) + _readOnly: Boolean, _isDirect: Boolean, + override private[nio] val isBigEndian: Boolean) extends DoubleBuffer(_capacity, null, -1) { position(_initialPosition) @@ -28,7 +29,7 @@ private[nio] final class HeapByteBufferDoubleView private ( def isReadOnly(): Boolean = _readOnly - def isDirect(): Boolean = false + def isDirect(): Boolean = _isDirect @noinline def slice(): DoubleBuffer = @@ -92,9 +93,9 @@ private[nio] object HeapByteBufferDoubleView { def apply(capacity: Int, byteArray: Array[Byte], byteArrayOffset: Int, initialPosition: Int, initialLimit: Int, readOnly: Boolean, - isBigEndian: Boolean): DoubleBuffer = { + isDirect: Boolean, isBigEndian: Boolean): DoubleBuffer = { new HeapByteBufferDoubleView(capacity, byteArray, byteArrayOffset, - initialPosition, initialLimit, readOnly, isBigEndian) + initialPosition, initialLimit, readOnly, isDirect, isBigEndian) } } diff --git a/javalib/src/main/scala/java/nio/HeapByteBufferFloatView.scala b/javalib/src/main/scala/java/nio/HeapByteBufferFloatView.scala index 65396404da..9d4a48fe34 100644 --- a/javalib/src/main/scala/java/nio/HeapByteBufferFloatView.scala +++ b/javalib/src/main/scala/java/nio/HeapByteBufferFloatView.scala @@ -17,7 +17,8 @@ private[nio] final class HeapByteBufferFloatView private ( override private[nio] val _byteArray: Array[Byte], override private[nio] val _byteArrayOffset: Int, _initialPosition: Int, _initialLimit: Int, - _readOnly: Boolean, override private[nio] val isBigEndian: Boolean) + _readOnly: Boolean, _isDirect: Boolean, + override private[nio] val isBigEndian: Boolean) extends FloatBuffer(_capacity, null, -1) { position(_initialPosition) @@ -28,7 +29,7 @@ private[nio] final class HeapByteBufferFloatView private ( def isReadOnly(): Boolean = _readOnly - def isDirect(): Boolean = false + def isDirect(): Boolean = _isDirect @noinline def slice(): FloatBuffer = @@ -92,9 +93,9 @@ private[nio] object HeapByteBufferFloatView { def apply(capacity: Int, byteArray: Array[Byte], byteArrayOffset: Int, initialPosition: Int, initialLimit: Int, readOnly: Boolean, - isBigEndian: Boolean): FloatBuffer = { + isDirect: Boolean, isBigEndian: Boolean): FloatBuffer = { new HeapByteBufferFloatView(capacity, byteArray, byteArrayOffset, - initialPosition, initialLimit, readOnly, isBigEndian) + initialPosition, initialLimit, readOnly, isDirect, isBigEndian) } } diff --git a/javalib/src/main/scala/java/nio/HeapByteBufferIntView.scala b/javalib/src/main/scala/java/nio/HeapByteBufferIntView.scala index 6751d8f730..cdb2faa144 100644 --- a/javalib/src/main/scala/java/nio/HeapByteBufferIntView.scala +++ b/javalib/src/main/scala/java/nio/HeapByteBufferIntView.scala @@ -17,7 +17,8 @@ private[nio] final class HeapByteBufferIntView private ( override private[nio] val _byteArray: Array[Byte], override private[nio] val _byteArrayOffset: Int, _initialPosition: Int, _initialLimit: Int, - _readOnly: Boolean, override private[nio] val isBigEndian: Boolean) + _readOnly: Boolean, _isDirect: Boolean, + override private[nio] val isBigEndian: Boolean) extends IntBuffer(_capacity, null, -1) { position(_initialPosition) @@ -28,7 +29,7 @@ private[nio] final class HeapByteBufferIntView private ( def isReadOnly(): Boolean = _readOnly - def isDirect(): Boolean = false + def isDirect(): Boolean = _isDirect @noinline def slice(): IntBuffer = @@ -92,9 +93,9 @@ private[nio] object HeapByteBufferIntView { def apply(capacity: Int, byteArray: Array[Byte], byteArrayOffset: Int, initialPosition: Int, initialLimit: Int, readOnly: Boolean, - isBigEndian: Boolean): IntBuffer = { + isDirect: Boolean, isBigEndian: Boolean): IntBuffer = { new HeapByteBufferIntView(capacity, byteArray, byteArrayOffset, - initialPosition, initialLimit, readOnly, isBigEndian) + initialPosition, initialLimit, readOnly, isDirect, isBigEndian) } } diff --git a/javalib/src/main/scala/java/nio/HeapByteBufferLongView.scala b/javalib/src/main/scala/java/nio/HeapByteBufferLongView.scala index 37edbfef4f..a9b36c8d63 100644 --- a/javalib/src/main/scala/java/nio/HeapByteBufferLongView.scala +++ b/javalib/src/main/scala/java/nio/HeapByteBufferLongView.scala @@ -17,7 +17,8 @@ private[nio] final class HeapByteBufferLongView private ( override private[nio] val _byteArray: Array[Byte], override private[nio] val _byteArrayOffset: Int, _initialPosition: Int, _initialLimit: Int, - _readOnly: Boolean, override private[nio] val isBigEndian: Boolean) + _readOnly: Boolean, _isDirect: Boolean, + override private[nio] val isBigEndian: Boolean) extends LongBuffer(_capacity, null, -1) { position(_initialPosition) @@ -28,7 +29,7 @@ private[nio] final class HeapByteBufferLongView private ( def isReadOnly(): Boolean = _readOnly - def isDirect(): Boolean = false + def isDirect(): Boolean = _isDirect @noinline def slice(): LongBuffer = @@ -92,9 +93,9 @@ private[nio] object HeapByteBufferLongView { def apply(capacity: Int, byteArray: Array[Byte], byteArrayOffset: Int, initialPosition: Int, initialLimit: Int, readOnly: Boolean, - isBigEndian: Boolean): LongBuffer = { + isDirect: Boolean, isBigEndian: Boolean): LongBuffer = { new HeapByteBufferLongView(capacity, byteArray, byteArrayOffset, - initialPosition, initialLimit, readOnly, isBigEndian) + initialPosition, initialLimit, readOnly, isDirect, isBigEndian) } } diff --git a/javalib/src/main/scala/java/nio/HeapByteBufferShortView.scala b/javalib/src/main/scala/java/nio/HeapByteBufferShortView.scala index c0f5c10d8c..67ccaa0512 100644 --- a/javalib/src/main/scala/java/nio/HeapByteBufferShortView.scala +++ b/javalib/src/main/scala/java/nio/HeapByteBufferShortView.scala @@ -17,7 +17,8 @@ private[nio] final class HeapByteBufferShortView private ( override private[nio] val _byteArray: Array[Byte], override private[nio] val _byteArrayOffset: Int, _initialPosition: Int, _initialLimit: Int, - _readOnly: Boolean, override private[nio] val isBigEndian: Boolean) + _readOnly: Boolean, _isDirect: Boolean, + override private[nio] val isBigEndian: Boolean) extends ShortBuffer(_capacity, null, -1) { position(_initialPosition) @@ -28,7 +29,7 @@ private[nio] final class HeapByteBufferShortView private ( def isReadOnly(): Boolean = _readOnly - def isDirect(): Boolean = false + def isDirect(): Boolean = _isDirect @noinline def slice(): ShortBuffer = @@ -92,9 +93,9 @@ private[nio] object HeapByteBufferShortView { def apply(capacity: Int, byteArray: Array[Byte], byteArrayOffset: Int, initialPosition: Int, initialLimit: Int, readOnly: Boolean, - isBigEndian: Boolean): ShortBuffer = { + isDirect: Boolean, isBigEndian: Boolean): ShortBuffer = { new HeapByteBufferShortView(capacity, byteArray, byteArrayOffset, - initialPosition, initialLimit, readOnly, isBigEndian) + initialPosition, initialLimit, readOnly, isDirect, isBigEndian) } } diff --git a/javalib/src/main/scala/java/nio/HeapCharBuffer.scala b/javalib/src/main/scala/java/nio/HeapCharBuffer.scala index 0e3dd270fa..8843054235 100644 --- a/javalib/src/main/scala/java/nio/HeapCharBuffer.scala +++ b/javalib/src/main/scala/java/nio/HeapCharBuffer.scala @@ -101,9 +101,12 @@ private[nio] final class HeapCharBuffer private ( private[nio] object HeapCharBuffer { private[nio] implicit object NewHeapCharBuffer extends GenHeapBuffer.NewHeapBuffer[CharBuffer, Char] { + @inline def apply(capacity: Int, array: Array[Char], arrayOffset: Int, initialPosition: Int, initialLimit: Int, - readOnly: Boolean): CharBuffer = { + readOnly: Boolean, direct: Boolean): CharBuffer = { + if (direct) + throw new AssertionError("Cannot create a direct HeapCharBuffer") new HeapCharBuffer(capacity, array, arrayOffset, initialPosition, initialLimit, readOnly) } diff --git a/javalib/src/main/scala/java/nio/HeapDoubleBuffer.scala b/javalib/src/main/scala/java/nio/HeapDoubleBuffer.scala index e0801f4dc9..0a2e2f4460 100644 --- a/javalib/src/main/scala/java/nio/HeapDoubleBuffer.scala +++ b/javalib/src/main/scala/java/nio/HeapDoubleBuffer.scala @@ -94,9 +94,12 @@ private[nio] final class HeapDoubleBuffer private ( private[nio] object HeapDoubleBuffer { private[nio] implicit object NewHeapDoubleBuffer extends GenHeapBuffer.NewHeapBuffer[DoubleBuffer, Double] { + @inline def apply(capacity: Int, array: Array[Double], arrayOffset: Int, initialPosition: Int, initialLimit: Int, - readOnly: Boolean): DoubleBuffer = { + readOnly: Boolean, direct: Boolean): DoubleBuffer = { + if (direct) + throw new AssertionError("Cannot create a direct HeapDoubleBuffer") new HeapDoubleBuffer(capacity, array, arrayOffset, initialPosition, initialLimit, readOnly) } diff --git a/javalib/src/main/scala/java/nio/HeapFloatBuffer.scala b/javalib/src/main/scala/java/nio/HeapFloatBuffer.scala index 803ec3429c..04915a97f7 100644 --- a/javalib/src/main/scala/java/nio/HeapFloatBuffer.scala +++ b/javalib/src/main/scala/java/nio/HeapFloatBuffer.scala @@ -94,9 +94,12 @@ private[nio] final class HeapFloatBuffer private ( private[nio] object HeapFloatBuffer { private[nio] implicit object NewHeapFloatBuffer extends GenHeapBuffer.NewHeapBuffer[FloatBuffer, Float] { + @inline def apply(capacity: Int, array: Array[Float], arrayOffset: Int, initialPosition: Int, initialLimit: Int, - readOnly: Boolean): FloatBuffer = { + readOnly: Boolean, direct: Boolean): FloatBuffer = { + if (direct) + throw new AssertionError("Cannot create a direct HeapFloatBuffer") new HeapFloatBuffer(capacity, array, arrayOffset, initialPosition, initialLimit, readOnly) } diff --git a/javalib/src/main/scala/java/nio/HeapIntBuffer.scala b/javalib/src/main/scala/java/nio/HeapIntBuffer.scala index 310ee512cf..b15f72df4e 100644 --- a/javalib/src/main/scala/java/nio/HeapIntBuffer.scala +++ b/javalib/src/main/scala/java/nio/HeapIntBuffer.scala @@ -94,9 +94,12 @@ private[nio] final class HeapIntBuffer private ( private[nio] object HeapIntBuffer { private[nio] implicit object NewHeapIntBuffer extends GenHeapBuffer.NewHeapBuffer[IntBuffer, Int] { + @inline def apply(capacity: Int, array: Array[Int], arrayOffset: Int, initialPosition: Int, initialLimit: Int, - readOnly: Boolean): IntBuffer = { + readOnly: Boolean, direct: Boolean): IntBuffer = { + if (direct) + throw new AssertionError("Cannot create a direct HeapIntBuffer") new HeapIntBuffer(capacity, array, arrayOffset, initialPosition, initialLimit, readOnly) } diff --git a/javalib/src/main/scala/java/nio/HeapLongBuffer.scala b/javalib/src/main/scala/java/nio/HeapLongBuffer.scala index f76486cc9d..5cd352c884 100644 --- a/javalib/src/main/scala/java/nio/HeapLongBuffer.scala +++ b/javalib/src/main/scala/java/nio/HeapLongBuffer.scala @@ -94,9 +94,12 @@ private[nio] final class HeapLongBuffer private ( private[nio] object HeapLongBuffer { private[nio] implicit object NewHeapLongBuffer extends GenHeapBuffer.NewHeapBuffer[LongBuffer, Long] { + @inline def apply(capacity: Int, array: Array[Long], arrayOffset: Int, initialPosition: Int, initialLimit: Int, - readOnly: Boolean): LongBuffer = { + readOnly: Boolean, direct: Boolean): LongBuffer = { + if (direct) + throw new AssertionError("Cannot create a direct HeapLongBuffer") new HeapLongBuffer(capacity, array, arrayOffset, initialPosition, initialLimit, readOnly) } diff --git a/javalib/src/main/scala/java/nio/HeapShortBuffer.scala b/javalib/src/main/scala/java/nio/HeapShortBuffer.scala index 304697b6bc..303c25e424 100644 --- a/javalib/src/main/scala/java/nio/HeapShortBuffer.scala +++ b/javalib/src/main/scala/java/nio/HeapShortBuffer.scala @@ -94,9 +94,12 @@ private[nio] final class HeapShortBuffer private ( private[nio] object HeapShortBuffer { private[nio] implicit object NewHeapShortBuffer extends GenHeapBuffer.NewHeapBuffer[ShortBuffer, Short] { + @inline def apply(capacity: Int, array: Array[Short], arrayOffset: Int, initialPosition: Int, initialLimit: Int, - readOnly: Boolean): ShortBuffer = { + readOnly: Boolean, direct: Boolean): ShortBuffer = { + if (direct) + throw new AssertionError("Cannot create a direct HeapShortBuffer") new HeapShortBuffer(capacity, array, arrayOffset, initialPosition, initialLimit, readOnly) } diff --git a/test-suite/js/src/main/scala/org/scalajs/testsuite/utils/Platform.scala b/test-suite/js/src/main/scala/org/scalajs/testsuite/utils/Platform.scala index 65ab362539..f3ed6f5ebe 100644 --- a/test-suite/js/src/main/scala/org/scalajs/testsuite/utils/Platform.scala +++ b/test-suite/js/src/main/scala/org/scalajs/testsuite/utils/Platform.scala @@ -86,8 +86,6 @@ object Platform { def hasCompliantModuleInit: Boolean = BuildInfo.compliantModuleInit - def hasDirectBuffers: Boolean = typedArrays - def regexSupportsUnicodeCase: Boolean = assumedESVersion >= ESVersion.ES2015 diff --git a/test-suite/js/src/test/scala/org/scalajs/testsuite/niobuffer/ByteBufferJSTest.scala b/test-suite/js/src/test/scala/org/scalajs/testsuite/niobuffer/ByteBufferJSTest.scala index a3001e4a13..225bf5f3d0 100644 --- a/test-suite/js/src/test/scala/org/scalajs/testsuite/niobuffer/ByteBufferJSTest.scala +++ b/test-suite/js/src/test/scala/org/scalajs/testsuite/niobuffer/ByteBufferJSTest.scala @@ -14,20 +14,6 @@ package org.scalajs.testsuite.niobuffer import org.scalajs.testsuite.niobuffer.BufferFactory.ByteBufferFactory -object AllocDirectByteBufferJSTest extends SupportsTypedArrays - -class AllocDirectByteBufferJSTest extends ByteBufferTest { - val factory: ByteBufferFactory = - new ByteBufferFactories.AllocDirectByteBufferFactory -} - -object SlicedAllocDirectByteBufferJSTest extends SupportsTypedArrays - -class SlicedAllocDirectByteBufferJSTest extends ByteBufferTest { - val factory: ByteBufferFactory = - new ByteBufferFactories.SlicedAllocDirectByteBufferFactory -} - object WrappedTypedArrayByteBufferJSTest extends SupportsTypedArrays class WrappedTypedArrayByteBufferJSTest extends ByteBufferTest { diff --git a/test-suite/js/src/test/scala/org/scalajs/testsuite/niobuffer/CharBufferJSTest.scala b/test-suite/js/src/test/scala/org/scalajs/testsuite/niobuffer/CharBufferJSTest.scala index 216f0483ae..278e93124e 100644 --- a/test-suite/js/src/test/scala/org/scalajs/testsuite/niobuffer/CharBufferJSTest.scala +++ b/test-suite/js/src/test/scala/org/scalajs/testsuite/niobuffer/CharBufferJSTest.scala @@ -38,31 +38,11 @@ class WrappedTypedArrayCharBufferJSTest extends CharBufferTest { // Char views of byte buffers -object CharViewOfAllocDirectByteBufferBigEndianJSTest extends SupportsTypedArrays - -class CharViewOfAllocDirectByteBufferBigEndianJSTest - extends CharViewOfByteBufferTest(new AllocDirectByteBufferFactory, ByteOrder.BIG_ENDIAN) - -object CharViewOfSlicedAllocDirectByteBufferBigEndianJSTest extends SupportsTypedArrays - -class CharViewOfSlicedAllocDirectByteBufferBigEndianJSTest - extends CharViewOfByteBufferTest(new SlicedAllocDirectByteBufferFactory, ByteOrder.BIG_ENDIAN) - object CharViewOfWrappedTypedArrayByteBufferBigEndianJSTest extends SupportsTypedArrays class CharViewOfWrappedTypedArrayByteBufferBigEndianJSTest extends CharViewOfByteBufferTest(new WrappedTypedArrayByteBufferFactory, ByteOrder.BIG_ENDIAN) -object CharViewOfAllocDirectByteBufferLittleEndianJSTest extends SupportsTypedArrays - -class CharViewOfAllocDirectByteBufferLittleEndianJSTest - extends CharViewOfByteBufferTest(new AllocDirectByteBufferFactory, ByteOrder.LITTLE_ENDIAN) - -object CharViewOfSlicedAllocDirectByteBufferLittleEndianJSTest extends SupportsTypedArrays - -class CharViewOfSlicedAllocDirectByteBufferLittleEndianJSTest - extends CharViewOfByteBufferTest(new SlicedAllocDirectByteBufferFactory, ByteOrder.LITTLE_ENDIAN) - object CharViewOfWrappedTypedArrayByteBufferLittleEndianJSTest extends SupportsTypedArrays class CharViewOfWrappedTypedArrayByteBufferLittleEndianJSTest @@ -70,35 +50,12 @@ class CharViewOfWrappedTypedArrayByteBufferLittleEndianJSTest // Read only Char views of byte buffers -object ReadOnlyCharViewOfAllocDirectByteBufferBigEndianJSTest extends SupportsTypedArrays - -class ReadOnlyCharViewOfAllocDirectByteBufferBigEndianJSTest - extends ReadOnlyCharViewOfByteBufferTest(new AllocDirectByteBufferFactory, ByteOrder.BIG_ENDIAN) - -object ReadOnlyCharViewOfSlicedAllocDirectByteBufferBigEndianJSTest extends SupportsTypedArrays - -class ReadOnlyCharViewOfSlicedAllocDirectByteBufferBigEndianJSTest - extends ReadOnlyCharViewOfByteBufferTest( - new SlicedAllocDirectByteBufferFactory, ByteOrder.BIG_ENDIAN) - object ReadOnlyCharViewOfWrappedTypedArrayByteBufferBigEndianJSTest extends SupportsTypedArrays class ReadOnlyCharViewOfWrappedTypedArrayByteBufferBigEndianJSTest extends ReadOnlyCharViewOfByteBufferTest( new WrappedTypedArrayByteBufferFactory, ByteOrder.BIG_ENDIAN) -object ReadOnlyCharViewOfAllocDirectByteBufferLittleEndianJSTest extends SupportsTypedArrays - -class ReadOnlyCharViewOfAllocDirectByteBufferLittleEndianJSTest - extends ReadOnlyCharViewOfByteBufferTest( - new AllocDirectByteBufferFactory, ByteOrder.LITTLE_ENDIAN) - -object ReadOnlyCharViewOfSlicedAllocDirectByteBufferLittleEndianJSTest extends SupportsTypedArrays - -class ReadOnlyCharViewOfSlicedAllocDirectByteBufferLittleEndianJSTest - extends ReadOnlyCharViewOfByteBufferTest( - new SlicedAllocDirectByteBufferFactory, ByteOrder.LITTLE_ENDIAN) - object ReadOnlyCharViewOfWrappedTypedArrayByteBufferLittleEndianJSTest extends SupportsTypedArrays class ReadOnlyCharViewOfWrappedTypedArrayByteBufferLittleEndianJSTest diff --git a/test-suite/js/src/test/scala/org/scalajs/testsuite/niobuffer/DoubleBufferJSTest.scala b/test-suite/js/src/test/scala/org/scalajs/testsuite/niobuffer/DoubleBufferJSTest.scala index 82d4f486b2..5527c8e49e 100644 --- a/test-suite/js/src/test/scala/org/scalajs/testsuite/niobuffer/DoubleBufferJSTest.scala +++ b/test-suite/js/src/test/scala/org/scalajs/testsuite/niobuffer/DoubleBufferJSTest.scala @@ -38,31 +38,11 @@ class WrappedTypedArrayDoubleBufferJSTest extends DoubleBufferTest { // Double views of byte buffers -object DoubleViewOfAllocDirectByteBufferBigEndianJSTest extends SupportsTypedArrays - -class DoubleViewOfAllocDirectByteBufferBigEndianJSTest - extends DoubleViewOfByteBufferTest(new AllocDirectByteBufferFactory, ByteOrder.BIG_ENDIAN) - -object DoubleViewOfSlicedAllocDirectByteBufferBigEndianJSTest extends SupportsTypedArrays - -class DoubleViewOfSlicedAllocDirectByteBufferBigEndianJSTest - extends DoubleViewOfByteBufferTest(new SlicedAllocDirectByteBufferFactory, ByteOrder.BIG_ENDIAN) - object DoubleViewOfWrappedTypedArrayByteBufferBigEndianJSTest extends SupportsTypedArrays class DoubleViewOfWrappedTypedArrayByteBufferBigEndianJSTest extends DoubleViewOfByteBufferTest(new WrappedTypedArrayByteBufferFactory, ByteOrder.BIG_ENDIAN) -object DoubleViewOfAllocDirectByteBufferLittleEndianJSTest extends SupportsTypedArrays - -class DoubleViewOfAllocDirectByteBufferLittleEndianJSTest - extends DoubleViewOfByteBufferTest(new AllocDirectByteBufferFactory, ByteOrder.LITTLE_ENDIAN) - -object DoubleViewOfSlicedAllocDirectByteBufferLittleEndianJSTest extends SupportsTypedArrays - -class DoubleViewOfSlicedAllocDirectByteBufferLittleEndianJSTest extends DoubleViewOfByteBufferTest( - new SlicedAllocDirectByteBufferFactory, ByteOrder.LITTLE_ENDIAN) - object DoubleViewOfWrappedTypedArrayByteBufferLittleEndianJSTest extends SupportsTypedArrays class DoubleViewOfWrappedTypedArrayByteBufferLittleEndianJSTest extends DoubleViewOfByteBufferTest( @@ -70,36 +50,12 @@ class DoubleViewOfWrappedTypedArrayByteBufferLittleEndianJSTest extends DoubleVi // Read only Double views of byte buffers -object ReadOnlyDoubleViewOfAllocDirectByteBufferBigEndianJSTest extends SupportsTypedArrays - -class ReadOnlyDoubleViewOfAllocDirectByteBufferBigEndianJSTest - extends ReadOnlyDoubleViewOfByteBufferTest( - new AllocDirectByteBufferFactory, ByteOrder.BIG_ENDIAN) - -object ReadOnlyDoubleViewOfSlicedAllocDirectByteBufferBigEndianJSTest extends SupportsTypedArrays - -class ReadOnlyDoubleViewOfSlicedAllocDirectByteBufferBigEndianJSTest - extends ReadOnlyDoubleViewOfByteBufferTest( - new SlicedAllocDirectByteBufferFactory, ByteOrder.BIG_ENDIAN) - object ReadOnlyDoubleViewOfWrappedTypedArrayByteBufferBigEndianJSTest extends SupportsTypedArrays class ReadOnlyDoubleViewOfWrappedTypedArrayByteBufferBigEndianJSTest extends ReadOnlyDoubleViewOfByteBufferTest( new WrappedTypedArrayByteBufferFactory, ByteOrder.BIG_ENDIAN) -object ReadOnlyDoubleViewOfAllocDirectByteBufferLittleEndianJSTest extends SupportsTypedArrays - -class ReadOnlyDoubleViewOfAllocDirectByteBufferLittleEndianJSTest - extends ReadOnlyDoubleViewOfByteBufferTest( - new AllocDirectByteBufferFactory, ByteOrder.LITTLE_ENDIAN) - -object ReadOnlyDoubleViewOfSlicedAllocDirectByteBufferLittleEndianJSTest extends SupportsTypedArrays - -class ReadOnlyDoubleViewOfSlicedAllocDirectByteBufferLittleEndianJSTest - extends ReadOnlyDoubleViewOfByteBufferTest( - new SlicedAllocDirectByteBufferFactory, ByteOrder.LITTLE_ENDIAN) - object ReadOnlyDoubleViewOfWrappedTypedArrayByteBufferLittleEndianJSTest extends SupportsTypedArrays class ReadOnlyDoubleViewOfWrappedTypedArrayByteBufferLittleEndianJSTest diff --git a/test-suite/js/src/test/scala/org/scalajs/testsuite/niobuffer/FloatBufferJSTest.scala b/test-suite/js/src/test/scala/org/scalajs/testsuite/niobuffer/FloatBufferJSTest.scala index adbcce684c..82e5052e4d 100644 --- a/test-suite/js/src/test/scala/org/scalajs/testsuite/niobuffer/FloatBufferJSTest.scala +++ b/test-suite/js/src/test/scala/org/scalajs/testsuite/niobuffer/FloatBufferJSTest.scala @@ -38,31 +38,11 @@ class WrappedTypedArrayFloatBufferJSTest extends FloatBufferTest { // Float views of byte buffers -object FloatViewOfAllocDirectByteBufferBigEndianJSTest extends SupportsTypedArrays - -class FloatViewOfAllocDirectByteBufferBigEndianJSTest - extends FloatViewOfByteBufferTest(new AllocDirectByteBufferFactory, ByteOrder.BIG_ENDIAN) - -object FloatViewOfSlicedAllocDirectByteBufferBigEndianJSTest extends SupportsTypedArrays - -class FloatViewOfSlicedAllocDirectByteBufferBigEndianJSTest - extends FloatViewOfByteBufferTest(new SlicedAllocDirectByteBufferFactory, ByteOrder.BIG_ENDIAN) - object FloatViewOfWrappedTypedArrayByteBufferBigEndianJSTest extends SupportsTypedArrays class FloatViewOfWrappedTypedArrayByteBufferBigEndianJSTest extends FloatViewOfByteBufferTest(new WrappedTypedArrayByteBufferFactory, ByteOrder.BIG_ENDIAN) -object FloatViewOfAllocDirectByteBufferLittleEndianJSTest extends SupportsTypedArrays - -class FloatViewOfAllocDirectByteBufferLittleEndianJSTest - extends FloatViewOfByteBufferTest(new AllocDirectByteBufferFactory, ByteOrder.LITTLE_ENDIAN) - -object FloatViewOfSlicedAllocDirectByteBufferLittleEndianJSTest extends SupportsTypedArrays - -class FloatViewOfSlicedAllocDirectByteBufferLittleEndianJSTest extends FloatViewOfByteBufferTest( - new SlicedAllocDirectByteBufferFactory, ByteOrder.LITTLE_ENDIAN) - object FloatViewOfWrappedTypedArrayByteBufferLittleEndianJSTest extends SupportsTypedArrays class FloatViewOfWrappedTypedArrayByteBufferLittleEndianJSTest extends FloatViewOfByteBufferTest( @@ -70,35 +50,12 @@ class FloatViewOfWrappedTypedArrayByteBufferLittleEndianJSTest extends FloatView // Read only Float views of byte buffers -object ReadOnlyFloatViewOfAllocDirectByteBufferBigEndianJSTest extends SupportsTypedArrays - -class ReadOnlyFloatViewOfAllocDirectByteBufferBigEndianJSTest - extends ReadOnlyFloatViewOfByteBufferTest(new AllocDirectByteBufferFactory, ByteOrder.BIG_ENDIAN) - -object ReadOnlyFloatViewOfSlicedAllocDirectByteBufferBigEndianJSTest extends SupportsTypedArrays - -class ReadOnlyFloatViewOfSlicedAllocDirectByteBufferBigEndianJSTest - extends ReadOnlyFloatViewOfByteBufferTest( - new SlicedAllocDirectByteBufferFactory, ByteOrder.BIG_ENDIAN) - object ReadOnlyFloatViewOfWrappedTypedArrayByteBufferBigEndianJSTest extends SupportsTypedArrays class ReadOnlyFloatViewOfWrappedTypedArrayByteBufferBigEndianJSTest extends ReadOnlyFloatViewOfByteBufferTest( new WrappedTypedArrayByteBufferFactory, ByteOrder.BIG_ENDIAN) -object ReadOnlyFloatViewOfAllocDirectByteBufferLittleEndianJSTest extends SupportsTypedArrays - -class ReadOnlyFloatViewOfAllocDirectByteBufferLittleEndianJSTest - extends ReadOnlyFloatViewOfByteBufferTest( - new AllocDirectByteBufferFactory, ByteOrder.LITTLE_ENDIAN) - -object ReadOnlyFloatViewOfSlicedAllocDirectByteBufferLittleEndianJSTest extends SupportsTypedArrays - -class ReadOnlyFloatViewOfSlicedAllocDirectByteBufferLittleEndianJSTest - extends ReadOnlyFloatViewOfByteBufferTest( - new SlicedAllocDirectByteBufferFactory, ByteOrder.LITTLE_ENDIAN) - object ReadOnlyFloatViewOfWrappedTypedArrayByteBufferLittleEndianJSTest extends SupportsTypedArrays class ReadOnlyFloatViewOfWrappedTypedArrayByteBufferLittleEndianJSTest diff --git a/test-suite/js/src/test/scala/org/scalajs/testsuite/niobuffer/IntBufferJSTest.scala b/test-suite/js/src/test/scala/org/scalajs/testsuite/niobuffer/IntBufferJSTest.scala index 98651bc497..defd59f901 100644 --- a/test-suite/js/src/test/scala/org/scalajs/testsuite/niobuffer/IntBufferJSTest.scala +++ b/test-suite/js/src/test/scala/org/scalajs/testsuite/niobuffer/IntBufferJSTest.scala @@ -38,31 +38,11 @@ class WrappedTypedArrayIntBufferJSTest extends IntBufferTest { // Int views of byte buffers -object IntViewOfAllocDirectByteBufferBigEndianJSTest extends SupportsTypedArrays - -class IntViewOfAllocDirectByteBufferBigEndianJSTest - extends IntViewOfByteBufferTest(new AllocDirectByteBufferFactory, ByteOrder.BIG_ENDIAN) - -object IntViewOfSlicedAllocDirectByteBufferBigEndianJSTest extends SupportsTypedArrays - -class IntViewOfSlicedAllocDirectByteBufferBigEndianJSTest - extends IntViewOfByteBufferTest(new SlicedAllocDirectByteBufferFactory, ByteOrder.BIG_ENDIAN) - object IntViewOfWrappedTypedArrayByteBufferBigEndianJSTest extends SupportsTypedArrays class IntViewOfWrappedTypedArrayByteBufferBigEndianJSTest extends IntViewOfByteBufferTest(new WrappedTypedArrayByteBufferFactory, ByteOrder.BIG_ENDIAN) -object IntViewOfAllocDirectByteBufferLittleEndianJSTest extends SupportsTypedArrays - -class IntViewOfAllocDirectByteBufferLittleEndianJSTest - extends IntViewOfByteBufferTest(new AllocDirectByteBufferFactory, ByteOrder.LITTLE_ENDIAN) - -object IntViewOfSlicedAllocDirectByteBufferLittleEndianJSTest extends SupportsTypedArrays - -class IntViewOfSlicedAllocDirectByteBufferLittleEndianJSTest - extends IntViewOfByteBufferTest(new SlicedAllocDirectByteBufferFactory, ByteOrder.LITTLE_ENDIAN) - object IntViewOfWrappedTypedArrayByteBufferLittleEndianJSTest extends SupportsTypedArrays class IntViewOfWrappedTypedArrayByteBufferLittleEndianJSTest @@ -70,35 +50,12 @@ class IntViewOfWrappedTypedArrayByteBufferLittleEndianJSTest // Read only Int views of byte buffers -object ReadOnlyIntViewOfAllocDirectByteBufferBigEndianJSTest extends SupportsTypedArrays - -class ReadOnlyIntViewOfAllocDirectByteBufferBigEndianJSTest - extends ReadOnlyIntViewOfByteBufferTest(new AllocDirectByteBufferFactory, ByteOrder.BIG_ENDIAN) - -object ReadOnlyIntViewOfSlicedAllocDirectByteBufferBigEndianJSTest extends SupportsTypedArrays - -class ReadOnlyIntViewOfSlicedAllocDirectByteBufferBigEndianJSTest - extends ReadOnlyIntViewOfByteBufferTest( - new SlicedAllocDirectByteBufferFactory, ByteOrder.BIG_ENDIAN) - object ReadOnlyIntViewOfWrappedTypedArrayByteBufferBigEndianJSTest extends SupportsTypedArrays class ReadOnlyIntViewOfWrappedTypedArrayByteBufferBigEndianJSTest extends ReadOnlyIntViewOfByteBufferTest( new WrappedTypedArrayByteBufferFactory, ByteOrder.BIG_ENDIAN) -object ReadOnlyIntViewOfAllocDirectByteBufferLittleEndianJSTest extends SupportsTypedArrays - -class ReadOnlyIntViewOfAllocDirectByteBufferLittleEndianJSTest - extends ReadOnlyIntViewOfByteBufferTest( - new AllocDirectByteBufferFactory, ByteOrder.LITTLE_ENDIAN) - -object ReadOnlyIntViewOfSlicedAllocDirectByteBufferLittleEndianJSTest extends SupportsTypedArrays - -class ReadOnlyIntViewOfSlicedAllocDirectByteBufferLittleEndianJSTest - extends ReadOnlyIntViewOfByteBufferTest( - new SlicedAllocDirectByteBufferFactory, ByteOrder.LITTLE_ENDIAN) - object ReadOnlyIntViewOfWrappedTypedArrayByteBufferLittleEndianJSTest extends SupportsTypedArrays class ReadOnlyIntViewOfWrappedTypedArrayByteBufferLittleEndianJSTest diff --git a/test-suite/js/src/test/scala/org/scalajs/testsuite/niobuffer/LongBufferJSTest.scala b/test-suite/js/src/test/scala/org/scalajs/testsuite/niobuffer/LongBufferJSTest.scala index eea21b7512..d9457f14d3 100644 --- a/test-suite/js/src/test/scala/org/scalajs/testsuite/niobuffer/LongBufferJSTest.scala +++ b/test-suite/js/src/test/scala/org/scalajs/testsuite/niobuffer/LongBufferJSTest.scala @@ -19,31 +19,11 @@ import org.scalajs.testsuite.niobuffer.ByteBufferJSFactories._ // Long views of byte buffers -object LongViewOfAllocDirectByteBufferBigEndianJSTest extends SupportsTypedArrays - -class LongViewOfAllocDirectByteBufferBigEndianJSTest - extends LongViewOfByteBufferTest(new AllocDirectByteBufferFactory, ByteOrder.BIG_ENDIAN) - -object LongViewOfSlicedAllocDirectByteBufferBigEndianJSTest extends SupportsTypedArrays - -class LongViewOfSlicedAllocDirectByteBufferBigEndianJSTest - extends LongViewOfByteBufferTest(new SlicedAllocDirectByteBufferFactory, ByteOrder.BIG_ENDIAN) - object LongViewOfWrappedTypedArrayByteBufferBigEndianJSTest extends SupportsTypedArrays class LongViewOfWrappedTypedArrayByteBufferBigEndianJSTest extends LongViewOfByteBufferTest(new WrappedTypedArrayByteBufferFactory, ByteOrder.BIG_ENDIAN) -object LongViewOfAllocDirectByteBufferLittleEndianJSTest extends SupportsTypedArrays - -class LongViewOfAllocDirectByteBufferLittleEndianJSTest - extends LongViewOfByteBufferTest(new AllocDirectByteBufferFactory, ByteOrder.LITTLE_ENDIAN) - -object LongViewOfSlicedAllocDirectByteBufferLittleEndianJSTest extends SupportsTypedArrays - -class LongViewOfSlicedAllocDirectByteBufferLittleEndianJSTest - extends LongViewOfByteBufferTest(new SlicedAllocDirectByteBufferFactory, ByteOrder.LITTLE_ENDIAN) - object LongViewOfWrappedTypedArrayByteBufferLittleEndianJSTest extends SupportsTypedArrays class LongViewOfWrappedTypedArrayByteBufferLittleEndianJSTest @@ -51,35 +31,12 @@ class LongViewOfWrappedTypedArrayByteBufferLittleEndianJSTest // Read only Long views of byte buffers -object ReadOnlyLongViewOfAllocDirectByteBufferBigEndianJSTest extends SupportsTypedArrays - -class ReadOnlyLongViewOfAllocDirectByteBufferBigEndianJSTest - extends ReadOnlyLongViewOfByteBufferTest(new AllocDirectByteBufferFactory, ByteOrder.BIG_ENDIAN) - -object ReadOnlyLongViewOfSlicedAllocDirectByteBufferBigEndianJSTest extends SupportsTypedArrays - -class ReadOnlyLongViewOfSlicedAllocDirectByteBufferBigEndianJSTest - extends ReadOnlyLongViewOfByteBufferTest( - new SlicedAllocDirectByteBufferFactory, ByteOrder.BIG_ENDIAN) - object ReadOnlyLongViewOfWrappedTypedArrayByteBufferBigEndianJSTest extends SupportsTypedArrays class ReadOnlyLongViewOfWrappedTypedArrayByteBufferBigEndianJSTest extends ReadOnlyLongViewOfByteBufferTest( new WrappedTypedArrayByteBufferFactory, ByteOrder.BIG_ENDIAN) -object ReadOnlyLongViewOfAllocDirectByteBufferLittleEndianJSTest extends SupportsTypedArrays - -class ReadOnlyLongViewOfAllocDirectByteBufferLittleEndianJSTest - extends ReadOnlyLongViewOfByteBufferTest( - new AllocDirectByteBufferFactory, ByteOrder.LITTLE_ENDIAN) - -object ReadOnlyLongViewOfSlicedAllocDirectByteBufferLittleEndianJSTest extends SupportsTypedArrays - -class ReadOnlyLongViewOfSlicedAllocDirectByteBufferLittleEndianJSTest - extends ReadOnlyLongViewOfByteBufferTest( - new SlicedAllocDirectByteBufferFactory, ByteOrder.LITTLE_ENDIAN) - object ReadOnlyLongViewOfWrappedTypedArrayByteBufferLittleEndianJSTest extends SupportsTypedArrays class ReadOnlyLongViewOfWrappedTypedArrayByteBufferLittleEndianJSTest diff --git a/test-suite/js/src/test/scala/org/scalajs/testsuite/niobuffer/ShortBufferJSTest.scala b/test-suite/js/src/test/scala/org/scalajs/testsuite/niobuffer/ShortBufferJSTest.scala index e632f2d8d4..7a195018cc 100644 --- a/test-suite/js/src/test/scala/org/scalajs/testsuite/niobuffer/ShortBufferJSTest.scala +++ b/test-suite/js/src/test/scala/org/scalajs/testsuite/niobuffer/ShortBufferJSTest.scala @@ -38,31 +38,11 @@ class WrappedTypedArrayShortBufferJSTest extends ShortBufferTest { // Short views of byte buffers -object ShortViewOfAllocDirectByteBufferBigEndianJSTest extends SupportsTypedArrays - -class ShortViewOfAllocDirectByteBufferBigEndianJSTest - extends ShortViewOfByteBufferTest(new AllocDirectByteBufferFactory, ByteOrder.BIG_ENDIAN) - -object ShortViewOfSlicedAllocDirectByteBufferBigEndianJSTest extends SupportsTypedArrays - -class ShortViewOfSlicedAllocDirectByteBufferBigEndianJSTest - extends ShortViewOfByteBufferTest(new SlicedAllocDirectByteBufferFactory, ByteOrder.BIG_ENDIAN) - object ShortViewOfWrappedTypedArrayByteBufferBigEndianJSTest extends SupportsTypedArrays class ShortViewOfWrappedTypedArrayByteBufferBigEndianJSTest extends ShortViewOfByteBufferTest(new WrappedTypedArrayByteBufferFactory, ByteOrder.BIG_ENDIAN) -object ShortViewOfAllocDirectByteBufferLittleEndianJSTest extends SupportsTypedArrays - -class ShortViewOfAllocDirectByteBufferLittleEndianJSTest - extends ShortViewOfByteBufferTest(new AllocDirectByteBufferFactory, ByteOrder.LITTLE_ENDIAN) - -object ShortViewOfSlicedAllocDirectByteBufferLittleEndianJSTest extends SupportsTypedArrays - -class ShortViewOfSlicedAllocDirectByteBufferLittleEndianJSTest extends ShortViewOfByteBufferTest( - new SlicedAllocDirectByteBufferFactory, ByteOrder.LITTLE_ENDIAN) - object ShortViewOfWrappedTypedArrayByteBufferLittleEndianJSTest extends SupportsTypedArrays class ShortViewOfWrappedTypedArrayByteBufferLittleEndianJSTest extends ShortViewOfByteBufferTest( @@ -70,35 +50,12 @@ class ShortViewOfWrappedTypedArrayByteBufferLittleEndianJSTest extends ShortView // Read only Short views of byte buffers -object ReadOnlyShortViewOfAllocDirectByteBufferBigEndianJSTest extends SupportsTypedArrays - -class ReadOnlyShortViewOfAllocDirectByteBufferBigEndianJSTest - extends ReadOnlyShortViewOfByteBufferTest(new AllocDirectByteBufferFactory, ByteOrder.BIG_ENDIAN) - -object ReadOnlyShortViewOfSlicedAllocDirectByteBufferBigEndianJSTest extends SupportsTypedArrays - -class ReadOnlyShortViewOfSlicedAllocDirectByteBufferBigEndianJSTest - extends ReadOnlyShortViewOfByteBufferTest( - new SlicedAllocDirectByteBufferFactory, ByteOrder.BIG_ENDIAN) - object ReadOnlyShortViewOfWrappedTypedArrayByteBufferBigEndianJSTest extends SupportsTypedArrays class ReadOnlyShortViewOfWrappedTypedArrayByteBufferBigEndianJSTest extends ReadOnlyShortViewOfByteBufferTest( new WrappedTypedArrayByteBufferFactory, ByteOrder.BIG_ENDIAN) -object ReadOnlyShortViewOfAllocDirectByteBufferLittleEndianJSTest extends SupportsTypedArrays - -class ReadOnlyShortViewOfAllocDirectByteBufferLittleEndianJSTest - extends ReadOnlyShortViewOfByteBufferTest( - new AllocDirectByteBufferFactory, ByteOrder.LITTLE_ENDIAN) - -object ReadOnlyShortViewOfSlicedAllocDirectByteBufferLittleEndianJSTest extends SupportsTypedArrays - -class ReadOnlyShortViewOfSlicedAllocDirectByteBufferLittleEndianJSTest - extends ReadOnlyShortViewOfByteBufferTest( - new SlicedAllocDirectByteBufferFactory, ByteOrder.LITTLE_ENDIAN) - object ReadOnlyShortViewOfWrappedTypedArrayByteBufferLittleEndianJSTest extends SupportsTypedArrays class ReadOnlyShortViewOfWrappedTypedArrayByteBufferLittleEndianJSTest diff --git a/test-suite/jvm/src/main/scala/org/scalajs/testsuite/utils/Platform.scala b/test-suite/jvm/src/main/scala/org/scalajs/testsuite/utils/Platform.scala index 38e5007356..3c9477e33b 100644 --- a/test-suite/jvm/src/main/scala/org/scalajs/testsuite/utils/Platform.scala +++ b/test-suite/jvm/src/main/scala/org/scalajs/testsuite/utils/Platform.scala @@ -45,7 +45,6 @@ object Platform { def hasCompliantNullPointers: Boolean = true def hasCompliantStringIndexOutOfBounds: Boolean = true def hasCompliantModule: Boolean = true - def hasDirectBuffers: Boolean = true def regexSupportsUnicodeCase: Boolean = true def regexSupportsUnicodeCharacterClasses: Boolean = true diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/BitSetTest.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/BitSetTest.scala index d32ef61fed..6a8776c27e 100644 --- a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/BitSetTest.scala +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/BitSetTest.scala @@ -1493,9 +1493,7 @@ class BitSetTest { assertEquals(1, allocateByteBuffer.position()) } - @Test def valueOf_ByteBuffer_typedArrays(): Unit = { - assumeTrue("requires support for direct Buffers", hasDirectBuffers) - + @Test def valueOf_ByteBuffer_direct(): Unit = { val eightBS = makeEightBS() val eightBytes = eightBS.toByteArray() diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/niobuffer/BaseBufferTest.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/niobuffer/BaseBufferTest.scala index ae1227ecb8..b015704076 100644 --- a/test-suite/shared/src/test/scala/org/scalajs/testsuite/niobuffer/BaseBufferTest.scala +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/niobuffer/BaseBufferTest.scala @@ -58,6 +58,11 @@ abstract class BaseBufferTest { assertEquals(9, buf2.capacity()) } + @Test def isDirect(): Unit = { + val buf = allocBuffer(10) + assertEquals(createsDirect, buf.isDirect()) + } + @Test def isReadOnly()(): Unit = { val buf = allocBuffer(10) if (createsReadOnly) @@ -345,6 +350,8 @@ abstract class BaseBufferTest { buf1.limit(7) buf1.mark() val buf2 = buf1.sliceChain() + assertEquals(buf1.isDirect(), buf2.isDirect()) + assertEquals(buf1.isReadOnly(), buf2.isReadOnly()) assertEquals(0, buf2.position()) assertEquals(4, buf2.limit()) assertEquals(4, buf2.capacity()) @@ -380,6 +387,8 @@ abstract class BaseBufferTest { buf1.limit(7) buf1.mark() val buf2 = buf1.duplicateChain() + assertEquals(buf1.isDirect(), buf2.isDirect()) + assertEquals(buf1.isReadOnly(), buf2.isReadOnly()) assertEquals(3, buf2.position()) assertEquals(7, buf2.limit()) assertEquals(10, buf2.capacity()) diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/niobuffer/BufferFactory.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/niobuffer/BufferFactory.scala index 96862e6a60..9ebd541ef6 100644 --- a/test-suite/shared/src/test/scala/org/scalajs/testsuite/niobuffer/BufferFactory.scala +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/niobuffer/BufferFactory.scala @@ -36,6 +36,8 @@ sealed abstract class BufferFactory { def boxedElemsFromInt(elems: Int*): Array[AnyRef] = boxed(elems.map(elemFromInt).toArray) + val createsDirect: Boolean = false + val createsReadOnly: Boolean = false protected[this] def explicitlyValidateCapacity(capacity: Int): Unit = { @@ -208,6 +210,8 @@ object BufferFactory { } trait WrappedTypedArrayBufferFactory extends WrappedBufferFactory { + override val createsDirect: Boolean = true + protected def baseWrap(array: Array[ElementType], offset: Int, length: Int): BufferType = { val buf = baseWrap(array) @@ -258,6 +262,12 @@ object BufferFactory { } trait ByteBufferViewFactory extends BufferFactory { + protected val byteBufferFactory: ByteBufferFactory + + require(!byteBufferFactory.createsReadOnly) + + override val createsDirect: Boolean = byteBufferFactory.createsDirect + def baseAllocBuffer(capacity: Int): BufferType def allocBuffer(capacity: Int): BufferType = diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/niobuffer/ByteBufferFactories.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/niobuffer/ByteBufferFactories.scala index 3fb38d2021..eb491cc1b4 100644 --- a/test-suite/shared/src/test/scala/org/scalajs/testsuite/niobuffer/ByteBufferFactories.scala +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/niobuffer/ByteBufferFactories.scala @@ -31,6 +31,8 @@ object ByteBufferFactories { } class AllocDirectByteBufferFactory extends ByteBufferFactory { + override val createsDirect: Boolean = true + def allocBuffer(capacity: Int): ByteBuffer = ByteBuffer.allocateDirect(capacity) } diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/niobuffer/ByteBufferTest.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/niobuffer/ByteBufferTest.scala index 440bc2e80b..8f2d751bf9 100644 --- a/test-suite/shared/src/test/scala/org/scalajs/testsuite/niobuffer/ByteBufferTest.scala +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/niobuffer/ByteBufferTest.scala @@ -1146,3 +1146,13 @@ class SlicedAllocByteBufferTest extends ByteBufferTest { val factory: ByteBufferFactory = new ByteBufferFactories.SlicedAllocByteBufferFactory } + +class AllocDirectByteBufferTest extends ByteBufferTest { + val factory: ByteBufferFactory = + new ByteBufferFactories.AllocDirectByteBufferFactory +} + +class SlicedAllocDirectByteBufferTest extends ByteBufferTest { + val factory: ByteBufferFactory = + new ByteBufferFactories.SlicedAllocDirectByteBufferFactory +} diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/niobuffer/CharBufferTest.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/niobuffer/CharBufferTest.scala index 3fd948c62a..ab543082f5 100644 --- a/test-suite/shared/src/test/scala/org/scalajs/testsuite/niobuffer/CharBufferTest.scala +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/niobuffer/CharBufferTest.scala @@ -37,10 +37,9 @@ abstract class CharBufferTest extends BaseBufferTest { } class ByteBufferCharViewFactory( - byteBufferFactory: BufferFactory.ByteBufferFactory, + protected val byteBufferFactory: BufferFactory.ByteBufferFactory, order: ByteOrder) extends Factory with BufferFactory.ByteBufferViewFactory { - require(!byteBufferFactory.createsReadOnly) def baseAllocBuffer(capacity: Int): CharBuffer = byteBufferFactory.allocBuffer(capacity * 2).order(order).asCharBuffer() @@ -181,3 +180,34 @@ class ReadOnlyCharViewOfWrappedByteBufferLittleEndianTest class ReadOnlyCharViewOfSlicedAllocByteBufferLittleEndianTest extends ReadOnlyCharViewOfByteBufferTest( new SlicedAllocByteBufferFactory, ByteOrder.LITTLE_ENDIAN) + +// Char views of direct byte buffers + +class CharViewOfAllocDirectByteBufferBigEndianTest + extends CharViewOfByteBufferTest(new AllocDirectByteBufferFactory, ByteOrder.BIG_ENDIAN) + +class CharViewOfSlicedAllocDirectByteBufferBigEndianTest + extends CharViewOfByteBufferTest(new SlicedAllocDirectByteBufferFactory, ByteOrder.BIG_ENDIAN) + +class CharViewOfAllocDirectByteBufferLittleEndianTest + extends CharViewOfByteBufferTest(new AllocDirectByteBufferFactory, ByteOrder.LITTLE_ENDIAN) + +class CharViewOfSlicedAllocDirectByteBufferLittleEndianTest + extends CharViewOfByteBufferTest(new SlicedAllocDirectByteBufferFactory, ByteOrder.LITTLE_ENDIAN) + +// Read only Char views of direct byte buffers + +class ReadOnlyCharViewOfAllocDirectByteBufferBigEndianTest + extends ReadOnlyCharViewOfByteBufferTest(new AllocDirectByteBufferFactory, ByteOrder.BIG_ENDIAN) + +class ReadOnlyCharViewOfSlicedAllocDirectByteBufferBigEndianTest + extends ReadOnlyCharViewOfByteBufferTest( + new SlicedAllocDirectByteBufferFactory, ByteOrder.BIG_ENDIAN) + +class ReadOnlyCharViewOfAllocDirectByteBufferLittleEndianTest + extends ReadOnlyCharViewOfByteBufferTest( + new AllocDirectByteBufferFactory, ByteOrder.LITTLE_ENDIAN) + +class ReadOnlyCharViewOfSlicedAllocDirectByteBufferLittleEndianTest + extends ReadOnlyCharViewOfByteBufferTest( + new SlicedAllocDirectByteBufferFactory, ByteOrder.LITTLE_ENDIAN) diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/niobuffer/DoubleBufferTest.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/niobuffer/DoubleBufferTest.scala index 511ebb7b22..906c702793 100644 --- a/test-suite/shared/src/test/scala/org/scalajs/testsuite/niobuffer/DoubleBufferTest.scala +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/niobuffer/DoubleBufferTest.scala @@ -33,10 +33,9 @@ abstract class DoubleBufferTest extends BaseBufferTest { } class ByteBufferDoubleViewFactory( - byteBufferFactory: BufferFactory.ByteBufferFactory, + protected val byteBufferFactory: BufferFactory.ByteBufferFactory, order: ByteOrder) extends Factory with BufferFactory.ByteBufferViewFactory { - require(!byteBufferFactory.createsReadOnly) def baseAllocBuffer(capacity: Int): DoubleBuffer = byteBufferFactory.allocBuffer(capacity * 8).order(order).asDoubleBuffer() @@ -121,3 +120,35 @@ class ReadOnlyDoubleViewOfWrappedByteBufferLittleEndianTest class ReadOnlyDoubleViewOfSlicedAllocByteBufferLittleEndianTest extends ReadOnlyDoubleViewOfByteBufferTest( new SlicedAllocByteBufferFactory, ByteOrder.LITTLE_ENDIAN) + +// Double views of direct byte buffers + +class DoubleViewOfAllocDirectByteBufferBigEndianTest + extends DoubleViewOfByteBufferTest(new AllocDirectByteBufferFactory, ByteOrder.BIG_ENDIAN) + +class DoubleViewOfSlicedAllocDirectByteBufferBigEndianTest + extends DoubleViewOfByteBufferTest(new SlicedAllocDirectByteBufferFactory, ByteOrder.BIG_ENDIAN) + +class DoubleViewOfAllocDirectByteBufferLittleEndianTest + extends DoubleViewOfByteBufferTest(new AllocDirectByteBufferFactory, ByteOrder.LITTLE_ENDIAN) + +class DoubleViewOfSlicedAllocDirectByteBufferLittleEndianTest extends DoubleViewOfByteBufferTest( + new SlicedAllocDirectByteBufferFactory, ByteOrder.LITTLE_ENDIAN) + +// Read only Double views of direct byte buffers + +class ReadOnlyDoubleViewOfAllocDirectByteBufferBigEndianTest + extends ReadOnlyDoubleViewOfByteBufferTest( + new AllocDirectByteBufferFactory, ByteOrder.BIG_ENDIAN) + +class ReadOnlyDoubleViewOfSlicedAllocDirectByteBufferBigEndianTest + extends ReadOnlyDoubleViewOfByteBufferTest( + new SlicedAllocDirectByteBufferFactory, ByteOrder.BIG_ENDIAN) + +class ReadOnlyDoubleViewOfAllocDirectByteBufferLittleEndianTest + extends ReadOnlyDoubleViewOfByteBufferTest( + new AllocDirectByteBufferFactory, ByteOrder.LITTLE_ENDIAN) + +class ReadOnlyDoubleViewOfSlicedAllocDirectByteBufferLittleEndianTest + extends ReadOnlyDoubleViewOfByteBufferTest( + new SlicedAllocDirectByteBufferFactory, ByteOrder.LITTLE_ENDIAN) diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/niobuffer/FloatBufferTest.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/niobuffer/FloatBufferTest.scala index f58ebbbb02..c87f972baa 100644 --- a/test-suite/shared/src/test/scala/org/scalajs/testsuite/niobuffer/FloatBufferTest.scala +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/niobuffer/FloatBufferTest.scala @@ -33,10 +33,9 @@ abstract class FloatBufferTest extends BaseBufferTest { } class ByteBufferFloatViewFactory( - byteBufferFactory: BufferFactory.ByteBufferFactory, + protected val byteBufferFactory: BufferFactory.ByteBufferFactory, order: ByteOrder) extends Factory with BufferFactory.ByteBufferViewFactory { - require(!byteBufferFactory.createsReadOnly) def baseAllocBuffer(capacity: Int): FloatBuffer = byteBufferFactory.allocBuffer(capacity * 4).order(order).asFloatBuffer() @@ -120,3 +119,34 @@ class ReadOnlyFloatViewOfWrappedByteBufferLittleEndianTest class ReadOnlyFloatViewOfSlicedAllocByteBufferLittleEndianTest extends ReadOnlyFloatViewOfByteBufferTest( new SlicedAllocByteBufferFactory, ByteOrder.LITTLE_ENDIAN) + +// Float views of direct byte buffers + +class FloatViewOfAllocDirectByteBufferBigEndianTest + extends FloatViewOfByteBufferTest(new AllocDirectByteBufferFactory, ByteOrder.BIG_ENDIAN) + +class FloatViewOfSlicedAllocDirectByteBufferBigEndianTest + extends FloatViewOfByteBufferTest(new SlicedAllocDirectByteBufferFactory, ByteOrder.BIG_ENDIAN) + +class FloatViewOfAllocDirectByteBufferLittleEndianTest + extends FloatViewOfByteBufferTest(new AllocDirectByteBufferFactory, ByteOrder.LITTLE_ENDIAN) + +class FloatViewOfSlicedAllocDirectByteBufferLittleEndianTest extends FloatViewOfByteBufferTest( + new SlicedAllocDirectByteBufferFactory, ByteOrder.LITTLE_ENDIAN) + +// Read only Float views of direct byte buffers + +class ReadOnlyFloatViewOfAllocDirectByteBufferBigEndianTest + extends ReadOnlyFloatViewOfByteBufferTest(new AllocDirectByteBufferFactory, ByteOrder.BIG_ENDIAN) + +class ReadOnlyFloatViewOfSlicedAllocDirectByteBufferBigEndianTest + extends ReadOnlyFloatViewOfByteBufferTest( + new SlicedAllocDirectByteBufferFactory, ByteOrder.BIG_ENDIAN) + +class ReadOnlyFloatViewOfAllocDirectByteBufferLittleEndianTest + extends ReadOnlyFloatViewOfByteBufferTest( + new AllocDirectByteBufferFactory, ByteOrder.LITTLE_ENDIAN) + +class ReadOnlyFloatViewOfSlicedAllocDirectByteBufferLittleEndianTest + extends ReadOnlyFloatViewOfByteBufferTest( + new SlicedAllocDirectByteBufferFactory, ByteOrder.LITTLE_ENDIAN) diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/niobuffer/IntBufferTest.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/niobuffer/IntBufferTest.scala index 6615a7bc65..9af857bdfc 100644 --- a/test-suite/shared/src/test/scala/org/scalajs/testsuite/niobuffer/IntBufferTest.scala +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/niobuffer/IntBufferTest.scala @@ -33,10 +33,9 @@ abstract class IntBufferTest extends BaseBufferTest { } class ByteBufferIntViewFactory( - byteBufferFactory: BufferFactory.ByteBufferFactory, + protected val byteBufferFactory: BufferFactory.ByteBufferFactory, order: ByteOrder) extends Factory with BufferFactory.ByteBufferViewFactory { - require(!byteBufferFactory.createsReadOnly) def baseAllocBuffer(capacity: Int): IntBuffer = byteBufferFactory.allocBuffer(capacity * 4).order(order).asIntBuffer() @@ -117,3 +116,33 @@ class ReadOnlyIntViewOfWrappedByteBufferLittleEndianTest class ReadOnlyIntViewOfSlicedAllocByteBufferLittleEndianTest extends ReadOnlyIntViewOfByteBufferTest( new SlicedAllocByteBufferFactory, ByteOrder.LITTLE_ENDIAN) + +// Int views of direct byte buffers + +class IntViewOfAllocDirectByteBufferBigEndianTest + extends IntViewOfByteBufferTest(new AllocDirectByteBufferFactory, ByteOrder.BIG_ENDIAN) + +class IntViewOfSlicedAllocDirectByteBufferBigEndianTest + extends IntViewOfByteBufferTest(new SlicedAllocDirectByteBufferFactory, ByteOrder.BIG_ENDIAN) + +class IntViewOfAllocDirectByteBufferLittleEndianTest + extends IntViewOfByteBufferTest(new AllocDirectByteBufferFactory, ByteOrder.LITTLE_ENDIAN) + +class IntViewOfSlicedAllocDirectByteBufferLittleEndianTest + extends IntViewOfByteBufferTest(new SlicedAllocDirectByteBufferFactory, ByteOrder.LITTLE_ENDIAN) + +// Read only Int views of direct byte buffers + +class ReadOnlyIntViewOfAllocDirectByteBufferBigEndianTest + extends ReadOnlyIntViewOfByteBufferTest(new AllocDirectByteBufferFactory, ByteOrder.BIG_ENDIAN) + +class ReadOnlyIntViewOfSlicedAllocDirectByteBufferBigEndianTest + extends ReadOnlyIntViewOfByteBufferTest( + new SlicedAllocDirectByteBufferFactory, ByteOrder.BIG_ENDIAN) + +class ReadOnlyIntViewOfAllocDirectByteBufferLittleEndianTest extends ReadOnlyIntViewOfByteBufferTest( + new AllocDirectByteBufferFactory, ByteOrder.LITTLE_ENDIAN) + +class ReadOnlyIntViewOfSlicedAllocDirectByteBufferLittleEndianTest + extends ReadOnlyIntViewOfByteBufferTest( + new SlicedAllocDirectByteBufferFactory, ByteOrder.LITTLE_ENDIAN) diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/niobuffer/LongBufferTest.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/niobuffer/LongBufferTest.scala index c31d054f8d..2ef4f9a3df 100644 --- a/test-suite/shared/src/test/scala/org/scalajs/testsuite/niobuffer/LongBufferTest.scala +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/niobuffer/LongBufferTest.scala @@ -33,10 +33,9 @@ abstract class LongBufferTest extends BaseBufferTest { } class ByteBufferLongViewFactory( - byteBufferFactory: BufferFactory.ByteBufferFactory, + protected val byteBufferFactory: BufferFactory.ByteBufferFactory, order: ByteOrder) extends Factory with BufferFactory.ByteBufferViewFactory { - require(!byteBufferFactory.createsReadOnly) def baseAllocBuffer(capacity: Int): LongBuffer = byteBufferFactory.allocBuffer(capacity * 8).order(order).asLongBuffer() @@ -118,3 +117,34 @@ class ReadOnlyLongViewOfWrappedByteBufferLittleEndianTest class ReadOnlyLongViewOfSlicedAllocByteBufferLittleEndianTest extends ReadOnlyLongViewOfByteBufferTest( new SlicedAllocByteBufferFactory, ByteOrder.LITTLE_ENDIAN) + +// Long views of direct byte buffers + +class LongViewOfAllocDirectByteBufferBigEndianTest + extends LongViewOfByteBufferTest(new AllocDirectByteBufferFactory, ByteOrder.BIG_ENDIAN) + +class LongViewOfSlicedAllocDirectByteBufferBigEndianTest + extends LongViewOfByteBufferTest(new SlicedAllocDirectByteBufferFactory, ByteOrder.BIG_ENDIAN) + +class LongViewOfAllocDirectByteBufferLittleEndianTest + extends LongViewOfByteBufferTest(new AllocDirectByteBufferFactory, ByteOrder.LITTLE_ENDIAN) + +class LongViewOfSlicedAllocDirectByteBufferLittleEndianTest + extends LongViewOfByteBufferTest(new SlicedAllocDirectByteBufferFactory, ByteOrder.LITTLE_ENDIAN) + +// Read only Long views of direct byte buffers + +class ReadOnlyLongViewOfAllocDirectByteBufferBigEndianTest + extends ReadOnlyLongViewOfByteBufferTest(new AllocDirectByteBufferFactory, ByteOrder.BIG_ENDIAN) + +class ReadOnlyLongViewOfSlicedAllocDirectByteBufferBigEndianTest + extends ReadOnlyLongViewOfByteBufferTest( + new SlicedAllocDirectByteBufferFactory, ByteOrder.BIG_ENDIAN) + +class ReadOnlyLongViewOfAllocDirectByteBufferLittleEndianTest + extends ReadOnlyLongViewOfByteBufferTest( + new AllocDirectByteBufferFactory, ByteOrder.LITTLE_ENDIAN) + +class ReadOnlyLongViewOfSlicedAllocDirectByteBufferLittleEndianTest + extends ReadOnlyLongViewOfByteBufferTest( + new SlicedAllocDirectByteBufferFactory, ByteOrder.LITTLE_ENDIAN) diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/niobuffer/ShortBufferTest.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/niobuffer/ShortBufferTest.scala index ee59e3832f..5cf836e3e5 100644 --- a/test-suite/shared/src/test/scala/org/scalajs/testsuite/niobuffer/ShortBufferTest.scala +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/niobuffer/ShortBufferTest.scala @@ -33,10 +33,9 @@ abstract class ShortBufferTest extends BaseBufferTest { } class ByteBufferShortViewFactory( - byteBufferFactory: BufferFactory.ByteBufferFactory, + protected val byteBufferFactory: BufferFactory.ByteBufferFactory, order: ByteOrder) extends Factory with BufferFactory.ByteBufferViewFactory { - require(!byteBufferFactory.createsReadOnly) def baseAllocBuffer(capacity: Int): ShortBuffer = byteBufferFactory.allocBuffer(capacity * 2).order(order).asShortBuffer() @@ -120,3 +119,34 @@ class ReadOnlyShortViewOfWrappedByteBufferLittleEndianTest class ReadOnlyShortViewOfSlicedAllocByteBufferLittleEndianTest extends ReadOnlyShortViewOfByteBufferTest( new SlicedAllocByteBufferFactory, ByteOrder.LITTLE_ENDIAN) + +// Short views of direct byte buffers + +class ShortViewOfAllocDirectByteBufferBigEndianTest + extends ShortViewOfByteBufferTest(new AllocDirectByteBufferFactory, ByteOrder.BIG_ENDIAN) + +class ShortViewOfSlicedAllocDirectByteBufferBigEndianTest + extends ShortViewOfByteBufferTest(new SlicedAllocDirectByteBufferFactory, ByteOrder.BIG_ENDIAN) + +class ShortViewOfAllocDirectByteBufferLittleEndianTest + extends ShortViewOfByteBufferTest(new AllocDirectByteBufferFactory, ByteOrder.LITTLE_ENDIAN) + +class ShortViewOfSlicedAllocDirectByteBufferLittleEndianTest extends ShortViewOfByteBufferTest( + new SlicedAllocDirectByteBufferFactory, ByteOrder.LITTLE_ENDIAN) + +// Read only Short views of direct byte buffers + +class ReadOnlyShortViewOfAllocDirectByteBufferBigEndianTest + extends ReadOnlyShortViewOfByteBufferTest(new AllocDirectByteBufferFactory, ByteOrder.BIG_ENDIAN) + +class ReadOnlyShortViewOfSlicedAllocDirectByteBufferBigEndianTest + extends ReadOnlyShortViewOfByteBufferTest( + new SlicedAllocDirectByteBufferFactory, ByteOrder.BIG_ENDIAN) + +class ReadOnlyShortViewOfAllocDirectByteBufferLittleEndianTest + extends ReadOnlyShortViewOfByteBufferTest( + new AllocDirectByteBufferFactory, ByteOrder.LITTLE_ENDIAN) + +class ReadOnlyShortViewOfSlicedAllocDirectByteBufferLittleEndianTest + extends ReadOnlyShortViewOfByteBufferTest( + new SlicedAllocDirectByteBufferFactory, ByteOrder.LITTLE_ENDIAN) From d74a453909e4db9d78ac687ba4280c29ed5f9fd5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Mon, 16 Mar 2026 09:26:45 +0100 Subject: [PATCH 24/32] Be maximally tolerant in `String.startsWith` with a `null` prefix. When the offset is out of bounds, it is unclear whether the method should throw or return false. The JVM is inconsistent. We choose to be maximally tolerant to delay UB until there is no other choice. --- .../src/main/scala/java/lang/_String.scala | 26 ++++++++++++++----- .../testsuite/javalib/lang/StringTest.scala | 14 ++++++++++ 2 files changed, 33 insertions(+), 7 deletions(-) diff --git a/javalib/src/main/scala/java/lang/_String.scala b/javalib/src/main/scala/java/lang/_String.scala index 16f1b6a56d..64359b15e8 100644 --- a/javalib/src/main/scala/java/lang/_String.scala +++ b/javalib/src/main/scala/java/lang/_String.scala @@ -373,13 +373,25 @@ final class _String private () // scalastyle:ignore @inline def startsWith(prefix: String, toffset: Int): scala.Boolean = { - if (LinkingInfo.esVersion >= ESVersion.ES2015) { - prefix.getClass() // null check - (toffset <= length() && toffset >= 0 && - thisString.asInstanceOf[js.Dynamic].startsWith(prefix, toffset).asInstanceOf[scala.Boolean]) - } else { - (toffset <= length() && toffset >= 0 && - thisString.jsSubstring(toffset, toffset + prefix.length()) == prefix) + /* If `prefix == null` and the offset is out of bounds, the result is not + * clearly specified. The JavaDoc could be interpreted as either returning + * false or throwing. The JVM is inconsistent. On Temurin, it returns + * `false` for a negative `toffset`, but throws an NPE for + * `toffset >= this.length()`. + * + * Since our NPEs are UB, we choose to be maximally tolerant. We want to + * delay the UB until there is no other choice. This guarantees that *if* + * the JVM returns `false`, *then* we don't run into UB. We run into UB + * *only if* the JVM throws an NPE (but not always). + */ + + toffset <= length() && toffset >= 0 && { + if (LinkingInfo.esVersion >= ESVersion.ES2015) { + prefix.getClass() // null check + thisString.asInstanceOf[js.Dynamic].startsWith(prefix, toffset).asInstanceOf[scala.Boolean] + } else { + thisString.jsSubstring(toffset, toffset + prefix.length()) == prefix + } } } diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/lang/StringTest.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/lang/StringTest.scala index aa8555a10c..93138e7d46 100644 --- a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/lang/StringTest.scala +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/lang/StringTest.scala @@ -484,7 +484,21 @@ class StringTest { assertFalse("Scala.js".startsWith("", -1)) assertFalse("Scala.js".startsWith("", 9)) + // When the offset is within bounds, a null prefix causes an NPE + assertThrowsNPEIfCompliant("Scala.js".startsWith(null, 0)) assertThrowsNPEIfCompliant("Scala.js".startsWith(null, 2)) + assertThrowsNPEIfCompliant("Scala.js".startsWith(null, 8)) + + /* But if the offset is out of bounds, the result is not clearly specified, + * and the JVM is inconsistent. + * Our chosen semantics is to be maximally tolerant, to delay UB until + * there is no other choice. We test that behavior. + */ + if (!executingInJVM) { + assertFalse("Scala.js".startsWith(null, -1)) + assertFalse("Scala.js".startsWith(null, 9)) + assertFalse("Scala.js".startsWith(null, 50)) + } } @Test def toCharArray(): Unit = From 3a2e7cfcb41b428226b8085acb84fd7a3d26ed60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Sun, 15 Mar 2026 11:49:28 +0100 Subject: [PATCH 25/32] Make ju.Objects.requireNonNull overloads follow Semantics for UB NPE. Unfortunately, it is unclear what to do about the two overloads of `ju.Objects.requireNonNull` with an explicit message. Either we keep the message and we have to throw an explicit NPE; or we trigger a UB NPE, but we ignore the message. We choose to trigger the UB, but then we catch a genuine NPE to rethrow it with the correct message. The message will still be lost when the NPEs are Fatal, but at least they are used when NPEs are Compliant. For the overload that takes a `messageSupplier`, additionally we choose to only call its `get()` method when we actually need the message, i.e., only in Compliant mode. These changes allow for good optimization opportunities. We intrinsify the 3 overloads so we can get the best possible code for every checked behavior. For Unchecked mode, the tests completely disappear. For Compliant and Fatal, we don't need to throw a fake NPE that we immediately try to catch and re-throw. --- .../src/main/scala/java/util/Objects.scala | 70 +++++++- .../frontend/optimizer/OptimizerCore.scala | 90 +++++++++- .../testsuite/javalib/util/ObjectsTest.scala | 154 +++++++++++++++++- 3 files changed, 301 insertions(+), 13 deletions(-) diff --git a/javalib/src/main/scala/java/util/Objects.scala b/javalib/src/main/scala/java/util/Objects.scala index 0f54da6f52..7dbc5c6975 100644 --- a/javalib/src/main/scala/java/util/Objects.scala +++ b/javalib/src/main/scala/java/util/Objects.scala @@ -66,14 +66,17 @@ object Objects { if (a.asInstanceOf[AnyRef] eq b.asInstanceOf[AnyRef]) 0 else c.compare(a, b) + // Intrinsic @inline - def requireNonNull[T](obj: T): T = - if (obj == null) throw new NullPointerException - else obj + def requireNonNull[T](obj: T): T = { + obj.getClass() // null check + obj + } + // Intrinsic @inline def requireNonNull[T](obj: T, message: String): T = - if (obj == null) throw new NullPointerException(message) + if (obj == null) throwNPEWithMessage(message) else obj @inline @@ -84,8 +87,65 @@ object Objects { def nonNull(obj: Any): Boolean = obj != null + // Intrinsic @inline def requireNonNull[T](obj: T, messageSupplier: Supplier[String]): T = - if (obj == null) throw new NullPointerException(messageSupplier.get()) + if (obj == null) throwNPEWithMessage(messageSupplier) else obj + + /* The following methods are our best attempt to deal with the overloads of + * `requireNonNull` with an explicit message. We want to trigger a UB NPE. + * However, if we do that, we lose the `message`. This is fine for the + * Unchecked behavior, debatable for Fatal, and plain wrong for Compliant. + * + * To recover the message in Compliant mode, we immediately catch a genuine + * NPE if that is what the UB throws, and rethrow a genuine NPE with the + * correct message. + * + * In Fatal mode, there is unfortunately nothing we can do. We have no way + * of constructing another UndefinedBehaviorError with a different cause. + * + * --- + * + * For the overload with a Supplier, there is an additional semantic decision + * to make: when `obj == null`, when exactly do we call + * `messageSupplier.get()`? There are two valid choices: + * + * - always, regardless of the checked behavior, or + * - only if and when we actually need a message, which would only happen in + * Compliant mode. + * + * We choose the latter alternative, because it is better optimizable. + * A program that would rely on the supplier being evaluated in non-Compliant + * mode would be dubious anyway. That would only result in well-defined + * semantics if its `get()` method threw an exception itself. + * + * --- + * + * The methods are `@noinline` because they are in a slow path anyway. + * We don't want the try..catch'es to appear at call site. That pollutes + * performance for the entire enclosing function in some engines. + */ + + @noinline private def throwNPEWithMessage(message: String): Nothing = { + try { + throw null + } catch { + case _: NullPointerException => + throw new NullPointerException(message) + } + } + + @noinline private def throwNPEWithMessage(messageSupplier: Supplier[String]): Nothing = { + try { + throw null + } catch { + case _: NullPointerException => + /* It is important that we only call `messageSupplier.get()` here (after UB). + * Otherwise a throwing messageSupplier might mask UB in Unchecked mode. + * For example in: `requireNonNull(null, () => throw new IllegalArgumentException())`. + */ + throw new NullPointerException(messageSupplier.get()) + } + } } diff --git a/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala b/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala index e52c4afc67..b80dedac3e 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala @@ -3341,6 +3341,81 @@ private[optimizer] abstract class OptimizerCore( default } + // java.util.Objects + + case RequireNonNullNoMessage => + // Replace by a checkNotNull so that the result gets a refined type in the process + val List(tobj) = targs + cont(checkNotNull(tobj)) + + case RequireNonNullWithMessage | RequireNonNullWithMessageSupplier => + val List(tobj, tmessage) = targs + + def objBinding = Binding.temp(LocalName("obj"), tobj) + def messageBinding = Binding.temp(LocalName("message"), tmessage) + + semantics.nullPointers match { + case CheckedBehavior.Compliant if tobj.tpe.isNullable => + /* Inline the specified semantics. We don't use the regular inlined + * body because it throws a dummy NPE that it catches before + * rethrowing the correct one. + * + * The constructor of NullPointerException and the `get()` method + * of `Supplier` must be reachable, since the javalib code must + * reference them (otherwise it could not be valid in the first + * place). + */ + withNewLocalDefs(List(objBinding, messageBinding)) { (localDefs, cont1) => + val List(objLocalDef, messageLocalDef) = localDefs + + val actualMessage: Tree = if (intrinsicCode == RequireNonNullWithMessage) { + messageLocalDef.newReplacement + } else { + trampoline { + pretransformApply(ApplyFlags.empty, messageLocalDef.toPreTransform, + MethodIdent(SupplierGetMethodName), Nil, AnyType, + isStat = false, usePreTransform = true) { tresultAny => + TailCalls.done { + finishTransformExpr(foldAsInstanceOf(tresultAny, StringClassType)) + } + } + } + } + + val resultType = objLocalDef.tpe.base.toNonNullable + + cont1(PreTransTree(Block( + If(BinaryOp(BinaryOp.===, objLocalDef.newReplacement, Null()), { + UnaryOp(UnaryOp.Throw, + New(NullPointerExceptionClass, + MethodIdent(StringArgConstructorName), List(actualMessage))) + }, { + finishTransformExpr(foldCast(objLocalDef.toPreTransform, resultType)) + })(resultType) + ))) + }(cont) + + case _ => + /* Evaluate the arguments, drop the message{,Supplier}, and check + * the obj for null (which is a cast in Unchecked). We can do this + * because the chosen semantics for the Fatal and Unchecked + * overloads of `requireNonNull` disregard the message{,Supplier}. + */ + finishTransformStat(tmessage) match { + case Skip() => + // Avoid the binding for tobj; use it as is + cont(checkNotNull(tobj)) + + case evalMessageStat => + withNewLocalDef(objBinding) { (objLocalDef, cont1) => + cont1(PreTransBlock( + evalMessageStat, + checkNotNull(objLocalDef.toPreTransform) + )) + }(cont) + } + } + // js.special case ObjectLiteral => @@ -6399,6 +6474,7 @@ private[optimizer] object OptimizerCore { FieldName(JavaScriptExceptionClass, SimpleFieldName("exception")) private val AnyArgConstructorName = MethodName.constructor(List(ClassRef(ObjectClass))) + private val StringArgConstructorName = MethodName.constructor(List(ClassRef(BoxedStringClass))) private val TupleFirstMethodName = MethodName("_1", Nil, ClassRef(ObjectClass)) private val TupleSecondMethodName = MethodName("_2", Nil, ClassRef(ObjectClass)) @@ -6406,6 +6482,8 @@ private[optimizer] object OptimizerCore { private val ClassTagApplyMethodName = MethodName("apply", List(ClassRef(ClassClass)), ClassRef(ClassName("scala.reflect.ClassTag"))) + private val SupplierGetMethodName = MethodName("get", Nil, ObjectRef) + def isUnsignedPowerOf2(x: Int): Boolean = (x & (x - 1)) == 0 && x != 0 @@ -7303,7 +7381,11 @@ private[optimizer] object OptimizerCore { final val ClassGetName = GenericArrayBuilderResult + 1 - final val ArrayToJSArray = ClassGetName + 1 + final val RequireNonNullNoMessage = ClassGetName + 1 + final val RequireNonNullWithMessage = RequireNonNullNoMessage + 1 + final val RequireNonNullWithMessageSupplier = RequireNonNullWithMessage + 1 + + final val ArrayToJSArray = RequireNonNullWithMessageSupplier + 1 final val ObjectLiteral = ArrayToJSArray + 1 @@ -7338,6 +7420,7 @@ private[optimizer] object OptimizerCore { private val O = ClassRef(ObjectClass) private val ClassClassRef = ClassRef(ClassClass) private val StringClassRef = ClassRef(BoxedStringClass) + private val SupplierClassRef = ClassRef(ClassName("java.util.function.Supplier")) private val SeqClassRef = ClassRef(ClassName("scala.collection.Seq")) private val ImmutableSeqClassRef = ClassRef(ClassName("scala.collection.immutable.Seq")) private val JSObjectClassRef = ClassRef(ClassName("scala.scalajs.js.Object")) @@ -7361,6 +7444,11 @@ private[optimizer] object OptimizerCore { ClassName("java.lang.Class") -> List( m("getName", Nil, StringClassRef) -> ClassGetName ), + ClassName("java.util.Objects$") -> List( + m("requireNonNull", List(O), O) -> RequireNonNullNoMessage, + m("requireNonNull", List(O, StringClassRef), O) -> RequireNonNullWithMessage, + m("requireNonNull", List(O, SupplierClassRef), O) -> RequireNonNullWithMessageSupplier + ), ClassName("scala.scalajs.runtime.package$") -> List( m("genericArrayToJSArray", List(O), JSArrayClassRef) -> ArrayToJSArray, m("refArrayToJSArray", List(ArrayTypeRef(O, 1)), JSArrayClassRef) -> ArrayToJSArray, diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/ObjectsTest.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/ObjectsTest.scala index 843bc2a16a..b6bcb9fee4 100644 --- a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/ObjectsTest.scala +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/ObjectsTest.scala @@ -16,10 +16,14 @@ import java.{util => ju} import org.junit.Test import org.junit.Assert._ -import org.scalajs.testsuite.utils.AssertThrows.assertThrows + +import org.scalajs.testsuite.utils.AssertThrows.{assertThrows, _} +import org.scalajs.testsuite.utils.Platform.hasCompliantNullPointers class ObjectsTest { + @noinline private def hide[T](x: T): T = x + @Test def testEquals(): Unit = { val obj = new Object assertTrue(ju.Objects.equals(null, null)) @@ -87,11 +91,67 @@ class ObjectsTest { assertTrue(ju.Objects.compare(1, 2, cmp1) < 0) } + /* The overloads of requireNonNull are subject to intrinsic optimizations. + * Make sure to test them with arguments that are both known and not-known + * to be nullable or non-nullable. + */ + @Test def requireNonNull(): Unit = { - assertThrows(classOf[NullPointerException], ju.Objects.requireNonNull(null)) - assertThrows(classOf[NullPointerException], ju.Objects.requireNonNull(null, "message")) + assertThrowsNPEIfCompliant(ju.Objects.requireNonNull(null)) + assertThrowsNPEIfCompliant(ju.Objects.requireNonNull(hide[String](null))) + assertEquals("abc", ju.Objects.requireNonNull("abc")) - assertEquals("abc", ju.Objects.requireNonNull("abc", "")) + assertEquals("abc", ju.Objects.requireNonNull(hide[String]("abc"))) + } + + @Test def requireNonNullWithMessage(): Unit = { + if (hasCompliantNullPointers) { + val e1 = assertThrows(classOf[NullPointerException], + ju.Objects.requireNonNull(null, "the message")) + assertEquals("the message", e1.getMessage()) + + val e2 = assertThrows(classOf[NullPointerException], + ju.Objects.requireNonNull(hide[String](null), "the message")) + assertEquals("the message", e2.getMessage()) + } + + assertEquals("abc", ju.Objects.requireNonNull("abc", "unexpected")) + assertEquals("abc", ju.Objects.requireNonNull(hide[String]("abc"), "unexpected")) + + // The effects of computing the arguments are preserved, in order + + locally { + var effects = 5 + + assertThrows(classOf[IllegalStateException], { + ju.Objects.requireNonNull({ + effects *= 2 + hide[String](null) + }, { + effects += 1 + throw new IllegalStateException() + hide[String]("unexpected") + }) + }) + + assertEquals(11, effects) + } + + if (hasCompliantNullPointers) { + var effects = 5 + + assertThrows(classOf[NullPointerException], { + ju.Objects.requireNonNull({ + effects *= 2 + hide[String](null) + }, { + effects += 1 + hide[String]("unexpected") + }) + }) + + assertEquals(11, effects) + } } @Test def requireNonNullWithMsgSupplier(): Unit = { @@ -101,6 +161,11 @@ class ObjectsTest { def get(): String = message } + // Hidden version; with a distinct instance so that successSupplier can still be inlined + val hiddenSuccessSupplier = hide(new ju.function.Supplier[String] { + def get(): String = message + }) + val failureSupplier = new ju.function.Supplier[String] { def get(): String = { throw new AssertionError( @@ -108,11 +173,86 @@ class ObjectsTest { } } - val e = assertThrows(classOf[NullPointerException], - ju.Objects.requireNonNull(null, successSupplier)) - assertEquals(message, e.getMessage()) + if (hasCompliantNullPointers) { + val e1 = assertThrows(classOf[NullPointerException], + ju.Objects.requireNonNull(null, successSupplier)) + assertEquals(message, e1.getMessage()) + val e2 = assertThrows(classOf[NullPointerException], + ju.Objects.requireNonNull(hide[String](null), successSupplier)) + assertEquals(message, e2.getMessage()) + + val e3 = assertThrows(classOf[NullPointerException], + ju.Objects.requireNonNull(null, hiddenSuccessSupplier)) + assertEquals(message, e3.getMessage()) + val e4 = assertThrows(classOf[NullPointerException], + ju.Objects.requireNonNull(hide[String](null), hiddenSuccessSupplier)) + assertEquals(message, e4.getMessage()) + + // If the supplier returns a null message, we get a null message + val e5 = assertThrows(classOf[NullPointerException], + ju.Objects.requireNonNull(null, () => null)) + assertNull(e5.getMessage()) + val e6 = assertThrows(classOf[NullPointerException], + ju.Objects.requireNonNull(hide[String](null), () => null)) + assertNull(e6.getMessage()) + + // If the supplier itself is null as well, we get an NPE with an unspecified message + assertThrows(classOf[NullPointerException], + ju.Objects.requireNonNull(null, null: ju.function.Supplier[String])) + assertThrows(classOf[NullPointerException], + ju.Objects.requireNonNull(hide[String](null), null: ju.function.Supplier[String])) + assertThrows(classOf[NullPointerException], + ju.Objects.requireNonNull(null, hide[ju.function.Supplier[String]](null))) + assertThrows(classOf[NullPointerException], + ju.Objects.requireNonNull(hide[String](null), hide[ju.function.Supplier[String]](null))) + } assertEquals("abc", ju.Objects.requireNonNull("abc", failureSupplier)) + assertEquals("abc", ju.Objects.requireNonNull(hide[String]("abc"), failureSupplier)) + + assertEquals("abc", + ju.Objects.requireNonNull("abc", null: ju.function.Supplier[String])) + assertEquals("abc", + ju.Objects.requireNonNull(hide[String]("abc"), hide[ju.function.Supplier[String]](null))) + + // The effects of computing the arguments are preserved, in order + + locally { + var effects = 5 + + assertThrows(classOf[IllegalStateException], { + ju.Objects.requireNonNull({ + effects *= 2 + hide[String](null) + }, { + effects += 1 + throw new IllegalStateException() + hide[ju.function.Supplier[String]](null) + }) + }) + + assertEquals(11, effects) + } + + if (hasCompliantNullPointers) { + var effects = 5 + + assertThrows(classOf[NullPointerException], { + ju.Objects.requireNonNull({ + effects *= 2 + hide[String](null) + }, { + effects += 1 + + { () => + effects = -100 - effects + "the message" + }: ju.function.Supplier[String] + }) + }) + + assertEquals(-111, effects) + } } @Test def isNull(): Unit = { From c48475dc1c7b6a4bea06e5b5b93600ee610fd27d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Sun, 15 Mar 2026 12:09:43 +0100 Subject: [PATCH 26/32] Use `requireNonNull` for all explicit null checks in the javalib. --- .../src/main/scala/java/io/FilterReader.scala | 4 +- .../src/main/scala/java/io/InputStream.scala | 5 +- javalib/src/main/scala/java/io/Reader.scala | 9 ++-- javalib/src/main/scala/java/io/Writer.scala | 6 +-- .../main/scala/java/lang/StringBuilder.scala | 6 +-- javalib/src/main/scala/java/lang/System.scala | 14 ++--- .../src/main/scala/java/lang/Throwables.scala | 7 ++- .../src/main/scala/java/lang/_String.scala | 37 ++++++------- .../main/scala/java/lang/reflect/Array.scala | 4 +- .../src/main/scala/java/math/BigDecimal.scala | 18 +++---- .../src/main/scala/java/math/BigInteger.scala | 13 ++--- .../main/scala/java/math/MathContext.scala | 22 +++----- .../src/main/scala/java/nio/ByteBuffer.scala | 6 +-- .../src/main/scala/java/util/ArrayDeque.scala | 43 +++++++-------- .../src/main/scala/java/util/Comparator.scala | 15 +++--- .../java/util/NullRejectingHashMap.scala | 54 +++++++------------ .../src/main/scala/java/util/Optional.scala | 9 ++-- .../main/scala/java/util/PriorityQueue.scala | 6 +-- .../src/main/scala/java/util/Throwables.scala | 29 ++++------ .../src/main/scala/java/util/TreeMap.scala | 3 +- .../util/concurrent/ConcurrentHashMap.scala | 8 ++- .../concurrent/ConcurrentLinkedQueue.scala | 21 ++++---- .../concurrent/ConcurrentSkipListSet.scala | 7 ++- .../math/BigDecimalConstructorsTest.scala | 4 +- .../javalib/math/MathContextTest.scala | 4 +- .../testsuite/javalib/util/OptionalTest.scala | 4 +- .../concurrent/ConcurrentHashMapTest.scala | 4 +- 27 files changed, 150 insertions(+), 212 deletions(-) diff --git a/javalib/src/main/scala/java/io/FilterReader.scala b/javalib/src/main/scala/java/io/FilterReader.scala index 810c875dde..b5aa1d9a45 100644 --- a/javalib/src/main/scala/java/io/FilterReader.scala +++ b/javalib/src/main/scala/java/io/FilterReader.scala @@ -12,9 +12,11 @@ package java.io +import java.util.Objects.requireNonNull + abstract class FilterReader protected (protected val in: Reader) extends Reader { - in.getClass() // null check + requireNonNull(in) override def close(): Unit = in.close() diff --git a/javalib/src/main/scala/java/io/InputStream.scala b/javalib/src/main/scala/java/io/InputStream.scala index e494bf967e..cc03bde1ae 100644 --- a/javalib/src/main/scala/java/io/InputStream.scala +++ b/javalib/src/main/scala/java/io/InputStream.scala @@ -13,6 +13,7 @@ package java.io import java.util.Arrays +import java.util.Objects.requireNonNull abstract class InputStream extends Closeable { def read(): Int @@ -143,7 +144,7 @@ abstract class InputStream extends Closeable { def markSupported(): Boolean = false def transferTo(out: OutputStream): Long = { - out.getClass() // Trigger NPE (if enabled). + val outNonNull = requireNonNull(out) var transferred = 0L val buf = new Array[Byte](4096) @@ -152,7 +153,7 @@ abstract class InputStream extends Closeable { while (bytesRead != -1) { bytesRead = read(buf) if (bytesRead != -1) { - out.write(buf, 0, bytesRead) + outNonNull.write(buf, 0, bytesRead) transferred += bytesRead } } diff --git a/javalib/src/main/scala/java/io/Reader.scala b/javalib/src/main/scala/java/io/Reader.scala index d2733b550c..9a5f56b429 100644 --- a/javalib/src/main/scala/java/io/Reader.scala +++ b/javalib/src/main/scala/java/io/Reader.scala @@ -12,18 +12,17 @@ package java.io -import java.nio.CharBuffer - import scala.annotation.tailrec +import java.nio.CharBuffer +import java.util.Objects.requireNonNull + abstract class Reader() extends Readable with Closeable { protected var lock: Object = this protected def this(lock: Object) = { this() - if (lock eq null) - throw new NullPointerException() - this.lock = lock + this.lock = requireNonNull(lock) } def read(target: CharBuffer): Int = { diff --git a/javalib/src/main/scala/java/io/Writer.scala b/javalib/src/main/scala/java/io/Writer.scala index 4dd6e1bd0d..b4956dcac1 100644 --- a/javalib/src/main/scala/java/io/Writer.scala +++ b/javalib/src/main/scala/java/io/Writer.scala @@ -12,14 +12,14 @@ package java.io +import java.util.Objects.requireNonNull + abstract class Writer() extends Appendable with Closeable with Flushable { protected var lock: Object = this protected def this(lock: Object) = { this() - if (lock eq null) - throw new NullPointerException() - this.lock = lock + this.lock = requireNonNull(lock) } def write(c: Int): Unit = diff --git a/javalib/src/main/scala/java/lang/StringBuilder.scala b/javalib/src/main/scala/java/lang/StringBuilder.scala index 1f560f1d54..d461dbb9d9 100644 --- a/javalib/src/main/scala/java/lang/StringBuilder.scala +++ b/javalib/src/main/scala/java/lang/StringBuilder.scala @@ -12,15 +12,15 @@ package java.lang +import java.util.Objects.requireNonNull + class StringBuilder extends AnyRef with CharSequence with Appendable with java.io.Serializable { private[this] var content: String = "" def this(str: String) = { this() - if (str eq null) - throw new NullPointerException - content = str + content = requireNonNull(str) } def this(initialCapacity: Int) = { diff --git a/javalib/src/main/scala/java/lang/System.scala b/javalib/src/main/scala/java/lang/System.scala index fb0f7baaba..1ada0cc0d2 100644 --- a/javalib/src/main/scala/java/lang/System.scala +++ b/javalib/src/main/scala/java/lang/System.scala @@ -20,6 +20,7 @@ import scala.scalajs.LinkingInfo import java.{util => ju} import java.util.function._ +import java.util.Objects.requireNonNull object System { /* System contains a bag of unrelated features. If we naively implement @@ -92,6 +93,9 @@ object System { import scala.{Boolean, Char, Byte, Short, Int, Long, Float, Double} def mismatch(): Unit = { + requireNonNull(src) + requireNonNull(dest) + // Trigger an ArrayStoreException subject to UB. new Array[String](1).asInstanceOf[Array[Object]](0) = Integer.valueOf(0) } @@ -122,9 +126,7 @@ object System { } } - if (src == null || dest == null) { - throw new NullPointerException() - } else (src match { + src match { case src: Array[AnyRef] => dest match { case dest: Array[AnyRef] => impl(src.length, dest.length, (i, j) => dest(i) = src(j)) @@ -172,7 +174,7 @@ object System { } case _ => mismatch() - }) + } } @inline @@ -284,9 +286,7 @@ object System { @inline def getenv(name: String): String = { - if (name eq null) - throw new NullPointerException - + requireNonNull(name) null } diff --git a/javalib/src/main/scala/java/lang/Throwables.scala b/javalib/src/main/scala/java/lang/Throwables.scala index dabca9619b..09294ed6ee 100644 --- a/javalib/src/main/scala/java/lang/Throwables.scala +++ b/javalib/src/main/scala/java/lang/Throwables.scala @@ -13,6 +13,7 @@ package java.lang import java.util.function._ +import java.util.Objects.requireNonNull import scala.scalajs.js.annotation.JSExport @@ -64,8 +65,7 @@ class Throwable protected (s: String, private var e: Throwable, if (writableStackTrace) { var i = 0 while (i < stackTrace.length) { - if (stackTrace(i) eq null) - throw new NullPointerException() + requireNonNull(stackTrace(i)) i += 1 } @@ -155,8 +155,7 @@ class Throwable protected (s: String, private var e: Throwable, } def addSuppressed(exception: Throwable): Unit = { - if (exception eq null) - throw new NullPointerException + requireNonNull(exception) if (exception eq this) throw new IllegalArgumentException diff --git a/javalib/src/main/scala/java/lang/_String.scala b/javalib/src/main/scala/java/lang/_String.scala index 64359b15e8..abe7cdf8b2 100644 --- a/javalib/src/main/scala/java/lang/_String.scala +++ b/javalib/src/main/scala/java/lang/_String.scala @@ -14,10 +14,7 @@ package java.lang import scala.annotation.{switch, tailrec} -import java.util.Comparator - import scala.scalajs.js -import scala.scalajs.js.annotation._ import scala.scalajs.js.JSStringOps.enableJSStringOps import scala.scalajs.LinkingInfo import scala.scalajs.LinkingInfo.ESVersion @@ -25,7 +22,8 @@ import scala.scalajs.LinkingInfo.ESVersion import java.lang.constant.{Constable, ConstantDesc} import java.nio.ByteBuffer import java.nio.charset.Charset -import java.util.Locale +import java.util.{Comparator, Locale} +import java.util.Objects.requireNonNull import java.util.function._ import java.util.regex._ @@ -196,8 +194,9 @@ final class _String private () // scalastyle:ignore @inline def endsWith(suffix: String): scala.Boolean = { if (LinkingInfo.esVersion >= ESVersion.ES2015) { - suffix.getClass() // null check - thisString.asInstanceOf[js.Dynamic].endsWith(suffix).asInstanceOf[scala.Boolean] + thisString.asInstanceOf[js.Dynamic] + .endsWith(requireNonNull(suffix)) + .asInstanceOf[scala.Boolean] } else { thisString.jsSubstring(this.length() - suffix.length()) == suffix } @@ -294,16 +293,15 @@ final class _String private () // scalastyle:ignore */ def regionMatches(ignoreCase: scala.Boolean, toffset: Int, other: String, ooffset: Int, len: Int): scala.Boolean = { - if (other == null) { - throw new NullPointerException() - } else if (toffset < 0 || ooffset < 0 || len > this.length() - toffset || - len > other.length() - ooffset) { + val otherNonNull = requireNonNull(other) + if (toffset < 0 || ooffset < 0 || len > this.length() - toffset || + len > otherNonNull.length() - ooffset) { false } else if (len <= 0) { true } else { val left = this.substring(toffset, toffset + len) - val right = other.substring(ooffset, ooffset + len) + val right = otherNonNull.substring(ooffset, ooffset + len) if (ignoreCase) left.equalsIgnoreCase(right) else left == right } } @@ -364,8 +362,9 @@ final class _String private () // scalastyle:ignore @inline def startsWith(prefix: String): scala.Boolean = { if (LinkingInfo.esVersion >= ESVersion.ES2015) { - prefix.getClass() // null check - thisString.asInstanceOf[js.Dynamic].startsWith(prefix).asInstanceOf[scala.Boolean] + thisString.asInstanceOf[js.Dynamic] + .startsWith(requireNonNull(prefix)) + .asInstanceOf[scala.Boolean] } else { thisString.jsSubstring(0, prefix.length()) == prefix } @@ -387,8 +386,9 @@ final class _String private () // scalastyle:ignore toffset <= length() && toffset >= 0 && { if (LinkingInfo.esVersion >= ESVersion.ES2015) { - prefix.getClass() // null check - thisString.asInstanceOf[js.Dynamic].startsWith(prefix, toffset).asInstanceOf[scala.Boolean] + thisString.asInstanceOf[js.Dynamic] + .startsWith(requireNonNull(prefix), toffset) + .asInstanceOf[scala.Boolean] } else { thisString.jsSubstring(toffset, toffset + prefix.length()) == prefix } @@ -1040,11 +1040,8 @@ object _String { // scalastyle:ignore result } - def `new`(original: String): String = { - if (original == null) - throw new NullPointerException - original - } + def `new`(original: String): String = + requireNonNull(original) def `new`(buffer: java.lang.StringBuffer): String = buffer.toString diff --git a/javalib/src/main/scala/java/lang/reflect/Array.scala b/javalib/src/main/scala/java/lang/reflect/Array.scala index 4be756a1ea..cf41bd3957 100644 --- a/javalib/src/main/scala/java/lang/reflect/Array.scala +++ b/javalib/src/main/scala/java/lang/reflect/Array.scala @@ -12,6 +12,8 @@ package java.lang.reflect +import java.util.Objects.requireNonNull + object Array { @inline def newInstance(componentType: Class[_], length: Int): AnyRef = @@ -208,7 +210,7 @@ object Array { } private def mismatch(array: AnyRef): Nothing = { - array.getClass() // null check + requireNonNull(array) throw new IllegalArgumentException("argument type mismatch") } } diff --git a/javalib/src/main/scala/java/math/BigDecimal.scala b/javalib/src/main/scala/java/math/BigDecimal.scala index efa799cd71..2ac3330a3b 100644 --- a/javalib/src/main/scala/java/math/BigDecimal.scala +++ b/javalib/src/main/scala/java/math/BigDecimal.scala @@ -27,6 +27,7 @@ import scala.annotation.tailrec import java.lang.{Double => JDouble} import java.util.Arrays +import java.util.Objects.requireNonNull import java.util.ScalaOps._ object BigDecimal { @@ -393,9 +394,7 @@ class BigDecimal() extends Number with Comparable[BigDecimal] { val last = offset + len - 1 // last index to be copied - if (in == null) - throw new NullPointerException("in == null") - + // implicit null check for `in` if (last >= in.length || offset < 0 || len <= 0 || last < 0) { throw new NumberFormatException( s"Bad offset/length: offset=${offset} len=$len in.length=${in.length}") @@ -557,11 +556,8 @@ class BigDecimal() extends Number with Comparable[BigDecimal] { def this(unscaledVal: BigInteger, scale: Int) = { this() - if (unscaledVal == null) - throw new NullPointerException("unscaledVal == null") - _scale = scale - setUnscaledValue(unscaledVal) + setUnscaledValue(requireNonNull(unscaledVal)) } def this(bi: BigInteger) = { @@ -743,9 +739,8 @@ class BigDecimal() extends Number with Comparable[BigDecimal] { divide(divisor, scale, RoundingMode.valueOf(roundingMode)) def divide(divisor: BigDecimal, scale: Int, roundingMode: RoundingMode): BigDecimal = { - if (roundingMode == null) - throw new NullPointerException("roundingMode == null") - else if (divisor.isZero) + requireNonNull(roundingMode) + if (divisor.isZero) throw new ArithmeticException("Division by zero") val diffScale = { @@ -1181,8 +1176,7 @@ class BigDecimal() extends Number with Comparable[BigDecimal] { } def setScale(newScale: Int, roundingMode: RoundingMode): BigDecimal = { - if (roundingMode == null) - throw new NullPointerException("roundingMode == null") + requireNonNull(roundingMode) val diffScale = newScale - _scale.toLong if (diffScale == 0) { diff --git a/javalib/src/main/scala/java/math/BigInteger.scala b/javalib/src/main/scala/java/math/BigInteger.scala index 4e1dc22d21..0077082968 100644 --- a/javalib/src/main/scala/java/math/BigInteger.scala +++ b/javalib/src/main/scala/java/math/BigInteger.scala @@ -43,6 +43,7 @@ package java.math import scala.annotation.tailrec +import java.util.Objects.requireNonNull import java.util.Random import java.util.ScalaOps._ import java.util.function._ @@ -117,14 +118,6 @@ object BigInteger { } } - @inline - private def checkNotNull[T <: AnyRef](reference: T): reference.type = { - if (reference == null) - throw new NullPointerException - else - reference - } - private[math] def checkRangeBasedOnIntArrayLength(byteLength: Int): Unit = { if (byteLength < 0 || byteLength >= ((Int.MaxValue + 1) >>> 5)) throw new ArithmeticException("BigInteger would overflow supported range") @@ -182,7 +175,7 @@ class BigInteger extends Number with Comparable[BigInteger] { def this(signum: Int, magnitude: Array[Byte]) = { this() - checkNotNull(magnitude) + requireNonNull(magnitude) if ((signum < -1) || (signum > 1)) throw new NumberFormatException("Invalid signum value") if (signum == 0) { @@ -238,7 +231,7 @@ class BigInteger extends Number with Comparable[BigInteger] { def this(s: String, radix: Int) = { this() - checkNotNull(s) + requireNonNull(s) if (Character.isRadixInvalid(radix)) throw new NumberFormatException("Radix out of range") if (s.isEmpty) diff --git a/javalib/src/main/scala/java/math/MathContext.scala b/javalib/src/main/scala/java/math/MathContext.scala index a1e0af0455..1f035ea17a 100644 --- a/javalib/src/main/scala/java/math/MathContext.scala +++ b/javalib/src/main/scala/java/math/MathContext.scala @@ -23,6 +23,8 @@ package java.math +import java.util.Objects.requireNonNull + object MathContext { val DECIMAL128 = MathContext(34, RoundingMode.HALF_EVEN) @@ -37,10 +39,9 @@ object MathContext { new MathContext(precision, roundingMode) private def getArgs(s: String): (Int, RoundingMode) = { - checkNotNull(s, "null string") val precisionLength = "precision=".length val roundingModeLength = "roundingMode=".length - val spaceIndex = s.indexOf(' ', precisionLength) + val spaceIndex = s.indexOf(' ', precisionLength) // implicit null check for `s` if (!s.startsWith("precision=") || spaceIndex == -1) invalidMathContext("Missing precision", s) @@ -66,18 +67,15 @@ object MathContext { private def invalidMathContext(reason: String, s: String): Nothing = throw new IllegalArgumentException(reason + ": " + s) - - private def checkNotNull(reference: AnyRef, errorMessage: AnyRef): Unit = { - if (reference == null) - throw new NullPointerException(String.valueOf(errorMessage)) - } } class MathContext(setPrecision: Int, setRoundingMode: RoundingMode) { + if (setPrecision < 0) + throw new IllegalArgumentException("Negative precision: " + setPrecision) private[math] val precision = setPrecision - private[math] val roundingMode = setRoundingMode + private[math] val roundingMode = requireNonNull(setRoundingMode) def getPrecision(): Int = precision @@ -93,7 +91,6 @@ class MathContext(setPrecision: Int, setRoundingMode: RoundingMode) { def this(s: String) = { this(MathContext.getArgs(s)) - checkValid() } override def equals(x: Any): Boolean = x match { @@ -108,11 +105,4 @@ class MathContext(setPrecision: Int, setRoundingMode: RoundingMode) { override def toString(): String = "precision=" + precision + " roundingMode=" + roundingMode - - private def checkValid(): Unit = { - if (precision < 0) - throw new IllegalArgumentException("Negative precision: " + precision) - if (roundingMode == null) - throw new NullPointerException("roundingMode == null") - } } diff --git a/javalib/src/main/scala/java/nio/ByteBuffer.scala b/javalib/src/main/scala/java/nio/ByteBuffer.scala index 7e0c4baaef..d1aaf7c302 100644 --- a/javalib/src/main/scala/java/nio/ByteBuffer.scala +++ b/javalib/src/main/scala/java/nio/ByteBuffer.scala @@ -12,6 +12,8 @@ package java.nio +import java.util.Objects.requireNonNull + import scala.scalajs.js import scala.scalajs.js.typedarray._ @@ -167,9 +169,7 @@ abstract class ByteBuffer private[nio] ( else ByteOrder.LITTLE_ENDIAN final def order(bo: ByteOrder): ByteBuffer = { - if (bo == null) - throw new NullPointerException - _isBigEndian = bo == ByteOrder.BIG_ENDIAN + _isBigEndian = requireNonNull(bo) == ByteOrder.BIG_ENDIAN this } diff --git a/javalib/src/main/scala/java/util/ArrayDeque.scala b/javalib/src/main/scala/java/util/ArrayDeque.scala index 236295371a..7ac81cd8a6 100644 --- a/javalib/src/main/scala/java/util/ArrayDeque.scala +++ b/javalib/src/main/scala/java/util/ArrayDeque.scala @@ -15,6 +15,7 @@ package java.util import java.lang.Cloneable import java.lang.Utils._ +import java.util.Objects.requireNonNull import java.util.ScalaOps._ class ArrayDeque[E] private (initialCapacity: Int) @@ -45,33 +46,27 @@ class ArrayDeque[E] private (initialCapacity: Int) offerLast(e) def offerFirst(e: E): Boolean = { - if (e == null) { - throw new NullPointerException() - } else { - ensureCapacityForAdd() - startIndex -= 1 - if (startIndex < 0) - startIndex = inner.length - 1 - inner(startIndex) = e.asInstanceOf[AnyRef] - status += 1 - empty = false - true - } + requireNonNull(e) + ensureCapacityForAdd() + startIndex -= 1 + if (startIndex < 0) + startIndex = inner.length - 1 + inner(startIndex) = e.asInstanceOf[AnyRef] + status += 1 + empty = false + true } def offerLast(e: E): Boolean = { - if (e == null) { - throw new NullPointerException() - } else { - ensureCapacityForAdd() - endIndex += 1 - if (endIndex > inner.length) - endIndex = 1 - inner(endIndex - 1) = e.asInstanceOf[AnyRef] - status += 1 - empty = false - true - } + requireNonNull(e) + ensureCapacityForAdd() + endIndex += 1 + if (endIndex > inner.length) + endIndex = 1 + inner(endIndex - 1) = e.asInstanceOf[AnyRef] + status += 1 + empty = false + true } def removeFirst(): E = { diff --git a/javalib/src/main/scala/java/util/Comparator.scala b/javalib/src/main/scala/java/util/Comparator.scala index 478827aa31..c7f1dd848a 100644 --- a/javalib/src/main/scala/java/util/Comparator.scala +++ b/javalib/src/main/scala/java/util/Comparator.scala @@ -12,6 +12,7 @@ package java.util +import java.util.Objects.requireNonNull import java.util.function._ // scalastyle:off equals.hash.code @@ -39,7 +40,7 @@ trait Comparator[A] { self => @inline def thenComparing(other: Comparator[_ >: A]): Comparator[A] = { - other.getClass() // null check + requireNonNull(other) new Comparator[A] with Serializable { def compare(o1: A, o2: A) = { val cmp = self.compare(o1, o2) @@ -126,8 +127,8 @@ object Comparator { @inline def comparing[T, U](keyExtractor: Function[_ >: T, _ <: U], keyComparator: Comparator[_ >: U]): Comparator[T] = { - keyExtractor.getClass() // null check - keyComparator.getClass() // null check + requireNonNull(keyExtractor) + requireNonNull(keyComparator) new Comparator[T] with Serializable { def compare(o1: T, o2: T): Int = keyComparator.compare(keyExtractor(o1), keyExtractor(o2)) @@ -140,7 +141,7 @@ object Comparator { @inline def comparing[T, U <: Comparable[U]]( keyExtractor: Function[_ >: T, _ <: U]): Comparator[T] = { - keyExtractor.getClass() // null check + requireNonNull(keyExtractor) new Comparator[T] with Serializable { def compare(o1: T, o2: T): Int = keyExtractor(o1).compareTo(keyExtractor(o2)) @@ -149,7 +150,7 @@ object Comparator { @inline def comparingInt[T](keyExtractor: ToIntFunction[_ >: T]): Comparator[T] = { - keyExtractor.getClass() // null check + requireNonNull(keyExtractor) new Comparator[T] with Serializable { def compare(o1: T, o2: T): Int = Integer.compare(keyExtractor.applyAsInt(o1), keyExtractor.applyAsInt(o2)) @@ -158,7 +159,7 @@ object Comparator { @inline def comparingLong[T](keyExtractor: ToLongFunction[_ >: T]): Comparator[T] = { - keyExtractor.getClass() // null check + requireNonNull(keyExtractor) new Comparator[T] with Serializable { def compare(o1: T, o2: T): Int = java.lang.Long.compare(keyExtractor.applyAsLong(o1), keyExtractor.applyAsLong(o2)) @@ -167,7 +168,7 @@ object Comparator { @inline def comparingDouble[T](keyExtractor: ToDoubleFunction[_ >: T]): Comparator[T] = { - keyExtractor.getClass() // null check + requireNonNull(keyExtractor) new Comparator[T] with Serializable { def compare(o1: T, o2: T): Int = java.lang.Double.compare(keyExtractor.applyAsDouble(o1), keyExtractor.applyAsDouble(o2)) diff --git a/javalib/src/main/scala/java/util/NullRejectingHashMap.scala b/javalib/src/main/scala/java/util/NullRejectingHashMap.scala index d10c1fb326..3124aa3493 100644 --- a/javalib/src/main/scala/java/util/NullRejectingHashMap.scala +++ b/javalib/src/main/scala/java/util/NullRejectingHashMap.scala @@ -12,6 +12,8 @@ package java.util +import java.util.Objects.requireNonNull + /** A subclass of `HashMap` that systematically rejects `null` keys and values. * * This class is used as the implementation of some other hashtable-like data @@ -39,27 +41,17 @@ private[util] class NullRejectingHashMap[K, V]( new NullRejectingHashMap.Node(key, hash, value, previous, next) } - override def get(key: Any): V = { - if (key == null) - throw new NullPointerException() - super.get(key) - } + override def get(key: Any): V = + super.get(requireNonNull(key)) - override def containsKey(key: Any): Boolean = { - if (key == null) - throw new NullPointerException() - super.containsKey(key) - } + override def containsKey(key: Any): Boolean = + super.containsKey(requireNonNull(key)) - override def put(key: K, value: V): V = { - if (key == null || value == null) - throw new NullPointerException() - super.put(key, value) - } + override def put(key: K, value: V): V = + super.put(requireNonNull(key), requireNonNull(value)) override def putIfAbsent(key: K, value: V): V = { - if (value == null) - throw new NullPointerException() + requireNonNull(value) val old = get(key) // throws if `key` is null if (old == null) super.put(key, value) @@ -83,11 +75,8 @@ private[util] class NullRejectingHashMap[K, V]( impl(m) } - override def remove(key: Any): V = { - if (key == null) - throw new NullPointerException() - super.remove(key) - } + override def remove(key: Any): V = + super.remove(requireNonNull(key)) override def remove(key: Any, value: Any): Boolean = { val old = get(key) // throws if `key` is null @@ -100,8 +89,8 @@ private[util] class NullRejectingHashMap[K, V]( } override def replace(key: K, oldValue: V, newValue: V): Boolean = { - if (oldValue == null || newValue == null) - throw new NullPointerException() + requireNonNull(oldValue) + requireNonNull(newValue) val old = get(key) // throws if `key` is null if (oldValue.equals(old)) { // false if `old` is null super.put(key, newValue) @@ -112,19 +101,15 @@ private[util] class NullRejectingHashMap[K, V]( } override def replace(key: K, value: V): V = { - if (value == null) - throw new NullPointerException() + requireNonNull(value) val old = get(key) // throws if `key` is null if (old != null) super.put(key, value) old } - override def containsValue(value: Any): Boolean = { - if (value == null) - throw new NullPointerException() - super.containsValue(value) - } + override def containsValue(value: Any): Boolean = + super.containsValue(requireNonNull(value)) override def clone(): AnyRef = new NullRejectingHashMap[K, V](this) @@ -135,10 +120,7 @@ private object NullRejectingHashMap { previous: HashMap.Node[K, V], next: HashMap.Node[K, V]) extends HashMap.Node[K, V](key, hash, value, previous, next) { - override def setValue(v: V): V = { - if (v == null) - throw new NullPointerException() - super.setValue(v) - } + override def setValue(v: V): V = + super.setValue(requireNonNull(v)) } } diff --git a/javalib/src/main/scala/java/util/Optional.scala b/javalib/src/main/scala/java/util/Optional.scala index 7507c2f93a..4eb9d2009e 100644 --- a/javalib/src/main/scala/java/util/Optional.scala +++ b/javalib/src/main/scala/java/util/Optional.scala @@ -13,6 +13,7 @@ package java.util import java.util.function._ +import java.util.Objects.requireNonNull final class Optional[T] private (value: T) { import Optional._ @@ -95,12 +96,8 @@ final class Optional[T] private (value: T) { object Optional { def empty[T](): Optional[T] = new Optional[T](null.asInstanceOf[T]) - def of[T](value: T): Optional[T] = { - if (value == null) - throw new NullPointerException() - else - new Optional[T](value) - } + def of[T](value: T): Optional[T] = + new Optional[T](requireNonNull(value)) def ofNullable[T](value: T): Optional[T] = new Optional[T](value) diff --git a/javalib/src/main/scala/java/util/PriorityQueue.scala b/javalib/src/main/scala/java/util/PriorityQueue.scala index 859f81c5e2..e5b79f9787 100644 --- a/javalib/src/main/scala/java/util/PriorityQueue.scala +++ b/javalib/src/main/scala/java/util/PriorityQueue.scala @@ -18,6 +18,8 @@ import scala.annotation.tailrec import java.lang.Utils.roundUpToPowerOfTwo +import java.util.Objects.requireNonNull + import scala.scalajs.LinkingInfo class PriorityQueue[E] private ( @@ -87,9 +89,7 @@ class PriorityQueue[E] private ( private var inner: innerImpl.Repr[E] = innerImpl.make[E](initialCapacity) override def add(e: E): Boolean = { - if (e == null) - throw new NullPointerException() - val newInner = innerImpl.push(inner, e) + val newInner = innerImpl.push(inner, requireNonNull(e)) if (LinkingInfo.isWebAssembly) // opt: for JS we know it's always the same inner = newInner fixUp(innerImpl.length(inner) - 1) diff --git a/javalib/src/main/scala/java/util/Throwables.scala b/javalib/src/main/scala/java/util/Throwables.scala index 0af47f7859..c525a52f6d 100644 --- a/javalib/src/main/scala/java/util/Throwables.scala +++ b/javalib/src/main/scala/java/util/Throwables.scala @@ -12,6 +12,8 @@ package java.util +import java.util.Objects.requireNonNull + class ServiceConfigurationError(s: String, e: Throwable) extends Error(s, e) { def this(s: String) = this(s, null) } @@ -21,8 +23,7 @@ class ConcurrentModificationException(s: String) extends RuntimeException(s) { } class DuplicateFormatFlagsException(f: String) extends IllegalFormatException { - if (f == null) - throw new NullPointerException() + requireNonNull(f) def getFlags(): String = f override def getMessage(): String = "Flags = '" + f + "'" @@ -31,9 +32,7 @@ class DuplicateFormatFlagsException(f: String) extends IllegalFormatException { class EmptyStackException extends RuntimeException class FormatFlagsConversionMismatchException(f: String, c: Char) extends IllegalFormatException { - - if (f == null) - throw new NullPointerException() + requireNonNull(f) def getFlags(): String = f def getConversion(): Char = c @@ -48,9 +47,7 @@ class IllegalFormatCodePointException(c: Int) extends IllegalFormatException { } class IllegalFormatConversionException(c: Char, arg: Class[_]) extends IllegalFormatException { - - if (arg == null) - throw new NullPointerException() + requireNonNull(arg) def getConversion(): Char = c def getArgumentClass(): Class[_] = arg @@ -61,8 +58,7 @@ class IllegalFormatConversionException(c: Char, arg: Class[_]) extends IllegalFo class IllegalFormatException private[util] () extends IllegalArgumentException class IllegalFormatFlagsException(f: String) extends IllegalFormatException { - if (f == null) - throw new NullPointerException() + requireNonNull(f) def getFlags(): String = f override def getMessage(): String = "Flags = '" + f + "'" @@ -107,16 +103,14 @@ class InvalidPropertiesFormatException(s: String) extends java.io.IOException(s) } class MissingFormatArgumentException(s: String) extends IllegalFormatException { - if (s == null) - throw new NullPointerException() + requireNonNull(s) def getFormatSpecifier(): String = s override def getMessage(): String = "Format specifier '" + s + "'" } class MissingFormatWidthException(s: String) extends IllegalFormatException { - if (s == null) - throw new NullPointerException() + requireNonNull(s) def getFormatSpecifier(): String = s override def getMessage(): String = s @@ -139,17 +133,14 @@ class TooManyListenersException(s: String) extends Exception(s) { } class UnknownFormatConversionException(s: String) extends IllegalFormatException { - - if (s == null) - throw new NullPointerException() + requireNonNull(s) def getConversion(): String = s override def getMessage(): String = "Conversion = '" + s + "'" } class UnknownFormatFlagsException(f: String) extends IllegalFormatException { - if (f == null) - throw new NullPointerException() + requireNonNull(f) def getFlags(): String = f override def getMessage(): String = "Flags = " + f diff --git a/javalib/src/main/scala/java/util/TreeMap.scala b/javalib/src/main/scala/java/util/TreeMap.scala index 98b611a485..83721cbc81 100644 --- a/javalib/src/main/scala/java/util/TreeMap.scala +++ b/javalib/src/main/scala/java/util/TreeMap.scala @@ -13,6 +13,7 @@ package java.util import java.lang.Cloneable +import java.util.Objects.requireNonNull import java.util.{RedBlackTree => RB} import java.util.function.{Function, BiFunction} @@ -113,7 +114,7 @@ class TreeMap[K, V] private (tree: RB.Tree[K, V])( } override def merge(key: K, value: V, remappingFunction: BiFunction[_ >: V, _ >: V, _ <: V]): V = { - value.getClass() // null check + requireNonNull(value) val node = RB.getNode(tree, key) if (node eq null) { diff --git a/javalib/src/main/scala/java/util/concurrent/ConcurrentHashMap.scala b/javalib/src/main/scala/java/util/concurrent/ConcurrentHashMap.scala index 49c5ac683e..b4d6ad8f72 100644 --- a/javalib/src/main/scala/java/util/concurrent/ConcurrentHashMap.scala +++ b/javalib/src/main/scala/java/util/concurrent/ConcurrentHashMap.scala @@ -16,6 +16,7 @@ import java.util.function.{BiConsumer, Consumer} import java.io.Serializable import java.util._ +import java.util.Objects.requireNonNull class ConcurrentHashMap[K, V] private (initialCapacity: Int, loadFactor: Float) extends AbstractMap[K, V] with ConcurrentMap[K, V] with Serializable { @@ -68,11 +69,8 @@ class ConcurrentHashMap[K, V] private (initialCapacity: Int, loadFactor: Float) new ConcurrentHashMap.KeySetView[K, V](this.inner, null.asInstanceOf[V]) } - def keySet(mappedValue: V): ConcurrentHashMap.KeySetView[K, V] = { - if (mappedValue == null) - throw new NullPointerException() - new ConcurrentHashMap.KeySetView[K, V](this.inner, mappedValue) - } + def keySet(mappedValue: V): ConcurrentHashMap.KeySetView[K, V] = + new ConcurrentHashMap.KeySetView[K, V](this.inner, requireNonNull(mappedValue)) def forEach(parallelismThreshold: Long, action: BiConsumer[_ >: K, _ >: V]): Unit = { // Note: It is tempting to simply call inner.forEach here: diff --git a/javalib/src/main/scala/java/util/concurrent/ConcurrentLinkedQueue.scala b/javalib/src/main/scala/java/util/concurrent/ConcurrentLinkedQueue.scala index d24b4554c3..bffce67a18 100644 --- a/javalib/src/main/scala/java/util/concurrent/ConcurrentLinkedQueue.scala +++ b/javalib/src/main/scala/java/util/concurrent/ConcurrentLinkedQueue.scala @@ -13,6 +13,7 @@ package java.util.concurrent import java.util._ +import java.util.Objects.requireNonNull import java.util.ScalaOps._ class ConcurrentLinkedQueue[E]() extends AbstractQueue[E] with Queue[E] with Serializable { @@ -30,22 +31,18 @@ class ConcurrentLinkedQueue[E]() extends AbstractQueue[E] with Queue[E] with Ser private var _size: Double = 0 override def add(e: E): Boolean = { - if (e == null) { - throw new NullPointerException() - } else { - val oldLast = last + val oldLast = last - last = new Node(e) + last = new Node(requireNonNull(e)) - _size += 1 + _size += 1 - if (oldLast ne null) - oldLast.next = last - else - head = last + if (oldLast ne null) + oldLast.next = last + else + head = last - true - } + true } override def offer(e: E): Boolean = diff --git a/javalib/src/main/scala/java/util/concurrent/ConcurrentSkipListSet.scala b/javalib/src/main/scala/java/util/concurrent/ConcurrentSkipListSet.scala index cfdb3efedc..8b1ce5ee74 100644 --- a/javalib/src/main/scala/java/util/concurrent/ConcurrentSkipListSet.scala +++ b/javalib/src/main/scala/java/util/concurrent/ConcurrentSkipListSet.scala @@ -14,6 +14,7 @@ package java.util.concurrent import java.lang.Cloneable import java.util._ +import java.util.Objects.requireNonNull class ConcurrentSkipListSet[E] private (inner: TreeSet[E]) extends AbstractSet[E] with NavigableSet[E] with Cloneable with Serializable { @@ -44,12 +45,10 @@ class ConcurrentSkipListSet[E] private (inner: TreeSet[E]) else inner.contains(o) override def add(e: E): Boolean = - if (e == null) throw new NullPointerException() - else inner.add(e) + inner.add(requireNonNull(e)) override def remove(o: Any): Boolean = - if (o == null) throw new NullPointerException() - else inner.remove(o) + inner.remove(requireNonNull(o)) override def clear(): Unit = inner.clear() diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/math/BigDecimalConstructorsTest.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/math/BigDecimalConstructorsTest.scala index 87da7b2e75..23d11aa44d 100644 --- a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/math/BigDecimalConstructorsTest.scala +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/math/BigDecimalConstructorsTest.scala @@ -22,7 +22,7 @@ import java.math._ import org.junit.Test import org.junit.Assert._ -import org.scalajs.testsuite.utils.AssertThrows.assertThrows +import org.scalajs.testsuite.utils.AssertThrows.{assertThrows, _} class BigDecimalConstructorsTest { @@ -34,7 +34,7 @@ class BigDecimalConstructorsTest { val aNumber = new BigDecimal(bA) assertTrue(aNumber.unscaledValue() == bA) assertEquals(0, aNumber.scale()) - assertThrows(classOf[NullPointerException], new BigDecimal(null.asInstanceOf[BigInteger])) + assertThrowsNPEIfCompliant(new BigDecimal(null.asInstanceOf[BigInteger])) } @Test def testConstrBigIntegerMathContext(): Unit = { diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/math/MathContextTest.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/math/MathContextTest.scala index 9869117b10..7504e0bed1 100644 --- a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/math/MathContextTest.scala +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/math/MathContextTest.scala @@ -22,7 +22,7 @@ import java.math.{MathContext, RoundingMode} import org.junit.Test import org.junit.Assert._ -import org.scalajs.testsuite.utils.AssertThrows.assertThrows +import org.scalajs.testsuite.utils.AssertThrows.{assertThrows, _} class MathContextTest { @@ -65,7 +65,7 @@ class MathContextTest { assertThrows(classOf[IllegalArgumentException], new MathContext("precision=22roundingMode=UP")) assertThrows(classOf[IllegalArgumentException], new MathContext("")) - assertThrows(classOf[NullPointerException], new MathContext(null)) + assertThrowsNPEIfCompliant(new MathContext(null)) } @Test def testMathContextConstructorEquality(): Unit = { diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/OptionalTest.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/OptionalTest.scala index ec8cfb1582..8ec563586f 100644 --- a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/OptionalTest.scala +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/OptionalTest.scala @@ -18,14 +18,14 @@ import org.junit.Test import java.util.Optional import java.util.function._ -import org.scalajs.testsuite.utils.AssertThrows.assertThrows +import org.scalajs.testsuite.utils.AssertThrows.{assertThrows, _} class OptionalTest { @Test def testCreation(): Unit = { Optional.empty[String]() Optional.of[String]("") - assertThrows(classOf[NullPointerException], Optional.of[String](null)) + assertThrowsNPEIfCompliant(Optional.of[String](null)) Optional.ofNullable[String]("") Optional.ofNullable[String](null) } diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/concurrent/ConcurrentHashMapTest.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/concurrent/ConcurrentHashMapTest.scala index 74103e8b56..a35245a4e4 100644 --- a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/concurrent/ConcurrentHashMapTest.scala +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/concurrent/ConcurrentHashMapTest.scala @@ -22,7 +22,7 @@ import org.junit.Assert._ import org.junit.Test import org.scalajs.testsuite.javalib.util.MapTest -import org.scalajs.testsuite.utils.AssertThrows.assertThrows +import org.scalajs.testsuite.utils.AssertThrows.{assertThrows, _} class ConcurrentHashMapTest extends MapTest { @@ -197,7 +197,7 @@ class ConcurrentHashMapTest extends MapTest { @Test def keySetWithNullMappedValue(): Unit = { val map = factory.empty[String, String] - assertThrows(classOf[NullPointerException], map.keySet(null)) + assertThrowsNPEIfCompliant(classOf[NullPointerException], map.keySet(null)) } @Test def addOnKeySetView(): Unit = { From 93f7e0c2469dba0af2ce1c77a0d426a2f7600214 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Wed, 4 Mar 2026 12:15:28 +0100 Subject: [PATCH 27/32] Fix missing NPE check in `StringBuilder.replace`. Run NPE tests for String{Builder,Buffer} when they are compliant, instead of only when executing on the JVM. --- .../main/scala/java/lang/StringBuilder.scala | 3 +- .../javalib/lang/StringBufferTest.scala | 28 +++++-------------- .../javalib/lang/StringBuilderTest.scala | 27 +++++------------- 3 files changed, 16 insertions(+), 42 deletions(-) diff --git a/javalib/src/main/scala/java/lang/StringBuilder.scala b/javalib/src/main/scala/java/lang/StringBuilder.scala index d461dbb9d9..4da96eae40 100644 --- a/javalib/src/main/scala/java/lang/StringBuilder.scala +++ b/javalib/src/main/scala/java/lang/StringBuilder.scala @@ -90,11 +90,12 @@ class StringBuilder extends AnyRef with CharSequence with Appendable with java.i } def replace(start: Int, end: Int, str: String): StringBuilder = { + val strNonNull = requireNonNull(str) val oldContent = content val length = oldContent.length // The call to substring implies the bounds checks for 0 <= start <= length - val firstPart = oldContent.substring(0, start) + str + val firstPart = oldContent.substring(0, start) + strNonNull if (end < start) oldContent.charAt(-1) diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/lang/StringBufferTest.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/lang/StringBufferTest.scala index 565be213ac..5907004e48 100644 --- a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/lang/StringBufferTest.scala +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/lang/StringBufferTest.scala @@ -16,7 +16,6 @@ import org.junit.Test import org.junit.Assert._ import org.scalajs.testsuite.utils.AssertThrows.{assertThrows, _} -import org.scalajs.testsuite.utils.Platform.executingInJVM import WrappedStringCharSequence.charSequence @@ -43,19 +42,13 @@ class StringBufferTest { @Test def initString(): Unit = { assertEquals("hello", new StringBuffer("hello").toString()) - if (executingInJVM) { - assertThrows(classOf[NullPointerException], - new StringBuffer(null: String)) - } + assertThrowsNPEIfCompliant(new StringBuffer(null: String)) } @Test def initCharSequence(): Unit = { assertEquals("hello", new StringBuffer(charSequence("hello")).toString()) - if (executingInJVM) { - assertThrows(classOf[NullPointerException], - new StringBuffer(null: CharSequence)) - } + assertThrowsNPEIfCompliant(new StringBuffer(null: CharSequence)) } @Test def appendAnyRef(): Unit = { @@ -113,8 +106,7 @@ class StringBufferTest { assertEquals("hello", resultFor(Array('h', 'e', 'l', 'l', 'o'))) - if (executingInJVM) - assertThrows(classOf[NullPointerException], resultFor(null)) + assertThrowsNPEIfCompliant(resultFor(null)) } @Test def appendCharArrayOffsetLen(): Unit = { @@ -125,8 +117,7 @@ class StringBufferTest { assertEquals("hello", resultFor(arr, 0, 5)) assertEquals("ell", resultFor(arr, 1, 3)) - if (executingInJVM) - assertThrows(classOf[NullPointerException], resultFor(null, 0, 0)) + assertThrowsNPEIfCompliant(resultFor(null, 0, 0)) assertThrows(classOf[IndexOutOfBoundsException], resultFor(arr, -1, 2)) assertThrows(classOf[IndexOutOfBoundsException], resultFor(arr, 3, 3)) @@ -202,8 +193,7 @@ class StringBufferTest { assertThrowsStringIIOBEIfCompliant(resultFor("0123", 4, 3, "x")) assertThrowsStringIIOBEIfCompliant(resultFor("0123", 5, 8, "x")) - if (executingInJVM) - assertThrows(classOf[NullPointerException], resultFor("0123", 1, 3, null)) + assertThrowsNPEIfCompliant(resultFor("0123", 1, 3, null)) } @Test def insertCharArrayOffsetLen(): Unit = { @@ -224,10 +214,7 @@ class StringBufferTest { assertThrowsStringIIOBEIfCompliant(resultFor("1234", 1, arr, 1, -2)) assertThrowsStringIIOBEIfCompliant(resultFor("1234", 1, arr, 4, 3)) - if (executingInJVM) { - assertThrows(classOf[NullPointerException], - resultFor("1234", 1, null, 0, 0)) - } + assertThrowsNPEIfCompliant(resultFor("1234", 1, null, 0, 0)) } @Test def insertAnyRef(): Unit = { @@ -266,8 +253,7 @@ class StringBufferTest { assertThrowsStringIIOBEIfCompliant(resultFor("1234", -1, arr)) assertThrowsStringIIOBEIfCompliant(resultFor("1234", 6, arr)) - if (executingInJVM) - assertThrows(classOf[NullPointerException], resultFor("1234", 1, null)) + assertThrowsNPEIfCompliant(resultFor("1234", 1, null)) } @Test def insertCharSequence(): Unit = { diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/lang/StringBuilderTest.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/lang/StringBuilderTest.scala index b3b08d38f0..c3d502ffb8 100644 --- a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/lang/StringBuilderTest.scala +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/lang/StringBuilderTest.scala @@ -45,19 +45,13 @@ class StringBuilderTest { @Test def initString(): Unit = { assertEquals("hello", new StringBuilder("hello").toString()) - if (executingInJVM) { - assertThrows(classOf[NullPointerException], - new StringBuilder(null: String)) - } + assertThrowsNPEIfCompliant(new StringBuilder(null: String)) } @Test def initCharSequence(): Unit = { assertEquals("hello", new StringBuilder(charSequence("hello")).toString()) - if (executingInJVM) { - assertThrows(classOf[NullPointerException], - new StringBuilder(null: CharSequence)) - } + assertThrowsNPEIfCompliant(new StringBuilder(null: CharSequence)) } @Test def appendAnyRef(): Unit = { @@ -115,8 +109,7 @@ class StringBuilderTest { assertEquals("hello", resultFor(Array('h', 'e', 'l', 'l', 'o'))) - if (executingInJVM) - assertThrows(classOf[NullPointerException], resultFor(null)) + assertThrowsNPEIfCompliant(resultFor(null)) } @Test def appendCharArrayOffsetLen(): Unit = { @@ -127,8 +120,7 @@ class StringBuilderTest { assertEquals("hello", resultFor(arr, 0, 5)) assertEquals("ell", resultFor(arr, 1, 3)) - if (executingInJVM) - assertThrows(classOf[NullPointerException], resultFor(null, 0, 0)) + assertThrowsNPEIfCompliant(resultFor(null, 0, 0)) assertThrows(classOf[IndexOutOfBoundsException], resultFor(arr, -1, 2)) assertThrows(classOf[IndexOutOfBoundsException], resultFor(arr, 3, 3)) @@ -204,8 +196,7 @@ class StringBuilderTest { assertThrowsStringIIOBEIfCompliant(resultFor("0123", 4, 3, "x")) assertThrowsStringIIOBEIfCompliant(resultFor("0123", 5, 8, "x")) - if (executingInJVM) - assertThrows(classOf[NullPointerException], resultFor("0123", 1, 3, null)) + assertThrowsNPEIfCompliant(resultFor("0123", 1, 3, null)) } @Test def insertCharArrayOffsetLen(): Unit = { @@ -226,10 +217,7 @@ class StringBuilderTest { assertThrowsStringIIOBEIfCompliant(resultFor("1234", 1, arr, 1, -2)) assertThrowsStringIIOBEIfCompliant(resultFor("1234", 1, arr, 4, 3)) - if (executingInJVM) { - assertThrows(classOf[NullPointerException], - resultFor("1234", 1, null, 0, 0)) - } + assertThrowsNPEIfCompliant(resultFor("1234", 1, null, 0, 0)) } @Test def insertAnyRef(): Unit = { @@ -268,8 +256,7 @@ class StringBuilderTest { assertThrowsStringIIOBEIfCompliant(resultFor("1234", -1, arr)) assertThrowsStringIIOBEIfCompliant(resultFor("1234", 6, arr)) - if (executingInJVM) - assertThrows(classOf[NullPointerException], resultFor("1234", 1, null)) + assertThrowsNPEIfCompliant(resultFor("1234", 1, null)) } @Test def insertCharSequence(): Unit = { From 3ca180405b18edb9b274f9a6601bf9093cc511d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Sun, 29 Mar 2026 17:28:43 +0200 Subject: [PATCH 28/32] Version 1.21.0. --- ir/shared/src/main/scala/org/scalajs/ir/ScalaJSVersions.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ir/shared/src/main/scala/org/scalajs/ir/ScalaJSVersions.scala b/ir/shared/src/main/scala/org/scalajs/ir/ScalaJSVersions.scala index 330ee0936b..50b74e7184 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/ScalaJSVersions.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/ScalaJSVersions.scala @@ -17,8 +17,8 @@ import java.util.concurrent.ConcurrentHashMap import scala.util.matching.Regex object ScalaJSVersions extends VersionChecks( - current = "1.21.0-SNAPSHOT", - binaryEmitted = "1.21-SNAPSHOT" + current = "1.21.0", + binaryEmitted = "1.21" ) /** Helper class to allow for testing of logic. */ From c4e7f43932551aabb573c925147e3841ac3ca4be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Mon, 30 Mar 2026 11:43:48 +0200 Subject: [PATCH 29/32] Towards 1.21.1. --- .../main/scala/org/scalajs/ir/ScalaJSVersions.scala | 2 +- project/BinaryIncompatibilities.scala | 11 ----------- project/Build.scala | 2 +- 3 files changed, 2 insertions(+), 13 deletions(-) diff --git a/ir/shared/src/main/scala/org/scalajs/ir/ScalaJSVersions.scala b/ir/shared/src/main/scala/org/scalajs/ir/ScalaJSVersions.scala index 50b74e7184..4dd1aaa5fb 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/ScalaJSVersions.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/ScalaJSVersions.scala @@ -17,7 +17,7 @@ import java.util.concurrent.ConcurrentHashMap import scala.util.matching.Regex object ScalaJSVersions extends VersionChecks( - current = "1.21.0", + current = "1.21.1-SNAPSHOT", binaryEmitted = "1.21" ) diff --git a/project/BinaryIncompatibilities.scala b/project/BinaryIncompatibilities.scala index 2148407f79..4713fe6bf8 100644 --- a/project/BinaryIncompatibilities.scala +++ b/project/BinaryIncompatibilities.scala @@ -5,17 +5,6 @@ import com.typesafe.tools.mima.core.ProblemFilters._ object BinaryIncompatibilities { val IR = Seq( - // !!! Breaking, OK in minor release - - ProblemFilters.exclude[DirectMissingMethodProblem]("org.scalajs.ir.Types#ClassType.this"), - ProblemFilters.exclude[DirectMissingMethodProblem]("org.scalajs.ir.Types#ClassType.apply"), - ProblemFilters.exclude[DirectMissingMethodProblem]("org.scalajs.ir.Types#ClassType.copy"), - ProblemFilters.exclude[DirectMissingMethodProblem]("org.scalajs.ir.Types#ArrayType.this"), - ProblemFilters.exclude[DirectMissingMethodProblem]("org.scalajs.ir.Types#ArrayType.apply"), - ProblemFilters.exclude[DirectMissingMethodProblem]("org.scalajs.ir.Types#ArrayType.copy"), - - ProblemFilters.exclude[MissingTypesProblem]("org.scalajs.ir.Types$ClassType$"), - ProblemFilters.exclude[MissingTypesProblem]("org.scalajs.ir.Types$ArrayType$"), ) val Linker = Seq( diff --git a/project/Build.scala b/project/Build.scala index 9b2398beb4..b6ee901534 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -386,7 +386,7 @@ object Build { "1.3.0", "1.3.1", "1.4.0", "1.5.0", "1.5.1", "1.6.0", "1.7.0", "1.7.1", "1.8.0", "1.9.0", "1.10.0", "1.10.1", "1.11.0", "1.12.0", "1.13.0", "1.13.1", "1.13.2", "1.14.0", "1.15.0", "1.16.0", "1.17.0", "1.18.0", - "1.18.1", "1.18.2", "1.19.0", "1.20.0", "1.20.1", "1.20.2") + "1.18.1", "1.18.2", "1.19.0", "1.20.0", "1.20.1", "1.20.2", "1.21.0") val previousVersion = previousVersions.last val previousBinaryCrossVersion = CrossVersion.binaryWith("sjs1_", "") From 5f8a08367e471696f2b7c022dbc746e9fc781ed5 Mon Sep 17 00:00:00 2001 From: Rikito Taniguchi Date: Thu, 2 Apr 2026 21:57:57 +0900 Subject: [PATCH 30/32] Refactor: retrieve WIT type information from symbols in `exitingPhase(typerPhase)` Remove `WitFunctionType` and `WitVariantValueTypes` maps from `JSGlobalAddons` that were stored during `PrepJSInterop`. Instead, query the compiler's symbol table directly using `exitingPhase(currentRun.typerPhase)` during JS code generation. This eliminates the store/retrieve pattern across compiler phases and simplifies WitExportInfo by removing its signature field. --- .../org/scalajs/nscplugin/GenJSCode.scala | 2 +- .../org/scalajs/nscplugin/GenWitInterop.scala | 89 +++++++++++++------ .../scalajs/nscplugin/JSGlobalAddons.scala | 25 +----- .../org/scalajs/nscplugin/PrepJSExports.scala | 6 +- .../org/scalajs/nscplugin/PrepJSInterop.scala | 29 ------ 5 files changed, 63 insertions(+), 88 deletions(-) diff --git a/compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala b/compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala index 6bff4abbad..80f4d99466 100644 --- a/compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala +++ b/compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala @@ -696,7 +696,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) val info = jsInterop.witExportOf(dd.symbol).get for (method <- genMethod(dd)) { methodsBuilder += method - witExportDefsBuilder += genWitExportDef(info, method) + witExportDefsBuilder += genWitExportDef(info, dd.symbol, method) } } else { methodsBuilder ++= genMethod(dd) diff --git a/compiler/src/main/scala/org/scalajs/nscplugin/GenWitInterop.scala b/compiler/src/main/scala/org/scalajs/nscplugin/GenWitInterop.scala index 0813438392..2646eac894 100644 --- a/compiler/src/main/scala/org/scalajs/nscplugin/GenWitInterop.scala +++ b/compiler/src/main/scala/org/scalajs/nscplugin/GenWitInterop.scala @@ -134,8 +134,8 @@ trait GenWitInterop[G <: Global with Singleton] extends SubComponent { implicit val pos = tree.pos val sym = tree.symbol withNewLocalNameScope { - val funcType = jsInterop.witFunctionTypeOf(sym) - val baseParams = funcType.params.map(toWIT(_)) + val (paramTypes, resultType) = witMethodSignatureOf(sym) + val baseParams = paramTypes.map(toWIT(_)) val params = name match { case _:js.WitFunctionName.Function | _:js.WitFunctionName.ResourceConstructor | @@ -146,7 +146,7 @@ trait GenWitInterop[G <: Global with Singleton] extends SubComponent { } val witFuncType = wit.FuncType( params, - toResultWIT(funcType.resultType) + toResultWIT(resultType) ) js.WitNativeMemberDef(flags, moduleName, name, encodeMethodSym(sym), witFuncType) @@ -158,8 +158,7 @@ trait GenWitInterop[G <: Global with Singleton] extends SubComponent { val sym = tree.symbol val flags = js.MemberFlags.empty.withNamespace(js.MemberNamespace.PublicStatic) - val funcType = jsInterop.witFunctionTypeOf(sym) - + val (paramTypes, resultType) = witMethodSignatureOf(sym) for { methodAnnot <- sym.getAnnotation(WitResourceStaticMethodAnnotation) resourceAnnot <- sym.owner.companionClass.getAnnotation(WitResourceImportAnnotation) @@ -170,8 +169,8 @@ trait GenWitInterop[G <: Global with Singleton] extends SubComponent { val name = js.WitFunctionName.ResourceStaticMethod( func = methodName, resource = resourceName) withNewLocalNameScope { - val params = funcType.params.map(p => toWIT(p)) - val ft = wit.FuncType(params, toResultWIT(funcType.resultType)) + val params = paramTypes.map(toWIT(_)) + val ft = wit.FuncType(params, toResultWIT(resultType)) js.WitNativeMemberDef(flags, moduleName, name, encodeMethodSym(sym), ft) } } @@ -182,7 +181,7 @@ trait GenWitInterop[G <: Global with Singleton] extends SubComponent { val sym = tree.symbol val flags = js.MemberFlags.empty.withNamespace(js.MemberNamespace.PublicStatic) - val funcType = jsInterop.witFunctionTypeOf(sym) + val (paramTypes, resultType) = witMethodSignatureOf(sym) for { methodAnnot <- sym.getAnnotation(WitResourceConstructorAnnotation) @@ -192,19 +191,20 @@ trait GenWitInterop[G <: Global with Singleton] extends SubComponent { } yield { val name = js.WitFunctionName.ResourceConstructor(resourceName) withNewLocalNameScope { - val params = funcType.params.map(p => toWIT(p)) - val ft = wit.FuncType(params, toResultWIT(funcType.resultType)) + val params = paramTypes.map(toWIT(_)) + val ft = wit.FuncType(params, toResultWIT(resultType)) js.WitNativeMemberDef(flags, moduleName, name, encodeMethodSym(sym), ft) } } } - def genWitExportDef(info: jsInterop.WitExportInfo, + def genWitExportDef(info: jsInterop.WitExportInfo, sym: Symbol, methodDef: js.MethodDef): js.WitExportDef = { withNewLocalNameScope { + val (paramTypes, resultType) = witMethodSignatureOf(sym) val signature = wit.FuncType( - info.signature.params.map(toWIT(_)), - toResultWIT(info.signature.resultType) + paramTypes.map(toWIT(_)), + toResultWIT(resultType) ) js.WitExportDef( info.moduleName, @@ -215,15 +215,46 @@ trait GenWitInterop[G <: Global with Singleton] extends SubComponent { } } + private def witMethodSignatureOf(sym: Symbol): (List[Type], Type) = { + exitingPhase(currentRun.typerPhase) { + val methodType = sym.tpe + val params = + if (methodType.paramss.isEmpty) Nil + else methodType.paramss.head.map(_.tpe) + (params, methodType.resultType) + } + } + + private def witVariantValueTypeOf(sym: Symbol): Type = { + exitingPhase(currentRun.typerPhase) { + if (sym.isModuleClass) { + UnitTpe + } else if (sym.isClass && sym.isFinal && !sym.isTrait) { + sym.primaryConstructor.paramss.flatten match { + case Nil => + UnitTpe + case param :: Nil => + param.tpe + case _ => + throw new AssertionError(s"Invalid WIT variant case shape for $sym") + } + } else { + throw new AssertionError(s"Invalid WIT variant case symbol $sym") + } + } + } + private def toWIT(tpe: Type): wit.ValType = { - unsigned2WIT.get(tpe.typeSymbolDirect).orElse { - toWITMaybeArray(tpe.dealiasWiden) + val widenedTpe = exitingPhase(currentRun.typerPhase)(tpe.dealiasWiden) + + unsigned2WIT.get(widenedTpe.typeSymbolDirect).orElse { + toWITMaybeArray(widenedTpe) }.orElse { - primitiveIRWIT.get(toIRType(tpe.dealiasWiden)) + primitiveIRWIT.get(toIRType(widenedTpe)) }.getOrElse { - tpe.dealiasWiden.typeSymbol match { + widenedTpe.typeSymbol match { case tsym if isWasmComponentTupleClass(tsym) => - wit.TupleType(tpe.typeArgs.map(toWIT(_))) + wit.TupleType(widenedTpe.baseType(tsym).typeArgs.map(toWIT(_))) case tsym if tsym.hasAnnotation(WitFlagsAnnotation) => // Read numFlags from annotation parameter @@ -236,26 +267,26 @@ trait GenWitInterop[G <: Global with Singleton] extends SubComponent { wit.FlagsType(className, numFlags) case tsym if isWasmWitRecordClass(tsym) => - // TODO: it needs to be sorted by the order of record in wit definition val className = encodeClassName(tsym) - val fields: List[wit.FieldType] = tsym.info.decls.collect { - case f if f.isField => - val label = encodeFieldSym(f)(f.pos).name - val fieldType = jsInterop.witVariantValueTypeOf(f) - val valueType = toWIT(fieldType) - wit.FieldType(label, valueType) - }.toList + val fields = exitingPhase(currentRun.typerPhase) { + tsym.primaryConstructor.paramss.flatten.map { param => + (param.name.dropLocal.toString(), param.tpe) + } + }.map { case (fieldName, fieldType) => + val label = Names.FieldName(className, Names.SimpleFieldName(fieldName)) + wit.FieldType(label, toWIT(fieldType)) + } wit.RecordType(className, fields) case tsym if isWasmWitResourceType(tsym) => wit.ResourceType(encodeClassName(tsym)) case tsym if tsym.isSubClass(ComponentResultClass) && tsym.isSealed => - val List(ok, err) = tpe.typeArgs + val List(ok, err) = widenedTpe.baseType(ComponentResultClass).typeArgs wit.ResultType(toResultWIT(ok), toResultWIT(err)) case tsym if tsym.fullName == "java.util.Optional" => - val List(t) = tpe.dealiasWiden.typeArgs + val List(t) = widenedTpe.baseType(tsym).typeArgs wit.OptionType(toWIT(t)) case tsym if tsym.hasAnnotation(WitVariantAnnotation) && tsym.isSealed => @@ -266,7 +297,7 @@ trait GenWitInterop[G <: Global with Singleton] extends SubComponent { val cases = tsym.sealedChildren.toList.sortBy(_.pos.line) map { child => // assert(child.isFinal) // assert(child.isClass) - val valueType = jsInterop.witVariantValueTypeOf(child) + val valueType = witVariantValueTypeOf(child) val caseTyp = if (toIRType(valueType) == jstpe.VoidType) { None } else { diff --git a/compiler/src/main/scala/org/scalajs/nscplugin/JSGlobalAddons.scala b/compiler/src/main/scala/org/scalajs/nscplugin/JSGlobalAddons.scala index 7d1fa3b288..db8af0797e 100644 --- a/compiler/src/main/scala/org/scalajs/nscplugin/JSGlobalAddons.scala +++ b/compiler/src/main/scala/org/scalajs/nscplugin/JSGlobalAddons.scala @@ -103,12 +103,6 @@ trait JSGlobalAddons extends JSDefinitions with CompatComponent { private val jsNativeLoadSpecs = mutable.Map.empty[Symbol, JSNativeLoadSpec] - private val componentFunctionTypes = - mutable.Map.empty[Symbol, WitFunctionType] - - private val WitVariantValueTypes = - mutable.Map.empty[Symbol, Type] - private val exportPrefix = "$js$exported$" private val methodExportPrefix = exportPrefix + "meth$" private val propExportPrefix = exportPrefix + "prop$" @@ -125,16 +119,10 @@ trait JSGlobalAddons extends JSDefinitions with CompatComponent { val pos: Position) extends ExportInfo - case class WitExportInfo(moduleName: String, name: String, - signature: WitFunctionType)( + case class WitExportInfo(moduleName: String, name: String)( val pos: Position) extends ExportInfo - case class WitFunctionType( - params: List[Type], - resultType: Type - ) - case class StaticExportInfo(jsName: String)(val pos: Position) extends ExportInfo sealed abstract class JSName { @@ -420,17 +408,6 @@ trait JSGlobalAddons extends JSDefinitions with CompatComponent { def jsNativeLoadSpecOfOption(sym: Symbol): Option[JSNativeLoadSpec] = jsNativeLoadSpecs.get(sym) - def storeWitVariantValueType(sym: Symbol, valueType: Type): Unit = - WitVariantValueTypes(sym) = valueType - - def witVariantValueTypeOf(sym: Symbol): Type = - WitVariantValueTypes(sym) - - def storeWitFunctionType(sym: Symbol, funcType: WitFunctionType): Unit = - componentFunctionTypes(sym) = funcType - - def witFunctionTypeOf(sym: Symbol): WitFunctionType = - componentFunctionTypes(sym) } } diff --git a/compiler/src/main/scala/org/scalajs/nscplugin/PrepJSExports.scala b/compiler/src/main/scala/org/scalajs/nscplugin/PrepJSExports.scala index eb735c5068..a6a86d271c 100644 --- a/compiler/src/main/scala/org/scalajs/nscplugin/PrepJSExports.scala +++ b/compiler/src/main/scala/org/scalajs/nscplugin/PrepJSExports.scala @@ -140,11 +140,7 @@ trait PrepJSExports[G <: Global with Singleton] { this: PrepJSInterop[G] => val wasmComponent = exports.collect { case info @ ExportInfo(moduleName, ExportDestination.WasmComponent(name)) => - val signature = jsInterop.WitFunctionType( - (if (sym.tpe.paramss.isEmpty) Nil else sym.tpe.paramss.head).map(_.tpe), - sym.tpe.resultType - ) - jsInterop.WitExportInfo(moduleName, name, signature)(info.pos) + jsInterop.WitExportInfo(moduleName, name)(info.pos) } if (sym.isMethod && wasmComponent.nonEmpty) { diff --git a/compiler/src/main/scala/org/scalajs/nscplugin/PrepJSInterop.scala b/compiler/src/main/scala/org/scalajs/nscplugin/PrepJSInterop.scala index 91a89c8f11..1a46b3a136 100644 --- a/compiler/src/main/scala/org/scalajs/nscplugin/PrepJSInterop.scala +++ b/compiler/src/main/scala/org/scalajs/nscplugin/PrepJSInterop.scala @@ -862,12 +862,6 @@ abstract class PrepJSInterop[G <: Global with Singleton](val global: G) reporter.error(pos, s"Return type '${returnType}' is not compatible with Component Model") } - - val funcType = jsInterop.WitFunctionType( - (if (sym.tpe.paramss.isEmpty) Nil else sym.tpe.paramss.head).map(_.tpe), - sym.tpe.resultType - ) - jsInterop.storeWitFunctionType(sym, funcType) } } } @@ -953,12 +947,6 @@ abstract class PrepJSInterop[G <: Global with Singleton](val global: G) overridden.fullName) } } - - val funcType = jsInterop.WitFunctionType( - (if (member.tpe.paramss.isEmpty) Nil else member.tpe.paramss.head).map(_.tpe), - member.tpe.resultType - ) - jsInterop.storeWitFunctionType(member, funcType) } } @@ -991,12 +979,6 @@ abstract class PrepJSInterop[G <: Global with Singleton](val global: G) s"Public method '${member.name}' in companion object of @WitResourceImport trait must be " + "annotated with @WitResourceConstructor or @WitResourceStaticMethod") } - - val funcType = jsInterop.WitFunctionType( - (if (member.tpe.paramss.isEmpty) Nil else member.tpe.paramss.head).map(_.tpe), - member.tpe.resultType - ) - jsInterop.storeWitFunctionType(member, funcType) } } } @@ -1024,7 +1006,6 @@ abstract class PrepJSInterop[G <: Global with Singleton](val global: G) private def validateWitVariantCase(caseSym: Symbol): Unit = { if (caseSym.isModuleClass) { // Regular object (enum case without payload) - jsInterop.storeWitVariantValueType(caseSym, definitions.UnitTpe) } else if (caseSym.isClass && caseSym.isFinal && !caseSym.isTrait) { // Final class (variant case with payload) val primaryCtor = caseSym.primaryConstructor @@ -1051,12 +1032,9 @@ abstract class PrepJSInterop[G <: Global with Singleton](val global: G) } else if (!isComponentModelCompatible(fieldType)) { reporter.error(param.pos, s"Field '${param.name}' has type '${fieldType}' which is not compatible with Component Model. ") - } else { - jsInterop.storeWitVariantValueType(caseSym, fieldType) } } else { // Zero parameters - enum case defined as a class - jsInterop.storeWitVariantValueType(caseSym, definitions.UnitTpe) } } else { reporter.error(caseSym.pos, @@ -1121,13 +1099,6 @@ abstract class PrepJSInterop[G <: Global with Singleton](val global: G) s"Field '${param.name}' has type '${fieldType}' which is not compatible with Component Model") } } - // Store field types for code generation - for { - f <- sym.info.decls - if !f.isMethod && f.isField - } { - jsInterop.storeWitVariantValueType(f, f.tpe) - } } } From aa2022d74aeb6318d8a16f0d1f91304b919b8baa Mon Sep 17 00:00:00 2001 From: Rikito Taniguchi Date: Fri, 3 Apr 2026 18:06:48 +0900 Subject: [PATCH 31/32] Amend ByteBuffer.allocateDirect to take the no-JS code path for pure Wasm --- .../src/main/scala/java/nio/ByteBuffer.scala | 24 ++++++++++++------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/javalib/src/main/scala/java/nio/ByteBuffer.scala b/javalib/src/main/scala/java/nio/ByteBuffer.scala index d1aaf7c302..87beac2354 100644 --- a/javalib/src/main/scala/java/nio/ByteBuffer.scala +++ b/javalib/src/main/scala/java/nio/ByteBuffer.scala @@ -19,6 +19,8 @@ import scala.scalajs.js.typedarray._ import scala.scalajs.LinkingInfo import scala.scalajs.LinkingInfo.ESVersion +import scala.scalajs.LinkingInfo.moduleKind +import scala.scalajs.LinkingInfo.ModuleKind.{MinimalWasmModule, WasmComponent} object ByteBuffer { private final val HashSeed = -547316498 // "java.nio.ByteBuffer".## @@ -31,16 +33,20 @@ object ByteBuffer { def allocateDirect(capacity: Int): ByteBuffer = { GenBuffer.validateAllocateCapacity(capacity) - if (LinkingInfo.esVersion >= ESVersion.ES2015 || - js.typeOf(js.Dynamic.global.Int8Array) != "undefined") { - TypedArrayByteBuffer.allocate(capacity) - } else { - /* Create a direct ByteBuffer that is actually backed by a regular Array. - * We can do this because the JavaDoc explicitly leaves it unspecified - * whether direct buffers are actually backed by an array or not. - * They only need to return `true` from `isDirect()`. - */ + LinkingInfo.linkTimeIf(moduleKind == MinimalWasmModule || moduleKind == WasmComponent) { HeapByteBuffer.allocateDirect(capacity) + } { + if (LinkingInfo.esVersion >= ESVersion.ES2015 || + js.typeOf(js.Dynamic.global.Int8Array) != "undefined") { + TypedArrayByteBuffer.allocate(capacity) + } else { + /* Create a direct ByteBuffer that is actually backed by a regular Array. + * We can do this because the JavaDoc explicitly leaves it unspecified + * whether direct buffers are actually backed by an array or not. + * They only need to return `true` from `isDirect()`. + */ + HeapByteBuffer.allocateDirect(capacity) + } } } From 2f58550d98c6852e41c743aa6ff7af441632fb54 Mon Sep 17 00:00:00 2001 From: lostflydev Date: Mon, 6 Apr 2026 01:11:44 +0500 Subject: [PATCH 32/32] Implement URI for pure Wasm (#151) Use RegExpImpl to abstract over js.RegExp / java.util.regex.Pattern: - URI parsing: restore _fld pattern using RegExpImpl.impl.exec/matches/ exists/getOrElse; remove parseURI helper - IPv6 detection: ipv6Re compiled via RegExpImpl.impl.compile; inline the test into uriStr - uriRe: single val with RegExpImpl.impl.compile at end of block - Quoting (5 patterns): use RegExpImpl.impl.replaceAll with java.util.function.Function; QuoteStrMapper object in object URI - Path normalization: String#split + ju.Arrays.asList/subList/ scalaOps.mkString RegExpImpl additions: - Flags object (Global/CaseInsensitive as Int) replaces String flags - compile(patternStr, flags: Int) overload - replaceAll(pattern, string, Function[String,String]) with jsReplace in JSRegExpImpl and Matcher.replaceAll in JavaRegExpImpl Unblock URITest in pure Wasm test suite. Add tests for previously untested code paths: multi-component constructors, IPv6 host, quoteIllegal, normalize edge cases --- javalib/src/main/scala/java/net/URI.scala | 142 +++++++++--------- .../scala/java/util/regex/RegExpImpl.scala | 52 +++++++ project/Build.scala | 4 +- .../testsuite/javalib/net/URITest.scala | 51 +++++++ 4 files changed, 172 insertions(+), 77 deletions(-) diff --git a/javalib/src/main/scala/java/net/URI.scala b/javalib/src/main/scala/java/net/URI.scala index f3f40f594c..abca333f56 100644 --- a/javalib/src/main/scala/java/net/URI.scala +++ b/javalib/src/main/scala/java/net/URI.scala @@ -12,14 +12,13 @@ package java.net -import scala.scalajs.js.RegExp -import scala.scalajs.js - import scala.annotation.tailrec -import java.lang.Utils._ import java.nio._ import java.nio.charset.{CodingErrorAction, StandardCharsets} +import java.{util => ju} +import java.util.ScalaOps._ +import java.util.regex.RegExpImpl final class URI(origStr: String) extends Serializable with Comparable[URI] { @@ -32,14 +31,14 @@ final class URI(origStr: String) extends Serializable with Comparable[URI] { * This is a local val for the primary constructor. It is a val, * since we'll set it to null after initializing all fields. */ - private[this] var _fld: RegExp.ExecResult = URI.uriRe.exec(origStr) - if (_fld == null) + private[this] var _fld = RegExpImpl.impl.exec(URI.uriRe, origStr) + if (!RegExpImpl.impl.matches(_fld)) throw new URISyntaxException(origStr, "Malformed URI") - private val _isAbsolute = undefOrIsDefined(_fld(AbsScheme)) - private val _isOpaque = undefOrIsDefined(_fld(AbsOpaquePart)) + private val _isAbsolute = RegExpImpl.impl.exists(_fld, AbsScheme) + private val _isOpaque = RegExpImpl.impl.exists(_fld, AbsOpaquePart) - @inline private def fld(idx: Int): String = undefOrGetOrNull(_fld(idx)) + @inline private def fld(idx: Int): String = RegExpImpl.impl.getOrElse(_fld, idx, null) @inline private def fld(absIdx: Int, relIdx: Int): String = if (_isAbsolute) fld(absIdx) else fld(relIdx) @@ -93,7 +92,7 @@ final class URI(origStr: String) extends Serializable with Comparable[URI] { private val _fragment = fld(Fragment) // End of default ctor. Unset helper field - _fld = null + _fld = null.asInstanceOf[RegExpImpl.impl.Repr] def this(scheme: String, ssp: String, fragment: String) = this(URI.uriStr(scheme, ssp, fragment)) @@ -217,11 +216,9 @@ final class URI(origStr: String) extends Serializable with Comparable[URI] { def normalize(): URI = if (_isOpaque || _path == null) this else { - import js.JSStringOps._ - val origPath = _path - val segments = origPath.jsSplit("/") + val segments = origPath.split("/", -1) // Step 1: Remove all "." segments // Step 2: Remove ".." segments preceded by non ".." segment until no @@ -279,17 +276,13 @@ final class URI(origStr: String) extends Serializable with Comparable[URI] { } } - // Truncate `segments` at `outIdx` - segments.length = outIdx - // Step 3: If path is relative and first segment contains ":", prepend "." // segment (according to JavaDoc). If the path is absolute, the first // segment is "" so the `contains(':')` returns false. - if (outIdx != 0 && segments(0).contains(":")) - segments.unshift(".") - - // Now add all the segments from step 1, 2 and 3 - val newPath = segments.join("/") + val prependDot = outIdx != 0 && segments(0).contains(":") + val normalized = + ju.Arrays.asList(segments).subList(0, outIdx).scalaOps.mkString("", "/", "") + val newPath = if (prependDot) "./" + normalized else normalized // Only create new instance if anything changed if (newPath == origPath) @@ -437,7 +430,8 @@ object URI { // (25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]) # 2001:db8:3:4::192.0.2.33 64:ff9b::192.0.2.33 (IPv4-Embedded IPv6 Address) } - private val ipv6Re = new RegExp("^" + ipv6address + "$", "i") + private val ipv6Re = + RegExpImpl.impl.compile("^" + ipv6address + "$", RegExpImpl.Flags.CaseInsensitive) // URI syntax parser. Based on RFC2396, RFC2732 and adaptations according to // JavaDoc. @@ -586,7 +580,7 @@ object URI { // URI-reference = [ absoluteURI | relativeURI ] [ "#" fragment ] val uriRef = "^(?:" + absoluteURI + "|" + relativeURI + ")(?:#" + fragment + ")?$" - new RegExp(uriRef, "i") + RegExpImpl.impl.compile(uriRef, RegExpImpl.Flags.CaseInsensitive) } private object Fields { @@ -643,7 +637,7 @@ object URI { resStr += quoteUserInfo(userInfo) + "@" if (host != null) { - if (URI.ipv6Re.test(host)) + if (RegExpImpl.impl.matches(RegExpImpl.impl.exec(URI.ipv6Re, host))) resStr += "[" + host + "]" else resStr += host @@ -753,7 +747,8 @@ object URI { } } - private val quoteStr: js.Function1[String, String] = { (str: String) => + /** Encode a matched string as percent-encoded UTF-8 bytes. */ + private def quoteStrFn(str: String): String = { val buf = StandardCharsets.UTF_8.encode(str) var res = "" @@ -765,39 +760,41 @@ object URI { res } + private object QuoteStrMapper extends ju.function.Function[String, String] { + def apply(t: String): String = quoteStrFn(t) + } + /** matches any character not in unreserved, punct, escaped or other */ - private val userInfoQuoteRe = new RegExp( - // !other = [\u0000-\u00a0\u1680\u2000-\u200a\u202f\u205f\u3000\u2028\u2029] - // Char class is: [:!other:^a-z0-9-_.!~*'(),;:$&+=%] - "[\u0000- \"#/<>?@\\[-\\^`{-}" + - "\u007f-\u00a0\u1680\u2000-\u200a\u202f\u205f\u3000\u2028\u2029]|" + - "%(?![0-9a-f]{2})", - "ig") + private val userInfoQuoteRe = RegExpImpl.impl.compile( + // !other = [\u0000-\u00a0\u1680\u2000-\u200a\u202f\u205f\u3000\u2028\u2029] + // Char class is: [:!other:^a-z0-9-_.!~*'(),;:$&+=%] + "[\u0000- \"#/<>?@\\[-\\^`{-}" + + "\u007f-\u00a0\u1680\u2000-\u200a\u202f\u205f\u3000\u2028\u2029]|" + + "%(?![0-9a-f]{2})", + RegExpImpl.Flags.Global | RegExpImpl.Flags.CaseInsensitive + ) /** Quote any character not in unreserved, punct, escaped or other */ - private def quoteUserInfo(str: String) = { - import js.JSStringOps._ - str.jsReplace(userInfoQuoteRe, quoteStr) - } + private def quoteUserInfo(str: String): String = + RegExpImpl.impl.replaceAll(userInfoQuoteRe, str, QuoteStrMapper) /** matches any character not in unreserved, punct, escaped, other or equal * to '/' or '@' */ - private val pathQuoteRe = new RegExp( - // !other = [\u0000-\u00a0\u1680\u2000-\u200a\u202f\u205f\u3000\u2028\u2029] - // Char class is: [:!other:^a-z0-9-_.!~*'(),;:$&+=%@/] - "[\u0000- \"#<>?\\[-\\^`{-}" + - "\u007f-\u00a0\u1680\u2000-\u200a\u202f\u205f\u3000\u2028\u2029]|" + - "%(?![0-9a-f]{2})", - "ig") + private val pathQuoteRe = RegExpImpl.impl.compile( + // !other = [\u0000-\u00a0\u1680\u2000-\u200a\u202f\u205f\u3000\u2028\u2029] + // Char class is: [:!other:^a-z0-9-_.!~*'(),;:$&+=%@/] + "[\u0000- \"#<>?\\[-\\^`{-}" + + "\u007f-\u00a0\u1680\u2000-\u200a\u202f\u205f\u3000\u2028\u2029]|" + + "%(?![0-9a-f]{2})", + RegExpImpl.Flags.Global | RegExpImpl.Flags.CaseInsensitive + ) /** Quote any character not in unreserved, punct, escaped, other or equal * to '/' or '@' */ - private def quotePath(str: String) = { - import js.JSStringOps._ - str.jsReplace(pathQuoteRe, quoteStr) - } + private def quotePath(str: String): String = + RegExpImpl.impl.replaceAll(pathQuoteRe, str, QuoteStrMapper) /** matches any character not in unreserved, punct, escaped, other or equal * to '@', '[' or ']' @@ -806,48 +803,45 @@ object URI { * in IPv6 addresses, but technically speaking they are in reserved * due to RFC2732). */ - private val authorityQuoteRe = new RegExp( - // !other = [\u0000-\u00a0\u1680\u2000-\u200a\u202f\u205f\u3000\u2028\u2029] - // Char class is: [:!other:^a-z0-9-_.!~*'(),;:$&+=%@\[\]] - "[\u0000- \"#/<>?\\^`{-}" + - "\u007f-\u00a0\u1680\u2000-\u200a\u202f\u205f\u3000\u2028\u2029]|" + - "%(?![0-9a-f]{2})", - "ig") + private[this] lazy val authorityQuoteRe = RegExpImpl.impl.compile( + // !other = [\u0000-\u00a0\u1680\u2000-\u200a\u202f\u205f\u3000\u2028\u2029] + // Char class is: [:!other:^a-z0-9-_.!~*'(),;:$&+=%@\[\]] + "[\u0000- \"#/<>?\\^`{-}" + + "\u007f-\u00a0\u1680\u2000-\u200a\u202f\u205f\u3000\u2028\u2029]|" + + "%(?![0-9a-f]{2})", + RegExpImpl.Flags.Global | RegExpImpl.Flags.CaseInsensitive + ) /** Quote any character not in unreserved, punct, escaped, other or equal * to '@' */ - private def quoteAuthority(str: String) = { - import js.JSStringOps._ - str.jsReplace(authorityQuoteRe, quoteStr) - } + private def quoteAuthority(str: String): String = + RegExpImpl.impl.replaceAll(authorityQuoteRe, str, QuoteStrMapper) /** matches any character not in unreserved, reserved, escaped or other */ - private val illegalQuoteRe = new RegExp( - // !other = [\u0000-\u00a0\u1680\u2000-\u200a\u202f\u205f\u3000\u2028\u2029] - // Char class is: [:!other:^a-z0-9-_.!~*'(),;:$&+=?/\\[\\]%] - "[\u0000- \"#<>@\\^`{-}" + - "\u007f-\u00a0\u1680\u2000-\u200a\u202f\u205f\u3000\u2028\u2029]|" + - "%(?![0-9a-f]{2})", - "ig") + private val illegalQuoteRe = RegExpImpl.impl.compile( + // !other = [\u0000-\u00a0\u1680\u2000-\u200a\u202f\u205f\u3000\u2028\u2029] + // Char class is: [:!other:^a-z0-9-_.!~*'(),;:$&+=?/\\[\\]%] + "[\u0000- \"#<>@\\^`{-}" + + "\u007f-\u00a0\u1680\u2000-\u200a\u202f\u205f\u3000\u2028\u2029]|" + + "%(?![0-9a-f]{2})", + RegExpImpl.Flags.Global | RegExpImpl.Flags.CaseInsensitive + ) /** Quote any character not in unreserved, reserved, escaped or other */ - private def quoteIllegal(str: String) = { - import js.JSStringOps._ - str.jsReplace(illegalQuoteRe, quoteStr) - } + private def quoteIllegal(str: String): String = + RegExpImpl.impl.replaceAll(illegalQuoteRe, str, QuoteStrMapper) /** matches characters not in ASCII * * Note: It is important that the match is maximal, since we might encounter * surrogates that need to be encoded in one shot. */ - private val nonASCIIQuoteRe = new RegExp("[^\u0000-\u007F]+", "g") + private val nonASCIIQuoteRe = + RegExpImpl.impl.compile("[^\u0000-\u007F]+", RegExpImpl.Flags.Global) - private def quoteNonASCII(str: String) = { - import js.JSStringOps._ - str.jsReplace(nonASCIIQuoteRe, quoteStr) - } + private def quoteNonASCII(str: String): String = + RegExpImpl.impl.replaceAll(nonASCIIQuoteRe, str, QuoteStrMapper) /** Case-insensitive comparison that accepts `null` values. * diff --git a/javalib/src/main/scala/java/util/regex/RegExpImpl.scala b/javalib/src/main/scala/java/util/regex/RegExpImpl.scala index 75bc392dba..8c03f612a9 100644 --- a/javalib/src/main/scala/java/util/regex/RegExpImpl.scala +++ b/javalib/src/main/scala/java/util/regex/RegExpImpl.scala @@ -29,6 +29,7 @@ private[java] sealed abstract class RegExpImpl { def compile(patternStr: String): PatRepr def compile(patternStr: String, global: Boolean): PatRepr + def compile(patternStr: String, flags: Int): PatRepr def exec(pattern: PatRepr, string: String): Repr def matches(r: Repr): Boolean def exists(r: Repr, index: Int): Boolean @@ -37,9 +38,20 @@ private[java] sealed abstract class RegExpImpl { def execFrom(pattern: PatRepr, string: String, startPos: Int): Repr def matchStart(r: Repr): Int def matchEnd(pattern: PatRepr, r: Repr): Int + + def replaceAll( + pattern: PatRepr, + string: String, + replacer: java.util.function.Function[String, String] + ): String } private[java] object RegExpImpl { + object Flags { + final val Global = 0x01 + final val CaseInsensitive = 0x02 + } + val impl = LinkingInfo.linkTimeIf[RegExpImpl]( moduleKind == MinimalWasmModule || moduleKind == WasmComponent) { JavaRegExpImpl @@ -61,6 +73,14 @@ private[java] object RegExpImpl { else new js.RegExp(patternStr) } + def compile(patternStr: String, flags: Int): PatRepr = { + val jsFlags = { + (if ((flags & Flags.Global) != 0) "g" else "") + + (if ((flags & Flags.CaseInsensitive) != 0) "i" else "") + } + new js.RegExp(patternStr, jsFlags) + } + def exec(pattern: PatRepr, string: String): Repr = pattern.exec(string) def matches(r: Repr): Boolean = r != null def exists(r: Repr, index: Int): Boolean = undefOrIsDefined(r(index)) @@ -83,6 +103,20 @@ private[java] object RegExpImpl { if (r == null) -1 else pattern.lastIndex } + + def replaceAll( + pattern: PatRepr, + string: String, + replacer: java.util.function.Function[String, String] + ): String = { + import js.JSStringOps._ + if (!pattern.global) + throw new IllegalArgumentException("replaceAll requires a global pattern") + val jsFunc: js.Function1[String, String] = { (matched: String) => + replacer.apply(matched) + } + string.jsReplace(pattern, jsFunc) + } } private object JavaRegExpImpl extends RegExpImpl { @@ -91,6 +125,14 @@ private[java] object RegExpImpl { def compile(patternStr: String): PatRepr = Pattern.compile(patternStr) def compile(patternStr: String, global: Boolean): PatRepr = Pattern.compile(patternStr) + + def compile(patternStr: String, flags: Int): PatRepr = { + var patFlags = 0 + if ((flags & Flags.CaseInsensitive) != 0) + patFlags |= Pattern.CASE_INSENSITIVE + Pattern.compile(patternStr, patFlags) + } + def exec(pattern: PatRepr, string: String): Repr = pattern.matcher(string) def matches(r: Repr): Boolean = r.matches() def exists(r: Repr, index: Int): Boolean = r.group(index) != null @@ -115,5 +157,15 @@ private[java] object RegExpImpl { if (r == null) -1 else r.end() } + + def replaceAll( + pattern: PatRepr, + string: String, + replacer: java.util.function.Function[String, String] + ): String = { + pattern.matcher(string).replaceAll(new java.util.function.Function[MatchResult, String] { + def apply(result: MatchResult): String = replacer.apply(result.group()) + }) + } } } diff --git a/project/Build.scala b/project/Build.scala index b7b514310a..4768236335 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -2361,10 +2361,8 @@ object Build { contains(f, "/shared/src/test/scala/org/scalajs/testsuite/") && ( // javalib/util !endsWith(f, "/DateTest.scala") && // js.Date - !endsWith(f, "/PropertiesTest.scala") && // Date.toString + !endsWith(f, "/PropertiesTest.scala") // Date.toString - // javalib/net - !endsWith(f, "/net/URITest.scala") // URI.normalize ) || contains(f, "/js/src/test/scala/org/scalajs/testsuite/") && ( // compiler diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/net/URITest.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/net/URITest.scala index 52f4b4f6b2..fe0ec85df2 100644 --- a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/net/URITest.scala +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/net/URITest.scala @@ -474,4 +474,55 @@ class URITest { assertTrue("SSP case-sensitive", new URI("mailto:john") != new URI("mailto:JOHN")) assertTrue(new URI("mailto:john") != new URI("MAILTO:jim")) } + + // Tests for multi-component constructors, normalize edge cases, and + // non-ASCII quoting. These exercise quoting/parsing code paths that + // were previously not covered by the test suite. + + @Test def multiComponentConstructorQuoting(): Unit = { + // 7-arg constructor exercises quoteUserInfo, quotePath, quoteAuthority + val uri = new URI("http", "us er", "example.com", 80, "/a path", "q=1 2", "frag ment") + assertEquals("http", uri.getScheme()) + assertEquals("example.com", uri.getHost()) + assertEquals(80, uri.getPort()) + assertEquals("/a path", uri.getPath()) + assertEquals("q=1 2", uri.getQuery()) + assertEquals("frag ment", uri.getFragment()) + assertEquals("us er", uri.getUserInfo()) + // Raw forms should have spaces percent-encoded + assertEquals("/a%20path", uri.getRawPath()) + assertEquals("q=1%202", uri.getRawQuery()) + assertEquals("frag%20ment", uri.getRawFragment()) + assertEquals("us%20er", uri.getRawUserInfo()) + } + + @Test def multiComponentConstructorWithIPv6Host(): Unit = { // exercises IPv6 detection + val uri = new URI("http", null, "::1", 8080, "/path", null, null) + assertEquals("[::1]", uri.getHost()) + assertEquals(8080, uri.getPort()) + assertEquals("/path", uri.getPath()) + assertTrue(uri.toString().contains("[::1]")) + } + + @Test def threeArgConstructorQuoting(): Unit = { // exercises quoteIllegal + val uri = new URI("foo", "hello world", "frag ment") + assertEquals("foo", uri.getScheme()) + assertEquals("hello world", uri.getSchemeSpecificPart()) + assertEquals("frag ment", uri.getFragment()) + assertEquals("hello%20world", uri.getRawSchemeSpecificPart()) + assertEquals("frag%20ment", uri.getRawFragment()) + } + + @Test def normalizeDotOnlyPaths(): Unit = { + // Dot-only relative paths (not covered by RFC resolve examples above) + assertEquals("", new URI(".").normalize().getPath()) + assertEquals("", new URI("./").normalize().getPath()) + assertEquals("..", new URI("..").normalize().getPath()) + assertEquals("../", new URI("../").normalize().getPath()) + } + + @Test def normalizeEmptySegments(): Unit = { + // Multiple consecutive slashes produce empty segments + assertEquals("/a/b", new URI("/a///b").normalize().getPath()) + } }