Skip to content

Commit

Permalink
Restore exec file compatibility after upgrade of ASM to version 9.5 (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
Godin committed Oct 10, 2023
1 parent 36fc079 commit 206e5be
Show file tree
Hide file tree
Showing 5 changed files with 179 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

import org.jacoco.core.analysis.ILine;
import org.jacoco.core.analysis.IMethodCoverage;
import org.jacoco.core.data.ExecutionDataWriter;
import org.jacoco.core.internal.analysis.filter.FilterContextMock;
import org.jacoco.core.internal.analysis.filter.Filters;
import org.jacoco.core.internal.analysis.filter.IFilter;
Expand Down Expand Up @@ -137,6 +138,62 @@ public void linear_instruction_sequence_should_ignore_instructions_when_filter_i
assertLine(1002, 0, 1, 0, 0);
}

// === Scenario: method invocation after zero line number

/**
* @see org.jacoco.core.internal.flow.LabelFlowAnalyzer#visitLineNumber(int,
* Label)
* @see org.jacoco.core.internal.instr.ZeroLineNumberTest
*/
private void createZeroLineNumber() {
final Label l0 = new Label();
method.visitLabel(l0);
method.visitLineNumber(1001, l0);
method.visitInsn(Opcodes.NOP);
final Label l1 = new Label();
method.visitLabel(l1);
method.visitLineNumber(0, l1);
method.visitMethodInsn(Opcodes.INVOKESTATIC, "Foo", "foo", "()V",
false);
method.visitInsn(Opcodes.NOP);
final Label l2 = new Label();
method.visitLabel(l2);
method.visitLineNumber(1002, l2);
method.visitInsn(Opcodes.RETURN);
}

@Test
public void zero_line_number_should_create_1_probe() {
createZeroLineNumber();
runMethodAnalzer();
assertEquals(1, nextProbeId);

// workaround for zero line number can be removed if needed
// during change of exec file version
assertEquals(0x1007, ExecutionDataWriter.FORMAT_VERSION);
}

@Test
public void zero_line_number_should_show_missed_when_no_probes_are_executed() {
createZeroLineNumber();
runMethodAnalzer();

assertLine(1001, 1, 0, 0, 0);
assertLine(0, 2, 0, 0, 0);
assertLine(1002, 1, 0, 0, 0);
}

@Test
public void zero_line_number_should_show_covered_when_probe_is_executed() {
createZeroLineNumber();
probes[0] = true;
runMethodAnalzer();

assertLine(1001, 0, 1, 0, 0);
assertLine(0, 0, 2, 0, 0);
assertLine(1002, 0, 1, 0, 0);
}

// === Scenario: simple if branch ===

private void createIfBranch() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,14 @@
*******************************************************************************/
package org.jacoco.core.internal.flow;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.objectweb.asm.Opcodes.*;

import org.jacoco.core.data.ExecutionDataWriter;
import org.junit.Before;
import org.junit.Test;
import org.objectweb.asm.Label;
Expand Down Expand Up @@ -304,6 +306,20 @@ public void testLineNumber() {
assertSame(label, analyzer.lineStart);
}

/**
* @see org.jacoco.core.internal.analysis.MethodAnalyzerTest#zero_line_number_should_create_1_probe()
* @see org.jacoco.core.internal.instr.ZeroLineNumberTest
*/
@Test
public void visitLineNumber_should_skip_zero() {
analyzer.visitLineNumber(0, label);
assertNull(analyzer.lineStart);

// workaround for zero line number can be removed if needed
// during change of exec file version
assertEquals(0x1007, ExecutionDataWriter.FORMAT_VERSION);
}

