Skip to content

Commit b787d3c

Browse files
committed
WIP - check global objects
1 parent 7694985 commit b787d3c

File tree

1 file changed

+193
-0
lines changed

1 file changed

+193
-0
lines changed
Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
package dotty.tools.dotc
2+
package transform
3+
package init
4+
5+
import core.*
6+
import Contexts.*
7+
import Symbols.*
8+
import Types.*
9+
import StdNames.*
10+
import NameKinds.OuterSelectName
11+
import NameKinds.SuperAccessorName
12+
13+
import ast.tpd.*
14+
import config.Printers.init as printer
15+
import reporting.trace as log
16+
17+
import Errors.*
18+
19+
import scala.collection.mutable
20+
import scala.annotation.tailrec
21+
22+
/** Check initialization safety of static objects
23+
*
24+
* The problem is illustrated by the example below:
25+
*
26+
* class Foo(val opposite: Foo)
27+
* case object A extends Foo(B) // A -> B
28+
* case object B extends Foo(A) // B -> A
29+
*
30+
* In the code above, the initialization of object `A` depends on `B` and vice
31+
* versa. There is no correct way to initialize the code above. The current
32+
* check issue a warning for the code above.
33+
*
34+
* At the high-level, the analysis has the following characteristics:
35+
*
36+
* 1. It is inter-procedural and flow-insensitive.
37+
*
38+
* 2. It is modular with respect to separate compilation, even incremental
39+
* compilation.
40+
*
41+
* 3. It is receiver-sensitive but not heap-sensitive -- fields are always
42+
* abstracted by types.
43+
*
44+
* 4. If the target of a virtual method call cannot be determined by its
45+
* receiver, the target is approximated by all methods of classes currently
46+
* being compiled and are instantiated. This is to some extent similar to
47+
* RTA (rapid type analysis).
48+
*
49+
* However, a class type is only added to the list when it leaks:
50+
*
51+
* - A value of the class is used as method argument.
52+
* - A value of the class is alised to a field or variable.
53+
*/
54+
object Objects:
55+
sealed abstract class Value
56+
57+
/**
58+
* Rerepsents values that are instances of the given class
59+
*
60+
* The parameter `klass` should be the concrete class of the value at runtime.
61+
*/
62+
case class OfClass(klass: ClassSymbol) extends Value
63+
64+
/**
65+
* Rerepsents values that are of the given type
66+
*/
67+
case class OfType(tp: Type) extends Value
68+
69+
object State:
70+
/**
71+
* Records the instantiated types during instantiation of a static object.
72+
*
73+
* Functions and by-name closures are called when they leak, therefore they
74+
* are not part of the instantiated types.
75+
*/
76+
class Data(allConcreteClasses: Set[ClassSymbol]):
77+
// object -> (class, types of the class)
78+
val instantiatedTypes = mutable.Map.empty[ClassSymbol, Map[ClassSymbol, List[Type]]]
79+
80+
opaque type Rep = Data
81+
82+
def init(classes: List[ClassSymbol])(using Context): Rep =
83+
val concreteClasses = classes.filter(Semantic.isConcreteClass).toSet
84+
new Data(concreteClasses)
85+
86+
type Contextual[T] = (Context, State.Rep) ?=> T
87+
88+
/** Check an individual class
89+
*
90+
* The class to be checked must be an instantiable concrete class.
91+
*/
92+
private def checkObject(classSym: ClassSymbol): Contextual[Unit] =
93+
val tpl = classSym.defTree.asInstanceOf[TypeDef].rhs.asInstanceOf[Template]
94+
init(tpl, OfClass(classSym), classSym)
95+
96+
def checkClasses(classes: List[ClassSymbol])(using Context): Unit =
97+
given State.Rep = State.init(classes)
98+
99+
for
100+
classSym <- classes
101+
if classSym.is(Flags.Module) && classSym.isStaticOwner
102+
do
103+
checkObject(classSym)
104+
105+
106+
/** Handles the evaluation of different expressions
107+
*
108+
* Note: Recursive call should go to `eval` instead of `cases`.
109+
*
110+
* @param expr The expression to be evaluated.
111+
* @param thisV The value for `C.this` where `C` is represented by the parameter `klass`.
112+
* @param klass The enclosing class where the expression `expr` is located.
113+
*/
114+
def eval(expr: Tree, thisV: Value, klass: ClassSymbol): Contextual[Value] = ???
115+
116+
def evalArgs(args: List[Arg], thisV: Value, klass: ClassSymbol): Contextual[List[Value]] =
117+
val argInfos = new mutable.ArrayBuffer[ArgInfo]
118+
args.foreach { arg =>
119+
val res =
120+
if arg.isByName then
121+
Fun(arg.tree, thisV, klass)
122+
else
123+
eval(arg.tree, thisV, klass)
124+
125+
argInfos += res
126+
}
127+
argInfos.toList
128+
129+
def init(tpl: Template, thisV: OfClass, klass: ClassSymbol): Contextual[Unit] =
130+
def superCall(tref: TypeRef, ctor: Symbol, args: List[Value]): Unit =
131+
val cls = tref.classSymbol.asClass
132+
133+
// follow constructor
134+
if cls.hasSource then thisV.callConstructor(ctor, args)
135+
136+
// parents
137+
def initParent(parent: Tree) =
138+
parent match
139+
case tree @ Block(stats, NewExpr(tref, New(tpt), ctor, argss)) => // can happen
140+
eval(stats, thisV, klass)
141+
val args = evalArgs(argss.flatten, thisV, klass)
142+
superCall(tref, ctor, args)
143+
144+
case tree @ NewExpr(tref, New(tpt), ctor, argss) => // extends A(args)
145+
val args = evalArgs(argss.flatten, thisV, klass)
146+
superCall(tref, ctor, args)
147+
148+
case _ => // extends A or extends A[T]
149+
val tref = typeRefOf(parent.tpe)
150+
superCall(tref, tref.classSymbol.primaryConstructor, Nil)
151+
152+
// see spec 5.1 about "Template Evaluation".
153+
// https://www.scala-lang.org/files/archive/spec/2.13/05-classes-and-objects.html
154+
if !klass.is(Flags.Trait) then
155+
// 1. first init parent class recursively
156+
// 2. initialize traits according to linearization order
157+
val superParent = tpl.parents.head
158+
val superCls = superParent.tpe.classSymbol.asClass
159+
initParent(superParent)
160+
161+
val parents = tpl.parents.tail
162+
val mixins = klass.baseClasses.tail.takeWhile(_ != superCls)
163+
164+
mixins.reverse.foreach { mixin =>
165+
parents.find(_.tpe.classSymbol == mixin) match
166+
case Some(parent) =>
167+
initParent(parent)
168+
case None =>
169+
// According to the language spec, if the mixin trait requires
170+
// arguments, then the class must provide arguments to it explicitly
171+
// in the parent list. That means we will encounter it in the Some
172+
// branch.
173+
//
174+
// When a trait A extends a parameterized trait B, it cannot provide
175+
// term arguments to B. That can only be done in a concrete class.
176+
val tref = typeRefOf(klass.typeRef.baseType(mixin).typeConstructor)
177+
val ctor = tref.classSymbol.primaryConstructor
178+
if ctor.exists then
179+
superCall(tref, ctor, Nil)
180+
}
181+
end if
182+
183+
// class body
184+
tpl.body.foreach {
185+
case vdef : ValDef if !vdef.symbol.is(Flags.Lazy) && !vdef.rhs.isEmpty =>
186+
// Throw the field value away, as the analysis is not heap-sensitive
187+
eval(vdef.rhs, thisV, klass)
188+
189+
case _: MemberDef =>
190+
191+
case tree =>
192+
eval(tree, thisV, klass)
193+
}

0 commit comments

Comments
 (0)