| // Copyright (c) 2018, 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.codeinspector; |
| |
| import static com.android.tools.r8.ir.desugar.itf.InterfaceMethodRewriter.DEFAULT_METHOD_PREFIX; |
| |
| import com.android.tools.r8.cf.code.CfInstruction; |
| import com.android.tools.r8.cf.code.CfPosition; |
| import com.android.tools.r8.code.Instruction; |
| import com.android.tools.r8.errors.Unimplemented; |
| import com.android.tools.r8.errors.Unreachable; |
| import com.android.tools.r8.graph.AppInfo; |
| import com.android.tools.r8.graph.AppView; |
| import com.android.tools.r8.graph.CfCode; |
| import com.android.tools.r8.graph.CfCode.LocalVariableInfo; |
| import com.android.tools.r8.graph.Code; |
| import com.android.tools.r8.graph.DexAnnotation; |
| import com.android.tools.r8.graph.DexCode; |
| import com.android.tools.r8.graph.DexDebugEvent; |
| import com.android.tools.r8.graph.DexDebugInfo; |
| import com.android.tools.r8.graph.DexDebugPositionState; |
| import com.android.tools.r8.graph.DexEncodedMethod; |
| import com.android.tools.r8.graph.DexItemFactory; |
| import com.android.tools.r8.graph.DexMethod; |
| import com.android.tools.r8.graph.DexString; |
| import com.android.tools.r8.graph.DexType; |
| import com.android.tools.r8.graph.MethodAccessFlags; |
| import com.android.tools.r8.graph.ProgramMethod; |
| import com.android.tools.r8.ir.code.IRCode; |
| import com.android.tools.r8.naming.MemberNaming; |
| import com.android.tools.r8.naming.MemberNaming.MethodSignature; |
| import com.android.tools.r8.naming.signature.GenericSignatureParser; |
| import com.android.tools.r8.references.MethodReference; |
| import com.android.tools.r8.references.Reference; |
| import com.android.tools.r8.references.TypeReference; |
| import com.android.tools.r8.utils.StringUtils; |
| import com.android.tools.r8.utils.codeinspector.LocalVariableTable.LocalVariableTableEntry; |
| import com.google.common.base.Predicates; |
| import com.google.common.collect.ImmutableList; |
| import it.unimi.dsi.fastutil.objects.Object2IntMap; |
| import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; |
| import java.util.Arrays; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.function.Predicate; |
| import java.util.stream.Collectors; |
| |
| public class FoundMethodSubject extends MethodSubject { |
| |
| private final CodeInspector codeInspector; |
| private final FoundClassSubject clazz; |
| private final DexEncodedMethod dexMethod; |
| |
| public FoundMethodSubject( |
| CodeInspector codeInspector, DexEncodedMethod encoded, FoundClassSubject clazz) { |
| this.codeInspector = codeInspector; |
| this.clazz = clazz; |
| this.dexMethod = encoded; |
| } |
| |
| @Override |
| public IRCode buildIR() { |
| return buildIR(AppView.createForD8(AppInfo.createInitialAppInfo(codeInspector.application))); |
| } |
| |
| @Override |
| public IRCode buildIR(AppView<?> appView) { |
| assert codeInspector.application.options.programConsumer != null; |
| return getProgramMethod().buildIR(appView); |
| } |
| |
| @Override |
| public boolean isPresent() { |
| return true; |
| } |
| |
| @Override |
| public boolean isRenamed() { |
| return clazz.getNaming() != null |
| && !getFinalSignature().name.equals(getOriginalSignature().name); |
| } |
| |
| @Override |
| public boolean isAbstract() { |
| return dexMethod.accessFlags.isAbstract(); |
| } |
| |
| @Override |
| public boolean isBridge() { |
| return dexMethod.accessFlags.isBridge(); |
| } |
| |
| @Override |
| public boolean isSynchronized() { |
| return dexMethod.accessFlags.isSynchronized(); |
| } |
| |
| @Override |
| public boolean isInstanceInitializer() { |
| return dexMethod.isInstanceInitializer(); |
| } |
| |
| @Override |
| public boolean isClassInitializer() { |
| return dexMethod.isClassInitializer(); |
| } |
| |
| @Override |
| public boolean isVirtual() { |
| return dexMethod.isNonPrivateVirtualMethod(); |
| } |
| |
| @Override |
| public MethodAccessFlags getAccessFlags() { |
| return dexMethod.getAccessFlags(); |
| } |
| |
| @Override |
| public DexEncodedMethod getMethod() { |
| return dexMethod; |
| } |
| |
| @Override |
| public ProgramMethod getProgramMethod() { |
| return new ProgramMethod(clazz.getDexProgramClass(), getMethod()); |
| } |
| |
| @Override |
| public MethodSignature getOriginalSignature() { |
| MethodSignature signature = getFinalSignature(); |
| if (clazz.getNaming() == null) { |
| return signature; |
| } |
| |
| // Map the parameters and return type to original names. This is needed as the in the |
| // Proguard map the names on the left side are the original names. E.g. |
| // |
| // X -> a |
| // X method(X) -> a |
| // |
| // whereas the final signature is for X.a is "a (a)" |
| String[] originalParameters = new String[signature.parameters.length]; |
| for (int i = 0; i < originalParameters.length; i++) { |
| originalParameters[i] = codeInspector.getOriginalTypeName(signature.parameters[i]); |
| } |
| String returnType = codeInspector.getOriginalTypeName(signature.type); |
| |
| MethodSignature lookupSignature = |
| new MethodSignature(signature.name, returnType, originalParameters); |
| |
| MemberNaming memberNaming = clazz.getNaming().lookup(lookupSignature); |
| return memberNaming != null ? (MethodSignature) memberNaming.getOriginalSignature() : signature; |
| } |
| |
| @Override |
| public MethodSignature getFinalSignature() { |
| return MethodSignature.fromDexMethod(dexMethod.getReference()); |
| } |
| |
| @Override |
| public String getOriginalSignatureAttribute() { |
| return codeInspector.getOriginalSignatureAttribute( |
| getFinalSignatureAttribute(), GenericSignatureParser::parseMethodSignature); |
| } |
| |
| public DexMethod getOriginalDexMethod(DexItemFactory dexItemFactory) { |
| MethodSignature methodSignature = getOriginalSignature(); |
| if (methodSignature.isQualified()) { |
| methodSignature = methodSignature.toUnqualified(); |
| } |
| return methodSignature.toDexMethod( |
| dexItemFactory, dexItemFactory.createType(clazz.getOriginalDescriptor())); |
| } |
| |
| @Override |
| public String getFinalSignatureAttribute() { |
| return dexMethod.getGenericSignature().toString(); |
| } |
| |
| public Iterable<InstructionSubject> instructions() { |
| return instructions(Predicates.alwaysTrue()); |
| } |
| |
| public Iterable<InstructionSubject> instructions(Predicate<InstructionSubject> predicate) { |
| return () -> iterateInstructions(predicate); |
| } |
| |
| @Override |
| public Iterator<InstructionSubject> iterateInstructions() { |
| return codeInspector.createInstructionIterator(this); |
| } |
| |
| @Override |
| public <T extends InstructionSubject> Iterator<T> iterateInstructions( |
| Predicate<InstructionSubject> filter) { |
| return new FilteredInstructionIterator<>(codeInspector, this, filter); |
| } |
| |
| @Override |
| public Iterator<TryCatchSubject> iterateTryCatches() { |
| return codeInspector.createTryCatchIterator(this); |
| } |
| |
| @Override |
| public <T extends TryCatchSubject> Iterator<T> iterateTryCatches( |
| Predicate<TryCatchSubject> filter) { |
| return new FilteredTryCatchIterator<>(codeInspector, this, filter); |
| } |
| |
| @Override |
| public boolean hasLocalVariableTable() { |
| Code code = getMethod().getCode(); |
| if (code == null) { |
| return false; |
| } |
| if (code.isDexCode()) { |
| DexCode dexCode = code.asDexCode(); |
| if (dexCode.getDebugInfo() != null) { |
| for (DexString parameter : dexCode.getDebugInfo().parameters) { |
| if (parameter != null) { |
| return true; |
| } |
| } |
| for (DexDebugEvent event : dexCode.getDebugInfo().events) { |
| if (event instanceof DexDebugEvent.StartLocal) { |
| return true; |
| } |
| } |
| } |
| return false; |
| } |
| if (code.isCfCode()) { |
| return !code.asCfCode().getLocalVariables().isEmpty(); |
| } |
| throw new Unreachable("Unexpected code type: " + code.getClass().getSimpleName()); |
| } |
| |
| @Override |
| public LineNumberTable getLineNumberTable() { |
| Code code = getMethod().getCode(); |
| if (code == null) { |
| return null; |
| } |
| if (code.isDexCode()) { |
| return getDexLineNumberTable(code.asDexCode()); |
| } |
| if (code.isCfCode()) { |
| return getCfLineNumberTable(code.asCfCode()); |
| } |
| throw new Unreachable("Unexpected code type: " + code.getClass().getSimpleName()); |
| } |
| |
| private LineNumberTable getCfLineNumberTable(CfCode code) { |
| int currentLine = -1; |
| Object2IntMap<InstructionSubject> lineNumberTable = |
| new Object2IntOpenHashMap<>(code.getInstructions().size()); |
| for (CfInstruction insn : code.getInstructions()) { |
| if (insn instanceof CfPosition) { |
| currentLine = ((CfPosition) insn).getPosition().line; |
| } |
| if (currentLine != -1) { |
| lineNumberTable.put(new CfInstructionSubject(insn, this), currentLine); |
| } |
| } |
| return currentLine == -1 ? null : new LineNumberTable(lineNumberTable); |
| } |
| |
| private LineNumberTable getDexLineNumberTable(DexCode code) { |
| DexDebugInfo debugInfo = code.getDebugInfo(); |
| if (debugInfo == null) { |
| return null; |
| } |
| Object2IntMap<InstructionSubject> lineNumberTable = new Object2IntOpenHashMap<>(); |
| DexDebugPositionState state = |
| new DexDebugPositionState(debugInfo.startLine, getMethod().getReference()); |
| Iterator<DexDebugEvent> iterator = Arrays.asList(debugInfo.events).iterator(); |
| for (Instruction insn : code.instructions) { |
| int offset = insn.getOffset(); |
| while (state.getCurrentPc() < offset && iterator.hasNext()) { |
| iterator.next().accept(state); |
| } |
| lineNumberTable.put(new DexInstructionSubject(insn, this), state.getCurrentLine()); |
| } |
| return new LineNumberTable(lineNumberTable); |
| } |
| |
| @Override |
| public LocalVariableTable getLocalVariableTable() { |
| Code code = getMethod().getCode(); |
| if (code.isDexCode()) { |
| return getDexLocalVariableTable(code.asDexCode()); |
| } |
| if (code.isCfCode()) { |
| return getCfLocalVariableTable(code.asCfCode()); |
| } |
| throw new Unreachable("Unexpected code type: " + code.getClass().getSimpleName()); |
| } |
| |
| private LocalVariableTable getCfLocalVariableTable(CfCode code) { |
| ImmutableList.Builder<LocalVariableTableEntry> builder = ImmutableList.builder(); |
| for (LocalVariableInfo localVariable : code.getLocalVariables()) { |
| builder.add( |
| new LocalVariableTableEntry( |
| localVariable.getIndex(), |
| localVariable.getLocal().name.toString(), |
| new TypeSubject(codeInspector, localVariable.getLocal().type), |
| localVariable.getLocal().signature == null |
| ? null |
| : localVariable.getLocal().signature.toString(), |
| new CfInstructionSubject(localVariable.getStart(), this), |
| new CfInstructionSubject(localVariable.getEnd(), this))); |
| } |
| return new LocalVariableTable(builder.build()); |
| } |
| |
| private LocalVariableTable getDexLocalVariableTable(DexCode code) { |
| throw new Unimplemented("No support for inspecting the line number table for DexCode"); |
| } |
| |
| @Override |
| public String toString() { |
| return dexMethod.toSourceString(); |
| } |
| |
| @Override |
| public AnnotationSubject annotation(String name) { |
| DexAnnotation annotation = codeInspector.findAnnotation(name, dexMethod.annotations()); |
| return annotation == null |
| ? new AbsentAnnotationSubject() |
| : new FoundAnnotationSubject(annotation); |
| } |
| |
| @Override |
| public FoundMethodSubject asFoundMethodSubject() { |
| return this; |
| } |
| |
| public MethodReference asMethodReference() { |
| DexMethod method = dexMethod.getReference(); |
| return Reference.method( |
| Reference.classFromDescriptor(method.holder.toDescriptorString()), |
| method.name.toString(), |
| Arrays.stream(method.proto.parameters.values) |
| .map(type -> Reference.typeFromDescriptor(type.toDescriptorString())) |
| .collect(Collectors.toList()), |
| Reference.returnTypeFromDescriptor(method.proto.returnType.toDescriptorString())); |
| } |
| |
| @Override |
| public String getJvmMethodSignatureAsString() { |
| return dexMethod.getName().toString() |
| + "(" |
| + StringUtils.join( |
| "", |
| Arrays.stream(dexMethod.getParameters().values) |
| .map(DexType::toDescriptorString) |
| .collect(Collectors.toList())) |
| + ")" |
| + dexMethod.returnType().toDescriptorString(); |
| } |
| |
| @Override |
| public MethodSubject toMethodOnCompanionClass() { |
| ClassSubject companionClass = clazz.toCompanionClass(); |
| MethodReference reference = asMethodReference(); |
| List<String> p = |
| ImmutableList.<String>builder() |
| .add(clazz.getFinalName()) |
| .addAll(reference.getFormalTypes().stream().map(TypeReference::getTypeName).iterator()) |
| .build(); |
| return companionClass.method( |
| reference.getReturnType().getTypeName(), |
| DEFAULT_METHOD_PREFIX + reference.getMethodName(), |
| p); |
| } |
| } |