Skip to content

Commit 2bd3044

Browse files
committed
SI-8131 fixes residual race condition in runtime reflection
Apparently some completers can call setInfo while they’re not yet done, which resets the LOCKED flag, and makes anything that uses LOCKED to track completion unreliable. Unfortunately, that’s exactly the mechanism that was used by runtime reflection to elide locking for symbols that are known to be initialized. This commit fixes the problematic lock elision strategy by introducing an explicit communication channel between SynchronizedSymbol’s and their completers. Now instead of trying hard to infer whether it’s already initialized or not, every symbol gets a volatile field that can be queried to provide necessary information.
1 parent f142d85 commit 2bd3044

11 files changed

+224
-118
lines changed

src/reflect/scala/reflect/internal/BuildUtils.scala

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,19 +33,19 @@ trait BuildUtils { self: SymbolTable =>
3333
}
3434

3535
def newFreeTerm(name: String, value: => Any, flags: Long = 0L, origin: String = null): FreeTermSymbol =
36-
newFreeTermSymbol(newTermName(name), value, flags, origin)
36+
newFreeTermSymbol(newTermName(name), value, flags, origin).markFlagsCompleted(mask = AllFlags)
3737

3838
def newFreeType(name: String, flags: Long = 0L, origin: String = null): FreeTypeSymbol =
39-
newFreeTypeSymbol(newTypeName(name), flags, origin)
39+
newFreeTypeSymbol(newTypeName(name), flags, origin).markFlagsCompleted(mask = AllFlags)
4040

4141
def newNestedSymbol(owner: Symbol, name: Name, pos: Position, flags: Long, isClass: Boolean): Symbol =
42-
owner.newNestedSymbol(name, pos, flags, isClass)
42+
owner.newNestedSymbol(name, pos, flags, isClass).markFlagsCompleted(mask = AllFlags)
4343

4444
def setAnnotations[S <: Symbol](sym: S, annots: List[AnnotationInfo]): S =
4545
sym.setAnnotations(annots)
4646

4747
def setTypeSignature[S <: Symbol](sym: S, tpe: Type): S =
48-
sym.setTypeSignature(tpe)
48+
sym.setTypeSignature(tpe).markAllCompleted()
4949

5050
def This(sym: Symbol): Tree = self.This(sym)
5151

src/reflect/scala/reflect/internal/Definitions.scala

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,12 @@ trait Definitions extends api.StandardDefinitions {
3030

3131
private def enterNewClass(owner: Symbol, name: TypeName, parents: List[Type], flags: Long = 0L): ClassSymbol = {
3232
val clazz = owner.newClassSymbol(name, NoPosition, flags)
33-
clazz setInfoAndEnter ClassInfoType(parents, newScope, clazz)
33+
clazz setInfoAndEnter ClassInfoType(parents, newScope, clazz) markAllCompleted
3434
}
3535
private def newMethod(owner: Symbol, name: TermName, formals: List[Type], restpe: Type, flags: Long): MethodSymbol = {
3636
val msym = owner.newMethod(name.encode, NoPosition, flags)
3737
val params = msym.newSyntheticValueParams(formals)
38-
msym setInfo MethodType(params, restpe)
38+
msym setInfo MethodType(params, restpe) markAllCompleted
3939
}
4040
private def enterNewMethod(owner: Symbol, name: TermName, formals: List[Type], restpe: Type, flags: Long = 0L): MethodSymbol =
4141
owner.info.decls enter newMethod(owner, name, formals, restpe, flags)
@@ -251,8 +251,8 @@ trait Definitions extends api.StandardDefinitions {
251251
}
252252

253253
// top types
254-
lazy val AnyClass = enterNewClass(ScalaPackageClass, tpnme.Any, Nil, ABSTRACT)
255-
lazy val AnyRefClass = newAlias(ScalaPackageClass, tpnme.AnyRef, ObjectTpe)
254+
lazy val AnyClass = enterNewClass(ScalaPackageClass, tpnme.Any, Nil, ABSTRACT) markAllCompleted
255+
lazy val AnyRefClass = newAlias(ScalaPackageClass, tpnme.AnyRef, ObjectTpe) markAllCompleted
256256
lazy val ObjectClass = getRequiredClass(sn.Object.toString)
257257

258258
// Cached types for core monomorphic classes
@@ -275,7 +275,7 @@ trait Definitions extends api.StandardDefinitions {
275275
val anyval = enterNewClass(ScalaPackageClass, tpnme.AnyVal, AnyTpe :: Nil, ABSTRACT)
276276
val av_constr = anyval.newClassConstructor(NoPosition)
277277
anyval.info.decls enter av_constr
278-
anyval
278+
anyval markAllCompleted
279279
}).asInstanceOf[ClassSymbol]
280280
def AnyVal_getClass = getMemberMethod(AnyValClass, nme.getClass_)
281281

