blob: 9595b06a20698872d78a43ae07fbc15434e6bfaf [file] [log] [blame]
// Copyright (c) 2020, 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 com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexReference;
import com.android.tools.r8.graph.EnclosingMethodAttribute;
import com.android.tools.r8.graph.ProgramDefinition;
import com.android.tools.r8.shaking.KeepInfo.Builder;
import com.android.tools.r8.shaking.KeepReason.ReflectiveUseFrom;
import com.android.tools.r8.utils.InternalOptions;
import com.google.common.collect.Sets;
import java.util.HashSet;
import java.util.Set;
import java.util.function.Consumer;
/** Keep information that can be associated with any item, i.e., class, method or field. */
public abstract class KeepInfo<B extends Builder<B, K>, K extends KeepInfo<B, K>> {
private final boolean allowAccessModification;
private final boolean allowAnnotationRemoval;
private final boolean allowMinification;
private final boolean allowOptimization;
private final boolean allowShrinking;
private final boolean allowSignatureRemoval;
private final boolean checkDiscarded;
private final boolean requireAccessModificationForRepackaging;
private KeepInfo(
boolean allowAccessModification,
boolean allowAnnotationRemoval,
boolean allowMinification,
boolean allowOptimization,
boolean allowShrinking,
boolean allowSignatureRemoval,
boolean checkDiscarded,
boolean requireAccessModificationForRepackaging) {
this.allowAccessModification = allowAccessModification;
this.allowAnnotationRemoval = allowAnnotationRemoval;
this.allowMinification = allowMinification;
this.allowOptimization = allowOptimization;
this.allowShrinking = allowShrinking;
this.allowSignatureRemoval = allowSignatureRemoval;
this.checkDiscarded = checkDiscarded;
this.requireAccessModificationForRepackaging = requireAccessModificationForRepackaging;
}
KeepInfo(B builder) {
this(
builder.isAccessModificationAllowed(),
builder.isAnnotationRemovalAllowed(),
builder.isMinificationAllowed(),
builder.isOptimizationAllowed(),
builder.isShrinkingAllowed(),
builder.isSignatureRemovalAllowed(),
builder.isCheckDiscardedEnabled(),
builder.isAccessModificationRequiredForRepackaging());
}
public static Joiner<?, ?, ?> newEmptyJoinerFor(DexReference reference) {
return reference.apply(
clazz -> KeepClassInfo.newEmptyJoiner(),
field -> KeepFieldInfo.newEmptyJoiner(),
method -> KeepMethodInfo.newEmptyJoiner());
}
abstract B builder();
/**
* True if an item may have all of its annotations removed.
*
* <p>If this returns false, some annotations may still be removed if the configuration does not
* keep all annotation attributes.
*/
public boolean isAnnotationRemovalAllowed(GlobalKeepInfoConfiguration configuration) {
return configuration.isAnnotationRemovalEnabled() && internalIsAnnotationRemovalAllowed();
}
boolean internalIsAnnotationRemovalAllowed() {
return allowAnnotationRemoval;
}
public boolean isCheckDiscardedEnabled(GlobalKeepInfoConfiguration configuration) {
return internalIsCheckDiscardedEnabled();
}
boolean internalIsCheckDiscardedEnabled() {
return checkDiscarded;
}
/**
* True if an item must be present in the output.
*
* @deprecated Prefer task dependent predicates.
*/
@Deprecated
public boolean isPinned(GlobalKeepInfoConfiguration configuration) {
return !isOptimizationAllowed(configuration) || !isShrinkingAllowed(configuration);
}
/**
* True if an item may have its name minified/changed.
*
* <p>This method requires knowledge of the global configuration as that can override the concrete
* value on a given item.
*/
public boolean isMinificationAllowed(GlobalKeepInfoConfiguration configuration) {
return configuration.isMinificationEnabled() && internalIsMinificationAllowed();
}
boolean internalIsMinificationAllowed() {
return allowMinification;
}
/**
* True if an item may be optimized (i.e., the item is not soft pinned).
*
* <p>This method requires knowledge of the global configuration as that can override the concrete
* value on a given item.
*/
public boolean isOptimizationAllowed(GlobalKeepInfoConfiguration configuration) {
return configuration.isOptimizationEnabled() && internalIsOptimizationAllowed();
}
boolean internalIsOptimizationAllowed() {
return allowOptimization;
}
/**
* True if an item is subject to shrinking (i.e., tree shaking).
*
* <p>This method requires knowledge of the global configuration as that can override the concrete
* value on a given item.
*/
public boolean isShrinkingAllowed(GlobalKeepInfoConfiguration configuration) {
return configuration.isTreeShakingEnabled() && internalIsShrinkingAllowed();
}
boolean internalIsShrinkingAllowed() {
return allowShrinking;
}
/**
* True if an item may have its generic signature removed.
*
* <p>This method requires knowledge of the global configuration as that can override the concrete
* value on a given item.
*/
public boolean isSignatureRemovalAllowed(GlobalKeepInfoConfiguration configuration) {
if (!configuration.isKeepAttributesSignatureEnabled()) {
return true;
}
return !configuration.isForceProguardCompatibilityEnabled()
&& internalIsSignatureRemovalAllowed();
}
boolean internalIsSignatureRemovalAllowed() {
return allowSignatureRemoval;
}
/**
* True if an item may be repackaged.
*
* <p>This method requires knowledge of the global configuration as that can override the concrete
* value on a given item.
*/
public abstract boolean isRepackagingAllowed(
ProgramDefinition definition, GlobalKeepInfoConfiguration configuration);
boolean internalIsAccessModificationRequiredForRepackaging() {
return requireAccessModificationForRepackaging;
}
/**
* 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(GlobalKeepInfoConfiguration configuration) {
return configuration.isAccessModificationEnabled() && internalIsAccessModificationAllowed();
}
// Internal accessor for the items access-modification bit.
boolean internalIsAccessModificationAllowed() {
return allowAccessModification;
}
public boolean isEnclosingMethodAttributeRemovalAllowed(
GlobalKeepInfoConfiguration configuration,
EnclosingMethodAttribute enclosingMethodAttribute,
AppView<AppInfoWithLiveness> appView) {
if (!configuration.isKeepEnclosingMethodAttributeEnabled()) {
return true;
}
if (configuration.isForceProguardCompatibilityEnabled()) {
return false;
}
return !isPinned(configuration) || !enclosingMethodAttribute.isEnclosingPinned(appView);
}
public boolean isInnerClassesAttributeRemovalAllowed(GlobalKeepInfoConfiguration configuration) {
if (!configuration.isKeepInnerClassesAttributeEnabled()) {
return true;
}
return !(configuration.isForceProguardCompatibilityEnabled() || isPinned(configuration));
}
public boolean isInnerClassesAttributeRemovalAllowed(
GlobalKeepInfoConfiguration configuration,
EnclosingMethodAttribute enclosingMethodAttribute) {
if (!configuration.isKeepInnerClassesAttributeEnabled()) {
return true;
}
if (configuration.isForceProguardCompatibilityEnabled()) {
return false;
}
// The inner class is dependent on the enclosingMethodAttribute and since it has been pruned
// we can also remove this inner class relationship.
return enclosingMethodAttribute == null || !isPinned(configuration);
}
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 (allowAccessModification || !other.internalIsAccessModificationAllowed())
&& (allowAnnotationRemoval || !other.internalIsAnnotationRemovalAllowed())
&& (allowMinification || !other.internalIsMinificationAllowed())
&& (allowOptimization || !other.internalIsOptimizationAllowed())
&& (allowShrinking || !other.internalIsShrinkingAllowed())
&& (allowSignatureRemoval || !other.internalIsSignatureRemovalAllowed())
&& (!checkDiscarded || other.internalIsCheckDiscardedEnabled());
}
/** Builder to construct an arbitrary keep info object. */
public abstract static class Builder<B extends Builder<B, K>, K extends KeepInfo<B, K>> {
abstract B self();
abstract K doBuild();
abstract K getTopInfo();
abstract K getBottomInfo();
abstract boolean isEqualTo(K other);
protected K original;
private boolean allowAccessModification;
private boolean allowAnnotationRemoval;
private boolean allowMinification;
private boolean allowOptimization;
private boolean allowShrinking;
private boolean allowSignatureRemoval;
private boolean checkDiscarded;
private boolean requireAccessModificationForRepackaging;
Builder() {
// Default initialized. Use should be followed by makeTop/makeBottom.
}
Builder(K original) {
this.original = original;
allowAccessModification = original.internalIsAccessModificationAllowed();
allowAnnotationRemoval = original.internalIsAnnotationRemovalAllowed();
allowMinification = original.internalIsMinificationAllowed();
allowOptimization = original.internalIsOptimizationAllowed();
allowShrinking = original.internalIsShrinkingAllowed();
allowSignatureRemoval = original.internalIsSignatureRemovalAllowed();
checkDiscarded = original.internalIsCheckDiscardedEnabled();
requireAccessModificationForRepackaging =
original.internalIsAccessModificationRequiredForRepackaging();
}
B makeTop() {
disallowAccessModification();
disallowAnnotationRemoval();
disallowMinification();
disallowOptimization();
disallowShrinking();
disallowSignatureRemoval();
unsetCheckDiscarded();
requireAccessModificationForRepackaging();
return self();
}
B makeBottom() {
allowAccessModification();
allowAnnotationRemoval();
allowMinification();
allowOptimization();
allowShrinking();
allowSignatureRemoval();
unsetCheckDiscarded();
unsetRequireAccessModificationForRepackaging();
return self();
}
public K build() {
if (original != null) {
if (internalIsEqualTo(original)) {
return original;
}
if (internalIsEqualTo(getTopInfo())) {
return getTopInfo();
}
if (internalIsEqualTo(getBottomInfo())) {
return getBottomInfo();
}
}
return doBuild();
}
boolean internalIsEqualTo(K other) {
return isAccessModificationAllowed() == other.internalIsAccessModificationAllowed()
&& isAnnotationRemovalAllowed() == other.internalIsAnnotationRemovalAllowed()
&& isMinificationAllowed() == other.internalIsMinificationAllowed()
&& isOptimizationAllowed() == other.internalIsOptimizationAllowed()
&& isShrinkingAllowed() == other.internalIsShrinkingAllowed()
&& isSignatureRemovalAllowed() == other.internalIsSignatureRemovalAllowed()
&& isCheckDiscardedEnabled() == other.internalIsCheckDiscardedEnabled()
&& isAccessModificationRequiredForRepackaging()
== other.internalIsAccessModificationRequiredForRepackaging();
}
public boolean isAccessModificationRequiredForRepackaging() {
return requireAccessModificationForRepackaging;
}
public boolean isAccessModificationAllowed() {
return allowAccessModification;
}
public boolean isAnnotationRemovalAllowed() {
return allowAnnotationRemoval;
}
public boolean isCheckDiscardedEnabled() {
return checkDiscarded;
}
public boolean isMinificationAllowed() {
return allowMinification;
}
public boolean isOptimizationAllowed() {
return allowOptimization;
}
public boolean isShrinkingAllowed() {
return allowShrinking;
}
public boolean isSignatureRemovalAllowed() {
return allowSignatureRemoval;
}
public B setAllowMinification(boolean allowMinification) {
this.allowMinification = allowMinification;
return self();
}
public B allowMinification() {
return setAllowMinification(true);
}
public B disallowMinification() {
return setAllowMinification(false);
}
public B setAllowOptimization(boolean allowOptimization) {
this.allowOptimization = allowOptimization;
return self();
}
public B allowOptimization() {
return setAllowOptimization(true);
}
public B disallowOptimization() {
return setAllowOptimization(false);
}
public B setAllowShrinking(boolean allowShrinking) {
this.allowShrinking = allowShrinking;
return self();
}
public B allowShrinking() {
return setAllowShrinking(true);
}
public B disallowShrinking() {
return setAllowShrinking(false);
}
public B setCheckDiscarded(boolean checkDiscarded) {
this.checkDiscarded = checkDiscarded;
return self();
}
public B setCheckDiscarded() {
return setCheckDiscarded(true);
}
public B unsetCheckDiscarded() {
return setCheckDiscarded(false);
}
public B setRequireAccessModificationForRepackaging(
boolean requireAccessModificationForRepackaging) {
this.requireAccessModificationForRepackaging = requireAccessModificationForRepackaging;
return self();
}
public B requireAccessModificationForRepackaging() {
return setRequireAccessModificationForRepackaging(true);
}
public B unsetRequireAccessModificationForRepackaging() {
return setRequireAccessModificationForRepackaging(false);
}
public B setAllowAccessModification(boolean allowAccessModification) {
this.allowAccessModification = allowAccessModification;
return self();
}
public B allowAccessModification() {
return setAllowAccessModification(true);
}
public B disallowAccessModification() {
return setAllowAccessModification(false);
}
public B setAllowAnnotationRemoval(boolean allowAnnotationRemoval) {
this.allowAnnotationRemoval = allowAnnotationRemoval;
return self();
}
public B allowAnnotationRemoval() {
return setAllowAnnotationRemoval(true);
}
public B disallowAnnotationRemoval() {
return setAllowAnnotationRemoval(false);
}
private B setAllowSignatureRemoval(boolean allowSignatureRemoval) {
this.allowSignatureRemoval = allowSignatureRemoval;
return self();
}
public B allowSignatureRemoval() {
return setAllowSignatureRemoval(true);
}
public B disallowSignatureRemoval() {
return setAllowSignatureRemoval(false);
}
}
/** Joiner to construct monotonically increasing keep info object. */
public abstract static class Joiner<
J extends Joiner<J, B, K>, B extends Builder<B, K>, K extends KeepInfo<B, K>> {
abstract J self();
final B builder;
/**
* The set of reasons and rules that have contributed to setting {@link Builder#allowShrinking}
* to false on this joiner. These are needed to report the correct -whyareyoukeeping reasons for
* rooted items.
*
* <p>An item should only have allowShrinking set to false if it is kept by a -keep rule or the
* {@link Enqueuer} detects a reflective access to the item (hence the {@link
* Set<ReflectiveUseFrom>}).
*
* <p>These are only needed for the interpretation of keep rules into keep info, and is
* therefore not stored in the keep info builder above.
*/
final Set<ReflectiveUseFrom> reasons = new HashSet<>();
final Set<ProguardKeepRuleBase> rules = Sets.newIdentityHashSet();
Joiner(B builder) {
this.builder = builder;
}
public J applyIf(boolean condition, Consumer<J> thenConsumer) {
if (condition) {
thenConsumer.accept(self());
}
return self();
}
public KeepClassInfo.Joiner asClassJoiner() {
return null;
}
public KeepFieldInfo.Joiner asFieldJoiner() {
return null;
}
public static KeepFieldInfo.Joiner asFieldJoinerOrNull(Joiner<?, ?, ?> joiner) {
return joiner != null ? joiner.asFieldJoiner() : null;
}
public KeepMethodInfo.Joiner asMethodJoiner() {
return null;
}
public Set<ReflectiveUseFrom> getReasons() {
return reasons;
}
public Set<ProguardKeepRuleBase> getRules() {
return rules;
}
public boolean isBottom() {
return builder.isEqualTo(builder.getBottomInfo());
}
public boolean isCheckDiscardedEnabled() {
return builder.isCheckDiscardedEnabled();
}
public boolean isOptimizationAllowed() {
return builder.isOptimizationAllowed();
}
public boolean isShrinkingAllowed() {
return builder.isShrinkingAllowed();
}
public boolean isTop() {
return builder.isEqualTo(builder.getTopInfo());
}
public J top() {
builder.makeTop();
return self();
}
public J addReason(ReflectiveUseFrom reason) {
reasons.add(reason);
return self();
}
public J addRule(ProguardKeepRuleBase rule) {
rules.add(rule);
return self();
}
public J disallowAccessModification() {
builder.disallowAccessModification();
return self();
}
public J disallowAnnotationRemoval() {
builder.disallowAnnotationRemoval();
return self();
}
public J disallowMinification() {
builder.disallowMinification();
return self();
}
public J disallowOptimization() {
builder.disallowOptimization();
return self();
}
public J disallowShrinking() {
builder.disallowShrinking();
return self();
}
public J disallowSignatureRemoval() {
builder.disallowSignatureRemoval();
return self();
}
public J setCheckDiscarded() {
builder.setCheckDiscarded();
return self();
}
public J requireAccessModificationForRepackaging() {
builder.requireAccessModificationForRepackaging();
return self();
}
public J merge(J joiner) {
Builder<B, K> builder = joiner.builder;
applyIf(!builder.isAccessModificationAllowed(), Joiner::disallowAccessModification);
applyIf(!builder.isAnnotationRemovalAllowed(), Joiner::disallowAnnotationRemoval);
applyIf(!builder.isMinificationAllowed(), Joiner::disallowMinification);
applyIf(!builder.isOptimizationAllowed(), Joiner::disallowOptimization);
applyIf(!builder.isShrinkingAllowed(), Joiner::disallowShrinking);
applyIf(!builder.isSignatureRemovalAllowed(), Joiner::disallowSignatureRemoval);
applyIf(builder.isCheckDiscardedEnabled(), Joiner::setCheckDiscarded);
applyIf(
builder.isAccessModificationRequiredForRepackaging(),
Joiner::requireAccessModificationForRepackaging);
reasons.addAll(joiner.reasons);
rules.addAll(joiner.rules);
return self();
}
@SuppressWarnings("unchecked")
public J mergeUnsafe(Joiner<?, ?, ?> joiner) {
return merge((J) joiner);
}
public K join() {
K joined = builder.build();
K original = builder.original;
assert original.isLessThanOrEquals(joined);
return joined;
}
public boolean verifyShrinkingDisallowedWithRule(InternalOptions options) {
assert !isShrinkingAllowed();
assert !getReasons().isEmpty() || !getRules().isEmpty() || !options.isShrinking();
return true;
}
}
}