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')