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>