Add removeFrame for inlineframes throwing NPE without check
Bug: 198267337
Change-Id: I88770dd31e9f3f062d0ae60c1e0807d71265ec24
diff --git a/src/main/java/com/android/tools/r8/ir/code/Position.java b/src/main/java/com/android/tools/r8/ir/code/Position.java
index 259c18b..cb2ff68 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Position.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Position.java
@@ -17,14 +17,15 @@
// A no-position marker. Not having a position means the position is implicitly defined by the
// context, e.g., the marker does not materialize anything concrete.
- private static final Position NO_POSITION = new Position(-1, null, null, null, false);
+ private static final Position NO_POSITION = new Position(-1, null, null, null, false, false);
// A synthetic marker position that should never materialize.
// This is used specifically to mark exceptional exit blocks from synchronized methods in release.
- private static final Position NO_POSITION_SYNTHETIC = new Position(-1, null, null, null, true);
+ private static final Position NO_POSITION_SYNTHETIC =
+ new Position(-1, null, null, null, true, false);
// Fake position to use for representing an actual position in testing code.
- private static final Position TESTING_POSITION = new Position(0, null, null, null, true);
+ private static final Position TESTING_POSITION = new Position(0, null, null, null, true, false);
public final int line;
public final DexString file;
@@ -37,6 +38,7 @@
public final DexMethod method;
public final Position callerPosition;
+ public final boolean removeInnerFrameIfThrowingNpe;
private static void specify(StructuralSpecification<Position, ?> spec) {
spec.withInt(p -> p.line)
@@ -47,19 +49,25 @@
}
private Position(
- int line, DexString file, DexMethod method, Position callerPosition, boolean synthetic) {
+ int line,
+ DexString file,
+ DexMethod method,
+ Position callerPosition,
+ boolean synthetic,
+ boolean removeInnerFrameIfThrowingNpe) {
this.line = line;
this.file = file;
this.synthetic = synthetic;
this.method = method;
this.callerPosition = callerPosition;
+ this.removeInnerFrameIfThrowingNpe = removeInnerFrameIfThrowingNpe;
assert callerPosition == null || callerPosition.method != null;
}
public static Position synthetic(int line, DexMethod method, Position callerPosition) {
assert line >= 0;
assert method != null;
- return new Position(line, null, method, callerPosition, true);
+ return new Position(line, null, method, callerPosition, true, false);
}
public static Position none() {
@@ -80,7 +88,7 @@
// it as the caller of the inlined Positions.
public static Position noneWithMethod(DexMethod method, Position callerPosition) {
assert method != null;
- return new Position(-1, null, method, callerPosition, false);
+ return new Position(-1, null, method, callerPosition, false, false);
}
public static Position getPositionForInlining(
@@ -151,12 +159,8 @@
@Override
public int hashCode() {
- int result = line;
- result = 31 * result + Objects.hashCode(file);
- result = 31 * result + (synthetic ? 1 : 0);
- result = 31 * result + Objects.hashCode(method);
- result = 31 * result + Objects.hashCode(callerPosition);
- return result;
+ return Objects.hash(
+ line, file, synthetic, method, callerPosition, removeInnerFrameIfThrowingNpe);
}
private String toString(boolean forceMethod) {
@@ -196,7 +200,8 @@
.setFile(file)
.setMethod(method)
.setCallerPosition(callerPosition)
- .setSynthetic(synthetic);
+ .setSynthetic(synthetic)
+ .setRemoveInnerFramesIfThrowingNpe(removeInnerFrameIfThrowingNpe);
}
public static class Builder {
@@ -206,6 +211,7 @@
public boolean synthetic;
public DexMethod method;
public Position callerPosition;
+ public boolean removeInnerFrameIfThrowingNpe;
public Builder setLine(int line) {
this.line = line;
@@ -232,10 +238,16 @@
return this;
}
+ public Builder setRemoveInnerFramesIfThrowingNpe(boolean removeInnerFramesIfThrowingNpe) {
+ this.removeInnerFrameIfThrowingNpe = removeInnerFramesIfThrowingNpe;
+ return this;
+ }
+
public Position build() {
assert line >= 0;
assert method != null;
- return new Position(line, file, method, callerPosition, synthetic);
+ return new Position(
+ line, file, method, callerPosition, synthetic, removeInnerFrameIfThrowingNpe);
}
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/Value.java b/src/main/java/com/android/tools/r8/ir/code/Value.java
index 7b97131..27713d8 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Value.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Value.java
@@ -476,6 +476,10 @@
return false;
}
+ public boolean isMaybeNull() {
+ return !isNeverNull();
+ }
+
public boolean usedInMonitorOperation() {
for (Instruction instruction : uniqueUsers()) {
if (instruction.isMonitor()) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
index 2f61964..abf7239 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
@@ -642,7 +642,12 @@
InternalOptions options = appView.options();
// Build the IR for a yet not processed method, and perform minimal IR processing.
- IRCode code = inliningIRProvider.getInliningIR(invoke, target);
+ boolean removeInnerFramesIfThrowingNpe =
+ invoke.isInvokeMethodWithReceiver()
+ && invoke.asInvokeMethodWithReceiver().getReceiver().isMaybeNull()
+ && !shouldSynthesizeNullCheckForReceiver;
+ IRCode code =
+ inliningIRProvider.getInliningIR(invoke, target, removeInnerFramesIfThrowingNpe);
// Insert a init class instruction if this is needed to preserve class initialization
// semantics.
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerCostAnalysis.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerCostAnalysis.java
index 33fa6fe..262f8be 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerCostAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerCostAnalysis.java
@@ -72,7 +72,7 @@
// Not a direct inlinee.
continue;
}
- IRCode inliningIR = inliningIRProvider.getAndCacheInliningIR(invoke, inlinee);
+ IRCode inliningIR = inliningIRProvider.getAndCacheInliningIR(invoke, inlinee, false);
int increment =
inlinee.getDefinition().getCode().estimatedSizeForInlining()
- estimateNumberOfNonMaterializingInstructions(invoke, inliningIR);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
index f93dc2a..a9c52e4 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
@@ -527,7 +527,7 @@
method, triggersClassInitializationBeforeSideEffect(code));
} else {
// Identifies if the method preserves null check of the receiver after inlining.
- final Value receiver = code.getThis();
+ Value receiver = code.getThis();
feedback.markCheckNullReceiverBeforeAnySideEffect(
method, receiver.isUsed() && checksNullBeforeSideEffect(code, receiver));
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/inliner/InliningIRProvider.java b/src/main/java/com/android/tools/r8/ir/optimize/inliner/InliningIRProvider.java
index f348ce7..1387311 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/inliner/InliningIRProvider.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/inliner/InliningIRProvider.java
@@ -32,19 +32,24 @@
this.methodProcessor = methodProcessor;
}
- public IRCode getInliningIR(InvokeMethod invoke, ProgramMethod method) {
+ public IRCode getInliningIR(
+ InvokeMethod invoke, ProgramMethod method, boolean removeInnerFramesIfNpe) {
IRCode cached = cache.remove(invoke);
if (cached != null) {
return cached;
}
Position position = Position.getPositionForInlining(appView, invoke, context);
+ if (removeInnerFramesIfNpe) {
+ position = position.builderWithCopy().setRemoveInnerFramesIfThrowingNpe(true).build();
+ }
Origin origin = method.getOrigin();
return method.buildInliningIR(
context, appView, valueNumberGenerator, position, origin, methodProcessor);
}
- public IRCode getAndCacheInliningIR(InvokeMethod invoke, ProgramMethod method) {
- IRCode inliningIR = getInliningIR(invoke, method);
+ public IRCode getAndCacheInliningIR(
+ InvokeMethod invoke, ProgramMethod method, boolean removeInnerFrameIfThrowingNpe) {
+ IRCode inliningIR = getInliningIR(invoke, method, removeInnerFrameIfThrowingNpe);
cacheInliningIR(invoke, inliningIR);
return inliningIR;
}
diff --git a/src/main/java/com/android/tools/r8/naming/mappinginformation/RewriteFrameMappingInformation.java b/src/main/java/com/android/tools/r8/naming/mappinginformation/RewriteFrameMappingInformation.java
index d9d8c4b..b27fe19 100644
--- a/src/main/java/com/android/tools/r8/naming/mappinginformation/RewriteFrameMappingInformation.java
+++ b/src/main/java/com/android/tools/r8/naming/mappinginformation/RewriteFrameMappingInformation.java
@@ -9,6 +9,8 @@
import com.android.tools.r8.errors.CompilationError;
import com.android.tools.r8.errors.Unimplemented;
import com.android.tools.r8.naming.MapVersion;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.Reference;
import com.android.tools.r8.retrace.internal.RetraceStackTraceContextImpl;
import com.android.tools.r8.retrace.internal.RetraceStackTraceCurrentEvaluationInformation;
import com.android.tools.r8.utils.DescriptorUtils;
@@ -18,6 +20,7 @@
import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;
import it.unimi.dsi.fastutil.ints.Int2IntMap;
+import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
@@ -102,9 +105,28 @@
return this;
}
- public static RewriteFrameMappingInformation create(
- List<Condition> conditions, List<RewriteAction> actions) {
- return new RewriteFrameMappingInformation(conditions, actions);
+ public static RewriteFrameMappingInformation.Builder builder() {
+ return new Builder();
+ }
+
+ public static class Builder {
+
+ private final List<Condition> conditions = new ArrayList<>();
+ private final List<RewriteAction> actions = new ArrayList<>();
+
+ public Builder addCondition(Condition condition) {
+ this.conditions.add(condition);
+ return this;
+ }
+
+ public Builder addRewriteAction(RewriteAction action) {
+ this.actions.add(action);
+ return this;
+ }
+
+ public RewriteFrameMappingInformation build() {
+ return new RewriteFrameMappingInformation(conditions, actions);
+ }
}
public abstract static class Condition {
@@ -140,15 +162,15 @@
static final String FUNCTION_NAME = "throws";
- private final String descriptor;
+ private final ClassReference classReference;
- private ThrowsCondition(String descriptor) {
- this.descriptor = descriptor;
+ private ThrowsCondition(ClassReference classReference) {
+ this.classReference = classReference;
}
@Override
protected JsonPrimitive serialize() {
- return new JsonPrimitive(FUNCTION_NAME + "(" + asThrowsCondition().getDescriptor() + ")");
+ return new JsonPrimitive(FUNCTION_NAME + "(" + classReference.getDescriptor() + ")");
}
@Override
@@ -156,10 +178,6 @@
return true;
}
- public String getDescriptor() {
- return descriptor;
- }
-
@Override
public ThrowsCondition asThrowsCondition() {
return this;
@@ -167,15 +185,18 @@
@Override
public boolean evaluate(RetraceStackTraceContextImpl context) {
- return context.getThrownException() != null
- && context.getThrownException().getDescriptor().equals(descriptor);
+ return classReference.equals(context.getThrownException());
}
public static ThrowsCondition deserialize(String conditionString) {
if (!DescriptorUtils.isClassDescriptor(conditionString)) {
throw new CompilationError("Unexpected throws-descriptor: " + conditionString);
}
- return new ThrowsCondition(conditionString);
+ return new ThrowsCondition(Reference.classFromDescriptor(conditionString));
+ }
+
+ public static ThrowsCondition create(ClassReference classReference) {
+ return new ThrowsCondition(classReference);
}
}
@@ -244,7 +265,7 @@
builder.incrementRemoveInnerFramesCount(numberOfFrames);
}
- static RemoveInnerFramesAction create(int numberOfFrames) {
+ public static RemoveInnerFramesAction create(int numberOfFrames) {
return new RemoveInnerFramesAction(numberOfFrames);
}
diff --git a/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java b/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
index 37b3626..2a4db34 100644
--- a/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
+++ b/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
@@ -53,6 +53,10 @@
import com.android.tools.r8.naming.mappinginformation.CompilerSynthesizedMappingInformation;
import com.android.tools.r8.naming.mappinginformation.FileNameInformation;
import com.android.tools.r8.naming.mappinginformation.MappingInformation;
+import com.android.tools.r8.naming.mappinginformation.RewriteFrameMappingInformation;
+import com.android.tools.r8.naming.mappinginformation.RewriteFrameMappingInformation.RemoveInnerFramesAction;
+import com.android.tools.r8.naming.mappinginformation.RewriteFrameMappingInformation.ThrowsCondition;
+import com.android.tools.r8.references.Reference;
import com.android.tools.r8.retrace.internal.RetraceUtils;
import com.android.tools.r8.shaking.KeepInfoCollection;
import com.android.tools.r8.utils.InternalOptions.LineNumberOptimization;
@@ -481,19 +485,16 @@
}
}
Range originalRange = new Range(firstPosition.originalLine, lastPosition.originalLine);
-
Range obfuscatedRange;
if (method.getCode().isDexCode()
&& method.getCode().asDexCode().getDebugInfo()
== DexDebugInfoForSingleLineMethod.getInstance()) {
assert firstPosition.originalLine == lastPosition.originalLine;
- method.getCode().asDexCode().setDebugInfo(null);
obfuscatedRange = new Range(0, MAX_LINE_NUMBER);
} else {
obfuscatedRange =
new Range(firstPosition.obfuscatedLine, lastPosition.obfuscatedLine);
}
-
ClassNaming.Builder classNamingBuilder = onDemandClassNamingBuilder.get();
MappedRange lastMappedRange =
classNamingBuilder.addMappedRange(
@@ -502,13 +503,26 @@
originalRange,
obfuscatedName);
Position caller = firstPosition.caller;
+ int inlineFramesCount = 0;
while (caller != null) {
+ inlineFramesCount += 1;
lastMappedRange =
classNamingBuilder.addMappedRange(
obfuscatedRange,
getOriginalMethodSignature.apply(caller.method),
new Range(Math.max(caller.line, 0)), // Prevent against "no-position".
obfuscatedName);
+ if (caller.removeInnerFrameIfThrowingNpe) {
+ lastMappedRange.addMappingInformation(
+ RewriteFrameMappingInformation.builder()
+ .addCondition(
+ ThrowsCondition.create(
+ Reference.classFromDescriptor(
+ appView.options().dexItemFactory().npeDescriptor.toString())))
+ .addRewriteAction(RemoveInnerFramesAction.create(inlineFramesCount))
+ .build(),
+ Unreachable::raise);
+ }
caller = caller.callerPosition;
}
for (MappingInformation info : methodMappingInfo) {
diff --git a/src/test/java/com/android/tools/r8/naming/retrace/DesugarLambdaRetraceTest.java b/src/test/java/com/android/tools/r8/naming/retrace/DesugarLambdaRetraceTest.java
index 7a4914a..59bc15d 100644
--- a/src/test/java/com/android/tools/r8/naming/retrace/DesugarLambdaRetraceTest.java
+++ b/src/test/java/com/android/tools/r8/naming/retrace/DesugarLambdaRetraceTest.java
@@ -58,21 +58,21 @@
private void checkIsSame(StackTrace actualStackTrace, StackTrace retracedStackTrace) {
// Even when SourceFile is present retrace replaces the file name in the stack trace.
assertThat(retracedStackTrace, isSame(expectedStackTrace));
- assertEquals(expectedActualStackTraceHeight(), actualStackTrace.size());
+ assertEquals(expectedActualStackTraceHeight(), actualStackTrace.getStackTraceLines().size());
}
private void checkIsSameExceptForFileName(
StackTrace actualStackTrace, StackTrace retracedStackTrace) {
// Even when SourceFile is present retrace replaces the file name in the stack trace.
assertThat(retracedStackTrace, isSameExceptForFileName(expectedStackTrace));
- assertEquals(expectedActualStackTraceHeight(), actualStackTrace.size());
+ assertEquals(expectedActualStackTraceHeight(), actualStackTrace.getStackTraceLines().size());
}
private void checkIsSameExceptForFileNameAndLineNumber(
StackTrace actualStackTrace, StackTrace retracedStackTrace) {
// Even when SourceFile is present retrace replaces the file name in the stack trace.
assertThat(retracedStackTrace, isSameExceptForFileNameAndLineNumber(expectedStackTrace));
- assertEquals(expectedActualStackTraceHeight(), actualStackTrace.size());
+ assertEquals(expectedActualStackTraceHeight(), actualStackTrace.getStackTraceLines().size());
}
@Test
diff --git a/src/test/java/com/android/tools/r8/naming/retrace/InliningRetraceTest.java b/src/test/java/com/android/tools/r8/naming/retrace/InliningRetraceTest.java
index be514ec..c3a07cd 100644
--- a/src/test/java/com/android/tools/r8/naming/retrace/InliningRetraceTest.java
+++ b/src/test/java/com/android/tools/r8/naming/retrace/InliningRetraceTest.java
@@ -52,7 +52,8 @@
ImmutableList.of("-keepattributes SourceFile,LineNumberTable"),
(StackTrace actualStackTrace, StackTrace retracedStackTrace) -> {
assertThat(retracedStackTrace, isSame(expectedStackTrace));
- assertEquals(expectedActualStackTraceHeight(), actualStackTrace.size());
+ assertEquals(
+ expectedActualStackTraceHeight(), actualStackTrace.getStackTraceLines().size());
});
}
@@ -64,7 +65,8 @@
ImmutableList.of("-keepattributes LineNumberTable"),
(StackTrace actualStackTrace, StackTrace retracedStackTrace) -> {
assertThat(retracedStackTrace, isSameExceptForFileName(expectedStackTrace));
- assertEquals(expectedActualStackTraceHeight(), actualStackTrace.size());
+ assertEquals(
+ expectedActualStackTraceHeight(), actualStackTrace.getStackTraceLines().size());
});
}
@@ -76,7 +78,8 @@
ImmutableList.of(),
(StackTrace actualStackTrace, StackTrace retracedStackTrace) -> {
assertThat(retracedStackTrace, isSameExceptForFileNameAndLineNumber(expectedStackTrace));
- assertEquals(expectedActualStackTraceHeight(), actualStackTrace.size());
+ assertEquals(
+ expectedActualStackTraceHeight(), actualStackTrace.getStackTraceLines().size());
});
}
diff --git a/src/test/java/com/android/tools/r8/naming/retrace/RetraceInlineeWithNoSuchMethodErrorTest.java b/src/test/java/com/android/tools/r8/naming/retrace/RetraceInlineeWithNoSuchMethodErrorTest.java
new file mode 100644
index 0000000..1c2f06d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/retrace/RetraceInlineeWithNoSuchMethodErrorTest.java
@@ -0,0 +1,130 @@
+// Copyright (c) 2021, 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.naming.retrace;
+
+import static com.android.tools.r8.naming.retrace.StackTrace.isSame;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.R8FullTestBuilder;
+import com.android.tools.r8.R8TestRunResult;
+import com.android.tools.r8.SingleTestRunResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestBuilder;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import java.util.List;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class RetraceInlineeWithNoSuchMethodErrorTest extends TestBase {
+
+ @Parameter() public TestParameters parameters;
+
+ @Parameter(1)
+ public boolean throwReceiverNpe;
+
+ @Parameters(name = "{0}, throwReceiverNpe: {1}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ getTestParameters().withAllRuntimesAndApiLevels().build(), BooleanUtils.values());
+ }
+
+ public StackTrace expectedStackTrace;
+
+ @Before
+ public void setup() throws Exception {
+ // Get the expected stack trace by running on the JVM.
+ TestBuilder<? extends SingleTestRunResult<?>, ?> testBuilder =
+ testForRuntime(parameters)
+ .addProgramClasses(Caller.class)
+ .addProgramClassFileData(getFoo());
+ SingleTestRunResult<?> runResult;
+ if (throwReceiverNpe) {
+ runResult =
+ testBuilder
+ .run(parameters.getRuntime(), Caller.class, "foo")
+ .assertFailureWithErrorThatThrows(NullPointerException.class);
+ } else {
+ runResult =
+ testBuilder
+ .run(parameters.getRuntime(), Caller.class)
+ .assertFailureWithErrorThatThrows(NoSuchMethodError.class);
+ }
+ if (parameters.isCfRuntime()) {
+ expectedStackTrace = runResult.map(StackTrace::extractFromJvm);
+ } else {
+ expectedStackTrace =
+ StackTrace.extractFromArt(runResult.getStdErr(), parameters.asDexRuntime().getVm());
+ }
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ R8FullTestBuilder r8FullTestBuilder =
+ testForR8(parameters.getBackend())
+ .addProgramClasses(Caller.class)
+ .addProgramClassFileData(getFoo())
+ .addKeepMainRule(Caller.class)
+ .addKeepAttributeLineNumberTable()
+ .addKeepAttributeSourceFile()
+ .setMinApi(parameters.getApiLevel())
+ .enableInliningAnnotations()
+ .enableExperimentalMapFileVersion();
+ R8TestRunResult runResult;
+ if (throwReceiverNpe) {
+ runResult =
+ r8FullTestBuilder
+ .run(parameters.getRuntime(), Caller.class, "foo")
+ .assertFailureWithErrorThatThrows(NullPointerException.class);
+ } else {
+ runResult =
+ r8FullTestBuilder
+ .run(parameters.getRuntime(), Caller.class)
+ .assertFailureWithErrorThatThrows(NoSuchMethodError.class);
+ }
+ runResult.inspectStackTrace(
+ (stackTrace, codeInspector) -> {
+ assertThat(stackTrace, isSame(expectedStackTrace));
+ ClassSubject fooClass = codeInspector.clazz(Foo.class);
+ assertThat(fooClass, isPresent());
+ // We are not inlining this because resolution fails
+ assertThat(fooClass.uniqueMethodWithName("inlinable"), isPresent());
+ });
+ }
+
+ private static byte[] getFoo() throws Exception {
+ return transformer(Foo.class).removeMethodsWithName("foo").transform();
+ }
+
+ public static class Foo {
+
+ /* Removed on input */
+ public Object foo() {
+ throw new RuntimeException("Will be removed");
+ }
+
+ Object inlinable() {
+ return foo();
+ }
+ }
+
+ static class Caller {
+ @NeverInline
+ static void caller(Foo f) {
+ f.inlinable();
+ }
+
+ public static void main(String[] args) {
+ caller(args.length == 0 ? new Foo() : null);
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/retrace/RetraceInlineeWithNullCheck.java b/src/test/java/com/android/tools/r8/naming/retrace/RetraceInlineeWithNullCheck.java
deleted file mode 100644
index 548025b..0000000
--- a/src/test/java/com/android/tools/r8/naming/retrace/RetraceInlineeWithNullCheck.java
+++ /dev/null
@@ -1,86 +0,0 @@
-// Copyright (c) 2021, 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.naming.retrace;
-
-import static com.android.tools.r8.naming.retrace.StackTrace.isSame;
-import static org.hamcrest.CoreMatchers.not;
-import static org.hamcrest.MatcherAssert.assertThat;
-
-import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.TestBase;
-import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestRuntime.CfRuntime;
-import java.util.List;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameter;
-import org.junit.runners.Parameterized.Parameters;
-
-@RunWith(Parameterized.class)
-public class RetraceInlineeWithNullCheck extends TestBase {
-
- @Parameter() public TestParameters parameters;
-
- @Parameters(name = "{0}")
- public static List<Object[]> data() {
- return buildParameters(
- getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build());
- }
-
- public StackTrace expectedStackTrace;
-
- @Before
- public void setup() throws Exception {
- // Get the expected stack trace by running on the JVM.
- expectedStackTrace =
- testForJvm()
- .addTestClasspath()
- .run(CfRuntime.getSystemRuntime(), Caller.class)
- .assertFailure()
- .map(StackTrace::extractFromJvm);
- }
-
- @Test
- public void testR8() throws Exception {
- testForR8(parameters.getBackend())
- .addInnerClasses(getClass())
- .addKeepMainRule(Caller.class)
- .addKeepAttributeLineNumberTable()
- .addKeepAttributeSourceFile()
- .setMinApi(parameters.getApiLevel())
- .enableInliningAnnotations()
- .run(parameters.getRuntime(), Caller.class)
- .assertFailureWithErrorThatThrows(NullPointerException.class)
- // TODO(b/197936862): The two should be the same
- .inspectStackTrace(
- (stackTrace, codeInspector) -> {
- assertThat(stackTrace, not(isSame(expectedStackTrace)));
- });
- }
-
- static class Foo {
- @NeverInline
- Object notInlinable() {
- System.out.println("Hello, world!");
- throw new RuntimeException("Foo");
- }
-
- Object inlinable() {
- return notInlinable();
- }
- }
-
- static class Caller {
- @NeverInline
- static void caller(Foo f) {
- f.inlinable();
- }
-
- public static void main(String[] args) {
- caller(System.currentTimeMillis() < 0 ? new Foo() : null);
- }
- }
-}
diff --git a/src/test/java/com/android/tools/r8/naming/retrace/RetraceInlineeWithNullCheckSequence.java b/src/test/java/com/android/tools/r8/naming/retrace/RetraceInlineeWithNullCheckSequenceTest.java
similarity index 75%
rename from src/test/java/com/android/tools/r8/naming/retrace/RetraceInlineeWithNullCheckSequence.java
rename to src/test/java/com/android/tools/r8/naming/retrace/RetraceInlineeWithNullCheckSequenceTest.java
index f7f7b72..7c0f956 100644
--- a/src/test/java/com/android/tools/r8/naming/retrace/RetraceInlineeWithNullCheckSequence.java
+++ b/src/test/java/com/android/tools/r8/naming/retrace/RetraceInlineeWithNullCheckSequenceTest.java
@@ -4,13 +4,12 @@
package com.android.tools.r8.naming.retrace;
import static com.android.tools.r8.naming.retrace.StackTrace.isSame;
-import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.MatcherAssert.assertThat;
import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.SingleTestRunResult;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestRuntime.CfRuntime;
import java.util.List;
import org.junit.Before;
import org.junit.Test;
@@ -20,7 +19,7 @@
import org.junit.runners.Parameterized.Parameters;
@RunWith(Parameterized.class)
-public class RetraceInlineeWithNullCheckSequence extends TestBase {
+public class RetraceInlineeWithNullCheckSequenceTest extends TestBase {
@Parameter() public TestParameters parameters;
@@ -35,12 +34,17 @@
@Before
public void setup() throws Exception {
// Get the expected stack trace by running on the JVM.
- expectedStackTrace =
- testForJvm()
- .addTestClasspath()
- .run(CfRuntime.getSystemRuntime(), Caller.class)
- .assertFailure()
- .map(StackTrace::extractFromJvm);
+ SingleTestRunResult<?> runResult =
+ testForRuntime(parameters)
+ .addProgramClasses(Caller.class, Foo.class)
+ .run(parameters.getRuntime(), Caller.class)
+ .assertFailureWithErrorThatThrows(NullPointerException.class);
+ if (parameters.isCfRuntime()) {
+ expectedStackTrace = runResult.map(StackTrace::extractFromJvm);
+ } else {
+ expectedStackTrace =
+ StackTrace.extractFromArt(runResult.getStdErr(), parameters.asDexRuntime().getVm());
+ }
}
@Test
@@ -52,16 +56,17 @@
.addKeepAttributeSourceFile()
.setMinApi(parameters.getApiLevel())
.enableInliningAnnotations()
+ .enableExperimentalMapFileVersion()
.run(parameters.getRuntime(), Caller.class)
.assertFailureWithErrorThatThrows(NullPointerException.class)
.inspectStackTrace(
(stackTrace, codeInspector) -> {
- // TODO(b/197936862): The two stacktraces should be the same
- assertThat(stackTrace, not(isSame(expectedStackTrace)));
+ assertThat(stackTrace, isSame(expectedStackTrace));
});
}
static class Foo {
+
@NeverInline
void notInlinable() {
System.out.println("Hello, world!");
diff --git a/src/test/java/com/android/tools/r8/naming/retrace/RetraceInlineeWithNullCheckTest.java b/src/test/java/com/android/tools/r8/naming/retrace/RetraceInlineeWithNullCheckTest.java
new file mode 100644
index 0000000..81c2200
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/retrace/RetraceInlineeWithNullCheckTest.java
@@ -0,0 +1,109 @@
+// Copyright (c) 2021, 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.naming.retrace;
+
+import static com.android.tools.r8.naming.retrace.StackTrace.isSame;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.R8FullTestBuilder;
+import com.android.tools.r8.R8TestRunResult;
+import com.android.tools.r8.SingleTestRunResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestBuilder;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.BooleanUtils;
+import java.util.List;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class RetraceInlineeWithNullCheckTest extends TestBase {
+
+ @Parameter(0)
+ public TestParameters parameters;
+
+ @Parameter(1)
+ public boolean throwReceiverNpe;
+
+ @Parameters(name = "{0}, throwReceiverNpe: {1}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build(),
+ BooleanUtils.values());
+ }
+
+ public StackTrace expectedStackTrace;
+
+ @Before
+ public void setup() throws Exception {
+ // Get the expected stack trace by running on the JVM.
+ TestBuilder<? extends SingleTestRunResult<?>, ?> testBuilder =
+ testForRuntime(parameters).addProgramClasses(Caller.class, Foo.class);
+ SingleTestRunResult<?> runResult;
+ if (throwReceiverNpe) {
+ runResult = testBuilder.run(parameters.getRuntime(), Caller.class, "foo");
+ } else {
+ runResult = testBuilder.run(parameters.getRuntime(), Caller.class);
+ }
+ if (parameters.isCfRuntime()) {
+ expectedStackTrace = runResult.map(StackTrace::extractFromJvm);
+ } else {
+ expectedStackTrace =
+ StackTrace.extractFromArt(runResult.getStdErr(), parameters.asDexRuntime().getVm());
+ }
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ R8FullTestBuilder r8FullTestBuilder =
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(Caller.class)
+ .addKeepAttributeLineNumberTable()
+ .addKeepAttributeSourceFile()
+ .setMinApi(parameters.getApiLevel())
+ .enableInliningAnnotations()
+ .enableExperimentalMapFileVersion();
+ R8TestRunResult runResult;
+ if (throwReceiverNpe) {
+ runResult = r8FullTestBuilder.run(parameters.getRuntime(), Caller.class, "foo");
+ } else {
+ runResult = r8FullTestBuilder.run(parameters.getRuntime(), Caller.class);
+ }
+ runResult
+ .assertFailureWithErrorThatThrows(NullPointerException.class)
+ .inspectStackTrace(
+ (stackTrace, codeInspector) -> {
+ assertThat(stackTrace, isSame(expectedStackTrace));
+ });
+ }
+
+ static class Foo {
+ @NeverInline
+ Object notInlinable() {
+ System.out.println("Hello, world!");
+ throw new NullPointerException("Foo");
+ }
+
+ Object inlinable() {
+ return notInlinable();
+ }
+ }
+
+ static class Caller {
+ @NeverInline
+ static void caller(Foo f) {
+ f.inlinable();
+ }
+
+ public static void main(String[] args) {
+ caller(args.length == 0 ? new Foo() : null);
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/retrace/StackTrace.java b/src/test/java/com/android/tools/r8/naming/retrace/StackTrace.java
index 8dde075..c4d6dad 100644
--- a/src/test/java/com/android/tools/r8/naming/retrace/StackTrace.java
+++ b/src/test/java/com/android/tools/r8/naming/retrace/StackTrace.java
@@ -14,11 +14,13 @@
import com.android.tools.r8.retrace.ProguardMapProducer;
import com.android.tools.r8.retrace.RetraceCommand;
import com.android.tools.r8.retrace.RetraceHelper;
+import com.android.tools.r8.utils.Box;
import com.android.tools.r8.utils.StringUtils;
import com.google.common.base.Equivalence;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
+import java.util.Optional;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
@@ -35,6 +37,7 @@
public static class Builder {
+ private String exceptionLine;
private List<StackTraceLine> stackTraceLines = new ArrayList<>();
private Builder() {}
@@ -80,8 +83,14 @@
return this;
}
+ public Builder setExceptionLine(String exceptionLine) {
+ this.exceptionLine = exceptionLine;
+ return this;
+ }
+
public StackTrace build() {
return new StackTrace(
+ exceptionLine,
stackTraceLines,
StringUtils.join(
"\n",
@@ -224,16 +233,19 @@
}
}
+ private final String exceptionLine;
private final List<StackTraceLine> stackTraceLines;
private final String originalStderr;
- private StackTrace(List<StackTraceLine> stackTraceLines, String originalStderr) {
+ private StackTrace(
+ String exceptionLine, List<StackTraceLine> stackTraceLines, String originalStderr) {
+ this.exceptionLine = exceptionLine;
this.stackTraceLines = stackTraceLines;
this.originalStderr = originalStderr;
}
public int size() {
- return stackTraceLines.size();
+ return stackTraceLines.size() + 1;
}
public StackTraceLine get(int index) {
@@ -272,6 +284,7 @@
// \tat com.android.tools.r8.naming.retrace.Main.a(:150)
// \tat com.android.tools.r8.naming.retrace.Main.a(:156)
// \tat com.android.tools.r8.naming.retrace.Main.main(:162)
+ Optional<String> exceptionLine = Optional.empty();
for (int i = 0; i < stderrLines.size(); i++) {
String line = stderrLines.get(i);
// Find all lines starting with "\tat" except "dalvik.system.NativeStart.main" frame
@@ -279,23 +292,27 @@
if (line.startsWith(TAB_AT_PREFIX)
&& !(vm.isOlderThanOrEqual(DexVm.ART_4_4_4_HOST)
&& line.contains("dalvik.system.NativeStart.main"))) {
+ if (!exceptionLine.isPresent() && i > 0) {
+ exceptionLine = Optional.of(stderrLines.get(i - 1));
+ }
stackTraceLines.add(StackTraceLine.parse(stderrLines.get(i)));
}
}
- return new StackTrace(stackTraceLines, stderr);
+ return new StackTrace(exceptionLine.orElse(""), stackTraceLines, stderr);
}
private static List<StackTraceLine> internalConvert(Stream<String> lines) {
return lines.map(StackTraceLine::parse).collect(Collectors.toList());
}
- private static List<StackTraceLine> internalExtractFromJvm(String stderr) {
- return internalConvert(
- StringUtils.splitLines(stderr).stream().filter(s -> s.startsWith(TAB_AT_PREFIX)));
+ private static List<StackTraceLine> internalExtractFromJvm(List<String> strings) {
+ return internalConvert(strings.stream().filter(s -> s.startsWith(TAB_AT_PREFIX)));
}
public static StackTrace extractFromJvm(String stderr) {
- return new StackTrace(internalExtractFromJvm(stderr), stderr);
+ List<String> strings = StringUtils.splitLines(stderr);
+ return new StackTrace(
+ strings.isEmpty() ? "" : strings.get(0), internalExtractFromJvm(strings), stderr);
}
public static StackTrace extractFromJvm(SingleTestRunResult result) {
@@ -312,27 +329,27 @@
}
public StackTrace retrace(String map, boolean allowExperimentalMapping) {
- class Box {
- List<String> result;
- }
- Box box = new Box();
+ Box<List<String>> box = new Box<>();
+ List<String> stackTrace =
+ stackTraceLines.stream().map(line -> line.originalLine).collect(Collectors.toList());
+ stackTrace.add(0, exceptionLine);
RetraceHelper.runForTesting(
RetraceCommand.builder()
.setProguardMapProducer(ProguardMapProducer.fromString(map))
- .setStackTrace(
- stackTraceLines.stream()
- .map(line -> line.originalLine)
- .collect(Collectors.toList()))
- .setRetracedStackTraceConsumer(retraced -> box.result = retraced)
+ .setStackTrace(stackTrace)
+ .setRetracedStackTraceConsumer(box::set)
.build(),
allowExperimentalMapping);
// Keep the original stderr in the retraced stacktrace.
- return new StackTrace(internalConvert(box.result.stream()), originalStderr);
+ return new StackTrace(
+ box.get().get(0), internalConvert(box.get().stream().skip(1)), originalStderr);
}
public StackTrace filter(Predicate<StackTraceLine> filter) {
return new StackTrace(
- stackTraceLines.stream().filter(filter).collect(Collectors.toList()), originalStderr);
+ exceptionLine,
+ stackTraceLines.stream().filter(filter).collect(Collectors.toList()),
+ originalStderr);
}
@Override
diff --git a/src/test/java/com/android/tools/r8/naming/retrace/StackTraceTest.java b/src/test/java/com/android/tools/r8/naming/retrace/StackTraceTest.java
index 16d31bb..f866398 100644
--- a/src/test/java/com/android/tools/r8/naming/retrace/StackTraceTest.java
+++ b/src/test/java/com/android/tools/r8/naming/retrace/StackTraceTest.java
@@ -51,7 +51,8 @@
}
private void checkOneLine(StackTrace stackTrace) {
- assertEquals(1, stackTrace.size());
+ assertEquals(2, stackTrace.size());
+ assertEquals(1, stackTrace.getStackTraceLines().size());
StackTraceLine stackTraceLine = stackTrace.get(0);
assertEquals("Test", stackTraceLine.className);
assertEquals("main", stackTraceLine.methodName);
diff --git a/src/test/java/com/android/tools/r8/naming/retrace/VerticalClassMergingRetraceTest.java b/src/test/java/com/android/tools/r8/naming/retrace/VerticalClassMergingRetraceTest.java
index 0482945..fc1e432 100644
--- a/src/test/java/com/android/tools/r8/naming/retrace/VerticalClassMergingRetraceTest.java
+++ b/src/test/java/com/android/tools/r8/naming/retrace/VerticalClassMergingRetraceTest.java
@@ -86,7 +86,8 @@
? retracedStackTrace
: retracedStackTrace.filter(this::filterSynthesizedMethodWhenLineNumberAvailable);
assertThat(reprocessedStackTrace, isSameExceptForFileName(expectedStackTrace));
- assertEquals(expectedActualStackTraceHeight(), actualStackTrace.size());
+ assertEquals(
+ expectedActualStackTraceHeight(), actualStackTrace.getStackTraceLines().size());
});
}
@@ -102,7 +103,8 @@
? retracedStackTrace
: retracedStackTrace.filter(this::filterSynthesizedMethodWhenLineNumberAvailable);
assertThat(reprocessedStackTrace, isSameExceptForFileName(expectedStackTrace));
- assertEquals(expectedActualStackTraceHeight(), actualStackTrace.size());
+ assertEquals(
+ expectedActualStackTraceHeight(), actualStackTrace.getStackTraceLines().size());
});
}
@@ -144,7 +146,8 @@
: retracedStackTrace.filter(this::filterSynthesizedBridgeMethod);
assertThat(
reprocessedStackTrace, isSameExceptForFileNameAndLineNumber(expectedStackTrace));
- assertEquals(expectedActualStackTraceHeight(), actualStackTrace.size());
+ assertEquals(
+ expectedActualStackTraceHeight(), actualStackTrace.getStackTraceLines().size());
});
}
}