Merge "Add support for -dontnote"
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInvokeDynamic.java b/src/main/java/com/android/tools/r8/cf/code/CfInvokeDynamic.java
index f830427..ca6c1de 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfInvokeDynamic.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInvokeDynamic.java
@@ -4,7 +4,6 @@
 package com.android.tools.r8.cf.code;
 
 import com.android.tools.r8.cf.CfPrinter;
-import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.DexCallSite;
 import com.android.tools.r8.graph.DexMethodHandle;
@@ -43,14 +42,7 @@
       bsmArgs[i] = decodeBootstrapArgument(bootstrapArgs.get(i), lens);
     }
     Handle bsmHandle = bootstrapMethod.toAsmHandle(lens);
-    DexString methodName;
-    if (lens.isIdentityLens()) {
-      methodName = callSite.methodName;
-    } else if (!callSite.interfaceMethods.isEmpty()) {
-      methodName = lens.lookupName(callSite.interfaceMethods.get(0));
-    } else {
-      throw new Unimplemented("Minification of non-lambda InvokeDynamic not supported");
-    }
+    DexString methodName = lens.lookupMethodName(callSite);
     visitor.visitInvokeDynamicInsn(
         methodName.toString(), callSite.methodProto.toDescriptorString(lens), bsmHandle, bsmArgs);
   }
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java b/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
index f024c13..702d628 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
@@ -3,14 +3,19 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.graph;
 
+import com.android.tools.r8.Diagnostic;
 import com.android.tools.r8.errors.CompilationError;
+import com.android.tools.r8.ir.desugar.LambdaDescriptor;
 import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.Reporter;
+import com.android.tools.r8.utils.StringDiagnostic;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Sets;
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.IdentityHashMap;
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.function.Function;
@@ -190,6 +195,55 @@
     return result;
   }
 
