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 { *; }");