Merge "The meet of precise types is only defined for identical types."
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfo.java b/src/main/java/com/android/tools/r8/graph/AppInfo.java
index 55c4134..0ef8a34 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfo.java
@@ -489,17 +489,22 @@
   }
 
   public boolean canTriggerStaticInitializer(DexType type, boolean ignoreTypeItself) {
+    DexClass clazz = definitionFor(type);
+    assert clazz != null;
+    return canTriggerStaticInitializer(clazz, ignoreTypeItself);
+  }
+
+  public boolean canTriggerStaticInitializer(DexClass clazz, boolean ignoreTypeItself) {
     Set<DexType> knownInterfaces = Sets.newIdentityHashSet();
 
     // Process superclass chain.
-    DexType clazz = type;
-    while (clazz != null && clazz != dexItemFactory.objectType) {
-      DexClass definition = definitionFor(clazz);
-      if (canTriggerStaticInitializer(definition) && (!ignoreTypeItself || clazz != type)) {
+    DexClass current = clazz;
+    while (current != null && current.type != dexItemFactory.objectType) {
+      if (canTriggerStaticInitializer(current) && (!ignoreTypeItself || current != clazz)) {
         return true;
       }
-      knownInterfaces.addAll(Arrays.asList(definition.interfaces.values));
-      clazz = definition.superType;
+      knownInterfaces.addAll(Arrays.asList(current.interfaces.values));
+      current = current.superType != null ? definitionFor(current.superType) : null;
     }
 
     // Process interfaces.
diff --git a/src/main/java/com/android/tools/r8/graph/DexClass.java b/src/main/java/com/android/tools/r8/graph/DexClass.java
index 64c85f3..fc1ef7b 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -8,7 +8,6 @@
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.kotlin.KotlinInfo;
 import com.android.tools.r8.origin.Origin;
-import com.android.tools.r8.utils.ThrowingConsumer;
 import com.google.common.base.MoreObjects;
 import com.google.common.collect.Iterators;
 import java.util.Arrays;
@@ -138,16 +137,6 @@
     }
   }
 
-  public <E extends Throwable> void forEachMethodThrowing(
-      ThrowingConsumer<DexEncodedMethod, E> consumer) throws E {
-    for (DexEncodedMethod method : directMethods()) {
-      consumer.accept(method);
-    }
-    for (DexEncodedMethod method : virtualMethods()) {
-      consumer.accept(method);
-    }
-  }
-
   public DexEncodedMethod[] allMethodsSorted() {
     int vLen = virtualMethods.length;
     int dLen = directMethods.length;
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
index d5f923e..3ea97e4 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
@@ -176,7 +176,8 @@
     }
     this.classInliner =
         (options.enableClassInlining && options.enableInlining && inliner != null)
-            ? new ClassInliner(appInfo.dexItemFactory, options.classInliningInstructionLimit)
+            ? new ClassInliner(
+            appInfo.dexItemFactory, lambdaRewriter, options.classInliningInstructionLimit)
             : null;
   }
 
@@ -356,12 +357,7 @@
       ExecutorService executor) throws ExecutionException {
     List<Future<?>> futures = new ArrayList<>();
     for (DexProgramClass clazz : classes) {
-      futures.add(
-          executor.submit(
-              () -> {
-                clazz.forEachMethodThrowing(this::convertMethodToDex);
-                return null; // we want a Callable not a Runnable to be able to throw
-              }));
+      futures.add(executor.submit(() -> clazz.forEachMethod(this::convertMethodToDex)));
     }
     ThreadUtils.awaitFutures(futures);
   }
@@ -565,7 +561,7 @@
     try {
       codeRewriter.enterCachedClass(clazz);
       // Process the generated class, but don't apply any outlining.
-      clazz.forEachMethodThrowing(this::optimizeSynthesizedMethod);
+      clazz.forEachMethod(this::optimizeSynthesizedMethod);
     } finally {
       codeRewriter.leaveCachedClass(clazz);
     }
@@ -733,11 +729,6 @@
       assert code.isConsistentSSA();
     }
 
-    if (interfaceMethodRewriter != null) {
-      interfaceMethodRewriter.rewriteMethodReferences(method, code);
-      assert code.isConsistentSSA();
-    }
-
     if (classInliner != null) {
       // Class inliner should work before lambda merger, so if it inlines the
       // lambda, it is not get collected by merger.
@@ -755,6 +746,11 @@
       assert code.isConsistentSSA();
     }
 
