Skip to content

Commit 530a704

Browse files
committed
Make error collection thread-safe
1 parent 0f3ba71 commit 530a704

File tree

1 file changed

+39
-28
lines changed
  • linker/shared/src/main/scala/org/scalajs/linker/analyzer

1 file changed

+39
-28
lines changed

linker/shared/src/main/scala/org/scalajs/linker/analyzer/Analyzer.scala

Lines changed: 39 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import scala.concurrent._
2020
import scala.util.{Try, Success, Failure}
2121

2222
import java.util.concurrent.ConcurrentLinkedQueue
23-
import java.util.concurrent.atomic.{AtomicBoolean, AtomicInteger}
23+
import java.util.concurrent.atomic._
2424

2525
import org.scalajs.ir
2626
import org.scalajs.ir.ClassKind
@@ -63,7 +63,7 @@ final class Analyzer(config: CommonPhaseConfig, initial: Boolean,
6363
private var objectClassInfo: ClassInfo = _
6464
private[this] var classLoader: ClassLoader = _
6565

66-
private[this] val _errors = mutable.Buffer.empty[Error]
66+
private[this] val _errors = new GrowingList[Error]
6767

6868
private var workQueue: WorkQueue = _
6969

@@ -155,37 +155,41 @@ final class Analyzer(config: CommonPhaseConfig, initial: Boolean,
155155
).distinct
156156

157157
if (publicModuleIDs.size > 1)
158-
_errors += MultiplePublicModulesWithoutModuleSupport(publicModuleIDs)
158+
_errors ::= MultiplePublicModulesWithoutModuleSupport(publicModuleIDs)
159159
}
160160

161161
val infos = classLoader.loadedInfos()
162162

163163
// Reach additional data, based on reflection methods used
164164
reachDataThroughReflection(infos)
165165

166-
if (failOnError && _errors.nonEmpty)
166+
val errs = _errors.get()
167+
168+
if (failOnError && errs.nonEmpty)
167169
reportErrors(logger)
168170

169171
new Analysis {
170172
val classInfos = infos
171173
val topLevelExportInfos = _topLevelExportInfos
172-
val errors = _errors
174+
val errors = errs
173175
}
174176
}
175177

176178
private def reportErrors(logger: Logger): Unit = {
177-
require(_errors.nonEmpty)
179+
val errors = _errors.get()
180+
181+
require(errors.nonEmpty)
178182

179183
val maxDisplayErrors = {
180184
val propName = "org.scalajs.linker.maxlinkingerrors"
181185
Try(System.getProperty(propName, "20").toInt).getOrElse(20).max(1)
182186
}
183187

184-
_errors
188+
errors
185189
.take(maxDisplayErrors)
186190
.foreach(logError(_, logger, Level.Error))
187191

188-
val skipped = _errors.size - maxDisplayErrors
192+
val skipped = errors.size - maxDisplayErrors
189193
if (skipped > 0)
190194
logger.log(Level.Error, s"Not showing $skipped more linking errors")
191195

@@ -333,7 +337,7 @@ final class Analyzer(config: CommonPhaseConfig, initial: Boolean,
333337

334338
case CycleInfo(cycle, root) =>
335339
assert(root == null, s"unresolved root: $root")
336-
_errors += CycleInInheritanceChain(cycle, fromAnalyzer)
340+
_errors ::= CycleInInheritanceChain(cycle, fromAnalyzer)
337341
}
338342
}
339343

