diff options
Diffstat (limited to 'org.jacoco.core/src/org/jacoco/core')
125 files changed, 2742 insertions, 877 deletions
diff --git a/org.jacoco.core/src/org/jacoco/core/JaCoCo.java b/org.jacoco.core/src/org/jacoco/core/JaCoCo.java index cb3fb4da..ce9ec053 100644 --- a/org.jacoco.core/src/org/jacoco/core/JaCoCo.java +++ b/org.jacoco.core/src/org/jacoco/core/JaCoCo.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2018 Mountainminds GmbH & Co. KG and Contributors + * Copyright (c) 2009, 2019 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 diff --git a/org.jacoco.core/src/org/jacoco/core/analysis/Analyzer.java b/org.jacoco.core/src/org/jacoco/core/analysis/Analyzer.java index 53dbf0c0..76b7be3c 100644 --- a/org.jacoco.core/src/org/jacoco/core/analysis/Analyzer.java +++ b/org.jacoco.core/src/org/jacoco/core/analysis/Analyzer.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2018 Mountainminds GmbH & Co. KG and Contributors + * Copyright (c) 2009, 2019 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 @@ -30,8 +30,10 @@ import org.jacoco.core.internal.analysis.ClassCoverageImpl; import org.jacoco.core.internal.analysis.StringPool; import org.jacoco.core.internal.data.CRC64; import org.jacoco.core.internal.flow.ClassProbesAdapter; +import org.jacoco.core.internal.instr.InstrSupport; import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.Opcodes; /** * An {@link Analyzer} instance processes a set of Java class files and @@ -99,15 +101,17 @@ public class Analyzer { return new ClassProbesAdapter(analyzer, false); } - /** - * Analyzes the class given as a ASM reader. - * - * @param reader - * reader with class definitions - */ - public void analyzeClass(final ClassReader reader) { - final ClassVisitor visitor = createAnalyzingVisitor( - CRC64.classId(reader.b), reader.getClassName()); + private void analyzeClass(final byte[] source) { + final long classId = CRC64.classId(source); + final ClassReader reader = InstrSupport.classReaderFor(source); + if ((reader.getAccess() & Opcodes.ACC_MODULE) != 0) { + return; + } + if ((reader.getAccess() & Opcodes.ACC_SYNTHETIC) != 0) { + return; + } + final ClassVisitor visitor = createAnalyzingVisitor(classId, + reader.getClassName()); reader.accept(visitor, 0); } @@ -124,7 +128,7 @@ public class Analyzer { public void analyzeClass(final byte[] buffer, final String location) throws IOException { try { - analyzeClass(new ClassReader(buffer)); + analyzeClass(buffer); } catch (final RuntimeException cause) { throw analyzerError(location, cause); } diff --git a/org.jacoco.core/src/org/jacoco/core/analysis/CounterComparator.java b/org.jacoco.core/src/org/jacoco/core/analysis/CounterComparator.java index 1848c6fc..8a67b970 100644 --- a/org.jacoco.core/src/org/jacoco/core/analysis/CounterComparator.java +++ b/org.jacoco.core/src/org/jacoco/core/analysis/CounterComparator.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2018 Mountainminds GmbH & Co. KG and Contributors + * Copyright (c) 2009, 2019 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 diff --git a/org.jacoco.core/src/org/jacoco/core/analysis/CoverageBuilder.java b/org.jacoco.core/src/org/jacoco/core/analysis/CoverageBuilder.java index a44afb78..784fbd82 100644 --- a/org.jacoco.core/src/org/jacoco/core/analysis/CoverageBuilder.java +++ b/org.jacoco.core/src/org/jacoco/core/analysis/CoverageBuilder.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2018 Mountainminds GmbH & Co. KG and Contributors + * Copyright (c) 2009, 2019 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 @@ -95,26 +95,22 @@ public class CoverageBuilder implements ICoverageVisitor { return result; } - // === IStructureVisitor === + // === ICoverageVisitor === public void visitCoverage(final IClassCoverage coverage) { - // Only consider classes that actually contain code: - if (coverage.getInstructionCounter().getTotalCount() > 0) { - final String name = coverage.getName(); - final IClassCoverage dup = classes.put(name, coverage); - if (dup != null) { - if (dup.getId() != coverage.getId()) { - throw new IllegalStateException( - "Can't add different class with same name: " - + name); - } - } else { - final String source = coverage.getSourceFileName(); - if (source != null) { - final SourceFileCoverageImpl sourceFile = getSourceFile( - source, coverage.getPackageName()); - sourceFile.increment(coverage); - } + final String name = coverage.getName(); + final IClassCoverage dup = classes.put(name, coverage); + if (dup != null) { + if (dup.getId() != coverage.getId()) { + throw new IllegalStateException( + "Can't add different class with same name: " + name); + } + } else { + final String source = coverage.getSourceFileName(); + if (source != null) { + final SourceFileCoverageImpl sourceFile = getSourceFile(source, + coverage.getPackageName()); + sourceFile.increment(coverage); } } } diff --git a/org.jacoco.core/src/org/jacoco/core/analysis/CoverageNodeImpl.java b/org.jacoco.core/src/org/jacoco/core/analysis/CoverageNodeImpl.java index c1bcb26a..6be3bb6f 100644 --- a/org.jacoco.core/src/org/jacoco/core/analysis/CoverageNodeImpl.java +++ b/org.jacoco.core/src/org/jacoco/core/analysis/CoverageNodeImpl.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2018 Mountainminds GmbH & Co. KG and Contributors + * Copyright (c) 2009, 2019 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 @@ -143,6 +143,10 @@ public class CoverageNodeImpl implements ICoverageNode { throw new AssertionError(entity); } + public boolean containsCode() { + return getInstructionCounter().getTotalCount() != 0; + } + public ICoverageNode getPlainCopy() { final CoverageNodeImpl copy = new CoverageNodeImpl(elementType, name); copy.instructionCounter = CounterImpl.getInstance(instructionCounter); diff --git a/org.jacoco.core/src/org/jacoco/core/analysis/IBundleCoverage.java b/org.jacoco.core/src/org/jacoco/core/analysis/IBundleCoverage.java index afcdc1aa..d488f475 100644 --- a/org.jacoco.core/src/org/jacoco/core/analysis/IBundleCoverage.java +++ b/org.jacoco.core/src/org/jacoco/core/analysis/IBundleCoverage.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2018 Mountainminds GmbH & Co. KG and Contributors + * Copyright (c) 2009, 2019 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 @@ -25,6 +25,6 @@ public interface IBundleCoverage extends ICoverageNode { * * @return all packages */ - public Collection<IPackageCoverage> getPackages(); + Collection<IPackageCoverage> getPackages(); -}
\ No newline at end of file +} diff --git a/org.jacoco.core/src/org/jacoco/core/analysis/IClassCoverage.java b/org.jacoco.core/src/org/jacoco/core/analysis/IClassCoverage.java index f72b7a85..81cc6fe7 100644 --- a/org.jacoco.core/src/org/jacoco/core/analysis/IClassCoverage.java +++ b/org.jacoco.core/src/org/jacoco/core/analysis/IClassCoverage.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2018 Mountainminds GmbH & Co. KG and Contributors + * Copyright (c) 2009, 2019 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 @@ -27,7 +27,7 @@ public interface IClassCoverage extends ISourceNode { * * @return class identifier */ - public long getId(); + long getId(); /** * Returns if the the analyzed class does match the execution data provided. @@ -37,14 +37,14 @@ public interface IClassCoverage extends ISourceNode { * @return <code>true</code> if this class does not match to the provided * execution data. */ - public boolean isNoMatch(); + boolean isNoMatch(); /** * Returns the VM signature of the class. * * @return VM signature of the class (may be <code>null</code>) */ - public String getSignature(); + String getSignature(); /** * Returns the VM name of the superclass. @@ -52,34 +52,34 @@ public interface IClassCoverage extends ISourceNode { * @return VM name of the super class (may be <code>null</code>, i.e. * <code>java/lang/Object</code>) */ - public String getSuperName(); + String getSuperName(); /** * Returns the VM names of implemented/extended interfaces. * * @return VM names of implemented/extended interfaces */ - public String[] getInterfaceNames(); + String[] getInterfaceNames(); /** * Returns the VM name of the package this class belongs to. * * @return VM name of the package */ - public String getPackageName(); + String getPackageName(); /** * Returns the optional name of the corresponding source file. * * @return name of the corresponding source file */ - public String getSourceFileName(); + String getSourceFileName(); /** * Returns the methods included in this class. * * @return methods of this class */ - public Collection<IMethodCoverage> getMethods(); + Collection<IMethodCoverage> getMethods(); -}
\ No newline at end of file +} diff --git a/org.jacoco.core/src/org/jacoco/core/analysis/ICounter.java b/org.jacoco.core/src/org/jacoco/core/analysis/ICounter.java index 5f30e02b..60c8dcc5 100644 --- a/org.jacoco.core/src/org/jacoco/core/analysis/ICounter.java +++ b/org.jacoco.core/src/org/jacoco/core/analysis/ICounter.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2018 Mountainminds GmbH & Co. KG and Contributors + * Copyright (c) 2009, 2019 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 @@ -20,7 +20,7 @@ public interface ICounter { /** * Different values provided by a counter. */ - public enum CounterValue { + enum CounterValue { /** Total number of items */ TOTALCOUNT, @@ -41,22 +41,22 @@ public interface ICounter { /** * Status flag for no items (value is 0x00). */ - public static final int EMPTY = 0x00; + int EMPTY = 0x00; /** * Status flag when all items are not covered (value is 0x01). */ - public static final int NOT_COVERED = 0x01; + int NOT_COVERED = 0x01; /** * Status flag when all items are covered (value is 0x02). */ - public static final int FULLY_COVERED = 0x02; + int FULLY_COVERED = 0x02; /** * Status flag when items are partly covered (value is 0x03). */ - public static final int PARTLY_COVERED = NOT_COVERED | FULLY_COVERED; + int PARTLY_COVERED = NOT_COVERED | FULLY_COVERED; /** * Returns the counter value of the given type. @@ -65,28 +65,28 @@ public interface ICounter { * value type to return * @return counter value */ - public double getValue(CounterValue value); + double getValue(CounterValue value); /** * Returns the total count of items. * * @return total count of items */ - public int getTotalCount(); + int getTotalCount(); /** * Returns the count of covered items. * * @return count of covered items */ - public int getCoveredCount(); + int getCoveredCount(); /** * Returns the count of missed items. * * @return count of missed items */ - public int getMissedCount(); + int getMissedCount(); /** * Calculates the ratio of covered to total count items. If total count @@ -94,7 +94,7 @@ public interface ICounter { * * @return ratio of covered to total count items */ - public double getCoveredRatio(); + double getCoveredRatio(); /** * Calculates the ratio of missed to total count items. If total count items @@ -102,7 +102,7 @@ public interface ICounter { * * @return ratio of missed to total count items */ - public double getMissedRatio(); + double getMissedRatio(); /** * Returns the coverage status of this counter. @@ -114,6 +114,6 @@ public interface ICounter { * * @return status of this line */ - public int getStatus(); + int getStatus(); } diff --git a/org.jacoco.core/src/org/jacoco/core/analysis/ICoverageNode.java b/org.jacoco.core/src/org/jacoco/core/analysis/ICoverageNode.java index 0756f410..20b107d4 100644 --- a/org.jacoco.core/src/org/jacoco/core/analysis/ICoverageNode.java +++ b/org.jacoco.core/src/org/jacoco/core/analysis/ICoverageNode.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2018 Mountainminds GmbH & Co. KG and Contributors + * Copyright (c) 2009, 2019 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 @@ -20,7 +20,7 @@ public interface ICoverageNode { /** * Type of a Java element represented by a {@link ICoverageNode} instance. */ - public enum ElementType { + enum ElementType { /** Method */ METHOD, @@ -45,7 +45,7 @@ public interface ICoverageNode { /** * Different counter types supported by JaCoCo. */ - public enum CounterEntity { + enum CounterEntity { /** Counter for instructions */ INSTRUCTION, @@ -71,56 +71,56 @@ public interface ICoverageNode { * * @return type of this node */ - public abstract ElementType getElementType(); + ElementType getElementType(); /** * Returns the name of this node. * * @return name of this node */ - public String getName(); + String getName(); /** * Returns the counter for byte code instructions. * * @return counter for instructions */ - public abstract ICounter getInstructionCounter(); + ICounter getInstructionCounter(); /** * Returns the counter for branches. * * @return counter for branches */ - public ICounter getBranchCounter(); + ICounter getBranchCounter(); /** * Returns the counter for lines. * * @return counter for lines */ - public ICounter getLineCounter(); + ICounter getLineCounter(); /** * Returns the counter for cyclomatic complexity. * * @return counter for complexity */ - public ICounter getComplexityCounter(); + ICounter getComplexityCounter(); /** * Returns the counter for methods. * * @return counter for methods */ - public ICounter getMethodCounter(); + ICounter getMethodCounter(); /** * Returns the counter for classes. * * @return counter for classes */ - public ICounter getClassCounter(); + ICounter getClassCounter(); /** * Generic access to the the counters. @@ -129,7 +129,14 @@ public interface ICoverageNode { * entity we're we want to have the counter for * @return counter for the given entity */ - public ICounter getCounter(CounterEntity entity); + ICounter getCounter(CounterEntity entity); + + /** + * Checks whether this node contains code relevant for code coverage. + * + * @return <code>true</code> if this node contains code relevant for code coverage + */ + boolean containsCode(); /** * Creates a plain copy of this node. While {@link ICoverageNode} @@ -139,6 +146,6 @@ public interface ICoverageNode { * * @return copy with counters only */ - public ICoverageNode getPlainCopy(); + ICoverageNode getPlainCopy(); -}
\ No newline at end of file +} diff --git a/org.jacoco.core/src/org/jacoco/core/analysis/ICoverageVisitor.java b/org.jacoco.core/src/org/jacoco/core/analysis/ICoverageVisitor.java index 5a71859f..3f702906 100644 --- a/org.jacoco.core/src/org/jacoco/core/analysis/ICoverageVisitor.java +++ b/org.jacoco.core/src/org/jacoco/core/analysis/ICoverageVisitor.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2018 Mountainminds GmbH & Co. KG and Contributors + * Copyright (c) 2009, 2019 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 @@ -23,6 +23,6 @@ public interface ICoverageVisitor { * @param coverage * coverage data for a class */ - public void visitCoverage(IClassCoverage coverage); + void visitCoverage(IClassCoverage coverage); } diff --git a/org.jacoco.core/src/org/jacoco/core/analysis/ILine.java b/org.jacoco.core/src/org/jacoco/core/analysis/ILine.java index cdd91eba..ef65dc0d 100644 --- a/org.jacoco.core/src/org/jacoco/core/analysis/ILine.java +++ b/org.jacoco.core/src/org/jacoco/core/analysis/ILine.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2018 Mountainminds GmbH & Co. KG and Contributors + * Copyright (c) 2009, 2019 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 @@ -22,14 +22,14 @@ public interface ILine { * * @return instruction counter */ - public ICounter getInstructionCounter(); + ICounter getInstructionCounter(); /** * Returns the branches counter for this line. * * @return branches counter */ - public ICounter getBranchCounter(); + ICounter getBranchCounter(); /** * Returns the coverage status of this line, calculated from the @@ -42,6 +42,6 @@ public interface ILine { * * @return status of this line */ - public int getStatus(); + int getStatus(); } diff --git a/org.jacoco.core/src/org/jacoco/core/analysis/IMethodCoverage.java b/org.jacoco.core/src/org/jacoco/core/analysis/IMethodCoverage.java index 3bd75169..b6755170 100644 --- a/org.jacoco.core/src/org/jacoco/core/analysis/IMethodCoverage.java +++ b/org.jacoco.core/src/org/jacoco/core/analysis/IMethodCoverage.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2018 Mountainminds GmbH & Co. KG and Contributors + * Copyright (c) 2009, 2019 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 @@ -22,13 +22,13 @@ public interface IMethodCoverage extends ISourceNode { * * @return descriptor */ - public String getDesc(); + String getDesc(); /** * Returns the generic signature of the method if defined. * * @return generic signature or <code>null</code> */ - public String getSignature(); + String getSignature(); -}
\ No newline at end of file +} diff --git a/org.jacoco.core/src/org/jacoco/core/analysis/IPackageCoverage.java b/org.jacoco.core/src/org/jacoco/core/analysis/IPackageCoverage.java index 8ff1a184..32121620 100644 --- a/org.jacoco.core/src/org/jacoco/core/analysis/IPackageCoverage.java +++ b/org.jacoco.core/src/org/jacoco/core/analysis/IPackageCoverage.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2018 Mountainminds GmbH & Co. KG and Contributors + * Copyright (c) 2009, 2019 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 @@ -28,13 +28,13 @@ public interface IPackageCoverage extends ICoverageNode { * * @return all classes */ - public Collection<IClassCoverage> getClasses(); + Collection<IClassCoverage> getClasses(); /** * Returns all source files in this package. * * @return all source files */ - public Collection<ISourceFileCoverage> getSourceFiles(); + Collection<ISourceFileCoverage> getSourceFiles(); -}
\ No newline at end of file +} diff --git a/org.jacoco.core/src/org/jacoco/core/analysis/ISourceFileCoverage.java b/org.jacoco.core/src/org/jacoco/core/analysis/ISourceFileCoverage.java index 6a49490b..5f01160a 100644 --- a/org.jacoco.core/src/org/jacoco/core/analysis/ISourceFileCoverage.java +++ b/org.jacoco.core/src/org/jacoco/core/analysis/ISourceFileCoverage.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2018 Mountainminds GmbH & Co. KG and Contributors + * Copyright (c) 2009, 2019 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 @@ -22,6 +22,6 @@ public interface ISourceFileCoverage extends ISourceNode { * * @return package name */ - public String getPackageName(); + String getPackageName(); -}
\ No newline at end of file +} diff --git a/org.jacoco.core/src/org/jacoco/core/analysis/ISourceNode.java b/org.jacoco.core/src/org/jacoco/core/analysis/ISourceNode.java index d6c90e5a..d64b2cc1 100644 --- a/org.jacoco.core/src/org/jacoco/core/analysis/ISourceNode.java +++ b/org.jacoco.core/src/org/jacoco/core/analysis/ISourceNode.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2018 Mountainminds GmbH & Co. KG and Contributors + * Copyright (c) 2009, 2019 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 @@ -18,7 +18,7 @@ package org.jacoco.core.analysis; public interface ISourceNode extends ICoverageNode { /** Place holder for unknown lines (no debug information) */ - public static int UNKNOWN_LINE = -1; + int UNKNOWN_LINE = -1; /** * The number of the first line coverage information is available for. If no @@ -26,7 +26,7 @@ public interface ISourceNode extends ICoverageNode { * * @return number of the first line or {@link #UNKNOWN_LINE} */ - public int getFirstLine(); + int getFirstLine(); /** * The number of the last line coverage information is available for. If no @@ -34,7 +34,7 @@ public interface ISourceNode extends ICoverageNode { * * @return number of the last line or {@link #UNKNOWN_LINE} */ - public int getLastLine(); + int getLastLine(); /** * Returns the line information for given line. @@ -43,6 +43,6 @@ public interface ISourceNode extends ICoverageNode { * line number of interest * @return line information */ - public ILine getLine(int nr); + ILine getLine(int nr); } diff --git a/org.jacoco.core/src/org/jacoco/core/analysis/NodeComparator.java b/org.jacoco.core/src/org/jacoco/core/analysis/NodeComparator.java index da94e07c..3fea25fb 100644 --- a/org.jacoco.core/src/org/jacoco/core/analysis/NodeComparator.java +++ b/org.jacoco.core/src/org/jacoco/core/analysis/NodeComparator.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2018 Mountainminds GmbH & Co. KG and Contributors + * Copyright (c) 2009, 2019 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 diff --git a/org.jacoco.core/src/org/jacoco/core/analysis/package-info.java b/org.jacoco.core/src/org/jacoco/core/analysis/package-info.java index afee23c2..d1a5e73b 100644 --- a/org.jacoco.core/src/org/jacoco/core/analysis/package-info.java +++ b/org.jacoco.core/src/org/jacoco/core/analysis/package-info.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2018 Mountainminds GmbH & Co. KG and Contributors + * Copyright (c) 2009, 2019 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 @@ -34,4 +34,4 @@ * +-- {@linkplain org.jacoco.core.analysis.ICoverageNode.ElementType#METHOD Method} * </pre> */ -package org.jacoco.core.analysis;
\ No newline at end of file +package org.jacoco.core.analysis; diff --git a/org.jacoco.core/src/org/jacoco/core/data/ExecutionData.java b/org.jacoco.core/src/org/jacoco/core/data/ExecutionData.java index 6c4b2e00..d98775eb 100644 --- a/org.jacoco.core/src/org/jacoco/core/data/ExecutionData.java +++ b/org.jacoco.core/src/org/jacoco/core/data/ExecutionData.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2018 Mountainminds GmbH & Co. KG and Contributors + * Copyright (c) 2009, 2019 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 diff --git a/org.jacoco.core/src/org/jacoco/core/data/ExecutionDataReader.java b/org.jacoco.core/src/org/jacoco/core/data/ExecutionDataReader.java index bdb98600..dd8519b5 100644 --- a/org.jacoco.core/src/org/jacoco/core/data/ExecutionDataReader.java +++ b/org.jacoco.core/src/org/jacoco/core/data/ExecutionDataReader.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2018 Mountainminds GmbH & Co. KG and Contributors + * Copyright (c) 2009, 2019 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 diff --git a/org.jacoco.core/src/org/jacoco/core/data/ExecutionDataStore.java b/org.jacoco.core/src/org/jacoco/core/data/ExecutionDataStore.java index 89d9dc66..3e567a3b 100644 --- a/org.jacoco.core/src/org/jacoco/core/data/ExecutionDataStore.java +++ b/org.jacoco.core/src/org/jacoco/core/data/ExecutionDataStore.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2018 Mountainminds GmbH & Co. KG and Contributors + * Copyright (c) 2009, 2019 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 diff --git a/org.jacoco.core/src/org/jacoco/core/data/ExecutionDataWriter.java b/org.jacoco.core/src/org/jacoco/core/data/ExecutionDataWriter.java index 259f1c1d..e697dda3 100644 --- a/org.jacoco.core/src/org/jacoco/core/data/ExecutionDataWriter.java +++ b/org.jacoco.core/src/org/jacoco/core/data/ExecutionDataWriter.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2018 Mountainminds GmbH & Co. KG and Contributors + * Copyright (c) 2009, 2019 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 diff --git a/org.jacoco.core/src/org/jacoco/core/data/IExecutionDataVisitor.java b/org.jacoco.core/src/org/jacoco/core/data/IExecutionDataVisitor.java index b2bccd6c..6cea7c57 100644 --- a/org.jacoco.core/src/org/jacoco/core/data/IExecutionDataVisitor.java +++ b/org.jacoco.core/src/org/jacoco/core/data/IExecutionDataVisitor.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2018 Mountainminds GmbH & Co. KG and Contributors + * Copyright (c) 2009, 2019 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 @@ -24,6 +24,6 @@ public interface IExecutionDataVisitor { * @param data * execution data for a class */ - public void visitClassExecution(ExecutionData data); + void visitClassExecution(ExecutionData data); } diff --git a/org.jacoco.core/src/org/jacoco/core/data/ISessionInfoVisitor.java b/org.jacoco.core/src/org/jacoco/core/data/ISessionInfoVisitor.java index 23e7711d..b80797a9 100644 --- a/org.jacoco.core/src/org/jacoco/core/data/ISessionInfoVisitor.java +++ b/org.jacoco.core/src/org/jacoco/core/data/ISessionInfoVisitor.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2018 Mountainminds GmbH & Co. KG and Contributors + * Copyright (c) 2009, 2019 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 @@ -25,6 +25,6 @@ public interface ISessionInfoVisitor { * @param info * session information */ - public void visitSessionInfo(final SessionInfo info); + void visitSessionInfo(SessionInfo info); } diff --git a/org.jacoco.core/src/org/jacoco/core/data/IncompatibleExecDataVersionException.java b/org.jacoco.core/src/org/jacoco/core/data/IncompatibleExecDataVersionException.java index b51b821d..cc508e73 100644 --- a/org.jacoco.core/src/org/jacoco/core/data/IncompatibleExecDataVersionException.java +++ b/org.jacoco.core/src/org/jacoco/core/data/IncompatibleExecDataVersionException.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2018 Mountainminds GmbH & Co. KG and Contributors + * Copyright (c) 2009, 2019 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 @@ -55,4 +55,4 @@ public class IncompatibleExecDataVersionException extends IOException { return actualVersion; } -}
\ No newline at end of file +} diff --git a/org.jacoco.core/src/org/jacoco/core/data/SessionInfo.java b/org.jacoco.core/src/org/jacoco/core/data/SessionInfo.java index 42a0d890..31f7b5e3 100644 --- a/org.jacoco.core/src/org/jacoco/core/data/SessionInfo.java +++ b/org.jacoco.core/src/org/jacoco/core/data/SessionInfo.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2018 Mountainminds GmbH & Co. KG and Contributors + * Copyright (c) 2009, 2019 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 diff --git a/org.jacoco.core/src/org/jacoco/core/data/SessionInfoStore.java b/org.jacoco.core/src/org/jacoco/core/data/SessionInfoStore.java index 0e8d667a..568dcc96 100644 --- a/org.jacoco.core/src/org/jacoco/core/data/SessionInfoStore.java +++ b/org.jacoco.core/src/org/jacoco/core/data/SessionInfoStore.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2018 Mountainminds GmbH & Co. KG and Contributors + * Copyright (c) 2009, 2019 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 diff --git a/org.jacoco.core/src/org/jacoco/core/data/package-info.java b/org.jacoco.core/src/org/jacoco/core/data/package-info.java index 4fb179c4..6e2f37bf 100644 --- a/org.jacoco.core/src/org/jacoco/core/data/package-info.java +++ b/org.jacoco.core/src/org/jacoco/core/data/package-info.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2018 Mountainminds GmbH & Co. KG and Contributors + * Copyright (c) 2009, 2019 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 @@ -13,4 +13,4 @@ /** * Representation and persistence of execution data and session information. */ -package org.jacoco.core.data;
\ No newline at end of file +package org.jacoco.core.data; diff --git a/org.jacoco.core/src/org/jacoco/core/instr/Instrumenter.java b/org.jacoco.core/src/org/jacoco/core/instr/Instrumenter.java index c8249784..561b09cb 100644 --- a/org.jacoco.core/src/org/jacoco/core/instr/Instrumenter.java +++ b/org.jacoco.core/src/org/jacoco/core/instr/Instrumenter.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2018 Mountainminds GmbH & Co. KG and Contributors + * Copyright (c) 2009, 2019 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 @@ -24,9 +24,11 @@ import java.util.zip.ZipOutputStream; import org.jacoco.core.internal.ContentTypeDetector; import org.jacoco.core.internal.InputStreams; import org.jacoco.core.internal.Pack200Streams; +import org.jacoco.core.internal.data.CRC64; import org.jacoco.core.internal.flow.ClassProbesAdapter; import org.jacoco.core.internal.instr.ClassInstrumenter; import org.jacoco.core.internal.instr.IProbeArrayStrategy; +import org.jacoco.core.internal.instr.InstrSupport; import org.jacoco.core.internal.instr.ProbeArrayStrategyFactory; import org.jacoco.core.internal.instr.SignatureRemover; import org.jacoco.core.runtime.IExecutionDataAccessorGenerator; @@ -67,15 +69,9 @@ public class Instrumenter { signatureRemover.setActive(flag); } - /** - * Creates a instrumented version of the given class if possible. - * - * @param reader - * definition of the class as ASM reader - * @return instrumented definition - * - */ - public byte[] instrument(final ClassReader reader) { + private byte[] instrument(final byte[] source) { + final long classId = CRC64.classId(source); + final ClassReader reader = InstrSupport.classReaderFor(source); final ClassWriter writer = new ClassWriter(reader, 0) { @Override protected String getCommonSuperClass(final String type1, @@ -84,9 +80,11 @@ public class Instrumenter { } }; final IProbeArrayStrategy strategy = ProbeArrayStrategyFactory - .createFor(reader, accessorGenerator); + .createFor(classId, reader, accessorGenerator); + final int version = InstrSupport.getMajorVersion(reader); final ClassVisitor visitor = new ClassProbesAdapter( - new ClassInstrumenter(strategy, writer), true); + new ClassInstrumenter(strategy, writer), + InstrSupport.needsFrames(version)); reader.accept(visitor, ClassReader.EXPAND_FRAMES); return writer.toByteArray(); } @@ -105,7 +103,7 @@ public class Instrumenter { public byte[] instrument(final byte[] buffer, final String name) throws IOException { try { - return instrument(new ClassReader(buffer)); + return instrument(buffer); } catch (final RuntimeException e) { throw instrumentError(name, e); } diff --git a/org.jacoco.core/src/org/jacoco/core/instr/package-info.java b/org.jacoco.core/src/org/jacoco/core/instr/package-info.java index 2f474032..1da3ef19 100644 --- a/org.jacoco.core/src/org/jacoco/core/instr/package-info.java +++ b/org.jacoco.core/src/org/jacoco/core/instr/package-info.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2018 Mountainminds GmbH & Co. KG and Contributors + * Copyright (c) 2009, 2019 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 @@ -16,4 +16,4 @@ * is the class {@link org.jacoco.core.instr.Instrumenter}. * </p> */ -package org.jacoco.core.instr;
\ No newline at end of file +package org.jacoco.core.instr; diff --git a/org.jacoco.core/src/org/jacoco/core/internal/ContentTypeDetector.java b/org.jacoco.core/src/org/jacoco/core/internal/ContentTypeDetector.java index ae665624..74574ecc 100644 --- a/org.jacoco.core/src/org/jacoco/core/internal/ContentTypeDetector.java +++ b/org.jacoco.core/src/org/jacoco/core/internal/ContentTypeDetector.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2018 Mountainminds GmbH & Co. KG and Contributors + * Copyright (c) 2009, 2019 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 @@ -83,6 +83,13 @@ public class ContentTypeDetector { case Opcodes.V1_7: case Opcodes.V1_8: case Opcodes.V9: + case Opcodes.V10: + case Opcodes.V11: + case Opcodes.V11 | Opcodes.V_PREVIEW: + case Opcodes.V12: + case Opcodes.V12 | Opcodes.V_PREVIEW: + case (Opcodes.V12 + 1): + case (Opcodes.V12 + 1) | Opcodes.V_PREVIEW: return CLASSFILE; } } diff --git a/org.jacoco.core/src/org/jacoco/core/internal/InputStreams.java b/org.jacoco.core/src/org/jacoco/core/internal/InputStreams.java index 7a77dd72..93f86f67 100644 --- a/org.jacoco.core/src/org/jacoco/core/internal/InputStreams.java +++ b/org.jacoco.core/src/org/jacoco/core/internal/InputStreams.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2018 Mountainminds GmbH & Co. KG and Contributors + * Copyright (c) 2009, 2019 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 diff --git a/org.jacoco.core/src/org/jacoco/core/internal/Pack200Streams.java b/org.jacoco.core/src/org/jacoco/core/internal/Pack200Streams.java index abcbe989..eae4fdfa 100644 --- a/org.jacoco.core/src/org/jacoco/core/internal/Pack200Streams.java +++ b/org.jacoco.core/src/org/jacoco/core/internal/Pack200Streams.java @@ -1,5 +1,5 @@ /*******************************************************************************
- * Copyright (c) 2009, 2018 Mountainminds GmbH & Co. KG and Contributors
+ * Copyright (c) 2009, 2019 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
diff --git a/org.jacoco.core/src/org/jacoco/core/internal/analysis/BundleCoverageImpl.java b/org.jacoco.core/src/org/jacoco/core/internal/analysis/BundleCoverageImpl.java index 5c1e39fd..9c6fcefc 100644 --- a/org.jacoco.core/src/org/jacoco/core/internal/analysis/BundleCoverageImpl.java +++ b/org.jacoco.core/src/org/jacoco/core/internal/analysis/BundleCoverageImpl.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2018 Mountainminds GmbH & Co. KG and Contributors + * Copyright (c) 2009, 2019 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 diff --git a/org.jacoco.core/src/org/jacoco/core/internal/analysis/ClassAnalyzer.java b/org.jacoco.core/src/org/jacoco/core/internal/analysis/ClassAnalyzer.java index afcf387e..a18ee7ed 100644 --- a/org.jacoco.core/src/org/jacoco/core/internal/analysis/ClassAnalyzer.java +++ b/org.jacoco.core/src/org/jacoco/core/internal/analysis/ClassAnalyzer.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2018 Mountainminds GmbH & Co. KG and Contributors + * Copyright (c) 2009, 2019 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 @@ -11,22 +11,36 @@ *******************************************************************************/ package org.jacoco.core.internal.analysis; -import org.jacoco.core.analysis.IMethodCoverage; +import java.util.HashSet; +import java.util.Set; + import org.jacoco.core.internal.analysis.filter.Filters; +import org.jacoco.core.internal.analysis.filter.IFilter; +import org.jacoco.core.internal.analysis.filter.IFilterContext; import org.jacoco.core.internal.flow.ClassProbesVisitor; import org.jacoco.core.internal.flow.MethodProbesVisitor; import org.jacoco.core.internal.instr.InstrSupport; +import org.objectweb.asm.AnnotationVisitor; import org.objectweb.asm.FieldVisitor; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.tree.MethodNode; /** * Analyzes the structure of a class. */ -public class ClassAnalyzer extends ClassProbesVisitor { +public class ClassAnalyzer extends ClassProbesVisitor + implements IFilterContext { private final ClassCoverageImpl coverage; private final boolean[] probes; private final StringPool stringPool; + private final Set<String> classAnnotations = new HashSet<String>(); + + private String sourceDebugExtension; + + private final IFilter filter; + /** * Creates a new analyzer that builds coverage data for a class. * @@ -42,6 +56,7 @@ public class ClassAnalyzer extends ClassProbesVisitor { this.coverage = coverage; this.probes = probes; this.stringPool = stringPool; + this.filter = Filters.all(); } @Override @@ -54,31 +69,57 @@ public class ClassAnalyzer extends ClassProbesVisitor { } @Override + public AnnotationVisitor visitAnnotation(final String desc, + final boolean visible) { + classAnnotations.add(desc); + return super.visitAnnotation(desc, visible); + } + + @Override public void visitSource(final String source, final String debug) { coverage.setSourceFileName(stringPool.get(source)); + sourceDebugExtension = debug; } @Override public MethodProbesVisitor visitMethod(final int access, final String name, - final String desc, final String signature, final String[] exceptions) { + final String desc, final String signature, + final String[] exceptions) { InstrSupport.assertNotInstrumented(name, coverage.getName()); - return new MethodAnalyzer(coverage.getName(), coverage.getSuperName(), - stringPool.get(name), stringPool.get(desc), - stringPool.get(signature), probes, Filters.ALL) { + final InstructionsBuilder builder = new InstructionsBuilder(probes); + + return new MethodAnalyzer(builder) { + @Override - public void visitEnd() { - super.visitEnd(); - final IMethodCoverage methodCoverage = getCoverage(); - if (methodCoverage.getInstructionCounter().getTotalCount() > 0) { - // Only consider methods that actually contain code - coverage.addMethod(methodCoverage); - } + public void accept(final MethodNode methodNode, + final MethodVisitor methodVisitor) { + super.accept(methodNode, methodVisitor); + addMethodCoverage(stringPool.get(name), stringPool.get(desc), + stringPool.get(signature), builder, methodNode); } }; } + private void addMethodCoverage(final String name, final String desc, + final String signature, final InstructionsBuilder icc, + final MethodNode methodNode) { + final MethodCoverageCalculator mcc = new MethodCoverageCalculator( + icc.getInstructions()); + filter.filter(methodNode, this, mcc); + + final MethodCoverageImpl mc = new MethodCoverageImpl(name, desc, + signature); + mcc.calculate(mc); + + if (mc.containsCode()) { + // Only consider methods that actually contain code + coverage.addMethod(mc); + } + + } + @Override public FieldVisitor visitField(final int access, final String name, final String desc, final String signature, final Object value) { @@ -91,4 +132,26 @@ public class ClassAnalyzer extends ClassProbesVisitor { // nothing to do } + // IFilterContext implementation + + public String getClassName() { + return coverage.getName(); + } + + public String getSuperClassName() { + return coverage.getSuperName(); + } + + public Set<String> getClassAnnotations() { + return classAnnotations; + } + + public String getSourceFileName() { + return coverage.getSourceFileName(); + } + + public String getSourceDebugExtension() { + return sourceDebugExtension; + } + } diff --git a/org.jacoco.core/src/org/jacoco/core/internal/analysis/ClassCoverageImpl.java b/org.jacoco.core/src/org/jacoco/core/internal/analysis/ClassCoverageImpl.java index cb45604c..444a81ed 100644 --- a/org.jacoco.core/src/org/jacoco/core/internal/analysis/ClassCoverageImpl.java +++ b/org.jacoco.core/src/org/jacoco/core/internal/analysis/ClassCoverageImpl.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2018 Mountainminds GmbH & Co. KG and Contributors + * Copyright (c) 2009, 2019 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 @@ -47,7 +47,6 @@ public class ClassCoverageImpl extends SourceNodeImpl implements IClassCoverage this.id = id; this.noMatch = noMatch; this.methods = new ArrayList<IMethodCoverage>(); - this.classCounter = CounterImpl.COUNTER_1_0; } /** @@ -59,10 +58,11 @@ public class ClassCoverageImpl extends SourceNodeImpl implements IClassCoverage public void addMethod(final IMethodCoverage method) { this.methods.add(method); increment(method); - // As class is considered as covered when at least one method is - // covered: + // Class is considered as covered when at least one method is covered: if (methodCounter.getCoveredCount() > 0) { this.classCounter = CounterImpl.COUNTER_0_1; + } else { + this.classCounter = CounterImpl.COUNTER_1_0; } } diff --git a/org.jacoco.core/src/org/jacoco/core/internal/analysis/CounterImpl.java b/org.jacoco.core/src/org/jacoco/core/internal/analysis/CounterImpl.java index 5d0317c1..e166c626 100644 --- a/org.jacoco.core/src/org/jacoco/core/internal/analysis/CounterImpl.java +++ b/org.jacoco.core/src/org/jacoco/core/internal/analysis/CounterImpl.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2018 Mountainminds GmbH & Co. KG and Contributors + * Copyright (c) 2009, 2019 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 diff --git a/org.jacoco.core/src/org/jacoco/core/internal/analysis/Instruction.java b/org.jacoco.core/src/org/jacoco/core/internal/analysis/Instruction.java new file mode 100644 index 00000000..c5a1aaae --- /dev/null +++ b/org.jacoco.core/src/org/jacoco/core/internal/analysis/Instruction.java @@ -0,0 +1,207 @@ +/******************************************************************************* + * Copyright (c) 2009, 2019 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.analysis; + +import java.util.BitSet; +import java.util.Collection; + +import org.jacoco.core.analysis.ICounter; + +/** + * Execution status of a single bytecode instruction internally used for + * coverage analysis. The execution status is recorded separately for each + * outgoing branch. Each instruction has at least one branch, for example in + * case of a simple sequence of instructions (by convention branch 0). Instances + * of this class are used in two steps: + * + * <h3>Step 1: Building the CFG</h3> + * + * For each bytecode instruction of a method a {@link Instruction} instance is + * created. In correspondence with the CFG these instances are linked with each + * other with the <code>addBranch()</code> methods. The executions status is + * either directly derived from a probe which has been inserted in the execution + * flow ({@link #addBranch(boolean, int)}) or indirectly propagated along the + * CFG edges ({@link #addBranch(Instruction, int)}). + * + * <h3>Step 2: Querying the Coverage Status</h3> + * + * After all instructions have been created and linked each instruction knows + * its execution status and can be queried with: + * + * <ul> + * <li>{@link #getLine()}</li> + * <li>{@link #getInstructionCounter()}</li> + * <li>{@link #getBranchCounter()}</li> + * </ul> + * + * For the purpose of filtering instructions can be combined to new + * instructions. Note that these methods create new {@link Instruction} + * instances and do not modify the existing ones. + * + * <ul> + * <li>{@link #merge(Instruction)}</li> + * <li>{@link #replaceBranches(Collection)}</li> + * </ul> + */ +public class Instruction { + + private final int line; + + private int branches; + + private final BitSet coveredBranches; + + private Instruction predecessor; + + private int predecessorBranch; + + /** + * New instruction at the given line. + * + * @param line + * source line this instruction belongs to + */ + public Instruction(final int line) { + this.line = line; + this.branches = 0; + this.coveredBranches = new BitSet(); + } + + /** + * Adds a branch to this instruction which execution status is indirectly + * derived from the execution status of the target instruction. In case the + * branch is covered the status is propagated also to the predecessors of + * this instruction. + * + * Note: This method is not idempotent and must be called exactly once for + * every branch. + * + * @param target + * target instruction of this branch + * @param branch + * branch identifier unique for this instruction + */ + public void addBranch(final Instruction target, final int branch) { + branches++; + target.predecessor = this; + target.predecessorBranch = branch; + if (!target.coveredBranches.isEmpty()) { + propagateExecutedBranch(this, branch); + } + } + + /** + * Adds a branch to this instruction which execution status is directly + * derived from a probe. In case the branch is covered the status is + * propagated also to the predecessors of this instruction. + * + * Note: This method is not idempotent and must be called exactly once for + * every branch. + * + * @param executed + * whether the corresponding probe has been executed + * @param branch + * branch identifier unique for this instruction + */ + public void addBranch(final boolean executed, final int branch) { + branches++; + if (executed) { + propagateExecutedBranch(this, branch); + } + } + + private static void propagateExecutedBranch(Instruction insn, int branch) { + // No recursion here, as there can be very long chains of instructions + while (insn != null) { + if (!insn.coveredBranches.isEmpty()) { + insn.coveredBranches.set(branch); + break; + } + insn.coveredBranches.set(branch); + branch = insn.predecessorBranch; + insn = insn.predecessor; + } + } + + /** + * Returns the source line this instruction belongs to. + * + * @return corresponding source line + */ + public int getLine() { + return line; + } + + /** + * Merges information about covered branches of this instruction with + * another instruction. + * + * @param other + * instruction to merge with + * @return new instance with merged branches + */ + public Instruction merge(final Instruction other) { + final Instruction result = new Instruction(this.line); + result.branches = this.branches; + result.coveredBranches.or(this.coveredBranches); + result.coveredBranches.or(other.coveredBranches); + return result; + } + + /** + * Creates a copy of this instruction where all outgoing branches are + * replaced with the given instructions. The coverage status of the new + * instruction is derived from the status of the given instructions. + * + * @param newBranches + * new branches to consider + * @return new instance with replaced branches + */ + public Instruction replaceBranches( + final Collection<Instruction> newBranches) { + final Instruction result = new Instruction(this.line); + result.branches = newBranches.size(); + int idx = 0; + for (final Instruction b : newBranches) { + if (!b.coveredBranches.isEmpty()) { + result.coveredBranches.set(idx++); + } + } + return result; + } + + /** + * Returns the instruction coverage counter of this instruction. It is + * always 1 instruction which is covered or not. + * + * @return the instruction coverage counter + */ + public ICounter getInstructionCounter() { + return coveredBranches.isEmpty() ? CounterImpl.COUNTER_1_0 + : CounterImpl.COUNTER_0_1; + } + + /** + * Returns the branch coverage counter of this instruction. Only + * instructions with at least 2 outgoing edges report branches. + * + * @return the branch coverage counter + */ + public ICounter getBranchCounter() { + if (branches < 2) { + return CounterImpl.COUNTER_0_0; + } + final int covered = coveredBranches.cardinality(); + return CounterImpl.getInstance(branches - covered, covered); + } + +} diff --git a/org.jacoco.core/src/org/jacoco/core/internal/analysis/InstructionsBuilder.java b/org.jacoco.core/src/org/jacoco/core/internal/analysis/InstructionsBuilder.java new file mode 100644 index 00000000..b22d872a --- /dev/null +++ b/org.jacoco.core/src/org/jacoco/core/internal/analysis/InstructionsBuilder.java @@ -0,0 +1,186 @@ +/******************************************************************************* + * Copyright (c) 2009, 2019 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.analysis; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.jacoco.core.analysis.ISourceNode; +import org.jacoco.core.internal.flow.LabelInfo; +import org.objectweb.asm.Label; +import org.objectweb.asm.tree.AbstractInsnNode; + +/** + * Stateful builder for the {@link Instruction}s of a method. All instructions + * of a method must be added in their original sequence along with additional + * information like line numbers. Afterwards the instructions can be obtained + * with the <code>getInstructions()</code> method. + */ +class InstructionsBuilder { + + /** Probe array of the class the analyzed method belongs to. */ + private final boolean[] probes; + + /** The line which belong to subsequently added instructions. */ + private int currentLine; + + /** The last instruction which has been added. */ + private Instruction currentInsn; + + /** + * All instructions of a method mapped from the ASM node to the + * corresponding {@link Instruction} instance. + */ + private final Map<AbstractInsnNode, Instruction> instructions; + + /** + * The labels which mark the subsequent instructions. + * + * Due to ASM issue #315745 there can be more than one label per instruction + */ + private final List<Label> currentLabel; + + /** + * List of all jumps within the control flow. We need to store jumps + * temporarily as the target {@link Instruction} may not been known yet. + */ + private final List<Jump> jumps; + + /** + * Creates a new builder instance which can be used to analyze a single + * method. + * + * @param probes + * probe array of the corresponding class used to determine the + * coverage status of every instruction. + */ + InstructionsBuilder(final boolean[] probes) { + this.probes = probes; + this.currentLine = ISourceNode.UNKNOWN_LINE; + this.currentInsn = null; + this.instructions = new HashMap<AbstractInsnNode, Instruction>(); + this.currentLabel = new ArrayList<Label>(2); + this.jumps = new ArrayList<Jump>(); + } + + /** + * Sets the current source line. All subsequently added instructions will be + * assigned to this line. If no line is set (e.g. for classes compiled + * without debug information) {@link ISourceNode#UNKNOWN_LINE} is assigned + * to the instructions. + */ + void setCurrentLine(final int line) { + currentLine = line; + } + + /** + * Adds a label which applies to the subsequently added instruction. Due to + * ASM internals multiple {@link Label}s can be added to an instruction. + */ + void addLabel(final Label label) { + currentLabel.add(label); + if (!LabelInfo.isSuccessor(label)) { + noSuccessor(); + } + } + + /** + * Adds a new instruction. Instructions are by default linked with the + * previous instruction unless specified otherwise. + */ + void addInstruction(final AbstractInsnNode node) { + final Instruction insn = new Instruction(currentLine); + final int labelCount = currentLabel.size(); + if (labelCount > 0) { + for (int i = labelCount; --i >= 0;) { + LabelInfo.setInstruction(currentLabel.get(i), insn); + } + currentLabel.clear(); + } + if (currentInsn != null) { + currentInsn.addBranch(insn, 0); + } + currentInsn = insn; + instructions.put(node, insn); + } + + /** + * Declares that the next instruction will not be a successor of the current + * instruction. This is the case with an unconditional jump or technically + * when a probe was inserted before. + */ + void noSuccessor() { + currentInsn = null; + } + + /** + * Adds a jump from the last added instruction. + * + * @param target + * jump target + * @param branch + * unique branch number + */ + void addJump(final Label target, final int branch) { + jumps.add(new Jump(currentInsn, target, branch)); + } + + /** + * Adds a new probe for the last instruction. + * + * @param probeId + * index in the probe array + * @param branch + * unique branch number for the last instruction + */ + void addProbe(final int probeId, final int branch) { + final boolean executed = probes != null && probes[probeId]; + currentInsn.addBranch(executed, branch); + } + + /** + * Returns the status for all instructions of this method. This method must + * be called exactly once after the instructions have been added. + * + * @return map of ASM instruction nodes to corresponding {@link Instruction} + * instances + */ + Map<AbstractInsnNode, Instruction> getInstructions() { + // Wire jumps: + for (final Jump j : jumps) { + j.wire(); + } + + return instructions; + } + + private static class Jump { + + private final Instruction source; + private final Label target; + private final int branch; + + Jump(final Instruction source, final Label target, final int branch) { + this.source = source; + this.target = target; + this.branch = branch; + } + + void wire() { + source.addBranch(LabelInfo.getInstruction(target), branch); + } + + } + +} diff --git a/org.jacoco.core/src/org/jacoco/core/internal/analysis/LineImpl.java b/org.jacoco.core/src/org/jacoco/core/internal/analysis/LineImpl.java index 9c03ea76..908bcb60 100644 --- a/org.jacoco.core/src/org/jacoco/core/internal/analysis/LineImpl.java +++ b/org.jacoco.core/src/org/jacoco/core/internal/analysis/LineImpl.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2018 Mountainminds GmbH & Co. KG and Contributors + * Copyright (c) 2009, 2019 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 diff --git a/org.jacoco.core/src/org/jacoco/core/internal/analysis/MethodAnalyzer.java b/org.jacoco.core/src/org/jacoco/core/internal/analysis/MethodAnalyzer.java index e10e5d9f..1aaadf2e 100644 --- a/org.jacoco.core/src/org/jacoco/core/internal/analysis/MethodAnalyzer.java +++ b/org.jacoco.core/src/org/jacoco/core/internal/analysis/MethodAnalyzer.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2018 Mountainminds GmbH & Co. KG and Contributors + * Copyright (c) 2009, 2019 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 @@ -11,20 +11,7 @@ *******************************************************************************/ package org.jacoco.core.internal.analysis; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import org.jacoco.core.analysis.ICounter; -import org.jacoco.core.analysis.IMethodCoverage; -import org.jacoco.core.analysis.ISourceNode; -import org.jacoco.core.internal.analysis.filter.IFilter; -import org.jacoco.core.internal.analysis.filter.IFilterOutput; import org.jacoco.core.internal.flow.IFrame; -import org.jacoco.core.internal.flow.Instruction; import org.jacoco.core.internal.flow.LabelInfo; import org.jacoco.core.internal.flow.MethodProbesVisitor; import org.objectweb.asm.Handle; @@ -35,91 +22,26 @@ import org.objectweb.asm.tree.MethodNode; import org.objectweb.asm.tree.TryCatchBlockNode; /** - * A {@link MethodProbesVisitor} that analyzes which statements and branches of - * a method have been executed based on given probe data. + * A {@link MethodProbesVisitor} that builds the {@link Instruction}s of a + * method to calculate the detailed execution status. */ -public class MethodAnalyzer extends MethodProbesVisitor - implements IFilterOutput { - - private final String className; - - private final String superClassName; - - private final boolean[] probes; - - private final IFilter filter; - - private final MethodCoverageImpl coverage; - - private int currentLine = ISourceNode.UNKNOWN_LINE; - - private int firstLine = ISourceNode.UNKNOWN_LINE; - - private int lastLine = ISourceNode.UNKNOWN_LINE; +public class MethodAnalyzer extends MethodProbesVisitor { - // Due to ASM issue #315745 there can be more than one label per instruction - private final List<Label> currentLabel = new ArrayList<Label>(2); + private final InstructionsBuilder builder; - /** List of all analyzed instructions */ - private final List<Instruction> instructions = new ArrayList<Instruction>(); - - /** List of all predecessors of covered probes */ - private final List<CoveredProbe> coveredProbes = new ArrayList<CoveredProbe>(); - - /** List of all jumps encountered */ - private final List<Jump> jumps = new ArrayList<Jump>(); - - /** Last instruction in byte code sequence */ - private Instruction lastInsn; - - /** - * New Method analyzer for the given probe data. - * - * @param className - * class name - * @param superClassName - * superclass name - * @param name - * method name - * @param desc - * method descriptor - * @param signature - * optional parameterized signature - * @param probes - * recorded probe date of the containing class or - * <code>null</code> if the class is not executed at all - * @param filter - * filter - */ - MethodAnalyzer(final String className, final String superClassName, - final String name, final String desc, final String signature, - final boolean[] probes, final IFilter filter) { - super(); - this.className = className; - this.superClassName = superClassName; - this.probes = probes; - this.filter = filter; - this.coverage = new MethodCoverageImpl(name, desc, signature); - } + /** Current node of the ASM tree API */ + private AbstractInsnNode currentNode; /** - * Returns the coverage data for this method after this visitor has been - * processed. - * - * @return coverage data for this method + * New instance that uses the given builder. */ - public IMethodCoverage getCoverage() { - return coverage; + MethodAnalyzer(final InstructionsBuilder builder) { + this.builder = builder; } - /** - * {@link MethodNode#accept(MethodVisitor)} - */ @Override public void accept(final MethodNode methodNode, final MethodVisitor methodVisitor) { - filter.filter(className, superClassName, methodNode, this); - methodVisitor.visitCode(); for (final TryCatchBlockNode n : methodNode.tryCatchBlocks) { n.accept(methodVisitor); @@ -132,139 +54,68 @@ public class MethodAnalyzer extends MethodProbesVisitor methodVisitor.visitEnd(); } - private final Set<AbstractInsnNode> ignored = new HashSet<AbstractInsnNode>(); - - /** - * Instructions that should be merged form disjoint sets. Coverage - * information from instructions of one set will be merged into - * representative instruction of set. - * - * Each such set is represented as a singly linked list: each element except - * one references another element from the same set, element without - * reference - is a representative of this set. - * - * This map stores reference (value) for elements of sets (key). - */ - private final Map<AbstractInsnNode, AbstractInsnNode> merged = new HashMap<AbstractInsnNode, AbstractInsnNode>(); - - private final Map<AbstractInsnNode, Instruction> nodeToInstruction = new HashMap<AbstractInsnNode, Instruction>(); - - private AbstractInsnNode currentNode; - - public void ignore(final AbstractInsnNode fromInclusive, - final AbstractInsnNode toInclusive) { - for (AbstractInsnNode i = fromInclusive; i != toInclusive; i = i - .getNext()) { - ignored.add(i); - } - ignored.add(toInclusive); - } - - private AbstractInsnNode findRepresentative(AbstractInsnNode i) { - AbstractInsnNode r = merged.get(i); - while (r != null) { - i = r; - r = merged.get(i); - } - return i; - } - - public void merge(AbstractInsnNode i1, AbstractInsnNode i2) { - i1 = findRepresentative(i1); - i2 = findRepresentative(i2); - if (i1 != i2) { - merged.put(i2, i1); - } - } - @Override public void visitLabel(final Label label) { - currentLabel.add(label); - if (!LabelInfo.isSuccessor(label)) { - lastInsn = null; - } + builder.addLabel(label); } @Override public void visitLineNumber(final int line, final Label start) { - currentLine = line; - if (firstLine > line || lastLine == ISourceNode.UNKNOWN_LINE) { - firstLine = line; - } - if (lastLine < line) { - lastLine = line; - } - } - - private void visitInsn() { - final Instruction insn = new Instruction(currentNode, currentLine); - nodeToInstruction.put(currentNode, insn); - instructions.add(insn); - if (lastInsn != null) { - insn.setPredecessor(lastInsn, 0); - } - final int labelCount = currentLabel.size(); - if (labelCount > 0) { - for (int i = labelCount; --i >= 0;) { - LabelInfo.setInstruction(currentLabel.get(i), insn); - } - currentLabel.clear(); - } - lastInsn = insn; + builder.setCurrentLine(line); } @Override public void visitInsn(final int opcode) { - visitInsn(); + builder.addInstruction(currentNode); } @Override public void visitIntInsn(final int opcode, final int operand) { - visitInsn(); + builder.addInstruction(currentNode); } @Override public void visitVarInsn(final int opcode, final int var) { - visitInsn(); + builder.addInstruction(currentNode); } @Override public void visitTypeInsn(final int opcode, final String type) { - visitInsn(); + builder.addInstruction(currentNode); } @Override public void visitFieldInsn(final int opcode, final String owner, final String name, final String desc) { - visitInsn(); + builder.addInstruction(currentNode); } @Override public void visitMethodInsn(final int opcode, final String owner, final String name, final String desc, final boolean itf) { - visitInsn(); + builder.addInstruction(currentNode); } @Override public void visitInvokeDynamicInsn(final String name, final String desc, final Handle bsm, final Object... bsmArgs) { - visitInsn(); + builder.addInstruction(currentNode); } @Override public void visitJumpInsn(final int opcode, final Label label) { - visitInsn(); - jumps.add(new Jump(lastInsn, label, 1)); + builder.addInstruction(currentNode); + builder.addJump(label, 1); } @Override public void visitLdcInsn(final Object cst) { - visitInsn(); + builder.addInstruction(currentNode); } @Override public void visitIincInsn(final int var, final int increment) { - visitInsn(); + builder.addInstruction(currentNode); } @Override @@ -280,15 +131,15 @@ public class MethodAnalyzer extends MethodProbesVisitor } private void visitSwitchInsn(final Label dflt, final Label[] labels) { - visitInsn(); + builder.addInstruction(currentNode); LabelInfo.resetDone(labels); int branch = 0; - jumps.add(new Jump(lastInsn, dflt, branch)); + builder.addJump(dflt, branch); LabelInfo.setDone(dflt); for (final Label l : labels) { if (!LabelInfo.isDone(l)) { branch++; - jumps.add(new Jump(lastInsn, l, branch)); + builder.addJump(l, branch); LabelInfo.setDone(l); } } @@ -296,26 +147,26 @@ public class MethodAnalyzer extends MethodProbesVisitor @Override public void visitMultiANewArrayInsn(final String desc, final int dims) { - visitInsn(); + builder.addInstruction(currentNode); } @Override public void visitProbe(final int probeId) { - addProbe(probeId, 0); - lastInsn = null; + builder.addProbe(probeId, 0); + builder.noSuccessor(); } @Override public void visitJumpInsnWithProbe(final int opcode, final Label label, final int probeId, final IFrame frame) { - visitInsn(); - addProbe(probeId, 1); + builder.addInstruction(currentNode); + builder.addProbe(probeId, 1); } @Override public void visitInsnWithProbe(final int opcode, final int probeId) { - visitInsn(); - addProbe(probeId, 0); + builder.addInstruction(currentNode); + builder.addProbe(probeId, 0); } @Override @@ -332,7 +183,7 @@ public class MethodAnalyzer extends MethodProbesVisitor private void visitSwitchInsnWithProbes(final Label dflt, final Label[] labels) { - visitInsn(); + builder.addInstruction(currentNode); LabelInfo.resetDone(dflt); LabelInfo.resetDone(labels); int branch = 0; @@ -347,81 +198,12 @@ public class MethodAnalyzer extends MethodProbesVisitor final int id = LabelInfo.getProbeId(label); if (!LabelInfo.isDone(label)) { if (id == LabelInfo.NO_PROBE) { - jumps.add(new Jump(lastInsn, label, branch)); + builder.addJump(label, branch); } else { - addProbe(id, branch); + builder.addProbe(id, branch); } LabelInfo.setDone(label); } } - @Override - public void visitEnd() { - // Wire jumps: - for (final Jump j : jumps) { - LabelInfo.getInstruction(j.target).setPredecessor(j.source, - j.branch); - } - // Propagate probe values: - for (final CoveredProbe p : coveredProbes) { - p.instruction.setCovered(p.branch); - } - // Merge: - for (final Instruction i : instructions) { - final AbstractInsnNode m = i.getNode(); - final AbstractInsnNode r = findRepresentative(m); - if (r != m) { - ignored.add(m); - nodeToInstruction.get(r).merge(i); - } - } - // Report result: - coverage.ensureCapacity(firstLine, lastLine); - for (final Instruction i : instructions) { - if (ignored.contains(i.getNode())) { - continue; - } - - final int total = i.getBranches(); - final int covered = i.getCoveredBranches(); - final ICounter instrCounter = covered == 0 ? CounterImpl.COUNTER_1_0 - : CounterImpl.COUNTER_0_1; - final ICounter branchCounter = total > 1 - ? CounterImpl.getInstance(total - covered, covered) - : CounterImpl.COUNTER_0_0; - coverage.increment(instrCounter, branchCounter, i.getLine()); - } - coverage.incrementMethodCounter(); - } - - private void addProbe(final int probeId, final int branch) { - lastInsn.addBranch(); - if (probes != null && probes[probeId]) { - coveredProbes.add(new CoveredProbe(lastInsn, branch)); - } - } - - private static class CoveredProbe { - final Instruction instruction; - final int branch; - - private CoveredProbe(final Instruction instruction, final int branch) { - this.instruction = instruction; - this.branch = branch; - } - } - - private static class Jump { - - final Instruction source; - final Label target; - final int branch; - - Jump(final Instruction source, final Label target, final int branch) { - this.source = source; - this.target = target; - this.branch = branch; - } - } - } diff --git a/org.jacoco.core/src/org/jacoco/core/internal/analysis/MethodCoverageCalculator.java b/org.jacoco.core/src/org/jacoco/core/internal/analysis/MethodCoverageCalculator.java new file mode 100644 index 00000000..ebe167a2 --- /dev/null +++ b/org.jacoco.core/src/org/jacoco/core/internal/analysis/MethodCoverageCalculator.java @@ -0,0 +1,178 @@ +/******************************************************************************* + * Copyright (c) 2009, 2019 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: + * Evgeny Mandrikov - initial API and implementation + * + *******************************************************************************/ +package org.jacoco.core.internal.analysis; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +import org.jacoco.core.analysis.ISourceFileCoverage; +import org.jacoco.core.analysis.ISourceNode; +import org.jacoco.core.internal.analysis.filter.IFilterOutput; +import org.objectweb.asm.tree.AbstractInsnNode; + +/** + * Calculates the filtered coverage of a single method. A instance of this class + * can be first used as {@link IFilterOutput} before the coverage result is + * calculated. + */ +class MethodCoverageCalculator implements IFilterOutput { + + private final Map<AbstractInsnNode, Instruction> instructions; + + private final Set<AbstractInsnNode> ignored; + + /** + * Instructions that should be merged form disjoint sets. Coverage + * information from instructions of one set will be merged into + * representative instruction of set. + * + * Each such set is represented as a singly linked list: each element except + * one references another element from the same set, element without + * reference - is a representative of this set. + * + * This map stores reference (value) for elements of sets (key). + */ + private final Map<AbstractInsnNode, AbstractInsnNode> merged; + + private final Map<AbstractInsnNode, Set<AbstractInsnNode>> replacements; + + MethodCoverageCalculator( + final Map<AbstractInsnNode, Instruction> instructions) { + this.instructions = instructions; + this.ignored = new HashSet<AbstractInsnNode>(); + this.merged = new HashMap<AbstractInsnNode, AbstractInsnNode>(); + this.replacements = new HashMap<AbstractInsnNode, Set<AbstractInsnNode>>(); + } + + /** + * Applies all specified filtering commands and calculates the resulting + * coverage. + * + * @param coverage + * the result is added to this coverage node + */ + void calculate(final MethodCoverageImpl coverage) { + applyMerges(); + applyReplacements(); + ensureCapacity(coverage); + + for (final Entry<AbstractInsnNode, Instruction> entry : instructions + .entrySet()) { + if (!ignored.contains(entry.getKey())) { + final Instruction instruction = entry.getValue(); + coverage.increment(instruction.getInstructionCounter(), + instruction.getBranchCounter(), instruction.getLine()); + } + } + + coverage.incrementMethodCounter(); + } + + private void applyMerges() { + // Merge to the representative: + for (final Entry<AbstractInsnNode, AbstractInsnNode> entry : merged + .entrySet()) { + final AbstractInsnNode node = entry.getKey(); + final Instruction instruction = instructions.get(node); + final AbstractInsnNode representativeNode = findRepresentative( + node); + ignored.add(node); + instructions.put(representativeNode, + instructions.get(representativeNode).merge(instruction)); + entry.setValue(representativeNode); + } + + // Get merged value back from representative + for (final Entry<AbstractInsnNode, AbstractInsnNode> entry : merged + .entrySet()) { + instructions.put(entry.getKey(), + instructions.get(entry.getValue())); + } + } + + private void applyReplacements() { + for (final Entry<AbstractInsnNode, Set<AbstractInsnNode>> entry : replacements + .entrySet()) { + final Set<AbstractInsnNode> replacements = entry.getValue(); + final List<Instruction> newBranches = new ArrayList<Instruction>( + replacements.size()); + for (final AbstractInsnNode b : replacements) { + newBranches.add(instructions.get(b)); + } + final AbstractInsnNode node = entry.getKey(); + instructions.put(node, + instructions.get(node).replaceBranches(newBranches)); + } + } + + private void ensureCapacity(final MethodCoverageImpl coverage) { + // Determine line range: + int firstLine = ISourceFileCoverage.UNKNOWN_LINE; + int lastLine = ISourceFileCoverage.UNKNOWN_LINE; + for (final Entry<AbstractInsnNode, Instruction> entry : instructions + .entrySet()) { + if (!ignored.contains(entry.getKey())) { + final int line = entry.getValue().getLine(); + if (line != ISourceNode.UNKNOWN_LINE) { + if (firstLine > line + || lastLine == ISourceNode.UNKNOWN_LINE) { + firstLine = line; + } + if (lastLine < line) { + lastLine = line; + } + } + } + } + + // Performance optimization to avoid incremental increase of line array: + coverage.ensureCapacity(firstLine, lastLine); + } + + private AbstractInsnNode findRepresentative(AbstractInsnNode i) { + AbstractInsnNode r; + while ((r = merged.get(i)) != null) { + i = r; + } + return i; + } + + // === IFilterOutput API === + + public void ignore(final AbstractInsnNode fromInclusive, + final AbstractInsnNode toInclusive) { + for (AbstractInsnNode i = fromInclusive; i != toInclusive; i = i + .getNext()) { + ignored.add(i); + } + ignored.add(toInclusive); + } + + public void merge(AbstractInsnNode i1, AbstractInsnNode i2) { + i1 = findRepresentative(i1); + i2 = findRepresentative(i2); + if (i1 != i2) { + merged.put(i2, i1); + } + } + + public void replaceBranches(final AbstractInsnNode source, + final Set<AbstractInsnNode> newTargets) { + replacements.put(source, newTargets); + } + +} diff --git a/org.jacoco.core/src/org/jacoco/core/internal/analysis/MethodCoverageImpl.java b/org.jacoco.core/src/org/jacoco/core/internal/analysis/MethodCoverageImpl.java index 1e09129a..aa7dccf0 100644 --- a/org.jacoco.core/src/org/jacoco/core/internal/analysis/MethodCoverageImpl.java +++ b/org.jacoco.core/src/org/jacoco/core/internal/analysis/MethodCoverageImpl.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2018 Mountainminds GmbH & Co. KG and Contributors + * Copyright (c) 2009, 2019 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 diff --git a/org.jacoco.core/src/org/jacoco/core/internal/analysis/PackageCoverageImpl.java b/org.jacoco.core/src/org/jacoco/core/internal/analysis/PackageCoverageImpl.java index 4f92fc7c..285ddd38 100644 --- a/org.jacoco.core/src/org/jacoco/core/internal/analysis/PackageCoverageImpl.java +++ b/org.jacoco.core/src/org/jacoco/core/internal/analysis/PackageCoverageImpl.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2018 Mountainminds GmbH & Co. KG and Contributors + * Copyright (c) 2009, 2019 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 diff --git a/org.jacoco.core/src/org/jacoco/core/internal/analysis/SourceFileCoverageImpl.java b/org.jacoco.core/src/org/jacoco/core/internal/analysis/SourceFileCoverageImpl.java index e689e5b1..dc4483f5 100644 --- a/org.jacoco.core/src/org/jacoco/core/internal/analysis/SourceFileCoverageImpl.java +++ b/org.jacoco.core/src/org/jacoco/core/internal/analysis/SourceFileCoverageImpl.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2018 Mountainminds GmbH & Co. KG and Contributors + * Copyright (c) 2009, 2019 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 diff --git a/org.jacoco.core/src/org/jacoco/core/internal/analysis/SourceNodeImpl.java b/org.jacoco.core/src/org/jacoco/core/internal/analysis/SourceNodeImpl.java index 6aa241c5..3fa3d692 100644 --- a/org.jacoco.core/src/org/jacoco/core/internal/analysis/SourceNodeImpl.java +++ b/org.jacoco.core/src/org/jacoco/core/internal/analysis/SourceNodeImpl.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2018 Mountainminds GmbH & Co. KG and Contributors + * Copyright (c) 2009, 2019 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 diff --git a/org.jacoco.core/src/org/jacoco/core/internal/analysis/StringPool.java b/org.jacoco.core/src/org/jacoco/core/internal/analysis/StringPool.java index a2e8d700..97e25aaa 100644 --- a/org.jacoco.core/src/org/jacoco/core/internal/analysis/StringPool.java +++ b/org.jacoco.core/src/org/jacoco/core/internal/analysis/StringPool.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2018 Mountainminds GmbH & Co. KG and Contributors + * Copyright (c) 2009, 2019 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 diff --git a/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/AbstractAnnotatedMethodFilter.java b/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/AbstractAnnotatedMethodFilter.java deleted file mode 100644 index 76b6b2f9..00000000 --- a/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/AbstractAnnotatedMethodFilter.java +++ /dev/null @@ -1,66 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2009, 2018 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.analysis.filter; - -import java.util.List; - -import org.objectweb.asm.tree.AnnotationNode; -import org.objectweb.asm.tree.MethodNode; - -/** - * Filters annotated methods. - */ -abstract class AbstractAnnotatedMethodFilter implements IFilter { - private final String descType; - - /** - * Configures a new filter instance. - * - * @param annotationType - * VM type of the annotation - */ - protected AbstractAnnotatedMethodFilter(final String annotationType) { - this.descType = "L" + annotationType + ";"; - } - - public void filter(final String className, final String superClassName, - final MethodNode methodNode, final IFilterOutput output) { - if (hasAnnotation(methodNode)) { - output.ignore(methodNode.instructions.getFirst(), - methodNode.instructions.getLast()); - } - } - - private boolean hasAnnotation(final MethodNode methodNode) { - final List<AnnotationNode> annotations = getAnnotations(methodNode); - if (annotations != null) { - for (final AnnotationNode annotation : annotations) { - if (descType.equals(annotation.desc)) { - return true; - } - } - } - return false; - } - - /** - * Retrieves the annotations to search from a method. Depending on the - * retention of the annotation this is either - * <code>visibleAnnotations</code> or <code>invisibleAnnotations</code>. - * - * @param methodNode - * method to retrieve annotations from - * @return list of annotations - */ - abstract List<AnnotationNode> getAnnotations(final MethodNode methodNode); - -} diff --git a/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/AbstractMatcher.java b/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/AbstractMatcher.java index b5aea723..38860a83 100644 --- a/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/AbstractMatcher.java +++ b/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/AbstractMatcher.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2018 Mountainminds GmbH & Co. KG and Contributors + * Copyright (c) 2009, 2019 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 @@ -17,6 +17,8 @@ import java.util.Map; import org.objectweb.asm.Opcodes; import org.objectweb.asm.tree.AbstractInsnNode; import org.objectweb.asm.tree.MethodInsnNode; +import org.objectweb.asm.tree.MethodNode; +import org.objectweb.asm.tree.TypeInsnNode; import org.objectweb.asm.tree.VarInsnNode; abstract class AbstractMatcher { @@ -25,13 +27,49 @@ abstract class AbstractMatcher { AbstractInsnNode cursor; - final void nextIsInvokeVirtual(final String owner, final String name) { - nextIs(Opcodes.INVOKEVIRTUAL); + /** + * Sets {@link #cursor} to first instruction of method if it is + * <code>ALOAD 0</code>, otherwise sets it to <code>null</code>. + */ + final void firstIsALoad0(final MethodNode methodNode) { + cursor = methodNode.instructions.getFirst(); + skipNonOpcodes(); + if (cursor.getOpcode() == Opcodes.ALOAD + && ((VarInsnNode) cursor).var == 0) { + return; + } + cursor = null; + } + + /** + * Moves {@link #cursor} to next instruction if it is {@link TypeInsnNode} + * with given opcode and operand, otherwise sets it to <code>null</code>. + */ + final void nextIsType(final int opcode, final String desc) { + nextIs(opcode); + if (cursor == null) { + return; + } + if (((TypeInsnNode) cursor).desc.equals(desc)) { + return; + } + cursor = null; + } + + /** + * Moves {@link #cursor} to next instruction if it is {@link MethodInsnNode} + * with given opcode, owner, name and descriptor, otherwise sets it to + * <code>null</code>. + */ + final void nextIsInvoke(final int opcode, final String owner, + final String name, final String descriptor) { + nextIs(opcode); if (cursor == null) { return; } final MethodInsnNode m = (MethodInsnNode) cursor; - if (owner.equals(m.owner) && name.equals(m.name)) { + if (owner.equals(m.owner) && name.equals(m.name) + && descriptor.equals(m.desc)) { return; } cursor = null; @@ -52,6 +90,25 @@ abstract class AbstractMatcher { } /** + * Moves {@link #cursor} to next instruction if it is + * <code>TABLESWITCH</code> or <code>LOOKUPSWITCH</code>, otherwise sets it + * to <code>null</code>. + */ + final void nextIsSwitch() { + next(); + if (cursor == null) { + return; + } + switch (cursor.getOpcode()) { + case Opcodes.TABLESWITCH: + case Opcodes.LOOKUPSWITCH: + return; + default: + cursor = null; + } + } + + /** * Moves {@link #cursor} to next instruction if it has given opcode, * otherwise sets it to <code>null</code>. */ @@ -76,12 +133,26 @@ abstract class AbstractMatcher { skipNonOpcodes(); } + /** + * Moves {@link #cursor} through {@link AbstractInsnNode#FRAME}, + * {@link AbstractInsnNode#LABEL}, {@link AbstractInsnNode#LINE}. + */ final void skipNonOpcodes() { + cursor = skipNonOpcodes(cursor); + } + + /** + * Returns first instruction from given and following it that is not + * {@link AbstractInsnNode#FRAME}, {@link AbstractInsnNode#LABEL}, + * {@link AbstractInsnNode#LINE}. + */ + static AbstractInsnNode skipNonOpcodes(AbstractInsnNode cursor) { while (cursor != null && (cursor.getType() == AbstractInsnNode.FRAME || cursor.getType() == AbstractInsnNode.LABEL || cursor.getType() == AbstractInsnNode.LINE)) { cursor = cursor.getNext(); } + return cursor; } } diff --git a/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/AnnotationGeneratedFilter.java b/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/AnnotationGeneratedFilter.java new file mode 100644 index 00000000..d78444c0 --- /dev/null +++ b/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/AnnotationGeneratedFilter.java @@ -0,0 +1,64 @@ +/******************************************************************************* + * Copyright (c) 2009, 2019 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: + * Evgeny Mandrikov - initial API and implementation + * + *******************************************************************************/ +package org.jacoco.core.internal.analysis.filter; + +import java.util.List; + +import org.objectweb.asm.tree.AnnotationNode; +import org.objectweb.asm.tree.MethodNode; + +/** + * Filters classes and methods annotated with + * {@link java.lang.annotation.RetentionPolicy#RUNTIME runtime visible} and + * {@link java.lang.annotation.RetentionPolicy#CLASS invisible} annotation whose + * simple name contains <code>Generated</code>. + */ +public final class AnnotationGeneratedFilter implements IFilter { + + public void filter(final MethodNode methodNode, + final IFilterContext context, final IFilterOutput output) { + + for (String annotation : context.getClassAnnotations()) { + if (matches(annotation)) { + output.ignore(methodNode.instructions.getFirst(), + methodNode.instructions.getLast()); + return; + } + } + + if (presentIn(methodNode.invisibleAnnotations) + || presentIn(methodNode.visibleAnnotations)) { + output.ignore(methodNode.instructions.getFirst(), + methodNode.instructions.getLast()); + } + + } + + private static boolean matches(final String annotation) { + final String name = annotation + .substring(Math.max(annotation.lastIndexOf('/'), + annotation.lastIndexOf('$')) + 1); + return name.contains("Generated"); + } + + private static boolean presentIn(final List<AnnotationNode> annotations) { + if (annotations != null) { + for (AnnotationNode annotation : annotations) { + if (matches(annotation.desc)) { + return true; + } + } + } + return false; + } + +} diff --git a/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/EnumEmptyConstructorFilter.java b/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/EnumEmptyConstructorFilter.java new file mode 100644 index 00000000..4a39d1e8 --- /dev/null +++ b/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/EnumEmptyConstructorFilter.java @@ -0,0 +1,61 @@ +/******************************************************************************* + * Copyright (c) 2009, 2019 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: + * Evgeny Mandrikov - initial API and implementation + * + *******************************************************************************/ +package org.jacoco.core.internal.analysis.filter; + +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.tree.MethodNode; + +/** + * Filters empty enum constructors. + * + * Constructor of enum is invoked from static initialization block to create + * instance of each enum constant. So it won't be executed if number of enum + * constants is zero. Such enums are sometimes used as alternative to classes + * with static utilities and private empty constructor. Implicit constructor of + * enum created by compiler doesn't have a synthetic flag and refers to a line + * of enum definition. Therefore in order to not have partial coverage of enum + * definition line in enums without enum constants and similarly to + * {@link PrivateEmptyNoArgConstructorFilter filter of private empty + * constructors} - empty constructor in enums without additional parameters + * should be filtered out even if it is not implicit. + */ +public final class EnumEmptyConstructorFilter implements IFilter { + + private static final String CONSTRUCTOR_NAME = "<init>"; + private static final String CONSTRUCTOR_DESC = "(Ljava/lang/String;I)V"; + + private static final String ENUM_TYPE = "java/lang/Enum"; + + public void filter(final MethodNode methodNode, + final IFilterContext context, final IFilterOutput output) { + if (ENUM_TYPE.equals(context.getSuperClassName()) + && CONSTRUCTOR_NAME.equals(methodNode.name) + && CONSTRUCTOR_DESC.equals(methodNode.desc) + && new Matcher().match(methodNode)) { + output.ignore(methodNode.instructions.getFirst(), + methodNode.instructions.getLast()); + } + } + + private static class Matcher extends AbstractMatcher { + private boolean match(final MethodNode methodNode) { + firstIsALoad0(methodNode); + nextIs(Opcodes.ALOAD); + nextIs(Opcodes.ILOAD); + nextIsInvoke(Opcodes.INVOKESPECIAL, ENUM_TYPE, CONSTRUCTOR_NAME, + CONSTRUCTOR_DESC); + nextIs(Opcodes.RETURN); + return cursor != null; + } + } + +} diff --git a/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/EnumFilter.java b/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/EnumFilter.java index c5f1784c..0c2eef58 100644 --- a/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/EnumFilter.java +++ b/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/EnumFilter.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2018 Mountainminds GmbH & Co. KG and Contributors + * Copyright (c) 2009, 2019 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 @@ -19,9 +19,10 @@ import org.objectweb.asm.tree.MethodNode; */ public final class EnumFilter implements IFilter { - public void filter(final String className, final String superClassName, - final MethodNode methodNode, final IFilterOutput output) { - if (isMethodFiltered(className, superClassName, methodNode.name, + public void filter(final MethodNode methodNode, + final IFilterContext context, final IFilterOutput output) { + if (isMethodFiltered(context.getClassName(), + context.getSuperClassName(), methodNode.name, methodNode.desc)) { output.ignore(methodNode.instructions.getFirst(), methodNode.instructions.getLast()); 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 0f344180..7a1053b5 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 @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2018 Mountainminds GmbH & Co. KG and Contributors + * Copyright (c) 2009, 2019 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 @@ -23,26 +23,38 @@ public final class Filters implements IFilter { */ public static final IFilter NONE = new Filters(); + private final IFilter[] filters; + /** - * Filter that combines all other filters. + * Creates filter that combines all other filters. + * + * @return filter that combines all other filters */ - public static final IFilter ALL = new Filters(new EnumFilter(), - new SyntheticFilter(), new SynchronizedFilter(), - new TryWithResourcesJavacFilter(), new TryWithResourcesEcjFilter(), - new FinallyFilter(), new PrivateEmptyNoArgConstructorFilter(), - new StringSwitchJavacFilter(), new LombokGeneratedFilter(), - new GroovyGeneratedFilter()); - - private final IFilter[] filters; + public static IFilter all() { + return new Filters(new EnumFilter(), new SyntheticFilter(), + new SynchronizedFilter(), new TryWithResourcesJavac11Filter(), + new TryWithResourcesJavacFilter(), + new TryWithResourcesEcjFilter(), new FinallyFilter(), + new PrivateEmptyNoArgConstructorFilter(), + new StringSwitchJavacFilter(), new StringSwitchEcjFilter(), + new EnumEmptyConstructorFilter(), + new AnnotationGeneratedFilter(), new KotlinGeneratedFilter(), + new KotlinLateinitFilter(), new KotlinWhenFilter(), + new KotlinWhenStringFilter(), + new KotlinUnsafeCastOperatorFilter(), + new KotlinNotNullOperatorFilter(), + new KotlinDefaultArgumentsFilter(), new KotlinInlineFilter(), + new KotlinCoroutineFilter()); + } private Filters(final IFilter... filters) { this.filters = filters; } - public void filter(final String className, final String superClassName, - final MethodNode methodNode, final IFilterOutput output) { + public void filter(final MethodNode methodNode, + final IFilterContext context, final IFilterOutput output) { for (final IFilter filter : filters) { - filter.filter(className, superClassName, methodNode, output); + filter.filter(methodNode, context, output); } } diff --git a/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/FinallyFilter.java b/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/FinallyFilter.java index 830d0c28..b2e0b145 100644 --- a/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/FinallyFilter.java +++ b/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/FinallyFilter.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2018 Mountainminds GmbH & Co. KG and Contributors + * Copyright (c) 2009, 2019 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 @@ -67,8 +67,8 @@ import org.objectweb.asm.tree.VarInsnNode; */ public final class FinallyFilter implements IFilter { - public void filter(final String className, final String superClassName, - final MethodNode methodNode, final IFilterOutput output) { + public void filter(final MethodNode methodNode, + final IFilterContext context, final IFilterOutput output) { for (final TryCatchBlockNode tryCatchBlock : methodNode.tryCatchBlocks) { if (tryCatchBlock.type == null) { filter(output, methodNode.tryCatchBlocks, tryCatchBlock); diff --git a/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/GroovyGeneratedFilter.java b/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/GroovyGeneratedFilter.java deleted file mode 100644 index 816cc4cd..00000000 --- a/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/GroovyGeneratedFilter.java +++ /dev/null @@ -1,36 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2009, 2018 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.analysis.filter; - -import java.util.List; - -import org.objectweb.asm.tree.AnnotationNode; -import org.objectweb.asm.tree.MethodNode; - -/** - * Filters methods annotated with <code>@groovy.transform.Generated</code>. - */ -public final class GroovyGeneratedFilter extends AbstractAnnotatedMethodFilter { - - /** - * New filter. - */ - public GroovyGeneratedFilter() { - super("groovy/transform/Generated"); - } - - @Override - List<AnnotationNode> getAnnotations(final MethodNode methodNode) { - return methodNode.visibleAnnotations; - } - -} diff --git a/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/IFilter.java b/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/IFilter.java index 82b7959a..b662ba4f 100644 --- a/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/IFilter.java +++ b/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/IFilter.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2018 Mountainminds GmbH & Co. KG and Contributors + * Copyright (c) 2009, 2019 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 @@ -14,8 +14,8 @@ package org.jacoco.core.internal.analysis.filter; import org.objectweb.asm.tree.MethodNode; /** - * Interface for filter implementations. Instances of filters are reused and so - * must be stateless. + * Interface for filter implementations. Instances of filters are created for + * analysis of each class and so can have per-class state. */ public interface IFilter { @@ -24,16 +24,13 @@ public interface IFilter { * expected to inspect the provided method and report its result to the * given {@link IFilterOutput} instance. * - * @param className - * class name - * @param superClassName - * superclass name * @param methodNode * method to inspect + * @param context + * context information for the method * @param output * callback to report filtering results to */ - void filter(String className, String superClassName, MethodNode methodNode, + void filter(MethodNode methodNode, IFilterContext context, IFilterOutput output); - } diff --git a/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/IFilterContext.java b/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/IFilterContext.java new file mode 100644 index 00000000..8b97654d --- /dev/null +++ b/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/IFilterContext.java @@ -0,0 +1,48 @@ +/*******************************************************************************
+ * Copyright (c) 2009, 2019 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.analysis.filter;
+
+import java.util.Set;
+
+/**
+ * Context information provided to filters.
+ */
+public interface IFilterContext {
+
+ /**
+ * @return vm name of the enclosing class
+ */
+ String getClassName();
+
+ /**
+ * @return vm name of the super class of the enclosing class
+ */
+ String getSuperClassName();
+
+ /**
+ * @return vm names of the class annotations of the enclosing class
+ */
+ Set<String> getClassAnnotations();
+
+ /**
+ * @return file name of the corresponding source file or <code>null</code>
+ * if not available
+ */
+ String getSourceFileName();
+
+ /**
+ * @return value of SourceDebugExtension attribute or <code>null</code> if
+ * not available
+ */
+ String getSourceDebugExtension();
+
+}
diff --git a/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/IFilterOutput.java b/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/IFilterOutput.java index 4ca9b814..0f022201 100644 --- a/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/IFilterOutput.java +++ b/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/IFilterOutput.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2018 Mountainminds GmbH & Co. KG and Contributors + * Copyright (c) 2009, 2019 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 @@ -11,6 +11,8 @@ *******************************************************************************/ package org.jacoco.core.internal.analysis.filter; +import java.util.Set; + import org.objectweb.asm.tree.AbstractInsnNode; /** @@ -41,4 +43,16 @@ public interface IFilterOutput { */ void merge(AbstractInsnNode i1, AbstractInsnNode i2); + /** + * Marks instruction whose outgoing branches should be replaced during + * computation of coverage. + * + * @param source + * instruction which branches should be replaced + * @param newTargets + * new targets of branches + */ + void replaceBranches(AbstractInsnNode source, + Set<AbstractInsnNode> newTargets); + } diff --git a/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/KotlinCoroutineFilter.java b/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/KotlinCoroutineFilter.java new file mode 100644 index 00000000..66d450a3 --- /dev/null +++ b/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/KotlinCoroutineFilter.java @@ -0,0 +1,215 @@ +/******************************************************************************* + * Copyright (c) 2009, 2019 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: + * Evgeny Mandrikov - initial API and implementation + * + *******************************************************************************/ +package org.jacoco.core.internal.analysis.filter; + +import java.util.ArrayList; +import java.util.List; + +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Type; +import org.objectweb.asm.tree.AbstractInsnNode; +import org.objectweb.asm.tree.JumpInsnNode; +import org.objectweb.asm.tree.LdcInsnNode; +import org.objectweb.asm.tree.MethodNode; +import org.objectweb.asm.tree.TableSwitchInsnNode; + +/** + * Filters branches that Kotlin compiler generates for coroutines. + */ +public final class KotlinCoroutineFilter implements IFilter { + + static boolean isLastArgumentContinuation(final MethodNode methodNode) { + final Type methodType = Type.getMethodType(methodNode.desc); + final int lastArgument = methodType.getArgumentTypes().length - 1; + return lastArgument >= 0 && "kotlin.coroutines.Continuation".equals( + methodType.getArgumentTypes()[lastArgument].getClassName()); + } + + public void filter(final MethodNode methodNode, + final IFilterContext context, final IFilterOutput output) { + + if (!KotlinGeneratedFilter.isKotlinClass(context)) { + return; + } + + new Matcher().match(methodNode, output); + + } + + private static class Matcher extends AbstractMatcher { + private void match(final MethodNode methodNode, + final IFilterOutput output) { + cursor = methodNode.instructions.getFirst(); + nextIsInvoke(Opcodes.INVOKESTATIC, + "kotlin/coroutines/intrinsics/IntrinsicsKt", + "getCOROUTINE_SUSPENDED", "()Ljava/lang/Object;"); + + if (cursor == null) { + cursor = skipNonOpcodes(methodNode.instructions.getFirst()); + + nextIsCreateStateInstance(); + + nextIsInvoke(Opcodes.INVOKESTATIC, + "kotlin/coroutines/intrinsics/IntrinsicsKt", + "getCOROUTINE_SUSPENDED", "()Ljava/lang/Object;"); + } + + nextIsVar(Opcodes.ASTORE, "COROUTINE_SUSPENDED"); + nextIsVar(Opcodes.ALOAD, "this"); + nextIs(Opcodes.GETFIELD); + nextIs(Opcodes.TABLESWITCH); + if (cursor == null) { + return; + } + final TableSwitchInsnNode s = (TableSwitchInsnNode) cursor; + final List<AbstractInsnNode> ignore = new ArrayList<AbstractInsnNode>( + s.labels.size() * 2); + + nextIs(Opcodes.ALOAD); + nextIsThrowOnFailure(); + + if (cursor == null) { + return; + } + ignore.add(methodNode.instructions.getFirst()); + ignore.add(cursor); + + int suspensionPoint = 1; + for (AbstractInsnNode i = cursor; i != null + && suspensionPoint < s.labels.size(); i = i.getNext()) { + cursor = i; + nextIsVar(Opcodes.ALOAD, "COROUTINE_SUSPENDED"); + nextIs(Opcodes.IF_ACMPNE); + if (cursor == null) { + continue; + } + final AbstractInsnNode continuationAfterLoadedResult = skipNonOpcodes( + ((JumpInsnNode) cursor).label); + nextIsVar(Opcodes.ALOAD, "COROUTINE_SUSPENDED"); + nextIs(Opcodes.ARETURN); + if (cursor == null + || skipNonOpcodes(cursor.getNext()) != skipNonOpcodes( + s.labels.get(suspensionPoint))) { + continue; + } + + for (AbstractInsnNode j = i; j != null; j = j.getNext()) { + cursor = j; + nextIs(Opcodes.ALOAD); + nextIsThrowOnFailure(); + + nextIs(Opcodes.ALOAD); + if (cursor != null && skipNonOpcodes(cursor + .getNext()) == continuationAfterLoadedResult) { + ignore.add(i); + ignore.add(cursor); + suspensionPoint++; + break; + } + } + } + + cursor = s.dflt; + nextIsType(Opcodes.NEW, "java/lang/IllegalStateException"); + nextIs(Opcodes.DUP); + nextIs(Opcodes.LDC); + if (cursor == null) { + return; + } + if (!((LdcInsnNode) cursor).cst.equals( + "call to 'resume' before 'invoke' with coroutine")) { + return; + } + nextIsInvoke(Opcodes.INVOKESPECIAL, + "java/lang/IllegalStateException", "<init>", + "(Ljava/lang/String;)V"); + nextIs(Opcodes.ATHROW); + if (cursor == null) { + return; + } + + output.ignore(s.dflt, cursor); + for (int i = 0; i < ignore.size(); i += 2) { + output.ignore(ignore.get(i), ignore.get(i + 1)); + } + } + + private void nextIsThrowOnFailure() { + final AbstractInsnNode c = cursor; + nextIsInvoke(Opcodes.INVOKESTATIC, "kotlin/ResultKt", + "throwOnFailure", "(Ljava/lang/Object;)V"); + if (cursor == null) { + cursor = c; + // Before resolution of + // https://youtrack.jetbrains.com/issue/KT-28015 in + // Kotlin 1.3.30 + nextIs(Opcodes.DUP); + nextIsType(Opcodes.INSTANCEOF, "kotlin/Result$Failure"); + nextIs(Opcodes.IFEQ); + nextIsType(Opcodes.CHECKCAST, "kotlin/Result$Failure"); + nextIs(Opcodes.GETFIELD); + nextIs(Opcodes.ATHROW); + nextIs(Opcodes.POP); + } + } + + private void nextIsCreateStateInstance() { + nextIs(Opcodes.INSTANCEOF); + + nextIs(Opcodes.IFEQ); + if (cursor == null) { + return; + } + final AbstractInsnNode createStateInstance = skipNonOpcodes( + ((JumpInsnNode) cursor).label); + + nextIs(Opcodes.ALOAD); + nextIs(Opcodes.CHECKCAST); + nextIs(Opcodes.ASTORE); + + nextIs(Opcodes.ALOAD); + nextIs(Opcodes.GETFIELD); + + nextIs(Opcodes.LDC); + nextIs(Opcodes.IAND); + nextIs(Opcodes.IFEQ); + if (cursor == null || skipNonOpcodes( + ((JumpInsnNode) cursor).label) != createStateInstance) { + return; + } + + nextIs(Opcodes.ALOAD); + nextIs(Opcodes.DUP); + nextIs(Opcodes.GETFIELD); + + nextIs(Opcodes.LDC); + nextIs(Opcodes.ISUB); + nextIs(Opcodes.PUTFIELD); + + nextIs(Opcodes.GOTO); + if (cursor == null) { + return; + } + final AbstractInsnNode afterCoroutineStateCreated = skipNonOpcodes( + ((JumpInsnNode) cursor).label); + + if (skipNonOpcodes(cursor.getNext()) != createStateInstance) { + return; + } + + cursor = afterCoroutineStateCreated; + nextIs(Opcodes.GETFIELD); + nextIs(Opcodes.ASTORE); + } + } + +} diff --git a/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/KotlinDefaultArgumentsFilter.java b/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/KotlinDefaultArgumentsFilter.java new file mode 100644 index 00000000..ef198447 --- /dev/null +++ b/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/KotlinDefaultArgumentsFilter.java @@ -0,0 +1,97 @@ +/******************************************************************************* + * Copyright (c) 2009, 2019 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: + * Evgeny Mandrikov - initial API and implementation + * + *******************************************************************************/ +package org.jacoco.core.internal.analysis.filter; + +import java.util.HashSet; +import java.util.Set; + +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Type; +import org.objectweb.asm.tree.AbstractInsnNode; +import org.objectweb.asm.tree.JumpInsnNode; +import org.objectweb.asm.tree.MethodNode; +import org.objectweb.asm.tree.VarInsnNode; + +/** + * Filters branches that Kotlin compiler generates for default arguments. + * + * For each default argument Kotlin compiler generates following bytecode to + * determine if it should be used or not: + * + * <pre> + * ILOAD maskVar + * ICONST_x, BIPUSH, SIPUSH, LDC or LDC_W + * IAND + * IFEQ label + * default argument + * label: + * </pre> + * + * Where <code>maskVar</code> is penultimate argument of synthetic method with + * suffix "$default". And its value can't be zero - invocation with all + * arguments uses original non synthetic method, thus <code>IFEQ</code> + * instructions should be ignored. + */ +public final class KotlinDefaultArgumentsFilter implements IFilter { + + static boolean isDefaultArgumentsMethodName(final String methodName) { + return methodName.endsWith("$default"); + } + + public void filter(final MethodNode methodNode, + final IFilterContext context, final IFilterOutput output) { + if ((methodNode.access & Opcodes.ACC_SYNTHETIC) == 0) { + return; + } + if (!isDefaultArgumentsMethodName(methodNode.name)) { + return; + } + if (!KotlinGeneratedFilter.isKotlinClass(context)) { + return; + } + + new Matcher().match(methodNode, output); + } + + private static class Matcher extends AbstractMatcher { + public void match(final MethodNode methodNode, + final IFilterOutput output) { + cursor = methodNode.instructions.getFirst(); + + final Set<AbstractInsnNode> ignore = new HashSet<AbstractInsnNode>(); + final int maskVar = Type.getMethodType(methodNode.desc) + .getArgumentTypes().length - 2; + while (true) { + if (cursor.getOpcode() != Opcodes.ILOAD) { + break; + } + if (((VarInsnNode) cursor).var != maskVar) { + break; + } + next(); + nextIs(Opcodes.IAND); + nextIs(Opcodes.IFEQ); + if (cursor == null) { + return; + } + ignore.add(cursor); + cursor = ((JumpInsnNode) cursor).label; + skipNonOpcodes(); + } + + for (AbstractInsnNode i : ignore) { + output.ignore(i, i); + } + } + } + +} diff --git a/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/KotlinGeneratedFilter.java b/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/KotlinGeneratedFilter.java new file mode 100644 index 00000000..129580a9 --- /dev/null +++ b/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/KotlinGeneratedFilter.java @@ -0,0 +1,61 @@ +/******************************************************************************* + * Copyright (c) 2009, 2019 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: + * Nikolay Krasko - initial API and implementation + * + *******************************************************************************/ +package org.jacoco.core.internal.analysis.filter; + +import org.objectweb.asm.tree.AbstractInsnNode; +import org.objectweb.asm.tree.MethodNode; + +/** + * Filters methods generated by the Kotlin compiler. Kotlin classes are + * identified by the <code>@kotlin.Metadata</code> annotations. In such classes + * generated methods do not have line numbers. + */ +public class KotlinGeneratedFilter implements IFilter { + + static final String KOTLIN_METADATA_DESC = "Lkotlin/Metadata;"; + + static boolean isKotlinClass(final IFilterContext context) { + return context.getClassAnnotations().contains(KOTLIN_METADATA_DESC); + } + + public void filter(final MethodNode methodNode, + final IFilterContext context, final IFilterOutput output) { + + if (context.getSourceFileName() == null) { + // probably full debug information is missing + // disabled filtering as all methods might be erroneously skipped + return; + } + + if (!isKotlinClass(context)) { + return; + } + + if (hasLineNumber(methodNode)) { + return; + } + + output.ignore(methodNode.instructions.getFirst(), + methodNode.instructions.getLast()); + } + + private boolean hasLineNumber(final MethodNode methodNode) { + for (AbstractInsnNode i = methodNode.instructions + .getFirst(); i != null; i = i.getNext()) { + if (AbstractInsnNode.LINE == i.getType()) { + return true; + } + } + return false; + } + +} diff --git a/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/KotlinInlineFilter.java b/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/KotlinInlineFilter.java new file mode 100644 index 00000000..5666de2d --- /dev/null +++ b/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/KotlinInlineFilter.java @@ -0,0 +1,139 @@ +/******************************************************************************* + * Copyright (c) 2009, 2019 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: + * Evgeny Mandrikov - initial API and implementation + * + *******************************************************************************/ +package org.jacoco.core.internal.analysis.filter; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.StringReader; +import java.util.BitSet; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.objectweb.asm.tree.AbstractInsnNode; +import org.objectweb.asm.tree.LineNumberNode; +import org.objectweb.asm.tree.MethodNode; + +/** + * Filters out instructions that were inlined by Kotlin compiler. + */ +public final class KotlinInlineFilter implements IFilter { + + private int firstGeneratedLineNumber = -1; + + public void filter(final MethodNode methodNode, + final IFilterContext context, final IFilterOutput output) { + if (context.getSourceDebugExtension() == null) { + return; + } + + if (!KotlinGeneratedFilter.isKotlinClass(context)) { + return; + } + + if (firstGeneratedLineNumber == -1) { + firstGeneratedLineNumber = getFirstGeneratedLineNumber( + context.getSourceFileName(), + context.getSourceDebugExtension()); + } + + int line = 0; + for (AbstractInsnNode i = methodNode.instructions + .getFirst(); i != null; i = i.getNext()) { + if (AbstractInsnNode.LINE == i.getType()) { + line = ((LineNumberNode) i).line; + } + if (line >= firstGeneratedLineNumber) { + output.ignore(i, i); + } + } + } + + private static int getFirstGeneratedLineNumber(final String sourceFileName, + final String smap) { + try { + final BufferedReader br = new BufferedReader( + new StringReader(smap)); + expectLine(br, "SMAP"); + // OutputFileName + expectLine(br, sourceFileName); + // DefaultStratumId + expectLine(br, "Kotlin"); + // StratumSection + expectLine(br, "*S Kotlin"); + // FileSection + expectLine(br, "*F"); + final BitSet sourceFileIds = new BitSet(); + String line; + while (!"*L".equals(line = br.readLine())) { + // AbsoluteFileName + br.readLine(); + + final Matcher m = FILE_INFO_PATTERN.matcher(line); + if (!m.matches()) { + throw new IllegalStateException( + "Unexpected SMAP line: " + line); + } + final String fileName = m.group(2); + if (fileName.equals(sourceFileName)) { + sourceFileIds.set(Integer.parseInt(m.group(1))); + } + } + if (sourceFileIds.isEmpty()) { + throw new IllegalStateException("Unexpected SMAP FileSection"); + } + // LineSection + int min = Integer.MAX_VALUE; + while (!"*E".equals(line = br.readLine())) { + final Matcher m = LINE_INFO_PATTERN.matcher(line); + if (!m.matches()) { + throw new IllegalStateException( + "Unexpected SMAP line: " + line); + } + final int inputStartLine = Integer.parseInt(m.group(1)); + final int lineFileID = Integer + .parseInt(m.group(2).substring(1)); + final int outputStartLine = Integer.parseInt(m.group(4)); + if (sourceFileIds.get(lineFileID) + && inputStartLine == outputStartLine) { + continue; + } + min = Math.min(outputStartLine, min); + } + return min; + } catch (final IOException e) { + // Must not happen with StringReader + throw new AssertionError(e); + } + } + + private static void expectLine(final BufferedReader br, + final String expected) throws IOException { + final String line = br.readLine(); + if (!expected.equals(line)) { + throw new IllegalStateException("Unexpected SMAP line: " + line); + } + } + + private static final Pattern LINE_INFO_PATTERN = Pattern.compile("" // + + "([0-9]++)" // InputStartLine + + "(#[0-9]++)?+" // LineFileID + + "(,[0-9]++)?+" // RepeatCount + + ":([0-9]++)" // OutputStartLine + + "(,[0-9]++)?+" // OutputLineIncrement + ); + + private static final Pattern FILE_INFO_PATTERN = Pattern.compile("" // + + "\\+ ([0-9]++)" // FileID + + " (.++)" // FileName + ); + +} diff --git a/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/KotlinLateinitFilter.java b/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/KotlinLateinitFilter.java new file mode 100644 index 00000000..12fe926c --- /dev/null +++ b/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/KotlinLateinitFilter.java @@ -0,0 +1,52 @@ +/******************************************************************************* + * Copyright (c) 2009, 2019 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: + * Fabian Mastenbroek - 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 branch in bytecode that Kotlin compiler generates for reading from + * <code>lateinit</code> properties. + */ +public class KotlinLateinitFilter implements IFilter { + + public void filter(final MethodNode methodNode, + final IFilterContext context, final IFilterOutput output) { + final Matcher matcher = new Matcher(); + for (AbstractInsnNode i = methodNode.instructions + .getFirst(); i != null; i = i.getNext()) { + matcher.match(i, output); + } + } + + private static class Matcher extends AbstractMatcher { + public void match(final AbstractInsnNode start, + final IFilterOutput output) { + + if (Opcodes.IFNONNULL != start.getOpcode()) { + return; + } + cursor = start; + + nextIs(Opcodes.LDC); + nextIsInvoke(Opcodes.INVOKESTATIC, "kotlin/jvm/internal/Intrinsics", + "throwUninitializedPropertyAccessException", + "(Ljava/lang/String;)V"); + + if (cursor != null) { + output.ignore(start, cursor); + } + } + } +} diff --git a/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/KotlinNotNullOperatorFilter.java b/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/KotlinNotNullOperatorFilter.java new file mode 100644 index 00000000..4dd223a3 --- /dev/null +++ b/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/KotlinNotNullOperatorFilter.java @@ -0,0 +1,49 @@ +/******************************************************************************* + * Copyright (c) 2009, 2019 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: + * 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 branch in bytecode that Kotlin compiler generates for not-null + * assertion operator. + */ +public final class KotlinNotNullOperatorFilter implements IFilter { + + public void filter(final MethodNode methodNode, + final IFilterContext context, final IFilterOutput output) { + final Matcher matcher = new Matcher(); + for (AbstractInsnNode i = methodNode.instructions + .getFirst(); i != null; i = i.getNext()) { + matcher.match(i, output); + } + } + + private static class Matcher extends AbstractMatcher { + public void match(final AbstractInsnNode start, + final IFilterOutput output) { + if (Opcodes.IFNONNULL != start.getOpcode()) { + return; + } + cursor = start; + nextIsInvoke(Opcodes.INVOKESTATIC, "kotlin/jvm/internal/Intrinsics", + "throwNpe", "()V"); + if (cursor == null) { + return; + } + output.ignore(start, cursor); + } + } + +} diff --git a/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/KotlinUnsafeCastOperatorFilter.java b/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/KotlinUnsafeCastOperatorFilter.java new file mode 100644 index 00000000..c298e945 --- /dev/null +++ b/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/KotlinUnsafeCastOperatorFilter.java @@ -0,0 +1,71 @@ +/******************************************************************************* + * Copyright (c) 2009, 2019 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: + * 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.JumpInsnNode; +import org.objectweb.asm.tree.LdcInsnNode; +import org.objectweb.asm.tree.MethodNode; + +/** + * Filters branch in bytecode that Kotlin compiler generates for "unsafe" cast + * operator. + */ +public final class KotlinUnsafeCastOperatorFilter implements IFilter { + + private static final String KOTLIN_TYPE_CAST_EXCEPTION = "kotlin/TypeCastException"; + + public void filter(final MethodNode methodNode, + final IFilterContext context, final IFilterOutput output) { + final Matcher matcher = new Matcher(); + for (AbstractInsnNode i = methodNode.instructions + .getFirst(); i != null; i = i.getNext()) { + matcher.match(i, output); + } + } + + private static class Matcher extends AbstractMatcher { + public void match(final AbstractInsnNode start, + final IFilterOutput output) { + + if (Opcodes.IFNONNULL != start.getOpcode()) { + return; + } + cursor = start; + + nextIsType(Opcodes.NEW, KOTLIN_TYPE_CAST_EXCEPTION); + nextIs(Opcodes.DUP); + nextIs(Opcodes.LDC); + if (cursor == null) { + return; + } + final LdcInsnNode ldc = (LdcInsnNode) cursor; + if (!(ldc.cst instanceof String && ((String) ldc.cst) + .startsWith("null cannot be cast to non-null type"))) { + return; + } + nextIsInvoke(Opcodes.INVOKESPECIAL, KOTLIN_TYPE_CAST_EXCEPTION, + "<init>", "(Ljava/lang/String;)V"); + nextIs(Opcodes.ATHROW); + if (cursor == null) { + return; + } + if (cursor.getNext() != ((JumpInsnNode) start).label) { + return; + } + + output.ignore(start, cursor); + } + } + +} diff --git a/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/KotlinWhenFilter.java b/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/KotlinWhenFilter.java new file mode 100644 index 00000000..a229aa04 --- /dev/null +++ b/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/KotlinWhenFilter.java @@ -0,0 +1,101 @@ +/******************************************************************************* + * Copyright (c) 2009, 2019 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: + * Evgeny Mandrikov - initial API and implementation + * + *******************************************************************************/ +package org.jacoco.core.internal.analysis.filter; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.tree.AbstractInsnNode; +import org.objectweb.asm.tree.InsnNode; +import org.objectweb.asm.tree.JumpInsnNode; +import org.objectweb.asm.tree.LabelNode; +import org.objectweb.asm.tree.LookupSwitchInsnNode; +import org.objectweb.asm.tree.MethodNode; +import org.objectweb.asm.tree.TableSwitchInsnNode; + +/** + * Filters bytecode that Kotlin compiler generates for <code>when</code> + * expressions which list all cases of <code>enum</code> or + * <code>sealed class</code>, i.e. which don't require explicit + * <code>else</code>. + */ +public final class KotlinWhenFilter implements IFilter { + + private static final String EXCEPTION = "kotlin/NoWhenBranchMatchedException"; + + public void filter(final MethodNode methodNode, + final IFilterContext context, final IFilterOutput output) { + final Matcher matcher = new Matcher(); + for (AbstractInsnNode i = methodNode.instructions + .getFirst(); i != null; i = i.getNext()) { + matcher.match(i, output); + } + } + + private static class Matcher extends AbstractMatcher { + void match(final AbstractInsnNode start, final IFilterOutput output) { + if (start.getType() != InsnNode.LABEL) { + return; + } + cursor = start; + + nextIsType(Opcodes.NEW, EXCEPTION); + nextIs(Opcodes.DUP); + nextIsInvoke(Opcodes.INVOKESPECIAL, EXCEPTION, "<init>", "()V"); + nextIs(Opcodes.ATHROW); + + for (AbstractInsnNode i = cursor; i != null; i = i.getPrevious()) { + if (i.getOpcode() == Opcodes.IFEQ + && ((JumpInsnNode) i).label == start) { + output.ignore(i, i); + output.ignore(start, cursor); + return; + + } else if (getDefaultLabel(i) == start) { + ignoreDefaultBranch(i, output); + output.ignore(start, cursor); + return; + + } + } + } + } + + private static LabelNode getDefaultLabel(final AbstractInsnNode i) { + switch (i.getOpcode()) { + case Opcodes.LOOKUPSWITCH: + return ((LookupSwitchInsnNode) i).dflt; + case Opcodes.TABLESWITCH: + return ((TableSwitchInsnNode) i).dflt; + default: + return null; + } + } + + private static void ignoreDefaultBranch(final AbstractInsnNode switchNode, + final IFilterOutput output) { + final List<LabelNode> labels; + if (switchNode.getOpcode() == Opcodes.LOOKUPSWITCH) { + labels = ((LookupSwitchInsnNode) switchNode).labels; + } else { + labels = ((TableSwitchInsnNode) switchNode).labels; + } + final Set<AbstractInsnNode> newTargets = new HashSet<AbstractInsnNode>(); + for (LabelNode label : labels) { + newTargets.add(AbstractMatcher.skipNonOpcodes(label)); + } + output.replaceBranches(switchNode, newTargets); + } + +} diff --git a/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/KotlinWhenStringFilter.java b/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/KotlinWhenStringFilter.java new file mode 100644 index 00000000..fcccb550 --- /dev/null +++ b/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/KotlinWhenStringFilter.java @@ -0,0 +1,103 @@ +/******************************************************************************* + * Copyright (c) 2009, 2019 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: + * Evgeny Mandrikov - initial API and implementation + * + *******************************************************************************/ +package org.jacoco.core.internal.analysis.filter; + +import java.util.HashSet; +import java.util.Set; + +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.tree.AbstractInsnNode; +import org.objectweb.asm.tree.JumpInsnNode; +import org.objectweb.asm.tree.LabelNode; +import org.objectweb.asm.tree.LookupSwitchInsnNode; +import org.objectweb.asm.tree.MethodNode; +import org.objectweb.asm.tree.TableSwitchInsnNode; +import org.objectweb.asm.tree.VarInsnNode; + +/** + * Filters bytecode that Kotlin compiler generates for <code>when</code> + * expressions with a <code>String</code>. + */ +public final class KotlinWhenStringFilter implements IFilter { + + public void filter(final MethodNode methodNode, + final IFilterContext context, final IFilterOutput output) { + final Matcher matcher = new Matcher(); + for (AbstractInsnNode i = methodNode.instructions + .getFirst(); i != null; i = i.getNext()) { + matcher.match(i, output); + } + } + + private static class Matcher extends AbstractMatcher { + public void match(final AbstractInsnNode start, + final IFilterOutput output) { + + if (Opcodes.ALOAD != start.getOpcode()) { + return; + } + cursor = start; + nextIsInvoke(Opcodes.INVOKEVIRTUAL, "java/lang/String", "hashCode", + "()I"); + nextIsSwitch(); + if (cursor == null) { + return; + } + vars.put("s", (VarInsnNode) start); + + final AbstractInsnNode s = cursor; + final int hashCodes; + final LabelNode defaultLabel; + if (s.getOpcode() == Opcodes.LOOKUPSWITCH) { + final LookupSwitchInsnNode lookupSwitch = (LookupSwitchInsnNode) cursor; + defaultLabel = lookupSwitch.dflt; + hashCodes = lookupSwitch.labels.size(); + } else { + final TableSwitchInsnNode tableSwitch = (TableSwitchInsnNode) cursor; + defaultLabel = tableSwitch.dflt; + hashCodes = tableSwitch.labels.size(); + } + + final Set<AbstractInsnNode> replacements = new HashSet<AbstractInsnNode>(); + replacements.add(skipNonOpcodes(defaultLabel)); + + for (int i = 0; i < hashCodes; i++) { + while (true) { + nextIsVar(Opcodes.ALOAD, "s"); + nextIs(Opcodes.LDC); + nextIsInvoke(Opcodes.INVOKEVIRTUAL, "java/lang/String", + "equals", "(Ljava/lang/Object;)Z"); + // jump to next comparison or default case + nextIs(Opcodes.IFEQ); + final JumpInsnNode jump = (JumpInsnNode) cursor; + // jump to case + nextIs(Opcodes.GOTO); + if (cursor == null) { + return; + } + + replacements + .add(skipNonOpcodes(((JumpInsnNode) cursor).label)); + + if (jump.label == defaultLabel) { + // end of comparisons for same hashCode + break; + } + } + } + + output.ignore(s.getNext(), cursor); + output.replaceBranches(s, replacements); + } + } + +} diff --git a/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/LombokGeneratedFilter.java b/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/LombokGeneratedFilter.java deleted file mode 100644 index b35e741f..00000000 --- a/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/LombokGeneratedFilter.java +++ /dev/null @@ -1,36 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2009, 2018 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.analysis.filter; - -import java.util.List; - -import org.objectweb.asm.tree.AnnotationNode; -import org.objectweb.asm.tree.MethodNode; - -/** - * Filters methods annotated with <code>@lombok.Generated</code>. - */ -public final class LombokGeneratedFilter extends AbstractAnnotatedMethodFilter { - - /** - * New filter. - */ - public LombokGeneratedFilter() { - super("lombok/Generated"); - } - - @Override - List<AnnotationNode> getAnnotations(final MethodNode methodNode) { - return methodNode.invisibleAnnotations; - } - -} diff --git a/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/PrivateEmptyNoArgConstructorFilter.java b/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/PrivateEmptyNoArgConstructorFilter.java index 08c654e3..236ef712 100644 --- a/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/PrivateEmptyNoArgConstructorFilter.java +++ b/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/PrivateEmptyNoArgConstructorFilter.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2018 Mountainminds GmbH & Co. KG and Contributors + * Copyright (c) 2009, 2019 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 @@ -12,21 +12,22 @@ package org.jacoco.core.internal.analysis.filter; import org.objectweb.asm.Opcodes; -import org.objectweb.asm.tree.MethodInsnNode; import org.objectweb.asm.tree.MethodNode; -import org.objectweb.asm.tree.VarInsnNode; /** * Filters private empty constructors that do not have arguments. */ public final class PrivateEmptyNoArgConstructorFilter implements IFilter { - public void filter(final String className, final String superClassName, - final MethodNode methodNode, final IFilterOutput output) { + private static final String CONSTRUCTOR_NAME = "<init>"; + private static final String CONSTRUCTOR_DESC = "()V"; + + public void filter(final MethodNode methodNode, + final IFilterContext context, final IFilterOutput output) { if ((methodNode.access & Opcodes.ACC_PRIVATE) != 0 - && "<init>".equals(methodNode.name) - && "()V".equals(methodNode.desc) - && new Matcher().match(methodNode, superClassName)) { + && CONSTRUCTOR_NAME.equals(methodNode.name) + && CONSTRUCTOR_DESC.equals(methodNode.desc) && new Matcher() + .match(methodNode, context.getSuperClassName())) { output.ignore(methodNode.instructions.getFirst(), methodNode.instructions.getLast()); } @@ -35,20 +36,11 @@ public final class PrivateEmptyNoArgConstructorFilter implements IFilter { private static class Matcher extends AbstractMatcher { private boolean match(final MethodNode methodNode, final String superClassName) { - cursor = methodNode.instructions.getFirst(); - skipNonOpcodes(); - if (cursor.getOpcode() != Opcodes.ALOAD - || ((VarInsnNode) cursor).var != 0) { - return false; - } - nextIs(Opcodes.INVOKESPECIAL); - MethodInsnNode m = (MethodInsnNode) cursor; - if (m != null && superClassName.equals(m.owner) - && "<init>".equals(m.name) && ("()V").equals(m.desc)) { - nextIs(Opcodes.RETURN); - return cursor != null; - } - return false; + firstIsALoad0(methodNode); + nextIsInvoke(Opcodes.INVOKESPECIAL, superClassName, + CONSTRUCTOR_NAME, CONSTRUCTOR_DESC); + nextIs(Opcodes.RETURN); + return cursor != null; } } diff --git a/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/StringSwitchEcjFilter.java b/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/StringSwitchEcjFilter.java new file mode 100644 index 00000000..e0aba35d --- /dev/null +++ b/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/StringSwitchEcjFilter.java @@ -0,0 +1,104 @@ +/******************************************************************************* + * Copyright (c) 2009, 2019 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: + * Evgeny Mandrikov - initial API and implementation + * + *******************************************************************************/ +package org.jacoco.core.internal.analysis.filter; + +import java.util.HashSet; +import java.util.Set; + +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.tree.AbstractInsnNode; +import org.objectweb.asm.tree.JumpInsnNode; +import org.objectweb.asm.tree.LabelNode; +import org.objectweb.asm.tree.LookupSwitchInsnNode; +import org.objectweb.asm.tree.MethodNode; +import org.objectweb.asm.tree.TableSwitchInsnNode; +import org.objectweb.asm.tree.VarInsnNode; + +/** + * Filters code that is generated by ECJ for a <code>switch</code> statement + * with a <code>String</code>. + */ +public final class StringSwitchEcjFilter implements IFilter { + + public void filter(final MethodNode methodNode, + final IFilterContext context, final IFilterOutput output) { + final Matcher matcher = new Matcher(); + for (AbstractInsnNode i = methodNode.instructions + .getFirst(); i != null; i = i.getNext()) { + matcher.match(i, output); + } + } + + private static class Matcher extends AbstractMatcher { + public void match(final AbstractInsnNode start, + final IFilterOutput output) { + + if (Opcodes.ASTORE != start.getOpcode()) { + return; + } + cursor = start; + nextIsInvoke(Opcodes.INVOKEVIRTUAL, "java/lang/String", "hashCode", + "()I"); + nextIsSwitch(); + if (cursor == null) { + return; + } + vars.put("s", (VarInsnNode) start); + + final AbstractInsnNode s = cursor; + final int hashCodes; + final LabelNode defaultLabel; + if (s.getOpcode() == Opcodes.LOOKUPSWITCH) { + final LookupSwitchInsnNode lookupSwitch = (LookupSwitchInsnNode) cursor; + defaultLabel = lookupSwitch.dflt; + hashCodes = lookupSwitch.labels.size(); + } else { + final TableSwitchInsnNode tableSwitch = (TableSwitchInsnNode) cursor; + defaultLabel = tableSwitch.dflt; + hashCodes = tableSwitch.labels.size(); + } + + final Set<AbstractInsnNode> replacements = new HashSet<AbstractInsnNode>(); + replacements.add(skipNonOpcodes(defaultLabel)); + + for (int i = 0; i < hashCodes; i++) { + while (true) { + nextIsVar(Opcodes.ALOAD, "s"); + nextIs(Opcodes.LDC); + nextIsInvoke(Opcodes.INVOKEVIRTUAL, "java/lang/String", + "equals", "(Ljava/lang/Object;)Z"); + // jump to case + nextIs(Opcodes.IFNE); + if (cursor == null) { + return; + } + + replacements + .add(skipNonOpcodes(((JumpInsnNode) cursor).label)); + + if (cursor.getNext().getOpcode() == Opcodes.GOTO) { + // end of comparisons for same hashCode + // jump to default + nextIs(Opcodes.GOTO); + break; + } else if (cursor.getNext() == defaultLabel) { + break; + } + } + } + + output.ignore(s.getNext(), cursor); + output.replaceBranches(s, replacements); + } + } + +} diff --git a/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/StringSwitchJavacFilter.java b/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/StringSwitchJavacFilter.java index e1d7a1cc..3033d9bc 100644 --- a/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/StringSwitchJavacFilter.java +++ b/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/StringSwitchJavacFilter.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2018 Mountainminds GmbH & Co. KG and Contributors + * Copyright (c) 2009, 2019 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 @@ -24,8 +24,8 @@ import org.objectweb.asm.tree.TableSwitchInsnNode; */ public final class StringSwitchJavacFilter implements IFilter { - public void filter(final String className, final String superClassName, - final MethodNode methodNode, final IFilterOutput output) { + public void filter(final MethodNode methodNode, + final IFilterContext context, final IFilterOutput output) { AbstractInsnNode i = methodNode.instructions.getFirst(); while (i != null) { filter(i, output); @@ -68,12 +68,14 @@ public final class StringSwitchJavacFilter implements IFilter { // Even if expression is not a variable, its result will be // precomputed before the previous two instructions: nextIsVar(Opcodes.ALOAD, "s"); - nextIsInvokeVirtual("java/lang/String", "hashCode"); + nextIsInvoke(Opcodes.INVOKEVIRTUAL, "java/lang/String", "hashCode", + "()I"); next(); while (true) { nextIsVar(Opcodes.ALOAD, "s"); nextIs(Opcodes.LDC); - nextIsInvokeVirtual("java/lang/String", "equals"); + nextIsInvoke(Opcodes.INVOKEVIRTUAL, "java/lang/String", + "equals", "(Ljava/lang/Object;)Z"); // jump to next comparison or second switch nextIs(Opcodes.IFEQ); // ICONST, BIPUSH or SIPUSH @@ -91,7 +93,8 @@ public final class StringSwitchJavacFilter implements IFilter { } } nextIsVar(Opcodes.ILOAD, "c"); - nextIs(Opcodes.TABLESWITCH); + // Can be TABLESWITCH or LOOKUPSWITCH depending on number of cases + nextIsSwitch(); return cursor != null; } } diff --git a/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/SynchronizedFilter.java b/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/SynchronizedFilter.java index 71ce0cfe..6341328f 100644 --- a/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/SynchronizedFilter.java +++ b/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/SynchronizedFilter.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2018 Mountainminds GmbH & Co. KG and Contributors + * Copyright (c) 2009, 2019 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 @@ -21,9 +21,9 @@ import org.objectweb.asm.tree.TryCatchBlockNode; */ public final class SynchronizedFilter implements IFilter { - public void filter(final String className, final String superClassName, - final MethodNode methodNode, final IFilterOutput output) { - for (TryCatchBlockNode tryCatch : methodNode.tryCatchBlocks) { + public void filter(final MethodNode methodNode, + final IFilterContext context, final IFilterOutput output) { + for (final TryCatchBlockNode tryCatch : methodNode.tryCatchBlocks) { if (tryCatch.type != null) { continue; } @@ -67,7 +67,7 @@ public final class SynchronizedFilter implements IFilter { nextIs(Opcodes.ALOAD); nextIs(Opcodes.MONITOREXIT); nextIs(Opcodes.ATHROW); - return cursor != null; + return cursor != null; } } diff --git a/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/SyntheticFilter.java b/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/SyntheticFilter.java index afba0f00..69c4092a 100644 --- a/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/SyntheticFilter.java +++ b/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/SyntheticFilter.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2018 Mountainminds GmbH & Co. KG and Contributors + * Copyright (c) 2009, 2019 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 @@ -19,13 +19,29 @@ import org.objectweb.asm.tree.MethodNode; */ public final class SyntheticFilter implements IFilter { - public void filter(final String className, final String superClassName, - final MethodNode methodNode, final IFilterOutput output) { - if ((methodNode.access & Opcodes.ACC_SYNTHETIC) != 0 - && !methodNode.name.startsWith("lambda$")) { - output.ignore(methodNode.instructions.getFirst(), - methodNode.instructions.getLast()); + public void filter(final MethodNode methodNode, + final IFilterContext context, final IFilterOutput output) { + if ((methodNode.access & Opcodes.ACC_SYNTHETIC) == 0) { + return; } + + if (methodNode.name.startsWith("lambda$")) { + return; + } + + if (KotlinGeneratedFilter.isKotlinClass(context)) { + if (KotlinDefaultArgumentsFilter + .isDefaultArgumentsMethodName(methodNode.name)) { + return; + } + + if (KotlinCoroutineFilter.isLastArgumentContinuation(methodNode)) { + return; + } + } + + output.ignore(methodNode.instructions.getFirst(), + methodNode.instructions.getLast()); } } diff --git a/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/TryWithResourcesEcjFilter.java b/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/TryWithResourcesEcjFilter.java index c25d61ff..94dea561 100644 --- a/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/TryWithResourcesEcjFilter.java +++ b/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/TryWithResourcesEcjFilter.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2018 Mountainminds GmbH & Co. KG and Contributors + * Copyright (c) 2009, 2019 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 @@ -27,13 +27,13 @@ import org.objectweb.asm.tree.TryCatchBlockNode; */ public final class TryWithResourcesEcjFilter implements IFilter { - public void filter(final String className, final String superClassName, - final MethodNode methodNode, final IFilterOutput output) { + public void filter(final MethodNode methodNode, + final IFilterContext context, final IFilterOutput output) { if (methodNode.tryCatchBlocks.isEmpty()) { return; } final Matcher matcher = new Matcher(output); - for (TryCatchBlockNode t : methodNode.tryCatchBlocks) { + for (final TryCatchBlockNode t : methodNode.tryCatchBlocks) { if (t.type == null) { matcher.start(t.handler); if (!matcher.matchEcj()) { @@ -200,7 +200,8 @@ public final class TryWithResourcesEcjFilter implements IFilter { // "primaryExc.addSuppressed(suppressedExc)" nextIsVar(Opcodes.ALOAD, "primaryExc"); nextIsVar(Opcodes.ALOAD, suppressedExc); - nextIsInvokeVirtual("java/lang/Throwable", "addSuppressed"); + nextIsInvoke(Opcodes.INVOKEVIRTUAL, "java/lang/Throwable", + "addSuppressed", "(Ljava/lang/Throwable;)V"); nextIsLabel(endLabel); return cursor != null; } diff --git a/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/TryWithResourcesJavac11Filter.java b/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/TryWithResourcesJavac11Filter.java new file mode 100644 index 00000000..7a20f74f --- /dev/null +++ b/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/TryWithResourcesJavac11Filter.java @@ -0,0 +1,145 @@ +/******************************************************************************* + * Copyright (c) 2009, 2019 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: + * 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.MethodInsnNode; +import org.objectweb.asm.tree.MethodNode; +import org.objectweb.asm.tree.TryCatchBlockNode; + +/** + * Filters code which is generated for try-with-resources statement by javac + * starting from version 11. + */ +public final class TryWithResourcesJavac11Filter implements IFilter { + + public void filter(final MethodNode methodNode, + final IFilterContext context, final IFilterOutput output) { + if (methodNode.tryCatchBlocks.isEmpty()) { + return; + } + final Matcher matcher = new Matcher(); + for (TryCatchBlockNode t : methodNode.tryCatchBlocks) { + if ("java/lang/Throwable".equals(t.type)) { + matcher.match(t.handler, output, true); + matcher.match(t.handler, output, false); + } + } + } + + /** + * <pre> + * r = ...; + * try { + * ... + * } body-only-finally { + * if (r != null) + * r.close(); + * } catch (Throwable primaryExc) { + * if (r != null) + * try { + * r.close(); + * } catch (Throwable t) { + * primaryExc.addSuppressed(t); + * } + * throw primaryExc; + * } + * </pre> + * + * <code>null</code> check for resource is omitted when it is initialized + * using <code>new</code> + */ + private class Matcher extends AbstractMatcher { + private boolean withNullCheck; + + private String expectedOwner; + + void match(final AbstractInsnNode start, final IFilterOutput output, + final boolean withNullCheck) { + this.withNullCheck = withNullCheck; + vars.clear(); + expectedOwner = null; + + cursor = start.getPrevious(); + nextIsVar(Opcodes.ASTORE, "primaryExc"); + nextIsJavacClose(); + nextIs(Opcodes.GOTO); + nextIsVar(Opcodes.ASTORE, "t"); + nextIsVar(Opcodes.ALOAD, "primaryExc"); + nextIsVar(Opcodes.ALOAD, "t"); + nextIsInvoke(Opcodes.INVOKEVIRTUAL, "java/lang/Throwable", + "addSuppressed", "(Ljava/lang/Throwable;)V"); // primaryExc.addSuppressed(t) + nextIsVar(Opcodes.ALOAD, "primaryExc"); + nextIs(Opcodes.ATHROW); + if (cursor == null) { + return; + } + final AbstractInsnNode end = cursor; + + AbstractInsnNode s = start.getPrevious(); + cursor = start.getPrevious(); + while (!nextIsJavacClose()) { + s = s.getPrevious(); + cursor = s; + if (cursor == null) { + return; + } + } + s = s.getNext(); + + final AbstractInsnNode m = cursor; + next(); + if (cursor.getOpcode() != Opcodes.GOTO) { + cursor = m; + } + + output.ignore(s, cursor); + output.ignore(start, end); + } + + private boolean nextIsJavacClose() { + if (withNullCheck) { + nextIsVar(Opcodes.ALOAD, "r"); + nextIs(Opcodes.IFNULL); + } + nextIsClose(); + return cursor != null; + } + + private void nextIsClose() { + nextIsVar(Opcodes.ALOAD, "r"); + next(); + if (cursor == null) { + return; + } + if (cursor.getOpcode() != Opcodes.INVOKEINTERFACE + && cursor.getOpcode() != Opcodes.INVOKEVIRTUAL) { + cursor = null; + return; + } + final MethodInsnNode m = (MethodInsnNode) cursor; + if (!"close".equals(m.name) || !"()V".equals(m.desc)) { + cursor = null; + return; + } + final String actual = m.owner; + if (expectedOwner == null) { + expectedOwner = actual; + } else if (!expectedOwner.equals(actual)) { + cursor = null; + } + } + + } + +} diff --git a/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/TryWithResourcesJavacFilter.java b/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/TryWithResourcesJavacFilter.java index f3b33bc9..23ecb0e6 100644 --- a/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/TryWithResourcesJavacFilter.java +++ b/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/TryWithResourcesJavacFilter.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2018 Mountainminds GmbH & Co. KG and Contributors + * Copyright (c) 2009, 2019 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 @@ -18,19 +18,21 @@ import org.objectweb.asm.tree.MethodNode; import org.objectweb.asm.tree.TryCatchBlockNode; /** - * Filters code that javac generates for try-with-resources statement. + * Filters code which is generated for try-with-resources statement by javac + * versions from 7 to 10. */ public final class TryWithResourcesJavacFilter implements IFilter { - public void filter(final String className, final String superClassName, - final MethodNode methodNode, final IFilterOutput output) { + public void filter(final MethodNode methodNode, + final IFilterContext context, final IFilterOutput output) { if (methodNode.tryCatchBlocks.isEmpty()) { return; } final Matcher matcher = new Matcher(output); - for (TryCatchBlockNode t : methodNode.tryCatchBlocks) { + for (final TryCatchBlockNode t : methodNode.tryCatchBlocks) { if ("java/lang/Throwable".equals(t.type)) { - for (Matcher.JavacPattern p : Matcher.JavacPattern.values()) { + for (final Matcher.JavacPattern p : Matcher.JavacPattern + .values()) { matcher.start(t.handler); if (matcher.matchJavac(p)) { break; @@ -195,7 +197,7 @@ public final class TryWithResourcesJavacFilter implements IFilter { final MethodInsnNode m = (MethodInsnNode) cursor; if ("$closeResource".equals(m.name) && "(Ljava/lang/Throwable;Ljava/lang/AutoCloseable;)V" - .equals(m.desc)) { + .equals(m.desc)) { return true; } cursor = null; @@ -214,7 +216,8 @@ public final class TryWithResourcesJavacFilter implements IFilter { // "primaryExc.addSuppressed(t)" nextIsVar(Opcodes.ALOAD, "primaryExc"); nextIsVar(Opcodes.ALOAD, ctx + "t"); - nextIsInvokeVirtual("java/lang/Throwable", "addSuppressed"); + nextIsInvoke(Opcodes.INVOKEVIRTUAL, "java/lang/Throwable", + "addSuppressed", "(Ljava/lang/Throwable;)V"); nextIs(Opcodes.GOTO); // "r.close()" nextIsClose(); diff --git a/org.jacoco.core/src/org/jacoco/core/internal/data/CRC64.java b/org.jacoco.core/src/org/jacoco/core/internal/data/CRC64.java index 974269b9..620a46f0 100644 --- a/org.jacoco.core/src/org/jacoco/core/internal/data/CRC64.java +++ b/org.jacoco.core/src/org/jacoco/core/internal/data/CRC64.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2018 Mountainminds GmbH & Co. KG and Contributors + * Copyright (c) 2009, 2019 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 diff --git a/org.jacoco.core/src/org/jacoco/core/internal/data/CompactDataInput.java b/org.jacoco.core/src/org/jacoco/core/internal/data/CompactDataInput.java index 082c1790..945b2b64 100644 --- a/org.jacoco.core/src/org/jacoco/core/internal/data/CompactDataInput.java +++ b/org.jacoco.core/src/org/jacoco/core/internal/data/CompactDataInput.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2018 Mountainminds GmbH & Co. KG and Contributors + * Copyright (c) 2009, 2019 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 diff --git a/org.jacoco.core/src/org/jacoco/core/internal/data/CompactDataOutput.java b/org.jacoco.core/src/org/jacoco/core/internal/data/CompactDataOutput.java index 7852102a..4d230f8e 100644 --- a/org.jacoco.core/src/org/jacoco/core/internal/data/CompactDataOutput.java +++ b/org.jacoco.core/src/org/jacoco/core/internal/data/CompactDataOutput.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2018 Mountainminds GmbH & Co. KG and Contributors + * Copyright (c) 2009, 2019 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 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 11ce434d..876a1351 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 @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2018 Mountainminds GmbH & Co. KG and Contributors + * Copyright (c) 2009, 2019 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 diff --git a/org.jacoco.core/src/org/jacoco/core/internal/flow/ClassProbesVisitor.java b/org.jacoco.core/src/org/jacoco/core/internal/flow/ClassProbesVisitor.java index fc3b979b..54817e80 100644 --- a/org.jacoco.core/src/org/jacoco/core/internal/flow/ClassProbesVisitor.java +++ b/org.jacoco.core/src/org/jacoco/core/internal/flow/ClassProbesVisitor.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2018 Mountainminds GmbH & Co. KG and Contributors + * Copyright (c) 2009, 2019 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 diff --git a/org.jacoco.core/src/org/jacoco/core/internal/flow/FrameSnapshot.java b/org.jacoco.core/src/org/jacoco/core/internal/flow/FrameSnapshot.java index 94e24cc9..a0c7449f 100644 --- a/org.jacoco.core/src/org/jacoco/core/internal/flow/FrameSnapshot.java +++ b/org.jacoco.core/src/org/jacoco/core/internal/flow/FrameSnapshot.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2018 Mountainminds GmbH & Co. KG and Contributors + * Copyright (c) 2009, 2019 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 diff --git a/org.jacoco.core/src/org/jacoco/core/internal/flow/IFrame.java b/org.jacoco.core/src/org/jacoco/core/internal/flow/IFrame.java index 09e99064..6079f443 100644 --- a/org.jacoco.core/src/org/jacoco/core/internal/flow/IFrame.java +++ b/org.jacoco.core/src/org/jacoco/core/internal/flow/IFrame.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2018 Mountainminds GmbH & Co. KG and Contributors + * Copyright (c) 2009, 2019 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 @@ -24,6 +24,6 @@ public interface IFrame { * @param mv * method visitor to emit frame event to */ - void accept(final MethodVisitor mv); + void accept(MethodVisitor mv); } diff --git a/org.jacoco.core/src/org/jacoco/core/internal/flow/IProbeIdGenerator.java b/org.jacoco.core/src/org/jacoco/core/internal/flow/IProbeIdGenerator.java index 4baa4f07..6f3f1977 100644 --- a/org.jacoco.core/src/org/jacoco/core/internal/flow/IProbeIdGenerator.java +++ b/org.jacoco.core/src/org/jacoco/core/internal/flow/IProbeIdGenerator.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2018 Mountainminds GmbH & Co. KG and Contributors + * Copyright (c) 2009, 2019 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 diff --git a/org.jacoco.core/src/org/jacoco/core/internal/flow/Instruction.java b/org.jacoco.core/src/org/jacoco/core/internal/flow/Instruction.java deleted file mode 100644 index d1e7cbeb..00000000 --- a/org.jacoco.core/src/org/jacoco/core/internal/flow/Instruction.java +++ /dev/null @@ -1,149 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2009, 2018 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.tree.AbstractInsnNode; - -import java.util.BitSet; - -/** - * Representation of a byte code instruction for analysis. Internally used for - * analysis. - */ -public class Instruction { - - private final AbstractInsnNode node; - - private final int line; - - private int branches; - - private final BitSet coveredBranches; - - private Instruction predecessor; - - private int predecessorBranch; - - /** - * New instruction at the given line. - * - * @param node - * corresponding node - * @param line - * source line this instruction belongs to - */ - public Instruction(final AbstractInsnNode node, final int line) { - this.node = node; - this.line = line; - this.branches = 0; - this.coveredBranches = new BitSet(); - } - - /** - * @return corresponding node - */ - public AbstractInsnNode getNode() { - return node; - } - - /** - * Adds an branch to this instruction. - */ - public void addBranch() { - branches++; - } - - /** - * Sets the given instruction as a predecessor of this instruction and adds - * branch to the predecessor. Probes are inserted in a way that every - * instruction has at most one direct predecessor. - * - * @see #addBranch() - * @param predecessor - * predecessor instruction - * @param branch - * branch number in predecessor that should be marked as covered - * when this instruction marked as covered - */ - public void setPredecessor(final Instruction predecessor, - final int branch) { - this.predecessor = predecessor; - predecessor.addBranch(); - this.predecessorBranch = branch; - } - - /** - * Marks one branch of this instruction as covered. Also recursively marks - * all predecessor instructions as covered if this is the first covered - * branch. - * - * @param branch - * branch number to mark as covered - */ - public void setCovered(final int branch) { - Instruction i = this; - int b = branch; - while (i != null) { - if (!i.coveredBranches.isEmpty()) { - i.coveredBranches.set(b); - break; - } - i.coveredBranches.set(b); - b = i.predecessorBranch; - i = i.predecessor; - } - } - - /** - * Returns the source line this instruction belongs to. - * - * @return corresponding source line - */ - public int getLine() { - return line; - } - - /** - * Returns the total number of branches starting from this instruction. - * - * @return total number of branches - */ - public int getBranches() { - return branches; - } - - /** - * Returns the number of covered branches starting from this instruction. - * - * @return number of covered branches - */ - public int getCoveredBranches() { - return coveredBranches.cardinality(); - } - - /** - * Merges information about covered branches of given instruction into this - * instruction. - * - * @param instruction - * instruction from which to merge - */ - public void merge(Instruction instruction) { - this.coveredBranches.or(instruction.coveredBranches); - } - - @Override - public String toString() { - return coveredBranches.toString(); - } - -} diff --git a/org.jacoco.core/src/org/jacoco/core/internal/flow/LabelFlowAnalyzer.java b/org.jacoco.core/src/org/jacoco/core/internal/flow/LabelFlowAnalyzer.java index f5b354f5..0f0e4d33 100644 --- a/org.jacoco.core/src/org/jacoco/core/internal/flow/LabelFlowAnalyzer.java +++ b/org.jacoco.core/src/org/jacoco/core/internal/flow/LabelFlowAnalyzer.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2018 Mountainminds GmbH & Co. KG and Contributors + * Copyright (c) 2009, 2019 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 diff --git a/org.jacoco.core/src/org/jacoco/core/internal/flow/LabelInfo.java b/org.jacoco.core/src/org/jacoco/core/internal/flow/LabelInfo.java index 1b65ae2a..85dc1d81 100644 --- a/org.jacoco.core/src/org/jacoco/core/internal/flow/LabelInfo.java +++ b/org.jacoco.core/src/org/jacoco/core/internal/flow/LabelInfo.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2018 Mountainminds GmbH & Co. KG and Contributors + * Copyright (c) 2009, 2019 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 @@ -11,6 +11,7 @@ *******************************************************************************/ package org.jacoco.core.internal.flow; +import org.jacoco.core.internal.analysis.Instruction; import org.objectweb.asm.Label; /** diff --git a/org.jacoco.core/src/org/jacoco/core/internal/flow/MethodProbesAdapter.java b/org.jacoco.core/src/org/jacoco/core/internal/flow/MethodProbesAdapter.java index 867ff93a..40e20a3e 100644 --- a/org.jacoco.core/src/org/jacoco/core/internal/flow/MethodProbesAdapter.java +++ b/org.jacoco.core/src/org/jacoco/core/internal/flow/MethodProbesAdapter.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2018 Mountainminds GmbH & Co. KG and Contributors + * Copyright (c) 2009, 2019 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 diff --git a/org.jacoco.core/src/org/jacoco/core/internal/flow/MethodProbesVisitor.java b/org.jacoco.core/src/org/jacoco/core/internal/flow/MethodProbesVisitor.java index 7514193b..75e63555 100644 --- a/org.jacoco.core/src/org/jacoco/core/internal/flow/MethodProbesVisitor.java +++ b/org.jacoco.core/src/org/jacoco/core/internal/flow/MethodProbesVisitor.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2018 Mountainminds GmbH & Co. KG and Contributors + * Copyright (c) 2009, 2019 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 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 index f6aa2e9d..92b08947 100644 --- a/org.jacoco.core/src/org/jacoco/core/internal/flow/MethodSanitizer.java +++ b/org.jacoco.core/src/org/jacoco/core/internal/flow/MethodSanitizer.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2018 Mountainminds GmbH & Co. KG and Contributors + * Copyright (c) 2009, 2019 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 diff --git a/org.jacoco.core/src/org/jacoco/core/internal/instr/ClassFieldProbeArrayStrategy.java b/org.jacoco.core/src/org/jacoco/core/internal/instr/ClassFieldProbeArrayStrategy.java index e8051358..950be683 100644 --- a/org.jacoco.core/src/org/jacoco/core/internal/instr/ClassFieldProbeArrayStrategy.java +++ b/org.jacoco.core/src/org/jacoco/core/internal/instr/ClassFieldProbeArrayStrategy.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2018 Mountainminds GmbH & Co. KG and Contributors + * Copyright (c) 2009, 2019 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 diff --git a/org.jacoco.core/src/org/jacoco/core/internal/instr/ClassInstrumenter.java b/org.jacoco.core/src/org/jacoco/core/internal/instr/ClassInstrumenter.java index 1117975f..7d5e9759 100644 --- a/org.jacoco.core/src/org/jacoco/core/internal/instr/ClassInstrumenter.java +++ b/org.jacoco.core/src/org/jacoco/core/internal/instr/ClassInstrumenter.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2018 Mountainminds GmbH & Co. KG and Contributors + * Copyright (c) 2009, 2019 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 diff --git a/org.jacoco.core/src/org/jacoco/core/internal/instr/CondyProbeArrayStrategy.java b/org.jacoco.core/src/org/jacoco/core/internal/instr/CondyProbeArrayStrategy.java new file mode 100644 index 00000000..ca2fb60d --- /dev/null +++ b/org.jacoco.core/src/org/jacoco/core/internal/instr/CondyProbeArrayStrategy.java @@ -0,0 +1,73 @@ +/******************************************************************************* + * Copyright (c) 2009, 2019 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: + * Evgeny Mandrikov - initial API and implementation + * + *******************************************************************************/ +package org.jacoco.core.internal.instr; + +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.ConstantDynamic; +import org.jacoco.core.runtime.IExecutionDataAccessorGenerator; +import org.objectweb.asm.Handle; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; + +/** + * This strategy for Java 11+ class files uses {@link ConstantDynamic} to hold + * the probe array and adds bootstrap method requesting the probe array from the + * runtime. + */ +public class CondyProbeArrayStrategy implements IProbeArrayStrategy { + + /** + * Descriptor of the bootstrap method. + */ + public static final String B_DESC = "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/Class;)[Z"; + + private final String className; + + private final boolean isInterface; + + private final long classId; + + private final IExecutionDataAccessorGenerator accessorGenerator; + + CondyProbeArrayStrategy(final String className, final boolean isInterface, + final long classId, + final IExecutionDataAccessorGenerator accessorGenerator) { + this.className = className; + this.isInterface = isInterface; + this.classId = classId; + this.accessorGenerator = accessorGenerator; + } + + public int storeInstance(final MethodVisitor mv, final boolean clinit, + final int variable) { + final Handle bootstrapMethod = new Handle(Opcodes.H_INVOKESTATIC, + className, InstrSupport.INITMETHOD_NAME, B_DESC, isInterface); + // As a workaround for https://bugs.openjdk.java.net/browse/JDK-8216970 + // constant should have type Object + mv.visitLdcInsn(new ConstantDynamic(InstrSupport.DATAFIELD_NAME, + "Ljava/lang/Object;", bootstrapMethod)); + mv.visitTypeInsn(Opcodes.CHECKCAST, "[Z"); + mv.visitVarInsn(Opcodes.ASTORE, variable); + return 1; + } + + public void addMembers(final ClassVisitor cv, final int probeCount) { + final MethodVisitor mv = cv.visitMethod(InstrSupport.INITMETHOD_ACC, + InstrSupport.INITMETHOD_NAME, B_DESC, null, null); + final int maxStack = accessorGenerator.generateDataAccessor(classId, + className, probeCount, mv); + mv.visitInsn(Opcodes.ARETURN); + mv.visitMaxs(maxStack, 3); + mv.visitEnd(); + } + +} diff --git a/org.jacoco.core/src/org/jacoco/core/internal/instr/DuplicateFrameEliminator.java b/org.jacoco.core/src/org/jacoco/core/internal/instr/DuplicateFrameEliminator.java index 34707752..bc3e54bf 100644 --- a/org.jacoco.core/src/org/jacoco/core/internal/instr/DuplicateFrameEliminator.java +++ b/org.jacoco.core/src/org/jacoco/core/internal/instr/DuplicateFrameEliminator.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2018 Mountainminds GmbH & Co. KG and Contributors + * Copyright (c) 2009, 2019 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 diff --git a/org.jacoco.core/src/org/jacoco/core/internal/instr/IProbeArrayStrategy.java b/org.jacoco.core/src/org/jacoco/core/internal/instr/IProbeArrayStrategy.java index e9ae4b6d..5fe0cdca 100644 --- a/org.jacoco.core/src/org/jacoco/core/internal/instr/IProbeArrayStrategy.java +++ b/org.jacoco.core/src/org/jacoco/core/internal/instr/IProbeArrayStrategy.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2018 Mountainminds GmbH & Co. KG and Contributors + * Copyright (c) 2009, 2019 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 @@ -39,7 +39,7 @@ public interface IProbeArrayStrategy { * called after all original members of the class has been processed. * * @param cv - * visitor to create fields and classes + * visitor to create fields and methods * @param probeCount * total number of probes required for this class */ diff --git a/org.jacoco.core/src/org/jacoco/core/internal/instr/IProbeInserter.java b/org.jacoco.core/src/org/jacoco/core/internal/instr/IProbeInserter.java index e6c8dc59..2f8dab61 100644 --- a/org.jacoco.core/src/org/jacoco/core/internal/instr/IProbeInserter.java +++ b/org.jacoco.core/src/org/jacoco/core/internal/instr/IProbeInserter.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2018 Mountainminds GmbH & Co. KG and Contributors + * Copyright (c) 2009, 2019 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 @@ -23,6 +23,6 @@ interface IProbeInserter { * @param id * id of the probe to insert */ - public void insertProbe(final int id); + void insertProbe(int id); } diff --git a/org.jacoco.core/src/org/jacoco/core/internal/instr/InstrSupport.java b/org.jacoco.core/src/org/jacoco/core/internal/instr/InstrSupport.java index b6632c62..85e83a3a 100644 --- a/org.jacoco.core/src/org/jacoco/core/internal/instr/InstrSupport.java +++ b/org.jacoco.core/src/org/jacoco/core/internal/instr/InstrSupport.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2018 Mountainminds GmbH & Co. KG and Contributors + * Copyright (c) 2009, 2019 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 @@ -13,6 +13,7 @@ package org.jacoco.core.internal.instr; import static java.lang.String.format; +import org.objectweb.asm.ClassReader; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; @@ -25,7 +26,7 @@ public final class InstrSupport { } /** ASM API version */ - public static final int ASM_API_VERSION = Opcodes.ASM6; + public static final int ASM_API_VERSION = Opcodes.ASM7; // === Data Field === @@ -158,6 +159,66 @@ public final class InstrSupport { static final int CLINIT_ACC = Opcodes.ACC_SYNTHETIC | Opcodes.ACC_STATIC; /** + * Gets major version number from given bytes of class (unsigned two bytes + * at offset 6). + * + * @param b + * bytes of class + * @return major version of bytecode + * @see <a href= + * "https://docs.oracle.com/javase/specs/jvms/se11/html/jvms-4.html#jvms-4.1">Java + * Virtual Machine Specification §4 The class File Format</a> + * @see #setMajorVersion(int, byte[]) + * @see #getMajorVersion(ClassReader) + */ + public static int getMajorVersion(final byte[] b) { + return ((b[6] & 0xFF) << 8) | (b[7] & 0xFF); + } + + /** + * Sets major version number in given bytes of class (unsigned two bytes at + * offset 6). + * + * @param majorVersion + * major version of bytecode to set + * @param b + * bytes of class + * @see #getMajorVersion(byte[]) + */ + public static void setMajorVersion(final int majorVersion, final byte[] b) { + b[6] = (byte) (majorVersion >>> 8); + b[7] = (byte) majorVersion; + } + + /** + * Gets major version number from given {@link ClassReader}. + * + * @param reader + * reader to get information about the class + * @return major version of bytecode + * @see ClassReader#ClassReader(byte[], int, int) + * @see #getMajorVersion(byte[]) + */ + public static int getMajorVersion(final ClassReader reader) { + // relative to the beginning of constant pool because ASM provides API + // to construct ClassReader which reads from the middle of array + final int firstConstantPoolEntryOffset = reader.getItem(1) - 1; + return reader.readUnsignedShort(firstConstantPoolEntryOffset - 4); + } + + /** + * Determines whether the given class file version requires stackmap frames. + * + * @param version + * class file version + * @return <code>true</code> if frames are required + */ + public static boolean needsFrames(final int version) { + // consider major version only (due to 1.1 anomaly) + return (version & 0xFFFF) >= Opcodes.V1_6; + } + + /** * Ensures that the given member does not correspond to a internal member * created by the instrumentation process. This would mean that the class is * already instrumented. @@ -174,7 +235,8 @@ public final class InstrSupport { final String owner) throws IllegalStateException { if (member.equals(DATAFIELD_NAME) || member.equals(INITMETHOD_NAME)) { throw new IllegalStateException(format( - "Class %s is already instrumented.", owner)); + "Cannot process instrumented class %s. Please supply original non-instrumented classes.", + owner)); } } @@ -200,4 +262,23 @@ public final class InstrSupport { } } + /** + * Creates a {@link ClassReader} instance for given bytes of class even if + * its version not yet supported by ASM. + * + * @param b + * bytes of class + * @return {@link ClassReader} + */ + public static ClassReader classReaderFor(final byte[] b) { + final int originalVersion = getMajorVersion(b); + if (originalVersion == Opcodes.V12 + 1) { + // temporarily downgrade version to bypass check in ASM + setMajorVersion(Opcodes.V12, b); + } + final ClassReader classReader = new ClassReader(b); + setMajorVersion(originalVersion, b); + return classReader; + } + } diff --git a/org.jacoco.core/src/org/jacoco/core/internal/instr/InterfaceFieldProbeArrayStrategy.java b/org.jacoco.core/src/org/jacoco/core/internal/instr/InterfaceFieldProbeArrayStrategy.java index 9325dddc..bf855fea 100644 --- a/org.jacoco.core/src/org/jacoco/core/internal/instr/InterfaceFieldProbeArrayStrategy.java +++ b/org.jacoco.core/src/org/jacoco/core/internal/instr/InterfaceFieldProbeArrayStrategy.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2018 Mountainminds GmbH & Co. KG and Contributors + * Copyright (c) 2009, 2019 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 diff --git a/org.jacoco.core/src/org/jacoco/core/internal/instr/LocalProbeArrayStrategy.java b/org.jacoco.core/src/org/jacoco/core/internal/instr/LocalProbeArrayStrategy.java index f9a506ca..67068ed7 100644 --- a/org.jacoco.core/src/org/jacoco/core/internal/instr/LocalProbeArrayStrategy.java +++ b/org.jacoco.core/src/org/jacoco/core/internal/instr/LocalProbeArrayStrategy.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2018 Mountainminds GmbH & Co. KG and Contributors + * Copyright (c) 2009, 2019 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 @@ -50,4 +50,4 @@ class LocalProbeArrayStrategy implements IProbeArrayStrategy { // nothing to do } -}
\ No newline at end of file +} diff --git a/org.jacoco.core/src/org/jacoco/core/internal/instr/MethodInstrumenter.java b/org.jacoco.core/src/org/jacoco/core/internal/instr/MethodInstrumenter.java index 472ffb7a..396368b5 100644 --- a/org.jacoco.core/src/org/jacoco/core/internal/instr/MethodInstrumenter.java +++ b/org.jacoco.core/src/org/jacoco/core/internal/instr/MethodInstrumenter.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2018 Mountainminds GmbH & Co. KG and Contributors + * Copyright (c) 2009, 2019 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 diff --git a/org.jacoco.core/src/org/jacoco/core/internal/instr/NoneProbeArrayStrategy.java b/org.jacoco.core/src/org/jacoco/core/internal/instr/NoneProbeArrayStrategy.java index 790a34a2..b3a4186f 100644 --- a/org.jacoco.core/src/org/jacoco/core/internal/instr/NoneProbeArrayStrategy.java +++ b/org.jacoco.core/src/org/jacoco/core/internal/instr/NoneProbeArrayStrategy.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2018 Mountainminds GmbH & Co. KG and Contributors + * Copyright (c) 2009, 2019 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 @@ -29,4 +29,4 @@ class NoneProbeArrayStrategy implements IProbeArrayStrategy { // nothing to do } -}
\ No newline at end of file +} diff --git a/org.jacoco.core/src/org/jacoco/core/internal/instr/ProbeArrayStrategyFactory.java b/org.jacoco.core/src/org/jacoco/core/internal/instr/ProbeArrayStrategyFactory.java index 349840b0..d5756b7f 100644 --- a/org.jacoco.core/src/org/jacoco/core/internal/instr/ProbeArrayStrategyFactory.java +++ b/org.jacoco.core/src/org/jacoco/core/internal/instr/ProbeArrayStrategyFactory.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2018 Mountainminds GmbH & Co. KG and Contributors + * Copyright (c) 2009, 2019 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 @@ -11,7 +11,6 @@ *******************************************************************************/ package org.jacoco.core.internal.instr; -import org.jacoco.core.internal.data.CRC64; import org.jacoco.core.internal.flow.ClassProbesAdapter; import org.jacoco.core.runtime.IExecutionDataAccessorGenerator; import org.objectweb.asm.ClassReader; @@ -30,26 +29,31 @@ public final class ProbeArrayStrategyFactory { * Creates a suitable strategy instance for the class described by the given * reader. Created instance must be used only to process a class or * interface for which it has been created and must be used only once. - * + * + * @param classId + * class identifier * @param reader * reader to get information about the class * @param accessorGenerator * accessor to the coverage runtime * @return strategy instance */ - public static IProbeArrayStrategy createFor(final ClassReader reader, + public static IProbeArrayStrategy createFor(final long classId, + final ClassReader reader, final IExecutionDataAccessorGenerator accessorGenerator) { final String className = reader.getClassName(); - final int version = getVersion(reader); - final long classId = CRC64.classId(reader.b); - final boolean withFrames = version >= Opcodes.V1_6; + final int version = InstrSupport.getMajorVersion(reader); if (isInterfaceOrModule(reader)) { final ProbeCounter counter = getProbeCounter(reader); if (counter.getCount() == 0) { return new NoneProbeArrayStrategy(); } + if (version >= Opcodes.V11 && counter.hasMethods()) { + return new CondyProbeArrayStrategy(className, true, classId, + accessorGenerator); + } if (version >= Opcodes.V1_8 && counter.hasMethods()) { return new InterfaceFieldProbeArrayStrategy(className, classId, counter.getCount(), accessorGenerator); @@ -58,8 +62,12 @@ public final class ProbeArrayStrategyFactory { counter.getCount(), accessorGenerator); } } else { + if (version >= Opcodes.V11) { + return new CondyProbeArrayStrategy(className, false, classId, + accessorGenerator); + } return new ClassFieldProbeArrayStrategy(className, classId, - withFrames, accessorGenerator); + InstrSupport.needsFrames(version), accessorGenerator); } } @@ -68,10 +76,6 @@ public final class ProbeArrayStrategyFactory { & (Opcodes.ACC_INTERFACE | Opcodes.ACC_MODULE)) != 0; } - private static int getVersion(final ClassReader reader) { - return reader.readShort(6); - } - private static ProbeCounter getProbeCounter(final ClassReader reader) { final ProbeCounter counter = new ProbeCounter(); reader.accept(new ClassProbesAdapter(counter, false), 0); diff --git a/org.jacoco.core/src/org/jacoco/core/internal/instr/ProbeCounter.java b/org.jacoco.core/src/org/jacoco/core/internal/instr/ProbeCounter.java index 2a323151..de223265 100644 --- a/org.jacoco.core/src/org/jacoco/core/internal/instr/ProbeCounter.java +++ b/org.jacoco.core/src/org/jacoco/core/internal/instr/ProbeCounter.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2018 Mountainminds GmbH & Co. KG and Contributors + * Copyright (c) 2009, 2019 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 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 61657141..63fbf765 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 @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2018 Mountainminds GmbH & Co. KG and Contributors + * Copyright (c) 2009, 2019 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 diff --git a/org.jacoco.core/src/org/jacoco/core/internal/instr/SignatureRemover.java b/org.jacoco.core/src/org/jacoco/core/internal/instr/SignatureRemover.java index 29995ee4..06994c0d 100644 --- a/org.jacoco.core/src/org/jacoco/core/internal/instr/SignatureRemover.java +++ b/org.jacoco.core/src/org/jacoco/core/internal/instr/SignatureRemover.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2018 Mountainminds GmbH & Co. KG and Contributors + * Copyright (c) 2009, 2019 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 diff --git a/org.jacoco.core/src/org/jacoco/core/package-info.java b/org.jacoco.core/src/org/jacoco/core/package-info.java index 0af292d4..84ebdf87 100644 --- a/org.jacoco.core/src/org/jacoco/core/package-info.java +++ b/org.jacoco.core/src/org/jacoco/core/package-info.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2018 Mountainminds GmbH & Co. KG and Contributors + * Copyright (c) 2009, 2019 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 @@ -13,4 +13,4 @@ /** * Meta information about JaCoCo. */ -package org.jacoco.core;
\ No newline at end of file +package org.jacoco.core; diff --git a/org.jacoco.core/src/org/jacoco/core/runtime/AbstractRuntime.java b/org.jacoco.core/src/org/jacoco/core/runtime/AbstractRuntime.java index 9c41d6cc..388e3e4f 100644 --- a/org.jacoco.core/src/org/jacoco/core/runtime/AbstractRuntime.java +++ b/org.jacoco.core/src/org/jacoco/core/runtime/AbstractRuntime.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2018 Mountainminds GmbH & Co. KG and Contributors + * Copyright (c) 2009, 2019 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 diff --git a/org.jacoco.core/src/org/jacoco/core/runtime/AgentOptions.java b/org.jacoco.core/src/org/jacoco/core/runtime/AgentOptions.java index 46c108e3..60f03c0a 100644 --- a/org.jacoco.core/src/org/jacoco/core/runtime/AgentOptions.java +++ b/org.jacoco.core/src/org/jacoco/core/runtime/AgentOptions.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2018 Mountainminds GmbH & Co. KG and Contributors + * Copyright (c) 2009, 2019 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 diff --git a/org.jacoco.core/src/org/jacoco/core/runtime/CommandLineSupport.java b/org.jacoco.core/src/org/jacoco/core/runtime/CommandLineSupport.java index 1493afb7..1f7fafc2 100644 --- a/org.jacoco.core/src/org/jacoco/core/runtime/CommandLineSupport.java +++ b/org.jacoco.core/src/org/jacoco/core/runtime/CommandLineSupport.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2018 Mountainminds GmbH & Co. KG and Contributors + * Copyright (c) 2009, 2019 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 @@ -54,13 +54,13 @@ final class CommandLineSupport { */ static String quote(final List<String> args) { final StringBuilder result = new StringBuilder(); - boolean seperate = false; + boolean separate = false; for (final String arg : args) { - if (seperate) { + if (separate) { result.append(BLANK); } result.append(quote(arg)); - seperate = true; + separate = true; } return result.toString(); } @@ -70,7 +70,7 @@ final class CommandLineSupport { * present. * * @param commandline - * combinded command line + * combined command line * @return list of arguments */ static List<String> split(final String commandline) { @@ -79,11 +79,11 @@ final class CommandLineSupport { } final List<String> args = new ArrayList<String>(); final StringBuilder current = new StringBuilder(); - int mode = M_STRIPWHITESPACE; + int mode = M_STRIP_WHITESPACE; int endChar = BLANK; for (final char c : commandline.toCharArray()) { switch (mode) { - case M_STRIPWHITESPACE: + case M_STRIP_WHITESPACE: if (!Character.isWhitespace(c)) { if (c == QUOTE) { endChar = QUOTE; @@ -91,13 +91,13 @@ final class CommandLineSupport { current.append(c); endChar = BLANK; } - mode = M_PARSEARGUMENT; + mode = M_PARSE_ARGUMENT; } break; - case M_PARSEARGUMENT: + case M_PARSE_ARGUMENT: if (c == endChar) { addArgument(args, current); - mode = M_STRIPWHITESPACE; + mode = M_STRIP_WHITESPACE; } else if (c == SLASH) { current.append(SLASH); mode = M_ESCAPED; @@ -110,11 +110,11 @@ final class CommandLineSupport { current.setCharAt(current.length() - 1, c); } else if (c == endChar) { addArgument(args, current); - mode = M_STRIPWHITESPACE; + mode = M_STRIP_WHITESPACE; } else { current.append(c); } - mode = M_PARSEARGUMENT; + mode = M_PARSE_ARGUMENT; break; } } @@ -130,8 +130,8 @@ final class CommandLineSupport { } } - private static final int M_STRIPWHITESPACE = 0; - private static final int M_PARSEARGUMENT = 1; + private static final int M_STRIP_WHITESPACE = 0; + private static final int M_PARSE_ARGUMENT = 1; private static final int M_ESCAPED = 2; private CommandLineSupport() { diff --git a/org.jacoco.core/src/org/jacoco/core/runtime/IExecutionDataAccessorGenerator.java b/org.jacoco.core/src/org/jacoco/core/runtime/IExecutionDataAccessorGenerator.java index 00783121..83df5744 100644 --- a/org.jacoco.core/src/org/jacoco/core/runtime/IExecutionDataAccessorGenerator.java +++ b/org.jacoco.core/src/org/jacoco/core/runtime/IExecutionDataAccessorGenerator.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2018 Mountainminds GmbH & Co. KG and Contributors + * Copyright (c) 2009, 2019 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 @@ -45,7 +45,7 @@ public interface IExecutionDataAccessorGenerator { * @return additional stack size required by the implementation, including * the instance pushed to the stack */ - public int generateDataAccessor(final long classid, final String classname, - final int probecount, MethodVisitor mv); + int generateDataAccessor(long classid, String classname, int probecount, + MethodVisitor mv); } diff --git a/org.jacoco.core/src/org/jacoco/core/runtime/IRemoteCommandVisitor.java b/org.jacoco.core/src/org/jacoco/core/runtime/IRemoteCommandVisitor.java index c4b12f1c..9057a4e4 100644 --- a/org.jacoco.core/src/org/jacoco/core/runtime/IRemoteCommandVisitor.java +++ b/org.jacoco.core/src/org/jacoco/core/runtime/IRemoteCommandVisitor.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2018 Mountainminds GmbH & Co. KG and Contributors + * Copyright (c) 2009, 2019 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 @@ -28,7 +28,6 @@ public interface IRemoteCommandVisitor { * @throws IOException * in case of problems with the remote connection */ - public void visitDumpCommand(final boolean dump, final boolean reset) - throws IOException; + void visitDumpCommand(boolean dump, boolean reset) throws IOException; } diff --git a/org.jacoco.core/src/org/jacoco/core/runtime/IRuntime.java b/org.jacoco.core/src/org/jacoco/core/runtime/IRuntime.java index 9eee8fba..ea055632 100644 --- a/org.jacoco.core/src/org/jacoco/core/runtime/IRuntime.java +++ b/org.jacoco.core/src/org/jacoco/core/runtime/IRuntime.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2018 Mountainminds GmbH & Co. KG and Contributors + * Copyright (c) 2009, 2019 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 @@ -26,12 +26,12 @@ public interface IRuntime extends IExecutionDataAccessorGenerator { * @throws Exception * any internal problem during startup */ - public void startup(RuntimeData data) throws Exception; + void startup(RuntimeData data) throws Exception; /** * Allows the coverage runtime to cleanup internals. This class should be * called when classes instrumented for this runtime are not used any more. */ - public void shutdown(); + void shutdown(); } diff --git a/org.jacoco.core/src/org/jacoco/core/runtime/InjectedClassRuntime.java b/org.jacoco.core/src/org/jacoco/core/runtime/InjectedClassRuntime.java new file mode 100644 index 00000000..ee7aa1ac --- /dev/null +++ b/org.jacoco.core/src/org/jacoco/core/runtime/InjectedClassRuntime.java @@ -0,0 +1,141 @@ +/******************************************************************************* + * Copyright (c) 2009, 2019 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: + * Evgeny Mandrikov - initial API and implementation + * + *******************************************************************************/ +package org.jacoco.core.runtime; + +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; + +/** + * {@link IRuntime} which defines a new class using + * {@code java.lang.invoke.MethodHandles.Lookup.defineClass} introduced in Java + * 9. Module where class will be defined must be opened to at least module of + * this class. + */ +public class InjectedClassRuntime extends AbstractRuntime { + + private static final String FIELD_NAME = "data"; + + private static final String FIELD_TYPE = "Ljava/lang/Object;"; + + private final Class<?> locator; + + private final String injectedClassName; + + /** + * Creates a new runtime which will define a class to the same class loader + * and in the same package and protection domain as given class. + * + * @param locator + * class to identify the target class loader and package + * @param simpleClassName + * simple name of the class to be defined + */ + public InjectedClassRuntime(final Class<?> locator, + final String simpleClassName) { + this.locator = locator; + this.injectedClassName = locator.getPackage().getName().replace('.', + '/') + '/' + simpleClassName; + } + + @Override + public void startup(final RuntimeData data) throws Exception { + super.startup(data); + Lookup // + .privateLookupIn(locator, Lookup.lookup()) // + .defineClass(createClass(injectedClassName)) // + .getField(FIELD_NAME) // + .set(null, data); + } + + public void shutdown() { + // nothing to do + } + + public int generateDataAccessor(final long classid, final String classname, + final int probecount, final MethodVisitor mv) { + mv.visitFieldInsn(Opcodes.GETSTATIC, injectedClassName, FIELD_NAME, + FIELD_TYPE); + + RuntimeData.generateAccessCall(classid, classname, probecount, mv); + + return 6; + } + + private static byte[] createClass(final String name) { + final ClassWriter cw = new ClassWriter(0); + cw.visit(Opcodes.V9, Opcodes.ACC_SYNTHETIC | Opcodes.ACC_PUBLIC, + name.replace('.', '/'), null, "java/lang/Object", null); + cw.visitField(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, FIELD_NAME, + FIELD_TYPE, null, null); + cw.visitEnd(); + return cw.toByteArray(); + } + + /** + * Provides access to classes {@code java.lang.invoke.MethodHandles} and + * {@code java.lang.invoke.MethodHandles.Lookup} introduced in Java 8. + */ + private static class Lookup { + + private final Object instance; + + private Lookup(final Object instance) { + this.instance = instance; + } + + /** + * @return a lookup object for the caller of this method + */ + static Lookup lookup() throws Exception { + return new Lookup(Class // + .forName("java.lang.invoke.MethodHandles") // + .getMethod("lookup") // + .invoke(null)); + } + + /** + * See corresponding method introduced in Java 9. + * + * @param targetClass + * the target class + * @param lookup + * the caller lookup object + * @return a lookup object for the target class, with private access + */ + static Lookup privateLookupIn(final Class<?> targetClass, + final Lookup lookup) throws Exception { + return new Lookup(Class // + .forName("java.lang.invoke.MethodHandles") // + .getMethod("privateLookupIn", Class.class, + Class.forName( + "java.lang.invoke.MethodHandles$Lookup")) // + .invoke(null, targetClass, lookup.instance)); + } + + /** + * See corresponding method introduced in Java 9. + * + * @param bytes + * the class bytes + * @return class + */ + Class<?> defineClass(final byte[] bytes) throws Exception { + return (Class<?>) Class // + .forName("java.lang.invoke.MethodHandles$Lookup") + .getMethod("defineClass", byte[].class) + .invoke(this.instance, new Object[] { bytes }); + } + + } + +} diff --git a/org.jacoco.core/src/org/jacoco/core/runtime/LoggerRuntime.java b/org.jacoco.core/src/org/jacoco/core/runtime/LoggerRuntime.java index 50cc7d70..72553956 100644 --- a/org.jacoco.core/src/org/jacoco/core/runtime/LoggerRuntime.java +++ b/org.jacoco.core/src/org/jacoco/core/runtime/LoggerRuntime.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2018 Mountainminds GmbH & Co. KG and Contributors + * Copyright (c) 2009, 2019 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 diff --git a/org.jacoco.core/src/org/jacoco/core/runtime/ModifiedSystemClassRuntime.java b/org.jacoco.core/src/org/jacoco/core/runtime/ModifiedSystemClassRuntime.java index 6d31358f..5f2cc497 100644 --- a/org.jacoco.core/src/org/jacoco/core/runtime/ModifiedSystemClassRuntime.java +++ b/org.jacoco.core/src/org/jacoco/core/runtime/ModifiedSystemClassRuntime.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2018 Mountainminds GmbH & Co. KG and Contributors + * Copyright (c) 2009, 2019 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 @@ -114,7 +114,7 @@ public class ModifiedSystemClassRuntime extends AbstractRuntime { * @return new runtime instance * * @throws ClassNotFoundException - * id the given class can not be found + * if the given class can not be found */ public static IRuntime createFor(final Instrumentation inst, final String className, final String accessFieldName) @@ -153,7 +153,7 @@ public class ModifiedSystemClassRuntime extends AbstractRuntime { */ public static byte[] instrument(final byte[] source, final String accessFieldName) { - final ClassReader reader = new ClassReader(source); + final ClassReader reader = InstrSupport.classReaderFor(source); final ClassWriter writer = new ClassWriter(reader, 0); reader.accept(new ClassVisitor(InstrSupport.ASM_API_VERSION, writer) { diff --git a/org.jacoco.core/src/org/jacoco/core/runtime/OfflineInstrumentationAccessGenerator.java b/org.jacoco.core/src/org/jacoco/core/runtime/OfflineInstrumentationAccessGenerator.java index 4201c719..af6671ee 100644 --- a/org.jacoco.core/src/org/jacoco/core/runtime/OfflineInstrumentationAccessGenerator.java +++ b/org.jacoco.core/src/org/jacoco/core/runtime/OfflineInstrumentationAccessGenerator.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2018 Mountainminds GmbH & Co. KG and Contributors + * Copyright (c) 2009, 2019 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 diff --git a/org.jacoco.core/src/org/jacoco/core/runtime/RemoteControlReader.java b/org.jacoco.core/src/org/jacoco/core/runtime/RemoteControlReader.java index b55c4ecd..46fb6d2c 100644 --- a/org.jacoco.core/src/org/jacoco/core/runtime/RemoteControlReader.java +++ b/org.jacoco.core/src/org/jacoco/core/runtime/RemoteControlReader.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2018 Mountainminds GmbH & Co. KG and Contributors + * Copyright (c) 2009, 2019 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 diff --git a/org.jacoco.core/src/org/jacoco/core/runtime/RemoteControlWriter.java b/org.jacoco.core/src/org/jacoco/core/runtime/RemoteControlWriter.java index f6a36930..8534471f 100644 --- a/org.jacoco.core/src/org/jacoco/core/runtime/RemoteControlWriter.java +++ b/org.jacoco.core/src/org/jacoco/core/runtime/RemoteControlWriter.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2018 Mountainminds GmbH & Co. KG and Contributors + * Copyright (c) 2009, 2019 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 diff --git a/org.jacoco.core/src/org/jacoco/core/runtime/RuntimeData.java b/org.jacoco.core/src/org/jacoco/core/runtime/RuntimeData.java index 74a51dcb..afb5b7f3 100644 --- a/org.jacoco.core/src/org/jacoco/core/runtime/RuntimeData.java +++ b/org.jacoco.core/src/org/jacoco/core/runtime/RuntimeData.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2018 Mountainminds GmbH & Co. KG and Contributors + * Copyright (c) 2009, 2019 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 diff --git a/org.jacoco.core/src/org/jacoco/core/runtime/SystemPropertiesRuntime.java b/org.jacoco.core/src/org/jacoco/core/runtime/SystemPropertiesRuntime.java index 5e86c9b6..d7c338c8 100644 --- a/org.jacoco.core/src/org/jacoco/core/runtime/SystemPropertiesRuntime.java +++ b/org.jacoco.core/src/org/jacoco/core/runtime/SystemPropertiesRuntime.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2018 Mountainminds GmbH & Co. KG and Contributors + * Copyright (c) 2009, 2019 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 diff --git a/org.jacoco.core/src/org/jacoco/core/runtime/URLStreamHandlerRuntime.java b/org.jacoco.core/src/org/jacoco/core/runtime/URLStreamHandlerRuntime.java index afce7246..55f9c874 100644 --- a/org.jacoco.core/src/org/jacoco/core/runtime/URLStreamHandlerRuntime.java +++ b/org.jacoco.core/src/org/jacoco/core/runtime/URLStreamHandlerRuntime.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2018 Mountainminds GmbH & Co. KG and Contributors + * Copyright (c) 2009, 2019 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 diff --git a/org.jacoco.core/src/org/jacoco/core/runtime/WildcardMatcher.java b/org.jacoco.core/src/org/jacoco/core/runtime/WildcardMatcher.java index 2b52d4b8..91feaa66 100644 --- a/org.jacoco.core/src/org/jacoco/core/runtime/WildcardMatcher.java +++ b/org.jacoco.core/src/org/jacoco/core/runtime/WildcardMatcher.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2018 Mountainminds GmbH & Co. KG and Contributors + * Copyright (c) 2009, 2019 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 @@ -14,9 +14,10 @@ package org.jacoco.core.runtime; import java.util.regex.Pattern; /** - * Matches strings against <code>?</code>/<code>*</code> wildcard expressions. - * Multiple expressions can be separated with a colon (:). In this case the - * expression matches if at least one part matches. + * Matches strings against glob like wildcard expressions where <code>?</code> + * matches any single character and <code>*</code> matches any number of any + * character. Multiple expressions can be separated with a colon (:). In this + * case the expression matches if at least one part matches. */ public class WildcardMatcher { @@ -47,7 +48,7 @@ public class WildcardMatcher { for (final char c : expression.toCharArray()) { switch (c) { case '?': - regex.append(".?"); + regex.append("."); break; case '*': regex.append(".*"); diff --git a/org.jacoco.core/src/org/jacoco/core/runtime/package-info.java b/org.jacoco.core/src/org/jacoco/core/runtime/package-info.java index 127b7a22..1ac7cccb 100644 --- a/org.jacoco.core/src/org/jacoco/core/runtime/package-info.java +++ b/org.jacoco.core/src/org/jacoco/core/runtime/package-info.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2018 Mountainminds GmbH & Co. KG and Contributors + * Copyright (c) 2009, 2019 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 @@ -13,4 +13,4 @@ /** * Runtime control and execution data collection. */ -package org.jacoco.core.runtime;
\ No newline at end of file +package org.jacoco.core.runtime; diff --git a/org.jacoco.core/src/org/jacoco/core/tools/ExecDumpClient.java b/org.jacoco.core/src/org/jacoco/core/tools/ExecDumpClient.java index c6a8007e..35617b49 100644 --- a/org.jacoco.core/src/org/jacoco/core/tools/ExecDumpClient.java +++ b/org.jacoco.core/src/org/jacoco/core/tools/ExecDumpClient.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2018 Mountainminds GmbH & Co. KG and Contributors + * Copyright (c) 2009, 2019 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 diff --git a/org.jacoco.core/src/org/jacoco/core/tools/ExecFileLoader.java b/org.jacoco.core/src/org/jacoco/core/tools/ExecFileLoader.java index c5c4555d..cf7b2e56 100644 --- a/org.jacoco.core/src/org/jacoco/core/tools/ExecFileLoader.java +++ b/org.jacoco.core/src/org/jacoco/core/tools/ExecFileLoader.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2018 Mountainminds GmbH & Co. KG and Contributors + * Copyright (c) 2009, 2019 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 diff --git a/org.jacoco.core/src/org/jacoco/core/tools/package-info.java b/org.jacoco.core/src/org/jacoco/core/tools/package-info.java index 7aa7e9f7..c35c5f9a 100644 --- a/org.jacoco.core/src/org/jacoco/core/tools/package-info.java +++ b/org.jacoco.core/src/org/jacoco/core/tools/package-info.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2018 Mountainminds GmbH & Co. KG and Contributors + * Copyright (c) 2009, 2019 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 @@ -14,4 +14,4 @@ * Collection of tools build on top of the JaCoCo core APIs. The tools offer * more high-level functionality useful for integrating JaCoCo. */ -package org.jacoco.core.tools;
\ No newline at end of file +package org.jacoco.core.tools; |