Synthesize null checks to allow inlining when the receiver is nullable
Bug: 130202534, 113859358
Change-Id: I1ed3ca3e1e8d8b0c42c654abf9583f985fce1e7f
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
index 85bc4cb..5c2ad3a 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -517,6 +517,12 @@
return this;
}
+ public IRCode buildEmptyThrowingIRCode(AppView<? extends AppInfo> appView, Origin origin) {
+ DexCode emptyThrowingDexCode = buildEmptyThrowingDexCode();
+ emptyThrowingDexCode.setOwner(this);
+ return emptyThrowingDexCode.buildIR(this, appView, origin);
+ }
+
/**
* Generates a {@link DexCode} object for the given instructions.
* <p>
diff --git a/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionIterator.java b/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionIterator.java
index 63e83d7..f13f95c 100644
--- a/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionIterator.java
@@ -13,6 +13,7 @@
import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
import com.android.tools.r8.ir.code.Phi.RegisterReadType;
+import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.IteratorUtils;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
@@ -183,6 +184,15 @@
}
@Override
+ public Value insertConstNullInstruction(IRCode code, InternalOptions options) {
+ ConstNumber constNumberInstruction = code.createConstNull();
+ // Note that we only keep position info for throwing instructions in release mode.
+ constNumberInstruction.setPosition(options.debug ? current.getPosition() : Position.none());
+ add(constNumberInstruction);
+ return constNumberInstruction.outValue();
+ }
+
+ @Override
public void replaceCurrentInstructionWithThrowNull(
AppView<? extends AppInfoWithSubtyping> appView,
IRCode code,
@@ -205,15 +215,11 @@
// Insert constant null before the instruction.
previous();
- ConstNumber constNumberInstruction = code.createConstNull();
- // Note that we only keep position info for throwing instructions in release mode.
- constNumberInstruction.setPosition(
- appView.options().debug ? current.getPosition() : Position.none());
- add(constNumberInstruction);
+ Value nullValue = insertConstNullInstruction(code, appView.options());
next();
// Replace the instruction by throw.
- Throw throwInstruction = new Throw(constNumberInstruction.outValue());
+ Throw throwInstruction = new Throw(nullValue);
for (Value inValue : current.inValues()) {
if (inValue.hasLocalInfo()) {
// Add this value as a debug value to avoid changing its live range.
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java
index 8186ca2..cd2aaae 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java
@@ -8,6 +8,7 @@
import com.android.tools.r8.graph.AppInfoWithSubtyping;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.utils.InternalOptions;
import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;
@@ -50,6 +51,8 @@
// Intentionally empty.
}
+ Value insertConstNullInstruction(IRCode code, InternalOptions options);
+
/**
* Replace the current instruction with null throwing instructions.
*
diff --git a/src/main/java/com/android/tools/r8/ir/code/LinearFlowInstructionIterator.java b/src/main/java/com/android/tools/r8/ir/code/LinearFlowInstructionIterator.java
index 12de1ce..9055045 100644
--- a/src/main/java/com/android/tools/r8/ir/code/LinearFlowInstructionIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/LinearFlowInstructionIterator.java
@@ -8,6 +8,7 @@
import com.android.tools.r8.graph.AppInfoWithSubtyping;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.utils.InternalOptions;
import java.util.List;
import java.util.ListIterator;
import java.util.Set;
@@ -38,6 +39,11 @@
}
@Override
+ public Value insertConstNullInstruction(IRCode code, InternalOptions options) {
+ return currentBlockIterator.insertConstNullInstruction(code, options);
+ }
+
+ @Override
public void replaceCurrentInstructionWithThrowNull(
AppView<? extends AppInfoWithSubtyping> appView,
IRCode code,
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 eb19549..ec51998 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
@@ -290,20 +290,6 @@
return null;
}
- // We can only inline an instance method call if we preserve the null check semantic (which
- // would throw NullPointerException if the receiver is null). Therefore we can inline only if
- // one of the following conditions is true:
- // * the candidate inlinee checks null receiver before any side effect
- // * the receiver is known to be non-null
- if (invoke.getReceiver().getTypeLattice().isNullable()
- && !candidate.getOptimizationInfo().checksNullReceiverBeforeAnySideEffect()) {
- if (info != null) {
- info.exclude(invoke, "receiver for candidate can be null");
- }
- assert !inliner.appView.appInfo().forceInline.contains(candidate.method);
- return null;
- }
-
Reason reason = computeInliningReason(candidate);
if (!candidate.isInliningCandidate(method, reason, inliner.appView.appInfo())) {
// Abort inlining attempt if the single target is not an inlining candidate.
@@ -320,7 +306,25 @@
if (info != null) {
info.include(invoke.getType(), candidate);
}
- return new InlineAction(candidate, invoke, reason);
+
+ InlineAction action = new InlineAction(candidate, invoke, reason);
+
+ if (invoke.getReceiver().getTypeLattice().isDefinitelyNull()) {
+ action.setShouldReturnEmptyThrowingCode();
+ } else {
+ // When inlining an instance method call, we need to preserve the null check for the 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 (invoke.getReceiver().getTypeLattice().isNullable()
+ && !candidate.getOptimizationInfo().checksNullReceiverBeforeAnySideEffect()) {
+ if (!appView.options().enableInliningOfInvokesWithNullableReceivers) {
+ return null;
+ }
+ action.setShouldSynthesizeNullCheckForReceiver();
+ }
+ }
+
+ return action;
}
@Override
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 bf8b690..a08892f 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
@@ -17,12 +17,14 @@
import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis;
import com.android.tools.r8.ir.code.BasicBlock;
import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.If;
import com.android.tools.r8.ir.code.Instruction;
import com.android.tools.r8.ir.code.InstructionIterator;
import com.android.tools.r8.ir.code.InstructionListIterator;
import com.android.tools.r8.ir.code.Invoke;
import com.android.tools.r8.ir.code.InvokeMethod;
import com.android.tools.r8.ir.code.Position;
+import com.android.tools.r8.ir.code.Throw;
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.code.ValueNumberGenerator;
import com.android.tools.r8.ir.conversion.CallSiteInformation;
@@ -407,22 +409,76 @@
public final Invoke invoke;
final Reason reason;
+ private boolean shouldReturnEmptyThrowingCode;
+ private boolean shouldSynthesizeNullCheckForReceiver;
+
InlineAction(DexEncodedMethod target, Invoke invoke, Reason reason) {
this.target = target;
this.invoke = invoke;
this.reason = reason;
}
+ void setShouldReturnEmptyThrowingCode() {
+ shouldReturnEmptyThrowingCode = true;
+ }
+
+ void setShouldSynthesizeNullCheckForReceiver() {
+ shouldSynthesizeNullCheckForReceiver = true;
+ }
+
public InlineeWithReason buildInliningIR(
DexEncodedMethod context,
ValueNumberGenerator generator,
AppView<? extends AppInfoWithSubtyping> appView,
Position callerPosition) {
- // Build the IR for a yet not processed method, and perform minimal IR processing.
Origin origin = appView.appInfo().originFor(target.method.holder);
- IRCode code = target.buildInliningIR(context, appView, generator, callerPosition, origin);
- if (!target.isProcessed()) {
- new LensCodeRewriter(appView).rewrite(code, target);
+
+ IRCode code;
+ if (shouldReturnEmptyThrowingCode) {
+ code = target.buildEmptyThrowingIRCode(appView, origin);
+ } else {
+ // Build the IR for a yet not processed method, and perform minimal IR processing.
+ code = target.buildInliningIR(context, appView, generator, callerPosition, origin);
+
+ // Insert a null check if this is needed to preserve the implicit null check for the
+ // receiver.
+ if (shouldSynthesizeNullCheckForReceiver) {
+ 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(arguments.size()).split(code, 0, null);
+ assert !throwBlock.hasCatchHandlers();
+
+ // Link the entry block to the successor of the newly inserted block.
+ BasicBlock continuationBlock = throwBlock.unlinkSingleSuccessor();
+ entryBlock.link(continuationBlock);
+
+ // Replace the last instruction of the entry block, which is now a goto instruction,
+ // with an `if-eqz` instruction that jumps to the newly inserted block if the receiver
+ // is null.
+ If ifInstruction = new If(If.Type.EQ, receiver);
+ entryBlock.replaceLastInstruction(ifInstruction);
+ assert ifInstruction.getTrueTarget() == throwBlock;
+ assert ifInstruction.fallthroughBlock() == continuationBlock;
+
+ // Replace the single goto instruction in the newly inserted block by `throw null`.
+ InstructionListIterator iterator = throwBlock.listIterator();
+ Value nullValue = iterator.insertConstNullInstruction(code, appView.options());
+ iterator.next();
+ iterator.replaceCurrentInstruction(new Throw(nullValue));
+ } else {
+ assert false : "Unable to synthesize a null check for the receiver";
+ }
+ }
+ if (!target.isProcessed()) {
+ new LensCodeRewriter(appView).rewrite(code, target);
+ }
}
return new InlineeWithReason(code, reason);
}
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index 2deea8a..8df2e21 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -138,6 +138,7 @@
public boolean enableNonNullTracking = true;
public boolean enableInlining =
!Version.isDev() || System.getProperty("com.android.tools.r8.disableinlining") == null;
+ public boolean enableInliningOfInvokesWithNullableReceivers = true;
public boolean enableClassInlining = true;
public boolean enableClassStaticizer = true;
public boolean enableInitializedClassesAnalysis = true;
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index 8cf70a9..0ad4f8e 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -1065,10 +1065,10 @@
}
public static Path runtimeJar(TestParameters parameters) {
- if (parameters.getBackend() == Backend.DEX) {
+ if (parameters.isDexRuntime()) {
return ToolHelper.getAndroidJar(parameters.getRuntime().asDex().getMinApiLevel());
} else {
- assert parameters.getBackend() == Backend.CF;
+ assert parameters.isCfRuntime();
return ToolHelper.getJava8RuntimeJar();
}
}
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/type/MissingClassesJoinTest.java b/src/test/java/com/android/tools/r8/ir/analysis/type/MissingClassesJoinTest.java
index 33fa52a..4d01f36 100644
--- a/src/test/java/com/android/tools/r8/ir/analysis/type/MissingClassesJoinTest.java
+++ b/src/test/java/com/android/tools/r8/ir/analysis/type/MissingClassesJoinTest.java
@@ -52,7 +52,7 @@
.compile()
.writeToZip();
- if (parameters.getBackend() == Backend.DEX && !allowTypeErrors) {
+ if (parameters.isDexRuntime() && !allowTypeErrors) {
testForD8()
// Intentionally not adding ASub2 as a program class.
.addProgramClasses(A.class, ASub1.class, Box.class, TestClass.class)
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/ConstClassCanonicalizationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/ConstClassCanonicalizationTest.java
index 4e8fdc8..08d4273 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/ConstClassCanonicalizationTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/ConstClassCanonicalizationTest.java
@@ -107,9 +107,7 @@
@Test
public void testJVMOutput() throws Exception {
- assumeTrue(
- "Only run JVM reference once (for CF backend)",
- parameters.getBackend() == Backend.CF);
+ assumeTrue("Only run JVM reference once (for CF backend)", parameters.isCfRuntime());
testForJvm()
.addTestClasspath()
.run(parameters.getRuntime(), MAIN)
@@ -118,7 +116,7 @@
@Test
public void testD8_incremental() throws Exception {
- assumeTrue("Only run D8 for Dex backend", parameters.getBackend() == Backend.DEX);
+ assumeTrue("Only run D8 for Dex backend", parameters.isDexRuntime());
Path zipA = temp.newFile("a.zip").toPath().toAbsolutePath();
testForD8()
@@ -173,7 +171,7 @@
@Test
public void testD8() throws Exception {
- assumeTrue("Only run D8 for Dex backend", parameters.getBackend() == Backend.DEX);
+ assumeTrue("Only run D8 for Dex backend", parameters.isDexRuntime());
D8TestRunResult result =
testForD8()
@@ -208,11 +206,11 @@
// The number of expected const-class instructions differs because constant canonicalization is
// only enabled for the DEX backend.
int expectedMainCount =
- parameters.getBackend() == Backend.CF ? ORIGINAL_MAIN_COUNT : CANONICALIZED_MAIN_COUNT;
+ parameters.isCfRuntime() ? ORIGINAL_MAIN_COUNT : CANONICALIZED_MAIN_COUNT;
int expectedOuterCount =
- parameters.getBackend() == Backend.CF ? ORIGINAL_OUTER_COUNT : CANONICALIZED_OUTER_COUNT;
+ parameters.isCfRuntime() ? ORIGINAL_OUTER_COUNT : CANONICALIZED_OUTER_COUNT;
int expectedInnerCount =
- parameters.getBackend() == Backend.CF ? ORIGINAL_INNER_COUNT : CANONICALIZED_INNER_COUNT;
+ parameters.isCfRuntime() ? ORIGINAL_INNER_COUNT : CANONICALIZED_INNER_COUNT;
test(result, expectedMainCount, expectedOuterCount, expectedInnerCount);
}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/IdempotentFunctionCallCanonicalizationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/IdempotentFunctionCallCanonicalizationTest.java
index 7bb6c66..65336aa 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/IdempotentFunctionCallCanonicalizationTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/IdempotentFunctionCallCanonicalizationTest.java
@@ -108,9 +108,7 @@
@Test
public void testJVMOutput() throws Exception {
- assumeTrue(
- "Only run JVM reference once (for CF backend)",
- parameters.getBackend() == Backend.CF);
+ assumeTrue("Only run JVM reference once (for CF backend)", parameters.isCfRuntime());
testForJvm()
.addTestClasspath()
.run(parameters.getRuntime(), MAIN)
@@ -149,7 +147,7 @@
@Test
public void testD8() throws Exception {
- assumeTrue("Only run D8 for Dex backend", parameters.getBackend() == Backend.DEX);
+ assumeTrue("Only run D8 for Dex backend", parameters.isDexRuntime());
D8TestRunResult result =
testForD8()
@@ -180,12 +178,9 @@
.setMinApi(parameters.getRuntime())
.run(parameters.getRuntime(), MAIN)
.assertSuccessWithOutput(JAVA_OUTPUT);
- int expectedBooleanValueOfCount =
- parameters.getBackend() == Backend.CF ? 6 : EXPECTED_BOOLEAN_VALUE_OF;
- int expectedIntValueOfCount =
- parameters.getBackend() == Backend.CF ? 5 : EXPECTED_INTEGER_VALUE_OF;
- int expectedLongValueOfCount =
- parameters.getBackend() == Backend.CF ? 10 : EXPECTED_LONG_VALUE_OF;
+ int expectedBooleanValueOfCount = parameters.isCfRuntime() ? 6 : EXPECTED_BOOLEAN_VALUE_OF;
+ int expectedIntValueOfCount = parameters.isCfRuntime() ? 5 : EXPECTED_INTEGER_VALUE_OF;
+ int expectedLongValueOfCount = parameters.isCfRuntime() ? 10 : EXPECTED_LONG_VALUE_OF;
test(result, expectedBooleanValueOfCount, expectedIntValueOfCount, expectedLongValueOfCount);
}
}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/ObjectsRequireNonNullTest.java b/src/test/java/com/android/tools/r8/ir/optimize/ObjectsRequireNonNullTest.java
index 3d80826..9e22ade 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/ObjectsRequireNonNullTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/ObjectsRequireNonNullTest.java
@@ -117,9 +117,7 @@
@Test
public void testJvmOutput() throws Exception {
- assumeTrue(
- "Only run JVM reference once (for CF backend)",
- parameters.getBackend() == Backend.CF);
+ assumeTrue("Only run JVM reference once (for CF backend)", parameters.isCfRuntime());
testForJvm()
.addTestClasspath()
.run(parameters.getRuntime(), MAIN)
@@ -168,7 +166,7 @@
@Test
public void testD8() throws Exception {
- assumeTrue("Only run D8 for Dex backend", parameters.getBackend() == Backend.DEX);
+ assumeTrue("Only run D8 for Dex backend", parameters.isDexRuntime());
D8TestRunResult result = testForD8()
.debug()
.addProgramClassesAndInnerClasses(MAIN)
@@ -188,7 +186,7 @@
@Test
public void testR8() throws Exception {
- assumeTrue("CF disables move result optimization", parameters.getBackend() == Backend.DEX);
+ assumeTrue("CF disables move result optimization", parameters.isDexRuntime());
R8TestRunResult result = testForR8(parameters.getBackend())
.addProgramClassesAndInnerClasses(MAIN)
.enableInliningAnnotations()
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/R8InliningTest.java b/src/test/java/com/android/tools/r8/ir/optimize/R8InliningTest.java
index 78109d5..86245a3 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/R8InliningTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/R8InliningTest.java
@@ -9,6 +9,7 @@
import com.android.tools.r8.R8Command;
import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.ToolHelper.ProcessResult;
import com.android.tools.r8.graph.DexEncodedMethod;
@@ -42,37 +43,27 @@
@RunWith(Parameterized.class)
public class R8InliningTest extends TestBase {
- private Backend backend;
-
private static final String DEFAULT_DEX_FILENAME = "classes.dex";
private static final String DEFAULT_MAP_FILENAME = "proguard.map";
+ private static final String NAME = "inlining";
+ private static final String KEEP_RULES_FILE = ToolHelper.EXAMPLES_DIR + NAME + "/keep-rules.txt";
- @Parameters(name = "{0}, backend={1}, minification={2}, allowaccessmodification={3}")
+ @Parameters(name = "{1}, allow access modification: {0}")
public static Collection<Object[]> data() {
- return buildParameters(
- ImmutableList.of("Inlining"),
- ToolHelper.getBackends(),
- BooleanUtils.values(),
- BooleanUtils.values());
+ return buildParameters(BooleanUtils.values(), getTestParameters().withAllRuntimes().build());
}
- private final String name;
- private final String keepRulesFile;
- private final boolean minification;
private final boolean allowAccessModification;
+ private final TestParameters parameters;
private Path outputDir = null;
- public R8InliningTest(
- String name, Backend backend, boolean minification, boolean allowAccessModification) {
- this.name = name.toLowerCase();
- this.keepRulesFile = ToolHelper.EXAMPLES_DIR + this.name + "/keep-rules.txt";
- this.backend = backend;
- this.minification = minification;
+ public R8InliningTest(boolean allowAccessModification, TestParameters parameters) {
this.allowAccessModification = allowAccessModification;
+ this.parameters = parameters;
}
private Path getInputFile() {
- return Paths.get(ToolHelper.EXAMPLES_BUILD_DIR, name + FileUtils.JAR_EXTENSION);
+ return Paths.get(ToolHelper.EXAMPLES_BUILD_DIR, NAME + FileUtils.JAR_EXTENSION);
}
private Path getGeneratedDexFile() {
@@ -80,14 +71,13 @@
}
private List<Path> getGeneratedFiles(Path dir) throws IOException {
- if (backend == Backend.DEX) {
+ if (parameters.isDexRuntime()) {
return Collections.singletonList(dir.resolve(Paths.get(DEFAULT_DEX_FILENAME)));
- } else {
- assert backend == Backend.CF;
- return Files.walk(dir)
- .filter(f -> f.toString().endsWith(".class"))
- .collect(Collectors.toList());
}
+ assert parameters.isCfRuntime();
+ return Files.walk(dir)
+ .filter(f -> f.toString().endsWith(".class"))
+ .collect(Collectors.toList());
}
private List<Path> getGeneratedFiles() throws IOException {
@@ -103,18 +93,18 @@
}
private void generateR8Version(Path out, Path mapFile, boolean inlining) throws Exception {
- assert backend == Backend.DEX || backend == Backend.CF;
+ assert parameters.isDexRuntime() || parameters.isCfRuntime();
R8Command.Builder commandBuilder =
R8Command.builder()
.addProgramFiles(getInputFile())
- .setOutput(out, outputMode(backend))
- .addProguardConfigurationFiles(Paths.get(keepRulesFile))
- .addLibraryFiles(TestBase.runtimeJar(backend))
+ .setOutput(out, outputMode(parameters.getBackend()))
+ .addProguardConfigurationFiles(Paths.get(KEEP_RULES_FILE))
+ .addLibraryFiles(TestBase.runtimeJar(parameters.getBackend()))
.setDisableMinification(true);
if (mapFile != null) {
commandBuilder.setProguardMapOutputPath(mapFile);
}
- if (backend == Backend.DEX) {
+ if (parameters.isDexRuntime()) {
commandBuilder.setMinApiLevel(AndroidApiLevel.M.getLevel());
}
if (allowAccessModification) {
@@ -129,6 +119,7 @@
// that the class is therefore made abstract.
o.enableClassInlining = false;
o.enableInlining = inlining;
+ o.enableInliningOfInvokesWithNullableReceivers = false;
o.inliningInstructionLimit = 6;
});
}
@@ -139,12 +130,12 @@
Path mapFile = outputDir.resolve(DEFAULT_MAP_FILENAME);
generateR8Version(outputDir, mapFile, true);
String output;
- if (backend == Backend.DEX) {
+ if (parameters.isDexRuntime()) {
output =
ToolHelper.runArtNoVerificationErrors(
outputDir.resolve(DEFAULT_DEX_FILENAME).toString(), "inlining.Inlining");
} else {
- assert backend == Backend.CF;
+ assert parameters.isCfRuntime();
output = ToolHelper.runJavaNoVerify(outputDir, "inlining.Inlining").stdout;
}
@@ -217,7 +208,7 @@
generateR8Version(nonInlinedOutputDir, null, false);
long nonInlinedSize, inlinedSize;
- if (backend == Backend.DEX) {
+ if (parameters.isDexRuntime()) {
Path nonInlinedDexFile = nonInlinedOutputDir.resolve(DEFAULT_DEX_FILENAME);
nonInlinedSize = Files.size(nonInlinedDexFile);
inlinedSize = Files.size(getGeneratedDexFile());
@@ -227,7 +218,7 @@
dump(getGeneratedDexFile(), "Inlining enabled");
}
} else {
- assert backend == Backend.CF;
+ assert parameters.isCfRuntime();
nonInlinedSize = sumOfClassFileSizes(nonInlinedOutputDir);
inlinedSize = sumOfClassFileSizes(outputDir);
}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/devirtualize/InvokeInterfaceToInvokeVirtualTest.java b/src/test/java/com/android/tools/r8/ir/optimize/devirtualize/InvokeInterfaceToInvokeVirtualTest.java
index 171dac4..695cf0a 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/devirtualize/InvokeInterfaceToInvokeVirtualTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/devirtualize/InvokeInterfaceToInvokeVirtualTest.java
@@ -5,25 +5,20 @@
import static org.junit.Assert.assertEquals;
-import com.android.tools.r8.R8Command;
import com.android.tools.r8.TestBase;
-import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.ir.optimize.devirtualize.invokeinterface.A;
import com.android.tools.r8.ir.optimize.devirtualize.invokeinterface.A0;
import com.android.tools.r8.ir.optimize.devirtualize.invokeinterface.A1;
import com.android.tools.r8.ir.optimize.devirtualize.invokeinterface.I;
import com.android.tools.r8.ir.optimize.devirtualize.invokeinterface.Main;
-import com.android.tools.r8.origin.Origin;
-import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.InstructionSubject;
import com.android.tools.r8.utils.codeinspector.MethodSubject;
-import com.google.common.collect.ImmutableList;
import com.google.common.collect.Streams;
-import java.nio.file.Path;
-import java.util.Collections;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -31,52 +26,40 @@
@RunWith(Parameterized.class)
public class InvokeInterfaceToInvokeVirtualTest extends TestBase {
- private Backend backend;
+ private final TestParameters parameters;
- @Parameterized.Parameters(name = "Backend: {0}")
- public static Backend[] data() {
- return ToolHelper.getBackends();
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimes().build();
}
- public InvokeInterfaceToInvokeVirtualTest(Backend backend) {
- this.backend = backend;
- }
-
- private AndroidApp runR8(AndroidApp app, Class main, Path out) throws Exception {
- R8Command command =
- ToolHelper.addProguardConfigurationConsumer(
- ToolHelper.prepareR8CommandBuilder(app, emptyConsumer(backend)),
- pgConfig -> {
- pgConfig.setPrintMapping(true);
- pgConfig.setPrintMappingFile(out.resolve(ToolHelper.DEFAULT_PROGUARD_MAP_FILE));
- })
- .addProguardConfiguration(
- ImmutableList.of(keepMainProguardConfiguration(main)), Origin.unknown())
- .setOutput(out, outputMode(backend))
- .addLibraryFiles(runtimeJar(backend))
- .build();
- return ToolHelper.runR8(command);
+ public InvokeInterfaceToInvokeVirtualTest(TestParameters parameters) {
+ this.parameters = parameters;
}
@Test
public void listOfInterface() throws Exception {
- byte[][] classes = {
- ToolHelper.getClassAsBytes(I.class),
- ToolHelper.getClassAsBytes(A.class),
- ToolHelper.getClassAsBytes(A0.class),
- ToolHelper.getClassAsBytes(A1.class),
- ToolHelper.getClassAsBytes(Main.class)
- };
- String main = Main.class.getCanonicalName();
- ProcessResult javaOutput = runOnJavaRaw(main, classes);
- assertEquals(0, javaOutput.exitCode);
+ String expectedOutput = StringUtils.lines("0");
- AndroidApp originalApp = buildAndroidApp(classes);
- Path out = temp.getRoot().toPath();
- AndroidApp processedApp = runR8(originalApp, Main.class, out);
+ if (parameters.isCfRuntime()) {
+ testForJvm()
+ .addTestClasspath()
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutput(expectedOutput);
+ }
- CodeInspector codeInspector = new CodeInspector(processedApp);
- ClassSubject clazz = codeInspector.clazz(main);
+ CodeInspector inspector =
+ testForR8(parameters.getBackend())
+ .addProgramClasses(I.class, A.class, A0.class, A1.class, Main.class)
+ .addKeepMainRule(Main.class)
+ .addOptionsModification(
+ options -> options.enableInliningOfInvokesWithNullableReceivers = false)
+ .setMinApi(parameters.getRuntime())
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutput(expectedOutput)
+ .inspector();
+
+ ClassSubject clazz = inspector.clazz(Main.class);
MethodSubject m = clazz.method(CodeInspector.MAIN);
long numOfInvokeInterface =
Streams.stream(m.iterateInstructions(InstructionSubject::isInvokeInterface)).count();
@@ -89,12 +72,5 @@
long numOfCast = Streams.stream(m.iterateInstructions(InstructionSubject::isCheckCast)).count();
// check-cast I ~> check-cast A0
assertEquals(1, numOfCast);
-
- ProcessResult output =
- backend == Backend.DEX
- ? runOnArtRaw(processedApp, main)
- : runOnJavaRaw(processedApp, main, Collections.emptyList());
- assertEquals(0, output.exitCode);
- assertEquals(javaOutput.stdout.trim(), output.stdout.trim());
}
}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineInvokeWithNullableReceiverTest.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineInvokeWithNullableReceiverTest.java
index fa613a7..c00b563 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineInvokeWithNullableReceiverTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineInvokeWithNullableReceiverTest.java
@@ -6,6 +6,7 @@
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertTrue;
@@ -65,12 +66,15 @@
MethodSubject methodSubject = classSubject.mainMethod();
assertThat(methodSubject, isPresent());
- // TODO(b/130202534): A `throw` instruction should have been synthesized into main().
- assertTrue(methodSubject.streamInstructions().noneMatch(InstructionSubject::isThrow));
+ // A `throw` instruction should have been synthesized into main().
+ assertTrue(methodSubject.streamInstructions().anyMatch(InstructionSubject::isThrow));
- // TODO(b/130202534): Class A should be absent.
+ // Class A is still present because the instance flows into a phi that has a null-check.
ClassSubject otherClassSubject = inspector.clazz(A.class);
assertThat(otherClassSubject, isPresent());
+
+ // Method A.m() should no longer be present due to inlining.
+ assertThat(otherClassSubject.uniqueMethodWithName("m"), not(isPresent()));
}
static class TestClass {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetClassTest.java b/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetClassTest.java
index c78f4b9..d1bd6c0 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetClassTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetClassTest.java
@@ -137,9 +137,7 @@
@Test
public void testJVMOutput() throws Exception {
- assumeTrue(
- "Only run JVM reference once (for CF backend)",
- parameters.getBackend() == Backend.CF);
+ assumeTrue("Only run JVM reference once (for CF backend)", parameters.isCfRuntime());
testForJvm()
.addTestClasspath()
.run(parameters.getRuntime(), MAIN)
@@ -194,7 +192,7 @@
@Test
public void testD8() throws Exception {
- assumeTrue("Only run D8 for Dex backend", parameters.getBackend() == Backend.DEX);
+ assumeTrue("Only run D8 for Dex backend", parameters.isDexRuntime());
// D8 debug.
D8TestRunResult result =
@@ -234,7 +232,7 @@
// The number of expected const-class instructions differs because constant canonicalization is
// only enabled for the DEX backend.
- int expectedConstClassCount = parameters.getBackend() == Backend.CF ? 7 : 5;
+ int expectedConstClassCount = parameters.isCfRuntime() ? 7 : 5;
// R8 release, no minification.
result =
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetNameInClassInitializerTest.java b/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetNameInClassInitializerTest.java
index 1ed8793..8ebc848 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetNameInClassInitializerTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetNameInClassInitializerTest.java
@@ -53,7 +53,7 @@
public void testJVMOutput() throws Exception {
assumeTrue(
"Only run JVM reference once (for CF backend)",
- parameters.getBackend() == Backend.CF && !enableMinification);
+ parameters.isCfRuntime() && !enableMinification);
testForJvm()
.addTestClasspath()
.run(parameters.getRuntime(), MAIN)
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetNameTest.java b/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetNameTest.java
index 4c80fca..6c21b28 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetNameTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetNameTest.java
@@ -219,7 +219,7 @@
public void testJVMOutput() throws Exception {
assumeTrue(
"Only run JVM reference once (for CF backend)",
- parameters.getBackend() == Backend.CF && !enableMinification);
+ parameters.isCfRuntime() && !enableMinification);
testForJvm()
.addTestClasspath()
.run(parameters.getRuntime(), MAIN)
@@ -237,9 +237,7 @@
@Test
public void testD8() throws Exception {
- assumeTrue(
- "Only run D8 for Dex backend)",
- parameters.getBackend() == Backend.DEX && !enableMinification);
+ assumeTrue("Only run D8 for Dex backend)", parameters.isDexRuntime() && !enableMinification);
D8TestRunResult result =
testForD8()
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetSimpleNameTest.java b/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetSimpleNameTest.java
index 5e33843..0d9c034 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetSimpleNameTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetSimpleNameTest.java
@@ -165,7 +165,7 @@
public void testJVMOutput() throws Exception {
assumeTrue(
"Only run JVM reference once (for CF backend)",
- parameters.getBackend() == Backend.CF && !enableMinification);
+ parameters.isCfRuntime() && !enableMinification);
testForJvm()
.addTestClasspath()
.run(parameters.getRuntime(), MAIN)
@@ -182,9 +182,7 @@
@Test
public void testD8() throws Exception {
- assumeTrue(
- "Only run D8 for Dex backend",
- parameters.getBackend() == Backend.DEX && !enableMinification);
+ assumeTrue("Only run D8 for Dex backend", parameters.isDexRuntime() && !enableMinification);
D8TestRunResult result =
testForD8()
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/string/NameThenLengthTest.java b/src/test/java/com/android/tools/r8/ir/optimize/string/NameThenLengthTest.java
index 85c935b..e030780 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/string/NameThenLengthTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/string/NameThenLengthTest.java
@@ -81,9 +81,7 @@
@Test
public void testJVMOutput() throws Exception {
- assumeTrue(
- "Only run JVM reference once (for CF backend)",
- parameters.getBackend() == Backend.CF);
+ assumeTrue("Only run JVM reference once (for CF backend)", parameters.isCfRuntime());
testForJvm()
.addTestClasspath()
.run(parameters.getRuntime(), MAIN)
@@ -135,7 +133,7 @@
@Test
public void testD8() throws Exception {
- assumeTrue("Only run D8 for Dex backend", parameters.getBackend() == Backend.DEX);
+ assumeTrue("Only run D8 for Dex backend", parameters.isDexRuntime());
D8TestRunResult result =
testForD8()
@@ -172,7 +170,7 @@
.run(parameters.getRuntime(), MAIN)
.assertSuccessWithOutput(JAVA_OUTPUT);
// No canonicalization in CF.
- int expectedConstNumber = parameters.getBackend() == Backend.CF ? 2 : 1;
+ int expectedConstNumber = parameters.isCfRuntime() ? 2 : 1;
// TODO(b/125303292): NAME_LENGTH is still not computed at compile time.
test(result, 1, 0, 0, expectedConstNumber);
}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/string/StringCanonicalizationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/string/StringCanonicalizationTest.java
index 1eb5649..08dbd8c 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/string/StringCanonicalizationTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/string/StringCanonicalizationTest.java
@@ -203,7 +203,7 @@
@Test
public void testD8() throws Exception {
- assumeTrue("Only run D8 for Dex backend", parameters.getBackend() == Backend.DEX);
+ assumeTrue("Only run D8 for Dex backend", parameters.isDexRuntime());
D8TestCompileResult result =
testForD8()
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/string/StringConcatenationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/string/StringConcatenationTest.java
index e0811eb..af13cb9 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/string/StringConcatenationTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/string/StringConcatenationTest.java
@@ -52,9 +52,7 @@
@Test
public void testJVMOutput() throws Exception {
- assumeTrue(
- "Only run JVM reference once (for CF backend)",
- parameters.getBackend() == Backend.CF);
+ assumeTrue("Only run JVM reference once (for CF backend)", parameters.isCfRuntime());
testForJvm()
.addTestClasspath()
.run(parameters.getRuntime(), MAIN)
@@ -118,7 +116,7 @@
@Test
public void testD8() throws Exception {
- assumeTrue("Only run D8 for Dex backend", parameters.getBackend() == Backend.DEX);
+ assumeTrue("Only run D8 for Dex backend", parameters.isDexRuntime());
D8TestRunResult result =
testForD8()
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/string/StringContentCheckTest.java b/src/test/java/com/android/tools/r8/ir/optimize/string/StringContentCheckTest.java
index 9b77cbc..153e6b0 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/string/StringContentCheckTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/string/StringContentCheckTest.java
@@ -187,9 +187,7 @@
@Test
public void testJVMOutput() throws Exception {
- assumeTrue(
- "Only run JVM reference once (for CF backend)",
- parameters.getBackend() == Backend.CF);
+ assumeTrue("Only run JVM reference once (for CF backend)", parameters.isCfRuntime());
testForJvm()
.addTestClasspath()
.run(parameters.getRuntime(), MAIN)
@@ -240,7 +238,7 @@
@Test
public void testD8() throws Exception {
- assumeTrue("Only run D8 for Dex backend", parameters.getBackend() == Backend.DEX);
+ assumeTrue("Only run D8 for Dex backend", parameters.isDexRuntime());
TestRunResult result =
testForD8()
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/string/StringInMonitorTest.java b/src/test/java/com/android/tools/r8/ir/optimize/string/StringInMonitorTest.java
index 833f2dc..c4ec59a 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/string/StringInMonitorTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/string/StringInMonitorTest.java
@@ -95,9 +95,7 @@
@Test
public void testJVMOutput() throws Exception {
- assumeTrue(
- "Only run JVM reference once (for CF backend)",
- parameters.getBackend() == Backend.CF);
+ assumeTrue("Only run JVM reference once (for CF backend)", parameters.isCfRuntime());
testForJvm()
.addTestClasspath()
.run(parameters.getRuntime(), MAIN)
@@ -119,7 +117,7 @@
assertEquals(expectedConstStringCount1, count);
// TODO(b/122302789): CfInstruction#getOffset()
- if (parameters.getBackend() == Backend.DEX) {
+ if (parameters.isDexRuntime()) {
Iterator<InstructionSubject> constStringIterator =
mainMethod.iterateInstructions(i -> i.isConstString(JumboStringMode.ALLOW));
// All const-string's in main(...) should be covered by try (or synthetic catch-all) region.
@@ -148,7 +146,7 @@
assertEquals(expectedConstStringCount2, count);
// In CF, we don't explicitly add monitor-{enter|exit} and catch-all for synchronized methods.
- if (parameters.getBackend() == Backend.DEX) {
+ if (parameters.isDexRuntime()) {
Iterator<InstructionSubject> constStringIterator =
sync.iterateInstructions(i -> i.isConstString(JumboStringMode.ALLOW));
// All const-string's in sync() should be covered by the synthetic catch-all regions.
@@ -178,7 +176,7 @@
@Test
public void testD8() throws Exception {
- assumeTrue("Only run D8 for Dex backend", parameters.getBackend() == Backend.DEX);
+ assumeTrue("Only run D8 for Dex backend", parameters.isDexRuntime());
D8TestRunResult result =
testForD8()
@@ -210,7 +208,7 @@
.run(parameters.getRuntime(), MAIN)
.assertSuccessWithOutput(JAVA_OUTPUT);
// Due to the different behavior regarding constant canonicalization.
- int expectedConstStringCount3 = parameters.getBackend() == Backend.CF ? 2 : 1;
+ int expectedConstStringCount3 = parameters.isCfRuntime() ? 2 : 1;
test(result, 2, 2, expectedConstStringCount3);
}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/string/StringIsEmptyTest.java b/src/test/java/com/android/tools/r8/ir/optimize/string/StringIsEmptyTest.java
index fba4ced..ffa3966 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/string/StringIsEmptyTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/string/StringIsEmptyTest.java
@@ -74,9 +74,7 @@
@Test
public void testJVMOutput() throws Exception {
- assumeTrue(
- "Only run JVM reference once (for CF backend)",
- parameters.getBackend() == Backend.CF);
+ assumeTrue("Only run JVM reference once (for CF backend)", parameters.isCfRuntime());
testForJvm()
.addTestClasspath()
.run(parameters.getRuntime(), MAIN)
@@ -116,7 +114,7 @@
@Test
public void testD8() throws Exception {
- assumeTrue("Only run D8 for Dex backend", parameters.getBackend() == Backend.DEX);
+ assumeTrue("Only run D8 for Dex backend", parameters.isDexRuntime());
D8TestRunResult result =
testForD8()
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/string/StringLengthTest.java b/src/test/java/com/android/tools/r8/ir/optimize/string/StringLengthTest.java
index 2d0bdea..735645c 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/string/StringLengthTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/string/StringLengthTest.java
@@ -105,9 +105,7 @@
@Test
public void testJVMOutput() throws Exception {
- assumeTrue(
- "Only run JVM reference once (for CF backend)",
- parameters.getBackend() == Backend.CF);
+ assumeTrue("Only run JVM reference once (for CF backend)", parameters.isCfRuntime());
// TODO(b/119097175)
if (!ToolHelper.isWindows()) {
testForJvm()
@@ -148,7 +146,7 @@
@Test
public void testD8() throws Exception {
- assumeTrue("Only run D8 for Dex backend", parameters.getBackend() == Backend.DEX);
+ assumeTrue("Only run D8 for Dex backend", parameters.isDexRuntime());
D8TestRunResult result =
testForD8()
@@ -188,6 +186,6 @@
if (!ToolHelper.isWindows()) {
result.assertSuccessWithOutput(JAVA_OUTPUT);
}
- test(result, 0, parameters.getBackend() == Backend.DEX ? 5 : 6);
+ test(result, 0, parameters.isDexRuntime() ? 5 : 6);
}
}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/string/StringToStringTest.java b/src/test/java/com/android/tools/r8/ir/optimize/string/StringToStringTest.java
index ea664a8..d6af1d2 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/string/StringToStringTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/string/StringToStringTest.java
@@ -72,9 +72,7 @@
@Test
public void testJVMOutput() throws Exception {
- assumeTrue(
- "Only run JVM reference once (for CF backend)",
- parameters.getBackend() == Backend.CF);
+ assumeTrue("Only run JVM reference once (for CF backend)", parameters.isCfRuntime());
testForJvm()
.addTestClasspath()
.run(parameters.getRuntime(), MAIN)
@@ -108,7 +106,7 @@
@Test
public void testD8() throws Exception {
- assumeTrue("Only run D8 for Dex backend", parameters.getBackend() == Backend.DEX);
+ assumeTrue("Only run D8 for Dex backend", parameters.isDexRuntime());
D8TestRunResult result =
testForD8()
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/string/StringValueOfTest.java b/src/test/java/com/android/tools/r8/ir/optimize/string/StringValueOfTest.java
index 512c54d..4d8dddb 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/string/StringValueOfTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/string/StringValueOfTest.java
@@ -148,9 +148,7 @@
@Test
public void testJVMOutput() throws Exception {
- assumeTrue(
- "Only run JVM reference once (for CF backend)",
- parameters.getBackend() == Backend.CF);
+ assumeTrue("Only run JVM reference once (for CF backend)", parameters.isCfRuntime());
testForJvm()
.addTestClasspath()
.run(parameters.getRuntime(), MAIN)
@@ -211,7 +209,7 @@
@Test
public void testD8() throws Exception {
- assumeTrue("Only run D8 for Dex backend", parameters.getBackend() == Backend.DEX);
+ assumeTrue("Only run D8 for Dex backend", parameters.isDexRuntime());
TestRunResult result =
testForD8()
@@ -246,8 +244,8 @@
.run(parameters.getRuntime(), MAIN)
.assertSuccessWithOutput(JAVA_OUTPUT);
// Due to the different behavior regarding constant canonicalization.
- int expectedNullCount = parameters.getBackend() == Backend.CF ? 2 : 1;
- int expectedNullStringCount = parameters.getBackend() == Backend.CF ? 2 : 1;
+ int expectedNullCount = parameters.isCfRuntime() ? 2 : 1;
+ int expectedNullStringCount = parameters.isCfRuntime() ? 2 : 1;
test(result, 3, expectedNullCount, expectedNullStringCount, 0, 1);
}
}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/NestedInterfaceMethodTest.java b/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/NestedInterfaceMethodTest.java
index a1e2578..b5752cc 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/NestedInterfaceMethodTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/NestedInterfaceMethodTest.java
@@ -40,7 +40,7 @@
public void test() throws Exception {
String expectedOutput = StringUtils.lines("In A.m()", "In A.m()");
- if (parameters.getBackend() == Backend.CF) {
+ if (parameters.isCfRuntime()) {
testForJvm().addTestClasspath().run(TestClass.class).assertSuccessWithOutput(expectedOutput);
}
@@ -50,7 +50,11 @@
.addKeepMainRule(TestClass.class)
.enableInliningAnnotations()
.enableMergeAnnotations()
- .addOptionsModification(options -> options.enableDevirtualization = false)
+ .addOptionsModification(
+ options -> {
+ options.enableDevirtualization = false;
+ options.enableInliningOfInvokesWithNullableReceivers = false;
+ })
.setMinApi(AndroidApiLevel.B)
.run(parameters.getRuntime(), TestClass.class)
.assertSuccessWithOutput(expectedOutput)
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedArgumentsCollisionTest.java b/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedArgumentsCollisionTest.java
index 9ed87ef..003a06f 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedArgumentsCollisionTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedArgumentsCollisionTest.java
@@ -46,7 +46,7 @@
public void test() throws Exception {
String expectedOutput = StringUtils.lines("Hello world", "Hello world");
- if (parameters.getBackend() == Backend.CF && !minification) {
+ if (parameters.isCfRuntime() && !minification) {
testForJvm()
.addTestClasspath()
.run(parameters.getRuntime(), TestClass.class)
diff --git a/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java b/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java
index 0f32501..11572ef 100644
--- a/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java
+++ b/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java
@@ -18,13 +18,13 @@
import com.android.tools.r8.ir.code.Instruction;
import com.android.tools.r8.ir.code.InstructionListIterator;
import com.android.tools.r8.ir.code.Move;
+import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.code.ValueType;
import com.android.tools.r8.utils.InternalOptions;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.Set;
-
import org.junit.Test;
public class RegisterMoveSchedulerTest {
@@ -48,6 +48,11 @@
}
@Override
+ public Value insertConstNullInstruction(IRCode code, InternalOptions options) {
+ throw new Unimplemented();
+ }
+
+ @Override
public void replaceCurrentInstructionWithThrowNull(
AppView<? extends AppInfoWithSubtyping> appView,
IRCode code,
diff --git a/src/test/java/com/android/tools/r8/kotlin/KotlinClassStaticizerTest.java b/src/test/java/com/android/tools/r8/kotlin/KotlinClassStaticizerTest.java
index 3bc1270..b76a4fb 100644
--- a/src/test/java/com/android/tools/r8/kotlin/KotlinClassStaticizerTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/KotlinClassStaticizerTest.java
@@ -6,8 +6,8 @@
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeTrue;
@@ -35,7 +35,7 @@
"class_staticizer",
mainClassName,
false,
- (app) -> {
+ app -> {
CodeInspector inspector = new CodeInspector(app);
assertThat(inspector.clazz("class_staticizer.Regular$Companion"), isPresent());
assertThat(inspector.clazz("class_staticizer.Derived$Companion"), isPresent());
@@ -57,7 +57,7 @@
"class_staticizer",
mainClassName,
true,
- (app) -> {
+ app -> {
CodeInspector inspector = new CodeInspector(app);
assertThat(inspector.clazz("class_staticizer.Regular$Companion"), not(isPresent()));
assertThat(inspector.clazz("class_staticizer.Derived$Companion"), not(isPresent()));
@@ -71,10 +71,14 @@
protected void runTest(String folder, String mainClass,
boolean enabled, AndroidAppInspector inspector) throws Exception {
runTest(
- folder, mainClass, null,
+ folder,
+ mainClass,
+ null,
options -> {
options.enableClassInlining = false;
options.enableClassStaticizer = enabled;
- }, inspector);
+ options.enableInliningOfInvokesWithNullableReceivers = false;
+ },
+ inspector);
}
}
diff --git a/src/test/java/com/android/tools/r8/kotlin/KotlinDuplicateAnnotationTest.java b/src/test/java/com/android/tools/r8/kotlin/KotlinDuplicateAnnotationTest.java
index c4979bc..8eb6a2e 100644
--- a/src/test/java/com/android/tools/r8/kotlin/KotlinDuplicateAnnotationTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/KotlinDuplicateAnnotationTest.java
@@ -52,7 +52,7 @@
@Test
public void test_dex() {
- assumeTrue("test DEX", parameters.getBackend() == Backend.DEX);
+ assumeTrue("test DEX", parameters.isDexRuntime());
try {
testForR8(parameters.getBackend())
.addProgramFiles(getKotlinJarFile(FOLDER))
@@ -70,7 +70,7 @@
@Test
public void test_cf() throws Exception {
- assumeTrue("test CF", parameters.getBackend() == Backend.CF);
+ assumeTrue("test CF", parameters.isCfRuntime());
testForR8(parameters.getBackend())
.addProgramFiles(getKotlinJarFile(FOLDER))
.addKeepMainRule(MAIN)
diff --git a/src/test/java/com/android/tools/r8/kotlin/R8KotlinPropertiesTest.java b/src/test/java/com/android/tools/r8/kotlin/R8KotlinPropertiesTest.java
index 76c9ba8..7f038cc 100644
--- a/src/test/java/com/android/tools/r8/kotlin/R8KotlinPropertiesTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/R8KotlinPropertiesTest.java
@@ -89,6 +89,9 @@
o.enableClassStaticizer = false;
};
+ private Consumer<InternalOptions> disableInliningOfInvokesWithNullableReceivers =
+ o -> o.enableInliningOfInvokesWithNullableReceivers = false;
+
public R8KotlinPropertiesTest(
KotlinTargetVersion targetVersion, boolean allowAccessModification) {
super(targetVersion, allowAccessModification);
@@ -413,7 +416,7 @@
runTest(
PACKAGE_NAME,
mainClass,
- disableAggressiveClassOptimizations,
+ disableAggressiveClassOptimizations.andThen(disableInliningOfInvokesWithNullableReceivers),
app -> {
CodeInspector codeInspector = new CodeInspector(app);
ClassSubject outerClass =
@@ -486,7 +489,7 @@
runTest(
PACKAGE_NAME,
mainClass,
- disableAggressiveClassOptimizations,
+ disableAggressiveClassOptimizations.andThen(disableInliningOfInvokesWithNullableReceivers),
app -> {
CodeInspector codeInspector = new CodeInspector(app);
ClassSubject outerClass =
@@ -521,7 +524,7 @@
runTest(
PACKAGE_NAME,
mainClass,
- disableAggressiveClassOptimizations,
+ disableAggressiveClassOptimizations.andThen(disableInliningOfInvokesWithNullableReceivers),
app -> {
CodeInspector codeInspector = new CodeInspector(app);
ClassSubject outerClass =
@@ -593,7 +596,7 @@
runTest(
PACKAGE_NAME,
mainClass,
- disableAggressiveClassOptimizations,
+ disableAggressiveClassOptimizations.andThen(disableInliningOfInvokesWithNullableReceivers),
app -> {
CodeInspector codeInspector = new CodeInspector(app);
ClassSubject outerClass =
@@ -622,7 +625,7 @@
runTest(
PACKAGE_NAME,
mainClass,
- disableAggressiveClassOptimizations,
+ disableAggressiveClassOptimizations.andThen(disableInliningOfInvokesWithNullableReceivers),
app -> {
CodeInspector codeInspector = new CodeInspector(app);
ClassSubject outerClass =
diff --git a/src/test/java/com/android/tools/r8/naming/applymapping/sourcelibrary/MemberResolutionTest.java b/src/test/java/com/android/tools/r8/naming/applymapping/sourcelibrary/MemberResolutionTest.java
index dc1fe84..fd2fef0 100644
--- a/src/test/java/com/android/tools/r8/naming/applymapping/sourcelibrary/MemberResolutionTest.java
+++ b/src/test/java/com/android/tools/r8/naming/applymapping/sourcelibrary/MemberResolutionTest.java
@@ -108,7 +108,7 @@
"AbstractChecker#check:PrivateInitialTag_AbstractChecker",
"ConcreteChecker#check:NewTag");
- if (parameters.getBackend() == Backend.CF) {
+ if (parameters.isCfRuntime()) {
testForJvm()
.addTestClasspath()
.run(parameters.getRuntime(), MemberResolutionTestMain.class)
diff --git a/src/test/java/com/android/tools/r8/peephole/suffixsharing/IdenticalBlockSuffixSharingWithArrayTypesTest.java b/src/test/java/com/android/tools/r8/peephole/suffixsharing/IdenticalBlockSuffixSharingWithArrayTypesTest.java
index 5f664bf..3f89628 100644
--- a/src/test/java/com/android/tools/r8/peephole/suffixsharing/IdenticalBlockSuffixSharingWithArrayTypesTest.java
+++ b/src/test/java/com/android/tools/r8/peephole/suffixsharing/IdenticalBlockSuffixSharingWithArrayTypesTest.java
@@ -65,7 +65,7 @@
@Test
public void testD8() throws Exception {
- assumeTrue("Only run D8 for DEX backend", parameters.getBackend() == Backend.DEX);
+ assumeTrue("Only run D8 for DEX backend", parameters.isDexRuntime());
String expectedOutput = StringUtils.lines("42");
testForD8()
diff --git a/src/test/java/com/android/tools/r8/shaking/ServiceLoaderTest.java b/src/test/java/com/android/tools/r8/shaking/ServiceLoaderTest.java
index 09567f2..c9251af 100644
--- a/src/test/java/com/android/tools/r8/shaking/ServiceLoaderTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ServiceLoaderTest.java
@@ -7,15 +7,15 @@
import static com.android.tools.r8.references.Reference.classFromClass;
import static com.android.tools.r8.references.Reference.methodFromMethod;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.core.IsNot.not;
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import com.android.tools.r8.DataEntryResource;
import com.android.tools.r8.R8TestRunResult;
import com.android.tools.r8.TestBase;
-import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.TestParameters;
import com.android.tools.r8.graph.AppServices;
import com.android.tools.r8.naming.AdaptResourceFileContentsTest.DataResourceConsumerForTesting;
import com.android.tools.r8.origin.Origin;
@@ -38,21 +38,21 @@
@RunWith(Parameterized.class)
public class ServiceLoaderTest extends TestBase {
- private final Backend backend;
private final boolean includeWorldGreeter;
+ private final TestParameters parameters;
private DataResourceConsumerForTesting dataResourceConsumer;
private static final String LINE_COMMENT = "# This is a comment.";
private static final String POSTFIX_COMMENT = "# POSTFIX_COMMENT";
- @Parameters(name = "Backend: {0}, include WorldGreeter: {1}")
+ @Parameters(name = "{1}, include WorldGreeter: {0}")
public static List<Object[]> data() {
- return buildParameters(ToolHelper.getBackends(), BooleanUtils.values());
+ return buildParameters(BooleanUtils.values(), getTestParameters().withAllRuntimes().build());
}
- public ServiceLoaderTest(Backend backend, boolean includeWorldGreeter) {
- this.backend = backend;
+ public ServiceLoaderTest(boolean includeWorldGreeter, TestParameters parameters) {
this.includeWorldGreeter = includeWorldGreeter;
+ this.parameters = parameters;
}
@Test
@@ -72,7 +72,7 @@
}
R8TestRunResult result =
- testForR8(backend)
+ testForR8(parameters.getBackend())
.addInnerClasses(ServiceLoaderTest.class)
.addKeepMainRule(TestClass.class)
.addDataEntryResources(
@@ -85,9 +85,11 @@
dataResourceConsumer =
new DataResourceConsumerForTesting(options.dataResourceConsumer);
options.dataResourceConsumer = dataResourceConsumer;
+ options.enableInliningOfInvokesWithNullableReceivers = false;
})
.enableGraphInspector()
- .run(TestClass.class)
+ .setMinApi(parameters.getRuntime())
+ .run(parameters.getRuntime(), TestClass.class)
.assertSuccessWithOutput(expectedOutput);
CodeInspector inspector = result.inspector();
@@ -163,7 +165,7 @@
}
CodeInspector inspector =
- testForR8(backend)
+ testForR8(parameters.getBackend())
.addInnerClasses(ServiceLoaderTest.class)
.addKeepMainRule(OtherTestClass.class)
.addDataEntryResources(
@@ -177,7 +179,8 @@
new DataResourceConsumerForTesting(options.dataResourceConsumer);
options.dataResourceConsumer = dataResourceConsumer;
})
- .run(OtherTestClass.class)
+ .setMinApi(parameters.getRuntime())
+ .run(parameters.getRuntime(), OtherTestClass.class)
.assertSuccessWithOutput(expectedOutput)
.inspector();
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 0fe8d7e..a955e0a 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
@@ -6,20 +6,16 @@
import static org.junit.Assert.assertEquals;
-import com.android.tools.r8.ClassFileConsumer;
-import com.android.tools.r8.DexIndexedConsumer;
-import com.android.tools.r8.R8Command;
import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.graph.invokesuper.Consumer;
import com.android.tools.r8.naming.MemberNaming.MethodSignature;
-import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.shaking.proxy.testclasses.BaseInterface;
import com.android.tools.r8.shaking.proxy.testclasses.Main;
import com.android.tools.r8.shaking.proxy.testclasses.SubClass;
import com.android.tools.r8.shaking.proxy.testclasses.SubInterface;
import com.android.tools.r8.shaking.proxy.testclasses.TestClass;
-import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.FoundMethodSubject;
@@ -29,6 +25,7 @@
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Streams;
import java.util.List;
+import java.util.function.Consumer;
import java.util.function.Predicate;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -36,55 +33,47 @@
@RunWith(Parameterized.class)
public class ProxiesTest extends TestBase {
- private Backend backend;
- @Parameterized.Parameters(name = "Backend: {0}")
- public static Backend[] data() {
- return ToolHelper.getBackends();
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimes().build();
}
- public ProxiesTest(Backend backend) {
- this.backend = backend;
+ public ProxiesTest(TestParameters parameters) {
+ this.parameters = parameters;
}
- private void runTest(List<String> additionalKeepRules, Consumer<CodeInspector> inspection,
- String expectedResult)
+ private void runTest(
+ List<String> additionalKeepRules, Consumer<CodeInspector> inspection, String expectedResult)
throws Exception {
- Class mainClass = Main.class;
- R8Command.Builder builder = R8Command.builder();
- builder.addProgramFiles(ToolHelper.getClassFilesForTestPackage(mainClass.getPackage()));
- builder.addProguardConfiguration(ImmutableList.of(
- "-keep class " + mainClass.getCanonicalName() + " {",
- // Keep x, y and z to avoid them being inlined into main.
- " private void x(com.android.tools.r8.shaking.proxy.testclasses.BaseInterface);",
- " private void y(com.android.tools.r8.shaking.proxy.testclasses.SubInterface);",
- " private void z(com.android.tools.r8.shaking.proxy.testclasses.TestClass);",
- " private void z(com.android.tools.r8.shaking.proxy.testclasses.SubClass);",
- " public static void main(java.lang.String[]);",
- "}",
- "-dontobfuscate"),
- Origin.unknown()
- );
- builder.addProguardConfiguration(additionalKeepRules, Origin.unknown());
- if (backend == Backend.DEX) {
- builder
- .setProgramConsumer(DexIndexedConsumer.emptyConsumer())
- .addLibraryFiles(ToolHelper.getDefaultAndroidJar());
- } else {
- assert backend == Backend.CF;
- builder
- .setProgramConsumer(ClassFileConsumer.emptyConsumer())
- .addLibraryFiles(ToolHelper.getJava8RuntimeJar());
- }
- AndroidApp app = ToolHelper.runR8(builder.build(), o -> {
- o.enableDevirtualization = false;
- // Tests indirectly check if a certain method is inlined or not, where the target method has
- // at least 4 instructions.
- o.inliningInstructionLimit = 4;
- });
- inspection.accept(new CodeInspector(app));
- String result = backend == Backend.DEX ? runOnArt(app, mainClass) : runOnJava(app, mainClass);
- assertEquals(StringUtils.withNativeLineSeparator(expectedResult), result);
+ testForR8(parameters.getBackend())
+ .addProgramFiles(ToolHelper.getClassFilesForTestPackage(Main.class.getPackage()))
+ .addKeepMainRule(Main.class)
+ .addKeepRules(
+ "-keep class " + Main.class.getCanonicalName() + " {",
+ // Keep x, y and z to avoid them being inlined into main.
+ " private void x(com.android.tools.r8.shaking.proxy.testclasses.BaseInterface);",
+ " private void y(com.android.tools.r8.shaking.proxy.testclasses.SubInterface);",
+ " private void z(com.android.tools.r8.shaking.proxy.testclasses.TestClass);",
+ " private void z(com.android.tools.r8.shaking.proxy.testclasses.SubClass);",
+ "}")
+ .addKeepRules(additionalKeepRules)
+ .addOptionsModification(
+ o -> {
+ o.enableDevirtualization = false;
+ o.enableInliningOfInvokesWithNullableReceivers = false;
+ // Tests indirectly check if a certain method is inlined or not, where the target
+ // method has at least 4 instructions.
+ o.inliningInstructionLimit = 4;
+ })
+ .noMinification()
+ .setMinApi(parameters.getRuntime())
+ .compile()
+ .inspect(inspection)
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutput(StringUtils.withNativeLineSeparator(expectedResult));
}
private int countInstructionInX(CodeInspector inspector, Predicate<InstructionSubject> invoke) {
diff --git a/src/test/java/com/android/tools/r8/shaking/reflection/ReflectiveNewInstanceTest.java b/src/test/java/com/android/tools/r8/shaking/reflection/ReflectiveNewInstanceTest.java
index f62d2d9..544a7ee 100644
--- a/src/test/java/com/android/tools/r8/shaking/reflection/ReflectiveNewInstanceTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/reflection/ReflectiveNewInstanceTest.java
@@ -41,7 +41,7 @@
String expectedOutput =
StringUtils.lines("Success", "Success", "Success", "Success", "Success");
- if (parameters.getBackend() == Backend.CF) {
+ if (parameters.isCfRuntime()) {
testForJvm()
.addTestClasspath()
.run(parameters.getRuntime(), TestClass.class)