blob: f13a407c587e44b87e88ae3b0d454f47e02c0a90 [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 static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
import static com.android.tools.r8.utils.MapUtils.ignoreKey;
import com.android.tools.r8.errors.Unreachable;
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.DexDefinition;
import com.android.tools.r8.graph.DexDefinitionSupplier;
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexEncodedMember;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexReference;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.GraphLens.NonIdentityGraphLens;
import com.android.tools.r8.graph.ProgramDefinition;
import com.android.tools.r8.graph.ProgramField;
import com.android.tools.r8.graph.ProgramMember;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.graph.PrunedItems;
import com.android.tools.r8.shaking.KeepFieldInfo.Joiner;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.MapUtils;
import com.google.common.collect.Streams;
import java.util.IdentityHashMap;
import java.util.Map;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
// Non-mutable collection of keep information pertaining to a program.
public abstract class KeepInfoCollection {
abstract void forEachRuleInstance(
AppView<? extends AppInfoWithClassHierarchy> appView,
BiConsumer<DexProgramClass, KeepClassInfo.Joiner> classRuleInstanceConsumer,
BiConsumer<ProgramField, KeepFieldInfo.Joiner> fieldRuleInstanceConsumer,
BiConsumer<ProgramMethod, KeepMethodInfo.Joiner> methodRuleInstanceConsumer);
// TODO(b/157538235): This should not be bottom.
private static KeepClassInfo keepInfoForNonProgramClass() {
return KeepClassInfo.bottom();
}
// TODO(b/157538235): This should not be bottom.
private static KeepMethodInfo keepInfoForNonProgramMethod() {
return KeepMethodInfo.bottom();
}
// TODO(b/157538235): This should not be bottom.
private static KeepFieldInfo keepInfoForNonProgramField() {
return KeepFieldInfo.bottom();
}
/**
* Base accessor for keep info on a class.
*
* <p>Access may never be granted directly on DexType as the "keep info" for any non-program type
* is not the same as the default keep info for a program type. By typing the interface at program
* item we can eliminate errors where a reference to a non-program item results in optimizations
* assuming aspects of it can be changed when in fact they can not.
*/
public abstract KeepClassInfo getClassInfo(DexProgramClass clazz);
/**
* Base accessor for keep info on a method.
*
* <p>See comment on class access for why this is typed at program method.
*/
public abstract KeepMethodInfo getMethodInfo(DexEncodedMethod method, DexProgramClass holder);
/**
* Base accessor for keep info on a field.
*
* <p>See comment on class access for why this is typed at program field.
*/
public abstract KeepFieldInfo getFieldInfo(DexEncodedField field, DexProgramClass holder);
public KeepMemberInfo<?, ?> getMemberInfo(DexEncodedMember<?, ?> member, DexProgramClass holder) {
if (member.isDexEncodedField()) {
return getFieldInfo(member.asDexEncodedField(), holder);
}
assert member.isDexEncodedMethod();
return getMethodInfo(member.asDexEncodedMethod(), holder);
}
public final KeepClassInfo getClassInfo(DexClass clazz) {
return clazz != null && clazz.isProgramClass()
? getClassInfo(clazz.asProgramClass())
: keepInfoForNonProgramClass();
}
public final KeepClassInfo getClassInfo(DexType type, DexDefinitionSupplier definitions) {
return getClassInfo(definitions.contextIndependentDefinitionFor(type));
}
public final KeepMemberInfo<?, ?> getMemberInfo(ProgramMember<?, ?> member) {
return getMemberInfo(member.getDefinition(), member.getHolder());
}
public final KeepMethodInfo getMethodInfo(ProgramMethod method) {
return getMethodInfo(method.getDefinition(), method.getHolder());
}
public final KeepMethodInfo getMethodInfo(
DexEncodedMethod method, DexDefinitionSupplier definitions) {
DexProgramClass holder =
asProgramClassOrNull(definitions.contextIndependentDefinitionFor(method.getHolderType()));
if (holder == null) {
return keepInfoForNonProgramMethod();
}
assert method == holder.lookupMethod(method.getReference());
return getMethodInfo(method, holder);
}
public final KeepMethodInfo getMethodInfoWithDefinitionLookup(
DexMethod method, DexDefinitionSupplier definitions) {
DexProgramClass holder =
asProgramClassOrNull(definitions.contextIndependentDefinitionFor(method.holder));
if (holder == null) {
return keepInfoForNonProgramMethod();
}
DexEncodedMethod definition = holder.lookupMethod(method);
return definition == null ? KeepMethodInfo.bottom() : getMethodInfo(definition, holder);
}
public final KeepFieldInfo getFieldInfo(ProgramField field) {
return getFieldInfo(field.getDefinition(), field.getHolder());
}
public final KeepFieldInfo getFieldInfo(
DexEncodedField field, DexDefinitionSupplier definitions) {
DexProgramClass holder =
asProgramClassOrNull(definitions.contextIndependentDefinitionFor(field.getHolderType()));
if (holder == null) {
return keepInfoForNonProgramField();
}
assert holder.lookupField(field.getReference()) == field;
return getFieldInfo(field, holder);
}
private KeepFieldInfo getFieldInfoWithDefinitionLookup(
DexField field, DexDefinitionSupplier definitions) {
DexProgramClass holder = asProgramClassOrNull(definitions.definitionFor(field.holder));
if (holder == null) {
return keepInfoForNonProgramField();
}
DexEncodedField definition = holder.lookupField(field);
return definition == null ? KeepFieldInfo.bottom() : getFieldInfo(definition, holder);
}
private KeepInfo<?, ?> getInfoWithDefinitionLookup(
DexReference reference, DexDefinitionSupplier definitions) {
if (reference.isDexType()) {
return getClassInfo(reference.asDexType(), definitions);
}
if (reference.isDexMethod()) {
return getMethodInfoWithDefinitionLookup(reference.asDexMethod(), definitions);
}
if (reference.isDexField()) {
return getFieldInfoWithDefinitionLookup(reference.asDexField(), definitions);
}
throw new Unreachable();
}
public final KeepInfo<?, ?> getInfo(DexDefinition definition, DexDefinitionSupplier definitions) {
if (definition.isDexClass()) {
return getClassInfo(definition.asDexClass());
}
if (definition.isDexEncodedMethod()) {
return getMethodInfo(definition.asDexEncodedMethod(), definitions);
}
if (definition.isDexEncodedField()) {
return getFieldInfo(definition.asDexEncodedField(), definitions);
}
throw new Unreachable();
}
public final KeepClassInfo getInfo(DexProgramClass clazz) {
return getClassInfo(clazz);
}
public final KeepInfo<?, ?> getInfo(ProgramDefinition definition) {
if (definition.isProgramClass()) {
return getClassInfo(definition.asProgramClass());
}
if (definition.isProgramMethod()) {
return getMethodInfo(definition.asProgramMethod());
}
if (definition.isProgramField()) {
return getFieldInfo(definition.asProgramField());
}
throw new Unreachable();
}
public final boolean isPinned(
ProgramDefinition definition, GlobalKeepInfoConfiguration configuration) {
return getInfo(definition).isPinned(configuration);
}
public final boolean isPinned(
DexDefinition definition,
GlobalKeepInfoConfiguration configuration,
DexDefinitionSupplier definitions) {
return getInfo(definition, definitions).isPinned(configuration);
}
public final boolean isPinnedWithDefinitionLookup(
DexReference reference,
GlobalKeepInfoConfiguration configuration,
DexDefinitionSupplier definitions) {
return getInfoWithDefinitionLookup(reference, definitions).isPinned(configuration);
}
public final boolean isMinificationAllowed(
ProgramDefinition definition, GlobalKeepInfoConfiguration configuration) {
return configuration.isMinificationEnabled()
&& getInfo(definition).isMinificationAllowed(configuration);
}
public abstract boolean verifyPinnedTypesAreLive(Set<DexType> liveTypes, InternalOptions options);
// TODO(b/156715504): We should try to avoid the need for iterating pinned items.
@Deprecated
public abstract void forEachPinnedType(Consumer<DexType> consumer, InternalOptions options);
// TODO(b/156715504): We should try to avoid the need for iterating pinned items.
@Deprecated
public abstract void forEachPinnedMethod(Consumer<DexMethod> consumer, InternalOptions options);
// TODO(b/156715504): We should try to avoid the need for iterating pinned items.
@Deprecated
public abstract void forEachPinnedField(Consumer<DexField> consumer, InternalOptions options);
public abstract KeepInfoCollection rewrite(
DexDefinitionSupplier definitions, NonIdentityGraphLens lens, InternalOptions options);
public abstract KeepInfoCollection mutate(Consumer<MutableKeepInfoCollection> mutator);
// Mutation interface for building up the keep info.
public static class MutableKeepInfoCollection extends KeepInfoCollection {
// These are typed at signatures but the interface should make sure never to allow access
// directly with a signature. See the comment in KeepInfoCollection.
private final Map<DexType, KeepClassInfo> keepClassInfo;
private final Map<DexMethod, KeepMethodInfo> keepMethodInfo;
private final Map<DexField, KeepFieldInfo> keepFieldInfo;
// Map of applied rules for which keys may need to be mutated.
private final Map<DexType, KeepClassInfo.Joiner> classRuleInstances;
private final Map<DexField, KeepFieldInfo.Joiner> fieldRuleInstances;
private final Map<DexMethod, KeepMethodInfo.Joiner> methodRuleInstances;
MutableKeepInfoCollection() {
this(
new IdentityHashMap<>(),
new IdentityHashMap<>(),
new IdentityHashMap<>(),
new IdentityHashMap<>(),
new IdentityHashMap<>(),
new IdentityHashMap<>());
}
private MutableKeepInfoCollection(
Map<DexType, KeepClassInfo> keepClassInfo,
Map<DexMethod, KeepMethodInfo> keepMethodInfo,
Map<DexField, KeepFieldInfo> keepFieldInfo,
Map<DexType, KeepClassInfo.Joiner> classRuleInstances,
Map<DexField, KeepFieldInfo.Joiner> fieldRuleInstances,
Map<DexMethod, KeepMethodInfo.Joiner> methodRuleInstances) {
this.keepClassInfo = keepClassInfo;
this.keepMethodInfo = keepMethodInfo;
this.keepFieldInfo = keepFieldInfo;
this.classRuleInstances = classRuleInstances;
this.fieldRuleInstances = fieldRuleInstances;
this.methodRuleInstances = methodRuleInstances;
}
public void removeKeepInfoForMergedClasses(PrunedItems prunedItems) {
if (prunedItems.hasRemovedClasses()) {
keepClassInfo.keySet().removeAll(prunedItems.getRemovedClasses());
}
if (prunedItems.hasRemovedFields()) {
keepFieldInfo.keySet().removeAll(prunedItems.getRemovedFields());
}
if (prunedItems.hasRemovedMembers()) {
keepMethodInfo.keySet().removeAll(prunedItems.getRemovedMethods());
}
}
public void removeKeepInfoForPrunedItems(PrunedItems prunedItems) {
if (prunedItems.hasRemovedClasses()) {
keepClassInfo.keySet().removeAll(prunedItems.getRemovedClasses());
}
if (prunedItems.hasRemovedClasses() || prunedItems.hasRemovedFields()) {
keepFieldInfo.keySet().removeIf(prunedItems::isRemoved);
}
if (prunedItems.hasRemovedClasses() || prunedItems.hasRemovedMembers()) {
keepMethodInfo.keySet().removeIf(prunedItems::isRemoved);
}
}
@Override
public KeepInfoCollection rewrite(
DexDefinitionSupplier definitions, NonIdentityGraphLens lens, InternalOptions options) {
Map<DexType, KeepClassInfo> newClassInfo = new IdentityHashMap<>(keepClassInfo.size());
keepClassInfo.forEach(
(type, info) -> {
DexType newType = lens.lookupType(type);
assert newType == type
|| !info.isPinned(options)
|| info.isMinificationAllowed(options)
|| info.isRepackagingAllowed(options);
KeepClassInfo previous = newClassInfo.put(newType, info);
assert previous == null;
});
Map<DexMethod, KeepMethodInfo> newMethodInfo = new IdentityHashMap<>(keepMethodInfo.size());
keepMethodInfo.forEach(
(method, info) -> {
DexMethod newMethod = lens.getRenamedMethodSignature(method);
assert !info.isPinned(options)
|| info.isMinificationAllowed(options)
|| newMethod.name == method.name;
assert !info.isPinned(options) || newMethod.getArity() == method.getArity();
assert !info.isPinned(options)
|| Streams.zip(
newMethod.getParameters().stream(),
method.getParameters().stream().map(lens::lookupType),
Object::equals)
.allMatch(x -> x);
assert !info.isPinned(options)
|| newMethod.getReturnType() == lens.lookupType(method.getReturnType());
KeepMethodInfo previous = newMethodInfo.put(newMethod, info);
// TODO(b/169927809): Avoid collisions.
// assert previous == null;
});
Map<DexField, KeepFieldInfo> newFieldInfo = new IdentityHashMap<>(keepFieldInfo.size());
keepFieldInfo.forEach(
(field, info) -> {
DexField newField = lens.getRenamedFieldSignature(field);
assert newField.name == field.name
|| !info.isPinned(options)
|| info.isMinificationAllowed(options);
KeepFieldInfo previous = newFieldInfo.put(newField, info);
assert previous == null;
});
return new MutableKeepInfoCollection(
newClassInfo,
newMethodInfo,
newFieldInfo,
rewriteRuleInstances(
classRuleInstances,
clazz -> {
DexType rewritten = lens.lookupType(clazz);
if (rewritten.isClassType()) {
return rewritten;
}
assert rewritten.isIntType();
return null;
},
KeepClassInfo::newEmptyJoiner),
rewriteRuleInstances(
fieldRuleInstances, lens::getRenamedFieldSignature, KeepFieldInfo::newEmptyJoiner),
rewriteRuleInstances(
methodRuleInstances,
lens::getRenamedMethodSignature,
KeepMethodInfo::newEmptyJoiner));
}
private static <R, J extends KeepInfo.Joiner<J, ?, ?>> Map<R, J> rewriteRuleInstances(
Map<R, J> ruleInstances, Function<R, R> rewriter, Supplier<J> newEmptyJoiner) {
return MapUtils.transform(
ruleInstances,
IdentityHashMap::new,
rewriter,
Function.identity(),
(reference, joiner, otherJoiner) ->
newEmptyJoiner.get().merge(joiner).merge(otherJoiner));
}
@Override
void forEachRuleInstance(
AppView<? extends AppInfoWithClassHierarchy> appView,
BiConsumer<DexProgramClass, KeepClassInfo.Joiner> classRuleInstanceConsumer,
BiConsumer<ProgramField, KeepFieldInfo.Joiner> fieldRuleInstanceConsumer,
BiConsumer<ProgramMethod, KeepMethodInfo.Joiner> methodRuleInstanceConsumer) {
classRuleInstances.forEach(
(type, ruleInstance) -> {
DexProgramClass clazz = asProgramClassOrNull(appView.definitionFor(type));
if (clazz != null) {
classRuleInstanceConsumer.accept(clazz, ruleInstance);
}
});
fieldRuleInstances.forEach(
(fieldReference, ruleInstance) -> {
DexProgramClass holder =
asProgramClassOrNull(appView.definitionFor(fieldReference.getHolderType()));
ProgramField field = holder.lookupProgramField(fieldReference);
if (field != null) {
fieldRuleInstanceConsumer.accept(field, ruleInstance);
}
});
methodRuleInstances.forEach(
(methodReference, ruleInstance) -> {
DexProgramClass holder =
asProgramClassOrNull(appView.definitionFor(methodReference.getHolderType()));
ProgramMethod method = holder.lookupProgramMethod(methodReference);
if (method != null) {
methodRuleInstanceConsumer.accept(method, ruleInstance);
}
});
}
void evaluateClassRule(DexProgramClass clazz, KeepClassInfo.Joiner minimumKeepInfo) {
if (!minimumKeepInfo.isBottom()) {
joinClass(clazz, joiner -> joiner.merge(minimumKeepInfo));
classRuleInstances
.computeIfAbsent(clazz.getType(), ignoreKey(KeepClassInfo::newEmptyJoiner))
.merge(minimumKeepInfo);
}
}
void evaluateFieldRule(ProgramField field, KeepFieldInfo.Joiner minimumKeepInfo) {
if (!minimumKeepInfo.isBottom()) {
joinField(field, joiner -> joiner.merge(minimumKeepInfo));
fieldRuleInstances
.computeIfAbsent(field.getReference(), ignoreKey(KeepFieldInfo::newEmptyJoiner))
.merge(minimumKeepInfo);
}
}
void evaluateMethodRule(ProgramMethod method, KeepMethodInfo.Joiner minimumKeepInfo) {
if (!minimumKeepInfo.isBottom()) {
joinMethod(method, joiner -> joiner.merge(minimumKeepInfo));
methodRuleInstances
.computeIfAbsent(method.getReference(), ignoreKey(KeepMethodInfo::newEmptyJoiner))
.merge(minimumKeepInfo);
}
}
@Override
public KeepClassInfo getClassInfo(DexProgramClass clazz) {
return keepClassInfo.getOrDefault(clazz.type, KeepClassInfo.bottom());
}
@Override
public KeepMethodInfo getMethodInfo(DexEncodedMethod method, DexProgramClass holder) {
assert method.getHolderType() == holder.type;
return keepMethodInfo.getOrDefault(method.getReference(), KeepMethodInfo.bottom());
}
@Override
public KeepFieldInfo getFieldInfo(DexEncodedField field, DexProgramClass holder) {
assert field.getHolderType() == holder.type;
return keepFieldInfo.getOrDefault(field.getReference(), KeepFieldInfo.bottom());
}
public void joinClass(DexProgramClass clazz, Consumer<? super KeepClassInfo.Joiner> fn) {
KeepClassInfo info = getClassInfo(clazz);
if (info.isTop()) {
assert info == KeepClassInfo.top();
return;
}
KeepClassInfo.Joiner joiner = info.joiner();
fn.accept(joiner);
KeepClassInfo joined = joiner.join();
if (!info.equals(joined)) {
keepClassInfo.put(clazz.type, joined);
}
}
public void keepClass(DexProgramClass clazz) {
joinClass(clazz, KeepInfo.Joiner::top);
}
public void joinMethod(ProgramMethod method, Consumer<? super KeepMethodInfo.Joiner> fn) {
KeepMethodInfo info = getMethodInfo(method);
if (info.isTop()) {
assert info == KeepMethodInfo.top();
return;
}
KeepMethodInfo.Joiner joiner = info.joiner();
fn.accept(joiner);
KeepMethodInfo joined = joiner.join();
if (!info.equals(joined)) {
keepMethodInfo.put(method.getReference(), joined);
}
}
public void keepMethod(ProgramMethod method) {
joinMethod(method, KeepInfo.Joiner::top);
}
public void joinField(ProgramField field, Consumer<? super KeepFieldInfo.Joiner> fn) {
KeepFieldInfo info = getFieldInfo(field);
if (info.isTop()) {
assert info == KeepFieldInfo.top();
return;
}
Joiner joiner = info.joiner();
fn.accept(joiner);
KeepFieldInfo joined = joiner.join();
if (!info.equals(joined)) {
keepFieldInfo.put(field.getReference(), joined);
}
}
public void keepField(ProgramField field) {
joinField(field, KeepInfo.Joiner::top);
}
@Override
public KeepInfoCollection mutate(Consumer<MutableKeepInfoCollection> mutator) {
mutator.accept(this);
return this;
}
@Override
public boolean verifyPinnedTypesAreLive(Set<DexType> liveTypes, InternalOptions options) {
keepClassInfo.forEach(
(type, info) -> {
assert !info.isPinned(options) || liveTypes.contains(type);
});
return true;
}
@Override
public void forEachPinnedType(Consumer<DexType> consumer, InternalOptions options) {
keepClassInfo.forEach(
(type, info) -> {
if (info.isPinned(options)) {
consumer.accept(type);
}
});
}
@Override
public void forEachPinnedMethod(Consumer<DexMethod> consumer, InternalOptions options) {
keepMethodInfo.forEach(
(method, info) -> {
if (info.isPinned(options)) {
consumer.accept(method);
}
});
}
@Override
public void forEachPinnedField(Consumer<DexField> consumer, InternalOptions options) {
keepFieldInfo.forEach(
(field, info) -> {
if (info.isPinned(options)) {
consumer.accept(field);
}
});
}
}
}