Do not publicize private members that are kept

Bug: 131130038, 155816423
Change-Id: Ica01d13e2c6905b2a6f6ad983b87d9c666d80cae
diff --git a/src/main/java/com/android/tools/r8/optimize/ClassAndMemberPublicizer.java b/src/main/java/com/android/tools/r8/optimize/ClassAndMemberPublicizer.java
index 8315925..5da8e5f 100644
--- a/src/main/java/com/android/tools/r8/optimize/ClassAndMemberPublicizer.java
+++ b/src/main/java/com/android/tools/r8/optimize/ClassAndMemberPublicizer.java
@@ -6,11 +6,13 @@
 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.DexApplication;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.graph.InnerClassAttribute;
@@ -82,43 +84,69 @@
   }
 
   private void publicizeType(DexType type) {
-    DexClass clazz = application.definitionFor(type);
-    if (clazz != null && clazz.isProgramClass()) {
-      clazz.accessFlags.promoteToPublic();
-
-      // Publicize fields.
-      clazz.forEachField(field -> field.accessFlags.promoteToPublic());
-
-      // Publicize methods.
-      Set<DexEncodedMethod> privateInstanceEncodedMethods = new LinkedHashSet<>();
-      clazz.forEachMethod(encodedMethod -> {
-        if (publicizeMethod(clazz, encodedMethod)) {
-          privateInstanceEncodedMethods.add(encodedMethod);
-        }
-      });
-      if (!privateInstanceEncodedMethods.isEmpty()) {
-        clazz.virtualizeMethods(privateInstanceEncodedMethods);
-      }
-
-      // 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()));
-      }
+    DexProgramClass clazz = asProgramClassOrNull(application.definitionFor(type));
+    if (clazz != null) {
+      publicizeClass(clazz);
     }
-
     subtypingInfo.forAllImmediateExtendsSubtypes(type, this::publicizeType);
   }
 
