• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*******************************************************************************
2  * Copyright (c) 2009, 2021 Mountainminds GmbH & Co. KG and Contributors
3  * This program and the accompanying materials are made available under
4  * the terms of the Eclipse Public License 2.0 which is available at
5  * http://www.eclipse.org/legal/epl-2.0
6  *
7  * SPDX-License-Identifier: EPL-2.0
8  *
9  * Contributors:
10  *    Evgeny Mandrikov - initial API and implementation
11  *
12  *******************************************************************************/
13 package org.jacoco.core.internal.analysis.filter;
14 
15 import java.io.BufferedReader;
16 import java.io.IOException;
17 import java.io.StringReader;
18 import java.util.BitSet;
19 import java.util.regex.Matcher;
20 import java.util.regex.Pattern;
21 
22 import org.objectweb.asm.tree.AbstractInsnNode;
23 import org.objectweb.asm.tree.LineNumberNode;
24 import org.objectweb.asm.tree.MethodNode;
25 
26 /**
27  * Filters out instructions that were inlined by Kotlin compiler.
28  */
29 public final class KotlinInlineFilter implements IFilter {
30 
31 	private int firstGeneratedLineNumber = -1;
32 
filter(final MethodNode methodNode, final IFilterContext context, final IFilterOutput output)33 	public void filter(final MethodNode methodNode,
34 			final IFilterContext context, final IFilterOutput output) {
35 		if (context.getSourceDebugExtension() == null) {
36 			return;
37 		}
38 
39 		if (!KotlinGeneratedFilter.isKotlinClass(context)) {
40 			return;
41 		}
42 
43 		if (firstGeneratedLineNumber == -1) {
44 			firstGeneratedLineNumber = getFirstGeneratedLineNumber(
45 					context.getSourceFileName(),
46 					context.getSourceDebugExtension());
47 		}
48 
49 		int line = 0;
50 		for (final AbstractInsnNode i : methodNode.instructions) {
51 			if (AbstractInsnNode.LINE == i.getType()) {
52 				line = ((LineNumberNode) i).line;
53 			}
54 			if (line >= firstGeneratedLineNumber) {
55 				output.ignore(i, i);
56 			}
57 		}
58 	}
59 
getFirstGeneratedLineNumber(final String sourceFileName, final String smap)60 	private static int getFirstGeneratedLineNumber(final String sourceFileName,
61 			final String smap) {
62 		try {
63 			final BufferedReader br = new BufferedReader(
64 					new StringReader(smap));
65 			expectLine(br, "SMAP");
66 			// OutputFileName
67 			expectLine(br, sourceFileName);
68 			// DefaultStratumId
69 			expectLine(br, "Kotlin");
70 			// StratumSection
71 			expectLine(br, "*S Kotlin");
72 			// FileSection
73 			expectLine(br, "*F");
74 			final BitSet sourceFileIds = new BitSet();
75 			String line;
76 			while (!"*L".equals(line = br.readLine())) {
77 				// AbsoluteFileName
78 				br.readLine();
79 
80 				final Matcher m = FILE_INFO_PATTERN.matcher(line);
81 				if (!m.matches()) {
82 					throw new IllegalStateException(
83 							"Unexpected SMAP line: " + line);
84 				}
85 				final String fileName = m.group(2);
86 				if (fileName.equals(sourceFileName)) {
87 					sourceFileIds.set(Integer.parseInt(m.group(1)));
88 				}
89 			}
90 			if (sourceFileIds.isEmpty()) {
91 				throw new IllegalStateException("Unexpected SMAP FileSection");
92 			}
93 			// LineSection
94 			int min = Integer.MAX_VALUE;
95 			while (true) {
96 				line = br.readLine();
97 				if (line.equals("*E") || line.equals("*S KotlinDebug")) {
98 					break;
99 				}
100 				final Matcher m = LINE_INFO_PATTERN.matcher(line);
101 				if (!m.matches()) {
102 					throw new IllegalStateException(
103 							"Unexpected SMAP line: " + line);
104 				}
105 				final int inputStartLine = Integer.parseInt(m.group(1));
106 				final int lineFileID = Integer
107 						.parseInt(m.group(2).substring(1));
108 				final int outputStartLine = Integer.parseInt(m.group(4));
109 				if (sourceFileIds.get(lineFileID)
110 						&& inputStartLine == outputStartLine) {
111 					continue;
112 				}
113 				min = Math.min(outputStartLine, min);
114 			}
115 			return min;
116 		} catch (final IOException e) {
117 			// Must not happen with StringReader
118 			throw new AssertionError(e);
119 		}
120 	}
121 
expectLine(final BufferedReader br, final String expected)122 	private static void expectLine(final BufferedReader br,
123 			final String expected) throws IOException {
124 		final String line = br.readLine();
125 		if (!expected.equals(line)) {
126 			throw new IllegalStateException("Unexpected SMAP line: " + line);
127 		}
128 	}
129 
130 	private static final Pattern LINE_INFO_PATTERN = Pattern.compile("" //
131 			+ "([0-9]++)" // InputStartLine
132 			+ "(#[0-9]++)?+" // LineFileID
133 			+ "(,[0-9]++)?+" // RepeatCount
134 			+ ":([0-9]++)" // OutputStartLine
135 			+ "(,[0-9]++)?+" // OutputLineIncrement
136 	);
137 
138 	private static final Pattern FILE_INFO_PATTERN = Pattern.compile("" //
139 			+ "\\+ ([0-9]++)" // FileID
140 			+ " (.++)" // FileName
141 	);
142 
143 }
144