GitHub #49: New agent option includebootstrapclasses 

New configuration option for the JaCoCo agent includebootstrapclasses to
also instrument classes from the bootstrap class loader. 
diff --git a/jacoco-maven-plugin/src/org/jacoco/maven/AbstractAgentMojo.java b/jacoco-maven-plugin/src/org/jacoco/maven/AbstractAgentMojo.java
index 4ed00c5..ee38381 100644
--- a/jacoco-maven-plugin/src/org/jacoco/maven/AbstractAgentMojo.java
+++ b/jacoco-maven-plugin/src/org/jacoco/maven/AbstractAgentMojo.java
@@ -73,6 +73,14 @@
 	 */
 	String exclClassLoaders;
 	/**
+	 * Specifies whether also classes from the bootstrap classloader should be
+	 * instrumented. Use this feature with caution, it needs heavy
+	 * includes/excludes tuning.
+	 * 
+	 * @parameter property="jacoco.includebootstrapclasses"
+	 */
+	Boolean includebootstrapclasses;
+	/**
 	 * A session identifier that is written with the execution data. Without
 	 * this parameter a random identifier is created by the agent.
 	 * 
@@ -178,6 +186,10 @@
 		if (exclClassLoaders != null) {
 			agentOptions.setExclClassloader(exclClassLoaders);
 		}
+		if (includebootstrapclasses != null) {
+			agentOptions.setIncludeBootstrapClasses(includebootstrapclasses
+					.booleanValue());
+		}
 		if (sessionId != null) {
 			agentOptions.setSessionId(sessionId);
 		}
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 15e3d1a..09b97aa 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
@@ -64,9 +64,17 @@
 	}
 
 	@Test
-	public void testFilterSystemClass() {
+	public void testFilterIncludesBootstrapClassesPositive() {
+		options.setIncludeBootstrapClasses(true);
 		CoverageTransformer t = createTransformer();
-		assertFalse(t.filter(null, "org/example/Foo"));
+		assertTrue(t.filter(null, "java/util/TreeSet"));
+	}
+
+	@Test
+	public void testFilterIncludesBootstrapClassesNegative() {
+		options.setIncludeBootstrapClasses(false);
+		CoverageTransformer t = createTransformer();
+		assertFalse(t.filter(null, "java/util/TreeSet"));
 	}
 
 	@Test
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 78d7b5e..8b14a23 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
@@ -44,6 +44,8 @@
 
 	private final ClassFileDumper classFileDumper;
 
+	private final boolean includeBootstrapClasses;
+
 	/**
 	 * New transformer with the given delegates.
 	 * 
@@ -63,6 +65,7 @@
 		excludes = new WildcardMatcher(toVMName(options.getExcludes()));
 		exclClassloader = new WildcardMatcher(options.getExclClassloader());
 		classFileDumper = new ClassFileDumper(options.getClassDumpDir());
+		includeBootstrapClasses = options.getIncludeBootstrapClasses();
 	}
 
 	public byte[] transform(final ClassLoader loader, final String classname,
@@ -102,12 +105,16 @@
 	 * @return <code>true</code> if the class should be instrumented
 	 */
 	protected boolean filter(final ClassLoader loader, final String classname) {
-		// Don't instrument classes of the bootstrap loader:
-		return loader != null &&
+		if (!includeBootstrapClasses) {
+			if (loader == null) {
+				return false;
+			}
+			if (exclClassloader.matches(loader.getClass().getName())) {
+				return false;
+			}
+		}
 
-		!classname.startsWith(AGENT_PREFIX) &&
-
-		!exclClassloader.matches(loader.getClass().getName()) &&
+		return !classname.startsWith(AGENT_PREFIX) &&
 
 		includes.matches(classname) &&
 
diff --git a/org.jacoco.ant.test/src/org/jacoco/ant/CoverageTaskTest.xml b/org.jacoco.ant.test/src/org/jacoco/ant/CoverageTaskTest.xml
index c6684f1..0afab42 100644
--- a/org.jacoco.ant.test/src/org/jacoco/ant/CoverageTaskTest.xml
+++ b/org.jacoco.ant.test/src/org/jacoco/ant/CoverageTaskTest.xml
@@ -18,7 +18,7 @@
 	<target name="setUp">
 		<tempfile property="temp.dir" prefix="jacocoTest" destdir="${java.io.tmpdir}" />
 		<mkdir dir="${temp.dir}"/>
-		<property name="exec.file" location="${temp.dir}/exec.file" />
+		<property name="exec.file" location="${temp.dir}/jacoco.exec" />
 	</target>
 
 	<target name="tearDown">
@@ -141,5 +141,22 @@
 		
 		<au:assertLogDoesntContain text="Target executed"/>
 	</target>
-	
+
+	<target name="testIncludeBootstrapClasses">
+		<jacoco:coverage destfile="${exec.file}" includebootstrapclasses="true" includes="java/sql/*">
+			<java classname="org.jacoco.ant.TestTarget" fork="true" failonerror="true">
+				<classpath path="${org.jacoco.ant.coverageTaskTest.classes.dir}"/>
+			</java>
+		</jacoco:coverage>
+		
+		<au:assertLogContains text="Enhancing java with coverage"/>
+		<au:assertFileExists file="${exec.file}"/>
+		<au:assertLogContains text="Target executed"/>
+		
+		<java classname="org.jacoco.ant.DumpExecClassNames" classpath="${java.class.path}" failonerror="true">
+			<arg value="${exec.file}" />
+		</java>
+		<au:assertLogContains text="java/sql/Timestamp"/>
+	</target>
+
 </project>
\ No newline at end of file
diff --git a/org.jacoco.ant.test/src/org/jacoco/ant/DumpExecClassNames.java b/org.jacoco.ant.test/src/org/jacoco/ant/DumpExecClassNames.java
new file mode 100644
index 0000000..60133b0
--- /dev/null
+++ b/org.jacoco.ant.test/src/org/jacoco/ant/DumpExecClassNames.java
@@ -0,0 +1,34 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2014 Mountainminds GmbH & Co. KG and Contributors
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    Marc R. Hoffmann - initial API and implementation
+ *    
+ *******************************************************************************/
+package org.jacoco.ant;
+
+import java.io.File;
+
+import org.jacoco.core.data.ExecutionData;
+import org.jacoco.core.tools.ExecFileLoader;
+
+/**
+ * Test utility to dump class names from exec file.
+ */
+public class DumpExecClassNames {
+
+	public static void main(String[] args) throws Exception {
+		final ExecFileLoader loader = new ExecFileLoader();
+		for (String f : args) {
+			loader.load(new File(f));
+		}
+		for (ExecutionData d : loader.getExecutionDataStore().getContents()) {
+			System.out.println(d.getName());
+		}
+	}
+
+}
diff --git a/org.jacoco.ant.test/src/org/jacoco/ant/InstrumentTaskTest.xml b/org.jacoco.ant.test/src/org/jacoco/ant/InstrumentTaskTest.xml
index dbf616d..345c9db 100644
--- a/org.jacoco.ant.test/src/org/jacoco/ant/InstrumentTaskTest.xml
+++ b/org.jacoco.ant.test/src/org/jacoco/ant/InstrumentTaskTest.xml
@@ -64,7 +64,7 @@
 		<jacoco:instrument destdir="${instr.dir}">
 			<fileset dir="${lib.dir}" includes="*.jar"/>
 		</jacoco:instrument>
-		<au:assertLogContains text="Instrumented 12 classes to ${temp.dir}"/>
+		<au:assertLogContains text="Instrumented 13 classes to ${temp.dir}"/>
 
 		<unzip src="${instr.dir}/test.jar" dest="${instr.dir}"/>
 		<au:assertFileDoesntExist file="${instr.dir}/META-INF/TEST.RSA" />
@@ -85,7 +85,7 @@
 		<jacoco:instrument destdir="${instr.dir}" removesignatures="false">
 			<fileset dir="${lib.dir}" includes="*.jar"/>
 		</jacoco:instrument>
-		<au:assertLogContains text="Instrumented 12 classes to ${temp.dir}"/>
+		<au:assertLogContains text="Instrumented 13 classes to ${temp.dir}"/>
 
 		<unzip src="${instr.dir}/test.jar" dest="${instr.dir}"/>
 		<au:assertFileExists file="${instr.dir}/META-INF/TEST.RSA" />
@@ -96,7 +96,7 @@
 		<jacoco:instrument destdir="${temp.dir}">
 			<fileset dir="${org.jacoco.ant.instrumentTaskTest.classes.dir}" includes="**/*.class"/>
 		</jacoco:instrument>
-		<au:assertLogContains text="Instrumented 12 classes to ${temp.dir}"/>
+		<au:assertLogContains text="Instrumented 13 classes to ${temp.dir}"/>
 		<au:assertFileExists file="${temp.dir}/org/jacoco/ant/InstrumentTaskTest.class" />
 
 		<echo file="${temp.dir}/jacoco-agent.properties">destfile=test.exec</echo>
@@ -113,7 +113,7 @@
 		<jacoco:instrument destdir="${temp.dir}">
 			<fileset dir="${org.jacoco.ant.instrumentTaskTest.classes.dir}" includes="**/*.class"/>
 		</jacoco:instrument>
-		<au:assertLogContains text="Instrumented 12 classes to ${temp.dir}"/>
+		<au:assertLogContains text="Instrumented 13 classes to ${temp.dir}"/>
 		<au:assertFileExists file="${temp.dir}/org/jacoco/ant/InstrumentTaskTest.class" />
 
 		<java classname="org.jacoco.ant.TestTarget" failonerror="true" fork="true">
diff --git a/org.jacoco.ant.test/src/org/jacoco/ant/TestTarget.java b/org.jacoco.ant.test/src/org/jacoco/ant/TestTarget.java
index 5b5023e..9bc91b6 100644
--- a/org.jacoco.ant.test/src/org/jacoco/ant/TestTarget.java
+++ b/org.jacoco.ant.test/src/org/jacoco/ant/TestTarget.java
@@ -31,6 +31,10 @@
 	}
 
 	public static void main(String[] args) throws Exception {
+
+		// Load some class from the bootstrap classloader:
+		new java.sql.Timestamp(0);
+
 		System.out.println("Target executed");
 
 		// Wait for termination file to turn up
diff --git a/org.jacoco.ant/src/org/jacoco/ant/AbstractCoverageTask.java b/org.jacoco.ant/src/org/jacoco/ant/AbstractCoverageTask.java
index 18ef70c..8a9cbb7 100644
--- a/org.jacoco.ant/src/org/jacoco/ant/AbstractCoverageTask.java
+++ b/org.jacoco.ant/src/org/jacoco/ant/AbstractCoverageTask.java
@@ -120,6 +120,17 @@
 	}
 
 	/**
+	 * Sets whether classes from the bootstrap classloader should be
+	 * instrumented.
+	 * 
+	 * @param enabled
+	 *            <code>true</code> if bootstrap classes should be instrumented
+	 */
+	public void setIncludeBootstrapClasses(final boolean enabled) {
+		agentOptions.setIncludeBootstrapClasses(enabled);
+	}
+
+	/**
 	 * Sets the session identifier. Default is a auto-generated id
 	 * 
 	 * @param id
diff --git a/org.jacoco.core.test/src/org/jacoco/core/runtime/AgentOptionsTest.java b/org.jacoco.core.test/src/org/jacoco/core/runtime/AgentOptionsTest.java
index e64beaa..75e7641 100644
--- a/org.jacoco.core.test/src/org/jacoco/core/runtime/AgentOptionsTest.java
+++ b/org.jacoco.core.test/src/org/jacoco/core/runtime/AgentOptionsTest.java
@@ -43,6 +43,7 @@
 		assertEquals("", options.getExcludes());
 		assertEquals("sun.reflect.DelegatingClassLoader",
 				options.getExclClassloader());
+		assertFalse(options.getIncludeBootstrapClasses());
 		assertNull(options.getSessionId());
 		assertTrue(options.getDumpOnExit());
 		assertEquals(AgentOptions.OutputMode.file, options.getOutput());
@@ -74,6 +75,7 @@
 		properties.put("includes", "org.*:com.*");
 		properties.put("excludes", "*Test");
 		properties.put("exclclassloader", "org.jacoco.test.TestLoader");
+		properties.put("includebootstrapclasses", "true");
 		properties.put("sessionid", "testsession");
 		properties.put("dumponexit", "false");
 		properties.put("output", "tcpserver");
@@ -89,6 +91,7 @@
 		assertEquals("org.*:com.*", options.getIncludes());
 		assertEquals("*Test", options.getExcludes());
 		assertEquals("org.jacoco.test.TestLoader", options.getExclClassloader());
+		assertTrue(options.getIncludeBootstrapClasses());
 		assertEquals("testsession", options.getSessionId());
 		assertFalse(options.getDumpOnExit());
 		assertEquals(AgentOptions.OutputMode.tcpserver, options.getOutput());
@@ -185,6 +188,34 @@
 	}
 
 	@Test
+	public void testGetIncludeBootstrapClassesTrue() {
+		AgentOptions options = new AgentOptions("includebootstrapclasses=true");
+		assertTrue(options.getIncludeBootstrapClasses());
+	}
+
+	@Test
+	public void testGetIncludeBootstrapClassesFalse() {
+		AgentOptions options = new AgentOptions("includebootstrapclasses=false");
+		assertFalse(options.getIncludeBootstrapClasses());
+	}
+
+	@Test
+	public void testSetIncludeBootstrapClassesTrue() {
+		AgentOptions options = new AgentOptions();
+		options.setIncludeBootstrapClasses(true);
+		assertTrue(options.getIncludeBootstrapClasses());
+		assertEquals("includebootstrapclasses=true", options.toString());
+	}
+
+	@Test
+	public void testSetIncludeBootstrapClassesFalse() {
+		AgentOptions options = new AgentOptions();
+		options.setIncludeBootstrapClasses(false);
+		assertFalse(options.getIncludeBootstrapClasses());
+		assertEquals("includebootstrapclasses=false", options.toString());
+	}
+
+	@Test
 	public void testGetSessionId() {
 		AgentOptions options = new AgentOptions("sessionid=testsession");
 		assertEquals("testsession", options.getSessionId());
diff --git a/org.jacoco.core/src/org/jacoco/core/runtime/AgentOptions.java b/org.jacoco.core/src/org/jacoco/core/runtime/AgentOptions.java
index f45a35a..a36fe4f 100644
--- a/org.jacoco.core/src/org/jacoco/core/runtime/AgentOptions.java
+++ b/org.jacoco.core/src/org/jacoco/core/runtime/AgentOptions.java
@@ -72,6 +72,13 @@
 	public static final String EXCLCLASSLOADER = "exclclassloader";
 
 	/**
+	 * Specifies whether also classes from the bootstrap classloader should be
+	 * instrumented. Use this feature with caution, it needs heavy
+	 * includes/excludes tuning. Default is <code>false</code>.
+	 */
+	public static final String INCLUDEBOOTSTRAPCLASSES = "includebootstrapclasses";
+
+	/**
 	 * Specifies a session identifier that is written with the execution data.
 	 * Without this parameter a random identifier is created by the agent.
 	 */
