Merge "Implement support for not overloading aggressively, making that the default."
diff --git a/build.gradle b/build.gradle
index 9299592..c97aa9d 100644
--- a/build.gradle
+++ b/build.gradle
@@ -23,6 +23,7 @@
     '-Xep:OvershadowingSubclassFields:WARN',
     '-Xep:IntLongMath:WARN',
     '-Xep:EqualsHashCode:WARN',
+    '-Xep:InconsistentOverloads:WARN',
     '-Xep:ArrayHashCode:WARN',
     '-Xep:EqualsIncompatibleType:WARN',
     '-Xep:NonOverridingEquals:WARN',
diff --git a/src/main/java/com/android/tools/r8/BaseCompilerCommand.java b/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
index 6c4b9bc..6961024 100644
--- a/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
+++ b/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
@@ -109,19 +109,19 @@
     private boolean enableDesugaring = true;
 
     protected Builder(CompilationMode mode) {
-      this(AndroidApp.builder(), mode, false);
+      this(mode, false, AndroidApp.builder());
     }
 
     protected Builder(CompilationMode mode, boolean ignoreDexInArchive) {
-      this(AndroidApp.builder(), mode, ignoreDexInArchive);
+      this(mode, ignoreDexInArchive, AndroidApp.builder());
     }
 
     // Internal constructor for testing.
-    Builder(AndroidApp app, CompilationMode mode) {
-      this(AndroidApp.builder(app), mode, false);
+    Builder(CompilationMode mode, AndroidApp app) {
+      this(mode, false, AndroidApp.builder(app));
     }
 
-    private Builder(AndroidApp.Builder builder, CompilationMode mode, boolean ignoreDexInArchive) {
+    private Builder(CompilationMode mode, boolean ignoreDexInArchive, AndroidApp.Builder builder) {
       super(builder, ignoreDexInArchive);
       assert mode != null;
       this.mode = mode;
diff --git a/src/main/java/com/android/tools/r8/D8.java b/src/main/java/com/android/tools/r8/D8.java
index a5213c5..ae370ae 100644
--- a/src/main/java/com/android/tools/r8/D8.java
+++ b/src/main/java/com/android/tools/r8/D8.java
@@ -199,7 +199,7 @@
       throws IOException, ExecutionException, ApiLevelException {
     final CfgPrinter printer = options.printCfg ? new CfgPrinter() : null;
 
-    IRConverter converter = new IRConverter(timing, appInfo, options, printer);
+    IRConverter converter = new IRConverter(appInfo, options, timing, printer);
     application = converter.convertToDex(application, executor);
 
     if (options.printCfg) {
diff --git a/src/main/java/com/android/tools/r8/D8Command.java b/src/main/java/com/android/tools/r8/D8Command.java
index b96dfb7..d0c99d5 100644
--- a/src/main/java/com/android/tools/r8/D8Command.java
+++ b/src/main/java/com/android/tools/r8/D8Command.java
@@ -44,7 +44,7 @@
     }
 
     private Builder(AndroidApp app) {
-      super(app, CompilationMode.DEBUG);
+      super(CompilationMode.DEBUG, app);
     }
 
     /** Add classpath file resources. */
diff --git a/src/main/java/com/android/tools/r8/GenerateMainDexListCommand.java b/src/main/java/com/android/tools/r8/GenerateMainDexListCommand.java
index 9d7478d..d29d9ae 100644
--- a/src/main/java/com/android/tools/r8/GenerateMainDexListCommand.java
+++ b/src/main/java/com/android/tools/r8/GenerateMainDexListCommand.java
@@ -68,7 +68,7 @@
      * Add proguard configuration for automatic main dex list calculation.
      */
     public GenerateMainDexListCommand.Builder addMainDexRules(List<String> lines) {
-      mainDexRules.add(new ProguardConfigurationSourceStrings(Paths.get("."), lines));
+      mainDexRules.add(new ProguardConfigurationSourceStrings(lines, Paths.get(".")));
       return self();
     }
 
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index d9066bd..a9f3329 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -22,6 +22,8 @@
 import com.android.tools.r8.jar.CfApplicationWriter;
 import com.android.tools.r8.naming.Minifier;
 import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.naming.ProguardMapApplier;
+import com.android.tools.r8.naming.SeedMapper;
 import com.android.tools.r8.naming.SourceFileRewriter;
 import com.android.tools.r8.optimize.BridgeMethodAnalysis;
 import com.android.tools.r8.optimize.MemberRebindingAnalysis;
@@ -129,7 +131,7 @@
     timing.begin("Create IR");
     try {
       IRConverter converter = new IRConverter(
-          timing, appInfo, options, printer, graphLense);
+          appInfo, options, timing, printer, graphLense);
       application = converter.optimize(application, executorService);
     } finally {
       timing.end();
@@ -259,6 +261,14 @@
           appInfo = appInfo.withLiveness()
               .prunedCopyFrom(application, classMerger.getRemovedClasses());
         }
+        if (options.proguardConfiguration.hasApplyMappingFile()) {
+          SeedMapper seedMapper = SeedMapper.seedMapperFromFile(
+              options.proguardConfiguration.getApplyMappingFile());
+          timing.begin("apply-mapping");
+          graphLense = new ProguardMapApplier(appInfo.withLiveness(), graphLense, seedMapper)
+              .run(timing);
+          timing.end();
+        }
         application = application.asDirect().rewrittenWithLense(graphLense);
         appInfo = appInfo.withLiveness().rewrittenWithLense(application.asDirect(), graphLense);
         // Collect switch maps and ordinals maps.
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index 1df56db..ad23c03 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -35,6 +35,7 @@
     private Optional<Boolean> treeShaking = Optional.empty();
     private Optional<Boolean> discardedChecker = Optional.empty();
     private Optional<Boolean> minification = Optional.empty();
+    private boolean ignoreMissingClassesWhenNotShrinking = false;
     private boolean ignoreMissingClasses = false;
     private boolean forceProguardCompatibility = false;
     private Path proguardMapOutput = null;
@@ -43,13 +44,16 @@
       super(CompilationMode.RELEASE);
     }
 
-    protected Builder(boolean ignoreDexInArchive, boolean forceProguardCompatibility) {
+    protected Builder(boolean ignoreDexInArchive, boolean forceProguardCompatibility,
+        boolean ignoreMissingClassesWhenNotShrinking, boolean ignoreMissingClasses) {
       super(CompilationMode.RELEASE, ignoreDexInArchive);
       this.forceProguardCompatibility = forceProguardCompatibility;
+      this.ignoreMissingClassesWhenNotShrinking = ignoreMissingClassesWhenNotShrinking;
+      this.ignoreMissingClasses = ignoreMissingClasses;
     }
 
     private Builder(AndroidApp app) {
-      super(app, CompilationMode.RELEASE);
+      super(CompilationMode.RELEASE, app);
     }
 
     @Override
@@ -105,7 +109,7 @@
      * Add proguard configuration for automatic main dex list calculation.
      */
     public Builder addMainDexRules(List<String> lines) {
-      mainDexRules.add(new ProguardConfigurationSourceStrings(Paths.get("."), lines));
+      mainDexRules.add(new ProguardConfigurationSourceStrings(lines, Paths.get(".")));
       return self();
     }
 
@@ -138,7 +142,7 @@
      * Add proguard configuration.
      */
     public Builder addProguardConfiguration(List<String> lines) {
-      proguardConfigs.add(new ProguardConfigurationSourceStrings(Paths.get("."), lines));
+      proguardConfigs.add(new ProguardConfigurationSourceStrings(lines, Paths.get(".")));
       return self();
     }
 
@@ -234,8 +238,6 @@
       getAppBuilder().addProgramFiles(configuration.getInjars());
       getAppBuilder().addLibraryFiles(configuration.getLibraryjars());
 
-      // TODO(b/64802420): setProguardMapFile if configuration.hasApplyMappingFile
-
       boolean useTreeShaking = treeShaking.orElse(configuration.isShrinking());
       boolean useDiscardedChecker = discardedChecker.orElse(true);
       boolean useMinification = minification.orElse(configuration.isObfuscating());
@@ -256,6 +258,7 @@
           useMinification,
           ignoreMissingClasses,
           forceProguardCompatibility,
+          ignoreMissingClassesWhenNotShrinking,
           proguardMapOutput);
     }
   }
@@ -297,6 +300,7 @@
   private final boolean useMinification;
   private final boolean ignoreMissingClasses;
   private final boolean forceProguardCompatibility;
+  private final boolean ignoreMissingClassesWhenNotShrinking;
   private final Path proguardMapOutput;
 
   public static Builder builder() {
@@ -415,6 +419,7 @@
       boolean useMinification,
       boolean ignoreMissingClasses,
       boolean forceProguardCompatibility,
+      boolean ignoreMissingClassesWhenNotShrinking,
       Path proguardMapOutput) {
     super(inputApp, outputPath, outputMode, mode, minApiLevel, diagnosticsHandler,
         enableDesugaring);
@@ -429,6 +434,7 @@
     this.useMinification = useMinification;
     this.ignoreMissingClasses = ignoreMissingClasses;
     this.forceProguardCompatibility = forceProguardCompatibility;
+    this.ignoreMissingClassesWhenNotShrinking = ignoreMissingClassesWhenNotShrinking;
     this.proguardMapOutput = proguardMapOutput;
   }
 
@@ -442,6 +448,7 @@
     useMinification = false;
     ignoreMissingClasses = false;
     forceProguardCompatibility = false;
