Trac #74: Don't Required Class Registration at Runtime
diff --git a/org.jacoco.agent.rt.test/src/org/jacoco/agent/rt/CoverageTransformerTest.java b/org.jacoco.agent.rt.test/src/org/jacoco/agent/rt/CoverageTransformerTest.java
index 20ff79e..2e14021 100644
--- a/org.jacoco.agent.rt.test/src/org/jacoco/agent/rt/CoverageTransformerTest.java
+++ b/org.jacoco.agent.rt.test/src/org/jacoco/agent/rt/CoverageTransformerTest.java
@@ -128,7 +128,9 @@
}
private static class StubRuntime extends AbstractRuntime {
- public int generateDataAccessor(long classid, MethodVisitor mv) {
+
+ public int generateDataAccessor(long classid, String classname,
+ int probecount, MethodVisitor mv) {
return 0;
}
diff --git a/org.jacoco.core.test/src/org/jacoco/core/data/ExecutionDataStoreTest.java b/org.jacoco.core.test/src/org/jacoco/core/data/ExecutionDataStoreTest.java
index a86dcc5..369545a 100644
--- a/org.jacoco.core.test/src/org/jacoco/core/data/ExecutionDataStoreTest.java
+++ b/org.jacoco.core.test/src/org/jacoco/core/data/ExecutionDataStoreTest.java
@@ -68,8 +68,39 @@
nameOutput);
}
- @Test(expected = IllegalArgumentException.class)
- public void testIdClash() {
+ @Test
+ public void testGetWithoutCreate() {
+ boolean[] data = new boolean[] { false, false, true };
+ store.put(1000, "Sample", data);
+ assertSame(data, store.getData(Long.valueOf(1000), "Sample", 3));
+ }
+
+ @Test
+ public void testGetWithCreate() {
+ boolean[] data = store.getData(Long.valueOf(1000), "Sample", 3);
+ assertEquals(3, data.length, 0.0);
+ assertFalse(data[0]);
+ assertFalse(data[1]);
+ assertFalse(data[2]);
+ assertSame(data, store.getData(Long.valueOf(1000), "Sample", 3));
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testGetNegative1() {
+ boolean[] data = new boolean[] { false, false, true };
+ store.put(1000, "Sample", data);
+ assertSame(data, store.getData(Long.valueOf(1000), "Other", 3));
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testGetNegative2() {
+ boolean[] data = new boolean[] { false, false, true };
+ store.put(1000, "Sample", data);
+ assertSame(data, store.getData(Long.valueOf(1000), "Sample", 4));
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testPutNegative() {
boolean[] data = new boolean[0];
store.put(1000, "Sample1", data);
store.put(1000, "Sample2", data);
@@ -90,7 +121,7 @@
}
@Test(expected = IllegalStateException.class)
- public void testNegative1() {
+ public void testMergeNegative() {
boolean[] data1 = new boolean[] { false, false };
store.visitClassExecution(1000, "Sample", data1);
boolean[] data2 = new boolean[] { false, false, false };
diff --git a/org.jacoco.core.test/src/org/jacoco/core/instr/MethodInstrumenterTest.java b/org.jacoco.core.test/src/org/jacoco/core/instr/MethodInstrumenterTest.java
index 9df317b..11de689 100644
--- a/org.jacoco.core.test/src/org/jacoco/core/instr/MethodInstrumenterTest.java
+++ b/org.jacoco.core.test/src/org/jacoco/core/instr/MethodInstrumenterTest.java
@@ -14,10 +14,10 @@
import static org.junit.Assert.assertEquals;
-import org.jacoco.core.runtime.IExecutionDataAccessorGenerator;
import org.jacoco.core.test.MethodRecorder;
import org.junit.Before;
import org.junit.Test;
+import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
@@ -29,7 +29,7 @@
*/
public class MethodInstrumenterTest {
- private IExecutionDataAccessorGenerator accessorGenerator;
+ private IProbeArrayStrategy probeArrayStrategy;
private MethodInstrumenter instrumenter;
@@ -41,16 +41,20 @@
public void setup() {
actual = new MethodRecorder();
expected = new MethodRecorder();
- accessorGenerator = new IExecutionDataAccessorGenerator() {
+ probeArrayStrategy = new IProbeArrayStrategy() {
- public int generateDataAccessor(long classid, MethodVisitor mv) {
+ public int pushInstance(MethodVisitor mv) {
mv.visitMethodInsn(Opcodes.INVOKESTATIC, "Target",
"$jacocoInit", "()[Z");
return 1;
}
+
+ public void addMembers(ClassVisitor delegate) {
+ }
+
};
instrumenter = new MethodInstrumenter(actual, 0, "test", "()V",
- accessorGenerator, 0);
+ probeArrayStrategy);
}
void sampleReturn() {
diff --git a/org.jacoco.core.test/src/org/jacoco/core/runtime/ExecutionDataAccessTest.java b/org.jacoco.core.test/src/org/jacoco/core/runtime/ExecutionDataAccessTest.java
new file mode 100644
index 0000000..17492b8
--- /dev/null
+++ b/org.jacoco.core.test/src/org/jacoco/core/runtime/ExecutionDataAccessTest.java
@@ -0,0 +1,147 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2010 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
+ *
+ * $Id: $
+ *******************************************************************************/
+package org.jacoco.core.runtime;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertSame;
+
+import java.util.concurrent.Callable;
+
+import org.jacoco.core.data.ExecutionDataStore;
+import org.jacoco.core.test.TargetLoader;
+import org.junit.Before;
+import org.junit.Test;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+
+/**
+ * Unit tests for {@link ExecutionDataAccess}.
+ *
+ * @author Marc R. Hoffmann
+ * @version $Revision: $
+ */
+public class ExecutionDataAccessTest {
+
+ private ExecutionDataStore store;
+
+ private Object access;
+
+ @Before
+ public void setup() {
+ store = new ExecutionDataStore();
+ access = new ExecutionDataAccess(store);
+ }
+
+ @Test
+ public void testGetExecutionData1() {
+ Object[] args = new Object[] { Long.valueOf(123), "Foo",
+ Integer.valueOf(3) };
+ access.equals(args);
+ boolean[] data = (boolean[]) args[0];
+ assertEquals(3, data.length, 0.0);
+ assertFalse(data[0]);
+ assertFalse(data[1]);
+ assertFalse(data[2]);
+ assertSame(store.getData(Long.valueOf(123)), data);
+ assertEquals("Foo", store.getName(Long.valueOf(123)));
+ }
+
+ @Test
+ public void testGenerateArgumentArray() throws Exception {
+ final ClassWriter writer = new ClassWriter(0);
+ writer.visit(Opcodes.V1_5, Opcodes.ACC_PUBLIC, "Sample", null,
+ "java/lang/Object", new String[] { Type
+ .getInternalName(Callable.class) });
+
+ // Constructor
+ MethodVisitor mv = writer.visitMethod(Opcodes.ACC_PUBLIC, "<init>",
+ "()V", null, new String[0]);
+ mv.visitCode();
+ mv.visitVarInsn(Opcodes.ALOAD, 0);
+ mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object", "<init>",
+ "()V");
+ mv.visitInsn(Opcodes.RETURN);
+ mv.visitMaxs(1, 1);
+ mv.visitEnd();
+
+ // call()
+ mv = writer.visitMethod(Opcodes.ACC_PUBLIC, "call",
+ "()Ljava/lang/Object;", null, new String[0]);
+ mv.visitCode();
+ ExecutionDataAccess.generateArgumentArray(1000, "Sample", 15, mv);
+ mv.visitInsn(Opcodes.ARETURN);
+ mv.visitMaxs(5, 1);
+ mv.visitEnd();
+
+ writer.visitEnd();
+ final TargetLoader loader = new TargetLoader("Sample", writer
+ .toByteArray());
+ Callable<?> callable = (Callable<?>) loader.newTargetInstance();
+ final Object[] args = (Object[]) callable.call();
+ assertEquals(3, args.length, 0.0);
+ assertEquals(Long.valueOf(1000), args[0]);
+ assertEquals("Sample", args[1]);
+ assertEquals(Integer.valueOf(15), args[2]);
+ }
+
+ @Test
+ public void testGenerateAccessCall() throws Exception {
+ final boolean[] data = store.getData(Long.valueOf(1234), "Sample", 5);
+
+ final ClassWriter writer = new ClassWriter(0);
+ writer.visit(Opcodes.V1_5, Opcodes.ACC_PUBLIC, "Sample", null,
+ "java/lang/Object", new String[] { Type
+ .getInternalName(Callable.class) });
+
+ // Constructor
+ MethodVisitor mv = writer.visitMethod(Opcodes.ACC_PUBLIC, "<init>",
+ "(Ljava/lang/Object;)V", null, new String[0]);
+ mv.visitCode();
+ mv.visitVarInsn(Opcodes.ALOAD, 0);
+ mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object", "<init>",
+ "()V");
+ mv.visitVarInsn(Opcodes.ALOAD, 0);
+ mv.visitVarInsn(Opcodes.ALOAD, 1);
+ mv.visitFieldInsn(Opcodes.PUTFIELD, "Sample", "access",
+ "Ljava/lang/Object;");
+ mv.visitInsn(Opcodes.RETURN);
+ mv.visitMaxs(2, 2);
+ mv.visitEnd();
+
+ // call()
+ mv = writer.visitMethod(Opcodes.ACC_PUBLIC, "call",
+ "()Ljava/lang/Object;", null, new String[0]);
+ mv.visitCode();
+ mv.visitVarInsn(Opcodes.ALOAD, 0);
+ mv.visitFieldInsn(Opcodes.GETFIELD, "Sample", "access",
+ "Ljava/lang/Object;");
+ ExecutionDataAccess.generateAccessCall(1234, "Sample", 5, mv);
+ mv.visitInsn(Opcodes.ARETURN);
+ mv.visitMaxs(6, 1);
+ mv.visitEnd();
+
+ writer.visitField(Opcodes.ACC_PRIVATE, "access", "Ljava/lang/Object;",
+ null, null);
+
+ writer.visitEnd();
+ final TargetLoader loader = new TargetLoader("Sample", writer
+ .toByteArray());
+ Callable<?> callable = (Callable<?>) loader.getTargetClass()
+ .getConstructor(Object.class).newInstance(access);
+ assertSame(data, callable.call());
+ }
+
+}
diff --git a/org.jacoco.core.test/src/org/jacoco/core/runtime/MapAdapterTest.java b/org.jacoco.core.test/src/org/jacoco/core/runtime/MapAdapterTest.java
deleted file mode 100644
index 3e7ae37..0000000
--- a/org.jacoco.core.test/src/org/jacoco/core/runtime/MapAdapterTest.java
+++ /dev/null
@@ -1,110 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2009, 2010 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
- *
- * $Id: $
- *******************************************************************************/
-package org.jacoco.core.runtime;
-
-import static org.junit.Assert.assertSame;
-
-import java.util.Collections;
-import java.util.Map;
-
-import org.jacoco.core.data.ExecutionDataStore;
-import org.junit.Before;
-import org.junit.Test;
-
-/**
- * Unit tests for {@link MapAdapter}.
- *
- * @author Marc R. Hoffmann
- * @version $Revision: $
- */
-public class MapAdapterTest {
-
- private ExecutionDataStore store;
-
- private Map<Long, boolean[]> map;
-
- @Before
- public void setup() {
- store = new ExecutionDataStore();
- map = new MapAdapter(store);
- }
-
- @Test
- public void testGet() {
- boolean[] arr = new boolean[] { false, true, false };
- store.put(123, "Foo", arr);
- assertSame(arr, map.get(Long.valueOf(123)));
- }
-
- @Test(expected = IllegalStateException.class)
- public void testGetNegative() {
- map.get(Long.valueOf(123));
- }
-
- @Test(expected = UnsupportedOperationException.class)
- public void testClear() {
- map.clear();
- }
-
- @Test(expected = UnsupportedOperationException.class)
- public void testContainsKey() {
- map.containsKey(Long.valueOf(1));
- }
-
- @Test(expected = UnsupportedOperationException.class)
- public void testContainsValue() {
- map.containsValue(Long.valueOf(1));
- }
-
- @Test(expected = UnsupportedOperationException.class)
- public void testEntrySet() {
- map.entrySet();
- }
-
- @Test(expected = UnsupportedOperationException.class)
- public void testIsEmpty() {
- map.isEmpty();
- }
-
- @Test(expected = UnsupportedOperationException.class)
- public void testKeySet() {
- map.keySet();
- }
-
- @Test(expected = UnsupportedOperationException.class)
- public void testPut() {
- map.put(Long.valueOf(0), new boolean[0]);
- }
-
- @Test(expected = UnsupportedOperationException.class)
- public void testPutAll() {
- final Map<Long, boolean[]> other = Collections.emptyMap();
- map.putAll(other);
- }
-
- @Test(expected = UnsupportedOperationException.class)
- public void testRemove() {
- map.remove(Long.valueOf(0));
- }
-
- @Test(expected = UnsupportedOperationException.class)
- public void testValues() {
- map.values();
- }
-
- @Test(expected = UnsupportedOperationException.class)
- public void testSize() {
- map.size();
- }
-
-}
diff --git a/org.jacoco.core.test/src/org/jacoco/core/runtime/ModifiedSystemClassRuntimeTest.java b/org.jacoco.core.test/src/org/jacoco/core/runtime/ModifiedSystemClassRuntimeTest.java
index a87ac30..54cc51b 100644
--- a/org.jacoco.core.test/src/org/jacoco/core/runtime/ModifiedSystemClassRuntimeTest.java
+++ b/org.jacoco.core.test/src/org/jacoco/core/runtime/ModifiedSystemClassRuntimeTest.java
@@ -15,7 +15,6 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
@@ -23,9 +22,7 @@
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.Instrumentation;
import java.lang.reflect.Field;
-import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
-import java.util.Map;
import java.util.jar.JarFile;
import org.jacoco.core.test.TargetLoader;
@@ -42,8 +39,7 @@
@Override
IRuntime createRuntime() {
return new ModifiedSystemClassRuntime(
- ModifiedSystemClassRuntimeTest.class, "accessMethod",
- "dataField");
+ ModifiedSystemClassRuntimeTest.class, "accessField");
}
@Test
@@ -60,13 +56,8 @@
ModifiedSystemClassRuntime.createFor(inst, TARGET_CLASS_NAME);
}
- // these static members emulate the instrumented system class
-
- public static Map<Long, boolean[]> dataField;
-
- public static boolean[] accessMethod(long id) {
- return dataField.get(Long.valueOf(id));
- }
+ /** This static member emulate the instrumented system class. */
+ public static Object accessField;
private static final String TARGET_CLASS_NAME = "org/jacoco/core/runtime/ModifiedSystemClassRuntimeTest";
@@ -183,22 +174,8 @@
.getTargetClass();
// Check added field:
- final Field f = targetClass.getField("$jacocoData");
+ final Field f = targetClass.getField("$jacocoAccess");
assertEquals(Modifier.PUBLIC | Modifier.STATIC, f.getModifiers(), 0.0);
- assertEquals(Map.class, f.getType());
-
- // Check added method:
- final Method m = targetClass.getMethod("$jacocoGet", Long.TYPE);
- assertEquals(Modifier.PUBLIC | Modifier.STATIC, m.getModifiers(), 0.0);
- assertEquals(boolean[].class, m.getReturnType());
-
- // See whether the added code works with the runtime:
- final ModifiedSystemClassRuntime r = new ModifiedSystemClassRuntime(
- targetClass, "$jacocoGet", "$jacocoData");
- r.startup();
- boolean[] data = new boolean[] { true, false, false };
- r.registerClass(5555, "Foo", data);
- assertSame(data, m.invoke(null, Long.valueOf(5555)));
+ assertEquals(Object.class, f.getType());
}
-
}
diff --git a/org.jacoco.core.test/src/org/jacoco/core/runtime/RuntimeTestBase.java b/org.jacoco.core.test/src/org/jacoco/core/runtime/RuntimeTestBase.java
index 8edb0dc..e2c7d55 100644
--- a/org.jacoco.core.test/src/org/jacoco/core/runtime/RuntimeTestBase.java
+++ b/org.jacoco.core.test/src/org/jacoco/core/runtime/RuntimeTestBase.java
@@ -67,50 +67,47 @@
}
@Test
+ public void testDataAccessor() throws InstantiationException,
+ IllegalAccessException {
+ ITarget t = generateAndInstantiateClass(1234);
+ runtime.collect(storage, false);
+ storage.assertData(1234, t.get());
+ }
+
+ @Test
public void testReset() throws InstantiationException,
IllegalAccessException {
- final boolean[] data1 = new boolean[] { true, true, false };
- runtime.registerClass(1001, "Target", data1);
+ final ITarget target = generateAndInstantiateClass(1000);
+ target.a();
+ target.b();
+
runtime.reset();
- assertFalse(data1[0]);
- assertFalse(data1[1]);
- assertFalse(data1[2]);
+
+ final boolean[] data = target.get();
+ assertFalse(data[0]);
+ assertFalse(data[1]);
}
@Test
public void testCollectAndReset() throws InstantiationException,
IllegalAccessException {
- final boolean[] data1 = new boolean[] { true, true, true };
- runtime.registerClass(1001, "Target", data1);
+ final ITarget target = generateAndInstantiateClass(1001);
+ target.a();
+ target.b();
+
runtime.collect(storage, true);
+
+ final boolean[] data = target.get();
storage.assertSize(1);
- storage.assertData(1001, data1);
- assertFalse(data1[0]);
- assertFalse(data1[1]);
- assertFalse(data1[2]);
- }
-
- @Test(expected = IllegalStateException.class)
- public void testNoClassRegistration() throws InstantiationException,
- IllegalAccessException {
- generateAndInstantiateClass(1001);
- }
-
- @Test
- public void testDataAccessor() throws InstantiationException,
- IllegalAccessException {
- final boolean[] data = newStructure();
- runtime.registerClass(1001, "Target", data);
- ITarget t = generateAndInstantiateClass(1001);
- assertSame(data, t.get());
+ storage.assertData(1001, data);
+ assertFalse(data[0]);
+ assertFalse(data[1]);
}
@Test
public void testNoLocalVariablesInDataAccessor()
throws InstantiationException, IllegalAccessException {
- final boolean[] data = newStructure();
- runtime.registerClass(1001, "Target", data);
- runtime.generateDataAccessor(1001, new EmptyVisitor() {
+ runtime.generateDataAccessor(1001, "Target", 5, new EmptyVisitor() {
@Override
public void visitVarInsn(int opcode, int var) {
fail("No usage of local variables allowed.");
@@ -121,28 +118,24 @@
@Test
public void testExecutionRecording() throws InstantiationException,
IllegalAccessException {
- boolean[] data1 = newStructure();
- runtime.registerClass(1001, "Target", data1);
generateAndInstantiateClass(1001).a();
runtime.collect(storage, false);
storage.assertSize(1);
- storage.assertData(1001, data1);
- assertTrue(data1[0]);
- assertFalse(data1[1]);
+ final boolean[] data = storage.getData(1001);
+ assertTrue(data[0]);
+ assertFalse(data[1]);
}
@Test
public void testLoadSameClassTwice() throws InstantiationException,
IllegalAccessException {
- boolean[] data1 = newStructure();
- runtime.registerClass(1001, "Target", data1);
generateAndInstantiateClass(1001).a();
generateAndInstantiateClass(1001).b();
runtime.collect(storage, false);
storage.assertSize(1);
- storage.assertData(1001, data1);
- assertTrue(data1[0]);
- assertTrue(data1[0]);
+ final boolean[] data = storage.getData(1001);
+ assertTrue(data[0]);
+ assertTrue(data[0]);
}
/**
@@ -179,7 +172,8 @@
gen.invokeConstructor(Type.getType(Object.class), new Method("<init>",
"()V"));
gen.loadThis();
- final int size = runtime.generateDataAccessor(classid, gen);
+ final int size = runtime.generateDataAccessor(classid, className, 2,
+ gen);
gen.putField(classType, "data", GeneratorConstants.PROBEDATA_TYPE);
gen.returnValue();
gen.visitMaxs(size + 1, 0);
@@ -237,18 +231,14 @@
boolean[] get();
- // implementations just mark method 0 as executed
+ // implementations just mark probe 0 as executed
void a();
- // implementations just mark method 1 as executed
+ // implementations just mark probe 1 as executed
void b();
}
- private boolean[] newStructure() {
- return new boolean[] { false, false };
- }
-
private static class TestStorage implements IExecutionDataVisitor {
private final Map<Long, boolean[]> data = new HashMap<Long, boolean[]>();
diff --git a/org.jacoco.core/src/org/jacoco/core/data/ExecutionDataStore.java b/org.jacoco.core/src/org/jacoco/core/data/ExecutionDataStore.java
index 8cc6a3f..88e4602 100644
--- a/org.jacoco.core/src/org/jacoco/core/data/ExecutionDataStore.java
+++ b/org.jacoco.core/src/org/jacoco/core/data/ExecutionDataStore.java
@@ -100,6 +100,30 @@
}
/**
+ * Returns the coverage date for the class with the given identifier. If
+ * there is not data available under the given id a new entry is created.
+ *
+ * @param classid
+ * class identifier
+ * @param name
+ * VM name of the class
+ * @param probecount
+ * probe array length
+ * @return execution data
+ */
+ public boolean[] getData(final Long classid, final String name,
+ final int probecount) {
+ Entry entry = entries.get(classid);
+ if (entry == null) {
+ entry = new Entry(name, new boolean[probecount]);
+ entries.put(classid, entry);
+ } else {
+ entry.checkCompatibility(classid, name, probecount);
+ }
+ return entry.data;
+ }
+
+ /**
* Returns the vm name of the class with the given id.
*
* @param classid
@@ -163,18 +187,23 @@
this.data = data;
}
- void merge(final Long classid, final String newName,
- final boolean[] newData) {
- if (!newName.equals(name)) {
- throw new IllegalArgumentException(format(
+ void checkCompatibility(final Long classid, final String otherName,
+ final int otherLength) {
+ if (!otherName.equals(name)) {
+ throw new IllegalStateException(format(
"Duplicate id %x for classes %s and %s.", classid,
- name, newName));
+ name, otherName));
}
- if (data.length != newData.length) {
+ if (data.length != otherLength) {
throw new IllegalStateException(format(
"Incompatible execution data for class %s (id %s).",
name, classid));
}
+ }
+
+ void merge(final Long classid, final String newName,
+ final boolean[] newData) {
+ checkCompatibility(classid, newName, newData.length);
for (int i = 0; i < data.length; i++) {
if (!data[i]) {
data[i] = newData[i];
diff --git a/org.jacoco.core/src/org/jacoco/core/instr/ClassInstrumenter.java b/org.jacoco.core/src/org/jacoco/core/instr/ClassInstrumenter.java
index 9461c1a..d1b315d 100644
--- a/org.jacoco.core/src/org/jacoco/core/instr/ClassInstrumenter.java
+++ b/org.jacoco.core/src/org/jacoco/core/instr/ClassInstrumenter.java
@@ -15,7 +15,6 @@
import static java.lang.String.format;
import org.jacoco.core.runtime.IExecutionDataAccessorGenerator;
-import org.jacoco.core.runtime.IRuntime;
import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.Attribute;
import org.objectweb.asm.ClassVisitor;
@@ -30,44 +29,46 @@
* @author Marc R. Hoffmann
* @version $Revision: $
*/
-public class ClassInstrumenter extends BlockClassAdapter implements
- IExecutionDataAccessorGenerator {
+public class ClassInstrumenter extends BlockClassAdapter {
private final ClassVisitor delegate;
private final long id;
- private final IRuntime runtime;
+ private final IExecutionDataAccessorGenerator accessorGenerator;
+
+ private IProbeArrayStrategy probeArrayStrategy;
private String className;
- // Inlining the coverage data init method is required for interfaces because
- // we can't add additional methods here.
- private boolean inlineInit;
-
/**
* Emits a instrumented version of this class to the given class visitor.
*
* @param id
* unique identifier given to this class
- * @param runtime
- * this runtime will be used for instrumentation
+ * @param accessorGenerator
+ * this generator will be used for instrumentation
* @param cv
* next delegate in the visitor chain will receive the
* instrumented class
*/
- public ClassInstrumenter(final long id, final IRuntime runtime,
+ public ClassInstrumenter(final long id,
+ final IExecutionDataAccessorGenerator accessorGenerator,
final ClassVisitor cv) {
this.delegate = cv;
this.id = id;
- this.runtime = runtime;
+ this.accessorGenerator = accessorGenerator;
}
public void visit(final int version, final int access, final String name,
final String signature, final String superName,
final String[] interfaces) {
this.className = name;
- this.inlineInit = (access & Opcodes.ACC_INTERFACE) != 0;
+ if ((access & Opcodes.ACC_INTERFACE) == 0) {
+ this.probeArrayStrategy = new ClassTypeStrategy();
+ } else {
+ this.probeArrayStrategy = new InterfaceTypeStrategy();
+ }
delegate.visit(version, access, name, signature, superName, interfaces);
}
@@ -90,8 +91,8 @@
if (mv == null) {
return null;
}
- final IExecutionDataAccessorGenerator gen = inlineInit ? runtime : this;
- return new MethodInstrumenter(mv, access, name, desc, gen, id);
+ return new MethodInstrumenter(mv, access, name, desc,
+ probeArrayStrategy);
}
@Override
@@ -102,82 +103,10 @@
}
public void visitEnd() {
- if (!inlineInit) {
- createDataField();
- createInitMethod();
- }
- registerClass();
+ probeArrayStrategy.addMembers(delegate);
delegate.visitEnd();
}
- private void createDataField() {
- delegate.visitField(GeneratorConstants.DATAFIELD_ACC,
- GeneratorConstants.DATAFIELD_NAME,
- GeneratorConstants.PROBEDATA_TYPE.getDescriptor(), null, null);
- }
-
- private void createInitMethod() {
- final int access = GeneratorConstants.INITMETHOD_ACC;
- final MethodVisitor mv = delegate.visitMethod(access,
- GeneratorConstants.INITMETHOD_NAME,
- GeneratorConstants.INITMETHOD_DESC, null, null);
-
- // Load the value of the static data field:
- mv.visitFieldInsn(Opcodes.GETSTATIC, className,
- GeneratorConstants.DATAFIELD_NAME,
- GeneratorConstants.PROBEDATA_TYPE.getDescriptor());
- mv.visitInsn(Opcodes.DUP);
-
- // Stack[1]: [Z
- // Stack[0]: [Z
-
- // Skip initialization when we already have a data array:
- final Label alreadyInitialized = new Label();
- mv.visitJumpInsn(Opcodes.IFNONNULL, alreadyInitialized);
-
- // Stack[0]: [Z
-
- mv.visitInsn(Opcodes.POP);
- final int size = genInitializeDataField(mv);
-
- // Stack[0]: [Z
-
- // Return the method's block array:
- mv.visitLabel(alreadyInitialized);
- mv.visitInsn(Opcodes.ARETURN);
-
- mv.visitMaxs(Math.max(size, 2), 1); // Maximum local stack size is 2
- mv.visitEnd();
- }
-
- /**
- * Generates the byte code to initialize the static coverage data field
- * within this class.
- *
- * The code will push the [[Z data array on the operand stack.
- *
- * @param mv
- * generator to emit code to
- */
- private int genInitializeDataField(final MethodVisitor mv) {
- final int size = runtime.generateDataAccessor(id, mv);
-
- // Stack[0]: [Z
-
- mv.visitInsn(Opcodes.DUP);
-
- // Stack[1]: [Z
- // Stack[0]: [Z
-
- mv.visitFieldInsn(Opcodes.PUTSTATIC, className,
- GeneratorConstants.DATAFIELD_NAME,
- GeneratorConstants.PROBEDATA_TYPE.getDescriptor());
-
- // Stack[0]: [Z
-
- return Math.max(size, 2); // Maximum local stack size is 2
- }
-
/**
* Ensures that the given member does not correspond to a internal member
* created by the instrumentation process. This would mean that the class
@@ -199,15 +128,6 @@
}
}
- /**
- * Create a execution data structure according to the structure of this
- * class and registers it with the runtime.
- */
- private void registerClass() {
- final boolean[] data = new boolean[getProbeCount()];
- runtime.registerClass(id, className, data);
- }
-
// Methods we simply delegate:
public AnnotationVisitor visitAnnotation(final String desc,
@@ -233,13 +153,108 @@
delegate.visitSource(source, debug);
}
- // === IExecutionDataAccessorGenerator ===
+ // === probe array strategies ===
- public int generateDataAccessor(final long classid, final MethodVisitor mv) {
- mv.visitMethodInsn(Opcodes.INVOKESTATIC, className,
- GeneratorConstants.INITMETHOD_NAME,
- GeneratorConstants.INITMETHOD_DESC);
- return 1;
+ private class ClassTypeStrategy implements IProbeArrayStrategy {
+
+ public int pushInstance(final MethodVisitor mv) {
+ mv.visitMethodInsn(Opcodes.INVOKESTATIC, className,
+ GeneratorConstants.INITMETHOD_NAME,
+ GeneratorConstants.INITMETHOD_DESC);
+ return 1;
+ }
+
+ public void addMembers(final ClassVisitor delegate) {
+ createDataField();
+ createInitMethod();
+ }
+
+ private void createDataField() {
+ delegate.visitField(GeneratorConstants.DATAFIELD_ACC,
+ GeneratorConstants.DATAFIELD_NAME,
+ GeneratorConstants.PROBEDATA_TYPE.getDescriptor(), null,
+ null);
+ }
+
+ private void createInitMethod() {
+ final MethodVisitor mv = delegate.visitMethod(
+ GeneratorConstants.INITMETHOD_ACC,
+ GeneratorConstants.INITMETHOD_NAME,
+ GeneratorConstants.INITMETHOD_DESC, null, null);
+
+ // Load the value of the static data field:
+ mv.visitFieldInsn(Opcodes.GETSTATIC, className,
+ GeneratorConstants.DATAFIELD_NAME,
+ GeneratorConstants.PROBEDATA_TYPE.getDescriptor());
+ mv.visitInsn(Opcodes.DUP);
+
+ // Stack[1]: [Z
+ // Stack[0]: [Z
+
+ // Skip initialization when we already have a data array:
+ final Label alreadyInitialized = new Label();
+ mv.visitJumpInsn(Opcodes.IFNONNULL, alreadyInitialized);
+
+ // Stack[0]: [Z
+
+ mv.visitInsn(Opcodes.POP);
+ final int size = genInitializeDataField(mv);
+
+ // Stack[0]: [Z
+
+ // Return the method's block array:
+ mv.visitLabel(alreadyInitialized);
+ mv.visitInsn(Opcodes.ARETURN);
+
+ mv.visitMaxs(Math.max(size, 2), 1); // Maximum local stack size is 2
+ mv.visitEnd();
+ }
+
+ /**
+ * Generates the byte code to initialize the static coverage data field
+ * within this class.
+ *
+ * The code will push the [[Z data array on the operand stack.
+ *
+ * @param mv
+ * generator to emit code to
+ */
+ private int genInitializeDataField(final MethodVisitor mv) {
+ final int size = accessorGenerator.generateDataAccessor(id,
+ className, getProbeCount(), mv);
+
+ // Stack[0]: [Z
+
+ mv.visitInsn(Opcodes.DUP);
+
+ // Stack[1]: [Z
+ // Stack[0]: [Z
+
+ mv.visitFieldInsn(Opcodes.PUTSTATIC, className,
+ GeneratorConstants.DATAFIELD_NAME,
+ GeneratorConstants.PROBEDATA_TYPE.getDescriptor());
+
+ // Stack[0]: [Z
+
+ return Math.max(size, 2); // Maximum local stack size is 2
+ }
+ }
+
+ private class InterfaceTypeStrategy implements IProbeArrayStrategy {
+
+ public int pushInstance(final MethodVisitor mv) {
+ // TODO As we don't know the actual probe count at this point in
+ // time use a hard coded value of 64. This is typically be way too
+ // big and will break static initializers in interfaces with more
+ // than 64 blocks. So this has to be replaced with the actual probe
+ // count.
+ final int size = accessorGenerator.generateDataAccessor(id,
+ className, 64, mv);
+ return size;
+ }
+
+ public void addMembers(final ClassVisitor delegate) {
+ }
}
}
diff --git a/org.jacoco.core/src/org/jacoco/core/instr/IProbeArrayStrategy.java b/org.jacoco.core/src/org/jacoco/core/instr/IProbeArrayStrategy.java
new file mode 100644
index 0000000..34f386f
--- /dev/null
+++ b/org.jacoco.core/src/org/jacoco/core/instr/IProbeArrayStrategy.java
@@ -0,0 +1,45 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2010 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
+ *
+ * $Id: $
+ *******************************************************************************/
+package org.jacoco.core.instr;
+
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.MethodVisitor;
+
+/**
+ * Strategies to retrieve the probe array instance for each method within a
+ * type. This abstraction is required as we need to follow a different strategy
+ * depending on whether the instrumented type is a class or interface.
+ *
+ * @author Marc R. Hoffmann
+ * @version $Revision: $
+ */
+interface IProbeArrayStrategy {
+
+ /**
+ * Creates code that pushes the probe array instance on the operand stack.
+ *
+ * @param mv
+ * visitor to create code
+ * @return maximum stack size required by the generated code
+ */
+ int pushInstance(MethodVisitor mv);
+
+ /**
+ * Adds additional class members required by this strategy.
+ *
+ * @param delegate
+ * visitor to create fields and classes
+ */
+ void addMembers(ClassVisitor delegate);
+
+}
diff --git a/org.jacoco.core/src/org/jacoco/core/instr/MethodInstrumenter.java b/org.jacoco.core/src/org/jacoco/core/instr/MethodInstrumenter.java
index d60a6b6..34ac71d 100644
--- a/org.jacoco.core/src/org/jacoco/core/instr/MethodInstrumenter.java
+++ b/org.jacoco.core/src/org/jacoco/core/instr/MethodInstrumenter.java
@@ -12,7 +12,6 @@
*******************************************************************************/
package org.jacoco.core.instr;
-import org.jacoco.core.runtime.IExecutionDataAccessorGenerator;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.commons.GeneratorAdapter;
@@ -24,12 +23,10 @@
* @author Marc R. Hoffmann
* @version $Revision: $
*/
-public class MethodInstrumenter extends GeneratorAdapter implements
+class MethodInstrumenter extends GeneratorAdapter implements
IBlockMethodVisitor {
- private final IExecutionDataAccessorGenerator accessorGenerator;
-
- private final long classid;
+ private final IProbeArrayStrategy probeArrayStrategy;
private int accessorStackSize;
@@ -46,18 +43,14 @@
* name of the method
* @param desc
* description of the method
- * @param accessorGenerator
- * the current coverage runtime
- * @param classid
- * the id of the enclosing type
+ * @param probeArrayStrategy
+ * strategy to get access to the probe array
*/
public MethodInstrumenter(final MethodVisitor mv, final int access,
final String name, final String desc,
- final IExecutionDataAccessorGenerator accessorGenerator,
- final long classid) {
+ final IProbeArrayStrategy probeArrayStrategy) {
super(mv, access, name, desc);
- this.accessorGenerator = accessorGenerator;
- this.classid = classid;
+ this.probeArrayStrategy = probeArrayStrategy;
}
@Override
@@ -65,7 +58,7 @@
super.visitCode();
// At the very beginning of the method we load the boolean[] array into
// a local variable that stores the probes for this class.
- accessorStackSize = accessorGenerator.generateDataAccessor(classid, mv);
+ accessorStackSize = probeArrayStrategy.pushInstance(mv);
// Stack[0]: [Z
diff --git a/org.jacoco.core/src/org/jacoco/core/runtime/AbstractRuntime.java b/org.jacoco.core/src/org/jacoco/core/runtime/AbstractRuntime.java
index 5d34dd7..65a3800 100644
--- a/org.jacoco.core/src/org/jacoco/core/runtime/AbstractRuntime.java
+++ b/org.jacoco.core/src/org/jacoco/core/runtime/AbstractRuntime.java
@@ -26,11 +26,15 @@
/** store for execution data */
protected final ExecutionDataStore store;
+ /** access for this runtime instance */
+ protected final ExecutionDataAccess access;
+
/**
* Creates a new runtime.
*/
protected AbstractRuntime() {
store = new ExecutionDataStore();
+ access = new ExecutionDataAccess(store);
}
public final void collect(final IExecutionDataVisitor visitor,
@@ -43,11 +47,6 @@
}
}
- public final void registerClass(final long classid, final String name,
- final boolean[] data) {
- store.put(classid, name, data);
- }
-
public final void reset() {
synchronized (store) {
store.reset();
diff --git a/org.jacoco.core/src/org/jacoco/core/runtime/ExecutionDataAccess.java b/org.jacoco.core/src/org/jacoco/core/runtime/ExecutionDataAccess.java
new file mode 100644
index 0000000..95f30a6
--- /dev/null
+++ b/org.jacoco.core/src/org/jacoco/core/runtime/ExecutionDataAccess.java
@@ -0,0 +1,166 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2010 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
+ *
+ * $Id: $
+ *******************************************************************************/
+package org.jacoco.core.runtime;
+
+import java.util.ListIterator;
+
+import org.jacoco.core.data.ExecutionDataStore;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+/**
+ * This class implements the access from instrumented classes to execution data
+ * storage in the runtime. Instead of directly referencing JaCoCo implementation
+ * classes the access is decoupled through a JRE API. This avoids dependencies
+ * from instrumented classes on JaCoCo APIs.
+ *
+ * The JRE interface method used is {@link Object#equals(Object)} where the
+ * passed argument is an {@link Object} array containing the class id (
+ * {@link Long}), the class vm name ({@link String}) and the probe count (
+ * {@link Integer}). After the method call the probe array instance is stored in
+ * the first slot of the {@link Object} array.
+ *
+ * @author Marc R. Hoffmann
+ * @version $Revision: $
+ */
+class ExecutionDataAccess {
+
+ private final ExecutionDataStore store;
+
+ ExecutionDataAccess(final ExecutionDataStore store) {
+ this.store = store;
+ }
+
+ /**
+ * Retrieves the execution probe array for a given class. The passed
+ * {@link Object} array instance is used for parameters and the return value
+ * as follows. Call parameters:
+ *
+ * <ul>
+ * <li>args[0]: class id ({@link Long})
+ * <li>args[1]: vm class name ({@link String})
+ * <li>args[2]: probe count ({@link Integer})
+ * </ul>
+ *
+ * Return value:
+ *
+ * <ul>
+ * <li>args[0]: probe array (<code>boolean[]</code>)
+ * </ul>
+ *
+ * @param args
+ * parameter array of length 3
+ */
+ public void getExecutionData(final Object[] args) {
+ final Long classid = (Long) args[0];
+ final String name = (String) args[1];
+ final int probecount = ((Integer) args[2]).intValue();
+ synchronized (store) {
+ args[0] = store.getData(classid, name, probecount);
+ }
+ }
+
+ /**
+ * Generates code that creates the argument array for the
+ * <code>getExecutionData()</code> method. The array instance is left on the
+ * operand stack. The generated code requires a stack size of 5.
+ *
+ * @param classid
+ * class identifier
+ * @param classname
+ * VM class name
+ * @param probecount
+ * probe count for this class
+ * @param mv
+ * visitor to emit generated code
+ */
+ public static void generateArgumentArray(final long classid,
+ final String classname, final int probecount, final MethodVisitor mv) {
+ mv.visitInsn(Opcodes.ICONST_3);
+ mv.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/Object");
+
+ // Class Id:
+ mv.visitInsn(Opcodes.DUP);
+ mv.visitInsn(Opcodes.ICONST_0);
+ mv.visitLdcInsn(Long.valueOf(classid));
+ mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/Long", "valueOf",
+ "(J)Ljava/lang/Long;");
+ mv.visitInsn(Opcodes.AASTORE);
+
+ // Class Name:
+ mv.visitInsn(Opcodes.DUP);
+ mv.visitInsn(Opcodes.ICONST_1);
+ mv.visitLdcInsn(classname);
+ mv.visitInsn(Opcodes.AASTORE);
+
+ // Probe Count:
+ mv.visitInsn(Opcodes.DUP);
+ mv.visitInsn(Opcodes.ICONST_2);
+ mv.visitLdcInsn(Integer.valueOf(probecount));
+ mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/Integer",
+ "valueOf", "(I)Ljava/lang/Integer;");
+ mv.visitInsn(Opcodes.AASTORE);
+ }
+
+ /**
+ * Generates the code that calls the runtime data access through the JRE API
+ * method {@link ListIterator#add(Object)}. The code pops a
+ * {@link ListIterator} instance from the stack and pushes the probe array
+ * of type <code>boolean[]</code> on the operand stack. The generated code
+ * requires a stack size of 6.
+ *
+ * @param classid
+ * @param classname
+ * @param probecount
+ * @param mv
+ */
+ public static void generateAccessCall(final long classid,
+ final String classname, final int probecount, final MethodVisitor mv) {
+ // stack[0]: Ljava/util/ListIterator;
+
+ generateArgumentArray(classid, classname, probecount, mv);
+
+ // stack[1]: [Ljava/lang/Object;
+ // stack[0]: Ljava/util/ListIterator;
+
+ mv.visitInsn(Opcodes.DUP_X1);
+
+ // stack[2]: [Ljava/lang/Object;
+ // stack[1]: Ljava/util/ListIterator;
+ // stack[0]: [Ljava/lang/Object;
+
+ mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Object", "equals",
+ "(Ljava/lang/Object;)Z");
+ mv.visitInsn(Opcodes.POP);
+
+ // stack[0]: [Ljava/lang/Object;
+
+ mv.visitInsn(Opcodes.ICONST_0);
+ mv.visitInsn(Opcodes.AALOAD);
+
+ // stack[0]: [Z
+
+ mv.visitTypeInsn(Opcodes.CHECKCAST, "[Z");
+ }
+
+ /**
+ * In violation of the regular semantic of {@link Object#equals(Object)}
+ * this implementation is used as the interface to the execution data store.
+ */
+ @Override
+ public boolean equals(final Object o) {
+ getExecutionData((Object[]) o);
+ return false;
+ }
+
+}
diff --git a/org.jacoco.core/src/org/jacoco/core/runtime/IExecutionDataAccessorGenerator.java b/org.jacoco.core/src/org/jacoco/core/runtime/IExecutionDataAccessorGenerator.java
index 4358d90..ae8f3e9 100644
--- a/org.jacoco.core/src/org/jacoco/core/runtime/IExecutionDataAccessorGenerator.java
+++ b/org.jacoco.core/src/org/jacoco/core/runtime/IExecutionDataAccessorGenerator.java
@@ -18,7 +18,7 @@
* The instrumented classes need a piece of code that obtains a
* <code>boolean[]</code> instance from the runtime. The mechanism is runtime
* specific and therefore abstracted by this interface. Implementations are
- * therefore provided by {@link IRuntime} implementations and are used by the
+ * provided by {@link IRuntime} implementations and are used by the
* instrumentation process.
*
* @author Marc R. Hoffmann
@@ -40,11 +40,16 @@
*
* @param classid
* identifier of the class
+ * @param classname
+ * VM class name
+ * @param probecount
+ * probe count for this class
* @param mv
* code output
* @return additional stack size required by the implementation, including
* the instance pushed to the stack
*/
- public int generateDataAccessor(long classid, MethodVisitor mv);
+ public int generateDataAccessor(final long classid, final String classname,
+ final int probecount, MethodVisitor mv);
}
diff --git a/org.jacoco.core/src/org/jacoco/core/runtime/IRuntime.java b/org.jacoco.core/src/org/jacoco/core/runtime/IRuntime.java
index 1db5298..aa10f0b 100644
--- a/org.jacoco.core/src/org/jacoco/core/runtime/IRuntime.java
+++ b/org.jacoco.core/src/org/jacoco/core/runtime/IRuntime.java
@@ -39,20 +39,6 @@
public void shutdown();
/**
- * Before a particular class gets loaded, its execution data structure must
- * be registered with the runtime through this method. This method must only
- * be called between {@link #startup()} and {@link #shutdown()}.
- *
- * @param classid
- * identifier of the class
- * @param name
- * VM name of the class
- * @param data
- * execution data structure for this class
- */
- public void registerClass(long classid, final String name, boolean[] data);
-
- /**
* Collects the current execution data and writes it to the given
* {@link IExecutionDataVisitor} object. This method must only be called
* between {@link #startup()} and {@link #shutdown()}.
diff --git a/org.jacoco.core/src/org/jacoco/core/runtime/LoggerRuntime.java b/org.jacoco.core/src/org/jacoco/core/runtime/LoggerRuntime.java
index ada12ea..68cf0e9 100644
--- a/org.jacoco.core/src/org/jacoco/core/runtime/LoggerRuntime.java
+++ b/org.jacoco.core/src/org/jacoco/core/runtime/LoggerRuntime.java
@@ -62,12 +62,15 @@
return l;
}
- public int generateDataAccessor(final long classid, final MethodVisitor mv) {
+ public int generateDataAccessor(final long classid, final String classname,
+ final int probecount, final MethodVisitor mv) {
// The data accessor performs the following steps:
//
- // final Object[] args = new Object[1];
+ // final Object[] args = new Object[3];
// args[0] = Long.valueOf(classid);
+ // args[1] = classname;
+ // args[2] = Integer.valueOf(probecount);
// Logger.getLogger(CHANNEL).log(Level.INFO, key, args);
// final byte[] probedata = (byte[]) args[0];
//
@@ -77,34 +80,7 @@
// 1. Create parameter array:
- mv.visitInsn(Opcodes.ICONST_1);
- mv.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/Object");
-
- // Stack[0]: [Ljava/lang/Object;
-
- mv.visitInsn(Opcodes.DUP);
-
- // Stack[1]: [Ljava/lang/Object;
- // Stack[0]: [Ljava/lang/Object;
-
- mv.visitInsn(Opcodes.ICONST_0);
- mv.visitLdcInsn(Long.valueOf(classid));
-
- // Stack[4]: J
- // Stack[3]: .
- // Stack[2]: I
- // Stack[1]: [Ljava/lang/Object;
- // Stack[0]: [Ljava/lang/Object;
-
- mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/Long", "valueOf",
- "(J)Ljava/lang/Long;");
-
- // Stack[3]: Ljava/lang/Long;
- // Stack[2]: I
- // Stack[1]: [Ljava/lang/Object;
- // Stack[0]: [Ljava/lang/Object;
-
- mv.visitInsn(Opcodes.AASTORE);
+ ExecutionDataAccess.generateArgumentArray(classid, classname, probecount, mv);
// Stack[0]: [Ljava/lang/Object;
@@ -196,16 +172,7 @@
@Override
public void publish(final LogRecord record) {
if (key.equals(record.getMessage())) {
- final Object[] params = record.getParameters();
- final Long id = (Long) params[0];
- synchronized (store) {
- final boolean[] data = store.getData(id);
- if (data == null) {
- throw new IllegalStateException(String.format(
- "Unknown class id %x.", id));
- }
- params[0] = data;
- }
+ access.getExecutionData(record.getParameters());
}
}
diff --git a/org.jacoco.core/src/org/jacoco/core/runtime/MapAdapter.java b/org.jacoco.core/src/org/jacoco/core/runtime/MapAdapter.java
deleted file mode 100644
index 378a55a..0000000
--- a/org.jacoco.core/src/org/jacoco/core/runtime/MapAdapter.java
+++ /dev/null
@@ -1,93 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2009, 2010 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
- *
- * $Id: $
- *******************************************************************************/
-package org.jacoco.core.runtime;
-
-import java.util.Collection;
-import java.util.Map;
-import java.util.Set;
-
-import org.jacoco.core.data.ExecutionDataStore;
-
-/**
- * Utility class that wraps read-access to an {@link ExecutionDataStore} in a
- * {@link Map} interface. The map interface can then safely be referenced by
- * instrumented code.
- *
- * @author Marc R. Hoffmann
- * @version $Revision: $
- */
-class MapAdapter implements Map<Long, boolean[]> {
-
- private final ExecutionDataStore store;
-
- MapAdapter(final ExecutionDataStore store) {
- this.store = store;
- }
-
- public boolean[] get(final Object key) {
- final Long id = (Long) key;
- synchronized (store) {
- final boolean[] data = store.getData(id);
- if (data == null) {
- throw new IllegalStateException(String.format(
- "Unknown class id %x.", id));
- }
- return data;
- }
- }
-
- public void clear() {
- throw new UnsupportedOperationException();
- }
-
- public boolean containsKey(final Object key) {
- throw new UnsupportedOperationException();
- }
-
- public boolean containsValue(final Object value) {
- throw new UnsupportedOperationException();
- }
-
- public Set<Entry<Long, boolean[]>> entrySet() {
- throw new UnsupportedOperationException();
- }
-
- public boolean isEmpty() {
- throw new UnsupportedOperationException();
- }
-
- public Set<Long> keySet() {
- throw new UnsupportedOperationException();
- }
-
- public boolean[] put(final Long key, final boolean[] value) {
- throw new UnsupportedOperationException();
- }
-
- public void putAll(final Map<? extends Long, ? extends boolean[]> t) {
- throw new UnsupportedOperationException();
- }
-
- public boolean[] remove(final Object key) {
- throw new UnsupportedOperationException();
- }
-
- public Collection<boolean[]> values() {
- throw new UnsupportedOperationException();
- }
-
- public int size() {
- throw new UnsupportedOperationException();
- }
-
-}
diff --git a/org.jacoco.core/src/org/jacoco/core/runtime/ModifiedSystemClassRuntime.java b/org.jacoco.core/src/org/jacoco/core/runtime/ModifiedSystemClassRuntime.java
index 2ff3550..a8ac39a 100644
--- a/org.jacoco.core/src/org/jacoco/core/runtime/ModifiedSystemClassRuntime.java
+++ b/org.jacoco.core/src/org/jacoco/core/runtime/ModifiedSystemClassRuntime.java
@@ -38,55 +38,48 @@
*/
public class ModifiedSystemClassRuntime extends AbstractRuntime {
+ private static final String ACCESS_FIELD_TYPE = "Ljava/lang/Object;";
+
private final Class<?> systemClass;
private final String systemClassName;
- private final String accessMethod;
-
- private final String dataField;
+ private final String accessFieldName;
/**
* Creates a new runtime based on the given class and members.
*
* @param systemClass
* system class that contains the execution data
- * @param accessMethod
- * name of the public static access method
- * @param dataField
- * name of the public static data field
+ * @param accessFieldName
+ * name of the public static runtime access field
*
*/
public ModifiedSystemClassRuntime(final Class<?> systemClass,
- final String accessMethod, final String dataField) {
+ final String accessFieldName) {
this.systemClass = systemClass;
this.systemClassName = systemClass.getName().replace('.', '/');
- this.accessMethod = accessMethod;
- this.dataField = dataField;
+ this.accessFieldName = accessFieldName;
}
public void startup() throws Exception {
- final Field field = systemClass.getField(dataField);
- field.set(null, new MapAdapter(store));
+ final Field field = systemClass.getField(accessFieldName);
+ field.set(null, new ExecutionDataAccess(store));
}
public void shutdown() {
// nothing to do
}
- public int generateDataAccessor(final long classid, final MethodVisitor mv) {
+ public int generateDataAccessor(final long classid, final String classname,
+ final int probecount, final MethodVisitor mv) {
- mv.visitLdcInsn(Long.valueOf(classid));
+ mv.visitFieldInsn(Opcodes.GETSTATIC, systemClassName, accessFieldName,
+ ACCESS_FIELD_TYPE);
- // Stack[1]: J
- // Stack[0]: .
+ ExecutionDataAccess.generateAccessCall(classid, classname, probecount, mv);
- mv.visitMethodInsn(Opcodes.INVOKESTATIC, systemClassName, accessMethod,
- "(J)[Z");
-
- // Stack[0]: [Z
-
- return 2;
+ return 6;
}
/**
@@ -105,7 +98,7 @@
*/
public static IRuntime createFor(final Instrumentation inst,
final String className) throws ClassNotFoundException {
- return createFor(inst, className, "$jacocoGet", "$jacocoData");
+ return createFor(inst, className, "$jacocoAccess");
}
/**
@@ -117,18 +110,16 @@
* instrumentation interface
* @param className
* VM name of the class to use
- * @param accessMethod
- * name of the added access method
- * @param dataField
- * name of the added data field
+ * @param accessFieldName
+ * name of the added runtime access field
* @return new runtime instance
*
* @throws ClassNotFoundException
* id the given class can not be found
*/
public static IRuntime createFor(final Instrumentation inst,
- final String className, final String accessMethod,
- final String dataField) throws ClassNotFoundException {
+ final String className, final String accessFieldName)
+ throws ClassNotFoundException {
final boolean[] instrumented = new boolean[] { false };
final ClassFileTransformer transformer = new ClassFileTransformer() {
public byte[] transform(final ClassLoader loader,
@@ -137,7 +128,7 @@
throws IllegalClassFormatException {
if (name.equals(className)) {
instrumented[0] = true;
- return instrument(source, accessMethod, dataField);
+ return instrument(source, accessFieldName);
}
return null;
}
@@ -149,7 +140,7 @@
final String msg = format("Class %s was not loaded.", className);
throw new RuntimeException(msg);
}
- return new ModifiedSystemClassRuntime(clazz, accessMethod, dataField);
+ return new ModifiedSystemClassRuntime(clazz, accessFieldName);
}
/**
@@ -158,33 +149,19 @@
*
* @param source
* class definition source
- * @param accessMethod
- * name of the access method
- * @param dataField
- * name of the data field
+ * @param accessFieldName
+ * name of the runtime access field
* @return instrumented version with added members
*/
public static byte[] instrument(final byte[] source,
- final String accessMethod, final String dataField) {
+ final String accessFieldName) {
final ClassReader reader = new ClassReader(source);
final ClassWriter writer = new ClassWriter(reader, 0);
reader.accept(new ClassAdapter(writer) {
- private String className;
-
- @Override
- public void visit(final int version, final int access,
- final String name, final String signature,
- final String superName, final String[] interfaces) {
- className = name;
- super.visit(version, access, name, signature, superName,
- interfaces);
- }
-
@Override
public void visitEnd() {
- createDataField(cv, dataField);
- createAccessMethod(cv, className, accessMethod, dataField);
+ createDataField(cv, accessFieldName);
super.visitEnd();
}
@@ -195,46 +172,7 @@
private static void createDataField(final ClassVisitor visitor,
final String dataField) {
visitor.visitField(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, dataField,
- "Ljava/util/Map;", "Ljava/util/Map<Ljava/lang/Long;[Z>;", null);
- }
-
- private static void createAccessMethod(final ClassVisitor visitor,
- final String className, final String accessMethod,
- final String dataField) {
- final int access = Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC;
- final String desc = "(J)[Z";
- final MethodVisitor mv = visitor.visitMethod(access, accessMethod,
- desc, null, null);
-
- mv.visitFieldInsn(Opcodes.GETSTATIC, className, dataField,
- "Ljava/util/Map;");
-
- // Stack[0]: Ljava/util/Map;
-
- mv.visitVarInsn(Opcodes.LLOAD, 0);
-
- // Stack[2]: J
- // Stack[1]: .
- // Stack[0]: Ljava/util/Map;
-
- mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/Long", "valueOf",
- "(J)Ljava/lang/Long;");
-
- // Stack[1]: Ljava/lang/Long;
- // Stack[0]: Ljava/util/Map;
-
- mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, "java/util/Map", "get",
- "(Ljava/lang/Object;)Ljava/lang/Object;");
-
- // Stack[0]: Ljava/lang/Object;
-
- mv.visitTypeInsn(Opcodes.CHECKCAST, "[Z");
-
- // Stack[0]: [Z
-
- mv.visitInsn(Opcodes.ARETURN);
- mv.visitMaxs(3, 2);
- mv.visitEnd();
+ ACCESS_FIELD_TYPE, null, null);
}
}
diff --git a/org.jacoco.core/src/org/jacoco/core/runtime/SystemPropertiesRuntime.java b/org.jacoco.core/src/org/jacoco/core/runtime/SystemPropertiesRuntime.java
index 338df48..6f467af 100644
--- a/org.jacoco.core/src/org/jacoco/core/runtime/SystemPropertiesRuntime.java
+++ b/org.jacoco.core/src/org/jacoco/core/runtime/SystemPropertiesRuntime.java
@@ -14,7 +14,6 @@
import java.util.Map;
-import org.jacoco.core.instr.GeneratorConstants;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
@@ -38,8 +37,6 @@
private final String key;
- private final Map<Long, boolean[]> dataAccess = new MapAdapter(store);
-
/**
* Creates a new runtime.
*/
@@ -47,8 +44,8 @@
this.key = KEYPREFIX + hashCode();
}
- public int generateDataAccessor(final long classid, final MethodVisitor mv) {
-
+ public int generateDataAccessor(final long classid, final String classname,
+ final int probecount, final MethodVisitor mv) {
mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/System",
"getProperties", "()Ljava/util/Properties;");
@@ -64,37 +61,15 @@
// Stack[0]: Ljava/lang/Object;
- mv.visitTypeInsn(Opcodes.CHECKCAST, "java/util/Map");
-
- // Stack[0]: Ljava/util/Map;
-
- mv.visitLdcInsn(Long.valueOf(classid));
-
- // Stack[2]: J
- // Stack[1]: .
- // Stack[0]: Ljava/util/Map;
-
- mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/Long", "valueOf",
- "(J)Ljava/lang/Long;");
-
- // Stack[1]: Ljava/lang/Long;
- // Stack[0]: Ljava/util/Map;
-
- mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, "java/util/Map", "get",
- "(Ljava/lang/Object;)Ljava/lang/Object;");
-
- // Stack[0]: Ljava/lang/Object;
-
- mv.visitTypeInsn(Opcodes.CHECKCAST, GeneratorConstants.PROBEDATA_TYPE
- .getInternalName());
+ ExecutionDataAccess.generateAccessCall(classid, classname, probecount, mv);
// Stack[0]: [Z
- return 3; // Maximum local stack size is 3
+ return 6; // Maximum local stack size is 3
}
public void startup() {
- System.getProperties().put(key, dataAccess);
+ System.getProperties().put(key, access);
}
public void shutdown() {
diff --git a/org.jacoco.doc/docroot/doc/changes.html b/org.jacoco.doc/docroot/doc/changes.html
index 7a3e14b..dbc1f91 100644
--- a/org.jacoco.doc/docroot/doc/changes.html
+++ b/org.jacoco.doc/docroot/doc/changes.html
@@ -28,7 +28,10 @@
<h3>API Changes</h3>
<ul>
- <li>Consistent usage of the term "Missed" instead of "NotCovered" in all APIs (Trac #72).</li>
+ <li>Consistent usage of the term "Missed" instead of "NotCovered" in all APIs
+ (Trac #72).</li>
+ <li>To support "off-line" instrumentation scenarios it is not required any
+ more to register instrumented classes with the runtime (Trac #74).</li>
</ul>
<h2>Release 0.3.1 (2010/02/09)</h2>
diff --git a/org.jacoco.doc/docroot/doc/implementation.html b/org.jacoco.doc/docroot/doc/implementation.html
index b9b6b7a..ec1be8c 100644
--- a/org.jacoco.doc/docroot/doc/implementation.html
+++ b/org.jacoco.doc/docroot/doc/implementation.html
@@ -143,10 +143,11 @@
<p>
The JaCoCo build renames all classes contained in the
<code>jacocoagent.jar</code> into classes with a
- <code>org.jacoco.<randomid></code> prefix, including the required ASM
- library classes. The identifier is created from a random number. As the agent
- does not provide any API, no one should be affected by this renaming. This
- trick also allows that JaCoCo tests can be verified with JaCoCo.
+ <code>org.jacoco.agent.rt_<randomid></code> prefix, including the
+ required ASM library classes. The identifier is created from a random number.
+ As the agent does not provide any API, no one should be affected by this
+ renaming. This trick also allows that JaCoCo tests can be verified with
+ JaCoCo.
</p>
@@ -217,41 +218,63 @@
<p class="intro">
Instrumented code typically gets a dependency to a coverage runtime which is
responsible for collecting and storing execution data. JaCoCo uses JRE types
- and interfaces only in generated instrumentation code.
+ only in generated instrumentation code.
</p>
<p>
-
-
Making a runtime library available to all instrumented classes can be a
painful or impossible task in frameworks that use their own class loading
mechanisms. Since Java 1.6 <code>java.lang.instrument.Instrumentation</code>
has an API to extends the bootsstrap loader. As our minimum target is Java 1.5
JaCoCo decouples the instrumented classes and the coverage runtime through
- official JRE API types only. Different approaches have been implemented and
- tested so far:
+ official JRE API types only. The instrumented classes communicate through the
+ <code>Object.equals(Object)</code> method with the runtime. A instrumented
+ class can retrieve its probe array instance with the following code. Note
+ that only JRE APIs are used:
+</p>
+
+
+<pre class="source">
+<span class="nr"> 1</span>Object access = ... // Retrieve instance
+<span class="nr"> 2</span>
+<span class="nr"> 3</span>Object[] args = new Object[3];
+<span class="nr"> 4</span>args[0] = Long.valueOf(8060044182221863588); // class id
+<span class="nr"> 5</span>args[1] = "com/example/MyClass"; // class name
+<span class="nr"> 6</span>args[2] = Integer.valueOf(24); // probe count
+<span class="nr"> 7</span>
+<span class="nr"> 8</span>access.equals(args);
+<span class="nr"> 9</span>
+<span class="nr"> 10</span>boolean[] probes = (boolean[]) args[0];
+</pre>
+
+<p>
+ The most tricky part takes place in line 1 and is not shown in the snippet
+ above. The object instance providing access to the coverage runtime through
+ its <code>equals()</code> method has to be obtained. Different approaches have
+ been implemented and tested so far:
</p>
<ul>
- <li><b><code>SystemPropertiesRuntime</code></b>: This approach stores a
- <code>java.util.Map</code> instance under a system property containing the
- execution data. This solution breaks the contract that system properties
- must only contain <code>java.lang.String</code> values and therefore causes
- trouble in applications that rely on this definition (e.g. Ant).</li>
+ <li><b><code>SystemPropertiesRuntime</code></b>: This approach stores the
+ object instance under a system property. This solution breaks the contract
+ that system properties must only contain <code>java.lang.String</code>
+ values and therefore causes trouble in applications that rely on this
+ definition (e.g. Ant).</li>
<li><b><code>LoggerRuntime</code></b>: Here we use a shared
- <code>java.util.logging.Logger</code> instance to report coverage data to.
- The coverage runtime registers a custom <code>Handler</code> to receive the
- data. This approach might break environments that install their own log
+ <code>java.util.logging.Logger</code> and communicate through the logging
+ parameter array instead of a <code>equals()</code> method. The coverage
+ runtime registers a custom <code>Handler</code> to receive the parameter
+ array. This approach might break environments that install their own log
managers (e.g. Glassfish).</li>
- <li><b><code>ModifiedSystemClassRuntime</code></b>: This approach adds
- additional APIs to existing JRE classes through instrumentation. Unlike the
- other methods above this is only possible for environments where a Java
+ <li><b><code>ModifiedSystemClassRuntime</code></b>: This approach adds a
+ public static field to an existing JRE class through instrumentation. Unlike
+ the other methods above this is only possible for environments where a Java
agent is active.</li>
</ul>
<p>
The current JaCoCo Java agent implementation uses the
- <code>ModifiedSystemClassRuntime</code> adding APIs to the class
+ <code>ModifiedSystemClassRuntime</code> adding a field to the class
<code>java.sql.Types</code>.
</p>