| // 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.utils.collections.ProgramMethodSet; |
| import com.google.common.collect.Sets; |
| 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.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 FieldAccessInfo { |
| |
| public static final FieldAccessInfoImpl MISSING_FIELD_ACCESS_INFO = new FieldAccessInfoImpl(null); |
| |
| public static int FLAG_IS_READ_FROM_ANNOTATION = 1 << 0; |
| public static int FLAG_IS_READ_FROM_METHOD_HANDLE = 1 << 1; |
| public static int FLAG_IS_WRITTEN_FROM_METHOD_HANDLE = 1 << 2; |
| public static int FLAG_HAS_REFLECTIVE_ACCESS = 1 << 3; |
| |
| // A direct reference to the definition of the field. |
| private 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 = AbstractAccessContexts.empty(); |
| |
| // Maps every direct and indirect reference in a write-context to the set of methods in which that |
| // reference appears. |
| private AbstractAccessContexts writesWithContexts = AbstractAccessContexts.empty(); |
| |
| public FieldAccessInfoImpl(DexField field) { |
| this.field = field; |
| } |
| |
| void destroyAccessContexts() { |
| readsWithContexts = AbstractAccessContexts.unknown(); |
| writesWithContexts = AbstractAccessContexts.unknown(); |
| } |
| |
| void flattenAccessContexts() { |
| flattenAccessContexts(readsWithContexts); |
| flattenAccessContexts(writesWithContexts); |
| } |
| |
| private void flattenAccessContexts(AbstractAccessContexts accessesWithContexts) { |
| accessesWithContexts.flattenAccessContexts(field); |
| } |
| |
| @Override |
| public FieldAccessInfoImpl asMutable() { |
| return this; |
| } |
| |
| @Override |
| public DexField getField() { |
| return field; |
| } |
| |
| public AbstractAccessContexts getReadsWithContexts() { |
| return readsWithContexts; |
| } |
| |
| public void setReadsWithContexts(AbstractAccessContexts readsWithContexts) { |
| this.readsWithContexts = readsWithContexts; |
| } |
| |
| public void setWritesWithContexts(AbstractAccessContexts writesWithContexts) { |
| this.writesWithContexts = writesWithContexts; |
| } |
| |
| @Override |
| public int getNumberOfReadContexts() { |
| return readsWithContexts.getNumberOfAccessContexts(); |
| } |
| |
| @Override |
| public int getNumberOfWriteContexts() { |
| return writesWithContexts.getNumberOfAccessContexts(); |
| } |
| |
| @Override |
| public ProgramMethod getUniqueReadContext() { |
| return readsWithContexts.isConcrete() |
| ? readsWithContexts.asConcrete().getUniqueAccessContext() |
| : null; |
| } |
| |
| @Override |
| public boolean hasKnownWriteContexts() { |
| return !writesWithContexts.isTop(); |
| } |
| |
| @Override |
| 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); |
| } |
| |
| 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"); |
| } |
| |
| @Override |
| public void forEachIndirectAccessWithContexts(BiConsumer<DexField, ProgramMethodSet> consumer) { |
| Map<DexField, ProgramMethodSet> indirectAccessesWithContexts = new IdentityHashMap<>(); |
| addAccessesWithContextsToMap( |
| readsWithContexts, access -> access != field, indirectAccessesWithContexts); |
| addAccessesWithContextsToMap( |
| writesWithContexts, access -> access != field, indirectAccessesWithContexts); |
| indirectAccessesWithContexts.forEach(consumer); |
| } |
| |
| private static void addAccessesWithContextsToMap( |
| AbstractAccessContexts accessesWithContexts, |
| Predicate<DexField> predicate, |
| Map<DexField, ProgramMethodSet> out) { |
| if (accessesWithContexts.isBottom()) { |
| return; |
| } |
| if (accessesWithContexts.isConcrete()) { |
| extendAccessesWithContexts( |
| accessesWithContexts.asConcrete().getAccessesWithContexts(), predicate, out); |
| return; |
| } |
| throw new Unreachable("Should never be iterating the indirect accesses when they are unknown"); |
| } |
| |
| private static void extendAccessesWithContexts( |
| Map<DexField, ProgramMethodSet> accessesWithContexts, |
| Predicate<DexField> predicate, |
| Map<DexField, ProgramMethodSet> out) { |
| accessesWithContexts.forEach( |
| (access, contexts) -> { |
| if (predicate.test(access)) { |
| out.computeIfAbsent(access, ignore -> ProgramMethodSet.create()).addAll(contexts); |
| } |
| }); |
| } |
| |
| @Override |
| public void forEachReadContext(Consumer<ProgramMethod> consumer) { |
| readsWithContexts.forEachAccessContext(consumer); |
| } |
| |
| @Override |
| public void forEachWriteContext(Consumer<ProgramMethod> consumer) { |
| writesWithContexts.forEachAccessContext(consumer); |
| } |
| |
| @Override |
| public boolean hasReflectiveAccess() { |
| return (flags & FLAG_HAS_REFLECTIVE_ACCESS) != 0; |
| } |
| |
| public void setHasReflectiveAccess() { |
| flags |= FLAG_HAS_REFLECTIVE_ACCESS; |
| } |
| |
| /** Returns true if this field is read by the program. */ |
| @Override |
| public boolean isRead() { |
| return !readsWithContexts.isEmpty() || isReadFromAnnotation() || isReadFromMethodHandle(); |
| } |
| |
| @Override |
| public boolean isReadFromAnnotation() { |
| return (flags & FLAG_IS_READ_FROM_ANNOTATION) != 0; |
| } |
| |
| public void setReadFromAnnotation() { |
| flags |= FLAG_IS_READ_FROM_ANNOTATION; |
| } |
| |
| @Override |
| public boolean isReadFromMethodHandle() { |
| return (flags & FLAG_IS_READ_FROM_METHOD_HANDLE) != 0; |
| } |
| |
| public void setReadFromMethodHandle() { |
| flags |= FLAG_IS_READ_FROM_METHOD_HANDLE; |
| } |
| |
| /** Returns true if this field is written by the program. */ |
| @Override |
| public boolean isWritten() { |
| return !writesWithContexts.isEmpty(); |
| } |
| |
| @Override |
| public boolean isWrittenFromMethodHandle() { |
| return (flags & FLAG_IS_WRITTEN_FROM_METHOD_HANDLE) != 0; |
| } |
| |
| 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. |
| */ |
| @Override |
| public boolean isWrittenInMethodSatisfying(Predicate<ProgramMethod> predicate) { |
| return writesWithContexts.isAccessedInMethodSatisfying(predicate); |
| } |
| |
| /** |
| * Returns true if this field is only written by methods for which {@param predicate} returns |
| * true. |
| */ |
| @Override |
| public boolean isWrittenOnlyInMethodSatisfying(Predicate<ProgramMethod> predicate) { |
| return writesWithContexts.isAccessedOnlyInMethodSatisfying(predicate); |
| } |
| |
| /** |
| * Returns true if this field is only read by methods for which {@param predicate} returns true. |
| */ |
| @Override |
| public boolean isReadOnlyInMethodSatisfying(Predicate<ProgramMethod> predicate) { |
| return readsWithContexts.isAccessedOnlyInMethodSatisfying(predicate); |
| } |
| |
| /** |
| * Returns true if this field is written by a method in the program other than {@param method}. |
| */ |
| @Override |
| public boolean isWrittenOutside(DexEncodedMethod method) { |
| return writesWithContexts.isAccessedOutside(method); |
| } |
| |
| public boolean recordRead(DexField access, ProgramMethod context) { |
| if (readsWithContexts.isBottom()) { |
| readsWithContexts = new ConcreteAccessContexts(); |
| } |
| if (readsWithContexts.isConcrete()) { |
| return readsWithContexts.asConcrete().recordAccess(access, context); |
| } |
| return false; |
| } |
| |
| public boolean recordWrite(DexField access, ProgramMethod context) { |
| if (writesWithContexts.isBottom()) { |
| writesWithContexts = new ConcreteAccessContexts(); |
| } |
| if (writesWithContexts.isConcrete()) { |
| return writesWithContexts.asConcrete().recordAccess(access, context); |
| } |
| return false; |
| } |
| |
| public void clearReads() { |
| readsWithContexts = AbstractAccessContexts.empty(); |
| } |
| |
| public void clearWrites() { |
| writesWithContexts = AbstractAccessContexts.empty(); |
| } |
| |
| public FieldAccessInfoImpl rewrittenWithLens(DexDefinitionSupplier definitions, GraphLens lens) { |
| FieldAccessInfoImpl rewritten = new FieldAccessInfoImpl(lens.lookupField(field)); |
| rewritten.flags = flags; |
| rewritten.readsWithContexts = readsWithContexts.rewrittenWithLens(definitions, lens); |
| rewritten.writesWithContexts = writesWithContexts.rewrittenWithLens(definitions, lens); |
| return rewritten; |
| } |
| |
| public FieldAccessInfoImpl join(FieldAccessInfoImpl impl) { |
| FieldAccessInfoImpl merged = new FieldAccessInfoImpl(field); |
| merged.flags = flags | impl.flags; |
| merged.readsWithContexts = readsWithContexts.join(impl.readsWithContexts); |
| merged.writesWithContexts = writesWithContexts.join(impl.writesWithContexts); |
| return merged; |
| } |
| } |