|
| 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