aboutsummaryrefslogtreecommitdiff
path: root/org.jacoco.core
diff options
context:
space:
mode:
authorEvgeny Mandrikov <Godin@users.noreply.github.com>2018-12-10 12:31:45 +0100
committerMarc R. Hoffmann <hoffmann@mountainminds.com>2018-12-10 12:31:45 +0100
commit490939ef743d679990fbabc0dba33bb4bb37e302 (patch)
tree162cf48104f2351ed19f7b502262847f79635ffa /org.jacoco.core
parent78b28dc8463c301b207c60f0394251d4f2ae9aba (diff)
downloadjacoco-490939ef743d679990fbabc0dba33bb4bb37e302.tar.gz
Add filter for instructions inlined by Kotlin compiler (#764)
Diffstat (limited to 'org.jacoco.core')
-rw-r--r--org.jacoco.core/src/org/jacoco/core/internal/analysis/ClassAnalyzer.java12
-rw-r--r--org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/Filters.java33
-rw-r--r--org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/IFilter.java4
-rw-r--r--org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/IFilterContext.java6
-rw-r--r--org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/KotlinInlineFilter.java135
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
+ );
+
+}