Version 2.1.36

Cherry pick: Rewrite invoke-super targeting rewritten desugared library methods.
CL: https://r8-review.googlesource.com/c/r8/+/51960
Bug: 157806261
Change-Id: Ifb1b8e739fdd4161fb259587106fe6bca542644a
diff --git a/src/main/java/com/android/tools/r8/Version.java b/src/main/java/com/android/tools/r8/Version.java
index bc82245..3daa05c 100644
--- a/src/main/java/com/android/tools/r8/Version.java
+++ b/src/main/java/com/android/tools/r8/Version.java
@@ -11,7 +11,7 @@
 
   // This field is accessed from release scripts using simple pattern matching.
   // Therefore, changing this field could break our release scripts.
-  public static final String LABEL = "2.1.35";
+  public static final String LABEL = "2.1.36";
 
   private Version() {
   }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
index 901517a..4e6d0ab 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
@@ -284,66 +284,52 @@
         if (instruction.isInvokeSuper()) {
           InvokeSuper invokeSuper = instruction.asInvokeSuper();
           DexMethod invokedMethod = invokeSuper.getInvokedMethod();
-          DexClass clazz = appInfo.definitionFor(invokedMethod.holder);
+          DexClass clazz = appInfo.definitionForHolder(invokedMethod);
           if (clazz == null) {
             // NOTE: leave unchanged those calls to undefined targets. This may lead to runtime
             // exception but we can not report it as error since it can also be the intended
             // behavior.
             warnMissingType(encodedMethod.method, invokedMethod.holder);
-          } else if (clazz.isInterface() && !clazz.isLibraryClass()) {
-            // NOTE: we intentionally don't desugar super calls into interface methods
-            // coming from android.jar since it is only possible in case v24+ version
-            // of android.jar is provided.
-            //
-            // We assume such calls are properly guarded by if-checks like
-            //    'if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.XYZ) { ... }'
-            //
-            // WARNING: This may result in incorrect code on older platforms!
-            // Retarget call to an appropriate method of companion class.
+            continue;
+          }
+          if (!clazz.isInterface()) {
+            // Skip non-interface invokes.
+            continue;
+          }
+          if (!clazz.isLibraryClass()) {
+            // For program and classpath retarget call to an appropriate method of companion class.
             DexMethod amendedMethod =
                 amendDefaultMethod(appInfo.definitionFor(encodedMethod.holder()), invokedMethod);
             instructions.replaceCurrentInstruction(
                 new InvokeStatic(defaultAsMethodOfCompanionClass(amendedMethod),
                     invokeSuper.outValue(), invokeSuper.arguments()));
           } else {
-            DexType dexType = maximallySpecificEmulatedInterfaceOrNull(invokedMethod);
-            if (dexType != null) {
-              // That invoke super may not resolve since the super method may not be present
-              // since it's in the emulated interface. We need to force resolution. If it resolves
-              // to a library method, then it needs to be rewritten.
-              // If it resolves to a program overrides, the invoke-super can remain.
-              DexEncodedMethod dexEncodedMethod =
-                  appView
-                      .appInfoForDesugaring()
-                      .lookupSuperTarget(invokeSuper.getInvokedMethod(), code.context());
-              if (dexEncodedMethod != null) {
-                DexClass dexClass = appView.definitionFor(dexEncodedMethod.holder());
-                if (dexClass != null && dexClass.isLibraryClass()) {
-                  // Rewriting is required because the super invoke resolves into a missing
-                  // method (method is on desugared library). Find out if it needs to be
-                  // retarget or if it just calls a companion class method and rewrite.
-                  DexMethod retargetMethod =
-                      options.desugaredLibraryConfiguration.retargetMethod(
-                          dexEncodedMethod.method, appView);
-                  if (retargetMethod == null) {
-                    DexMethod originalCompanionMethod =
-                        instanceAsMethodOfCompanionClass(
-                            dexEncodedMethod.method, DEFAULT_METHOD_PREFIX, factory);
-                    DexMethod companionMethod =
-                        factory.createMethod(
-                            getCompanionClassType(dexType),
-                            factory.protoWithDifferentFirstParameter(
-                                originalCompanionMethod.proto, dexType),
-                            originalCompanionMethod.name);
-                    instructions.replaceCurrentInstruction(
-                        new InvokeStatic(
-                            companionMethod, invokeSuper.outValue(), invokeSuper.arguments()));
-                  } else {
-                    instructions.replaceCurrentInstruction(
-                        new InvokeStatic(
-                            retargetMethod, invokeSuper.outValue(), invokeSuper.arguments()));
-                  }
-                }
+            // Rewriting is required if the super invoke resolves to a default method on the
+            // desugared library. Retarget or rewrite to the desugared library companion class.
+            DexEncodedMethod dexEncodedMethod =
+                targetForInvokeSuperDispatchToDefaultMethod(
+                    invokedMethod, clazz.asLibraryClass(), code.context());
+            if (dexEncodedMethod != null) {
+              DexMethod retargetMethod =
+                  options.desugaredLibraryConfiguration.retargetMethod(
+                      dexEncodedMethod.method, appView);
+              if (retargetMethod == null) {
+                DexMethod originalCompanionMethod =
+                    instanceAsMethodOfCompanionClass(
+                        dexEncodedMethod.method, DEFAULT_METHOD_PREFIX, factory);
+                DexMethod companionMethod =
+                    factory.createMethod(
+                        getCompanionClassType(clazz.type),
+                        factory.protoWithDifferentFirstParameter(
+                            originalCompanionMethod.proto, clazz.type),
+                        originalCompanionMethod.name);
+                instructions.replaceCurrentInstruction(
+                    new InvokeStatic(
+                        companionMethod, invokeSuper.outValue(), invokeSuper.arguments()));
+              } else {
+                instructions.replaceCurrentInstruction(
+                    new InvokeStatic(
+                        retargetMethod, invokeSuper.outValue(), invokeSuper.arguments()));
               }
             }
           }
@@ -418,6 +404,28 @@
     }
   }
 
+  private DexEncodedMethod targetForInvokeSuperDispatchToDefaultMethod(
+      DexMethod invokedSuperMethod, DexLibraryClass holder, ProgramMethod context) {
+    assert invokedSuperMethod.holder == holder.type;
+    assert holder.isInterface();
+    DexEncodedMethod definition = holder.lookupMethod(invokedSuperMethod);
+    if (definition == null || !definition.isDefaultMethod()) {
+      return null;
+    }
+    // Only default methods on emulated interfaces or rewritten types need to be dealt with.
+    if (!emulatedMethods.contains(invokedSuperMethod.name)
+        && !appView.rewritePrefix.hasRewrittenType(holder.type, appView)) {
+      return null;
+    }
+    DexEncodedMethod target =
+        appView.appInfoForDesugaring().lookupSuperTarget(invokedSuperMethod, context);
+    DexClass targetHolder = appView.definitionForHolder(target);
+    if (targetHolder == null || !targetHolder.isLibraryClass()) {
+      return null;
+    }
+    return target;
+  }
+
   private DexType maximallySpecificEmulatedInterfaceOrNull(DexMethod invokedMethod) {
     // Here we try to avoid doing the expensive look-up on all invokes.
     if (!emulatedMethods.contains(invokedMethod.name)) {
diff --git a/src/test/java/com/android/tools/r8/D8TestBuilder.java b/src/test/java/com/android/tools/r8/D8TestBuilder.java
index edfb00c..1496847 100644
--- a/src/test/java/com/android/tools/r8/D8TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/D8TestBuilder.java
@@ -74,12 +74,9 @@
 
   @Override
   public D8TestBuilder enableCoreLibraryDesugaring(
-      AndroidApiLevel minAPILevel, KeepRuleConsumer keepRuleConsumer) {
-    if (minAPILevel.getLevel() < AndroidApiLevel.O.getLevel()) {
-      // Use P to mimic current Android Studio.
-      builder.addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P));
-      builder.addDesugaredLibraryConfiguration(
-          StringResource.fromFile(ToolHelper.DESUGAR_LIB_JSON_FOR_TESTING));
+      AndroidApiLevel minApiLevel, KeepRuleConsumer keepRuleConsumer) {
+    if (minApiLevel.getLevel() < AndroidApiLevel.O.getLevel()) {
+      super.enableCoreLibraryDesugaring(minApiLevel, keepRuleConsumer);
       builder.setDesugaredLibraryKeepRuleConsumer(keepRuleConsumer);
     }
     return self();
diff --git a/src/test/java/com/android/tools/r8/R8TestBuilder.java b/src/test/java/com/android/tools/r8/R8TestBuilder.java
index 61db3e3..2675329 100644
--- a/src/test/java/com/android/tools/r8/R8TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/R8TestBuilder.java
@@ -545,12 +545,9 @@
 
   @Override
   public T enableCoreLibraryDesugaring(
-      AndroidApiLevel minAPILevel, KeepRuleConsumer keepRuleConsumer) {
-    if (minAPILevel.getLevel() < AndroidApiLevel.O.getLevel()) {
-      // Use P to mimic current Android Studio.
-      builder.addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P));
-      builder.addDesugaredLibraryConfiguration(
-          StringResource.fromFile(ToolHelper.DESUGAR_LIB_JSON_FOR_TESTING));
+      AndroidApiLevel minApiLevel, KeepRuleConsumer keepRuleConsumer) {
+    if (minApiLevel.getLevel() < AndroidApiLevel.O.getLevel()) {
+      super.enableCoreLibraryDesugaring(minApiLevel, keepRuleConsumer);
       builder.setDesugaredLibraryKeepRuleConsumer(keepRuleConsumer);
     }
     return self();
diff --git a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
index b7869e4..ce03733 100644
--- a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
@@ -10,7 +10,6 @@
 import com.android.tools.r8.TestBase.Backend;
 import com.android.tools.r8.debug.DebugTestConfig;
 import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase.KeepRuleConsumer;
-import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.AndroidAppConsumers;
@@ -378,8 +377,16 @@
   }
 
   public T enableCoreLibraryDesugaring(
-      AndroidApiLevel minAPILevel, KeepRuleConsumer keepRuleConsumer) {
-    throw new Unreachable("Should be overridden or is unsupported.");
+      AndroidApiLevel minApiLevel, KeepRuleConsumer keepRuleConsumer) {
+    assert minApiLevel.getLevel() < AndroidApiLevel.O.getLevel();
+    builder.addDesugaredLibraryConfiguration(
+        StringResource.fromFile(ToolHelper.DESUGAR_LIB_JSON_FOR_TESTING));
+    // TODO(b/158543446): This should not be setting an implicit library file. Doing so causes
+    //  inconsistent library setup depending on the api level and makes tests hard to read and
+    //  reason about.
+    // Use P to mimic current Android Studio.
+    builder.addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P));
+    return self();
   }
 
   @Override
