diff options
Diffstat (limited to 'test')
23 files changed, 304 insertions, 52 deletions
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))) } |