|  | // 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) { | 
|  | newContexts.add(lens.mapProgramMethod(context, definitions)); | 
|  | } | 
|  | }); | 
|  | 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; | 
|  | } | 
|  | } | 
|  | } |