Version 2.0.101

Partial cherry pick: Check accessibility to fields and members based on the resolution holder
CL: https://r8-review.googlesource.com/c/r8/+/54092

This CL cherry picks the tests from https://r8-review.googlesource.com/c/r8/+/54092.

The non-test changes does not cherry pick cleanly. Therefore, this CL manually implements the fixes from https://r8-review.googlesource.com/c/r8/+/54092 in 2.0.

Unlike the change in https://r8-review.googlesource.com/c/r8/+/54092, this fix does not attempt to clean up the AccessControl API.

Bug: 169045091
Change-Id: Ibfe17b69e2c06caddb6b38d3f395b199668a54d4
diff --git a/src/main/java/com/android/tools/r8/Version.java b/src/main/java/com/android/tools/r8/Version.java
index cd0482d..3215d59 100644
--- a/src/main/java/com/android/tools/r8/Version.java
+++ b/src/main/java/com/android/tools/r8/Version.java
@@ -11,7 +11,7 @@
 
   // This field is accessed from release scripts using simple pattern matching.
   // Therefore, changing this field could break our release scripts.
-  public static final String LABEL = "2.0.100";
+  public static final String LABEL = "2.0.101";
 
   private Version() {
   }
diff --git a/src/main/java/com/android/tools/r8/graph/AccessControl.java b/src/main/java/com/android/tools/r8/graph/AccessControl.java
index 3b4d52f..03a3fa3 100644
--- a/src/main/java/com/android/tools/r8/graph/AccessControl.java
+++ b/src/main/java/com/android/tools/r8/graph/AccessControl.java
@@ -20,41 +20,46 @@
 
   public static boolean isMethodAccessible(
       DexEncodedMethod method,
-      DexClass holder,
+      DexClass resolvedHolder,
+      DexClass initialResolutionHolder,
       DexProgramClass context,
       AppInfoWithSubtyping appInfo) {
-    return isMemberAccessible(method.accessFlags, holder, context, appInfo);
+    return isMemberAccessible(
+        method.accessFlags, resolvedHolder, initialResolutionHolder, context, appInfo);
   }
 
   public static boolean isFieldAccessible(
       DexEncodedField field,
-      DexClass holder,
+      DexClass resolvedHolder,
+      DexClass initialResolutionHolder,
       DexProgramClass context,
       AppInfoWithSubtyping appInfo) {
-    return isMemberAccessible(field.accessFlags, holder, context, appInfo);
+    return isMemberAccessible(
+        field.accessFlags, resolvedHolder, initialResolutionHolder, context, appInfo);
   }
 
   private static boolean isMemberAccessible(
       AccessFlags<?> memberFlags,
-      DexClass holder,
+      DexClass resolvedHolder,
+      DexClass initialResolutionHolder,
       DexProgramClass context,
       AppInfoWithSubtyping appInfo) {
-    if (!isClassAccessible(holder, context)) {
+    if (!isClassAccessible(initialResolutionHolder, context)) {
       return false;
     }
     if (memberFlags.isPublic()) {
       return true;
     }
     if (memberFlags.isPrivate()) {
-      return isNestMate(holder, context);
+      return isNestMate(resolvedHolder, context);
     }
-    if (holder.getType().isSamePackage(context.getType())) {
+    if (resolvedHolder.getType().isSamePackage(context.getType())) {
       return true;
     }
     if (!memberFlags.isProtected()) {
       return false;
     }
-    return appInfo.isSubtype(context.getType(), holder.getType());
+    return appInfo.isSubtype(context.getType(), resolvedHolder.getType());
   }
 
   private static boolean isNestMate(DexClass clazz, DexProgramClass context) {
diff --git a/src/main/java/com/android/tools/r8/graph/ResolutionResult.java b/src/main/java/com/android/tools/r8/graph/ResolutionResult.java
index ad22d6d..8774d9c 100644
--- a/src/main/java/com/android/tools/r8/graph/ResolutionResult.java
+++ b/src/main/java/com/android/tools/r8/graph/ResolutionResult.java
@@ -118,7 +118,7 @@
     @Override
     public boolean isAccessibleFrom(DexProgramClass context, AppInfoWithSubtyping appInfo) {
       return AccessControl.isMethodAccessible(
-          resolvedMethod, initialResolutionHolder, context, appInfo);
+          resolvedMethod, resolvedHolder, initialResolutionHolder, context, appInfo);
     }
 
     @Override
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 05108dc..b0c5d5a 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -2054,7 +2054,7 @@
     if (contextOrNull != null
         && !resolution.isUnresolved()
         && !AccessControl.isMethodAccessible(
-            resolution.method, holder, contextOrNull.holder, appInfo)) {
+            resolution.method, resolution.holder, holder, contextOrNull.holder, appInfo)) {
       // Not accessible from this context, so this call will cause a runtime exception.
       // Note that the resolution is not cached, as another call context may be valid.
       return;
diff --git a/src/main/java/com/android/tools/r8/utils/AndroidApp.java b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
index 6f47839..d766da8 100644
--- a/src/main/java/com/android/tools/r8/utils/AndroidApp.java
+++ b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
@@ -895,6 +895,10 @@
       return this;
     }
 
+    public Builder addClassProgramData(byte[]... data) {
+      return addClassProgramData(Arrays.asList(data));
+    }
+
     /**
      * Add Java-bytecode program data.
      */
diff --git a/src/test/examplesJava11/com/android/tools/r8/shaking/b169045091/examples/NestHost.java b/src/test/examplesJava11/com/android/tools/r8/shaking/b169045091/examples/NestHost.java
new file mode 100644
index 0000000..5c1453e
--- /dev/null
+++ b/src/test/examplesJava11/com/android/tools/r8/shaking/b169045091/examples/NestHost.java
@@ -0,0 +1,11 @@
+// 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.shaking.b169045091.examples;
+
+public class NestHost {
+  /*private*/ int f;
+
+  public static class NestMember extends NestHost {}
+}
diff --git a/src/test/examplesJava11/com/android/tools/r8/shaking/b169045091/examples/NonNestMember.java b/src/test/examplesJava11/com/android/tools/r8/shaking/b169045091/examples/NonNestMember.java
new file mode 100644
index 0000000..02fd5e7
--- /dev/null
+++ b/src/test/examplesJava11/com/android/tools/r8/shaking/b169045091/examples/NonNestMember.java
@@ -0,0 +1,7 @@
+// 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.shaking.b169045091.examples;
+
+public class NonNestMember extends NestHost.NestMember {}
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index ba45374..10f43aa 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -234,6 +234,11 @@
     return ClassFileTransformer.create(clazz);
   }
 
