From f6e483cfa84d8f95fd3443106d5f5cc06999aba9 Mon Sep 17 00:00:00 2001 From: Brett Kail Date: Mon, 16 Oct 2017 23:12:35 -0500 Subject: [PATCH] Add filter for assert statement --- .../analysis/filter/AssertFilterTest.java | 124 ++++++++++++++++++ .../jacoco/core/test/filter/AssertTest.java | 58 ++++++++ .../core/test/filter/targets/Assert.java | 40 ++++++ .../test/validation/ValidationTestBase.java | 46 ++++++- .../analysis/filter/AssertFilter.java | 115 ++++++++++++++++ .../internal/analysis/filter/Filters.java | 2 +- org.jacoco.doc/docroot/doc/changes.html | 3 + 7 files changed, 384 insertions(+), 4 deletions(-) create mode 100644 org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/AssertFilterTest.java create mode 100644 org.jacoco.core.test/src/org/jacoco/core/test/filter/AssertTest.java create mode 100644 org.jacoco.core.test/src/org/jacoco/core/test/filter/targets/Assert.java create mode 100644 org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/AssertFilter.java diff --git a/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/AssertFilterTest.java b/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/AssertFilterTest.java new file mode 100644 index 0000000000..ad535dfb55 --- /dev/null +++ b/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/AssertFilterTest.java @@ -0,0 +1,124 @@ +/******************************************************************************* + * Copyright (c) 2009, 2017 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: + * + *******************************************************************************/ +package org.jacoco.core.internal.analysis.filter; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.fail; + +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.AbstractInsnNode; +import org.objectweb.asm.tree.MethodNode; + +public class AssertFilterTest implements IFilterOutput { + + private final IFilter filter = new AssertFilter(); + + private AbstractInsnNode fromInclusive; + private AbstractInsnNode toInclusive; + + @Test + public void should_filter_initialize() { + final Label disable = new Label(); + final Label init = new Label(); + + final MethodNode m = new MethodNode(InstrSupport.ASM_API_VERSION, 0, + "", "()V", null, null); + m.visitLdcInsn(Type.getObjectType("p/C")); + final AbstractInsnNode fromInclusive = m.instructions.getLast(); + + m.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Class", + "desiredAssertionStatus", "()Z", false); + m.visitJumpInsn(Opcodes.IFNE, disable); + m.visitInsn(Opcodes.ICONST_1); + m.visitJumpInsn(Opcodes.GOTO, init); + + m.visitLabel(disable); + m.visitInsn(Opcodes.ICONST_0); + + m.visitLabel(init); + m.visitFieldInsn(Opcodes.PUTSTATIC, "p/C", "$assertionsDisabled", "Z"); + final AbstractInsnNode toInclusive = m.instructions.getLast(); + + filter.filter("p/C", "java/lang/Object", m, this); + assertEquals(fromInclusive, this.fromInclusive); + assertEquals(toInclusive, this.toInclusive); + } + + @Test + public void should_filter_assert() { + final Label disabled = new Label(); + + final MethodNode m = new MethodNode(InstrSupport.ASM_API_VERSION, 0, + "m", "(Z)V", null, null); + m.visitFieldInsn(Opcodes.GETSTATIC, "p/C", "$assertionsDisabled", "Z"); + final AbstractInsnNode fromInclusive = m.instructions.getLast(); + + m.visitJumpInsn(Opcodes.IFNE, disabled); + final AbstractInsnNode toInclusive = m.instructions.getLast(); + + m.visitVarInsn(Opcodes.ILOAD, 1); + m.visitJumpInsn(Opcodes.IFNE, disabled); + m.visitTypeInsn(Opcodes.NEW, "java/lang/AssertionError"); + m.visitInsn(Opcodes.DUP); + m.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/AssertionError", + "", "()V", false); + m.visitInsn(Opcodes.ATHROW); + m.visitLabel(disabled); + + filter.filter("p/C", "java/lang/Object", m, this); + assertEquals(fromInclusive, this.fromInclusive); + assertEquals(toInclusive, this.toInclusive); + } + + @Test + public void should_filter_assert_message() { + final Label disabled = new Label(); + + final MethodNode m = new MethodNode(InstrSupport.ASM_API_VERSION, 0, + "m", "(Z)V", null, null); + m.visitFieldInsn(Opcodes.GETSTATIC, "p/C", "$assertionsDisabled", "Z"); + final AbstractInsnNode fromInclusive = m.instructions.getLast(); + + m.visitJumpInsn(Opcodes.IFNE, disabled); + final AbstractInsnNode toInclusive = m.instructions.getLast(); + + m.visitVarInsn(Opcodes.ILOAD, 1); + m.visitJumpInsn(Opcodes.IFNE, disabled); + m.visitTypeInsn(Opcodes.NEW, "java/lang/AssertionError"); + m.visitInsn(Opcodes.DUP); + m.visitLdcInsn("m"); + m.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/AssertionError", + "", "(Ljava/lang/Object;)V", false); + m.visitInsn(Opcodes.ATHROW); + m.visitLabel(disabled); + + filter.filter("p/C", "java/lang/Object", m, this); + assertEquals(fromInclusive, this.fromInclusive); + assertEquals(toInclusive, this.toInclusive); + } + + public void ignore(final AbstractInsnNode fromInclusive, + final AbstractInsnNode toInclusive) { + assertNull(this.fromInclusive); + this.fromInclusive = fromInclusive; + this.toInclusive = toInclusive; + } + + public void merge(final AbstractInsnNode i1, final AbstractInsnNode i2) { + fail(); + } + +} diff --git a/org.jacoco.core.test/src/org/jacoco/core/test/filter/AssertTest.java b/org.jacoco.core.test/src/org/jacoco/core/test/filter/AssertTest.java new file mode 100644 index 0000000000..c5e9c7abb1 --- /dev/null +++ b/org.jacoco.core.test/src/org/jacoco/core/test/filter/AssertTest.java @@ -0,0 +1,58 @@ +/******************************************************************************* + * Copyright (c) 2009, 2017 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: + * + *******************************************************************************/ +package org.jacoco.core.test.filter; + +import org.jacoco.core.analysis.ICounter; +import org.jacoco.core.test.filter.targets.Assert; +import org.jacoco.core.test.validation.ValidationTestBase; +import org.junit.Test; + +/** + * Test of filtering of a bytecode that is generated for an assert statement. + */ +public class AssertTest extends ValidationTestBase { + + public AssertTest() { + super(Assert.class); + } + + /** + * {@link Assert} + */ + @Test + public void clinit() { + assertMethod("", ICounter.FULLY_COVERED); + } + + /** + * {@link Assert#simple()} + */ + @Test + public void simple() { + assertLine("simple", ICounter.PARTLY_COVERED, 1, 1, 4, 2); + } + + /** + * {@link Assert#message()} + */ + @Test + public void message() { + assertLine("message", ICounter.PARTLY_COVERED, 1, 1, 5, 2); + } + + /** + * {@link Assert.SimpleClinit} + */ + @Test + public void clinit_simple() { + assertLine("clinit", ICounter.PARTLY_COVERED, 1, 1, 4, 2); + } +} diff --git a/org.jacoco.core.test/src/org/jacoco/core/test/filter/targets/Assert.java b/org.jacoco.core.test/src/org/jacoco/core/test/filter/targets/Assert.java new file mode 100644 index 0000000000..7d0016cc78 --- /dev/null +++ b/org.jacoco.core.test/src/org/jacoco/core/test/filter/targets/Assert.java @@ -0,0 +1,40 @@ +/******************************************************************************* + * Copyright (c) 2009, 2017 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: + * + *******************************************************************************/ +package org.jacoco.core.test.filter.targets; + +public class Assert { + + public static boolean b = true; + + public static void simple() { + assert b; // $line-simple$ + } + + public static void message() { + assert b : "m"; // $line-message$ + } + + public static class SimpleClinit { + static { + assert b; // $line-clinit$ + } + + public static void init() { + } + } + + public static void main(String[] args) { + simple(); + message(); + SimpleClinit.init(); + } + +} diff --git a/org.jacoco.core.test/src/org/jacoco/core/test/validation/ValidationTestBase.java b/org.jacoco.core.test/src/org/jacoco/core/test/validation/ValidationTestBase.java index c8693f9791..e026cd3ea6 100644 --- a/org.jacoco.core.test/src/org/jacoco/core/test/validation/ValidationTestBase.java +++ b/org.jacoco.core.test/src/org/jacoco/core/test/validation/ValidationTestBase.java @@ -12,6 +12,7 @@ package org.jacoco.core.test.validation; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.fail; import java.io.IOException; @@ -20,8 +21,10 @@ import org.jacoco.core.analysis.Analyzer; import org.jacoco.core.analysis.CoverageBuilder; +import org.jacoco.core.analysis.IClassCoverage; import org.jacoco.core.analysis.ICounter; import org.jacoco.core.analysis.ILine; +import org.jacoco.core.analysis.IMethodCoverage; import org.jacoco.core.analysis.ISourceFileCoverage; import org.jacoco.core.data.ExecutionData; import org.jacoco.core.data.ExecutionDataStore; @@ -54,6 +57,8 @@ public abstract class ValidationTestBase { private ISourceFileCoverage sourceCoverage; + private IClassCoverage classCoverage; + private Source source; private InstrumentingLoader loader; @@ -76,6 +81,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(); } @@ -92,14 +98,23 @@ private void analyze(final ExecutionDataStore store) throws IOException { analyze(analyzer, data); } - String srcName = target.getName().replace('.', '/') + ".java"; + String name = target.getName().replace('.', '/'); + String srcName = name + ".java"; for (ISourceFileCoverage file : builder.getSourceFiles()) { if (srcName.equals(file.getPackageName() + "/" + file.getName())) { sourceCoverage = file; - return; + break; + } + } + assertNotNull("No source node for " + name, sourceCoverage); + + for (IClassCoverage c : builder.getClasses()) { + if (c.getName().equals(name)) { + classCoverage = c; + break; } } - fail("No source node found for " + srcName); + assertNotNull("No class node for " + name, classCoverage); } private void analyze(final Analyzer analyzer, final ExecutionData data) @@ -114,6 +129,18 @@ protected void assertMethodCount(final int expectedTotal) { sourceCoverage.getMethodCounter().getTotalCount()); } + protected void assertMethod(final String name, final int status) { + for (IMethodCoverage method : classCoverage.getMethods()) { + if (method.getName().equals(name)) { + int methodStatus = method.getInstructionCounter().getStatus(); + assertEquals("Instruction status in method " + name, + STATUS_NAME[status], STATUS_NAME[methodStatus]); + return; + } + } + fail("No method node for " + name); + } + protected void assertLine(final String tag, final int status) { final int nr = source.getLineNumber(tag); final ILine line = sourceCoverage.getLine(nr); @@ -135,6 +162,19 @@ protected void assertLine(final String tag, final int status, line.getBranchCounter()); } + protected void assertLine(final String tag, final int status, + final int missedBranches, final int coveredBranches, + final int missedInstructions, final int coveredInstructions) { + assertLine(tag, status, missedBranches, coveredBranches); + final int nr = source.getLineNumber(tag); + final ILine line = sourceCoverage.getLine(nr); + final String msg = String.format("Instructions in line %s: %s", + Integer.valueOf(nr), source.getLine(nr)); + assertEquals(msg + " branches", CounterImpl + .getInstance(missedInstructions, coveredInstructions), + line.getInstructionCounter()); + } + protected void assertLogEvents(String... events) throws Exception { final Method getter = Class.forName(Stubs.class.getName(), false, loader).getMethod("getLogEvents"); diff --git a/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/AssertFilter.java b/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/AssertFilter.java new file mode 100644 index 0000000000..3d543a997f --- /dev/null +++ b/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/AssertFilter.java @@ -0,0 +1,115 @@ +/******************************************************************************* + * Copyright (c) 2009, 2017 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: + * + *******************************************************************************/ +package org.jacoco.core.internal.analysis.filter; + +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Type; +import org.objectweb.asm.tree.AbstractInsnNode; +import org.objectweb.asm.tree.FieldInsnNode; +import org.objectweb.asm.tree.JumpInsnNode; +import org.objectweb.asm.tree.LabelNode; +import org.objectweb.asm.tree.LdcInsnNode; +import org.objectweb.asm.tree.MethodInsnNode; +import org.objectweb.asm.tree.MethodNode; + +/** + * Filters code that is generated by javac for an assert statement. + */ +public final class AssertFilter implements IFilter { + + public void filter(final String className, final String superClassName, + final MethodNode methodNode, final IFilterOutput output) { + if (methodNode.name.equals("")) { + AbstractInsnNode i = methodNode.instructions.getFirst(); + while (i != null) { + filterClinit(className, i, output); + i = i.getNext(); + } + } + + AbstractInsnNode i = methodNode.instructions.getFirst(); + while (i != null) { + filter(className, i, output); + i = i.getNext(); + } + } + + private static void filterClinit(final String className, + final AbstractInsnNode start, final IFilterOutput output) { + if (start.getOpcode() == Opcodes.LDC) { + final LdcInsnNode ldc = (LdcInsnNode) start; + if (ldc.cst instanceof Type + && ((Type) ldc.cst).getInternalName().equals(className)) { + final AbstractInsnNode to = new ClinitMatcher().match(className, + start); + if (to != null) { + output.ignore(start, to); + } + } + } + } + + private static boolean isAssertionsDisabledField(final String className, + final FieldInsnNode field) { + return field.owner.equals(className) + && field.name.equals("$assertionsDisabled") + && field.desc.equals("Z"); + } + + private static class ClinitMatcher extends AbstractMatcher { + AbstractInsnNode match(final String className, + final AbstractInsnNode start) { + cursor = start; + nextIs(Opcodes.INVOKEVIRTUAL); + final MethodInsnNode m = (MethodInsnNode) cursor; + if (m == null || !m.owner.equals("java/lang/Class") + || !m.name.equals("desiredAssertionStatus") + || !m.desc.equals("()Z")) { + return null; + } + nextIs(Opcodes.IFNE); + if (cursor == null) { + return null; + } + final LabelNode zeroLabel = ((JumpInsnNode) cursor).label; + nextIs(Opcodes.ICONST_1); + nextIs(Opcodes.GOTO); + if (cursor == null) { + return null; + } + final LabelNode putstaticLabel = ((JumpInsnNode) cursor).label; + if (cursor.getNext() != zeroLabel) { + return null; + } + nextIs(Opcodes.ICONST_0); + if (cursor.getNext() != putstaticLabel) { + return null; + } + nextIs(Opcodes.PUTSTATIC); + if (cursor == null || !isAssertionsDisabledField(className, + (FieldInsnNode) cursor)) { + return null; + } + return cursor; + } + } + + private static void filter(final String className, + final AbstractInsnNode start, final IFilterOutput output) { + if (start.getOpcode() == Opcodes.GETSTATIC && isAssertionsDisabledField( + className, (FieldInsnNode) start)) { + final AbstractInsnNode ifne = start.getNext(); + if (ifne != null && ifne.getOpcode() == Opcodes.IFNE) { + output.ignore(start, ifne); + } + } + } +} diff --git a/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/Filters.java b/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/Filters.java index e2c26d928e..e66a391702 100644 --- a/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/Filters.java +++ b/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/Filters.java @@ -27,7 +27,7 @@ public final class Filters implements IFilter { * Filter that combines all other filters. */ public static final IFilter ALL = new Filters(new EnumFilter(), - new SyntheticFilter(), new SynchronizedFilter(), + new SyntheticFilter(), new SynchronizedFilter(), new AssertFilter(), new TryWithResourcesJavacFilter(), new TryWithResourcesEcjFilter(), new PrivateEmptyNoArgConstructorFilter(), new StringSwitchJavacFilter(), new LombokGeneratedFilter()); diff --git a/org.jacoco.doc/docroot/doc/changes.html b/org.jacoco.doc/docroot/doc/changes.html index 5ae14dd52c..864e81e944 100644 --- a/org.jacoco.doc/docroot/doc/changes.html +++ b/org.jacoco.doc/docroot/doc/changes.html @@ -42,6 +42,9 @@

New Features

  • Exclude from a report a part of bytecode that javac generates for a String in switch statement (GitHub #596).
  • +
  • Exclude from a report a part of bytecode that javac generates for an + assert statement + (GitHub #613).
  • Maven aggregated reports will now also include modules of runtime and provided dependencies (GitHub #498,