blob: 871ed696637689193bb4d029d8d0d3286f8918c6 [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.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 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;
}
}