Allow using non-public static fields for triggering clinit

Bug: 151281085
Change-Id: If0dac5965cb8c4aeb7cff6bda5f431477476b92a
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedField.java b/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
index a6c8186..8a21e7b 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
@@ -138,10 +138,18 @@
     return accessFlags.isStatic();
   }
 
+  public boolean isPackagePrivate() {
+    return accessFlags.isPackagePrivate();
+  }
+
   public boolean isPrivate() {
     return accessFlags.isPrivate();
   }
 
+  public boolean isProtected() {
+    return accessFlags.isProtected();
+  }
+
   public boolean isPublic() {
     return accessFlags.isPublic();
   }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/AssumeDynamicTypeRemover.java b/src/main/java/com/android/tools/r8/ir/optimize/AssumeDynamicTypeRemover.java
index 0ebe028..6bb9b03 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/AssumeDynamicTypeRemover.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/AssumeDynamicTypeRemover.java
@@ -55,9 +55,6 @@
   public void markUsersForRemoval(Value value) {
     for (Instruction user : value.aliasedUsers()) {
       if (user.isAssumeDynamicType()) {
-        assert value.numberOfAllUsers() == 1
-            : "Expected value flowing into Assume<DynamicTypeAssumption> instruction to have a "
-                + "unique user.";
         markForRemoval(user.asAssumeDynamicType());
       }
     }
diff --git a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
index ed4c3e3..7df2fbc 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -44,6 +44,7 @@
 import com.android.tools.r8.utils.ListUtils;
 import com.android.tools.r8.utils.PredicateSet;
 import com.android.tools.r8.utils.SetUtils;
+import com.android.tools.r8.utils.Visibility;
 import com.android.tools.r8.utils.WorkList;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.ImmutableSortedSet;
@@ -164,8 +165,11 @@
   public final Set<DexType> neverMerge;
   /** Set of const-class references. */
   public final Set<DexType> constClassReferences;
-  /** Set of init-class references. */
-  public final Set<DexType> initClassReferences;
+  /**
+   * A map from seen init-class references to the minimum required visibility of the corresponding
+   * static field.
+   */
+  public final Map<DexType, Visibility> initClassReferences;
   /**
    * All methods and fields whose value *must* never be propagated due to a configuration directive.
    * (testing only).
@@ -231,7 +235,7 @@
       EnumValueInfoMapCollection enumValueInfoMaps,
       Set<DexType> instantiatedLambdas,
       Set<DexType> constClassReferences,
-      Set<DexType> initClassReferences) {
+      Map<DexType, Visibility> initClassReferences) {
     super(application);
     this.missingTypes = missingTypes;
     this.liveTypes = liveTypes;
@@ -316,7 +320,7 @@
       EnumValueInfoMapCollection enumValueInfoMaps,
       Set<DexType> instantiatedLambdas,
       Set<DexType> constClassReferences,
-      Set<DexType> initClassReferences) {
+      Map<DexType, Visibility> initClassReferences) {
     super(appInfoWithSubtyping);
     this.missingTypes = missingTypes;
     this.liveTypes = liveTypes;
@@ -1077,7 +1081,7 @@
         enumValueInfoMaps.rewrittenWithLens(lens),
         rewriteItems(instantiatedLambdas, lens::lookupType),
         rewriteItems(constClassReferences, lens::lookupType),
-        rewriteItems(initClassReferences, lens::lookupType));
+        rewriteReferenceKeys(initClassReferences, lens::lookupType));
   }
 
   /**
diff --git a/src/main/java/com/android/tools/r8/shaking/ClassInitFieldSynthesizer.java b/src/main/java/com/android/tools/r8/shaking/ClassInitFieldSynthesizer.java
index a6231aa..60bb9d7 100644
--- a/src/main/java/com/android/tools/r8/shaking/ClassInitFieldSynthesizer.java
+++ b/src/main/java/com/android/tools/r8/shaking/ClassInitFieldSynthesizer.java
@@ -7,6 +7,7 @@
 import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
 
 import com.android.tools.r8.dex.Constants;
+import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexAnnotationSet;
 import com.android.tools.r8.graph.DexEncodedField;
@@ -16,6 +17,7 @@
 import com.android.tools.r8.graph.FieldAccessFlags;
 import com.android.tools.r8.graph.InitClassLens;
 import com.android.tools.r8.utils.ThreadUtils;
+import com.android.tools.r8.utils.Visibility;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
 
@@ -36,7 +38,7 @@
     appView.setInitClassLens(lensBuilder.build());
   }
 
-  private void synthesizeClassInitField(DexType type) {
+  private void synthesizeClassInitField(DexType type, Visibility minimumRequiredVisibility) {
     DexProgramClass clazz = asProgramClassOrNull(appView.definitionFor(type));
     if (clazz == null) {
       assert false;
@@ -46,12 +48,12 @@
     DexEncodedField encodedClinitField = null;
     for (DexEncodedField staticField : clazz.staticFields()) {
       // We need to field to be accessible from the contexts in which it is accessed.
-      if (!staticField.isPublic()) {
+      if (!isMinimumRequiredVisibility(staticField, minimumRequiredVisibility)) {
         continue;
       }
       // When compiling for dex, we can't use wide fields since we've only allocated a single
       // register for the out-value of each ClassInit instruction
-      if (appView.options().isGeneratingDex() && staticField.field.type.isWideType()) {
+      if (staticField.field.type.isWideType()) {
         continue;
       }
       encodedClinitField = staticField;
@@ -74,4 +76,21 @@
     }
     lensBuilder.map(type, encodedClinitField.field);
   }
+
+  private boolean isMinimumRequiredVisibility(
+      DexEncodedField field, Visibility minimumRequiredVisibility) {
+    if (field.isPublic()) {
+      return true;
+    }
+    switch (minimumRequiredVisibility) {
+      case PROTECTED:
+        return field.isProtected();
+      case PACKAGE_PRIVATE:
+        return field.isPackagePrivate() || field.isProtected();
+      case PUBLIC:
+        return false;
+      default:
+        throw new Unreachable();
+    }
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
index e6040a0..2bc230c 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -97,6 +97,7 @@
 import com.android.tools.r8.utils.SetUtils;
 import com.android.tools.r8.utils.StringDiagnostic;
 import com.android.tools.r8.utils.Timing;
+import com.android.tools.r8.utils.Visibility;
 import com.android.tools.r8.utils.WorkList;
 import com.google.common.base.Equivalence.Wrapper;
 import com.google.common.collect.ImmutableSet;
@@ -299,8 +300,11 @@
    */
   private final Set<DexType> constClassReferences = Sets.newIdentityHashSet();
 
-  /** A set of seen init-class references. */
-  private final Set<DexType> initClassReferences = Sets.newIdentityHashSet();
+  /**
+   * A map from seen init-class references to the minimum required visibility of the corresponding
+   * static field.
+   */
+  private final Map<DexType, Visibility> initClassReferences = new IdentityHashMap<>();
 
   /**
    * A map from annotation classes to annotations that need to be processed should the classes ever
@@ -736,19 +740,60 @@
   boolean traceInitClass(DexType type, ProgramMethod currentMethod) {
     assert type.isClassType();
 
-    if (!initClassReferences.add(type)) {
+    Visibility oldMinimumRequiredVisibility = initClassReferences.get(type);
+    if (oldMinimumRequiredVisibility == null) {
+      DexProgramClass clazz = getProgramClassOrNull(type);
+      if (clazz == null) {
+        assert false;
+        return false;
+      }
+
+      initClassReferences.put(
+          type, computeMinimumRequiredVisibilityForInitClassField(type, currentMethod.getHolder()));
+
+      markTypeAsLive(type, classReferencedFromReporter(currentMethod.getMethod()));
+      markDirectAndIndirectClassInitializersAsLive(clazz);
+      return true;
+    }
+
+    if (oldMinimumRequiredVisibility.isPublic()) {
       return false;
     }
 
-    DexProgramClass clazz = getProgramClassOrNull(type);
-    if (clazz == null) {
-      assert false;
+    Visibility minimumRequiredVisibilityForCurrentMethod =
+        computeMinimumRequiredVisibilityForInitClassField(type, currentMethod.getHolder());
+
+    // There should never be a need to have an InitClass instruction for the enclosing class.
+    assert !minimumRequiredVisibilityForCurrentMethod.isPrivate();
+
+    if (minimumRequiredVisibilityForCurrentMethod.isPublic()) {
+      initClassReferences.put(type, minimumRequiredVisibilityForCurrentMethod);
+      return true;
+    }
+
+    if (oldMinimumRequiredVisibility.isProtected()) {
       return false;
     }
 
-    markTypeAsLive(type, classReferencedFromReporter(currentMethod.getMethod()));
-    markDirectAndIndirectClassInitializersAsLive(clazz);
-    return true;
+    if (minimumRequiredVisibilityForCurrentMethod.isProtected()) {
+      initClassReferences.put(type, minimumRequiredVisibilityForCurrentMethod);
+      return true;
+    }
+
+    assert oldMinimumRequiredVisibility.isPackagePrivate();
+    assert minimumRequiredVisibilityForCurrentMethod.isPackagePrivate();
+    return false;
+  }
+
+  private Visibility computeMinimumRequiredVisibilityForInitClassField(
+      DexType clazz, DexProgramClass context) {
+    if (clazz.isSamePackage(context.type)) {
+      return Visibility.PACKAGE_PRIVATE;
+    }
+    if (appInfo.isStrictSubtypeOf(context.type, clazz)) {
+      return Visibility.PROTECTED;
+    }
+    return Visibility.PUBLIC;
   }
 
   void traceMethodHandle(
diff --git a/src/main/java/com/android/tools/r8/utils/ThreadUtils.java b/src/main/java/com/android/tools/r8/utils/ThreadUtils.java
index 9bb8877..84aa0d0 100644
--- a/src/main/java/com/android/tools/r8/utils/ThreadUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/ThreadUtils.java
@@ -7,6 +7,7 @@
 import java.util.Collection;
 import java.util.Iterator;
 import java.util.List;
+import java.util.Map;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
@@ -39,6 +40,18 @@
         executorService);
   }
 
+  public static <T, U, E extends Exception> void processItems(
+      Map<T, U> items, ThrowingBiConsumer<T, U, E> consumer, ExecutorService executorService)
+      throws ExecutionException {
+    processItemsWithResults(
+        items.entrySet(),
+        arg -> {
+          consumer.accept(arg.getKey(), arg.getValue());
+          return null;
+        },
+        executorService);
+  }
+
   public static void awaitFutures(Iterable<? extends Future<?>> futures)
       throws ExecutionException {
     Iterator<? extends Future<?>> futureIterator = futures.iterator();
diff --git a/src/main/java/com/android/tools/r8/utils/Visibility.java b/src/main/java/com/android/tools/r8/utils/Visibility.java
new file mode 100644
index 0000000..32ec7ba
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/Visibility.java
@@ -0,0 +1,50 @@
+// 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.utils;
+
+import com.android.tools.r8.errors.Unreachable;
+
+public enum Visibility {
+  PUBLIC,
+  PROTECTED,
+  PRIVATE,
+  PACKAGE_PRIVATE;
+
+  public boolean isPackagePrivate() {
+    return this == PACKAGE_PRIVATE;
+  }
+
+  public boolean isPrivate() {
+    return this == PRIVATE;
+  }
+
+  public boolean isProtected() {
+    return this == PROTECTED;
+  }
+
+  public boolean isPublic() {
+    return this == PUBLIC;
+  }
+
+  @Override
+  public String toString() {
+    switch (this) {
+      case PUBLIC:
+        return "public";
+
+      case PROTECTED:
+        return "protected";
+
+      case PRIVATE:
+        return "private";
+
+      case PACKAGE_PRIVATE:
+        return "package-private";
+
+      default:
+        throw new Unreachable("Unexpected visibility");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/internal/YouTubeV1419TreeShakeJarVerificationTest.java b/src/test/java/com/android/tools/r8/internal/YouTubeV1419TreeShakeJarVerificationTest.java
index dd47f7f..97b4db0 100644
--- a/src/test/java/com/android/tools/r8/internal/YouTubeV1419TreeShakeJarVerificationTest.java
+++ b/src/test/java/com/android/tools/r8/internal/YouTubeV1419TreeShakeJarVerificationTest.java
@@ -14,6 +14,7 @@
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.analysis.ProtoApplicationStats;
 import org.junit.Test;
@@ -30,7 +31,7 @@
 
   @Parameters(name = "{0}")
   public static TestParametersCollection data() {
-    return getTestParameters().withDexRuntimes().build();
+    return getTestParameters().withNoneRuntime().build();
   }
 
   public YouTubeV1419TreeShakeJarVerificationTest(TestParameters parameters) {
@@ -44,11 +45,17 @@
     assumeTrue(isLocalDevelopment());
     assumeTrue(shouldRunSlowTests());
 
+    LibrarySanitizer librarySanitizer =
+        new LibrarySanitizer(temp).addProguardConfigurationFiles(getKeepRuleFiles()).sanitize();
+
     R8TestCompileResult compileResult =
-        testForR8(parameters.getBackend())
-            .addKeepRuleFiles(getKeepRuleFiles())
+        testForR8(Backend.DEX)
+            .addKeepRuleFiles(librarySanitizer.getSanitizedProguardConfiguration())
+            .addLibraryFiles(librarySanitizer.getSanitizedLibrary())
+            .addMainDexRuleFiles(getMainDexRuleFiles())
             .allowDiagnosticMessages()
             .allowUnusedProguardConfigurationRules()
+            .setMinApi(AndroidApiLevel.H_MR2)
             .compile()
             .assertAllInfoMessagesMatch(
                 containsString("Proguard configuration rule does not match anything"))
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/sideeffect/SingletonClassInitializerPatternCannotBePostponedTest.java b/src/test/java/com/android/tools/r8/ir/analysis/sideeffect/SingletonClassInitializerPatternCannotBePostponedTest.java
index b3a0265..7bc227a 100644
--- a/src/test/java/com/android/tools/r8/ir/analysis/sideeffect/SingletonClassInitializerPatternCannotBePostponedTest.java
+++ b/src/test/java/com/android/tools/r8/ir/analysis/sideeffect/SingletonClassInitializerPatternCannotBePostponedTest.java
@@ -48,8 +48,8 @@
     ClassSubject classSubject = inspector.clazz(A.class);
     assertThat(classSubject, isPresent());
 
-    // A static field A.$r8$clinit has been synthesized to allow inlining of A.inlineable().
-    assertThat(classSubject.uniqueFieldWithName("$r8$clinit"), isPresent());
+    // The field A.INSTANCE has been accessed to allow inlining of A.inlineable().
+    assertThat(classSubject.uniqueFieldWithName("INSTANCE"), isPresent());
     assertThat(classSubject.uniqueMethodWithName("inlineable"), not(isPresent()));
   }
 
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java b/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java
index a360336..98f6b1e 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java
@@ -12,6 +12,7 @@
 import com.android.tools.r8.retrace.RetraceMethodResult;
 import com.android.tools.r8.retrace.RetraceMethodResult.Element;
 import com.android.tools.r8.utils.Box;
+import com.android.tools.r8.utils.Visibility;
 import com.google.common.collect.ImmutableList;
 import java.util.List;
 import java.util.stream.Collectors;
@@ -21,33 +22,6 @@
 
 public class Matchers {
 
-  private enum Visibility {
-    PUBLIC,
-    PROTECTED,
-    PRIVATE,
-    PACKAGE_PRIVATE;
-
-    @Override
-    public String toString() {
-      switch (this) {
-        case PUBLIC:
-          return "public";
-
-        case PROTECTED:
-          return "protected";
-
-        case PRIVATE:
-          return "private";
-
-        case PACKAGE_PRIVATE:
-          return "package-private";
-
-        default:
-          throw new Unreachable("Unexpected visibility");
-      }
-    }
-  }
-
   private static String type(Subject subject) {
     String type = "<unknown subject type>";
     if (subject instanceof ClassSubject) {