summaryrefslogtreecommitdiff
path: root/src/compiler
diff options
context:
space:
mode:
authorLukas Rytz <lukas.rytz@typesafe.com>2016-04-20 15:49:05 +0200
committerLukas Rytz <lukas.rytz@typesafe.com>2016-04-20 15:49:05 +0200
commitd24e298cbd955101a6b4342602b32ed37643dfb0 (patch)
treefb001a420a6fde6593ee263ca47169ace2fa638c /src/compiler
parent3c398f0af9b8bf18f292f1b514a2af453d36cdd8 (diff)
parentb61c35c38931be3b690bce92d5db9647802b1b34 (diff)
downloadscala-d24e298cbd955101a6b4342602b32ed37643dfb0.tar.gz
scala-d24e298cbd955101a6b4342602b32ed37643dfb0.tar.bz2
scala-d24e298cbd955101a6b4342602b32ed37643dfb0.zip
Merge pull request #5096 from lrytz/traitParents
Ensure ClassBTypes constructed from symbol and classfile are identical
Diffstat (limited to 'src/compiler')
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala359
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala29
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala30
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/GenBCode.scala2
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/opt/InlineInfoAttribute.scala44
-rw-r--r--src/compiler/scala/tools/nsc/transform/LazyVals.scala14
-rw-r--r--src/compiler/scala/tools/nsc/transform/Mixin.scala38
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 _ =>
}
}