Merge commit '17495d90b34de7210672c7715b0aa5d734dbc094' into dev-release
diff --git a/src/library_desugar/desugar_jdk_libs.json b/src/library_desugar/desugar_jdk_libs.json
index 049699a..d46b12d 100644
--- a/src/library_desugar/desugar_jdk_libs.json
+++ b/src/library_desugar/desugar_jdk_libs.json
@@ -2,7 +2,7 @@
   "configuration_format_version": 3,
   "group_id" : "com.tools.android",
   "artifact_id" : "desugar_jdk_libs",
-  "version": "1.0.10",
+  "version": "1.0.11",
   "required_compilation_api_level": 26,
   "synthesized_library_classes_package_prefix": "j$.",
   "common_flags": [
diff --git a/src/library_desugar/java/java/util/DoubleSummaryStatisticsConversions.java b/src/library_desugar/java/java/util/DoubleSummaryStatisticsConversions.java
index a0f6c8d..63845fc 100644
--- a/src/library_desugar/java/java/util/DoubleSummaryStatisticsConversions.java
+++ b/src/library_desugar/java/java/util/DoubleSummaryStatisticsConversions.java
@@ -4,80 +4,19 @@
 
 package java.util;
 
-import java.lang.reflect.Field;
-
 public class DoubleSummaryStatisticsConversions {
 
-  private static final Field JAVA_LONG_COUNT_FIELD;
-  private static final Field JAVA_DOUBLE_SUM_FIELD;
-  private static final Field JAVA_DOUBLE_MIN_FIELD;
-  private static final Field JAVA_DOUBLE_MAX_FIELD;
-  private static final Field JD_LONG_COUNT_FIELD;
-  private static final Field JD_DOUBLE_SUM_FIELD;
-  private static final Field JD_DOUBLE_MIN_FIELD;
-  private static final Field JD_DOUBLE_MAX_FIELD;
-
-  static {
-    Class<?> javaDoubleSummaryStatisticsClass = java.util.DoubleSummaryStatistics.class;
-    JAVA_LONG_COUNT_FIELD = getField(javaDoubleSummaryStatisticsClass, "count");
-    JAVA_LONG_COUNT_FIELD.setAccessible(true);
-    JAVA_DOUBLE_SUM_FIELD = getField(javaDoubleSummaryStatisticsClass, "sum");
-    JAVA_DOUBLE_SUM_FIELD.setAccessible(true);
-    JAVA_DOUBLE_MIN_FIELD = getField(javaDoubleSummaryStatisticsClass, "min");
-    JAVA_DOUBLE_MIN_FIELD.setAccessible(true);
-    JAVA_DOUBLE_MAX_FIELD = getField(javaDoubleSummaryStatisticsClass, "max");
-    JAVA_DOUBLE_MAX_FIELD.setAccessible(true);
-
-    Class<?> jdDoubleSummaryStatisticsClass = j$.util.DoubleSummaryStatistics.class;
-    JD_LONG_COUNT_FIELD = getField(jdDoubleSummaryStatisticsClass, "count");
-    JD_LONG_COUNT_FIELD.setAccessible(true);
-    JD_DOUBLE_SUM_FIELD = getField(jdDoubleSummaryStatisticsClass, "sum");
-    JD_DOUBLE_SUM_FIELD.setAccessible(true);
-    JD_DOUBLE_MIN_FIELD = getField(jdDoubleSummaryStatisticsClass, "min");
-    JD_DOUBLE_MIN_FIELD.setAccessible(true);
-    JD_DOUBLE_MAX_FIELD = getField(jdDoubleSummaryStatisticsClass, "max");
-    JD_DOUBLE_MAX_FIELD.setAccessible(true);
-  }
-
   private DoubleSummaryStatisticsConversions() {}
 
-  private static Field getField(Class<?> clazz, String name) {
-    try {
-      return clazz.getDeclaredField(name);
-    } catch (NoSuchFieldException e) {
-      throw new Error("Failed summary statistics set-up.", e);
-    }
-  }
-
   public static j$.util.DoubleSummaryStatistics convert(java.util.DoubleSummaryStatistics stats) {
-    if (stats == null) {
-      return null;
-    }
-    j$.util.DoubleSummaryStatistics newInstance = new j$.util.DoubleSummaryStatistics();
-    try {
-      JD_LONG_COUNT_FIELD.set(newInstance, stats.getCount());
-      JD_DOUBLE_SUM_FIELD.set(newInstance, stats.getSum());
-      JD_DOUBLE_MIN_FIELD.set(newInstance, stats.getMin());
-      JD_DOUBLE_MAX_FIELD.set(newInstance, stats.getMax());
-    } catch (IllegalAccessException e) {
-      throw new Error("Failed summary statistics conversion.", e);
-    }
-    return newInstance;
+    throw new Error(
+        "Java 8+ API desugaring (library desugaring) cannot convert"
+            + " to java.util.DoubleSummaryStatistics");
   }
 
   public static java.util.DoubleSummaryStatistics convert(j$.util.DoubleSummaryStatistics stats) {
-    if (stats == null) {
-      return null;
-    }
-    java.util.DoubleSummaryStatistics newInstance = new java.util.DoubleSummaryStatistics();
-    try {
-      JAVA_LONG_COUNT_FIELD.set(newInstance, stats.getCount());
-      JAVA_DOUBLE_SUM_FIELD.set(newInstance, stats.getSum());
-      JAVA_DOUBLE_MIN_FIELD.set(newInstance, stats.getMin());
-      JAVA_DOUBLE_MAX_FIELD.set(newInstance, stats.getMax());
-    } catch (IllegalAccessException e) {
-      throw new Error("Failed summary statistics conversion.", e);
-    }
-    return newInstance;
+    throw new Error(
+        "Java 8+ API desugaring (library desugaring) cannot convert"
+            + " from java.util.DoubleSummaryStatistics");
   }
 }
diff --git a/src/library_desugar/java/java/util/IntSummaryStatisticsConversions.java b/src/library_desugar/java/java/util/IntSummaryStatisticsConversions.java
index 8e9b616..3645a41 100644
--- a/src/library_desugar/java/java/util/IntSummaryStatisticsConversions.java
+++ b/src/library_desugar/java/java/util/IntSummaryStatisticsConversions.java
@@ -4,81 +4,19 @@
 
 package java.util;
 
-import java.lang.reflect.Field;
-import java.lang.reflect.Modifier;
-
 public class IntSummaryStatisticsConversions {
 
-  private static final Field JAVA_LONG_COUNT_FIELD;
-  private static final Field JAVA_LONG_SUM_FIELD;
-  private static final Field JAVA_INT_MIN_FIELD;
-  private static final Field JAVA_INT_MAX_FIELD;
-  private static final Field JD_LONG_COUNT_FIELD;
-  private static final Field JD_LONG_SUM_FIELD;
-  private static final Field JD_INT_MIN_FIELD;
-  private static final Field JD_INT_MAX_FIELD;
-
-  static {
-    Class<?> javaIntSummaryStatisticsClass = java.util.IntSummaryStatistics.class;
-    JAVA_LONG_COUNT_FIELD = getField(javaIntSummaryStatisticsClass, "count");
-    JAVA_LONG_COUNT_FIELD.setAccessible(true);
-    JAVA_LONG_SUM_FIELD = getField(javaIntSummaryStatisticsClass, "sum");
-    JAVA_LONG_SUM_FIELD.setAccessible(true);
-    JAVA_INT_MIN_FIELD = getField(javaIntSummaryStatisticsClass, "min");
-    JAVA_INT_MIN_FIELD.setAccessible(true);
-    JAVA_INT_MAX_FIELD = getField(javaIntSummaryStatisticsClass, "max");
-    JAVA_INT_MAX_FIELD.setAccessible(true);
-
-    Class<?> jdIntSummaryStatisticsClass = j$.util.IntSummaryStatistics.class;
-    JD_LONG_COUNT_FIELD = getField(jdIntSummaryStatisticsClass, "count");
-    JD_LONG_COUNT_FIELD.setAccessible(true);
-    JD_LONG_SUM_FIELD = getField(jdIntSummaryStatisticsClass, "sum");
-    JD_LONG_SUM_FIELD.setAccessible(true);
-    JD_INT_MIN_FIELD = getField(jdIntSummaryStatisticsClass, "min");
-    JD_INT_MIN_FIELD.setAccessible(true);
-    JD_INT_MAX_FIELD = getField(jdIntSummaryStatisticsClass, "max");
-    JD_INT_MAX_FIELD.setAccessible(true);
-  }
-
   private IntSummaryStatisticsConversions() {}
 
-  private static Field getField(Class<?> clazz, String name) {
-    try {
-      return clazz.getDeclaredField(name);
-    } catch (NoSuchFieldException e) {
-      throw new Error("Failed summary statistics set-up.", e);
-    }
-  }
-
   public static j$.util.IntSummaryStatistics convert(java.util.IntSummaryStatistics stats) {
-    if (stats == null) {
-      return null;
-    }
-    j$.util.IntSummaryStatistics newInstance = new j$.util.IntSummaryStatistics();
-    try {
-      JD_LONG_COUNT_FIELD.set(newInstance, stats.getCount());
-      JD_LONG_SUM_FIELD.set(newInstance, stats.getSum());
-      JD_INT_MIN_FIELD.set(newInstance, stats.getMin());
-      JD_INT_MAX_FIELD.set(newInstance, stats.getMax());
-    } catch (IllegalAccessException e) {
-      throw new Error("Failed summary statistics conversion.", e);
-    }
-    return newInstance;
+    throw new Error(
+        "Java 8+ API desugaring (library desugaring) cannot convert"
+            + " to java.util.IntSummaryStatistics");
   }
 
   public static java.util.IntSummaryStatistics convert(j$.util.IntSummaryStatistics stats) {
-    if (stats == null) {
-      return null;
-    }
-    java.util.IntSummaryStatistics newInstance = new java.util.IntSummaryStatistics();
-    try {
-      JAVA_LONG_COUNT_FIELD.set(newInstance, stats.getCount());
-      JAVA_LONG_SUM_FIELD.set(newInstance, stats.getSum());
-      JAVA_INT_MIN_FIELD.set(newInstance, stats.getMin());
-      JAVA_INT_MAX_FIELD.set(newInstance, stats.getMax());
-    } catch (IllegalAccessException e) {
-      throw new Error("Failed summary statistics conversion.", e);
-    }
-    return newInstance;
+    throw new Error(
+        "Java 8+ API desugaring (library desugaring) cannot convert"
+            + " from java.util.IntSummaryStatistics");
   }
 }
diff --git a/src/library_desugar/java/java/util/LongSummaryStatisticsConversions.java b/src/library_desugar/java/java/util/LongSummaryStatisticsConversions.java
index ae21eb2..af2c95b 100644
--- a/src/library_desugar/java/java/util/LongSummaryStatisticsConversions.java
+++ b/src/library_desugar/java/java/util/LongSummaryStatisticsConversions.java
@@ -4,81 +4,19 @@
 
 package java.util;
 
-import java.lang.reflect.Field;
-import java.lang.reflect.Modifier;
-
 public class LongSummaryStatisticsConversions {
 
-  private static final Field JAVA_LONG_COUNT_FIELD;
-  private static final Field JAVA_LONG_SUM_FIELD;
-  private static final Field JAVA_LONG_MIN_FIELD;
-  private static final Field JAVA_LONG_MAX_FIELD;
-  private static final Field JD_LONG_COUNT_FIELD;
-  private static final Field JD_LONG_SUM_FIELD;
-  private static final Field JD_LONG_MIN_FIELD;
-  private static final Field JD_LONG_MAX_FIELD;
-
-  static {
-    Class<?> javaLongSummaryStatisticsClass = java.util.LongSummaryStatistics.class;
-    JAVA_LONG_COUNT_FIELD = getField(javaLongSummaryStatisticsClass, "count");
-    JAVA_LONG_COUNT_FIELD.setAccessible(true);
-    JAVA_LONG_SUM_FIELD = getField(javaLongSummaryStatisticsClass, "sum");
-    JAVA_LONG_SUM_FIELD.setAccessible(true);
-    JAVA_LONG_MIN_FIELD = getField(javaLongSummaryStatisticsClass, "min");
-    JAVA_LONG_MIN_FIELD.setAccessible(true);
-    JAVA_LONG_MAX_FIELD = getField(javaLongSummaryStatisticsClass, "max");
-    JAVA_LONG_MAX_FIELD.setAccessible(true);
-
-    Class<?> jdLongSummaryStatisticsClass = j$.util.LongSummaryStatistics.class;
-    JD_LONG_COUNT_FIELD = getField(jdLongSummaryStatisticsClass, "count");
-    JD_LONG_COUNT_FIELD.setAccessible(true);
-    JD_LONG_SUM_FIELD = getField(jdLongSummaryStatisticsClass, "sum");
-    JD_LONG_SUM_FIELD.setAccessible(true);
-    JD_LONG_MIN_FIELD = getField(jdLongSummaryStatisticsClass, "min");
-    JD_LONG_MIN_FIELD.setAccessible(true);
-    JD_LONG_MAX_FIELD = getField(jdLongSummaryStatisticsClass, "max");
-    JD_LONG_MAX_FIELD.setAccessible(true);
-  }
-
   private LongSummaryStatisticsConversions() {}
 
-  private static Field getField(Class<?> clazz, String name) {
-    try {
-      return clazz.getDeclaredField(name);
-    } catch (NoSuchFieldException e) {
-      throw new Error("Failed summary statistics set-up.", e);
-    }
-  }
-
   public static j$.util.LongSummaryStatistics convert(java.util.LongSummaryStatistics stats) {
-    if (stats == null) {
-      return null;
-    }
-    j$.util.LongSummaryStatistics newInstance = new j$.util.LongSummaryStatistics();
-    try {
-      JD_LONG_COUNT_FIELD.set(newInstance, stats.getCount());
-      JD_LONG_SUM_FIELD.set(newInstance, stats.getSum());
-      JD_LONG_MIN_FIELD.set(newInstance, stats.getMin());
-      JD_LONG_MAX_FIELD.set(newInstance, stats.getMax());
-    } catch (IllegalAccessException e) {
-      throw new Error("Failed summary statistics conversion.", e);
-    }
-    return newInstance;
+    throw new Error(
+        "Java 8+ API desugaring (library desugaring) cannot convert"
+            + " to java.util.LongSummaryStatistics");
   }
 
   public static java.util.LongSummaryStatistics convert(j$.util.LongSummaryStatistics stats) {
-    if (stats == null) {
-      return null;
-    }
-    java.util.LongSummaryStatistics newInstance = new java.util.LongSummaryStatistics();
-    try {
-      JAVA_LONG_COUNT_FIELD.set(newInstance, stats.getCount());
-      JAVA_LONG_SUM_FIELD.set(newInstance, stats.getSum());
-      JAVA_LONG_MIN_FIELD.set(newInstance, stats.getMin());
-      JAVA_LONG_MAX_FIELD.set(newInstance, stats.getMax());
-    } catch (IllegalAccessException e) {
-      throw new Error("Failed summary statistics conversion.", e);
-    }
-    return newInstance;
+    throw new Error(
+        "Java 8+ API desugaring (library desugaring) cannot convert"
+            + " from java.util.LongSummaryStatistics");
   }
 }
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 07948a1..17ee9af 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -6,6 +6,8 @@
 import static com.android.tools.r8.R8Command.USAGE_MESSAGE;
 import static com.android.tools.r8.utils.ExceptionUtils.unwrapExecutionException;
 
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.cf.code.CfPosition;
 import com.android.tools.r8.dex.ApplicationReader;
 import com.android.tools.r8.dex.ApplicationWriter;
 import com.android.tools.r8.dex.Marker;
@@ -16,10 +18,15 @@
 import com.android.tools.r8.graph.AppServices;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.AppliedGraphLens;
+import com.android.tools.r8.graph.CfCode;
+import com.android.tools.r8.graph.Code;
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexCode;
+import com.android.tools.r8.graph.DexDebugEvent;
 import com.android.tools.r8.graph.DexDefinition;
 import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexReference;
 import com.android.tools.r8.graph.DexType;
@@ -609,11 +616,6 @@
         }
       }
 
-      // Overwrite SourceFile if specified. This step should be done after IR conversion.
-      timing.begin("Rename SourceFile");
-      new SourceFileRewriter(appViewWithLiveness).run();
-      timing.end();
-
       // Collect the already pruned types before creating a new app info without liveness.
       Set<DexType> prunedTypes = appView.withLiveness().appInfo().getPrunedTypes();
 
@@ -699,7 +701,8 @@
                       executorService,
                       timing)
                   .withEnumValueInfoMaps(enumValueInfoMapCollection));
-
+          // Rerunning the enqueuer should not give rise to any method rewritings.
+          assert enqueuer.buildGraphLense(appView) == appView.graphLense();
           appView.withGeneratedMessageLiteBuilderShrinker(
               shrinker ->
                   shrinker.rewriteDeadBuilderReferencesFromDynamicMethods(
@@ -815,6 +818,8 @@
       new KotlinMetadataRewriter(appView, namingLens).run(executorService);
       timing.end();
 
+      assert verifyMovedMethodsHaveOriginalMethodPosition(appView, application);
+
       timing.begin("Line number remapping");
       // When line number optimization is turned off the identity mapping for line numbers is
       // used. We still run the line number optimizer to collect line numbers and inline frame
@@ -823,6 +828,11 @@
           LineNumberOptimizer.run(appView, application, inputApp, namingLens);
       timing.end();
 
+      // Overwrite SourceFile if specified. This step should be done after IR conversion.
+      timing.begin("Rename SourceFile");
+      new SourceFileRewriter(appView, application).run();
+      timing.end();
+
       // If a method filter is present don't produce output since the application is likely partial.
       if (options.hasMethodsFilter()) {
         System.out.println("Finished compilation with method filter: ");
@@ -893,6 +903,58 @@
     }
   }
 
+  private static boolean verifyMovedMethodsHaveOriginalMethodPosition(
+      AppView<?> appView, DirectMappedDexApplication application) {
+    application
+        .classes()
+        .forEach(
+            clazz -> {
+              clazz.forEachProgramMethod(
+                  method -> {
+                    DexMethod originalMethod =
+                        appView.graphLense().getOriginalMethodSignature(method.getReference());
+                    if (originalMethod != method.getReference()) {
+                      DexMethod originalMethod2 =
+                          appView.graphLense().getOriginalMethodSignature(method.getReference());
+                      appView.graphLense().getOriginalMethodSignature(method.getReference());
+                      DexEncodedMethod definition = method.getDefinition();
+                      Code code = definition.getCode();
+                      if (code == null) {
+                        return;
+                      }
+                      if (code.isCfCode()) {
+                        assert verifyOriginalMethodInPosition(code.asCfCode(), originalMethod);
+                      } else {
+                        assert code.isDexCode();
+                        assert verifyOriginalMethodInDebugInfo(code.asDexCode(), originalMethod);
+                      }
+                    }
+                  });
+            });
+    return true;
+  }
+
+  private static boolean verifyOriginalMethodInPosition(CfCode code, DexMethod originalMethod) {
+    for (CfInstruction instruction : code.instructions) {
+      if (!instruction.isPosition()) {
+        continue;
+      }
+      CfPosition position = instruction.asPosition();
+      assert position.getPosition().getOutermostCaller().method == originalMethod;
+    }
+    return true;
+  }
+
+  private static boolean verifyOriginalMethodInDebugInfo(DexCode code, DexMethod originalMethod) {
+    if (code.getDebugInfo() == null) {
+      return true;
+    }
+    for (DexDebugEvent event : code.getDebugInfo().events) {
+      assert !event.isSetInlineFrame() || event.asSetInlineFrame().hasOuterPosition(originalMethod);
+    }
+    return true;
+  }
+
   private AppView<AppInfoWithLiveness> runEnqueuer(
       AnnotationRemover.Builder annotationRemoverBuilder,
       ExecutorService executorService,
@@ -917,6 +979,7 @@
                 options.getProguardConfiguration().getDontWarnPatterns(),
                 executorService,
                 timing));
+    appView.setGraphLense(enqueuer.buildGraphLense(appView));
     if (InternalOptions.assertionsEnabled()) {
       // Register the dead proto types. These are needed to verify that no new missing types are
       // reported and that no dead proto types are referenced in the generated application.
diff --git a/src/main/java/com/android/tools/r8/graph/DexDebugEvent.java b/src/main/java/com/android/tools/r8/graph/DexDebugEvent.java
index f790131..059f7f6 100644
--- a/src/main/java/com/android/tools/r8/graph/DexDebugEvent.java
+++ b/src/main/java/com/android/tools/r8/graph/DexDebugEvent.java
@@ -38,6 +38,14 @@
 
   public abstract void accept(DexDebugEventVisitor visitor);
 
+  public boolean isSetInlineFrame() {
+    return false;
+  }
+
+  public SetInlineFrame asSetInlineFrame() {
+    return null;
+  }
+
   public static class AdvancePC extends DexDebugEvent {
 
     public final int delta;
@@ -421,6 +429,21 @@
       SetInlineFrame o = (SetInlineFrame) other;
       return callee == o.callee && Objects.equals(caller, o.caller);
     }
+
+    @Override
+    public boolean isSetInlineFrame() {
+      return true;
+    }
+
+    @Override
+    public SetInlineFrame asSetInlineFrame() {
+      return this;
+    }
+
+    public boolean hasOuterPosition(DexMethod method) {
+      return (caller == null && callee == method)
+          || (caller != null && caller.getOutermostCaller().method == method);
+    }
   }
 
   public static class Default extends DexDebugEvent {
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
index 5cbe06a..2269517 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -36,6 +36,7 @@
 import com.android.tools.r8.dex.MixedSectionCollection;
 import com.android.tools.r8.errors.InternalCompilerError;
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.DexDebugEvent.SetInlineFrame;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Invoke;
 import com.android.tools.r8.ir.code.ValueType;
@@ -858,6 +859,36 @@
     }
   }
 
+  public static void setOriginalMethodPosition(Code code, DexMethod originalMethod) {
+    if (code.isDexCode()) {
+      DexCode dexCode = code.asDexCode();
+      DexDebugInfo debugInfo = dexCode.getDebugInfo();
+      if (debugInfo == null) {
+        return;
+      }
+      for (DexDebugEvent event : debugInfo.events) {
+        if (event.isSetInlineFrame() && event.asSetInlineFrame().hasOuterPosition(originalMethod)) {
+          return;
+        }
+      }
+      DexDebugEvent[] newEvents = new DexDebugEvent[debugInfo.events.length + 1];
+      newEvents[0] = new SetInlineFrame(originalMethod, null);
+      System.arraycopy(debugInfo.events, 0, newEvents, 1, debugInfo.events.length);
+      dexCode.setDebugInfo(new DexDebugInfo(debugInfo.startLine, debugInfo.parameters, newEvents));
+    } else {
+      assert code.isCfCode();
+      CfCode cfCode = code.asCfCode();
+      for (CfInstruction instruction : cfCode.instructions) {
+        if (instruction.isPosition()) {
+          assert instruction.asPosition().getPosition().getOutermostCaller().method
+              == originalMethod;
+          return;
+        }
+      }
+      assert false : "The original position should be present in the CF code.";
+    }
+  }
+
   private DexEncodedMethod toMethodThatLogsErrorDexCode(DexItemFactory itemFactory) {
     checkIfObsolete();
     Signature signature = MethodSignature.fromDexMethod(method);
diff --git a/src/main/java/com/android/tools/r8/ir/code/Position.java b/src/main/java/com/android/tools/r8/ir/code/Position.java
index 6ed94b4..d11bf38 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Position.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Position.java
@@ -85,9 +85,8 @@
       assert position.isNone();
       position = Position.noneWithMethod(context.getReference(), null);
     }
-    assert position.callerPosition == null
-        || position.getOutermostCaller().method
-            == appView.graphLense().getOriginalMethodSignature(context.getReference());
+    assert position.getOutermostCaller().method
+        == appView.graphLense().getOriginalMethodSignature(context.getReference());
     return position;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
index 00eb737..9055230 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
@@ -3,6 +3,8 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.conversion;
 
+import static com.android.tools.r8.ir.conversion.CfSourceUtils.ensureLabel;
+
 import com.android.tools.r8.cf.CfRegisterAllocator;
 import com.android.tools.r8.cf.LoadStoreHelper;
 import com.android.tools.r8.cf.TypeVerificationHelper;
