Skip to content

Commit

Permalink
Add filter for Java assert statement (#1196)
Browse files Browse the repository at this point in the history
  • Loading branch information
Godin committed Jun 10, 2021
1 parent ec70eb5 commit 5d24b06
Show file tree
Hide file tree
Showing 9 changed files with 332 additions and 1 deletion.
@@ -0,0 +1,27 @@
/*******************************************************************************
* Copyright (c) 2009, 2021 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.test.validation.java5;

import org.jacoco.core.test.validation.ValidationTestBase;
import org.jacoco.core.test.validation.java5.targets.AssertTarget;

/**
* Test of code coverage in {@link AssertTarget}.
*/
public class AssertTest extends ValidationTestBase {

public AssertTest() {
super(AssertTarget.class);
}

}
@@ -0,0 +1,29 @@
/*******************************************************************************
* Copyright (c) 2009, 2021 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.test.validation.java5.targets;

import static org.jacoco.core.test.validation.targets.Stubs.t;

/**
* This target exercises assert statement.
*/
public class AssertTarget { // assertFullyCovered()

private AssertTarget() {
}

public static void main(String[] args) {
assert t() : "msg"; // assertPartlyCovered(1, 1)
}

}
Expand Up @@ -137,6 +137,42 @@ public void nextIsVar() {
matcher.nextIsVar(Opcodes.ILOAD, "name");
}

@Test
public void nextIsField() {
m.visitInsn(Opcodes.NOP);
m.visitFieldInsn(Opcodes.PUTSTATIC, "owner", "name", "Z");

// should set cursor to null when opcode mismatch
matcher.cursor = m.instructions.getFirst();
matcher.nextIsField(Opcodes.GETSTATIC, "owner", "name", "Z");
assertNull(matcher.cursor);

// should set cursor to null when owner mismatch
matcher.cursor = m.instructions.getFirst();
matcher.nextIsField(Opcodes.PUTSTATIC, "another_owner", "name", "Z");
assertNull(matcher.cursor);

// should set cursor to null when name mismatch
matcher.cursor = m.instructions.getFirst();
matcher.nextIsField(Opcodes.PUTSTATIC, "owner", "another_name", "Z");
assertNull(matcher.cursor);

// should set cursor to null when descriptor mismatch
matcher.cursor = m.instructions.getFirst();
matcher.nextIsField(Opcodes.PUTSTATIC, "owner", "name",
"another_descriptor");
assertNull(matcher.cursor);

// should set cursor to next instruction when match
matcher.cursor = m.instructions.getFirst();
matcher.nextIsField(Opcodes.PUTSTATIC, "owner", "name", "Z");
assertSame(m.instructions.getLast(), matcher.cursor);

// should not do anything when cursor is null
matcher.cursor = null;
matcher.nextIsField(Opcodes.PUTSTATIC, "owner", "name", "Z");
}

@Test
public void nextIsInvoke() {
m.visitInsn(Opcodes.NOP);
Expand Down
@@ -0,0 +1,145 @@
/*******************************************************************************
* Copyright (c) 2009, 2021 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.analysis.filter;

import org.jacoco.core.internal.instr.InstrSupport;
import org.junit.Test;
import org.objectweb.asm.Label;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.MethodNode;

/**
* Unit tests for {@link AssertFilter}.
*/
public class AssertFilterTest extends FilterTestBase {

private final AssertFilter filter = new AssertFilter();

/**
* <code><pre>
* class Example {
* void example(boolean b) {
* ...
* assert b : "message";
* ...
* }
* }
* </pre></code>
*/
@Test
public void should_filter_static_initializer() {
context.className = "Example";
final MethodNode m = new MethodNode(InstrSupport.ASM_API_VERSION,
Opcodes.ACC_STATIC, "<clinit>", "()V", null, null);

m.visitLdcInsn(Type.getType("LExample;"));
final Range range = new Range();
range.fromInclusive = m.instructions.getLast();
m.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Class",
"desiredAssertionStatus", "()Z", false);
final Label label1 = new Label();
final Label label2 = new Label();
m.visitJumpInsn(Opcodes.IFNE, label1);
m.visitInsn(Opcodes.ICONST_1);
m.visitJumpInsn(Opcodes.GOTO, label2);
m.visitLabel(label1);
m.visitInsn(Opcodes.ICONST_0);
m.visitLabel(label2);
m.visitFieldInsn(Opcodes.PUTSTATIC, "Example", "$assertionsDisabled",
"Z");
range.toInclusive = m.instructions.getLast();
m.visitInsn(Opcodes.RETURN);

filter.filter(m, context, output);

assertIgnored(range);
}

/**
* <code><pre>
* class Example {
* static final f;
*
* static {
* f = !Example.class.desiredAssertionStatus();
* }
* }
* </pre></code>
*/
@Test
public void should_not_filter_static_initializer_when_field_name_does_not_match() {
context.className = "Example";
final MethodNode m = new MethodNode(InstrSupport.ASM_API_VERSION,
Opcodes.ACC_STATIC, "<clinit>", "()V", null, null);

m.visitLdcInsn(Type.getType("LExample;"));
m.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Class",
"desiredAssertionStatus", "()Z", false);
final Label label1 = new Label();
final Label label2 = new Label();
m.visitJumpInsn(Opcodes.IFNE, label1);
m.visitInsn(Opcodes.ICONST_1);
m.visitJumpInsn(Opcodes.GOTO, label2);
m.visitLabel(label1);
m.visitInsn(Opcodes.ICONST_0);
m.visitLabel(label2);
m.visitFieldInsn(Opcodes.PUTSTATIC, "Foo", "f", "Z");
m.visitInsn(Opcodes.RETURN);

filter.filter(m, context, output);

assertIgnored();
}

/**
* <code><pre>
* class Example {
* void example(boolean b) {
* ...
* assert b : "message";
* ...
* }
* }
* </pre></code>
*/
@Test
public void should_filter_assert() {
context.className = "Example";
final MethodNode m = new MethodNode(InstrSupport.ASM_API_VERSION, 0,
"example", "()V", null, null);

m.visitInsn(Opcodes.NOP);
m.visitFieldInsn(Opcodes.GETSTATIC, "Example", "$assertionsDisabled",
"Z");
final Label label = new Label();
m.visitJumpInsn(Opcodes.IFNE, label);
final Range range = new Range(m.instructions.getLast(),
m.instructions.getLast());
m.visitVarInsn(Opcodes.ILOAD, 1);
m.visitJumpInsn(Opcodes.IFNE, label);
m.visitTypeInsn(Opcodes.NEW, "java/lang/AssertionError");
m.visitInsn(Opcodes.DUP);
m.visitLdcInsn("message");
m.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/AssertionError",
"<init>", "(Ljava/lang/Object;)V", false);
m.visitInsn(Opcodes.ATHROW);
m.visitLabel(label);
m.visitInsn(Opcodes.NOP);

filter.filter(m, context, output);

assertIgnored(range);
}

}
Expand Up @@ -71,6 +71,7 @@ public void setup() throws Exception {

private ExecutionDataStore execute() throws Exception {
loader = new InstrumentingLoader(target);
loader.setDefaultAssertionStatus(true);
run(loader.loadClass(target.getName()));
return loader.collect();
}
Expand Down
Expand Up @@ -17,6 +17,7 @@

import org.objectweb.asm.Opcodes;
import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.FieldInsnNode;
import org.objectweb.asm.tree.MethodInsnNode;
import org.objectweb.asm.tree.MethodNode;
import org.objectweb.asm.tree.TypeInsnNode;
Expand Down Expand Up @@ -76,6 +77,25 @@ final void nextIsInvoke(final int opcode, final String owner,
cursor = null;
}

/**
* Moves {@link #cursor} to next instruction if it is {@link FieldInsnNode}
* with given opcode, owner, name and descriptor, otherwise sets it to
* <code>null</code>.
*/
final void nextIsField(final int opcode, final String owner,
final String name, final String descriptor) {
nextIs(opcode);
if (cursor == null) {
return;
}
final FieldInsnNode f = (FieldInsnNode) cursor;
if (owner.equals(f.owner) && name.equals(f.name)
&& descriptor.equals(f.desc)) {
return;
}
cursor = null;
}

final void nextIsVar(final int opcode, final String name) {
nextIs(opcode);
if (cursor == null) {
Expand Down
@@ -0,0 +1,66 @@
/*******************************************************************************
* Copyright (c) 2009, 2021 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.analysis.filter;

import org.objectweb.asm.Opcodes;
import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.MethodNode;

/**
* Filters code that is generated for an <code>assert</code> statement.
*/
final class AssertFilter implements IFilter {

public void filter(final MethodNode methodNode,
final IFilterContext context, final IFilterOutput output) {
final Matcher matcher = new Matcher();
if ("<clinit>".equals(methodNode.name)) {
for (final AbstractInsnNode i : methodNode.instructions) {
matcher.matchSet(context.getClassName(), i, output);
}
}
for (final AbstractInsnNode i : methodNode.instructions) {
matcher.matchGet(context.getClassName(), i, output);
}
}

private static class Matcher extends AbstractMatcher {
public void matchSet(final String className,
final AbstractInsnNode start, final IFilterOutput output) {
cursor = start;
nextIsInvoke(Opcodes.INVOKEVIRTUAL, "java/lang/Class",
"desiredAssertionStatus", "()Z");
nextIs(Opcodes.IFNE);
nextIs(Opcodes.ICONST_1);
nextIs(Opcodes.GOTO);
nextIs(Opcodes.ICONST_0);
nextIsField(Opcodes.PUTSTATIC, className, "$assertionsDisabled",
"Z");
if (cursor != null) {
output.ignore(start, cursor);
}
}

public void matchGet(final String className,
final AbstractInsnNode start, final IFilterOutput output) {
cursor = start;
nextIsField(Opcodes.GETSTATIC, className, "$assertionsDisabled",
"Z");
nextIs(Opcodes.IFNE);
if (cursor != null) {
output.ignore(cursor, cursor);
}
}
}

}
Expand Up @@ -37,7 +37,7 @@ public static IFilter all() {
new TryWithResourcesJavac11Filter(),
new TryWithResourcesJavacFilter(),
new TryWithResourcesEcjFilter(), new FinallyFilter(),
new PrivateEmptyNoArgConstructorFilter(),
new PrivateEmptyNoArgConstructorFilter(), new AssertFilter(),
new StringSwitchJavacFilter(), new StringSwitchFilter(),
new EnumEmptyConstructorFilter(), new RecordsFilter(),
new AnnotationGeneratedFilter(), new KotlinGeneratedFilter(),
Expand Down
7 changes: 7 additions & 0 deletions org.jacoco.doc/docroot/doc/changes.html
Expand Up @@ -20,6 +20,13 @@ <h1>Change History</h1>

<h2>Snapshot Build @qualified.bundle.version@ (@build.date@)</h2>

<h3>New Features</h3>
<ul>
<li>Part of bytecode generated by the Java compilers for <code>assert</code>
statement is filtered out during generation of report
(GitHub <a href="https://github.com/jacoco/jacoco/issues/1196">#1196</a>).</li>
</ul>

<h3>Fixed bugs</h3>
<ul>
<li>Fixed <code>NullPointerException</code> during filtering
Expand Down

0 comments on commit 5d24b06

Please sign in to comment.