Add access modification bit to keep info.
Bug: 157012327
Change-Id: I8030aafc43d58e2287c29d09a06570cf2fdcce2d
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
index 3c55b1a..9c5e2e4 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
@@ -381,8 +381,8 @@
for (DexType type : enumsToUnbox) {
DexProgramClass clazz = asProgramClassOrNull(appView.definitionFor(type));
assert !keepInfo.getClassInfo(clazz).isPinned();
- clazz.forEachProgramMethod(keepInfo::unpinMethod);
- clazz.forEachField(field -> keepInfo.unpinField(clazz, field));
+ clazz.forEachProgramMethod(keepInfo::unsafeUnpinMethod);
+ clazz.forEachField(field -> keepInfo.unsafeUnpinField(clazz, field));
}
});
}
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 4d3b6aa..df5a09c 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -133,12 +133,6 @@
public final Set<DexCallSite> callSites;
/** Collection of keep requirements for the program. */
private final KeepInfoCollection keepInfo;
- /**
- * Set of kept items that are allowed to be publicized.
- *
- * <p>TODO(b/156715504): merge into keep info.
- */
- private final Set<DexReference> allowAccessModification;
/** All items with assumemayhavesideeffects rule. */
public final Map<DexReference, ProguardMemberRule> mayHaveSideEffects;
/** All items with assumenosideeffects rule. */
@@ -217,7 +211,6 @@
SortedMap<DexMethod, ProgramMethodSet> staticInvokes,
Set<DexCallSite> callSites,
KeepInfoCollection keepInfo,
- Set<DexReference> allowAccessModification,
Map<DexReference, ProguardMemberRule> mayHaveSideEffects,
Map<DexReference, ProguardMemberRule> noSideEffects,
Map<DexReference, ProguardMemberRule> assumedValues,
@@ -253,7 +246,6 @@
this.fieldAccessInfoCollection = fieldAccessInfoCollection;
this.objectAllocationInfoCollection = objectAllocationInfoCollection;
this.keepInfo = keepInfo;
- this.allowAccessModification = allowAccessModification;
this.mayHaveSideEffects = mayHaveSideEffects;
this.noSideEffects = noSideEffects;
this.assumedValues = assumedValues;
@@ -304,7 +296,6 @@
SortedMap<DexMethod, ProgramMethodSet> staticInvokes,
Set<DexCallSite> callSites,
KeepInfoCollection keepInfo,
- Set<DexReference> allowAccessModification,
Map<DexReference, ProguardMemberRule> mayHaveSideEffects,
Map<DexReference, ProguardMemberRule> noSideEffects,
Map<DexReference, ProguardMemberRule> assumedValues,
@@ -340,7 +331,6 @@
this.fieldAccessInfoCollection = fieldAccessInfoCollection;
this.objectAllocationInfoCollection = objectAllocationInfoCollection;
this.keepInfo = keepInfo;
- this.allowAccessModification = allowAccessModification;
this.mayHaveSideEffects = mayHaveSideEffects;
this.noSideEffects = noSideEffects;
this.assumedValues = assumedValues;
@@ -392,7 +382,6 @@
previous.staticInvokes,
previous.callSites,
previous.keepInfo,
- previous.allowAccessModification,
previous.mayHaveSideEffects,
previous.noSideEffects,
previous.assumedValues,
@@ -443,7 +432,6 @@
previous.staticInvokes,
previous.callSites,
extendPinnedItems(previous, additionalPinnedItems),
- previous.allowAccessModification,
previous.mayHaveSideEffects,
previous.noSideEffects,
previous.assumedValues,
@@ -526,7 +514,6 @@
this.fieldAccessInfoCollection = previous.fieldAccessInfoCollection;
this.objectAllocationInfoCollection = previous.objectAllocationInfoCollection;
this.keepInfo = previous.keepInfo;
- this.allowAccessModification = previous.allowAccessModification;
this.mayHaveSideEffects = previous.mayHaveSideEffects;
this.noSideEffects = previous.noSideEffects;
this.assumedValues = previous.assumedValues;
@@ -930,7 +917,9 @@
public boolean isAccessModificationAllowed(DexReference reference) {
assert options().getProguardConfiguration().isAccessModificationAllowed();
- return allowAccessModification.contains(reference) || !isPinned(reference);
+ return keepInfo
+ .getInfo(reference, this)
+ .isAccessModificationAllowed(options().getProguardConfiguration());
}
public boolean isPinned(DexReference reference) {
@@ -1025,7 +1014,6 @@
// after second tree shaking.
callSites,
keepInfo.rewrite(lens),
- lens.rewriteReferences(allowAccessModification),
lens.rewriteReferenceKeys(mayHaveSideEffects),
lens.rewriteReferenceKeys(noSideEffects),
lens.rewriteReferenceKeys(assumedValues),
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 01032ab..9938f36 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -310,13 +310,6 @@
private final MutableKeepInfoCollection keepInfo = new MutableKeepInfoCollection();
/**
- * A set of references that we are keeping due to keep rules, which we are allowed to publicize.
- */
- // TODO(b/156715504): This should be maintained in a structure that describes what we are allowed
- // and not allowed to do with program items that are referenced from keep rules.
- private final Map<DexReference, OptionalBool> allowAccessModification = new IdentityHashMap<>();
-
- /**
* A set of seen const-class references that both serve as an initial lock-candidate set and will
* prevent statically merging the classes referenced.
*/
@@ -643,7 +636,7 @@
if (item.isDexClass()) {
DexProgramClass clazz = item.asDexClass().asProgramClass();
KeepReasonWitness witness = graphReporter.reportKeepClass(precondition, rules, clazz);
- keepInfo.pinClass(clazz);
+ keepClassWithRules(clazz, rules);
if (clazz.isAnnotation()) {
workList.enqueueMarkAnnotationInstantiatedAction(clazz, witness);
} else if (clazz.isInterface()) {
@@ -666,7 +659,7 @@
DexEncodedField field = item.asDexEncodedField();
DexProgramClass holder = getProgramClassOrNull(field.holder());
if (holder != null) {
- keepInfo.pinField(holder, field);
+ keepFieldWithRules(holder, field, rules);
workList.enqueueMarkFieldKeptAction(
new ProgramField(holder, field),
graphReporter.reportKeepField(precondition, rules, field));
@@ -675,7 +668,7 @@
DexEncodedMethod encodedMethod = item.asDexEncodedMethod();
DexProgramClass holder = getProgramClassOrNull(encodedMethod.holder());
if (holder != null) {
- keepInfo.pinMethod(holder, encodedMethod);
+ keepMethodWithRules(holder, encodedMethod, rules);
workList.enqueueMarkMethodKeptAction(
new ProgramMethod(holder, encodedMethod),
graphReporter.reportKeepMethod(precondition, rules, encodedMethod));
@@ -683,7 +676,6 @@
} else {
throw new IllegalArgumentException(item.toString());
}
- setAllowAccessModification(item.toReference(), rules);
}
private void enqueueFirstNonSerializableClassInitializer(
@@ -1814,13 +1806,11 @@
(dexType, ignored) -> {
if (holder.isProgramClass()) {
DexProgramClass holderClass = holder.asProgramClass();
- keepInfo.pinClass(holderClass);
- setAllowAccessModification(holderClass.type);
+ keepInfo.keepClass(holderClass);
rootSet.shouldNotBeMinified(holder.toReference());
for (DexEncodedMember<?, ?> member : holder.members()) {
- keepInfo.pinMember(holderClass, member);
+ keepInfo.keepMember(holderClass, member);
DexMember<?, ?> memberReference = member.toReference();
- setAllowAccessModification(memberReference);
rootSet.shouldNotBeMinified(memberReference);
}
}
@@ -2535,8 +2525,7 @@
// TODO(sgjesse): Does this have to be enqueued as a root item? Right now it is done as the
// marking for not renaming it is in the root set.
workList.enqueueMarkMethodKeptAction(new ProgramMethod(clazz, valuesMethod), reason);
- keepInfo.pinMethod(clazz, valuesMethod);
- setAllowAccessModification(valuesMethod.toReference());
+ keepInfo.keepMethod(clazz, valuesMethod);
rootSet.shouldNotBeMinified(valuesMethod.toReference());
}
}
@@ -2640,27 +2629,44 @@
return appInfoWithLiveness;
}
- private void setAllowAccessModification(DexReference reference) {
- allowAccessModification.put(reference, OptionalBool.unknown());
+ private void keepClassWithRules(DexProgramClass clazz, Set<ProguardKeepRuleBase> rules) {
+ keepInfo.joinClass(
+ clazz,
+ info ->
+ info.pin()
+ .lazyDisallowAccessModification(() -> computeDisallowAccessModification(rules)));
}
- private void setAllowAccessModification(DexReference reference, Set<ProguardKeepRuleBase> rules) {
- assert rules != null;
- assert !rules.isEmpty();
- OptionalBool allowAccessModificationOfReference =
- allowAccessModification.getOrDefault(reference, OptionalBool.TRUE);
- if (allowAccessModificationOfReference.isTrue()) {
- for (ProguardKeepRuleBase rule : rules) {
- ProguardKeepRuleModifiers modifiers =
- (rule.isProguardIfRule() ? rule.asProguardIfRule().getSubsequentRule() : rule)
- .getModifiers();
- if (!modifiers.allowsAccessModification) {
- allowAccessModificationOfReference = OptionalBool.FALSE;
- break;
- }
+ private void keepMethodWithRules(
+ DexProgramClass holder, DexEncodedMethod method, Set<ProguardKeepRuleBase> rules) {
+ keepInfo.joinMethod(
+ holder,
+ method,
+ info ->
+ info.pin()
+ .lazyDisallowAccessModification(() -> computeDisallowAccessModification(rules)));
+ }
+
+ private void keepFieldWithRules(
+ DexProgramClass holder, DexEncodedField field, Set<ProguardKeepRuleBase> rules) {
+ keepInfo.joinField(
+ holder,
+ field,
+ info ->
+ info.pin()
+ .lazyDisallowAccessModification(() -> computeDisallowAccessModification(rules)));
+ }
+
+ private boolean computeDisallowAccessModification(Set<ProguardKeepRuleBase> rules) {
+ for (ProguardKeepRuleBase rule : rules) {
+ ProguardKeepRuleModifiers modifiers =
+ (rule.isProguardIfRule() ? rule.asProguardIfRule().getSubsequentRule() : rule)
+ .getModifiers();
+ if (!modifiers.allowsAccessModification) {
+ return true;
}
- allowAccessModification.put(reference, allowAccessModificationOfReference);
}
+ return false;
}
private static class SyntheticAdditions {
@@ -2672,15 +2678,16 @@
Map<DexType, DexClasspathClass> syntheticClasspathClasses = new IdentityHashMap<>();
- // Subset of live methods that need to be pinned.
- Set<ProgramMethod> pinnedMethods = Sets.newIdentityHashSet();
+ // Subset of live methods that need have keep requirements.
+ List<Pair<ProgramMethod, Consumer<KeepMethodInfo.Joiner>>> liveMethodsWithKeepActions =
+ new ArrayList<>();
// Subset of synthesized classes that need to be added to the main-dex file.
Set<DexType> mainDexTypes = Sets.newIdentityHashSet();
boolean isEmpty() {
boolean empty = syntheticInstantiations.isEmpty() && liveMethods.isEmpty();
- assert !empty || (pinnedMethods.isEmpty() && mainDexTypes.isEmpty());
+ assert !empty || (liveMethodsWithKeepActions.isEmpty() && mainDexTypes.isEmpty());
return empty;
}
@@ -2704,9 +2711,10 @@
liveMethods.put(signature, method);
}
- void addLiveAndPinnedMethod(ProgramMethod method) {
+ void addLiveMethodWithKeepAction(
+ ProgramMethod method, Consumer<KeepMethodInfo.Joiner> keepAction) {
addLiveMethod(method);
- pinnedMethods.add(method);
+ liveMethodsWithKeepActions.add(new Pair<>(method, keepAction));
}
void amendApplication(Builder appBuilder) {
@@ -2725,11 +2733,8 @@
// All synthetic additions are initial tree shaking only. No need to track keep reasons.
KeepReasonWitness fakeReason = enqueuer.graphReporter.fakeReportShouldNotBeUsed();
- pinnedMethods.forEach(
- method -> {
- enqueuer.keepInfo.pinMethod(method);
- enqueuer.setAllowAccessModification(method.getReference());
- });
+ liveMethodsWithKeepActions.forEach(
+ item -> enqueuer.keepInfo.joinMethod(item.getFirst(), item.getSecond()));
for (Pair<DexProgramClass, ProgramMethod> clazzAndContext :
syntheticInstantiations.values()) {
enqueuer.workList.enqueueMarkInstantiatedAction(
@@ -2779,7 +2784,7 @@
DexProgramClass holder = bridge.getHolder();
DexEncodedMethod method = bridge.getDefinition();
holder.addVirtualMethod(method);
- additions.addLiveAndPinnedMethod(bridge);
+ additions.addLiveMethodWithKeepAction(bridge, KeepMethodInfo.Joiner::pin);
}
syntheticInterfaceMethodBridges.clear();
}
@@ -2844,9 +2849,6 @@
// to a static method will invalidate the reachable method sets for tracing methods.
ensureLambdaAccessibility();
- // Remove the items from `allowAccessModification` that we are not allowed to publicize.
- allowAccessModification.entrySet().removeIf(entry -> !entry.getValue().isTrue());
-
// Compute the set of dead proto types.
deadProtoTypeCandidates.removeIf(this::isTypeLive);
Set<DexType> deadProtoTypes =
@@ -2921,7 +2923,6 @@
toImmutableSortedMap(staticInvokes, PresortedComparable::slowCompare),
callSites,
keepInfo,
- allowAccessModification.keySet(),
rootSet.mayHaveSideEffects,
rootSet.noSideEffects,
rootSet.assumedValues,
@@ -3329,7 +3330,7 @@
assert desugaredLambdaImplementationMethods.isEmpty()
|| options.desugarState == DesugarState.ON;
for (DexMethod method : desugaredLambdaImplementationMethods) {
- keepInfo.unpinMethod(method);
+ keepInfo.unsafeUnpinMethod(method);
rootSet.prune(method);
}
desugaredLambdaImplementationMethods.clear();
@@ -3618,7 +3619,6 @@
}
if (!keepInfo.getFieldInfo(encodedField, clazz).isPinned()) {
keepInfo.pinField(clazz, encodedField);
- setAllowAccessModification(encodedField.field);
markFieldAsKept(new ProgramField(clazz, encodedField), KeepReason.reflectiveUseIn(method));
}
} else {
@@ -3806,7 +3806,6 @@
// interface into its unique subtype, if any.
// TODO(b/145344105): This should be superseded by the unknown interface hierarchy.
keepInfo.pinClass(clazz);
- setAllowAccessModification(clazz.type);
KeepReason reason = KeepReason.reflectiveUseIn(method);
markInterfaceAsInstantiated(clazz, graphReporter.registerClass(clazz, reason));
@@ -3814,7 +3813,6 @@
// illegal rewritings of invoke-interface instructions into invoke-virtual instructions.
for (DexEncodedMethod virtualMethod : clazz.virtualMethods()) {
keepInfo.pinMethod(clazz, virtualMethod);
- setAllowAccessModification(virtualMethod.method);
markVirtualMethodAsReachable(virtualMethod.method, true, null, reason);
}
}
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 a8cf2df..58367a9 100644
--- a/src/main/java/com/android/tools/r8/shaking/KeepClassInfo.java
+++ b/src/main/java/com/android/tools/r8/shaking/KeepClassInfo.java
@@ -4,13 +4,13 @@
package com.android.tools.r8.shaking;
/** Immutable keep requirements for a class. */
-public final class KeepClassInfo extends KeepInfo {
+public final class KeepClassInfo extends KeepInfo<KeepClassInfo.Builder, KeepClassInfo> {
// Requires all aspects of a class to be kept.
- private static final KeepClassInfo TOP = new KeepClassInfo(true);
+ private static final KeepClassInfo TOP = new Builder().makeTop().build();
// Requires no aspects of a class to be kept.
- private static final KeepClassInfo BOTTOM = new KeepClassInfo(false);
+ private static final KeepClassInfo BOTTOM = new Builder().makeBottom().build();
public static KeepClassInfo top() {
return TOP;
@@ -20,27 +20,46 @@
return BOTTOM;
}
- private KeepClassInfo(boolean pinned) {
- super(pinned);
+ private KeepClassInfo(Builder builder) {
+ super(builder);
}
- public Builder builder() {
+ private Builder builder() {
return new Builder(this);
}
+ public Joiner joiner() {
+ assert !isTop();
+ return new Joiner(this);
+ }
+
+ @Override
+ public boolean isTop() {
+ return this.equals(top());
+ }
+
+ @Override
+ public boolean isBottom() {
+ return this.equals(bottom());
+ }
+
public static class Builder extends KeepInfo.Builder<Builder, KeepClassInfo> {
+ private Builder() {
+ super();
+ }
+
private Builder(KeepClassInfo original) {
super(original);
}
@Override
- public KeepClassInfo top() {
+ public KeepClassInfo getTopInfo() {
return TOP;
}
@Override
- public KeepClassInfo bottom() {
+ public KeepClassInfo getBottomInfo() {
return BOTTOM;
}
@@ -56,7 +75,19 @@
@Override
public KeepClassInfo doBuild() {
- return new KeepClassInfo(isPinned());
+ return new KeepClassInfo(this);
+ }
+ }
+
+ public static class Joiner extends KeepInfo.Joiner<Joiner, Builder, KeepClassInfo> {
+
+ public Joiner(KeepClassInfo info) {
+ super(info.builder());
+ }
+
+ @Override
+ Joiner self() {
+ return this;
}
}
}
diff --git a/src/main/java/com/android/tools/r8/shaking/KeepFieldInfo.java b/src/main/java/com/android/tools/r8/shaking/KeepFieldInfo.java
index 7416d8f..6f22db4 100644
--- a/src/main/java/com/android/tools/r8/shaking/KeepFieldInfo.java
+++ b/src/main/java/com/android/tools/r8/shaking/KeepFieldInfo.java
@@ -4,13 +4,13 @@
package com.android.tools.r8.shaking;
/** Immutable keep requirements for a field. */
-public final class KeepFieldInfo extends KeepInfo {
+public final class KeepFieldInfo extends KeepInfo<KeepFieldInfo.Builder, KeepFieldInfo> {
// Requires all aspects of a field to be kept.
- private static final KeepFieldInfo TOP = new KeepFieldInfo(true);
+ private static final KeepFieldInfo TOP = new Builder().makeTop().build();
// Requires no aspects of a field to be kept.
- private static final KeepFieldInfo BOTTOM = new KeepFieldInfo(false);
+ private static final KeepFieldInfo BOTTOM = new Builder().makeBottom().build();
public static KeepFieldInfo top() {
return TOP;
@@ -20,27 +20,48 @@
return BOTTOM;
}
- private KeepFieldInfo(boolean pinned) {
- super(pinned);
+ private KeepFieldInfo(Builder builder) {
+ super(builder);
}
- public Builder builder() {
+ // This builder is not private as there are known instances where it is safe to modify keep info
+ // in a non-upwards direction.
+ Builder builder() {
return new Builder(this);
}
+ public Joiner joiner() {
+ assert !isTop();
+ return new Joiner(this);
+ }
+
+ @Override
+ public boolean isTop() {
+ return this.equals(top());
+ }
+
+ @Override
+ public boolean isBottom() {
+ return this.equals(bottom());
+ }
+
public static class Builder extends KeepInfo.Builder<Builder, KeepFieldInfo> {
+ private Builder() {
+ super();
+ }
+
private Builder(KeepFieldInfo original) {
super(original);
}
@Override
- public KeepFieldInfo top() {
+ public KeepFieldInfo getTopInfo() {
return TOP;
}
@Override
- public KeepFieldInfo bottom() {
+ public KeepFieldInfo getBottomInfo() {
return BOTTOM;
}
@@ -56,7 +77,19 @@
@Override
public KeepFieldInfo doBuild() {
- return new KeepFieldInfo(isPinned());
+ return new KeepFieldInfo(this);
+ }
+ }
+
+ public static class Joiner extends KeepInfo.Joiner<Joiner, Builder, KeepFieldInfo> {
+
+ public Joiner(KeepFieldInfo info) {
+ super(info.builder());
+ }
+
+ @Override
+ Joiner self() {
+ return this;
}
}
}
diff --git a/src/main/java/com/android/tools/r8/shaking/KeepInfo.java b/src/main/java/com/android/tools/r8/shaking/KeepInfo.java
index 246eeb7..761547a 100644
--- a/src/main/java/com/android/tools/r8/shaking/KeepInfo.java
+++ b/src/main/java/com/android/tools/r8/shaking/KeepInfo.java
@@ -3,60 +3,125 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.shaking;
+import com.android.tools.r8.shaking.KeepInfo.Builder;
+import java.util.function.Supplier;
+
/** Keep information that can be associated with any item, i.e., class, method or field. */
-public abstract class KeepInfo {
+public abstract class KeepInfo<B extends Builder, K extends KeepInfo> {
private final boolean pinned;
+ private final boolean allowAccessModification;
- public KeepInfo(boolean pinned) {
+ private KeepInfo(boolean pinned, boolean allowAccessModification) {
this.pinned = pinned;
+ this.allowAccessModification = allowAccessModification;
}
+ KeepInfo(B builder) {
+ this(builder.isPinned(), builder.isAccessModificationAllowed());
+ }
+
+ /** True if an item must be present in the output. */
public boolean isPinned() {
return pinned;
}
+ /**
+ * True if an item may have its access flags modified.
+ *
+ * <p>This method requires knowledge of the global access modification as that will override the
+ * concrete value on a given item.
+ *
+ * @param configuration Global configuration object to determine access modification.
+ */
+ public boolean isAccessModificationAllowed(ProguardConfiguration configuration) {
+ return configuration.isAccessModificationAllowed() && internalIsAccessModificationAllowed();
+ }
+
+ // Internal accessor for the items access-modification bit.
+ boolean internalIsAccessModificationAllowed() {
+ return allowAccessModification;
+ }
+
+ public abstract boolean isTop();
+
+ public abstract boolean isBottom();
+
+ public boolean isLessThanOrEquals(K other) {
+ // An item is less, aka, lower in the lattice, if each of its attributes is at least as
+ // permissive of that on other.
+ return (!pinned || other.isPinned())
+ && (allowAccessModification || !other.internalIsAccessModificationAllowed());
+ }
+
+ /** Builder to construct an arbitrary keep info object. */
public abstract static class Builder<B extends Builder, K extends KeepInfo> {
- public abstract B self();
+ abstract B self();
- public abstract K doBuild();
+ abstract K doBuild();
- public abstract K top();
+ abstract K getTopInfo();
- public abstract K bottom();
+ abstract K getBottomInfo();
- public abstract boolean isEqualTo(K other);
+ abstract boolean isEqualTo(K other);
private K original;
private boolean pinned;
+ private boolean allowAccessModification;
- public Builder(K original) {
+ Builder() {
+ // Default initialized. Use should be followed by makeTop/makeBottom.
+ }
+
+ Builder(K original) {
this.original = original;
pinned = original.isPinned();
+ allowAccessModification = original.internalIsAccessModificationAllowed();
+ }
+
+ B makeTop() {
+ pin();
+ disallowAccessModification();
+ return self();
+ }
+
+ B makeBottom() {
+ unpin();
+ allowAccessModification();
+ return self();
}
public K build() {
- if (internalIsEqualTo(original)) {
- return original;
- }
- if (internalIsEqualTo(top())) {
- return top();
- }
- if (internalIsEqualTo(bottom())) {
- return bottom();
+ if (original != null) {
+ if (internalIsEqualTo(original)) {
+ return original;
+ }
+ if (internalIsEqualTo(getTopInfo())) {
+ return getTopInfo();
+ }
+ if (internalIsEqualTo(getBottomInfo())) {
+ return getBottomInfo();
+ }
}
return doBuild();
}
private boolean internalIsEqualTo(K other) {
- return isPinned() == other.isPinned() && isEqualTo(other);
+ return isPinned() == other.isPinned()
+ && isAccessModificationAllowed() == other.internalIsAccessModificationAllowed()
+ && isEqualTo(other);
}
public boolean isPinned() {
return pinned;
}
+ public boolean isAccessModificationAllowed() {
+ return allowAccessModification;
+ }
+
public B setPinned(boolean pinned) {
this.pinned = pinned;
return self();
@@ -69,5 +134,61 @@
public B unpin() {
return setPinned(false);
}
+
+ public B setAllowAccessModification(boolean allowAccessModification) {
+ this.allowAccessModification = allowAccessModification;
+ return self();
+ }
+
+ public B allowAccessModification() {
+ return setAllowAccessModification(true);
+ }
+
+ public B disallowAccessModification() {
+ return setAllowAccessModification(false);
+ }
+ }
+
+ /** Joiner to construct monotonically increasing keep info object. */
+ public abstract static class Joiner<
+ J extends Joiner, B extends Builder, K extends KeepInfo<B, K>> {
+
+ abstract J self();
+
+ private final Builder<B, K> builder;
+
+ Joiner(Builder<B, K> builder) {
+ this.builder = builder;
+ }
+
+ public boolean isTop() {
+ return builder.isEqualTo(builder.getTopInfo());
+ }
+
+ public J top() {
+ builder.makeTop();
+ return self();
+ }
+
+ public J pin() {
+ builder.pin();
+ return self();
+ }
+
+ // Lazy modification of access modification.
+ // Only forced if access modification is still allowed.
+ public J lazyDisallowAccessModification(Supplier<Boolean> lazyShouldDisallow) {
+ if (builder.isAccessModificationAllowed() && lazyShouldDisallow.get()) {
+ builder.disallowAccessModification();
+ }
+ return self();
+ }
+
+ public K join() {
+ K joined = builder.build();
+ K original = builder.original;
+ assert original.isLessThanOrEquals(joined);
+ return joined;
+ }
}
}
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 57c1f32..97a7fb2 100644
--- a/src/main/java/com/android/tools/r8/shaking/KeepInfoCollection.java
+++ b/src/main/java/com/android/tools/r8/shaking/KeepInfoCollection.java
@@ -19,6 +19,7 @@
import com.android.tools.r8.graph.GraphLense.NestedGraphLense;
import com.android.tools.r8.graph.ProgramField;
import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.shaking.KeepFieldInfo.Joiner;
import java.util.Collection;
import java.util.IdentityHashMap;
import java.util.Map;
@@ -216,59 +217,110 @@
return keepFieldInfo.getOrDefault(field.field, KeepFieldInfo.bottom());
}
- public void pinClass(DexProgramClass clazz) {
+ public void joinClass(DexProgramClass clazz, Consumer<KeepClassInfo.Joiner> fn) {
KeepClassInfo info = getClassInfo(clazz);
- if (!info.isPinned()) {
- keepClassInfo.put(clazz.type, info.builder().pin().build());
+ if (info.isTop()) {
+ return;
+ }
+ KeepClassInfo.Joiner joiner = info.joiner();
+ fn.accept(joiner);
+ KeepClassInfo joined = joiner.join();
+ if (!info.equals(joined)) {
+ keepClassInfo.put(clazz.type, joined);
}
}
- public void pinMember(DexProgramClass holder, DexEncodedMember<?, ?> member) {
- if (member.isDexEncodedMethod()) {
- pinMethod(holder, member.asDexEncodedMethod());
- } else {
- assert member.isDexEncodedField();
- pinField(holder, member.asDexEncodedField());
+ public void keepClass(DexProgramClass clazz) {
+ joinClass(clazz, KeepInfo.Joiner::top);
+ }
+
+ public void pinClass(DexProgramClass clazz) {
+ joinClass(clazz, KeepInfo.Joiner::pin);
+ }
+
+ public void joinMethod(
+ DexProgramClass holder, DexEncodedMethod method, Consumer<KeepMethodInfo.Joiner> fn) {
+ KeepMethodInfo info = getMethodInfo(method, holder);
+ if (info == KeepMethodInfo.top()) {
+ return;
+ }
+ KeepMethodInfo.Joiner joiner = info.joiner();
+ fn.accept(joiner);
+ KeepMethodInfo joined = joiner.join();
+ if (!info.equals(joined)) {
+ keepMethodInfo.put(method.method, joined);
}
}
- public void pinMethod(ProgramMethod programMethod) {
- pinMethod(programMethod.getHolder(), programMethod.getDefinition());
+ public void joinMethod(ProgramMethod programMethod, Consumer<KeepMethodInfo.Joiner> fn) {
+ joinMethod(programMethod.getHolder(), programMethod.getDefinition(), fn);
+ }
+
+ public void keepMethod(ProgramMethod programMethod) {
+ keepMethod(programMethod.getHolder(), programMethod.getDefinition());
+ }
+
+ public void keepMethod(DexProgramClass holder, DexEncodedMethod method) {
+ joinMethod(holder, method, KeepInfo.Joiner::top);
}
public void pinMethod(DexProgramClass holder, DexEncodedMethod method) {
- KeepMethodInfo info = getMethodInfo(method, holder);
- if (!info.isPinned()) {
- keepMethodInfo.put(method.method, info.builder().pin().build());
- }
+ joinMethod(holder, method, KeepInfo.Joiner::pin);
}
- public void unpinMethod(ProgramMethod method) {
+ // Unpinning a method represents a non-monotonic change to the keep info of that item.
+ // This is generally unsound as it requires additional analysis to determine that a method that
+ // was pinned no longer is. A known sound example is the enum analysis that will identify
+ // non-escaping enums on enum types that are not pinned, thus their methods do not need to be
+ // retained even if a rule has marked them as conditionally pinned.
+ public void unsafeUnpinMethod(ProgramMethod method) {
+ // This asserts that the holder is not pinned as some analysis must have established that the
+ // type is not "present" and thus the method need not be pinned.
assert !getClassInfo(method.getHolder()).isPinned();
- unpinMethod(method.getReference());
+ unsafeUnpinMethod(method.getReference());
}
- // TODO(b/156715504): We should never need to unpin items. Rather avoid pinning to begin with.
+ // TODO(b/156715504): Avoid pinning/unpinning references.
@Deprecated
- public void unpinMethod(DexMethod method) {
+ public void unsafeUnpinMethod(DexMethod method) {
KeepMethodInfo info = keepMethodInfo.get(method);
if (info != null && info.isPinned()) {
keepMethodInfo.put(method, info.builder().unpin().build());
}
}
- public void pinField(ProgramField programField) {
- pinField(programField.getHolder(), programField.getDefinition());
- }
-
- public void pinField(DexProgramClass holder, DexEncodedField field) {
+ public void joinField(
+ DexProgramClass holder, DexEncodedField field, Consumer<KeepFieldInfo.Joiner> fn) {
KeepFieldInfo info = getFieldInfo(field, holder);
- if (!info.isPinned()) {
- keepFieldInfo.put(field.field, info.builder().pin().build());
+ if (info.isTop()) {
+ return;
+ }
+ Joiner joiner = info.joiner();
+ fn.accept(joiner);
+ KeepFieldInfo joined = joiner.join();
+ if (!info.equals(joined)) {
+ keepFieldInfo.put(field.field, joined);
}
}
- public void unpinField(DexProgramClass holder, DexEncodedField field) {
+ public void keepField(DexProgramClass holder, DexEncodedField field) {
+ joinField(holder, field, KeepInfo.Joiner::top);
+ }
+
+ public void pinField(DexProgramClass holder, DexEncodedField field) {
+ joinField(holder, field, KeepInfo.Joiner::pin);
+ }
+
+ public void keepMember(DexProgramClass holder, DexEncodedMember<?, ?> member) {
+ if (member.isDexEncodedMethod()) {
+ keepMethod(holder, member.asDexEncodedMethod());
+ } else {
+ assert member.isDexEncodedField();
+ keepField(holder, member.asDexEncodedField());
+ }
+ }
+
+ public void unsafeUnpinField(DexProgramClass holder, DexEncodedField field) {
assert holder.type == field.holder();
assert !getClassInfo(holder).isPinned();
KeepFieldInfo info = this.keepFieldInfo.get(field.toReference());
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 f3bb073..98eae1b 100644
--- a/src/main/java/com/android/tools/r8/shaking/KeepMethodInfo.java
+++ b/src/main/java/com/android/tools/r8/shaking/KeepMethodInfo.java
@@ -4,13 +4,13 @@
package com.android.tools.r8.shaking;
/** Immutable keep requirements for a method. */
-public final class KeepMethodInfo extends KeepInfo {
+public final class KeepMethodInfo extends KeepInfo<KeepMethodInfo.Builder, KeepMethodInfo> {
// Requires all aspects of a method to be kept.
- private static final KeepMethodInfo TOP = new KeepMethodInfo(true);
+ private static final KeepMethodInfo TOP = new Builder().makeTop().build();
// Requires no aspects of a method to be kept.
- private static final KeepMethodInfo BOTTOM = new KeepMethodInfo(false);
+ private static final KeepMethodInfo BOTTOM = new Builder().makeBottom().build();
public static KeepMethodInfo top() {
return TOP;
@@ -20,16 +20,37 @@
return BOTTOM;
}
- private KeepMethodInfo(boolean pinned) {
- super(pinned);
+ private KeepMethodInfo(Builder builder) {
+ super(builder);
}
- public Builder builder() {
+ // This builder is not private as there are known instances where it is safe to modify keep info
+ // in a non-upwards direction.
+ Builder builder() {
return new Builder(this);
}
+ public Joiner joiner() {
+ assert !isTop();
+ return new Joiner(this);
+ }
+
+ @Override
+ public boolean isTop() {
+ return this.equals(top());
+ }
+
+ @Override
+ public boolean isBottom() {
+ return this.equals(bottom());
+ }
+
public static class Builder extends KeepInfo.Builder<Builder, KeepMethodInfo> {
+ private Builder() {
+ super();
+ }
+
private Builder(KeepMethodInfo original) {
super(original);
}
@@ -40,12 +61,12 @@
}
@Override
- public KeepMethodInfo top() {
+ public KeepMethodInfo getTopInfo() {
return TOP;
}
@Override
- public KeepMethodInfo bottom() {
+ public KeepMethodInfo getBottomInfo() {
return BOTTOM;
}
@@ -56,7 +77,19 @@
@Override
public KeepMethodInfo doBuild() {
- return new KeepMethodInfo(isPinned());
+ return new KeepMethodInfo(this);
+ }
+ }
+
+ public static class Joiner extends KeepInfo.Joiner<Joiner, Builder, KeepMethodInfo> {
+
+ public Joiner(KeepMethodInfo info) {
+ super(info.builder());
+ }
+
+ @Override
+ Joiner self() {
+ return this;
}
}
}