Merge "Enhance visibility check when rebinding fields."
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
index a5ac28c..d5ac39d 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
@@ -202,7 +202,7 @@
         }
         return NEVER;
       } else {
-      /* package-private */
+        /* package-private */
         return targetHolder.isSamePackage(contextHolder) ? PACKAGE : NEVER;
       }
     }
diff --git a/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java b/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
index de171a3..3372a20 100644
--- a/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
+++ b/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
@@ -11,8 +11,9 @@
 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.ir.optimize.Inliner.Constraint;
 import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
-import com.google.common.collect.Sets;
+import java.util.Map;
 import java.util.Set;
 import java.util.function.BiFunction;
 import java.util.function.Function;
@@ -124,7 +125,7 @@
       // Rebind to the lowest library class or program class.
       if (target != null && target.method != method) {
         DexClass targetClass = appInfo.definitionFor(target.method.holder);
-        // If the targetclass is not public but the targeted method is, we might run into
+        // If the target class is not public but the targeted method is, we might run into
         // visibility problems when rebinding.
         if (!targetClass.accessFlags.isPublic() && target.accessFlags.isPublic()) {
           // If the original class is public and this method is public, it might have been called
@@ -178,27 +179,37 @@
     return null;
   }
 
-  private void computeFieldRebinding(Set<DexField> fields,
+  private void computeFieldRebinding(Map<DexField, Set<DexEncodedMethod>> fields,
       BiFunction<DexType, DexField, DexEncodedField> lookup,
       BiFunction<DexClass, DexField, DexEncodedField> lookupTargetOnClass) {
-    for (DexField field : fields) {
+    for (Map.Entry<DexField, Set<DexEncodedMethod>> entry : fields.entrySet()) {
+      DexField field = entry.getKey();
       field = lense.lookupField(field, null);
       DexEncodedField target = lookup.apply(field.getHolder(), field);
       // Rebind to the lowest library class or program class. Do not rebind accesses to fields that
-      // are not public, as this might lead to access violation errors.
-      if (target != null && target.field != field && isVisibleFromOtherClasses(target)) {
+      // are not visible from the access context.
+      Set<DexEncodedMethod> contexts = entry.getValue();
+      if (target != null && target.field != field && contexts.stream().allMatch(context ->
+          isVisibleFromOriginalContext(context.method.getHolder(), target))) {
         builder.map(field, validTargetFor(target.field, field, lookupTargetOnClass));
       }
     }
   }
 
-  private boolean isVisibleFromOtherClasses(DexEncodedField field) {
-    // If the field is not public, the visibility on the class can not be a further constraint.
-    if (!field.accessFlags.isPublic()) {
-      return true;
+  private boolean isVisibleFromOriginalContext(DexType context, DexEncodedField field) {
+    DexType holderType = field.field.getHolder();
+    DexClass holder = appInfo.definitionFor(holderType);
+    if (holder == null) {
+      return false;
     }
-    // If the field is public, then a non-public holder class will further constrain visibility.
-    return appInfo.definitionFor(field.field.getHolder()).accessFlags.isPublic();
+    Constraint classVisibility =
+        Constraint.deriveConstraint(context, holderType, holder.accessFlags, appInfo);
+    if (classVisibility == Constraint.NEVER) {
+      return false;
+    }
+    Constraint fieldVisibility =
+        Constraint.deriveConstraint(context, holderType, field.accessFlags, appInfo);
+    return fieldVisibility != Constraint.NEVER;
   }
 
   public GraphLense run() {
@@ -213,10 +224,15 @@
     // Likewise static invokes.
     computeMethodRebinding(appInfo.staticInvokes, this::anyLookup);
 
-    computeFieldRebinding(Sets.union(appInfo.staticFieldReads, appInfo.staticFieldWrites),
+    computeFieldRebinding(appInfo.staticFieldReads,
         appInfo::resolveFieldOn, DexClass::lookupField);
-    computeFieldRebinding(Sets.union(appInfo.instanceFieldReads, appInfo.instanceFieldWrites),
+    computeFieldRebinding(appInfo.staticFieldWrites,
         appInfo::resolveFieldOn, DexClass::lookupField);
+    computeFieldRebinding(appInfo.instanceFieldReads,
+        appInfo::resolveFieldOn, DexClass::lookupField);
+    computeFieldRebinding(appInfo.instanceFieldWrites,
+        appInfo::resolveFieldOn, DexClass::lookupField);
+
     return builder.build(appInfo.dexItemFactory, lense);
   }
 }
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 a726391..2774709 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -99,10 +99,14 @@
       Maps.newIdentityHashMap();
   private final Map<DexType, Set<DexMethod>> directInvokes = Maps.newIdentityHashMap();
   private final Map<DexType, Set<DexMethod>> staticInvokes = Maps.newIdentityHashMap();
-  private final Map<DexType, Set<DexField>> instanceFieldsWritten = Maps.newIdentityHashMap();
-  private final Map<DexType, Set<DexField>> instanceFieldsRead = Maps.newIdentityHashMap();
-  private final Map<DexType, Set<DexField>> staticFieldsRead = Maps.newIdentityHashMap();
-  private final Map<DexType, Set<DexField>> staticFieldsWritten = Maps.newIdentityHashMap();
+  private final Map<DexType, Set<TargetWithContext<DexField>>> instanceFieldsWritten =
+      Maps.newIdentityHashMap();
+  private final Map<DexType, Set<TargetWithContext<DexField>>> instanceFieldsRead =
+      Maps.newIdentityHashMap();
+  private final Map<DexType, Set<TargetWithContext<DexField>>> staticFieldsRead =
+      Maps.newIdentityHashMap();
+  private final Map<DexType, Set<TargetWithContext<DexField>>> staticFieldsWritten =
+      Maps.newIdentityHashMap();
 
   private final ProtoLiteExtension protoLiteExtension;
   private final Set<DexField> protoLiteFields = Sets.newIdentityHashSet();
@@ -361,7 +365,7 @@
 
     @Override
     public boolean registerInstanceFieldWrite(DexField field) {
-      if (!registerItemWithTarget(instanceFieldsWritten, field)) {
+      if (!registerItemWithTargetAndContext(instanceFieldsWritten, field, currentMethod)) {
         return false;
       }
       if (Log.ENABLED) {
@@ -374,7 +378,7 @@
 
     @Override
     public boolean registerInstanceFieldRead(DexField field) {
-      if (!registerItemWithTarget(instanceFieldsRead, field)) {
+      if (!registerItemWithTargetAndContext(instanceFieldsRead, field, currentMethod)) {
         return false;
       }
       if (Log.ENABLED) {
@@ -392,7 +396,7 @@
 
     @Override
     public boolean registerStaticFieldRead(DexField field) {
-      if (!registerItemWithTarget(staticFieldsRead, field)) {
+      if (!registerItemWithTargetAndContext(staticFieldsRead, field, currentMethod)) {
         return false;
       }
       if (Log.ENABLED) {
@@ -404,7 +408,7 @@
 
     @Override
     public boolean registerStaticFieldWrite(DexField field) {
-      if (!registerItemWithTarget(staticFieldsWritten, field)) {
+      if (!registerItemWithTargetAndContext(staticFieldsWritten, field, currentMethod)) {
         return false;
       }
       if (Log.ENABLED) {
@@ -1208,34 +1212,41 @@
     }
   }
 
-  private Set<DexField> collectFields(Map<DexType, Set<DexField>> map) {
-    return map.values().stream().flatMap(Collection::stream)
-        .collect(Collectors.toCollection(Sets::newIdentityHashSet));
+  private Map<DexField, Set<DexEncodedMethod>> collectFields(
+      Map<DexType, Set<TargetWithContext<DexField>>> map) {
+    Map<DexField, Set<DexEncodedMethod>> result = new IdentityHashMap<>();
+    for (Map.Entry<DexType, Set<TargetWithContext<DexField>>> entry : map.entrySet()) {
+      for (TargetWithContext<DexField> fieldWithContext : entry.getValue()) {
+        DexField field = fieldWithContext.getTarget();
+        DexEncodedMethod context = fieldWithContext.getContext();
+        result.computeIfAbsent(field, k -> Sets.newIdentityHashSet())
+            .add(context);
+      }
+    }
+    return result;
   }
 
-  SortedSet<DexField> collectInstanceFieldsRead() {
-    return ImmutableSortedSet.copyOf(
-        PresortedComparable<DexField>::slowCompareTo, collectFields(instanceFieldsRead));
+  Map<DexField, Set<DexEncodedMethod>> collectInstanceFieldsRead() {
+    return Collections.unmodifiableMap(collectFields(instanceFieldsRead));
   }
 
-  SortedSet<DexField> collectInstanceFieldsWritten() {
-    return ImmutableSortedSet.copyOf(
-        PresortedComparable<DexField>::slowCompareTo, collectFields(instanceFieldsWritten));
+  Map<DexField, Set<DexEncodedMethod>> collectInstanceFieldsWritten() {
+    return Collections.unmodifiableMap(collectFields(instanceFieldsWritten));
   }
 
-  SortedSet<DexField> collectStaticFieldsRead() {
-    return ImmutableSortedSet.copyOf(
-        PresortedComparable<DexField>::slowCompareTo, collectFields(staticFieldsRead));
+  Map<DexField, Set<DexEncodedMethod>> collectStaticFieldsRead() {
+    return Collections.unmodifiableMap(collectFields(staticFieldsRead));
   }
 
-  SortedSet<DexField> collectStaticFieldsWritten() {
-    return ImmutableSortedSet.copyOf(
-        PresortedComparable<DexField>::slowCompareTo, collectFields(staticFieldsWritten));
+  Map<DexField, Set<DexEncodedMethod>> collectStaticFieldsWritten() {
+    return Collections.unmodifiableMap(collectFields(staticFieldsWritten));
   }
 
-  private Set<DexField> collectReachedFields(Map<DexType, Set<DexField>> map,
-      Function<DexField, DexField> lookup) {
-    return map.values().stream().flatMap(set -> set.stream().map(lookup).filter(Objects::nonNull))
+  private Set<DexField> collectReachedFields(
+      Set<DexField> set, Function<DexField, DexField> lookup) {
+    return set.stream()
+        .map(lookup)
+        .filter(Objects::nonNull)
         .collect(Collectors.toCollection(Sets::newIdentityHashSet));
   }
 
@@ -1249,16 +1260,11 @@
     return target == null ? null : target.field;
   }
 
-  SortedSet<DexField> collectFieldsRead() {
+  SortedSet<DexField> mergeFieldAccesses(Set<DexField> instanceFields, Set<DexField> staticFields) {
     return ImmutableSortedSet.copyOf(PresortedComparable<DexField>::slowCompareTo,
-        Sets.union(collectReachedFields(instanceFieldsRead, this::tryLookupInstanceField),
-            collectReachedFields(staticFieldsRead, this::tryLookupStaticField)));
-  }
-
-  SortedSet<DexField> collectFieldsWritten() {
-    return ImmutableSortedSet.copyOf(PresortedComparable<DexField>::slowCompareTo,
-        Sets.union(collectReachedFields(instanceFieldsWritten, this::tryLookupInstanceField),
-            collectReachedFields(staticFieldsWritten, this::tryLookupStaticField)));
+        Sets.union(
+            collectReachedFields(instanceFields, this::tryLookupInstanceField),
+            collectReachedFields(staticFields, this::tryLookupStaticField)));
   }
 
   private void markClassAsInstantiatedWithCompatRule(DexClass clazz) {
@@ -1429,21 +1435,21 @@
      */
     public final SortedSet<DexField> fieldsWritten;
     /**
-     * Set of all field ids used in instance field reads.
+     * Set of all field ids used in instance field reads, along with access context.
      */
-    public final SortedSet<DexField> instanceFieldReads;
+    public final Map<DexField, Set<DexEncodedMethod>> instanceFieldReads;
     /**
-     * Set of all field ids used in instance field writes.
+     * Set of all field ids used in instance field writes, along with access context.
      */
-    public final SortedSet<DexField> instanceFieldWrites;
+    public final Map<DexField, Set<DexEncodedMethod>> instanceFieldWrites;
     /**
-     * Set of all field ids used in static static field reads.
+     * Set of all field ids used in static static field reads, along with access context.
      */
-    public final SortedSet<DexField> staticFieldReads;
+    public final Map<DexField, Set<DexEncodedMethod>> staticFieldReads;
     /**
-     * Set of all field ids used in static field writes.
+     * Set of all field ids used in static field writes, along with access context.
      */
-    public final SortedSet<DexField> staticFieldWrites;
+    public final Map<DexField, Set<DexEncodedMethod>> staticFieldWrites;
     /**
      * Set of all methods referenced in virtual invokes;
      */
@@ -1515,8 +1521,10 @@
       this.instanceFieldWrites = enqueuer.collectInstanceFieldsWritten();
       this.staticFieldReads = enqueuer.collectStaticFieldsRead();
       this.staticFieldWrites = enqueuer.collectStaticFieldsWritten();
-      this.fieldsRead = enqueuer.collectFieldsRead();
-      this.fieldsWritten = enqueuer.collectFieldsWritten();
+      this.fieldsRead = enqueuer.mergeFieldAccesses(
+          instanceFieldReads.keySet(), staticFieldReads.keySet());
+      this.fieldsWritten = enqueuer.mergeFieldAccesses(
+          instanceFieldWrites.keySet(), staticFieldWrites.keySet());
       this.pinnedItems = rewritePinnedItemsToDescriptors(enqueuer.pinnedItems);
       this.virtualInvokes = joinInvokedMethods(enqueuer.virtualInvokes);
       this.interfaceInvokes = joinInvokedMethods(enqueuer.interfaceInvokes);
@@ -1532,8 +1540,8 @@
       this.prunedTypes = Collections.emptySet();
       this.switchMaps = Collections.emptyMap();
       this.ordinalsMaps = Collections.emptyMap();
-      assert Sets.intersection(instanceFieldReads, staticFieldReads).size() == 0;
-      assert Sets.intersection(instanceFieldWrites, staticFieldWrites).size() == 0;
+      assert Sets.intersection(instanceFieldReads.keySet(), staticFieldReads.keySet()).isEmpty();
+      assert Sets.intersection(instanceFieldWrites.keySet(), staticFieldWrites.keySet()).isEmpty();
     }
 
     private AppInfoWithLiveness(AppInfoWithLiveness previous, DexApplication application,
@@ -1566,8 +1574,8 @@
       this.prunedTypes = mergeSets(previous.prunedTypes, removedClasses);
       this.switchMaps = previous.switchMaps;
       this.ordinalsMaps = previous.ordinalsMaps;
-      assert Sets.intersection(instanceFieldReads, staticFieldReads).size() == 0;
-      assert Sets.intersection(instanceFieldWrites, staticFieldWrites).size() == 0;
+      assert Sets.intersection(instanceFieldReads.keySet(), staticFieldReads.keySet()).isEmpty();
+      assert Sets.intersection(instanceFieldWrites.keySet(), staticFieldWrites.keySet()).isEmpty();
     }
 
     private AppInfoWithLiveness(AppInfoWithLiveness previous,
@@ -1579,10 +1587,14 @@
       this.targetedMethods = rewriteItems(previous.targetedMethods, lense::lookupMethod);
       this.liveMethods = rewriteItems(previous.liveMethods, lense::lookupMethod);
       this.liveFields = rewriteItems(previous.liveFields, lense::lookupField);
-      this.instanceFieldReads = rewriteItems(previous.instanceFieldReads, lense::lookupField);
-      this.instanceFieldWrites = rewriteItems(previous.instanceFieldWrites, lense::lookupField);
-      this.staticFieldReads = rewriteItems(previous.staticFieldReads, lense::lookupField);
-      this.staticFieldWrites = rewriteItems(previous.staticFieldWrites, lense::lookupField);
+      this.instanceFieldReads =
+          rewriteKeysWhileMergingValues(previous.instanceFieldReads, lense::lookupField);
+      this.instanceFieldWrites =
+          rewriteKeysWhileMergingValues(previous.instanceFieldWrites, lense::lookupField);
+      this.staticFieldReads =
+          rewriteKeysWhileMergingValues(previous.staticFieldReads, lense::lookupField);
+      this.staticFieldWrites =
+          rewriteKeysWhileMergingValues(previous.staticFieldWrites, lense::lookupField);
       this.fieldsRead = rewriteItems(previous.fieldsRead, lense::lookupField);
       this.fieldsWritten = rewriteItems(previous.fieldsWritten, lense::lookupField);
       this.pinnedItems = rewriteMixedItems(previous.pinnedItems, lense);
@@ -1608,8 +1620,8 @@
       this.ordinalsMaps = rewriteKeys(previous.ordinalsMaps, lense::lookupType);
       this.protoLiteFields = previous.protoLiteFields;
       // Sanity check sets after rewriting.
-      assert Sets.intersection(instanceFieldReads, staticFieldReads).isEmpty();
-      assert Sets.intersection(instanceFieldWrites, staticFieldWrites).isEmpty();
+      assert Sets.intersection(instanceFieldReads.keySet(), staticFieldReads.keySet()).isEmpty();
+      assert Sets.intersection(instanceFieldWrites.keySet(), staticFieldWrites.keySet()).isEmpty();
     }
 
     public AppInfoWithLiveness(AppInfoWithLiveness previous,
@@ -1744,6 +1756,18 @@
       return builder.build();
     }
 
+    private static <T extends PresortedComparable<T>, S> Map<T, Set<S>>
+        rewriteKeysWhileMergingValues(
+            Map<T, Set<S>> original, BiFunction<T, DexEncodedMethod, T> rewrite) {
+      Map<T, Set<S>> result = new IdentityHashMap<>();
+      for (T item : original.keySet()) {
+        T rewrittenKey = rewrite.apply(item, null);
+        result.computeIfAbsent(rewrittenKey, k -> Sets.newIdentityHashSet())
+            .addAll(original.get(item));
+      }
+      return Collections.unmodifiableMap(result);
+    }
+
     private static ImmutableSet<DexItem> rewriteMixedItems(
         Set<DexItem> original, GraphLense lense) {
       ImmutableSet.Builder<DexItem> builder = ImmutableSet.builder();
@@ -2095,6 +2119,10 @@
       return target;
     }
 
+    public DexEncodedMethod getContext() {
+      return context;
+    }
+
     @Override
     public int hashCode() {
       return target.hashCode() * 31 + context.hashCode();
diff --git a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
index 4d7240e..923398d 100644
--- a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
@@ -550,24 +550,6 @@
   // Tests where the output of R8 fails when run with Art.
   private static final Multimap<String, TestCondition> failingRunWithArt =
       new ImmutableListMultimap.Builder<String, TestCondition>()
-          // This test relies on specific field access patterns, which we rewrite.
-          .put("064-field-access",
-              TestCondition.match(
-                  TestCondition.R8DEX_NOT_AFTER_D8_COMPILER,
-                  TestCondition.runtimesUpTo(DexVm.Version.V4_4_4)))
-          .put("064-field-access",
-              TestCondition.match(
-                  TestCondition.R8DEX_COMPILER,
-                  TestCondition.runtimes(
-                      DexVm.Version.DEFAULT, DexVm.Version.V7_0_0, DexVm.Version.V6_0_1,
-                      DexVm.Version.V5_1_1)))
-          .put("064-field-access",
-              TestCondition.match(
-                  TestCondition.tools(DexTool.NONE),
-                  TestCondition.D8_AFTER_R8CF_COMPILER,
-                  TestCondition.runtimes(
-                      DexVm.Version.DEFAULT, DexVm.Version.V7_0_0, DexVm.Version.V6_0_1,
-                      DexVm.Version.V5_1_1)))
           // The growth limit test fails after processing by R8 because R8 will eliminate an
           // "unneeded" const store. The following reflective call to the VM's GC will then see the
           // large array as still live and the subsequent allocations will fail to reach the desired
diff --git a/src/test/java/com/android/tools/r8/regress/B76025099.java b/src/test/java/com/android/tools/r8/regress/B76025099.java
index 9fa694d..767420b 100644
--- a/src/test/java/com/android/tools/r8/regress/B76025099.java
+++ b/src/test/java/com/android/tools/r8/regress/B76025099.java
@@ -3,7 +3,9 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.regress;
 
+import static com.android.tools.r8.utils.DexInspectorMatchers.isPresent;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
 
 import com.android.tools.r8.OutputMode;
 import com.android.tools.r8.R8Command;
@@ -11,50 +13,113 @@
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.ProcessResult;
 import com.android.tools.r8.VmTestRunner;
-import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.code.InvokeDirect;
+import com.android.tools.r8.code.IputObject;
+import com.android.tools.r8.code.ReturnVoid;
+import com.android.tools.r8.graph.DexCode;
+import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.android.tools.r8.utils.DexInspector.MethodSubject;
 import com.android.tools.r8.utils.FileUtils;
 import com.google.common.collect.ImmutableList;
+import java.io.File;
+import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
-import org.junit.Ignore;
+import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import regress_76025099.Main;
+import regress_76025099.impl.Impl;
 
 @RunWith(VmTestRunner.class)
 public class B76025099 extends TestBase {
-
   private static final String PRG =
       ToolHelper.EXAMPLES_BUILD_DIR + "regress_76025099" + FileUtils.JAR_EXTENSION;
 
-  private AndroidApp runR8(AndroidApp app, Class main, Path out) throws Exception {
+  private AndroidApp runR8(AndroidApp app) throws Exception {
      R8Command command =
         ToolHelper.addProguardConfigurationConsumer(
             ToolHelper.prepareR8CommandBuilder(app),
             pgConfig -> {
               pgConfig.setPrintMapping(true);
-              pgConfig.setPrintMappingFile(out.resolve(ToolHelper.DEFAULT_PROGUARD_MAP_FILE));
+              pgConfig.setPrintMappingFile(map);
             })
-        .addProguardConfiguration(
-            ImmutableList.of(keepMainProguardConfiguration(main)),
-            Origin.unknown())
-        .setOutput(out, OutputMode.DexIndexed)
+        .addProguardConfigurationFiles(pgConfig)
+        .setOutput(tempRoot.toPath(), OutputMode.DexIndexed)
         .build();
     return ToolHelper.runR8(command, o -> {
       o.enableMinification = false;
     });
   }
 
-  @Ignore("b/76025099")
+  private File tempRoot;
+  private Path jarPath;
+  private AndroidApp originalApp;
+  private String mainName;
+  private Path pgConfig;
+  private Path map;
+
+  @Before
+  public void setUp() throws Exception {
+    tempRoot = temp.getRoot();
+    jarPath = Paths.get(PRG);
+    originalApp = readJar(jarPath);
+    mainName = Main.class.getCanonicalName();
+    pgConfig = File.createTempFile("keep-rules", ".config", tempRoot).toPath();
+    String config = keepMainProguardConfiguration(Main.class);
+    config += System.lineSeparator() + "-dontobfuscate";
+    Files.write(pgConfig, config.getBytes());
+    map = File.createTempFile("proguard", ".map", tempRoot).toPath();
+  }
+
   @Test
-  public void test() throws Exception {
-    Path out = temp.getRoot().toPath();
-    Path jarPath = Paths.get(PRG);
-    String mainName = Main.class.getCanonicalName();
+  public void testProguardAndD8() throws Exception {
+    if (!isRunProguard()) {
+      return;
+    }
+
     ProcessResult jvmOutput = ToolHelper.runJava(ImmutableList.of(jarPath), mainName);
     assertEquals(0, jvmOutput.exitCode);
-    AndroidApp processedApp = runR8(readJar(jarPath), Main.class, out);
+
+    Path proguarded =
+        File.createTempFile("proguarded", FileUtils.JAR_EXTENSION, tempRoot).toPath();
+    ProcessResult proguardResult = ToolHelper.runProguardRaw(jarPath, proguarded, pgConfig, map);
+    assertEquals(0, proguardResult.exitCode);
+
+    AndroidApp processedApp = ToolHelper.runD8(readJar(proguarded));
+    verifyFieldAccess(processedApp, jvmOutput);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    ProcessResult jvmOutput = ToolHelper.runJava(ImmutableList.of(jarPath), mainName);
+    assertEquals(0, jvmOutput.exitCode);
+
+    AndroidApp processedApp = runR8(originalApp);
+    verifyFieldAccess(processedApp, jvmOutput);
+  }
+
+  private void verifyFieldAccess(AndroidApp processedApp, ProcessResult jvmOutput)
+      throws Exception {
+    DexInspector inspector = new DexInspector(processedApp);
+    ClassSubject impl = inspector.clazz(Impl.class);
+    assertThat(impl, isPresent());
+    MethodSubject init = impl.init(ImmutableList.of("java.lang.String"));
+    assertThat(init, isPresent());
+    DexCode dexCode = init.getMethod().getCode().asDexCode();
+    checkInstructions(dexCode, ImmutableList.of(
+        InvokeDirect.class,
+        IputObject.class,
+        ReturnVoid.class
+    ));
+    IputObject iput = (IputObject) dexCode.instructions[1];
+    DexField fld = iput.getField();
+    assertEquals("name", fld.name.toString());
+    assertEquals("regress_76025099.impl.Impl", fld.getHolder().toSourceString());
+
     ProcessResult artOutput = runOnArtRaw(processedApp, mainName);
     assertEquals(0, artOutput.exitCode);
     assertEquals(jvmOutput.stdout, artOutput.stdout);
diff --git a/src/test/java/com/android/tools/r8/resolution/PublicFieldInnerClassTestRunner.java b/src/test/java/com/android/tools/r8/resolution/PublicFieldInnerClassTestRunner.java
index b13a08e..6642352 100644
--- a/src/test/java/com/android/tools/r8/resolution/PublicFieldInnerClassTestRunner.java
+++ b/src/test/java/com/android/tools/r8/resolution/PublicFieldInnerClassTestRunner.java
@@ -4,7 +4,6 @@
 package com.android.tools.r8.resolution;
 
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotEquals;
 
 import com.android.tools.r8.ClassFileConsumer;
 import com.android.tools.r8.CompilationMode;
@@ -46,12 +45,11 @@
     assertEquals(0, runInput.exitCode);
     Path outDex = temp.getRoot().toPath().resolve("dex.zip");
     build(new DexIndexedConsumer.ArchiveConsumer(outDex));
-    // TODO(b/76191597): Change to runArtNoVerificationErrors + assertEquals when bug is fixed
-    ProcessResult runDex = ToolHelper.runArtRaw(
+    ProcessResult runDex = ToolHelper.runArtNoVerificationErrorsRaw(
         outDex.toString(), CLASS.getCanonicalName());
-    assertNotEquals(runInput.stdout, runDex.stdout);
-    assertNotEquals(runInput.exitCode, runDex.exitCode);
-    assertNotEquals(
+    assertEquals(runInput.stdout, runDex.stdout);
+    assertEquals(runInput.exitCode, runDex.exitCode);
+    assertEquals(
         -1,
         runDex.stderr.indexOf("java.lang.NoSuchFieldError"));
   }