@@ -165,8 +172,9 @@
 	public static final String JMX = "jmx";
 
 	private static final Collection<String> VALID_OPTIONS = Arrays.asList(
-			DESTFILE, APPEND, INCLUDES, EXCLUDES, EXCLCLASSLOADER, SESSIONID,
-			DUMPONEXIT, OUTPUT, ADDRESS, PORT, CLASSDUMPDIR, JMX);
+			DESTFILE, APPEND, INCLUDES, EXCLUDES, EXCLCLASSLOADER,
+			INCLUDEBOOTSTRAPCLASSES, SESSIONID, DUMPONEXIT, OUTPUT, ADDRESS,
+			PORT, CLASSDUMPDIR, JMX);
 
 	private final Map<String, String> options;
 
@@ -335,6 +343,27 @@
 	}
 
 	/**
+	 * Returns whether classes from the bootstrap classloader should be
+	 * instrumented.
+	 * 
+	 * @return <code>true</code> if coverage data will be written on VM exit
+	 */
+	public boolean getIncludeBootstrapClasses() {
+		return getOption(INCLUDEBOOTSTRAPCLASSES, false);
+	}
+
+	/**
+	 * Sets whether classes from the bootstrap classloader should be
+	 * instrumented.
+	 * 
+	 * @param enabled
+	 *            <code>true</code> if bootstrap classes should be instrumented
+	 */
+	public void setIncludeBootstrapClasses(final boolean enabled) {
+		setOption(INCLUDEBOOTSTRAPCLASSES, enabled);
+	}
+
+	/**
 	 * Returns the session identifier.
 	 * 
 	 * @return session identifier
@@ -354,7 +383,7 @@
 	}
 
 	/**
-	 * Returns whether coverage data should be dumped on exit
+	 * Returns whether coverage data should be dumped on exit.
 	 * 
 	 * @return <code>true</code> if coverage data will be written on VM exit
 	 */