-  private boolean publicizeMethod(DexClass holder, DexEncodedMethod encodedMethod) {
-    MethodAccessFlags accessFlags = encodedMethod.accessFlags;
+  private void publicizeClass(DexProgramClass clazz) {
+    clazz.accessFlags.promoteToPublic();
+
+    // Publicize fields.
+    clazz.forEachField(
+        field -> {
+          if (field.isPublic()) {
+            return;
+          }
+          if (appView.appInfo().isPinned(field.field)) {
+            // TODO(b/131130038): Also do not publicize package-private and protected fields that
+            //  are kept.
+            if (field.isPrivate()) {
+              return;
+            }
+          }
+          field.accessFlags.promoteToPublic();
+        });
+
+    // Publicize methods.
+    Set<DexEncodedMethod> privateInstanceMethods = new LinkedHashSet<>();
+    clazz.forEachMethod(
+        method -> {
+          if (publicizeMethod(clazz, method)) {
+            privateInstanceMethods.add(method);
+          }
+        });
+    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 boolean publicizeMethod(DexProgramClass holder, DexEncodedMethod method) {
+    MethodAccessFlags accessFlags = method.accessFlags;
     if (accessFlags.isPublic()) {
       return false;
     }
-    if (!accessFlags.isPrivate() || appView.dexItemFactory().isConstructor(encodedMethod.method)) {
+    // If this method is mentioned in keep rules, do not transform (rule applications changed).
+    if (appView.appInfo().isPinned(method.method)) {
+      // TODO(b/131130038): Also do not publicize package-private and protected methods that are
+      //  kept.
+      if (method.isPrivate()) {
+        return false;
+      }
+    }
+
+    if (!accessFlags.isPrivate() || appView.dexItemFactory().isConstructor(method.method)) {
       // TODO(b/150589374): This should check for dispatch targets or just abandon in
       //  package-private.
       accessFlags.promoteToPublic();
@@ -126,10 +154,6 @@
     }
 
     if (!accessFlags.isStatic()) {
-      // If this method is mentioned in keep rules, do not transform (rule applications changed).
-      if (appView.appInfo().isPinned(encodedMethod.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.
@@ -138,20 +162,20 @@
         return false;
       }
 
-      boolean wasSeen = methodPoolCollection.markIfNotSeen(holder, encodedMethod.method);
+      boolean wasSeen = methodPoolCollection.markIfNotSeen(holder, method.method);
       if (wasSeen) {
         // We can't do anything further because even renaming is not allowed due to the keep rule.
-        if (appView.rootSet().mayNotBeMinified(encodedMethod.method, appView)) {
+        if (appView.rootSet().mayNotBeMinified(method.method, appView)) {
           return false;
         }
         // TODO(b/111118390): Renaming will enable more private instance methods to be publicized.
         return false;
       }
-      lenseBuilder.add(encodedMethod.method);
+      lenseBuilder.add(method.method);
       accessFlags.promoteToFinal();
       accessFlags.promoteToPublic();
       // The method just became public and is therefore not a library override.
-      encodedMethod.setLibraryMethodOverride(OptionalBool.FALSE);
+      method.setLibraryMethodOverride(OptionalBool.FALSE);
       return true;
     }
 
diff --git a/src/test/java/com/android/tools/r8/accessrelaxation/AccessRelaxationProguardCompatTest.java b/src/test/java/com/android/tools/r8/accessrelaxation/AccessRelaxationProguardCompatTest.java
index 7ea2765..3efb9b1 100644
--- a/src/test/java/com/android/tools/r8/accessrelaxation/AccessRelaxationProguardCompatTest.java
+++ b/src/test/java/com/android/tools/r8/accessrelaxation/AccessRelaxationProguardCompatTest.java
@@ -15,10 +15,7 @@
 import com.android.tools.r8.utils.codeinspector.FieldSubject;
 import org.junit.Test;
 
-/**
- * Tests that both R8 and Proguard may change the visibility of a field or method that is explicitly
- * kept.
- */
+/** Tests that Proguard may change the visibility of a field or method that is explicitly kept. */
 public class AccessRelaxationProguardCompatTest extends TestBase {
 
   private static Class<?> clazz = AccessRelaxationProguardCompatTestClass.class;
@@ -35,7 +32,7 @@
             "}")
         .allowAccessModification()
         .compile()
-        .inspect(AccessRelaxationProguardCompatTest::inspect);
+        .inspect(inspector -> inspect(inspector, true));
   }
 
   @Test
@@ -49,10 +46,10 @@
             "}")
         .allowAccessModification()
         .compile()
-        .inspect(AccessRelaxationProguardCompatTest::inspect);
+        .inspect(inspector -> inspect(inspector, false));
   }
 
-  private static void inspect(CodeInspector inspector) {
+  private static void inspect(CodeInspector inspector, boolean isR8) {
     ClassSubject classSubject = inspector.clazz(clazzWithGetter);
     assertThat(classSubject, isPresent());
 
@@ -60,7 +57,11 @@
     assertThat(fieldSubject, isPresent());
 
     // Although this field was explicitly kept, it is no longer private.
-    assertThat(fieldSubject, not(isPrivate()));
+    if (isR8) {
+      assertThat(fieldSubject, isPrivate());
+    } else {
+      assertThat(fieldSubject, not(isPrivate()));
+    }
   }
 }
 
diff --git a/src/test/java/com/android/tools/r8/accessrelaxation/PrivateKeptMembersPublicizerTest.java b/src/test/java/com/android/tools/r8/accessrelaxation/PrivateKeptMembersPublicizerTest.java
new file mode 100644
index 0000000..940479a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/accessrelaxation/PrivateKeptMembersPublicizerTest.java
@@ -0,0 +1,67 @@
+// Copyright (c) 2020, 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.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class PrivateKeptMembersPublicizerTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public PrivateKeptMembersPublicizerTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(PrivateKeptMembersPublicizerTest.class)
+        .addKeepClassAndMembersRules(TestClass.class)
+        .allowAccessModification()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(this::inspect)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("Hello world!");
+  }
+
+  private void inspect(CodeInspector inspector) {
+    ClassSubject classSubject = inspector.clazz(TestClass.class);
+    assertThat(classSubject, isPresent());
+    assertTrue(classSubject.uniqueFieldWithName("greeting").isPrivate());
+    assertTrue(classSubject.uniqueMethodWithName("greet").isPrivate());
+  }
+
+  static class TestClass {
+
+    private static String greeting = "Hello world!";
+
+    public static void main(String[] args) {
+      greet(greeting);
+    }
+
+    private static void greet(String message) {
+      System.out.println(message);
+    }
+  }
+}