@@ -351,7 +355,7 @@ final class Analyzer(config: CommonPhaseConfig, initial: Boolean,
351355
// Assemble loaded infos.
352356
val infos = _classInfos.collect { case (k, i: ClassInfo) => (k, i) }
353357

354-
assert(_errors.nonEmpty || infos.size == _classInfos.size,
358+
assert(_errors.get().nonEmpty || infos.size == _classInfos.size,
355359
"unloaded classes in post load phase")
356360

357361
infos
@@ -499,7 +503,7 @@ final class Analyzer(config: CommonPhaseConfig, initial: Boolean,
499503

500504
def link()(implicit from: From): Unit = {
501505
if (nonExistent)
502-
_errors += MissingClass(this, from)
506+
_errors ::= MissingClass(this, from)
503507

504508
linkedFrom ::= from
505509
}
@@ -516,7 +520,7 @@ final class Analyzer(config: CommonPhaseConfig, initial: Boolean,
516520
case ClassKind.Class | ClassKind.ModuleClass | ClassKind.HijackedClass =>
517521
val superCl = superClass.get // checked by ClassDef checker.
518522
if (superCl.kind != ClassKind.Class) {
519-
_errors += InvalidSuperClass(superCl, this, from)
523+
_errors ::= InvalidSuperClass(superCl, this, from)
520524
Some(objectClassInfo)
521525
} else {
522526
superClass
@@ -539,7 +543,7 @@ final class Analyzer(config: CommonPhaseConfig, initial: Boolean,
539543
case ClassKind.JSClass | ClassKind.NativeJSClass =>
540544
superClass // ok
541545
case _ =>
542-
_errors += InvalidSuperClass(superCl, this, from)
546+
_errors ::= InvalidSuperClass(superCl, this, from)
543547
None
544548
}
545549

@@ -551,7 +555,7 @@ final class Analyzer(config: CommonPhaseConfig, initial: Boolean,
551555
case _ if superCl eq objectClassInfo =>
552556
superClass // ok
553557
case _ =>
554-
_errors += InvalidSuperClass(superCl, this, from)
558+
_errors ::= InvalidSuperClass(superCl, this, from)
555559
Some(objectClassInfo)
556560
}
557561

@@ -563,7 +567,7 @@ final class Analyzer(config: CommonPhaseConfig, initial: Boolean,
563567
case _ if superCl eq objectClassInfo =>
564568
superClass // ok
565569
case _ =>
566-
_errors += InvalidSuperClass(superCl, this, from)
570+
_errors ::= InvalidSuperClass(superCl, this, from)
567571
None
568572
}
569573
}
@@ -588,7 +592,7 @@ final class Analyzer(config: CommonPhaseConfig, initial: Boolean,
588592
// Remove it but do not report an additional error message
589593
false
590594
} else if (superIntf.kind != validSuperIntfKind) {
591-
_errors += InvalidImplementedInterface(superIntf, this, from)
595+
_errors ::= InvalidImplementedInterface(superIntf, this, from)
592596
false
593597
} else {
594598
true
@@ -754,7 +758,7 @@ final class Analyzer(config: CommonPhaseConfig, initial: Boolean,
754758
* We use fromAnalyzer because we don't have any From here (we
755759
* shouldn't, since lookup methods are not supposed to produce errors).
756760
*/
757-
_errors += ConflictingDefaultMethods(notShadowed, fromAnalyzer)
761+
_errors ::= ConflictingDefaultMethods(notShadowed, fromAnalyzer)
758762
}
759763

760764
notShadowed.headOption
@@ -1004,14 +1008,14 @@ final class Analyzer(config: CommonPhaseConfig, initial: Boolean,
10041008
_topLevelExportInfos.get(key).fold[Unit] {
10051009
_topLevelExportInfos.put(key, info)
10061010
} { other =>
1007-
_errors += ConflictingTopLevelExport(tle.moduleID, tle.exportName, List(info, other))
1011+
_errors ::= ConflictingTopLevelExport(tle.moduleID, tle.exportName, List(info, other))
10081012
}
10091013
}
10101014
}
10111015

10121016
def accessModule()(implicit from: From): Unit = {
10131017
if (!isAnyModuleClass) {
1014-
_errors += NotAModule(this, from)
1018+
_errors ::= NotAModule(this, from)
10151019
} else if (!isModuleAccessed) {
10161020
isModuleAccessed = true
10171021

@@ -1199,7 +1203,7 @@ final class Analyzer(config: CommonPhaseConfig, initial: Boolean,
11991203
if (jsNativeMembersUsed.add(name)) {
12001204
maybeJSNativeLoadSpec match {
12011205
case None =>
1202-
_errors += MissingJSNativeMember(this, name, from)
1206+
_errors ::= MissingJSNativeMember(this, name, from)
12031207
case Some(jsNativeLoadSpec) =>
12041208
validateLoadSpec(jsNativeLoadSpec, Some(name))
12051209
}
@@ -1229,7 +1233,7 @@ final class Analyzer(config: CommonPhaseConfig, initial: Boolean,
12291233
if (isNoModule) {
12301234
jsNativeLoadSpec match {
12311235
case JSNativeLoadSpec.Import(module, _) =>
1232-
_errors += ImportWithoutModuleSupport(module, this, jsNativeMember, from)
1236+
_errors ::= ImportWithoutModuleSupport(module, this, jsNativeMember, from)
12331237
case _ =>
12341238
}
12351239
}
@@ -1310,12 +1314,12 @@ final class Analyzer(config: CommonPhaseConfig, initial: Boolean,
13101314

13111315
private def checkExistent()(implicit from: From) = {
13121316
if (nonExistent)
1313-
_errors += MissingMethod(this, from)
1317+
_errors ::= MissingMethod(this, from)
13141318
}
13151319

13161320
private def checkConcrete()(implicit from: From) = {
13171321
if (nonExistent || isAbstract)
1318-
_errors += MissingMethod(this, from)
1322+
_errors ::= MissingMethod(this, from)
13191323
}
13201324

13211325
private[this] def doReach(): Unit = {
@@ -1330,7 +1334,7 @@ final class Analyzer(config: CommonPhaseConfig, initial: Boolean,
13301334
val exportName: String = data.exportName
13311335

13321336
if (isNoModule && !ir.Trees.JSGlobalRef.isValidJSGlobalRefName(exportName)) {
1333-
_errors += InvalidTopLevelExportInScript(this)
1337+
_errors ::= InvalidTopLevelExportInScript(this)
13341338
}
13351339

13361340
val staticDependencies: mutable.Set[ClassName] = mutable.Set.empty
@@ -1423,7 +1427,7 @@ final class Analyzer(config: CommonPhaseConfig, initial: Boolean,
14231427

14241428
if (!dataInClass.methodsCalledDynamicImport.isEmpty) {
14251429
if (isNoModule) {
1426-
_errors += DynamicImportWithoutModuleSupport(from)
1430+
_errors ::= DynamicImportWithoutModuleSupport(from)
14271431
} else {
14281432
dynamicDependencies += className
14291433
// In terms of reachability, a dynamic import call is just a static call.
@@ -1456,17 +1460,17 @@ final class Analyzer(config: CommonPhaseConfig, initial: Boolean,
14561460

14571461
if ((globalFlags & ReachabilityInfo.FlagAccessedNewTarget) != 0 &&
14581462
config.coreSpec.esFeatures.esVersion < ESVersion.ES2015) {
1459-
_errors += NewTargetWithoutES2015Support(from)
1463+
_errors ::= NewTargetWithoutES2015Support(from)
14601464
}
14611465

14621466
if ((globalFlags & ReachabilityInfo.FlagAccessedImportMeta) != 0 &&
14631467
config.coreSpec.moduleKind != ModuleKind.ESModule) {
1464-
_errors += ImportMetaWithoutESModule(from)
1468+
_errors ::= ImportMetaWithoutESModule(from)
14651469
}
14661470

14671471
if ((globalFlags & ReachabilityInfo.FlagUsedExponentOperator) != 0 &&
14681472
config.coreSpec.esFeatures.esVersion < ESVersion.ES2016) {
1469-
_errors += ExponentOperatorWithoutES2016Support(from)
1473+
_errors ::= ExponentOperatorWithoutES2016Support(from)
14701474
}
14711475
}
14721476
}
@@ -1573,4 +1577,11 @@ object Analyzer {
15731577
}
15741578
}
15751579
}
1580+
1581+
private final class GrowingList[A] {
1582+
private val list = new AtomicReference[List[A]](Nil)
1583+
def ::=(item: A): Unit = list.updateAndGet(item :: _)
1584+
def get(): List[A] = list.get()
1585+
def clear(): Unit = list.set(Nil)
1586+
}
15761587
}

0 commit comments

Comments
 (0)