KotlinInlineFilter.java
/*******************************************************************************
* Copyright (c) 2009, 2024 Mountainminds GmbH & Co. KG and Contributors
* This program and the accompanying materials are made available under
* the terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*
* 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 (final AbstractInsnNode i : methodNode.instructions) {
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 (true) {
line = br.readLine();
if (line.equals("*E") || line.equals("*S KotlinDebug")) {
break;
}
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
);
}