blob: 15c3a7a06f222006837c2941750687072c2e9688 [file] [log] [blame]
// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.utils;
import static com.android.tools.r8.utils.FileUtils.CLASS_EXTENSION;
import com.android.tools.r8.ProgramResource;
import com.android.tools.r8.ProgramResource.Kind;
import com.android.tools.r8.ProgramResourceProvider;
import com.android.tools.r8.ResourceException;
import com.android.tools.r8.kotlin.KotlinSourceDebugExtensionParser.KotlinSourceDebugExtensionParserResult;
import com.android.tools.r8.references.ClassReference;
import com.android.tools.r8.references.Reference;
import com.android.tools.r8.utils.ArchiveResourceProvider.ArchiveResourceProviderHelper;
import com.android.tools.r8.utils.timing.Timing;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectMaps;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import java.io.IOException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
public class CfLineToMethodMapper {
private static final String NAME_DESCRIPTOR_SEPARATOR = ";;";
private final Map<String, Int2ObjectMap<String>> sourceMethodMapping;
private CfLineToMethodMapper(Map<String, Int2ObjectMap<String>> sourceMethodMapping) {
this.sourceMethodMapping = sourceMethodMapping;
}
public static CfLineToMethodMapper create(
AndroidApp inputApp,
KotlinSourceDebugExtensionCollection kotlinSourceDebugExtensions,
Timing timing) {
Set<ClassReference> kotlinClassesWithInlineFunctions =
getKotlinClassesWithInlineFunctions(kotlinSourceDebugExtensions, timing);
return new CfLineToMethodMapper(
readLineNumbersFromClassFiles(inputApp, kotlinClassesWithInlineFunctions, timing));
}
public String lookupNameAndDescriptor(String binaryName, int lineNumber)
throws ResourceException {
return sourceMethodMapping.getOrDefault(binaryName, Int2ObjectMaps.emptyMap()).get(lineNumber);
}
private static Set<ClassReference> getKotlinClassesWithInlineFunctions(
KotlinSourceDebugExtensionCollection kotlinSourceDebugExtensions, Timing timing) {
try (Timing t0 = timing.begin("Get kotlin classes with inline functions")) {
Set<ClassReference> kotlinClassesWithInlineFunctions = new HashSet<>();
for (KotlinSourceDebugExtensionParserResult kotlinSourceDebugExtension :
kotlinSourceDebugExtensions.values()) {
boolean first = true;
for (var inlineePosition : kotlinSourceDebugExtension.getInlineePositions().values()) {
if (first) {
// Skip. It is the current holder.
first = false;
} else if (inlineePosition != null) {
String binaryName = inlineePosition.getSource().getPath();
kotlinClassesWithInlineFunctions.add(Reference.classFromBinaryName(binaryName));
}
}
}
return kotlinClassesWithInlineFunctions;
}
}
private static Map<String, Int2ObjectMap<String>> readLineNumbersFromClassFiles(
AndroidApp inputApp, Set<ClassReference> kotlinClassesWithInlineFunctions, Timing timing) {
try (Timing t0 = timing.begin("Read line numbers from class files")) {
Map<String, Int2ObjectMap<String>> sourceMethodMapping = new HashMap<>();
try {
for (ProgramResourceProvider resourceProvider : inputApp.getProgramResourceProviders()) {
// TODO(b/391785584): Do not use the input providers here.
if (resourceProvider instanceof InternalProgramClassProvider) {
continue;
}
if (resourceProvider instanceof ArchiveResourceProvider) {
// Special case the ArchiveResourceProvider to minimize disk I/O.
ArchiveResourceProvider provider = (ArchiveResourceProvider) resourceProvider;
ArchiveResourceProviderHelper helper = new ArchiveResourceProviderHelper(provider);
timing.begin("Read archive");
helper.accept(
programResource ->
processProgramResource(
programResource,
kotlinClassesWithInlineFunctions,
sourceMethodMapping,
timing),
entry -> {
String name = entry.getName();
if (!name.endsWith(CLASS_EXTENSION)) {
return false;
}
String binaryName = name.substring(0, name.length() - CLASS_EXTENSION.length());
return kotlinClassesWithInlineFunctions.contains(
Reference.classFromBinaryName(binaryName));
});
timing.end();
} else {
for (ProgramResource programResource : resourceProvider.getProgramResources()) {
processProgramResource(
programResource, kotlinClassesWithInlineFunctions, sourceMethodMapping, timing);
}
}
}
} catch (ResourceException e) {
throw new RuntimeException(e);
}
return sourceMethodMapping;
}
}
private static void processProgramResource(
ProgramResource programResource,
Set<ClassReference> kotlinClassesWithInlineFunctions,
Map<String, Int2ObjectMap<String>> sourceMethodMapping,
Timing timing) {
if (programResource.getKind() != Kind.CF) {
return;
}
Set<String> classDescriptors = programResource.getClassDescriptors();
if (classDescriptors == null || classDescriptors.size() != 1) {
return;
}
String classDescriptor = classDescriptors.iterator().next();
ClassReference classReference = Reference.classFromDescriptor(classDescriptor);
if (!kotlinClassesWithInlineFunctions.contains(classReference)) {
return;
}
timing.begin("Parse class");
Int2ObjectMap<String> currentLineNumberMapping = new Int2ObjectOpenHashMap<>();
ClassVisitor.visit(programResource, currentLineNumberMapping);
sourceMethodMapping.put(classReference.getBinaryName(), currentLineNumberMapping);
timing.end();
}
public static String getName(String nameAndDescriptor) {
int index = nameAndDescriptor.indexOf(NAME_DESCRIPTOR_SEPARATOR);
assert index > 0;
return nameAndDescriptor.substring(0, index);
}
public static String getDescriptor(String nameAndDescriptor) {
int index = nameAndDescriptor.indexOf(NAME_DESCRIPTOR_SEPARATOR);
assert index > 0;
return nameAndDescriptor.substring(index + NAME_DESCRIPTOR_SEPARATOR.length());
}
private static class ClassVisitor extends org.objectweb.asm.ClassVisitor {
private final Int2ObjectMap<String> lineNumberMapping;
private ClassVisitor(Int2ObjectMap<String> lineNumberMapping) {
super(InternalOptions.ASM_VERSION);
this.lineNumberMapping = lineNumberMapping;
}
public static void visit(
ProgramResource programResource, Int2ObjectMap<String> lineNumberMapping) {
byte[] bytes;
try {
bytes = StreamUtils.streamToByteArrayClose(programResource.getByteStream());
} catch (ResourceException | IOException e) {
// Intentionally left empty because the addition of inline info for kotlin inline
// functions is a best effort.
return;
}
new ClassReader(bytes).accept(new ClassVisitor(lineNumberMapping), ClassReader.SKIP_FRAMES);
}
@Override
public MethodVisitor visitMethod(
int access, String name, String descriptor, String signature, String[] exceptions) {
return new MethodLineVisitor(
name + NAME_DESCRIPTOR_SEPARATOR + descriptor, lineNumberMapping);
}
}
private static class MethodLineVisitor extends MethodVisitor {
private final String nameAndDescriptor;
private final Map<Integer, String> lineMethodMapping;
private MethodLineVisitor(String nameAndDescriptor, Map<Integer, String> lineMethodMapping) {
super(InternalOptions.ASM_VERSION);
this.nameAndDescriptor = nameAndDescriptor;
this.lineMethodMapping = lineMethodMapping;
}
@Override
public void visitLineNumber(int line, Label start) {
lineMethodMapping.put(line, nameAndDescriptor);
}
}
}