Merge commit '987f83c71114164cc92f7c7ecbd827c8acdefdd5' into dev-release
diff --git a/src/main/java/com/android/tools/r8/D8.java b/src/main/java/com/android/tools/r8/D8.java
index 8a88d3d..fa5a935 100644
--- a/src/main/java/com/android/tools/r8/D8.java
+++ b/src/main/java/com/android/tools/r8/D8.java
@@ -264,6 +264,7 @@
             appView.getSyntheticItems().computeFinalSynthetics(appView);
         if (result != null) {
           appView.setAppInfo(new AppInfo(result.commit, appView.appInfo().getMainDexClasses()));
+          appView.pruneItems(result.prunedItems);
         }
         new CfApplicationWriter(
                 appView,
@@ -307,6 +308,7 @@
             appView.getSyntheticItems().computeFinalSynthetics(appView);
         if (result != null) {
           appView.setAppInfo(new AppInfo(result.commit, appView.appInfo().getMainDexClasses()));
+          appView.pruneItems(result.prunedItems);
         }
 
         new ApplicationWriter(
diff --git a/src/main/java/com/android/tools/r8/L8.java b/src/main/java/com/android/tools/r8/L8.java
index 332b864..a06be47 100644
--- a/src/main/java/com/android/tools/r8/L8.java
+++ b/src/main/java/com/android/tools/r8/L8.java
@@ -131,6 +131,7 @@
           appView.getSyntheticItems().computeFinalSynthetics(appView);
       if (result != null) {
         appView.setAppInfo(new AppInfo(result.commit, appView.appInfo().getMainDexClasses()));
+        appView.pruneItems(result.prunedItems);
       }
 
       NamingLens namingLens = PrefixRewritingNamingLens.createPrefixRewritingNamingLens(appView);
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 089c747..ea95988 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -428,8 +428,8 @@
                   appViewWithLiveness, appViewWithLiveness.appInfo().computeSubtypingInfo())
               .run();
 
-          if (appView.options().protoShrinking().isProtoEnumShrinkingEnabled()) {
-            appView.protoShrinker().enumProtoShrinker.clearDeadEnumLiteMaps();
+          if (appView.options().protoShrinking().isEnumLiteProtoShrinkingEnabled()) {
+            appView.protoShrinker().enumLiteProtoShrinker.clearDeadEnumLiteMaps();
           }
 
           AnnotationRemover annotationRemover =
@@ -795,8 +795,8 @@
         }
 
         if (appView.options().protoShrinking().isProtoShrinkingEnabled()) {
-          if (appView.options().protoShrinking().isProtoEnumShrinkingEnabled()) {
-            appView.protoShrinker().enumProtoShrinker.verifyDeadEnumLiteMapsAreDead();
+          if (appView.options().protoShrinking().isEnumLiteProtoShrinkingEnabled()) {
+            appView.protoShrinker().enumLiteProtoShrinker.verifyDeadEnumLiteMapsAreDead();
           }
 
           IRConverter converter = new IRConverter(appView, timing, null, mainDexTracingResult);
@@ -849,6 +849,7 @@
               lens, appBuilder.build(), memberRebindingLens.getPrevious());
         }
       }
+      assert Repackaging.verifyIdentityRepackaging(appView);
 
       // Add automatic main dex classes to an eventual manual list of classes.
       if (!options.mainDexKeepRules.isEmpty()) {
@@ -860,12 +861,11 @@
       if (result != null) {
         if (appView.appInfo().hasLiveness()) {
           appViewWithLiveness.setAppInfo(
-              appViewWithLiveness
-                  .appInfo()
-                  .rebuildWithLiveness(result.commit, result.removedSyntheticClasses));
+              appViewWithLiveness.appInfo().rebuildWithLiveness(result.commit));
         } else {
           appView.setAppInfo(appView.appInfo().rebuildWithClassHierarchy(result.commit));
         }
+        appViewWithLiveness.pruneItems(result.prunedItems);
       }
 
       // Perform minification.
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfo.java b/src/main/java/com/android/tools/r8/graph/AppInfo.java
index 3c5f4b7..cfacdbb 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfo.java
@@ -63,6 +63,18 @@
     this.obsolete = obsolete;
   }
 
+  public AppInfo prunedCopyFrom(PrunedItems prunedItems) {
+    assert getClass() == AppInfo.class;
+    assert checkIfObsolete();
+    assert prunedItems.getPrunedApp() == app();
+    if (prunedItems.isEmpty()) {
+      return this;
+    }
+    return new AppInfo(
+        getSyntheticItems().commitPrunedItems(prunedItems),
+        getMainDexClasses().withoutPrunedItems(prunedItems));
+  }
+
   protected InternalOptions options() {
     return app.options;
   }
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java b/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
index 144b1de..b6100c2 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
@@ -93,6 +93,20 @@
         getMainDexClasses());
   }
 
+  @Override
+  public AppInfoWithClassHierarchy prunedCopyFrom(PrunedItems prunedItems) {
+    assert getClass() == AppInfoWithClassHierarchy.class;
+    assert checkIfObsolete();
+    assert prunedItems.getPrunedApp() == app();
+    if (prunedItems.isEmpty()) {
+      return this;
+    }
+    return new AppInfoWithClassHierarchy(
+        getSyntheticItems().commitPrunedItems(prunedItems),
+        getClassToFeatureSplitMap().withoutPrunedItems(prunedItems),
+        getMainDexClasses().withoutPrunedItems(prunedItems));
+  }
+
   public ClassToFeatureSplitMap getClassToFeatureSplitMap() {
     return classToFeatureSplitMap;
   }
diff --git a/src/main/java/com/android/tools/r8/graph/AppView.java b/src/main/java/com/android/tools/r8/graph/AppView.java
index 8e0800b..566fa31 100644
--- a/src/main/java/com/android/tools/r8/graph/AppView.java
+++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -90,7 +90,7 @@
   // TODO(b/169115389): Remove
   private Set<DexMethod> cfByteCodePassThrough = ImmutableSet.of();
 
-  private Map<DexClass, DexValueString> sourceDebugExtensions = new IdentityHashMap<>();
+  private Map<DexType, DexValueString> sourceDebugExtensions = new IdentityHashMap<>();
 
   // When input has been (partially) desugared these are the classes which has been library
   // desugared. This information is populated in the IR converter.
@@ -251,11 +251,11 @@
   }
 
   public void setSourceDebugExtensionForType(DexClass clazz, DexValueString sourceDebugExtension) {
-    this.sourceDebugExtensions.put(clazz, sourceDebugExtension);
+    sourceDebugExtensions.put(clazz.type, sourceDebugExtension);
   }
 
   public DexValueString getSourceDebugExtensionForType(DexClass clazz) {
-    return this.sourceDebugExtensions.get(clazz);
+    return sourceDebugExtensions.get(clazz.type);
   }
 
   @Override
@@ -321,8 +321,8 @@
   }
 
   public <U> U withProtoEnumShrinker(Function<EnumLiteProtoShrinker, U> fn, U defaultValue) {
-    if (protoShrinker != null && options().protoShrinking().isProtoEnumShrinkingEnabled()) {
-      return fn.apply(protoShrinker.enumProtoShrinker);
+    if (protoShrinker != null && options().protoShrinking().isEnumLiteProtoShrinkingEnabled()) {
+      return fn.apply(protoShrinker.enumLiteProtoShrinker);
     }
     return defaultValue;
   }
@@ -569,16 +569,27 @@
   }
 
   public void pruneItems(PrunedItems prunedItems) {
-    pruneItems(prunedItems, withLiveness());
-  }
-
-  private static void pruneItems(PrunedItems prunedItems, AppView<AppInfoWithLiveness> appView) {
-    if (!prunedItems.hasRemovedClasses() && !appView.options().configurationDebugging) {
-      assert appView.appInfo().app() == prunedItems.getPrunedApp();
+    if (prunedItems.isEmpty()) {
+      assert appInfo().app() == prunedItems.getPrunedApp();
       return;
     }
-    appView.setAppInfo(appView.appInfo().prunedCopyFrom(prunedItems));
-    appView.setAppServices(appView.appServices().prunedCopy(prunedItems));
+    if (appInfo.hasLiveness()) {
+      AppView<AppInfoWithLiveness> self = withLiveness();
+      self.setAppInfo(self.appInfo().prunedCopyFrom(prunedItems));
+    } else if (appInfo.hasClassHierarchy()) {
+      AppView<AppInfoWithClassHierarchy> self = withClassHierarchy();
+      self.setAppInfo(self.appInfo().prunedCopyFrom(prunedItems));
+    } else {
+      pruneAppInfo(prunedItems, this);
+    }
+    if (appServices() != null) {
+      setAppServices(appServices().prunedCopy(prunedItems));
+    }
+  }
+
+  @SuppressWarnings("unchecked")
+  private static void pruneAppInfo(PrunedItems prunedItems, AppView<?> appView) {
+    ((AppView<AppInfo>) appView).setAppInfo(appView.appInfo().prunedCopyFrom(prunedItems));
   }
 
   public void rewriteWithLens(NonIdentityGraphLens lens) {
diff --git a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
index 0faef1b..1ace989 100644
--- a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
@@ -46,9 +46,6 @@
 
   public static final DexProgramClass[] EMPTY_ARRAY = {};
 
-  private static final DexEncodedArray SENTINEL_NOT_YET_COMPUTED =
-      new DexEncodedArray(DexValue.EMPTY_ARRAY);
-
   private final ProgramResource.Kind originKind;
   private final Collection<DexProgramClass> synthesizedFrom;
   private CfVersion initialClassFileVersion = null;
diff --git a/src/main/java/com/android/tools/r8/graph/DexValue.java b/src/main/java/com/android/tools/r8/graph/DexValue.java
index 97d304b..f355030 100644
--- a/src/main/java/com/android/tools/r8/graph/DexValue.java
+++ b/src/main/java/com/android/tools/r8/graph/DexValue.java
@@ -307,6 +307,10 @@
     return null;
   }
 
+  public boolean isNestedDexValue() {
+    return false;
+  }
+
   public abstract AbstractValue toAbstractValue(AbstractValueFactory factory);
 
   static DexValue fromAsmBootstrapArgument(
@@ -1133,6 +1137,11 @@
     }
 
     @Override
+    public boolean isNestedDexValue() {
+      return true;
+    }
+
+    @Override
     public DexType getType(DexItemFactory factory) {
       throw new Unreachable();
     }
diff --git a/src/main/java/com/android/tools/r8/graph/PrunedItems.java b/src/main/java/com/android/tools/r8/graph/PrunedItems.java
index 2df596c..ed42622 100644
--- a/src/main/java/com/android/tools/r8/graph/PrunedItems.java
+++ b/src/main/java/com/android/tools/r8/graph/PrunedItems.java
@@ -34,6 +34,10 @@
     return new Builder().setPrunedApp(application).build();
   }
 
+  public boolean isEmpty() {
+    return removedClasses.isEmpty() && additionalPinnedItems.isEmpty();
+  }
+
   public DexApplication getPrunedApp() {
     return prunedApp;
   }
diff --git a/src/main/java/com/android/tools/r8/repackaging/RepackagingTreeFixer.java b/src/main/java/com/android/tools/r8/graph/TreeFixerBase.java
similarity index 71%
rename from src/main/java/com/android/tools/r8/repackaging/RepackagingTreeFixer.java
rename to src/main/java/com/android/tools/r8/graph/TreeFixerBase.java
index 17d06a3..a10db71 100644
--- a/src/main/java/com/android/tools/r8/repackaging/RepackagingTreeFixer.java
+++ b/src/main/java/com/android/tools/r8/graph/TreeFixerBase.java
@@ -2,64 +2,67 @@
 // 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.repackaging;
+package com.android.tools.r8.graph;
 
-import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexEncodedField;
-import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.graph.DexField;
-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.DexProto;
-import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.DexTypeList;
-import com.android.tools.r8.graph.DirectMappedDexApplication;
-import com.android.tools.r8.graph.EnclosingMethodAttribute;
-import com.android.tools.r8.graph.InnerClassAttribute;
-import com.android.tools.r8.graph.NestHostClassAttribute;
-import com.android.tools.r8.graph.NestMemberClassAttribute;
-import com.android.tools.r8.shaking.AnnotationFixer;
-import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.IdentityHashMap;
 import java.util.List;
 import java.util.Map;
 