@@ -322,7 +324,7 @@
     } while (block != null);
     // TODO(mkroghj) Move computation of stack-height to CF instructions.
     if (!openLocalVariables.isEmpty()) {
-      CfLabel endLabel = ensureLabel();
+      CfLabel endLabel = ensureLabel(instructions);
       for (LocalVariableInfo info : openLocalVariables.values()) {
         info.setEnd(endLabel);
         localVariablesTable.add(info);
@@ -460,7 +462,7 @@
         }
       } else {
         if (instruction.isNewInstance()) {
-          newInstanceLabels.put(instruction.asNewInstance(), ensureLabel());
+          newInstanceLabels.put(instruction.asNewInstance(), ensureLabel(instructions));
         }
         updatePositionAndLocals(instruction);
         instruction.buildCf(this);
@@ -481,7 +483,7 @@
     if (!didLocalsChange && !didPositionChange) {
       return;
     }
-    CfLabel label = ensureLabel();
+    CfLabel label = ensureLabel(instructions);
     if (didLocalsChange) {
       updateLocals(label);
     }
@@ -526,20 +528,6 @@
     return pendingLocalChanges;
   }
 
-  private CfLabel ensureLabel() {
-    CfInstruction last = getLastInstruction();
-    if (last instanceof CfLabel) {
-      return (CfLabel) last;
-    }
-    CfLabel label = new CfLabel();
-    add(label);
-    return label;
-  }
-
-  private CfInstruction getLastInstruction() {
-    return instructions.isEmpty() ? null : instructions.get(instructions.size() - 1);
-  }
-
   private void addFrame(BasicBlock block) {
     List<TypeInfo> stack = registerAllocator.getTypesAtBlockEntry(block).stack;
     List<FrameType> stackTypes;
@@ -570,7 +558,7 @@
     // CfCode.
     boolean didLocalsChange = localsChanged();
     if (didLocalsChange) {
-      CfLabel label = ensureLabel();
+      CfLabel label = ensureLabel(instructions);
       updateLocals(label);
     }
 
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CfSourceUtils.java b/src/main/java/com/android/tools/r8/ir/conversion/CfSourceUtils.java
new file mode 100644
index 0000000..9c3a2f1
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CfSourceUtils.java
@@ -0,0 +1,26 @@
+// 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.ir.conversion;
+
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.cf.code.CfLabel;
+import java.util.List;
+
+public class CfSourceUtils {
+
+  public static CfLabel ensureLabel(List<CfInstruction> instructions) {
+    CfInstruction last = getLastInstruction(instructions);
+    if (last != null && last.isLabel()) {
+      return last.asLabel();
+    }
+    CfLabel label = new CfLabel();
+    instructions.add(label);
+    return label;
+  }
+
+  private static CfInstruction getLastInstruction(List<CfInstruction> instructions) {
+    return instructions.isEmpty() ? null : instructions.get(instructions.size() - 1);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java
index a277a71..2b8ce66 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java
@@ -4,6 +4,8 @@
 
 package com.android.tools.r8.ir.desugar;
 
+import static com.android.tools.r8.graph.DexEncodedMethod.setOriginalMethodPosition;
+
 import com.android.tools.r8.cf.code.CfInstruction;
 import com.android.tools.r8.cf.code.CfInvoke;
 import com.android.tools.r8.code.Instruction;
@@ -94,6 +96,12 @@
         newFlags.promoteToStatic();
         DexEncodedMethod.setDebugInfoWithFakeThisParameter(
             code, companionMethod.getArity(), appView);
+        if (!appView.options().isDesugaredLibraryCompilation()) {
+          setOriginalMethodPosition(
+              code, appView.graphLense().getOriginalMethodSignature(virtual.method));
+        } else {
+          assert appView.graphLense().isIdentityLense();
+        }
         DexEncodedMethod implMethod =
             new DexEncodedMethod(
                 companionMethod,
@@ -128,13 +136,18 @@
       if (originalFlags.isPrivate()) {
         newFlags.promoteToPublic();
       }
-
       DexMethod oldMethod = direct.method;
       if (isStaticMethod(direct)) {
         assert originalFlags.isPrivate() || originalFlags.isPublic()
             : "Static interface method " + direct.toSourceString() + " is expected to "
             + "either be public or private in " + iface.origin;
         DexMethod companionMethod = rewriter.staticAsMethodOfCompanionClass(oldMethod);
+        if (!appView.options().isDesugaredLibraryCompilation()) {
+          setOriginalMethodPosition(
+              direct.getCode(), appView.graphLense().getOriginalMethodSignature(oldMethod));
+        } else {
+          assert appView.graphLense().isIdentityLense();
+        }
         DexEncodedMethod implMethod =
             new DexEncodedMethod(
                 companionMethod,
@@ -162,6 +175,12 @@
           }
           DexEncodedMethod.setDebugInfoWithFakeThisParameter(
               code, companionMethod.getArity(), appView);
+          if (!appView.options().isDesugaredLibraryCompilation()) {
+            setOriginalMethodPosition(
+                code, appView.graphLense().getOriginalMethodSignature(oldMethod));
+          } else {
+            assert appView.graphLense().isIdentityLense();
+          }
           DexEncodedMethod implMethod =
               new DexEncodedMethod(
                   companionMethod,
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 d203a12..e40e0e4 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
@@ -670,8 +670,7 @@
                   encodedMethod -> {
                     assert encodedMethod.isDirectMethod();
                     // We need to create a new method with the same code to be able to safely relax
-                    // its
-                    // accessibility and make it virtual.
+                    // its accessibility and make it virtual.
                     MethodAccessFlags newAccessFlags = encodedMethod.accessFlags.copy();
                     newAccessFlags.unsetPrivate();
                     newAccessFlags.setPublic();
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 8a7b983..fb66a2a 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
@@ -7,10 +7,13 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexApplication.Builder;
 import com.android.tools.r8.graph.DexCallSite;
+import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.GraphLense;
+import com.android.tools.r8.graph.GraphLense.NestedGraphLense;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.analysis.type.Nullability;
 import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
@@ -29,6 +32,7 @@
 import com.android.tools.r8.utils.collections.SortedProgramMethodSet;
 import com.google.common.collect.BiMap;
 import com.google.common.collect.HashBiMap;
+import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Sets;
 import java.util.ArrayList;
@@ -342,4 +346,35 @@
   public Map<DexType, LambdaClass> getKnownLambdaClasses() {
     return knownLambdaClasses;
   }
+
+  public GraphLense buildMappingLense(AppView<?> appView) {
+    if (originalMethodSignatures.isEmpty()) {
+      return appView.graphLense();
+    }
+    return new LambdaRewriterLense(
+        originalMethodSignatures, appView.graphLense(), appView.dexItemFactory());
+  }
+
+  static class LambdaRewriterLense extends NestedGraphLense {
+
+    public LambdaRewriterLense(
+        BiMap<DexMethod, DexMethod> originalMethodSignatures,
+        GraphLense graphLense,
+        DexItemFactory factory) {
+      super(
+          ImmutableMap.of(),
+          ImmutableMap.of(),
+          ImmutableMap.of(),
+          null,
+          originalMethodSignatures,
+          graphLense,
+          factory);
+    }
+
+    @Override
+    protected boolean isLegitimateToHaveEmptyMappings() {
+      return true;
+    }
+  }
 }
+
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerCostAnalysis.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerCostAnalysis.java
index 3feac87..f80e1d7 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerCostAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerCostAnalysis.java
@@ -12,8 +12,11 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
+import com.android.tools.r8.ir.code.AssumeAndCheckCastAliasedValueConfiguration;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InstructionIterator;
 import com.android.tools.r8.ir.code.InvokeMethod;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.optimize.inliner.InliningIRProvider;
@@ -23,6 +26,7 @@
 import java.util.Map;
 import java.util.Set;
 
+// TODO(b/160634549): Rename or refactor this to reflect its non-cost related analysis.
 /** Analysis that estimates the cost of class inlining an object allocation. */
 class ClassInlinerCostAnalysis {
 
@@ -73,6 +77,28 @@
         continue;
       }
       IRCode inliningIR = inliningIRProvider.getAndCacheInliningIR(invoke, inlinee);
+
+      // If the instance is part of a phi, then inlining will invalidate the inliner assumptions.
+      // TODO(b/160634549): This is not a budget miss but a hard requirement.
+      InstructionIterator iterator = inliningIR.entryBlock().iterator();
+      while (iterator.hasNext()) {
+        Instruction next = iterator.next();
+        if (!next.isArgument()) {
+          break;
+        }
+        Value argumentValue = next.outValue();
+        TypeElement argumentType = argumentValue.getType();
+        if (argumentType.isClassType()
+            && argumentType.asClassType().getClassType() == eligibleClass.type) {
+          assert argumentValue.uniqueUsers().stream()
+              .noneMatch(
+                  AssumeAndCheckCastAliasedValueConfiguration.getInstance()::isIntroducingAnAlias);
+          if (argumentValue.hasPhiUsers()) {
+            return true;
+          }
+        }
+      }
+
       int increment =
           inlinee.getDefinition().getCode().estimatedSizeForInlining()
               - estimateNumberOfNonMaterializingInstructions(invoke, inliningIR);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
index e03bc6b..1ffca3d 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
@@ -25,6 +25,7 @@
 import com.android.tools.r8.graph.FieldResolutionResult;
 import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.graph.GraphLense.NestedGraphLense;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.RewrittenPrototypeDescription;
 import com.android.tools.r8.graph.RewrittenPrototypeDescription.ArgumentInfoCollection;
 import com.android.tools.r8.graph.RewrittenPrototypeDescription.RewrittenTypeInfo;
@@ -162,7 +163,7 @@
             analyzeCheckCast(instruction.asCheckCast(), eligibleEnums);
             break;
           case Opcodes.INVOKE_STATIC:
-            analyzeInvokeStatic(instruction.asInvokeStatic(), eligibleEnums);
+            analyzeInvokeStatic(instruction.asInvokeStatic(), eligibleEnums, code.context());
             break;
           case Opcodes.STATIC_GET:
           case Opcodes.INSTANCE_GET:
@@ -210,11 +211,17 @@
     }
   }
 
-  private void analyzeInvokeStatic(InvokeStatic invokeStatic, Set<DexType> eligibleEnums) {
+  private void analyzeInvokeStatic(
+      InvokeStatic invokeStatic, Set<DexType> eligibleEnums, ProgramMethod context) {
     DexMethod invokedMethod = invokeStatic.getInvokedMethod();
     DexProgramClass enumClass = getEnumUnboxingCandidateOrNull(invokedMethod.holder);
     if (enumClass != null) {
-      eligibleEnums.add(enumClass.type);
+      DexEncodedMethod method = invokeStatic.lookupSingleTarget(appView, context);
+      if (method != null) {
+        eligibleEnums.add(enumClass.type);
+      } else {
+        markEnumAsUnboxable(Reason.INVALID_INVOKE, enumClass);
+      }
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumValueOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumValueOptimizer.java
index 2cd8633..789f4d9 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumValueOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumValueOptimizer.java
@@ -75,6 +75,12 @@
         boolean isOrdinalInvoke = invokedMethod == factory.enumMethods.ordinal;
         boolean isNameInvoke = invokedMethod == factory.enumMethods.name;
         boolean isToStringInvoke = invokedMethod == factory.enumMethods.toString;
+
+        // TODO(b/160667929): Re-enable name()/toString() optimizations.
+        if (!isOrdinalInvoke) {
+          continue;
+        }
+
         if (!isOrdinalInvoke && !isNameInvoke && !isToStringInvoke) {
           continue;
         }
diff --git a/src/main/java/com/android/tools/r8/naming/ClassNameMapper.java b/src/main/java/com/android/tools/r8/naming/ClassNameMapper.java
index e98d9b9..43a0511 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNameMapper.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNameMapper.java
@@ -6,6 +6,7 @@
 import static com.android.tools.r8.naming.ClassNameMapper.MissingFileAction.MISSING_FILE_IS_ERROR;
 import static com.android.tools.r8.utils.DescriptorUtils.descriptorToJavaType;
 
+import com.android.tools.r8.DiagnosticsHandler;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
@@ -16,6 +17,7 @@
 import com.android.tools.r8.position.Position;
 import com.android.tools.r8.utils.BiMapContainer;
 import com.android.tools.r8.utils.ChainableStringConsumer;
+import com.android.tools.r8.utils.Reporter;
 import com.google.common.collect.BiMap;
 import com.google.common.collect.ImmutableBiMap;
 import com.google.common.collect.ImmutableMap;
@@ -68,7 +70,7 @@
 
   public static ClassNameMapper mapperFromInputStream(InputStream in) throws IOException {
     return mapperFromBufferedReader(
-        new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8)));
+        new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8)), null);
   }
 
   public static ClassNameMapper mapperFromFile(Path path) throws IOException {
@@ -87,12 +89,20 @@
   }
 
   public static ClassNameMapper mapperFromString(String contents) throws IOException {
-    return mapperFromBufferedReader(CharSource.wrap(contents).openBufferedStream());
+    return mapperFromBufferedReader(CharSource.wrap(contents).openBufferedStream(), null);
   }
 
-  private static ClassNameMapper mapperFromBufferedReader(BufferedReader reader)
-      throws IOException {
-    try (ProguardMapReader proguardReader = new ProguardMapReader(reader)) {
+  public static ClassNameMapper mapperFromString(
+      String contents, DiagnosticsHandler diagnosticsHandler) throws IOException {
+    return mapperFromBufferedReader(
+        CharSource.wrap(contents).openBufferedStream(), diagnosticsHandler);
+  }
+
+  private static ClassNameMapper mapperFromBufferedReader(
+      BufferedReader reader, DiagnosticsHandler diagnosticsHandler) throws IOException {
+    try (ProguardMapReader proguardReader =
+        new ProguardMapReader(
+            reader, diagnosticsHandler != null ? diagnosticsHandler : new Reporter())) {
       ClassNameMapper.Builder builder = ClassNameMapper.builder();
       proguardReader.parse(builder);
       return builder.build();
diff --git a/src/main/java/com/android/tools/r8/naming/ClassNaming.java b/src/main/java/com/android/tools/r8/naming/ClassNaming.java
index 1404387..5a282d0 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNaming.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNaming.java
@@ -3,7 +3,9 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.naming;
 
+import com.android.tools.r8.DiagnosticsHandler;
 import com.android.tools.r8.naming.MemberNaming.Signature;
+import com.android.tools.r8.naming.mappinginformation.MappingInformation;
 import com.android.tools.r8.utils.ThrowingConsumer;
 
 /**
@@ -14,8 +16,16 @@
 public interface ClassNaming {
 
   abstract class Builder {
+
     public abstract Builder addMemberEntry(MemberNaming entry);
 
+    public abstract Builder addMappingInformation(MappingInformation mappingInformation);
+
+    public abstract Builder addMappingInformation(
+        MappingInformation mappingInformation,
+        DiagnosticsHandler diagnosticsHandler,
+        int lineNumber);
+
     public abstract ClassNaming build();
 
     /** This is an optional method, may be implemented as no-op */
diff --git a/src/main/java/com/android/tools/r8/naming/ClassNamingForMapApplier.java b/src/main/java/com/android/tools/r8/naming/ClassNamingForMapApplier.java
index 65bc602..acbb577 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNamingForMapApplier.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNamingForMapApplier.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.DiagnosticsHandler;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
@@ -10,6 +11,7 @@
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
 import com.android.tools.r8.naming.MemberNaming.Signature;
 import com.android.tools.r8.naming.MemberNaming.Signature.SignatureKind;
+import com.android.tools.r8.naming.mappinginformation.MappingInformation;
 import com.android.tools.r8.position.Position;
 import com.android.tools.r8.utils.Reporter;
 import com.android.tools.r8.utils.ThrowingConsumer;
@@ -35,6 +37,7 @@
 public class ClassNamingForMapApplier implements ClassNaming {
 
   public static class Builder extends ClassNaming.Builder {
+
     private final String originalName;
     private final String renamedName;
     private final Position position;
@@ -75,6 +78,21 @@
     }
 
     @Override
+    public ClassNaming.Builder addMappingInformation(MappingInformation mappingInformation) {
+      // Intentionally kept empty until we support additional information with -applymapping.
+      return this;
+    }
+
+    @Override
+    public ClassNaming.Builder addMappingInformation(
+        MappingInformation mappingInformation,
+        DiagnosticsHandler diagnosticsHandler,
+        int lineNumber) {
+      // Intentionally kept empty until we support additional information with -applymapping.
+      return this;
+    }
+
+    @Override
     public ClassNamingForMapApplier build() {
       return new ClassNamingForMapApplier(
           renamedName, originalName, position, qualifiedMethodMembers, methodMembers, fieldMembers);
diff --git a/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java b/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java
index 5a00fcb..98293ab 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java
@@ -3,10 +3,15 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.naming;
 
+import static com.android.tools.r8.naming.MemberNaming.NoSignature.NO_SIGNATURE;
+
+import com.android.tools.r8.DiagnosticsHandler;
 import com.android.tools.r8.naming.MemberNaming.FieldSignature;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
 import com.android.tools.r8.naming.MemberNaming.Signature;
 import com.android.tools.r8.naming.MemberNaming.Signature.SignatureKind;
+import com.android.tools.r8.naming.mappinginformation.MappingInformation;
+import com.android.tools.r8.naming.mappinginformation.MappingInformationDiagnostics;
 import com.android.tools.r8.utils.ChainableStringConsumer;
 import com.android.tools.r8.utils.ThrowingConsumer;
 import com.google.common.collect.ImmutableMap;
@@ -19,6 +24,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
+import java.util.function.Consumer;
 
 /**
  * Stores name information for a class.
@@ -28,12 +34,14 @@
 public class ClassNamingForNameMapper implements ClassNaming {
 
   public static class Builder extends ClassNaming.Builder {
+
     private final String originalName;
     private final String renamedName;
     private final Map<MethodSignature, MemberNaming> methodMembers = Maps.newHashMap();
     private final Map<FieldSignature, MemberNaming> fieldMembers = Maps.newHashMap();
     private final Map<String, List<MappedRange>> mappedRangesByName = Maps.newHashMap();
     private final Map<String, List<MemberNaming>> mappedNamingsByName = Maps.newHashMap();
+    private final Map<Signature, List<MappingInformation>> additionalMappings = Maps.newHashMap();
 
     private Builder(String renamedName, String originalName) {
       this.originalName = originalName;
@@ -43,9 +51,9 @@
     @Override
     public ClassNaming.Builder addMemberEntry(MemberNaming entry) {
       if (entry.isMethodNaming()) {
-        methodMembers.put((MethodSignature) entry.getRenamedSignature(), entry);
+        methodMembers.put(entry.getRenamedSignature().asMethodSignature(), entry);
       } else {
-        fieldMembers.put((FieldSignature) entry.getRenamedSignature(), entry);
+        fieldMembers.put(entry.getRenamedSignature().asFieldSignature(), entry);
       }
       mappedNamingsByName
           .computeIfAbsent(entry.getRenamedName(), m -> new ArrayList<>())
@@ -54,6 +62,45 @@
     }
 
     @Override
+    public ClassNaming.Builder addMappingInformation(MappingInformation mappingInformation) {
+      return addMappingInformation(
+          mappingInformation,
+          other -> {
+            assert false;
+          });
+    }
+
+    @Override
+    public ClassNaming.Builder addMappingInformation(
+        MappingInformation mappingInformation,
+        DiagnosticsHandler diagnosticsHandler,
+        int lineNumber) {
+      return addMappingInformation(
+          mappingInformation,
+          other ->
+              diagnosticsHandler.warning(
+                  MappingInformationDiagnostics.notAllowedCombination(
+                      originalName, renamedName, mappingInformation, other, lineNumber)));
+    }
+
+    private ClassNaming.Builder addMappingInformation(
+        MappingInformation mappingInformation, Consumer<MappingInformation> notAllowedCombination) {
+      Signature signature =
+          mappingInformation.isSignatureMappingInformation()
+              ? mappingInformation.asSignatureMappingInformation().getSignature()
+              : NO_SIGNATURE;
+      List<MappingInformation> additionalMappingForSignature =
+          additionalMappings.computeIfAbsent(signature, ignored -> new ArrayList<>());
+      for (MappingInformation information : additionalMappingForSignature) {
+        if (!information.allowOther(mappingInformation)) {
+          notAllowedCombination.accept(information);
+        }
+      }
+      additionalMappingForSignature.add(mappingInformation);
+      return this;
+    }
+
+    @Override
     public ClassNamingForNameMapper build() {
       Map<String, MappedRangesOfName> map;
 
@@ -67,7 +114,13 @@
       }
 
       return new ClassNamingForNameMapper(
-          renamedName, originalName, methodMembers, fieldMembers, map, mappedNamingsByName);
+          renamedName,
+          originalName,
+          methodMembers,
+          fieldMembers,
+          map,
+          mappedNamingsByName,
+          additionalMappings);
     }
 
     /** The parameters are forwarded to MappedRange constructor, see explanation there. */
@@ -198,19 +251,23 @@
 
   public final Map<String, List<MemberNaming>> mappedNamingsByName;
 
+  private final Map<Signature, List<MappingInformation>> additionalMappings;
+
   private ClassNamingForNameMapper(
       String renamedName,
       String originalName,
       Map<MethodSignature, MemberNaming> methodMembers,
       Map<FieldSignature, MemberNaming> fieldMembers,
       Map<String, MappedRangesOfName> mappedRangesByRenamedName,
-      Map<String, List<MemberNaming>> mappedNamingsByName) {
+      Map<String, List<MemberNaming>> mappedNamingsByName,
+      Map<Signature, List<MappingInformation>> additionalMappings) {
     this.renamedName = renamedName;
     this.originalName = originalName;
     this.methodMembers = ImmutableMap.copyOf(methodMembers);
     this.fieldMembers = ImmutableMap.copyOf(fieldMembers);
     this.mappedRangesByRenamedName = mappedRangesByRenamedName;
     this.mappedNamingsByName = mappedNamingsByName;
+    this.additionalMappings = additionalMappings;
   }
 
   public MappedRangesOfName getMappedRangesForRenamedName(String renamedName) {
@@ -298,7 +355,16 @@
   void write(ChainableStringConsumer consumer) {
     consumer.accept(originalName).accept(" -> ").accept(renamedName).accept(":\n");
 
-    // First print field member namings.
+    // Print all additional mapping information.
+    additionalMappings.forEach(
+        (signature, mappingInformations) -> {
+          assert !mappingInformations.isEmpty();
+          for (MappingInformation mappingInformation : mappingInformations) {
+            consumer.accept("# " + mappingInformation.serialize()).accept("\n");
+          }
+        });
+
+    // Print field member namings.
     forAllFieldNaming(m -> consumer.accept("    ").accept(m.toString()).accept("\n"));
 
     // Sort MappedRanges by sequence number to restore construction order (original Proguard-map
@@ -313,6 +379,10 @@
     }
   }
 
+  public Map<Signature, List<MappingInformation>> getAdditionalMappings() {
+    return additionalMappings;
+  }
+
   @Override
   public String toString() {
     StringBuilder builder = new StringBuilder();
diff --git a/src/main/java/com/android/tools/r8/naming/MemberNaming.java b/src/main/java/com/android/tools/r8/naming/MemberNaming.java
index 84f726d..51c3272 100644
--- a/src/main/java/com/android/tools/r8/naming/MemberNaming.java
+++ b/src/main/java/com/android/tools/r8/naming/MemberNaming.java
@@ -58,12 +58,12 @@
   final Position position;
 
   public MemberNaming(Signature signature, String renamedName) {
-    this(signature, renamedName, Position.UNKNOWN);
+    this(signature, signature.asRenamed(renamedName), Position.UNKNOWN);
   }
 
-  public MemberNaming(Signature signature, String renamedName, Position position) {
+  public MemberNaming(Signature signature, Signature renamedSignature, Position position) {
     this.signature = signature;
-    this.renamedSignature = signature.asRenamed(renamedName);
+    this.renamedSignature = renamedSignature;
     this.position = position;
   }
 
@@ -168,6 +168,40 @@
     }
   }
 
+  public static class NoSignature extends Signature {
+
+    public static final NoSignature NO_SIGNATURE = new NoSignature();
+
+    public NoSignature() {
+      super("NO SIGNATURE");
+    }
+
+    @Override
+    Signature asRenamed(String renamedName) {
+      throw new Unreachable("Should not be called on NoSignature");
+    }
+
+    @Override
+    public SignatureKind kind() {
+      throw new Unreachable("Should not be called on NoSignature");
+    }
+
+    @Override
+    public boolean equals(Object o) {
+      return o == this;
+    }
+
+    @Override
+    public int hashCode() {
+      return 7;
+    }
+
+    @Override
+    void write(Writer builder) throws IOException {
+      throw new Unreachable("Should not be called on NoSignature");
+    }
+  }
+
   public static class FieldSignature extends Signature {
 
     public final String type;
diff --git a/src/main/java/com/android/tools/r8/naming/ProguardMapReader.java b/src/main/java/com/android/tools/r8/naming/ProguardMapReader.java
index 747423f..23a9a06 100644
--- a/src/main/java/com/android/tools/r8/naming/ProguardMapReader.java
+++ b/src/main/java/com/android/tools/r8/naming/ProguardMapReader.java
@@ -3,17 +3,24 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.naming;
 
+import com.android.tools.r8.DiagnosticsHandler;
 import com.android.tools.r8.naming.MemberNaming.FieldSignature;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
 import com.android.tools.r8.naming.MemberNaming.Signature;
+import com.android.tools.r8.naming.mappinginformation.MappingInformation;
+import com.android.tools.r8.naming.mappinginformation.SignatureMappingInformation;
 import com.android.tools.r8.position.TextPosition;
 import com.android.tools.r8.utils.IdentifierUtils;
 import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.Maps;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
 import java.io.BufferedReader;
 import java.io.IOException;
 import java.util.HashMap;
 import java.util.LinkedList;
 import java.util.List;
+import java.util.Map;
 import java.util.Objects;
 
 /**
@@ -54,16 +61,19 @@
 public class ProguardMapReader implements AutoCloseable {
 
   private final BufferedReader reader;
+  private final JsonParser jsonParser = new JsonParser();
+  private final DiagnosticsHandler diagnosticsHandler;
 
   @Override
   public void close() throws IOException {
-    if (reader != null) {
-      reader.close();
-    }
+    reader.close();
   }
 
-  ProguardMapReader(BufferedReader reader) {
+  ProguardMapReader(BufferedReader reader, DiagnosticsHandler diagnosticsHandler) {
     this.reader = reader;
+    this.diagnosticsHandler = diagnosticsHandler;
+    assert reader != null;
+    assert diagnosticsHandler != null;
   }
 
   // Internal parser state
@@ -118,7 +128,7 @@
     for (int i = 0; i < line.length(); ++i) {
       char c = line.charAt(i);
       if (c == '#') {
-        return true;
+        return !hasFirstCharJsonBrace(line, i);
       } else if (!StringUtils.isWhitespace(c)) {
         return false;
       }
@@ -126,6 +136,33 @@
     return true;
   }
 
+  private boolean isCommentLineWithJsonBrace() {
+    if (line == null) {
+      return false;
+    }
+    for (int i = 0; i < line.length(); ++i) {
+      char c = line.charAt(i);
+      if (c == '#') {
+        return hasFirstCharJsonBrace(line, i);
+      } else if (!Character.isWhitespace(c)) {
+        return false;
+      }
+    }
+    return false;
+  }
+
+  private static boolean hasFirstCharJsonBrace(String line, int commentCharIndex) {
+    for (int i = commentCharIndex + 1; i < line.length(); i++) {
+      char c = line.charAt(i);
+      if (c == '{') {
+        return true;
+      } else if (!Character.isWhitespace(c)) {
+        return false;
+      }
+    }
+    return false;
+  }
+
   private boolean skipLine() throws IOException {
     lineOffset = 0;
     do {
@@ -213,10 +250,26 @@
     MemberNaming lastAddedNaming = null;
     MemberNaming activeMemberNaming = null;
     Range previousMappedRange = null;
+    Map<Signature, SignatureMappingInformation> mappingInformation = Maps.newHashMap();
     do {
       Object originalRange = null;
       Range mappedRange = null;
-
+      // Try to parse any information added in comments above member namings
+      if (isCommentLineWithJsonBrace()) {
+        MappingInformation mappingInfo =
+            MappingInformation.fromJsonObject(parseJsonInComment(), diagnosticsHandler, lineNo);
+        if (mappingInfo != null) {
+          if (mappingInfo.isSignatureMappingInformation()) {
+            SignatureMappingInformation sigMapInfo = mappingInfo.asSignatureMappingInformation();
+            mappingInformation.put(sigMapInfo.getSignature(), sigMapInfo);
+          } else {
+            classNamingBuilder.addMappingInformation(mappingInfo, diagnosticsHandler, lineNo);
+          }
+        }
+        // Skip reading the rest of the line.
+        lineOffset = line.length();
+        continue;
+      }
       // Parse the member line '  x:y:name:z:q -> renamedName'.
       if (!StringUtils.isWhitespace(peekCodePoint())) {
         break;
@@ -274,7 +327,16 @@
           }
         }
       }
-      activeMemberNaming = new MemberNaming(signature, renamedName, getPosition());
+      if (mappingInformation.containsKey(signature)) {
+        activeMemberNaming =
+            new MemberNaming(
+                signature,
+                mappingInformation.get(signature).apply(signature, renamedName, diagnosticsHandler),
+                getPosition());
+      } else {
+        activeMemberNaming =
+            new MemberNaming(signature, signature.asRenamed(renamedName), getPosition());
+      }
       previousMappedRange = mappedRange;
     } while (nextLine());
 
@@ -451,6 +513,20 @@
     return result;
   }
 
+  private JsonObject parseJsonInComment() {
+    assert isCommentLineWithJsonBrace();
+    try {
+      int firstIndex = 0;
+      while (line.charAt(firstIndex) != '{') {
+        firstIndex++;
+      }
+      return jsonParser.parse(line.substring(firstIndex)).getAsJsonObject();
+    } catch (com.google.gson.JsonSyntaxException ex) {
+      // An info message is reported in MappingInformation.
+      return null;
+    }
+  }
+
   private class ParseException extends RuntimeException {
 
     private final int lineNo;
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 be69416..5c50c06 100644
--- a/src/main/java/com/android/tools/r8/naming/SeedMapper.java
+++ b/src/main/java/com/android/tools/r8/naming/SeedMapper.java
@@ -74,7 +74,7 @@
   private static SeedMapper seedMapperFromInputStream(Reporter reporter, InputStream in)
       throws IOException {
     BufferedReader reader = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8));
-    try (ProguardMapReader proguardReader = new ProguardMapReader(reader)) {
+    try (ProguardMapReader proguardReader = new ProguardMapReader(reader, reporter)) {
       SeedMapper.Builder builder = SeedMapper.builder(reporter);
       proguardReader.parse(builder);
       return builder.build();
diff --git a/src/main/java/com/android/tools/r8/naming/SourceFileRewriter.java b/src/main/java/com/android/tools/r8/naming/SourceFileRewriter.java
index 1e57331..e2bf357 100644
--- a/src/main/java/com/android/tools/r8/naming/SourceFileRewriter.java
+++ b/src/main/java/com/android/tools/r8/naming/SourceFileRewriter.java
@@ -5,12 +5,12 @@
 
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.Code;
+import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexDebugEvent;
 import com.android.tools.r8.graph.DexDebugEvent.SetFile;
 import com.android.tools.r8.graph.DexDebugInfo;
 import com.android.tools.r8.graph.DexString;
-import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.ProguardConfiguration;
 import java.util.Arrays;
 
@@ -21,10 +21,12 @@
  */
 public class SourceFileRewriter {
 
-  private final AppView<AppInfoWithLiveness> appView;
+  private final AppView<?> appView;
+  private final DexApplication application;
 
-  public SourceFileRewriter(AppView<AppInfoWithLiveness> appView) {
+  public SourceFileRewriter(AppView<?> appView, DexApplication application) {
     this.appView = appView;
+    this.application = application;
   }
 
   public void run() {
@@ -37,16 +39,18 @@
         && appView.options().forceProguardCompatibility) {
       return;
     }
+    boolean isMinifying = appView.options().isMinifying();
+    assert !isMinifying || appView.appInfo().hasLiveness();
     // Now, the user wants either to remove source file attribute or to rename it for non-kept
     // classes.
     DexString defaultRenaming = getSourceFileRenaming(proguardConfiguration);
-    for (DexClass clazz : appView.appInfo().classes()) {
+    for (DexClass clazz : application.classes()) {
       // We only parse sourceFile if -keepattributes SourceFile, but for compat we should add
       // a source file name, otherwise line positions will not be printed on the JVM or old version
       // of ART.
       if (!hasRenameSourceFileAttribute
           && proguardConfiguration.getKeepAttributes().sourceFile
-          && !appView.appInfo().isMinificationAllowed(clazz.type)) {
+          && !(isMinifying && appView.withLiveness().appInfo().isMinificationAllowed(clazz.type))) {
         continue;
       }
       clazz.sourceFile = defaultRenaming;
diff --git a/src/main/java/com/android/tools/r8/naming/mappinginformation/FileNameInformation.java b/src/main/java/com/android/tools/r8/naming/mappinginformation/FileNameInformation.java
new file mode 100644
index 0000000..87b3e7b
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/naming/mappinginformation/FileNameInformation.java
@@ -0,0 +1,70 @@
+// 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.naming.mappinginformation;
+
+import com.android.tools.r8.DiagnosticsHandler;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonPrimitive;
+
+public class FileNameInformation extends MappingInformation {
+
+  private final String fileName;
+
+  public static final String ID = "sourceFile";
+  static final String FILE_NAME_KEY = "fileName";
+
+  private FileNameInformation(String fileName) {
+    super(NO_LINE_NUMBER);
+    this.fileName = fileName;
+  }
+
+  public String getFileName() {
+    return fileName;
+  }
+
+  @Override
+  public String serialize() {
+    JsonObject result = new JsonObject();
+    result.add(MAPPING_ID_KEY, new JsonPrimitive(ID));
+    result.add(FILE_NAME_KEY, new JsonPrimitive(fileName));
+    return result.toString();
+  }
+
+  @Override
+  public boolean isFileNameInformation() {
+    return true;
+  }
+
+  @Override
+  public FileNameInformation asFileNameInformation() {
+    return this;
+  }
+
+  @Override
+  public boolean allowOther(MappingInformation information) {
+    return !information.isFileNameInformation();
+  }
+
+  public static FileNameInformation build(String fileName) {
+    return new FileNameInformation(fileName);
+  }
+
+  public static FileNameInformation build(
+      JsonObject object, DiagnosticsHandler diagnosticsHandler, int lineNumber) {
+    try {
+      JsonElement fileName =
+          getJsonElementFromObject(object, diagnosticsHandler, lineNumber, FILE_NAME_KEY, ID);
+      if (fileName == null) {
+        return null;
+      }
+      return new FileNameInformation(fileName.getAsString());
+    } catch (UnsupportedOperationException | IllegalStateException ignored) {
+      diagnosticsHandler.info(
+          MappingInformationDiagnostics.invalidValueForObjectWithId(lineNumber, FILE_NAME_KEY, ID));
+      return null;
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/naming/mappinginformation/MappingInformation.java b/src/main/java/com/android/tools/r8/naming/mappinginformation/MappingInformation.java
new file mode 100644
index 0000000..c1c5105
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/naming/mappinginformation/MappingInformation.java
@@ -0,0 +1,97 @@
+// Copyright (c) 2019, 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.mappinginformation;
+
+import com.android.tools.r8.DiagnosticsHandler;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+
+public abstract class MappingInformation {
+
+  static final int NO_LINE_NUMBER = -1;
+
+  public static final String MAPPING_ID_KEY = "id";
+
+  private final int lineNumber;
+
+  MappingInformation(int lineNumber) {
+    this.lineNumber = lineNumber;
+  }
+
+  public int getLineNumber() {
+    return lineNumber;
+  }
+
+  public abstract String serialize();
+
+  public boolean isSignatureMappingInformation() {
+    return false;
+  }
+
+  public SignatureMappingInformation asSignatureMappingInformation() {
+    return null;
+  }
+
+  public boolean isFileNameInformation() {
+    return false;
+  }
+
+  public FileNameInformation asFileNameInformation() {
+    return null;
+  }
+
+  public boolean isMethodSignatureChangedInformation() {
+    return false;
+  }
+
+  public MethodSignatureChangedInformation asMethodSignatureChangedInformation() {
+    return null;
+  }
+
+  public abstract boolean allowOther(MappingInformation information);
+
+  public static MappingInformation fromJsonObject(
+      JsonObject object, DiagnosticsHandler diagnosticsHandler, int lineNumber) {
+    if (object == null) {
+      diagnosticsHandler.info(MappingInformationDiagnostics.notValidJson(lineNumber));
+      return null;
+    }
+    JsonElement id = object.get(MAPPING_ID_KEY);
+    if (id == null) {
+      diagnosticsHandler.info(
+          MappingInformationDiagnostics.noKeyInJson(lineNumber, MAPPING_ID_KEY));
+      return null;
+    }
+    String idString = id.getAsString();
+    if (idString == null) {
+      diagnosticsHandler.info(
+          MappingInformationDiagnostics.notValidString(lineNumber, MAPPING_ID_KEY));
+      return null;
+    }
+    switch (idString) {
+      case MethodSignatureChangedInformation.ID:
+        return MethodSignatureChangedInformation.build(object, diagnosticsHandler, lineNumber);
+      case FileNameInformation.ID:
+        return FileNameInformation.build(object, diagnosticsHandler, lineNumber);
+      default:
+        diagnosticsHandler.info(MappingInformationDiagnostics.noHandlerFor(lineNumber, idString));
+        return null;
+    }
+  }
+
+  static JsonElement getJsonElementFromObject(
+      JsonObject object,
+      DiagnosticsHandler diagnosticsHandler,
+      int lineNumber,
+      String key,
+      String id) {
+    JsonElement element = object.get(key);
+    if (element == null) {
+      diagnosticsHandler.info(
+          MappingInformationDiagnostics.noKeyForObjectWithId(lineNumber, key, MAPPING_ID_KEY, id));
+    }
+    return element;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/naming/mappinginformation/MappingInformationDiagnostics.java b/src/main/java/com/android/tools/r8/naming/mappinginformation/MappingInformationDiagnostics.java
new file mode 100644
index 0000000..6cc0b28
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/naming/mappinginformation/MappingInformationDiagnostics.java
@@ -0,0 +1,113 @@
+// Copyright (c) 2019, 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.mappinginformation;
+
+import com.android.tools.r8.Diagnostic;
+import com.android.tools.r8.Keep;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.Position;
+import com.android.tools.r8.position.TextPosition;
+
+@Keep
+public class MappingInformationDiagnostics implements Diagnostic {
+
+  private final String message;
+  private final Position position;
+
+  @Override
+  public Origin getOrigin() {
+    return Origin.unknown();
+  }
+
+  @Override
+  public Position getPosition() {
+    return position;
+  }
+
+  @Override
+  public String getDiagnosticMessage() {
+    return message;
+  }
+
+  private MappingInformationDiagnostics(String message, Position position) {
+    this.message = message;
+    this.position = position;
+  }
+
+  static MappingInformationDiagnostics noHandlerFor(int lineNumber, String value) {
+    return new MappingInformationDiagnostics(
+        String.format("Could not find a handler for %s", value),
+        new TextPosition(1, lineNumber, TextPosition.UNKNOWN_COLUMN));
+  }
+
+  static MappingInformationDiagnostics noKeyInJson(int lineNumber, String key) {
+    return new MappingInformationDiagnostics(
+        String.format("Could not locate '%s' in the JSON object", key),
+        new TextPosition(1, lineNumber, TextPosition.UNKNOWN_COLUMN));
+  }
+
+  static MappingInformationDiagnostics notValidJson(int lineNumber) {
+    return new MappingInformationDiagnostics(
+        "Not valid JSON", new TextPosition(1, lineNumber, TextPosition.UNKNOWN_COLUMN));
+  }
+
+  static MappingInformationDiagnostics notValidString(int lineNumber, String key) {
+    return new MappingInformationDiagnostics(
+        String.format("The value of '%s' is not a valid string in the JSON object", key),
+        new TextPosition(1, lineNumber, TextPosition.UNKNOWN_COLUMN));
+  }
+
+  static MappingInformationDiagnostics tooManyInformationalParameters(int lineNumber) {
+    return new MappingInformationDiagnostics(
+        "More informational parameters than actual parameters for method signature",
+        new TextPosition(1, lineNumber, TextPosition.UNKNOWN_COLUMN));
+  }
+
+  static MappingInformationDiagnostics noKeyForObjectWithId(
+      int lineNumber, String key, String mappingKey, String mappingValue) {
+    return new MappingInformationDiagnostics(
+        String.format("Could not find '%s' for object with %s '%s'", key, mappingKey, mappingValue),
+        new TextPosition(1, lineNumber, TextPosition.UNKNOWN_COLUMN));
+  }
+
+  static MappingInformationDiagnostics invalidValueForObjectWithId(
+      int lineNumber, String mappingKey, String mappingValue) {
+    return new MappingInformationDiagnostics(
+        String.format(
+            "Could not decode the information for the object with %s '%s'",
+            mappingKey, mappingValue),
+        new TextPosition(1, lineNumber, TextPosition.UNKNOWN_COLUMN));
+  }
+
+  static MappingInformationDiagnostics tooManyEntriesForParameterInformation(int lineNumber) {
+    return new MappingInformationDiagnostics(
+        "Parameter information do not have 1 or 2 entries",
+        new TextPosition(1, lineNumber, TextPosition.UNKNOWN_COLUMN));
+  }
+
+  static MappingInformationDiagnostics invalidParameterInformationObject(int lineNumber) {
+    return new MappingInformationDiagnostics(
+        "Parameter information is not an index and a string representation of a type",
+        new TextPosition(1, lineNumber, TextPosition.UNKNOWN_COLUMN));
+  }
+
+  public static MappingInformationDiagnostics notAllowedCombination(
+      String className,
+      String renamedClassName,
+      MappingInformation one,
+      MappingInformation other,
+      int lineNumber) {
+    return new MappingInformationDiagnostics(
+        "The mapping '"
+            + one.serialize()
+            + "' is not allowed in combination with '"
+            + other.serialize()
+            + "' in the mapping for "
+            + className
+            + " -> "
+            + renamedClassName,
+        new TextPosition(1, lineNumber, TextPosition.UNKNOWN_COLUMN));
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/naming/mappinginformation/MethodSignatureChangedInformation.java b/src/main/java/com/android/tools/r8/naming/mappinginformation/MethodSignatureChangedInformation.java
new file mode 100644
index 0000000..8bb023d
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/naming/mappinginformation/MethodSignatureChangedInformation.java
@@ -0,0 +1,258 @@
+// Copyright (c) 2019, 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.mappinginformation;
+
+import static com.android.tools.r8.naming.mappinginformation.MappingInformationDiagnostics.invalidParameterInformationObject;
+import static com.android.tools.r8.naming.mappinginformation.MappingInformationDiagnostics.invalidValueForObjectWithId;
+import static com.android.tools.r8.naming.mappinginformation.MappingInformationDiagnostics.tooManyEntriesForParameterInformation;
+import static com.android.tools.r8.naming.mappinginformation.MappingInformationDiagnostics.tooManyInformationalParameters;
+
+import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.naming.MemberNaming.MethodSignature;
+import com.android.tools.r8.naming.MemberNaming.Signature;
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonPrimitive;
+
+/**
+ * The MethodSignatureChangedInformation structure adds extra information regarding the mapped
+ * method signature that is otherwise not available in the existing proguard mapping format. The
+ * JSON-structure is as follows:
+ *
+ * <pre>
+ *   {
+ *     "id": "argumentsChanged",
+ *     "signature": { methodSignature },
+ *     "returnType": "java.lang.String",
+ *     "receiver": false,
+ *     "params": [
+ *       [1], // <-- parameter with original index 1 (starting index based on receiver) is removed.
+ *       [2, Foo] // <-- parameter with index 2 has type Foo
+ *     ]
+ *   }
+ * </pre>
+ */
+public class MethodSignatureChangedInformation extends SignatureMappingInformation {
+
+  private ParameterInformation[] argumentInfos;
+  private final boolean receiver;
+  private final String returnType;
+  private final MethodSignature signature;
+
+  public static final String ID = "methodSignatureChanged";
+  private static final String RETURN_TYPE_KEY = "returnType";
+  private static final String PARAMS_KEY = "params";
+  private static final String RECEIVER_KEY = "receiver";
+
+  @Override
+  public String serialize() {
+    JsonObject result = new JsonObject();
+    serializeMethodSignature(result, signature);
+    result.add(MAPPING_ID_KEY, new JsonPrimitive(ID));
+    result.add(RECEIVER_KEY, new JsonPrimitive(receiver));
+    result.add(RETURN_TYPE_KEY, new JsonPrimitive(returnType));
+    JsonArray arguments = new JsonArray();
+    for (ParameterInformation argInfo : argumentInfos) {
+      arguments.add(argInfo.serialize());
+    }
+    result.add(PARAMS_KEY, arguments);
+    return result.toString();
+  }
+
+  @Override
+  public boolean allowOther(MappingInformation information) {
+    return !information.isMethodSignatureChangedInformation();
+  }
+
+  @Override
+  public Signature getSignature() {
+    return signature;
+  }
+
+  @Override
+  public Signature apply(
+      Signature originalSignature, String renamedName, DiagnosticsHandler diagnosticsHandler) {
+    if (originalSignature == null || !originalSignature.isMethodSignature()) {
+      assert false : "Should only call apply for method signature";
+      return originalSignature;
+    }
+    MethodSignature signature = originalSignature.asMethodSignature();
+    String type = signature.type;
+    String[] parameters = signature.parameters;
+    int numberOfArgumentsRemoved = getNumberOfArgumentsRemoved();
+    if (numberOfArgumentsRemoved > parameters.length) {
+      // The mapping information is not up to date with the current signature.
+      diagnosticsHandler.warning(tooManyInformationalParameters(getLineNumber()));
+      return new MethodSignature(renamedName, type, parameters);
+    }
+    String[] newParameters = new String[parameters.length - numberOfArgumentsRemoved];
+    int insertIndex = 0;
+    for (int i = 0; i < parameters.length; i++) {
+      ParameterInformation argInfo = getParameterInformation(i);
+      if (argInfo != null && argInfo.getType() == null) {
+        // Argument has been removed.
+      } else {
+        if (insertIndex >= newParameters.length) {
+          // The mapping information is not up to date with the current signature.
+          diagnosticsHandler.warning(tooManyInformationalParameters(getLineNumber()));
+          return new MethodSignature(renamedName, type, parameters);
+        } else if (argInfo == null) {
+          // Unchanged, take current parameter.
+          newParameters[insertIndex++] = parameters[i];
+        } else {
+          newParameters[insertIndex++] = argInfo.getType();
+        }
+      }
+    }
+    assert insertIndex == newParameters.length;
+    return new MethodSignature(renamedName, getReturnType(), newParameters);
+  }
+
+  @Override
+  public boolean isMethodSignatureChangedInformation() {
+    return true;
+  }
+
+  public int getNumberOfArgumentsRemoved() {
+    int removedCount = 0;
+    for (ParameterInformation argInfo : argumentInfos) {
+      if (argInfo.type == null) {
+        removedCount++;
+      }
+    }
+    return removedCount;
+  }
+
+  public boolean hasReceiver() {
+    return receiver;
+  }
+
+  public String getReturnType() {
+    return returnType;
+  }
+
+  public ParameterInformation getParameterInformation(int index) {
+    int subtractIndex = receiver ? 1 : 0;
+    for (int i = 0; i < argumentInfos.length; i++) {
+      if (argumentInfos[i].index - subtractIndex == index) {
+        return argumentInfos[i];
+      }
+    }
+    return null;
+  }
+
+  @Override
+  public MethodSignatureChangedInformation asMethodSignatureChangedInformation() {
+    return this;
+  }
+
+  private MethodSignatureChangedInformation(
+      MethodSignature signature,
+      String returnType,
+      boolean hasReceiver,
+      ParameterInformation[] argumentInfos,
+      int lineNumber) {
+    super(lineNumber);
+    this.signature = signature;
+    this.argumentInfos = argumentInfos;
+    this.returnType = returnType;
+    this.receiver = hasReceiver;
+  }
+
+  public static MappingInformation build(
+      JsonObject object, DiagnosticsHandler diagnosticsHandler, int lineNumber) {
+    try {
+      JsonElement returnTypeElement =
+          getJsonElementFromObject(object, diagnosticsHandler, lineNumber, RETURN_TYPE_KEY, ID);
+      JsonElement receiverElement =
+          getJsonElementFromObject(object, diagnosticsHandler, lineNumber, RECEIVER_KEY, ID);
+      JsonElement argsElement =
+          getJsonElementFromObject(object, diagnosticsHandler, lineNumber, PARAMS_KEY, ID);
+      MethodSignature signature = getMethodSignature(object, ID, diagnosticsHandler, lineNumber);
+      if (signature == null
+          || returnTypeElement == null
+          || receiverElement == null
+          || argsElement == null) {
+        return null;
+      }
+      JsonArray argumentsArray = argsElement.getAsJsonArray();
+      if (argumentsArray == null) {
+        return null;
+      }
+      ParameterInformation[] args = new ParameterInformation[argumentsArray.size()];
+      for (int i = 0; i < argumentsArray.size(); i++) {
+        args[i] =
+            ParameterInformation.fromJsonArray(
+                argumentsArray.get(i).getAsJsonArray(), diagnosticsHandler, lineNumber);
+      }
+      return new MethodSignatureChangedInformation(
+          signature,
+          returnTypeElement.getAsString(),
+          receiverElement.getAsBoolean(),
+          args,
+          lineNumber);
+    } catch (UnsupportedOperationException | IllegalStateException ignored) {
+      diagnosticsHandler.info(invalidValueForObjectWithId(lineNumber, MAPPING_ID_KEY, ID));
+      return null;
+    }
+  }
+
+  public static class ParameterInformation {
+    private final int index;
+    private final String type;
+
+    public int getIndex() {
+      return index;
+    }
+
+    public String getType() {
+      return type;
+    }
+
+    private ParameterInformation(int index, String type) {
+      this.index = index;
+      this.type = type;
+    }
+
+    static ParameterInformation fromJsonArray(
+        JsonArray argumentInfo, DiagnosticsHandler diagnosticsHandler, int lineNumber) {
+      assert argumentInfo != null;
+      try {
+        if (argumentInfo.size() > 2) {
+          diagnosticsHandler.info(tooManyEntriesForParameterInformation(lineNumber));
+          return null;
+        }
+        int index = argumentInfo.get(0).getAsInt();
+        if (argumentInfo.size() == 1) {
+          // This is a removed argument - no type information
+          return new ParameterInformation(index, null);
+        } else {
+          return new ParameterInformation(index, argumentInfo.get(1).getAsString());
+        }
+      } catch (UnsupportedOperationException | IllegalStateException ignored) {
+        diagnosticsHandler.info(invalidParameterInformationObject(lineNumber));
+        return null;
+      }
+    }
+
+    public static ParameterInformation buildRemovedParameterInformation(int index) {
+      return new ParameterInformation(index, null);
+    }
+
+    public static ParameterInformation buildChangedParameterInformation(int index, String type) {
+      return new ParameterInformation(index, type);
+    }
+
+    JsonArray serialize() {
+      JsonArray serializedArray = new JsonArray();
+      serializedArray.add(index);
+      if (type != null) {
+        serializedArray.add(type);
+      }
+      return serializedArray;
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/naming/mappinginformation/SignatureMappingInformation.java b/src/main/java/com/android/tools/r8/naming/mappinginformation/SignatureMappingInformation.java
new file mode 100644
index 0000000..aba33bb
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/naming/mappinginformation/SignatureMappingInformation.java
@@ -0,0 +1,64 @@
+// 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.naming.mappinginformation;
+
+import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.naming.MemberNaming.MethodSignature;
+import com.android.tools.r8.naming.MemberNaming.Signature;
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+
+public abstract class SignatureMappingInformation extends MappingInformation {
+
+  private static final String SIGNATURE_KEY = "signature";
+
+  SignatureMappingInformation(int lineNumber) {
+    super(lineNumber);
+  }
+
+  @Override
+  public boolean isSignatureMappingInformation() {
+    return true;
+  }
+
+  @Override
+  public SignatureMappingInformation asSignatureMappingInformation() {
+    return this;
+  }
+
+  public abstract Signature getSignature();
+
+  public abstract Signature apply(
+      Signature originalSignature, String renamedName, DiagnosticsHandler diagnosticsHandler);
+
+  JsonObject serializeMethodSignature(JsonObject object, MethodSignature signature) {
+    JsonArray signatureArr = new JsonArray();
+    signatureArr.add(signature.type);
+    signatureArr.add(signature.name);
+    for (String parameter : signature.parameters) {
+      signatureArr.add(parameter);
+    }
+    object.add(SIGNATURE_KEY, signatureArr);
+    return object;
+  }
+
+  static MethodSignature getMethodSignature(
+      JsonObject object, String id, DiagnosticsHandler diagnosticsHandler, int lineNumber) {
+    JsonElement signatureElement =
+        getJsonElementFromObject(object, diagnosticsHandler, lineNumber, SIGNATURE_KEY, id);
+    if (signatureElement == null || !signatureElement.isJsonArray()) {
+      return null;
+    }
+    // Signature will be [returnType, name, param1, param2, ...].
+    JsonArray signature = signatureElement.getAsJsonArray();
+    String[] parameters = new String[signature.size() - 2];
+    for (int i = 2; i < signature.size(); i++) {
+      parameters[i - 2] = signature.get(i).getAsString();
+    }
+    return new MethodSignature(
+        signature.get(1).getAsString(), signature.get(0).getAsString(), parameters);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/retrace/Retrace.java b/src/main/java/com/android/tools/r8/retrace/Retrace.java
index 3d59b8b..0cb273e 100644
--- a/src/main/java/com/android/tools/r8/retrace/Retrace.java
+++ b/src/main/java/com/android/tools/r8/retrace/Retrace.java
@@ -138,7 +138,8 @@
       Timing timing = Timing.create("R8 retrace", command.printMemory());
       timing.begin("Read proguard map");
       ClassNameMapper classNameMapper =
-          ClassNameMapper.mapperFromString(command.proguardMapProducer.get());
+          ClassNameMapper.mapperFromString(
+              command.proguardMapProducer.get(), command.diagnosticsHandler);
       timing.end();
       RetraceBase retraceBase = RetraceBaseImpl.create(classNameMapper);
       RetraceCommandLineResult result;
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceBase.java b/src/main/java/com/android/tools/r8/retrace/RetraceBase.java
index 2874fc8..ee7d7ab 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetraceBase.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceBase.java
@@ -19,11 +19,5 @@
 
   RetraceTypeResult retrace(TypeReference typeReference);
 
-  String retraceSourceFile(ClassReference classReference, String sourceFile);
-
-  String retraceSourceFile(
-      ClassReference classReference,
-      String sourceFile,
-      ClassReference retracedClassReference,
-      boolean hasRetraceResult);
+  RetraceSourceFileResult retraceSourceFile(ClassReference classReference, String sourceFile);
 }
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceBaseImpl.java b/src/main/java/com/android/tools/r8/retrace/RetraceBaseImpl.java
index 9a68706..1561457 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetraceBaseImpl.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceBaseImpl.java
@@ -10,15 +10,9 @@
 import com.android.tools.r8.references.MethodReference;
 import com.android.tools.r8.references.TypeReference;
 import com.android.tools.r8.utils.Box;
-import com.google.common.collect.Sets;
-import com.google.common.io.Files;
-import java.util.Set;
 
 public class RetraceBaseImpl implements RetraceBase {
 
-  private static final Set<String> UNKNOWN_SOURCEFILE_NAMES =
-      Sets.newHashSet("", "SourceFile", "Unknown", "Unknown Source");
-
   private final ClassNameMapper classNameMapper;
 
   private RetraceBaseImpl(ClassNameMapper classNameMapper) {
@@ -46,10 +40,11 @@
   }
 
   @Override
-  public String retraceSourceFile(ClassReference classReference, String sourceFile) {
-    Box<String> retracedSourceFile = new Box<>();
+  public RetraceSourceFileResult retraceSourceFile(
+      ClassReference classReference, String sourceFile) {
+    Box<RetraceSourceFileResult> retracedSourceFile = new Box<>();
     retrace(classReference)
-        .forEach(element -> retracedSourceFile.set(element.retraceSourceFile(sourceFile, this)));
+        .forEach(element -> retracedSourceFile.set(element.retraceSourceFile(sourceFile)));
     return retracedSourceFile.get();
   }
 
@@ -57,42 +52,4 @@
   public RetraceTypeResult retrace(TypeReference typeReference) {
     return new RetraceTypeResult(typeReference, this);
   }
-
-  @Override
-  public String retraceSourceFile(
-      ClassReference obfuscatedClass,
-      String sourceFile,
-      ClassReference retracedClassReference,
-      boolean hasRetraceResult) {
-    boolean fileNameProbablyChanged =
-        hasRetraceResult
-            && !retracedClassReference.getTypeName().startsWith(obfuscatedClass.getTypeName());
-    if (!UNKNOWN_SOURCEFILE_NAMES.contains(sourceFile) && !fileNameProbablyChanged) {
-      // We have no new information, only rewrite filename if it is unknown.
-      // PG-retrace will always rewrite the filename, but that seems a bit to harsh to do.
-      return sourceFile;
-    }
-    if (!hasRetraceResult) {
-      // We have no mapping but but the file name is unknown, so the best we can do is take the
-      // name of the obfuscated clazz.
-      assert obfuscatedClass.getTypeName().equals(retracedClassReference.getTypeName());
-      return getClassSimpleName(obfuscatedClass.getTypeName()) + ".java";
-    }
-    String newFileName = getClassSimpleName(retracedClassReference.getTypeName());
-    String extension = Files.getFileExtension(sourceFile);
-    if (extension.isEmpty()) {
-      extension = "java";
-    }
-    return newFileName + "." + extension;
-  }
-
-  private static String getClassSimpleName(String clazz) {
-    int lastIndexOfPeriod = clazz.lastIndexOf('.');
-    // Check if we can find a subclass separator.
-    int endIndex = clazz.lastIndexOf('$');
-    if (lastIndexOfPeriod > endIndex || endIndex < 0) {
-      endIndex = clazz.length();
-    }
-    return clazz.substring(lastIndexOfPeriod + 1, endIndex);
-  }
 }
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceClassResult.java b/src/main/java/com/android/tools/r8/retrace/RetraceClassResult.java
index 25505fe..a631ddf 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetraceClassResult.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceClassResult.java
@@ -4,10 +4,14 @@
 
 package com.android.tools.r8.retrace;
 
+import static com.android.tools.r8.naming.MemberNaming.NoSignature.NO_SIGNATURE;
+import static com.android.tools.r8.retrace.RetraceUtils.synthesizeFileName;
+
 import com.android.tools.r8.Keep;
 import com.android.tools.r8.naming.ClassNamingForNameMapper;
 import com.android.tools.r8.naming.ClassNamingForNameMapper.MappedRangesOfName;
 import com.android.tools.r8.naming.MemberNaming;
+import com.android.tools.r8.naming.mappinginformation.MappingInformation;
 import com.android.tools.r8.references.ClassReference;
 import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.retrace.RetraceClassResult.Element;
@@ -122,9 +126,26 @@
       return classResult;
     }
 
-    public String retraceSourceFile(String fileName, RetraceBase retraceBase) {
-      return retraceBase.retraceSourceFile(
-          classResult.obfuscatedReference, fileName, classReference, mapper != null);
+    public RetraceSourceFileResult retraceSourceFile(String sourceFile) {
+      if (mapper != null && mapper.getAdditionalMappings().size() > 0) {
+        List<MappingInformation> mappingInformations =
+            mapper.getAdditionalMappings().get(NO_SIGNATURE);
+        if (mappingInformations != null) {
+          for (MappingInformation mappingInformation : mappingInformations) {
+            if (mappingInformation.isFileNameInformation()) {
+              return new RetraceSourceFileResult(
+                  mappingInformation.asFileNameInformation().getFileName(), false);
+            }
+          }
+        }
+      }
+      return new RetraceSourceFileResult(
+          synthesizeFileName(
+              classReference.getTypeName(),
+              classResult.obfuscatedReference.getTypeName(),
+              sourceFile,
+              mapper != null),
+          true);
     }
 
     public RetraceFieldResult lookupField(String fieldName) {
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceMethodResult.java b/src/main/java/com/android/tools/r8/retrace/RetraceMethodResult.java
index f0719ea..ff1af91 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetraceMethodResult.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceMethodResult.java
@@ -180,5 +180,9 @@
       }
       return mappedRange.getFirstLineNumberOfOriginalRange();
     }
+
+    public RetraceSourceFileResult retraceSourceFile(String sourceFile) {
+      return RetraceUtils.getSourceFile(classElement, methodReference.getHolderClass(), sourceFile);
+    }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceRegularExpression.java b/src/main/java/com/android/tools/r8/retrace/RetraceRegularExpression.java
index f1603f7..e2e78b2 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetraceRegularExpression.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceRegularExpression.java
@@ -671,20 +671,21 @@
             retracedStrings.add(retraceString);
             continue;
           }
-          String newSourceFile =
-              retraceString.getQualifiedContext() != null
-                  ? retraceBase.retraceSourceFile(
-                      retraceString.classContext.getClassReference(),
-                      fileName,
+          RetraceSourceFileResult sourceFileResult =
+              retraceString.getMethodContext() != null
+                  ? retraceString.getMethodContext().retraceSourceFile(fileName)
+                  : RetraceUtils.getSourceFile(
+                      retraceString.getClassContext(),
                       retraceString.getQualifiedContext(),
-                      true)
-                  : retraceString.classContext.retraceSourceFile(fileName, retraceBase);
+                      fileName);
           retracedStrings.add(
               retraceString
                   .transform()
                   .setSource(fileName)
                   .replaceInString(
-                      newSourceFile, matcher.start(captureGroup), matcher.end(captureGroup))
+                      sourceFileResult.getFilename(),
+                      matcher.start(captureGroup),
+                      matcher.end(captureGroup))
                   .build());
         }
         return retracedStrings;
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceSourceFileResult.java b/src/main/java/com/android/tools/r8/retrace/RetraceSourceFileResult.java
new file mode 100644
index 0000000..4b21be1
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceSourceFileResult.java
@@ -0,0 +1,27 @@
+// 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.retrace;
+
+import com.android.tools.r8.Keep;
+
+@Keep
+public class RetraceSourceFileResult {
+
+  private final String filename;
+  private final boolean synthesized;
+
+  RetraceSourceFileResult(String filename, boolean synthesized) {
+    this.filename = filename;
+    this.synthesized = synthesized;
+  }
+
+  public String getFilename() {
+    return filename;
+  }
+
+  public boolean isSynthesized() {
+    return synthesized;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceStackTrace.java b/src/main/java/com/android/tools/r8/retrace/RetraceStackTrace.java
index c63a707..719dd44 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetraceStackTrace.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceStackTrace.java
@@ -421,7 +421,8 @@
         List<StackTraceLine> lines,
         String classLoaderName) {
       ClassReference classReference = Reference.classFromTypeName(clazz);
-      RetraceMethodResult retraceResult = retraceBase.retrace(classReference).lookupMethod(method);
+      RetraceClassResult classResult = retraceBase.retrace(classReference);
+      RetraceMethodResult retraceResult = classResult.lookupMethod(method);
       if (linePosition != NO_POSITION && linePosition != INVALID_POSITION) {
         retraceResult = retraceResult.narrowByLine(linePosition);
       }
@@ -437,8 +438,7 @@
                     methodReference.getHolderClass().getTypeName(),
                     methodReference.getMethodName(),
                     methodDescriptionFromMethodReference(methodReference, verbose),
-                    retraceBase.retraceSourceFile(
-                        classReference, fileName, methodReference.getHolderClass(), true),
+                    methodElement.retraceSourceFile(fileName).getFilename(),
                     hasLinePosition()
                         ? methodElement.getOriginalLineNumber(linePosition)
                         : linePosition,
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceUtils.java b/src/main/java/com/android/tools/r8/retrace/RetraceUtils.java
index 81c2272..c82bf36 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetraceUtils.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceUtils.java
@@ -4,11 +4,18 @@
 
 package com.android.tools.r8.retrace;
 
+import com.android.tools.r8.references.ClassReference;
 import com.android.tools.r8.references.MethodReference;
 import com.android.tools.r8.references.TypeReference;
+import com.google.common.collect.Sets;
+import com.google.common.io.Files;
+import java.util.Set;
 
 public class RetraceUtils {
 
+  private static final Set<String> UNKNOWN_SOURCEFILE_NAMES =
+      Sets.newHashSet("", "SourceFile", "Unknown", "Unknown Source");
+
   public static String methodDescriptionFromMethodReference(
       MethodReference methodReference, boolean verbose) {
     if (!verbose || methodReference.isUnknown()) {
@@ -35,4 +42,64 @@
     sb.append(")");
     return sb.toString();
   }
+
+  public static boolean hasPredictableSourceFileName(String originalClassName, String sourceFile) {
+    String synthesizedSourceFileName = getClassSimpleName(originalClassName) + ".java";
+    return synthesizedSourceFileName.equals(sourceFile);
+  }
+
+  private static String getClassSimpleName(String clazz) {
+    int lastIndexOfPeriod = clazz.lastIndexOf('.');
+    // Check if we can find a subclass separator.
+    int endIndex = clazz.lastIndexOf('$');
+    if (lastIndexOfPeriod > endIndex || endIndex < 0) {
+      endIndex = clazz.length();
+    }
+    return clazz.substring(lastIndexOfPeriod + 1, endIndex);
+  }
+
+  static RetraceSourceFileResult getSourceFile(
+      RetraceClassResult.Element classElement, ClassReference context, String sourceFile) {
+    // For inline frames we do not have the class element associated with it.
+    if (context == null) {
+      return classElement.retraceSourceFile(sourceFile);
+    }
+    if (context.equals(classElement.getClassReference())) {
+      return classElement.retraceSourceFile(sourceFile);
+    } else {
+      return new RetraceSourceFileResult(
+          synthesizeFileName(
+              context.getTypeName(),
+              classElement.getClassReference().getTypeName(),
+              sourceFile,
+              true),
+          true);
+    }
+  }
+
+  public static String synthesizeFileName(
+      String retracedClassName,
+      String minifiedClassName,
+      String sourceFile,
+      boolean hasRetraceResult) {
+    boolean fileNameProbablyChanged =
+        hasRetraceResult && !retracedClassName.startsWith(minifiedClassName);
+    if (!UNKNOWN_SOURCEFILE_NAMES.contains(sourceFile) && !fileNameProbablyChanged) {
+      // We have no new information, only rewrite filename if it is unknown.
+      // PG-retrace will always rewrite the filename, but that seems a bit to harsh to do.
+      return sourceFile;
+    }
+    String extension = Files.getFileExtension(sourceFile);
+    if (extension.isEmpty()) {
+      extension = "java";
+    }
+    if (!hasRetraceResult) {
+      // We have no mapping but but file name is unknown, so the best we can do is take the
+      // name of the obfuscated clazz.
+      assert minifiedClassName.equals(retracedClassName);
+      return getClassSimpleName(minifiedClassName) + "." + extension;
+    }
+    String newFileName = getClassSimpleName(retracedClassName);
+    return newFileName + "." + extension;
+  }
 }
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 e033e78..5c0f735 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -57,6 +57,7 @@
 import com.android.tools.r8.graph.FieldAccessInfoCollectionImpl;
 import com.android.tools.r8.graph.FieldAccessInfoImpl;
 import com.android.tools.r8.graph.FieldResolutionResult;
+import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.graph.InnerClassAttribute;
 import com.android.tools.r8.graph.LookupLambdaTarget;
 import com.android.tools.r8.graph.LookupTarget;
@@ -1883,21 +1884,27 @@
     }
   }
 
+  private void shouldNotBeMinified(DexReference reference) {
+    if (options.isMinificationEnabled()) {
+      rootSet.shouldNotBeMinified(reference);
+    }
+  }
+
   private void keepClassAndAllMembers(DexProgramClass clazz, KeepReason keepReason) {
     KeepReasonWitness keepReasonWitness = graphReporter.registerClass(clazz, keepReason);
     markClassAsInstantiatedWithCompatRule(clazz.asProgramClass(), keepReasonWitness);
     keepInfo.keepClass(clazz);
-    rootSet.shouldNotBeMinified(clazz.toReference());
+    shouldNotBeMinified(clazz.toReference());
     clazz.forEachProgramField(
         field -> {
           keepInfo.keepField(field);
-          rootSet.shouldNotBeMinified(field.getReference());
+          shouldNotBeMinified(field.getReference());
           markFieldAsKept(field, keepReasonWitness);
         });
     clazz.forEachProgramMethod(
         method -> {
           keepInfo.keepMethod(method);
-          rootSet.shouldNotBeMinified(method.getReference());
+          shouldNotBeMinified(method.getReference());
           markMethodAsKept(method, keepReasonWitness);
         });
   }
@@ -2593,7 +2600,7 @@
       // marking for not renaming it is in the root set.
       workList.enqueueMarkMethodKeptAction(new ProgramMethod(clazz, valuesMethod), reason);
       keepInfo.keepMethod(clazz, valuesMethod);
-      rootSet.shouldNotBeMinified(valuesMethod.toReference());
+      shouldNotBeMinified(valuesMethod.toReference());
     }
   }
 
@@ -2672,6 +2679,7 @@
       // This is simulating the effect of the "root set" applied rules.
       // This is done only in the initial pass, in subsequent passes the "rules" are reapplied
       // by iterating the instances.
+      assert appView.options().isMinificationEnabled() || rootSet.noObfuscation.isEmpty();
       for (DexReference reference : rootSet.noObfuscation) {
         keepInfo.evaluateRule(reference, appInfo, Joiner::disallowMinification);
       }
@@ -2715,6 +2723,12 @@
     return appInfoWithLiveness;
   }
 
+  public GraphLense buildGraphLense(AppView<?> appView) {
+    return lambdaRewriter != null
+        ? lambdaRewriter.buildMappingLense(appView)
+        : appView.graphLense();
+  }
+
   private void keepClassWithRules(DexProgramClass clazz, Set<ProguardKeepRuleBase> rules) {
     keepInfo.joinClass(clazz, info -> applyKeepRules(rules, info));
   }
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardKeepRule.java b/src/main/java/com/android/tools/r8/shaking/ProguardKeepRule.java
index 305c108..326a95e 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardKeepRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardKeepRule.java
@@ -149,7 +149,10 @@
     builder.matchAllSpecification();
     builder.setType(ProguardKeepRuleType.KEEP);
     modifiers.accept(builder.getModifiersBuilder());
-    return builder.build();
+    ProguardKeepRule rule = builder.build();
+    // Consider the default keep all rule as always matched.
+    rule.markAsUsed();
+    return rule;
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
index ec9f101..08830cc 100644
--- a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
@@ -326,6 +326,7 @@
     assert Sets.intersection(neverInline, alwaysInline).isEmpty()
             && Sets.intersection(neverInline, forceInline).isEmpty()
         : "A method cannot be marked as both -neverinline and -forceinline/-alwaysinline.";
+    assert appView.options().isMinificationEnabled() || noObfuscation.isEmpty();
     return new RootSet(
         noShrinking,
         noObfuscation,
@@ -1035,7 +1036,9 @@
         .computeIfAbsent(item.toReference(), x -> new MutableItemsWithRules())
         .addClassWithRule(type, context);
     // Unconditionally add to no-obfuscation, as that is only checked for surviving items.
-    noObfuscation.add(type);
+    if (appView.options().isMinificationEnabled()) {
+      noObfuscation.add(type);
+    }
   }
 
   private void includeDescriptorClasses(DexDefinition item, ProguardKeepRuleBase context) {
@@ -1133,7 +1136,7 @@
         context.markAsUsed();
       }
 
-      if (!modifiers.allowsObfuscation) {
+      if (appView.options().isMinificationEnabled() && !modifiers.allowsObfuscation) {
         noObfuscation.add(item.toReference());
         context.markAsUsed();
       }
diff --git a/src/main/java/com/android/tools/r8/utils/AndroidApp.java b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
index 8599b02..9dd4865 100644
--- a/src/main/java/com/android/tools/r8/utils/AndroidApp.java
+++ b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
@@ -472,7 +472,13 @@
   }
 
   private String getBuildPropertiesContents(InternalOptions options) {
-    return "min-api=" + options.minApiLevel;
+    return String.join(
+        "\n",
+        ImmutableList.of(
+            "mode=" + (options.debug ? "debug" : "release"),
+            "min-api=" + options.minApiLevel,
+            "tree-shaking=" + options.isTreeShakingEnabled(),
+            "minification=" + options.isMinificationEnabled()));
   }
 
   private int dumpLibraryResources(int nextDexIndex, ZipOutputStream out)
diff --git a/src/main/java/com/android/tools/r8/utils/CompileDumpCompatR8.java b/src/main/java/com/android/tools/r8/utils/CompileDumpCompatR8.java
index 936395f..e349c9a 100644
--- a/src/main/java/com/android/tools/r8/utils/CompileDumpCompatR8.java
+++ b/src/main/java/com/android/tools/r8/utils/CompileDumpCompatR8.java
@@ -9,6 +9,7 @@
 import com.android.tools.r8.DexIndexedConsumer.ArchiveConsumer;
 import com.android.tools.r8.OutputMode;
 import com.android.tools.r8.R8;
+import com.android.tools.r8.R8Command;
 import com.android.tools.r8.R8Command.Builder;
 import java.nio.file.Path;
 import java.nio.file.Paths;
@@ -17,6 +18,8 @@
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
 
 /**
  * Wrapper to make it easy to call R8 in compat mode when compiling a dump file.
@@ -44,7 +47,8 @@
           "--main-dex-list-output",
           "--pg-conf",
           "--pg-map-output",
-          "--desugared-lib");
+          "--desugared-lib",
+          "--threads");
 
   private static final List<String> VALID_OPTIONS_WITH_TWO_OPERANDS =
       Arrays.asList("--feature-jar");
@@ -69,6 +73,7 @@
     List<Path> classpath = new ArrayList<>();
     List<Path> config = new ArrayList<>();
     int minApi = 1;
+    int threads = -1;
     for (int i = 0; i < args.length; i++) {
       String option = args[i];
       if (VALID_OPTIONS.contains(option)) {
@@ -129,6 +134,11 @@
               pgMapOutput = Paths.get(operand);
               break;
             }
+          case "--threads":
+            {
+              threads = Integer.parseInt(operand);
+              break;
+            }
           default:
             throw new IllegalArgumentException("Unimplemented option: " + option);
         }
@@ -174,6 +184,16 @@
     if (pgMapOutput != null) {
       commandBuilder.setProguardMapOutputPath(pgMapOutput);
     }
-    R8.run(commandBuilder.build());
+    R8Command command = commandBuilder.build();
+    if (threads != -1) {
+      ExecutorService executor = Executors.newWorkStealingPool(threads);
+      try {
+        R8.run(command, executor);
+      } finally {
+        executor.shutdown();
+      }
+    } else {
+      R8.run(command);
+    }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java b/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
index 83f8ec0..6907805 100644
--- a/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
+++ b/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
@@ -47,6 +47,8 @@
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.naming.Range;
+import com.android.tools.r8.naming.mappinginformation.FileNameInformation;
+import com.android.tools.r8.retrace.RetraceUtils;
 import com.android.tools.r8.shaking.KeepInfoCollection;
 import com.android.tools.r8.utils.InternalOptions.LineNumberOptimization;
 import com.google.common.base.Suppliers;
@@ -287,6 +289,15 @@
                       originalType.toSourceString(),
                       com.android.tools.r8.position.Position.UNKNOWN));
 
+      // Check if source file should be added to the map
+      if (clazz.sourceFile != null) {
+        String sourceFile = clazz.sourceFile.toString();
+        if (!RetraceUtils.hasPredictableSourceFileName(clazz.toSourceString(), sourceFile)) {
+          Builder builder = onDemandClassNamingBuilder.get();
+          builder.addMappingInformation(FileNameInformation.build(sourceFile));
+        }
+      }
+
       // If the class is renamed add it to the classNamingBuilder.
       addClassToClassNaming(originalType, renamedClassName, onDemandClassNamingBuilder);
 
@@ -367,12 +378,8 @@
           signatures.put(originalMethod, originalSignature);
           Function<DexMethod, MethodSignature> getOriginalMethodSignature =
               m -> {
-                DexMethod original = appView.graphLense().getOriginalMethodSignature(m);
                 return signatures.computeIfAbsent(
-                    original,
-                    key ->
-                        MethodSignature.fromDexMethod(
-                            original, original.holder != clazz.getType()));
+                    m, key -> MethodSignature.fromDexMethod(m, m.holder != clazz.getType()));
               };
 
           MemberNaming memberNaming = new MemberNaming(originalSignature, obfuscatedName);
diff --git a/src/test/java/com/android/tools/r8/MarkersTest.java b/src/test/java/com/android/tools/r8/MarkersTest.java
index cc7b81e..f2e539b 100644
--- a/src/test/java/com/android/tools/r8/MarkersTest.java
+++ b/src/test/java/com/android/tools/r8/MarkersTest.java
@@ -17,7 +17,10 @@
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.FileUtils;
+import com.google.common.base.Charsets;
 import com.google.common.collect.ImmutableList;
+import com.google.gson.JsonParser;
 import java.nio.file.Path;
 import java.util.Collection;
 import org.hamcrest.Matcher;
@@ -65,11 +68,18 @@
     }
     L8.run(builder.build());
     Collection<Marker> markers = ExtractMarker.extractMarkerFromDexFile(output);
+    String version =
+        new JsonParser()
+            .parse(FileUtils.readTextFile(ToolHelper.DESUGAR_LIB_JSON_FOR_TESTING, Charsets.UTF_8))
+            .getAsJsonObject()
+            .get("version")
+            .getAsString();
+
     Matcher<Marker> l8Matcher =
         allOf(
             markerTool(Tool.L8),
             markerCompilationMode(compilationMode),
-            markerDesugaredLibraryIdentifier("com.tools.android:desugar_jdk_libs:1.0.10"),
+            markerDesugaredLibraryIdentifier("com.tools.android:desugar_jdk_libs:" + version),
             markerHasChecksums(false));
     Matcher<Marker> d8Matcher =
         allOf(
diff --git a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
index b262e4e..f53e8b3 100644
--- a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
@@ -7,6 +7,7 @@
 import com.android.tools.r8.TestBase.Backend;
 import com.android.tools.r8.references.MethodReference;
 import com.android.tools.r8.references.TypeReference;
+import com.android.tools.r8.shaking.ProguardKeepAttributes;
 import java.io.IOException;
 import java.nio.file.Path;
 import java.util.Arrays;
@@ -154,7 +155,7 @@
     return addKeepMainRule(mainClass.getTypeName());
   }
 
-  public T addKeepMainRules(Class<?>[] mainClasses) {
+  public T addKeepMainRules(Class<?>... mainClasses) {
     for (Class<?> mainClass : mainClasses) {
       this.addKeepMainRule(mainClass);
     }
@@ -210,11 +211,15 @@
   }
 
   public T addKeepAttributeLineNumberTable() {
-    return addKeepAttributes("LineNumberTable");
+    return addKeepAttributes(ProguardKeepAttributes.LINE_NUMBER_TABLE);
+  }
+
+  public T addKeepAttributeSourceFile() {
+    return addKeepAttributes(ProguardKeepAttributes.SOURCE_FILE);
   }
 
   public T addKeepRuntimeVisibleAnnotations() {
-    return addKeepAttributes("RuntimeVisibleAnnotations");
+    return addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS);
   }
 
   public T addKeepAllAttributes() {
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryConfigurationParsingTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryConfigurationParsingTest.java
index 1141cb3..12ed820 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryConfigurationParsingTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryConfigurationParsingTest.java
@@ -299,9 +299,13 @@
     } else if (element instanceof Integer) {
       builder.append(element);
     } else if (element instanceof List) {
-      toJsonList((List<Object>) element, builder);
+      @SuppressWarnings("unchecked")
+      List<Object> elements = (List<Object>) element;
+      toJsonList(elements, builder);
     } else if (element instanceof Map) {
-      toJsonObject((Map<String, Object>) element, builder);
+      @SuppressWarnings("unchecked")
+      Map<String, Object> elements = (Map<String, Object>) element;
+      toJsonObject(elements, builder);
     } else {
       throw new IllegalStateException("Unexpected object type: " + element.getClass());
     }
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/APIConversionTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/APIConversionTest.java
index ad8e74c..fcc2124 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/APIConversionTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/APIConversionTest.java
@@ -30,8 +30,7 @@
 
   private static final AndroidApiLevel MIN_SUPPORTED = AndroidApiLevel.N;
   private static final String EXPECTED_RESULT =
-      StringUtils.lines(
-          "[5, 6, 7]", "j$.$r8$wrapper$java$util$stream$IntStream$-V-WRP", "IntSummaryStatistics");
+      StringUtils.lines("[5, 6, 7]", "j$.$r8$wrapper$java$util$stream$IntStream$-V-WRP");
 
   @Parameters(name = "{0}, shrinkDesugaredLibrary: {1}")
   public static List<Object[]> data() {
@@ -75,7 +74,11 @@
             keepRuleConsumer.get(),
             shrinkDesugaredLibrary)
         .run(parameters.getRuntime(), Executor.class)
-        .assertSuccessWithOutput(EXPECTED_RESULT);
+        .assertFailureWithOutput(EXPECTED_RESULT)
+        .assertFailureWithErrorThatMatches(
+            containsString(
+                "Java 8+ API desugaring (library desugaring) cannot convert"
+                    + " from java.util.IntSummaryStatistics"));
   }
 
   @Test
@@ -95,7 +98,11 @@
             keepRuleConsumer.get(),
             shrinkDesugaredLibrary)
         .run(parameters.getRuntime(), Executor.class)
-        .assertSuccessWithOutput(EXPECTED_RESULT);
+        .assertFailureWithOutput(EXPECTED_RESULT)
+        .assertFailureWithErrorThatMatches(
+            containsString(
+                "Java 8+ API desugaring (library desugaring) cannot convert"
+                    + " from java.util.IntSummaryStatistics"));
   }
 
   static class Executor {
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/SummaryStatisticsConversionTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/SummaryStatisticsConversionTest.java
index 21074c9..7fe00d5 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/SummaryStatisticsConversionTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/SummaryStatisticsConversionTest.java
@@ -29,6 +29,16 @@
   private static final AndroidApiLevel MIN_SUPPORTED = AndroidApiLevel.N;
   private static final String EXPECTED_RESULT =
       StringUtils.lines(
+          "Java 8+ API desugaring (library desugaring) cannot convert"
+              + " from java.util.IntSummaryStatistics",
+          "Java 8+ API desugaring (library desugaring) cannot convert"
+              + " to java.util.LongSummaryStatistics",
+          "Java 8+ API desugaring (library desugaring) cannot convert"
+              + " to java.util.IntSummaryStatistics",
+          "Java 8+ API desugaring (library desugaring) cannot convert"
+              + " to java.util.DoubleSummaryStatistics");
+  private static final String SUCCESS_EXPECTED_RESULT =
+      StringUtils.lines(
           "2", "1", "42", "42", "42", "1", "42", "42", "42", "1", "42.0", "42.0", "42.0");
   private static Path CUSTOM_LIB;
 
@@ -105,41 +115,58 @@
     }
 
     private static void realTest() {
-      System.out.println("foo".subSequence(0, 2).codePoints().summaryStatistics().getCount());
+      try {
+        System.out.println("foo".subSequence(0, 2).codePoints().summaryStatistics().getCount());
+      } catch (Error e) {
+        System.out.println(e.getMessage());
+      }
     }
 
     public static void longTest() {
       long[] longs = new long[1];
       longs[0] = 42L;
-      LongSummaryStatistics mix =
-          CustomLibClass.mix(Arrays.stream(longs).summaryStatistics(), new LongSummaryStatistics());
-      System.out.println(mix.getCount());
-      System.out.println(mix.getMin());
-      System.out.println(mix.getMax());
-      System.out.println(mix.getSum());
+      try {
+        LongSummaryStatistics mix =
+            CustomLibClass.mix(
+                Arrays.stream(longs).summaryStatistics(), new LongSummaryStatistics());
+        System.out.println(mix.getCount());
+        System.out.println(mix.getMin());
+        System.out.println(mix.getMax());
+        System.out.println(mix.getSum());
+      } catch (Error e) {
+        System.out.println(e.getMessage());
+      }
     }
 
     public static void intTest() {
       int[] ints = new int[1];
       ints[0] = 42;
-      IntSummaryStatistics mix =
-          CustomLibClass.mix(Arrays.stream(ints).summaryStatistics(), new IntSummaryStatistics());
-      System.out.println(mix.getCount());
-      System.out.println(mix.getMin());
-      System.out.println(mix.getMax());
-      System.out.println(mix.getSum());
+      try {
+        IntSummaryStatistics mix =
+            CustomLibClass.mix(Arrays.stream(ints).summaryStatistics(), new IntSummaryStatistics());
+        System.out.println(mix.getCount());
+        System.out.println(mix.getMin());
+        System.out.println(mix.getMax());
+        System.out.println(mix.getSum());
+      } catch (Error e) {
+        System.out.println(e.getMessage());
+      }
     }
 
     public static void doubleTest() {
       double[] doubles = new double[1];
       doubles[0] = 42L;
-      DoubleSummaryStatistics mix =
-          CustomLibClass.mix(
-              Arrays.stream(doubles).summaryStatistics(), new DoubleSummaryStatistics());
-      System.out.println(mix.getCount());
-      System.out.println(mix.getMin());
-      System.out.println(mix.getMax());
-      System.out.println(mix.getSum());
+      try {
+        DoubleSummaryStatistics mix =
+            CustomLibClass.mix(
+                Arrays.stream(doubles).summaryStatistics(), new DoubleSummaryStatistics());
+        System.out.println(mix.getCount());
+        System.out.println(mix.getMin());
+        System.out.println(mix.getMax());
+        System.out.println(mix.getSum());
+      } catch (Error e) {
+        System.out.println(e.getMessage());
+      }
     }
   }
 
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/AnnotationEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/AnnotationEnumUnboxingTest.java
index 1cf4e93..6483aee 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/AnnotationEnumUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/AnnotationEnumUnboxingTest.java
@@ -23,7 +23,7 @@
 
   private final TestParameters parameters;
   private final boolean enumValueOptimization;
-  private final KeepRule enumKeepRules;
+  private final EnumKeepRules enumKeepRules;
 
   @Parameters(name = "{0} valueOpt: {1} keep: {2}")
   public static List<Object[]> data() {
@@ -31,7 +31,7 @@
   }
 
   public AnnotationEnumUnboxingTest(
-      TestParameters parameters, boolean enumValueOptimization, KeepRule enumKeepRules) {
+      TestParameters parameters, boolean enumValueOptimization, EnumKeepRules enumKeepRules) {
     this.parameters = parameters;
     this.enumValueOptimization = enumValueOptimization;
     this.enumKeepRules = enumKeepRules;
@@ -46,7 +46,7 @@
         .addInnerClasses(AnnotationEnumUnboxingTest.class)
         .noMinification()
         .addKeepMainRule(Main.class)
-        .addKeepRules(enumKeepRules.getKeepRule())
+        .addKeepRules(enumKeepRules.getKeepRules())
         .addKeepClassRules(ClassAnnotationDefault.class)
         .addKeepRuntimeVisibleAnnotations()
         .enableNeverClassInliningAnnotations()
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/ComparisonEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/ComparisonEnumUnboxingTest.java
index 19c069d..9873711 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/ComparisonEnumUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/ComparisonEnumUnboxingTest.java
@@ -23,7 +23,7 @@
 
   private final TestParameters parameters;
   private final boolean enumValueOptimization;
-  private final KeepRule enumKeepRules;
+  private final EnumKeepRules enumKeepRules;
 
   @Parameters(name = "{0} valueOpt: {1} keep: {2}")
   public static List<Object[]> data() {
@@ -31,7 +31,7 @@
   }
 
   public ComparisonEnumUnboxingTest(
-      TestParameters parameters, boolean enumValueOptimization, KeepRule enumKeepRules) {
+      TestParameters parameters, boolean enumValueOptimization, EnumKeepRules enumKeepRules) {
     this.parameters = parameters;
     this.enumValueOptimization = enumValueOptimization;
     this.enumKeepRules = enumKeepRules;
@@ -45,7 +45,7 @@
             .addKeepMainRules(INPUTS)
             .enableInliningAnnotations()
             .enableNeverClassInliningAnnotations()
-            .addKeepRules(enumKeepRules.getKeepRule())
+            .addKeepRules(enumKeepRules.getKeepRules())
             .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
             .allowDiagnosticInfoMessages()
             .setMinApi(parameters.getApiLevel())
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/DoubleProcessingEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/DoubleProcessingEnumUnboxingTest.java
index 7582177..d1e5b4a 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/DoubleProcessingEnumUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/DoubleProcessingEnumUnboxingTest.java
@@ -28,7 +28,7 @@
 public class DoubleProcessingEnumUnboxingTest extends EnumUnboxingTestBase {
   private final TestParameters parameters;
   private final boolean enumValueOptimization;
-  private final KeepRule enumKeepRules;
+  private final EnumKeepRules enumKeepRules;
   private final boolean minification;
 
   @Parameters(name = "{0} valueOpt: {1} keep: {2} minif: {3}")
@@ -43,7 +43,7 @@
   public DoubleProcessingEnumUnboxingTest(
       TestParameters parameters,
       boolean enumValueOptimization,
-      KeepRule enumKeepRules,
+      EnumKeepRules enumKeepRules,
       boolean minification) {
     this.parameters = parameters;
     this.enumValueOptimization = enumValueOptimization;
@@ -59,7 +59,7 @@
             .addProgramClasses(JavaLibrary1.class, JavaLibrary1.LibEnum1.class)
             .addKeepMethodRules(
                 Reference.methodFromMethod(JavaLibrary1.class.getDeclaredMethod("libCall")))
-            .addKeepRules(enumKeepRules.getKeepRule())
+            .addKeepRules(enumKeepRules.getKeepRules())
             .enableNeverClassInliningAnnotations()
             .enableInliningAnnotations()
             .minification(minification)
@@ -72,7 +72,7 @@
             .addProgramClasses(App.class, App.AppEnum.class)
             .addProgramFiles(javaLibShrunk)
             .addKeepMainRule(App.class)
-            .addKeepRules(enumKeepRules.getKeepRule())
+            .addKeepRules(enumKeepRules.getKeepRules())
             .enableNeverClassInliningAnnotations()
             .enableInliningAnnotations()
             .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/DoubleProcessingMergeEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/DoubleProcessingMergeEnumUnboxingTest.java
index e5f3913..49fc118 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/DoubleProcessingMergeEnumUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/DoubleProcessingMergeEnumUnboxingTest.java
@@ -29,7 +29,7 @@
 public class DoubleProcessingMergeEnumUnboxingTest extends EnumUnboxingTestBase {
   private final TestParameters parameters;
   private final boolean enumValueOptimization;
-  private final KeepRule enumKeepRules;
+  private final EnumKeepRules enumKeepRules;
   private final boolean minification;
 
   @Parameters(name = "{0} valueOpt: {1} keep: {2} minif: {3}")
@@ -44,7 +44,7 @@
   public DoubleProcessingMergeEnumUnboxingTest(
       TestParameters parameters,
       boolean enumValueOptimization,
-      KeepRule enumKeepRules,
+      EnumKeepRules enumKeepRules,
       boolean minification) {
     this.parameters = parameters;
     this.enumValueOptimization = enumValueOptimization;
@@ -63,7 +63,7 @@
             .addProgramClasses(App.class, App.AppEnum.class)
             .addProgramFiles(javaLibShrunk1, javaLibShrunk2)
             .addKeepMainRule(App.class)
-            .addKeepRules(enumKeepRules.getKeepRule())
+            .addKeepRules(enumKeepRules.getKeepRules())
             .enableNeverClassInliningAnnotations()
             .enableInliningAnnotations()
             .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
@@ -82,7 +82,7 @@
     return testForR8(Backend.CF)
         .addProgramClasses(libClass, enumLibClass)
         .addKeepMethodRules(Reference.methodFromMethod(libClass.getDeclaredMethod("libCall")))
-        .addKeepRules(enumKeepRules.getKeepRule())
+        .addKeepRules(enumKeepRules.getKeepRules())
         .enableNeverClassInliningAnnotations()
         .enableInliningAnnotations()
         .minification(minification)
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/EnumClinitWithSideEffectsUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/EnumClinitWithSideEffectsUnboxingTest.java
index c3e548e..92aa76e 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/EnumClinitWithSideEffectsUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/EnumClinitWithSideEffectsUnboxingTest.java
@@ -10,7 +10,7 @@
 @RunWith(Parameterized.class)
 public class EnumClinitWithSideEffectsUnboxingTest extends EnumUnboxingTestBase {
 
-  private final KeepRule enumKeepRule;
+  private final EnumKeepRules enumEnumKeepRules;
   private final TestParameters parameters;
 
   @Parameters(name = "{1}, enum keep rule: {0}")
@@ -19,8 +19,9 @@
         getAllEnumKeepRules(), getTestParameters().withAllRuntimesAndApiLevels().build());
   }
 
-  public EnumClinitWithSideEffectsUnboxingTest(KeepRule enumKeepRule, TestParameters parameters) {
-    this.enumKeepRule = enumKeepRule;
+  public EnumClinitWithSideEffectsUnboxingTest(
+      EnumKeepRules enumEnumKeepRules, TestParameters parameters) {
+    this.enumEnumKeepRules = enumEnumKeepRules;
     this.parameters = parameters;
   }
 
@@ -29,7 +30,7 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(EnumClinitWithSideEffectsUnboxingTest.class)
         .addKeepMainRule(TestClass.class)
-        .addKeepRules(enumKeepRule.getKeepRule())
+        .addKeepRules(enumEnumKeepRules.getKeepRules())
         .setMinApi(parameters.getApiLevel())
         .compile()
         .run(parameters.getRuntime(), TestClass.class)
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/EnumInitWithSideEffectsUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/EnumInitWithSideEffectsUnboxingTest.java
index b9445c2..c3967e5 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/EnumInitWithSideEffectsUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/EnumInitWithSideEffectsUnboxingTest.java
@@ -10,7 +10,7 @@
 @RunWith(Parameterized.class)
 public class EnumInitWithSideEffectsUnboxingTest extends EnumUnboxingTestBase {
 
-  private final KeepRule enumKeepRule;
+  private final EnumKeepRules enumEnumKeepRules;
   private final TestParameters parameters;
 
   @Parameters(name = "{1}, enum keep rule: {0}")
@@ -19,8 +19,9 @@
         getAllEnumKeepRules(), getTestParameters().withAllRuntimesAndApiLevels().build());
   }
 
-  public EnumInitWithSideEffectsUnboxingTest(KeepRule enumKeepRule, TestParameters parameters) {
-    this.enumKeepRule = enumKeepRule;
+  public EnumInitWithSideEffectsUnboxingTest(
+      EnumKeepRules enumEnumKeepRules, TestParameters parameters) {
+    this.enumEnumKeepRules = enumEnumKeepRules;
     this.parameters = parameters;
   }
 
@@ -29,7 +30,7 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(EnumInitWithSideEffectsUnboxingTest.class)
         .addKeepMainRule(TestClass.class)
-        .addKeepRules(enumKeepRule.getKeepRule())
+        .addKeepRules(enumEnumKeepRules.getKeepRules())
         .setMinApi(parameters.getApiLevel())
         .compile()
         .run(parameters.getRuntime(), TestClass.class)
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/EnumToStringLibTest.java b/src/test/java/com/android/tools/r8/enumunboxing/EnumToStringLibTest.java
new file mode 100644
index 0000000..83393bf
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/enumunboxing/EnumToStringLibTest.java
@@ -0,0 +1,218 @@
+// 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.enumunboxing;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndRenamed;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.core.StringContains.containsString;
+
+import com.android.tools.r8.R8TestCompileResult;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.util.List;
+import org.junit.Assume;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class EnumToStringLibTest extends EnumUnboxingTestBase {
+
+  private final TestParameters parameters;
+  private final boolean enumValueOptimization;
+  private final EnumKeepRules enumKeepRules;
+  private final boolean enumUnboxing;
+
+  @Parameterized.Parameters(name = "{0} valueOpt: {1} keep: {2} unbox: {3}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withDexRuntimes().withAllApiLevels().build(),
+        BooleanUtils.values(),
+        getAllEnumKeepRules(),
+        BooleanUtils.values());
+  }
+
+  public EnumToStringLibTest(
+      TestParameters parameters,
+      boolean enumValueOptimization,
+      EnumKeepRules enumKeepRules,
+      boolean enumUnboxing) {
+    this.parameters = parameters;
+    this.enumValueOptimization = enumValueOptimization;
+    this.enumKeepRules = enumKeepRules;
+    this.enumUnboxing = enumUnboxing;
+  }
+
+  @Test
+  public void testToStringLib() throws Exception {
+    Assume.assumeFalse(
+        "The test rely on valueOf, so only studio or snap keep rules are valid.",
+        enumKeepRules == EnumKeepRules.NONE);
+    // Compile the lib cf to cf.
+    R8TestCompileResult javaLibShrunk = compileLibrary();
+    assertEnumFieldsMinified(javaLibShrunk.inspector());
+    // Compile the program with the lib.
+    R8TestCompileResult compile =
+        testForR8(parameters.getBackend())
+            .addProgramClasses(AlwaysCorrectProgram.class, AlwaysCorrectProgram2.class)
+            .addProgramFiles(javaLibShrunk.writeToZip())
+            .addKeepMainRule(AlwaysCorrectProgram.class)
+            .addKeepMainRule(AlwaysCorrectProgram2.class)
+            .addKeepRules(enumKeepRules.getKeepRules())
+            .addOptionsModification(
+                options -> {
+                  options.enableEnumUnboxing = enumUnboxing;
+                  options.enableEnumValueOptimization = enumValueOptimization;
+                  options.enableEnumSwitchMapRemoval = enumValueOptimization;
+                  options.testing.enableEnumUnboxingDebugLogs = enumUnboxing;
+                })
+            .allowDiagnosticInfoMessages(enumUnboxing)
+            .setMinApi(parameters.getApiLevel())
+            .compile();
+    compile
+        .run(parameters.getRuntime(), AlwaysCorrectProgram.class)
+        .assertSuccessWithOutputLines("0", "1", "2", "0", "1", "2", "0", "1", "2");
+    if (!enumKeepRules.isSnap() && enumUnboxing) {
+      // TODO(b/160667929): Fix toString and enum unboxing.
+      compile
+          .run(parameters.getRuntime(), AlwaysCorrectProgram2.class)
+          .assertFailureWithErrorThatMatches(containsString("IllegalArgumentException"));
+      return;
+    }
+    compile
+        .run(parameters.getRuntime(), AlwaysCorrectProgram2.class)
+        .assertSuccessWithOutputLines("0", "1", "2", "0", "1", "2");
+  }
+
+  private void assertEnumFieldsMinified(CodeInspector codeInspector) throws Exception {
+    if (enumKeepRules.isSnap()) {
+      return;
+    }
+    ClassSubject clazz = codeInspector.clazz(ToStringLib.LibEnum.class);
+    assertThat(clazz, isPresent());
+    for (String fieldName : new String[] {"COFFEE", "BEAN", "SUGAR"}) {
+      assertThat(
+          codeInspector.field(ToStringLib.LibEnum.class.getField(fieldName)),
+          isPresentAndRenamed());
+    }
+  }
+
+  private R8TestCompileResult compileLibrary() throws Exception {
+    return testForR8(Backend.CF)
+        .addProgramClasses(ToStringLib.class, ToStringLib.LibEnum.class)
+        .addKeepRules(enumKeepRules.getKeepRules())
+        // TODO(b/160535629): Work-around on some optimizations relying on $VALUES name.
+        .addKeepRules(
+            "-keep enum "
+                + ToStringLib.LibEnum.class.getName()
+                + " { static "
+                + ToStringLib.LibEnum.class.getName()
+                + "[] $VALUES; }")
+        .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
+        .addKeepMethodRules(
+            Reference.methodFromMethod(
+                ToStringLib.class.getDeclaredMethod("lookupByName", String.class)),
+            Reference.methodFromMethod(ToStringLib.class.getDeclaredMethod("getCoffee")),
+            Reference.methodFromMethod(ToStringLib.class.getDeclaredMethod("getBean")),
+            Reference.methodFromMethod(ToStringLib.class.getDeclaredMethod("getSugar")),
+            Reference.methodFromMethod(ToStringLib.class.getDeclaredMethod("directCoffee")),
+            Reference.methodFromMethod(ToStringLib.class.getDeclaredMethod("directBean")),
+            Reference.methodFromMethod(ToStringLib.class.getDeclaredMethod("directSugar")))
+        .addKeepClassRules(ToStringLib.LibEnum.class)
+        .allowDiagnosticMessages()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspectDiagnosticMessages(
+            msg ->
+                assertEnumIsBoxed(
+                    ToStringLib.LibEnum.class, ToStringLib.LibEnum.class.getSimpleName(), msg));
+  }
+
+  // This class emulates a library with the three public methods getEnumXXX.
+  public static class ToStringLib {
+
+    // We pick names here that we assume won't be picked by the minifier (i.e., not a,b,c).
+    public enum LibEnum {
+      COFFEE,
+      BEAN,
+      SUGAR;
+    }
+
+    // If there is a keep rule on LibEnum fields, then ToStringLib.lookupByName("COFFEE")
+    // should answer 0, else, the behavior of ToStringLib.lookupByName("COFFEE") is undefined.
+    // ToStringLib.lookupByName(LibEnum.COFFEE.toString()) should always answer 0, no matter
+    // what keep rules are on LibEnum.
+    public static int lookupByName(String key) {
+      if (key == null) {
+        return -1;
+      } else if (key.contains(LibEnum.COFFEE.name())) {
+        return LibEnum.COFFEE.ordinal();
+      } else if (key.contains(LibEnum.BEAN.name())) {
+        return LibEnum.BEAN.ordinal();
+      } else if (key.contains(LibEnum.SUGAR.name())) {
+        return LibEnum.SUGAR.ordinal();
+      } else {
+        return -2;
+      }
+    }
+
+    // The following method should always return 0, no matter what keep rules are on LibEnum.
+    public static int directCoffee() {
+      return LibEnum.valueOf(LibEnum.COFFEE.toString()).ordinal();
+    }
+
+    public static int directBean() {
+      return LibEnum.valueOf(LibEnum.BEAN.toString()).ordinal();
+    }
+
+    public static int directSugar() {
+      return LibEnum.valueOf(LibEnum.SUGAR.toString()).ordinal();
+    }
+
+    public static LibEnum getCoffee() {
+      return LibEnum.COFFEE;
+    }
+
+    public static LibEnum getBean() {
+      return LibEnum.BEAN;
+    }
+
+    public static LibEnum getSugar() {
+      return LibEnum.SUGAR;
+    }
+  }
+
+  // The next two classes emulate a program using the ToStringLib library.
+  public static class AlwaysCorrectProgram {
+
+    public static void main(String[] args) {
+      System.out.println(ToStringLib.directCoffee());
+      System.out.println(ToStringLib.directBean());
+      System.out.println(ToStringLib.directSugar());
+      System.out.println(ToStringLib.lookupByName(ToStringLib.getCoffee().toString()));
+      System.out.println(ToStringLib.lookupByName(ToStringLib.getBean().toString()));
+      System.out.println(ToStringLib.lookupByName(ToStringLib.getSugar().toString()));
+      System.out.println(ToStringLib.LibEnum.valueOf(ToStringLib.getCoffee().toString()).ordinal());
+      System.out.println(ToStringLib.LibEnum.valueOf(ToStringLib.getBean().toString()).ordinal());
+      System.out.println(ToStringLib.LibEnum.valueOf(ToStringLib.getSugar().toString()).ordinal());
+    }
+  }
+
+  public static class AlwaysCorrectProgram2 {
+
+    public static void main(String[] args) {
+      System.out.println(ToStringLib.lookupByName("COFFEE"));
+      System.out.println(ToStringLib.lookupByName("BEAN"));
+      System.out.println(ToStringLib.lookupByName("SUGAR"));
+      System.out.println(ToStringLib.LibEnum.valueOf("COFFEE").ordinal());
+      System.out.println(ToStringLib.LibEnum.valueOf("BEAN").ordinal());
+      System.out.println(ToStringLib.LibEnum.valueOf("SUGAR").ordinal());
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingArrayTest.java b/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingArrayTest.java
index d9c9350..d4b73ff 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingArrayTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingArrayTest.java
@@ -28,7 +28,7 @@
 
   private final TestParameters parameters;
   private final boolean enumValueOptimization;
-  private final KeepRule enumKeepRules;
+  private final EnumKeepRules enumKeepRules;
 
   @Parameters(name = "{0} valueOpt: {1} keep: {2}")
   public static List<Object[]> data() {
@@ -36,7 +36,7 @@
   }
 
   public EnumUnboxingArrayTest(
-      TestParameters parameters, boolean enumValueOptimization, KeepRule enumKeepRules) {
+      TestParameters parameters, boolean enumValueOptimization, EnumKeepRules enumKeepRules) {
     this.parameters = parameters;
     this.enumValueOptimization = enumValueOptimization;
     this.enumKeepRules = enumKeepRules;
@@ -50,7 +50,7 @@
             .addKeepMainRules(SUCCESSES)
             .enableInliningAnnotations()
             .enableNeverClassInliningAnnotations()
-            .addKeepRules(enumKeepRules.getKeepRule())
+            .addKeepRules(enumKeepRules.getKeepRules())
             .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
             .allowDiagnosticInfoMessages()
             .setMinApi(parameters.getApiLevel())
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingB160535628Test.java b/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingB160535628Test.java
new file mode 100644
index 0000000..5ccd903
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingB160535628Test.java
@@ -0,0 +1,143 @@
+// 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.enumunboxing;
+
+import static org.hamcrest.core.StringContains.containsString;
+
+import com.android.tools.r8.R8TestCompileResult;
+import com.android.tools.r8.TestDiagnosticMessages;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.BooleanUtils;
+import java.nio.file.Path;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class EnumUnboxingB160535628Test extends EnumUnboxingTestBase {
+
+  private final TestParameters parameters;
+  private final boolean missingStaticMethods;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withDexRuntimes().withAllApiLevels().build(), BooleanUtils.values());
+  }
+
+  public EnumUnboxingB160535628Test(TestParameters parameters, boolean missingStaticMethods) {
+    this.parameters = parameters;
+    this.missingStaticMethods = missingStaticMethods;
+  }
+
+  @Test
+  public void testCallToMissingStaticMethodInUnboxedEnum() throws Exception {
+    // Compile the lib cf to cf.
+    Path javaLibShrunk = compileLibrary();
+    // Compile the program with the lib.
+    // This should compile without error into code raising the correct NoSuchMethod errors.
+    R8TestCompileResult compile =
+        testForR8(parameters.getBackend())
+            .addProgramClasses(ProgramValueOf.class, ProgramStaticMethod.class)
+            .addProgramFiles(javaLibShrunk)
+            .addKeepMainRules(ProgramValueOf.class, ProgramStaticMethod.class)
+            .addOptionsModification(
+                options -> {
+                  options.enableEnumUnboxing = true;
+                  options.testing.enableEnumUnboxingDebugLogs = true;
+                })
+            .allowDiagnosticMessages()
+            .setMinApi(parameters.getApiLevel())
+            .compile()
+            .inspectDiagnosticMessages(
+                // The enums cannot be unboxed if static methods are missing,
+                // but they should be unboxed otherwise.
+                this::assertEnumUnboxedIfStaticMethodsPresent);
+    if (missingStaticMethods) {
+      compile
+          .run(parameters.getRuntime(), ProgramValueOf.class)
+          .assertFailureWithErrorThatMatches(containsString("NoSuchMethodError"))
+          .assertFailureWithErrorThatMatches(containsString("valueOf"));
+      compile
+          .run(parameters.getRuntime(), ProgramStaticMethod.class)
+          .assertFailureWithErrorThatMatches(containsString("NoSuchMethodError"))
+          .assertFailureWithErrorThatMatches(containsString("staticMethod"));
+    } else {
+      compile.run(parameters.getRuntime(), ProgramValueOf.class).assertSuccessWithOutputLines("0");
+      compile
+          .run(parameters.getRuntime(), ProgramStaticMethod.class)
+          .assertSuccessWithOutputLines("42");
+    }
+  }
+
+  private Path compileLibrary() throws Exception {
+    return testForR8(Backend.CF)
+        .addProgramClasses(Lib.class, Lib.LibEnumStaticMethod.class, Lib.LibEnum.class)
+        .addKeepRules("-keep enum * { <fields>; }")
+        .addKeepRules(missingStaticMethods ? "" : "-keep enum * { static <methods>; }")
+        .addOptionsModification(
+            options -> {
+              options.enableEnumUnboxing = true;
+              options.testing.enableEnumUnboxingDebugLogs = true;
+            })
+        .addKeepClassRules(Lib.LibEnumStaticMethod.class)
+        .allowDiagnosticMessages()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspectDiagnosticMessages(
+            msg -> {
+              assertEnumIsBoxed(
+                  Lib.LibEnumStaticMethod.class,
+                  Lib.LibEnumStaticMethod.class.getSimpleName(),
+                  msg);
+              assertEnumIsBoxed(Lib.LibEnum.class, Lib.LibEnum.class.getSimpleName(), msg);
+            })
+        .writeToZip();
+  }
+
+  private void assertEnumUnboxedIfStaticMethodsPresent(TestDiagnosticMessages msg) {
+    if (missingStaticMethods) {
+      assertEnumIsBoxed(
+          Lib.LibEnumStaticMethod.class, Lib.LibEnumStaticMethod.class.getSimpleName(), msg);
+      assertEnumIsBoxed(Lib.LibEnum.class, Lib.LibEnum.class.getSimpleName(), msg);
+    } else {
+      assertEnumIsUnboxed(
+          Lib.LibEnumStaticMethod.class, Lib.LibEnumStaticMethod.class.getSimpleName(), msg);
+      assertEnumIsUnboxed(Lib.LibEnum.class, Lib.LibEnum.class.getSimpleName(), msg);
+    }
+  }
+
+  public static class Lib {
+
+    public enum LibEnumStaticMethod {
+      A,
+      B;
+
+      static int staticMethod() {
+        return 42;
+      }
+    }
+
+    public enum LibEnum {
+      A,
+      B
+    }
+  }
+
+  public static class ProgramValueOf {
+
+    public static void main(String[] args) {
+      System.out.println(Lib.LibEnumStaticMethod.valueOf(Lib.LibEnum.A.name()).ordinal());
+    }
+  }
+
+  public static class ProgramStaticMethod {
+
+    public static void main(String[] args) {
+      System.out.println(Lib.LibEnumStaticMethod.staticMethod());
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingClassStaticizerTest.java b/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingClassStaticizerTest.java
index 6fb5fd1..4f724d3 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingClassStaticizerTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingClassStaticizerTest.java
@@ -24,7 +24,7 @@
 
   private final TestParameters parameters;
   private final boolean enumValueOptimization;
-  private final KeepRule enumKeepRules;
+  private final EnumKeepRules enumKeepRules;
 
   @Parameterized.Parameters(name = "{0} valueOpt: {1} keep: {2}")
   public static List<Object[]> data() {
@@ -32,7 +32,7 @@
   }
 
   public EnumUnboxingClassStaticizerTest(
-      TestParameters parameters, boolean enumValueOptimization, KeepRule enumKeepRules) {
+      TestParameters parameters, boolean enumValueOptimization, EnumKeepRules enumKeepRules) {
     this.parameters = parameters;
     this.enumValueOptimization = enumValueOptimization;
     this.enumKeepRules = enumKeepRules;
@@ -44,7 +44,7 @@
         testForR8(parameters.getBackend())
             .addInnerClasses(EnumUnboxingClassStaticizerTest.class)
             .addKeepMainRule(TestClass.class)
-            .addKeepRules(enumKeepRules.getKeepRule())
+            .addKeepRules(enumKeepRules.getKeepRules())
             .enableNeverClassInliningAnnotations()
             .enableInliningAnnotations()
             .noMinification() // For assertions.
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingReturnNullTest.java b/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingReturnNullTest.java
index 2a1be5f..a0173b5 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingReturnNullTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingReturnNullTest.java
@@ -23,7 +23,7 @@
 
   private final TestParameters parameters;
   private final boolean enumValueOptimization;
-  private final KeepRule enumKeepRules;
+  private final EnumKeepRules enumKeepRules;
 
   @Parameterized.Parameters(name = "{0} valueOpt: {1} keep: {2}")
   public static List<Object[]> data() {
@@ -31,7 +31,7 @@
   }
 
   public EnumUnboxingReturnNullTest(
-      TestParameters parameters, boolean enumValueOptimization, KeepRule enumKeepRules) {
+      TestParameters parameters, boolean enumValueOptimization, EnumKeepRules enumKeepRules) {
     this.parameters = parameters;
     this.enumValueOptimization = enumValueOptimization;
     this.enumKeepRules = enumKeepRules;
@@ -43,7 +43,7 @@
     testForR8(parameters.getBackend())
         .addProgramClasses(classToTest, ENUM_CLASS)
         .addKeepMainRule(classToTest)
-        .addKeepRules(enumKeepRules.getKeepRule())
+        .addKeepRules(enumKeepRules.getKeepRules())
         .enableNeverClassInliningAnnotations()
         .enableInliningAnnotations()
         .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingSideEffectClInitTest.java b/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingSideEffectClInitTest.java
index 5f05a31..128466c 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingSideEffectClInitTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingSideEffectClInitTest.java
@@ -17,7 +17,7 @@
   private static final Class<?> ENUM_CLASS = MyEnum.class;
   private final TestParameters parameters;
   private final boolean enumValueOptimization;
-  private final KeepRule enumKeepRules;
+  private final EnumKeepRules enumKeepRules;
 
   @Parameters(name = "{0} valueOpt: {1} keep: {2}")
   public static List<Object[]> data() {
@@ -25,7 +25,7 @@
   }
 
   public EnumUnboxingSideEffectClInitTest(
-      TestParameters parameters, boolean enumValueOptimization, KeepRule enumKeepRules) {
+      TestParameters parameters, boolean enumValueOptimization, EnumKeepRules enumKeepRules) {
     this.parameters = parameters;
     this.enumValueOptimization = enumValueOptimization;
     this.enumKeepRules = enumKeepRules;
@@ -37,7 +37,7 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(EnumUnboxingSideEffectClInitTest.class)
         .addKeepMainRule(classToTest)
-        .addKeepRules(enumKeepRules.getKeepRule())
+        .addKeepRules(enumKeepRules.getKeepRules())
         .enableNeverClassInliningAnnotations()
         .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
         .allowDiagnosticInfoMessages()
@@ -47,7 +47,7 @@
             m -> {
               // The snap keep rule forces to keep the static MainEnum#e field, so the enum
               // cannot be unboxed anymore.
-              if (enumKeepRules.toString().equals("snap")) {
+              if (enumKeepRules.isSnap()) {
                 assertEnumIsBoxed(ENUM_CLASS, classToTest.getSimpleName(), m);
               } else {
                 assertEnumIsUnboxed(ENUM_CLASS, classToTest.getSimpleName(), m);
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingTestBase.java b/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingTestBase.java
index 11ab1ae..1f82de1 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingTestBase.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingTestBase.java
@@ -13,44 +13,41 @@
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.StringUtils;
-import com.google.common.collect.ImmutableList;
 import java.util.List;
 
 public class EnumUnboxingTestBase extends TestBase {
 
+  // Default keep rule present in Android Studio.
   private static final String KEEP_ENUM_STUDIO =
       "-keepclassmembers enum * {\n"
           + " public static **[] values();\n"
           + " public static ** valueOf(java.lang.String);\n"
           + "}";
+  // Default keep rule present in Snap.
   private static final String KEEP_ENUM_SNAP =
       "-keepclassmembers enum * {\n"
           + "<fields>;\n"
           + " public static **[] values();\n"
           + " public static ** valueOf(java.lang.String);\n"
           + "}";
-  private static final List<KeepRule> KEEP_ENUM =
-      ImmutableList.of(
-          new KeepRule("none", ""),
-          new KeepRule("studio", KEEP_ENUM_STUDIO),
-          new KeepRule("snap", KEEP_ENUM_SNAP));
 
-  public static class KeepRule {
-    private final String name;
-    private final String keepRule;
+  public enum EnumKeepRules {
+    NONE(""),
+    STUDIO(KEEP_ENUM_STUDIO),
+    SNAP(KEEP_ENUM_SNAP);
 
-    private KeepRule(String name, String keepRule) {
-      this.name = name;
-      this.keepRule = keepRule;
+    private final String keepRules;
+
+    public String getKeepRules() {
+      return keepRules;
     }
 
-    public String getKeepRule() {
-      return keepRule;
+    public boolean isSnap() {
+      return this == SNAP;
     }
 
-    @Override
-    public String toString() {
-      return name;
+    EnumKeepRules(String keepRules) {
+      this.keepRules = keepRules;
     }
   }
 
@@ -96,10 +93,10 @@
     return buildParameters(
         getTestParameters().withDexRuntimes().withAllApiLevels().build(),
         BooleanUtils.values(),
-        KEEP_ENUM);
+        getAllEnumKeepRules());
   }
 
-  static List<KeepRule> getAllEnumKeepRules() {
-    return KEEP_ENUM;
+  static EnumKeepRules[] getAllEnumKeepRules() {
+    return EnumKeepRules.values();
   }
 }
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/EqualsCompareToEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/EqualsCompareToEnumUnboxingTest.java
index de86bd7..2888ee4 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/EqualsCompareToEnumUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/EqualsCompareToEnumUnboxingTest.java
@@ -18,7 +18,7 @@
 
   private final TestParameters parameters;
   private final boolean enumValueOptimization;
-  private final KeepRule enumKeepRules;
+  private final EnumKeepRules enumKeepRules;
 
   @Parameters(name = "{0} valueOpt: {1} keep: {2}")
   public static List<Object[]> data() {
@@ -26,7 +26,7 @@
   }
 
   public EqualsCompareToEnumUnboxingTest(
-      TestParameters parameters, boolean enumValueOptimization, KeepRule enumKeepRules) {
+      TestParameters parameters, boolean enumValueOptimization, EnumKeepRules enumKeepRules) {
     this.parameters = parameters;
     this.enumValueOptimization = enumValueOptimization;
     this.enumKeepRules = enumKeepRules;
@@ -40,7 +40,7 @@
             .addInnerClasses(EqualsCompareToEnumUnboxingTest.class)
             .addKeepMainRule(EnumEqualscompareTo.class)
             .enableNeverClassInliningAnnotations()
-            .addKeepRules(enumKeepRules.getKeepRule())
+            .addKeepRules(enumKeepRules.getKeepRules())
             .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
             .allowDiagnosticInfoMessages()
             .setMinApi(parameters.getApiLevel())
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/FailingEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/FailingEnumUnboxingTest.java
index f828413..08ec7bd 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/FailingEnumUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/FailingEnumUnboxingTest.java
@@ -27,7 +27,7 @@
 
   private final TestParameters parameters;
   private final boolean enumValueOptimization;
-  private final KeepRule enumKeepRules;
+  private final EnumKeepRules enumKeepRules;
 
   @Parameters(name = "{0} valueOpt: {1} keep: {2}")
   public static List<Object[]> data() {
@@ -35,7 +35,7 @@
   }
 
   public FailingEnumUnboxingTest(
-      TestParameters parameters, boolean enumValueOptimization, KeepRule enumKeepRules) {
+      TestParameters parameters, boolean enumValueOptimization, EnumKeepRules enumKeepRules) {
     this.parameters = parameters;
     this.enumValueOptimization = enumValueOptimization;
     this.enumKeepRules = enumKeepRules;
@@ -51,7 +51,7 @@
     R8TestCompileResult compile =
         r8FullTestBuilder
             .enableNeverClassInliningAnnotations()
-            .addKeepRules(enumKeepRules.getKeepRule())
+            .addKeepRules(enumKeepRules.getKeepRules())
             .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
             .allowDiagnosticInfoMessages()
             .setMinApi(parameters.getApiLevel())
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/FailingMethodEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/FailingMethodEnumUnboxingTest.java
index 411a747..e42ac65 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/FailingMethodEnumUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/FailingMethodEnumUnboxingTest.java
@@ -35,7 +35,7 @@
 
   private final TestParameters parameters;
   private final boolean enumValueOptimization;
-  private final KeepRule enumKeepRules;
+  private final EnumKeepRules enumKeepRules;
 
   @Parameters(name = "{0} valueOpt: {1} keep: {2}")
   public static List<Object[]> data() {
@@ -43,7 +43,7 @@
   }
 
   public FailingMethodEnumUnboxingTest(
-      TestParameters parameters, boolean enumValueOptimization, KeepRule enumKeepRules) {
+      TestParameters parameters, boolean enumValueOptimization, EnumKeepRules enumKeepRules) {
     this.parameters = parameters;
     this.enumValueOptimization = enumValueOptimization;
     this.enumKeepRules = enumKeepRules;
@@ -55,7 +55,7 @@
         testForR8(parameters.getBackend())
             .addInnerClasses(FailingMethodEnumUnboxingTest.class)
             .addKeepMainRules(FAILURES)
-            .addKeepRules(enumKeepRules.getKeepRule())
+            .addKeepRules(enumKeepRules.getKeepRules())
             .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
             .allowDiagnosticInfoMessages()
             .enableInliningAnnotations()
@@ -71,7 +71,7 @@
                       assertEnumIsBoxed(
                           failure.getDeclaredClasses()[0], failure.getSimpleName(), m))
               .run(parameters.getRuntime(), failure);
-      if (failure == EnumSetTest.class && enumKeepRules.getKeepRule().isEmpty()) {
+      if (failure == EnumSetTest.class && enumKeepRules.getKeepRules().isEmpty()) {
         // EnumSet and EnumMap cannot be used without the enumKeepRules.
         run.assertFailure();
       } else {
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/FieldPutEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/FieldPutEnumUnboxingTest.java
index 8ab4bcf..4d222d5 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/FieldPutEnumUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/FieldPutEnumUnboxingTest.java
@@ -25,7 +25,7 @@
 
   private final TestParameters parameters;
   private final boolean enumValueOptimization;
-  private final KeepRule enumKeepRules;
+  private final EnumKeepRules enumKeepRules;
 
   @Parameters(name = "{0} valueOpt: {1} keep: {2}")
   public static List<Object[]> data() {
@@ -33,7 +33,7 @@
   }
 
   public FieldPutEnumUnboxingTest(
-      TestParameters parameters, boolean enumValueOptimization, KeepRule enumKeepRules) {
+      TestParameters parameters, boolean enumValueOptimization, EnumKeepRules enumKeepRules) {
     this.parameters = parameters;
     this.enumValueOptimization = enumValueOptimization;
     this.enumKeepRules = enumKeepRules;
@@ -45,7 +45,7 @@
         testForR8(parameters.getBackend())
             .addInnerClasses(FieldPutEnumUnboxingTest.class)
             .addKeepMainRules(INPUTS)
-            .addKeepRules(enumKeepRules.getKeepRule())
+            .addKeepRules(enumKeepRules.getKeepRules())
             .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
             .allowDiagnosticInfoMessages()
             .enableInliningAnnotations()
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/InterfaceEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/InterfaceEnumUnboxingTest.java
index 5dcf75d..fba8006 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/InterfaceEnumUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/InterfaceEnumUnboxingTest.java
@@ -33,7 +33,7 @@
 
   private final TestParameters parameters;
   private final boolean enumValueOptimization;
-  private final KeepRule enumKeepRules;
+  private final EnumKeepRules enumKeepRules;
 
   @Parameters(name = "{0} valueOpt: {1} keep: {2}")
   public static List<Object[]> data() {
@@ -41,7 +41,7 @@
   }
 
   public InterfaceEnumUnboxingTest(
-      TestParameters parameters, boolean enumValueOptimization, KeepRule enumKeepRules) {
+      TestParameters parameters, boolean enumValueOptimization, EnumKeepRules enumKeepRules) {
     this.parameters = parameters;
     this.enumValueOptimization = enumValueOptimization;
     this.enumKeepRules = enumKeepRules;
@@ -58,7 +58,7 @@
             .enableMergeAnnotations()
             .enableInliningAnnotations()
             .enableNeverClassInliningAnnotations()
-            .addKeepRules(enumKeepRules.getKeepRule())
+            .addKeepRules(enumKeepRules.getKeepRules())
             .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
             .allowDiagnosticInfoMessages()
             .setMinApi(parameters.getApiLevel())
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/JavaCGeneratedMethodTest.java b/src/test/java/com/android/tools/r8/enumunboxing/JavaCGeneratedMethodTest.java
index 728a346..5d4501f 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/JavaCGeneratedMethodTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/JavaCGeneratedMethodTest.java
@@ -19,7 +19,7 @@
 
   private final TestParameters parameters;
   private final boolean enumValueOptimization;
-  private final KeepRule enumKeepRules;
+  private final EnumKeepRules enumKeepRules;
 
   @Parameterized.Parameters(name = "{0} valueOpt: {1} keep: {2}")
   public static List<Object[]> data() {
@@ -27,7 +27,7 @@
   }
 
   public JavaCGeneratedMethodTest(
-      TestParameters parameters, boolean enumValueOptimization, KeepRule enumKeepRules) {
+      TestParameters parameters, boolean enumValueOptimization, EnumKeepRules enumKeepRules) {
     this.parameters = parameters;
     this.enumValueOptimization = enumValueOptimization;
     this.enumKeepRules = enumKeepRules;
@@ -40,7 +40,7 @@
         testForR8(parameters.getBackend())
             .addProgramClasses(classToTest, ENUM_CLASS)
             .addKeepMainRule(classToTest)
-            .addKeepRules(enumKeepRules.getKeepRule())
+            .addKeepRules(enumKeepRules.getKeepRules())
             .enableNeverClassInliningAnnotations()
             .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
             .addOptionsModification(
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/NullOutValueInvokeEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/NullOutValueInvokeEnumUnboxingTest.java
index c65427a..8e15ad1 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/NullOutValueInvokeEnumUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/NullOutValueInvokeEnumUnboxingTest.java
@@ -21,7 +21,7 @@
 
   private final TestParameters parameters;
   private final boolean enumValueOptimization;
-  private final KeepRule enumKeepRules;
+  private final EnumKeepRules enumKeepRules;
 
   @Parameters(name = "{0} valueOpt: {1} keep: {2}")
   public static List<Object[]> data() {
@@ -29,7 +29,7 @@
   }
 
   public NullOutValueInvokeEnumUnboxingTest(
-      TestParameters parameters, boolean enumValueOptimization, KeepRule enumKeepRules) {
+      TestParameters parameters, boolean enumValueOptimization, EnumKeepRules enumKeepRules) {
     this.parameters = parameters;
     this.enumValueOptimization = enumValueOptimization;
     this.enumKeepRules = enumKeepRules;
@@ -42,7 +42,7 @@
         testForR8(parameters.getBackend())
             .addProgramClasses(classToTest, ENUM_CLASS)
             .addKeepMainRule(classToTest)
-            .addKeepRules(enumKeepRules.getKeepRule())
+            .addKeepRules(enumKeepRules.getKeepRules())
             .enableNeverClassInliningAnnotations()
             .enableInliningAnnotations()
             .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/OrdinalHashCodeEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/OrdinalHashCodeEnumUnboxingTest.java
index 8bd41c3..79f9176 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/OrdinalHashCodeEnumUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/OrdinalHashCodeEnumUnboxingTest.java
@@ -20,7 +20,7 @@
 
   private final TestParameters parameters;
   private final boolean enumValueOptimization;
-  private final KeepRule enumKeepRules;
+  private final EnumKeepRules enumKeepRules;
 
   @Parameters(name = "{0} valueOpt: {1} keep: {2}")
   public static List<Object[]> data() {
@@ -28,7 +28,7 @@
   }
 
   public OrdinalHashCodeEnumUnboxingTest(
-      TestParameters parameters, boolean enumValueOptimization, KeepRule enumKeepRules) {
+      TestParameters parameters, boolean enumValueOptimization, EnumKeepRules enumKeepRules) {
     this.parameters = parameters;
     this.enumValueOptimization = enumValueOptimization;
     this.enumKeepRules = enumKeepRules;
@@ -41,7 +41,7 @@
         testForR8(parameters.getBackend())
             .addProgramClasses(classToTest, ENUM_CLASS)
             .addKeepMainRule(classToTest)
-            .addKeepRules(enumKeepRules.getKeepRule())
+            .addKeepRules(enumKeepRules.getKeepRules())
             .enableNeverClassInliningAnnotations()
             .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
             .allowDiagnosticInfoMessages()
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/PhiEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/PhiEnumUnboxingTest.java
index ba217f6..8aa2d5e 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/PhiEnumUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/PhiEnumUnboxingTest.java
@@ -21,7 +21,7 @@
 
   private final TestParameters parameters;
   private final boolean enumValueOptimization;
-  private final KeepRule enumKeepRules;
+  private final EnumKeepRules enumKeepRules;
 
   @Parameters(name = "{0} valueOpt: {1} keep: {2}")
   public static List<Object[]> data() {
@@ -29,7 +29,7 @@
   }
 
   public PhiEnumUnboxingTest(
-      TestParameters parameters, boolean enumValueOptimization, KeepRule enumKeepRules) {
+      TestParameters parameters, boolean enumValueOptimization, EnumKeepRules enumKeepRules) {
     this.parameters = parameters;
     this.enumValueOptimization = enumValueOptimization;
     this.enumKeepRules = enumKeepRules;
@@ -42,7 +42,7 @@
         testForR8(parameters.getBackend())
             .addProgramClasses(classToTest, ENUM_CLASS)
             .addKeepMainRule(classToTest)
-            .addKeepRules(enumKeepRules.getKeepRule())
+            .addKeepRules(enumKeepRules.getKeepRules())
             .enableInliningAnnotations()
             .enableNeverClassInliningAnnotations()
             .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/PinnedEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/PinnedEnumUnboxingTest.java
index fff500b..1c34891 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/PinnedEnumUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/PinnedEnumUnboxingTest.java
@@ -20,7 +20,7 @@
 
   private final TestParameters parameters;
   private final boolean enumValueOptimization;
-  private final KeepRule enumKeepRules;
+  private final EnumKeepRules enumKeepRules;
 
   @Parameters(name = "{0} valueOpt: {1} keep: {2}")
   public static List<Object[]> data() {
@@ -28,7 +28,7 @@
   }
 
   public PinnedEnumUnboxingTest(
-      TestParameters parameters, boolean enumValueOptimization, KeepRule enumKeepRules) {
+      TestParameters parameters, boolean enumValueOptimization, EnumKeepRules enumKeepRules) {
     this.parameters = parameters;
     this.enumValueOptimization = enumValueOptimization;
     this.enumKeepRules = enumKeepRules;
@@ -42,7 +42,7 @@
             .addKeepMainRules(BOXED)
             .addKeepClassRules(MainWithKeptEnum.MyEnum.class)
             .addKeepMethodRules(MainWithKeptEnumArray.class, "keptMethod()")
-            .addKeepRules(enumKeepRules.getKeepRule())
+            .addKeepRules(enumKeepRules.getKeepRules())
             .enableNeverClassInliningAnnotations()
             .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
             .allowDiagnosticInfoMessages()
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/StaticMethodsEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/StaticMethodsEnumUnboxingTest.java
index 0d01ffe..390fe18 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/StaticMethodsEnumUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/StaticMethodsEnumUnboxingTest.java
@@ -19,7 +19,7 @@
 
   private final TestParameters parameters;
   private final boolean enumValueOptimization;
-  private final KeepRule enumKeepRules;
+  private final EnumKeepRules enumKeepRules;
 
   @Parameters(name = "{0} valueOpt: {1} keep: {2}")
   public static List<Object[]> data() {
@@ -27,7 +27,7 @@
   }
 
   public StaticMethodsEnumUnboxingTest(
-      TestParameters parameters, boolean enumValueOptimization, KeepRule enumKeepRules) {
+      TestParameters parameters, boolean enumValueOptimization, EnumKeepRules enumKeepRules) {
     this.parameters = parameters;
     this.enumValueOptimization = enumValueOptimization;
     this.enumKeepRules = enumKeepRules;
@@ -40,7 +40,7 @@
         testForR8(parameters.getBackend())
             .addInnerClasses(StaticMethodsEnumUnboxingTest.class)
             .addKeepMainRule(classToTest)
-            .addKeepRules(enumKeepRules.getKeepRule())
+            .addKeepRules(enumKeepRules.getKeepRules())
             .enableNeverClassInliningAnnotations()
             .enableInliningAnnotations()
             .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/SwitchEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/SwitchEnumUnboxingTest.java
index dc2e217..76c06cb 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/SwitchEnumUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/SwitchEnumUnboxingTest.java
@@ -21,7 +21,7 @@
 
   private final TestParameters parameters;
   private final boolean enumValueOptimization;
-  private final KeepRule enumKeepRules;
+  private final EnumKeepRules enumKeepRules;
 
   @Parameters(name = "{0} valueOpt: {1} keep: {2}")
   public static List<Object[]> data() {
@@ -29,7 +29,7 @@
   }
 
   public SwitchEnumUnboxingTest(
-      TestParameters parameters, boolean enumValueOptimization, KeepRule enumKeepRules) {
+      TestParameters parameters, boolean enumValueOptimization, EnumKeepRules enumKeepRules) {
     this.parameters = parameters;
     this.enumValueOptimization = enumValueOptimization;
     this.enumKeepRules = enumKeepRules;
@@ -42,7 +42,7 @@
         testForR8(parameters.getBackend())
             .addInnerClasses(SwitchEnumUnboxingTest.class)
             .addKeepMainRule(classToTest)
-            .addKeepRules(enumKeepRules.getKeepRule())
+            .addKeepRules(enumKeepRules.getKeepRules())
             .enableInliningAnnotations()
             .enableNeverClassInliningAnnotations()
             .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/ToStringEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/ToStringEnumUnboxingTest.java
index b4f97ab..50a4d58 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/ToStringEnumUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/ToStringEnumUnboxingTest.java
@@ -18,7 +18,7 @@
 
   private final TestParameters parameters;
   private final boolean enumValueOptimization;
-  private final KeepRule enumKeepRules;
+  private final EnumKeepRules enumKeepRules;
 
   @Parameters(name = "{0} valueOpt: {1} keep: {2}")
   public static List<Object[]> data() {
@@ -26,7 +26,7 @@
   }
 
   public ToStringEnumUnboxingTest(
-      TestParameters parameters, boolean enumValueOptimization, KeepRule enumKeepRules) {
+      TestParameters parameters, boolean enumValueOptimization, EnumKeepRules enumKeepRules) {
     this.parameters = parameters;
     this.enumValueOptimization = enumValueOptimization;
     this.enumKeepRules = enumKeepRules;
@@ -40,7 +40,7 @@
             .addInnerClasses(ToStringEnumUnboxingTest.class)
             .addKeepMainRule(EnumNameToString.class)
             .enableNeverClassInliningAnnotations()
-            .addKeepRules(enumKeepRules.getKeepRule())
+            .addKeepRules(enumKeepRules.getKeepRules())
             .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
             .allowDiagnosticInfoMessages()
             .setMinApi(parameters.getApiLevel())
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/ToStringOverrideEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/ToStringOverrideEnumUnboxingTest.java
index b7643a0..8b9b546 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/ToStringOverrideEnumUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/ToStringOverrideEnumUnboxingTest.java
@@ -18,7 +18,7 @@
 
   private final TestParameters parameters;
   private final boolean enumValueOptimization;
-  private final KeepRule enumKeepRules;
+  private final EnumKeepRules enumKeepRules;
 
   @Parameters(name = "{0} valueOpt: {1} keep: {2}")
   public static List<Object[]> data() {
@@ -26,7 +26,7 @@
   }
 
   public ToStringOverrideEnumUnboxingTest(
-      TestParameters parameters, boolean enumValueOptimization, KeepRule enumKeepRules) {
+      TestParameters parameters, boolean enumValueOptimization, EnumKeepRules enumKeepRules) {
     this.parameters = parameters;
     this.enumValueOptimization = enumValueOptimization;
     this.enumKeepRules = enumKeepRules;
@@ -40,7 +40,7 @@
             .addInnerClasses(ToStringOverrideEnumUnboxingTest.class)
             .addKeepMainRule(EnumNameToString.class)
             .enableNeverClassInliningAnnotations()
-            .addKeepRules(enumKeepRules.getKeepRule())
+            .addKeepRules(enumKeepRules.getKeepRules())
             .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
             .allowDiagnosticInfoMessages()
             .setMinApi(parameters.getApiLevel())
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/ValueOfEnumUnboxingFailureTest.java b/src/test/java/com/android/tools/r8/enumunboxing/ValueOfEnumUnboxingFailureTest.java
index e2dcc4c..1da8c62 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/ValueOfEnumUnboxingFailureTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/ValueOfEnumUnboxingFailureTest.java
@@ -16,7 +16,7 @@
 
   private final TestParameters parameters;
   private final boolean enumValueOptimization;
-  private final KeepRule enumKeepRules;
+  private final EnumKeepRules enumKeepRules;
 
   @Parameterized.Parameters(name = "{0} valueOpt: {1} keep: {2}")
   public static List<Object[]> data() {
@@ -24,7 +24,7 @@
   }
 
   public ValueOfEnumUnboxingFailureTest(
-      TestParameters parameters, boolean enumValueOptimization, KeepRule enumKeepRules) {
+      TestParameters parameters, boolean enumValueOptimization, EnumKeepRules enumKeepRules) {
     this.parameters = parameters;
     this.enumValueOptimization = enumValueOptimization;
     this.enumKeepRules = enumKeepRules;
@@ -37,7 +37,7 @@
         .addInnerClasses(ValueOfEnumUnboxingFailureTest.class)
         .addKeepMainRule(success)
         .enableNeverClassInliningAnnotations()
-        .addKeepRules(enumKeepRules.getKeepRule())
+        .addKeepRules(enumKeepRules.getKeepRules())
         .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
         .allowDiagnosticInfoMessages()
         .setMinApi(parameters.getApiLevel())
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/ValueOfEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/ValueOfEnumUnboxingTest.java
index 37f31c9..23293f5 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/ValueOfEnumUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/ValueOfEnumUnboxingTest.java
@@ -23,7 +23,7 @@
 
   private final TestParameters parameters;
   private final boolean enumValueOptimization;
-  private final KeepRule enumKeepRules;
+  private final EnumKeepRules enumKeepRules;
 
   @Parameters(name = "{0} valueOpt: {1} keep: {2}")
   public static List<Object[]> data() {
@@ -31,7 +31,7 @@
   }
 
   public ValueOfEnumUnboxingTest(
-      TestParameters parameters, boolean enumValueOptimization, KeepRule enumKeepRules) {
+      TestParameters parameters, boolean enumValueOptimization, EnumKeepRules enumKeepRules) {
     this.parameters = parameters;
     this.enumValueOptimization = enumValueOptimization;
     this.enumKeepRules = enumKeepRules;
@@ -44,7 +44,7 @@
             .addInnerClasses(ValueOfEnumUnboxingTest.class)
             .addKeepMainRules(SUCCESSES)
             .enableNeverClassInliningAnnotations()
-            .addKeepRules(enumKeepRules.getKeepRule())
+            .addKeepRules(enumKeepRules.getKeepRules())
             .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
             .allowDiagnosticInfoMessages()
             .setMinApi(parameters.getApiLevel())
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/VirtualMethodsEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/VirtualMethodsEnumUnboxingTest.java
index 6496c7a..6323e8b 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/VirtualMethodsEnumUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/VirtualMethodsEnumUnboxingTest.java
@@ -19,7 +19,7 @@
 
   private final TestParameters parameters;
   private final boolean enumValueOptimization;
-  private final KeepRule enumKeepRules;
+  private final EnumKeepRules enumKeepRules;
 
   @Parameters(name = "{0} valueOpt: {1} keep: {2}")
   public static List<Object[]> data() {
@@ -27,7 +27,7 @@
   }
 
   public VirtualMethodsEnumUnboxingTest(
-      TestParameters parameters, boolean enumValueOptimization, KeepRule enumKeepRules) {
+      TestParameters parameters, boolean enumValueOptimization, EnumKeepRules enumKeepRules) {
     this.parameters = parameters;
     this.enumValueOptimization = enumValueOptimization;
     this.enumKeepRules = enumKeepRules;
@@ -40,7 +40,7 @@
         testForR8(parameters.getBackend())
             .addInnerClasses(VirtualMethodsEnumUnboxingTest.class)
             .addKeepMainRule(classToTest)
-            .addKeepRules(enumKeepRules.getKeepRule())
+            .addKeepRules(enumKeepRules.getKeepRules())
             .enableNeverClassInliningAnnotations()
             .enableInliningAnnotations()
             .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedArgumentsCollisionMappingTest.java b/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedArgumentsCollisionMappingTest.java
index 5e7a17d..30f5c93 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedArgumentsCollisionMappingTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedArgumentsCollisionMappingTest.java
@@ -9,11 +9,11 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 
+import com.android.tools.r8.CompilationMode;
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.R8TestRunResult;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -28,20 +28,25 @@
 public class UnusedArgumentsCollisionMappingTest extends TestBase {
 
   private final TestParameters parameters;
+  private final CompilationMode compilationMode;
 
-  @Parameters(name = "{0}")
-  public static TestParametersCollection data() {
-    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  @Parameters(name = "{0}, compilation mode: {1}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withAllRuntimesAndApiLevels().build(), CompilationMode.values());
   }
 
-  public UnusedArgumentsCollisionMappingTest(TestParameters parameters) {
+  public UnusedArgumentsCollisionMappingTest(
+      TestParameters parameters, CompilationMode compilationMode) {
     this.parameters = parameters;
+    this.compilationMode = compilationMode;
   }
 
   @Test
   public void testR8() throws Exception {
     R8TestRunResult runResult =
         testForR8(parameters.getBackend())
+            .setMode(compilationMode)
             .addProgramClasses(Main.class)
             .setMinApi(parameters.getApiLevel())
             .addKeepMainRule(Main.class)
@@ -50,14 +55,13 @@
             .run(parameters.getRuntime(), Main.class)
             .assertSuccessWithOutputLines("test with unused", "test with used: foo")
             .inspect(this::verifyArgumentsRemoved);
-    // TODO(b/140851070): Duplicate entries for different methods.
     assertEquals(
-        2,
+        1,
         StringUtils.splitLines(runResult.proguardMap()).stream()
             .filter(line -> line.contains("void test(java.lang.String,java.lang.String)"))
             .count());
     assertEquals(
-        0,
+        1,
         StringUtils.splitLines(runResult.proguardMap()).stream()
             .filter(line -> line.contains("void test(java.lang.String)"))
             .count());
diff --git a/src/test/java/com/android/tools/r8/naming/ProguardMapReaderArgumentsTest.java b/src/test/java/com/android/tools/r8/naming/ProguardMapReaderArgumentsTest.java
new file mode 100644
index 0000000..8b68bd2
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/ProguardMapReaderArgumentsTest.java
@@ -0,0 +1,226 @@
+// Copyright (c) 2019, 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 com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticPosition;
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticType;
+import static com.android.tools.r8.PositionMatcher.positionLine;
+import static junit.framework.TestCase.assertEquals;
+import static junit.framework.TestCase.assertFalse;
+import static junit.framework.TestCase.assertNotNull;
+import static junit.framework.TestCase.assertTrue;
+import static org.hamcrest.CoreMatchers.allOf;
+import static org.hamcrest.core.StringContains.containsString;
+
+import com.android.tools.r8.TestDiagnosticMessagesImpl;
+import com.android.tools.r8.naming.MemberNaming.MethodSignature;
+import com.android.tools.r8.naming.mappinginformation.MappingInformationDiagnostics;
+import com.android.tools.r8.utils.Reporter;
+import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableList;
+import java.io.IOException;
+import java.util.List;
+import org.junit.Before;
+import org.junit.Test;
+
+public class ProguardMapReaderArgumentsTest {
+
+  private Reporter reporter;
+  private TestDiagnosticMessagesImpl testDiagnosticMessages;
+
+  @Before
+  public void setUp() {
+    testDiagnosticMessages = new TestDiagnosticMessagesImpl();
+    reporter = new Reporter(testDiagnosticMessages);
+  }
+
+  @Test
+  public void testMethodCanParseMemberComments() throws IOException {
+    String mapWithArgumentRemovalInformation =
+        StringUtils.join(
+            "\n",
+            "android.constraint.Placeholder -> a.b.b.f:",
+            "# Just a comment", // Regular comment
+            "    int mContentId -> b",
+            "# {}", // Valid JSON with no information
+            "    147:161:void updatePreLayout(android.constraint.Layout) -> a",
+            "# {foo:bar}}}", // Not valid JSON
+            "    194:204:void updatePostMeasure(android.constraint.Layout) -> b",
+            "# { 'id': 'bar' }", // Valid json but no handler for bar.
+            "    194:204:void updatePostMeasure(android.constraint.Layout) -> c");
+    ClassNameMapper cnm =
+        ClassNameMapper.mapperFromString(mapWithArgumentRemovalInformation, reporter);
+    ClassNamingForNameMapper classNaming = cnm.getClassNaming("a.b.b.f");
+    assertNotNull(classNaming);
+    testDiagnosticMessages.assertOnlyInfos();
+    testDiagnosticMessages
+        .assertInfosMatch(
+            ImmutableList.of(
+                allOf(
+                    diagnosticMessage(containsString("Could not locate 'id'")),
+                    diagnosticPosition(positionLine(4))),
+                allOf(
+                    diagnosticMessage(containsString("Not valid JSON")),
+                    diagnosticPosition(positionLine(6))),
+                allOf(
+                    diagnosticMessage(containsString("Could not find a handler for bar")),
+                    diagnosticPosition(positionLine(8)))))
+        .assertAllInfosMatch(diagnosticType(MappingInformationDiagnostics.class));
+  }
+
+  @Test
+  public void testMethodWillReportWhenParsingArgumentsChangedMemberComments() throws IOException {
+    String mapWithArgumentRemovalInformation =
+        StringUtils.join(
+                "\n",
+                "android.constraint.Placeholder -> a.b.b.f:",
+                "# Just a comment", // Regular comment
+                "    int mContentId -> b",
+                // Valid JSON but missing signature
+                "# { 'id': 'methodSignatureChanged', "
+                    + "'signature': ['void', 'updatePreLayout', 'android.constraint.Layout'],"
+                    + "'returnType': [], 'receiver': true, 'params': []}",
+                "    147:161:void updatePreLayout(android.constraint.Layout) -> a",
+                // Valid JSON but not an argumentsChanged object
+                "# { 'id': 'methodSignatureChanged', "
+                    + "'signature': ['void', 'updatePreLayout', 'android.constraint.Layout'],"
+                    + "'returnType': [], 'receiver': true, 'params': []}",
+                "    147:161:void updatePreLayout(android.constraint.Layout) -> a",
+                // Valid JSON but not an arguments_changed object.
+                "# { 'id': 'methodSignatureChanged', "
+                    + "'signature': ['void','updatePreLayout','android.constraint.Layout'],"
+                    + "'returnType': 'foo', 'receiver': 1, 'params': 'foo' }",
+                "    194:204:void updatePostMeasure(android.constraint.Layout) -> a")
+            .replace("'", "\"");
+    ClassNameMapper cnm =
+        ClassNameMapper.mapperFromString(mapWithArgumentRemovalInformation, reporter);
+    ClassNamingForNameMapper classNaming = cnm.getClassNaming("a.b.b.f");
+    assertNotNull(classNaming);
+    testDiagnosticMessages.assertOnlyInfos();
+    testDiagnosticMessages
+        .assertInfosMatch(
+            ImmutableList.of(
+                allOf(
+                    diagnosticMessage(containsString("Could not decode")),
+                    diagnosticPosition(positionLine(4))),
+                allOf(
+                    diagnosticMessage(containsString("Could not decode")),
+                    diagnosticPosition(positionLine(6))),
+                allOf(
+                    diagnosticMessage(containsString("Could not decode")),
+                    diagnosticPosition(positionLine(8)))))
+        .assertAllInfosMatch(diagnosticType(MappingInformationDiagnostics.class));
+  }
+
+  @Test
+  public void testMethodCanParseArgumentRemoval() throws IOException {
+    String mapWithArgumentRemovalInformation =
+        StringUtils.lines(
+            "android.constraint.Placeholder -> a.b.b.f:",
+            "    int mContentId -> b",
+            "# { 'id': 'methodSignatureChanged',"
+                + "'signature': ["
+                + "'void','updatePreLayout','android.constraint.Layout','String','int'],"
+                + " 'returnType': 'void', 'receiver': true, 'params':[[1],[2]]}",
+            "    147:161:void updatePreLayout(android.constraint.Layout,String,int) -> a",
+            "# {'id':'methodSignatureChanged',"
+                + "'signature': ["
+                + "'void','updatePreMeasure','android.constraint.Layout','String','int'],"
+                + "'returnType': 'void', 'receiver': true, 'params':[[3]]}",
+            "    162:173:void updatePreMeasure(android.constraint.Layout,String,int) -> a",
+            "    194:204:void updatePostMeasure(android.constraint.Layout,String,int) -> a");
+    ClassNameMapper cnm = ClassNameMapper.mapperFromString(mapWithArgumentRemovalInformation);
+    ClassNamingForNameMapper classNaming = cnm.getClassNaming("a.b.b.f");
+    assertNotNull(classNaming);
+
+    List<MemberNaming> members = classNaming.lookupByOriginalName("mContentId");
+    assertFalse(members.isEmpty());
+    MemberNaming fieldContentId = members.get(0);
+    assertNotNull(fieldContentId);
+    assertTrue(!fieldContentId.isMethodNaming());
+
+    members = classNaming.lookupByOriginalName("updatePreLayout");
+    assertFalse(members.isEmpty());
+    MemberNaming updatePreLayout = members.get(0);
+    assertNotNull(updatePreLayout);
+    assertTrue(updatePreLayout.isMethodNaming());
+    MethodSignature renamedPreLayout = (MethodSignature) updatePreLayout.getRenamedSignature();
+    assertEquals(1, renamedPreLayout.parameters.length);
+    assertEquals("int", renamedPreLayout.parameters[0]);
+
+    members = classNaming.lookupByOriginalName("updatePreMeasure");
+    assertFalse(members.isEmpty());
+    MemberNaming updatePreMeasure = members.get(0);
+    assertNotNull(updatePreMeasure);
+    assertTrue(updatePreMeasure.isMethodNaming());
+    MethodSignature renamedPreMeasure = (MethodSignature) updatePreMeasure.getRenamedSignature();
+    assertEquals(2, renamedPreMeasure.parameters.length);
+    assertEquals("android.constraint.Layout", renamedPreMeasure.parameters[0]);
+    assertEquals("String", renamedPreMeasure.parameters[1]);
+
+    members = classNaming.lookupByOriginalName("updatePostMeasure");
+    assertFalse(members.isEmpty());
+    MemberNaming updatePostMeasure = members.get(0);
+    assertNotNull(updatePostMeasure);
+    assertTrue(updatePostMeasure.isMethodNaming());
+    MethodSignature renamedPostMeasure = (MethodSignature) updatePostMeasure.getRenamedSignature();
+    assertEquals(3, renamedPostMeasure.parameters.length);
+  }
+
+  @Test
+  public void testMethodCanParseArgumentChanged() throws IOException {
+    String mapWithArgumentRemovalInformation =
+        StringUtils.join(
+            "\n",
+            "android.constraint.Placeholder -> a.b.b.f:",
+            "# {'id':'methodSignatureChanged',"
+                + "'signature':["
+                + "'void','updatePreLayout','android.constraint.Layout','String','float'],"
+                + "'returnType': 'void',"
+                + "'receiver': true,"
+                + "'params':[[1,int],[2,Foo]]}",
+            "# {'id':'methodSignatureChanged',"
+                + "'signature':["
+                + "'void','updatePreMeasure','android.constraint.Layout','String','int'],"
+                + "'returnType': 'void', "
+                + "'receiver': true, "
+                + "'params':[[2,com.baz.Bar],[3]]}",
+            "  147:161:void updatePreLayout(android.constraint.Layout,String,float) -> a",
+            "  162:173:void updatePreMeasure(android.constraint.Layout,String,int) -> a",
+            "  194:204:void updatePostMeasure(android.constraint.Layout,String,int) -> a");
+    ClassNameMapper cnm = ClassNameMapper.mapperFromString(mapWithArgumentRemovalInformation);
+    ClassNamingForNameMapper classNaming = cnm.getClassNaming("a.b.b.f");
+    assertNotNull(classNaming);
+
+    List<MemberNaming> members = classNaming.lookupByOriginalName("updatePreLayout");
+    assertFalse(members.isEmpty());
+    MemberNaming updatePreLayout = members.get(0);
+    assertNotNull(updatePreLayout);
+    assertTrue(updatePreLayout.isMethodNaming());
+    MethodSignature renamedPreLayout = (MethodSignature) updatePreLayout.getRenamedSignature();
+    assertEquals(3, renamedPreLayout.parameters.length);
+    assertEquals("int", renamedPreLayout.parameters[0]);
+    assertEquals("Foo", renamedPreLayout.parameters[1]);
+    assertEquals("float", renamedPreLayout.parameters[2]);
+
+    members = classNaming.lookupByOriginalName("updatePreMeasure");
+    assertFalse(members.isEmpty());
+    MemberNaming updatePreMeasure = members.get(0);
+    assertNotNull(updatePreMeasure);
+    assertTrue(updatePreMeasure.isMethodNaming());
+    MethodSignature renamedPreMeasure = (MethodSignature) updatePreMeasure.getRenamedSignature();
+    assertEquals(2, renamedPreMeasure.parameters.length);
+    assertEquals("android.constraint.Layout", renamedPreMeasure.parameters[0]);
+    assertEquals("com.baz.Bar", renamedPreMeasure.parameters[1]);
+
+    members = classNaming.lookupByOriginalName("updatePostMeasure");
+    assertFalse(members.isEmpty());
+    MemberNaming updatePostMeasure = members.get(0);
+    assertNotNull(updatePostMeasure);
+    assertTrue(updatePostMeasure.isMethodNaming());
+    MethodSignature renamedPostMeasure = (MethodSignature) updatePostMeasure.getRenamedSignature();
+    assertEquals(3, renamedPostMeasure.parameters.length);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/retrace/StackTrace.java b/src/test/java/com/android/tools/r8/naming/retrace/StackTrace.java
index 2d5e31b..d2b2aba 100644
--- a/src/test/java/com/android/tools/r8/naming/retrace/StackTrace.java
+++ b/src/test/java/com/android/tools/r8/naming/retrace/StackTrace.java
@@ -280,6 +280,10 @@
   }
 
   public StackTrace retrace(String map) {
+    return retrace(map, null);
+  }
+
+  public StackTrace retrace(String map, String regularExpression) {
     class Box {
       List<String> result;
     }
@@ -291,6 +295,7 @@
                 stackTraceLines.stream()
                     .map(line -> line.originalLine)
                     .collect(Collectors.toList()))
+            .setRegularExpression(regularExpression)
             .setRetracedStackTraceConsumer(retraced -> box.result = retraced)
             .build());
     // Keep the original stderr in the retraced stacktrace.
diff --git a/src/test/java/com/android/tools/r8/regress/Regress160394262Test.java b/src/test/java/com/android/tools/r8/regress/Regress160394262Test.java
new file mode 100644
index 0000000..0c3206c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/regress/Regress160394262Test.java
@@ -0,0 +1,176 @@
+// 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;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class Regress160394262Test extends TestBase {
+
+  static final String EXPECTED = StringUtils.lines("1;2;3");
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimes().withAllApiLevels().build();
+  }
+
+  public Regress160394262Test(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testReference() throws Exception {
+    testForRuntime(parameters)
+        .addInnerClasses(Regress160394262Test.class)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .allowAccessModification()
+        .addKeepMainRule(TestClass.class)
+        .addInnerClasses(Regress160394262Test.class)
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED)
+        .inspect(this::checkJoinerIsClassInlined);
+  }
+
+  private void checkJoinerIsClassInlined(CodeInspector inspector) {
+    // TODO(b/160640028): When compiling to DEX a trivial phi remains in the inline code preventing
+    //  class inlining of Joiner and the anonymous Joiner subclass.
+    if (parameters.isDexRuntime()) {
+      assertThat(inspector.clazz(Joiner.class), isPresent());
+      assertThat(inspector.clazz(Joiner.class.getTypeName() + "$1"), isPresent());
+      return;
+    }
+    assertThat(inspector.clazz(Joiner.class), not(isPresent()));
+    assertThat(inspector.clazz(Joiner.class.getTypeName() + "$1"), not(isPresent()));
+  }
+
+  static class TestClass {
+
+    public void foo(List<?> items) {
+      System.out.println(Joiner.on(";").skipNulls().join(items));
+    }
+
+    public static void main(String[] args) {
+      new TestClass().foo(Arrays.asList("1", 2, 3l, null));
+    }
+  }
+
+  // Minimized copy of com.google.common.base.Preconditions
+  static class Preconditions {
+
+    public static <T> T checkNotNull(T reference) {
+      if (reference == null) {
+        throw new NullPointerException();
+      } else {
+        return reference;
+      }
+    }
+
+    public static <T> T checkNotNull(T reference, Object errorMessage) {
+      if (reference == null) {
+        throw new NullPointerException(String.valueOf(errorMessage));
+      } else {
+        return reference;
+      }
+    }
+  }
+
+  // Minimized copy of com.google.common.base.Joiner
+  static class Joiner {
+
+    private final String separator;
+
+    public static Joiner on(String separator) {
+      return new Joiner(separator);
+    }
+
+    private Joiner(String separator) {
+      this.separator = Preconditions.checkNotNull(separator);
+    }
+
+    private Joiner(Joiner prototype) {
+      this.separator = prototype.separator;
+    }
+
+    public <A extends Appendable> A appendTo(A appendable, Iterator<?> parts) throws IOException {
+      Preconditions.checkNotNull(appendable);
+      if (parts.hasNext()) {
+        appendable.append(this.toString(parts.next()));
+        while (parts.hasNext()) {
+          appendable.append(this.separator);
+          appendable.append(this.toString(parts.next()));
+        }
+      }
+      return appendable;
+    }
+
+    public final String join(Iterable<?> parts) {
+      return join(parts.iterator());
+    }
+
+    public final String join(Iterator<?> parts) {
+      StringBuilder builder = new StringBuilder();
+      try {
+        appendTo((Appendable) builder, parts);
+        return builder.toString();
+      } catch (IOException e) {
+        throw new AssertionError(e);
+      }
+    }
+
+    public Joiner skipNulls() {
+      return new Joiner(this) {
+        public <A extends Appendable> A appendTo(A appendable, Iterator<?> parts)
+            throws IOException {
+          Preconditions.checkNotNull(appendable, "appendable");
+          Preconditions.checkNotNull(parts, "parts");
+          Object part;
+          while (parts.hasNext()) {
+            part = parts.next();
+            if (part != null) {
+              appendable.append(Joiner.this.toString(part));
+              break;
+            }
+          }
+          while (parts.hasNext()) {
+            part = parts.next();
+            if (part != null) {
+              appendable.append(Joiner.this.separator);
+              appendable.append(Joiner.this.toString(part));
+            }
+          }
+          return appendable;
+        }
+      };
+    }
+
+    CharSequence toString(Object part) {
+      Preconditions.checkNotNull(part);
+      return part instanceof CharSequence ? (CharSequence) part : part.toString();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/retrace/DuplicateMappingsTest.java b/src/test/java/com/android/tools/r8/retrace/DuplicateMappingsTest.java
new file mode 100644
index 0000000..bbe2d5b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/retrace/DuplicateMappingsTest.java
@@ -0,0 +1,64 @@
+// 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.retrace;
+
+import static org.hamcrest.CoreMatchers.allOf;
+import static org.hamcrest.CoreMatchers.containsString;
+
+import com.android.tools.r8.DiagnosticsMatcher;
+import com.android.tools.r8.PositionMatcher;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestDiagnosticMessagesImpl;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableList;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class DuplicateMappingsTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public DuplicateMappingsTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testSourceFileName() {
+    TestDiagnosticMessagesImpl diagnosticsHandler = new TestDiagnosticMessagesImpl();
+    Retrace.run(
+        RetraceCommand.builder(diagnosticsHandler)
+            .setProguardMapProducer(
+                () ->
+                    StringUtils.lines(
+                        "com.android.tools.r8.retrace.SourceFileTest$ClassWithCustomFileName ->"
+                            + " com.android.tools.r8.retrace.a:",
+                        "# {'id':'sourceFile','fileName':'foobarbaz.java'}",
+                        "# {'id':'sourceFile','fileName':'foobarbaz2.java'}"))
+            .setStackTrace(ImmutableList.of())
+            .setRetracedStackTraceConsumer(
+                strings -> {
+                  // No need to do anything, we are just checking for diagnostics.
+                })
+            .build());
+    diagnosticsHandler
+        .assertWarningsCount(1)
+        .assertWarningsMatch(
+            allOf(
+                DiagnosticsMatcher.diagnosticMessage(containsString("The mapping")),
+                DiagnosticsMatcher.diagnosticMessage(
+                    containsString("is not allowed in combination with")),
+                DiagnosticsMatcher.diagnosticPosition(PositionMatcher.positionLine(3))));
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/retrace/SourceFileTest.java b/src/test/java/com/android/tools/r8/retrace/SourceFileTest.java
new file mode 100644
index 0000000..fb7f280
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/retrace/SourceFileTest.java
@@ -0,0 +1,140 @@
+// 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.retrace;
+
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.R8FullTestBuilder;
+import com.android.tools.r8.R8TestRunResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.naming.retrace.StackTrace;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.util.List;
+import java.util.function.BiConsumer;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class SourceFileTest extends TestBase {
+
+  private final TestParameters parameters;
+  private final boolean useRegularExpression;
+  private static final String FILE_NAME = "foobarbaz.java";
+
+  @Parameters(name = "{0}, useRegularExpression: {1}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withAllRuntimesAndApiLevels().build(), BooleanUtils.values());
+  }
+
+  public SourceFileTest(TestParameters parameters, boolean useRegularExpression) {
+    this.parameters = parameters;
+    this.useRegularExpression = useRegularExpression;
+  }
+
+  @Test
+  public void testRuntime() throws Exception {
+    testForRuntime(parameters)
+        .addProgramClasses(Main.class, ClassWithoutCustomFileName.class)
+        .addProgramClassFileData(
+            transformer(ClassWithCustomFileName.class).setSourceFile(FILE_NAME).transform())
+        .run(parameters.getRuntime(), Main.class)
+        .assertFailureWithErrorThatMatches(containsString("Hello World!"))
+        .inspectStackTrace(
+            stackTrace -> {
+              assertEquals(FILE_NAME, stackTrace.getStackTraceLines().get(0).fileName);
+            });
+  }
+
+  @Test
+  public void testR8WithCustomFileName() throws Exception {
+    runTest(
+        false,
+        ((stackTrace, inspector) -> {
+          assertEquals(FILE_NAME, stackTrace.getStackTraceLines().get(0).fileName);
+          assertEquals(
+              1,
+              inspector
+                  .clazz(ClassWithCustomFileName.class)
+                  .getNaming()
+                  .getAdditionalMappings()
+                  .size());
+        }));
+  }
+
+  @Test
+  public void testR8WithoutCustomFileName() throws Exception {
+    runTest(
+        true,
+        ((stackTrace, inspector) -> {
+          assertEquals("SourceFileTest.java", stackTrace.getStackTraceLines().get(0).fileName);
+          assertEquals(
+              0,
+              inspector
+                  .clazz(ClassWithoutCustomFileName.class)
+                  .getNaming()
+                  .getAdditionalMappings()
+                  .size());
+        }));
+  }
+
+  private void runTest(boolean addDummyArg, BiConsumer<StackTrace, CodeInspector> consumer)
+      throws Exception {
+    R8FullTestBuilder r8FullTestBuilder =
+        testForR8(parameters.getBackend())
+            .addProgramClasses(Main.class, ClassWithoutCustomFileName.class)
+            .addProgramClassFileData(
+                transformer(ClassWithCustomFileName.class).setSourceFile(FILE_NAME).transform())
+            .addKeepClassRules(ClassWithoutCustomFileName.class)
+            .enableInliningAnnotations()
+            .addKeepMainRule(Main.class)
+            .setMinApi(parameters.getApiLevel())
+            .addKeepAttributeSourceFile();
+    R8TestRunResult runResult =
+        addDummyArg
+            ? r8FullTestBuilder.run(parameters.getRuntime(), Main.class, "foo")
+            : r8FullTestBuilder.run(parameters.getRuntime(), Main.class);
+    runResult.assertFailureWithErrorThatMatches(containsString("Hello World!"));
+    StackTrace originalStackTrace = runResult.getOriginalStackTrace();
+    StackTrace retracedStackTrace =
+        originalStackTrace.retrace(
+            runResult.proguardMap(),
+            useRegularExpression ? Retrace.DEFAULT_REGULAR_EXPRESSION : null);
+    runResult.inspectFailure(inspector -> consumer.accept(retracedStackTrace, inspector));
+  }
+
+  public static class ClassWithoutCustomFileName {
+
+    @NeverInline
+    public static void foo() {
+      throw new RuntimeException("Hello World!");
+    }
+  }
+
+  public static class ClassWithCustomFileName {
+
+    @NeverInline
+    public static void foo() {
+      throw new RuntimeException("Hello World!");
+    }
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      if (args.length == 0) {
+        ClassWithCustomFileName.foo();
+      } else {
+        ClassWithoutCustomFileName.foo();
+      }
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/rewrite/enums/EnumOptimizationTest.java b/src/test/java/com/android/tools/r8/rewrite/enums/EnumOptimizationTest.java
index ae4947a..1bf5f83 100644
--- a/src/test/java/com/android/tools/r8/rewrite/enums/EnumOptimizationTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/enums/EnumOptimizationTest.java
@@ -20,6 +20,7 @@
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import java.util.List;
 import java.util.Objects;
+import org.junit.Assume;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -74,13 +75,14 @@
     if (enableOptimization) {
       assertOrdinalReplacedWithConst(clazz.uniqueMethodWithName("simple"), 1);
       assertOrdinalReplacedWithConst(clazz.uniqueMethodWithName("local"), 1);
+      // TODO(b/160667929): Re-enable name()/toString() optimizations.
       // String concatenation optimization is enabled for DEX output.
       // Even replaced ordinal is concatenated (and gone).
-      if (parameters.isDexRuntime()) {
-        assertOrdinalReplacedAndGone(clazz.uniqueMethodWithName("multipleUsages"));
-      } else {
-        assertOrdinalReplacedWithConst(clazz.uniqueMethodWithName("multipleUsages"), 1);
-      }
+      //      if (parameters.isDexRuntime()) {
+      //        assertOrdinalReplacedAndGone(clazz.uniqueMethodWithName("multipleUsages"));
+      //      } else {
+      assertOrdinalReplacedWithConst(clazz.uniqueMethodWithName("multipleUsages"), 1);
+      //      }
       assertOrdinalReplacedWithConst(clazz.uniqueMethodWithName("inlined"), 1);
       assertOrdinalReplacedWithConst(clazz.uniqueMethodWithName("inSwitch"), 11);
       assertOrdinalReplacedWithConst(clazz.uniqueMethodWithName("differentTypeStaticField"), 1);
@@ -121,6 +123,7 @@
     assertTrue(clazz.isPresent());
 
     if (enableOptimization) {
+      Assume.assumeTrue("TODO(b/160667929): Re-enable name()/toString() optimizations.", false);
       assertNameReplacedWithConst(clazz.uniqueMethodWithName("simple"), "TWO");
       assertNameReplacedWithConst(clazz.uniqueMethodWithName("local"), "TWO");
       // String concatenation optimization is enabled for DEX output.
@@ -171,6 +174,7 @@
     assertToStringWasNotReplaced(clazz.uniqueMethodWithName("valueWithoutToString"));
 
     if (enableOptimization) {
+      Assume.assumeTrue("TODO(b/160667929): Re-enable name()/toString() optimizations.", false);
       assertToStringReplacedWithConst(clazz.uniqueMethodWithName("noToString"), "TWO");
       assertToStringReplacedWithConst(clazz.uniqueMethodWithName("local"), "TWO");
       assertToStringReplacedWithConst(clazz.uniqueMethodWithName("multipleUsages"), "TWO");
diff --git a/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java b/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
index 1f1d744..c34dc98 100644
--- a/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
+++ b/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
@@ -266,6 +266,16 @@
         });
   }
 
+  public ClassFileTransformer setSourceFile(String sourceFile) {
+    return addClassTransformer(
+        new ClassTransformer() {
+          @Override
+          public void visitSource(String source, String debug) {
+            super.visitSource(sourceFile, debug);
+          }
+        });
+  }
+
   public ClassFileTransformer setAccessFlags(Consumer<ClassAccessFlags> fn) {
     return addClassTransformer(
         new ClassTransformer() {
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java
index 6a04a0a..59c8760 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java
@@ -7,6 +7,7 @@
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.naming.ClassNamingForNameMapper;
 import com.android.tools.r8.references.ClassReference;
 import java.util.List;
 import java.util.function.Consumer;
@@ -177,4 +178,9 @@
   public KotlinClassMetadata getKotlinClassMetadata() {
     return null;
   }
+
+  @Override
+  public ClassNamingForNameMapper getNaming() {
+    return null;
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java
index 84d76a1..b2266ce 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java
@@ -8,6 +8,7 @@
 
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.naming.ClassNamingForNameMapper;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
 import com.android.tools.r8.references.ClassReference;
 import com.android.tools.r8.references.MethodReference;
@@ -211,4 +212,6 @@
         Reference.classFromDescriptor(
             descriptor.substring(0, descriptor.length() - 1) + COMPANION_CLASS_NAME_SUFFIX + ";"));
   }
+
+  public abstract ClassNamingForNameMapper getNaming();
 }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java
index bd65812..0b1d950 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java
@@ -403,4 +403,9 @@
     return KotlinClassMetadataReader.toKotlinClassMetadata(
         codeInspector.getFactory().kotlin, annotationSubject.getAnnotation());
   }
+
+  @Override
+  public ClassNamingForNameMapper getNaming() {
+    return naming;
+  }
 }
diff --git a/tools/compiledump.py b/tools/compiledump.py
index 511ec8d..962bc23 100755
--- a/tools/compiledump.py
+++ b/tools/compiledump.py
@@ -3,15 +3,15 @@
 # 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.
 
+import archive
 import argparse
+import jdk
 import os
+import retrace
 import subprocess
 import sys
 import zipfile
 
-import archive
-import jdk
-import retrace
 import utils
 
 
@@ -65,6 +65,18 @@
       help='Enable Java debug agent and suspend compilation (default disabled)',
       default=False,
       action='store_true')
+  parser.add_argument(
+      '--xmx',
+      help='Set JVM max heap size (-Xmx)',
+      default=None)
+  parser.add_argument(
+      '--threads',
+      help='Set the number of threads to use',
+      default=None)
+  parser.add_argument(
+      '--min-api',
+      help='Set min-api (default read from dump properties file)',
+      default=None)
   return parser
 
 def error(msg):
@@ -155,6 +167,13 @@
 def determine_output(args, temp):
   return os.path.join(temp, 'out.jar')
 
+def determine_min_api(args, build_properties):
+  if args.min_api:
+    return args.min_api
+  if 'min-api' in build_properties:
+    return build_properties.get('min-api')
+  return None
+
 def determine_feature_output(feature_jar, temp):
   return os.path.join(temp, os.path.basename(feature_jar)[:-4] + ".out.jar")
 
@@ -193,6 +212,7 @@
     version = determine_version(args, dump)
     compiler = determine_compiler(args, dump)
     out = determine_output(args, temp)
+    min_api = determine_min_api(args, build_properties)
     jar = args.r8_jar if args.r8_jar else download_distribution(args, version, temp)
     wrapper_dir = prepare_wrapper(jar, temp)
     cmd = [jdk.GetJavaExecutable()]
@@ -201,6 +221,8 @@
         print "WARNING: Running debugging agent on r8lib is questionable..."
       cmd.append(
           '-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=*:5005')
+    if args.xmx:
+      cmd.append('-Xmx' + args.xmx)
     if args.ea:
       cmd.append('-ea')
     if args.printtimes:
@@ -225,8 +247,10 @@
       cmd.extend(['--pg-conf', dump.config_file()])
     if compiler != 'd8':
       cmd.extend(['--pg-map-output', '%s.map' % out])
-    if 'min-api' in build_properties:
-      cmd.extend(['--min-api', build_properties.get('min-api')])
+    if min_api:
+      cmd.extend(['--min-api', min_api])
+    if args.threads:
+      cmd.extend(['--threads', args.threads])
     cmd.extend(otherargs)
     utils.PrintCmd(cmd)
     try: