Version 1.4.36

Cherry-pick:
Enhance visibility check when rebinding methods.
CL: https://r8-review.googlesource.com/c/r8/+/33800

Cherry-pick:
Reproduce b/123730538: method rebinding missed calling context.
CL: https://r8-review.googlesource.com/c/r8/+/33741

Bug: 111622837, 123730538
Change-Id: Id39992913b81132f17946a9a77c996c1b777c6db
diff --git a/src/main/java/com/android/tools/r8/Version.java b/src/main/java/com/android/tools/r8/Version.java
index 558540a..6ebdc7c 100644
--- a/src/main/java/com/android/tools/r8/Version.java
+++ b/src/main/java/com/android/tools/r8/Version.java
@@ -11,7 +11,7 @@
 
   // This field is accessed from release scripts using simple pattern matching.
   // Therefore, changing this field could break our release scripts.
-  public static final String LABEL = "1.4.35";
+  public static final String LABEL = "1.4.36";
 
   private Version() {
   }
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
index 4db858b..0d05021 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -640,7 +640,6 @@
 
   public DexEncodedMethod toForwardingMethod(DexClass holder, DexItemFactory itemFactory) {
     checkIfObsolete();
-    assert accessFlags.isPublic();
     // Clear the final flag, as this method is now overwritten. Do this before creating the builder
     // for the forwarding method, as the forwarding method will copy the access flags from this,
     // and if different forwarding methods are created in different subclasses the first could be
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 3c300b6..608f915 100644
--- a/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
+++ b/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
@@ -120,8 +120,10 @@
   }
 
   private void computeMethodRebinding(
-      Set<DexMethod> methods, Function<DexMethod, DexEncodedMethod> lookupTarget, Type invokeType) {
-    for (DexMethod method : methods) {
+      Map<DexMethod, Set<DexEncodedMethod>> methodsWithContexts,
+      Function<DexMethod, DexEncodedMethod> lookupTarget,
+      Type invokeType) {
+    for (DexMethod method : methodsWithContexts.keySet()) {
       // We can safely ignore array types, as the corresponding methods are defined in a library.
       if (!method.getHolder().isClassType()) {
         continue;
@@ -149,7 +151,10 @@
 
         // If the target class is not public but the targeted method is, we might run into
         // visibility problems when rebinding.
-        if (mayNeedBridgeForVisibility(target, targetClass)) {
+        final DexEncodedMethod finalTarget = target;
+        Set<DexEncodedMethod> contexts = methodsWithContexts.get(method);
+        if (contexts.stream().anyMatch(context ->
+            mayNeedBridgeForVisibility(context.method.getHolder(), finalTarget))) {
           target =
               insertBridgeForVisibilityIfNeeded(
                   method, target, originalClass, targetClass, lookupTarget);
@@ -205,8 +210,20 @@
     return findHolderForInterfaceMethodBridge(superClass.asProgramClass(), iface);
   }
 
-  private boolean mayNeedBridgeForVisibility(DexEncodedMethod target, DexClass targetClass) {
-    return !targetClass.accessFlags.isPublic() && target.accessFlags.isPublic();
+  private boolean mayNeedBridgeForVisibility(DexType context, DexEncodedMethod method) {
+    DexType holderType = method.method.getHolder();
+    DexClass holder = appInfo.definitionFor(holderType);
+    if (holder == null) {
+      return false;
+    }
+    ConstraintWithTarget classVisibility =
+        ConstraintWithTarget.deriveConstraint(context, holderType, holder.accessFlags, appInfo);
+    ConstraintWithTarget methodVisibility =
+        ConstraintWithTarget.deriveConstraint(context, holderType, method.accessFlags, appInfo);
+    // We may need bridge for visibility if the target class is not visible while the target method
+    // is visible from the calling context.
+    return classVisibility == ConstraintWithTarget.NEVER
+        && methodVisibility != ConstraintWithTarget.NEVER;
   }
 
   private DexEncodedMethod insertBridgeForVisibilityIfNeeded(
@@ -263,15 +280,15 @@
     return null;
   }
 
-  private void computeFieldRebinding(Map<DexField, Set<DexEncodedMethod>> fields,
+  private void computeFieldRebinding(
+      Map<DexField, Set<DexEncodedMethod>> fieldsWithContexts,
       BiFunction<DexType, DexField, DexEncodedField> lookup,
       BiFunction<DexClass, DexField, DexEncodedField> lookupTargetOnClass) {
-    for (Map.Entry<DexField, Set<DexEncodedMethod>> entry : fields.entrySet()) {
-      DexField field = entry.getKey();
+    for (DexField field : fieldsWithContexts.keySet()) {
       DexEncodedField target = lookup.apply(field.getHolder(), field);
       // Rebind to the lowest library class or program class. Do not rebind accesses to fields that
       // are not visible from the access context.
-      Set<DexEncodedMethod> contexts = entry.getValue();
+      Set<DexEncodedMethod> contexts = fieldsWithContexts.get(field);
       if (target != null && target.field != field
           && contexts.stream().allMatch(context ->
               isVisibleFromOriginalContext(context.method.getHolder(), target))) {
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 55ceee8..a51ca83 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -88,7 +88,9 @@
 import java.util.Objects;
 import java.util.Queue;
 import java.util.Set;
+import java.util.SortedMap;
 import java.util.SortedSet;
+import java.util.TreeMap;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
 import java.util.function.BiConsumer;
@@ -116,12 +118,16 @@
   private RootSet rootSet;
   private ProguardClassFilter dontWarnPatterns;
 
-  private final Map<DexType, Set<DexMethod>> virtualInvokes = Maps.newIdentityHashMap();
-  private final Map<DexType, Set<DexMethod>> interfaceInvokes = Maps.newIdentityHashMap();
+  private final Map<DexType, Set<TargetWithContext<DexMethod>>> virtualInvokes =
+      Maps.newIdentityHashMap();
+  private final Map<DexType, Set<TargetWithContext<DexMethod>>> interfaceInvokes =
+      Maps.newIdentityHashMap();
   private final Map<DexType, Set<TargetWithContext<DexMethod>>> superInvokes =
       Maps.newIdentityHashMap();
-  private final Map<DexType, Set<DexMethod>> directInvokes = Maps.newIdentityHashMap();
-  private final Map<DexType, Set<DexMethod>> staticInvokes = Maps.newIdentityHashMap();
+  private final Map<DexType, Set<TargetWithContext<DexMethod>>> directInvokes =
+      Maps.newIdentityHashMap();
+  private final Map<DexType, Set<TargetWithContext<DexMethod>>> staticInvokes =
+      Maps.newIdentityHashMap();
   private final Map<DexType, Set<TargetWithContext<DexField>>> instanceFieldsWritten =
       Maps.newIdentityHashMap();
   private final Map<DexType, Set<TargetWithContext<DexField>>> instanceFieldsRead =
@@ -439,7 +445,7 @@
         // Revisit the current method to implicitly add -keep rule for items with reflective access.
         pendingReflectiveUses.add(currentMethod);
       }
-      if (!registerItemWithTarget(virtualInvokes, method)) {
+      if (!registerItemWithTargetAndContext(virtualInvokes, method, currentMethod)) {
         return false;
       }
       if (Log.ENABLED) {
@@ -455,7 +461,7 @@
     }
 
     boolean registerInvokeDirect(DexMethod method, KeepReason keepReason) {
-      if (!registerItemWithTarget(directInvokes, method)) {
+      if (!registerItemWithTargetAndContext(directInvokes, method, currentMethod)) {
         return false;
       }
       if (Log.ENABLED) {
@@ -482,7 +488,7 @@
       if (method == appInfo.dexItemFactory.enumMethods.valueOf) {
         pendingReflectiveUses.add(currentMethod);
       }
-      if (!registerItemWithTarget(staticInvokes, method)) {
+      if (!registerItemWithTargetAndContext(staticInvokes, method, currentMethod)) {
         return false;
       }
       if (Log.ENABLED) {
@@ -498,7 +504,7 @@
     }
 
     boolean registerInvokeInterface(DexMethod method, KeepReason keepReason) {
-      if (!registerItemWithTarget(interfaceInvokes, method)) {
+      if (!registerItemWithTargetAndContext(interfaceInvokes, method, currentMethod)) {
         return false;
       }
       if (Log.ENABLED) {
@@ -1629,34 +1635,18 @@
     }
   }
 
-  private Map<DexField, Set<DexEncodedMethod>> collectFields(
-      Map<DexType, Set<TargetWithContext<DexField>>> map) {
-    Map<DexField, Set<DexEncodedMethod>> result = new IdentityHashMap<>();
-    for (Entry<DexType, Set<TargetWithContext<DexField>>> entry : map.entrySet()) {
-      for (TargetWithContext<DexField> fieldWithContext : entry.getValue()) {
-        DexField field = fieldWithContext.getTarget();
-        DexEncodedMethod context = fieldWithContext.getContext();
-        result.computeIfAbsent(field, k -> Sets.newIdentityHashSet())
+  <T extends Descriptor<?, T>> SortedMap<T, Set<DexEncodedMethod>> collectDescriptors(
+      Map<DexType, Set<TargetWithContext<T>>> map) {
+    SortedMap<T, Set<DexEncodedMethod>> result = new TreeMap<>(PresortedComparable::slowCompare);
+    for (Entry<DexType, Set<TargetWithContext<T>>> entry : map.entrySet()) {
+      for (TargetWithContext<T> descriptorWithContext : entry.getValue()) {
+        T descriptor = descriptorWithContext.getTarget();
+        DexEncodedMethod context = descriptorWithContext.getContext();
+        result.computeIfAbsent(descriptor, k -> Sets.newIdentityHashSet())
             .add(context);
       }
     }
-    return result;
-  }
-
-  Map<DexField, Set<DexEncodedMethod>> collectInstanceFieldsRead() {
-    return Collections.unmodifiableMap(collectFields(instanceFieldsRead));
-  }
-
-  Map<DexField, Set<DexEncodedMethod>> collectInstanceFieldsWritten() {
-    return Collections.unmodifiableMap(collectFields(instanceFieldsWritten));
-  }
-
-  Map<DexField, Set<DexEncodedMethod>> collectStaticFieldsRead() {
-    return Collections.unmodifiableMap(collectFields(staticFieldsRead));
-  }
-
-  Map<DexField, Set<DexEncodedMethod>> collectStaticFieldsWritten() {
-    return Collections.unmodifiableMap(collectFields(staticFieldsWritten));
+    return Collections.unmodifiableSortedMap(result);
   }
 
   private Set<DexField> collectReachedFields(
@@ -1915,39 +1905,39 @@
     /**
      * Set of all field ids used in instance field reads, along with access context.
      */
-    public final Map<DexField, Set<DexEncodedMethod>> instanceFieldReads;
+    public final SortedMap<DexField, Set<DexEncodedMethod>> instanceFieldReads;
     /**
      * Set of all field ids used in instance field writes, along with access context.
      */
-    public final Map<DexField, Set<DexEncodedMethod>> instanceFieldWrites;
+    public final SortedMap<DexField, Set<DexEncodedMethod>> instanceFieldWrites;
     /**
      * Set of all field ids used in static field reads, along with access context.
      */
-    public final Map<DexField, Set<DexEncodedMethod>> staticFieldReads;
+    public final SortedMap<DexField, Set<DexEncodedMethod>> staticFieldReads;
     /**
      * Set of all field ids used in static field writes, along with access context.
      */
-    public final Map<DexField, Set<DexEncodedMethod>> staticFieldWrites;
+    public final SortedMap<DexField, Set<DexEncodedMethod>> staticFieldWrites;
     /**
-     * Set of all methods referenced in virtual invokes;
+     * Set of all methods referenced in virtual invokes, along with calling context.
      */
-    public final SortedSet<DexMethod> virtualInvokes;
+    public final SortedMap<DexMethod, Set<DexEncodedMethod>> virtualInvokes;
     /**
-     * Set of all methods referenced in interface invokes;
+     * Set of all methods referenced in interface invokes, along with calling context.
      */
-    public final SortedSet<DexMethod> interfaceInvokes;
+    public final SortedMap<DexMethod, Set<DexEncodedMethod>> interfaceInvokes;
     /**
-     * Set of all methods referenced in super invokes;
+     * Set of all methods referenced in super invokes, along with calling context.
      */
-    public final SortedSet<DexMethod> superInvokes;
+    public final SortedMap<DexMethod, Set<DexEncodedMethod>> superInvokes;
     /**
-     * Set of all methods referenced in direct invokes;
+     * Set of all methods referenced in direct invokes, along with calling context.
      */
-    public final SortedSet<DexMethod> directInvokes;
+    public final SortedMap<DexMethod, Set<DexEncodedMethod>> directInvokes;
     /**
-     * Set of all methods referenced in static invokes;
+     * Set of all methods referenced in static invokes, along with calling context.
      */
-    public final SortedSet<DexMethod> staticInvokes;
+    public final SortedMap<DexMethod, Set<DexEncodedMethod>> staticInvokes;
     /**
      * Set of live call sites in the code. Note that if desugaring has taken place call site objects
      * will have been removed from the code.
@@ -2041,20 +2031,20 @@
               DexMethod::slowCompareTo, enqueuer.virtualMethodsTargetedByInvokeDirect);
       this.liveMethods = toSortedDescriptorSet(enqueuer.liveMethods.getItems());
       this.liveFields = toSortedDescriptorSet(enqueuer.liveFields.getItems());
-      this.instanceFieldReads = enqueuer.collectInstanceFieldsRead();
-      this.instanceFieldWrites = enqueuer.collectInstanceFieldsWritten();
-      this.staticFieldReads = enqueuer.collectStaticFieldsRead();
-      this.staticFieldWrites = enqueuer.collectStaticFieldsWritten();
+      this.instanceFieldReads = enqueuer.collectDescriptors(enqueuer.instanceFieldsRead);
+      this.instanceFieldWrites = enqueuer.collectDescriptors(enqueuer.instanceFieldsWritten);
+      this.staticFieldReads = enqueuer.collectDescriptors(enqueuer.staticFieldsRead);
+      this.staticFieldWrites = enqueuer.collectDescriptors(enqueuer.staticFieldsWritten);
       this.fieldsRead = enqueuer.mergeFieldAccesses(
           instanceFieldReads.keySet(), staticFieldReads.keySet());
       this.fieldsWritten = enqueuer.mergeFieldAccesses(
           instanceFieldWrites.keySet(), staticFieldWrites.keySet());
       this.pinnedItems = enqueuer.pinnedItems;
-      this.virtualInvokes = joinInvokedMethods(enqueuer.virtualInvokes);
-      this.interfaceInvokes = joinInvokedMethods(enqueuer.interfaceInvokes);
-      this.superInvokes = joinInvokedMethods(enqueuer.superInvokes, TargetWithContext::getTarget);
-      this.directInvokes = joinInvokedMethods(enqueuer.directInvokes);
-      this.staticInvokes = joinInvokedMethods(enqueuer.staticInvokes);
+      this.virtualInvokes = enqueuer.collectDescriptors(enqueuer.virtualInvokes);
+      this.interfaceInvokes = enqueuer.collectDescriptors(enqueuer.interfaceInvokes);
+      this.superInvokes = enqueuer.collectDescriptors(enqueuer.superInvokes);
+      this.directInvokes = enqueuer.collectDescriptors(enqueuer.directInvokes);
+      this.staticInvokes = enqueuer.collectDescriptors(enqueuer.staticInvokes);
       this.callSites = enqueuer.callSites;
       this.brokenSuperInvokes =
           ImmutableSortedSet.copyOf(DexMethod::slowCompareTo, enqueuer.brokenSuperInvokes);
@@ -2153,11 +2143,16 @@
       this.fieldsRead = rewriteItems(previous.fieldsRead, lense::lookupField);
       this.fieldsWritten = rewriteItems(previous.fieldsWritten, lense::lookupField);
       this.pinnedItems = lense.rewriteReferencesConservatively(previous.pinnedItems);
-      this.virtualInvokes = lense.rewriteMethodsConservatively(previous.virtualInvokes);
-      this.interfaceInvokes = lense.rewriteMethodsConservatively(previous.interfaceInvokes);
-      this.superInvokes = lense.rewriteMethodsConservatively(previous.superInvokes);
-      this.directInvokes = lense.rewriteMethodsConservatively(previous.directInvokes);
-      this.staticInvokes = lense.rewriteMethodsConservatively(previous.staticInvokes);
+      this.virtualInvokes = rewriteKeysConservativelyWhileMergingValues(
+          previous.virtualInvokes, lense::lookupMethodInAllContexts);
+      this.interfaceInvokes = rewriteKeysConservativelyWhileMergingValues(
+          previous.interfaceInvokes, lense::lookupMethodInAllContexts);
+      this.superInvokes = rewriteKeysConservativelyWhileMergingValues(
+          previous.superInvokes, lense::lookupMethodInAllContexts);
+      this.directInvokes = rewriteKeysConservativelyWhileMergingValues(
+          previous.directInvokes, lense::lookupMethodInAllContexts);
+      this.staticInvokes = rewriteKeysConservativelyWhileMergingValues(
+          previous.staticInvokes, lense::lookupMethodInAllContexts);
       // TODO(sgjesse): Rewrite call sites as well? Right now they are only used by minification
       // after second tree shaking.
       this.callSites = previous.callSites;
@@ -2294,16 +2289,6 @@
       return isInstantiatedDirectly(type) || isInstantiatedIndirectly(type);
     }
 
-    private SortedSet<DexMethod> joinInvokedMethods(Map<DexType, Set<DexMethod>> invokes) {
-      return joinInvokedMethods(invokes, Function.identity());
-    }
-
-    private <T> SortedSet<DexMethod> joinInvokedMethods(Map<DexType, Set<T>> invokes,
-        Function<T, DexMethod> getter) {
-      return invokes.values().stream().flatMap(Set::stream).map(getter)
-          .collect(ImmutableSortedSet.toImmutableSortedSet(PresortedComparable::slowCompare));
-    }
-
     private Object2BooleanMap<DexReference> joinIdentifierNameStrings(
         Set<DexReference> explicit, Set<DexReference> implicit) {
       Object2BooleanMap<DexReference> result = new Object2BooleanArrayMap<>();
@@ -2336,17 +2321,30 @@
       return builder.build();
     }
 
-
     private static <T extends PresortedComparable<T>, S>
-        Map<T, Set<S>> rewriteKeysWhileMergingValues(
+        SortedMap<T, Set<S>> rewriteKeysWhileMergingValues(
             Map<T, Set<S>> original, Function<T, T> rewrite) {
-      Map<T, Set<S>> result = new IdentityHashMap<>();
+      SortedMap<T, Set<S>> result = new TreeMap<>(PresortedComparable::slowCompare);
       for (T item : original.keySet()) {
         T rewrittenKey = rewrite.apply(item);
         result.computeIfAbsent(rewrittenKey, k -> Sets.newIdentityHashSet())
             .addAll(original.get(item));
       }
-      return Collections.unmodifiableMap(result);
+      return Collections.unmodifiableSortedMap(result);
+    }
+
+    private static <T extends PresortedComparable<T>, S>
+        SortedMap<T, Set<S>> rewriteKeysConservativelyWhileMergingValues(
+            Map<T, Set<S>> original, Function<T, Set<T>> rewrite) {
+      SortedMap<T, Set<S>> result = new TreeMap<>(PresortedComparable::slowCompare);
+      for (T item : original.keySet()) {
+        Set<T> rewrittenKeys = rewrite.apply(item);
+        for (T rewrittenKey : rewrittenKeys) {
+          result.computeIfAbsent(rewrittenKey, k -> Sets.newIdentityHashSet())
+              .addAll(original.get(item));
+        }
+      }
+      return Collections.unmodifiableSortedMap(result);
     }
 
     @Override
diff --git a/src/test/java/com/android/tools/r8/resolution/b123730538/B123730538.java b/src/test/java/com/android/tools/r8/resolution/b123730538/B123730538.java
new file mode 100644
index 0000000..462d514
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/resolution/b123730538/B123730538.java
@@ -0,0 +1,99 @@
+// 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.b123730538;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertThat;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.resolution.b123730538.runner.PublicClassExtender;
+import com.android.tools.r8.resolution.b123730538.runner.Runner;
+import com.android.tools.r8.resolution.b123730538.sub.PublicClass;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import com.google.common.collect.ImmutableList;
+import java.nio.file.Path;
+import java.util.List;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class B123730538 extends TestBase {
+  private static final Class MAIN = Runner.class;
+  private static List<Path> CLASSES;
+  private static final String EXPECTED_OUTPUT = StringUtils.lines("pkg.AbstractClass::foo");
+
+  private final Backend backend;
+
+  @Parameterized.Parameters(name = "Backend: {0}")
+  public static Object[] data() {
+    return Backend.values();
+  }
+
+  public B123730538(Backend backend) {
+    this.backend = backend;
+  }
+
+  @BeforeClass
+  public static void setUpClass() throws Exception {
+    CLASSES = ImmutableList.<Path>builder()
+        .addAll(ToolHelper.getClassFilesForTestPackage(MAIN.getPackage()))
+        .addAll(ToolHelper.getClassFilesForTestPackage(PublicClass.class.getPackage()))
+        .build();
+  }
+
+  @Test
+  public void testProguard() throws Exception {
+    Path inJar = temp.newFile("input.jar").toPath().toAbsolutePath();
+    writeToJar(inJar, CLASSES);
+    testForProguard()
+        .addProgramFiles(inJar)
+        .addKeepMainRule(MAIN)
+        .addKeepRules("-dontoptimize")
+        .run(MAIN)
+        .assertSuccessWithOutput(EXPECTED_OUTPUT)
+        .inspect(this::inspect);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(backend)
+        .addProgramFiles(CLASSES)
+        .addKeepMainRule(MAIN)
+        .addKeepRules("-dontoptimize")
+        .run(MAIN)
+        .assertSuccessWithOutput(EXPECTED_OUTPUT)
+        .inspect(this::inspect);
+  }
+
+  private void inspect(CodeInspector inspector) {
+    MethodSubject foo = inspector.clazz(
+        PublicClass.class.getTypeName().replace("PublicClass", "AbstractClass"))
+        .uniqueMethodWithName("foo");
+    assertThat(foo, isPresent());
+
+    ClassSubject main = inspector.clazz(PublicClassExtender.class);
+    assertThat(main, isPresent());
+    MethodSubject methodSubject = main.uniqueMethodWithName("delegate");
+    assertThat(methodSubject, isPresent());
+
+    methodSubject
+        .iterateInstructions(InstructionSubject::isInvokeVirtual)
+        .forEachRemaining(instructionSubject -> {
+          String methodName = instructionSubject.getMethod().name.toString();
+          // Method references will be renamed.
+          assertNotEquals("foo", methodName);
+          assertEquals(foo.getFinalName(), methodName);
+        });
+  }
+
+}
diff --git a/src/test/java/com/android/tools/r8/resolution/b123730538/runner/AnotherPublicClassExtender.java b/src/test/java/com/android/tools/r8/resolution/b123730538/runner/AnotherPublicClassExtender.java
new file mode 100644
index 0000000..8377885
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/resolution/b123730538/runner/AnotherPublicClassExtender.java
@@ -0,0 +1,13 @@
+// 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.b123730538.runner;
+
+import com.android.tools.r8.resolution.b123730538.sub.AnotherPublicClass;
+
+// To make AbstractPublicClass not mergeable for both R8 and Proguard.
+class AnotherPublicClassExtender extends AnotherPublicClass {
+  void delegate() {
+    foo();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/resolution/b123730538/runner/PublicClassExtender.java b/src/test/java/com/android/tools/r8/resolution/b123730538/runner/PublicClassExtender.java
new file mode 100644
index 0000000..b866eea
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/resolution/b123730538/runner/PublicClassExtender.java
@@ -0,0 +1,15 @@
+// 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.b123730538.runner;
+
+import com.android.tools.r8.resolution.b123730538.sub.PublicClass;
+
+public class PublicClassExtender extends PublicClass {
+  void delegate() {
+    // Method reference should be PublicClassExtender#foo, not the definition AbstractClass#foo
+    // because package-private AbstractClass is not visible from this calling context.
+    // Otherwise, we will see java.lang.IllegalAccessError.
+    foo();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/resolution/b123730538/runner/Runner.java b/src/test/java/com/android/tools/r8/resolution/b123730538/runner/Runner.java
new file mode 100644
index 0000000..54e0c6e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/resolution/b123730538/runner/Runner.java
@@ -0,0 +1,26 @@
+// 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.b123730538.runner;
+
+public class Runner {
+
+  static int counter = 0;
+
+  static Object create() {
+    if (counter++ % 2 == 0) {
+      return new PublicClassExtender();
+    } else {
+      return new AnotherPublicClassExtender();
+    }
+  }
+
+  public static void main(String[] args) {
+    Object instance = create();
+    if (instance instanceof PublicClassExtender) {
+      ((PublicClassExtender) instance).delegate();
+    } else {
+      ((AnotherPublicClassExtender) instance).delegate();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/resolution/b123730538/sub/AbstractClass.java b/src/test/java/com/android/tools/r8/resolution/b123730538/sub/AbstractClass.java
new file mode 100644
index 0000000..a99e9c5
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/resolution/b123730538/sub/AbstractClass.java
@@ -0,0 +1,10 @@
+// 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.b123730538.sub;
+
+abstract class AbstractClass {
+  protected void foo() {
+    System.out.println("pkg.AbstractClass::foo");
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/resolution/b123730538/sub/AnotherPublicClass.java b/src/test/java/com/android/tools/r8/resolution/b123730538/sub/AnotherPublicClass.java
new file mode 100644
index 0000000..b9433a1
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/resolution/b123730538/sub/AnotherPublicClass.java
@@ -0,0 +1,8 @@
+// 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.b123730538.sub;
+
+// To make AbstractClass not mergeable for both R8 and Proguard.
+public class AnotherPublicClass extends AbstractClass {
+}
diff --git a/src/test/java/com/android/tools/r8/resolution/b123730538/sub/PublicClass.java b/src/test/java/com/android/tools/r8/resolution/b123730538/sub/PublicClass.java
new file mode 100644
index 0000000..c3afbfc
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/resolution/b123730538/sub/PublicClass.java
@@ -0,0 +1,7 @@
+// 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.b123730538.sub;
+
+public class PublicClass extends AbstractClass {
+}