Version 1.0.22

Merge: Proper desugaring of invoke-direct calls to default interface methods.
CL: https://r8-review.googlesource.com/c/r8/+/18220

Merge: Fixing super calls to default interface methods in desugaring.
CL: https://r8-review.googlesource.com/c/r8/+/19080
Change-Id: If4285e7e1aaad0b5f085761103611c3079deb576
diff --git a/src/main/java/com/android/tools/r8/Version.java b/src/main/java/com/android/tools/r8/Version.java
index 398d25d..ab1f699 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 = "v1.0.21";
+  public static final String LABEL = "v1.0.22";
 
   private Version() {
   }
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 fa0958b..f042cf7 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
@@ -32,8 +32,6 @@
   private final Set<DexClass> processedClasses = Sets.newIdentityHashSet();
   // Maps already created methods into default methods they were generated based on.
   private final Map<DexEncodedMethod, DexEncodedMethod> createdMethods = new IdentityHashMap<>();
-  // Caches default interface method info for already processed interfaces.
-  private final Map<DexType, DefaultMethodsHelper.Collection> cache = new IdentityHashMap<>();
 
   ClassProcessor(InterfaceMethodRewriter rewriter) {
     this.rewriter = rewriter;
@@ -67,7 +65,7 @@
     if (superClass != null && superType != rewriter.factory.objectType) {
       if (superClass.isInterface()) {
         throw new CompilationError("Interface `" + superClass.toSourceString()
-        + "` used as super class of `" + clazz.toSourceString() + "`.");
+            + "` used as super class of `" + clazz.toSourceString() + "`.");
       }
       process(superClass);
     }
@@ -136,7 +134,7 @@
     // the future.
     while (current.type != rewriter.factory.objectType) {
       for (DexType type : current.interfaces.values) {
-        helper.merge(getOrCreateInterfaceInfo(clazz, current, type));
+        helper.merge(rewriter.getOrCreateInterfaceInfo(clazz, current, type));
       }
 
       accumulatedVirtualMethods.addAll(Arrays.asList(clazz.virtualMethods()));
@@ -167,8 +165,8 @@
         } else {
           String message = "Default method desugaring of `" + clazz.toSourceString() + "` failed";
           if (current == clazz) {
-            message += " because its super class `" + clazz.superType.toSourceString()
-            + "` is missing";
+            message += " because its super class `" +
+                clazz.superType.toSourceString() + "` is missing";
           } else {
             message +=
                 " because it's hierarchy is incomplete. The class `"
@@ -252,54 +250,4 @@
       }
     }
   }
-
-  private DefaultMethodsHelper.Collection getOrCreateInterfaceInfo(
-      DexClass classToDesugar,
-      DexClass implementing,
-      DexType iface) {
-    DefaultMethodsHelper.Collection collection = cache.get(iface);
-    if (collection != null) {
-      return collection;
-    }
-    collection = createInterfaceInfo(classToDesugar, implementing, iface);
-    cache.put(iface, collection);
-    return collection;
-  }
-
-  private DefaultMethodsHelper.Collection createInterfaceInfo(
-      DexClass classToDesugar,
-      DexClass implementing,
-      DexType iface) {
-    DefaultMethodsHelper helper = new DefaultMethodsHelper();
-    DexClass definedInterface = rewriter.findDefinitionFor(iface);
-    if (definedInterface == null) {
-      rewriter.warnMissingInterface(classToDesugar, implementing, iface);
-      return helper.wrapInCollection();
-    }
-
-    if (!definedInterface.isInterface()) {
-      throw new CompilationError(
-          "Type " + iface.toSourceString() + " is referenced as an interface of `"
-          + implementing.toString() + "`.");
-    }
-
-    // Merge information from all superinterfaces.
-    for (DexType superinterface : definedInterface.interfaces.values) {
-      helper.merge(getOrCreateInterfaceInfo(classToDesugar, definedInterface, superinterface));
-    }
-
-    // Hide by virtual methods of this interface.
-    for (DexEncodedMethod virtual : definedInterface.virtualMethods()) {
-      helper.hideMatches(virtual.method);
-    }
-
-    // Add all default methods of this interface.
-    for (DexEncodedMethod encoded : definedInterface.virtualMethods()) {
-      if (rewriter.isDefaultMethod(encoded)) {
-        helper.addDefaultMethod(encoded);
-      }
-    }
-
-    return helper.wrapInCollection();
-  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/DefaultMethodsHelper.java b/src/main/java/com/android/tools/r8/ir/desugar/DefaultMethodsHelper.java
index df65e7e..fb051aa 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/DefaultMethodsHelper.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/DefaultMethodsHelper.java
@@ -40,6 +40,22 @@
       this.live = live;
       this.hidden = hidden;
     }
