blob: 987aeadfeb95317ec6fb76f822247f14fd428973 [file] [log] [blame]
// Copyright (c) 2020, 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.retrace.internal;
import com.android.tools.r8.references.Reference;
import com.android.tools.r8.references.TypeReference;
import com.android.tools.r8.retrace.RetraceClassResult;
import com.android.tools.r8.retrace.RetraceFieldElement;
import com.android.tools.r8.retrace.RetraceFieldResult;
import com.android.tools.r8.retrace.RetraceFrameElement;
import com.android.tools.r8.retrace.RetraceFrameResult;
import com.android.tools.r8.retrace.RetraceStackTraceContext;
import com.android.tools.r8.retrace.RetraceStackTraceElementProxy;
import com.android.tools.r8.retrace.RetraceStackTraceElementProxyResult;
import com.android.tools.r8.retrace.RetraceThrownExceptionElement;
import com.android.tools.r8.retrace.RetraceTypeResult;
import com.android.tools.r8.retrace.RetraceTypeResult.Element;
import com.android.tools.r8.retrace.RetracedClassReference;
import com.android.tools.r8.retrace.RetracedFieldReference;
import com.android.tools.r8.retrace.RetracedMethodReference;
import com.android.tools.r8.retrace.RetracedSingleFrame;
import com.android.tools.r8.retrace.RetracedSourceFile;
import com.android.tools.r8.retrace.RetracedTypeReference;
import com.android.tools.r8.retrace.Retracer;
import com.android.tools.r8.retrace.StackTraceElementProxy;
import com.android.tools.r8.retrace.StackTraceElementProxyRetracer;
import com.android.tools.r8.utils.Box;
import com.android.tools.r8.utils.ListUtils;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.OptionalInt;
import java.util.function.Consumer;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class StackTraceElementProxyRetracerImpl<T, ST extends StackTraceElementProxy<T, ST>>
implements StackTraceElementProxyRetracer<T, ST> {
private final Retracer retracer;
public StackTraceElementProxyRetracerImpl(Retracer retracer) {
this.retracer = retracer;
}
@Override
public RetraceStackTraceElementProxyResult<T, ST> retrace(
ST element, RetraceStackTraceContext context) {
RetraceStackTraceElementProxyResultImpl<T, ST> currentResult =
RetraceStackTraceElementProxyResultImpl.Builder.<T, ST>create()
.setResultStream(Stream.of(RetraceStackTraceElementProxyImpl.create(element, context)))
.setResultContext(RetraceStackTraceContext::empty)
.build();
if (!element.hasClassName()
&& !element.hasFieldOrReturnType()
&& !element.hasMethodArguments()) {
return currentResult;
}
currentResult = retraceFieldOrReturnType(currentResult, element);
currentResult = retracedMethodArguments(currentResult, element);
if (element.hasClassName()) {
RetraceClassResult classResult = retracer.retraceClass(element.getClassReference());
if (element.hasMethodName()) {
currentResult = retraceMethod(currentResult, element, classResult, context);
} else if (element.hasFieldName()) {
currentResult = retraceField(currentResult, element, classResult);
} else {
currentResult = retraceClassOrType(currentResult, classResult);
}
}
return currentResult;
}
private RetraceStackTraceElementProxyResultImpl<T, ST> retraceClassOrType(
RetraceStackTraceElementProxyResultImpl<T, ST> currentResult,
RetraceClassResult classResult) {
return currentResult
.builder()
.setResultStream(
currentResult.stream()
.flatMap(
proxy ->
// We assume, since no method was defined for this stack trace element, that
// this was a thrown exception.
classResult.lookupThrownException(proxy.getContext()).stream()
.map(
thrownExceptionElement ->
buildProxyForRewrittenThrownExceptionElement(
classResult, proxy, thrownExceptionElement))))
.build();
}
private RetraceStackTraceElementProxyImpl<T, ST> buildProxyForRewrittenThrownExceptionElement(
RetraceClassResult classResult,
RetraceStackTraceElementProxyImpl<T, ST> proxy,
RetraceThrownExceptionElement thrownExceptionElement) {
return proxy
.builder()
.setRetracedClass(thrownExceptionElement.getRetracedClass())
.joinAmbiguous(classResult.isAmbiguous())
.setTopFrame(true)
.setContext(thrownExceptionElement.getContext())
.apply(
setSourceFileOnProxy(
thrownExceptionElement::getSourceFile,
thrownExceptionElement.getRetracedClass(),
classResult))
.build();
}
private RetraceStackTraceElementProxyResultImpl<T, ST> retraceMethod(
RetraceStackTraceElementProxyResultImpl<T, ST> currentResult,
ST element,
RetraceClassResult classResult,
RetraceStackTraceContext context) {
Box<RetraceStackTraceContext> resultingContext = new Box<>(RetraceStackTraceContext.empty());
RetraceStackTraceElementProxyResultImpl.Builder<T, ST> resultBuilder =
currentResult.builder().setResultContext(resultingContext::get);
return resultBuilder
.setResultStream(
currentResult.stream()
.flatMap(
proxy -> {
RetraceFrameResult frameResult =
classResult.lookupFrame(
context,
element.hasLineNumber()
? OptionalInt.of(element.getLineNumber())
: OptionalInt.empty(),
element.getMethodName());
if (!frameResult.isEmpty()) {
return classResult.stream()
.map(
classElement ->
proxy
.builder()
.setTopFrame(true)
.joinAmbiguous(classResult.isAmbiguous())
.setRetracedClass(classElement.getRetracedClass())
.applyIf(
element.hasLineNumber(),
b -> b.setLineNumber(element.getLineNumber()))
.apply(
setSourceFileOnProxy(
classElement::getSourceFile,
classElement.getRetracedClass(),
classResult))
.build());
}
return frameResult.stream()
.flatMap(
frameElement -> {
resultingContext.set(frameElement.getRetraceStackTraceContext());
return frameElement
.streamRewritten(context)
.map(
singleFrame ->
buildProxyForRewrittenFrameElement(
element,
classResult,
proxy,
frameResult,
frameElement,
singleFrame));
});
}))
.build();
}
private RetraceStackTraceElementProxyImpl<T, ST> buildProxyForRewrittenFrameElement(
ST element,
RetraceClassResult classResult,
RetraceStackTraceElementProxyImpl<T, ST> proxy,
RetraceFrameResult frameResult,
RetraceFrameElement frameElement,
RetracedSingleFrame singleFrame) {
boolean isTopFrame = singleFrame.getIndex() == 0;
RetracedMethodReference method = singleFrame.getMethodReference();
return proxy
.builder()
.setRetracedClass(method.getHolderClass())
.setRetracedMethod(method)
.joinAmbiguous(frameResult.isAmbiguous())
.setTopFrame(isTopFrame)
.setContext(frameElement.getRetraceStackTraceContext())
.applyIf(
element.hasLineNumber(),
builder -> {
builder.setLineNumber(method.getOriginalPositionOrDefault(element.getLineNumber()));
})
.apply(
setSourceFileOnProxy(
() -> frameElement.getSourceFile(method), method.getHolderClass(), classResult))
.build();
}
private RetraceStackTraceElementProxyResultImpl<T, ST> retraceField(
RetraceStackTraceElementProxyResultImpl<T, ST> currentResult,
ST element,
RetraceClassResult classResult) {
return currentResult
.builder()
.setResultStream(
currentResult.stream()
.flatMap(
proxy -> {
RetraceFieldResult retraceFieldResult =
classResult.lookupField(element.getFieldName());
return retraceFieldResult.stream()
.map(
fieldElement ->
buildProxyForRewrittenFieldElement(
classResult, proxy, retraceFieldResult, fieldElement));
}))
.build();
}
private RetraceStackTraceElementProxyImpl<T, ST> buildProxyForRewrittenFieldElement(
RetraceClassResult classResult,
RetraceStackTraceElementProxyImpl<T, ST> proxy,
RetraceFieldResult retraceFieldResult,
RetraceFieldElement fieldElement) {
return proxy
.builder()
.setRetracedClass(fieldElement.getField().getHolderClass())
.setRetracedField(fieldElement.getField())
.joinAmbiguous(retraceFieldResult.isAmbiguous())
.setTopFrame(true)
.apply(
setSourceFileOnProxy(
fieldElement::getSourceFile, fieldElement.getField().getHolderClass(), classResult))
.build();
}
private Consumer<RetraceStackTraceElementProxyImpl.Builder<T, ST>> setSourceFileOnProxy(
Supplier<RetracedSourceFile> sourceFile,
RetracedClassReference classReference,
RetraceClassResult classResult) {
return proxy -> {
ST original = proxy.originalElement;
if (!original.hasSourceFile()) {
return;
}
RetracedSourceFile retracedSourceFile = sourceFile.get();
proxy.setSourceFile(
retracedSourceFile.hasRetraceResult()
? retracedSourceFile.getSourceFile()
: RetraceUtils.inferSourceFile(
classReference.getTypeName(), original.getSourceFile(), classResult.isEmpty()));
};
}
private RetraceStackTraceElementProxyResultImpl<T, ST> retraceFieldOrReturnType(
RetraceStackTraceElementProxyResultImpl<T, ST> currentResult, ST element) {
if (!element.hasFieldOrReturnType()) {
return currentResult;
}
RetraceStackTraceElementProxyResultImpl.Builder<T, ST> resultBuilder = currentResult.builder();
String elementOrReturnType = element.getFieldOrReturnType();
if (elementOrReturnType.equals("void")) {
return resultBuilder
.setResultStream(
currentResult.stream()
.map(
proxy ->
buildProxyForRewrittenReturnType(
proxy, RetracedTypeReferenceImpl.createVoid(), proxy.isAmbiguous())))
.build();
} else {
TypeReference typeReference = Reference.typeFromTypeName(elementOrReturnType);
RetraceTypeResult retraceTypeResult = retracer.retraceType(typeReference);
List<Element> retracedElements = retraceTypeResult.stream().collect(Collectors.toList());
return resultBuilder
.setResultStream(
currentResult.stream()
.flatMap(
proxy ->
retracedElements.stream()
.map(
retracedResult ->
buildProxyForRewrittenReturnType(
proxy,
retracedResult.getType(),
retraceTypeResult.isAmbiguous()))))
.build();
}
}
private RetraceStackTraceElementProxyImpl<T, ST> buildProxyForRewrittenReturnType(
RetraceStackTraceElementProxyImpl<T, ST> proxy,
RetracedTypeReference type,
boolean isAmbiguous) {
return proxy.builder().setRetracedFieldOrReturnType(type).joinAmbiguous(isAmbiguous).build();
}
private RetraceStackTraceElementProxyResultImpl<T, ST> retracedMethodArguments(
RetraceStackTraceElementProxyResultImpl<T, ST> currentResult, ST element) {
if (!element.hasMethodArguments()) {
return currentResult;
}
List<RetraceTypeResult> retracedResults =
Arrays.stream(element.getMethodArguments().split(","))
.map(typeName -> retracer.retraceType(Reference.typeFromTypeName(typeName)))
.collect(Collectors.toList());
List<List<RetracedTypeReference>> initial = new ArrayList<>();
initial.add(new ArrayList<>());
List<List<RetracedTypeReference>> allRetracedArguments =
ListUtils.fold(
retracedResults,
initial,
(acc, retracedTypeResult) -> {
List<List<RetracedTypeReference>> newResult = new ArrayList<>();
retracedTypeResult.forEach(
retracedElement ->
acc.forEach(
oldResult -> {
List<RetracedTypeReference> newList = new ArrayList<>(oldResult);
newList.add(retracedElement.getType());
newResult.add(newList);
}));
return newResult;
});
boolean isAmbiguous = allRetracedArguments.size() > 1;
return currentResult
.builder()
.setResultStream(
currentResult.stream()
.flatMap(
proxy ->
allRetracedArguments.stream()
.map(
retracedArguments ->
proxy
.builder()
.setRetracedMethodArguments(retracedArguments)
.joinAmbiguous(isAmbiguous)
.build())))
.build();
}
static class RetraceStackTraceElementProxyImpl<T, ST extends StackTraceElementProxy<T, ST>>
implements RetraceStackTraceElementProxy<T, ST> {
private final ST originalItem;
private final RetracedClassReference retracedClass;
private final RetracedMethodReference retracedMethod;
private final RetracedFieldReference retracedField;
private final RetracedTypeReference fieldOrReturnType;
private final List<RetracedTypeReference> methodArguments;
private final String sourceFile;
private final int lineNumber;
private final boolean isAmbiguous;
private final boolean isTopFrame;
private final RetraceStackTraceContext context;
private RetraceStackTraceElementProxyImpl(
ST originalItem,
RetracedClassReference retracedClass,
RetracedMethodReference retracedMethod,
RetracedFieldReference retracedField,
RetracedTypeReference fieldOrReturnType,
List<RetracedTypeReference> methodArguments,
String sourceFile,
int lineNumber,
boolean isAmbiguous,
boolean isTopFrame,
RetraceStackTraceContext context) {
assert originalItem != null;
this.originalItem = originalItem;
this.retracedClass = retracedClass;
this.retracedMethod = retracedMethod;
this.retracedField = retracedField;
this.fieldOrReturnType = fieldOrReturnType;
this.methodArguments = methodArguments;
this.sourceFile = sourceFile;
this.lineNumber = lineNumber;
this.isAmbiguous = isAmbiguous;
this.isTopFrame = isTopFrame;
this.context = context;
}
@Override
public boolean isAmbiguous() {
return isAmbiguous;
}
@Override
public boolean isTopFrame() {
return isTopFrame;
}
@Override
public boolean hasRetracedClass() {
return retracedClass != null;
}
@Override
public boolean hasRetracedMethod() {
return retracedMethod != null;
}
@Override
public boolean hasRetracedField() {
return retracedField != null;
}
@Override
public boolean hasSourceFile() {
return sourceFile != null;
}
@Override
public boolean hasLineNumber() {
return lineNumber != -1;
}
@Override
public boolean hasRetracedFieldOrReturnType() {
return fieldOrReturnType != null;
}
@Override
public boolean hasRetracedMethodArguments() {
return methodArguments != null;
}
@Override
public ST getOriginalItem() {
return originalItem;
}
@Override
public RetracedClassReference getRetracedClass() {
return retracedClass;
}
@Override
public RetracedMethodReference getRetracedMethod() {
return retracedMethod;
}
@Override
public RetracedFieldReference getRetracedField() {
return retracedField;
}
@Override
public RetracedTypeReference getRetracedFieldOrReturnType() {
return fieldOrReturnType;
}
@Override
public List<RetracedTypeReference> getRetracedMethodArguments() {
return methodArguments;
}
@Override
public String getSourceFile() {
return sourceFile;
}
private static <T, ST extends StackTraceElementProxy<T, ST>>
RetraceStackTraceElementProxyImpl<T, ST> create(
ST originalItem, RetraceStackTraceContext context) {
return new RetraceStackTraceElementProxyImpl<T, ST>(
originalItem, null, null, null, null, null, null, -1, false, false, context);
}
Builder<T, ST> builder() {
Builder<T, ST> builder = new Builder<>(originalItem);
builder.classContext = retracedClass;
builder.methodContext = retracedMethod;
builder.retracedField = retracedField;
builder.fieldOrReturnType = fieldOrReturnType;
builder.methodArguments = methodArguments;
builder.sourceFile = sourceFile;
builder.lineNumber = lineNumber;
builder.isAmbiguous = isAmbiguous;
builder.isTopFrame = isTopFrame;
builder.context = context;
return builder;
}
@Override
public int getLineNumber() {
return lineNumber;
}
@Override
public RetraceStackTraceContext getContext() {
return context;
}
@Override
public int compareTo(RetraceStackTraceElementProxy<T, ST> other) {
if (this == other) {
return 0;
}
int classCompare = Boolean.compare(hasRetracedClass(), other.hasRetracedClass());
if (classCompare != 0) {
return classCompare;
}
if (hasRetracedClass()) {
classCompare =
getRetracedClass().getTypeName().compareTo(other.getRetracedClass().getTypeName());
if (classCompare != 0) {
return classCompare;
}
}
int methodCompare = Boolean.compare(hasRetracedMethod(), other.hasRetracedMethod());
if (methodCompare != 0) {
return methodCompare;
}
if (hasRetracedMethod()) {
methodCompare = getRetracedMethod().compareTo(other.getRetracedMethod());
if (methodCompare != 0) {
return methodCompare;
}
}
int sourceFileCompare = Boolean.compare(hasSourceFile(), other.hasSourceFile());
if (sourceFileCompare != 0) {
return sourceFileCompare;
}
if (hasSourceFile()) {
sourceFileCompare = getSourceFile().compareTo(other.getSourceFile());
if (sourceFileCompare != 0) {
return sourceFileCompare;
}
}
int lineNumberCompare = Boolean.compare(hasLineNumber(), other.hasLineNumber());
if (lineNumberCompare != 0) {
return lineNumberCompare;
}
if (hasLineNumber()) {
return Integer.compare(lineNumber, other.getLineNumber());
}
return 0;
}
private static class Builder<T, ST extends StackTraceElementProxy<T, ST>> {
private final ST originalElement;
private RetracedClassReference classContext;
private RetracedMethodReference methodContext;
private RetracedFieldReference retracedField;
private RetracedTypeReference fieldOrReturnType;
private List<RetracedTypeReference> methodArguments;
private String sourceFile;
private int lineNumber = -1;
private boolean isAmbiguous;
private boolean isTopFrame;
private RetraceStackTraceContext context;
private Builder(ST originalElement) {
this.originalElement = originalElement;
}
private Builder<T, ST> setRetracedClass(RetracedClassReference retracedClass) {
this.classContext = retracedClass;
return this;
}
private Builder<T, ST> setRetracedMethod(RetracedMethodReference methodElement) {
this.methodContext = methodElement;
return this;
}
private Builder<T, ST> setRetracedField(RetracedFieldReference retracedField) {
this.retracedField = retracedField;
return this;
}
private Builder<T, ST> setRetracedFieldOrReturnType(RetracedTypeReference retracedType) {
this.fieldOrReturnType = retracedType;
return this;
}
private Builder<T, ST> setRetracedMethodArguments(List<RetracedTypeReference> arguments) {
this.methodArguments = arguments;
return this;
}
private Builder<T, ST> setSourceFile(String sourceFile) {
this.sourceFile = sourceFile;
return this;
}
private Builder<T, ST> setLineNumber(int lineNumber) {
this.lineNumber = lineNumber;
return this;
}
private Builder<T, ST> joinAmbiguous(boolean ambiguous) {
this.isAmbiguous = ambiguous || this.isAmbiguous;
return this;
}
private Builder<T, ST> setTopFrame(boolean topFrame) {
isTopFrame = topFrame;
return this;
}
private Builder<T, ST> setContext(RetraceStackTraceContext context) {
this.context = context;
return this;
}
private Builder<T, ST> apply(Consumer<Builder<T, ST>> consumer) {
consumer.accept(this);
return this;
}
private Builder<T, ST> applyIf(boolean condition, Consumer<Builder<T, ST>> consumer) {
if (condition) {
consumer.accept(this);
}
return this;
}
private RetraceStackTraceElementProxyImpl<T, ST> build() {
RetracedClassReference retracedClass = classContext;
if (methodContext != null) {
retracedClass = methodContext.getHolderClass();
}
return new RetraceStackTraceElementProxyImpl<>(
originalElement,
retracedClass,
methodContext,
retracedField,
fieldOrReturnType,
methodArguments,
sourceFile,
lineNumber,
isAmbiguous,
isTopFrame,
context);
}
}
}
}