Throw null if an instruction will do.
Based on SSA value's nullability as well as uninstantiated type info,
if an instruction requires non-null receiver, e.g., instance-{get|put},
we can replace it with `throw null` when the receiver is known to be
null.
One caveat is, for field instructions, resolution-related errors come
first. This CL refactors dead condition of {instance|static}-get and
rather simulates field resolutions. If it will raise errors other
than NPE, we should honor that.
Bug: 72443802, 129530569
Change-Id: Iecddc7feafd07da8496634835ddc0cacc08feea8
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
index 90dfef0..dfda737 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -181,23 +181,29 @@
public final DexString methodDescriptor = createString("Ljava/lang/reflect/Method;");
public final DexString enumDescriptor = createString("Ljava/lang/Enum;");
public final DexString annotationDescriptor = createString("Ljava/lang/annotation/Annotation;");
- public final DexString throwableDescriptor = createString(throwableDescriptorString);
- public final DexString exceptionInInitializerErrorDescriptor =
- createString("Ljava/lang/ExceptionInInitializerError;");
public final DexString objectsDescriptor = createString("Ljava/util/Objects;");
+
public final DexString stringBuilderDescriptor = createString("Ljava/lang/StringBuilder;");
public final DexString stringBufferDescriptor = createString("Ljava/lang/StringBuffer;");
+
public final DexString varHandleDescriptor = createString("Ljava/lang/invoke/VarHandle;");
public final DexString methodHandleDescriptor = createString("Ljava/lang/invoke/MethodHandle;");
public final DexString methodTypeDescriptor = createString("Ljava/lang/invoke/MethodType;");
-
public final DexString invocationHandlerDescriptor =
createString("Ljava/lang/reflect/InvocationHandler;");
- public final DexString npeDescriptor = createString("Ljava/lang/NullPointerException;");
public final DexString proxyDescriptor = createString("Ljava/lang/reflect/Proxy;");
public final DexString serviceLoaderDescriptor = createString("Ljava/util/ServiceLoader;");
public final DexString listDescriptor = createString("Ljava/util/List;");
+ public final DexString throwableDescriptor = createString(throwableDescriptorString);
+ public final DexString illegalAccessErrorDescriptor =
+ createString("Ljava/lang/IllegalAccessError;");
+ public final DexString icceDescriptor = createString("Ljava/lang/IncompatibleClassChangeError;");
+ public final DexString exceptionInInitializerErrorDescriptor =
+ createString("Ljava/lang/ExceptionInInitializerError;");
+ public final DexString noSuchFieldErrorDescriptor = createString("Ljava/lang/NoSuchFieldError");
+ public final DexString npeDescriptor = createString("Ljava/lang/NullPointerException;");
+
public final DexString intFieldUpdaterDescriptor =
createString("Ljava/util/concurrent/atomic/AtomicIntegerFieldUpdater;");
public final DexString longFieldUpdaterDescriptor =
@@ -243,9 +249,7 @@
public final DexType classArrayType = createType(classArrayDescriptor);
public final DexType enumType = createType(enumDescriptor);
public final DexType annotationType = createType(annotationDescriptor);
- public final DexType throwableType = createType(throwableDescriptor);
- public final DexType exceptionInInitializerErrorType =
- createType(exceptionInInitializerErrorDescriptor);
+
public final DexType classType = createType(classDescriptor);
public final DexType classLoaderType = createType(classLoaderDescriptor);
public final DexType autoCloseableType = createType(autoCloseableDescriptor);
@@ -256,13 +260,19 @@
public final DexType varHandleType = createType(varHandleDescriptor);
public final DexType methodHandleType = createType(methodHandleDescriptor);
public final DexType methodTypeType = createType(methodTypeDescriptor);
-
public final DexType invocationHandlerType = createType(invocationHandlerDescriptor);
- public final DexType npeType = createType(npeDescriptor);
public final DexType proxyType = createType(proxyDescriptor);
public final DexType serviceLoaderType = createType(serviceLoaderDescriptor);
public final DexType listType = createType(listDescriptor);
+ public final DexType throwableType = createType(throwableDescriptor);
+ public final DexType illegalAccessErrorType = createType(illegalAccessErrorDescriptor);
+ public final DexType icceType = createType(icceDescriptor);
+ public final DexType exceptionInInitializerErrorType =
+ createType(exceptionInInitializerErrorDescriptor);
+ public final DexType noSuchFieldErrorType = createType(noSuchFieldErrorDescriptor);
+ public final DexType npeType = createType(npeDescriptor);
+
public final StringBuildingMethods stringBuilderMethods =
new StringBuildingMethods(stringBuilderType);
public final StringBuildingMethods stringBufferMethods =
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/AbstractError.java b/src/main/java/com/android/tools/r8/ir/analysis/AbstractError.java
new file mode 100644
index 0000000..dcfd5c5
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/analysis/AbstractError.java
@@ -0,0 +1,56 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.ir.analysis;
+
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexType;
+
+// Note that this is not what D8/R8 can throw.
+//
+// Finer-grained abstraction of Throwable that an instruction can throw if any.
+//
+// Top // throwing, but not quite sure what it would be.
+// / | \
+// NPE ICCE ... // specific exceptions.
+// \ | /
+// Bottom // not throwing.
+public class AbstractError {
+
+ private static final AbstractError TOP = new AbstractError();
+ private static final AbstractError BOTTOM = new AbstractError();
+
+ private DexType simulatedError;
+
+ private AbstractError() {}
+
+ private AbstractError(DexType throwable) {
+ simulatedError = throwable;
+ }
+
+ public static AbstractError top() {
+ return TOP;
+ }
+
+ public static AbstractError bottom() {
+ return BOTTOM;
+ }
+
+ public static AbstractError specific(DexType throwable) {
+ return new AbstractError(throwable);
+ }
+
+ public boolean isThrowing() {
+ return this != BOTTOM;
+ }
+
+ public DexType getSpecificError(DexItemFactory factory) {
+ assert isThrowing();
+ if (simulatedError != null) {
+ return simulatedError;
+ }
+ assert this == TOP;
+ return factory.throwableType;
+ }
+
+}
diff --git a/src/main/java/com/android/tools/r8/ir/code/ArrayGet.java b/src/main/java/com/android/tools/r8/ir/code/ArrayGet.java
index 3e2af3d..331a9ac 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ArrayGet.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ArrayGet.java
@@ -240,6 +240,16 @@
}
@Override
+ public boolean throwsOnNullInput() {
+ return true;
+ }
+
+ @Override
+ public Value getNonNullInput() {
+ return array();
+ }
+
+ @Override
public void constrainType(TypeConstraintResolver constraintResolver) {
constraintResolver.constrainArrayMemberType(type, dest(), array(), t -> type = t);
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/ArrayLength.java b/src/main/java/com/android/tools/r8/ir/code/ArrayLength.java
index 061aa53..6e6a9f8 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ArrayLength.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ArrayLength.java
@@ -119,4 +119,15 @@
public boolean throwsNpeIfValueIsNull(Value value, DexItemFactory dexItemFactory) {
return array() == value;
}
+
+ @Override
+ public boolean throwsOnNullInput() {
+ return true;
+ }
+
+ @Override
+ public Value getNonNullInput() {
+ return array();
+ }
+
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/ArrayPut.java b/src/main/java/com/android/tools/r8/ir/code/ArrayPut.java
index a76bf42..fa3a846 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ArrayPut.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ArrayPut.java
@@ -189,6 +189,16 @@
}
@Override
+ public boolean throwsOnNullInput() {
+ return true;
+ }
+
+ @Override
+ public Value getNonNullInput() {
+ return array();
+ }
+
+ @Override
public void constrainType(TypeConstraintResolver constraintResolver) {
constraintResolver.constrainArrayMemberType(type, value(), array(), t -> type = t);
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/FieldInstruction.java b/src/main/java/com/android/tools/r8/ir/code/FieldInstruction.java
index 2900491..ac7d337 100644
--- a/src/main/java/com/android/tools/r8/ir/code/FieldInstruction.java
+++ b/src/main/java/com/android/tools/r8/ir/code/FieldInstruction.java
@@ -3,7 +3,14 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.ir.code;
+import static com.android.tools.r8.optimize.MemberRebindingAnalysis.isMemberVisibleFromOriginalContext;
+
+import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.analysis.AbstractError;
import java.util.Collections;
import java.util.List;
@@ -42,6 +49,53 @@
}
@Override
+ public AbstractError instructionInstanceCanThrow(
+ AppView<? extends AppInfo> appView, DexType context) {
+ // Not applicable for D8.
+ if (!appView.enableWholeProgramOptimizations()) {
+ return AbstractError.top();
+ }
+
+ // TODO(b/123857022): Should be possible to use definitionFor().
+ DexEncodedField resolvedField = appView.appInfo().resolveField(getField());
+ // * NoSuchFieldError (resolution failure).
+ if (resolvedField == null) {
+ return AbstractError.specific(appView.dexItemFactory().noSuchFieldErrorType);
+ }
+ // * IncompatibleClassChangeError (instance-* for static field and vice versa).
+ if (resolvedField.isStaticMember()) {
+ if (isInstanceGet() || isInstancePut()) {
+ return AbstractError.specific(appView.dexItemFactory().icceType);
+ }
+ } else {
+ if (isStaticGet() || isStaticPut()) {
+ return AbstractError.specific(appView.dexItemFactory().icceType);
+ }
+ }
+ // * IllegalAccessError (not visible from the access context).
+ if (!isMemberVisibleFromOriginalContext(
+ appView, context, resolvedField.field.holder, resolvedField.accessFlags)) {
+ return AbstractError.specific(appView.dexItemFactory().illegalAccessErrorType);
+ }
+ // * NullPointerException (null receiver).
+ if (isInstanceGet() || isInstancePut()) {
+ Value receiver = inValues.get(0);
+ if (receiver.isAlwaysNull(appView) || receiver.typeLattice.isNullable()) {
+ return AbstractError.specific(appView.dexItemFactory().npeType);
+ }
+ }
+ // May trigger <clinit> that may have side effects.
+ if (field.holder.classInitializationMayHaveSideEffects(
+ appView,
+ // Types that are a super type of `context` are guaranteed to be initialized already.
+ type -> appView.isSubtype(context, type).isTrue())) {
+ return AbstractError.top();
+ }
+
+ return AbstractError.bottom();
+ }
+
+ @Override
public boolean hasInvariantOutType() {
// TODO(jsjeon): what if the target field is known to be non-null?
return true;
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java b/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java
index 86d549d..6bb754f 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java
@@ -4,8 +4,6 @@
package com.android.tools.r8.ir.code;
-import static com.android.tools.r8.optimize.MemberRebindingAnalysis.isMemberVisibleFromOriginalContext;
-
import com.android.tools.r8.cf.LoadStoreHelper;
import com.android.tools.r8.cf.TypeVerificationHelper;
import com.android.tools.r8.cf.code.CfFieldInstruction;
@@ -20,7 +18,6 @@
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AppInfo;
import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexType;
@@ -105,30 +102,22 @@
}
@Override
- public boolean canBeDeadCode(AppView<? extends AppInfo> appView, IRCode code) {
- // Not applicable for D8.
- if (!appView.enableWholeProgramOptimizations()) {
- return false;
- }
+ public boolean instructionMayHaveSideEffects(
+ AppView<? extends AppInfo> appView, DexType context) {
+ return instructionInstanceCanThrow(appView, context).isThrowing();
+ }
+ @Override
+ public boolean canBeDeadCode(AppView<? extends AppInfo> appView, IRCode code) {
// instance-get can be dead code as long as it cannot have any of the following:
// * NoSuchFieldError (resolution failure)
+ // * IncompatibleClassChangeError (instance-* instruction for static fields)
// * IllegalAccessError (not visible from the access context)
- // * NullPointerException (null receiver).
- // TODO(b/123857022): Should be possible to use definitionFor().
- AppInfo appInfo = appView.appInfo();
- DexEncodedField resolvedField = appInfo.resolveField(getField());
- if (resolvedField == null) {
- return false;
- }
- if (!isMemberVisibleFromOriginalContext(
- appView,
- code.method.method.holder,
- resolvedField.field.holder,
- resolvedField.accessFlags)) {
- return false;
- }
- return object().getTypeLattice().nullability().isDefinitelyNotNull();
+ // * NullPointerException (null receiver)
+ boolean canBeDeadCode = !instructionMayHaveSideEffects(appView, code.method.method.holder);
+ assert appView.enableWholeProgramOptimizations() || !canBeDeadCode
+ : "Expected instance-get instruction to have side effects in D8";
+ return canBeDeadCode;
}
@Override
@@ -200,6 +189,16 @@
}
@Override
+ public boolean throwsOnNullInput() {
+ return true;
+ }
+
+ @Override
+ public Value getNonNullInput() {
+ return object();
+ }
+
+ @Override
public boolean definitelyTriggersClassInitialization(
DexType clazz,
DexType context,
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstancePut.java b/src/main/java/com/android/tools/r8/ir/code/InstancePut.java
index e4b062f..20c4733 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstancePut.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstancePut.java
@@ -174,6 +174,16 @@
}
@Override
+ public boolean throwsOnNullInput() {
+ return true;
+ }
+
+ @Override
+ public Value getNonNullInput() {
+ return object();
+ }
+
+ @Override
public boolean definitelyTriggersClassInitialization(
DexType clazz,
DexType context,
diff --git a/src/main/java/com/android/tools/r8/ir/code/Instruction.java b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
index dcbef34..bc9bc6b 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Instruction.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
@@ -12,6 +12,7 @@
import com.android.tools.r8.graph.DebugLocalInfo;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.analysis.AbstractError;
import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis.AnalysisAssumption;
import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis.Query;
import com.android.tools.r8.ir.analysis.constant.Bottom;
@@ -514,8 +515,15 @@
return instructionInstanceCanThrow();
}
+ public AbstractError instructionInstanceCanThrow(
+ AppView<? extends AppInfo> appView, DexType context) {
+ return instructionInstanceCanThrow() ? AbstractError.top() : AbstractError.bottom();
+ }
+
/** Returns true is this instruction can be treated as dead code if its outputs are not used. */
public boolean canBeDeadCode(AppView<? extends AppInfo> appView, IRCode code) {
+ // TODO(b/129530569): instructions with fine-grained side effect analysis may use:
+ // return !instructionMayHaveSideEffects(appView, code.method.method.holder);
return !instructionInstanceCanThrow();
}
@@ -1264,6 +1272,21 @@
return false;
}
+ // Any instructions that override `throwsNpeIfValueIsNull` should override this too.
+ // `throw` instruction is also throwing if the input is null. However, this util and the below
+ // one are used to insert Assume instruction if input is known to be non-null or replace the
+ // instruction with `throw null`. In either case, nullability of `throw` does not add anything
+ // new: `throw X` with null reference X has the same effect as `throw null`; `throw X` with
+ // non-null reference X ends, hence having non-null X after that instruction is non-sense.
+ public boolean throwsOnNullInput() {
+ return false;
+ }
+
+ // Any instructions that override `throwsOnNullInput` should override this too.
+ public Value getNonNullInput() {
+ throw new Unreachable("Should conform to throwsOnNullInput.");
+ }
+
/**
* Indicates whether the instruction triggers the class initialization (i.e. the <clinit> method)
* of the given class at runtime execution.
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java b/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java
index 1ca8df7..2e15003 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java
@@ -48,6 +48,16 @@
}
@Override
+ public boolean throwsOnNullInput() {
+ return true;
+ }
+
+ @Override
+ public Value getNonNullInput() {
+ return getReceiver();
+ }
+
+ @Override
public boolean verifyTypes(AppView<? extends AppInfo> appView) {
assert super.verifyTypes(appView);
diff --git a/src/main/java/com/android/tools/r8/ir/code/Monitor.java b/src/main/java/com/android/tools/r8/ir/code/Monitor.java
index 60b99f8..8d691db 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Monitor.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Monitor.java
@@ -134,4 +134,14 @@
public boolean throwsNpeIfValueIsNull(Value value, DexItemFactory dexItemFactory) {
return object() == value;
}
+
+ @Override
+ public boolean throwsOnNullInput() {
+ return true;
+ }
+
+ @Override
+ public Value getNonNullInput() {
+ return object();
+ }
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/StaticGet.java b/src/main/java/com/android/tools/r8/ir/code/StaticGet.java
index 328b2f2..6c48db2 100644
--- a/src/main/java/com/android/tools/r8/ir/code/StaticGet.java
+++ b/src/main/java/com/android/tools/r8/ir/code/StaticGet.java
@@ -3,8 +3,6 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.ir.code;
-import static com.android.tools.r8.optimize.MemberRebindingAnalysis.isMemberVisibleFromOriginalContext;
-
import com.android.tools.r8.cf.LoadStoreHelper;
import com.android.tools.r8.cf.TypeVerificationHelper;
import com.android.tools.r8.cf.code.CfFieldInstruction;
@@ -19,7 +17,6 @@
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AppInfo;
import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis;
@@ -98,36 +95,22 @@
}
@Override
- public boolean canBeDeadCode(AppView<? extends AppInfo> appView, IRCode code) {
- // Not applicable for D8.
- if (!appView.enableWholeProgramOptimizations()) {
- return false;
- }
+ public boolean instructionMayHaveSideEffects(
+ AppView<? extends AppInfo> appView, DexType context) {
+ return instructionInstanceCanThrow(appView, context).isThrowing();
+ }
+ @Override
+ public boolean canBeDeadCode(AppView<? extends AppInfo> appView, IRCode code) {
// static-get can be dead as long as it cannot have any of the following:
// * NoSuchFieldError (resolution failure)
+ // * IncompatibleClassChangeError (static-* instruction for instance fields)
// * IllegalAccessError (not visible from the access context)
// * side-effects in <clinit>
- // TODO(b/123857022): Should be possible to use definitionFor().
- AppInfo appInfo = appView.appInfo();
- DexEncodedField resolvedField = appInfo.resolveField(getField());
- if (resolvedField == null) {
- return false;
- }
- if (!isMemberVisibleFromOriginalContext(
- appView,
- code.method.method.holder,
- resolvedField.field.holder,
- resolvedField.accessFlags)) {
- return false;
- }
- DexType context = code.method.method.holder;
- return !getField()
- .holder
- .classInitializationMayHaveSideEffects(
- appInfo,
- // Types that are a super type of `context` are guaranteed to be initialized already.
- type -> appView.isSubtype(context, type).isTrue());
+ boolean canBeDeadCode = !instructionMayHaveSideEffects(appView, code.method.method.holder);
+ assert appView.enableWholeProgramOptimizations() || !canBeDeadCode
+ : "Expected static-get instruction to have side effects in D8";
+ return canBeDeadCode;
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
index 2e72732..bfdbd88 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
@@ -935,7 +935,7 @@
devirtualizer.devirtualizeInvokeInterface(code, method.method.holder);
}
if (uninstantiatedTypeOptimization != null) {
- uninstantiatedTypeOptimization.rewrite(method, code);
+ uninstantiatedTypeOptimization.rewrite(code);
}
assert code.verifyTypes(appView);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/NonNullTracker.java b/src/main/java/com/android/tools/r8/ir/optimize/NonNullTracker.java
index fffd3da..9d493dc 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/NonNullTracker.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/NonNullTracker.java
@@ -5,7 +5,6 @@
import static com.android.tools.r8.ir.code.DominatorTree.Assumption.MAY_HAVE_UNREACHABLE_BLOCKS;
-import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AppInfo;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexEncodedMethod;
@@ -25,7 +24,6 @@
import com.android.tools.r8.ir.code.Phi;
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.conversion.OptimizationFeedback;
-import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Predicates;
import com.google.common.collect.Sets;
import it.unimi.dsi.fastutil.ints.IntArrayList;
@@ -49,36 +47,6 @@
this.appView = appView;
}
- @VisibleForTesting
- static boolean throwsOnNullInput(Instruction instruction) {
- return (instruction.isInvokeMethodWithReceiver() && !instruction.isInvokeDirect())
- || instruction.isInstanceGet()
- || instruction.isInstancePut()
- || instruction.isArrayGet()
- || instruction.isArrayPut()
- || instruction.isArrayLength()
- || instruction.isMonitor();
- }
-
- private Value getNonNullInput(Instruction instruction) {
- if (instruction.isInvokeMethodWithReceiver()) {
- return instruction.asInvokeMethodWithReceiver().getReceiver();
- } else if (instruction.isInstanceGet()) {
- return instruction.asInstanceGet().object();
- } else if (instruction.isInstancePut()) {
- return instruction.asInstancePut().object();
- } else if (instruction.isArrayGet()) {
- return instruction.asArrayGet().array();
- } else if (instruction.isArrayPut()) {
- return instruction.asArrayPut().array();
- } else if (instruction.isArrayLength()) {
- return instruction.asArrayLength().array();
- } else if (instruction.isMonitor()) {
- return instruction.asMonitor().object();
- }
- throw new Unreachable("Should conform to throwsOnNullInput.");
- }
-
public void addNonNull(IRCode code) {
addNonNullInPart(code, code.listIterator(), Predicates.alwaysTrue());
}
@@ -114,10 +82,10 @@
knownToBeNonNullValues.add(knownToBeNonNullValue);
}
}
- if (throwsOnNullInput(current)) {
- Value knownToBeNonNullValue = getNonNullInput(current);
- if (isNonNullCandidate(knownToBeNonNullValue)) {
- knownToBeNonNullValues.add(knownToBeNonNullValue);
+ if (current.throwsOnNullInput()) {
+ Value couldBeNonNull = current.getNonNullInput();
+ if (isNonNullCandidate(couldBeNonNull)) {
+ knownToBeNonNullValues.add(couldBeNonNull);
}
}
if (current.isInvokeMethod() && !current.isInvokePolymorphic()) {
@@ -365,8 +333,8 @@
return predecessorIndexes;
}
- private boolean isNonNullCandidate(Value knownToBeNonNullValue) {
- TypeLatticeElement typeLattice = knownToBeNonNullValue.getTypeLattice();
+ private static boolean isNonNullCandidate(Value couldBeNonNullValue) {
+ TypeLatticeElement typeLattice = couldBeNonNullValue.getTypeLattice();
return typeLattice.isReference() && !typeLattice.isNullType() && typeLattice.isNullable();
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java b/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java
index 31ae148..40f20ed 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java
@@ -8,6 +8,7 @@
import static com.android.tools.r8.ir.optimize.UninstantiatedTypeOptimization.Strategy.DISALLOW_ARGUMENT_REMOVAL;
import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.AppInfoWithSubtyping;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexEncodedField;
@@ -23,6 +24,7 @@
import com.android.tools.r8.graph.GraphLense.RewrittenPrototypeDescription.RemovedArgumentInfo;
import com.android.tools.r8.graph.GraphLense.RewrittenPrototypeDescription.RemovedArgumentsInfo;
import com.android.tools.r8.graph.TopDownClassHierarchyTraversal;
+import com.android.tools.r8.ir.analysis.AbstractError;
import com.android.tools.r8.ir.analysis.type.Nullability;
import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
import com.android.tools.r8.ir.code.BasicBlock;
@@ -104,8 +106,10 @@
private final AppView<AppInfoWithLiveness> appView;
private int numberOfInstanceGetOrInstancePutWithNullReceiver = 0;
+ private int numberOfArrayInstructionsWithNullArray = 0;
private int numberOfInvokesWithNullArgument = 0;
private int numberOfInvokesWithNullReceiver = 0;
+ private int numberOfMonitorWithNullReceiver = 0;
public UninstantiatedTypeOptimization(AppView<AppInfoWithLiveness> appView) {
this.appView = appView;
@@ -349,7 +353,7 @@
method.holder, dexItemFactory.createProto(newReturnType, newParameters), method.name);
}
- public void rewrite(DexEncodedMethod method, IRCode code) {
+ public void rewrite(IRCode code) {
Set<BasicBlock> blocksToBeRemoved = Sets.newIdentityHashSet();
ListIterator<BasicBlock> blockIterator = code.listIterator();
while (blockIterator.hasNext()) {
@@ -360,22 +364,35 @@
InstructionListIterator instructionIterator = block.listIterator();
while (instructionIterator.hasNext()) {
Instruction instruction = instructionIterator.next();
- if (instruction.isFieldInstruction()) {
- if (instruction.isInstanceGet() || instruction.isInstancePut()) {
- rewriteInstanceFieldInstruction(
- instruction.asFieldInstruction(),
- blockIterator,
- instructionIterator,
- code,
- blocksToBeRemoved);
- } else {
- rewriteStaticFieldInstruction(
- instruction.asFieldInstruction(),
- blockIterator,
- instructionIterator,
- code,
- blocksToBeRemoved);
+ if (instruction.throwsOnNullInput()) {
+ Value couldBeNullValue = instruction.getNonNullInput();
+ if (isThrowNullCandidate(
+ couldBeNullValue, instruction, appView, code.method.method.holder)) {
+ if (instruction.isInstanceGet() || instruction.isInstancePut()) {
+ ++numberOfInstanceGetOrInstancePutWithNullReceiver;
+ } else if (instruction.isInvokeMethodWithReceiver()) {
+ ++numberOfInvokesWithNullReceiver;
+ } else if (instruction.isArrayGet()
+ || instruction.isArrayPut()
+ || instruction.isArrayLength()) {
+ ++numberOfArrayInstructionsWithNullArray;
+ } else if (instruction.isMonitor()) {
+ ++numberOfMonitorWithNullReceiver;
+ } else {
+ assert false;
+ }
+ instructionIterator.replaceCurrentInstructionWithThrowNull(
+ appView, code, blockIterator, blocksToBeRemoved);
+ continue;
}
+ }
+ if (instruction.isFieldInstruction()) {
+ rewriteFieldInstruction(
+ instruction.asFieldInstruction(),
+ blockIterator,
+ instructionIterator,
+ code,
+ blocksToBeRemoved);
} else if (instruction.isInvokeMethod()) {
rewriteInvoke(
instruction.asInvokeMethod(),
@@ -392,6 +409,29 @@
assert code.isConsistentSSA();
}
+ private static boolean isThrowNullCandidate(
+ Value couldBeNullValue,
+ Instruction current,
+ AppView<? extends AppInfoWithSubtyping> appView,
+ DexType context) {
+ if (!couldBeNullValue.isAlwaysNull(appView)) {
+ return false;
+ }
+ if (current.isFieldInstruction()) {
+ // Other resolution-related errors come first.
+ AbstractError abstractError =
+ current.asFieldInstruction().instructionInstanceCanThrow(appView, context);
+ if (abstractError.isThrowing()
+ && abstractError.getSpecificError(appView.dexItemFactory())
+ != appView.dexItemFactory().npeType) {
+ // We can't replace the current instruction with `throw null` if it may throw another
+ // Error/Exception than NullPointerException.
+ return false;
+ }
+ }
+ return true;
+ }
+
public void logResults() {
assert Log.ENABLED;
Log.info(
@@ -399,50 +439,19 @@
"Number of instance-get/instance-put with null receiver: %s",
numberOfInstanceGetOrInstancePutWithNullReceiver);
Log.info(
+ getClass(),
+ "Number of array instructions with null reference: %s",
+ numberOfArrayInstructionsWithNullArray);
+ Log.info(
getClass(), "Number of invokes with null argument: %s", numberOfInvokesWithNullArgument);
Log.info(
getClass(), "Number of invokes with null receiver: %s", numberOfInvokesWithNullReceiver);
+ Log.info(
+ getClass(), "Number of monitor with null receiver: %s", numberOfMonitorWithNullReceiver);
}
- private void rewriteInstanceFieldInstruction(
- FieldInstruction instruction,
- ListIterator<BasicBlock> blockIterator,
- InstructionListIterator instructionIterator,
- IRCode code,
- Set<BasicBlock> blocksToBeRemoved) {
- assert instruction.isInstanceGet() || instruction.isInstancePut();
- boolean replacedByThrowNull = false;
-
- Value receiver = instruction.inValues().get(0);
- if (receiver.isAlwaysNull(appView)) {
- // Unable to rewrite instruction if the receiver is defined from "const-number 0", since this
- // would lead to an IncompatibleClassChangeError (see MemberResolutionTest#lookupStaticField-
- // WithFieldGetFromNullReferenceDirectly).
- if (!receiver.getTypeLattice().isDefinitelyNull()) {
- instructionIterator.replaceCurrentInstructionWithThrowNull(
- appView, code, blockIterator, blocksToBeRemoved);
- ++numberOfInstanceGetOrInstancePutWithNullReceiver;
- replacedByThrowNull = true;
- }
- }
-
- if (!replacedByThrowNull) {
- rewriteFieldInstruction(
- instruction, blockIterator, instructionIterator, code, blocksToBeRemoved);
- }
- }
-
- private void rewriteStaticFieldInstruction(
- FieldInstruction instruction,
- ListIterator<BasicBlock> blockIterator,
- InstructionListIterator instructionIterator,
- IRCode code,
- Set<BasicBlock> blocksToBeRemoved) {
- assert instruction.isStaticGet() || instruction.isStaticPut();
- rewriteFieldInstruction(
- instruction, blockIterator, instructionIterator, code, blocksToBeRemoved);
- }
-
+ // instance-{get|put} with a null receiver has already been rewritten to `throw null`.
+ // At this point, field-instruction whose target field type is uninstantiated will be handled.
private void rewriteFieldInstruction(
FieldInstruction instruction,
ListIterator<BasicBlock> blockIterator,
@@ -451,13 +460,18 @@
Set<BasicBlock> blocksToBeRemoved) {
DexType fieldType = instruction.getField().type;
if (fieldType.isAlwaysNull(appView)) {
- // Before trying to remove this instruction, we need to be sure that the field actually
- // exists. Otherwise this instruction would throw a NoSuchFieldError exception.
- DexEncodedField field = appView.definitionFor(instruction.getField());
- if (field == null) {
+ AbstractError abstractError =
+ instruction.instructionInstanceCanThrow(appView, code.method.method.holder);
+ if (abstractError.isThrowing()) {
return;
}
+ // TODO(b/123857022): Should be possible to use definitionFor().
+ DexEncodedField field = appView.appInfo().resolveField(instruction.getField());
+ if (field == null) {
+ assert false : "Expected field-instruction with non-existent field to throw";
+ return;
+ }
// We also need to be sure that this field instruction cannot trigger static class
// initialization.
if (field.field.holder != code.method.method.holder) {
@@ -497,22 +511,16 @@
}
}
+ // invoke instructions with a null receiver has already been rewritten to `throw null`.
+ // At this point, we attempt to explore non-null-param-or-throw optimization info and replace
+ // the invocation with `throw null` if an argument is known to be null and the method is going to
+ // throw for that null argument.
private void rewriteInvoke(
InvokeMethod invoke,
ListIterator<BasicBlock> blockIterator,
InstructionListIterator instructionIterator,
IRCode code,
Set<BasicBlock> blocksToBeRemoved) {
- if (invoke.isInvokeMethodWithReceiver()) {
- Value receiver = invoke.asInvokeMethodWithReceiver().getReceiver();
- if (receiver.isAlwaysNull(appView)) {
- instructionIterator.replaceCurrentInstructionWithThrowNull(
- appView, code, blockIterator, blocksToBeRemoved);
- ++numberOfInvokesWithNullReceiver;
- return;
- }
- }
-
DexEncodedMethod target =
invoke.lookupSingleTarget(appView.appInfo(), code.method.method.holder);
if (target == null) {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/AlwaysThrowNullTest.java b/src/test/java/com/android/tools/r8/ir/optimize/AlwaysThrowNullTest.java
new file mode 100644
index 0000000..cc5893c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/AlwaysThrowNullTest.java
@@ -0,0 +1,254 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.ir.optimize;
+
+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 static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.D8TestRunResult;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NeverPropagateValue;
+import com.android.tools.r8.R8TestRunResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.TestRunResult;
+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.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+class AlwaysThrowNullTestClass {
+ @NeverMerge
+ static class Base {
+ Object dead;
+
+ void foo() {
+ System.out.println("Base::foo");
+ }
+ }
+
+ static class Uninitialized extends Base {
+ }
+
+ @NeverPropagateValue
+ @NeverInline
+ static void uninstantiatedInstancePutWithNPEGuard(Uninitialized arg) {
+ try {
+ arg.dead = new Object();
+ } catch (NullPointerException npe) {
+ System.out.println("Expected NPE");
+ }
+ }
+
+ @NeverPropagateValue
+ @NeverInline
+ static void uninstantiatedInstancePutWithOtherGuard(Uninitialized arg) {
+ try {
+ arg.dead = new Object();
+ } catch (IllegalArgumentException e) {
+ fail("Unexpected exception kind");
+ }
+ }
+
+ @NeverPropagateValue
+ @NeverInline
+ static void uninstantiatedInstanceInvoke(Uninitialized arg) {
+ arg.foo();
+ // Dead code.
+ System.out.println("invoke-virtual with null receiver: " + arg.dead.toString());
+ }
+
+ static Object nullObject() {
+ return null;
+ }
+
+ @NeverPropagateValue
+ @NeverInline
+ static void nullReferenceInvoke() {
+ try {
+ Object o = nullObject();
+ o.hashCode();
+ // Dead code.
+ System.out.println("invoke-virtual with null receiver: " + o.toString());
+ } catch (NullPointerException npe) {
+ System.out.println("Expected NPE");
+ }
+ }
+
+ @NeverPropagateValue
+ @NeverInline
+ static void uninstantiatedInstancePut(Uninitialized arg) {
+ arg.dead = new Object();
+ // Dead code.
+ System.out.println("instance-put with null receiver: " + arg.dead.toString());
+ }
+
+ @NeverPropagateValue
+ @NeverInline
+ static void uninstantiatedInstanceGet(Uninitialized arg) {
+ Object dead = arg.dead;
+ // Dead code.
+ System.out.println("instance-get with null receiver: " + dead.toString());
+ }
+
+ public static void main(String[] args) {
+ Base b = new Base();
+ try {
+ uninstantiatedInstancePutWithNPEGuard(null);
+ } catch (NullPointerException npe) {
+ fail("Unexpected NPE");
+ }
+ try {
+ uninstantiatedInstancePutWithOtherGuard(null);
+ fail("Expected NullPointerException");
+ } catch (NullPointerException npe) {
+ System.out.println("Expected NPE");
+ }
+ try {
+ uninstantiatedInstanceInvoke(null);
+ fail("Expected NullPointerException");
+ } catch (NullPointerException npe) {
+ System.out.println("Expected NPE");
+ }
+ try {
+ nullReferenceInvoke();
+ } catch (NullPointerException npe) {
+ fail("Unexpected NPE");
+ }
+ try {
+ uninstantiatedInstancePut(null);
+ fail("Expected NullPointerException");
+ } catch (NullPointerException npe) {
+ System.out.println("Expected NPE");
+ }
+ try {
+ uninstantiatedInstanceGet(null);
+ fail("Expected NullPointerException");
+ } catch (NullPointerException npe) {
+ System.out.println("Expected NPE");
+ }
+ }
+}
+
+@RunWith(Parameterized.class)
+public class AlwaysThrowNullTest extends TestBase {
+ private static final String JAVA_OUTPUT = StringUtils.lines(
+ "Expected NPE",
+ "Expected NPE",
+ "Expected NPE",
+ "Expected NPE",
+ "Expected NPE",
+ "Expected NPE"
+ );
+ private static final Class<?> MAIN = AlwaysThrowNullTestClass.class;
+ private static final List<String> METHODS_WITH_NPE_GUARD = ImmutableList.of(
+ "uninstantiatedInstancePutWithNPEGuard",
+ "nullReferenceInvoke"
+ );
+ private static final List<String> METHODS_WITHOUT_GUARD = ImmutableList.of(
+ "uninstantiatedInstanceInvoke",
+ "uninstantiatedInstancePut",
+ "uninstantiatedInstanceGet"
+ );
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimes().build();
+ }
+
+ private final TestParameters parameters;
+
+ public AlwaysThrowNullTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testJvmOutput() throws Exception {
+ assumeTrue("Only run JVM reference once (for CF backend)", parameters.isCfRuntime());
+ testForJvm()
+ .addTestClasspath()
+ .run(parameters.getRuntime(), MAIN)
+ .assertSuccessWithOutput(JAVA_OUTPUT);
+ }
+
+ private void test(TestRunResult result, boolean hasLiveness) throws Exception {
+ CodeInspector codeInspector = result.inspector();
+ ClassSubject mainClass = codeInspector.clazz(MAIN);
+
+ int expectedThrow = hasLiveness ? 1 : 0;
+ for (String methodName : METHODS_WITH_NPE_GUARD) {
+ MethodSubject withNPEGuard = mainClass.uniqueMethodWithName(methodName);
+ assertThat(withNPEGuard, isPresent());
+ // catch handlers could be split, and thus not always 1, but some small positive numbers.
+ assertTrue(
+ Streams.stream(
+ withNPEGuard.iterateTryCatches(
+ t -> t.isCatching(NullPointerException.class.getName()))
+ ).count() > 0);
+ assertEquals(
+ expectedThrow,
+ Streams.stream(withNPEGuard.iterateInstructions(InstructionSubject::isThrow)).count());
+ }
+
+ int expectedHandler = hasLiveness ? 0 : 1;
+ MethodSubject withOtherGuard =
+ mainClass.uniqueMethodWithName("uninstantiatedInstancePutWithOtherGuard");
+ assertThat(withOtherGuard, isPresent());
+ assertEquals(expectedHandler, Streams.stream(withOtherGuard.iterateTryCatches()).count());
+
+ for (String methodName : METHODS_WITHOUT_GUARD) {
+ MethodSubject mtd = mainClass.uniqueMethodWithName(methodName);
+ assertThat(mtd, isPresent());
+ assertEquals(
+ hasLiveness,
+ Streams.stream(mtd.iterateInstructions(InstructionSubject::isInvoke)).count() == 0);
+ assertEquals(
+ expectedThrow,
+ Streams.stream(mtd.iterateInstructions(InstructionSubject::isThrow)).count());
+ }
+ }
+
+ @Test
+ public void testD8() throws Exception {
+ assumeTrue("Only run D8 for Dex backend", parameters.isDexRuntime());
+ D8TestRunResult result =
+ testForD8()
+ .release()
+ .addProgramClassesAndInnerClasses(MAIN)
+ .setMinApi(parameters.getRuntime())
+ .run(parameters.getRuntime(), MAIN)
+ .assertSuccessWithOutput(JAVA_OUTPUT);
+ test(result, false);
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ R8TestRunResult result =
+ testForR8(parameters.getBackend())
+ .addProgramClassesAndInnerClasses(MAIN)
+ .enableMergeAnnotations()
+ .enableInliningAnnotations()
+ .enableMemberValuePropagationAnnotations()
+ .addKeepMainRule(MAIN)
+ .noMinification()
+ .setMinApi(parameters.getRuntime())
+ .run(parameters.getRuntime(), MAIN)
+ .assertSuccessWithOutput(JAVA_OUTPUT);
+ test(result, true);
+ }
+
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/NonNullTrackerTest.java b/src/test/java/com/android/tools/r8/ir/optimize/NonNullTrackerTest.java
index 3f3ccd2..26a3f4e 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/NonNullTrackerTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/NonNullTrackerTest.java
@@ -64,7 +64,7 @@
if (curr.isAssumeNonNull()) {
// Make sure non-null is added to the right place.
assertTrue(prev == null
- || NonNullTracker.throwsOnNullInput(prev)
+ || prev.throwsOnNullInput()
|| (prev.isIf() && prev.asIf().isZeroTest())
|| !curr.getBlock().getPredecessors().contains(prev.getBlock()));
// Make sure non-null is used or inserted for arguments.
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 9e22ade..df4598f 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
@@ -6,6 +6,7 @@
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.fail;
import static org.junit.Assume.assumeTrue;
import com.android.tools.r8.D8TestRunResult;
@@ -77,12 +78,14 @@
unknownArg(instance);
try {
unknownArg(null);
+ fail("Expected NullPointerException");
} catch (NullPointerException npe) {
System.out.println("Expected NPE");
}
try {
consumeUninitialized(null);
+ fail("Expected NullPointerException");
} catch (NullPointerException npe) {
System.out.println("Expected NPE");
}
@@ -167,35 +170,38 @@
@Test
public void testD8() throws Exception {
assumeTrue("Only run D8 for Dex backend", parameters.isDexRuntime());
- D8TestRunResult result = testForD8()
- .debug()
- .addProgramClassesAndInnerClasses(MAIN)
- .setMinApi(parameters.getRuntime())
- .run(parameters.getRuntime(), MAIN)
- .assertSuccessWithOutput(JAVA_OUTPUT);
+ D8TestRunResult result =
+ testForD8()
+ .debug()
+ .addProgramClassesAndInnerClasses(MAIN)
+ .setMinApi(parameters.getRuntime())
+ .run(parameters.getRuntime(), MAIN)
+ .assertSuccessWithOutput(JAVA_OUTPUT);
test(result, 2, 1);
- result = testForD8()
- .release()
- .addProgramClassesAndInnerClasses(MAIN)
- .setMinApi(parameters.getRuntime())
- .run(parameters.getRuntime(), MAIN)
- .assertSuccessWithOutput(JAVA_OUTPUT);
+ result =
+ testForD8()
+ .release()
+ .addProgramClassesAndInnerClasses(MAIN)
+ .setMinApi(parameters.getRuntime())
+ .run(parameters.getRuntime(), MAIN)
+ .assertSuccessWithOutput(JAVA_OUTPUT);
test(result, 0, 1);
}
@Test
public void testR8() throws Exception {
assumeTrue("CF disables move result optimization", parameters.isDexRuntime());
- R8TestRunResult result = testForR8(parameters.getBackend())
- .addProgramClassesAndInnerClasses(MAIN)
- .enableInliningAnnotations()
- .enableMemberValuePropagationAnnotations()
- .addKeepMainRule(MAIN)
- .noMinification()
- .setMinApi(parameters.getRuntime())
- .run(parameters.getRuntime(), MAIN)
- .assertSuccessWithOutput(JAVA_OUTPUT);
+ R8TestRunResult result =
+ testForR8(parameters.getBackend())
+ .addProgramClassesAndInnerClasses(MAIN)
+ .enableInliningAnnotations()
+ .enableMemberValuePropagationAnnotations()
+ .addKeepMainRule(MAIN)
+ .noMinification()
+ .setMinApi(parameters.getRuntime())
+ .run(parameters.getRuntime(), MAIN)
+ .assertSuccessWithOutput(JAVA_OUTPUT);
test(result, 0, 0);
}
}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/instanceofremoval/UninstantiatedAnnotationTypeTest.java b/src/test/java/com/android/tools/r8/ir/optimize/instanceofremoval/UninstantiatedAnnotationTypeTest.java
index 7f61c30..98f28ba 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/instanceofremoval/UninstantiatedAnnotationTypeTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/instanceofremoval/UninstantiatedAnnotationTypeTest.java
@@ -5,12 +5,13 @@
package com.android.tools.r8.ir.optimize.instanceofremoval;
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.assertThat;
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.TestBase;
-import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.InstructionSubject;
@@ -58,15 +59,15 @@
}
}
- @Parameters(name = "Backend: {0}")
- public static Backend[] data() {
- return ToolHelper.getBackends();
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimes().build();
}
- private final Backend backend;
+ private final TestParameters parameters;
- public UninstantiatedAnnotationTypeTest(Backend backend) {
- this.backend = backend;
+ public UninstantiatedAnnotationTypeTest(TestParameters parameters) {
+ this.parameters = parameters;
}
@Test
@@ -76,12 +77,13 @@
testForJvm().addTestClasspath().run(TestClass.class).assertSuccessWithOutput(expected);
CodeInspector inspector =
- testForR8(backend)
+ testForR8(parameters.getBackend())
.addInnerClasses(UninstantiatedAnnotationTypeTest.class)
.addKeepMainRule(TestClass.class)
.addKeepRules("-keepattributes RuntimeVisibleAnnotations")
.enableInliningAnnotations()
- .run(TestClass.class)
+ .setMinApi(parameters.getRuntime())
+ .run(parameters.getRuntime(), TestClass.class)
.assertSuccessWithOutput(expected)
.inspector();
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/instanceofremoval/UninstantiatedLibraryTypeTest.java b/src/test/java/com/android/tools/r8/ir/optimize/instanceofremoval/UninstantiatedLibraryTypeTest.java
index cff27b4..62db9fc 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/instanceofremoval/UninstantiatedLibraryTypeTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/instanceofremoval/UninstantiatedLibraryTypeTest.java
@@ -5,12 +5,13 @@
package com.android.tools.r8.ir.optimize.instanceofremoval;
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.assertThat;
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.TestBase;
-import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.InstructionSubject;
@@ -59,15 +60,15 @@
}
}
- @Parameters(name = "Backend: {0}")
- public static Backend[] data() {
- return ToolHelper.getBackends();
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimes().build();
}
- private final Backend backend;
+ private final TestParameters parameters;
- public UninstantiatedLibraryTypeTest(Backend backend) {
- this.backend = backend;
+ public UninstantiatedLibraryTypeTest(TestParameters parameters) {
+ this.parameters = parameters;
}
@Test
@@ -77,11 +78,12 @@
testForJvm().addTestClasspath().run(TestClass.class).assertSuccessWithOutput(expected);
CodeInspector inspector =
- testForR8(backend)
+ testForR8(parameters.getBackend())
.addInnerClasses(UninstantiatedLibraryTypeTest.class)
.addKeepMainRule(TestClass.class)
.enableInliningAnnotations()
- .run(TestClass.class)
+ .setMinApi(parameters.getRuntime())
+ .run(parameters.getRuntime(), TestClass.class)
.assertSuccessWithOutput(expected)
.inspector();
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/instanceofremoval/UninstantiatedProgramTypeTest.java b/src/test/java/com/android/tools/r8/ir/optimize/instanceofremoval/UninstantiatedProgramTypeTest.java
index df2739a..471405d 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/instanceofremoval/UninstantiatedProgramTypeTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/instanceofremoval/UninstantiatedProgramTypeTest.java
@@ -5,13 +5,14 @@
package com.android.tools.r8.ir.optimize.instanceofremoval;
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 com.android.tools.r8.NeverInline;
import com.android.tools.r8.TestBase;
-import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.InstructionSubject;
@@ -58,15 +59,15 @@
}
}
- @Parameters(name = "Backend: {0}")
- public static Backend[] data() {
- return ToolHelper.getBackends();
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimes().build();
}
- private final Backend backend;
+ private final TestParameters parameters;
- public UninstantiatedProgramTypeTest(Backend backend) {
- this.backend = backend;
+ public UninstantiatedProgramTypeTest(TestParameters parameters) {
+ this.parameters = parameters;
}
@Test
@@ -76,11 +77,12 @@
testForJvm().addTestClasspath().run(TestClass.class).assertSuccessWithOutput(expected);
CodeInspector inspector =
- testForR8(backend)
+ testForR8(parameters.getBackend())
.addInnerClasses(UninstantiatedProgramTypeTest.class)
.addKeepMainRule(TestClass.class)
.enableInliningAnnotations()
- .run(TestClass.class)
+ .setMinApi(parameters.getRuntime())
+ .run(parameters.getRuntime(), TestClass.class)
.assertSuccessWithOutput(expected)
.inspector();
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerTest.java b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerTest.java
index b5071f4..a676b5d 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerTest.java
@@ -6,17 +6,17 @@
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.assertFalse;
import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
-import static org.junit.Assume.assumeTrue;
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.TestRunResult;
-import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.code.Instruction;
import com.android.tools.r8.code.InvokeDirect;
import com.android.tools.r8.code.InvokeStatic;
@@ -60,20 +60,20 @@
@RunWith(Parameterized.class)
public class ClassStaticizerTest 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() {
+ // TODO(b/112831361): support for class staticizer in CF backend.
+ return getTestParameters().withDexRuntimes().build();
}
- public ClassStaticizerTest(Backend backend) {
- this.backend = backend;
+ public ClassStaticizerTest(TestParameters parameters) {
+ this.parameters = parameters;
}
@Test
public void testTrivial() throws Exception {
- assumeTrue("b/112831361", backend == Backend.DEX);
Class<?> main = TrivialTestClass.class;
Class<?>[] classes = {
NeverInline.class,
@@ -86,17 +86,18 @@
SimpleWithPhi.Companion.class
};
String javaOutput = runOnJava(main);
- TestRunResult result = testForR8(backend)
- .addProgramClasses(classes)
- .enableProguardTestOptions()
- .enableInliningAnnotations()
- .addKeepMainRule(main)
- .noMinification()
- .addKeepRules("-allowaccessmodification")
- .addKeepRules("-keepattributes InnerClasses,EnclosingMethod")
- .addOptionsModification(this::configure)
- .run(main)
- .assertSuccessWithOutput(javaOutput);
+ TestRunResult result =
+ testForR8(parameters.getBackend())
+ .addProgramClasses(classes)
+ .enableInliningAnnotations()
+ .addKeepMainRule(main)
+ .noMinification()
+ .addKeepRules("-allowaccessmodification")
+ .addKeepRules("-keepattributes InnerClasses,EnclosingMethod")
+ .addOptionsModification(this::configure)
+ .setMinApi(parameters.getRuntime())
+ .run(parameters.getRuntime(), main)
+ .assertSuccessWithOutput(javaOutput);
CodeInspector inspector = result.inspector();
ClassSubject clazz = inspector.clazz(main);
@@ -155,7 +156,6 @@
@Test
public void testMoveToHost_fieldOnly() throws Exception {
- assumeTrue("b/112831361", backend == Backend.DEX);
Class<?> main = MoveToHostFieldOnlyTestClass.class;
Class<?>[] classes = {
NeverInline.class,
@@ -163,15 +163,16 @@
HostOkFieldOnly.class,
CandidateOkFieldOnly.class
};
- TestRunResult result = testForR8(backend)
- .addProgramClasses(classes)
- .enableProguardTestOptions()
- .enableInliningAnnotations()
- .addKeepMainRule(main)
- .noMinification()
- .addKeepRules("-allowaccessmodification")
- .addOptionsModification(this::configure)
- .run(main);
+ TestRunResult result =
+ testForR8(parameters.getBackend())
+ .addProgramClasses(classes)
+ .enableInliningAnnotations()
+ .addKeepMainRule(main)
+ .noMinification()
+ .addKeepRules("-allowaccessmodification")
+ .addOptionsModification(this::configure)
+ .setMinApi(parameters.getRuntime())
+ .run(parameters.getRuntime(), main);
CodeInspector inspector = result.inspector();
ClassSubject clazz = inspector.clazz(main);
@@ -185,7 +186,6 @@
@Test
public void testMoveToHost() throws Exception {
- assumeTrue("b/112831361", backend == Backend.DEX);
Class<?> main = MoveToHostTestClass.class;
Class<?>[] classes = {
NeverInline.class,
@@ -200,16 +200,17 @@
CandidateConflictField.class
};
String javaOutput = runOnJava(main);
- TestRunResult result = testForR8(backend)
- .addProgramClasses(classes)
- .enableProguardTestOptions()
- .enableInliningAnnotations()
- .addKeepMainRule(main)
- .noMinification()
- .addKeepRules("-allowaccessmodification")
- .addOptionsModification(this::configure)
- .run(main)
- .assertSuccessWithOutput(javaOutput);
+ TestRunResult result =
+ testForR8(parameters.getBackend())
+ .addProgramClasses(classes)
+ .enableInliningAnnotations()
+ .addKeepMainRule(main)
+ .noMinification()
+ .addKeepRules("-allowaccessmodification")
+ .addOptionsModification(this::configure)
+ .setMinApi(parameters.getRuntime())
+ .run(parameters.getRuntime(), main)
+ .assertSuccessWithOutput(javaOutput);
CodeInspector inspector = result.inspector();
ClassSubject clazz = inspector.clazz(main);
@@ -313,28 +314,28 @@
private void configure(InternalOptions options) {
options.enableClassInlining = false;
+ options.enableUninstantiatedTypeOptimization = false;
}
-
@Test
public void dualInlinedMethodRewritten() throws Exception {
- assumeTrue("b/112831361", backend == Backend.DEX);
Class<?> main = DualCallTest.class;
Class<?>[] classes = {
DualCallTest.class,
Candidate.class
};
String javaOutput = runOnJava(main);
- TestRunResult result = testForR8(backend)
- .addProgramClasses(classes)
- .enableProguardTestOptions()
- .enableInliningAnnotations()
- .addKeepMainRule(main)
- .noMinification()
- .addKeepRules("-allowaccessmodification")
- .addOptionsModification(this::configure)
- .run(main)
- .assertSuccessWithOutput(javaOutput);
+ TestRunResult result =
+ testForR8(parameters.getBackend())
+ .addProgramClasses(classes)
+ .enableInliningAnnotations()
+ .addKeepMainRule(main)
+ .noMinification()
+ .addKeepRules("-allowaccessmodification")
+ .addOptionsModification(this::configure)
+ .setMinApi(parameters.getRuntime())
+ .run(parameters.getRuntime(), main)
+ .assertSuccessWithOutput(javaOutput);
CodeInspector inspector = result.inspector();
ClassSubject clazz = inspector.clazz(main);
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 4d8dddb..91ffa3d 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
@@ -99,7 +99,11 @@
} catch (NullPointerException npe) {
fail("Not expected: " + npe);
}
- System.out.println(consumeUninitialized(null));
+ try {
+ System.out.println(consumeUninitialized(null));
+ } catch (NullPointerException npe) {
+ fail("Not expected: " + npe);
+ }
// No matter what we pass, that function will return null.
// But, we're not sure about it, hence not optimizing String#valueOf.
@@ -129,7 +133,6 @@
private static final Class<?> MAIN = StringValueOfTestMain.class;
private static final String STRING_DESCRIPTOR = "Ljava/lang/String;";
- private static final String STRING_TYPE = "java.lang.String";
@Parameterized.Parameters(name = "{0}")
public static TestParametersCollection data() {
diff --git a/src/test/java/com/android/tools/r8/naming/EnumMinificationKotlinTest.java b/src/test/java/com/android/tools/r8/naming/EnumMinificationKotlinTest.java
index cd1520e..d56ae8e 100644
--- a/src/test/java/com/android/tools/r8/naming/EnumMinificationKotlinTest.java
+++ b/src/test/java/com/android/tools/r8/naming/EnumMinificationKotlinTest.java
@@ -4,11 +4,11 @@
package com.android.tools.r8.naming;
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.assertThat;
import com.android.tools.r8.KotlinTestBase;
-import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.TestParameters;
import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
import com.android.tools.r8.utils.BooleanUtils;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
@@ -27,30 +27,35 @@
private static final String MAIN_CLASS_NAME = "minify_enum.MainKt";
private static final String ENUM_CLASS_NAME = "minify_enum.MinifyEnum";
- private final Backend backend;
+ private final TestParameters parameters;
private final boolean minify;
- @Parameterized.Parameters(name = "Backend: {0} target: {1} minify: {2}")
+ @Parameterized.Parameters(name = "{0} target: {1} minify: {2}")
public static Collection<Object[]> data() {
- return buildParameters(ToolHelper.getBackends(), KotlinTargetVersion.values(), BooleanUtils.values());
+ return buildParameters(
+ getTestParameters().withAllRuntimes().build(),
+ KotlinTargetVersion.values(),
+ BooleanUtils.values());
}
public EnumMinificationKotlinTest(
- Backend backend, KotlinTargetVersion targetVersion, boolean minify) {
+ TestParameters parameters, KotlinTargetVersion targetVersion, boolean minify) {
super(targetVersion);
- this.backend = backend;
+ this.parameters = parameters;
this.minify = minify;
}
@Test
public void b121221542() throws Exception {
- CodeInspector inspector = testForR8(backend)
- .addProgramFiles(getKotlinJarFile(FOLDER))
- .addProgramFiles(getJavaJarFile(FOLDER))
- .addKeepMainRule(MAIN_CLASS_NAME)
- .minification(minify)
- .run(MAIN_CLASS_NAME)
- .inspector();
+ CodeInspector inspector =
+ testForR8(parameters.getBackend())
+ .addProgramFiles(getKotlinJarFile(FOLDER))
+ .addProgramFiles(getJavaJarFile(FOLDER))
+ .addKeepMainRule(MAIN_CLASS_NAME)
+ .minification(minify)
+ .setMinApi(parameters.getRuntime())
+ .run(parameters.getRuntime(), MAIN_CLASS_NAME)
+ .inspector();
ClassSubject enumClass = inspector.clazz(ENUM_CLASS_NAME);
assertThat(enumClass, isPresent());
assertEquals(minify, enumClass.isRenamed());
diff --git a/src/test/java/com/android/tools/r8/shaking/FieldReadsJasminTest.java b/src/test/java/com/android/tools/r8/shaking/FieldReadsJasminTest.java
index 20b2850..f22cc41 100644
--- a/src/test/java/com/android/tools/r8/shaking/FieldReadsJasminTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/FieldReadsJasminTest.java
@@ -5,13 +5,14 @@
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static com.android.tools.r8.utils.codeinspector.Matchers.isRenamed;
+import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
-import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.jasmin.JasminBuilder;
import com.android.tools.r8.jasmin.JasminBuilder.ClassBuilder;
import com.android.tools.r8.jasmin.JasminTestBase;
@@ -30,15 +31,15 @@
public class FieldReadsJasminTest extends JasminTestBase {
private static final String CLS = "Empty";
private static final String MAIN = "Main";
- private final Backend backend;
+ private final TestParameters parameters;
- @Parameterized.Parameters(name = "Backend: {0}")
- public static Object[] data() {
- return ToolHelper.getBackends();
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimes().build();
}
- public FieldReadsJasminTest(Backend backend) {
- this.backend = backend;
+ public FieldReadsJasminTest(TestParameters parameters) {
+ this.parameters = parameters;
}
@Test
@@ -79,9 +80,10 @@
}
private void ensureNoFields(JasminBuilder app, ClassBuilder clazz) throws Exception {
- testForR8(backend)
+ testForR8(parameters.getBackend())
.addProgramClassFileData(app.buildClasses())
.addKeepRules("-keep class * { <methods>; }")
+ .setMinApi(parameters.getRuntime())
.compile()
.inspect(inspector -> {
ClassSubject classSubject = inspector.clazz(clazz.name);
@@ -129,9 +131,10 @@
ClassBuilder fieldHolder,
String fieldName)
throws Exception {
- testForR8(backend)
+ testForR8(parameters.getBackend())
.addProgramClassFileData(app.buildClasses())
.addKeepRules("-keep class * { <methods>; }")
+ .setMinApi(parameters.getRuntime())
.compile()
.inspect(inspector -> {
FieldSubject fld = inspector.clazz(fieldHolder.name).uniqueFieldWithName(fieldName);
@@ -264,9 +267,10 @@
ClassBuilder fieldHolder,
String fieldName)
throws Exception {
- testForR8(backend)
+ testForR8(parameters.getBackend())
.addProgramClassFileData(app.buildClasses())
.addKeepRules("-keep class * { <methods>; }")
+ .setMinApi(parameters.getRuntime())
.compile()
.inspect(inspector -> {
FieldSubject fld = inspector.clazz(fieldHolder.name).uniqueFieldWithName(fieldName);
diff --git a/src/test/java/com/android/tools/r8/shaking/InvalidTypesTest.java b/src/test/java/com/android/tools/r8/shaking/InvalidTypesTest.java
index 032bd05..64dbadf 100644
--- a/src/test/java/com/android/tools/r8/shaking/InvalidTypesTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/InvalidTypesTest.java
@@ -13,6 +13,7 @@
import com.android.tools.r8.DXTestRunResult;
import com.android.tools.r8.ProguardTestRunResult;
import com.android.tools.r8.R8TestRunResult;
+import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestRunResult;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.ToolHelper.DexVm.Version;
@@ -64,19 +65,20 @@
}
}
- private final Backend backend;
+ private final TestParameters parameters;
private final Mode mode;
private final boolean useInterface;
- public InvalidTypesTest(Backend backend, Mode mode, boolean useInterface) {
- this.backend = backend;
+ public InvalidTypesTest(TestParameters parameters, Mode mode, boolean useInterface) {
+ this.parameters = parameters;
this.mode = mode;
this.useInterface = useInterface;
}
- @Parameters(name = "Backend: {0}, mode: {1}, use interface: {2}")
+ @Parameters(name = "{0}, mode: {1}, use interface: {2}")
public static Collection<Object[]> parameters() {
- return buildParameters(ToolHelper.getBackends(), Mode.values(), BooleanUtils.values());
+ return buildParameters(
+ getTestParameters().withAllRuntimes().build(), Mode.values(), BooleanUtils.values());
}
@Test
@@ -167,7 +169,7 @@
Path inputJar = temp.getRoot().toPath().resolve("input.jar");
jasminBuilder.writeJar(inputJar);
- if (backend == Backend.CF) {
+ if (parameters.isCfRuntime()) {
TestRunResult<?> jvmResult = testForJvm().addClasspath(inputJar).run(mainClass.name);
checkTestRunResult(jvmResult, Compiler.JAVAC);
@@ -179,7 +181,7 @@
.run(mainClass.name);
checkTestRunResult(proguardResult, Compiler.PROGUARD);
} else {
- assert backend == Backend.DEX;
+ assert parameters.isDexRuntime();
DXTestRunResult dxResult = testForDX().addProgramFiles(inputJar).run(mainClass.name);
checkTestRunResult(dxResult, Compiler.DX);
@@ -189,7 +191,7 @@
}
R8TestRunResult r8Result =
- testForR8(backend)
+ testForR8(parameters.getBackend())
.addProgramFiles(inputJar)
.addKeepMainRule(mainClass.name)
.addKeepRules(
@@ -202,7 +204,8 @@
}
})
.enableInliningAnnotations()
- .run(mainClass.name);
+ .setMinApi(parameters.getRuntime())
+ .run(parameters.getRuntime(), mainClass.name);
checkTestRunResult(r8Result, Compiler.R8);
}
@@ -304,13 +307,13 @@
}
private Matcher<String> getMatcherForExpectedError() {
- if (backend == Backend.CF) {
+ if (parameters.isCfRuntime()) {
return allOf(
containsString("java.lang.VerifyError"),
containsString("Bad type in putfield/putstatic"));
}
- assert backend == Backend.DEX;
+ assert parameters.isDexRuntime();
return allOf(
containsString("java.lang.VerifyError"),
anyOf(