Version 2.0.63

Cherry-pick: Rewrite invoke-direct to default methods in parent
interfaces
CL: https://r8-review.googlesource.com/50262

Cherry-pick: Desugar default call to looked up virtual method
CL: https://r8-review.googlesource.com/50290

Cherry-pick: Add a test for renaming of lambda methods to mapped names
CL: https://r8-review.googlesource.com/50147

Cherry-pick: Reserve mapped to names to prevent clashes at runtime
CL: https://r8-review.googlesource.com/50181

Cherry-pick: Rewrite invoke-static getter users to use staticized methods
CL: https://r8-review.googlesource.com/50300

Cherry-pick: Reland "Do not write zip output to invalid file."
CL: https://r8-review.googlesource.com/50294

Cherry-pick: Update CompileToInvalidFileTest to use both cf and dex
consumers
CL: https://r8-review.googlesource.com/50302

Bug: 153042496
Bug: 152715309
Bug: 152800551
Bug: 152973695
Change-Id: I4048107a167c30585c9da6a4e74d09ace9f012cf
diff --git a/src/main/java/com/android/tools/r8/Version.java b/src/main/java/com/android/tools/r8/Version.java
index e53349e..b9b47c0 100644
--- a/src/main/java/com/android/tools/r8/Version.java
+++ b/src/main/java/com/android/tools/r8/Version.java
@@ -11,7 +11,7 @@
 
   // This field is accessed from release scripts using simple pattern matching.
   // Therefore, changing this field could break our release scripts.
-  public static final String LABEL = "2.0.62";
+  public static final String LABEL = "2.0.63";
 
   private Version() {
   }
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 ad19ed4..b830c3e 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfo.java
@@ -436,6 +436,16 @@
     return builder.resolve();
   }
 