+  /**
+   * Resolve the methods implemented by the lambda expression that created the {@code callSite}.
+   *
+   * <p>If {@code callSite} was not created as a result of a lambda expression (i.e. the metafactory
+   * is not {@code LambdaMetafactory}), the empty set is returned.
+   *
+   * <p>If the metafactory is neither {@code LambdaMetafactory} nor {@code StringConcatFactory}, a
+   * warning is issued.
+   *
+   * <p>The returned set of methods all have {@code callSite.methodName} as the method name.
+   *
+   * @param callSite Call site to resolve.
+   * @param reporter Reporter used when an unknown metafactory is encountered.
+   * @return Methods implemented by the lambda expression that created the {@code callSite}.
+   */
+  public Set<DexEncodedMethod> lookupLambdaImplementedMethods(
+      DexCallSite callSite, Reporter reporter) {
+    List<DexType> callSiteInterfaces =
+        LambdaDescriptor.getInterfaces(callSite, this, dexItemFactory);
+    if (callSiteInterfaces == null) {
+      if (!isStringConcat(callSite.bootstrapMethod)) {
+        Diagnostic message =
+            new StringDiagnostic("Unknown bootstrap method " + callSite.bootstrapMethod);
+        reporter.warning(message);
+      }
+      return Collections.emptySet();
+    }
+    Set<DexEncodedMethod> result = new HashSet<>();
+    for (DexType iface : callSiteInterfaces) {
+      assert iface.isInterface();
+      DexClass clazz = definitionFor(iface);
+      if (clazz != null) {
+        clazz.forEachMethod(
+            method -> {
+              if (method.method.name == callSite.methodName && method.accessFlags.isAbstract()) {
+                result.add(method);
+              }
+            });
+      }
+    }
+    return result;
+  }
+
+  private boolean isStringConcat(DexMethodHandle bootstrapMethod) {
+    return bootstrapMethod.type.isInvokeStatic()
+        && (bootstrapMethod.asMethod() == dexItemFactory.stringConcatWithConstantsMethod
+            || bootstrapMethod.asMethod() == dexItemFactory.stringConcatMethod);
+  }
+
   @Override
   public void registerNewType(DexType newType, DexType superType) {
     // Register the relationship between this type and its superType.
diff --git a/src/main/java/com/android/tools/r8/graph/DexCallSite.java b/src/main/java/com/android/tools/r8/graph/DexCallSite.java
index ac954f8..c193134 100644
--- a/src/main/java/com/android/tools/r8/graph/DexCallSite.java
+++ b/src/main/java/com/android/tools/r8/graph/DexCallSite.java
@@ -29,27 +29,22 @@
   public final DexMethodHandle bootstrapMethod;
   public final List<DexValue> bootstrapArgs;
 
-  public final List<DexMethod> interfaceMethods;
-
   private DexEncodedArray encodedArray = null;
 
   DexCallSite(
       DexString methodName,
       DexProto methodProto,
       DexMethodHandle bootstrapMethod,
-      List<DexValue> bootstrapArgs,
-      List<DexMethod> interfaceMethods) {
+      List<DexValue> bootstrapArgs) {
     assert methodName != null;
     assert methodProto != null;
     assert bootstrapMethod != null;
     assert bootstrapArgs != null;
-    assert interfaceMethods != null;
 
     this.methodName = methodName;
     this.methodProto = methodProto;
     this.bootstrapMethod = bootstrapMethod;
     this.bootstrapArgs = bootstrapArgs;
-    this.interfaceMethods = interfaceMethods;
   }
 
   public static DexCallSite fromAsmInvokeDynamic(
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
index 364c0ee..6b99724 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -15,9 +15,7 @@
 import com.android.tools.r8.graph.DexDebugEvent.SetInlineFrame;
 import com.android.tools.r8.graph.DexDebugEvent.SetPrologueEnd;
 import com.android.tools.r8.graph.DexMethodHandle.MethodHandleType;
-import com.android.tools.r8.graph.DexValue.DexValueMethodType;
 import com.android.tools.r8.ir.code.Position;
-import com.android.tools.r8.ir.desugar.LambdaDescriptor;
 import com.android.tools.r8.kotlin.Kotlin;
 import com.android.tools.r8.naming.NamingLens;
 import com.google.common.base.Strings;
@@ -27,7 +25,6 @@
 import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
 import java.util.ArrayList;
 import java.util.Collection;
-import java.util.Collections;
 import java.util.HashMap;
 import java.util.IdentityHashMap;
 import java.util.List;
@@ -257,6 +254,33 @@
           createProto(callSiteType, lookupType, stringType, methodTypeType, objectArrayType),
           createString(METAFACTORY_ALT_METHOD_NAME));
 
+  public final DexType stringConcatFactoryType =
+      createType("Ljava/lang/invoke/StringConcatFactory;");
+
+  public final DexMethod stringConcatWithConstantsMethod =
+      createMethod(
+          stringConcatFactoryType,
+          createProto(
+              callSiteType,
+              lookupType,
+              stringType,
+              methodTypeType,
+              stringType,
+              objectArrayType),
+          createString("makeConcatWithConstants")
+      );
+
+  public final DexMethod stringConcatMethod =
+      createMethod(
+          stringConcatFactoryType,
+          createProto(
+              callSiteType,
+              lookupType,
+              stringType,
+              methodTypeType),
+          createString("makeConcat")
+      );
+
   private boolean skipNameValidationForTesting = false;
 
   public void setSkipNameValidationForTesting(boolean skipNameValidationForTesting) {
@@ -588,46 +612,10 @@
       DexString methodName, DexProto methodProto,
       DexMethodHandle bootstrapMethod, List<DexValue> bootstrapArgs) {
     assert !sorted;
-    List<DexMethod> interfaceMethods =
-        getCallSiteInterfaceMethods(methodName, methodProto, bootstrapMethod, bootstrapArgs);
-    DexCallSite callSite =
-        new DexCallSite(methodName, methodProto, bootstrapMethod, bootstrapArgs, interfaceMethods);
+    DexCallSite callSite = new DexCallSite(methodName, methodProto, bootstrapMethod, bootstrapArgs);
     return canonicalize(callSites, callSite);
   }
 
-  private List<DexMethod> getCallSiteInterfaceMethods(
-      DexString methodName,
-      DexProto methodProto,
-      DexMethodHandle bootstrapMethodHandle,
-      List<DexValue> bootstrapArgs) {
-    // TODO(mathiasr): Unify this with LambdaDescriptor.infer().
-    if (!bootstrapMethodHandle.type.isInvokeStatic()) {
-      return Collections.emptyList();
-    }
-    DexMethod bootstrapMethod = bootstrapMethodHandle.asMethod();
-    if (bootstrapMethod != metafactoryMethod && bootstrapMethod != metafactoryAltMethod) {
-      return Collections.emptyList();
-    }
-    DexType interfaceType = methodProto.returnType;
-    assert bootstrapMethod == metafactoryAltMethod || bootstrapArgs.size() == 3;
-    // Signature of main functional interface method.
-    // In Java docs, this argument is named 'samMethodType'.
-    DexValueMethodType funcErasedSignature = (DexValueMethodType) bootstrapArgs.get(0);
-    DexMethod mainMethod = createMethod(interfaceType, funcErasedSignature.value, methodName);
-    if (bootstrapMethod == metafactoryAltMethod) {
-      List<DexMethod> result = new ArrayList<>();
-      result.add(mainMethod);
-      LambdaDescriptor.extractAltMetafactory(
-          this,
-          bootstrapArgs,
-          type -> result.add(createMethod(type, funcErasedSignature.value, methodName)),
-          bridge -> {});
-      return result;
-    } else {
-      return Collections.singletonList(mainMethod);
-    }
-  }
-
   public DexMethod createMethod(DexString clazzDescriptor, DexString name,
       DexString returnTypeDescriptor,
       DexString[] parameterDescriptors) {
@@ -738,4 +726,8 @@
   synchronized public void forAllTypes(Consumer<DexType> f) {
     new ArrayList<>(types.values()).forEach(f);
   }
+
+  public void forAllCallSites(Consumer<DexCallSite> f) {
+    new ArrayList<>(callSites.values()).forEach(f);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaDescriptor.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaDescriptor.java
index 49a797a..b7653fa 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaDescriptor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaDescriptor.java
@@ -56,10 +56,10 @@
     targetMethod = null;
   }
 
-  private LambdaDescriptor(LambdaRewriter rewriter, DexCallSite callSite,
+  private LambdaDescriptor(AppInfo appInfo, DexCallSite callSite,
       DexString name, DexProto erasedProto, DexProto enforcedProto,
       DexMethodHandle implHandle, DexType mainInterface, DexTypeList captures) {
-    assert rewriter != null;
+    assert appInfo != null;
     assert callSite != null;
     assert name != null;
     assert erasedProto != null;
@@ -76,7 +76,7 @@
     this.captures = captures;
 
     this.interfaces.add(mainInterface);
-    this.targetMethod = lookupTargetMethod(rewriter);
+    this.targetMethod = lookupTargetMethod(appInfo);
   }
 
   final DexType getImplReceiverType() {
@@ -88,13 +88,12 @@
     return captures.length > 0 ? captures[0] : params[0];
   }
 
-  private DexEncodedMethod lookupTargetMethod(LambdaRewriter rewriter) {
+  private DexEncodedMethod lookupTargetMethod(AppInfo appInfo) {
     // Find the lambda's impl-method target.
     DexMethod method = implHandle.asMethod();
     switch (implHandle.type) {
       case INVOKE_DIRECT:
       case INVOKE_INSTANCE: {
-        AppInfo appInfo = rewriter.converter.appInfo;
         DexEncodedMethod target = appInfo.lookupVirtualTarget(getImplReceiverType(), method);
         if (target == null) {
           target = appInfo.lookupDirectTarget(method);
@@ -106,21 +105,18 @@
       }
 
       case INVOKE_STATIC: {
-        AppInfo appInfo = rewriter.converter.appInfo;
         DexEncodedMethod target = appInfo.lookupStaticTarget(method);
         assert target == null || target.accessFlags.isStatic();
         return target;
       }
 
       case INVOKE_CONSTRUCTOR: {
-        AppInfo appInfo = rewriter.converter.appInfo;
         DexEncodedMethod target = appInfo.lookupDirectTarget(method);
         assert target == null || target.accessFlags.isConstructor();
         return target;
       }
 
       case INVOKE_INTERFACE: {
-        AppInfo appInfo = rewriter.converter.appInfo;
         DexEncodedMethod target = appInfo.lookupVirtualTarget(getImplReceiverType(), method);
         assert target == null || isInstanceMethod(target);
         return target;
@@ -220,7 +216,7 @@
    * Matches call site for lambda metafactory invocation pattern and
    * returns extracted match information, or null if match failed.
    */
-  static LambdaDescriptor infer(LambdaRewriter rewriter, DexCallSite callSite) {
+  static LambdaDescriptor infer(DexCallSite callSite, AppInfo appInfo, DexItemFactory factory) {
     // We expect bootstrap method to be either `metafactory` or `altMetafactory` method
     // of `java.lang.invoke.LambdaMetafactory` class. Both methods are static.
     if (!callSite.bootstrapMethod.type.isInvokeStatic()) {
@@ -228,8 +224,8 @@
     }
 
     DexMethod bootstrapMethod = callSite.bootstrapMethod.asMethod();
-    boolean isMetafactoryMethod = bootstrapMethod == rewriter.factory.metafactoryMethod;
-    boolean isAltMetafactoryMethod = bootstrapMethod == rewriter.factory.metafactoryAltMethod;
+    boolean isMetafactoryMethod = bootstrapMethod == factory.metafactoryMethod;
+    boolean isAltMetafactoryMethod = bootstrapMethod == factory.metafactoryAltMethod;
     if (!isMetafactoryMethod && !isAltMetafactoryMethod) {
       // It is not a lambda, thus no need to manage this call site.
       return LambdaDescriptor.MATCH_FAILED;
@@ -254,7 +250,7 @@
     DexValue.DexValueMethodType funcEnforcedSignature =
         getBootstrapArgument(callSite.bootstrapArgs, 2, DexValue.DexValueMethodType.class);
     if (!isEnforcedSignatureValid(
-        rewriter, funcEnforcedSignature.value, funcErasedSignature.value)) {
+        factory, funcEnforcedSignature.value, funcErasedSignature.value)) {
       throw new Unreachable(
           "Enforced and erased signatures are inconsistent in " + callSite.toString());
     }
@@ -268,7 +264,7 @@
     DexTypeList captures = lambdaFactoryProto.parameters;
 
     // Create a match.
-    LambdaDescriptor match = new LambdaDescriptor(rewriter, callSite,
+    LambdaDescriptor match = new LambdaDescriptor(appInfo, callSite,
         funcMethodName, funcErasedSignature.value, funcEnforcedSignature.value,
         lambdaImplMethodHandle, mainFuncInterface, captures);
 
@@ -279,7 +275,7 @@
       }
     } else {
       extractAltMetafactory(
-          rewriter.factory,
+          factory,
           callSite.bootstrapArgs,
           interfaceType -> {
             if (!match.interfaces.contains(interfaceType)) {
@@ -292,7 +288,7 @@
     return match;
   }
 
-  public static void extractAltMetafactory(
+  private static void extractAltMetafactory(
       DexItemFactory dexItemFactory,
       List<DexValue> bootstrapArgs,
       Consumer<DexType> interfaceConsumer,
@@ -333,6 +329,16 @@
     }
   }
 
+  public static List<DexType> getInterfaces(
+      DexCallSite callSite, AppInfo appInfo, DexItemFactory factory) {
+    LambdaDescriptor descriptor = infer(callSite, appInfo, factory);
+    if (descriptor == LambdaDescriptor.MATCH_FAILED) {
+      return null;
+    }
+    assert descriptor.interfaces != null;
+    return descriptor.interfaces;
+  }
+
   @SuppressWarnings("unchecked")
   private static <T> T getBootstrapArgument(List<DexValue> bootstrapArgs, int i, Class<T> clazz) {
     if (bootstrapArgs.size() < i) {
@@ -347,8 +353,8 @@
   }
 
   private static boolean isEnforcedSignatureValid(
-      LambdaRewriter rewriter, DexProto enforced, DexProto erased) {
-    if (!isSameOrDerived(rewriter.factory, enforced.returnType, erased.returnType)) {
+      DexItemFactory factory, DexProto enforced, DexProto erased) {
+    if (!isSameOrDerived(factory, enforced.returnType, erased.returnType)) {
       return false;
     }
     DexType[] enforcedValues = enforced.parameters.values;
@@ -358,7 +364,7 @@
       return false;
     }
     for (int i = 0; i < count; i++) {
-      if (!isSameOrDerived(rewriter.factory, enforcedValues[i], erasedValues[i])) {
+      if (!isSameOrDerived(factory, enforcedValues[i], erasedValues[i])) {
         return false;
       }
     }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
index 82792df..11ff65b 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
@@ -190,8 +190,12 @@
     // in rare case when another thread has same call site processed concurrently,
     // but this is a low price to pay comparing to making whole method synchronous.
     LambdaDescriptor descriptor = getKnown(knownCallSites, callSite);
-    return descriptor != null ? descriptor
-        : putIfAbsent(knownCallSites, callSite, LambdaDescriptor.infer(this, callSite));
+    return descriptor != null
+        ? descriptor
+        : putIfAbsent(
+            knownCallSites,
+            callSite,
+            LambdaDescriptor.infer(callSite, this.converter.appInfo, this.factory));
   }
 
   private boolean isInMainDexList(DexType type) {
diff --git a/src/main/java/com/android/tools/r8/naming/MemberNameMinifier.java b/src/main/java/com/android/tools/r8/naming/MemberNameMinifier.java
index 9e85188..1358598 100644
--- a/src/main/java/com/android/tools/r8/naming/MemberNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/MemberNameMinifier.java
@@ -9,6 +9,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.shaking.RootSetBuilder.RootSet;
 import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.Reporter;
 import com.google.common.collect.ImmutableList;
 import java.util.IdentityHashMap;
 import java.util.Map;
@@ -25,6 +26,7 @@
   protected final NamingState<StateType, ?> globalState;
   protected final boolean useUniqueMemberNames;
   protected final boolean overloadAggressively;
+  protected final Reporter reporter;
 
   MemberNameMinifier(AppInfoWithSubtyping appInfo, RootSet rootSet, InternalOptions options) {
     this.appInfo = appInfo;
@@ -32,6 +34,7 @@
     this.dictionary = options.proguardConfiguration.getObfuscationDictionary();
     this.useUniqueMemberNames = options.proguardConfiguration.isUseUniqueClassMemberNames();
     this.overloadAggressively = options.proguardConfiguration.isOverloadAggressively();
+    this.reporter = options.reporter;
     this.globalState = NamingState.createRoot(
         appInfo.dexItemFactory, dictionary, getKeyTransform(), useUniqueMemberNames);
   }
diff --git a/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java b/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java
index 25a84b6..c6729e8 100644
--- a/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.naming;
 
 import com.android.tools.r8.graph.AppInfoWithSubtyping;
+import com.android.tools.r8.graph.DexCallSite;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexMethod;
@@ -18,11 +19,13 @@
 import com.google.common.base.Equivalence.Wrapper;
 import com.google.common.collect.Sets;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.IdentityHashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Map.Entry;
 import java.util.Set;
 import java.util.function.Function;
 
@@ -88,6 +91,7 @@
 class MethodNameMinifier extends MemberNameMinifier<DexMethod, DexProto> {
 
   private final Equivalence<DexMethod> equivalence = MethodSignatureEquivalence.get();
+  private final Map<DexCallSite, DexString> callSiteRenaming = new IdentityHashMap<>();
 
   MethodNameMinifier(AppInfoWithSubtyping appInfo, RootSet rootSet, InternalOptions options) {
     super(appInfo, rootSet, options);
@@ -104,7 +108,18 @@
     }
   }
 
-  Map<DexMethod, DexString> computeRenaming(Timing timing) {
+  static class MethodRenaming {
+    final Map<DexMethod, DexString> renaming;
+    final Map<DexCallSite, DexString> callSiteRenaming;
+
+    private MethodRenaming(
+        Map<DexMethod, DexString> renaming, Map<DexCallSite, DexString> callSiteRenaming) {
+      this.renaming = renaming;
+      this.callSiteRenaming = callSiteRenaming;
+    }
+  }
+
+  MethodRenaming computeRenaming(Timing timing) {
     // Phase 1: Reserve all the names that need to be kept and allocate linked state in the
     //          library part.
     timing.begin("Phase 1");
@@ -133,7 +148,7 @@
     assignNamesToClassesMethods(appInfo.dexItemFactory.objectType, true);
     timing.end();
 
-    return renaming;
+    return new MethodRenaming(renaming, callSiteRenaming);
   }
 
   private void assignNamesToClassesMethods(DexType type, boolean doPrivates) {
@@ -210,6 +225,53 @@
             method, collectedStates, globalStateMap, sourceMethodsMap, originStates, iface));
       }
     });
+    // If the input program contains a multi-interface lambda expression that implements
+    // interface methods with different protos, we need to make sure that
+    // the implemented lambda methods are renamed to the same name.
+    // To achieve this, we map each DexCallSite that corresponds to a lambda expression to one of
+    // the DexMethods it implements, and we unify the DexMethods that need to be renamed together.
+    Map<DexCallSite, DexMethod> callSites = new IdentityHashMap<>();
+    // Union-find structure to keep track of methods that must be renamed together.
+    // Note that if the input does not use multi-interface lambdas,
+    // unificationParent will remain empty.
+    Map<Wrapper<DexMethod>, Wrapper<DexMethod>> unificationParent = new HashMap<>();
+    appInfo.dexItemFactory.forAllCallSites(
+        callSite -> {
+          Set<Wrapper<DexMethod>> callSiteMethods = new HashSet<>();
+          Set<DexEncodedMethod> implementedMethods =
+              appInfo.lookupLambdaImplementedMethods(callSite, reporter);
+          if (implementedMethods.isEmpty()) {
+            return;
+          }
+          callSites.put(callSite, implementedMethods.iterator().next().method);
+          for (DexEncodedMethod method : implementedMethods) {
+            DexType iface = method.method.holder;
+            assert iface.isInterface();
+            Set<NamingState<DexProto, ?>> collectedStates = getReachableStates(iface, frontierMap);
+            addStatesToGlobalMapForMethod(
+                method, collectedStates, globalStateMap, sourceMethodsMap, originStates, iface);
+            callSiteMethods.add(equivalence.wrap(method.method));
+          }
+          if (callSiteMethods.size() > 1) {
+            // Implemented interfaces have different return types. Unify them.
+            Wrapper<DexMethod> mainKey = callSiteMethods.iterator().next();
+            mainKey = unificationParent.getOrDefault(mainKey, mainKey);
+            for (Wrapper<DexMethod> key : callSiteMethods) {
+              unificationParent.put(key, mainKey);
+            }
+          }
+        });
+    Map<Wrapper<DexMethod>, Set<Wrapper<DexMethod>>> unification = new HashMap<>();
+    for (Wrapper<DexMethod> key : unificationParent.keySet()) {
+      // Find root with path-compression.
+      Wrapper<DexMethod> root = unificationParent.get(key);
+      while (unificationParent.get(root) != root) {
+        Wrapper<DexMethod> k = unificationParent.get(unificationParent.get(root));
+        unificationParent.put(root, k);
+        root = k;
+      }
+      unification.computeIfAbsent(root, k -> new HashSet<>()).add(key);
+    }
     timing.end();
     // Go over every method and assign a name.
     timing.begin("Allocate names");
@@ -218,12 +280,36 @@
     List<Wrapper<DexMethod>> methods = new ArrayList<>(globalStateMap.keySet());
     methods.sort((a, b) -> globalStateMap.get(b).size() - globalStateMap.get(a).size());
     for (Wrapper<DexMethod> key : methods) {
+      if (!unificationParent.getOrDefault(key, key).equals(key)) {
+        continue;
+      }
+      List<MethodNamingState> collectedStates = new ArrayList<>();
+      Set<DexMethod> sourceMethods = Sets.newIdentityHashSet();
+      for (Wrapper<DexMethod> k : unification.getOrDefault(key, Collections.singleton(key))) {
+        DexMethod unifiedMethod = k.get();
+        assert unifiedMethod != null;
+        sourceMethods.addAll(sourceMethodsMap.get(k));
+        for (NamingState<DexProto, ?> namingState : globalStateMap.get(k)) {
+          collectedStates.add(
+              new MethodNamingState(namingState, unifiedMethod.name, unifiedMethod.proto));
+        }
+      }
       DexMethod method = key.get();
-      assignNameForInterfaceMethodInAllStates(
-          method,
-          globalStateMap.get(key),
-          sourceMethodsMap.get(key),
-          originStates.get(key));
+      assert method != null;
+      MethodNamingState originState =
+          new MethodNamingState(originStates.get(key), method.name, method.proto);
+      assignNameForInterfaceMethodInAllStates(collectedStates, sourceMethods, originState);
+    }
+    for (Entry<DexCallSite, DexMethod> entry : callSites.entrySet()) {
+      DexMethod method = entry.getValue();
+      DexString renamed = renaming.get(method);
+      if (originStates.get(equivalence.wrap(method)).isReserved(method.name, method.proto)) {
+        assert renamed == null;
+        callSiteRenaming.put(entry.getKey(), method.name);
+      } else {
+        assert renamed != null;
+        callSiteRenaming.put(entry.getKey(), renamed);
+      }
     }
     timing.end();
   }
@@ -264,41 +350,31 @@
   }
 
   private void assignNameForInterfaceMethodInAllStates(
-      DexMethod method,
-      Set<NamingState<DexProto, ?>> collectedStates,
+      List<MethodNamingState> collectedStates,
       Set<DexMethod> sourceMethods,
-      NamingState<DexProto, ?> originState) {
-    boolean isReserved = false;
-    if (globalState.isReserved(method.name, method.proto)) {
-      for (NamingState<DexProto, ?> state : collectedStates) {
-        if (state.isReserved(method.name, method.proto)) {
-          isReserved = true;
-          break;
-        }
+      MethodNamingState originState) {
+    if (anyIsReserved(collectedStates)) {
+      // This method's name is reserved in at least one naming state, so reserve it everywhere.
+      for (MethodNamingState state : collectedStates) {
+        state.reserveName();
       }
-      if (isReserved) {
-        // This method's name is reserved in at least on naming state, so reserve it everywhere.
-        for (NamingState<DexProto, ?> state : collectedStates) {
-          state.reserveName(method.name, method.proto);
-        }
-        return;
-      }
+      return;
     }
     // We use the origin state to allocate a name here so that we can reuse names between different
     // unrelated interfaces. This saves some space. The alternative would be to use a global state
     // for allocating names, which would save the work to search here.
     DexString candidate = null;
     do {
-      candidate = originState.assignNewNameFor(method.name, method.proto, false);
-      for (NamingState<DexProto, ?> state : collectedStates) {
-        if (!state.isAvailable(method.name, method.proto, candidate)) {
+      candidate = originState.assignNewNameFor(false);
+      for (MethodNamingState state : collectedStates) {
+        if (!state.isAvailable(candidate)) {
           candidate = null;
           break;
         }
       }
     } while (candidate == null);
-    for (NamingState<DexProto, ?> state : collectedStates) {
-      state.addRenaming(method.name, method.proto, candidate);
+    for (MethodNamingState state : collectedStates) {
+      state.addRenaming(candidate);
     }
     // Rename all methods in interfaces that gave rise to this renaming.
     for (DexMethod sourceMethod : sourceMethods) {
@@ -306,6 +382,20 @@
     }
   }
 
+  private boolean anyIsReserved(List<MethodNamingState> collectedStates) {
+    DexString name = collectedStates.get(0).getName();
+    Map<DexProto, Boolean> globalStateCache = new HashMap<>();
+    for (MethodNamingState state : collectedStates) {
+      assert state.getName() == name;
+      if (globalStateCache.computeIfAbsent(
+              state.getProto(), proto -> globalState.isReserved(name, proto))
+          && state.isReserved()) {
+        return true;
+      }
+    }
+    return false;
+  }
+
   private void reserveNamesInClasses(DexType type, DexType libraryFrontier,
       NamingState<DexProto, ?> parent,
       Map<DexType, DexType> frontierMap) {
@@ -356,4 +446,51 @@
       globalState.reserveName(method.method.name, method.method.proto);
     }
   }
+
+  /**
+   * Capture a (name, proto)-pair for a {@link NamingState}. Each method methodState.METHOD(...)
+   * simply defers to parent.METHOD(name, proto, ...). This allows R8 to assign the same name to
+   * methods with different prototypes, which is needed for multi-interface lambdas.
+   */
+  private static class MethodNamingState {
+
+    private final NamingState<DexProto, ?> parent;
+    private final DexString name;
+    private final DexProto proto;
+
+    MethodNamingState(NamingState<DexProto, ?> parent, DexString name, DexProto proto) {
+      assert parent != null;
+      this.parent = parent;
+      this.name = name;
+      this.proto = proto;
+    }
+
+    DexString assignNewNameFor(boolean markAsUsed) {
+      return parent.assignNewNameFor(name, proto, markAsUsed);
+    }
+
+    void reserveName() {
+      parent.reserveName(name, proto);
+    }
+
+    boolean isReserved() {
+      return parent.isReserved(name, proto);
+    }
+
+    boolean isAvailable(DexString candidate) {
+      return parent.isAvailable(name, proto, candidate);
+    }
+
+    void addRenaming(DexString newName) {
+      parent.addRenaming(name, proto, newName);
+    }
+
+    DexString getName() {
+      return name;
+    }
+
+    DexProto getProto() {
+      return proto;
+    }
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/naming/Minifier.java b/src/main/java/com/android/tools/r8/naming/Minifier.java
index 6bd0c6d..382ac92 100644
--- a/src/main/java/com/android/tools/r8/naming/Minifier.java
+++ b/src/main/java/com/android/tools/r8/naming/Minifier.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.naming;
 
 import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.DexCallSite;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexField;
@@ -11,6 +12,7 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.naming.MethodNameMinifier.MethodRenaming;
 import com.android.tools.r8.optimize.MemberRebindingAnalysis;
 import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.RootSetBuilder.RootSet;
@@ -44,7 +46,7 @@
         new ClassNameMinifier(appInfo, rootSet, options).computeRenaming(timing);
     timing.end();
     timing.begin("MinifyMethods");
-    Map<DexMethod, DexString> methodRenaming =
+    MethodRenaming methodRenaming =
         new MethodNameMinifier(appInfo, rootSet, options).computeRenaming(timing);
     timing.end();
     timing.begin("MinifyFields");
@@ -64,12 +66,15 @@
     private final AppInfo appInfo;
     private final Map<DexItem, DexString> renaming = new IdentityHashMap<>();
 
-    private MinifiedRenaming(Map<DexType, DexString> classRenaming,
-        Map<DexMethod, DexString> methodRenaming, Map<DexField, DexString> fieldRenaming,
+    private MinifiedRenaming(
+        Map<DexType, DexString> classRenaming,
+        MethodRenaming methodRenaming,
+        Map<DexField, DexString> fieldRenaming,
         AppInfo appInfo) {
       this.appInfo = appInfo;
       renaming.putAll(classRenaming);
-      renaming.putAll(methodRenaming);
+      renaming.putAll(methodRenaming.renaming);
+      renaming.putAll(methodRenaming.callSiteRenaming);
       renaming.putAll(fieldRenaming);
     }
 
@@ -90,6 +95,11 @@
     }
 
     @Override
+    public DexString lookupMethodName(DexCallSite callSite) {
+      return renaming.getOrDefault(callSite, callSite.methodName);
+    }
+
+    @Override
     public DexString lookupName(DexField field) {
       return renaming.getOrDefault(field, field.name);
     }
diff --git a/src/main/java/com/android/tools/r8/naming/NamingLens.java b/src/main/java/com/android/tools/r8/naming/NamingLens.java
index fe73dc5..f1f13b3 100644
--- a/src/main/java/com/android/tools/r8/naming/NamingLens.java
+++ b/src/main/java/com/android/tools/r8/naming/NamingLens.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.naming;
 
+import com.android.tools.r8.graph.DexCallSite;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexItem;
 import com.android.tools.r8.graph.DexMethod;
@@ -35,6 +36,8 @@
 
   public abstract DexString lookupName(DexMethod method);
 
+  public abstract DexString lookupMethodName(DexCallSite callSite);
+
   public abstract DexString lookupName(DexField field);
 
   public static NamingLens getIdentityLens() {
@@ -86,6 +89,11 @@
     }
 
     @Override
+    public DexString lookupMethodName(DexCallSite callSite) {
+      return callSite.methodName;
+    }
+
+    @Override
     public DexString lookupName(DexField field) {
       return field.name;
     }
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 3a31203..25f5cc1 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -158,6 +158,12 @@
   private final SetWithReason<DexEncodedField> liveFields = new SetWithReason<>();
 
   /**
+   * Set of interface types for which a lambda expression can be reached. These never have a single
+   * interface implementation.
+   */
+  private final SetWithReason<DexType> instantiatedLambdas = new SetWithReason<>();
+
+  /**
    * A queue of items that need processing. Different items trigger different actions:
    */
   private final Queue<Action> workList = Queues.newArrayDeque();
@@ -438,6 +444,15 @@
       return false;
     }
 
+    @Override
+    public void registerCallSite(DexCallSite callSite) {
+      super.registerCallSite(callSite);
+      for (DexEncodedMethod method :
+          appInfo.lookupLambdaImplementedMethods(callSite, options.reporter)) {
+        markLambdaInstantiated(method.method.holder, currentMethod);
+      }
+    }
+
     private boolean registerConstClassOrCheckCast(DexType type) {
       if (forceProguardCompatibility) {
         DexType baseType = type.toBaseType(appInfo.dexItemFactory);
@@ -817,6 +832,10 @@
     workList.add(Action.markInstantiated(clazz, KeepReason.instantiatedIn(method)));
   }
 
+  private void markLambdaInstantiated(DexType itf, DexEncodedMethod method) {
+    instantiatedLambdas.add(itf, KeepReason.instantiatedIn(method));
+  }
+
   private void markDirectStaticOrConstructorMethodAsLive(
       DexEncodedMethod encodedMethod, KeepReason reason) {
     assert encodedMethod != null;
@@ -1529,12 +1548,17 @@
      */
     final Map<DexType, Reference2IntMap<DexField>> ordinalsMaps;
 
+    final ImmutableSortedSet<DexType> instantiatedLambdas;
+
     private AppInfoWithLiveness(AppInfoWithSubtyping appInfo, Enqueuer enqueuer) {
       super(appInfo);
       this.liveTypes = ImmutableSortedSet.copyOf(
           PresortedComparable<DexType>::slowCompareTo, enqueuer.liveTypes);
       this.instantiatedTypes = ImmutableSortedSet.copyOf(
           PresortedComparable<DexType>::slowCompareTo, enqueuer.instantiatedTypes.getItems());
+      this.instantiatedLambdas =
+          ImmutableSortedSet.copyOf(
+              PresortedComparable<DexType>::slowCompareTo, enqueuer.instantiatedLambdas.getItems());
       this.targetedMethods = toSortedDescriptorSet(enqueuer.targetedMethods.getItems());
       this.liveMethods = toSortedDescriptorSet(enqueuer.liveMethods.getItems());
       this.liveFields = toSortedDescriptorSet(enqueuer.liveFields.getItems());
@@ -1568,6 +1592,7 @@
       super(application);
       this.liveTypes = previous.liveTypes;
       this.instantiatedTypes = previous.instantiatedTypes;
+      this.instantiatedLambdas = previous.instantiatedLambdas;
       this.targetedMethods = previous.targetedMethods;
       this.liveMethods = previous.liveMethods;
       this.liveFields = previous.liveFields;
@@ -1603,6 +1628,7 @@
       super(application, lense);
       this.liveTypes = rewriteItems(previous.liveTypes, lense::lookupType);
       this.instantiatedTypes = rewriteItems(previous.instantiatedTypes, lense::lookupType);
+      this.instantiatedLambdas = rewriteItems(previous.instantiatedLambdas, lense::lookupType);
       this.targetedMethods = rewriteItems(previous.targetedMethods, lense::lookupMethod);
       this.liveMethods = rewriteItems(previous.liveMethods, lense::lookupMethod);
       this.liveFields = rewriteItems(previous.liveFields, lense::lookupField);
@@ -1645,6 +1671,7 @@
       super(previous);
       this.liveTypes = previous.liveTypes;
       this.instantiatedTypes = previous.instantiatedTypes;
+      this.instantiatedLambdas = previous.instantiatedLambdas;
       this.targetedMethods = previous.targetedMethods;
       this.liveMethods = previous.liveMethods;
       this.liveFields = previous.liveFields;
@@ -2021,6 +2048,9 @@
 
     public DexEncodedMethod lookupSingleInterfaceTarget(
         DexMethod method, DexType refinedReceiverType) {
+      if (instantiatedLambdas.contains(method.holder)) {
+        return null;
+      }
       DexClass holder = definitionFor(method.holder);
       if ((holder == null) || holder.isLibraryClass() || !holder.accessFlags.isInterface()) {
         return null;
diff --git a/src/main/java/com/android/tools/r8/utils/AbortException.java b/src/main/java/com/android/tools/r8/utils/AbortException.java
index 897e022..0253dfa 100644
--- a/src/main/java/com/android/tools/r8/utils/AbortException.java
+++ b/src/main/java/com/android/tools/r8/utils/AbortException.java
@@ -9,5 +9,11 @@
  * {@link com.android.tools.r8.DiagnosticsHandler}.
  */
 public class AbortException extends RuntimeException {
+  public AbortException() {
 
+  }
+
+  public AbortException(String message) {
+    super(message);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/utils/Reporter.java b/src/main/java/com/android/tools/r8/utils/Reporter.java
index 8ce1c09..63b95b3 100644
--- a/src/main/java/com/android/tools/r8/utils/Reporter.java
+++ b/src/main/java/com/android/tools/r8/utils/Reporter.java
@@ -15,6 +15,7 @@
 
   private final DiagnosticsHandler clientHandler;
   private int errorCount = 0;
+  private Diagnostic lastError;
   private final Collection<Throwable> suppressedExceptions = new ArrayList<>();
 
   public Reporter(DiagnosticsHandler clientHandler) {
@@ -35,6 +36,7 @@
   public void error(Diagnostic error) {
     clientHandler.error(error);
     synchronized (this) {
+      lastError = error;
       errorCount++;
     }
   }
@@ -46,6 +48,7 @@
   public void error(Diagnostic error, Throwable suppressedException) {
     clientHandler.error(error);
     synchronized (this) {
+      lastError = error;
       errorCount++;
       suppressedExceptions.add(suppressedException);
     }
@@ -75,7 +78,12 @@
   public void failIfPendingErrors() {
     synchronized (this) {
       if (errorCount != 0) {
-        AbortException abort = new AbortException();
+        AbortException abort;
+        if (lastError != null && lastError.getDiagnosticMessage() != null) {
+          abort = new AbortException("Error: " + lastError.getDiagnosticMessage());
+        } else {
+          abort = new AbortException();
+        }
         throw addSuppressedExceptions(abort);
       }
     }
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index bd66301..43a443e 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -1148,13 +1148,20 @@
   public static ProcessResult runProguardRaw(
       String proguardScript, Path inJar, Path outJar, List<Path> configs, Path map)
       throws IOException {
+    return runProguardRaw(
+        proguardScript, inJar, outJar, ToolHelper.getDefaultAndroidJar(), configs, map);
+  }
+
+  public static ProcessResult runProguardRaw(
+      String proguardScript, Path inJar, Path outJar, Path lib, List<Path> configs, Path map)
+      throws IOException {
     List<String> command = new ArrayList<>();
     command.add(proguardScript);
     command.add("-forceprocessing");  // Proguard just checks the creation time on the in/out jars.
     command.add("-injars");
     command.add(inJar.toString());
     command.add("-libraryjars");
-    command.add(ToolHelper.getDefaultAndroidJar().toString());
+    command.add(lib.toString());
     configs.forEach(config -> command.add("@" + config));
     command.add("-outjar");
     command.add(outJar.toString());
@@ -1196,6 +1203,11 @@
     return runProguardRaw(getProguard6Script(), inJar, outJar, ImmutableList.of(config), map);
   }
 
+  public static ProcessResult runProguard6Raw(
+      Path inJar, Path outJar, Path lib, Path config, Path map) throws IOException {
+    return runProguardRaw(getProguard6Script(), inJar, outJar, lib, ImmutableList.of(config), map);
+  }
+
   public static String runProguard6(Path inJar, Path outJar, Path config, Path map)
       throws IOException {
     return runProguard6(inJar, outJar, ImmutableList.of(config), map);
diff --git a/src/test/java/com/android/tools/r8/naming/LambdaRenamingTest.java b/src/test/java/com/android/tools/r8/naming/LambdaRenamingTest.java
new file mode 100644
index 0000000..e6b44b3
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/LambdaRenamingTest.java
@@ -0,0 +1,185 @@
+// 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.naming;
+
+public class LambdaRenamingTest {
+
+  public static final Class[] CLASSES = {
+    LambdaRenamingTest.class,
+    Interface.class,
+    DummyInterface1.class,
+    DummyInterface2.class,
+    DummyInterface3.class,
+    DummyInterface4.class,
+    ObjectInterface.class,
+    IntegerInterface.class,
+    ReservedNameObjectInterface1.class,
+    ReservedNameIntegerInterface1.class,
+    ReservedNameObjectInterface2.class,
+    ReservedNameIntegerInterface2.class,
+    InexactImplementation.class,
+    DummyImplementation.class,
+    ReservedImplementation1.class,
+    ReservedImplementation2.class,
+  };
+
+  interface Interface {
+    String method();
+  }
+
+  // Interface methods are renamed in decreasing order of number of interfaces they appear in.
+  // Define many interfaces with "Object foo();" so that it will receive the first name "a".
+  // Define "Object inexactMethod();" in one of them so that it will be named "b".
+  // Then define "Integer inexactMethod();" in an unrelated interface so it will be named "a"
+  // (when renaming aggressively).
+  interface DummyInterface1 {
+    Object foo();
+
+    Object inexactMethod();
+  }
+
+  interface DummyInterface2 {
+    Object foo();
+  }
+
+  interface DummyInterface3 {
+    Object foo();
+  }
+
+  interface DummyInterface4 {
+    Object foo();
+  }
+
+  interface ObjectInterface {
+    Object inexactMethod();
+  }
+
+  interface IntegerInterface {
+    Integer inexactMethod();
+  }
+
+  interface ReservedNameObjectInterface1 {
+    // The following method is explicitly kept in the test's ProGuard config.
+    Object reservedMethod1();
+  }
+
+  interface ReservedNameIntegerInterface1 {
+    Integer reservedMethod1();
+  }
+
+  interface ReservedNameObjectInterface2 {
+    Object reservedMethod2();
+  }
+
+  interface ReservedNameIntegerInterface2 {
+    // The following method is explicitly kept in the test's ProGuard config.
+    Integer reservedMethod2();
+  }
+
+  static class InexactImplementation implements ObjectInterface, DummyInterface1, IntegerInterface {
+    @Override
+    public Object foo() {
+      return null;
+    }
+
+    @Override
+    public Integer inexactMethod() {
+      return 10;
+    }
+  }
+
+  static class DummyImplementation implements DummyInterface1 {
+    @Override
+    public Object foo() {
+      return null;
+    }
+
+    @Override
+    public Integer inexactMethod() {
+      return 11;
+    }
+  }
+
+  static class ReservedImplementation1
+      implements ReservedNameIntegerInterface1, ReservedNameObjectInterface1 {
+    @Override
+    public Integer reservedMethod1() {
+      return 101;
+    }
+  }
+
+  static class ReservedImplementation2
+      implements ReservedNameIntegerInterface2, ReservedNameObjectInterface2 {
+    @Override
+    public Integer reservedMethod2() {
+      return 102;
+    }
+  }
+
+  public static void main(String[] args) {
+    dummyMethod(new InexactImplementation(), () -> null, () -> null, () -> null);
+    dummyMethod(new DummyImplementation(), () -> null, () -> null, () -> null);
+    invokeInteger(new InexactImplementation());
+    invokeInteger((IntegerInterface) getInexactLambda());
+    invokeObject(new InexactImplementation());
+    invokeObject((ObjectInterface) getInexactLambda());
+    invokeIntegerReserved1(new ReservedImplementation1());
+    invokeIntegerReserved1((ReservedNameIntegerInterface1) getReservedLambda1());
+    invokeObjectReserved1(new ReservedImplementation1());
+    invokeObjectReserved1((ReservedNameObjectInterface1) getReservedLambda1());
+    invokeIntegerReserved2(new ReservedImplementation2());
+    invokeIntegerReserved2((ReservedNameIntegerInterface2) getReservedLambda2());
+    invokeObjectReserved2(new ReservedImplementation2());
+    invokeObjectReserved2((ReservedNameObjectInterface2) getReservedLambda2());
+  }
+
+  private static Object getInexactLambda() {
+    return (IntegerInterface & ObjectInterface) () -> 30;
+  }
+
+  private static Object getReservedLambda1() {
+    return (ReservedNameIntegerInterface1 & ReservedNameObjectInterface1) () -> 301;
+  }
+
+  private static Object getReservedLambda2() {
+    return (ReservedNameIntegerInterface2 & ReservedNameObjectInterface2) () -> 302;
+  }
+
+  private static void dummyMethod(
+      DummyInterface1 instance1,
+      DummyInterface2 instance2,
+      DummyInterface3 instance3,
+      DummyInterface4 instance4) {
+    System.out.println(instance1.foo());
+    System.out.println(instance2.foo());
+    System.out.println(instance3.foo());
+    System.out.println(instance4.foo());
+    System.out.println(instance1.inexactMethod());
+  }
+
+  private static void invokeInteger(IntegerInterface instance) {
+    System.out.println(instance.inexactMethod());
+  }
+
+  private static void invokeObject(ObjectInterface instance) {
+    System.out.println(instance.inexactMethod());
+  }
+
+  private static void invokeIntegerReserved1(ReservedNameIntegerInterface1 instance) {
+    System.out.println(instance.reservedMethod1());
+  }
+
+  private static void invokeObjectReserved1(ReservedNameObjectInterface1 instance) {
+    System.out.println(instance.reservedMethod1());
+  }
+
+  private static void invokeIntegerReserved2(ReservedNameIntegerInterface2 instance) {
+    System.out.println(instance.reservedMethod2());
+  }
+
+  private static void invokeObjectReserved2(ReservedNameObjectInterface2 instance) {
+    System.out.println(instance.reservedMethod2());
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/LambdaRenamingTestRunner.java b/src/test/java/com/android/tools/r8/naming/LambdaRenamingTestRunner.java
new file mode 100644
index 0000000..7a2123e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/LambdaRenamingTestRunner.java
@@ -0,0 +1,148 @@
+// 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.naming;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+
+import com.android.tools.r8.ClassFileConsumer;
+import com.android.tools.r8.ClassFileConsumer.ArchiveConsumer;
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.DexIndexedConsumer;
+import com.android.tools.r8.ProgramConsumer;
+import com.android.tools.r8.R8Command;
+import com.android.tools.r8.R8Command.Builder;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.FileUtils;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import org.junit.Before;
+import org.junit.Test;
+
+public class LambdaRenamingTestRunner extends TestBase {
+  static final Class CLASS = LambdaRenamingTest.class;
+  static final Class[] CLASSES = LambdaRenamingTest.CLASSES;
+
+  private Path inputJar;
+  private ProcessResult runInput;
+
+  @Before
+  public void writeAndRunInputJar() throws IOException {
+    inputJar = temp.getRoot().toPath().resolve("input.jar");
+    ArchiveConsumer buildInput = new ArchiveConsumer(inputJar);
+    for (Class clazz : CLASSES) {
+      buildInput.accept(
+          ToolHelper.getClassAsBytes(clazz),
+          DescriptorUtils.javaTypeToDescriptor(clazz.getName()),
+          null);
+    }
+    buildInput.finished(null);
+    runInput = ToolHelper.runJava(inputJar, CLASS.getCanonicalName());
+    assertEquals(0, runInput.exitCode);
+  }
+
+  private Path writeProguardRules(boolean aggressive) throws IOException {
+    Path pgConfig = temp.getRoot().toPath().resolve("keep.txt");
+    FileUtils.writeTextFile(
+        pgConfig,
+        "-keep public class " + CLASS.getCanonicalName() + " {",
+        "  public static void main(...);",
+        "}",
+        "-keep interface " + CLASS.getCanonicalName() + "$ReservedNameObjectInterface1 {",
+        "  public java.lang.Object reservedMethod1();",
+        "}",
+        "-keep interface " + CLASS.getCanonicalName() + "$ReservedNameIntegerInterface2 {",
+        "  public java.lang.Integer reservedMethod2();",
+        "}",
+        aggressive ? "-overloadaggressively" : "# Not overloading aggressively");
+    return pgConfig;
+  }
+
+  @Test
+  public void testProguard() throws Exception {
+    buildAndRunProguard("pg.jar", false);
+  }
+
+  @Test
+  public void testProguardAggressive() throws Exception {
+    buildAndRunProguard("pg-aggressive.jar", true);
+  }
+
+  @Test
+  public void testCf() throws Exception {
+    buildAndRunCf("cf.zip", false);
+  }
+
+  @Test
+  public void testCfAggressive() throws Exception {
+    buildAndRunCf("cf-aggressive.zip", true);
+  }
+
+  @Test
+  public void testDex() throws Exception {
+    buildAndRunDex("dex.zip", false);
+  }
+
+  @Test
+  public void testDexAggressive() throws Exception {
+    buildAndRunDex("dex-aggressive.zip", true);
+  }
+
+  private void buildAndRunCf(String outName, boolean aggressive) throws Exception {
+    Path outCf = temp.getRoot().toPath().resolve(outName);
+    build(new ClassFileConsumer.ArchiveConsumer(outCf), aggressive);
+    ProcessResult runCf = ToolHelper.runJava(outCf, CLASS.getCanonicalName());
+    assertEquals(runInput.toString(), runCf.toString());
+  }
+
+  private void buildAndRunDex(String outName, boolean aggressive) throws Exception {
+    Path outDex = temp.getRoot().toPath().resolve(outName);
+    build(new DexIndexedConsumer.ArchiveConsumer(outDex), aggressive);
+    ProcessResult runDex =
+        ToolHelper.runArtNoVerificationErrorsRaw(outDex.toString(), CLASS.getCanonicalName());
+    assertEquals(runInput.stdout, runDex.stdout);
+    assertEquals(runInput.exitCode, runDex.exitCode);
+  }
+
+  private void build(ProgramConsumer consumer, boolean aggressive) throws Exception {
+    Builder builder =
+        ToolHelper.addProguardConfigurationConsumer(
+                R8Command.builder(), configuration -> configuration.setPrintMapping(true))
+            .setMode(CompilationMode.DEBUG)
+            .addLibraryFiles(ToolHelper.getAndroidJar(ToolHelper.getMinApiLevelForDexVm()))
+            .addProgramFiles(inputJar)
+            .setProgramConsumer(consumer)
+            .addProguardConfigurationFiles(writeProguardRules(aggressive));
+    if (consumer instanceof ClassFileConsumer) {
+      // TODO(b/75997473): Enable inlining when supported by CF backend
+      ToolHelper.runR8(builder.build(), options -> options.enableInlining = false);
+    } else {
+      builder.setMinApiLevel(AndroidApiLevel.O.getLevel());
+      ToolHelper.runR8(builder.build());
+    }
+  }
+
+  private void buildAndRunProguard(String outName, boolean aggressive) throws Exception {
+    Path pgConfig = writeProguardRules(aggressive);
+    Path outPg = temp.getRoot().toPath().resolve(outName);
+    ProcessResult proguardResult =
+        ToolHelper.runProguard6Raw(
+            inputJar, outPg, Paths.get(ToolHelper.JAVA_8_RUNTIME), pgConfig, null);
+    System.out.println(proguardResult.stdout);
+    if (proguardResult.exitCode != 0) {
+      System.out.println(proguardResult.stderr);
+    }
+    assertEquals(0, proguardResult.exitCode);
+    ProcessResult runPg = ToolHelper.runJava(outPg, CLASS.getCanonicalName());
+    // Proguard renames IntegerInterface.inexactMethod() and ObjectInterface.inexactMethod()
+    // to different names, which causes AbstractMethodError.
+    assertNotEquals(-1, runPg.stderr.indexOf("AbstractMethodError"));
+    assertNotEquals(0, runPg.exitCode);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/InstantiatedLambdasTest.java b/src/test/java/com/android/tools/r8/shaking/InstantiatedLambdasTest.java
new file mode 100644
index 0000000..4a5cfa8
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/InstantiatedLambdasTest.java
@@ -0,0 +1,42 @@
+// 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.shaking;
+
+public class InstantiatedLambdasTest {
+
+  public static final Class[] CLASSES = {
+    InstantiatedLambdasTest.class,
+    Interface.class,
+    ClassImplementation.class,
+  };
+
+  interface Interface {
+    String method();
+  }
+
+  static class ClassImplementation implements Interface {
+    @Override
+    public String method() {
+      return "Class implementation";
+    }
+  }
+
+  public static void main(String[] args) {
+    invoke(getClassImplementation());
+    invoke(getLambdaImplementation());
+  }
+
+  private static Interface getClassImplementation() {
+    return new ClassImplementation();
+  }
+
+  private static Interface getLambdaImplementation() {
+    return () -> "Lambda implementation";
+  }
+
+  private static void invoke(Interface instance) {
+    System.out.println(instance.method());
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/InstantiatedLambdasTestRunner.java b/src/test/java/com/android/tools/r8/shaking/InstantiatedLambdasTestRunner.java
new file mode 100644
index 0000000..4e638df
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/InstantiatedLambdasTestRunner.java
@@ -0,0 +1,138 @@
+// 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.shaking;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.ClassFileConsumer;
+import com.android.tools.r8.ClassFileConsumer.ArchiveConsumer;
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.DexIndexedConsumer;
+import com.android.tools.r8.ProgramConsumer;
+import com.android.tools.r8.R8Command;
+import com.android.tools.r8.R8Command.Builder;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.FileUtils;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import org.junit.Before;
+import org.junit.Test;
+
+public class InstantiatedLambdasTestRunner extends TestBase {
+  static final Class CLASS = InstantiatedLambdasTest.class;
+  static final Class[] CLASSES = InstantiatedLambdasTest.CLASSES;
+
+  private Path inputJar;
+  private ProcessResult runInput;
+
+  @Before
+  public void writeAndRunInputJar() throws IOException {
+    inputJar = temp.getRoot().toPath().resolve("input.jar");
+    ArchiveConsumer buildInput = new ArchiveConsumer(inputJar);
+    for (Class clazz : CLASSES) {
+      buildInput.accept(
+          ToolHelper.getClassAsBytes(clazz),
+          DescriptorUtils.javaTypeToDescriptor(clazz.getName()),
+          null);
+    }
+    buildInput.finished(null);
+    runInput = ToolHelper.runJava(inputJar, CLASS.getCanonicalName());
+    assertEquals(0, runInput.exitCode);
+  }
+
+  private Path writeProguardRules(boolean aggressive) throws IOException {
+    Path pgConfig = temp.getRoot().toPath().resolve("keep.txt");
+    FileUtils.writeTextFile(
+        pgConfig,
+        "-keep public class " + CLASS.getCanonicalName() + " {",
+        "  public static void main(...);",
+        "}",
+        aggressive ? "-overloadaggressively" : "# Not overloading aggressively");
+    return pgConfig;
+  }
+
+  @Test
+  public void testProguard() throws Exception {
+    buildAndRunProguard("pg.jar", false);
+  }
+
+  @Test
+  public void testProguardAggressive() throws Exception {
+    buildAndRunProguard("pg-aggressive.jar", true);
+  }
+
+  @Test
+  public void testCf() throws Exception {
+    buildAndRunCf("cf.zip", false);
+  }
+
+  @Test
+  public void testCfAggressive() throws Exception {
+    buildAndRunCf("cf-aggressive.zip", true);
+  }
+
+  @Test
+  public void testDex() throws Exception {
+    buildAndRunDex("dex.zip", false);
+  }
+
+  @Test
+  public void testDexAggressive() throws Exception {
+    buildAndRunDex("dex-aggressive.zip", true);
+  }
+
+  private void buildAndRunCf(String outName, boolean aggressive) throws Exception {
+    Path outCf = temp.getRoot().toPath().resolve(outName);
+    build(new ClassFileConsumer.ArchiveConsumer(outCf), aggressive);
+    ProcessResult runCf = ToolHelper.runJava(outCf, CLASS.getCanonicalName());
+    assertEquals(runInput.toString(), runCf.toString());
+  }
+
+  private void buildAndRunDex(String outName, boolean aggressive) throws Exception {
+    Path outDex = temp.getRoot().toPath().resolve(outName);
+    build(new DexIndexedConsumer.ArchiveConsumer(outDex), aggressive);
+    ProcessResult runDex =
+        ToolHelper.runArtNoVerificationErrorsRaw(outDex.toString(), CLASS.getCanonicalName());
+    assertEquals(runInput.stdout, runDex.stdout);
+    assertEquals(runInput.exitCode, runDex.exitCode);
+  }
+
+  private void build(ProgramConsumer consumer, boolean aggressive) throws Exception {
+    Builder builder =
+        ToolHelper.addProguardConfigurationConsumer(
+                R8Command.builder(), configuration -> configuration.setPrintMapping(true))
+            .setMode(CompilationMode.DEBUG)
+            .addLibraryFiles(ToolHelper.getAndroidJar(ToolHelper.getMinApiLevelForDexVm()))
+            .addProgramFiles(inputJar)
+            .setProgramConsumer(consumer)
+            .addProguardConfigurationFiles(writeProguardRules(aggressive));
+    if (consumer instanceof ClassFileConsumer) {
+      // TODO(b/75997473): Enable inlining when supported by CF backend
+      ToolHelper.runR8(builder.build(), options -> options.enableInlining = false);
+    } else {
+      builder.setMinApiLevel(AndroidApiLevel.O.getLevel());
+      ToolHelper.runR8(builder.build());
+    }
+  }
+
+  private void buildAndRunProguard(String outName, boolean aggressive) throws Exception {
+    Path pgConfig = writeProguardRules(aggressive);
+    Path outPg = temp.getRoot().toPath().resolve(outName);
+    ProcessResult proguardResult =
+        ToolHelper.runProguard6Raw(
+            inputJar, outPg, Paths.get(ToolHelper.JAVA_8_RUNTIME), pgConfig, null);
+    System.out.println(proguardResult.stdout);
+    if (proguardResult.exitCode != 0) {
+      System.out.println(proguardResult.stderr);
+    }
+    assertEquals(0, proguardResult.exitCode);
+    ProcessResult runPg = ToolHelper.runJava(outPg, CLASS.getCanonicalName());
+    assertEquals(0, runPg.exitCode);
+  }
+}