Do not violate JVMS regarding initialization of final fields (#434)
Without this change instrumented classes can't pass checks
and cause IllegalAccessError starting from OpenJDK 9 EA b127
(see https://bugs.openjdk.java.net/browse/JDK-8157181).
diff --git a/org.jacoco.core.test/src-java8/org/jacoco/core/test/validation/BadCycleInterfaceTest.java b/org.jacoco.core.test/src-java8/org/jacoco/core/test/validation/BadCycleInterfaceTest.java
new file mode 100644
index 0000000..1b76d98
--- /dev/null
+++ b/org.jacoco.core.test/src-java8/org/jacoco/core/test/validation/BadCycleInterfaceTest.java
@@ -0,0 +1,43 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2016 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:
+ * Evgeny Mandrikov - initial API and implementation
+ *
+ *******************************************************************************/
+package org.jacoco.core.test.validation;
+
+import org.jacoco.core.analysis.ICounter;
+import org.jacoco.core.test.validation.targets.BadCycleInterface;
+import org.junit.Test;
+
+/**
+ * Test of "bad cycles" with interfaces.
+ */
+public class BadCycleInterfaceTest extends BadCycleTestBase {
+
+ public BadCycleInterfaceTest() throws Exception {
+ super("src-java8", BadCycleInterface.class);
+ }
+
+ @Test
+ public void test() throws Exception {
+ loader.loadClass(BadCycleInterface.Child.class.getName())
+ .getMethod("childStaticMethod").invoke(null);
+
+ analyze(BadCycleInterface.Child.class);
+
+ if (System.getProperty("java.version").startsWith("9-ea")) {
+ // JDK-9042842
+ assertLine("2", ICounter.NOT_COVERED);
+ } else {
+ assertLine("2", ICounter.FULLY_COVERED);
+ }
+ assertLine("4", ICounter.FULLY_COVERED);
+ }
+
+}
diff --git a/org.jacoco.core.test/src-java8/org/jacoco/core/test/validation/InterfaceDefaultMethodsTest.java b/org.jacoco.core.test/src-java8/org/jacoco/core/test/validation/InterfaceDefaultMethodsTest.java
index 405c4cf..66c3843 100644
--- a/org.jacoco.core.test/src-java8/org/jacoco/core/test/validation/InterfaceDefaultMethodsTest.java
+++ b/org.jacoco.core.test/src-java8/org/jacoco/core/test/validation/InterfaceDefaultMethodsTest.java
@@ -16,7 +16,7 @@
import org.junit.Test;
/**
- * Tests of static initializer in interfaces.
+ * Tests of static initializer and default methods in interfaces.
*/
public class InterfaceDefaultMethodsTest extends ValidationTestBase {
diff --git a/org.jacoco.core.test/src-java8/org/jacoco/core/test/validation/InterfaceOnlyDefaultMethodsTest.java b/org.jacoco.core.test/src-java8/org/jacoco/core/test/validation/InterfaceOnlyDefaultMethodsTest.java
new file mode 100644
index 0000000..24553d7
--- /dev/null
+++ b/org.jacoco.core.test/src-java8/org/jacoco/core/test/validation/InterfaceOnlyDefaultMethodsTest.java
@@ -0,0 +1,38 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2016 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:
+ * Evgeny Mandrikov - initial API and implementation
+ *
+ *******************************************************************************/
+package org.jacoco.core.test.validation;
+
+import org.jacoco.core.analysis.ICounter;
+import org.jacoco.core.test.validation.targets.InterfaceOnlyDefaultMethodsTarget;
+import org.junit.Test;
+
+/**
+ * Tests of default methods in interfaces.
+ */
+public class InterfaceOnlyDefaultMethodsTest extends ValidationTestBase {
+
+ public InterfaceOnlyDefaultMethodsTest() {
+ super("src-java8", InterfaceOnlyDefaultMethodsTarget.class);
+ }
+
+ @Override
+ protected void run(final Class<?> targetClass) throws Exception {
+ loader.add(InterfaceOnlyDefaultMethodsTarget.Impl.class).newInstance();
+ }
+
+ @Test
+ public void testCoverageResult() {
+ assertLine("m1", ICounter.FULLY_COVERED);
+ assertLine("m2", ICounter.NOT_COVERED);
+ }
+
+}
diff --git a/org.jacoco.core.test/src-java8/org/jacoco/core/test/validation/targets/BadCycleInterface.java b/org.jacoco.core.test/src-java8/org/jacoco/core/test/validation/targets/BadCycleInterface.java
new file mode 100644
index 0000000..c2b1519
--- /dev/null
+++ b/org.jacoco.core.test/src-java8/org/jacoco/core/test/validation/targets/BadCycleInterface.java
@@ -0,0 +1,48 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2016 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:
+ * Evgeny Mandrikov - initial API and implementation
+ *
+ *******************************************************************************/
+package org.jacoco.core.test.validation.targets;
+
+public class BadCycleInterface {
+
+ public interface Base {
+ static final Object BASE_CONST = new Child() {
+ {
+ Stubs.nop("base clinit"); // $line-1$
+ }
+ }.childDefaultMethod();
+
+ default void baseDefaultMethod() {
+ }
+ }
+
+ public interface Child extends Base {
+ static final Object CHILD_CONST = new Object() {
+ {
+ Stubs.nop("child clinit"); // $line-3$
+ }
+ };
+
+ default Object childDefaultMethod() {
+ Stubs.nop("child default method"); // $line-2$
+ return null;
+ }
+
+ static void childStaticMethod() {
+ Stubs.nop("child static method"); // $line-4$
+ }
+ }
+
+ public static void main(String[] args) {
+ Child.childStaticMethod();
+ }
+
+}
diff --git a/org.jacoco.core.test/src-java8/org/jacoco/core/test/validation/targets/InterfaceDefaultMethodsTarget.java b/org.jacoco.core.test/src-java8/org/jacoco/core/test/validation/targets/InterfaceDefaultMethodsTarget.java
index 65028b9..4bd91b3 100644
--- a/org.jacoco.core.test/src-java8/org/jacoco/core/test/validation/targets/InterfaceDefaultMethodsTarget.java
+++ b/org.jacoco.core.test/src-java8/org/jacoco/core/test/validation/targets/InterfaceDefaultMethodsTarget.java
@@ -14,7 +14,7 @@
import static org.jacoco.core.test.validation.targets.Stubs.i1;
/**
- * This test target is an interface with a class initializer.
+ * This test target is an interface with a class initializer and default methods.
*/
public interface InterfaceDefaultMethodsTarget {
diff --git a/org.jacoco.core.test/src-java8/org/jacoco/core/test/validation/targets/InterfaceOnlyDefaultMethodsTarget.java b/org.jacoco.core.test/src-java8/org/jacoco/core/test/validation/targets/InterfaceOnlyDefaultMethodsTarget.java
new file mode 100644
index 0000000..a27409e
--- /dev/null
+++ b/org.jacoco.core.test/src-java8/org/jacoco/core/test/validation/targets/InterfaceOnlyDefaultMethodsTarget.java
@@ -0,0 +1,36 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2016 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:
+ * Evgeny Mandrikov - initial API and implementation
+ *
+ *******************************************************************************/
+package org.jacoco.core.test.validation.targets;
+
+/**
+ * This test target is an interface with only default methods.
+ */
+public interface InterfaceOnlyDefaultMethodsTarget {
+
+ // no <clinit>, only default methods:
+
+ default void m1() {
+ return; // $line-m1$
+ }
+
+ default void m2() {
+ return; // $line-m2$
+ }
+
+ public class Impl implements InterfaceOnlyDefaultMethodsTarget {
+
+ public Impl() {
+ m1();
+ }
+ }
+
+}
diff --git a/org.jacoco.core.test/src/org/jacoco/core/internal/instr/ClassInstrumenterTest.java b/org.jacoco.core.test/src/org/jacoco/core/internal/instr/ClassInstrumenterTest.java
index 428ba65..5d41771 100644
--- a/org.jacoco.core.test/src/org/jacoco/core/internal/instr/ClassInstrumenterTest.java
+++ b/org.jacoco.core.test/src/org/jacoco/core/internal/instr/ClassInstrumenterTest.java
@@ -62,7 +62,7 @@
// === IProbeArrayStrategy ===
- public int storeInstance(MethodVisitor mv, int variable) {
+ public int storeInstance(MethodVisitor mv, boolean clinit, int variable) {
return 0;
}
diff --git a/org.jacoco.core.test/src/org/jacoco/core/internal/instr/ProbeArrayStrategyFactoryTest.java b/org.jacoco.core.test/src/org/jacoco/core/internal/instr/ProbeArrayStrategyFactoryTest.java
index c8da6d6..3ecbd3a 100644
--- a/org.jacoco.core.test/src/org/jacoco/core/internal/instr/ProbeArrayStrategyFactoryTest.java
+++ b/org.jacoco.core.test/src/org/jacoco/core/internal/instr/ProbeArrayStrategyFactoryTest.java
@@ -14,7 +14,10 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.util.ArrayList;
+import java.util.List;
import org.jacoco.core.runtime.IExecutionDataAccessorGenerator;
import org.jacoco.core.runtime.OfflineInstrumentationAccessGenerator;
@@ -98,9 +101,8 @@
assertDataField(InstrSupport.DATAFIELD_ACC);
assertInitMethod(true);
- final ClassVisitorMock cv = new ClassVisitorMock();
- strategy.storeInstance(cv.visitMethod(0, null, null, null, null), 0);
- assertFalse(cv.interfaceMethod);
+ strategy.storeInstance(cv.visitMethod(0, null, null, null, null), false,
+ 0);
}
@Test
@@ -119,21 +121,21 @@
@Test(expected = UnsupportedOperationException.class)
public void testEmptyInterface7StoreInstance() {
- IProbeArrayStrategy strategy = test(Opcodes.V1_7,
- Opcodes.ACC_INTERFACE, false, false);
- strategy.storeInstance(null, 0);
+ IProbeArrayStrategy strategy = test(Opcodes.V1_7, Opcodes.ACC_INTERFACE,
+ false, false);
+ strategy.storeInstance(null, false, 0);
}
@Test
public void testInterface8() {
+ cv.isInterface = true;
final IProbeArrayStrategy strategy = test(Opcodes.V1_8,
Opcodes.ACC_INTERFACE, false, true);
assertDataField(InstrSupport.DATAFIELD_INTF_ACC);
- assertInitMethod(true);
+ assertInitAndClinitMethods();
- final ClassVisitorMock cv = new ClassVisitorMock();
- strategy.storeInstance(cv.visitMethod(0, null, null, null, null), 0);
- assertTrue(cv.interfaceMethod);
+ strategy.storeInstance(cv.visitMethod(0, null, null, null, null), false,
+ 0);
}
@Test
@@ -143,6 +145,13 @@
assertNoInitMethod();
}
+ @Test(expected = UnsupportedOperationException.class)
+ public void testEmptyInterface8StoreInstance() {
+ final IProbeArrayStrategy strategy = test(Opcodes.V1_8,
+ Opcodes.ACC_INTERFACE, false, false);
+ strategy.storeInstance(null, false, 0);
+ }
+
@Test
public void testClinitInterface8() {
test(Opcodes.V1_8, Opcodes.ACC_INTERFACE, true, false);
@@ -150,6 +159,18 @@
assertNoInitMethod();
}
+ @Test
+ public void testClinitAndMethodsInterface8() {
+ cv.isInterface = true;
+ final IProbeArrayStrategy strategy = test(Opcodes.V1_8,
+ Opcodes.ACC_INTERFACE, true, true);
+ assertDataField(InstrSupport.DATAFIELD_INTF_ACC);
+ assertInitAndClinitMethods();
+
+ strategy.storeInstance(cv.visitMethod(0, "<clinit>", null, null, null),
+ true, 0);
+ }
+
private IProbeArrayStrategy test(int version, int access, boolean clinit,
boolean method) {
ClassWriter writer = new ClassWriter(0);
@@ -179,16 +200,40 @@
return strategy;
}
+ private static class AddedMethod {
+ private final int access;
+ private final String name;
+ private final String desc;
+ private boolean frames;
+
+ AddedMethod(int access, String name, String desc) {
+ this.access = access;
+ this.name = name;
+ this.desc = desc;
+ }
+
+ void assertInitMethod(boolean frames) {
+ assertEquals(InstrSupport.INITMETHOD_NAME, name);
+ assertEquals(InstrSupport.INITMETHOD_DESC, desc);
+ assertEquals(InstrSupport.INITMETHOD_ACC, access);
+ assertEquals(Boolean.valueOf(frames), Boolean.valueOf(frames));
+ }
+
+ void assertClinit() {
+ assertEquals(InstrSupport.CLINIT_NAME, name);
+ assertEquals(InstrSupport.CLINIT_DESC, desc);
+ assertEquals(InstrSupport.CLINIT_ACC, access);
+ assertEquals(Boolean.valueOf(false), Boolean.valueOf(frames));
+ }
+ }
+
private static class ClassVisitorMock extends ClassVisitor {
+ private boolean isInterface;
+
private int fieldAccess;
private String fieldName;
-
- private int methodAccess;
- private String methodName;
-
- private boolean frames;
- private boolean interfaceMethod;
+ private final List<AddedMethod> methods = new ArrayList<AddedMethod>();
ClassVisitorMock() {
super(Opcodes.ASM5);
@@ -206,20 +251,51 @@
@Override
public MethodVisitor visitMethod(int access, String name, String desc,
String signature, String[] exceptions) {
- assertNull(methodName);
- methodAccess = access;
- methodName = name;
+ final AddedMethod m = new AddedMethod(access, name, desc);
+ methods.add(m);
return new MethodVisitor(Opcodes.ASM5) {
@Override
public void visitFrame(int type, int nLocal, Object[] local,
int nStack, Object[] stack) {
- frames = true;
+ m.frames = true;
+ }
+
+ @Override
+ public void visitFieldInsn(int opcode, String owner,
+ String name, String desc) {
+ assertEquals(InstrSupport.DATAFIELD_NAME, name);
+ assertEquals(InstrSupport.DATAFIELD_DESC, desc);
+
+ if (opcode == Opcodes.GETSTATIC) {
+ assertEquals(InstrSupport.INITMETHOD_NAME,
+ methods.get(methods.size() - 1).name);
+ } else if (opcode == Opcodes.PUTSTATIC) {
+ if (isInterface) {
+ assertEquals(InstrSupport.CLINIT_NAME,
+ methods.get(methods.size() - 1).name);
+ } else {
+ assertEquals(InstrSupport.INITMETHOD_NAME,
+ methods.get(methods.size() - 1).name);
+ }
+ } else {
+ fail();
+ }
}
@Override
public void visitMethodInsn(int opcode, String owner,
String name, String desc, boolean itf) {
- interfaceMethod = itf;
+ if ("getProbes".equals(name)) {
+ // method's owner is not interface:
+ assertFalse(itf);
+ return;
+ }
+ assertEquals(itf, isInterface);
+
+ assertEquals(Opcodes.INVOKESTATIC, opcode);
+ assertEquals("Foo", owner);
+ assertEquals(InstrSupport.INITMETHOD_NAME, name);
+ assertEquals(InstrSupport.INITMETHOD_DESC, desc);
}
};
}
@@ -235,13 +311,18 @@
}
void assertInitMethod(boolean frames) {
- assertEquals(InstrSupport.INITMETHOD_NAME, cv.methodName);
- assertEquals(InstrSupport.INITMETHOD_ACC, cv.methodAccess);
- assertEquals(Boolean.valueOf(frames), Boolean.valueOf(cv.frames));
+ assertEquals(cv.methods.size(), 1);
+ cv.methods.get(0).assertInitMethod(frames);
+ }
+
+ void assertInitAndClinitMethods() {
+ assertEquals(2, cv.methods.size());
+ cv.methods.get(0).assertInitMethod(true);
+ cv.methods.get(1).assertClinit();
}
void assertNoInitMethod() {
- assertNull(cv.methodName);
+ assertEquals(0, cv.methods.size());
}
}
diff --git a/org.jacoco.core.test/src/org/jacoco/core/internal/instr/ProbeInserterTest.java b/org.jacoco.core.test/src/org/jacoco/core/internal/instr/ProbeInserterTest.java
index 6f2d7ec..6a2f9c6 100644
--- a/org.jacoco.core.test/src/org/jacoco/core/internal/instr/ProbeInserterTest.java
+++ b/org.jacoco.core.test/src/org/jacoco/core/internal/instr/ProbeInserterTest.java
@@ -39,9 +39,8 @@
expected = new MethodRecorder();
expectedVisitor = expected.getVisitor();
arrayStrategy = new IProbeArrayStrategy() {
-
- public int storeInstance(MethodVisitor mv, int variable) {
- mv.visitLdcInsn("init");
+ public int storeInstance(MethodVisitor mv, boolean clinit, int variable) {
+ mv.visitLdcInsn(clinit ? "clinit" : "init");
return 5;
}
@@ -57,7 +56,7 @@
@Test
public void testVariableStatic() {
- ProbeInserter pi = new ProbeInserter(Opcodes.ACC_STATIC, "()V",
+ ProbeInserter pi = new ProbeInserter(Opcodes.ACC_STATIC, "m", "()V",
actualVisitor, arrayStrategy);
pi.insertProbe(0);
@@ -69,7 +68,7 @@
@Test
public void testVariableNonStatic() {
- ProbeInserter pi = new ProbeInserter(0, "()V", actualVisitor,
+ ProbeInserter pi = new ProbeInserter(0, "m", "()V", actualVisitor,
arrayStrategy);
pi.insertProbe(0);
@@ -81,7 +80,7 @@
@Test
public void testVariableNonStatic_IZObject() {
- ProbeInserter pi = new ProbeInserter(0, "(IZLjava/lang/Object;)V",
+ ProbeInserter pi = new ProbeInserter(0, "m", "(IZLjava/lang/Object;)V",
actualVisitor, arrayStrategy);
pi.insertProbe(0);
@@ -93,7 +92,7 @@
@Test
public void testVariableNonStatic_JD() {
- ProbeInserter pi = new ProbeInserter(0, "(JD)V", actualVisitor,
+ ProbeInserter pi = new ProbeInserter(0, "m", "(JD)V", actualVisitor,
arrayStrategy);
pi.insertProbe(0);
@@ -105,7 +104,7 @@
@Test
public void testVisitCode() {
- ProbeInserter pi = new ProbeInserter(0, "()V", actualVisitor,
+ ProbeInserter pi = new ProbeInserter(0, "m", "()V", actualVisitor,
arrayStrategy);
pi.visitCode();
@@ -113,8 +112,17 @@
}
@Test
+ public void testVisitClinit() {
+ ProbeInserter pi = new ProbeInserter(0, "<clinit>", "()V",
+ actualVisitor, arrayStrategy);
+ pi.visitCode();
+
+ expectedVisitor.visitLdcInsn("clinit");
+ }
+
+ @Test
public void testVisitVarIns() {
- ProbeInserter pi = new ProbeInserter(0, "(II)V", actualVisitor,
+ ProbeInserter pi = new ProbeInserter(0, "m", "(II)V", actualVisitor,
arrayStrategy);
pi.visitVarInsn(Opcodes.ALOAD, 0);
@@ -135,7 +143,7 @@
@Test
public void testVisitIincInsn() {
- ProbeInserter pi = new ProbeInserter(0, "(II)V", actualVisitor,
+ ProbeInserter pi = new ProbeInserter(0, "m", "(II)V", actualVisitor,
arrayStrategy);
pi.visitIincInsn(0, 100);
pi.visitIincInsn(1, 101);
@@ -155,7 +163,7 @@
@Test
public void testVisitLocalVariable() {
- ProbeInserter pi = new ProbeInserter(0, "(II)V", actualVisitor,
+ ProbeInserter pi = new ProbeInserter(0, "m", "(II)V", actualVisitor,
arrayStrategy);
pi.visitLocalVariable(null, null, null, null, null, 0);
@@ -176,7 +184,7 @@
@Test
public void testVisitMaxs1() {
- ProbeInserter pi = new ProbeInserter(0, "(II)V", actualVisitor,
+ ProbeInserter pi = new ProbeInserter(0, "m", "(II)V", actualVisitor,
arrayStrategy);
pi.visitCode();
pi.visitMaxs(0, 8);
@@ -187,7 +195,7 @@
@Test
public void testVisitMaxs2() {
- ProbeInserter pi = new ProbeInserter(0, "(II)V", actualVisitor,
+ ProbeInserter pi = new ProbeInserter(0, "m", "(II)V", actualVisitor,
arrayStrategy);
pi.visitCode();
pi.visitMaxs(10, 8);
@@ -198,7 +206,7 @@
@Test
public void testVisitFrame() {
- ProbeInserter pi = new ProbeInserter(0, "(J)V", actualVisitor,
+ ProbeInserter pi = new ProbeInserter(0, "m", "(J)V", actualVisitor,
arrayStrategy);
pi.visitFrame(Opcodes.F_NEW, 3, new Object[] { "Foo", Opcodes.LONG,
@@ -210,7 +218,7 @@
@Test
public void testVisitFrameNoLocals() {
- ProbeInserter pi = new ProbeInserter(Opcodes.ACC_STATIC, "()V",
+ ProbeInserter pi = new ProbeInserter(Opcodes.ACC_STATIC, "m", "()V",
actualVisitor, arrayStrategy);
pi.visitFrame(Opcodes.F_NEW, 0, new Object[] {}, 0, new Object[0]);
@@ -221,7 +229,7 @@
@Test
public void testVisitFrameProbeAt0() {
- ProbeInserter pi = new ProbeInserter(Opcodes.ACC_STATIC, "()V",
+ ProbeInserter pi = new ProbeInserter(Opcodes.ACC_STATIC, "m", "()V",
actualVisitor, arrayStrategy);
pi.visitFrame(Opcodes.F_NEW, 2, new Object[] { Opcodes.DOUBLE, "Foo" },
@@ -233,7 +241,7 @@
@Test
public void testFillOneWord() {
- ProbeInserter pi = new ProbeInserter(Opcodes.ACC_STATIC, "(I)V",
+ ProbeInserter pi = new ProbeInserter(Opcodes.ACC_STATIC, "m", "(I)V",
actualVisitor, arrayStrategy);
pi.visitFrame(Opcodes.F_NEW, 0, new Object[] {}, 0, new Object[] {});
@@ -245,7 +253,7 @@
@Test
public void testFillTwoWord() {
- ProbeInserter pi = new ProbeInserter(Opcodes.ACC_STATIC, "(J)V",
+ ProbeInserter pi = new ProbeInserter(Opcodes.ACC_STATIC, "m", "(J)V",
actualVisitor, arrayStrategy);
pi.visitFrame(Opcodes.F_NEW, 0, new Object[] {}, 0, new Object[] {});
@@ -257,7 +265,7 @@
@Test
public void testFillPartly() {
- ProbeInserter pi = new ProbeInserter(Opcodes.ACC_STATIC, "(DIJ)V",
+ ProbeInserter pi = new ProbeInserter(Opcodes.ACC_STATIC, "m", "(DIJ)V",
actualVisitor, arrayStrategy);
pi.visitFrame(Opcodes.F_NEW, 1, new Object[] { Opcodes.DOUBLE }, 0,
@@ -271,7 +279,7 @@
@Test(expected = IllegalArgumentException.class)
public void testVisitFrame_invalidType() {
- ProbeInserter pi = new ProbeInserter(0, "()V", actualVisitor,
+ ProbeInserter pi = new ProbeInserter(0, "m", "()V", actualVisitor,
arrayStrategy);
pi.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
}
diff --git a/org.jacoco.core.test/src/org/jacoco/core/test/InstrumentingLoader.java b/org.jacoco.core.test/src/org/jacoco/core/test/InstrumentingLoader.java
new file mode 100644
index 0000000..99bbaed
--- /dev/null
+++ b/org.jacoco.core.test/src/org/jacoco/core/test/InstrumentingLoader.java
@@ -0,0 +1,75 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2016 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:
+ * Evgeny Mandrikov - initial API and implementation
+ *
+ *******************************************************************************/
+package org.jacoco.core.test;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.jacoco.core.data.ExecutionDataStore;
+import org.jacoco.core.data.SessionInfoStore;
+import org.jacoco.core.instr.Instrumenter;
+import org.jacoco.core.runtime.IRuntime;
+import org.jacoco.core.runtime.RuntimeData;
+import org.jacoco.core.runtime.SystemPropertiesRuntime;
+
+public final class InstrumentingLoader extends ClassLoader {
+
+ private final RuntimeData data;
+ private final IRuntime runtime;
+
+ public InstrumentingLoader() throws Exception {
+ data = new RuntimeData();
+ runtime = new SystemPropertiesRuntime();
+ runtime.startup(data);
+ }
+
+ @Override
+ protected synchronized Class<?> loadClass(String name, boolean resolve)
+ throws ClassNotFoundException {
+ if (name.startsWith("org.jacoco.core.test.validation.targets.")) {
+ final byte[] bytes;
+ try {
+ bytes = getClassBytes(name);
+ } catch (IOException e) {
+ throw new ClassNotFoundException("Unable to load", e);
+ }
+ final byte[] instrumented;
+ try {
+ instrumented = new Instrumenter(runtime).instrument(bytes,
+ name);
+ } catch (IOException e) {
+ throw new ClassNotFoundException("Unable to instrument", e);
+ }
+ final Class<?> c = defineClass(name, instrumented, 0,
+ instrumented.length);
+ if (resolve) {
+ resolveClass(c);
+ }
+ return c;
+ }
+ return super.loadClass(name, resolve);
+ }
+
+ public byte[] getClassBytes(String name) throws IOException {
+ final String resource = "/" + name.replace('.', '/') + ".class";
+ final InputStream in = getClass().getResourceAsStream(resource);
+ return TargetLoader.getClassDataAsBytes(in);
+ }
+
+ public ExecutionDataStore collect() {
+ final ExecutionDataStore store = new ExecutionDataStore();
+ data.collect(store, new SessionInfoStore(), false);
+ runtime.shutdown();
+ return store;
+ }
+
+}
diff --git a/org.jacoco.core.test/src/org/jacoco/core/test/TargetLoader.java b/org.jacoco.core.test/src/org/jacoco/core/test/TargetLoader.java
index 00bab63..de54015 100644
--- a/org.jacoco.core.test/src/org/jacoco/core/test/TargetLoader.java
+++ b/org.jacoco.core.test/src/org/jacoco/core/test/TargetLoader.java
@@ -58,7 +58,11 @@
}
public static byte[] getClassDataAsBytes(Class<?> clazz) throws IOException {
- InputStream in = getClassData(clazz);
+ return getClassDataAsBytes(getClassData(clazz));
+ }
+
+ public static byte[] getClassDataAsBytes(InputStream in) throws
+ IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
byte[] buffer = new byte[0x100];
int len;
diff --git a/org.jacoco.core.test/src/org/jacoco/core/test/validation/BadCycleClassTest.java b/org.jacoco.core.test/src/org/jacoco/core/test/validation/BadCycleClassTest.java
new file mode 100644
index 0000000..47820e6
--- /dev/null
+++ b/org.jacoco.core.test/src/org/jacoco/core/test/validation/BadCycleClassTest.java
@@ -0,0 +1,37 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2016 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:
+ * Evgeny Mandrikov - initial API and implementation
+ *
+ *******************************************************************************/
+package org.jacoco.core.test.validation;
+
+import org.jacoco.core.analysis.ICounter;
+import org.jacoco.core.test.validation.targets.BadCycleClass;
+import org.junit.Test;
+
+/**
+ * Test of "bad cycles" with classes.
+ */
+public class BadCycleClassTest extends BadCycleTestBase {
+
+ public BadCycleClassTest() throws Exception {
+ super(BadCycleClass.class);
+ }
+
+ @Test
+ public void test() throws Exception {
+ loader.loadClass(BadCycleClass.Child.class.getName()).newInstance();
+
+ analyze(BadCycleClass.Child.class);
+ assertLine("1", ICounter.FULLY_COVERED);
+ assertLine("2", ICounter.FULLY_COVERED);
+ assertLine("3", ICounter.FULLY_COVERED);
+ }
+
+}
diff --git a/org.jacoco.core.test/src/org/jacoco/core/test/validation/BadCycleTestBase.java b/org.jacoco.core.test/src/org/jacoco/core/test/validation/BadCycleTestBase.java
new file mode 100644
index 0000000..dd7b499
--- /dev/null
+++ b/org.jacoco.core.test/src/org/jacoco/core/test/validation/BadCycleTestBase.java
@@ -0,0 +1,66 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2016 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:
+ * Evgeny Mandrikov - initial API and implementation
+ *
+ *******************************************************************************/
+package org.jacoco.core.test.validation;
+
+import static org.junit.Assert.assertEquals;
+
+import java.io.IOException;
+import java.util.Collection;
+
+import org.jacoco.core.analysis.Analyzer;
+import org.jacoco.core.analysis.CoverageBuilder;
+import org.jacoco.core.analysis.IClassCoverage;
+import org.jacoco.core.analysis.ISourceFileCoverage;
+import org.jacoco.core.data.ExecutionDataStore;
+import org.jacoco.core.test.InstrumentingLoader;
+
+class BadCycleTestBase extends ValidationTestBase {
+
+ protected final InstrumentingLoader loader = new InstrumentingLoader();
+
+ BadCycleTestBase(final Class<?> target) throws Exception {
+ super(target);
+ }
+
+ BadCycleTestBase(final String srcFolder, final Class<?> target)
+ throws Exception {
+ super(srcFolder, target);
+ }
+
+ @Override
+ public final void setup() throws Exception {
+ // nop
+ }
+
+ @Override
+ protected final void run(Class<?> targetClass) throws Exception {
+ // nop
+ }
+
+ final void analyze(Class<?> cls) throws IOException {
+ final byte[] bytes = loader.getClassBytes(cls.getName());
+ final ExecutionDataStore store = loader.collect();
+
+ final CoverageBuilder builder = new CoverageBuilder();
+ final Analyzer analyzer = new Analyzer(store, builder);
+ analyzer.analyzeClass(bytes, "TestTarget");
+ final Collection<IClassCoverage> classes = builder.getClasses();
+ assertEquals(1, classes.size(), 0.0);
+ classCoverage = classes.iterator().next();
+ final Collection<ISourceFileCoverage> files = builder.getSourceFiles();
+ assertEquals(1, files.size(), 0.0);
+ sourceCoverage = files.iterator().next();
+
+ source = Source.getSourceFor(srcFolder, target);
+ }
+
+}
diff --git a/org.jacoco.core.test/src/org/jacoco/core/test/validation/targets/BadCycleClass.java b/org.jacoco.core.test/src/org/jacoco/core/test/validation/targets/BadCycleClass.java
new file mode 100644
index 0000000..44e40cd
--- /dev/null
+++ b/org.jacoco.core.test/src/org/jacoco/core/test/validation/targets/BadCycleClass.java
@@ -0,0 +1,44 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2016 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:
+ * Evgeny Mandrikov - initial API and implementation
+ *
+ *******************************************************************************/
+package org.jacoco.core.test.validation.targets;
+
+public class BadCycleClass {
+
+ public static class Base {
+ static final Child b = new Child();
+
+ static {
+ b.someMethod();
+ }
+ }
+
+ public static class Child extends Base {
+
+ static {
+ Stubs.nop("child clinit"); // $line-3$
+ }
+
+ public Child() {
+ Stubs.nop("child init"); // $line-1$
+ }
+
+ void someMethod() {
+ Stubs.nop("child someMethod"); // $line-2$
+ }
+
+ }
+
+ public static void main(String[] args) {
+ new Child();
+ }
+
+}
diff --git a/org.jacoco.core/src/org/jacoco/core/internal/instr/FieldProbeArrayStrategy.java b/org.jacoco.core/src/org/jacoco/core/internal/instr/ClassFieldProbeArrayStrategy.java
similarity index 84%
rename from org.jacoco.core/src/org/jacoco/core/internal/instr/FieldProbeArrayStrategy.java
rename to org.jacoco.core/src/org/jacoco/core/internal/instr/ClassFieldProbeArrayStrategy.java
index 26902c2..3b7bf91 100644
--- a/org.jacoco.core/src/org/jacoco/core/internal/instr/FieldProbeArrayStrategy.java
+++ b/org.jacoco.core/src/org/jacoco/core/internal/instr/ClassFieldProbeArrayStrategy.java
@@ -18,11 +18,11 @@
import org.objectweb.asm.Opcodes;
/**
- * The strategy for regular classes and Java 8 interfaces which adds a static
- * field to hold the probe array and a static initialization method requesting
- * the probe array from the runtime.
+ * The strategy for regular classes adds a static field to hold the probe array
+ * and a static initialization method requesting the probe array from the
+ * runtime.
*/
-class FieldProbeArrayStrategy implements IProbeArrayStrategy {
+class ClassFieldProbeArrayStrategy implements IProbeArrayStrategy {
/**
* Frame stack with a single boolean array.
@@ -37,26 +37,22 @@
private final String className;
private final long classId;
private final boolean withFrames;
- private final boolean isInterface;
- private final int fieldAccess;
private final IExecutionDataAccessorGenerator accessorGenerator;
- FieldProbeArrayStrategy(final String className, final long classId,
- final boolean withFrames, final boolean isInterface,
- final int fieldAccess,
+ ClassFieldProbeArrayStrategy(final String className, final long classId,
+ final boolean withFrames,
final IExecutionDataAccessorGenerator accessorGenerator) {
this.className = className;
this.classId = classId;
this.withFrames = withFrames;
- this.isInterface = isInterface;
- this.fieldAccess = fieldAccess;
this.accessorGenerator = accessorGenerator;
}
- public int storeInstance(final MethodVisitor mv, final int variable) {
+ public int storeInstance(final MethodVisitor mv, final boolean clinit,
+ final int variable) {
mv.visitMethodInsn(Opcodes.INVOKESTATIC, className,
InstrSupport.INITMETHOD_NAME, InstrSupport.INITMETHOD_DESC,
- isInterface);
+ false);
mv.visitVarInsn(Opcodes.ASTORE, variable);
return 1;
}
@@ -67,7 +63,7 @@
}
private void createDataField(final ClassVisitor cv) {
- cv.visitField(fieldAccess, InstrSupport.DATAFIELD_NAME,
+ cv.visitField(InstrSupport.DATAFIELD_ACC, InstrSupport.DATAFIELD_NAME,
InstrSupport.DATAFIELD_DESC, null, null);
}
diff --git a/org.jacoco.core/src/org/jacoco/core/internal/instr/ClassInstrumenter.java b/org.jacoco.core/src/org/jacoco/core/internal/instr/ClassInstrumenter.java
index 6cea48c..a70c673 100644
--- a/org.jacoco.core/src/org/jacoco/core/internal/instr/ClassInstrumenter.java
+++ b/org.jacoco.core/src/org/jacoco/core/internal/instr/ClassInstrumenter.java
@@ -70,7 +70,7 @@
}
final MethodVisitor frameEliminator = new DuplicateFrameEliminator(mv);
final ProbeInserter probeVariableInserter = new ProbeInserter(access,
- desc, frameEliminator, probeArrayStrategy);
+ name, desc, frameEliminator, probeArrayStrategy);
return new MethodInstrumenter(probeVariableInserter,
probeVariableInserter);
}
diff --git a/org.jacoco.core/src/org/jacoco/core/internal/instr/IProbeArrayStrategy.java b/org.jacoco.core/src/org/jacoco/core/internal/instr/IProbeArrayStrategy.java
index 8973785..87a415a 100644
--- a/org.jacoco.core/src/org/jacoco/core/internal/instr/IProbeArrayStrategy.java
+++ b/org.jacoco.core/src/org/jacoco/core/internal/instr/IProbeArrayStrategy.java
@@ -26,11 +26,13 @@
*
* @param mv
* visitor to create code
+ * @param clinit
+ * true in case of {@code <clinit>} method
* @param variable
* variable index to store probe array to
* @return maximum stack size required by the generated code
*/
- int storeInstance(MethodVisitor mv, int variable);
+ int storeInstance(MethodVisitor mv, boolean clinit, int variable);
/**
* Adds additional class members required by this strategy. This method is
diff --git a/org.jacoco.core/src/org/jacoco/core/internal/instr/InstrSupport.java b/org.jacoco.core/src/org/jacoco/core/internal/instr/InstrSupport.java
index ab054df..5d34a66 100644
--- a/org.jacoco.core/src/org/jacoco/core/internal/instr/InstrSupport.java
+++ b/org.jacoco.core/src/org/jacoco/core/internal/instr/InstrSupport.java
@@ -34,14 +34,37 @@
/**
* Access modifiers of the field that stores coverage information of a
* class.
+ *
+ * According to Java Virtual Machine Specification <a href=
+ * "https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-6.html#jvms-6.5.putstatic">
+ * §6.5.putstatic</a> this field must not be final:
+ *
+ * <blockquote>
+ * <p>
+ * if the field is final, it must be declared in the current class, and the
+ * instruction must occur in the {@code <clinit>} method of the current
+ * class.
+ * </p>
+ * </blockquote>
*/
public static final int DATAFIELD_ACC = Opcodes.ACC_SYNTHETIC
- | Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC | Opcodes.ACC_TRANSIENT
- | Opcodes.ACC_FINAL;
+ | Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC | Opcodes.ACC_TRANSIENT;
/**
* Access modifiers of the field that stores coverage information of a Java
* 8 interface.
+ *
+ * According to Java Virtual Machine Specification <a href=
+ * "https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.5-200-A.3">
+ * §4.5</a>:
+ *
+ * <blockquote>
+ * <p>
+ * Fields of interfaces must have their ACC_PUBLIC, ACC_STATIC, and
+ * ACC_FINAL flags set; they may have their ACC_SYNTHETIC flag set and must
+ * not have any of the other flags.
+ * </p>
+ * </blockquote>
*/
public static final int DATAFIELD_INTF_ACC = Opcodes.ACC_SYNTHETIC
| Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC | Opcodes.ACC_FINAL;
@@ -71,6 +94,67 @@
| Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC;
/**
+ * Name of the interface initialization method.
+ *
+ * According to Java Virtual Machine Specification <a href=
+ * "https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-2.html#jvms-2.9-200">
+ * §2.9</a>:
+ *
+ * <blockquote>
+ * <p>
+ * A class or interface has at most one class or interface initialization
+ * method and is initialized by invoking that method. The initialization
+ * method of a class or interface has the special name {@code <clinit>},
+ * takes no arguments, and is void.
+ * </p>
+ * <p>
+ * Other methods named {@code <clinit>} in a class file are of no
+ * consequence. They are not class or interface initialization methods. They
+ * cannot be invoked by any Java Virtual Machine instruction and are never
+ * invoked by the Java Virtual Machine itself.
+ * </p>
+ * <p>
+ * In a class file whose version number is 51.0 or above, the method must
+ * additionally have its ACC_STATIC flag set in order to be the class or
+ * interface initialization method.
+ * </p>
+ * <p>
+ * This requirement was introduced in Java SE 7. In a class file whose
+ * version number is 50.0 or below, a method named {@code <clinit>} that is
+ * void and takes no arguments is considered the class or interface
+ * initialization method regardless of the setting of its ACC_STATIC flag.
+ * </p>
+ * </blockquote>
+ *
+ * And <a href=
+ * "https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.6-200-A.6">
+ * §4.6</a>:
+ *
+ * <blockquote>
+ * <p>
+ * Class and interface initialization methods are called implicitly by the
+ * Java Virtual Machine. The value of their access_flags item is ignored
+ * except for the setting of the ACC_STRICT flag.
+ * </p>
+ * </blockquote>
+ */
+ static final String CLINIT_NAME = "<clinit>";
+
+ /**
+ * Descriptor of the interface initialization method.
+ *
+ * @see #CLINIT_NAME
+ */
+ static final String CLINIT_DESC = "()V";
+
+ /**
+ * Access flags of the interface initialization method generated by JaCoCo.
+ *
+ * @see #CLINIT_NAME
+ */
+ static final int CLINIT_ACC = Opcodes.ACC_SYNTHETIC | Opcodes.ACC_STATIC;
+
+ /**
* Ensures that the given member does not correspond to a internal member
* created by the instrumentation process. This would mean that the class is
* already instrumented.
diff --git a/org.jacoco.core/src/org/jacoco/core/internal/instr/InterfaceFieldProbeArrayStrategy.java b/org.jacoco.core/src/org/jacoco/core/internal/instr/InterfaceFieldProbeArrayStrategy.java
new file mode 100644
index 0000000..00228e9
--- /dev/null
+++ b/org.jacoco.core/src/org/jacoco/core/internal/instr/InterfaceFieldProbeArrayStrategy.java
@@ -0,0 +1,154 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2016 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:
+ * Evgeny Mandrikov - initial API and implementation
+ *
+ *******************************************************************************/
+package org.jacoco.core.internal.instr;
+
+import org.jacoco.core.runtime.IExecutionDataAccessorGenerator;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+/**
+ * This strategy for Java 8 interfaces adds a static method requesting the probe
+ * array from the runtime, a static field to hold the probe array and adds code
+ * for its initialization into interface initialization method.
+ */
+class InterfaceFieldProbeArrayStrategy implements IProbeArrayStrategy {
+
+ /**
+ * Frame stack with a single boolean array.
+ */
+ private static final Object[] FRAME_STACK_ARRZ = new Object[] { InstrSupport.DATAFIELD_DESC };
+
+ /**
+ * Empty frame locals.
+ */
+ private static final Object[] FRAME_LOCALS_EMPTY = new Object[0];
+
+ private final String className;
+ private final long classId;
+ private final int probeCount;
+ private final IExecutionDataAccessorGenerator accessorGenerator;
+
+ private boolean seenClinit = false;
+
+ InterfaceFieldProbeArrayStrategy(final String className, final long classId,
+ final int probeCount,
+ final IExecutionDataAccessorGenerator accessorGenerator) {
+ this.className = className;
+ this.classId = classId;
+ this.probeCount = probeCount;
+ this.accessorGenerator = accessorGenerator;
+ }
+
+ public int storeInstance(final MethodVisitor mv, final boolean clinit,
+ final int variable) {
+ if (clinit) {
+ final int maxStack = accessorGenerator.generateDataAccessor(classId,
+ className, probeCount, mv);
+
+ // Stack[0]: [Z
+
+ mv.visitInsn(Opcodes.DUP);
+
+ // Stack[1]: [Z
+ // Stack[0]: [Z
+
+ mv.visitFieldInsn(Opcodes.PUTSTATIC, className,
+ InstrSupport.DATAFIELD_NAME, InstrSupport.DATAFIELD_DESC);
+
+ // Stack[0]: [Z
+
+ mv.visitVarInsn(Opcodes.ASTORE, variable);
+
+ seenClinit = true;
+ return Math.max(maxStack, 2);
+ } else {
+ mv.visitMethodInsn(Opcodes.INVOKESTATIC, className,
+ InstrSupport.INITMETHOD_NAME, InstrSupport.INITMETHOD_DESC,
+ true);
+ mv.visitVarInsn(Opcodes.ASTORE, variable);
+ return 1;
+ }
+ }
+
+ public void addMembers(final ClassVisitor cv, final int probeCount) {
+ createDataField(cv);
+ createInitMethod(cv, probeCount);
+ if (!seenClinit) {
+ createClinitMethod(cv, probeCount);
+ }
+ }
+
+ private void createDataField(final ClassVisitor cv) {
+ cv.visitField(InstrSupport.DATAFIELD_INTF_ACC,
+ InstrSupport.DATAFIELD_NAME, InstrSupport.DATAFIELD_DESC, null,
+ null);
+ }
+
+ private void createInitMethod(final ClassVisitor cv, final int probeCount) {
+ final MethodVisitor mv = cv.visitMethod(InstrSupport.INITMETHOD_ACC,
+ InstrSupport.INITMETHOD_NAME, InstrSupport.INITMETHOD_DESC,
+ null, null);
+ mv.visitCode();
+
+ // Load the value of the static data field:
+ mv.visitFieldInsn(Opcodes.GETSTATIC, className,
+ InstrSupport.DATAFIELD_NAME, InstrSupport.DATAFIELD_DESC);
+ 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 = accessorGenerator.generateDataAccessor(classId,
+ className, probeCount, mv);
+
+ // Stack[0]: [Z
+
+ // Return the class' probe array:
+ mv.visitFrame(Opcodes.F_NEW, 0, FRAME_LOCALS_EMPTY, 1,
+ FRAME_STACK_ARRZ);
+ mv.visitLabel(alreadyInitialized);
+ mv.visitInsn(Opcodes.ARETURN);
+
+ mv.visitMaxs(Math.max(size, 2), 0); // Maximum local stack size is 2
+ mv.visitEnd();
+ }
+
+ private void createClinitMethod(final ClassVisitor cv,
+ final int probeCount) {
+ final MethodVisitor mv = cv.visitMethod(InstrSupport.CLINIT_ACC,
+ InstrSupport.CLINIT_NAME, InstrSupport.CLINIT_DESC, null, null);
+ mv.visitCode();
+
+ final int maxStack = accessorGenerator.generateDataAccessor(classId,
+ className, probeCount, mv);
+
+ // Stack[0]: [Z
+
+ mv.visitFieldInsn(Opcodes.PUTSTATIC, className,
+ InstrSupport.DATAFIELD_NAME, InstrSupport.DATAFIELD_DESC);
+
+ mv.visitInsn(Opcodes.RETURN);
+
+ mv.visitMaxs(maxStack, 0);
+ mv.visitEnd();
+ }
+
+}
diff --git a/org.jacoco.core/src/org/jacoco/core/internal/instr/LocalProbeArrayStrategy.java b/org.jacoco.core/src/org/jacoco/core/internal/instr/LocalProbeArrayStrategy.java
index f13dddf..ff0b884 100644
--- a/org.jacoco.core/src/org/jacoco/core/internal/instr/LocalProbeArrayStrategy.java
+++ b/org.jacoco.core/src/org/jacoco/core/internal/instr/LocalProbeArrayStrategy.java
@@ -38,7 +38,8 @@
this.accessorGenerator = accessorGenerator;
}
- public int storeInstance(final MethodVisitor mv, final int variable) {
+ public int storeInstance(final MethodVisitor mv, final boolean clinit,
+ final int variable) {
final int maxStack = accessorGenerator.generateDataAccessor(classId,
className, probeCount, mv);
mv.visitVarInsn(Opcodes.ASTORE, variable);
diff --git a/org.jacoco.core/src/org/jacoco/core/internal/instr/NoneProbeArrayStrategy.java b/org.jacoco.core/src/org/jacoco/core/internal/instr/NoneProbeArrayStrategy.java
index 6b29518..0c4d572 100644
--- a/org.jacoco.core/src/org/jacoco/core/internal/instr/NoneProbeArrayStrategy.java
+++ b/org.jacoco.core/src/org/jacoco/core/internal/instr/NoneProbeArrayStrategy.java
@@ -20,7 +20,8 @@
*/
class NoneProbeArrayStrategy implements IProbeArrayStrategy {
- public int storeInstance(final MethodVisitor mv, final int variable) {
+ public int storeInstance(final MethodVisitor mv, final boolean clinit,
+ final int variable) {
throw new UnsupportedOperationException();
}
diff --git a/org.jacoco.core/src/org/jacoco/core/internal/instr/ProbeArrayStrategyFactory.java b/org.jacoco.core/src/org/jacoco/core/internal/instr/ProbeArrayStrategyFactory.java
index 8ce2f23..bd185fe 100644
--- a/org.jacoco.core/src/org/jacoco/core/internal/instr/ProbeArrayStrategyFactory.java
+++ b/org.jacoco.core/src/org/jacoco/core/internal/instr/ProbeArrayStrategyFactory.java
@@ -28,7 +28,8 @@
/**
* Creates a suitable strategy instance for the class described by the given
- * reader.
+ * reader. Created instance must be used only to process a class or
+ * interface for which it has been created and must be used only once.
*
* @param reader
* reader to get information about the class
@@ -50,16 +51,15 @@
return new NoneProbeArrayStrategy();
}
if (version >= Opcodes.V1_8 && counter.hasMethods()) {
- return new FieldProbeArrayStrategy(className, classId,
- withFrames, true, InstrSupport.DATAFIELD_INTF_ACC,
- accessorGenerator);
+ return new InterfaceFieldProbeArrayStrategy(className, classId,
+ counter.getCount(), accessorGenerator);
} else {
return new LocalProbeArrayStrategy(className, classId,
counter.getCount(), accessorGenerator);
}
} else {
- return new FieldProbeArrayStrategy(className, classId, withFrames,
- false, InstrSupport.DATAFIELD_ACC, accessorGenerator);
+ return new ClassFieldProbeArrayStrategy(className, classId,
+ withFrames, accessorGenerator);
}
}
diff --git a/org.jacoco.core/src/org/jacoco/core/internal/instr/ProbeCounter.java b/org.jacoco.core/src/org/jacoco/core/internal/instr/ProbeCounter.java
index 0028fcd..d45a8f9 100644
--- a/org.jacoco.core/src/org/jacoco/core/internal/instr/ProbeCounter.java
+++ b/org.jacoco.core/src/org/jacoco/core/internal/instr/ProbeCounter.java
@@ -30,7 +30,7 @@
@Override
public MethodProbesVisitor visitMethod(final int access, final String name,
final String desc, final String signature, final String[] exceptions) {
- if (!"<clinit>".equals(name)) {
+ if (!InstrSupport.CLINIT_NAME.equals(name)) {
methods = true;
}
return null;
diff --git a/org.jacoco.core/src/org/jacoco/core/internal/instr/ProbeInserter.java b/org.jacoco.core/src/org/jacoco/core/internal/instr/ProbeInserter.java
index 6cf8460..5e0084b 100644
--- a/org.jacoco.core/src/org/jacoco/core/internal/instr/ProbeInserter.java
+++ b/org.jacoco.core/src/org/jacoco/core/internal/instr/ProbeInserter.java
@@ -27,6 +27,12 @@
private final IProbeArrayStrategy arrayStrategy;
+ /**
+ * <code>true</code> if method is a class or interface initialization
+ * method.
+ */
+ private final boolean clinit;
+
/** Position of the inserted variable. */
private final int variable;
@@ -37,7 +43,9 @@
* Creates a new {@link ProbeInserter}.
*
* @param access
- * access flags of the adapted method.
+ * access flags of the adapted method
+ * @param name
+ * the method's name
* @param desc
* the method's descriptor
* @param mv
@@ -46,9 +54,10 @@
* callback to create the code that retrieves the reference to
* the probe array
*/
- ProbeInserter(final int access, final String desc, final MethodVisitor mv,
+ ProbeInserter(final int access, final String name, final String desc, final MethodVisitor mv,
final IProbeArrayStrategy arrayStrategy) {
super(JaCoCo.ASM_API_VERSION, mv);
+ this.clinit = InstrSupport.CLINIT_NAME.equals(name);
this.arrayStrategy = arrayStrategy;
int pos = (Opcodes.ACC_STATIC & access) == 0 ? 1 : 0;
for (final Type t : Type.getArgumentTypes(desc)) {
@@ -82,7 +91,7 @@
@Override
public void visitCode() {
- accessorStackSize = arrayStrategy.storeInstance(mv, variable);
+ accessorStackSize = arrayStrategy.storeInstance(mv, clinit, variable);
mv.visitCode();
}
diff --git a/org.jacoco.doc/docroot/doc/changes.html b/org.jacoco.doc/docroot/doc/changes.html
index b2e9d4b..e62b4a8 100644
--- a/org.jacoco.doc/docroot/doc/changes.html
+++ b/org.jacoco.doc/docroot/doc/changes.html
@@ -29,6 +29,10 @@
<h3>Fixed Bugs</h3>
<ul>
+ <li>Fix instrumentation to not violate Java Virtual Machine Specification regarding
+ initialization of final fields, otherwise <code>IllegalAccessError</code>
+ will be thrown starting from OpenJDK 9 EA b127
+ (GitHub <a href="https://github.com/jacoco/jacoco/issues/434">#434</a>).</li>
<li>Fix instrumentation of interfaces with default methods to not create incorrect
constant pool entries, which lead to <code>IncompatibleClassChangeError</code>
starting from OpenJDK 9 EA b122
diff --git a/org.jacoco.tests/pom.xml b/org.jacoco.tests/pom.xml
index f91f275..742e90d 100644
--- a/org.jacoco.tests/pom.xml
+++ b/org.jacoco.tests/pom.xml
@@ -50,7 +50,7 @@
<artifactId>jacoco-maven-plugin</artifactId>
<version>${project.version}</version>
<configuration>
- <exclClassLoaders>sun.reflect.DelegatingClassLoader:org.jacoco.core.test.TargetLoader</exclClassLoaders>
+ <exclClassLoaders>sun.reflect.DelegatingClassLoader:org.jacoco.core.test.TargetLoader:org.jacoco.core.test.InstrumentingLoader</exclClassLoaders>
<sessionId>${project.artifactId}</sessionId>
<includes>
<include>${jacoco.includes}</include>