+  // Non-private lookup (ie, not resolution) to find interface targets.
+  public DexEncodedMethod lookupMaximallySpecificTarget(DexClass clazz, DexMethod method) {
+    MaximallySpecificMethodsBuilder builder = new MaximallySpecificMethodsBuilder(clazz);
+    resolveMethodStep3Helper(clazz, method, builder);
+    ResolutionResult resolution = builder.resolve();
+    return resolution.isSingleResolution()
+        ? resolution.asSingleResolution().getResolvedMethod()
+        : null;
+  }
+
   /** Helper method that builds the set of maximally specific methods. */
   private void resolveMethodStep3Helper(
       DexClass clazz, DexMethod method, MaximallySpecificMethodsBuilder builder) {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
index 5ff8e57..7ca1e54 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
@@ -327,38 +327,46 @@
           if (clazz == null) {
             // Report missing class since we don't know if it is an interface.
             warnMissingType(encodedMethod.method, method.holder);
-
           } else if (clazz.isInterface()) {
             if (clazz.isLibraryClass()) {
               throw new CompilationError("Unexpected call to a private method " +
                   "defined in library class " + clazz.toSourceString(),
                   getMethodOrigin(encodedMethod.method));
             }
-
-            // This might be either private method call, or a call to default
-            // interface method made via invoke-direct.
-            DexEncodedMethod virtualTarget = null;
-            for (DexEncodedMethod candidate : clazz.virtualMethods()) {
-              if (candidate.method == method) {
-                virtualTarget = candidate;
-                break;
-              }
-            }
-
-            if (virtualTarget != null) {
-              // This is a invoke-direct call to a virtual method.
-              instructions.replaceCurrentInstruction(
-                  new InvokeStatic(defaultAsMethodOfCompanionClass(method),
-                      invokeDirect.outValue(), invokeDirect.arguments()));
-
-            } else {
-              // Otherwise this must be a private instance method call. Note that the referenced
+            DexEncodedMethod directTarget = appView.definitionFor(method);
+            if (directTarget != null) {
+              // This can be a private instance method call. Note that the referenced
               // method is expected to be in the current class since it is private, but desugaring
               // may move some methods or their code into other classes.
-
-              instructions.replaceCurrentInstruction(
-                  new InvokeStatic(privateAsMethodOfCompanionClass(method),
-                      invokeDirect.outValue(), invokeDirect.arguments()));
+              if (directTarget.isPrivateMethod()) {
+                instructions.replaceCurrentInstruction(
+                    new InvokeStatic(
+                        privateAsMethodOfCompanionClass(method),
+                        invokeDirect.outValue(),
+                        invokeDirect.arguments()));
+              } else {
+                instructions.replaceCurrentInstruction(
+                    new InvokeStatic(
+                        defaultAsMethodOfCompanionClass(method),
+                        invokeDirect.outValue(),
+                        invokeDirect.arguments()));
+              }
+            } else {
+              // The method can be a default method in the interface hierarchy.
+              DexEncodedMethod virtualTarget =
+                  appView.appInfo().lookupMaximallySpecificTarget(clazz, method);
+              if (virtualTarget != null) {
+                // This is a invoke-direct call to a virtual method.
+                instructions.replaceCurrentInstruction(
+                    new InvokeStatic(
+                        defaultAsMethodOfCompanionClass(virtualTarget.method),
+                        invokeDirect.outValue(),
+                        invokeDirect.arguments()));
+              } else {
+                // The below assert is here because a well-type program should have a target, but we
+                // cannot throw a compilation error, since we have no knowledge about the input.
+                assert false;
+              }
             }
           }
         }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java
index e019acd..3bc9825 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java
@@ -6,6 +6,7 @@
 
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DebugLocalInfo;
+import com.android.tools.r8.graph.Descriptor;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
@@ -65,6 +66,7 @@
   private final Map<DexEncodedMethod, CandidateInfo> hostClassInits = new IdentityHashMap<>();
   private final Set<DexEncodedMethod> methodsToBeStaticized = Sets.newIdentityHashSet();
   private final Map<DexField, CandidateInfo> singletonFields = new IdentityHashMap<>();
+  private final Map<DexMethod, CandidateInfo> singletonGetters = new IdentityHashMap<>();
   private final Map<DexType, DexType> candidateToHostMapping = new IdentityHashMap<>();
 
   StaticizingProcessor(
@@ -94,7 +96,7 @@
                 .add(collectOptimizationInfo(feedback)));
 
     // Enqueue instance methods to be staticized (only remove references to 'this'). Intentionally
-    // not collection optimization info for these methods, since they will be reprocessed again
+    // not collecting optimization info for these methods, since they will be reprocessed again
     // below once staticized.
     enqueueMethodsWithCodeOptimizations(
         methodsToBeStaticized, optimizations -> optimizations.add(this::removeReferencesToThis));
@@ -203,19 +205,36 @@
       for (DexEncodedMethod method : info.referencedFrom) {
         IRCode code = method.buildIR(appView, appView.appInfo().originFor(method.method.holder));
         assert code != null;
-        List<StaticGet> singletonFieldReads =
+        List<Instruction> singletonUsers =
             Streams.stream(code.instructionIterator())
-                .filter(Instruction::isStaticGet)
-                .map(Instruction::asStaticGet)
-                .filter(get -> get.getField() == info.singletonField.field)
+                .filter(
+                    instruction -> {
+                      if (instruction.isStaticGet()
+                          && instruction.asStaticGet().getField() == info.singletonField.field) {
+                        return true;
+                      }
+                      DexEncodedMethod getter = info.getter.get();
+                      return getter != null
+                          && instruction.isInvokeStatic()
+                          && instruction.asInvokeStatic().getInvokedMethod() == getter.method;
+                    })
                 .collect(Collectors.toList());
         boolean fixableFieldReadsPerUsage = true;
-        for (StaticGet read : singletonFieldReads) {
-          Value dest = read.dest();
+        for (Instruction user : singletonUsers) {
+          if (user.outValue() == null) {
+            continue;
+          }
+          Value dest = user.outValue();
           visited.clear();
           trivialPhis.clear();
-          boolean onlyHasTrivialPhis = testAndCollectPhisComposedOfSameFieldRead(
-              visited, dest.uniquePhiUsers(), read.getField(), trivialPhis);
+          assert user.isInvokeStatic() || user.isStaticGet();
+          Descriptor member =
+              user.isStaticGet()
+                  ? user.asStaticGet().getField()
+                  : user.asInvokeStatic().getInvokedMethod();
+          boolean onlyHasTrivialPhis =
+              testAndCollectPhisComposedOfSameMember(
+                  visited, dest.uniquePhiUsers(), member, trivialPhis);
           if (dest.hasPhiUsers() && !onlyHasTrivialPhis) {
             fixableFieldReadsPerUsage = false;
             break;
@@ -255,6 +274,10 @@
         }
       }
       singletonFields.put(candidate.singletonField.field, candidate);
+      DexEncodedMethod getter = candidate.getter.get();
+      if (getter != null) {
+        singletonGetters.put(getter.method, candidate);
+      }
       referencingExtraMethods.addAll(candidate.referencedFrom);
     }
 
@@ -341,28 +364,38 @@
   }
 
   private void rewriteReferences(IRCode code) {
-    // Process all singleton field reads and rewrite their users.
-    List<StaticGet> singletonFieldReads =
+    // Fetch all instructions that reference singletons to avoid concurrent modifications to the
+    // instruction list that can arise from doing it directly in the iterator.
+    List<Instruction> singletonUsers =
         Streams.stream(code.instructionIterator())
-            .filter(Instruction::isStaticGet)
-            .map(Instruction::asStaticGet)
-            .filter(get -> singletonFields.containsKey(get.getField()))
+            .filter(
+                instruction ->
+                    (instruction.isStaticGet()
+                            && singletonFields.containsKey(
+                                instruction.asFieldInstruction().getField()))
+                        || (instruction.isInvokeStatic()
+                            && singletonGetters.containsKey(
+                                instruction.asInvokeStatic().getInvokedMethod())))
             .collect(Collectors.toList());
-
-    singletonFieldReads.forEach(
-        read -> {
-          DexField field = read.getField();
-          CandidateInfo candidateInfo = singletonFields.get(field);
-          assert candidateInfo != null;
-          Value value = read.dest();
-          if (value != null) {
-            fixupStaticizedFieldReadUsers(code, value, field);
-          }
-          if (!candidateInfo.preserveRead.get()) {
-            read.removeOrReplaceByDebugLocalRead(code);
-          }
-        });
-
+    for (Instruction singletonUser : singletonUsers) {
+      CandidateInfo candidateInfo;
+      Descriptor member;
+      if (singletonUser.isStaticGet()) {
+        candidateInfo = singletonFields.get(singletonUser.asStaticGet().getField());
+        member = singletonUser.asStaticGet().getField();
+      } else {
+        assert singletonUser.isInvokeStatic();
+        candidateInfo = singletonGetters.get(singletonUser.asInvokeStatic().getInvokedMethod());
+        member = singletonUser.asInvokeStatic().getInvokedMethod();
+      }
+      Value value = singletonUser.outValue();
+      if (value != null) {
+        fixupStaticizedFieldUsers(code, value, member);
+      }
+      if (!candidateInfo.preserveRead.get()) {
+        singletonUser.removeOrReplaceByDebugLocalRead(code);
+      }
+    }
     if (!candidateToHostMapping.isEmpty()) {
       remapMovedCandidates(code);
     }
@@ -429,7 +462,7 @@
   //    invoke-virtual { s1, ... } mtd1
   //    goto Exit
   //  b2:
-  //    s2 <- static-get singleton
+  //    s2 <- invoke-static getter()
   //    ...
   //    invoke-virtual { s2, ... } mtd1
   //    goto Exit
@@ -443,7 +476,7 @@
   //    ...
   //    goto Exit
   //  b2:
-  //    s2 <- static-get singleton
+  //    s2 <- invoke-static getter()
   //    ...
   //    goto Exit
   //  Exit:
@@ -454,8 +487,8 @@
   // From staticizer's viewpoint, `sp` is trivial in the sense that it is composed of values that
   // refer to the same singleton field. If so, we can safely relax the assertion; remove uses of
   // field reads; remove quasi-trivial phis; and then remove original field reads.
-  private boolean testAndCollectPhisComposedOfSameFieldRead(
-      Set<Phi> visited, Set<Phi> phisToCheck, DexField field, Set<Phi> trivialPhis) {
+  private boolean testAndCollectPhisComposedOfSameMember(
+      Set<Phi> visited, Set<Phi> phisToCheck, Descriptor dexMember, Set<Phi> trivialPhis) {
     for (Phi phi : phisToCheck) {
       if (!visited.add(phi)) {
         continue;
@@ -466,16 +499,20 @@
         if (v.isPhi()) {
           chainedPhis.add(operand.asPhi());
         } else {
-          if (!v.definition.isStaticGet()) {
+          Instruction definition = v.definition;
+          if (!definition.isStaticGet() && !definition.isInvokeStatic()) {
             return false;
           }
-          if (v.definition.asStaticGet().getField() != field) {
+          if (definition.isStaticGet() && definition.asStaticGet().getField() != dexMember) {
+            return false;
+          } else if (definition.isInvokeStatic()
+              && definition.asInvokeStatic().getInvokedMethod() != dexMember) {
             return false;
           }
         }
       }
       if (!chainedPhis.isEmpty()) {
-        if (!testAndCollectPhisComposedOfSameFieldRead(visited, chainedPhis, field, trivialPhis)) {
+        if (!testAndCollectPhisComposedOfSameMember(visited, chainedPhis, dexMember, trivialPhis)) {
           return false;
         }
       }
@@ -486,13 +523,14 @@
 
   // Fixup field read usages. Same as {@link #fixupStaticizedThisUsers} except this one determines
   // quasi-trivial phis, based on the original field.
-  private void fixupStaticizedFieldReadUsers(IRCode code, Value dest, DexField field) {
+  private void fixupStaticizedFieldUsers(IRCode code, Value dest, Descriptor member) {
     assert dest != null;
     // During the examine phase, field reads with any phi users have been invalidated, hence zero.
     // However, it may be not true if re-processing introduces phis after optimizing common suffix.
     Set<Phi> trivialPhis = Sets.newIdentityHashSet();
-    boolean onlyHasTrivialPhis = testAndCollectPhisComposedOfSameFieldRead(
-        Sets.newIdentityHashSet(), dest.uniquePhiUsers(), field, trivialPhis);
+    boolean onlyHasTrivialPhis =
+        testAndCollectPhisComposedOfSameMember(
+            Sets.newIdentityHashSet(), dest.uniquePhiUsers(), member, trivialPhis);
     assert !dest.hasPhiUsers() || onlyHasTrivialPhis;
     assert trivialPhis.isEmpty() || onlyHasTrivialPhis;
 
diff --git a/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java b/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
index 1a07b28..09aaea5 100644
--- a/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
@@ -133,7 +133,8 @@
     ClassNameMinifier classNameMinifier =
         new ClassNameMinifier(
             appView,
-            new ApplyMappingClassNamingStrategy(appView, mappedNames),
+            new ApplyMappingClassNamingStrategy(
+                appView, mappedNames, seedMapper.getMappedToDescriptorNames()),
             // The package naming strategy will actually not be used since all classes and methods
             // will be output with identity name if not found in mapping. However, there is a check
             // in the ClassNameMinifier that the strategy should produce a "fresh" name so we just
@@ -391,10 +392,13 @@
   static class ApplyMappingClassNamingStrategy extends MinificationClassNamingStrategy {
 
     private final Map<DexType, DexString> mappings;
+    private final Set<String> mappedNames;
 
-    ApplyMappingClassNamingStrategy(AppView<?> appView, Map<DexType, DexString> mappings) {
+    ApplyMappingClassNamingStrategy(
+        AppView<?> appView, Map<DexType, DexString> mappings, Set<String> mappedNames) {
       super(appView);
       this.mappings = mappings;
+      this.mappedNames = mappedNames;
     }
 
     @Override
@@ -405,7 +409,16 @@
         Predicate<DexString> isUsed) {
       assert !mappings.containsKey(type);
       assert appView.rootSet().mayBeMinified(type, appView);
-      return super.next(type, packagePrefix, state, isUsed);
+      return super.next(
+          type,
+          packagePrefix,
+          state,
+          candidate -> {
+            if (mappedNames.contains(candidate.toString())) {
+              return true;
+            }
+            return isUsed.test(candidate);
+          });
     }
 
     @Override
diff --git a/src/main/java/com/android/tools/r8/naming/SeedMapper.java b/src/main/java/com/android/tools/r8/naming/SeedMapper.java
index d7f36da..be69416 100644
--- a/src/main/java/com/android/tools/r8/naming/SeedMapper.java
+++ b/src/main/java/com/android/tools/r8/naming/SeedMapper.java
@@ -20,6 +20,7 @@
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.Map;
 import java.util.Set;
 
@@ -37,6 +38,7 @@
 
   static class Builder extends ProguardMap.Builder {
     final Map<String, ClassNamingForMapApplier.Builder> map = new HashMap<>();
+    final Set<String> mappedToDescriptorNames = new HashSet<>();
     private final Reporter reporter;
 
     private Builder(Reporter reporter) {
@@ -47,9 +49,11 @@
     ClassNamingForMapApplier.Builder classNamingBuilder(
         String renamedName, String originalName, Position position) {
       String originalDescriptor = javaTypeToDescriptor(originalName);
+      String renamedDescriptorName = javaTypeToDescriptor(renamedName);
+      mappedToDescriptorNames.add(renamedDescriptorName);
       ClassNamingForMapApplier.Builder classNamingBuilder =
           ClassNamingForMapApplier.builder(
-              javaTypeToDescriptor(renamedName), originalDescriptor, position, reporter);
+              renamedDescriptorName, originalDescriptor, position, reporter);
       if (map.put(originalDescriptor, classNamingBuilder) != null) {
         reporter.error(ProguardMapError.duplicateSourceClass(originalName, position));
       }
@@ -59,7 +63,7 @@
     @Override
     SeedMapper build() {
       reporter.failIfPendingErrors();
-      return new SeedMapper(ImmutableMap.copyOf(map), reporter);
+      return new SeedMapper(ImmutableMap.copyOf(map), mappedToDescriptorNames, reporter);
     }
   }
 
@@ -82,15 +86,20 @@
   }
 
   private final ImmutableMap<String, ClassNamingForMapApplier> mappings;
+  private final Set<String> mappedToDescriptorNames;
   private final Reporter reporter;
 
-  private SeedMapper(Map<String, ClassNamingForMapApplier.Builder> mappings, Reporter reporter) {
+  private SeedMapper(
+      Map<String, ClassNamingForMapApplier.Builder> mappings,
+      Set<String> mappedToDescriptorNames,
+      Reporter reporter) {
     this.reporter = reporter;
     ImmutableMap.Builder<String, ClassNamingForMapApplier> builder = ImmutableMap.builder();
     for(Map.Entry<String, ClassNamingForMapApplier.Builder> entry : mappings.entrySet()) {
       builder.put(entry.getKey(), entry.getValue().build());
     }
     this.mappings = builder.build();
+    this.mappedToDescriptorNames = mappedToDescriptorNames;
     verifyMappingsAreConflictFree();
   }
 
@@ -140,6 +149,10 @@
     return mappings.keySet();
   }
 
+  public Set<String> getMappedToDescriptorNames() {
+    return mappedToDescriptorNames;
+  }
+
   public ClassNamingForMapApplier getMapping(String key) {
     return mappings.get(key);
   }
diff --git a/src/main/java/com/android/tools/r8/utils/ArchiveBuilder.java b/src/main/java/com/android/tools/r8/utils/ArchiveBuilder.java
index 2276790..a47684f 100644
--- a/src/main/java/com/android/tools/r8/utils/ArchiveBuilder.java
+++ b/src/main/java/com/android/tools/r8/utils/ArchiveBuilder.java
@@ -86,14 +86,9 @@
   }
 
   /** Get or open the zip output stream. */
-  private synchronized ZipOutputStream getStream(DiagnosticsHandler handler) {
+  private synchronized ZipOutputStream getStream() throws IOException {
     assert !closed;
-    try {
-      getStreamRaw();
-    } catch (IOException e) {
-      handler.error(new ExceptionDiagnostic(e, origin));
-    }
-    return stream;
+    return getStreamRaw();
   }
 
   private void handleIOException(IOException e, DiagnosticsHandler handler) {
@@ -117,9 +112,9 @@
     }
     ZipEntry entry = new ZipEntry(name);
     entry.setTime(0);
-    ZipOutputStream zip = getStream(handler);
     synchronized (this) {
       try {
+        ZipOutputStream zip = getStream();
         zip.putNextEntry(entry);
         zip.closeEntry();
       } catch (IOException e) {
@@ -150,7 +145,7 @@
 
   private void writeFileNow(String name, ByteDataView content, DiagnosticsHandler handler) {
     try {
-      ZipUtils.writeToZipStream(getStream(handler), name, content, ZipEntry.DEFLATED);
+      ZipUtils.writeToZipStream(getStream(), name, content, ZipEntry.DEFLATED);
     } catch (IOException e) {
       handleIOException(e, handler);
     }
diff --git a/src/test/java/com/android/tools/r8/desugar/DefaultMethodWithAccessTest.java b/src/test/java/com/android/tools/r8/desugar/DefaultMethodWithAccessTest.java
new file mode 100644
index 0000000..0c778d0
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/DefaultMethodWithAccessTest.java
@@ -0,0 +1,96 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.desugar;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.transformers.ClassFileTransformer;
+import com.android.tools.r8.utils.BooleanUtils;
+import java.io.IOException;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+import org.objectweb.asm.Opcodes;
+// This is a reproduction of b/153042496 in a java-only setting.
+
+@RunWith(Parameterized.class)
+public class DefaultMethodWithAccessTest extends TestBase {
+
+  private final TestParameters parameters;
+  private final boolean implementI0I1;
+
+  @Parameters(name = "{0}, implementI0I1: {1}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withAllRuntimesAndApiLevels().build(), BooleanUtils.values());
+  }
+
+  public DefaultMethodWithAccessTest(TestParameters parameters, boolean implementI0I1) {
+    this.parameters = parameters;
+    this.implementI0I1 = implementI0I1;
+  }
+
+  @Test
+  public void testRuntime() throws ExecutionException, CompilationFailedException, IOException {
+    testForRuntime(parameters)
+        .addProgramClasses(I0.class, I1.class, Main.class, Impl.class)
+        .addProgramClassFileData(transformI2AccessToInvokeSpecial())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("Hello World!");
+  }
+
+  private byte[] transformI2AccessToInvokeSpecial() throws IOException {
+    ClassFileTransformer classFileTransformer =
+        transformer(I2.class)
+            .transformMethodInsnInMethod(
+                "access",
+                (opcode, owner, name, descriptor, isInterface, continuation) -> {
+                  continuation.apply(
+                      name.equals("print") ? Opcodes.INVOKESPECIAL : opcode,
+                      owner,
+                      name,
+                      descriptor,
+                      isInterface);
+                });
+    if (implementI0I1) {
+      classFileTransformer.setImplements(I0.class, I1.class);
+    }
+    return classFileTransformer.transform();
+  }
+
+  public interface I0 {
+    void print();
+  }
+
+  public interface I1 {
+    default void print() {
+      System.out.println("Hello World!");
+    }
+  }
+
+  public interface I2 extends /* I0, */ I1 {
+
+    static void access(I2 i2) {
+      /* invoke-special */ i2.print();
+    }
+  }
+
+  public static class Impl implements I2 {}
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      testPrint(new Impl());
+    }
+
+    public static void testPrint(I2 i) {
+      I2.access(i);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/PrivateMethodsInInterfaceTest.java b/src/test/java/com/android/tools/r8/desugar/PrivateMethodsInInterfaceTest.java
new file mode 100644
index 0000000..564f831
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/PrivateMethodsInInterfaceTest.java
@@ -0,0 +1,102 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.desugar;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import java.io.IOException;
+import java.util.concurrent.ExecutionException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+import org.objectweb.asm.Opcodes;
+
+@RunWith(Parameterized.class)
+public class PrivateMethodsInInterfaceTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public PrivateMethodsInInterfaceTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testRuntime()
+      throws NoSuchMethodException, IOException, CompilationFailedException, ExecutionException {
+    testForRuntime(parameters)
+        .addProgramClasses(SubI.class, Impl.class, Main.class)
+        .addProgramClassFileData(transformIToPrivate())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("Hello World!", "Hello World!", "Hello World!");
+  }
+
+  private byte[] transformIToPrivate() throws NoSuchMethodException, IOException {
+    return transformer(I.class)
+        .setPrivate(I.class.getDeclaredMethod("bar"))
+        .setPrivate(I.class.getDeclaredMethod("baz", I.class))
+        .transformMethodInsnInMethod(
+            "foo",
+            ((opcode, owner, name, descriptor, isInterface, continuation) -> {
+              continuation.apply(
+                  name.equals("bar") ? Opcodes.INVOKESPECIAL : opcode,
+                  owner,
+                  name,
+                  descriptor,
+                  isInterface);
+            }))
+        .transformMethodInsnInMethod(
+            "baz",
+            ((opcode, owner, name, descriptor, isInterface, continuation) -> {
+              continuation.apply(
+                  name.equals("bar") ? Opcodes.INVOKESPECIAL : opcode,
+                  owner,
+                  name,
+                  descriptor,
+                  isInterface);
+            }))
+        .transform();
+  }
+
+  public interface I {
+
+    default void foo() {
+      bar();
+      I.qux(this);
+    }
+
+    /* private */ default void bar() {
+      System.out.println("Hello World!");
+    }
+
+    /* private */ static void baz(I i) {
+      i.bar();
+    }
+
+    static void qux(I i) {
+      baz(i);
+    }
+  }
+
+  public interface SubI extends I {}
+
+  public static class Impl implements SubI {}
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      Impl impl = new Impl();
+      impl.foo();
+      I.qux(impl);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerTest.java b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerTest.java
index d0a4064..5cd6a9a 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerTest.java
@@ -12,6 +12,7 @@
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 
+import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
@@ -50,11 +51,14 @@
 import com.android.tools.r8.ir.optimize.staticizer.trivial.TrivialTestClass;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
 import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Streams;
+import java.io.IOException;
 import java.util.List;
+import java.util.concurrent.ExecutionException;
 import java.util.stream.Collectors;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -64,6 +68,37 @@
 public class ClassStaticizerTest extends TestBase {
   private final TestParameters parameters;
 
+  private static final String EXPECTED =
+      StringUtils.lines(
+          "Simple::bar(Simple::foo())",
+          "Simple::bar(0)",
+          "SimpleWithPhi$Companion::bar(SimpleWithPhi$Companion::foo()) true",
+          "SimpleWithSideEffects::<clinit>()",
+          "SimpleWithSideEffects::bar(SimpleWithSideEffects::foo())",
+          "SimpleWithSideEffects::bar(1)",
+          "SimpleWithParams::bar(SimpleWithParams::foo())",
+          "SimpleWithParams::bar(2)",
+          "SimpleWithGetter::bar(SimpleWithGetter::foo())",
+          "SimpleWithGetter::bar(3)",
+          "Simple::bar(Simple::foo())",
+          "Simple::bar(4)",
+          "Simple::bar(Simple::foo())",
+          "Simple::bar(5)");
+
+  private static final Class<?> main = TrivialTestClass.class;
+  private static final Class<?>[] classes = {
+    NeverInline.class,
+    TrivialTestClass.class,
+    Simple.class,
+    SimpleWithGetter.class,
+    SimpleWithLazyInit.class,
+    SimpleWithParams.class,
+    SimpleWithPhi.class,
+    SimpleWithPhi.Companion.class,
+    SimpleWithSideEffects.class,
+    SimpleWithThrowingGetter.class
+  };
+
   @Parameterized.Parameters(name = "{0}")
   public static TestParametersCollection data() {
     // TODO(b/112831361): support for class staticizer in CF backend.
@@ -75,21 +110,20 @@
   }
 
   @Test
+  public void testWithoutAccessModification()
+      throws ExecutionException, CompilationFailedException, IOException {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(classes)
+        .addKeepMainRule(main)
+        .addKeepAttributes("InnerClasses", "EnclosingMethod")
+        .addOptionsModification(this::configure)
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), main)
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  @Test
   public void testTrivial() throws Exception {
-    Class<?> main = TrivialTestClass.class;
-    Class<?>[] classes = {
-        NeverInline.class,
-        TrivialTestClass.class,
-        Simple.class,
-        SimpleWithGetter.class,
-        SimpleWithLazyInit.class,
-        SimpleWithParams.class,
-        SimpleWithPhi.class,
-        SimpleWithPhi.Companion.class,
-        SimpleWithSideEffects.class,
-        SimpleWithThrowingGetter.class
-    };
-    String javaOutput = runOnJava(main);
     TestRunResult result =
         testForR8(parameters.getBackend())
             .addProgramClasses(classes)
@@ -101,7 +135,7 @@
             .allowAccessModification()
             .setMinApi(parameters.getApiLevel())
             .run(parameters.getRuntime(), main)
-            .assertSuccessWithOutput(javaOutput);
+            .assertSuccessWithOutput(EXPECTED);
 
     CodeInspector inspector = result.inspector();
     ClassSubject clazz = inspector.clazz(main);
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/trivial/SimpleWithGetter.java b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/trivial/SimpleWithGetter.java
index 55487ac..6bc91b4 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/trivial/SimpleWithGetter.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/trivial/SimpleWithGetter.java
@@ -15,11 +15,11 @@
 
   @NeverInline
   String foo() {
-    return bar("Simple::foo()");
+    return bar("SimpleWithGetter::foo()");
   }
 
   @NeverInline
   String bar(String other) {
-    return "Simple::bar(" + other + ")";
+    return "SimpleWithGetter::bar(" + other + ")";
   }
 }
diff --git a/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingDesugarLambdaTest.java b/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingDesugarLambdaTest.java
new file mode 100644
index 0000000..63fe00f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingDesugarLambdaTest.java
@@ -0,0 +1,120 @@
+package com.android.tools.r8.naming.applymapping;
+
+import static com.android.tools.r8.Collectors.toSingle;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isRenamed;
+import static junit.framework.TestCase.assertEquals;
+import static junit.framework.TestCase.assertNotSame;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.R8TestCompileResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.concurrent.ExecutionException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+// This is a reproduction of b/152715309.
+@RunWith(Parameterized.class)
+public class ApplyMappingDesugarLambdaTest extends TestBase {
+
+  private static final String EXPECTED = "FOO";
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withDexRuntimes().withAllApiLevels().build();
+  }
+
+  public ApplyMappingDesugarLambdaTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testR8() throws CompilationFailedException, IOException, ExecutionException {
+    // Create a dictionary to control the naming.
+    Path dictionary = temp.getRoot().toPath().resolve("dictionary.txt");
+    FileUtils.writeTextFile(dictionary, "e");
+
+    final String finalName = "com.android.tools.r8.naming.applymapping.e";
+
+    R8TestCompileResult libraryResult =
+        testForR8(parameters.getBackend())
+            .addProgramClasses(A.class)
+            .addKeepClassAndMembersRulesWithAllowObfuscation(A.class)
+            .setMinApi(parameters.getApiLevel())
+            .addKeepRules(
+                "-keeppackagenames", "-classobfuscationdictionary " + dictionary.toString())
+            .compile()
+            .inspect(
+                inspector -> {
+                  assertThat(inspector.clazz(A.class), isRenamed());
+                  assertEquals(finalName, inspector.clazz(A.class).getFinalName());
+                });
+
+    Path libraryPath = libraryResult.writeToZip();
+
+    // Ensure that the library works as supposed.
+    testForD8()
+        .addProgramClasses(I.class, Main.class)
+        .addClasspathFiles(libraryPath)
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), Main.class, EXPECTED)
+        .assertSuccessWithOutputLines(EXPECTED);
+
+    testForR8(parameters.getBackend())
+        .addClasspathClasses(A.class)
+        .addProgramClasses(I.class, Main.class)
+        .addKeepMainRule(Main.class)
+        .addKeepClassAndMembersRules(I.class)
+        .setMinApi(parameters.getApiLevel())
+        .addApplyMapping(libraryResult.getProguardMap())
+        .addOptionsModification(internalOptions -> internalOptions.enableClassInlining = false)
+        .addKeepRules("-classobfuscationdictionary " + dictionary.toString())
+        .compile()
+        .inspect(
+            inspector -> {
+              // Assert that there is a lambda class created.
+              assertEquals(3, inspector.allClasses().size());
+              FoundClassSubject lambdaClass =
+                  inspector.allClasses().stream()
+                      .filter(FoundClassSubject::isSynthetic)
+                      .collect(toSingle());
+              assertNotSame(finalName, lambdaClass.getFinalName());
+            })
+        .addRunClasspathFiles(libraryPath)
+        .run(parameters.getRuntime(), Main.class, EXPECTED)
+        .assertSuccessWithOutputLines(EXPECTED);
+  }
+
+  public static class A {
+
+    A(int bar) {
+      System.out.println(bar);
+    }
+  }
+
+  @FunctionalInterface
+  public interface I {
+
+    void doStuff();
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      processI(() -> System.out.println(args[0]));
+    }
+
+    public static void processI(I i) {
+      i.doStuff();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/regress/b152973695/CompileToInvalidFileTest.java b/src/test/java/com/android/tools/r8/regress/b152973695/CompileToInvalidFileTest.java
new file mode 100644
index 0000000..d635af8
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/regress/b152973695/CompileToInvalidFileTest.java
@@ -0,0 +1,104 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.regress.b152973695;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeFalse;
+
+import com.android.tools.r8.ClassFileConsumer;
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.DexIndexedConsumer.ArchiveConsumer;
+import com.android.tools.r8.ProgramConsumer;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.BooleanUtils;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.StandardOpenOption;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class CompileToInvalidFileTest extends TestBase {
+
+  private static final Path INVALID_FILE = Paths.get("!@#/\\INVALID_FILE");
+
+  private final boolean classFileConsumer;
+
+  @Parameterized.Parameters(name = "{0}, classfileConsumer: {1}")
+  public static List<Object[]> data() {
+    return buildParameters(getTestParameters().withNoneRuntime().build(), BooleanUtils.values());
+  }
+
+  public CompileToInvalidFileTest(TestParameters parameters, boolean classFileConsumer) {
+    this.classFileConsumer = classFileConsumer;
+  }
+
+  @Test
+  public void testCompileToInvalidFileD8() {
+    assumeFalse(classFileConsumer);
+    ensureInvalidFileIsInvalid();
+    ProgramConsumer programConsumer =
+        classFileConsumer
+            ? new ClassFileConsumer.ArchiveConsumer(INVALID_FILE)
+            : new ArchiveConsumer(INVALID_FILE);
+    try {
+      testForD8().addProgramClasses(Main.class).setProgramConsumer(programConsumer).compile();
+      fail("Expected a CompilationFailedException but the code succeeded");
+    } catch (CompilationFailedException ex) {
+      assertInvalidFileNotFound(ex);
+    } catch (Throwable t) {
+      fail("Expected a CompilationFailedException but got instead " + t);
+    }
+  }
+
+  @Test
+  public void testCompileToInvalidFileR8() {
+    ensureInvalidFileIsInvalid();
+    ProgramConsumer programConsumer =
+        classFileConsumer
+            ? new ClassFileConsumer.ArchiveConsumer(INVALID_FILE)
+            : new ArchiveConsumer(INVALID_FILE);
+    try {
+      testForR8(classFileConsumer ? Backend.CF : Backend.DEX)
+          .addProgramClasses(Main.class)
+          .addKeepMainRule(Main.class)
+          .setProgramConsumer(programConsumer)
+          .compile();
+      fail("Expected a CompilationFailedException but the code succeeded");
+    } catch (CompilationFailedException ex) {
+      assertInvalidFileNotFound(ex);
+    } catch (Throwable t) {
+      fail("Expected a CompilationFailedException but got instead " + t);
+    }
+  }
+
+  private void assertInvalidFileNotFound(CompilationFailedException ex) {
+    assertTrue(ex.getCause().getMessage().contains("File not found"));
+    assertTrue(ex.getCause().getMessage().contains(INVALID_FILE.toString()));
+  }
+
+  private void ensureInvalidFileIsInvalid() {
+    try {
+      Files.newOutputStream(
+          INVALID_FILE, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
+      fail("Expected an IOException but the code succeeded");
+    } catch (IOException ignored) {
+    } catch (Throwable t) {
+      fail("Expected an IOException but got instead " + t);
+    }
+  }
+
+  static class Main {
+    public static void main(String[] args) {
+      System.out.println("Hello world!");
+    }
+  }
+}