Merge "Rename merged exception classes in DeadCodeRemover"
diff --git a/build.gradle b/build.gradle
index 7720cf3..7f41caa 100644
--- a/build.gradle
+++ b/build.gradle
@@ -61,7 +61,10 @@
     '-Xep:MissingDefault:ERROR',
     '-Xep:MultipleTopLevelClasses:ERROR',
     '-Xep:NarrowingCompoundAssignment:ERROR',
-    '-Xep:BoxedPrimitiveConstructor:ERROR']
+    '-Xep:BoxedPrimitiveConstructor:ERROR',
+    '-Xep:LogicalAssignment:ERROR',
+    '-Xep:FloatCast:ERROR',
+    '-Xep:ReturnValueIgnored:ERROR']
 
 apply from: 'copyAdditionalJctfCommonFiles.gradle'
 
diff --git a/src/main/java/com/android/tools/r8/ArchiveClassFileProvider.java b/src/main/java/com/android/tools/r8/ArchiveClassFileProvider.java
index b3d59fb..af83158 100644
--- a/src/main/java/com/android/tools/r8/ArchiveClassFileProvider.java
+++ b/src/main/java/com/android/tools/r8/ArchiveClassFileProvider.java
@@ -17,6 +17,7 @@
 import java.io.Closeable;
 import java.io.IOException;
 import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
 import java.nio.file.NoSuchFileException;
 import java.nio.file.Path;
