blob: fcd4f5cf21e43318f20f799c2a5b8869abde1a80 [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.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;
// 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;
}
@Override
public FieldAccessInfoImpl asMutable() {
return this;
}
@Override
public DexField getField() {
return field;
}
@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<DexMethod> 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<DexMethod> visited = Sets.newIdentityHashSet();
if (readsWithContexts != null) {
for (Set<DexEncodedMethod> encodedReadContexts : readsWithContexts.values()) {
for (DexEncodedMethod encodedReadContext : encodedReadContexts) {
DexMethod readContext = encodedReadContext.method;
if (visited.add(readContext)) {
consumer.accept(readContext);
}
}
}
}
}
/** 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 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(GraphLense lens) {
FieldAccessInfoImpl rewritten = new FieldAccessInfoImpl(lens.lookupField(field));
if (readsWithContexts != null) {
rewritten.readsWithContexts = new IdentityHashMap<>();
readsWithContexts.forEach(
(access, contexts) ->
rewritten
.readsWithContexts
.computeIfAbsent(lens.lookupField(access), ignore -> Sets.newIdentityHashSet())
.addAll(contexts));
}
if (writesWithContexts != null) {
rewritten.writesWithContexts = new IdentityHashMap<>();
writesWithContexts.forEach(
(access, contexts) ->
rewritten
.writesWithContexts
.computeIfAbsent(lens.lookupField(access), ignore -> Sets.newIdentityHashSet())
.addAll(contexts));
}
return rewritten;
}
}