blob: 1665e216013b4eb11f6ad307be049a1e1726e061 [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.AppInfo;
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.GraphLense.NestedGraphLense;
import com.android.tools.r8.graph.ProgramField;
import com.android.tools.r8.graph.ProgramMethod;
import java.util.Collection;
import java.util.IdentityHashMap;
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 {
/**
* 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 final KeepClassInfo getClassInfo(DexType type, DexDefinitionSupplier definitions) {
DexProgramClass clazz = asProgramClassOrNull(definitions.definitionFor(type));
return clazz == null ? KeepClassInfo.top() : getClassInfo(clazz);
}
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 KeepMethodInfo.top();
}
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 KeepFieldInfo.top();
}
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 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 verifyNoneArePinned(Collection<DexType> types, AppInfo appInfo) {
for (DexType type : types) {
DexProgramClass clazz =
asProgramClassOrNull(appInfo.definitionForWithoutExistenceAssert(type));
assert clazz == null || !getClassInfo(clazz).isPinned();
}
return true;
}
// 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(NestedGraphLense lens);
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;
MutableKeepInfoCollection() {
this(new IdentityHashMap<>(), new IdentityHashMap<>(), new IdentityHashMap<>());
}
private MutableKeepInfoCollection(
Map<DexType, KeepClassInfo> keepClassInfo,
Map<DexMethod, KeepMethodInfo> keepMethodInfo,
Map<DexField, KeepFieldInfo> keepFieldInfo) {
this.keepClassInfo = keepClassInfo;
this.keepMethodInfo = keepMethodInfo;
this.keepFieldInfo = keepFieldInfo;
}
@Override
public KeepInfoCollection rewrite(NestedGraphLense lens) {
Map<DexType, KeepClassInfo> newClassInfo = new IdentityHashMap<>(keepClassInfo.size());
keepClassInfo.forEach(
(type, info) -> {
DexType newType = lens.lookupType(type);
assert !info.isPinned() || type == newType;
newClassInfo.put(newType, info);
});
Map<DexMethod, KeepMethodInfo> newMethodInfo = new IdentityHashMap<>(keepMethodInfo.size());
keepMethodInfo.forEach(
(method, info) -> {
Set<DexMethod> methods = lens.lookupMethodInAllContexts(method);
assert !info.isPinned() || methods.contains(method);
for (DexMethod newMethod : methods) {
// TODO(b/157012327): Should this distribute keep-info from pinned methods too?
if (!info.isPinned() || method == newMethod) {
newMethodInfo.put(newMethod, info);
}
}
});
Map<DexField, KeepFieldInfo> newFieldInfo = new IdentityHashMap<>(keepFieldInfo.size());
keepFieldInfo.forEach(
(field, info) -> {
DexField newField = lens.lookupField(field);
assert !info.isPinned() || field == newField;
newFieldInfo.put(newField, info);
});
return new MutableKeepInfoCollection(newClassInfo, newMethodInfo, newFieldInfo);
}
@Override
public KeepClassInfo getClassInfo(DexProgramClass clazz) {
return keepClassInfo.getOrDefault(clazz.type, KeepClassInfo.bottom());
}
@Override
public KeepMethodInfo getMethodInfo(DexEncodedMethod method, DexProgramClass holder) {
assert method.holder() == holder.type;
return keepMethodInfo.getOrDefault(method.method, KeepMethodInfo.bottom());
}
@Override
public KeepFieldInfo getFieldInfo(DexEncodedField field, DexProgramClass holder) {
assert field.holder() == holder.type;
return keepFieldInfo.getOrDefault(field.field, KeepFieldInfo.bottom());
}
public void pinClass(DexProgramClass clazz) {
KeepClassInfo info = getClassInfo(clazz);
if (!info.isPinned()) {
keepClassInfo.put(clazz.type, info.builder().pin().build());
}
}
public void pinMember(DexProgramClass holder, DexEncodedMember<?, ?> member) {
if (member.isDexEncodedMethod()) {
pinMethod(holder, member.asDexEncodedMethod());
} else {
assert member.isDexEncodedField();
pinField(holder, member.asDexEncodedField());
}
}
public void pinMethod(ProgramMethod programMethod) {
pinMethod(programMethod.getHolder(), programMethod.getDefinition());
}
public void pinMethod(DexProgramClass holder, DexEncodedMethod method) {
KeepMethodInfo info = getMethodInfo(method, holder);
if (!info.isPinned()) {
keepMethodInfo.put(method.method, info.builder().pin().build());
}
}
public void unpinMethod(ProgramMethod method) {
assert !getClassInfo(method.getHolder()).isPinned();
unpinMethod(method.getReference());
}
// TODO(b/156715504): We should never need to unpin items. Rather avoid pinning to begin with.
@Deprecated
public void unpinMethod(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) {
KeepFieldInfo info = getFieldInfo(field, holder);
if (!info.isPinned()) {
keepFieldInfo.put(field.field, info.builder().pin().build());
}
}
@Override
public KeepInfoCollection mutate(Consumer<MutableKeepInfoCollection> mutator) {
mutator.accept(this);
return this;
}
@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);
}
});
}
}
}