| // 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 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 it.unimi.dsi.fastutil.ints.Int2ReferenceOpenHashMap; | |
| import java.io.IOException; | |
| import java.util.HashMap; | |
| import java.util.Map; | |
| import org.objectweb.asm.ClassReader; | |
| import org.objectweb.asm.Label; | |
| import org.objectweb.asm.MethodVisitor; | |
| public class CfLineToMethodMapper { | |
| private Map<String, Int2ReferenceOpenHashMap<String>> sourceMethodMapping = null; | |
| private final AndroidApp inputApp; | |
| private static final String NAME_DESCRIPTOR_SEPARATOR = ";;"; | |
| public CfLineToMethodMapper(AndroidApp inputApp) { | |
| this.inputApp = inputApp; | |
| } | |
| public String lookupNameAndDescriptor(String binaryName, int lineNumber) | |
| throws ResourceException { | |
| if (sourceMethodMapping == null) { | |
| sourceMethodMapping = new HashMap<>(); | |
| readLineNumbersFromClassFiles(); | |
| } | |
| Int2ReferenceOpenHashMap<String> lineMappings = sourceMethodMapping.get(binaryName); | |
| return lineMappings == null ? null : lineMappings.get(lineNumber); | |
| } | |
| private void readLineNumbersFromClassFiles() throws ResourceException { | |
| ClassVisitor classVisitor = new ClassVisitor(); | |
| for (ProgramResourceProvider resourceProvider : inputApp.getProgramResourceProviders()) { | |
| // TODO(b/391785584): Do not use the input providers here. | |
| if (resourceProvider instanceof InternalProgramClassProvider) { | |
| continue; | |
| } | |
| if (resourceProvider instanceof ArchiveResourceProvider) { | |
| ArchiveResourceProvider provider = (ArchiveResourceProvider) resourceProvider; | |
| provider.accept( | |
| programResource -> { | |
| if (programResource.getKind() != Kind.CF) { | |
| return; | |
| } | |
| try { | |
| new ClassReader(StreamUtils.streamToByteArrayClose(programResource.getByteStream())) | |
| .accept(classVisitor, ClassReader.SKIP_FRAMES); | |
| } catch (IOException | ResourceException e) { | |
| // Intentionally left empty because the addition of inline info for kotlin inline | |
| // functions is a best effort. | |
| } | |
| }); | |
| } else { | |
| for (ProgramResource programResource : resourceProvider.getProgramResources()) { | |
| if (programResource.getKind() == Kind.CF) { | |
| try { | |
| new ClassReader(StreamUtils.streamToByteArrayClose(programResource.getByteStream())) | |
| .accept(classVisitor, ClassReader.SKIP_FRAMES); | |
| } catch (IOException e) { | |
| // Intentionally left empty because the addition of inline info for kotlin inline | |
| // functions is a best effort. | |
| } | |
| } | |
| } | |
| } | |
| } | |
| } | |
| 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 class ClassVisitor extends org.objectweb.asm.ClassVisitor { | |
| private Int2ReferenceOpenHashMap<String> currentLineNumberMapping = null; | |
| private ClassVisitor() { | |
| super(InternalOptions.ASM_VERSION); | |
| } | |
| @Override | |
| public void visit( | |
| int version, | |
| int access, | |
| String name, | |
| String signature, | |
| String superName, | |
| String[] interfaces) { | |
| super.visit(version, access, name, signature, superName, interfaces); | |
| currentLineNumberMapping = | |
| sourceMethodMapping.computeIfAbsent(name, ignored -> new Int2ReferenceOpenHashMap<>()); | |
| } | |
| @Override | |
| public MethodVisitor visitMethod( | |
| int access, String name, String descriptor, String signature, String[] exceptions) { | |
| return new MethodLineVisitor( | |
| name + NAME_DESCRIPTOR_SEPARATOR + descriptor, currentLineNumberMapping); | |
| } | |
| } | |
| 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); | |
| } | |
| } | |
| } |