+
+    // If there is just one live method having specified
+    // signature return it, otherwise return null.
+    DexMethod getSingleCandidate(DexMethod method) {
+      DexMethod candidate = null;
+      for (DexEncodedMethod encodedMethod : live) {
+        DexMethod current = encodedMethod.method;
+        if (current.proto == method.proto && current.name == method.name) {
+          if (candidate != null) {
+            return null;
+          }
+          candidate = current;
+        }
+      }
+      return candidate;
+    }
   }
 
   final void merge(Collection collection) {
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 3d1f79d..9b8b8e2 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
@@ -26,6 +26,7 @@
 import com.android.tools.r8.ir.code.InvokeStatic;
 import com.android.tools.r8.ir.code.InvokeSuper;
 import com.android.tools.r8.ir.conversion.IRConverter;
+import com.android.tools.r8.ir.desugar.DefaultMethodsHelper.Collection;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.StringDiagnostic;
@@ -33,6 +34,7 @@
 import java.util.ListIterator;
 import java.util.Map;
 import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
 
 //
 // Default and static interface method desugaring rewriter (note that lambda
@@ -75,6 +77,9 @@
   // to this collection since it is only filled in ClassProcessor running synchronously.
   private final Set<DexEncodedMethod> forwardingMethods = Sets.newIdentityHashSet();
 
+  // Caches default interface method info for already processed interfaces.
+  private final Map<DexType, DefaultMethodsHelper.Collection> cache = new ConcurrentHashMap<>();
+
   /**
    * A set of dexitems we have reported missing to dedupe warnings.
    */
@@ -166,8 +171,10 @@
             // of android.jar is provided.
             // WARNING: This may result in incorrect code on older platforms!
             // Retarget call to an appropriate method of companion class.
+            DexMethod amendedMethod = amendDefaultMethod(
+                findDefinitionFor(encodedMethod.method.holder), method);
             instructions.replaceCurrentInstruction(
-                new InvokeStatic(defaultAsMethodOfCompanionClass(method),
+                new InvokeStatic(defaultAsMethodOfCompanionClass(amendedMethod),
                     invokeSuper.outValue(), invokeSuper.arguments()));
           }
           continue;
@@ -180,9 +187,6 @@
             continue;
           }
 
-          // This is a private instance method call. Note that the referenced method
-          // is expected to be in the current class since it is private, but desugaring
-          // may move some methods or their code into other classes.
           DexClass clazz = findDefinitionFor(method.holder);
           if (clazz == null) {
             // Report missing class since we don't know if it is an interface.
@@ -195,9 +199,32 @@
                   getMethodOrigin(encodedMethod.method));
 
             }
-            instructions.replaceCurrentInstruction(
-                new InvokeStatic(privateAsMethodOfCompanionClass(method),
-                    invokeDirect.outValue(), invokeDirect.arguments()));
+
+            // This might be either private method call, or a call to default
+            // interface method made via invoke-direct.
+            DexEncodedMethod virtualTarget = null;
+            for (DexEncodedMethod candidate : clazz.virtualMethods()) {
+              if (candidate.method == method) {
+                virtualTarget = candidate;
+                break;
+              }
+            }
+
+            if (virtualTarget != null) {
+              // This is a invoke-direct call to a virtual method.
+              instructions.replaceCurrentInstruction(
+                  new InvokeStatic(defaultAsMethodOfCompanionClass(method),
+                      invokeDirect.outValue(), invokeDirect.arguments()));
+
+            } else {
+              // Otherwise this must be a private instance method call. Note that the referenced
+              // method is expected to be in the current class since it is private, but desugaring
+              // may move some methods or their code into other classes.
+
+              instructions.replaceCurrentInstruction(
+                  new InvokeStatic(privateAsMethodOfCompanionClass(method),
+                      invokeDirect.outValue(), invokeDirect.arguments()));
+            }
           }
         }
       }
@@ -274,6 +301,15 @@
         factory.createString(prefix + method.name.toString()));
   }
 
