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>