diff options
author | Adriaan Moors <adriaan.moors@typesafe.com> | 2015-02-13 11:47:52 -0800 |
---|---|---|
committer | Adriaan Moors <adriaan.moors@typesafe.com> | 2015-02-13 11:47:52 -0800 |
commit | da49d9a00ec373a0e7f2ffe946a897a65c9b0741 (patch) | |
tree | 3b6de81397aa269a2e426edb023b714316f08e50 | |
parent | 3b93e1fecaa8aa76d36abd327e146c6b16cd453b (diff) | |
parent | c283ce145a7b34f18deeeac5c3de2b102169e074 (diff) | |
download | scala-da49d9a00ec373a0e7f2ffe946a897a65c9b0741.tar.gz scala-da49d9a00ec373a0e7f2ffe946a897a65c9b0741.tar.bz2 scala-da49d9a00ec373a0e7f2ffe946a897a65c9b0741.zip |
Merge pull request #4296 from lrytz/t9105
Fixes and tests for InnerClass / EnclsoingMethod classfile attributes
21 files changed, 791 insertions, 104 deletions
diff --git a/build-ant-macros.xml b/build-ant-macros.xml index 609f106d09..259d6a6eb6 100644 --- a/build-ant-macros.xml +++ b/build-ant-macros.xml @@ -751,6 +751,7 @@ <attribute name="srcdir" default="files"/> <!-- TODO: make targets for `pending` and other subdirs --> <attribute name="colors" default="${partest.colors}"/> <attribute name="scalacOpts" default="${partest.scalac_opts} ${scalac.args.optimise}"/> + <attribute name="javaOpts" default="${env.ANT_OPTS}"/> <attribute name="pcp" default="${toString:partest.compilation.path}"/> <attribute name="kinds"/> <sequential> @@ -759,6 +760,7 @@ kinds="@{kinds}" colors="@{colors}" scalacOpts="@{scalacOpts}" + javaOpts="@{javaOpts}" compilationpath="@{pcp}"/> </sequential> </macrodef> diff --git a/src/compiler/scala/tools/nsc/CompilationUnits.scala b/src/compiler/scala/tools/nsc/CompilationUnits.scala index 1a6843a249..6be1fda1b5 100644 --- a/src/compiler/scala/tools/nsc/CompilationUnits.scala +++ b/src/compiler/scala/tools/nsc/CompilationUnits.scala @@ -25,9 +25,9 @@ trait CompilationUnits { global: Global => class CompilationUnit(val source: SourceFile) extends CompilationUnitContextApi { self => /** the fresh name creator */ - implicit val fresh: FreshNameCreator = new FreshNameCreator - def freshTermName(prefix: String = "x$") = global.freshTermName(prefix) - def freshTypeName(prefix: String) = global.freshTypeName(prefix) + implicit val fresh: FreshNameCreator = new FreshNameCreator + def freshTermName(prefix: String = nme.FRESH_TERM_NAME_PREFIX) = global.freshTermName(prefix) + def freshTypeName(prefix: String) = global.freshTypeName(prefix) /** the content of the compilation unit in tree form */ var body: Tree = EmptyTree diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeAsmCommon.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeAsmCommon.scala index a5f33aa786..27827015c3 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeAsmCommon.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeAsmCommon.scala @@ -22,6 +22,31 @@ final class BCodeAsmCommon[G <: Global](val global: G) { } /** + * 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 @@ -29,10 +54,68 @@ final class BCodeAsmCommon[G <: Global](val global: G) { */ def isAnonymousOrLocalClass(classSym: Symbol): Boolean = { assert(classSym.isClass, s"not a class: $classSym") - // Here used to be an `assert(!classSym.isDelambdafyFunction)`: delambdafy lambda classes are - // always top-level. However, SI-8900 shows an example where the weak name-based implementation - // of isDelambdafyFunction failed (for a function declared in a package named "lambda"). - classSym.isAnonymousClass || !classSym.originalOwner.isClass + 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 } /** @@ -60,12 +143,27 @@ final class BCodeAsmCommon[G <: Global](val global: G) { */ 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) Some(sym) - else enclosingMethod(sym.originalOwner) + else if (sym.isMethod) { + if (doesNotExist(sym)) None else Some(sym) + } + else enclosingMethod(nextEnclosing(sym)) } - enclosingMethod(classSym.originalOwner) + enclosingMethod(nextEnclosing(classSym)) } /** @@ -74,11 +172,10 @@ final class BCodeAsmCommon[G <: Global](val global: G) { */ 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) + 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) @@ -92,11 +189,22 @@ final class BCodeAsmCommon[G <: Global](val global: G) { * 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)})") + // 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(enclosingClassForEnclosingMethodAttribute(classSym)), + classDesc(enclosingClass), methodOpt.map(_.javaSimpleName.toString).orNull, methodOpt.map(methodDesc).orNull)) } else { @@ -121,11 +229,13 @@ final class BCodeAsmCommon[G <: Global](val global: G) { * 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 memberClassesOf(classSymbol: Symbol): List[Symbol] = classSymbol.info.decls.collect({ - case sym if sym.isClass => + def memberClassesForInnerClassTable(classSymbol: Symbol): List[Symbol] = classSymbol.info.decls.collect({ + case sym if sym.isClass && !considerAsTopLevelImplementationArtifact(sym) => sym - case sym if sym.isModule => + 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 diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala index 8d1c37532e..ccee230191 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala @@ -362,8 +362,8 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { else if (sym == definitions.NullClass) RT_NULL else { val r = classBTypeFromSymbol(sym) - if (r.isNestedClass) innerClassBufferASM += r - r + if (r.isNestedClass) innerClassBufferASM += r + r } } diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala b/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala index a9bce82acd..a194fe8fe4 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala @@ -527,7 +527,7 @@ abstract class BTypes { * local and anonymous classes, no matter if there is an enclosing method or not. Accordingly, the * "class" field (see below) must be always defined, while the "method" field may be null. * - * NOTE: When a EnclosingMethod attribute is requried (local and anonymous classes), the "outer" + * NOTE: When an EnclosingMethod attribute is requried (local and anonymous classes), the "outer" * field in the InnerClass table must be null. * * Fields: @@ -634,6 +634,28 @@ abstract class BTypes { * } * } * + * + * Traits Members + * -------------- + * + * Some trait methods don't exist in the generated interface, but only in the implementation class + * (private methods in traits for example). Since EnclosingMethod expresses a source-level property, + * but the source-level enclosing method doesn't exist in the classfile, we the enclosing method + * is null (the enclosing class is still emitted). + * See BCodeAsmCommon.considerAsTopLevelImplementationArtifact + * + * + * Implementation Classes, Specialized Classes, Delambdafy:method closure classes + * ------------------------------------------------------------------------------ + * + * Trait implementation classes and specialized classes are always considered top-level. Again, + * the InnerClass / EnclosingMethod attributes describe a source-level properties. The impl + * classes are compilation artifacts. + * + * The same is true for delambdafy:method closure classes. These classes are generated at + * top-level in the delambdafy phase, no special support is required in the backend. + * + * * Mirror Classes * -------------- * diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala b/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala index 94f9b585d9..2af665d31c 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala @@ -125,39 +125,71 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes { * nested classes, but NOT nested in C, that are used within C. */ val nestedClassSymbols = { + val linkedClass = exitingPickler(classSym.linkedClassOfClass) // linkedCoC does not work properly in late phases + // The lambdalift phase lifts all nested classes to the enclosing class, so if we collect // member classes right after lambdalift, we obtain all nested classes, including local and // anonymous ones. val nestedClasses = { - val nested = exitingPhase(currentRun.lambdaliftPhase)(memberClassesOf(classSym)) + val allNested = exitingPhase(currentRun.lambdaliftPhase)(memberClassesForInnerClassTable(classSym)) + val nested = { + // Classes nested in value classes are nested in the companion at this point. For InnerClass / + // EnclosingMethod, we use the value class as the outer class. So we remove nested classes + // from the companion that were originally nested in the value class. + if (exitingPickler(linkedClass.isDerivedValueClass)) allNested.filterNot(classOriginallyNestedInClass(_, linkedClass)) + else allNested + } + if (isTopLevelModuleClass(classSym)) { // For Java compatibility, member classes of top-level objects are treated as members of // the top-level companion class, see comment below. - val members = exitingPickler(memberClassesOf(classSym)) + val members = exitingPickler(memberClassesForInnerClassTable(classSym)) nested diff members } else { nested } } - // If this is a top-level class, the member classes of the companion object are added as - // members of the class. For example: - // class C { } - // object C { - // class D - // def f = { class E } - // } - // The class D is added as a member of class C. The reason is: for Java compatibility, the - // InnerClass attribute for D has "C" (NOT the module class "C$") as the outer class of D - // (done by buildNestedInfo). See comment in BTypes. - // For consistency, the InnerClass entry for D needs to be present in C - to Java it looks - // like D is a member of C, not C$. - val linkedClass = exitingPickler(classSym.linkedClassOfClass) // linkedCoC does not work properly in late phases - val companionModuleMembers = { - // phase travel to exitingPickler: this makes sure that memberClassesOf only sees member classes, - // not local classes of the companion module (E in the exmaple) that were lifted by lambdalift. - if (isTopLevelModuleClass(linkedClass)) exitingPickler(memberClassesOf(linkedClass)) - else Nil + val companionModuleMembers = if (considerAsTopLevelImplementationArtifact(classSym)) Nil else { + // If this is a top-level non-impl (*) class, the member classes of the companion object are + // added as members of the class. For example: + // class C { } + // object C { + // class D + // def f = { class E } + // } + // The class D is added as a member of class C. The reason is: for Java compatibility, the + // InnerClass attribute for D has "C" (NOT the module class "C$") as the outer class of D + // (done by buildNestedInfo). See comment in BTypes. + // For consistency, the InnerClass entry for D needs to be present in C - to Java it looks + // like D is a member of C, not C$. + // + // (*) We exclude impl classes: if the classfile for the impl class exists on the classpath, + // a linkedClass symbol is found for which isTopLevelModule is true, so we end up searching + // members of that weird impl-class-module-class-symbol. that search probably cannot return + // any classes, but it's better to exclude it. + val javaCompatMembers = { + if (linkedClass != NoSymbol && isTopLevelModuleClass(linkedClass)) + // phase travel to exitingPickler: this makes sure that memberClassesForInnerClassTable only sees member + // classes, not local classes of the companion module (E in the exmaple) that were lifted by lambdalift. + exitingPickler(memberClassesForInnerClassTable(linkedClass)) + else + Nil + } + + // Classes nested in value classes are nested in the companion at this point. For InnerClass / + // EnclosingMethod we use the value class as enclosing class. Here we search nested classes + // in the companion that were originally nested in the value class, and we add them as nested + // in the value class. + val valueClassCompanionMembers = { + if (linkedClass != NoSymbol && exitingPickler(classSym.isDerivedValueClass)) { + val moduleMemberClasses = exitingPhase(currentRun.lambdaliftPhase)(memberClassesForInnerClassTable(linkedClass)) + moduleMemberClasses.filter(classOriginallyNestedInClass(_, classSym)) + } else + Nil + } + + javaCompatMembers ++ valueClassCompanionMembers } nestedClasses ++ companionModuleMembers @@ -191,7 +223,8 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes { assert(innerClassSym.isClass, s"Cannot build NestedInfo for non-class symbol $innerClassSym") val isTopLevel = innerClassSym.rawowner.isPackageClass - if (isTopLevel) None + // impl classes are considered top-level, see comment in BTypes + if (isTopLevel || considerAsTopLevelImplementationArtifact(innerClassSym)) None else { // See comment in BTypes, when is a class marked static in the InnerClass table. val isStaticNestedClass = isOriginallyStaticOwner(innerClassSym.originalOwner) @@ -227,7 +260,9 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes { } val innerName: Option[String] = { - if (innerClassSym.isAnonymousClass || innerClassSym.isAnonymousFunction) None + // 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 (exitingPickler(innerClassSym.isAnonymousClass || innerClassSym.isAnonymousFunction)) None else Some(innerClassSym.rawname + innerClassSym.moduleSuffix) // moduleSuffix for module classes } @@ -246,7 +281,7 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes { classBTypeFromInternalName.getOrElse(internalName, { val c = ClassBType(internalName) // class info consistent with BCodeHelpers.genMirrorClass - val nested = exitingPickler(memberClassesOf(moduleClassSym)) map classBTypeFromSymbol + val nested = exitingPickler(memberClassesForInnerClassTable(moduleClassSym)) map classBTypeFromSymbol c.info = ClassInfo( superClass = Some(ObjectReference), interfaces = Nil, diff --git a/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala b/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala index abe3bc512c..707336e5de 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala @@ -595,7 +595,8 @@ abstract class GenASM extends SubComponent with BytecodeWriters { self => val x = innerClassSymbolFor(s) if(x ne NoSymbol) { assert(x.isClass, "not an inner-class symbol") - val isInner = !x.rawowner.isPackageClass + // impl classes are considered top-level, see comment in BTypes + val isInner = !considerAsTopLevelImplementationArtifact(s) && !x.rawowner.isPackageClass if (isInner) { innerClassBuffer += x collectInnerClass(x.rawowner) @@ -692,30 +693,55 @@ abstract class GenASM extends SubComponent with BytecodeWriters { self => } } - def innerName(innerSym: Symbol): String = - if (innerSym.isAnonymousClass || innerSym.isAnonymousFunction) - null - else - innerSym.rawname + innerSym.moduleSuffix + def innerName(innerSym: Symbol): String = { + // 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 (exitingPickler(innerSym.isAnonymousClass || innerSym.isAnonymousFunction)) null + else innerSym.rawname + innerSym.moduleSuffix + } + + val linkedClass = exitingPickler(csym.linkedClassOfClass) // linkedCoC does not work properly in late phases innerClassBuffer ++= { - val members = exitingPickler(memberClassesOf(csym)) + val members = exitingPickler(memberClassesForInnerClassTable(csym)) // lambdalift makes all classes (also local, anonymous) members of their enclosing class - val allNested = exitingPhase(currentRun.lambdaliftPhase)(memberClassesOf(csym)) + val allNested = exitingPhase(currentRun.lambdaliftPhase)(memberClassesForInnerClassTable(csym)) + val nested = { + // Classes nested in value classes are nested in the companion at this point. For InnerClass / + // EnclosingMethod, we use the value class as the outer class. So we remove nested classes + // from the companion that were originally nested in the value class. + if (exitingPickler(linkedClass.isDerivedValueClass)) allNested.filterNot(classOriginallyNestedInClass(_, linkedClass)) + else allNested + } - // for the mirror class, we take the members of the companion module class (Java compat, - // see doc in BTypes.scala). for module classes, we filter out those members. - if (isMirror) members - else if (isTopLevelModule(csym)) allNested diff members - else allNested - } + // for the mirror class, we take the members of the companion module class (Java compat, see doc in BTypes.scala). + // for module classes, we filter out those members. + if (isMirror) members + else if (isTopLevelModule(csym)) nested diff members + else nested + } + + if (!considerAsTopLevelImplementationArtifact(csym)) { + // If this is a top-level non-impl class, add members of the companion object. These are the + // classes for which we change the InnerClass entry to allow using them from Java. + // We exclude impl classes: if the classfile for the impl class exists on the classpath, a + // linkedClass symbol is found for which isTopLevelModule is true, so we end up searching + // members of that weird impl-class-module-class-symbol. that search probably cannot return + // any classes, but it's better to exclude it. + if (linkedClass != NoSymbol && isTopLevelModule(linkedClass)) { + // phase travel to exitingPickler: this makes sure that memberClassesForInnerClassTable only + // sees member classes, not local classes that were lifted by lambdalift. + innerClassBuffer ++= exitingPickler(memberClassesForInnerClassTable(linkedClass)) + } - // If this is a top-level class, add members of the companion object. - val linkedClass = exitingPickler(csym.linkedClassOfClass) // linkedCoC does not work properly in late phases - if (isTopLevelModule(linkedClass)) { - // phase travel to exitingPickler: this makes sure that memberClassesOf only sees member classes, - // not local classes that were lifted by lambdalift. - innerClassBuffer ++= exitingPickler(memberClassesOf(linkedClass)) + // Classes nested in value classes are nested in the companion at this point. For InnerClass / + // EnclosingMethod we use the value class as enclosing class. Here we search nested classes + // in the companion that were originally nested in the value class, and we add them as nested + // in the value class. + if (linkedClass != NoSymbol && exitingPickler(csym.isDerivedValueClass)) { + val moduleMemberClasses = exitingPhase(currentRun.lambdaliftPhase)(memberClassesForInnerClassTable(linkedClass)) + innerClassBuffer ++= moduleMemberClasses.filter(classOriginallyNestedInClass(_, csym)) + } } val allInners: List[Symbol] = innerClassBuffer.toList filterNot deadCode.elidedClosures diff --git a/src/compiler/scala/tools/nsc/transform/LambdaLift.scala b/src/compiler/scala/tools/nsc/transform/LambdaLift.scala index a703542587..5e2fe21eec 100644 --- a/src/compiler/scala/tools/nsc/transform/LambdaLift.scala +++ b/src/compiler/scala/tools/nsc/transform/LambdaLift.scala @@ -250,21 +250,30 @@ abstract class LambdaLift extends InfoTransform { debuglog("renaming in %s: %s => %s".format(sym.owner.fullLocationString, originalName, sym.name)) } + // make sure that the name doesn't make the symbol accidentally `isAnonymousClass` (et.al) by + // introducing `$anon` in its name. to be cautious, we don't make this change in the default + // backend under 2.11.x, so only in GenBCode. + def nonAnon(s: String) = if (settings.Ybackend.value == "GenBCode") nme.ensureNonAnon(s) else s + def newName(sym: Symbol): Name = { val originalName = sym.name def freshen(prefix: String): Name = if (originalName.isTypeName) unit.freshTypeName(prefix) else unit.freshTermName(prefix) + val join = nme.NAME_JOIN_STRING if (sym.isAnonymousFunction && sym.owner.isMethod) { - freshen(sym.name + nme.NAME_JOIN_STRING + sym.owner.name + nme.NAME_JOIN_STRING) + freshen(sym.name + join + nonAnon(sym.owner.name.toString) + join) } else { + val name = freshen(sym.name + join) // SI-5652 If the lifted symbol is accessed from an inner class, it will be made public. (where?) - // Generating a unique name, mangled with the enclosing class name, avoids a VerifyError - // in the case that a sub-class happens to lifts out a method with the *same* name. - val name = freshen("" + sym.name + nme.NAME_JOIN_STRING) - if (originalName.isTermName && !sym.enclClass.isImplClass && calledFromInner(sym)) nme.expandedName(name.toTermName, sym.enclClass) - else name + // Generating a unique name, mangled with the enclosing full class name (including + // package - subclass might have the same name), avoids a VerifyError in the case + // that a sub-class happens to lifts out a method with the *same* name. + if (originalName.isTermName && !sym.enclClass.isImplClass && calledFromInner(sym)) + newTermNameCached(nonAnon(sym.enclClass.fullName('$')) + nme.EXPAND_SEPARATOR_STRING + name) + else + name } } diff --git a/src/compiler/scala/tools/nsc/transform/UnCurry.scala b/src/compiler/scala/tools/nsc/transform/UnCurry.scala index 3544dc9966..e1cf53059a 100644 --- a/src/compiler/scala/tools/nsc/transform/UnCurry.scala +++ b/src/compiler/scala/tools/nsc/transform/UnCurry.scala @@ -225,6 +225,10 @@ abstract class UnCurry extends InfoTransform if (inlineFunctionExpansion || !canUseDelamdafyMethod) { val parents = addSerializable(abstractFunctionForFunctionType(fun.tpe)) val anonClass = fun.symbol.owner newAnonymousFunctionClass(fun.pos, inConstructorFlag) addAnnotation SerialVersionUIDAnnotation + // The original owner is used in the backend for the EnclosingMethod attribute. If fun is + // nested in a value-class method, its owner was already changed to the extension method. + // Saving the original owner allows getting the source structure from the class symbol. + defineOriginalOwner(anonClass, fun.symbol.originalOwner) anonClass setInfo ClassInfoType(parents, newScope, anonClass) val applyMethodDef = mkMethod(anonClass, nme.apply) diff --git a/src/intellij-14/scaladoc.iml.SAMPLE b/src/intellij-14/scaladoc.iml.SAMPLE index 1e7621ffed..5c7015aa61 100644 --- a/src/intellij-14/scaladoc.iml.SAMPLE +++ b/src/intellij-14/scaladoc.iml.SAMPLE @@ -13,5 +13,6 @@ <orderEntry type="library" name="scaladoc-deps" level="project" /> <orderEntry type="library" name="partest" level="project" /> <orderEntry type="library" name="starr-no-deps" level="project" /> + <orderEntry type="library" name="ant" level="project" /> </component> </module>
\ No newline at end of file diff --git a/src/interactive/scala/tools/nsc/interactive/Global.scala b/src/interactive/scala/tools/nsc/interactive/Global.scala index 9b34a39e02..a3d0346f81 100644 --- a/src/interactive/scala/tools/nsc/interactive/Global.scala +++ b/src/interactive/scala/tools/nsc/interactive/Global.scala @@ -152,7 +152,7 @@ class Global(settings: Settings, _reporter: Reporter, projectName: String = "") // don't keep the original owner in presentation compiler runs // (the map will grow indefinitely, and the only use case is the backend) - override protected def saveOriginalOwner(sym: Symbol) { } + override def defineOriginalOwner(sym: Symbol, owner: Symbol): Unit = { } override def forInteractive = true override protected def synchronizeNames = true diff --git a/src/reflect/scala/reflect/internal/StdNames.scala b/src/reflect/scala/reflect/internal/StdNames.scala index c94ee996e4..f32b7326fe 100644 --- a/src/reflect/scala/reflect/internal/StdNames.scala +++ b/src/reflect/scala/reflect/internal/StdNames.scala @@ -112,6 +112,23 @@ trait StdNames { val ROOT: NameType = "<root>" val SPECIALIZED_SUFFIX: NameType = "$sp" + val NESTED_IN: String = "$nestedIn" + val NESTED_IN_ANON_CLASS: String = NESTED_IN + ANON_CLASS_NAME.toString.replace("$", "") + val NESTED_IN_ANON_FUN: String = NESTED_IN + ANON_FUN_NAME.toString.replace("$", "") + val NESTED_IN_LAMBDA: String = NESTED_IN + DELAMBDAFY_LAMBDA_CLASS_NAME.toString.replace("$", "") + + /** + * Ensures that name mangling does not accidentally make a class respond `true` to any of + * isAnonymousClass, isAnonymousFunction, isDelambdafyFunction, e.g. by introducing "$anon". + */ + def ensureNonAnon(name: String) = { + name + .replace(nme.ANON_CLASS_NAME.toString, NESTED_IN_ANON_CLASS) + .replace(nme.ANON_FUN_NAME.toString, NESTED_IN_ANON_FUN) + .replace(nme.DELAMBDAFY_LAMBDA_CLASS_NAME.toString, NESTED_IN_LAMBDA) + } + + // value types (and AnyRef) are all used as terms as well // as (at least) arguments to the @specialize annotation. final val Boolean: NameType = "Boolean" diff --git a/src/reflect/scala/reflect/internal/Symbols.scala b/src/reflect/scala/reflect/internal/Symbols.scala index d5fc52abbf..d23a102b28 100644 --- a/src/reflect/scala/reflect/internal/Symbols.scala +++ b/src/reflect/scala/reflect/internal/Symbols.scala @@ -66,14 +66,19 @@ trait Symbols extends api.Symbols { self: SymbolTable => // when under some flag. Define per-phase invariants for owner/owned relationships, // e.g. after flatten all classes are owned by package classes, there are lots and // lots of these to be declared (or more realistically, discovered.) + // could be private since 2.11.6, but left protected to avoid potential breakages (eg ensime) protected def saveOriginalOwner(sym: Symbol): Unit = { // some synthetic symbols have NoSymbol as owner initially if (sym.owner != NoSymbol) { if (originalOwnerMap contains sym) () - else originalOwnerMap(sym) = sym.rawowner + else defineOriginalOwner(sym, sym.rawowner) } } + def defineOriginalOwner(sym: Symbol, owner: Symbol): Unit = { + originalOwnerMap(sym) = owner + } + def symbolOf[T: WeakTypeTag]: TypeSymbol = weakTypeOf[T].typeSymbolDirect.asType abstract class SymbolContextApiImpl extends SymbolApi { diff --git a/test/files/jvm/innerClassAttribute.check b/test/files/jvm/innerClassAttribute.check index 20518aa49e..bb532e4f36 100644 --- a/test/files/jvm/innerClassAttribute.check +++ b/test/files/jvm/innerClassAttribute.check @@ -14,27 +14,27 @@ A19 / null / null A19 / null / null A19 / null / null -- A20 -- -A20$$anonfun$4 / null / null / 17 +A20$$anonfun$6 / null / null / 17 fun1: attribute for itself and the two child closures `() => ()` and `() => () => 1` -A20$$anonfun$4 / null / null / 17 -A20$$anonfun$4$$anonfun$apply$1 / null / null / 17 -A20$$anonfun$4$$anonfun$apply$3 / null / null / 17 +A20$$anonfun$6 / null / null / 17 +A20$$anonfun$6$$anonfun$apply$1 / null / null / 17 +A20$$anonfun$6$$anonfun$apply$3 / null / null / 17 fun2 () => (): itself and the outer closure -A20$$anonfun$4 / null / null / 17 -A20$$anonfun$4$$anonfun$apply$1 / null / null / 17 +A20$$anonfun$6 / null / null / 17 +A20$$anonfun$6$$anonfun$apply$1 / null / null / 17 fun3 () => () => (): itself, the outer closure and its child closure -A20$$anonfun$4 / null / null / 17 -A20$$anonfun$4$$anonfun$apply$3 / null / null / 17 -A20$$anonfun$4$$anonfun$apply$3$$anonfun$apply$2 / null / null / 17 +A20$$anonfun$6 / null / null / 17 +A20$$anonfun$6$$anonfun$apply$3 / null / null / 17 +A20$$anonfun$6$$anonfun$apply$3$$anonfun$apply$2 / null / null / 17 fun4: () => 1: itself and the two outer closures -A20$$anonfun$4 / null / null / 17 -A20$$anonfun$4$$anonfun$apply$3 / null / null / 17 -A20$$anonfun$4$$anonfun$apply$3$$anonfun$apply$2 / null / null / 17 -enclosing: nested closures have the apply method of the outer closure +A20$$anonfun$6 / null / null / 17 +A20$$anonfun$6$$anonfun$apply$3 / null / null / 17 +A20$$anonfun$6$$anonfun$apply$3$$anonfun$apply$2 / null / null / 17 +enclosing: nested closures have outer class defined, but no outer method A20 / null / null -A20$$anonfun$4 / apply / ()Lscala/Function0; -A20$$anonfun$4 / apply / ()Lscala/Function0; -A20$$anonfun$4$$anonfun$apply$3 / apply / ()Lscala/Function0; +A20$$anonfun$6 / null / null +A20$$anonfun$6 / null / null +A20$$anonfun$6$$anonfun$apply$3 / null / null #partest -Ydelambdafy:method -- A4 -- null / null / null @@ -47,7 +47,7 @@ fun1: attribute for itself and the two child closures `() => ()` and `() => () = fun2 () => (): itself and the outer closure fun3 () => () => (): itself, the outer closure and its child closure fun4: () => 1: itself and the two outer closures -enclosing: nested closures have the apply method of the outer closure +enclosing: nested closures have outer class defined, but no outer method null / null / null null / null / null null / null / null diff --git a/test/files/jvm/innerClassAttribute/Classes_1.scala b/test/files/jvm/innerClassAttribute/Classes_1.scala index 9c3ea7f013..fb1f32aa3d 100644 --- a/test/files/jvm/innerClassAttribute/Classes_1.scala +++ b/test/files/jvm/innerClassAttribute/Classes_1.scala @@ -185,3 +185,113 @@ trait A24 extends A24Base { override object Conc extends A24Sym } } + +class SI_9105 { + // the EnclosingMethod attributes depend on the delambdafy strategy (inline vs method) + + // outerClass-inline enclMeth-inline outerClass-method enclMeth-method + val fun = () => { + class A // closure null (*) SI_9105 null + def m: Object = { class B; new B } // closure m$1 SI_9105 m$1 + val f: Object = { class C; new C } // closure null (*) SI_9105 null + } + def met = () => { + class D // closure null (*) SI_9105 met + def m: Object = { class E; new E } // closure m$1 SI_9105 m$1 + val f: Object = { class F; new F } // closure null (*) SI_9105 met + } + + // (*) the originalOwner chain of A (similar for D) is: SI_9105.fun.$anonfun-value.A + // we can get to the anonfun-class (created by uncurry), but not to the apply method. + // + // for C and F, the originalOwner chain is fun.$anonfun-value.f.C. at later phases, the rawowner of f is + // an apply$sp method of the closure class. we could use that as enclosing method, but it would be unsystematic + // (A / D don't have an encl meth either), and also strange to use the $sp, which is a compilation artifact. + // So using `null` looks more like the situation in the source code: C / F are nested classes of the anon-fun, and + // there's no method in between. + + def byName[T](op: => T) = 0 + + val bnV = byName { + class G // closure null (*) SI_9105 null + def m: Object = { class H; new H } // closure m$1 SI_9105 m$1 + val f: Object = { class I; new I } // closure null (*) SI_9105 null + } + def bnM = byName { + class J // closure null (*) SI_9105 bnM + def m: Object = { class K; new K } // closure m$1 SI_9105 m$1 + val f: Object = { class L; new L } // closure null (*) SI_9105 bnM + } +} + +trait SI_9124 { + trait A // member class, no enclosing method attribute + + new A { def f1 = 0 } // nested class, enclosing class SI_9124, no encl meth + + def f = new A { def f2 = 0 } // enclosing method is f in the interface SI_9124 + + private def g = new A { def f3 = 0 } // only encl class (SI_9124), encl meth is null because the interface SI_9124 doesn't have a method g + + object O { // member, no encl meth attribute + new A { def f4 = 0 } // enclosing class is O$, no enclosing method + } + + val f1 = { new A { def f5 = 0 }; 1 } // encl class SI_9124, no encl meth + private val f2 = { new A { def f6 = 0 }; 1 } // like above +} + +trait ImplClassesAreTopLevel { + // all impl classes are top-level, so they don't appear in any InnerClass entry, and none of them have an EnclosingMethod attr + trait B1 { def f = 1 } + { trait B2 { def f = 1 }; new B2 {} } + val m = { + trait B3 { def f = 1 } + new B3 {} + } + def n = { + trait B4 { def f = 1 } + new B4 {} + } +} + +class SpecializedClassesAreTopLevel { + // all specialized classes are top-level + class A[@specialized(Int) T]; new A[Int] + + object T { + class B[@specialized(Int) T]; new B[Int] + } + + // these crash the compiler, SI-7625 + + // { class B[@specialized(Int) T]; new B[Int] } + + // val m: Object = { + // class C[@specialized(Int) T] + // new C[Int] + // } + + // def n: Object = { + // class D[@specialized(Int) T] + // new D[Int] + // } +} + +object NestedInValueClass { + // note that we can only test anonymous functions, nested classes are not allowed inside value classes + class A(val arg: String) extends AnyVal { + // A has InnerClass entries for the two closures (and for A and A$). not for B / C + def f = { + def g = List().map(x => (() => x)) // outer class A, no outer method (g is moved to the companion, doesn't exist in A) + g.map(x => (() => x)) // outer class A, outer method f + } + // statements and field declarations are not allowed in value classes + } + + object A { + // A$ has InnerClass entries for B, C, A, A$. Also for the closures above, because they are referenced in A$'s bytecode. + class B // member class of A$ + def f = { class C; new C } // outer class A$, outer method f + } +} diff --git a/test/files/jvm/innerClassAttribute/Test.scala b/test/files/jvm/innerClassAttribute/Test.scala index 3820048cb4..bc9aa2376a 100644 --- a/test/files/jvm/innerClassAttribute/Test.scala +++ b/test/files/jvm/innerClassAttribute/Test.scala @@ -16,6 +16,16 @@ object Test extends BytecodeTest { loadClassNode(className).innerClasses.asScala.toList.sortBy(_.name) } + def ownInnerClassNode(n: String) = innerClassNodes(n).filter(_.name == n).head + + def testInner(cls: String, fs: (InnerClassNode => Unit)*) = { + val ns = innerClassNodes(cls) + assert(ns.length == fs.length, ns) + (ns zip fs.toList) foreach { case (n, f) => f(n) } + } + + + final case class EnclosingMethod(name: String, descriptor: String, outerClass: String) def enclosingMethod(className: String) = { val n = loadClassNode(className) @@ -215,7 +225,7 @@ object Test extends BytecodeTest { assertAnonymous(anon1, "A18$$anon$5") assertAnonymous(anon2, "A18$$anon$6") - assertLocal(a, "A18$A$1", "A$1") + assertLocal(a, "A18$A$2", "A$2") assertLocal(b, "A18$B$4", "B$4") assertEnclosingMethod( @@ -226,7 +236,7 @@ object Test extends BytecodeTest { "A18", "g$1", "()V") assertEnclosingMethod( - "A18$A$1", + "A18$A$2", "A18", "g$1", "()V") assertEnclosingMethod( "A18$B$4", @@ -256,10 +266,10 @@ object Test extends BytecodeTest { printInnerClassNodes("A20") - val fun1 = lambdaClass("A20$$anonfun$4", "A20$lambda$1") - val fun2 = lambdaClass("A20$$anonfun$4$$anonfun$apply$1", "A20$lambda$$$nestedInAnonfun$5$1") - val fun3 = lambdaClass("A20$$anonfun$4$$anonfun$apply$3", "A20$lambda$$$nestedInAnonfun$5$2") - val fun4 = lambdaClass("A20$$anonfun$4$$anonfun$apply$3$$anonfun$apply$2", "A20$lambda$$$nestedInAnonfun$7$1") + val fun1 = lambdaClass("A20$$anonfun$6", "A20$lambda$1") + val fun2 = lambdaClass("A20$$anonfun$6$$anonfun$apply$1", "A20$lambda$$$nestedInAnonfun$5$1") + val fun3 = lambdaClass("A20$$anonfun$6$$anonfun$apply$3", "A20$lambda$$$nestedInAnonfun$5$2") + val fun4 = lambdaClass("A20$$anonfun$6$$anonfun$apply$3$$anonfun$apply$2", "A20$lambda$$$nestedInAnonfun$7$1") println("fun1: attribute for itself and the two child closures `() => ()` and `() => () => 1`") printInnerClassNodes(fun1) @@ -270,7 +280,7 @@ object Test extends BytecodeTest { println("fun4: () => 1: itself and the two outer closures") printInnerClassNodes(fun4) - println("enclosing: nested closures have the apply method of the outer closure") + println("enclosing: nested closures have outer class defined, but no outer method") printEnclosingMethod(fun1) printEnclosingMethod(fun2) printEnclosingMethod(fun3) @@ -316,12 +326,238 @@ object Test extends BytecodeTest { } def testA24() { - val List(defsCls, abs, conc, defsApi, defsApiImpl) = innerClassNodes("A24$DefinitionsClass") + val List(defsCls, abs, conc, defsApi) = innerClassNodes("A24$DefinitionsClass") assertMember(defsCls, "A24", "DefinitionsClass") assertMember(abs, "A24$DefinitionsClass", "Abs$") assertMember(conc, "A24$DefinitionsClass", "Conc$") assertMember(defsApi, "A24Base", "DefinitionsApi", flags = publicAbstractInterface) - assertMember(defsApiImpl, "A24Base", "DefinitionsApi$class", flags = Flags.ACC_PUBLIC | Flags.ACC_ABSTRACT) + } + + def testSI_9105() { + val isDelambdafyMethod = classpath.findClass("SI_9105$lambda$1").isDefined + if (isDelambdafyMethod) { + assertEnclosingMethod ("SI_9105$A$3" , "SI_9105", null , null) + assertEnclosingMethod ("SI_9105$B$5" , "SI_9105", "m$1", "()Ljava/lang/Object;") + assertEnclosingMethod ("SI_9105$C$1" , "SI_9105", null , null) + assertEnclosingMethod ("SI_9105$D$1" , "SI_9105", "met", "()Lscala/Function0;") + assertEnclosingMethod ("SI_9105$E$1" , "SI_9105", "m$3", "()Ljava/lang/Object;") + assertEnclosingMethod ("SI_9105$F$1" , "SI_9105", "met", "()Lscala/Function0;") + assertNoEnclosingMethod("SI_9105$lambda$$met$1") + assertNoEnclosingMethod("SI_9105$lambda$1") + assertNoEnclosingMethod("SI_9105") + + assertLocal(innerClassNodes("SI_9105$A$3").head, "SI_9105$A$3", "A$3") + assertLocal(innerClassNodes("SI_9105$B$5").head, "SI_9105$B$5", "B$5") + assertLocal(innerClassNodes("SI_9105$C$1").head, "SI_9105$C$1", "C$1") + assertLocal(innerClassNodes("SI_9105$D$1").head, "SI_9105$D$1", "D$1") + assertLocal(innerClassNodes("SI_9105$E$1").head, "SI_9105$E$1", "E$1") + assertLocal(innerClassNodes("SI_9105$F$1").head, "SI_9105$F$1", "F$1") + + // by-name + assertEnclosingMethod("SI_9105$G$1", "SI_9105", null , null) + assertEnclosingMethod("SI_9105$H$1", "SI_9105", "m$2", "()Ljava/lang/Object;") + assertEnclosingMethod("SI_9105$I$1", "SI_9105", null , null) + assertEnclosingMethod("SI_9105$J$1", "SI_9105", "bnM", "()I") + assertEnclosingMethod("SI_9105$K$2", "SI_9105", "m$4", "()Ljava/lang/Object;") + assertEnclosingMethod("SI_9105$L$1", "SI_9105", "bnM", "()I") + + assert(innerClassNodes("SI_9105$lambda$$met$1").isEmpty) + assert(innerClassNodes("SI_9105$lambda$1").isEmpty) + assert(innerClassNodes("SI_9105").length == 12) // the 12 local classes + } else { + // comment in innerClassAttribute/Classes_1.scala explains the difference between A / C and D / F. + assertEnclosingMethod ("SI_9105$$anonfun$4$A$3" , "SI_9105$$anonfun$4" , null , null) + assertEnclosingMethod ("SI_9105$$anonfun$4$B$5" , "SI_9105$$anonfun$4" , "m$1" , "()Ljava/lang/Object;") + assertEnclosingMethod ("SI_9105$$anonfun$4$C$1" , "SI_9105$$anonfun$4" , null , null) + assertEnclosingMethod ("SI_9105$$anonfun$met$1$D$1", "SI_9105$$anonfun$met$1", null , null) + assertEnclosingMethod ("SI_9105$$anonfun$met$1$E$1", "SI_9105$$anonfun$met$1", "m$3" , "()Ljava/lang/Object;") + assertEnclosingMethod ("SI_9105$$anonfun$met$1$F$1", "SI_9105$$anonfun$met$1", null , null) + assertEnclosingMethod ("SI_9105$$anonfun$4" , "SI_9105" , null , null) + assertEnclosingMethod ("SI_9105$$anonfun$met$1" , "SI_9105" , "met" , "()Lscala/Function0;") + assertNoEnclosingMethod("SI_9105") + + assertLocal(ownInnerClassNode("SI_9105$$anonfun$4$A$3"), "SI_9105$$anonfun$4$A$3" , "A$3") + assertLocal(ownInnerClassNode("SI_9105$$anonfun$4$B$5"), "SI_9105$$anonfun$4$B$5" , "B$5") + assertLocal(ownInnerClassNode("SI_9105$$anonfun$4$C$1"), "SI_9105$$anonfun$4$C$1" , "C$1") + assertLocal(ownInnerClassNode("SI_9105$$anonfun$met$1$D$1"), "SI_9105$$anonfun$met$1$D$1", "D$1") + assertLocal(ownInnerClassNode("SI_9105$$anonfun$met$1$E$1"), "SI_9105$$anonfun$met$1$E$1", "E$1") + assertLocal(ownInnerClassNode("SI_9105$$anonfun$met$1$F$1"), "SI_9105$$anonfun$met$1$F$1", "F$1") + + // by-name + assertEnclosingMethod("SI_9105$$anonfun$5$G$1", "SI_9105$$anonfun$5", null, null) + assertEnclosingMethod("SI_9105$$anonfun$5$H$1", "SI_9105$$anonfun$5", "m$2", "()Ljava/lang/Object;") + assertEnclosingMethod("SI_9105$$anonfun$5$I$1", "SI_9105$$anonfun$5", null, null) + assertEnclosingMethod("SI_9105$$anonfun$bnM$1$J$1", "SI_9105$$anonfun$bnM$1", null, null) + assertEnclosingMethod("SI_9105$$anonfun$bnM$1$K$2", "SI_9105$$anonfun$bnM$1", "m$4", "()Ljava/lang/Object;") + assertEnclosingMethod("SI_9105$$anonfun$bnM$1$L$1", "SI_9105$$anonfun$bnM$1", null, null) + + assertAnonymous(ownInnerClassNode("SI_9105$$anonfun$4"), "SI_9105$$anonfun$4") + assertAnonymous(ownInnerClassNode("SI_9105$$anonfun$met$1"), "SI_9105$$anonfun$met$1") + + assert(innerClassNodes("SI_9105$$anonfun$4").length == 4) // itself and three of the local classes + assert(innerClassNodes("SI_9105$$anonfun$met$1").length == 4) // itself and three of the local classes + assert(innerClassNodes("SI_9105").length == 4) // the four anon funs + } + } + + def testSI_9124() { + val classes: Map[String, String] = { + List("SI_9124$$anon$10", + "SI_9124$$anon$11", + "SI_9124$$anon$12", + "SI_9124$$anon$8", + "SI_9124$$anon$9", + "SI_9124$O$$anon$13").map({ name => + val node = loadClassNode(name) + val fMethod = node.methods.asScala.find(_.name.startsWith("f")).get.name + (fMethod, node.name) + }).toMap + } + + // println(classes) + + assertNoEnclosingMethod("SI_9124$A") + assertEnclosingMethod(classes("f1"), "SI_9124", null, null) + assertEnclosingMethod(classes("f2"), "SI_9124", "f", "()LSI_9124$A;") + assertEnclosingMethod(classes("f3"), "SI_9124", null, null) + assertEnclosingMethod(classes("f4"), "SI_9124$O$", null, null) + assertEnclosingMethod(classes("f5"), "SI_9124", null, null) + assertEnclosingMethod(classes("f6"), "SI_9124", null, null) + assertNoEnclosingMethod("SI_9124$O$") + + assertMember(ownInnerClassNode("SI_9124$A"), "SI_9124", "A", flags = publicAbstractInterface) + classes.values.foreach(n => assertAnonymous(ownInnerClassNode(n), n)) + assertMember(ownInnerClassNode("SI_9124$O$"), "SI_9124", "O$") + } + + def testImplClassesTopLevel() { + val classes = List( + "ImplClassesAreTopLevel$$anon$14", + "ImplClassesAreTopLevel$$anon$15", + "ImplClassesAreTopLevel$$anon$16", + "ImplClassesAreTopLevel$B1$class", + "ImplClassesAreTopLevel$B1", + "ImplClassesAreTopLevel$B2$1$class", + "ImplClassesAreTopLevel$B2$1", + "ImplClassesAreTopLevel$B3$1$class", + "ImplClassesAreTopLevel$B3$1", + "ImplClassesAreTopLevel$B4$class", + "ImplClassesAreTopLevel$B4$1", + "ImplClassesAreTopLevel$class", + "ImplClassesAreTopLevel") + + classes.filter(_.endsWith("$class")).foreach(assertNoEnclosingMethod) + classes.flatMap(innerClassNodes).foreach(icn => assert(!icn.name.endsWith("$class"), icn)) + + assertNoEnclosingMethod("ImplClassesAreTopLevel$B1") // member, no encl meth attr + + // no encl meth, but encl class + List("ImplClassesAreTopLevel$B2$1", "ImplClassesAreTopLevel$B3$1", + "ImplClassesAreTopLevel$$anon$14", "ImplClassesAreTopLevel$$anon$15").foreach(assertEnclosingMethod(_, "ImplClassesAreTopLevel", null, null)) + + // encl meth n + List("ImplClassesAreTopLevel$B4$1", "ImplClassesAreTopLevel$$anon$16").foreach(assertEnclosingMethod(_, "ImplClassesAreTopLevel", "n", "()Ljava/lang/Object;")) + + val an14 = assertAnonymous(_: InnerClassNode, "ImplClassesAreTopLevel$$anon$14") + val an15 = assertAnonymous(_: InnerClassNode, "ImplClassesAreTopLevel$$anon$15") + val an16 = assertAnonymous(_: InnerClassNode, "ImplClassesAreTopLevel$$anon$16") + val b1 = assertMember(_: InnerClassNode, "ImplClassesAreTopLevel", "B1", flags = publicAbstractInterface) + val b2 = assertLocal(_ : InnerClassNode, "ImplClassesAreTopLevel$B2$1", "B2$1", flags = publicAbstractInterface) + val b3 = assertLocal(_ : InnerClassNode, "ImplClassesAreTopLevel$B3$1", "B3$1", flags = publicAbstractInterface) + val b4 = assertLocal(_ : InnerClassNode, "ImplClassesAreTopLevel$B4$1", "B4$1", flags = publicAbstractInterface) + + testInner("ImplClassesAreTopLevel$$anon$14", an14, b3) + testInner("ImplClassesAreTopLevel$$anon$15", an15, b2) + testInner("ImplClassesAreTopLevel$$anon$16", an16, b4) + + testInner("ImplClassesAreTopLevel$B1$class", b1) + testInner("ImplClassesAreTopLevel$B2$1$class", b2) + testInner("ImplClassesAreTopLevel$B3$1$class", b3) + testInner("ImplClassesAreTopLevel$B4$class", b4) + + testInner("ImplClassesAreTopLevel$B1", b1) + testInner("ImplClassesAreTopLevel$B2$1", b2) + testInner("ImplClassesAreTopLevel$B3$1", b3) + testInner("ImplClassesAreTopLevel$B4$1", b4) + + testInner("ImplClassesAreTopLevel$class", an14, an15, an16) + testInner("ImplClassesAreTopLevel", an14, an15, an16, b1, b2, b3, b4) + } + + def testSpecializedClassesTopLevel() { + val cls = List( + "SpecializedClassesAreTopLevel$A$mcI$sp", + "SpecializedClassesAreTopLevel$A", + "SpecializedClassesAreTopLevel$T$", + "SpecializedClassesAreTopLevel$T$B$mcI$sp", + "SpecializedClassesAreTopLevel$T$B", + "SpecializedClassesAreTopLevel") + + // all classes are members, no local (can't test local, they crash in specialize) + cls.foreach(assertNoEnclosingMethod) + cls.flatMap(innerClassNodes).foreach(icn => assert(!icn.name.endsWith("$sp"), icn)) + + val a = assertMember(_: InnerClassNode, "SpecializedClassesAreTopLevel", "A") + val t = assertMember(_: InnerClassNode, "SpecializedClassesAreTopLevel", "T$") + val b = assertMember(_: InnerClassNode, "SpecializedClassesAreTopLevel$T$", "B", Some("SpecializedClassesAreTopLevel$T$B")) + + List("SpecializedClassesAreTopLevel$A$mcI$sp", "SpecializedClassesAreTopLevel$A").foreach(testInner(_, a)) + testInner("SpecializedClassesAreTopLevel", a, t) + List("SpecializedClassesAreTopLevel$T$", "SpecializedClassesAreTopLevel$T$B$mcI$sp", "SpecializedClassesAreTopLevel$T$B").foreach(testInner(_, t, b)) + } + + def testNestedInValueClass() { + List( + "NestedInValueClass", + "NestedInValueClass$", + "NestedInValueClass$A", + "NestedInValueClass$A$", + "NestedInValueClass$A$B").foreach(assertNoEnclosingMethod) + + assertEnclosingMethod("NestedInValueClass$A$C$2", "NestedInValueClass$A$", "f", "()Ljava/lang/Object;") + + type I = InnerClassNode + val a = assertMember(_: I, "NestedInValueClass", "A", flags = publicStatic | Flags.ACC_FINAL) + val am = assertMember(_: I, "NestedInValueClass", "A$", flags = publicStatic) + val b = assertMember(_: I, "NestedInValueClass$A$", "B", Some("NestedInValueClass$A$B"), flags = publicStatic) + val c = assertLocal(_: I, "NestedInValueClass$A$C$2", "C$2") + + testInner("NestedInValueClass$") + testInner("NestedInValueClass", a, am) + testInner("NestedInValueClass$A$B", am, b) + testInner("NestedInValueClass$A$C$2", am, c) + + val isDelambdafyMethod = classpath.findClass("NestedInValueClass$A$lambda$$f$extension$1").isDefined + if (isDelambdafyMethod) { + List( + "NestedInValueClass$A$lambda$$g$2$1", + "NestedInValueClass$A$lambda$$f$extension$1", + "NestedInValueClass$A$lambda$$$nestedInAnonfun$13$1", + "NestedInValueClass$A$lambda$$$nestedInAnonfun$15$1").foreach(assertNoEnclosingMethod) + testInner("NestedInValueClass$A", a, am) + testInner("NestedInValueClass$A$", a, am, b, c) + testInner("NestedInValueClass$A$lambda$$g$2$1", am) + testInner("NestedInValueClass$A$lambda$$f$extension$1", am) + testInner("NestedInValueClass$A$lambda$$$nestedInAnonfun$13$1", am) + testInner("NestedInValueClass$A$lambda$$$nestedInAnonfun$15$1", am) + } else { + assertEnclosingMethod("NestedInValueClass$A$$anonfun$g$2$1" , "NestedInValueClass$A" , null, null) + assertEnclosingMethod("NestedInValueClass$A$$anonfun$g$2$1$$anonfun$apply$4" , "NestedInValueClass$A$$anonfun$g$2$1" , null, null) + assertEnclosingMethod("NestedInValueClass$A$$anonfun$f$extension$1" , "NestedInValueClass$A" , "f", "()Lscala/collection/immutable/List;") + assertEnclosingMethod("NestedInValueClass$A$$anonfun$f$extension$1$$anonfun$apply$5", "NestedInValueClass$A$$anonfun$f$extension$1", null, null) + + val gfun = assertAnonymous(_: I, "NestedInValueClass$A$$anonfun$g$2$1") + val ffun = assertAnonymous(_: I, "NestedInValueClass$A$$anonfun$f$extension$1") + val gfunfun = assertAnonymous(_: I, "NestedInValueClass$A$$anonfun$g$2$1$$anonfun$apply$4") + val ffunfun = assertAnonymous(_: I, "NestedInValueClass$A$$anonfun$f$extension$1$$anonfun$apply$5") + + testInner("NestedInValueClass$A", a, am, ffun, gfun) + testInner("NestedInValueClass$A$", a, am, ffun, gfun, b, c) + testInner("NestedInValueClass$A$$anonfun$g$2$1", a, am, gfun, gfunfun) + testInner("NestedInValueClass$A$$anonfun$g$2$1$$anonfun$apply$4", am, gfun, gfunfun) + testInner("NestedInValueClass$A$$anonfun$f$extension$1", a, am, ffun, ffunfun) + testInner("NestedInValueClass$A$$anonfun$f$extension$1$$anonfun$apply$5", am, ffun, ffunfun) + } } def show(): Unit = { @@ -347,5 +583,10 @@ object Test extends BytecodeTest { testA22() testA23() testA24() + testSI_9105() + testSI_9124() + testImplClassesTopLevel() + testSpecializedClassesTopLevel() + testNestedInValueClass() } } diff --git a/test/files/jvm/innerClassEnclMethodJavaReflection.scala b/test/files/jvm/innerClassEnclMethodJavaReflection.scala new file mode 100644 index 0000000000..ee39cb43bf --- /dev/null +++ b/test/files/jvm/innerClassEnclMethodJavaReflection.scala @@ -0,0 +1,65 @@ +import scala.reflect.io._ +import java.net.URLClassLoader + +object Test extends App { + val jarsOrDirectories = Set("partest.lib", "partest.reflect", "partest.comp") map sys.props + + object AllowedMissingClass { + // Some classes in scala-compiler.jar have references to jline / ant classes, which seem to be + // not on the classpath. We just skip over those classes. + // PENDING: for now we also allow missing $anonfun classes: the optimizer may eliminate some closures + // that are refferred to in EnclosingClass attributes. SI-9136 + val allowedMissingPackages = Set("jline", "org.apache.tools.ant", "$anonfun") + + def ok(t: Throwable) = { + allowedMissingPackages.exists(p => t.getMessage.replace('/', '.').contains(p)) + } + + def unapply(t: Throwable): Option[Throwable] = t match { + case _: NoClassDefFoundError | _: ClassNotFoundException | _: TypeNotPresentException if ok(t) => Some(t) + case _ => None + } + } + + jarsOrDirectories foreach testClasses + + def testClasses(jarOrDirectory: String): Unit = { + val classPath = AbstractFile.getDirectory(new java.io.File(jarOrDirectory)) + + def flatten(f: AbstractFile): Iterator[AbstractFile] = + if (f.isClassContainer) f.iterator.flatMap(flatten) + else Iterator(f) + + val classFullNames = flatten(classPath).filter(_.hasExtension("class")).map(_.path.replace("/", ".").replaceAll(".class$", "")) + + // it seems that Class objects can only be GC'd together with their class loader + // (http://stackoverflow.com/questions/2433261/when-and-how-are-classes-garbage-collected-in-java) + // if we just use the same class loader for the entire test (Class.forName), we run out of PermGen + // even with that, we still neeed a PermGen of 90M or so, the default 64 is not enough. I tried + // using one class loader per 100 classes, but that didn't help, the classes didn't get GC'd. + val classLoader = new URLClassLoader(Array(classPath.toURL)) + + val faulty = new collection.mutable.ListBuffer[(String, Throwable)] + + def tryGetClass(name: String) = try { + Some[Class[_]](classLoader.loadClass(name)) + } catch { + case AllowedMissingClass(_) => None + } + + for (name <- classFullNames; cls <- tryGetClass(name)) { + try { + cls.getEnclosingMethod + cls.getEnclosingClass + cls.getEnclosingConstructor + cls.getDeclaredClasses + } catch { + case AllowedMissingClass(_) => + case t: Throwable => faulty += ((name, t)) + } + } + + if (faulty.nonEmpty) + println(faulty.toList mkString "\n") + } +} diff --git a/test/files/jvm/javaReflection.check b/test/files/jvm/javaReflection.check index d40599507d..8180ecff8a 100644 --- a/test/files/jvm/javaReflection.check +++ b/test/files/jvm/javaReflection.check @@ -5,7 +5,7 @@ A$$anonfun$$lessinit$greater$1 / null (canon) / $anonfun$$lessinit$greater$1 (si - properties : true (local) / false (member) A$$anonfun$$lessinit$greater$1$$anonfun$apply$1 / null (canon) / $anonfun$apply$1 (simple) - declared cls: List() -- enclosing : null (declaring cls) / class A$$anonfun$$lessinit$greater$1 (cls) / null (constr) / public final scala.Function0 A$$anonfun$$lessinit$greater$1.apply() (meth) +- enclosing : null (declaring cls) / class A$$anonfun$$lessinit$greater$1 (cls) / null (constr) / null (meth) - properties : true (local) / false (member) A$$anonfun$2 / null (canon) / $anonfun$2 (simple) - declared cls: List() diff --git a/test/files/jvm/t9105.check b/test/files/jvm/t9105.check new file mode 100644 index 0000000000..34750833f1 --- /dev/null +++ b/test/files/jvm/t9105.check @@ -0,0 +1,18 @@ +#partest !-Ydelambdafy:method +(class C$$anonfun$1$A$1,class C$$anonfun$1,null) +(class C$$anonfun$1$B$1,class C$$anonfun$1,private final java.lang.Object C$$anonfun$1.m$1()) +(class C$$anonfun$1$C$1,class C$$anonfun$1,null) +(class C$$anonfun$1$$anonfun$2$D$1,class C$$anonfun$1$$anonfun$2,null) +(class C$$anonfun$met$1$E$1,class C$$anonfun$met$1,null) +(class C$$anonfun$met$1$F$1,class C$$anonfun$met$1,private final java.lang.Object C$$anonfun$met$1.m$2()) +(class C$$anonfun$met$1$G$1,class C$$anonfun$met$1,null) +(class C$$anonfun$met$1$$anonfun$3$H$1,class C$$anonfun$met$1$$anonfun$3,null) +#partest -Ydelambdafy:method +(class C$A$1,class C,null) +(class C$B$1,class C,private final java.lang.Object C.m$1()) +(class C$C$1,class C,null) +(class C$D$1,class C,null) +(class C$E$1,class C,public scala.Function0 C.met()) +(class C$F$1,class C,private final java.lang.Object C.m$2()) +(class C$G$1,class C,public scala.Function0 C.met()) +(class C$H$1,class C,public scala.Function0 C.met()) diff --git a/test/files/jvm/t9105.scala b/test/files/jvm/t9105.scala new file mode 100644 index 0000000000..636ee8a768 --- /dev/null +++ b/test/files/jvm/t9105.scala @@ -0,0 +1,22 @@ +class C { + val fun = () => { + class A + def m: Object = { class B; new B } + val f: Object = { class C; new C } + val g = () => { class D; new D } + List[Object](new A, m, f, g()) + } + def met = () => { + class E + def m: Object = { class F; new F } + val f: Object = { class G; new G } + val g = () => { class H; new H } + List[Object](new E, m, f, g()) + } +} + +object Test extends App { + val x = new C().fun.apply() ::: new C().met.apply() + val results = x.map(_.getClass).map(cls => (cls, cls.getEnclosingClass, cls.getEnclosingMethod)) + println(results.mkString("\n")) +} diff --git a/versions.properties b/versions.properties index 62aebc41b2..fa08e56346 100644 --- a/versions.properties +++ b/versions.properties @@ -27,7 +27,7 @@ actors-migration.version.number=1.1.0 jline.version=2.12.1 # external modules, used internally (not shipped) -partest.version.number=1.0.3 +partest.version.number=1.0.5 scalacheck.version.number=1.11.4 # TODO: modularize the compiler |