Make no assumptions about the type of assumed return field values
Bug: 233828966
Bug: 150836439
Change-Id: I6e580e418dd03cb6682e59e024b1167272cfc7ea
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 5d07a1c..d70426f 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -79,6 +79,7 @@
import com.android.tools.r8.shaking.AbstractMethodRemover;
import com.android.tools.r8.shaking.AnnotationRemover;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.AssumeInfoCollection;
import com.android.tools.r8.shaking.ClassInitFieldSynthesizer;
import com.android.tools.r8.shaking.DefaultTreePrunerConfiguration;
import com.android.tools.r8.shaking.DiscardedChecker;
@@ -338,6 +339,7 @@
options.itemFactory, options.getMinApiLevel()));
}
}
+ AssumeInfoCollection.Builder assumeInfoCollectionBuilder = AssumeInfoCollection.builder();
SubtypingInfo subtypingInfo = SubtypingInfo.create(appView);
appView.setRootSet(
RootSet.builder(
@@ -345,7 +347,9 @@
subtypingInfo,
Iterables.concat(
options.getProguardConfiguration().getRules(), synthesizedProguardRules))
+ .setAssumeInfoCollectionBuilder(assumeInfoCollectionBuilder)
.build(executorService));
+ appView.setAssumeInfoCollection(assumeInfoCollectionBuilder.build());
// Compute the main dex rootset that will be the base of first and final main dex tracing
// before building a new appview with only live classes (and invalidating subtypingInfo).
diff --git a/src/main/java/com/android/tools/r8/errors/AssumeValuesMissingStaticFieldDiagnostic.java b/src/main/java/com/android/tools/r8/errors/AssumeValuesMissingStaticFieldDiagnostic.java
new file mode 100644
index 0000000..38f6a46
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/errors/AssumeValuesMissingStaticFieldDiagnostic.java
@@ -0,0 +1,78 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.errors;
+
+import com.android.tools.r8.Diagnostic;
+import com.android.tools.r8.Keep;
+import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.Position;
+
+@Keep
+public class AssumeValuesMissingStaticFieldDiagnostic implements Diagnostic {
+
+ private final DexType fieldHolder;
+ private final DexString fieldName;
+ private final Origin origin;
+ private final Position position;
+
+ private AssumeValuesMissingStaticFieldDiagnostic(
+ DexType fieldHolder, DexString fieldName, Origin origin, Position position) {
+ this.fieldHolder = fieldHolder;
+ this.fieldName = fieldName;
+ this.origin = origin;
+ this.position = position;
+ }
+
+ @Override
+ public Origin getOrigin() {
+ return origin;
+ }
+
+ @Override
+ public Position getPosition() {
+ return position;
+ }
+
+ @Override
+ public String getDiagnosticMessage() {
+ return "The field "
+ + fieldHolder.getTypeName()
+ + "."
+ + fieldName
+ + " is used as the return value in an -assumenosideeffects or -assumevalues rule"
+ + ", but no such static field exists.";
+ }
+
+ public static class Builder {
+
+ private DexType fieldHolder;
+ private DexString fieldName;
+ private Origin origin;
+ private Position position;
+
+ public Builder() {}
+
+ public Builder setField(DexType fieldHolder, DexString fieldName) {
+ this.fieldHolder = fieldHolder;
+ this.fieldName = fieldName;
+ return this;
+ }
+
+ public Builder setOrigin(Origin origin) {
+ this.origin = origin;
+ return this;
+ }
+
+ public Builder setPosition(Position position) {
+ this.position = position;
+ return this;
+ }
+
+ public AssumeValuesMissingStaticFieldDiagnostic build() {
+ return new AssumeValuesMissingStaticFieldDiagnostic(fieldHolder, fieldName, origin, position);
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/graph/AppView.java b/src/main/java/com/android/tools/r8/graph/AppView.java
index 6dd2ee7..ec80f8f 100644
--- a/src/main/java/com/android/tools/r8/graph/AppView.java
+++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -35,6 +35,7 @@
import com.android.tools.r8.optimize.argumentpropagation.ArgumentPropagator;
import com.android.tools.r8.optimize.interfaces.collection.OpenClosedInterfacesCollection;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.AssumeInfoCollection;
import com.android.tools.r8.shaking.KeepClassInfo;
import com.android.tools.r8.shaking.KeepFieldInfo;
import com.android.tools.r8.shaking.KeepInfoCollection;
@@ -74,6 +75,7 @@
private T appInfo;
private AppInfoWithClassHierarchy appInfoForDesugaring;
private AppServices appServices;
+ private AssumeInfoCollection assumeInfoCollection = AssumeInfoCollection.builder().build();
private final DontWarnConfiguration dontWarnConfiguration;
private final WholeProgramOptimizations wholeProgramOptimizations;
private GraphLens codeLens = GraphLens.getIdentityLens();
@@ -311,6 +313,14 @@
this.appServices = appServices;
}
+ public AssumeInfoCollection getAssumeInfoCollection() {
+ return assumeInfoCollection;
+ }
+
+ public void setAssumeInfoCollection(AssumeInfoCollection assumeInfoCollection) {
+ this.assumeInfoCollection = assumeInfoCollection;
+ }
+
public DontWarnConfiguration getDontWarnConfiguration() {
return dontWarnConfiguration;
}
@@ -760,6 +770,7 @@
if (appServices() != null) {
setAppServices(appServices().prunedCopy(prunedItems));
}
+ setAssumeInfoCollection(getAssumeInfoCollection().withoutPrunedItems(prunedItems));
if (hasProguardCompatibilityActions()) {
setProguardCompatibilityActions(
getProguardCompatibilityActions().withoutPrunedItems(prunedItems));
@@ -857,6 +868,8 @@
.setAppInfo(appView.appInfoWithLiveness().rewrittenWithLens(application, lens));
}
appView.setAppServices(appView.appServices().rewrittenWithLens(lens));
+ appView.setAssumeInfoCollection(
+ appView.getAssumeInfoCollection().rewrittenWithLens(appView, lens));
if (appView.hasInitClassLens()) {
appView.setInitClassLens(appView.initClassLens().rewrittenWithLens(lens));
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexClass.java b/src/main/java/com/android/tools/r8/graph/DexClass.java
index a377b46..6f5e604 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -539,17 +539,26 @@
return lookupTarget(instanceFields, field);
}
- public DexField lookupUniqueInstanceFieldWithName(DexString name) {
- DexField field = null;
- for (DexEncodedField encodedField : instanceFields()) {
- if (encodedField.getReference().name == name) {
- if (field != null) {
+ public DexEncodedField lookupUniqueInstanceFieldWithName(DexString name) {
+ return internalLookupUniqueFieldThatMatches(field -> field.getName() == name, instanceFields());
+ }
+
+ public DexEncodedField lookupUniqueStaticFieldWithName(DexString name) {
+ return internalLookupUniqueFieldThatMatches(field -> field.getName() == name, staticFields());
+ }
+
+ private static DexEncodedField internalLookupUniqueFieldThatMatches(
+ Predicate<DexEncodedField> predicate, List<DexEncodedField> fields) {
+ DexEncodedField result = null;
+ for (DexEncodedField field : fields) {
+ if (predicate.test(field)) {
+ if (result != null) {
return null;
}
- field = encodedField.getReference();
+ result = field;
}
}
- return field;
+ return result;
}
/** Find method in this class matching {@param method}. */
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreserveMethodCharacteristics.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreserveMethodCharacteristics.java
index ab305a3..a6e27f5 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreserveMethodCharacteristics.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreserveMethodCharacteristics.java
@@ -60,7 +60,7 @@
AppView<AppInfoWithLiveness> appView, DexEncodedMethod method) {
return new MethodCharacteristics(
method,
- appView.appInfo().isAssumeNoSideEffectsMethod(method.getReference()),
+ appView.getAssumeInfoCollection().isSideEffectFree(method.getReference()),
appView.appInfo().getMainDexInfo().isTracedMethodRoot(method.getReference()));
}
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 eb22b19..fbe3ff5 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,10 +202,10 @@
return new ProtoTypeObject(constClass.getValue());
} else if (definition.isConstString()) {
ConstString constString = definition.asConstString();
- DexField field =
+ DexEncodedField field =
context.getHolder().lookupUniqueInstanceFieldWithName(constString.getValue());
if (field != null) {
- return new LiveProtoFieldObject(field);
+ return new LiveProtoFieldObject(field.getReference());
}
// This const-string refers to a field that no longer exists. In this case, we create a
// special dead-object instead of failing with an InvalidRawMessageInfoException below.
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 bc1a554..bab5e75 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
@@ -31,6 +31,11 @@
return knownArrayLengthStates.computeIfAbsent(length, KnownLengthArrayState::new);
}
+ public NumberFromIntervalValue createNumberFromIntervalValue(
+ long minInclusive, long maxInclusive) {
+ return new NumberFromIntervalValue(minInclusive, maxInclusive);
+ }
+
public SingleFieldValue createSingleFieldValue(DexField field, ObjectState state) {
return state.isEmpty()
? new SingleStatelessFieldValue(field)
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/NumberFromIntervalValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/NumberFromIntervalValue.java
index 3238680..94d2d59 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/NumberFromIntervalValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/NumberFromIntervalValue.java
@@ -31,6 +31,14 @@
return maxInclusive - minInclusive + 1;
}
+ public long getMinInclusive() {
+ return minInclusive;
+ }
+
+ public long getMaxInclusive() {
+ return maxInclusive;
+ }
+
@Override
public boolean isNumberFromIntervalValue() {
return true;
diff --git a/src/main/java/com/android/tools/r8/ir/code/FieldInstruction.java b/src/main/java/com/android/tools/r8/ir/code/FieldInstruction.java
index 552ebf3..3551561 100644
--- a/src/main/java/com/android/tools/r8/ir/code/FieldInstruction.java
+++ b/src/main/java/com/android/tools/r8/ir/code/FieldInstruction.java
@@ -8,6 +8,7 @@
import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexClassAndField;
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexField;
@@ -83,9 +84,9 @@
}
SingleFieldResolutionResult<?> singleFieldResolutionResult =
resolutionResult.asSingleFieldResolutionResult();
- DexEncodedField resolvedField = singleFieldResolutionResult.getResolvedField();
+ DexClassAndField resolvedField = singleFieldResolutionResult.getResolutionPair();
// Check if the instruction may fail with an IncompatibleClassChangeError.
- if (resolvedField.isStatic() != isStaticFieldInstruction()) {
+ if (resolvedField.getAccessFlags().isStatic() != isStaticFieldInstruction()) {
return true;
}
// Check if the resolution target is accessible.
@@ -115,11 +116,8 @@
isStaticFieldInstruction() && !assumption.canAssumeClassIsAlreadyInitialized();
if (mayTriggerClassInitialization) {
// Only check for <clinit> side effects if there is no -assumenosideeffects rule.
- if (appView.appInfo().hasLiveness()) {
- AppInfoWithLiveness appInfoWithLiveness = appView.appInfo().withLiveness();
- if (appInfoWithLiveness.noSideEffects.containsKey(resolvedField.getReference())) {
- return false;
- }
+ if (appView.getAssumeInfoCollection().isSideEffectFree(resolvedField)) {
+ return false;
}
// May trigger <clinit> that may have side effects.
if (field.holder.classInitializationMayHaveSideEffectsInContext(appView, context)) {
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java b/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java
index 668d515..f5e7b03 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java
@@ -216,12 +216,10 @@
}
DexClassAndMethod resolvedMethod = resolutionResult.getResolutionPair();
- if (appView.hasLiveness()) {
- if (appView.appInfoWithLiveness().isAssumeNoSideEffectsMethod(getInvokedMethod())
- || appView.appInfoWithLiveness().isAssumeNoSideEffectsMethod(resolvedMethod)) {
+ if (appView.getAssumeInfoCollection().isSideEffectFree(getInvokedMethod())
+ || appView.getAssumeInfoCollection().isSideEffectFree(resolvedMethod)) {
return false;
}
- }
// Find the target and check if the invoke may have side effects.
DexClassAndMethod singleTarget = lookupSingleTarget(appView, context);
@@ -237,10 +235,8 @@
}
// Verify that the target method does not have side-effects.
- if (appView.hasLiveness()) {
- if (appView.appInfoWithLiveness().isAssumeNoSideEffectsMethod(singleTarget)) {
- return false;
- }
+ if (appView.getAssumeInfoCollection().isSideEffectFree(singleTarget)) {
+ return false;
}
DexEncodedMethod singleTargetDefinition = singleTarget.getDefinition();
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java b/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java
index 8f47b31..9e6a918 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java
@@ -220,7 +220,7 @@
}
// Verify that the target method does not have side-effects.
- if (appViewWithLiveness.appInfo().isAssumeNoSideEffectsMethod(singleTarget)) {
+ if (appView.getAssumeInfoCollection().isSideEffectFree(singleTarget)) {
return false;
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/Value.java b/src/main/java/com/android/tools/r8/ir/code/Value.java
index bb83120..54710df 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Value.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Value.java
@@ -27,6 +27,7 @@
import com.android.tools.r8.ir.analysis.type.Nullability;
import com.android.tools.r8.ir.analysis.type.TypeElement;
import com.android.tools.r8.ir.analysis.value.AbstractValue;
+import com.android.tools.r8.ir.analysis.value.NumberFromIntervalValue;
import com.android.tools.r8.ir.analysis.value.UnknownValue;
import com.android.tools.r8.ir.optimize.DeadCodeRemover.DeadInstructionResult;
import com.android.tools.r8.ir.regalloc.LiveIntervals;
@@ -940,8 +941,8 @@
return isThis;
}
- public void setValueRange(LongInterval range) {
- valueRange = range;
+ public void setValueRange(NumberFromIntervalValue range) {
+ valueRange = new LongInterval(range.getMinInclusive(), range.getMaxInclusive());
}
public boolean hasValueRange() {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/AssumeInserter.java b/src/main/java/com/android/tools/r8/ir/optimize/AssumeInserter.java
index df9d508..ed206d4 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/AssumeInserter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/AssumeInserter.java
@@ -240,9 +240,7 @@
if (invoke.hasUsedOutValue() && invoke.getOutType().isReferenceType()) {
AssumeInfo assumeInfo =
AssumeInfoLookup.lookupAssumeInfo(appView, resolutionResult, singleTarget);
- if (assumeInfo != null
- && assumeInfo.hasReturnInfo()
- && assumeInfo.getReturnInfo().isNonNull()) {
+ if (assumeInfo.getAssumeType().getNullability().isDefinitelyNotNull()) {
assumedValuesBuilder.addNonNullValueKnownToDominateAllUsers(invoke, invoke.outValue());
}
}
@@ -294,10 +292,8 @@
DexClassAndField field = resolutionResult.getResolutionPair();
if (field.getType().isReferenceType()) {
- AssumeInfo assumeInfo = AssumeInfoLookup.lookupAssumeInfo(appView, field);
- if (assumeInfo != null
- && assumeInfo.hasReturnInfo()
- && assumeInfo.getReturnInfo().isNonNull()) {
+ AssumeInfo assumeInfo = appView.getAssumeInfoCollection().get(field);
+ if (assumeInfo.getAssumeType().getNullability().isDefinitelyNotNull()) {
assumedValuesBuilder.addNonNullValueKnownToDominateAllUsers(fieldGet, fieldGet.outValue());
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
index 107bd20..75c275f 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
@@ -43,6 +43,7 @@
import com.android.tools.r8.ir.optimize.inliner.InliningReasonStrategy;
import com.android.tools.r8.ir.optimize.inliner.WhyAreYouNotInliningReporter;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.AssumeInfoCollection;
import com.android.tools.r8.shaking.MainDexInfo;
import com.android.tools.r8.synthesis.SyntheticItems;
import com.android.tools.r8.utils.BooleanUtils;
@@ -339,16 +340,16 @@
SingleResolutionResult<?> resolutionResult,
ProgramMethod singleTarget,
WhyAreYouNotInliningReporter whyAreYouNotInliningReporter) {
- AppInfoWithLiveness appInfo = appView.appInfo();
DexMethod singleTargetReference = singleTarget.getReference();
if (!appView.getKeepInfo(singleTarget).isInliningAllowed(appView.options())) {
whyAreYouNotInliningReporter.reportPinned();
return true;
}
- if (appInfo.noSideEffects.containsKey(invoke.getInvokedMethod())
- || appInfo.noSideEffects.containsKey(resolutionResult.getResolvedMethod().getReference())
- || appInfo.noSideEffects.containsKey(singleTargetReference)) {
+ AssumeInfoCollection assumeInfoCollection = appView.getAssumeInfoCollection();
+ if (assumeInfoCollection.isSideEffectFree(invoke.getInvokedMethod())
+ || assumeInfoCollection.isSideEffectFree(resolutionResult.getResolutionPair())
+ || assumeInfoCollection.isSideEffectFree(singleTargetReference)) {
return !singleTarget.getDefinition().getOptimizationInfo().forceInline();
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java b/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
index d572d2f..d30ee64 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
@@ -3,10 +3,8 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.ir.optimize;
-import static com.android.tools.r8.ir.analysis.type.Nullability.maybeNull;
import static com.google.common.base.Predicates.alwaysTrue;
-import com.android.tools.r8.graph.AccessControl;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexClassAndField;
import com.android.tools.r8.graph.DexClassAndMethod;
@@ -41,10 +39,7 @@
import com.android.tools.r8.ir.optimize.membervaluepropagation.assume.AssumeInfo;
import com.android.tools.r8.ir.optimize.membervaluepropagation.assume.AssumeInfoLookup;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.shaking.ProguardMemberRuleReturnValue;
import com.android.tools.r8.utils.IteratorUtils;
-import com.android.tools.r8.utils.Reporter;
-import com.android.tools.r8.utils.StringDiagnostic;
import com.google.common.collect.Sets;
import java.util.ListIterator;
import java.util.Set;
@@ -55,19 +50,13 @@
private static final OptimizationFeedback feedback = OptimizationFeedbackSimple.getInstance();
private final AppView<AppInfoWithLiveness> appView;
- private final Reporter reporter;
-
- // Fields for which we have reported warnings to due Proguard configuration rules.
- private final Set<DexField> warnedFields = Sets.newIdentityHashSet();
public MemberValuePropagation(AppView<AppInfoWithLiveness> appView) {
this.appView = appView;
- this.reporter = appView.options().reporter;
}
private void rewriteArrayGet(
IRCode code,
- ProgramMethod context,
Set<Value> affectedValues,
ListIterator<BasicBlock> blocks,
InstructionListIterator iterator,
@@ -126,93 +115,36 @@
if (field.isProgramField()) {
return appView.appInfo().mayPropagateValueFor(appView, field.getReference());
}
- return appView.appInfo().assumedValues.containsKey(field.getReference())
- || appView.appInfo().noSideEffects.containsKey(field.getReference());
+ return appView.getAssumeInfoCollection().contains(field);
}
private boolean mayPropagateValueFor(DexClassAndMethod method) {
if (method.isProgramMethod()) {
return appView.appInfo().mayPropagateValueFor(appView, method.getReference());
}
- return appView.appInfo().assumedValues.containsKey(method.getReference())
- || appView.appInfo().noSideEffects.containsKey(method.getReference());
+ return appView.getAssumeInfoCollection().contains(method);
}
private Instruction createReplacementFromAssumeInfo(
AssumeInfo assumeInfo, IRCode code, Instruction instruction) {
- if (!assumeInfo.hasReturnInfo()) {
+ if (assumeInfo.getAssumeValue().isUnknown()) {
return null;
}
- ProguardMemberRuleReturnValue returnValueRule = assumeInfo.getReturnInfo();
-
- // Check if this value can be assumed constant.
- if (returnValueRule.isSingleValue()) {
- if (instruction.getOutType().isReferenceType()) {
- if (returnValueRule.getSingleValue() == 0) {
- return appView
- .abstractValueFactory()
- .createNullValue()
- .createMaterializingInstruction(appView, code, instruction);
- }
- return null;
+ AbstractValue assumeValue = assumeInfo.getAssumeValue();
+ if (assumeValue.isSingleValue()) {
+ SingleValue singleValue = assumeValue.asSingleValue();
+ if (singleValue.isMaterializableInContext(appView, code.context())) {
+ return singleValue.createMaterializingInstruction(appView, code, instruction);
}
- return appView.abstractValueFactory()
- .createSingleNumberValue(returnValueRule.getSingleValue())
- .createMaterializingInstruction(appView, code, instruction);
- }
-
- if (returnValueRule.isField()) {
- DexField field = returnValueRule.getField();
- assert instruction.getOutType() == TypeElement.fromDexType(field.type, maybeNull(), appView);
-
- DexClassAndField staticField = appView.appInfo().lookupStaticTarget(field);
- if (staticField == null) {
- if (warnedFields.add(field)) {
- reporter.warning(
- new StringDiagnostic(
- "Field `"
- + field.toSourceString()
- + "` is used in an -assumevalues rule but does not exist.",
- code.origin));
- }
- return null;
- }
-
- if (AccessControl.isMemberAccessible(
- staticField, staticField.getHolder(), code.context(), appView)
- .isTrue()) {
- return StaticGet.builder()
- .setField(field)
- .setFreshOutValue(code, field.getTypeElement(appView), instruction.getLocalInfo())
- .build();
- }
-
- Instruction replacement =
- staticField
- .getDefinition()
- .valueAsConstInstruction(code, instruction.getLocalInfo(), appView);
- if (replacement == null) {
- reporter.warning(
- new StringDiagnostic(
- "Unable to apply the rule `"
- + returnValueRule.toString()
- + "`: Could not determine the value of field `"
- + field.toSourceString()
- + "`",
- code.origin));
- return null;
- }
- return replacement;
}
return null;
}
private void setValueRangeFromAssumeInfo(AssumeInfo assumeInfo, Value value) {
- if (assumeInfo.hasReturnInfo() && assumeInfo.getReturnInfo().isValueRange()) {
- assert !assumeInfo.getReturnInfo().isSingleValue();
- value.setValueRange(assumeInfo.getReturnInfo().getValueRange());
+ if (assumeInfo.getAssumeValue().isNumberFromIntervalValue()) {
+ value.setValueRange(assumeInfo.getAssumeValue().asNumberFromIntervalValue());
}
}
@@ -232,10 +164,9 @@
return false;
}
affectedValues.addAll(current.outValue().affectedValues());
- if (assumeInfo.isAssumeNoSideEffects()) {
+ if (assumeInfo.isSideEffectFree()) {
iterator.replaceCurrentInstruction(replacement);
} else {
- assert assumeInfo.isAssumeValues();
BasicBlock block = current.getBlock();
Position position = current.getPosition();
if (current.hasOutValue()) {
@@ -397,7 +328,7 @@
}
// Check if there is a Proguard configuration rule that specifies the value of the field.
- AssumeInfo lookup = AssumeInfoLookup.lookupAssumeInfo(appView, target);
+ AssumeInfo lookup = appView.getAssumeInfoCollection().get(target);
if (lookup != null
&& applyAssumeInfoIfPossible(code, affectedValues, blocks, iterator, current, lookup)) {
return;
@@ -539,8 +470,7 @@
while (iterator.hasNext()) {
Instruction current = iterator.next();
if (current.isArrayGet()) {
- rewriteArrayGet(
- code, context, affectedValues, blockIterator, iterator, current.asArrayGet());
+ rewriteArrayGet(code, affectedValues, blockIterator, iterator, current.asArrayGet());
} else if (current.isInvokeMethod()) {
rewriteInvokeMethodWithConstantValues(
code, context, affectedValues, blockIterator, iterator, current.asInvokeMethod());
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/sideeffects/JavaLangObjectsSideEffectCollection.java b/src/main/java/com/android/tools/r8/ir/optimize/library/sideeffects/JavaLangObjectsSideEffectCollection.java
index bd7edf7..0c65d04 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/sideeffects/JavaLangObjectsSideEffectCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/sideeffects/JavaLangObjectsSideEffectCollection.java
@@ -37,14 +37,14 @@
return false;
}
+ // Check if there is an -assumenosideeffects rule for the toString() method.
+ if (appView.getAssumeInfoCollection().isSideEffectFree(toStringMethodReference)) {
+ return false;
+ }
+
if (appView.appInfo().hasLiveness()) {
AppInfoWithLiveness appInfo = appView.appInfo().withLiveness();
- // Check if there is an -assumenosideeffects rule for the toString() method.
- if (appInfo.isAssumeNoSideEffectsMethod(toStringMethodReference)) {
- return false;
- }
-
// Check if this is a program class with a toString() method that does not have side effects.
DexClass clazz = appInfo.definitionFor(classType);
if (clazz != null && clazz.isEffectivelyFinal(appView)) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/membervaluepropagation/assume/AssumeInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/membervaluepropagation/assume/AssumeInfo.java
index a704abb..21d8d33 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/membervaluepropagation/assume/AssumeInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/membervaluepropagation/assume/AssumeInfo.java
@@ -4,64 +4,188 @@
package com.android.tools.r8.ir.optimize.membervaluepropagation.assume;
-import com.android.tools.r8.shaking.ProguardMemberRule;
-import com.android.tools.r8.shaking.ProguardMemberRuleReturnValue;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.GraphLens;
+import com.android.tools.r8.graph.PrunedItems;
+import com.android.tools.r8.ir.analysis.type.DynamicType;
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
+import com.android.tools.r8.ir.analysis.value.SingleFieldValue;
+import com.android.tools.r8.ir.analysis.value.objectstate.ObjectState;
+import java.util.Objects;
public class AssumeInfo {
- public enum AssumeType {
- ASSUME_NO_SIDE_EFFECTS,
- ASSUME_VALUES;
+ private static final AssumeInfo EMPTY =
+ new AssumeInfo(DynamicType.unknown(), AbstractValue.unknown(), false);
- AssumeType meet(AssumeType type) {
- return this == ASSUME_NO_SIDE_EFFECTS || type == ASSUME_NO_SIDE_EFFECTS
- ? ASSUME_NO_SIDE_EFFECTS
- : ASSUME_VALUES;
+ private final DynamicType assumeType;
+ private final AbstractValue assumeValue;
+ private final boolean isSideEffectFree;
+
+ private AssumeInfo(DynamicType assumeType, AbstractValue assumeValue, boolean isSideEffectFree) {
+ this.assumeType = assumeType;
+ this.assumeValue = assumeValue;
+ this.isSideEffectFree = isSideEffectFree;
+ }
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ public static AssumeInfo create(
+ DynamicType assumeType, AbstractValue assumeValue, boolean isSideEffectFree) {
+ return assumeType.isUnknown() && assumeValue.isUnknown() && !isSideEffectFree
+ ? empty()
+ : new AssumeInfo(assumeType, assumeValue, isSideEffectFree);
+ }
+
+ public static AssumeInfo empty() {
+ return EMPTY;
+ }
+
+ public DynamicType getAssumeType() {
+ return assumeType;
+ }
+
+ public AbstractValue getAssumeValue() {
+ return assumeValue;
+ }
+
+ public boolean isEmpty() {
+ if (this == empty()) {
+ return true;
}
+ assert !assumeType.isUnknown() || !assumeValue.isUnknown() || isSideEffectFree;
+ return false;
}
- private final AssumeType type;
- private final ProguardMemberRule rule;
-
- public AssumeInfo(AssumeType type, ProguardMemberRule rule) {
- this.type = type;
- this.rule = rule;
+ public boolean isSideEffectFree() {
+ return isSideEffectFree;
}
- public boolean hasReturnInfo() {
- return rule.hasReturnValue();
+ public AssumeInfo meet(AssumeInfo other) {
+ DynamicType meetType = internalMeetType(assumeType, other.assumeType);
+ AbstractValue meetValue = internalMeetValue(assumeValue, other.assumeValue);
+ boolean meetIsSideEffectFree =
+ internalMeetIsSideEffectFree(isSideEffectFree, other.isSideEffectFree);
+ return AssumeInfo.create(meetType, meetValue, meetIsSideEffectFree);
}
- public ProguardMemberRuleReturnValue getReturnInfo() {
- return rule.getReturnValue();
+ private static DynamicType internalMeetType(DynamicType type, DynamicType other) {
+ if (type.equals(other)) {
+ return type;
+ }
+ if (type.isUnknown()) {
+ return other;
+ }
+ if (other.isUnknown()) {
+ return type;
+ }
+ return DynamicType.unknown();
}
- public boolean isAssumeNoSideEffects() {
- return type == AssumeType.ASSUME_NO_SIDE_EFFECTS;
+ private static AbstractValue internalMeetValue(AbstractValue value, AbstractValue other) {
+ if (value.equals(other)) {
+ return value;
+ }
+ if (value.isUnknown()) {
+ return other;
+ }
+ if (other.isUnknown()) {
+ return value;
+ }
+ return AbstractValue.unknown();
}
- public boolean isAssumeValues() {
- return type == AssumeType.ASSUME_VALUES;
+ private static boolean internalMeetIsSideEffectFree(
+ boolean isSideEffectFree, boolean otherIsSideEffectFree) {
+ return isSideEffectFree || otherIsSideEffectFree;
}
- public AssumeInfo meet(AssumeInfo lookup) {
- return new AssumeInfo(type.meet(lookup.type), rule.hasReturnValue() ? rule : lookup.rule);
+ public AssumeInfo rewrittenWithLens(AppView<?> appView, GraphLens graphLens) {
+ // Verify that there is no need to rewrite the assumed type.
+ assert assumeType.isNotNullType() || assumeType.isUnknown();
+ // If the assumed value is a static field, then rewrite it.
+ if (assumeValue.isSingleFieldValue()) {
+ DexField field = assumeValue.asSingleFieldValue().getField();
+ DexField rewrittenField = graphLens.getRenamedFieldSignature(field);
+ if (rewrittenField != field) {
+ SingleFieldValue rewrittenAssumeValue =
+ appView
+ .abstractValueFactory()
+ .createSingleFieldValue(rewrittenField, ObjectState.empty());
+ return create(assumeType, rewrittenAssumeValue, isSideEffectFree);
+ }
+ }
+ return this;
+ }
+
+ public AssumeInfo withoutPrunedItems(PrunedItems prunedItems) {
+ // Verify that there is no need to prune the assumed type.
+ assert assumeType.isNotNullType() || assumeType.isUnknown();
+ // If the assumed value is a static field, and the static field is removed, then prune the
+ // assumed value.
+ if (assumeValue.isSingleFieldValue()
+ && prunedItems.isRemoved(assumeValue.asSingleFieldValue().getField())) {
+ return create(assumeType, AbstractValue.unknown(), isSideEffectFree);
+ }
+ return this;
}
@Override
public boolean equals(Object other) {
- if (other == null) {
- return false;
+ if (this == other) {
+ return true;
}
- if (!(other instanceof AssumeInfo)) {
+ if (other == null || getClass() != other.getClass()) {
return false;
}
AssumeInfo assumeInfo = (AssumeInfo) other;
- return type == assumeInfo.type && rule == assumeInfo.rule;
+ return assumeValue.equals(assumeInfo.assumeValue)
+ && assumeType.equals(assumeInfo.assumeType)
+ && isSideEffectFree == assumeInfo.isSideEffectFree;
}
@Override
public int hashCode() {
- return type.ordinal() * 31 + rule.hashCode();
+ return Objects.hash(assumeValue, assumeType, isSideEffectFree);
+ }
+
+ public static class Builder {
+
+ private DynamicType assumeType = DynamicType.unknown();
+ private AbstractValue assumeValue = AbstractValue.unknown();
+ private boolean isSideEffectFree = false;
+
+ public Builder meet(AssumeInfo assumeInfo) {
+ return meetAssumeType(assumeInfo.assumeType)
+ .meetAssumeValue(assumeInfo.assumeValue)
+ .meetIsSideEffectFree(assumeInfo.isSideEffectFree);
+ }
+
+ public Builder meetAssumeType(DynamicType assumeType) {
+ this.assumeType = internalMeetType(this.assumeType, assumeType);
+ return this;
+ }
+
+ public Builder meetAssumeValue(AbstractValue assumeValue) {
+ this.assumeValue = internalMeetValue(this.assumeValue, assumeValue);
+ return this;
+ }
+
+ public Builder meetIsSideEffectFree(boolean isSideEffectFree) {
+ this.isSideEffectFree = internalMeetIsSideEffectFree(this.isSideEffectFree, isSideEffectFree);
+ return this;
+ }
+
+ public Builder setIsSideEffectFree() {
+ this.isSideEffectFree = true;
+ return this;
+ }
+
+ public AssumeInfo build() {
+ return create(assumeType, assumeValue, isSideEffectFree);
+ }
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/membervaluepropagation/assume/AssumeInfoLookup.java b/src/main/java/com/android/tools/r8/ir/optimize/membervaluepropagation/assume/AssumeInfoLookup.java
index d207ac7..223fa00 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/membervaluepropagation/assume/AssumeInfoLookup.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/membervaluepropagation/assume/AssumeInfoLookup.java
@@ -4,15 +4,11 @@
package com.android.tools.r8.ir.optimize.membervaluepropagation.assume;
-import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexClassAndMember;
import com.android.tools.r8.graph.DexClassAndMethod;
-import com.android.tools.r8.graph.DexMember;
import com.android.tools.r8.graph.MethodResolutionResult.SingleResolutionResult;
-import com.android.tools.r8.ir.optimize.membervaluepropagation.assume.AssumeInfo.AssumeType;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.shaking.ProguardMemberRule;
+import com.android.tools.r8.shaking.AssumeInfoCollection;
public class AssumeInfoLookup {
@@ -20,33 +16,12 @@
AppView<AppInfoWithLiveness> appView,
SingleResolutionResult<?> resolutionResult,
DexClassAndMethod singleTarget) {
- AssumeInfo resolutionLookup = lookupAssumeInfo(appView, resolutionResult.getResolutionPair());
- if (resolutionLookup == null) {
- return singleTarget != null ? lookupAssumeInfo(appView, singleTarget) : null;
- }
+ AssumeInfoCollection assumeInfoCollection = appView.getAssumeInfoCollection();
+ AssumeInfo resolutionLookup = assumeInfoCollection.get(resolutionResult.getResolutionPair());
AssumeInfo singleTargetLookup =
- singleTarget != null ? lookupAssumeInfo(appView, singleTarget) : null;
+ singleTarget != null ? assumeInfoCollection.get(singleTarget) : null;
return singleTargetLookup != null
? resolutionLookup.meet(singleTargetLookup)
: resolutionLookup;
}
-
- public static AssumeInfo lookupAssumeInfo(
- AppView<? extends AppInfoWithClassHierarchy> appView, DexClassAndMember<?, ?> member) {
- DexMember<?, ?> reference = member.getReference();
- ProguardMemberRule assumeNoSideEffectsRule = appView.rootSet().noSideEffects.get(reference);
- ProguardMemberRule assumeValuesRule = appView.rootSet().assumedValues.get(reference);
- if (assumeNoSideEffectsRule == null && assumeValuesRule == null) {
- return null;
- }
- AssumeType type =
- assumeNoSideEffectsRule != null
- ? AssumeType.ASSUME_NO_SIDE_EFFECTS
- : AssumeType.ASSUME_VALUES;
- if ((assumeNoSideEffectsRule != null && assumeNoSideEffectsRule.hasReturnValue())
- || assumeValuesRule == null) {
- return new AssumeInfo(type, assumeNoSideEffectsRule);
- }
- return new AssumeInfo(type, assumeValuesRule);
- }
}
diff --git a/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java b/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
index 0f67814..f3cb024 100644
--- a/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
+++ b/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
@@ -106,7 +106,7 @@
invokeType)) {
eligibleLibraryMethod = currentResolvedMethod.asLibraryMethod();
}
- if (appView.appInfo().isAssumeMethod(currentResolvedMethod)) {
+ if (appView.getAssumeInfoCollection().contains(currentResolvedMethod)) {
break;
}
DexClass currentResolvedHolder = currentResolvedMethod.getHolder();
diff --git a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
index 1d5e67d..677338b 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -17,7 +17,6 @@
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexClassAndField;
import com.android.tools.r8.graph.DexClassAndMember;
-import com.android.tools.r8.graph.DexClassAndMethod;
import com.android.tools.r8.graph.DexClasspathClass;
import com.android.tools.r8.graph.DexDefinition;
import com.android.tools.r8.graph.DexDefinitionSupplier;
@@ -143,10 +142,6 @@
private final KeepInfoCollection keepInfo;
/** All items with assumemayhavesideeffects rule. */
public final Map<DexReference, ProguardMemberRule> mayHaveSideEffects;
- /** All items with assumenosideeffects rule. */
- public final Map<DexMember<?, ?>, ProguardMemberRule> noSideEffects;
- /** All items with assumevalues rule. */
- public final Map<DexMember<?, ?>, ProguardMemberRule> assumedValues;
/** All methods that should be inlined if possible due to a configuration directive. */
private final Set<DexMethod> alwaysInline;
/**
@@ -222,8 +217,6 @@
Map<DexCallSite, ProgramMethodSet> callSites,
KeepInfoCollection keepInfo,
Map<DexReference, ProguardMemberRule> mayHaveSideEffects,
- Map<DexMember<?, ?>, ProguardMemberRule> noSideEffects,
- Map<DexMember<?, ?>, ProguardMemberRule> assumedValues,
Set<DexMethod> alwaysInline,
Set<DexMethod> neverInlineDueToSingleCaller,
Set<DexMethod> whyAreYouNotInlining,
@@ -255,8 +248,6 @@
this.objectAllocationInfoCollection = objectAllocationInfoCollection;
this.keepInfo = keepInfo;
this.mayHaveSideEffects = mayHaveSideEffects;
- this.noSideEffects = noSideEffects;
- this.assumedValues = assumedValues;
this.callSites = callSites;
this.alwaysInline = alwaysInline;
this.neverInlineDueToSingleCaller = neverInlineDueToSingleCaller;
@@ -299,8 +290,6 @@
previous.callSites,
previous.keepInfo,
previous.mayHaveSideEffects,
- previous.noSideEffects,
- previous.assumedValues,
previous.alwaysInline,
previous.neverInlineDueToSingleCaller,
previous.whyAreYouNotInlining,
@@ -346,8 +335,6 @@
previous.callSites,
extendPinnedItems(previous, prunedItems.getAdditionalPinnedItems()),
previous.mayHaveSideEffects,
- pruneMapFromMembers(previous.noSideEffects, prunedItems, executorService, futures),
- pruneMapFromMembers(previous.assumedValues, prunedItems, executorService, futures),
pruneMethods(previous.alwaysInline, prunedItems, executorService, futures),
pruneMethods(previous.neverInlineDueToSingleCaller, prunedItems, executorService, futures),
pruneMethods(previous.whyAreYouNotInlining, prunedItems, executorService, futures),
@@ -551,8 +538,6 @@
callSites,
keepInfo,
mayHaveSideEffects,
- noSideEffects,
- assumedValues,
alwaysInline,
neverInlineDueToSingleCaller,
whyAreYouNotInlining,
@@ -630,8 +615,6 @@
this.objectAllocationInfoCollection = previous.objectAllocationInfoCollection;
this.keepInfo = previous.keepInfo;
this.mayHaveSideEffects = previous.mayHaveSideEffects;
- this.noSideEffects = previous.noSideEffects;
- this.assumedValues = previous.assumedValues;
this.callSites = previous.callSites;
this.alwaysInline = previous.alwaysInline;
this.neverInlineDueToSingleCaller = previous.neverInlineDueToSingleCaller;
@@ -762,26 +745,6 @@
return neverInlineDueToSingleCaller.contains(method.getReference());
}
- public boolean isAssumeMethod(DexClassAndMethod method) {
- return isAssumeNoSideEffectsMethod(method) || isAssumeValuesMethod(method);
- }
-
- public boolean isAssumeNoSideEffectsMethod(DexMethod method) {
- return noSideEffects.containsKey(method);
- }
-
- public boolean isAssumeNoSideEffectsMethod(DexClassAndMethod method) {
- return isAssumeNoSideEffectsMethod(method.getReference());
- }
-
- public boolean isAssumeValuesMethod(DexMethod method) {
- return assumedValues.containsKey(method);
- }
-
- public boolean isAssumeValuesMethod(DexClassAndMethod method) {
- return isAssumeValuesMethod(method.getReference());
- }
-
public boolean isWhyAreYouNotInliningMethod(DexMethod method) {
return whyAreYouNotInlining.contains(method);
}
@@ -1239,13 +1202,6 @@
keepInfo.rewrite(definitionSupplier, lens, application.options),
// Take any rule in case of collisions.
lens.rewriteReferenceKeys(mayHaveSideEffects, (reference, rules) -> ListUtils.first(rules)),
- // Take the assume rule from the representative in case of collisions.
- lens.rewriteReferenceKeys(
- noSideEffects,
- (reference, rules) -> noSideEffects.get(lens.getOriginalMemberSignature(reference))),
- lens.rewriteReferenceKeys(
- assumedValues,
- (reference, rules) -> assumedValues.get(lens.getOriginalMemberSignature(reference))),
lens.rewriteReferences(alwaysInline),
lens.rewriteReferences(neverInlineDueToSingleCaller),
lens.rewriteReferences(whyAreYouNotInlining),
diff --git a/src/main/java/com/android/tools/r8/shaking/AssumeInfoCollection.java b/src/main/java/com/android/tools/r8/shaking/AssumeInfoCollection.java
new file mode 100644
index 0000000..5d1686f
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/shaking/AssumeInfoCollection.java
@@ -0,0 +1,144 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.shaking;
+
+import static com.android.tools.r8.utils.MapUtils.ignoreKey;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClassAndMember;
+import com.android.tools.r8.graph.DexMember;
+import com.android.tools.r8.graph.GraphLens;
+import com.android.tools.r8.graph.PrunedItems;
+import com.android.tools.r8.ir.analysis.type.DynamicType;
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
+import com.android.tools.r8.ir.optimize.membervaluepropagation.assume.AssumeInfo;
+import com.android.tools.r8.utils.MapUtils;
+import java.util.IdentityHashMap;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.Consumer;
+
+public class AssumeInfoCollection {
+
+ private final Map<DexMember<?, ?>, AssumeInfo> backing;
+
+ AssumeInfoCollection(Map<DexMember<?, ?>, AssumeInfo> backing) {
+ assert backing.values().stream().noneMatch(AssumeInfo::isEmpty);
+ this.backing = backing;
+ }
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ public boolean contains(DexClassAndMember<?, ?> member) {
+ return backing.containsKey(member.getReference());
+ }
+
+ private AssumeInfo get(DexMember<?, ?> member) {
+ return backing.getOrDefault(member, AssumeInfo.empty());
+ }
+
+ public AssumeInfo get(DexClassAndMember<?, ?> member) {
+ return get(member.getReference());
+ }
+
+ public boolean isSideEffectFree(DexMember<?, ?> member) {
+ return get(member).isSideEffectFree();
+ }
+
+ public boolean isSideEffectFree(DexClassAndMember<?, ?> member) {
+ return isSideEffectFree(member.getReference());
+ }
+
+ public AssumeInfoCollection rewrittenWithLens(AppView<?> appView, GraphLens graphLens) {
+ Map<DexMember<?, ?>, AssumeInfo> rewrittenCollection = new IdentityHashMap<>();
+ backing.forEach(
+ (reference, info) -> {
+ DexMember<?, ?> rewrittenReference = graphLens.getRenamedMemberSignature(reference);
+ AssumeInfo rewrittenInfo = info.rewrittenWithLens(appView, graphLens);
+ assert !rewrittenInfo.isEmpty();
+ rewrittenCollection.put(rewrittenReference, rewrittenInfo);
+ });
+ return new AssumeInfoCollection(rewrittenCollection);
+ }
+
+ public AssumeInfoCollection withoutPrunedItems(PrunedItems prunedItems) {
+ Map<DexMember<?, ?>, AssumeInfo> rewrittenCollection = new IdentityHashMap<>();
+ backing.forEach(
+ (reference, info) -> {
+ if (!prunedItems.isRemoved(reference)) {
+ AssumeInfo rewrittenInfo = info.withoutPrunedItems(prunedItems);
+ if (!rewrittenInfo.isEmpty()) {
+ rewrittenCollection.put(reference, rewrittenInfo);
+ }
+ }
+ });
+ return new AssumeInfoCollection(rewrittenCollection);
+ }
+
+ public static class Builder {
+
+ private final Map<DexMember<?, ?>, AssumeInfo.Builder> backing = new ConcurrentHashMap<>();
+
+ public Builder applyIf(boolean condition, Consumer<Builder> consumer) {
+ if (condition) {
+ consumer.accept(this);
+ }
+ return this;
+ }
+
+ public AssumeInfo buildInfo(DexClassAndMember<?, ?> member) {
+ AssumeInfo.Builder builder = backing.get(member.getReference());
+ return builder != null ? builder.build() : AssumeInfo.empty();
+ }
+
+ private AssumeInfo.Builder getOrCreateAssumeInfo(DexMember<?, ?> member) {
+ return backing.computeIfAbsent(member, ignoreKey(AssumeInfo::builder));
+ }
+
+ private AssumeInfo.Builder getOrCreateAssumeInfo(DexClassAndMember<?, ?> member) {
+ return getOrCreateAssumeInfo(member.getReference());
+ }
+
+ public boolean isEmpty() {
+ return backing.isEmpty();
+ }
+
+ public Builder meet(DexMember<?, ?> member, AssumeInfo assumeInfo) {
+ getOrCreateAssumeInfo(member).meet(assumeInfo);
+ return this;
+ }
+
+ public Builder meetAssumeType(DexClassAndMember<?, ?> member, DynamicType assumeType) {
+ getOrCreateAssumeInfo(member).meetAssumeType(assumeType);
+ return this;
+ }
+
+ public Builder meetAssumeValue(DexClassAndMember<?, ?> member, AbstractValue assumeValue) {
+ getOrCreateAssumeInfo(member).meetAssumeValue(assumeValue);
+ return this;
+ }
+
+ public Builder setIsSideEffectFree(DexClassAndMember<?, ?> member) {
+ getOrCreateAssumeInfo(member).setIsSideEffectFree();
+ return this;
+ }
+
+ public AssumeInfoCollection build() {
+ return new AssumeInfoCollection(
+ MapUtils.newIdentityHashMap(
+ builder ->
+ backing.forEach(
+ (reference, infoBuilder) -> {
+ AssumeInfo info = infoBuilder.build();
+ if (!info.isEmpty()) {
+ builder.accept(reference, info);
+ }
+ }),
+ backing.size()));
+ }
+ }
+}
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 8dd3707..02f3eb9 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -4143,8 +4143,6 @@
callSites,
keepInfo,
rootSet.mayHaveSideEffects,
- rootSet.noSideEffects,
- rootSet.assumedValues,
amendWithCompanionMethods(rootSet.alwaysInline),
amendWithCompanionMethods(rootSet.neverInlineDueToSingleCaller),
amendWithCompanionMethods(rootSet.whyAreYouNotInlining),
diff --git a/src/main/java/com/android/tools/r8/shaking/EnqueuerDeferredTracingImpl.java b/src/main/java/com/android/tools/r8/shaking/EnqueuerDeferredTracingImpl.java
index 85d14b9..41c48cb 100644
--- a/src/main/java/com/android/tools/r8/shaking/EnqueuerDeferredTracingImpl.java
+++ b/src/main/java/com/android/tools/r8/shaking/EnqueuerDeferredTracingImpl.java
@@ -26,7 +26,6 @@
import com.android.tools.r8.ir.conversion.IRToDexFinalizer;
import com.android.tools.r8.ir.conversion.MethodConversionOptions.MutableMethodConversionOptions;
import com.android.tools.r8.ir.optimize.membervaluepropagation.assume.AssumeInfo;
-import com.android.tools.r8.ir.optimize.membervaluepropagation.assume.AssumeInfoLookup;
import com.android.tools.r8.shaking.Enqueuer.FieldAccessKind;
import com.android.tools.r8.shaking.Enqueuer.FieldAccessMetadata;
import com.android.tools.r8.shaking.Enqueuer.Mode;
@@ -99,8 +98,8 @@
// If the value of the field is not guaranteed to be the default value, even if it is never
// assigned, then give up.
// TODO(b/205810841): Allow this by handling this in the corresponding IR rewriter.
- AssumeInfo assumeInfo = AssumeInfoLookup.lookupAssumeInfo(appView, field);
- if (assumeInfo != null && assumeInfo.hasReturnInfo()) {
+ AssumeInfo assumeInfo = appView.getAssumeInfoCollection().get(field);
+ if (!assumeInfo.getAssumeValue().isUnknown()) {
return enqueueDeferredEnqueuerActions(field);
}
if (field.getAccessFlags().isStatic() && field.getDefinition().hasExplicitStaticValue()) {
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 34841ef..1a8c950 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
@@ -8,7 +8,6 @@
import com.android.tools.r8.InputDependencyGraphConsumer;
import com.android.tools.r8.Version;
import com.android.tools.r8.dex.Constants;
-import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
@@ -19,7 +18,6 @@
import com.android.tools.r8.position.TextRange;
import com.android.tools.r8.shaking.ProguardConfiguration.Builder;
import com.android.tools.r8.shaking.ProguardTypeMatcher.ClassOrType;
-import com.android.tools.r8.shaking.ProguardTypeMatcher.MatchSpecificType;
import com.android.tools.r8.shaking.ProguardWildcard.BackReference;
import com.android.tools.r8.shaking.ProguardWildcard.Pattern;
import com.android.tools.r8.utils.IdentifierUtils;
@@ -1475,23 +1473,16 @@
ruleBuilder.setReturnValue(
new ProguardMemberRuleReturnValue(new LongInterval(min, max)));
} else {
- if (ruleBuilder.getTypeMatcher() instanceof MatchSpecificType) {
- int lastDotIndex = qualifiedFieldNameOrInteger.lastIndexOf(".");
- DexType fieldType =
- ((MatchSpecificType) ruleBuilder.getTypeMatcher()).type;
- DexType fieldClass =
- dexItemFactory.createType(
- javaTypeToDescriptor(
- qualifiedFieldNameOrInteger.substring(0, lastDotIndex)));
- DexString fieldName =
- dexItemFactory.createString(
- qualifiedFieldNameOrInteger.substring(lastDotIndex + 1));
- DexField field =
- dexItemFactory.createField(fieldClass, fieldType, fieldName);
- ruleBuilder.setReturnValue(new ProguardMemberRuleReturnValue(field));
- } else {
- throw parseError("Expected specific type", fieldOrValueStart);
- }
+ int lastDotIndex = qualifiedFieldNameOrInteger.lastIndexOf(".");
+ DexType fieldHolder =
+ dexItemFactory.createType(
+ javaTypeToDescriptor(
+ qualifiedFieldNameOrInteger.substring(0, lastDotIndex)));
+ DexString fieldName =
+ dexItemFactory.createString(
+ qualifiedFieldNameOrInteger.substring(lastDotIndex + 1));
+ ruleBuilder.setReturnValue(
+ new ProguardMemberRuleReturnValue(fieldHolder, fieldName));
}
}
}
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardMemberRuleReturnValue.java b/src/main/java/com/android/tools/r8/shaking/ProguardMemberRuleReturnValue.java
index 11b8868..8894a53 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardMemberRuleReturnValue.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardMemberRuleReturnValue.java
@@ -4,7 +4,17 @@
package com.android.tools.r8.shaking;
-import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.analysis.type.DynamicType;
+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.objectstate.ObjectState;
+import com.android.tools.r8.utils.BooleanUtils;
import com.android.tools.r8.utils.LongInterval;
public class ProguardMemberRuleReturnValue {
@@ -18,34 +28,44 @@
private final Type type;
private final boolean booleanValue;
private final LongInterval longInterval;
- private final DexField field;
+ private final DexType fieldHolder;
+ private final DexString fieldName;
+ private final boolean notNull;
ProguardMemberRuleReturnValue(boolean value) {
this.type = Type.BOOLEAN;
this.booleanValue = value;
this.longInterval = null;
- this.field = null;
+ this.fieldHolder = null;
+ this.fieldName = null;
+ this.notNull = false;
}
ProguardMemberRuleReturnValue(LongInterval value) {
this.type = Type.VALUE_RANGE;
this.booleanValue = false;
this.longInterval = value;
- this.field = null;
+ this.fieldHolder = null;
+ this.fieldName = null;
+ this.notNull = false;
}
- ProguardMemberRuleReturnValue(DexField field) {
+ ProguardMemberRuleReturnValue(DexType fieldHolder, DexString fieldName) {
this.type = Type.FIELD;
this.booleanValue = false;
this.longInterval = null;
- this.field = field;
+ this.fieldHolder = fieldHolder;
+ this.fieldName = fieldName;
+ this.notNull = false;
}
ProguardMemberRuleReturnValue() {
this.type = Type.NULL;
this.booleanValue = false;
this.longInterval = null;
- this.field = null;
+ this.fieldHolder = null;
+ this.fieldName = null;
+ this.notNull = false;
}
public boolean isBoolean() {
@@ -105,9 +125,60 @@
return longInterval;
}
- public DexField getField() {
+ public DexType getFieldHolder() {
assert isField();
- return field;
+ return fieldHolder;
+ }
+
+ public DexString getFieldName() {
+ assert isField();
+ return fieldName;
+ }
+
+ public AbstractValue toAbstractValue(AppView<?> appView, DexType valueType) {
+ AbstractValueFactory abstractValueFactory = appView.abstractValueFactory();
+ switch (type) {
+ case BOOLEAN:
+ return abstractValueFactory.createSingleNumberValue(BooleanUtils.intValue(booleanValue));
+ case FIELD:
+ DexClass holder = appView.definitionFor(fieldHolder);
+ if (holder != null) {
+ DexEncodedField field = holder.lookupUniqueStaticFieldWithName(fieldName);
+ if (field != null) {
+ return abstractValueFactory.createSingleFieldValue(
+ field.getReference(), ObjectState.empty());
+ }
+ }
+ return AbstractValue.unknown();
+ case NULL:
+ return abstractValueFactory.createNullValue();
+ case VALUE_RANGE:
+ if (longInterval.isSingleValue()) {
+ long singleValue = longInterval.getSingleValue();
+ if (valueType.isReferenceType()) {
+ return singleValue == 0
+ ? abstractValueFactory.createNullValue()
+ : AbstractValue.unknown();
+ }
+ return abstractValueFactory.createSingleNumberValue(singleValue);
+ }
+ return abstractValueFactory.createNumberFromIntervalValue(
+ longInterval.getMin(), longInterval.getMax());
+ default:
+ throw new Unreachable("Unexpected type: " + type);
+ }
+ }
+
+ public DynamicType toDynamicType(AppView<?> appView, DexType valueType) {
+ if (valueType.isReferenceType()) {
+ if (type == Type.VALUE_RANGE && !longInterval.containsValue(0)) {
+ return DynamicType.definitelyNotNull();
+ }
+ if (notNull) {
+ return DynamicType.definitelyNotNull();
+ }
+ }
+ return DynamicType.unknown();
}
@Override
@@ -126,7 +197,7 @@
}
} else {
assert isField();
- result.append(field.holder.toSourceString() + '.' + field.name);
+ result.append(getFieldHolder().getTypeName()).append('.').append(getFieldName());
}
return result.toString();
}
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 471f865..c8818b1 100644
--- a/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java
@@ -10,6 +10,7 @@
import com.android.tools.r8.dex.Constants;
import com.android.tools.r8.errors.AssumeNoSideEffectsRuleForObjectMembersDiagnostic;
+import com.android.tools.r8.errors.AssumeValuesMissingStaticFieldDiagnostic;
import com.android.tools.r8.errors.InlinableStaticFinalFieldPreconditionDiagnostic;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
@@ -45,9 +46,12 @@
import com.android.tools.r8.graph.PrunedItems;
import com.android.tools.r8.graph.SubtypingInfo;
import com.android.tools.r8.ir.analysis.proto.GeneratedMessageLiteBuilderShrinker;
+import com.android.tools.r8.ir.analysis.type.DynamicType;
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
import com.android.tools.r8.ir.desugar.itf.InterfaceDesugaringSyntheticHelper;
import com.android.tools.r8.ir.desugar.itf.InterfaceMethodDesugaringBaseEventConsumer;
import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackSimple;
+import com.android.tools.r8.ir.optimize.membervaluepropagation.assume.AssumeInfo;
import com.android.tools.r8.logging.Log;
import com.android.tools.r8.shaking.AnnotationMatchResult.AnnotationsIgnoredMatchResult;
import com.android.tools.r8.shaking.AnnotationMatchResult.ConcreteAnnotationMatchResult;
@@ -105,6 +109,7 @@
public static class RootSetBuilder {
private final AppView<? extends AppInfoWithClassHierarchy> appView;
+ private AssumeInfoCollection.Builder assumeInfoCollectionBuilder;
private final SubtypingInfo subtypingInfo;
private final DirectMappedDexApplication application;
private final Iterable<? extends ProguardConfigurationRule> rules;
@@ -127,8 +132,6 @@
new IdentityHashMap<>();
private final Map<DexReference, ProguardMemberRule> mayHaveSideEffects =
new IdentityHashMap<>();
- private final Map<DexMember<?, ?>, ProguardMemberRule> noSideEffects = new IdentityHashMap<>();
- private final Map<DexMember<?, ?>, ProguardMemberRule> assumedValues = new IdentityHashMap<>();
private final Set<DexMember<?, ?>> identifierNameStrings = Sets.newIdentityHashSet();
private final Map<DexMethod, ProgramMethod> keptMethodBridges = new ConcurrentHashMap<>();
private final Queue<DelayedRootSetActionItem> delayedRootSetActionItems =
@@ -176,6 +179,12 @@
// Intentionally empty.
}
+ public RootSetBuilder setAssumeInfoCollectionBuilder(
+ AssumeInfoCollection.Builder assumeInfoCollectionBuilder) {
+ this.assumeInfoCollectionBuilder = assumeInfoCollectionBuilder;
+ return this;
+ }
+
// Process a class with the keep rule.
private void process(DexClass clazz, ProguardConfigurationRule rule, ProguardIfRule ifRule) {
if (!satisfyClassType(rule, clazz)) {
@@ -256,13 +265,17 @@
markClass(clazz, rule, ifRule);
markMatchingVisibleMethods(clazz, memberKeepRules, rule, null, true, ifRule);
markMatchingVisibleFields(clazz, memberKeepRules, rule, null, true, ifRule);
- } else if (rule instanceof ProguardAssumeMayHaveSideEffectsRule
- || rule instanceof ProguardAssumeNoSideEffectRule
- || rule instanceof ProguardAssumeValuesRule) {
+ } else if (rule instanceof ProguardAssumeMayHaveSideEffectsRule) {
markMatchingVisibleMethods(clazz, memberKeepRules, rule, null, true, ifRule);
- markMatchingOverriddenMethods(
- appView.appInfo(), clazz, memberKeepRules, rule, null, true, ifRule);
+ markMatchingOverriddenMethods(clazz, memberKeepRules, rule, null, true, ifRule);
markMatchingVisibleFields(clazz, memberKeepRules, rule, null, true, ifRule);
+ } else if (rule instanceof ProguardAssumeNoSideEffectRule
+ || rule instanceof ProguardAssumeValuesRule) {
+ if (assumeInfoCollectionBuilder != null) {
+ markMatchingVisibleMethods(clazz, memberKeepRules, rule, null, true, ifRule);
+ markMatchingOverriddenMethods(clazz, memberKeepRules, rule, null, true, ifRule);
+ markMatchingVisibleFields(clazz, memberKeepRules, rule, null, true, ifRule);
+ }
} else if (rule instanceof NoFieldTypeStrengtheningRule) {
markMatchingFields(clazz, memberKeepRules, rule, null, ifRule);
} else if (rule instanceof InlineRule
@@ -349,7 +362,7 @@
}
finalizeCheckDiscardedInformation();
generateAssumeNoSideEffectsWarnings();
- if (!noSideEffects.isEmpty() || !assumedValues.isEmpty()) {
+ if (assumeInfoCollectionBuilder != null && !assumeInfoCollectionBuilder.isEmpty()) {
BottomUpClassHierarchyTraversal.forAllClasses(appView, subtypingInfo)
.visit(appView.appInfo().classes(), this::propagateAssumeRules);
}
@@ -381,8 +394,6 @@
noHorizontalClassMerging,
neverPropagateValue,
mayHaveSideEffects,
- noSideEffects,
- assumedValues,
dependentKeepClassCompatRule,
identifierNameStrings,
ifRules,
@@ -401,17 +412,12 @@
assert !encodedMethod.shouldNotHaveCode();
continue;
}
- propagateAssumeRules(clazz.type, encodedMethod.getReference(), subTypes, noSideEffects);
- propagateAssumeRules(clazz.type, encodedMethod.getReference(), subTypes, assumedValues);
+ propagateAssumeRules(clazz, encodedMethod.getReference(), subTypes);
}
}
- private void propagateAssumeRules(
- DexType type,
- DexMethod reference,
- Set<DexType> subTypes,
- Map<DexMember<?, ?>, ProguardMemberRule> assumeRulePool) {
- ProguardMemberRule ruleToBePropagated = null;
+ private void propagateAssumeRules(DexClass clazz, DexMethod reference, Set<DexType> subTypes) {
+ AssumeInfo infoToBePropagated = null;
for (DexType subType : subTypes) {
DexMethod referenceInSubType =
appView.dexItemFactory().createMethod(subType, reference.proto, reference.name);
@@ -419,34 +425,34 @@
// override the method, and when the retrieval of bound rule fails, it is unclear whether it
// is due to the lack of the definition or it indeed means no matching rules. Similar to how
// we apply those assume rules, here we use a resolved target.
- DexEncodedMethod target =
+ DexClassAndMethod target =
appView
.appInfo()
.unsafeResolveMethodDueToDexFormatLegacy(referenceInSubType)
- .getSingleTarget();
+ .getResolutionPair();
// But, the resolution should not be landed on the current type we are visiting.
- if (target == null || target.getHolderType() == type) {
+ if (target == null || target.getHolder() == clazz) {
continue;
}
- ProguardMemberRule ruleInSubType = assumeRulePool.get(target.getReference());
+ AssumeInfo ruleInSubType = assumeInfoCollectionBuilder.buildInfo(target);
// We are looking for the greatest lower bound of assume rules from all sub types.
// If any subtype doesn't have a matching assume rule, the lower bound is literally nothing.
if (ruleInSubType == null) {
- ruleToBePropagated = null;
+ infoToBePropagated = null;
break;
}
- if (ruleToBePropagated == null) {
- ruleToBePropagated = ruleInSubType;
+ if (infoToBePropagated == null) {
+ infoToBePropagated = ruleInSubType;
} else {
// TODO(b/133208961): Introduce comparison/meet of assume rules.
- if (!ruleToBePropagated.equals(ruleInSubType)) {
- ruleToBePropagated = null;
+ if (!infoToBePropagated.equals(ruleInSubType)) {
+ infoToBePropagated = null;
break;
}
}
}
- if (ruleToBePropagated != null) {
- assumeRulePool.put(reference, ruleToBePropagated);
+ if (infoToBePropagated != null) {
+ assumeInfoCollectionBuilder.meet(reference, infoToBePropagated);
}
}
@@ -663,7 +669,6 @@
}
private void markMatchingOverriddenMethods(
- AppInfoWithClassHierarchy appInfoWithSubtyping,
DexClass clazz,
Collection<ProguardMemberRule> memberKeepRules,
ProguardConfigurationRule rule,
@@ -1156,36 +1161,17 @@
return;
}
evaluateKeepRule(
- item.asProgramDefinition(), context.asProguardKeepRule(), rule, precondition, ifRule);
+ item.asProgramDefinition(), context.asProguardKeepRule(), precondition, ifRule);
} else if (context instanceof ProguardAssumeMayHaveSideEffectsRule) {
mayHaveSideEffects.put(item.getReference(), rule);
context.markAsUsed();
} else if (context instanceof ProguardAssumeNoSideEffectRule) {
- if (item.isMember()) {
- DexClassAndMember<?, ?> member = item.asMember();
- if (member.getHolderType() == appView.dexItemFactory().objectType) {
- assert member.isMethod();
- reportAssumeNoSideEffectsWarningForJavaLangClassMethod(
- member.asMethod(), (ProguardAssumeNoSideEffectRule) context);
- } else {
- noSideEffects.put(member.getReference(), rule);
- if (member.isMethod()) {
- DexClassAndMethod method = member.asMethod();
- if (method.getDefinition().isClassInitializer()) {
- feedback.classInitializerMayBePostponed(method.getDefinition());
- }
- }
- }
- context.markAsUsed();
- }
+ evaluateAssumeNoSideEffectsRule(item, (ProguardAssumeNoSideEffectRule) context, rule);
+ } else if (context instanceof ProguardAssumeValuesRule) {
+ evaluateAssumeValuesRule(item, (ProguardAssumeValuesRule) context, rule);
} else if (context instanceof ProguardWhyAreYouKeepingRule) {
reasonAsked.computeIfAbsent(item.getReference(), i -> i);
context.markAsUsed();
- } else if (context instanceof ProguardAssumeValuesRule) {
- if (item.isMember()) {
- assumedValues.put(item.asMember().getReference(), rule);
- context.markAsUsed();
- }
} else if (context.isProguardCheckDiscardRule()) {
assert item.isProgramMember();
evaluateCheckDiscardMemberRule(
@@ -1427,10 +1413,60 @@
}
}
+ private void evaluateAssumeNoSideEffectsRule(
+ Definition item, ProguardAssumeNoSideEffectRule context, ProguardMemberRule rule) {
+ assert assumeInfoCollectionBuilder != null;
+ if (!item.isMember()) {
+ return;
+ }
+ DexClassAndMember<?, ?> member = item.asMember();
+ if (member.getHolderType() == appView.dexItemFactory().objectType) {
+ assert member.isMethod();
+ reportAssumeNoSideEffectsWarningForJavaLangClassMethod(member.asMethod(), context);
+ } else {
+ DexType valueType =
+ member.getReference().apply(DexField::getType, DexMethod::getReturnType);
+ assumeInfoCollectionBuilder
+ .applyIf(
+ rule.hasReturnValue(),
+ builder -> {
+ DynamicType assumeType = rule.getReturnValue().toDynamicType(appView, valueType);
+ AbstractValue assumeValue =
+ rule.getReturnValue().toAbstractValue(appView, valueType);
+ builder.meetAssumeType(member, assumeType).meetAssumeValue(member, assumeValue);
+ reportAssumeValuesWarningForMissingReturnField(context, rule, assumeValue);
+ })
+ .setIsSideEffectFree(member);
+ if (member.isMethod()) {
+ DexClassAndMethod method = member.asMethod();
+ if (method.getDefinition().isClassInitializer()) {
+ feedback.classInitializerMayBePostponed(method.getDefinition());
+ }
+ }
+ }
+ context.markAsUsed();
+ }
+
+ private void evaluateAssumeValuesRule(
+ Definition item, ProguardAssumeValuesRule context, ProguardMemberRule rule) {
+ assert assumeInfoCollectionBuilder != null;
+ if (!item.isMember() || !rule.hasReturnValue()) {
+ return;
+ }
+ DexClassAndMember<?, ?> member = item.asMember();
+ DexType valueType = member.getReference().apply(DexField::getType, DexMethod::getReturnType);
+ DynamicType assumeType = rule.getReturnValue().toDynamicType(appView, valueType);
+ AbstractValue assumeValue = rule.getReturnValue().toAbstractValue(appView, valueType);
+ assumeInfoCollectionBuilder
+ .meetAssumeType(member, assumeType)
+ .meetAssumeValue(member, assumeValue);
+ reportAssumeValuesWarningForMissingReturnField(context, rule, assumeValue);
+ context.markAsUsed();
+ }
+
private void evaluateKeepRule(
ProgramDefinition item,
ProguardKeepRule context,
- ProguardMemberRule rule,
DexProgramClass precondition,
ProguardIfRule ifRule) {
if (item.isField()) {
@@ -1663,6 +1699,22 @@
.build());
});
}
+
+ private void reportAssumeValuesWarningForMissingReturnField(
+ ProguardConfigurationRule context, ProguardMemberRule rule, AbstractValue assumeValue) {
+ if (rule.hasReturnValue() && rule.getReturnValue().isField()) {
+ assert assumeValue.isSingleFieldValue() || assumeValue.isUnknown();
+ if (assumeValue.isUnknown()) {
+ ProguardMemberRuleReturnValue returnValue = rule.getReturnValue();
+ options.reporter.warning(
+ new AssumeValuesMissingStaticFieldDiagnostic.Builder()
+ .setField(returnValue.getFieldHolder(), returnValue.getFieldName())
+ .setOrigin(context.getOrigin())
+ .setPosition(context.getPosition())
+ .build());
+ }
+ }
+ }
}
abstract static class RootSetBase {
@@ -1712,8 +1764,6 @@
public final Set<DexType> noHorizontalClassMerging;
public final Set<DexMember<?, ?>> neverPropagateValue;
public final Map<DexReference, ProguardMemberRule> mayHaveSideEffects;
- public final Map<DexMember<?, ?>, ProguardMemberRule> noSideEffects;
- public final Map<DexMember<?, ?>, ProguardMemberRule> assumedValues;
public final Set<DexMember<?, ?>> identifierNameStrings;
public final Set<ProguardIfRule> ifRules;
@@ -1733,8 +1783,6 @@
Set<DexType> noHorizontalClassMerging,
Set<DexMember<?, ?>> neverPropagateValue,
Map<DexReference, ProguardMemberRule> mayHaveSideEffects,
- Map<DexMember<?, ?>, ProguardMemberRule> noSideEffects,
- Map<DexMember<?, ?>, ProguardMemberRule> assumedValues,
Map<DexType, Set<ProguardKeepRuleBase>> dependentKeepClassCompatRule,
Set<DexMember<?, ?>> identifierNameStrings,
Set<ProguardIfRule> ifRules,
@@ -1759,8 +1807,6 @@
this.noHorizontalClassMerging = noHorizontalClassMerging;
this.neverPropagateValue = neverPropagateValue;
this.mayHaveSideEffects = mayHaveSideEffects;
- this.noSideEffects = noSideEffects;
- this.assumedValues = assumedValues;
this.identifierNameStrings = Collections.unmodifiableSet(identifierNameStrings);
this.ifRules = Collections.unmodifiableSet(ifRules);
}
@@ -1819,7 +1865,6 @@
pruneDeadReferences(noVerticalClassMerging, definitions, enqueuer);
pruneDeadReferences(noHorizontalClassMerging, definitions, enqueuer);
pruneDeadReferences(alwaysInline, definitions, enqueuer);
- pruneDeadReferences(noSideEffects.keySet(), definitions, enqueuer);
}
private static void pruneDeadReferences(
@@ -1871,8 +1916,6 @@
noHorizontalClassMerging,
neverPropagateValue,
mayHaveSideEffects,
- noSideEffects,
- assumedValues,
dependentKeepClassCompatRule,
identifierNameStrings,
ifRules,
@@ -2044,8 +2087,6 @@
StringBuilder builder = new StringBuilder();
builder.append("RootSet");
builder.append("\nreasonAsked: " + reasonAsked.size());
- builder.append("\nnoSideEffects: " + noSideEffects.size());
- builder.append("\nassumedValues: " + assumedValues.size());
builder.append("\nidentifierNameStrings: " + identifierNameStrings.size());
builder.append("\nifRules: " + ifRules.size());
return builder.toString();
@@ -2164,8 +2205,6 @@
Collections.emptySet(),
emptyMap(),
emptyMap(),
- emptyMap(),
- emptyMap(),
Collections.emptySet(),
ifRules,
delayedRootSetActionItems,
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/LibraryFieldPropagationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/LibraryFieldPropagationTest.java
index 2a420cf..99de3f5 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/LibraryFieldPropagationTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/LibraryFieldPropagationTest.java
@@ -35,7 +35,8 @@
@Parameterized.Parameters(name = "{0}, with assume values rule: {1}")
public static List<Object[]> data() {
- return buildParameters(getTestParameters().withAllRuntimes().build(), BooleanUtils.values());
+ return buildParameters(
+ getTestParameters().withAllRuntimesAndApiLevels().build(), BooleanUtils.values());
}
public LibraryFieldPropagationTest(TestParameters parameters, boolean withAssumeValuesRule) {
@@ -71,7 +72,7 @@
withAssumeValuesRule
? "-assumevalues class java.lang.Thread { public int MIN_PRIORITY return 1; }"
: "")
- .setMinApi(parameters.getRuntime())
+ .setMinApi(parameters.getApiLevel())
.compile()
.inspect(this::verifyFieldValueNotPropagated)
.run(parameters.getRuntime(), MAIN)
diff --git a/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java b/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
index 44c8c6e..927fd18 100644
--- a/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
@@ -494,9 +494,8 @@
assertFalse(rule.getReturnValue().isValueRange());
assertTrue(rule.getReturnValue().isField());
assertFalse(rule.getReturnValue().isNull());
- assertEquals("com.google.C", rule.getReturnValue().getField().holder.toString());
- assertEquals("int", rule.getReturnValue().getField().type.toString());
- assertEquals("X", rule.getReturnValue().getField().name.toString());
+ assertEquals("com.google.C", rule.getReturnValue().getFieldHolder().getTypeName());
+ assertEquals("X", rule.getReturnValue().getFieldName().toString());
matches |= 1 << 4;
} else if (rule.getName().matches("returnsNull")) {
assertTrue(rule.hasReturnValue());
@@ -515,9 +514,8 @@
assertFalse(rule.getReturnValue().isValueRange());
assertTrue(rule.getReturnValue().isField());
assertFalse(rule.getReturnValue().isNull());
- assertEquals("com.google.C", rule.getReturnValue().getField().holder.toString());
- assertEquals("Object", rule.getReturnValue().getField().type.toString());
- assertEquals("X", rule.getReturnValue().getField().name.toString());
+ assertEquals("com.google.C", rule.getReturnValue().getFieldHolder().getTypeName());
+ assertEquals("X", rule.getReturnValue().getFieldName().toString());
matches |= 1 << 7;
} else {
fail("Unexpected");
@@ -581,9 +579,8 @@
assertFalse(rule.getReturnValue().isValueRange());
assertTrue(rule.getReturnValue().isField());
assertFalse(rule.getReturnValue().isNull());
- assertEquals("com.google.C", rule.getReturnValue().getField().holder.toString());
- assertEquals("int", rule.getReturnValue().getField().type.toString());
- assertEquals("X", rule.getReturnValue().getField().name.toString());
+ assertEquals("com.google.C", rule.getReturnValue().getFieldHolder().getTypeName());
+ assertEquals("X", rule.getReturnValue().getFieldName().toString());
matches |= 1 << 4;
} else if (rule.getName().matches("isNull")) {
assertTrue(rule.hasReturnValue());
@@ -602,9 +599,8 @@
assertFalse(rule.getReturnValue().isValueRange());
assertTrue(rule.getReturnValue().isField());
assertFalse(rule.getReturnValue().isNull());
- assertEquals("com.google.C", rule.getReturnValue().getField().holder.toString());
- assertEquals("Object", rule.getReturnValue().getField().type.toString());
- assertEquals("X", rule.getReturnValue().getField().name.toString());
+ assertEquals("com.google.C", rule.getReturnValue().getFieldHolder().getTypeName());
+ assertEquals("X", rule.getReturnValue().getFieldName().toString());
matches |= 1 << 7;
} else {
fail("Unexpected");
diff --git a/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/AssumenosideeffectsPropagationTest.java b/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/AssumenosideeffectsPropagationTest.java
index 8130a6f..b73fc3e 100644
--- a/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/AssumenosideeffectsPropagationTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/AssumenosideeffectsPropagationTest.java
@@ -88,15 +88,8 @@
? StringUtils.lines(
"[AnotherSub2, debug]: message08", "[AnotherSub2, debug]: message5", "The end")
: StringUtils.lines(
- // TODO(b/133208961): Introduce comparison/meet of assume rules.
- // Itf has side effects for all methods, since we don't compute the meet yet.
- "[Sub1, info]: message00",
"[Base1, debug]: message00",
- "[Sub1, verbose]: message00",
- "[Base2, info]: message08",
"[AnotherSub2, debug]: message08",
- "[AnotherSub2, verbose]: message08",
- // Base2#debug also has side effects.
"[AnotherSub2, debug]: message5",
"The end");
case NON_SPECIFIC_RULES_ALL:
diff --git a/src/test/java/com/android/tools/r8/shaking/assumevalues/AssumeReturnsFieldWithSubtypingTest.java b/src/test/java/com/android/tools/r8/shaking/assumevalues/AssumeReturnsFieldWithSubtypingTest.java
new file mode 100644
index 0000000..63ea0c7
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/assumevalues/AssumeReturnsFieldWithSubtypingTest.java
@@ -0,0 +1,58 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.shaking.assumevalues;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+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 AssumeReturnsFieldWithSubtypingTest 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(
+ "-assumevalues class " + Main.class.getTypeName() + " {",
+ " java.lang.Object getGreeting() return " + Main.class.getTypeName() + ".greeting;",
+ "}",
+ // TODO(b/233828966): Maybe disallow shrinking of this in the first round of shaking.
+ "-keepclassmembers,allowobfuscation class " + Main.class.getTypeName() + "{",
+ " java.lang.String greeting;",
+ "}")
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("Hello world!");
+ }
+
+ static class Main {
+
+ static String greeting = System.currentTimeMillis() > 0 ? "Hello world!" : null;
+
+ public static void main(String[] args) {
+ System.out.println(getGreeting());
+ }
+
+ static Object getGreeting() {
+ return "Unexpected";
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/assumevalues/AssumeValuesMissingStaticFieldDiagnosticTest.java b/src/test/java/com/android/tools/r8/shaking/assumevalues/AssumeValuesMissingStaticFieldDiagnosticTest.java
new file mode 100644
index 0000000..cc00fac
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/assumevalues/AssumeValuesMissingStaticFieldDiagnosticTest.java
@@ -0,0 +1,63 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.shaking.assumevalues;
+
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticType;
+import static org.hamcrest.CoreMatchers.allOf;
+import static org.hamcrest.CoreMatchers.equalTo;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.errors.AssumeValuesMissingStaticFieldDiagnostic;
+import com.android.tools.r8.utils.AndroidApiLevel;
+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 AssumeValuesMissingStaticFieldDiagnosticTest extends TestBase {
+
+ @Parameter(0)
+ public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withNoneRuntime().build();
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(Backend.DEX)
+ .addInnerClasses(getClass())
+ .addKeepMainRule(Main.class)
+ .addKeepRules(
+ "-assumevalues class " + Main.class.getTypeName() + " {",
+ " static java.lang.Object get() return Missing.field;",
+ "}")
+ .allowDiagnosticWarningMessages()
+ .setMinApi(AndroidApiLevel.LATEST)
+ .compileWithExpectedDiagnostics(
+ diagnostics ->
+ diagnostics.assertWarningsMatch(
+ allOf(
+ diagnosticType(AssumeValuesMissingStaticFieldDiagnostic.class),
+ diagnosticMessage(
+ equalTo(
+ "The field Missing.field is used as the return value in an "
+ + "-assumenosideeffects or -assumevalues rule, but no such "
+ + "static field exists.")))));
+ }
+
+ static class Main {
+
+ static Object get() {
+ return null;
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/assumevalues/SynthesizedRulesFromApiLevelTest.java b/src/test/java/com/android/tools/r8/shaking/assumevalues/SynthesizedRulesFromApiLevelTest.java
index b2f0964..9b5cc93 100644
--- a/src/test/java/com/android/tools/r8/shaking/assumevalues/SynthesizedRulesFromApiLevelTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/assumevalues/SynthesizedRulesFromApiLevelTest.java
@@ -303,13 +303,17 @@
"-assumevalues class android.os.Build$VERSION { *; }"
};
- for (String rule : rules) {
+ for (int ruleIndex = 0; ruleIndex < rules.length; ruleIndex++) {
+ final int finalRuleIndex = ruleIndex;
+ String rule = rules[ruleIndex];
runTest(
AndroidApiLevel.O_MR1,
AndroidApiLevel.O_MR1,
AndroidApiLevel.O_MR1,
expectedResultForNative(AndroidApiLevel.O_MR1),
- builder -> builder.allowUnusedProguardConfigurationRules(backend == Backend.CF),
+ builder ->
+ builder.allowUnusedProguardConfigurationRules(
+ backend == Backend.CF || finalRuleIndex >= 4),
this::compatCodePresent,
ImmutableList.of(rule),
SynthesizedRule.NOT_PRESENT);