+    if (interfaceMethodRewriter != null) {
+      interfaceMethodRewriter.rewriteMethodReferences(method, code);
+      assert code.isConsistentSSA();
+    }
+
     if (lambdaMerger != null) {
       lambdaMerger.processMethodCode(method, code);
       assert code.isConsistentSSA();
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
index 81407bd..86f3f17 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
@@ -29,11 +29,13 @@
 import com.android.tools.r8.ir.code.Invoke;
 import com.android.tools.r8.ir.synthetic.SynthesizedCode;
 import com.android.tools.r8.origin.SynthesizedOrigin;
+import com.google.common.base.Suppliers;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
 import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.Supplier;
 
 /**
  * Represents lambda class generated for a lambda descriptor in context
@@ -64,6 +66,8 @@
   final Target target;
   final AtomicBoolean addToMainDexList = new AtomicBoolean(false);
   private final Collection<DexProgramClass> synthesizedFrom = new ArrayList<DexProgramClass>(1);
+  private final Supplier<DexProgramClass> lazyDexClass =
+      Suppliers.memoize(this::synthesizeLambdaClass); // NOTE: thread-safe.
 
   LambdaClass(LambdaRewriter rewriter, DexType accessedFrom,
       DexType lambdaClassType, LambdaDescriptor descriptor) {
@@ -119,7 +123,11 @@
     return rewriter.factory.createType(lambdaClassDescriptor.toString());
   }
 
-  final DexProgramClass synthesizeLambdaClass() {
+  final DexProgramClass getLambdaClass() {
+    return lazyDexClass.get();
+  }
+
+  private DexProgramClass synthesizeLambdaClass() {
     return new DexProgramClass(
         type,
         null,
@@ -171,7 +179,7 @@
                 Constants.ACC_PUBLIC | Constants.ACC_FINAL, false),
             DexAnnotationSet.empty(),
             ParameterAnnotationsList.empty(),
-            new SynthesizedCode(new LambdaMainMethodSourceCode(this, mainMethod)));
+            new SynthesizedCode(() -> new LambdaMainMethodSourceCode(this, mainMethod)));
 
     // Synthesize bridge methods.
     for (DexProto bridgeProto : descriptor.bridges) {
@@ -188,7 +196,7 @@
               DexAnnotationSet.empty(),
               ParameterAnnotationsList.empty(),
               new SynthesizedCode(
-                  new LambdaBridgeMethodSourceCode(this, mainMethod, bridgeMethod)));
+                  () -> new LambdaBridgeMethodSourceCode(this, mainMethod, bridgeMethod)));
     }
     return methods;
   }
@@ -208,7 +216,7 @@
                 true),
             DexAnnotationSet.empty(),
             ParameterAnnotationsList.empty(),
-            new SynthesizedCode(new LambdaConstructorSourceCode(this)));
+            new SynthesizedCode(() -> new LambdaConstructorSourceCode(this)));
 
     // Class constructor for stateless lambda classes.
     if (stateless) {
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 e7686db..51b8b8a 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
@@ -141,7 +141,6 @@
           if (method.name == deserializeLambdaMethodName &&
               method.proto == deserializeLambdaMethodProto) {
             assert encoded.accessFlags.isStatic();
-            assert encoded.accessFlags.isPrivate();
             assert encoded.accessFlags.isSynthetic();
 
             DexEncodedMethod[] newMethods = new DexEncodedMethod[methodCount - 1];
@@ -170,10 +169,19 @@
     }
   }
 
+  /**
+   * Returns a synthetic class for desugared lambda or `null` if the `type`
+   * does not represent one. Method can be called concurrently.
+   */
+  public DexProgramClass getLambdaClass(DexType type) {
+    LambdaClass lambdaClass = getKnown(knownLambdaClasses, type);
+    return lambdaClass == null ? null : lambdaClass.getLambdaClass();
+  }
+
   /** Generates lambda classes and adds them to the builder. */
   public void synthesizeLambdaClasses(Builder<?> builder) {
     for (LambdaClass lambdaClass : knownLambdaClasses.values()) {
-      DexProgramClass synthesizedClass = lambdaClass.synthesizeLambdaClass();
+      DexProgramClass synthesizedClass = lambdaClass.getLambdaClass();
       converter.optimizeSynthesizedClass(synthesizedClass);
       builder.addSynthesizedClass(synthesizedClass, lambdaClass.addToMainDexList.get());
     }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ForcedInliningOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/ForcedInliningOracle.java
index d3d1e2f..85f7a39 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/ForcedInliningOracle.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/ForcedInliningOracle.java
@@ -60,7 +60,8 @@
 
   @Override
   public void ensureMethodProcessed(DexEncodedMethod target, IRCode inlinee) {
-    assert target.isProcessed();
+    // Do nothing. If the method is not yet processed, we still should
+    // be able to build IR for inlining, though.
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
index 5bf2c74..3a2371d 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
@@ -8,10 +8,10 @@
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.desugar.LambdaRewriter;
 import com.android.tools.r8.ir.optimize.CodeRewriter;
 import com.android.tools.r8.ir.optimize.Inliner.InliningInfo;
 import com.android.tools.r8.ir.optimize.InliningOracle;
@@ -27,15 +27,18 @@
 
 public final class ClassInliner {
   private final DexItemFactory factory;
+  private final LambdaRewriter lambdaRewriter;
   private final int totalMethodInstructionLimit;
-  private final ConcurrentHashMap<DexType, Boolean> knownClasses = new ConcurrentHashMap<>();
+  private final ConcurrentHashMap<DexClass, Boolean> knownClasses = new ConcurrentHashMap<>();
 
   public interface InlinerAction {
     void inline(Map<InvokeMethod, InliningInfo> methods);
   }
 
-  public ClassInliner(DexItemFactory factory, int totalMethodInstructionLimit) {
+  public ClassInliner(DexItemFactory factory,
+      LambdaRewriter lambdaRewriter, int totalMethodInstructionLimit) {
     this.factory = factory;
+    this.lambdaRewriter = lambdaRewriter;
     this.totalMethodInstructionLimit = totalMethodInstructionLimit;
   }
 
@@ -142,8 +145,8 @@
       while (rootsIterator.hasNext()) {
         Instruction root = rootsIterator.next();
         InlineCandidateProcessor processor =
-            new InlineCandidateProcessor(factory, appInfo,
-                type -> isClassEligible(appInfo, type),
+            new InlineCandidateProcessor(factory, appInfo, lambdaRewriter,
+                clazz -> isClassEligible(appInfo, clazz),
                 isProcessedConcurrently, method, root);
 
         // Assess eligibility of instance and class.
@@ -180,7 +183,7 @@
     } while (repeat);
   }
 
-  private boolean isClassEligible(AppInfo appInfo, DexType clazz) {
+  private boolean isClassEligible(AppInfo appInfo, DexClass clazz) {
     Boolean eligible = knownClasses.get(clazz);
     if (eligible == null) {
       Boolean computed = computeClassEligible(appInfo, clazz);
@@ -195,15 +198,14 @@
   //   - is not an abstract class or interface
   //   - does not declare finalizer
   //   - does not trigger any static initializers except for its own
-  private boolean computeClassEligible(AppInfo appInfo, DexType clazz) {
-    DexClass definition = appInfo.definitionFor(clazz);
-    if (definition == null || definition.isLibraryClass() ||
-        definition.accessFlags.isAbstract() || definition.accessFlags.isInterface()) {
+  private boolean computeClassEligible(AppInfo appInfo, DexClass clazz) {
+    if (clazz == null || clazz.isLibraryClass() ||
+        clazz.accessFlags.isAbstract() || clazz.accessFlags.isInterface()) {
       return false;
     }
 
     // Class must not define finalizer.
-    for (DexEncodedMethod method : definition.virtualMethods()) {
+    for (DexEncodedMethod method : clazz.virtualMethods()) {
       if (method.method.name == factory.finalizeMethodName &&
           method.method.proto == factory.objectMethods.finalize.proto) {
         return false;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
index 57ce42f..5fedf37 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
@@ -28,6 +28,7 @@
 import com.android.tools.r8.ir.code.InvokeMethod;
 import com.android.tools.r8.ir.code.InvokeMethodWithReceiver;
 import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.desugar.LambdaRewriter;
 import com.android.tools.r8.ir.optimize.Inliner.InlineAction;
 import com.android.tools.r8.ir.optimize.Inliner.InliningInfo;
 import com.android.tools.r8.ir.optimize.Inliner.Reason;
@@ -53,7 +54,8 @@
 
   private final DexItemFactory factory;
   private final AppInfoWithLiveness appInfo;
-  private final Predicate<DexType> isClassEligible;
+  private final LambdaRewriter lambdaRewriter;
+  private final Predicate<DexClass> isClassEligible;
   private final Predicate<DexEncodedMethod> isProcessedConcurrently;
   private final DexEncodedMethod method;
   private final Instruction root;
@@ -61,6 +63,7 @@
   private Value eligibleInstance;
   private DexType eligibleClass;
   private DexClass eligibleClassDefinition;
+  private boolean isDesugaredLambda;
 
   private final Map<InvokeMethod, InliningInfo> methodCallsOnInstance
       = new IdentityHashMap<>();
@@ -73,10 +76,11 @@
 
   InlineCandidateProcessor(
       DexItemFactory factory, AppInfoWithLiveness appInfo,
-      Predicate<DexType> isClassEligible,
+      LambdaRewriter lambdaRewriter, Predicate<DexClass> isClassEligible,
       Predicate<DexEncodedMethod> isProcessedConcurrently,
       DexEncodedMethod method, Instruction root) {
     this.factory = factory;
+    this.lambdaRewriter = lambdaRewriter;
     this.isClassEligible = isClassEligible;
     this.method = method;
     this.root = root;
@@ -99,6 +103,11 @@
     eligibleClass = isNewInstance() ?
         root.asNewInstance().clazz : root.asStaticGet().getField().type;
     eligibleClassDefinition = appInfo.definitionFor(eligibleClass);
+    if (eligibleClassDefinition == null && lambdaRewriter != null) {
+      // Check if the class is synthesized for a desugared lambda
+      eligibleClassDefinition = lambdaRewriter.getLambdaClass(eligibleClass);
+      isDesugaredLambda = eligibleClassDefinition != null;
+    }
     return eligibleClassDefinition != null;
   }
 
@@ -114,7 +123,7 @@
   //      * class has class initializer marked as TrivialClassInitializer, and
   //        class initializer initializes the field we are reading here.
   boolean isClassAndUsageEligible() {
-    if (!isClassEligible.test(eligibleClass)) {
+    if (!isClassEligible.test(eligibleClassDefinition)) {
       return false;
     }
 
@@ -129,6 +138,11 @@
 
     assert root.isStaticGet();
 
+    // We know that desugared lambda classes satisfy eligibility requirements.
+    if (isDesugaredLambda) {
+      return true;
+    }
+
     // Checking if we can safely inline class implemented following singleton-like
     // pattern, by which we assume a static final field holding on to the reference
     // initialized in class constructor.
@@ -444,7 +458,7 @@
         : "Inlined constructor? [invoke: " + initInvoke +
         ", expected class: " + eligibleClass + "]";
 
-    DexEncodedMethod definition = appInfo.definitionFor(init);
+    DexEncodedMethod definition = findSingleTarget(init, true);
     if (definition == null || isProcessedConcurrently.test(definition)) {
       return null;
     }
@@ -455,6 +469,12 @@
       return null;
     }
 
+    if (isDesugaredLambda) {
+      // Lambda desugaring synthesizes eligible constructors.
+      markSizeForInlining(definition);
+      return new InliningInfo(definition, eligibleClass);
+    }
+
     // If the superclass of the initializer is NOT java.lang.Object, the super class
     // initializer being called must be classified as TrivialInstanceInitializer.
     //
@@ -499,7 +519,7 @@
   private InliningInfo isEligibleMethodCall(boolean allowMethodsWithoutNormalReturns,
       DexMethod callee, Predicate<ClassInlinerEligibility> eligibilityAcceptanceCheck) {
 
-    DexEncodedMethod singleTarget = findSingleTarget(callee);
+    DexEncodedMethod singleTarget = findSingleTarget(callee, false);
     if (singleTarget == null || isProcessedConcurrently.test(singleTarget)) {
       return null;
     }
@@ -507,6 +527,16 @@
       return null; // Don't inline itself.
     }
 
+    if (isDesugaredLambda) {
+      // If this is the call to method of the desugared lambda, we consider only calls
+      // to main lambda method eligible (for both direct and indirect calls).
+      if (singleTarget.accessFlags.isBridge()) {
+        return null;
+      }
+      markSizeForInlining(singleTarget);
+      return new InliningInfo(singleTarget, eligibleClass);
+    }
+
     OptimizationInfo optimizationInfo = singleTarget.getOptimizationInfo();
 
     ClassInlinerEligibility eligibility = optimizationInfo.getClassInlinerEligibility();
@@ -652,6 +682,9 @@
 
   private boolean exemptFromInstructionLimit(DexEncodedMethod inlinee) {
     DexType inlineeHolder = inlinee.method.holder;
+    if (isDesugaredLambda && inlineeHolder == eligibleClass) {
+      return true;
+    }
     if (appInfo.isPinned(inlineeHolder)) {
       return false;
     }
@@ -674,14 +707,16 @@
     return root.isNewInstance();
   }
 
-  private DexEncodedMethod findSingleTarget(DexMethod callee) {
+  private DexEncodedMethod findSingleTarget(DexMethod callee, boolean isDirect) {
     // We don't use computeSingleTarget(...) on invoke since it sometimes fails to
     // find the single target, while this code may be more successful since we exactly
     // know what is the actual type of the receiver.
 
     // Note that we also intentionally limit ourselves to methods directly defined in
     // the instance's class. This may be improved later.
-    return eligibleClassDefinition.lookupVirtualMethod(callee);
+    return isDirect
+        ? eligibleClassDefinition.lookupDirectMethod(callee)
+        : eligibleClassDefinition.lookupVirtualMethod(callee);
   }
 
   private void removeInstruction(Instruction instruction) {
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/SynthesizedCode.java b/src/main/java/com/android/tools/r8/ir/synthetic/SynthesizedCode.java
index a5807c3..7835da2 100644
--- a/src/main/java/com/android/tools/r8/ir/synthetic/SynthesizedCode.java
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/SynthesizedCode.java
@@ -10,25 +10,33 @@
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.Position;
+import com.android.tools.r8.ir.code.ValueNumberGenerator;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.ir.conversion.SourceCode;
 import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.InternalOptions;
 import java.util.function.Consumer;
+import java.util.function.Supplier;
 
 public final class SynthesizedCode extends Code {
 
-  private final SourceCode sourceCode;
+  private final Supplier<SourceCode> sourceCodeProvider;
   private final Consumer<UseRegistry> registryCallback;
 
-  public SynthesizedCode(SourceCode sourceCode) {
-    this.sourceCode = sourceCode;
+  public SynthesizedCode(SourceCode sourceCodeProvider) {
+    this(() -> sourceCodeProvider);
+  }
+
+  public SynthesizedCode(Supplier<SourceCode> sourceCodeProvider) {
+    this.sourceCodeProvider = sourceCodeProvider;
     this.registryCallback = SynthesizedCode::registerReachableDefinitionsDefault;
   }
 
-  public SynthesizedCode(SourceCode sourceCode, Consumer<UseRegistry> callback) {
-    this.sourceCode = sourceCode;
+  public SynthesizedCode(
+      SourceCode sourceCode, Consumer<UseRegistry> callback) {
+    this.sourceCodeProvider = () -> sourceCode;
     this.registryCallback = callback;
   }
 
@@ -40,7 +48,15 @@
   @Override
   public final IRCode buildIR(
       DexEncodedMethod encodedMethod, AppInfo appInfo, InternalOptions options, Origin origin) {
-    return new IRBuilder(encodedMethod, appInfo, sourceCode, options).build();
+    return new IRBuilder(encodedMethod, appInfo, sourceCodeProvider.get(), options).build();
+  }
+
+  @Override
+  public IRCode buildInliningIR(
+      DexEncodedMethod encodedMethod, AppInfo appInfo, InternalOptions options,
+      ValueNumberGenerator valueNumberGenerator, Position callerPosition, Origin origin) {
+    return new IRBuilder(encodedMethod, appInfo,
+        sourceCodeProvider.get(), options, valueNumberGenerator).build();
   }
 
   @Override
@@ -59,17 +75,16 @@
 
   @Override
   protected final int computeHashCode() {
-    return sourceCode.hashCode();
+    throw new Unreachable();
   }
 
   @Override
   protected final boolean computeEquals(Object other) {
-    return other instanceof SynthesizedCode &&
-        this.sourceCode.equals(((SynthesizedCode) other).sourceCode);
+    throw new Unreachable();
   }
 
   @Override
   public final String toString(DexEncodedMethod method, ClassNameMapper naming) {
-    return "SynthesizedCode: " + sourceCode.toString();
+    return "SynthesizedCode";
   }
 }
diff --git a/src/main/java/com/android/tools/r8/kotlin/Kotlin.java b/src/main/java/com/android/tools/r8/kotlin/Kotlin.java
index 05c7b92..fb6ef33 100644
--- a/src/main/java/com/android/tools/r8/kotlin/Kotlin.java
+++ b/src/main/java/com/android/tools/r8/kotlin/Kotlin.java
@@ -10,11 +10,20 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Sets;
 import java.util.Set;
 
 /** Class provides basic information about symbols related to Kotlin support. */
 public final class Kotlin {
+  // Simply "Lkotlin/", but to avoid being renamed by Shadow.relocate
+  private static final String KOTLIN =
+      String.join("", ImmutableList.of("L", "k", "o", "t", "l", "i", "n", "/"));
+
+  static String addKotlinPrefix(String str) {
+    return KOTLIN + str;
+  }
+
   public final DexItemFactory factory;
 
   public final Functional functional;
@@ -44,14 +53,15 @@
       //
       // This implementation just ignores lambdas with arity > 22.
       for (int i = 0; i <= 22; i++) {
-        functions.add(factory.createType("Lkotlin/jvm/functions/Function" + i + ";"));
+        functions.add(factory.createType(addKotlinPrefix("jvm/functions/Function") + i + ";"));
       }
     }
 
     public final DexString kotlinStyleLambdaInstanceName = factory.createString("INSTANCE");
 
-    public final DexType functionBase = factory.createType("Lkotlin/jvm/internal/FunctionBase;");
-    public final DexType lambdaType = factory.createType("Lkotlin/jvm/internal/Lambda;");
+    public final DexType functionBase =
+        factory.createType(addKotlinPrefix("jvm/internal/FunctionBase;"));
+    public final DexType lambdaType = factory.createType(addKotlinPrefix("jvm/internal/Lambda;"));
 
     public final DexMethod lambdaInitializerMethod = factory.createMethod(
         lambdaType,
@@ -64,7 +74,7 @@
   }
 
   public final class Metadata {
-    public final DexType kotlinMetadataType = factory.createType("Lkotlin/Metadata;");
+    public final DexType kotlinMetadataType = factory.createType(addKotlinPrefix("Metadata;"));
     public final DexString kind = factory.createString("k");
     public final DexString metadataVersion = factory.createString("mv");
     public final DexString bytecodeVersion = factory.createString("bv");
@@ -77,7 +87,7 @@
 
   // kotlin.jvm.internal.Intrinsics class
   public final class Intrinsics {
-    public final DexType type = factory.createType("Lkotlin/jvm/internal/Intrinsics;");
+    public final DexType type = factory.createType(addKotlinPrefix("jvm/internal/Intrinsics;"));
     public final DexMethod throwParameterIsNullException = factory.createMethod(type,
         factory.createProto(factory.voidType, factory.stringType), "throwParameterIsNullException");
     public final DexMethod checkParameterIsNotNull = factory.createMethod(type,
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinClassMetadataReader.java b/src/main/java/com/android/tools/r8/kotlin/KotlinClassMetadataReader.java
index 32aebff..389a8f8 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClassMetadataReader.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClassMetadataReader.java
@@ -36,7 +36,7 @@
             new StringDiagnostic("Class " + clazz.type.toSourceString()
                 + " has malformed kotlin.Metadata: " + e.getMessage()));
       } catch (Throwable e) {
-         reporter.warning(
+        reporter.warning(
             new StringDiagnostic("Unexpected error while reading " + clazz.type.toSourceString()
                 + "'s kotlin.Metadata: " + e.getMessage()));
       }
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
index f20335b..be9261f 100644
--- a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
@@ -4,11 +4,15 @@
 
 package com.android.tools.r8;
 
+import static org.junit.Assert.assertEquals;
+
 import com.android.tools.r8.ToolHelper.DexVm;
 import com.android.tools.r8.ToolHelper.DexVm.Version;
 import com.android.tools.r8.VmTestRunner.IgnoreIfVmOlderThan;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.DexInspector.FoundClassSubject;
 import com.android.tools.r8.utils.OffOrAuto;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
@@ -94,6 +98,7 @@
         .withOptionConsumer(opts -> opts.enableClassInlining = false)
         .withBuilderTransformation(
             b -> b.addProguardConfiguration(PROGUARD_OPTIONS, Origin.unknown()))
+        .withDexCheck(inspector -> checkLambdaCount(inspector, 179, "lambdadesugaring"))
         .run();
 
     test("lambdadesugaring", "lambdadesugaring", "LambdaDesugaring")
@@ -101,6 +106,7 @@
         .withOptionConsumer(opts -> opts.enableClassInlining = true)
         .withBuilderTransformation(
             b -> b.addProguardConfiguration(PROGUARD_OPTIONS, Origin.unknown()))
+        .withDexCheck(inspector -> checkLambdaCount(inspector, 23, "lambdadesugaring"))
         .run();
   }
 
@@ -112,6 +118,7 @@
         .withOptionConsumer(opts -> opts.enableClassInlining = false)
         .withBuilderTransformation(
             b -> b.addProguardConfiguration(PROGUARD_OPTIONS, Origin.unknown()))
+        .withDexCheck(inspector -> checkLambdaCount(inspector, 179, "lambdadesugaring"))
         .run();
 
     test("lambdadesugaring", "lambdadesugaring", "LambdaDesugaring")
@@ -119,6 +126,7 @@
         .withOptionConsumer(opts -> opts.enableClassInlining = true)
         .withBuilderTransformation(
             b -> b.addProguardConfiguration(PROGUARD_OPTIONS, Origin.unknown()))
+        .withDexCheck(inspector -> checkLambdaCount(inspector, 23, "lambdadesugaring"))
         .run();
   }
 
@@ -131,6 +139,7 @@
         .withOptionConsumer(opts -> opts.enableClassInlining = false)
         .withBuilderTransformation(
             b -> b.addProguardConfiguration(PROGUARD_OPTIONS_N_PLUS, Origin.unknown()))
+        .withDexCheck(inspector -> checkLambdaCount(inspector, 40, "lambdadesugaringnplus"))
         .run();
 
     test("lambdadesugaringnplus", "lambdadesugaringnplus", "LambdasWithStaticAndDefaultMethods")
@@ -139,6 +148,7 @@
         .withOptionConsumer(opts -> opts.enableClassInlining = true)
         .withBuilderTransformation(
             b -> b.addProguardConfiguration(PROGUARD_OPTIONS_N_PLUS, Origin.unknown()))
+        .withDexCheck(inspector -> checkLambdaCount(inspector, 5, "lambdadesugaringnplus"))
         .run();
   }
 
@@ -151,6 +161,7 @@
         .withOptionConsumer(opts -> opts.enableClassInlining = false)
         .withBuilderTransformation(
             b -> b.addProguardConfiguration(PROGUARD_OPTIONS_N_PLUS, Origin.unknown()))
+        .withDexCheck(inspector -> checkLambdaCount(inspector, 40, "lambdadesugaringnplus"))
         .run();
 
     test("lambdadesugaringnplus", "lambdadesugaringnplus", "LambdasWithStaticAndDefaultMethods")
@@ -159,9 +170,21 @@
         .withOptionConsumer(opts -> opts.enableClassInlining = true)
         .withBuilderTransformation(
             b -> b.addProguardConfiguration(PROGUARD_OPTIONS_N_PLUS, Origin.unknown()))
+        .withDexCheck(inspector -> checkLambdaCount(inspector, 5, "lambdadesugaringnplus"))
         .run();
   }
 
+  private void checkLambdaCount(DexInspector inspector, int expectedCount, String prefix) {
+    int count = 0;
+    for (FoundClassSubject clazz : inspector.allClasses()) {
+      if (clazz.isSynthesizedJavaLambdaClass() &&
+          clazz.getOriginalName().startsWith(prefix)) {
+        count++;
+      }
+    }
+    assertEquals(expectedCount, count);
+  }
+
   class R8TestRunner extends TestRunner<R8TestRunner> {
 
     R8TestRunner(String testName, String packageName, String mainClass) {
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index 2667ce2..2767df7 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -474,8 +474,9 @@
   public static byte[] getClassAsBytes(Class clazz) throws IOException {
     String s = clazz.getSimpleName() + ".class";
     Class outer = clazz.getEnclosingClass();
-    if (outer != null) {
+    while (outer != null) {
       s = outer.getSimpleName() + '$' + s;
+      outer = outer.getEnclosingClass();
     }
     return ByteStreams.toByteArray(clazz.getResourceAsStream(s));
   }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java
index 3239191..e7a8285 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java
@@ -29,6 +29,7 @@
 import com.android.tools.r8.ir.optimize.classinliner.code.C;
 import com.android.tools.r8.ir.optimize.classinliner.code.CodeTestClass;
 import com.android.tools.r8.ir.optimize.classinliner.invalidroot.InvalidRootsTestClass;
+import com.android.tools.r8.ir.optimize.classinliner.lambdas.LambdasTestClass;
 import com.android.tools.r8.ir.optimize.classinliner.trivial.ClassWithFinal;
 import com.android.tools.r8.ir.optimize.classinliner.trivial.CycleReferenceAB;
 import com.android.tools.r8.ir.optimize.classinliner.trivial.CycleReferenceBA;
@@ -301,6 +302,37 @@
     assertFalse(inspector.clazz(InvalidRootsTestClass.B.class).isPresent());
   }
 
+  @Test
+  public void testDesugaredLambdas() throws Exception {
+    byte[][] classes = {
+        ToolHelper.getClassAsBytes(LambdasTestClass.class),
+        ToolHelper.getClassAsBytes(LambdasTestClass.Iface.class),
+        ToolHelper.getClassAsBytes(LambdasTestClass.IfaceUtil.class),
+    };
+    AndroidApp app = runR8(buildAndroidApp(classes), LambdasTestClass.class);
+
+    String javaOutput = runOnJava(LambdasTestClass.class);
+    String artOutput = runOnArt(app, LambdasTestClass.class);
+    assertEquals(javaOutput, artOutput);
+
+    DexInspector inspector = new DexInspector(app);
+    ClassSubject clazz = inspector.clazz(LambdasTestClass.class);
+
+    assertEquals(
+        Sets.newHashSet(
+            "java.lang.StringBuilder"),
+        collectTypes(clazz, "testStatelessLambda", "void"));
+
+    assertEquals(
+        Sets.newHashSet(
+            "java.lang.StringBuilder"),
+        collectTypes(clazz, "testStatefulLambda", "void", "java.lang.String", "java.lang.String"));
+
+    assertEquals(0,
+        inspector.allClasses().stream()
+            .filter(ClassSubject::isSynthesizedJavaLambdaClass).count());
+  }
+
   private Set<String> collectTypes(
       ClassSubject clazz, String methodName, String retValue, String... params) {
     return Stream.concat(
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/lambdas/LambdasTestClass.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/lambdas/LambdasTestClass.java
new file mode 100644
index 0000000..130f895
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/lambdas/LambdasTestClass.java
@@ -0,0 +1,54 @@
+// 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.ir.optimize.classinliner.lambdas;
+
+public class LambdasTestClass {
+  private static int ID = 0;
+
+  private static int nextInt() {
+    return ID++;
+  }
+
+  private static String next() {
+    return Integer.toString(nextInt());
+  }
+
+  public interface Iface {
+    String foo();
+  }
+
+  public static class IfaceUtil {
+    public static void act(Iface iface) {
+      System.out.println("" + next() + "> " + iface.foo());
+    }
+  }
+
+  public static void main(String[] args) {
+    LambdasTestClass test = new LambdasTestClass();
+    test.testStatelessLambda();
+    test.testStatefulLambda(next(), next());
+  }
+
+  public static String exact() {
+    return next();
+  }
+
+  public static String almost(String... s) {
+    return next();
+  }
+
+  private synchronized void testStatelessLambda() {
+    IfaceUtil.act(() -> next());
+    IfaceUtil.act(LambdasTestClass::next);
+    IfaceUtil.act(LambdasTestClass::exact);
+    IfaceUtil.act(LambdasTestClass::almost);
+  }
+
+  private synchronized void testStatefulLambda(String a, String b) {
+    IfaceUtil.act(() -> a);
+    IfaceUtil.act(() -> a + b);
+    IfaceUtil.act((a + b)::toLowerCase);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/DexInspector.java b/src/test/java/com/android/tools/r8/utils/DexInspector.java
index eba3be8..955d1ee 100644
--- a/src/test/java/com/android/tools/r8/utils/DexInspector.java
+++ b/src/test/java/com/android/tools/r8/utils/DexInspector.java
@@ -423,6 +423,8 @@
 
     public abstract boolean isAnonymousClass();
 
+    public abstract boolean isSynthesizedJavaLambdaClass();
+
     public abstract String getOriginalSignatureAttribute();
 
     public abstract String getFinalSignatureAttribute();
@@ -514,6 +516,11 @@
     }
 
     @Override
+    public boolean isSynthesizedJavaLambdaClass() {
+      return false;
+    }
+
+    @Override
     public String getOriginalSignatureAttribute() {
       return null;
     }
@@ -709,6 +716,11 @@
     }
 
     @Override
+    public boolean isSynthesizedJavaLambdaClass() {
+      return dexClass.type.getName().contains("$Lambda$");
+    }
+
+    @Override
     public String getOriginalSignatureAttribute() {
       return DexInspector.this.getOriginalSignatureAttribute(
           dexClass.annotations, GenericSignatureParser::parseClassSignature);