diff --git a/project/Build.scala b/project/Build.scala index 8442ce5de914..c7e1518aaead 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -15,7 +15,7 @@ object DottyBuild extends Build { resourceDirectory in Compile := baseDirectory.value / "resources", unmanagedSourceDirectories in Compile := Seq((scalaSource in Compile).value), unmanagedSourceDirectories in Test := Seq((scalaSource in Test).value), - + // include sources in eclipse (downloads source code for all dependencies) //http://stackoverflow.com/questions/10472840/how-to-attach-sources-to-sbt-managed-dependencies-in-scala-ide#answer-11683728 com.typesafe.sbteclipse.plugin.EclipsePlugin.EclipseKeys.withSource := true, @@ -28,17 +28,21 @@ object DottyBuild extends Build { "org.scala-lang.modules" %% "scala-xml" % "1.0.1"), // get junit onboard - libraryDependencies += "com.novocode" % "junit-interface" % "0.9" % "test", + libraryDependencies += "com.novocode" % "junit-interface" % "0.11-RC1" % "test", // scalac options scalacOptions in Global ++= Seq("-feature", "-deprecation", "-language:_"), + javacOptions ++= Seq("-Xlint:unchecked", "-Xlint:deprecation"), + // enable verbose exception messages for JUnit - testOptions += Tests.Argument(TestFrameworks.JUnit, "-a", "-v"), + testOptions += Tests.Argument(TestFrameworks.JUnit, "-a", "-v", "--run-listener=test.ContextEscapeDetector"), // Adjust classpath for running dotty mainClass in (Compile, run) := Some("dotty.tools.dotc.Main"), fork in run := true, fork in Test := true, + parallelExecution in Test := false, + // http://grokbase.com/t/gg/simple-build-tool/135ke5y90p/sbt-setting-jvm-boot-paramaters-for-scala javaOptions <++= (managedClasspath in Runtime, packageBin in Compile) map { (attList, bin) => // put the Scala {library, reflect, compiler} in the classpath diff --git a/src/dotty/tools/dotc/Bench.scala b/src/dotty/tools/dotc/Bench.scala index 13507e1cec03..2e0e15e83d58 100644 --- a/src/dotty/tools/dotc/Bench.scala +++ b/src/dotty/tools/dotc/Bench.scala @@ -46,11 +46,11 @@ object Bench extends Driver { else (args(pos + 1).toInt, (args take pos) ++ (args drop (pos + 2))) } - override def process(args: Array[String]): Reporter = { + override def process(args: Array[String], rootCtx: Context): Reporter = { val (numCompilers, args1) = extractNumArg(args, "#compilers") val (numRuns, args2) = extractNumArg(args1, "#runs") this.numRuns = numRuns - ntimes(numCompilers)(super.process(args2)) + ntimes(numCompilers)(super.process(args2, rootCtx)) } } diff --git a/src/dotty/tools/dotc/Driver.scala b/src/dotty/tools/dotc/Driver.scala index 5bf65544a562..d65d7c1d3b3b 100644 --- a/src/dotty/tools/dotc/Driver.scala +++ b/src/dotty/tools/dotc/Driver.scala @@ -22,8 +22,8 @@ abstract class Driver extends DotClass { protected def initCtx = (new ContextBase).initialCtx - def process(args: Array[String]): Reporter = { - val summary = CompilerCommand.distill(args)(initCtx) + def process(args: Array[String], rootCtx: Context): Reporter = { + val summary = CompilerCommand.distill(args)(rootCtx) implicit val ctx: Context = initCtx.fresh.setSettings(summary.sstate) val fileNames = CompilerCommand.checkUsage(summary) try { @@ -41,7 +41,7 @@ abstract class Driver extends DotClass { } def main(args: Array[String]): Unit = - sys.exit(if (process(args).hasErrors) 1 else 0) + sys.exit(if (process(args, initCtx).hasErrors) 1 else 0) } class FatalError(msg: String) extends Exception diff --git a/test/test/CompilerTest.scala b/test/test/CompilerTest.scala index d25cfb637eb6..d52e74de7b9e 100644 --- a/test/test/CompilerTest.scala +++ b/test/test/CompilerTest.scala @@ -13,7 +13,7 @@ class CompilerTest extends DottyTest { def compileArgs(args: Array[String], xerrors: Int = 0): Unit = { val allArgs = args ++ defaultOptions val processor = if (allArgs.exists(_.startsWith("#"))) Bench else Main - val nerrors = processor.process(allArgs).count(Reporter.ERROR.level) + val nerrors = processor.process(allArgs, ctx).count(Reporter.ERROR.level) assert(nerrors == xerrors, s"Wrong # of errors. Expected: $xerrors, found: $nerrors") } diff --git a/test/test/ContextEscapeDetection.java b/test/test/ContextEscapeDetection.java new file mode 100644 index 000000000000..233630eb2643 --- /dev/null +++ b/test/test/ContextEscapeDetection.java @@ -0,0 +1,40 @@ +package test; + +import dotty.tools.dotc.core.Contexts; +import org.junit.*; + +import java.lang.ref.WeakReference; +import java.util.LinkedList; +import java.util.List; + + +public abstract class ContextEscapeDetection { + public static class TestContext{ + public TestContext(WeakReference context, String testName) { + this.context = context; + this.testName = testName; + } + + public final WeakReference context; + public final String testName; + + } + public static final List contexts = new LinkedList<>(); + + public abstract Contexts.Context getCtx(); + + public abstract void clearCtx(); + + @Before + public synchronized void stealContext() { + contexts.add(new TestContext(new WeakReference<>(this.getCtx()), this.getClass().getName())); + } + + @After + public synchronized void clearContext() { + this.clearCtx(); + } + + +} + diff --git a/test/test/ContextEscapeDetector.java b/test/test/ContextEscapeDetector.java new file mode 100644 index 000000000000..c7768fd57647 --- /dev/null +++ b/test/test/ContextEscapeDetector.java @@ -0,0 +1,109 @@ +package test; + +import org.junit.runner.Description; +import org.junit.runner.Result; +import org.junit.runner.notification.RunListener; +import org.junit.Assert; + +import java.lang.ref.WeakReference; + +public class ContextEscapeDetector extends RunListener { + + //context can be captured by objects, eg NoDenotation + public static final int CONTEXTS_ALLOWED = 1; + + @Override + public void testRunFinished(Result result) throws Exception { + if (contextsAlive() > CONTEXTS_ALLOWED) { + forceGCHeuristic0(); + if (contextsAlive() > CONTEXTS_ALLOWED) { + forceGCHeuristic1(); + if (contextsAlive() > CONTEXTS_ALLOWED) { + forceGCHeuristic2(); + forceGCHeuristic1(); + int contextAlive = contextsAlive(); + if (contextAlive > CONTEXTS_ALLOWED) { + StringBuilder names = new StringBuilder(); + for (ContextEscapeDetection.TestContext ref : ContextEscapeDetection.contexts) { + if (ref.context.get() != null) names.append(ref.testName).append(' '); + } + Assert.fail("Multiple contexts survived test suite: " + names.toString()); + } + } + } + } + super.testRunFinished(result); + } + + private static synchronized int contextsAlive() { + int count = 0; + for (ContextEscapeDetection.TestContext ref : ContextEscapeDetection.contexts) { + if (ref.context.get() != null) count++; + } + return count; + } + + private static volatile Object o = null; + + private static synchronized void forceGCHeuristic0() { + System.gc(); + Runtime.getRuntime().gc(); + System.gc(); + Runtime.getRuntime().gc(); + System.gc(); + Runtime.getRuntime().gc(); + System.gc(); + Runtime.getRuntime().gc(); + System.gc(); + } + + private static synchronized void forceGCHeuristic1() { + Object obj = new Object(); + WeakReference ref = new WeakReference(obj); + obj = null; + while (ref.get() != null) { + System.gc(); + } + } + + private static synchronized void forceGCHeuristic2() { + try { + Object[] arr = new Object[1024]; // upto 8 GB + WeakReference ref = new WeakReference(arr); + o = arr; // make sure array isn't optimized away + + Runtime runtime = Runtime.getRuntime(); + // allocate memory until no more that 64MB is left + for (int i = 0; i < 1024 && + runtime.totalMemory() != runtime.maxMemory() || + runtime.freeMemory() < 1024 * 1024 * 64; i++) { + int[] data = new int[1024 * 1024]; // 8MB + for (int j = 0; j < 1024 * 1024; j++) { + data[j] = j; // force actual pages allocation + } + arr[i] = data; + } + o = null; + arr = new Object[128]; + o = arr; + // allocate 1 more GB + for (int i = 0; i < 128; i++) { + int[] data = new int[1024 * 1024]; // 8MB + for (int j = 0; j < 1024 * 1024; j++) { + data[j] = j; // force actual pages allocation + } + arr[i] = data; + } + o = null; + arr = null; + + forceGCHeuristic0(); + while (ref.get() != null) { + System.gc(); + } + } catch (OutOfMemoryError e) { + o = null; + // just swallow + } + } +} diff --git a/test/test/DottyTest.scala b/test/test/DottyTest.scala index 3c308e230b5c..4efb1e615069 100644 --- a/test/test/DottyTest.scala +++ b/test/test/DottyTest.scala @@ -14,11 +14,11 @@ import dotty.tools.dotc.Compiler import dotty.tools.dotc import dotty.tools.dotc.core.Phases.Phase -class DottyTest { +class DottyTest extends ContextEscapeDetection{ dotty.tools.dotc.parsing.Scanners // initialize keywords - implicit val ctx: Context = { + implicit var ctx: Contexts.Context = { val base = new ContextBase import base.settings._ val ctx = base.initialCtx.fresh @@ -37,6 +37,10 @@ class DottyTest { ctx } + override def getCtx: Context = ctx + override def clearCtx() = { + ctx = null + } private def compilerWithChecker(phase: String)(assertion:(tpd.Tree, Context) => Unit) = new Compiler { override def phases = { val allPhases = super.phases