Add access control routines to resolution results.

Bug: 145187573
Change-Id: Ib3b83fae2bfd1a342ea544a7f0b58ffff1a132e5
diff --git a/src/main/java/com/android/tools/r8/graph/AccessControl.java b/src/main/java/com/android/tools/r8/graph/AccessControl.java
new file mode 100644
index 0000000..3b4d52f
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/AccessControl.java
@@ -0,0 +1,69 @@
+// Copyright (c) 2019, 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.graph;
+
+/**
+ * Definitions of access control routines.
+ *
+ * <p>Follows SE 11, jvm spec, section 5.4.4 on "Access Control", except for aspects related to
+ * "run-time module", for which all items are assumed to be in the same single such module.
+ */
+public class AccessControl {
+
+  public static boolean isClassAccessible(DexClass clazz, DexProgramClass context) {
+    if (clazz.accessFlags.isPublic()) {
+      return true;
+    }
+    return clazz.getType().isSamePackage(context.getType());
+  }
+
+  public static boolean isMethodAccessible(
+      DexEncodedMethod method,
+      DexClass holder,
+      DexProgramClass context,
+      AppInfoWithSubtyping appInfo) {
+    return isMemberAccessible(method.accessFlags, holder, context, appInfo);
+  }
+
+  public static boolean isFieldAccessible(
+      DexEncodedField field,
+      DexClass holder,
+      DexProgramClass context,
+      AppInfoWithSubtyping appInfo) {
+    return isMemberAccessible(field.accessFlags, holder, context, appInfo);
+  }
+
+  private static boolean isMemberAccessible(
+      AccessFlags<?> memberFlags,
+      DexClass holder,
+      DexProgramClass context,
+      AppInfoWithSubtyping appInfo) {
+    if (!isClassAccessible(holder, context)) {
+      return false;
+    }
+    if (memberFlags.isPublic()) {
+      return true;
+    }
+    if (memberFlags.isPrivate()) {
+      return isNestMate(holder, context);
+    }
+    if (holder.getType().isSamePackage(context.getType())) {
+      return true;
+    }
+    if (!memberFlags.isProtected()) {
+      return false;
+    }
+    return appInfo.isSubtype(context.getType(), holder.getType());
+  }
+
+  private static boolean isNestMate(DexClass clazz, DexProgramClass context) {
+    if (clazz == context) {
+      return true;
+    }
+    if (!clazz.isInANest() || !context.isInANest()) {
+      return false;
+    }
+    return clazz.getNestHost() == context.getNestHost();
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfo.java b/src/main/java/com/android/tools/r8/graph/AppInfo.java
index 270e91e..cf3b100 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfo.java
@@ -11,6 +11,7 @@
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.ListUtils;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableMap.Builder;
 import java.util.ArrayList;
@@ -19,6 +20,7 @@
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Map.Entry;
 import java.util.concurrent.ConcurrentHashMap;
 
 public class AppInfo implements DexDefinitionSupplier {
@@ -356,50 +358,53 @@
     assert checkIfObsolete();
     assert !clazz.isInterface();
     // Step 2:
-    DexEncodedMethod singleTarget = resolveMethodOnClassStep2(clazz, method);
-    if (singleTarget != null) {
-      return new SingleResolutionResult(singleTarget);
+    SingleResolutionResult result = resolveMethodOnClassStep2(clazz, method, clazz);
+    if (result != null) {
+      return result;
     }
     // Finally Step 3:
-    return resolveMethodStep3(clazz, method);
+    return resolveMethodStep3(clazz, method, clazz);
   }
 
   /**
-   * Implements step 2 of method resolution on classes as per
-   * <a href="https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-5.html#jvms-5.4.3.3">
-   * Section 5.4.3.3 of the JVM Spec</a>.
+   * Implements step 2 of method resolution on classes as per <a
+   * href="https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-5.html#jvms-5.4.3.3">Section
+   * 5.4.3.3 of the JVM Spec</a>.
    */
-  private DexEncodedMethod resolveMethodOnClassStep2(DexClass clazz, DexMethod method) {
+  private SingleResolutionResult resolveMethodOnClassStep2(
+      DexClass clazz, DexMethod method, DexClass initialResolutionHolder) {
     // Pt. 1: Signature polymorphic method check.
     // See also <a href="https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-2.html#jvms-2.9">
     // Section 2.9 of the JVM Spec</a>.
     DexEncodedMethod result = clazz.lookupSignaturePolymorphicMethod(method.name, dexItemFactory);
     if (result != null) {
-      return result;
+      return new SingleResolutionResult(initialResolutionHolder, clazz, result);
     }
     // Pt 2: Find a method that matches the descriptor.
     result = clazz.lookupMethod(method);
     if (result != null) {
-      return result;
+      return new SingleResolutionResult(initialResolutionHolder, clazz, result);
     }
     // Pt 3: Apply step two to direct superclass of holder.
     if (clazz.superType != null) {
       DexClass superClass = definitionFor(clazz.superType);
       if (superClass != null) {
-        return resolveMethodOnClassStep2(superClass, method);
+        return resolveMethodOnClassStep2(superClass, method, initialResolutionHolder);
       }
     }
     return null;
   }
 
   /**
-   * Implements step 3 of
-   * <a href="https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-5.html#jvms-5.4.3.3">
-   * Section 5.4.3.3 of the JVM Spec</a>. As this is the same for interfaces and classes, we share
-   * one implementation.
+   * Implements step 3 of <a
+   * href="https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-5.html#jvms-5.4.3.3">Section
+   * 5.4.3.3 of the JVM Spec</a>. As this is the same for interfaces and classes, we share one
+   * implementation.
    */
-  public ResolutionResult resolveMethodStep3(DexClass clazz, DexMethod method) {
-    MaximallySpecificMethodsBuilder builder = new MaximallySpecificMethodsBuilder();
+  public ResolutionResult resolveMethodStep3(
+      DexClass clazz, DexMethod method, DexClass initialResolutionHolder) {
+    MaximallySpecificMethodsBuilder builder =
+        new MaximallySpecificMethodsBuilder(initialResolutionHolder);
     resolveMethodStep3Helper(clazz, method, builder);
     return builder.resolve();
   }
@@ -476,7 +481,7 @@
     // Step 2: Look for exact method on interface.
     DexEncodedMethod result = definition.lookupMethod(desc);
     if (result != null) {
-      return new SingleResolutionResult(result);
+      return new SingleResolutionResult(definition, definition, result);
     }
     // Step 3: Look for matching method on object class.
     DexClass objectClass = definitionFor(dexItemFactory.objectType);
@@ -485,11 +490,11 @@
     }
     result = objectClass.lookupMethod(desc);
     if (result != null && result.accessFlags.isPublic() && !result.accessFlags.isAbstract()) {
-      return new SingleResolutionResult(result);
+      return new SingleResolutionResult(definition, objectClass, result);
     }
     // Step 3: Look for maximally-specific superinterface methods or any interface definition.
     //         This is the same for classes and interfaces.
-    return resolveMethodStep3(definition, desc);
+    return resolveMethodStep3(definition, desc, definition);
   }
 
   /**
@@ -591,6 +596,8 @@
 
   private static class MaximallySpecificMethodsBuilder {
 
+    private final DexClass initialResolutionHolder;
+
     // The set of actual maximally specific methods.
     // This set is linked map so that in the case where a number of methods remain a deterministic
     // choice can be made. The map is from definition classes to their maximally specific method, or
@@ -599,6 +606,10 @@
     // prior to writing.
     LinkedHashMap<DexClass, DexEncodedMethod> maximallySpecificMethods = new LinkedHashMap<>();
 
+    public MaximallySpecificMethodsBuilder(DexClass initialResolutionHolder) {
+      this.initialResolutionHolder = initialResolutionHolder;
+    }
+
     void addCandidate(DexClass holder, DexEncodedMethod method, AppInfo appInfo) {
       // If this candidate is already a candidate or it is shadowed, then no need to continue.
       if (maximallySpecificMethods.containsKey(holder)) {
@@ -641,32 +652,42 @@
       }
       // Fast path in the common case of a single method.
       if (maximallySpecificMethods.size() == 1) {
-        return new SingleResolutionResult(maximallySpecificMethods.values().iterator().next());
+        Entry<DexClass, DexEncodedMethod> first =
+            maximallySpecificMethods.entrySet().iterator().next();
+        return new SingleResolutionResult(
+            initialResolutionHolder, first.getKey(), first.getValue());
       }
-      DexEncodedMethod firstMaximallySpecificMethod = null;
-      List<DexEncodedMethod> nonAbstractMethods = new ArrayList<>(maximallySpecificMethods.size());
-      for (DexEncodedMethod method : maximallySpecificMethods.values()) {
+      Entry<DexClass, DexEncodedMethod> firstMaximallySpecificMethod = null;
+      List<Entry<DexClass, DexEncodedMethod>> nonAbstractMethods =
+          new ArrayList<>(maximallySpecificMethods.size());
+      for (Entry<DexClass, DexEncodedMethod> entry : maximallySpecificMethods.entrySet()) {
+        DexEncodedMethod method = entry.getValue();
         if (method == null) {
           // Ignore shadowed candidates.
           continue;
         }
         if (firstMaximallySpecificMethod == null) {
-          firstMaximallySpecificMethod = method;
+          firstMaximallySpecificMethod = entry;
         }
         if (method.isNonAbstractVirtualMethod()) {
-          nonAbstractMethods.add(method);
+          nonAbstractMethods.add(entry);
         }
       }
       // If there are no non-abstract methods, then any candidate will suffice as a target.
       // For deterministic resolution, we return the first mapped method (of the linked map).
       if (nonAbstractMethods.isEmpty()) {
-        return new SingleResolutionResult(firstMaximallySpecificMethod);
+        return new SingleResolutionResult(
+            initialResolutionHolder,
+            firstMaximallySpecificMethod.getKey(),
+            firstMaximallySpecificMethod.getValue());
       }
       // If there is exactly one non-abstract method (a default method) it is the resolution target.
       if (nonAbstractMethods.size() == 1) {
-        return new SingleResolutionResult(nonAbstractMethods.get(0));
+        Entry<DexClass, DexEncodedMethod> entry = nonAbstractMethods.get(0);
+        return new SingleResolutionResult(
+            initialResolutionHolder, entry.getKey(), entry.getValue());
       }
-      return IncompatibleClassResult.create(nonAbstractMethods);
+      return IncompatibleClassResult.create(ListUtils.map(nonAbstractMethods, Entry::getValue));
     }
   }
 
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 1f8343e..16dff0d 100644
--- a/src/main/java/com/android/tools/r8/graph/ResolutionResult.java
+++ b/src/main/java/com/android/tools/r8/graph/ResolutionResult.java
@@ -27,6 +27,11 @@
 
   public abstract boolean hasSingleTarget();
 
+  public abstract boolean isAccessibleFrom(DexProgramClass context, AppInfoWithSubtyping appInfo);
+
+  public abstract boolean isAccessibleForVirtualDispatchFrom(
+      DexProgramClass context, AppInfoWithSubtyping appInfo);
+
   public abstract boolean isValidVirtualTarget(InternalOptions options);
 
   public abstract boolean isValidVirtualTargetForDynamicDispatch();
@@ -136,7 +141,9 @@
   }
 
   public static class SingleResolutionResult extends ResolutionResult {
-    final DexEncodedMethod resolutionTarget;
+    final DexClass initialResolutionHolder;
+    final DexClass targetHolder;
+    final DexEncodedMethod targetMethod;
 
     public static boolean isValidVirtualTarget(InternalOptions options, DexEncodedMethod target) {
       return options.canUseNestBasedAccess()
@@ -144,24 +151,46 @@
           : target.isVirtualMethod();
     }
 
-    public SingleResolutionResult(DexEncodedMethod resolutionTarget) {
-      assert resolutionTarget != null;
-      this.resolutionTarget = resolutionTarget;
+    public SingleResolutionResult(
+        DexClass initialResolutionHolder, DexClass targetHolder, DexEncodedMethod targetMethod) {
+      assert initialResolutionHolder != null;
+      assert targetHolder != null;
+      assert targetMethod != null;
+      assert targetHolder.type == targetMethod.method.holder;
+      this.targetHolder = targetHolder;
+      this.targetMethod = targetMethod;
+      this.initialResolutionHolder = initialResolutionHolder;
+    }
+
+    @Override
+    public boolean isAccessibleFrom(DexProgramClass context, AppInfoWithSubtyping appInfo) {
+      return AccessControl.isMethodAccessible(
+          targetMethod, initialResolutionHolder, context, appInfo);
+    }
+
+    @Override
+    public boolean isAccessibleForVirtualDispatchFrom(
+        DexProgramClass context, AppInfoWithSubtyping appInfo) {
+      // If a private method is accessible (which implies it is via its nest), then it is a valid
+      // virtual dispatch target if non-static.
+      return isAccessibleFrom(context, appInfo)
+          && (targetMethod.isVirtualMethod()
+              || (targetMethod.isPrivateMethod() && !targetMethod.isStatic()));
     }
 
     @Override
     public boolean isValidVirtualTarget(InternalOptions options) {
-      return isValidVirtualTarget(options, resolutionTarget);
+      return isValidVirtualTarget(options, targetMethod);
     }
 
     @Override
     public boolean isValidVirtualTargetForDynamicDispatch() {
-      return resolutionTarget.isVirtualMethod();
+      return targetMethod.isVirtualMethod();
     }
 
     @Override
     public DexEncodedMethod getSingleTarget() {
-      return resolutionTarget;
+      return targetMethod;
     }
 
     @Override
@@ -202,6 +231,17 @@
     }
 
     @Override
+    public boolean isAccessibleFrom(DexProgramClass context, AppInfoWithSubtyping appInfo) {
+      return true;
+    }
+
+    @Override
+    public boolean isAccessibleForVirtualDispatchFrom(
+        DexProgramClass context, AppInfoWithSubtyping appInfo) {
+      return true;
+    }
+
+    @Override
     public boolean isValidVirtualTarget(InternalOptions options) {
       return true;
     }
@@ -229,6 +269,17 @@
     }
 
     @Override
+    public boolean isAccessibleFrom(DexProgramClass context, AppInfoWithSubtyping appInfo) {
+      return false;
+    }
+
+    @Override
+    public boolean isAccessibleForVirtualDispatchFrom(
+        DexProgramClass context, AppInfoWithSubtyping appInfo) {
+      return false;
+    }
+
+    @Override
     public boolean isValidVirtualTarget(InternalOptions options) {
       return false;
     }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java
index 6881dbc..e559247 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java
@@ -321,7 +321,11 @@
     // If target is a non-interface library class it may be an emulated interface.
     if (!libraryHolder.isInterface()) {
       // Here we use step-3 of resolution to find a maximally specific default interface method.
-      target = appView.appInfo().resolveMethodStep3(libraryHolder, method).getSingleTarget();
+      target =
+          appView
+              .appInfo()
+              .resolveMethodStep3(libraryHolder, method, libraryHolder)
+              .getSingleTarget();
       if (target != null && rewriter.isEmulatedInterface(target.method.holder)) {
         targetHolder = appView.definitionFor(target.method.holder);
         addForward.accept(targetHolder, target);
diff --git a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
index 708d328..e2641d1 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -29,7 +29,6 @@
 import com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult;
 import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
 import com.android.tools.r8.ir.code.Invoke.Type;
-import com.android.tools.r8.ir.optimize.NestUtils;
 import com.android.tools.r8.utils.CollectionUtils;
 import com.android.tools.r8.utils.SetUtils;
 import com.google.common.collect.ImmutableList;
@@ -1017,9 +1016,24 @@
       DexType refinedReceiverType,
       ClassTypeLatticeElement receiverLowerBoundType) {
     assert checkIfObsolete();
-    DexEncodedMethod directResult = nestAccessLookup(method, invocationContext);
-    if (directResult != null) {
-      return directResult;
+    // TODO: replace invocationContext by a DexProgramClass typed formal.
+    DexProgramClass invocationClass = asProgramClassOrNull(definitionFor(invocationContext));
+    assert invocationClass != null;
+
+    ResolutionResult resolutionResult = resolveMethodOnClass(method.holder, method);
+    if (!resolutionResult.isAccessibleForVirtualDispatchFrom(invocationClass, this)) {
+      return null;
+    }
+
+    DexEncodedMethod topTarget = resolutionResult.getSingleTarget();
+    if (topTarget == null) {
+      // A null target represents a valid target without a known defintion, ie, array clone().
+      return null;
+    }
+
+    // If the target is a private method, then the invocation is a direct access to a nest member.
+    if (topTarget.isPrivateMethod()) {
+      return topTarget;
     }
 
     // If the lower-bound on the receiver type is the same as the upper-bound, then we have exact
@@ -1027,7 +1041,6 @@
     // from the runtime type of the receiver.
     if (receiverLowerBoundType != null) {
       if (receiverLowerBoundType.getClassType() == refinedReceiverType) {
-        ResolutionResult resolutionResult = resolveMethod(method.holder, method, false);
         if (resolutionResult.hasSingleTarget()
             && resolutionResult.isValidVirtualTargetForDynamicDispatch()) {
           ResolutionResult refinedResolutionResult = resolveMethod(refinedReceiverType, method);
@@ -1049,14 +1062,13 @@
     assert method != null;
     assert isSubtype(refinedReceiverType, method.holder);
     if (method.holder.isArrayType()) {
-      // For javac output this will only be clone(), but in general the methods from Object can
-      // be invoked with an array type holder.
       return null;
     }
     DexClass holder = definitionFor(method.holder);
-    if (holder == null || holder.isNotProgramClass() || holder.isInterface()) {
+    if (holder == null || holder.isNotProgramClass()) {
       return null;
     }
+    assert !holder.isInterface();
     boolean refinedReceiverIsStrictSubType = refinedReceiverType != method.holder;
     DexProgramClass refinedHolder =
         (refinedReceiverIsStrictSubType ? definitionFor(refinedReceiverType) : holder)
@@ -1098,21 +1110,6 @@
     return result;
   }
 
-  private DexEncodedMethod nestAccessLookup(DexMethod method, DexType invocationContext) {
-    if (method.holder == invocationContext || !definitionFor(invocationContext).isInANest()) {
-      return null;
-    }
-    DexEncodedMethod directTarget = lookupDirectTarget(method);
-    assert directTarget == null || directTarget.method.holder == method.holder;
-    if (directTarget != null
-        && directTarget.isPrivateMethod()
-        && NestUtils.sameNest(method.holder, invocationContext, this)) {
-      return directTarget;
-    }
-
-    return null;
-  }
-
   /**
    * Computes which methods overriding <code>method</code> are visible for the subtypes of type.
    *
@@ -1218,10 +1215,9 @@
       DexType refinedReceiverType,
       ClassTypeLatticeElement receiverLowerBoundType) {
     assert checkIfObsolete();
-    DexEncodedMethod directResult = nestAccessLookup(method, invocationContext);
-    if (directResult != null) {
-      return directResult;
-    }
+    // Replace DexType invocationContext by DexProgramClass throughout.
+    DexProgramClass invocationClass = asProgramClassOrNull(definitionFor(invocationContext));
+    assert invocationClass != null;
 
     // If the lower-bound on the receiver type is the same as the upper-bound, then we have exact
     // runtime type information. In this case, the invoke will dispatch to the resolution result
@@ -1249,13 +1245,24 @@
     if (holder == null || !holder.accessFlags.isInterface()) {
       return null;
     }
-    // First check that there is a target for this invoke-interface to hit. If there is none,
-    // this will fail at runtime.
-    DexEncodedMethod topTarget = resolveMethodOnInterface(holder, method).getSingleTarget();
-    if (topTarget == null || !SingleResolutionResult.isValidVirtualTarget(options(), topTarget)) {
+    // First check that there is a visible and valid target for this invoke-interface to hit.
+    // If there is none, this will fail at runtime.
+    ResolutionResult topResolution = resolveMethodOnInterface(holder, method);
+    if (!topResolution.isAccessibleForVirtualDispatchFrom(invocationClass, this)) {
       return null;
     }
 
+    DexEncodedMethod topTarget = topResolution.getSingleTarget();
+    if (topTarget == null) {
+      // An null target represents a valid target with no known defintion, eg, array clone().
+      return null;
+    }
+
+    // If the target is a private method, then the invocation is a direct access to a nest member.
+    if (topTarget.isPrivateMethod()) {
+      return topTarget;
+    }
+
     // If the invoke could target a method in a class that is not visible to R8, then give up.
     if (canVirtualMethodBeImplementedInExtraSubclass(holder, method)) {
       return null;
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 a9f60ec..a053f3d 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -2022,7 +2022,7 @@
     assert interfaceInvoke == holder.isInterface();
     Set<DexEncodedMethod> possibleTargets =
         // TODO(b/140214802): Call on the resolution once proper resolution and lookup is resolved.
-        new SingleResolutionResult(resolution.method)
+        new SingleResolutionResult(holder, resolution.holder, resolution.method)
             .lookupVirtualDispatchTargets(interfaceInvoke, appInfo);
     if (possibleTargets == null || possibleTargets.isEmpty()) {
       return;
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index 57eae6b..cefac4f 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -27,6 +27,7 @@
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexCode;
 import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProto;
@@ -34,6 +35,7 @@
 import com.android.tools.r8.graph.SmaliWriter;
 import com.android.tools.r8.jasmin.JasminBuilder;
 import com.android.tools.r8.origin.Origin;
+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.references.TypeReference;
@@ -75,6 +77,7 @@
 import java.io.FileInputStream;
 import java.io.FileOutputStream;
 import java.io.IOException;
+import java.lang.reflect.Field;
 import java.lang.reflect.Method;
 import java.lang.reflect.Modifier;
 import java.nio.charset.StandardCharsets;
@@ -588,6 +591,17 @@
     return factory.createType(type.getDescriptor());
   }
 
+  protected static DexField buildField(Field field, DexItemFactory factory) {
+    return buildField(Reference.fieldFromField(field), factory);
+  }
+
+  protected static DexField buildField(FieldReference field, DexItemFactory factory) {
+    return factory.createField(
+        buildType(field.getHolderClass(), factory),
+        buildType(field.getFieldType(), factory),
+        field.getFieldName());
+  }
+
   protected static DexMethod buildMethod(Method method, DexItemFactory factory) {
     return buildMethod(Reference.methodFromMethod(method), factory);
   }
diff --git a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestMethodInlinedTest.java b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestMethodInlinedTest.java
index d448e1e..4b5e452 100644
--- a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestMethodInlinedTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestMethodInlinedTest.java
@@ -40,6 +40,15 @@
   }
 
   @Test
+  public void testReference() throws Exception {
+    testForRuntime(parameters)
+        .addProgramFiles(classesMatching("NestPvtMethodCallInlined"))
+        .run(parameters.getRuntime(), getMainClass("pvtCallInlined"))
+        .disassemble()
+        .assertSuccessWithOutput(getExpectedResult("pvtCallInlined"));
+  }
+
+  @Test
   public void testPvtMethodCallInlined() throws Exception {
     List<Path> toCompile = classesMatching("NestPvtMethodCallInlined");
     testForR8(parameters.getBackend())
diff --git a/src/test/java/com/android/tools/r8/resolution/access/NestStaticMethodAccessTest.java b/src/test/java/com/android/tools/r8/resolution/access/NestStaticMethodAccessTest.java
index 63c6161..5c1d7a8 100644
--- a/src/test/java/com/android/tools/r8/resolution/access/NestStaticMethodAccessTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/access/NestStaticMethodAccessTest.java
@@ -14,7 +14,6 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.ResolutionResult;
-import com.android.tools.r8.ir.optimize.NestUtils;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.transformers.ClassFileTransformer;
 import com.android.tools.r8.utils.BooleanUtils;
@@ -71,10 +70,7 @@
         appInfo.definitionFor(buildType(B.class, appInfo.dexItemFactory())).asProgramClass();
     DexMethod bar = buildMethod(A.class.getDeclaredMethod("bar"), appInfo.dexItemFactory());
     ResolutionResult resolutionResult = appInfo.resolveMethod(bar.holder, bar);
-    // TODO(b/145187573): Update to check the full access control once possible.
-    assertEquals(
-        inSameNest,
-        NestUtils.sameNest(bClass.type, resolutionResult.getSingleTarget().method.holder, appView));
+    assertEquals(inSameNest, resolutionResult.isAccessibleFrom(bClass, appInfo));
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/resolution/access/NestStaticMethodAccessWithIntermediateClassTest.java b/src/test/java/com/android/tools/r8/resolution/access/NestStaticMethodAccessWithIntermediateClassTest.java
index a487f40..bd6dbed 100644
--- a/src/test/java/com/android/tools/r8/resolution/access/NestStaticMethodAccessWithIntermediateClassTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/access/NestStaticMethodAccessWithIntermediateClassTest.java
@@ -14,7 +14,6 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.ResolutionResult;
-import com.android.tools.r8.ir.optimize.NestUtils;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.transformers.ClassFileTransformer;
 import com.android.tools.r8.utils.BooleanUtils;
@@ -73,10 +72,7 @@
         appInfo.definitionFor(buildType(B.class, appInfo.dexItemFactory())).asProgramClass();
     DexMethod bar = buildMethod(A.class.getDeclaredMethod("bar"), appInfo.dexItemFactory());
     ResolutionResult resolutionResult = appInfo.resolveMethod(bar.holder, bar);
-    // TODO(b/145187573): Update to check the full access control once possible.
-    assertEquals(
-        inSameNest,
-        NestUtils.sameNest(bClass.type, resolutionResult.getSingleTarget().method.holder, appView));
+    assertEquals(inSameNest, resolutionResult.isAccessibleFrom(bClass, appInfo));
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/resolution/access/NestVirtualMethodAccessTest.java b/src/test/java/com/android/tools/r8/resolution/access/NestVirtualMethodAccessTest.java
index 59267b2..69cdad5 100644
--- a/src/test/java/com/android/tools/r8/resolution/access/NestVirtualMethodAccessTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/access/NestVirtualMethodAccessTest.java
@@ -14,7 +14,6 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.ResolutionResult;
-import com.android.tools.r8.ir.optimize.NestUtils;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.transformers.ClassFileTransformer;
 import com.android.tools.r8.utils.BooleanUtils;
@@ -73,10 +72,7 @@
         appInfo.definitionFor(buildType(B.class, appInfo.dexItemFactory())).asProgramClass();
     DexMethod bar = buildMethod(A.class.getDeclaredMethod("bar"), appInfo.dexItemFactory());
     ResolutionResult resolutionResult = appInfo.resolveMethod(bar.holder, bar);
-    // TODO(b/145187573): Update to check the full access control once possible.
-    assertEquals(
-        inSameNest,
-        NestUtils.sameNest(bClass.type, resolutionResult.getSingleTarget().method.holder, appView));
+    assertEquals(inSameNest, resolutionResult.isAccessibleFrom(bClass, appInfo));
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/resolution/access/NestVirtualMethodAccessWithIntermediateClassTest.java b/src/test/java/com/android/tools/r8/resolution/access/NestVirtualMethodAccessWithIntermediateClassTest.java
index 68cc59e..d6343fc 100644
--- a/src/test/java/com/android/tools/r8/resolution/access/NestVirtualMethodAccessWithIntermediateClassTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/access/NestVirtualMethodAccessWithIntermediateClassTest.java
@@ -17,7 +17,6 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.ResolutionResult;
-import com.android.tools.r8.ir.optimize.NestUtils;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.transformers.ClassFileTransformer;
 import com.android.tools.r8.utils.BooleanUtils;
@@ -76,10 +75,7 @@
         appInfo.definitionFor(buildType(B.class, appInfo.dexItemFactory())).asProgramClass();
     DexMethod bar = buildMethod(A.class.getDeclaredMethod("bar"), appInfo.dexItemFactory());
     ResolutionResult resolutionResult = appInfo.resolveMethod(bar.holder, bar);
-    // TODO(b/145187573): Update to check the full access control once possible.
-    assertEquals(
-        inSameNest,
-        NestUtils.sameNest(bClass.type, resolutionResult.getSingleTarget().method.holder, appView));
+    assertEquals(inSameNest, resolutionResult.isAccessibleFrom(bClass, appInfo));
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/resolution/access/SelfVirtualMethodAccessTest.java b/src/test/java/com/android/tools/r8/resolution/access/SelfVirtualMethodAccessTest.java
new file mode 100644
index 0000000..a997622
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/resolution/access/SelfVirtualMethodAccessTest.java
@@ -0,0 +1,108 @@
+// Copyright (c) 2019, 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.resolution.access;
+
+import static org.hamcrest.CoreMatchers.containsString;
+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.graph.AppView;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.ResolutionResult;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableList;
+import java.util.Collection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class SelfVirtualMethodAccessTest extends TestBase {
+
+  static final String EXPECTED = StringUtils.lines("A::bar");
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public SelfVirtualMethodAccessTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  public Collection<Class<?>> getClasses() {
+    return ImmutableList.of(Main.class);
+  }
+
+  public Collection<byte[]> getTransformedClasses() throws Exception {
+    return ImmutableList.of(
+        transformer(A.class).setPrivate(A.class.getDeclaredMethod("bar")).transform());
+  }
+
+  @Test
+  public void testResolutionAccess() throws Exception {
+    AppView<AppInfoWithLiveness> appView =
+        computeAppViewWithLiveness(
+            buildClasses(getClasses()).addClassProgramData(getTransformedClasses()).build(),
+            Main.class);
+    AppInfoWithLiveness appInfo = appView.appInfo();
+    DexProgramClass aClass =
+        appInfo.definitionFor(buildType(A.class, appInfo.dexItemFactory())).asProgramClass();
+    DexMethod bar = buildMethod(A.class.getDeclaredMethod("bar"), appInfo.dexItemFactory());
+    ResolutionResult resolutionResult = appInfo.resolveMethod(bar.holder, bar);
+    assertTrue(resolutionResult.isAccessibleFrom(aClass, appInfo));
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForRuntime(parameters)
+        .addProgramClasses(getClasses())
+        .addProgramClassFileData(getTransformedClasses())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(getClasses())
+        .addProgramClassFileData(getTransformedClasses())
+        .setMinApi(parameters.getApiLevel())
+        .addKeepMainRule(Main.class)
+        .run(parameters.getRuntime(), Main.class)
+        .apply(
+            result -> {
+              if (parameters.isCfRuntime()) {
+                result.assertSuccessWithOutput(EXPECTED);
+              } else {
+                // TODO(b/145187969): R8 compiles an incorrect program.
+                result.assertFailureWithErrorThatMatches(
+                    containsString(NullPointerException.class.getName()));
+              }
+            });
+  }
+
+  static class A {
+    /* will be private */ void bar() {
+      System.out.println("A::bar");
+    }
+
+    public void foo() {
+      // Virtual invoke to private method.
+      bar();
+    }
+  }
+
+  static class Main {
+    public static void main(String[] args) {
+      new A().foo();
+    }
+  }
+}
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
new file mode 100644
index 0000000..e517531
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/resolution/access/indirectfield/IndirectFieldAccessTest.java
@@ -0,0 +1,105 @@
+// Copyright (c) 2019, 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.resolution.access.indirectfield;
+
+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.TestRunResult;
+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.DexEncodedField;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.resolution.access.indirectfield.pkg.C;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.StringUtils;
+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 IndirectFieldAccessTest extends TestBase {
+
+  static final String EXPECTED = StringUtils.lines("42");
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public IndirectFieldAccessTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  public List<Class<?>> getClasses() {
+    return ImmutableList.of(Main.class, A.class, B.class, C.class);
+  }
+
+  @Test
+  public void testResolutionAccess() throws Exception {
+    AppView<AppInfoWithLiveness> appView =
+        computeAppViewWithLiveness(readClasses(getClasses()), Main.class);
+    AppInfoWithLiveness appInfo = appView.appInfo();
+    DexProgramClass cClass =
+        appInfo.definitionFor(buildType(C.class, appInfo.dexItemFactory())).asProgramClass();
+    DexField f =
+        buildField(
+            // 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());
+    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));
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForRuntime(parameters)
+        .addProgramClasses(getClasses())
+        .run(parameters.getRuntime(), Main.class)
+        .disassemble()
+        .apply(this::checkExpectedResult);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(getClasses())
+        .setMinApi(parameters.getApiLevel())
+        .addKeepMainRule(Main.class)
+        .run(parameters.getRuntime(), Main.class)
+        .apply(this::checkExpectedResult);
+  }
+
+  private void checkExpectedResult(TestRunResult<?> result) {
+    result.assertSuccessWithOutput(EXPECTED);
+  }
+
+  /* non-public */ static class A {
+    public int f = 42;
+  }
+
+  public static class B extends A {
+    // Intentionally emtpy.
+    // Provides access to A.f from outside this package, eg, from pkg.C::bar().
+  }
+
+  static class Main {
+    public static void main(String[] args) {
+      new C().bar();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/resolution/access/indirectfield/pkg/C.java b/src/test/java/com/android/tools/r8/resolution/access/indirectfield/pkg/C.java
new file mode 100644
index 0000000..9094062
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/resolution/access/indirectfield/pkg/C.java
@@ -0,0 +1,12 @@
+// Copyright (c) 2019, 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.resolution.access.indirectfield.pkg;
+
+import com.android.tools.r8.resolution.access.indirectfield.IndirectFieldAccessTest.B;
+
+public class C {
+  public void bar() {
+    System.out.println(new B().f);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/resolution/access/indirectmethod/IndirectMethodAccessTest.java b/src/test/java/com/android/tools/r8/resolution/access/indirectmethod/IndirectMethodAccessTest.java
new file mode 100644
index 0000000..1d75a8b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/resolution/access/indirectmethod/IndirectMethodAccessTest.java
@@ -0,0 +1,108 @@
+// Copyright (c) 2019, 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.resolution.access.indirectmethod;
+
+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.TestRunResult;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.ResolutionResult;
+import com.android.tools.r8.resolution.access.indirectmethod.pkg.C;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableList;
+import java.io.IOException;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class IndirectMethodAccessTest extends TestBase {
+
+  static final String EXPECTED = StringUtils.lines("A::foo");
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public IndirectMethodAccessTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  public List<Class<?>> getClasses() {
+    return ImmutableList.of(Main.class, A.class, C.class);
+  }
+
+  private List<byte[]> getTransforms() throws IOException {
+    return ImmutableList.of(
+        // Compilation with javac will generate a synthetic bridge for foo. Remove it.
+        transformer(B.class)
+            .removeMethods((access, name, descriptor, signature, exceptions) -> name.equals("foo"))
+            .transform());
+  }
+
+  @Test
+  public void testResolutionAccess() throws Exception {
+    AppView<AppInfoWithLiveness> appView =
+        computeAppViewWithLiveness(
+            buildClasses(getClasses()).addClassProgramData(getTransforms()).build(), Main.class);
+    AppInfoWithLiveness appInfo = appView.appInfo();
+    DexProgramClass cClass =
+        appInfo.definitionFor(buildType(C.class, appInfo.dexItemFactory())).asProgramClass();
+    DexMethod bar = buildMethod(B.class.getMethod("foo"), appInfo.dexItemFactory());
+    ResolutionResult resolutionResult = appInfo.resolveMethod(bar.holder, bar);
+    assertTrue(resolutionResult.isAccessibleForVirtualDispatchFrom(cClass, appInfo));
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForRuntime(parameters)
+        .addProgramClasses(getClasses())
+        .addProgramClassFileData(getTransforms())
+        .run(parameters.getRuntime(), Main.class)
+        .disassemble()
+        .apply(this::checkExpectedResult);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(getClasses())
+        .addProgramClassFileData(getTransforms())
+        .setMinApi(parameters.getApiLevel())
+        .addKeepMainRule(Main.class)
+        .run(parameters.getRuntime(), Main.class)
+        .apply(this::checkExpectedResult);
+  }
+
+  private void checkExpectedResult(TestRunResult<?> result) {
+    result.assertSuccessWithOutput(EXPECTED);
+  }
+
+  /* non-public */ static class A {
+    public void foo() {
+      System.out.println("A::foo");
+    }
+  }
+
+  public static class B extends A {
+    // Intentionally emtpy.
+    // Provides access to A.foo from outside this package, eg, from pkg.C::bar().
+  }
+
+  static class Main {
+    public static void main(String[] args) {
+      new C().bar();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/resolution/access/indirectmethod/pkg/C.java b/src/test/java/com/android/tools/r8/resolution/access/indirectmethod/pkg/C.java
new file mode 100644
index 0000000..2ff8db8
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/resolution/access/indirectmethod/pkg/C.java
@@ -0,0 +1,12 @@
+// Copyright (c) 2019, 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.resolution.access.indirectmethod.pkg;
+
+import com.android.tools.r8.resolution.access.indirectmethod.IndirectMethodAccessTest.B;
+
+public class C {
+  public void bar() {
+    new B().foo();
+  }
+}
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 09faf46..461323d 100644
--- a/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
+++ b/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
@@ -134,7 +134,7 @@
     return this;
   }
 
-  /** Base addtion of a transformer on methods. */
+  /** Base addition of a transformer on methods. */
   public ClassFileTransformer addMethodTransformer(MethodTransformer transformer) {
     methodTransformers.add(transformer);
     return this;
@@ -263,6 +263,24 @@
         });
   }
 
+  @FunctionalInterface
+  public interface MethodPredicate {
+    boolean test(int access, String name, String descriptor, String signature, String[] exceptions);
+  }
+
+  public ClassFileTransformer removeMethods(MethodPredicate predicate) {
+    return addClassTransformer(
+        new ClassTransformer() {
+          @Override
+          public MethodVisitor visitMethod(
+              int access, String name, String descriptor, String signature, String[] exceptions) {
+            return predicate.test(access, name, descriptor, signature, exceptions)
+                ? null
+                : super.visitMethod(access, name, descriptor, signature, exceptions);
+          }
+        });
+  }
+
   /** Abstraction of the MethodVisitor.visitMethodInsn method with its continuation. */
   @FunctionalInterface
   public interface MethodInsnTransform {