Merge commit '51676e787bd368b5c2c9fa8ddbd04c95bf770e5e' into dev-release
Change-Id: I28698766989c1dd49d980d822edb81f787612706
diff --git a/src/main/java/com/android/tools/r8/cf/LoadStoreHelper.java b/src/main/java/com/android/tools/r8/cf/LoadStoreHelper.java
index d7dbde7..9139e16 100644
--- a/src/main/java/com/android/tools/r8/cf/LoadStoreHelper.java
+++ b/src/main/java/com/android/tools/r8/cf/LoadStoreHelper.java
@@ -286,7 +286,7 @@
return new DexItemBasedConstString(
stackValue, computedConstant.getItem(), computedConstant.getNameComputationInfo());
} else if (constant.isConstClass()) {
- return new ConstClass(stackValue, constant.asConstClass().getValue());
+ return new ConstClass(stackValue, constant.asConstClass().getType());
} else {
throw new Unreachable("Unexpected constant value: " + value);
}
diff --git a/src/main/java/com/android/tools/r8/graph/AccessFlags.java b/src/main/java/com/android/tools/r8/graph/AccessFlags.java
index d52e9ae..4151107 100644
--- a/src/main/java/com/android/tools/r8/graph/AccessFlags.java
+++ b/src/main/java/com/android/tools/r8/graph/AccessFlags.java
@@ -158,6 +158,10 @@
return isSet(Constants.ACC_PUBLIC);
}
+ public boolean wasPublic() {
+ return wasSet(Constants.ACC_PUBLIC);
+ }
+
public void setPublic() {
assert !isPrivate() && !isProtected();
set(Constants.ACC_PUBLIC);
diff --git a/src/main/java/com/android/tools/r8/graph/AppServices.java b/src/main/java/com/android/tools/r8/graph/AppServices.java
index 5e03a38..da91743 100644
--- a/src/main/java/com/android/tools/r8/graph/AppServices.java
+++ b/src/main/java/com/android/tools/r8/graph/AppServices.java
@@ -12,10 +12,8 @@
import com.android.tools.r8.ProgramResourceProvider;
import com.android.tools.r8.ResourceException;
import com.android.tools.r8.errors.CompilationError;
-import com.android.tools.r8.features.ClassToFeatureSplitMap;
import com.android.tools.r8.graph.lens.GraphLens;
import com.android.tools.r8.origin.Origin;
-import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.StringDiagnostic;
@@ -78,10 +76,6 @@
assert verifyRewrittenWithLens();
Map<FeatureSplit, List<DexType>> featureSplitListMap = services.get(serviceType);
if (featureSplitListMap == null) {
- assert false
- : "Unexpected attempt to get service implementations for non-service type `"
- + serviceType.toSourceString()
- + "`";
return ImmutableList.of();
}
ImmutableList.Builder<DexType> builder = ImmutableList.builder();
@@ -91,43 +85,8 @@
return builder.build();
}
- public boolean hasServiceImplementationsInFeature(
- AppView<? extends AppInfoWithLiveness> appView, DexType serviceType) {
- ClassToFeatureSplitMap classToFeatureSplitMap = appView.appInfo().getClassToFeatureSplitMap();
- if (classToFeatureSplitMap.isEmpty()) {
- return false;
- }
- Map<FeatureSplit, List<DexType>> featureImplementations = services.get(serviceType);
- if (featureImplementations == null || featureImplementations.isEmpty()) {
- assert false
- : "Unexpected attempt to get service implementations for non-service type `"
- + serviceType.toSourceString()
- + "`";
- return true;
- }
- if (featureImplementations.keySet().stream().anyMatch(feature -> !feature.isBase())) {
- return true;
- }
- // All service implementations are in one of the base splits.
- assert featureImplementations.size() <= 2;
- // Check if service is defined feature
- DexProgramClass serviceClass = appView.definitionForProgramType(serviceType);
- if (serviceClass != null && classToFeatureSplitMap.isInFeature(serviceClass, appView)) {
- return true;
- }
- for (Entry<FeatureSplit, List<DexType>> entry : featureImplementations.entrySet()) {
- FeatureSplit feature = entry.getKey();
- assert feature.isBase();
- List<DexType> implementationTypes = entry.getValue();
- for (DexType implementationType : implementationTypes) {
- DexProgramClass implementationClass = appView.definitionForProgramType(implementationType);
- if (implementationClass != null
- && classToFeatureSplitMap.isInFeature(implementationClass, appView)) {
- return true;
- }
- }
- }
- return false;
+ public Map<FeatureSplit, List<DexType>> serviceImplementationsByFeatureFor(DexType serviceType) {
+ return services.get(serviceType);
}
public AppServices rewrittenWithLens(GraphLens graphLens, Timing timing) {
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 a7e37bc..3c056f8 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -249,6 +249,8 @@
public final DexString runtimeExceptionDescriptor = createString("Ljava/lang/RuntimeException;");
public final DexString assertionErrorDescriptor = createString("Ljava/lang/AssertionError;");
+ public final DexString noSuchElementExceptionDescriptor =
+ createString("Ljava/util/NoSuchElementException;");
public final DexString charSequenceDescriptor = createString("Ljava/lang/CharSequence;");
public final DexString charSequenceArrayDescriptor = createString("[Ljava/lang/CharSequence;");
public final DexString stringDescriptor = createString("Ljava/lang/String;");
@@ -575,6 +577,11 @@
public final DexType runtimeExceptionType = createStaticallyKnownType(runtimeExceptionDescriptor);
public final DexType assertionErrorType = createStaticallyKnownType(assertionErrorDescriptor);
public final DexType throwableType = createStaticallyKnownType(throwableDescriptor);
+ public final DexType noSuchElementExceptionType =
+ createStaticallyKnownType(noSuchElementExceptionDescriptor);
+ public final DexMethod noSuchElementExceptionInit =
+ createInstanceInitializer(noSuchElementExceptionType);
+
public final DexType illegalAccessErrorType =
createStaticallyKnownType(illegalAccessErrorDescriptor);
public final DexType illegalArgumentExceptionType =
@@ -870,6 +877,7 @@
public final ObjectMethodsMembers objectMethodsMembers = new ObjectMethodsMembers();
public final ServiceLoaderMethods serviceLoaderMethods = new ServiceLoaderMethods();
+ public final IteratorMethods iteratorMethods = new IteratorMethods();
public final StringConcatFactoryMembers stringConcatFactoryMembers =
new StringConcatFactoryMembers();
@@ -2659,6 +2667,12 @@
}
}
+ public class IteratorMethods {
+ public final DexMethod hasNext =
+ createMethod(iteratorType, createProto(booleanType), "hasNext");
+ public final DexMethod next = createMethod(iteratorType, createProto(objectType), "next");
+ }
+
private static <T extends DexItem> T canonicalize(Map<T, T> map, T item) {
assert item != null;
assert !DexItemFactory.isInternalSentinel(item);
diff --git a/src/main/java/com/android/tools/r8/graph/GenericSignatureContextBuilder.java b/src/main/java/com/android/tools/r8/graph/GenericSignatureContextBuilder.java
index 1a3e78f..0cc9356 100644
--- a/src/main/java/com/android/tools/r8/graph/GenericSignatureContextBuilder.java
+++ b/src/main/java/com/android/tools/r8/graph/GenericSignatureContextBuilder.java
@@ -249,6 +249,9 @@
return typeParameterContext;
}
TypeParameterSubstitutions methodFormals = this.formalsInfo.get(reference);
+ if (methodFormals == null) {
+ return typeParameterContext;
+ }
if (clazz != null && !prunedHere) {
DexEncodedMethod method = clazz.lookupMethod(reference.asDexMethod());
prunedHere =
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/equivalence/BasicBlockBehavioralSubsumption.java b/src/main/java/com/android/tools/r8/ir/analysis/equivalence/BasicBlockBehavioralSubsumption.java
index 35619e1..1da7136 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/equivalence/BasicBlockBehavioralSubsumption.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/equivalence/BasicBlockBehavioralSubsumption.java
@@ -282,7 +282,7 @@
}
ConstClass constClassInstruction = instruction.asConstClass();
ConstClass otherConstClassInstruction = other.asConstClass();
- return constClassInstruction.getValue() == otherConstClassInstruction.getValue();
+ return constClassInstruction.getType() == otherConstClassInstruction.getType();
}
if (instruction.isConstNumber()) {
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/TrivialFieldAccessReprocessor.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/TrivialFieldAccessReprocessor.java
index d5f90ba..da5c7fc 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/TrivialFieldAccessReprocessor.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/TrivialFieldAccessReprocessor.java
@@ -35,6 +35,7 @@
import com.android.tools.r8.ir.conversion.PostMethodProcessor;
import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackDelayed;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.KeepFieldInfo;
import com.android.tools.r8.threading.ThreadingModule;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.SetUtils;
@@ -126,7 +127,8 @@
private void markFieldAsDead(DexEncodedField field) {
// Don't mark pinned fields as dead, since they need to remain in the app even if all reads and
// writes are removed.
- if (appView.appInfo().isPinned(field)) {
+ KeepFieldInfo keepInfo = appView.getKeepInfo().getFieldInfo(field, appView);
+ if (!keepInfo.isOptimizationAllowed(appView.options())) {
assert field.getType().isAlwaysNull(appView);
} else {
getSimpleFeedback().markFieldAsDead(field);
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/DataflowAnalysisResult.java b/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/DataflowAnalysisResult.java
index 73eeb55..550019d 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/DataflowAnalysisResult.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/DataflowAnalysisResult.java
@@ -44,6 +44,10 @@
this.blockExitStates = blockExitStates;
}
+ public StateType getBlockExitState(Block block) {
+ return blockExitStates.get(block);
+ }
+
public StateType join(AppView<?> appView) {
StateType result = null;
for (StateType blockExitState : blockExitStates.values()) {
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/path/PathConstraintAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/path/PathConstraintAnalysis.java
new file mode 100644
index 0000000..dd5b2c9
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/analysis/path/PathConstraintAnalysis.java
@@ -0,0 +1,47 @@
+// Copyright (c) 2024, 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.path;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.ir.analysis.framework.intraprocedural.IntraproceduralDataflowAnalysis;
+import com.android.tools.r8.ir.analysis.path.state.PathConstraintAnalysisState;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+
+/**
+ * An analysis that computes the set of active path constraints for each program point. Each path
+ * constraint is an expression where only the arguments of the current method are allowed as open
+ * variables.
+ *
+ * <p>Consider the following example.
+ *
+ * <pre>
+ * static Object Foo(Object o, int flags) {
+ * if ((flags & 1) != 0) {
+ * o = DEFAULT_VALUE;
+ * }
+ * return o;
+ * }
+ * </pre>
+ *
+ * <ul>
+ * <li>The path constraint for the entry block is the empty set, since control flow
+ * unconditionally hits this block.
+ * <li>The path constraint for the block containing {@code o = DEFAULT_VALUE} is [(flags & 1) !=
+ * 0].
+ * <li>The path constraint for the (empty) ELSE block is [(flags & 1) == 0].
+ * <li>The path constraint for the return block is the empty set.
+ * </ul>
+ */
+public class PathConstraintAnalysis
+ extends IntraproceduralDataflowAnalysis<PathConstraintAnalysisState> {
+
+ public PathConstraintAnalysis(AppView<AppInfoWithLiveness> appView, IRCode code) {
+ super(
+ appView,
+ PathConstraintAnalysisState.bottom(),
+ code,
+ new PathConstraintAnalysisTransferFunction(appView.abstractValueFactory()));
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/path/PathConstraintAnalysisTransferFunction.java b/src/main/java/com/android/tools/r8/ir/analysis/path/PathConstraintAnalysisTransferFunction.java
new file mode 100644
index 0000000..6b86310
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/analysis/path/PathConstraintAnalysisTransferFunction.java
@@ -0,0 +1,70 @@
+// Copyright (c) 2024, 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.path;
+
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.analysis.framework.intraprocedural.AbstractTransferFunction;
+import com.android.tools.r8.ir.analysis.framework.intraprocedural.TransferFunctionResult;
+import com.android.tools.r8.ir.analysis.path.state.PathConstraintAnalysisState;
+import com.android.tools.r8.ir.analysis.value.AbstractValueFactory;
+import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.If;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.optimize.argumentpropagation.computation.ComputationTreeBuilder;
+import com.android.tools.r8.optimize.argumentpropagation.computation.ComputationTreeNode;
+
+public class PathConstraintAnalysisTransferFunction
+ implements AbstractTransferFunction<BasicBlock, Instruction, PathConstraintAnalysisState> {
+
+ private final ComputationTreeBuilder computationTreeBuilder;
+
+ PathConstraintAnalysisTransferFunction(AbstractValueFactory abstractValueFactory) {
+ computationTreeBuilder = new ComputationTreeBuilder(abstractValueFactory);
+ }
+
+ @Override
+ public TransferFunctionResult<PathConstraintAnalysisState> apply(
+ Instruction instruction, PathConstraintAnalysisState state) {
+ // Instructions normally to not change the current path constraint.
+ //
+ // One exception is when information can be deduced from throwing instructions that succeed.
+ // For example, if the instruction `arg.method()` succeeds then it can be inferred that the
+ // subsequent instruction is only executed if `arg != null`.
+ return state;
+ }
+
+ @Override
+ public PathConstraintAnalysisState computeBlockEntryState(
+ BasicBlock block, BasicBlock predecessor, PathConstraintAnalysisState predecessorExitState) {
+ if (predecessorExitState.isUnknown()) {
+ return predecessorExitState;
+ }
+ // We currently only amend the path constraint in presence of if-instructions.
+ If theIf = predecessor.exit().asIf();
+ if (theIf != null) {
+ // TODO(b/302281503): Ensure the computed computation tree is cached in the builder so that
+ // we do not rebuild the tree over-and-over again during the execution of this (worklist)
+ // analysis.
+ ComputationTreeNode newPathConstraint = computationTreeBuilder.buildComputationTree(theIf);
+ if (!newPathConstraint.isUnknown()) {
+ boolean negate = block != theIf.getTrueTarget();
+ return predecessorExitState.add(newPathConstraint, negate);
+ }
+ }
+ return predecessorExitState;
+ }
+
+ @Override
+ public PathConstraintAnalysisState computeExceptionalBlockEntryState(
+ BasicBlock block,
+ DexType guard,
+ BasicBlock throwBlock,
+ Instruction throwInstruction,
+ PathConstraintAnalysisState throwState) {
+ // For the purpose of this analysis we don't (?) care much about the path constraints for blocks
+ // that are reached from catch handlers. Therefore, we currently set the state to UNKNOWN for
+ // all blocks that can be reached from a catch handler.
+ return PathConstraintAnalysisState.unknown();
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/path/state/BottomPathConstraintAnalysisState.java b/src/main/java/com/android/tools/r8/ir/analysis/path/state/BottomPathConstraintAnalysisState.java
new file mode 100644
index 0000000..6ed306f
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/analysis/path/state/BottomPathConstraintAnalysisState.java
@@ -0,0 +1,38 @@
+// Copyright (c) 2024, 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.path.state;
+
+import com.android.tools.r8.optimize.argumentpropagation.computation.ComputationTreeNode;
+
+public class BottomPathConstraintAnalysisState extends PathConstraintAnalysisState {
+
+ private static final BottomPathConstraintAnalysisState INSTANCE =
+ new BottomPathConstraintAnalysisState();
+
+ private BottomPathConstraintAnalysisState() {}
+
+ static BottomPathConstraintAnalysisState getInstance() {
+ return INSTANCE;
+ }
+
+ @Override
+ public PathConstraintAnalysisState add(ComputationTreeNode pathConstraint, boolean negate) {
+ return ConcretePathConstraintAnalysisState.create(pathConstraint, negate);
+ }
+
+ @Override
+ public boolean isBottom() {
+ return true;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return this == other;
+ }
+
+ @Override
+ public int hashCode() {
+ return System.identityHashCode(this);
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/path/state/ConcretePathConstraintAnalysisState.java b/src/main/java/com/android/tools/r8/ir/analysis/path/state/ConcretePathConstraintAnalysisState.java
new file mode 100644
index 0000000..361ed99
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/analysis/path/state/ConcretePathConstraintAnalysisState.java
@@ -0,0 +1,160 @@
+// Copyright (c) 2024, 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.path.state;
+
+import com.android.tools.r8.optimize.argumentpropagation.computation.ComputationTreeNode;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * Represents a non-trivial (neither bottom nor top) path constraint that must be satisfied to reach
+ * a given program point.
+ *
+ * <p>The state should be interpreted as follows:
+ *
+ * <ol>
+ * <li>If a path constraint is BOTH in {@link #pathConstraints} AND {@link
+ * #negatedPathConstraints} then the path constraint should be IGNORED.
+ * <li>If a path constraint is ONLY in {@link #pathConstraints} then the current program point can
+ * only be reached if the path constraint is satisfied.
+ * <li>If a path constraint is ONLY in {@link #negatedPathConstraints} then the current program
+ * point can only be reached if the path constraint is NOT satisfied.
+ * </ol>
+ *
+ * <p>Example: In the below example, when entering the IF-THEN branch, we add the path constraint
+ * [(flags & 1) != 0] to {@link #pathConstraints}. When entering the (empty) IF-ELSE branch, we add
+ * the same path constraint to {@link #negatedPathConstraints}. When reaching the return block, the
+ * two path constraint states are joined, resulting in the state where {@link #pathConstraints} and
+ * {@link #negatedPathConstraints} both contains the constraint [(flags & 1) != 0].
+ *
+ * <pre>
+ * static Object Foo(Object o, int flags) {
+ * if ((flags & 1) != 0) {
+ * o = DEFAULT_VALUE;
+ * }
+ * return o;
+ * }
+ * </pre>
+ */
+public class ConcretePathConstraintAnalysisState extends PathConstraintAnalysisState {
+
+ private final Set<ComputationTreeNode> pathConstraints;
+ private final Set<ComputationTreeNode> negatedPathConstraints;
+
+ ConcretePathConstraintAnalysisState(
+ Set<ComputationTreeNode> pathConstraints, Set<ComputationTreeNode> negatedPathConstraints) {
+ this.pathConstraints = pathConstraints;
+ this.negatedPathConstraints = negatedPathConstraints;
+ }
+
+ static ConcretePathConstraintAnalysisState create(
+ ComputationTreeNode pathConstraint, boolean negate) {
+ Set<ComputationTreeNode> pathConstraints = Collections.singleton(pathConstraint);
+ if (negate) {
+ return new ConcretePathConstraintAnalysisState(Collections.emptySet(), pathConstraints);
+ } else {
+ return new ConcretePathConstraintAnalysisState(pathConstraints, Collections.emptySet());
+ }
+ }
+
+ @Override
+ public PathConstraintAnalysisState add(ComputationTreeNode pathConstraint, boolean negate) {
+ if (negate) {
+ if (negatedPathConstraints.contains(pathConstraint)) {
+ return this;
+ }
+ return new ConcretePathConstraintAnalysisState(
+ pathConstraints, add(pathConstraint, negatedPathConstraints));
+ } else {
+ if (pathConstraints.contains(pathConstraint)) {
+ return this;
+ }
+ return new ConcretePathConstraintAnalysisState(
+ add(pathConstraint, pathConstraints), negatedPathConstraints);
+ }
+ }
+
+ private static Set<ComputationTreeNode> add(
+ ComputationTreeNode pathConstraint, Set<ComputationTreeNode> pathConstraints) {
+ if (pathConstraints.isEmpty()) {
+ return Collections.singleton(pathConstraint);
+ }
+ assert !pathConstraints.contains(pathConstraint);
+ Set<ComputationTreeNode> newPathConstraints = new HashSet<>(pathConstraints);
+ newPathConstraints.add(pathConstraint);
+ return newPathConstraints;
+ }
+
+ public Set<ComputationTreeNode> getPathConstraints() {
+ return pathConstraints;
+ }
+
+ public Set<ComputationTreeNode> getNegatedPathConstraints() {
+ return negatedPathConstraints;
+ }
+
+ @Override
+ public boolean isConcrete() {
+ return true;
+ }
+
+ @Override
+ public ConcretePathConstraintAnalysisState asConcreteState() {
+ return this;
+ }
+
+ public ConcretePathConstraintAnalysisState join(ConcretePathConstraintAnalysisState other) {
+ Set<ComputationTreeNode> newPathConstraints = join(pathConstraints, other.pathConstraints);
+ Set<ComputationTreeNode> newNegatedPathConstraints =
+ join(negatedPathConstraints, other.negatedPathConstraints);
+ if (identical(newPathConstraints, newNegatedPathConstraints)) {
+ return this;
+ }
+ if (other.identical(newPathConstraints, newNegatedPathConstraints)) {
+ return other;
+ }
+ return new ConcretePathConstraintAnalysisState(newPathConstraints, newNegatedPathConstraints);
+ }
+
+ private static Set<ComputationTreeNode> join(
+ Set<ComputationTreeNode> pathConstraints, Set<ComputationTreeNode> otherPathConstraints) {
+ if (pathConstraints.isEmpty()) {
+ return otherPathConstraints;
+ }
+ if (otherPathConstraints.isEmpty()) {
+ return pathConstraints;
+ }
+ Set<ComputationTreeNode> newPathConstraints =
+ new HashSet<>(pathConstraints.size() + otherPathConstraints.size());
+ newPathConstraints.addAll(pathConstraints);
+ newPathConstraints.addAll(otherPathConstraints);
+ return newPathConstraints;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof ConcretePathConstraintAnalysisState)) {
+ return false;
+ }
+ ConcretePathConstraintAnalysisState state = (ConcretePathConstraintAnalysisState) obj;
+ return pathConstraints.equals(state.pathConstraints)
+ && negatedPathConstraints.equals(state.negatedPathConstraints);
+ }
+
+ public boolean identical(
+ Set<ComputationTreeNode> pathConstraints, Set<ComputationTreeNode> negatedPathConstraints) {
+ return this.pathConstraints == pathConstraints
+ && this.negatedPathConstraints == negatedPathConstraints;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(pathConstraints, negatedPathConstraints);
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/path/state/PathConstraintAnalysisState.java b/src/main/java/com/android/tools/r8/ir/analysis/path/state/PathConstraintAnalysisState.java
new file mode 100644
index 0000000..fea03cc
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/analysis/path/state/PathConstraintAnalysisState.java
@@ -0,0 +1,55 @@
+// Copyright (c) 2024, 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.path.state;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.ir.analysis.framework.intraprocedural.AbstractState;
+import com.android.tools.r8.optimize.argumentpropagation.computation.ComputationTreeNode;
+
+public abstract class PathConstraintAnalysisState
+ extends AbstractState<PathConstraintAnalysisState> {
+
+ public static BottomPathConstraintAnalysisState bottom() {
+ return BottomPathConstraintAnalysisState.getInstance();
+ }
+
+ public static UnknownPathConstraintAnalysisState unknown() {
+ return UnknownPathConstraintAnalysisState.getInstance();
+ }
+
+ public abstract PathConstraintAnalysisState add(
+ ComputationTreeNode pathConstraint, boolean negate);
+
+ public boolean isBottom() {
+ return false;
+ }
+
+ public boolean isConcrete() {
+ return false;
+ }
+
+ public boolean isUnknown() {
+ return false;
+ }
+
+ @Override
+ public PathConstraintAnalysisState asAbstractState() {
+ return this;
+ }
+
+ public ConcretePathConstraintAnalysisState asConcreteState() {
+ return null;
+ }
+
+ @Override
+ public PathConstraintAnalysisState join(AppView<?> appView, PathConstraintAnalysisState other) {
+ if (isBottom() || other.isUnknown()) {
+ return other;
+ }
+ if (other.isBottom() || isUnknown()) {
+ return this;
+ }
+ return asConcreteState().join(other.asConcreteState());
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/path/state/UnknownPathConstraintAnalysisState.java b/src/main/java/com/android/tools/r8/ir/analysis/path/state/UnknownPathConstraintAnalysisState.java
new file mode 100644
index 0000000..17684e6
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/analysis/path/state/UnknownPathConstraintAnalysisState.java
@@ -0,0 +1,38 @@
+// Copyright (c) 2024, 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.path.state;
+
+import com.android.tools.r8.optimize.argumentpropagation.computation.ComputationTreeNode;
+
+public class UnknownPathConstraintAnalysisState extends PathConstraintAnalysisState {
+
+ private static final UnknownPathConstraintAnalysisState INSTANCE =
+ new UnknownPathConstraintAnalysisState();
+
+ private UnknownPathConstraintAnalysisState() {}
+
+ static UnknownPathConstraintAnalysisState getInstance() {
+ return INSTANCE;
+ }
+
+ @Override
+ public PathConstraintAnalysisState add(ComputationTreeNode pathConstraint, boolean negate) {
+ return this;
+ }
+
+ @Override
+ public boolean isUnknown() {
+ return true;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return this == other;
+ }
+
+ @Override
+ public int hashCode() {
+ return System.identityHashCode(this);
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/RawMessageInfoDecoder.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/RawMessageInfoDecoder.java
index ef36cea..226e172 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/RawMessageInfoDecoder.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/RawMessageInfoDecoder.java
@@ -202,7 +202,7 @@
Instruction definition = root.definition;
if (definition.isConstClass()) {
ConstClass constClass = definition.asConstClass();
- return new ProtoTypeObject(constClass.getValue());
+ return new ProtoTypeObject(constClass.getType());
} else if (definition.isConstString()) {
ConstString constString = definition.asConstString();
DexEncodedField field =
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValue.java
index cc8cfb9..b83c92e 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValue.java
@@ -9,9 +9,11 @@
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.lens.GraphLens;
import com.android.tools.r8.ir.analysis.value.objectstate.ObjectState;
+import com.android.tools.r8.optimize.argumentpropagation.computation.ComputationTreeNode;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import java.util.function.IntFunction;
-public abstract class AbstractValue {
+public abstract class AbstractValue implements ComputationTreeNode {
public static BottomValue bottom() {
return BottomValue.getInstance();
@@ -21,6 +23,12 @@
return UnknownValue.getInstance();
}
+ @Override
+ public AbstractValue evaluate(
+ IntFunction<AbstractValue> argumentAssignment, AbstractValueFactory abstractValueFactory) {
+ return this;
+ }
+
public abstract boolean isNonTrivial();
public boolean isSingleBoolean() {
@@ -241,6 +249,7 @@
return false;
}
+ @Override
public boolean isUnknown() {
return false;
}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValueFactory.java b/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValueFactory.java
index 27534c2..cf6875d 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValueFactory.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValueFactory.java
@@ -14,6 +14,7 @@
import com.android.tools.r8.ir.analysis.value.objectstate.KnownLengthArrayState;
import com.android.tools.r8.ir.analysis.value.objectstate.ObjectState;
import com.android.tools.r8.naming.dexitembasedstring.NameComputationInfo;
+import com.android.tools.r8.utils.BooleanUtils;
import java.util.concurrent.ConcurrentHashMap;
public class AbstractValueFactory {
@@ -102,6 +103,10 @@
: new SingleStatefulFieldValue(field, state);
}
+ public SingleNumberValue createSingleBooleanValue(boolean value) {
+ return createUncheckedSingleNumberValue(BooleanUtils.intValue(value));
+ }
+
public SingleNumberValue createSingleNumberValue(long value, TypeElement type) {
assert type.isPrimitiveType();
return createUncheckedSingleNumberValue(value);
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/arithmetic/AbstractCalculator.java b/src/main/java/com/android/tools/r8/ir/analysis/value/arithmetic/AbstractCalculator.java
index 9b37213..92e4816 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/arithmetic/AbstractCalculator.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/arithmetic/AbstractCalculator.java
@@ -7,12 +7,18 @@
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.ir.analysis.value.AbstractValue;
+import com.android.tools.r8.ir.analysis.value.AbstractValueFactory;
import com.android.tools.r8.utils.BitUtils;
public class AbstractCalculator {
public static AbstractValue andIntegers(
AppView<?> appView, AbstractValue left, AbstractValue right) {
+ return andIntegers(appView.abstractValueFactory(), left, right);
+ }
+
+ public static AbstractValue andIntegers(
+ AbstractValueFactory abstractValueFactory, AbstractValue left, AbstractValue right) {
if (left.isZero()) {
return left;
}
@@ -22,25 +28,21 @@
if (left.isSingleNumberValue() && right.isSingleNumberValue()) {
int result =
left.asSingleNumberValue().getIntValue() & right.asSingleNumberValue().getIntValue();
- return appView.abstractValueFactory().createUncheckedSingleNumberValue(result);
+ return abstractValueFactory.createUncheckedSingleNumberValue(result);
}
if (left.hasDefinitelySetAndUnsetBitsInformation()
&& right.hasDefinitelySetAndUnsetBitsInformation()) {
- return appView
- .abstractValueFactory()
- .createDefiniteBitsNumberValue(
- left.getDefinitelySetIntBits() & right.getDefinitelySetIntBits(),
- left.getDefinitelyUnsetIntBits() | right.getDefinitelyUnsetIntBits());
+ return abstractValueFactory.createDefiniteBitsNumberValue(
+ left.getDefinitelySetIntBits() & right.getDefinitelySetIntBits(),
+ left.getDefinitelyUnsetIntBits() | right.getDefinitelyUnsetIntBits());
}
if (left.hasDefinitelySetAndUnsetBitsInformation()) {
- return appView
- .abstractValueFactory()
- .createDefiniteBitsNumberValue(0, left.getDefinitelyUnsetIntBits());
+ return abstractValueFactory.createDefiniteBitsNumberValue(
+ 0, left.getDefinitelyUnsetIntBits());
}
if (right.hasDefinitelySetAndUnsetBitsInformation()) {
- return appView
- .abstractValueFactory()
- .createDefiniteBitsNumberValue(0, right.getDefinitelyUnsetIntBits());
+ return abstractValueFactory.createDefiniteBitsNumberValue(
+ 0, right.getDefinitelyUnsetIntBits());
}
return AbstractValue.unknown();
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java
index 2efeeae..d1fc0ed 100644
--- a/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java
@@ -229,30 +229,6 @@
return dstIterator;
}
- @Override
- public BasicBlock addThrowingInstructionToPossiblyThrowingBlock(
- IRCode code,
- ListIterator<BasicBlock> blockIterator,
- Instruction instruction,
- InternalOptions options) {
- if (block.hasCatchHandlers()) {
- BasicBlock splitBlock = split(code, blockIterator, false);
- splitBlock.listIterator(code).add(instruction);
- assert !block.hasCatchHandlers();
- assert splitBlock.hasCatchHandlers();
- block.copyCatchHandlers(code, blockIterator, splitBlock, options);
- if (blockIterator != null) {
- while (IteratorUtils.peekPrevious(blockIterator) != splitBlock) {
- blockIterator.previous();
- }
- }
- return splitBlock;
- } else {
- add(instruction);
- return null;
- }
- }
-
/**
* Replaces the last instruction returned by {@link #next} or {@link #previous} with the specified
* instruction.
diff --git a/src/main/java/com/android/tools/r8/ir/code/ConstClass.java b/src/main/java/com/android/tools/r8/ir/code/ConstClass.java
index 72721d2..fe1c8b3 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ConstClass.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ConstClass.java
@@ -68,17 +68,13 @@
public static ConstClass copyOf(Value newValue, ConstClass original) {
assert newValue != original.outValue();
- return new ConstClass(newValue, original.getValue());
+ return new ConstClass(newValue, original.getType());
}
public Value dest() {
return outValue;
}
- public DexType getValue() {
- return clazz;
- }
-
@Override
public void buildDex(DexBuilder builder) {
int dest = builder.allocatedRegister(dest(), getNumber());
@@ -123,7 +119,7 @@
ProgramMethod context,
AbstractValueSupplier abstractValueSupplier,
SideEffectAssumption assumption) {
- DexType baseType = getValue().toBaseType(appView.dexItemFactory());
+ DexType baseType = getType().toBaseType(appView.dexItemFactory());
if (baseType.isPrimitiveType()) {
return false;
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/ConstNumber.java b/src/main/java/com/android/tools/r8/ir/code/ConstNumber.java
index 634225b..95df669 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ConstNumber.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ConstNumber.java
@@ -339,13 +339,16 @@
@Override
public AbstractValue getAbstractValue(
AppView<?> appView, ProgramMethod context, AbstractValueSupplier abstractValueSupplier) {
+ return getAbstractValue(appView.abstractValueFactory());
+ }
+
+ public AbstractValue getAbstractValue(AbstractValueFactory abstractValueFactory) {
if (outValue.hasLocalInfo()) {
return AbstractValue.unknown();
}
- AbstractValueFactory factory = appView.abstractValueFactory();
return getOutType().isReferenceType()
- ? factory.createNullValue(getOutType())
- : factory.createSingleNumberValue(value, getOutType());
+ ? abstractValueFactory.createNullValue(getOutType())
+ : abstractValueFactory.createSingleNumberValue(value, getOutType());
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/ConstantValueUtils.java b/src/main/java/com/android/tools/r8/ir/code/ConstantValueUtils.java
index 9855ee6..381f4b4 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ConstantValueUtils.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ConstantValueUtils.java
@@ -24,7 +24,7 @@
}
if (alias.definition.isConstClass()) {
- return alias.definition.asConstClass().getValue();
+ return alias.definition.asConstClass().getType();
}
if (alias.definition.isInvokeStatic()) {
diff --git a/src/main/java/com/android/tools/r8/ir/code/IRCode.java b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
index 5077746..307acdf 100644
--- a/src/main/java/com/android/tools/r8/ir/code/IRCode.java
+++ b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
@@ -1109,7 +1109,7 @@
return new IRCodeInstructionIterator(this);
}
- public InstructionListIterator instructionListIterator() {
+ public IRCodeInstructionListIterator instructionListIterator() {
return new IRCodeInstructionListIterator(this);
}
@@ -1241,6 +1241,14 @@
return createNumberConstant(Float.floatToIntBits(value), TypeElement.getFloat(), local);
}
+ public ConstNumber createBooleanConstant(boolean value) {
+ return createBooleanConstant(value, null);
+ }
+
+ public ConstNumber createBooleanConstant(boolean value, DebugLocalInfo local) {
+ return createNumberConstant(value ? 1 : 0, TypeElement.getInt(), local);
+ }
+
public ConstNumber createIntConstant(int value) {
return createIntConstant(value, null);
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionListIterator.java
index 6b33cca..13e393a 100644
--- a/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionListIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionListIterator.java
@@ -35,6 +35,10 @@
this.instructionIterator = blockIterator.next().listIterator(code);
}
+ public IRCode getCode() {
+ return code;
+ }
+
@Override
public Value insertConstNumberInstruction(
IRCode code, InternalOptions options, long value, TypeElement type) {
@@ -244,14 +248,11 @@
code, blockIterator, instructionsToAdd, options);
}
- @Override
- public BasicBlock addThrowingInstructionToPossiblyThrowingBlock(
- IRCode code,
- ListIterator<BasicBlock> blockIterator,
- Instruction instruction,
- InternalOptions options) {
- return instructionIterator.addThrowingInstructionToPossiblyThrowingBlock(
- code, blockIterator, instruction, options);
+ public void addPossiblyThrowingInstructionsToPossiblyThrowingBlock(
+ Collection<? extends Instruction> instructionsToAdd, InternalOptions options) {
+ instructionIterator =
+ instructionIterator.addPossiblyThrowingInstructionsToPossiblyThrowingBlock(
+ code, blockIterator, instructionsToAdd, options);
}
@Override
@@ -259,6 +260,13 @@
instructionIterator.remove();
}
+ public void removeRemainingInBlockIgnoreOutValue() {
+ while (instructionIterator.hasNext()) {
+ instructionIterator.next();
+ instructionIterator.removeInstructionIgnoreOutValue();
+ }
+ }
+
@Override
public void set(Instruction instruction) {
instructionIterator.set(instruction);
diff --git a/src/main/java/com/android/tools/r8/ir/code/IfType.java b/src/main/java/com/android/tools/r8/ir/code/IfType.java
index eef3f3b..5babdae 100644
--- a/src/main/java/com/android/tools/r8/ir/code/IfType.java
+++ b/src/main/java/com/android/tools/r8/ir/code/IfType.java
@@ -4,14 +4,63 @@
package com.android.tools.r8.ir.code;
import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
+import com.android.tools.r8.ir.analysis.value.AbstractValueFactory;
+import com.android.tools.r8.ir.analysis.value.SingleNumberValue;
public enum IfType {
- EQ,
- GE,
- GT,
- LE,
- LT,
- NE;
+ EQ {
+ @Override
+ public AbstractValue evaluate(
+ SingleNumberValue operand, AbstractValueFactory abstractValueFactory) {
+ return abstractValueFactory.createSingleBooleanValue(operand.getValue() == 0);
+ }
+ },
+ GE {
+ @Override
+ public AbstractValue evaluate(
+ SingleNumberValue operand, AbstractValueFactory abstractValueFactory) {
+ return abstractValueFactory.createSingleBooleanValue(operand.getValue() >= 0);
+ }
+ },
+ GT {
+ @Override
+ public AbstractValue evaluate(
+ SingleNumberValue operand, AbstractValueFactory abstractValueFactory) {
+ return abstractValueFactory.createSingleBooleanValue(operand.getValue() > 0);
+ }
+ },
+ LE {
+ @Override
+ public AbstractValue evaluate(
+ SingleNumberValue operand, AbstractValueFactory abstractValueFactory) {
+ return abstractValueFactory.createSingleBooleanValue(operand.getValue() <= 0);
+ }
+ },
+ LT {
+ @Override
+ public AbstractValue evaluate(
+ SingleNumberValue operand, AbstractValueFactory abstractValueFactory) {
+ return abstractValueFactory.createSingleBooleanValue(operand.getValue() < 0);
+ }
+ },
+ NE {
+ @Override
+ public AbstractValue evaluate(
+ SingleNumberValue operand, AbstractValueFactory abstractValueFactory) {
+ return abstractValueFactory.createSingleBooleanValue(operand.getValue() != 0);
+ }
+ };
+
+ public AbstractValue evaluate(AbstractValue operand, AbstractValueFactory abstractValueFactory) {
+ if (operand.isSingleNumberValue()) {
+ return evaluate(operand.asSingleNumberValue(), abstractValueFactory);
+ }
+ return AbstractValue.unknown();
+ }
+
+ public abstract AbstractValue evaluate(
+ SingleNumberValue operand, AbstractValueFactory abstractValueFactory);
public boolean isEqualsOrNotEquals() {
return this == EQ || this == NE;
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 19c675b..c1e8aa6 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
@@ -20,6 +20,7 @@
import com.google.common.collect.Sets;
import java.util.Arrays;
import java.util.Collection;
+import java.util.Collections;
import java.util.Iterator;
import java.util.ListIterator;
import java.util.Set;
@@ -69,11 +70,14 @@
code, blockIterator, Arrays.asList(instructionsToAdd), options);
}
- BasicBlock addThrowingInstructionToPossiblyThrowingBlock(
+ default InstructionListIterator addPossiblyThrowingInstructionToPossiblyThrowingBlock(
IRCode code,
- ListIterator<BasicBlock> blockIterator,
- Instruction instruction,
- InternalOptions options);
+ BasicBlockIterator blockIterator,
+ Instruction instructionToAdd,
+ InternalOptions options) {
+ return addPossiblyThrowingInstructionsToPossiblyThrowingBlock(
+ code, blockIterator, Collections.singleton(instructionToAdd), options);
+ }
default void addAndPositionBeforeNewInstruction(Instruction instruction) {
add(instruction);
diff --git a/src/main/java/com/android/tools/r8/ir/code/LinearFlowInstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/LinearFlowInstructionListIterator.java
index 63c5c94..0885e72 100644
--- a/src/main/java/com/android/tools/r8/ir/code/LinearFlowInstructionListIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/LinearFlowInstructionListIterator.java
@@ -210,16 +210,6 @@
}
@Override
- public BasicBlock addThrowingInstructionToPossiblyThrowingBlock(
- IRCode code,
- ListIterator<BasicBlock> blockIterator,
- Instruction instruction,
- InternalOptions options) {
- return currentBlockIterator.addThrowingInstructionToPossiblyThrowingBlock(
- code, blockIterator, instruction, options);
- }
-
- @Override
public void removeOrReplaceByDebugLocalRead() {
currentBlockIterator.removeOrReplaceByDebugLocalRead();
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/NextUntilIterator.java b/src/main/java/com/android/tools/r8/ir/code/NextUntilIterator.java
index d87fed8..8877b0e 100644
--- a/src/main/java/com/android/tools/r8/ir/code/NextUntilIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/NextUntilIterator.java
@@ -24,4 +24,19 @@
}
return null;
}
+
+ /**
+ * Continue to call {@link #next} until it returns the given item.
+ *
+ * @returns item if it was found, null otherwise.
+ */
+ @SuppressWarnings({"TypeParameterUnusedInFormals", "unchecked"})
+ default <S extends T> S nextUntil(T item) {
+ while (hasNext()) {
+ if (next() == item) {
+ return (S) item;
+ }
+ }
+ return null;
+ }
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/NumericType.java b/src/main/java/com/android/tools/r8/ir/code/NumericType.java
index 85b80b5..4e8d0d0 100644
--- a/src/main/java/com/android/tools/r8/ir/code/NumericType.java
+++ b/src/main/java/com/android/tools/r8/ir/code/NumericType.java
@@ -16,6 +16,10 @@
FLOAT,
DOUBLE;
+ public boolean isInt() {
+ return this == INT;
+ }
+
public DexType toDexType(DexItemFactory factory) {
switch (this) {
case BYTE:
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 7383048..dcc3abf 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
@@ -898,7 +898,7 @@
timing.end();
}
- if (options.debug || appView.getKeepInfo(code.context()).isPinned(options)) {
+ if (options.debug || appView.getKeepInfo(code.context()).isCodeReplacementAllowed(options)) {
return;
}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
index ca0f4269..679a884 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
@@ -116,6 +116,7 @@
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.LazyBox;
import com.android.tools.r8.verticalclassmerging.InterfaceTypeToClassTypeLensCodeRewriterHelper;
+import com.google.common.collect.ImmutableList;
import com.google.common.collect.Sets;
import java.util.ArrayDeque;
import java.util.ArrayList;
@@ -721,7 +722,7 @@
Instruction replacement =
new InstructionReplacer(code, current, iterator, affectedPhis)
.replaceInstructionIfTypeChanged(
- constClass.getValue(),
+ constClass.getType(),
(t, v) ->
t.isPrimitiveType() || t.isVoidType()
? StaticGet.builder()
@@ -1062,7 +1063,8 @@
.setOutValue(castOutValue)
.setPosition(fieldGet.asFieldInstruction())
.build();
- iterator.addThrowingInstructionToPossiblyThrowingBlock(code, blocks, checkCast, options);
+ iterator.addPossiblyThrowingInstructionsToPossiblyThrowingBlock(
+ code, blocks, ImmutableList.of(checkCast), options);
affectedPhis.addAll(checkCast.outValue().uniquePhiUsers());
}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java b/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java
index a8fe188..d31a884 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java
@@ -52,13 +52,17 @@
CallGraph callGraph,
MethodProcessorEventConsumer eventConsumer,
ProgramMethodSet methodsToProcess) {
- this.callSiteInformation = callGraph.createCallSiteInformation(appView, this);
this.eventConsumer = eventConsumer;
this.methodsToProcess = methodsToProcess;
this.processorContext = appView.createProcessorContext();
+ this.callSiteInformation = callGraph.createCallSiteInformation(appView, this);
this.waves = createWaves(callGraph);
}
+ public void addMethodToProcess(ProgramMethod method) {
+ methodsToProcess.add(method);
+ }
+
public void markCallersForProcessing(ProgramMethod method) {
assert wave.contains(method);
synchronized (methodsToProcess) {
@@ -173,10 +177,6 @@
});
put(set);
}
- if (methodsToReprocessBuilder.isEmpty()) {
- // Nothing to revisit.
- return null;
- }
ProgramMethodSet methodsToReprocess = methodsToReprocessBuilder.build(appView);
// TODO(b/333677610): Check this assert when bridges synthesized by member rebinding is
// always removed
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/callgraph/CallSiteInformation.java b/src/main/java/com/android/tools/r8/ir/conversion/callgraph/CallSiteInformation.java
index 84685ae..f89b900 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/callgraph/CallSiteInformation.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/callgraph/CallSiteInformation.java
@@ -140,15 +140,19 @@
}
}
Set<Node> callersWithDeterministicOrder = node.getCallersWithDeterministicOrder();
- DexMethod caller = reference;
+ ProgramMethod caller = method;
// We can have recursive methods where the recursive call is the only call site. We do
// not track callers for these.
if (!callersWithDeterministicOrder.isEmpty()) {
assert callersWithDeterministicOrder.size() == 1;
- caller = callersWithDeterministicOrder.iterator().next().getMethod().getReference();
+ caller = callersWithDeterministicOrder.iterator().next().getProgramMethod();
}
assert !singleCallerMethods.containsKey(reference);
- singleCallerMethods.put(reference, caller);
+ singleCallerMethods.put(reference, caller.getReference());
+ if (methodProcessor.isPostMethodProcessor()
+ && appView.getKeepInfo(caller).isReprocessingAllowed(options, caller)) {
+ methodProcessor.asPostMethodProcessor().addMethodToProcess(caller);
+ }
} else if (numberOfCallSites > 1 && methodProcessor.isPrimaryMethodProcessor()) {
multiCallerInlineCandidates.add(reference);
}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/callgraph/InvokeExtractor.java b/src/main/java/com/android/tools/r8/ir/conversion/callgraph/InvokeExtractor.java
index 809f647..5ab36a6 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/callgraph/InvokeExtractor.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/callgraph/InvokeExtractor.java
@@ -57,13 +57,13 @@
// We don't care about calls to native methods.
return;
}
- if (!appViewWithLiveness
+ if (appViewWithLiveness
.getKeepInfo(callee)
- .isOptimizationAllowed(appViewWithLiveness.options())) {
- // Since the callee is kept and optimizations are disallowed, we cannot inline it into the
- // caller, and we also cannot collect any optimization info for the method. Therefore, we
- // drop the call edge to reduce the total number of call graph edges, which should lead to
- // fewer call graph cycles.
+ .isCodeReplacementAllowed(appViewWithLiveness.options())) {
+ // Since the code of the callee may be replaced, we cannot inline it into the caller, and we
+ // also cannot collect any optimization info for the method. Therefore, we drop the call edge
+ // to reduce the total number of call graph edges, which should lead to fewer call graph
+ // cycles.
return;
}
nodeFactory.apply(callee).addCallerConcurrently(currentMethod, likelySpuriousCallEdge);
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/ClassGetNameOptimizer.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/ClassGetNameOptimizer.java
index 73f5f8a..9941ea0 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/passes/ClassGetNameOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/ClassGetNameOptimizer.java
@@ -100,7 +100,7 @@
}
ConstClass constClass = in.definition.asConstClass();
- DexType type = constClass.getValue();
+ DexType type = constClass.getType();
int arrayDepth = type.getNumberOfLeadingSquareBrackets();
DexType baseType = type.toBaseType(dexItemFactory);
// Make sure base type is a class type.
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/FilledNewArrayRewriter.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/FilledNewArrayRewriter.java
index c5e7aaa..e408515 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/passes/FilledNewArrayRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/FilledNewArrayRewriter.java
@@ -582,7 +582,7 @@
if (elementValue.isConstString()) {
addOccurrence(elementValue.getDefinition().asConstString().getValue());
} else if (elementValue.isConstClass()) {
- addOccurrence(elementValue.getDefinition().asConstClass().getValue());
+ addOccurrence(elementValue.getDefinition().asConstClass().getType());
} else if (elementValue.isDefinedByInstructionSatisfying(Instruction::isStaticGet)) {
addOccurrence(elementValue.getDefinition().asStaticGet().getField());
}
@@ -600,7 +600,7 @@
return value;
}
} else if (elementValue.isConstClass()) {
- DexType type = elementValue.getDefinition().asConstClass().getValue();
+ DexType type = elementValue.getDefinition().asConstClass().getType();
Value value = constantValue.get(type);
if (value != null) {
seenOcourence(type);
@@ -657,7 +657,7 @@
return instruction.asStaticGet().getField();
} else {
assert instruction.isConstClass();
- return instruction.asConstClass().getValue();
+ return instruction.asConstClass().getType();
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/TrivialCheckCastAndInstanceOfRemover.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/TrivialCheckCastAndInstanceOfRemover.java
index 984e829..4a2d298 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/passes/TrivialCheckCastAndInstanceOfRemover.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/TrivialCheckCastAndInstanceOfRemover.java
@@ -274,8 +274,7 @@
if (checkCast.isSafeCheckCast()
|| checkCast
.getFirstOperand()
- .getDynamicType(appViewWithLiveness)
- .getDynamicUpperBoundType()
+ .getDynamicUpperBoundType(appViewWithLiveness)
.lessThanOrEqualUpToNullability(castTypeLattice, appView)) {
TypeElement useType =
TypeUtils.computeUseType(appViewWithLiveness, context, checkCast.outValue());
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ClassInitializerDefaultsOptimization.java b/src/main/java/com/android/tools/r8/ir/optimize/ClassInitializerDefaultsOptimization.java
index b6804e7..f8b502a 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/ClassInitializerDefaultsOptimization.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/ClassInitializerDefaultsOptimization.java
@@ -563,7 +563,7 @@
Value inValue = invoke.inValues().get(0);
return !inValue.isPhi()
&& inValue.definition.isConstClass()
- && inValue.definition.asConstClass().getValue() == clazz.type;
+ && inValue.definition.asConstClass().getType() == clazz.type;
}
return false;
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ConstantCanonicalizer.java b/src/main/java/com/android/tools/r8/ir/optimize/ConstantCanonicalizer.java
index 13a06ea..7a18075 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/ConstantCanonicalizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/ConstantCanonicalizer.java
@@ -163,7 +163,7 @@
assert candidate.instructionTypeCanBeCanonicalized();
switch (candidate.opcode()) {
case CONST_CLASS:
- return candidate.asConstClass().getValue().hashCode();
+ return candidate.asConstClass().getType().hashCode();
case CONST_NUMBER:
return Long.hashCode(candidate.asConstNumber().getRawValue())
+ 13 * candidate.outType().hashCode();
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 4b9c547..99463ac 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
@@ -1299,15 +1299,28 @@
// Convert and remove virtual single caller inlined methods to abstract or throw null.
singleCallerInlinedMethodsForClass.removeIf(
(callee, caller) -> {
- // TODO(b/203188583): Enable pruning of methods with generic signatures. For this to
- // work we need to pass in a seed to GenericSignatureContextBuilder.create in R8.
- if (callee.getDefinition().belongsToVirtualPool()
- || callee.getDefinition().getGenericSignature().hasSignature()) {
+ boolean convertToAbstractOrThrowNullMethod =
+ callee.getDefinition().belongsToVirtualPool();
+ if (callee.getDefinition().getGenericSignature().hasSignature()) {
+ // TODO(b/203188583): Enable pruning of methods with generic signatures. For this
+ // to work we need to pass in a seed to GenericSignatureContextBuilder.create in
+ // R8.
+ convertToAbstractOrThrowNullMethod = true;
+ } else if (appView.options().configurationDebugging
+ && appView.getSyntheticItems().isSynthetic(callee.getHolder())) {
+ // If static synthetic methods are removed after being single caller inlined, we
+ // need to unregister them as synthetic methods in the synthetic items collection.
+ // This means that they will not be renamed to ExternalSynthetic leading to
+ // assertion errors. This should only be a problem when configuration debugging is
+ // enabled, since configuration debugging disables shrinking of the synthetic
+ // method's holder.
+ convertToAbstractOrThrowNullMethod = true;
+ }
+ if (convertToAbstractOrThrowNullMethod) {
callee.convertToAbstractOrThrowNullMethod(appView);
converter.onMethodCodePruned(callee);
- return true;
}
- return false;
+ return convertToAbstractOrThrowNullMethod;
});
// Remove direct single caller inlined methods from the application.
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ServiceLoaderRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/ServiceLoaderRewriter.java
index 86706f2..d7bf459 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/ServiceLoaderRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/ServiceLoaderRewriter.java
@@ -4,34 +4,45 @@
package com.android.tools.r8.ir.optimize;
+import com.android.tools.r8.FeatureSplit;
import com.android.tools.r8.androidapi.AndroidApiLevelCompute;
import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
+import com.android.tools.r8.features.ClassToFeatureSplitMap;
import com.android.tools.r8.graph.AppServices;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexItemFactory.IteratorMethods;
import com.android.tools.r8.graph.DexItemFactory.ServiceLoaderMethods;
-import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexProto;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.MethodAccessFlags;
import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.code.BasicBlock;
import com.android.tools.r8.ir.code.ConstClass;
import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.IRCodeInstructionListIterator;
import com.android.tools.r8.ir.code.Instruction;
-import com.android.tools.r8.ir.code.InstructionListIterator;
+import com.android.tools.r8.ir.code.InvokeDirect;
+import com.android.tools.r8.ir.code.InvokeMethod;
import com.android.tools.r8.ir.code.InvokeStatic;
import com.android.tools.r8.ir.code.InvokeVirtual;
+import com.android.tools.r8.ir.code.NewInstance;
+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.conversion.MethodProcessor;
import com.android.tools.r8.ir.conversion.passes.CodeRewriterPass;
import com.android.tools.r8.ir.conversion.passes.result.CodeRewriterResult;
import com.android.tools.r8.ir.desugar.ServiceLoaderSourceCode;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.utils.BooleanBox;
+import com.android.tools.r8.utils.ConsumerUtils;
import com.android.tools.r8.utils.ListUtils;
-import com.google.common.collect.ImmutableList;
+import com.android.tools.r8.utils.WorkList;
import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
@@ -67,11 +78,16 @@
private final AndroidApiLevelCompute apiLevelCompute;
private final ServiceLoaderMethods serviceLoaderMethods;
+ private final IteratorMethods iteratorMethods;
+ private final boolean hasAssumeNoSideEffects;
public ServiceLoaderRewriter(AppView<?> appView) {
super(appView);
this.apiLevelCompute = appView.apiLevelCompute();
this.serviceLoaderMethods = appView.dexItemFactory().serviceLoaderMethods;
+ this.iteratorMethods = appView.dexItemFactory().iteratorMethods;
+ hasAssumeNoSideEffects =
+ appView.getAssumeInfoCollection().isSideEffectFree(serviceLoaderMethods.load);
}
@Override
@@ -100,156 +116,396 @@
IRCode code,
MethodProcessor methodProcessor,
MethodProcessingContext methodProcessingContext) {
- InstructionListIterator instructionIterator = code.instructionListIterator();
// Create a map from service type to loader methods local to this context since two
// service loader calls to the same type in different methods and in the same wave can race.
Map<DexType, DexEncodedMethod> synthesizedServiceLoaders = new IdentityHashMap<>();
- while (instructionIterator.hasNext()) {
- Instruction instruction = instructionIterator.next();
+ IRCodeInstructionListIterator iterator = code.instructionListIterator();
+ Map<Instruction, Instruction> replacements = new HashMap<>();
+ Map<Instruction, List<Instruction>> replacementExtras = new HashMap<>();
- // Check if instruction is an invoke static on the desired form of ServiceLoader.load.
- if (!instruction.isInvokeStatic()) {
+ while (iterator.hasNext()) {
+ Instruction instruction = iterator.next();
+
+ // Find ServiceLoader.load() calls.
+ InvokeStatic invokeInstr = instruction.asInvokeStatic();
+ if (invokeInstr == null
+ || !serviceLoaderMethods.isLoadMethod(invokeInstr.getInvokedMethod())) {
continue;
}
- InvokeStatic serviceLoaderLoad = instruction.asInvokeStatic();
- DexMethod invokedMethod = serviceLoaderLoad.getInvokedMethod();
- if (!serviceLoaderMethods.isLoadMethod(invokedMethod)) {
+ // See if it can be optimized.
+ ServiceLoaderLoadResult loadResult = analyzeServiceLoaderLoad(code, invokeInstr);
+ if (loadResult == null) {
continue;
}
+ // See if there is a subsequent iterator() call that can be optimized.
+ DirectRewriteResult directRewriteResult = analyzeForDirectRewrite(loadResult);
- // Check that the first argument is a const class.
- Value argument = serviceLoaderLoad.getFirstArgument().getAliasedValue();
- if (!argument.isDefinedByInstructionSatisfying(Instruction::isConstClass)) {
- report(code.context(), null, "The service loader type could not be determined");
- continue;
+ // Remove ServiceLoader.load()
+ replacements.put(loadResult.loadInvoke, code.createConstNull());
+ // Remove getClassLoader() if we are about to remove all users.
+ if (loadResult.classLoaderInvoke != null
+ && loadResult.classLoaderInvoke.outValue().aliasedUsers().stream()
+ .allMatch(ins -> ins.isAssume() || replacements.containsKey(ins))) {
+ replacements.put(loadResult.classLoaderInvoke, code.createConstNull());
}
-
- ConstClass constClass = argument.getDefinition().asConstClass();
- if (invokedMethod.isNotIdenticalTo(serviceLoaderMethods.loadWithClassLoader)) {
- report(
- code.context(),
- constClass.getType(),
- "Inlining is only supported for `java.util.ServiceLoader.load(java.lang.Class,"
- + " java.lang.ClassLoader)`");
- continue;
+ if (directRewriteResult != null) {
+ populateDirectRewriteChanges(
+ code, replacements, replacementExtras, loadResult, directRewriteResult);
+ } else {
+ populateSyntheticChanges(
+ code,
+ methodProcessor,
+ methodProcessingContext,
+ synthesizedServiceLoaders,
+ replacements,
+ loadResult);
}
-
- String invalidUserMessage =
- "The returned ServiceLoader instance must only be used in a call to `java.util.Iterator"
- + " java.lang.ServiceLoader.iterator()`";
- Value serviceLoaderLoadOut = serviceLoaderLoad.outValue();
- if (!serviceLoaderLoadOut.hasSingleUniqueUser() || serviceLoaderLoadOut.hasPhiUsers()) {
- report(code.context(), constClass.getType(), invalidUserMessage);
- continue;
- }
-
- // Check that the only user is a call to iterator().
- InvokeVirtual singleUniqueUser = serviceLoaderLoadOut.singleUniqueUser().asInvokeVirtual();
- if (singleUniqueUser == null
- || singleUniqueUser.getInvokedMethod().isNotIdenticalTo(serviceLoaderMethods.iterator)) {
- report(
- code.context(), constClass.getType(), invalidUserMessage + ", but found other usages");
- continue;
- }
-
- // Check that the service is not kept.
- if (appView().appInfo().isPinnedWithDefinitionLookup(constClass.getValue())) {
- report(code.context(), constClass.getType(), "The service loader type is kept");
- continue;
- }
-
- // Check that the service is configured in the META-INF/services.
- AppServices appServices = appView.appServices();
- if (!appServices.allServiceTypes().contains(constClass.getValue())) {
- // Error already reported in the Enqueuer.
- continue;
- }
-
- // Check that we are not service loading anything from a feature into base.
- if (appServices.hasServiceImplementationsInFeature(appView(), constClass.getValue())) {
- report(
- code.context(),
- constClass.getType(),
- "The service loader type has implementations in a feature split");
- continue;
- }
-
- // Check that ClassLoader used is the ClassLoader defined for the service configuration
- // that we are instantiating or NULL.
- Value classLoaderValue = serviceLoaderLoad.getLastArgument().getAliasedValue();
- if (classLoaderValue.isPhi()) {
- report(
- code.context(),
- constClass.getType(),
- "The java.lang.ClassLoader argument must be defined locally as null or "
- + constClass.getType()
- + ".class.getClassLoader()");
- continue;
- }
- InvokeVirtual classLoaderInvoke = classLoaderValue.getDefinition().asInvokeVirtual();
- boolean isGetClassLoaderOnConstClassOrNull =
- classLoaderValue.getType().isNullType()
- || (classLoaderInvoke != null
- && classLoaderInvoke.arguments().size() == 1
- && classLoaderInvoke.getReceiver().getAliasedValue().isConstClass()
- && classLoaderInvoke
- .getReceiver()
- .getAliasedValue()
- .getDefinition()
- .asConstClass()
- .getValue()
- .isIdenticalTo(constClass.getValue()));
- if (!isGetClassLoaderOnConstClassOrNull) {
- report(
- code.context(),
- constClass.getType(),
- "The java.lang.ClassLoader argument must be defined locally as null or "
- + constClass.getType()
- + ".class.getClassLoader()");
- continue;
- }
-
- List<DexType> dexTypes = appServices.serviceImplementationsFor(constClass.getValue());
- List<DexClass> classes = new ArrayList<>(dexTypes.size());
- boolean seenNull = false;
- for (DexType serviceImpl : dexTypes) {
- DexClass serviceImplementation = appView.definitionFor(serviceImpl);
- if (serviceImplementation == null) {
- report(
- code.context(),
- constClass.getType(),
- "Unable to find definition for service implementation " + serviceImpl.getTypeName());
- seenNull = true;
- }
- classes.add(serviceImplementation);
- }
- if (seenNull) {
- continue;
- }
-
- // We can perform the rewrite of the ServiceLoader.load call.
- DexEncodedMethod synthesizedMethod =
- synthesizedServiceLoaders.computeIfAbsent(
- constClass.getValue(),
- service -> {
- DexEncodedMethod addedMethod =
- createSynthesizedMethod(
- service, classes, methodProcessor, methodProcessingContext);
- if (appView.options().isGeneratingClassFiles()) {
- addedMethod.upgradeClassFileVersion(
- code.context().getDefinition().getClassFileVersion());
- }
- return addedMethod;
- });
-
- new Rewriter(code, instructionIterator, serviceLoaderLoad)
- .perform(classLoaderInvoke, synthesizedMethod.getReference());
}
+
+ if (replacements.isEmpty()) {
+ return CodeRewriterResult.NO_CHANGE;
+ }
+
+ AffectedValues affectedValues = new AffectedValues();
+ iterator = code.instructionListIterator();
+ while (iterator.hasNext()) {
+ Instruction instruction = iterator.next();
+ Instruction replacement = replacements.get(instruction);
+ if (replacement == null) {
+ continue;
+ }
+ iterator.replaceCurrentInstruction(replacement, affectedValues);
+ List<Instruction> extras = replacementExtras.get(instruction);
+ if (extras != null) {
+ iterator.addPossiblyThrowingInstructionsToPossiblyThrowingBlock(extras, options);
+ if (ListUtils.last(extras).isThrow()) {
+ iterator.removeRemainingInBlockIgnoreOutValue();
+ }
+ }
+ }
+
+ code.removeUnreachableBlocks(affectedValues, ConsumerUtils.emptyConsumer());
+ code.removeRedundantBlocks();
+ affectedValues.narrowingWithAssumeRemoval(appView, code);
assert code.isConsistentSSA(appView);
- return synthesizedServiceLoaders.isEmpty()
- ? CodeRewriterResult.NO_CHANGE
- : CodeRewriterResult.HAS_CHANGED;
+
+ return CodeRewriterResult.HAS_CHANGED;
+ }
+
+ private void populateSyntheticChanges(
+ IRCode code,
+ MethodProcessor methodProcessor,
+ MethodProcessingContext methodProcessingContext,
+ Map<DexType, DexEncodedMethod> synthesizedServiceLoaders,
+ Map<Instruction, Instruction> replacements,
+ ServiceLoaderLoadResult loadResult) {
+ DexEncodedMethod synthesizedMethod =
+ synthesizedServiceLoaders.computeIfAbsent(
+ loadResult.serviceType,
+ service -> {
+ DexEncodedMethod addedMethod =
+ createSynthesizedMethod(
+ service, loadResult.implClasses, methodProcessor, methodProcessingContext);
+ if (appView.options().isGeneratingClassFiles()) {
+ addedMethod.upgradeClassFileVersion(
+ code.context().getDefinition().getClassFileVersion());
+ }
+ return addedMethod;
+ });
+ InvokeStatic synthesizedInvoke =
+ new InvokeStatic(
+ synthesizedMethod.getReference(),
+ loadResult.iteratorInvoke.outValue(),
+ Collections.emptyList());
+ replacements.put(loadResult.iteratorInvoke, synthesizedInvoke);
+ }
+
+ private void populateDirectRewriteChanges(
+ IRCode code,
+ Map<Instruction, Instruction> replacements,
+ Map<Instruction, List<Instruction>> replacementExtras,
+ ServiceLoaderLoadResult loadResult,
+ DirectRewriteResult directRewriteResult) {
+ // Remove ServiceLoader.iterator()
+ replacements.put(loadResult.iteratorInvoke, code.createConstNull());
+
+ // Iterator.hasNext() --> true / false
+ if (directRewriteResult.hasNextInstr != null) {
+ replacements.put(
+ directRewriteResult.hasNextInstr,
+ code.createBooleanConstant(!loadResult.implClasses.isEmpty()));
+ }
+ if (directRewriteResult.nextInstr != null) {
+ Position position = directRewriteResult.nextInstr.getPosition();
+ if (loadResult.implClasses.isEmpty()) {
+ // Iterator.next() -> null
+ replacements.put(directRewriteResult.nextInstr, code.createConstNull());
+
+ // throw new NoSuchElementException()
+ NewInstance newInstanceInstr =
+ new NewInstance(
+ dexItemFactory.noSuchElementExceptionType,
+ code.createValue(
+ dexItemFactory.noSuchElementExceptionType.toNonNullTypeElement(appView)));
+ InvokeDirect initInstr =
+ new InvokeDirect(
+ dexItemFactory.noSuchElementExceptionInit,
+ null,
+ List.of(newInstanceInstr.outValue()));
+ Throw throwInstr = new Throw(newInstanceInstr.outValue());
+
+ newInstanceInstr.setPosition(position);
+ initInstr.setPosition(position);
+ throwInstr.setPosition(position);
+ replacementExtras.put(
+ directRewriteResult.nextInstr, List.of(newInstanceInstr, initInstr, throwInstr));
+ } else {
+ // Iterator.next() -> new ServiceImpl()
+ DexType clazz = loadResult.implClasses.get(0).getType();
+ NewInstance newInstance =
+ new NewInstance(
+ clazz,
+ code.createValue(
+ clazz.toNonNullTypeElement(appView),
+ directRewriteResult.nextInstr.getLocalInfo()));
+ replacements.put(directRewriteResult.nextInstr, newInstance);
+ InvokeDirect initInstr =
+ new InvokeDirect(
+ dexItemFactory.createInstanceInitializer(clazz),
+ null,
+ List.of(newInstance.outValue()));
+ initInstr.setPosition(position);
+ replacementExtras.put(directRewriteResult.nextInstr, List.of(initInstr));
+ }
+ }
+ }
+
+ private ServiceLoaderLoadResult analyzeServiceLoaderLoad(IRCode code, InvokeStatic invokeInstr) {
+ // Check that the first argument is a const class.
+ Value argument = invokeInstr.getFirstArgument().getAliasedValue();
+ if (!argument.isDefinedByInstructionSatisfying(Instruction::isConstClass)) {
+ report(code.context(), null, "The service loader type could not be determined");
+ return null;
+ }
+
+ ConstClass constClass = argument.getDefinition().asConstClass();
+ DexType serviceType = constClass.getType();
+ if (invokeInstr.getInvokedMethod().isNotIdenticalTo(serviceLoaderMethods.loadWithClassLoader)) {
+ report(
+ code.context(),
+ serviceType,
+ "Inlining is only supported for `java.util.ServiceLoader.load(java.lang.Class,"
+ + " java.lang.ClassLoader)`");
+ return null;
+ }
+
+ String invalidUserMessage =
+ "The returned ServiceLoader instance must only be used in a call to `java.util.Iterator"
+ + " java.lang.ServiceLoader.iterator()`";
+ Value serviceLoaderLoadOut = invokeInstr.outValue();
+ if (!serviceLoaderLoadOut.hasSingleUniqueUser() || serviceLoaderLoadOut.hasPhiUsers()) {
+ report(code.context(), serviceType, invalidUserMessage);
+ return null;
+ }
+
+ // Check that the only user is a call to iterator().
+ InvokeVirtual iteratorInvoke = serviceLoaderLoadOut.singleUniqueUser().asInvokeVirtual();
+ if (iteratorInvoke == null
+ || iteratorInvoke.getInvokedMethod().isNotIdenticalTo(serviceLoaderMethods.iterator)) {
+ report(code.context(), serviceType, invalidUserMessage + ", but found other usages");
+ return null;
+ }
+
+ // Check that the service is not kept.
+ if (appView().appInfo().isPinnedWithDefinitionLookup(serviceType)) {
+ report(code.context(), serviceType, "The service loader type is kept");
+ return null;
+ }
+
+ // Check that ClassLoader used is the ClassLoader defined for the service configuration
+ // that we are instantiating or NULL.
+ Value classLoaderValue = invokeInstr.getLastArgument().getAliasedValue();
+ if (classLoaderValue.isPhi()) {
+ report(
+ code.context(),
+ serviceType,
+ "The java.lang.ClassLoader argument must be defined locally as null or "
+ + serviceType
+ + ".class.getClassLoader()");
+ return null;
+ }
+ boolean isNullClassLoader = classLoaderValue.getType().isNullType();
+ InvokeVirtual classLoaderInvoke = classLoaderValue.getDefinition().asInvokeVirtual();
+ boolean isGetClassLoaderOnConstClass =
+ classLoaderInvoke != null
+ && classLoaderInvoke.arguments().size() == 1
+ && classLoaderInvoke.getReceiver().getAliasedValue().isConstClass()
+ && classLoaderInvoke
+ .getReceiver()
+ .getAliasedValue()
+ .getDefinition()
+ .asConstClass()
+ .getType()
+ .isIdenticalTo(serviceType);
+ if (!isNullClassLoader && !isGetClassLoaderOnConstClass) {
+ report(
+ code.context(),
+ serviceType,
+ "The java.lang.ClassLoader argument must be defined locally as null or "
+ + serviceType
+ + ".class.getClassLoader()");
+ return null;
+ }
+
+ // Check that we are not service loading anything from a feature into base.
+ AppServices appServices = appView.appServices();
+ // Check that we are not service loading anything from a feature into base.
+ if (hasServiceImplementationInDifferentFeature(code, serviceType, isNullClassLoader)) {
+ return null;
+ }
+
+ List<DexType> dexTypes = appServices.serviceImplementationsFor(serviceType);
+ List<DexClass> implClasses = new ArrayList<>(dexTypes.size());
+ for (DexType serviceImpl : dexTypes) {
+ DexClass serviceImplementation = appView.definitionFor(serviceImpl);
+ if (serviceImplementation == null) {
+ report(
+ code.context(),
+ serviceType,
+ "Unable to find definition for service implementation " + serviceImpl.getTypeName());
+ return null;
+ }
+ if (appView.isSubtype(serviceImpl, serviceType).isFalse()) {
+ report(
+ code.context(),
+ serviceType,
+ "Implementation is not a subtype of the service: " + serviceImpl.getTypeName());
+ return null;
+ }
+ DexEncodedMethod method = serviceImplementation.getDefaultInitializer();
+ if (method == null) {
+ report(
+ code.context(),
+ serviceType,
+ "Implementation has no default constructor: " + serviceImpl.getTypeName());
+ return null;
+ }
+ if (!method.getAccessFlags().wasPublic()) {
+ // A non-public constructor causes a ServiceConfigurationError on APIs 24 & 25 (Nougat).
+ report(
+ code.context(),
+ serviceType,
+ "Implementation's default constructor is not public: " + serviceImpl.getTypeName());
+ return null;
+ }
+ implClasses.add(serviceImplementation);
+ }
+
+ return new ServiceLoaderLoadResult(
+ invokeInstr, classLoaderInvoke, serviceType, implClasses, iteratorInvoke);
+ }
+
+ /**
+ * Checks that:
+ *
+ * <pre>
+ * * -assumenosideeffects for ServiceLoader.load() is set.
+ * * ServiceLoader.iterator() is used only by a single .hasNext() and/or .next()
+ * * .iterator(), .hasNext(), and .next() are all in the same try/catch.
+ * * .hasNext() never comes after a call to .next()
+ * * .hasNext() and .next() are not in a loop.
+ * </pre>
+ */
+ private DirectRewriteResult analyzeForDirectRewrite(ServiceLoaderLoadResult loadResult) {
+ // Require -assumenosideeffects class java.util.ServiceLoader { java.lang.Object load(...); }
+ // because this direct rewriting does not wrap exceptions in ServiceConfigurationError.
+ if (!hasAssumeNoSideEffects) {
+ return null;
+ }
+ InvokeVirtual iteratorInvoke = loadResult.iteratorInvoke;
+
+ if (iteratorInvoke.outValue().hasPhiUsers()) {
+ return null;
+ }
+ InvokeMethod hasNextInstr = null;
+ InvokeMethod nextInstr = null;
+ // We only bother to support a single call to hasNext() and next(), and they must appear within
+ // the same try/catch.
+ for (Instruction user : iteratorInvoke.outValue().aliasedUsers()) {
+ if (user.isAssume()) {
+ if (user.outValue().hasPhiUsers()) {
+ return null;
+ }
+ continue;
+ }
+ if (!user.getBlock().hasEquivalentCatchHandlers(iteratorInvoke.getBlock())) {
+ return null;
+ }
+ InvokeMethod curCall = user.asInvokeMethod();
+ if (curCall == null) {
+ return null;
+ }
+ if (curCall.getInvokedMethod().isIdenticalTo(iteratorMethods.hasNext)) {
+ if (hasNextInstr != null) {
+ return null;
+ }
+ hasNextInstr = curCall;
+ } else if (curCall.getInvokedMethod().isIdenticalTo(iteratorMethods.next)) {
+ if (nextInstr != null) {
+ return null;
+ }
+ nextInstr = curCall;
+ } else {
+ return null;
+ }
+ }
+
+ BasicBlock iteratorBlock = iteratorInvoke.getBlock();
+ BasicBlock hasNextBlock = hasNextInstr != null ? hasNextInstr.getBlock() : null;
+ BasicBlock nextBlock = nextInstr != null ? nextInstr.getBlock() : null;
+ if (hasNextBlock != null && nextBlock != null) {
+ // See if hasNext() is reachable after next().
+ if (hasNextBlock == nextBlock) {
+ if (nextBlock.iterator(nextInstr).nextUntil(hasNextInstr) != null) {
+ return null;
+ }
+ } else if (hasPredecessorPathTo(iteratorBlock, hasNextBlock, nextBlock)) {
+ return null;
+ }
+ }
+
+ // Make sure each instruction can be run at most once (no loops).
+ if (hasNextBlock != null && loopExists(iteratorBlock, hasNextBlock)) {
+ return null;
+ }
+ if (nextBlock != null && loopExists(iteratorBlock, nextBlock)) {
+ return null;
+ }
+
+ return new DirectRewriteResult(hasNextInstr, nextInstr);
+ }
+
+ private static boolean loopExists(BasicBlock subgraphEntryBlock, BasicBlock targetBlock) {
+ return hasPredecessorPathTo(subgraphEntryBlock, targetBlock, targetBlock);
+ }
+
+ private static boolean hasPredecessorPathTo(
+ BasicBlock subgraphEntryBlock, BasicBlock subgraphExitBlock, BasicBlock targetBlock) {
+ if (subgraphEntryBlock == subgraphExitBlock) {
+ return false;
+ }
+ WorkList<BasicBlock> workList = WorkList.newIdentityWorkList();
+ workList.markAsSeen(subgraphEntryBlock);
+ workList.addIfNotSeen(subgraphExitBlock.getPredecessors());
+ while (workList.hasNext()) {
+ BasicBlock curBlock = workList.next();
+ if (curBlock == targetBlock) {
+ return true;
+ }
+ workList.addIfNotSeen(curBlock.getPredecessors());
+ }
+ return false;
}
private void report(ProgramMethod method, DexType serviceLoaderType, String message) {
@@ -268,6 +524,64 @@
}
}
+ private boolean hasServiceImplementationInDifferentFeature(
+ IRCode code, DexType serviceType, boolean baseFeatureOnly) {
+ AppView<AppInfoWithLiveness> appViewWithClasses = appView();
+ ClassToFeatureSplitMap classToFeatureSplitMap =
+ appViewWithClasses.appInfo().getClassToFeatureSplitMap();
+ if (classToFeatureSplitMap.isEmpty()) {
+ return false;
+ }
+ Map<FeatureSplit, List<DexType>> featureImplementations =
+ appView.appServices().serviceImplementationsByFeatureFor(serviceType);
+ if (featureImplementations == null || featureImplementations.isEmpty()) {
+ return false;
+ }
+ DexProgramClass serviceClass = appView.definitionForProgramType(serviceType);
+ if (serviceClass == null) {
+ return false;
+ }
+ FeatureSplit serviceFeature =
+ classToFeatureSplitMap.getFeatureSplit(serviceClass, appViewWithClasses);
+ if (baseFeatureOnly && !serviceFeature.isBase()) {
+ report(
+ code.context(),
+ serviceType,
+ "ClassLoader arg was null and service interface is in non-base feature");
+ return true;
+ }
+ for (var entry : featureImplementations.entrySet()) {
+ FeatureSplit metaInfFeature = entry.getKey();
+ if (!metaInfFeature.isBase()) {
+ if (baseFeatureOnly) {
+ report(
+ code.context(),
+ serviceType,
+ "ClassLoader arg was null and META-INF/ service entry found in non-base feature");
+ return true;
+ }
+ if (metaInfFeature != serviceFeature) {
+ report(
+ code.context(),
+ serviceType,
+ "META-INF/ service found in different feature from service interface");
+ return true;
+ }
+ }
+ for (DexType impl : entry.getValue()) {
+ FeatureSplit implFeature = classToFeatureSplitMap.getFeatureSplit(impl, appViewWithClasses);
+ if (implFeature != serviceFeature) {
+ report(
+ code.context(),
+ serviceType,
+ "Implementation found in different feature from service interface: " + impl);
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
private DexEncodedMethod createSynthesizedMethod(
DexType serviceType,
List<DexClass> classes,
@@ -300,77 +614,34 @@
return method.getDefinition();
}
- /**
- * Rewriter assumes that the code is of the form:
- *
- * <pre>
- * ConstClass v1 <- X
- * ConstClass v2 <- X or NULL
- * Invoke-Virtual v3 <- v2; method: java.lang.ClassLoader java.lang.Class.getClassLoader()
- * Invoke-Static v4 <- v1, v3; method: java.util.ServiceLoader java.util.ServiceLoader
- * .load(java.lang.Class, java.lang.ClassLoader)
- * Invoke-Virtual v5 <- v4; method: java.util.Iterator java.util.ServiceLoader.iterator()
- * </pre>
- *
- * and rewrites it to:
- *
- * <pre>
- * Invoke-Static v5 <- ; method: java.util.Iterator syn(X)()
- * </pre>
- *
- * where syn(X) is the synthesized method generated for the service class.
- *
- * <p>We rely on the DeadCodeRemover to remove the ConstClasses and any aliased values no longer
- * used.
- */
- private static class Rewriter {
+ private static class ServiceLoaderLoadResult {
+ public final InvokeStatic loadInvoke;
+ public final InvokeVirtual classLoaderInvoke;
+ public final DexType serviceType;
+ public final List<DexClass> implClasses;
+ public final InvokeVirtual iteratorInvoke;
- private final IRCode code;
- private final InvokeStatic serviceLoaderLoad;
-
- private final InstructionListIterator iterator;
-
- Rewriter(IRCode code, InstructionListIterator iterator, InvokeStatic serviceLoaderLoad) {
- this.iterator = iterator;
- this.code = code;
- this.serviceLoaderLoad = serviceLoaderLoad;
- }
-
- public void perform(InvokeVirtual classLoaderInvoke, DexMethod method) {
- // Remove the ClassLoader call since this can throw and will not be removed otherwise.
- if (classLoaderInvoke != null) {
- BooleanBox allClassLoaderUsersAreServiceLoaders =
- new BooleanBox(!classLoaderInvoke.outValue().hasPhiUsers());
- classLoaderInvoke
- .outValue()
- .aliasedUsers()
- .forEach(user -> allClassLoaderUsersAreServiceLoaders.and(user == serviceLoaderLoad));
- if (allClassLoaderUsersAreServiceLoaders.get()) {
- clearGetClassLoader(classLoaderInvoke);
- iterator.nextUntil(i -> i == serviceLoaderLoad);
- }
+ public ServiceLoaderLoadResult(
+ InvokeStatic loadInvoke,
+ InvokeVirtual classLoaderInvoke,
+ DexType serviceType,
+ List<DexClass> implClasses,
+ InvokeVirtual iteratorInvoke) {
+ this.loadInvoke = loadInvoke;
+ this.classLoaderInvoke = classLoaderInvoke;
+ this.serviceType = serviceType;
+ this.implClasses = implClasses;
+ this.iteratorInvoke = iteratorInvoke;
}
+ }
- // Remove the ServiceLoader.load call.
- InvokeVirtual serviceLoaderIterator =
- serviceLoaderLoad.outValue().singleUniqueUser().asInvokeVirtual();
- iterator.replaceCurrentInstruction(code.createConstNull());
+ private static class DirectRewriteResult {
+ public final InvokeMethod nextInstr;
+ public final InvokeMethod hasNextInstr;
- // Find the iterator instruction and replace it.
- iterator.nextUntil(x -> x == serviceLoaderIterator);
- InvokeStatic synthesizedInvoke =
- new InvokeStatic(method, serviceLoaderIterator.outValue(), ImmutableList.of());
- iterator.replaceCurrentInstruction(synthesizedInvoke);
- }
-
- private void clearGetClassLoader(InvokeVirtual classLoaderInvoke) {
- while (iterator.hasPrevious()) {
- Instruction instruction = iterator.previous();
- if (instruction == classLoaderInvoke) {
- iterator.replaceCurrentInstruction(code.createConstNull());
- break;
- }
- }
+ public DirectRewriteResult(InvokeMethod hasNextInstr, InvokeMethod nextInstr) {
+ this.hasNextInstr = hasNextInstr;
+ this.nextInstr = nextInstr;
}
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java
index b26ed45..7a20f638 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java
@@ -485,7 +485,7 @@
// MyEnum a = Enum.valueOf(MyEnum.class, "A");
// - as a receiver for a name method, to allow unboxing of:
// MyEnum.class.getName();
- DexType enumType = constClass.getValue();
+ DexType enumType = constClass.getType();
if (!enumUnboxingCandidatesInfo.isCandidate(enumType)) {
return;
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java
index aabff86..bbfd363 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java
@@ -614,8 +614,7 @@
if (!invoke.getFirstArgument().isConstClass()) {
return;
}
- DexType enumType =
- invoke.getFirstArgument().getConstInstruction().asConstClass().getValue();
+ DexType enumType = invoke.getFirstArgument().getConstInstruction().asConstClass().getType();
if (!unboxedEnumsData.isUnboxedEnum(enumType)) {
return;
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
index c2b8988..fbf3767 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
@@ -247,8 +247,6 @@
IRCode code,
OptimizationFeedback feedback,
InstanceFieldInitializationInfoCollection instanceFieldInitializationInfos) {
- assert !appView.appInfo().isPinned(method);
-
if (!method.isInstanceInitializer()) {
return;
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodResolutionOptimizationInfoAnalysis.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodResolutionOptimizationInfoAnalysis.java
index 7cd5d83..291d46c 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodResolutionOptimizationInfoAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodResolutionOptimizationInfoAnalysis.java
@@ -169,9 +169,9 @@
} else {
for (DexEncodedMethod method : clazz.virtualMethods()) {
KeepMethodInfo keepInfo = appViewWithLiveness.getKeepInfo().getMethodInfo(method, clazz);
- if (!keepInfo.isShrinkingAllowed(appViewWithLiveness.options())) {
- // Method is kept and could be overridden outside app (e.g., in tests). Verify we don't
- // have any optimization info recorded for non-abstract methods.
+ if (keepInfo.isCodeReplacementAllowed(appViewWithLiveness.options())) {
+ // Method can be replaced. Verify we don't have any optimization info recorded for
+ // non-abstract methods.
assert method.isAbstract()
|| method.getOptimizationInfo().isDefault()
|| method.getOptimizationInfo().returnValueHasBeenPropagated();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/inliner/InlinerUtils.java b/src/main/java/com/android/tools/r8/ir/optimize/inliner/InlinerUtils.java
index a29b465..970ecac 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/inliner/InlinerUtils.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/inliner/InlinerUtils.java
@@ -33,7 +33,7 @@
if (monitorEnterValue.isPhi() || !monitorEnterValue.definition.isConstClass()) {
nonConstantMonitorEnterValues.add(monitorEnterValue);
} else {
- constantMonitorEnterValues.add(monitorEnterValue.definition.asConstClass().getValue());
+ constantMonitorEnterValues.add(monitorEnterValue.definition.asConstClass().getType());
}
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/EnumMethodOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/EnumMethodOptimizer.java
index f5cc0a5..6af000b 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/EnumMethodOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/EnumMethodOptimizer.java
@@ -66,7 +66,7 @@
if (invoke.getBlock().hasCatchHandlers()) {
return;
}
- DexType enumType = invoke.inValues().get(0).getConstInstruction().asConstClass().getValue();
+ DexType enumType = invoke.inValues().get(0).getConstInstruction().asConstClass().getType();
DexProgramClass enumClass = appView.definitionForProgramType(enumType);
if (enumClass == null
|| !enumClass.isEnum()
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/typechecks/CheckCastAndInstanceOfMethodSpecialization.java b/src/main/java/com/android/tools/r8/ir/optimize/typechecks/CheckCastAndInstanceOfMethodSpecialization.java
index 2e2c0a1..7bda442 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/typechecks/CheckCastAndInstanceOfMethodSpecialization.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/typechecks/CheckCastAndInstanceOfMethodSpecialization.java
@@ -119,10 +119,10 @@
return;
}
- // Verify that the methods are not pinned. They shouldn't be, since we've computed an abstract
- // return value for both.
- assert !appView.appInfo().isPinned(method);
- assert !appView.appInfo().isPinned(parentMethod);
+ // Verify that the methods are not replaceable. They shouldn't be, since we've computed an
+ // abstract return value for both.
+ assert !appView.getKeepInfo(method).isCodeReplacementAllowed(appView.options());
+ assert !appView.getKeepInfo(parentMethod).isCodeReplacementAllowed(appView.options());
if (appView
.appInfo()
diff --git a/src/main/java/com/android/tools/r8/naming/IdentifierNameStringUtils.java b/src/main/java/com/android/tools/r8/naming/IdentifierNameStringUtils.java
index 9074bdb..f312d7d 100644
--- a/src/main/java/com/android/tools/r8/naming/IdentifierNameStringUtils.java
+++ b/src/main/java/com/android/tools/r8/naming/IdentifierNameStringUtils.java
@@ -245,7 +245,7 @@
if (!classValue.isConstClass()) {
return null;
}
- DexType holderType = classValue.getConstInstruction().asConstClass().getValue();
+ DexType holderType = classValue.getConstInstruction().asConstClass().getType();
if (holderType.isArrayType()) {
// None of the fields or methods of an array type will be renamed, since they are all
// declared in the library. Hence there is no need to handle this case.
@@ -262,7 +262,7 @@
if (!fieldTypeValue.isConstClass()) {
return null;
}
- DexType fieldType = fieldTypeValue.getConstInstruction().asConstClass().getValue();
+ DexType fieldType = fieldTypeValue.getConstInstruction().asConstClass().getType();
return IdentifierNameStringLookupResult.fromUncategorized(
inferFieldInHolder(holder, dexString.toString(), fieldType));
}
@@ -419,7 +419,7 @@
return null;
}
if (value.isConstant() && value.getConstInstruction().isConstClass()) {
- return value.getConstInstruction().asConstClass().getValue();
+ return value.getConstInstruction().asConstClass().getType();
}
if (value.definition.isStaticGet()) {
return factory.primitiveTypesBoxedTypeFields.boxedFieldTypeToPrimitiveType(
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorCodeScanner.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorCodeScanner.java
index 4f374e6..8b19ac3 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorCodeScanner.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorCodeScanner.java
@@ -211,644 +211,610 @@
IRCode code,
AbstractValueSupplier abstractValueSupplier,
Timing timing) {
- timing.begin("Argument propagation scanner");
- for (Instruction instruction : code.instructions()) {
- if (instruction.isFieldPut()) {
- scan(instruction.asFieldPut(), abstractValueSupplier, method, timing);
- } else if (instruction.isInvokeMethod()) {
- scan(instruction.asInvokeMethod(), abstractValueSupplier, method, timing);
- } else if (instruction.isInvokeCustom()) {
- scan(instruction.asInvokeCustom());
- }
- }
- timing.end();
+ new CodeScanner(abstractValueSupplier, code, method).scan(timing);
}
- private void scan(
- FieldPut fieldPut,
- AbstractValueSupplier abstractValueSupplier,
- ProgramMethod context,
- Timing timing) {
- ProgramField field = fieldPut.resolveField(appView, context).getProgramField();
- if (field == null) {
- // Nothing to propagate.
- return;
- }
- addTemporaryFieldState(fieldPut, field, abstractValueSupplier, context, timing);
- }
+ protected class CodeScanner {
- private void addTemporaryFieldState(
- FieldPut fieldPut,
- ProgramField field,
- AbstractValueSupplier abstractValueSupplier,
- ProgramMethod context,
- Timing timing) {
- timing.begin("Add field state");
- fieldStates.addTemporaryFieldState(
- field,
- () -> computeFieldState(fieldPut, field, abstractValueSupplier, context, timing),
- timing,
- (existingFieldState, fieldStateToAdd) -> {
- DexType inStaticType = null;
- NonEmptyValueState newFieldState =
- existingFieldState.mutableJoin(
- appView,
- fieldStateToAdd,
- inStaticType,
- field.getType(),
- StateCloner.getCloner(),
- Action.empty());
- return narrowFieldState(field, newFieldState);
- });
- timing.end();
- }
+ protected final AbstractValueSupplier abstractValueSupplier;
+ protected final IRCode code;
+ protected final ProgramMethod context;
- private NonEmptyValueState computeFieldState(
- FieldPut fieldPut,
- ProgramField resolvedField,
- AbstractValueSupplier abstractValueSupplier,
- ProgramMethod context,
- Timing timing) {
- timing.begin("Compute field state for field-put");
- NonEmptyValueState result =
- computeFieldState(fieldPut, resolvedField, abstractValueSupplier, context);
- timing.end();
- return result;
- }
-
- private NonEmptyValueState computeFieldState(
- FieldPut fieldPut,
- ProgramField field,
- AbstractValueSupplier abstractValueSupplier,
- ProgramMethod context) {
- TypeElement fieldType = field.getType().toTypeElement(appView);
- if (!fieldPut.value().getType().lessThanOrEqual(fieldType, appView)) {
- return ValueState.unknown();
+ protected CodeScanner(
+ AbstractValueSupplier abstractValueSupplier, IRCode code, ProgramMethod method) {
+ this.abstractValueSupplier = abstractValueSupplier;
+ this.code = code;
+ this.context = method;
}
- NonEmptyValueState inFlowState = computeInFlowState(field.getType(), fieldPut.value(), context);
- if (inFlowState != null) {
- return inFlowState;
- }
-
- if (field.getType().isArrayType()) {
- Nullability nullability = fieldPut.value().getType().nullability();
- return ConcreteArrayTypeValueState.create(nullability);
- }
-
- AbstractValue abstractValue = abstractValueSupplier.getAbstractValue(fieldPut.value());
- if (abstractValue.isUnknown()) {
- abstractValue =
- getFallbackAbstractValueForField(
- field,
- () -> ObjectStateAnalysis.computeObjectState(fieldPut.value(), appView, context));
- }
- if (field.getType().isClassType()) {
- DynamicType dynamicType =
- WideningUtils.widenDynamicNonReceiverType(
- appView, fieldPut.value().getDynamicType(appView), field.getType());
- return ConcreteClassTypeValueState.create(abstractValue, dynamicType);
- } else {
- assert field.getType().isPrimitiveType();
- return ConcretePrimitiveTypeValueState.create(abstractValue);
- }
- }
-
- // If the value is an argument of the enclosing method or defined by a field-get, then clearly we
- // have no information about its abstract value (yet). Instead of treating this as having an
- // unknown runtime value, we instead record a flow constraint.
- private InFlow computeInFlow(DexType staticType, Value value, ProgramMethod context) {
- Value valueRoot = value.getAliasedValue(aliasedValueConfiguration);
- if (valueRoot.isArgument()) {
- MethodParameter inParameter =
- methodParameterFactory.create(context, valueRoot.getDefinition().asArgument().getIndex());
- return castBaseInFlow(widenBaseInFlow(staticType, inParameter, context), value);
- } else if (valueRoot.isDefinedByInstructionSatisfying(Instruction::isFieldGet)) {
- FieldGet fieldGet = valueRoot.getDefinition().asFieldGet();
- ProgramField field = fieldGet.resolveField(appView, context).getProgramField();
- if (field == null) {
- return null;
- }
- if (fieldGet.isInstanceGet()) {
- Value receiverValue = fieldGet.asInstanceGet().object();
- BaseInFlow receiverInFlow =
- asBaseInFlowOrNull(computeInFlow(staticType, receiverValue, context));
- if (receiverInFlow != null
- && receiverInFlow.equals(widenBaseInFlow(staticType, receiverInFlow, context))) {
- return new InstanceFieldReadAbstractFunction(receiverInFlow, field.getReference());
+ public void scan(Timing timing) {
+ timing.begin("Argument propagation scanner");
+ for (Instruction instruction : code.instructions()) {
+ if (instruction.isFieldPut()) {
+ scanFieldPut(instruction.asFieldPut(), timing);
+ } else if (instruction.isInvokeMethod()) {
+ scanInvoke(instruction.asInvokeMethod(), timing);
+ } else if (instruction.isInvokeCustom()) {
+ scanInvokeCustom(instruction.asInvokeCustom());
}
}
- return castBaseInFlow(
- widenBaseInFlow(staticType, fieldValueFactory.create(field), context), value);
+ timing.end();
}
- return null;
- }
- private InFlow castBaseInFlow(InFlow inFlow, Value value) {
- if (inFlow.isUnknownAbstractFunction()) {
- return inFlow;
- }
- assert inFlow.isBaseInFlow();
- Value valueRoot = value.getAliasedValue();
- if (!valueRoot.isDefinedByInstructionSatisfying(Instruction::isCheckCast)) {
- return inFlow;
- }
- CheckCast checkCast = valueRoot.getDefinition().asCheckCast();
- return new CastAbstractFunction(inFlow.asBaseInFlow(), checkCast.getType());
- }
-
- private InFlow widenBaseInFlow(DexType staticType, BaseInFlow inFlow, ProgramMethod context) {
- if (inFlow.isFieldValue()) {
- if (isFieldValueAlreadyUnknown(staticType, inFlow.asFieldValue().getField())) {
- return AbstractFunction.unknown();
+ private void scanFieldPut(FieldPut fieldPut, Timing timing) {
+ ProgramField field = fieldPut.resolveField(appView, context).getProgramField();
+ if (field == null) {
+ // Nothing to propagate.
+ return;
}
- } else {
- assert inFlow.isMethodParameter();
- if (isMethodParameterAlreadyUnknown(staticType, inFlow.asMethodParameter(), context)) {
- return AbstractFunction.unknown();
- }
+ addTemporaryFieldState(fieldPut, field, timing);
}
- return inFlow;
- }
- private NonEmptyValueState computeInFlowState(
- DexType staticType, Value value, ProgramMethod context) {
- InFlow inFlow = computeInFlow(staticType, value, context);
- if (inFlow == null) {
- return null;
+ private void addTemporaryFieldState(FieldPut fieldPut, ProgramField field, Timing timing) {
+ timing.begin("Add field state");
+ fieldStates.addTemporaryFieldState(
+ field,
+ () -> computeFieldState(fieldPut, field, timing),
+ timing,
+ (existingFieldState, fieldStateToAdd) -> {
+ DexType inStaticType = null;
+ NonEmptyValueState newFieldState =
+ existingFieldState.mutableJoin(
+ appView,
+ fieldStateToAdd,
+ inStaticType,
+ field.getType(),
+ StateCloner.getCloner(),
+ Action.empty());
+ return narrowFieldState(field, newFieldState);
+ });
+ timing.end();
}
- if (inFlow.isUnknownAbstractFunction()) {
- return ValueState.unknown();
- }
- assert inFlow.isBaseInFlow()
- || inFlow.isCastAbstractFunction()
- || inFlow.isInstanceFieldReadAbstractFunction();
- return ConcreteValueState.create(staticType, inFlow);
- }
- // Strengthens the abstract value of static final fields to a (self-)SingleFieldValue when the
- // abstract value is unknown. The soundness of this is based on the fact that static final fields
- // will never have their value changed after the <clinit> finishes, so value in a static final
- // field can always be rematerialized by reading the field.
- private NonEmptyValueState narrowFieldState(ProgramField field, NonEmptyValueState fieldState) {
- AbstractValue fallbackAbstractValue =
- getFallbackAbstractValueForField(field, ObjectState::empty);
- if (!fallbackAbstractValue.isUnknown()) {
- AbstractValue abstractValue = fieldState.getAbstractValue(appView);
- if (!abstractValue.isUnknown()) {
- return fieldState;
+ private NonEmptyValueState computeFieldState(
+ FieldPut fieldPut, ProgramField resolvedField, Timing timing) {
+ timing.begin("Compute field state for field-put");
+ NonEmptyValueState result = computeFieldState(fieldPut, resolvedField);
+ timing.end();
+ return result;
+ }
+
+ private NonEmptyValueState computeFieldState(FieldPut fieldPut, ProgramField field) {
+ TypeElement fieldType = field.getType().toTypeElement(appView);
+ if (!fieldPut.value().getType().lessThanOrEqual(fieldType, appView)) {
+ return ValueState.unknown();
}
+
+ NonEmptyValueState inFlowState =
+ computeInFlowState(field.getType(), fieldPut.value(), context);
+ if (inFlowState != null) {
+ return inFlowState;
+ }
+
if (field.getType().isArrayType()) {
- // We do not track an abstract value for array types.
- return fieldState;
+ Nullability nullability = fieldPut.value().getType().nullability();
+ return ConcreteArrayTypeValueState.create(nullability);
+ }
+
+ AbstractValue abstractValue = abstractValueSupplier.getAbstractValue(fieldPut.value());
+ if (abstractValue.isUnknown()) {
+ abstractValue =
+ getFallbackAbstractValueForField(
+ field,
+ () -> ObjectStateAnalysis.computeObjectState(fieldPut.value(), appView, context));
}
if (field.getType().isClassType()) {
DynamicType dynamicType =
- fieldState.isReferenceState()
- ? fieldState.asReferenceState().getDynamicType()
- : DynamicType.unknown();
- return new ConcreteClassTypeValueState(fallbackAbstractValue, dynamicType);
+ WideningUtils.widenDynamicNonReceiverType(
+ appView, fieldPut.value().getDynamicType(appView), field.getType());
+ return ConcreteClassTypeValueState.create(abstractValue, dynamicType);
} else {
assert field.getType().isPrimitiveType();
- return new ConcretePrimitiveTypeValueState(fallbackAbstractValue);
+ return ConcretePrimitiveTypeValueState.create(abstractValue);
}
}
- return fieldState;
- }
- private AbstractValue getFallbackAbstractValueForField(
- ProgramField field, Supplier<ObjectState> objectStateSupplier) {
- if (field.isFinalOrEffectivelyFinal(appView) && field.getAccessFlags().isStatic()) {
- return appView
- .abstractValueFactory()
- .createSingleFieldValue(field.getReference(), objectStateSupplier.get());
- }
- return AbstractValue.unknown();
- }
-
- private void scan(
- InvokeMethod invoke,
- AbstractValueSupplier abstractValueSupplier,
- ProgramMethod context,
- Timing timing) {
- DexMethod invokedMethod = invoke.getInvokedMethod();
- if (invokedMethod.getHolderType().isArrayType()) {
- // Nothing to propagate; the targeted method is not a program method.
- return;
+ // If the value is an argument of the enclosing method or defined by a field-get, then clearly
+ // we have no information about its abstract value (yet). Instead of treating this as having an
+ // unknown runtime value, we instead record a flow constraint.
+ private InFlow computeInFlow(DexType staticType, Value value, ProgramMethod context) {
+ Value valueRoot = value.getAliasedValue(aliasedValueConfiguration);
+ if (valueRoot.isArgument()) {
+ MethodParameter inParameter =
+ methodParameterFactory.create(
+ context, valueRoot.getDefinition().asArgument().getIndex());
+ return castBaseInFlow(widenBaseInFlow(staticType, inParameter, context), value);
+ } else if (valueRoot.isDefinedByInstructionSatisfying(Instruction::isFieldGet)) {
+ FieldGet fieldGet = valueRoot.getDefinition().asFieldGet();
+ ProgramField field = fieldGet.resolveField(appView, context).getProgramField();
+ if (field == null) {
+ return null;
+ }
+ if (fieldGet.isInstanceGet()) {
+ Value receiverValue = fieldGet.asInstanceGet().object();
+ BaseInFlow receiverInFlow =
+ asBaseInFlowOrNull(computeInFlow(staticType, receiverValue, context));
+ if (receiverInFlow != null
+ && receiverInFlow.equals(widenBaseInFlow(staticType, receiverInFlow, context))) {
+ return new InstanceFieldReadAbstractFunction(receiverInFlow, field.getReference());
+ }
+ }
+ return castBaseInFlow(
+ widenBaseInFlow(staticType, fieldValueFactory.create(field), context), value);
+ }
+ return null;
}
- if (appView.options().testing.checkReceiverAlwaysNullInCallSiteOptimization
- && invoke.isInvokeMethodWithReceiver()
- && invoke.asInvokeMethodWithReceiver().getReceiver().isAlwaysNull(appView)) {
- // Nothing to propagate; the invoke instruction always fails.
- return;
+ private InFlow castBaseInFlow(InFlow inFlow, Value value) {
+ if (inFlow.isUnknownAbstractFunction()) {
+ return inFlow;
+ }
+ assert inFlow.isBaseInFlow();
+ Value valueRoot = value.getAliasedValue();
+ if (!valueRoot.isDefinedByInstructionSatisfying(Instruction::isCheckCast)) {
+ return inFlow;
+ }
+ CheckCast checkCast = valueRoot.getDefinition().asCheckCast();
+ return new CastAbstractFunction(inFlow.asBaseInFlow(), checkCast.getType());
}
- SingleResolutionResult<?> resolutionResult =
- invoke.resolveMethod(appView, context).asSingleResolution();
- if (resolutionResult == null) {
- // Nothing to propagate; the invoke instruction fails.
- return;
+ private InFlow widenBaseInFlow(DexType staticType, BaseInFlow inFlow, ProgramMethod context) {
+ if (inFlow.isFieldValue()) {
+ if (isFieldValueAlreadyUnknown(staticType, inFlow.asFieldValue().getField())) {
+ return AbstractFunction.unknown();
+ }
+ } else {
+ assert inFlow.isMethodParameter();
+ if (isMethodParameterAlreadyUnknown(staticType, inFlow.asMethodParameter(), context)) {
+ return AbstractFunction.unknown();
+ }
+ }
+ return inFlow;
}
- if (!resolutionResult.getResolvedHolder().isProgramClass()) {
- // Nothing to propagate; this could dispatch to a program method, but we cannot optimize
- // methods that override non-program methods.
- return;
+ private NonEmptyValueState computeInFlowState(
+ DexType staticType, Value value, ProgramMethod context) {
+ InFlow inFlow = computeInFlow(staticType, value, context);
+ if (inFlow == null) {
+ return null;
+ }
+ if (inFlow.isUnknownAbstractFunction()) {
+ return ValueState.unknown();
+ }
+ assert inFlow.isBaseInFlow()
+ || inFlow.isCastAbstractFunction()
+ || inFlow.isInstanceFieldReadAbstractFunction();
+ return ConcreteValueState.create(staticType, inFlow);
}
- ProgramMethod resolvedMethod = resolutionResult.getResolvedProgramMethod();
- if (resolvedMethod.getDefinition().isLibraryMethodOverride().isPossiblyTrue()) {
- assert resolvedMethod.getDefinition().isLibraryMethodOverride().isTrue();
- // Nothing to propagate; we don't know anything about methods that can be called from outside
- // the program.
- return;
+ // Strengthens the abstract value of static final fields to a (self-)SingleFieldValue when the
+ // abstract value is unknown. The soundness of this is based on the fact that static final
+ // fields will never have their value changed after the <clinit> finishes, so value in a static
+ // final field can always be rematerialized by reading the field.
+ private NonEmptyValueState narrowFieldState(ProgramField field, NonEmptyValueState fieldState) {
+ AbstractValue fallbackAbstractValue =
+ getFallbackAbstractValueForField(field, ObjectState::empty);
+ if (!fallbackAbstractValue.isUnknown()) {
+ AbstractValue abstractValue = fieldState.getAbstractValue(appView);
+ if (!abstractValue.isUnknown()) {
+ return fieldState;
+ }
+ if (field.getType().isArrayType()) {
+ // We do not track an abstract value for array types.
+ return fieldState;
+ }
+ if (field.getType().isClassType()) {
+ DynamicType dynamicType =
+ fieldState.isReferenceState()
+ ? fieldState.asReferenceState().getDynamicType()
+ : DynamicType.unknown();
+ return new ConcreteClassTypeValueState(fallbackAbstractValue, dynamicType);
+ } else {
+ assert field.getType().isPrimitiveType();
+ return new ConcretePrimitiveTypeValueState(fallbackAbstractValue);
+ }
+ }
+ return fieldState;
}
- if (invoke.arguments().size() != resolvedMethod.getDefinition().getNumberOfArguments()
- || invoke.isInvokeStatic() != resolvedMethod.getAccessFlags().isStatic()) {
- // Nothing to propagate; the invoke instruction fails.
- return;
+ private AbstractValue getFallbackAbstractValueForField(
+ ProgramField field, Supplier<ObjectState> objectStateSupplier) {
+ if (field.isFinalOrEffectivelyFinal(appView) && field.getAccessFlags().isStatic()) {
+ return appView
+ .abstractValueFactory()
+ .createSingleFieldValue(field.getReference(), objectStateSupplier.get());
+ }
+ return AbstractValue.unknown();
}
- if (invoke.isInvokeInterface()) {
- if (!resolutionResult.getInitialResolutionHolder().isInterface()) {
+ private void scanInvoke(InvokeMethod invoke, Timing timing) {
+ DexMethod invokedMethod = invoke.getInvokedMethod();
+ if (invokedMethod.getHolderType().isArrayType()) {
+ // Nothing to propagate; the targeted method is not a program method.
+ return;
+ }
+
+ if (appView.options().testing.checkReceiverAlwaysNullInCallSiteOptimization
+ && invoke.isInvokeMethodWithReceiver()
+ && invoke.asInvokeMethodWithReceiver().getReceiver().isAlwaysNull(appView)) {
+ // Nothing to propagate; the invoke instruction always fails.
+ return;
+ }
+
+ SingleResolutionResult<?> resolutionResult =
+ invoke.resolveMethod(appView, context).asSingleResolution();
+ if (resolutionResult == null) {
// Nothing to propagate; the invoke instruction fails.
return;
}
- }
- if (invoke.isInvokeSuper()) {
- // Use the super target instead of the resolved method to ensure that we propagate the
- // argument information to the targeted method.
- DexClassAndMethod target =
- resolutionResult.lookupInvokeSuperTarget(context.getHolder(), appView);
- if (target == null) {
+ if (!resolutionResult.getResolvedHolder().isProgramClass()) {
+ // Nothing to propagate; this could dispatch to a program method, but we cannot optimize
+ // methods that override non-program methods.
+ return;
+ }
+
+ ProgramMethod resolvedMethod = resolutionResult.getResolvedProgramMethod();
+ if (resolvedMethod.getDefinition().isLibraryMethodOverride().isPossiblyTrue()) {
+ assert resolvedMethod.getDefinition().isLibraryMethodOverride().isTrue();
+ // Nothing to propagate; we don't know anything about methods that can be called from
+ // outside the program.
+ return;
+ }
+
+ if (invoke.arguments().size() != resolvedMethod.getDefinition().getNumberOfArguments()
+ || invoke.isInvokeStatic() != resolvedMethod.getAccessFlags().isStatic()) {
// Nothing to propagate; the invoke instruction fails.
return;
}
- if (!target.isProgramMethod()) {
- throw new Unreachable(
- "Expected super target of a non-library override to be a program method ("
- + "resolved program method: "
- + resolvedMethod
- + ", "
- + "super non-program method: "
- + target
- + ")");
+
+ if (invoke.isInvokeInterface()) {
+ if (!resolutionResult.getInitialResolutionHolder().isInterface()) {
+ // Nothing to propagate; the invoke instruction fails.
+ return;
+ }
}
- resolvedMethod = target.asProgramMethod();
+
+ if (invoke.isInvokeSuper()) {
+ // Use the super target instead of the resolved method to ensure that we propagate the
+ // argument information to the targeted method.
+ DexClassAndMethod target =
+ resolutionResult.lookupInvokeSuperTarget(context.getHolder(), appView);
+ if (target == null) {
+ // Nothing to propagate; the invoke instruction fails.
+ return;
+ }
+ if (!target.isProgramMethod()) {
+ throw new Unreachable(
+ "Expected super target of a non-library override to be a program method ("
+ + "resolved program method: "
+ + resolvedMethod
+ + ", "
+ + "super non-program method: "
+ + target
+ + ")");
+ }
+ resolvedMethod = target.asProgramMethod();
+ }
+
+ // Find the method where to store the information about the arguments from this invoke.
+ // If the invoke may dispatch to more than one method, we intentionally do not compute all
+ // possible dispatch targets and propagate the information to these methods (this is
+ // expensive). Instead we record the information in one place and then later propagate the
+ // information to all dispatch targets.
+ addTemporaryMethodState(invoke, resolvedMethod, timing);
}
- // Find the method where to store the information about the arguments from this invoke.
- // If the invoke may dispatch to more than one method, we intentionally do not compute all
- // possible dispatch targets and propagate the information to these methods (this is expensive).
- // Instead we record the information in one place and then later propagate the information to
- // all dispatch targets.
- addTemporaryMethodState(invoke, resolvedMethod, abstractValueSupplier, context, timing);
- }
+ protected void addTemporaryMethodState(
+ InvokeMethod invoke, ProgramMethod resolvedMethod, Timing timing) {
+ timing.begin("Add method state");
+ methodStates.addTemporaryMethodState(
+ appView,
+ getRepresentative(invoke, resolvedMethod),
+ existingMethodState ->
+ computeMethodState(invoke, resolvedMethod, existingMethodState, timing),
+ timing);
+ timing.end();
+ }
- protected void addTemporaryMethodState(
- InvokeMethod invoke,
- ProgramMethod resolvedMethod,
- AbstractValueSupplier abstractValueSupplier,
- ProgramMethod context,
- Timing timing) {
- timing.begin("Add method state");
- methodStates.addTemporaryMethodState(
- appView,
- getRepresentative(invoke, resolvedMethod),
- existingMethodState ->
- computeMethodState(
+ private MethodState computeMethodState(
+ InvokeMethod invoke,
+ ProgramMethod resolvedMethod,
+ MethodState existingMethodState,
+ Timing timing) {
+ assert !existingMethodState.isUnknown();
+
+ // If this invoke may target at most one method, then we compute a state that maps each
+ // parameter to the abstract value and dynamic type provided by this call site. Otherwise, we
+ // compute a polymorphic method state, which includes information about the receiver's dynamic
+ // type bounds.
+ timing.begin("Compute method state for invoke");
+ MethodState result;
+ if (shouldUsePolymorphicMethodState(invoke, resolvedMethod)) {
+ assert existingMethodState.isBottom() || existingMethodState.isPolymorphic();
+ result =
+ computePolymorphicMethodState(
+ invoke.asInvokeMethodWithReceiver(),
+ resolvedMethod,
+ existingMethodState.asPolymorphicOrBottom());
+ } else {
+ assert existingMethodState.isBottom() || existingMethodState.isMonomorphic();
+ result =
+ computeMonomorphicMethodState(
invoke,
resolvedMethod,
- abstractValueSupplier,
- context,
- existingMethodState,
- timing),
- timing);
- timing.end();
- }
+ invoke.lookupSingleProgramTarget(appView, context),
+ existingMethodState.asMonomorphicOrBottom());
+ }
+ timing.end();
+ return result;
+ }
- private MethodState computeMethodState(
- InvokeMethod invoke,
- ProgramMethod resolvedMethod,
- AbstractValueSupplier abstractValueSupplier,
- ProgramMethod context,
- MethodState existingMethodState,
- Timing timing) {
- assert !existingMethodState.isUnknown();
+ // TODO(b/190154391): Add a strategy that widens the dynamic receiver type to allow easily
+ // experimenting with the performance/size trade-off between precise/imprecise handling of
+ // dynamic dispatch.
+ private MethodState computePolymorphicMethodState(
+ InvokeMethodWithReceiver invoke,
+ ProgramMethod resolvedMethod,
+ ConcretePolymorphicMethodStateOrBottom existingMethodState) {
+ DynamicTypeWithUpperBound dynamicReceiverType = invoke.getReceiver().getDynamicType(appView);
+ // TODO(b/331587404): Investigate if we can replace the receiver by null before entering this
+ // pass, so that this special case is not needed.
+ if (dynamicReceiverType.isNullType()) {
+ assert appView.testing().allowNullDynamicTypeInCodeScanner : "b/250634405";
+ // This can happen if we were unable to determine that the receiver is a phi value where
+ // null information has not been propagated down. Ideally this case would never happen as it
+ // should be possible to replace the receiver by the null constant in this case. Since the
+ // receiver is known to be null, no argument information should be propagated to the
+ // callees, so we return bottom here.
+ return MethodState.bottom();
+ }
- // If this invoke may target at most one method, then we compute a state that maps each
- // parameter to the abstract value and dynamic type provided by this call site. Otherwise, we
- // compute a polymorphic method state, which includes information about the receiver's dynamic
- // type bounds.
- timing.begin("Compute method state for invoke");
- MethodState result;
- if (shouldUsePolymorphicMethodState(invoke, resolvedMethod)) {
- assert existingMethodState.isBottom() || existingMethodState.isPolymorphic();
- result =
- computePolymorphicMethodState(
- invoke.asInvokeMethodWithReceiver(),
- resolvedMethod,
- abstractValueSupplier,
- context,
- existingMethodState.asPolymorphicOrBottom());
- } else {
- assert existingMethodState.isBottom() || existingMethodState.isMonomorphic();
- result =
+ ProgramMethod singleTarget = invoke.lookupSingleProgramTarget(appView, context);
+ DynamicTypeWithUpperBound bounds =
+ computeBoundsForPolymorphicMethodState(resolvedMethod, singleTarget, dynamicReceiverType);
+ MethodState existingMethodStateForBounds =
+ existingMethodState.isPolymorphic()
+ ? existingMethodState.asPolymorphic().getMethodStateForBounds(bounds)
+ : MethodState.bottom();
+
+ if (existingMethodStateForBounds.isPolymorphic()) {
+ assert false;
+ return MethodState.unknown();
+ }
+
+ // If we already don't know anything about the parameters for the given type bounds, then
+ // don't compute a method state.
+ if (existingMethodStateForBounds.isUnknown()) {
+ return MethodState.bottom();
+ }
+
+ ConcreteMonomorphicMethodStateOrUnknown methodStateForBounds =
computeMonomorphicMethodState(
invoke,
resolvedMethod,
- invoke.lookupSingleProgramTarget(appView, context),
- abstractValueSupplier,
- context,
- existingMethodState.asMonomorphicOrBottom());
- }
- timing.end();
- return result;
- }
-
- // TODO(b/190154391): Add a strategy that widens the dynamic receiver type to allow easily
- // experimenting with the performance/size trade-off between precise/imprecise handling of
- // dynamic dispatch.
- private MethodState computePolymorphicMethodState(
- InvokeMethodWithReceiver invoke,
- ProgramMethod resolvedMethod,
- AbstractValueSupplier abstractValueSupplier,
- ProgramMethod context,
- ConcretePolymorphicMethodStateOrBottom existingMethodState) {
- DynamicTypeWithUpperBound dynamicReceiverType = invoke.getReceiver().getDynamicType(appView);
- // TODO(b/331587404): Investigate if we can replace the receiver by null before entering this
- // pass, so that this special case is not needed.
- if (dynamicReceiverType.isNullType()) {
- assert appView.testing().allowNullDynamicTypeInCodeScanner : "b/250634405";
- // This can happen if we were unable to determine that the receiver is a phi value where null
- // information has not been propagated down. Ideally this case would never happen as it should
- // be possible to replace the receiver by the null constant in this case.
- //
- // Since the receiver is known to be null, no argument information should be propagated to the
- // callees, so we return bottom here.
- return MethodState.bottom();
+ singleTarget,
+ existingMethodStateForBounds.asMonomorphicOrBottom(),
+ dynamicReceiverType);
+ return ConcretePolymorphicMethodState.create(bounds, methodStateForBounds);
}
- ProgramMethod singleTarget = invoke.lookupSingleProgramTarget(appView, context);
- DynamicTypeWithUpperBound bounds =
- computeBoundsForPolymorphicMethodState(resolvedMethod, singleTarget, dynamicReceiverType);
- MethodState existingMethodStateForBounds =
- existingMethodState.isPolymorphic()
- ? existingMethodState.asPolymorphic().getMethodStateForBounds(bounds)
- : MethodState.bottom();
+ private DynamicTypeWithUpperBound computeBoundsForPolymorphicMethodState(
+ ProgramMethod resolvedMethod,
+ ProgramMethod singleTarget,
+ DynamicTypeWithUpperBound dynamicReceiverType) {
+ DynamicTypeWithUpperBound bounds =
+ singleTarget != null
+ ? DynamicType.createExact(
+ singleTarget.getHolderType().toTypeElement(appView).asClassType())
+ : dynamicReceiverType.withNullability(Nullability.maybeNull());
- if (existingMethodStateForBounds.isPolymorphic()) {
- assert false;
- return MethodState.unknown();
- }
+ // We intentionally drop the nullability for the type bounds. This increases the number of
+ // collisions in the polymorphic method states, which does not change the precision (since the
+ // nullability does not have any impact on the possible dispatch targets) and is good for
+ // state pruning.
+ assert bounds.getDynamicUpperBoundType().nullability().isMaybeNull();
- // If we already don't know anything about the parameters for the given type bounds, then don't
- // compute a method state.
- if (existingMethodStateForBounds.isUnknown()) {
- return MethodState.bottom();
- }
+ // If the bounds are trivial (i.e., the upper bound is equal to the holder of the virtual root
+ // method), then widen the type bounds to 'unknown'.
+ DexMethod virtualRootMethod = getVirtualRootMethod(resolvedMethod);
+ if (virtualRootMethod == null) {
+ assert false : "Unexpected virtual method without root: " + resolvedMethod;
+ return bounds;
+ }
- ConcreteMonomorphicMethodStateOrUnknown methodStateForBounds =
- computeMonomorphicMethodState(
- invoke,
- resolvedMethod,
- singleTarget,
- abstractValueSupplier,
- context,
- existingMethodStateForBounds.asMonomorphicOrBottom(),
- dynamicReceiverType);
- return ConcretePolymorphicMethodState.create(bounds, methodStateForBounds);
- }
-
- private DynamicTypeWithUpperBound computeBoundsForPolymorphicMethodState(
- ProgramMethod resolvedMethod,
- ProgramMethod singleTarget,
- DynamicTypeWithUpperBound dynamicReceiverType) {
- DynamicTypeWithUpperBound bounds =
- singleTarget != null
- ? DynamicType.createExact(
- singleTarget.getHolderType().toTypeElement(appView).asClassType())
- : dynamicReceiverType.withNullability(Nullability.maybeNull());
-
- // We intentionally drop the nullability for the type bounds. This increases the number of
- // collisions in the polymorphic method states, which does not change the precision (since the
- // nullability does not have any impact on the possible dispatch targets) and is good for state
- // pruning.
- assert bounds.getDynamicUpperBoundType().nullability().isMaybeNull();
-
- // If the bounds are trivial (i.e., the upper bound is equal to the holder of the virtual root
- // method), then widen the type bounds to 'unknown'.
- DexMethod virtualRootMethod = getVirtualRootMethod(resolvedMethod);
- if (virtualRootMethod == null) {
- assert false : "Unexpected virtual method without root: " + resolvedMethod;
+ DynamicType trivialBounds =
+ DynamicType.create(
+ appView, virtualRootMethod.getHolderType().toTypeElement(appView).asClassType());
+ if (bounds.equals(trivialBounds)) {
+ return DynamicType.unknown();
+ }
return bounds;
}
- DynamicType trivialBounds =
- DynamicType.create(
- appView, virtualRootMethod.getHolderType().toTypeElement(appView).asClassType());
- if (bounds.equals(trivialBounds)) {
- return DynamicType.unknown();
- }
- return bounds;
- }
-
- private ConcreteMonomorphicMethodStateOrUnknown computeMonomorphicMethodState(
- InvokeMethod invoke,
- ProgramMethod resolvedMethod,
- ProgramMethod singleTarget,
- AbstractValueSupplier abstractValueSupplier,
- ProgramMethod context,
- ConcreteMonomorphicMethodStateOrBottom existingMethodState) {
- return computeMonomorphicMethodState(
- invoke,
- resolvedMethod,
- singleTarget,
- abstractValueSupplier,
- context,
- existingMethodState,
- invoke.isInvokeMethodWithReceiver()
- ? invoke.getFirstArgument().getDynamicType(appView)
- : null);
- }
-
- @SuppressWarnings("UnusedVariable")
- private ConcreteMonomorphicMethodStateOrUnknown computeMonomorphicMethodState(
- InvokeMethod invoke,
- ProgramMethod resolvedMethod,
- ProgramMethod singleTarget,
- AbstractValueSupplier abstractValueSupplier,
- ProgramMethod context,
- ConcreteMonomorphicMethodStateOrBottom existingMethodState,
- DynamicType dynamicReceiverType) {
- List<ValueState> parameterStates = new ArrayList<>(invoke.arguments().size());
-
- MethodReprocessingCriteria methodReprocessingCriteria =
- singleTarget != null
- ? reprocessingCriteriaCollection.getReprocessingCriteria(singleTarget)
- : MethodReprocessingCriteria.alwaysReprocess();
-
- int argumentIndex = 0;
- if (invoke.isInvokeMethodWithReceiver()) {
- assert dynamicReceiverType != null;
- parameterStates.add(
- computeParameterStateForReceiver(
- resolvedMethod,
- dynamicReceiverType,
- existingMethodState,
- methodReprocessingCriteria.getParameterReprocessingCriteria(0)));
- argumentIndex++;
+ private ConcreteMonomorphicMethodStateOrUnknown computeMonomorphicMethodState(
+ InvokeMethod invoke,
+ ProgramMethod resolvedMethod,
+ ProgramMethod singleTarget,
+ ConcreteMonomorphicMethodStateOrBottom existingMethodState) {
+ return computeMonomorphicMethodState(
+ invoke,
+ resolvedMethod,
+ singleTarget,
+ existingMethodState,
+ invoke.isInvokeMethodWithReceiver()
+ ? invoke.getFirstArgument().getDynamicType(appView)
+ : null);
}
- for (; argumentIndex < invoke.arguments().size(); argumentIndex++) {
- parameterStates.add(
- computeParameterStateForNonReceiver(
- invoke,
- singleTarget,
- argumentIndex,
- invoke.getArgument(argumentIndex),
- abstractValueSupplier,
- context,
- existingMethodState));
+ @SuppressWarnings("UnusedVariable")
+ private ConcreteMonomorphicMethodStateOrUnknown computeMonomorphicMethodState(
+ InvokeMethod invoke,
+ ProgramMethod resolvedMethod,
+ ProgramMethod singleTarget,
+ ConcreteMonomorphicMethodStateOrBottom existingMethodState,
+ DynamicType dynamicReceiverType) {
+ List<ValueState> parameterStates = new ArrayList<>(invoke.arguments().size());
+
+ MethodReprocessingCriteria methodReprocessingCriteria =
+ singleTarget != null
+ ? reprocessingCriteriaCollection.getReprocessingCriteria(singleTarget)
+ : MethodReprocessingCriteria.alwaysReprocess();
+
+ int argumentIndex = 0;
+ if (invoke.isInvokeMethodWithReceiver()) {
+ assert dynamicReceiverType != null;
+ parameterStates.add(
+ computeParameterStateForReceiver(
+ resolvedMethod,
+ dynamicReceiverType,
+ existingMethodState,
+ methodReprocessingCriteria.getParameterReprocessingCriteria(0)));
+ argumentIndex++;
+ }
+
+ for (; argumentIndex < invoke.arguments().size(); argumentIndex++) {
+ parameterStates.add(
+ computeParameterStateForNonReceiver(
+ invoke,
+ singleTarget,
+ argumentIndex,
+ invoke.getArgument(argumentIndex),
+ existingMethodState));
+ }
+
+ // We simulate that the return value is used for methods with void return type. This ensures
+ // that we will widen the method state to unknown if/when all parameter states become unknown.
+ boolean isReturnValueUsed = invoke.getReturnType().isVoidType() || invoke.hasUsedOutValue();
+ return ConcreteMonomorphicMethodState.create(isReturnValueUsed, parameterStates);
}
- // We simulate that the return value is used for methods with void return type. This ensures
- // that we will widen the method state to unknown if/when all parameter states become unknown.
- boolean isReturnValueUsed = invoke.getReturnType().isVoidType() || invoke.hasUsedOutValue();
- return ConcreteMonomorphicMethodState.create(isReturnValueUsed, parameterStates);
- }
+ // For receivers there is not much point in trying to track an abstract value. Therefore we only
+ // track the dynamic type for receivers.
+ // TODO(b/190154391): Consider validating the above hypothesis by using
+ // computeParameterStateForNonReceiver() for receivers.
+ private ValueState computeParameterStateForReceiver(
+ ProgramMethod resolvedMethod,
+ DynamicType dynamicReceiverType,
+ ConcreteMonomorphicMethodStateOrBottom existingMethodState,
+ ParameterReprocessingCriteria parameterReprocessingCriteria) {
+ // Don't compute a state for this parameter if the stored state is already unknown.
+ if (existingMethodState.isMonomorphic()
+ && existingMethodState.asMonomorphic().getParameterState(0).isUnknown()) {
+ return ValueState.unknown();
+ }
- // For receivers there is not much point in trying to track an abstract value. Therefore we only
- // track the dynamic type for receivers.
- // TODO(b/190154391): Consider validating the above hypothesis by using
- // computeParameterStateForNonReceiver() for receivers.
- private ValueState computeParameterStateForReceiver(
- ProgramMethod resolvedMethod,
- DynamicType dynamicReceiverType,
- ConcreteMonomorphicMethodStateOrBottom existingMethodState,
- ParameterReprocessingCriteria parameterReprocessingCriteria) {
- // Don't compute a state for this parameter if the stored state is already unknown.
- if (existingMethodState.isMonomorphic()
- && existingMethodState.asMonomorphic().getParameterState(0).isUnknown()) {
- return ValueState.unknown();
+ // For receivers we only track the dynamic type. Therefore, if there is no need to track the
+ // dynamic type of the receiver of the targeted method, then just return unknown.
+ if (!parameterReprocessingCriteria.shouldReprocessDueToDynamicType()) {
+ return ValueState.unknown();
+ }
+
+ DynamicType widenedDynamicReceiverType =
+ WideningUtils.widenDynamicReceiverType(appView, resolvedMethod, dynamicReceiverType);
+ return widenedDynamicReceiverType.isUnknown()
+ ? ValueState.unknown()
+ : new ConcreteReceiverValueState(dynamicReceiverType);
}
- // For receivers we only track the dynamic type. Therefore, if there is no need to track the
- // dynamic type of the receiver of the targeted method, then just return unknown.
- if (!parameterReprocessingCriteria.shouldReprocessDueToDynamicType()) {
- return ValueState.unknown();
+ @SuppressWarnings("UnusedVariable")
+ private ValueState computeParameterStateForNonReceiver(
+ InvokeMethod invoke,
+ ProgramMethod singleTarget,
+ int argumentIndex,
+ Value argument,
+ ConcreteMonomorphicMethodStateOrBottom existingMethodState) {
+ ValueState modeledState =
+ modeling.modelParameterStateForArgumentToFunction(
+ invoke, singleTarget, argumentIndex, argument, context);
+ if (modeledState != null) {
+ return modeledState;
+ }
+
+ // Don't compute a state for this parameter if the stored state is already unknown.
+ if (existingMethodState.isMonomorphic()
+ && existingMethodState.asMonomorphic().getParameterState(argumentIndex).isUnknown()) {
+ return ValueState.unknown();
+ }
+
+ DexType parameterType =
+ invoke.getInvokedMethod().getArgumentType(argumentIndex, invoke.isInvokeStatic());
+
+ // If the value is an argument of the enclosing method, then clearly we have no information
+ // about its abstract value. Instead of treating this as having an unknown runtime value, we
+ // instead record a flow constraint that specifies that all values that flow into the
+ // parameter of this enclosing method also flows into the corresponding parameter of the
+ // methods potentially called from this invoke instruction.
+ NonEmptyValueState inFlowState = computeInFlowState(parameterType, argument, context);
+ if (inFlowState != null) {
+ return inFlowState;
+ }
+
+ // Only track the nullability for array types.
+ if (parameterType.isArrayType()) {
+ Nullability nullability = argument.getType().nullability();
+ return ConcreteArrayTypeValueState.create(nullability);
+ }
+
+ AbstractValue abstractValue = abstractValueSupplier.getAbstractValue(argument);
+
+ // For class types, we track both the abstract value and the dynamic type. If both are
+ // unknown, then use UnknownParameterState.
+ if (parameterType.isClassType()) {
+ DynamicType dynamicType = argument.getDynamicType(appView);
+ DynamicType widenedDynamicType =
+ WideningUtils.widenDynamicNonReceiverType(appView, dynamicType, parameterType);
+ return ConcreteClassTypeValueState.create(abstractValue, widenedDynamicType);
+ } else {
+ // For primitive types, we only track the abstract value, thus if the abstract value is
+ // unknown, we use UnknownParameterState.
+ assert parameterType.isPrimitiveType();
+ return ConcretePrimitiveTypeValueState.create(abstractValue);
+ }
}
- DynamicType widenedDynamicReceiverType =
- WideningUtils.widenDynamicReceiverType(appView, resolvedMethod, dynamicReceiverType);
- return widenedDynamicReceiverType.isUnknown()
- ? ValueState.unknown()
- : new ConcreteReceiverValueState(dynamicReceiverType);
- }
+ @SuppressWarnings("ReferenceEquality")
+ private DexMethod getRepresentative(InvokeMethod invoke, ProgramMethod resolvedMethod) {
+ if (resolvedMethod.getDefinition().belongsToDirectPool()) {
+ return resolvedMethod.getReference();
+ }
- @SuppressWarnings("UnusedVariable")
- private ValueState computeParameterStateForNonReceiver(
- InvokeMethod invoke,
- ProgramMethod singleTarget,
- int argumentIndex,
- Value argument,
- AbstractValueSupplier abstractValueSupplier,
- ProgramMethod context,
- ConcreteMonomorphicMethodStateOrBottom existingMethodState) {
- ValueState modeledState =
- modeling.modelParameterStateForArgumentToFunction(
- invoke, singleTarget, argumentIndex, argument, context);
- if (modeledState != null) {
- return modeledState;
+ if (isMonomorphicVirtualMethod(resolvedMethod)) {
+ return resolvedMethod.getReference();
+ }
+
+ if (invoke.isInvokeInterface()) {
+ assert !isMonomorphicVirtualMethod(resolvedMethod);
+ return getVirtualRootMethod(resolvedMethod);
+ }
+
+ assert invoke.isInvokeSuper() || invoke.isInvokeVirtual();
+
+ DexMethod rootMethod = getVirtualRootMethod(resolvedMethod);
+ assert rootMethod != null;
+ assert !isMonomorphicVirtualMethod(resolvedMethod)
+ || rootMethod == resolvedMethod.getReference();
+ return rootMethod;
}
- // Don't compute a state for this parameter if the stored state is already unknown.
- if (existingMethodState.isMonomorphic()
- && existingMethodState.asMonomorphic().getParameterState(argumentIndex).isUnknown()) {
- return ValueState.unknown();
+ private boolean shouldUsePolymorphicMethodState(
+ InvokeMethod invoke, ProgramMethod resolvedMethod) {
+ return !resolvedMethod.getDefinition().belongsToDirectPool()
+ && !isMonomorphicVirtualMethod(getRepresentative(invoke, resolvedMethod));
}
- DexType parameterType =
- invoke.getInvokedMethod().getArgumentType(argumentIndex, invoke.isInvokeStatic());
-
- // If the value is an argument of the enclosing method, then clearly we have no information
- // about its abstract value. Instead of treating this as having an unknown runtime value, we
- // instead record a flow constraint that specifies that all values that flow into the parameter
- // of this enclosing method also flows into the corresponding parameter of the methods
- // potentially called from this invoke instruction.
- NonEmptyValueState inFlowState = computeInFlowState(parameterType, argument, context);
- if (inFlowState != null) {
- return inFlowState;
- }
-
- // Only track the nullability for array types.
- if (parameterType.isArrayType()) {
- Nullability nullability = argument.getType().nullability();
- return ConcreteArrayTypeValueState.create(nullability);
- }
-
- AbstractValue abstractValue = abstractValueSupplier.getAbstractValue(argument);
-
- // For class types, we track both the abstract value and the dynamic type. If both are unknown,
- // then use UnknownParameterState.
- if (parameterType.isClassType()) {
- DynamicType dynamicType = argument.getDynamicType(appView);
- DynamicType widenedDynamicType =
- WideningUtils.widenDynamicNonReceiverType(appView, dynamicType, parameterType);
- return ConcreteClassTypeValueState.create(abstractValue, widenedDynamicType);
- } else {
- // For primitive types, we only track the abstract value, thus if the abstract value is
- // unknown,
- // we use UnknownParameterState.
- assert parameterType.isPrimitiveType();
- return ConcretePrimitiveTypeValueState.create(abstractValue);
- }
- }
-
- @SuppressWarnings("ReferenceEquality")
- private DexMethod getRepresentative(InvokeMethod invoke, ProgramMethod resolvedMethod) {
- if (resolvedMethod.getDefinition().belongsToDirectPool()) {
- return resolvedMethod.getReference();
- }
-
- if (isMonomorphicVirtualMethod(resolvedMethod)) {
- return resolvedMethod.getReference();
- }
-
- if (invoke.isInvokeInterface()) {
- assert !isMonomorphicVirtualMethod(resolvedMethod);
- return getVirtualRootMethod(resolvedMethod);
- }
-
- assert invoke.isInvokeSuper() || invoke.isInvokeVirtual();
-
- DexMethod rootMethod = getVirtualRootMethod(resolvedMethod);
- assert rootMethod != null;
- assert !isMonomorphicVirtualMethod(resolvedMethod)
- || rootMethod == resolvedMethod.getReference();
- return rootMethod;
- }
-
- private boolean shouldUsePolymorphicMethodState(
- InvokeMethod invoke, ProgramMethod resolvedMethod) {
- return !resolvedMethod.getDefinition().belongsToDirectPool()
- && !isMonomorphicVirtualMethod(getRepresentative(invoke, resolvedMethod));
- }
-
- private void scan(InvokeCustom invoke) {
- // If the bootstrap method is program declared it will be called. The call is with runtime
- // provided arguments so ensure that the argument information is unknown.
- DexMethodHandle bootstrapMethod = invoke.getCallSite().bootstrapMethod;
- SingleResolutionResult<?> resolution =
- appView
- .appInfo()
- .resolveMethodLegacy(bootstrapMethod.asMethod(), bootstrapMethod.isInterface)
- .asSingleResolution();
- if (resolution != null && resolution.getResolvedHolder().isProgramClass()) {
- methodStates.set(resolution.getResolvedProgramMethod(), UnknownMethodState.get());
+ private void scanInvokeCustom(InvokeCustom invoke) {
+ // If the bootstrap method is program declared it will be called. The call is with runtime
+ // provided arguments so ensure that the argument information is unknown.
+ DexMethodHandle bootstrapMethod = invoke.getCallSite().bootstrapMethod;
+ SingleResolutionResult<?> resolution =
+ appView
+ .appInfo()
+ .resolveMethodLegacy(bootstrapMethod.asMethod(), bootstrapMethod.isInterface)
+ .asSingleResolution();
+ if (resolution != null && resolution.getResolvedHolder().isProgramClass()) {
+ methodStates.set(resolution.getResolvedProgramMethod(), UnknownMethodState.get());
+ }
}
}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/computation/ComputationTreeArgumentNode.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/computation/ComputationTreeArgumentNode.java
new file mode 100644
index 0000000..2ea279f
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/computation/ComputationTreeArgumentNode.java
@@ -0,0 +1,57 @@
+// Copyright (c) 2024, 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.optimize.argumentpropagation.computation;
+
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
+import com.android.tools.r8.ir.analysis.value.AbstractValueFactory;
+import com.android.tools.r8.utils.ArrayUtils;
+import java.util.Objects;
+import java.util.function.IntFunction;
+
+/** Represents the read of an argument. */
+public class ComputationTreeArgumentNode extends ComputationTreeBaseNode {
+
+ private static final int NUM_CANONICALIZED_INSTANCES = 32;
+ private static final ComputationTreeArgumentNode[] CANONICALIZED_INSTANCES =
+ ArrayUtils.initialize(
+ new ComputationTreeArgumentNode[NUM_CANONICALIZED_INSTANCES],
+ ComputationTreeArgumentNode::new);
+
+ private final int argumentIndex;
+
+ private ComputationTreeArgumentNode(int argumentIndex) {
+ this.argumentIndex = argumentIndex;
+ }
+
+ public static ComputationTreeArgumentNode create(int argumentIndex) {
+ return argumentIndex < NUM_CANONICALIZED_INSTANCES
+ ? CANONICALIZED_INSTANCES[argumentIndex]
+ : new ComputationTreeArgumentNode(argumentIndex);
+ }
+
+ @Override
+ public AbstractValue evaluate(
+ IntFunction<AbstractValue> argumentAssignment, AbstractValueFactory abstractValueFactory) {
+ return argumentAssignment.apply(argumentIndex);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof ComputationTreeArgumentNode)) {
+ return false;
+ }
+ ComputationTreeArgumentNode node = (ComputationTreeArgumentNode) obj;
+ assert argumentIndex >= NUM_CANONICALIZED_INSTANCES
+ || node.argumentIndex >= NUM_CANONICALIZED_INSTANCES;
+ return argumentIndex == node.argumentIndex;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(getClass(), argumentIndex);
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/computation/ComputationTreeBaseNode.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/computation/ComputationTreeBaseNode.java
new file mode 100644
index 0000000..2228c3e
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/computation/ComputationTreeBaseNode.java
@@ -0,0 +1,16 @@
+// Copyright (c) 2024, 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.optimize.argumentpropagation.computation;
+
+/**
+ * Represents a computation tree with no open variables other than the arguments of a given method.
+ */
+abstract class ComputationTreeBaseNode implements ComputationTreeNode {
+
+ @Override
+ public abstract boolean equals(Object obj);
+
+ @Override
+ public abstract int hashCode();
+}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/computation/ComputationTreeBuilder.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/computation/ComputationTreeBuilder.java
new file mode 100644
index 0000000..aca8a03
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/computation/ComputationTreeBuilder.java
@@ -0,0 +1,84 @@
+// Copyright (c) 2024, 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.optimize.argumentpropagation.computation;
+
+import static com.android.tools.r8.ir.code.Opcodes.AND;
+import static com.android.tools.r8.ir.code.Opcodes.ARGUMENT;
+import static com.android.tools.r8.ir.code.Opcodes.CONST_NUMBER;
+import static com.android.tools.r8.ir.code.Opcodes.IF;
+
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
+import com.android.tools.r8.ir.analysis.value.AbstractValueFactory;
+import com.android.tools.r8.ir.analysis.value.UnknownValue;
+import com.android.tools.r8.ir.code.And;
+import com.android.tools.r8.ir.code.Argument;
+import com.android.tools.r8.ir.code.ConstNumber;
+import com.android.tools.r8.ir.code.If;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.Value;
+
+public class ComputationTreeBuilder {
+
+ private final AbstractValueFactory abstractValueFactory;
+
+ public ComputationTreeBuilder(AbstractValueFactory abstractValueFactory) {
+ this.abstractValueFactory = abstractValueFactory;
+ }
+
+ // TODO(b/302281503): "Long lived" computation trees (i.e., the ones that survive past the IR
+ // conversion of the current method) should be canonicalized.
+ // TODO(b/302281503): If we start building larger computation trees then make sure to the
+ // computation trees for intermediate instructions to ensure that we do not build the computation
+ // tree for a given instruction more than once.
+ public ComputationTreeNode buildComputationTree(Instruction instruction) {
+ switch (instruction.opcode()) {
+ case AND:
+ {
+ And and = instruction.asAnd();
+ ComputationTreeNode left = buildComputationTreeFromValue(and.leftValue());
+ ComputationTreeNode right = buildComputationTreeFromValue(and.rightValue());
+ return ComputationTreeLogicalBinopAndNode.create(left, right);
+ }
+ case ARGUMENT:
+ {
+ Argument argument = instruction.asArgument();
+ if (argument.getOutType().isInt()) {
+ return ComputationTreeArgumentNode.create(argument.getIndex());
+ }
+ break;
+ }
+ case CONST_NUMBER:
+ {
+ ConstNumber constNumber = instruction.asConstNumber();
+ if (constNumber.getOutType().isInt()) {
+ return constNumber.getAbstractValue(abstractValueFactory);
+ }
+ break;
+ }
+ case IF:
+ {
+ If theIf = instruction.asIf();
+ if (theIf.isZeroTest()) {
+ ComputationTreeNode operand = buildComputationTreeFromValue(theIf.lhs());
+ return ComputationTreeUnopCompareNode.create(operand, theIf.getType());
+ }
+ break;
+ }
+ default:
+ break;
+ }
+ return AbstractValue.unknown();
+ }
+
+ private ComputationTreeNode buildComputationTreeFromValue(Value value) {
+ if (value.isPhi()) {
+ return unknown();
+ }
+ return buildComputationTree(value.getDefinition());
+ }
+
+ private static UnknownValue unknown() {
+ return AbstractValue.unknown();
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/computation/ComputationTreeLogicalBinopAndNode.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/computation/ComputationTreeLogicalBinopAndNode.java
new file mode 100644
index 0000000..f18f081
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/computation/ComputationTreeLogicalBinopAndNode.java
@@ -0,0 +1,50 @@
+// Copyright (c) 2024, 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.optimize.argumentpropagation.computation;
+
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
+import com.android.tools.r8.ir.analysis.value.AbstractValueFactory;
+import com.android.tools.r8.ir.analysis.value.arithmetic.AbstractCalculator;
+import java.util.Objects;
+import java.util.function.IntFunction;
+
+public class ComputationTreeLogicalBinopAndNode extends ComputationTreeLogicalBinopNode {
+
+ private ComputationTreeLogicalBinopAndNode(ComputationTreeNode left, ComputationTreeNode right) {
+ super(left, right);
+ }
+
+ public static ComputationTreeNode create(ComputationTreeNode left, ComputationTreeNode right) {
+ if (left.isUnknown() && right.isUnknown()) {
+ return AbstractValue.unknown();
+ }
+ return new ComputationTreeLogicalBinopAndNode(left, right);
+ }
+
+ @Override
+ public AbstractValue evaluate(
+ IntFunction<AbstractValue> argumentAssignment, AbstractValueFactory abstractValueFactory) {
+ assert getNumericType().isInt();
+ AbstractValue leftValue = left.evaluate(argumentAssignment, abstractValueFactory);
+ AbstractValue rightValue = right.evaluate(argumentAssignment, abstractValueFactory);
+ return AbstractCalculator.andIntegers(abstractValueFactory, leftValue, rightValue);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof ComputationTreeLogicalBinopAndNode)) {
+ return false;
+ }
+ ComputationTreeLogicalBinopAndNode node = (ComputationTreeLogicalBinopAndNode) obj;
+ return internalIsEqualTo(node);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(getClass(), left, right);
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/computation/ComputationTreeLogicalBinopNode.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/computation/ComputationTreeLogicalBinopNode.java
new file mode 100644
index 0000000..8f0c95f
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/computation/ComputationTreeLogicalBinopNode.java
@@ -0,0 +1,26 @@
+// Copyright (c) 2024, 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.optimize.argumentpropagation.computation;
+
+import com.android.tools.r8.ir.code.NumericType;
+
+public abstract class ComputationTreeLogicalBinopNode extends ComputationTreeBaseNode {
+
+ final ComputationTreeNode left;
+ final ComputationTreeNode right;
+
+ ComputationTreeLogicalBinopNode(ComputationTreeNode left, ComputationTreeNode right) {
+ assert !left.isUnknown() || !right.isUnknown();
+ this.left = left;
+ this.right = right;
+ }
+
+ public NumericType getNumericType() {
+ return NumericType.INT;
+ }
+
+ boolean internalIsEqualTo(ComputationTreeLogicalBinopNode node) {
+ return left.equals(node.left) && right.equals(node.right);
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/computation/ComputationTreeNode.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/computation/ComputationTreeNode.java
new file mode 100644
index 0000000..f055cff
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/computation/ComputationTreeNode.java
@@ -0,0 +1,22 @@
+// Copyright (c) 2024, 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.optimize.argumentpropagation.computation;
+
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
+import com.android.tools.r8.ir.analysis.value.AbstractValueFactory;
+import java.util.function.IntFunction;
+
+/**
+ * Represents a computation tree with no open variables other than the arguments of a given method.
+ */
+public interface ComputationTreeNode {
+
+ /** Evaluates the current computation tree on the given argument assignment. */
+ AbstractValue evaluate(
+ IntFunction<AbstractValue> argumentAssignment, AbstractValueFactory abstractValueFactory);
+
+ default boolean isUnknown() {
+ return false;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/computation/ComputationTreeUnopCompareNode.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/computation/ComputationTreeUnopCompareNode.java
new file mode 100644
index 0000000..fb4c465
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/computation/ComputationTreeUnopCompareNode.java
@@ -0,0 +1,51 @@
+// Copyright (c) 2024, 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.optimize.argumentpropagation.computation;
+
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
+import com.android.tools.r8.ir.analysis.value.AbstractValueFactory;
+import com.android.tools.r8.ir.code.IfType;
+import java.util.Objects;
+import java.util.function.IntFunction;
+
+public class ComputationTreeUnopCompareNode extends ComputationTreeUnopNode {
+
+ private final IfType type;
+
+ private ComputationTreeUnopCompareNode(ComputationTreeNode operand, IfType type) {
+ super(operand);
+ this.type = type;
+ }
+
+ public static ComputationTreeNode create(ComputationTreeNode operand, IfType type) {
+ if (operand.isUnknown()) {
+ return AbstractValue.unknown();
+ }
+ return new ComputationTreeUnopCompareNode(operand, type);
+ }
+
+ @Override
+ public AbstractValue evaluate(
+ IntFunction<AbstractValue> argumentAssignment, AbstractValueFactory abstractValueFactory) {
+ AbstractValue operandValue = operand.evaluate(argumentAssignment, abstractValueFactory);
+ return type.evaluate(operandValue, abstractValueFactory);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof ComputationTreeUnopCompareNode)) {
+ return false;
+ }
+ ComputationTreeUnopCompareNode node = (ComputationTreeUnopCompareNode) obj;
+ return type == node.type && internalIsEqualTo(node);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(getClass(), operand, type);
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/computation/ComputationTreeUnopNode.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/computation/ComputationTreeUnopNode.java
new file mode 100644
index 0000000..3c3fc1e
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/computation/ComputationTreeUnopNode.java
@@ -0,0 +1,18 @@
+// Copyright (c) 2024, 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.optimize.argumentpropagation.computation;
+
+public abstract class ComputationTreeUnopNode extends ComputationTreeBaseNode {
+
+ final ComputationTreeNode operand;
+
+ ComputationTreeUnopNode(ComputationTreeNode operand) {
+ assert !operand.isUnknown();
+ this.operand = operand;
+ }
+
+ boolean internalIsEqualTo(ComputationTreeUnopNode node) {
+ return operand.equals(node.operand);
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/DefaultFieldValueJoiner.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/DefaultFieldValueJoiner.java
index 45848ad..28d0ead 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/DefaultFieldValueJoiner.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/DefaultFieldValueJoiner.java
@@ -25,6 +25,7 @@
import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcreteArrayTypeValueState;
import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcreteClassTypeValueState;
import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcretePrimitiveTypeValueState;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcreteReferenceTypeValueState;
import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcreteValueState;
import com.android.tools.r8.optimize.argumentpropagation.codescanner.FieldStateCollection;
import com.android.tools.r8.optimize.argumentpropagation.codescanner.NonEmptyValueState;
@@ -120,9 +121,12 @@
if (state.isUnknown()) {
return;
}
- if (state.isReferenceState()
- && state.asReferenceState().getNullability().isNullable()) {
- return;
+ if (state.isReferenceState()) {
+ ConcreteReferenceTypeValueState referenceState = state.asReferenceState();
+ if (referenceState.getNullability().isNullable()
+ && referenceState.getAbstractValue(appView).isUnknown()) {
+ return;
+ }
}
fieldsOfInterest
.computeIfAbsent(field.getHolder(), ignoreKey(ArrayList::new))
diff --git a/src/main/java/com/android/tools/r8/optimize/bridgehoisting/BridgeHoisting.java b/src/main/java/com/android/tools/r8/optimize/bridgehoisting/BridgeHoisting.java
index a1de155..b91bff4 100644
--- a/src/main/java/com/android/tools/r8/optimize/bridgehoisting/BridgeHoisting.java
+++ b/src/main/java/com/android/tools/r8/optimize/bridgehoisting/BridgeHoisting.java
@@ -26,13 +26,13 @@
import com.google.common.base.Equivalence.Wrapper;
import com.google.common.collect.Iterables;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
-import java.util.HashSet;
+import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
-import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
@@ -102,38 +102,42 @@
eligibleSubclasses.add(subclass);
}
}
- for (Wrapper<DexMethod> candidate : getCandidatesForHoisting(eligibleSubclasses)) {
- hoistBridgeIfPossible(candidate.get(), clazz, eligibleSubclasses);
+ for (ProgramMethod candidate : getCandidatesForHoisting(eligibleSubclasses)) {
+ hoistBridgeIfPossible(candidate, clazz, eligibleSubclasses);
}
}
- private Set<Wrapper<DexMethod>> getCandidatesForHoisting(List<DexProgramClass> subclasses) {
+ private Collection<ProgramMethod> getCandidatesForHoisting(List<DexProgramClass> subclasses) {
Equivalence<DexMethod> equivalence = MethodSignatureEquivalence.get();
- Set<Wrapper<DexMethod>> candidates = new HashSet<>();
+ Map<Wrapper<DexMethod>, ProgramMethod> candidates = new LinkedHashMap<>();
for (DexProgramClass subclass : subclasses) {
- for (DexEncodedMethod method : subclass.virtualMethods()) {
+ for (ProgramMethod method : subclass.virtualProgramMethods()) {
+ if (appView.getKeepInfo(method).isPinned(appView.options())) {
+ continue;
+ }
BridgeInfo bridgeInfo = method.getOptimizationInfo().getBridgeInfo();
if (bridgeInfo != null && bridgeInfo.isVirtualBridgeInfo()) {
- candidates.add(equivalence.wrap(method.getReference()));
+ candidates.put(equivalence.wrap(method.getReference()), method);
}
}
}
- return candidates;
+ return candidates.values();
}
private void hoistBridgeIfPossible(
- DexMethod method, DexProgramClass clazz, List<DexProgramClass> subclasses) {
+ ProgramMethod method, DexProgramClass clazz, List<DexProgramClass> subclasses) {
// If the method is defined on the parent class, we cannot hoist the bridge.
// TODO(b/153147967): If the declared method is abstract, we could replace it by the bridge.
// Add a test.
- if (clazz.lookupProgramMethod(method) != null) {
+ DexMethod methodReference = method.getReference();
+ if (clazz.lookupProgramMethod(methodReference) != null) {
return;
}
// Bail out if the bridge is also declared in the parent class. In that case, hoisting would
// change the behavior of calling the bridge on an instance of the parent class.
MethodResolutionResult res =
- appView.appInfo().resolveMethodOnClass(clazz.getSuperType(), method);
+ appView.appInfo().resolveMethodOnClass(clazz.getSuperType(), methodReference);
if (res.isSingleResolution()) {
if (!res.getResolvedMethod().isAbstract()) {
return;
@@ -147,10 +151,13 @@
// implicitly defined by the signature of the invoke-virtual instruction).
Map<Wrapper<DexMethod>, List<DexProgramClass>> eligibleVirtualInvokeBridges = new HashMap<>();
for (DexProgramClass subclass : subclasses) {
- DexEncodedMethod definition = subclass.lookupVirtualMethod(method);
+ DexEncodedMethod definition = subclass.lookupVirtualMethod(methodReference);
if (definition == null) {
DexEncodedMethod resolutionTarget =
- appView.appInfo().resolveMethodOnClassLegacy(subclass, method).getSingleTarget();
+ appView
+ .appInfo()
+ .resolveMethodOnClassLegacy(subclass, methodReference)
+ .getSingleTarget();
if (resolutionTarget == null || resolutionTarget.isAbstract()) {
// The fact that this class does not declare the bridge (or the bridge is abstract) should
// not prevent us from hoisting the bridge.
@@ -208,7 +215,7 @@
// Choose one of the bridge definitions as the one that we will be moving to the superclass.
List<ProgramMethod> eligibleBridgeMethods =
- getBridgesEligibleForHoisting(eligibleSubclasses, method);
+ getBridgesEligibleForHoisting(eligibleSubclasses, methodReference);
ProgramMethod representative = eligibleBridgeMethods.iterator().next();
// Guard against accessibility issues.
@@ -234,8 +241,7 @@
feedback.setBridgeInfo(representative, new VirtualBridgeInfo(methodToInvoke));
// Move the bridge method to the super class, and record this in the graph lens.
- DexMethod newMethodReference =
- appView.dexItemFactory().createMethod(clazz.type, method.proto, method.name);
+ DexMethod newMethodReference = methodReference.withHolder(clazz, appView.dexItemFactory());
DexEncodedMethod newMethod =
representative
.getDefinition()
@@ -250,9 +256,8 @@
representative.getReference());
// Remove all of the bridges in the eligible subclasses.
- assert !appView.appInfo().isPinnedWithDefinitionLookup(method);
for (DexProgramClass subclass : eligibleSubclasses) {
- DexEncodedMethod removed = subclass.removeMethod(method);
+ DexEncodedMethod removed = subclass.removeMethod(methodReference);
assert removed != null;
}
}
diff --git a/src/main/java/com/android/tools/r8/optimize/compose/ArgumentPropagatorCodeScannerForComposableFunctions.java b/src/main/java/com/android/tools/r8/optimize/compose/ArgumentPropagatorCodeScannerForComposableFunctions.java
index 174e8ba..2116f3a 100644
--- a/src/main/java/com/android/tools/r8/optimize/compose/ArgumentPropagatorCodeScannerForComposableFunctions.java
+++ b/src/main/java/com/android/tools/r8/optimize/compose/ArgumentPropagatorCodeScannerForComposableFunctions.java
@@ -7,6 +7,7 @@
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.ir.code.AbstractValueSupplier;
+import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.InvokeMethod;
import com.android.tools.r8.optimize.argumentpropagation.ArgumentPropagatorCodeScanner;
import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodParameter;
@@ -25,16 +26,12 @@
}
@Override
- protected void addTemporaryMethodState(
- InvokeMethod invoke,
- ProgramMethod resolvedMethod,
+ public void scan(
+ ProgramMethod method,
+ IRCode code,
AbstractValueSupplier abstractValueSupplier,
- ProgramMethod context,
Timing timing) {
- ComposableCallGraphNode node = callGraph.getNodes().get(resolvedMethod);
- if (node != null && node.isComposable()) {
- super.addTemporaryMethodState(invoke, resolvedMethod, abstractValueSupplier, context, timing);
- }
+ new CodeScanner(abstractValueSupplier, code, method).scan(timing);
}
@Override
@@ -43,4 +40,21 @@
// We haven't defined the virtual root mapping, so we can't tell.
return false;
}
+
+ private class CodeScanner extends ArgumentPropagatorCodeScanner.CodeScanner {
+
+ protected CodeScanner(
+ AbstractValueSupplier abstractValueSupplier, IRCode code, ProgramMethod method) {
+ super(abstractValueSupplier, code, method);
+ }
+
+ @Override
+ protected void addTemporaryMethodState(
+ InvokeMethod invoke, ProgramMethod resolvedMethod, Timing timing) {
+ ComposableCallGraphNode node = callGraph.getNodes().get(resolvedMethod);
+ if (node != null && node.isComposable()) {
+ super.addTemporaryMethodState(invoke, resolvedMethod, timing);
+ }
+ }
+ }
}
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
index 1766f40..147ce09 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -3921,7 +3921,7 @@
timing.begin("Retain keep info");
applicableRules = keepInfoCollection.getApplicableRules();
EnqueuerEvent preconditionEvent = UnconditionalKeepInfoEvent.get();
- keepInfo.registerCompilerSynthesizedMethods(keepInfoCollection);
+ keepInfo.registerCompilerSynthesizedItems(keepInfoCollection);
keepInfoCollection.forEachRuleInstance(
appView,
(clazz, minimumKeepInfo) ->
@@ -5594,7 +5594,7 @@
// SomeEnumClass.valueOf(java.lang.String) which is generated by javac for all enums will
// call this method.
if (invoke.inValues().get(0).isConstClass()) {
- DexType type = invoke.inValues().get(0).definition.asConstClass().getValue();
+ DexType type = invoke.inValues().get(0).definition.asConstClass().getType();
DexProgramClass clazz = getProgramClassOrNull(type, method);
if (clazz != null && clazz.isEnum()) {
markEnumValuesAsReachable(clazz, KeepReason.invokedFrom(method));
@@ -5610,7 +5610,7 @@
Value argument = invoke.inValues().get(0).getAliasedValue();
if (!argument.isPhi() && argument.definition.isConstClass()) {
- DexType serviceType = argument.definition.asConstClass().getValue();
+ DexType serviceType = argument.definition.asConstClass().getType();
if (!appView.appServices().allServiceTypes().contains(serviceType)) {
// Should never happen.
return;
diff --git a/src/main/java/com/android/tools/r8/shaking/GlobalKeepInfoConfiguration.java b/src/main/java/com/android/tools/r8/shaking/GlobalKeepInfoConfiguration.java
index 7dfbfeb..994e94c 100644
--- a/src/main/java/com/android/tools/r8/shaking/GlobalKeepInfoConfiguration.java
+++ b/src/main/java/com/android/tools/r8/shaking/GlobalKeepInfoConfiguration.java
@@ -6,6 +6,8 @@
/** Globally controlled settings that affect the default values for kept items. */
public interface GlobalKeepInfoConfiguration {
+ boolean isCodeReplacementForceEnabled();
+
boolean isTreeShakingEnabled();
boolean isMinificationEnabled();
diff --git a/src/main/java/com/android/tools/r8/shaking/KeepClassInfo.java b/src/main/java/com/android/tools/r8/shaking/KeepClassInfo.java
index c1c5ec0..3bb0f20 100644
--- a/src/main/java/com/android/tools/r8/shaking/KeepClassInfo.java
+++ b/src/main/java/com/android/tools/r8/shaking/KeepClassInfo.java
@@ -11,7 +11,7 @@
import java.util.function.Function;
/** Immutable keep requirements for a class. */
-public final class KeepClassInfo extends KeepInfo<KeepClassInfo.Builder, KeepClassInfo> {
+public class KeepClassInfo extends KeepInfo<KeepClassInfo.Builder, KeepClassInfo> {
// Requires all aspects of a class to be kept.
private static final KeepClassInfo TOP =
@@ -53,16 +53,18 @@
private final boolean allowHorizontalClassMerging;
private final boolean allowPermittedSubclassesRemoval;
private final boolean allowRepackaging;
+ private final boolean allowSyntheticSharing;
private final boolean allowUnusedInterfaceRemoval;
private final boolean allowVerticalClassMerging;
private final boolean checkEnumUnboxed;
- private KeepClassInfo(Builder builder) {
+ KeepClassInfo(Builder builder) {
super(builder);
this.allowClassInlining = builder.isClassInliningAllowed();
this.allowHorizontalClassMerging = builder.isHorizontalClassMergingAllowed();
this.allowPermittedSubclassesRemoval = builder.isPermittedSubclassesRemovalAllowed();
this.allowRepackaging = builder.isRepackagingAllowed();
+ this.allowSyntheticSharing = builder.isSyntheticSharingAllowed();
this.allowUnusedInterfaceRemoval = builder.isUnusedInterfaceRemovalAllowed();
this.allowVerticalClassMerging = builder.isVerticalClassMergingAllowed();
this.checkEnumUnboxed = builder.isCheckEnumUnboxedEnabled();
@@ -147,6 +149,14 @@
return allowRepackaging;
}
+ public boolean isSyntheticSharingAllowed() {
+ return internalIsSyntheticSharingAllowed();
+ }
+
+ private boolean internalIsSyntheticSharingAllowed() {
+ return allowSyntheticSharing;
+ }
+
boolean internalIsUnusedInterfaceRemovalAllowed() {
return allowUnusedInterfaceRemoval;
}
@@ -182,20 +192,22 @@
private boolean allowHorizontalClassMerging;
private boolean allowPermittedSubclassesRemoval;
private boolean allowRepackaging;
+ private boolean allowSyntheticSharing;
private boolean allowUnusedInterfaceRemoval;
private boolean allowVerticalClassMerging;
private boolean checkEnumUnboxed;
- private Builder() {
+ Builder() {
super();
}
- private Builder(KeepClassInfo original) {
+ Builder(KeepClassInfo original) {
super(original);
allowClassInlining = original.internalIsClassInliningAllowed();
allowHorizontalClassMerging = original.internalIsHorizontalClassMergingAllowed();
allowPermittedSubclassesRemoval = original.internalIsPermittedSubclassesRemovalAllowed();
allowRepackaging = original.internalIsRepackagingAllowed();
+ allowSyntheticSharing = original.internalIsSyntheticSharingAllowed();
allowUnusedInterfaceRemoval = original.internalIsUnusedInterfaceRemovalAllowed();
allowVerticalClassMerging = original.internalIsVerticalClassMergingAllowed();
checkEnumUnboxed = original.internalIsCheckEnumUnboxedEnabled();
@@ -296,6 +308,17 @@
return setAllowRepackaging(false);
}
+ // Synthetic sharing.
+
+ public boolean isSyntheticSharingAllowed() {
+ return allowSyntheticSharing;
+ }
+
+ private Builder setAllowSyntheticSharing(boolean allowSyntheticSharing) {
+ this.allowSyntheticSharing = allowSyntheticSharing;
+ return self();
+ }
+
// Unused interface removal.
public Builder allowUnusedInterfaceRemoval() {
@@ -363,6 +386,7 @@
&& isPermittedSubclassesRemovalAllowed()
== other.internalIsPermittedSubclassesRemovalAllowed()
&& isRepackagingAllowed() == other.internalIsRepackagingAllowed()
+ && isSyntheticSharingAllowed() == other.internalIsSyntheticSharingAllowed()
&& isUnusedInterfaceRemovalAllowed() == other.internalIsUnusedInterfaceRemovalAllowed()
&& isVerticalClassMergingAllowed() == other.internalIsVerticalClassMergingAllowed();
}
@@ -379,6 +403,8 @@
.disallowHorizontalClassMerging()
.disallowPermittedSubclassesRemoval()
.disallowRepackaging()
+ // Synthetic sharing is always allowed, unless explicitly set to false.
+ .setAllowSyntheticSharing(true)
.disallowUnusedInterfaceRemoval()
.disallowVerticalClassMerging()
.unsetCheckEnumUnboxed();
@@ -391,6 +417,7 @@
.allowHorizontalClassMerging()
.allowPermittedSubclassesRemoval()
.allowRepackaging()
+ .setAllowSyntheticSharing(true)
.allowUnusedInterfaceRemoval()
.allowVerticalClassMerging()
.unsetCheckEnumUnboxed();
@@ -403,6 +430,10 @@
super(info.builder());
}
+ protected Joiner(KeepClassInfo.Builder builder) {
+ super(builder);
+ }
+
public Joiner disallowClassInlining() {
builder.disallowClassInlining();
return self();
@@ -423,6 +454,11 @@
return self();
}
+ public Joiner disallowSyntheticSharing() {
+ builder.setAllowSyntheticSharing(false);
+ return self();
+ }
+
public Joiner disallowUnusedInterfaceRemoval() {
builder.disallowUnusedInterfaceRemoval();
return self();
@@ -460,6 +496,7 @@
!joiner.builder.isPermittedSubclassesRemovalAllowed(),
Joiner::disallowPermittedSubclassesRemoval)
.applyIf(!joiner.builder.isRepackagingAllowed(), Joiner::disallowRepackaging)
+ .applyIf(!joiner.builder.isSyntheticSharingAllowed(), Joiner::disallowSyntheticSharing)
.applyIf(
!joiner.builder.isUnusedInterfaceRemovalAllowed(),
Joiner::disallowUnusedInterfaceRemoval)
diff --git a/src/main/java/com/android/tools/r8/shaking/KeepInfoCollection.java b/src/main/java/com/android/tools/r8/shaking/KeepInfoCollection.java
index 990c260..69f6f02 100644
--- a/src/main/java/com/android/tools/r8/shaking/KeepInfoCollection.java
+++ b/src/main/java/com/android/tools/r8/shaking/KeepInfoCollection.java
@@ -531,15 +531,26 @@
}
}
+ public void ensureCompilerSynthesizedClass(DexProgramClass clazz) {
+ keepClassInfo.computeIfAbsent(clazz.getType(), ignoreKey(SyntheticKeepClassInfo::bottom));
+ }
+
@Override
public void registerCompilerSynthesizedMethod(ProgramMethod method) {
assert !keepMethodInfo.containsKey(method.getReference());
keepMethodInfo.put(method.getReference(), SyntheticKeepMethodInfo.bottom());
}
- public void registerCompilerSynthesizedMethods(KeepInfoCollection keepInfoCollection) {
+ public void registerCompilerSynthesizedItems(KeepInfoCollection keepInfoCollection) {
keepInfoCollection.mutate(
mutableKeepInfoCollection -> {
+ mutableKeepInfoCollection.keepClassInfo.forEach(
+ (c, info) -> {
+ if (info instanceof SyntheticKeepClassInfo) {
+ assert !keepClassInfo.containsKey(c);
+ keepClassInfo.put(c, info);
+ }
+ });
mutableKeepInfoCollection.keepMethodInfo.forEach(
(m, info) -> {
if (info instanceof SyntheticKeepMethodInfo) {
diff --git a/src/main/java/com/android/tools/r8/shaking/KeepMemberInfo.java b/src/main/java/com/android/tools/r8/shaking/KeepMemberInfo.java
index 7394058..ab20efa 100644
--- a/src/main/java/com/android/tools/r8/shaking/KeepMemberInfo.java
+++ b/src/main/java/com/android/tools/r8/shaking/KeepMemberInfo.java
@@ -8,6 +8,7 @@
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.ProgramMember;
import com.android.tools.r8.shaking.KeepMemberInfo.Builder;
+import com.android.tools.r8.utils.InternalOptions;
/** Immutable keep requirements for a member. */
@SuppressWarnings("BadImport")
@@ -21,7 +22,6 @@
this.allowValuePropagation = builder.isValuePropagationAllowed();
}
- @SuppressWarnings("BadImport")
public boolean isKotlinMetadataRemovalAllowed(
DexProgramClass holder, GlobalKeepInfoConfiguration configuration) {
// Checking the holder for missing kotlin information relies on the holder being processed
@@ -31,18 +31,17 @@
public boolean isValuePropagationAllowed(
AppView<AppInfoWithLiveness> appView, ProgramMember<?, ?> member) {
+ InternalOptions options = appView.options();
+ if (!internalIsValuePropagationAllowed()) {
+ return false;
+ }
+ if (member.isMethod() && !asMethodInfo().isCodeReplacementAllowed(options)) {
+ return true;
+ }
DexType type =
member.isField() ? member.asField().getType() : member.asMethod().getReturnType();
boolean isTypeInstantiated = !type.isAlwaysNull(appView);
- return isValuePropagationAllowed(appView.options(), isTypeInstantiated);
- }
-
- public boolean isValuePropagationAllowed(
- GlobalKeepInfoConfiguration configuration, boolean isTypeInstantiated) {
- if (!isOptimizationAllowed(configuration) && isTypeInstantiated) {
- return false;
- }
- return internalIsValuePropagationAllowed();
+ return isOptimizationAllowed(options) || !isTypeInstantiated;
}
boolean internalIsValuePropagationAllowed() {
diff --git a/src/main/java/com/android/tools/r8/shaking/KeepMethodInfo.java b/src/main/java/com/android/tools/r8/shaking/KeepMethodInfo.java
index 571d3e8..a40c7e6 100644
--- a/src/main/java/com/android/tools/r8/shaking/KeepMethodInfo.java
+++ b/src/main/java/com/android/tools/r8/shaking/KeepMethodInfo.java
@@ -31,6 +31,7 @@
private final boolean allowThrowsRemoval;
private final boolean allowClassInlining;
private final boolean allowClosedWorldReasoning;
+ private final boolean allowCodeReplacement;
private final boolean allowConstantArgumentOptimization;
private final boolean allowInlining;
private final boolean allowMethodStaticizing;
@@ -50,6 +51,7 @@
this.allowThrowsRemoval = builder.isThrowsRemovalAllowed();
this.allowClassInlining = builder.isClassInliningAllowed();
this.allowClosedWorldReasoning = builder.isClosedWorldReasoningAllowed();
+ this.allowCodeReplacement = builder.isCodeReplacementAllowed();
this.allowConstantArgumentOptimization = builder.isConstantArgumentOptimizationAllowed();
this.allowInlining = builder.isInliningAllowed();
this.allowMethodStaticizing = builder.isMethodStaticizingAllowed();
@@ -129,6 +131,16 @@
return allowClosedWorldReasoning;
}
+ public boolean isCodeReplacementAllowed(GlobalKeepInfoConfiguration configuration) {
+ return configuration.isCodeReplacementForceEnabled()
+ ? !isOptimizationAllowed(configuration)
+ : internalIsCodeReplacementAllowed();
+ }
+
+ boolean internalIsCodeReplacementAllowed() {
+ return allowCodeReplacement;
+ }
+
public boolean isConstantArgumentOptimizationAllowed(GlobalKeepInfoConfiguration configuration) {
return isOptimizationAllowed(configuration) && internalIsConstantArgumentOptimizationAllowed();
}
@@ -273,6 +285,7 @@
private boolean allowThrowsRemoval;
private boolean allowClassInlining;
private boolean allowClosedWorldReasoning;
+ private boolean allowCodeReplacement;
private boolean allowConstantArgumentOptimization;
private boolean allowInlining;
private boolean allowMethodStaticizing;
@@ -296,6 +309,7 @@
allowThrowsRemoval = original.internalIsThrowsRemovalAllowed();
allowClassInlining = original.internalIsClassInliningAllowed();
allowClosedWorldReasoning = original.internalIsClosedWorldReasoningAllowed();
+ allowCodeReplacement = original.internalIsCodeReplacementAllowed();
allowConstantArgumentOptimization = original.internalIsConstantArgumentOptimizationAllowed();
allowInlining = original.internalIsInliningAllowed();
allowMethodStaticizing = original.internalIsMethodStaticizingAllowed();
@@ -339,6 +353,15 @@
return self();
}
+ public boolean isCodeReplacementAllowed() {
+ return allowCodeReplacement;
+ }
+
+ public Builder setAllowCodeReplacement(boolean allowCodeReplacement) {
+ this.allowCodeReplacement = allowCodeReplacement;
+ return self();
+ }
+
public boolean isConstantArgumentOptimizationAllowed() {
return allowConstantArgumentOptimization;
}
@@ -483,6 +506,7 @@
&& isThrowsRemovalAllowed() == other.internalIsThrowsRemovalAllowed()
&& isClassInliningAllowed() == other.internalIsClassInliningAllowed()
&& isClosedWorldReasoningAllowed() == other.internalIsClosedWorldReasoningAllowed()
+ && isCodeReplacementAllowed() == other.internalIsCodeReplacementAllowed()
&& isConstantArgumentOptimizationAllowed()
== other.internalIsConstantArgumentOptimizationAllowed()
&& isInliningAllowed() == other.internalIsInliningAllowed()
@@ -513,6 +537,7 @@
.setAllowThrowsRemoval(false)
.setAllowClassInlining(false)
.setAllowClosedWorldReasoning(false)
+ .setAllowCodeReplacement(true)
.setAllowConstantArgumentOptimization(false)
.setAllowInlining(false)
.setAllowMethodStaticizing(false)
@@ -534,6 +559,7 @@
.setAllowThrowsRemoval(true)
.setAllowClassInlining(true)
.setAllowClosedWorldReasoning(true)
+ .setAllowCodeReplacement(false)
.setAllowConstantArgumentOptimization(true)
.setAllowInlining(true)
.setAllowMethodStaticizing(true)
@@ -575,6 +601,11 @@
return self();
}
+ public Joiner allowCodeReplacement() {
+ builder.setAllowCodeReplacement(true);
+ return self();
+ }
+
public Joiner disallowConstantArgumentOptimization() {
builder.setAllowConstantArgumentOptimization(false);
return self();
@@ -662,6 +693,7 @@
.applyIf(!joiner.builder.isClassInliningAllowed(), Joiner::disallowClassInlining)
.applyIf(
!joiner.builder.isClosedWorldReasoningAllowed(), Joiner::disallowClosedWorldReasoning)
+ .applyIf(joiner.builder.isCodeReplacementAllowed(), Joiner::allowCodeReplacement)
.applyIf(
!joiner.builder.isConstantArgumentOptimizationAllowed(),
Joiner::disallowConstantArgumentOptimization)
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
index f511bee..5b11df3 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
@@ -1074,6 +1074,8 @@
} else if (options.isTestingOptionsEnabled()) {
if (acceptString("annotationremoval")) {
builder.getModifiersBuilder().setAllowsAnnotationRemoval(true);
+ } else if (acceptString("codereplacement")) {
+ builder.getModifiersBuilder().setAllowsCodeReplacement(true);
}
}
} else if (acceptString("includedescriptorclasses")) {
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardKeepRuleBase.java b/src/main/java/com/android/tools/r8/shaking/ProguardKeepRuleBase.java
index 308ab6e..fb02899 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardKeepRuleBase.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardKeepRuleBase.java
@@ -22,10 +22,6 @@
super();
}
- public ProguardKeepRuleType getKeepRuleType() {
- return type;
- }
-
public B setType(ProguardKeepRuleType type) {
this.type = type;
return self();
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardKeepRuleModifiers.java b/src/main/java/com/android/tools/r8/shaking/ProguardKeepRuleModifiers.java
index 6073953..c904cc0 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardKeepRuleModifiers.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardKeepRuleModifiers.java
@@ -10,6 +10,8 @@
private boolean allowsAccessModification = false;
private boolean allowsAnnotationRemoval = false;
+ // The default value is determined by InternalOptions.
+ private boolean allowsCodeReplacement = false;
private boolean allowsRepackaging = false;
private boolean allowsShrinking = false;
private boolean allowsOptimization = false;
@@ -22,6 +24,7 @@
public Builder setAllowsAll() {
setAllowsAccessModification(true);
setAllowsAnnotationRemoval(true);
+ setAllowsCodeReplacement(false);
setAllowsObfuscation(true);
setAllowsOptimization(true);
setAllowsRepackaging(true);
@@ -39,6 +42,11 @@
return this;
}
+ public Builder setAllowsCodeReplacement(boolean allowsCodeReplacement) {
+ this.allowsCodeReplacement = allowsCodeReplacement;
+ return this;
+ }
+
public Builder setAllowsShrinking(boolean allowsShrinking) {
this.allowsShrinking = allowsShrinking;
return this;
@@ -75,6 +83,7 @@
return new ProguardKeepRuleModifiers(
allowsAccessModification,
allowsAnnotationRemoval,
+ allowsCodeReplacement,
allowsRepackaging,
allowsShrinking,
allowsOptimization,
@@ -86,6 +95,7 @@
public final boolean allowsAccessModification;
public final boolean allowsAnnotationRemoval;
+ public final boolean allowsCodeReplacement;
public final boolean allowsRepackaging;
public final boolean allowsShrinking;
public final boolean allowsOptimization;
@@ -96,6 +106,7 @@
private ProguardKeepRuleModifiers(
boolean allowsAccessModification,
boolean allowsAnnotationRemoval,
+ boolean allowsCodeReplacement,
boolean allowsRepackaging,
boolean allowsShrinking,
boolean allowsOptimization,
@@ -104,6 +115,7 @@
boolean allowsPermittedSubclassesRemoval) {
this.allowsAccessModification = allowsAccessModification;
this.allowsAnnotationRemoval = allowsAnnotationRemoval;
+ this.allowsCodeReplacement = allowsCodeReplacement;
this.allowsRepackaging = allowsRepackaging;
this.allowsShrinking = allowsShrinking;
this.allowsOptimization = allowsOptimization;
@@ -122,6 +134,7 @@
public boolean isBottom() {
return allowsAccessModification
&& allowsAnnotationRemoval
+ && !allowsCodeReplacement
&& allowsRepackaging
&& allowsObfuscation
&& allowsOptimization
@@ -138,6 +151,7 @@
ProguardKeepRuleModifiers that = (ProguardKeepRuleModifiers) o;
return allowsAccessModification == that.allowsAccessModification
&& allowsAnnotationRemoval == that.allowsAnnotationRemoval
+ && allowsCodeReplacement == that.allowsCodeReplacement
&& allowsRepackaging == that.allowsRepackaging
&& allowsShrinking == that.allowsShrinking
&& allowsOptimization == that.allowsOptimization
@@ -151,6 +165,7 @@
return Objects.hash(
allowsAccessModification,
allowsAnnotationRemoval,
+ allowsCodeReplacement,
allowsRepackaging,
allowsShrinking,
allowsOptimization,
@@ -164,6 +179,7 @@
StringBuilder builder = new StringBuilder();
appendWithComma(builder, allowsAccessModification, "allowaccessmodification");
appendWithComma(builder, allowsAnnotationRemoval, "allowannotationremoval");
+ appendWithComma(builder, allowsCodeReplacement, "allowcodereplacement");
appendWithComma(builder, allowsRepackaging, "allowrepackaging");
appendWithComma(builder, allowsObfuscation, "allowobfuscation");
appendWithComma(builder, allowsShrinking, "allowshrinking");
diff --git a/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java b/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java
index e0170ef..4836910 100644
--- a/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java
@@ -1713,6 +1713,13 @@
context.markAsUsed();
}
+ if (item.isProgramMethod()
+ && !appView.options().isCodeReplacementForceEnabled()
+ && modifiers.allowsCodeReplacement) {
+ itemJoiner.computeIfAbsent().asMethodJoiner().allowCodeReplacement();
+ context.markAsUsed();
+ }
+
if (item.isProgramClass()
&& appView.options().isKeepPermittedSubclassesEnabled()
&& !modifiers.allowsPermittedSubclassesRemoval) {
diff --git a/src/main/java/com/android/tools/r8/shaking/SyntheticKeepClassInfo.java b/src/main/java/com/android/tools/r8/shaking/SyntheticKeepClassInfo.java
new file mode 100644
index 0000000..7a64979
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/shaking/SyntheticKeepClassInfo.java
@@ -0,0 +1,130 @@
+// Copyright (c) 2024, 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.shaking;
+
+public class SyntheticKeepClassInfo extends KeepClassInfo {
+
+ // Requires no aspects of a method to be kept.
+ private static final SyntheticKeepClassInfo BOTTOM = new Builder().makeBottom().build();
+
+ public static SyntheticKeepClassInfo bottom() {
+ return BOTTOM;
+ }
+
+ public static Joiner newEmptyJoiner() {
+ return bottom().joiner();
+ }
+
+ @Override
+ Builder builder() {
+ return new Builder(this);
+ }
+
+ public static class Builder extends KeepClassInfo.Builder {
+
+ public Builder() {
+ super();
+ }
+
+ private Builder(SyntheticKeepClassInfo original) {
+ super(original);
+ }
+
+ @Override
+ public boolean isMinificationAllowed() {
+ // Synthetic items can always be minified.
+ return true;
+ }
+
+ @Override
+ public boolean isOptimizationAllowed() {
+ // Synthetic items can always be optimized.
+ return true;
+ }
+
+ @Override
+ public boolean isShrinkingAllowed() {
+ // Synthetic items can always be removed.
+ return true;
+ }
+
+ @Override
+ public SyntheticKeepClassInfo doBuild() {
+ return new SyntheticKeepClassInfo(this);
+ }
+
+ @Override
+ public SyntheticKeepClassInfo build() {
+ return (SyntheticKeepClassInfo) super.build();
+ }
+
+ @Override
+ public Builder makeBottom() {
+ super.makeBottom();
+ return self();
+ }
+
+ @Override
+ public Builder self() {
+ return this;
+ }
+ }
+
+ public static class Joiner extends KeepClassInfo.Joiner {
+
+ public Joiner(SyntheticKeepClassInfo info) {
+ super(info.builder());
+ }
+
+ @Override
+ public Joiner disallowMinification() {
+ // Ignore as synthetic items can always be minified.
+ return self();
+ }
+
+ @Override
+ public Joiner disallowOptimization() {
+ // Ignore as synthetic items can always be optimized.
+ return self();
+ }
+
+ @Override
+ public Joiner disallowShrinking() {
+ // Ignore as synthetic items can always be removed.
+ return self();
+ }
+
+ @Override
+ Joiner self() {
+ return this;
+ }
+ }
+
+ public SyntheticKeepClassInfo(Builder builder) {
+ super(builder);
+ }
+
+ @Override
+ public boolean isMinificationAllowed(GlobalKeepInfoConfiguration configuration) {
+ // Synthetic items can always be minified.
+ return true;
+ }
+
+ @Override
+ public boolean isOptimizationAllowed(GlobalKeepInfoConfiguration configuration) {
+ // Synthetic items can always be minified.
+ return true;
+ }
+
+ @Override
+ public boolean isShrinkingAllowed(GlobalKeepInfoConfiguration configuration) {
+ // Synthetic items can always be minified.
+ return true;
+ }
+
+ @Override
+ public Joiner joiner() {
+ return new Joiner(this);
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
index 4644989..ece8caa 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
@@ -26,8 +26,10 @@
import com.android.tools.r8.graph.lens.NonIdentityGraphLens;
import com.android.tools.r8.ir.code.NumberGenerator;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.KeepClassInfo;
import com.android.tools.r8.shaking.KeepInfoCollection;
import com.android.tools.r8.shaking.MainDexInfo;
+import com.android.tools.r8.shaking.SyntheticKeepClassInfo;
import com.android.tools.r8.synthesis.SyntheticItems.State;
import com.android.tools.r8.synthesis.SyntheticNaming.Phase;
import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
@@ -298,7 +300,9 @@
mainDexInfoBuilder.build());
}
- private <R extends SyntheticReference<R, D, ?>, D extends SyntheticDefinition<R, D, ?>>
+ private <
+ R extends SyntheticReference<R, D, DexProgramClass>,
+ D extends SyntheticDefinition<R, D, DexProgramClass>>
Map<DexType, EquivalenceGroup<D>> computeEquivalences(
AppView<?> appView,
ImmutableMap<DexType, List<R>> references,
@@ -314,6 +318,7 @@
timing.begin("Potential equivalences");
Collection<List<D>> potentialEquivalences =
computePotentialEquivalences(
+ appView,
definitions,
intermediate,
appView.options(),
@@ -836,8 +841,9 @@
}
@SuppressWarnings("MixedMutabilityReturnType")
- private static <T extends SyntheticDefinition<?, T, ?>>
+ private static <T extends SyntheticDefinition<?, T, DexProgramClass>>
Collection<List<T>> computePotentialEquivalences(
+ AppView<?> appView,
Map<DexType, T> definitions,
boolean intermediate,
InternalOptions options,
@@ -870,12 +876,24 @@
}
RepresentativeMap map = t -> syntheticTypes.contains(t) ? options.dexItemFactory().voidType : t;
Map<HashCode, List<T>> equivalences = new HashMap<>(definitions.size());
+ List<List<T>> result = new ArrayList<>();
for (T definition : definitions.values()) {
- HashCode hash =
- definition.computeHash(map, intermediate, classToFeatureSplitMap, syntheticItems);
- equivalences.computeIfAbsent(hash, k -> new ArrayList<>()).add(definition);
+ DexProgramClass holder = definition.getHolder();
+ KeepClassInfo keepInfo =
+ appView.getKeepInfoOrDefault(holder, SyntheticKeepClassInfo.bottom());
+ if (keepInfo.isSyntheticSharingAllowed()) {
+ HashCode hash =
+ definition.computeHash(map, intermediate, classToFeatureSplitMap, syntheticItems);
+ equivalences.computeIfAbsent(hash, k -> new ArrayList<>()).add(definition);
+ } else {
+ result.add(ImmutableList.of(definition));
+ }
}
- return equivalences.values();
+ if (result.isEmpty()) {
+ return equivalences.values();
+ }
+ result.addAll(equivalences.values());
+ return result;
}
private <R extends SyntheticReference<R, D, ?>, D extends SyntheticDefinition<R, D, ?>>
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 615f440..8ac9315 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -797,6 +797,11 @@
}
@Override
+ public boolean isCodeReplacementForceEnabled() {
+ return getTestingOptions().allowCodeReplacement;
+ }
+
+ @Override
public boolean isTreeShakingEnabled() {
return isShrinking();
}
@@ -2401,6 +2406,7 @@
public boolean addCallEdgesForLibraryInvokes = false;
public boolean allowClassInliningOfSynthetics = true;
+ public boolean allowCodeReplacement = true;
public boolean allowInjectedAnnotationMethods = false;
public boolean allowInliningOfOutlines = true;
public boolean allowInliningOfSynthetics = true;
diff --git a/src/main/java/com/android/tools/r8/verticalclassmerging/VerticalClassMerger.java b/src/main/java/com/android/tools/r8/verticalclassmerging/VerticalClassMerger.java
index 6772ced..acb8b0d 100644
--- a/src/main/java/com/android/tools/r8/verticalclassmerging/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/verticalclassmerging/VerticalClassMerger.java
@@ -13,6 +13,7 @@
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.DirectMappedDexApplication;
import com.android.tools.r8.graph.ImmediateProgramSubtypingInfo;
import com.android.tools.r8.graph.ProgramMethod;
@@ -24,6 +25,7 @@
import com.android.tools.r8.profile.art.ArtProfileCompletenessChecker;
import com.android.tools.r8.profile.rewriting.ProfileCollectionAdditions;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.KeepClassInfo.Joiner;
import com.android.tools.r8.shaking.KeepInfoCollection;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.ThreadUtils;
@@ -286,6 +288,13 @@
PrunedItems.builder()
.setRemovedClasses(verticallyMergedClasses.getSources())
.build());
+ for (DexType target : verticallyMergedClasses.getTargets()) {
+ DexProgramClass targetClass = appView.definitionFor(target).asProgramClass();
+ if (appView.getSyntheticItems().isSynthetic(targetClass)) {
+ mutator.ensureCompilerSynthesizedClass(targetClass);
+ mutator.joinClass(targetClass, Joiner::disallowSyntheticSharing);
+ }
+ }
});
timing.end();
}
diff --git a/src/test/examples/identifiernamestring/keep-rules-1.txt b/src/test/examples/identifiernamestring/keep-rules-1.txt
index 682999f..2e0c2ca 100644
--- a/src/test/examples/identifiernamestring/keep-rules-1.txt
+++ b/src/test/examples/identifiernamestring/keep-rules-1.txt
@@ -9,4 +9,5 @@
-keepnames class identifiernamestring.A
+-dontoptimize
-dontshrink
diff --git a/src/test/examples/memberrebinding3/keep-rules.txt b/src/test/examples/memberrebinding3/keep-rules.txt
new file mode 100644
index 0000000..51b9f3c
--- /dev/null
+++ b/src/test/examples/memberrebinding3/keep-rules.txt
@@ -0,0 +1 @@
+-dontoptimize
diff --git a/src/test/examplesJava11/nesthostexample/FullNestOnProgramPathTest.java b/src/test/examplesJava11/nesthostexample/FullNestOnProgramPathTest.java
index c36a73b..5bc8808 100644
--- a/src/test/examplesJava11/nesthostexample/FullNestOnProgramPathTest.java
+++ b/src/test/examplesJava11/nesthostexample/FullNestOnProgramPathTest.java
@@ -106,7 +106,7 @@
parameters.assumeR8TestParameters();
R8TestCompileResult compileResult =
testForR8(parameters.getBackend())
- .noTreeShaking()
+ .addDontShrink()
.addDontObfuscate()
.addKeepAllAttributes()
.addOptionsModification(options -> options.enableNestReduction = false)
@@ -134,7 +134,7 @@
parameters.assumeR8TestParameters();
for (Class<?> clazz : NEST_MAIN_RESULT.keySet()) {
testForR8(parameters.getBackend())
- .noTreeShaking()
+ .addDontShrink()
.addDontObfuscate()
.addKeepAllAttributes()
.setMinApi(parameters)
diff --git a/src/test/examplesJava11/nesthostexample/NestAttributesUpdateTest.java b/src/test/examplesJava11/nesthostexample/NestAttributesUpdateTest.java
index 52960eb..ecac6fb 100644
--- a/src/test/examplesJava11/nesthostexample/NestAttributesUpdateTest.java
+++ b/src/test/examplesJava11/nesthostexample/NestAttributesUpdateTest.java
@@ -125,7 +125,7 @@
throws Exception {
testForR8(parameters.getBackend())
.addKeepMainRule(mainClass)
- .minification(minification)
+ .addDontObfuscateUnless(minification)
.addOptionsModification(
options -> {
// Disable optimizations else additional classes are removed since they become unused.
diff --git a/src/test/examplesJava11/nesthostexample/NestConstructorRemovedArgTest.java b/src/test/examplesJava11/nesthostexample/NestConstructorRemovedArgTest.java
index 70c0120..216673a 100644
--- a/src/test/examplesJava11/nesthostexample/NestConstructorRemovedArgTest.java
+++ b/src/test/examplesJava11/nesthostexample/NestConstructorRemovedArgTest.java
@@ -56,7 +56,7 @@
public void testRemoveArgConstructorNestsR8NoTreeShaking() throws Exception {
parameters.assumeR8TestParameters();
testForR8(parameters.getBackend())
- .noTreeShaking()
+ .addDontShrink()
.addKeepMainRule(MAIN_CLASS)
.addDontObfuscate()
.setMinApi(parameters)
diff --git a/src/test/java/com/android/tools/r8/bridgeremoval/RemoveVisibilityBridgeMethodsTest.java b/src/test/java/com/android/tools/r8/bridgeremoval/RemoveVisibilityBridgeMethodsTest.java
index 2c372de..8d9654e 100644
--- a/src/test/java/com/android/tools/r8/bridgeremoval/RemoveVisibilityBridgeMethodsTest.java
+++ b/src/test/java/com/android/tools/r8/bridgeremoval/RemoveVisibilityBridgeMethodsTest.java
@@ -43,7 +43,7 @@
.addInnerClasses(RemoveVisibilityBridgeMethodsTest.class)
.addKeepMainRule(Main.class)
.allowAccessModification()
- .minification(minification)
+ .addDontObfuscateUnless(minification)
.setMinApi(parameters)
.compile()
.inspect(this::inspect)
@@ -106,7 +106,7 @@
.addKeepMainRule(mainClass.name)
.addOptionsModification(options -> options.inlinerOptions().enableInlining = false)
.allowAccessModification()
- .minification(minification)
+ .addDontObfuscateUnless(minification)
.setMinApi(parameters)
.compile()
.inspect(
diff --git a/src/test/java/com/android/tools/r8/bridgeremoval/bridgestokeep/KeepNonVisibilityBridgeMethodsTest.java b/src/test/java/com/android/tools/r8/bridgeremoval/bridgestokeep/KeepNonVisibilityBridgeMethodsTest.java
index 52fd6e9..33dea90 100644
--- a/src/test/java/com/android/tools/r8/bridgeremoval/bridgestokeep/KeepNonVisibilityBridgeMethodsTest.java
+++ b/src/test/java/com/android/tools/r8/bridgeremoval/bridgestokeep/KeepNonVisibilityBridgeMethodsTest.java
@@ -60,7 +60,7 @@
.enableNeverClassInliningAnnotations()
.enableNoVerticalClassMergingAnnotations()
.enableProguardTestOptions()
- .minification(minification)
+ .addDontObfuscateUnless(minification)
.setMinApi(parameters)
.compile()
.inspect(
diff --git a/src/test/java/com/android/tools/r8/cf/AlwaysNullGetItemTestRunner.java b/src/test/java/com/android/tools/r8/cf/AlwaysNullGetItemTestRunner.java
index a8f2ac2..9a2e6e4 100644
--- a/src/test/java/com/android/tools/r8/cf/AlwaysNullGetItemTestRunner.java
+++ b/src/test/java/com/android/tools/r8/cf/AlwaysNullGetItemTestRunner.java
@@ -39,7 +39,7 @@
.addProgramClassesAndInnerClasses(CLASS)
.debug()
.addDontObfuscate()
- .noTreeShaking()
+ .addDontShrink()
.setMinApi(parameters)
.compile()
.run(parameters.getRuntime(), CLASS)
diff --git a/src/test/java/com/android/tools/r8/cf/CloserTestRunner.java b/src/test/java/com/android/tools/r8/cf/CloserTestRunner.java
index dea6642..8fa6bb6 100644
--- a/src/test/java/com/android/tools/r8/cf/CloserTestRunner.java
+++ b/src/test/java/com/android/tools/r8/cf/CloserTestRunner.java
@@ -45,7 +45,7 @@
.addKeepMainRule(CloserTest.class)
.setMode(CompilationMode.RELEASE)
.addDontObfuscate()
- .noTreeShaking()
+ .addDontShrink()
.enableInliningAnnotations()
.compile()
.run(CloserTest.class)
diff --git a/src/test/java/com/android/tools/r8/cf/DebugInfoTestRunner.java b/src/test/java/com/android/tools/r8/cf/DebugInfoTestRunner.java
index 6ab2aef..d0023d0 100644
--- a/src/test/java/com/android/tools/r8/cf/DebugInfoTestRunner.java
+++ b/src/test/java/com/android/tools/r8/cf/DebugInfoTestRunner.java
@@ -60,7 +60,7 @@
private R8FullTestBuilder builder() {
return testForR8(parameters.getBackend())
.debug()
- .noTreeShaking()
+ .addDontShrink()
.addDontObfuscate()
.addOptionsModification(o -> o.invalidDebugInfoFatal = true)
.setMinApi(parameters);
diff --git a/src/test/java/com/android/tools/r8/cf/KeepDeserializeLambdaMethodTestRunner.java b/src/test/java/com/android/tools/r8/cf/KeepDeserializeLambdaMethodTestRunner.java
index e002b80..6716f80 100644
--- a/src/test/java/com/android/tools/r8/cf/KeepDeserializeLambdaMethodTestRunner.java
+++ b/src/test/java/com/android/tools/r8/cf/KeepDeserializeLambdaMethodTestRunner.java
@@ -91,7 +91,7 @@
// TODO(b/148836254): Support deserialized lambdas without the need of additional rules.
"-keep class * { private static synthetic void lambda$*(); }");
} else {
- builder.addDontObfuscate().noTreeShaking();
+ builder.addDontObfuscate().addDontShrink();
}
builder
.run(parameters.getRuntime(), getMainClass())
diff --git a/src/test/java/com/android/tools/r8/cf/PrintSeedsWithDeserializeLambdaMethodTest.java b/src/test/java/com/android/tools/r8/cf/PrintSeedsWithDeserializeLambdaMethodTest.java
index 5d9dd66..89f13b9 100644
--- a/src/test/java/com/android/tools/r8/cf/PrintSeedsWithDeserializeLambdaMethodTest.java
+++ b/src/test/java/com/android/tools/r8/cf/PrintSeedsWithDeserializeLambdaMethodTest.java
@@ -59,7 +59,7 @@
.addPrintSeeds()
.allowStdoutMessages()
.addDontObfuscate()
- .noTreeShaking()
+ .addDontShrink()
.run(parameters.getRuntime(), getMainClass())
.assertSuccessWithOutput(EXPECTED)
.inspect(this::checkPresenceOfDeserializedLambdas);
diff --git a/src/test/java/com/android/tools/r8/cf/TryRangeTestRunner.java b/src/test/java/com/android/tools/r8/cf/TryRangeTestRunner.java
index 80a7beb..21ee0fc 100644
--- a/src/test/java/com/android/tools/r8/cf/TryRangeTestRunner.java
+++ b/src/test/java/com/android/tools/r8/cf/TryRangeTestRunner.java
@@ -55,7 +55,7 @@
.addKeepMainRule(TryRangeTest.class)
.setMode(CompilationMode.RELEASE)
.addDontObfuscate()
- .noTreeShaking()
+ .addDontShrink()
.enableInliningAnnotations()
.addOptionsModification(o -> o.enableLoadStoreOptimization = false)
.run(parameters.getRuntime(), TryRangeTest.class)
@@ -70,7 +70,7 @@
.addKeepMainRule(TryRangeTestLimitRange.class)
.setMode(CompilationMode.RELEASE)
.addDontObfuscate()
- .noTreeShaking()
+ .addDontShrink()
.enableInliningAnnotations()
.addOptionsModification(
o -> {
diff --git a/src/test/java/com/android/tools/r8/checkdiscarded/CheckDiscardedOverriddenMethodTest.java b/src/test/java/com/android/tools/r8/checkdiscarded/CheckDiscardedOverriddenMethodTest.java
index 6227c12..c321302 100644
--- a/src/test/java/com/android/tools/r8/checkdiscarded/CheckDiscardedOverriddenMethodTest.java
+++ b/src/test/java/com/android/tools/r8/checkdiscarded/CheckDiscardedOverriddenMethodTest.java
@@ -50,7 +50,7 @@
.enableNeverClassInliningAnnotations()
.enableInliningAnnotations()
.enableNoVerticalClassMergingAnnotations()
- .minification(minification)
+ .addDontObfuscateUnless(minification)
.setMinApi(parameters)
// Asserting that -checkdiscard is not giving any information out on an un-removed
// sub-type member.
diff --git a/src/test/java/com/android/tools/r8/checkdiscarded/CheckDiscardedTest.java b/src/test/java/com/android/tools/r8/checkdiscarded/CheckDiscardedTest.java
index ef048be..7260ad8 100644
--- a/src/test/java/com/android/tools/r8/checkdiscarded/CheckDiscardedTest.java
+++ b/src/test/java/com/android/tools/r8/checkdiscarded/CheckDiscardedTest.java
@@ -58,7 +58,7 @@
UnusedClass.class, UsedClass.class, Main.class, WillBeGone.class, WillStay.class)
.addKeepMainRule(Main.class)
.addKeepRules(checkDiscardRule(checkMembers, annotation))
- .minification(minify)
+ .addDontObfuscateUnless(minify)
.addOptionsModification(this::noInlining)
.compile();
assertNull(onCompilationFailure);
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/ConstClassAfterVerticalClassMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/ConstClassAfterVerticalClassMergingTest.java
new file mode 100644
index 0000000..4f44022
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/ConstClassAfterVerticalClassMergingTest.java
@@ -0,0 +1,74 @@
+// Copyright (c) 2024, 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.classmerging.horizontal;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.codeinspector.HorizontallyMergedClassesInspector;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class ConstClassAfterVerticalClassMergingTest extends TestBase {
+
+ @Parameter(0)
+ public boolean enableSyntheticSharing;
+
+ @Parameter(1)
+ public TestParameters parameters;
+
+ @Parameters(name = "{1}, synthetic sharing: {0}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ BooleanUtils.values(), getTestParameters().withAllRuntimesAndApiLevels().build());
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(Main.class)
+ .addHorizontallyMergedClassesInspector(
+ HorizontallyMergedClassesInspector::assertNoClassesMerged)
+ .addVerticallyMergedClassesInspector(
+ inspector ->
+ inspector
+ .applyIf(
+ parameters.isDexRuntime(), i -> i.assertMergedIntoSubtype(I.class, J.class))
+ .assertNoOtherClassesMerged())
+ .setMinApi(parameters)
+ .addOptionsModification(
+ options -> options.getTestingOptions().enableSyntheticSharing = enableSyntheticSharing)
+ .compile()
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("2");
+ }
+
+ static class Main {
+
+ public static void main(String[] args) {
+ Map<Class<?>, Object> map = new HashMap<>();
+ map.put(I.class, (I) () -> System.out.println("I"));
+ map.put(J.class, (J) () -> System.out.println("J"));
+ System.out.println(map.size());
+ }
+ }
+
+ interface I {
+
+ void f();
+ }
+
+ interface J {
+
+ void g();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerTest.java b/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerTest.java
index 723e674..627c16f 100644
--- a/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerTest.java
@@ -855,7 +855,6 @@
ImmutableSet.of(
"classmerging.SuperCallToMergedClassIsRewrittenTest",
"classmerging.A",
- "classmerging.D",
"classmerging.F");
JasminBuilder jasminBuilder = new JasminBuilder();
diff --git a/src/test/java/com/android/tools/r8/classpath/SuperclassOfLocalClassesOnClasspathTest.java b/src/test/java/com/android/tools/r8/classpath/SuperclassOfLocalClassesOnClasspathTest.java
new file mode 100644
index 0000000..8f5f324
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classpath/SuperclassOfLocalClassesOnClasspathTest.java
@@ -0,0 +1,138 @@
+// Copyright (c) 2024, 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.classpath;
+
+package com.android.tools.r8.classpath;
+
+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.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.ZipUtils.ZipBuilder;
+import java.nio.file.Path;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class SuperclassOfLocalClassesOnClasspathTest extends TestBase {
+
+ @Parameter(0)
+ public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ static Path classpathDex;
+ static Path classpathCf;
+
+ @BeforeClass
+ public static void setUpClasspath() throws Exception {
+ // Build classpath DEX with API level 1 to work with all API levels.
+ classpathDex =
+ testForD8(getStaticTemp())
+ .addProgramClasses(A.class)
+ .setMinApi(AndroidApiLevel.B)
+ .compile()
+ .writeToZip();
+ classpathCf = getStaticTemp().newFile("classpath2.zip").toPath();
+ ZipBuilder.builder(classpathCf)
+ .addFile(
+ DescriptorUtils.getPathFromJavaType(A.class),
+ ToolHelper.getClassFileForTestClass(A.class))
+ .build();
+ }
+
+ @Test
+ public void testD8() throws Exception {
+ parameters.assumeDexRuntime();
+ testForD8()
+ .addClasspathClasses(A.class)
+ .addInnerClasses(A.class)
+ .addProgramClasses(I.class)
+ .addProgramClasses(TestClass.class)
+ .setMinApi(parameters)
+ .addRunClasspathFiles(classpathDex)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccess();
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ // Split A and its inner classes on classpath/program.
+ .addClasspathClasses(A.class)
+ .addInnerClasses(A.class)
+ .addProgramClasses(I.class)
+ .addProgramClasses(TestClass.class)
+ .addKeepMainRule(TestClass.class)
+ .addKeepRules("-keep class " + A.class.getTypeName() + "$* { *; }")
+ .setMinApi(parameters)
+ .addRunClasspathFiles(parameters.isDexRuntime() ? classpathDex : classpathCf)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccess();
+ }
+
+ interface I {
+ void m();
+ }
+
+ static class A {
+ static {
+ // Anonymous local class in <clinit>.
+ new I() {
+ public void m() {}
+ };
+ }
+
+ static {
+ // Named local class in <clinit>.
+ class Local {}
+ new Local();
+ }
+
+ public A() {
+ anonymousLocalClass();
+ namedLocalClass();
+
+ // Anonymous local class in <init>.
+ new I() {
+ public void m() {}
+ };
+
+ // Named local class in <init>.
+ class Local {
+ public void m() {}
+ }
+
+ new Local();
+ }
+
+ private void anonymousLocalClass() {
+ new I() {
+ public void m() {}
+ };
+ }
+
+ private void namedLocalClass() {
+ class Local {
+ public void m() {}
+ }
+ new Local();
+ }
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ new A();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/compatproguard/ForNameTest.java b/src/test/java/com/android/tools/r8/compatproguard/ForNameTest.java
index 97cb7c9..9c7cf17 100644
--- a/src/test/java/com/android/tools/r8/compatproguard/ForNameTest.java
+++ b/src/test/java/com/android/tools/r8/compatproguard/ForNameTest.java
@@ -43,7 +43,7 @@
// Add main dex rule to disable Class.forName() optimization.
.addMainDexRules("-keep class " + CLASS_NAME)
.addDontOptimize()
- .noTreeShaking()
+ .addDontShrink()
.setMinApi(AndroidApiLevel.B));
ClassSubject clazz = inspector.clazz(CLASS_NAME);
@@ -79,7 +79,7 @@
.addMainDexRules("-keep class " + CLASS_NAME)
.addDontOptimize()
.addDontObfuscate()
- .noTreeShaking()
+ .addDontShrink()
.setMinApi(AndroidApiLevel.B));
ClassSubject clazz = inspector.clazz(CLASS_NAME);
diff --git a/src/test/java/com/android/tools/r8/debug/KotlinStdLibCompilationTest.java b/src/test/java/com/android/tools/r8/debug/KotlinStdLibCompilationTest.java
index 7a10656..11b4f24 100644
--- a/src/test/java/com/android/tools/r8/debug/KotlinStdLibCompilationTest.java
+++ b/src/test/java/com/android/tools/r8/debug/KotlinStdLibCompilationTest.java
@@ -80,7 +80,7 @@
.addKeepAllAttributes()
.allowDiagnosticWarningMessages()
.addDontObfuscate()
- .noTreeShaking()
+ .addDontShrink()
.setMode(CompilationMode.DEBUG)
.setMinApi(parameters)
.applyIf(
diff --git a/src/test/java/com/android/tools/r8/debug/LambdaOuterContextTestRunner.java b/src/test/java/com/android/tools/r8/debug/LambdaOuterContextTestRunner.java
index d6b8dc4..72f67f8 100644
--- a/src/test/java/com/android/tools/r8/debug/LambdaOuterContextTestRunner.java
+++ b/src/test/java/com/android/tools/r8/debug/LambdaOuterContextTestRunner.java
@@ -60,7 +60,7 @@
.addProgramClassesAndInnerClasses(CLASS)
.debug()
.addDontObfuscate()
- .noTreeShaking()
+ .addDontShrink()
.setMinApi(parameters)
.run(parameters.getRuntime(), CLASS)
.assertSuccessWithOutput(EXPECTED)
diff --git a/src/test/java/com/android/tools/r8/debug/LineNumberOptimizationTest.java b/src/test/java/com/android/tools/r8/debug/LineNumberOptimizationTest.java
index ec4a891..a811ca2 100644
--- a/src/test/java/com/android/tools/r8/debug/LineNumberOptimizationTest.java
+++ b/src/test/java/com/android/tools/r8/debug/LineNumberOptimizationTest.java
@@ -52,7 +52,7 @@
.addProgramClasses(LineNumberOptimization1.class, LineNumberOptimization2.class)
.setMinApi(parameters)
.setMode(dontOptimizeByEnablingDebug ? CompilationMode.DEBUG : CompilationMode.RELEASE)
- .noTreeShaking()
+ .addDontShrink()
.addDontObfuscate()
.addKeepAttributeSourceFile()
.addKeepAttributeLineNumberTable()
diff --git a/src/test/java/com/android/tools/r8/debug/LoadInvokeLoadOptimizationTestRunner.java b/src/test/java/com/android/tools/r8/debug/LoadInvokeLoadOptimizationTestRunner.java
index e33be2e..d2e3120 100644
--- a/src/test/java/com/android/tools/r8/debug/LoadInvokeLoadOptimizationTestRunner.java
+++ b/src/test/java/com/android/tools/r8/debug/LoadInvokeLoadOptimizationTestRunner.java
@@ -58,7 +58,7 @@
@Test
public void testR8() throws Throwable {
testForR8(parameters.getBackend())
- .noTreeShaking()
+ .addDontShrink()
.addDontObfuscate()
.addKeepRules("-keepattributes SourceFile,LineNumberTable")
.addProgramClasses(CLASS)
diff --git a/src/test/java/com/android/tools/r8/debug/LocalsTest.java b/src/test/java/com/android/tools/r8/debug/LocalsTest.java
index add47dc..8b914ad 100644
--- a/src/test/java/com/android/tools/r8/debug/LocalsTest.java
+++ b/src/test/java/com/android/tools/r8/debug/LocalsTest.java
@@ -48,8 +48,8 @@
.addProgramClasses(Locals.class)
.setMinApi(parameters)
.debug()
- .treeShaking(false)
- .minification(false)
+ .addDontShrink()
+ .addDontObfuscate()
.addKeepAllClassesRule()
.debugConfig(parameters.getRuntime())
: testForRuntime(parameters)
diff --git a/src/test/java/com/android/tools/r8/debug/SynchronizedBlockTest.java b/src/test/java/com/android/tools/r8/debug/SynchronizedBlockTest.java
index 6aafde3..c7ee7f1 100644
--- a/src/test/java/com/android/tools/r8/debug/SynchronizedBlockTest.java
+++ b/src/test/java/com/android/tools/r8/debug/SynchronizedBlockTest.java
@@ -45,8 +45,8 @@
.addProgramClasses(SynchronizedBlock.class)
.setMinApi(parameters)
.debug()
- .treeShaking(false)
- .minification(false)
+ .addDontShrink()
+ .addDontObfuscate()
.addKeepAllClassesRule()
.debugConfig(parameters.getRuntime())
: testForRuntime(parameters)
diff --git a/src/test/java/com/android/tools/r8/debuginfo/KotlinDebugInfoTestRunner.java b/src/test/java/com/android/tools/r8/debuginfo/KotlinDebugInfoTestRunner.java
index f2fa291..6b2e7f0 100644
--- a/src/test/java/com/android/tools/r8/debuginfo/KotlinDebugInfoTestRunner.java
+++ b/src/test/java/com/android/tools/r8/debuginfo/KotlinDebugInfoTestRunner.java
@@ -86,7 +86,7 @@
.apply(configuration)
.debug()
.addDontObfuscate()
- .noTreeShaking()
+ .addDontShrink()
.compile()
.run(parameters.getRuntime(), className)
.assertSuccessWithOutput(runInput.stdout);
diff --git a/src/test/java/com/android/tools/r8/debuginfo/pc2pc/DifferentParameterCountMultilineCodeTestRunner.java b/src/test/java/com/android/tools/r8/debuginfo/pc2pc/DifferentParameterCountMultilineCodeTestRunner.java
index 8cd026d..5c35005 100644
--- a/src/test/java/com/android/tools/r8/debuginfo/pc2pc/DifferentParameterCountMultilineCodeTestRunner.java
+++ b/src/test/java/com/android/tools/r8/debuginfo/pc2pc/DifferentParameterCountMultilineCodeTestRunner.java
@@ -43,7 +43,7 @@
.addProgramClasses(CLASS)
.addKeepMainRule(CLASS)
// Keep all the methods but allow renaming.
- .noTreeShaking()
+ .addDontShrink()
.addKeepAttributeLineNumberTable()
.addKeepAttributeSourceFile()
.addKeepRules("-renamesourcefileattribute " + (customSourceFile ? "X" : "SourceFile"))
diff --git a/src/test/java/com/android/tools/r8/debuginfo/pc2pc/DifferentParameterCountSingleLineCodeTestRunner.java b/src/test/java/com/android/tools/r8/debuginfo/pc2pc/DifferentParameterCountSingleLineCodeTestRunner.java
index 6c31cf2..7a1bd68 100644
--- a/src/test/java/com/android/tools/r8/debuginfo/pc2pc/DifferentParameterCountSingleLineCodeTestRunner.java
+++ b/src/test/java/com/android/tools/r8/debuginfo/pc2pc/DifferentParameterCountSingleLineCodeTestRunner.java
@@ -43,7 +43,7 @@
.addProgramClasses(CLASS)
.addKeepMainRule(CLASS)
// Keep all the methods but allow renaming.
- .noTreeShaking()
+ .addDontShrink()
.addKeepAttributeLineNumberTable()
.addKeepAttributeSourceFile()
.addKeepRules("-renamesourcefileattribute " + (customSourceFile ? "X" : "SourceFile"))
diff --git a/src/test/java/com/android/tools/r8/desugar/DefaultLambdaWithInvokeInterfaceTestRunner.java b/src/test/java/com/android/tools/r8/desugar/DefaultLambdaWithInvokeInterfaceTestRunner.java
index 42e6975..e9a894d 100644
--- a/src/test/java/com/android/tools/r8/desugar/DefaultLambdaWithInvokeInterfaceTestRunner.java
+++ b/src/test/java/com/android/tools/r8/desugar/DefaultLambdaWithInvokeInterfaceTestRunner.java
@@ -55,7 +55,7 @@
testForR8(Backend.CF)
.addProgramClassesAndInnerClasses(CLASS)
.addDontObfuscate()
- .noTreeShaking()
+ .addDontShrink()
.debug()
.compile();
compileResult
diff --git a/src/test/java/com/android/tools/r8/desugar/DefaultLambdaWithSelfReferenceTestRunner.java b/src/test/java/com/android/tools/r8/desugar/DefaultLambdaWithSelfReferenceTestRunner.java
index ce5f0bd..04f4efe 100644
--- a/src/test/java/com/android/tools/r8/desugar/DefaultLambdaWithSelfReferenceTestRunner.java
+++ b/src/test/java/com/android/tools/r8/desugar/DefaultLambdaWithSelfReferenceTestRunner.java
@@ -100,7 +100,7 @@
.addProgramClassesAndInnerClasses(CLASS)
.setMinApi(parameters)
.addDontObfuscate()
- .noTreeShaking()
+ .addDontShrink()
.addKeepAllAttributes()
.debug()
.compile()
diff --git a/src/test/java/com/android/tools/r8/desugar/backports/BackportDuplicationTest.java b/src/test/java/com/android/tools/r8/desugar/backports/BackportDuplicationTest.java
index 9284fa8..4b9b180 100644
--- a/src/test/java/com/android/tools/r8/desugar/backports/BackportDuplicationTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/backports/BackportDuplicationTest.java
@@ -90,7 +90,7 @@
.addKeepMainRule(TestClass.class)
.addKeepClassAndMembersRules(MiniAssert.class)
.setMinApi(parameters)
- .minification(minify)
+ .addDontObfuscateUnless(minify)
.run(parameters.getRuntime(), TestClass.class)
.assertSuccessWithOutput(EXPECTED)
.inspect(this::checkNoOriginalsAndNoInternalSynthetics);
diff --git a/src/test/java/com/android/tools/r8/desugar/jdk8272564/Jdk8272564Test.java b/src/test/java/com/android/tools/r8/desugar/jdk8272564/Jdk8272564Test.java
index 43853de..52809ee 100644
--- a/src/test/java/com/android/tools/r8/desugar/jdk8272564/Jdk8272564Test.java
+++ b/src/test/java/com/android/tools/r8/desugar/jdk8272564/Jdk8272564Test.java
@@ -155,7 +155,7 @@
testForR8(parameters.getBackend())
.addProgramFiles(Jdk8272564.jar())
.setMinApi(parameters)
- .noTreeShaking()
+ .addDontShrink()
.addKeepClassAndMembersRules(Jdk8272564.Main.typeName())
.run(parameters.getRuntime(), Jdk8272564.Main.typeName())
.inspect(this::assertJdk8272564NotFixedCodeR8)
diff --git a/src/test/java/com/android/tools/r8/desugar/lambdas/LambdaInStacktraceTest.java b/src/test/java/com/android/tools/r8/desugar/lambdas/LambdaInStacktraceTest.java
index 7c2fab0..1aeff4d 100644
--- a/src/test/java/com/android/tools/r8/desugar/lambdas/LambdaInStacktraceTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/lambdas/LambdaInStacktraceTest.java
@@ -9,7 +9,6 @@
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.ToolHelper.DexVm.Version;
import com.android.tools.r8.utils.StringUtils;
import java.util.ArrayList;
import java.util.List;
@@ -85,7 +84,7 @@
.addKeepMainRule(TestRunner.class)
.addKeepAttributeSourceFile()
.addKeepRules("-renamesourcefileattribute SourceFile")
- .noTreeShaking()
+ .addDontShrink()
.addDontOptimize()
.run(parameters.getRuntime(), TestRunner.class, Boolean.toString(isDalvik))
.assertSuccess()
diff --git a/src/test/java/com/android/tools/r8/desugar/lambdas/LambdaStaticInstanceFieldDuplicationTest.java b/src/test/java/com/android/tools/r8/desugar/lambdas/LambdaStaticInstanceFieldDuplicationTest.java
index a33995b..4d56e6a 100644
--- a/src/test/java/com/android/tools/r8/desugar/lambdas/LambdaStaticInstanceFieldDuplicationTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/lambdas/LambdaStaticInstanceFieldDuplicationTest.java
@@ -72,7 +72,7 @@
// Prevent R8 from eliminating the lambdas by keeping the application of them.
.addKeepClassAndMembersRules(Accept.class)
.setMinApi(parameters)
- .minification(minify)
+ .addDontObfuscateUnless(minify)
.run(parameters.getRuntime(), TestClass.class)
.assertSuccessWithOutput(EXPECTED)
.inspect(this::checkNoOriginalsAndNoInternalSynthetics);
diff --git a/src/test/java/com/android/tools/r8/desugar/lambdas/LambdaToSysOutPrintlnDuplicationTest.java b/src/test/java/com/android/tools/r8/desugar/lambdas/LambdaToSysOutPrintlnDuplicationTest.java
index 0581c23..05870c7 100644
--- a/src/test/java/com/android/tools/r8/desugar/lambdas/LambdaToSysOutPrintlnDuplicationTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/lambdas/LambdaToSysOutPrintlnDuplicationTest.java
@@ -70,7 +70,7 @@
.addProgramClasses(CLASSES)
.addKeepMainRule(TestClass.class)
.setMinApi(parameters)
- .minification(minify)
+ .addDontObfuscateUnless(minify)
.run(parameters.getRuntime(), TestClass.class)
.assertSuccessWithOutput(EXPECTED)
.inspect(this::checkNoOriginalsAndNoInternalSynthetics);
diff --git a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestCompilationExceptionTest.java b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestCompilationExceptionTest.java
index 9ac144b..ec66458 100644
--- a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestCompilationExceptionTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestCompilationExceptionTest.java
@@ -93,7 +93,7 @@
.addProgramFiles(matchingClasses);
} else {
return testForR8(parameters.getBackend())
- .noTreeShaking()
+ .addDontShrink()
.addDontObfuscate()
.addKeepAllAttributes()
.setMinApi(parameters)
diff --git a/src/test/java/com/android/tools/r8/desugar/records/EmptyRecordTest.java b/src/test/java/com/android/tools/r8/desugar/records/EmptyRecordTest.java
index 7b1065a..9c88c93 100644
--- a/src/test/java/com/android/tools/r8/desugar/records/EmptyRecordTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/records/EmptyRecordTest.java
@@ -81,7 +81,7 @@
.applyIf(
parameters.isCfRuntime(),
testBuilder -> testBuilder.addLibraryFiles(RecordTestUtils.getJdk15LibraryFiles(temp)))
- .minification(enableMinification)
+ .addDontObfuscateUnless(enableMinification)
.applyIf(enableRepackaging, b -> b.addKeepRules("-repackageclasses p"))
.setMinApi(parameters)
.compile()
diff --git a/src/test/java/com/android/tools/r8/desugar/records/RecordShrinkFieldTest.java b/src/test/java/com/android/tools/r8/desugar/records/RecordShrinkFieldTest.java
index 0c007f2..e8587ca 100644
--- a/src/test/java/com/android/tools/r8/desugar/records/RecordShrinkFieldTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/records/RecordShrinkFieldTest.java
@@ -67,7 +67,7 @@
.addProgramClassFileData(PROGRAM_DATA)
.setMinApi(parameters)
.addKeepMainRule(MAIN_TYPE)
- .minification(minifying)
+ .addDontObfuscateUnless(minifying)
.compile()
.inspect(inspector -> inspect(inspector, false))
.run(parameters.getRuntime(), MAIN_TYPE)
@@ -82,7 +82,7 @@
testForR8(Backend.CF)
.addProgramClassFileData(PROGRAM_DATA)
.addKeepMainRule(MAIN_TYPE)
- .minification(minifying)
+ .addDontObfuscateUnless(minifying)
.addLibraryFiles(RecordTestUtils.getJdk15LibraryFiles(temp))
.compile()
.writeToZip();
@@ -90,7 +90,7 @@
.addProgramFiles(desugared)
.setMinApi(parameters)
.addKeepMainRule(MAIN_TYPE)
- .minification(minifying)
+ .addDontObfuscateUnless(minifying)
.compile()
.inspect(inspector -> inspect(inspector, true))
.run(parameters.getRuntime(), MAIN_TYPE)
diff --git a/src/test/java/com/android/tools/r8/desugaring/interfacemethods/Regress148461139.java b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/Regress148461139.java
index e7219f1..d73eeb6 100644
--- a/src/test/java/com/android/tools/r8/desugaring/interfacemethods/Regress148461139.java
+++ b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/Regress148461139.java
@@ -33,7 +33,7 @@
testForR8(parameters.getBackend())
.setMinApi(parameters)
.addProgramClasses(Condition.class)
- .noTreeShaking()
+ .addDontShrink()
.compile()
.inspect(i -> assertTrue(i.clazz(Condition.class).isAbstract()));
}
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/DoubleProcessingEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/DoubleProcessingEnumUnboxingTest.java
index bd6b65d..df5e669 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/DoubleProcessingEnumUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/DoubleProcessingEnumUnboxingTest.java
@@ -61,7 +61,7 @@
.addKeepRules(enumKeepRules.getKeepRules())
.enableNeverClassInliningAnnotations()
.enableInliningAnnotations()
- .minification(minification)
+ .addDontObfuscateUnless(minification)
.compile()
.writeToZip();
// Compile the app with the lib.
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/DoubleProcessingMergeEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/DoubleProcessingMergeEnumUnboxingTest.java
index aa341a7..a5d0de3 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/DoubleProcessingMergeEnumUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/DoubleProcessingMergeEnumUnboxingTest.java
@@ -81,7 +81,7 @@
.addKeepRules(enumKeepRules.getKeepRules())
.enableNeverClassInliningAnnotations()
.enableInliningAnnotations()
- .minification(minification)
+ .addDontObfuscateUnless(minification)
.addKeepPackageNamesRule(libClass.getPackage())
.compile()
.writeToZip();
diff --git a/src/test/java/com/android/tools/r8/graph/MissingClassThrowingTest.java b/src/test/java/com/android/tools/r8/graph/MissingClassThrowingTest.java
index 333ab92..2a502d7 100644
--- a/src/test/java/com/android/tools/r8/graph/MissingClassThrowingTest.java
+++ b/src/test/java/com/android/tools/r8/graph/MissingClassThrowingTest.java
@@ -69,7 +69,7 @@
.addKeepAllClassesRule()
.addKeepAllAttributes()
.addDontObfuscate()
- .noTreeShaking()
+ .addDontShrink()
.enableInliningAnnotations()
.enableNoHorizontalClassMergingAnnotations()
.debug()
diff --git a/src/test/java/com/android/tools/r8/internal/Regression127524985.java b/src/test/java/com/android/tools/r8/internal/Regression127524985.java
index 8bfef9e..c9e61ed 100644
--- a/src/test/java/com/android/tools/r8/internal/Regression127524985.java
+++ b/src/test/java/com/android/tools/r8/internal/Regression127524985.java
@@ -68,7 +68,7 @@
assumeTrue(parameters.isCfRuntime());
testForR8(parameters.getBackend())
.debug()
- .noTreeShaking()
+ .addDontShrink()
.addDontObfuscate()
.addKeepAllAttributes()
.addKeepRules("-dontwarn")
diff --git a/src/test/java/com/android/tools/r8/internal/opensourceapps/TiviTest.java b/src/test/java/com/android/tools/r8/internal/opensourceapps/TiviTest.java
index 05d0d8b..b11215a 100644
--- a/src/test/java/com/android/tools/r8/internal/opensourceapps/TiviTest.java
+++ b/src/test/java/com/android/tools/r8/internal/opensourceapps/TiviTest.java
@@ -50,6 +50,7 @@
public void testR8() throws Exception {
testForR8(Backend.DEX)
.addProgramFiles(outDirectory.resolve("program.jar"))
+ .addOptionsModification(options -> options.getTestingOptions().allowCodeReplacement = false)
.apply(this::configure)
.compile();
}
diff --git a/src/test/java/com/android/tools/r8/internal/proto/Proto3ShrinkingTest.java b/src/test/java/com/android/tools/r8/internal/proto/Proto3ShrinkingTest.java
index 21314c0..375c5bc 100644
--- a/src/test/java/com/android/tools/r8/internal/proto/Proto3ShrinkingTest.java
+++ b/src/test/java/com/android/tools/r8/internal/proto/Proto3ShrinkingTest.java
@@ -68,7 +68,7 @@
.allowUnusedDontWarnPatterns()
.allowUnusedProguardConfigurationRules()
.enableProtoShrinking()
- .minification(enableMinification)
+ .addDontObfuscateUnless(enableMinification)
.setMinApi(parameters)
.compile()
.assertAllInfoMessagesMatch(
@@ -120,7 +120,7 @@
.allowUnusedDontWarnPatterns()
.allowUnusedProguardConfigurationRules()
.enableProtoShrinking()
- .minification(enableMinification)
+ .addDontObfuscateUnless(enableMinification)
.setMinApi(parameters)
.compile()
.assertAllInfoMessagesMatch(
diff --git a/src/test/java/com/android/tools/r8/internal/proto/ProtoShrinkingTest.java b/src/test/java/com/android/tools/r8/internal/proto/ProtoShrinkingTest.java
index e9181de..71c43bf 100644
--- a/src/test/java/com/android/tools/r8/internal/proto/ProtoShrinkingTest.java
+++ b/src/test/java/com/android/tools/r8/internal/proto/ProtoShrinkingTest.java
@@ -110,7 +110,7 @@
.allowUnusedProguardConfigurationRules()
.enableProguardTestOptions()
.enableProtoShrinking()
- .minification(enableMinification)
+ .addDontObfuscateUnless(enableMinification)
.setMinApi(parameters)
.compile()
.assertAllInfoMessagesMatch(
@@ -425,7 +425,7 @@
.allowUnusedDontWarnPatterns()
.allowUnusedProguardConfigurationRules()
.enableProtoShrinking()
- .minification(enableMinification)
+ .addDontObfuscateUnless(enableMinification)
.setMinApi(parameters)
.compile()
.assertAllInfoMessagesMatch(
@@ -454,7 +454,7 @@
.allowUnusedDontWarnPatterns()
.allowUnusedProguardConfigurationRules()
.enableProtoShrinking()
- .minification(enableMinification)
+ .addDontObfuscateUnless(enableMinification)
.setMinApi(parameters)
.compile()
.assertAllInfoMessagesMatch(
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/constant/B132897042.java b/src/test/java/com/android/tools/r8/ir/analysis/constant/B132897042.java
index 253612a..3415ebc 100644
--- a/src/test/java/com/android/tools/r8/ir/analysis/constant/B132897042.java
+++ b/src/test/java/com/android/tools/r8/ir/analysis/constant/B132897042.java
@@ -35,7 +35,7 @@
"-assumevalues class" + LibClass.class.getName() + " {",
" static int SDK_INT return 1..28;",
"}"))
- .noTreeShaking()
+ .addDontShrink()
.setMinApi(parameters)
.compile()
.assertNoMessages();
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/path/PathConstraintAnalysisUnitTest.java b/src/test/java/com/android/tools/r8/ir/analysis/path/PathConstraintAnalysisUnitTest.java
new file mode 100644
index 0000000..56573a5
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/analysis/path/PathConstraintAnalysisUnitTest.java
@@ -0,0 +1,112 @@
+// Copyright (c) 2024, 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.path;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+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.AppView;
+import com.android.tools.r8.ir.analysis.framework.intraprocedural.DataflowAnalysisResult;
+import com.android.tools.r8.ir.analysis.framework.intraprocedural.DataflowAnalysisResult.SuccessfulDataflowAnalysisResult;
+import com.android.tools.r8.ir.analysis.path.state.ConcretePathConstraintAnalysisState;
+import com.android.tools.r8.ir.analysis.path.state.PathConstraintAnalysisState;
+import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.optimize.argumentpropagation.computation.ComputationTreeNode;
+import com.android.tools.r8.optimize.argumentpropagation.computation.ComputationTreeUnopCompareNode;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class PathConstraintAnalysisUnitTest extends TestBase {
+
+ @Parameter(0)
+ public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withNoneRuntime().build();
+ }
+
+ @Test
+ public void test() throws Exception {
+ AndroidApp app =
+ AndroidApp.builder()
+ .addProgramFiles(ToolHelper.getClassFileForTestClass(Main.class))
+ .addLibraryFile(ToolHelper.getMostRecentAndroidJar())
+ .build();
+ AppView<AppInfoWithLiveness> appView = computeAppViewWithLiveness(app);
+ CodeInspector inspector = new CodeInspector(app);
+ IRCode code =
+ inspector.clazz(Main.class).uniqueMethodWithOriginalName("greet").buildIR(appView);
+ PathConstraintAnalysis analysis = new PathConstraintAnalysis(appView, code);
+ DataflowAnalysisResult result = analysis.run(code.entryBlock());
+ assertTrue(result.isSuccessfulAnalysisResult());
+ SuccessfulDataflowAnalysisResult<BasicBlock, PathConstraintAnalysisState> successfulResult =
+ result.asSuccessfulAnalysisResult();
+
+ // Inspect ENTRY state.
+ PathConstraintAnalysisState entryConstraint =
+ successfulResult.getBlockExitState(code.entryBlock());
+ assertTrue(entryConstraint.isBottom());
+
+ // Inspect THEN state.
+ PathConstraintAnalysisState thenConstraint =
+ successfulResult.getBlockExitState(code.entryBlock().exit().asIf().getTrueTarget());
+ assertTrue(thenConstraint.isConcrete());
+
+ ConcretePathConstraintAnalysisState concreteThenConstraint = thenConstraint.asConcreteState();
+ assertEquals(1, concreteThenConstraint.getPathConstraints().size());
+ assertEquals(0, concreteThenConstraint.getNegatedPathConstraints().size());
+
+ ComputationTreeNode thenPathConstraint =
+ concreteThenConstraint.getPathConstraints().iterator().next();
+ assertTrue(thenPathConstraint instanceof ComputationTreeUnopCompareNode);
+
+ // Inspect ELSE state.
+ PathConstraintAnalysisState elseConstraint =
+ successfulResult.getBlockExitState(code.entryBlock().exit().asIf().fallthroughBlock());
+ assertTrue(elseConstraint.isConcrete());
+
+ ConcretePathConstraintAnalysisState concreteElseConstraint = elseConstraint.asConcreteState();
+ assertEquals(0, concreteElseConstraint.getPathConstraints().size());
+ assertEquals(1, concreteElseConstraint.getNegatedPathConstraints().size());
+
+ ComputationTreeNode elsePathConstraint =
+ concreteElseConstraint.getNegatedPathConstraints().iterator().next();
+ assertEquals(thenPathConstraint, elsePathConstraint);
+
+ // Inspect RETURN state.
+ PathConstraintAnalysisState returnConstraint =
+ successfulResult.getBlockExitState(code.computeNormalExitBlocks().get(0));
+ assertTrue(returnConstraint.isConcrete());
+
+ ConcretePathConstraintAnalysisState concreteReturnConstraint =
+ returnConstraint.asConcreteState();
+ assertEquals(1, concreteReturnConstraint.getPathConstraints().size());
+ assertEquals(
+ concreteReturnConstraint.getPathConstraints(),
+ concreteReturnConstraint.getNegatedPathConstraints());
+ }
+
+ static class Main {
+
+ public static void greet(String greeting, int flags) {
+ if ((flags & 1) != 0) {
+ greeting = "Hello, world!";
+ }
+ System.out.println(greeting);
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/AllowCodeReplacementTest.java b/src/test/java/com/android/tools/r8/ir/optimize/AllowCodeReplacementTest.java
new file mode 100644
index 0000000..0180424
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/AllowCodeReplacementTest.java
@@ -0,0 +1,122 @@
+// Copyright (c) 2024, 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.isAbsentIf;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.TestBase;
+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.MethodSubject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class AllowCodeReplacementTest extends TestBase {
+
+ @Parameter(0)
+ public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withDefaultRuntimes().withMinimumApiLevel().build();
+ }
+
+ @Test
+ public void testFeatureDisabledByDefault() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(Main.class)
+ .addKeepRules(
+ "-keepclassmembers,allowshrinking class " + Main.class.getTypeName() + " {",
+ " boolean alwaysFalse();",
+ "}")
+ .addOptionsModification(options -> assertTrue(options.testing.allowCodeReplacement))
+ .setMinApi(parameters)
+ .compile()
+ .inspect(inspector -> inspect(inspector, false))
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("Hello, world!");
+ }
+
+ @Test
+ public void testAnalyzeKeptMethod() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(Main.class)
+ .addKeepRules(
+ "-keepclassmembers,allowshrinking class " + Main.class.getTypeName() + " {",
+ " boolean alwaysFalse();",
+ "}")
+ .addOptionsModification(options -> options.testing.allowCodeReplacement = false)
+ .setMinApi(parameters)
+ .compile()
+ .inspect(inspector -> inspect(inspector, true))
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("Hello, world!");
+ }
+
+ @Test
+ public void testKeepForCodeReplacement() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(Main.class)
+ .addKeepRules(
+ "-keepclassmembers,allowcodereplacement,allowshrinking class "
+ + Main.class.getTypeName()
+ + " {",
+ " boolean alwaysFalse();",
+ "}")
+ .addOptionsModification(options -> options.testing.allowCodeReplacement = false)
+ .enableProguardTestOptions()
+ .setMinApi(parameters)
+ .compile()
+ .inspect(inspector -> inspect(inspector, false))
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("Hello, world!");
+ }
+
+ private void inspect(CodeInspector inspector, boolean isOptimized) {
+ ClassSubject mainClassSubject = inspector.clazz(Main.class);
+ assertThat(mainClassSubject, isPresent());
+
+ MethodSubject mainMethodSubject = mainClassSubject.mainMethod();
+ assertThat(mainMethodSubject, isPresent());
+ assertEquals(
+ isOptimized,
+ inspector
+ .clazz(Main.class)
+ .mainMethod()
+ .streamInstructions()
+ .noneMatch(i -> i.isConstString("Unreachable")));
+
+ MethodSubject alwaysFalseMethodSubject =
+ mainClassSubject.uniqueMethodWithOriginalName("alwaysFalse");
+ assertThat(alwaysFalseMethodSubject, isAbsentIf(isOptimized));
+ }
+
+ static class Main {
+
+ public static void main(String[] args) {
+ if (alwaysFalse()) {
+ System.out.println("Unreachable");
+ } else {
+ System.out.println("Hello, world!");
+ }
+ }
+
+ static boolean alwaysFalse() {
+ return false;
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/RestartLambdaPropagationWithDefaultArgumentTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/RestartLambdaPropagationWithDefaultArgumentTest.java
new file mode 100644
index 0000000..9d571ee
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/RestartLambdaPropagationWithDefaultArgumentTest.java
@@ -0,0 +1,154 @@
+// Copyright (c) 2024, 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.callsites;
+
+import static com.android.tools.r8.utils.codeinspector.CodeMatchers.isInvokeWithTarget;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class RestartLambdaPropagationWithDefaultArgumentTest extends TestBase {
+
+ // Deliberately setting the highest bit in this mask to be able to distinguish it from the int 2.
+ private static final int FLAGS = 0b10 | (1 << 31);
+
+ @Parameter(0)
+ public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withDexRuntimesAndAllApiLevels().build();
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(Main.class)
+ .enableInliningAnnotations()
+ .setMinApi(parameters)
+ .compile()
+ .inspect(
+ inspector -> {
+ ClassSubject mainClassSubject = inspector.clazz(Main.class);
+ assertThat(mainClassSubject, isPresent());
+
+ ClassSubject lambdaClassSubject =
+ inspector.clazz(SyntheticItemsTestUtils.syntheticLambdaClass(Main.class, 0));
+ assertThat(lambdaClassSubject, isPresent());
+ // TODO(b/302281503): Lambda should not capture the two constant string arguments.
+ assertEquals(2, lambdaClassSubject.allInstanceFields().size());
+
+ MethodSubject lambdaInitSubject = lambdaClassSubject.uniqueInstanceInitializer();
+ assertThat(lambdaInitSubject, isPresent());
+ // TODO(b/302281503): Lambda should not capture the two constant string arguments.
+ assertEquals(2, lambdaInitSubject.getParameters().size());
+ assertTrue(lambdaInitSubject.getParameter(0).is(String.class));
+ assertTrue(lambdaInitSubject.getParameter(1).is(String.class));
+
+ MethodSubject mainMethodSubject = mainClassSubject.mainMethod();
+ assertThat(mainMethodSubject, isPresent());
+ // TODO(b/302281503): This argument should be removed as a result of constant
+ // propagation into the restartableMethod.
+ assertTrue(
+ mainMethodSubject
+ .streamInstructions()
+ .anyMatch(instruction -> instruction.isConstString("DefaultValueNeverUsed")));
+ // TODO(b/302281503): This argument is never used and should be removed.
+ assertTrue(
+ mainMethodSubject
+ .streamInstructions()
+ .anyMatch(
+ instruction ->
+ instruction.isConstString("Unused[DefaultValueAlwaysUsed]")));
+
+ MethodSubject restartableMethodSubject =
+ mainClassSubject.uniqueMethodWithOriginalName("restartableMethod");
+ assertThat(restartableMethodSubject, isPresent());
+ assertTrue(
+ restartableMethodSubject
+ .streamInstructions()
+ .anyMatch(instruction -> instruction.isConstNumber(FLAGS)));
+ assertTrue(
+ restartableMethodSubject
+ .streamInstructions()
+ .noneMatch(
+ instruction ->
+ instruction.isConstString("Unused[DefaultValueNeverUsed]")));
+ assertTrue(
+ restartableMethodSubject
+ .streamInstructions()
+ .anyMatch(
+ instruction -> instruction.isConstString("DefaultValueAlwaysUsed")));
+ assertTrue(
+ restartableMethodSubject
+ .streamInstructions()
+ .anyMatch(
+ instruction -> isInvokeWithTarget(lambdaInitSubject).test(instruction)));
+ })
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines(
+ "Postponing!",
+ "Restarting!",
+ "DefaultValueNeverUsed",
+ "DefaultValueAlwaysUsed",
+ Integer.toString(FLAGS),
+ "Stopping!");
+ }
+
+ static class Main {
+
+ public static void main(String[] args) {
+ Runnable restarter =
+ restartableMethod("DefaultValueNeverUsed", "Unused[DefaultValueAlwaysUsed]", FLAGS, true);
+ restarter.run();
+ }
+
+ @NeverInline
+ static Runnable restartableMethod(
+ String defaultValueNeverUsed, String defaultValueAlwaysUsed, int flags, boolean doRestart) {
+ if ((flags & 1) != 0) {
+ defaultValueNeverUsed = "Unused[DefaultValueNeverUsed]";
+ }
+ if ((flags & 2) != 0) {
+ defaultValueAlwaysUsed = "DefaultValueAlwaysUsed";
+ }
+ if (doRestart) {
+ System.out.println("Postponing!");
+ String finalDefaultValueNeverUsed = defaultValueNeverUsed;
+ String finalDefaultValueAlwaysUsed = defaultValueAlwaysUsed;
+ return () -> {
+ System.out.println("Restarting!");
+ Runnable restarter =
+ restartableMethod(
+ finalDefaultValueNeverUsed, finalDefaultValueAlwaysUsed, flags, false);
+ if (restarter == null) {
+ System.out.println("Stopping!");
+ } else {
+ throw new RuntimeException();
+ }
+ };
+ }
+ System.out.println(defaultValueNeverUsed);
+ System.out.println(defaultValueAlwaysUsed);
+ System.out.println(flags);
+ return null;
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/canonicalization/DexItemBasedConstStringCanonicalizationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/canonicalization/DexItemBasedConstStringCanonicalizationTest.java
index a8bd35a..372423d 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/canonicalization/DexItemBasedConstStringCanonicalizationTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/canonicalization/DexItemBasedConstStringCanonicalizationTest.java
@@ -113,7 +113,7 @@
testForR8(parameters.getBackend())
.addProgramClasses(MAIN, CanonicalizationTestClass.class)
.addKeepMainRule(MAIN)
- .minification(enableMinification)
+ .addDontObfuscateUnless(enableMinification)
.setMinApi(parameters)
.addOptionsModification(this::configure)
.run(parameters.getRuntime(), MAIN)
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/checkcast/CastInDeadCodeafterInstanceOfOptimizationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/checkcast/CastInDeadCodeafterInstanceOfOptimizationTest.java
new file mode 100644
index 0000000..c690efd
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/checkcast/CastInDeadCodeafterInstanceOfOptimizationTest.java
@@ -0,0 +1,61 @@
+// Copyright (c) 2024, 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.checkcast;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.StringUtils;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+// This is a regression test for b/358913905.
+@RunWith(Parameterized.class)
+public class CastInDeadCodeafterInstanceOfOptimizationTest extends TestBase {
+
+ @Parameter(0)
+ public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ private static final String EXPECTED_OUTPUT = StringUtils.lines("Hello, world!");
+
+ @Test
+ public void testD8() throws Exception {
+ parameters.assumeDexRuntime();
+ testForD8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .setMinApi(parameters)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED_OUTPUT);
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(TestClass.class)
+ .setMinApi(parameters)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED_OUTPUT);
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ Object object = new Object();
+ if (object instanceof int[]) {
+ int[] object2 = (int[]) object;
+ }
+ System.out.println("Hello, world!");
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/checkcast/CheckCastInterfaceArrayTest.java b/src/test/java/com/android/tools/r8/ir/optimize/checkcast/CheckCastInterfaceArrayTest.java
index 9887c3f..6d45501 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/checkcast/CheckCastInterfaceArrayTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/checkcast/CheckCastInterfaceArrayTest.java
@@ -44,7 +44,7 @@
.addKeepMainRule(TestClass.class)
.setMinApi(parameters)
.addDontObfuscate()
- .noTreeShaking()
+ .addDontShrink()
.compile()
.run(parameters.getRuntime(), TestClass.class)
.assertSuccessWithOutput(EXPECTED);
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/SingleTargetAfterInliningTest.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/SingleTargetAfterInliningTest.java
index 59e609d..3bd367b 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/inliner/SingleTargetAfterInliningTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/SingleTargetAfterInliningTest.java
@@ -25,13 +25,17 @@
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
import org.junit.runners.Parameterized.Parameters;
@RunWith(Parameterized.class)
public class SingleTargetAfterInliningTest extends TestBase {
- private final int maxInliningDepth;
- private final TestParameters parameters;
+ @Parameter(0)
+ public int maxInliningDepth;
+
+ @Parameter(1)
+ public TestParameters parameters;
@Parameters(name = "{1}, max inlining depth: {0}")
public static List<Object[]> data() {
@@ -39,11 +43,6 @@
ImmutableList.of(0, 1), getTestParameters().withAllRuntimesAndApiLevels().build());
}
- public SingleTargetAfterInliningTest(int maxInliningDepth, TestParameters parameters) {
- this.maxInliningDepth = maxInliningDepth;
- this.parameters = parameters;
- }
-
@Test
public void test() throws Exception {
testForR8(parameters.getBackend())
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/DefaultFieldValueJoinerWithUnknownDynamicTypeTest.java b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/DefaultFieldValueJoinerWithUnknownDynamicTypeTest.java
new file mode 100644
index 0000000..0a5e1ab
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/DefaultFieldValueJoinerWithUnknownDynamicTypeTest.java
@@ -0,0 +1,103 @@
+// Copyright (c) 2024, 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.membervaluepropagation;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class DefaultFieldValueJoinerWithUnknownDynamicTypeTest extends TestBase {
+
+ @Parameter(0)
+ public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(Main.class)
+ .addKeepRules(
+ "-keepclassmembers class " + Main.class.getTypeName() + "{",
+ " *** emptyList();",
+ " *** emptySet(boolean);",
+ "}")
+ .enableInliningAnnotations()
+ .enableNeverClassInliningAnnotations()
+ .setMinApi(parameters)
+ .compile()
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithEmptyOutput();
+ }
+
+ @NeverClassInline
+ static class Main {
+
+ static final Collection<Object> DEFAULT = emptyList();
+
+ Collection<Object> cache;
+
+ public static void main(String[] args) {
+ Main main = new Main();
+ Collection<Object> emptySet = main.test(false);
+ Collection<Object> emptyList = main.test(true);
+ checkNotIdentical(emptySet, emptyList);
+ Collection<Object> cachedEmptyList = main.test(true);
+ checkIdentical(emptyList, cachedEmptyList);
+ }
+
+ @NeverInline
+ Collection<Object> test(boolean doThrow) {
+ if (cache != null) {
+ return cache;
+ }
+ try {
+ return emptySet(doThrow);
+ } catch (Throwable t) {
+ cache = DEFAULT;
+ return cache;
+ }
+ }
+
+ // @Keep
+ static Collection<Object> emptyList() {
+ return new ArrayList<>();
+ }
+
+ // @Keep
+ static Collection<Object> emptySet(boolean doThrow) {
+ if (doThrow) {
+ throw new RuntimeException();
+ }
+ return Collections.emptySet();
+ }
+
+ static void checkIdentical(Object o1, Object o2) {
+ if (o1 != o2) {
+ throw new RuntimeException();
+ }
+ }
+
+ static void checkNotIdentical(Object o1, Object o2) {
+ if (o1 == o2) {
+ throw new RuntimeException();
+ }
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/FieldWriteBeforeFieldReadTest.java b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/FieldWriteBeforeFieldReadTest.java
index a603d83..4a681ad 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/FieldWriteBeforeFieldReadTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/FieldWriteBeforeFieldReadTest.java
@@ -24,22 +24,20 @@
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
import org.junit.runners.Parameterized.Parameters;
@RunWith(Parameterized.class)
public class FieldWriteBeforeFieldReadTest extends TestBase {
- private final TestParameters parameters;
+ @Parameter(0)
+ public TestParameters parameters;
@Parameters(name = "{0}")
public static TestParametersCollection data() {
return getTestParameters().withAllRuntimesAndApiLevels().build();
}
- public FieldWriteBeforeFieldReadTest(TestParameters parameters) {
- this.parameters = parameters;
- }
-
@Test
public void test() throws Exception {
testForR8(parameters.getBackend())
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/reflection/ForNameInterfaceTest.java b/src/test/java/com/android/tools/r8/ir/optimize/reflection/ForNameInterfaceTest.java
index 817463a..1c3626f 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/reflection/ForNameInterfaceTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/reflection/ForNameInterfaceTest.java
@@ -50,7 +50,7 @@
testForR8(parameters.getBackend())
.addInnerClasses(ForNameInterfaceTest.class)
.addKeepMainRule(Main.class)
- .minification(false)
+ .addDontObfuscate()
.setMinApi(parameters)
.run(parameters.getRuntime(), Main.class)
.assertSuccessWithOutputLines(
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 45b83f4..d4c23d5 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
@@ -67,7 +67,7 @@
.enableInliningAnnotations()
.addKeepMainRule(MAIN)
.addKeepRules("-keep class **.GetNameClinit*")
- .minification(enableMinification)
+ .addDontObfuscateUnless(enableMinification)
.setMinApi(parameters)
.addOptionsModification(this::configure)
.run(parameters.getRuntime(), MAIN)
@@ -83,7 +83,7 @@
.enableInliningAnnotations()
.addKeepMainRule(MAIN)
.addKeepRules("-keep,allowobfuscation class **.GetNameClinit*")
- .minification(enableMinification)
+ .addDontObfuscateUnless(enableMinification)
.setMinApi(parameters)
.addOptionsModification(this::configure)
.compile();
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 e322f8d..dbee197 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
@@ -271,7 +271,7 @@
.addKeepRules("-keep class **.GetName0*")
.addKeepRules("-keepattributes InnerClasses,EnclosingMethod")
.addKeepRules("-printmapping " + createNewMappingPath().toAbsolutePath().toString())
- .minification(enableMinification)
+ .addDontObfuscateUnless(enableMinification)
.setMinApi(parameters)
.addOptionsModification(this::configure)
.compile()
@@ -298,7 +298,7 @@
.addKeepRules("-keep,allowobfuscation class **.GetName0*")
.addKeepRules("-keepattributes InnerClasses,EnclosingMethod")
.addKeepRules("-printmapping " + createNewMappingPath().toAbsolutePath().toString())
- .minification(enableMinification)
+ .addDontObfuscateUnless(enableMinification)
.setMinApi(parameters)
.addOptionsModification(this::configure)
.compile()
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 533c75f..aa4ae93 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
@@ -206,7 +206,7 @@
.enableInliningAnnotations()
.addKeepAllClassesRule()
.addKeepAttributeInnerClassesAndEnclosingMethod()
- .minification(enableMinification)
+ .addDontObfuscateUnless(enableMinification)
.setMinApi(parameters)
.addOptionsModification(this::configure)
.run(parameters.getRuntime(), MAIN)
@@ -224,7 +224,7 @@
.addKeepRules("-keep class **.ClassGetSimpleName*")
.addKeepRules("-keep class **.Outer*")
.addKeepAttributeInnerClassesAndEnclosingMethod()
- .minification(enableMinification)
+ .addDontObfuscateUnless(enableMinification)
.setMinApi(parameters)
.addOptionsModification(this::configure)
.run(parameters.getRuntime(), MAIN)
@@ -245,7 +245,7 @@
// then use OUTPUT_WITH_SHRUNK_ATTRIBUTES
.addKeepRules("-keep,allowobfuscation class **.Outer*")
.addKeepAttributeInnerClassesAndEnclosingMethod()
- .minification(enableMinification)
+ .addDontObfuscateUnless(enableMinification)
.setMinApi(parameters)
.addOptionsModification(this::configure)
.run(parameters.getRuntime(), MAIN)
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/reflection/InnerClassNameTestRunner.java b/src/test/java/com/android/tools/r8/ir/optimize/reflection/InnerClassNameTestRunner.java
index e88fb06..61ea74d 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/reflection/InnerClassNameTestRunner.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/reflection/InnerClassNameTestRunner.java
@@ -229,7 +229,7 @@
.addKeepAttributeInnerClassesAndEnclosingMethod()
.addProgramClassFileData(InnerClassNameTestDump.dump(config, parameters))
.allowDiagnosticInfoMessages(hasMalformedInnerClassAttribute())
- .minification(minify)
+ .addDontObfuscateUnless(minify)
.addOptionsModification(
options -> {
options.disableInnerClassSeparatorValidationWhenRepackaging = true;
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/PrivateInstanceMethodCollisionTest.java b/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/PrivateInstanceMethodCollisionTest.java
index eff87f7..d45d543 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/PrivateInstanceMethodCollisionTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/PrivateInstanceMethodCollisionTest.java
@@ -67,7 +67,7 @@
.enableNeverClassInliningAnnotations()
.enableNoHorizontalClassMergingAnnotations()
.enableNoMethodStaticizingAnnotations()
- .minification(minification)
+ .addDontObfuscateUnless(minification)
.allowAccessModification(allowAccessModification)
.applyIf(
allowAccessModification,
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/UninstantiatedAnnotatedArgumentsTest.java b/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/UninstantiatedAnnotatedArgumentsTest.java
index e5c56c7..798002a 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/UninstantiatedAnnotatedArgumentsTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/UninstantiatedAnnotatedArgumentsTest.java
@@ -72,7 +72,7 @@
.enableNoParameterReorderingAnnotations()
.enableUnusedArgumentAnnotations()
// TODO(b/123060011): Mapping not working in presence of argument removal.
- .minification(keepUninstantiatedArguments)
+ .addDontObfuscateUnless(keepUninstantiatedArguments)
.setMinApi(parameters)
.compile()
.inspect(this::verifyOutput)
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/CollisionWithLibraryMethodsTest.java b/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/CollisionWithLibraryMethodsTest.java
index 44fd6bc..3f2cd87 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/CollisionWithLibraryMethodsTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/CollisionWithLibraryMethodsTest.java
@@ -53,7 +53,7 @@
.enableNeverClassInliningAnnotations()
.enableNoMethodStaticizingAnnotations()
.enableInliningAnnotations()
- .minification(minification)
+ .addDontObfuscateUnless(minification)
.setMinApi(parameters)
.compile()
.inspect(this::verify)
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/PrivateInstanceMethodCollisionTest.java b/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/PrivateInstanceMethodCollisionTest.java
index ccfb422..c55bb32 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/PrivateInstanceMethodCollisionTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/PrivateInstanceMethodCollisionTest.java
@@ -67,7 +67,7 @@
.enableInliningAnnotations()
.enableNeverClassInliningAnnotations()
.enableNoMethodStaticizingAnnotations()
- .minification(minification)
+ .addDontObfuscateUnless(minification)
.allowAccessModification(allowAccessModification)
.applyIf(
allowAccessModification,
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedAnnotatedArgumentsTest.java b/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedAnnotatedArgumentsTest.java
index 764d4f8..c4cef26 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedAnnotatedArgumentsTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedAnnotatedArgumentsTest.java
@@ -70,7 +70,7 @@
.enableConstantArgumentAnnotations()
.enableUnusedArgumentAnnotations(keepUnusedArguments)
// TODO(b/123060011): Mapping not working in presence of unused argument removal.
- .minification(keepUnusedArguments)
+ .addDontObfuscateUnless(keepUnusedArguments)
.setMinApi(parameters)
.compile()
.inspect(this::verifyOutput)
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedArgumentRemovalWithOverridingTest.java b/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedArgumentRemovalWithOverridingTest.java
index 9a844ac..b07f468 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedArgumentRemovalWithOverridingTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedArgumentRemovalWithOverridingTest.java
@@ -50,7 +50,7 @@
.enableNeverClassInliningAnnotations()
.enableInliningAnnotations()
.enableNoVerticalClassMergingAnnotations()
- .minification(minification)
+ .addDontObfuscateUnless(minification)
.setMinApi(parameters)
.compile()
.inspect(this::verify)
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 d772b8f..ba233be 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
@@ -62,7 +62,7 @@
.enableNeverClassInliningAnnotations()
.enableNoMethodStaticizingAnnotations()
.enableNoVerticalClassMergingAnnotations()
- .minification(minification)
+ .addDontObfuscateUnless(minification)
.setMinApi(parameters)
.compile()
.inspect(this::verifyUnusedArgumentsRemovedAndNoCollisions)
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedArgumentsTestBase.java b/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedArgumentsTestBase.java
index 49efe19..2cd91b2 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedArgumentsTestBase.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedArgumentsTestBase.java
@@ -75,7 +75,7 @@
.addProgramClasses(getTestClass())
.addProgramClasses(getAdditionalClasses())
.addKeepMainRule(getTestClass())
- .minification(minification)
+ .addDontObfuscateUnless(minification)
.addOptionsModification(options -> options.enableSideEffectAnalysis = false)
.apply(this::configure)
.run(parameters.getRuntime(), getTestClass())
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 9dc6503..ed272e7 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
@@ -211,15 +211,6 @@
}
@Override
- public BasicBlock addThrowingInstructionToPossiblyThrowingBlock(
- IRCode code,
- ListIterator<BasicBlock> blockIterator,
- Instruction instruction,
- InternalOptions options) {
- throw new Unimplemented();
- }
-
- @Override
public BasicBlock split(
IRCode code, ListIterator<BasicBlock> blockIterator, boolean keepCatchHandlers) {
throw new Unimplemented();
diff --git a/src/test/java/com/android/tools/r8/kotlin/ProcessKotlinReflectionLibTest.java b/src/test/java/com/android/tools/r8/kotlin/ProcessKotlinReflectionLibTest.java
index ede0d93..52ee123 100644
--- a/src/test/java/com/android/tools/r8/kotlin/ProcessKotlinReflectionLibTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/ProcessKotlinReflectionLibTest.java
@@ -67,31 +67,29 @@
@Test
public void testAsIs() throws Exception {
- test(builder -> builder.addDontObfuscate().addDontOptimize().noTreeShaking());
+ test(builder -> builder.addDontObfuscate().addDontOptimize().addDontShrink());
}
@Test
public void testDontShrinkAndDontOptimize() throws Exception {
- test(builder -> builder.addDontOptimize().noTreeShaking());
+ test(builder -> builder.addDontOptimize().addDontShrink());
}
@Test
public void testDontShrinkAndDontObfuscate() throws Exception {
- test(builder -> builder.addDontObfuscate().noTreeShaking());
+ test(builder -> builder.addDontObfuscate().addDontShrink());
}
@Test
public void testDontShrink() throws Exception {
- test(TestShrinkerBuilder::noTreeShaking);
+ test(TestShrinkerBuilder::addDontShrink);
}
@Test
public void testDontShrinkDifferently() throws Exception {
test(
builder ->
- builder
- .addKeepRules("-keep,allowobfuscation class **.*KClasses*")
- .noTreeShaking());
+ builder.addKeepRules("-keep,allowobfuscation class **.*KClasses*").addDontShrink());
}
@Test
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexListOutputTest.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexListOutputTest.java
index 5bc57ba..4de0f57 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexListOutputTest.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexListOutputTest.java
@@ -99,7 +99,7 @@
Path mainDexListOutput = temp.getRoot().toPath().resolve("main-dex-output.txt");
testForR8(Backend.DEX)
.addProgramClasses(HelloWorldMain.class)
- .noTreeShaking()
+ .addDontShrink()
.addDontObfuscate()
.setMinApi(AndroidApiLevel.K)
.addMainDexRuleFiles(mainDexRules)
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexTracingTest.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexTracingTest.java
index 19db97f..9eea1c5 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexTracingTest.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexTracingTest.java
@@ -396,7 +396,7 @@
.assumeAllMethodsMayHaveSideEffects()
.setMinApi(minSdk)
.addDontObfuscate()
- .noTreeShaking()
+ .addDontShrink()
.addDontOptimize()
.setMainDexListConsumer(ToolHelper.consumeString(r8MainDexListOutput::set))
.allowDiagnosticMessages()
diff --git a/src/test/java/com/android/tools/r8/maindexlist/checkdiscard/MainDexListCheckDiscard.java b/src/test/java/com/android/tools/r8/maindexlist/checkdiscard/MainDexListCheckDiscard.java
index 3f1fb69..db968c1 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/checkdiscard/MainDexListCheckDiscard.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/checkdiscard/MainDexListCheckDiscard.java
@@ -54,7 +54,7 @@
.setMinApi(AndroidApiLevel.K)
.addMainDexRules(keepMainProguardConfiguration(HelloWorldMain.class))
.addMainDexRules(checkDiscardRule)
- .noTreeShaking()
+ .addDontShrink()
.addDontObfuscate()
.compile();
}
diff --git a/src/test/java/com/android/tools/r8/maindexlist/whyareyoukeeping/MainDexListWhyAreYouKeeping.java b/src/test/java/com/android/tools/r8/maindexlist/whyareyoukeeping/MainDexListWhyAreYouKeeping.java
index 0aafb8d..c8a267a 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/whyareyoukeeping/MainDexListWhyAreYouKeeping.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/whyareyoukeeping/MainDexListWhyAreYouKeeping.java
@@ -85,7 +85,7 @@
public void runTestWithR8(GraphConsumer consumer, String rule) throws Exception {
R8FullTestBuilder builder =
testForR8(Backend.DEX)
- .noTreeShaking()
+ .addDontShrink()
.addDontObfuscate()
.setMinApi(AndroidApiLevel.K)
.addProgramClasses(CLASSES)
diff --git a/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingTest.java b/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingTest.java
index 6ed1857..15d91c2 100644
--- a/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingTest.java
+++ b/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingTest.java
@@ -6,32 +6,23 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
-import com.android.tools.r8.R8Command;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.naming.MemberNaming.MethodSignature;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.TestDescriptionWatcher;
+import com.android.tools.r8.utils.ThrowingConsumer;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.FieldAccessInstructionSubject;
import com.android.tools.r8.utils.codeinspector.InstructionSubject;
import com.android.tools.r8.utils.codeinspector.InvokeInstructionSubject;
import com.android.tools.r8.utils.codeinspector.MethodSubject;
import com.google.common.collect.ImmutableList;
-import java.io.File;
-import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
-import java.util.Arrays;
import java.util.Collection;
-import java.util.Collections;
import java.util.Iterator;
-import java.util.List;
-import java.util.function.Consumer;
-import java.util.stream.Collectors;
-import org.junit.Assume;
-import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -49,7 +40,7 @@
private final Backend backend;
private final String keepRuleFile;
private final Path programFile;
- private final Consumer<CodeInspector> inspection;
+ private final ThrowingConsumer<CodeInspector, Exception> inspection;
private final int minApiLevel;
@Rule
@@ -64,34 +55,6 @@
this.minApiLevel = configuration.getMinApiLevel();
}
- @Before
- public void runR8() throws Exception {
- // Generate R8 processed version without library option.
- String out = temp.getRoot().getCanonicalPath();
- // NOTE: It is important to turn off inlining to ensure
- // dex inspection of invokes is predictable.
- R8Command.Builder builder =
- R8Command.builder()
- .setOutput(Paths.get(out), TestBase.outputMode(backend))
- .addLibraryFiles(JAR_LIBRARY, TestBase.runtimeJar(backend))
- .setDisableTreeShaking(true)
- .setDisableMinification(true);
- if (backend == Backend.DEX) {
- builder.setMinApiLevel(minApiLevel);
- }
- if (keepRuleFile != null) {
- builder.addProguardConfigurationFiles(Paths.get(ToolHelper.EXAMPLES_DIR, name, keepRuleFile));
- ToolHelper.allowTestProguardOptions(builder);
- }
- ToolHelper.getAppBuilder(builder).addProgramFiles(programFile);
- ToolHelper.runR8(
- builder.build(),
- options -> {
- options.inlinerOptions().enableInlining = false;
- options.enableRedundantFieldLoadElimination = false;
- });
- }
-
private static boolean coolInvokes(InstructionSubject instruction) {
if (!instruction.isInvokeVirtual() && !instruction.isInvokeInterface() &&
!instruction.isInvokeStatic()) {
@@ -185,14 +148,14 @@
final Backend backend;
final String keepRuleFile;
final AndroidVersion version;
- final Consumer<CodeInspector> processedInspection;
+ final ThrowingConsumer<CodeInspector, Exception> processedInspection;
private TestConfiguration(
String name,
Backend backend,
String keepRuleFile,
AndroidVersion version,
- Consumer<CodeInspector> processedInspection) {
+ ThrowingConsumer<CodeInspector, Exception> processedInspection) {
this.name = name;
this.backend = backend;
this.keepRuleFile = keepRuleFile;
@@ -206,14 +169,10 @@
Backend backend,
String keepRuleFile,
AndroidVersion version,
- Consumer<CodeInspector> processedInspection) {
+ ThrowingConsumer<CodeInspector, Exception> processedInspection) {
builder.add(new TestConfiguration(name, backend, keepRuleFile, version, processedInspection));
}
- public Path getDexPath() {
- return getBuildPath().resolve(name).resolve("classes.dex");
- }
-
public Path getJarPath() {
return getBuildPath().resolve(name + ".jar");
}
@@ -267,7 +226,7 @@
builder,
"memberrebinding3",
backend,
- null,
+ "keep-rules.txt",
TestConfiguration.AndroidVersion.PRE_N,
MemberRebindingTest::inspect3);
TestConfiguration.add(
@@ -282,23 +241,24 @@
}
@Test
- public void memberRebindingTest() throws IOException {
- Assume.assumeTrue(ToolHelper.artSupported() || ToolHelper.compareAgaintsGoldenFiles());
-
- Path out = Paths.get(temp.getRoot().getCanonicalPath());
- List<Path> processed;
- if (backend == Backend.DEX) {
- processed = Collections.singletonList(out.resolve("classes.dex"));
- } else {
- assert backend == Backend.CF;
- processed =
- Arrays.stream(out.resolve(name).toFile().listFiles(f -> f.toString().endsWith(".class")))
- .map(File::toPath)
- .collect(Collectors.toList());
- }
-
- CodeInspector inspector = new CodeInspector(processed);
- inspection.accept(inspector);
-
+ public void memberRebindingTest() throws Exception {
+ testForR8(backend)
+ .addProgramFiles(programFile)
+ .addClasspathFiles(JAR_LIBRARY)
+ .applyIf(
+ keepRuleFile != null,
+ b -> b.addKeepRuleFiles(Paths.get(ToolHelper.EXAMPLES_DIR, name, keepRuleFile)))
+ .applyIf(backend.isDex(), b -> b.setMinApi(minApiLevel))
+ .addDontObfuscate()
+ .addDontShrink()
+ .addKeepRules("-neverpropagatevalue class * { *; }")
+ .addOptionsModification(
+ options -> {
+ options.enableRedundantFieldLoadElimination = false;
+ options.inlinerOptions().enableInlining = false;
+ })
+ .enableProguardTestOptions()
+ .compile()
+ .inspect(inspection);
}
}
diff --git a/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromThrowsClauseWithNoShrinkingTest.java b/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromThrowsClauseWithNoShrinkingTest.java
index 7542952..8cece91 100644
--- a/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromThrowsClauseWithNoShrinkingTest.java
+++ b/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromThrowsClauseWithNoShrinkingTest.java
@@ -51,7 +51,7 @@
.transform());
b.enableInliningAnnotations();
b.addKeepClassAndMembersRules(DescriptorUtils.descriptorToJavaType(NEW_A_DESCRIPTOR));
- b.noTreeShaking();
+ b.addDontShrink();
});
}
diff --git a/src/test/java/com/android/tools/r8/naming/AvoidRTest.java b/src/test/java/com/android/tools/r8/naming/AvoidRTest.java
index 4a35018..38e2e99 100644
--- a/src/test/java/com/android/tools/r8/naming/AvoidRTest.java
+++ b/src/test/java/com/android/tools/r8/naming/AvoidRTest.java
@@ -61,7 +61,7 @@
builder.addProgramClassFileData(jasminBuilder.buildClasses());
Set<String> usedDescriptors = new HashSet<>();
builder
- .noTreeShaking()
+ .addDontShrink()
.addKeepRules("-classobfuscationdictionary " + dictionary)
.compile()
.inspect(
@@ -89,7 +89,7 @@
builder.addProgramClassFileData(jasminBuilder.buildClasses());
Set<String> usedNames = new HashSet<>();
builder
- .noTreeShaking()
+ .addDontShrink()
.compile()
.inspect(
codeInspector -> {
@@ -122,7 +122,7 @@
builder.addProgramClassFileData(jasminBuilder.buildClasses());
Set<String> usedDescriptors = new HashSet<>();
builder
- .noTreeShaking()
+ .addDontShrink()
.addKeepRules(keepRule)
.compile()
.inspect(
diff --git a/src/test/java/com/android/tools/r8/naming/CovariantReturnTypeTest.java b/src/test/java/com/android/tools/r8/naming/CovariantReturnTypeTest.java
index 5d11b24..9c28fa1 100644
--- a/src/test/java/com/android/tools/r8/naming/CovariantReturnTypeTest.java
+++ b/src/test/java/com/android/tools/r8/naming/CovariantReturnTypeTest.java
@@ -63,7 +63,7 @@
.addKeepMainRule("package.TestClass")
.addKeepRules("-keepconstantarguments class * { *; }")
.enableProguardTestOptions()
- .noTreeShaking()
+ .addDontShrink()
.compile()
.inspector();
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 8bca773..af2865a 100644
--- a/src/test/java/com/android/tools/r8/naming/EnumMinificationKotlinTest.java
+++ b/src/test/java/com/android/tools/r8/naming/EnumMinificationKotlinTest.java
@@ -59,7 +59,7 @@
.addKeepMainRule(MAIN_CLASS_NAME)
.addKeepClassRulesWithAllowObfuscation(ENUM_CLASS_NAME)
.allowDiagnosticWarningMessages()
- .minification(minify)
+ .addDontObfuscateUnless(minify)
.setMinApi(parameters)
.compile()
.assertAllWarningMessagesMatch(
diff --git a/src/test/java/com/android/tools/r8/naming/FieldMinificationObfuscationDictionaryDuplicateTest.java b/src/test/java/com/android/tools/r8/naming/FieldMinificationObfuscationDictionaryDuplicateTest.java
index 60208a1c..28a0ba9 100644
--- a/src/test/java/com/android/tools/r8/naming/FieldMinificationObfuscationDictionaryDuplicateTest.java
+++ b/src/test/java/com/android/tools/r8/naming/FieldMinificationObfuscationDictionaryDuplicateTest.java
@@ -45,7 +45,7 @@
FileUtils.writeTextFile(dictionary, "a");
testForR8(parameters.getBackend())
.addProgramClasses(A.class)
- .noTreeShaking()
+ .addDontShrink()
.addKeepRules("-obfuscationdictionary " + dictionary.toString())
.addKeepMainRule(A.class)
.setMinApi(parameters)
diff --git a/src/test/java/com/android/tools/r8/naming/KotlinIntrinsicsIdentifierTest.java b/src/test/java/com/android/tools/r8/naming/KotlinIntrinsicsIdentifierTest.java
index b89cc49..0c52a0e 100644
--- a/src/test/java/com/android/tools/r8/naming/KotlinIntrinsicsIdentifierTest.java
+++ b/src/test/java/com/android/tools/r8/naming/KotlinIntrinsicsIdentifierTest.java
@@ -102,7 +102,7 @@
"}")
.allowDiagnosticWarningMessages()
.enableProguardTestOptions()
- .minification(minification)
+ .addDontObfuscateUnless(minification)
.compile()
.assertAllWarningMessagesMatch(
equalTo("Resource 'META-INF/MANIFEST.MF' already exists."));
@@ -172,7 +172,7 @@
"}"))
.addNeverSingleCallerInlineAnnotations()
.allowDiagnosticWarningMessages()
- .minification(minification)
+ .addDontObfuscateUnless(minification)
.compile()
.assertAllWarningMessagesMatch(
equalTo("Resource 'META-INF/MANIFEST.MF' already exists."))
diff --git a/src/test/java/com/android/tools/r8/naming/MinificationMixedCaseAndNumbersTest.java b/src/test/java/com/android/tools/r8/naming/MinificationMixedCaseAndNumbersTest.java
index fd8b808..b6c69c2 100644
--- a/src/test/java/com/android/tools/r8/naming/MinificationMixedCaseAndNumbersTest.java
+++ b/src/test/java/com/android/tools/r8/naming/MinificationMixedCaseAndNumbersTest.java
@@ -54,7 +54,7 @@
testForR8(parameters.getBackend())
.addInnerClasses(MinificationMixedCaseAndNumbersTest.class)
.addKeepMainRule(Main.class)
- .noTreeShaking()
+ .addDontShrink()
.addKeepRules(
"-dontusemixedcaseclassnames", "-keeppackagenames com.android.tools.r8.naming")
.setMinApi(parameters)
diff --git a/src/test/java/com/android/tools/r8/naming/PackageObfuscationDictionaryDuplicateTest.java b/src/test/java/com/android/tools/r8/naming/PackageObfuscationDictionaryDuplicateTest.java
index a3936e0..a769120 100644
--- a/src/test/java/com/android/tools/r8/naming/PackageObfuscationDictionaryDuplicateTest.java
+++ b/src/test/java/com/android/tools/r8/naming/PackageObfuscationDictionaryDuplicateTest.java
@@ -46,7 +46,7 @@
FileUtils.writeTextFile(dictionary, "a");
testForR8(parameters.getBackend())
.addProgramClassesAndInnerClasses(Top.class, A.class, C.class)
- .noTreeShaking()
+ .addDontShrink()
.addKeepRules("-packageobfuscationdictionary " + dictionary.toString())
.addKeepMainRule(C.class)
.setMinApi(parameters)
diff --git a/src/test/java/com/android/tools/r8/naming/PackagePrivateOverrideSameMethodNameTest.java b/src/test/java/com/android/tools/r8/naming/PackagePrivateOverrideSameMethodNameTest.java
index a5687f1..aa311d4 100644
--- a/src/test/java/com/android/tools/r8/naming/PackagePrivateOverrideSameMethodNameTest.java
+++ b/src/test/java/com/android/tools/r8/naming/PackagePrivateOverrideSameMethodNameTest.java
@@ -63,7 +63,7 @@
.setMinApi(parameters)
.enableInliningAnnotations()
.enableNeverClassInliningAnnotations()
- .minification(minification)
+ .addDontObfuscateUnless(minification)
.run(parameters.getRuntime(), Main.class)
.apply(this::assertSuccessOutput);
if (parameters.isDexRuntime() && parameters.getDexRuntimeVersion().isDalvik()) {
diff --git a/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingAfterDevirtualizationTest.java b/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingAfterDevirtualizationTest.java
index 1219836..319c4e8 100644
--- a/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingAfterDevirtualizationTest.java
+++ b/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingAfterDevirtualizationTest.java
@@ -125,7 +125,7 @@
assertThat(inspector.clazz(LibInterfaceB.class), not(isPresent()));
testForR8(parameters.getBackend())
- .noTreeShaking()
+ .addDontShrink()
.addDontObfuscate()
.addProgramClasses(PROGRAM_CLASSES)
.addApplyMapping(libraryResult.getProguardMap())
@@ -158,7 +158,7 @@
assertThat(inspector.clazz(LibInterfaceB.class), not(isPresent()));
testForR8(parameters.getBackend())
- .noTreeShaking()
+ .addDontShrink()
.addDontObfuscate()
.addProgramClasses(PROGRAM_CLASSES)
.addApplyMapping(libraryResult.getProguardMap())
diff --git a/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingAfterHorizontalMergingFieldTest.java b/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingAfterHorizontalMergingFieldTest.java
index 22a0643..1e11234 100644
--- a/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingAfterHorizontalMergingFieldTest.java
+++ b/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingAfterHorizontalMergingFieldTest.java
@@ -102,7 +102,7 @@
!= inspector.clazz(LibraryB.class).isPresent());
testForR8(parameters.getBackend())
- .noTreeShaking()
+ .addDontShrink()
.addDontObfuscate()
.addProgramClasses(PROGRAM_CLASSES)
.addApplyMapping(libraryResult.getProguardMap())
diff --git a/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingAfterHorizontalMergingMethodTest.java b/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingAfterHorizontalMergingMethodTest.java
index 1c19a2e..38b04ec 100644
--- a/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingAfterHorizontalMergingMethodTest.java
+++ b/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingAfterHorizontalMergingMethodTest.java
@@ -110,7 +110,7 @@
!= inspector.clazz(LibraryB.class).isPresent());
testForR8(parameters.getBackend())
- .noTreeShaking()
+ .addDontShrink()
.addDontObfuscate()
.addProgramClasses(PROGRAM_CLASSES)
.addApplyMapping(libraryResult.getProguardMap())
diff --git a/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingAfterVerticalMergingFieldTest.java b/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingAfterVerticalMergingFieldTest.java
index 3b08988..139eae6 100644
--- a/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingAfterVerticalMergingFieldTest.java
+++ b/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingAfterVerticalMergingFieldTest.java
@@ -90,7 +90,7 @@
assertThat(inspector.clazz(LibrarySubclass.class), isPresent());
testForR8(parameters.getBackend())
- .noTreeShaking()
+ .addDontShrink()
.addDontObfuscate()
.addProgramClasses(PROGRAM_CLASSES)
.addApplyMapping(libraryResult.getProguardMap())
diff --git a/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingAfterVerticalMergingMethodTest.java b/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingAfterVerticalMergingMethodTest.java
index c5817de..80bc496 100644
--- a/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingAfterVerticalMergingMethodTest.java
+++ b/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingAfterVerticalMergingMethodTest.java
@@ -127,7 +127,7 @@
private static R8TestCompileResult compileProgram(Backend backend, String proguardMap)
throws CompilationFailedException {
return testForR8(getStaticTemp(), backend)
- .noTreeShaking()
+ .addDontShrink()
.addDontObfuscate()
.addProgramClasses(PROGRAM_CLASSES)
.addApplyMapping(proguardMap)
diff --git a/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingInnerClassesPreserveTest.java b/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingInnerClassesPreserveTest.java
index bf3d004..4db3cd8 100644
--- a/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingInnerClassesPreserveTest.java
+++ b/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingInnerClassesPreserveTest.java
@@ -50,7 +50,7 @@
.addApplyMapping(libraryCompileResult.getProguardMap())
.addKeepAttributes("EnclosingMethod", "InnerClasses")
.setMinApi(parameters)
- .noTreeShaking()
+ .addDontShrink()
.compile()
.addRunClasspathFiles(libraryCompileResult.writeToZip())
.run(parameters.getRuntime(), ProgramClassWithSimpleLibraryReference.class)
diff --git a/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingInterfaceInvokeTest.java b/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingInterfaceInvokeTest.java
index da531ea..c8bb6e6 100644
--- a/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingInterfaceInvokeTest.java
+++ b/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingInterfaceInvokeTest.java
@@ -74,7 +74,7 @@
.addClasspathClasses(classPathClasses)
.addProgramClasses(TestApp.class)
.addDontObfuscate()
- .noTreeShaking()
+ .addDontShrink()
.addApplyMapping(libraryResult.getProguardMap())
.setMinApi(parameters)
.compile()
diff --git a/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingInterfaceTest.java b/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingInterfaceTest.java
index ff6de98..db2a10b 100644
--- a/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingInterfaceTest.java
+++ b/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingInterfaceTest.java
@@ -86,7 +86,7 @@
.addKeepAllClassesRule()
.addApplyMapping(libraryCompileResult.getProguardMap())
.setMinApi(parameters)
- .noTreeShaking()
+ .addDontShrink()
.compile()
.addRunClasspathFiles(libraryCompileResult.writeToZip())
.run(parameters.getRuntime(), testClass)
diff --git a/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingReservationTest.java b/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingReservationTest.java
index 278caf6..8b9c128 100644
--- a/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingReservationTest.java
+++ b/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingReservationTest.java
@@ -48,7 +48,7 @@
.addKeepMainRule(Runner.class)
.addApplyMapping(R.class.getTypeName() + " -> " + R.class.getTypeName() + ":")
.setMinApi(parameters)
- .noTreeShaking()
+ .addDontShrink()
.compile()
.addRunClasspathFiles(libraryCompileResult.writeToZip())
.run(parameters.getRuntime(), Runner.class)
diff --git a/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingRotateNameClashTest.java b/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingRotateNameClashTest.java
index a0ce1ee..81ba247 100644
--- a/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingRotateNameClashTest.java
+++ b/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingRotateNameClashTest.java
@@ -45,7 +45,7 @@
.addLibraryFiles(parameters.getDefaultRuntimeLibrary())
.addProgramClasses(C.class)
.addKeepMainRule(C.class)
- .noTreeShaking()
+ .addDontShrink()
.addApplyMapping(
StringUtils.lines(
A.class.getTypeName() + " -> " + B.class.getTypeName() + ":",
diff --git a/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingSameStaticNameTest.java b/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingSameStaticNameTest.java
index 168cf97..71e101a 100644
--- a/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingSameStaticNameTest.java
+++ b/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingSameStaticNameTest.java
@@ -59,7 +59,7 @@
@Test
public void test_b131532229() throws Exception {
testForR8(parameters.getBackend())
- .noTreeShaking()
+ .addDontShrink()
.addLibraryClasses(A.class, B.class)
.addLibraryFiles(parameters.getDefaultRuntimeLibrary())
.addProgramClasses(C.class)
diff --git a/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingVirtualInvokeTest.java b/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingVirtualInvokeTest.java
index a72ff12..3e4723a 100644
--- a/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingVirtualInvokeTest.java
+++ b/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingVirtualInvokeTest.java
@@ -152,7 +152,7 @@
throws ExecutionException, IOException, CompilationFailedException {
R8TestCompileResult libraryCompileResult = compilationResults.apply(parameters);
testForR8(parameters.getBackend())
- .noTreeShaking()
+ .addDontShrink()
.addDontObfuscate()
.addProgramClasses(PROGRAM_CLASSES)
.addApplyMapping(libraryCompileResult.getProguardMap())
diff --git a/src/test/java/com/android/tools/r8/naming/applymapping/desugar/DefaultInterfaceMethodTest.java b/src/test/java/com/android/tools/r8/naming/applymapping/desugar/DefaultInterfaceMethodTest.java
index 37f2b71..3521a29 100644
--- a/src/test/java/com/android/tools/r8/naming/applymapping/desugar/DefaultInterfaceMethodTest.java
+++ b/src/test/java/com/android/tools/r8/naming/applymapping/desugar/DefaultInterfaceMethodTest.java
@@ -89,7 +89,7 @@
}
testForR8(parameters.getBackend())
- .noTreeShaking()
+ .addDontShrink()
.addProgramClasses(ProgramClass.class)
.addClasspathClasses(LibraryInterface.class)
.addApplyMapping(libraryResult.getProguardMap())
diff --git a/src/test/java/com/android/tools/r8/naming/applymapping/desugar/StaticInterfaceMethodTest.java b/src/test/java/com/android/tools/r8/naming/applymapping/desugar/StaticInterfaceMethodTest.java
index d42b085..45d0a3b 100644
--- a/src/test/java/com/android/tools/r8/naming/applymapping/desugar/StaticInterfaceMethodTest.java
+++ b/src/test/java/com/android/tools/r8/naming/applymapping/desugar/StaticInterfaceMethodTest.java
@@ -95,7 +95,7 @@
}
testForR8(parameters.getBackend())
- .noTreeShaking()
+ .addDontShrink()
.addProgramClasses(ProgramClass.class)
.addClasspathClasses(LibraryInterface.class)
.addApplyMapping(libraryResult.getProguardMap())
diff --git a/src/test/java/com/android/tools/r8/naming/applymapping/shared/NameClashTest.java b/src/test/java/com/android/tools/r8/naming/applymapping/shared/NameClashTest.java
index 4a2c79e..97f2633 100644
--- a/src/test/java/com/android/tools/r8/naming/applymapping/shared/NameClashTest.java
+++ b/src/test/java/com/android/tools/r8/naming/applymapping/shared/NameClashTest.java
@@ -120,7 +120,7 @@
.addProgramFiles(prgJarThatUsesOriginalLib)
.addKeepMainRule(MAIN)
.addKeepRules("-applymapping " + mappingFile)
- .noTreeShaking()
+ .addDontShrink()
.compile()
.run(MAIN)
.assertSuccessWithOutput(EXPECTED_OUTPUT);
@@ -133,7 +133,7 @@
.addProgramFiles(prgJarThatUsesOriginalLib)
.addKeepMainRule(MAIN)
.addKeepRules("-applymapping " + mappingFile)
- .noTreeShaking()
+ .addDontShrink()
.addDontObfuscate()
.compile()
.run(MAIN)
@@ -146,7 +146,7 @@
.addProgramFiles(prgJarThatUsesOriginalLib)
.addKeepMainRule(MAIN)
.addKeepRules("-applymapping " + mappingFile)
- .noTreeShaking()
+ .addDontShrink()
.compile()
.run(MAIN)
.assertSuccessWithOutput(EXPECTED_OUTPUT);
@@ -158,7 +158,7 @@
.addProgramFiles(prgJarThatUsesOriginalLib)
.addKeepMainRule(MAIN)
.addKeepRules("-applymapping " + mappingFile)
- .noTreeShaking()
+ .addDontShrink()
.compile()
.run(MAIN)
.assertSuccessWithOutput(EXPECTED_OUTPUT);
@@ -170,7 +170,7 @@
.addProgramFiles(prgJarThatUsesMinifiedLib)
.addKeepMainRule(MAIN)
.addKeepRules("-applymapping " + mappingFile)
- .noTreeShaking()
+ .addDontShrink()
.compile()
.run(MAIN)
.assertSuccessWithOutput(EXPECTED_OUTPUT);
diff --git a/src/test/java/com/android/tools/r8/naming/applymapping/sourcelibrary/ApplyMappingTest.java b/src/test/java/com/android/tools/r8/naming/applymapping/sourcelibrary/ApplyMappingTest.java
index e0d32a3..223b45c 100644
--- a/src/test/java/com/android/tools/r8/naming/applymapping/sourcelibrary/ApplyMappingTest.java
+++ b/src/test/java/com/android/tools/r8/naming/applymapping/sourcelibrary/ApplyMappingTest.java
@@ -144,7 +144,8 @@
testForR8(parameters.getBackend())
.addProgramFiles(NAMING001_JAR)
.addKeepRuleFiles(Paths.get(ToolHelper.EXAMPLES_DIR, "naming001", "keep-rules-106.txt"))
- .noTreeShaking()
+ .addDontOptimize()
+ .addDontShrink()
.setMinApi(parameters)
.compile()
.inspector();
diff --git a/src/test/java/com/android/tools/r8/naming/arraytypes/ArrayTypesTest.java b/src/test/java/com/android/tools/r8/naming/arraytypes/ArrayTypesTest.java
index 74d750d..fb4f3ef6 100644
--- a/src/test/java/com/android/tools/r8/naming/arraytypes/ArrayTypesTest.java
+++ b/src/test/java/com/android/tools/r8/naming/arraytypes/ArrayTypesTest.java
@@ -68,7 +68,7 @@
private void runR8Test(boolean enableMinification) throws Exception {
testForR8(parameters.getBackend())
- .minification(enableMinification)
+ .addDontObfuscateUnless(enableMinification)
.addProgramClasses(Main.class, A.class)
.addProgramClassFileData(generateTestClass())
.addKeepMainRule(Main.class)
@@ -103,7 +103,7 @@
.addKeepMainRule(Main.class)
.addKeepRules("-applymapping " + mappingFile.toAbsolutePath())
.addDontObfuscate()
- .noTreeShaking()
+ .addDontShrink()
.setMinApi(parameters)
.run(parameters.getRuntime(), Main.class)
.assertSuccessWithOutput(expectedOutput);
diff --git a/src/test/java/com/android/tools/r8/naming/b124357885/B124357885Test.java b/src/test/java/com/android/tools/r8/naming/b124357885/B124357885Test.java
index 4b3c35f..50b9bc5 100644
--- a/src/test/java/com/android/tools/r8/naming/b124357885/B124357885Test.java
+++ b/src/test/java/com/android/tools/r8/naming/b124357885/B124357885Test.java
@@ -70,7 +70,7 @@
+ " { *** test(); }"))
.enableInliningAnnotations()
.enableKeepUnusedReturnValueAnnotations()
- .minification(minification)
+ .addDontObfuscateUnless(minification)
.setMinApi(parameters)
.compile()
.inspect(
diff --git a/src/test/java/com/android/tools/r8/naming/b126592786/B126592786.java b/src/test/java/com/android/tools/r8/naming/b126592786/B126592786.java
index ce1faf5..adc08a2 100644
--- a/src/test/java/com/android/tools/r8/naming/b126592786/B126592786.java
+++ b/src/test/java/com/android/tools/r8/naming/b126592786/B126592786.java
@@ -42,7 +42,7 @@
public void runTest(boolean genericTypeLive) throws Exception {
Class<?> mainClass = genericTypeLive ? MainGenericTypeLive.class : MainGenericTypeNotLive.class;
testForR8(parameters.getBackend())
- .minification(minify)
+ .addDontObfuscateUnless(minify)
.addProgramClasses(GetClassUtil.class, A.class, GenericType.class, mainClass, Marker.class)
.addKeepMainRule(mainClass)
.addKeepRules(
diff --git a/src/test/java/com/android/tools/r8/naming/b131810441/AnonymousClassRenamingTest.java b/src/test/java/com/android/tools/r8/naming/b131810441/AnonymousClassRenamingTest.java
index bc4f872..2adbaec 100644
--- a/src/test/java/com/android/tools/r8/naming/b131810441/AnonymousClassRenamingTest.java
+++ b/src/test/java/com/android/tools/r8/naming/b131810441/AnonymousClassRenamingTest.java
@@ -50,7 +50,7 @@
.addKeepMainRule(TestMain.class)
.addKeepAttributes("InnerClasses", "EnclosingMethod")
.enableInliningAnnotations()
- .minification(enableMinification)
+ .addDontObfuscateUnless(enableMinification)
.setMinApi(parameters)
.run(parameters.getRuntime(), TestMain.class)
.assertSuccessWithOutput(EXPECTED_OUTPUT)
diff --git a/src/test/java/com/android/tools/r8/naming/b132460884/LocalClassRenamingTest.java b/src/test/java/com/android/tools/r8/naming/b132460884/LocalClassRenamingTest.java
index 8be4f0b..1c18921 100644
--- a/src/test/java/com/android/tools/r8/naming/b132460884/LocalClassRenamingTest.java
+++ b/src/test/java/com/android/tools/r8/naming/b132460884/LocalClassRenamingTest.java
@@ -83,8 +83,8 @@
.addProgramClasses(TestMain.class)
.addKeepMainRule(TestMain.class)
.addKeepAttributes("Signature", "InnerClasses")
- .noTreeShaking()
- .minification(enableMinification)
+ .addDontShrink()
+ .addDontObfuscateUnless(enableMinification)
.setMinApi(parameters)
.compile()
.inspect(
diff --git a/src/test/java/com/android/tools/r8/optimize/serviceloader/ServiceLoaderClassLoaderRewritingTest.java b/src/test/java/com/android/tools/r8/optimize/serviceloader/ServiceLoaderClassLoaderRewritingTest.java
index 1df6b3a..9695e90 100644
--- a/src/test/java/com/android/tools/r8/optimize/serviceloader/ServiceLoaderClassLoaderRewritingTest.java
+++ b/src/test/java/com/android/tools/r8/optimize/serviceloader/ServiceLoaderClassLoaderRewritingTest.java
@@ -4,25 +4,17 @@
package com.android.tools.r8.optimize.serviceloader;
-import static junit.framework.TestCase.assertNull;
-
-import com.android.tools.r8.DataEntryResource;
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.utils.StringUtils;
-import java.nio.file.Path;
import java.util.ServiceLoader;
-import java.util.zip.ZipFile;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@RunWith(Parameterized.class)
public class ServiceLoaderClassLoaderRewritingTest extends ServiceLoaderTestBase {
-
- private final TestParameters parameters;
private final String EXPECTED_OUTPUT = StringUtils.lines("Hello World!");
public interface Service {
@@ -67,30 +59,17 @@
}
public ServiceLoaderClassLoaderRewritingTest(TestParameters parameters) {
- this.parameters = parameters;
+ super(parameters);
}
@Test
public void testRewritings() throws Exception {
- Path path = temp.newFile("out.zip").toPath();
- testForR8(parameters.getBackend())
- .addInnerClasses(ServiceLoaderClassLoaderRewritingTest.class)
+ serviceLoaderTest(Service.class, ServiceImpl.class)
.addKeepMainRule(MainRunner.class)
- .setMinApi(parameters)
.enableInliningAnnotations()
- .addDataEntryResources(
- DataEntryResource.fromBytes(
- StringUtils.lines(ServiceImpl.class.getTypeName()).getBytes(),
- "META-INF/services/" + Service.class.getTypeName(),
- Origin.unknown()))
.compile()
- .writeToZip(path)
.inspect(inspector -> verifyNoServiceLoaderLoads(inspector.clazz(MainRunner.class)))
.run(parameters.getRuntime(), MainRunner.class)
.assertSuccessWithOutput(EXPECTED_OUTPUT);
-
- // Check that we have removed the service configuration from META-INF/services.
- ZipFile zip = new ZipFile(path.toFile());
- assertNull(zip.getEntry("META-INF/services/" + Service.class.getTypeName()));
}
}
diff --git a/src/test/java/com/android/tools/r8/optimize/serviceloader/ServiceLoaderConstClassFromCalleeTest.java b/src/test/java/com/android/tools/r8/optimize/serviceloader/ServiceLoaderConstClassFromCalleeTest.java
index da90e4a..a6556a2 100644
--- a/src/test/java/com/android/tools/r8/optimize/serviceloader/ServiceLoaderConstClassFromCalleeTest.java
+++ b/src/test/java/com/android/tools/r8/optimize/serviceloader/ServiceLoaderConstClassFromCalleeTest.java
@@ -5,41 +5,30 @@
import static junit.framework.TestCase.assertEquals;
-import com.android.tools.r8.DataEntryResource;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.origin.Origin;
-import com.android.tools.r8.utils.StringUtils;
import java.util.ServiceLoader;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameter;
import org.junit.runners.Parameterized.Parameters;
@RunWith(Parameterized.class)
public class ServiceLoaderConstClassFromCalleeTest extends ServiceLoaderTestBase {
- @Parameter(0)
- public TestParameters parameters;
-
@Parameters(name = "{0}")
public static TestParametersCollection data() {
return getTestParameters().withAllRuntimesAndApiLevels().build();
}
+ public ServiceLoaderConstClassFromCalleeTest(TestParameters parameters) {
+ super(parameters);
+ }
+
@Test
public void test() throws Exception {
- testForR8(parameters.getBackend())
- .addInnerClasses(getClass())
+ serviceLoaderTest(Service.class, ServiceImpl.class, ServiceImpl2.class)
.addKeepMainRule(Main.class)
- .setMinApi(parameters)
- .addDataEntryResources(
- DataEntryResource.fromBytes(
- StringUtils.lines(ServiceImpl.class.getTypeName(), ServiceImpl2.class.getTypeName())
- .getBytes(),
- "META-INF/services/" + Service.class.getTypeName(),
- Origin.unknown()))
.run(parameters.getRuntime(), Main.class)
.assertSuccessWithOutputLines("Hello, world!")
// Check that the call to ServiceLoader.load is removed.
diff --git a/src/test/java/com/android/tools/r8/optimize/serviceloader/ServiceLoaderMultipleCallsSameMethodTest.java b/src/test/java/com/android/tools/r8/optimize/serviceloader/ServiceLoaderMultipleCallsSameMethodTest.java
index 149fd33..5cd038d 100644
--- a/src/test/java/com/android/tools/r8/optimize/serviceloader/ServiceLoaderMultipleCallsSameMethodTest.java
+++ b/src/test/java/com/android/tools/r8/optimize/serviceloader/ServiceLoaderMultipleCallsSameMethodTest.java
@@ -5,31 +5,24 @@
package com.android.tools.r8.optimize.serviceloader;
import static junit.framework.TestCase.assertEquals;
-import static junit.framework.TestCase.assertNull;
import static junit.framework.TestCase.assertTrue;
import static org.junit.Assert.assertFalse;
import com.android.tools.r8.CompilationFailedException;
-import com.android.tools.r8.DataEntryResource;
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
import java.io.IOException;
-import java.nio.file.Path;
import java.util.ServiceLoader;
import java.util.concurrent.ExecutionException;
-import java.util.zip.ZipFile;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@RunWith(Parameterized.class)
public class ServiceLoaderMultipleCallsSameMethodTest extends ServiceLoaderTestBase {
-
- private final TestParameters parameters;
private final String EXPECTED_OUTPUT = StringUtils.lines("Hello World!", "Hello World!");
public interface Service {
@@ -45,13 +38,6 @@
}
}
- public static class ServiceImpl2 implements Service {
-
- @Override
- public void print() {
- System.out.println("Hello World 2!");
- }
- }
public static class MainRunner {
@@ -77,32 +63,23 @@
}
public ServiceLoaderMultipleCallsSameMethodTest(TestParameters parameters) {
- this.parameters = parameters;
+ super(parameters);
}
@Test
public void testRewritings() throws IOException, CompilationFailedException, ExecutionException {
- Path path = temp.newFile("out.zip").toPath();
- testForR8(parameters.getBackend())
- .addInnerClasses(ServiceLoaderMultipleCallsSameMethodTest.class)
+ serviceLoaderTest(Service.class, ServiceImpl.class)
.addKeepMainRule(MainRunner.class)
- .setMinApi(parameters)
.enableInliningAnnotations()
- .addDataEntryResources(
- DataEntryResource.fromBytes(
- StringUtils.lines(ServiceImpl.class.getTypeName()).getBytes(),
- "META-INF/services/" + Service.class.getTypeName(),
- Origin.unknown()))
.compile()
- .writeToZip(path)
.run(parameters.getRuntime(), MainRunner.class)
.assertSuccessWithOutput(EXPECTED_OUTPUT)
- // Check that we have actually rewritten the calls to ServiceLoader.load.
- .inspect(this::verifyNoServiceLoaderLoads)
- .inspect(this::verifyNoClassLoaders)
.inspect(
inspector -> {
- // Check the synthesize service loader method is a single shared method.
+ verifyNoServiceLoaderLoads(inspector.clazz(MainRunner.class));
+ verifyNoClassLoaders(inspector);
+ verifyServiceMetaInf(inspector, Service.class, ServiceImpl.class);
+ // Check the synthesized service loader method is a single shared method.
// Due to minification we just check there is only a single synthetic class with a
// single static method.
boolean found = false;
@@ -115,9 +92,5 @@
}
}
});
-
- // Check that we have removed the service configuration from META-INF/services.
- ZipFile zip = new ZipFile(path.toFile());
- assertNull(zip.getEntry("META-INF/services/" + Service.class.getTypeName()));
}
}
diff --git a/src/test/java/com/android/tools/r8/optimize/serviceloader/ServiceLoaderMultipleCallsTest.java b/src/test/java/com/android/tools/r8/optimize/serviceloader/ServiceLoaderMultipleCallsTest.java
index c688bd4..5b2d452 100644
--- a/src/test/java/com/android/tools/r8/optimize/serviceloader/ServiceLoaderMultipleCallsTest.java
+++ b/src/test/java/com/android/tools/r8/optimize/serviceloader/ServiceLoaderMultipleCallsTest.java
@@ -5,31 +5,24 @@
package com.android.tools.r8.optimize.serviceloader;
import static junit.framework.TestCase.assertEquals;
-import static junit.framework.TestCase.assertNull;
import static junit.framework.TestCase.assertTrue;
import static org.junit.Assert.assertFalse;
import com.android.tools.r8.CompilationFailedException;
-import com.android.tools.r8.DataEntryResource;
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
import java.io.IOException;
-import java.nio.file.Path;
import java.util.ServiceLoader;
import java.util.concurrent.ExecutionException;
-import java.util.zip.ZipFile;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@RunWith(Parameterized.class)
public class ServiceLoaderMultipleCallsTest extends ServiceLoaderTestBase {
-
- private final TestParameters parameters;
private final String EXPECTED_OUTPUT = StringUtils.lines("Hello World!", "Hello World!");
public interface Service {
@@ -45,14 +38,6 @@
}
}
- public static class ServiceImpl2 implements Service {
-
- @Override
- public void print() {
- System.out.println("Hello World 2!");
- }
- }
-
public static class MainRunner {
public static void main(String[] args) {
@@ -81,31 +66,22 @@
}
public ServiceLoaderMultipleCallsTest(TestParameters parameters) {
- this.parameters = parameters;
+ super(parameters);
}
@Test
public void testRewritings() throws IOException, CompilationFailedException, ExecutionException {
- Path path = temp.newFile("out.zip").toPath();
- testForR8(parameters.getBackend())
- .addInnerClasses(ServiceLoaderMultipleCallsTest.class)
+ serviceLoaderTest(Service.class, ServiceImpl.class)
.addKeepMainRule(MainRunner.class)
- .setMinApi(parameters)
.enableInliningAnnotations()
- .addDataEntryResources(
- DataEntryResource.fromBytes(
- StringUtils.lines(ServiceImpl.class.getTypeName()).getBytes(),
- "META-INF/services/" + Service.class.getTypeName(),
- Origin.unknown()))
.compile()
- .writeToZip(path)
.run(parameters.getRuntime(), MainRunner.class)
.assertSuccessWithOutput(EXPECTED_OUTPUT)
.inspect(
inspector -> {
// Check that we have actually rewritten the calls to ServiceLoader.load.
assertEquals(0, getServiceLoaderLoads(inspector));
- // Check the synthesize service loader method is a single shared method.
+ // Check the synthesized service loader method is a single shared method.
// Due to minification we just check there is only a single synthetic class with a
// single static method.
boolean found = false;
@@ -118,9 +94,5 @@
}
}
});
-
- // Check that we have removed the service configuration from META-INF/services.
- ZipFile zip = new ZipFile(path.toFile());
- assertNull(zip.getEntry("META-INF/services/" + Service.class.getTypeName()));
}
}
diff --git a/src/test/java/com/android/tools/r8/optimize/serviceloader/ServiceLoaderRewritingLineSeparatorTest.java b/src/test/java/com/android/tools/r8/optimize/serviceloader/ServiceLoaderRewritingLineSeparatorTest.java
index 7610575..c142442 100644
--- a/src/test/java/com/android/tools/r8/optimize/serviceloader/ServiceLoaderRewritingLineSeparatorTest.java
+++ b/src/test/java/com/android/tools/r8/optimize/serviceloader/ServiceLoaderRewritingLineSeparatorTest.java
@@ -5,7 +5,6 @@
package com.android.tools.r8.optimize.serviceloader;
import static junit.framework.TestCase.assertEquals;
-import static junit.framework.TestCase.assertNull;
import com.android.tools.r8.CompilationFailedException;
import com.android.tools.r8.DataEntryResource;
@@ -13,10 +12,8 @@
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.utils.StringUtils;
import java.io.IOException;
-import java.nio.file.Path;
import java.util.List;
import java.util.concurrent.ExecutionException;
-import java.util.zip.ZipFile;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -24,8 +21,6 @@
@RunWith(Parameterized.class)
public class ServiceLoaderRewritingLineSeparatorTest extends ServiceLoaderTestBase {
-
- private final TestParameters parameters;
private final Separator lineSeparator;
private final String EXPECTED_OUTPUT =
@@ -55,18 +50,16 @@
}
public ServiceLoaderRewritingLineSeparatorTest(TestParameters parameters, Separator separator) {
- this.parameters = parameters;
+ super(parameters);
this.lineSeparator = separator;
}
@Test
public void testRewritingWithMultipleWithLineSeparator()
throws IOException, CompilationFailedException, ExecutionException {
- Path path = temp.newFile("out.zip").toPath();
- testForR8(parameters.getBackend())
+ serviceLoaderTest(null)
.addInnerClasses(ServiceLoaderRewritingTest.class)
.addKeepMainRule(ServiceLoaderRewritingTest.MainRunner.class)
- .setMinApi(parameters)
.addDataEntryResources(
DataEntryResource.fromBytes(
StringUtils.join(
@@ -77,14 +70,17 @@
"META-INF/services/" + ServiceLoaderRewritingTest.Service.class.getTypeName(),
Origin.unknown()))
.compile()
- .writeToZip(path)
.run(parameters.getRuntime(), ServiceLoaderRewritingTest.MainRunner.class)
.assertSuccessWithOutput(EXPECTED_OUTPUT + StringUtils.lines("Hello World 2!"))
// Check that we have actually rewritten the calls to ServiceLoader.load.
- .inspect(inspector -> assertEquals(0, getServiceLoaderLoads(inspector)));
-
- // Check that we have removed the service configuration from META-INF/services.
- ZipFile zip = new ZipFile(path.toFile());
- assertNull(zip.getEntry("META-INF/services"));
+ .inspect(
+ inspector -> {
+ assertEquals(0, getServiceLoaderLoads(inspector));
+ verifyServiceMetaInf(
+ inspector,
+ ServiceLoaderRewritingTest.Service.class,
+ ServiceLoaderRewritingTest.ServiceImpl.class,
+ ServiceLoaderRewritingTest.ServiceImpl2.class);
+ });
}
}
diff --git a/src/test/java/com/android/tools/r8/optimize/serviceloader/ServiceLoaderRewritingTest.java b/src/test/java/com/android/tools/r8/optimize/serviceloader/ServiceLoaderRewritingTest.java
index 1483561..18f4a79 100644
--- a/src/test/java/com/android/tools/r8/optimize/serviceloader/ServiceLoaderRewritingTest.java
+++ b/src/test/java/com/android/tools/r8/optimize/serviceloader/ServiceLoaderRewritingTest.java
@@ -4,30 +4,23 @@
package com.android.tools.r8.optimize.serviceloader;
-import static com.android.tools.r8.TestBase.getTestParameters;
-import static junit.framework.Assert.assertNotNull;
-import static junit.framework.TestCase.assertEquals;
-import static junit.framework.TestCase.assertNull;
-import static junit.framework.TestCase.assertTrue;
+import static com.android.tools.r8.ToolHelper.DexVm.Version.V7_0_0;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
import static org.junit.Assume.assumeTrue;
import com.android.tools.r8.CompilationFailedException;
-import com.android.tools.r8.DataEntryResource;
-import com.android.tools.r8.DiagnosticsMatcher;
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.ToolHelper.DexVm.Version;
-import com.android.tools.r8.ir.optimize.ServiceLoaderRewriterDiagnostic;
-import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.BooleanUtils;
import com.android.tools.r8.utils.StringUtils;
-import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import java.io.IOException;
-import java.nio.file.Path;
+import java.util.List;
+import java.util.NoSuchElementException;
+import java.util.ServiceConfigurationError;
import java.util.ServiceLoader;
import java.util.concurrent.ExecutionException;
-import java.util.zip.ZipFile;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -35,7 +28,6 @@
@RunWith(Parameterized.class)
public class ServiceLoaderRewritingTest extends ServiceLoaderTestBase {
- private final TestParameters parameters;
private final String EXPECTED_OUTPUT =
StringUtils.lines("Hello World!", "Hello World!", "Hello World!");
@@ -60,6 +52,14 @@
}
}
+ public static class ServiceImplNoDefaultConstructor extends ServiceImpl {
+ public ServiceImplNoDefaultConstructor(int unused) {}
+ }
+
+ public static class ServiceImplNonPublicConstructor extends ServiceImpl {
+ ServiceImplNonPublicConstructor() {}
+ }
+
public static class MainRunner {
public static void main(String[] args) {
@@ -150,208 +150,183 @@
}
}
- @Parameterized.Parameters(name = "{0}")
- public static TestParametersCollection data() {
- return getTestParameters().withAllRuntimesAndApiLevels().build();
+ @Parameterized.Parameters(name = "{0}, enableRewriting: {1}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ getTestParameters().withAllRuntimesAndApiLevels().build(), BooleanUtils.values());
}
- public ServiceLoaderRewritingTest(TestParameters parameters) {
- this.parameters = parameters;
+ public ServiceLoaderRewritingTest(TestParameters parameters, boolean enableRewriting) {
+ super(parameters, enableRewriting);
+ }
+
+ private void expectRewritten(CodeInspector inspector) {
+ long found = getServiceLoaderLoads(inspector);
+ if (enableRewriting) {
+ assertEquals(0, found);
+ } else {
+ assertNotEquals(0, found);
+ }
+ }
+
+ private boolean isDexV7() {
+ // Runtime uses boot classloader rather than system classloader on this version.
+ return parameters.isDexRuntime() && parameters.getDexRuntimeVersion() == V7_0_0;
}
@Test
- public void testRewritings() throws IOException, CompilationFailedException, ExecutionException {
- Path path = temp.newFile("out.zip").toPath();
- testForR8(parameters.getBackend())
- .addInnerClasses(ServiceLoaderRewritingTest.class)
- .addKeepMainRule(MainRunner.class)
- .setMinApi(parameters)
- .addDataEntryResources(
- DataEntryResource.fromBytes(
- StringUtils.lines(ServiceImpl.class.getTypeName()).getBytes(),
- "META-INF/services/" + Service.class.getTypeName(),
- Origin.unknown()))
- .compile()
- .writeToZip(path)
- .run(parameters.getRuntime(), MainRunner.class)
- .assertSuccessWithOutput(EXPECTED_OUTPUT)
- // Check that we have actually rewritten the calls to ServiceLoader.load.
- .inspect(inspector -> assertEquals(0, getServiceLoaderLoads(inspector)));
-
- // Check that we have removed the service configuration from META-INF/services.
- ZipFile zip = new ZipFile(path.toFile());
- assertNull(zip.getEntry("META-INF/services/" + Service.class.getTypeName()));
- }
-
- @Test
- public void testRewritingWithMultiple()
+ public void testRewritingWithNoImpls()
throws IOException, CompilationFailedException, ExecutionException {
- Path path = temp.newFile("out.zip").toPath();
- testForR8(parameters.getBackend())
- .addInnerClasses(ServiceLoaderRewritingTest.class)
+ serviceLoaderTest(null)
.addKeepMainRule(MainRunner.class)
- .setMinApi(parameters)
- .addDataEntryResources(
- DataEntryResource.fromBytes(
- StringUtils.lines(ServiceImpl.class.getTypeName(), ServiceImpl2.class.getTypeName())
- .getBytes(),
- "META-INF/services/" + Service.class.getTypeName(),
- Origin.unknown()))
.compile()
- .writeToZip(path)
.run(parameters.getRuntime(), MainRunner.class)
- .assertSuccessWithOutput(EXPECTED_OUTPUT + StringUtils.lines("Hello World 2!"))
- // Check that we have actually rewritten the calls to ServiceLoader.load.
- .inspect(inspector -> assertEquals(0, getServiceLoaderLoads(inspector)));
+ .assertFailureWithErrorThatThrows(NoSuchElementException.class)
+ .inspectFailure(this::expectRewritten);
+ }
- // Check that we have removed the service configuration from META-INF/services.
- ZipFile zip = new ZipFile(path.toFile());
- assertNull(zip.getEntry("META-INF/services/" + Service.class.getTypeName()));
+ @Test
+ public void testRewritings() throws Exception {
+ serviceLoaderTest(Service.class, ServiceImpl.class)
+ .addKeepMainRule(MainRunner.class)
+ .compile()
+ .run(parameters.getRuntime(), MainRunner.class)
+ .applyIf(
+ isDexV7() && !enableRewriting,
+ runResult ->
+ runResult.assertFailureWithErrorThatThrows(ServiceConfigurationError.class),
+ runResult ->
+ runResult
+ .assertSuccessWithOutput(EXPECTED_OUTPUT)
+ .inspect(
+ inspector -> {
+ expectRewritten(inspector);
+ verifyServiceMetaInf(inspector, Service.class, ServiceImpl.class);
+ }));
+ }
+
+ @Test
+ public void testRewritingWithMultiple() throws Exception {
+ serviceLoaderTest(Service.class, ServiceImpl.class, ServiceImpl2.class)
+ .addKeepMainRule(MainRunner.class)
+ .compile()
+ .run(parameters.getRuntime(), MainRunner.class)
+ .applyIf(
+ isDexV7() && !enableRewriting,
+ runResult ->
+ runResult.assertFailureWithErrorThatThrows(ServiceConfigurationError.class),
+ runResult ->
+ runResult
+ .assertSuccessWithOutput(EXPECTED_OUTPUT + StringUtils.lines("Hello World 2!"))
+ .inspect(
+ inspector -> {
+ expectRewritten(inspector);
+ verifyServiceMetaInf(
+ inspector, Service.class, ServiceImpl.class, ServiceImpl2.class);
+ }));
}
@Test
public void testRewritingsWithCatchHandlers()
throws IOException, CompilationFailedException, ExecutionException {
- Path path = temp.newFile("out.zip").toPath();
- testForR8(parameters.getBackend())
- .addInnerClasses(ServiceLoaderRewritingTest.class)
+ serviceLoaderTest(Service.class, ServiceImpl.class, ServiceImpl2.class)
.addKeepMainRule(MainWithTryCatchRunner.class)
- .setMinApi(parameters)
- .addDataEntryResources(
- DataEntryResource.fromBytes(
- StringUtils.lines(ServiceImpl.class.getTypeName(), ServiceImpl2.class.getTypeName())
- .getBytes(),
- "META-INF/services/" + Service.class.getTypeName(),
- Origin.unknown()))
.compile()
- .writeToZip(path)
.run(parameters.getRuntime(), MainWithTryCatchRunner.class)
.assertSuccessWithOutput(StringUtils.lines("Hello World!"))
- // Check that we have actually rewritten the calls to ServiceLoader.load.
- .inspect(inspector -> assertEquals(0, getServiceLoaderLoads(inspector)));
-
- // Check that we have removed the service configuration from META-INF/services.
- ZipFile zip = new ZipFile(path.toFile());
- assertNull(zip.getEntry("META-INF/services/" + Service.class.getTypeName()));
+ .inspect(
+ inspector -> {
+ expectRewritten(inspector);
+ verifyServiceMetaInf(inspector, Service.class, ServiceImpl.class, ServiceImpl2.class);
+ });
}
@Test
public void testDoNoRewrite() throws IOException, CompilationFailedException, ExecutionException {
- Path path = temp.newFile("out.zip").toPath();
- CodeInspector inspector =
- testForR8(parameters.getBackend())
- .addInnerClasses(ServiceLoaderRewritingTest.class)
- .addKeepMainRule(OtherRunner.class)
- .setMinApi(parameters)
- .addKeepRules(
- "-whyareyounotinlining class "
- + ServiceLoader.class.getTypeName()
- + " { *** load(...); }")
- .enableExperimentalWhyAreYouNotInlining()
- .addDataEntryResources(
- DataEntryResource.fromBytes(
- StringUtils.lines(ServiceImpl.class.getTypeName()).getBytes(),
- "META-INF/services/" + Service.class.getTypeName(),
- Origin.unknown()))
- .allowDiagnosticInfoMessages()
- .compile()
- .assertAllInfosMatch(
- DiagnosticsMatcher.diagnosticType(ServiceLoaderRewriterDiagnostic.class))
- .assertAtLeastOneInfoMessage()
- .writeToZip(path)
- .run(parameters.getRuntime(), OtherRunner.class)
- .assertSuccessWithOutput(EXPECTED_OUTPUT)
- .inspector();
+ serviceLoaderTest(Service.class, ServiceImpl.class)
+ .addKeepMainRule(OtherRunner.class)
+ .allowDiagnosticInfoMessages(enableRewriting)
+ .compileWithExpectedDiagnostics(expectedDiagnostics)
+ .run(parameters.getRuntime(), OtherRunner.class)
+ .assertSuccessWithOutput(EXPECTED_OUTPUT)
+ .inspect(
+ inspector -> {
+ assertEquals(3, getServiceLoaderLoads(inspector));
+ verifyServiceMetaInf(inspector, Service.class, ServiceImpl.class);
+ });
+ }
- // Check that we have not rewritten the calls to ServiceLoader.load.
- assertEquals(3, getServiceLoaderLoads(inspector));
+ @Test
+ public void testDoNoRewriteNoDefaultConstructor()
+ throws IOException, CompilationFailedException, ExecutionException {
+ serviceLoaderTest(Service.class, ServiceImplNoDefaultConstructor.class)
+ .addKeepMainRule(MainRunner.class)
+ .allowDiagnosticInfoMessages(enableRewriting)
+ .compileWithExpectedDiagnostics(expectedDiagnostics)
+ .run(parameters.getRuntime(), MainRunner.class)
+ .assertFailureWithErrorThatThrows(ServiceConfigurationError.class);
+ }
- // Check that we have not removed the service configuration from META-INF/services.
- ZipFile zip = new ZipFile(path.toFile());
- ClassSubject serviceImpl = inspector.clazz(ServiceImpl.class);
- assertTrue(serviceImpl.isPresent());
- assertNotNull(zip.getEntry("META-INF/services/" + serviceImpl.getFinalName()));
+ @Test
+ public void testDoNoRewriteNonSubclass()
+ throws IOException, CompilationFailedException, ExecutionException {
+ serviceLoaderTest(Service.class, MainRunner.class)
+ .addKeepMainRule(MainRunner.class)
+ .allowDiagnosticInfoMessages(enableRewriting)
+ .compileWithExpectedDiagnostics(expectedDiagnostics)
+ .run(parameters.getRuntime(), MainRunner.class)
+ .assertFailureWithErrorThatThrows(ServiceConfigurationError.class);
+ }
+
+ @Test
+ public void testDoNoRewriteNonPublicConstructor()
+ throws IOException, CompilationFailedException, ExecutionException {
+ // This throws a ServiceConfigurationError only on Android 7.
+ serviceLoaderTest(Service.class, ServiceImplNonPublicConstructor.class)
+ .addKeepMainRule(MainRunner.class)
+ .allowDiagnosticInfoMessages(enableRewriting)
+ .compileWithExpectedDiagnostics(expectedDiagnostics)
+ .run(parameters.getRuntime(), MainRunner.class)
+ .applyIf(
+ !isDexV7(),
+ runResult -> runResult.assertSuccessWithOutput(EXPECTED_OUTPUT),
+ runResult ->
+ runResult.assertFailureWithErrorThatThrows(ServiceConfigurationError.class));
}
@Test
public void testDoNoRewriteWhenEscaping()
throws IOException, CompilationFailedException, ExecutionException {
- Path path = temp.newFile("out.zip").toPath();
- CodeInspector inspector =
- testForR8(parameters.getBackend())
- .addInnerClasses(ServiceLoaderRewritingTest.class)
- .addKeepMainRule(EscapingRunner.class)
- .enableInliningAnnotations()
- .setMinApi(parameters)
- .addKeepRules(
- "-whyareyounotinlining class "
- + ServiceLoader.class.getTypeName()
- + " { *** load(...); }")
- .enableExperimentalWhyAreYouNotInlining()
- .addDontObfuscate()
- .addDataEntryResources(
- DataEntryResource.fromBytes(
- StringUtils.lines(ServiceImpl.class.getTypeName()).getBytes(),
- "META-INF/services/" + Service.class.getTypeName(),
- Origin.unknown()))
- .allowDiagnosticInfoMessages()
- .compile()
- .assertAllInfosMatch(
- DiagnosticsMatcher.diagnosticType(ServiceLoaderRewriterDiagnostic.class))
- .assertAtLeastOneInfoMessage()
- .writeToZip(path)
- .run(parameters.getRuntime(), EscapingRunner.class)
- .assertSuccessWithOutput(EXPECTED_OUTPUT)
- .inspector();
-
- // Check that we have not rewritten the calls to ServiceLoader.load.
- assertEquals(3, getServiceLoaderLoads(inspector));
-
- // Check that we have not removed the service configuration from META-INF/services.
- ZipFile zip = new ZipFile(path.toFile());
- ClassSubject serviceImpl = inspector.clazz(ServiceImpl.class);
- assertTrue(serviceImpl.isPresent());
- assertNotNull(zip.getEntry("META-INF/services/" + serviceImpl.getFinalName()));
+ serviceLoaderTest(Service.class, ServiceImpl.class)
+ .addKeepMainRule(EscapingRunner.class)
+ .enableInliningAnnotations()
+ .addDontObfuscate()
+ .allowDiagnosticInfoMessages(enableRewriting)
+ .compileWithExpectedDiagnostics(expectedDiagnostics)
+ .run(parameters.getRuntime(), EscapingRunner.class)
+ .assertSuccessWithOutput(EXPECTED_OUTPUT)
+ .inspect(
+ inspector -> {
+ assertEquals(3, getServiceLoaderLoads(inspector));
+ verifyServiceMetaInf(inspector, Service.class, ServiceImpl.class);
+ });
}
@Test
public void testDoNoRewriteWhenClassLoaderIsPhi()
throws IOException, CompilationFailedException, ExecutionException {
- Path path = temp.newFile("out.zip").toPath();
- CodeInspector inspector =
- testForR8(parameters.getBackend())
- .addInnerClasses(ServiceLoaderRewritingTest.class)
- .addKeepMainRule(LoadWhereClassLoaderIsPhi.class)
- .enableInliningAnnotations()
- .setMinApi(parameters)
- .addKeepRules(
- "-whyareyounotinlining class "
- + ServiceLoader.class.getTypeName()
- + " { *** load(...); }")
- .enableExperimentalWhyAreYouNotInlining()
- .addDataEntryResources(
- DataEntryResource.fromBytes(
- StringUtils.lines(ServiceImpl.class.getTypeName()).getBytes(),
- "META-INF/services/" + Service.class.getTypeName(),
- Origin.unknown()))
- .allowDiagnosticInfoMessages()
- .compile()
- .assertAllInfosMatch(
- DiagnosticsMatcher.diagnosticType(ServiceLoaderRewriterDiagnostic.class))
- .assertAtLeastOneInfoMessage()
- .writeToZip(path)
- .run(parameters.getRuntime(), LoadWhereClassLoaderIsPhi.class)
- .assertSuccessWithOutputLines("Hello World!")
- .inspector();
-
- // Check that we have not rewritten the calls to ServiceLoader.load.
- assertEquals(1, getServiceLoaderLoads(inspector));
-
- // Check that we have not removed the service configuration from META-INF/services.
- ZipFile zip = new ZipFile(path.toFile());
- ClassSubject serviceImpl = inspector.clazz(ServiceImpl.class);
- assertTrue(serviceImpl.isPresent());
- assertNotNull(zip.getEntry("META-INF/services/" + serviceImpl.getFinalName()));
+ serviceLoaderTest(Service.class, ServiceImpl.class)
+ .addKeepMainRule(LoadWhereClassLoaderIsPhi.class)
+ .enableInliningAnnotations()
+ .allowDiagnosticInfoMessages(enableRewriting)
+ .compileWithExpectedDiagnostics(expectedDiagnostics)
+ .run(parameters.getRuntime(), LoadWhereClassLoaderIsPhi.class)
+ .assertSuccessWithOutputLines("Hello World!")
+ .inspect(
+ inspector -> {
+ assertEquals(1, getServiceLoaderLoads(inspector));
+ verifyServiceMetaInf(inspector, Service.class, ServiceImpl.class);
+ });
}
@Test
@@ -361,41 +336,18 @@
// https://android-review.googlesource.com/c/platform/libcore/+/273135
assumeTrue(
parameters.getRuntime().isCf()
- || !parameters.getRuntime().asDex().getVm().getVersion().equals(Version.V7_0_0));
- Path path = temp.newFile("out.zip").toPath();
- CodeInspector inspector =
- testForR8(parameters.getBackend())
- .addInnerClasses(ServiceLoaderRewritingTest.class)
- .addKeepMainRule(MainRunner.class)
- .addKeepClassRules(Service.class)
- .setMinApi(parameters)
- .addKeepRules(
- "-whyareyounotinlining class "
- + ServiceLoader.class.getTypeName()
- + " { *** load(...); }")
- .enableExperimentalWhyAreYouNotInlining()
- .addDataEntryResources(
- DataEntryResource.fromBytes(
- StringUtils.lines(ServiceImpl.class.getTypeName()).getBytes(),
- "META-INF/services/" + Service.class.getTypeName(),
- Origin.unknown()))
- .allowDiagnosticInfoMessages()
- .compile()
- .assertAllInfosMatch(
- DiagnosticsMatcher.diagnosticType(ServiceLoaderRewriterDiagnostic.class))
- .assertAtLeastOneInfoMessage()
- .writeToZip(path)
- .run(parameters.getRuntime(), MainRunner.class)
- .assertSuccessWithOutput(EXPECTED_OUTPUT)
- .inspector();
-
- // Check that we have not rewritten the calls to ServiceLoader.load.
- assertEquals(3, getServiceLoaderLoads(inspector));
-
- // Check that we have not removed the service configuration from META-INF/services.
- ZipFile zip = new ZipFile(path.toFile());
- ClassSubject service = inspector.clazz(Service.class);
- assertTrue(service.isPresent());
- assertNotNull(zip.getEntry("META-INF/services/" + service.getFinalName()));
+ || !parameters.getRuntime().asDex().getVm().getVersion().equals(V7_0_0));
+ serviceLoaderTest(Service.class, ServiceImpl.class)
+ .addKeepMainRule(MainRunner.class)
+ .addKeepClassRules(Service.class)
+ .allowDiagnosticInfoMessages(enableRewriting)
+ .compileWithExpectedDiagnostics(expectedDiagnostics)
+ .run(parameters.getRuntime(), MainRunner.class)
+ .assertSuccessWithOutput(EXPECTED_OUTPUT)
+ .inspect(
+ inspector -> {
+ assertEquals(3, getServiceLoaderLoads(inspector));
+ verifyServiceMetaInf(inspector, Service.class, ServiceImpl.class);
+ });
}
}
diff --git a/src/test/java/com/android/tools/r8/optimize/serviceloader/ServiceLoaderRewritingWithAssumeNoSideEffectsTest.java b/src/test/java/com/android/tools/r8/optimize/serviceloader/ServiceLoaderRewritingWithAssumeNoSideEffectsTest.java
new file mode 100644
index 0000000..c57f579
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/optimize/serviceloader/ServiceLoaderRewritingWithAssumeNoSideEffectsTest.java
@@ -0,0 +1,278 @@
+// Copyright (c) 2024, 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.optimize.serviceloader;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.R8FullTestBuilder;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.io.IOException;
+import java.util.Iterator;
+import java.util.ServiceLoader;
+import java.util.concurrent.ExecutionException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class ServiceLoaderRewritingWithAssumeNoSideEffectsTest extends ServiceLoaderTestBase {
+ private final String EXPECTED_OUTPUT = StringUtils.lines("Hello World!");
+
+ public interface Service {
+
+ void print();
+ }
+
+ public static class ServiceImpl implements Service {
+
+ @Override
+ public void print() {
+ System.out.println("Hello World!");
+ }
+ }
+
+ public static class ServiceImpl2 implements Service {
+
+ @Override
+ public void print() {
+ System.out.println("Hello World 2!");
+ }
+ }
+
+ public static class MainRunner {
+ public static void main(String[] args) {
+ ServiceLoader.load(Service.class, Service.class.getClassLoader()).iterator().next().print();
+ }
+ }
+
+ public static class HasNextRunner {
+ public static void main(String[] args) {
+ Iterator<Service> iterator =
+ ServiceLoader.load(Service.class, Service.class.getClassLoader()).iterator();
+ if (iterator.hasNext()) {
+ iterator.next().print();
+ }
+ }
+ }
+
+ public static class MultipleCallsRunner {
+ public static void main(String[] args) {
+ Iterator<Service> iterator =
+ ServiceLoader.load(Service.class, Service.class.getClassLoader()).iterator();
+ if (iterator.hasNext() && iterator.hasNext()) {
+ iterator.next().print();
+ }
+ }
+ }
+
+ public static class HasNextAfterNextRunner {
+ public static void main(String[] args) {
+ Iterator<Service> iterator =
+ ServiceLoader.load(Service.class, Service.class.getClassLoader()).iterator();
+
+ iterator.next().print();
+ if (iterator.hasNext()) {
+ System.out.println("not reached");
+ }
+ }
+ }
+
+ public static class HasNextAfterNextWithTryCatch {
+ public static void main(String[] args) {
+ try {
+ Iterator<Service> iterator =
+ ServiceLoader.load(Service.class, Service.class.getClassLoader()).iterator();
+
+ iterator.next().print();
+ if (iterator.hasNext()) {
+ System.out.println("not reached");
+ }
+ } catch (Throwable t) {
+ System.out.println("unreachable");
+ }
+ }
+ }
+
+ public static class LoopingRunner {
+ public static void main(String[] args) {
+ Iterator<Service> iterator =
+ ServiceLoader.load(Service.class, Service.class.getClassLoader()).iterator();
+ // Loop without a call to hasNext().
+ for (int i = System.currentTimeMillis() > 0 ? 0 : 1; i < 1; ++i) {
+ iterator.next().print();
+ }
+ }
+ }
+
+ public static class PhiRunner {
+ public static void main(String[] args) {
+ Iterator<Service> iterator =
+ System.currentTimeMillis() > 0
+ ? ServiceLoader.load(Service.class, Service.class.getClassLoader()).iterator()
+ : null;
+ iterator.next().print();
+ }
+ }
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public ServiceLoaderRewritingWithAssumeNoSideEffectsTest(TestParameters parameters) {
+ super(parameters);
+ }
+
+ private static void assertIteratorPresent(CodeInspector inspector, boolean expected) {
+ assertEquals(0, getServiceLoaderLoads(inspector));
+
+ boolean hasIteratorCall =
+ inspector
+ .streamInstructions()
+ .anyMatch(ins -> ins.isInvoke() && ins.getMethod().name.toString().equals("iterator"));
+ assertEquals(expected, hasIteratorCall);
+ }
+
+ private R8FullTestBuilder doTest(Class<?>... implClasses) throws IOException {
+ return serviceLoaderTest(Service.class, implClasses)
+ .addKeepRules("-assumenosideeffects class java.util.ServiceLoader { *** load(...); }");
+ }
+
+ @Test
+ public void testRewritingWithNoImplsHasNext()
+ throws IOException, CompilationFailedException, ExecutionException {
+ doTest()
+ .addKeepMainRule(HasNextRunner.class)
+ .compile()
+ .run(parameters.getRuntime(), HasNextRunner.class)
+ .assertSuccessWithOutput("")
+ .inspect(inspector -> assertIteratorPresent(inspector, false));
+ }
+
+ @Test
+ public void testRewritingWithMultiple()
+ throws IOException, CompilationFailedException, ExecutionException {
+ doTest(ServiceImpl.class, ServiceImpl2.class)
+ .addKeepMainRule(MainRunner.class)
+ .compile()
+ .run(parameters.getRuntime(), MainRunner.class)
+ .assertSuccessWithOutput(EXPECTED_OUTPUT)
+ .inspect(
+ inspector -> {
+ assertIteratorPresent(inspector, false);
+ // ServiceImpl2 gets removed since next() is called only once.
+ verifyServiceMetaInf(inspector, Service.class, ServiceImpl.class);
+ });
+ }
+
+ @Test
+ public void testRewritingWithHasNext()
+ throws IOException, CompilationFailedException, ExecutionException {
+ doTest(ServiceImpl.class)
+ .addKeepMainRule(HasNextRunner.class)
+ .compile()
+ .run(parameters.getRuntime(), HasNextRunner.class)
+ .assertSuccessWithOutput(EXPECTED_OUTPUT)
+ .inspect(
+ inspector -> {
+ assertIteratorPresent(inspector, false);
+ verifyServiceMetaInf(inspector, Service.class, ServiceImpl.class);
+ });
+ }
+
+ @Test
+ public void testDoNotRewriteMultipleCalls()
+ throws IOException, CompilationFailedException, ExecutionException {
+ doTest(ServiceImpl.class)
+ .addKeepMainRule(MultipleCallsRunner.class)
+ .compile()
+ .run(parameters.getRuntime(), MultipleCallsRunner.class)
+ .assertSuccessWithOutput(EXPECTED_OUTPUT)
+ .inspect(
+ inspector -> {
+ assertIteratorPresent(inspector, true);
+ verifyServiceMetaInf(inspector, Service.class, ServiceImpl.class);
+ });
+ }
+
+ @Test
+ public void testDoNotRewriteHasNextAfterNext()
+ throws IOException, CompilationFailedException, ExecutionException {
+ doTest(ServiceImpl.class)
+ .addKeepMainRule(HasNextAfterNextRunner.class)
+ .compile()
+ .run(parameters.getRuntime(), HasNextAfterNextRunner.class)
+ .assertSuccessWithOutput(EXPECTED_OUTPUT)
+ .inspect(
+ inspector -> {
+ assertIteratorPresent(inspector, true);
+ verifyServiceMetaInf(inspector, Service.class, ServiceImpl.class);
+ });
+ }
+
+ @Test
+ public void testDoNotRewriteHasNextAfterNextWithTryCatch()
+ throws IOException, CompilationFailedException, ExecutionException {
+ doTest(ServiceImpl.class)
+ .addKeepMainRule(HasNextAfterNextWithTryCatch.class)
+ .compile()
+ .run(parameters.getRuntime(), HasNextAfterNextWithTryCatch.class)
+ .assertSuccessWithOutput(EXPECTED_OUTPUT)
+ .inspect(
+ inspector -> {
+ assertIteratorPresent(inspector, true);
+ verifyServiceMetaInf(inspector, Service.class, ServiceImpl.class);
+ });
+ }
+
+ @Test
+ public void testDoNotRewriteHasNextAfterNextBlocks()
+ throws IOException, CompilationFailedException, ExecutionException {
+ doTest(ServiceImpl.class)
+ .addKeepMainRule(HasNextAfterNextRunner.class)
+ .compile()
+ .run(parameters.getRuntime(), HasNextAfterNextRunner.class)
+ .assertSuccessWithOutput(EXPECTED_OUTPUT)
+ .inspect(
+ inspector -> {
+ assertIteratorPresent(inspector, true);
+ verifyServiceMetaInf(inspector, Service.class, ServiceImpl.class);
+ });
+ }
+
+ @Test
+ public void testDoNotRewriteLoop()
+ throws IOException, CompilationFailedException, ExecutionException {
+ doTest(ServiceImpl.class)
+ .addKeepMainRule(LoopingRunner.class)
+ .compile()
+ .run(parameters.getRuntime(), LoopingRunner.class)
+ .assertSuccessWithOutput(EXPECTED_OUTPUT)
+ .inspect(
+ inspector -> {
+ assertIteratorPresent(inspector, true);
+ verifyServiceMetaInf(inspector, Service.class, ServiceImpl.class);
+ });
+ }
+
+ @Test
+ public void testDoNotRewritePhiUser()
+ throws IOException, CompilationFailedException, ExecutionException {
+ doTest(ServiceImpl.class)
+ .addKeepMainRule(PhiRunner.class)
+ .compile()
+ .run(parameters.getRuntime(), PhiRunner.class)
+ .assertSuccessWithOutput(EXPECTED_OUTPUT)
+ .inspect(
+ inspector -> {
+ assertIteratorPresent(inspector, true);
+ verifyServiceMetaInf(inspector, Service.class, ServiceImpl.class);
+ });
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/optimize/serviceloader/ServiceLoaderTestBase.java b/src/test/java/com/android/tools/r8/optimize/serviceloader/ServiceLoaderTestBase.java
index 21276a2..809f782 100644
--- a/src/test/java/com/android/tools/r8/optimize/serviceloader/ServiceLoaderTestBase.java
+++ b/src/test/java/com/android/tools/r8/optimize/serviceloader/ServiceLoaderTestBase.java
@@ -4,38 +4,65 @@
package com.android.tools.r8.optimize.serviceloader;
import static com.android.tools.r8.utils.codeinspector.CodeMatchers.invokesMethodWithName;
-import static junit.framework.TestCase.assertTrue;
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.DataEntryResource;
+import com.android.tools.r8.DiagnosticsMatcher;
+import com.android.tools.r8.R8FullTestBuilder;
import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestCompilerBuilder.DiagnosticsConsumer;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.graph.AppServices;
+import com.android.tools.r8.ir.optimize.ServiceLoaderRewriterDiagnostic;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.DataResourceConsumerForTesting;
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.ImmutableMap;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
public class ServiceLoaderTestBase extends TestBase {
+ private static final DiagnosticsConsumer<CompilationFailedException> REWRITER_DIAGNOSTICS =
+ diagnostics ->
+ diagnostics
+ .assertOnlyInfos()
+ .assertAllInfosMatch(
+ DiagnosticsMatcher.diagnosticType(ServiceLoaderRewriterDiagnostic.class));
+
+ protected final TestParameters parameters;
+ protected final boolean enableRewriting;
+ protected DataResourceConsumerForTesting dataResourceConsumer;
+ protected final DiagnosticsConsumer<CompilationFailedException> expectedDiagnostics;
+
+ public ServiceLoaderTestBase(TestParameters parameters) {
+ this(parameters, true);
+ }
+
+ public ServiceLoaderTestBase(TestParameters parameters, boolean enableRewriting) {
+ this.parameters = parameters;
+ this.enableRewriting = enableRewriting;
+ if (enableRewriting) {
+ expectedDiagnostics = REWRITER_DIAGNOSTICS;
+ } else {
+ expectedDiagnostics = diagnostics -> diagnostics.assertNoInfos();
+ }
+ }
public static long getServiceLoaderLoads(CodeInspector inspector) {
- return inspector.allClasses().stream()
- .mapToLong(ServiceLoaderTestBase::getServiceLoaderLoads)
- .reduce(0, Long::sum);
- }
-
- public static long getServiceLoaderLoads(CodeInspector inspector, Class<?> clazz) {
- return getServiceLoaderLoads(inspector.clazz(clazz));
- }
-
- public static long getServiceLoaderLoads(ClassSubject classSubject) {
- assertTrue(classSubject.isPresent());
- return classSubject.allMethods(MethodSubject::hasCode).stream()
- .mapToLong(
- method ->
- method
- .streamInstructions()
- .filter(ServiceLoaderTestBase::isServiceLoaderLoad)
- .count())
- .sum();
+ return inspector
+ .streamInstructions()
+ .filter(ServiceLoaderTestBase::isServiceLoaderLoad)
+ .count();
}
private static boolean isServiceLoaderLoad(InstructionSubject instruction) {
@@ -43,22 +70,89 @@
&& instruction.getMethod().qualifiedName().contains("ServiceLoader.load");
}
- public void verifyNoClassLoaders(CodeInspector inspector) {
- inspector.allClasses().forEach(this::verifyNoClassLoaders);
+ public static void verifyNoClassLoaders(CodeInspector inspector) {
+ inspector.allClasses().forEach(ServiceLoaderTestBase::verifyNoClassLoaders);
}
- public void verifyNoClassLoaders(ClassSubject classSubject) {
+ public static void verifyNoClassLoaders(ClassSubject classSubject) {
assertTrue(classSubject.isPresent());
classSubject.forAllMethods(
method -> assertThat(method, not(invokesMethodWithName("getClassLoader"))));
}
- public void verifyNoServiceLoaderLoads(CodeInspector inspector) {
- inspector.allClasses().forEach(this::verifyNoServiceLoaderLoads);
- }
-
- public void verifyNoServiceLoaderLoads(ClassSubject classSubject) {
+ public static void verifyNoServiceLoaderLoads(ClassSubject classSubject) {
assertTrue(classSubject.isPresent());
classSubject.forAllMethods(method -> assertThat(method, not(invokesMethodWithName("load"))));
}
+
+ public Map<String, List<String>> getServiceMappings() {
+ return dataResourceConsumer.getAll().entrySet().stream()
+ .filter(e -> e.getKey().startsWith(AppServices.SERVICE_DIRECTORY_NAME))
+ .collect(
+ Collectors.toMap(
+ e -> e.getKey().substring(AppServices.SERVICE_DIRECTORY_NAME.length()),
+ e -> e.getValue()));
+ }
+
+ public void verifyServiceMetaInf(
+ CodeInspector inspector, Class<?> serviceClass, Class<?> serviceImplClass) {
+ // Account for renaming, and for the impl to be merged with the interface.
+ String finalServiceName = inspector.clazz(serviceClass).getFinalName();
+ String finalImplName = inspector.clazz(serviceImplClass).getFinalName();
+ if (finalServiceName == null) {
+ finalServiceName = finalImplName;
+ }
+ Map<String, List<String>> actual = getServiceMappings();
+ Map<String, List<String>> expected =
+ ImmutableMap.of(finalServiceName, Collections.singletonList(finalImplName));
+ assertEquals(expected, actual);
+ }
+
+ public void verifyServiceMetaInf(
+ CodeInspector inspector,
+ Class<?> serviceClass,
+ Class<?> serviceImplClass1,
+ Class<?> serviceImplClass2) {
+ // Account for renaming. No class merging should happen.
+ String finalServiceName = inspector.clazz(serviceClass).getFinalName();
+ String finalImplName1 = inspector.clazz(serviceImplClass1).getFinalName();
+ String finalImplName2 = inspector.clazz(serviceImplClass2).getFinalName();
+ Map<String, List<String>> actual = getServiceMappings();
+ Map<String, List<String>> expected =
+ ImmutableMap.of(finalServiceName, Arrays.asList(finalImplName1, finalImplName2));
+ assertEquals(expected, actual);
+ }
+
+ protected R8FullTestBuilder serviceLoaderTest(Class<?> serviceClass, Class<?>... implClasses)
+ throws IOException {
+ return serviceLoaderTestNoClasses(serviceClass, implClasses).addInnerClasses(getClass());
+ }
+
+ protected R8FullTestBuilder serviceLoaderTestNoClasses(
+ Class<?> serviceClass, Class<?>... implClasses) throws IOException {
+ R8FullTestBuilder ret =
+ testForR8(parameters.getBackend())
+ .setMinApi(parameters)
+ .addOptionsModification(
+ o -> {
+ dataResourceConsumer = new DataResourceConsumerForTesting(o.dataResourceConsumer);
+ o.dataResourceConsumer = dataResourceConsumer;
+ o.enableServiceLoaderRewriting = enableRewriting;
+ })
+ // Enables ServiceLoader optimization failure diagnostics.
+ .enableExperimentalWhyAreYouNotInlining()
+ .addKeepRules("-whyareyounotinlining class java.util.ServiceLoader { *** load(...); }");
+ if (implClasses.length > 0) {
+ String implLines =
+ Arrays.stream(implClasses)
+ .map(c -> c.getTypeName() + "\n")
+ .collect(Collectors.joining(""));
+ ret.addDataEntryResources(
+ DataEntryResource.fromBytes(
+ implLines.getBytes(),
+ AppServices.SERVICE_DIRECTORY_NAME + serviceClass.getTypeName(),
+ Origin.unknown()));
+ }
+ return ret;
+ }
}
diff --git a/src/test/java/com/android/tools/r8/optimize/serviceloader/ServiceWithFeatureNullClassLoaderTest.java b/src/test/java/com/android/tools/r8/optimize/serviceloader/ServiceWithFeatureNullClassLoaderTest.java
new file mode 100644
index 0000000..a815899
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/optimize/serviceloader/ServiceWithFeatureNullClassLoaderTest.java
@@ -0,0 +1,80 @@
+// Copyright (c) 2024, 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.optimize.serviceloader;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.R8TestCompileResult;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.util.ServiceLoader;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class ServiceWithFeatureNullClassLoaderTest extends ServiceLoaderTestBase {
+
+ public interface Service {
+
+ void print();
+ }
+
+ public static class ServiceImpl implements Service {
+
+ @Override
+ public void print() {
+ System.out.println("Hello World!");
+ }
+ }
+
+ public static class MainRunner {
+
+ public static void main(String[] args) {
+ run1();
+ }
+
+ @NeverInline
+ public static void run1() {
+ for (Service x : ServiceLoader.load(Service.class, null)) {
+ x.print();
+ }
+ }
+
+ @NeverInline
+ public static void checkNotNull(ClassLoader classLoader) {
+ if (classLoader == null) {
+ throw new NullPointerException("ClassLoader should not be null");
+ }
+ }
+ }
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withDexRuntimes().withAllApiLevels().build();
+ }
+
+ public ServiceWithFeatureNullClassLoaderTest(TestParameters parameters) {
+ super(parameters);
+ }
+
+ @Test
+ public void testNoRewritings() throws Exception {
+ R8TestCompileResult result =
+ serviceLoaderTestNoClasses(Service.class, ServiceImpl.class)
+ .addFeatureSplit(MainRunner.class, Service.class, ServiceImpl.class)
+ .enableInliningAnnotations()
+ .addKeepMainRule(MainRunner.class)
+ .allowDiagnosticInfoMessages()
+ .compileWithExpectedDiagnostics(expectedDiagnostics);
+
+ CodeInspector inspector = result.featureInspector();
+ assertEquals(getServiceLoaderLoads(inspector), 1);
+ // Check that we have not removed the service configuration from META-INF/services.
+ verifyServiceMetaInf(inspector, Service.class, ServiceImpl.class);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/optimize/serviceloader/ServiceWithFeatureTest.java b/src/test/java/com/android/tools/r8/optimize/serviceloader/ServiceWithFeatureTest.java
new file mode 100644
index 0000000..34237e1
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/optimize/serviceloader/ServiceWithFeatureTest.java
@@ -0,0 +1,77 @@
+// Copyright (c) 2024, 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.optimize.serviceloader;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import java.util.ServiceLoader;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class ServiceWithFeatureTest extends ServiceLoaderTestBase {
+
+ public interface Service {
+
+ void print();
+ }
+
+ public static class ServiceImpl implements Service {
+
+ @Override
+ public void print() {
+ System.out.println("Hello World!");
+ }
+ }
+
+ public static class MainRunner {
+
+ public static void main(String[] args) {
+ run1();
+ }
+
+ @NeverInline
+ public static void run1() {
+ ClassLoader classLoader = Service.class.getClassLoader();
+ checkNotNull(classLoader);
+ for (Service x : ServiceLoader.load(Service.class, classLoader)) {
+ x.print();
+ }
+ }
+
+ @NeverInline
+ public static void checkNotNull(ClassLoader classLoader) {
+ if (classLoader == null) {
+ throw new NullPointerException("ClassLoader should not be null");
+ }
+ }
+ }
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withDexRuntimes().withAllApiLevels().build();
+ }
+
+ public ServiceWithFeatureTest(TestParameters parameters) {
+ super(parameters);
+ }
+
+ @Test
+ public void testRewritings() throws Exception {
+ serviceLoaderTestNoClasses(Service.class, ServiceImpl.class)
+ .addFeatureSplit(MainRunner.class, Service.class, ServiceImpl.class)
+ .enableInliningAnnotations()
+ .addKeepMainRule(MainRunner.class)
+ .compile()
+ .inspect(
+ inspector -> {},
+ inspector -> {
+ verifyNoServiceLoaderLoads(inspector.clazz(MainRunner.class));
+ verifyServiceMetaInf(inspector, Service.class, ServiceImpl.class);
+ });
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/regress/b165825758/Regress165825758Test.java b/src/test/java/com/android/tools/r8/regress/b165825758/Regress165825758Test.java
index 8f93035..4034d59 100644
--- a/src/test/java/com/android/tools/r8/regress/b165825758/Regress165825758Test.java
+++ b/src/test/java/com/android/tools/r8/regress/b165825758/Regress165825758Test.java
@@ -26,23 +26,22 @@
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
@RunWith(Parameterized.class)
public class Regress165825758Test extends TestBase {
- static final String EXPECTED = StringUtils.lines("Hello, world");
+ private static final String EXPECTED = StringUtils.lines("Hello, world");
- private final TestParameters parameters;
+ @Parameter(0)
+ public TestParameters parameters;
- @Parameterized.Parameters(name = "{0}")
+ @Parameters(name = "{0}")
public static TestParametersCollection data() {
return getTestParameters().withAllRuntimesAndApiLevels().build();
}
- public Regress165825758Test(TestParameters parameters) {
- this.parameters = parameters;
- }
-
@Test
public void testReference() throws Exception {
testForRuntime(parameters)
diff --git a/src/test/java/com/android/tools/r8/regress/b78493232/Regress78493232_WithPhi.java b/src/test/java/com/android/tools/r8/regress/b78493232/Regress78493232_WithPhi.java
index d052107..07b52af 100644
--- a/src/test/java/com/android/tools/r8/regress/b78493232/Regress78493232_WithPhi.java
+++ b/src/test/java/com/android/tools/r8/regress/b78493232/Regress78493232_WithPhi.java
@@ -10,6 +10,7 @@
import com.android.tools.r8.AsmTestBase;
import com.android.tools.r8.D8TestRunResult;
+import com.android.tools.r8.R8FullTestBuilder;
import com.android.tools.r8.R8TestRunResult;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
@@ -88,7 +89,7 @@
.addProgramClasses(CLASSES)
.addProgramClassFileData(CLASS_BYTES)
.allowDiagnosticWarningMessages()
- .treeShaking(treeShake)
+ .applyIf(!treeShake, R8FullTestBuilder::addDontShrink)
.addDontObfuscate()
.setMinApi(parameters)
.addOptionsModification(options -> options.testing.readInputStackMaps = false)
diff --git a/src/test/java/com/android/tools/r8/retrace/RetraceLambdaTest.java b/src/test/java/com/android/tools/r8/retrace/RetraceLambdaTest.java
index eb871a6..e78aece 100644
--- a/src/test/java/com/android/tools/r8/retrace/RetraceLambdaTest.java
+++ b/src/test/java/com/android/tools/r8/retrace/RetraceLambdaTest.java
@@ -98,7 +98,7 @@
.addInnerClasses(getClass())
.addKeepMainRule(Main.class)
.addKeepPackageNamesRule(getClass().getPackage())
- .noTreeShaking()
+ .addDontShrink()
.addDontOptimize()
.addKeepAttributeSourceFile()
.addKeepAttributeLineNumberTable()
diff --git a/src/test/java/com/android/tools/r8/retrace/StackTraceWithPcAndJumboStringTestRunner.java b/src/test/java/com/android/tools/r8/retrace/StackTraceWithPcAndJumboStringTestRunner.java
index 25e7cc8..655a095 100644
--- a/src/test/java/com/android/tools/r8/retrace/StackTraceWithPcAndJumboStringTestRunner.java
+++ b/src/test/java/com/android/tools/r8/retrace/StackTraceWithPcAndJumboStringTestRunner.java
@@ -46,7 +46,7 @@
public void testR8() throws Exception {
testForR8(parameters.getBackend())
.addProgramClasses(getTestClass())
- .noTreeShaking()
+ .addDontShrink()
.addKeepAttributeLineNumberTable()
.addKeepMainRule(getTestClass())
.setMinApi(parameters)
diff --git a/src/test/java/com/android/tools/r8/rewrite/ScriptEngineTest.java b/src/test/java/com/android/tools/r8/rewrite/ScriptEngineTest.java
index 045cc76..e954b79 100644
--- a/src/test/java/com/android/tools/r8/rewrite/ScriptEngineTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/ScriptEngineTest.java
@@ -89,7 +89,7 @@
// TODO(b/136633154): This should work both with and without -dontobfuscate.
.addDontObfuscate()
// TODO(b/136633154): This should work both with and without -dontshrink.
- .noTreeShaking()
+ .addDontShrink()
.compile()
.applyIf(
parameters.isDexRuntime(),
diff --git a/src/test/java/com/android/tools/r8/rewrite/assertions/RemoveAssertionsTest.java b/src/test/java/com/android/tools/r8/rewrite/assertions/RemoveAssertionsTest.java
index e8bfc88..04a1132 100644
--- a/src/test/java/com/android/tools/r8/rewrite/assertions/RemoveAssertionsTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/assertions/RemoveAssertionsTest.java
@@ -197,7 +197,7 @@
return testForR8(getStaticTemp(), Backend.CF)
.addProgramClasses(ClassWithAssertions.class)
.debug()
- .noTreeShaking()
+ .addDontShrink()
.addDontObfuscate()
.addAssertionsConfiguration(transformation)
.compile();
@@ -221,7 +221,7 @@
.addProgramClasses(ChromuimAssertionHookMock.class)
.setMinApi(AndroidApiLevel.B)
.debug()
- .noTreeShaking()
+ .addDontShrink()
.addDontObfuscate()
.setMinApi(minApi)
.compile();
@@ -350,7 +350,7 @@
.addProgramClasses(ClassWithAssertions.class)
.debug()
.setMinApi(minApi)
- .noTreeShaking()
+ .addDontShrink()
.addDontObfuscate()
.compile()
.writeToZip();
diff --git a/src/test/java/com/android/tools/r8/rewrite/staticvalues/inlibraries/StaticLibraryValuesChangeTest.java b/src/test/java/com/android/tools/r8/rewrite/staticvalues/inlibraries/StaticLibraryValuesChangeTest.java
index 7d2c4bc..906d8e4 100644
--- a/src/test/java/com/android/tools/r8/rewrite/staticvalues/inlibraries/StaticLibraryValuesChangeTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/staticvalues/inlibraries/StaticLibraryValuesChangeTest.java
@@ -85,7 +85,7 @@
PreloadedClassFileProvider.fromClassData(
DescriptorUtils.javaTypeToDescriptor(LibraryClass.class.getName()),
compileTimeLibrary.buildClasses().get(0)))
- .noTreeShaking()
+ .addDontShrink()
.addDontObfuscate()
.compile()
// Merge the compiled TestMain with the runtime version of LibraryClass.
diff --git a/src/test/java/com/android/tools/r8/shaking/FunctionTest.java b/src/test/java/com/android/tools/r8/shaking/FunctionTest.java
index eda629d..a5c498a 100644
--- a/src/test/java/com/android/tools/r8/shaking/FunctionTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/FunctionTest.java
@@ -42,7 +42,7 @@
public void testR8Working() throws Exception {
testForR8(parameters.getBackend())
.addKeepMainRule(TestClass.class)
- .noTreeShaking()
+ .addDontShrink()
.addDontObfuscate()
.enableInliningAnnotations()
.addInnerClasses(FunctionTest.class)
diff --git a/src/test/java/com/android/tools/r8/shaking/TreeShakingTest.java b/src/test/java/com/android/tools/r8/shaking/TreeShakingTest.java
index 6ae395d..023b120 100644
--- a/src/test/java/com/android/tools/r8/shaking/TreeShakingTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/TreeShakingTest.java
@@ -166,7 +166,7 @@
ToolHelper.getAppBuilder(b.getBuilder())
.addProgramFiles(Paths.get(programFile)))
.enableProguardTestOptions()
- .minification(minify.isMinify())
+ .addDontObfuscateUnless(minify.isMinify())
.setMinApi(parameters)
.addKeepRuleFiles(
ListUtils.map(
diff --git a/src/test/java/com/android/tools/r8/shaking/annotations/ReflectiveAnnotationUseTest.java b/src/test/java/com/android/tools/r8/shaking/annotations/ReflectiveAnnotationUseTest.java
index 6af8bd4..82e9f74 100644
--- a/src/test/java/com/android/tools/r8/shaking/annotations/ReflectiveAnnotationUseTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/annotations/ReflectiveAnnotationUseTest.java
@@ -102,7 +102,7 @@
.addKeepRules(KEEP_ANNOTATIONS)
.addKeepRules("-keep @interface " + ANNOTATION_NAME + " {", " *;", "}")
.allowDiagnosticWarningMessages()
- .minification(minify)
+ .addDontObfuscateUnless(minify)
.setMinApi(parameters)
.compile()
.assertAllWarningMessagesMatch(
@@ -143,7 +143,7 @@
" java.lang.String *f2();",
"}")
.allowDiagnosticWarningMessages()
- .minification(minify)
+ .addDontObfuscateUnless(minify)
.setMinApi(parameters)
.compile()
.assertAllWarningMessagesMatch(
@@ -181,7 +181,7 @@
.addKeepMainRule(MAIN_CLASS_NAME)
.addKeepRules(KEEP_ANNOTATIONS)
.allowDiagnosticWarningMessages()
- .minification(minify)
+ .addDontObfuscateUnless(minify)
.setMinApi(parameters)
.compile()
.assertAllWarningMessagesMatch(
@@ -218,7 +218,7 @@
.addProgramFiles(getJavaJarFile(FOLDER))
.addKeepMainRule(MAIN_CLASS_NAME)
.allowDiagnosticWarningMessages()
- .minification(minify)
+ .addDontObfuscateUnless(minify)
.setMinApi(parameters)
.compile()
.assertAllWarningMessagesMatch(
diff --git a/src/test/java/com/android/tools/r8/shaking/attributes/EnclosingMethodTest.java b/src/test/java/com/android/tools/r8/shaking/attributes/EnclosingMethodTest.java
index d93c615..971b408 100644
--- a/src/test/java/com/android/tools/r8/shaking/attributes/EnclosingMethodTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/attributes/EnclosingMethodTest.java
@@ -95,7 +95,7 @@
.addKeepMainRule(MAIN)
.addKeepRules("-keep class **.GetName*")
.addKeepRules("-keepattributes InnerClasses,EnclosingMethod")
- .minification(enableMinification)
+ .addDontObfuscateUnless(enableMinification)
.setMinApi(parameters)
.run(parameters.getRuntime(), MAIN)
.assertSuccessWithOutput(OUTPUT_WITH_SHRUNK_ATTRIBUTES);
diff --git a/src/test/java/com/android/tools/r8/shaking/attributes/InnerClassesSimpleTest.java b/src/test/java/com/android/tools/r8/shaking/attributes/InnerClassesSimpleTest.java
index 41de889..0af92c9 100644
--- a/src/test/java/com/android/tools/r8/shaking/attributes/InnerClassesSimpleTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/attributes/InnerClassesSimpleTest.java
@@ -40,7 +40,7 @@
.addInnerClasses(getClass())
.addKeepMainRule(Main.class)
.addKeepPackageNamesRule(getClass().getPackage())
- .noTreeShaking()
+ .addDontShrink()
.applyIf(!minify, TestShrinkerBuilder::addDontObfuscate)
.compile()
.writeToZip();
diff --git a/src/test/java/com/android/tools/r8/shaking/attributes/MissingEnclosingMethodTest.java b/src/test/java/com/android/tools/r8/shaking/attributes/MissingEnclosingMethodTest.java
index 06e43a0..615f9cf 100644
--- a/src/test/java/com/android/tools/r8/shaking/attributes/MissingEnclosingMethodTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/attributes/MissingEnclosingMethodTest.java
@@ -116,7 +116,7 @@
.apply(config::addInnerClasses)
.addKeepMainRule(DataClassUser.class)
.addKeepAttributes("Signature", "InnerClasses", "EnclosingMethod")
- .minification(enableMinification)
+ .addDontObfuscateUnless(enableMinification)
.setMinApi(parameters)
.run(parameters.getRuntime(), DataClassUser.class)
.assertSuccessWithOutput(EXPECTED_OUTPUT);
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/IfRuleWithFieldAnnotation.java b/src/test/java/com/android/tools/r8/shaking/ifrule/IfRuleWithFieldAnnotation.java
index 18d41dd..aff5d9d 100644
--- a/src/test/java/com/android/tools/r8/shaking/ifrule/IfRuleWithFieldAnnotation.java
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/IfRuleWithFieldAnnotation.java
@@ -94,7 +94,7 @@
.addProgramClasses(Foo.class, Bar.class, SerializedName.class, FooNotCallingBar.class)
.addDontWarn(getClass())
.addKeepMainRule(FooNotCallingBar.class)
- .noMinification()
+ .addDontObfuscate()
.addKeepRules(CONDITIONAL_KEEP_RULE)
.compile()
.inspect(
@@ -113,7 +113,7 @@
.addProgramClasses(Foo.class, Bar.class, SerializedName.class, FooNotCallingBar.class)
.addDontWarn(getClass())
.addKeepMainRule(FooNotCallingBar.class)
- .noMinification()
+ .addDontObfuscate()
.compile()
.inspect(
codeInspector -> {
diff --git a/src/test/java/com/android/tools/r8/shaking/keepparameternames/KeepParameterNamesTest.java b/src/test/java/com/android/tools/r8/shaking/keepparameternames/KeepParameterNamesTest.java
index 41f3dcc..2dd2b3f 100644
--- a/src/test/java/com/android/tools/r8/shaking/keepparameternames/KeepParameterNamesTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/keepparameternames/KeepParameterNamesTest.java
@@ -178,7 +178,7 @@
.enableInliningAnnotations()
.enableNeverClassInliningAnnotations()
.enableUnusedArgumentAnnotations()
- .minification(enableMinification)
+ .addDontObfuscateUnless(enableMinification)
.apply(this::configureKeepParameterNames)
.compile()
.inspect(this::checkLocalVariableTable)
@@ -217,7 +217,7 @@
.enableUnusedArgumentAnnotations()
.addInnerClasses(KeepParameterNamesTest.class)
.addKeepMainRule(TestClass.class)
- .minification(enableMinification)
+ .addDontObfuscateUnless(enableMinification)
.apply(this::configureKeepParameterNames)
.compile()
.inspect(this::checkLocalVariableTableNotKept)
diff --git a/src/test/java/com/android/tools/r8/shaking/proxy/MockitoTest.java b/src/test/java/com/android/tools/r8/shaking/proxy/MockitoTest.java
index 1b75532..0865b95 100644
--- a/src/test/java/com/android/tools/r8/shaking/proxy/MockitoTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/proxy/MockitoTest.java
@@ -52,7 +52,7 @@
.addProgramFiles(MOCKITO_INTERFACE_JAR)
.addKeepRuleFiles(flagToKeepTestRunner)
.addDontWarn("org.mockito.**")
- .minification(minify)
+ .addDontObfuscateUnless(minify)
.setMinApi(parameters)
.compile()
.inspector();
@@ -71,7 +71,7 @@
.addProgramFiles(MOCKITO_INTERFACE_JAR)
.addKeepRuleFiles(flagToKeepInterfaceConditionally)
.addDontWarn("org.mockito.**")
- .minification(minify)
+ .addDontObfuscateUnless(minify)
.setMinApi(parameters)
.compile()
.inspector();
diff --git a/src/test/java/com/android/tools/r8/shaking/whyareyoukeeping/WhyAreYouKeepingOverriddenMethodTest.java b/src/test/java/com/android/tools/r8/shaking/whyareyoukeeping/WhyAreYouKeepingOverriddenMethodTest.java
index 79c5edc..ba197fb 100644
--- a/src/test/java/com/android/tools/r8/shaking/whyareyoukeeping/WhyAreYouKeepingOverriddenMethodTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/whyareyoukeeping/WhyAreYouKeepingOverriddenMethodTest.java
@@ -54,7 +54,7 @@
.enableNeverClassInliningAnnotations()
.enableInliningAnnotations()
.enableNoVerticalClassMergingAnnotations()
- .minification(minification)
+ .addDontObfuscateUnless(minification)
.setMinApi(AndroidApiLevel.B)
// Redirect the compilers stdout to intercept the '-whyareyoukeeping' output
.collectStdout()
@@ -72,7 +72,7 @@
.enableNeverClassInliningAnnotations()
.enableInliningAnnotations()
.enableNoVerticalClassMergingAnnotations()
- .minification(minification)
+ .addDontObfuscateUnless(minification)
.setMinApi(AndroidApiLevel.B)
.setKeptGraphConsumer(graphConsumer)
.compile();
diff --git a/src/test/java/com/android/tools/r8/utils/DataResourceConsumerForTesting.java b/src/test/java/com/android/tools/r8/utils/DataResourceConsumerForTesting.java
index 77274e6..dc2dfe8 100644
--- a/src/test/java/com/android/tools/r8/utils/DataResourceConsumerForTesting.java
+++ b/src/test/java/com/android/tools/r8/utils/DataResourceConsumerForTesting.java
@@ -54,6 +54,10 @@
@Override
public void finished(DiagnosticsHandler handler) {}
+ public Map<String, ImmutableList<String>> getAll() {
+ return resources;
+ }
+
public ImmutableList<String> get(String name) {
return resources.get(name);
}
diff --git a/src/test/testbase/java/com/android/tools/r8/ProguardTestBuilder.java b/src/test/testbase/java/com/android/tools/r8/ProguardTestBuilder.java
index 758f1cf..275592f 100644
--- a/src/test/testbase/java/com/android/tools/r8/ProguardTestBuilder.java
+++ b/src/test/testbase/java/com/android/tools/r8/ProguardTestBuilder.java
@@ -110,12 +110,6 @@
command.add(outJar.toString());
command.add("-printmapping");
command.add(mapFile.toString());
- if (enableTreeShaking.isFalse()) {
- command.add("-dontshrink");
- }
- if (enableMinification.isFalse()) {
- command.add("-dontobfuscate");
- }
ProcessBuilder pbuilder = new ProcessBuilder(command);
ProcessResult result = ToolHelper.runProcess(pbuilder, getStdoutForTesting());
if (result.exitCode != 0) {
diff --git a/src/test/testbase/java/com/android/tools/r8/R8TestBuilder.java b/src/test/testbase/java/com/android/tools/r8/R8TestBuilder.java
index ecd1ea7..bf3998d 100644
--- a/src/test/testbase/java/com/android/tools/r8/R8TestBuilder.java
+++ b/src/test/testbase/java/com/android/tools/r8/R8TestBuilder.java
@@ -114,12 +114,6 @@
builder.addProguardConfiguration(keepRules, Origin.unknown());
}
builder.addMainDexRulesFiles(mainDexRulesFiles);
- if (enableTreeShaking.isFalse()) {
- builder.setDisableTreeShaking(true);
- }
- if (enableMinification.isFalse()) {
- builder.setDisableMinification(true);
- }
StringBuilder proguardMapBuilder = wrapProguardMapConsumer(builder);
if (!applyMappingMaps.isEmpty()) {
try {
diff --git a/src/test/testbase/java/com/android/tools/r8/R8TestCompileResult.java b/src/test/testbase/java/com/android/tools/r8/R8TestCompileResult.java
index f9db531..942cc92 100644
--- a/src/test/testbase/java/com/android/tools/r8/R8TestCompileResult.java
+++ b/src/test/testbase/java/com/android/tools/r8/R8TestCompileResult.java
@@ -140,6 +140,11 @@
AndroidApp.builder().addProgramFile(feature).setProguardMapOutputData(proguardMap).build());
}
+ public CodeInspector featureInspector() throws IOException {
+ assert features.size() == 1;
+ return featureInspector(features.get(0));
+ }
+
@SafeVarargs
public final <E extends Throwable> R8TestCompileResult inspect(
ThrowingConsumer<CodeInspector, E>... consumers) throws IOException, E {
diff --git a/src/test/testbase/java/com/android/tools/r8/TestShrinkerBuilder.java b/src/test/testbase/java/com/android/tools/r8/TestShrinkerBuilder.java
index 57d2137..2dbe0e2 100644
--- a/src/test/testbase/java/com/android/tools/r8/TestShrinkerBuilder.java
+++ b/src/test/testbase/java/com/android/tools/r8/TestShrinkerBuilder.java
@@ -15,7 +15,6 @@
import com.android.tools.r8.shaking.ProguardKeepAttributes;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.FileUtils;
-import com.android.tools.r8.utils.OptionalBool;
import com.android.tools.r8.utils.StringUtils;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Sets;
@@ -35,9 +34,6 @@
T extends TestShrinkerBuilder<C, B, CR, RR, T>>
extends TestCompilerBuilder<C, B, CR, RR, T> {
- protected OptionalBool enableTreeShaking = OptionalBool.UNKNOWN;
- protected OptionalBool enableMinification = OptionalBool.UNKNOWN;
-
private final Set<Class<? extends Annotation>> addedTestingAnnotations =
Sets.newIdentityHashSet();
@@ -49,10 +45,6 @@
return false;
}
- public boolean isR8TestBuilder() {
- return false;
- }
-
public boolean isR8CompatTestBuilder() {
return false;
}
@@ -90,25 +82,6 @@
return backend == Backend.DEX ? super.getMinApiLevel() : -1;
}
- public T treeShaking(boolean enable) {
- enableTreeShaking = OptionalBool.of(enable);
- return self();
- }
-
- public T noTreeShaking() {
- return treeShaking(false);
- }
-
- public T minification(boolean enable) {
- enableMinification = OptionalBool.of(enable);
- return self();
- }
-
- @Deprecated
- public T noMinification() {
- return minification(false);
- }
-
public T addClassObfuscationDictionary(String... names) throws IOException {
Path path = getState().getNewTempFolder().resolve("classobfuscationdictionary.txt");
FileUtils.writeTextFile(path, StringUtils.join(" ", names));
@@ -140,6 +113,13 @@
+ clazz.getTypeName());
}
+ public T addDontObfuscateUnless(boolean enableMinification) {
+ if (!enableMinification) {
+ return addDontObfuscate();
+ }
+ return self();
+ }
+
public T addDontOptimize() {
return addKeepRules("-dontoptimize");
}
diff --git a/src/test/testbase/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java b/src/test/testbase/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
index 74bdc47..7c06d42 100644
--- a/src/test/testbase/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
+++ b/src/test/testbase/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
@@ -67,6 +67,7 @@
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
+import java.util.stream.Stream;
public class CodeInspector {
@@ -401,6 +402,12 @@
return builder.build();
}
+ public Stream<InstructionSubject> streamInstructions() {
+ return allClasses().stream()
+ .flatMap(cls -> cls.allMethods(MethodSubject::hasCode).stream())
+ .flatMap(MethodSubject::streamInstructions);
+ }
+
public FieldSubject field(Field field) {
return field(Reference.fieldFromField(field));
}
diff --git a/src/test/testbase/java/com/android/tools/r8/utils/codeinspector/TypeSubject.java b/src/test/testbase/java/com/android/tools/r8/utils/codeinspector/TypeSubject.java
index efa2c30..1140771 100644
--- a/src/test/testbase/java/com/android/tools/r8/utils/codeinspector/TypeSubject.java
+++ b/src/test/testbase/java/com/android/tools/r8/utils/codeinspector/TypeSubject.java
@@ -41,6 +41,10 @@
throw new Unreachable("Cannot determine if a type is synthetic");
}
+ public boolean is(Class<?> clazz) {
+ return is(clazz.getTypeName());
+ }
+
public boolean is(String type) {
return dexType.equals(codeInspector.toDexType(type));
}
diff --git a/tools/r8_release.py b/tools/r8_release.py
index 6090377..5e0f695 100755
--- a/tools/r8_release.py
+++ b/tools/r8_release.py
@@ -358,8 +358,9 @@
message = f'DO NOT SUBMIT: {message}'
if commit_info:
message += f'\n\n{commit_info}'
+ message = message.replace("'", r"\'")
return subprocess.check_output(
- f"g4 change --desc '{message}\n'",
+ f"g4 change --desc $'{message}\n'",
shell=True).decode('utf-8')