Replace receiver check null analysis and place when inlining
Bug: 206427323
Bug: 215339687
Bug: 214377135
Change-Id: I525453812223767a4105c42791f795facdcfac76
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 9fd0c74..5de6390 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
@@ -180,6 +180,16 @@
.build();
}
+ public Position replaceOutermostCallerPosition(Position newOutermostCallerPosition) {
+ if (!hasCallerPosition()) {
+ return newOutermostCallerPosition;
+ }
+ return builderWithCopy()
+ .setCallerPosition(
+ getCallerPosition().replaceOutermostCallerPosition(newOutermostCallerPosition))
+ .build();
+ }
+
@Override
public final boolean equals(Object other) {
return Equatable.equalsImpl(this, other);
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/MethodOptimizationFeedback.java b/src/main/java/com/android/tools/r8/ir/conversion/MethodOptimizationFeedback.java
index 8b229f9..0da54ff 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/MethodOptimizationFeedback.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/MethodOptimizationFeedback.java
@@ -54,8 +54,6 @@
void markAsPropagated(DexEncodedMethod method);
- void markCheckNullReceiverBeforeAnySideEffect(DexEncodedMethod method, boolean mark);
-
void markTriggerClassInitBeforeAnySideEffect(DexEncodedMethod method, boolean mark);
void setBridgeInfo(DexEncodedMethod method, BridgeInfo bridgeInfo);
@@ -87,8 +85,6 @@
void unsetBridgeInfo(DexEncodedMethod method);
- void unsetCheckNullReceiverBeforeAnySideEffect(ProgramMethod method);
-
void unsetClassInitializerMayBePostponed(ProgramMethod method);
void unsetClassInlinerMethodConstraint(ProgramMethod method);
@@ -131,7 +127,6 @@
if (method.getOptimizationInfo().isMutableOptimizationInfo()) {
unsetAbstractReturnValue(method);
unsetBridgeInfo(method.getDefinition());
- unsetCheckNullReceiverBeforeAnySideEffect(method);
unsetClassInitializerMayBePostponed(method);
unsetClassInlinerMethodConstraint(method);
unsetDynamicReturnType(method);
@@ -158,8 +153,5 @@
methodNeverReturnsNormally(method);
setUnusedArguments(
method, BitSetUtils.createFilled(true, method.getDefinition().getNumberOfArguments()));
- if (method.getDefinition().isInstance()) {
- markCheckNullReceiverBeforeAnySideEffect(method.getDefinition(), true);
- }
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
index 4e6bea5..a39688a 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
@@ -374,15 +374,9 @@
// receiver. Therefore, if the receiver may be null and the candidate inlinee does not
// throw if the receiver is null before any other side effect, then we must synthesize a
// null check.
- if (!singleTarget
- .getDefinition()
- .getOptimizationInfo()
- .checksNullReceiverBeforeAnySideEffect()) {
- if (!inlinerOptions.enableInliningOfInvokesWithNullableReceivers) {
- whyAreYouNotInliningReporter.reportReceiverMaybeNull();
- return null;
- }
- action.setShouldSynthesizeNullCheckForReceiver();
+ if (!inlinerOptions.enableInliningOfInvokesWithNullableReceivers) {
+ whyAreYouNotInliningReporter.reportReceiverMaybeNull();
+ return null;
}
}
return action;
@@ -484,9 +478,7 @@
ProgramMethod singleTarget,
InliningIRProvider inliningIRProvider,
WhyAreYouNotInliningReporter whyAreYouNotInliningReporter) {
- boolean removeInnerFramesIfThrowingNpe = false;
- IRCode inlinee =
- inliningIRProvider.getInliningIR(invoke, singleTarget, removeInnerFramesIfThrowingNpe);
+ IRCode inlinee = inliningIRProvider.getInliningIR(invoke, singleTarget);
// In the Java VM Specification section "4.10.2.4. Instance Initialization Methods and
// Newly Created Objects" it says:
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 785c461..834e78d 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
@@ -4,6 +4,7 @@
package com.android.tools.r8.ir.optimize;
import static com.android.tools.r8.ir.analysis.type.Nullability.definitelyNotNull;
+import static com.android.tools.r8.ir.optimize.SimpleDominatingEffectAnalysis.canInlineWithoutSynthesizingNullCheckForReceiver;
import static com.android.tools.r8.utils.MapUtils.ignoreKey;
import static com.google.common.base.Predicates.not;
@@ -51,6 +52,7 @@
import com.android.tools.r8.ir.conversion.LensCodeRewriter;
import com.android.tools.r8.ir.conversion.MethodProcessor;
import com.android.tools.r8.ir.conversion.PostMethodProcessor;
+import com.android.tools.r8.ir.optimize.SimpleDominatingEffectAnalysis.SimpleEffectAnalysisResult;
import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackIgnore;
import com.android.tools.r8.ir.optimize.inliner.DefaultInliningReasonStrategy;
@@ -515,7 +517,6 @@
final Reason reason;
private boolean shouldSynthesizeInitClass;
- private boolean shouldSynthesizeNullCheckForReceiver;
private DexProgramClass downcastClass;
@@ -539,15 +540,9 @@
}
void setShouldSynthesizeInitClass() {
- assert !shouldSynthesizeNullCheckForReceiver;
shouldSynthesizeInitClass = true;
}
- void setShouldSynthesizeNullCheckForReceiver() {
- assert !shouldSynthesizeInitClass;
- shouldSynthesizeNullCheckForReceiver = true;
- }
-
InlineeWithReason buildInliningIR(
AppView<? extends AppInfoWithClassHierarchy> appView,
InvokeMethod invoke,
@@ -558,12 +553,7 @@
InternalOptions options = appView.options();
// Build the IR for a yet not processed method, and perform minimal IR processing.
- boolean removeInnerFramesIfThrowingNpe =
- invoke.isInvokeMethodWithReceiver()
- && invoke.asInvokeMethodWithReceiver().getReceiver().isMaybeNull()
- && !shouldSynthesizeNullCheckForReceiver;
- IRCode code =
- inliningIRProvider.getInliningIR(invoke, target, removeInnerFramesIfThrowingNpe);
+ IRCode code = inliningIRProvider.getInliningIR(invoke, target);
// Insert a init class instruction if this is needed to preserve class initialization
// semantics.
@@ -582,11 +572,24 @@
target.getDefinition().isSynchronized() && options.isGeneratingClassFiles();
boolean isSynthesizingNullCheckForReceiverUsingMonitorEnter =
shouldSynthesizeMonitorEnterExit && !target.getDefinition().isStatic();
- if (shouldSynthesizeNullCheckForReceiver
+ if (invoke.isInvokeMethodWithReceiver()
+ && invoke.asInvokeMethodWithReceiver().getReceiver().isMaybeNull()
&& !isSynthesizingNullCheckForReceiverUsingMonitorEnter) {
- synthesizeNullCheckForReceiver(appView, code, invoke);
+ SimpleEffectAnalysisResult checksReceiverBeingNull =
+ canInlineWithoutSynthesizingNullCheckForReceiver(appView, code);
+ if (!checksReceiverBeingNull.hasResult()
+ || !checksReceiverBeingNull.isPartial()
+ || (checksReceiverBeingNull.isPartial()
+ && checksReceiverBeingNull.topMostNotSatisfiedBlockSize() > 1)) {
+ synthesizeNullCheckForReceiver(appView, code, invoke, code.entryBlock());
+ } else {
+ checksReceiverBeingNull.forEachSatisfyingInstruction(
+ this::setRemoveInnerFramePositionForReceiverUse);
+ // Also add a null check on failing paths
+ checksReceiverBeingNull.forEachTopMostNotSatisfiedBlock(
+ block -> synthesizeNullCheckForReceiver(appView, code, invoke, block));
+ }
}
-
// Insert monitor-enter and monitor-exit instructions if the method is synchronized.
if (shouldSynthesizeMonitorEnterExit) {
TypeElement throwableType =
@@ -727,18 +730,15 @@
}
private void synthesizeNullCheckForReceiver(
- AppView<?> appView, IRCode code, InvokeMethod invoke) {
+ AppView<?> appView, IRCode code, InvokeMethod invoke, BasicBlock block) {
List<Value> arguments = code.collectArguments();
if (!arguments.isEmpty()) {
Value receiver = arguments.get(0);
assert receiver.isThis();
-
- BasicBlock entryBlock = code.entryBlock();
-
// Insert a new block between the last argument instruction and the first actual
// instruction of the method.
BasicBlock throwBlock =
- entryBlock.listIterator(code, arguments.size()).split(code, 0, null);
+ block.listIterator(code, block.isEntry() ? arguments.size() : 0).split(code, 0, null);
assert !throwBlock.hasCatchHandlers();
InstructionListIterator iterator = throwBlock.listIterator(code);
@@ -754,6 +754,21 @@
assert false : "Unable to synthesize a null check for the receiver";
}
}
+
+ private void setRemoveInnerFramePositionForReceiverUse(Instruction instruction) {
+ Position position = instruction.getPosition();
+ if (position == null) {
+ assert false : "Expected position for inlinee call to receiver";
+ return;
+ }
+ Position removeInnerFrame =
+ position
+ .getOutermostCaller()
+ .builderWithCopy()
+ .setRemoveInnerFramesIfThrowingNpe(true)
+ .build();
+ instruction.forceOverwritePosition(position.replaceOutermostCallerPosition(removeInnerFrame));
+ }
}
public static class RetryAction extends InlineResult {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/SimpleDominatingEffectAnalysis.java b/src/main/java/com/android/tools/r8/ir/optimize/SimpleDominatingEffectAnalysis.java
new file mode 100644
index 0000000..8942395
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/SimpleDominatingEffectAnalysis.java
@@ -0,0 +1,324 @@
+// Copyright (c) 2022, 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.ir.optimize;
+
+import static com.android.tools.r8.ir.optimize.SimpleDominatingEffectAnalysis.InstructionEffect.NO_EFFECT;
+import static com.android.tools.r8.ir.optimize.SimpleDominatingEffectAnalysis.InstructionEffect.OTHER_EFFECT;
+import static com.android.tools.r8.utils.TraversalContinuation.BREAK;
+import static com.android.tools.r8.utils.TraversalContinuation.CONTINUE;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.utils.DepthFirstSearchWorkListBase.DFSNodeWithState;
+import com.android.tools.r8.utils.DepthFirstSearchWorkListBase.StatefulDepthFirstSearchWorkList;
+import com.android.tools.r8.utils.IntBox;
+import com.android.tools.r8.utils.TraversalContinuation;
+import com.google.common.collect.ImmutableList;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
+import java.util.function.Function;
+
+public class SimpleDominatingEffectAnalysis {
+
+ public enum InstructionEffect {
+ NO_EFFECT,
+ DESIRED_EFFECT,
+ OTHER_EFFECT;
+
+ public boolean isDesired() {
+ return this == DESIRED_EFFECT;
+ }
+
+ public boolean isOther() {
+ return this == OTHER_EFFECT;
+ }
+
+ public boolean isNoEffect() {
+ return this == NO_EFFECT;
+ }
+
+ public static InstructionEffect fromBoolean(boolean value) {
+ return value ? DESIRED_EFFECT : OTHER_EFFECT;
+ }
+
+ public ResultState toResultState() {
+ switch (this) {
+ case NO_EFFECT:
+ return ResultState.NOT_COMPUTED;
+ case DESIRED_EFFECT:
+ return ResultState.SATISFIED;
+ default:
+ assert isOther();
+ return ResultState.NOT_SATISFIED;
+ }
+ }
+ }
+
+ /**
+ * ResultState encodes a lattice on the following form: PARTIAL / \ SATISFIED NOT_SATISFIED \ /
+ * NOT_COMPUTED
+ *
+ * <p>PARTIAL results occur when have control flow where one or more branches are SATISFIED and
+ * one or more branches are NOT_SATISFIED.
+ */
+ private enum ResultState {
+ PARTIAL,
+ SATISFIED,
+ NOT_SATISFIED,
+ NOT_COMPUTED;
+
+ public boolean isPartial() {
+ return this == PARTIAL;
+ }
+
+ public boolean isSatisfied() {
+ return this == SATISFIED;
+ }
+
+ public boolean isNotSatisfied() {
+ return this == NOT_SATISFIED;
+ }
+
+ public boolean isNotComputed() {
+ return this == NOT_COMPUTED;
+ }
+
+ public ResultState join(ResultState other) {
+ if (isPartial() || other.isNotComputed()) {
+ return this;
+ }
+ if (isNotComputed() || other.isPartial()) {
+ return other;
+ }
+ if (this == other) {
+ return this;
+ }
+ return PARTIAL;
+ }
+ }
+
+ private static class ResultStateWithPartialBlocks {
+
+ private final ResultState state;
+ private final List<BasicBlock> failingBlocks;
+
+ private ResultStateWithPartialBlocks(ResultState state, List<BasicBlock> failingBlocks) {
+ this.state = state;
+ this.failingBlocks = failingBlocks;
+ }
+
+ public ResultStateWithPartialBlocks joinChildren(
+ List<DFSNodeWithState<BasicBlock, ResultStateWithPartialBlocks>> childNodes) {
+ assert state.isNotComputed();
+ ResultState newState =
+ childNodes.isEmpty() ? ResultState.NOT_SATISFIED : ResultState.NOT_COMPUTED;
+ for (DFSNodeWithState<BasicBlock, ResultStateWithPartialBlocks> childNode : childNodes) {
+ ResultStateWithPartialBlocks childState = childNode.getState();
+ assert !childState.state.isNotComputed();
+ newState = newState.join(childState.state);
+ }
+ assert !newState.isNotComputed();
+ List<BasicBlock> newFailingBlocks = new ArrayList<>();
+ if (newState.isPartial()) {
+ // Compute the initial basic blocks where that leads to OTHER_EFFECT.
+ for (DFSNodeWithState<BasicBlock, ResultStateWithPartialBlocks> childNode : childNodes) {
+ if (childNode.getState().state.isNotSatisfied()) {
+ newFailingBlocks.add(childNode.getNode());
+ } else if (childNode.getState().state.isPartial()) {
+ newFailingBlocks.addAll(childNode.getState().failingBlocks);
+ }
+ }
+ }
+ return new ResultStateWithPartialBlocks(newState, newFailingBlocks);
+ }
+ }
+
+ // The instruction analysis drives the SimpleDominatingEffectAnalysis by assigning an effect
+ // to a basic block.
+ public interface InstructionAnalysis {
+
+ // Analyse the instruction and assign and effect. If the desired effect is seen, it should
+ // dominate other users and DESIRED_EFFECT should be returned. If a violating effect is seen
+ // return OTHER_EFFECT. When an effect is seen there is no need to visit successor instructions
+ // in the block or successor blocks.
+ // If the instruction do not violate the effect, use NO_EFFECT. The analysis will, if the entire
+ // block is visited without any effect, visit successor blocks until an effect is found, the
+ // depth is violated or we see a return for which the result of this path will be notSatisfied.
+ InstructionEffect analyze(Instruction instruction);
+
+ // Return the successors of the block. The default is to only look at normal successors.
+ default List<BasicBlock> getSuccessors(BasicBlock block) {
+ return block.getNormalSuccessors();
+ }
+
+ // The max bound on instructions to consider before giving up.
+ default int maxNumberOfInstructions() {
+ return 100;
+ }
+ }
+
+ public static class SimpleEffectAnalysisResult {
+
+ private final List<Instruction> satisfyingInstructions;
+ private final List<BasicBlock> topmostNotSatisfiedBlocks;
+
+ private SimpleEffectAnalysisResult(
+ List<Instruction> satisfyingInstructions, List<BasicBlock> topmostNotSatisfiedBlocks) {
+
+ this.satisfyingInstructions = satisfyingInstructions;
+ this.topmostNotSatisfiedBlocks = topmostNotSatisfiedBlocks;
+ }
+
+ public boolean hasResult() {
+ return true;
+ }
+
+ public void forEachSatisfyingInstruction(Consumer<Instruction> instructionConsumer) {
+ satisfyingInstructions.forEach(instructionConsumer);
+ }
+
+ public void forEachTopMostNotSatisfiedBlock(Consumer<BasicBlock> blockConsumer) {
+ topmostNotSatisfiedBlocks.forEach(blockConsumer);
+ }
+
+ public int topMostNotSatisfiedBlockSize() {
+ return topmostNotSatisfiedBlocks.size();
+ }
+
+ public static SimpleEffectAnalysisResultBuilder builder() {
+ return new SimpleEffectAnalysisResultBuilder();
+ }
+
+ public boolean isPartial() {
+ return !satisfyingInstructions.isEmpty() && !topmostNotSatisfiedBlocks.isEmpty();
+ }
+ }
+
+ private static class SimpleEffectAnalysisResultBuilder {
+
+ List<Instruction> satisfyingInstructions = new ArrayList<>();
+ List<BasicBlock> failingBlocksForPartialResults = ImmutableList.of();
+ private boolean isFailed;
+
+ public void fail() {
+ isFailed = true;
+ }
+
+ public void addSatisfyingInstruction(Instruction instruction) {
+ satisfyingInstructions.add(instruction);
+ }
+
+ public void setFailingBlocksForPartialResults(List<BasicBlock> basicBlocks) {
+ this.failingBlocksForPartialResults = basicBlocks;
+ }
+
+ public SimpleEffectAnalysisResult build() {
+ return isFailed
+ ? NO_RESULT
+ : new SimpleEffectAnalysisResult(satisfyingInstructions, failingBlocksForPartialResults);
+ }
+ }
+
+ private static final SimpleEffectAnalysisResult NO_RESULT =
+ new SimpleEffectAnalysisResult(ImmutableList.of(), ImmutableList.of()) {
+ @Override
+ public boolean hasResult() {
+ return false;
+ }
+ };
+
+ public static SimpleEffectAnalysisResult run(IRCode code, InstructionAnalysis analysis) {
+ SimpleEffectAnalysisResultBuilder builder = SimpleEffectAnalysisResult.builder();
+ IntBox visitedInstructions = new IntBox();
+ new StatefulDepthFirstSearchWorkList<BasicBlock, ResultStateWithPartialBlocks>() {
+
+ @Override
+ protected TraversalContinuation process(
+ DFSNodeWithState<BasicBlock, ResultStateWithPartialBlocks> node,
+ Function<BasicBlock, DFSNodeWithState<BasicBlock, ResultStateWithPartialBlocks>>
+ childNodeConsumer) {
+ InstructionEffect effect = NO_EFFECT;
+ for (Instruction instruction : node.getNode().getInstructions()) {
+ if (visitedInstructions.getAndIncrement() > analysis.maxNumberOfInstructions()) {
+ builder.fail();
+ return BREAK;
+ }
+ effect = analysis.analyze(instruction);
+ if (!effect.isNoEffect()) {
+ if (effect.isDesired()) {
+ builder.addSatisfyingInstruction(instruction);
+ }
+ break;
+ }
+ }
+ if (effect.isNoEffect()) {
+ List<BasicBlock> successors = analysis.getSuccessors(node.getNode());
+ for (BasicBlock successor : successors) {
+ DFSNodeWithState<BasicBlock, ResultStateWithPartialBlocks> childNode =
+ childNodeConsumer.apply(successor);
+ if (childNode.hasState()) {
+ // If we see a block where the children have not been processed we cannot guarantee
+ // all paths having the effect since - ex. we could have a non-terminating loop.
+ builder.fail();
+ return BREAK;
+ }
+ }
+ }
+ node.setState(new ResultStateWithPartialBlocks(effect.toResultState(), ImmutableList.of()));
+ return CONTINUE;
+ }
+
+ @Override
+ protected TraversalContinuation joiner(
+ DFSNodeWithState<BasicBlock, ResultStateWithPartialBlocks> node,
+ List<DFSNodeWithState<BasicBlock, ResultStateWithPartialBlocks>> childNodes) {
+ ResultStateWithPartialBlocks resultState = node.getState();
+ if (resultState.state.isNotComputed()) {
+ resultState = resultState.joinChildren(childNodes);
+ } else {
+ assert resultState.state.isSatisfied() || resultState.state.isNotSatisfied();
+ assert childNodes.isEmpty();
+ }
+ node.setState(resultState);
+ if (node.getNode().isEntry()) {
+ builder.setFailingBlocksForPartialResults(resultState.failingBlocks);
+ }
+ return CONTINUE;
+ }
+ }.run(code.entryBlock());
+
+ return builder.build();
+ }
+
+ public static SimpleEffectAnalysisResult canInlineWithoutSynthesizingNullCheckForReceiver(
+ AppView<?> appView, IRCode code) {
+ assert code.context().getDefinition().isVirtualMethod();
+ Value receiver = code.getThis();
+ if (!receiver.isUsed()) {
+ return NO_RESULT;
+ }
+ ProgramMethod context = code.context();
+ return run(
+ code,
+ instruction -> {
+ if ((instruction.isInvokeMethodWithReceiver()
+ && instruction.asInvokeMethodWithReceiver().getReceiver() == receiver)
+ || (instruction.isInstanceFieldInstruction()
+ && instruction.asInstanceFieldInstruction().object() == receiver)
+ || (instruction.isMonitorEnter() && instruction.asMonitor().object() == receiver)) {
+ // Conservatively bailout if there are catch handlers.
+ return InstructionEffect.fromBoolean(!instruction.getBlock().hasCatchHandlers());
+ }
+ return instruction.instructionMayHaveSideEffects(appView, context)
+ ? OTHER_EFFECT
+ : NO_EFFECT;
+ });
+ }
+}
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 06e2b18..93b5d8f 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
@@ -75,7 +75,7 @@
// Not a direct inlinee.
continue;
}
- IRCode inliningIR = inliningIRProvider.getAndCacheInliningIR(invoke, inlinee, false);
+ IRCode inliningIR = inliningIRProvider.getAndCacheInliningIR(invoke, inlinee);
int increment =
inlinee.getDefinition().getCode().estimatedSizeForInlining()
- estimateSizeOfNonMaterializingInstructions(invoke, inliningIR);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationInfo.java
index 09b1cc3..95483ce 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationInfo.java
@@ -31,7 +31,6 @@
static int UNKNOWN_RETURNED_ARGUMENT = -1;
static boolean UNKNOWN_NEVER_RETURNS_NORMALLY = false;
static AbstractValue UNKNOWN_ABSTRACT_RETURN_VALUE = UnknownValue.getInstance();
- static boolean UNKNOWN_CHECKS_NULL_RECEIVER_BEFORE_ANY_SIDE_EFFECT = false;
static boolean UNKNOWN_TRIGGERS_CLASS_INIT_BEFORE_ANY_SIDE_EFFECT = false;
static boolean UNKNOWN_INITIALIZER_ENABLING_JAVA_ASSERTIONS = false;
static boolean UNKNOWN_MAY_HAVE_SIDE_EFFECTS = true;
@@ -167,11 +166,6 @@
}
@Override
- public boolean checksNullReceiverBeforeAnySideEffect() {
- return UNKNOWN_CHECKS_NULL_RECEIVER_BEFORE_ANY_SIDE_EFFECT;
- }
-
- @Override
public boolean triggersClassInitBeforeAnySideEffect() {
return UNKNOWN_TRIGGERS_CLASS_INIT_BEFORE_ANY_SIDE_EFFECT;
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfo.java
index a4667c9..17cffe5 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfo.java
@@ -88,8 +88,6 @@
public abstract boolean forceInline();
- public abstract boolean checksNullReceiverBeforeAnySideEffect();
-
public abstract boolean triggersClassInitBeforeAnySideEffect();
public abstract boolean mayHaveSideEffects();
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 a0c4eb2..acf0da6 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
@@ -542,11 +542,6 @@
// Identifies if the method preserves class initialization after inlining.
feedback.markTriggerClassInitBeforeAnySideEffect(
method, triggersClassInitializationBeforeSideEffect(code));
- } else {
- // Identifies if the method preserves null check of the receiver after inlining.
- Value receiver = code.getThis();
- feedback.markCheckNullReceiverBeforeAnySideEffect(
- method, receiver.isUsed() && checksNullBeforeSideEffect(code, receiver));
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MutableMethodOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MutableMethodOptimizationInfo.java
index 64bdb91..69f0497 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MutableMethodOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MutableMethodOptimizationInfo.java
@@ -89,7 +89,7 @@
private static final int UNUSED_FLAG_1 = 0x20;
private static final int NEVER_RETURNS_NORMALLY_FLAG = 0x40;
private static final int UNUSED_FLAG_2 = 0x80;
- private static final int CHECKS_NULL_RECEIVER_BEFORE_ANY_SIDE_EFFECT_FLAG = 0x100;
+ private static final int UNUSED_FLAG_3 = 0x100;
private static final int TRIGGERS_CLASS_INIT_BEFORE_ANY_SIDE_EFFECT_FLAG = 0x200;
private static final int INITIALIZER_ENABLING_JAVA_ASSERTIONS_FLAG = 0x400;
private static final int REACHABILITY_SENSITIVE_FLAG = 0x800;
@@ -116,9 +116,7 @@
defaultFlags |=
BooleanUtils.intValue(defaultOptInfo.neverReturnsNormally()) * NEVER_RETURNS_NORMALLY_FLAG;
defaultFlags |= 0 * UNUSED_FLAG_2;
- defaultFlags |=
- BooleanUtils.intValue(defaultOptInfo.checksNullReceiverBeforeAnySideEffect())
- * CHECKS_NULL_RECEIVER_BEFORE_ANY_SIDE_EFFECT_FLAG;
+ defaultFlags |= 0 * UNUSED_FLAG_3;
defaultFlags |=
BooleanUtils.intValue(defaultOptInfo.triggersClassInitBeforeAnySideEffect())
* TRIGGERS_CLASS_INIT_BEFORE_ANY_SIDE_EFFECT_FLAG;
@@ -488,11 +486,6 @@
}
@Override
- public boolean checksNullReceiverBeforeAnySideEffect() {
- return isFlagSet(CHECKS_NULL_RECEIVER_BEFORE_ANY_SIDE_EFFECT_FLAG);
- }
-
- @Override
public boolean triggersClassInitBeforeAnySideEffect() {
return isFlagSet(TRIGGERS_CLASS_INIT_BEFORE_ANY_SIDE_EFFECT_FLAG);
}
@@ -671,14 +664,6 @@
}
}
- void markCheckNullReceiverBeforeAnySideEffect(boolean mark) {
- setFlag(CHECKS_NULL_RECEIVER_BEFORE_ANY_SIDE_EFFECT_FLAG, mark);
- }
-
- void unsetCheckNullReceiverBeforeAnySideEffect() {
- clearFlag(CHECKS_NULL_RECEIVER_BEFORE_ANY_SIDE_EFFECT_FLAG);
- }
-
void markTriggerClassInitBeforeAnySideEffect(boolean mark) {
setFlag(TRIGGERS_CLASS_INIT_BEFORE_ANY_SIDE_EFFECT_FLAG, mark);
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java
index 5e9386c..4b6241a 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java
@@ -229,12 +229,6 @@
}
@Override
- public synchronized void markCheckNullReceiverBeforeAnySideEffect(
- DexEncodedMethod method, boolean mark) {
- getMethodOptimizationInfoForUpdating(method).markCheckNullReceiverBeforeAnySideEffect(mark);
- }
-
- @Override
public synchronized void markTriggerClassInitBeforeAnySideEffect(
DexEncodedMethod method, boolean mark) {
getMethodOptimizationInfoForUpdating(method).markTriggerClassInitBeforeAnySideEffect(mark);
@@ -311,11 +305,6 @@
}
@Override
- public synchronized void unsetCheckNullReceiverBeforeAnySideEffect(ProgramMethod method) {
- getMethodOptimizationInfoForUpdating(method).unsetCheckNullReceiverBeforeAnySideEffect();
- }
-
- @Override
public synchronized void unsetClassInitializerMayBePostponed(ProgramMethod method) {
getMethodOptimizationInfoForUpdating(method).unsetClassInitializerMayBePostponed();
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackIgnore.java b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackIgnore.java
index fd6d27e..ac66916 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackIgnore.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackIgnore.java
@@ -94,9 +94,6 @@
public void markProcessed(DexEncodedMethod method, ConstraintWithTarget state) {}
@Override
- public void markCheckNullReceiverBeforeAnySideEffect(DexEncodedMethod method, boolean mark) {}
-
- @Override
public void markTriggerClassInitBeforeAnySideEffect(DexEncodedMethod method, boolean mark) {}
@Override
@@ -143,9 +140,6 @@
public void unsetBridgeInfo(DexEncodedMethod method) {}
@Override
- public void unsetCheckNullReceiverBeforeAnySideEffect(ProgramMethod method) {}
-
- @Override
public void unsetClassInitializerMayBePostponed(ProgramMethod method) {}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java
index a763db6..5be58f0 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java
@@ -137,11 +137,6 @@
}
@Override
- public void markCheckNullReceiverBeforeAnySideEffect(DexEncodedMethod method, boolean mark) {
- method.getMutableOptimizationInfo().markCheckNullReceiverBeforeAnySideEffect(mark);
- }
-
- @Override
public void markTriggerClassInitBeforeAnySideEffect(DexEncodedMethod method, boolean mark) {
// Ignored.
}
@@ -234,12 +229,6 @@
}
@Override
- public void unsetCheckNullReceiverBeforeAnySideEffect(ProgramMethod method) {
- withMutableMethodOptimizationInfo(
- method, MutableMethodOptimizationInfo::unsetCheckNullReceiverBeforeAnySideEffect);
- }
-
- @Override
public void unsetClassInitializerMayBePostponed(ProgramMethod method) {
withMutableMethodOptimizationInfo(
method, MutableMethodOptimizationInfo::unsetClassInitializerMayBePostponed);
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 677ccdd..958b02d 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
@@ -43,14 +43,12 @@
public static InliningIRProvider getThrowingInstance() {
return new InliningIRProvider() {
@Override
- public IRCode getInliningIR(
- InvokeMethod invoke, ProgramMethod method, boolean removeInnerFramesIfNpe) {
+ public IRCode getInliningIR(InvokeMethod invoke, ProgramMethod method) {
throw new Unreachable();
}
@Override
- public IRCode getAndCacheInliningIR(
- InvokeMethod invoke, ProgramMethod method, boolean removeInnerFrameIfThrowingNpe) {
+ public IRCode getAndCacheInliningIR(InvokeMethod invoke, ProgramMethod method) {
throw new Unreachable();
}
@@ -76,24 +74,23 @@
};
}
- public IRCode getInliningIR(
- InvokeMethod invoke, ProgramMethod method, boolean removeInnerFramesIfNpe) {
+ public IRCode getInliningIR(InvokeMethod invoke, ProgramMethod method) {
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);
+ context,
+ appView,
+ valueNumberGenerator,
+ Position.getPositionForInlining(appView, invoke, context),
+ origin,
+ methodProcessor);
}
- public IRCode getAndCacheInliningIR(
- InvokeMethod invoke, ProgramMethod method, boolean removeInnerFrameIfThrowingNpe) {
- IRCode inliningIR = getInliningIR(invoke, method, removeInnerFrameIfThrowingNpe);
+ public IRCode getAndCacheInliningIR(InvokeMethod invoke, ProgramMethod method) {
+ IRCode inliningIR = getInliningIR(invoke, method);
cacheInliningIR(invoke, inliningIR);
return inliningIR;
}
diff --git a/src/main/java/com/android/tools/r8/utils/DepthFirstSearchWorkListBase.java b/src/main/java/com/android/tools/r8/utils/DepthFirstSearchWorkListBase.java
index 5900602..c82f332 100644
--- a/src/main/java/com/android/tools/r8/utils/DepthFirstSearchWorkListBase.java
+++ b/src/main/java/com/android/tools/r8/utils/DepthFirstSearchWorkListBase.java
@@ -31,6 +31,8 @@
S getState();
void setState(S backtrackState);
+
+ boolean hasState();
}
enum ProcessingState {
@@ -93,6 +95,11 @@
public void setState(S state) {
this.state = state;
}
+
+ @Override
+ public boolean hasState() {
+ return state != null;
+ }
}
private final ArrayDeque<T> workList = new ArrayDeque<>();
diff --git a/src/main/java/com/android/tools/r8/utils/WorkList.java b/src/main/java/com/android/tools/r8/utils/WorkList.java
index 70faab7..79c2ed7 100644
--- a/src/main/java/com/android/tools/r8/utils/WorkList.java
+++ b/src/main/java/com/android/tools/r8/utils/WorkList.java
@@ -92,6 +92,18 @@
return false;
}
+ public boolean addFirstIfNotSeen(T item) {
+ if (seen.add(item)) {
+ workingList.addFirst(item);
+ return true;
+ }
+ return false;
+ }
+
+ public void addFirstIgnoringSeenSet(T item) {
+ workingList.addFirst(item);
+ }
+
public boolean hasNext() {
return !workingList.isEmpty();
}
diff --git a/src/test/java/com/android/tools/r8/naming/retrace/RetraceInlineBranchTest.java b/src/test/java/com/android/tools/r8/naming/retrace/RetraceInlineBranchTest.java
new file mode 100644
index 0000000..92b6019
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/retrace/RetraceInlineBranchTest.java
@@ -0,0 +1,157 @@
+// Copyright (c) 2022, 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.naming.retrace.StackTrace.isSameExceptForLineNumbers;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.naming.retrace.StackTrace.StackTraceLine;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.stream.Collectors;
+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 RetraceInlineBranchTest extends TestBase {
+
+ @Parameter() public TestParameters parameters;
+
+ public StackTrace expectedStackTrace;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ @Before
+ public void setup() throws Exception {
+ // Get the expected stack trace by running on the JVM.
+ expectedStackTrace =
+ testForRuntime(parameters)
+ .addInnerClasses(getClass())
+ .run(parameters.getRuntime(), Main.class)
+ .getStackTrace();
+ }
+
+ @Test
+ public void testR8() throws Throwable {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .setMinApi(parameters.getApiLevel())
+ .addKeepMainRule(Main.class)
+ .addKeepAttributeLineNumberTable()
+ .enableNeverClassInliningAnnotations()
+ .enableInliningAnnotations()
+ .run(parameters.getRuntime(), Main.class)
+ .inspectStackTrace(
+ (stackTrace, inspector) -> {
+ if (canUseJavaUtilObjectsRequireNonNull(parameters)) {
+ StackTrace requireNonNullFrame =
+ StackTrace.builder().add(stackTrace.get(0)).build();
+ assertThat(
+ requireNonNullFrame,
+ isSameExceptForLineNumbers(
+ StackTrace.builder()
+ .add(
+ StackTraceLine.builder()
+ .setClassName(Objects.class.getTypeName())
+ .setMethodName("requireNonNull")
+ .setFileName("Objects.java")
+ .build())
+ .build()));
+
+ StackTrace stackTraceWithoutRequireNonNull =
+ StackTrace.builder().add(stackTrace).remove(0).build();
+ assertThat(stackTraceWithoutRequireNonNull, isSame(expectedStackTrace));
+ } else {
+ // To check that we inserted the check in the branch correctly we use a rudimentary
+ // line check by comparing that the getClass method comes later than the first if.
+ MethodSubject methodSubject =
+ inspector.clazz(Main.class).uniqueMethodWithName("call");
+ assertThat(methodSubject, isPresent());
+ List<InstructionSubject> getClassCalls =
+ methodSubject
+ .streamInstructions()
+ .filter(
+ instructionSubject ->
+ instructionSubject.isInvoke()
+ && instructionSubject
+ .getMethod()
+ .qualifiedName()
+ .contains("getClass"))
+ .collect(Collectors.toList());
+ assertEquals(1, getClassCalls.size());
+ Optional<InstructionSubject> firstIf =
+ methodSubject.streamInstructions().filter(InstructionSubject::isIf).findFirst();
+ assertTrue(firstIf.isPresent());
+ assertTrue(
+ methodSubject.getLineNumberForInstruction(getClassCalls.get(0))
+ > methodSubject.getLineNumberForInstruction(firstIf.get()));
+ assertThat(stackTrace, isSame(expectedStackTrace));
+ }
+ });
+ }
+
+ @NeverClassInline
+ static class Foo {
+
+ private int value;
+
+ @NeverInline
+ int getValue() {
+ return value;
+ }
+
+ @NeverInline
+ void setValue(int value) {
+ this.value = value;
+ }
+
+ void inlinable(int arg) {
+ String newValue;
+ if (arg > 0) {
+ newValue = getValue() + "";
+ } else {
+ if (arg < 0) {
+ setValue(arg);
+ newValue = "-1";
+ } else {
+ // When inlining this block this is the only path that needs a null-check.
+ newValue = "Hello World";
+ }
+ }
+ System.out.println(newValue);
+ }
+ }
+
+ static class Main {
+
+ @NeverInline
+ private static void call(Foo foo, String[] args) {
+ foo.inlinable(args.length);
+ }
+
+ public static void main(String[] args) {
+ call(args.length == 0 ? null : new Foo(), args);
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/retrace/RetraceInlineConditionTest.java b/src/test/java/com/android/tools/r8/naming/retrace/RetraceInlineConditionTest.java
index 15466f7..36921ce 100644
--- a/src/test/java/com/android/tools/r8/naming/retrace/RetraceInlineConditionTest.java
+++ b/src/test/java/com/android/tools/r8/naming/retrace/RetraceInlineConditionTest.java
@@ -4,13 +4,16 @@
package com.android.tools.r8.naming.retrace;
+import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.MatcherAssert.assertThat;
-import com.android.tools.r8.R8TestCompileResult;
+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.TestParametersCollection;
import org.hamcrest.CoreMatchers;
+import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -22,25 +25,39 @@
@Parameter() public TestParameters parameters;
+ public StackTrace expectedStackTrace;
+
@Parameters(name = "{0}")
public static TestParametersCollection data() {
return getTestParameters().withAllRuntimesAndApiLevels().build();
}
+ @Before
+ public void setup() throws Exception {
+ // Get the expected stack trace by running on the JVM.
+ TestBuilder<? extends SingleTestRunResult<?>, ?> testBuilder =
+ testForRuntime(parameters).addInnerClasses(getClass());
+ SingleTestRunResult<?> runResult = testBuilder.run(parameters.getRuntime(), Main.class);
+ if (parameters.isCfRuntime()) {
+ expectedStackTrace = runResult.map(StackTrace::extractFromJvm);
+ } else {
+ expectedStackTrace =
+ StackTrace.extractFromArt(runResult.getStdErr(), parameters.asDexRuntime().getVm());
+ }
+ }
+
@Test
public void testR8() throws Throwable {
- R8TestCompileResult compileResult =
- testForR8(parameters.getBackend())
- .addInnerClasses(getClass())
- .setMinApi(parameters.getApiLevel())
- .addKeepMainRule(Main.class)
- .compile()
- .inspectProguardMap(
- map -> {
- // TODO(b/215339687): We should not have a rewriteFrame in the mapping file since
- // an explicit null check should be inserted.
- assertThat(map, CoreMatchers.containsString("com.android.tools.r8.rewriteFrame"));
- });
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .setMinApi(parameters.getApiLevel())
+ .addKeepMainRule(Main.class)
+ .compile()
+ .inspectProguardMap(
+ map -> {
+ assertThat(
+ map, not(CoreMatchers.containsString("com.android.tools.r8.rewriteFrame")));
+ });
}
static class Foo {
diff --git a/src/test/java/com/android/tools/r8/naming/retrace/RetraceInlineeWithNullCheckFollowingImplicitReceiverNullCheckTest.java b/src/test/java/com/android/tools/r8/naming/retrace/RetraceInlineeWithNullCheckFollowingImplicitReceiverNullCheckTest.java
index b5cd101..15f867f 100644
--- a/src/test/java/com/android/tools/r8/naming/retrace/RetraceInlineeWithNullCheckFollowingImplicitReceiverNullCheckTest.java
+++ b/src/test/java/com/android/tools/r8/naming/retrace/RetraceInlineeWithNullCheckFollowingImplicitReceiverNullCheckTest.java
@@ -31,12 +31,10 @@
}
public StackTrace expectedStackTrace;
- public StackTrace unexpectedStackTrace;
@Before
public void setup() throws Exception {
expectedStackTrace = getStackTrace();
- unexpectedStackTrace = getStackTrace("foo");
}
private StackTrace getStackTrace(String... args) throws Exception {
@@ -62,9 +60,8 @@
.setMinApi(parameters.getApiLevel())
.run(parameters.getRuntime(), Caller.class)
.assertFailureWithErrorThatThrows(NullPointerException.class)
- // TODO(b/214377135): Should retrace to expectedStackTrace.
.inspectStackTrace(
- (stackTrace, codeInspector) -> assertThat(stackTrace, isSame(unexpectedStackTrace)));
+ (stackTrace, codeInspector) -> assertThat(stackTrace, isSame(expectedStackTrace)));
}
static class Foo {
diff --git a/src/test/java/com/android/tools/r8/naming/retrace/RetraceInlineeWithNullCheckInlinedTest.java b/src/test/java/com/android/tools/r8/naming/retrace/RetraceInlineeWithNullCheckInlinedTest.java
index 6143522..6b7b2d0 100644
--- a/src/test/java/com/android/tools/r8/naming/retrace/RetraceInlineeWithNullCheckInlinedTest.java
+++ b/src/test/java/com/android/tools/r8/naming/retrace/RetraceInlineeWithNullCheckInlinedTest.java
@@ -4,19 +4,21 @@
package com.android.tools.r8.naming.retrace;
import static com.android.tools.r8.naming.retrace.StackTrace.isSame;
+import static com.android.tools.r8.naming.retrace.StackTrace.isSameExceptForLineNumbers;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndRenamed;
-import static com.android.tools.r8.utils.codeinspector.Matchers.notIf;
import static org.hamcrest.MatcherAssert.assertThat;
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.NoMethodStaticizing;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.naming.retrace.StackTrace.StackTraceLine;
import com.android.tools.r8.utils.BooleanUtils;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.MethodSubject;
import java.util.List;
+import java.util.Objects;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -71,8 +73,28 @@
MethodSubject staticized = callerClass.uniqueMethodWithName("outerCaller");
assertThat(staticized, isPresentAndRenamed());
// TODO(b/214377135): The stack traces should be the same (when 206427323) is
- // resolved.
- assertThat(stackTrace, notIf(isSame(expectedStackTrace), throwReceiverNpe));
+ // resolved.
+ if (throwReceiverNpe && canUseJavaUtilObjectsRequireNonNull(parameters)) {
+ StackTrace requireNonNullFrame =
+ StackTrace.builder().add(stackTrace.get(0)).build();
+ assertThat(
+ requireNonNullFrame,
+ isSameExceptForLineNumbers(
+ StackTrace.builder()
+ .add(
+ StackTraceLine.builder()
+ .setClassName(Objects.class.getTypeName())
+ .setMethodName("requireNonNull")
+ .setFileName("Objects.java")
+ .build())
+ .build()));
+
+ StackTrace stackTraceWithoutRequireNonNull =
+ StackTrace.builder().add(stackTrace).remove(0).build();
+ assertThat(stackTraceWithoutRequireNonNull, isSame(expectedStackTrace));
+ } else {
+ assertThat(stackTrace, isSame(expectedStackTrace));
+ }
});
}
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
index 13e3e7f..70c2be4 100644
--- a/src/test/java/com/android/tools/r8/naming/retrace/RetraceInlineeWithNullCheckTest.java
+++ b/src/test/java/com/android/tools/r8/naming/retrace/RetraceInlineeWithNullCheckTest.java
@@ -8,11 +8,7 @@
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.naming.retrace.StackTrace.StackTraceLine;
import com.android.tools.r8.utils.BooleanUtils;
@@ -43,46 +39,36 @@
public StackTrace expectedStackTrace;
+ private String[] getArgs() {
+ return throwReceiverNpe ? new String[] {"foo"} : new String[0];
+ }
+
@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());
- }
+ // Get the expected stack trace by running on the JVM/ART.
+ expectedStackTrace =
+ testForRuntime(parameters)
+ .addProgramClasses(Caller.class, Foo.class)
+ .run(parameters.getRuntime(), Caller.class, getArgs())
+ .getStackTrace();
}
@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
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(Caller.class)
+ .addKeepAttributeLineNumberTable()
+ .addKeepAttributeSourceFile()
+ .setMinApi(parameters.getApiLevel())
+ .enableInliningAnnotations()
+ .enableExperimentalMapFileVersion()
+ .run(parameters.getRuntime(), Caller.class, getArgs())
.assertFailureWithErrorThatThrows(NullPointerException.class)
.inspectStackTrace(
(stackTrace, codeInspector) -> {
+ // TODO(b/214377135): The stack traces should be the same (when 206427323) is
+ // resolved.
if (throwReceiverNpe && canUseJavaUtilObjectsRequireNonNull(parameters)) {
StackTrace requireNonNullFrame =
StackTrace.builder().add(stackTrace.get(0)).build();
diff --git a/src/test/java/com/android/tools/r8/shaking/proxy/ProxiesTest.java b/src/test/java/com/android/tools/r8/shaking/proxy/ProxiesTest.java
index e7bb456..047a109 100644
--- a/src/test/java/com/android/tools/r8/shaking/proxy/ProxiesTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/proxy/ProxiesTest.java
@@ -65,7 +65,6 @@
.addOptionsModification(
o -> {
o.enableDevirtualization = false;
- o.inlinerOptions().enableInliningOfInvokesWithNullableReceivers = false;
})
.enableAlwaysInliningAnnotations()
.noMinification()
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/MethodSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/MethodSubject.java
index 6803e16..594b17a 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/MethodSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/MethodSubject.java
@@ -89,9 +89,9 @@
return Streams.stream(iterateTryCatches());
}
- public void getLineNumberForInstruction(InstructionSubject subject) {
+ public int getLineNumberForInstruction(InstructionSubject subject) {
assert hasLineNumberTable();
- getLineNumberTable().getLineForInstruction(subject);
+ return getLineNumberTable().getLineForInstruction(subject);
}
@Override