Add API for controlling empty member rules to default init conversion
Bug: b/356344563
Fixes: b/356074807
Change-Id: I356d07ebed5da56268a1235cc8e26bb5b38b41d6
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index 3bd0ac5..a5caffa 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -485,6 +485,19 @@
}
/**
+ * Used to disable that keep rules with no member rules are implicitly converted into rules that
+ * keep the default instance constructor.
+ *
+ * <p>This currently defaults to true in stable versions.
+ */
+ public Builder setEnableEmptyMemberRulesToDefaultInitRuleConversion(
+ boolean enableEmptyMemberRulesToDefaultInitRuleConversion) {
+ parserOptionsBuilder.setEnableEmptyMemberRulesToDefaultInitRuleConversion(
+ enableEmptyMemberRulesToDefaultInitRuleConversion);
+ return this;
+ }
+
+ /**
* Used to specify if the application is using isolated splits, i.e., if split APKs installed
* for this application are loaded into their own Context objects.
*
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
index 8cffa28..fc42207 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
@@ -846,8 +846,7 @@
.setStart(start);
parseRuleTypeAndModifiers(keepRuleBuilder);
parseClassSpec(keepRuleBuilder);
- if (configurationBuilder.isForceProguardCompatibility()
- || options.isForceEmptyMemberRulesToDefaultInitRuleConversionEnabled()) {
+ if (options.isEmptyMemberRulesToDefaultInitRuleConversionEnabled(configurationBuilder)) {
if (keepRuleBuilder.getMemberRules().isEmpty()
&& keepRuleBuilder.getKeepRuleType()
!= ProguardKeepRuleType.KEEP_CLASSES_WITH_MEMBERS) {
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParserOptions.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParserOptions.java
index acdd37c..157be68 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParserOptions.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParserOptions.java
@@ -8,30 +8,36 @@
public class ProguardConfigurationParserOptions {
+ private final boolean enableEmptyMemberRulesToDefaultInitRuleConversion;
private final boolean enableExperimentalCheckEnumUnboxed;
private final boolean enableExperimentalConvertCheckNotNull;
private final boolean enableExperimentalWhyAreYouNotInlining;
private final boolean enableTestingOptions;
- private final boolean forceEnableEmptyMemberRulesToDefaultInitRuleConversion;
ProguardConfigurationParserOptions(
+ boolean enableEmptyMemberRulesToDefaultInitRuleConversion,
boolean enableExperimentalCheckEnumUnboxed,
boolean enableExperimentalConvertCheckNotNull,
boolean enableExperimentalWhyAreYouNotInlining,
- boolean enableTestingOptions,
- boolean forceEnableEmptyMemberRulesToDefaultInitRuleConversion) {
+ boolean enableTestingOptions) {
this.enableExperimentalCheckEnumUnboxed = enableExperimentalCheckEnumUnboxed;
this.enableExperimentalConvertCheckNotNull = enableExperimentalConvertCheckNotNull;
this.enableExperimentalWhyAreYouNotInlining = enableExperimentalWhyAreYouNotInlining;
this.enableTestingOptions = enableTestingOptions;
- this.forceEnableEmptyMemberRulesToDefaultInitRuleConversion =
- forceEnableEmptyMemberRulesToDefaultInitRuleConversion;
+ this.enableEmptyMemberRulesToDefaultInitRuleConversion =
+ enableEmptyMemberRulesToDefaultInitRuleConversion;
}
public static Builder builder() {
return new Builder();
}
+ public boolean isEmptyMemberRulesToDefaultInitRuleConversionEnabled(
+ ProguardConfiguration.Builder configurationBuilder) {
+ return enableEmptyMemberRulesToDefaultInitRuleConversion
+ || configurationBuilder.isForceProguardCompatibility();
+ }
+
public boolean isExperimentalCheckEnumUnboxedEnabled() {
return enableExperimentalCheckEnumUnboxed;
}
@@ -44,23 +50,22 @@
return enableExperimentalWhyAreYouNotInlining;
}
- public boolean isForceEmptyMemberRulesToDefaultInitRuleConversionEnabled() {
- return forceEnableEmptyMemberRulesToDefaultInitRuleConversion;
- }
-
public boolean isTestingOptionsEnabled() {
return enableTestingOptions;
}
public static class Builder {
+ private boolean enableEmptyMemberRulesToDefaultInitRuleConversion;
private boolean enableExperimentalCheckEnumUnboxed;
private boolean enableExperimentalConvertCheckNotNull;
private boolean enableExperimentalWhyAreYouNotInlining;
private boolean enableTestingOptions;
- private boolean forceEnableEmptyMemberRulesToDefaultInitRuleConversion;
public Builder readEnvironment() {
+ enableEmptyMemberRulesToDefaultInitRuleConversion =
+ parseSystemPropertyOrDefault(
+ "com.android.tools.r8.enableEmptyMemberRulesToDefaultInitRuleConversion", false);
enableExperimentalCheckEnumUnboxed =
parseSystemPropertyOrDefault(
"com.android.tools.r8.experimental.enablecheckenumunboxed", false);
@@ -72,9 +77,13 @@
"com.android.tools.r8.experimental.enablewhyareyounotinlining", false);
enableTestingOptions =
parseSystemPropertyOrDefault("com.android.tools.r8.allowTestProguardOptions", false);
- forceEnableEmptyMemberRulesToDefaultInitRuleConversion =
- parseSystemPropertyOrDefault(
- "com.android.tools.r8.enableEmptyMemberRulesToDefaultInitRuleConversion", false);
+ return this;
+ }
+
+ public Builder setEnableEmptyMemberRulesToDefaultInitRuleConversion(
+ boolean enableEmptyMemberRulesToDefaultInitRuleConversion) {
+ this.enableEmptyMemberRulesToDefaultInitRuleConversion =
+ enableEmptyMemberRulesToDefaultInitRuleConversion;
return this;
}
@@ -103,11 +112,11 @@
public ProguardConfigurationParserOptions build() {
return new ProguardConfigurationParserOptions(
+ enableEmptyMemberRulesToDefaultInitRuleConversion,
enableExperimentalCheckEnumUnboxed,
enableExperimentalConvertCheckNotNull,
enableExperimentalWhyAreYouNotInlining,
- enableTestingOptions,
- forceEnableEmptyMemberRulesToDefaultInitRuleConversion);
+ enableTestingOptions);
}
}
}
diff --git a/src/test/java/com/android/tools/r8/shaking/EmptyMemberRulesToDefaultInitRuleConversionTest.java b/src/test/java/com/android/tools/r8/shaking/EmptyMemberRulesToDefaultInitRuleConversionTest.java
new file mode 100644
index 0000000..bdb0a85
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/EmptyMemberRulesToDefaultInitRuleConversionTest.java
@@ -0,0 +1,65 @@
+// Copyright (c) 2024, 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.shaking;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentIf;
+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 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 EmptyMemberRulesToDefaultInitRuleConversionTest extends TestBase {
+
+ @Parameter(0)
+ public boolean enableEmptyMemberRulesToDefaultInitRuleConversion;
+
+ @Parameter(1)
+ public TestParameters parameters;
+
+ @Parameters(name = "{1}, convert: {0}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ BooleanUtils.values(),
+ getTestParameters().withDefaultRuntimes().withMinimumApiLevel().build());
+ }
+
+ @Test
+ public void testCompat() throws Exception {
+ testForR8Compat(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepClassRules(Main.class)
+ .enableEmptyMemberRulesToDefaultInitRuleConversion(
+ enableEmptyMemberRulesToDefaultInitRuleConversion)
+ .setMinApi(parameters)
+ .compile()
+ .inspect(inspector -> assertThat(inspector.clazz(Main.class).init(), isPresent()));
+ }
+
+ @Test
+ public void testFull() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepClassRules(Main.class)
+ .enableEmptyMemberRulesToDefaultInitRuleConversion(
+ enableEmptyMemberRulesToDefaultInitRuleConversion)
+ .setMinApi(parameters)
+ .compile()
+ .inspect(
+ inspector ->
+ assertThat(
+ inspector.clazz(Main.class).init(),
+ isPresentIf(enableEmptyMemberRulesToDefaultInitRuleConversion)));
+ }
+
+ static class Main {}
+}
diff --git a/src/test/testbase/java/com/android/tools/r8/R8TestBuilder.java b/src/test/testbase/java/com/android/tools/r8/R8TestBuilder.java
index 4d7338d..3309783 100644
--- a/src/test/testbase/java/com/android/tools/r8/R8TestBuilder.java
+++ b/src/test/testbase/java/com/android/tools/r8/R8TestBuilder.java
@@ -76,6 +76,7 @@
private AllowedDiagnosticMessages allowedDiagnosticMessages = AllowedDiagnosticMessages.NONE;
private boolean allowUnusedProguardConfigurationRules = false;
+ private boolean enableEmptyMemberRulesToDefaultInitRuleConversion = false;
private boolean enableIsolatedSplits = false;
private boolean enableMissingLibraryApiModeling = true;
private boolean enableStartupLayoutOptimization = true;
@@ -139,6 +140,8 @@
ToolHelper.addSyntheticProguardRulesConsumerForTesting(
builder, rules -> box.syntheticProguardRules = rules);
libraryDesugaringTestConfiguration.configure(builder);
+ builder.setEnableEmptyMemberRulesToDefaultInitRuleConversion(
+ enableEmptyMemberRulesToDefaultInitRuleConversion);
builder.setEnableIsolatedSplits(enableIsolatedSplits);
builder.setEnableExperimentalMissingLibraryApiModeling(enableMissingLibraryApiModeling);
builder.setEnableStartupLayoutOptimization(enableStartupLayoutOptimization);
@@ -882,6 +885,13 @@
return self();
}
+ public T enableEmptyMemberRulesToDefaultInitRuleConversion(
+ boolean enableEmptyMemberRulesToDefaultInitRuleConversion) {
+ this.enableEmptyMemberRulesToDefaultInitRuleConversion =
+ enableEmptyMemberRulesToDefaultInitRuleConversion;
+ return self();
+ }
+
public T enableIsolatedSplits(boolean enableIsolatedSplits) {
this.enableIsolatedSplits = enableIsolatedSplits;
return self();