Fixed inconsistent stackmap frames when instrumenting class files
produced by certain tools like ProGuard (GitHub #85).
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 e6f3ce1..250aa14 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
@@ -209,25 +209,64 @@
}
@Test
- public void testVisitFrameDeadCode() {
- ProbeInserter pi = new ProbeInserter(0, "(J)V", actualVisitor,
- arrayStrategy);
+ public void testVisitFrameNoLocals() {
+ ProbeInserter pi = new ProbeInserter(Opcodes.ACC_STATIC, "()V",
+ actualVisitor, arrayStrategy);
- // Such sequences are generated by ASM to replace dead code, see
- // http://asm.ow2.org/doc/developer-guide.html#deadcode
- pi.visitFrame(Opcodes.F_NEW, 0, new Object[] {}, 1,
- new Object[] { "java/lang/Throwable" });
- pi.visitInsn(Opcodes.NOP);
- pi.visitInsn(Opcodes.NOP);
- pi.visitInsn(Opcodes.ATHROW);
+ pi.visitFrame(Opcodes.F_NEW, 0, new Object[] {}, 0, new Object[0]);
+
+ expectedVisitor.visitFrame(Opcodes.F_NEW, 1, new Object[] { "[Z" }, 0,
+ new Object[0]);
+ }
+
+ @Test
+ public void testVisitFrameProbeAt0() {
+ ProbeInserter pi = new ProbeInserter(Opcodes.ACC_STATIC, "()V",
+ actualVisitor, arrayStrategy);
+
+ pi.visitFrame(Opcodes.F_NEW, 2, new Object[] { Opcodes.DOUBLE, "Foo" },
+ 0, new Object[0]);
+
+ expectedVisitor.visitFrame(Opcodes.F_NEW, 3, new Object[] { "[Z",
+ Opcodes.DOUBLE, "Foo" }, 0, new Object[0]);
+ }
+
+ @Test
+ public void testFillOneWord() {
+ ProbeInserter pi = new ProbeInserter(Opcodes.ACC_STATIC, "(I)V",
+ actualVisitor, arrayStrategy);
+
+ pi.visitFrame(Opcodes.F_NEW, 0, new Object[] {}, 0, new Object[] {});
+
+ // The locals in this frame are filled with TOP up to the probe variable
+ expectedVisitor.visitFrame(Opcodes.F_NEW, 2, new Object[] {
+ Opcodes.TOP, "[Z", }, 0, new Object[] {});
+ }
+
+ @Test
+ public void testFillTwoWord() {
+ ProbeInserter pi = new ProbeInserter(Opcodes.ACC_STATIC, "(J)V",
+ actualVisitor, arrayStrategy);
+
+ pi.visitFrame(Opcodes.F_NEW, 0, new Object[] {}, 0, new Object[] {});
// The locals in this frame are filled with TOP up to the probe variable
expectedVisitor.visitFrame(Opcodes.F_NEW, 3, new Object[] {
- Opcodes.TOP, Opcodes.TOP, "[Z", }, 1,
- new Object[] { "java/lang/Throwable" });
- expectedVisitor.visitInsn(Opcodes.NOP);
- expectedVisitor.visitInsn(Opcodes.NOP);
- expectedVisitor.visitInsn(Opcodes.ATHROW);
+ Opcodes.TOP, Opcodes.TOP, "[Z", }, 0, new Object[] {});
+ }
+
+ @Test
+ public void testFillPartly() {
+ ProbeInserter pi = new ProbeInserter(Opcodes.ACC_STATIC, "(DIJ)V",
+ actualVisitor, arrayStrategy);
+
+ pi.visitFrame(Opcodes.F_NEW, 1, new Object[] { Opcodes.DOUBLE }, 0,
+ new Object[] {});
+
+ // The locals in this frame are filled with TOP up to the probe variable
+ expectedVisitor.visitFrame(Opcodes.F_NEW, 5, new Object[] {
+ Opcodes.DOUBLE, Opcodes.TOP, Opcodes.TOP, Opcodes.TOP, "[Z", },
+ 0, new Object[] {});
}
@Test(expected = IllegalArgumentException.class)
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 b250fc8..2db2d39 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
@@ -29,9 +29,6 @@
/** Position of the inserted variable. */
private final int variable;
- /** Index the inserted variable. */
- private final int variableIdx;
-
/** Maximum stack usage of the code to access the probe array. */
private int accessorStackSize;
@@ -52,13 +49,10 @@
final IProbeArrayStrategy arrayStrategy) {
super(Opcodes.ASM4, mv);
this.arrayStrategy = arrayStrategy;
- int idx = (Opcodes.ACC_STATIC & access) == 0 ? 1 : 0;
- int pos = idx;
+ int pos = (Opcodes.ACC_STATIC & access) == 0 ? 1 : 0;
for (final Type t : Type.getArgumentTypes(desc)) {
- idx++;
pos += t.getSize();
}
- variableIdx = idx;
variable = pos;
}
@@ -109,12 +103,6 @@
}
@Override
- public final void visitFieldInsn(final int opcode, final String owner,
- final String name, final String desc) {
- mv.visitFieldInsn(opcode, owner, name, desc);
- }
-
- @Override
public void visitMaxs(final int maxStack, final int maxLocals) {
// Max stack size of the probe code is 3 which can add to the
// original stack size depending on the probe locations. The accessor
@@ -141,22 +129,30 @@
"ClassReader.accept() should be called with EXPAND_FRAMES flag");
}
- final int n = Math.max(nLocal, variableIdx) + 1;
- final Object[] newLocal = new Object[n];
- for (int i = 0; i < n; i++) {
- if (i < variableIdx) {
- // For dead code it is possible to specify less locals than
- // we have method parameters.
- newLocal[i] = i < nLocal ? local[i] : Opcodes.TOP;
- continue;
+ final Object[] newLocal = new Object[Math.max(nLocal, variable) + 1];
+ int idx = 0; // Arrays index for existing locals
+ int newIdx = 0; // Array index for new locals
+ int pos = 0; // Current variable position
+ while (idx < nLocal || pos <= variable) {
+ if (pos == variable) {
+ newLocal[newIdx++] = InstrSupport.DATAFIELD_DESC;
+ pos++;
+ } else {
+ if (idx < nLocal) {
+ final Object t = local[idx++];
+ newLocal[newIdx++] = t;
+ pos++;
+ if (t == Opcodes.LONG || t == Opcodes.DOUBLE) {
+ pos++;
+ }
+ } else {
+ // Fill unused slots with TOP
+ newLocal[newIdx++] = Opcodes.TOP;
+ pos++;
+ }
}
- if (i > variableIdx) {
- newLocal[i] = local[i - 1];
- continue;
- }
- newLocal[i] = InstrSupport.DATAFIELD_DESC;
}
- mv.visitFrame(type, n, newLocal, nStack, stack);
+ mv.visitFrame(type, newIdx, newLocal, nStack, stack);
}
}
diff --git a/org.jacoco.doc/docroot/doc/changes.html b/org.jacoco.doc/docroot/doc/changes.html
index ef166c4..6ce065a 100644
--- a/org.jacoco.doc/docroot/doc/changes.html
+++ b/org.jacoco.doc/docroot/doc/changes.html
@@ -20,6 +20,12 @@
<h2>Trunk Build @qualified.bundle.version@ (@build.date@)</h2>
+<h3>Fixed Bugs</h3>
+<ul>
+ <li>Fixed inconsistent stackmap frames when instrumenting class files produced
+ by certain tools like ProGuard (GitHub #85).</li>
+</ul>
+
<h2>Release 0.6.2 (2013/02/03)</h2>
<h3>New Features</h3>