diff options
Diffstat (limited to 'src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala')
-rw-r--r-- | src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala | 730 |
1 files changed, 558 insertions, 172 deletions
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala index 1b97681743..a32c21795d 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala @@ -8,7 +8,6 @@ package tools.nsc package backend.jvm import scala.tools.asm -import scala.collection.mutable import scala.tools.nsc.io.AbstractFile import GenBCode._ import BackendReporting._ @@ -22,8 +21,266 @@ import BackendReporting._ */ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { import global._ + import definitions._ import bTypes._ import coreBTypes._ + import BTypes.{InternalName, InlineInfo, MethodInlineInfo} + + /** + * 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.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) { + // lambda lift renames symbols and may accidentally introduce `$lambda` into a class name, making `isDelambdafyFunction` true. + // 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) = { + // 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). + val enclCls = nextEnclosingClass(method) + exitingPickler(enclCls.isDerivedValueClass) && method.owner != enclCls + } + + 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)) + 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] = { + // specialized classes are always top-level, see comment in BTypes + if (isAnonymousOrLocalClass(classSym) && !considerAsTopLevelImplementationArtifact(classSym)) { + val enclosingClass = enclosingClassForEnclosingMethodAttribute(classSym) + val methodOpt = enclosingMethodForEnclosingMethodAttribute(classSym) + for (m <- methodOpt) assert(m.owner == enclosingClass, s"the owner of the enclosing method ${m.locationString} should be the same as the enclosing class $enclosingClass") + 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) + + /** + * 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 isEffectivelyFinal = classSym.isEffectivelyFinal + + val sam = { + if (classSym.isEffectivelyFinal) None + else { + // Phase travel necessary. For example, nullary methods (getter of an abstract val) get an + // empty parameter list in later phases and would therefore be picked as SAM. + val samSym = exitingPickler(definitions.samOf(classSym.tpe)) + if (samSym == NoSymbol) None + else Some(samSym.javaSimpleName.toString + methodSymToDescriptor(samSym)) + } + } + + 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) + + // In `trait T { object O }`, `oSym.isEffectivelyFinalOrNotOverridden` is true, but the + // method is abstract in bytecode, `defDef.rhs.isEmpty`. Abstract methods are excluded + // so they are not marked final in the InlineInfo attribute. + // + // However, due to https://github.com/scala/scala-dev/issues/126, this currently does not + // work, the abstract accessor for O will be marked effectivelyFinal. + val effectivelyFinal = methodSym.isEffectivelyFinalOrNotOverridden && !methodSym.isDeferred + + val info = MethodInlineInfo( + effectivelyFinal = effectivelyFinal, + annotatedInline = methodSym.hasAnnotation(ScalaInlineClass), + annotatedNoInline = methodSym.hasAnnotation(ScalaNoInlineClass) + ) + Some((signature, info)) + } + }).toMap + + InlineInfo(isEffectivelyFinal, sam, methodInlineInfos, warning) + } /* * must-single-thread @@ -191,20 +448,18 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { } /* - * Populates the InnerClasses JVM attribute with `refedInnerClasses`. - * In addition to inner classes mentioned somewhere in `jclass` (where `jclass` is a class file being emitted) - * `refedInnerClasses` should contain those inner classes defined as direct member classes of `jclass` - * but otherwise not mentioned in `jclass`. + * Populates the InnerClasses JVM attribute with `refedInnerClasses`. See also the doc on inner + * classes in BTypes.scala. * - * `refedInnerClasses` may contain duplicates, - * need not contain the enclosing inner classes of each inner class it lists (those are looked up for consistency). + * `refedInnerClasses` may contain duplicates, need not contain the enclosing inner classes of + * each inner class it lists (those are looked up and included). * - * This method serializes in the InnerClasses JVM attribute in an appropriate order, - * not necessarily that given by `refedInnerClasses`. + * This method serializes in the InnerClasses JVM attribute in an appropriate order, not + * necessarily that given by `refedInnerClasses`. * * can-multi-thread */ - final def addInnerClassesASM(jclass: asm.ClassVisitor, refedInnerClasses: List[ClassBType]) { + final def addInnerClasses(jclass: asm.ClassVisitor, refedInnerClasses: List[ClassBType]) { val allNestedClasses = refedInnerClasses.flatMap(_.enclosingNestedClassesChain.get).distinct // sorting ensures nested classes are listed after their enclosing class thus satisfying the Eclipse Java compiler @@ -310,85 +565,122 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { final val emitLines = debugLevel >= 2 final val emitVars = debugLevel >= 3 - /* - * Contains class-symbols that: - * (a) are known to denote inner classes - * (b) are mentioned somewhere in the class being generated. - * - * In other words, the lifetime of `innerClassBufferASM` is associated to "the class being generated". - */ - final val innerClassBufferASM = mutable.Set.empty[ClassBType] - /** - * The class internal name for a given class symbol. If the symbol describes a nested class, the - * ClassBType is added to the innerClassBufferASM. + * The class internal name for a given class symbol. */ final def internalName(sym: Symbol): String = { // For each java class, the scala compiler creates a class and a module (thus a module class). - // If the `sym` is a java module class, we use the java class instead. This ensures that we - // register the class (instead of the module class) in innerClassBufferASM. + // If the `sym` is a java module class, we use the java class instead. This ensures that the + // ClassBType is created from the main class (instead of the module class). // The two symbols have the same name, so the resulting internalName is the same. // Phase travel (exitingPickler) required for SI-6613 - linkedCoC is only reliable in early phases (nesting) val classSym = if (sym.isJavaDefined && sym.isModuleClass) exitingPickler(sym.linkedClassOfClass) else sym - getClassBTypeAndRegisterInnerClass(classSym).internalName + classBTypeFromSymbol(classSym).internalName } + } // end of trait BCInnerClassGen + + trait BCAnnotGen extends BCInnerClassGen { + private lazy val AnnotationRetentionPolicyModule = AnnotationRetentionPolicyAttr.companionModule + private lazy val AnnotationRetentionPolicySourceValue = AnnotationRetentionPolicyModule.tpe.member(TermName("SOURCE")) + private lazy val AnnotationRetentionPolicyClassValue = AnnotationRetentionPolicyModule.tpe.member(TermName("CLASS")) + private lazy val AnnotationRetentionPolicyRuntimeValue = AnnotationRetentionPolicyModule.tpe.member(TermName("RUNTIME")) /** - * The ClassBType for a class symbol. If the class is nested, the ClassBType is added to the - * innerClassBufferASM. + * Annotations are not processed by the compilation pipeline like ordinary trees. Instead, the + * typer extracts them into [[AnnotationInfo]] objects which are attached to the corresponding + * symbol (sym.annotations) or type (as an AnnotatedType, eliminated by erasure). * - * TODO: clean up the way we track referenced inner classes. - * doing it during code generation is not correct when the optimizer changes the code. + * For Scala annotations this is OK: they are stored in the pickle and ignored by the backend. + * Java annoations on the other hand are additionally emitted to the classfile in Java's format. + * + * This means that [[Type]] instances within an AnnotaionInfo reach the backend non-erased. Examples: + * - @(javax.annotation.Resource @annotation.meta.getter) val x = 0 + * Here, annotationInfo.atp is an AnnotatedType. + * - @SomeAnnotation[T] val x = 0 + * In principle, the annotationInfo.atp is a non-erased type ref. However, this cannot + * actually happen because Java annotations cannot be generic. + * - @javax.annotation.Resource(`type` = classOf[List[_]]) val x = 0 + * The annotationInfo.assocs contains a LiteralAnnotArg(Constant(tp)) where tp is the + * non-erased existential type. */ - final def getClassBTypeAndRegisterInnerClass(sym: Symbol): ClassBType = { - val r = classBTypeFromSymbol(sym) - if (r.isNestedClass.get) innerClassBufferASM += r - r + def erasedType(tp: Type): Type = enteringErasure { + // make sure we don't erase value class references to the type that the value class boxes + // this is basically the same logic as in erasure's preTransform, case Literal(classTag). + tp.dealiasWiden match { + case tr @ TypeRef(_, clazz, _) if clazz.isDerivedValueClass => erasure.scalaErasure.eraseNormalClassRef(tr) + case tpe => erasure.erasure(tpe.typeSymbol)(tpe) + } } - /** - * The BType for a type reference. If the result is a ClassBType for a nested class, it is added - * to the innerClassBufferASM. - * TODO: clean up the way we track referenced inner classes. - */ - final def toTypeKind(t: Type): BType = typeToBType(t) match { - case c: ClassBType if c.isNestedClass.get => - innerClassBufferASM += c - c - case r => r + def descriptorForErasedType(tp: Type): String = typeToBType(erasedType(tp)).descriptor + + /** Whether an annotation should be emitted as a Java annotation + * .initialize: if 'annot' is read from pickle, atp might be uninitialized + */ + private def shouldEmitAnnotation(annot: AnnotationInfo) = { + annot.symbol.initialize.isJavaDefined && + annot.matches(ClassfileAnnotationClass) && + retentionPolicyOf(annot) != AnnotationRetentionPolicySourceValue && + annot.args.isEmpty } - /** - * Class components that are nested classes are added to the innerClassBufferASM. - * TODO: clean up the way we track referenced inner classes. - */ - final def asmMethodType(msym: Symbol): MethodBType = { - val r = methodBTypeFromSymbol(msym) - (r.returnType :: r.argumentTypes) foreach { - case c: ClassBType if c.isNestedClass.get => innerClassBufferASM += c + private 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 } - r } - /** - * The jvm descriptor of a type. If `t` references a nested class, its ClassBType is added to - * the innerClassBufferASM. - */ - final def descriptor(t: Type): String = { toTypeKind(t).descriptor } - - /** - * The jvm descriptor for a symbol. If `sym` represents a nested class, its ClassBType is added - * to the innerClassBufferASM. - */ - final def descriptor(sym: Symbol): String = { getClassBTypeAndRegisterInnerClass(sym).descriptor } - - } // end of trait BCInnerClassGen - - trait BCAnnotGen extends BCInnerClassGen { + 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 ubytesToCharArray(bytes: Array[Byte]): Array[Char] = { + val ca = new Array[Char](bytes.length) + var idx = 0 + while(idx < bytes.length) { + val b: Byte = bytes(idx) + assert((b & ~0x7f) == 0) + ca(idx) = b.asInstanceOf[Char] + idx += 1 + } + ca + } - import genASM.{ubytesToCharArray, arrEncode} - import bCodeAsmCommon.{shouldEmitAnnotation, isRuntimeVisible} + final def arrEncode(sb: ScalaSigBytes): Array[String] = { + var strs: List[String] = Nil + val bSeven: Array[Byte] = sb.sevenBitsMayBeZero + // chop into slices of at most 65535 bytes, counting 0x00 as taking two bytes (as per JVMS 4.4.7 The CONSTANT_Utf8_info Structure) + var prevOffset = 0 + var offset = 0 + var encLength = 0 + while(offset < bSeven.length) { + val deltaEncLength = (if(bSeven(offset) == 0) 2 else 1) + val newEncLength = encLength.toLong + deltaEncLength + if(newEncLength >= 65535) { + val ba = bSeven.slice(prevOffset, offset) + strs ::= new java.lang.String(ubytesToCharArray(ba)) + encLength = 0 + prevOffset = offset + } else { + encLength += deltaEncLength + offset += 1 + } + } + if(prevOffset < offset) { + assert(offset == bSeven.length) + val ba = bSeven.slice(prevOffset, offset) + strs ::= new java.lang.String(ubytesToCharArray(ba)) + } + assert(strs.size > 1, "encode instead as one String via strEncode()") // TODO too strict? + strs.reverse.toArray + } /* * can-multi-thread @@ -420,9 +712,10 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { case StringTag => assert(const.value != null, const) // TODO this invariant isn't documented in `case class Constant` av.visit(name, const.stringValue) // `stringValue` special-cases null, but that execution path isn't exercised for a const with StringTag - case ClazzTag => av.visit(name, toTypeKind(const.typeValue).toASMType) + case ClazzTag => + av.visit(name, typeToBType(erasedType(const.typeValue)).toASMType) case EnumTag => - val edesc = descriptor(const.tpe) // the class descriptor of the enumeration class. + val edesc = descriptorForErasedType(const.tpe) // the class descriptor of the enumeration class. val evalue = const.symbolValue.name.toString // value the actual enumeration value. av.visitEnum(name, edesc, evalue) } @@ -435,7 +728,7 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { av.visit(name, strEncode(sb)) } else { val arrAnnotV: asm.AnnotationVisitor = av.visitArray(name) - for(arg <- genASM.arrEncode(sb)) { arrAnnotV.visit(name, arg) } + for(arg <- arrEncode(sb)) { arrAnnotV.visit(name, arg) } arrAnnotV.visitEnd() } // for the lazy val in ScalaSigBytes to be GC'ed, the invoker of emitAnnotations() should hold the ScalaSigBytes in a method-local var that doesn't escape. @@ -447,7 +740,7 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { case NestedAnnotArg(annInfo) => val AnnotationInfo(typ, args, assocs) = annInfo assert(args.isEmpty, args) - val desc = descriptor(typ) // the class descriptor of the nested annotation class + val desc = descriptorForErasedType(typ) // the class descriptor of the nested annotation class val nestedVisitor = av.visitAnnotation(name, desc) emitAssocs(nestedVisitor, assocs) } @@ -472,7 +765,7 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { for(annot <- annotations; if shouldEmitAnnotation(annot)) { val AnnotationInfo(typ, args, assocs) = annot assert(args.isEmpty, args) - val av = cw.visitAnnotation(descriptor(typ), isRuntimeVisible(annot)) + val av = cw.visitAnnotation(descriptorForErasedType(typ), isRuntimeVisible(annot)) emitAssocs(av, assocs) } } @@ -484,7 +777,7 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { for(annot <- annotations; if shouldEmitAnnotation(annot)) { val AnnotationInfo(typ, args, assocs) = annot assert(args.isEmpty, args) - val av = mw.visitAnnotation(descriptor(typ), isRuntimeVisible(annot)) + val av = mw.visitAnnotation(descriptorForErasedType(typ), isRuntimeVisible(annot)) emitAssocs(av, assocs) } } @@ -496,7 +789,7 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { for(annot <- annotations; if shouldEmitAnnotation(annot)) { val AnnotationInfo(typ, args, assocs) = annot assert(args.isEmpty, args) - val av = fw.visitAnnotation(descriptor(typ), isRuntimeVisible(annot)) + val av = fw.visitAnnotation(descriptorForErasedType(typ), isRuntimeVisible(annot)) emitAssocs(av, assocs) } } @@ -511,16 +804,40 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { annot <- annots) { val AnnotationInfo(typ, args, assocs) = annot assert(args.isEmpty, args) - val pannVisitor: asm.AnnotationVisitor = jmethod.visitParameterAnnotation(idx, descriptor(typ), isRuntimeVisible(annot)) + val pannVisitor: asm.AnnotationVisitor = jmethod.visitParameterAnnotation(idx, descriptorForErasedType(typ), isRuntimeVisible(annot)) emitAssocs(pannVisitor, assocs) } } + /* + * must-single-thread + */ + def emitParamNames(jmethod: asm.MethodVisitor, params: List[Symbol]) = { + for (param <- params) { + var access = asm.Opcodes.ACC_FINAL + if (param.isArtifact) + access |= asm.Opcodes.ACC_SYNTHETIC + jmethod.visitParameter(param.name.decoded, access) + } + } } // end of trait BCAnnotGen trait BCJGenSigGen { - def getCurrentCUnit(): CompilationUnit + // @M don't generate java generics sigs for (members of) implementation + // classes, as they are monomorphic (TODO: ok?) + private def needsGenericSignature(sym: Symbol) = !( + // PP: This condition used to include sym.hasExpandedName, but this leads + // to the total loss of generic information if a private member is + // accessed from a closure: both the field and the accessor were generated + // without it. This is particularly bad because the availability of + // generic information could disappear as a consequence of a seemingly + // unrelated change. + settings.Ynogenericsig + || sym.isArtifact + || sym.isLiftedMethod + || sym.isBridge + ) /* @return * - `null` if no Java signature is to be added (`null` is what ASM expects in these cases). @@ -528,7 +845,63 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { * * must-single-thread */ - def getGenericSignature(sym: Symbol, owner: Symbol): String = genASM.getGenericSignature(sym, owner, getCurrentCUnit()) + def getGenericSignature(sym: Symbol, owner: Symbol): String = { + val memberTpe = enteringErasure(owner.thisType.memberInfo(sym)) + getGenericSignature(sym, owner, memberTpe) + } + + def getGenericSignature(sym: Symbol, owner: Symbol, memberTpe: Type): String = { + if (!needsGenericSignature(sym)) { return null } + + val jsOpt: Option[String] = erasure.javaSig(sym, memberTpe) + if (jsOpt.isEmpty) { return null } + + val sig = jsOpt.get + log(sig) // This seems useful enough in the general case. + + def wrap(op: => Unit) = { + try { op; true } + catch { case _: Throwable => false } + } + + if (settings.Xverify) { + // Run the signature parser to catch bogus signatures. + val isValidSignature = wrap { + // Alternative: scala.tools.reflect.SigParser (frontend to sun.reflect.generics.parser.SignatureParser) + import scala.tools.asm.util.CheckClassAdapter + if (sym.isMethod) { CheckClassAdapter checkMethodSignature sig } // requires asm-util.jar + else if (sym.isTerm) { CheckClassAdapter checkFieldSignature sig } + else { CheckClassAdapter checkClassSignature sig } + } + + if(!isValidSignature) { + reporter.warning(sym.pos, + sm"""|compiler bug: created invalid generic signature for $sym in ${sym.owner.skipPackageObject.fullName} + |signature: $sig + |if this is reproducible, please report bug at https://issues.scala-lang.org/ + """.trim) + return null + } + } + + if ((settings.check containsName phaseName)) { + val normalizedTpe = enteringErasure(erasure.prepareSigMap(memberTpe)) + val bytecodeTpe = owner.thisType.memberInfo(sym) + if (!sym.isType && !sym.isConstructor && !(erasure.erasure(sym)(normalizedTpe) =:= bytecodeTpe)) { + reporter.warning(sym.pos, + sm"""|compiler bug: created generic signature for $sym in ${sym.owner.skipPackageObject.fullName} that does not conform to its erasure + |signature: $sig + |original type: $memberTpe + |normalized type: $normalizedTpe + |erasure type: $bytecodeTpe + |if this is reproducible, please report bug at http://issues.scala-lang.org/ + """.trim) + return null + } + } + + sig + } } // end of trait BCJGenSigGen @@ -541,11 +914,15 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { * must-single-thread */ def addRemoteExceptionAnnot(isRemoteClass: Boolean, isJMethodPublic: Boolean, meth: Symbol) { - val needsAnnotation = ( - ( isRemoteClass || - isRemote(meth) && isJMethodPublic - ) && !(meth.throwsAnnotations contains definitions.RemoteExceptionClass) - ) + def hasThrowsRemoteException = meth.annotations.exists { + case ThrownException(exc) => exc.typeSymbol == definitions.RemoteExceptionClass + case _ => false + } + val needsAnnotation = { + (isRemoteClass || + isRemote(meth) && isJMethodPublic + ) && !hasThrowsRemoteException + } if (needsAnnotation) { val c = Constant(definitions.RemoteExceptionClass.tpe) val arg = Literal(c) setType c.tpe @@ -557,10 +934,26 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { * * must-single-thread */ - private def addForwarder(isRemoteClass: Boolean, jclass: asm.ClassVisitor, module: Symbol, m: Symbol) { + private def addForwarder(isRemoteClass: Boolean, jclass: asm.ClassVisitor, module: Symbol, m: Symbol): Unit = { + def staticForwarderGenericSignature(sym: Symbol, moduleClass: Symbol): String = { + if (sym.isDeferred) null // only add generic signature if method concrete; bug #1745 + else { + // SI-3452 Static forwarder generation uses the same erased signature as the method if forwards to. + // By rights, it should use the signature as-seen-from the module class, and add suitable + // primitive and value-class boxing/unboxing. + // But for now, just like we did in mixin, we just avoid writing a wrong generic signature + // (one that doesn't erase to the actual signature). See run/t3452b for a test case. + val memberTpe = enteringErasure(moduleClass.thisType.memberInfo(sym)) + val erasedMemberType = erasure.erasure(sym)(memberTpe) + if (erasedMemberType =:= sym.info) + getGenericSignature(sym, moduleClass, memberTpe) + else null + } + } + val moduleName = internalName(module) val methodInfo = module.thisType.memberInfo(m) - val paramJavaTypes: List[BType] = methodInfo.paramTypes map toTypeKind + val paramJavaTypes: List[BType] = methodInfo.paramTypes map typeToBType // val paramNames = 0 until paramJavaTypes.length map ("x_" + _) /* Forwarders must not be marked final, @@ -574,12 +967,12 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { ) // TODO needed? for(ann <- m.annotations) { ann.symbol.initialize } - val jgensig = genASM.staticForwarderGenericSignature(m, module, getCurrentCUnit()) + val jgensig = staticForwarderGenericSignature(m, module) addRemoteExceptionAnnot(isRemoteClass, hasPublicBitSet(flags), m) val (throws, others) = m.annotations partition (_.symbol == definitions.ThrowsClass) val thrownExceptions: List[String] = getExceptions(throws) - val jReturnType = toTypeKind(methodInfo.resultType) + val jReturnType = typeToBType(methodInfo.resultType) val mdesc = MethodBType(paramJavaTypes, jReturnType).descriptor val mirrorMethodName = m.javaSimpleName.toString val mirrorMethod: asm.MethodVisitor = jclass.visitMethod( @@ -595,7 +988,7 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { mirrorMethod.visitCode() - mirrorMethod.visitFieldInsn(asm.Opcodes.GETSTATIC, moduleName, strMODULE_INSTANCE_FIELD, descriptor(module)) + mirrorMethod.visitFieldInsn(asm.Opcodes.GETSTATIC, moduleName, strMODULE_INSTANCE_FIELD, classBTypeFromSymbol(module).descriptor) var index = 0 for(jparamType <- paramJavaTypes) { @@ -604,7 +997,7 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { index += jparamType.size } - mirrorMethod.visitMethodInsn(asm.Opcodes.INVOKEVIRTUAL, moduleName, mirrorMethodName, asmMethodType(m).descriptor, false) + mirrorMethod.visitMethodInsn(asm.Opcodes.INVOKEVIRTUAL, moduleName, mirrorMethodName, methodBTypeFromSymbol(m).descriptor, false) mirrorMethod.visitInsn(jReturnType.typedOpcode(asm.Opcodes.IRETURN)) mirrorMethod.visitMaxs(0, 0) // just to follow protocol, dummy arguments @@ -629,9 +1022,9 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { } debuglog(s"Potentially conflicting names for forwarders: $conflictingNames") - for (m <- moduleClass.info.membersBasedOnFlags(bCodeAsmCommon.ExcludedForwarderFlags, symtab.Flags.METHOD)) { + for (m <- moduleClass.info.membersBasedOnFlags(BCodeHelpers.ExcludedForwarderFlags, symtab.Flags.METHOD)) { if (m.isType || m.isDeferred || (m.owner eq definitions.ObjectClass) || m.isConstructor) - debuglog(s"No forwarder for '$m' from $jclassName to '$moduleClass'") + debuglog(s"No forwarder for '$m' from $jclassName to '$moduleClass': ${m.isType} || ${m.isDeferred} || ${m.owner eq definitions.ObjectClass} || ${m.isConstructor}") else if (conflictingNames(m.name)) log(s"No forwarder for $m due to conflict with ${linkedClass.info.member(m.name)}") else if (m.hasAccessBoundary) @@ -654,8 +1047,11 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { * must-single-thread */ def getExceptions(excs: List[AnnotationInfo]): List[String] = { - for (ThrownException(exc) <- excs.distinct) - yield internalName(exc) + for (ThrownException(tp) <- excs.distinct) + yield { + val erased = erasedType(tp) + internalName(erased.typeSymbol) + } } } // end of trait BCForwardersGen @@ -682,60 +1078,6 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { new java.lang.Long(id) ).visitEnd() } - - /** - * Add: - * private static java.util.Map $deserializeLambdaCache$ = null - * private static Object $deserializeLambda$(SerializedLambda l) { - * var cache = $deserializeLambdaCache$ - * if (cache eq null) { - * cache = new java.util.HashMap() - * $deserializeLambdaCache$ = cache - * } - * return scala.compat.java8.runtime.LambdaDeserializer.deserializeLambda(MethodHandles.lookup(), cache, l); - * } - */ - def addLambdaDeserialize(clazz: Symbol, jclass: asm.ClassVisitor): Unit = { - val cw = jclass - import scala.tools.asm.Opcodes._ - - // Need to force creation of BTypes for these as `getCommonSuperClass` is called on - // automatically computing the max stack size (`visitMaxs`) during method writing. - javaUtilHashMapReference - javaUtilMapReference - - cw.visitInnerClass("java/lang/invoke/MethodHandles$Lookup", "java/lang/invoke/MethodHandles", "Lookup", ACC_PUBLIC + ACC_FINAL + ACC_STATIC) - - { - val fv = cw.visitField(ACC_PRIVATE + ACC_STATIC + ACC_SYNTHETIC, "$deserializeLambdaCache$", "Ljava/util/Map;", null, null) - fv.visitEnd() - } - - { - val mv = cw.visitMethod(ACC_PRIVATE + ACC_STATIC + ACC_SYNTHETIC, "$deserializeLambda$", "(Ljava/lang/invoke/SerializedLambda;)Ljava/lang/Object;", null, null) - mv.visitCode() - // javaBinaryName returns the internal name of a class. Also used in BTypesFromsymbols.classBTypeFromSymbol. - mv.visitFieldInsn(GETSTATIC, clazz.javaBinaryName.toString, "$deserializeLambdaCache$", "Ljava/util/Map;") - mv.visitVarInsn(ASTORE, 1) - mv.visitVarInsn(ALOAD, 1) - val l0 = new asm.Label() - mv.visitJumpInsn(IFNONNULL, l0) - mv.visitTypeInsn(NEW, "java/util/HashMap") - mv.visitInsn(DUP) - mv.visitMethodInsn(INVOKESPECIAL, "java/util/HashMap", "<init>", "()V", false) - mv.visitVarInsn(ASTORE, 1) - mv.visitVarInsn(ALOAD, 1) - mv.visitFieldInsn(PUTSTATIC, clazz.javaBinaryName.toString, "$deserializeLambdaCache$", "Ljava/util/Map;") - mv.visitLabel(l0) - mv.visitFieldInsn(GETSTATIC, "scala/compat/java8/runtime/LambdaDeserializer$", "MODULE$", "Lscala/compat/java8/runtime/LambdaDeserializer$;") - mv.visitMethodInsn(INVOKESTATIC, "java/lang/invoke/MethodHandles", "lookup", "()Ljava/lang/invoke/MethodHandles$Lookup;", false) - mv.visitVarInsn(ALOAD, 1) - mv.visitVarInsn(ALOAD, 0) - mv.visitMethodInsn(INVOKEVIRTUAL, "scala/compat/java8/runtime/LambdaDeserializer$", "deserializeLambda", "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/util/Map;Ljava/lang/invoke/SerializedLambda;)Ljava/lang/Object;", false) - mv.visitInsn(ARETURN) - mv.visitEnd() - } - } } // end of trait BCClassGen /* functionality for building plain and mirror classes */ @@ -748,9 +1090,6 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { /* builder of mirror classes */ class JMirrorBuilder extends JCommonBuilder { - private var cunit: CompilationUnit = _ - def getCurrentCUnit(): CompilationUnit = cunit; - /* Generate a mirror class for a top-level module. A mirror class is a class * containing only static methods that forward to the corresponding method * on the MODULE instance of the given Scala object. It will only be @@ -762,8 +1101,6 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { def genMirrorClass(moduleClass: Symbol, cunit: CompilationUnit): asm.tree.ClassNode = { assert(moduleClass.isModuleClass) assert(moduleClass.companionClass == NoSymbol, moduleClass) - innerClassBufferASM.clear() - this.cunit = cunit val bType = mirrorClassClassBType(moduleClass) val mirrorClass = new asm.tree.ClassNode @@ -772,7 +1109,7 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { bType.info.get.flags, bType.internalName, null /* no java-generic-signature */, - ObjectReference.internalName, + ObjectRef.internalName, EMPTY_STRING_ARRAY ) @@ -785,9 +1122,6 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { addForwarders(isRemote(moduleClass), mirrorClass, bType.internalName, moduleClass) - innerClassBufferASM ++= bType.info.get.nestedClasses - addInnerClassesASM(mirrorClass, innerClassBufferASM.toList) - mirrorClass.visitEnd() ("" + moduleClass.name) // this side-effect is necessary, really. @@ -811,18 +1145,15 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { def javaSimpleName(s: Symbol): String = { s.javaSimpleName.toString } - innerClassBufferASM.clear() + val beanInfoType = beanInfoClassClassBType(cls) - val flags = javaFlags(cls) - - val beanInfoName = (internalName(cls) + "BeanInfo") val beanInfoClass = new asm.tree.ClassNode beanInfoClass.visit( classfileVersion, - flags, - beanInfoName, + beanInfoType.info.get.flags, + beanInfoType.internalName, null, // no java-generic-signature - "scala/beans/ScalaBeanInfo", + sbScalaBeanInfoRef.internalName, EMPTY_STRING_ARRAY ) @@ -859,7 +1190,7 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { EMPTY_STRING_ARRAY // no throwable exceptions ) - val stringArrayJType: BType = ArrayBType(StringReference) + val stringArrayJType: BType = ArrayBType(StringRef) val conJType: BType = MethodBType( classBTypeFromSymbol(definitions.ClassClass) :: stringArrayJType :: stringArrayJType :: Nil, UNIT @@ -872,7 +1203,7 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { constructor.visitLdcInsn(new java.lang.Integer(fi)) if (f == null) { constructor.visitInsn(asm.Opcodes.ACONST_NULL) } else { constructor.visitLdcInsn(f) } - constructor.visitInsn(StringReference.typedOpcode(asm.Opcodes.IASTORE)) + constructor.visitInsn(StringRef.typedOpcode(asm.Opcodes.IASTORE)) fi += 1 } } @@ -885,12 +1216,12 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { // push the string array of field information constructor.visitLdcInsn(new java.lang.Integer(fieldList.length)) - constructor.visitTypeInsn(asm.Opcodes.ANEWARRAY, StringReference.internalName) + constructor.visitTypeInsn(asm.Opcodes.ANEWARRAY, StringRef.internalName) push(fieldList) // push the string array of method information constructor.visitLdcInsn(new java.lang.Integer(methodList.length)) - constructor.visitTypeInsn(asm.Opcodes.ANEWARRAY, StringReference.internalName) + constructor.visitTypeInsn(asm.Opcodes.ANEWARRAY, StringRef.internalName) push(methodList) // invoke the superclass constructor, which will do the @@ -901,9 +1232,6 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { constructor.visitMaxs(0, 0) // just to follow protocol, dummy arguments constructor.visitEnd() - innerClassBufferASM ++= classBTypeFromSymbol(cls).info.get.nestedClasses - addInnerClassesASM(beanInfoClass, innerClassBufferASM.toList) - beanInfoClass.visitEnd() beanInfoClass @@ -932,8 +1260,7 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { * must-single-thread */ def legacyAddCreatorCode(clinit: asm.MethodVisitor, cnode: asm.tree.ClassNode, thisName: String) { - // this tracks the inner class in innerClassBufferASM, if needed. - val androidCreatorType = getClassBTypeAndRegisterInnerClass(AndroidCreatorClass) + val androidCreatorType = classBTypeFromSymbol(AndroidCreatorClass) val tdesc_creator = androidCreatorType.descriptor cnode.visitField( @@ -975,3 +1302,62 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { } // end of trait JAndroidBuilder } + +object BCodeHelpers { + val ExcludedForwarderFlags = { + import scala.tools.nsc.symtab.Flags._ + // Should include DEFERRED but this breaks findMember. + SPECIALIZED | LIFTED | PROTECTED | STATIC | EXPANDEDNAME | BridgeAndPrivateFlags | MACRO + } + + /** + * 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 + } + + class TestOp(val op: Int) extends AnyVal { + import TestOp._ + def negate = this match { + case EQ => NE + case NE => EQ + case LT => GE + case GE => LT + case GT => LE + case LE => GT + } + def opcodeIF = asm.Opcodes.IFEQ + op + def opcodeIFICMP = asm.Opcodes.IF_ICMPEQ + op + } + + object TestOp { + val EQ = new TestOp(0) + val NE = new TestOp(1) + val LT = new TestOp(2) + val GE = new TestOp(3) + val GT = new TestOp(4) + val LE = new TestOp(5) + } + + class InvokeStyle(val style: Int) extends AnyVal { + import InvokeStyle._ + def isVirtual: Boolean = this == Virtual + def isStatic : Boolean = this == Static + def isSpecial: Boolean = this == Special + def isSuper : Boolean = this == Super + + def hasInstance = this != Static + } + + object InvokeStyle { + val Virtual = new InvokeStyle(0) // InvokeVirtual or InvokeInterface + val Static = new InvokeStyle(1) // InvokeStatic + val Special = new InvokeStyle(2) // InvokeSpecial (private methods, constructors) + val Super = new InvokeStyle(3) // InvokeSpecial (super calls) + } +} |