+    ignoreMissingClassesWhenNotShrinking = false;
     proguardMapOutput = null;
   }
   public boolean useTreeShaking() {
@@ -479,6 +486,9 @@
     assert !internal.ignoreMissingClasses;
     internal.ignoreMissingClasses = ignoreMissingClasses;
     internal.ignoreMissingClasses |= proguardConfiguration.isIgnoreWarnings();
+    internal.ignoreMissingClasses |=
+        ignoreMissingClassesWhenNotShrinking && !proguardConfiguration.isShrinking();
+
     assert !internal.verbose;
     internal.mainDexKeepRules = mainDexKeepRules;
     internal.minimalMainDex = internal.debug;
diff --git a/src/main/java/com/android/tools/r8/ReadMainDexList.java b/src/main/java/com/android/tools/r8/ReadMainDexList.java
index a0c4789..661e324 100644
--- a/src/main/java/com/android/tools/r8/ReadMainDexList.java
+++ b/src/main/java/com/android/tools/r8/ReadMainDexList.java
@@ -4,7 +4,6 @@
 package com.android.tools.r8;
 
 import com.android.tools.r8.naming.ClassNameMapper;
-import com.android.tools.r8.naming.ProguardMapReader;
 import com.android.tools.r8.utils.FileUtils;
 import com.google.common.collect.Iterators;
 import java.nio.file.Path;
@@ -57,7 +56,7 @@
     Path mainDexList = Paths.get(arg);
 
     final ClassNameMapper mapper =
-        arguments.hasNext() ? ProguardMapReader.mapperFromFile(Paths.get(arguments.next())) : null;
+        arguments.hasNext() ? ClassNameMapper.mapperFromFile(Paths.get(arguments.next())) : null;
 
     FileUtils.readTextFile(mainDexList)
         .stream()
diff --git a/src/main/java/com/android/tools/r8/ReadProguardMap.java b/src/main/java/com/android/tools/r8/ReadProguardMap.java
index 2ea2ca7..a05537c 100644
--- a/src/main/java/com/android/tools/r8/ReadProguardMap.java
+++ b/src/main/java/com/android/tools/r8/ReadProguardMap.java
@@ -3,7 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8;
 
-import com.android.tools.r8.naming.ProguardMapReader;
+import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.utils.Timing;
 import java.io.IOException;
 import java.nio.file.Paths;
@@ -22,7 +22,7 @@
     try {
       System.out.println("  - reading " + fileName);
       timing.begin("Reading " + fileName);
-      ProguardMapReader.mapperFromFile(Paths.get(fileName));
+      ClassNameMapper.mapperFromFile(Paths.get(fileName));
       timing.end();
     } catch (IOException e) {
       System.err.print("Failed to parse Proguard mapping file: " + e.getMessage());
diff --git a/src/main/java/com/android/tools/r8/benchmarks/FrameworkIncrementalDexingBenchmark.java b/src/main/java/com/android/tools/r8/benchmarks/FrameworkIncrementalDexingBenchmark.java
index 2120714..e904e53 100644
--- a/src/main/java/com/android/tools/r8/benchmarks/FrameworkIncrementalDexingBenchmark.java
+++ b/src/main/java/com/android/tools/r8/benchmarks/FrameworkIncrementalDexingBenchmark.java
@@ -22,6 +22,7 @@
 import com.google.common.collect.ImmutableMap;
 import com.google.common.io.ByteStreams;
 import java.io.IOException;
+import java.io.InputStream;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.ArrayList;
@@ -174,7 +175,9 @@
       throws IOException, CompilationException {
     List<byte[]> bytes = new ArrayList<>(outputs.size());
     for (Resource input : outputs.values()) {
-      bytes.add(ByteStreams.toByteArray(input.getStream()));
+      try (InputStream inputStream = input.getStream()) {
+        bytes.add(ByteStreams.toByteArray(inputStream));
+      }
     }
     long start = System.nanoTime();
     D8Output out =
diff --git a/src/main/java/com/android/tools/r8/code/Format45cc.java b/src/main/java/com/android/tools/r8/code/Format45cc.java
index c269fad..f5123cf 100644
--- a/src/main/java/com/android/tools/r8/code/Format45cc.java
+++ b/src/main/java/com/android/tools/r8/code/Format45cc.java
@@ -117,13 +117,13 @@
     StringBuilder builder = new StringBuilder();
     appendRegisterArguments(builder, " ");
     builder.append(" ");
-    builder.append(toString(BBBB, naming));
+    builder.append(itemToString(BBBB, naming));
     builder.append(", ");
-    builder.append(toString(HHHH, naming));
+    builder.append(itemToString(HHHH, naming));
     return formatString(builder.toString());
   }
 
-  private String toString(IndexedDexItem indexedDexItem, ClassNameMapper naming) {
+  private String itemToString(IndexedDexItem indexedDexItem, ClassNameMapper naming) {
     String str;
     if (naming == null) {
       str = indexedDexItem.toSmaliString();
diff --git a/src/main/java/com/android/tools/r8/compatproguard/CompatProguard.java b/src/main/java/com/android/tools/r8/compatproguard/CompatProguard.java
index 4c49a6a..a6b45ae 100644
--- a/src/main/java/com/android/tools/r8/compatproguard/CompatProguard.java
+++ b/src/main/java/com/android/tools/r8/compatproguard/CompatProguard.java
@@ -33,14 +33,16 @@
     public final String output;
     public final int minApi;
     public final boolean forceProguardCompatibility;
+    public final boolean ignoreMissingClasses;
     public final boolean multiDex;
     public final List<String> proguardConfig;
 
     CompatProguardOptions(List<String> proguardConfig, String output, int minApi,
-        boolean multiDex, boolean forceProguardCompatibility) {
+        boolean multiDex, boolean forceProguardCompatibility, boolean ignoreMissingClasses) {
       this.output = output;
       this.minApi = minApi;
       this.forceProguardCompatibility = forceProguardCompatibility;
+      this.ignoreMissingClasses = ignoreMissingClasses;
       this.multiDex = multiDex;
       this.proguardConfig = proguardConfig;
     }
@@ -49,6 +51,7 @@
       String output = null;
       int minApi = 1;
       boolean forceProguardCompatibility = false;
+      boolean ignoreMissingClasses = false;
       boolean multiDex = false;
       boolean coreLibrary = false;
 
@@ -62,6 +65,8 @@
               minApi = Integer.valueOf(args[++i]);
             } else if (arg.equals("--force-proguard-compatibility")) {
               forceProguardCompatibility = true;
+            } else if (arg.equals("--ignore-missing-classes")) {
+              ignoreMissingClasses = true;
             } else if (arg.equals("--output")) {
               output = args[++i];
             } else if (arg.equals("--multi-dex")) {
@@ -82,7 +87,7 @@
         builder.add(currentLine.toString());
       }
       return new CompatProguardOptions(builder.build(), output, minApi, multiDex,
-          forceProguardCompatibility);
+          forceProguardCompatibility, ignoreMissingClasses);
     }
   }
 
@@ -95,7 +100,8 @@
     // Run R8 passing all the options from the command line as a Proguard configuration.
     CompatProguardOptions options = CompatProguardOptions.parse(args);
     R8Command.Builder builder =
-        new CompatProguardCommandBuilder(options.forceProguardCompatibility);
+        new CompatProguardCommandBuilder(
+            options.forceProguardCompatibility, options.ignoreMissingClasses);
     builder.setOutputPath(Paths.get(options.output))
         .addProguardConfiguration(options.proguardConfig)
         .setMinApiLevel(options.minApi);
diff --git a/src/main/java/com/android/tools/r8/compatproguard/CompatProguardCommandBuilder.java b/src/main/java/com/android/tools/r8/compatproguard/CompatProguardCommandBuilder.java
index 71bc6eb..18ec474 100644
--- a/src/main/java/com/android/tools/r8/compatproguard/CompatProguardCommandBuilder.java
+++ b/src/main/java/com/android/tools/r8/compatproguard/CompatProguardCommandBuilder.java
@@ -7,8 +7,9 @@
 import com.android.tools.r8.R8Command;
 
 public class CompatProguardCommandBuilder extends R8Command.Builder {
-  public CompatProguardCommandBuilder(boolean forceProguardCompatibility) {
-    super(true, forceProguardCompatibility);
+  public CompatProguardCommandBuilder(boolean forceProguardCompatibility,
+      boolean ignoreMissingClasses) {
+    super(true, forceProguardCompatibility, true, ignoreMissingClasses);
     setEnableDesugaring(false);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/dex/ApplicationReader.java b/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
index 3e4d28d..db5ebf2 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
@@ -20,7 +20,7 @@
 import com.android.tools.r8.graph.JarApplicationReader;
 import com.android.tools.r8.graph.JarClassFileReader;
 import com.android.tools.r8.graph.LazyLoadedDexApplication;
-import com.android.tools.r8.naming.ProguardMapReader;
+import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.ClassProvider;
@@ -111,7 +111,7 @@
     if (inputApp.hasProguardMap()) {
       futures.add(executorService.submit(() -> {
         try (InputStream map = inputApp.getProguardMap()) {
-          builder.setProguardMap(ProguardMapReader.mapperFromInputStream(map));
+          builder.setProguardMap(ClassNameMapper.mapperFromInputStream(map));
         } catch (IOException e) {
           throw new RuntimeException(e);
         }
diff --git a/src/main/java/com/android/tools/r8/dex/BaseFile.java b/src/main/java/com/android/tools/r8/dex/BaseFile.java
index dd9b325..86143c8 100644
--- a/src/main/java/com/android/tools/r8/dex/BaseFile.java
+++ b/src/main/java/com/android/tools/r8/dex/BaseFile.java
@@ -18,7 +18,9 @@
   protected final ByteBuffer buffer;
 
   protected BaseFile(Resource resource) throws IOException {
-    buffer = ByteBuffer.wrap(ByteStreams.toByteArray(resource.getStream()));
+    try (InputStream input = resource.getStream()) {
+      buffer = ByteBuffer.wrap(ByteStreams.toByteArray(input));
+    }
   }
 
   protected BaseFile(String name) throws IOException {
diff --git a/src/main/java/com/android/tools/r8/dex/DexFileReader.java b/src/main/java/com/android/tools/r8/dex/DexFileReader.java
index 770afdf..64909cc 100644
--- a/src/main/java/com/android/tools/r8/dex/DexFileReader.java
+++ b/src/main/java/com/android/tools/r8/dex/DexFileReader.java
@@ -983,7 +983,7 @@
     DexString shorty = indexedItems.getString(shortyIndex);
     DexType returnType = indexedItems.getType(returnTypeIndex);
     DexTypeList parameters = typeListAt(parametersOffsetIndex);
-    return dexItemFactory.createProto(shorty, returnType, parameters);
+    return dexItemFactory.createProto(returnType, shorty, parameters);
   }
 
   private DexMethod methodAt(int index) {
diff --git a/src/main/java/com/android/tools/r8/errors/InternalCompilerError.java b/src/main/java/com/android/tools/r8/errors/InternalCompilerError.java
index b51c7ec..a5080d3 100644
--- a/src/main/java/com/android/tools/r8/errors/InternalCompilerError.java
+++ b/src/main/java/com/android/tools/r8/errors/InternalCompilerError.java
@@ -14,8 +14,8 @@
   public InternalCompilerError() {
   }
 
-  public InternalCompilerError(String s) {
-    super(s);
+  public InternalCompilerError(String message) {
+    super(message);
   }
 
   public InternalCompilerError(String message, Throwable cause) {
diff --git a/src/main/java/com/android/tools/r8/graph/DexClass.java b/src/main/java/com/android/tools/r8/graph/DexClass.java
index 5050711..dda559c 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -18,7 +18,7 @@
   private static final DexEncodedField[] NO_FIELDS = {};
 
   public final Origin origin;
-  public final DexType type;
+  public DexType type;
   public final DexAccessFlags accessFlags;
   public DexType superType;
   public DexTypeList interfaces;
diff --git a/src/main/java/com/android/tools/r8/graph/DexCode.java b/src/main/java/com/android/tools/r8/graph/DexCode.java
index 6a2fcbf..a05299c 100644
--- a/src/main/java/com/android/tools/r8/graph/DexCode.java
+++ b/src/main/java/com/android/tools/r8/graph/DexCode.java
@@ -168,11 +168,10 @@
 
   public IRCode buildIR(
       DexEncodedMethod encodedMethod,
-      ValueNumberGenerator valueNumberGenerator,
-      InternalOptions options)
+      InternalOptions options, ValueNumberGenerator valueNumberGenerator)
       throws ApiLevelException {
     DexSourceCode source = new DexSourceCode(this, encodedMethod);
-    IRBuilder builder = new IRBuilder(encodedMethod, source, valueNumberGenerator, options);
+    IRBuilder builder = new IRBuilder(encodedMethod, source, options, valueNumberGenerator);
     return builder.build();
   }
 
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
index 99325f9..1974397 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -265,11 +265,11 @@
     return code == null ? null : code.buildIR(this, options);
   }
 
-  public IRCode buildIR(ValueNumberGenerator valueNumberGenerator, InternalOptions options)
+  public IRCode buildIR(InternalOptions options, ValueNumberGenerator valueNumberGenerator)
       throws ApiLevelException {
     return code == null
         ? null
-        : code.asDexCode().buildIR(this, valueNumberGenerator, options);
+        : code.asDexCode().buildIR(this, options, valueNumberGenerator);
   }
 
   public void setCode(
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
index 153c2e6..a7d44ba 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -346,23 +346,19 @@
     return createField(clazz, type, createString(name));
   }
 
-  public DexProto createProto(DexString shorty, DexType returnType, DexTypeList parameters) {
+  public DexProto createProto(DexType returnType, DexString shorty, DexTypeList parameters) {
     assert !sorted;
     DexProto proto = new DexProto(shorty, returnType, parameters);
     return canonicalize(protos, proto);
   }
 
-  public DexProto createProto(DexString shorty, DexType returnType, DexType[] parameters) {
+  public DexProto createProto(DexType returnType, DexType... parameters) {
     assert !sorted;
-    return createProto(shorty, returnType,
+    return createProto(returnType, createShorty(returnType, parameters),
         parameters.length == 0 ? DexTypeList.empty() : new DexTypeList(parameters));
   }
 
-  public DexProto createProto(DexType returnType, DexType... parameters) {
-    return createProto(createShorty(returnType, parameters), returnType, parameters);
-  }
-
-  public DexString createShorty(DexType returnType, DexType[] argumentTypes) {
+  private DexString createShorty(DexType returnType, DexType[] argumentTypes) {
     StringBuilder shortyBuilder = new StringBuilder();
     shortyBuilder.append(returnType.toShorty());
     for (DexType argumentType : argumentTypes) {
@@ -407,7 +403,7 @@
     for (int i = 0; i < parameterDescriptors.length; i++) {
       parameterTypes[i] = createType(parameterDescriptors[i]);
     }
-    DexProto proto = createProto(shorty(returnType, parameterTypes), returnType, parameterTypes);
+    DexProto proto = createProto(returnType, parameterTypes);
 
     return createMethod(clazz, proto, name);
   }
@@ -464,20 +460,6 @@
     return method.name == classConstructorMethodName;
   }
 
-  private DexString shorty(DexType returnType, DexType[] parameters) {
-    StringBuilder builder = new StringBuilder();
-    addToShorty(builder, returnType);
-    for (DexType parameter : parameters) {
-      addToShorty(builder, parameter);
-    }
-    return createString(builder.toString());
-  }
-
-  private void addToShorty(StringBuilder builder, DexType type) {
-    char first = type.toDescriptorString().charAt(0);
-    builder.append(first == '[' ? 'L' : first);
-  }
-
   private static <S extends PresortedComparable<S>> void assignSortedIndices(Collection<S> items,
       NamingLens namingLens) {
     List<S> sorted = new ArrayList<>(items);
diff --git a/src/main/java/com/android/tools/r8/graph/GraphLense.java b/src/main/java/com/android/tools/r8/graph/GraphLense.java
index 3abe7c5..ed4106e 100644
--- a/src/main/java/com/android/tools/r8/graph/GraphLense.java
+++ b/src/main/java/com/android/tools/r8/graph/GraphLense.java
@@ -23,13 +23,12 @@
 
   public static class Builder {
 
-    private Builder() {
-
+    protected Builder() {
     }
 
-    private final Map<DexType, DexType> typeMap = new IdentityHashMap<>();
-    private final Map<DexMethod, DexMethod> methodMap = new IdentityHashMap<>();
-    private final Map<DexField, DexField> fieldMap = new IdentityHashMap<>();
+    protected final Map<DexType, DexType> typeMap = new IdentityHashMap<>();
+    protected final Map<DexMethod, DexMethod> methodMap = new IdentityHashMap<>();
+    protected final Map<DexField, DexField> fieldMap = new IdentityHashMap<>();
 
     public void map(DexType from, DexType to) {
       typeMap.put(from, to);
@@ -44,10 +43,13 @@
     }
 
     public GraphLense build(DexItemFactory dexItemFactory) {
-      return build(new IdentityGraphLense(), dexItemFactory);
+      return build(dexItemFactory, new IdentityGraphLense());
     }
 
-    public GraphLense build(GraphLense previousLense, DexItemFactory dexItemFactory) {
+    public GraphLense build(DexItemFactory dexItemFactory, GraphLense previousLense) {
+      if (typeMap.isEmpty() && methodMap.isEmpty() && fieldMap.isEmpty()) {
+        return previousLense;
+      }
       return new NestedGraphLense(typeMap, methodMap, fieldMap, previousLense, dexItemFactory);
     }
 
@@ -96,17 +98,17 @@
     }
   }
 
-  private static class NestedGraphLense extends GraphLense {
+  public static class NestedGraphLense extends GraphLense {
 
     private final GraphLense previousLense;
-    private final DexItemFactory dexItemFactory;
+    protected final DexItemFactory dexItemFactory;
 
     private final Map<DexType, DexType> typeMap;
     private final Map<DexType, DexType> arrayTypeCache = new IdentityHashMap<>();
     private final Map<DexMethod, DexMethod> methodMap;
     private final Map<DexField, DexField> fieldMap;
 
-    private NestedGraphLense(Map<DexType, DexType> typeMap, Map<DexMethod, DexMethod> methodMap,
+    public NestedGraphLense(Map<DexType, DexType> typeMap, Map<DexMethod, DexMethod> methodMap,
         Map<DexField, DexField> fieldMap, GraphLense previousLense, DexItemFactory dexItemFactory) {
       this.typeMap = typeMap;
       this.methodMap = methodMap;
@@ -118,7 +120,7 @@
     @Override
     public DexType lookupType(DexType type, DexEncodedMethod context) {
       if (type.isArrayType()) {
-        synchronized(this) {
+        synchronized (this) {
           // This block need to be synchronized due to arrayTypeCache.
           DexType result = arrayTypeCache.get(type);
           if (result == null) {
@@ -154,5 +156,24 @@
     public boolean isContextFree() {
       return previousLense.isContextFree();
     }
+
+    @Override
+    public String toString() {
+      StringBuilder builder = new StringBuilder();
+      for (Map.Entry<DexType, DexType> entry : typeMap.entrySet()) {
+        builder.append(entry.getKey().toSourceString()).append(" -> ");
+        builder.append(entry.getValue().toSourceString()).append(System.lineSeparator());
+      }
+      for (Map.Entry<DexMethod, DexMethod> entry : methodMap.entrySet()) {
+        builder.append(entry.getKey().toSourceString()).append(" -> ");
+        builder.append(entry.getValue().toSourceString()).append(System.lineSeparator());
+      }
+      for (Map.Entry<DexField, DexField> entry : fieldMap.entrySet()) {
+        builder.append(entry.getKey().toSourceString()).append(" -> ");
+        builder.append(entry.getValue().toSourceString()).append(System.lineSeparator());
+      }
+      builder.append(previousLense.toString());
+      return builder.toString();
+    }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/graph/JarApplicationReader.java b/src/main/java/com/android/tools/r8/graph/JarApplicationReader.java
index 757ae71..6bf1a17 100644
--- a/src/main/java/com/android/tools/r8/graph/JarApplicationReader.java
+++ b/src/main/java/com/android/tools/r8/graph/JarApplicationReader.java
@@ -18,6 +18,7 @@
  * It does not currently support multithreaded reading.
  */
 public class JarApplicationReader {
+
   public final InternalOptions options;
 
   ConcurrentHashMap<String, DexString> stringCache = new ConcurrentHashMap<>();
@@ -111,8 +112,8 @@
       argumentDescriptors[i] = arguments[i].getDescriptor();
     }
     DexProto proto = options.itemFactory.createProto(
-        getString(shortyDescriptor.toString()),
         getTypeFromDescriptor(returnType.getDescriptor()),
+        getString(shortyDescriptor.toString()),
         getTypeListFromDescriptors(argumentDescriptors));
     return proto;
   }
diff --git a/src/main/java/com/android/tools/r8/graph/JarCode.java b/src/main/java/com/android/tools/r8/graph/JarCode.java
index e842028..44bc529 100644
--- a/src/main/java/com/android/tools/r8/graph/JarCode.java
+++ b/src/main/java/com/android/tools/r8/graph/JarCode.java
@@ -94,34 +94,34 @@
       throws ApiLevelException {
     triggerDelayedParsingIfNeccessary();
     return options.debug
-        ? internalBuildWithLocals(encodedMethod, null, options)
-        : internalBuild(encodedMethod, null, options);
+        ? internalBuildWithLocals(encodedMethod, options, null)
+        : internalBuild(encodedMethod, options, null);
   }
 
   public IRCode buildIR(
-      DexEncodedMethod encodedMethod, ValueNumberGenerator generator, InternalOptions options)
+      DexEncodedMethod encodedMethod, InternalOptions options, ValueNumberGenerator generator)
       throws ApiLevelException {
     assert generator != null;
     triggerDelayedParsingIfNeccessary();
     return options.debug
-        ? internalBuildWithLocals(encodedMethod, generator, options)
-        : internalBuild(encodedMethod, generator, options);
+        ? internalBuildWithLocals(encodedMethod, options, generator)
+        : internalBuild(encodedMethod, options, generator);
   }
 
   private IRCode internalBuildWithLocals(
-      DexEncodedMethod encodedMethod, ValueNumberGenerator generator, InternalOptions options)
+      DexEncodedMethod encodedMethod, InternalOptions options, ValueNumberGenerator generator)
       throws ApiLevelException {
     try {
-      return internalBuild(encodedMethod, generator, options);
+      return internalBuild(encodedMethod, options, generator);
     } catch (InvalidDebugInfoException e) {
       options.warningInvalidDebugInfo(encodedMethod, origin, e);
       node.localVariables.clear();
-      return internalBuild(encodedMethod, generator, options);
+      return internalBuild(encodedMethod, options, generator);
     }
   }
 
   private IRCode internalBuild(
-      DexEncodedMethod encodedMethod, ValueNumberGenerator generator, InternalOptions options)
+      DexEncodedMethod encodedMethod, InternalOptions options, ValueNumberGenerator generator)
       throws ApiLevelException {
     if (!options.debug) {
       node.localVariables.clear();
@@ -130,7 +130,7 @@
     IRBuilder builder =
         (generator == null)
             ? new IRBuilder(encodedMethod, source, options)
-            : new IRBuilder(encodedMethod, source, generator, options);
+            : new IRBuilder(encodedMethod, source, options, generator);
     return builder.build();
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java b/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
index bfbfb02..37b3119 100644
--- a/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
+++ b/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
@@ -959,10 +959,10 @@
    * <p>The constructed basic block has no predecessors and has one
    * successors which is the target block.
    *
-   * @param target the target of the goto block
    * @param blockNumber the block number of the goto block
+   * @param target the target of the goto block
    */
-  public static BasicBlock createGotoBlock(BasicBlock target, int blockNumber) {
+  public static BasicBlock createGotoBlock(int blockNumber, BasicBlock target) {
     BasicBlock block = createGotoBlock(blockNumber);
     block.getSuccessors().add(target);
     return block;
diff --git a/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionIterator.java b/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionIterator.java
index ff9e257..7bf8ec1 100644
--- a/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionIterator.java
@@ -219,7 +219,7 @@
   }
 
   @Override
-  public BasicBlock split(int instructions, IRCode code, ListIterator<BasicBlock> blocksIterator) {
+  public BasicBlock split(IRCode code, int instructions, ListIterator<BasicBlock> blocksIterator) {
     // Split at the current cursor position.
     BasicBlock newBlock = split(code, blocksIterator);
     assert blocksIterator == null || IteratorUtils.peekPrevious(blocksIterator) == newBlock;
@@ -341,7 +341,7 @@
       List<BasicBlock> blocksToRemove, DexType downcast) {
     assert blocksToRemove != null;
     boolean inlineeCanThrow = canThrow(inlinee);
-    BasicBlock invokeBlock = split(1, code, blocksIterator);
+    BasicBlock invokeBlock = split(code, 1, blocksIterator);
     assert invokeBlock.getInstructions().size() == 2;
     assert invokeBlock.getInstructions().getFirst().isInvoke();
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/IRCode.java b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
index 5e0fe8e..e5fb6ed 100644
--- a/src/main/java/com/android/tools/r8/ir/code/IRCode.java
+++ b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
@@ -74,7 +74,7 @@
           // correct predecessor and successor structure. It is inserted
           // at the end of the list of blocks disregarding branching
           // structure.
-          BasicBlock newBlock = BasicBlock.createGotoBlock(block, nextBlockNumber++);
+          BasicBlock newBlock = BasicBlock.createGotoBlock(nextBlockNumber++, block);
           newBlocks.add(newBlock);
           pred.replaceSuccessor(block, newBlock);
           newBlock.getPredecessors().add(pred);
@@ -108,7 +108,7 @@
           fallthrough = fallthrough.exit().fallthroughBlock();
         }
         if (fallthrough != null) {
-          BasicBlock newFallthrough = BasicBlock.createGotoBlock(fallthrough, nextBlockNumber++);
+          BasicBlock newFallthrough = BasicBlock.createGotoBlock(nextBlockNumber++, fallthrough);
           current.exit().setFallthroughBlock(newFallthrough);
           newFallthrough.getPredecessors().add(current);
           fallthrough.replacePredecessor(current, newFallthrough);
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java
index fc7817b..603ce6e 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java
@@ -126,8 +126,8 @@
    * If the current block have catch handlers these catch handlers will be attached to the block
    * containing the throwing instruction after the split.
    *
-   * @param instructions the number of instructions to include in the second block.
    * @param code the IR code for the block this iterator originates from.
+   * @param instructions the number of instructions to include in the second block.
    * @param blockIterator basic block iterator used to iterate the blocks. This must be positioned
    * just after the block for this is the instruction iterator. After this method returns it will be
    * positioned just after the second block inserted. Calling {@link #remove} without further
@@ -135,13 +135,13 @@
    * @return Returns the new block with the instructions after the cursor.
    */
   // TODO(sgjesse): Refactor to avoid the need for passing code and blockIterator.
-  BasicBlock split(int instructions, IRCode code, ListIterator<BasicBlock> blockIterator);
+  BasicBlock split(IRCode code, int instructions, ListIterator<BasicBlock> blockIterator);
 
   /**
-   * See {@link #split(int, IRCode, ListIterator)}.
+   * See {@link #split(IRCode, int, ListIterator)}.
    */
-  default BasicBlock split(int instructions, IRCode code) {
-    return split(instructions, code, null);
+  default BasicBlock split(IRCode code, int instructions) {
+    return split(code, instructions, null);
   }
 
   /**
diff --git a/src/main/java/com/android/tools/r8/ir/code/Phi.java b/src/main/java/com/android/tools/r8/ir/code/Phi.java
index 78c5c4e..dc6d500 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Phi.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Phi.java
@@ -70,7 +70,7 @@
     for (BasicBlock pred : block.getPredecessors()) {
       EdgeType edgeType = pred.getEdgeType(block);
       // Since this read has been delayed we must provide the local info for the value.
-      Value operand = builder.readRegister(register, pred, edgeType, type, getLocalInfo());
+      Value operand = builder.readRegister(register, type, pred, edgeType, getLocalInfo());
       canBeNull |= operand.canBeNull();
       appendOperand(operand);
     }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Value.java b/src/main/java/com/android/tools/r8/ir/code/Value.java
index 1915155..ea43cf8 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Value.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Value.java
@@ -593,10 +593,10 @@
 
   public boolean isDead(InternalOptions options) {
     // Totally unused values are trivially dead.
-    return !isUsed() || isDead(new HashSet<>(), options);
+    return !isUsed() || isDead(options, new HashSet<>());
   }
 
-  protected boolean isDead(Set<Value> active, InternalOptions options) {
+  protected boolean isDead(InternalOptions options, Set<Value> active) {
     // If the value has debug users we cannot eliminate it since it represents a value in a local
     // variable that should be visible in the debugger.
     if (numberOfDebugUsers() != 0) {
@@ -613,12 +613,12 @@
       // Instructions with no out value cannot be dead code by the current definition
       // (unused out value). They typically side-effect input values or deals with control-flow.
       assert outValue != null;
-      if (!active.contains(outValue) && !outValue.isDead(active, options)) {
+      if (!active.contains(outValue) && !outValue.isDead(options, active)) {
         return false;
       }
     }
     for (Phi phi : uniquePhiUsers()) {
-      if (!active.contains(phi) && !phi.isDead(active, options)) {
+      if (!active.contains(phi) && !phi.isDead(options, active)) {
         return false;
       }
     }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java
index 042232b..2335f2b 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java
@@ -386,7 +386,7 @@
       if (ifsNeedingRewrite.contains(block)) {
         If theIf = block.exit().asIf();
         BasicBlock trueTarget = theIf.getTrueTarget();
-        BasicBlock newBlock = BasicBlock.createGotoBlock(trueTarget, ir.blocks.size());
+        BasicBlock newBlock = BasicBlock.createGotoBlock(ir.blocks.size(), trueTarget);
         theIf.setTrueTarget(newBlock);
         theIf.invert();
         it.add(newBlock);
@@ -641,7 +641,8 @@
       item = tryItems.get(i);
       coalescedTryItems.add(item);
       // Trim the range start for non-throwing instructions when starting a new range.
-      List<com.android.tools.r8.ir.code.Instruction> instructions = blocksWithHandlers.get(i).getInstructions();
+      List<com.android.tools.r8.ir.code.Instruction> instructions = blocksWithHandlers.get(i)
+          .getInstructions();
       for (com.android.tools.r8.ir.code.Instruction insn : instructions) {
         if (insn.instructionTypeCanThrow()) {
           item.start = getInfo(insn).getOffset();
@@ -819,7 +820,8 @@
     private Instruction[] instructions;
     private final int size;
 
-    public MultiFixedSizeInfo(com.android.tools.r8.ir.code.Instruction ir, Instruction[] instructions) {
+    public MultiFixedSizeInfo(com.android.tools.r8.ir.code.Instruction ir,
+        Instruction[] instructions) {
       super(ir);
       this.instructions = instructions;
       int size = 0;
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 2dceeab..4a96ae6 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
@@ -291,14 +291,13 @@
   private int nextBlockNumber = 0;
 
   public IRBuilder(DexEncodedMethod method, SourceCode source, InternalOptions options) {
-    this(method, source, new ValueNumberGenerator(), options);
+    this(method, source, options, new ValueNumberGenerator());
   }
 
   public IRBuilder(
       DexEncodedMethod method,
       SourceCode source,
-      ValueNumberGenerator valueNumberGenerator,
-      InternalOptions options) {
+      InternalOptions options, ValueNumberGenerator valueNumberGenerator) {
     assert source != null;
     this.method = method;
     this.source = source;
@@ -1624,7 +1623,7 @@
 
   public Value readRegister(int register, MoveType type) {
     DebugLocalInfo local = getCurrentLocal(register);
-    Value value = readRegister(register, currentBlock, EdgeType.NON_EDGE, type, local);
+    Value value = readRegister(register, type, currentBlock, EdgeType.NON_EDGE, local);
     // Check that any information about a current-local is consistent with the read.
     if (local != null && value.getLocalInfo() != local && !value.isUninitializedLocal()) {
       throw new InvalidDebugInfoException(
@@ -1643,10 +1642,10 @@
 
   public Value readRegisterIgnoreLocal(int register, MoveType type) {
     DebugLocalInfo local = getCurrentLocal(register);
-    return readRegister(register, currentBlock, EdgeType.NON_EDGE, type, local);
+    return readRegister(register, type, currentBlock, EdgeType.NON_EDGE, local);
   }
 
-  public Value readRegister(int register, BasicBlock block, EdgeType readingEdge, MoveType type,
+  public Value readRegister(int register, MoveType type, BasicBlock block, EdgeType readingEdge,
       DebugLocalInfo local) {
     checkRegister(register);
     Value value = block.readCurrentDefinition(register, readingEdge);
@@ -1665,7 +1664,7 @@
       assert block.verifyFilledPredecessors();
       BasicBlock pred = block.getPredecessors().get(0);
       EdgeType edgeType = pred.getEdgeType(block);
-      value = readRegister(register, pred, edgeType, type, local);
+      value = readRegister(register, type, pred, edgeType, local);
     } else {
       Phi phi = new Phi(valueNumberGenerator.next(), block, type, local);
       // We need to write the phi before adding operands to break cycles. If the phi is trivial
@@ -1939,7 +1938,7 @@
       BasicBlock target = pair.second;
 
       // New block with one unfilled predecessor.
-      BasicBlock newBlock = BasicBlock.createGotoBlock(target, nextBlockNumber++);
+      BasicBlock newBlock = BasicBlock.createGotoBlock(nextBlockNumber++, target);
       blocks.add(newBlock);
       newBlock.incrementUnfilledPredecessorCount();
 
@@ -2018,7 +2017,7 @@
             int otherPredecessorIndex = values.get(v);
             BasicBlock joinBlock = joinBlocks.get(otherPredecessorIndex);
             if (joinBlock == null) {
-              joinBlock = BasicBlock.createGotoBlock(block, blocks.size() + blocksToAdd.size());
+              joinBlock = BasicBlock.createGotoBlock(blocks.size() + blocksToAdd.size(), block);
               joinBlocks.put(otherPredecessorIndex, joinBlock);
               blocksToAdd.add(joinBlock);
               BasicBlock otherPredecessor = block.getPredecessors().get(otherPredecessorIndex);
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 dda8031..da022fe 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
@@ -82,11 +82,8 @@
   private DexString highestSortingString;
 
   private IRConverter(
-      Timing timing,
-      AppInfo appInfo,
-      GraphLense graphLense,
-      InternalOptions options,
-      CfgPrinter printer,
+      AppInfo appInfo, InternalOptions options, Timing timing,
+      CfgPrinter printer, GraphLense graphLense,
       boolean enableWholeProgramOptimizations) {
     assert appInfo != null;
     assert options != null;
@@ -127,30 +124,26 @@
   public IRConverter(
       AppInfo appInfo,
       InternalOptions options) {
-    this(null, appInfo, null, options, null, false);
+    this(appInfo, options, null, null, null, false);
   }
 
   /**
    * Create an IR converter for processing methods with full program optimization disabled.
    */
   public IRConverter(
-      Timing timing,
-      AppInfo appInfo,
-      InternalOptions options,
+      AppInfo appInfo, InternalOptions options, Timing timing,
       CfgPrinter printer) {
-    this(timing, appInfo, null, options, printer, false);
+    this(appInfo, options, timing, printer, null, false);
   }
 
   /**
    * Create an IR converter for processing methods with full program optimization enabled.
    */
   public IRConverter(
-      Timing timing,
-      AppInfoWithSubtyping appInfo,
-      InternalOptions options,
+      AppInfoWithSubtyping appInfo, InternalOptions options, Timing timing,
       CfgPrinter printer,
       GraphLense graphLense) {
-    this(timing, appInfo, graphLense, options, printer, true);
+    this(appInfo, options, timing, printer, graphLense, true);
   }
 
   private boolean enableInterfaceMethodDesugaring() {
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 ab3efdb..8a309e2 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
@@ -116,7 +116,7 @@
               // If the current block has catch handlers split the check cast into its own block.
               if (newInvoke.getBlock().hasCatchHandlers()) {
                 iterator.previous();
-                iterator.split(1, code, blocks);
+                iterator.split(code, 1, blocks);
               }
             }
           }
@@ -228,7 +228,9 @@
     return methodHandle;
   }
 
-  private Type getInvokeType(InvokeMethod invoke, DexMethod actualTarget,
+  private Type getInvokeType(
+      InvokeMethod invoke,
+      DexMethod actualTarget,
       DexMethod originalTarget) {
     if (invoke.isInvokeVirtual() || invoke.isInvokeInterface()) {
       // Get the invoke type of the actual definition.
@@ -237,7 +239,8 @@
         return invoke.getType();
       } else {
         DexClass originalTargetClass = appInfo.definitionFor(originalTarget.holder);
-        if (originalTargetClass.isInterface() ^ (invoke.getType() == Type.INTERFACE)) {
+        if ((originalTargetClass != null && originalTargetClass.isInterface())
+            ^ (invoke.getType() == Type.INTERFACE)) {
           // The invoke was wrong to start with, so we keep it wrong. This is to ensure we get
           // the IncompatibleClassChangeError the original invoke would have triggered.
           return newTargetClass.accessFlags.isInterface()
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/AccessorMethodSourceCode.java b/src/main/java/com/android/tools/r8/ir/desugar/AccessorMethodSourceCode.java
index 160da3c..1cf64fd 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/AccessorMethodSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/AccessorMethodSourceCode.java
@@ -20,7 +20,7 @@
 final class AccessorMethodSourceCode extends SynthesizedLambdaSourceCode {
 
   AccessorMethodSourceCode(LambdaClass lambda) {
-    super(/* no receiver for static method */ null, lambda, lambda.target.callTarget);
+    super(lambda, lambda.target.callTarget, null /* no receiver for static method */);
     // We should never need an accessor for interface methods since
     // they are supposed to be public.
     assert !descriptor().implHandle.type.isInvokeInterface();
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
index 31c6de1..408f53b 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
@@ -58,6 +58,7 @@
 //       forward the call to an appropriate method in interface companion class.
 //
 public final class InterfaceMethodRewriter {
+
   // Public for testing.
   public static final String COMPANION_CLASS_NAME_SUFFIX = "-CC";
   private static final String DEFAULT_METHOD_PREFIX = "$default$";
@@ -75,11 +76,17 @@
    */
   private Set<DexItem> reportedMissing = Sets.newIdentityHashSet();
 
-  /** Defines a minor variation in desugaring. */
+  /**
+   * Defines a minor variation in desugaring.
+   */
   public enum Flavor {
-    /** Process all application resources. */
+    /**
+     * Process all application resources.
+     */
     IncludeAllResources,
-    /** Process all but DEX application resources. */
+    /**
+     * Process all but DEX application resources.
+     */
     ExcludeDexResources
   }
 
@@ -174,13 +181,14 @@
       } else if (holderClass.isInterface()) {
         throw new Unimplemented(
             "Desugaring of static interface method handle as in `"
-            + referencedFrom.toSourceString() + "` in is not yet supported.");
+                + referencedFrom.toSourceString() + "` in is not yet supported.");
       }
     }
   }
 
   /**
    * Returns the class definition for the specified type.
+   *
    * @return may return null if no definition for the given type is available.
    */
   final DexClass findDefinitionFor(DexType type) {
@@ -313,7 +321,7 @@
           .append("`");
     }
     options.diagnosticsHandler.warning(
-        new StringDiagnostic(classToDesugar.getOrigin(), builder.toString()));
+        new StringDiagnostic(builder.toString(), classToDesugar.getOrigin()));
   }
 
   private void warnMissingType(DexMethod referencedFrom, DexType missing) {
@@ -331,6 +339,6 @@
         .append("`");
     DexClass referencedFromClass = converter.appInfo.definitionFor(referencedFrom.getHolder());
     options.diagnosticsHandler.warning(
-        new StringDiagnostic(referencedFromClass.getOrigin(), builder.toString()));
+        new StringDiagnostic(builder.toString(), referencedFromClass.getOrigin()));
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClassConstructorSourceCode.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClassConstructorSourceCode.java
index 539fcf0..411ab4c 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClassConstructorSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClassConstructorSourceCode.java
@@ -15,7 +15,7 @@
 final class LambdaClassConstructorSourceCode extends SynthesizedLambdaSourceCode {
 
   LambdaClassConstructorSourceCode(LambdaClass lambda) {
-    super(null /* Class initializer is static */, lambda, lambda.classConstructor);
+    super(lambda, lambda.classConstructor, null /* Class initializer is static */);
     assert lambda.instanceField != null;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/SynthesizedLambdaSourceCode.java b/src/main/java/com/android/tools/r8/ir/desugar/SynthesizedLambdaSourceCode.java
index 9a3531f..bd38e1a 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/SynthesizedLambdaSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/SynthesizedLambdaSourceCode.java
@@ -16,14 +16,14 @@
   final DexMethod currentMethod;
   final LambdaClass lambda;
 
-  SynthesizedLambdaSourceCode(DexType receiver, LambdaClass lambda, DexMethod currentMethod) {
+  SynthesizedLambdaSourceCode(LambdaClass lambda, DexMethod currentMethod, DexType receiver) {
     super(receiver, currentMethod.proto);
     this.lambda = lambda;
     this.currentMethod = currentMethod;
   }
 
   SynthesizedLambdaSourceCode(LambdaClass lambda, DexMethod currentMethod) {
-    this(lambda.type, lambda, currentMethod);
+    this(lambda, currentMethod, lambda.type);
   }
 
   final LambdaDescriptor descriptor() {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
index 9699ec1..ad4639e 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
@@ -232,14 +232,14 @@
         GraphLense graphLense, InternalOptions options) throws ApiLevelException {
       if (target.isProcessed()) {
         assert target.getCode().isDexCode();
-        return target.buildIR(generator, options);
+        return target.buildIR(options, generator);
       } else {
         // Build the IR for a yet not processed method, and perform minimal IR processing.
         IRCode code;
         if (target.getCode().isJarCode()) {
-          code = target.getCode().asJarCode().buildIR(target, generator, options);
+          code = target.getCode().asJarCode().buildIR(target, options, generator);
         } else {
-          code = target.getCode().asDexCode().buildIR(target, generator, options);
+          code = target.getCode().asDexCode().buildIR(target, options, generator);
         }
         new LensCodeRewriter(graphLense, appInfo).rewrite(code, target);
         return code;
diff --git a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
index 4662cd0..83788c7 100644
--- a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
@@ -16,9 +16,9 @@
 import java.io.IOException;
 import java.util.Collections;
 import java.util.concurrent.ExecutorService;
-import jdk.internal.org.objectweb.asm.Type;
 import org.objectweb.asm.ClassWriter;
 import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Type;
 
 public class CfApplicationWriter {
   private final DexApplication application;
diff --git a/src/main/java/com/android/tools/r8/naming/ClassNameMapper.java b/src/main/java/com/android/tools/r8/naming/ClassNameMapper.java
index 1360964..2e654a8 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNameMapper.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNameMapper.java
@@ -3,6 +3,8 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.naming;
 
+import static com.android.tools.r8.utils.DescriptorUtils.descriptorToJavaType;
+
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
@@ -10,26 +12,79 @@
 import com.android.tools.r8.naming.MemberNaming.FieldSignature;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
 import com.android.tools.r8.naming.MemberNaming.Signature;
-import com.android.tools.r8.utils.DescriptorUtils;
 import com.google.common.collect.BiMap;
 import com.google.common.collect.ImmutableBiMap;
 import com.google.common.collect.ImmutableMap;
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
 import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
 import java.io.StringWriter;
 import java.io.Writer;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.function.Consumer;
 
-public class ClassNameMapper {
+public class ClassNameMapper implements ProguardMap {
 
-  private final ImmutableMap<String, ClassNaming> classNameMappings;
+  static class Builder extends ProguardMap.Builder {
+    final ImmutableMap.Builder<String, ClassNamingForNameMapper.Builder> mapBuilder;
+
+    private Builder() {
+      mapBuilder = ImmutableMap.builder();
+    }
+
+    @Override
+    ClassNamingForNameMapper.Builder classNamingBuilder(String renamedName, String originalName) {
+      ClassNamingForNameMapper.Builder classNamingBuilder =
+          ClassNamingForNameMapper.builder(renamedName, originalName);
+      mapBuilder.put(renamedName, classNamingBuilder);
+      return classNamingBuilder;
+    }
+
+    @Override
+    ClassNameMapper build(){
+      return new ClassNameMapper(mapBuilder.build());
+    }
+  }
+
+  static Builder builder() {
+    return new Builder();
+  }
+
+  public static ClassNameMapper mapperFromInputStream(InputStream in) throws IOException {
+    BufferedReader reader = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8));
+    try (ProguardMapReader proguardReader = new ProguardMapReader(reader)) {
+      ClassNameMapper.Builder builder = ClassNameMapper.builder();
+      proguardReader.parse(builder);
+      return builder.build();
+    }
+  }
+
+  public static ClassNameMapper mapperFromFile(Path path) throws IOException {
+    return mapperFromInputStream(Files.newInputStream(path));
+  }
+
+  static ClassNameMapper mapperFromString(String contents) throws IOException {
+    return mapperFromInputStream(
+        new ByteArrayInputStream(contents.getBytes(StandardCharsets.UTF_8)));
+  }
+
+  private final ImmutableMap<String, ClassNamingForNameMapper> classNameMappings;
   private ImmutableBiMap<String, String> nameMapping;
 
   private Map<Signature, Signature> signatureMap = new HashMap<>();
 
-  ClassNameMapper(Map<String, ClassNaming> classNameMappings) {
-    this.classNameMappings = ImmutableMap.copyOf(classNameMappings);
+  private ClassNameMapper(Map<String, ClassNamingForNameMapper.Builder> classNameMappings) {
+    ImmutableMap.Builder<String, ClassNamingForNameMapper> builder = ImmutableMap.builder();
+    for(Map.Entry<String, ClassNamingForNameMapper.Builder> entry : classNameMappings.entrySet()) {
+      builder.put(entry.getKey(), entry.getValue().build());
+    }
+    this.classNameMappings = builder.build();
   }
 
   private Signature canonicalizeSignature(Signature signature) {
@@ -65,7 +120,7 @@
    * Returns the deobfuscated name if a mapping was found. Otherwise it returns the passed in name.
    */
   public String deobfuscateClassName(String name) {
-    ClassNaming classNaming = classNameMappings.get(name);
+    ClassNamingForNameMapper classNaming = classNameMappings.get(name);
     if (classNaming == null) {
       return name;
     }
@@ -73,15 +128,27 @@
   }
 
   private String deobfuscateType(String asString) {
-    return DescriptorUtils.descriptorToJavaType(asString, this);
+    return descriptorToJavaType(asString, this);
   }
 
-  public ClassNaming getClassNaming(String name) {
+  @Override
+  public boolean hasMapping(DexType type) {
+    String decoded = descriptorToJavaType(type.descriptor.toString());
+    return classNameMappings.containsKey(decoded);
+  }
+
+  @Override
+  public ClassNamingForNameMapper getClassNaming(DexType type) {
+    String decoded = descriptorToJavaType(type.descriptor.toString());
+    return classNameMappings.get(decoded);
+  }
+
+  public ClassNamingForNameMapper getClassNaming(String name) {
     return classNameMappings.get(name);
   }
 
   public void write(Writer writer, boolean collapseRanges) throws IOException {
-    for (ClassNaming naming : classNameMappings.values()) {
+    for (ClassNamingForNameMapper naming : classNameMappings.values()) {
       naming.write(writer, collapseRanges);
     }
   }
@@ -129,15 +196,15 @@
     } else if (item instanceof DexMethod) {
       return lookupName(getRenamedMethodSignature((DexMethod) item), ((DexMethod) item).holder);
     } else if (item instanceof DexType) {
-      return DescriptorUtils.descriptorToJavaType(((DexType) item).toDescriptorString(), this);
+      return descriptorToJavaType(((DexType) item).toDescriptorString(), this);
     } else {
       return item.toString();
     }
   }
 
   private String lookupName(Signature signature, DexType clazz) {
-    String decoded = DescriptorUtils.descriptorToJavaType(clazz.descriptor.toString());
-    ClassNaming classNaming = getClassNaming(decoded);
+    String decoded = descriptorToJavaType(clazz.descriptor.toString());
+    ClassNamingForNameMapper classNaming = getClassNaming(decoded);
     if (classNaming == null) {
       return decoded + " " + signature.toString();
     }
@@ -149,8 +216,7 @@
   }
 
   public Signature originalSignatureOf(DexMethod method) {
-    String decoded = DescriptorUtils
-        .descriptorToJavaType(method.holder.descriptor.toString());
+    String decoded = descriptorToJavaType(method.holder.descriptor.toString());
     MethodSignature memberSignature = getRenamedMethodSignature(method);
     ClassNaming classNaming = getClassNaming(decoded);
     if (classNaming == null) {
diff --git a/src/main/java/com/android/tools/r8/naming/ClassNaming.java b/src/main/java/com/android/tools/r8/naming/ClassNaming.java
index 74c5006..d211e7b 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNaming.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNaming.java
@@ -4,114 +4,30 @@
 package com.android.tools.r8.naming;
 
 import com.android.tools.r8.naming.MemberNaming.Signature;
-import java.io.IOException;
-import java.io.StringWriter;
-import java.io.Writer;
-import java.util.ArrayList;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.function.Consumer;
+import com.android.tools.r8.utils.ThrowingConsumer;
 
 /**
  * Stores name information for a class.
  * <p>
- * This includes how the class was renamed and information on the classes members.
+ * Implementers will include how the class was renamed and information on the class's members.
  */
-public class ClassNaming {
+public interface ClassNaming {
 
-  public final String originalName;
-  public final String renamedName;
-
-  /**
-   * Mapping from the renamed signature to the naming information for a member.
-   * <p>
-   * A renamed signature is a signature where the member's name has been obfuscated but not the type
-   * information.
-   **/
-  final Map<Signature, MemberNaming> members = new LinkedHashMap<>();
-
-  ClassNaming(String renamedName, String originalName) {
-    this.renamedName = renamedName;
-    this.originalName = originalName;
+  abstract class Builder {
+    abstract Builder addMemberEntry(MemberNaming entry);
+    abstract ClassNaming build();
   }
 
-  void addMemberEntry(MemberNaming entry) {
-    Signature renamedSignature = entry.renamedSignature;
-    members.put(renamedSignature, entry);
-  }
+  MemberNaming lookup(Signature renamedSignature);
 
-  public MemberNaming lookup(Signature renamedSignature) {
-    return members.get(renamedSignature);
-  }
+  MemberNaming lookupByOriginalSignature(Signature original);
 
-  public MemberNaming lookupByOriginalSignature(Signature original) {
-    for (MemberNaming naming : members.values()) {
-      if (naming.signature.equals(original)) {
-        return naming;
-      }
-    }
-    return null;
-  }
+  <T extends Throwable> void forAllMemberNaming(
+      ThrowingConsumer<MemberNaming, T> consumer) throws T;
 
-  public List<MemberNaming> lookupByOriginalName(String originalName) {
-    List<MemberNaming> result = new ArrayList<>();
-    for (MemberNaming naming : members.values()) {
-      if (naming.signature.name.equals(originalName)) {
-        result.add(naming);
-      }
-    }
-    return result;
-  }
+  <T extends Throwable> void forAllFieldNaming(
+      ThrowingConsumer<MemberNaming, T> consumer) throws T;
 
-  public void forAllMemberNaming(Consumer<MemberNaming> consumer) {
-    members.values().forEach(consumer);
-  }
-
-  void write(Writer writer, boolean collapseRanges) throws IOException {
-    writer.append(originalName);
-    writer.append(" -> ");
-    writer.append(renamedName);
-    writer.append(":\n");
-    for (MemberNaming member : members.values()) {
-      member.write(writer, collapseRanges, true);
-    }
-  }
-
-  @Override
-  public String toString() {
-    try {
-      StringWriter writer = new StringWriter();
-      write(writer, false);
-      return writer.toString();
-    } catch (IOException e) {
-      return e.toString();
-    }
-  }
-
-  @Override
-  public boolean equals(Object o) {
-    if (this == o) {
-      return true;
-    }
-    if (!(o instanceof ClassNaming)) {
-      return false;
-    }
-
-    ClassNaming that = (ClassNaming) o;
-
-    return originalName.equals(that.originalName)
-        && renamedName.equals(that.renamedName)
-        && members.equals(that.members);
-
-  }
-
-  @Override
-  public int hashCode() {
-    int result = originalName.hashCode();
-    result = 31 * result + renamedName.hashCode();
-    result = 31 * result + members.hashCode();
-    return result;
-  }
+  <T extends Throwable> void forAllMethodNaming(
+      ThrowingConsumer<MemberNaming, T> consumer) throws T;
 }
-
diff --git a/src/main/java/com/android/tools/r8/naming/ClassNamingForMapApplier.java b/src/main/java/com/android/tools/r8/naming/ClassNamingForMapApplier.java
new file mode 100644
index 0000000..d94fe57
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/naming/ClassNamingForMapApplier.java
@@ -0,0 +1,193 @@
+// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.naming;
+
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.naming.MemberNaming.FieldSignature;
+import com.android.tools.r8.naming.MemberNaming.MethodSignature;
+import com.android.tools.r8.naming.MemberNaming.Signature;
+import com.android.tools.r8.naming.MemberNaming.Signature.SignatureKind;
+import com.android.tools.r8.utils.ThrowingConsumer;
+import com.google.common.collect.ImmutableMap;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Stores name information for a class.
+ * <p>
+ * The main differences of this against {@link ClassNamingForNameMapper} are:
+ *   1) field and method mappings are maintained and searched separately for faster lookup;
+ *   2) similar to the relation between {@link ClassNameMapper} and {@link SeedMapper}, this one
+ *   uses original {@link Signature} as a key to look up {@link MemberNaming},
+ *   whereas {@link ClassNamingForNameMapper} uses renamed {@link Signature} as a key; and thus
+ *   3) logic of {@link #lookup} and {@link #lookupByOriginalSignature} are inverted; and
+ *   4) {@link #lookupByOriginalItem}'s are introduced for lightweight lookup.
+ */
+public class ClassNamingForMapApplier implements ClassNaming {
+
+  public static class Builder extends ClassNaming.Builder {
+    private final String originalName;
+    private final String renamedName;
+    private final Map<MethodSignature, MemberNaming> methodMembers = new HashMap<>();
+    private final Map<FieldSignature, MemberNaming> fieldMembers = new HashMap<>();
+
+    private Builder(String renamedName, String originalName) {
+      this.originalName = originalName;
+      this.renamedName = renamedName;
+    }
+
+    @Override
+    ClassNaming.Builder addMemberEntry(MemberNaming entry) {
+      // Unlike {@link ClassNamingForNameMapper.Builder#addMemberEntry},
+      // the key is original signature.
+      if (entry.isMethodNaming()) {
+        methodMembers.put((MethodSignature) entry.getOriginalSignature(), entry);
+      } else {
+        fieldMembers.put((FieldSignature) entry.getOriginalSignature(), entry);
+      }
+      return this;
+    }
+
+    @Override
+    ClassNamingForMapApplier build() {
+      return new ClassNamingForMapApplier(renamedName, originalName, methodMembers, fieldMembers);
+    }
+  }
+
+  static Builder builder(String renamedName, String originalName) {
+    return new Builder(renamedName, originalName);
+  }
+
+  private final String originalName;
+  final String renamedName;
+
+  private final ImmutableMap<MethodSignature, MemberNaming> methodMembers;
+  private final ImmutableMap<FieldSignature, MemberNaming> fieldMembers;
+
+  // Constructor to help chaining {@link ClassNamingForMapApplier} according to class hierarchy.
+  ClassNamingForMapApplier(ClassNamingForMapApplier proxy) {
+    this(proxy.renamedName, proxy.originalName, proxy.methodMembers, proxy.fieldMembers);
+  }
+
+  private ClassNamingForMapApplier(
+      String renamedName,
+      String originalName,
+      Map<MethodSignature, MemberNaming> methodMembers,
+      Map<FieldSignature, MemberNaming> fieldMembers) {
+    this.renamedName = renamedName;
+    this.originalName = originalName;
+    this.methodMembers = ImmutableMap.copyOf(methodMembers);
+    this.fieldMembers = ImmutableMap.copyOf(fieldMembers);
+  }
+
+  @Override
+  public <T extends Throwable> void forAllMemberNaming(
+      ThrowingConsumer<MemberNaming, T> consumer) throws T {
+    forAllFieldNaming(consumer);
+    forAllMethodNaming(consumer);
+  }
+
+  @Override
+  public <T extends Throwable> void forAllFieldNaming(
+      ThrowingConsumer<MemberNaming, T> consumer) throws T {
+    for (MemberNaming naming : fieldMembers.values()) {
+      consumer.accept(naming);
+    }
+  }
+
+  @Override
+  public <T extends Throwable> void forAllMethodNaming(
+      ThrowingConsumer<MemberNaming, T> consumer) throws T {
+    for (MemberNaming naming : methodMembers.values()) {
+      consumer.accept(naming);
+    }
+  }
+
+  @Override
+  public MemberNaming lookup(Signature renamedSignature) {
+    // As the key is inverted, this looks a lot like
+    //   {@link ClassNamingForNameMapper#lookupByOriginalSignature}.
+    if (renamedSignature.kind() == SignatureKind.METHOD) {
+      for (MemberNaming memberNaming : methodMembers.values()) {
+        if (memberNaming.getRenamedSignature().equals(renamedSignature)) {
+          return memberNaming;
+        }
+      }
+      return null;
+    } else {
+      assert renamedSignature.kind() == SignatureKind.FIELD;
+      for (MemberNaming memberNaming : fieldMembers.values()) {
+        if (memberNaming.getRenamedSignature().equals(renamedSignature)) {
+          return memberNaming;
+        }
+      }
+      return null;
+    }
+  }
+
+  @Override
+  public MemberNaming lookupByOriginalSignature(Signature original) {
+    // As the key is inverted, this looks a lot like {@link ClassNamingForNameMapper#lookup}.
+    if (original.kind() == SignatureKind.METHOD) {
+      return methodMembers.get(original);
+    } else {
+      assert original.kind() == SignatureKind.FIELD;
+      return fieldMembers.get(original);
+    }
+  }
+
+  MemberNaming lookupByOriginalItem(DexField field) {
+    for (Map.Entry<FieldSignature, MemberNaming> entry : fieldMembers.entrySet()) {
+      FieldSignature signature = entry.getKey();
+      if (signature.name.equals(field.name.toString())
+          && signature.type.equals(field.type.getName())) {
+        return entry.getValue();
+      }
+    }
+    return null;
+  }
+
+  protected MemberNaming lookupByOriginalItem(DexMethod method) {
+    for (Map.Entry<MethodSignature, MemberNaming> entry : methodMembers.entrySet()) {
+      MethodSignature signature = entry.getKey();
+      if (signature.name.equals(method.name.toString())
+          && signature.type.equals(method.proto.returnType.toString())
+          && Arrays.equals(signature.parameters,
+              Arrays.stream(method.proto.parameters.values)
+                  .map(DexType::toString).toArray(String[]::new))) {
+        return entry.getValue();
+      }
+    }
+    return null;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (!(o instanceof ClassNamingForMapApplier)) {
+      return false;
+    }
+
+    ClassNamingForMapApplier that = (ClassNamingForMapApplier) o;
+
+    return originalName.equals(that.originalName)
+        && renamedName.equals(that.renamedName)
+        && methodMembers.equals(that.methodMembers)
+        && fieldMembers.equals(that.fieldMembers);
+  }
+
+  @Override
+  public int hashCode() {
+    int result = originalName.hashCode();
+    result = 31 * result + renamedName.hashCode();
+    result = 31 * result + methodMembers.hashCode();
+    result = 31 * result + fieldMembers.hashCode();
+    return result;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java b/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java
new file mode 100644
index 0000000..1585492
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java
@@ -0,0 +1,194 @@
+// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.naming;
+
+import com.android.tools.r8.naming.MemberNaming.FieldSignature;
+import com.android.tools.r8.naming.MemberNaming.MethodSignature;
+import com.android.tools.r8.naming.MemberNaming.Signature;
+import com.android.tools.r8.naming.MemberNaming.Signature.SignatureKind;
+import com.android.tools.r8.utils.ThrowingConsumer;
+import com.google.common.collect.ImmutableMap;
+import java.io.IOException;
+import java.io.StringWriter;
+import java.io.Writer;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Stores name information for a class.
+ * <p>
+ * This includes how the class was renamed and information on the classes members.
+ */
+public class ClassNamingForNameMapper implements ClassNaming {
+
+  public static class Builder extends ClassNaming.Builder {
+    private final String originalName;
+    private final String renamedName;
+    private final Map<MethodSignature, MemberNaming> methodMembers = new HashMap<>();
+    private final Map<FieldSignature, MemberNaming> fieldMembers = new HashMap<>();
+
+    private Builder(String renamedName, String originalName) {
+      this.originalName = originalName;
+      this.renamedName = renamedName;
+    }
+
+    @Override
+    ClassNaming.Builder addMemberEntry(MemberNaming entry) {
+      if (entry.isMethodNaming()) {
+        methodMembers.put((MethodSignature) entry.getRenamedSignature(), entry);
+      } else {
+        fieldMembers.put((FieldSignature) entry.getRenamedSignature(), entry);
+      }
+      return this;
+    }
+
+    @Override
+    ClassNamingForNameMapper build() {
+      return new ClassNamingForNameMapper(renamedName, originalName, methodMembers, fieldMembers);
+    }
+  }
+
+  static Builder builder(String renamedName, String originalName) {
+    return new Builder(renamedName, originalName);
+  }
+
+  public final String originalName;
+  private final String renamedName;
+
+  /**
+   * Mapping from the renamed signature to the naming information for a member.
+   * <p>
+   * A renamed signature is a signature where the member's name has been obfuscated but not the type
+   * information.
+   **/
+  private final ImmutableMap<MethodSignature, MemberNaming> methodMembers;
+  private final ImmutableMap<FieldSignature, MemberNaming> fieldMembers;
+
+  private ClassNamingForNameMapper(
+      String renamedName,
+      String originalName,
+      Map<MethodSignature, MemberNaming> methodMembers,
+      Map<FieldSignature, MemberNaming> fieldMembers) {
+    this.renamedName = renamedName;
+    this.originalName = originalName;
+    this.methodMembers = ImmutableMap.copyOf(methodMembers);
+    this.fieldMembers = ImmutableMap.copyOf(fieldMembers);
+  }
+
+  @Override
+  public MemberNaming lookup(Signature renamedSignature) {
+    if (renamedSignature.kind() == SignatureKind.METHOD) {
+      return methodMembers.get(renamedSignature);
+    } else {
+      assert renamedSignature.kind() == SignatureKind.FIELD;
+      return fieldMembers.get(renamedSignature);
+    }
+  }
+
+  @Override
+  public MemberNaming lookupByOriginalSignature(Signature original) {
+    if (original.kind() == SignatureKind.METHOD) {
+      for (MemberNaming memberNaming: methodMembers.values()) {
+        if (memberNaming.signature.equals(original)) {
+          return memberNaming;
+        }
+      }
+      return null;
+    } else {
+      assert original.kind() == SignatureKind.FIELD;
+      for (MemberNaming memberNaming : fieldMembers.values()) {
+        if (memberNaming.signature.equals(original)) {
+          return memberNaming;
+        }
+      }
+      return null;
+    }
+  }
+
+  public List<MemberNaming> lookupByOriginalName(String originalName) {
+    List<MemberNaming> result = new ArrayList<>();
+    for (MemberNaming naming : methodMembers.values()) {
+      if (naming.signature.name.equals(originalName)) {
+        result.add(naming);
+      }
+    }
+    for (MemberNaming naming : fieldMembers.values()) {
+      if (naming.signature.name.equals(originalName)) {
+        result.add(naming);
+      }
+    }
+    return result;
+  }
+
+  @Override
+  public <T extends Throwable> void forAllMemberNaming(
+      ThrowingConsumer<MemberNaming, T> consumer) throws T {
+    forAllFieldNaming(consumer);
+    forAllMethodNaming(consumer);
+  }
+
+  @Override
+  public <T extends Throwable> void forAllFieldNaming(
+      ThrowingConsumer<MemberNaming, T> consumer) throws T {
+    for (MemberNaming naming : fieldMembers.values()) {
+      consumer.accept(naming);
+    }
+  }
+
+  @Override
+  public <T extends Throwable> void forAllMethodNaming(
+      ThrowingConsumer<MemberNaming, T> consumer) throws T {
+    for (MemberNaming naming : methodMembers.values()) {
+      consumer.accept(naming);
+    }
+  }
+
+  void write(Writer writer, boolean collapseRanges) throws IOException {
+    writer.append(originalName);
+    writer.append(" -> ");
+    writer.append(renamedName);
+    writer.append(":\n");
+    forAllMemberNaming(memberNaming -> memberNaming.write(writer, collapseRanges, true));
+  }
+
+  @Override
+  public String toString() {
+    try {
+      StringWriter writer = new StringWriter();
+      write(writer, false);
+      return writer.toString();
+    } catch (IOException e) {
+      return e.toString();
+    }
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (!(o instanceof ClassNamingForNameMapper)) {
+      return false;
+    }
+
+    ClassNamingForNameMapper that = (ClassNamingForNameMapper) o;
+
+    return originalName.equals(that.originalName)
+        && renamedName.equals(that.renamedName)
+        && methodMembers.equals(that.methodMembers)
+        && fieldMembers.equals(that.fieldMembers);
+  }
+
+  @Override
+  public int hashCode() {
+    int result = originalName.hashCode();
+    result = 31 * result + renamedName.hashCode();
+    result = 31 * result + methodMembers.hashCode();
+    result = 31 * result + fieldMembers.hashCode();
+    return result;
+  }
+}
+
diff --git a/src/main/java/com/android/tools/r8/naming/MemberNaming.java b/src/main/java/com/android/tools/r8/naming/MemberNaming.java
index 5f2ea93..c494c08 100644
--- a/src/main/java/com/android/tools/r8/naming/MemberNaming.java
+++ b/src/main/java/com/android/tools/r8/naming/MemberNaming.java
@@ -3,8 +3,11 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.naming;
 
+import static com.android.tools.r8.utils.DescriptorUtils.javaTypeToDescriptor;
+
 import com.android.tools.r8.dex.Constants;
 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.DexType;
 import com.android.tools.r8.naming.MemberNaming.Signature.SignatureKind;
@@ -90,6 +93,14 @@
     return signature;
   }
 
+  public String getOriginalName() {
+    return signature.name;
+  }
+
+  public Signature getRenamedSignature() {
+    return renamedSignature;
+  }
+
   public String getRenamedName() {
     return renamedSignature.name;
   }
@@ -222,6 +233,13 @@
           field.type.toSourceString());
     }
 
+    DexField toDexField(DexItemFactory factory, DexType clazz) {
+      return factory.createField(
+          clazz,
+          factory.createType(javaTypeToDescriptor(type)),
+          factory.createString(name));
+    }
+
     @Override
     Signature asRenamed(String renamedName) {
       return new FieldSignature(renamedName, type);
@@ -297,6 +315,18 @@
           parameterTypes);
     }
 
+    DexMethod toDexMethod(DexItemFactory factory, DexType clazz) {
+      DexType[] paramTypes = new DexType[parameters.length];
+      for (int i = 0; i < parameters.length; i++) {
+        paramTypes[i] = factory.createType(javaTypeToDescriptor(parameters[i]));
+      }
+      DexType returnType = factory.createType(javaTypeToDescriptor(type));
+      return factory.createMethod(
+          clazz,
+          factory.createProto(returnType, paramTypes),
+          factory.createString(name));
+    }
+
     public static MethodSignature initializer(String[] parameters) {
       return new MethodSignature(Constants.INSTANCE_INITIALIZER_NAME, "void", parameters);
     }
diff --git a/src/main/java/com/android/tools/r8/naming/MinifiedNameMapPrinter.java b/src/main/java/com/android/tools/r8/naming/MinifiedNameMapPrinter.java
index e9ec08c..0e971c2 100644
--- a/src/main/java/com/android/tools/r8/naming/MinifiedNameMapPrinter.java
+++ b/src/main/java/com/android/tools/r8/naming/MinifiedNameMapPrinter.java
@@ -43,24 +43,24 @@
     return copy;
   }
 
-  private void write(DexProgramClass clazz, PrintStream out) {
+  private void writeClass(DexProgramClass clazz, PrintStream out) {
     seenTypes.add(clazz.type);
     DexString descriptor = namingLens.lookupDescriptor(clazz.type);
     out.print(DescriptorUtils.descriptorToJavaType(clazz.type.descriptor.toSourceString()));
     out.print(" -> ");
     out.print(DescriptorUtils.descriptorToJavaType(descriptor.toSourceString()));
     out.println(":");
-    write(sortedCopy(
+    writeFields(sortedCopy(
         clazz.instanceFields(), Comparator.comparing(DexEncodedField::toSourceString)), out);
-    write(sortedCopy(
+    writeFields(sortedCopy(
         clazz.staticFields(), Comparator.comparing(DexEncodedField::toSourceString)), out);
-    write(sortedCopy(
+    writeMethods(sortedCopy(
         clazz.directMethods(), Comparator.comparing(DexEncodedMethod::toSourceString)), out);
-    write(sortedCopy(
+    writeMethods(sortedCopy(
         clazz.virtualMethods(), Comparator.comparing(DexEncodedMethod::toSourceString)), out);
   }
 
-  private void write(DexType type, PrintStream out) {
+  private void writeType(DexType type, PrintStream out) {
     if (type.isClassType() && seenTypes.add(type)) {
       DexString descriptor = namingLens.lookupDescriptor(type);
       out.print(DescriptorUtils.descriptorToJavaType(type.descriptor.toSourceString()));
@@ -70,7 +70,7 @@
     }
   }
 
-  private void write(DexEncodedField[] fields, PrintStream out) {
+  private void writeFields(DexEncodedField[] fields, PrintStream out) {
     for (DexEncodedField encodedField : fields) {
       DexField field = encodedField.field;
       DexString renamed = namingLens.lookupName(field);
@@ -102,7 +102,7 @@
     out.println(renamed);
   }
 
-  private void write(DexEncodedMethod[] methods, PrintStream out) {
+  private void writeMethods(DexEncodedMethod[] methods, PrintStream out) {
     for (DexEncodedMethod encodedMethod : methods) {
       DexMethod method = encodedMethod.method;
       DexString renamed = namingLens.lookupName(method);
@@ -125,9 +125,9 @@
     // First write out all classes that have been renamed.
     List<DexProgramClass> classes = new ArrayList<>(application.classes());
     classes.sort(Comparator.comparing(DexProgramClass::toSourceString));
-    classes.forEach(clazz -> write(clazz, out));
+    classes.forEach(clazz -> writeClass(clazz, out));
     // Now write out all types only mentioned in descriptors that have been renamed.
-    namingLens.forAllRenamedTypes(type -> write(type, out));
+    namingLens.forAllRenamedTypes(type -> writeType(type, out));
   }
 
   public void write(Path destination) throws IOException {
diff --git a/src/main/java/com/android/tools/r8/naming/ProguardMap.java b/src/main/java/com/android/tools/r8/naming/ProguardMap.java
new file mode 100644
index 0000000..5b5c76e
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/naming/ProguardMap.java
@@ -0,0 +1,17 @@
+// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.naming;
+
+import com.android.tools.r8.graph.DexType;
+
+public interface ProguardMap {
+
+  abstract class Builder {
+    abstract ClassNaming.Builder classNamingBuilder(String renamedName, String originalName);
+    abstract ProguardMap build();
+  }
+
+  boolean hasMapping(DexType type);
+  ClassNaming getClassNaming(DexType type);
+}
diff --git a/src/main/java/com/android/tools/r8/naming/ProguardMapApplier.java b/src/main/java/com/android/tools/r8/naming/ProguardMapApplier.java
new file mode 100644
index 0000000..1a2551e
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/naming/ProguardMapApplier.java
@@ -0,0 +1,475 @@
+// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.naming;
+
+import com.android.tools.r8.graph.DexAnnotation;
+import com.android.tools.r8.graph.DexAnnotationSet;
+import com.android.tools.r8.graph.DexClass;
+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.DexMethod;
+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.GraphLense;
+import com.android.tools.r8.naming.MemberNaming.FieldSignature;
+import com.android.tools.r8.naming.MemberNaming.MethodSignature;
+import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
+import com.android.tools.r8.utils.ArrayUtils;
+import com.android.tools.r8.utils.ThrowingConsumer;
+import com.android.tools.r8.utils.Timing;
+import it.unimi.dsi.fastutil.ints.Int2ObjectArrayMap;
+import java.util.HashSet;
+import java.util.IdentityHashMap;
+import java.util.Map;
+import java.util.Set;
+
+public class ProguardMapApplier {
+
+  private final AppInfoWithLiveness appInfo;
+  private final GraphLense previousLense;
+  private final SeedMapper seedMapper;
+
+  public ProguardMapApplier(
+      AppInfoWithLiveness appInfo,
+      GraphLense previousLense,
+      SeedMapper seedMapper) {
+    this.appInfo = appInfo;
+    this.previousLense = previousLense;
+    this.seedMapper = seedMapper;
+  }
+
+  public GraphLense run(Timing timing) {
+    timing.begin("from-pg-map-to-lense");
+    GraphLense lenseFromMap = new MapToLenseConverter().run(previousLense);
+    timing.end();
+    timing.begin("fix-types-in-programs");
+    GraphLense typeFixedLense = new TreeFixer(lenseFromMap).run();
+    timing.end();
+    return typeFixedLense;
+  }
+
+  class MapToLenseConverter {
+
+    private final ConflictFreeBuilder lenseBuilder;
+    private final Map<DexProto, DexProto> protoFixupCache = new IdentityHashMap<>();
+
+    MapToLenseConverter() {
+      lenseBuilder = new ConflictFreeBuilder();
+    }
+
+    private GraphLense run(GraphLense previousLense) {
+      // To handle inherited yet undefined methods in library classes, we are traversing types in
+      // a subtyping order. That also helps us detect conflicted mappings in a diamond case:
+      //   LibItfA#foo -> a, LibItfB#foo -> b, while PrgA implements LibItfA and LibItfB.
+      // For all type appearances in members, we apply class mappings on-the-fly, e.g.,
+      //   LibA -> a:
+      //     ...foo(LibB) -> bar
+      //   LibB -> b:
+      // Suppose PrgA extends LibA, and type map for LibB to b is not applied when visiting PrgA.
+      // Then, method map would look like: PrgA#foo(LibB) -> PrgA#bar(LibB),
+      //   which should be: PrgA#foo(LibB) -> PrgA#bar(b).
+      // In this case, we should check class naming for LibB in the given pg-map, and if exist,
+      // should apply that naming at the time when making a mapping for PrgA#foo.
+      applyClassMappingForClasses(appInfo.dexItemFactory.objectType, null);
+      // TODO(b/64802420): maybe worklist-based visiting?
+      // Suppose PrgA implements LibItfA, LibItfB, and LibItfC; and PrgB extends PrgA.
+      // With interface hierarchy-based visiting, both program classes are visited three times.
+      DexType.forAllInterfaces(
+          appInfo.dexItemFactory,
+          itf -> applyClassMappingForInterfaces(itf, null));
+      return lenseBuilder.build(appInfo.dexItemFactory, previousLense);
+    }
+
+    private void applyClassMappingForClasses(
+        DexType type, ChainedClassNaming classNamingFromSuperType) {
+      ChainedClassNaming classNaming = chainClassNaming(type, classNamingFromSuperType);
+      applyClassMapping(type, classNaming);
+      type.forAllExtendsSubtypes(subtype -> {
+        applyClassMappingForClasses(subtype, classNaming);
+      });
+    }
+
+    private void applyClassMappingForInterfaces(
+        DexType type, ChainedClassNaming classNamingFromSuperType) {
+      ChainedClassNaming classNaming = chainClassNaming(type, classNamingFromSuperType);
+      DexClass clazz = appInfo.definitionFor(type);
+      // We account program classes that implement library interfaces (to be obfuscated).
+      if (clazz != null && clazz.isProgramClass()) {
+        applyClassMapping(type, classNaming);
+      }
+      type.forAllExtendsSubtypes(subtype -> {
+        applyClassMappingForInterfaces(subtype, classNaming);
+      });
+      type.forAllImplementsSubtypes(subtype -> {
+        applyClassMappingForInterfaces(subtype, classNaming);
+      });
+    }
+
+    private ChainedClassNaming chainClassNaming(
+        DexType type, ChainedClassNaming classNamingFromSuperType) {
+      // Use super-type's mapping or update the mapping if the current type has its own mapping.
+      return seedMapper.hasMapping(type)
+          ? new ChainedClassNaming(classNamingFromSuperType, seedMapper.getClassNaming(type))
+          : classNamingFromSuperType;
+    }
+
+    private void applyClassMapping(DexType type, ChainedClassNaming classNaming) {
+      if (classNaming != null) {
+        if (seedMapper.hasMapping(type) && lenseBuilder.lookup(type) == type) {
+          DexType appliedType = appInfo.dexItemFactory.createType(classNaming.renamedName);
+          lenseBuilder.map(type, appliedType);
+        }
+        applyMemberMapping(type, classNaming);
+      }
+    }
+
+    private void applyMemberMapping(DexType from, ChainedClassNaming classNaming) {
+      DexClass clazz = appInfo.definitionFor(from);
+      if (clazz == null) return;
+
+      Set<MemberNaming> appliedMemberNaming = new HashSet<>();
+      clazz.forEachField(encodedField -> {
+        MemberNaming memberNaming = classNaming.lookupByOriginalItem(encodedField.field);
+        if (memberNaming != null) {
+          appliedMemberNaming.add(memberNaming);
+          applyFieldMapping(encodedField.field, memberNaming);
+        }
+      });
+
+      clazz.forEachMethod(encodedMethod -> {
+        MemberNaming memberNaming = classNaming.lookupByOriginalItem(encodedMethod.method);
+        if (memberNaming != null) {
+          appliedMemberNaming.add(memberNaming);
+          applyMethodMapping(encodedMethod.method, memberNaming);
+        }
+      });
+
+      // We need to handle a lib class that extends another lib class where some members are not
+      // overridden, resulting in absence of definitions. References to those members need to be
+      // redirected via lense as well.
+      if (clazz.isLibraryClass()) {
+        classNaming.forAllFieldNaming(memberNaming -> {
+          if (!appliedMemberNaming.contains(memberNaming)) {
+            DexField pretendedOriginalField =
+                ((FieldSignature) memberNaming.getOriginalSignature())
+                    .toDexField(appInfo.dexItemFactory, from);
+            applyFieldMapping(pretendedOriginalField, memberNaming);
+          }
+        });
+        classNaming.forAllMethodNaming(memberNaming -> {
+          if (!appliedMemberNaming.contains(memberNaming)) {
+            DexMethod pretendedOriginalMethod =
+                ((MethodSignature) memberNaming.getOriginalSignature())
+                    .toDexMethod(appInfo.dexItemFactory, from);
+            applyMethodMapping(pretendedOriginalMethod, memberNaming);
+          }
+        });
+      }
+    }
+
+    private void applyFieldMapping(DexField originalField, MemberNaming memberNaming) {
+      FieldSignature appliedSignature = (FieldSignature) memberNaming.getRenamedSignature();
+      DexField appliedField =
+          appInfo.dexItemFactory.createField(
+              applyClassMappingOnTheFly(originalField.clazz),
+              applyClassMappingOnTheFly(originalField.type),
+              appInfo.dexItemFactory.createString(appliedSignature.name));
+      lenseBuilder.map(originalField, appliedField);
+    }
+
+    private void applyMethodMapping(DexMethod originalMethod, MemberNaming memberNaming) {
+      MethodSignature appliedSignature = (MethodSignature) memberNaming.getRenamedSignature();
+      DexMethod appliedMethod =
+          appInfo.dexItemFactory.createMethod(
+              applyClassMappingOnTheFly(originalMethod.holder),
+              applyClassMappingOnTheFly(originalMethod.proto),
+              appInfo.dexItemFactory.createString(appliedSignature.name));
+      lenseBuilder.map(originalMethod, appliedMethod);
+    }
+
+    private DexType applyClassMappingOnTheFly(DexType from) {
+      if (seedMapper.hasMapping(from)) {
+        DexType appliedType = lenseBuilder.lookup(from);
+        if (appliedType != from) {
+          return appliedType;
+        }
+        // If not applied yet, build the type mapping here.
+        // Note that, unlike {@link #applyClassMapping}, we don't apply member mappings.
+        ClassNamingForMapApplier classNaming = seedMapper.getClassNaming(from);
+        appliedType = appInfo.dexItemFactory.createType(classNaming.renamedName);
+        lenseBuilder.map(from, appliedType);
+        return appliedType;
+      }
+      return from;
+    }
+
+    private DexProto applyClassMappingOnTheFly(DexProto proto) {
+      DexProto result = protoFixupCache.get(proto);
+      if (result == null) {
+        DexType returnType = applyClassMappingOnTheFly(proto.returnType);
+        DexType[] arguments = applyClassMappingOnTheFly(proto.parameters.values);
+        if (arguments != null || returnType != proto.returnType) {
+          arguments = arguments == null ? proto.parameters.values : arguments;
+          result = appInfo.dexItemFactory.createProto(returnType, arguments);
+        } else {
+          result = proto;
+        }
+        protoFixupCache.put(proto, result);
+      }
+      return result;
+    }
+
+    private DexType[] applyClassMappingOnTheFly(DexType[] types) {
+      Map<Integer, DexType> changed = new Int2ObjectArrayMap<>();
+      for (int i = 0; i < types.length; i++) {
+        DexType applied = applyClassMappingOnTheFly(types[i]);
+        if (applied != types[i]) {
+          changed.put(i, applied);
+        }
+      }
+      return changed.isEmpty()
+          ? null
+          : ArrayUtils.copyWithSparseChanges(DexType[].class, types, changed);
+    }
+  }
+
+  static class ChainedClassNaming extends ClassNamingForMapApplier {
+    final ChainedClassNaming superClassNaming;
+
+    ChainedClassNaming(
+        ChainedClassNaming superClassNaming,
+        ClassNamingForMapApplier thisClassNaming) {
+      super(thisClassNaming);
+      this.superClassNaming = superClassNaming;
+    }
+
+    @Override
+    public <T extends Throwable> void forAllMethodNaming(
+        ThrowingConsumer<MemberNaming, T> consumer) throws T {
+      super.forAllMethodNaming(consumer);
+      if (superClassNaming != null) {
+        superClassNaming.forAllMethodNaming(consumer);
+      }
+    }
+
+    @Override
+    protected MemberNaming lookupByOriginalItem(DexMethod method) {
+      MemberNaming memberNaming = super.lookupByOriginalItem(method);
+      if (memberNaming != null) {
+        return memberNaming;
+      }
+      // Moving up if chained.
+      if (superClassNaming != null) {
+        return superClassNaming.lookupByOriginalItem(method);
+      }
+      return null;
+    }
+  }
+
+  class TreeFixer {
+    private final ConflictFreeBuilder lenseBuilder;
+    private final GraphLense appliedLense;
+    private final Map<DexProto, DexProto> protoFixupCache = new IdentityHashMap<>();
+
+    TreeFixer(GraphLense appliedLense) {
+      this.lenseBuilder = new ConflictFreeBuilder();
+      this.appliedLense = appliedLense;
+    }
+
+    private GraphLense run() {
+      // Suppose PrgA extends LibA, and adds its own method, say foo that inputs LibA instance.
+      // If that library class and members are renamed somehow, all the inherited members in PrgA
+      // will be also renamed when applying those mappings. However, that newly added method, foo,
+      // with renamed LibA as an argument, won't be updated. Here at TreeFixer, we want to change
+      // PrgA#foo signature: from LibA to a renamed name.
+      appInfo.classes().forEach(this::fixClass);
+      appInfo.libraryClasses().forEach(this::fixClass);
+      return lenseBuilder.build(appInfo.dexItemFactory, appliedLense);
+    }
+
+    private void fixClass(DexClass clazz) {
+      clazz.type = substituteType(clazz.type, null);
+      clazz.superType = substituteType(clazz.superType, null);
+      clazz.interfaces = substituteTypesIn(clazz.interfaces);
+      clazz.annotations = substituteTypesIn(clazz.annotations);
+      clazz.setDirectMethods(substituteTypesIn(clazz.directMethods()));
+      clazz.setVirtualMethods(substituteTypesIn(clazz.virtualMethods()));
+      clazz.setStaticFields(substituteTypesIn(clazz.staticFields()));
+      clazz.setInstanceFields(substituteTypesIn(clazz.instanceFields()));
+    }
+
+    private DexEncodedMethod[] substituteTypesIn(DexEncodedMethod[] methods) {
+      if (methods == null) {
+        return null;
+      }
+      for (int i = 0; i < methods.length; i++) {
+        DexEncodedMethod encodedMethod = methods[i];
+        DexMethod appliedMethod = appliedLense.lookupMethod(encodedMethod.method, encodedMethod);
+        DexProto newProto = substituteTypesIn(appliedMethod.proto, encodedMethod);
+        DexMethod newMethod;
+        if (newProto != appliedMethod.proto) {
+          newMethod = appInfo.dexItemFactory.createMethod(
+              substituteType(appliedMethod.holder, encodedMethod), newProto, appliedMethod.name);
+          lenseBuilder.map(encodedMethod.method, newMethod);
+        } else {
+          newMethod = appliedMethod;
+        }
+        // Explicitly fix members.
+        methods[i] = encodedMethod.toTypeSubstitutedMethod(newMethod);
+      }
+      return methods;
+    }
+
+    private DexEncodedField[] substituteTypesIn(DexEncodedField[] fields) {
+      if (fields == null) {
+        return null;
+      }
+      for (int i = 0; i < fields.length; i++) {
+        DexEncodedField encodedField = fields[i];
+        DexField appliedField = appliedLense.lookupField(encodedField.field, null);
+        DexType newType = substituteType(appliedField.type, null);
+        DexField newField;
+        if (newType != appliedField.type) {
+          newField = appInfo.dexItemFactory.createField(
+              substituteType(appliedField.clazz, null), newType, appliedField.name);
+          lenseBuilder.map(encodedField.field, newField);
+        } else {
+          newField = appliedField;
+        }
+        // Explicitly fix members.
+        fields[i] = encodedField.toTypeSubstitutedField(newField);
+      }
+      return fields;
+    }
+
+    private DexProto substituteTypesIn(DexProto proto, DexEncodedMethod context) {
+      DexProto result = protoFixupCache.get(proto);
+      if (result == null) {
+        DexType returnType = substituteType(proto.returnType, context);
+        DexType[] arguments = substituteTypesIn(proto.parameters.values, context);
+        if (arguments != null || returnType != proto.returnType) {
+          arguments = arguments == null ? proto.parameters.values : arguments;
+          result = appInfo.dexItemFactory.createProto(returnType, arguments);
+        } else {
+          result = proto;
+        }
+        protoFixupCache.put(proto, result);
+      }
+      return result;
+    }
+
+    private DexAnnotationSet substituteTypesIn(DexAnnotationSet annotations) {
+      if (annotations.isEmpty()) {
+        return annotations;
+      }
+      DexAnnotation[] result = substituteTypesIn(annotations.annotations);
+      return result == null ? annotations : new DexAnnotationSet(result);
+    }
+
+    private DexAnnotation[] substituteTypesIn(DexAnnotation[] annotations) {
+      Map<Integer, DexAnnotation> changed = new Int2ObjectArrayMap<>();
+      for (int i = 0; i < annotations.length; i++) {
+        DexAnnotation applied = substituteTypesIn(annotations[i]);
+        if (applied != annotations[i]) {
+          changed.put(i, applied);
+        }
+      }
+      return changed.isEmpty()
+          ? null
+          : ArrayUtils.copyWithSparseChanges(DexAnnotation[].class, annotations, changed);
+    }
+
+    private DexAnnotation substituteTypesIn(DexAnnotation annotation) {
+      return new DexAnnotation(annotation.visibility, substituteTypesIn(annotation.annotation));
+    }
+
+    private DexEncodedAnnotation substituteTypesIn(DexEncodedAnnotation annotation) {
+      return new DexEncodedAnnotation(substituteType(annotation.type, null), annotation.elements);
+    }
+
+    private DexTypeList substituteTypesIn(DexTypeList types) {
+      if (types.isEmpty()) {
+        return types;
+      }
+      DexType[] result = substituteTypesIn(types.values, null);
+      return result == null ? types : new DexTypeList(result);
+    }
+
+    private DexType[] substituteTypesIn(DexType[] types, DexEncodedMethod context) {
+      Map<Integer, DexType> changed = new Int2ObjectArrayMap<>();
+      for (int i = 0; i < types.length; i++) {
+        DexType applied = substituteType(types[i], context);
+        if (applied != types[i]) {
+          changed.put(i, applied);
+        }
+      }
+      return changed.isEmpty()
+          ? null
+          : ArrayUtils.copyWithSparseChanges(DexType[].class, types, changed);
+    }
+
+    private DexType substituteType(DexType type, DexEncodedMethod context) {
+      if (type == null) {
+        return null;
+      }
+      if (type.isArrayType()) {
+        DexType base = type.toBaseType(appInfo.dexItemFactory);
+        DexType fixed = substituteType(base, context);
+        if (base == fixed) {
+          return type;
+        } else {
+          return type.replaceBaseType(fixed, appInfo.dexItemFactory);
+        }
+      }
+      return appliedLense.lookupType(type, context);
+    }
+  }
+
+  private static class ConflictFreeBuilder extends GraphLense.Builder {
+    ConflictFreeBuilder() {
+      super();
+    }
+
+    @Override
+    public void map(DexType from, DexType to) {
+      if (typeMap.containsKey(from)) {
+        String keptName = typeMap.get(from).getName();
+        if (!keptName.equals(to.getName())) {
+          throw ProguardMapError.keptTypeWasRenamed(from, keptName, to.getName());
+        }
+      }
+      super.map(from, to);
+    }
+
+    @Override
+    public void map(DexMethod from, DexMethod to) {
+      if (methodMap.containsKey(from)) {
+        String keptName = methodMap.get(from).name.toString();
+        if (!keptName.equals(to.name.toString())) {
+          throw ProguardMapError.keptMethodWasRenamed(from, keptName, to.name.toString());
+        }
+      }
+      super.map(from, to);
+    }
+
+    @Override
+    public void map(DexField from, DexField to) {
+      if (fieldMap.containsKey(from)) {
+        String keptName = fieldMap.get(from).name.toString();
+        if (!keptName.equals(to.name.toString())) {
+          throw ProguardMapError.keptFieldWasRenamed(from, keptName, to.name.toString());
+        }
+      }
+      super.map(from, to);
+    }
+
+    // Helper to determine whether to apply the class mapping on-the-fly or applied already.
+    DexType lookup(DexType from) {
+      return typeMap.getOrDefault(from, from);
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/naming/ProguardMapError.java b/src/main/java/com/android/tools/r8/naming/ProguardMapError.java
new file mode 100644
index 0000000..ce659e1
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/naming/ProguardMapError.java
@@ -0,0 +1,38 @@
+// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.naming;
+
+import com.android.tools.r8.errors.CompilationError;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexType;
+
+public class ProguardMapError extends CompilationError {
+  private ProguardMapError(String message) {
+    super(message);
+  }
+
+  private ProguardMapError(String message, Throwable cause) {
+    super(message, cause);
+  }
+
+  static ProguardMapError keptTypeWasRenamed(DexType type, String keptName, String rename) {
+    return new ProguardMapError(
+        "Warning: " + type + createMessageForConflict(keptName, rename));
+  }
+
+  static ProguardMapError keptMethodWasRenamed(DexMethod method, String keptName, String rename) {
+    return new ProguardMapError(
+        "Warning: " + method.toSourceString() + createMessageForConflict(keptName, rename));
+  }
+
+  static ProguardMapError keptFieldWasRenamed(DexField field, String keptName, String rename) {
+    return new ProguardMapError(
+        "Warning: " + field.toSourceString() + createMessageForConflict(keptName, rename));
+  }
+
+  private static String createMessageForConflict(String keptName, String rename) {
+    return " is not being kept as '" + keptName + "', but remapped to '" + rename + "'";
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/naming/ProguardMapReader.java b/src/main/java/com/android/tools/r8/naming/ProguardMapReader.java
index 415b8a7..5f1e150 100644
--- a/src/main/java/com/android/tools/r8/naming/ProguardMapReader.java
+++ b/src/main/java/com/android/tools/r8/naming/ProguardMapReader.java
@@ -9,20 +9,12 @@
 import com.android.tools.r8.naming.MemberNaming.Range;
 import com.android.tools.r8.naming.MemberNaming.Signature;
 import com.android.tools.r8.naming.MemberNaming.SingleLineRange;
-import com.google.common.collect.ImmutableMap;
 import java.io.BufferedReader;
-import java.io.ByteArrayInputStream;
 import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.nio.charset.StandardCharsets;
-import java.nio.file.Files;
-import java.nio.file.Path;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.LinkedList;
 import java.util.List;
-import java.util.Map;
 import java.util.function.Consumer;
 
 /**
@@ -71,26 +63,10 @@
     }
   }
 
-  private ProguardMapReader(BufferedReader reader) {
+  ProguardMapReader(BufferedReader reader) {
     this.reader = reader;
   }
 
-  public static ClassNameMapper mapperFromInputStream(InputStream in) throws IOException {
-    BufferedReader reader = new BufferedReader(new InputStreamReader(in, "UTF8"));
-    try (ProguardMapReader proguardReader = new ProguardMapReader(reader)) {
-      return proguardReader.parse();
-    }
-  }
-
-  public static ClassNameMapper mapperFromFile(Path path) throws IOException {
-    return mapperFromInputStream(Files.newInputStream(path));
-  }
-
-  public static ClassNameMapper mapperFromString(String contents) throws IOException {
-    return mapperFromInputStream(
-        new ByteArrayInputStream(contents.getBytes(StandardCharsets.UTF_8)));
-  }
-
   // Internal parser state
   private int lineNo = 0;
   private int lineOffset = 0;
@@ -146,17 +122,15 @@
     return c;
   }
 
-  public ClassNameMapper parse() throws IOException {
+  void parse(ProguardMap.Builder mapBuilder) throws IOException {
     // Read the first line.
     line = reader.readLine();
-    Map<String, ClassNaming> classNames = parseClassMappings();
-    return new ClassNameMapper(classNames);
+    parseClassMappings(mapBuilder);
   }
 
   // Parsing of entries
 
-  private Map<String, ClassNaming> parseClassMappings() throws IOException {
-    ImmutableMap.Builder<String, ClassNaming> builder = ImmutableMap.builder();
+  private void parseClassMappings(ProguardMap.Builder mapBuilder) throws IOException {
     while (hasLine()) {
       String before = parseType(false);
       skipWhitespace();
@@ -172,16 +146,14 @@
       skipWhitespace();
       String after = parseType(false);
       expect(':');
-      ClassNaming currentClass = new ClassNaming(after, before);
-      builder.put(after, currentClass);
+      ClassNaming.Builder currentClassBuilder = mapBuilder.classNamingBuilder(after, before);
       if (nextLine()) {
-        parseMemberMappings(currentClass);
+        parseMemberMappings(currentClassBuilder);
       }
     }
-    return builder.build();
   }
 
-  private void parseMemberMappings(ClassNaming currentClass) throws IOException {
+  private void parseMemberMappings(ClassNaming.Builder classNamingBuilder) throws IOException {
     MemberNaming current = null;
     Range previousInlineRange = null;
     Signature previousSignature = null;
@@ -226,7 +198,7 @@
         if (current == null || !previousSignature.equals(current.signature)) {
           if (collectedInfos.size() == 1) {
             current = new MemberNaming(previousSignature, previousRenamedName, previousInlineRange);
-            currentClass.addMemberEntry(current);
+            classNamingBuilder.addMemberEntry(current);
           } else {
             if (Log.ENABLED && !collectedInfos.isEmpty()) {
               Log.warn(getClass(),
@@ -254,7 +226,7 @@
     if (current == null || !previousSignature.equals(current.signature)) {
       if (collectedInfos.size() == 1) {
         current = new MemberNaming(previousSignature, previousRenamedName, previousInlineRange);
-        currentClass.addMemberEntry(current);
+        classNamingBuilder.addMemberEntry(current);
       }
     } else {
       MemberNaming finalCurrent = current;
diff --git a/src/main/java/com/android/tools/r8/naming/SeedMapper.java b/src/main/java/com/android/tools/r8/naming/SeedMapper.java
new file mode 100644
index 0000000..c4931f8
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/naming/SeedMapper.java
@@ -0,0 +1,90 @@
+// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.naming;
+
+import static com.android.tools.r8.utils.DescriptorUtils.javaTypeToDescriptor;
+
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.naming.MemberNaming.Signature;
+import com.google.common.collect.ImmutableMap;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Map;
+
+/**
+ * Mappings read from the given ProGuard map.
+ * <p>
+ * The main differences of this against {@link ClassNameMapper} and
+ * {@link ClassNameMapper#getObfuscatedToOriginalMapping()} are:
+ *   1) the key is the original descriptor, not the obfuscated java name. Thus, it is much easier
+ *   to look up what mapping to apply while traversing {@link DexType}s; and
+ *   2) the value is {@link ClassNamingForMapApplier}, another variant of {@link ClassNaming},
+ *   which also uses original {@link Signature} as a key, instead of renamed {@link Signature}.
+ */
+public class SeedMapper implements ProguardMap {
+
+  static class Builder extends ProguardMap.Builder {
+    final ImmutableMap.Builder<String, ClassNamingForMapApplier.Builder> mapBuilder;
+
+    private Builder() {
+      mapBuilder = ImmutableMap.builder();
+    }
+
+    @Override
+    ClassNamingForMapApplier.Builder classNamingBuilder(String renamedName, String originalName) {
+      String originalDescriptor = javaTypeToDescriptor(originalName);
+      ClassNamingForMapApplier.Builder classNamingBuilder =
+          ClassNamingForMapApplier.builder(javaTypeToDescriptor(renamedName), originalDescriptor);
+      mapBuilder.put(originalDescriptor, classNamingBuilder);
+      return classNamingBuilder;
+    }
+
+    @Override
+    SeedMapper build() {
+      return new SeedMapper(mapBuilder.build());
+    }
+  }
+
+  static Builder builder() {
+    return new Builder();
+  }
+
+  private static SeedMapper seedMapperFromInputStream(InputStream in) throws IOException {
+    BufferedReader reader = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8));
+    try (ProguardMapReader proguardReader = new ProguardMapReader(reader)) {
+      SeedMapper.Builder builder = SeedMapper.builder();
+      proguardReader.parse(builder);
+      return builder.build();
+    }
+  }
+
+  public static SeedMapper seedMapperFromFile(Path path) throws IOException {
+    return seedMapperFromInputStream(Files.newInputStream(path));
+  }
+
+  private final ImmutableMap<String, ClassNamingForMapApplier> mappings;
+
+  private SeedMapper(Map<String, ClassNamingForMapApplier.Builder> mappings) {
+    ImmutableMap.Builder<String, ClassNamingForMapApplier> builder = ImmutableMap.builder();
+    for(Map.Entry<String, ClassNamingForMapApplier.Builder> entry : mappings.entrySet()) {
+      builder.put(entry.getKey(), entry.getValue().build());
+    }
+    this.mappings = builder.build();
+  }
+
+  @Override
+  public boolean hasMapping(DexType type) {
+    return mappings.containsKey(type.descriptor.toString());
+  }
+
+  @Override
+  public ClassNamingForMapApplier getClassNaming(DexType type) {
+    return mappings.get(type.descriptor.toString());
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/optimize/BridgeMethodAnalysis.java b/src/main/java/com/android/tools/r8/optimize/BridgeMethodAnalysis.java
index 72ecedf..3fabf47 100644
--- a/src/main/java/com/android/tools/r8/optimize/BridgeMethodAnalysis.java
+++ b/src/main/java/com/android/tools/r8/optimize/BridgeMethodAnalysis.java
@@ -112,5 +112,18 @@
     public boolean isContextFree() {
       return false;
     }
+
+    @Override
+    public String toString() {
+      StringBuilder builder = new StringBuilder();
+      builder.append("------ BridgeMap ------").append(System.lineSeparator());
+      for (Map.Entry<DexMethod, DexMethod> entry : bridgeTargetToBridgeMap.entrySet()) {
+        builder.append(entry.getKey().toSourceString()).append(" -> ");
+        builder.append(entry.getValue().toSourceString()).append(System.lineSeparator());
+      }
+      builder.append("-----------------------").append(System.lineSeparator());
+      builder.append(previousLense.toString());
+      return builder.toString();
+    }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java b/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
index 17da59b..71d6e46 100644
--- a/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
+++ b/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
@@ -219,6 +219,6 @@
         appInfo::lookupStaticTarget, DexClass::findStaticTarget);
     computeFieldRebinding(Sets.union(appInfo.instanceFieldReads, appInfo.instanceFieldWrites),
         appInfo::lookupInstanceTarget, DexClass::findInstanceTarget);
-    return builder.build(lense, appInfo.dexItemFactory);
+    return builder.build(appInfo.dexItemFactory, lense);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
index a24bdb9..3f64afd 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
@@ -256,8 +256,6 @@
         }
       } else if (acceptString("applymapping")) {
         configurationBuilder.setApplyMappingFile(parseFileName());
-        // TODO(b/64802420): warn until it is fully implemented.
-        warnIgnoringOptions("applymapping");
       } else if (acceptString("assumenosideeffects")) {
         ProguardAssumeNoSideEffectRule rule = parseAssumeNoSideEffectsRule();
         configurationBuilder.addRule(rule);
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationSourceStrings.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationSourceStrings.java
index 44b3393..cb69320 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationSourceStrings.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationSourceStrings.java
@@ -12,6 +12,7 @@
 import joptsimple.internal.Strings;
 
 public class ProguardConfigurationSourceStrings implements ProguardConfigurationSource {
+
   private final Path basePath;
   private final List<String> config;
 
@@ -20,13 +21,13 @@
    * {@param basePath}, which allows all other options that use a relative path to reach out
    * to desired paths appropriately.
    */
-  public ProguardConfigurationSourceStrings(Path basePath, List<String> config) {
+  public ProguardConfigurationSourceStrings(List<String> config, Path basePath) {
     this.basePath = basePath;
     this.config = config;
   }
 
   private ProguardConfigurationSourceStrings(List<String> config) {
-    this(Paths.get("."), config);
+    this(config, Paths.get("."));
   }
 
   @VisibleForTesting
@@ -36,7 +37,7 @@
   }
 
   @Override
-  public String get() throws IOException{
+  public String get() throws IOException {
     return Strings.join(config, System.lineSeparator());
   }
 
diff --git a/src/main/java/com/android/tools/r8/shaking/SimpleClassMerger.java b/src/main/java/com/android/tools/r8/shaking/SimpleClassMerger.java
index 9affabf..b50d78e 100644
--- a/src/main/java/com/android/tools/r8/shaking/SimpleClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/SimpleClassMerger.java
@@ -181,7 +181,7 @@
     if (Log.ENABLED) {
       Log.debug(getClass(), "Merged %d classes.", numberOfMerges);
     }
-    return renamedMembersLense.build(graphLense, application.dexItemFactory);
+    return renamedMembersLense.build(application.dexItemFactory, graphLense);
   }
 
   private class ClassMerger {
@@ -438,7 +438,7 @@
         DexType fixed = fixupType(type);
         lense.map(type, fixed);
       }
-      return lense.build(graphLense, application.dexItemFactory);
+      return lense.build(application.dexItemFactory, graphLense);
     }
 
     private DexEncodedMethod[] removeDupes(DexEncodedMethod[] methods) {
diff --git a/src/main/java/com/android/tools/r8/utils/AndroidApp.java b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
index 24f5c08..67aa288 100644
--- a/src/main/java/com/android/tools/r8/utils/AndroidApp.java
+++ b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
@@ -596,11 +596,11 @@
     /**
      * Add Java-bytecode program data.
      */
-    public Builder addClassProgramData(Origin origin, byte[] data) {
+    public Builder addClassProgramData(byte[] data, Origin origin) {
       return addProgramResources(Kind.CLASS, Resource.fromBytes(origin, data));
     }
 
-    public Builder addClassProgramData(Origin origin, byte[] data, Set<String> classDescriptors) {
+    public Builder addClassProgramData(byte[] data, Origin origin, Set<String> classDescriptors) {
       return addProgramResources(Kind.CLASS, Resource.fromBytes(origin, data, classDescriptors));
     }
 
@@ -613,15 +613,6 @@
     }
 
     /**
-     * Inform whether ProGuard map has already been set or not.
-     *
-     * ProGuard option -applymapping will override R8/Dissemble option -pg-map.
-     */
-    public boolean hasProguardMap() {
-      return proguardMap != null;
-    }
-
-    /**
      * Set proguard-map file.
      */
     public Builder setProguardMapFile(Path file) {
diff --git a/src/main/java/com/android/tools/r8/utils/AndroidAppOutputSink.java b/src/main/java/com/android/tools/r8/utils/AndroidAppOutputSink.java
index a4f2236..0f85cac 100644
--- a/src/main/java/com/android/tools/r8/utils/AndroidAppOutputSink.java
+++ b/src/main/java/com/android/tools/r8/utils/AndroidAppOutputSink.java
@@ -92,7 +92,7 @@
     } else if (!classFiles.isEmpty()) {
       assert dexFilesWithPrimary.isEmpty() && dexFilesWithId.isEmpty();
       classFiles.forEach(
-          d -> builder.addClassProgramData(Origin.unknown(), d.contents, d.descriptors));
+          d -> builder.addClassProgramData(d.contents, Origin.unknown(), d.descriptors));
     }
     closed = true;
     super.close();
diff --git a/src/main/java/com/android/tools/r8/utils/ArrayUtils.java b/src/main/java/com/android/tools/r8/utils/ArrayUtils.java
new file mode 100644
index 0000000..976768b
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/ArrayUtils.java
@@ -0,0 +1,36 @@
+// Copyright (c) 2017, 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.utils;
+
+import java.lang.reflect.Array;
+import java.util.Map;
+
+public class ArrayUtils {
+
+  /**
+   * Copies the input array and then applies specified sparse changes.
+   *
+   * @param clazz target type's Class to cast
+   * @param original an array of original elements
+   * @param changedElements sparse changes to apply
+   * @param <T> target type
+   * @return a copy of original arrays while sparse changes are applied
+   */
+  public static <T> T[] copyWithSparseChanges(
+      Class<T[]> clazz, T[] original, Map<Integer, T> changedElements) {
+    T[] results = clazz.cast(Array.newInstance(clazz.getComponentType(), original.length));
+    int pos = 0;
+    for (Map.Entry<Integer, T> entry : changedElements.entrySet()) {
+      int i = entry.getKey();
+      System.arraycopy(original, pos, results, pos, i - pos);
+      results[i] = entry.getValue();
+      pos = i + 1;
+    }
+    if (pos < original.length) {
+      System.arraycopy(original, pos, results, pos, original.length - pos);
+    }
+    return results;
+  }
+
+}
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 a04e28f..1c0cc61 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -125,6 +125,7 @@
   public boolean minimalMainDex;
 
   public static class InvalidParameterAnnotationInfo {
+
     final DexMethod method;
     final int expectedParameterCount;
     final int actualParameterCount;
@@ -190,7 +191,7 @@
               .append(" actual count: ")
               .append(info.actualParameterCount);
         }
-        diagnosticsHandler.info(new StringDiagnostic(origin, builder.toString()));
+        diagnosticsHandler.info(new StringDiagnostic(builder.toString(), origin));
       }
       printed = true;
     }
@@ -209,7 +210,7 @@
         for (DexEncodedMethod method : warningInvalidDebugInfo.get(origin)) {
           builder.append("\n  ").append(method.toSourceString());
         }
-        diagnosticsHandler.info(new StringDiagnostic(origin, builder.toString()));
+        diagnosticsHandler.info(new StringDiagnostic(builder.toString(), origin));
       }
       printed = true;
       printOutdatedToolchain = true;
diff --git a/src/main/java/com/android/tools/r8/utils/StringDiagnostic.java b/src/main/java/com/android/tools/r8/utils/StringDiagnostic.java
index b2add4a..d2c6560 100644
--- a/src/main/java/com/android/tools/r8/utils/StringDiagnostic.java
+++ b/src/main/java/com/android/tools/r8/utils/StringDiagnostic.java
@@ -7,14 +7,15 @@
 import com.android.tools.r8.Resource.Origin;
 
 public class StringDiagnostic implements Diagnostic {
+
   private final Origin origin;
   private final String message;
 
   public StringDiagnostic(String message) {
-    this(Origin.unknown(), message);
+    this(message, Origin.unknown());
   }
 
-  public StringDiagnostic(Origin origin, String message) {
+  public StringDiagnostic(String message, Origin origin) {
     this.origin = origin;
     this.message = message;
   }
diff --git a/src/main/java/com/android/tools/r8/utils/ZipUtils.java b/src/main/java/com/android/tools/r8/utils/ZipUtils.java
index ca9a404..76d84c2 100644
--- a/src/main/java/com/android/tools/r8/utils/ZipUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/ZipUtils.java
@@ -9,6 +9,7 @@
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
+import java.io.OutputStream;
 import java.nio.file.Path;
 import java.util.ArrayList;
 import java.util.Enumeration;
@@ -53,8 +54,9 @@
           Path outPath = outDirectoryPath.resolve(name);
           File outFile = outPath.toFile();
           outFile.getParentFile().mkdirs();
-          FileOutputStream output = new FileOutputStream(outFile);
-          ByteStreams.copy(input, output);
+          try (OutputStream output = new FileOutputStream(outFile)) {
+            ByteStreams.copy(input, output);
+          }
           outFiles.add(outFile);
         }
       });
diff --git a/src/test/examples/applymapping044/AsubB.java b/src/test/examples/applymapping044/AsubB.java
new file mode 100644
index 0000000..2c5fd0c
--- /dev/null
+++ b/src/test/examples/applymapping044/AsubB.java
@@ -0,0 +1,13 @@
+// Copyright (c) 2017, 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 applymapping044;
+
+import naming044.A;
+import naming044.sub.SubB;
+
+public class AsubB extends SubB {
+  public int boo(A a) {
+    return f(a) * 3;
+  }
+}
diff --git a/src/test/examples/applymapping044/Main.java b/src/test/examples/applymapping044/Main.java
new file mode 100644
index 0000000..9d5f577
--- /dev/null
+++ b/src/test/examples/applymapping044/Main.java
@@ -0,0 +1,20 @@
+// Copyright (c) 2017, 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 applymapping044;
+
+import naming044.A;
+import naming044.B;
+import naming044.sub.SubB;
+
+public class Main {
+  public static void main(String[] args) {
+    B.m();
+    SubB.n();
+    A a = new A();
+    B b = new B();
+    b.f(a);
+    AsubB subB = new AsubB();
+    subB.f(a);
+  }
+}
diff --git a/src/test/examples/applymapping044/keep-rules-apply-mapping.txt b/src/test/examples/applymapping044/keep-rules-apply-mapping.txt
new file mode 100644
index 0000000..56f43b2
--- /dev/null
+++ b/src/test/examples/applymapping044/keep-rules-apply-mapping.txt
@@ -0,0 +1,13 @@
+# Copyright (c) 2017, 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.
+
+-keep public class applymapping044.Main {
+  public static void main(...);
+}
+
+-keep,allowobfuscation class * {
+  *;
+}
+
+-applymapping test-mapping.txt
diff --git a/src/test/examples/applymapping044/keep-rules.txt b/src/test/examples/applymapping044/keep-rules.txt
new file mode 100644
index 0000000..efed0ec
--- /dev/null
+++ b/src/test/examples/applymapping044/keep-rules.txt
@@ -0,0 +1,11 @@
+# Copyright (c) 2017, 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.
+
+-keep public class applymapping044.Main {
+  public static void main(...);
+}
+
+-keep,allowobfuscation class * {
+  *;
+}
diff --git a/src/test/examples/applymapping044/test-mapping.txt b/src/test/examples/applymapping044/test-mapping.txt
new file mode 100644
index 0000000..41c2c5c
--- /dev/null
+++ b/src/test/examples/applymapping044/test-mapping.txt
@@ -0,0 +1,9 @@
+naming044.A -> naming044.x:
+    int f -> o
+naming044.B -> naming044.y:
+    int m() -> n
+    int f(naming044.A) -> p
+naming044.sub.SubA -> naming044.z.x:
+    int f -> q
+naming044.sub.SubB -> naming044.z.y:
+    int n() -> m
diff --git a/src/test/examples/minification/conflict-mapping.txt b/src/test/examples/minification/conflict-mapping.txt
new file mode 100644
index 0000000..a4453da
--- /dev/null
+++ b/src/test/examples/minification/conflict-mapping.txt
@@ -0,0 +1,4 @@
+minification.InterfaceA -> ItfA:
+    int functionFromIntToInt(int) -> foo
+minification.InterfaceB -> ItfB:
+    int functionFromIntToInt(int) -> bar
diff --git a/src/test/examples/minification/keep-rules-apply-conflict-mapping.txt b/src/test/examples/minification/keep-rules-apply-conflict-mapping.txt
new file mode 100644
index 0000000..41f2261
--- /dev/null
+++ b/src/test/examples/minification/keep-rules-apply-conflict-mapping.txt
@@ -0,0 +1,14 @@
+# Copyright (c) 2017, 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.
+
+-applymapping conflict-mapping.txt
+
+# Keep the application entry point. Get rid of everything that is not
+# reachable from there.
+-keep public class minification.Minification {
+  public static void main(...);
+}
+
+# allow access modification to enable minification
+-allowaccessmodification
diff --git a/src/test/examples/naming001/keep-rules-105.txt b/src/test/examples/naming001/keep-rules-105.txt
new file mode 100644
index 0000000..f3bf7f6
--- /dev/null
+++ b/src/test/examples/naming001/keep-rules-105.txt
@@ -0,0 +1,11 @@
+# Copyright (c) 2017, 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.
+
+-allowaccessmodification
+
+-keep class naming001.D {
+  public static void main(...);
+}
+
+-applymapping mapping-105.txt
diff --git a/src/test/examples/naming001/mapping-105.txt b/src/test/examples/naming001/mapping-105.txt
new file mode 100644
index 0000000..3437a93
--- /dev/null
+++ b/src/test/examples/naming001/mapping-105.txt
@@ -0,0 +1,2 @@
+naming001.D -> naming001.D:
+    void keep() -> peek
diff --git a/src/test/examples/naming044/B.java b/src/test/examples/naming044/B.java
index 7580945..f723423 100644
--- a/src/test/examples/naming044/B.java
+++ b/src/test/examples/naming044/B.java
@@ -7,4 +7,7 @@
   public static int m() {
     return A.f;
   }
+  public int f(A a) {
+    return a.f;
+  }
 }
diff --git a/src/test/examples/naming044/sub/SubB.java b/src/test/examples/naming044/sub/SubB.java
index badc5f2..823de8c 100644
--- a/src/test/examples/naming044/sub/SubB.java
+++ b/src/test/examples/naming044/sub/SubB.java
@@ -3,7 +3,9 @@
 // BSD-style license that can be found in the LICENSE file.
 package naming044.sub;
 
-public class SubB {
+import naming044.B;
+
+public class SubB extends B {
   public static int n() {
     return SubA.f;
   }
diff --git a/src/test/examplesAndroidO/invokecustom/TestGenerator.java b/src/test/examplesAndroidO/invokecustom/TestGenerator.java
index 574fe14..e42b9c9 100644
--- a/src/test/examplesAndroidO/invokecustom/TestGenerator.java
+++ b/src/test/examplesAndroidO/invokecustom/TestGenerator.java
@@ -3,13 +3,14 @@
 // BSD-style license that can be found in the LICENSE file.
 package invokecustom;
 
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
 import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
 import java.lang.invoke.CallSite;
 import java.lang.invoke.MethodHandle;
 import java.lang.invoke.MethodHandles;
 import java.lang.invoke.MethodType;
+import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import org.objectweb.asm.ClassReader;
@@ -36,30 +37,34 @@
   }
 
   private void generateTests() throws IOException {
-    ClassReader cr = new ClassReader(new FileInputStream(classNamePath.toFile()));
-    ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);
-    cr.accept(
-        new ClassVisitor(Opcodes.ASM6, cw) {
-          @Override
-          public void visitEnd() {
-            generateMethodTest1(cw);
-            generateMethodTest2(cw);
-            generateMethodTest3(cw);
-            generateMethodTest4(cw);
-            generateMethodTest5(cw);
-            generateMethodTest6(cw);
-            generateMethodTest7(cw);
-            generateMethodTest8(cw);
-            generateMethodTest9(cw);
-            generateMethodTest10(cw);
-            generateMethodTest11(cw);
-            generateMethodTest12(cw);
-            generateMethodTest13(cw);
-            generateMethodMain(cw);
-            super.visitEnd();
-          }
-        }, 0);
-    new FileOutputStream(classNamePath.toFile()).write(cw.toByteArray());
+    try (InputStream input = Files.newInputStream(classNamePath)) {
+      ClassReader cr = new ClassReader(input);
+      ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);
+      cr.accept(
+          new ClassVisitor(Opcodes.ASM6, cw) {
+            @Override
+            public void visitEnd() {
+              generateMethodTest1(cw);
+              generateMethodTest2(cw);
+              generateMethodTest3(cw);
+              generateMethodTest4(cw);
+              generateMethodTest5(cw);
+              generateMethodTest6(cw);
+              generateMethodTest7(cw);
+              generateMethodTest8(cw);
+              generateMethodTest9(cw);
+              generateMethodTest10(cw);
+              generateMethodTest11(cw);
+              generateMethodTest12(cw);
+              generateMethodTest13(cw);
+              generateMethodMain(cw);
+              super.visitEnd();
+            }
+          }, 0);
+      try (OutputStream output = Files.newOutputStream(classNamePath)) {
+        output.write(cw.toByteArray());
+      }
+    }
   }
 
   /* generate main method that only call all test methods. */
diff --git a/src/test/examplesAndroidO/invokecustom2/TestGenerator.java b/src/test/examplesAndroidO/invokecustom2/TestGenerator.java
index ab77d32..9bbc03a 100644
--- a/src/test/examplesAndroidO/invokecustom2/TestGenerator.java
+++ b/src/test/examplesAndroidO/invokecustom2/TestGenerator.java
@@ -4,13 +4,14 @@
 
 package invokecustom2;
 
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
 import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
 import java.lang.invoke.CallSite;
 import java.lang.invoke.MethodHandle;
 import java.lang.invoke.MethodHandles;
 import java.lang.invoke.MethodType;
+import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import org.objectweb.asm.ClassReader;
@@ -37,26 +38,30 @@
   }
 
   private void generateTests() throws IOException {
-    ClassReader cr = new ClassReader(new FileInputStream(classNamePath.toFile()));
-    ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);
-    cr.accept(
-        new ClassVisitor(Opcodes.ASM6, cw) {
-          @Override
-          public void visitEnd() {
-            generateMethodTest1(cw);
-            generateMethodTest2(cw);
-            generateMethodTest3(cw);
-            generateMethodTest4(cw);
-            generateMethodTest5(cw);
-            generateMethodTest6(cw);
-            generateMethodTest7(cw);
-            generateMethodTest8(cw);
-            generateMethodTest9(cw);
-            generateMethodMain(cw);
-            super.visitEnd();
-          }
-        }, 0);
-    new FileOutputStream(classNamePath.toFile()).write(cw.toByteArray());
+    try (InputStream input = Files.newInputStream(classNamePath)) {
+      ClassReader cr = new ClassReader(input);
+      ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);
+      cr.accept(
+          new ClassVisitor(Opcodes.ASM6, cw) {
+            @Override
+            public void visitEnd() {
+              generateMethodTest1(cw);
+              generateMethodTest2(cw);
+              generateMethodTest3(cw);
+              generateMethodTest4(cw);
+              generateMethodTest5(cw);
+              generateMethodTest6(cw);
+              generateMethodTest7(cw);
+              generateMethodTest8(cw);
+              generateMethodTest9(cw);
+              generateMethodMain(cw);
+              super.visitEnd();
+            }
+          }, 0);
+      try (OutputStream output = Files.newOutputStream(classNamePath)) {
+        output.write(cw.toByteArray());
+      }
+    }
   }
 
   /* generate main method that only call all test methods. */
diff --git a/src/test/examplesAndroidO/stringconcat/TestGenerator.java b/src/test/examplesAndroidO/stringconcat/TestGenerator.java
index 6a837a4..e72f438 100644
--- a/src/test/examplesAndroidO/stringconcat/TestGenerator.java
+++ b/src/test/examplesAndroidO/stringconcat/TestGenerator.java
@@ -3,12 +3,13 @@
 // BSD-style license that can be found in the LICENSE file.
 package stringconcat;
 
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
 import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
 import java.lang.invoke.CallSite;
 import java.lang.invoke.MethodHandles;
 import java.lang.invoke.MethodType;
+import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.ArrayList;
@@ -46,177 +47,181 @@
   }
 
   private static void generateTests(Path classNamePath) throws IOException {
-    ClassReader cr = new ClassReader(new FileInputStream(classNamePath.toFile()));
-    ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);
-    cr.accept(
-        new ClassVisitor(Opcodes.ASM6, cw) {
-          @Override
-          public MethodVisitor visitMethod(int access,
-              final String methodName, String desc, String signature, String[] exceptions) {
-            MethodVisitor mv = super.visitMethod(access, methodName, desc, signature, exceptions);
-            return new MethodVisitor(Opcodes.ASM6, mv) {
-              private List<Object> recentConstants = new ArrayList<>();
+    try (InputStream input = Files.newInputStream(classNamePath)) {
+      ClassReader cr = new ClassReader(input);
+      ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);
+      cr.accept(
+          new ClassVisitor(Opcodes.ASM6, cw) {
+            @Override
+            public MethodVisitor visitMethod(int access,
+                final String methodName, String desc, String signature, String[] exceptions) {
+              MethodVisitor mv = super.visitMethod(access, methodName, desc, signature, exceptions);
+              return new MethodVisitor(Opcodes.ASM6, mv) {
+                private List<Object> recentConstants = new ArrayList<>();
 
-              @Override
-              public void visitLdcInsn(Object cst) {
-                if (!recentConstants.isEmpty() ||
-                    (cst instanceof String && ((String) cst).startsWith(RECIPE_PREFIX))) {
-                  // Add the constant, don't push anything on stack.
-                  recentConstants.add(cst);
-                  return;
-                }
-                super.visitLdcInsn(cst);
-              }
-
-              @Override
-              public void visitMethodInsn(
-                  int opcode, String owner, String name, String desc, boolean itf) {
-                // Replace calls to 'makeConcat(...)' with appropriate `invokedynamic`.
-                if (opcode == Opcodes.INVOKESTATIC && name.equals("makeConcat")) {
-                  mv.visitInvokeDynamicInsn(MAKE_CONCAT.getName(), desc, MAKE_CONCAT);
-                  recentConstants.clear();
-                  return;
-                }
-
-                // Replace calls to 'makeConcat(...)' with appropriate `invokedynamic`.
-                if (opcode == Opcodes.INVOKESTATIC && name.equals("makeConcatWithConstants")) {
-                  if (recentConstants.isEmpty()) {
-                    throw new AssertionError("No constants detected in `" +
-                        methodName + "`: call to " + name + desc);
+                @Override
+                public void visitLdcInsn(Object cst) {
+                  if (!recentConstants.isEmpty() ||
+                      (cst instanceof String && ((String) cst).startsWith(RECIPE_PREFIX))) {
+                    // Add the constant, don't push anything on stack.
+                    recentConstants.add(cst);
+                    return;
                   }
-                  recentConstants.set(0,
-                      ((String) recentConstants.get(0)).substring(RECIPE_PREFIX.length()));
-
-                  mv.visitInvokeDynamicInsn(MAKE_CONCAT_WITH_CONSTANTS.getName(),
-                      removeLastParams(desc, recentConstants.size()), MAKE_CONCAT_WITH_CONSTANTS,
-                      recentConstants.toArray(new Object[recentConstants.size()]));
-                  recentConstants.clear();
-                  return;
+                  super.visitLdcInsn(cst);
                 }
 
-                // Otherwise fall back to default implementation.
-                super.visitMethodInsn(opcode, owner, name, desc, itf);
-              }
-
-              private String removeLastParams(String descr, int paramsToRemove) {
-                MethodType methodType =
-                    MethodType.fromMethodDescriptorString(
-                        descr, this.getClass().getClassLoader());
-                return methodType
-                    .dropParameterTypes(
-                        methodType.parameterCount() - paramsToRemove,
-                        methodType.parameterCount())
-                    .toMethodDescriptorString();
-              }
-
-              @Override
-              public void visitInsn(int opcode) {
-                switch (opcode) {
-                  case Opcodes.ICONST_0:
-                    if (!recentConstants.isEmpty()) {
-                      recentConstants.add(0);
-                      return;
-                    }
-                    break;
-                  case Opcodes.ICONST_1:
-                    if (!recentConstants.isEmpty()) {
-                      recentConstants.add(1);
-                      return;
-                    }
-                    break;
-                  case Opcodes.ICONST_2:
-                    if (!recentConstants.isEmpty()) {
-                      recentConstants.add(2);
-                      return;
-                    }
-                    break;
-                  case Opcodes.ICONST_3:
-                    if (!recentConstants.isEmpty()) {
-                      recentConstants.add(3);
-                      return;
-                    }
-                    break;
-                  case Opcodes.ICONST_4:
-                    if (!recentConstants.isEmpty()) {
-                      recentConstants.add(4);
-                      return;
-                    }
-                    break;
-                  case Opcodes.ICONST_5:
-                    if (!recentConstants.isEmpty()) {
-                      recentConstants.add(5);
-                      return;
-                    }
-                    break;
-                  case Opcodes.ICONST_M1:
-                    if (!recentConstants.isEmpty()) {
-                      recentConstants.add(-1);
-                      return;
-                    }
-                    break;
-                  default:
+                @Override
+                public void visitMethodInsn(
+                    int opcode, String owner, String name, String desc, boolean itf) {
+                  // Replace calls to 'makeConcat(...)' with appropriate `invokedynamic`.
+                  if (opcode == Opcodes.INVOKESTATIC && name.equals("makeConcat")) {
+                    mv.visitInvokeDynamicInsn(MAKE_CONCAT.getName(), desc, MAKE_CONCAT);
                     recentConstants.clear();
-                    break;
+                    return;
+                  }
+
+                  // Replace calls to 'makeConcat(...)' with appropriate `invokedynamic`.
+                  if (opcode == Opcodes.INVOKESTATIC && name.equals("makeConcatWithConstants")) {
+                    if (recentConstants.isEmpty()) {
+                      throw new AssertionError("No constants detected in `" +
+                          methodName + "`: call to " + name + desc);
+                    }
+                    recentConstants.set(0,
+                        ((String) recentConstants.get(0)).substring(RECIPE_PREFIX.length()));
+
+                    mv.visitInvokeDynamicInsn(MAKE_CONCAT_WITH_CONSTANTS.getName(),
+                        removeLastParams(desc, recentConstants.size()), MAKE_CONCAT_WITH_CONSTANTS,
+                        recentConstants.toArray(new Object[recentConstants.size()]));
+                    recentConstants.clear();
+                    return;
+                  }
+
+                  // Otherwise fall back to default implementation.
+                  super.visitMethodInsn(opcode, owner, name, desc, itf);
                 }
-                super.visitInsn(opcode);
-              }
 
-              @Override
-              public void visitIntInsn(int opcode, int operand) {
-                recentConstants.clear();
-                super.visitIntInsn(opcode, operand);
-              }
+                private String removeLastParams(String descr, int paramsToRemove) {
+                  MethodType methodType =
+                      MethodType.fromMethodDescriptorString(
+                          descr, this.getClass().getClassLoader());
+                  return methodType
+                      .dropParameterTypes(
+                          methodType.parameterCount() - paramsToRemove,
+                          methodType.parameterCount())
+                      .toMethodDescriptorString();
+                }
 
-              @Override
-              public void visitVarInsn(int opcode, int var) {
-                recentConstants.clear();
-                super.visitVarInsn(opcode, var);
-              }
+                @Override
+                public void visitInsn(int opcode) {
+                  switch (opcode) {
+                    case Opcodes.ICONST_0:
+                      if (!recentConstants.isEmpty()) {
+                        recentConstants.add(0);
+                        return;
+                      }
+                      break;
+                    case Opcodes.ICONST_1:
+                      if (!recentConstants.isEmpty()) {
+                        recentConstants.add(1);
+                        return;
+                      }
+                      break;
+                    case Opcodes.ICONST_2:
+                      if (!recentConstants.isEmpty()) {
+                        recentConstants.add(2);
+                        return;
+                      }
+                      break;
+                    case Opcodes.ICONST_3:
+                      if (!recentConstants.isEmpty()) {
+                        recentConstants.add(3);
+                        return;
+                      }
+                      break;
+                    case Opcodes.ICONST_4:
+                      if (!recentConstants.isEmpty()) {
+                        recentConstants.add(4);
+                        return;
+                      }
+                      break;
+                    case Opcodes.ICONST_5:
+                      if (!recentConstants.isEmpty()) {
+                        recentConstants.add(5);
+                        return;
+                      }
+                      break;
+                    case Opcodes.ICONST_M1:
+                      if (!recentConstants.isEmpty()) {
+                        recentConstants.add(-1);
+                        return;
+                      }
+                      break;
+                    default:
+                      recentConstants.clear();
+                      break;
+                  }
+                  super.visitInsn(opcode);
+                }
 
-              @Override
-              public void visitTypeInsn(int opcode, String type) {
-                recentConstants.clear();
-                super.visitTypeInsn(opcode, type);
-              }
+                @Override
+                public void visitIntInsn(int opcode, int operand) {
+                  recentConstants.clear();
+                  super.visitIntInsn(opcode, operand);
+                }
 
-              @Override
-              public void visitFieldInsn(int opcode, String owner, String name, String desc) {
-                recentConstants.clear();
-                super.visitFieldInsn(opcode, owner, name, desc);
-              }
+                @Override
+                public void visitVarInsn(int opcode, int var) {
+                  recentConstants.clear();
+                  super.visitVarInsn(opcode, var);
+                }
 
-              @Override
-              public void visitJumpInsn(int opcode, Label label) {
-                recentConstants.clear();
-                super.visitJumpInsn(opcode, label);
-              }
+                @Override
+                public void visitTypeInsn(int opcode, String type) {
+                  recentConstants.clear();
+                  super.visitTypeInsn(opcode, type);
+                }
 
-              @Override
-              public void visitIincInsn(int var, int increment) {
-                recentConstants.clear();
-                super.visitIincInsn(var, increment);
-              }
+                @Override
+                public void visitFieldInsn(int opcode, String owner, String name, String desc) {
+                  recentConstants.clear();
+                  super.visitFieldInsn(opcode, owner, name, desc);
+                }
 
-              @Override
-              public void visitTableSwitchInsn(int min, int max, Label dflt, Label... labels) {
-                recentConstants.clear();
-                super.visitTableSwitchInsn(min, max, dflt, labels);
-              }
+                @Override
+                public void visitJumpInsn(int opcode, Label label) {
+                  recentConstants.clear();
+                  super.visitJumpInsn(opcode, label);
+                }
 
-              @Override
-              public void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels) {
-                recentConstants.clear();
-                super.visitLookupSwitchInsn(dflt, keys, labels);
-              }
+                @Override
+                public void visitIincInsn(int var, int increment) {
+                  recentConstants.clear();
+                  super.visitIincInsn(var, increment);
+                }
 
-              @Override
-              public void visitMultiANewArrayInsn(String desc, int dims) {
-                recentConstants.clear();
-                super.visitMultiANewArrayInsn(desc, dims);
-              }
-            };
-          }
-        }, 0);
-    new FileOutputStream(classNamePath.toFile()).write(cw.toByteArray());
+                @Override
+                public void visitTableSwitchInsn(int min, int max, Label dflt, Label... labels) {
+                  recentConstants.clear();
+                  super.visitTableSwitchInsn(min, max, dflt, labels);
+                }
+
+                @Override
+                public void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels) {
+                  recentConstants.clear();
+                  super.visitLookupSwitchInsn(dflt, keys, labels);
+                }
+
+                @Override
+                public void visitMultiANewArrayInsn(String desc, int dims) {
+                  recentConstants.clear();
+                  super.visitMultiANewArrayInsn(desc, dims);
+                }
+              };
+            }
+          }, 0);
+      try (OutputStream output = Files.newOutputStream(classNamePath)) {
+        output.write(cw.toByteArray());
+      }
+    }
   }
 }
diff --git a/src/test/examplesAndroidP/invokecustom/TestGenerator.java b/src/test/examplesAndroidP/invokecustom/TestGenerator.java
index 375dc34..7ee4bdc 100644
--- a/src/test/examplesAndroidP/invokecustom/TestGenerator.java
+++ b/src/test/examplesAndroidP/invokecustom/TestGenerator.java
@@ -3,13 +3,14 @@
 // BSD-style license that can be found in the LICENSE file.
 package invokecustom;
 
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
+
 import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
 import java.lang.invoke.CallSite;
-import java.lang.invoke.MethodHandle;
 import java.lang.invoke.MethodHandles;
 import java.lang.invoke.MethodType;
+import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import org.objectweb.asm.ClassReader;
@@ -36,19 +37,23 @@
   }
 
   private void generateTests() throws IOException {
-    ClassReader cr = new ClassReader(new FileInputStream(classNamePath.toFile()));
-    ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);
-    cr.accept(
-        new ClassVisitor(Opcodes.ASM6, cw) {
-          @Override
-          public void visitEnd() {
-            generateMethodTest1(cw);
-            generateMethodTest2(cw);
-            generateMethodMain(cw);
-            super.visitEnd();
-          }
-        }, 0);
-    new FileOutputStream(classNamePath.toFile()).write(cw.toByteArray());
+    try (InputStream inputStream = Files.newInputStream(classNamePath)) {
+      ClassReader cr = new ClassReader(inputStream);
+      ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);
+      cr.accept(
+          new ClassVisitor(Opcodes.ASM6, cw) {
+            @Override
+            public void visitEnd() {
+              generateMethodTest1(cw);
+              generateMethodTest2(cw);
+              generateMethodMain(cw);
+              super.visitEnd();
+            }
+          }, 0);
+      try (OutputStream output = Files.newOutputStream(classNamePath)) {
+        output.write(cw.toByteArray());
+      }
+    }
   }
 
   /* Generate main method that only call all test methods. */
diff --git a/src/test/java/com/android/tools/r8/debug/DebugTestBase.java b/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
index de8c673..f3fd9d2 100644
--- a/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
+++ b/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
@@ -14,11 +14,10 @@
 import com.android.tools.r8.ToolHelper.ProcessResult;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.naming.ClassNameMapper;
-import com.android.tools.r8.naming.ClassNaming;
+import com.android.tools.r8.naming.ClassNamingForNameMapper;
 import com.android.tools.r8.naming.MemberNaming;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
 import com.android.tools.r8.naming.MemberNaming.Signature;
-import com.android.tools.r8.naming.ProguardMapReader;
 import com.android.tools.r8.shaking.ProguardConfiguration;
 import com.android.tools.r8.shaking.ProguardRuleParserException;
 import com.android.tools.r8.utils.AndroidApiLevel;
@@ -353,7 +352,7 @@
       paths[indexPath++] = languageFeatures.getDexPath().toString();
       Path proguardMapPath = Paths.get(paths[indexPath - 1]).resolveSibling(PROGUARD_MAP_FILENAME);
       if (Files.exists(proguardMapPath)) {
-        classNameMapper = ProguardMapReader.mapperFromFile(proguardMapPath);
+        classNameMapper = ClassNameMapper.mapperFromFile(proguardMapPath);
       }
     }
     for (Path extraPath : extraPaths) {
@@ -670,7 +669,7 @@
       @Override
       public String getObfuscatedMethodName(
           String originalClassName, String originalMethodName, String methodSignatureOrNull) {
-        ClassNaming naming;
+        ClassNamingForNameMapper naming;
         String obfuscatedClassName =
             classNameMapper.getObfuscatedToOriginalMapping().inverse().get(originalClassName);
         if (obfuscatedClassName != null) {
@@ -705,7 +704,7 @@
       /** Assumes classNameMapper is valid. Return null if no member naming found. */
       private MemberNaming getMemberNaming(
           String obfuscatedClassName, String obfuscatedMethodName, String genericMethodSignature) {
-        ClassNaming classNaming = classNameMapper.getClassNaming(obfuscatedClassName);
+        ClassNamingForNameMapper classNaming = classNameMapper.getClassNaming(obfuscatedClassName);
         if (classNaming == null) {
           return null;
         }
diff --git a/src/test/java/com/android/tools/r8/debug/JasminDebugTest.java b/src/test/java/com/android/tools/r8/debug/JasminDebugTest.java
index 2b0084d..ddcbf8c 100644
--- a/src/test/java/com/android/tools/r8/debug/JasminDebugTest.java
+++ b/src/test/java/com/android/tools/r8/debug/JasminDebugTest.java
@@ -9,7 +9,7 @@
 import com.google.common.collect.ImmutableList;
 import jasmin.ClassFile;
 import java.io.File;
-import java.io.FileOutputStream;
+import java.io.OutputStream;
 import java.io.StringReader;
 import java.nio.file.Files;
 import java.nio.file.Path;
@@ -88,7 +88,9 @@
       file.readJasmin(new StringReader(clazz.toString()), clazz.name, false);
       Path path = out.toPath().resolve(clazz.name + ".class");
       Files.createDirectories(path.getParent());
-      file.write(new FileOutputStream(path.toFile()));
+      try (OutputStream outputStream = Files.newOutputStream(path)) {
+        file.write(outputStream);
+      }
       if (isRunningJava()) {
         extraPaths.add(path);
       } else {
diff --git a/src/test/java/com/android/tools/r8/ir/BasicBlockIteratorTest.java b/src/test/java/com/android/tools/r8/ir/BasicBlockIteratorTest.java
index cf191de..ad9a26a 100644
--- a/src/test/java/com/android/tools/r8/ir/BasicBlockIteratorTest.java
+++ b/src/test/java/com/android/tools/r8/ir/BasicBlockIteratorTest.java
@@ -4,7 +4,6 @@
 
 package com.android.tools.r8.ir;
 
-import com.android.tools.r8.CompilationException;
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.ir.code.BasicBlock;
@@ -52,12 +51,12 @@
     // Build the code, and split the code into three blocks.
     ValueNumberGenerator valueNumberGenerator = new ValueNumberGenerator();
     DexEncodedMethod method = getMethod(application, signature);
-    IRCode code = method.buildIR(valueNumberGenerator, new InternalOptions());
+    IRCode code = method.buildIR(new InternalOptions(), valueNumberGenerator);
     ListIterator<BasicBlock> blocks = code.listIterator();
     InstructionListIterator iter = blocks.next().listIterator();
     iter.nextUntil(i -> !i.isArgument());
     iter.previous();
-    iter.split(1, code, blocks);
+    iter.split(code, 1, blocks);
     return code;
   }
 
diff --git a/src/test/java/com/android/tools/r8/ir/InlineTest.java b/src/test/java/com/android/tools/r8/ir/InlineTest.java
index 8258324..27a3ce9 100644
--- a/src/test/java/com/android/tools/r8/ir/InlineTest.java
+++ b/src/test/java/com/android/tools/r8/ir/InlineTest.java
@@ -76,13 +76,13 @@
     // Return the processed method for inspection.
     ValueNumberGenerator valueNumberGenerator = new ValueNumberGenerator();
     DexEncodedMethod method = getMethod(application, signature);
-    IRCode code = method.buildIR(valueNumberGenerator, new InternalOptions());
+    IRCode code = method.buildIR(new InternalOptions(), valueNumberGenerator);
 
     DexEncodedMethod methodA = getMethod(application, signatureA);
-    IRCode codeA = methodA.buildIR(valueNumberGenerator, new InternalOptions());
+    IRCode codeA = methodA.buildIR(new InternalOptions(), valueNumberGenerator);
 
     DexEncodedMethod methodB = getMethod(application, signatureB);
-    IRCode codeB = methodB.buildIR(valueNumberGenerator, new InternalOptions());
+    IRCode codeB = methodB.buildIR(new InternalOptions(), valueNumberGenerator);
 
     return new SmaliTestBase.TestApplication(application, method, code,
         ImmutableList.of(codeA, codeB), valueNumberGenerator, options);
@@ -159,10 +159,10 @@
     // Return the processed method for inspection.
     ValueNumberGenerator valueNumberGenerator = new ValueNumberGenerator();
     DexEncodedMethod method = getMethod(application, signature);
-    IRCode code = method.buildIR(valueNumberGenerator, new InternalOptions());
+    IRCode code = method.buildIR(new InternalOptions(), valueNumberGenerator);
 
     DexEncodedMethod methodA = getMethod(application, signatureA);
-    IRCode codeA = methodA.buildIR(valueNumberGenerator, new InternalOptions());
+    IRCode codeA = methodA.buildIR(new InternalOptions(), valueNumberGenerator);
 
     return new TestApplication(application, method, code,
         ImmutableList.of(codeA), valueNumberGenerator, options);
@@ -239,19 +239,19 @@
     // Return the processed method for inspection.
     ValueNumberGenerator valueNumberGenerator = new ValueNumberGenerator();
     DexEncodedMethod method = getMethod(application, signature);
-    IRCode code = method.buildIR(valueNumberGenerator, new InternalOptions());
+    IRCode code = method.buildIR(new InternalOptions(), valueNumberGenerator);
 
     // Build three copies of a and b for inlining three times.
     List<IRCode> additionalCode = new ArrayList<>();
     for (int i = 0; i < 3; i++) {
       DexEncodedMethod methodA = getMethod(application, signatureA);
-      IRCode codeA = methodA.buildIR(valueNumberGenerator, new InternalOptions());
+      IRCode codeA = methodA.buildIR(new InternalOptions(), valueNumberGenerator);
       additionalCode.add(codeA);
     }
 
     for (int i = 0; i < 3; i++) {
       DexEncodedMethod methodB = getMethod(application, signatureB);
-      IRCode codeB = methodB.buildIR(valueNumberGenerator, new InternalOptions());
+      IRCode codeB = methodB.buildIR(new InternalOptions(), valueNumberGenerator);
       additionalCode.add(codeB);
     }
 
@@ -373,13 +373,13 @@
     // Return the processed method for inspection.
     ValueNumberGenerator valueNumberGenerator = new ValueNumberGenerator();
     DexEncodedMethod method = getMethod(application, signature);
-    IRCode code = method.buildIR(valueNumberGenerator, new InternalOptions());
+    IRCode code = method.buildIR(new InternalOptions(), valueNumberGenerator);
 
     DexEncodedMethod methodA = getMethod(application, signatureA);
-    IRCode codeA = methodA.buildIR(valueNumberGenerator, new InternalOptions());
+    IRCode codeA = methodA.buildIR(new InternalOptions(), valueNumberGenerator);
 
     DexEncodedMethod methodB = getMethod(application, signatureB);
-    IRCode codeB = methodB.buildIR(valueNumberGenerator, new InternalOptions());
+    IRCode codeB = methodB.buildIR(new InternalOptions(), valueNumberGenerator);
 
     return new TestApplication(application, method, code,
         ImmutableList.of(codeA, codeB), valueNumberGenerator, options);
@@ -487,13 +487,13 @@
     // Return the processed method for inspection.
     ValueNumberGenerator valueNumberGenerator = new ValueNumberGenerator();
     DexEncodedMethod method = getMethod(application, signature);
-    IRCode code = method.buildIR(valueNumberGenerator, new InternalOptions());
+    IRCode code = method.buildIR(new InternalOptions(), valueNumberGenerator);
 
     DexEncodedMethod methodA = getMethod(application, signatureA);
-    IRCode codeA = methodA.buildIR(valueNumberGenerator, new InternalOptions());
+    IRCode codeA = methodA.buildIR(new InternalOptions(), valueNumberGenerator);
 
     DexEncodedMethod methodB = getMethod(application, signatureB);
-    IRCode codeB = methodB.buildIR(valueNumberGenerator, new InternalOptions());
+    IRCode codeB = methodB.buildIR(new InternalOptions(), valueNumberGenerator);
 
     return new TestApplication(application, method, code,
         ImmutableList.of(codeA, codeB), valueNumberGenerator, options);
@@ -600,13 +600,13 @@
     // Return the processed method for inspection.
     ValueNumberGenerator valueNumberGenerator = new ValueNumberGenerator();
     DexEncodedMethod method = getMethod(application, signature);
-    IRCode code = method.buildIR(valueNumberGenerator, new InternalOptions());
+    IRCode code = method.buildIR(new InternalOptions(), valueNumberGenerator);
 
     DexEncodedMethod methodA = getMethod(application, signatureA);
-    IRCode codeA = methodA.buildIR(valueNumberGenerator, new InternalOptions());
+    IRCode codeA = methodA.buildIR(new InternalOptions(), valueNumberGenerator);
 
     DexEncodedMethod methodB = getMethod(application, signatureB);
-    IRCode codeB = methodB.buildIR(valueNumberGenerator, new InternalOptions());
+    IRCode codeB = methodB.buildIR(new InternalOptions(), valueNumberGenerator);
 
     return new TestApplication(application, method, code,
         ImmutableList.of(codeA, codeB), valueNumberGenerator, options);
@@ -714,19 +714,19 @@
     // Return the processed method for inspection.
     ValueNumberGenerator valueNumberGenerator = new ValueNumberGenerator();
     DexEncodedMethod method = getMethod(application, signature);
-    IRCode code = method.buildIR(valueNumberGenerator, new InternalOptions());
+    IRCode code = method.buildIR(new InternalOptions(), valueNumberGenerator);
 
     // Build three copies of a and b for inlining three times.
     List<IRCode> additionalCode = new ArrayList<>();
     for (int i = 0; i < 3; i++) {
       DexEncodedMethod methodA = getMethod(application, signatureA);
-      IRCode codeA = methodA.buildIR(valueNumberGenerator, new InternalOptions());
+      IRCode codeA = methodA.buildIR(new InternalOptions(), valueNumberGenerator);
       additionalCode.add(codeA);
     }
 
     for (int i = 0; i < 3; i++) {
       DexEncodedMethod methodB = getMethod(application, signatureB);
-      IRCode codeB = methodB.buildIR(valueNumberGenerator, new InternalOptions());
+      IRCode codeB = methodB.buildIR(new InternalOptions(), valueNumberGenerator);
       additionalCode.add(codeB);
     }
 
@@ -871,19 +871,19 @@
     // Return the processed method for inspection.
     ValueNumberGenerator valueNumberGenerator = new ValueNumberGenerator();
     DexEncodedMethod method = getMethod(application, signature);
-    IRCode code = method.buildIR(valueNumberGenerator, new InternalOptions());
+    IRCode code = method.buildIR(new InternalOptions(), valueNumberGenerator);
 
     // Build three copies of a and b for inlining three times.
     List<IRCode> additionalCode = new ArrayList<>();
     for (int i = 0; i < 3; i++) {
       DexEncodedMethod methodA = getMethod(application, signatureA);
-      IRCode codeA = methodA.buildIR(valueNumberGenerator, new InternalOptions());
+      IRCode codeA = methodA.buildIR(new InternalOptions(), valueNumberGenerator);
       additionalCode.add(codeA);
     }
 
     for (int i = 0; i < 3; i++) {
       DexEncodedMethod methodB = getMethod(application, signatureB);
-      IRCode codeB = methodB.buildIR(valueNumberGenerator, new InternalOptions());
+      IRCode codeB = methodB.buildIR(new InternalOptions(), valueNumberGenerator);
       additionalCode.add(codeB);
     }
 
@@ -1118,13 +1118,13 @@
     // Return the processed method for inspection.
     ValueNumberGenerator valueNumberGenerator = new ValueNumberGenerator();
     DexEncodedMethod method = getMethod(application, signature);
-    IRCode code = method.buildIR(valueNumberGenerator, new InternalOptions());
+    IRCode code = method.buildIR(new InternalOptions(), valueNumberGenerator);
 
     DexEncodedMethod methodA = getMethod(application, signatureA);
-    IRCode codeA = methodA.buildIR(valueNumberGenerator, new InternalOptions());
+    IRCode codeA = methodA.buildIR(new InternalOptions(), valueNumberGenerator);
 
     DexEncodedMethod methodB = getMethod(application, signatureB);
-    IRCode codeB = methodB.buildIR(valueNumberGenerator, new InternalOptions());
+    IRCode codeB = methodB.buildIR(new InternalOptions(), valueNumberGenerator);
 
     return new TestApplication(application, method, code,
         ImmutableList.of(codeA, codeB), valueNumberGenerator, options);
diff --git a/src/test/java/com/android/tools/r8/ir/InstructionIteratorTest.java b/src/test/java/com/android/tools/r8/ir/InstructionIteratorTest.java
index b9eef9c..a688ffe 100644
--- a/src/test/java/com/android/tools/r8/ir/InstructionIteratorTest.java
+++ b/src/test/java/com/android/tools/r8/ir/InstructionIteratorTest.java
@@ -52,12 +52,12 @@
     // Build the code, and split the code into three blocks.
     ValueNumberGenerator valueNumberGenerator = new ValueNumberGenerator();
     DexEncodedMethod method = getMethod(application, signature);
-    IRCode code = method.buildIR(valueNumberGenerator, new InternalOptions());
+    IRCode code = method.buildIR(new InternalOptions(), valueNumberGenerator);
     ListIterator<BasicBlock> blocks = code.listIterator();
     InstructionListIterator iter = blocks.next().listIterator();
     iter.nextUntil(i -> !i.isArgument());
     iter.previous();
-    iter.split(1, code, blocks);
+    iter.split(code, 1, blocks);
     return code;
   }
 
diff --git a/src/test/java/com/android/tools/r8/ir/SplitBlockTest.java b/src/test/java/com/android/tools/r8/ir/SplitBlockTest.java
index 7351206..9192b2f 100644
--- a/src/test/java/com/android/tools/r8/ir/SplitBlockTest.java
+++ b/src/test/java/com/android/tools/r8/ir/SplitBlockTest.java
@@ -64,7 +64,7 @@
     // Return the processed method for inspection.
     ValueNumberGenerator valueNumberGenerator = new ValueNumberGenerator();
     DexEncodedMethod method = getMethod(application, signature);
-    IRCode code = method.buildIR(valueNumberGenerator, new InternalOptions());
+    IRCode code = method.buildIR(new InternalOptions(), valueNumberGenerator);
 
     return new TestApplication(application, method, code, valueNumberGenerator, options);
   }
@@ -123,7 +123,7 @@
       assertTrue(!block.getInstructions().get(i).isArgument());
 
       InstructionListIterator iterator = test.listIteratorAt(block, i);
-      BasicBlock newBlock = iterator.split(1, code);
+      BasicBlock newBlock = iterator.split(code, 1);
       assertTrue(code.isConsistentSSA());
 
       assertEquals(initialBlockCount + 2, code.blocks.size());
@@ -182,7 +182,7 @@
     // Return the processed method for inspection.
     ValueNumberGenerator valueNumberGenerator = new ValueNumberGenerator();
     DexEncodedMethod method = getMethod(application, signature);
-    IRCode code = method.buildIR(valueNumberGenerator, new InternalOptions());
+    IRCode code = method.buildIR(new InternalOptions(), valueNumberGenerator);
     return new TestApplication(application, method, code, valueNumberGenerator, options);
   }
 
@@ -247,7 +247,7 @@
       assertEquals(secondBlockInstructions, instructionCount);
 
       InstructionListIterator iterator = test.listIteratorAt(block, i);
-      BasicBlock newBlock = iterator.split(1, code);
+      BasicBlock newBlock = iterator.split(code, 1);
       assertTrue(code.isConsistentSSA());
 
       assertEquals(initialBlockCount + 2, code.blocks.size());
@@ -307,7 +307,7 @@
     // Return the processed method for inspection.
     ValueNumberGenerator valueNumberGenerator = new ValueNumberGenerator();
     DexEncodedMethod method = getMethod(application, signature);
-    IRCode code = method.buildIR(valueNumberGenerator, new InternalOptions());
+    IRCode code = method.buildIR(new InternalOptions(), valueNumberGenerator);
 
     return new TestApplication(application, method, code, valueNumberGenerator, options);
   }
@@ -431,7 +431,7 @@
     // Return the processed method for inspection.
     ValueNumberGenerator valueNumberGenerator = new ValueNumberGenerator();
     DexEncodedMethod method = getMethod(application, signature);
-    IRCode code = method.buildIR(valueNumberGenerator, new InternalOptions());
+    IRCode code = method.buildIR(new InternalOptions(), valueNumberGenerator);
 
     return new TestApplication(application, method, code, valueNumberGenerator, options);
   }
diff --git a/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java b/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java
index c8ea3bb..a1046db 100644
--- a/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java
+++ b/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java
@@ -99,7 +99,8 @@
     }
 
     @Override
-    public BasicBlock split(int instructions, IRCode code, ListIterator<BasicBlock> blockIterator) {
+    public BasicBlock split(IRCode code, int instructions,
+        ListIterator<BasicBlock> blockIterator) {
       throw new Unimplemented();
     }
 
diff --git a/src/test/java/com/android/tools/r8/jasmin/JasminBuilder.java b/src/test/java/com/android/tools/r8/jasmin/JasminBuilder.java
index 9baed48..caada82 100644
--- a/src/test/java/com/android/tools/r8/jasmin/JasminBuilder.java
+++ b/src/test/java/com/android/tools/r8/jasmin/JasminBuilder.java
@@ -191,7 +191,7 @@
         }
       };
       builder.addClassProgramData(
-          origin, compile(clazz), Collections.singleton(clazz.getDescriptor()));
+          compile(clazz), origin, Collections.singleton(clazz.getDescriptor()));
     }
     return builder.build();
   }
diff --git a/src/test/java/com/android/tools/r8/jasmin/JasminTestBase.java b/src/test/java/com/android/tools/r8/jasmin/JasminTestBase.java
index 67decf2..15cf848 100644
--- a/src/test/java/com/android/tools/r8/jasmin/JasminTestBase.java
+++ b/src/test/java/com/android/tools/r8/jasmin/JasminTestBase.java
@@ -132,7 +132,10 @@
     for (ClassBuilder clazz : builder.getClasses()) {
       ClassFile file = new ClassFile();
       file.readJasmin(new StringReader(clazz.toString()), clazz.name, false);
-      file.write(new FileOutputStream(classes.toPath().resolve(clazz.name + ".class").toFile()));
+      try (OutputStream outputStream =
+          Files.newOutputStream(classes.toPath().resolve(clazz.name + ".class"))) {
+        file.write(outputStream);
+      }
     }
     List<String> args = new ArrayList<>();
     args.add("--output=" + dex.toString());
diff --git a/src/test/java/com/android/tools/r8/naming/ApplyMappingTest.java b/src/test/java/com/android/tools/r8/naming/ApplyMappingTest.java
new file mode 100644
index 0000000..dac440a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/ApplyMappingTest.java
@@ -0,0 +1,238 @@
+// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.naming;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import com.android.tools.r8.CompilationException;
+import com.android.tools.r8.R8Command;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.shaking.ProguardRuleParserException;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.DexInspector.InstructionSubject;
+import com.android.tools.r8.utils.DexInspector.InvokeInstructionSubject;
+import com.android.tools.r8.utils.DexInspector.MethodSubject;
+import com.android.tools.r8.utils.FileUtils;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Iterator;
+import java.util.concurrent.ExecutionException;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+public class ApplyMappingTest {
+
+  private static final String MAPPING = "test-mapping.txt";
+
+  private static final Path MINIFICATION_JAR =
+      Paths.get(ToolHelper.EXAMPLES_BUILD_DIR, "minification" + FileUtils.JAR_EXTENSION);
+
+  private static final Path NAMING001_JAR =
+      Paths.get(ToolHelper.EXAMPLES_BUILD_DIR, "naming001" + FileUtils.JAR_EXTENSION);
+
+  private static final Path NAMING044_JAR =
+      Paths.get(ToolHelper.EXAMPLES_BUILD_DIR, "naming044" + FileUtils.JAR_EXTENSION);
+
+  private static final Path APPLYMAPPING044_JAR =
+      Paths.get(ToolHelper.EXAMPLES_BUILD_DIR, "applymapping044" + FileUtils.JAR_EXTENSION);
+
+  @Rule
+  public TemporaryFolder temp = new TemporaryFolder();
+
+  private Path out;
+
+  @Before
+  public void setup() throws IOException {
+    out = temp.newFolder("outdex").toPath();
+  }
+
+  @Test
+  public void test044_obfuscate_and_apply()
+      throws IOException, CompilationException, ProguardRuleParserException, ExecutionException {
+    // keep rules that allow obfuscations while keeping everything.
+    Path flagForObfuscation =
+        Paths.get(ToolHelper.EXAMPLES_DIR, "naming044", "keep-rules-005.txt");
+    Path proguardMap = out.resolve(MAPPING);
+    AndroidApp obfuscatedApp = runR8(
+        getCommandForApps(out, flagForObfuscation, NAMING044_JAR)
+            .addProguardConfigurationConsumer(c -> {
+              c.setPrintMapping(true);
+              c.setPrintMappingFile(proguardMap);
+            }).build());
+
+    // Obviously, dumped map and resource inside the app should be *identical*.
+    ClassNameMapper mapperFromFile = ClassNameMapper.mapperFromFile(proguardMap);
+    ClassNameMapper mapperFromApp =
+        ClassNameMapper.mapperFromInputStream(obfuscatedApp.getProguardMap());
+    assertEquals(mapperFromFile, mapperFromApp);
+
+    Path instrOut = temp.newFolder("instr").toPath();
+    Path flag = Paths.get(ToolHelper.EXAMPLES_DIR, "applymapping044", "keep-rules.txt");
+    AndroidApp instrApp = runR8(
+        getCommandForInstrumentation(instrOut, flag, NAMING044_JAR, APPLYMAPPING044_JAR)
+            .addProguardConfigurationConsumer(c -> {
+              c.setApplyMappingFile(proguardMap);
+            })
+            .setMinification(false)
+            .build());
+
+    DexInspector inspector = new DexInspector(instrApp);
+    MethodSubject main = inspector.clazz("applymapping044.Main").method(DexInspector.MAIN);
+    Iterator<InvokeInstructionSubject> iterator =
+        main.iterateInstructions(InstructionSubject::isInvoke);
+    // B#m()
+    String b = iterator.next().holder().toString();
+    assertEquals("naming044.B", mapperFromApp.deobfuscateClassName(b));
+    // sub.SubB#n()
+    String subB = iterator.next().holder().toString();
+    assertEquals("naming044.sub.SubB", mapperFromApp.deobfuscateClassName(subB));
+    // Skip A#<init>
+    iterator.next();
+    // Skip B#<init>
+    iterator.next();
+    // B#f(A)
+    InvokeInstructionSubject f = iterator.next();
+    DexType a1 = f.invokedMethod().proto.parameters.values[0];
+    assertNotEquals("naming044.A", a1.toString());
+    assertEquals("naming044.A", mapperFromApp.deobfuscateClassName(a1.toString()));
+    assertNotEquals("f", f.invokedMethod().name.toSourceString());
+    // Skip AsubB#<init>
+    iterator.next();
+    // AsubB#f(A)
+    InvokeInstructionSubject overloaded_f = iterator.next();
+    DexMethod aSubB_f = overloaded_f.invokedMethod();
+    DexType a2 = aSubB_f.proto.parameters.values[0];
+    assertNotEquals("naming044.A", a2.toString());
+    assertEquals("naming044.A", mapperFromApp.deobfuscateClassName(a2.toString()));
+    assertNotEquals("f", overloaded_f.invokedMethod().name.toSourceString());
+    // B#f == AsubB#f
+    assertEquals(f.invokedMethod().name.toString(), aSubB_f.name.toString());
+  }
+
+  @Test
+  public void test044_apply()
+      throws IOException, CompilationException, ProguardRuleParserException, ExecutionException {
+    Path flag =
+        Paths.get(ToolHelper.EXAMPLES_DIR, "applymapping044", "keep-rules-apply-mapping.txt");
+    AndroidApp outputApp = runR8(
+        getCommandForInstrumentation(out, flag, NAMING044_JAR, APPLYMAPPING044_JAR)
+            .setMinification(false)
+            .build());
+
+    // Make sure the given proguard map is indeed applied.
+    DexInspector inspector = new DexInspector(outputApp);
+    MethodSubject main = inspector.clazz("applymapping044.Main").method(DexInspector.MAIN);
+    Iterator<InvokeInstructionSubject> iterator =
+        main.iterateInstructions(InstructionSubject::isInvoke);
+    // B#m() -> y#n()
+    InvokeInstructionSubject m = iterator.next();
+    assertEquals("naming044.y", m.holder().toString());
+    assertEquals("n", m.invokedMethod().name.toSourceString());
+    // sub.SubB#n() -> z.y#m()
+    InvokeInstructionSubject n = iterator.next();
+    assertEquals("naming044.z.y", n.holder().toString());
+    assertEquals("m", n.invokedMethod().name.toSourceString());
+    // Skip A#<init>
+    iterator.next();
+    // Skip B#<init>
+    iterator.next();
+    // B#f(A) -> y#p(x)
+    InvokeInstructionSubject f = iterator.next();
+    DexType a1 = f.invokedMethod().proto.parameters.values[0];
+    assertEquals("naming044.x", a1.toString());
+    assertEquals("p", f.invokedMethod().name.toSourceString());
+    // Skip AsubB#<init>
+    iterator.next();
+    // AsubB#f(A) -> AsubB#p(x)
+    InvokeInstructionSubject overloaded_f = iterator.next();
+    DexMethod aSubB_f = overloaded_f.invokedMethod();
+    DexType a2 = aSubB_f.proto.parameters.values[0];
+    assertEquals("naming044.x", a2.toString());
+    assertEquals("p", aSubB_f.name.toSourceString());
+    // B#f == AsubB#f
+    assertEquals(f.invokedMethod().name.toString(), aSubB_f.name.toString());
+  }
+
+  @Test
+  public void test_naming001_rule105()
+      throws IOException, CompilationException, ProguardRuleParserException, ExecutionException {
+    // keep rules to reserve D and E, along with a proguard map.
+    Path flag = Paths.get(ToolHelper.EXAMPLES_DIR, "naming001", "keep-rules-105.txt");
+    Path proguardMap = out.resolve(MAPPING);
+    AndroidApp outputApp = runR8(
+        getCommandForApps(out, flag, NAMING001_JAR)
+            .addProguardConfigurationConsumer(c -> {
+              c.setPrintMapping(true);
+              c.setPrintMappingFile(proguardMap);
+            })
+            .setMinification(false)
+            .build());
+
+    // Make sure the given proguard map is indeed applied.
+    DexInspector inspector = new DexInspector(outputApp);
+    MethodSubject main = inspector.clazz("naming001.D").method(DexInspector.MAIN);
+    Iterator<InvokeInstructionSubject> iterator =
+        main.iterateInstructions(InstructionSubject::isInvoke);
+    // mapping-105 simply includes: naming001.D#keep -> peek
+    // naming001.E extends D, hence its keep() should be renamed to peek as well.
+    // Skip E#<init>
+    iterator.next();
+    // E#keep() should be replaced with peek by applying the map.
+    InvokeInstructionSubject m = iterator.next();
+    assertEquals("peek", m.invokedMethod().name.toSourceString());
+    // E could be renamed randomly, though.
+    assertNotEquals("naming001.E", m.holder().toString());
+  }
+
+  @Test
+  public void test_minification_conflict_mapping()
+      throws IOException, CompilationException, ExecutionException, ProguardRuleParserException {
+    Path flag = Paths.get(
+        ToolHelper.EXAMPLES_DIR, "minification", "keep-rules-apply-conflict-mapping.txt");
+    try {
+      runR8(getCommandForApps(out, flag, MINIFICATION_JAR).build());
+      fail("Expect to detect renaming conflict");
+    } catch (ProguardMapError e) {
+      assertTrue(e.getMessage().contains("functionFromIntToInt"));
+    }
+  }
+
+  private R8Command.Builder getCommandForInstrumentation(
+      Path out, Path flag, Path mainApp, Path instrApp)
+      throws CompilationException, IOException {
+    return R8Command.builder()
+        .addLibraryFiles(Paths.get(ToolHelper.getDefaultAndroidJar()), mainApp)
+        .addProgramFiles(instrApp)
+        .setOutputPath(out)
+        .addProguardConfigurationFiles(flag);
+  }
+
+  private R8Command.Builder getCommandForApps(
+      Path out, Path flag, Path... jars)
+      throws CompilationException, IOException {
+    return R8Command.builder()
+        .addLibraryFiles(Paths.get(ToolHelper.getDefaultAndroidJar()))
+        .addProgramFiles(jars)
+        .setOutputPath(out)
+        .addProguardConfigurationFiles(flag);
+  }
+
+  private static AndroidApp runR8(R8Command command)
+      throws ProguardRuleParserException, ExecutionException, CompilationException, IOException {
+    return ToolHelper.runR8(command, options -> {
+      // Disable inlining to make this test not depend on inlining decisions.
+      options.inlineAccessors = false;
+    });
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/ProguardMapReaderTest.java b/src/test/java/com/android/tools/r8/naming/ProguardMapReaderTest.java
index dccfd8a..a818a58 100644
--- a/src/test/java/com/android/tools/r8/naming/ProguardMapReaderTest.java
+++ b/src/test/java/com/android/tools/r8/naming/ProguardMapReaderTest.java
@@ -19,19 +19,19 @@
 
   @Test
   public void parseThrowingMap() throws IOException {
-    ProguardMapReader.mapperFromFile(Paths.get(ROOT, EXAMPLE_MAP));
+    ClassNameMapper.mapperFromFile(Paths.get(ROOT, EXAMPLE_MAP));
   }
 
   @Test
   public void roundTripTest() throws IOException {
-    ClassNameMapper firstMapper = ProguardMapReader.mapperFromFile(Paths.get(ROOT, EXAMPLE_MAP));
-    ClassNameMapper secondMapper = ProguardMapReader.mapperFromString(firstMapper.toString());
+    ClassNameMapper firstMapper = ClassNameMapper.mapperFromFile(Paths.get(ROOT, EXAMPLE_MAP));
+    ClassNameMapper secondMapper = ClassNameMapper.mapperFromString(firstMapper.toString());
     Assert.assertEquals(firstMapper, secondMapper);
   }
 
   @Test
   public void parseMapWithPackageInfo() throws IOException {
-    ClassNameMapper mapper = ProguardMapReader.mapperFromString(EXAMPLE_MAP_WITH_PACKAGE_INFO);
+    ClassNameMapper mapper = ClassNameMapper.mapperFromString(EXAMPLE_MAP_WITH_PACKAGE_INFO);
     Assert.assertTrue(mapper.getObfuscatedToOriginalMapping().isEmpty());
   }
 }
diff --git a/src/test/java/com/android/tools/r8/shaking/TreeShakingTest.java b/src/test/java/com/android/tools/r8/shaking/TreeShakingTest.java
index b285ac0..c45ae37 100644
--- a/src/test/java/com/android/tools/r8/shaking/TreeShakingTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/TreeShakingTest.java
@@ -61,8 +61,11 @@
   private static final List<Path> JAR_LIBRARIES = ListUtils.map(ImmutableList
       .of(ANDROID_JAR, ToolHelper.EXAMPLES_BUILD_DIR + "shakinglib.jar"), Paths::get);
   private static final String EMPTY_FLAGS = "src/test/proguard/valid/empty.flags";
-
-  private static Set<String> IGNORED = ImmutableSet.of(
+  private static final Set<String> IGNORED_FLAGS = ImmutableSet.of(
+      "minification:conflict-mapping.txt",
+      "minification:keep-rules-apply-conflict-mapping.txt"
+  );
+  private static final Set<String> IGNORED = ImmutableSet.of(
       // there's no point in running those without obfuscation
       "shaking1:keep-rules-repackaging.txt:DEX:false",
       "shaking1:keep-rules-repackaging.txt:JAR:false",
@@ -844,7 +847,8 @@
       String mainClass, String keepName, List<String> keepList, boolean minify,
       Consumer<DexInspector> inspection, BiConsumer<String, String> outputComparator,
       BiConsumer<DexInspector, DexInspector> dexComparator) {
-    if (!IGNORED.contains(test + ":" + keepName + ":" + kind + ":" + minify)) {
+    if (!IGNORED_FLAGS.contains(test + ":" + keepName)
+        && !IGNORED.contains(test + ":" + keepName + ":" + kind + ":" + minify)) {
       testCases.add(new Object[]{
           test, kind, mainClass, keepList, minify, inspection, outputComparator, dexComparator});
     }
diff --git a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ForceProguardCompatibilityTest.java b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ForceProguardCompatibilityTest.java
index 1519f6c..298876f 100644
--- a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ForceProguardCompatibilityTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ForceProguardCompatibilityTest.java
@@ -55,7 +55,7 @@
 
   private void runAnnotationsTest(boolean forceProguardCompatibility, boolean keepAnnotations) throws Exception {
     R8Command.Builder builder =
-        new CompatProguardCommandBuilder(forceProguardCompatibility);
+        new CompatProguardCommandBuilder(forceProguardCompatibility, false);
     // Add application classes including the annotation class.
     Class mainClass = TestMain.class;
     Class mentionedClassWithAnnotations = TestMain.MentionedClassWithAnnotation.class;
diff --git a/src/test/java/com/android/tools/r8/utils/ArrayUtilsTest.java b/src/test/java/com/android/tools/r8/utils/ArrayUtilsTest.java
new file mode 100644
index 0000000..c44d1cf
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/ArrayUtilsTest.java
@@ -0,0 +1,102 @@
+// Copyright (c) 2017, 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.utils;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
+
+import it.unimi.dsi.fastutil.ints.Int2IntArrayMap;
+import java.util.Map;
+import org.junit.Test;
+
+public class ArrayUtilsTest {
+
+  @Test
+  public void testCopyWithSparseChanges_identical() throws Exception {
+    int size = 3;
+    Integer[] input = new Integer[size];
+    for (int i = 0; i < size; i++) {
+      input[i] = i;
+    }
+    Integer[] output =
+        ArrayUtils.copyWithSparseChanges(Integer[].class, input, new Int2IntArrayMap());
+    assertNotEquals(input, output);
+    for (int i = 0; i < size; i++) {
+      assertTrue(i == output[i]);
+    }
+  }
+
+  @Test
+  public void testCopyWithSparseChanges_oneChangeAtBeginning() throws Exception {
+    int size = 3;
+    Integer[] input = new Integer[size];
+    for (int i = 0; i < size; i++) {
+      input[i] = i;
+    }
+    Map<Integer, Integer> changes = new Int2IntArrayMap();
+    changes.put(0, size);
+    Integer[] output = ArrayUtils.copyWithSparseChanges(Integer[].class, input, changes);
+    assertNotEquals(input, output);
+    assertTrue(size == output[0]);
+    for (int i = 1; i < size; i++) {
+      assertTrue(i == output[i]);
+    }
+  }
+
+  @Test
+  public void testCopyWithSparseChanges_oneChangeInMiddle() throws Exception {
+    int size = 3;
+    Integer[] input = new Integer[size];
+    for (int i = 0; i < size; i++) {
+      input[i] = i;
+    }
+    Map<Integer, Integer> changes = new Int2IntArrayMap();
+    changes.put(1, size);
+    Integer[] output = ArrayUtils.copyWithSparseChanges(Integer[].class, input, changes);
+    assertNotEquals(input, output);
+    assertTrue(size == output[1]);
+    for (int i = 0; i < size; i++) {
+      if (i == 1) {
+        continue;
+      }
+      assertTrue(i == output[i]);
+    }
+  }
+
+  @Test
+  public void testCopyWithSparseChanges_oneChangeAtEnd() throws Exception {
+    int size = 3;
+    Integer[] input = new Integer[size];
+    for (int i = 0; i < size; i++) {
+      input[i] = i;
+    }
+    Map<Integer, Integer> changes = new Int2IntArrayMap();
+    changes.put(2, size);
+    Integer[] output = ArrayUtils.copyWithSparseChanges(Integer[].class, input, changes);
+    assertNotEquals(input, output);
+    assertTrue(size == output[2]);
+    for (int i = 0; i < size - 1; i++) {
+      assertTrue(i == output[i]);
+    }
+  }
+
+  @Test
+  public void testCopyWithSparseChanges_twoChangesAtEnds() throws Exception {
+    int size = 3;
+    Integer[] input = new Integer[size];
+    for (int i = 0; i < size; i++) {
+      input[i] = i;
+    }
+    Map<Integer, Integer> changes = new Int2IntArrayMap();
+    changes.put(0, size);
+    changes.put(2, size);
+    Integer[] output = ArrayUtils.copyWithSparseChanges(Integer[].class, input, changes);
+    assertNotEquals(input, output);
+    assertTrue(size == output[0]);
+    assertFalse(size == output[1]);
+    assertTrue(size == output[2]);
+  }
+
+}
diff --git a/src/test/java/com/android/tools/r8/utils/DexInspector.java b/src/test/java/com/android/tools/r8/utils/DexInspector.java
index 612043e..36e4961 100644
--- a/src/test/java/com/android/tools/r8/utils/DexInspector.java
+++ b/src/test/java/com/android/tools/r8/utils/DexInspector.java
@@ -58,12 +58,11 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.DexValue;
 import com.android.tools.r8.naming.ClassNameMapper;
-import com.android.tools.r8.naming.ClassNaming;
+import com.android.tools.r8.naming.ClassNamingForNameMapper;
 import com.android.tools.r8.naming.MemberNaming;
 import com.android.tools.r8.naming.MemberNaming.FieldSignature;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
 import com.android.tools.r8.naming.MemberNaming.Signature;
-import com.android.tools.r8.naming.ProguardMapReader;
 import com.google.common.collect.BiMap;
 import com.google.common.collect.ImmutableList;
 import java.io.IOException;
@@ -111,7 +110,7 @@
       throws IOException, ExecutionException {
     ExecutorService executor = Executors.newSingleThreadExecutor();
     if (mappingFile != null) {
-      this.mapping = ProguardMapReader.mapperFromFile(Paths.get(mappingFile));
+      this.mapping = ClassNameMapper.mapperFromFile(Paths.get(mappingFile));
       originalToObfuscatedMapping = this.mapping.getObfuscatedToOriginalMapping().inverse();
     } else {
       this.mapping = null;
@@ -166,7 +165,7 @@
   }
 
   public ClassSubject clazz(String name) {
-    ClassNaming naming = null;
+    ClassNamingForNameMapper naming = null;
     if (mapping != null) {
       String obfuscated = originalToObfuscatedMapping.get(name);
       if (obfuscated != null) {
@@ -183,7 +182,7 @@
 
   public void forAllClasses(Consumer<FoundClassSubject> inspection) {
     forAll(application.classes(), clazz -> {
-      ClassNaming naming = null;
+      ClassNamingForNameMapper naming = null;
       if (mapping != null) {
         String obfuscated = originalToObfuscatedMapping.get(clazz.type.toSourceString());
         if (obfuscated != null) {
@@ -360,9 +359,9 @@
   public class FoundClassSubject extends ClassSubject {
 
     private final DexClass dexClass;
-    private final ClassNaming naming;
+    private final ClassNamingForNameMapper naming;
 
-    private FoundClassSubject(DexClass dexClass, ClassNaming naming) {
+    private FoundClassSubject(DexClass dexClass, ClassNamingForNameMapper naming) {
       this.dexClass = dexClass;
       this.naming = naming;
     }