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) + ")"; + } +} +