summaryrefslogtreecommitdiff
path: root/src/compiler/scala/tools/nsc/backend/jvm/BCodeAsmCommon.scala
diff options
context:
space:
mode:
authorLukas Rytz <lukas.rytz@gmail.com>2014-07-01 16:28:24 +0200
committerLukas Rytz <lukas.rytz@gmail.com>2014-09-01 14:29:17 +0200
commite3107465c3e8ac80d1cc6a759e2f3298c2531424 (patch)
tree5aacf420f8845cabed5088b2209ed1e5a9b74c51 /src/compiler/scala/tools/nsc/backend/jvm/BCodeAsmCommon.scala
parent2606bd921e434a6d8edb21f7f04dbfb10045026e (diff)
downloadscala-e3107465c3e8ac80d1cc6a759e2f3298c2531424.tar.gz
scala-e3107465c3e8ac80d1cc6a759e2f3298c2531424.tar.bz2
scala-e3107465c3e8ac80d1cc6a759e2f3298c2531424.zip
Fix InnerClass / EnclosingMethod attributes
This commit seems bigger than it is. Most of it is tests, and moving some code around. The actual changes are small, but a bit subtle. The InnerClass and EnclosingMethod attributes should now be close to the JVM spec (which is summarized in BTypes.scala). New tests make sure that changes to these attributes, and changes to the way Java reflection sees Scala classfiles, don't go unnoticed. A new file, BCodeAsmCommon, holds code that's shared between the two backend (it could hold more, future work). In general, the difficulty with emitting InnerClass / EnclosingMethod is that we need to find out source-level properties. We need to make sure to do enough phase-travelling, and work around destructive changes to the ownerchain in lambdalift (we use originalOwner a lot). The change to JavaMirrors is prompted by the change to the EnclosingMethod attribute, which changes Java reflection's answer to getEnclosingMethod and getEnclosingConstructor. Classes defined in field initializers no longer have an enclosing method, just an enclosing class, which broke an assumption in JavaMirrors. There's one change in erasure. Before this change, when an object declaration implements / overrides a method, and a bridge is required, then the bridge method was actually a ModuleSymbol (it would get the lateMETHOD flag and be emitted as a method anyway). This is confusing, when iterating through the members of a class, you can find two modules with the same name, and one of them doesn't have a module class. Now, such bridge methods will be MethodSymbols. Removed Symbol.originalEnclosingMethod, that is a backend thing and doesn't need to live in the symbol API.
Diffstat (limited to 'src/compiler/scala/tools/nsc/backend/jvm/BCodeAsmCommon.scala')
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/BCodeAsmCommon.scala130
1 files changed, 130 insertions, 0 deletions
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeAsmCommon.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeAsmCommon.scala
new file mode 100644
index 0000000000..e5b4c4a6c2
--- /dev/null
+++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeAsmCommon.scala
@@ -0,0 +1,130 @@
+/* NSC -- new Scala compiler
+ * Copyright 2005-2014 LAMP/EPFL
+ * @author Martin Odersky
+ */
+
+package scala.tools.nsc.backend.jvm
+
+import scala.tools.nsc.Global
+import PartialFunction._
+
+/**
+ * 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._
+
+ /**
+ * 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 = {
+ def isDelambdafyLambdaClass(classSym: Symbol): Boolean = {
+ classSym.isAnonymousFunction && (settings.Ydelambdafy.value == "method")
+ }
+
+ assert(classSym.isClass, s"not a class: $classSym")
+
+ !isDelambdafyLambdaClass(classSym) &&
+ (classSym.isAnonymousClass || !classSym.originalOwner.isClass)
+ }
+
+ /**
+ * 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 enclosingMethod(sym: Symbol): Option[Symbol] = {
+ if (sym.isClass || sym == NoSymbol) None
+ else if (sym.isMethod) Some(sym)
+ else enclosingMethod(sym.originalOwner)
+ }
+ enclosingMethod(classSym.originalOwner)
+ }
+
+ /**
+ * 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)
+ def enclosingClass(sym: Symbol): Symbol = {
+ if (sym.isClass) sym
+ else enclosingClass(sym.originalOwner)
+ }
+ enclosingClass(classSym.originalOwner)
+ }
+
+ final case class EnclosingMethodEntry(owner: String, name: String, methodDescriptor: String)
+
+ /**
+ * If 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] = {
+ if (isAnonymousOrLocalClass(classSym)) {
+ val methodOpt = enclosingMethodForEnclosingMethodAttribute(classSym)
+ debuglog(s"enclosing method for $classSym is $methodOpt (in ${methodOpt.map(_.enclClass)})")
+ Some(EnclosingMethodEntry(
+ classDesc(enclosingClassForEnclosingMethodAttribute(classSym)),
+ 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)
+ }
+
+ /**
+ * 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.
+ */
+ def memberClassesOf(classSymbol: Symbol): List[Symbol] = classSymbol.info.decls.collect({
+ case sym if sym.isClass =>
+ sym
+ case sym if sym.isModule =>
+ val r = exitingPickler(sym.moduleClass)
+ assert(r != NoSymbol, sym.fullLocationString)
+ r
+ })(collection.breakOut)
+}