| // 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; |
| } |
| } |