Add matchers for asserting inline information
Before we would only match on the inline information provided by a
stack trace - or check if a method no longer existed in the output.
This CL add matchers around RetraceMethodResult, allowing one to
specify exactly the inline stack for an instruction.
Bug: 145903423
Change-Id: I022ebe32b1e0a186913d7369caf8c72ea21ac9ab
diff --git a/src/main/java/com/android/tools/r8/code/Instruction.java b/src/main/java/com/android/tools/r8/code/Instruction.java
index 1f95782..d549e28 100644
--- a/src/main/java/com/android/tools/r8/code/Instruction.java
+++ b/src/main/java/com/android/tools/r8/code/Instruction.java
@@ -179,6 +179,10 @@
return false;
}
+ public boolean isThrow() {
+ return false;
+ }
+
public int getPayloadOffset() {
return 0;
}
diff --git a/src/main/java/com/android/tools/r8/code/Throw.java b/src/main/java/com/android/tools/r8/code/Throw.java
index 247b475..40d69f1 100644
--- a/src/main/java/com/android/tools/r8/code/Throw.java
+++ b/src/main/java/com/android/tools/r8/code/Throw.java
@@ -43,4 +43,9 @@
public boolean canThrow() {
return true;
}
+
+ @Override
+ public boolean isThrow() {
+ return true;
+ }
}
diff --git a/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java b/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java
index 1a1d4ef..147c251 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java
@@ -419,6 +419,16 @@
}
}
+ public int getFirstLineNumberOfOriginalRange() {
+ if (originalRange == null) {
+ return 0;
+ } else if (originalRange instanceof Integer) {
+ return (int) originalRange;
+ } else {
+ return ((Range) originalRange).from;
+ }
+ }
+
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
diff --git a/src/main/java/com/android/tools/r8/retrace/Result.java b/src/main/java/com/android/tools/r8/retrace/Result.java
index 2940b12..12e92d3 100644
--- a/src/main/java/com/android/tools/r8/retrace/Result.java
+++ b/src/main/java/com/android/tools/r8/retrace/Result.java
@@ -6,9 +6,12 @@
import com.android.tools.r8.Keep;
import java.util.function.Consumer;
+import java.util.stream.Stream;
@Keep
public abstract class Result<R, RR extends Result<R, RR>> {
+ public abstract Stream<R> stream();
+
public abstract RR forEach(Consumer<R> resultConsumer);
}
diff --git a/src/main/java/com/android/tools/r8/retrace/Retrace.java b/src/main/java/com/android/tools/r8/retrace/Retrace.java
index 6a4c2e3..79d9721 100644
--- a/src/main/java/com/android/tools/r8/retrace/Retrace.java
+++ b/src/main/java/com/android/tools/r8/retrace/Retrace.java
@@ -116,7 +116,7 @@
try {
ClassNameMapper classNameMapper =
ClassNameMapper.mapperFromString(command.proguardMapProducer.get());
- RetraceBase retraceBase = new RetraceBaseImpl(classNameMapper);
+ RetraceBase retraceBase = RetraceBaseImpl.create(classNameMapper);
RetraceCommandLineResult result;
if (command.regularExpression != null) {
result =
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceBaseImpl.java b/src/main/java/com/android/tools/r8/retrace/RetraceBaseImpl.java
index c60cd91..9a68706 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetraceBaseImpl.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceBaseImpl.java
@@ -21,10 +21,14 @@
private final ClassNameMapper classNameMapper;
- RetraceBaseImpl(ClassNameMapper classNameMapper) {
+ private RetraceBaseImpl(ClassNameMapper classNameMapper) {
this.classNameMapper = classNameMapper;
}
+ public static RetraceBase create(ClassNameMapper classNameMapper) {
+ return new RetraceBaseImpl(classNameMapper);
+ }
+
@Override
public RetraceMethodResult retrace(MethodReference methodReference) {
return retrace(methodReference.getHolderClass()).lookupMethod(methodReference.getMethodName());
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceClassResult.java b/src/main/java/com/android/tools/r8/retrace/RetraceClassResult.java
index ee21fd2..32e533d 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetraceClassResult.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceClassResult.java
@@ -80,7 +80,7 @@
return mapper != null;
}
- Stream<Element> stream() {
+ public Stream<Element> stream() {
return Stream.of(
new Element(
this,
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceFieldResult.java b/src/main/java/com/android/tools/r8/retrace/RetraceFieldResult.java
index 733eec7..38eb7c9 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetraceFieldResult.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceFieldResult.java
@@ -48,7 +48,7 @@
return memberNamings.size() > 1;
}
- Stream<Element> stream() {
+ public Stream<Element> stream() {
if (!hasRetraceResult()) {
return Stream.of(
new Element(
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceMethodResult.java b/src/main/java/com/android/tools/r8/retrace/RetraceMethodResult.java
index 347c646..cb47139 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetraceMethodResult.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceMethodResult.java
@@ -90,7 +90,7 @@
classElement, new MappedRangesOfName(narrowedRanges), obfuscatedName);
}
- Stream<Element> stream() {
+ public Stream<Element> stream() {
if (!hasRetraceResult()) {
return Stream.of(new Element(this, classElement, getUnknownReference(), null));
}
@@ -160,5 +160,12 @@
public int getOriginalLineNumber(int linePosition) {
return mappedRange != null ? mappedRange.getOriginalLineNumber(linePosition) : linePosition;
}
+
+ public int getFirstLineNumberOfOriginalRange() {
+ if (mappedRange == null) {
+ return 0;
+ }
+ return mappedRange.getFirstLineNumberOfOriginalRange();
+ }
}
}
diff --git a/src/test/java/com/android/tools/r8/Collectors.java b/src/test/java/com/android/tools/r8/Collectors.java
new file mode 100644
index 0000000..0e05ce0
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/Collectors.java
@@ -0,0 +1,19 @@
+// 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;
+
+import java.util.stream.Collector;
+
+public abstract class Collectors {
+
+ public static <T> Collector<T, ?, T> toSingle() {
+ return java.util.stream.Collectors.collectingAndThen(
+ java.util.stream.Collectors.toList(),
+ items -> {
+ assert items.size() == 1;
+ return items.get(0);
+ });
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/R8TestRunResult.java b/src/test/java/com/android/tools/r8/R8TestRunResult.java
index 88a75bd..50eb3be 100644
--- a/src/test/java/com/android/tools/r8/R8TestRunResult.java
+++ b/src/test/java/com/android/tools/r8/R8TestRunResult.java
@@ -9,6 +9,7 @@
import com.android.tools.r8.ToolHelper.ProcessResult;
import com.android.tools.r8.naming.retrace.StackTrace;
import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.ThrowingBiConsumer;
import com.android.tools.r8.utils.ThrowingConsumer;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.graphinspector.GraphInspector;
@@ -69,12 +70,19 @@
return graphInspector.get();
}
- public R8TestRunResult inspectGraph(Consumer<GraphInspector> consumer)
+ public R8TestRunResult inspectGraph(Consumer<GraphInspector> consumer)
throws IOException, ExecutionException {
consumer.accept(graphInspector());
return self();
}
+ public <E extends Throwable> R8TestRunResult inspectStackTrace(
+ ThrowingBiConsumer<StackTrace, CodeInspector, E> consumer)
+ throws E, IOException, ExecutionException {
+ consumer.accept(getStackTrace(), new CodeInspector(app, proguardMap));
+ return self();
+ }
+
public String proguardMap() {
return proguardMap;
}
diff --git a/src/test/java/com/android/tools/r8/retrace/KotlinInlineFunctionRetraceTest.java b/src/test/java/com/android/tools/r8/retrace/KotlinInlineFunctionRetraceTest.java
index b469732..986bd3b 100644
--- a/src/test/java/com/android/tools/r8/retrace/KotlinInlineFunctionRetraceTest.java
+++ b/src/test/java/com/android/tools/r8/retrace/KotlinInlineFunctionRetraceTest.java
@@ -4,20 +4,29 @@
package com.android.tools.r8.retrace;
+import static com.android.tools.r8.Collectors.toSingle;
import static com.android.tools.r8.KotlinCompilerTool.KOTLINC;
import static com.android.tools.r8.ToolHelper.getFilesInTestFolderRelativeToClass;
+import static com.android.tools.r8.utils.codeinspector.Matchers.containsInlinePosition;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isInlineFrame;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isInlineStack;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.core.StringContains.containsString;
import com.android.tools.r8.CompilationFailedException;
import com.android.tools.r8.CompilationMode;
-import com.android.tools.r8.R8TestRunResult;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.TestRuntime;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
+import com.android.tools.r8.naming.retrace.StackTrace;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.Matchers.InlinePosition;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
import java.io.IOException;
import java.util.concurrent.ExecutionException;
import org.junit.Test;
@@ -64,156 +73,155 @@
public void testRetraceKotlinInlineStaticFunction()
throws ExecutionException, CompilationFailedException, IOException {
String main = "retrace.MainKt";
- R8TestRunResult result =
- testForR8(parameters.getBackend())
- .addProgramFiles(
- kotlinc(
- parameters.isCfRuntime()
- ? parameters.getRuntime().asCf()
- : TestRuntime.getCheckedInJdk9(),
- KOTLINC,
- KotlinTargetVersion.JAVA_8)
- .addSourceFiles(
- getFilesInTestFolderRelativeToClass(
- KotlinInlineFunctionRetraceTest.class, "kt", ".kt"))
- .compile())
- .addProgramFiles(ToolHelper.getKotlinStdlibJar())
- .addKeepAttributes("SourceFile", "LineNumberTable")
- .setMode(CompilationMode.RELEASE)
- .addKeepMainRule(main)
- .setMinApi(parameters.getApiLevel())
- .run(parameters.getRuntime(), main)
- .assertFailureWithErrorThatMatches(containsString("inlineExceptionStatic"))
- .inspectStackTrace(
- stackTrace -> {
- // TODO(b/b/145903423): Strengthen assertions by using retrace api directly.
- assertThat(
- stackTrace.get(0).originalLine,
- containsString(
- "at retrace.InlineFunctionKt.inlineExceptionStatic"
- + "(InlineFunctionKt.kt:8)"));
- assertThat(
- stackTrace.get(1).originalLine,
- containsString("at " + main + ".main(Main.kt:15)"));
- });
+ testForR8(parameters.getBackend())
+ .addProgramFiles(
+ kotlinc(
+ parameters.isCfRuntime()
+ ? parameters.getRuntime().asCf()
+ : TestRuntime.getCheckedInJdk9(),
+ KOTLINC,
+ KotlinTargetVersion.JAVA_8)
+ .addSourceFiles(
+ getFilesInTestFolderRelativeToClass(
+ KotlinInlineFunctionRetraceTest.class, "kt", ".kt"))
+ .compile())
+ .addProgramFiles(ToolHelper.getKotlinStdlibJar())
+ .addKeepAttributes("SourceFile", "LineNumberTable")
+ .setMode(CompilationMode.RELEASE)
+ .addKeepMainRule(main)
+ .setMinApi(parameters.getApiLevel())
+ .run(parameters.getRuntime(), main)
+ .assertFailureWithErrorThatMatches(containsString("inlineExceptionStatic"))
+ .inspectStackTrace(
+ (stackTrace, codeInspector) -> {
+ MethodSubject mainSubject = codeInspector.clazz(main).uniqueMethodWithName("main");
+ InlinePosition inlineStack =
+ InlinePosition.stack(
+ InlinePosition.create(
+ "retrace.InlineFunctionKt", "inlineExceptionStatic", 2, 8),
+ InlinePosition.create(mainSubject.asFoundMethodSubject(), 2, 15));
+ checkInlineInformation(stackTrace, codeInspector, mainSubject, inlineStack);
+ });
}
@Test
public void testRetraceKotlinInlineInstanceFunction()
throws ExecutionException, CompilationFailedException, IOException {
String main = "retrace.MainInstanceKt";
- R8TestRunResult result =
- testForR8(parameters.getBackend())
- .addProgramFiles(
- kotlinc(
- parameters.isCfRuntime()
- ? parameters.getRuntime().asCf()
- : TestRuntime.getCheckedInJdk9(),
- KOTLINC,
- KotlinTargetVersion.JAVA_8)
- .addSourceFiles(
- getFilesInTestFolderRelativeToClass(
- KotlinInlineFunctionRetraceTest.class, "kt", ".kt"))
- .compile())
- .addProgramFiles(ToolHelper.getKotlinStdlibJar())
- .addKeepAttributes("SourceFile", "LineNumberTable")
- .setMode(CompilationMode.RELEASE)
- .addKeepMainRule(main)
- .setMinApi(parameters.getApiLevel())
- .run(parameters.getRuntime(), main)
- .assertFailureWithErrorThatMatches(containsString("inlineExceptionInstance"))
- .inspectStackTrace(
- // TODO(b/b/145903423): Strengthen assertions by using retrace api directly.
- stackTrace -> {
- assertThat(
- stackTrace.get(0).originalLine,
- containsString(
- "at retrace.InlineFunction.inlineExceptionInstance"
- + "(InlineFunction.kt:15)"));
- assertThat(
- stackTrace.get(1).originalLine,
- containsString("at " + main + ".main(MainInstance.kt:13)"));
- });
+ testForR8(parameters.getBackend())
+ .addProgramFiles(
+ kotlinc(
+ parameters.isCfRuntime()
+ ? parameters.getRuntime().asCf()
+ : TestRuntime.getCheckedInJdk9(),
+ KOTLINC,
+ KotlinTargetVersion.JAVA_8)
+ .addSourceFiles(
+ getFilesInTestFolderRelativeToClass(
+ KotlinInlineFunctionRetraceTest.class, "kt", ".kt"))
+ .compile())
+ .addProgramFiles(ToolHelper.getKotlinStdlibJar())
+ .addKeepAttributes("SourceFile", "LineNumberTable")
+ .setMode(CompilationMode.RELEASE)
+ .addKeepMainRule(main)
+ .setMinApi(parameters.getApiLevel())
+ .run(parameters.getRuntime(), main)
+ .assertFailureWithErrorThatMatches(containsString("inlineExceptionInstance"))
+ .inspectStackTrace(
+ (stackTrace, codeInspector) -> {
+ MethodSubject mainSubject = codeInspector.clazz(main).uniqueMethodWithName("main");
+ InlinePosition inlineStack =
+ InlinePosition.stack(
+ InlinePosition.create(
+ "retrace.InlineFunction", "inlineExceptionInstance", 2, 15),
+ InlinePosition.create(mainSubject.asFoundMethodSubject(), 2, 13));
+ checkInlineInformation(stackTrace, codeInspector, mainSubject, inlineStack);
+ });
}
@Test
public void testRetraceKotlinNestedInlineFunction()
throws ExecutionException, CompilationFailedException, IOException {
String main = "retrace.MainNestedKt";
- R8TestRunResult result =
- testForR8(parameters.getBackend())
- .addProgramFiles(
- kotlinc(
- parameters.isCfRuntime()
- ? parameters.getRuntime().asCf()
- : TestRuntime.getCheckedInJdk9(),
- KOTLINC,
- KotlinTargetVersion.JAVA_8)
- .addSourceFiles(
- getFilesInTestFolderRelativeToClass(
- KotlinInlineFunctionRetraceTest.class, "kt", ".kt"))
- .compile())
- .addProgramFiles(ToolHelper.getKotlinStdlibJar())
- .addKeepAttributes("SourceFile", "LineNumberTable")
- .setMode(CompilationMode.RELEASE)
- .addKeepMainRule(main)
- .setMinApi(parameters.getApiLevel())
- .run(parameters.getRuntime(), main)
- .assertFailureWithErrorThatMatches(containsString("inlineExceptionStatic"))
- .inspectStackTrace(
- stackTrace -> {
- // TODO(b/b/145903423): Strengthen assertions by using retrace api directly.
- assertThat(
- stackTrace.get(0).originalLine,
- containsString(
- "at retrace.InlineFunctionKt.inlineExceptionStatic"
- + "(InlineFunctionKt.kt:8)"));
- assertThat(
- stackTrace.get(1).originalLine,
- containsString(
- "at retrace.NestedInlineFunctionKt.nestedInline"
- + "(NestedInlineFunctionKt.kt:10)"));
- assertThat(
- stackTrace.get(2).originalLine,
- containsString("at " + main + ".main(MainNested.kt:19)"));
- });
+ testForR8(parameters.getBackend())
+ .addProgramFiles(
+ kotlinc(
+ parameters.isCfRuntime()
+ ? parameters.getRuntime().asCf()
+ : TestRuntime.getCheckedInJdk9(),
+ KOTLINC,
+ KotlinTargetVersion.JAVA_8)
+ .addSourceFiles(
+ getFilesInTestFolderRelativeToClass(
+ KotlinInlineFunctionRetraceTest.class, "kt", ".kt"))
+ .compile())
+ .addProgramFiles(ToolHelper.getKotlinStdlibJar())
+ .addKeepAttributes("SourceFile", "LineNumberTable")
+ .setMode(CompilationMode.RELEASE)
+ .addKeepMainRule(main)
+ .setMinApi(parameters.getApiLevel())
+ .run(parameters.getRuntime(), main)
+ .assertFailureWithErrorThatMatches(containsString("inlineExceptionStatic"))
+ .inspectStackTrace(
+ (stackTrace, codeInspector) -> {
+ MethodSubject mainSubject = codeInspector.clazz(main).uniqueMethodWithName("main");
+ InlinePosition inlineStack =
+ InlinePosition.stack(
+ InlinePosition.create(
+ "retrace.InlineFunctionKt", "inlineExceptionStatic", 3, 8),
+ InlinePosition.create(
+ "retrace.NestedInlineFunctionKt", "nestedInline", 3, 10),
+ InlinePosition.create(mainSubject.asFoundMethodSubject(), 3, 19));
+ checkInlineInformation(stackTrace, codeInspector, mainSubject, inlineStack);
+ });
}
@Test
public void testRetraceKotlinNestedInlineFunctionOnFirstLine()
throws ExecutionException, CompilationFailedException, IOException {
String main = "retrace.MainNestedFirstLineKt";
- R8TestRunResult result =
- testForR8(parameters.getBackend())
- .addProgramFiles(
- kotlinc(TestRuntime.getCheckedInJdk8(), KOTLINC, KotlinTargetVersion.JAVA_8)
- .addSourceFiles(
- getFilesInTestFolderRelativeToClass(
- KotlinInlineFunctionRetraceTest.class, "kt", ".kt"))
- .compile())
- .addProgramFiles(ToolHelper.getKotlinStdlibJar())
- .addKeepAttributes("SourceFile", "LineNumberTable")
- .setMode(CompilationMode.RELEASE)
- .addKeepMainRule(main)
- .setMinApi(parameters.getApiLevel())
- .run(parameters.getRuntime(), main)
- .assertFailureWithErrorThatMatches(containsString("inlineExceptionStatic"))
- .inspectStackTrace(
- stackTrace -> {
- // TODO(b/b/145903423): Strengthen assertions by using retrace api directly.
- assertThat(
- stackTrace.get(0).originalLine,
- containsString(
- "at retrace.InlineFunctionKt.inlineExceptionStatic"
- + "(InlineFunctionKt.kt:8)"));
- assertThat(
- stackTrace.get(1).originalLine,
- containsString(
- "at retrace.NestedInlineFunctionKt.nestedInlineOnFirstLine"
- + "(NestedInlineFunctionKt.kt:15)"));
- assertThat(
- stackTrace.get(2).originalLine,
- containsString("at " + main + ".main(MainNestedFirstLine.kt:20)"));
- });
+ testForR8(parameters.getBackend())
+ .addProgramFiles(
+ kotlinc(TestRuntime.getCheckedInJdk8(), KOTLINC, KotlinTargetVersion.JAVA_8)
+ .addSourceFiles(
+ getFilesInTestFolderRelativeToClass(
+ KotlinInlineFunctionRetraceTest.class, "kt", ".kt"))
+ .compile())
+ .addProgramFiles(ToolHelper.getKotlinStdlibJar())
+ .addKeepAttributes("SourceFile", "LineNumberTable")
+ .setMode(CompilationMode.RELEASE)
+ .addKeepMainRule(main)
+ .setMinApi(parameters.getApiLevel())
+ .run(parameters.getRuntime(), main)
+ .assertFailureWithErrorThatMatches(containsString("inlineExceptionStatic"))
+ .inspectStackTrace(
+ (stackTrace, codeInspector) -> {
+ MethodSubject mainSubject = codeInspector.clazz(main).uniqueMethodWithName("main");
+ InlinePosition inlineStack =
+ InlinePosition.stack(
+ InlinePosition.create(
+ "retrace.InlineFunctionKt", "inlineExceptionStatic", 2, 8),
+ InlinePosition.create(
+ "retrace.NestedInlineFunctionKt", "nestedInlineOnFirstLine", 2, 15),
+ InlinePosition.create(mainSubject.asFoundMethodSubject(), 2, 20));
+ checkInlineInformation(stackTrace, codeInspector, mainSubject, inlineStack);
+ });
+ }
+
+ private void checkInlineInformation(
+ StackTrace stackTrace,
+ CodeInspector codeInspector,
+ MethodSubject mainSubject,
+ InlinePosition inlineStack) {
+ assertThat(mainSubject, isPresent());
+ RetraceMethodResult retraceResult =
+ mainSubject
+ .streamInstructions()
+ .filter(InstructionSubject::isThrow)
+ .collect(toSingle())
+ .retracePosition(codeInspector.retrace());
+ assertThat(retraceResult, isInlineFrame());
+ assertThat(retraceResult, isInlineStack(inlineStack));
+ assertThat(stackTrace, containsInlinePosition(inlineStack));
}
}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java b/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java
index 417e40e..1fd4abe 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java
@@ -6,7 +6,16 @@
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AccessFlags;
+import com.android.tools.r8.naming.retrace.StackTrace;
+import com.android.tools.r8.naming.retrace.StackTrace.StackTraceLine;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.retrace.RetraceMethodResult;
+import com.android.tools.r8.retrace.RetraceMethodResult.Element;
+import com.android.tools.r8.utils.Box;
+import com.android.tools.r8.utils.ListUtils;
import com.google.common.collect.ImmutableList;
+import java.util.List;
+import java.util.stream.Collectors;
import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.hamcrest.TypeSafeMatcher;
@@ -339,4 +348,189 @@
}
};
}
+
+ public static Matcher<RetraceMethodResult> isInlineFrame() {
+ return new TypeSafeMatcher<RetraceMethodResult>() {
+ @Override
+ protected boolean matchesSafely(RetraceMethodResult item) {
+ return !item.isAmbiguous() && item.stream().count() > 1;
+ }
+
+ @Override
+ public void describeTo(Description description) {
+ description.appendText("is not an inline frame");
+ }
+ };
+ }
+
+ public static Matcher<RetraceMethodResult> isInlineStack(InlinePosition startPosition) {
+ return new TypeSafeMatcher<RetraceMethodResult>() {
+ @Override
+ protected boolean matchesSafely(RetraceMethodResult item) {
+ Box<InlinePosition> currentPosition = new Box<>(startPosition);
+ Box<Boolean> returnValue = new Box<>();
+ item.forEach(
+ element -> {
+ boolean sameMethod;
+ InlinePosition currentInline = currentPosition.get();
+ if (currentInline == null) {
+ returnValue.set(false);
+ return;
+ }
+ if (currentInline.hasMethodSubject()) {
+ sameMethod =
+ element
+ .getMethodReference()
+ .equals(
+ currentInline.methodSubject.asFoundMethodSubject().asMethodReference());
+ } else {
+ MethodReference methodReference = element.getMethodReference();
+ sameMethod =
+ methodReference.getMethodName().equals(currentInline.methodName)
+ || methodReference
+ .getHolderClass()
+ .getTypeName()
+ .equals(currentInline.holder);
+ }
+ boolean samePosition =
+ element.getOriginalLineNumber(currentInline.minifiedPosition)
+ == currentInline.originalPosition;
+ if (!returnValue.isSet() || returnValue.get()) {
+ returnValue.set(sameMethod & samePosition);
+ }
+ currentPosition.set(currentInline.caller);
+ });
+ return returnValue.isSet() && returnValue.get();
+ }
+
+ @Override
+ public void describeTo(Description description) {
+ description.appendText("is not matching the inlining stack");
+ }
+ };
+ }
+
+ public static Matcher<RetraceMethodResult> isInlinedInto(InlinePosition inlinePosition) {
+ return new TypeSafeMatcher<RetraceMethodResult>() {
+ @Override
+ protected boolean matchesSafely(RetraceMethodResult item) {
+ if (item.isAmbiguous() || !inlinePosition.methodSubject.isPresent()) {
+ return false;
+ }
+ List<Element> references = item.stream().collect(Collectors.toList());
+ if (references.size() < 2) {
+ return false;
+ }
+ Element lastElement = ListUtils.last(references);
+ if (!lastElement
+ .getMethodReference()
+ .equals(inlinePosition.methodSubject.asFoundMethodSubject().asMethodReference())) {
+ return false;
+ }
+ return lastElement.getFirstLineNumberOfOriginalRange() == inlinePosition.originalPosition;
+ }
+
+ @Override
+ public void describeTo(Description description) {
+ description.appendText("is not inlined into " + inlinePosition.getMethodName());
+ }
+ };
+ }
+
+ public static Matcher<StackTrace> containsInlinePosition(InlinePosition inlinePosition) {
+ return new TypeSafeMatcher<StackTrace>() {
+ @Override
+ protected boolean matchesSafely(StackTrace item) {
+ return containsInlineStack(item, 0, inlinePosition);
+ }
+
+ @Override
+ public void describeTo(Description description) {
+ description.appendText("cannot be found in stack trace");
+ }
+
+ private boolean containsInlineStack(
+ StackTrace stackTrace, int index, InlinePosition currentPosition) {
+ if (currentPosition == null) {
+ return true;
+ }
+ if (index >= stackTrace.size()) {
+ return false;
+ }
+ StackTraceLine stackTraceLine = stackTrace.get(index);
+ boolean resultHere =
+ stackTraceLine.className.equals(currentPosition.getClassName())
+ && stackTraceLine.methodName.equals(currentPosition.getMethodName())
+ && stackTraceLine.lineNumber == currentPosition.originalPosition;
+ if (resultHere && containsInlineStack(stackTrace, index + 1, currentPosition.caller)) {
+ return true;
+ }
+ // Maybe the inline position starts from the top on the next position.
+ return containsInlineStack(stackTrace, index + 1, inlinePosition);
+ }
+ };
+ }
+
+ public static class InlinePosition {
+ private final FoundMethodSubject methodSubject;
+ private final String holder;
+ private final String methodName;
+ private final int minifiedPosition;
+ private final int originalPosition;
+
+ private InlinePosition caller;
+
+ private InlinePosition(
+ FoundMethodSubject methodSubject,
+ String holder,
+ String methodName,
+ int minifiedPosition,
+ int originalPosition) {
+ this.methodSubject = methodSubject;
+ this.holder = holder;
+ this.methodName = methodName;
+ this.minifiedPosition = minifiedPosition;
+ this.originalPosition = originalPosition;
+ assert methodSubject != null || holder != null;
+ assert methodSubject != null || methodName != null;
+ }
+
+ public static InlinePosition create(
+ FoundMethodSubject methodSubject, int minifiedPosition, int originalPosition) {
+ return new InlinePosition(methodSubject, null, null, minifiedPosition, originalPosition);
+ }
+
+ public static InlinePosition create(
+ String holder, String methodName, int minifiedPosition, int originalPosition) {
+ return new InlinePosition(null, holder, methodName, minifiedPosition, originalPosition);
+ }
+
+ public static InlinePosition stack(InlinePosition... stack) {
+ setCaller(1, stack);
+ return stack[0];
+ }
+
+ private static void setCaller(int index, InlinePosition... stack) {
+ assert index > 0;
+ if (index >= stack.length) {
+ return;
+ }
+ stack[index - 1].caller = stack[index];
+ setCaller(index + 1, stack);
+ }
+
+ boolean hasMethodSubject() {
+ return methodSubject != null;
+ }
+
+ String getMethodName() {
+ return hasMethodSubject() ? methodSubject.getOriginalName(false) : methodName;
+ }
+
+ String getClassName() {
+ return hasMethodSubject()
+ ? methodSubject.asMethodReference().getHolderClass().getTypeName()
+ : holder;
+ }
+ }
}