Verify the absence of unused proto extensions in Proto2ShrinkingTest
Change-Id: I409ac751c3b106b107d08e475a3af698fbc8e105
diff --git a/src/main/java/com/android/tools/r8/ir/code/StaticPut.java b/src/main/java/com/android/tools/r8/ir/code/StaticPut.java
index 22192f2..55f4b12 100644
--- a/src/main/java/com/android/tools/r8/ir/code/StaticPut.java
+++ b/src/main/java/com/android/tools/r8/ir/code/StaticPut.java
@@ -112,6 +112,14 @@
DexEncodedField encodedField = appInfoWithLiveness.resolveField(getField());
assert encodedField != null : "NoSuchFieldError (resolution failure) should be caught.";
+
+ boolean isDeadProtoExtensionField =
+ appView.withGeneratedExtensionRegistryShrinker(
+ shrinker -> shrinker.isDeadProtoExtensionField(encodedField.field), false);
+ if (isDeadProtoExtensionField) {
+ return false;
+ }
+
return appInfoWithLiveness.isFieldRead(encodedField);
}
diff --git a/src/test/java/com/android/tools/r8/internal/proto/Proto2ShrinkingTest.java b/src/test/java/com/android/tools/r8/internal/proto/Proto2ShrinkingTest.java
index 9d8871f..8a7e6d6 100644
--- a/src/test/java/com/android/tools/r8/internal/proto/Proto2ShrinkingTest.java
+++ b/src/test/java/com/android/tools/r8/internal/proto/Proto2ShrinkingTest.java
@@ -4,8 +4,13 @@
package com.android.tools.r8.internal.proto;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.google.common.collect.ImmutableList;
import java.nio.file.Path;
@@ -17,6 +22,9 @@
@RunWith(Parameterized.class)
public class Proto2ShrinkingTest extends ProtoShrinkingTestBase {
+ private static final String EXT_C =
+ "com.android.tools.r8.proto2.Shrinking$PartiallyUsedWithExtension$ExtC";
+
private static List<Path> PROGRAM_FILES =
ImmutableList.of(PROTO2_EXAMPLES_JAR, PROTO2_PROTO_JAR, PROTOBUF_LITE_JAR);
@@ -41,10 +49,18 @@
@Test
public void test() throws Exception {
+ CodeInspector inputInspector = new CodeInspector(PROGRAM_FILES);
testForR8(parameters.getBackend())
.addProgramFiles(PROGRAM_FILES)
.addKeepMainRule("proto2.TestClass")
.addKeepRuleFiles(PROTOBUF_LITE_PROGUARD_RULES)
+ .addKeepRules(alwaysInlineNewSingularGeneratedExtensionRule())
+ // TODO(b/112437944): Attempt to prove that DEFAULT_INSTANCE is non-null, such that the
+ // following "assumenotnull" rule can be omitted.
+ .addKeepRules(
+ "-assumenosideeffects class " + EXT_C + " {",
+ " private static final " + EXT_C + " DEFAULT_INSTANCE return 1..42;",
+ "}")
.addOptionsModification(
options -> {
options.enableGeneratedMessageLiteShrinking = true;
@@ -56,6 +72,8 @@
.minification(enableMinification)
.setMinApi(parameters.getRuntime())
.compile()
+ .inspect(
+ outputInspector -> verifyUnusedExtensionsAreRemoved(inputInspector, outputInspector))
.run(parameters.getRuntime(), "proto2.TestClass")
.assertSuccessWithOutputLines(
"--- roundtrip ---",
@@ -86,6 +104,56 @@
"10");
}
+ private void verifyUnusedExtensionsAreRemoved(
+ CodeInspector inputInspector, CodeInspector outputInspector) {
+ // Verify that the registry was split across multiple methods in the input.
+ {
+ ClassSubject generatedExtensionRegistryLoader =
+ inputInspector.clazz("com.google.protobuf.proto2_registryGeneratedExtensionRegistryLite");
+ assertThat(generatedExtensionRegistryLoader, isPresent());
+ assertThat(
+ generatedExtensionRegistryLoader.uniqueMethodWithName("findLiteExtensionByNumber"),
+ isPresent());
+ assertThat(
+ generatedExtensionRegistryLoader.uniqueMethodWithName("findLiteExtensionByNumber1"),
+ isPresent());
+ assertThat(
+ generatedExtensionRegistryLoader.uniqueMethodWithName("findLiteExtensionByNumber2"),
+ isPresent());
+ }
+
+ // Verify that the registry methods are still present in the output.
+ // TODO(b/112437944): Should they be optimized into a single findLiteExtensionByNumber() method?
+ {
+ ClassSubject generatedExtensionRegistryLoader =
+ outputInspector.clazz(
+ "com.google.protobuf.proto2_registryGeneratedExtensionRegistryLite");
+ assertThat(generatedExtensionRegistryLoader, isPresent());
+ assertThat(
+ generatedExtensionRegistryLoader.uniqueMethodWithName("findLiteExtensionByNumber"),
+ isPresent());
+ assertThat(
+ generatedExtensionRegistryLoader.uniqueMethodWithName("findLiteExtensionByNumber1"),
+ isPresent());
+ assertThat(
+ generatedExtensionRegistryLoader.uniqueMethodWithName("findLiteExtensionByNumber2"),
+ isPresent());
+ }
+
+ // Verify that unused extensions have been removed with -allowaccessmodification.
+ if (allowAccessModification) {
+ List<String> unusedExtensionNames =
+ ImmutableList.of(
+ "com.android.tools.r8.proto2.Shrinking$HasNoUsedExtensions",
+ "com.android.tools.r8.proto2.Shrinking$PartiallyUsedWithExtension$ExtB",
+ "com.android.tools.r8.proto2.Shrinking$PartiallyUsedWithExtension$ExtC");
+ for (String unusedExtensionName : unusedExtensionNames) {
+ assertThat(inputInspector.clazz(unusedExtensionName), isPresent());
+ assertThat(outputInspector.clazz(unusedExtensionName), not(isPresent()));
+ }
+ }
+ }
+
@Test
public void testNoRewriting() throws Exception {
testForR8(parameters.getBackend())
diff --git a/src/test/java/com/android/tools/r8/internal/proto/ProtoShrinkingTestBase.java b/src/test/java/com/android/tools/r8/internal/proto/ProtoShrinkingTestBase.java
index ec83adc..9a88fe1 100644
--- a/src/test/java/com/android/tools/r8/internal/proto/ProtoShrinkingTestBase.java
+++ b/src/test/java/com/android/tools/r8/internal/proto/ProtoShrinkingTestBase.java
@@ -69,6 +69,14 @@
}
}
+ static String alwaysInlineNewSingularGeneratedExtensionRule() {
+ return StringUtils.lines(
+ "-alwaysinline class com.google.protobuf.GeneratedMessageLite {",
+ " com.google.protobuf.GeneratedMessageLite$GeneratedExtension"
+ + " newSingularGeneratedExtension(...);",
+ "}");
+ }
+
static String keepAllProtosRule() {
return StringUtils.lines(
"-keep class * extends com.google.protobuf.GeneratedMessageLite { *; }");