Trac #21: Code coverage for static initializers in interfaces.
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 8b89d8f..72bd259 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,9 +14,11 @@
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.MethodVisitor;
import org.objectweb.asm.Opcodes;
/**
@@ -27,6 +29,8 @@
*/
public class MethodInstrumenterTest {
+ private IExecutionDataAccessorGenerator accessorGenerator;
+
private MethodInstrumenter instrumenter;
private MethodRecorder expected;
@@ -37,8 +41,16 @@
public void setup() {
actual = new MethodRecorder();
expected = new MethodRecorder();
+ accessorGenerator = new IExecutionDataAccessorGenerator() {
+
+ public int generateDataAccessor(long classid, MethodVisitor mv) {
+ mv.visitMethodInsn(Opcodes.INVOKESTATIC, "Target",
+ "$jacocoInit", "()[Z");
+ return 1;
+ }
+ };
instrumenter = new MethodInstrumenter(actual, 0, "test", "()V",
- "Target");
+ accessorGenerator, 0);
}
void sampleReturn() {
diff --git a/org.jacoco.core.test/src/org/jacoco/core/test/validation/BooleanExpressionsTest.java b/org.jacoco.core.test/src/org/jacoco/core/test/validation/BooleanExpressionsTest.java
index 6eeba64..7095275 100644
--- a/org.jacoco.core.test/src/org/jacoco/core/test/validation/BooleanExpressionsTest.java
+++ b/org.jacoco.core.test/src/org/jacoco/core/test/validation/BooleanExpressionsTest.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright (c) 2009 Mountainminds GmbH & Co. KG and others
+ * Copyright (c) 2009, 2010 Mountainminds GmbH & Co. KG and others
* 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
@@ -30,6 +30,12 @@
super(Target02.class);
}
+ @Override
+ protected void run(final Class<?> targetClass) throws Exception {
+ final Object instance = targetClass.newInstance();
+ ((Runnable) instance).run();
+ }
+
@Test
public void testCoverageResult() {
diff --git a/org.jacoco.core.test/src/org/jacoco/core/test/validation/ControlStructuresTest.java b/org.jacoco.core.test/src/org/jacoco/core/test/validation/ControlStructuresTest.java
index 45f126c..6240e82 100644
--- a/org.jacoco.core.test/src/org/jacoco/core/test/validation/ControlStructuresTest.java
+++ b/org.jacoco.core.test/src/org/jacoco/core/test/validation/ControlStructuresTest.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright (c) 2009 Mountainminds GmbH & Co. KG and others
+ * Copyright (c) 2009, 2010 Mountainminds GmbH & Co. KG and others
* 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
@@ -31,6 +31,12 @@
super(Target01.class);
}
+ @Override
+ protected void run(final Class<?> targetClass) throws Exception {
+ final Object instance = targetClass.newInstance();
+ ((Runnable) instance).run();
+ }
+
@Test
public void testCoverageResult() {
diff --git a/org.jacoco.core.test/src/org/jacoco/core/test/validation/InterfaceClassInitializerTest.java b/org.jacoco.core.test/src/org/jacoco/core/test/validation/InterfaceClassInitializerTest.java
new file mode 100644
index 0000000..c32727d
--- /dev/null
+++ b/org.jacoco.core.test/src/org/jacoco/core/test/validation/InterfaceClassInitializerTest.java
@@ -0,0 +1,47 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2010 Mountainminds GmbH & Co. KG and others
+ * 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.test.validation;
+
+import static org.jacoco.core.analysis.ILines.FULLY_COVERED;
+
+import org.jacoco.core.test.validation.targets.Target04;
+import org.junit.Test;
+
+/**
+ * Tests of static initializer in interfaces.
+ *
+ * @author Marc R. Hoffmann
+ * @version $Revision: $
+ */
+public class InterfaceClassInitializerTest extends ValidationTestBase {
+
+ public InterfaceClassInitializerTest() {
+ super(Target04.class);
+ }
+
+ @Override
+ protected void run(final Class<?> targetClass) throws Exception {
+ // Force class initialization
+ targetClass.getField("CONST1").get(null);
+ }
+
+ @Test
+ public void testCoverageResult() {
+
+ // All constant fields are initialized:
+ assertLine("const1", FULLY_COVERED);
+ assertLine("const2", FULLY_COVERED);
+ assertLine("const2", FULLY_COVERED);
+ }
+
+}
diff --git a/org.jacoco.core.test/src/org/jacoco/core/test/validation/ValidationTestBase.java b/org.jacoco.core.test/src/org/jacoco/core/test/validation/ValidationTestBase.java
index da2d2c0..73c6b0d 100644
--- a/org.jacoco.core.test/src/org/jacoco/core/test/validation/ValidationTestBase.java
+++ b/org.jacoco.core.test/src/org/jacoco/core/test/validation/ValidationTestBase.java
@@ -36,7 +36,7 @@
* @author Marc R. Hoffmann
* @version $Revision: $
*/
-public class ValidationTestBase {
+public abstract class ValidationTestBase {
private static final String[] STATUS_NAME = new String[4];
@@ -47,7 +47,7 @@
STATUS_NAME[ILines.PARTLY_COVERED] = "PARTLY_COVERED";
}
- protected final Class<? extends Runnable> target;
+ protected final Class<?> target;
protected ClassCoverage classCoverage;
@@ -57,7 +57,7 @@
protected Source source;
- protected ValidationTestBase(final Class<? extends Runnable> target) {
+ protected ValidationTestBase(final Class<?> target) {
this.target = target;
}
@@ -76,15 +76,17 @@
runtime.startup();
final byte[] bytes = new Instrumenter(runtime).instrument(reader);
final TargetLoader loader = new TargetLoader(target, bytes);
- final Object instance = loader.getTargetClass().newInstance();
- ((Runnable) instance).run();
+ run(loader.getTargetClass());
final ExecutionDataStore store = new ExecutionDataStore();
runtime.collect(store, false);
runtime.shutdown();
return store;
}
- private void analyze(ClassReader reader, ExecutionDataStore store) {
+ protected abstract void run(final Class<?> targetClass) throws Exception;
+
+ private void analyze(final ClassReader reader,
+ final ExecutionDataStore store) {
final CoverageBuilder builder = new CoverageBuilder(store);
final Analyzer analyzer = new Analyzer(builder);
analyzer.analyze(reader);
diff --git a/org.jacoco.core.test/src/org/jacoco/core/test/validation/targets/Target04.java b/org.jacoco.core.test/src/org/jacoco/core/test/validation/targets/Target04.java
new file mode 100644
index 0000000..e44eb52
--- /dev/null
+++ b/org.jacoco.core.test/src/org/jacoco/core/test/validation/targets/Target04.java
@@ -0,0 +1,32 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2010 Mountainminds GmbH & Co. KG and others
+ * 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.test.validation.targets;
+
+import static org.jacoco.core.test.validation.targets.Stubs.i1;
+import static org.jacoco.core.test.validation.targets.Stubs.i2;
+
+/**
+ * This test target is an interface with a class initializer.
+ *
+ * @author Marc R. Hoffmann
+ * @version $Revision: $
+ */
+public interface Target04 {
+
+ public static final int CONST1 = i1(); // $line-const1$
+
+ public static final int CONST2 = i2(); // $line-const2$
+
+ public static final Object CONST3 = new Object(); // $line-const3$
+
+}
diff --git a/org.jacoco.core/src/org/jacoco/core/instr/Analyzer.java b/org.jacoco.core/src/org/jacoco/core/instr/Analyzer.java
index 3685853..9af8bd1 100644
--- a/org.jacoco.core/src/org/jacoco/core/instr/Analyzer.java
+++ b/org.jacoco.core/src/org/jacoco/core/instr/Analyzer.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright (c) 2009 Mountainminds GmbH & Co. KG and others
+ * Copyright (c) 2009, 2010 Mountainminds GmbH & Co. KG and others
* 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
@@ -26,7 +26,6 @@
import org.jacoco.core.data.IStructureVisitor;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
-import org.objectweb.asm.Opcodes;
/**
* Several APIs to analyze class structures.
@@ -68,10 +67,6 @@
* reader with class definitions
*/
public void analyze(final ClassReader reader) {
- if ((reader.getAccess() & Opcodes.ACC_INTERFACE) != 0) {
- return;
- }
-
final ClassVisitor visitor = createAnalyzingVisitor(CRC64
.checksum(reader.b));
reader.accept(visitor, 0);
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 e204d26..4552de0 100644
--- a/org.jacoco.core/src/org/jacoco/core/instr/ClassInstrumenter.java
+++ b/org.jacoco.core/src/org/jacoco/core/instr/ClassInstrumenter.java
@@ -14,6 +14,7 @@
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;
@@ -29,7 +30,8 @@
* @author Marc R. Hoffmann
* @version $Revision: $
*/
-public class ClassInstrumenter extends BlockClassAdapter {
+public class ClassInstrumenter extends BlockClassAdapter implements
+ IExecutionDataAccessorGenerator {
private final ClassVisitor delegate;
@@ -39,6 +41,10 @@
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.
*
@@ -61,6 +67,7 @@
final String signature, final String superName,
final String[] interfaces) {
this.className = name;
+ this.inlineInit = (access & Opcodes.ACC_INTERFACE) != 0;
delegate.visit(version, access, name, signature, superName, interfaces);
}
@@ -83,7 +90,8 @@
if (mv == null) {
return null;
}
- return new MethodInstrumenter(mv, access, name, desc, className);
+ final IExecutionDataAccessorGenerator gen = inlineInit ? runtime : this;
+ return new MethodInstrumenter(mv, access, name, desc, gen, id);
}
@Override
@@ -94,8 +102,10 @@
}
public void visitEnd() {
- createDataField();
- createInitMethod();
+ if (!inlineInit) {
+ createDataField();
+ createInitMethod();
+ }
registerClass();
delegate.visitEnd();
}
@@ -223,4 +233,13 @@
delegate.visitSource(source, debug);
}
+ // === IExecutionDataAccessorGenerator ===
+
+ public int generateDataAccessor(final long classid, final MethodVisitor mv) {
+ mv.visitMethodInsn(Opcodes.INVOKESTATIC, className,
+ GeneratorConstants.INITMETHOD_NAME,
+ GeneratorConstants.INITMETHOD_DESC);
+ return 1;
+ }
+
}
diff --git a/org.jacoco.core/src/org/jacoco/core/instr/Instrumenter.java b/org.jacoco.core/src/org/jacoco/core/instr/Instrumenter.java
index 0216925..de4b2e0 100644
--- a/org.jacoco.core/src/org/jacoco/core/instr/Instrumenter.java
+++ b/org.jacoco.core/src/org/jacoco/core/instr/Instrumenter.java
@@ -19,7 +19,6 @@
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
-import org.objectweb.asm.Opcodes;
/**
* Several APIs to instrument Java class definitions for coverage tracing.
@@ -64,12 +63,6 @@
*
*/
public byte[] instrument(final ClassReader reader) {
-
- // Don't instrument interfaces
- if ((reader.getAccess() & Opcodes.ACC_INTERFACE) != 0) {
- return null;
- }
-
final ClassWriter writer = new ClassWriter(reader, 0);
final ClassVisitor visitor = createInstrumentingVisitor(CRC64
.checksum(reader.b), writer);
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 27efd47..284e256 100644
--- a/org.jacoco.core/src/org/jacoco/core/instr/MethodInstrumenter.java
+++ b/org.jacoco.core/src/org/jacoco/core/instr/MethodInstrumenter.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright (c) 2009 Mountainminds GmbH & Co. KG and others
+ * Copyright (c) 2009, 2010 Mountainminds GmbH & Co. KG and others
* 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
@@ -12,6 +12,7 @@
*******************************************************************************/
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;
@@ -26,7 +27,11 @@
public class MethodInstrumenter extends GeneratorAdapter implements
IBlockMethodVisitor {
- private final String enclosingType;
+ private final IExecutionDataAccessorGenerator accessorGenerator;
+
+ private final long classid;
+
+ private int accessorStackSize;
private int probeArray;
@@ -41,13 +46,18 @@
* name of the method
* @param desc
* description of the method
- * @param enclosingType
- * type enclosing this method
+ * @param accessorGenerator
+ * the current coverage runtime
+ * @param classid
+ * the id of the enclosing type
*/
public MethodInstrumenter(final MethodVisitor mv, final int access,
- final String name, final String desc, final String enclosingType) {
+ final String name, final String desc,
+ final IExecutionDataAccessorGenerator accessorGenerator,
+ final long classid) {
super(mv, access, name, desc);
- this.enclosingType = enclosingType;
+ this.accessorGenerator = accessorGenerator;
+ this.classid = classid;
}
@Override
@@ -55,9 +65,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.
- mv.visitMethodInsn(Opcodes.INVOKESTATIC, enclosingType,
- GeneratorConstants.INITMETHOD_NAME,
- GeneratorConstants.INITMETHOD_DESC);
+ accessorStackSize = accessorGenerator.generateDataAccessor(classid, mv);
// Stack[0]: [Z
@@ -67,8 +75,12 @@
@Override
public void visitMaxs(final int maxStack, final int maxLocals) {
- // Max stack size of the probe code is 3
- super.visitMaxs(maxStack + 3, maxLocals + 1);
+ // Max stack size of the probe code is 3 which can add to the
+ // original stack size depending on the probe locations. The accessor
+ // stack size is an absolute maximum, as the accessor code is inserted
+ // at the very beginning of each method when the stack size is empty.
+ final int increasedStack = Math.max(maxStack + 3, accessorStackSize);
+ super.visitMaxs(increasedStack, maxLocals + 1);
}
// === IBlockMethodVisitor ===
diff --git a/org.jacoco.core/src/org/jacoco/core/runtime/IExecutionDataAccessorGenerator.java b/org.jacoco.core/src/org/jacoco/core/runtime/IExecutionDataAccessorGenerator.java
new file mode 100644
index 0000000..88ea8c9
--- /dev/null
+++ b/org.jacoco.core/src/org/jacoco/core/runtime/IExecutionDataAccessorGenerator.java
@@ -0,0 +1,50 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2010 Mountainminds GmbH & Co. KG and others
+ * 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 org.objectweb.asm.MethodVisitor;
+
+/**
+ * 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
+ * instrumentation process.
+ *
+ * @author Marc R. Hoffmann
+ * @version $Revision: $
+ */
+public interface IExecutionDataAccessorGenerator {
+
+ /**
+ * This method generates the byte code required to obtain the coverage data
+ * structure for the class with the given id. Typically the instrumentation
+ * process will embed this code into a method that is called on class
+ * initialization. This method can be called at any time even outside the
+ * target VM.
+ *
+ * The generated code must push a <code>boolean[]</code> instance to the
+ * operand stack. Except this result object the generated code must not make
+ * any assumptions about the structure of the embedding method or class. The
+ * generated code must not use or allocate local variables.
+ *
+ * @param classid
+ * identifier of the 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);
+
+}
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 540dd57..485fc66 100644
--- a/org.jacoco.core/src/org/jacoco/core/runtime/IRuntime.java
+++ b/org.jacoco.core/src/org/jacoco/core/runtime/IRuntime.java
@@ -13,7 +13,6 @@
package org.jacoco.core.runtime;
import org.jacoco.core.data.IExecutionDataVisitor;
-import org.objectweb.asm.MethodVisitor;
/**
* This interface represents a particular mechanism to collect execution
@@ -22,28 +21,7 @@
* @author Marc R. Hoffmann
* @version $Revision: $
*/
-public interface IRuntime {
-
- /**
- * This method generates the byte code required to obtain the coverage data
- * structure for the class with the given id. Typically the instrumentation
- * process will embed this code into a method that is called on class
- * initialization. This method can be called at any time even outside the
- * target VM.
- *
- * The generated code must push a <code>boolean[]</code> instance to the
- * operand stack. Except this result object the generated code must not make
- * any assumptions about the structure of the embedding method or class. The
- * generated code must not use or allocate local variables.
- *
- * @param classid
- * identifier of the 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 interface IRuntime extends IExecutionDataAccessorGenerator {
/**
* Starts the coverage runtime. This method MUST be called before any class
diff --git a/org.jacoco.doc/docroot/doc/changes.html b/org.jacoco.doc/docroot/doc/changes.html
index dfcded1..9652915 100644
--- a/org.jacoco.doc/docroot/doc/changes.html
+++ b/org.jacoco.doc/docroot/doc/changes.html
@@ -19,6 +19,11 @@
<h2>Next Release</h2>
+<h3>New Features</h3>
+<ul>
+ <li>Code coverage for static initializers in interfaces (Trac #21).</li>
+</ul>
+
<h3>API Changes</h3>
<ul>
<li>Consistent usage of the term "Missed" instead of "NotCovered" in all APIs (Trac #72).</li>
@@ -33,14 +38,6 @@
<h2>Release 0.3.0 (2010/02/02)</h2>
-<h3>API Changes</h3>
-<ul>
- <li>Agent option and Ant task parameter <code>file</code> changed to
- <code>destfile</code> (Trac #59).</li>
- <li>Agent option and Ant task parameter <code>merge</code> changed to
- <code>append</code> (Trac #51).</li>
-</ul>
-
<h3>New Features</h3>
<ul>
<li>Report renders anonymous classes with type information (Trac #46).</li>
@@ -54,6 +51,14 @@
application servers like Glassfish.</li>
</ul>
+<h3>API Changes</h3>
+<ul>
+ <li>Agent option and Ant task parameter <code>file</code> changed to
+ <code>destfile</code> (Trac #59).</li>
+ <li>Agent option and Ant task parameter <code>merge</code> changed to
+ <code>append</code> (Trac #51).</li>
+</ul>
+
<h2>Release 0.2.0 (2010/01/08)</h2>
<h3>New Features</h3>