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