summaryrefslogtreecommitdiff
path: root/src/compiler/scala/tools/nsc/backend/jvm/BCodeAsmCommon.scala
blob: 93f5159f89257fe0b84c248864c16de5198dc8f3 (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
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
/* NSC -- new Scala compiler
 * Copyright 2005-2014 LAMP/EPFL
 * @author  Martin Odersky
 */

package scala.tools.nsc
package backend.jvm

import scala.tools.nsc.Global
import scala.tools.nsc.backend.jvm.BTypes.{InternalName, MethodInlineInfo, InlineInfo}
import BackendReporting.ClassSymbolInfoFailureSI9111
import scala.tools.asm

/**
 * This trait contains code shared between GenBCode and GenASM that depends on types defined in
 * the compiler cake (Global).
 */
final class BCodeAsmCommon[G <: Global](val global: G) {
  import global._
  import definitions._

  val ExcludedForwarderFlags = {
    import scala.tools.nsc.symtab.Flags._
    // Should include DEFERRED but this breaks findMember.
    SPECIALIZED | LIFTED | PROTECTED | STATIC | EXPANDEDNAME | BridgeAndPrivateFlags | MACRO
  }

  /**
   * True for classes generated by the Scala compiler that are considered top-level in terms of
   * the InnerClass / EnclosingMethod classfile attributes. See comment in BTypes.
   */
  def considerAsTopLevelImplementationArtifact(classSym: Symbol) = {
    classSym.isImplClass || classSym.isSpecialized
  }

  /**
   * Cache the value of delambdafy == "inline" for each run. We need to query this value many
   * times, so caching makes sense.
   */
  object delambdafyInline {
    private var runId = -1
    private var value = false

    def apply(): Boolean = {
      if (runId != global.currentRunId) {
        runId = global.currentRunId
        value = settings.Ydelambdafy.value == "inline"
      }
      value
    }
  }

  /**
   * True if `classSym` is an anonymous class or a local class. I.e., false if `classSym` is a
   * member class. This method is used to decide if we should emit an EnclosingMethod attribute.
   * It is also used to decide whether the "owner" field in the InnerClass attribute should be
   * null.
   */
  def isAnonymousOrLocalClass(classSym: Symbol): Boolean = {
    assert(classSym.isClass, s"not a class: $classSym")
    val r = exitingPickler(classSym.isAnonymousClass) || !classSym.originalOwner.isClass
    if (r && settings.Ybackend.value == "GenBCode") {
      // this assertion only holds in GenBCode. lambda lift renames symbols and may accidentally
      // introduce `$lambda` into a class name, making `isDelambdafyFunction` true. under GenBCode
      // we prevent this, see `nonAnon` in LambdaLift.
      // phase travel necessary: after flatten, the name includes the name of outer classes.
      // if some outer name contains $lambda, a non-lambda class is considered lambda.
      assert(exitingPickler(!classSym.isDelambdafyFunction), classSym.name)
    }
    r
  }

  /**
   * The next enclosing definition in the source structure. Includes anonymous function classes
   * under delambdafy:inline, even though they are only generated during UnCurry.
   */
  def nextEnclosing(sym: Symbol): Symbol = {
    val origOwner = sym.originalOwner
    // phase travel necessary: after flatten, the name includes the name of outer classes.
    // if some outer name contains $anon, a non-anon class is considered anon.
    if (delambdafyInline() && sym.rawowner.isAnonymousFunction) {
      // SI-9105: special handling for anonymous functions under delambdafy:inline.
      //
      //   class C { def t = () => { def f { class Z } } }
      //
      //   class C { def t = byNameMethod { def f { class Z } } }
      //
      // In both examples, the method f lambda-lifted into the anonfun class.
      //
      // In both examples, the enclosing method of Z is f, the enclosing class is the anonfun.
      // So nextEnclosing needs to return the following chain:  Z - f - anonFunClassSym - ...
      //
      // In the first example, the initial owner of f is a TermSymbol named "$anonfun" (note: not the anonFunClassSym!)
      // In the second, the initial owner of f is t (no anon fun term symbol for by-name args!).
      //
      // In both cases, the rawowner of class Z is the anonFunClassSym. So the check in the `if`
      // above makes sure we don't jump over the anonymous function in the by-name argument case.
      //
      // However, we cannot directly return the rawowner: if `sym` is Z, we need to include method f
      // in the result. This is done by comparing the rawowners (read: lambdalift-targets) of `sym`
      // and `sym.originalOwner`: if they are the same, then the originalOwner is "in between", and
      // we need to return it.
      // If the rawowners are different, the symbol was not in between. In the first example, the
      // originalOwner of `f` is the anonfun-term-symbol, whose rawowner is C. So the nextEnclosing
      // of `f` is its rawowner, the anonFunClassSym.
      //
      // In delambdafy:method we don't have that problem. The f method is lambda-lifted into C,
      // not into the anonymous function class. The originalOwner chain is Z - f - C.
      if (sym.originalOwner.rawowner == sym.rawowner) sym.originalOwner
      else sym.rawowner
    } else {
      origOwner
    }
  }

  def nextEnclosingClass(sym: Symbol): Symbol = {
    if (sym.isClass) sym
    else nextEnclosingClass(nextEnclosing(sym))
  }

  def classOriginallyNestedInClass(nestedClass: Symbol, enclosingClass: Symbol) ={
    nextEnclosingClass(nextEnclosing(nestedClass)) == enclosingClass
  }

  /**
   * Returns the enclosing method for non-member classes. In the following example
   *
   * class A {
   *   def f = {
   *     class B {
   *       class C
   *     }
   *   }
   * }
   *
   * the method returns Some(f) for B, but None for C, because C is a member class. For non-member
   * classes that are not enclosed by a method, it returns None:
   *
   * class A {
   *   { class B }
   * }
   *
   * In this case, for B, we return None.
   *
   * The EnclosingMethod attribute needs to be added to non-member classes (see doc in BTypes).
   * This is a source-level property, so we need to use the originalOwner chain to reconstruct it.
   */
  private def enclosingMethodForEnclosingMethodAttribute(classSym: Symbol): Option[Symbol] = {
    assert(classSym.isClass, classSym)

    def doesNotExist(method: Symbol) = {
      // (1) SI-9124, some trait methods don't exist in the generated interface. see comment in BTypes.
      // (2) Value classes. Member methods of value classes exist in the generated box class. However,
      //     nested methods lifted into a value class are moved to the companion object and don't exist
      //     in the value class itself. We can identify such nested methods: the initial enclosing class
      //     is a value class, but the current owner is some other class (the module class).
      method.owner.isTrait && method.isImplOnly || { // (1)
        val enclCls = nextEnclosingClass(method)
        exitingPickler(enclCls.isDerivedValueClass) && method.owner != enclCls // (2)
      }
    }

    def enclosingMethod(sym: Symbol): Option[Symbol] = {
      if (sym.isClass || sym == NoSymbol) None
      else if (sym.isMethod) {
        if (doesNotExist(sym)) None else Some(sym)
      }
      else enclosingMethod(nextEnclosing(sym))
    }
    enclosingMethod(nextEnclosing(classSym))
  }

  /**
   * The enclosing class for emitting the EnclosingMethod attribute. Since this is a source-level
   * property, this method looks at the originalOwner chain. See doc in BTypes.
   */
  private def enclosingClassForEnclosingMethodAttribute(classSym: Symbol): Symbol = {
    assert(classSym.isClass, classSym)
    val r = nextEnclosingClass(nextEnclosing(classSym))
    // this should be an assertion, but we are more cautious for now as it was introduced before the 2.11.6 minor release
    if (considerAsTopLevelImplementationArtifact(r)) devWarning(s"enclosing class of $classSym should not be an implementation artifact class: $r")
    r
  }

  final case class EnclosingMethodEntry(owner: String, name: String, methodDescriptor: String)

  /**
   * Data for emitting an EnclosingMethod attribute. None if `classSym` is a member class (not
   * an anonymous or local class). See doc in BTypes.
   *
   * The class is parametrized by two functions to obtain a bytecode class descriptor for a class
   * symbol, and to obtain a method signature descriptor fro a method symbol. These function depend
   * on the implementation of GenASM / GenBCode, so they need to be passed in.
   */
  def enclosingMethodAttribute(classSym: Symbol, classDesc: Symbol => String, methodDesc: Symbol => String): Option[EnclosingMethodEntry] = {
    // trait impl classes are always top-level, see comment in BTypes
    if (isAnonymousOrLocalClass(classSym) && !considerAsTopLevelImplementationArtifact(classSym)) {
      val enclosingClass = enclosingClassForEnclosingMethodAttribute(classSym)
      val methodOpt = enclosingMethodForEnclosingMethodAttribute(classSym) match {
        case some @ Some(m) =>
          if (m.owner != enclosingClass) {
            // This should never happen. In case it does, it prevents emitting an invalid
            // EnclosingMethod attribute: if the attribute specifies an enclosing method,
            // it needs to exist in the specified enclosing class.
            devWarning(s"the owner of the enclosing method ${m.locationString} should be the same as the enclosing class $enclosingClass")
            None
          } else some
        case none => none
      }
      Some(EnclosingMethodEntry(
        classDesc(enclosingClass),
        methodOpt.map(_.javaSimpleName.toString).orNull,
        methodOpt.map(methodDesc).orNull))
    } else {
      None
    }
  }

  /**
   * This is basically a re-implementation of sym.isStaticOwner, but using the originalOwner chain.
   *
   * The problem is that we are interested in a source-level property. Various phases changed the
   * symbol's properties in the meantime, mostly lambdalift modified (destructively) the owner.
   * Therefore, `sym.isStatic` is not what we want. For example, in
   *   object T { def f { object U } }
   * the owner of U is T, so UModuleClass.isStatic is true. Phase travel does not help here.
   */
  def isOriginallyStaticOwner(sym: Symbol): Boolean = {
    sym.isPackageClass || sym.isModuleClass && isOriginallyStaticOwner(sym.originalOwner)
  }

  /**
   * Reconstruct the classfile flags from a Java defined class symbol.
   *
   * The implementation of this method is slightly different that `javaFlags` in BTypesFromSymbols.
   * The javaFlags method is primarily used to map Scala symbol flags to sensible classfile flags
   * that are used in the generated classfiles. For example, all classes emitted by the Scala
   * compiler have ACC_PUBLIC.
   *
   * When building a [[ClassBType]] from a Java class symbol, the flags in the type's `info` have
   * to correspond exactly to the flags in the classfile. For example, if the class is package
   * protected (i.e., it doesn't have the ACC_PUBLIC flag), this needs to be reflected in the
   * ClassBType. For example, the inliner needs the correct flags for access checks.
   *
   * Class flags are listed here:
   *   https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.1-200-E.1
   */
  def javaClassfileFlags(classSym: Symbol): Int = {
    assert(classSym.isJava, s"Expected Java class symbol, got ${classSym.fullName}")
    import asm.Opcodes._
    def enumFlags = ACC_ENUM | {
      // Java enums have the `ACC_ABSTRACT` flag if they have a deferred method.
      // We cannot trust `hasAbstractFlag`: the ClassfileParser adds `ABSTRACT` and `SEALED` to all
      // Java enums for exhaustiveness checking.
      val hasAbstractMethod = classSym.info.decls.exists(s => s.isMethod && s.isDeferred)
      if (hasAbstractMethod) ACC_ABSTRACT else 0
    }
    GenBCode.mkFlags(
      // SI-9393: the classfile / java source parser make java annotation symbols look like classes.
      // here we recover the actual classfile flags.
      if (classSym.hasJavaAnnotationFlag)                        ACC_ANNOTATION | ACC_INTERFACE | ACC_ABSTRACT else 0,
      if (classSym.isPublic)                                     ACC_PUBLIC    else 0,
      if (classSym.isFinal)                                      ACC_FINAL     else 0,
      // see the link above. javac does the same: ACC_SUPER for all classes, but not interfaces.
      if (classSym.isInterface)                                  ACC_INTERFACE else ACC_SUPER,
      // for Java enums, we cannot trust `hasAbstractFlag` (see comment in enumFlags)
      if (!classSym.hasJavaEnumFlag && classSym.hasAbstractFlag) ACC_ABSTRACT  else 0,
      if (classSym.isArtifact)                                   ACC_SYNTHETIC else 0,
      if (classSym.hasJavaEnumFlag)                              enumFlags     else 0
    )
  }

  /**
   * The member classes of a class symbol. Note that the result of this method depends on the
   * current phase, for example, after lambdalift, all local classes become member of the enclosing
   * class.
   *
   * Impl classes are always considered top-level, see comment in BTypes.
   */
  def memberClassesForInnerClassTable(classSymbol: Symbol): List[Symbol] = classSymbol.info.decls.collect({
    case sym if sym.isClass && !considerAsTopLevelImplementationArtifact(sym) =>
      sym
    case sym if sym.isModule && !considerAsTopLevelImplementationArtifact(sym) => // impl classes get the lateMODULE flag in mixin
      val r = exitingPickler(sym.moduleClass)
      assert(r != NoSymbol, sym.fullLocationString)
      r
  })(collection.breakOut)

  lazy val AnnotationRetentionPolicyModule       = AnnotationRetentionPolicyAttr.companionModule
  lazy val AnnotationRetentionPolicySourceValue  = AnnotationRetentionPolicyModule.tpe.member(TermName("SOURCE"))
  lazy val AnnotationRetentionPolicyClassValue   = AnnotationRetentionPolicyModule.tpe.member(TermName("CLASS"))
  lazy val AnnotationRetentionPolicyRuntimeValue = AnnotationRetentionPolicyModule.tpe.member(TermName("RUNTIME"))

  /** Whether an annotation should be emitted as a Java annotation
    * .initialize: if 'annot' is read from pickle, atp might be uninitialized
    */
  def shouldEmitAnnotation(annot: AnnotationInfo) = {
    annot.symbol.initialize.isJavaDefined &&
      annot.matches(ClassfileAnnotationClass) &&
      retentionPolicyOf(annot) != AnnotationRetentionPolicySourceValue &&
      annot.args.isEmpty
  }

  def isRuntimeVisible(annot: AnnotationInfo): Boolean = {
    annot.atp.typeSymbol.getAnnotation(AnnotationRetentionAttr) match {
      case Some(retentionAnnot) =>
        retentionAnnot.assocs.contains(nme.value -> LiteralAnnotArg(Constant(AnnotationRetentionPolicyRuntimeValue)))
      case _ =>
        // SI-8926: if the annotation class symbol doesn't have a @RetentionPolicy annotation, the
        // annotation is emitted with visibility `RUNTIME`
        true
    }
  }

  private def retentionPolicyOf(annot: AnnotationInfo): Symbol =
    annot.atp.typeSymbol.getAnnotation(AnnotationRetentionAttr).map(_.assocs).flatMap(assoc =>
      assoc.collectFirst {
        case (`nme`.value, LiteralAnnotArg(Constant(value: Symbol))) => value
      }).getOrElse(AnnotationRetentionPolicyClassValue)

  def implementedInterfaces(classSym: Symbol): List[Symbol] = {
    // Additional interface parents based on annotations and other cues
    def newParentForAnnotation(ann: AnnotationInfo): Option[Type] = ann.symbol match {
      case RemoteAttr => Some(RemoteInterfaceClass.tpe)
      case _          => None
    }

    // SI-9393: java annotations are interfaces, but the classfile / java source parsers make them look like classes.
    def isInterfaceOrTrait(sym: Symbol) = sym.isInterface || sym.isTrait || sym.hasJavaAnnotationFlag

    val classParents = {
      val parents = classSym.info.parents
      // SI-9393: the classfile / java source parsers add Annotation and ClassfileAnnotation to the
      // parents of a java annotations. undo this for the backend (where we need classfile-level information).
      if (classSym.hasJavaAnnotationFlag) parents.filterNot(c => c.typeSymbol == ClassfileAnnotationClass || c.typeSymbol == AnnotationClass)
      else parents
    }

    val allParents = classParents ++ classSym.annotations.flatMap(newParentForAnnotation)

    // We keep the superClass when computing minimizeParents to eliminate more interfaces.
    // Example: T can be eliminated from D
    //   trait T
    //   class C extends T
    //   class D extends C with T
    val interfaces = erasure.minimizeParents(allParents) match {
      case superClass :: ifs if !isInterfaceOrTrait(superClass.typeSymbol) =>
        ifs
      case ifs =>
        // minimizeParents removes the superclass if it's redundant, for example:
        //  trait A
        //  class C extends Object with A  // minimizeParents removes Object
        ifs
    }
    interfaces.map(_.typeSymbol)
  }

  /**
   * This is a hack to work around SI-9111. The completer of `methodSym` may report type errors. We
   * cannot change the typer context of the completer at this point and make it silent: the context
   * captured when creating the completer in the namer. However, we can temporarily replace
   * global.reporter (it's a var) to store errors.
   */
  def completeSilentlyAndCheckErroneous(sym: Symbol): Boolean = {
    if (sym.hasCompleteInfo) false
    else {
      val originalReporter = global.reporter
      val storeReporter = new reporters.StoreReporter()
      global.reporter = storeReporter
      try {
        sym.info
      } finally {
        global.reporter = originalReporter
      }
      sym.isErroneous
    }
  }

  /**
   * Build the [[InlineInfo]] for a class symbol.
   */
  def buildInlineInfoFromClassSymbol(classSym: Symbol, classSymToInternalName: Symbol => InternalName, methodSymToDescriptor: Symbol => String): InlineInfo = {
    val traitSelfType = if (classSym.isTrait && !classSym.isImplClass) {
      // The mixin phase uses typeOfThis for the self parameter in implementation class methods.
      val selfSym = classSym.typeOfThis.typeSymbol
      if (selfSym != classSym) Some(classSymToInternalName(selfSym)) else None
    } else {
      None
    }

    val isEffectivelyFinal = classSym.isEffectivelyFinal

    var warning = Option.empty[ClassSymbolInfoFailureSI9111]

    // Primitive methods cannot be inlined, so there's no point in building a MethodInlineInfo. Also, some
    // primitive methods (e.g., `isInstanceOf`) have non-erased types, which confuses [[typeToBType]].
    val methodInlineInfos = classSym.info.decls.iterator.filter(m => m.isMethod && !scalaPrimitives.isPrimitive(m)).flatMap({
      case methodSym =>
        if (completeSilentlyAndCheckErroneous(methodSym)) {
          // Happens due to SI-9111. Just don't provide any MethodInlineInfo for that method, we don't need fail the compiler.
          if (!classSym.isJavaDefined) devWarning("SI-9111 should only be possible for Java classes")
          warning = Some(ClassSymbolInfoFailureSI9111(classSym.fullName))
          None
        } else {
          val name      = methodSym.javaSimpleName.toString // same as in genDefDef
          val signature = name + methodSymToDescriptor(methodSym)

          // Some detours are required here because of changing flags (lateDEFERRED, lateMODULE):
          // 1. Why the phase travel? Concrete trait methods obtain the lateDEFERRED flag in Mixin.
          //    This makes isEffectivelyFinalOrNotOverridden false, which would prevent non-final
          //    but non-overridden methods of sealed traits from being inlined.
          // 2. Why the special case for `classSym.isImplClass`? Impl class symbols obtain the
          //    lateMODULE flag during Mixin. During the phase travel to exitingPickler, the late
          //    flag is ignored. The members are therefore not isEffectivelyFinal (their owner
          //    is not a module). Since we know that all impl class members are static, we can
          //    just take the shortcut.
          val effectivelyFinal = classSym.isImplClass || exitingPickler(methodSym.isEffectivelyFinalOrNotOverridden)

          // Identify trait interface methods that have a static implementation in the implementation
          // class. Invocations of these methods can be re-wrired directly to the static implementation
          // if they are final or the receiver is known.
          //
          // Using `erasure.needsImplMethod` is not enough: it keeps field accessors, module getters
          // and super accessors. When AddInterfaces creates the impl class, these methods are
          // initially added to it.
          //
          // The mixin phase later on filters out most of these members from the impl class (see
          // Mixin.isImplementedStatically). However, accessors for concrete lazy vals remain in the
          // impl class after mixin. So the filter in mixin is not exactly what we need here (we
          // want to identify concrete trait methods, not any accessors). So we check some symbol
          // properties manually.
          val traitMethodWithStaticImplementation = {
            import symtab.Flags._
            classSym.isTrait && !classSym.isImplClass &&
              erasure.needsImplMethod(methodSym) &&
              !methodSym.isModule &&
              !(methodSym hasFlag (ACCESSOR | SUPERACCESSOR))
          }

          val info = MethodInlineInfo(
            effectivelyFinal                    = effectivelyFinal,
            traitMethodWithStaticImplementation = traitMethodWithStaticImplementation,
            annotatedInline                     = methodSym.hasAnnotation(ScalaInlineClass),
            annotatedNoInline                   = methodSym.hasAnnotation(ScalaNoInlineClass)
          )
          Some((signature, info))
        }
    }).toMap

    InlineInfo(traitSelfType, isEffectivelyFinal, methodInlineInfos, warning)
  }
}

object BCodeAsmCommon {
  /**
   * Valid flags for InnerClass attribute entry.
   * See http://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.7.6
   */
  val INNER_CLASSES_FLAGS = {
    asm.Opcodes.ACC_PUBLIC   | asm.Opcodes.ACC_PRIVATE   | asm.Opcodes.ACC_PROTECTED  |
      asm.Opcodes.ACC_STATIC   | asm.Opcodes.ACC_FINAL     | asm.Opcodes.ACC_INTERFACE  |
      asm.Opcodes.ACC_ABSTRACT | asm.Opcodes.ACC_SYNTHETIC | asm.Opcodes.ACC_ANNOTATION |
      asm.Opcodes.ACC_ENUM
  }
}