+  // It is possible that referenced method actually points to an interface which does
+  // not define this default methods, but inherits it. We are making our best effort
+  // to find an appropriate method, but still use the original one in case we fail.
+  private DexMethod amendDefaultMethod(DexClass classToDesugar, DexMethod method) {
+    DexMethod singleCandidate = getOrCreateInterfaceInfo(
+        classToDesugar, classToDesugar, method.holder).getSingleCandidate(method);
+    return singleCandidate != null ? singleCandidate : method;
+  }
+
   // Represent a default interface method as a method of companion class.
   final DexMethod defaultAsMethodOfCompanionClass(DexMethod method) {
     return instanceAsMethodOfCompanionClass(method, DEFAULT_METHOD_PREFIX);
@@ -309,6 +345,14 @@
     for (DexEncodedMethod method : forwardingMethods) {
       converter.optimizeSynthesizedMethod(method);
     }
+
+    // Cached data is not needed any more.
+    clear();
+  }
+
+  private void clear() {
+    this.cache.clear();
+    this.forwardingMethods.clear();
   }
 
   private static boolean shouldProcess(
@@ -406,4 +450,62 @@
     DexClass clazz = converter.appInfo.definitionFor(holder);
     return clazz == null ? Origin.unknown() : clazz.getOrigin();
   }
+
+  final DefaultMethodsHelper.Collection getOrCreateInterfaceInfo(
+      DexClass classToDesugar,
+      DexClass implementing,
+      DexType iface) {
+    DefaultMethodsHelper.Collection collection = cache.get(iface);
+    if (collection != null) {
+      return collection;
+    }
+    collection = createInterfaceInfo(classToDesugar, implementing, iface);
+    Collection existing = cache.putIfAbsent(iface, collection);
+    return existing != null ? existing : collection;
+  }
+
+  private DefaultMethodsHelper.Collection createInterfaceInfo(
+      DexClass classToDesugar,
+      DexClass implementing,
+      DexType iface) {
+    DefaultMethodsHelper helper = new DefaultMethodsHelper();
+    DexClass definedInterface = findDefinitionFor(iface);
+    if (definedInterface == null) {
+      warnMissingInterface(classToDesugar, implementing, iface);
+      return helper.wrapInCollection();
+    }
+
+    if (!definedInterface.isInterface()) {
+      throw new CompilationError(
+          "Type " + iface.toSourceString() + " is referenced as an interface from `"
+              + implementing.toString() + "`.");
+    }
+
+    if (definedInterface.isLibraryClass()) {
+      // NOTE: We intentionally ignore all candidates coming from android.jar
+      // since it is only possible in case v24+ version of android.jar is provided.
+      // WARNING: This may result in incorrect code if something else than Android bootclasspath
+      // classes are given as libraries!
+      return helper.wrapInCollection();
+    }
+
+    // Merge information from all superinterfaces.
+    for (DexType superinterface : definedInterface.interfaces.values) {
+      helper.merge(getOrCreateInterfaceInfo(classToDesugar, definedInterface, superinterface));
+    }
+
+    // Hide by virtual methods of this interface.
+    for (DexEncodedMethod virtual : definedInterface.virtualMethods()) {
+      helper.hideMatches(virtual.method);
+    }
+
+    // Add all default methods of this interface.
+    for (DexEncodedMethod encoded : definedInterface.virtualMethods()) {
+      if (isDefaultMethod(encoded)) {
+        helper.addDefaultMethod(encoded);
+      }
+    }
+
+    return helper.wrapInCollection();
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/AsmTestBase.java b/src/test/java/com/android/tools/r8/AsmTestBase.java
index 3b35904..c38830b 100644
--- a/src/test/java/com/android/tools/r8/AsmTestBase.java
+++ b/src/test/java/com/android/tools/r8/AsmTestBase.java
@@ -13,6 +13,8 @@
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.ZipUtils;
 import com.google.common.io.ByteStreams;
 import java.io.File;
 import java.io.IOException;
@@ -20,6 +22,7 @@
 import java.nio.file.Path;
 import java.nio.file.StandardOpenOption;
 import java.util.concurrent.ExecutionException;
+import java.util.function.Consumer;
 import java.util.zip.ZipEntry;
 import java.util.zip.ZipOutputStream;
 import org.junit.Assert;
@@ -43,6 +46,21 @@
     ensureSameOutput(main, app, classes);
   }
 