+  public static ClassFileTransformer transformer(Path path, ClassReference classReference)
+      throws IOException {
+    return ClassFileTransformer.create(Files.readAllBytes(path), classReference);
+  }
+
   public static ClassFileTransformer transformer(byte[] bytes, ClassReference classReference) {
     return ClassFileTransformer.create(bytes, classReference);
   }
@@ -427,6 +432,10 @@
     return buildClasses(programClasses, libraryClasses).build();
   }
 
+  protected static AndroidApp.Builder buildClasses() throws IOException {
+    return buildClasses(Collections.emptyList());
+  }
+
   protected static AndroidApp.Builder buildClasses(Collection<Class<?>> programClasses)
       throws IOException {
     return buildClasses(programClasses, Collections.emptyList());
@@ -631,6 +640,13 @@
         method.getMethodName());
   }
 
+  protected static DexMethod buildNullaryVoidMethod(
+      Class<?> clazz, String name, DexItemFactory factory) {
+    return buildMethod(
+        Reference.method(Reference.classFromClass(clazz), name, Collections.emptyList(), null),
+        factory);
+  }
+
   protected static DexProto buildProto(
       TypeReference returnType, List<TypeReference> formalTypes, DexItemFactory factory) {
     return factory.createProto(
diff --git a/src/test/java/com/android/tools/r8/regress/b158429654/InliningNonAccessible.java b/src/test/java/com/android/tools/r8/regress/b158429654/InliningNonAccessible.java
new file mode 100644
index 0000000..dfed133
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/regress/b158429654/InliningNonAccessible.java
@@ -0,0 +1,47 @@
+// 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.regress.b158429654;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.regress.b158429654.innerpackage.InnerClass;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class InliningNonAccessible extends TestBase {
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static List<Object[]> data() {
+    return buildParameters(getTestParameters().withAllRuntimesAndApiLevels().build());
+  }
+
+  public InliningNonAccessible(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testCompileToInvalidFileD8() throws Exception {
+    testForR8(parameters.getBackend())
+        .setMinApi(parameters.getApiLevel())
+        .addProgramClasses(OuterAbstract.class, OuterImpl.class, InnerClass.class)
+        .addProgramClasses(Main.class)
+        .addKeepMainRule(Main.class)
+        .noMinification()
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("42");
+  }
+
+  static class Main {
+    public static void main(String[] args) {
+      OuterImpl.register(args);
+      new InnerClass().foobar();
+      System.out.println("42");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/regress/b158429654/OuterAbstract.java b/src/test/java/com/android/tools/r8/regress/b158429654/OuterAbstract.java
new file mode 100644
index 0000000..0c5a078
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/regress/b158429654/OuterAbstract.java
@@ -0,0 +1,19 @@
+// 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.regress.b158429654;
+
+public abstract class OuterAbstract {
+  private static OuterAbstract sInstance;
+
+  public static OuterAbstract getInstance() {
+    return sInstance;
+  }
+
+  public static void setsInstance(OuterAbstract sInstance) {
+    OuterAbstract.sInstance = sInstance;
+  }
+
+  public abstract void theMethod();
+}
diff --git a/src/test/java/com/android/tools/r8/regress/b158429654/OuterImpl.java b/src/test/java/com/android/tools/r8/regress/b158429654/OuterImpl.java
new file mode 100644
index 0000000..edca5c1
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/regress/b158429654/OuterImpl.java
@@ -0,0 +1,17 @@
+// 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.regress.b158429654;
+
+class OuterImpl extends OuterAbstract {
+
+  public static void register(String[] args) {
+    OuterAbstract.setsInstance(new OuterImpl());
+  }
+
+  private OuterImpl() {}
+
+  @Override
+  public void theMethod() {}
+}
diff --git a/src/test/java/com/android/tools/r8/regress/b158429654/innerpackage/InnerClass.java b/src/test/java/com/android/tools/r8/regress/b158429654/innerpackage/InnerClass.java
new file mode 100644
index 0000000..b2f8582
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/regress/b158429654/innerpackage/InnerClass.java
@@ -0,0 +1,14 @@
+// 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.regress.b158429654.innerpackage;
+
+import com.android.tools.r8.regress.b158429654.OuterAbstract;
+
+public class InnerClass {
+
+  public void foobar() {
+    OuterAbstract.getInstance().theMethod();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/resolution/access/indirectfield/IndirectFieldAccessTest.java b/src/test/java/com/android/tools/r8/resolution/access/indirectfield/IndirectFieldAccessTest.java
index e517531..e05a9b7 100644
--- a/src/test/java/com/android/tools/r8/resolution/access/indirectfield/IndirectFieldAccessTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/access/indirectfield/IndirectFieldAccessTest.java
@@ -57,12 +57,14 @@
             // Reflecting on B.class.getField("f") will give A.f, so manually create the reference.
             Reference.field(Reference.classFromClass(B.class), "f", Reference.INT),
             appInfo.dexItemFactory());
+    DexProgramClass resolvedHolder =
+        appInfo.definitionFor(buildType(A.class, appInfo.dexItemFactory())).asProgramClass();
     DexClass initialResolutionHolder = appInfo.definitionFor(f.holder);
     DexEncodedField resolutionTarget = appInfo.resolveField(f);
     // TODO(b/145723539): Test access via the resolution result once possible.
     assertTrue(
         AccessControl.isFieldAccessible(
-            resolutionTarget, initialResolutionHolder, cClass, appInfo));
+            resolutionTarget, resolvedHolder, initialResolutionHolder, cClass, appInfo));
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/shaking/b169045091/B169045091.java b/src/test/java/com/android/tools/r8/shaking/b169045091/B169045091.java
new file mode 100644
index 0000000..1e37300
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/b169045091/B169045091.java
@@ -0,0 +1,143 @@
+// 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.shaking.b169045091;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.b169045091.testclasses.HelloGreeter;
+import com.android.tools.r8.shaking.b169045091.testclasses.WorldGreeterBase;
+import com.google.common.collect.ImmutableList;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class B169045091 extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public B169045091(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testRuntime() throws Exception {
+    testForRuntime(parameters)
+        .addProgramClasses(getProgramClasses())
+        .addProgramClassFileData(getWorldGreeterClassFileData())
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("Hello world!");
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(getProgramClasses())
+        .addProgramClassFileData(getWorldGreeterClassFileData())
+        .addKeepMainRule(TestClass.class)
+        .enableNeverClassInliningAnnotations()
+        .enableInliningAnnotations()
+        .enableMergeAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .noMinification()
+        .compile()
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("Hello world!");
+  }
+
+  private List<Class<?>> getProgramClasses() {
+    return ImmutableList.of(
+        TestClass.class, HelloGreeter.class, HelloGreeterBase.class, WorldGreeterBase.class);
+  }
+
+  private byte[] getWorldGreeterClassFileData() throws Exception {
+    return transformer(WorldGreeter.class)
+        .removeMethods(
+            (int access, String name, String descriptor, String signature, String[] exceptions) ->
+                name.equals("world"))
+        .transform();
+  }
+
+  @Test
+  public void testAccessibility() throws Exception {
+    AppView<AppInfoWithLiveness> appView =
+        computeAppViewWithLiveness(
+            buildClasses(getProgramClasses())
+                .addClassProgramData(getWorldGreeterClassFileData())
+                .build(),
+            TestClass.class);
+    AppInfoWithLiveness appInfo = appView.appInfo();
+    DexItemFactory dexItemFactory = appView.dexItemFactory();
+
+    DexProgramClass context =
+        appView.definitionFor(buildType(TestClass.class, dexItemFactory)).asProgramClass();
+
+    // Test that HelloGreeter.greet() is accessible to TestClass.
+    DexMethod helloReference = buildNullaryVoidMethod(HelloGreeter.class, "hello", dexItemFactory);
+    assertTrue(
+        appInfo
+            .resolveMethodOnClass(helloReference.holder, helloReference)
+            .isAccessibleFrom(context, appInfo));
+
+    // Test that WorldGreeter.greet() is inaccessible to TestClass.
+    DexMethod worldReference = buildNullaryVoidMethod(WorldGreeter.class, "world", dexItemFactory);
+    assertFalse(
+        appInfo
+            .resolveMethodOnClass(worldReference.holder, worldReference)
+            .isAccessibleFrom(context, appInfo));
+  }
+
+  public static class TestClass {
+
+    public static void main(String[] args) {
+      // HelloGreeterBase.greet() is accessible to TestClass because they are in the same package.
+      new HelloGreeter().hello();
+
+      try {
+        // WorldGreeterBase.world() is inaccessible to TestClass.
+        new WorldGreeter().world();
+        throw new RuntimeException();
+      } catch (IllegalAccessError e) {
+        System.out.println(" world!");
+      }
+    }
+  }
+
+  @NeverMerge
+  public static class HelloGreeterBase {
+    @NeverInline
+    protected void hello() {
+      System.out.print("Hello");
+    }
+  }
+
+  @NeverClassInline
+  public static class WorldGreeter extends WorldGreeterBase {
+
+    // Removed by a transformer.
+    @Override
+    public void world() {
+      super.world();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/b169045091/NestMemberAccessibilityTest.java b/src/test/java/com/android/tools/r8/shaking/b169045091/NestMemberAccessibilityTest.java
new file mode 100644
index 0000000..5c34963
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/b169045091/NestMemberAccessibilityTest.java
@@ -0,0 +1,149 @@
+// 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.shaking.b169045091;
+
+import static com.android.tools.r8.references.Reference.INT;
+import static org.junit.Assert.assertFalse;
+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.ToolHelper;
+import com.android.tools.r8.graph.AccessControl;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.b169045091.B169045091.TestClass;
+import com.android.tools.r8.shaking.b169045091.examples.NestHost;
+import com.android.tools.r8.shaking.b169045091.examples.NestHost.NestMember;
+import com.android.tools.r8.shaking.b169045091.examples.NonNestMember;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.google.common.collect.ImmutableList;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.List;
+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 NestMemberAccessibilityTest extends TestBase {
+
+  private final Path TEST_DIRECTORY =
+      Paths.get(ToolHelper.EXAMPLES_JAVA11_BUILD_DIR)
+          .resolve(
+              DescriptorUtils.getBinaryNameFromJavaType(NestHost.class.getPackage().getName()));
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return TestBase.getTestParameters().withNoneRuntime().build();
+  }
+
+  public NestMemberAccessibilityTest(TestParameters parameters) {}
+
+  @Test
+  public void testAccessibility() throws Exception {
+    AppView<AppInfoWithLiveness> appView =
+        computeAppViewWithLiveness(
+            buildClasses()
+                .addProgramFiles(getProgramFiles())
+                .addClassProgramData(getNestHostClassFileData())
+                .build(),
+            TestClass.class);
+    AppInfoWithLiveness appInfo = appView.appInfo();
+    DexItemFactory dexItemFactory = appView.dexItemFactory();
+
+    DexProgramClass hostContext =
+        appView
+            .definitionForProgramType(buildType(NestHost.class, dexItemFactory))
+            .asProgramClass();
+
+    DexProgramClass memberContext =
+        appView
+            .definitionForProgramType(buildType(NestMember.class, dexItemFactory))
+            .asProgramClass();
+
+    DexProgramClass nonMemberContext =
+        appView
+            .definitionForProgramType(buildType(NonNestMember.class, dexItemFactory))
+            .asProgramClass();
+
+    // Test that NestHost.f is accessible to NestHost and NestMember but not NonNestMember.
+    DexField hostFieldReference =
+        buildField(
+            Reference.field(Reference.classFromClass(NestHost.class), "f", INT), dexItemFactory);
+    assertTrue(
+        AccessControl.isFieldAccessible(
+            appInfo.resolveField(hostFieldReference),
+            hostContext,
+            hostContext,
+            hostContext,
+            appInfo));
+    assertTrue(
+        AccessControl.isFieldAccessible(
+            appInfo.resolveField(hostFieldReference),
+            hostContext,
+            hostContext,
+            memberContext,
+            appInfo));
+    assertFalse(
+        AccessControl.isFieldAccessible(
+            appInfo.resolveField(hostFieldReference),
+            hostContext,
+            hostContext,
+            nonMemberContext,
+            appInfo));
+
+    // Test that NestMember.f is accessible to NestMember but not NonNestMember.
+    DexField memberFieldReference =
+        buildField(
+            Reference.field(Reference.classFromClass(NestMember.class), "f", INT), dexItemFactory);
+    assertTrue(
+        AccessControl.isFieldAccessible(
+            appInfo.resolveField(memberFieldReference),
+            hostContext,
+            memberContext,
+            memberContext,
+            appInfo));
+    assertFalse(
+        AccessControl.isFieldAccessible(
+            appInfo.resolveField(memberFieldReference),
+            hostContext,
+            memberContext,
+            nonMemberContext,
+            appInfo));
+
+    // Test that NonNestMember.f is inaccessible to NonNestMember.
+    DexField nonMemberFieldReference =
+        buildField(
+            Reference.field(Reference.classFromClass(NonNestMember.class), "f", INT),
+            dexItemFactory);
+    assertFalse(
+        AccessControl.isFieldAccessible(
+            appInfo.resolveField(nonMemberFieldReference),
+            hostContext,
+            nonMemberContext,
+            nonMemberContext,
+            appInfo));
+  }
+
+  private List<Path> getProgramFiles() {
+    return ImmutableList.of(
+        TEST_DIRECTORY.resolve("NestHost$NestMember.class"),
+        TEST_DIRECTORY.resolve("NonNestMember.class"));
+  }
+
+  private byte[] getNestHostClassFileData() throws Exception {
+    return transformer(
+            TEST_DIRECTORY.resolve("NestHost.class"), Reference.classFromClass(NestHost.class))
+        .setPrivate(NestHost.class.getDeclaredField("f"))
+        .transform();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/b169045091/examples/NestHost.java b/src/test/java/com/android/tools/r8/shaking/b169045091/examples/NestHost.java
new file mode 100644
index 0000000..872e37b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/b169045091/examples/NestHost.java
@@ -0,0 +1,15 @@
+// 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.shaking.b169045091.examples;
+
+/**
+ * Mirror of src/test/examplesJava11/com/android/tools/r8/shaking/b169045091/examples/NestHost.java
+ */
+public class NestHost {
+
+  public int f;
+
+  public static class NestMember {}
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/b169045091/examples/NonNestMember.java b/src/test/java/com/android/tools/r8/shaking/b169045091/examples/NonNestMember.java
new file mode 100644
index 0000000..26de699
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/b169045091/examples/NonNestMember.java
@@ -0,0 +1,11 @@
+// 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.shaking.b169045091.examples;
+
+/**
+ * Mirror of
+ * src/test/examplesJava11/com/android/tools/r8/shaking/b169045091/examples/NonNestMember.java.
+ */
+public class NonNestMember {}
diff --git a/src/test/java/com/android/tools/r8/shaking/b169045091/testclasses/HelloGreeter.java b/src/test/java/com/android/tools/r8/shaking/b169045091/testclasses/HelloGreeter.java
new file mode 100644
index 0000000..edc59a7
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/b169045091/testclasses/HelloGreeter.java
@@ -0,0 +1,11 @@
+// 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.shaking.b169045091.testclasses;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.shaking.b169045091.B169045091.HelloGreeterBase;
+
+@NeverClassInline
+public class HelloGreeter extends HelloGreeterBase {}
diff --git a/src/test/java/com/android/tools/r8/shaking/b169045091/testclasses/WorldGreeterBase.java b/src/test/java/com/android/tools/r8/shaking/b169045091/testclasses/WorldGreeterBase.java
new file mode 100644
index 0000000..8f30083
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/b169045091/testclasses/WorldGreeterBase.java
@@ -0,0 +1,16 @@
+// 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.shaking.b169045091.testclasses;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NeverMerge;
+
+@NeverMerge
+public class WorldGreeterBase {
+  @NeverInline
+  protected void world() {
+    System.out.println(" world!");
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java b/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
index 0c8b4c1..d012ab6 100644
--- a/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
+++ b/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
@@ -13,15 +13,18 @@
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.graph.AccessFlags;
+import com.android.tools.r8.graph.FieldAccessFlags;
 import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
 import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.FieldReference;
 import com.android.tools.r8.references.MethodReference;
 import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.transformers.MethodTransformer.MethodContext;
 import com.android.tools.r8.utils.DescriptorUtils;
 import java.io.IOException;
 import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
 import java.lang.reflect.Method;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -317,6 +320,16 @@
             });
   }
 
+  public ClassFileTransformer setPrivate(Field field) {
+    return setAccessFlags(
+        field,
+        accessFlags -> {
+          accessFlags.setPrivate();
+          accessFlags.unsetProtected();
+          accessFlags.unsetPublic();
+        });
+  }
+
   public ClassFileTransformer setPublic(Method method) {
     return setAccessFlags(
         method,
@@ -346,11 +359,34 @@
     return setAccessFlags(Reference.methodFromMethod(constructor), setter);
   }
 
+  public ClassFileTransformer setAccessFlags(Field field, Consumer<FieldAccessFlags> setter) {
+    return setAccessFlags(Reference.fieldFromField(field), setter);
+  }
+
   public ClassFileTransformer setAccessFlags(Method method, Consumer<MethodAccessFlags> setter) {
     return setAccessFlags(Reference.methodFromMethod(method), setter);
   }
 
   private ClassFileTransformer setAccessFlags(
+      FieldReference fieldReference, Consumer<FieldAccessFlags> setter) {
+    return addClassTransformer(
+        new ClassTransformer() {
+
+          @Override
+          public FieldVisitor visitField(
+              int access, String name, String descriptor, String signature, Object value) {
+            FieldAccessFlags accessFlags = FieldAccessFlags.fromCfAccessFlags(access);
+            if (name.equals(fieldReference.getFieldName())
+                && descriptor.equals(fieldReference.getFieldType().getDescriptor())) {
+              setter.accept(accessFlags);
+            }
+            return super.visitField(
+                accessFlags.getAsCfAccessFlags(), name, descriptor, signature, value);
+          }
+        });
+  }
+
+  private ClassFileTransformer setAccessFlags(
       MethodReference methodReference, Consumer<MethodAccessFlags> setter) {
     return addClassTransformer(
         new ClassTransformer() {