@@ -287,8 +287,10 @@ trait Definitions extends api.StandardDefinitions {
287287
locally {
288288
this initFlags ABSTRACT | FINAL
289289
this setInfoAndEnter ClassInfoType(List(parent.tpe), newScope, this)
290+
this markAllCompleted
290291
}
291292
final override def isBottomClass = true
293+
final override def isThreadsafe(purpose: SymbolOps): Boolean = true
292294
}
293295
final object NothingClass extends BottomClassSymbol(tpnme.Nothing, AnyClass) {
294296
override def isSubClass(that: Symbol) = true
@@ -357,7 +359,7 @@ trait Definitions extends api.StandardDefinitions {
357359
def delayedInitMethod = getMemberMethod(DelayedInitClass, nme.delayedInit)
358360

359361
lazy val TypeConstraintClass = requiredClass[scala.annotation.TypeConstraint]
360-
lazy val SingletonClass = enterNewClass(ScalaPackageClass, tpnme.Singleton, AnyTpe :: Nil, ABSTRACT | TRAIT | FINAL)
362+
lazy val SingletonClass = enterNewClass(ScalaPackageClass, tpnme.Singleton, AnyTpe :: Nil, ABSTRACT | TRAIT | FINAL) markAllCompleted
361363
lazy val SerializableClass = requiredClass[scala.Serializable]
362364
lazy val JavaSerializableClass = requiredClass[java.io.Serializable] modifyInfo fixupAsAnyTrait
363365
lazy val ComparableClass = requiredClass[java.lang.Comparable[_]] modifyInfo fixupAsAnyTrait
@@ -1126,6 +1128,7 @@ trait Definitions extends api.StandardDefinitions {
11261128
lazy val AnnotationDefaultAttr: ClassSymbol = {
11271129
val sym = RuntimePackageClass.newClassSymbol(tpnme.AnnotationDefaultATTR, NoPosition, 0L)
11281130
sym setInfo ClassInfoType(List(AnnotationClass.tpe), newScope, sym)
1131+
markAllCompleted(sym)
11291132
RuntimePackageClass.info.decls.toList.filter(_.name == sym.name) match {
11301133
case existing :: _ =>
11311134
existing.asInstanceOf[ClassSymbol]
@@ -1225,7 +1228,7 @@ trait Definitions extends api.StandardDefinitions {
12251228
val tparam = clazz.newSyntheticTypeParam("T0", flags)
12261229
val parents = List(AnyRefTpe, parentFn(tparam))
12271230

1228-
clazz setInfo GenPolyType(List(tparam), ClassInfoType(parents, newScope, clazz))
1231+
clazz setInfo GenPolyType(List(tparam), ClassInfoType(parents, newScope, clazz)) markAllCompleted
12291232
}
12301233

12311234
def newPolyMethod(typeParamCount: Int, owner: Symbol, name: TermName, flags: Long)(createFn: PolyMethodCreator): MethodSymbol = {
@@ -1236,7 +1239,7 @@ trait Definitions extends api.StandardDefinitions {
12361239
case (_, restpe) => NullaryMethodType(restpe)
12371240
}
12381241

1239-
msym setInfoAndEnter genPolyType(tparams, mtpe)
1242+
msym setInfoAndEnter genPolyType(tparams, mtpe) markAllCompleted
12401243
}
12411244

12421245
/** T1 means one type parameter.

src/reflect/scala/reflect/internal/Importers.scala

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ package internal
44

55
import scala.collection.mutable.WeakHashMap
66
import scala.ref.WeakReference
7+
import scala.reflect.internal.Flags._
78

89
// SI-6241: move importers to a mirror
910
trait Importers extends api.Importers { to: SymbolTable =>
@@ -87,6 +88,7 @@ trait Importers extends api.Importers { to: SymbolTable =>
8788
}
8889
my setInfo GenPolyType(mytypeParams, importType(theirCore))
8990
my setAnnotations (their.annotations map importAnnotationInfo)
91+
markAllCompleted(my)
9092
}
9193
}
9294
} finally {
@@ -142,6 +144,7 @@ trait Importers extends api.Importers { to: SymbolTable =>
142144
myowner.newTypeSymbol(myname.toTypeName, mypos, myflags)
143145
}
144146
symMap.weakUpdate(their, my)
147+
markFlagsCompleted(my)(mask = AllFlags)
145148
my setInfo recreatedSymbolCompleter(my, their)
146149
}
147150

src/reflect/scala/reflect/internal/Symbols.scala

Lines changed: 76 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -55,16 +55,6 @@ trait Symbols extends api.Symbols { self: SymbolTable =>
5555
def newFreeTypeSymbol(name: TypeName, flags: Long = 0L, origin: String): FreeTypeSymbol =
5656
new FreeTypeSymbol(name, origin) initFlags flags
5757

58-
/** Determines whether the given information request should trigger the given symbol's completer.
59-
* See comments to `Symbol.needsInitialize` for details.
60-
*/
61-
protected def shouldTriggerCompleter(symbol: Symbol, completer: Type, isFlagRelated: Boolean, mask: Long) =
62-
completer match {
63-
case null => false
64-
case _: FlagAgnosticCompleter => !isFlagRelated
65-
case _ => abort(s"unsupported completer: $completer of class ${if (completer != null) completer.getClass else null} for symbol ${symbol.fullName}")
66-
}
67-
6858
/** The original owner of a class. Used by the backend to generate
6959
* EnclosingMethod attributes.
7060
*/
@@ -106,12 +96,14 @@ trait Symbols extends api.Symbols { self: SymbolTable =>
10696
}
10797

10898
def knownDirectSubclasses = {
109-
if (!isCompilerUniverse && needsInitialize(isFlagRelated = false, mask = 0)) initialize
99+
// See `getFlag` to learn more about the `isThreadsafe` call in the body of this method.
100+
if (!isCompilerUniverse && !isThreadsafe(purpose = AllOps)) initialize
110101
children
111102
}
112103

113104
def selfType = {
114-
if (!isCompilerUniverse && needsInitialize(isFlagRelated = false, mask = 0)) initialize
105+
// See `getFlag` to learn more about the `isThreadsafe` call in the body of this method.
106+
if (!isCompilerUniverse && !isThreadsafe(purpose = AllOps)) initialize
115107
typeOfThis
116108
}
117109

@@ -147,6 +139,11 @@ trait Symbols extends api.Symbols { self: SymbolTable =>
147139
with Annotatable[Symbol]
148140
with Attachable {
149141

142+
// makes sure that all symbols that runtime reflection deals with are synchronized
143+
private def isSynchronized = this.isInstanceOf[scala.reflect.runtime.SynchronizedSymbols#SynchronizedSymbol]
144+
private def isAprioriThreadsafe = isThreadsafe(AllOps)
145+
assert(isCompilerUniverse || isSynchronized || isAprioriThreadsafe, s"unsafe symbol $initName (child of $initOwner) in runtime reflection universe")
146+
150147
type AccessBoundaryType = Symbol
151148
type AnnotationType = AnnotationInfo
152149

@@ -616,20 +613,55 @@ trait Symbols extends api.Symbols { self: SymbolTable =>
616613
&& isTopLevel
617614
&& nme.isReplWrapperName(name)
618615
)
616+
617+
/** In our current architecture, symbols for top-level classes and modules
618+
* are created as dummies. Package symbols just call newClass(name) or newModule(name) and
619+
* consider their job done.
620+
*
621+
* In order for such a dummy to provide meaningful info (e.g. a list of its members),
622+
* it needs to go through unpickling. Unpickling is a process of reading Scala metadata
623+
* from ScalaSignature annotations and assigning it to symbols and types.
624+
*
625+
* A single unpickling session takes a top-level class or module, parses the ScalaSignature annotation
626+
* and then reads metadata for the unpicklee, its companion (if any) and all their members recursively
627+
* (i.e. the pickle not only contains info about directly nested classes/modules, but also about
628+
* classes/modules nested into those and so on).
629+
*
630+
* Unpickling is triggered automatically whenever typeSignature (info in compiler parlance) is called.
631+
* This happens because package symbols assign completer thunks to the dummies they create.
632+
* Therefore metadata loading happens lazily and transparently.
633+
*
634+
* Almost transparently. Unfortunately metadata isn't limited to just signatures (i.e. lists of members).
635+
* It also includes flags (which determine e.g. whether a class is sealed or not), annotations and privateWithin.
636+
* This gives rise to unpleasant effects like in SI-6277, when a flag test called on an uninitialize symbol
637+
* produces incorrect results.
638+
*
639+
* One might think that the solution is simple: automatically call the completer
640+
* whenever one needs flags, annotations and privateWithin - just like it's done for typeSignature.
641+
* Unfortunately, this leads to weird crashes in scalac, and currently we can't attempt
642+
* to fix the core of the compiler risk stability a few weeks before the final release.
643+
* upd. Haha, "a few weeks before the final release". This surely sounds familiar :)
644+
*
645+
* However we do need to fix this for runtime reflection, since this idionsynchrazy is not something
646+
* we'd like to expose to reflection users. Therefore a proposed solution is to check whether we're in a
647+
* runtime reflection universe, and if yes and if we've not yet loaded the requested info, then to commence initialization.
648+
*/
619649
final def getFlag(mask: Long): Long = {
620-
if (!isCompilerUniverse && needsInitialize(isFlagRelated = true, mask = mask)) initialize
650+
if (!isCompilerUniverse && !isThreadsafe(purpose = FlagOps(mask))) initialize
621651
flags & mask
622652
}
623653
/** Does symbol have ANY flag in `mask` set? */
624654
final def hasFlag(mask: Long): Boolean = {
625-
if (!isCompilerUniverse && needsInitialize(isFlagRelated = true, mask = mask)) initialize
655+
// See `getFlag` to learn more about the `isThreadsafe` call in the body of this method.
656+
if (!isCompilerUniverse && !isThreadsafe(purpose = FlagOps(mask))) initialize
626657
(flags & mask) != 0
627658
}
628659
def hasFlag(mask: Int): Boolean = hasFlag(mask.toLong)
629660

630661
/** Does symbol have ALL the flags in `mask` set? */
631662
final def hasAllFlags(mask: Long): Boolean = {
632-
if (!isCompilerUniverse && needsInitialize(isFlagRelated = true, mask = mask)) initialize
663+
// See `getFlag` to learn more about the `isThreadsafe` call in the body of this method.
664+
if (!isCompilerUniverse && !isThreadsafe(purpose = FlagOps(mask))) initialize
633665
(flags & mask) == mask
634666
}
635667

@@ -950,12 +982,21 @@ trait Symbols extends api.Symbols { self: SymbolTable =>
950982
final def isInitialized: Boolean =
951983
validTo != NoPeriod
952984

953-
/** Some completers call sym.setInfo when still in-flight and then proceed with initialization (e.g. see LazyPackageType)
954-
* setInfo sets _validTo to current period, which means that after a call to setInfo isInitialized will start returning true.
955-
* Unfortunately, this doesn't mean that info becomes ready to be used, because subsequent initialization might change the info.
956-
* Therefore we need this method to distinguish between initialized and really initialized symbol states.
985+
/** We consider a symbol to be thread-safe, when multiple concurrent threads can call its methods
986+
* (either directly or indirectly via public reflection or internal compiler infrastructure),
987+
* without any locking and everything works as it should work.
988+
*
989+
* In its basic form, `isThreadsafe` always returns false. Runtime reflection augments reflection infrastructure
990+
* with threadsafety-tracking mechanism implemented in `SynchronizedSymbol` that communicates with underlying completers
991+
* and can sometimes return true if the symbol has been completed to the point of thread safety.
992+
*
993+
* The `purpose` parameter signifies whether we want to just check immutability of certain flags for the given mask.
994+
* This is necessary to enable robust auto-initialization of `Symbol.flags` for runtime reflection, and is also quite handy
995+
* in avoiding unnecessary initializations when requesting for flags that have already been set.
957996
*/
958-
final def isFullyInitialized: Boolean = _validTo != NoPeriod && (flags & LOCKED) == 0
997+
def isThreadsafe(purpose: SymbolOps): Boolean = false
998+
def markFlagsCompleted(mask: Long): this.type = this
999+
def markAllCompleted(): this.type = this
9591000

9601001
/** Can this symbol be loaded by a reflective mirror?
9611002
*
@@ -1232,7 +1273,8 @@ trait Symbols extends api.Symbols { self: SymbolTable =>
12321273
*/
12331274
private[this] var _privateWithin: Symbol = _
12341275
def privateWithin = {
1235-
if (!isCompilerUniverse && needsInitialize(isFlagRelated = false, mask = 0)) initialize
1276+
// See `getFlag` to learn more about the `isThreadsafe` call in the body of this method.
1277+
if (!isCompilerUniverse && !isThreadsafe(purpose = AllOps)) initialize
12361278
_privateWithin
12371279
}
12381280
def privateWithin_=(sym: Symbol) { _privateWithin = sym }
@@ -1490,46 +1532,6 @@ trait Symbols extends api.Symbols { self: SymbolTable =>
14901532
catch { case _: CyclicReference => debuglog("Hit cycle in maybeInitialize of $this") ; false }
14911533
}
14921534

1493-
/** Called when the programmer requests information that might require initialization of the underlying symbol.
1494-
*
1495-
* `isFlagRelated` and `mask` describe the nature of this information.
1496-
* isFlagRelated = true means that the programmer needs particular bits in flags.
1497-
* isFlagRelated = false means that the request is unrelated to flags (annotations or privateWithin).
1498-
*
1499-
* In our current architecture, symbols for top-level classes and modules
1500-
* are created as dummies. Package symbols just call newClass(name) or newModule(name) and
1501-
* consider their job done.
1502-
*
1503-
* In order for such a dummy to provide meaningful info (e.g. a list of its members),
1504-
* it needs to go through unpickling. Unpickling is a process of reading Scala metadata
1505-
* from ScalaSignature annotations and assigning it to symbols and types.
1506-
*
1507-
* A single unpickling session takes a top-level class or module, parses the ScalaSignature annotation
1508-
* and then reads metadata for the unpicklee, its companion (if any) and all their members recursively
1509-
* (i.e. the pickle not only contains info about directly nested classes/modules, but also about
1510-
* classes/modules nested into those and so on).
1511-
*
1512-
* Unpickling is triggered automatically whenever typeSignature (info in compiler parlance) is called.
1513-
* This happens because package symbols assign completer thunks to the dummies they create.
1514-
* Therefore metadata loading happens lazily and transparently.
1515-
*
1516-
* Almost transparently. Unfortunately metadata isn't limited to just signatures (i.e. lists of members).
1517-
* It also includes flags (which determine e.g. whether a class is sealed or not), annotations and privateWithin.
1518-
* This gives rise to unpleasant effects like in SI-6277, when a flag test called on an uninitialize symbol
1519-
* produces incorrect results.
1520-
*
1521-
* One might think that the solution is simple: automatically call the completer whenever one needs
1522-
* flags, annotations and privateWithin - just like it's done for typeSignature. Unfortunately, this
1523-
* leads to weird crashes in scalac, and currently we can't attempt to fix the core of the compiler
1524-
* risk stability a few weeks before the final release.
1525-
*
1526-
* However we do need to fix this for runtime reflection, since it's not something we'd like to
1527-
* expose to reflection users. Therefore a proposed solution is to check whether we're in a
1528-
* runtime reflection universe and if yes then to commence initialization.
1529-
*/
1530-
protected def needsInitialize(isFlagRelated: Boolean, mask: Long) =
1531-
!isInitialized && (flags & LOCKED) == 0 && shouldTriggerCompleter(this, if (infos ne null) infos.info else null, isFlagRelated, mask)
1532-
15331535
/** Was symbol's type updated during given phase? */
15341536
final def hasTypeAt(pid: Phase#Id): Boolean = {
15351537
assert(isCompilerUniverse)
@@ -1688,7 +1690,8 @@ trait Symbols extends api.Symbols { self: SymbolTable =>
16881690
* the annotations attached to member a definition (class, method, type, field).
16891691
*/
16901692
def annotations: List[AnnotationInfo] = {
1691-
if (!isCompilerUniverse && needsInitialize(isFlagRelated = false, mask = 0)) initialize
1693+
// See `getFlag` to learn more about the `isThreadsafe` call in the body of this method.
1694+
if (!isCompilerUniverse && !isThreadsafe(purpose = AllOps)) initialize
16921695
_annotations
16931696
}
16941697

@@ -3512,6 +3515,17 @@ trait Symbols extends api.Symbols { self: SymbolTable =>
35123515

35133516
Statistics.newView("#symbols")(ids)
35143517

3518+
3519+
// -------------- Completion --------------------------------------------------------
3520+
3521+
// is used to differentiate levels of thread-safety in `Symbol.isThreadsafe`
3522+
case class SymbolOps(isFlagRelated: Boolean, mask: Long)
3523+
val AllOps = SymbolOps(isFlagRelated = false, mask = 0L)
3524+
def FlagOps(mask: Long) = SymbolOps(isFlagRelated = true, mask = mask)
3525+
3526+
private def relevantSymbols(syms: Seq[Symbol]) = syms.flatMap(sym => List(sym, sym.moduleClass, sym.sourceModule))
3527+
def markFlagsCompleted(syms: Symbol*)(mask: Long): Unit = relevantSymbols(syms).foreach(_.markFlagsCompleted(mask))
3528+
def markAllCompleted(syms: Symbol*): Unit = relevantSymbols(syms).foreach(_.markAllCompleted)
35153529
}
35163530

35173531
object SymbolsStats {

0 commit comments

Comments
 (0)