+  protected void ensureSameOutput(String main, int apiLevel, byte[]... classes)
+      throws Exception {
+    AndroidApp app = buildAndroidApp(classes);
+    Consumer<InternalOptions> setMinApiLevel = o -> o.minApiLevel = apiLevel;
+    ProcessResult javaResult = runOnJava(main, classes);
+    ProcessResult d8Result = runOnArtRaw(compileWithD8(app, setMinApiLevel), main);
+    ProcessResult r8Result = runOnArtRaw(compileWithR8(app, setMinApiLevel), main);
+    ProcessResult r8ShakenResult = runOnArtRaw(
+        compileWithR8(app, keepMainProguardConfiguration(main) + "-dontobfuscate\n",
+            setMinApiLevel), main);
+    Assert.assertEquals(javaResult.stdout, d8Result.stdout);
+    Assert.assertEquals(javaResult.stdout, r8Result.stdout);
+    Assert.assertEquals(javaResult.stdout, r8ShakenResult.stdout);
+  }
+
   private void ensureSameOutput(String main, AndroidApp app, byte[]... classes)
       throws IOException, CompilationException, ExecutionException, CompilationFailedException,
       ProguardRuleParserException {
@@ -123,27 +141,27 @@
   }
 
   private Path writeToZip(byte[]... classes) throws IOException {
-    NameExtrator nameExtrator = new NameExtrator();
     File result = temp.newFile();
     try (ZipOutputStream out = new ZipOutputStream(Files.newOutputStream(result.toPath(),
         StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING))) {
       for (byte[] clazz : classes) {
-        String name = nameExtrator.getName(clazz);
-        ZipEntry zipEntry = new ZipEntry(DescriptorUtils.getPathFromJavaType(name));
-        zipEntry.setSize(clazz.length);
-        out.putNextEntry(zipEntry);
-        out.write(clazz);
-        out.closeEntry();
+        String name = loadClassFromDump(clazz).getTypeName();
+        ZipUtils.writeToZipStream(
+            out, DescriptorUtils.getPathFromJavaType(name), clazz);
       }
     }
     return result.toPath();
   }
 
