blob: 7c882b46ec823b21ff507a48382881c6165bfd8d [file] [log] [blame]
// 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.InterfaceDesugaringForTesting.getDefaultMethodPrefix;
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 List<AnnotationSubject> annotations() {
throw new Unimplemented();
}
@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(),
getDefaultMethodPrefix() + reference.getMethodName(),
p);
}
}