GitHub #272: Exclude dynamically generated classes

Exclude dynamically generated classes from instrumentation for better
interoperability with JMockit.
diff --git a/org.jacoco.agent.rt.test/src/org/jacoco/agent/rt/internal/CoverageTransformerTest.java b/org.jacoco.agent.rt.test/src/org/jacoco/agent/rt/internal/CoverageTransformerTest.java
index 0f5ef32..68f25fc 100644
--- a/org.jacoco.agent.rt.test/src/org/jacoco/agent/rt/internal/CoverageTransformerTest.java
+++ b/org.jacoco.agent.rt.test/src/org/jacoco/agent/rt/internal/CoverageTransformerTest.java
@@ -21,6 +21,9 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.lang.instrument.IllegalClassFormatException;
+import java.security.CodeSource;
+import java.security.ProtectionDomain;
+import java.security.cert.Certificate;
 
 import org.jacoco.core.JaCoCo;
 import org.jacoco.core.runtime.AbstractRuntime;
@@ -41,6 +44,8 @@
 
 	private ClassLoader classLoader;
 
+	private ProtectionDomain protectionDomain;
+
 	private StubRuntime runtime;
 
 	@Before
@@ -48,6 +53,7 @@
 		recorder = new ExceptionRecorder();
 		options = new AgentOptions();
 		classLoader = getClass().getClassLoader();
+		protectionDomain = getClass().getProtectionDomain();
 		runtime = new StubRuntime();
 	}
 
@@ -57,6 +63,33 @@
 	}
 
 	@Test
