| // 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.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); |
| |
| // A direct reference to the definition of the field. |
| private DexField field; |
| |
| // If this field has a reflective access. |
| private boolean hasReflectiveAccess; |
| |
| // Maps every direct and indirect reference in a read-context to the set of methods in which that |
| // reference appears. |
| private Map<DexField, Set<DexEncodedMethod>> readsWithContexts; |
| |
| // Maps every direct and indirect reference in a write-context to the set of methods in which that |
| // reference appears. |
| private Map<DexField, Set<DexEncodedMethod>> writesWithContexts; |
| |
| public FieldAccessInfoImpl(DexField field) { |
| this.field = field; |
| } |
| |
| void flattenAccessContexts() { |
| flattenAccessContexts(readsWithContexts); |
| flattenAccessContexts(writesWithContexts); |
| } |
| |
| private void flattenAccessContexts(Map<DexField, Set<DexEncodedMethod>> accessesWithContexts) { |
| if (accessesWithContexts != null) { |
| Set<DexEncodedMethod> flattenedAccessContexts = |
| accessesWithContexts.computeIfAbsent(field, ignore -> Sets.newIdentityHashSet()); |
| accessesWithContexts.forEach( |
| (access, contexts) -> { |
| if (access != field) { |
| flattenedAccessContexts.addAll(contexts); |
| } |
| }); |
| accessesWithContexts.clear(); |
| if (!flattenedAccessContexts.isEmpty()) { |
| accessesWithContexts.put(field, flattenedAccessContexts); |
| } |
| assert accessesWithContexts.size() <= 1; |
| } |
| } |
| |
| @Override |
| public FieldAccessInfoImpl asMutable() { |
| return this; |
| } |
| |
| @Override |
| public DexField getField() { |
| return field; |
| } |
| |
| @Override |
| public int getNumberOfReadContexts() { |
| return getNumberOfAccessContexts(readsWithContexts); |
| } |
| |
| @Override |
| public int getNumberOfWriteContexts() { |
| return getNumberOfAccessContexts(writesWithContexts); |
| } |
| |
| private int getNumberOfAccessContexts(Map<DexField, Set<DexEncodedMethod>> accessesWithContexts) { |
| if (accessesWithContexts == null) { |
| return 0; |
| } |
| if (accessesWithContexts.size() == 1) { |
| return accessesWithContexts.values().iterator().next().size(); |
| } |
| throw new Unreachable("Should only be querying the number of access contexts after flattening"); |
| } |
| |
| @Override |
| public DexEncodedMethod getUniqueReadContext() { |
| if (readsWithContexts != null && readsWithContexts.size() == 1) { |
| Set<DexEncodedMethod> contexts = readsWithContexts.values().iterator().next(); |
| if (contexts.size() == 1) { |
| return contexts.iterator().next(); |
| } |
| } |
| return null; |
| } |
| |
| @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(); |
| forEachAccessInMap( |
| readsWithContexts, access -> access != field && visited.add(access), consumer); |
| forEachAccessInMap( |
| writesWithContexts, access -> access != field && visited.add(access), consumer); |
| } |
| |
| private static void forEachAccessInMap( |
| Map<DexField, Set<DexEncodedMethod>> accessesWithContexts, |
| Predicate<DexField> predicate, |
| Consumer<DexField> consumer) { |
| if (accessesWithContexts != null) { |
| accessesWithContexts.forEach( |
| (access, contexts) -> { |
| if (predicate.test(access)) { |
| consumer.accept(access); |
| } |
| }); |
| } |
| } |
| |
| @Override |
| public void forEachIndirectAccessWithContexts( |
| BiConsumer<DexField, Set<DexEncodedMethod>> consumer) { |
| Map<DexField, Set<DexEncodedMethod>> indirectAccessesWithContexts = new IdentityHashMap<>(); |
| extendAccessesWithContexts( |
| indirectAccessesWithContexts, access -> access != field, readsWithContexts); |
| extendAccessesWithContexts( |
| indirectAccessesWithContexts, access -> access != field, writesWithContexts); |
| indirectAccessesWithContexts.forEach(consumer); |
| } |
| |
| private void extendAccessesWithContexts( |
| Map<DexField, Set<DexEncodedMethod>> accessesWithContexts, |
| Predicate<DexField> predicate, |
| Map<DexField, Set<DexEncodedMethod>> extension) { |
| if (extension != null) { |
| extension.forEach( |
| (access, contexts) -> { |
| if (predicate.test(access)) { |
| accessesWithContexts |
| .computeIfAbsent(access, ignore -> Sets.newIdentityHashSet()) |
| .addAll(contexts); |
| } |
| }); |
| } |
| } |
| |
| @Override |
| public void forEachReadContext(Consumer<DexEncodedMethod> consumer) { |
| forEachAccessContext(readsWithContexts, consumer); |
| } |
| |
| @Override |
| public void forEachWriteContext(Consumer<DexEncodedMethod> consumer) { |
| forEachAccessContext(writesWithContexts, consumer); |
| } |
| |
| private void forEachAccessContext( |
| Map<DexField, Set<DexEncodedMethod>> accessesWithContexts, |
| Consumer<DexEncodedMethod> 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<DexEncodedMethod> visited = Sets.newIdentityHashSet(); |
| if (accessesWithContexts != null) { |
| for (Set<DexEncodedMethod> encodedAccessContexts : accessesWithContexts.values()) { |
| for (DexEncodedMethod encodedAccessContext : encodedAccessContexts) { |
| if (visited.add(encodedAccessContext)) { |
| consumer.accept(encodedAccessContext); |
| } |
| } |
| } |
| } |
| } |
| |
| @Override |
| public boolean hasReflectiveAccess() { |
| return hasReflectiveAccess; |
| } |
| |
| public void setHasReflectiveAccess() { |
| hasReflectiveAccess = true; |
| } |
| |
| /** Returns true if this field is read by the program. */ |
| @Override |
| public boolean isRead() { |
| return readsWithContexts != null && !readsWithContexts.isEmpty(); |
| } |
| |
| @Override |
| public boolean isReadOnlyIn(DexEncodedMethod method) { |
| assert isRead(); |
| assert method != null; |
| DexEncodedMethod uniqueReadContext = getUniqueReadContext(); |
| return uniqueReadContext != null && uniqueReadContext == method; |
| } |
| |
| /** Returns true if this field is written by the program. */ |
| @Override |
| public boolean isWritten() { |
| return writesWithContexts != null && !writesWithContexts.isEmpty(); |
| } |
| |
| /** |
| * Returns true if this field is written by a method for which {@param predicate} returns true. |
| */ |
| @Override |
| public boolean isWrittenInMethodSatisfying(Predicate<DexEncodedMethod> predicate) { |
| if (writesWithContexts != null) { |
| for (Set<DexEncodedMethod> encodedWriteContexts : writesWithContexts.values()) { |
| for (DexEncodedMethod encodedWriteContext : encodedWriteContexts) { |
| if (predicate.test(encodedWriteContext)) { |
| return true; |
| } |
| } |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * Returns true if this field is written by a method in the program other than {@param method}. |
| */ |
| @Override |
| public boolean isWrittenOutside(DexEncodedMethod method) { |
| if (writesWithContexts != null) { |
| for (Set<DexEncodedMethod> encodedWriteContexts : writesWithContexts.values()) { |
| for (DexEncodedMethod encodedWriteContext : encodedWriteContexts) { |
| if (encodedWriteContext != method) { |
| return true; |
| } |
| } |
| } |
| } |
| return false; |
| } |
| |
| public boolean recordRead(DexField access, DexEncodedMethod context) { |
| if (readsWithContexts == null) { |
| readsWithContexts = new IdentityHashMap<>(); |
| } |
| return readsWithContexts |
| .computeIfAbsent(access, ignore -> Sets.newIdentityHashSet()) |
| .add(context); |
| } |
| |
| public boolean recordWrite(DexField access, DexEncodedMethod context) { |
| if (writesWithContexts == null) { |
| writesWithContexts = new IdentityHashMap<>(); |
| } |
| return writesWithContexts |
| .computeIfAbsent(access, ignore -> Sets.newIdentityHashSet()) |
| .add(context); |
| } |
| |
| public void clearReads() { |
| readsWithContexts = null; |
| } |
| |
| public void clearWrites() { |
| writesWithContexts = null; |
| } |
| |
| public FieldAccessInfoImpl rewrittenWithLens(DexDefinitionSupplier definitions, GraphLense lens) { |
| FieldAccessInfoImpl rewritten = new FieldAccessInfoImpl(lens.lookupField(field)); |
| if (hasReflectiveAccess) { |
| rewritten.setHasReflectiveAccess(); |
| } |
| if (readsWithContexts != null) { |
| rewritten.readsWithContexts = new IdentityHashMap<>(); |
| readsWithContexts.forEach( |
| (access, contexts) -> { |
| Set<DexEncodedMethod> newContexts = |
| rewritten.readsWithContexts.computeIfAbsent( |
| lens.lookupField(access), ignore -> Sets.newIdentityHashSet()); |
| for (DexEncodedMethod context : contexts) { |
| newContexts.add(lens.mapDexEncodedMethod(context, definitions)); |
| } |
| }); |
| } |
| if (writesWithContexts != null) { |
| rewritten.writesWithContexts = new IdentityHashMap<>(); |
| writesWithContexts.forEach( |
| (access, contexts) -> { |
| Set<DexEncodedMethod> newContexts = |
| rewritten.writesWithContexts.computeIfAbsent( |
| lens.lookupField(access), ignore -> Sets.newIdentityHashSet()); |
| for (DexEncodedMethod context : contexts) { |
| newContexts.add(lens.mapDexEncodedMethod(context, definitions)); |
| } |
| }); |
| } |
| return rewritten; |
| } |
| } |