blob: 06fdee6443f7c96c3ec04caad986056120361bc7 [file] [log] [blame]
// Copyright (c) 2019, 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.graph;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AbstractAccessContexts.ConcreteAccessContexts;
import com.android.tools.r8.graph.lens.GraphLens;
import com.android.tools.r8.shaking.Enqueuer;
import com.android.tools.r8.shaking.Enqueuer.FieldAccessKind;
import com.android.tools.r8.utils.BitUtils;
import com.android.tools.r8.utils.ObjectUtils;
import com.android.tools.r8.utils.collections.ProgramMethodSet;
import com.google.common.collect.Sets;
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Predicate;
/**
* Holds whole program information about the usage of a given field.
*
* <p>The information is generated by the {@link com.android.tools.r8.shaking.Enqueuer}.
*/
public class FieldAccessInfoImpl implements MutableFieldAccessInfo {
public static final FieldAccessInfoImpl MISSING_FIELD_ACCESS_INFO = new FieldAccessInfoImpl(null);
public static final int FLAG_IS_READ_FROM_ANNOTATION = 1 << 0;
public static final int FLAG_IS_READ_FROM_METHOD_HANDLE = 1 << 1;
public static final int FLAG_IS_WRITTEN_FROM_METHOD_HANDLE = 1 << 2;
public static final int FLAG_HAS_REFLECTIVE_READ = 1 << 3;
public static final int FLAG_HAS_REFLECTIVE_WRITE = 1 << 4;
public static final int FLAG_IS_READ_DIRECTLY = 1 << 5;
public static final int FLAG_IS_READ_FROM_RECORD_INVOKE_DYNAMIC = 1 << 6;
public static final int FLAG_IS_READ_FROM_FIND_LITE_EXTENSION_BY_NUMBER_METHOD = 1 << 7;
public static final int FLAG_IS_READ_FROM_NON_FIND_LITE_EXTENSION_BY_NUMBER_METHOD = 1 << 8;
public static final int FLAG_IS_WRITTEN_DIRECTLY = 1 << 9;
public static final int FLAG_IS_WRITTEN_FROM_NON_CLASS_INITIALIZER = 1 << 10;
public static final int FLAG_IS_WRITTEN_FROM_NON_INSTANCE_INITIALIZER = 1 << 11;
// A direct reference to the definition of the field.
private final DexField field;
// If this field is accessed from a method handle or has a reflective access.
private int flags;
// Maps every direct and indirect reference in a read-context to the set of methods in which that
// reference appears.
private AbstractAccessContexts readsWithContexts;
// Maps every direct and indirect reference in a write-context to the set of methods in which that
// reference appears.
private AbstractAccessContexts writesWithContexts;
// The unique write context of this field, or null.
private ProgramMethod uniqueWriteContext;
public FieldAccessInfoImpl(DexField field) {
this(field, 0, AbstractAccessContexts.empty(), AbstractAccessContexts.empty());
}
public FieldAccessInfoImpl(DexField field, int flags, ProgramMethod uniqueWriteContext) {
this(field, flags, AbstractAccessContexts.unknown(), AbstractAccessContexts.unknown());
this.uniqueWriteContext = uniqueWriteContext;
}
public FieldAccessInfoImpl(
DexField field,
int flags,
AbstractAccessContexts readsWithContexts,
AbstractAccessContexts writesWithContexts) {
this.field = field;
this.flags = flags;
this.readsWithContexts = readsWithContexts;
this.writesWithContexts = writesWithContexts;
}
public void destroyAccessContexts(Enqueuer.Mode mode) {
assert uniqueWriteContext == null;
if (mode.isInitialTreeShaking()) {
setUniqueWriteContextFromWritesWithContexts();
}
readsWithContexts = AbstractAccessContexts.unknown();
writesWithContexts = AbstractAccessContexts.unknown();
}
@Override
public DexField getField() {
return field;
}
public void setReadsWithContexts(AbstractAccessContexts readsWithContexts) {
this.readsWithContexts = readsWithContexts;
}
public void setWritesWithContexts(AbstractAccessContexts writesWithContexts) {
this.writesWithContexts = writesWithContexts;
}
public ProgramMethod getUniqueWriteContext() {
// We only set the `uniqueWriteContext` when we destroy `writesWithContexts`. Therefore,
// disallow uses of this method if the `writesWithContexts` has not been set to top.
assert writesWithContexts.isTop();
return uniqueWriteContext;
}
private void setUniqueWriteContextFromWritesWithContexts() {
if (writesWithContexts.isConcrete() && !isWrittenIndirectly()) {
Map<DexField, ProgramMethodSet> accessesWithContexts =
writesWithContexts.asConcrete().getAccessesWithContexts();
if (accessesWithContexts.size() == 1) {
ProgramMethodSet contexts = accessesWithContexts.values().iterator().next();
if (contexts.size() == 1) {
uniqueWriteContext = contexts.getFirst();
}
}
}
}
void clearUniqueWriteContext() {
uniqueWriteContext = null;
}
@Override
public ProgramMethod getUniqueWriteContextForCallGraphConstruction() {
return getUniqueWriteContext();
}
@Override
public ProgramMethod getUniqueWriteContextForFieldValueAnalysis() {
return getUniqueWriteContext();
}
public boolean hasKnownReadContexts() {
return !readsWithContexts.isTop();
}
public boolean hasKnownWriteContexts() {
return !writesWithContexts.isTop();
}
public void forEachIndirectAccess(Consumer<DexField> consumer) {
// There can be indirect reads and writes of the same field reference, so we need to keep track
// of the previously-seen indirect accesses to avoid reporting duplicates.
Set<DexField> visited = Sets.newIdentityHashSet();
forEachIndirectAccess(consumer, readsWithContexts, visited);
forEachIndirectAccess(consumer, writesWithContexts, visited);
}
@SuppressWarnings("ReferenceEquality")
private void forEachIndirectAccess(
Consumer<DexField> consumer,
AbstractAccessContexts accessesWithContexts,
Set<DexField> visited) {
if (accessesWithContexts.isBottom()) {
return;
}
if (accessesWithContexts.isConcrete()) {
accessesWithContexts
.asConcrete()
.forEachAccess(consumer, access -> access != field && visited.add(access));
return;
}
throw new Unreachable("Should never be iterating the indirect accesses when they are unknown");
}
public void forEachAccessContext(Consumer<ProgramMethod> consumer) {
readsWithContexts.forEachAccessContext(consumer);
writesWithContexts.forEachAccessContext(consumer);
}
@Override
public boolean hasReflectiveAccess() {
return hasReflectiveRead() || hasReflectiveWrite();
}
@Override
public boolean hasReflectiveRead() {
return BitUtils.isBitInMaskSet(flags, FLAG_HAS_REFLECTIVE_READ);
}
public boolean setHasReflectiveRead() {
if (!hasReflectiveRead()) {
flags |= FLAG_HAS_REFLECTIVE_READ;
return true;
}
return false;
}
@Override
public boolean hasReflectiveWrite() {
return BitUtils.isBitInMaskSet(flags, FLAG_HAS_REFLECTIVE_WRITE);
}
public boolean setHasReflectiveWrite() {
if (!hasReflectiveWrite()) {
flags |= FLAG_HAS_REFLECTIVE_WRITE;
return true;
}
return false;
}
@Override
public boolean isEffectivelyFinal(ProgramField field) {
if (field.getAccessFlags().isStatic()) {
return BitUtils.isBitInMaskUnset(flags, FLAG_IS_WRITTEN_FROM_NON_CLASS_INITIALIZER);
} else {
return BitUtils.isBitInMaskUnset(flags, FLAG_IS_WRITTEN_FROM_NON_INSTANCE_INITIALIZER);
}
}
public void setWrittenFromNonClassInitializer() {
flags |= FLAG_IS_WRITTEN_FROM_NON_CLASS_INITIALIZER;
}
public void clearWrittenFromNonClassInitializer() {
flags &= ~FLAG_IS_WRITTEN_FROM_NON_CLASS_INITIALIZER;
}
public void setWrittenFromNonInstanceInitializer() {
flags |= FLAG_IS_WRITTEN_FROM_NON_INSTANCE_INITIALIZER;
}
public void clearWrittenFromNonInstanceInitializer() {
flags &= ~FLAG_IS_WRITTEN_FROM_NON_INSTANCE_INITIALIZER;
}
/** Returns true if this field is read by the program. */
@Override
public boolean isRead() {
return isReadDirectly() || isReadIndirectly();
}
public boolean isReadDirectly() {
return BitUtils.isBitInMaskSet(flags, FLAG_IS_READ_DIRECTLY);
}
public void setReadDirectly() {
flags |= FLAG_IS_READ_DIRECTLY;
}
private void clearReadDirectly() {
flags &= ~FLAG_IS_READ_DIRECTLY;
}
@Override
public boolean isReadFromAnnotation() {
return BitUtils.isBitInMaskSet(flags, FLAG_IS_READ_FROM_ANNOTATION);
}
public void setReadFromAnnotation() {
flags |= FLAG_IS_READ_FROM_ANNOTATION;
}
@Override
public boolean isReadFromMethodHandle() {
return BitUtils.isBitInMaskSet(flags, FLAG_IS_READ_FROM_METHOD_HANDLE);
}
public void setAccessedFromMethodHandle(FieldAccessKind accessKind) {
if (accessKind.isRead()) {
setReadFromMethodHandle();
} else {
setWrittenFromMethodHandle();
}
}
public void setReadFromMethodHandle() {
flags |= FLAG_IS_READ_FROM_METHOD_HANDLE;
}
@Override
public boolean isReadFromRecordInvokeDynamic() {
return BitUtils.isBitInMaskSet(flags, FLAG_IS_READ_FROM_RECORD_INVOKE_DYNAMIC);
}
public void setReadFromRecordInvokeDynamic() {
flags |= FLAG_IS_READ_FROM_RECORD_INVOKE_DYNAMIC;
}
public void clearReadFromRecordInvokeDynamic() {
flags &= ~FLAG_IS_READ_FROM_RECORD_INVOKE_DYNAMIC;
}
@Override
public boolean isReadOnlyFromFindLiteExtensionByNumberMethod() {
return BitUtils.isBitInMaskSet(flags, FLAG_IS_READ_FROM_FIND_LITE_EXTENSION_BY_NUMBER_METHOD)
&& BitUtils.isBitInMaskUnset(
flags, FLAG_IS_READ_FROM_NON_FIND_LITE_EXTENSION_BY_NUMBER_METHOD);
}
public void setReadFromFindLiteExtensionByNumberMethod() {
flags |= FLAG_IS_READ_FROM_FIND_LITE_EXTENSION_BY_NUMBER_METHOD;
}
public void setReadFromNonFindLiteExtensionByNumberMethod() {
flags |= FLAG_IS_READ_FROM_NON_FIND_LITE_EXTENSION_BY_NUMBER_METHOD;
}
/** Returns true if this field is written by the program. */
@Override
public boolean isWritten() {
return isWrittenDirectly() || isWrittenIndirectly();
}
public boolean isWrittenDirectly() {
return BitUtils.isBitInMaskSet(flags, FLAG_IS_WRITTEN_DIRECTLY);
}
public void setWrittenDirectly() {
flags |= FLAG_IS_WRITTEN_DIRECTLY;
}
private void clearWrittenDirectly() {
flags &= ~FLAG_IS_WRITTEN_DIRECTLY;
}
@Override
public boolean isWrittenFromMethodHandle() {
return BitUtils.isBitInMaskSet(flags, FLAG_IS_WRITTEN_FROM_METHOD_HANDLE);
}
public void setWrittenFromMethodHandle() {
flags |= FLAG_IS_WRITTEN_FROM_METHOD_HANDLE;
}
/**
* Returns true if this field is written by a method for which {@param predicate} returns true.
*/
public boolean isWrittenInMethodSatisfying(Predicate<ProgramMethod> predicate) {
return writesWithContexts.isAccessedInMethodSatisfying(predicate);
}
/**
* Returns true if this field is written by a method in the program other than {@param method}.
*/
public boolean isWrittenOutside(DexEncodedMethod method) {
return writesWithContexts.isAccessedOutside(method) || isWrittenIndirectly();
}
public boolean recordRead(DexField access, ProgramMethod context) {
setReadDirectly();
if (readsWithContexts.isBottom()) {
readsWithContexts = new ConcreteAccessContexts();
}
if (readsWithContexts.isConcrete()) {
return readsWithContexts.asConcrete().recordAccess(access, context);
}
return false;
}
public boolean recordWrite(DexField access, ProgramMethod context) {
setWrittenDirectly();
updateEffectivelyFinalFlags(context);
if (writesWithContexts.isBottom()) {
writesWithContexts = new ConcreteAccessContexts();
}
if (writesWithContexts.isConcrete()) {
return writesWithContexts.asConcrete().recordAccess(access, context);
}
return false;
}
private void updateEffectivelyFinalFlags(ProgramMethod context) {
if (context.getAccessFlags().isConstructor()
&& context.getHolderType().isIdenticalTo(field.getHolderType())) {
if (context.getAccessFlags().isStatic()) {
setWrittenFromNonInstanceInitializer();
} else {
setWrittenFromNonClassInitializer();
}
} else {
setWrittenFromNonClassInitializer();
setWrittenFromNonInstanceInitializer();
}
}
@Override
public void clearReads() {
assert !hasReflectiveAccess();
assert !isReadFromAnnotation();
assert !isReadFromMethodHandle();
assert readsWithContexts.isTop();
clearReadDirectly();
clearReadFromRecordInvokeDynamic();
}
@Override
public void clearWrites() {
assert !isWrittenIndirectly();
assert writesWithContexts.isTop();
clearWrittenDirectly();
clearWrittenFromNonClassInitializer();
clearWrittenFromNonInstanceInitializer();
clearUniqueWriteContext();
}
public FieldAccessInfoImpl rewrittenWithLens(
DexDefinitionSupplier definitions, GraphLens lens, GraphLens appliedLens) {
assert readsWithContexts.isTop();
assert writesWithContexts.isTop();
DexField rewrittenField = lens.lookupField(field, appliedLens);
ProgramMethod rewrittenUniqueWriteContext = null;
if (uniqueWriteContext != null) {
rewrittenUniqueWriteContext =
uniqueWriteContext.rewrittenWithLens(lens, appliedLens, definitions);
}
if (rewrittenField.isIdenticalTo(field)
&& (ObjectUtils.identical(rewrittenUniqueWriteContext, uniqueWriteContext)
|| (rewrittenUniqueWriteContext != null
&& rewrittenUniqueWriteContext.isStructurallyEqualTo(uniqueWriteContext)))) {
return this;
}
return new FieldAccessInfoImpl(rewrittenField, flags, rewrittenUniqueWriteContext);
}
public FieldAccessInfoImpl join(FieldAccessInfoImpl impl) {
assert readsWithContexts.isTop();
assert writesWithContexts.isTop();
return new FieldAccessInfoImpl(
field,
flags | impl.flags,
AbstractAccessContexts.unknown(),
AbstractAccessContexts.unknown());
}
public FieldAccessInfoImpl withoutPrunedItems(PrunedItems prunedItems) {
assert readsWithContexts.isTop();
assert writesWithContexts.isTop();
if (uniqueWriteContext != null && prunedItems.isRemoved(uniqueWriteContext.getReference())) {
uniqueWriteContext = null;
}
return this;
}
}