-  private static class NameExtrator extends ClassLoader {
+  private static Class loadClassFromDump(byte[] dump) {
+    return new DumpLoader().loadClass(dump);
+  }
 
-    public String getName(byte[] clazz) {
-      Class loaded = defineClass(clazz, 0, clazz.length);
-      return loaded.getTypeName();
+  private static class DumpLoader extends ClassLoader {
+
+    @SuppressWarnings("deprecation")
+    Class loadClass(byte[] clazz) {
+      return defineClass(clazz, 0, clazz.length);
     }
   }
 }
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index 6cc9bb9..b30c141 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -442,6 +442,15 @@
   private static final String ANGLER_DIR = TOOLS + "/linux/art/product/angler";
   private static final String ANGLER_BOOT_IMAGE = ANGLER_DIR + "/system/framework/boot.art";
 
+  public static byte[] getClassAsBytes(Class clazz) throws IOException {
+    String s = clazz.getSimpleName() + ".class";
+    Class outer = clazz.getEnclosingClass();
+    if (outer != null) {
+      s = outer.getSimpleName() + '$' + s;
+    }
+    return ByteStreams.toByteArray(clazz.getResourceAsStream(s));
+  }
+
   public static String getArtDir(DexVm version) {
     String dir = ART_DIRS.get(version);
     if (dir == null) {
@@ -591,6 +600,10 @@
     }
   }
 
+  public static int getMinApiLevelForDexVm() {
+    return getMinApiLevelForDexVm(ToolHelper.getDexVm());
+  }
+
   public static int getMinApiLevelForDexVm(DexVm dexVm) {
     switch (dexVm.version) {
       case DEFAULT:
diff --git a/src/test/java/com/android/tools/r8/desugaring/interfacemethods/InterfaceMethodDesugaringTests.java b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/InterfaceMethodDesugaringTests.java
new file mode 100644
index 0000000..d2cf708
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/InterfaceMethodDesugaringTests.java
@@ -0,0 +1,107 @@
+// Copyright (c) 2018, 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.desugaring.interfacemethods;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.AsmTestBase;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.VmTestRunner;
+import com.android.tools.r8.VmTestRunner.IgnoreForRangeOfVmVersions;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+@RunWith(VmTestRunner.class)
+public class InterfaceMethodDesugaringTests extends AsmTestBase {
+
+  @Test
+  public void testInvokeSpecialToDefaultMethod() throws Exception {
+    ensureSameOutput(
+        com.android.tools.r8.desugaring.interfacemethods.test0.TestMain.class.getCanonicalName(),
+        ToolHelper.getMinApiLevelForDexVm(),
+        ToolHelper.getClassAsBytes(
+            com.android.tools.r8.desugaring.interfacemethods.test0.TestMain.class),
+        patchInterfaceWithDefaults(ToolHelper.getClassAsBytes(
+            com.android.tools.r8.desugaring.interfacemethods.test0.InterfaceWithDefaults.class)));
+  }
+
+  // NOTE: this particular test is working on pre-N devices since
+  //       it's fixed by interface default method desugaring.
+  @IgnoreForRangeOfVmVersions(from = Version.V7_0_0, to = Version.DEFAULT)
+  @Test
+  public void testInvokeSpecialToDefaultMethodFromStatic() throws Exception {
+    ensureSameOutput(
+        com.android.tools.r8.desugaring.interfacemethods.test1.TestMain.class.getCanonicalName(),
+        ToolHelper.getMinApiLevelForDexVm(),
+        ToolHelper.getClassAsBytes(
+            com.android.tools.r8.desugaring.interfacemethods.test1.TestMain.class),
+        patchInterfaceWithDefaults(ToolHelper.getClassAsBytes(
+            com.android.tools.r8.desugaring.interfacemethods.test1.InterfaceWithDefaults.class)));
+  }
+
+  @Test
+  public void testInvokeSpecialToInheritedDefaultMethod() throws Exception {
+    ensureSameOutput(
+        com.android.tools.r8.desugaring.interfacemethods.test2.TestMain.class.getCanonicalName(),
+        ToolHelper.getMinApiLevelForDexVm(),
+        ToolHelper.getClassAsBytes(
+            com.android.tools.r8.desugaring.interfacemethods.test2.TestMain.class),
+        ToolHelper.getClassAsBytes(
+            com.android.tools.r8.desugaring.interfacemethods.test2.Test.class),
+        ToolHelper.getClassAsBytes(
+            com.android.tools.r8.desugaring.interfacemethods.test2.LeftTest.class),
+        ToolHelper.getClassAsBytes(
+            com.android.tools.r8.desugaring.interfacemethods.test2.RightTest.class),
+        ToolHelper.getClassAsBytes(
+            com.android.tools.r8.desugaring.interfacemethods.test2.Test2.class));
+  }
+
+  private static class MutableInteger {
+    int value;
+  }
+
+  private byte[] patchInterfaceWithDefaults(byte[] classBytes) throws IOException {
+    MutableInteger patched = new MutableInteger();
+    try (InputStream input = new ByteArrayInputStream(classBytes)) {
+      ClassReader cr = new ClassReader(input);
+      ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);
+      cr.accept(
+          new ClassVisitor(Opcodes.ASM6, cw) {
+            @Override
+            public MethodVisitor visitMethod(int access, String name,
+                String desc, String signature, String[] exceptions) {
+              MethodVisitor visitor = super.visitMethod(access, name, desc, signature, exceptions);
+              return new MethodVisitor(Opcodes.ASM6, visitor) {
+                @Override
+                public void visitMethodInsn(
+                    int opcode, String owner, String name, String desc, boolean itf) {
+                  if (opcode == Opcodes.INVOKEINTERFACE &&
+                      owner.endsWith("InterfaceWithDefaults") &&
+                      name.equals("foo")) {
+                    assertEquals(0, patched.value);
+                    super.visitMethodInsn(Opcodes.INVOKESPECIAL, owner, name, desc, itf);
+                    patched.value++;
+
+                  } else {
+                    super.visitMethodInsn(opcode, owner, name, desc, itf);
+                  }
+                }
+              };
+            }
+          }, 0);
+      assertEquals(1, patched.value);
+      return cw.toByteArray();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugaring/interfacemethods/test0/InterfaceWithDefaults.java b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/test0/InterfaceWithDefaults.java
new file mode 100644
index 0000000..dc671dc
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/test0/InterfaceWithDefaults.java
@@ -0,0 +1,18 @@
+// Copyright (c) 2018, 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.desugaring.interfacemethods.test0;
+
+public interface InterfaceWithDefaults {
+  default void foo() {
+    System.out.println("InterfaceWithDefaults::foo()");
+  }
+
+  default void bar() {
+    System.out.println("InterfaceWithDefaults::bar()");
+    this.foo();
+  }
+
+  void test();
+}
diff --git a/src/test/java/com/android/tools/r8/desugaring/interfacemethods/test0/TestMain.java b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/test0/TestMain.java
new file mode 100644
index 0000000..79ac7b0
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/test0/TestMain.java
@@ -0,0 +1,23 @@
+// Copyright (c) 2018, 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.desugaring.interfacemethods.test0;
+
+public class TestMain implements InterfaceWithDefaults {
+  @Override
+  public void test() {
+    System.out.println("TestMain::test()");
+    this.foo();
+    this.bar();
+  }
+
+  @Override
+  public void foo() {
+    System.out.println("TestMain::foo()");
+  }
+
+  public static void main(String[] args) {
+    new TestMain().test();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugaring/interfacemethods/test1/InterfaceWithDefaults.java b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/test1/InterfaceWithDefaults.java
new file mode 100644
index 0000000..6f79c96
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/test1/InterfaceWithDefaults.java
@@ -0,0 +1,18 @@
+// Copyright (c) 2018, 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.desugaring.interfacemethods.test1;
+
+public interface InterfaceWithDefaults {
+  default void foo() {
+    System.out.println("InterfaceWithDefaults::foo()");
+  }
+
+  static void bar(InterfaceWithDefaults iface) {
+    System.out.println("InterfaceWithDefaults::bar()");
+    iface.foo();
+  }
+
+  void test();
+}
diff --git a/src/test/java/com/android/tools/r8/desugaring/interfacemethods/test1/TestMain.java b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/test1/TestMain.java
new file mode 100644
index 0000000..e38a17d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/test1/TestMain.java
@@ -0,0 +1,23 @@
+// Copyright (c) 2018, 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.desugaring.interfacemethods.test1;
+
+public class TestMain implements InterfaceWithDefaults {
+  @Override
+  public void test() {
+    System.out.println("TestMain::test()");
+    this.foo();
+    InterfaceWithDefaults.bar(this);
+  }
+
+  @Override
+  public void foo() {
+    System.out.println("TestMain::foo()");
+  }
+
+  public static void main(String[] args) {
+    new TestMain().test();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugaring/interfacemethods/test2/LeftTest.java b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/test2/LeftTest.java
new file mode 100644
index 0000000..4a5b2de
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/test2/LeftTest.java
@@ -0,0 +1,8 @@
+// Copyright (c) 2018, 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.desugaring.interfacemethods.test2;
+
+public interface LeftTest extends Test {
+}
diff --git a/src/test/java/com/android/tools/r8/desugaring/interfacemethods/test2/RightTest.java b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/test2/RightTest.java
new file mode 100644
index 0000000..6c6ea7e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/test2/RightTest.java
@@ -0,0 +1,8 @@
+// Copyright (c) 2018, 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.desugaring.interfacemethods.test2;
+
+public interface RightTest extends Test {
+}
diff --git a/src/test/java/com/android/tools/r8/desugaring/interfacemethods/test2/Test.java b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/test2/Test.java
new file mode 100644
index 0000000..1bf3295
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/test2/Test.java
@@ -0,0 +1,11 @@
+// Copyright (c) 2018, 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.desugaring.interfacemethods.test2;
+
+public interface Test {
+  default String foo(String a) {
+    return "Test::foo(" + a + ")";
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugaring/interfacemethods/test2/Test2.java b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/test2/Test2.java
new file mode 100644
index 0000000..b076cde
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/test2/Test2.java
@@ -0,0 +1,11 @@
+// Copyright (c) 2018, 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.desugaring.interfacemethods.test2;
+
+public interface Test2 extends LeftTest, RightTest {
+  default String bar(String a) {
+    return "Test2::bar(" + LeftTest.super.foo(a) + " + " + RightTest.super.foo(a) + ")";
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugaring/interfacemethods/test2/TestMain.java b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/test2/TestMain.java
new file mode 100644
index 0000000..ca86963
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/test2/TestMain.java
@@ -0,0 +1,19 @@
+// Copyright (c) 2018, 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.desugaring.interfacemethods.test2;
+
+public class TestMain implements Test2 {
+  public static void main(String... args) {
+    TestMain m = new TestMain();
+    System.out.println(m.bar("first"));
+    System.out.println(m.foo("second"));
+    System.out.println(m.fooDelegate("third"));
+  }
+
+  private String fooDelegate(String a) {
+    return "TestMain::fooDelegate(" + Test2.super.foo(a) + ")";
+  }
+}
+