diff options
author | Dmitry Petrashko <dark@d-d.me> | 2014-05-06 10:25:59 +0200 |
---|---|---|
committer | Dmitry Petrashko <dark@d-d.me> | 2014-05-06 10:25:59 +0200 |
commit | bf9ae99e77f179e9f5ad3c2074edbb5aab0fe9f1 (patch) | |
tree | 6e7e2e9f5d21566fd9f74e4861a13e32ef375d3b /test | |
parent | a782adaf302713a1a049b9a72dacc0483ed67229 (diff) | |
parent | b88b79d03517dad973de2aa81c2e5f702d20b2e1 (diff) | |
download | dotty-bf9ae99e77f179e9f5ad3c2074edbb5aab0fe9f1.tar.gz dotty-bf9ae99e77f179e9f5ad3c2074edbb5aab0fe9f1.tar.bz2 dotty-bf9ae99e77f179e9f5ad3c2074edbb5aab0fe9f1.zip |
Merge pull request #119 from DarkDimius/leaks
Context escape detection.
Diffstat (limited to 'test')
-rw-r--r-- | test/test/CompilerTest.scala | 2 | ||||
-rw-r--r-- | test/test/ContextEscapeDetection.java | 40 | ||||
-rw-r--r-- | test/test/ContextEscapeDetector.java | 109 | ||||
-rw-r--r-- | test/test/DottyTest.scala | 8 |
4 files changed, 156 insertions, 3 deletions
diff --git a/test/test/CompilerTest.scala b/test/test/CompilerTest.scala index d25cfb637..d52e74de7 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 000000000..233630eb2 --- /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<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(); + } + + +} + diff --git a/test/test/ContextEscapeDetector.java b/test/test/ContextEscapeDetector.java new file mode 100644 index 000000000..c7768fd57 --- /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<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 + } + } +} diff --git a/test/test/DottyTest.scala b/test/test/DottyTest.scala index 3c308e230..4efb1e615 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 |