diff options
author | Evgeny Mandrikov <Godin@users.noreply.github.com> | 2018-12-10 12:31:45 +0100 |
---|---|---|
committer | Marc R. Hoffmann <hoffmann@mountainminds.com> | 2018-12-10 12:31:45 +0100 |
commit | 490939ef743d679990fbabc0dba33bb4bb37e302 (patch) | |
tree | 162cf48104f2351ed19f7b502262847f79635ffa /org.jacoco.core | |
parent | 78b28dc8463c301b207c60f0394251d4f2ae9aba (diff) | |
download | jacoco-490939ef743d679990fbabc0dba33bb4bb37e302.tar.gz |
Add filter for instructions inlined by Kotlin compiler (#764)
Diffstat (limited to 'org.jacoco.core')
5 files changed, 172 insertions, 18 deletions
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 02bc380a..c584cacf 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 @@ -31,14 +31,16 @@ import org.objectweb.asm.tree.MethodNode; public class ClassAnalyzer extends ClassProbesVisitor implements IFilterContext { - private final IFilter filter = Filters.ALL; - 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. * @@ -54,6 +56,7 @@ public class ClassAnalyzer extends ClassProbesVisitor this.coverage = coverage; this.probes = probes; this.stringPool = stringPool; + this.filter = Filters.all(); } @Override @@ -75,6 +78,7 @@ public class ClassAnalyzer extends ClassProbesVisitor @Override public void visitSource(final String source, final String debug) { coverage.setSourceFileName(stringPool.get(source)); + sourceDebugExtension = debug; } @Override @@ -146,4 +150,8 @@ public class ClassAnalyzer extends ClassProbesVisitor return coverage.getSourceFileName(); } + public String getSourceDebugExtension() { + return sourceDebugExtension; + } + } 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 76b050f4..f7ffa98d 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 @@ -23,22 +23,27 @@ 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 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 KotlinDefaultArgumentsFilter()); - - 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 KotlinDefaultArgumentsFilter(), new KotlinInlineFilter()); + } private Filters(final IFilter... filters) { this.filters = filters; 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 2c64da1e..97f870d7 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 @@ -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 { 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 index 88c46483..0bc37535 100644 --- 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 @@ -39,4 +39,10 @@ public interface IFilterContext { */
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/KotlinInlineFilter.java b/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/KotlinInlineFilter.java new file mode 100644 index 00000000..4f52dadc --- /dev/null +++ b/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/KotlinInlineFilter.java @@ -0,0 +1,135 @@ +/******************************************************************************* + * 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: + * 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.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"); + int sourceFileId = -1; + 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)) { + sourceFileId = Integer.parseInt(m.group(1)); + } + } + // 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 (sourceFileId == 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 + ); + +} |