Check accessibility to fields and members based on the resolution holder

This also cleans up the AccessControl a bit by using the ProgramDefinition type for the context arguments, instead of having methods that take a DexProgramClass context and bridges for turning a ProgramMethod context into DexProgramClass.

Bug: 169045091
Change-Id: Ie99f3512df09bab387a97fe3acc37ce3102c8dc3
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 4bb68b9..ed19598 100644
--- a/src/main/java/com/android/tools/r8/graph/AccessControl.java
+++ b/src/main/java/com/android/tools/r8/graph/AccessControl.java
@@ -15,62 +15,53 @@
 public class AccessControl {
 
   public static OptionalBool isClassAccessible(
-      DexClass clazz, ProgramMethod context, AppView<? extends AppInfoWithClassHierarchy> appView) {
-    return isClassAccessible(
-        clazz, context.getHolder(), appView.appInfo().getClassToFeatureSplitMap());
+      DexClass clazz,
+      ProgramDefinition context,
+      AppView<? extends AppInfoWithClassHierarchy> appView) {
+    return isClassAccessible(clazz, context, appView.appInfo().getClassToFeatureSplitMap());
   }
 
   public static OptionalBool isClassAccessible(
-      DexClass clazz, DexProgramClass context, ClassToFeatureSplitMap classToFeatureSplitMap) {
-    if (!clazz.isPublic() && !clazz.getType().isSamePackage(context.getType())) {
+      DexClass clazz, ProgramDefinition context, ClassToFeatureSplitMap classToFeatureSplitMap) {
+    if (!clazz.isPublic() && !clazz.getType().isSamePackage(context.getContextType())) {
       return OptionalBool.FALSE;
     }
     if (clazz.isProgramClass()
-        && !classToFeatureSplitMap.isInBaseOrSameFeatureAs(clazz.asProgramClass(), context)) {
+        && !classToFeatureSplitMap.isInBaseOrSameFeatureAs(
+            clazz.asProgramClass(), context.getContextClass())) {
       return OptionalBool.UNKNOWN;
     }
     return OptionalBool.TRUE;
   }
 
-  public static OptionalBool isMethodAccessible(
-      DexEncodedMethod method,
-      DexClass holder,
-      ProgramMethod context,
+  /** Intentionally package-private, use {@link MemberResolutionResult#isAccessibleFrom}. */
+  static OptionalBool isMemberAccessible(
+      SuccessfulMemberResolutionResult<?, ?> resolutionResult,
+      ProgramDefinition context,
+      AppInfoWithClassHierarchy appInfo) {
+    return isMemberAccessible(
+        resolutionResult.getResolutionPair(),
+        resolutionResult.getInitialResolutionHolder(),
+        context,
+        appInfo);
+  }
+
+  public static OptionalBool isMemberAccessible(
+      DexClassAndMember<?, ?> member,
+      DexClass initialResolutionHolder,
+      ProgramDefinition context,
       AppView<? extends AppInfoWithClassHierarchy> appView) {
-    return isMethodAccessible(method, holder, context.getHolder(), appView.appInfo());
+    return isMemberAccessible(member, initialResolutionHolder, context, appView.appInfo());
   }
 
-  public static OptionalBool isMethodAccessible(
-      DexEncodedMethod method,
-      DexClass holder,
-      DexProgramClass context,
+  public static OptionalBool isMemberAccessible(
+      DexClassAndMember<?, ?> member,
+      DexClass initialResolutionHolder,
+      ProgramDefinition context,
       AppInfoWithClassHierarchy appInfo) {
-    return isMemberAccessible(method.accessFlags, holder, context, appInfo);
-  }
-
-  public static OptionalBool isFieldAccessible(
-      DexEncodedField field,
-      DexClass holder,
-      ProgramMethod context,
-      AppView<? extends AppInfoWithClassHierarchy> appView) {
-    return isFieldAccessible(field, holder, context.getHolder(), appView.appInfo());
-  }
-
-  public static OptionalBool isFieldAccessible(
-      DexEncodedField field,
-      DexClass holder,
-      DexProgramClass context,
-      AppInfoWithClassHierarchy appInfo) {
-    return isMemberAccessible(field.accessFlags, holder, context, appInfo);
-  }
-
-  private static OptionalBool isMemberAccessible(
-      AccessFlags<?> memberFlags,
-      DexClass holder,
-      DexProgramClass context,
-      AppInfoWithClassHierarchy appInfo) {
+    AccessFlags<?> memberFlags = member.getDefinition().getAccessFlags();
     OptionalBool classAccessibility =
-        isClassAccessible(holder, context, appInfo.getClassToFeatureSplitMap());
+        isClassAccessible(initialResolutionHolder, context, appInfo.getClassToFeatureSplitMap());
     if (classAccessibility.isFalse()) {
       return OptionalBool.FALSE;
     }
@@ -78,15 +69,16 @@
       return classAccessibility;
     }
     if (memberFlags.isPrivate()) {
-      if (!isNestMate(holder, context)) {
+      if (!isNestMate(member.getHolder(), context.getContextClass())) {
         return OptionalBool.FALSE;
       }
       return classAccessibility;
     }
-    if (holder.getType().isSamePackage(context.getType())) {
+    if (member.getHolderType().isSamePackage(context.getContextType())) {
       return classAccessibility;
     }
-    if (memberFlags.isProtected() && appInfo.isSubtype(context.getType(), holder.getType())) {
+    if (memberFlags.isProtected()
+        && appInfo.isSubtype(context.getContextType(), member.getHolderType())) {
       return classAccessibility;
     }
     return OptionalBool.FALSE;
diff --git a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
index 68da447..b5fe6b8 100644
--- a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
@@ -494,6 +494,11 @@
   }
 
   @Override
+  public DexProgramClass getContextClass() {
+    return this;
+  }
+
+  @Override
   public DexType getContextType() {
     return getType();
   }
diff --git a/src/main/java/com/android/tools/r8/graph/FieldResolutionResult.java b/src/main/java/com/android/tools/r8/graph/FieldResolutionResult.java
index 486996e..ecb09ca 100644
--- a/src/main/java/com/android/tools/r8/graph/FieldResolutionResult.java
+++ b/src/main/java/com/android/tools/r8/graph/FieldResolutionResult.java
@@ -7,7 +7,7 @@
 import com.android.tools.r8.utils.OptionalBool;
 
 public abstract class FieldResolutionResult
-    implements MemberResolutionResult<DexEncodedField, DexField> {
+    extends MemberResolutionResult<DexEncodedField, DexField> {
 
   public static FailedFieldResolutionResult failure() {
     return FailedFieldResolutionResult.INSTANCE;
@@ -21,9 +21,6 @@
     return null;
   }
 
-  public abstract OptionalBool isAccessibleFrom(
-      ProgramMethod context, AppInfoWithClassHierarchy appInfo);
-
   public boolean isSuccessfulResolution() {
     return false;
   }
@@ -85,14 +82,15 @@
       return resolvedField;
     }
 
+    @Override
     public DexClassAndField getResolutionPair() {
       return DexClassAndField.create(resolvedHolder, resolvedField);
     }
 
     @Override
-    public OptionalBool isAccessibleFrom(ProgramMethod context, AppInfoWithClassHierarchy appInfo) {
-      return AccessControl.isFieldAccessible(
-          resolvedField, initialResolutionHolder, context.getHolder(), appInfo);
+    public OptionalBool isAccessibleFrom(
+        ProgramDefinition context, AppInfoWithClassHierarchy appInfo) {
+      return AccessControl.isMemberAccessible(this, context, appInfo);
     }
 
     @Override
@@ -121,7 +119,8 @@
     private static final FailedFieldResolutionResult INSTANCE = new FailedFieldResolutionResult();
 
     @Override
-    public OptionalBool isAccessibleFrom(ProgramMethod context, AppInfoWithClassHierarchy appInfo) {
+    public OptionalBool isAccessibleFrom(
+        ProgramDefinition context, AppInfoWithClassHierarchy appInfo) {
       return OptionalBool.FALSE;
     }
 
@@ -140,7 +139,8 @@
     private static final UnknownFieldResolutionResult INSTANCE = new UnknownFieldResolutionResult();
 
     @Override
-    public OptionalBool isAccessibleFrom(ProgramMethod context, AppInfoWithClassHierarchy appInfo) {
+    public OptionalBool isAccessibleFrom(
+        ProgramDefinition context, AppInfoWithClassHierarchy appInfo) {
       return OptionalBool.FALSE;
     }
 
diff --git a/src/main/java/com/android/tools/r8/graph/MemberResolutionResult.java b/src/main/java/com/android/tools/r8/graph/MemberResolutionResult.java
index da70e09..01294be 100644
--- a/src/main/java/com/android/tools/r8/graph/MemberResolutionResult.java
+++ b/src/main/java/com/android/tools/r8/graph/MemberResolutionResult.java
@@ -4,10 +4,20 @@
 
 package com.android.tools.r8.graph;
 
-public interface MemberResolutionResult<
+import com.android.tools.r8.utils.OptionalBool;
+
+public abstract class MemberResolutionResult<
     D extends DexEncodedMember<D, R>, R extends DexMember<D, R>> {
 
-  boolean isSuccessfulMemberResolutionResult();
+  public abstract boolean isSuccessfulMemberResolutionResult();
 
-  SuccessfulMemberResolutionResult<D, R> asSuccessfulMemberResolutionResult();
+  public abstract SuccessfulMemberResolutionResult<D, R> asSuccessfulMemberResolutionResult();
+
+  public abstract OptionalBool isAccessibleFrom(
+      ProgramDefinition context, AppInfoWithClassHierarchy appInfo);
+
+  public final OptionalBool isAccessibleFrom(
+      ProgramDefinition context, AppView<? extends AppInfoWithClassHierarchy> appView) {
+    return isAccessibleFrom(context, appView.appInfo());
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/graph/ProgramDefinition.java b/src/main/java/com/android/tools/r8/graph/ProgramDefinition.java
index 91edb06..883b0a6 100644
--- a/src/main/java/com/android/tools/r8/graph/ProgramDefinition.java
+++ b/src/main/java/com/android/tools/r8/graph/ProgramDefinition.java
@@ -6,6 +6,8 @@
 
 public interface ProgramDefinition {
 
+  DexProgramClass getContextClass();
+
   DexType getContextType();
 
   DexDefinition getDefinition();
diff --git a/src/main/java/com/android/tools/r8/graph/ProgramMember.java b/src/main/java/com/android/tools/r8/graph/ProgramMember.java
index 328c71f..fc55509 100644
--- a/src/main/java/com/android/tools/r8/graph/ProgramMember.java
+++ b/src/main/java/com/android/tools/r8/graph/ProgramMember.java
@@ -8,7 +8,14 @@
     extends ProgramDefinition {
 
   @Override
+  default DexProgramClass getContextClass() {
+    return getHolder();
+  }
+
+  @Override
   D getDefinition();
 
+  DexProgramClass getHolder();
+
   DexType getHolderType();
 }
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 82b02c4..4db00fc 100644
--- a/src/main/java/com/android/tools/r8/graph/ResolutionResult.java
+++ b/src/main/java/com/android/tools/r8/graph/ResolutionResult.java
@@ -18,8 +18,7 @@
 import java.util.function.BiPredicate;
 import java.util.function.Consumer;
 
-public abstract class ResolutionResult
-    implements MemberResolutionResult<DexEncodedMethod, DexMethod> {
+public abstract class ResolutionResult extends MemberResolutionResult<DexEncodedMethod, DexMethod> {
 
   /**
    * Returns true if resolution succeeded *and* the resolved method has a known definition.
@@ -75,21 +74,8 @@
     return null;
   }
 
-  public abstract OptionalBool isAccessibleFrom(
-      DexProgramClass context, AppInfoWithClassHierarchy appInfo);
-
-  public final OptionalBool isAccessibleFrom(
-      ProgramMethod context, AppInfoWithClassHierarchy appInfo) {
-    return isAccessibleFrom(context.getHolder(), appInfo);
-  }
-
   public abstract OptionalBool isAccessibleForVirtualDispatchFrom(
-      DexProgramClass context, AppInfoWithClassHierarchy appInfo);
-
-  public final OptionalBool isAccessibleForVirtualDispatchFrom(
-      ProgramMethod context, AppInfoWithClassHierarchy appInfo) {
-    return isAccessibleForVirtualDispatchFrom(context.getHolder(), appInfo);
-  }
+      ProgramDefinition context, AppInfoWithClassHierarchy appInfo);
 
   public abstract boolean isVirtualTarget();
 
@@ -183,6 +169,7 @@
           : null;
     }
 
+    @Override
     public DexClassAndMethod getResolutionPair() {
       return DexClassAndMethod.create(resolvedHolder, resolvedMethod);
     }
@@ -210,14 +197,13 @@
 
     @Override
     public OptionalBool isAccessibleFrom(
-        DexProgramClass context, AppInfoWithClassHierarchy appInfo) {
-      return AccessControl.isMethodAccessible(
-          resolvedMethod, initialResolutionHolder, context, appInfo);
+        ProgramDefinition context, AppInfoWithClassHierarchy appInfo) {
+      return AccessControl.isMemberAccessible(this, context, appInfo);
     }
 
     @Override
     public OptionalBool isAccessibleForVirtualDispatchFrom(
-        DexProgramClass context, AppInfoWithClassHierarchy appInfo) {
+        ProgramDefinition context, AppInfoWithClassHierarchy appInfo) {
       if (resolvedMethod.isVirtualMethod()) {
         return isAccessibleFrom(context, appInfo);
       }
@@ -781,13 +767,13 @@
 
     @Override
     public OptionalBool isAccessibleFrom(
-        DexProgramClass context, AppInfoWithClassHierarchy appInfo) {
+        ProgramDefinition context, AppInfoWithClassHierarchy appInfo) {
       return OptionalBool.TRUE;
     }
 
     @Override
     public OptionalBool isAccessibleForVirtualDispatchFrom(
-        DexProgramClass context, AppInfoWithClassHierarchy appInfo) {
+        ProgramDefinition context, AppInfoWithClassHierarchy appInfo) {
       return OptionalBool.TRUE;
     }
 
@@ -816,13 +802,13 @@
 
     @Override
     public OptionalBool isAccessibleFrom(
-        DexProgramClass context, AppInfoWithClassHierarchy appInfo) {
+        ProgramDefinition context, AppInfoWithClassHierarchy appInfo) {
       return OptionalBool.FALSE;
     }
 
     @Override
     public OptionalBool isAccessibleForVirtualDispatchFrom(
-        DexProgramClass context, AppInfoWithClassHierarchy appInfo) {
+        ProgramDefinition context, AppInfoWithClassHierarchy appInfo) {
       return OptionalBool.FALSE;
     }
 
diff --git a/src/main/java/com/android/tools/r8/graph/SuccessfulMemberResolutionResult.java b/src/main/java/com/android/tools/r8/graph/SuccessfulMemberResolutionResult.java
index 84d89d0..4a94338 100644
--- a/src/main/java/com/android/tools/r8/graph/SuccessfulMemberResolutionResult.java
+++ b/src/main/java/com/android/tools/r8/graph/SuccessfulMemberResolutionResult.java
@@ -12,4 +12,6 @@
   DexClass getResolvedHolder();
 
   D getResolvedMember();
+
+  DexClassAndMember<D, R> getResolutionPair();
 }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleFieldValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleFieldValue.java
index b7e359e..3c9bd86 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleFieldValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleFieldValue.java
@@ -6,7 +6,6 @@
 
 import static com.android.tools.r8.ir.analysis.type.Nullability.maybeNull;
 
-import com.android.tools.r8.graph.AccessControl;
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
@@ -14,6 +13,7 @@
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.EnumValueInfoMapCollection.EnumValueInfoMap;
+import com.android.tools.r8.graph.FieldResolutionResult.SuccessfulFieldResolutionResult;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
@@ -80,12 +80,9 @@
   @Override
   public boolean isMaterializableInContext(
       AppView<AppInfoWithLiveness> appView, ProgramMethod context) {
-    return AccessControl.isFieldAccessible(
-            appView.appInfo().resolveField(field).getResolvedField(),
-            appView.definitionForHolder(field),
-            context.getHolder(),
-            appView.appInfo())
-        .isTrue();
+    SuccessfulFieldResolutionResult resolutionResult =
+        appView.appInfo().resolveField(field).asSuccessfulResolution();
+    return resolutionResult != null && resolutionResult.isAccessibleFrom(context, appView).isTrue();
   }
 
   @Override
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 ca93336..f0f1c29 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
@@ -65,7 +65,6 @@
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.IteratorUtils;
 import com.android.tools.r8.utils.ListUtils;
-import com.android.tools.r8.utils.OptionalBool;
 import com.android.tools.r8.utils.SetUtils;
 import com.android.tools.r8.utils.Timing;
 import com.android.tools.r8.utils.collections.ProgramMethodSet;
@@ -983,7 +982,8 @@
                   .appInfo()
                   .resolveMethod(invoke.getInvokedMethod(), invoke.getInterfaceBit())
                   .asSingleResolution();
-          if (resolutionResult == null) {
+          if (resolutionResult == null
+              || resolutionResult.isAccessibleFrom(context, appView).isPossiblyFalse()) {
             continue;
           }
 
@@ -994,16 +994,17 @@
             continue;
           }
 
-          OptionalBool methodAccessible =
-              AccessControl.isMethodAccessible(
-                  singleTarget.getDefinition(),
-                  singleTarget.getHolder().asDexClass(),
-                  context,
-                  appView);
-          if (!methodAccessible.isTrue()) {
-            continue;
+          DexType downcastTypeOrNull = getDowncastTypeIfNeeded(strategy, invoke, singleTarget);
+          if (downcastTypeOrNull != null) {
+            DexClass downcastClass = appView.definitionFor(downcastTypeOrNull, context);
+            if (downcastClass == null
+                || AccessControl.isClassAccessible(downcastClass, context, appView)
+                    .isPossiblyFalse()) {
+              continue;
+            }
           }
 
+
           DexEncodedMethod singleTargetMethod = singleTarget.getDefinition();
           WhyAreYouNotInliningReporter whyAreYouNotInliningReporter =
               oracle.isForcedInliningOracle()
@@ -1069,12 +1070,7 @@
           iterator.previous();
           strategy.markInlined(inlinee);
           iterator.inlineInvoke(
-              appView,
-              code,
-              inlinee.code,
-              blockIterator,
-              blocksToRemove,
-              getDowncastTypeIfNeeded(strategy, invoke, singleTarget));
+              appView, code, inlinee.code, blockIterator, blocksToRemove, downcastTypeOrNull);
 
           if (inlinee.reason == Reason.SINGLE_CALLER) {
             feedback.markInlinedIntoSingleCallSite(singleTargetMethod);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
index ec1f830..b917507 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
@@ -60,7 +60,6 @@
 import com.android.tools.r8.ir.optimize.inliner.NopWhyAreYouNotInliningReporter;
 import com.android.tools.r8.kotlin.KotlinClassLevelInfo;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.utils.OptionalBool;
 import com.android.tools.r8.utils.Pair;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.Timing;
@@ -292,7 +291,8 @@
                   .appInfo()
                   .resolveMethod(invokeMethod.getInvokedMethod(), invokeMethod.getInterfaceBit())
                   .asSingleResolution();
-          if (resolutionResult == null) {
+          if (resolutionResult == null
+              || resolutionResult.isAccessibleFrom(method, appView).isPossiblyFalse()) {
             return user; // Not eligible.
           }
 
@@ -311,13 +311,11 @@
             return user; // Not eligible.
           }
 
-          OptionalBool methodAccessible =
-              AccessControl.isMethodAccessible(
-                  singleTargetMethod, singleTarget.getHolder().asDexClass(), method, appView);
-
-          if (!methodAccessible.isTrue()) {
-            return user; // Not eligible.
+          if (AccessControl.isClassAccessible(singleTarget.getHolder(), method, appView)
+              .isPossiblyFalse()) {
+            continue;
           }
+
           // Eligible constructor call (for new instance roots only).
           if (user.isInvokeDirect()) {
             InvokeDirect invoke = user.asInvokeDirect();
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 b8022b9..5d3f09b 100644
--- a/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
+++ b/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
@@ -6,6 +6,7 @@
 import com.android.tools.r8.graph.AccessControl;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexClassAndField;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexField;
@@ -330,8 +331,8 @@
       return;
     }
 
-    DexEncodedField resolvedField = resolutionResult.getResolvedField();
-    if (resolvedField.field == field) {
+    DexClassAndField resolvedField = resolutionResult.getResolutionPair();
+    if (resolvedField.getReference() == field) {
       assert false;
       return;
     }
@@ -341,7 +342,7 @@
     boolean accessibleInAllContexts = true;
     for (ProgramMethod context : contexts) {
       boolean inaccessibleInContext =
-          AccessControl.isFieldAccessible(
+          AccessControl.isMemberAccessible(
                   resolvedField, resolutionResult.getResolvedHolder(), context, appView)
               .isPossiblyFalse();
       if (inaccessibleInContext) {
@@ -353,7 +354,8 @@
     if (accessibleInAllContexts) {
       builder.map(
           field,
-          lens.lookupField(validTargetFor(resolvedField.field, field, DexClass::lookupField)));
+          lens.lookupField(
+              validTargetFor(resolvedField.getReference(), field, DexClass::lookupField)));
     }
   }
 
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 bb8cb6e..b76e08f 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -337,6 +337,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);
   }
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
index acf084b..dfed133 100644
--- a/src/test/java/com/android/tools/r8/regress/b158429654/InliningNonAccessible.java
+++ b/src/test/java/com/android/tools/r8/regress/b158429654/InliningNonAccessible.java
@@ -40,8 +40,7 @@
   static class Main {
     public static void main(String[] args) {
       OuterImpl.register(args);
-      InnerClass inner = new InnerClass();
-      inner.foobar();
+      new InnerClass().foobar();
       System.out.println("42");
     }
   }
diff --git a/src/test/java/com/android/tools/r8/resolution/packageprivate/PackagePrivateWithDefaultMethodTest.java b/src/test/java/com/android/tools/r8/resolution/packageprivate/PackagePrivateWithDefaultMethodTest.java
index ed64145..3920cc8 100644
--- a/src/test/java/com/android/tools/r8/resolution/packageprivate/PackagePrivateWithDefaultMethodTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/packageprivate/PackagePrivateWithDefaultMethodTest.java
@@ -5,9 +5,7 @@
 package com.android.tools.r8.resolution.packageprivate;
 
 import static org.hamcrest.CoreMatchers.containsString;
-import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
 import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.CompilationFailedException;
@@ -29,10 +27,7 @@
 import com.android.tools.r8.resolution.packageprivate.a.NonAbstractExtendingA;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.transformers.ClassTransformer;
-import com.google.common.collect.ImmutableSet;
 import java.io.IOException;
-import java.util.HashSet;
-import java.util.Set;
 import java.util.concurrent.ExecutionException;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -74,14 +69,9 @@
     ResolutionResult resolutionResult = appInfo.resolveMethodOnClass(method);
     DexProgramClass context =
         appView.definitionForProgramType(buildType(A.class, appInfo.dexItemFactory()));
+    assertTrue(resolutionResult.isAccessibleFrom(context, appView).isFalse());
     LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appInfo);
-    assertTrue(lookupResult.isLookupResultSuccess());
-    Set<String> targets = new HashSet<>();
-    lookupResult.forEach(
-        target -> targets.add(target.getDefinition().qualifiedName()), lambda -> fail());
-    // TODO(b/148591377): The set should be empty.
-    ImmutableSet<String> expected = ImmutableSet.of(Abstract.class.getTypeName() + ".foo");
-    assertEquals(expected, targets);
+    assertTrue(lookupResult.isLookupResultFailure());
   }
 
   @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..29a7280
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/b169045091/B169045091.java
@@ -0,0 +1,142 @@
+// 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.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoVerticalClassMerging;
+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()
+        .enableNoVerticalClassMergingAnnotations()
+        .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 {
+    assumeTrue(parameters.useRuntimeAsNoneRuntime());
+    AppView<AppInfoWithLiveness> appView =
+        computeAppViewWithLiveness(
+            buildClasses(getProgramClasses())
+                .addClassProgramData(getWorldGreeterClassFileData())
+                .build(),
+            TestClass.class);
+    AppInfoWithLiveness appInfo = appView.appInfo();
+    DexItemFactory dexItemFactory = appView.dexItemFactory();
+
+    DexProgramClass context =
+        appView
+            .contextIndependentDefinitionFor(buildType(TestClass.class, dexItemFactory))
+            .asProgramClass();
+
+    // Test that HelloGreeter.greet() is accessible to TestClass.
+    DexMethod helloReference = buildNullaryVoidMethod(HelloGreeter.class, "hello", dexItemFactory);
+    assertTrue(
+        appInfo.resolveMethodOnClass(helloReference).isAccessibleFrom(context, appView).isTrue());
+
+    // Test that WorldGreeter.greet() is inaccessible to TestClass.
+    DexMethod worldReference = buildNullaryVoidMethod(WorldGreeter.class, "world", dexItemFactory);
+    assertTrue(
+        appInfo.resolveMethodOnClass(worldReference).isAccessibleFrom(context, appView).isFalse());
+  }
+
+  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!");
+      }
+    }
+  }
+
+  @NoVerticalClassMerging
+  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..62923a7
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/b169045091/NestMemberAccessibilityTest.java
@@ -0,0 +1,131 @@
+// 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.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.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) {
+    parameters.assertNoneRuntime();
+  }
+
+  @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
+            .contextIndependentDefinitionFor(buildType(NestHost.class, dexItemFactory))
+            .asProgramClass();
+
+    DexProgramClass memberContext =
+        appView
+            .contextIndependentDefinitionFor(buildType(NestMember.class, dexItemFactory))
+            .asProgramClass();
+
+    DexProgramClass nonMemberContext =
+        appView
+            .contextIndependentDefinitionFor(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(
+        appInfo.resolveField(hostFieldReference).isAccessibleFrom(hostContext, appView).isTrue());
+    assertTrue(
+        appInfo.resolveField(hostFieldReference).isAccessibleFrom(memberContext, appView).isTrue());
+    assertTrue(
+        appInfo
+            .resolveField(hostFieldReference)
+            .isAccessibleFrom(nonMemberContext, appView)
+            .isFalse());
+
+    // Test that NestMember.f is accessible to NestMember but not NonNestMember.
+    DexField memberFieldReference =
+        buildField(
+            Reference.field(Reference.classFromClass(NestMember.class), "f", INT), dexItemFactory);
+    assertTrue(
+        appInfo
+            .resolveField(memberFieldReference)
+            .isAccessibleFrom(memberContext, appView)
+            .isTrue());
+    assertTrue(
+        appInfo
+            .resolveField(memberFieldReference)
+            .isAccessibleFrom(nonMemberContext, appView)
+            .isFalse());
+
+    // Test that NonNestMember.f is inaccessible to NonNestMember.
+    DexField nonMemberFieldReference =
+        buildField(
+            Reference.field(Reference.classFromClass(NonNestMember.class), "f", INT),
+            dexItemFactory);
+    assertTrue(
+        appInfo
+            .resolveField(nonMemberFieldReference)
+            .isAccessibleFrom(nonMemberContext, appView)
+            .isFalse());
+  }
+
+  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..9dc0b62
--- /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.NoVerticalClassMerging;
+
+@NoVerticalClassMerging
+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 1f686ef..fa627ef 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.dex.Constants;
 import com.android.tools.r8.graph.AccessFlags;
 import com.android.tools.r8.graph.ClassAccessFlags;
+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;
@@ -364,6 +367,16 @@
     return setAccessFlags(method, MethodAccessFlags::setBridge);
   }
 
+  public ClassFileTransformer setPrivate(Field field) {
+    return setAccessFlags(
+        field,
+        accessFlags -> {
+          accessFlags.setPrivate();
+          accessFlags.unsetProtected();
+          accessFlags.unsetPublic();
+        });
+  }
+
   public ClassFileTransformer setPublic(Method method) {
     return setAccessFlags(
         method,
@@ -393,11 +406,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() {