diff options
41 files changed, 1157 insertions, 451 deletions
diff --git a/src/compiler/scala/tools/nsc/Reporting.scala b/src/compiler/scala/tools/nsc/Reporting.scala index 4d7e9e753f..72a4b69536 100644 --- a/src/compiler/scala/tools/nsc/Reporting.scala +++ b/src/compiler/scala/tools/nsc/Reporting.scala @@ -26,7 +26,7 @@ trait Reporting extends scala.reflect.internal.Reporting { self: ast.Positions w protected def PerRunReporting = new PerRunReporting class PerRunReporting extends PerRunReportingBase { /** Collects for certain classes of warnings during this run. */ - private class ConditionalWarning(what: String, option: Settings#BooleanSetting) { + private class ConditionalWarning(what: String, option: Settings#BooleanSetting)(reRunFlag: String = option.name) { val warnings = mutable.LinkedHashMap[Position, String]() def warn(pos: Position, msg: String) = if (option) reporter.warning(pos, msg) @@ -37,16 +37,16 @@ trait Reporting extends scala.reflect.internal.Reporting { self: ast.Positions w val warningVerb = if (numWarnings == 1) "was" else "were" val warningCount = countElementsAsString(numWarnings, s"$what warning") - reporter.warning(NoPosition, s"there $warningVerb $warningCount; re-run with ${option.name} for details") + reporter.warning(NoPosition, s"there $warningVerb $warningCount; re-run with $reRunFlag for details") } } // This change broke sbt; I gave it the thrilling name of uncheckedWarnings0 so // as to recover uncheckedWarnings for its ever-fragile compiler interface. - private val _deprecationWarnings = new ConditionalWarning("deprecation", settings.deprecation) - private val _uncheckedWarnings = new ConditionalWarning("unchecked", settings.unchecked) - private val _featureWarnings = new ConditionalWarning("feature", settings.feature) - private val _inlinerWarnings = new ConditionalWarning("inliner", settings.YinlinerWarnings) + private val _deprecationWarnings = new ConditionalWarning("deprecation", settings.deprecation)() + private val _uncheckedWarnings = new ConditionalWarning("unchecked", settings.unchecked)() + private val _featureWarnings = new ConditionalWarning("feature", settings.feature)() + private val _inlinerWarnings = new ConditionalWarning("inliner", settings.YinlinerWarnings)(if (settings.isBCodeAskedFor) settings.YoptWarnings.name else settings.YinlinerWarnings.name) private val _allConditionalWarnings = List(_deprecationWarnings, _uncheckedWarnings, _featureWarnings, _inlinerWarnings) // TODO: remove in favor of the overload that takes a Symbol, give that argument a default (NoSymbol) diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeAsmCommon.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeAsmCommon.scala index 2ebf338f5e..162da4236a 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeAsmCommon.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeAsmCommon.scala @@ -7,7 +7,8 @@ package scala.tools.nsc package backend.jvm import scala.tools.nsc.Global -import scala.tools.nsc.backend.jvm.BTypes.{MethodInlineInfo, InlineInfo, InternalName} +import scala.tools.nsc.backend.jvm.BTypes.{InternalName, MethodInlineInfo, InlineInfo} +import BackendReporting.ClassSymbolInfoFailureSI9111 /** * This trait contains code shared between GenBCode and GenASM that depends on types defined in @@ -336,7 +337,7 @@ final class BCodeAsmCommon[G <: Global](val global: G) { val isEffectivelyFinal = classSym.isEffectivelyFinal - var warning = Option.empty[String] + 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]]. @@ -345,7 +346,7 @@ final class BCodeAsmCommon[G <: Global](val global: G) { 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(s"Failed to get the type of a method of class symbol ${classSym.fullName} due to SI-9111") + warning = Some(ClassSymbolInfoFailureSI9111(classSym.fullName)) None } else { val name = methodSym.javaSimpleName.toString // same as in genDefDef diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala index 1b3f124dd8..15b014bdd3 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala @@ -13,6 +13,7 @@ import scala.annotation.switch import scala.tools.asm import GenBCode._ +import BackendReporting._ /* * @@ -93,7 +94,7 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { val thrownKind = tpeTK(expr) // `throw null` is valid although scala.Null (as defined in src/libray-aux) isn't a subtype of Throwable. // Similarly for scala.Nothing (again, as defined in src/libray-aux). - assert(thrownKind.isNullType || thrownKind.isNothingType || thrownKind.asClassBType.isSubtypeOf(ThrowableReference)) + assert(thrownKind.isNullType || thrownKind.isNothingType || thrownKind.asClassBType.isSubtypeOf(ThrowableReference).get) genLoad(expr, thrownKind) lineNumber(expr) emit(asm.Opcodes.ATHROW) // ICode enters here into enterIgnoreMode, we'll rely instead on DCE at ClassNode level. @@ -230,7 +231,7 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { if (isArithmeticOp(code)) genArithmeticOp(tree, code) else if (code == scalaPrimitives.CONCAT) genStringConcat(tree) - else if (code == scalaPrimitives.HASH) genScalaHash(receiver) + else if (code == scalaPrimitives.HASH) genScalaHash(receiver, tree.pos) else if (isArrayOp(code)) genArrayOp(tree, code, expectedType) else if (isLogicalOp(code) || isComparisonOp(code)) { val success, failure, after = new asm.Label @@ -584,7 +585,7 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { // if (fun.symbol.isConstructor) Static(true) else SuperCall(mix); mnode.visitVarInsn(asm.Opcodes.ALOAD, 0) genLoadArguments(args, paramTKs(app)) - genCallMethod(fun.symbol, invokeStyle, pos = app.pos) + genCallMethod(fun.symbol, invokeStyle, app.pos) generatedType = asmMethodType(fun.symbol).returnType // 'new' constructor call: Note: since constructors are @@ -626,7 +627,7 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { mnode.visitTypeInsn(asm.Opcodes.NEW, rt.internalName) bc dup generatedType genLoadArguments(args, paramTKs(app)) - genCallMethod(ctor, icodes.opcodes.Static(onInstance = true)) + genCallMethod(ctor, icodes.opcodes.Static(onInstance = true), app.pos) case _ => abort(s"Cannot instantiate $tpt of kind: $generatedType") @@ -636,7 +637,7 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { val nativeKind = tpeTK(expr) genLoad(expr, nativeKind) val MethodNameAndType(mname, methodType) = asmBoxTo(nativeKind) - bc.invokestatic(BoxesRunTime.internalName, mname, methodType.descriptor) + bc.invokestatic(BoxesRunTime.internalName, mname, methodType.descriptor, app.pos) generatedType = boxResultType(fun.symbol) // was toTypeKind(fun.symbol.tpe.resultType) case Apply(fun @ _, List(expr)) if currentRun.runDefinitions.isUnbox(fun.symbol) => @@ -644,7 +645,7 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { val boxType = unboxResultType(fun.symbol) // was toTypeKind(fun.symbol.owner.linkedClassOfClass.tpe) generatedType = boxType val MethodNameAndType(mname, methodType) = asmUnboxTo(boxType) - bc.invokestatic(BoxesRunTime.internalName, mname, methodType.descriptor) + bc.invokestatic(BoxesRunTime.internalName, mname, methodType.descriptor, app.pos) case app @ Apply(fun, args) => val sym = fun.symbol @@ -695,10 +696,10 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { // 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;") + bc.invokevirtual(target, "clone", "()Ljava/lang/Object;", app.pos) } else { - genCallMethod(sym, invokeStyle, hostClass, app.pos) + genCallMethod(sym, invokeStyle, app.pos, hostClass) } } // end of genNormalMethodCall() @@ -810,7 +811,7 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { } def adapt(from: BType, to: BType) { - if (!from.conformsTo(to)) { + if (!from.conformsTo(to).get) { to match { case UNIT => bc drop from case _ => bc.emitT2T(from, to) @@ -976,23 +977,23 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { // Optimization for expressions of the form "" + x. We can avoid the StringBuilder. case List(Literal(Constant("")), arg) => genLoad(arg, ObjectReference) - genCallMethod(String_valueOf, icodes.opcodes.Static(onInstance = false)) + genCallMethod(String_valueOf, icodes.opcodes.Static(onInstance = false), arg.pos) case concatenations => - bc.genStartConcat + bc.genStartConcat(tree.pos) for (elem <- concatenations) { val kind = tpeTK(elem) genLoad(elem, kind) - bc.genStringConcat(kind) + bc.genStringConcat(kind, elem.pos) } - bc.genEndConcat + bc.genEndConcat(tree.pos) } StringReference } - def genCallMethod(method: Symbol, style: InvokeStyle, hostClass0: Symbol = null, pos: Position = NoPosition) { + def genCallMethod(method: Symbol, style: InvokeStyle, pos: Position, hostClass0: Symbol = null) { val siteSymbol = claszSymbol val hostSymbol = if (hostClass0 == null) method.owner else hostClass0 @@ -1036,26 +1037,26 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { } if (style.isStatic) { - if (style.hasInstance) { bc.invokespecial (jowner, jname, mdescr) } - else { bc.invokestatic (jowner, jname, mdescr) } + if (style.hasInstance) { bc.invokespecial (jowner, jname, mdescr, pos) } + else { bc.invokestatic (jowner, jname, mdescr, pos) } } else if (style.isDynamic) { - if (needsInterfaceCall(receiver)) { bc.invokeinterface(jowner, jname, mdescr) } - else { bc.invokevirtual (jowner, jname, mdescr) } + 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) + bc.invokespecial(jowner, jname, mdescr, pos) initModule() } } // end of genCallMethod() /* Generate the scala ## method. */ - def genScalaHash(tree: Tree): BType = { + def genScalaHash(tree: Tree, applyPos: Position): BType = { genLoadModule(ScalaRunTimeModule) // TODO why load ScalaRunTimeModule if ## has InvokeStyle of Static(false) ? genLoad(tree, ObjectReference) - genCallMethod(hashMethodSym, icodes.opcodes.Static(onInstance = false)) + genCallMethod(hashMethodSym, icodes.opcodes.Static(onInstance = false), applyPos) INT } @@ -1187,8 +1188,8 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { // TODO !!!!!!!!!! isReferenceType, in the sense of TypeKind? (ie non-array, non-boxed, non-nothing, may be null) if (scalaPrimitives.isUniversalEqualityOp(code) && tpeTK(lhs).isClass) { // `lhs` has reference type - if (code == EQ) genEqEqPrimitive(lhs, rhs, success, failure) - else genEqEqPrimitive(lhs, rhs, failure, success) + if (code == EQ) genEqEqPrimitive(lhs, rhs, success, failure, tree.pos) + else genEqEqPrimitive(lhs, rhs, failure, success, tree.pos) } else if (scalaPrimitives.isComparisonOp(code)) genComparisonOp(lhs, rhs, code) @@ -1208,7 +1209,7 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { * @param l left-hand-side of the '==' * @param r right-hand-side of the '==' */ - def genEqEqPrimitive(l: Tree, r: Tree, success: asm.Label, failure: asm.Label) { + def genEqEqPrimitive(l: Tree, r: Tree, success: asm.Label, failure: asm.Label, pos: Position) { /* True if the equality comparison is between values that require the use of the rich equality * comparator (scala.runtime.Comparator.equals). This is the case when either side of the @@ -1232,7 +1233,7 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { } genLoad(l, ObjectReference) genLoad(r, ObjectReference) - genCallMethod(equalsMethod, icodes.opcodes.Static(onInstance = false)) + genCallMethod(equalsMethod, icodes.opcodes.Static(onInstance = false), pos) genCZJUMP(success, failure, icodes.NE, BOOL) } else { @@ -1248,7 +1249,7 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { // SI-7852 Avoid null check if L is statically non-null. genLoad(l, ObjectReference) genLoad(r, ObjectReference) - genCallMethod(Object_equals, icodes.opcodes.Dynamic) + genCallMethod(Object_equals, icodes.opcodes.Dynamic, pos) genCZJUMP(success, failure, icodes.NE, BOOL) } else { // l == r -> if (l eq null) r eq null else l.equals(r) @@ -1269,7 +1270,7 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { markProgramPoint(lNonNull) locals.load(eqEqTempLocal) - genCallMethod(Object_equals, icodes.opcodes.Dynamic) + genCallMethod(Object_equals, icodes.opcodes.Dynamic, pos) genCZJUMP(success, failure, icodes.NE, BOOL) } } diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala index 246d565987..3b7dbc18da 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala @@ -11,6 +11,7 @@ import scala.tools.asm import scala.collection.mutable import scala.tools.nsc.io.AbstractFile import GenBCode._ +import BackendReporting._ /* * Traits encapsulating functionality to convert Scala AST Trees into ASM ClassNodes. @@ -67,7 +68,7 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { override def getCommonSuperClass(inameA: String, inameB: String): String = { val a = classBTypeFromInternalName(inameA) val b = classBTypeFromInternalName(inameB) - val lub = a.jvmWiseLUB(b) + val lub = a.jvmWiseLUB(b).get val lubName = lub.internalName assert(lubName != "scala/Any") lubName // ASM caches the answer during the lifetime of a ClassWriter. We outlive that. Not sure whether caching on our side would improve things. @@ -205,12 +206,12 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { * can-multi-thread */ final def addInnerClassesASM(jclass: asm.ClassVisitor, refedInnerClasses: List[ClassBType]) { - val allNestedClasses = refedInnerClasses.flatMap(_.enclosingNestedClassesChain).distinct + val allNestedClasses = refedInnerClasses.flatMap(_.enclosingNestedClassesChain.get).distinct // sorting ensures nested classes are listed after their enclosing class thus satisfying the Eclipse Java compiler for (nestedClass <- allNestedClasses.sortBy(_.internalName.toString)) { // Extract the innerClassEntry - we know it exists, enclosingNestedClassesChain only returns nested classes. - val Some(e) = nestedClass.innerClassAttributeEntry + val Some(e) = nestedClass.innerClassAttributeEntry.get jclass.visitInnerClass(e.name, e.outerName, e.innerName, e.flags) } } @@ -341,7 +342,7 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { */ final def getClassBTypeAndRegisterInnerClass(sym: Symbol): ClassBType = { val r = classBTypeFromSymbol(sym) - if (r.isNestedClass) innerClassBufferASM += r + if (r.isNestedClass.get) innerClassBufferASM += r r } @@ -351,7 +352,7 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { * 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 => + case c: ClassBType if c.isNestedClass.get => innerClassBufferASM += c c case r => r @@ -364,7 +365,7 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { final def asmMethodType(msym: Symbol): MethodBType = { val r = methodBTypeFromSymbol(msym) (r.returnType :: r.argumentTypes) foreach { - case c: ClassBType if c.isNestedClass => innerClassBufferASM += c + case c: ClassBType if c.isNestedClass.get => innerClassBufferASM += c case _ => } r @@ -714,7 +715,7 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { val mirrorClass = new asm.tree.ClassNode mirrorClass.visit( classfileVersion, - bType.info.flags, + bType.info.get.flags, bType.internalName, null /* no java-generic-signature */, ObjectReference.internalName, @@ -730,7 +731,7 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { addForwarders(isRemote(moduleClass), mirrorClass, bType.internalName, moduleClass) - innerClassBufferASM ++= bType.info.nestedClasses + innerClassBufferASM ++= bType.info.get.nestedClasses addInnerClassesASM(mirrorClass, innerClassBufferASM.toList) mirrorClass.visitEnd() @@ -846,7 +847,7 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { constructor.visitMaxs(0, 0) // just to follow protocol, dummy arguments constructor.visitEnd() - innerClassBufferASM ++= classBTypeFromSymbol(cls).info.nestedClasses + innerClassBufferASM ++= classBTypeFromSymbol(cls).info.get.nestedClasses addInnerClassesASM(beanInfoClass, innerClassBufferASM.toList) beanInfoClass.visitEnd() diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeIdiomatic.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeIdiomatic.scala index c743ebd16f..9993357eee 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeIdiomatic.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeIdiomatic.scala @@ -11,6 +11,7 @@ import scala.tools.asm import scala.annotation.switch import scala.collection.mutable import GenBCode._ +import scala.tools.asm.tree.MethodInsnNode /* * A high-level facade to the ASM API for bytecode generation. @@ -105,7 +106,7 @@ abstract class BCodeIdiomatic extends SubComponent { */ abstract class JCodeMethodN { - def jmethod: asm.MethodVisitor + def jmethod: asm.tree.MethodNode import asm.Opcodes; import icodes.opcodes.{ Static, Dynamic, SuperCall } @@ -205,20 +206,21 @@ abstract class BCodeIdiomatic extends SubComponent { /* * can-multi-thread */ - final def genStartConcat { + final def genStartConcat(pos: Position): Unit = { jmethod.visitTypeInsn(Opcodes.NEW, StringBuilderClassName) jmethod.visitInsn(Opcodes.DUP) invokespecial( StringBuilderClassName, INSTANCE_CONSTRUCTOR_NAME, - "()V" + "()V", + pos ) } /* * can-multi-thread */ - final def genStringConcat(el: BType) { + final def genStringConcat(el: BType, pos: Position): Unit = { val jtype = if (el.isArray || el.isClass) ObjectReference @@ -226,14 +228,14 @@ abstract class BCodeIdiomatic extends SubComponent { val bt = MethodBType(List(jtype), StringBuilderReference) - invokevirtual(StringBuilderClassName, "append", bt.descriptor) + invokevirtual(StringBuilderClassName, "append", bt.descriptor, pos) } /* * can-multi-thread */ - final def genEndConcat { - invokevirtual(StringBuilderClassName, "toString", "()Ljava/lang/String;") + final def genEndConcat(pos: Position): Unit = { + invokevirtual(StringBuilderClassName, "toString", "()Ljava/lang/String;", pos) } /* @@ -389,20 +391,26 @@ abstract class BCodeIdiomatic extends SubComponent { final def rem(tk: BType) { emitPrimitive(JCodeMethodN.remOpcodes, tk) } // can-multi-thread // can-multi-thread - final def invokespecial(owner: String, name: String, desc: String) { - jmethod.visitMethodInsn(Opcodes.INVOKESPECIAL, owner, name, desc, false) + final def invokespecial(owner: String, name: String, desc: String, pos: Position) { + addInvoke(Opcodes.INVOKESPECIAL, owner, name, desc, false, pos) } // can-multi-thread - final def invokestatic(owner: String, name: String, desc: String) { - jmethod.visitMethodInsn(Opcodes.INVOKESTATIC, owner, name, desc, false) + final def invokestatic(owner: String, name: String, desc: String, pos: Position) { + addInvoke(Opcodes.INVOKESTATIC, owner, name, desc, false, pos) } // can-multi-thread - final def invokeinterface(owner: String, name: String, desc: String) { - jmethod.visitMethodInsn(Opcodes.INVOKEINTERFACE, owner, name, desc, true) + final def invokeinterface(owner: String, name: String, desc: String, pos: Position) { + addInvoke(Opcodes.INVOKEINTERFACE, owner, name, desc, true, pos) } // can-multi-thread - final def invokevirtual(owner: String, name: String, desc: String) { - jmethod.visitMethodInsn(Opcodes.INVOKEVIRTUAL, owner, name, desc, false) + final def invokevirtual(owner: String, name: String, desc: String, pos: Position) { + addInvoke(Opcodes.INVOKEVIRTUAL, owner, name, desc, false, pos) + } + + private def addInvoke(opcode: Int, owner: String, name: String, desc: String, itf: Boolean, pos: Position) = { + val node = new MethodInsnNode(opcode, owner, name, desc, itf) + jmethod.instructions.add(node) + if (settings.YoptInlinerEnabled) callsitePositions(node) = pos } // can-multi-thread diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala index 61606419bd..2a06c62e37 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala @@ -4,20 +4,17 @@ */ -package scala -package tools.nsc +package scala.tools.nsc package backend package jvm import scala.collection.{ mutable, immutable } import scala.tools.nsc.backend.jvm.opt.ByteCodeRepository import scala.tools.nsc.symtab._ -import scala.annotation.switch import scala.tools.asm -import scala.tools.asm.util.{TraceMethodVisitor, ASMifier} -import java.io.PrintWriter import GenBCode._ +import BackendReporting._ /* * @@ -122,11 +119,11 @@ abstract class BCodeSkelBuilder extends BCodeHelpers { addClassFields() - innerClassBufferASM ++= classBType.info.nestedClasses + innerClassBufferASM ++= classBType.info.get.nestedClasses gen(cd.impl) addInnerClassesASM(cnode, innerClassBufferASM.toList) - cnode.visitAttribute(classBType.inlineInfoAttribute) + cnode.visitAttribute(classBType.inlineInfoAttribute.get) if (AsmUtils.traceClassEnabled && cnode.name.contains(AsmUtils.traceClassPattern)) AsmUtils.traceClass(cnode) @@ -146,9 +143,9 @@ abstract class BCodeSkelBuilder extends BCodeHelpers { val ps = claszSymbol.info.parents val superClass: String = if (ps.isEmpty) ObjectReference.internalName else internalName(ps.head.typeSymbol) - val interfaceNames = classBTypeFromSymbol(claszSymbol).info.interfaces map { + val interfaceNames = classBTypeFromSymbol(claszSymbol).info.get.interfaces map { case classBType => - if (classBType.isNestedClass) { innerClassBufferASM += classBType } + if (classBType.isNestedClass.get) { innerClassBufferASM += classBType } classBType.internalName } diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala b/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala index 872d1cc522..d2ee944916 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala @@ -7,12 +7,15 @@ package scala.tools.nsc package backend.jvm import scala.annotation.switch +import scala.collection.concurrent.TrieMap +import scala.reflect.internal.util.Position import scala.tools.asm import asm.Opcodes -import scala.tools.asm.tree.{InnerClassNode, ClassNode} +import scala.tools.asm.tree.{MethodInsnNode, InnerClassNode, ClassNode} import scala.tools.nsc.backend.jvm.BTypes.{InlineInfo, MethodInlineInfo} +import scala.tools.nsc.backend.jvm.BackendReporting._ +import BackendReporting.RightBiasedEither import scala.tools.nsc.backend.jvm.opt._ -import opt.OptimizerReporting._ import scala.collection.convert.decorateAsScala._ /** @@ -41,6 +44,8 @@ abstract class BTypes { val callGraph: CallGraph[this.type] + val backendReporting: BackendReporting + // Allows to define per-run caches here and in the CallGraph component, which don't have a global def recordPerRunCache[T <: collection.generic.Clearable](cache: T): T @@ -50,6 +55,9 @@ abstract class BTypes { // When the inliner is not enabled, there's no point in adding InlineInfos to all ClassBTypes def inlinerEnabled: Boolean + // Settings that define what kind of optimizer warnings are emitted. + def warnSettings: WarnSettings + /** * A map from internal names to ClassBTypes. Every ClassBType is added to this map on its * construction. @@ -61,7 +69,19 @@ abstract class BTypes { * Concurrent because stack map frames are computed when in the class writer, which might run * on multiple classes concurrently. */ - val classBTypeFromInternalName: collection.concurrent.Map[InternalName, ClassBType] = recordPerRunCache(collection.concurrent.TrieMap.empty[InternalName, ClassBType]) + val classBTypeFromInternalName: collection.concurrent.Map[InternalName, ClassBType] = recordPerRunCache(TrieMap.empty) + + /** + * Store the position of every MethodInsnNode during code generation. This allows each callsite + * in the call graph to remember its source position, which is required for inliner warnings. + */ + val callsitePositions: collection.concurrent.Map[MethodInsnNode, Position] = recordPerRunCache(TrieMap.empty) + + /** + * Contains the internal names of all classes that are defined in Java source files of the current + * compilation run (mixed compilation). Used for more detailed error reporting. + */ + val javaDefinedClasses: collection.mutable.Set[InternalName] = recordPerRunCache(collection.mutable.Set.empty) /** * Obtain the BType for a type descriptor or internal name. For class descriptors, the ClassBType @@ -73,29 +93,33 @@ abstract class BTypes { * * This method supports both descriptors and internal names. */ - def bTypeForDescriptorOrInternalNameFromClassfile(desc: String): Option[BType] = (desc(0): @switch) match { - case 'V' => Some(UNIT) - case 'Z' => Some(BOOL) - case 'C' => Some(CHAR) - case 'B' => Some(BYTE) - case 'S' => Some(SHORT) - case 'I' => Some(INT) - case 'F' => Some(FLOAT) - case 'J' => Some(LONG) - case 'D' => Some(DOUBLE) - case '[' => bTypeForDescriptorOrInternalNameFromClassfile(desc.substring(1)) map ArrayBType + def bTypeForDescriptorOrInternalNameFromClassfile(desc: String): BType = (desc(0): @switch) match { + case 'V' => UNIT + case 'Z' => BOOL + case 'C' => CHAR + case 'B' => BYTE + case 'S' => SHORT + case 'I' => INT + case 'F' => FLOAT + case 'J' => LONG + case 'D' => DOUBLE + case '[' => ArrayBType(bTypeForDescriptorOrInternalNameFromClassfile(desc.substring(1))) case 'L' if desc.last == ';' => classBTypeFromParsedClassfile(desc.substring(1, desc.length - 1)) case _ => classBTypeFromParsedClassfile(desc) } /** - * Parse the classfile for `internalName` and construct the [[ClassBType]]. Returns `None` if the - * classfile cannot be found in the `byteCodeRepository`. + * Parse the classfile for `internalName` and construct the [[ClassBType]]. If the classfile cannot + * be found in the `byteCodeRepository`, the `info` of the resulting ClassBType is undefined. */ - def classBTypeFromParsedClassfile(internalName: InternalName): Option[ClassBType] = { - classBTypeFromInternalName.get(internalName) orElse { - byteCodeRepository.classNode(internalName) map classBTypeFromClassNode - } + def classBTypeFromParsedClassfile(internalName: InternalName): ClassBType = { + classBTypeFromInternalName.getOrElse(internalName, { + val res = ClassBType(internalName) + byteCodeRepository.classNode(internalName) match { + case Left(msg) => res.info = Left(NoClassBTypeInfoMissingBytecode(msg)); res + case Right(c) => setClassInfoFromParsedClassfile(c, res) + } + }) } /** @@ -108,26 +132,15 @@ abstract class BTypes { } private def setClassInfoFromParsedClassfile(classNode: ClassNode, classBType: ClassBType): ClassBType = { - def ensureClassBTypeFromParsedClassfile(internalName: InternalName): ClassBType = { - classBTypeFromParsedClassfile(internalName) getOrElse { - // When building a ClassBType from a parsed classfile, we need the ClassBTypes for all - // referenced types. - // TODO: make this more robust with respect to incomplete classpaths. - // Maybe not those parts of the ClassBType that require the missing class are not actually - // queried during the backend, so every part of a ClassBType that requires parsing a - // (potentially missing) classfile should be computed lazily. - assertionError(s"Could not find bytecode for class $internalName") - } - } val superClass = classNode.superName match { case null => assert(classNode.name == ObjectReference.internalName, s"class with missing super type: ${classNode.name}") None case superName => - Some(ensureClassBTypeFromParsedClassfile(superName)) + Some(classBTypeFromParsedClassfile(superName)) } - val interfaces: List[ClassBType] = classNode.interfaces.asScala.map(ensureClassBTypeFromParsedClassfile)(collection.breakOut) + val interfaces: List[ClassBType] = classNode.interfaces.asScala.map(classBTypeFromParsedClassfile)(collection.breakOut) val flags = classNode.access @@ -145,15 +158,13 @@ abstract class BTypes { def nestedInCurrentClass(innerClassNode: InnerClassNode): Boolean = { (innerClassNode.outerName != null && innerClassNode.outerName == classNode.name) || (innerClassNode.outerName == null && { - val classNodeForInnerClass = byteCodeRepository.classNode(innerClassNode.name) getOrElse { - assertionError(s"Could not find bytecode for class ${innerClassNode.name}") - } + val classNodeForInnerClass = byteCodeRepository.classNode(innerClassNode.name).get // TODO: don't get here, but set the info to Left at the end classNodeForInnerClass.outerClass == classNode.name }) } val nestedClasses: List[ClassBType] = classNode.innerClasses.asScala.collect({ - case i if nestedInCurrentClass(i) => ensureClassBTypeFromParsedClassfile(i.name) + case i if nestedInCurrentClass(i) => classBTypeFromParsedClassfile(i.name) })(collection.breakOut) // if classNode is a nested class, it has an innerClass attribute for itself. in this @@ -163,11 +174,11 @@ abstract class BTypes { val enclosingClass = if (innerEntry.outerName != null) { // if classNode is a member class, the outerName is non-null - ensureClassBTypeFromParsedClassfile(innerEntry.outerName) + classBTypeFromParsedClassfile(innerEntry.outerName) } else { // for anonymous or local classes, the outerName is null, but the enclosing class is // stored in the EnclosingMethod attribute (which ASM encodes in classNode.outerClass). - ensureClassBTypeFromParsedClassfile(classNode.outerClass) + classBTypeFromParsedClassfile(classNode.outerClass) } val staticFlag = (innerEntry.access & Opcodes.ACC_STATIC) != 0 NestedInfo(enclosingClass, Option(innerEntry.outerName), Option(innerEntry.innerName), staticFlag) @@ -175,7 +186,7 @@ abstract class BTypes { val inlineInfo = inlineInfoFromClassfile(classNode) - classBType.info = ClassInfo(superClass, interfaces, flags, nestedClasses, nestedInfo, inlineInfo) + classBType.info = Right(ClassInfo(superClass, interfaces, flags, nestedClasses, nestedInfo, inlineInfo)) classBType } @@ -186,12 +197,16 @@ abstract class BTypes { */ def inlineInfoFromClassfile(classNode: ClassNode): InlineInfo = { def fromClassfileAttribute: Option[InlineInfo] = { - // TODO: if this is a scala class and there's no attribute, emit an inliner warning if the InlineInfo is used if (classNode.attrs == null) None else classNode.attrs.asScala.collect({ case a: InlineInfoAttribute => a}).headOption.map(_.inlineInfo) } def fromClassfileWithoutAttribute = { + val warning = { + val isScala = classNode.attrs != null && classNode.attrs.asScala.exists(a => a.`type` == BTypes.ScalaAttributeName || a.`type` == BTypes.ScalaSigAttributeName) + if (isScala) Some(NoInlineInfoAttribute(classNode.name)) + else None + } // when building MethodInlineInfos for the members of a ClassSymbol, we exclude those methods // in scalaPrimitives. This is necessary because some of them have non-erased types, which would // require special handling. Excluding is OK because they are never inlined. @@ -209,7 +224,7 @@ abstract class BTypes { traitImplClassSelfType = None, isEffectivelyFinal = BytecodeUtils.isFinalClass(classNode), methodInfos = methodInfos, - warning = None) + warning) } // The InlineInfo is built from the classfile (not from the symbol) for all classes that are NOT @@ -283,7 +298,7 @@ abstract class BTypes { * promotions (e.g. BYTE to INT). Its operation can be visualized more easily in terms of the * Java bytecode type hierarchy. */ - final def conformsTo(other: BType): Boolean = { + final def conformsTo(other: BType): Either[NoClassBTypeInfo, Boolean] = tryEither(Right({ assert(isRef || isPrimitive, s"conformsTo cannot handle $this") assert(other.isRef || other.isPrimitive, s"conformsTo cannot handle $other") @@ -291,7 +306,7 @@ abstract class BTypes { case ArrayBType(component) => if (other == ObjectReference || other == jlCloneableReference || other == jioSerializableReference) true else other match { - case ArrayBType(otherComponoent) => component.conformsTo(otherComponoent) + case ArrayBType(otherComponoent) => component.conformsTo(otherComponoent).orThrow case _ => false } @@ -300,7 +315,7 @@ abstract class BTypes { if (other.isBoxed) this == other else if (other == ObjectReference) true else other match { - case otherClassType: ClassBType => classType.isSubtypeOf(otherClassType) // e.g., java/lang/Double conforms to java/lang/Number + case otherClassType: ClassBType => classType.isSubtypeOf(otherClassType).orThrow // e.g., java/lang/Double conforms to java/lang/Number case _ => false } } else if (isNullType) { @@ -310,7 +325,7 @@ abstract class BTypes { } else if (isNothingType) { true } else other match { - case otherClassType: ClassBType => classType.isSubtypeOf(otherClassType) + case otherClassType: ClassBType => classType.isSubtypeOf(otherClassType).orThrow // case ArrayBType(_) => this.isNullType // documentation only, because `if (isNullType)` above covers this case case _ => // isNothingType || // documentation only, because `if (isNothingType)` above covers this case @@ -325,7 +340,7 @@ abstract class BTypes { assert(isPrimitive && other.isPrimitive, s"Expected primitive types $this - $other") this == other } - } + })) /** * Compute the upper bound of two types. @@ -765,9 +780,22 @@ abstract class BTypes { * A ClassBType represents a class or interface type. The necessary information to build a * ClassBType is extracted from compiler symbols and types, see BTypesFromSymbols. * - * Currently non-final due to SI-9111 + * The `info` field contains either the class information on an error message why the info could + * not be computed. There are two reasons for an erroneous info: + * 1. The ClassBType was built from a class symbol that stems from a java source file, and the + * symbol's type could not be completed successfully (SI-9111) + * 2. The ClassBType should be built from a classfile, but the class could not be found on the + * compilation classpath. + * + * Note that all ClassBTypes required in a non-optimzied run are built during code generation from + * the class symbols referenced by the ASTs, so they have a valid info. Therefore the backend + * often invokes `info.get` (which asserts the info to exist) when reading data from the ClassBType. + * + * The inliner on the other hand uses ClassBTypes that are built from classfiles, which may have + * a missing info. In order not to crash the compiler unnecessarily, the inliner does not force + * infos using `get`, but it reports inliner warnings for missing infos that prevent inlining. */ - /*final*/ case class ClassBType(internalName: InternalName) extends RefBType { + final case class ClassBType(internalName: InternalName) extends RefBType { /** * Write-once variable allows initializing a cyclic graph of infos. This is required for * nested classes. Example: for the definition `class A { class B }` we have @@ -775,14 +803,14 @@ abstract class BTypes { * B.info.nestedInfo.outerClass == A * A.info.nestedClasses contains B */ - private var _info: ClassInfo = null + private var _info: Either[NoClassBTypeInfo, ClassInfo] = null - def info: ClassInfo = { + def info: Either[NoClassBTypeInfo, ClassInfo] = { assert(_info != null, s"ClassBType.info not yet assigned: $this") _info } - def info_=(i: ClassInfo): Unit = { + def info_=(i: Either[NoClassBTypeInfo, ClassInfo]): Unit = { assert(_info == null, s"Cannot set ClassBType.info multiple times: $this") _info = i checkInfoConsistency() @@ -791,27 +819,29 @@ abstract class BTypes { classBTypeFromInternalName(internalName) = this private def checkInfoConsistency(): Unit = { + if (info.isLeft) return + // we assert some properties. however, some of the linked ClassBType (members, superClass, // interfaces) may not yet have an `_info` (initialization of cyclic structures). so we do a - // best-effort verification. - def ifInit(c: ClassBType)(p: ClassBType => Boolean): Boolean = c._info == null || p(c) + // best-effort verification. also we don't report an error if the info is a Left. + def ifInit(c: ClassBType)(p: ClassBType => Boolean): Boolean = c._info == null || c.info.isLeft || p(c) def isJLO(t: ClassBType) = t.internalName == ObjectReference.internalName assert(!ClassBType.isInternalPhantomType(internalName), s"Cannot create ClassBType for phantom type $this") assert( - if (info.superClass.isEmpty) { isJLO(this) || (isCompilingPrimitive && ClassBType.hasNoSuper(internalName)) } - else if (isInterface) isJLO(info.superClass.get) - else !isJLO(this) && ifInit(info.superClass.get)(!_.isInterface), - s"Invalid superClass in $this: ${info.superClass}" + if (info.get.superClass.isEmpty) { isJLO(this) || (isCompilingPrimitive && ClassBType.hasNoSuper(internalName)) } + else if (isInterface.get) isJLO(info.get.superClass.get) + else !isJLO(this) && ifInit(info.get.superClass.get)(!_.isInterface.get), + s"Invalid superClass in $this: ${info.get.superClass}" ) assert( - info.interfaces.forall(c => ifInit(c)(_.isInterface)), - s"Invalid interfaces in $this: ${info.interfaces}" + info.get.interfaces.forall(c => ifInit(c)(_.isInterface.get)), + s"Invalid interfaces in $this: ${info.get.interfaces}" ) - assert(info.nestedClasses.forall(c => ifInit(c)(_.isNestedClass)), info.nestedClasses) + assert(info.get.nestedClasses.forall(c => ifInit(c)(_.isNestedClass.get)), info.get.nestedClasses) } /** @@ -819,12 +849,12 @@ abstract class BTypes { */ def simpleName: String = internalName.split("/").last - def isInterface = (info.flags & asm.Opcodes.ACC_INTERFACE) != 0 + def isInterface: Either[NoClassBTypeInfo, Boolean] = info.map(i => (i.flags & asm.Opcodes.ACC_INTERFACE) != 0) - def superClassesTransitive: List[ClassBType] = info.superClass match { - case None => Nil - case Some(sc) => sc :: sc.superClassesTransitive - } + def superClassesTransitive: Either[NoClassBTypeInfo, List[ClassBType]] = info.flatMap(i => i.superClass match { + case None => Right(Nil) + case Some(sc) => sc.superClassesTransitive.map(sc :: _) + }) /** * The prefix of the internal name until the last '/', or the empty string. @@ -837,15 +867,19 @@ abstract class BTypes { } } - def isPublic = (info.flags & asm.Opcodes.ACC_PUBLIC) != 0 + def isPublic: Either[NoClassBTypeInfo, Boolean] = info.map(i => (i.flags & asm.Opcodes.ACC_PUBLIC) != 0) - def isNestedClass = info.nestedInfo.isDefined + def isNestedClass: Either[NoClassBTypeInfo, Boolean] = info.map(_.nestedInfo.isDefined) - def enclosingNestedClassesChain: List[ClassBType] = - if (isNestedClass) this :: info.nestedInfo.get.enclosingClass.enclosingNestedClassesChain - else Nil + def enclosingNestedClassesChain: Either[NoClassBTypeInfo, List[ClassBType]] = { + isNestedClass.flatMap(isNested => { + // if isNested is true, we know that info.get is defined, and nestedInfo.get is also defined. + if (isNested) info.get.nestedInfo.get.enclosingClass.enclosingNestedClassesChain.map(this :: _) + else Right(Nil) + }) + } - def innerClassAttributeEntry: Option[InnerClassEntry] = info.nestedInfo map { + def innerClassAttributeEntry: Either[NoClassBTypeInfo, Option[InnerClassEntry]] = info.map(i => i.nestedInfo map { case NestedInfo(_, outerName, innerName, isStaticNestedClass) => InnerClassEntry( internalName, @@ -853,30 +887,39 @@ abstract class BTypes { innerName.orNull, GenBCode.mkFlags( // the static flag in the InnerClass table has a special meaning, see InnerClass comment - info.flags & ~Opcodes.ACC_STATIC, + i.flags & ~Opcodes.ACC_STATIC, if (isStaticNestedClass) Opcodes.ACC_STATIC else 0 ) & ClassBType.INNER_CLASSES_FLAGS ) - } - - def inlineInfoAttribute: InlineInfoAttribute = InlineInfoAttribute(info.inlineInfo) + }) - def isSubtypeOf(other: ClassBType): Boolean = { - if (this == other) return true + def inlineInfoAttribute: Either[NoClassBTypeInfo, InlineInfoAttribute] = info.map(i => { + // InlineInfos are serialized for classes being compiled. For those the info was built by + // buildInlineInfoFromClassSymbol, which only adds a warning under SI-9111, which in turn + // only happens for class symbols of java source files. + // we could put this assertion into InlineInfoAttribute, but it is more safe to put it here + // where it affect only GenBCode, and not add any assertion to GenASM in 2.11.6. + assert(i.inlineInfo.warning.isEmpty, i.inlineInfo.warning) + InlineInfoAttribute(i.inlineInfo) + }) - if (isInterface) { - if (other == ObjectReference) return true // interfaces conform to Object - if (!other.isInterface) return false // this is an interface, the other is some class other than object. interfaces cannot extend classes, so the result is false. + def isSubtypeOf(other: ClassBType): Either[NoClassBTypeInfo, Boolean] = try { + if (this == other) return Right(true) + if (isInterface.orThrow) { + if (other == ObjectReference) return Right(true) // interfaces conform to Object + if (!other.isInterface.orThrow) return Right(false) // this is an interface, the other is some class other than object. interfaces cannot extend classes, so the result is false. // else: this and other are both interfaces. continue to (*) } else { - val sc = info.superClass - if (sc.isDefined && sc.get.isSubtypeOf(other)) return true // the superclass of this class conforms to other - if (!other.isInterface) return false // this and other are both classes, and the superclass of this does not conform + val sc = info.orThrow.superClass + if (sc.isDefined && sc.get.isSubtypeOf(other).orThrow) return Right(true) // the superclass of this class conforms to other + if (!other.isInterface.orThrow) return Right(false) // this and other are both classes, and the superclass of this does not conform // else: this is a class, the other is an interface. continue to (*) } // (*) check if some interface of this class conforms to other. - info.interfaces.exists(_.isSubtypeOf(other)) + Right(info.orThrow.interfaces.exists(_.isSubtypeOf(other).orThrow)) + } catch { + case Invalid(noInfo: NoClassBTypeInfo) => Left(noInfo) } /** @@ -886,34 +929,36 @@ abstract class BTypes { * http://comments.gmane.org/gmane.comp.java.vm.languages/2293 * https://issues.scala-lang.org/browse/SI-3872 */ - def jvmWiseLUB(other: ClassBType): ClassBType = { + def jvmWiseLUB(other: ClassBType): Either[NoClassBTypeInfo, ClassBType] = { def isNotNullOrNothing(c: ClassBType) = !c.isNullType && !c.isNothingType assert(isNotNullOrNothing(this) && isNotNullOrNothing(other), s"jvmWiseLub for null or nothing: $this - $other") - val res: ClassBType = (this.isInterface, other.isInterface) match { - case (true, true) => - // exercised by test/files/run/t4761.scala - if (other.isSubtypeOf(this)) this - else if (this.isSubtypeOf(other)) other - else ObjectReference - - case (true, false) => - if (other.isSubtypeOf(this)) this else ObjectReference - - case (false, true) => - if (this.isSubtypeOf(other)) other else ObjectReference + tryEither { + val res: ClassBType = (this.isInterface.orThrow, other.isInterface.orThrow) match { + case (true, true) => + // exercised by test/files/run/t4761.scala + if (other.isSubtypeOf(this).orThrow) this + else if (this.isSubtypeOf(other).orThrow) other + else ObjectReference + + case (true, false) => + if (other.isSubtypeOf(this).orThrow) this else ObjectReference + + case (false, true) => + if (this.isSubtypeOf(other).orThrow) other else ObjectReference + + case _ => + // TODO @lry I don't really understand the reasoning here. + // Both this and other are classes. The code takes (transitively) all superclasses and + // finds the first common one. + // MOST LIKELY the answer can be found here, see the comments and links by Miguel: + // - https://issues.scala-lang.org/browse/SI-3872 + firstCommonSuffix(this :: this.superClassesTransitive.orThrow, other :: other.superClassesTransitive.orThrow) + } - case _ => - // TODO @lry I don't really understand the reasoning here. - // Both this and other are classes. The code takes (transitively) all superclasses and - // finds the first common one. - // MOST LIKELY the answer can be found here, see the comments and links by Miguel: - // - https://issues.scala-lang.org/browse/SI-3872 - firstCommonSuffix(this :: this.superClassesTransitive, other :: other.superClassesTransitive) + assert(isNotNullOrNothing(res), s"jvmWiseLub computed: $res") + Right(res) } - - assert(isNotNullOrNothing(res), s"jvmWiseLub computed: $res") - res } private def firstCommonSuffix(as: List[ClassBType], bs: List[ClassBType]): ClassBType = { @@ -1080,11 +1125,15 @@ object BTypes { * @param methodInfos The [[MethodInlineInfo]]s for the methods declared in this class. * The map is indexed by the string s"$name$descriptor" (to * disambiguate overloads). + * + * @param warning Contains an warning message if an error occured when building this + * InlineInfo, for example if some classfile could not be found on + * the classpath. This warning can be reported later by the inliner. */ final case class InlineInfo(traitImplClassSelfType: Option[InternalName], isEffectivelyFinal: Boolean, methodInfos: Map[String, MethodInlineInfo], - warning: Option[String]) + warning: Option[ClassInlineInfoWarning]) val EmptyInlineInfo = InlineInfo(None, false, Map.empty, None) @@ -1102,4 +1151,8 @@ object BTypes { traitMethodWithStaticImplementation: Boolean, annotatedInline: Boolean, annotatedNoInline: Boolean) + + // no static way (without symbol table instance) to get to nme.ScalaATTR / ScalaSignatureATTR + val ScalaAttributeName = "Scala" + val ScalaSigAttributeName = "ScalaSig" }
\ No newline at end of file diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala b/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala index b90030dd8c..eeb6ed24a2 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala @@ -9,6 +9,7 @@ package backend.jvm import scala.tools.asm import scala.tools.nsc.backend.jvm.opt.{CallGraph, Inliner, ByteCodeRepository} import scala.tools.nsc.backend.jvm.BTypes.{InlineInfo, MethodInlineInfo, InternalName} +import BackendReporting._ /** * This class mainly contains the method classBTypeFromSymbol, which extracts the necessary @@ -34,12 +35,14 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes { val coreBTypes = new CoreBTypesProxy[this.type](this) import coreBTypes._ - val byteCodeRepository = new ByteCodeRepository(global.classPath, recordPerRunCache(collection.concurrent.TrieMap.empty)) + val byteCodeRepository = new ByteCodeRepository(global.classPath, javaDefinedClasses, recordPerRunCache(collection.concurrent.TrieMap.empty)) val inliner: Inliner[this.type] = new Inliner(this) val callGraph: CallGraph[this.type] = new CallGraph(this) + val backendReporting: BackendReporting = new BackendReportingImpl(global) + final def initializeCoreBTypes(): Unit = { coreBTypes.setBTypes(new CoreBTypes[this.type](this)) } @@ -50,6 +53,16 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes { def inlinerEnabled: Boolean = settings.YoptInlinerEnabled + def warnSettings: WarnSettings = { + val c = settings.YoptWarningsChoices + // cannot extract settings.YoptWarnings into a local val due to some dependent typing issue. + WarnSettings( + !settings.YoptWarnings.isSetByUser || settings.YoptWarnings.contains(c.atInlineFailedSummary.name) || settings.YoptWarnings.contains(c.atInlineFailed.name), + settings.YoptWarnings.contains(c.noInlineMixed.name), + settings.YoptWarnings.contains(c.noInlineMissingBytecode.name), + settings.YoptWarnings.contains(c.noInlineMissingScalaInlineInfoAttr.name)) + } + // helpers that need access to global. // TODO @lry create a separate component, they don't belong to BTypesFromSymbols @@ -104,28 +117,20 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes { else { val internalName = classSym.javaBinaryName.toString classBTypeFromInternalName.getOrElse(internalName, { + // The new ClassBType is added to the map in its constructor, before we set its info. This + // allows initializing cyclic dependencies, see the comment on variable ClassBType._info. + val res = ClassBType(internalName) if (completeSilentlyAndCheckErroneous(classSym)) { - new ErroneousClassBType(internalName) + res.info = Left(NoClassBTypeInfoClassSymbolInfoFailedSI9111(classSym.fullName)) + res } else { - // The new ClassBType is added to the map in its constructor, before we set its info. This - // allows initializing cyclic dependencies, see the comment on variable ClassBType._info. - setClassInfo(classSym, ClassBType(internalName)) + setClassInfo(classSym, res) } }) } } /** - * Part of the workaround for SI-9111. Makes sure that the compiler only fails if the ClassInfo - * of the symbol that could not be completed is actually required. - */ - private class ErroneousClassBType(internalName: InternalName) extends ClassBType(internalName) { - def msg = s"The class info for $internalName could not be completed due to SI-9111." - override def info: ClassInfo = opt.OptimizerReporting.assertionError(msg) - override def info_=(i: ClassInfo): Unit = opt.OptimizerReporting.assertionError(msg) - } - - /** * Builds a [[MethodBType]] for a method symbol. */ final def methodBTypeFromSymbol(methodSymbol: Symbol): MethodBType = { @@ -202,7 +207,7 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes { case ThisType(sym) => classBTypeFromSymbol(sym) case SingleType(_, sym) => primitiveOrClassToBType(sym) case ConstantType(_) => typeToBType(t.underlying) - case RefinedType(parents, _) => parents.map(typeToBType(_).asClassBType).reduceLeft((a, b) => a.jvmWiseLUB(b)) + case RefinedType(parents, _) => parents.map(typeToBType(_).asClassBType).reduceLeft((a, b) => a.jvmWiseLUB(b).get) } } } @@ -340,7 +345,7 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes { val inlineInfo = buildInlineInfo(classSym, classBType.internalName) - classBType.info = ClassInfo(superClass, interfaces, flags, nestedClasses, nestedInfo, inlineInfo) + classBType.info = Right(ClassInfo(superClass, interfaces, flags, nestedClasses, nestedInfo, inlineInfo)) classBType } @@ -421,13 +426,10 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes { // symbols being compiled. For non-compiled classes, we could not build MethodInlineInfos // for those mixin members, which prevents inlining. byteCodeRepository.classNode(internalName) match { - case Some(classNode) => + case Right(classNode) => inlineInfoFromClassfile(classNode) - case None => - // TODO: inliner warning if the InlineInfo for that class is being used - // We can still use the inline information built from the symbol, even though mixin - // members will be missing. - buildFromSymbol + case Left(missingClass) => + InlineInfo(None, false, Map.empty, Some(ClassNotFoundWhenBuildingInlineInfoFromSymbol(missingClass))) } } } @@ -444,14 +446,13 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes { val c = ClassBType(internalName) // class info consistent with BCodeHelpers.genMirrorClass val nested = exitingPickler(memberClassesForInnerClassTable(moduleClassSym)) map classBTypeFromSymbol - c.info = ClassInfo( + c.info = Right(ClassInfo( superClass = Some(ObjectReference), interfaces = Nil, flags = asm.Opcodes.ACC_SUPER | asm.Opcodes.ACC_PUBLIC | asm.Opcodes.ACC_FINAL, nestedClasses = nested, nestedInfo = None, - InlineInfo(None, true, Map.empty, None) // no InlineInfo needed, scala never invokes methods on the mirror class - ) + InlineInfo(None, true, Map.empty, None))) // no InlineInfo needed, scala never invokes methods on the mirror class c }) } diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BackendReporting.scala b/src/compiler/scala/tools/nsc/backend/jvm/BackendReporting.scala new file mode 100644 index 0000000000..a06fb4bab8 --- /dev/null +++ b/src/compiler/scala/tools/nsc/backend/jvm/BackendReporting.scala @@ -0,0 +1,265 @@ +package scala.tools.nsc +package backend.jvm + +import scala.tools.asm.tree.{AbstractInsnNode, MethodNode} +import scala.tools.nsc.backend.jvm.BTypes.InternalName +import scala.reflect.internal.util.Position + +/** + * Interface for emitting inline warnings. The interface is required because the implementation + * depends on Global, which is not available in BTypes (only in BTypesFromSymbols). + */ +sealed abstract class BackendReporting { + def inlinerWarning(pos: Position, message: String): Unit +} + +final class BackendReportingImpl(val global: Global) extends BackendReporting { + import global._ + + def inlinerWarning(pos: Position, message: String): Unit = { + currentRun.reporting.inlinerWarning(pos, message) + } +} + +/** + * Utilities for error reporting. + * + * Defines some tools to make error reporting with Either easier. Would be subsumed by a right-biased + * Either in the standard library (or scalaz \/) (Validation is different, it accumulates multiple + * errors). + */ +object BackendReporting { + def methodSignature(classInternalName: InternalName, name: String, desc: String) = { + classInternalName + "::" + name + desc + } + + def methodSignature(classInternalName: InternalName, method: MethodNode): String = { + methodSignature(classInternalName, method.name, method.desc) + } + + def assertionError(message: String): Nothing = throw new AssertionError(message) + + implicit class RightBiasedEither[A, B](val v: Either[A, B]) extends AnyVal { + def map[U](f: B => U) = v.right.map(f) + def flatMap[BB](f: B => Either[A, BB]) = v.right.flatMap(f) + def filter(f: B => Boolean)(implicit empty: A): Either[A, B] = v match { + case Left(_) => v + case Right(e) => if (f(e)) v else Left(empty) // scalaz.\/ requires an implicit Monoid m to get m.empty + } + def foreach[U](f: B => U) = v.right.foreach(f) + + def getOrElse[BB >: B](alt: => BB): BB = v.right.getOrElse(alt) + + /** + * Get the value, fail with an assertion if this is an error. + */ + def get: B = { + assert(v.isRight, v.left.get) + v.right.get + } + + /** + * Get the right value of an `Either` by throwing a potential error message. Can simplify the + * implementation of methods that act on multiple `Either` instances. Instead of flat-mapping, + * the first error can be collected as + * + * tryEither { + * eitherOne.orThrow .... eitherTwo.orThrow ... eitherThree.orThrow + * } + */ + def orThrow: B = v match { + case Left(m) => throw Invalid(m) + case Right(t) => t + } + } + + case class Invalid[A](e: A) extends Exception + + /** + * See documentation of orThrow above. + */ + def tryEither[A, B](op: => Either[A, B]): Either[A, B] = try { op } catch { case Invalid(e) => Left(e.asInstanceOf[A]) } + + final case class WarnSettings(atInlineFailed: Boolean, noInlineMixed: Boolean, noInlineMissingBytecode: Boolean, noInlineMissingScalaInlineInfoAttr: Boolean) + + sealed trait OptimizerWarning { + def emitWarning(settings: WarnSettings): Boolean + } + + // Method filter in RightBiasedEither requires an implicit empty value. Taking the value here + // in scope allows for-comprehensions that desugar into filter calls (for example when using a + // tuple de-constructor). + implicit object emptyOptimizerWarning extends OptimizerWarning { + def emitWarning(settings: WarnSettings): Boolean = false + } + + sealed trait MissingBytecodeWarning extends OptimizerWarning { + override def toString = this match { + case ClassNotFound(internalName, definedInJavaSource) => + s"The classfile for $internalName could not be found on the compilation classpath." + { + if (definedInJavaSource) "\nThe class is defined in a Java source file that is being compiled (mixed compilation), therefore no bytecode is available." + else "" + } + + case MethodNotFound(name, descriptor, ownerInternalName, missingClasses) => + val (javaDef, others) = missingClasses.partition(_.definedInJavaSource) + s"The method $name$descriptor could not be found in the class $ownerInternalName or any of its parents." + + (if (others.isEmpty) "" else others.map(_.internalName).mkString("\nNote that the following parent classes could not be found on the classpath: ", ", ", "")) + + (if (javaDef.isEmpty) "" else javaDef.map(_.internalName).mkString("\nNote that the following parent classes are defined in Java sources (mixed compilation), no bytecode is available: ", ",", "")) + + case FieldNotFound(name, descriptor, ownerInternalName, missingClass) => + s"The field node $name$descriptor could not be found because the classfile $ownerInternalName cannot be found on the classpath." + + missingClass.map(c => s" Reason:\n$c").getOrElse("") + } + + def emitWarning(settings: WarnSettings): Boolean = this match { + case ClassNotFound(_, javaDefined) => + if (javaDefined) settings.noInlineMixed + else settings.noInlineMissingBytecode + + case m @ MethodNotFound(_, _, _, missing) => + if (m.isArrayMethod) false + else settings.noInlineMissingBytecode || missing.exists(_.emitWarning(settings)) + + case FieldNotFound(_, _, _, missing) => + settings.noInlineMissingBytecode || missing.exists(_.emitWarning(settings)) + } + } + + case class ClassNotFound(internalName: InternalName, definedInJavaSource: Boolean) extends MissingBytecodeWarning + case class MethodNotFound(name: String, descriptor: String, ownerInternalNameOrArrayDescriptor: InternalName, missingClasses: List[ClassNotFound]) extends MissingBytecodeWarning { + def isArrayMethod = ownerInternalNameOrArrayDescriptor.charAt(0) == '[' + } + case class FieldNotFound(name: String, descriptor: String, ownerInternalName: InternalName, missingClass: Option[ClassNotFound]) extends MissingBytecodeWarning + + sealed trait NoClassBTypeInfo extends OptimizerWarning { + override def toString = this match { + case NoClassBTypeInfoMissingBytecode(cause) => + cause.toString + + case NoClassBTypeInfoClassSymbolInfoFailedSI9111(classFullName) => + s"Failed to get the type of class symbol $classFullName due to SI-9111." + } + + def emitWarning(settings: WarnSettings): Boolean = this match { + case NoClassBTypeInfoMissingBytecode(cause) => cause.emitWarning(settings) + case NoClassBTypeInfoClassSymbolInfoFailedSI9111(_) => settings.noInlineMissingBytecode + } + } + + case class NoClassBTypeInfoMissingBytecode(cause: MissingBytecodeWarning) extends NoClassBTypeInfo + case class NoClassBTypeInfoClassSymbolInfoFailedSI9111(classFullName: String) extends NoClassBTypeInfo + + /** + * Used in the CallGraph for nodes where an issue occurred determining the callee information. + */ + sealed trait CalleeInfoWarning extends OptimizerWarning { + def declarationClass: InternalName + def name: String + def descriptor: String + + def warningMessageSignature = BackendReporting.methodSignature(declarationClass, name, descriptor) + + override def toString = this match { + case MethodInlineInfoIncomplete(_, _, _, cause) => + s"The inline information for $warningMessageSignature may be incomplete:\n" + cause + + case MethodInlineInfoMissing(_, _, _, cause) => + s"No inline information for method $warningMessageSignature could be found." + + cause.map(" Possible reason:\n" + _).getOrElse("") + + case MethodInlineInfoError(_, _, _, cause) => + s"Error while computing the inline information for method $warningMessageSignature:\n" + cause + + case RewriteTraitCallToStaticImplMethodFailed(_, _, _, cause) => + cause.toString + } + + def emitWarning(settings: WarnSettings): Boolean = this match { + case MethodInlineInfoIncomplete(_, _, _, cause) => cause.emitWarning(settings) + + case MethodInlineInfoMissing(_, _, _, Some(cause)) => cause.emitWarning(settings) + case MethodInlineInfoMissing(_, _, _, None) => settings.noInlineMissingBytecode + + case MethodInlineInfoError(_, _, _, cause) => cause.emitWarning(settings) + + case RewriteTraitCallToStaticImplMethodFailed(_, _, _, cause) => cause.emitWarning(settings) + } + } + + case class MethodInlineInfoIncomplete(declarationClass: InternalName, name: String, descriptor: String, cause: ClassInlineInfoWarning) extends CalleeInfoWarning + case class MethodInlineInfoMissing(declarationClass: InternalName, name: String, descriptor: String, cause: Option[ClassInlineInfoWarning]) extends CalleeInfoWarning + case class MethodInlineInfoError(declarationClass: InternalName, name: String, descriptor: String, cause: NoClassBTypeInfo) extends CalleeInfoWarning + case class RewriteTraitCallToStaticImplMethodFailed(declarationClass: InternalName, name: String, descriptor: String, cause: OptimizerWarning) extends CalleeInfoWarning + + sealed trait CannotInlineWarning extends OptimizerWarning { + def calleeDeclarationClass: InternalName + def name: String + def descriptor: String + + def calleeMethodSig = BackendReporting.methodSignature(calleeDeclarationClass, name, descriptor) + + override def toString = this match { + case IllegalAccessInstruction(_, _, _, callsiteClass, instruction) => + s"The callee $calleeMethodSig contains the instruction ${AsmUtils.textify(instruction)}" + + s"\nthat would cause an IllegalAccessError when inlined into class $callsiteClass." + + case IllegalAccessCheckFailed(_, _, _, callsiteClass, instruction, cause) => + s"Failed to check if $calleeMethodSig can be safely inlined to $callsiteClass without causing an IllegalAccessError. Checking instruction ${AsmUtils.textify(instruction)} failed:\n" + cause + + case MethodWithHandlerCalledOnNonEmptyStack(_, _, _, callsiteClass, callsiteName, callsiteDesc) => + s"""The operand stack at the callsite in ${BackendReporting.methodSignature(callsiteClass, callsiteName, callsiteDesc)} contains more values than the + |arguments expected by the callee $calleeMethodSig. These values would be discarded + |when entering an exception handler declared in the inlined method.""".stripMargin + + case SynchronizedMethod(_, _, _) => + s"Method $calleeMethodSig cannot be inlined because it is synchronized." + } + + def emitWarning(settings: WarnSettings): Boolean = this match { + case _: IllegalAccessInstruction | _: MethodWithHandlerCalledOnNonEmptyStack | _: SynchronizedMethod => + settings.atInlineFailed + + case IllegalAccessCheckFailed(_, _, _, _, _, cause) => + cause.emitWarning(settings) + } + } + case class IllegalAccessInstruction(calleeDeclarationClass: InternalName, name: String, descriptor: String, + callsiteClass: InternalName, instruction: AbstractInsnNode) extends CannotInlineWarning + case class IllegalAccessCheckFailed(calleeDeclarationClass: InternalName, name: String, descriptor: String, + callsiteClass: InternalName, instruction: AbstractInsnNode, cause: OptimizerWarning) extends CannotInlineWarning + case class MethodWithHandlerCalledOnNonEmptyStack(calleeDeclarationClass: InternalName, name: String, descriptor: String, + callsiteClass: InternalName, callsiteName: String, callsiteDesc: String) extends CannotInlineWarning + case class SynchronizedMethod(calleeDeclarationClass: InternalName, name: String, descriptor: String) extends CannotInlineWarning + + /** + * Used in the InlineInfo of a ClassBType, when some issue occurred obtaining the inline information. + */ + sealed trait ClassInlineInfoWarning extends OptimizerWarning { + override def toString = this match { + case NoInlineInfoAttribute(internalName) => + s"The Scala classfile $internalName does not have a ScalaInlineInfo attribute." + + case ClassSymbolInfoFailureSI9111(classFullName) => + s"Failed to get the type of a method of class symbol $classFullName due to SI-9111." + + case ClassNotFoundWhenBuildingInlineInfoFromSymbol(missingClass) => + s"Failed to build the inline information: $missingClass." + + case UnknownScalaInlineInfoVersion(internalName, version) => + s"Cannot read ScalaInlineInfo version $version in classfile $internalName. Use a more recent compiler." + } + + def emitWarning(settings: WarnSettings): Boolean = this match { + case NoInlineInfoAttribute(_) => settings.noInlineMissingScalaInlineInfoAttr + case ClassNotFoundWhenBuildingInlineInfoFromSymbol(cause) => cause.emitWarning(settings) + case ClassSymbolInfoFailureSI9111(_) => settings.noInlineMissingBytecode + case UnknownScalaInlineInfoVersion(_, _) => settings.noInlineMissingScalaInlineInfoAttr + } + } + + case class NoInlineInfoAttribute(internalName: InternalName) extends ClassInlineInfoWarning + case class ClassSymbolInfoFailureSI9111(classFullName: String) extends ClassInlineInfoWarning + case class ClassNotFoundWhenBuildingInlineInfoFromSymbol(missingClass: ClassNotFound) extends ClassInlineInfoWarning + case class UnknownScalaInlineInfoVersion(internalName: InternalName, version: Int) extends ClassInlineInfoWarning +} diff --git a/src/compiler/scala/tools/nsc/backend/jvm/CoreBTypes.scala b/src/compiler/scala/tools/nsc/backend/jvm/CoreBTypes.scala index 246235f395..492fe3ae79 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/CoreBTypes.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/CoreBTypes.scala @@ -99,10 +99,9 @@ class CoreBTypes[BTFS <: BTypesFromSymbols[_ <: Global]](val bTypes: BTFS) { * * Therefore, when RT_NOTHING or RT_NULL are to be emitted, a mapping is needed: the internal * names of NothingClass and NullClass can't be emitted as-is. - * TODO @lry Once there's a 2.11.3 starr, use the commented argument list. The current starr crashes on the type literal `scala.runtime.Nothing$` */ - lazy val RT_NOTHING : ClassBType = classBTypeFromSymbol(rootMirror.getRequiredClass("scala.runtime.Nothing$")) // (requiredClass[scala.runtime.Nothing$]) - lazy val RT_NULL : ClassBType = classBTypeFromSymbol(rootMirror.getRequiredClass("scala.runtime.Null$")) // (requiredClass[scala.runtime.Null$]) + lazy val RT_NOTHING : ClassBType = classBTypeFromSymbol(requiredClass[scala.runtime.Nothing$]) + lazy val RT_NULL : ClassBType = classBTypeFromSymbol(requiredClass[scala.runtime.Null$]) lazy val ObjectReference : ClassBType = classBTypeFromSymbol(ObjectClass) lazy val objArrayReference : ArrayBType = ArrayBType(ObjectReference) diff --git a/src/compiler/scala/tools/nsc/backend/jvm/GenBCode.scala b/src/compiler/scala/tools/nsc/backend/jvm/GenBCode.scala index 173aa0ca30..be1595dc29 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/GenBCode.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/GenBCode.scala @@ -307,6 +307,10 @@ abstract class GenBCode extends BCodeSyncAndTry { arrivalPos = 0 // just in case scalaPrimitives.init() bTypes.initializeCoreBTypes() + bTypes.javaDefinedClasses.clear() + bTypes.javaDefinedClasses ++= currentRun.symSource collect { + case (sym, _) if sym.isJavaDefined => sym.javaBinaryName.toString + } Statistics.stopTimer(BackendStats.bcodeInitTimer, initStart) // initBytecodeWriter invokes fullName, thus we have to run it before the typer-dependent thread is activated. diff --git a/src/compiler/scala/tools/nsc/backend/jvm/opt/ByteCodeRepository.scala b/src/compiler/scala/tools/nsc/backend/jvm/opt/ByteCodeRepository.scala index 0958601d73..607b7145d6 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/opt/ByteCodeRepository.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/ByteCodeRepository.scala @@ -11,9 +11,9 @@ import scala.tools.asm import asm.tree._ import scala.collection.convert.decorateAsScala._ import scala.tools.asm.Attribute +import scala.tools.nsc.backend.jvm.BackendReporting._ import scala.tools.nsc.io.AbstractFile import scala.tools.nsc.util.ClassFileLookup -import OptimizerReporting._ import BytecodeUtils._ import ByteCodeRepository._ import BTypes.InternalName @@ -29,10 +29,10 @@ import java.util.concurrent.atomic.AtomicLong * corresponds to a class being compiled. * The `Long` field encodes the age of the node in the map, which allows removing * old entries when the map grows too large. - * For Java classes in mixed compilation, the map contains `None`: there is no - * ClassNode generated by the backend and also no classfile that could be parsed. + * For Java classes in mixed compilation, the map contains an error message: no + * ClassNode is generated by the backend and also no classfile that could be parsed. */ -class ByteCodeRepository(val classPath: ClassFileLookup[AbstractFile], val classes: collection.concurrent.Map[InternalName, Option[(ClassNode, Source, Long)]]) { +class ByteCodeRepository(val classPath: ClassFileLookup[AbstractFile], val isJavaSourceDefined: InternalName => Boolean, val classes: collection.concurrent.Map[InternalName, Either[ClassNotFound, (ClassNode, Source, Long)]]) { private val maxCacheSize = 1500 private val targetSize = 500 @@ -46,24 +46,24 @@ class ByteCodeRepository(val classPath: ClassFileLookup[AbstractFile], val class * We can only remove classes with `Source == Classfile`, those can be parsed again if requested. */ private def limitCacheSize(): Unit = { - if (classes.count(c => c._2.isDefined && c._2.get._2 == Classfile) > maxCacheSize) { + if (classes.count(c => c._2.isRight && c._2.right.get._2 == Classfile) > maxCacheSize) { val removeId = idCounter.get - targetSize val toRemove = classes.iterator.collect({ - case (name, Some((_, Classfile, id))) if id < removeId => name + case (name, Right((_, Classfile, id))) if id < removeId => name }).toList toRemove foreach classes.remove } } def add(classNode: ClassNode, source: Source) = { - classes(classNode.name) = Some((classNode, source, idCounter.incrementAndGet())) + classes(classNode.name) = Right((classNode, source, idCounter.incrementAndGet())) } /** * The class node and source for an internal name. If the class node is not yet available, it is * parsed from the classfile on the compile classpath. */ - def classNodeAndSource(internalName: InternalName): Option[(ClassNode, Source)] = { + def classNodeAndSource(internalName: InternalName): Either[ClassNotFound, (ClassNode, Source)] = { val r = classes.getOrElseUpdate(internalName, { limitCacheSize() parseClass(internalName).map((_, Classfile, idCounter.incrementAndGet())) @@ -75,42 +75,66 @@ class ByteCodeRepository(val classPath: ClassFileLookup[AbstractFile], val class * The class node for an internal name. If the class node is not yet available, it is parsed from * the classfile on the compile classpath. */ - def classNode(internalName: InternalName): Option[ClassNode] = classNodeAndSource(internalName).map(_._1) + def classNode(internalName: InternalName): Either[ClassNotFound, ClassNode] = classNodeAndSource(internalName).map(_._1) /** * The field node for a field matching `name` and `descriptor`, accessed in class `classInternalName`. * The declaration of the field may be in one of the superclasses. * - * @return The [[FieldNode]] of the requested field and the [[InternalName]] of its declaring class. + * @return The [[FieldNode]] of the requested field and the [[InternalName]] of its declaring + * class, or an error message if the field could not be found */ - def fieldNode(classInternalName: InternalName, name: String, descriptor: String): Option[(FieldNode, InternalName)] = { - classNode(classInternalName).flatMap(c => - c.fields.asScala.find(f => f.name == name && f.desc == descriptor).map((_, classInternalName)) orElse { - Option(c.superName).flatMap(n => fieldNode(n, name, descriptor)) - }) + def fieldNode(classInternalName: InternalName, name: String, descriptor: String): Either[FieldNotFound, (FieldNode, InternalName)] = { + def fieldNodeImpl(parent: InternalName): Either[FieldNotFound, (FieldNode, InternalName)] = { + def msg = s"The field node $name$descriptor could not be found in class $classInternalName or any of its superclasses." + classNode(parent) match { + case Left(e) => Left(FieldNotFound(name, descriptor, classInternalName, Some(e))) + case Right(c) => + c.fields.asScala.find(f => f.name == name && f.desc == descriptor) match { + case Some(f) => Right((f, parent)) + case None => + if (c.superName == null) Left(FieldNotFound(name, descriptor, classInternalName, None)) + else fieldNode(c.superName, name, descriptor) + } + } + } + fieldNodeImpl(classInternalName) } /** * The method node for a method matching `name` and `descriptor`, accessed in class `classInternalName`. * The declaration of the method may be in one of the parents. * - * @return The [[MethodNode]] of the requested method and the [[InternalName]] of its declaring class. + * @return The [[MethodNode]] of the requested method and the [[InternalName]] of its declaring + * class, or an error message if the method could not be found. */ - def methodNode(ownerInternalNameOrArrayDescriptor: String, name: String, descriptor: String): Option[(MethodNode, InternalName)] = { - // In a MethodInsnNode, the `owner` field may be an array descriptor, for exmple when invoking `clone`. - // We don't inline array methods (they are native anyway), so just return None. - if (ownerInternalNameOrArrayDescriptor.charAt(0) == '[') None - else { - classNode(ownerInternalNameOrArrayDescriptor).flatMap(c => - c.methods.asScala.find(m => m.name == name && m.desc == descriptor).map((_, ownerInternalNameOrArrayDescriptor)) orElse { - val parents = Option(c.superName) ++ c.interfaces.asScala - // `view` to stop at the first result - parents.view.flatMap(methodNode(_, name, descriptor)).headOption - }) + def methodNode(ownerInternalNameOrArrayDescriptor: String, name: String, descriptor: String): Either[MethodNotFound, (MethodNode, InternalName)] = { + // on failure, returns a list of class names that could not be found on the classpath + def methodNodeImpl(ownerInternalName: InternalName): Either[List[ClassNotFound], (MethodNode, InternalName)] = { + classNode(ownerInternalName) match { + case Left(e) => Left(List(e)) + case Right(c) => + c.methods.asScala.find(m => m.name == name && m.desc == descriptor) match { + case Some(m) => Right((m, ownerInternalName)) + case None => findInParents(Option(c.superName) ++: c.interfaces.asScala.toList, Nil) + } + } + } + + // find the MethodNode in one of the parent classes + def findInParents(parents: List[InternalName], failedClasses: List[ClassNotFound]): Either[List[ClassNotFound], (MethodNode, InternalName)] = parents match { + case x :: xs => methodNodeImpl(x).left.flatMap(failed => findInParents(xs, failed ::: failedClasses)) + case Nil => Left(failedClasses) } + + // In a MethodInsnNode, the `owner` field may be an array descriptor, for exmple when invoking `clone`. We don't have a method node to return in this case. + if (ownerInternalNameOrArrayDescriptor.charAt(0) == '[') + Left(MethodNotFound(name, descriptor, ownerInternalNameOrArrayDescriptor, Nil)) + else + methodNodeImpl(ownerInternalNameOrArrayDescriptor).left.map(MethodNotFound(name, descriptor, ownerInternalNameOrArrayDescriptor, _)) } - private def parseClass(internalName: InternalName): Option[ClassNode] = { + private def parseClass(internalName: InternalName): Either[ClassNotFound, ClassNode] = { val fullName = internalName.replace('/', '.') classPath.findClassFile(fullName) map { classFile => val classNode = new asm.tree.ClassNode() @@ -131,6 +155,9 @@ class ByteCodeRepository(val classPath: ClassFileLookup[AbstractFile], val class // https://jcp.org/aboutJava/communityprocess/final/jsr045/index.html removeLineNumberNodes(classNode) classNode + } match { + case Some(node) => Right(node) + case None => Left(ClassNotFound(internalName, isJavaSourceDefined(internalName))) } } } diff --git a/src/compiler/scala/tools/nsc/backend/jvm/opt/BytecodeUtils.scala b/src/compiler/scala/tools/nsc/backend/jvm/opt/BytecodeUtils.scala index d2658bcd2a..14e8cccc60 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/opt/BytecodeUtils.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/BytecodeUtils.scala @@ -85,7 +85,7 @@ object BytecodeUtils { def isFinalClass(classNode: ClassNode): Boolean = (classNode.access & Opcodes.ACC_FINAL) != 0 - def isFinalMethod(methodNode: MethodNode): Boolean = (methodNode.access & (Opcodes.ACC_FINAL | Opcodes.ACC_PRIVATE)) != 0 + def isFinalMethod(methodNode: MethodNode): Boolean = (methodNode.access & (Opcodes.ACC_FINAL | Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC)) != 0 def nextExecutableInstruction(instruction: AbstractInsnNode, alsoKeep: AbstractInsnNode => Boolean = Set()): Option[AbstractInsnNode] = { var result = instruction diff --git a/src/compiler/scala/tools/nsc/backend/jvm/opt/CallGraph.scala b/src/compiler/scala/tools/nsc/backend/jvm/opt/CallGraph.scala index 18b95184e5..cd204e8043 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/opt/CallGraph.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/CallGraph.scala @@ -7,9 +7,11 @@ package scala.tools.nsc package backend.jvm package opt +import scala.reflect.internal.util.{NoPosition, Position} import scala.tools.asm.tree._ import scala.collection.convert.decorateAsScala._ -import scala.tools.nsc.backend.jvm.BTypes.InternalName +import scala.tools.nsc.backend.jvm.BTypes.{MethodInlineInfo, InternalName} +import scala.tools.nsc.backend.jvm.BackendReporting._ import scala.tools.nsc.backend.jvm.opt.BytecodeUtils.AsmAnalyzer import ByteCodeRepository.{Source, CompilationUnit} @@ -25,39 +27,40 @@ class CallGraph[BT <: BTypes](val btypes: BT) { def analyzeCallsites(methodNode: MethodNode, definingClass: ClassBType): List[Callsite] = { + case class CallsiteInfo(safeToInline: Boolean, annotatedInline: Boolean, annotatedNoInline: Boolean, warning: Option[CalleeInfoWarning]) + /** * Analyze a callsite and gather meta-data that can be used for inlining decisions. - * - * @return Three booleans indicating whether - * 1. the callsite can be safely inlined - * 2. the callee is annotated `@inline` - * 3. the callee is annotated `@noinline` */ - def analyzeCallsite(calleeMethodNode: MethodNode, calleeDeclarationClassBType: ClassBType, receiverTypeInternalName: InternalName, calleeSource: Source): (Boolean, Boolean, Boolean) = { + def analyzeCallsite(calleeMethodNode: MethodNode, calleeDeclarationClassBType: ClassBType, receiverTypeInternalName: InternalName, calleeSource: Source): CallsiteInfo = { val methodSignature = calleeMethodNode.name + calleeMethodNode.desc - // The inlineInfo.methodInfos of a ClassBType holds an InlineInfo for each method *declared* - // within a class (not for inherited methods). Since we already have the classBType of the - // callee, we only check there for the methodInlineInfo, we should find it there. - calleeDeclarationClassBType.info.inlineInfo.methodInfos.find(_._1 == methodSignature) match { - case Some((_, methodInlineInfo)) => - val canInlineFromSource = inlineGlobalEnabled || calleeSource == CompilationUnit - // A non-final method can be inline if the receiver type is a final subclass. Example: - // class A { @inline def f = 1 }; object B extends A; B.f // can be inlined - def isStaticallyResolved: Boolean = { - // TODO: type analysis can render more calls statically resolved - // Example: `new A.f` can be inlined, the receiver type is known to be exactly A. - methodInlineInfo.effectivelyFinal || { - // TODO: inline warning when the receiver class cannot be found on the classpath - classBTypeFromParsedClassfile(receiverTypeInternalName).exists(_.info.inlineInfo.isEffectivelyFinal) + try { + // The inlineInfo.methodInfos of a ClassBType holds an InlineInfo for each method *declared* + // within a class (not for inherited methods). Since we already have the classBType of the + // callee, we only check there for the methodInlineInfo, we should find it there. + calleeDeclarationClassBType.info.orThrow.inlineInfo.methodInfos.get(methodSignature) match { + case Some(methodInlineInfo) => + val canInlineFromSource = inlineGlobalEnabled || calleeSource == CompilationUnit + // A non-final method can be inline if the receiver type is a final subclass. Example: + // class A { @inline def f = 1 }; object B extends A; B.f // can be inlined + def isStaticallyResolved: Boolean = { + // TODO: type analysis can render more calls statically resolved + // Example: `new A.f` can be inlined, the receiver type is known to be exactly A. + methodInlineInfo.effectivelyFinal || classBTypeFromParsedClassfile(receiverTypeInternalName).info.orThrow.inlineInfo.isEffectivelyFinal } - } - - (canInlineFromSource && isStaticallyResolved, methodInlineInfo.annotatedInline, methodInlineInfo.annotatedNoInline) + val warning = calleeDeclarationClassBType.info.orThrow.inlineInfo.warning.map( + MethodInlineInfoIncomplete(calleeDeclarationClassBType.internalName, calleeMethodNode.name, calleeMethodNode.desc, _)) + CallsiteInfo(canInlineFromSource && isStaticallyResolved, methodInlineInfo.annotatedInline, methodInlineInfo.annotatedNoInline, warning) - case None => - // TODO: issue inliner warning - (false, false, false) + case None => + val warning = MethodInlineInfoMissing(calleeDeclarationClassBType.internalName, calleeMethodNode.name, calleeMethodNode.desc, calleeDeclarationClassBType.info.orThrow.inlineInfo.warning) + CallsiteInfo(false, false, false, Some(warning)) + } + } catch { + case Invalid(noInfo: NoClassBTypeInfo) => + val warning = MethodInlineInfoError(calleeDeclarationClassBType.internalName, calleeMethodNode.name, calleeMethodNode.desc, noInfo) + CallsiteInfo(false, false, false, Some(warning)) } } @@ -72,25 +75,22 @@ class CallGraph[BT <: BTypes](val btypes: BT) { methodNode.instructions.iterator.asScala.collect({ case call: MethodInsnNode => - // TODO: log an inliner warning if the callee method cannot be found in the code repo? eg it's not on the classpath. - val callee = byteCodeRepository.methodNode(call.owner, call.name, call.desc) flatMap { - case (method, declarationClass) => - // TODO: log inliner warning if callee decl class cannot be found? - byteCodeRepository.classNodeAndSource(declarationClass) map { - case (declarationClassNode, source) => - val declarationClassBType = classBTypeFromClassNode(declarationClassNode) - val (safeToInline, annotatedInline, annotatedNoInline) = analyzeCallsite(method, declarationClassBType, call.owner, source) - Callee( - callee = method, - calleeDeclarationClass = declarationClassBType, - safeToInline = safeToInline, - annotatedInline = annotatedInline, - annotatedNoInline = annotatedNoInline - ) - } + val callee: Either[OptimizerWarning, Callee] = for { + (method, declarationClass) <- byteCodeRepository.methodNode(call.owner, call.name, call.desc): Either[OptimizerWarning, (MethodNode, InternalName)] + (declarationClassNode, source) <- byteCodeRepository.classNodeAndSource(declarationClass): Either[OptimizerWarning, (ClassNode, Source)] + declarationClassBType = classBTypeFromClassNode(declarationClassNode) + } yield { + val CallsiteInfo(safeToInline, annotatedInline, annotatedNoInline, warning) = analyzeCallsite(method, declarationClassBType, call.owner, source) + Callee( + callee = method, + calleeDeclarationClass = declarationClassBType, + safeToInline = safeToInline, + annotatedInline = annotatedInline, + annotatedNoInline = annotatedNoInline, + calleeInfoWarning = warning) } - val argInfos = if (callee.isEmpty) Nil else { + val argInfos = if (callee.isLeft) Nil else { // TODO: for now it's Nil, because we don't run any data flow analysis // there's no point in using the parameter types, that doesn't add any information. // NOTE: need to run the same analyses after inlining, to re-compute the argInfos for the @@ -104,7 +104,8 @@ class CallGraph[BT <: BTypes](val btypes: BT) { callsiteClass = definingClass, callee = callee, argInfos = argInfos, - callsiteStackHeight = analyzer.frameAt(call).getStackSize + callsiteStackHeight = analyzer.frameAt(call).getStackSize, + callsitePosition = callsitePositions.getOrElse(call, NoPosition) ) }).toList } @@ -117,15 +118,20 @@ class CallGraph[BT <: BTypes](val btypes: BT) { * @param callsiteClass The class containing the callsite * @param callee The callee, as it appears in the invocation instruction. For virtual * calls, an override of the callee might be invoked. Also, the callee - * can be abstract. `None` if the callee MethodNode cannot be found in - * the bytecode repository. + * can be abstract. Contains a warning message if the callee MethodNode + * cannot be found in the bytecode repository. * @param argInfos Information about the invocation receiver and arguments * @param callsiteStackHeight The stack height at the callsite, required by the inliner + * @param callsitePosition The source position of the callsite, used for inliner warnings. */ final case class Callsite(callsiteInstruction: MethodInsnNode, callsiteMethod: MethodNode, callsiteClass: ClassBType, - callee: Option[Callee], argInfos: List[ArgInfo], - callsiteStackHeight: Int) { - override def toString = s"Invocation of ${callee.map(_.calleeDeclarationClass.internalName).getOrElse("?")}.${callsiteInstruction.name + callsiteInstruction.desc}@${callsiteMethod.instructions.indexOf(callsiteInstruction)} in ${callsiteClass.internalName}.${callsiteMethod.name}" + callee: Either[OptimizerWarning, Callee], argInfos: List[ArgInfo], + callsiteStackHeight: Int, callsitePosition: Position) { + override def toString = + "Invocation of" + + s" ${callee.map(_.calleeDeclarationClass.internalName).getOrElse("?")}.${callsiteInstruction.name + callsiteInstruction.desc}" + + s"@${callsiteMethod.instructions.indexOf(callsiteInstruction)}" + + s" in ${callsiteClass.internalName}.${callsiteMethod.name}" } /** @@ -147,8 +153,11 @@ class CallGraph[BT <: BTypes](val btypes: BT) { * and the inliner settings (project / global) allow inlining it. * @param annotatedInline True if the callee is annotated @inline * @param annotatedNoInline True if the callee is annotated @noinline + * @param calleeInfoWarning An inliner warning if some information was not available while + * gathering the information about this callee. */ final case class Callee(callee: MethodNode, calleeDeclarationClass: ClassBType, safeToInline: Boolean, - annotatedInline: Boolean, annotatedNoInline: Boolean) + annotatedInline: Boolean, annotatedNoInline: Boolean, + calleeInfoWarning: Option[CalleeInfoWarning]) } 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 4812f2290f..e7dd5abc57 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/opt/InlineInfoAttribute.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/InlineInfoAttribute.scala @@ -9,6 +9,7 @@ package opt import scala.tools.asm._ import scala.tools.nsc.backend.jvm.BTypes.{InlineInfo, MethodInlineInfo} +import scala.tools.nsc.backend.jvm.BackendReporting.UnknownScalaInlineInfoVersion /** * This attribute stores the InlineInfo for a ClassBType as an independent classfile attribute. @@ -119,7 +120,7 @@ case class InlineInfoAttribute(inlineInfo: InlineInfo) extends Attribute(InlineI InlineInfoAttribute(InlineInfo(self, isFinal, infos, None)) } else { - val msg = s"Cannot read ScalaInlineInfo version $version in classfile ${cr.getClassName}. Use a more recent compiler." + val msg = UnknownScalaInlineInfoVersion(cr.getClassName, version) InlineInfoAttribute(BTypes.EmptyInlineInfo.copy(warning = Some(msg))) } } diff --git a/src/compiler/scala/tools/nsc/backend/jvm/opt/Inliner.scala b/src/compiler/scala/tools/nsc/backend/jvm/opt/Inliner.scala index b2459862ea..7ce98ecff1 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/opt/Inliner.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/Inliner.scala @@ -15,9 +15,10 @@ import scala.collection.convert.decorateAsScala._ import scala.collection.convert.decorateAsJava._ import AsmUtils._ import BytecodeUtils._ -import OptimizerReporting._ import collection.mutable import scala.tools.asm.tree.analysis.{SourceInterpreter, Analyzer} +import BackendReporting._ +import scala.tools.nsc.backend.jvm.BTypes.InternalName class Inliner[BT <: BTypes](val btypes: BT) { import btypes._ @@ -27,10 +28,19 @@ class Inliner[BT <: BTypes](val btypes: BT) { rewriteFinalTraitMethodInvocations() for (request <- collectAndOrderInlineRequests) { - val Some(callee) = request.callee - inline(request.callsiteInstruction, request.callsiteStackHeight, request.callsiteMethod, request.callsiteClass, + val Right(callee) = request.callee // collectAndOrderInlineRequests returns callsites with a known callee + + val r = inline(request.callsiteInstruction, request.callsiteStackHeight, request.callsiteMethod, request.callsiteClass, callee.callee, callee.calleeDeclarationClass, receiverKnownNotNull = false, keepLineNumbers = false) + + for (warning <- r) { + if ((callee.annotatedInline && btypes.warnSettings.atInlineFailed) || warning.emitWarning(warnSettings)) { + val annotWarn = if (callee.annotatedInline) " is annotated @inline but" else "" + val msg = s"${BackendReporting.methodSignature(callee.calleeDeclarationClass.internalName, callee.callee)}$annotWarn could not be inlined:\n$warning" + backendReporting.inlinerWarning(request.callsitePosition, msg) + } + } } } @@ -61,18 +71,51 @@ class Inliner[BT <: BTypes](val btypes: BT) { */ def selectCallsitesForInlining: List[Callsite] = { callsites.valuesIterator.filter({ - case Callsite(_, _, _, Some(Callee(callee, _, safeToInline, annotatedInline, _)), _, _) => - // For trait methods the callee is abstract: "trait T { @inline final def f = 1}". - // A callsite (t: T).f is `safeToInline` (effectivelyFinal is true), but the callee is the - // abstract method in the interface. - // Even though we such invocations are re-written using `rewriteFinalTraitMethodInvocation`, - // the guard is kept here for the cases where the rewrite fails. - !isAbstractMethod(callee) && safeToInline && annotatedInline - - case _ => false + case callsite @ Callsite(_, _, _, Right(Callee(callee, calleeDeclClass, safeToInline, annotatedInline, _, warning)), _, _, pos) => + val res = doInlineCallsite(callsite) + + if (!res) { + if (annotatedInline && btypes.warnSettings.atInlineFailed) { + // if the callsite is annotated @inline, we report an inline warning even if the underlying + // reason is, for example, mixed compilation (which has a separate -Yopt-warning flag). + def initMsg = s"${BackendReporting.methodSignature(calleeDeclClass.internalName, callee)} is annotated @inline but cannot be inlined" + def warnMsg = warning.map(" Possible reason:\n" + _).getOrElse("") + if (!safeToInline) + backendReporting.inlinerWarning(pos, s"$initMsg: the method is not final and may be overridden." + warnMsg) + else if (doRewriteTraitCallsite(callsite) && isAbstractMethod(callee)) + backendReporting.inlinerWarning(pos, s"$initMsg: the trait method call could not be rewritten to the static implementation method." + warnMsg) + else + backendReporting.inlinerWarning(pos, s"$initMsg." + warnMsg) + } else if (warning.isDefined && warning.get.emitWarning(warnSettings)) { + // when annotatedInline is false, and there is some warning, the callsite metadata is possibly incomplete. + backendReporting.inlinerWarning(pos, s"there was a problem determining if method ${callee.name} can be inlined: \n"+ warning.get) + } + } + + res + + case Callsite(ins, _, _, Left(warning), _, _, pos) => + if (warning.emitWarning(warnSettings)) + backendReporting.inlinerWarning(pos, s"failed to determine if ${ins.name} should be inlined:\n$warning") + false }).toList } + /** + * The current inlining heuristics are simple: inline calls to methods annotated @inline. + */ + def doInlineCallsite(callsite: Callsite): Boolean = callsite match { + case Callsite(_, _, _, Right(Callee(callee, calleeDeclClass, safeToInline, annotatedInline, _, warning)), _, _, pos) => + // Usually, safeToInline implies that the callee is not abstract. + // But for final trait methods, the callee is abstract: "trait T { @inline final def f = 1}". + // A callsite (t: T).f is `safeToInline`, but the callee is the abstract method in the interface. + // We try to rewrite these calls to the static impl method, but that may not always succeed, + // in which case we cannot inline the call. + annotatedInline && safeToInline && !isAbstractMethod(callee) + + case _ => false + } + def rewriteFinalTraitMethodInvocations(): Unit = { // Rewriting final trait method callsites to the implementation class enables inlining. // We cannot just iterate over the values of the `callsites` map because the rewrite changes the @@ -81,33 +124,51 @@ class Inliner[BT <: BTypes](val btypes: BT) { } /** + * True for statically resolved trait callsites that should be rewritten to the static implementation method. + */ + def doRewriteTraitCallsite(callsite: Callsite) = callsite.callee match { + case Right(Callee(callee, calleeDeclarationClass, true, annotatedInline, annotatedNoInline, infoWarning)) if isAbstractMethod(callee) => + // The pattern matches abstract methods that are `safeToInline`. This can only match the interface method of a final, concrete + // trait method. An abstract method (in a trait or abstract class) is never `safeToInline` (abstract methods cannot be final). + // See also comment in `doInlineCallsite` + for (i <- calleeDeclarationClass.isInterface) assert(i, s"expected interface call (final trait method) when inlining abstract method: $callsite") + true + + case _ => false + } + + /** * Rewrite the INVOKEINTERFACE callsite of a final trait method invocation to INVOKESTATIC of the * corresponding method in the implementation class. This enables inlining final trait methods. * * In a final trait method callsite, the callee is safeToInline and the callee method is abstract * (the receiver type is the interface, so the method is abstract). */ - def rewriteFinalTraitMethodInvocation(callsite: Callsite): Unit = callsite.callee match { - case Some(Callee(callee, calleeDeclarationClass, true, true, annotatedNoInline)) if isAbstractMethod(callee) => - assert(calleeDeclarationClass.isInterface, s"expected interface call (final trait method) when inlining abstract method: $callsite") + def rewriteFinalTraitMethodInvocation(callsite: Callsite): Unit = { + if (doRewriteTraitCallsite(callsite)) { + val Right(Callee(callee, calleeDeclarationClass, safeToInline, annotatedInline, annotatedNoInline, infoWarning)) = callsite.callee val traitMethodArgumentTypes = asm.Type.getArgumentTypes(callee.desc) - val selfParamType = calleeDeclarationClass.info.inlineInfo.traitImplClassSelfType match { + val implClassInternalName = calleeDeclarationClass.internalName + "$class" + + val selfParamTypeV: Either[OptimizerWarning, ClassBType] = calleeDeclarationClass.info.map(_.inlineInfo.traitImplClassSelfType match { case Some(internalName) => classBTypeFromParsedClassfile(internalName) - case None => Some(calleeDeclarationClass) - } + case None => calleeDeclarationClass + }) - val implClassInternalName = calleeDeclarationClass.internalName + "$class" + def implClassMethodV(implMethodDescriptor: String): Either[OptimizerWarning, MethodNode] = { + byteCodeRepository.methodNode(implClassInternalName, callee.name, implMethodDescriptor).map(_._1) + } // The rewrite reading the implementation class and the implementation method from the bytecode // repository. If either of the two fails, the rewrite is not performed. - for { - // TODO: inline warnings if selfClassType, impl class or impl method cannot be found - selfType <- selfParamType - implClassMethodDescriptor = asm.Type.getMethodDescriptor(asm.Type.getReturnType(callee.desc), selfType.toASMType +: traitMethodArgumentTypes: _*) - (implClassMethod, _) <- byteCodeRepository.methodNode(implClassInternalName, callee.name, implClassMethodDescriptor) - implClassBType <- classBTypeFromParsedClassfile(implClassInternalName) + val res = for { + selfParamType <- selfParamTypeV + implMethodDescriptor = asm.Type.getMethodDescriptor(asm.Type.getReturnType(callee.desc), selfParamType.toASMType +: traitMethodArgumentTypes: _*) + implClassMethod <- implClassMethodV(implMethodDescriptor) + implClassBType = classBTypeFromParsedClassfile(implClassInternalName) + selfTypeOk <- calleeDeclarationClass.isSubtypeOf(selfParamType) } yield { // The self parameter type may be incompatible with the trait type. @@ -116,16 +177,16 @@ class Inliner[BT <: BTypes](val btypes: BT) { // a call to T.foo to T$class.foo, we need to cast the receiver to S, otherwise we get a // VerifyError. We run a `SourceInterpreter` to find all producer instructions of the // receiver value and add a cast to the self type after each. - if (!calleeDeclarationClass.isSubtypeOf(selfType)) { + if (!selfTypeOk) { val analyzer = new AsmAnalyzer(callsite.callsiteMethod, callsite.callsiteClass.internalName, new SourceInterpreter) val receiverValue = analyzer.frameAt(callsite.callsiteInstruction).peekDown(traitMethodArgumentTypes.length) for (i <- receiverValue.insns.asScala) { - val cast = new TypeInsnNode(CHECKCAST, selfType.internalName) + val cast = new TypeInsnNode(CHECKCAST, selfParamType.internalName) callsite.callsiteMethod.instructions.insert(i, cast) } } - val newCallsiteInstruction = new MethodInsnNode(INVOKESTATIC, implClassInternalName, callee.name, implClassMethodDescriptor, false) + val newCallsiteInstruction = new MethodInsnNode(INVOKESTATIC, implClassInternalName, callee.name, implMethodDescriptor, false) callsite.callsiteMethod.instructions.insert(callsite.callsiteInstruction, newCallsiteInstruction) callsite.callsiteMethod.instructions.remove(callsite.callsiteInstruction) @@ -134,19 +195,26 @@ class Inliner[BT <: BTypes](val btypes: BT) { callsiteInstruction = newCallsiteInstruction, callsiteMethod = callsite.callsiteMethod, callsiteClass = callsite.callsiteClass, - callee = Some(Callee( + callee = Right(Callee( callee = implClassMethod, calleeDeclarationClass = implClassBType, - safeToInline = true, - annotatedInline = true, - annotatedNoInline = annotatedNoInline)), + safeToInline = safeToInline, + annotatedInline = annotatedInline, + annotatedNoInline = annotatedNoInline, + calleeInfoWarning = infoWarning)), argInfos = Nil, - callsiteStackHeight = callsite.callsiteStackHeight + callsiteStackHeight = callsite.callsiteStackHeight, + callsitePosition = callsite.callsitePosition ) callGraph.callsites(newCallsiteInstruction) = staticCallsite } - case _ => + for (warning <- res.left) { + val Right(callee) = callsite.callee + val newCallee = callee.copy(calleeInfoWarning = Some(RewriteTraitCallToStaticImplMethodFailed(calleeDeclarationClass.internalName, callee.callee.name, callee.callee.desc, warning))) + callGraph.callsites(callsite.callsiteInstruction) = callsite.copy(callee = Right(newCallee)) + } + } } /** @@ -240,7 +308,7 @@ class Inliner[BT <: BTypes](val btypes: BT) { */ def inline(callsiteInstruction: MethodInsnNode, callsiteStackHeight: Int, callsiteMethod: MethodNode, callsiteClass: ClassBType, callee: MethodNode, calleeDeclarationClass: ClassBType, - receiverKnownNotNull: Boolean, keepLineNumbers: Boolean): Option[String] = { + receiverKnownNotNull: Boolean, keepLineNumbers: Boolean): Option[CannotInlineWarning] = { canInline(callsiteInstruction, callsiteStackHeight, callsiteMethod, callsiteClass, callee, calleeDeclarationClass) orElse { // New labels for the cloned instructions val labelsMap = cloneLabels(callee) @@ -363,7 +431,8 @@ class Inliner[BT <: BTypes](val btypes: BT) { callsiteClass = callsiteClass, callee = originalCallsite.callee, argInfos = Nil, // TODO: re-compute argInfos for new destination (once we actually compute them) - callsiteStackHeight = callsiteStackHeight + originalCallsite.callsiteStackHeight + callsiteStackHeight = callsiteStackHeight + originalCallsite.callsiteStackHeight, + callsitePosition = originalCallsite.callsitePosition ) case None => @@ -386,7 +455,7 @@ class Inliner[BT <: BTypes](val btypes: BT) { * @return `Some(message)` if inlining cannot be performed, `None` otherwise */ def canInline(callsiteInstruction: MethodInsnNode, callsiteStackHeight: Int, callsiteMethod: MethodNode, callsiteClass: ClassBType, - callee: MethodNode, calleeDeclarationClass: ClassBType): Option[String] = { + callee: MethodNode, calleeDeclarationClass: ClassBType): Option[CannotInlineWarning] = { def calleeDesc = s"${callee.name} of type ${callee.desc} in ${calleeDeclarationClass.internalName}" def methodMismatch = s"Wrong method node for inlining ${textify(callsiteInstruction)}: $calleeDesc" @@ -416,37 +485,43 @@ class Inliner[BT <: BTypes](val btypes: BT) { if (isSynchronizedMethod(callee)) { // Could be done by locking on the receiver, wrapping the inlined code in a try and unlocking // in finally. But it's probably not worth the effort, scala never emits synchronized methods. - Some(s"Method ${methodSignature(calleeDeclarationClass.internalName, callee)} is not inlined because it is synchronized") + Some(SynchronizedMethod(calleeDeclarationClass.internalName, callee.name, callee.desc)) } else if (!callee.tryCatchBlocks.isEmpty && stackHasNonParameters) { - Some( - s"""The operand stack at the callsite in ${methodSignature(callsiteClass.internalName, callsiteMethod)} contains more values than the - |arguments expected by the callee ${methodSignature(calleeDeclarationClass.internalName, callee)}. These values would be discarded - |when entering an exception handler declared in the inlined method.""".stripMargin - ) + Some(MethodWithHandlerCalledOnNonEmptyStack( + calleeDeclarationClass.internalName, callee.name, callee.desc, + callsiteClass.internalName, callsiteMethod.name, callsiteMethod.desc)) } else findIllegalAccess(callee.instructions, callsiteClass) map { - case illegalAccessIns => - s"""The callee ${methodSignature(calleeDeclarationClass.internalName, callee)} contains the instruction ${AsmUtils.textify(illegalAccessIns)} - |that would cause an IllegalAccessError when inlined into class ${callsiteClass.internalName}""".stripMargin + case (illegalAccessIns, None) => + IllegalAccessInstruction( + calleeDeclarationClass.internalName, callee.name, callee.desc, + callsiteClass.internalName, illegalAccessIns) + + case (illegalAccessIns, Some(warning)) => + IllegalAccessCheckFailed( + calleeDeclarationClass.internalName, callee.name, callee.desc, + callsiteClass.internalName, illegalAccessIns, warning) } } /** * Returns the first instruction in the `instructions` list that would cause a - * [[java.lang.IllegalAccessError]] when inlined into the `destinationClass`. Returns `None` if - * all instructions can be legally transplanted. + * [[java.lang.IllegalAccessError]] when inlined into the `destinationClass`. + * + * If validity of some instruction could not be checked because an error occurred, the instruction + * is returned together with a warning message that describes the problem. */ - def findIllegalAccess(instructions: InsnList, destinationClass: ClassBType): Option[AbstractInsnNode] = { + def findIllegalAccess(instructions: InsnList, destinationClass: ClassBType): Option[(AbstractInsnNode, Option[OptimizerWarning])] = { /** * Check if a type is accessible to some class, as defined in JVMS 5.4.4. * (A1) C is public * (A2) C and D are members of the same run-time package */ - def classIsAccessible(accessed: BType, from: ClassBType = destinationClass): Boolean = (accessed: @unchecked) match { + def classIsAccessible(accessed: BType, from: ClassBType = destinationClass): Either[OptimizerWarning, Boolean] = (accessed: @unchecked) match { // TODO: A2 requires "same run-time package", which seems to be package + classloader (JMVS 5.3.). is the below ok? - case c: ClassBType => c.isPublic || c.packageInternalName == from.packageInternalName + case c: ClassBType => c.isPublic.map(_ || c.packageInternalName == from.packageInternalName) case a: ArrayBType => classIsAccessible(a.elementType, from) - case _: PrimitiveBType => true + case _: PrimitiveBType => Right(true) } /** @@ -471,27 +546,29 @@ class Inliner[BT <: BTypes](val btypes: BT) { * run-time package as D. * (B4) R is private and is declared in D. */ - def memberIsAccessible(memberFlags: Int, memberDeclClass: ClassBType, memberRefClass: ClassBType): Boolean = { + def memberIsAccessible(memberFlags: Int, memberDeclClass: ClassBType, memberRefClass: ClassBType): Either[OptimizerWarning, Boolean] = { // TODO: B3 requires "same run-time package", which seems to be package + classloader (JMVS 5.3.). is the below ok? def samePackageAsDestination = memberDeclClass.packageInternalName == destinationClass.packageInternalName val key = (ACC_PUBLIC | ACC_PROTECTED | ACC_PRIVATE) & memberFlags key match { case ACC_PUBLIC => // B1 - true + Right(true) case ACC_PROTECTED => // B2 - val condB2 = destinationClass.isSubtypeOf(memberDeclClass) && { - val isStatic = (ACC_STATIC & memberFlags) != 0 - isStatic || memberRefClass.isSubtypeOf(destinationClass) || destinationClass.isSubtypeOf(memberRefClass) + tryEither { + val condB2 = destinationClass.isSubtypeOf(memberDeclClass).orThrow && { + val isStatic = (ACC_STATIC & memberFlags) != 0 + isStatic || memberRefClass.isSubtypeOf(destinationClass).orThrow || destinationClass.isSubtypeOf(memberRefClass).orThrow + } + Right(condB2 || samePackageAsDestination) // B3 (protected) } - condB2 || samePackageAsDestination // B3 (protected) - case 0 => // B3 (default access) - samePackageAsDestination + case 0 => // B3 (default access) + Right(samePackageAsDestination) case ACC_PRIVATE => // B4 - memberDeclClass == destinationClass + Right(memberDeclClass == destinationClass) } } @@ -502,49 +579,68 @@ class Inliner[BT <: BTypes](val btypes: BT) { * byteCodeRepository, it is considered as not legal. This is known to happen in mixed * compilation: for Java classes there is no classfile that could be parsed, nor does the * compiler generate any bytecode. + * + * Returns a warning message describing the problem if checking the legality for the instruction + * failed. */ - def isLegal(instruction: AbstractInsnNode): Boolean = instruction match { + def isLegal(instruction: AbstractInsnNode): Either[OptimizerWarning, Boolean] = instruction match { case ti: TypeInsnNode => // NEW, ANEWARRAY, CHECKCAST or INSTANCEOF. For these instructions, the reference // "must be a symbolic reference to a class, array, or interface type" (JVMS 6), so // it can be an internal name, or a full array descriptor. - bTypeForDescriptorOrInternalNameFromClassfile(ti.desc).exists(classIsAccessible(_)) + classIsAccessible(bTypeForDescriptorOrInternalNameFromClassfile(ti.desc)) case ma: MultiANewArrayInsnNode => // "a symbolic reference to a class, array, or interface type" - bTypeForDescriptorOrInternalNameFromClassfile(ma.desc).exists(classIsAccessible(_)) + classIsAccessible(bTypeForDescriptorOrInternalNameFromClassfile(ma.desc)) case fi: FieldInsnNode => - (for { - fieldRefClass <- classBTypeFromParsedClassfile(fi.owner) - (fieldNode, fieldDeclClassNode) <- byteCodeRepository.fieldNode(fieldRefClass.internalName, fi.name, fi.desc) - fieldDeclClass <- classBTypeFromParsedClassfile(fieldDeclClassNode) + val fieldRefClass = classBTypeFromParsedClassfile(fi.owner) + for { + (fieldNode, fieldDeclClassNode) <- byteCodeRepository.fieldNode(fieldRefClass.internalName, fi.name, fi.desc): Either[OptimizerWarning, (FieldNode, InternalName)] + fieldDeclClass = classBTypeFromParsedClassfile(fieldDeclClassNode) + res <- memberIsAccessible(fieldNode.access, fieldDeclClass, fieldRefClass) } yield { - memberIsAccessible(fieldNode.access, fieldDeclClass, fieldRefClass) - }) getOrElse false + res + } case mi: MethodInsnNode => - if (mi.owner.charAt(0) == '[') true // array methods are accessible - else (for { - methodRefClass <- classBTypeFromParsedClassfile(mi.owner) - (methodNode, methodDeclClassNode) <- byteCodeRepository.methodNode(methodRefClass.internalName, mi.name, mi.desc) - methodDeclClass <- classBTypeFromParsedClassfile(methodDeclClassNode) - } yield { - memberIsAccessible(methodNode.access, methodDeclClass, methodRefClass) - }) getOrElse false + if (mi.owner.charAt(0) == '[') Right(true) // array methods are accessible + else { + val methodRefClass = classBTypeFromParsedClassfile(mi.owner) + for { + (methodNode, methodDeclClassNode) <- byteCodeRepository.methodNode(methodRefClass.internalName, mi.name, mi.desc): Either[OptimizerWarning, (MethodNode, InternalName)] + methodDeclClass = classBTypeFromParsedClassfile(methodDeclClassNode) + res <- memberIsAccessible(methodNode.access, methodDeclClass, methodRefClass) + } yield { + res + } + } case ivd: InvokeDynamicInsnNode => // TODO @lry check necessary conditions to inline an indy, instead of giving up - false + Right(false) case ci: LdcInsnNode => ci.cst match { - case t: asm.Type => bTypeForDescriptorOrInternalNameFromClassfile(t.getInternalName).exists(classIsAccessible(_)) - case _ => true + case t: asm.Type => classIsAccessible(bTypeForDescriptorOrInternalNameFromClassfile(t.getInternalName)) + case _ => Right(true) } - case _ => true + case _ => Right(true) } - instructions.iterator.asScala.find(!isLegal(_)) + val it = instructions.iterator.asScala + @tailrec def find: Option[(AbstractInsnNode, Option[OptimizerWarning])] = { + if (!it.hasNext) None // all instructions are legal + else { + val i = it.next() + isLegal(i) match { + case Left(warning) => Some((i, Some(warning))) // checking isLegal for i failed + case Right(false) => Some((i, None)) // an illegal instruction was found + case _ => find + } + } + } + find } } diff --git a/src/compiler/scala/tools/nsc/backend/jvm/opt/OptimizerReporting.scala b/src/compiler/scala/tools/nsc/backend/jvm/opt/OptimizerReporting.scala deleted file mode 100644 index 5b47bc88c2..0000000000 --- a/src/compiler/scala/tools/nsc/backend/jvm/opt/OptimizerReporting.scala +++ /dev/null @@ -1,28 +0,0 @@ -/* NSC -- new Scala compiler - * Copyright 2005-2014 LAMP/EPFL - * @author Martin Odersky - */ - -package scala.tools.nsc -package backend.jvm -package opt - -import scala.tools.asm -import asm.tree._ -import scala.tools.nsc.backend.jvm.BTypes.InternalName - -/** - * Reporting utilities used in the optimizer. - * - * TODO: move out of opt package, rename: it's already used outside the optimizer. - * Centralize backend reporting here. - */ -object OptimizerReporting { - def methodSignature(classInternalName: InternalName, method: MethodNode): String = { - classInternalName + "::" + method.name + method.desc - } - - // TODO: clean up reporting of the inliner, test inline failure warnings, etc - def inlineFailure(reason: String): Nothing = MissingRequirementError.signal(reason) - def assertionError(message: String): Nothing = throw new AssertionError(message) -} diff --git a/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala b/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala index 43b634eee1..d273995e6e 100644 --- a/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala +++ b/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala @@ -256,6 +256,25 @@ trait ScalaSettings extends AbsScalaSettings def YoptInlineGlobal = Yopt.contains(YoptChoices.inlineGlobal) def YoptInlinerEnabled = YoptInlineProject || YoptInlineGlobal + object YoptWarningsChoices extends MultiChoiceEnumeration { + val none = Choice("none" , "No optimizer warnings.") + val atInlineFailedSummary = Choice("at-inline-failed-summary" , "One-line summary if there were @inline method calls that could not be inlined.") + val atInlineFailed = Choice("at-inline-failed" , "A detailed warning for each @inline method call that could not be inlined.") + val noInlineMixed = Choice("no-inline-mixed" , "In mixed compilation, warn at callsites methods defined in java sources (the inlining decision cannot be made without bytecode).") + val noInlineMissingBytecode = Choice("no-inline-missing-bytecode" , "Warn if an inlining decision cannot be made because a the bytecode of a class or member cannot be found on the compilation classpath.") + val noInlineMissingScalaInlineInfoAttr = Choice("no-inline-missing-attribute", "Warn if an inlining decision cannot be made because a Scala classfile does not have a ScalaInlineInfo attribute.") + } + + val YoptWarnings = MultiChoiceSetting( + name = "-Yopt-warnings", + helpArg = "warnings", + descr = "Enable optimizer warnings", + domain = YoptWarningsChoices, + default = Some(List(YoptWarningsChoices.atInlineFailed.name))) withPostSetHook (self => { + if (self.value subsetOf Set(YoptWarningsChoices.none, YoptWarningsChoices.atInlineFailedSummary)) YinlinerWarnings.value = false + else YinlinerWarnings.value = true + }) + private def removalIn212 = "This flag is scheduled for removal in 2.12. If you have a case where you need this flag then please report a bug." object YstatisticsPhases extends MultiChoiceEnumeration { val parser, typer, patmat, erasure, cleanup, jvm = Value } diff --git a/test/files/run/colltest1.scala b/test/files/run/colltest1.scala index e0ec378585..de8780a050 100644 --- a/test/files/run/colltest1.scala +++ b/test/files/run/colltest1.scala @@ -1,5 +1,5 @@ /* - * filter: inliner warnings; re-run with -Yinline-warnings for details + * filter: inliner warnings; re-run with */ import scala.collection._ import scala.language.postfixOps diff --git a/test/files/run/compiler-asSeenFrom.scala b/test/files/run/compiler-asSeenFrom.scala index 677dd40ddc..a60c2e8925 100644 --- a/test/files/run/compiler-asSeenFrom.scala +++ b/test/files/run/compiler-asSeenFrom.scala @@ -1,5 +1,5 @@ /* - * filter: inliner warning; re-run with -Yinline-warnings for details + * filter: inliner warning; re-run with */ import scala.tools.nsc._ import scala.tools.partest.DirectTest diff --git a/test/files/run/existentials-in-compiler.scala b/test/files/run/existentials-in-compiler.scala index dfc7048b31..e516eddf95 100644 --- a/test/files/run/existentials-in-compiler.scala +++ b/test/files/run/existentials-in-compiler.scala @@ -1,5 +1,5 @@ /* - * filter: inliner warnings; re-run with -Yinline-warnings for details + * filter: inliner warnings; re-run with */ import scala.tools.nsc._ import scala.tools.partest.CompilerTest diff --git a/test/files/run/is-valid-num.scala b/test/files/run/is-valid-num.scala index 4ab2fac8dd..156121cab5 100644 --- a/test/files/run/is-valid-num.scala +++ b/test/files/run/is-valid-num.scala @@ -1,5 +1,5 @@ /* - * filter: inliner warnings; re-run with -Yinline-warnings for details + * filter: inliner warnings; re-run with */ object Test { def x = BigInt("10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000") diff --git a/test/files/run/iterator-from.scala b/test/files/run/iterator-from.scala index e2ca5864ea..e7ba1aeb28 100644 --- a/test/files/run/iterator-from.scala +++ b/test/files/run/iterator-from.scala @@ -1,5 +1,5 @@ /* This file tests iteratorFrom, keysIteratorFrom, and valueIteratorFrom on various sorted sets and maps - * filter: inliner warnings; re-run with -Yinline-warnings for details + * filter: inliner warnings; re-run with */ import scala.util.{Random => R} diff --git a/test/files/run/mapConserve.scala b/test/files/run/mapConserve.scala index f52af3b9f4..c17754283a 100644 --- a/test/files/run/mapConserve.scala +++ b/test/files/run/mapConserve.scala @@ -1,5 +1,5 @@ /* - * filter: inliner warnings; re-run with -Yinline-warnings for details + * filter: inliner warnings; re-run with */ import scala.annotation.tailrec import scala.collection.mutable.ListBuffer diff --git a/test/files/run/pc-conversions.scala b/test/files/run/pc-conversions.scala index 5fecac9d94..d4ae305aa7 100644 --- a/test/files/run/pc-conversions.scala +++ b/test/files/run/pc-conversions.scala @@ -1,5 +1,5 @@ /* - * filter: inliner warning; re-run with -Yinline-warnings for details + * filter: inliner warning; re-run with */ import collection._ diff --git a/test/files/run/stringinterpolation_macro-run.scala b/test/files/run/stringinterpolation_macro-run.scala index e18375d521..ae7c0e5d7a 100644 --- a/test/files/run/stringinterpolation_macro-run.scala +++ b/test/files/run/stringinterpolation_macro-run.scala @@ -1,5 +1,5 @@ /* - * filter: inliner warnings; re-run with -Yinline-warnings for details + * filter: inliner warnings; re-run with */ object Test extends App { diff --git a/test/files/run/synchronized.check b/test/files/run/synchronized.check index eab191b4ed..9add05ea0c 100644 --- a/test/files/run/synchronized.check +++ b/test/files/run/synchronized.check @@ -1,4 +1,8 @@ +#partest !-Ybackend:GenBCode warning: there were 14 inliner warnings; re-run with -Yinline-warnings for details +#partest -Ybackend:GenBCode +warning: there were 14 inliner warnings; re-run with -Yopt-warnings for details +#partest .|. c1.f1: OK .|. c1.fi: OK .|... c1.fv: OK diff --git a/test/files/run/t7096.scala b/test/files/run/t7096.scala index 872562dd4d..f723d70abe 100644 --- a/test/files/run/t7096.scala +++ b/test/files/run/t7096.scala @@ -1,5 +1,5 @@ /* - * filter: inliner warning; re-run with -Yinline-warnings for details + * filter: inliner warning; re-run with */ import scala.tools.partest._ import scala.tools.nsc._ diff --git a/test/files/run/t7582.check b/test/files/run/t7582.check index cd951d8d4f..2a11210000 100644 --- a/test/files/run/t7582.check +++ b/test/files/run/t7582.check @@ -1,2 +1,6 @@ +#partest !-Ybackend:GenBCode warning: there was one inliner warning; re-run with -Yinline-warnings for details +#partest -Ybackend:GenBCode +warning: there was one inliner warning; re-run with -Yopt-warnings for details +#partest 2 diff --git a/test/files/run/t7582b.check b/test/files/run/t7582b.check index cd951d8d4f..2a11210000 100644 --- a/test/files/run/t7582b.check +++ b/test/files/run/t7582b.check @@ -1,2 +1,6 @@ +#partest !-Ybackend:GenBCode warning: there was one inliner warning; re-run with -Yinline-warnings for details +#partest -Ybackend:GenBCode +warning: there was one inliner warning; re-run with -Yopt-warnings for details +#partest 2 diff --git a/test/junit/scala/tools/nsc/backend/jvm/CodeGenTools.scala b/test/junit/scala/tools/nsc/backend/jvm/CodeGenTools.scala index c64f6e7f10..5d5215d887 100644 --- a/test/junit/scala/tools/nsc/backend/jvm/CodeGenTools.scala +++ b/test/junit/scala/tools/nsc/backend/jvm/CodeGenTools.scala @@ -6,11 +6,12 @@ import scala.collection.mutable.ListBuffer import scala.reflect.internal.util.BatchSourceFile import scala.reflect.io.VirtualDirectory import scala.tools.asm.Opcodes -import scala.tools.asm.tree.{AbstractInsnNode, LabelNode, ClassNode, MethodNode} +import scala.tools.asm.tree.{ClassNode, MethodNode} import scala.tools.cmd.CommandLineParser import scala.tools.nsc.backend.jvm.opt.LocalOpt import scala.tools.nsc.io.AbstractFile -import scala.tools.nsc.settings.{MutableSettings, ScalaSettings} +import scala.tools.nsc.reporters.StoreReporter +import scala.tools.nsc.settings.MutableSettings import scala.tools.nsc.{Settings, Global} import scala.tools.partest.ASMConverters import scala.collection.JavaConverters._ @@ -52,7 +53,7 @@ object CodeGenTools { val settings = new Settings() val args = (CommandLineParser tokenize defaultArgs) ++ (CommandLineParser tokenize extraArgs) settings.processArguments(args, processAll = true) - new Global(settings) + new Global(settings, new StoreReporter) } def newRun(compiler: Global): compiler.Run = { @@ -61,6 +62,8 @@ object CodeGenTools { new compiler.Run() } + def reporter(compiler: Global) = compiler.reporter.asInstanceOf[StoreReporter] + def makeSourceFile(code: String, filename: String): BatchSourceFile = new BatchSourceFile(filename, code) def getGeneratedClassfiles(outDir: AbstractFile): List[(String, Array[Byte])] = { @@ -75,9 +78,18 @@ object CodeGenTools { files(outDir) } - def compile(compiler: Global)(scalaCode: String, javaCode: List[(String, String)] = Nil): List[(String, Array[Byte])] = { + def checkReport(compiler: Global, allowMessage: StoreReporter#Info => Boolean = _ => false): Unit = { + val disallowed = reporter(compiler).infos.toList.filter(!allowMessage(_)) // toList prevents an infer-non-wildcard-existential warning. + if (disallowed.nonEmpty) { + val msg = disallowed.mkString("\n") + assert(false, "The compiler issued non-allowed warnings or errors:\n" + msg) + } + } + + def compile(compiler: Global)(scalaCode: String, javaCode: List[(String, String)] = Nil, allowMessage: StoreReporter#Info => Boolean = _ => false): List[(String, Array[Byte])] = { val run = newRun(compiler) run.compileSources(makeSourceFile(scalaCode, "unitTestSource.scala") :: javaCode.map(p => makeSourceFile(p._1, p._2))) + checkReport(compiler, allowMessage) getGeneratedClassfiles(compiler.settings.outputDirs.getSingleOutput.get) } @@ -90,7 +102,7 @@ object CodeGenTools { * The output directory is a physical directory, I have not figured out if / how it's possible to * add a VirtualDirectory to the classpath of a compiler. */ - def compileSeparately(codes: List[String], extraArgs: String = ""): List[(String, Array[Byte])] = { + def compileSeparately(codes: List[String], extraArgs: String = "", allowMessage: StoreReporter#Info => Boolean = _ => false, afterEach: AbstractFile => Unit = _ => ()): List[(String, Array[Byte])] = { val outDir = AbstractFile.getDirectory(TempDir.createTempDir()) val outDirPath = outDir.canonicalPath val argsWithOutDir = extraArgs + s" -d $outDirPath -cp $outDirPath" @@ -98,6 +110,8 @@ object CodeGenTools { for (code <- codes) { val compiler = newCompilerWithoutVirtualOutdir(extraArgs = argsWithOutDir) new compiler.Run().compileSources(List(makeSourceFile(code, "unitTestSource.scala"))) + checkReport(compiler, allowMessage) + afterEach(outDir) } val classfiles = getGeneratedClassfiles(outDir) @@ -105,29 +119,29 @@ object CodeGenTools { classfiles } - def compileClassesSeparately(codes: List[String], extraArgs: String = "") = { - readAsmClasses(compileSeparately(codes, extraArgs)) + def compileClassesSeparately(codes: List[String], extraArgs: String = "", allowMessage: StoreReporter#Info => Boolean = _ => false, afterEach: AbstractFile => Unit = _ => ()) = { + readAsmClasses(compileSeparately(codes, extraArgs, allowMessage, afterEach)) } def readAsmClasses(classfiles: List[(String, Array[Byte])]) = { classfiles.map(p => AsmUtils.readClass(p._2)).sortBy(_.name) } - def compileClasses(compiler: Global)(code: String, javaCode: List[(String, String)] = Nil): List[ClassNode] = { - readAsmClasses(compile(compiler)(code, javaCode)) + def compileClasses(compiler: Global)(code: String, javaCode: List[(String, String)] = Nil, allowMessage: StoreReporter#Info => Boolean = _ => false): List[ClassNode] = { + readAsmClasses(compile(compiler)(code, javaCode, allowMessage)) } - def compileMethods(compiler: Global)(code: String): List[MethodNode] = { - compileClasses(compiler)(s"class C { $code }").head.methods.asScala.toList.filterNot(_.name == "<init>") + def compileMethods(compiler: Global)(code: String, allowMessage: StoreReporter#Info => Boolean = _ => false): List[MethodNode] = { + compileClasses(compiler)(s"class C { $code }", allowMessage = allowMessage).head.methods.asScala.toList.filterNot(_.name == "<init>") } - def singleMethodInstructions(compiler: Global)(code: String): List[Instruction] = { - val List(m) = compileMethods(compiler)(code) + def singleMethodInstructions(compiler: Global)(code: String, allowMessage: StoreReporter#Info => Boolean = _ => false): List[Instruction] = { + val List(m) = compileMethods(compiler)(code, allowMessage = allowMessage) instructionsFromMethod(m) } - def singleMethod(compiler: Global)(code: String): Method = { - val List(m) = compileMethods(compiler)(code) + def singleMethod(compiler: Global)(code: String, allowMessage: StoreReporter#Info => Boolean = _ => false): Method = { + val List(m) = compileMethods(compiler)(code, allowMessage = allowMessage) convertMethod(m) } diff --git a/test/junit/scala/tools/nsc/backend/jvm/DirectCompileTest.scala b/test/junit/scala/tools/nsc/backend/jvm/DirectCompileTest.scala index 94877fb037..4086f7dd7b 100644 --- a/test/junit/scala/tools/nsc/backend/jvm/DirectCompileTest.scala +++ b/test/junit/scala/tools/nsc/backend/jvm/DirectCompileTest.scala @@ -89,6 +89,10 @@ class DirectCompileTest extends ClearAfterClass { case Invoke(_, "B", "f", _, _) => true case _ => false }, ins) + } + @Test + def compileErroneous(): Unit = { + compileClasses(compiler)("class C { def f: String = 1 }", allowMessage = _.msg contains "type mismatch") } } diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/BTypesFromClassfileTest.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/BTypesFromClassfileTest.scala index 761f214f82..1b6c080234 100644 --- a/test/junit/scala/tools/nsc/backend/jvm/opt/BTypesFromClassfileTest.scala +++ b/test/junit/scala/tools/nsc/backend/jvm/opt/BTypesFromClassfileTest.scala @@ -15,6 +15,8 @@ import CodeGenTools._ import scala.tools.partest.ASMConverters import ASMConverters._ +import BackendReporting._ + import scala.collection.convert.decorateAsScala._ @RunWith(classOf[JUnit4]) @@ -39,7 +41,7 @@ class BTypesFromClassfileTest { if (checked(fromSym.internalName)) checked else { assert(fromSym == fromClassfile, s"$fromSym != $fromClassfile") - sameInfo(fromSym.info, fromClassfile.info, checked + fromSym.internalName) + sameInfo(fromSym.info.get, fromClassfile.info.get, checked + fromSym.internalName) } } @@ -73,7 +75,7 @@ class BTypesFromClassfileTest { // and anonymous classes as members of the outer class. But not for unpickled symbols). // The fromClassfile info has all nested classes, including anonymous and local. So we filter // them out: member classes are identified by having the `outerName` defined. - val memberClassesFromClassfile = fromClassfile.nestedClasses.filter(_.info.nestedInfo.get.outerName.isDefined) + val memberClassesFromClassfile = fromClassfile.nestedClasses.filter(_.info.get.nestedInfo.get.outerName.isDefined) // Sorting is required: the backend sorts all InnerClass entries by internalName before writing // them to the classfile (to make it deterministic: the entries are collected in a Set during // code generation). @@ -85,7 +87,7 @@ class BTypesFromClassfileTest { clearCache() val fromSymbol = classBTypeFromSymbol(classSym) clearCache() - val fromClassfile = bTypes.classBTypeFromParsedClassfile(fromSymbol.internalName).get + val fromClassfile = bTypes.classBTypeFromParsedClassfile(fromSymbol.internalName) sameBType(fromSymbol, fromClassfile) } diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/CallGraphTest.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/CallGraphTest.scala index d7344ae61f..9fda034a04 100644 --- a/test/junit/scala/tools/nsc/backend/jvm/opt/CallGraphTest.scala +++ b/test/junit/scala/tools/nsc/backend/jvm/opt/CallGraphTest.scala @@ -11,27 +11,29 @@ import org.junit.Assert._ import scala.tools.asm.tree._ import scala.tools.asm.tree.analysis._ +import scala.tools.nsc.reporters.StoreReporter import scala.tools.testing.AssertUtil._ import CodeGenTools._ import scala.tools.partest.ASMConverters import ASMConverters._ import AsmUtils._ +import BackendReporting._ import scala.collection.convert.decorateAsScala._ @RunWith(classOf[JUnit4]) class CallGraphTest { - val compiler = newCompiler(extraArgs = "-Ybackend:GenBCode -Yopt:inline-global") + val compiler = newCompiler(extraArgs = "-Ybackend:GenBCode -Yopt:inline-global -Yopt-warnings") import compiler.genBCode.bTypes._ // allows inspecting the caches after a compilation run val notPerRun: List[Clearable] = List(classBTypeFromInternalName, byteCodeRepository.classes, callGraph.callsites) notPerRun foreach compiler.perRunCaches.unrecordCache - def compile(code: String): List[ClassNode] = { + def compile(code: String, allowMessage: StoreReporter#Info => Boolean): List[ClassNode] = { notPerRun.foreach(_.clear()) - compileClasses(compiler)(code) + compileClasses(compiler)(code, allowMessage = allowMessage) } def callsInMethod(methodNode: MethodNode): List[MethodInsnNode] = methodNode.instructions.iterator.asScala.collect({ @@ -69,7 +71,20 @@ class CallGraphTest { // Get the ClassNodes from the code repo (don't use the unparsed ClassNodes returned by compile). // The callGraph.callsites map is indexed by instructions of those ClassNodes. - val List(cCls, cMod, dCls, testCls) = compile(code).map(c => byteCodeRepository.classNode(c.name).get) + + val ok = Set( + "D::f1()I is annotated @inline but cannot be inlined: the method is not final and may be overridden", // only one warning for D.f1: C.f1 is not annotated @inline + "C::f3()I is annotated @inline but cannot be inlined: the method is not final and may be overridden", // only one warning for C.f3: D.f3 does not have @inline (and it would also be safe to inline) + "C::f7()I is annotated @inline but cannot be inlined: the method is not final and may be overridden", // two warnings (the error message mentions C.f7 even if the receiver type is D, because f7 is inherited from C) + "operand stack at the callsite in Test::t1(LC;)I contains more values", + "operand stack at the callsite in Test::t2(LD;)I contains more values") + var msgCount = 0 + val checkMsg = (m: StoreReporter#Info) => { + msgCount += 1 + ok exists (m.msg contains _) + } + val List(cCls, cMod, dCls, testCls) = compile(code, checkMsg).map(c => byteCodeRepository.classNode(c.name).get) + assert(msgCount == 6, msgCount) val List(cf1, cf2, cf3, cf4, cf5, cf6, cf7) = cCls.methods.iterator.asScala.filter(_.name.startsWith("f")).toList.sortBy(_.name) val List(df1, df3) = dCls.methods.iterator.asScala.filter(_.name.startsWith("f")).toList.sortBy(_.name) diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/InlineInfoTest.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/InlineInfoTest.scala index 4e12ed757e..57088bdd2f 100644 --- a/test/junit/scala/tools/nsc/backend/jvm/opt/InlineInfoTest.scala +++ b/test/junit/scala/tools/nsc/backend/jvm/opt/InlineInfoTest.scala @@ -14,6 +14,8 @@ import ASMConverters._ import AsmUtils._ import scala.tools.testing.ClearAfterClass +import BackendReporting._ + import scala.collection.convert.decorateAsScala._ object InlineInfoTest extends ClearAfterClass.Clearable { @@ -53,7 +55,7 @@ class InlineInfoTest { |class C extends T with U """.stripMargin val classes = compile(code) - val fromSyms = classes.map(c => compiler.genBCode.bTypes.classBTypeFromInternalName(c.name).info.inlineInfo) + val fromSyms = classes.map(c => compiler.genBCode.bTypes.classBTypeFromInternalName(c.name).info.get.inlineInfo) val fromAttrs = classes.map(c => { assert(c.attrs.asScala.exists(_.isInstanceOf[InlineInfoAttribute]), c.attrs) diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/InlineWarningTest.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/InlineWarningTest.scala new file mode 100644 index 0000000000..fedc074a15 --- /dev/null +++ b/test/junit/scala/tools/nsc/backend/jvm/opt/InlineWarningTest.scala @@ -0,0 +1,146 @@ +package scala.tools.nsc +package backend.jvm +package opt + +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import org.junit.Test +import scala.collection.generic.Clearable +import scala.collection.mutable.ListBuffer +import scala.reflect.internal.util.BatchSourceFile +import scala.tools.asm.Opcodes._ +import org.junit.Assert._ + +import scala.tools.asm.tree._ +import scala.tools.asm.tree.analysis._ +import scala.tools.nsc.backend.jvm.opt.BytecodeUtils.AsmAnalyzer +import scala.tools.nsc.io._ +import scala.tools.nsc.reporters.StoreReporter +import scala.tools.testing.AssertUtil._ + +import CodeGenTools._ +import scala.tools.partest.ASMConverters +import ASMConverters._ +import AsmUtils._ + +import BackendReporting._ + +import scala.collection.convert.decorateAsScala._ +import scala.tools.testing.ClearAfterClass + +object InlineWarningTest extends ClearAfterClass.Clearable { + val argsNoWarn = "-Ybackend:GenBCode -Yopt:l:classpath" + val args = argsNoWarn + " -Yopt-warnings" + var compiler = newCompiler(extraArgs = args) + def clear(): Unit = { compiler = null } +} + +@RunWith(classOf[JUnit4]) +class InlineWarningTest extends ClearAfterClass { + ClearAfterClass.stateToClear = InlineWarningTest + + val compiler = InlineWarningTest.compiler + + def compile(scalaCode: String, javaCode: List[(String, String)] = Nil, allowMessage: StoreReporter#Info => Boolean = _ => false): List[ClassNode] = { + compileClasses(compiler)(scalaCode, javaCode, allowMessage) + } + + @Test + def nonFinal(): Unit = { + val code = + """class C { + | @inline def m1 = 1 + |} + |trait T { + | @inline def m2 = 1 + |} + |class D extends C with T + | + |class Test { + | def t1(c: C, t: T, d: D) = c.m1 + t.m2 + d.m1 + d.m2 + |} + """.stripMargin + var count = 0 + val warns = Set( + "C::m1()I is annotated @inline but cannot be inlined: the method is not final and may be overridden", + "T::m2()I is annotated @inline but cannot be inlined: the method is not final and may be overridden", + "D::m2()I is annotated @inline but cannot be inlined: the method is not final and may be overridden") + compile(code, allowMessage = i => {count += 1; warns.exists(i.msg contains _)}) + assert(count == 4, count) + } + + @Test + def traitMissingImplClass(): Unit = { + val codeA = "trait T { @inline final def f = 1 }" + val codeB = "class C { def t1(t: T) = t.f }" + + val removeImpl = (outDir: AbstractFile) => { + val f = outDir.lookupName("T$class.class", directory = false) + if (f != null) f.delete() + } + + val warn = + """T::f()I is annotated @inline but cannot be inlined: the trait method call could not be rewritten to the static implementation method. Possible reason: + |The method f(LT;)I could not be found in the class T$class or any of its parents. + |Note that the following parent classes could not be found on the classpath: T$class""".stripMargin + + var c = 0 + compileSeparately(List(codeA, codeB), extraArgs = InlineWarningTest.args, afterEach = removeImpl, allowMessage = i => {c += 1; i.msg contains warn}) + assert(c == 1, c) + + // only summary here + compileSeparately(List(codeA, codeB), extraArgs = InlineWarningTest.argsNoWarn, afterEach = removeImpl, allowMessage = _.msg contains "there was one inliner warning") + } + + @Test + def handlerNonEmptyStack(): Unit = { + val code = + """class C { + | @noinline def q = 0 + | @inline final def foo = try { q } catch { case e: Exception => 2 } + | def t1 = println(foo) // inline warning here: foo cannot be inlined on top of a non-empty stack + |} + """.stripMargin + + var c = 0 + compile(code, allowMessage = i => {c += 1; i.msg contains "operand stack at the callsite in C::t1()V contains more values"}) + assert(c == 1, c) + } + + @Test + def mixedWarnings(): Unit = { + val javaCode = + """public class A { + | public static final int bar() { return 100; } + |} + """.stripMargin + + val scalaCode = + """class B { + | @inline final def flop = A.bar + | def g = flop + |} + """.stripMargin + + val warns = List( + """failed to determine if bar should be inlined: + |The method bar()I could not be found in the class A or any of its parents. + |Note that the following parent classes are defined in Java sources (mixed compilation), no bytecode is available: A""".stripMargin, + + """B::flop()I is annotated @inline but could not be inlined: + |Failed to check if B::flop()I can be safely inlined to B without causing an IllegalAccessError. Checking instruction INVOKESTATIC A.bar ()I failed: + |The method bar()I could not be found in the class A or any of its parents. + |Note that the following parent classes are defined in Java sources (mixed compilation), no bytecode is available: A""".stripMargin) + + var c = 0 + val List(b) = compile(scalaCode, List((javaCode, "A.java")), allowMessage = i => {c += 1; warns.tail.exists(i.msg contains _)}) + assert(c == 1, c) + + // no warnings here + compileClasses(newCompiler(extraArgs = InlineWarningTest.argsNoWarn + " -Yopt-warnings:none"))(scalaCode, List((javaCode, "A.java"))) + + c = 0 + compileClasses(newCompiler(extraArgs = InlineWarningTest.argsNoWarn + " -Yopt-warnings:no-inline-mixed"))(scalaCode, List((javaCode, "A.java")), allowMessage = i => {c += 1; warns.exists(i.msg contains _)}) + assert(c == 2, c) + } +} diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerIllegalAccessTest.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerIllegalAccessTest.scala index 91404acba7..03d2f2f108 100644 --- a/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerIllegalAccessTest.scala +++ b/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerIllegalAccessTest.scala @@ -59,7 +59,7 @@ class InlinerIllegalAccessTest extends ClearAfterClass { def check(classNode: ClassNode, test: Option[AbstractInsnNode] => Unit) = { for (m <- methods) - test(inliner.findIllegalAccess(m.instructions, classBTypeFromParsedClassfile(classNode.name).get)) + test(inliner.findIllegalAccess(m.instructions, classBTypeFromParsedClassfile(classNode.name)).map(_._1)) } check(cClass, assertEmpty) @@ -153,7 +153,7 @@ class InlinerIllegalAccessTest extends ClearAfterClass { val List(rbD, rcD, rfD, rgD) = dCl.methods.asScala.toList.filter(_.name(0) == 'r').sortBy(_.name) def check(method: MethodNode, dest: ClassNode, test: Option[AbstractInsnNode] => Unit): Unit = { - test(inliner.findIllegalAccess(method.instructions, classBTypeFromParsedClassfile(dest.name).get)) + test(inliner.findIllegalAccess(method.instructions, classBTypeFromParsedClassfile(dest.name)).map(_._1)) } val cOrDOwner = (_: Option[AbstractInsnNode] @unchecked) match { diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerSeparateCompilationTest.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerSeparateCompilationTest.scala index 58a262c401..5c9bd1c188 100644 --- a/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerSeparateCompilationTest.scala +++ b/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerSeparateCompilationTest.scala @@ -43,7 +43,8 @@ class InlinerSeparateCompilationTest { |} """.stripMargin - val List(c, o, oMod, t, tCls) = compileClassesSeparately(List(codeA, codeB), args) + val warn = "T::f()I is annotated @inline but cannot be inlined: the method is not final and may be overridden" + val List(c, o, oMod, t, tCls) = compileClassesSeparately(List(codeA, codeB), args + " -Yopt-warnings", _.msg contains warn) assertInvoke(getSingleMethod(c, "t1"), "T", "f") assertNoInvoke(getSingleMethod(c, "t2")) assertNoInvoke(getSingleMethod(c, "t3")) diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerTest.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerTest.scala index 7f58f77b15..d32c1b2958 100644 --- a/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerTest.scala +++ b/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerTest.scala @@ -15,6 +15,7 @@ import scala.tools.asm.tree._ import scala.tools.asm.tree.analysis._ import scala.tools.nsc.backend.jvm.opt.BytecodeUtils.AsmAnalyzer import scala.tools.nsc.io._ +import scala.tools.nsc.reporters.StoreReporter import scala.tools.testing.AssertUtil._ import CodeGenTools._ @@ -22,11 +23,13 @@ import scala.tools.partest.ASMConverters import ASMConverters._ import AsmUtils._ +import BackendReporting._ + import scala.collection.convert.decorateAsScala._ import scala.tools.testing.ClearAfterClass object InlinerTest extends ClearAfterClass.Clearable { - var compiler = newCompiler(extraArgs = "-Ybackend:GenBCode -Yopt:l:classpath") + var compiler = newCompiler(extraArgs = "-Ybackend:GenBCode -Yopt:l:classpath -Yopt-warnings") // allows inspecting the caches after a compilation run def notPerRun: List[Clearable] = List(compiler.genBCode.bTypes.classBTypeFromInternalName, compiler.genBCode.bTypes.byteCodeRepository.classes, compiler.genBCode.bTypes.callGraph.callsites) @@ -61,9 +64,9 @@ class InlinerTest extends ClearAfterClass { val compiler = InlinerTest.compiler import compiler.genBCode.bTypes._ - def compile(scalaCode: String, javaCode: List[(String, String)] = Nil): List[ClassNode] = { + def compile(scalaCode: String, javaCode: List[(String, String)] = Nil, allowMessage: StoreReporter#Info => Boolean = _ => false): List[ClassNode] = { InlinerTest.notPerRun.foreach(_.clear()) - compileClasses(compiler)(scalaCode, javaCode) + compileClasses(compiler)(scalaCode, javaCode, allowMessage) } def checkCallsite(callsite: callGraph.Callsite, callee: MethodNode) = { @@ -76,10 +79,10 @@ class InlinerTest extends ClearAfterClass { } // inline first invocation of f into g in class C - def inlineTest(code: String, mod: ClassNode => Unit = _ => ()): (MethodNode, Option[String]) = { + def inlineTest(code: String, mod: ClassNode => Unit = _ => ()): (MethodNode, Option[CannotInlineWarning]) = { val List(cls) = compile(code) mod(cls) - val clsBType = classBTypeFromParsedClassfile(cls.name).get + val clsBType = classBTypeFromParsedClassfile(cls.name) val List(f, g) = cls.methods.asScala.filter(m => Set("f", "g")(m.name)).toList.sortBy(_.name) val fCall = g.instructions.iterator.asScala.collect({ case i: MethodInsnNode if i.name == "f" => i }).next() @@ -166,7 +169,7 @@ class InlinerTest extends ClearAfterClass { val f = cls.methods.asScala.find(_.name == "f").get f.access |= ACC_SYNCHRONIZED }) - assert(can.get contains "synchronized", can) + assert(can.get.isInstanceOf[SynchronizedMethod], can) } @Test @@ -192,7 +195,7 @@ class InlinerTest extends ClearAfterClass { |} """.stripMargin val (_, r) = inlineTest(code) - assert(r.get contains "operand stack at the callsite", r) + assert(r.get.isInstanceOf[MethodWithHandlerCalledOnNonEmptyStack], r) } @Test @@ -213,8 +216,8 @@ class InlinerTest extends ClearAfterClass { val List(c, d) = compile(code) - val cTp = classBTypeFromParsedClassfile(c.name).get - val dTp = classBTypeFromParsedClassfile(d.name).get + val cTp = classBTypeFromParsedClassfile(c.name) + val dTp = classBTypeFromParsedClassfile(d.name) val g = c.methods.asScala.find(_.name == "g").get val h = d.methods.asScala.find(_.name == "h").get @@ -234,7 +237,7 @@ class InlinerTest extends ClearAfterClass { receiverKnownNotNull = true, keepLineNumbers = true) - assert(r.get contains "would cause an IllegalAccessError", r) + assert(r.get.isInstanceOf[IllegalAccessInstruction], r) } @Test @@ -373,7 +376,7 @@ class InlinerTest extends ClearAfterClass { val List(c) = compile(code) val f = c.methods.asScala.find(_.name == "f").get val callsiteIns = f.instructions.iterator().asScala.collect({ case c: MethodInsnNode => c }).next() - val clsBType = classBTypeFromParsedClassfile(c.name).get + val clsBType = classBTypeFromParsedClassfile(c.name) val analyzer = new AsmAnalyzer(f, clsBType.internalName) val integerClassBType = classBTypeFromInternalName("java/lang/Integer") @@ -457,8 +460,15 @@ class InlinerTest extends ClearAfterClass { |} """.stripMargin + val warn = + """B::flop()I is annotated @inline but could not be inlined: + |Failed to check if B::flop()I can be safely inlined to B without causing an IllegalAccessError. Checking instruction INVOKESTATIC A.bar ()I failed: + |The method bar()I could not be found in the class A or any of its parents. + |Note that the following parent classes are defined in Java sources (mixed compilation), no bytecode is available: A""".stripMargin - val List(b) = compile(scalaCode, List((javaCode, "A.java"))) + var c = 0 + val List(b) = compile(scalaCode, List((javaCode, "A.java")), allowMessage = i => {c += 1; i.msg contains warn}) + assert(c == 1, c) val ins = getSingleMethod(b, "g").instructions val invokeFlop = Invoke(INVOKEVIRTUAL, "B", "flop", "()I", false) assert(ins contains invokeFlop, ins.stringLines) @@ -526,7 +536,12 @@ class InlinerTest extends ClearAfterClass { | def t2 = this.f |} """.stripMargin - val List(c, t, tClass) = compile(code) + val warns = Set( + "C::f()I is annotated @inline but cannot be inlined: the method is not final and may be overridden", + "T::f()I is annotated @inline but cannot be inlined: the method is not final and may be overridden") + var count = 0 + val List(c, t, tClass) = compile(code, allowMessage = i => {count += 1; warns.exists(i.msg contains _)}) + assert(count == 2, count) assertInvoke(getSingleMethod(c, "t1"), "T", "f") assertInvoke(getSingleMethod(c, "t2"), "C", "f") } @@ -561,7 +576,10 @@ class InlinerTest extends ClearAfterClass { | def t3(t: T) = t.f // no inlining here |} """.stripMargin - val List(c, oMirror, oModule, t, tClass) = compile(code) + val warn = "T::f()I is annotated @inline but cannot be inlined: the method is not final and may be overridden" + var count = 0 + val List(c, oMirror, oModule, t, tClass) = compile(code, allowMessage = i => {count += 1; i.msg contains warn}) + assert(count == 1, count) assertNoInvoke(getSingleMethod(oModule, "f")) @@ -663,7 +681,11 @@ class InlinerTest extends ClearAfterClass { | def m5b(t: T2b) = t.f // re-written to T2b$class.f, inlined, ICONST_1 |} """.stripMargin - val List(ca, cb, t1, t1C, t2a, t2aC, t2b, t2bC) = compile(code) + + val warning = "T1::f()I is annotated @inline but cannot be inlined: the method is not final and may be overridden" + var count = 0 + val List(ca, cb, t1, t1C, t2a, t2aC, t2b, t2bC) = compile(code, allowMessage = i => {count += 1; i.msg contains warning}) + assert(count == 4, count) // see comments, f is not inlined 4 times val t2aCfDesc = t2aC.methods.asScala.find(_.name == "f").get.desc assert(t2aCfDesc == "(LT1;)I", t2aCfDesc) // self-type of T2a is T1 @@ -737,4 +759,36 @@ class InlinerTest extends ClearAfterClass { val cast = TypeOp(CHECKCAST, "C") Set(t1, t2).foreach(m => assert(m.instructions.contains(cast), m.instructions)) } + + @Test + def abstractMethodWarning(): Unit = { + val code = + """abstract class C { + | @inline def foo: Int + |} + |class T { + | def t1(c: C) = c.foo + |} + """.stripMargin + val warn = "C::foo()I is annotated @inline but cannot be inlined: the method is not final and may be overridden" + var c = 0 + compile(code, allowMessage = i => {c += 1; i.msg contains warn}) + assert(c == 1, c) + } + + @Test + def abstractFinalMethodError(): Unit = { + val code = + """abstract class C { + | @inline final def foo: Int + |} + |trait T { + | @inline final def bar: Int + |} + """.stripMargin + val err = "abstract member may not have final modifier" + var i = 0 + compile(code, allowMessage = info => {i += 1; info.msg contains err}) + assert(i == 2, i) + } } diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/MethodLevelOpts.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/MethodLevelOpts.scala index 2c71e9d533..1ce1b88ff2 100644 --- a/test/junit/scala/tools/nsc/backend/jvm/opt/MethodLevelOpts.scala +++ b/test/junit/scala/tools/nsc/backend/jvm/opt/MethodLevelOpts.scala @@ -31,7 +31,8 @@ class MethodLevelOpts extends ClearAfterClass { @Test def eliminateEmptyTry(): Unit = { val code = "def f = { try {} catch { case _: Throwable => 0; () }; 1 }" - assertSameCode(singleMethodInstructions(methodOptCompiler)(code), wrapInDefault(Op(ICONST_1), Op(IRETURN))) + val warn = "a pure expression does nothing in statement position" + assertSameCode(singleMethodInstructions(methodOptCompiler)(code, allowMessage = _.msg contains warn), wrapInDefault(Op(ICONST_1), Op(IRETURN))) } @Test diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/UnreachableCodeTest.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/UnreachableCodeTest.scala index c2e2a1b883..da9853148b 100644 --- a/test/junit/scala/tools/nsc/backend/jvm/opt/UnreachableCodeTest.scala +++ b/test/junit/scala/tools/nsc/backend/jvm/opt/UnreachableCodeTest.scala @@ -154,7 +154,8 @@ class UnreachableCodeTest extends ClearAfterClass { assertSameCode(noDce.dropNonOp, List(Op(ICONST_1), Op(IRETURN), Op(ATHROW), Op(ATHROW))) // when NOT computing stack map frames, ASM's ClassWriter does not replace dead code by NOP/ATHROW - val noDceNoFrames = singleMethodInstructions(noOptNoFramesCompiler)(code) + val warn = "target:jvm-1.5 is deprecated" + val noDceNoFrames = singleMethodInstructions(noOptNoFramesCompiler)(code, allowMessage = _.msg contains warn) assertSameCode(noDceNoFrames.dropNonOp, List(Op(ICONST_1), Op(IRETURN), Op(ICONST_2), Op(IRETURN))) } |