Trac #196: Remove class file attributes with invalid code offsets caused by other byte code processing tools to avoid verifier errors.
diff --git a/org.jacoco.core.test/src/org/jacoco/core/internal/flow/MethodSanitizerTest.java b/org.jacoco.core.test/src/org/jacoco/core/internal/flow/MethodSanitizerTest.java
new file mode 100644
index 0000000..b75a502
--- /dev/null
+++ b/org.jacoco.core.test/src/org/jacoco/core/internal/flow/MethodSanitizerTest.java
@@ -0,0 +1,152 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2012 Mountainminds GmbH & Co. KG and Contributors
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Marc R. Hoffmann - initial API and implementation
+ *
+ *******************************************************************************/
+package org.jacoco.core.internal.flow;
+
+import static org.junit.Assert.assertEquals;
+
+import java.util.List;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.tree.MethodNode;
+import org.objectweb.asm.util.TraceMethodVisitor;
+
+/**
+ * Unit tests for {@link MethodSanitizer}.
+ */
+public class MethodSanitizerTest {
+
+ private MethodNode actual;
+ private MethodNode expected;
+
+ private MethodVisitor sanitizer;
+
+ @Before
+ public void setup() {
+ actual = new MethodNode(0, "test", "()V", null, null);
+ expected = new MethodNode(0, "test", "()V", null, null);
+ sanitizer = new MethodSanitizer(actual, 0, "test", "()V", null, null);
+ }
+
+ @Test
+ public void testLocalVariablePositive() {
+ Label l1 = new Label();
+ Label l2 = new Label();
+ sanitizer.visitCode();
+ sanitizer.visitLabel(l1);
+ sanitizer.visitInsn(Opcodes.RETURN);
+ sanitizer.visitLabel(l2);
+ sanitizer.visitLocalVariable("x", "I", null, l1, l2, 0);
+ sanitizer.visitMaxs(0, 0);
+ sanitizer.visitEnd();
+
+ Label m1 = new Label();
+ Label m2 = new Label();
+ expected.visitLabel(m1);
+ expected.visitInsn(Opcodes.RETURN);
+ expected.visitLabel(m2);
+ expected.visitLocalVariable("x", "I", null, m1, m2, 0);
+ expected.visitMaxs(0, 0);
+ expected.visitEnd();
+
+ assertOutput();
+ }
+
+ @Test
+ public void testLocalVariableNegative1() {
+ Label l1 = new Label();
+ Label l2 = new Label();
+ sanitizer.visitCode();
+ sanitizer.visitInsn(Opcodes.RETURN);
+ sanitizer.visitLabel(l2);
+ sanitizer.visitLocalVariable("x", "I", null, l1, l2, 0);
+ sanitizer.visitMaxs(0, 0);
+ sanitizer.visitEnd();
+
+ Label m2 = new Label();
+ expected.visitInsn(Opcodes.RETURN);
+ expected.visitLabel(m2);
+ expected.visitMaxs(0, 0);
+ expected.visitEnd();
+
+ assertOutput();
+ }
+
+ @Test
+ public void testLocalVariableNegative2() {
+ Label l1 = new Label();
+ Label l2 = new Label();
+ sanitizer.visitCode();
+ sanitizer.visitLabel(l1);
+ sanitizer.visitInsn(Opcodes.RETURN);
+ sanitizer.visitLocalVariable("x", "I", null, l1, l2, 0);
+ sanitizer.visitMaxs(0, 0);
+ sanitizer.visitEnd();
+
+ Label m1 = new Label();
+ expected.visitLabel(m1);
+ expected.visitInsn(Opcodes.RETURN);
+ expected.visitMaxs(0, 0);
+ expected.visitEnd();
+
+ assertOutput();
+ }
+
+ @Test
+ public void testLineNumberPositive() {
+ Label l1 = new Label();
+ sanitizer.visitCode();
+ sanitizer.visitLabel(l1);
+ sanitizer.visitLineNumber(15, l1);
+ sanitizer.visitInsn(Opcodes.RETURN);
+ sanitizer.visitMaxs(0, 0);
+ sanitizer.visitEnd();
+
+ Label m1 = new Label();
+ expected.visitLabel(m1);
+ expected.visitLineNumber(15, m1);
+ expected.visitInsn(Opcodes.RETURN);
+ expected.visitMaxs(0, 0);
+ expected.visitEnd();
+
+ assertOutput();
+ }
+
+ @Test
+ public void testLineNumberNegative() {
+ Label l1 = new Label();
+ sanitizer.visitCode();
+ sanitizer.visitLineNumber(15, l1);
+ sanitizer.visitInsn(Opcodes.RETURN);
+ sanitizer.visitMaxs(0, 0);
+ sanitizer.visitEnd();
+
+ expected.visitInsn(Opcodes.RETURN);
+ expected.visitMaxs(0, 0);
+ expected.visitEnd();
+
+ assertOutput();
+ }
+
+ private void assertOutput() {
+ assertEquals(dump(expected), dump(actual));
+ }
+
+ private List<?> dump(MethodNode node) {
+ final TraceMethodVisitor trace = new TraceMethodVisitor();
+ node.accept(trace);
+ return trace.getText();
+ }
+}
diff --git a/org.jacoco.core/src/org/jacoco/core/internal/flow/ClassProbesAdapter.java b/org.jacoco.core/src/org/jacoco/core/internal/flow/ClassProbesAdapter.java
index ab63c1d..e542c3d 100644
--- a/org.jacoco.core/src/org/jacoco/core/internal/flow/ClassProbesAdapter.java
+++ b/org.jacoco.core/src/org/jacoco/core/internal/flow/ClassProbesAdapter.java
@@ -16,7 +16,6 @@
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.commons.EmptyVisitor;
-import org.objectweb.asm.commons.JSRInlinerAdapter;
/**
* A {@link org.objectweb.asm.ClassVisitor} that calculates probes for every
@@ -102,8 +101,9 @@
} else {
methodProbes = mv;
}
- return new JSRInlinerAdapter(null, access, name, desc, signature,
+ return new MethodSanitizer(null, access, name, desc, signature,
exceptions) {
+
@Override
public void visitEnd() {
super.visitEnd();
diff --git a/org.jacoco.core/src/org/jacoco/core/internal/flow/MethodSanitizer.java b/org.jacoco.core/src/org/jacoco/core/internal/flow/MethodSanitizer.java
new file mode 100644
index 0000000..c672eb2
--- /dev/null
+++ b/org.jacoco.core/src/org/jacoco/core/internal/flow/MethodSanitizer.java
@@ -0,0 +1,58 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2012 Mountainminds GmbH & Co. KG and Contributors
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Marc R. Hoffmann - initial API and implementation
+ *
+ *******************************************************************************/
+package org.jacoco.core.internal.flow;
+
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.commons.JSRInlinerAdapter;
+
+/**
+ * This method visitor fixes two potential issues with Java byte code:
+ *
+ * <ul>
+ * <li>Remove JSR instructions by inlining subroutines.</li>
+ * <li>Remove code attributes line number and local variable name if they point
+ * to invalid offsets which some tools create. When writing out such invalid
+ * labels with ASM class files do not verify any more.</li>
+ * </ul>
+ */
+class MethodSanitizer extends JSRInlinerAdapter {
+
+ MethodSanitizer(final MethodVisitor mv, final int access,
+ final String name, final String desc, final String signature,
+ final String[] exceptions) {
+ super(mv, access, name, desc, signature, exceptions);
+ }
+
+ @Override
+ public void visitLocalVariable(final String name, final String desc,
+ final String signature, final Label start, final Label end,
+ final int index) {
+ // Here we rely on the usage of the info fields by the tree API. If the
+ // labels have been properly used before the info field contains a
+ // reference to the LabelNode, otherwise null.
+ if (start.info != null && end.info != null) {
+ super.visitLocalVariable(name, desc, signature, start, end, index);
+ }
+ }
+
+ @Override
+ public void visitLineNumber(final int line, final Label start) {
+ // Here we rely on the usage of the info fields by the tree API. If the
+ // labels have been properly used before the info field contains a
+ // reference to the LabelNode, otherwise null.
+ if (start.info != null) {
+ super.visitLineNumber(line, start);
+ }
+ }
+
+}
diff --git a/org.jacoco.doc/docroot/doc/changes.html b/org.jacoco.doc/docroot/doc/changes.html
index b7a76f1..49bbbe4 100644
--- a/org.jacoco.doc/docroot/doc/changes.html
+++ b/org.jacoco.doc/docroot/doc/changes.html
@@ -23,6 +23,8 @@
<h3>New Features</h3>
<ul>
<li>Support for class redefinitions by other agents like JMockit (SF #3509409).</li>
+ <li>Remove class file attributes with invalid code offsets caused by other
+ byte code processing tools to avoid verifier errors (Trac #196).
</ul>
<h3>Fixed Bugs</h3>