Skip to content

Context escape detection. #119

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
May 6, 2014
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions project/Build.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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
Expand Down
4 changes: 2 additions & 2 deletions src/dotty/tools/dotc/Bench.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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))
}
}

Expand Down
6 changes: 3 additions & 3 deletions src/dotty/tools/dotc/Driver.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion test/test/CompilerTest.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}

Expand Down
40 changes: 40 additions & 0 deletions test/test/ContextEscapeDetection.java
Original file line number Diff line number Diff line change
@@ -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<Contexts.Context> context, String testName) {
this.context = context;
this.testName = testName;
}

public final WeakReference<Contexts.Context> context;
public final String testName;

}
public static final List<TestContext> 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();
}


}

109 changes: 109 additions & 0 deletions test/test/ContextEscapeDetector.java
Original file line number Diff line number Diff line change
@@ -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<Object>(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<Object>(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
}
}
}
8 changes: 6 additions & 2 deletions test/test/DottyTest.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down