| // 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); |
| } |
| } |
| } |
| } |