@Test
public void testMethodInsn() {
analyzer.visitLineNumber(42, label);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
/*******************************************************************************
* Copyright (c) 2009, 2023 Mountainminds GmbH & Co. KG and Contributors
* This program and the accompanying materials are made available under
* the terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Evgeny Mandrikov - initial API and implementation
*
*******************************************************************************/
package org.jacoco.core.internal.instr;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;

import org.jacoco.core.instr.Instrumenter;
import org.jacoco.core.internal.data.CRC64;
import org.jacoco.core.runtime.IRuntime;
import org.jacoco.core.runtime.RuntimeData;
import org.jacoco.core.runtime.SystemPropertiesRuntime;
import org.jacoco.core.test.TargetLoader;
import org.junit.Test;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;

/**
* @see org.jacoco.core.internal.flow.LabelFlowAnalyzer#visitLineNumber(int,
* Label)
* @see org.jacoco.core.internal.analysis.MethodAnalyzerTest#zero_line_number_should_create_1_probe()
*/
public class ZeroLineNumberTest {

@Test
public void zero_line_numbers_should_be_preserved_during_instrumentation_and_should_not_cause_insertion_of_additional_probes()
throws Exception {
final IRuntime runtime = new SystemPropertiesRuntime();
final RuntimeData data = new RuntimeData();
runtime.startup(data);

final byte[] original = createClass();
final byte[] instrumented = new Instrumenter(runtime)
.instrument(original, "Sample");

final Class<?> cls = new TargetLoader().add("Sample", instrumented);
try {
cls.newInstance();
fail("Exception expected");
} catch (final Exception e) {
assertEquals(0, e.getStackTrace()[1].getLineNumber());
}

data.getExecutionData(CRC64.classId(original), "Sample", 2);
}

private static byte[] createClass() {
final ClassWriter cv = new ClassWriter(ClassWriter.COMPUTE_MAXS);
cv.visit(Opcodes.V1_5, Opcodes.ACC_PUBLIC, "Sample", null,
"java/lang/Object", null);
cv.visitSource("Sample.java", null);

MethodVisitor mv = cv.visitMethod(Opcodes.ACC_PUBLIC, "<init>", "()V",
null, null);
mv.visitCode();
final Label label1 = new Label();
mv.visitLabel(label1);
mv.visitLineNumber(1, label1);
mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object", "<init>",
"()V", false);
final Label label2 = new Label();
mv.visitLabel(label2);
mv.visitLineNumber(0, label2);
mv.visitMethodInsn(Opcodes.INVOKESTATIC, "Sample", "throw", "()V",
false);
mv.visitInsn(Opcodes.RETURN);
mv.visitMaxs(0, 0);
mv.visitEnd();

mv = cv.visitMethod(Opcodes.ACC_STATIC, "throw", "()V", null, null);
mv.visitCode();
mv.visitTypeInsn(Opcodes.NEW, "java/lang/RuntimeException");
mv.visitInsn(Opcodes.DUP);
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/RuntimeException",
"<init>", "()V", false);
mv.visitInsn(Opcodes.ATHROW);
mv.visitMaxs(0, 0);
mv.visitEnd();

cv.visitEnd();
return cv.toByteArray();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,12 @@ public void visitLabel(final Label label) {

@Override
public void visitLineNumber(final int line, final Label start) {
if (line == 0) {
// ASM versions prior to 9.5 were ignoring zero line numbers
// (https://gitlab.ow2.org/asm/asm/-/issues/317989)
// so we ignore them here to preserve exec file compatibility
return;
}
lineStart = start;
}

Expand Down
3 changes: 3 additions & 0 deletions org.jacoco.doc/docroot/doc/changes.html
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ <h3>Fixed bugs</h3>
local variable of method parameters is overridden in the method body to
store a value of type long or double
(GitHub <a href="https://github.com/jacoco/jacoco/issues/893">#893</a>).</li>
<li>Restore exec file compatibility with versions from 0.7.5 to 0.8.8
in case of class files with zero line numbers
(GitHub <a href="https://github.com/jacoco/jacoco/issues/1492">#1492</a>).</li>
</ul>

<h3>Non-functional Changes</h3>
Expand Down

0 comments on commit 206e5be

Please sign in to comment.