-public class RepackagingTreeFixer {
+public abstract class TreeFixerBase {
 
-  private final DirectMappedDexApplication.Builder appBuilder;
-  private final AppView<AppInfoWithLiveness> appView;
+  private final AppView<?> appView;
   private final DexItemFactory dexItemFactory;
-  private final RepackagingLens.Builder lensBuilder = new RepackagingLens.Builder();
-  private final Map<DexType, DexType> repackagedClasses;
 
-  private final Map<DexType, DexProgramClass> newProgramClasses = new IdentityHashMap<>();
+  private Map<DexType, DexProgramClass> newProgramClasses = null;
   private final Map<DexType, DexProgramClass> synthesizedFromClasses = new IdentityHashMap<>();
   private final Map<DexProto, DexProto> protoFixupCache = new IdentityHashMap<>();
 
-  public RepackagingTreeFixer(
-      DirectMappedDexApplication.Builder appBuilder,
-      AppView<AppInfoWithLiveness> appView,
-      Map<DexType, DexType> repackagedClasses) {
-    this.appBuilder = appBuilder;
+  public TreeFixerBase(AppView<?> appView) {
     this.appView = appView;
     this.dexItemFactory = appView.dexItemFactory();
-    this.repackagedClasses = repackagedClasses;
   }
 
-  public RepackagingLens run() {
-    // Globally substitute repackaged class types.
-    for (DexProgramClass clazz : appView.appInfo().classesWithDeterministicOrder()) {
+  /** Mapping of a class type to a potentially new class type. */
+  public abstract DexType mapClassType(DexType type);
+
+  /** Callback invoked each time an encoded field changes field reference. */
+  public abstract void recordFieldChange(DexField from, DexField to);
+
+  /** Callback invoked each time an encoded method changes method reference. */
+  public abstract void recordMethodChange(DexMethod from, DexMethod to);
+
+  /** Callback invoked each time a program class definition changes type reference. */
+  public abstract void recordClassChange(DexType from, DexType to);
+
+  private DexProgramClass recordClassChange(DexProgramClass from, DexProgramClass to) {
+    recordClassChange(from.getType(), to.getType());
+    return to;
+  }
+
+  private DexEncodedField recordFieldChange(DexEncodedField from, DexEncodedField to) {
+    recordFieldChange(from.field, to.field);
+    return to;
+  }
+
+  /** Callback to allow custom handling when an encoded method changes. */
+  public DexEncodedMethod recordMethodChange(DexEncodedMethod from, DexEncodedMethod to) {
+    recordMethodChange(from.method, to.method);
+    return to;
+  }
+
+  /** Fixup a collection of classes. */
+  public Collection<DexProgramClass> fixupClasses(Collection<DexProgramClass> classes) {
+    assert newProgramClasses == null;
+    newProgramClasses = new IdentityHashMap<>();
+    for (DexProgramClass clazz : classes) {
       newProgramClasses.computeIfAbsent(clazz.getType(), ignore -> fixupClass(clazz));
     }
-    appBuilder.replaceProgramClasses(new ArrayList<>(newProgramClasses.values()));
-    RepackagingLens lens = lensBuilder.build(appView);
-    new AnnotationFixer(lens).run(newProgramClasses.values());
-    return lens;
+    return newProgramClasses.values();
   }
 
+  // Should remain private as the correctness of the fixup requires the lazy 'newProgramClasses'.
   private DexProgramClass fixupClass(DexProgramClass clazz) {
     DexProgramClass newClass =
         new DexProgramClass(
@@ -93,8 +96,19 @@
         fixupMethods(
             clazz.getMethodCollection().virtualMethods(),
             clazz.getMethodCollection().numberOfVirtualMethods()));
+    // Transfer properties that are not passed to the constructor.
+    if (clazz.hasClassFileVersion()) {
+      newClass.setInitialClassFileVersion(clazz.getInitialClassFileVersion());
+    }
+    if (clazz.isDeprecated()) {
+      newClass.setDeprecated();
+    }
+    if (clazz.getKotlinInfo() != null) {
+      newClass.setKotlinInfo(clazz.getKotlinInfo());
+    }
+    // If the class type changed, record the move in the lens.
     if (newClass.getType() != clazz.getType()) {
-      lensBuilder.recordMove(clazz.getType(), newClass.getType());
+      return recordClassChange(clazz, newClass);
     }
     return newClass;
   }
@@ -120,7 +134,8 @@
         : enclosingMethodAttribute;
   }
 
-  private DexEncodedField[] fixupFields(List<DexEncodedField> fields) {
+  /** Fixup a list of fields. */
+  public DexEncodedField[] fixupFields(List<DexEncodedField> fields) {
     if (fields == null) {
       return DexEncodedField.EMPTY_ARRAY;
     }
@@ -135,13 +150,13 @@
     DexField fieldReference = field.getReference();
     DexField newFieldReference = fixupFieldReference(fieldReference);
     if (newFieldReference != fieldReference) {
-      lensBuilder.recordMove(fieldReference, newFieldReference);
-      return field.toTypeSubstitutedField(newFieldReference);
+      return recordFieldChange(field, field.toTypeSubstitutedField(newFieldReference));
     }
     return field;
   }
 
-  private DexField fixupFieldReference(DexField field) {
+  /** Fixup a field reference. */
+  public DexField fixupFieldReference(DexField field) {
     DexType newType = fixupType(field.type);
     DexType newHolder = fixupType(field.holder);
     return dexItemFactory.createField(newHolder, newType, field.name);
@@ -181,21 +196,20 @@
     }
     return newMethods;
   }
-
-  private DexEncodedMethod fixupMethod(DexEncodedMethod method) {
+  /** Fixup a method definition. */
+  public DexEncodedMethod fixupMethod(DexEncodedMethod method) {
     DexMethod methodReference = method.getReference();
     DexMethod newMethodReference = fixupMethodReference(methodReference);
     if (newMethodReference != methodReference) {
-      lensBuilder.recordMove(methodReference, newMethodReference);
-      return method.toTypeSubstitutedMethod(newMethodReference);
+      return recordMethodChange(method, method.toTypeSubstitutedMethod(newMethodReference));
     }
     return method;
   }
 
-  private DexMethod fixupMethodReference(DexMethod method) {
-    return appView
-        .dexItemFactory()
-        .createMethod(fixupType(method.holder), fixupProto(method.proto), method.name);
+  /** Fixup a method reference. */
+  public DexMethod fixupMethodReference(DexMethod method) {
+    return dexItemFactory.createMethod(
+        fixupType(method.holder), fixupProto(method.proto), method.name);
   }
 
   private NestHostClassAttribute fixupNestHost(NestHostClassAttribute nestHostClassAttribute) {
@@ -221,7 +235,8 @@
     return changed ? newNestMemberAttributes : nestMemberAttributes;
   }
 
-  private DexProto fixupProto(DexProto proto) {
+  /** Fixup a proto descriptor. */
+  public DexProto fixupProto(DexProto proto) {
     DexProto result = protoFixupCache.get(proto);
     if (result == null) {
       DexType returnType = fixupType(proto.returnType);
@@ -232,8 +247,10 @@
     return result;
   }
 
+  // Should remain private as its correctness relies on the setup of 'newProgramClasses'.
   private Collection<DexProgramClass> fixupSynthesizedFrom(
       Collection<DexProgramClass> synthesizedFrom) {
+    assert newProgramClasses != null;
     if (synthesizedFrom.isEmpty()) {
       return synthesizedFrom;
     }
@@ -258,7 +275,8 @@
     return type != null ? fixupType(type) : null;
   }
 
-  private DexType fixupType(DexType type) {
+  /** Fixup a type reference. */
+  public DexType fixupType(DexType type) {
     if (type.isArrayType()) {
       DexType base = type.toBaseType(dexItemFactory);
       DexType fixed = fixupType(base);
@@ -268,7 +286,7 @@
       return type.replaceBaseType(fixed, dexItemFactory);
     }
     if (type.isClassType()) {
-      return repackagedClasses.getOrDefault(type, type);
+      return mapClassType(type);
     }
     return type;
   }
@@ -289,4 +307,9 @@
     DexType[] newTypes = fixupTypes(types.values);
     return newTypes != types.values ? new DexTypeList(newTypes) : types;
   }
+
+  /** Fixup a method signature. */
+  public DexMethodSignature fixupMethodSignature(DexMethodSignature signature) {
+    return signature.withProto(fixupProto(signature.getProto()));
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/TreeFixer.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/TreeFixer.java
index 0f85054..87a1e31 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/TreeFixer.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/TreeFixer.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.horizontalclassmerging;
 
+import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass.FieldSetter;
 import com.android.tools.r8.graph.DexEncodedField;
@@ -16,6 +17,7 @@
 import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.TreeFixerBase;
 import com.android.tools.r8.ir.conversion.ExtraUnusedNullParameter;
 import com.android.tools.r8.shaking.AnnotationFixer;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
@@ -38,7 +40,7 @@
  * have been remapped to new classes by the class merger. While doing so, all updated changes are
  * tracked in {@link TreeFixer#lensBuilder}.
  */
-class TreeFixer {
+class TreeFixer extends TreeFixerBase {
   private final Map<DexProto, DexProto> protoFixupCache = new IdentityHashMap<>();
   private final HorizontallyMergedClasses mergedClasses;
   private final HorizontalClassMergerGraphLens.Builder lensBuilder;
@@ -55,6 +57,7 @@
       HorizontalClassMergerGraphLens.Builder lensBuilder,
       FieldAccessInfoCollectionModifier.Builder fieldAccessChangesBuilder,
       SyntheticArgumentClass syntheticArgumentClass) {
+    super(appView);
     this.appView = appView;
     this.mergedClasses = mergedClasses;
     this.lensBuilder = lensBuilder;
@@ -384,53 +387,26 @@
     }
   }
 
-  public DexField fixupFieldReference(DexField field) {
-    DexType newType = fixupType(field.type);
-    DexType newHolder = fixupType(field.holder);
-    return appView.dexItemFactory().createField(newHolder, newType, field.name);
+  @Override
+  public DexType mapClassType(DexType type) {
+    return mergedClasses.getMergeTargetOrDefault(type);
   }
 
-  private DexMethod fixupMethodReference(DexMethod method) {
-    return appView
-        .dexItemFactory()
-        .createMethod(fixupType(method.holder), fixupProto(method.proto), method.name);
+  @Override
+  public void recordClassChange(DexType from, DexType to) {
+    // Classes are not changed but in-place updated.
+    throw new Unreachable();
   }
 
-  private DexMethodSignature fixupMethodSignature(DexMethodSignature signature) {
-    return signature.withProto(fixupProto(signature.getProto()));
+  @Override
+  public void recordFieldChange(DexField from, DexField to) {
+    // Fields are manually changed in 'fixupFields' above.
+    throw new Unreachable();
   }
 
-  private DexProto fixupProto(DexProto proto) {
-    DexProto result = protoFixupCache.get(proto);
-    if (result == null) {
-      DexType returnType = fixupType(proto.returnType);
-      DexType[] arguments = fixupTypes(proto.parameters.values);
-      result = appView.dexItemFactory().createProto(returnType, arguments);
-      protoFixupCache.put(proto, result);
-    }
-    return result;
-  }
-
-  private DexType fixupType(DexType type) {
-    if (type.isArrayType()) {
-      DexType base = type.toBaseType(appView.dexItemFactory());
-      DexType fixed = fixupType(base);
-      if (base == fixed) {
-        return type;
-      }
-      return type.replaceBaseType(fixed, appView.dexItemFactory());
-    }
-    if (type.isClassType()) {
-      type = mergedClasses.getMergeTargetOrDefault(type);
-    }
-    return type;
-  }
-
-  private DexType[] fixupTypes(DexType[] types) {
-    DexType[] result = new DexType[types.length];
-    for (int i = 0; i < result.length; i++) {
-      result[i] = fixupType(types[i]);
-    }
-    return result;
+  @Override
+  public void recordMethodChange(DexMethod from, DexMethod to) {
+    // Methods are manually changed in 'fixupMethods' above.
+    throw new Unreachable();
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValueAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValueAnalysis.java
index 03455fb..281215f 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValueAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValueAnalysis.java
@@ -316,9 +316,11 @@
       return computeObjectState(definition.outValue());
     }
     if (definition.isStaticGet()) {
-      // TODO(b/166532388) : Enums with many instance rely on staticGets to set the $VALUES data
-      // instead of directly keeping the values in registers. We could consider analysing these
-      // and answer the analysed object state here.
+      // Enums with many instance rely on staticGets to set the $VALUES data instead of directly
+      // keeping the values in registers, due to the max capacity of the redundant field load
+      // elimination. The capacity has already been increased, so that this case is extremely
+      // uncommon (very large enums).
+      // TODO(b/169050248): We could consider analysing these to answer the object state here.
       return ObjectState.empty();
     }
     return ObjectState.empty();
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/EnumLiteProtoShrinker.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/EnumLiteProtoShrinker.java
index 0b04f9b..64f5bea 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/EnumLiteProtoShrinker.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/EnumLiteProtoShrinker.java
@@ -56,7 +56,7 @@
   }
 
   public void clearDeadEnumLiteMaps() {
-    assert appView.options().protoShrinking().isProtoEnumShrinkingEnabled();
+    assert appView.options().protoShrinking().isEnumLiteProtoShrinkingEnabled();
     // The optimization only enables further enums to be unboxed, no point to run it if enum
     // unboxing is disabled.
     if (!appView.options().enableEnumUnboxing) {
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoEnumSwitchMapRemover.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoEnumSwitchMapRemover.java
new file mode 100644
index 0000000..00e824f
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoEnumSwitchMapRemover.java
@@ -0,0 +1,68 @@
+// 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.analysis.proto;
+
+import com.android.tools.r8.errors.CompilationError;
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.analysis.fieldvalueanalysis.StaticFieldValues;
+import com.android.tools.r8.ir.analysis.fieldvalueanalysis.StaticFieldValues.EnumStaticFieldValues;
+import com.android.tools.r8.ir.analysis.value.ObjectState;
+import com.android.tools.r8.ir.analysis.value.SingleNumberValue;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+/** Proto enum switch maps can be removed even if the static enum fields are pinned. */
+public class ProtoEnumSwitchMapRemover {
+
+  private final ProtoReferences references;
+
+  private final Map<DexType, EnumStaticFieldValues> staticFieldValuesMap =
+      new ConcurrentHashMap<>();
+
+  public ProtoEnumSwitchMapRemover(ProtoReferences references) {
+    this.references = references;
+  }
+
+  public void recordStaticValues(DexProgramClass clazz, StaticFieldValues staticFieldValues) {
+    if (staticFieldValues == null || !staticFieldValues.isEnumStaticFieldValues()) {
+      return;
+    }
+    assert clazz.isEnum();
+    EnumStaticFieldValues enumStaticFieldValues = staticFieldValues.asEnumStaticFieldValues();
+    if (isProtoEnum(clazz)) {
+      staticFieldValuesMap.put(clazz.type, enumStaticFieldValues);
+    }
+  }
+
+  private boolean isProtoEnum(DexProgramClass clazz) {
+    assert clazz.isEnum();
+    if (clazz.type == references.methodToInvokeType) {
+      return true;
+    }
+    return clazz.getInterfaces().contains(references.enumLiteMapType);
+  }
+
+  public SingleNumberValue getOrdinal(
+      DexProgramClass enumClass, DexEncodedField enumInstanceField, DexEncodedField ordinalField) {
+    if (enumClass == null || !isProtoEnum(enumClass)) {
+      return null;
+    }
+    EnumStaticFieldValues enumStaticFieldValues = staticFieldValuesMap.get(enumClass.type);
+    if (enumStaticFieldValues == null) {
+      if (enumClass.type == references.methodToInvokeType) {
+        throw new CompilationError("Proto optimizations: missing information for MethodToInvoke.");
+      }
+      return null;
+    }
+    ObjectState state =
+        enumStaticFieldValues.getObjectStateForPossiblyPinnedField(enumInstanceField.field);
+    if (state == null) {
+      return null;
+    }
+    return state.getAbstractFieldValue(ordinalField).asSingleNumberValue();
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoShrinker.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoShrinker.java
index 2674265..a0f75d7 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoShrinker.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoShrinker.java
@@ -20,7 +20,8 @@
   public final GeneratedExtensionRegistryShrinker generatedExtensionRegistryShrinker;
   public final GeneratedMessageLiteShrinker generatedMessageLiteShrinker;
   public final GeneratedMessageLiteBuilderShrinker generatedMessageLiteBuilderShrinker;
-  public final EnumLiteProtoShrinker enumProtoShrinker;
+  public final EnumLiteProtoShrinker enumLiteProtoShrinker;
+  public final ProtoEnumSwitchMapRemover protoEnumSwitchMapRemover;
   public final ProtoReferences references;
 
   private Set<DexType> deadProtoTypes = Sets.newIdentityHashSet();
@@ -42,10 +43,14 @@
         appView.options().protoShrinking().enableGeneratedMessageLiteBuilderShrinking
             ? new GeneratedMessageLiteBuilderShrinker(appView, references)
             : null;
-    this.enumProtoShrinker =
-        appView.options().protoShrinking().isProtoEnumShrinkingEnabled()
+    this.enumLiteProtoShrinker =
+        appView.options().protoShrinking().isEnumLiteProtoShrinkingEnabled()
             ? new EnumLiteProtoShrinker(appView, references)
             : null;
+    this.protoEnumSwitchMapRemover =
+        appView.options().protoShrinking().enableRemoveProtoEnumSwitchMap()
+            ? new ProtoEnumSwitchMapRemover(references)
+            : null;
     this.references = references;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/RawMessageInfoDecoder.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/RawMessageInfoDecoder.java
index 363a2de..c3ab146 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/RawMessageInfoDecoder.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/RawMessageInfoDecoder.java
@@ -19,6 +19,7 @@
 import com.android.tools.r8.ir.analysis.proto.schema.ProtoFieldType;
 import com.android.tools.r8.ir.analysis.proto.schema.ProtoFieldTypeFactory;
 import com.android.tools.r8.ir.analysis.proto.schema.ProtoMessageInfo;
+import com.android.tools.r8.ir.analysis.proto.schema.ProtoMessageInfo.ProtoMessageInfoBuilderException;
 import com.android.tools.r8.ir.analysis.proto.schema.ProtoObject;
 import com.android.tools.r8.ir.analysis.proto.schema.ProtoObjectFromInvokeStatic;
 import com.android.tools.r8.ir.analysis.proto.schema.ProtoObjectFromStaticGet;
@@ -184,7 +185,7 @@
       }
 
       return builder.build();
-    } catch (InvalidRawMessageInfoException e) {
+    } catch (InvalidRawMessageInfoException | ProtoMessageInfoBuilderException e) {
       // This should generally not happen, so leave an assert here just in case.
       assert false;
       return null;
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoMessageInfo.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoMessageInfo.java
index 28de572..6135fb5 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoMessageInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoMessageInfo.java
@@ -59,7 +59,7 @@
       oneOfObjects.add(new ProtoOneOfObjectPair(oneOfObject, oneOfCaseObject));
     }
 
-    public ProtoMessageInfo build() {
+    public ProtoMessageInfo build() throws ProtoMessageInfoBuilderException {
       removeDeadFields();
       removeUnusedSharedData();
       return new ProtoMessageInfo(dynamicMethod, flags, fields, hasBitsObjects, oneOfObjects);
@@ -79,7 +79,7 @@
       }
     }
 
-    private void removeUnusedSharedData() {
+    private void removeUnusedSharedData() throws ProtoMessageInfoBuilderException {
       if (fields == null || fields.isEmpty()) {
         oneOfObjects = null;
         hasBitsObjects = null;
@@ -149,6 +149,13 @@
           i++;
         }
 
+        // We should not have any bits pointing to dead proto fields.
+        for (ProtoFieldObject hasBitsObject : hasBitsObjects) {
+          if (hasBitsObject.isDeadProtoFieldObject()) {
+            throw new ProtoMessageInfoBuilderException();
+          }
+        }
+
         assert hasBitsObjects.stream().noneMatch(ProtoFieldObject::isDeadProtoFieldObject);
       }
 
@@ -171,6 +178,10 @@
     }
   }
 
+  public static class ProtoMessageInfoBuilderException extends Exception {
+    private ProtoMessageInfoBuilderException() {}
+  }
+
   private final ProgramMethod dynamicMethod;
   private final int flags;
 
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/ArrayTypeElement.java b/src/main/java/com/android/tools/r8/ir/analysis/type/ArrayTypeElement.java
index 4821b2b..f33a1d4 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/ArrayTypeElement.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/ArrayTypeElement.java
@@ -56,6 +56,11 @@
     return nesting;
   }
 
+  @Override
+  public boolean isPrimitiveArrayType() {
+    return memberTypeLattice.isPrimitiveType();
+  }
+
   public TypeElement getMemberType() {
     return memberTypeLattice;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/TypeElement.java b/src/main/java/com/android/tools/r8/ir/analysis/type/TypeElement.java
index 10f5ece..095333f 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/TypeElement.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/TypeElement.java
@@ -262,6 +262,10 @@
     return false;
   }
 
+  public boolean isPrimitiveArrayType() {
+    return false;
+  }
+
   public ArrayTypeElement asArrayType() {
     return null;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
index 042441e..c1d51db 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
@@ -2251,6 +2251,15 @@
   }
 
   private Value getUninitializedDebugLocalValue(int register, ValueTypeConstraint typeConstraint) {
+    if (appView.options().invalidDebugInfoStrict) {
+      throw new InvalidDebugInfoException(
+          "Information in locals-table is invalid. "
+              + "Local refers to uninitialized register: "
+              + register
+              + " with constraint "
+              + typeConstraint
+              + ".");
+    }
     // A debug initiated value must have a precise type constraint.
     assert typeConstraint.isPrecise();
     TypeElement type = typeConstraint.isObject() ? getNull() : typeConstraint.toPrimitiveType();
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
index 7861771..a6203e9 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
@@ -1724,6 +1724,12 @@
     if (enumUnboxer != null) {
       enumUnboxer.recordEnumState(method.getHolder(), staticFieldValues);
     }
+    if (appView.options().protoShrinking().enableRemoveProtoEnumSwitchMap()) {
+      appView
+          .protoShrinker()
+          .protoEnumSwitchMapRemover
+          .recordStaticValues(method.getHolder(), staticFieldValues);
+    }
     methodOptimizationInfoCollector.collectMethodOptimizationInfo(
         method, code, feedback, dynamicTypeOptimization, instanceFieldInitializationInfos, timing);
   }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
index cf605e2..76e3b0f 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
@@ -6,6 +6,7 @@
 import static com.android.tools.r8.graph.UseRegistry.MethodHandleUse.NOT_ARGUMENT_TO_LAMBDA_METAFACTORY;
 import static com.android.tools.r8.ir.code.Invoke.Type.STATIC;
 import static com.android.tools.r8.ir.code.Invoke.Type.VIRTUAL;
+import static com.android.tools.r8.ir.code.Opcodes.ASSUME;
 import static com.android.tools.r8.ir.code.Opcodes.CHECK_CAST;
 import static com.android.tools.r8.ir.code.Opcodes.CONST_CLASS;
 import static com.android.tools.r8.ir.code.Opcodes.CONST_METHOD_HANDLE;
@@ -56,6 +57,7 @@
 import com.android.tools.r8.ir.analysis.type.DestructivePhiTypeUpdater;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.analysis.value.SingleNumberValue;
+import com.android.tools.r8.ir.code.Assume;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.CatchHandlers;
 import com.android.tools.r8.ir.code.CheckCast;
@@ -595,6 +597,50 @@
             }
             break;
 
+          case ASSUME:
+            {
+              // TODO(b/174543992): It's not clear we should rewrite the assumes here. The code
+              // present fixes the problem for enum unboxing, but not for lambda merging.
+              // The LensCodeRewriter is run before most assume instructions are inserted, however,
+              // the call site optimization may propagate assumptions at IR building time, and such
+              // assumes are already present.
+              // R8 clears the assumes if the type is rewritten to a primitive type.
+              Assume assume = current.asAssume();
+              if (assume.hasOutValue()) {
+                TypeElement type = assume.getOutType();
+                TypeElement substituted =
+                    type.fixupClassTypeReferences(graphLens::lookupType, appView);
+                if (substituted != type) {
+                  assert type.isArrayType() || type.isClassType();
+                  if (substituted.isPrimitiveType()) {
+                    assert type.isClassType();
+                    assert appView.unboxedEnums().isUnboxedEnum(type.asClassType().getClassType());
+                    // Any assumption of a class type being converted to a primitive type is
+                    // invalid. Dynamic type is irrelevant and non null is incorrect.
+                    assume.outValue().replaceUsers(assume.src());
+                    iterator.removeOrReplaceByDebugLocalRead();
+                  } else if (substituted.isPrimitiveArrayType()) {
+                    assert type.isArrayType();
+                    // Non-null assumptions on a class array type being converted to a primitive
+                    // array type remains, but dynamic type becomes irrelevant.
+                    assume.unsetDynamicTypeAssumption();
+                    if (assume.hasNonNullAssumption()) {
+                      current.outValue().setType(substituted);
+                      affectedPhis.addAll(current.outValue().uniquePhiUsers());
+                    } else {
+                      iterator.removeOrReplaceByDebugLocalRead();
+                    }
+                  } else {
+                    assert !substituted.isPrimitiveType();
+                    assert !substituted.isPrimitiveArrayType();
+                    current.outValue().setType(substituted);
+                    affectedPhis.addAll(current.outValue().uniquePhiUsers());
+                  }
+                }
+              }
+            }
+            break;
+
           default:
             if (current.hasOutValue()) {
               // For all other instructions, substitute any changed type.
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 d73a7c1..42787b7 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
@@ -240,7 +240,6 @@
         }
 
         MethodAccessFlags newFlags = virtual.accessFlags.copy();
-        newFlags.unsetBridge();
         newFlags.promoteToStatic();
         DexEncodedMethod.setDebugInfoWithFakeThisParameter(
             code, companionMethod.getArity(), appView);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadElimination.java b/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadElimination.java
index cabff87..10b8b25 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadElimination.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadElimination.java
@@ -54,11 +54,12 @@
 public class RedundantFieldLoadElimination {
 
   private static final int MAX_CAPACITY = 10000;
-  private static final int MAX_CAPACITY_PER_BLOCK = 50;
+  private static final int MIN_CAPACITY_PER_BLOCK = 50;
 
   private final AppView<?> appView;
   private final ProgramMethod method;
   private final IRCode code;
+  private final int maxCapacityPerBlock;
 
   // Values that may require type propagation.
   private final Set<Value> affectedValues = Sets.newIdentityHashSet();
@@ -74,6 +75,7 @@
     this.appView = appView;
     this.method = code.context();
     this.code = code;
+    this.maxCapacityPerBlock = Math.max(MIN_CAPACITY_PER_BLOCK, MAX_CAPACITY / code.blocks.size());
   }
 
   public static boolean shouldRun(AppView<?> appView, IRCode code) {
@@ -185,7 +187,7 @@
         // Already visited.
         continue;
       }
-      activeState = activeStates.computeActiveStateOnBlockEntry(head);
+      activeState = activeStates.computeActiveStateOnBlockEntry(head, maxCapacityPerBlock);
       activeStates.removeDeadBlockExitStates(head, pendingNormalSuccessors);
       BasicBlock block = head;
       BasicBlock end = null;
@@ -444,19 +446,20 @@
 
     private int capacity = MAX_CAPACITY;
 
-    BlockState computeActiveStateOnBlockEntry(BasicBlock block) {
+    BlockState computeActiveStateOnBlockEntry(BasicBlock block, int maxCapacityPerBlock) {
       if (block.isEntry()) {
-        return new BlockState();
+        return new BlockState(maxCapacityPerBlock);
       }
       List<BasicBlock> predecessors = block.getPredecessors();
       Iterator<BasicBlock> predecessorIterator = predecessors.iterator();
-      BlockState state = new BlockState(activeStateAtExit.get(predecessorIterator.next()));
+      BlockState state =
+          new BlockState(maxCapacityPerBlock, activeStateAtExit.get(predecessorIterator.next()));
       while (predecessorIterator.hasNext()) {
         BasicBlock predecessor = predecessorIterator.next();
         BlockState predecessorExitState = activeStateAtExit.get(predecessor);
         if (predecessorExitState == null) {
           // Not processed yet.
-          return new BlockState();
+          return new BlockState(maxCapacityPerBlock);
         }
         state.intersect(predecessorExitState);
       }
@@ -479,7 +482,7 @@
 
     private void ensureCapacity(BlockState state) {
       int stateSize = state.size();
-      assert stateSize <= MAX_CAPACITY_PER_BLOCK;
+      assert stateSize <= state.maxCapacity;
       int numberOfItemsToRemove = stateSize - capacity;
       if (numberOfItemsToRemove <= 0) {
         return;
@@ -568,9 +571,14 @@
 
     private LinkedHashMap<DexField, FieldValue> nonFinalStaticFieldValues;
 
-    public BlockState() {}
+    private final int maxCapacity;
 
-    public BlockState(BlockState state) {
+    public BlockState(int maxCapacity) {
+      this.maxCapacity = maxCapacity;
+    }
+
+    public BlockState(int maxCapacity, BlockState state) {
+      this(maxCapacity);
       if (state != null) {
         if (state.finalInstanceFieldValues != null && !state.finalInstanceFieldValues.isEmpty()) {
           finalInstanceFieldValues = new LinkedHashMap<>();
@@ -606,8 +614,8 @@
 
     public void ensureCapacityForNewElement() {
       int size = size();
-      assert size <= MAX_CAPACITY_PER_BLOCK;
-      if (size == MAX_CAPACITY_PER_BLOCK) {
+      assert size <= maxCapacity;
+      if (size == maxCapacity) {
         reduceSize(1);
       }
     }
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 b7addaa..88eedce 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
@@ -307,6 +307,20 @@
         // is null, the same NPE happens at runtime, we can assume the value is non null in the
         // switch map after the ordinal call.
         SingleNumberValue ordinalValue = getOrdinalValue(code, abstractValue, true);
+        if (ordinalValue == null
+            && appView.options().protoShrinking().enableRemoveProtoEnumSwitchMap()) {
+          ordinalValue =
+              appView
+                  .protoShrinker()
+                  .protoEnumSwitchMapRemover
+                  .getOrdinal(
+                      appView.programDefinitionFor(info.enumClass, code.context()),
+                      enumInstanceField,
+                      appView
+                          .appInfo()
+                          .resolveField(factory.enumMembers.ordinalField, code.context())
+                          .getResolvedField());
+        }
         if (ordinalValue == null) {
           return null;
         }
diff --git a/src/main/java/com/android/tools/r8/repackaging/Repackaging.java b/src/main/java/com/android/tools/r8/repackaging/Repackaging.java
index 79b2e1e..51e419b 100644
--- a/src/main/java/com/android/tools/r8/repackaging/Repackaging.java
+++ b/src/main/java/com/android/tools/r8/repackaging/Repackaging.java
@@ -8,8 +8,11 @@
 import static com.android.tools.r8.utils.DescriptorUtils.DESCRIPTOR_PACKAGE_SEPARATOR;
 import static com.android.tools.r8.utils.DescriptorUtils.INNER_CLASS_SEPARATOR;
 
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexField;
 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.DexType;
 import com.android.tools.r8.graph.DirectMappedDexApplication;
@@ -17,14 +20,21 @@
 import com.android.tools.r8.graph.ProgramPackage;
 import com.android.tools.r8.graph.ProgramPackageCollection;
 import com.android.tools.r8.graph.SortedProgramPackageCollection;
+import com.android.tools.r8.graph.TreeFixerBase;
+import com.android.tools.r8.repackaging.RepackagingLens.Builder;
+import com.android.tools.r8.shaking.AnnotationFixer;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.ProguardConfiguration;
+import com.android.tools.r8.synthesis.CommittedItems;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.Timing;
 import com.google.common.collect.BiMap;
 import com.google.common.collect.HashBiMap;
+import java.util.ArrayList;
+import java.util.Collection;
 import java.util.HashSet;
 import java.util.Iterator;
+import java.util.List;
 import java.util.Set;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
@@ -63,6 +73,55 @@
     return lens;
   }
 
+  public static boolean verifyIdentityRepackaging(
+      AppView<? extends AppInfoWithClassHierarchy> appView) {
+    // Running the tree fixer with an identity mapping helps ensure that the fixup of of items is
+    // complete as the rewrite replaces all items regardless of repackaging.
+    // The identity mapping should result in no move callbacks being called.
+    Collection<DexProgramClass> newProgramClasses =
+        new TreeFixerBase(appView) {
+          @Override
+          public DexType mapClassType(DexType type) {
+            return type;
+          }
+
+          @Override
+          public void recordFieldChange(DexField from, DexField to) {
+            assert false;
+          }
+
+          @Override
+          public void recordMethodChange(DexMethod from, DexMethod to) {
+            assert false;
+          }
+
+          @Override
+          public void recordClassChange(DexType from, DexType to) {
+            assert false;
+          }
+        }.fixupClasses(appView.appInfo().classesWithDeterministicOrder());
+    CommittedItems committedItems =
+        appView
+            .getSyntheticItems()
+            .commit(
+                appView
+                    .appInfo()
+                    .app()
+                    .builder()
+                    .replaceProgramClasses(new ArrayList<>(newProgramClasses))
+                    .build());
+    if (appView.appInfo().hasLiveness()) {
+      appView
+          .withLiveness()
+          .setAppInfo(appView.withLiveness().appInfo().rebuildWithLiveness(committedItems));
+    } else {
+      appView
+          .withClassHierarchy()
+          .setAppInfo(appView.appInfo().rebuildWithClassHierarchy(committedItems));
+    }
+    return true;
+  }
+
   private RepackagingLens run(
       DirectMappedDexApplication.Builder appBuilder, ExecutorService executorService)
       throws ExecutionException {
@@ -80,7 +139,48 @@
     if (mappings.isEmpty()) {
       return null;
     }
-    return new RepackagingTreeFixer(appBuilder, appView, mappings).run();
+    RepackagingLens.Builder lensBuilder = new RepackagingLens.Builder();
+    List<DexProgramClass> newProgramClasses =
+        new ArrayList<>(
+            new RepackagingTreeFixer(appView, mappings, lensBuilder)
+                .fixupClasses(appView.appInfo().classesWithDeterministicOrder()));
+    appBuilder.replaceProgramClasses(newProgramClasses);
+    RepackagingLens lens = lensBuilder.build(appView);
+    new AnnotationFixer(lens).run(appBuilder.getProgramClasses());
+    return lens;
+  }
+
+  private static class RepackagingTreeFixer extends TreeFixerBase {
+
+    private final BiMap<DexType, DexType> mappings;
+    private final Builder lensBuilder;
+
+    public RepackagingTreeFixer(
+        AppView<?> appView, BiMap<DexType, DexType> mappings, Builder lensBuilder) {
+      super(appView);
+      this.mappings = mappings;
+      this.lensBuilder = lensBuilder;
+    }
+
+    @Override
+    public DexType mapClassType(DexType type) {
+      return mappings.getOrDefault(type, type);
+    }
+
+    @Override
+    public void recordFieldChange(DexField from, DexField to) {
+      lensBuilder.recordMove(from, to);
+    }
+
+    @Override
+    public void recordMethodChange(DexMethod from, DexMethod to) {
+      lensBuilder.recordMove(from, to);
+    }
+
+    @Override
+    public void recordClassChange(DexType from, DexType to) {
+      lensBuilder.recordMove(from, to);
+    }
   }
 
   private void processPackagesInDesiredLocation(
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 90c1cca..ef0bff7 100644
--- a/src/main/java/com/android/tools/r8/retrace/Retrace.java
+++ b/src/main/java/com/android/tools/r8/retrace/Retrace.java
@@ -15,13 +15,8 @@
 import com.android.tools.r8.retrace.RetraceCommand.ProguardMapProducer;
 import com.android.tools.r8.retrace.internal.PlainStackTraceVisitor;
 import com.android.tools.r8.retrace.internal.RetraceAbortException;
-import com.android.tools.r8.retrace.internal.RetraceCommandLineResult;
 import com.android.tools.r8.retrace.internal.RetraceRegularExpression;
-import com.android.tools.r8.retrace.internal.RetracerImpl;
-import com.android.tools.r8.retrace.internal.StackTraceElementProxyRetracerImpl;
-import com.android.tools.r8.retrace.internal.StackTraceElementProxyRetracerImpl.RetraceStackTraceProxyImpl;
 import com.android.tools.r8.retrace.internal.StackTraceElementStringProxy;
-import com.android.tools.r8.retrace.internal.StackTraceVisitor;
 import com.android.tools.r8.utils.Box;
 import com.android.tools.r8.utils.ExceptionDiagnostic;
 import com.android.tools.r8.utils.OptionsParsing;
@@ -43,6 +38,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Scanner;
+import java.util.function.BiConsumer;
 
 /**
  * A retrace tool for obfuscated stack traces.
@@ -170,61 +166,18 @@
     try {
       Timing timing = Timing.create("R8 retrace", command.printMemory());
       timing.begin("Read proguard map");
-      RetracerImpl retracer =
-          RetracerImpl.create(command.proguardMapProducer, command.diagnosticsHandler);
+      Retracer retracer =
+          Retracer.createDefault(command.proguardMapProducer, command.diagnosticsHandler);
       timing.end();
-      RetraceCommandLineResult result;
       timing.begin("Parse and Retrace");
       StackTraceVisitor<StackTraceElementStringProxy> stackTraceVisitor =
           command.regularExpression != null
-              ? new RetraceRegularExpression(
-                  retracer, command.stackTrace, command.regularExpression)
+              ? new RetraceRegularExpression(command.stackTrace, command.regularExpression)
               : new PlainStackTraceVisitor(command.stackTrace, command.diagnosticsHandler);
-        StackTraceElementProxyRetracer<StackTraceElementStringProxy> proxyRetracer =
-            new StackTraceElementProxyRetracerImpl<>(retracer);
-        List<String> retracedStrings = new ArrayList<>();
-      stackTraceVisitor.forEach(
-          stackTraceElement -> {
-            Box<List<RetraceStackTraceProxyImpl<StackTraceElementStringProxy>>> currentList =
-                new Box<>();
-            Map<
-                    RetraceStackTraceProxyImpl<StackTraceElementStringProxy>,
-                    List<RetraceStackTraceProxyImpl<StackTraceElementStringProxy>>>
-                ambiguousBlocks = new HashMap<>();
-            proxyRetracer
-                .retrace(stackTraceElement)
-                .forEach(
-                    retracedElement -> {
-                      if (retracedElement.isTopFrame() || !retracedElement.hasRetracedClass()) {
-                        List<RetraceStackTraceProxyImpl<StackTraceElementStringProxy>> block =
-                            new ArrayList<>();
-                        ambiguousBlocks.put(retracedElement, block);
-                        currentList.set(block);
-                      }
-                      currentList.get().add(retracedElement);
-                    });
-            ambiguousBlocks.keySet().stream()
-                .sorted()
-                .forEach(
-                    topFrame -> {
-                      ambiguousBlocks
-                          .get(topFrame)
-                          .forEach(
-                              frame -> {
-                                StackTraceElementStringProxy originalItem = frame.getOriginalItem();
-                                retracedStrings.add(
-                                    originalItem.toRetracedItem(
-                                        frame, !currentList.isSet(), command.isVerbose));
-                                // Use the current list as indicator for us seeing the first
-                                // sorted element.
-                                currentList.set(null);
-                              });
-                    });
-          });
-        result = new RetraceCommandLineResult(retracedStrings);
       timing.end();
       timing.begin("Report result");
-      command.retracedStackTraceConsumer.accept(result.getNodes());
+      command.retracedStackTraceConsumer.accept(
+          runInternal(stackTraceVisitor, retracer, command.isVerbose));
       timing.end();
       if (command.printTimes()) {
         timing.report();
@@ -235,6 +188,75 @@
     }
   }
 
+  /**
+   * Entry point for running retrace with default parsing on a stack trace
+   *
+   * @param retracer the retracer used to parse each stack trace frame.
+   * @param stackTrace the stack trace to be parsed.
+   * @param isVerbose if the output should embed verbose information.
+   * @return the retraced strings in flat format
+   */
+  public static List<String> run(Retracer retracer, List<String> stackTrace, boolean isVerbose) {
+    return runInternal(
+        new RetraceRegularExpression(stackTrace, DEFAULT_REGULAR_EXPRESSION), retracer, isVerbose);
+  }
+
+  private static List<String> runInternal(
+      StackTraceVisitor<StackTraceElementStringProxy> stackTraceVisitor,
+      Retracer retracer,
+      boolean isVerbose) {
+    List<String> retracedStrings = new ArrayList<>();
+    Box<StackTraceElementStringProxy> lastReportedFrame = new Box<>();
+    run(
+        stackTraceVisitor,
+        StackTraceElementProxyRetracer.createDefault(retracer),
+        (stackTraceElement, frames) -> {
+          frames.forEach(
+              frame -> {
+                StackTraceElementStringProxy originalItem = frame.getOriginalItem();
+                retracedStrings.add(
+                    originalItem.toRetracedItem(
+                        frame, lastReportedFrame.get() == stackTraceElement, isVerbose));
+                lastReportedFrame.set(stackTraceElement);
+              });
+        });
+    return retracedStrings;
+  }
+
+  /**
+   * @param stackTraceVisitor the stack trace visitor.
+   * @param proxyRetracer the retracer used to parse each stack trace frame.
+   * @param resultConsumer consumer to accept each parsed stack trace frame. The stack-trace element
+   *     is guaranteed to be unique per line.
+   */
+  public static <T extends StackTraceElementProxy<?>> void run(
+      StackTraceVisitor<T> stackTraceVisitor,
+      StackTraceElementProxyRetracer<T> proxyRetracer,
+      BiConsumer<T, List<RetraceStackTraceProxy<T>>> resultConsumer) {
+    stackTraceVisitor.forEach(
+        stackTraceElement -> {
+          Box<List<RetraceStackTraceProxy<T>>> currentList = new Box<>();
+          Map<RetraceStackTraceProxy<T>, List<RetraceStackTraceProxy<T>>> ambiguousBlocks =
+              new HashMap<>();
+          proxyRetracer
+              .retrace(stackTraceElement)
+              .forEach(
+                  retracedElement -> {
+                    if (retracedElement.isTopFrame() || !retracedElement.hasRetracedClass()) {
+                      List<RetraceStackTraceProxy<T>> block = new ArrayList<>();
+                      ambiguousBlocks.put(retracedElement, block);
+                      currentList.set(block);
+                    }
+                    currentList.get().add(retracedElement);
+                  });
+          ambiguousBlocks.keySet().stream()
+              .sorted()
+              .forEach(
+                  topFrame ->
+                      resultConsumer.accept(stackTraceElement, ambiguousBlocks.get(topFrame)));
+        });
+  }
+
   public static void run(String[] args) throws RetraceFailedException {
     // To be compatible with standard retrace and remapper, we translate -arg into --arg.
     String[] mappedArgs = new String[args.length];
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 f9d0d6f..6751a2a 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetraceClassResult.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceClassResult.java
@@ -33,6 +33,8 @@
 
   RetraceClassResult forEach(Consumer<Element> resultConsumer);
 
+  boolean hasRetraceResult();
+
   boolean isAmbiguous();
 
   @Keep
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceCommand.java b/src/main/java/com/android/tools/r8/retrace/RetraceCommand.java
index b6247df..d9f67ef 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetraceCommand.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceCommand.java
@@ -9,6 +9,7 @@
 import java.util.List;
 import java.util.function.Consumer;
 
+@Keep
 public class RetraceCommand {
 
   final boolean isVerbose;
@@ -144,7 +145,6 @@
     }
   }
 
-  @Keep
   public interface ProguardMapProducer {
     String get();
   }
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/RetraceInvalidStackTraceLineDiagnostics.java b/src/main/java/com/android/tools/r8/retrace/RetraceInvalidStackTraceLineDiagnostics.java
similarity index 92%
rename from src/main/java/com/android/tools/r8/retrace/internal/RetraceInvalidStackTraceLineDiagnostics.java
rename to src/main/java/com/android/tools/r8/retrace/RetraceInvalidStackTraceLineDiagnostics.java
index 547534f..d2a6827 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/RetraceInvalidStackTraceLineDiagnostics.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceInvalidStackTraceLineDiagnostics.java
@@ -2,7 +2,7 @@
 // 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.internal;
+package com.android.tools.r8.retrace;
 
 import com.android.tools.r8.Diagnostic;
 import com.android.tools.r8.Keep;
@@ -40,7 +40,7 @@
     return message;
   }
 
-  static RetraceInvalidStackTraceLineDiagnostics createNull(int lineNumber) {
+  public static RetraceInvalidStackTraceLineDiagnostics createNull(int lineNumber) {
     return new RetraceInvalidStackTraceLineDiagnostics(lineNumber, NULL_STACK_TRACE_LINE_MESSAGE);
   }
 
diff --git a/src/main/java/com/android/tools/r8/retrace/Retracer.java b/src/main/java/com/android/tools/r8/retrace/Retracer.java
index 57b7607..ed1d338 100644
--- a/src/main/java/com/android/tools/r8/retrace/Retracer.java
+++ b/src/main/java/com/android/tools/r8/retrace/Retracer.java
@@ -4,11 +4,14 @@
 
 package com.android.tools.r8.retrace;
 
+import com.android.tools.r8.DiagnosticsHandler;
 import com.android.tools.r8.Keep;
 import com.android.tools.r8.references.ClassReference;
 import com.android.tools.r8.references.FieldReference;
 import com.android.tools.r8.references.MethodReference;
 import com.android.tools.r8.references.TypeReference;
+import com.android.tools.r8.retrace.RetraceCommand.ProguardMapProducer;
+import com.android.tools.r8.retrace.internal.RetracerImpl;
 
 /** This is the main api interface for retrace. */
 @Keep
@@ -23,4 +26,9 @@
   RetraceFieldResult retraceField(FieldReference fieldReference);
 
   RetraceTypeResult retraceType(TypeReference typeReference);
+
+  static Retracer createDefault(
+      ProguardMapProducer proguardMapProducer, DiagnosticsHandler diagnosticsHandler) {
+    return RetracerImpl.create(proguardMapProducer, diagnosticsHandler);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/retrace/StackTraceElementProxyRetracer.java b/src/main/java/com/android/tools/r8/retrace/StackTraceElementProxyRetracer.java
index 17d9005..17de5f1 100644
--- a/src/main/java/com/android/tools/r8/retrace/StackTraceElementProxyRetracer.java
+++ b/src/main/java/com/android/tools/r8/retrace/StackTraceElementProxyRetracer.java
@@ -5,11 +5,16 @@
 package com.android.tools.r8.retrace;
 
 import com.android.tools.r8.Keep;
-import com.android.tools.r8.retrace.internal.StackTraceElementProxyRetracerImpl.RetraceStackTraceProxyImpl;
+import com.android.tools.r8.retrace.internal.StackTraceElementProxyRetracerImpl;
 import java.util.stream.Stream;
 
 @Keep
 public interface StackTraceElementProxyRetracer<T extends StackTraceElementProxy<?>> {
 
-  Stream<RetraceStackTraceProxyImpl<T>> retrace(T element);
+  Stream<RetraceStackTraceProxy<T>> retrace(T element);
+
+  static <T extends StackTraceElementProxy<?>> StackTraceElementProxyRetracer<T> createDefault(
+      Retracer retracer) {
+    return new StackTraceElementProxyRetracerImpl<>(retracer);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/StackTraceVisitor.java b/src/main/java/com/android/tools/r8/retrace/StackTraceVisitor.java
similarity index 77%
rename from src/main/java/com/android/tools/r8/retrace/internal/StackTraceVisitor.java
rename to src/main/java/com/android/tools/r8/retrace/StackTraceVisitor.java
index dfe937a..bf2de73 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/StackTraceVisitor.java
+++ b/src/main/java/com/android/tools/r8/retrace/StackTraceVisitor.java
@@ -2,11 +2,12 @@
 // 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.internal;
+package com.android.tools.r8.retrace;
 
-import com.android.tools.r8.retrace.StackTraceElementProxy;
+import com.android.tools.r8.Keep;
 import java.util.function.Consumer;
 
+@Keep
 public interface StackTraceVisitor<T extends StackTraceElementProxy<?>> {
 
   void forEach(Consumer<T> consumer);
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/PlainStackTraceVisitor.java b/src/main/java/com/android/tools/r8/retrace/internal/PlainStackTraceVisitor.java
index 7f7d7fc..90fedbd 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/PlainStackTraceVisitor.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/PlainStackTraceVisitor.java
@@ -7,6 +7,8 @@
 import static com.google.common.base.Predicates.not;
 
 import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.retrace.RetraceInvalidStackTraceLineDiagnostics;
+import com.android.tools.r8.retrace.StackTraceVisitor;
 import com.android.tools.r8.retrace.internal.StackTraceElementStringProxy.ClassNameType;
 import com.android.tools.r8.retrace.internal.StackTraceElementStringProxy.StackTraceElementStringProxyBuilder;
 import com.android.tools.r8.utils.DescriptorUtils;
@@ -165,8 +167,8 @@
               .registerClassName(classStartIndex, methodSeparator, ClassNameType.TYPENAME)
               .registerMethodName(methodSeparator + 1, parensStart);
       // Check if we have a filename and position.
-      int separatorIndex = firstCharFromIndex(line, parensStart, ':');
-      if (separatorIndex < parensEnd) {
+      int separatorIndex = line.lastIndexOf(':', parensEnd);
+      if (separatorIndex > -1 && separatorIndex < parensEnd) {
         builder.registerSourceFile(parensStart + 1, separatorIndex);
         builder.registerLineNumber(separatorIndex + 1, parensEnd);
       } else {
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/RetraceClassResultImpl.java b/src/main/java/com/android/tools/r8/retrace/internal/RetraceClassResultImpl.java
index 45b7fdd..15fdfdc 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/RetraceClassResultImpl.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/RetraceClassResultImpl.java
@@ -17,6 +17,7 @@
 import com.android.tools.r8.references.TypeReference;
 import com.android.tools.r8.retrace.RetraceClassResult;
 import com.android.tools.r8.retrace.RetraceFrameResult;
+import com.android.tools.r8.retrace.Retracer;
 import com.android.tools.r8.utils.Pair;
 import com.google.common.collect.ImmutableList;
 import java.util.ArrayList;
@@ -29,17 +30,17 @@
 
   private final ClassReference obfuscatedReference;
   private final ClassNamingForNameMapper mapper;
-  private final RetracerImpl retracer;
+  private final Retracer retracer;
 
   private RetraceClassResultImpl(
-      ClassReference obfuscatedReference, ClassNamingForNameMapper mapper, RetracerImpl retracer) {
+      ClassReference obfuscatedReference, ClassNamingForNameMapper mapper, Retracer retracer) {
     this.obfuscatedReference = obfuscatedReference;
     this.mapper = mapper;
     this.retracer = retracer;
   }
 
   static RetraceClassResultImpl create(
-      ClassReference obfuscatedReference, ClassNamingForNameMapper mapper, RetracerImpl retracer) {
+      ClassReference obfuscatedReference, ClassNamingForNameMapper mapper, Retracer retracer) {
     return new RetraceClassResultImpl(obfuscatedReference, mapper, retracer);
   }
 
@@ -162,7 +163,8 @@
         : mappedRangesForPosition;
   }
 
-  boolean hasRetraceResult() {
+  @Override
+  public boolean hasRetraceResult() {
     return mapper != null;
   }
 
@@ -196,7 +198,7 @@
         RetraceClassResultImpl classResult,
         List<Pair<ElementImpl, T>> mappings,
         D definition,
-        RetracerImpl retracer);
+        Retracer retracer);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/RetraceFieldResultImpl.java b/src/main/java/com/android/tools/r8/retrace/internal/RetraceFieldResultImpl.java
index 3a65b0a..73b7dbd 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/RetraceFieldResultImpl.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/RetraceFieldResultImpl.java
@@ -4,31 +4,30 @@
 
 package com.android.tools.r8.retrace.internal;
 
-import com.android.tools.r8.Keep;
 import com.android.tools.r8.naming.MemberNaming;
 import com.android.tools.r8.naming.MemberNaming.FieldSignature;
 import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.retrace.RetraceFieldResult;
 import com.android.tools.r8.retrace.RetraceSourceFileResult;
+import com.android.tools.r8.retrace.Retracer;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.Pair;
 import java.util.List;
 import java.util.function.Consumer;
 import java.util.stream.Stream;
 
-@Keep
 public class RetraceFieldResultImpl implements RetraceFieldResult {
 
   private final RetraceClassResultImpl classResult;
   private final List<Pair<RetraceClassResultImpl.ElementImpl, List<MemberNaming>>> memberNamings;
   private final FieldDefinition fieldDefinition;
-  private final RetracerImpl retracer;
+  private final Retracer retracer;
 
   RetraceFieldResultImpl(
       RetraceClassResultImpl classResult,
       List<Pair<RetraceClassResultImpl.ElementImpl, List<MemberNaming>>> memberNamings,
       FieldDefinition fieldDefinition,
-      RetracerImpl retracer) {
+      Retracer retracer) {
     this.classResult = classResult;
     this.memberNamings = memberNamings;
     this.fieldDefinition = fieldDefinition;
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/RetraceFrameResultImpl.java b/src/main/java/com/android/tools/r8/retrace/internal/RetraceFrameResultImpl.java
index 24c5a8e..35f1f63 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/RetraceFrameResultImpl.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/RetraceFrameResultImpl.java
@@ -13,6 +13,7 @@
 import com.android.tools.r8.retrace.RetraceSourceFileResult;
 import com.android.tools.r8.retrace.RetracedClassMember;
 import com.android.tools.r8.retrace.RetracedMethod;
+import com.android.tools.r8.retrace.Retracer;
 import com.android.tools.r8.utils.Pair;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Lists;
@@ -29,14 +30,14 @@
   private final MethodDefinition methodDefinition;
   private final int obfuscatedPosition;
   private final List<Pair<RetraceClassResultImpl.ElementImpl, List<MappedRange>>> mappedRanges;
-  private final RetracerImpl retracer;
+  private final Retracer retracer;
 
   public RetraceFrameResultImpl(
       RetraceClassResultImpl classResult,
       List<Pair<RetraceClassResultImpl.ElementImpl, List<MappedRange>>> mappedRanges,
       MethodDefinition methodDefinition,
       int obfuscatedPosition,
-      RetracerImpl retracer) {
+      Retracer retracer) {
     this.classResult = classResult;
     this.methodDefinition = methodDefinition;
     this.obfuscatedPosition = obfuscatedPosition;
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/RetraceMethodResultImpl.java b/src/main/java/com/android/tools/r8/retrace/internal/RetraceMethodResultImpl.java
index 93fa544..46d63bc 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/RetraceMethodResultImpl.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/RetraceMethodResultImpl.java
@@ -4,11 +4,11 @@
 
 package com.android.tools.r8.retrace.internal;
 
-import com.android.tools.r8.Keep;
 import com.android.tools.r8.naming.ClassNamingForNameMapper.MappedRange;
 import com.android.tools.r8.naming.ClassNamingForNameMapper.MappedRangesOfName;
 import com.android.tools.r8.references.MethodReference;
 import com.android.tools.r8.retrace.RetraceMethodResult;
+import com.android.tools.r8.retrace.Retracer;
 import com.android.tools.r8.utils.Pair;
 import com.google.common.collect.ImmutableList;
 import java.util.ArrayList;
@@ -16,19 +16,18 @@
 import java.util.function.Consumer;
 import java.util.stream.Stream;
 
-@Keep
 public class RetraceMethodResultImpl implements RetraceMethodResult {
 
   private final MethodDefinition methodDefinition;
   private final RetraceClassResultImpl classResult;
   private final List<Pair<RetraceClassResultImpl.ElementImpl, List<MappedRange>>> mappedRanges;
-  private final RetracerImpl retracer;
+  private final Retracer retracer;
 
   RetraceMethodResultImpl(
       RetraceClassResultImpl classResult,
       List<Pair<RetraceClassResultImpl.ElementImpl, List<MappedRange>>> mappedRanges,
       MethodDefinition methodDefinition,
-      RetracerImpl retracer) {
+      Retracer retracer) {
     this.classResult = classResult;
     this.mappedRanges = mappedRanges;
     this.methodDefinition = methodDefinition;
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/RetraceRegularExpression.java b/src/main/java/com/android/tools/r8/retrace/internal/RetraceRegularExpression.java
index ca9a2f8..d475594 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/RetraceRegularExpression.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/RetraceRegularExpression.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.retrace.internal;
 
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.retrace.StackTraceVisitor;
 import com.android.tools.r8.retrace.internal.StackTraceElementStringProxy.ClassNameType;
 import com.android.tools.r8.retrace.internal.StackTraceElementStringProxy.StackTraceElementStringProxyBuilder;
 import java.util.ArrayList;
@@ -15,7 +16,6 @@
 
 public class RetraceRegularExpression implements StackTraceVisitor<StackTraceElementStringProxy> {
 
-  private final RetracerImpl retracer;
   private final List<String> stackTrace;
   private final String regularExpression;
 
@@ -35,9 +35,7 @@
   private static final String CAPTURE_GROUP_PREFIX = "captureGroup";
   private static final int FIRST_CAPTURE_GROUP_INDEX = 0;
 
-  public RetraceRegularExpression(
-      RetracerImpl retracer, List<String> stackTrace, String regularExpression) {
-    this.retracer = retracer;
+  public RetraceRegularExpression(List<String> stackTrace, String regularExpression) {
     this.stackTrace = stackTrace;
     this.regularExpression = regularExpression;
   }
@@ -168,6 +166,8 @@
     }
   }
 
+  private static final String anyLetterWithMarkers = "\\p{L}\\p{M}*+";
+
   // TODO(b/145731185): Extend support for identifiers with strings inside back ticks.
   private static final String javaIdentifierSegment =
       "\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}*";
@@ -275,7 +275,7 @@
 
     @Override
     String subExpression() {
-      return "(?:(\\w*[\\. ])?(\\w*)?)";
+      return "(?:([" + anyLetterWithMarkers + "_: \\.]*[" + anyLetterWithMarkers + "_\\.])?)";
     }
 
     @Override
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/RetraceTypeResultImpl.java b/src/main/java/com/android/tools/r8/retrace/internal/RetraceTypeResultImpl.java
index 0680800..b48d5f3 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/RetraceTypeResultImpl.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/RetraceTypeResultImpl.java
@@ -6,20 +6,21 @@
 
 import com.android.tools.r8.references.TypeReference;
 import com.android.tools.r8.retrace.RetraceTypeResult;
+import com.android.tools.r8.retrace.Retracer;
 import java.util.function.Consumer;
 import java.util.stream.Stream;
 
 public class RetraceTypeResultImpl implements RetraceTypeResult {
 
   private final TypeReference obfuscatedType;
-  private final RetracerImpl retracer;
+  private final Retracer retracer;
 
-  private RetraceTypeResultImpl(TypeReference obfuscatedType, RetracerImpl retracer) {
+  private RetraceTypeResultImpl(TypeReference obfuscatedType, Retracer retracer) {
     this.obfuscatedType = obfuscatedType;
     this.retracer = retracer;
   }
 
-  static RetraceTypeResultImpl create(TypeReference obfuscatedType, RetracerImpl retracer) {
+  static RetraceTypeResultImpl create(TypeReference obfuscatedType, Retracer retracer) {
     return new RetraceTypeResultImpl(obfuscatedType, retracer);
   }
 
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/RetraceUtils.java b/src/main/java/com/android/tools/r8/retrace/internal/RetraceUtils.java
index 46e8ddc..dc96687 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/RetraceUtils.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/RetraceUtils.java
@@ -10,11 +10,13 @@
 import com.android.tools.r8.references.MethodReference;
 import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.references.TypeReference;
+import com.android.tools.r8.retrace.RetraceClassResult;
 import com.android.tools.r8.retrace.RetraceClassResult.Element;
 import com.android.tools.r8.retrace.RetraceSourceFileResult;
 import com.android.tools.r8.retrace.RetracedClass;
 import com.android.tools.r8.retrace.RetracedMethod;
 import com.android.tools.r8.retrace.RetracedMethod.KnownRetracedMethod;
+import com.android.tools.r8.retrace.Retracer;
 import com.android.tools.r8.utils.Box;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.google.common.collect.Sets;
@@ -72,7 +74,7 @@
   }
 
   static RetraceSourceFileResult getSourceFile(
-      Element classElement, RetracedClass context, String sourceFile, RetracerImpl retracer) {
+      Element classElement, RetracedClass context, String sourceFile, Retracer retracer) {
     // If no context is specified always retrace using the found class element.
     if (context == null) {
       return classElement.retraceSourceFile(sourceFile);
@@ -80,8 +82,7 @@
     if (context.equals(classElement.getRetracedClass())) {
       return classElement.retraceSourceFile(sourceFile);
     } else {
-      RetraceClassResultImpl contextClassResult =
-          retracer.retraceClass(context.getClassReference());
+      RetraceClassResult contextClassResult = retracer.retraceClass(context.getClassReference());
       assert !contextClassResult.isAmbiguous();
       if (contextClassResult.hasRetraceResult()) {
         Box<RetraceSourceFileResult> retraceSourceFile = new Box<>();
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/RetracedClassImpl.java b/src/main/java/com/android/tools/r8/retrace/internal/RetracedClassImpl.java
index b2da5d8..47c41b9 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/RetracedClassImpl.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/RetracedClassImpl.java
@@ -4,11 +4,9 @@
 
 package com.android.tools.r8.retrace.internal;
 
-import com.android.tools.r8.Keep;
 import com.android.tools.r8.references.ClassReference;
 import com.android.tools.r8.retrace.RetracedClass;
 
-@Keep
 public final class RetracedClassImpl implements RetracedClass {
 
   private final ClassReference classReference;
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/RetracedFieldImpl.java b/src/main/java/com/android/tools/r8/retrace/internal/RetracedFieldImpl.java
index aee0cde..9dd96c8 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/RetracedFieldImpl.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/RetracedFieldImpl.java
@@ -4,13 +4,11 @@
 
 package com.android.tools.r8.retrace.internal;
 
-import com.android.tools.r8.Keep;
 import com.android.tools.r8.references.FieldReference;
 import com.android.tools.r8.references.TypeReference;
 import com.android.tools.r8.retrace.RetracedField;
 import java.util.Objects;
 
-@Keep
 public abstract class RetracedFieldImpl implements RetracedField {
 
   private RetracedFieldImpl() {}
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/RetracedMethodImpl.java b/src/main/java/com/android/tools/r8/retrace/internal/RetracedMethodImpl.java
index 1f4ad68..ec4ff42 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/RetracedMethodImpl.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/RetracedMethodImpl.java
@@ -4,7 +4,6 @@
 
 package com.android.tools.r8.retrace.internal;
 
-import com.android.tools.r8.Keep;
 import com.android.tools.r8.references.MethodReference;
 import com.android.tools.r8.references.TypeReference;
 import com.android.tools.r8.retrace.RetracedMethod;
@@ -14,7 +13,6 @@
 import java.util.Objects;
 import java.util.Optional;
 
-@Keep
 public abstract class RetracedMethodImpl implements RetracedMethod {
 
   private static final int NO_POSITION = -1;
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/RetracedTypeImpl.java b/src/main/java/com/android/tools/r8/retrace/internal/RetracedTypeImpl.java
index d467cc1..38d1a83 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/RetracedTypeImpl.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/RetracedTypeImpl.java
@@ -4,13 +4,11 @@
 
 package com.android.tools.r8.retrace.internal;
 
-import com.android.tools.r8.Keep;
 import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.references.TypeReference;
 import com.android.tools.r8.retrace.RetracedType;
 import java.util.Objects;
 
-@Keep
 public final class RetracedTypeImpl implements RetracedType {
 
   private final TypeReference typeReference;
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/StackTraceElementProxyRetracerImpl.java b/src/main/java/com/android/tools/r8/retrace/internal/StackTraceElementProxyRetracerImpl.java
index a949fc1..c3453b5 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/StackTraceElementProxyRetracerImpl.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/StackTraceElementProxyRetracerImpl.java
@@ -8,11 +8,14 @@
 import com.android.tools.r8.references.TypeReference;
 import com.android.tools.r8.retrace.RetraceClassResult;
 import com.android.tools.r8.retrace.RetraceFieldResult;
+import com.android.tools.r8.retrace.RetraceFrameResult;
 import com.android.tools.r8.retrace.RetraceStackTraceProxy;
+import com.android.tools.r8.retrace.RetraceTypeResult;
 import com.android.tools.r8.retrace.RetracedClass;
 import com.android.tools.r8.retrace.RetracedField;
 import com.android.tools.r8.retrace.RetracedMethod;
 import com.android.tools.r8.retrace.RetracedType;
+import com.android.tools.r8.retrace.Retracer;
 import com.android.tools.r8.retrace.StackTraceElementProxy;
 import com.android.tools.r8.retrace.StackTraceElementProxyRetracer;
 import com.android.tools.r8.utils.Box;
@@ -27,18 +30,18 @@
 public class StackTraceElementProxyRetracerImpl<T extends StackTraceElementProxy<?>>
     implements StackTraceElementProxyRetracer<T> {
 
-  private final RetracerImpl retracer;
+  private final Retracer retracer;
 
-  public StackTraceElementProxyRetracerImpl(RetracerImpl retracer) {
+  public StackTraceElementProxyRetracerImpl(Retracer retracer) {
     this.retracer = retracer;
   }
 
   @Override
-  public Stream<RetraceStackTraceProxyImpl<T>> retrace(T element) {
+  public Stream<RetraceStackTraceProxy<T>> retrace(T element) {
     if (!element.hasClassName()) {
       return Stream.of(RetraceStackTraceProxyImpl.builder(element).build());
     }
-    RetraceClassResultImpl classResult = retracer.retraceClass(element.getClassReference());
+    RetraceClassResult classResult = retracer.retraceClass(element.getClassReference());
     if (element.hasMethodName()) {
       return retraceMethod(element, classResult);
     } else if (element.hasFieldName()) {
@@ -48,7 +51,7 @@
     }
   }
 
-  private Stream<RetraceStackTraceProxyImpl<T>> retraceClassOrType(
+  private Stream<RetraceStackTraceProxy<T>> retraceClassOrType(
       T element, RetraceClassResult classResult) {
     return classResult.stream()
         .flatMap(
@@ -76,15 +79,15 @@
                                     })));
   }
 
-  private Stream<RetraceStackTraceProxyImpl<T>> retraceMethod(
-      T element, RetraceClassResultImpl classResult) {
+  private Stream<RetraceStackTraceProxy<T>> retraceMethod(
+      T element, RetraceClassResult classResult) {
     return retraceFieldOrReturnType(element)
         .flatMap(
             fieldOrReturnTypeConsumer ->
                 retracedMethodArguments(element)
                     .flatMap(
                         argumentsConsumer -> {
-                          RetraceFrameResultImpl frameResult =
+                          RetraceFrameResult frameResult =
                               element.hasLineNumber()
                                   ? classResult.lookupFrame(
                                       element.getMethodName(), element.getLineNumber())
@@ -123,7 +126,7 @@
                         }));
   }
 
-  private Stream<RetraceStackTraceProxyImpl<T>> retraceField(
+  private Stream<RetraceStackTraceProxy<T>> retraceField(
       T element, RetraceClassResult classResult) {
     return retraceFieldOrReturnType(element)
         .flatMap(
@@ -166,7 +169,7 @@
       return Stream.of(proxy -> proxy.setRetracedFieldOrReturnType(RetracedTypeImpl.createVoid()));
     } else {
       TypeReference typeReference = Reference.typeFromTypeName(elementOrReturnType);
-      RetraceTypeResultImpl retraceTypeResult = retracer.retraceType(typeReference);
+      RetraceTypeResult retraceTypeResult = retracer.retraceType(typeReference);
       return retraceTypeResult.stream()
           .map(
               type ->
@@ -184,7 +187,7 @@
     if (!element.hasMethodArguments()) {
       return Stream.of(noEffect -> {});
     }
-    List<RetraceTypeResultImpl> retracedResults =
+    List<RetraceTypeResult> retracedResults =
         Arrays.stream(element.getMethodArguments().split(","))
             .map(typeName -> retracer.retraceType(Reference.typeFromTypeName(typeName)))
             .collect(Collectors.toList());
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/StackTraceElementStringProxy.java b/src/main/java/com/android/tools/r8/retrace/internal/StackTraceElementStringProxy.java
index f649598..5a76e44 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/StackTraceElementStringProxy.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/StackTraceElementStringProxy.java
@@ -10,11 +10,11 @@
 
 import com.android.tools.r8.references.ClassReference;
 import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.retrace.RetraceStackTraceProxy;
 import com.android.tools.r8.retrace.RetracedClass;
 import com.android.tools.r8.retrace.RetracedField;
 import com.android.tools.r8.retrace.RetracedType;
 import com.android.tools.r8.retrace.StackTraceElementProxy;
-import com.android.tools.r8.retrace.internal.StackTraceElementProxyRetracerImpl.RetraceStackTraceProxyImpl;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.StringUtils.BraceType;
 import com.android.tools.r8.utils.TriFunction;
@@ -136,7 +136,7 @@
   }
 
   public String toRetracedItem(
-      RetraceStackTraceProxyImpl<StackTraceElementStringProxy> retracedProxy,
+      RetraceStackTraceProxy<StackTraceElementStringProxy> retracedProxy,
       boolean printAmbiguous,
       boolean verbose) {
     StringBuilder sb = new StringBuilder();
@@ -334,7 +334,7 @@
     protected final int startIndex;
     protected final int endIndex;
     private final TriFunction<
-            RetraceStackTraceProxyImpl<StackTraceElementStringProxy>,
+            RetraceStackTraceProxy<StackTraceElementStringProxy>,
             StackTraceElementStringProxy,
             Boolean,
             String>
@@ -344,7 +344,7 @@
         int startIndex,
         int endIndex,
         TriFunction<
-                RetraceStackTraceProxyImpl<StackTraceElementStringProxy>,
+                RetraceStackTraceProxy<StackTraceElementStringProxy>,
                 StackTraceElementStringProxy,
                 Boolean,
                 String>
@@ -367,7 +367,7 @@
         int startIndex,
         int endIndex,
         TriFunction<
-                RetraceStackTraceProxyImpl<StackTraceElementStringProxy>,
+                RetraceStackTraceProxy<StackTraceElementStringProxy>,
                 StackTraceElementStringProxy,
                 Boolean,
                 String>
diff --git a/src/main/java/com/android/tools/r8/shaking/AnnotationFixer.java b/src/main/java/com/android/tools/r8/shaking/AnnotationFixer.java
index a67af38..4fdad6a 100644
--- a/src/main/java/com/android/tools/r8/shaking/AnnotationFixer.java
+++ b/src/main/java/com/android/tools/r8/shaking/AnnotationFixer.java
@@ -4,15 +4,21 @@
 
 package com.android.tools.r8.shaking;
 
+import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.DexAnnotation;
 import com.android.tools.r8.graph.DexAnnotationElement;
 import com.android.tools.r8.graph.DexEncodedAnnotation;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexReference;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.DexValue;
+import com.android.tools.r8.graph.DexValue.DexItemBasedValueString;
+import com.android.tools.r8.graph.DexValue.DexValueAnnotation;
 import com.android.tools.r8.graph.DexValue.DexValueArray;
+import com.android.tools.r8.graph.DexValue.DexValueEnum;
 import com.android.tools.r8.graph.DexValue.DexValueType;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.utils.ArrayUtils;
@@ -56,27 +62,65 @@
   }
 
   private DexAnnotationElement rewriteAnnotationElement(DexAnnotationElement original) {
-    DexValue rewrittenValue = rewriteValue(original.value);
+    DexValue rewrittenValue = rewriteComplexValue(original.value);
     if (rewrittenValue != original.value) {
       return new DexAnnotationElement(original.name, rewrittenValue);
     }
     return original;
   }
 
-  private DexValue rewriteValue(DexValue value) {
-    if (value.isDexValueType()) {
+  private DexValue rewriteComplexValue(DexValue value) {
+    if (value.isDexValueArray()) {
+      DexValue[] originalValues = value.asDexValueArray().getValues();
+      DexValue[] rewrittenValues =
+          ArrayUtils.map(DexValue[].class, originalValues, this::rewriteComplexValue);
+      if (rewrittenValues != originalValues) {
+        return new DexValueArray(rewrittenValues);
+      }
+    } else if (value.isDexValueAnnotation()) {
+      DexValueAnnotation original = value.asDexValueAnnotation();
+      DexEncodedAnnotation rewritten = rewriteEncodedAnnotation(original.getValue());
+      if (original.value == rewritten) {
+        return value;
+      }
+      return new DexValueAnnotation(rewritten);
+    }
+    return rewriteNestedValue(value);
+  }
+
+  private DexValue rewriteNestedValue(DexValue value) {
+    if (value.isDexItemBasedValueString()) {
+      DexItemBasedValueString valueString = value.asDexItemBasedValueString();
+      DexReference original = valueString.value;
+      DexReference rewritten = lens.lookupReference(original);
+      if (original != rewritten) {
+        return new DexItemBasedValueString(rewritten, valueString.getNameComputationInfo());
+      }
+    } else if (value.isDexValueEnum()) {
+      DexField original = value.asDexValueEnum().value;
+      DexField rewritten = lens.lookupField(original);
+      if (original != rewritten) {
+        return new DexValueEnum(rewritten);
+      }
+    } else if (value.isDexValueField()) {
+      throw new Unreachable("Unexpected field in annotation");
+    } else if (value.isDexValueMethod()) {
+      throw new Unreachable("Unexpected method in annotation");
+    } else if (value.isDexValueMethodHandle()) {
+      throw new Unreachable("Unexpected method handle in annotation");
+    } else if (value.isDexValueMethodType()) {
+      throw new Unreachable("Unexpected method type in annotation");
+    } else if (value.isDexValueString()) {
+      // If we identified references in the string it would be a DexItemBasedValueString.
+    } else if (value.isDexValueType()) {
       DexType originalType = value.asDexValueType().value;
       DexType rewrittenType = lens.lookupType(originalType);
       if (rewrittenType != originalType) {
         return new DexValueType(rewrittenType);
       }
-    } else if (value.isDexValueArray()) {
-      DexValue[] originalValues = value.asDexValueArray().getValues();
-      DexValue[] rewrittenValues =
-          ArrayUtils.map(DexValue[].class, originalValues, this::rewriteValue);
-      if (rewrittenValues != originalValues) {
-        return new DexValueArray(rewrittenValues);
-      }
+    } else {
+      // Assert that we have not forgotten a value.
+      assert !value.isNestedDexValue();
     }
     return value;
   }
diff --git a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
index 7f93d17..9061db8 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -267,16 +267,14 @@
     verify();
   }
 
-  private AppInfoWithLiveness(
-      AppInfoWithLiveness previous, CommittedItems committedItems, Set<DexType> removedTypes) {
+  private AppInfoWithLiveness(AppInfoWithLiveness previous, CommittedItems committedItems) {
     this(
         committedItems,
         previous.getClassToFeatureSplitMap(),
         previous.getMainDexClasses(),
         previous.deadProtoTypes,
         previous.missingTypes,
-        CollectionUtils.mergeSets(
-            Sets.difference(previous.liveTypes, removedTypes), committedItems.getCommittedTypes()),
+        CollectionUtils.mergeSets(previous.liveTypes, committedItems.getCommittedTypes()),
         previous.targetedMethods,
         previous.failedResolutionTargets,
         previous.bootstrapMethods,
@@ -925,8 +923,14 @@
    * Returns a copy of this AppInfoWithLiveness where the set of classes is pruned using the given
    * DexApplication object.
    */
+  @Override
   public AppInfoWithLiveness prunedCopyFrom(PrunedItems prunedItems) {
+    assert getClass() == AppInfoWithLiveness.class;
     assert checkIfObsolete();
+    if (prunedItems.isEmpty()) {
+      assert app() == prunedItems.getPrunedApp();
+      return this;
+    }
     if (prunedItems.hasRemovedClasses()) {
       // Rebuild the hierarchy.
       objectAllocationInfoCollection.mutate(
@@ -937,9 +941,8 @@
     return new AppInfoWithLiveness(this, prunedItems);
   }
 
-  public AppInfoWithLiveness rebuildWithLiveness(
-      CommittedItems committedItems, Set<DexType> removedTypes) {
-    return new AppInfoWithLiveness(this, committedItems, removedTypes);
+  public AppInfoWithLiveness rebuildWithLiveness(CommittedItems committedItems) {
+    return new AppInfoWithLiveness(this, committedItems);
   }
 
   public AppInfoWithLiveness rewrittenWithLens(
diff --git a/src/main/java/com/android/tools/r8/shaking/MainDexClasses.java b/src/main/java/com/android/tools/r8/shaking/MainDexClasses.java
index 4e6b632..9c1c529 100644
--- a/src/main/java/com/android/tools/r8/shaking/MainDexClasses.java
+++ b/src/main/java/com/android/tools/r8/shaking/MainDexClasses.java
@@ -89,6 +89,9 @@
   }
 
   public MainDexClasses withoutPrunedItems(PrunedItems prunedItems) {
+    if (prunedItems.isEmpty()) {
+      return this;
+    }
     MainDexClasses mainDexClassesAfterPruning = createEmptyMainDexClasses();
     for (DexType mainDexClass : mainDexClasses) {
       if (!prunedItems.getRemovedClasses().contains(mainDexClass)) {
diff --git a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
index 6f7d998..e049766 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -15,12 +15,10 @@
 import com.android.tools.r8.graph.DexAnnotationSet;
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexClass;
-import com.android.tools.r8.graph.DexClass.FieldSetter;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMember;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexField;
-import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMember;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
@@ -42,6 +40,7 @@
 import com.android.tools.r8.graph.RewrittenPrototypeDescription;
 import com.android.tools.r8.graph.SubtypingInfo;
 import com.android.tools.r8.graph.TopDownClassHierarchyTraversal;
+import com.android.tools.r8.graph.TreeFixerBase;
 import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.graph.classmerging.VerticallyMergedClasses;
 import com.android.tools.r8.ir.code.Invoke.Type;
@@ -654,7 +653,8 @@
 
     timing.begin("fixup");
     VerticalClassMergerGraphLens lens =
-        new TreeFixer(appView, lensBuilder, verticallyMergedClasses, synthesizedBridges)
+        new VerticalClassMergerTreeFixer(
+                appView, lensBuilder, verticallyMergedClasses, synthesizedBridges)
             .fixupTypeReferences();
     KeepInfoCollection keepInfo = appView.appInfo().getKeepInfo();
     keepInfo.mutate(mutator -> mutator.removeKeepInfoForPrunedItems(mergedClasses.keySet()));
@@ -1465,23 +1465,20 @@
     method.accessFlags.setPrivate();
   }
 
-  private static class TreeFixer {
+  private static class VerticalClassMergerTreeFixer extends TreeFixerBase {
 
     private final AppView<AppInfoWithLiveness> appView;
-    private final DexItemFactory dexItemFactory;
     private final VerticalClassMergerGraphLens.Builder lensBuilder;
     private final VerticallyMergedClasses mergedClasses;
     private final List<SynthesizedBridgeCode> synthesizedBridges;
 
-    private final Map<DexProto, DexProto> protoFixupCache = new IdentityHashMap<>();
-
-    TreeFixer(
+    VerticalClassMergerTreeFixer(
         AppView<AppInfoWithLiveness> appView,
         VerticalClassMergerGraphLens.Builder lensBuilder,
         VerticallyMergedClasses mergedClasses,
         List<SynthesizedBridgeCode> synthesizedBridges) {
+      super(appView);
       this.appView = appView;
-      this.dexItemFactory = appView.dexItemFactory();
       this.lensBuilder =
           VerticalClassMergerGraphLens.Builder.createBuilderForFixup(lensBuilder, mergedClasses);
       this.mergedClasses = mergedClasses;
@@ -1492,11 +1489,11 @@
       // Globally substitute merged class types in protos and holders.
       for (DexProgramClass clazz : appView.appInfo().classes()) {
         clazz.getMethodCollection().replaceMethods(this::fixupMethod);
-        fixupFields(clazz.staticFields(), clazz::setStaticField);
-        fixupFields(clazz.instanceFields(), clazz::setInstanceField);
+        clazz.setStaticFields(fixupFields(clazz.staticFields()));
+        clazz.setInstanceFields(fixupFields(clazz.instanceFields()));
       }
       for (SynthesizedBridgeCode synthesizedBridge : synthesizedBridges) {
-        synthesizedBridge.updateMethodSignatures(this::fixupMethod);
+        synthesizedBridge.updateMethodSignatures(this::fixupMethodReference);
       }
       VerticalClassMergerGraphLens lens = lensBuilder.build(appView, mergedClasses);
       if (lens != null) {
@@ -1505,85 +1502,45 @@
       return lens;
     }
 
-    private DexEncodedMethod fixupMethod(DexEncodedMethod method) {
-      DexMethod methodReference = method.method;
-      DexMethod newMethodReference = fixupMethod(methodReference);
-      if (newMethodReference != methodReference) {
-        if (!lensBuilder.hasOriginalSignatureMappingFor(newMethodReference)) {
-          lensBuilder
-              .map(methodReference, newMethodReference)
-              .recordMove(methodReference, newMethodReference);
-        }
-        DexEncodedMethod newMethod = method.toTypeSubstitutedMethod(newMethodReference);
-        if (newMethod.isNonPrivateVirtualMethod()) {
-          // Since we changed the return type or one of the parameters, this method cannot be a
-          // classpath or library method override, since we only class merge program classes.
-          assert !method.isLibraryMethodOverride().isTrue();
-          newMethod.setLibraryMethodOverride(OptionalBool.FALSE);
-        }
-        return newMethod;
-      }
-      return method;
-    }
-
-    private void fixupFields(List<DexEncodedField> fields, FieldSetter setter) {
-      if (fields == null) {
-        return;
-      }
-      for (int i = 0; i < fields.size(); i++) {
-        DexEncodedField encodedField = fields.get(i);
-        DexField field = encodedField.field;
-        DexType newType = fixupType(field.type);
-        DexType newHolder = fixupType(field.holder);
-        DexField newField = dexItemFactory.createField(newHolder, newType, field.name);
-        if (newField != encodedField.field) {
-          if (!lensBuilder.hasOriginalSignatureMappingFor(newField)) {
-            lensBuilder.map(field, newField);
-          }
-          setter.setField(i, encodedField.toTypeSubstitutedField(newField));
-        }
-      }
-    }
-
-    private DexMethod fixupMethod(DexMethod method) {
-      return dexItemFactory.createMethod(
-          fixupType(method.holder), fixupProto(method.proto), method.name);
-    }
-
-    private DexProto fixupProto(DexProto proto) {
-      DexProto result = protoFixupCache.get(proto);
-      if (result == null) {
-        DexType returnType = fixupType(proto.returnType);
-        DexType[] arguments = fixupTypes(proto.parameters.values);
-        result = dexItemFactory.createProto(returnType, arguments);
-        protoFixupCache.put(proto, result);
-      }
-      return result;
-    }
-
-    private DexType fixupType(DexType type) {
-      if (type.isArrayType()) {
-        DexType base = type.toBaseType(dexItemFactory);
-        DexType fixed = fixupType(base);
-        if (base == fixed) {
-          return type;
-        }
-        return type.replaceBaseType(fixed, dexItemFactory);
-      }
-      if (type.isClassType()) {
-        while (mergedClasses.hasBeenMergedIntoSubtype(type)) {
-          type = mergedClasses.getTargetFor(type);
-        }
+    @Override
+    public DexType mapClassType(DexType type) {
+      while (mergedClasses.hasBeenMergedIntoSubtype(type)) {
+        type = mergedClasses.getTargetFor(type);
       }
       return type;
     }
 
-    private DexType[] fixupTypes(DexType[] types) {
-      DexType[] result = new DexType[types.length];
-      for (int i = 0; i < result.length; i++) {
-        result[i] = fixupType(types[i]);
+    @Override
+    public void recordClassChange(DexType from, DexType to) {
+      // Fixup of classes is not used so no class type should change.
+      throw new Unreachable();
+    }
+
+    @Override
+    public void recordFieldChange(DexField from, DexField to) {
+      if (!lensBuilder.hasOriginalSignatureMappingFor(to)) {
+        lensBuilder.map(from, to);
       }
-      return result;
+    }
+
+    @Override
+    public void recordMethodChange(DexMethod from, DexMethod to) {
+      if (!lensBuilder.hasOriginalSignatureMappingFor(to)) {
+        lensBuilder.map(from, to).recordMove(from, to);
+      }
+    }
+
+    @Override
+    public DexEncodedMethod recordMethodChange(
+        DexEncodedMethod method, DexEncodedMethod newMethod) {
+      recordMethodChange(method.method, newMethod.method);
+      if (newMethod.isNonPrivateVirtualMethod()) {
+        // Since we changed the return type or one of the parameters, this method cannot be a
+        // classpath or library method override, since we only class merge program classes.
+        assert !method.isLibraryMethodOverride().isTrue();
+        newMethod.setLibraryMethodOverride(OptionalBool.FALSE);
+      }
+      return newMethod;
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
index 6fa737b..c18b29d 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
@@ -13,6 +13,7 @@
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.GraphLens.Builder;
 import com.android.tools.r8.graph.GraphLens.NestedGraphLens;
+import com.android.tools.r8.graph.PrunedItems;
 import com.android.tools.r8.shaking.MainDexClasses;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.InternalOptions;
@@ -39,11 +40,11 @@
 
   public static class Result {
     public final CommittedItems commit;
-    public final ImmutableSet<DexType> removedSyntheticClasses;
+    public final PrunedItems prunedItems;
 
-    public Result(CommittedItems commit, ImmutableSet<DexType> removedSyntheticClasses) {
+    public Result(CommittedItems commit, PrunedItems prunedItems) {
       this.commit = commit;
-      this.removedSyntheticClasses = removedSyntheticClasses;
+      this.prunedItems = prunedItems;
     }
   }
 
@@ -126,6 +127,17 @@
 
     appView.setGraphLens(lensBuilder.build(options.itemFactory, graphLens));
     assert appView.appInfo().getMainDexClasses() == mainDexClasses;
+
+    Set<DexType> finalSyntheticTypes = Sets.newIdentityHashSet();
+    finalSyntheticClasses.forEach(clazz -> finalSyntheticTypes.add(clazz.getType()));
+
+    Set<DexType> prunedSynthetics = Sets.newIdentityHashSet();
+    for (DexType type : syntheticItems.keySet()) {
+      if (!finalSyntheticTypes.contains(type)) {
+        prunedSynthetics.add(type);
+      }
+    }
+
     return new Result(
         new CommittedItems(
             SyntheticItems.INVALID_ID_AFTER_SYNTHETIC_FINALIZATION,
@@ -133,7 +145,7 @@
             legacySyntheticTypes,
             ImmutableMap.of(),
             ImmutableList.of()),
-        syntheticItems.keySet());
+        PrunedItems.builder().setPrunedApp(app).addRemovedClasses(prunedSynthetics).build());
   }
 
   private boolean verifyNoNestedSynthetics() {
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index 175a39e..dfbeff6 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -331,6 +331,9 @@
   public boolean quiet = false;
   // Throw exception if there is a warning about invalid debug info.
   public boolean invalidDebugInfoFatal = false;
+  // Don't gracefully recover from invalid debug info.
+  public boolean invalidDebugInfoStrict =
+      System.getProperty("com.android.tools.r8.strictdebuginfo") != null;
 
   // When dexsplitting we ignore main dex classes missing in the application. These will be
   // fused together by play store when shipped for pre-L devices.
@@ -1335,6 +1338,13 @@
     public boolean enableGeneratedMessageLiteBuilderShrinking = false;
     public boolean traverseOneOfAndRepeatedProtoFields = false;
     public boolean enableEnumLiteProtoShrinking = false;
+    // Breaks the Chrome build if this is not enabled because of MethodToInvoke switchMaps.
+    // See b/174530756 for more details.
+    public boolean enableProtoEnumSwitchMapShrinking = true;
+
+    public boolean enableRemoveProtoEnumSwitchMap() {
+      return isProtoShrinkingEnabled() && enableProtoEnumSwitchMapShrinking;
+    }
 
     public boolean isProtoShrinkingEnabled() {
       return enableGeneratedExtensionRegistryShrinking
@@ -1343,7 +1353,7 @@
           || enableEnumLiteProtoShrinking;
     }
 
-    public boolean isProtoEnumShrinkingEnabled() {
+    public boolean isEnumLiteProtoShrinkingEnabled() {
       return enableEnumLiteProtoShrinking;
     }
   }
diff --git a/src/main/keep.txt b/src/main/keep.txt
index f8bbc12..82f6965 100644
--- a/src/main/keep.txt
+++ b/src/main/keep.txt
@@ -20,6 +20,7 @@
 
 -keepattributes SourceFile, LineNumberTable, InnerClasses, EnclosingMethod, Exceptions, Signature
 
+-keepparameternames
 -keeppackagenames com.android.tools.r8
 -repackageclasses com.android.tools.r8.internal
 
diff --git a/src/test/java/com/android/tools/r8/SingleTestRunResult.java b/src/test/java/com/android/tools/r8/SingleTestRunResult.java
index c03c8e05..7f04357 100644
--- a/src/test/java/com/android/tools/r8/SingleTestRunResult.java
+++ b/src/test/java/com/android/tools/r8/SingleTestRunResult.java
@@ -8,6 +8,7 @@
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNotNull;
 
+import com.android.tools.r8.ToolHelper.DexVm;
 import com.android.tools.r8.ToolHelper.ProcessResult;
 import com.android.tools.r8.naming.retrace.StackTrace;
 import com.android.tools.r8.utils.AndroidApp;
@@ -16,6 +17,8 @@
 import java.io.IOException;
 import java.io.PrintStream;
 import java.util.concurrent.ExecutionException;
+import java.util.function.Consumer;
+import java.util.function.Predicate;
 import org.hamcrest.Matcher;
 
 public abstract class SingleTestRunResult<RR extends SingleTestRunResult<RR>>
@@ -23,6 +26,7 @@
   protected final AndroidApp app;
   private final TestRuntime runtime;
   private final ProcessResult result;
+  private boolean executedSatisfyingRuntime = false;
 
   public SingleTestRunResult(AndroidApp app, TestRuntime runtime, ProcessResult result) {
     this.app = app;
@@ -169,4 +173,19 @@
     ps.println(sb.toString());
     return self();
   }
+
+  public RR forDexRuntimeSatisfying(Predicate<DexVm.Version> predicate, Consumer<RR> action) {
+    if (runtime.isDex() && predicate.test(runtime.asDex().getVm().getVersion())) {
+      action.accept(self());
+      executedSatisfyingRuntime = true;
+    }
+    return self();
+  }
+
+  public RR otherwise(Consumer<RR> action) {
+    if (!executedSatisfyingRuntime) {
+      action.accept(self());
+    }
+    return self();
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/TestRunResult.java b/src/test/java/com/android/tools/r8/TestRunResult.java
index 849e7d7..c775215 100644
--- a/src/test/java/com/android/tools/r8/TestRunResult.java
+++ b/src/test/java/com/android/tools/r8/TestRunResult.java
@@ -81,13 +81,6 @@
     return assertFailure();
   }
 
-  public RR assertFailureWithErrorThatMatchesIf(boolean condition, Matcher<String> matcher) {
-    if (condition) {
-      return assertFailureWithErrorThatMatches(matcher);
-    }
-    return self();
-  }
-
   public RR assertFailureWithOutput(String expected) {
     assertStdoutMatches(is(expected));
     return assertFailure();
diff --git a/src/test/java/com/android/tools/r8/desugar/DesugarToClassFileDeprecatedAttribute.java b/src/test/java/com/android/tools/r8/desugar/DesugarToClassFileDeprecatedAttribute.java
index 0eac295..563ba1c 100644
--- a/src/test/java/com/android/tools/r8/desugar/DesugarToClassFileDeprecatedAttribute.java
+++ b/src/test/java/com/android/tools/r8/desugar/DesugarToClassFileDeprecatedAttribute.java
@@ -8,7 +8,9 @@
 
 import com.android.tools.r8.ByteDataView;
 import com.android.tools.r8.ClassFileConsumer;
+import com.android.tools.r8.ClassFileConsumer.ForwardingConsumer;
 import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.R8FullTestBuilder;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -116,6 +118,28 @@
     }
   }
 
+  @Test
+  public void testR8() throws Exception {
+    R8FullTestBuilder builder =
+        testForR8(parameters.getBackend())
+            .addProgramClasses(TestClass.class)
+            .addKeepClassAndMembersRules(TestClass.class)
+            .addKeepAllAttributes()
+            .setMinApi(parameters.getApiLevel());
+    if (parameters.isCfRuntime()) {
+      builder.setProgramConsumer(
+          new ForwardingConsumer(null) {
+            @Override
+            public void accept(ByteDataView data, String descriptor, DiagnosticsHandler handler) {
+              checkDeprecatedAttributes(data.getBuffer());
+            }
+          });
+    }
+    builder
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("Hello, world!");
+  }
+
   @Deprecated
   public static class TestClass {
     @Deprecated public Object object = new Object();
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/LargeEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/LargeEnumUnboxingTest.java
new file mode 100644
index 0000000..66c861a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/enumunboxing/LargeEnumUnboxingTest.java
@@ -0,0 +1,246 @@
+// 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 com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.StringUtils;
+import java.util.List;
+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 LargeEnumUnboxingTest extends EnumUnboxingTestBase {
+
+  private static final String EXPECTED_RESULT =
+      StringUtils.lines(
+          "B1falsetruefalse1",
+          "B2falsetruefalse2",
+          "B3falsetruefalse3",
+          "B4falsetruefalse4",
+          "B5falsetruefalse5",
+          "B6falsetruefalse6",
+          "B7falsetruefalse7",
+          "B8falsetruefalse8",
+          "B9falsetruefalse9",
+          "B10falsetruefalse10",
+          "B11falsetruefalse11",
+          "B12falsetruefalse12",
+          "B13falsetruefalse13",
+          "E1falsefalsetrue14",
+          "E2falsefalsetrue15",
+          "E3falsefalsetrue16",
+          "E4falsefalsetrue17",
+          "E5falsefalsetrue18",
+          "E6falsefalsetrue19",
+          "E7falsefalsetrue20",
+          "E8falsefalsetrue21",
+          "E9falsefalsetrue22",
+          "E10falsefalsetrue23",
+          "E11falsefalsetrue24",
+          "E12falsefalsetrue25",
+          "E13falsefalsetrue26",
+          "E14falsefalsetrue27",
+          "E15falsefalsetrue28",
+          "G1falsefalsefalse29",
+          "G2falsefalsefalse30",
+          "G3falsefalsefalse31",
+          "G4falsefalsefalse32",
+          "G5falsefalsefalse33",
+          "G6falsefalsefalse34",
+          "G7falsefalsefalse35",
+          "G8falsefalsefalse36",
+          "G9falsefalsefalse37",
+          "I1truefalsefalse38",
+          "I2truefalsefalse39",
+          "I3truefalsefalse40",
+          "I4truefalsefalse41",
+          "I5truefalsefalse42",
+          "I6truefalsefalse43",
+          "I7truefalsefalse44",
+          "I8truefalsefalse45",
+          "I9truefalsefalse46",
+          "I10truefalsefalse47",
+          "I11truefalsefalse48",
+          "I12truefalsefalse49",
+          "I13truefalsefalse50",
+          "I14truefalsefalse51",
+          "I15truefalsefalse52",
+          "I16truefalsefalse53",
+          "J1falsefalsefalse54",
+          "J2falsefalsefalse55");
+
+  private final TestParameters parameters;
+  private final boolean enumValueOptimization;
+  private final EnumKeepRules enumKeepRules;
+
+  @Parameters(name = "{0} valueOpt: {1} keep: {2}")
+  public static List<Object[]> data() {
+    return enumUnboxingTestParameters();
+  }
+
+  public LargeEnumUnboxingTest(
+      TestParameters parameters, boolean enumValueOptimization, EnumKeepRules enumKeepRules) {
+    this.parameters = parameters;
+    this.enumValueOptimization = enumValueOptimization;
+    this.enumKeepRules = enumKeepRules;
+  }
+
+  @Test
+  public void testEnumUnboxing() throws Exception {
+    Class<?> mainClass = Main.class;
+    testForR8(parameters.getBackend())
+        .addProgramClasses(mainClass, LargeEnum.class)
+        .addKeepMainRule(mainClass)
+        .addKeepRules(enumKeepRules.getKeepRules())
+        .enableNeverClassInliningAnnotations()
+        .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
+        .allowDiagnosticInfoMessages()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspectDiagnosticMessages(
+            m -> assertEnumIsUnboxed(LargeEnum.class, mainClass.getSimpleName(), m))
+        .run(parameters.getRuntime(), mainClass)
+        .assertSuccessWithOutput(EXPECTED_RESULT);
+  }
+
+  @NeverClassInline
+  enum LargeEnum {
+    B1("1"),
+    B2("2"),
+    B3("3"),
+    B4("4"),
+    B5("5"),
+    B6("6"),
+    B7("7"),
+    B8("8"),
+    B9("9"),
+    B10("10"),
+    B11("11"),
+    B12("12"),
+    B13("13"),
+
+    E1("14"),
+    E2("15"),
+    E3("16"),
+    E4("17"),
+    E5("18"),
+    E6("19"),
+    E7("20"),
+    E8("21"),
+    E9("22"),
+    E10("23"),
+    E11("24"),
+    E12("25"),
+    E13("26"),
+    E14("27"),
+    E15("28"),
+
+    G1("29"),
+    G2("30"),
+    G3("31"),
+    G4("32"),
+    G5("33"),
+    G6("34"),
+    G7("35"),
+    G8("36"),
+    G9("37"),
+
+    I1("38"),
+    I2("39"),
+    I3("40"),
+    I4("41"),
+    I5("42"),
+    I6("43"),
+    I7("44"),
+    I8("45"),
+    I9("46"),
+    I10("47"),
+    I11("48"),
+    I12("49"),
+    I13("50"),
+    I14("51"),
+    I15("52"),
+    I16("53"),
+
+    J1("54"),
+    J2("55");
+
+    private final String num;
+
+    LargeEnum(String num) {
+      this.num = num;
+    }
+
+    public String getNum() {
+      return num;
+    }
+
+    public boolean isI() {
+      return this == I1
+          || this == I2
+          || this == I3
+          || this == I4
+          || this == I5
+          || this == I6
+          || this == I7
+          || this == I8
+          || this == I9
+          || this == I10
+          || this == I11
+          || this == I12
+          || this == I13
+          || this == I14
+          || this == I15
+          || this == I16;
+    }
+
+    public boolean isB() {
+      return this == B1
+          || this == B2
+          || this == B3
+          || this == B4
+          || this == B5
+          || this == B6
+          || this == B7
+          || this == B8
+          || this == B9
+          || this == B10
+          || this == B11
+          || this == B12
+          || this == B13;
+    }
+
+    public boolean isE() {
+      return this == E1
+          || this == E2
+          || this == E3
+          || this == E4
+          || this == E5
+          || this == E6
+          || this == E7
+          || this == E8
+          || this == E9
+          || this == E10
+          || this == E11
+          || this == E12
+          || this == E13
+          || this == E14
+          || this == E15;
+    }
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      for (LargeEnum value : LargeEnum.values()) {
+        System.out.println(
+            value.toString() + value.isI() + value.isB() + value.isE() + value.getNum());
+      }
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/jasmin/InvalidDebugInfoTests.java b/src/test/java/com/android/tools/r8/jasmin/InvalidDebugInfoTests.java
index 9394995..edef7bf 100644
--- a/src/test/java/com/android/tools/r8/jasmin/InvalidDebugInfoTests.java
+++ b/src/test/java/com/android/tools/r8/jasmin/InvalidDebugInfoTests.java
@@ -3,20 +3,87 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.jasmin;
 
-import static org.junit.Assert.assertEquals;
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
+import static org.hamcrest.CoreMatchers.containsString;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
+import com.android.tools.r8.Diagnostic;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestDiagnosticMessages;
+import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.debuginfo.DebugInfoInspector;
 import com.android.tools.r8.jasmin.JasminBuilder.ClassFileVersion;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
-import com.android.tools.r8.utils.AndroidApp;
+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;
+import org.hamcrest.Matcher;
 import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
 
-public class InvalidDebugInfoTests extends JasminTestBase {
+@RunWith(Parameterized.class)
+public class InvalidDebugInfoTests extends TestBase {
+
+  @Parameters(name = "{0}, strict:{1}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withAllRuntimesAndApiLevels().enableApiLevelsForCf().build(),
+        BooleanUtils.values());
+  }
+
+  private final TestParameters parameters;
+  private final boolean strict;
+
+  public InvalidDebugInfoTests(TestParameters parameters, boolean strict) {
+    this.parameters = parameters;
+    this.strict = strict;
+  }
+
+  private void optionsModification(InternalOptions options) {
+    options.invalidDebugInfoStrict = strict;
+    options.testing.forceIRForCfToCfDesugar = true;
+  }
+
+  private final Matcher<Diagnostic> cfNotSupportedDiagnostic =
+      diagnosticMessage(
+          containsString("Compiling to Java class files with D8 is not officially supported"));
+
+  private void assertInvalidTypeMessage(TestDiagnosticMessages diagnostics) {
+    assertInvalidInfoMessages(diagnostics, "Attempt to define local of type");
+  }
+
+  private void assertUninitializedLocalMessage(TestDiagnosticMessages diagnostics) {
+    assertInvalidInfoMessages(diagnostics, "Local refers to uninitialized register");
+  }
+
+  private void assertInvalidInfoMessages(TestDiagnosticMessages diagnostics, String message) {
+    if (parameters.isCfRuntime()) {
+      diagnostics.assertNoErrors();
+      diagnostics.assertWarningsMatch(cfNotSupportedDiagnostic);
+    } else {
+      diagnostics.assertOnlyInfos();
+    }
+    // The reporting of invalid debug info issues three info items:
+    diagnostics.assertInfosMatch(
+        ImmutableList.of(
+            diagnosticMessage(containsString("Stripped invalid locals information")),
+            diagnosticMessage(containsString(message)),
+            diagnosticMessage(containsString("sign of using an outdated Java toolchain"))));
+  }
+
+  private void assertNoMessages(TestDiagnosticMessages diagnostics) {
+    if (parameters.isCfRuntime()) {
+      diagnostics.assertOnlyWarnings();
+      diagnostics.assertWarningsMatch(cfNotSupportedDiagnostic);
+    } else {
+      diagnostics.assertNoMessages();
+    }
+  }
 
   // This is a regression test for invalid live-ranges of locals generated by some old Java
   // compilers. The issue is that a local slot may have been initialized outside the live-scope of
@@ -75,10 +142,26 @@
         "  return");
 
     String expected = "42" + ToolHelper.LINE_SEPARATOR + "0" + ToolHelper.LINE_SEPARATOR;
-    String javaResult = runOnJava(builder, clazz.name);
-    assertEquals(expected, javaResult);
-    String artResult = runOnArtD8(builder, clazz.name);
-    assertEquals(expected, artResult);
+    if (parameters.isCfRuntime()) {
+      testForJvm()
+          .addProgramClassFileData(builder.buildClasses())
+          .run(parameters.getRuntime(), clazz.name)
+          .assertSuccessWithOutput(expected);
+    }
+    testForD8(parameters.getBackend())
+        .addProgramClassFileData(builder.buildClasses())
+        .setMinApi(parameters.getApiLevel())
+        .addOptionsModification(this::optionsModification)
+        .compileWithExpectedDiagnostics(
+            diagnostics -> {
+              if (strict) {
+                assertUninitializedLocalMessage(diagnostics);
+              } else {
+                assertNoMessages(diagnostics);
+              }
+            })
+        .run(parameters.getRuntime(), clazz.name)
+        .assertSuccessWithOutput(expected);
   }
 
   // Regression test to check that we properly add UninitializedLocal SSA values for methods that
@@ -124,10 +207,26 @@
         "  return");
 
     String expected = "42" + ToolHelper.LINE_SEPARATOR;
-    String javaResult = runOnJava(builder, clazz.name);
-    assertEquals(expected, javaResult);
-    String artResult = runOnArtD8(builder, clazz.name);
-    assertEquals(expected, artResult);
+    if (parameters.isCfRuntime()) {
+      testForJvm()
+          .addProgramClassFileData(builder.buildClasses())
+          .run(parameters.getRuntime(), clazz.name)
+          .assertSuccessWithOutput(expected);
+    }
+    testForD8(parameters.getBackend())
+        .addProgramClassFileData(builder.buildClasses())
+        .addOptionsModification(this::optionsModification)
+        .setMinApi(parameters.getApiLevel())
+        .compileWithExpectedDiagnostics(
+            diagnostics -> {
+              if (strict) {
+                assertUninitializedLocalMessage(diagnostics);
+              } else {
+                assertNoMessages(diagnostics);
+              }
+            })
+        .run(parameters.getRuntime(), clazz.name)
+        .assertSuccessWithOutput(expected);
   }
 
   @Test
@@ -165,13 +264,29 @@
         "  return");
 
     String expected = StringUtils.lines("42", "7.5");
-    String javaResult = runOnJava(builder, clazz.name);
-    assertEquals(expected, javaResult);
-    AndroidApp app = compileWithD8(builder);
-    String artResult = runOnArt(app, clazz.name);
-    assertEquals(expected, artResult);
-    DebugInfoInspector info = new DebugInfoInspector(app, clazz.name, method);
-    assertFalse(info.hasLocalsInfo());
+    if (parameters.isCfRuntime()) {
+      testForJvm()
+          .addProgramClassFileData(builder.buildClasses())
+          .run(parameters.getRuntime(), clazz.name)
+          .assertSuccessWithOutput(expected);
+    }
+    testForD8(parameters.getBackend())
+        .addProgramClassFileData(builder.buildClasses())
+        .setMinApi(parameters.getApiLevel())
+        .addOptionsModification(this::optionsModification)
+        .compileWithExpectedDiagnostics(
+            diagnostics -> {
+              if (strict) {
+                assertUninitializedLocalMessage(diagnostics);
+              } else {
+                assertInvalidTypeMessage(diagnostics);
+              }
+            })
+        .run(parameters.getRuntime(), clazz.name)
+        .assertSuccessWithOutput(expected)
+        .inspect(
+            inspector ->
+                assertFalse(inspector.clazz(clazz.name).method(method).hasLocalVariableTable()));
   }
 
   @Test
@@ -208,13 +323,22 @@
         "  return");
 
     String expected = StringUtils.lines("42", "7.5");
-    String javaResult = runOnJava(builder, clazz.name);
-    assertEquals(expected, javaResult);
-    AndroidApp app = compileWithD8(builder);
-    String artResult = runOnArt(app, clazz.name);
-    assertEquals(expected, artResult);
-    DebugInfoInspector info = new DebugInfoInspector(app, clazz.name, method);
-    assertFalse(info.hasLocalsInfo());
+    if (parameters.isCfRuntime()) {
+      testForJvm()
+          .addProgramClassFileData(builder.buildClasses())
+          .run(parameters.getRuntime(), clazz.name)
+          .assertSuccessWithOutput(expected);
+    }
+    testForD8(parameters.getBackend())
+        .addProgramClassFileData(builder.buildClasses())
+        .addOptionsModification(this::optionsModification)
+        .setMinApi(parameters.getApiLevel())
+        .compileWithExpectedDiagnostics(this::assertInvalidTypeMessage)
+        .run(parameters.getRuntime(), clazz.name)
+        .assertSuccessWithOutput(expected)
+        .inspect(
+            inspector ->
+                assertFalse(inspector.clazz(clazz.name).method(method).hasLocalVariableTable()));
   }
 
   @Test
@@ -258,13 +382,29 @@
         "  return");
 
     String expected = StringUtils.lines("42");
-    String javaResult = runOnJava(builder, clazz.name);
-    assertEquals(expected, javaResult);
-    AndroidApp app = compileWithD8(builder);
-    String artResult = runOnArt(app, clazz.name);
-    assertEquals(expected, artResult);
-    DebugInfoInspector info = new DebugInfoInspector(app, clazz.name, method);
-    assertFalse(info.hasLocalsInfo());
+    if (parameters.isCfRuntime()) {
+      testForJvm()
+          .addProgramClassFileData(builder.buildClasses())
+          .run(parameters.getRuntime(), clazz.name)
+          .assertSuccessWithOutput(expected);
+    }
+    testForD8(parameters.getBackend())
+        .addProgramClassFileData(builder.buildClasses())
+        .addOptionsModification(this::optionsModification)
+        .setMinApi(parameters.getApiLevel())
+        .compileWithExpectedDiagnostics(
+            diagnostics -> {
+              if (strict) {
+                assertUninitializedLocalMessage(diagnostics);
+              } else {
+                assertInvalidTypeMessage(diagnostics);
+              }
+            })
+        .run(parameters.getRuntime(), clazz.name)
+        .assertSuccessWithOutput(expected)
+        .inspect(
+            inspector ->
+                assertFalse(inspector.clazz(clazz.name).method(method).hasLocalVariableTable()));
   }
 
   @Test
@@ -309,14 +449,24 @@
         "  return");
 
     String expected = StringUtils.lines("42");
-    String javaResult = runOnJava(builder, clazz.name);
-    assertEquals(expected, javaResult);
-    AndroidApp app = compileWithD8(builder);
-    String artResult = runOnArt(app, clazz.name);
-    assertEquals(expected, artResult);
-    DebugInfoInspector info = new DebugInfoInspector(app, clazz.name, method);
-    // Note: This code is actually invalid debug info, but we do not reject it because both types
-    // are reference types. If we ever change that we should update this test.
-    assertTrue(info.hasLocalsInfo());
+    if (parameters.isCfRuntime()) {
+      testForJvm()
+          .addProgramClassFileData(builder.buildClasses())
+          .run(parameters.getRuntime(), clazz.name)
+          .assertSuccessWithOutput(expected);
+    }
+    testForD8(parameters.getBackend())
+        .addProgramClassFileData(builder.buildClasses())
+        .addOptionsModification(this::optionsModification)
+        .setMinApi(parameters.getApiLevel())
+        .compileWithExpectedDiagnostics(this::assertNoMessages)
+        .run(parameters.getRuntime(), clazz.name)
+        .assertSuccessWithOutput(expected)
+        .inspect(
+            inspector -> {
+              // Note: This code is actually invalid debug info, but we do not reject it because
+              // both types are reference types. If we change that we should update this test.
+              assertTrue(inspector.clazz(clazz.name).method(method).hasLocalVariableTable());
+            });
   }
 }
diff --git a/src/test/java/com/android/tools/r8/repackage/EnumAndIdentifierBasedStringInAnnotationTest.java b/src/test/java/com/android/tools/r8/repackage/EnumAndIdentifierBasedStringInAnnotationTest.java
new file mode 100644
index 0000000..f542e6e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/repackage/EnumAndIdentifierBasedStringInAnnotationTest.java
@@ -0,0 +1,71 @@
+// 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.repackage;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndRenamed;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.TestParameters;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class EnumAndIdentifierBasedStringInAnnotationTest extends RepackageTestBase {
+
+  public EnumAndIdentifierBasedStringInAnnotationTest(
+      String flattenPackageHierarchyOrRepackageClasses, TestParameters parameters) {
+    super(flattenPackageHierarchyOrRepackageClasses, parameters);
+  }
+
+  @Test
+  public void testRuntime() throws Exception {
+    testForRuntime(parameters)
+        .addInnerClasses(EnumAndIdentifierBasedStringInAnnotationTest.class)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("TEST_ONE");
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(EnumAndIdentifierBasedStringInAnnotationTest.class)
+        .addKeepMainRule(Main.class)
+        .addKeepClassAndMembersRules(MyAnnotation.class)
+        .addKeepClassAndMembersRulesWithAllowObfuscation(Enum.class)
+        .addKeepRules("-keepclassmembers,allowshrinking class ** { *; }")
+        .setMinApi(parameters.getApiLevel())
+        .addKeepRuntimeVisibleAnnotations()
+        .apply(this::configureRepackaging)
+        .compile()
+        .inspect(inspector -> assertThat(inspector.clazz(Enum.class), isPresentAndRenamed()))
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("TEST_ONE");
+  }
+
+  public enum Enum {
+    TEST_ONE,
+    TEST_TWO
+  }
+
+  @Retention(RetentionPolicy.RUNTIME)
+  @Target({ElementType.TYPE})
+  public @interface MyAnnotation {
+
+    Enum value() default Enum.TEST_TWO;
+  }
+
+  @MyAnnotation(value = Enum.TEST_ONE)
+  public static class Main {
+
+    public static void main(String[] args) {
+      System.out.println(Main.class.getAnnotation(MyAnnotation.class).value());
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/retrace/RetraceFieldTests.java b/src/test/java/com/android/tools/r8/retrace/RetraceFieldTests.java
index 2b90ffc..3ff24fa 100644
--- a/src/test/java/com/android/tools/r8/retrace/RetraceFieldTests.java
+++ b/src/test/java/com/android/tools/r8/retrace/RetraceFieldTests.java
@@ -5,7 +5,6 @@
 package com.android.tools.r8.retrace;
 
 import com.android.tools.r8.TestDiagnosticMessagesImpl;
-import com.android.tools.r8.retrace.internal.RetracerImpl;
 import com.android.tools.r8.retrace.mappings.FieldsWithSameMinifiedNameMapping;
 import com.android.tools.r8.retrace.mappings.MappingForTest;
 import java.util.function.Consumer;
@@ -21,6 +20,6 @@
 
   private void runRetraceTest(MappingForTest mappingForTest, Consumer<Retracer> inspection) {
     inspection.accept(
-        RetracerImpl.create(mappingForTest::mapping, new TestDiagnosticMessagesImpl()));
+        Retracer.createDefault(mappingForTest::mapping, new TestDiagnosticMessagesImpl()));
   }
 }
diff --git a/src/test/java/com/android/tools/r8/retrace/RetraceTests.java b/src/test/java/com/android/tools/r8/retrace/RetraceTests.java
index 82d3915..0c7dd7a 100644
--- a/src/test/java/com/android/tools/r8/retrace/RetraceTests.java
+++ b/src/test/java/com/android/tools/r8/retrace/RetraceTests.java
@@ -15,7 +15,6 @@
 import com.android.tools.r8.TestDiagnosticMessagesImpl;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.retrace.internal.RetraceAbortException;
-import com.android.tools.r8.retrace.internal.RetracerImpl;
 import com.android.tools.r8.retrace.stacktraces.ActualBotStackTraceBase;
 import com.android.tools.r8.retrace.stacktraces.ActualIdentityStackTrace;
 import com.android.tools.r8.retrace.stacktraces.ActualRetraceBotStackTrace;
@@ -24,6 +23,7 @@
 import com.android.tools.r8.retrace.stacktraces.AmbiguousWithMultipleLineMappingsStackTrace;
 import com.android.tools.r8.retrace.stacktraces.AmbiguousWithSignatureNonVerboseStackTrace;
 import com.android.tools.r8.retrace.stacktraces.CircularReferenceStackTrace;
+import com.android.tools.r8.retrace.stacktraces.ColonInFileNameStackTrace;
 import com.android.tools.r8.retrace.stacktraces.FileNameExtensionStackTrace;
 import com.android.tools.r8.retrace.stacktraces.InlineFileNameStackTrace;
 import com.android.tools.r8.retrace.stacktraces.InlineFileNameWithInnerClassesStackTrace;
@@ -32,6 +32,7 @@
 import com.android.tools.r8.retrace.stacktraces.InlineWithLineNumbersStackTrace;
 import com.android.tools.r8.retrace.stacktraces.InvalidStackTrace;
 import com.android.tools.r8.retrace.stacktraces.MemberFieldOverlapStackTrace;
+import com.android.tools.r8.retrace.stacktraces.MultipleDotsInFileNameStackTrace;
 import com.android.tools.r8.retrace.stacktraces.NamedModuleStackTrace;
 import com.android.tools.r8.retrace.stacktraces.NullStackTrace;
 import com.android.tools.r8.retrace.stacktraces.ObfucatedExceptionClassStackTrace;
@@ -39,6 +40,7 @@
 import com.android.tools.r8.retrace.stacktraces.RetraceAssertionErrorStackTrace;
 import com.android.tools.r8.retrace.stacktraces.StackTraceForTest;
 import com.android.tools.r8.retrace.stacktraces.SuppressedStackTrace;
+import com.android.tools.r8.retrace.stacktraces.UnicodeInFileNameStackTrace;
 import com.android.tools.r8.retrace.stacktraces.UnknownSourceStackTrace;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.google.common.collect.ImmutableList;
@@ -194,16 +196,31 @@
   }
 
   @Test
-  public void testMemberFieldOverlapStackTrace() throws Exception {
+  public void testColonInSourceFileNameStackTrace() {
+    runRetraceTest(new ColonInFileNameStackTrace());
+  }
+
+  @Test
+  public void testMultipleDotsInFileNameStackTrace() {
+    runRetraceTest(new MultipleDotsInFileNameStackTrace());
+  }
+
+  @Test
+  public void testUnicodeInFileNameStackTrace() {
+    runRetraceTest(new UnicodeInFileNameStackTrace());
+  }
+
+  @Test
+  public void testMemberFieldOverlapStackTrace() {
     MemberFieldOverlapStackTrace stackTraceForTest = new MemberFieldOverlapStackTrace();
     runRetraceTest(stackTraceForTest);
     inspectRetraceTest(stackTraceForTest, stackTraceForTest::inspectField);
   }
 
   private void inspectRetraceTest(
-      StackTraceForTest stackTraceForTest, Consumer<Retracer> inspection) throws Exception {
+      StackTraceForTest stackTraceForTest, Consumer<Retracer> inspection) {
     inspection.accept(
-        RetracerImpl.create(stackTraceForTest::mapping, new TestDiagnosticMessagesImpl()));
+        Retracer.createDefault(stackTraceForTest::mapping, new TestDiagnosticMessagesImpl()));
   }
 
   private TestDiagnosticMessagesImpl runRetraceTest(StackTraceForTest stackTraceForTest) {
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/ColonInFileNameStackTrace.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/ColonInFileNameStackTrace.java
new file mode 100644
index 0000000..0bdb870
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/ColonInFileNameStackTrace.java
@@ -0,0 +1,35 @@
+// 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.stacktraces;
+
+import com.google.common.collect.ImmutableList;
+import java.util.List;
+
+public class ColonInFileNameStackTrace implements StackTraceForTest {
+
+  @Override
+  public List<String> obfuscatedStackTrace() {
+    return ImmutableList.of("  at a.s(:foo::bar:1)");
+  }
+
+  @Override
+  public String mapping() {
+    return "some.Class -> a:\n"
+        // Sourcefile metadata.
+        + "# {\"id\":\"sourceFile\",\"fileName\":\"Class.kt\"}\n"
+        + "    1:3:int strawberry(int):99:101 -> s\n"
+        + "    4:5:int mango(float):121:122 -> s\n";
+  }
+
+  @Override
+  public List<String> retracedStackTrace() {
+    return ImmutableList.of("  at some.Class.strawberry(Class.kt:99)");
+  }
+
+  @Override
+  public int expectedWarnings() {
+    return 0;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/MultipleDotsInFileNameStackTrace.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/MultipleDotsInFileNameStackTrace.java
new file mode 100644
index 0000000..a67335f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/MultipleDotsInFileNameStackTrace.java
@@ -0,0 +1,35 @@
+// 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.stacktraces;
+
+import com.google.common.collect.ImmutableList;
+import java.util.List;
+
+public class MultipleDotsInFileNameStackTrace implements StackTraceForTest {
+
+  @Override
+  public List<String> obfuscatedStackTrace() {
+    return ImmutableList.of("  at a.s(foo.bar.baz:1)");
+  }
+
+  @Override
+  public String mapping() {
+    return "some.Class -> a:\n"
+        // Sourcefile metadata.
+        + "# {\"id\":\"sourceFile\",\"fileName\":\"Class.kt\"}\n"
+        + "    1:3:int strawberry(int):99:101 -> s\n"
+        + "    4:5:int mango(float):121:122 -> s\n";
+  }
+
+  @Override
+  public List<String> retracedStackTrace() {
+    return ImmutableList.of("  at some.Class.strawberry(Class.kt:99)");
+  }
+
+  @Override
+  public int expectedWarnings() {
+    return 0;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/UnicodeInFileNameStackTrace.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/UnicodeInFileNameStackTrace.java
new file mode 100644
index 0000000..5090c39
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/UnicodeInFileNameStackTrace.java
@@ -0,0 +1,35 @@
+// 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.stacktraces;
+
+import com.google.common.collect.ImmutableList;
+import java.util.List;
+
+public class UnicodeInFileNameStackTrace implements StackTraceForTest {
+
+  @Override
+  public List<String> obfuscatedStackTrace() {
+    return ImmutableList.of("  at a.s(Blåbærgrød.jàvà:1)");
+  }
+
+  @Override
+  public String mapping() {
+    return "some.Class -> a:\n"
+        // Sourcefile metadata.
+        + "# {\"id\":\"sourceFile\",\"fileName\":\"Class.kt\"}\n"
+        + "    1:3:int strawberry(int):99:101 -> s\n"
+        + "    4:5:int mango(float):121:122 -> s\n";
+  }
+
+  @Override
+  public List<String> retracedStackTrace() {
+    return ImmutableList.of("  at some.Class.strawberry(Class.kt:99)");
+  }
+
+  @Override
+  public int expectedWarnings() {
+    return 0;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java b/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
index d2518b6..d70775f 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
@@ -36,7 +36,6 @@
 import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.retrace.Retracer;
 import com.android.tools.r8.retrace.internal.DirectClassNameMapperProguardMapProducer;
-import com.android.tools.r8.retrace.internal.RetracerImpl;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.BiMapContainer;
 import com.android.tools.r8.utils.DescriptorUtils;
@@ -464,7 +463,7 @@
   }
 
   public Retracer retrace() {
-    return RetracerImpl.create(
+    return Retracer.createDefault(
         new InternalProguardMapProducer(
             mapping == null ? ClassNameMapper.builder().build() : mapping),
         new TestDiagnosticMessagesImpl());