summaryrefslogtreecommitdiff
path: root/src/reflect/scala/reflect/runtime/SymbolLoaders.scala
blob: 9ce6331e33a277eb113457d5742a49a2b28aaec5 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
package scala
package reflect
package runtime

import internal.Flags
import java.lang.{Class => jClass, Package => jPackage}
import scala.collection.mutable
import scala.reflect.runtime.ReflectionUtils.scalacShouldntLoadClass
import scala.reflect.internal.Flags._

private[reflect] trait SymbolLoaders { self: SymbolTable =>

  /** The standard completer for top-level classes
   *  @param clazz   The top-level class
   *  @param module  The companion object of `clazz`
   *  Calling `complete` on this type will assign the infos of `clazz` and `module`
   *  by unpickling information from the corresponding Java class. If no Java class
   *  is found, a package is created instead.
   */
  class TopClassCompleter(clazz: Symbol, module: Symbol) extends SymLoader with FlagAssigningCompleter {
    markFlagsCompleted(clazz, module)(mask = ~TopLevelPickledFlags)
    override def complete(sym: Symbol) = {
      debugInfo("completing "+sym+"/"+clazz.fullName)
      assert(sym == clazz || sym == module || sym == module.moduleClass)
      slowButSafeEnteringPhaseNotLaterThan(picklerPhase) {
        val loadingMirror = mirrorThatLoaded(sym)
        val javaClass = loadingMirror.javaClass(clazz.javaClassName)
        loadingMirror.unpickleClass(clazz, module, javaClass)
        // NOTE: can't mark as thread-safe here, because unpickleClass might decide to delegate to FromJavaClassCompleter
        // if (!isCompilerUniverse) markAllCompleted(clazz, module)
      }
    }
    override def load(sym: Symbol) = complete(sym)
  }

  /** Create a class and a companion object, enter in enclosing scope,
   *  and initialize with a lazy type completer.
   *  @param owner   The owner of the newly created class and object
   *  @param name    The simple name of the newly created class
   *  @param completer  The completer to be used to set the info of the class and the module
   */
  protected def initAndEnterClassAndModule(owner: Symbol, name: TypeName, completer: (Symbol, Symbol) => LazyType) = {
    assert(!(name.toString endsWith "[]"), name)
    val clazz = owner.newClass(name)
    val module = owner.newModule(name.toTermName)
    // without this check test/files/run/t5256g and test/files/run/t5256h will crash
    // todo. reflection meeting verdict: need to enter the symbols into the first symbol in the owner chain that has a non-empty scope
    if (owner.info.decls != EmptyScope) {
      owner.info.decls enter clazz
      owner.info.decls enter module
    }
    initClassAndModule(clazz, module, completer(clazz, module))
    (clazz, module)
  }

  protected def setAllInfos(clazz: Symbol, module: Symbol, info: Type) = {
    List(clazz, module, module.moduleClass) foreach (_ setInfo info)
  }

  protected def initClassAndModule(clazz: Symbol, module: Symbol, completer: LazyType) =
    setAllInfos(clazz, module, completer)

  /** The type completer for packages.
   */
  class LazyPackageType extends LazyType with FlagAgnosticCompleter {
    override def complete(sym: Symbol) {
      assert(sym.isPackageClass)
      // Time travel to a phase before refchecks avoids an initialization issue. `openPackageModule`
      // creates a module symbol and invokes invokes `companionModule` while the `infos` field is
      // still null. This calls `isModuleNotMethod`, which forces the `info` if run after refchecks.
      slowButSafeEnteringPhaseNotLaterThan(picklerPhase) {
        sym setInfo new ClassInfoType(List(), new PackageScope(sym), sym)
        // override def safeToString = pkgClass.toString
        openPackageModule(sym)
        markAllCompleted(sym)
      }
    }
  }


  // Since runtime reflection doesn't have a luxury of enumerating all classes
  // on the classpath, it has to materialize symbols for top-level definitions
  // (packages, classes, objects) on demand.
  //
  // Someone asks us for a class named `foo.Bar`? Easy. Let's speculatively create
  // a package named `foo` and then look up `newTypeName("bar")` in its decls.
  // This lookup, implemented in `SymbolLoaders.PackageScope` tests the waters by
  // trying to to `Class.forName("foo.Bar")` and then creates a ClassSymbol upon
  // success (the whole story is a bit longer, but the rest is irrelevant here).
  //
  // That's all neat, but these non-deterministic mutations of the global symbol
  // table give a lot of trouble in multi-threaded setting. One of the popular
  // reflection crashes happens when multiple threads happen to trigger symbol
  // materialization multiple times for the same symbol, making subsequent
  // reflective operations stumble upon outrageous stuff like overloaded packages.
  //
  // Short of significantly changing SymbolLoaders I see no other way than just
  // to slap a global lock on materialization in runtime reflection.
  class PackageScope(pkgClass: Symbol) extends Scope
      with SynchronizedScope {
    assert(pkgClass.isType)

    // materializing multiple copies of the same symbol in PackageScope is a very popular bug
    // this override does its best to guard against it
    override def enter[T <: Symbol](sym: T): T = {
      // workaround for SI-7728
      if (isCompilerUniverse) super.enter(sym)
      else {
        val existing = super.lookupEntry(sym.name)
        def eitherIsMethod(sym1: Symbol, sym2: Symbol) = sym1.isMethod || sym2.isMethod
        assert(existing == null || eitherIsMethod(existing.sym, sym), s"pkgClass = $pkgClass, sym = $sym, existing = $existing")
        super.enter(sym)
      }
    }

    override def enterIfNew[T <: Symbol](sym: T): T = {
      val existing = super.lookupEntry(sym.name)
      if (existing == null) enter(sym)
      else existing.sym.asInstanceOf[T]
    }

    // package scopes need to synchronize on the GIL
    // because lookupEntry might cause changes to the global symbol table
    override def syncLockSynchronized[T](body: => T): T = gilSynchronized(body)
    private val negatives = new mutable.HashSet[Name]
    override def lookupEntry(name: Name): ScopeEntry = syncLockSynchronized {
      val e = super.lookupEntry(name)
      if (e != null)
        e
      else if (scalacShouldntLoadClass(name) || (negatives contains name))
        null
      else {
        val path =
          if (pkgClass.isEmptyPackageClass) name.toString
          else pkgClass.fullName + "." + name
        val currentMirror = mirrorThatLoaded(pkgClass)
        currentMirror.tryJavaClass(path) match {
          case Some(cls) =>
            val loadingMirror = currentMirror.mirrorDefining(cls)
            val (_, module) =
              if (loadingMirror eq currentMirror) {
                initAndEnterClassAndModule(pkgClass, name.toTypeName, new TopClassCompleter(_, _))
              } else {
                val origOwner = loadingMirror.packageNameToScala(pkgClass.fullName)
                val clazz = origOwner.info decl name.toTypeName
                val module = origOwner.info decl name.toTermName
                assert(clazz != NoSymbol)
                assert(module != NoSymbol)
                // currentMirror.mirrorDefining(cls) might side effect by entering symbols into pkgClass.info.decls
                // therefore, even though in the beginning of this method, super.lookupEntry(name) returned null
                // entering clazz/module now will result in a double-enter assertion in PackageScope.enter
                // here's how it might happen
                // 1) we are the rootMirror
                // 2) cls.getClassLoader is different from our classloader
                // 3) mirrorDefining(cls) looks up a mirror corresponding to that classloader and cannot find it
                // 4) mirrorDefining creates a new mirror
                // 5) that triggers Mirror.init() of the new mirror
                // 6) that triggers definitions.syntheticCoreClasses
                // 7) that might materialize symbols and enter them into our scope (because syntheticCoreClasses live in rootMirror)
                // 8) now we come back here and try to enter one of the now entered symbols => BAM!
                // therefore we use enterIfNew rather than just enter
                enterIfNew(clazz)
                enterIfNew(module)
                (clazz, module)
              }
            debugInfo(s"created $module/${module.moduleClass} in $pkgClass")
            lookupEntry(name)
          case none =>
            debugInfo("*** not found : "+path)
            negatives += name
            null
        }
      }
    }
  }

  /** Assert that packages have package scopes */
  override def validateClassInfo(tp: ClassInfoType) {
    assert(!tp.typeSymbol.isPackageClass || tp.decls.isInstanceOf[PackageScope])
  }

  override def newPackageScope(pkgClass: Symbol) = new PackageScope(pkgClass)

  override def scopeTransform(owner: Symbol)(op: => Scope): Scope =
    if (owner.isPackageClass) owner.info.decls else op
}