summaryrefslogtreecommitdiff
path: root/test
diff options
context:
space:
mode:
authorLukas Rytz <lukas.rytz@gmail.com>2015-03-11 11:38:17 -0700
committerLukas Rytz <lukas.rytz@gmail.com>2015-03-11 15:18:22 -0700
commitf8731c5b17274d68de3469e34727e24a937ffc84 (patch)
treeee0659cee396cc334a3015c21c9c46cdbc83e847 /test
parent57c07204ca452564b930085cfa9e8b099e45b2a9 (diff)
downloadscala-f8731c5b17274d68de3469e34727e24a937ffc84.tar.gz
scala-f8731c5b17274d68de3469e34727e24a937ffc84.tar.bz2
scala-f8731c5b17274d68de3469e34727e24a937ffc84.zip
Issue inliner warnings for callsites that cannot be inlined
Issue precise warnings when the inliner fails to inline or analyze a callsite. Inline failures may have various causes, for example because some class cannot be found on the classpath when building the call graph. So we need to store problems that happen early in the optimizer (when building the necessary data structures, call graph, ClassBTypes) to be able to report them later in case the inliner accesses the related data. We use Either to store these warning messages. The commit introduces an implicit class `RightBiasedEither` to make Either easier to use for error propagation. This would be subsumed by a biased either in the standard library (or could use a Validation). The `info` of each ClassBType is now an Either. There are two cases where the info is not available: - The type info should be parsed from a classfile, but the class cannot be found on the classpath - SI-9111, the type of a Java source originating class symbol cannot be completed This means that the operations on ClassBType that query the info now return an Either, too. Each Callsite in the call graph now stores the source position of the call instruction. Since the call graph is built after code generation, we build a map from invocation nodes to positions during code gen and query it when building the call graph. The new inliner can report a large number of precise warnings when a callsite cannot be inlined, or if the inlining metadata cannot be computed precisely, for example due to a missing classfile. The new -Yopt-warnings multi-choice option allows configuring inliner warnings. By default (no option provided), a one-line summary is issued in case there were callsites annotated @inline that could not be inlined.
Diffstat (limited to 'test')
-rw-r--r--test/files/run/colltest1.scala2
-rw-r--r--test/files/run/compiler-asSeenFrom.scala2
-rw-r--r--test/files/run/existentials-in-compiler.scala2
-rw-r--r--test/files/run/is-valid-num.scala2
-rw-r--r--test/files/run/iterator-from.scala2
-rw-r--r--test/files/run/mapConserve.scala2
-rw-r--r--test/files/run/pc-conversions.scala2
-rw-r--r--test/files/run/stringinterpolation_macro-run.scala2
-rw-r--r--test/files/run/synchronized.check4
-rw-r--r--test/files/run/t7096.scala2
-rw-r--r--test/files/run/t7582.check4
-rw-r--r--test/files/run/t7582b.check4
-rw-r--r--test/junit/scala/tools/nsc/backend/jvm/CodeGenTools.scala44
-rw-r--r--test/junit/scala/tools/nsc/backend/jvm/DirectCompileTest.scala4
-rw-r--r--test/junit/scala/tools/nsc/backend/jvm/opt/BTypesFromClassfileTest.scala8
-rw-r--r--test/junit/scala/tools/nsc/backend/jvm/opt/CallGraphTest.scala23
-rw-r--r--test/junit/scala/tools/nsc/backend/jvm/opt/InlineInfoTest.scala4
-rw-r--r--test/junit/scala/tools/nsc/backend/jvm/opt/InlineWarningTest.scala146
-rw-r--r--test/junit/scala/tools/nsc/backend/jvm/opt/InlinerIllegalAccessTest.scala4
-rw-r--r--test/junit/scala/tools/nsc/backend/jvm/opt/InlinerSeparateCompilationTest.scala3
-rw-r--r--test/junit/scala/tools/nsc/backend/jvm/opt/InlinerTest.scala84
-rw-r--r--test/junit/scala/tools/nsc/backend/jvm/opt/MethodLevelOpts.scala3
-rw-r--r--test/junit/scala/tools/nsc/backend/jvm/opt/UnreachableCodeTest.scala3
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)))
}