diff --git a/src/test/java/com/android/tools/r8/desugar/InvokeSuperToEmulatedDefaultMethodTest.java b/src/test/java/com/android/tools/r8/desugar/InvokeSuperToEmulatedDefaultMethodTest.java
new file mode 100644
index 0000000..9d3d498
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/InvokeSuperToEmulatedDefaultMethodTest.java
@@ -0,0 +1,155 @@
+// 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.desugar;
+
+import static org.junit.Assume.assumeFalse;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase;
+import com.android.tools.r8.utils.StringUtils;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class InvokeSuperToEmulatedDefaultMethodTest extends DesugaredLibraryTestBase {
+
+  private static final String EXPECTED = StringUtils.lines("bar", "bar");
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  private final TestParameters parameters;
+
+  public InvokeSuperToEmulatedDefaultMethodTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  private boolean needsDefaultInterfaceMethodDesugaring() {
+    return parameters.isDexRuntime()
+        && parameters.getApiLevel().isLessThan(apiLevelWithDefaultInterfaceMethodsSupport());
+  }
+
+  @Test
+  public void testReference() throws Exception {
+    assumeFalse(needsDefaultInterfaceMethodDesugaring());
+    testForRuntime(parameters)
+        .addInnerClasses(InvokeSuperToEmulatedDefaultMethodTest.class)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  @Test
+  public void testDesugaring() throws Exception {
+    assumeTrue(needsDefaultInterfaceMethodDesugaring());
+    testForD8()
+        .addInnerClasses(InvokeSuperToEmulatedDefaultMethodTest.class)
+        .setMinApi(parameters.getApiLevel())
+        .enableCoreLibraryDesugaring(parameters.getApiLevel())
+        .compile()
+        .addDesugaredCoreLibraryRunClassPath(this::buildDesugaredLibrary, parameters.getApiLevel())
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  public interface StringMap extends Map<String, String> {
+
+    @Override
+    default String getOrDefault(Object key, String defaultValue) {
+      // Simple forward that targets the desugared library.
+      return Map.super.getOrDefault(key + "oo", defaultValue);
+    }
+  }
+
+  public interface StringMapIndirection extends StringMap {
+
+    @Override
+    default String getOrDefault(Object key, String defaultValue) {
+      // Simple forward to a program defined default method.
+      return StringMap.super.getOrDefault("f", defaultValue);
+    }
+  }
+
+  public static class StringHashMap implements StringMapIndirection {
+    HashMap<String, String> delegate = new HashMap<>();
+
+    @Override
+    public int size() {
+      return delegate.size();
+    }
+
+    @Override
+    public boolean isEmpty() {
+      return delegate.isEmpty();
+    }
+
+    @Override
+    public boolean containsKey(Object key) {
+      return delegate.containsKey(key);
+    }
+
+    @Override
+    public boolean containsValue(Object value) {
+      return delegate.containsValue(value);
+    }
+
+    @Override
+    public String get(Object key) {
+      return delegate.get(key);
+    }
+
+    @Override
+    public String put(String key, String value) {
+      return delegate.put(key, value);
+    }
+
+    @Override
+    public String remove(Object key) {
+      return delegate.remove(key);
+    }
+
+    @Override
+    public void putAll(Map<? extends String, ? extends String> m) {
+      delegate.putAll(m);
+    }
+
+    @Override
+    public void clear() {
+      delegate.clear();
+    }
+
+    @Override
+    public Set<String> keySet() {
+      return delegate.keySet();
+    }
+
+    @Override
+    public Collection<String> values() {
+      return delegate.values();
+    }
+
+    @Override
+    public Set<Entry<String, String>> entrySet() {
+      return delegate.entrySet();
+    }
+  }
+
+  public static class TestClass {
+
+    public static void main(String[] args) {
+      StringHashMap map = new StringHashMap();
+      map.put("foo", "bar");
+      System.out.println(map.getOrDefault("foo", "not found"));
+      System.out.println(map.getOrDefault("bar", "not found"));
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/InvokeSuperToRewrittenDefaultMethodTest.java b/src/test/java/com/android/tools/r8/desugar/InvokeSuperToRewrittenDefaultMethodTest.java
new file mode 100644
index 0000000..f63c4d9
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/InvokeSuperToRewrittenDefaultMethodTest.java
@@ -0,0 +1,139 @@
+// 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.desugar;
+
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticType;
+import static org.hamcrest.CoreMatchers.allOf;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeFalse;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.StringResource;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.ExceptionDiagnostic;
+import com.android.tools.r8.utils.StringUtils;
+import java.util.List;
+import java.util.function.Consumer;
+import java.util.function.IntConsumer;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class InvokeSuperToRewrittenDefaultMethodTest extends DesugaredLibraryTestBase {
+
+  private static final String EXPECTED = StringUtils.lines("Y", "89");
+
+  @Parameterized.Parameters(name = "{0}, old-rt:{1}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withAllRuntimesAndApiLevels().build(), BooleanUtils.values());
+  }
+
+  private final TestParameters parameters;
+  private final boolean rtWithoutConsumer;
+
+  public InvokeSuperToRewrittenDefaultMethodTest(
+      TestParameters parameters, boolean rtWithoutConsumer) {
+    this.parameters = parameters;
+    this.rtWithoutConsumer = rtWithoutConsumer;
+  }
+
+  private boolean needsDefaultInterfaceMethodDesugaring() {
+    return parameters.isDexRuntime()
+        && parameters.getApiLevel().isLessThan(apiLevelWithDefaultInterfaceMethodsSupport());
+  }
+
+  @Test
+  public void testReference() throws Exception {
+    assumeFalse(needsDefaultInterfaceMethodDesugaring());
+    testForRuntime(parameters)
+        .addInnerClasses(InvokeSuperToRewrittenDefaultMethodTest.class)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  @Test
+  public void testDesugaring() throws Exception {
+    assumeTrue(needsDefaultInterfaceMethodDesugaring());
+    try {
+      testForD8()
+          .addInnerClasses(InvokeSuperToRewrittenDefaultMethodTest.class)
+          .setMinApi(parameters.getApiLevel())
+          .apply(
+              b -> {
+                if (rtWithoutConsumer) {
+                  b.addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.B));
+                  // TODO(b/158543446): Remove this once enableCoreLibraryDesugaring is fixed.
+                  b.getBuilder()
+                      .addDesugaredLibraryConfiguration(
+                          StringResource.fromFile(ToolHelper.DESUGAR_LIB_JSON_FOR_TESTING));
+                } else {
+                  // TODO(b/158543446): Move this out to the shared builder once
+                  //  enableCoreLibraryDesugaring is fixed.
+                  b.enableCoreLibraryDesugaring(parameters.getApiLevel());
+                }
+              })
+          .compileWithExpectedDiagnostics(
+              diagnostics -> {
+                if (rtWithoutConsumer) {
+                  diagnostics.assertOnlyErrors();
+                  // TODO(b/158543011): Should fail with a nice user error for invalid library.
+                  diagnostics.assertErrorsMatch(
+                      allOf(
+                          diagnosticType(ExceptionDiagnostic.class),
+                          diagnosticMessage(containsString("AssertionError"))));
+                } else {
+                  diagnostics.assertNoMessages();
+                }
+              })
+          .addDesugaredCoreLibraryRunClassPath(
+              this::buildDesugaredLibrary, parameters.getApiLevel())
+          .run(parameters.getRuntime(), TestClass.class)
+          .assertSuccessWithOutput(EXPECTED);
+      assertFalse(rtWithoutConsumer);
+    } catch (CompilationFailedException e) {
+      assertTrue(rtWithoutConsumer);
+    }
+  }
+
+  @FunctionalInterface
+  public interface CharConsumer extends Consumer<Character>, IntConsumer {
+
+    void accept(char c);
+
+    @Override
+    default void accept(int value) {
+      accept((char) value);
+    }
+
+    @Override
+    default void accept(Character character) {
+      accept(character.charValue());
+    }
+
+    @Override
+    default Consumer<Character> andThen(Consumer<? super Character> after) {
+      // Simple forward to the default method of the parent.
+      // Must be rewritten to target the companion class of the rewritten Consumer type.
+      return Consumer.super.andThen(after);
+    }
+  }
+
+  public static class TestClass {
+
+    public static void main(String[] args) {
+      CharConsumer consumer = System.out::println;
+      consumer.andThen((Consumer<? super Character>) c -> System.out.println((int) c)).accept('Y');
+    }
+  }
+}