blob: 616fcf5c637f5fc9b46627bcc36e65146b74996d [file] [log] [blame]
// Copyright (c) 2020, 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.utils.collections.ProgramMethodSet;
import java.util.IdentityHashMap;
import java.util.Map;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Predicate;
/**
* For a concrete field, stores the contexts in which the field is accessed.
*
* <p>If the concrete field does not have any accesses, then {@link EmptyAccessContexts}.
*
* <p>If nothing is nothing about the accesses to the concrete field, then {@link
* UnknownAccessContexts}.
*
* <p>Otherwise, the concrete contexts in which the field is accessed is maintained by {@link
* ConcreteAccessContexts}. The access contexts are qualified by the field reference they access.
*
* <p>Example: If a field `int Foo.field` is accessed directly in `void Main.direct()` and
* indirectly via a non-rebound reference `int FooSub.field` in `void Main.indirect()`, then the
* collection is:
*
* <pre>
* ConcreteAccessContexts {
* `int Foo.field` -> { `void Main.direct()` }
* `int FooSub.field` -> { `void Main.indirect()` }
* }
* </pre>
*/
public abstract class AbstractAccessContexts {
abstract void flattenAccessContexts(DexField field);
abstract void forEachAccessContext(Consumer<ProgramMethod> consumer);
/**
* Returns true if this field is written by a method for which {@param predicate} returns true.
*/
abstract boolean isAccessedInMethodSatisfying(Predicate<ProgramMethod> predicate);
/**
* Returns true if this field is only written by methods for which {@param predicate} returns
* true.
*/
abstract boolean isAccessedOnlyInMethodSatisfying(Predicate<ProgramMethod> predicate);
/**
* Returns true if this field is written by a method in the program other than {@param method}.
*/
abstract boolean isAccessedOutside(DexEncodedMethod method);
abstract int getNumberOfAccessContexts();
public boolean isBottom() {
return false;
}
public boolean isConcrete() {
return false;
}
abstract boolean isEmpty();
public ConcreteAccessContexts asConcrete() {
return null;
}
public boolean isTop() {
return false;
}
abstract AbstractAccessContexts rewrittenWithLens(
DexDefinitionSupplier definitions, GraphLens lens);
public static EmptyAccessContexts empty() {
return EmptyAccessContexts.getInstance();
}
public static UnknownAccessContexts unknown() {
return UnknownAccessContexts.getInstance();
}
public abstract AbstractAccessContexts join(AbstractAccessContexts contexts);
public static class EmptyAccessContexts extends AbstractAccessContexts {
public static EmptyAccessContexts INSTANCE = new EmptyAccessContexts();
private EmptyAccessContexts() {}
public static EmptyAccessContexts getInstance() {
return INSTANCE;
}
@Override
void flattenAccessContexts(DexField field) {
// Intentionally empty.
}
@Override
void forEachAccessContext(Consumer<ProgramMethod> consumer) {
// Intentionally empty.
}
@Override
boolean isAccessedInMethodSatisfying(Predicate<ProgramMethod> predicate) {
return false;
}
@Override
boolean isAccessedOnlyInMethodSatisfying(Predicate<ProgramMethod> predicate) {
return true;
}
@Override
boolean isAccessedOutside(DexEncodedMethod method) {
return false;
}
@Override
int getNumberOfAccessContexts() {
return 0;
}
@Override
public boolean isBottom() {
return true;
}
@Override
public boolean isEmpty() {
return true;
}
@Override
AbstractAccessContexts rewrittenWithLens(DexDefinitionSupplier definitions, GraphLens lens) {
return this;
}
@Override
public AbstractAccessContexts join(AbstractAccessContexts contexts) {
return contexts;
}
}
public static class ConcreteAccessContexts extends AbstractAccessContexts {
private final Map<DexField, ProgramMethodSet> accessesWithContexts;
public ConcreteAccessContexts() {
this(new IdentityHashMap<>());
}
public ConcreteAccessContexts(Map<DexField, ProgramMethodSet> accessesWithContexts) {
this.accessesWithContexts = accessesWithContexts;
}
void forEachAccess(Consumer<DexField> consumer, Predicate<DexField> predicate) {
if (accessesWithContexts != null) {
accessesWithContexts.forEach(
(access, contexts) -> {
if (predicate.test(access)) {
consumer.accept(access);
}
});
}
}
@Override
void forEachAccessContext(Consumer<ProgramMethod> 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.
ProgramMethodSet visited = ProgramMethodSet.create();
if (accessesWithContexts != null) {
for (ProgramMethodSet encodedAccessContexts : accessesWithContexts.values()) {
for (ProgramMethod encodedAccessContext : encodedAccessContexts) {
if (visited.add(encodedAccessContext)) {
consumer.accept(encodedAccessContext);
}
}
}
}
}
public Map<DexField, ProgramMethodSet> getAccessesWithContexts() {
return accessesWithContexts;
}
@Override
int getNumberOfAccessContexts() {
if (accessesWithContexts.size() == 1) {
return accessesWithContexts.values().iterator().next().size();
}
throw new Unreachable(
"Should only be querying the number of access contexts after flattening");
}
ProgramMethod getUniqueAccessContext() {
if (accessesWithContexts != null && accessesWithContexts.size() == 1) {
ProgramMethodSet contexts = accessesWithContexts.values().iterator().next();
if (contexts.size() == 1) {
return contexts.iterator().next();
}
}
return null;
}
@Override
void flattenAccessContexts(DexField field) {
if (accessesWithContexts != null) {
ProgramMethodSet flattenedAccessContexts =
accessesWithContexts.computeIfAbsent(field, ignore -> ProgramMethodSet.create());
accessesWithContexts.forEach(
(access, contexts) -> {
if (access != field) {
flattenedAccessContexts.addAll(contexts);
}
});
accessesWithContexts.clear();
if (!flattenedAccessContexts.isEmpty()) {
accessesWithContexts.put(field, flattenedAccessContexts);
}
assert accessesWithContexts.size() <= 1;
}
}
/**
* Returns true if this field is written by a method for which {@param predicate} returns true.
*/
@Override
public boolean isAccessedInMethodSatisfying(Predicate<ProgramMethod> predicate) {
for (ProgramMethodSet encodedWriteContexts : accessesWithContexts.values()) {
for (ProgramMethod encodedWriteContext : encodedWriteContexts) {
if (predicate.test(encodedWriteContext)) {
return true;
}
}
}
return false;
}
/**
* Returns true if this field is only written by methods for which {@param predicate} returns
* true.
*/
@Override
public boolean isAccessedOnlyInMethodSatisfying(Predicate<ProgramMethod> predicate) {
for (ProgramMethodSet encodedWriteContexts : accessesWithContexts.values()) {
for (ProgramMethod encodedWriteContext : encodedWriteContexts) {
if (!predicate.test(encodedWriteContext)) {
return false;
}
}
}
return true;
}
/**
* Returns true if this field is written by a method in the program other than {@param method}.
*/
@Override
public boolean isAccessedOutside(DexEncodedMethod method) {
for (ProgramMethodSet encodedWriteContexts : accessesWithContexts.values()) {
for (ProgramMethod encodedWriteContext : encodedWriteContexts) {
if (encodedWriteContext.getDefinition() != method) {
return true;
}
}
}
return false;
}
@Override
public boolean isConcrete() {
return true;
}
@Override
public ConcreteAccessContexts asConcrete() {
return this;
}
@Override
public boolean isEmpty() {
return accessesWithContexts.isEmpty();
}
public boolean recordAccess(DexField access, ProgramMethod context) {
return accessesWithContexts
.computeIfAbsent(access, ignore -> ProgramMethodSet.create())
.add(context);
}
@Override
ConcreteAccessContexts rewrittenWithLens(DexDefinitionSupplier definitions, GraphLens lens) {
Map<DexField, ProgramMethodSet> newAccessesWithContexts = new IdentityHashMap<>();
accessesWithContexts.forEach(
(access, contexts) -> {
ProgramMethodSet newContexts =
newAccessesWithContexts.computeIfAbsent(
lens.lookupField(access), ignore -> ProgramMethodSet.create());
for (ProgramMethod context : contexts) {
ProgramMethod newContext = lens.mapProgramMethod(context, definitions);
assert newContext != null : "Unable to map context: " + context.toSourceString();
newContexts.add(newContext);
}
});
return new ConcreteAccessContexts(newAccessesWithContexts);
}
@Override
public AbstractAccessContexts join(AbstractAccessContexts contexts) {
if (contexts.isEmpty()) {
return this;
}
if (contexts.isTop()) {
return contexts;
}
Map<DexField, ProgramMethodSet> newAccessesWithContexts = new IdentityHashMap<>();
accessesWithContexts.forEach(
(field, methodSet) ->
newAccessesWithContexts.put(field, ProgramMethodSet.create(methodSet)));
BiConsumer<DexField, ProgramMethodSet> addAllMethods =
(field, methodSet) ->
newAccessesWithContexts
.computeIfAbsent(field, ignore -> ProgramMethodSet.create())
.addAll(methodSet);
contexts.asConcrete().accessesWithContexts.forEach(addAllMethods);
return new ConcreteAccessContexts(newAccessesWithContexts);
}
}
public static class UnknownAccessContexts extends AbstractAccessContexts {
public static UnknownAccessContexts INSTANCE = new UnknownAccessContexts();
private UnknownAccessContexts() {}
public static UnknownAccessContexts getInstance() {
return INSTANCE;
}
@Override
void flattenAccessContexts(DexField field) {
// Intentionally empty.
}
@Override
void forEachAccessContext(Consumer<ProgramMethod> consumer) {
throw new Unreachable("Should never be iterating the access contexts when they are unknown");
}
@Override
boolean isAccessedInMethodSatisfying(Predicate<ProgramMethod> predicate) {
return true;
}
@Override
boolean isAccessedOnlyInMethodSatisfying(Predicate<ProgramMethod> predicate) {
return false;
}
@Override
boolean isAccessedOutside(DexEncodedMethod method) {
return true;
}
@Override
int getNumberOfAccessContexts() {
throw new Unreachable(
"Should never be querying the number of access contexts when they are unknown");
}
@Override
public boolean isEmpty() {
return false;
}
@Override
public boolean isTop() {
return true;
}
@Override
AbstractAccessContexts rewrittenWithLens(DexDefinitionSupplier definitions, GraphLens lens) {
return this;
}
@Override
public AbstractAccessContexts join(AbstractAccessContexts contexts) {
return this;
}
}
}