@@ -60,7 +61,7 @@
     assert isArchive(archive);
     origin = new PathOrigin(archive);
     try {
-      zipFile = new ZipFile(archive.toFile());
+      zipFile = new ZipFile(archive.toFile(), StandardCharsets.UTF_8);
     } catch (IOException e) {
       if (!Files.exists(archive)) {
         throw new NoSuchFileException(archive.toString());
diff --git a/src/main/java/com/android/tools/r8/ArchiveProgramResourceProvider.java b/src/main/java/com/android/tools/r8/ArchiveProgramResourceProvider.java
index baac9bc..425b3c3 100644
--- a/src/main/java/com/android/tools/r8/ArchiveProgramResourceProvider.java
+++ b/src/main/java/com/android/tools/r8/ArchiveProgramResourceProvider.java
@@ -16,6 +16,7 @@
 import com.google.common.io.ByteStreams;
 import java.io.IOException;
 import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.ArrayList;
@@ -60,7 +61,10 @@
 
   public static ArchiveProgramResourceProvider fromArchive(
       Path archive, Predicate<String> include) {
-    return fromSupplier(new PathOrigin(archive), () -> new ZipFile(archive.toFile()), include);
+    return fromSupplier(
+        new PathOrigin(archive),
+        () -> new ZipFile(archive.toFile(), StandardCharsets.UTF_8),
+        include);
   }
 
   public static ArchiveProgramResourceProvider fromSupplier(
diff --git a/src/main/java/com/android/tools/r8/D8CommandParser.java b/src/main/java/com/android/tools/r8/D8CommandParser.java
index 9e5d36a..93c0126 100644
--- a/src/main/java/com/android/tools/r8/D8CommandParser.java
+++ b/src/main/java/com/android/tools/r8/D8CommandParser.java
@@ -3,16 +3,79 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8;
 
+import static com.android.tools.r8.utils.FileUtils.isArchive;
+
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.origin.PathOrigin;
+import com.android.tools.r8.utils.ExceptionDiagnostic;
 import com.android.tools.r8.utils.FlagFile;
 import com.android.tools.r8.utils.StringDiagnostic;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Sets;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.NoSuchFileException;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.Arrays;
+import java.util.List;
+import java.util.Set;
 
 public class D8CommandParser extends BaseCompilerCommandParser {
 
+  static class OrderedClassFileResourceProvider implements ClassFileResourceProvider {
+    static class Builder {
+      private final ImmutableList.Builder<ClassFileResourceProvider> builder =
+          ImmutableList.builder();
+      boolean empty = true;
+
+      OrderedClassFileResourceProvider build() {
+        return new OrderedClassFileResourceProvider(builder.build());
+      }
+
+      Builder addClassFileResourceProvider(ClassFileResourceProvider provider) {
+        builder.add(provider);
+        empty = false;
+        return this;
+      }
+
+      boolean isEmpty() {
+        return empty;
+      }
+    }
+
+    final List<ClassFileResourceProvider> providers;
+    final Set<String> descriptors = Sets.newHashSet();
+
+    private OrderedClassFileResourceProvider(ImmutableList<ClassFileResourceProvider> providers) {
+      this.providers = providers;
+      // Collect all descriptors that can be provided.
+      this.providers.forEach(provider -> this.descriptors.addAll(provider.getClassDescriptors()));
+    }
+
+    static Builder builder() {
+      return new Builder();
+    }
+
+    @Override
+    public Set<String> getClassDescriptors() {
+      return descriptors;
+    }
+
+    @Override
+    public ProgramResource getProgramResource(String descriptor) {
+      // Search the providers in order. Return the program resource from the first provider that
+      // can provide it.
+      for (ClassFileResourceProvider provider : providers) {
+        if (provider.getClassDescriptors().contains(descriptor)) {
+          return provider.getProgramResource(descriptor);
+        }
+      }
+      return null;
+    }
+  }
+
   public static void main(String[] args) throws CompilationFailedException {
     D8Command command = parse(args, Origin.root()).build();
     if (command.isPrintHelp()) {
@@ -76,6 +139,8 @@
     Path outputPath = null;
     OutputMode outputMode = null;
     boolean hasDefinedApiLevel = false;
+    OrderedClassFileResourceProvider.Builder classpathBuilder =
+        OrderedClassFileResourceProvider.builder();
     String[] expandedArgs = FlagFile.expandFlagFiles(args, builder);
     try {
       for (int i = 0; i < expandedArgs.length; i++) {
@@ -115,7 +180,22 @@
         } else if (arg.equals("--lib")) {
           builder.addLibraryFiles(Paths.get(expandedArgs[++i]));
         } else if (arg.equals("--classpath")) {
-          builder.addClasspathFiles(Paths.get(expandedArgs[++i]));
+          Path file = Paths.get(expandedArgs[++i]);
+          try {
+            if (!Files.exists(file)) {
+              throw new NoSuchFileException(file.toString());
+            }
+            if (isArchive(file)) {
+              classpathBuilder.addClassFileResourceProvider(new ArchiveClassFileProvider(file));
+            } else if (Files.isDirectory(file)) {
+              classpathBuilder.addClassFileResourceProvider(
+                  DirectoryClassFileProvider.fromDirectory(file));
+            } else {
+              throw new CompilationError("Unsupported classpath file type", new PathOrigin(file));
+            }
+          } catch (IOException e) {
+            builder.error(new ExceptionDiagnostic(e, new PathOrigin(file)));
+          }
         } else if (arg.equals("--main-dex-list")) {
           builder.addMainDexListFiles(Paths.get(expandedArgs[++i]));
         } else if (arg.equals("--optimize-multidex-for-linearalloc")) {
@@ -140,6 +220,9 @@
           builder.addProgramFiles(Paths.get(arg));
         }
       }
+      if (!classpathBuilder.isEmpty()) {
+        builder.addClasspathResourceProvider(classpathBuilder.build());
+      }
       if (compilationMode != null) {
         builder.setMode(compilationMode);
       }
diff --git a/src/main/java/com/android/tools/r8/Disassemble.java b/src/main/java/com/android/tools/r8/Disassemble.java
index 9b1599f..7b17ec8 100644
--- a/src/main/java/com/android/tools/r8/Disassemble.java
+++ b/src/main/java/com/android/tools/r8/Disassemble.java
@@ -14,7 +14,6 @@
 import com.android.tools.r8.utils.StringDiagnostic;
 import com.android.tools.r8.utils.ThreadUtils;
 import com.android.tools.r8.utils.Timing;
-import com.google.common.collect.ImmutableList;
 import java.io.IOException;
 import java.nio.file.Path;
 import java.nio.file.Paths;
@@ -33,6 +32,7 @@
       private Path proguardMapFile = null;
       private boolean useSmali = false;
       private boolean allInfo = false;
+      private boolean useIr;
 
       @Override
       Builder self() {
@@ -63,6 +63,11 @@
         return this;
       }
 
+      public Builder setUseIr(boolean useIr) {
+        this.useIr = useIr;
+        return this;
+      }
+
       @Override
       protected DisassembleCommand makeCommand() {
         // If printing versions ignore everything else.
@@ -74,24 +79,26 @@
             getOutputPath(),
             proguardMapFile == null ? null : StringResource.fromFile(proguardMapFile),
             allInfo,
-            useSmali);
+            useSmali,
+            useIr);
       }
     }
 
-    static final String USAGE_MESSAGE = String.join("\n", ImmutableList.of(
-        "Usage: disasm [options] <input-files>",
-        " where <input-files> are dex files",
-        " and options are:",
-        "  --all                   # Include all information in disassembly.",
-        "  --smali                 # Disassemble using smali syntax.",
-        "  --pg-map <file>         # Proguard map <file> for mapping names.",
-        "  --output                # Specify a file or directory to write to.",
-        "  --version               # Print the version of r8.",
-        "  --help                  # Print this message."));
-
+    static final String USAGE_MESSAGE =
+        "Usage: disasm [options] <input-files>\n"
+            + " where <input-files> are dex files\n"
+            + " and options are:\n"
+            + "  --all                   # Include all information in disassembly.\n"
+            + "  --smali                 # Disassemble using smali syntax.\n"
+            + "  --ir                    # Print IR before and after optimization.\n"
+            + "  --pg-map <file>         # Proguard map <file> for mapping names.\n"
+            + "  --output                # Specify a file or directory to write to.\n"
+            + "  --version               # Print the version of r8.\n"
+            + "  --help                  # Print this message.";
 
     private final boolean allInfo;
     private final boolean useSmali;
+    private final boolean useIr;
 
     public static Builder builder() {
       return new Builder();
@@ -112,10 +119,12 @@
           builder.setPrintHelp(true);
         } else if (arg.equals("--version")) {
           builder.setPrintVersion(true);
-          } else if (arg.equals("--all")) {
+        } else if (arg.equals("--all")) {
           builder.setAllInfo(true);
         } else if (arg.equals("--smali")) {
           builder.setUseSmali(true);
+        } else if (arg.equals("--ir")) {
+          builder.setUseIr(true);
         } else if (arg.equals("--pg-map")) {
           builder.setProguardMapFile(Paths.get(args[++i]));
         } else if (arg.equals("--output")) {
@@ -132,13 +141,18 @@
     }
 
     private DisassembleCommand(
-        AndroidApp inputApp, Path outputPath, StringResource proguardMap,
-        boolean allInfo, boolean useSmali) {
+        AndroidApp inputApp,
+        Path outputPath,
+        StringResource proguardMap,
+        boolean allInfo,
+        boolean useSmali,
+        boolean useIr) {
       super(inputApp);
       this.outputPath = outputPath;
       this.proguardMap = proguardMap;
       this.allInfo = allInfo;
       this.useSmali = useSmali;
+      this.useIr = useIr;
     }
 
     private DisassembleCommand(boolean printHelp, boolean printVersion) {
@@ -147,6 +161,7 @@
       proguardMap = null;
       allInfo = false;
       useSmali = false;
+      useIr = false;
     }
 
     public Path getOutputPath() {
@@ -157,6 +172,10 @@
       return useSmali;
     }
 
+    public boolean useIr() {
+      return useIr;
+    }
+
     @Override
     InternalOptions getInternalOptions() {
       InternalOptions internal = new InternalOptions();
@@ -190,9 +209,10 @@
     try {
       DexApplication application =
           new ApplicationReader(app, options, timing).read(command.proguardMap, executor);
-      DexByteCodeWriter writer = command.useSmali()
-          ? new SmaliWriter(application, options)
-          : new AssemblyWriter(application, options, command.allInfo);
+      DexByteCodeWriter writer =
+          command.useSmali()
+              ? new SmaliWriter(application, options)
+              : new AssemblyWriter(application, options, command.allInfo, command.useIr());
       if (command.getOutputPath() != null) {
         writer.write(command.getOutputPath());
       } else {
diff --git a/src/main/java/com/android/tools/r8/PrintSeeds.java b/src/main/java/com/android/tools/r8/PrintSeeds.java
index b1bc7ca..782c91d 100644
--- a/src/main/java/com/android/tools/r8/PrintSeeds.java
+++ b/src/main/java/com/android/tools/r8/PrintSeeds.java
@@ -20,17 +20,32 @@
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
 
+/**
+ * PrintSeeds prints the classes, interfaces, methods and fields selected by a given ProGuard
+ * configuration &lt;pg-conf.txt&gt; when compiling a given program &lt;r8.jar&gt; alongside a given
+ * library &lt;rt.jar&gt;.
+ *
+ * <p>The output format is identical to what is printed when {@code -printseeds} is specified in
+ * &lt;pg-conf.txt&gt;, but running PrintSeeds can be faster than running R8 with {@code
+ * -printseeds}. See also the {@link PrintUses} program in R8.
+ */
 public class PrintSeeds {
 
   private static final String USAGE =
       "Arguments: <rt.jar> <r8.jar> <pg-conf.txt>\n"
           + "\n"
           + "PrintSeeds prints the classes, interfaces, methods and fields selected by\n"
-          + "<pg-conf.txt> when compiling <r8.jar> alongside <rt.jar>.";
+          + "<pg-conf.txt> when compiling <r8.jar> alongside <rt.jar>.\n"
+          + "\n"
+          + "The output format is identical to what is printed when -printseeds is specified in\n"
+          + "<pg-conf.txt>, but running PrintSeeds can be faster than running R8 with \n"
+          + "-printseeds. See also the "
+          + PrintUses.class.getSimpleName()
+          + " program in R8.";
 
   public static void main(String[] args) throws Exception {
     if (args.length != 3) {
-      System.out.println(USAGE);
+      System.out.println(USAGE.replace("\n", System.lineSeparator()));
       System.exit(1);
     }
     Path rtJar = Paths.get(args[0]);
diff --git a/src/main/java/com/android/tools/r8/PrintUses.java b/src/main/java/com/android/tools/r8/PrintUses.java
index 0e69de6..c762182 100644
--- a/src/main/java/com/android/tools/r8/PrintUses.java
+++ b/src/main/java/com/android/tools/r8/PrintUses.java
@@ -31,6 +31,17 @@
 import java.util.Map;
 import java.util.Set;
 
+/**
+ * PrintUses prints the classes, interfaces, methods and fields used by a given program
+ * &lt;sample.jar&gt;, restricted to classes and interfaces in a given library &lt;r8.jar&gt; that
+ * are not in &lt;sample.jar&gt;.
+ *
+ * <p>The output is in the same format as what is printed when specifying {@code -printseeds} in a
+ * ProGuard configuration file. See also the {@link PrintSeeds} program in R8.
+ *
+ * <p>Note that this tool is not related to the {@code -printusage} option of ProGuard configuration
+ * files.
+ */
 public class PrintUses {
 
   private static final String USAGE =
@@ -38,7 +49,12 @@
           + "\n"
           + "PrintUses prints the classes, interfaces, methods and fields used by <sample.jar>,\n"
           + "restricted to classes and interfaces in <r8.jar> that are not in <sample.jar>.\n"
-          + "<rt.jar> and <r8.jar> should point to libraries used by <sample.jar>.";
+          + "<rt.jar> and <r8.jar> should point to libraries used by <sample.jar>.\n"
+          + "\n"
+          + "The output is in the same format as what is printed when specifying -printseeds in\n"
+          + "a ProGuard configuration file. See also the "
+          + PrintSeeds.class.getSimpleName()
+          + " program in R8.";
 
   private final Set<String> descriptors;
   private final PrintStream out;
@@ -191,7 +207,7 @@
 
   public static void main(String[] args) throws Exception {
     if (args.length != 3) {
-      System.out.println(USAGE);
+      System.out.println(USAGE.replace("\n", System.lineSeparator()));
       return;
     }
     AndroidApp.Builder builder = AndroidApp.builder();
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index e42d8b0..2e0b531 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -11,7 +11,6 @@
 import com.android.tools.r8.dex.Marker.Tool;
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.graph.AppInfoWithSubtyping;
-import com.android.tools.r8.graph.ClassAndMemberPublicizer;
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
@@ -28,6 +27,7 @@
 import com.android.tools.r8.naming.ProguardMapSupplier;
 import com.android.tools.r8.naming.SeedMapper;
 import com.android.tools.r8.naming.SourceFileRewriter;
+import com.android.tools.r8.optimize.ClassAndMemberPublicizer;
 import com.android.tools.r8.optimize.MemberRebindingAnalysis;
 import com.android.tools.r8.optimize.VisibilityBridgeRemover;
 import com.android.tools.r8.origin.CommandLineOrigin;
diff --git a/src/main/java/com/android/tools/r8/Version.java b/src/main/java/com/android/tools/r8/Version.java
index fb3b067..1783f30 100644
--- a/src/main/java/com/android/tools/r8/Version.java
+++ b/src/main/java/com/android/tools/r8/Version.java
@@ -11,7 +11,7 @@
 
   // This field is accessed from release scripts using simple pattern matching.
   // Therefore, changing this field could break our release scripts.
-  public static final String LABEL = "1.2.21-dev";
+  public static final String LABEL = "1.2.23-dev";
 
   private Version() {
   }
diff --git a/src/main/java/com/android/tools/r8/compatdexbuilder/CompatDexBuilder.java b/src/main/java/com/android/tools/r8/compatdexbuilder/CompatDexBuilder.java
index 90b863c..2621979 100644
--- a/src/main/java/com/android/tools/r8/compatdexbuilder/CompatDexBuilder.java
+++ b/src/main/java/com/android/tools/r8/compatdexbuilder/CompatDexBuilder.java
@@ -17,6 +17,7 @@
 import com.google.common.io.ByteStreams;
 import java.io.IOException;
 import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
 import java.nio.file.Paths;
 import java.util.ArrayList;
@@ -127,7 +128,7 @@
 
       List<ZipEntry> toDex = new ArrayList<>();
 
-      try (ZipFile zipFile = new ZipFile(input)) {
+      try (ZipFile zipFile = new ZipFile(input, StandardCharsets.UTF_8)) {
         final Enumeration<? extends ZipEntry> entries = zipFile.entries();
         while (entries.hasMoreElements()) {
           ZipEntry entry = entries.nextElement();
diff --git a/src/main/java/com/android/tools/r8/compatdx/CompatDx.java b/src/main/java/com/android/tools/r8/compatdx/CompatDx.java
index f512a34..871f6d8 100644
--- a/src/main/java/com/android/tools/r8/compatdx/CompatDx.java
+++ b/src/main/java/com/android/tools/r8/compatdx/CompatDx.java
@@ -34,6 +34,7 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.PrintStream;
+import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
@@ -572,7 +573,7 @@
       // For each input archive file, add all class files within.
       for (Path input : inputs) {
         if (isArchive(input)) {
-          try (ZipFile zipFile = new ZipFile(input.toFile())) {
+          try (ZipFile zipFile = new ZipFile(input.toFile(), StandardCharsets.UTF_8)) {
             final Enumeration<? extends ZipEntry> entries = zipFile.entries();
             while (entries.hasMoreElements()) {
               ZipEntry entry = entries.nextElement();
diff --git a/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java b/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java
index 89abadd..23dbfbf 100644
--- a/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java
+++ b/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java
@@ -3,9 +3,15 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.graph;
 
+import com.android.tools.r8.ClassFileConsumer;
+import com.android.tools.r8.ir.conversion.IRConverter;
+import com.android.tools.r8.ir.conversion.OptimizationFeedback;
+import com.android.tools.r8.ir.conversion.OptimizationFeedbackIgnore;
 import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.naming.MemberNaming.FieldSignature;
+import com.android.tools.r8.utils.CfgPrinter;
 import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.Timing;
 import java.io.PrintStream;
 
 public class AssemblyWriter extends DexByteCodeWriter {
@@ -13,12 +19,29 @@
   private final boolean writeAllClassInfo;
   private final boolean writeFields;
   private final boolean writeAnnotations;
+  private final boolean writeIR;
+  private final AppInfoWithSubtyping appInfo;
+  private final Timing timing = new Timing("AssemblyWriter");
+  private final OptimizationFeedback ignoreOptimizationFeedback = new OptimizationFeedbackIgnore();
 
-  public AssemblyWriter(DexApplication application, InternalOptions options, boolean allInfo) {
+  public AssemblyWriter(
+      DexApplication application, InternalOptions options, boolean allInfo, boolean writeIR) {
     super(application, options);
     this.writeAllClassInfo = allInfo;
     this.writeFields = allInfo;
     this.writeAnnotations = allInfo;
+    this.writeIR = writeIR;
+    if (writeIR) {
+      this.appInfo = new AppInfoWithSubtyping(application.toDirect());
+      if (options.programConsumer == null) {
+        // Use class-file backend, since the CF frontend for testing does not support desugaring of
+        // synchronized methods for the DEX backend (b/109789541).
+        options.programConsumer = ClassFileConsumer.emptyConsumer();
+      }
+      options.outline.enabled = false;
+    } else {
+      this.appInfo = null;
+    }
   }
 
   @Override
@@ -88,10 +111,21 @@
     ps.println();
     Code code = method.getCode();
     if (code != null) {
-      ps.println(code.toString(method, naming));
+      if (writeIR) {
+        writeIR(method, ps);
+      } else {
+        ps.println(code.toString(method, naming));
+      }
     }
   }
 
+  private void writeIR(DexEncodedMethod method, PrintStream ps) {
+    CfgPrinter printer = new CfgPrinter();
+    new IRConverter(appInfo, options, timing, printer)
+        .processMethod(method, ignoreOptimizationFeedback, null, null, null);
+    ps.println(printer.toString());
+  }
+
   private void writeAnnotations(DexAnnotationSet annotations, PrintStream ps) {
     if (writeAnnotations) {
       if (!annotations.isEmpty()) {
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 831edbe..026c2b5 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -790,6 +790,7 @@
     private Code code;
     private CompilationState compilationState = CompilationState.NOT_PROCESSED;
     private OptimizationInfo optimizationInfo = DefaultOptimizationInfo.DEFAULT;
+    private final int classFileVersion;
 
     private Builder(DexEncodedMethod from) {
       // Copy all the mutable state of a DexEncodedMethod here.
@@ -800,6 +801,7 @@
       code = from.code;
       compilationState = from.compilationState;
       optimizationInfo = from.optimizationInfo.copy();
+      classFileVersion = from.classFileVersion;
     }
 
     public void setMethod(DexMethod method) {
@@ -816,7 +818,8 @@
       assert annotations != null;
       assert parameterAnnotations != null;
       DexEncodedMethod result =
-          new DexEncodedMethod(method, accessFlags, annotations, parameterAnnotations, code);
+          new DexEncodedMethod(
+              method, accessFlags, annotations, parameterAnnotations, code, classFileVersion);
       result.compilationState = compilationState;
       result.optimizationInfo = optimizationInfo;
       return result;
diff --git a/src/main/java/com/android/tools/r8/graph/DexValue.java b/src/main/java/com/android/tools/r8/graph/DexValue.java
index ed99d25..2310891 100644
--- a/src/main/java/com/android/tools/r8/graph/DexValue.java
+++ b/src/main/java/com/android/tools/r8/graph/DexValue.java
@@ -557,7 +557,7 @@
 
     @Override
     public int hashCode() {
-      return (int) value * 19;
+      return (int) (value * 19);
     }
 
     @Override
@@ -615,7 +615,7 @@
 
     @Override
     public int hashCode() {
-      return (int) value * 29;
+      return (int) (value * 29);
     }
 
     @Override
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java b/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java
index ca79b0f..309e1d0 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java
@@ -367,6 +367,13 @@
     }
     setLocalVariableLists();
     readEndingLocals(builder);
+    if (currentBlockInfo != null && instruction.canThrow()) {
+      Snapshot exceptionTransfer =
+          state.getSnapshot().exceptionTransfer(builder.getFactory().throwableType);
+      for (int target : currentBlockInfo.exceptionalSuccessors) {
+        recordStateForTarget(target, exceptionTransfer);
+      }
+    }
     if (isControlFlow(instruction)) {
       ensureDebugValueLivenessControl(builder);
       instruction.buildIR(builder, state, this);
@@ -376,13 +383,6 @@
       }
       state.clear();
     } else {
-      if (currentBlockInfo != null && instruction.canThrow()) {
-        Snapshot exceptionTransfer =
-            state.getSnapshot().exceptionTransfer(builder.getFactory().throwableType);
-        for (int target : currentBlockInfo.exceptionalSuccessors) {
-          recordStateForTarget(target, exceptionTransfer);
-        }
-      }
       instruction.buildIR(builder, state, this);
       ensureDebugValueLiveness(builder);
       if (builder.getCFG().containsKey(currentInstructionIndex + 1)) {
diff --git a/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java b/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
index 56e381b..e3ec746 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
@@ -8,9 +8,11 @@
 import static com.android.tools.r8.utils.DescriptorUtils.getPackageBinaryNameFromJavaType;
 
 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.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexItem;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.DexString;
@@ -18,16 +20,20 @@
 import com.android.tools.r8.graph.InnerClassAttribute;
 import com.android.tools.r8.naming.signature.GenericSignatureAction;
 import com.android.tools.r8.naming.signature.GenericSignatureParser;
+import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.RootSetBuilder.RootSet;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.InternalOptions.PackageObfuscationMode;
+import com.android.tools.r8.utils.Reporter;
+import com.android.tools.r8.utils.StringDiagnostic;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.Timing;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Maps;
 import com.google.common.collect.Sets;
+import java.lang.reflect.GenericSignatureFormatError;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.Iterator;
@@ -40,6 +46,7 @@
 
   private final AppInfoWithLiveness appInfo;
   private final RootSet rootSet;
+  private final Reporter reporter;
   private final PackageObfuscationMode packageObfuscationMode;
   private final boolean isAccessModificationAllowed;
   private final Set<String> noObfuscationPrefixes = Sets.newHashSet();
@@ -66,6 +73,7 @@
       InternalOptions options) {
     this.appInfo = appInfo;
     this.rootSet = rootSet;
+    this.reporter = options.reporter;
     this.packageObfuscationMode = options.proguardConfiguration.getPackageObfuscationMode();
     this.isAccessModificationAllowed = options.proguardConfiguration.isAccessModificationAllowed();
     this.packageDictionary = options.proguardConfiguration.getPackageObfuscationDictionary();
@@ -152,27 +160,75 @@
     }
   }
 
+  private void parseError(DexItem item, Origin origin, GenericSignatureFormatError e) {
+    StringBuilder message = new StringBuilder("Invalid class signature for ");
+    if (item instanceof DexClass) {
+      message.append("class ");
+      message.append(((DexClass) item).getType().toSourceString());
+    } else if (item instanceof DexEncodedField) {
+      message.append("field ");
+      message.append(item.toSourceString());
+    } else {
+      assert item instanceof DexEncodedMethod;
+      message.append("method ");
+      message.append(item.toSourceString());
+    }
+    message.append(".\n");
+    message.append(e.getMessage());
+    reporter.warning(new StringDiagnostic(message.toString(), origin));
+  }
+
   private void renameTypesInGenericSignatures() {
     for (DexClass clazz : appInfo.classes()) {
-      rewriteGenericSignatures(clazz.annotations.annotations,
-          genericSignatureParser::parseClassSignature);
+      clazz.annotations = rewriteGenericSignatures(clazz.annotations,
+          genericSignatureParser::parseClassSignature,
+          e -> parseError(clazz, clazz.getOrigin(), e));
       clazz.forEachField(field -> rewriteGenericSignatures(
-          field.annotations.annotations, genericSignatureParser::parseFieldSignature));
+          field.annotations, genericSignatureParser::parseFieldSignature,
+          e -> parseError(field, clazz.getOrigin(), e)));
       clazz.forEachMethod(method -> rewriteGenericSignatures(
-          method.annotations.annotations, genericSignatureParser::parseMethodSignature));
+          method.annotations, genericSignatureParser::parseMethodSignature,
+          e -> parseError(method, clazz.getOrigin(), e)));
     }
   }
 
-  private void rewriteGenericSignatures(DexAnnotation[] annotations, Consumer<String> parser) {
-    for (int i = 0; i < annotations.length; i++) {
-      DexAnnotation annotation = annotations[i];
+  private DexAnnotationSet rewriteGenericSignatures(
+      DexAnnotationSet annotations,
+      Consumer<String> parser,
+      Consumer<GenericSignatureFormatError> parseError) {
+    // There can be no more than one signature annotation in an annotation set.
+    final int VALID = -1;
+    int invalid = VALID;
+    for (int i = 0; i < annotations.annotations.length && invalid == VALID; i++) {
+      DexAnnotation annotation = annotations.annotations[i];
       if (DexAnnotation.isSignatureAnnotation(annotation, appInfo.dexItemFactory)) {
-        parser.accept(DexAnnotation.getSignature(annotation));
-        annotations[i] = DexAnnotation.createSignatureAnnotation(
-            genericSignatureRewriter.getRenamedSignature(),
-            appInfo.dexItemFactory);
+        try {
+          parser.accept(DexAnnotation.getSignature(annotation));
+          annotations.annotations[i] = DexAnnotation.createSignatureAnnotation(
+              genericSignatureRewriter.getRenamedSignature(),
+              appInfo.dexItemFactory);
+        } catch (GenericSignatureFormatError e) {
+          parseError.accept(e);
+          invalid = i;
+        }
       }
     }
+
+    // Return the rewritten signatures if it was valid and could be rewritten.
+    if (invalid == VALID) {
+      return annotations;
+    }
+    // Remove invalid signature if found.
+    DexAnnotation[] prunedAnnotations =
+        new DexAnnotation[annotations.annotations.length - 1];
+    int dest = 0;
+    for (int i = 0; i < annotations.annotations.length; i++) {
+      if (i != invalid) {
+        prunedAnnotations[dest++] = annotations.annotations[i];
+      }
+    }
+    assert dest == prunedAnnotations.length;
+    return new DexAnnotationSet(prunedAnnotations);
   }
 
   /**
@@ -453,7 +509,6 @@
       String renamed =
           getClassBinaryNameFromDescriptor(
               renaming.getOrDefault(type, type.descriptor).toString());
-      assert renamed.startsWith(enclosingRenamedBinaryName + Minifier.INNER_CLASS_SEPARATOR);
       String outName = renamed.substring(enclosingRenamedBinaryName.length() + 1);
       renamedSignature.append(outName);
       return type;
diff --git a/src/main/java/com/android/tools/r8/graph/ClassAndMemberPublicizer.java b/src/main/java/com/android/tools/r8/optimize/ClassAndMemberPublicizer.java
similarity index 87%
rename from src/main/java/com/android/tools/r8/graph/ClassAndMemberPublicizer.java
rename to src/main/java/com/android/tools/r8/optimize/ClassAndMemberPublicizer.java
index 7a13348..297dd57 100644
--- a/src/main/java/com/android/tools/r8/graph/ClassAndMemberPublicizer.java
+++ b/src/main/java/com/android/tools/r8/optimize/ClassAndMemberPublicizer.java
@@ -1,7 +1,13 @@
 // Copyright (c) 2016, 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.graph;
+package com.android.tools.r8.optimize;
+
+import com.android.tools.r8.graph.DexApplication;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.MethodAccessFlags;
 
 public final class ClassAndMemberPublicizer {
   private final DexItemFactory factory;
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 ed9cbdb..c4276dc 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
@@ -812,45 +812,45 @@
         skipWhitespace();
         switch (peekChar()) {
           case 'a':
-            if (found = acceptString("abstract")) {
+            if ((found = acceptString("abstract"))) {
               flags.setAbstract();
             }
             break;
           case 'f':
-            if (found = acceptString("final")) {
+            if ((found = acceptString("final"))) {
               flags.setFinal();
             }
             break;
           case 'n':
-            if (found = acceptString("native")) {
+            if ((found = acceptString("native"))) {
               flags.setNative();
             }
             break;
           case 'p':
-            if (found = acceptString("public")) {
+            if ((found = acceptString("public"))) {
               flags.setPublic();
-            } else if (found = acceptString("private")) {
+            } else if ((found = acceptString("private"))) {
               flags.setPrivate();
-            } else if (found = acceptString("protected")) {
+            } else if ((found = acceptString("protected"))) {
               flags.setProtected();
             }
             break;
           case 's':
-            if (found = acceptString("synchronized")) {
+            if ((found = acceptString("synchronized"))) {
               flags.setSynchronized();
-            } else if (found = acceptString("static")) {
+            } else if ((found = acceptString("static"))) {
               flags.setStatic();
-            } else if (found = acceptString("strictfp")) {
+            } else if ((found = acceptString("strictfp"))) {
               flags.setStrict();
             }
             break;
           case 't':
-            if (found = acceptString("transient")) {
+            if ((found = acceptString("transient"))) {
               flags.setTransient();
             }
             break;
           case 'v':
-            if (found = acceptString("volatile")) {
+            if ((found = acceptString("volatile"))) {
               flags.setVolatile();
             }
             break;
diff --git a/src/main/java/com/android/tools/r8/utils/ArchiveResourceProvider.java b/src/main/java/com/android/tools/r8/utils/ArchiveResourceProvider.java
index 499ba47..0f06ca8 100644
--- a/src/main/java/com/android/tools/r8/utils/ArchiveResourceProvider.java
+++ b/src/main/java/com/android/tools/r8/utils/ArchiveResourceProvider.java
@@ -22,6 +22,7 @@
 import com.google.common.io.ByteStreams;
 import java.io.IOException;
 import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.ArrayList;
@@ -49,7 +50,7 @@
   private List<ProgramResource> readArchive() throws IOException {
     List<ProgramResource> dexResources = new ArrayList<>();
     List<ProgramResource> classResources = new ArrayList<>();
-    try (ZipFile zipFile = new ZipFile(archive.getPath().toFile())) {
+    try (ZipFile zipFile = new ZipFile(archive.getPath().toFile(), StandardCharsets.UTF_8)) {
       final Enumeration<? extends ZipEntry> entries = zipFile.entries();
       while (entries.hasMoreElements()) {
         ZipEntry entry = entries.nextElement();
@@ -106,7 +107,7 @@
 
   @Override
   public void accept(Visitor resourceBrowser) throws ResourceException {
-    try (ZipFile zipFile = new ZipFile(archive.getPath().toFile())) {
+    try (ZipFile zipFile = new ZipFile(archive.getPath().toFile(), StandardCharsets.UTF_8)) {
       final Enumeration<? extends ZipEntry> entries = zipFile.entries();
       while (entries.hasMoreElements()) {
         ZipEntry entry = entries.nextElement();
diff --git a/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java b/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
index b938596..f10ff69 100644
--- a/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
+++ b/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
@@ -20,6 +20,7 @@
 import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.naming.ClassNaming;
+import com.android.tools.r8.naming.ClassNaming.Builder;
 import com.android.tools.r8.naming.MemberNaming;
 import com.android.tools.r8.naming.MemberNaming.FieldSignature;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
@@ -187,6 +188,22 @@
     }
   }
 
+  // We will be remapping positional debug events and collect them as MappedPositions.
+  private static class MappedPosition {
+    private final DexMethod method;
+    private final int originalLine;
+    private final Position caller;
+    private final int obfuscatedLine;
+
+    private MappedPosition(
+        DexMethod method, int originalLine, Position caller, int obfuscatedLine) {
+      this.method = method;
+      this.originalLine = originalLine;
+      this.caller = caller;
+      this.obfuscatedLine = obfuscatedLine;
+    }
+  }
+
   public static ClassNameMapper run(
       DexApplication application, NamingLens namingLens, boolean identityMapping) {
     IdentityHashMap<DexString, List<DexProgramClass>> classesOfFiles = new IdentityHashMap<>();
@@ -199,25 +216,8 @@
         continue;
       }
 
-      // Group methods by name
       IdentityHashMap<DexString, List<DexEncodedMethod>> methodsByName =
-          new IdentityHashMap<>(clazz.directMethods().length + clazz.virtualMethods().length);
-      clazz.forEachMethod(
-          method -> {
-            // Add method only if renamed or contains positions.
-            if (namingLens.lookupName(method.method) != method.method.name
-                || doesContainPositions(method)) {
-              methodsByName.compute(
-                  method.method.name,
-                  (name, methods) -> {
-                    if (methods == null) {
-                      methods = new ArrayList<>();
-                    }
-                    methods.add(method);
-                    return methods;
-                  });
-            }
-          });
+          groupMethodsByName(namingLens, clazz);
 
       // At this point we don't know if we really need to add this class to the builder.
       // It depends on whether any methods/fields are renamed or some methods contain positions.
@@ -230,24 +230,11 @@
                       DescriptorUtils.descriptorToJavaType(renamedClassName.toString()),
                       clazz.toString()));
 
-      // We do know we need to create a ClassNaming.Builder if the class itself had been renamed.
-      if (!clazz.toString().equals(renamedClassName.toString())) {
-        // Not using return value, it's registered in classNameMapperBuilder
-        onDemandClassNamingBuilder.get();
-      }
+      // If the class is renamed add it to the classNamingBuilder.
+      addClassToClassNaming(clazz, renamedClassName, onDemandClassNamingBuilder);
 
       // First transfer renamed fields to classNamingBuilder.
-      clazz.forEachField(
-          dexEncodedField -> {
-            DexField dexField = dexEncodedField.field;
-            DexString renamedName = namingLens.lookupName(dexField);
-            if (renamedName != dexField.name) {
-              FieldSignature signature =
-                  new FieldSignature(dexField.name.toString(), dexField.type.toString());
-              MemberNaming memberNaming = new MemberNaming(signature, renamedName.toString());
-              onDemandClassNamingBuilder.get().addMemberEntry(memberNaming);
-            }
-          });
+      addFieldsToClassNaming(namingLens, clazz, onDemandClassNamingBuilder);
 
       // Then process the methods.
       for (List<DexEncodedMethod> methods : methodsByName.values()) {
@@ -256,47 +243,13 @@
           // deterministic behaviour: the algorithm will assign new line numbers in this order.
           // Methods with different names can share the same line numbers, that's why they don't
           // need to be sorted.
-          methods.sort(
-              (lhs, rhs) -> {
-                // Sort by startline, then DexEncodedMethod.slowCompare.
-                // Use startLine = 0 if no debuginfo.
-                Code lhsCode = lhs.getCode();
-                Code rhsCode = rhs.getCode();
-                DexCode lhsDexCode =
-                    lhsCode == null || !lhsCode.isDexCode() ? null : lhsCode.asDexCode();
-                DexCode rhsDexCode =
-                    rhsCode == null || !rhsCode.isDexCode() ? null : rhsCode.asDexCode();
-                DexDebugInfo lhsDebugInfo = lhsDexCode == null ? null : lhsDexCode.getDebugInfo();
-                DexDebugInfo rhsDebugInfo = rhsDexCode == null ? null : rhsDexCode.getDebugInfo();
-                int lhsStartLine = lhsDebugInfo == null ? 0 : lhsDebugInfo.startLine;
-                int rhsStartLine = rhsDebugInfo == null ? 0 : rhsDebugInfo.startLine;
-                int startLineDiff = lhsStartLine - rhsStartLine;
-                if (startLineDiff != 0) return startLineDiff;
-                return DexEncodedMethod.slowCompare(lhs, rhs);
-              });
+          sortMethods(methods);
         }
 
         PositionRemapper positionRemapper =
             identityMapping ? new IdentityPositionRemapper() : new OptimizingPositionRemapper();
 
         for (DexEncodedMethod method : methods) {
-
-          // We will be remapping positional debug events and collect them as MappedPositions.
-          class MappedPosition {
-            private final DexMethod method;
-            private final int originalLine;
-            private final Position caller;
-            private final int obfuscatedLine;
-
-            private MappedPosition(
-                DexMethod method, int originalLine, Position caller, int obfuscatedLine) {
-              this.method = method;
-              this.originalLine = originalLine;
-              this.caller = caller;
-              this.obfuscatedLine = obfuscatedLine;
-            }
-          }
-
           List<MappedPosition> mappedPositions = new ArrayList<>();
 
           if (doesContainPositions(method)) {
@@ -429,6 +382,75 @@
     return classNameMapperBuilder.build();
   }
 
+  // Sort by startline, then DexEncodedMethod.slowCompare.
+  // Use startLine = 0 if no debuginfo.
+  private static void sortMethods(List<DexEncodedMethod> methods) {
+    methods.sort(
+        (lhs, rhs) -> {
+          Code lhsCode = lhs.getCode();
+          Code rhsCode = rhs.getCode();
+          DexCode lhsDexCode =
+              lhsCode == null || !lhsCode.isDexCode() ? null : lhsCode.asDexCode();
+          DexCode rhsDexCode =
+              rhsCode == null || !rhsCode.isDexCode() ? null : rhsCode.asDexCode();
+          DexDebugInfo lhsDebugInfo = lhsDexCode == null ? null : lhsDexCode.getDebugInfo();
+          DexDebugInfo rhsDebugInfo = rhsDexCode == null ? null : rhsDexCode.getDebugInfo();
+          int lhsStartLine = lhsDebugInfo == null ? 0 : lhsDebugInfo.startLine;
+          int rhsStartLine = rhsDebugInfo == null ? 0 : rhsDebugInfo.startLine;
+          int startLineDiff = lhsStartLine - rhsStartLine;
+          if (startLineDiff != 0) return startLineDiff;
+          return DexEncodedMethod.slowCompare(lhs, rhs);
+        });
+  }
+
+  @SuppressWarnings("ReturnValueIgnored")
+  private static void addClassToClassNaming(DexProgramClass clazz, DexString renamedClassName,
+      Supplier<Builder> onDemandClassNamingBuilder) {
+    // We do know we need to create a ClassNaming.Builder if the class itself had been renamed.
+    if (!clazz.toString().equals(renamedClassName.toString())) {
+      // Not using return value, it's registered in classNameMapperBuilder
+      onDemandClassNamingBuilder.get();
+    }
+  }
+
+  private static void addFieldsToClassNaming(NamingLens namingLens, DexProgramClass clazz,
+      Supplier<Builder> onDemandClassNamingBuilder) {
+    clazz.forEachField(
+        dexEncodedField -> {
+          DexField dexField = dexEncodedField.field;
+          DexString renamedName = namingLens.lookupName(dexField);
+          if (renamedName != dexField.name) {
+            FieldSignature signature =
+                new FieldSignature(dexField.name.toString(), dexField.type.toString());
+            MemberNaming memberNaming = new MemberNaming(signature, renamedName.toString());
+            onDemandClassNamingBuilder.get().addMemberEntry(memberNaming);
+          }
+        });
+  }
+
+  private static IdentityHashMap<DexString, List<DexEncodedMethod>> groupMethodsByName(
+      NamingLens namingLens, DexProgramClass clazz) {
+    IdentityHashMap<DexString, List<DexEncodedMethod>> methodsByName =
+        new IdentityHashMap<>(clazz.directMethods().length + clazz.virtualMethods().length);
+    clazz.forEachMethod(
+        method -> {
+          // Add method only if renamed or contains positions.
+          if (namingLens.lookupName(method.method) != method.method.name
+              || doesContainPositions(method)) {
+            methodsByName.compute(
+                method.method.name,
+                (name, methods) -> {
+                  if (methods == null) {
+                    methods = new ArrayList<>();
+                  }
+                  methods.add(method);
+                  return methods;
+                });
+          }
+        });
+    return methodsByName;
+  }
+
   private static boolean doesContainPositions(DexEncodedMethod method) {
     Code code = method.getCode();
     if (code == null || !code.isDexCode()) {
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 1b032e5..849377b 100644
--- a/src/main/java/com/android/tools/r8/utils/ZipUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/ZipUtils.java
@@ -10,6 +10,7 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.nio.charset.StandardCharsets;
 import java.nio.file.Path;
 import java.util.ArrayList;
 import java.util.Enumeration;
@@ -27,7 +28,7 @@
   }
 
   public static void iter(String zipFileStr, OnEntryHandler handler) throws IOException {
-    try (ZipFile zipFile = new ZipFile(zipFileStr)) {
+    try (ZipFile zipFile = new ZipFile(zipFileStr, StandardCharsets.UTF_8)) {
       final Enumeration<? extends ZipEntry> entries = zipFile.entries();
       while (entries.hasMoreElements()) {
         ZipEntry entry = entries.nextElement();
diff --git a/src/test/apiUsageSample/com/android/tools/apiusagesample/D8ApiUsageSample.java b/src/test/apiUsageSample/com/android/tools/apiusagesample/D8ApiUsageSample.java
index ad31005..2ef0123 100644
--- a/src/test/apiUsageSample/com/android/tools/apiusagesample/D8ApiUsageSample.java
+++ b/src/test/apiUsageSample/com/android/tools/apiusagesample/D8ApiUsageSample.java
@@ -22,6 +22,7 @@
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
@@ -387,7 +388,7 @@
     for (Path file : files) {
       if (isArchive(file)) {
         Origin zipOrigin = new PathOrigin(file);
-        ZipInputStream zip = new ZipInputStream(Files.newInputStream(file));
+        ZipInputStream zip = new ZipInputStream(Files.newInputStream(file), StandardCharsets.UTF_8);
         ZipEntry entry;
         while (null != (entry = zip.getNextEntry())) {
           if (isClassFile(Paths.get(entry.getName()))) {
diff --git a/src/test/apiUsageSample/com/android/tools/apiusagesample/R8ApiUsageSample.java b/src/test/apiUsageSample/com/android/tools/apiusagesample/R8ApiUsageSample.java
index 7230f55..2c2e8d7 100644
--- a/src/test/apiUsageSample/com/android/tools/apiusagesample/R8ApiUsageSample.java
+++ b/src/test/apiUsageSample/com/android/tools/apiusagesample/R8ApiUsageSample.java
@@ -22,6 +22,7 @@
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
@@ -378,7 +379,7 @@
     for (Path file : files) {
       if (isArchive(file)) {
         Origin zipOrigin = new PathOrigin(file);
-        ZipInputStream zip = new ZipInputStream(Files.newInputStream(file));
+        ZipInputStream zip = new ZipInputStream(Files.newInputStream(file), StandardCharsets.UTF_8);
         ZipEntry entry;
         while (null != (entry = zip.getNextEntry())) {
           if (isClassFile(Paths.get(entry.getName()))) {
diff --git a/src/test/java/com/android/tools/r8/CfFrontendExamplesTest.java b/src/test/java/com/android/tools/r8/CfFrontendExamplesTest.java
index 4e07849..4f749b5 100644
--- a/src/test/java/com/android/tools/r8/CfFrontendExamplesTest.java
+++ b/src/test/java/com/android/tools/r8/CfFrontendExamplesTest.java
@@ -85,6 +85,11 @@
   }
 
   @Test
+  public void testInlining() throws Exception {
+    runTest("inlining.Inlining");
+  }
+
+  @Test
   public void testInstanceVariable() throws Exception {
     runTest("instancevariable.InstanceVariable");
   }
diff --git a/src/test/java/com/android/tools/r8/D8CommandTest.java b/src/test/java/com/android/tools/r8/D8CommandTest.java
index 1b846a5..87453d0 100644
--- a/src/test/java/com/android/tools/r8/D8CommandTest.java
+++ b/src/test/java/com/android/tools/r8/D8CommandTest.java
@@ -6,13 +6,13 @@
 import static com.android.tools.r8.R8CommandTest.getOutputPath;
 import static com.android.tools.r8.ToolHelper.EXAMPLES_BUILD_DIR;
 import static com.android.tools.r8.utils.FileUtils.JAR_EXTENSION;
-import static java.nio.file.StandardOpenOption.CREATE_NEW;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertTrue;
 
 import com.android.sdklib.AndroidVersion;
+import com.android.tools.r8.D8CommandParser.OrderedClassFileResourceProvider;
 import com.android.tools.r8.ToolHelper.ProcessResult;
 import com.android.tools.r8.dex.Marker;
 import com.android.tools.r8.dex.Marker.Tool;
@@ -23,8 +23,8 @@
 import com.android.tools.r8.utils.ZipUtils;
 import com.google.common.collect.ImmutableList;
 import java.io.IOException;
+import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
-import java.nio.file.OpenOption;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.List;
@@ -263,13 +263,37 @@
         tmpClassesDir.toString());
     AndroidApp inputApp = ToolHelper.getApp(command);
     assertEquals(1, inputApp.getClasspathResourceProviders().size());
+    OrderedClassFileResourceProvider classpathProvider =
+        (OrderedClassFileResourceProvider) inputApp.getClasspathResourceProviders().get(0);
+    assertEquals(1, classpathProvider.providers.size());
     assertTrue(Files.isSameFile(tmpClassesDir,
-        ((DirectoryClassFileProvider) inputApp.getClasspathResourceProviders().get(0)).getRoot()));
+        ((DirectoryClassFileProvider) classpathProvider.providers.get(0)).getRoot()));
     assertEquals(1, inputApp.getLibraryResourceProviders().size());
     assertTrue(Files.isSameFile(tmpClassesDir,
         ((DirectoryClassFileProvider) inputApp.getLibraryResourceProviders().get(0)).getRoot()));
   }
 
+  @Test
+  public void folderClasspathMultiple() throws Throwable {
+    Path inputFile =
+        Paths.get(ToolHelper.EXAMPLES_ANDROID_N_BUILD_DIR, "interfacemethods" + JAR_EXTENSION);
+    Path tmpClassesDir1 = temp.newFolder().toPath();
+    Path tmpClassesDir2 = temp.newFolder().toPath();
+    ZipUtils.unzip(inputFile.toString(), tmpClassesDir1.toFile());
+    ZipUtils.unzip(inputFile.toString(), tmpClassesDir2.toFile());
+    D8Command command = parse("--classpath", tmpClassesDir1.toString(), "--classpath",
+        tmpClassesDir2.toString());
+    AndroidApp inputApp = ToolHelper.getApp(command);
+    assertEquals(1, inputApp.getClasspathResourceProviders().size());
+    OrderedClassFileResourceProvider classpathProvider =
+        (OrderedClassFileResourceProvider) inputApp.getClasspathResourceProviders().get(0);
+    assertEquals(2, classpathProvider.providers.size());
+    assertTrue(Files.isSameFile(tmpClassesDir1,
+        ((DirectoryClassFileProvider) classpathProvider.providers.get(0)).getRoot()));
+    assertTrue(Files.isSameFile(tmpClassesDir2,
+        ((DirectoryClassFileProvider) classpathProvider.providers.get(1)).getRoot()));
+  }
+
   @Test(expected = CompilationFailedException.class)
   public void classFolderProgram() throws Throwable {
     Path inputFile =
@@ -319,7 +343,7 @@
     Path input = Paths.get(EXAMPLES_BUILD_DIR, "arithmetic.jar");
     ProgramResourceProvider myProvider =
         ArchiveProgramResourceProvider.fromSupplier(
-            new MyOrigin(), () -> new ZipFile(input.toFile()));
+            new MyOrigin(), () -> new ZipFile(input.toFile(), StandardCharsets.UTF_8));
     D8Command command =
         D8Command.builder()
             .setProgramConsumer(DexIndexedConsumer.emptyConsumer())
@@ -464,7 +488,7 @@
             .setOutput(emptyZip, OutputMode.DexIndexed)
             .build());
     assertTrue(Files.exists(emptyZip));
-    assertEquals(0, new ZipFile(emptyZip.toFile()).size());
+    assertEquals(0, new ZipFile(emptyZip.toFile(), StandardCharsets.UTF_8).size());
   }
 
   private D8Command parse(String... args) throws CompilationFailedException {
diff --git a/src/test/java/com/android/tools/r8/OrderedClassFileResourceProviderTest.java b/src/test/java/com/android/tools/r8/OrderedClassFileResourceProviderTest.java
new file mode 100644
index 0000000..dc497c4
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/OrderedClassFileResourceProviderTest.java
@@ -0,0 +1,113 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.D8CommandParser.OrderedClassFileResourceProvider;
+import com.android.tools.r8.origin.Origin;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import java.io.InputStream;
+import java.util.Map;
+import java.util.Set;
+import org.junit.Test;
+
+public class OrderedClassFileResourceProviderTest extends TestBase {
+  class SimpleClassFileResourceProvider implements ClassFileResourceProvider {
+
+    private final Set<String> descriptors;
+    private final ProgramResource fixedProgramResource;
+
+    SimpleClassFileResourceProvider(int id, Set<String> descriptors) {
+      this.descriptors = descriptors;
+      this.fixedProgramResource = new SimpleProgramResource(id);
+    }
+
+    @Override
+    public Set<String> getClassDescriptors() {
+      return descriptors;
+    }
+
+    @Override
+    public ProgramResource getProgramResource(String descriptor) {
+      return fixedProgramResource;
+    }
+  }
+
+  class SimpleProgramResource implements ProgramResource {
+
+    private final Origin origin;
+
+    SimpleProgramResource(int id) {
+      origin = new SimpleOrigin(id);
+    }
+
+    @Override
+    public Kind getKind() {
+      return null;
+    }
+
+    @Override
+    public InputStream getByteStream() throws ResourceException {
+      return null;
+    }
+
+    @Override
+    public Set<String> getClassDescriptors() {
+      return null;
+    }
+
+    @Override
+    public Origin getOrigin() {
+      return origin;
+    }
+  }
+
+  public class SimpleOrigin extends Origin {
+
+    private final int id;
+
+    private SimpleOrigin(int index) {
+      super(root());
+      this.id = index;
+    }
+
+    int getId() {
+      return id;
+    }
+
+    @Override
+    public String part() {
+      return "Test";
+    }
+  }
+
+  @Test
+  public void test() {
+    OrderedClassFileResourceProvider.Builder builder = OrderedClassFileResourceProvider.builder();
+    builder.addClassFileResourceProvider(new SimpleClassFileResourceProvider(1, ImmutableSet.of(
+        "L/a/a/a", "L/a/a/b", "L/a/a/c"
+    )));
+    builder.addClassFileResourceProvider(new SimpleClassFileResourceProvider(2, ImmutableSet.of(
+        "L/a/a/b", "L/a/a/c", "L/a/a/d"
+        )));
+    ClassFileResourceProvider provider = builder.build();
+    assertEquals(
+        ImmutableSet.of("L/a/a/a", "L/a/a/b", "L/a/a/c", "L/a/a/d"),
+        provider.getClassDescriptors());
+
+    Map<String, Integer> expectations = ImmutableMap.of(
+        "L/a/a/a", 1,
+        "L/a/a/b", 1,
+        "L/a/a/c", 1,
+        "L/a/a/d", 2
+    );
+    expectations.forEach((descriptor, id) ->
+        assertEquals(
+            (int) id,
+            ((SimpleOrigin) provider.getProgramResource(descriptor).getOrigin()).getId()));
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/R8CommandTest.java b/src/test/java/com/android/tools/r8/R8CommandTest.java
index fbd88be..16bbae9 100644
--- a/src/test/java/com/android/tools/r8/R8CommandTest.java
+++ b/src/test/java/com/android/tools/r8/R8CommandTest.java
@@ -18,6 +18,7 @@
 import com.google.common.collect.ImmutableList;
 import java.io.IOException;
 import java.lang.reflect.Method;
+import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
@@ -459,7 +460,7 @@
             .setOutput(emptyZip, OutputMode.DexIndexed)
             .build());
     assertTrue(Files.exists(emptyZip));
-    assertEquals(0, new ZipFile(emptyZip.toFile()).size());
+    assertEquals(0, new ZipFile(emptyZip.toFile(), StandardCharsets.UTF_8).size());
   }
 
   private R8Command parse(String... args) throws CompilationFailedException {
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesTest.java b/src/test/java/com/android/tools/r8/R8RunExamplesTest.java
index 348dbf1..087012c 100644
--- a/src/test/java/com/android/tools/r8/R8RunExamplesTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunExamplesTest.java
@@ -41,6 +41,7 @@
         "filledarray.FilledArray",
         "hello.Hello",
         "ifstatements.IfStatements",
+        "inlining.Inlining",
         "instancevariable.InstanceVariable",
         "instanceofstring.InstanceofString",
         "invoke.Invoke",
diff --git a/src/test/java/com/android/tools/r8/RunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/RunExamplesAndroidOTest.java
index 2c6a72a..b0876c9 100644
--- a/src/test/java/com/android/tools/r8/RunExamplesAndroidOTest.java
+++ b/src/test/java/com/android/tools/r8/RunExamplesAndroidOTest.java
@@ -27,6 +27,7 @@
 import com.google.common.io.ByteStreams;
 import java.io.IOException;
 import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.ArrayList;
@@ -580,7 +581,7 @@
 
   protected DexInspector getMainDexInspector(Path zip)
       throws ZipException, IOException, ExecutionException {
-    try (ZipFile zipFile = new ZipFile(zip.toFile())) {
+    try (ZipFile zipFile = new ZipFile(zip.toFile(), StandardCharsets.UTF_8)) {
       try (InputStream in =
           zipFile.getInputStream(zipFile.getEntry(ToolHelper.DEFAULT_DEX_FILENAME))) {
         return new DexInspector(
diff --git a/src/test/java/com/android/tools/r8/RunExamplesAndroidPTest.java b/src/test/java/com/android/tools/r8/RunExamplesAndroidPTest.java
index 182fe80..4701ac3 100644
--- a/src/test/java/com/android/tools/r8/RunExamplesAndroidPTest.java
+++ b/src/test/java/com/android/tools/r8/RunExamplesAndroidPTest.java
@@ -25,6 +25,7 @@
 import com.google.common.io.ByteStreams;
 import java.io.IOException;
 import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.ArrayList;
@@ -256,7 +257,7 @@
 
   protected DexInspector getMainDexInspector(Path zip)
       throws ZipException, IOException, ExecutionException {
-    try (ZipFile zipFile = new ZipFile(zip.toFile())) {
+    try (ZipFile zipFile = new ZipFile(zip.toFile(), StandardCharsets.UTF_8)) {
       try (InputStream in =
           zipFile.getInputStream(zipFile.getEntry(ToolHelper.DEFAULT_DEX_FILENAME))) {
         return new DexInspector(
diff --git a/src/test/java/com/android/tools/r8/RunExamplesJava9Test.java b/src/test/java/com/android/tools/r8/RunExamplesJava9Test.java
index 284ed68..9560c1f 100644
--- a/src/test/java/com/android/tools/r8/RunExamplesJava9Test.java
+++ b/src/test/java/com/android/tools/r8/RunExamplesJava9Test.java
@@ -25,6 +25,7 @@
 import com.google.common.io.ByteStreams;
 import java.io.IOException;
 import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.ArrayList;
@@ -309,7 +310,7 @@
 
   protected DexInspector getMainDexInspector(Path zip)
       throws ZipException, IOException, ExecutionException {
-    try (ZipFile zipFile = new ZipFile(zip.toFile())) {
+    try (ZipFile zipFile = new ZipFile(zip.toFile(), StandardCharsets.UTF_8)) {
       try (InputStream in =
           zipFile.getInputStream(zipFile.getEntry(ToolHelper.DEFAULT_DEX_FILENAME))) {
         return new DexInspector(
diff --git a/src/test/java/com/android/tools/r8/compatdexbuilder/CompatDexBuilderTests.java b/src/test/java/com/android/tools/r8/compatdexbuilder/CompatDexBuilderTests.java
index e49c740..341fb67 100644
--- a/src/test/java/com/android/tools/r8/compatdexbuilder/CompatDexBuilderTests.java
+++ b/src/test/java/com/android/tools/r8/compatdexbuilder/CompatDexBuilderTests.java
@@ -15,6 +15,7 @@
 import com.android.tools.r8.ToolHelper.ArtCommandBuilder;
 import com.google.common.collect.ImmutableList;
 import java.io.IOException;
+import java.nio.charset.StandardCharsets;
 import java.nio.file.Path;
 import java.util.Enumeration;
 import java.util.HashSet;
@@ -65,7 +66,7 @@
     for (String className : CLASS_NAMES) {
       expectedNames.add(SUBDIR + "/" + className + ".class.dex");
     }
-    try (ZipFile zipFile = new ZipFile(outputZip.toFile())) {
+    try (ZipFile zipFile = new ZipFile(outputZip.toFile(), StandardCharsets.UTF_8)) {
       for (Enumeration<? extends ZipEntry> e = zipFile.entries(); e.hasMoreElements(); ) {
         ZipEntry ze = e.nextElement();
         expectedNames.remove(ze.getName());
diff --git a/src/test/java/com/android/tools/r8/compatdx/CompatDxTests.java b/src/test/java/com/android/tools/r8/compatdx/CompatDxTests.java
index c55428f..7d181bc 100644
--- a/src/test/java/com/android/tools/r8/compatdx/CompatDxTests.java
+++ b/src/test/java/com/android/tools/r8/compatdx/CompatDxTests.java
@@ -17,6 +17,7 @@
 import com.google.common.collect.ImmutableMap;
 import java.io.IOException;
 import java.net.URI;
+import java.nio.charset.StandardCharsets;
 import java.nio.file.FileSystem;
 import java.nio.file.FileSystems;
 import java.nio.file.Files;
@@ -197,8 +198,8 @@
   }
 
   private void compareArchiveFiles(Path d8File, Path dxFile) throws IOException {
-    ZipFile d8Zip = new ZipFile(d8File.toFile());
-    ZipFile dxZip = new ZipFile(dxFile.toFile());
+    ZipFile d8Zip = new ZipFile(d8File.toFile(), StandardCharsets.UTF_8);
+    ZipFile dxZip = new ZipFile(dxFile.toFile(), StandardCharsets.UTF_8);
     // TODO(zerny): This should test resource containment too once supported.
     Set<String> d8Content = d8Zip.stream().map(ZipEntry::getName).collect(Collectors.toSet());
     Set<String> dxContent = dxZip.stream().map(ZipEntry::getName).collect(Collectors.toSet());
diff --git a/src/test/java/com/android/tools/r8/naming/GenericSignatureParserTest.java b/src/test/java/com/android/tools/r8/naming/GenericSignatureParserTest.java
index 751ac83..1d1604b 100644
--- a/src/test/java/com/android/tools/r8/naming/GenericSignatureParserTest.java
+++ b/src/test/java/com/android/tools/r8/naming/GenericSignatureParserTest.java
@@ -82,7 +82,7 @@
     parseSimpleError(
         GenericSignatureParser::parseClassSignature,
         e -> assertTrue(e.getMessage().startsWith("Expected L at position 1")));
-    // TODO(sgjesse): The position 2 reported here is onr off.
+    // TODO(sgjesse): The position 2 reported here is one off.
     parseSimpleError(
         GenericSignatureParser::parseFieldSignature,
         e -> assertTrue(e.getMessage().startsWith("Expected L, [ or T at position 2")));
@@ -104,7 +104,7 @@
           fail("Succesfully parsed " + signature.substring(0, i) + " (position " + i +")");
         }
       } catch (GenericSignatureFormatError e) {
-        assertTrue("" + i + " Was: " + e.getMessage(), e.getMessage().contains("at position " + (i + 1)));
+        assertTrue(e.getMessage().contains("at position " + (i + 1)));
       }
     }
   }
diff --git a/src/test/java/com/android/tools/r8/naming/MinifierClassSignatureTest.java b/src/test/java/com/android/tools/r8/naming/MinifierClassSignatureTest.java
new file mode 100644
index 0000000..68cd36f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/MinifierClassSignatureTest.java
@@ -0,0 +1,496 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.naming;
+
+import static com.android.tools.r8.utils.DexInspectorMatchers.isPresent;
+import static com.android.tools.r8.utils.DexInspectorMatchers.isRenamed;
+import static org.hamcrest.CoreMatchers.not;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertThat;
+import static org.objectweb.asm.Opcodes.ACC_FINAL;
+import static org.objectweb.asm.Opcodes.ACC_SUPER;
+import static org.objectweb.asm.Opcodes.ACC_SYNTHETIC;
+import static org.objectweb.asm.Opcodes.ALOAD;
+import static org.objectweb.asm.Opcodes.INVOKESPECIAL;
+import static org.objectweb.asm.Opcodes.PUTFIELD;
+import static org.objectweb.asm.Opcodes.RETURN;
+import static org.objectweb.asm.Opcodes.V1_8;
+
+import com.android.tools.r8.DexIndexedConsumer;
+import com.android.tools.r8.DiagnosticsChecker;
+import com.android.tools.r8.R8Command;
+import com.android.tools.r8.StringConsumer;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.graph.invokesuper.Consumer;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import org.junit.Test;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.FieldVisitor;
+import org.objectweb.asm.MethodVisitor;
+
+public class MinifierClassSignatureTest extends TestBase {
+  /*
+
+  class Simple {
+  }
+  class Base<T> {
+  }
+  class Outer<T> {
+    class Inner {
+      class InnerInner {
+      }
+      class ExtendsInnerInner extends InnerInner {
+      }
+    }
+    class ExtendsInner extends Inner {
+    }
+  }
+
+  */
+
+  String baseSignature = "<T:Ljava/lang/Object;>Ljava/lang/Object;";
+  String outerSignature = "<T:Ljava/lang/Object;>Ljava/lang/Object;";
+  String extendsInnerSignature = "LOuter<TT;>.Inner;";
+  String extendsInnerInnerSignature = "LOuter<TT;>.Inner.InnerInner;";
+
+  private byte[] dumpSimple(String classSignature) throws Exception {
+
+    ClassWriter cw = new ClassWriter(0);
+    MethodVisitor mv;
+
+    String signature = classSignature;
+    cw.visit(V1_8, ACC_SUPER, "Simple", signature, "java/lang/Object", null);
+
+    {
+      mv = cw.visitMethod(0, "<init>", "()V", null, null);
+      mv.visitCode();
+      mv.visitVarInsn(ALOAD, 0);
+      mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+      mv.visitInsn(RETURN);
+      mv.visitMaxs(1, 1);
+      mv.visitEnd();
+    }
+    cw.visitEnd();
+
+    return cw.toByteArray();
+  }
+
+  private byte[] dumpBase(String classSignature) throws Exception {
+
+    final String javacClassSignature = baseSignature;
+    ClassWriter cw = new ClassWriter(0);
+    MethodVisitor mv;
+
+    String signature = classSignature != null ? classSignature : javacClassSignature;
+    cw.visit(V1_8, ACC_SUPER, "Base", signature, "java/lang/Object", null);
+
+    {
+      mv = cw.visitMethod(0, "<init>", "()V", null, null);
+      mv.visitCode();
+      mv.visitVarInsn(ALOAD, 0);
+      mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+      mv.visitInsn(RETURN);
+      mv.visitMaxs(1, 1);
+      mv.visitEnd();
+    }
+    cw.visitEnd();
+
+    return cw.toByteArray();
+  }
+
+
+  private byte[] dumpOuter(String classSignature) {
+
+    final String javacClassSignature = outerSignature;
+    ClassWriter cw = new ClassWriter(0);
+    MethodVisitor mv;
+
+    String signature = classSignature != null ? classSignature : javacClassSignature;
+    cw.visit(V1_8, ACC_SUPER, "Outer", signature, "java/lang/Object", null);
+
+    cw.visitInnerClass("Outer$ExtendsInner", "Outer", "ExtendsInner", 0);
+
+    cw.visitInnerClass("Outer$Inner", "Outer", "Inner", 0);
+
+    {
+      mv = cw.visitMethod(0, "<init>", "()V", null, null);
+      mv.visitCode();
+      mv.visitVarInsn(ALOAD, 0);
+      mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+      mv.visitInsn(RETURN);
+      mv.visitMaxs(1, 1);
+      mv.visitEnd();
+    }
+    cw.visitEnd();
+
+    return cw.toByteArray();
+  }
+
+  private byte[] dumpInner(String classSignature) {
+
+    final String javacClassSignature = null;
+    ClassWriter cw = new ClassWriter(0);
+    FieldVisitor fv;
+    MethodVisitor mv;
+
+    String signature = classSignature != null ? classSignature : javacClassSignature;
+    cw.visit(V1_8, ACC_SUPER, "Outer$Inner", signature, "java/lang/Object", null);
+
+    cw.visitInnerClass("Outer$Inner", "Outer", "Inner", 0);
+
+    cw.visitInnerClass("Outer$Inner$ExtendsInnerInner", "Outer$Inner", "ExtendsInnerInner", 0);
+
+    cw.visitInnerClass("Outer$Inner$InnerInner", "Outer$Inner", "InnerInner", 0);
+
+    {
+      fv = cw.visitField(ACC_FINAL + ACC_SYNTHETIC, "this$0", "LOuter;", null, null);
+      fv.visitEnd();
+    }
+    {
+      mv = cw.visitMethod(0, "<init>", "(LOuter;)V", null, null);
+      mv.visitCode();
+      mv.visitVarInsn(ALOAD, 0);
+      mv.visitVarInsn(ALOAD, 1);
+      mv.visitFieldInsn(PUTFIELD, "Outer$Inner", "this$0", "LOuter;");
+      mv.visitVarInsn(ALOAD, 0);
+      mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+      mv.visitInsn(RETURN);
+      mv.visitMaxs(2, 2);
+      mv.visitEnd();
+    }
+    cw.visitEnd();
+
+    return cw.toByteArray();
+  }
+
+  private byte[] dumpExtendsInner(String classSignature) throws Exception {
+
+    final String javacClassSignature = extendsInnerSignature;
+    ClassWriter cw = new ClassWriter(0);
+    FieldVisitor fv;
+    MethodVisitor mv;
+
+    String signature = classSignature != null ? classSignature : javacClassSignature;
+    cw.visit(V1_8, ACC_SUPER, "Outer$ExtendsInner", signature, "Outer$Inner", null);
+
+    cw.visitInnerClass("Outer$Inner", "Outer", "Inner", 0);
+
+    cw.visitInnerClass("Outer$ExtendsInner", "Outer", "ExtendsInner", 0);
+
+    {
+      fv = cw.visitField(ACC_FINAL + ACC_SYNTHETIC, "this$0", "LOuter;", null, null);
+      fv.visitEnd();
+    }
+    {
+      mv = cw.visitMethod(0, "<init>", "(LOuter;)V", null, null);
+      mv.visitCode();
+      mv.visitVarInsn(ALOAD, 0);
+      mv.visitVarInsn(ALOAD, 1);
+      mv.visitFieldInsn(PUTFIELD, "Outer$ExtendsInner", "this$0", "LOuter;");
+      mv.visitVarInsn(ALOAD, 0);
+      mv.visitVarInsn(ALOAD, 1);
+      mv.visitMethodInsn(INVOKESPECIAL, "Outer$Inner", "<init>", "(LOuter;)V", false);
+      mv.visitInsn(RETURN);
+      mv.visitMaxs(2, 2);
+      mv.visitEnd();
+    }
+    cw.visitEnd();
+
+    return cw.toByteArray();
+  }
+
+  private byte[] dumpInnerInner(String classSignature) throws Exception {
+
+    final String javacClassSignature = null;
+    ClassWriter cw = new ClassWriter(0);
+    FieldVisitor fv;
+    MethodVisitor mv;
+
+    String signature = classSignature != null ? classSignature : javacClassSignature;
+    cw.visit(V1_8, ACC_SUPER, "Outer$Inner$InnerInner", signature, "java/lang/Object", null);
+
+    cw.visitInnerClass("Outer$Inner", "Outer", "Inner", 0);
+
+    cw.visitInnerClass("Outer$Inner$InnerInner", "Outer$Inner", "InnerInner", 0);
+
+    {
+      fv = cw.visitField(ACC_FINAL + ACC_SYNTHETIC, "this$1", "LOuter$Inner;", null, null);
+      fv.visitEnd();
+    }
+    {
+      mv = cw.visitMethod(0, "<init>", "(LOuter$Inner;)V", null, null);
+      mv.visitCode();
+      mv.visitVarInsn(ALOAD, 0);
+      mv.visitVarInsn(ALOAD, 1);
+      mv.visitFieldInsn(PUTFIELD, "Outer$Inner$InnerInner", "this$1", "LOuter$Inner;");
+      mv.visitVarInsn(ALOAD, 0);
+      mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+      mv.visitInsn(RETURN);
+      mv.visitMaxs(2, 2);
+      mv.visitEnd();
+    }
+    cw.visitEnd();
+
+    return cw.toByteArray();
+  }
+
+  private byte[] dumpExtendsInnerInner(String classSignature) throws Exception {
+
+    final String javacClassSignature = extendsInnerInnerSignature;
+    ClassWriter cw = new ClassWriter(0);
+    FieldVisitor fv;
+    MethodVisitor mv;
+
+    String signature = classSignature != null ? classSignature : javacClassSignature;
+    cw.visit(V1_8, ACC_SUPER, "Outer$Inner$ExtendsInnerInner", signature, "Outer$Inner$InnerInner",
+        null);
+
+    cw.visitInnerClass("Outer$Inner", "Outer", "Inner", 0);
+
+    cw.visitInnerClass("Outer$Inner$InnerInner", "Outer$Inner", "InnerInner", 0);
+
+    cw.visitInnerClass("Outer$Inner$ExtendsInnerInner", "Outer$Inner", "ExtendsInnerInner", 0);
+
+    {
+      fv = cw.visitField(ACC_FINAL + ACC_SYNTHETIC, "this$1", "LOuter$Inner;", null, null);
+      fv.visitEnd();
+    }
+    {
+      mv = cw.visitMethod(0, "<init>", "(LOuter$Inner;)V", null, null);
+      mv.visitCode();
+      mv.visitVarInsn(ALOAD, 0);
+      mv.visitVarInsn(ALOAD, 1);
+      mv.visitFieldInsn(PUTFIELD, "Outer$Inner$ExtendsInnerInner", "this$1", "LOuter$Inner;");
+      mv.visitVarInsn(ALOAD, 0);
+      mv.visitVarInsn(ALOAD, 1);
+      mv.visitMethodInsn(INVOKESPECIAL, "Outer$Inner$InnerInner", "<init>", "(LOuter$Inner;)V",
+          false);
+      mv.visitInsn(RETURN);
+      mv.visitMaxs(2, 2);
+      mv.visitEnd();
+    }
+    cw.visitEnd();
+
+    return cw.toByteArray();
+  }
+
+  public void runTest(
+      ImmutableMap<String, String> signatures,
+      Consumer<DiagnosticsChecker> diagnostics,
+      Consumer<DexInspector> inspect)
+      throws Exception {
+    DiagnosticsChecker checker = new DiagnosticsChecker();
+    DexInspector inspector = new DexInspector(
+      ToolHelper.runR8(R8Command.builder(checker)
+          .addClassProgramData(dumpSimple(signatures.get("Simple")), Origin.unknown())
+          .addClassProgramData(dumpBase(signatures.get("Base")), Origin.unknown())
+          .addClassProgramData(dumpOuter(signatures.get("Outer")), Origin.unknown())
+          .addClassProgramData(dumpInner(signatures.get("Outer$Inner")), Origin.unknown())
+          .addClassProgramData(
+              dumpExtendsInner(signatures.get("Outer$ExtendsInner")), Origin.unknown())
+          .addClassProgramData(
+              dumpInnerInner(signatures.get("Outer$Inner$InnerInner")), Origin.unknown())
+          .addClassProgramData(
+              dumpExtendsInnerInner(
+                  signatures.get("Outer$Inner$ExtendsInnerInner")), Origin.unknown())
+          .addProguardConfiguration(ImmutableList.of(
+              "-keepattributes InnerClasses,EnclosingMethod,Signature",
+              "-keep,allowobfuscation class **"
+          ), Origin.unknown())
+          .setProgramConsumer(DexIndexedConsumer.emptyConsumer())
+          .setProguardMapConsumer(StringConsumer.emptyConsumer())
+          .build()));
+    // All classes are kept, and renamed.
+    assertThat(inspector.clazz("Simple"), isRenamed());
+    assertThat(inspector.clazz("Base"), isRenamed());
+    assertThat(inspector.clazz("Outer"), isRenamed());
+    assertThat(inspector.clazz("Outer$Inner"), isRenamed());
+    assertThat(inspector.clazz("Outer$ExtendsInner"), isRenamed());
+    assertThat(inspector.clazz("Outer$Inner$InnerInner"), isRenamed());
+    assertThat(inspector.clazz("Outer$Inner$ExtendsInnerInner"), isRenamed());
+
+    // Test that classes with have their original signature if the default was provided.
+    if (!signatures.containsKey("Simple")) {
+      assertNull(inspector.clazz("Simple").getOriginalSignatureAttribute());
+    }
+    if (!signatures.containsKey("Base")) {
+      assertEquals(baseSignature, inspector.clazz("Base").getOriginalSignatureAttribute());
+    }
+    if (!signatures.containsKey("Outer")) {
+      assertEquals(outerSignature, inspector.clazz("Outer").getOriginalSignatureAttribute());
+    }
+    if (!signatures.containsKey("Outer$Inner")) {
+      assertNull(inspector.clazz("Outer$Inner").getOriginalSignatureAttribute());
+    }
+    if (!signatures.containsKey("Outer$ExtendsInner")) {
+      assertEquals(extendsInnerSignature,
+          inspector.clazz("Outer$ExtendsInner").getOriginalSignatureAttribute());
+    }
+    if (!signatures.containsKey("Outer$Inner$InnerInner")) {
+      assertNull(inspector.clazz("Outer$Inner$InnerInner").getOriginalSignatureAttribute());
+    }
+    if (!signatures.containsKey("Outer$Inner$ExtendsInnerInner")) {
+      assertEquals(extendsInnerInnerSignature,
+          inspector.clazz("Outer$Inner$ExtendsInnerInner").getOriginalSignatureAttribute());
+    }
+
+    diagnostics.accept(checker);
+    inspect.accept(inspector);
+  }
+
+  private void testSingleClass(String name, String signature,
+      Consumer<DiagnosticsChecker> diagnostics,
+      Consumer<DexInspector> inspector)
+      throws Exception {
+    ImmutableMap<String, String> signatures = ImmutableMap.of(name, signature);
+    runTest(signatures, diagnostics, inspector);
+  }
+
+  private void isOriginUnknown(Origin origin) {
+    assertSame(Origin.unknown(), origin);
+  }
+
+  private void noWarnings(DiagnosticsChecker checker) {
+    assertEquals(0, checker.warnings.size());
+  }
+
+  private void noInspection(DexInspector inspector) {
+  }
+
+  private void noSignatureAttribute(ClassSubject clazz) {
+    assertNull(clazz.getFinalSignatureAttribute());
+    assertNull(clazz.getOriginalSignatureAttribute());
+  }
+
+  @Test
+  public void originalJavacSignatures() throws Exception {
+    // Test using the signatures generated by javac.
+    runTest(ImmutableMap.of(), this::noWarnings, this::noInspection);
+  }
+
+  @Test
+  public void classSignature_empty() throws Exception {
+    testSingleClass("Outer", "", this::noWarnings, inspector -> {
+      ClassSubject outer = inspector.clazz("Outer");
+      assertNull(outer.getFinalSignatureAttribute());
+      assertNull(outer.getOriginalSignatureAttribute());
+    });
+  }
+
+  @Test
+  public void classSignatureOuter_valid() throws Exception {
+    // class Outer<T extends Simple> extends Base<T>
+    String signature = "<T:LSimple;>LBase<TT;>;";
+    testSingleClass("Outer", signature, this::noWarnings, inspector -> {
+      ClassSubject outer = inspector.clazz("Outer");
+      ClassSubject simple = inspector.clazz("Simple");
+      ClassSubject base = inspector.clazz("Base");
+      String baseDescriptorWithoutSemicolon =
+          base.getFinalDescriptor().substring(0, base.getFinalDescriptor().length() - 1);
+      String minifiedSignature =
+          "<T:" +  simple.getFinalDescriptor() + ">" + baseDescriptorWithoutSemicolon + "<TT;>;";
+      assertEquals(minifiedSignature, outer.getFinalSignatureAttribute());
+      assertEquals(signature, outer.getOriginalSignatureAttribute());
+    });
+  }
+
+  @Test
+  public void classSignatureExtendsInner_valid() throws Exception {
+    String signature = "LOuter<TT;>.Inner;";
+    testSingleClass("Outer$ExtendsInner", signature, this::noWarnings, inspector -> {
+      ClassSubject extendsInner = inspector.clazz("Outer$ExtendsInner");
+      ClassSubject outer = inspector.clazz("Outer");
+      ClassSubject inner = inspector.clazz("Outer$Inner");
+      String outerDescriptorWithoutSemicolon =
+          outer.getFinalDescriptor().substring(0, outer.getFinalDescriptor().length() - 1);
+      String innerFinalDescriptor = inner.getFinalDescriptor();
+      String innerLastPart =
+          innerFinalDescriptor.substring(innerFinalDescriptor.indexOf("$") + 1);
+      String minifiedSignature = outerDescriptorWithoutSemicolon + "<TT;>." + innerLastPart;
+      assertEquals(minifiedSignature, extendsInner.getFinalSignatureAttribute());
+      assertEquals(signature, extendsInner.getOriginalSignatureAttribute());
+    });
+  }
+
+  @Test
+  public void classSignatureOuter_classNotFound() throws Exception {
+    String signature = "<T:LNotFound;>LNotFound;";
+    testSingleClass("Outer", signature, this::noWarnings, inspector -> {
+      assertThat(inspector.clazz("NotFound"), not(isPresent()));
+      ClassSubject outer = inspector.clazz("Outer");
+      assertEquals(signature, outer.getOriginalSignatureAttribute());
+    });
+  }
+
+  @Test
+  public void classSignatureOuter_invalid() throws Exception {
+    testSingleClass("Outer", "X", diagnostics -> {
+      assertEquals(1, diagnostics.warnings.size());
+      DiagnosticsChecker.checkDiagnostic(diagnostics.warnings.get(0), this::isOriginUnknown,
+          "Invalid class signature for class Outer", "Expected L at position 1");
+    }, inspector -> noSignatureAttribute(inspector.clazz("Outer")));
+  }
+
+  @Test
+  public void classSignatureOuter_invalidEnd() throws Exception {
+    testSingleClass("Outer", "<L", diagnostics -> {
+      assertEquals(1, diagnostics.warnings.size());
+      DiagnosticsChecker.checkDiagnostic(diagnostics.warnings.get(0), this::isOriginUnknown,
+          "Invalid class signature for class Outer", "Unexpected end of signature at position 3");
+    }, inspector -> noSignatureAttribute(inspector.clazz("Outer")));
+  }
+
+  @Test
+  public void classSignatureExtendsInner_invalid() throws Exception {
+    testSingleClass("Outer$ExtendsInner", "X", diagnostics -> {
+      assertEquals(1, diagnostics.warnings.size());
+      DiagnosticsChecker.checkDiagnostic(diagnostics.warnings.get(0), this::isOriginUnknown,
+          "Invalid class signature for class Outer$ExtendsInner", "Expected L at position 1");
+    }, inspector -> noSignatureAttribute(inspector.clazz("Outer$ExtendsInner")));
+  }
+
+  @Test
+  public void classSignatureExtendsInnerInner_invalid() throws Exception {
+    testSingleClass("Outer$Inner$ExtendsInnerInner", "X", diagnostics -> {
+      assertEquals(1, diagnostics.warnings.size());
+      DiagnosticsChecker.checkDiagnostic(diagnostics.warnings.get(0), this::isOriginUnknown,
+          "Invalid class signature for class Outer$Inner$ExtendsInnerInner",
+          "Expected L at position 1");
+    }, inspector -> noSignatureAttribute(inspector.clazz("Outer$Inner$ExtendsInnerInner")));
+  }
+
+  @Test
+  public void multipleWarnings() throws Exception {
+    runTest(ImmutableMap.of(
+        "Outer", "X",
+        "Outer$ExtendsInner", "X",
+        "Outer$Inner$ExtendsInnerInner", "X"), diagnostics -> {
+      assertEquals(3, diagnostics.warnings.size());
+    }, inspector -> {
+      noSignatureAttribute(inspector.clazz("Outer"));
+      noSignatureAttribute(inspector.clazz("Outer$ExtendsInner"));
+      noSignatureAttribute(inspector.clazz("Outer$Inner$ExtendsInnerInner"));
+    });
+  }
+  @Test
+  public void regress80029761() throws Exception {
+    String signature = "LOuter<TT;>.com/example/Inner;";
+    testSingleClass("Outer$ExtendsInner", signature, diagnostics -> {
+      assertEquals(1, diagnostics.warnings.size());
+      DiagnosticsChecker.checkDiagnostic(diagnostics.warnings.get(0), this::isOriginUnknown,
+          "Invalid class signature for class Outer$ExtendsInner", "Expected ; at position 16");
+    }, inspector -> {
+      noSignatureAttribute(inspector.clazz("Outer$ExtendsInner"));
+    });
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/NamingTestBase.java b/src/test/java/com/android/tools/r8/naming/NamingTestBase.java
index d9808c0..f311c15 100644
--- a/src/test/java/com/android/tools/r8/naming/NamingTestBase.java
+++ b/src/test/java/com/android/tools/r8/naming/NamingTestBase.java
@@ -5,9 +5,9 @@
 
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.graph.AppInfoWithSubtyping;
-import com.android.tools.r8.graph.ClassAndMemberPublicizer;
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.optimize.ClassAndMemberPublicizer;
 import com.android.tools.r8.shaking.Enqueuer;
 import com.android.tools.r8.shaking.ProguardConfiguration;
 import com.android.tools.r8.shaking.ProguardRuleParserException;
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 ace5c9b..38de25e 100644
--- a/src/test/java/com/android/tools/r8/utils/DexInspector.java
+++ b/src/test/java/com/android/tools/r8/utils/DexInspector.java
@@ -3,6 +3,8 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.utils;
 
+import static org.junit.Assert.assertTrue;
+
 import com.android.tools.r8.StringResource;
 import com.android.tools.r8.code.Const4;
 import com.android.tools.r8.code.ConstString;
@@ -53,6 +55,7 @@
 import com.android.tools.r8.code.Throw;
 import com.android.tools.r8.dex.ApplicationReader;
 import com.android.tools.r8.graph.DexAnnotation;
+import com.android.tools.r8.graph.DexAnnotationElement;
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexCode;
@@ -65,6 +68,8 @@
 import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.DexValue;
+import com.android.tools.r8.graph.DexValue.DexValueArray;
+import com.android.tools.r8.graph.DexValue.DexValueString;
 import com.android.tools.r8.graph.InnerClassAttribute;
 import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.naming.ClassNamingForNameMapper;
@@ -72,6 +77,8 @@
 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.signature.GenericSignatureAction;
+import com.android.tools.r8.naming.signature.GenericSignatureParser;
 import com.android.tools.r8.smali.SmaliBuilder;
 import com.google.common.collect.BiMap;
 import com.google.common.collect.ImmutableList;
@@ -367,6 +374,10 @@
     public abstract boolean isLocalClass();
 
     public abstract boolean isAnonymousClass();
+
+    public abstract String getOriginalSignatureAttribute();
+
+    public abstract String getFinalSignatureAttribute();
   }
 
   private class AbsentClassSubject extends ClassSubject {
@@ -448,6 +459,16 @@
     public boolean isAnonymousClass() {
       return false;
     }
+
+    @Override
+    public String getOriginalSignatureAttribute() {
+      return null;
+    }
+
+    @Override
+    public String getFinalSignatureAttribute() {
+      return null;
+    }
   }
 
   public class FoundClassSubject extends ClassSubject {
@@ -641,6 +662,98 @@
     }
 
     @Override
+    public String getOriginalSignatureAttribute() {
+      // Build the generic signature using the current mapping if any.
+      class GenericSignatureGenerater implements GenericSignatureAction<String> {
+
+        private StringBuilder signature;
+
+        public String getSignature() {
+          return signature.toString();
+        }
+
+        @Override
+        public void parsedSymbol(char symbol) {
+          signature.append(symbol);
+        }
+
+        @Override
+        public void parsedIdentifier(String identifier) {
+          signature.append(identifier);
+        }
+
+        @Override
+        public String parsedTypeName(String name) {
+          String type = name;
+          if (originalToObfuscatedMapping != null) {
+            String original = originalToObfuscatedMapping.inverse().get(name);
+            type = original != null ? original : name;
+          }
+          signature.append(type);
+          return type;
+        }
+
+        @Override
+        public String parsedInnerTypeName(String enclosingType, String name) {
+          if (originalToObfuscatedMapping != null) {
+            // The enclosingType has already been mapped if a mapping is present.
+            String minifiedEnclosing = originalToObfuscatedMapping.get(enclosingType);
+            String type = originalToObfuscatedMapping.inverse().get(minifiedEnclosing + "$" + name);
+            if (type != null) {
+              assert type.startsWith(enclosingType + "$");
+              name = type.substring(enclosingType.length() + 1);
+            }
+            signature.append(name);
+            return type;
+          } else {
+            String type = enclosingType + "$" + name;
+            signature.append(name);
+            return type;
+          }
+        }
+
+        @Override
+        public void start() {
+          signature = new StringBuilder();
+        }
+
+        @Override
+        public void stop() {
+          // nothing to do
+        }
+      }
+
+      String finalSignature = getFinalSignatureAttribute();
+      if (finalSignature == null || mapping == null) {
+        return finalSignature;
+      }
+
+      GenericSignatureGenerater rewriter = new GenericSignatureGenerater();
+      GenericSignatureParser<String> parser = new GenericSignatureParser<>(rewriter);
+      parser.parseClassSignature(finalSignature);
+      return rewriter.getSignature();
+    }
+
+    @Override
+    public String getFinalSignatureAttribute() {
+      DexAnnotation annotation = findAnnotation("dalvik.annotation.Signature");
+      if (annotation == null) {
+        return null;
+      }
+      assert annotation.annotation.elements.length == 1;
+      DexAnnotationElement element = annotation.annotation.elements[0];
+      assert element.value instanceof DexValueArray;
+      StringBuilder builder = new StringBuilder();
+      DexValueArray valueArray = (DexValueArray) element.value;
+      for (DexValue value : valueArray.getValues()) {
+        assertTrue(value instanceof DexValueString);
+        DexValueString s = (DexValueString) value;
+        builder.append(s.getValue());
+      }
+      return builder.toString();
+    }
+
+    @Override
     public String toString() {
       return dexClass.toSourceString();
     }