+	public void testHasSourceLocationNegative1() {
+		CoverageTransformer t = createTransformer();
+		assertFalse(t.hasSourceLocation(null));
+	}
+
+	@Test
+	public void testHasSourceLocationNegative2() {
+		CoverageTransformer t = createTransformer();
+		ProtectionDomain pd = new ProtectionDomain(null, null);
+		assertFalse(t.hasSourceLocation(pd));
+	}
+
+	@Test
+	public void testHasSourceLocationNegative3() {
+		CoverageTransformer t = createTransformer();
+		CodeSource cs = new CodeSource(null, new Certificate[0]);
+		ProtectionDomain pd = new ProtectionDomain(cs, null);
+		assertFalse(t.hasSourceLocation(pd));
+	}
+
+	@Test
+	public void testHasSourceLocationPositive() {
+		CoverageTransformer t = createTransformer();
+		assertTrue(t.hasSourceLocation(protectionDomain));
+	}
+
+	@Test
 	public void testFilterAgentClass() {
 		CoverageTransformer t = createTransformer();
 		assertFalse(t.filter(classLoader,
@@ -78,14 +111,34 @@
 	}
 
 	@Test
-	public void testFilterClassLoaderPositive() {
+	public void testFilterClassLoaderPositive1() {
+		options.setInclBootstrapClasses(false);
 		options.setExclClassloader("org.jacoco.agent.SomeWhere$*");
 		CoverageTransformer t = createTransformer();
 		assertTrue(t.filter(classLoader, "org/example/Foo"));
 	}
 
 	@Test
-	public void testFilterClassLoaderNegative() {
+	public void testFilterClassLoaderPositive2() {
+		options.setInclBootstrapClasses(true);
+		options.setExclClassloader("org.jacoco.agent.SomeWhere$*");
+		CoverageTransformer t = createTransformer();
+		assertTrue(t.filter(classLoader, "org/example/Foo"));
+	}
+
+	@Test
+	public void testFilterClassLoaderNegative1() {
+		options.setInclBootstrapClasses(false);
+		options.setExclClassloader("org.jacoco.agent.rt.internal.CoverageTransformerTest$*");
+		CoverageTransformer t = createTransformer();
+		ClassLoader myClassLoader = new ClassLoader(null) {
+		};
+		assertFalse(t.filter(myClassLoader, "org/example/Foo"));
+	}
+
+	@Test
+	public void testFilterClassLoaderNegative2() {
+		options.setInclBootstrapClasses(true);
 		options.setExclClassloader("org.jacoco.agent.rt.internal.CoverageTransformerTest$*");
 		CoverageTransformer t = createTransformer();
 		ClassLoader myClassLoader = new ClassLoader(null) {
@@ -129,17 +182,25 @@
 	}
 
 	@Test
-	public void testTransformFiltered() throws IllegalClassFormatException {
+	public void testTransformFiltered1() throws IllegalClassFormatException {
 		CoverageTransformer t = createTransformer();
-		assertNull(t.transform(null, "org.jacoco.Sample", null, null,
+		assertNull(t.transform(classLoader, "org.jacoco.Sample", null, null,
 				new byte[0]));
 	}
 
 	@Test
+	public void testTransformFiltered2() throws IllegalClassFormatException {
+		CoverageTransformer t = createTransformer();
+		assertNull(t.transform(null, "org.jacoco.Sample", null,
+				protectionDomain, new byte[0]));
+	}
+
+	@Test
 	public void testTransformFailure() {
 		CoverageTransformer t = createTransformer();
 		try {
-			t.transform(classLoader, "org.jacoco.Sample", null, null, null);
+			t.transform(classLoader, "org.jacoco.Sample", null,
+					protectionDomain, null);
 			fail("IllegalClassFormatException expected.");
 		} catch (IllegalClassFormatException e) {
 			assertEquals("Error while instrumenting class org.jacoco.Sample.",
@@ -156,8 +217,8 @@
 		CoverageTransformer t = createTransformer();
 		// Just pick any non-system class outside our namespace
 		final Class<?> target = JaCoCo.class;
-		assertNull(t.transform(classLoader, target.getName(), target, null,
-				getClassData(target)));
+		assertNull(t.transform(classLoader, target.getName(), target,
+				protectionDomain, getClassData(target)));
 	}
 
 	private CoverageTransformer createTransformer() {
diff --git a/org.jacoco.agent.rt/src/org/jacoco/agent/rt/internal/CoverageTransformer.java b/org.jacoco.agent.rt/src/org/jacoco/agent/rt/internal/CoverageTransformer.java
index 3130ec5..b268889 100644
--- a/org.jacoco.agent.rt/src/org/jacoco/agent/rt/internal/CoverageTransformer.java
+++ b/org.jacoco.agent.rt/src/org/jacoco/agent/rt/internal/CoverageTransformer.java
@@ -13,6 +13,7 @@
 
 import java.lang.instrument.ClassFileTransformer;
 import java.lang.instrument.IllegalClassFormatException;
+import java.security.CodeSource;
 import java.security.ProtectionDomain;
 
 import org.jacoco.core.instr.Instrumenter;
@@ -73,8 +74,13 @@
 			final ProtectionDomain protectionDomain,
 			final byte[] classfileBuffer) throws IllegalClassFormatException {
 
+		// We do not support class retransformation:
 		if (classBeingRedefined != null) {
-			// We do not support class retransformation.
+			return null;
+		}
+
+		// We exclude dynamically created non-bootstrap classes.
+		if (loader != null && !hasSourceLocation(protectionDomain)) {
 			return null;
 		}
 
@@ -96,6 +102,25 @@
 	}
 
 	/**
+	 * Checks whether this protection domain is associated with a source
+	 * location.
+	 * 
+	 * @param protectionDomain
+	 *            protection domain to check (or <code>null</code>)
+	 * @return <code>true</code> if a source location is defined
+	 */
+	boolean hasSourceLocation(final ProtectionDomain protectionDomain) {
+		if (protectionDomain == null) {
+			return false;
+		}
+		final CodeSource codeSource = protectionDomain.getCodeSource();
+		if (codeSource == null) {
+			return false;
+		}
+		return codeSource.getLocation() != null;
+	}
+
+	/**
 	 * Checks whether this class should be instrumented.
 	 * 
 	 * @param loader
@@ -104,11 +129,12 @@
 	 *            VM name of the class to check
 	 * @return <code>true</code> if the class should be instrumented
 	 */
-	protected boolean filter(final ClassLoader loader, final String classname) {
-		if (!includeBootstrapClasses) {
-			if (loader == null) {
+	boolean filter(final ClassLoader loader, final String classname) {
+		if (loader == null) {
+			if (!includeBootstrapClasses) {
 				return false;
 			}
+		} else {
 			if (exclClassloader.matches(loader.getClass().getName())) {
 				return false;
 			}
diff --git a/org.jacoco.doc/docroot/doc/changes.html b/org.jacoco.doc/docroot/doc/changes.html
index 0ebae67..758b30c 100644
--- a/org.jacoco.doc/docroot/doc/changes.html
+++ b/org.jacoco.doc/docroot/doc/changes.html
@@ -25,6 +25,9 @@
   <li>For offline instrumemtation agent configuration supports system properties
       replacements. Implementation based on pull request of GitHub user 'debugger'
       (GitHub <a href="https://github.com/jacoco/jacoco/issues/262">#262</a>).</li>
+  <li>Exclude dynamically generated classes from instrumentation for better
+      interoperability with JMockit, analysis contributed by Rogério Liesenfeld
+      (GitHub <a href="https://github.com/jacoco/jacoco/issues/272">#272</a>).</li>
 </ul>
 
 <h3>Fixed Bugs</h3>