@@ -363,7 +392,7 @@
 	}
 
 	/**
-	 * Sets whether coverage data should be dumped on exit
+	 * Sets whether coverage data should be dumped on exit.
 	 * 
 	 * @param dumpOnExit
 	 *            <code>true</code> if coverage data should be written on VM
diff --git a/org.jacoco.doc/docroot/doc/agent.html b/org.jacoco.doc/docroot/doc/agent.html
index c3dec5f..5a1628d 100644
--- a/org.jacoco.doc/docroot/doc/agent.html
+++ b/org.jacoco.doc/docroot/doc/agent.html
@@ -117,6 +117,14 @@
       <td><code>sun.reflect.DelegatingClassLoader</code></td>
     </tr>
     <tr>
+      <td><code>includebootstrapclasses</code></td>
+      <td>Specifies whether also classes from the bootstrap classloader should
+          be instrumented. Use this feature with caution, it needs heavy
+          includes/excludes tuning. 
+      </td>
+      <td><code>false</code></td>
+    </tr>
+    <tr>
       <td><code>sessionid</code></td>
       <td>A session identifier that is written with the execution data. Without
           this parameter a random identifier is created by the agent.
diff --git a/org.jacoco.doc/docroot/doc/ant.html b/org.jacoco.doc/docroot/doc/ant.html
index 7400b7d..aac3473 100644
--- a/org.jacoco.doc/docroot/doc/ant.html
+++ b/org.jacoco.doc/docroot/doc/ant.html
@@ -205,6 +205,14 @@
       <td><code>sun.reflect.DelegatingClassLoader</code></td>
     </tr>
     <tr>
+      <td><code>includebootstrapclasses</code></td>
+      <td>Specifies whether also classes from the bootstrap classloader should
+          be instrumented. Use this feature with caution, it needs heavy
+          includes/excludes tuning. 
+      </td>
+      <td><code>false</code></td>
+    </tr>
+    <tr>
       <td><code>sessionid</code></td>
       <td>A session identifier that is written with the execution data. Without
           this parameter a random identifier is created by the agent.
diff --git a/org.jacoco.doc/docroot/doc/changes.html b/org.jacoco.doc/docroot/doc/changes.html
index 51679b6..9d5209a 100644
--- a/org.jacoco.doc/docroot/doc/changes.html
+++ b/org.jacoco.doc/docroot/doc/changes.html
@@ -20,6 +20,14 @@
 
 <h2>Snapshot Build @qualified.bundle.version@ (@build.date@)</h2>
 
+<h3>New Features</h3>
+<ul>
+  <li>New configuration option for the JaCoCo agent
+      <code>includebootstrapclasses</code> to also instrument classes from the
+      bootstrap class loader.
+      (GitHub <a href="https://github.com/jacoco/jacoco/issues/49">#49</a>).</li>
+</ul>
+
 <h2>Release 0.7.1 (2014/05/08)</h2>
 
 <h3>Fixed Bugs</h3>