From 693f91f18e95317c27609a23a97b57b581bf6d28 Mon Sep 17 00:00:00 2001 From: Tobias Schlatter Date: Sat, 5 Aug 2023 15:24:29 +0200 Subject: [PATCH 1/4] Add non-trivial test that missing jl.Object doesn't crash Analyzer --- .../test/scala/org/scalajs/linker/AnalyzerTest.scala | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/linker/shared/src/test/scala/org/scalajs/linker/AnalyzerTest.scala b/linker/shared/src/test/scala/org/scalajs/linker/AnalyzerTest.scala index e28017127c..fc8b427f5d 100644 --- a/linker/shared/src/test/scala/org/scalajs/linker/AnalyzerTest.scala +++ b/linker/shared/src/test/scala/org/scalajs/linker/AnalyzerTest.scala @@ -54,6 +54,16 @@ class AnalyzerTest { assertExactErrors(analysis, MissingJavaLangObjectClass(fromAnalyzer)) } + @Test + def missingJavaLangObjectButOthers(): AsyncResult = await { + val classDefs = Seq(classDef("A", superClass = Some(ObjectClass))) + + val analysis = computeAnalysis(classDefs, + reqsFactory.classData("A"), stdlib = TestIRRepo.empty) + + assertExactErrors(analysis, MissingJavaLangObjectClass(fromAnalyzer)) + } + @Test def cycleInInheritanceChainThroughParentClasses(): AsyncResult = await { val classDefs = Seq( From 31781d4121eda347d88d21b4cb5c10bae32afebb Mon Sep 17 00:00:00 2001 From: Tobias Schlatter Date: Sat, 5 Aug 2023 14:29:38 +0200 Subject: [PATCH 2/4] Analyzer: Always validate superClass and interfaces --- .../org/scalajs/linker/analyzer/Analyzer.scala | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) 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 207e6300df..b7cb3214a8 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 @@ -461,15 +461,11 @@ final class Analyzer(config: CommonPhaseConfig, initial: Boolean, val isNativeJSClass = kind == ClassKind.NativeJSClass || kind == ClassKind.NativeJSModuleClass - // Note: j.l.Object is special and is validated upfront - val superClass: Option[ClassInfo] = - if (className == ObjectClass) unvalidatedSuperClass - else validateSuperClass(unvalidatedSuperClass) + validateSuperClass(unvalidatedSuperClass) val interfaces: List[ClassInfo] = - if (className == ObjectClass) unvalidatedInterfaces - else validateInterfaces(unvalidatedInterfaces) + validateInterfaces(unvalidatedInterfaces) /** Ancestors of this class or interface. * @@ -497,6 +493,11 @@ final class Analyzer(config: CommonPhaseConfig, initial: Boolean, def from = FromClass(this) kind match { + case _ if className == ObjectClass => + assert(superClass.isEmpty) + + None + case ClassKind.Class | ClassKind.ModuleClass | ClassKind.HijackedClass => val superCl = superClass.get // checked by ClassDef checker. if (superCl.kind != ClassKind.Class) { From f437415a6422216a4c356ec811330634717292f8 Mon Sep 17 00:00:00 2001 From: Tobias Schlatter Date: Sat, 5 Aug 2023 14:31:22 +0200 Subject: [PATCH 3/4] Analyzer: Fix queue buildup during class loading The Analyzer is built with the assumption that the number of pending tasks never drops to zero until the whole analysis is completed. However, our loading sequence violated this assumption: since the constructor of LoadingClass scheduled info loading immediately, it was possible for info loading to complete before linking is requested on the class. If this condition happens on the initial calling thread (which itself is not tracked as a "task"), the pending task count would drop to zero. --- .../scalajs/linker/analyzer/Analyzer.scala | 28 +++++++++++-------- 1 file changed, 17 insertions(+), 11 deletions(-) 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 b7cb3214a8..3ca3f5d217 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 @@ -352,7 +352,13 @@ final class Analyzer(config: CommonPhaseConfig, initial: Boolean, _classInfos.get(className) match { case None => val loading = new LoadingClass(className) + + _classInfos(className) = loading + + // Request linking before scheduling the loading to avoid the task queue + // dropping to zero intermittently. loading.requestLink(knownDescendants)(onSuccess) + loading.startLoad() case Some(loading: LoadingClass) => loading.requestLink(knownDescendants)(onSuccess) @@ -377,17 +383,6 @@ final class Analyzer(config: CommonPhaseConfig, initial: Boolean, private val promise = Promise[LoadingResult]() private var knownDescendants = Set[LoadingClass](this) - _classInfos(className) = this - - infoLoader.loadInfo(className)(workQueue.ec) match { - case Some(future) => - workQueue.enqueue(future)(link(_, nonExistent = false)) - - case None => - val data = createMissingClassInfo(className) - link(data, nonExistent = true) - } - def requestLink(knownDescendants: Set[LoadingClass])(onSuccess: LoadingResult => Unit): Unit = { if (knownDescendants.contains(this)) { onSuccess(CycleInfo(Nil, this)) @@ -397,6 +392,17 @@ final class Analyzer(config: CommonPhaseConfig, initial: Boolean, } } + def startLoad(): Unit = { + infoLoader.loadInfo(className)(workQueue.ec) match { + case Some(future) => + workQueue.enqueue(future)(link(_, nonExistent = false)) + + case None => + val data = createMissingClassInfo(className) + link(data, nonExistent = true) + } + } + private def link(data: Infos.ClassInfo, nonExistent: Boolean): Unit = { lookupAncestors(data.superClass.toList ++ data.interfaces) { classes => val (superClass, interfaces) = From 42cd08dfda446be56a313078a5cbf998e82c6e67 Mon Sep 17 00:00:00 2001 From: Tobias Schlatter Date: Sat, 5 Aug 2023 11:39:55 +0200 Subject: [PATCH 4/4] Use standard loading mechanism for jl.Object in Analyzer Discovered as a simplification while working on #1626. --- .../scalajs/linker/analyzer/Analysis.scala | 3 --- .../scalajs/linker/analyzer/Analyzer.scala | 24 ++++++++----------- .../org/scalajs/linker/AnalyzerTest.scala | 10 ++++++-- 3 files changed, 18 insertions(+), 19 deletions(-) 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 46050e65b1..5d4ca8efb0 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 @@ -165,7 +165,6 @@ object Analysis { def from: From } - final case class MissingJavaLangObjectClass(from: From) extends Error final case class CycleInInheritanceChain(encodedClassNames: List[ClassName], from: From) extends Error final case class MissingClass(info: ClassInfo, from: From) extends Error @@ -216,8 +215,6 @@ object Analysis { def logError(error: Error, logger: Logger, level: Level): Unit = { val headMsg = error match { - case MissingJavaLangObjectClass(_) => - "Fatal error: java.lang.Object is missing" case CycleInInheritanceChain(encodedClassNames, _) => ("Fatal error: cycle in inheritance chain involving " + encodedClassNames.map(_.nameString).mkString(", ")) 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 3ca3f5d217..1bcc71044b 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 @@ -99,19 +99,11 @@ final class Analyzer(config: CommonPhaseConfig, initial: Boolean, /* Load the java.lang.Object class, and validate it * If it is missing or invalid, we're in deep trouble, and cannot continue. */ - infoLoader.loadInfo(ObjectClass)(workQueue.ec) match { - case None => - _errors += MissingJavaLangObjectClass(fromAnalyzer) - - case Some(future) => - workQueue.enqueue(future) { data => - objectClassInfo = new ClassInfo(data, - unvalidatedSuperClass = None, - unvalidatedInterfaces = Nil, nonExistent = false) - - objectClassInfo.link() - onSuccess() - } + lookupClass(ObjectClass) { clazz => + if (!clazz.nonExistent) { + objectClassInfo = clazz + onSuccess() + } } } @@ -1486,8 +1478,12 @@ final class Analyzer(config: CommonPhaseConfig, initial: Boolean, } private def createMissingClassInfo(className: ClassName): Infos.ClassInfo = { + val superClass = + if (className == ObjectClass) None + else Some(ObjectClass) + new Infos.ClassInfoBuilder(className, ClassKind.Class, - superClass = Some(ObjectClass), interfaces = Nil, jsNativeLoadSpec = None) + superClass = superClass, interfaces = Nil, jsNativeLoadSpec = None) .addMethod(makeSyntheticMethodInfo(NoArgConstructorName, MemberNamespace.Constructor)) .result() } diff --git a/linker/shared/src/test/scala/org/scalajs/linker/AnalyzerTest.scala b/linker/shared/src/test/scala/org/scalajs/linker/AnalyzerTest.scala index fc8b427f5d..06b8747849 100644 --- a/linker/shared/src/test/scala/org/scalajs/linker/AnalyzerTest.scala +++ b/linker/shared/src/test/scala/org/scalajs/linker/AnalyzerTest.scala @@ -51,7 +51,10 @@ class AnalyzerTest { @Test def missingJavaLangObject(): AsyncResult = await { val analysis = computeAnalysis(Nil, stdlib = TestIRRepo.empty) - assertExactErrors(analysis, MissingJavaLangObjectClass(fromAnalyzer)) + assertContainsError("MissingClass(jlObject)", analysis) { + case MissingClass(ClsInfo(name), fromAnalyzer) => + name == ObjectClass.nameString + } } @Test @@ -61,7 +64,10 @@ class AnalyzerTest { val analysis = computeAnalysis(classDefs, reqsFactory.classData("A"), stdlib = TestIRRepo.empty) - assertExactErrors(analysis, MissingJavaLangObjectClass(fromAnalyzer)) + assertContainsError("MissingClass(jlObject)", analysis) { + case MissingClass(ClsInfo(name), fromAnalyzer) => + name == ObjectClass.nameString + } } @Test