Skip to content

Commit 5caa6aa

Browse files
authored
Check safe initialization of static objects (#16970)
The problem is illustrated by the example below: ``` Scala class Foo(val opposite: Foo) case object A extends Foo(B) // A -> B case object B extends Foo(A) // B -> A ``` The check aims to be simple for programmers to understand, expressive, fast, and sound. The check is centered around two design ideas: (1) initialization-time irrelevance; (2) partial ordering. The check enforces the principle of _initialization-time irrelevance_, which means that the time when a static object is initialized should not change program semantics. For that purpose, it enforces the following rule: > **The initialization of a static object should not directly or indirectly read or write mutable state owned by another static object**. This principle not only puts the initialization of static objects on a solid foundation but also avoids whole-program analysis. Partial ordering means that the initialization dependencies of static objects form a directed-acyclic graph (DAG). No cycles with length bigger than 1 allowed --- which might lead to deadlocks in the presence of concurrency and strong coupling & subtle contracts between objects. Related Issues: #16152 #9176 #11262 scala/bug#9312 scala/bug#9115 scala/bug#9261 scala/bug#5366 scala/bug#9360
2 parents 07723d7 + ce093cb commit 5caa6aa

File tree

72 files changed

+2250
-48
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

72 files changed

+2250
-48
lines changed

compiler/src/dotty/tools/dotc/config/ScalaSettings.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -381,6 +381,7 @@ private sealed trait YSettings:
381381
val YnoKindPolymorphism: Setting[Boolean] = BooleanSetting("-Yno-kind-polymorphism", "Disable kind polymorphism.")
382382
val YexplicitNulls: Setting[Boolean] = BooleanSetting("-Yexplicit-nulls", "Make reference types non-nullable. Nullable types can be expressed with unions: e.g. String|Null.")
383383
val YcheckInit: Setting[Boolean] = BooleanSetting("-Ysafe-init", "Ensure safe initialization of objects")
384+
val YcheckInitGlobal: Setting[Boolean] = BooleanSetting("-Ysafe-init-global", "Check safe initialization of global objects")
384385
val YrequireTargetName: Setting[Boolean] = BooleanSetting("-Yrequire-targetName", "Warn if an operator is defined without a @targetName annotation")
385386
val YrecheckTest: Setting[Boolean] = BooleanSetting("-Yrecheck-test", "Run basic rechecking (internal test only)")
386387
val YccDebug: Setting[Boolean] = BooleanSetting("-Ycc-debug", "Used in conjunction with captureChecking language import, debug info for captured references")
@@ -398,4 +399,3 @@ private sealed trait YSettings:
398399

399400
val YforceInlineWhileTyping: Setting[Boolean] = BooleanSetting("-Yforce-inline-while-typing", "Make non-transparent inline methods inline when typing. Emulates the old inlining behavior of 3.0.0-M3.")
400401
end YSettings
401-

compiler/src/dotty/tools/dotc/core/Definitions.scala

+5
Original file line numberDiff line numberDiff line change
@@ -1047,6 +1047,11 @@ class Definitions {
10471047

10481048
@tu lazy val JavaRepeatableAnnot: ClassSymbol = requiredClass("java.lang.annotation.Repeatable")
10491049

1050+
// Initialization annotations
1051+
@tu lazy val InitModule: Symbol = requiredModule("scala.annotation.init")
1052+
@tu lazy val InitWidenAnnot: ClassSymbol = InitModule.requiredClass("widen")
1053+
@tu lazy val InitRegionMethod: Symbol = InitModule.requiredMethod("region")
1054+
10501055
// A list of meta-annotations that are relevant for fields and accessors
10511056
@tu lazy val NonBeanMetaAnnots: Set[Symbol] =
10521057
Set(FieldMetaAnnot, GetterMetaAnnot, ParamMetaAnnot, SetterMetaAnnot, CompanionClassMetaAnnot, CompanionMethodMetaAnnot)

compiler/src/dotty/tools/dotc/core/Symbols.scala

+2-1
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,8 @@ object Symbols {
8383
ctx.settings.YretainTrees.value ||
8484
denot.owner.isTerm || // no risk of leaking memory after a run for these
8585
denot.isOneOf(InlineOrProxy) || // need to keep inline info
86-
ctx.settings.YcheckInit.value // initialization check
86+
ctx.settings.YcheckInit.value || // initialization check
87+
ctx.settings.YcheckInitGlobal.value
8788

8889
/** The last denotation of this symbol */
8990
private var lastDenot: SymDenotation = _

compiler/src/dotty/tools/dotc/core/Types.scala

+9
Original file line numberDiff line numberDiff line change
@@ -376,6 +376,15 @@ object Types {
376376
case _ => false
377377
}
378378

379+
/** Returns the annotation that is an instance of `cls` carried by the type. */
380+
@tailrec final def getAnnotation(cls: ClassSymbol)(using Context): Option[Annotation] = stripTypeVar match {
381+
case AnnotatedType(tp, annot) =>
382+
if annot.matches(cls) then Some(annot)
383+
else tp.getAnnotation(cls)
384+
case _ =>
385+
None
386+
}
387+
379388
/** Does this type have a supertype with an annotation satisfying given predicate `p`? */
380389
def derivesAnnotWith(p: Annotation => Boolean)(using Context): Boolean = this match {
381390
case tp: AnnotatedType => p(tp.annot) || tp.parent.derivesAnnotWith(p)

compiler/src/dotty/tools/dotc/transform/init/Checker.scala

+6-2
Original file line numberDiff line numberDiff line change
@@ -28,15 +28,19 @@ class Checker extends Phase:
2828
override val runsAfter = Set(Pickler.name)
2929

3030
override def isEnabled(using Context): Boolean =
31-
super.isEnabled && ctx.settings.YcheckInit.value
31+
super.isEnabled && (ctx.settings.YcheckInit.value || ctx.settings.YcheckInitGlobal.value)
3232

3333
override def runOn(units: List[CompilationUnit])(using Context): List[CompilationUnit] =
3434
val checkCtx = ctx.fresh.setPhase(this.start)
3535
val traverser = new InitTreeTraverser()
3636
units.foreach { unit => traverser.traverse(unit.tpdTree) }
3737
val classes = traverser.getClasses()
3838

39-
Semantic.checkClasses(classes)(using checkCtx)
39+
if ctx.settings.YcheckInit.value then
40+
Semantic.checkClasses(classes)(using checkCtx)
41+
42+
if ctx.settings.YcheckInitGlobal.value then
43+
Objects.checkClasses(classes)(using checkCtx)
4044

4145
units
4246

0 commit comments

Comments
 (0)