diff options
Diffstat (limited to 'src/compiler')
7 files changed, 276 insertions, 240 deletions
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala index cea264c57a..fe08c4355d 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala @@ -34,14 +34,6 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { * Functionality to build the body of ASM MethodNode, except for `synchronized` and `try` expressions. */ abstract class PlainBodyBuilder(cunit: CompilationUnit) extends PlainSkelBuilder(cunit) { - /* If the selector type has a member with the right name, - * it is the host class; otherwise the symbol's owner. - */ - def findHostClass(selector: Type, sym: Symbol) = selector member sym.name match { - case NoSymbol => debuglog(s"Rejecting $selector as host class for $sym") ; sym.owner - case _ => selector.typeSymbol - } - /* ---------------- helper utils for generating methods and code ---------------- */ def emit(opc: Int) { mnode.visitInsn(opc) } @@ -69,12 +61,14 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { def genStat(tree: Tree) { lineNumber(tree) tree match { - case Assign(lhs @ Select(_, _), rhs) => + case Assign(lhs @ Select(qual, _), rhs) => val isStatic = lhs.symbol.isStaticMember if (!isStatic) { genLoadQualifier(lhs) } genLoad(rhs, symInfoTK(lhs.symbol)) lineNumber(tree) - fieldStore(lhs.symbol) + // receiverClass is used in the bytecode to access the field. using sym.owner may lead to IllegalAccessError, SI-4283 + val receiverClass = qual.tpe.typeSymbol + fieldStore(lhs.symbol, receiverClass) case Assign(lhs, rhs) => val s = lhs.symbol @@ -169,21 +163,13 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { genLoad(args.head, INT) generatedType = k.asArrayBType.componentType bc.aload(elementType) - } - else if (scalaPrimitives.isArraySet(code)) { - args match { - case a1 :: a2 :: Nil => - genLoad(a1, INT) - genLoad(a2) - // the following line should really be here, but because of bugs in erasure - // we pretend we generate whatever type is expected from us. - //generatedType = UNIT - bc.astore(elementType) - case _ => - abort(s"Too many arguments for array set operation: $tree") - } - } - else { + } else if (scalaPrimitives.isArraySet(code)) { + val List(a1, a2) = args + genLoad(a1, INT) + genLoad(a2) + generatedType = UNIT + bc.astore(elementType) + } else { generatedType = INT emit(asm.Opcodes.ARRAYLENGTH) } @@ -338,26 +324,22 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { assert(tree.symbol.isModule, s"Selection of non-module from empty package: $tree sym: ${tree.symbol} at: ${tree.pos}") genLoadModule(tree) - case Select(qualifier, selector) => + case Select(qualifier, _) => val sym = tree.symbol generatedType = symInfoTK(sym) - val hostClass = findHostClass(qualifier.tpe, sym) - debuglog(s"Host class of $sym with qual $qualifier (${qualifier.tpe}) is $hostClass") val qualSafeToElide = treeInfo isQualifierSafeToElide qualifier - def genLoadQualUnlessElidable() { if (!qualSafeToElide) { genLoadQualifier(tree) } } - + // receiverClass is used in the bytecode to access the field. using sym.owner may lead to IllegalAccessError, SI-4283 + def receiverClass = qualifier.tpe.typeSymbol if (sym.isModule) { genLoadQualUnlessElidable() genLoadModule(tree) - } - else if (sym.isStaticMember) { + } else if (sym.isStaticMember) { genLoadQualUnlessElidable() - fieldLoad(sym, hostClass) - } - else { + fieldLoad(sym, receiverClass) + } else { genLoadQualifier(tree) - fieldLoad(sym, hostClass) + fieldLoad(sym, receiverClass) } case Ident(name) => @@ -410,24 +392,18 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { /* * must-single-thread */ - def fieldLoad( field: Symbol, hostClass: Symbol = null) { - fieldOp(field, isLoad = true, hostClass) - } + def fieldLoad(field: Symbol, hostClass: Symbol): Unit = fieldOp(field, isLoad = true, hostClass) + /* * must-single-thread */ - def fieldStore(field: Symbol, hostClass: Symbol = null) { - fieldOp(field, isLoad = false, hostClass) - } + def fieldStore(field: Symbol, hostClass: Symbol): Unit = fieldOp(field, isLoad = false, hostClass) /* * must-single-thread */ - private def fieldOp(field: Symbol, isLoad: Boolean, hostClass: Symbol) { - // LOAD_FIELD.hostClass , CALL_METHOD.hostClass , and #4283 - val owner = - if (hostClass == null) internalName(field.owner) - else internalName(hostClass) + private def fieldOp(field: Symbol, isLoad: Boolean, hostClass: Symbol): Unit = { + val owner = internalName(if (hostClass == null) field.owner else hostClass) val fieldJName = field.javaSimpleName.toString val fieldDescr = symInfoTK(field).descriptor val isStatic = field.isStaticMember @@ -435,7 +411,6 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { if (isLoad) { if (isStatic) asm.Opcodes.GETSTATIC else asm.Opcodes.GETFIELD } else { if (isStatic) asm.Opcodes.PUTSTATIC else asm.Opcodes.PUTFIELD } mnode.visitFieldInsn(opc, owner, fieldJName, fieldDescr) - } // ---------------- emitting constant values ---------------- @@ -532,21 +507,6 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { var generatedType = expectedType lineNumber(app) - def genSuperApply(hostClass: Symbol, fun: Symbol, args: List[Tree]) = { - // 'super' call: Note: since constructors are supposed to - // return an instance of what they construct, we have to take - // special care. On JVM they are 'void', and Scala forbids (syntactically) - // to call super constructors explicitly and/or use their 'returned' value. - // therefore, we can ignore this fact, and generate code that leaves nothing - // on the stack (contrary to what the type in the AST says). - - val invokeStyle = InvokeStyle.Super - mnode.visitVarInsn(asm.Opcodes.ALOAD, 0) - genLoadArguments(args, paramTKs(app)) - genCallMethod(fun, invokeStyle, app.pos, hostClass) - generatedType = methodBTypeFromSymbol(fun).returnType - } - app match { case Apply(TypeApply(fun, targs), _) => @@ -594,19 +554,33 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { generatedType = genTypeApply() - case Apply(fun @ Select(Super(qual, mix), _), args) => - val hostClass = qual.symbol.parentSymbols.filter(_.name == mix) match { - case Nil => - // We get here for trees created by SuperSelect which use tpnme.EMPTY as the super qualifier - // Subsequent code uses the owner of fun.symbol to target the call. - null - case parent :: Nil=> - parent - case parents => - devWarning("ambiguous parent class qualifier: " + qual.symbol.parentSymbols) - null + case Apply(fun @ Select(Super(_, _), _), args) => + def initModule() { + // we initialize the MODULE$ field immediately after the super ctor + if (!isModuleInitialized && + jMethodName == INSTANCE_CONSTRUCTOR_NAME && + fun.symbol.javaSimpleName.toString == INSTANCE_CONSTRUCTOR_NAME && + isStaticModuleClass(claszSymbol)) { + isModuleInitialized = true + mnode.visitVarInsn(asm.Opcodes.ALOAD, 0) + mnode.visitFieldInsn( + asm.Opcodes.PUTSTATIC, + thisBType.internalName, + strMODULE_INSTANCE_FIELD, + thisBType.descriptor + ) + } } - genSuperApply(hostClass, fun.symbol, args) + // 'super' call: Note: since constructors are supposed to + // return an instance of what they construct, we have to take + // special care. On JVM they are 'void', and Scala forbids (syntactically) + // to call super constructors explicitly and/or use their 'returned' value. + // therefore, we can ignore this fact, and generate code that leaves nothing + // on the stack (contrary to what the type in the AST says). + mnode.visitVarInsn(asm.Opcodes.ALOAD, 0) + genLoadArguments(args, paramTKs(app)) + generatedType = genCallMethod(fun.symbol, InvokeStyle.Super, app.pos) + initModule() // 'new' constructor call: Note: since constructors are // thought to return an instance of what they construct, @@ -675,80 +649,73 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { case app @ Apply(fun, args) => val sym = fun.symbol - if (sym.isLabel) { // jump to a label + if (sym.isLabel) { // jump to a label genLoadLabelArguments(args, labelDef(sym), app.pos) bc goTo programPoint(sym) } else if (isPrimitive(sym)) { // primitive method call generatedType = genPrimitiveOp(app, expectedType) - } else { // normal method call - - def genNormalMethodCall() { - - val invokeStyle = - if (sym.isStaticMember) InvokeStyle.Static - else if (sym.isPrivate || sym.isClassConstructor) InvokeStyle.Special - else InvokeStyle.Virtual - - if (invokeStyle.hasInstance) { - genLoadQualifier(fun) + } else { // normal method call + val invokeStyle = + if (sym.isStaticMember) InvokeStyle.Static + else if (sym.isPrivate || sym.isClassConstructor) InvokeStyle.Special + else InvokeStyle.Virtual + + if (invokeStyle.hasInstance) genLoadQualifier(fun) + genLoadArguments(args, paramTKs(app)) + + val Select(qual, _) = fun // fun is a Select, also checked in genLoadQualifier + if (sym == definitions.Array_clone) { + // Special-case Array.clone, introduced in 36ef60e. The goal is to generate this call + // as "[I.clone" instead of "java/lang/Object.clone". This is consistent with javac. + // Arrays have a public method `clone` (jls 10.7). + // + // The JVMS is not explicit about this, but that receiver type can be an array type + // descriptor (instead of a class internal name): + // invokevirtual #2; //Method "[I".clone:()Ljava/lang/Object + // + // Note that using `Object.clone()` would work as well, but only because the JVM + // relaxes protected access specifically if the receiver is an array: + // http://hg.openjdk.java.net/jdk8/jdk8/hotspot/file/87ee5ee27509/src/share/vm/interpreter/linkResolver.cpp#l439 + // Example: `class C { override def clone(): Object = "hi" }` + // Emitting `def f(c: C) = c.clone()` as `Object.clone()` gives a VerifyError. + val target: String = tpeTK(qual).asRefBType.classOrArrayType + val methodBType = methodBTypeFromSymbol(sym) + bc.invokevirtual(target, sym.javaSimpleName.toString, methodBType.descriptor, app.pos) + generatedType = methodBType.returnType + } else { + val receiverClass = if (!invokeStyle.isVirtual) null else { + // receiverClass is used in the bytecode to as the method receiver. using sym.owner + // may lead to IllegalAccessErrors, see 9954eaf / aladdin bug 455. + val qualSym = qual.tpe.typeSymbol + if (qualSym == ArrayClass) { + // For invocations like `Array(1).hashCode` or `.wait()`, use Object as receiver + // in the bytecode. Using the array descriptor (like we do for clone above) seems + // to work as well, but it seems safer not to change this. Javac also uses Object. + // Note that array apply/update/length are handled by isPrimitive (above). + assert(sym.owner == ObjectClass, s"unexpected array call: ${show(app)}") + ObjectClass + } else qualSym } - genLoadArguments(args, paramTKs(app)) - - // In "a couple cases", squirrel away a extra information (hostClass, targetTypeKind). TODO Document what "in a couple cases" refers to. - var hostClass: Symbol = null - var targetTypeKind: BType = null - fun match { - case Select(qual, _) => - val qualSym = findHostClass(qual.tpe, sym) - if (qualSym == ArrayClass) { - targetTypeKind = tpeTK(qual) - log(s"Stored target type kind for ${sym.fullName} as $targetTypeKind") - } - else { - hostClass = qualSym - if (qual.tpe.typeSymbol != qualSym) { - log(s"Precisified host class for $sym from ${qual.tpe.typeSymbol.fullName} to ${qualSym.fullName}") - } - } - - case _ => - } - if ((targetTypeKind != null) && (sym == definitions.Array_clone) && invokeStyle.isVirtual) { - // An invokevirtual points to a CONSTANT_Methodref_info which in turn points to a - // CONSTANT_Class_info of the receiver type. - // The JVMS is not explicit about this, but that receiver type may be an array type - // descriptor (instead of a class internal name): - // invokevirtual #2; //Method "[I".clone:()Ljava/lang/Object - val target: String = targetTypeKind.asRefBType.classOrArrayType - bc.invokevirtual(target, "clone", "()Ljava/lang/Object;", app.pos) - } - else { - genCallMethod(sym, invokeStyle, app.pos, hostClass) - // Check if the Apply tree has an InlineAnnotatedAttachment, added by the typer - // for callsites marked `f(): @inline/noinline`. For nullary calls, the attachment - // is on the Select node (not on the Apply node added by UnCurry). - def checkInlineAnnotated(t: Tree): Unit = { - if (t.hasAttachment[InlineAnnotatedAttachment]) lastInsn match { - case m: MethodInsnNode => - if (app.hasAttachment[NoInlineCallsiteAttachment.type]) noInlineAnnotatedCallsites += m - else inlineAnnotatedCallsites += m - case _ => - } else t match { - case Apply(fun, _) => checkInlineAnnotated(fun) - case _ => - } + generatedType = genCallMethod(sym, invokeStyle, app.pos, receiverClass) + + // Check if the Apply tree has an InlineAnnotatedAttachment, added by the typer + // for callsites marked `f(): @inline/noinline`. For nullary calls, the attachment + // is on the Select node (not on the Apply node added by UnCurry). + def recordInlineAnnotated(t: Tree): Unit = { + if (t.hasAttachment[InlineAnnotatedAttachment]) lastInsn match { + case m: MethodInsnNode => + if (app.hasAttachment[NoInlineCallsiteAttachment.type]) noInlineAnnotatedCallsites += m + else inlineAnnotatedCallsites += m + case _ => + } else t match { + case Apply(fun, _) => recordInlineAnnotated(fun) + case _ => } - checkInlineAnnotated(app) } - - } // end of genNormalMethodCall() - - genNormalMethodCall() - - generatedType = methodBTypeFromSymbol(sym).returnType + recordInlineAnnotated(app) + } } - } generatedType @@ -1026,11 +993,11 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { def genStringConcat(tree: Tree): BType = { lineNumber(tree) liftStringConcat(tree) match { - // Optimization for expressions of the form "" + x. We can avoid the StringBuilder. case List(Literal(Constant("")), arg) => genLoad(arg, ObjectRef) genCallMethod(String_valueOf, InvokeStyle.Static, arg.pos) + case concatenations => bc.genStartConcat(tree.pos) for (elem <- concatenations) { @@ -1047,74 +1014,77 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { bc.genConcat(elemType, loadedElem.pos) } bc.genEndConcat(tree.pos) - } - StringRef } - def genCallMethod(method: Symbol, style: InvokeStyle, pos: Position, hostClass0: Symbol = null) { - - val siteSymbol = claszSymbol - val hostSymbol = if (hostClass0 == null) method.owner else hostClass0 + /** + * Generate a method invocation. If `specificReceiver != null`, it is used as receiver in the + * invocation instruction, otherwise `method.owner`. A specific receiver class is needed to + * prevent an IllegalAccessError, (aladdin bug 455). + */ + def genCallMethod(method: Symbol, style: InvokeStyle, pos: Position, specificReceiver: Symbol = null): BType = { val methodOwner = method.owner - // info calls so that types are up to date; erasure may add lateINTERFACE to traits - hostSymbol.info ; methodOwner.info - - def needsInterfaceCall(sym: Symbol) = ( - sym.isTraitOrInterface - || sym.isJavaDefined && sym.isNonBottomSubClass(definitions.ClassfileAnnotationClass) - ) + // the class used in the invocation's method descriptor in the classfile + val receiverClass = { + if (specificReceiver != null) + assert(style.isVirtual || specificReceiver == methodOwner, s"specificReceiver can only be specified for virtual calls. $method - $specificReceiver") + + val useSpecificReceiver = specificReceiver != null && !specificReceiver.isBottomClass + val receiver = if (useSpecificReceiver) specificReceiver else methodOwner + + // workaround for a JVM bug: https://bugs.openjdk.java.net/browse/JDK-8154587 + // when an interface method overrides a member of Object (note that all interfaces implicitly + // have superclass Object), the receiver needs to be the interface declaring the override (and + // not a sub-interface that inherits it). example: + // trait T { override def clone(): Object = "" } + // trait U extends T + // class C extends U + // class D { def f(u: U) = u.clone() } + // The invocation `u.clone()` needs `T` as a receiver: + // - using Object is illegal, as Object.clone is protected + // - using U results in a `NoSuchMethodError: U.clone. This is the JVM bug. + // Note that a mixin forwarder is generated, so the correct method is executed in the end: + // class C { override def clone(): Object = super[T].clone() } + val isTraitMethodOverridingObjectMember = { + receiver != methodOwner && // fast path - the boolean is used to pick either of these two, if they are the same it does not matter + style.isVirtual && + receiver.isTraitOrInterface && + ObjectTpe.decl(method.name).exists && // fast path - compute overrideChain on the next line only if necessary + method.overrideChain.last.owner == ObjectClass + } + if (isTraitMethodOverridingObjectMember) methodOwner else receiver + } - val isTraitCallToObjectMethod = - hostSymbol != methodOwner && methodOwner.isTraitOrInterface && ObjectTpe.decl(method.name) != NoSymbol && method.overrideChain.last.owner == ObjectClass + receiverClass.info // ensure types the type is up to date; erasure may add lateINTERFACE to traits + val receiverName = internalName(receiverClass) - // whether to reference the type of the receiver or - // the type of the method owner - val useMethodOwner = (( - !style.isVirtual - || hostSymbol.isBottomClass - || methodOwner == definitions.ObjectClass - ) && !(style.isSuper && hostSymbol != null)) || isTraitCallToObjectMethod - val receiver = if (useMethodOwner) methodOwner else hostSymbol - val jowner = internalName(receiver) + // super calls are only allowed to direct parents + if (style.isSuper && receiverClass.isTraitOrInterface && !cnode.interfaces.contains(receiverName)) { + thisBType.info.get.inlineInfo.lateInterfaces += receiverName + cnode.interfaces.add(receiverName) + } - if (style.isSuper && (isTraitCallToObjectMethod || receiver.isTraitOrInterface) && !cnode.interfaces.contains(jowner)) - cnode.interfaces.add(jowner) + def needsInterfaceCall(sym: Symbol) = { + sym.isTraitOrInterface || + sym.isJavaDefined && sym.isNonBottomSubClass(definitions.ClassfileAnnotationClass) + } val jname = method.javaSimpleName.toString val bmType = methodBTypeFromSymbol(method) val mdescr = bmType.descriptor - def initModule() { - // we initialize the MODULE$ field immediately after the super ctor - if (!isModuleInitialized && - jMethodName == INSTANCE_CONSTRUCTOR_NAME && - jname == INSTANCE_CONSTRUCTOR_NAME && - isStaticModuleClass(siteSymbol)) { - isModuleInitialized = true - mnode.visitVarInsn(asm.Opcodes.ALOAD, 0) - mnode.visitFieldInsn( - asm.Opcodes.PUTSTATIC, - thisName, - strMODULE_INSTANCE_FIELD, - "L" + thisName + ";" - ) - } - } - - if (style.isStatic) { bc.invokestatic (jowner, jname, mdescr, pos) } - else if (style.isSpecial) { bc.invokespecial (jowner, jname, mdescr, pos) } - else if (style.isVirtual) { - if (needsInterfaceCall(receiver)) { bc.invokeinterface(jowner, jname, mdescr, pos) } - else { bc.invokevirtual (jowner, jname, mdescr, pos) } - } - else { - assert(style.isSuper, s"An unknown InvokeStyle: $style") - bc.invokespecial(jowner, jname, mdescr, pos) - initModule() + import InvokeStyle._ + style match { + case Static => bc.invokestatic (receiverName, jname, mdescr, pos) + case Special => bc.invokespecial (receiverName, jname, mdescr, pos) + case Virtual => + if (needsInterfaceCall(receiverClass)) bc.invokeinterface(receiverName, jname, mdescr, pos) + else bc.invokevirtual (receiverName, jname, mdescr, pos) + case Super => bc.invokespecial (receiverName, jname, mdescr, pos) } + bmType.returnType } // end of genCallMethod() /* Generate the scala ## method. */ @@ -1122,7 +1092,6 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { genLoadModule(ScalaRunTimeModule) // TODO why load ScalaRunTimeModule if ## has InvokeStyle of Static(false) ? genLoad(tree, ObjectRef) genCallMethod(hashMethodSym, InvokeStyle.Static, applyPos) - INT } /* diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala index 4c41cfc380..f190c1f2de 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala @@ -59,7 +59,7 @@ abstract class BCodeSkelBuilder extends BCodeHelpers { // current class var cnode: asm.tree.ClassNode = null - var thisName: String = null // the internal name of the class being emitted + var thisBType: ClassBType = null var claszSymbol: Symbol = null var isCZParcelable = false @@ -91,9 +91,7 @@ abstract class BCodeSkelBuilder extends BCodeHelpers { isCZParcelable = isAndroidParcelableClass(claszSymbol) isCZStaticModule = isStaticModuleClass(claszSymbol) isCZRemote = isRemote(claszSymbol) - thisName = internalName(claszSymbol) - - val classBType = classBTypeFromSymbol(claszSymbol) + thisBType = classBTypeFromSymbol(claszSymbol) cnode = new asm.tree.ClassNode() @@ -114,7 +112,6 @@ abstract class BCodeSkelBuilder extends BCodeHelpers { gen(cd.impl) - val shouldAddLambdaDeserialize = ( settings.target.value == "jvm-1.8" && settings.Ydelambdafy.value == "method" @@ -123,7 +120,7 @@ abstract class BCodeSkelBuilder extends BCodeHelpers { if (shouldAddLambdaDeserialize) backendUtils.addLambdaDeserialize(cnode) - cnode.visitAttribute(classBType.inlineInfoAttribute.get) + cnode.visitAttribute(thisBType.inlineInfoAttribute.get) if (AsmUtils.traceClassEnabled && cnode.name.contains(AsmUtils.traceClassPattern)) AsmUtils.traceClass(cnode) @@ -144,7 +141,7 @@ abstract class BCodeSkelBuilder extends BCodeHelpers { val thisSignature = getGenericSignature(claszSymbol, claszSymbol.owner) cnode.visit(classfileVersion, flags, - thisName, thisSignature, + thisBType.internalName, thisSignature, superClass, interfaceNames.toArray) if (emitSource) { @@ -157,7 +154,7 @@ abstract class BCodeSkelBuilder extends BCodeHelpers { case _ => () } - val ssa = getAnnotPickle(thisName, claszSymbol) + val ssa = getAnnotPickle(thisBType.internalName, claszSymbol) cnode.visitAttribute(if (ssa.isDefined) pickleMarkerLocal else pickleMarkerForeign) emitAnnotations(cnode, claszSymbol.annotations ++ ssa) @@ -178,7 +175,7 @@ abstract class BCodeSkelBuilder extends BCodeHelpers { } if (isCandidateForForwarders) { log(s"Adding static forwarders from '$claszSymbol' to implementations in '$lmoc'") - addForwarders(isRemote(claszSymbol), cnode, thisName, lmoc.moduleClass) + addForwarders(isRemote(claszSymbol), cnode, thisBType.internalName, lmoc.moduleClass) } } } @@ -196,7 +193,7 @@ abstract class BCodeSkelBuilder extends BCodeHelpers { val fv = cnode.visitField(GenBCode.PublicStaticFinal, // TODO confirm whether we really don't want ACC_SYNTHETIC nor ACC_DEPRECATED strMODULE_INSTANCE_FIELD, - "L" + thisName + ";", + thisBType.descriptor, null, // no java-generic-signature null // no initial value ) @@ -220,11 +217,11 @@ abstract class BCodeSkelBuilder extends BCodeHelpers { /* "legacy static initialization" */ if (isCZStaticModule) { - clinit.visitTypeInsn(asm.Opcodes.NEW, thisName) + clinit.visitTypeInsn(asm.Opcodes.NEW, thisBType.internalName) clinit.visitMethodInsn(asm.Opcodes.INVOKESPECIAL, - thisName, INSTANCE_CONSTRUCTOR_NAME, "()V", false) + thisBType.internalName, INSTANCE_CONSTRUCTOR_NAME, "()V", false) } - if (isCZParcelable) { legacyAddCreatorCode(clinit, cnode, thisName) } + if (isCZParcelable) { legacyAddCreatorCode(clinit, cnode, thisBType.internalName) } clinit.visitInsn(asm.Opcodes.RETURN) clinit.visitMaxs(0, 0) // just to follow protocol, dummy arguments @@ -604,7 +601,7 @@ abstract class BCodeSkelBuilder extends BCodeHelpers { if (!hasStaticBitSet) { mnode.visitLocalVariable( "this", - "L" + thisName + ";", + thisBType.descriptor, null, veryFirstProgramPoint, onePastLastProgramPoint, @@ -686,8 +683,8 @@ abstract class BCodeSkelBuilder extends BCodeHelpers { val jname = callee.javaSimpleName.toString val jtype = methodBTypeFromSymbol(callee).descriptor insnParcA = new asm.tree.MethodInsnNode(asm.Opcodes.INVOKESTATIC, jowner, jname, jtype, false) - // PUTSTATIC `thisName`.CREATOR; - insnParcB = new asm.tree.FieldInsnNode(asm.Opcodes.PUTSTATIC, thisName, "CREATOR", andrFieldDescr) + // PUTSTATIC `thisBType.internalName`.CREATOR; + insnParcB = new asm.tree.FieldInsnNode(asm.Opcodes.PUTSTATIC, thisBType.internalName, "CREATOR", andrFieldDescr) } // insert a few instructions for initialization before each return instruction diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala b/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala index f6bccca050..49353afef4 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala @@ -7,7 +7,7 @@ package scala.tools.nsc package backend.jvm import scala.annotation.switch -import scala.collection.{mutable, concurrent} +import scala.collection.{concurrent, mutable} import scala.collection.concurrent.TrieMap import scala.reflect.internal.util.Position import scala.tools.asm @@ -18,6 +18,7 @@ import scala.tools.nsc.backend.jvm.BackendReporting._ import scala.tools.nsc.backend.jvm.analysis.BackendUtils import scala.tools.nsc.backend.jvm.opt._ import scala.collection.convert.decorateAsScala._ +import scala.collection.mutable.ListBuffer import scala.tools.nsc.settings.ScalaSettings /** @@ -180,8 +181,6 @@ abstract class BTypes { Some(classBTypeFromParsedClassfile(superName)) } - val interfaces: List[ClassBType] = classNode.interfaces.asScala.map(classBTypeFromParsedClassfile)(collection.breakOut) - val flags = classNode.access /** @@ -226,6 +225,9 @@ abstract class BTypes { val inlineInfo = inlineInfoFromClassfile(classNode) + val classfileInterfaces: List[ClassBType] = classNode.interfaces.asScala.map(classBTypeFromParsedClassfile)(collection.breakOut) + val interfaces = classfileInterfaces.filterNot(i => inlineInfo.lateInterfaces.contains(i.internalName)) + classBType.info = Right(ClassInfo(superClass, interfaces, flags, nestedClasses, nestedInfo, inlineInfo)) classBType } @@ -1144,7 +1146,27 @@ object BTypes { final case class InlineInfo(isEffectivelyFinal: Boolean, sam: Option[String], methodInfos: Map[String, MethodInlineInfo], - warning: Option[ClassInlineInfoWarning]) + warning: Option[ClassInlineInfoWarning]) { + /** + * A super call (invokespecial) to a default method T.m is only allowed if the interface T is + * a direct parent of the class. Super calls are introduced for example in Mixin when generating + * forwarder methods: + * + * trait T { override def clone(): Object = "hi" } + * trait U extends T + * class C extends U + * + * The class C gets a forwarder that invokes T.clone(). During code generation the interface T + * is added as direct parent to class C. Note that T is not a (direct) parent in the frontend + * type of class C. + * + * All interfaces that are added to a class during code generation are added to this buffer and + * stored in the InlineInfo classfile attribute. This ensures that the ClassBTypes for a + * specific class is the same no matter if it's constructed from a Symbol or from a classfile. + * This is tested in BTypesFromClassfileTest. + */ + val lateInterfaces: ListBuffer[InternalName] = ListBuffer.empty + } val EmptyInlineInfo = InlineInfo(false, None, Map.empty, None) diff --git a/src/compiler/scala/tools/nsc/backend/jvm/GenBCode.scala b/src/compiler/scala/tools/nsc/backend/jvm/GenBCode.scala index b0ec37db97..65bd62a413 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/GenBCode.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/GenBCode.scala @@ -187,7 +187,7 @@ abstract class GenBCode extends BCodeSyncAndTry { // -------------- "plain" class -------------- val pcb = new PlainClassBuilder(cunit) pcb.genPlainClass(cd) - val outF = if (needsOutFolder) getOutFolder(claszSymbol, pcb.thisName, cunit) else null + val outF = if (needsOutFolder) getOutFolder(claszSymbol, pcb.thisBType.internalName, cunit) else null val plainC = pcb.cnode // -------------- bean info class, if needed -------------- diff --git a/src/compiler/scala/tools/nsc/backend/jvm/opt/InlineInfoAttribute.scala b/src/compiler/scala/tools/nsc/backend/jvm/opt/InlineInfoAttribute.scala index 079a9eec9b..79d26b0b4e 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/opt/InlineInfoAttribute.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/InlineInfoAttribute.scala @@ -47,11 +47,12 @@ case class InlineInfoAttribute(inlineInfo: InlineInfo) extends Attribute(InlineI result.putByte(InlineInfoAttribute.VERSION) - var finalSelfSam = 0 - if (inlineInfo.isEffectivelyFinal) finalSelfSam |= 1 - // finalSelfSam |= 2 // no longer written - if (inlineInfo.sam.isDefined) finalSelfSam |= 4 - result.putByte(finalSelfSam) + var flags = 0 + if (inlineInfo.isEffectivelyFinal) flags |= 1 + // flags |= 2 // no longer written + if (inlineInfo.sam.isDefined) flags |= 4 + if (inlineInfo.lateInterfaces.nonEmpty) flags |= 8 + result.putByte(flags) for (samNameDesc <- inlineInfo.sam) { val (name, desc) = samNameDesc.span(_ != '(') @@ -78,6 +79,9 @@ case class InlineInfoAttribute(inlineInfo: InlineInfo) extends Attribute(InlineI result.putByte(inlineInfo) } + result.putShort(inlineInfo.lateInterfaces.length) + for (i <- inlineInfo.lateInterfaces) result.putShort(cw.newUTF8(i)) + result } @@ -97,10 +101,11 @@ case class InlineInfoAttribute(inlineInfo: InlineInfo) extends Attribute(InlineI val version = nextByte() if (version == 1) { - val finalSelfSam = nextByte() - val isFinal = (finalSelfSam & 1) != 0 - val hasSelf = (finalSelfSam & 2) != 0 - val hasSam = (finalSelfSam & 4) != 0 + val flags = nextByte() + val isFinal = (flags & 1) != 0 + val hasSelf = (flags & 2) != 0 + val hasSam = (flags & 4) != 0 + val hasLateInterfaces = (flags & 8) != 0 if (hasSelf) nextUTF8() // no longer used @@ -116,14 +121,21 @@ case class InlineInfoAttribute(inlineInfo: InlineInfo) extends Attribute(InlineI val desc = nextUTF8() val inlineInfo = nextByte() - val isFinal = (inlineInfo & 1) != 0 - // val traitMethodWithStaticImplementation = (inlineInfo & 2) != 0 // no longer used - val isInline = (inlineInfo & 4) != 0 - val isNoInline = (inlineInfo & 8) != 0 + val isFinal = (inlineInfo & 1) != 0 + // = (inlineInfo & 2) != 0 // no longer used + val isInline = (inlineInfo & 4) != 0 + val isNoInline = (inlineInfo & 8) != 0 (name + desc, MethodInlineInfo(isFinal, isInline, isNoInline)) }).toMap - InlineInfoAttribute(InlineInfo(isFinal, sam, infos, None)) + val lateInterfaces = if (!hasLateInterfaces) Nil else { + val numLateInterfaces = nextShort() + (0 until numLateInterfaces).map(_ => nextUTF8()) + } + + val info = InlineInfo(isFinal, sam, infos, None) + info.lateInterfaces ++= lateInterfaces + InlineInfoAttribute(info) } else { val msg = UnknownScalaInlineInfoVersion(cr.getClassName, version) InlineInfoAttribute(BTypes.EmptyInlineInfo.copy(warning = Some(msg))) @@ -141,7 +153,7 @@ object InlineInfoAttribute { * is ignored. * * [u1] version - * [u1] isEffectivelyFinal (<< 0), hasTraitImplClassSelfType (<< 1), hasSam (<< 2) + * [u1] isEffectivelyFinal (<< 0), hasTraitImplClassSelfType (<< 1), hasSam (<< 2), hasLateInterfaces (<< 3) * [u2]? traitImplClassSelfType (reference) * [u2]? samName (reference) * [u2]? samDescriptor (reference) @@ -149,6 +161,8 @@ object InlineInfoAttribute { * [u2] name (reference) * [u2] descriptor (reference) * [u1] isFinal (<< 0), traitMethodWithStaticImplementation (<< 1), hasInlineAnnotation (<< 2), hasNoInlineAnnotation (<< 3) + * [u2]? numLateInterfaces + * [u2] lateInterface (reference) */ final val VERSION: Byte = 1 diff --git a/src/compiler/scala/tools/nsc/transform/LazyVals.scala b/src/compiler/scala/tools/nsc/transform/LazyVals.scala index 8a0086dbdd..bc9f70679c 100644 --- a/src/compiler/scala/tools/nsc/transform/LazyVals.scala +++ b/src/compiler/scala/tools/nsc/transform/LazyVals.scala @@ -215,14 +215,16 @@ abstract class LazyVals extends Transform with TypingTransformers with ast.TreeD def mkSlowPathDef(clazz: Symbol, lzyVal: Symbol, cond: Tree, syncBody: List[Tree], stats: List[Tree], retVal: Tree): Tree = { - // Q: is there a reason to first set owner to `clazz` (by using clazz.newMethod), and then - // changing it to lzyVal.owner very soon after? Could we just do lzyVal.owner.newMethod? - val defSym = clazz.newMethod(nme.newLazyValSlowComputeName(lzyVal.name.toTermName), lzyVal.pos, STABLE | PRIVATE) + val owner = lzyVal.owner + val defSym = owner.newMethod(nme.newLazyValSlowComputeName(lzyVal.name.toTermName), lzyVal.pos, STABLE | PRIVATE) defSym setInfo MethodType(List(), lzyVal.tpe.resultType) - defSym.owner = lzyVal.owner + if (owner.isClass) owner.info.decls.enter(defSym) debuglog(s"crete slow compute path $defSym with owner ${defSym.owner} for lazy val $lzyVal") - if (bitmaps.contains(lzyVal)) - bitmaps(lzyVal).map(_.owner = defSym) + // this is a hack i don't understand for lazy vals nested in a lazy val, introduced in 3769f4d, + // tested in pos/t3670 (add9be64). class A { val n = { lazy val b = { lazy val dd = 3; dd }; b } } + // bitmaps has an entry bMethodSym -> List(bitmap$0), where bitmap$0.owner == bMethodSym. + // now we set bitmap$0.owner = b$lzycomputeMethodSym. + for (bitmap <- bitmaps(lzyVal)) bitmap.owner = defSym val rhs: Tree = gen.mkSynchronizedCheck(clazz, cond, syncBody, stats).changeOwner(currentOwner -> defSym) DefDef(defSym, addBitmapDefs(lzyVal, BLOCK(rhs, retVal))) diff --git a/src/compiler/scala/tools/nsc/transform/Mixin.scala b/src/compiler/scala/tools/nsc/transform/Mixin.scala index ed7ef0d8fd..03935c3d67 100644 --- a/src/compiler/scala/tools/nsc/transform/Mixin.scala +++ b/src/compiler/scala/tools/nsc/transform/Mixin.scala @@ -47,7 +47,6 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL { sym.isMethod && (!sym.hasFlag(DEFERRED | SUPERACCESSOR) || (sym hasFlag lateDEFERRED)) && sym.owner.isTrait - && sym.isMethod && (!sym.isModule || sym.hasFlag(PRIVATE | LIFTED)) && (!(sym hasFlag (ACCESSOR | SUPERACCESSOR)) || sym.isLazy) && !sym.isPrivate @@ -240,8 +239,41 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL { for (member <- mixinClass.info.decls ; if isImplementedStatically(member)) { member overridingSymbol clazz match { case NoSymbol => - if (clazz.info.findMember(member.name, 0, 0L, stableOnly = false).alternatives contains member) - cloneAndAddMixinMember(mixinClass, member).asInstanceOf[TermSymbol] setAlias member + val isMemberOfClazz = clazz.info.findMember(member.name, 0, 0L, stableOnly = false).alternatives.contains(member) + if (isMemberOfClazz) { + val competingMethods = clazz.baseClasses.iterator + .filter(_ ne mixinClass) + .map(member.overriddenSymbol) + .filter(_.exists) + .toList + + // `member` is a concrete `method` defined in `mixinClass`, which is a base class of + // `clazz`, and the method is not overridden in `clazz`. A forwarder is needed if: + // + // - A non-trait base class defines matching method. Example: + // class C {def f: Int}; trait T extends C {def f = 1}; class D extends T + // Even if C.f is abstract, the forwarder in D is needed, otherwise the JVM would + // resolve `D.f` to `C.f`, see jvms-6.5.invokevirtual. + // + // - There exists another concrete, matching method in any of the base classes, and + // the `mixinClass` does not itself extend that base class. In this case the + // forwarder is needed to disambiguate. Example: + // trait T1 {def f = 1}; trait T2 extends T1 {override def f = 2}; class C extends T2 + // In C we don't need a forwarder for f because T2 extends T1, so the JVM resolves + // C.f to T2.f non-ambiguously. See jvms-5.4.3.3, "maximally-specific method". + // trait U1 {def f = 1}; trait U2 {self:U1 => override def f = 2}; class D extends U2 + // In D the forwarder is needed, the interfaces U1 and U2 are unrelated at the JVM + // level. + + lazy val mixinSuperInterfaces = mixinClass.ancestors.filter(_.isTraitOrInterface) + val needsForwarder = competingMethods.exists(m => { + !m.owner.isTraitOrInterface || + (!m.isDeferred && !mixinSuperInterfaces.contains(m.owner)) + }) + if (needsForwarder) + cloneAndAddMixinMember(mixinClass, member).asInstanceOf[TermSymbol] setAlias member + } + case _ => } } |