Reintroduce legacy access modifier for testing
This partially reverts d2732c9c0b3812069ad0dd1592cc77cb16c13552 and a046083828eaf3a7878b9ff9db15ca28f0f9b78b.
Change-Id: I730b36a13269f196aeb6db22bb9b41f3d05c35cc
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 94109ba..10d9755 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -64,6 +64,7 @@
import com.android.tools.r8.naming.ProguardMapMinifier;
import com.android.tools.r8.naming.RecordRewritingNamingLens;
import com.android.tools.r8.naming.signature.GenericSignatureRewriter;
+import com.android.tools.r8.optimize.LegacyAccessModifier;
import com.android.tools.r8.optimize.MemberRebindingAnalysis;
import com.android.tools.r8.optimize.MemberRebindingIdentityLens;
import com.android.tools.r8.optimize.MemberRebindingIdentityLensFactory;
@@ -452,6 +453,9 @@
// to clear the cache, so that we will recompute the type lattice elements.
appView.dexItemFactory().clearTypeElementsCache();
+ // TODO(b/132677331): Remove legacy access modifier.
+ LegacyAccessModifier.run(appViewWithLiveness, executorService, timing);
+
// This pass attempts to reduce the number of nests and nest size to allow further passes, and
// should therefore be run after the publicizer.
new NestReducer(appViewWithLiveness).run(executorService, timing);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/MemberPoolCollection.java b/src/main/java/com/android/tools/r8/ir/optimize/MemberPoolCollection.java
new file mode 100644
index 0000000..bd0376b
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/MemberPoolCollection.java
@@ -0,0 +1,276 @@
+// 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.ir.optimize;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexMember;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.SubtypingInfo;
+import com.android.tools.r8.graph.TopDownClassHierarchyTraversal;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.ThreadUtils;
+import com.android.tools.r8.utils.Timing;
+import com.android.tools.r8.utils.WorkList;
+import com.google.common.base.Equivalence;
+import com.google.common.base.Equivalence.Wrapper;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Deque;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Future;
+import java.util.function.BiFunction;
+import java.util.function.Predicate;
+
+// Per-class collection of member signatures.
+public abstract class MemberPoolCollection<R extends DexMember<?, R>> {
+
+ final Equivalence<R> equivalence;
+ final AppView<AppInfoWithLiveness> appView;
+ final SubtypingInfo subtypingInfo;
+ final Map<DexClass, MemberPool<R>> memberPools = new ConcurrentHashMap<>();
+
+ MemberPoolCollection(
+ AppView<AppInfoWithLiveness> appView,
+ Equivalence<R> equivalence,
+ SubtypingInfo subtypingInfo) {
+ this.appView = appView;
+ this.equivalence = equivalence;
+ this.subtypingInfo = subtypingInfo;
+ }
+
+ public void buildAll(ExecutorService executorService, Timing timing) throws ExecutionException {
+ timing.begin("Building member pool collection");
+ try {
+ List<Future<?>> futures = new ArrayList<>();
+
+ // Generate a future for each class that will build the member pool collection for the
+ // corresponding class. Note that, we visit the classes using a top-down class hierarchy
+ // traversal, since this ensures that we do not visit library classes that are not
+ // reachable from any program class.
+ TopDownClassHierarchyTraversal.forAllClasses(appView)
+ .visit(appView.appInfo().classes(), clazz -> submit(clazz, futures, executorService));
+ ThreadUtils.awaitFutures(futures);
+ } finally {
+ timing.end();
+ }
+ }
+
+ public MemberPool<R> buildForHierarchy(
+ DexClass clazz, ExecutorService executorService, Timing timing) throws ExecutionException {
+ timing.begin("Building member pool collection");
+ try {
+ List<Future<?>> futures = new ArrayList<>();
+ submitAll(
+ getAllSuperTypesInclusive(clazz, memberPools::containsKey), futures, executorService);
+ submitAll(getAllSubTypesExclusive(clazz, memberPools::containsKey), futures, executorService);
+ ThreadUtils.awaitFutures(futures);
+ } finally {
+ timing.end();
+ }
+ return get(clazz);
+ }
+
+ public boolean hasPool(DexClass clazz) {
+ return memberPools.containsKey(clazz);
+ }
+
+ public MemberPool<R> get(DexClass clazz) {
+ assert hasPool(clazz);
+ return memberPools.get(clazz);
+ }
+
+ public boolean markIfNotSeen(DexClass clazz, R reference) {
+ MemberPool<R> memberPool = get(clazz);
+ Wrapper<R> key = equivalence.wrap(reference);
+ if (memberPool.hasSeen(key)) {
+ return true;
+ }
+ memberPool.seen(key);
+ return false;
+ }
+
+ private void submitAll(
+ Iterable<? extends DexClass> classes,
+ List<Future<?>> futures,
+ ExecutorService executorService) {
+ for (DexClass clazz : classes) {
+ submit(clazz, futures, executorService);
+ }
+ }
+
+ private void submit(DexClass clazz, List<Future<?>> futures, ExecutorService executorService) {
+ futures.add(executorService.submit(computeMemberPoolForClass(clazz)));
+ }
+
+ abstract Runnable computeMemberPoolForClass(DexClass clazz);
+
+ private Set<DexClass> getAllSuperTypesInclusive(
+ DexClass subject, Predicate<DexClass> stoppingCriterion) {
+ Set<DexClass> superTypes = new HashSet<>();
+ Deque<DexClass> worklist = new ArrayDeque<>();
+ worklist.add(subject);
+ while (!worklist.isEmpty()) {
+ DexClass clazz = worklist.pop();
+ if (stoppingCriterion.test(clazz)) {
+ continue;
+ }
+ if (superTypes.add(clazz)) {
+ if (clazz.superType != null) {
+ addNonNull(worklist, appView.definitionFor(clazz.superType));
+ }
+ for (DexType interfaceType : clazz.interfaces.values) {
+ addNonNull(worklist, appView.definitionFor(interfaceType));
+ }
+ }
+ }
+ return superTypes;
+ }
+
+ private Set<DexClass> getAllSubTypesExclusive(
+ DexClass subject, Predicate<DexClass> stoppingCriterion) {
+ Set<DexClass> subTypes = new HashSet<>();
+ Deque<DexClass> worklist = new ArrayDeque<>();
+ subtypingInfo.forAllImmediateExtendsSubtypes(
+ subject.type, type -> addNonNull(worklist, appView.definitionFor(type)));
+ subtypingInfo.forAllImmediateImplementsSubtypes(
+ subject.type, type -> addNonNull(worklist, appView.definitionFor(type)));
+ while (!worklist.isEmpty()) {
+ DexClass clazz = worklist.pop();
+ if (stoppingCriterion.test(clazz)) {
+ continue;
+ }
+ if (subTypes.add(clazz)) {
+ subtypingInfo.forAllImmediateExtendsSubtypes(
+ clazz.type, type -> addNonNull(worklist, appView.definitionFor(type)));
+ subtypingInfo.forAllImmediateImplementsSubtypes(
+ clazz.type, type -> addNonNull(worklist, appView.definitionFor(type)));
+ }
+ }
+ return subTypes;
+ }
+
+ public static class MemberPool<T> {
+
+ private final DexClass clazz;
+ private final Equivalence<T> equivalence;
+ private MemberPool<T> superType;
+ private final Set<MemberPool<T>> interfaces = new HashSet<>();
+ private final Set<MemberPool<T>> subTypes = new HashSet<>();
+ private final Set<Wrapper<T>> memberPool = new HashSet<>();
+
+ MemberPool(Equivalence<T> equivalence, DexClass clazz) {
+ this.equivalence = equivalence;
+ this.clazz = clazz;
+ }
+
+ synchronized void linkSupertype(MemberPool<T> superType) {
+ assert this.superType == null;
+ this.superType = superType;
+ }
+
+ synchronized void linkSubtype(MemberPool<T> subType) {
+ boolean added = subTypes.add(subType);
+ assert added;
+ }
+
+ synchronized void linkInterface(MemberPool<T> itf) {
+ boolean added = interfaces.add(itf);
+ assert added;
+ }
+
+ public void seen(T member) {
+ seen(equivalence.wrap(member));
+ }
+
+ public synchronized void seen(Wrapper<T> member) {
+ boolean added = memberPool.add(member);
+ assert added;
+ }
+
+ public boolean hasSeen(Wrapper<T> member) {
+ return fold(member, false, true, (t, ignored) -> true);
+ }
+
+ public boolean hasSeenDirectly(Wrapper<T> member) {
+ return here(member, false, (t, ignored) -> true);
+ }
+
+ public boolean hasSeenStrictlyAbove(Wrapper<T> member) {
+ return above(member, false, false, true, (t, ignored) -> true);
+ }
+
+ public boolean hasSeenStrictlyBelow(Wrapper<T> member) {
+ return below(member, false, true, (t, ignored) -> true);
+ }
+
+ private <S> S above(
+ Wrapper<T> member,
+ boolean inclusive,
+ S value,
+ S terminator,
+ BiFunction<DexClass, S, S> accumulator) {
+ WorkList<MemberPool<T>> workList = WorkList.newIdentityWorkList(this);
+ while (workList.hasNext()) {
+ MemberPool<T> next = workList.next();
+ if (inclusive) {
+ value = next.here(member, value, accumulator);
+ if (value == terminator) {
+ return value;
+ }
+ }
+ inclusive = true;
+ if (next.superType != null) {
+ workList.addIfNotSeen(next.superType);
+ }
+ workList.addIfNotSeen(next.interfaces);
+ }
+ return value;
+ }
+
+ private <S> S here(Wrapper<T> member, S value, BiFunction<DexClass, S, S> accumulator) {
+ if (memberPool.contains(member)) {
+ return accumulator.apply(clazz, value);
+ }
+ return value;
+ }
+
+ public <S> S below(
+ Wrapper<T> member, S value, S terminator, BiFunction<DexClass, S, S> accumulator) {
+ WorkList<MemberPool<T>> workList = WorkList.newIdentityWorkList(this.subTypes);
+ while (workList.hasNext()) {
+ MemberPool<T> next = workList.next();
+ value = next.here(member, value, accumulator);
+ if (value == terminator) {
+ return value;
+ }
+ workList.addIfNotSeen(next.interfaces);
+ workList.addIfNotSeen(next.subTypes);
+ }
+ return value;
+ }
+
+ public <S> S fold(
+ Wrapper<T> member, S initialValue, S terminator, BiFunction<DexClass, S, S> accumulator) {
+ S value = above(member, true, initialValue, terminator, accumulator);
+ if (value == terminator) {
+ return value;
+ }
+ return below(member, initialValue, terminator, accumulator);
+ }
+ }
+
+ private static <T> void addNonNull(Collection<T> collection, T item) {
+ if (item != null) {
+ collection.add(item);
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/MethodPoolCollection.java b/src/main/java/com/android/tools/r8/ir/optimize/MethodPoolCollection.java
new file mode 100644
index 0000000..78eb343
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/MethodPoolCollection.java
@@ -0,0 +1,80 @@
+// Copyright (c) 2018, 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.ir.optimize;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.SubtypingInfo;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.MethodSignatureEquivalence;
+import com.google.common.base.Predicates;
+import java.util.function.Predicate;
+
+// Per-class collection of method signatures.
+//
+// Example use cases:
+// *) in publicizer,
+// to determine if a private method does not collide with methods in that class hierarchy.
+// *) in vertical class merger,
+// before moving a default interface method to its subtype, check if it does not collide with one
+// in the given class hierarchy.
+public class MethodPoolCollection extends MemberPoolCollection<DexMethod> {
+
+ private final Predicate<DexEncodedMethod> methodTester;
+
+ public MethodPoolCollection(AppView<AppInfoWithLiveness> appView, SubtypingInfo subtypingInfo) {
+ this(appView, subtypingInfo, Predicates.alwaysTrue());
+ }
+
+ public MethodPoolCollection(
+ AppView<AppInfoWithLiveness> appView,
+ SubtypingInfo subtypingInfo,
+ Predicate<DexEncodedMethod> methodTester) {
+ super(appView, MethodSignatureEquivalence.get(), subtypingInfo);
+ this.methodTester = methodTester;
+ }
+
+ public static boolean excludesPrivateInstanceMethod(DexEncodedMethod method) {
+ return !method.isPrivateMethod() || method.isStatic();
+ }
+
+ @Override
+ Runnable computeMemberPoolForClass(DexClass clazz) {
+ return () -> {
+ MemberPool<DexMethod> methodPool =
+ memberPools.computeIfAbsent(clazz, k -> new MemberPool<>(equivalence, k));
+ clazz.forEachMethod(
+ encodedMethod -> {
+ if (methodTester.test(encodedMethod)) {
+ methodPool.seen(equivalence.wrap(encodedMethod.getReference()));
+ }
+ });
+ if (clazz.superType != null) {
+ DexClass superClazz = appView.definitionFor(clazz.superType);
+ if (superClazz != null) {
+ MemberPool<DexMethod> superPool =
+ memberPools.computeIfAbsent(
+ superClazz, k -> new MemberPool<>(equivalence, superClazz));
+ superPool.linkSubtype(methodPool);
+ methodPool.linkSupertype(superPool);
+ }
+ }
+ if (clazz.isInterface()) {
+ for (DexType subtype : subtypingInfo.allImmediateSubtypes(clazz.type)) {
+ DexClass subClazz = appView.definitionFor(subtype);
+ if (subClazz != null) {
+ MemberPool<DexMethod> childPool =
+ memberPools.computeIfAbsent(subClazz, k -> new MemberPool<>(equivalence, subClazz));
+ methodPool.linkSubtype(childPool);
+ childPool.linkInterface(methodPool);
+ }
+ }
+ }
+ };
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/optimize/LegacyAccessModifier.java b/src/main/java/com/android/tools/r8/optimize/LegacyAccessModifier.java
new file mode 100644
index 0000000..c51b13c
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/optimize/LegacyAccessModifier.java
@@ -0,0 +1,225 @@
+// Copyright (c) 2016, 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.optimize;
+
+import static com.android.tools.r8.dex.Constants.ACC_PRIVATE;
+import static com.android.tools.r8.dex.Constants.ACC_PROTECTED;
+import static com.android.tools.r8.dex.Constants.ACC_PUBLIC;
+import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.FieldAccessFlags;
+import com.android.tools.r8.graph.InnerClassAttribute;
+import com.android.tools.r8.graph.MethodAccessFlags;
+import com.android.tools.r8.graph.ProgramDefinition;
+import com.android.tools.r8.graph.ProgramField;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.SubtypingInfo;
+import com.android.tools.r8.ir.optimize.MemberPoolCollection.MemberPool;
+import com.android.tools.r8.ir.optimize.MethodPoolCollection;
+import com.android.tools.r8.optimize.PublicizerLens.PublicizedLensBuilder;
+import com.android.tools.r8.optimize.accessmodification.AccessModifierOptions;
+import com.android.tools.r8.optimize.redundantbridgeremoval.RedundantBridgeRemover;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.MethodSignatureEquivalence;
+import com.android.tools.r8.utils.OptionalBool;
+import com.android.tools.r8.utils.Timing;
+import com.google.common.base.Equivalence.Wrapper;
+import java.util.LinkedHashSet;
+import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+
+public final class LegacyAccessModifier {
+
+ private final AppView<AppInfoWithLiveness> appView;
+ private final SubtypingInfo subtypingInfo;
+ private final MethodPoolCollection methodPoolCollection;
+
+ private final PublicizedLensBuilder lensBuilder = PublicizerLens.createBuilder();
+
+ private LegacyAccessModifier(AppView<AppInfoWithLiveness> appView) {
+ this.appView = appView;
+ this.subtypingInfo = appView.appInfo().computeSubtypingInfo();
+ this.methodPoolCollection =
+ // We will add private instance methods when we promote them.
+ new MethodPoolCollection(
+ appView, subtypingInfo, MethodPoolCollection::excludesPrivateInstanceMethod);
+ }
+
+ /**
+ * Marks all package private and protected methods and fields as public. Makes all private static
+ * methods public. Makes private instance methods public final instance methods, if possible.
+ *
+ * <p>This will destructively update the DexApplication passed in as argument.
+ */
+ public static void run(
+ AppView<AppInfoWithLiveness> appView, ExecutorService executorService, Timing timing)
+ throws ExecutionException {
+ AccessModifierOptions accessModifierOptions = appView.options().getAccessModifierOptions();
+ if (accessModifierOptions.isAccessModificationEnabled()
+ && accessModifierOptions.isLegacyAccessModifierEnabled()) {
+ timing.begin("Access modification");
+ new LegacyAccessModifier(appView).internalRun(executorService, timing);
+ timing.end();
+ if (appView.graphLens().isPublicizerLens()) {
+ // We can now remove redundant bridges. Note that we do not need to update the
+ // invoke-targets here, as the existing invokes will simply dispatch to the now
+ // visible super-method. MemberRebinding, if run, will then dispatch it correctly.
+ new RedundantBridgeRemover(appView.withLiveness()).run(executorService, timing);
+ }
+ }
+ }
+
+ private void internalRun(ExecutorService executorService, Timing timing)
+ throws ExecutionException {
+ // Phase 1: Collect methods to check if private instance methods don't have conflicts.
+ methodPoolCollection.buildAll(executorService, timing);
+
+ // Phase 2: Visit classes and promote class/member to public if possible.
+ timing.begin("Phase 2: promoteToPublic");
+ appView.appInfo().forEachReachableInterface(clazz -> processType(clazz.getType()));
+ processType(appView.dexItemFactory().objectType);
+ timing.end();
+
+ PublicizerLens publicizerLens = lensBuilder.build(appView);
+ if (publicizerLens != null) {
+ appView.setGraphLens(publicizerLens);
+ }
+
+ appView.notifyOptimizationFinishedForTesting();
+ }
+
+ private void doPublicize(ProgramDefinition definition) {
+ definition.getAccessFlags().promoteToPublic();
+ }
+
+ private void processType(DexType type) {
+ DexProgramClass clazz = asProgramClassOrNull(appView.definitionFor(type));
+ if (clazz != null) {
+ processClass(clazz);
+ }
+ subtypingInfo.forAllImmediateExtendsSubtypes(type, this::processType);
+ }
+
+ private void processClass(DexProgramClass clazz) {
+ if (appView.appInfo().isAccessModificationAllowed(clazz)) {
+ doPublicize(clazz);
+ }
+
+ // Publicize fields.
+ clazz.forEachProgramField(this::processField);
+
+ // Publicize methods.
+ Set<DexEncodedMethod> privateInstanceMethods = new LinkedHashSet<>();
+ clazz.forEachProgramMethod(
+ method -> {
+ if (publicizeMethod(method)) {
+ privateInstanceMethods.add(method.getDefinition());
+ }
+ });
+ if (!privateInstanceMethods.isEmpty()) {
+ clazz.virtualizeMethods(privateInstanceMethods);
+ }
+
+ // Publicize inner class attribute.
+ InnerClassAttribute attr = clazz.getInnerClassAttributeForThisClass();
+ if (attr != null) {
+ int accessFlags = ((attr.getAccess() | ACC_PUBLIC) & ~ACC_PRIVATE) & ~ACC_PROTECTED;
+ clazz.replaceInnerClassAttributeForThisClass(
+ new InnerClassAttribute(
+ accessFlags, attr.getInner(), attr.getOuter(), attr.getInnerName()));
+ }
+ }
+
+ private void processField(ProgramField field) {
+ if (appView.appInfo().isAccessModificationAllowed(field)) {
+ publicizeField(field);
+ }
+ }
+
+ private void publicizeField(ProgramField field) {
+ FieldAccessFlags flags = field.getAccessFlags();
+ if (!flags.isPublic()) {
+ flags.promoteToPublic();
+ }
+ }
+
+ private boolean publicizeMethod(ProgramMethod method) {
+ MethodAccessFlags accessFlags = method.getAccessFlags();
+ if (accessFlags.isPublic()) {
+ return false;
+ }
+ // If this method is mentioned in keep rules, do not transform (rule applications changed).
+ DexEncodedMethod definition = method.getDefinition();
+ if (!appView.appInfo().isAccessModificationAllowed(method)) {
+ // TODO(b/131130038): Also do not publicize package-private and protected methods that are
+ // kept.
+ if (definition.isPrivate()) {
+ return false;
+ }
+ }
+
+ if (method.getDefinition().isInstanceInitializer() || accessFlags.isProtected()) {
+ doPublicize(method);
+ return false;
+ }
+
+ if (accessFlags.isPackagePrivate()) {
+ // If we publicize a package private method we have to ensure there is no overrides of it. We
+ // could potentially publicize a method if it only has package-private overrides.
+ // TODO(b/182136236): See if we can break the hierarchy for clusters.
+ MemberPool<DexMethod> memberPool = methodPoolCollection.get(method.getHolder());
+ Wrapper<DexMethod> methodKey = MethodSignatureEquivalence.get().wrap(method.getReference());
+ if (memberPool.below(
+ methodKey,
+ false,
+ true,
+ (clazz, ignored) ->
+ !method.getContextType().getPackageName().equals(clazz.getType().getPackageName()))) {
+ return false;
+ }
+ doPublicize(method);
+ return false;
+ }
+
+ assert accessFlags.isPrivate();
+
+ if (accessFlags.isStatic()) {
+ // For private static methods we can just relax the access to public, since
+ // even though JLS prevents from declaring static method in derived class if
+ // an instance method with same signature exists in superclass, JVM actually
+ // does not take into account access of the static methods.
+ doPublicize(method);
+ return false;
+ }
+
+ // We can't publicize private instance methods in interfaces or methods that are copied from
+ // interfaces to lambda-desugared classes because this will be added as a new default method.
+ // TODO(b/111118390): It might be possible to transform it into static methods, though.
+ if (method.getHolder().isInterface() || accessFlags.isSynthetic()) {
+ return false;
+ }
+
+ boolean wasSeen = methodPoolCollection.markIfNotSeen(method.getHolder(), method.getReference());
+ if (wasSeen) {
+ // We can't do anything further because even renaming is not allowed due to the keep rule.
+ if (!appView.appInfo().isMinificationAllowed(method)) {
+ return false;
+ }
+ // TODO(b/111118390): Renaming will enable more private instance methods to be publicized.
+ return false;
+ }
+ lensBuilder.add(method.getReference());
+ accessFlags.promoteToFinal();
+ doPublicize(method);
+ // The method just became public and is therefore not a library override.
+ definition.setLibraryMethodOverride(OptionalBool.FALSE);
+ return true;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/optimize/accessmodification/AccessModifier.java b/src/main/java/com/android/tools/r8/optimize/accessmodification/AccessModifier.java
index 6304490..c197b85 100644
--- a/src/main/java/com/android/tools/r8/optimize/accessmodification/AccessModifier.java
+++ b/src/main/java/com/android/tools/r8/optimize/accessmodification/AccessModifier.java
@@ -61,7 +61,9 @@
AppView<AppInfoWithLiveness> appView, ExecutorService executorService, Timing timing)
throws ExecutionException {
timing.begin("Access modification");
- if (appView.options().getAccessModifierOptions().isAccessModificationEnabled()) {
+ AccessModifierOptions accessModifierOptions = appView.options().getAccessModifierOptions();
+ if (accessModifierOptions.isAccessModificationEnabled()
+ && !accessModifierOptions.isLegacyAccessModifierEnabled()) {
new AccessModifier(appView)
.processStronglyConnectedComponents(executorService)
.installLens(executorService, timing);
diff --git a/src/main/java/com/android/tools/r8/optimize/accessmodification/AccessModifierOptions.java b/src/main/java/com/android/tools/r8/optimize/accessmodification/AccessModifierOptions.java
index 7644cb3..81c1f03 100644
--- a/src/main/java/com/android/tools/r8/optimize/accessmodification/AccessModifierOptions.java
+++ b/src/main/java/com/android/tools/r8/optimize/accessmodification/AccessModifierOptions.java
@@ -5,9 +5,14 @@
package com.android.tools.r8.optimize.accessmodification;
import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.SystemPropertyUtils;
public class AccessModifierOptions {
+ private boolean enableLegacyAccessModifier =
+ SystemPropertyUtils.parseSystemPropertyOrDefault(
+ "com.android.tools.r8.accessmodification.legacy", false);
+
// TODO(b/131130038): Do not allow accessmodification when kept.
private boolean forceModifyPackagePrivateAndProtectedMethods = true;
@@ -25,6 +30,9 @@
if (isAccessModificationRulePresent()) {
return true;
}
+ if (isLegacyAccessModifierEnabled()) {
+ return false;
+ }
// TODO(b/288062771): Enable access modification by default for L8.
return options.synthesizedClassPrefix.isEmpty()
&& !options.forceProguardCompatibility
@@ -45,4 +53,8 @@
this.forceModifyPackagePrivateAndProtectedMethods =
forceModifyPackagePrivateAndProtectedMethods;
}
+
+ public boolean isLegacyAccessModifierEnabled() {
+ return enableLegacyAccessModifier;
+ }
}