Mark fields as final with -allowaccessmodification
Change-Id: I8f936ff0936a88bd79b903f1af89234f8e22a84b
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index ab6b1a0..c75fbf2 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -67,7 +67,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.ClassAndMemberPublicizer;
+import com.android.tools.r8.optimize.AccessModifier;
import com.android.tools.r8.optimize.MemberRebindingAnalysis;
import com.android.tools.r8.optimize.MemberRebindingIdentityLensFactory;
import com.android.tools.r8.optimize.VisibilityBridgeRemover;
@@ -462,7 +462,7 @@
if (options.getProguardConfiguration().isAccessModificationAllowed()) {
SubtypingInfo subtypingInfo = appViewWithLiveness.appInfo().computeSubtypingInfo();
GraphLens publicizedLens =
- ClassAndMemberPublicizer.run(
+ AccessModifier.run(
executorService,
timing,
appViewWithLiveness.appInfo().app(),
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
index 51a4a4a..b785927 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -447,6 +447,10 @@
return isInstanceInitializer() || isClassInitializer();
}
+ public boolean isInitializer(boolean isStatic) {
+ return isStatic ? isClassInitializer() : isInstanceInitializer();
+ }
+
public boolean isInstanceInitializer() {
checkIfObsolete();
return accessFlags.isConstructor() && !accessFlags.isStatic();
diff --git a/src/main/java/com/android/tools/r8/optimize/ClassAndMemberPublicizer.java b/src/main/java/com/android/tools/r8/optimize/AccessModifier.java
similarity index 80%
rename from src/main/java/com/android/tools/r8/optimize/ClassAndMemberPublicizer.java
rename to src/main/java/com/android/tools/r8/optimize/AccessModifier.java
index ad17739..7661768 100644
--- a/src/main/java/com/android/tools/r8/optimize/ClassAndMemberPublicizer.java
+++ b/src/main/java/com/android/tools/r8/optimize/AccessModifier.java
@@ -10,11 +10,12 @@
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexApplication;
-import com.android.tools.r8.graph.DexEncodedField;
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.FieldAccessInfo;
import com.android.tools.r8.graph.GraphLens;
import com.android.tools.r8.graph.InnerClassAttribute;
import com.android.tools.r8.graph.MethodAccessFlags;
@@ -26,7 +27,8 @@
import com.android.tools.r8.ir.optimize.MethodPoolCollection;
import com.android.tools.r8.optimize.PublicizerLens.PublicizedLensBuilder;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.shaking.KeepInfoCollection;
+import com.android.tools.r8.shaking.KeepFieldInfo;
+import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.MethodSignatureEquivalence;
import com.android.tools.r8.utils.OptionalBool;
import com.android.tools.r8.utils.Timing;
@@ -36,23 +38,23 @@
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
-public final class ClassAndMemberPublicizer {
+public final class AccessModifier {
private final DexApplication application;
private final AppView<AppInfoWithLiveness> appView;
- private final KeepInfoCollection keepInfo;
+ private final InternalOptions options;
private final SubtypingInfo subtypingInfo;
private final MethodPoolCollection methodPoolCollection;
private final PublicizedLensBuilder lensBuilder = PublicizerLens.createBuilder();
- private ClassAndMemberPublicizer(
+ private AccessModifier(
DexApplication application,
AppView<AppInfoWithLiveness> appView,
SubtypingInfo subtypingInfo) {
this.application = application;
this.appView = appView;
- this.keepInfo = appView.appInfo().getKeepInfo();
+ this.options = appView.options();
this.subtypingInfo = subtypingInfo;
this.methodPoolCollection =
// We will add private instance methods when we promote them.
@@ -73,8 +75,7 @@
AppView<AppInfoWithLiveness> appView,
SubtypingInfo subtypingInfo)
throws ExecutionException {
- return new ClassAndMemberPublicizer(application, appView, subtypingInfo)
- .run(executorService, timing);
+ return new AccessModifier(application, appView, subtypingInfo).run(executorService, timing);
}
private GraphLens run(ExecutorService executorService, Timing timing) throws ExecutionException {
@@ -83,8 +84,8 @@
// Phase 2: Visit classes and promote class/member to public if possible.
timing.begin("Phase 2: promoteToPublic");
- appView.appInfo().forEachReachableInterface(clazz -> publicizeType(clazz.getType()));
- publicizeType(appView.dexItemFactory().objectType);
+ appView.appInfo().forEachReachableInterface(clazz -> processType(clazz.getType()));
+ processType(appView.dexItemFactory().objectType);
timing.end();
return lensBuilder.build(appView);
@@ -94,21 +95,21 @@
definition.getAccessFlags().promoteToPublic();
}
- private void publicizeType(DexType type) {
+ private void processType(DexType type) {
DexProgramClass clazz = asProgramClassOrNull(application.definitionFor(type));
if (clazz != null) {
- publicizeClass(clazz);
+ processClass(clazz);
}
- subtypingInfo.forAllImmediateExtendsSubtypes(type, this::publicizeType);
+ subtypingInfo.forAllImmediateExtendsSubtypes(type, this::processType);
}
- private void publicizeClass(DexProgramClass clazz) {
+ private void processClass(DexProgramClass clazz) {
if (appView.appInfo().isAccessModificationAllowed(clazz)) {
doPublicize(clazz);
}
// Publicize fields.
- clazz.forEachProgramField(this::publicizeField);
+ clazz.forEachProgramField(this::processField);
// Publicize methods.
Set<DexEncodedMethod> privateInstanceMethods = new LinkedHashSet<>();
@@ -132,19 +133,43 @@
}
}
+ private void processField(ProgramField field) {
+ finalizeField(field);
+ publicizeField(field);
+ }
+
+ private void finalizeField(ProgramField field) {
+ FieldAccessFlags flags = field.getAccessFlags();
+ FieldAccessInfo accessInfo =
+ appView.appInfo().getFieldAccessInfoCollection().get(field.getReference());
+ KeepFieldInfo keepInfo = appView.getKeepInfo(field);
+ if (keepInfo.isAccessModificationAllowed(options)
+ && !keepInfo.isPinned(options)
+ && !accessInfo.hasReflectiveWrite()
+ && !accessInfo.isWrittenFromMethodHandle()
+ && accessInfo.isWrittenOnlyInMethodSatisfying(
+ method ->
+ method.getDefinition().isInitializer(flags.isStatic())
+ && method.getHolder() == field.getHolder())
+ && !flags.isFinal()
+ && !flags.isVolatile()) {
+ flags.promoteToFinal();
+ }
+ }
+
private void publicizeField(ProgramField field) {
- DexEncodedField definition = field.getDefinition();
- if (definition.isPublic()) {
+ FieldAccessFlags flags = field.getAccessFlags();
+ if (flags.isPublic()) {
return;
}
if (!appView.appInfo().isAccessModificationAllowed(field)) {
// TODO(b/131130038): Also do not publicize package-private and protected fields that
// are kept.
- if (definition.isPrivate()) {
+ if (flags.isPrivate()) {
return;
}
}
- doPublicize(field);
+ flags.promoteToPublic();
}
private boolean publicizeMethod(ProgramMethod method) {
diff --git a/src/test/java/com/android/tools/r8/accessrelaxation/EffectiveFinalFieldMarkedFinalTest.java b/src/test/java/com/android/tools/r8/accessrelaxation/EffectiveFinalFieldMarkedFinalTest.java
new file mode 100644
index 0000000..b1c5fc8
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/accessrelaxation/EffectiveFinalFieldMarkedFinalTest.java
@@ -0,0 +1,73 @@
+// Copyright (c) 2022, 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.accessrelaxation;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isFinal;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.onlyIf;
+import static org.hamcrest.CoreMatchers.allOf;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class EffectiveFinalFieldMarkedFinalTest extends TestBase {
+
+ @Parameter(0)
+ public boolean allowAccessModification;
+
+ @Parameter(1)
+ public TestParameters parameters;
+
+ @Parameters(name = "{1}, allowaccessmodification: {0}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ BooleanUtils.values(), getTestParameters().withAllRuntimesAndApiLevels().build());
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(Main.class)
+ .allowAccessModification(allowAccessModification)
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(
+ inspector -> {
+ ClassSubject mainClassSubject = inspector.clazz(Main.class);
+ assertThat(mainClassSubject, isPresent());
+ assertThat(
+ mainClassSubject.uniqueFieldWithName("instanceField"),
+ allOf(isPresent(), onlyIf(allowAccessModification, isFinal())));
+ assertThat(
+ mainClassSubject.uniqueFieldWithName("staticField"),
+ allOf(isPresent(), onlyIf(allowAccessModification, isFinal())));
+ })
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("Hello, world!");
+ }
+
+ static class Main {
+
+ static String staticField = System.currentTimeMillis() > 0 ? "Hello" : null;
+
+ String instanceField = System.currentTimeMillis() > 0 ? ", world!" : null;
+
+ public static void main(String[] args) {
+ System.out.print(staticField);
+ System.out.println(new Main().instanceField);
+ }
+ }
+}