blob: ddd5bfcac82930056b37f87b85f8425bcbb6c3d3 [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 com.android.tools.r8.errors.Unreachable;
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.shaking.KeepFieldInfo.Joiner;
import com.android.tools.r8.utils.InternalOptions;
import com.google.common.collect.Streams;
import java.util.ArrayList;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;
// Non-mutable collection of keep information pertaining to a program.
public abstract class KeepInfoCollection {
// 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();
}
abstract Map<DexReference, List<Consumer<KeepInfo.Joiner<?, ?, ?>>>> getRuleInstances();
/**
* 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(DexType type, DexDefinitionSupplier definitions) {
DexProgramClass clazz = asProgramClassOrNull(definitions.definitionFor(type));
return clazz == null ? keepInfoForNonProgramClass() : getClassInfo(clazz);
}
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(DexMethod method, DexDefinitionSupplier definitions) {
DexProgramClass holder = asProgramClassOrNull(definitions.definitionFor(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(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);
}
public final KeepInfo<?, ?> getInfo(DexReference reference, DexDefinitionSupplier definitions) {
if (reference.isDexType()) {
return getClassInfo(reference.asDexType(), definitions);
}
if (reference.isDexMethod()) {
return getMethodInfo(reference.asDexMethod(), definitions);
}
if (reference.isDexField()) {
return getFieldInfo(reference.asDexField(), definitions);
}
throw new Unreachable();
}
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(DexReference reference, DexDefinitionSupplier definitions) {
return getInfo(reference, definitions).isPinned();
}
public final boolean isPinned(DexType type, DexDefinitionSupplier definitions) {
return getClassInfo(type, definitions).isPinned();
}
public final boolean isPinned(DexMethod method, DexDefinitionSupplier definitions) {
return getMethodInfo(method, definitions).isPinned();
}
public final boolean isPinned(DexField field, DexDefinitionSupplier definitions) {
return getFieldInfo(field, definitions).isPinned();
}
public final boolean isMinificationAllowed(
DexReference reference,
DexDefinitionSupplier definitions,
GlobalKeepInfoConfiguration configuration) {
return configuration.isMinificationEnabled()
&& getInfo(reference, definitions).isMinificationAllowed(configuration);
}
public abstract boolean verifyPinnedTypesAreLive(Set<DexType> liveTypes);
// TODO(b/156715504): We should try to avoid the need for iterating pinned items.
@Deprecated
public abstract void forEachPinnedType(Consumer<DexType> consumer);
// TODO(b/156715504): We should try to avoid the need for iterating pinned items.
@Deprecated
public abstract void forEachPinnedMethod(Consumer<DexMethod> consumer);
// TODO(b/156715504): We should try to avoid the need for iterating pinned items.
@Deprecated
public abstract void forEachPinnedField(Consumer<DexField> consumer);
public abstract KeepInfoCollection rewrite(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<DexReference, List<Consumer<KeepInfo.Joiner<?, ?, ?>>>> ruleInstances;
MutableKeepInfoCollection() {
this(
new IdentityHashMap<>(),
new IdentityHashMap<>(),
new IdentityHashMap<>(),
new IdentityHashMap<>());
}
private MutableKeepInfoCollection(
Map<DexType, KeepClassInfo> keepClassInfo,
Map<DexMethod, KeepMethodInfo> keepMethodInfo,
Map<DexField, KeepFieldInfo> keepFieldInfo,
Map<DexReference, List<Consumer<KeepInfo.Joiner<?, ?, ?>>>> ruleInstances) {
this.keepClassInfo = keepClassInfo;
this.keepMethodInfo = keepMethodInfo;
this.keepFieldInfo = keepFieldInfo;
this.ruleInstances = ruleInstances;
}
public void removeKeepInfoForPrunedItems(Set<DexType> removedClasses) {
keepClassInfo.keySet().removeIf(removedClasses::contains);
keepFieldInfo.keySet().removeIf(field -> removedClasses.contains(field.getHolderType()));
keepMethodInfo.keySet().removeIf(method -> removedClasses.contains(method.getHolderType()));
}
@Override
public KeepInfoCollection rewrite(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()
|| 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()
|| info.isMinificationAllowed(options)
|| newMethod.name == method.name;
assert !info.isPinned() || newMethod.getArity() == method.getArity();
assert !info.isPinned()
|| Streams.zip(
newMethod.getParameters().stream(),
method.getParameters().stream().map(lens::lookupType),
Object::equals)
.allMatch(x -> x);
assert !info.isPinned()
|| 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()
|| info.isMinificationAllowed(options);
KeepFieldInfo previous = newFieldInfo.put(newField, info);
assert previous == null;
});
Map<DexReference, List<Consumer<KeepInfo.Joiner<?, ?, ?>>>> newRuleInstances =
new IdentityHashMap<>(ruleInstances.size());
ruleInstances.forEach(
(reference, consumers) -> {
DexReference newReference;
if (reference.isDexType()) {
DexType newType = lens.lookupType(reference.asDexType());
if (!newType.isClassType()) {
assert newType.isIntType() : "Expected only enum unboxing type changes.";
return;
}
newReference = newType;
} else if (reference.isDexMethod()) {
newReference = lens.getRenamedMethodSignature(reference.asDexMethod());
} else {
assert reference.isDexField();
newReference = lens.getRenamedFieldSignature(reference.asDexField());
}
newRuleInstances.put(newReference, consumers);
});
return new MutableKeepInfoCollection(
newClassInfo, newMethodInfo, newFieldInfo, newRuleInstances);
}
@Override
Map<DexReference, List<Consumer<KeepInfo.Joiner<?, ?, ?>>>> getRuleInstances() {
return ruleInstances;
}
void evaluateRule(
DexReference reference,
DexDefinitionSupplier definitions,
Consumer<KeepInfo.Joiner<?, ?, ?>> fn) {
joinInfo(reference, definitions, fn);
if (!getInfo(reference, definitions).isBottom()) {
ruleInstances.computeIfAbsent(reference, k -> new ArrayList<>()).add(fn);
}
}
@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<KeepClassInfo.Joiner> fn) {
KeepClassInfo info = getClassInfo(clazz);
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 joinInfo(
DexReference reference,
DexDefinitionSupplier definitions,
Consumer<KeepInfo.Joiner<?, ?, ?>> fn) {
if (reference.isDexType()) {
DexType type = reference.asDexType();
DexProgramClass clazz = asProgramClassOrNull(definitions.definitionFor(type));
if (clazz != null) {
joinClass(clazz, fn::accept);
}
} else if (reference.isDexMethod()) {
DexMethod method = reference.asDexMethod();
DexProgramClass clazz = asProgramClassOrNull(definitions.definitionFor(method.holder));
ProgramMethod definition = method.lookupOnProgramClass(clazz);
if (definition != null) {
joinMethod(definition, fn::accept);
}
} else {
assert reference.isDexField();
DexField field = reference.asDexField();
DexProgramClass clazz = asProgramClassOrNull(definitions.definitionFor(field.holder));
ProgramField definition = field.lookupOnProgramClass(clazz);
if (definition != null) {
joinField(definition, fn::accept);
}
}
}
public void keepClass(DexProgramClass clazz) {
joinClass(clazz, KeepInfo.Joiner::top);
}
public void pinClass(DexProgramClass clazz) {
joinClass(clazz, KeepInfo.Joiner::pin);
}
public void joinMethod(ProgramMethod method, Consumer<KeepMethodInfo.Joiner> fn) {
KeepMethodInfo info = getMethodInfo(method);
if (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 pinMethod(ProgramMethod method) {
joinMethod(method, KeepInfo.Joiner::pin);
}
// TODO(b/157700141): Avoid pinning/unpinning references.
@Deprecated
public void unsafeUnpinMethod(DexMethod method) {
KeepMethodInfo info = keepMethodInfo.get(method);
if (info != null && info.isPinned()) {
keepMethodInfo.put(method, info.builder().unpin().build());
}
}
public void unsetRequireAllowAccessModificationForRepackaging(ProgramDefinition definition) {
if (definition.isProgramClass()) {
DexProgramClass clazz = definition.asProgramClass();
KeepClassInfo info = getClassInfo(clazz);
keepClassInfo.put(
clazz.getType(), info.builder().unsetRequireAccessModificationForRepackaging().build());
} else if (definition.isProgramMethod()) {
ProgramMethod method = definition.asProgramMethod();
KeepMethodInfo info = getMethodInfo(method);
keepMethodInfo.put(
method.getReference(),
info.builder().unsetRequireAccessModificationForRepackaging().build());
} else if (definition.isProgramField()) {
ProgramField field = definition.asProgramField();
KeepFieldInfo info = getFieldInfo(field);
keepFieldInfo.put(
field.getReference(),
info.builder().unsetRequireAccessModificationForRepackaging().build());
} else {
throw new Unreachable();
}
}
public void joinField(ProgramField field, Consumer<KeepFieldInfo.Joiner> fn) {
KeepFieldInfo info = getFieldInfo(field);
if (info.isTop()) {
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);
}
public void pinField(ProgramField field) {
joinField(field, KeepInfo.Joiner::pin);
}
@Override
public KeepInfoCollection mutate(Consumer<MutableKeepInfoCollection> mutator) {
mutator.accept(this);
return this;
}
@Override
public boolean verifyPinnedTypesAreLive(Set<DexType> liveTypes) {
keepClassInfo.forEach(
(type, info) -> {
assert !info.isPinned() || liveTypes.contains(type);
});
return true;
}
@Override
public void forEachPinnedType(Consumer<DexType> consumer) {
keepClassInfo.forEach(
(type, info) -> {
if (info.isPinned()) {
consumer.accept(type);
}
});
}
@Override
public void forEachPinnedMethod(Consumer<DexMethod> consumer) {
keepMethodInfo.forEach(
(method, info) -> {
if (info.isPinned()) {
consumer.accept(method);
}
});
}
@Override
public void forEachPinnedField(Consumer<DexField> consumer) {
keepFieldInfo.forEach(
(field, info) -> {
if (info.isPinned()) {
consumer.accept(field);
}
});
}
}
}