Version 1.2.26

Warn about and remove invalid field and method signatures
CL: https://r8-review.googlesource.com/c/r8/+/22381

Fix rewriting of inner classes not found in signatures
CL: https://r8-review.googlesource.com/c/r8/+/22380
Change-Id: I72c4c631c126824ef39c71b353fe4fecf1f38c3b
diff --git a/src/main/java/com/android/tools/r8/Version.java b/src/main/java/com/android/tools/r8/Version.java
index 6e6a120..98a6d86 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.25";
+  public static final String LABEL = "1.2.26";
 
   private Version() {
   }
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 e3ec746..01fff01 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
@@ -161,7 +161,7 @@
   }
 
   private void parseError(DexItem item, Origin origin, GenericSignatureFormatError e) {
-    StringBuilder message = new StringBuilder("Invalid class signature for ");
+    StringBuilder message = new StringBuilder("Invalid signature for ");
     if (item instanceof DexClass) {
       message.append("class ");
       message.append(((DexClass) item).getType().toSourceString());
@@ -183,12 +183,14 @@
       clazz.annotations = rewriteGenericSignatures(clazz.annotations,
           genericSignatureParser::parseClassSignature,
           e -> parseError(clazz, clazz.getOrigin(), e));
-      clazz.forEachField(field -> rewriteGenericSignatures(
-          field.annotations, genericSignatureParser::parseFieldSignature,
-          e -> parseError(field, clazz.getOrigin(), e)));
-      clazz.forEachMethod(method -> rewriteGenericSignatures(
-          method.annotations, genericSignatureParser::parseMethodSignature,
-          e -> parseError(method, clazz.getOrigin(), e)));
+      clazz.forEachField(field ->
+          field.annotations = rewriteGenericSignatures(
+              field.annotations, genericSignatureParser::parseFieldSignature,
+              e -> parseError(field, clazz.getOrigin(), e)));
+      clazz.forEachMethod(method ->
+        method.annotations = rewriteGenericSignatures(
+            method.annotations, genericSignatureParser::parseMethodSignature,
+            e -> parseError(method, clazz.getOrigin(), e)));
     }
   }
 
@@ -506,11 +508,18 @@
       String enclosingRenamedBinaryName =
           getClassBinaryNameFromDescriptor(
               renaming.getOrDefault(enclosingType, enclosingType.descriptor).toString());
-      String renamed =
-          getClassBinaryNameFromDescriptor(
-              renaming.getOrDefault(type, type.descriptor).toString());
-      String outName = renamed.substring(enclosingRenamedBinaryName.length() + 1);
-      renamedSignature.append(outName);
+      DexString renamedDescriptor = renaming.get(type);
+      if (renamedDescriptor != null) {
+        // Pick the renamed inner class from the fully renamed binary name.
+        String fullRenamedBinaryName =
+            getClassBinaryNameFromDescriptor(renamedDescriptor.toString());
+        renamedSignature.append(
+            fullRenamedBinaryName.substring(enclosingRenamedBinaryName.length() + 1));
+      } else {
+        // Did not find the class - keep the inner class name as is.
+        // TODO(110085899): Warn about missing classes in signatures?
+        renamedSignature.append(name);
+      }
       return type;
     }
 
diff --git a/src/main/java/com/android/tools/r8/naming/signature/GenericSignatureParser.java b/src/main/java/com/android/tools/r8/naming/signature/GenericSignatureParser.java
index 5b549a3..c1f6a7d 100644
--- a/src/main/java/com/android/tools/r8/naming/signature/GenericSignatureParser.java
+++ b/src/main/java/com/android/tools/r8/naming/signature/GenericSignatureParser.java
@@ -84,7 +84,7 @@
       throw e;
     } catch (Throwable t) {
       Error e = new GenericSignatureFormatError(
-          "Unknown error parsing generic signature: " + t.getMessage());
+          "Unknown error parsing class signature: " + t.getMessage());
       e.addSuppressed(t);
       throw e;
     }
@@ -100,7 +100,7 @@
       throw e;
     } catch (Throwable t) {
       Error e = new GenericSignatureFormatError(
-          "Unknown error parsing generic signature: " + t.getMessage());
+          "Unknown error parsing method signature: " + t.getMessage());
       e.addSuppressed(t);
       throw e;
     }
@@ -112,14 +112,14 @@
       setInput(signature);
       parseFieldTypeSignature();
       actions.stop();
-  } catch (GenericSignatureFormatError e) {
-    throw e;
-  } catch (Throwable t) {
-    Error e = new GenericSignatureFormatError(
-        "Unknown error parsing generic signature: " + t.getMessage());
-    e.addSuppressed(t);
-    throw e;
-  }
+    } catch (GenericSignatureFormatError e) {
+      throw e;
+    } catch (Throwable t) {
+      Error e = new GenericSignatureFormatError(
+          "Unknown error parsing field signature: " + t.getMessage());
+      e.addSuppressed(t);
+      throw e;
+    }
   }
 
   private void setInput(String input) {
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 1d1604b..957bc1d 100644
--- a/src/test/java/com/android/tools/r8/naming/GenericSignatureParserTest.java
+++ b/src/test/java/com/android/tools/r8/naming/GenericSignatureParserTest.java
@@ -350,7 +350,8 @@
     forMethodSignatures(this::parseMethodSignature);
   }
 
-  private void failingParseAction(Consumer<GenericSignatureParser<String>> parse)
+  private void failingParseAction(
+      Consumer<GenericSignatureParser<String>> parse, String errorMessageType)
       throws Exception {
     class ThrowsInParserActionBase<E extends Error> extends ReGenerateGenericSignatureRewriter {
       protected Supplier<? extends E> exceptionSupplier;
@@ -452,7 +453,8 @@
           assertEquals("ERROR", e.getMessage());
         } else {
           plainErrorCount++;
-          assertEquals("Unknown error parsing generic signature: ERROR", e.getMessage());
+          assertEquals("Unknown error parsing "
+              + errorMessageType + " signature: ERROR", e.getMessage());
         }
       }
     }
@@ -463,10 +465,10 @@
   public void failingParseAction() throws Exception {
     // These signatures hits all action callbacks.
     failingParseAction(parser -> parser.parseClassSignature(
-        "<U:Ljava/lang/Object;>LOuter<TT;>.Inner;Ljava/util/List<TU;>;"));
+        "<U:Ljava/lang/Object;>LOuter<TT;>.Inner;Ljava/util/List<TU;>;"), "class");
     failingParseAction(
-        parser -> parser.parseFieldSignature("LOuter$InnerInterface<TU;>.Inner;"));
+        parser -> parser.parseFieldSignature("LOuter$InnerInterface<TU;>.Inner;"), "field");
     failingParseAction(
-        parser -> parser.parseMethodSignature("(LOuter$InnerInterface<TU;>.Inner;)V"));
+        parser -> parser.parseMethodSignature("(LOuter$InnerInterface<TU;>.Inner;)V"), "method");
   }
 }
diff --git a/src/test/java/com/android/tools/r8/naming/MinifierClassSignatureTest.java b/src/test/java/com/android/tools/r8/naming/MinifierClassSignatureTest.java
index 68cd36f..b9ff7ea 100644
--- a/src/test/java/com/android/tools/r8/naming/MinifierClassSignatureTest.java
+++ b/src/test/java/com/android/tools/r8/naming/MinifierClassSignatureTest.java
@@ -424,7 +424,7 @@
 
   @Test
   public void classSignatureOuter_classNotFound() throws Exception {
-    String signature = "<T:LNotFound;>LNotFound;";
+    String signature = "<T:LNotFound;>LAlsoNotFound;";
     testSingleClass("Outer", signature, this::noWarnings, inspector -> {
       assertThat(inspector.clazz("NotFound"), not(isPresent()));
       ClassSubject outer = inspector.clazz("Outer");
@@ -433,11 +433,51 @@
   }
 
   @Test
+  public void classSignatureExtendsInner_innerClassNotFound() throws Exception {
+    String signature = "LOuter<TT;>.NotFound;";
+    testSingleClass("Outer$ExtendsInner", signature, this::noWarnings, inspector -> {
+      assertThat(inspector.clazz("NotFound"), not(isPresent()));
+      ClassSubject outer = inspector.clazz("Outer$ExtendsInner");
+      assertEquals(signature, outer.getOriginalSignatureAttribute());
+    });
+  }
+
+  @Test
+  public void classSignatureExtendsInner_outerAndInnerClassNotFound() throws Exception {
+    String signature = "LNotFound<TT;>.AlsoNotFound;";
+    testSingleClass("Outer$ExtendsInner", signature, this::noWarnings, inspector -> {
+      assertThat(inspector.clazz("NotFound"), not(isPresent()));
+      ClassSubject outer = inspector.clazz("Outer$ExtendsInner");
+      assertEquals(signature, outer.getOriginalSignatureAttribute());
+    });
+  }
+
+  @Test
+  public void classSignatureExtendsInner_nestedInnerClassNotFound() throws Exception {
+    String signature = "LOuter<TT;>.Inner.NotFound;";
+    testSingleClass("Outer$ExtendsInner", signature, this::noWarnings, inspector -> {
+      assertThat(inspector.clazz("NotFound"), not(isPresent()));
+      ClassSubject outer = inspector.clazz("Outer$ExtendsInner");
+      assertEquals(signature, outer.getOriginalSignatureAttribute());
+    });
+  }
+
+  @Test
+  public void classSignatureExtendsInner_multipleMestedInnerClassesNotFound() throws Exception {
+    String signature = "LOuter<TT;>.NotFound.AlsoNotFound;";
+    testSingleClass("Outer$ExtendsInner", signature, this::noWarnings, inspector -> {
+      assertThat(inspector.clazz("NotFound"), not(isPresent()));
+      ClassSubject outer = inspector.clazz("Outer$ExtendsInner");
+      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");
+          "Invalid signature for class Outer", "Expected L at position 1");
     }, inspector -> noSignatureAttribute(inspector.clazz("Outer")));
   }
 
@@ -446,7 +486,7 @@
     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");
+          "Invalid signature for class Outer", "Unexpected end of signature at position 3");
     }, inspector -> noSignatureAttribute(inspector.clazz("Outer")));
   }
 
@@ -455,7 +495,7 @@
     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");
+          "Invalid signature for class Outer$ExtendsInner", "Expected L at position 1");
     }, inspector -> noSignatureAttribute(inspector.clazz("Outer$ExtendsInner")));
   }
 
@@ -464,7 +504,7 @@
     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",
+          "Invalid signature for class Outer$Inner$ExtendsInnerInner",
           "Expected L at position 1");
     }, inspector -> noSignatureAttribute(inspector.clazz("Outer$Inner$ExtendsInnerInner")));
   }
@@ -488,7 +528,7 @@
     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");
+          "Invalid 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/MinifierFieldSignatureTest.java b/src/test/java/com/android/tools/r8/naming/MinifierFieldSignatureTest.java
new file mode 100644
index 0000000..e48ce56
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/MinifierFieldSignatureTest.java
@@ -0,0 +1,289 @@
+// 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_PUBLIC;
+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.android.tools.r8.utils.DexInspector.FieldSubject;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import java.util.Map;
+import org.junit.Test;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.FieldVisitor;
+import org.objectweb.asm.MethodVisitor;
+
+
+public class MinifierFieldSignatureTest extends TestBase {
+  /*
+
+  class Fields<X extends String> {
+    class Inner {
+    }
+    public X anX;
+    public X[] anArrayOfX;
+    public Fields<X> aFieldsOfX;
+    public Fields<X>.Inner aFieldsOfXInner;
+  }
+
+  */
+
+  private String anXSignature = "TX;";
+  private String anArrayOfXSignature = "[TX;";
+  private String aFieldsOfXSignature = "LFields<TX;>;";
+  private String aFieldsOfXInnerSignature = "LFields<TX;>.Inner;";
+
+  public byte[] dumpFields(Map<String, String> signatures) throws Exception {
+
+    ClassWriter cw = new ClassWriter(0);
+    FieldVisitor fv;
+    MethodVisitor mv;
+    String signature;
+
+    cw.visit(V1_8, ACC_SUPER, "Fields", "<X:Ljava/lang/String;>Ljava/lang/Object;",
+        "java/lang/Object", null);
+
+    cw.visitInnerClass("Fields$Inner", "Fields", "Inner", 0);
+
+    {
+      signature = signatures.get("anX");
+      signature = signature == null ? anXSignature : signature;
+      fv = cw.visitField(ACC_PUBLIC, "anX", "Ljava/lang/String;", signature, null);
+      fv.visitEnd();
+    }
+    {
+      signature = signatures.get("anArrayOfX");
+      signature = signature == null ? anArrayOfXSignature : signature;
+      fv = cw.visitField(
+          ACC_PUBLIC, "anArrayOfX", "[Ljava/lang/String;", signature, null);
+      fv.visitEnd();
+    }
+    {
+      signature = signatures.get("aFieldsOfX");
+      signature = signature == null ? aFieldsOfXSignature : signature;
+      fv = cw.visitField(ACC_PUBLIC, "aFieldsOfX", "LFields;", signature, null);
+      fv.visitEnd();
+    }
+    {
+      signature = signatures.get("aFieldsOfXInner");
+      signature = signature == null ? aFieldsOfXInnerSignature : signature;
+      fv = cw.visitField(
+          ACC_PUBLIC, "aFieldsOfXInner", "LFields$Inner;", signature, null);
+      fv.visitEnd();
+    }
+    {
+      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();
+  }
+
+  public byte[] dumpInner() throws Exception {
+
+    ClassWriter cw = new ClassWriter(0);
+    FieldVisitor fv;
+    MethodVisitor mv;
+
+    cw.visit(V1_8, ACC_SUPER, "Fields$Inner", null, "java/lang/Object", null);
+
+    cw.visitInnerClass("Fields$Inner", "Fields", "Inner", 0);
+
+    {
+      fv = cw.visitField(ACC_FINAL + ACC_SYNTHETIC, "this$0", "LFields;", null, null);
+      fv.visitEnd();
+    }
+    {
+      mv = cw.visitMethod(0, "<init>", "(LFields;)V", null, null);
+      mv.visitCode();
+      mv.visitVarInsn(ALOAD, 0);
+      mv.visitVarInsn(ALOAD, 1);
+      mv.visitFieldInsn(PUTFIELD, "Fields$Inner", "this$0", "LFields;");
+      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 FieldSubject lookupAnX(DexInspector inspector) {
+    ClassSubject clazz = inspector.clazz("Fields");
+    return clazz.field("java.lang.String", "anX");
+  }
+
+  private FieldSubject lookupAnArrayOfX(DexInspector inspector) {
+    ClassSubject clazz = inspector.clazz("Fields");
+    return clazz.field("java.lang.String[]", "anArrayOfX");
+  }
+
+  private FieldSubject lookupAFieldsOfX(DexInspector inspector) {
+    ClassSubject clazz = inspector.clazz("Fields");
+    return clazz.field("Fields", "aFieldsOfX");
+  }
+
+  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(dumpFields(signatures), Origin.unknown())
+          .addClassProgramData(dumpInner(), 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.
+    ClassSubject clazz = inspector.clazz("Fields");
+    assertThat(clazz, isRenamed());
+    assertThat(inspector.clazz("Fields$Inner"), isRenamed());
+
+    FieldSubject anX = lookupAnX(inspector);
+    FieldSubject anArrayOfX = lookupAnArrayOfX(inspector);
+    FieldSubject aFieldsOfX =lookupAFieldsOfX(inspector);
+    FieldSubject aFieldsOfXInner = clazz.field("Fields$Inner", "aFieldsOfXInner");
+
+    // Check that all fields have been renamed
+    assertThat(anX, isRenamed());
+    assertThat(anArrayOfX, isRenamed());
+    assertThat(aFieldsOfX, isRenamed());
+    assertThat(aFieldsOfXInner, isRenamed());
+
+    //System.out.println(generic.getFinalSignatureAttribute());
+    //System.out.println(generic.getOriginalSignatureAttribute());
+
+    // Test that methods have their original signature if the default was provided.
+    if (!signatures.containsKey("anX")) {
+      assertEquals(anXSignature, anX.getOriginalSignatureAttribute());
+    }
+    if (!signatures.containsKey("anArrayOfX")) {
+      assertEquals(anArrayOfXSignature, anArrayOfX.getOriginalSignatureAttribute());
+    }
+    if (!signatures.containsKey("aFieldsOfX")) {
+      assertEquals(
+          aFieldsOfXSignature, aFieldsOfX.getOriginalSignatureAttribute());
+    }
+    if (!signatures.containsKey("aFieldsOfXInner")) {
+      assertEquals(
+          aFieldsOfXInnerSignature, aFieldsOfXInner.getOriginalSignatureAttribute());
+    }
+
+    diagnostics.accept(checker);
+    inspect.accept(inspector);
+  }
+
+  private void testSingleField(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(FieldSubject field) {
+    assertThat(field, isPresent());
+    assertNull(field.getFinalSignatureAttribute());
+    assertNull(field.getOriginalSignatureAttribute());
+  }
+
+  @Test
+  public void originalJavacSignatures() throws Exception {
+    // Test using the signatures generated by javac.
+    runTest(ImmutableMap.of(), this::noWarnings, this::noInspection);
+  }
+
+  @Test
+  public void signatureEmpty() throws Exception {
+    testSingleField("anX", "", this::noWarnings,
+        inspector -> noSignatureAttribute(lookupAnX(inspector)));
+  }
+
+  @Test
+  public void signatureInvalid() throws Exception {
+    testSingleField("anX", "X", diagnostics -> {
+      assertEquals(1, diagnostics.warnings.size());
+      // TODO(sgjesse): The position 2 reported here is one off.
+      DiagnosticsChecker.checkDiagnostic(diagnostics.warnings.get(0), this::isOriginUnknown,
+          "Invalid signature for field",
+          "java.lang.String Fields.anX",
+          "Expected L, [ or T at position 2");
+    }, inspector -> noSignatureAttribute(lookupAnX(inspector)));
+  }
+
+  @Test
+  public void classNotFound() throws Exception {
+    String signature = "LNotFound<TX;>.InnerNotFound.InnerAlsoNotFound;";
+    testSingleField("anX", signature, this::noWarnings, inspector -> {
+      assertThat(inspector.clazz("NotFound"), not(isPresent()));
+      assertEquals(signature, lookupAnX(inspector).getOriginalSignatureAttribute());
+    });
+  }
+
+  @Test
+  public void multipleWarnings() throws Exception {
+    runTest(ImmutableMap.of(
+        "anX", "X",
+        "anArrayOfX", "X",
+        "aFieldsOfX", "X"
+    ), diagnostics -> {
+      assertEquals(3, diagnostics.warnings.size());
+    }, inspector -> {
+      noSignatureAttribute(lookupAnX(inspector));
+      noSignatureAttribute(lookupAnArrayOfX(inspector));
+      noSignatureAttribute(lookupAFieldsOfX(inspector));
+    });
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/MinifierMethodSignatureTest.java b/src/test/java/com/android/tools/r8/naming/MinifierMethodSignatureTest.java
new file mode 100644
index 0000000..41750ae
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/MinifierMethodSignatureTest.java
@@ -0,0 +1,314 @@
+// 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_PUBLIC;
+import static org.objectweb.asm.Opcodes.ACC_STATIC;
+import static org.objectweb.asm.Opcodes.ACC_SUPER;
+import static org.objectweb.asm.Opcodes.ACC_SYNTHETIC;
+import static org.objectweb.asm.Opcodes.ACONST_NULL;
+import static org.objectweb.asm.Opcodes.ALOAD;
+import static org.objectweb.asm.Opcodes.ARETURN;
+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.android.tools.r8.utils.DexInspector.MethodSubject;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import java.util.Map;
+import org.junit.Test;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.FieldVisitor;
+import org.objectweb.asm.MethodVisitor;
+
+public class MinifierMethodSignatureTest extends TestBase {
+  /*
+
+  class Methods<X extends Throwable> {
+    class Inner {
+    }
+    public static <T extends Throwable> T generic(T a, Methods<T>.Inner b) { return null; }
+    public Methods<X>.Inner parameterizedReturn() { return null; }
+    public void parameterizedArguments(X a, Methods<X>.Inner b) { }
+    public void parametrizedThrows() throws X { }
+  }
+
+  */
+
+  private String genericSignature = "<T:Ljava/lang/Throwable;>(TT;LMethods<TT;>.Inner;)TT;";
+  private String parameterizedReturnSignature = "()LMethods<TX;>.Inner;";
+  private String parameterizedArgumentsSignature = "(TX;LMethods<TX;>.Inner;)V";
+  private String parametrizedThrowsSignature = "()V^TX;";
+
+  private byte[] dumpMethods(Map<String, String> signatures) throws Exception {
+
+    ClassWriter cw = new ClassWriter(0);
+    MethodVisitor mv;
+    String signature;
+
+    cw.visit(V1_8, ACC_SUPER, "Methods", "<X:Ljava/lang/Throwable;>Ljava/lang/Object;",
+        "java/lang/Object", null);
+
+    cw.visitInnerClass("Methods$Inner", "Methods", "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();
+    }
+    {
+      signature = signatures.get("generic");
+      signature = signature == null ? genericSignature : signature;
+      mv = cw.visitMethod(ACC_PUBLIC + ACC_STATIC, "generic",
+          "(Ljava/lang/Throwable;LMethods$Inner;)Ljava/lang/Throwable;",
+          signature, null);
+      mv.visitCode();
+      mv.visitInsn(ACONST_NULL);
+      mv.visitInsn(ARETURN);
+      mv.visitMaxs(1, 2);
+      mv.visitEnd();
+    }
+    {
+      signature = signatures.get("parameterizedReturn");
+      signature = signature == null ? parameterizedReturnSignature : signature;
+      mv = cw.visitMethod(ACC_PUBLIC, "parameterizedReturn", "()LMethods$Inner;",
+          signature, null);
+      mv.visitCode();
+      mv.visitInsn(ACONST_NULL);
+      mv.visitInsn(ARETURN);
+      mv.visitMaxs(1, 1);
+      mv.visitEnd();
+    }
+    {
+      signature = signatures.get("parameterizedArguments");
+      signature = signature == null ? parameterizedArgumentsSignature : signature;
+      mv = cw.visitMethod(ACC_PUBLIC, "parameterizedArguments",
+          "(Ljava/lang/Throwable;LMethods$Inner;)V", signature, null);
+      mv.visitCode();
+      mv.visitInsn(RETURN);
+      mv.visitMaxs(0, 3);
+      mv.visitEnd();
+    }
+    {
+      signature = signatures.get("parametrizedThrows");
+      signature = signature == null ? parametrizedThrowsSignature : signature;
+      mv = cw.visitMethod(ACC_PUBLIC, "parametrizedThrows", "()V", signature,
+          new String[] { "java/lang/Throwable" });
+      mv.visitCode();
+      mv.visitInsn(RETURN);
+      mv.visitMaxs(0, 1);
+      mv.visitEnd();
+    }
+    cw.visitEnd();
+
+    return cw.toByteArray();
+  }
+
+  private byte[] dumpInner() throws Exception {
+
+    ClassWriter cw = new ClassWriter(0);
+    FieldVisitor fv;
+    MethodVisitor mv;
+
+    cw.visit(V1_8, ACC_SUPER, "Methods$Inner", null, "java/lang/Object", null);
+
+    cw.visitInnerClass("Methods$Inner", "Methods", "Inner", 0);
+
+    {
+      fv = cw.visitField(ACC_FINAL + ACC_SYNTHETIC, "this$0", "LMethods;", null, null);
+      fv.visitEnd();
+    }
+    {
+      mv = cw.visitMethod(0, "<init>", "(LMethods;)V", null, null);
+      mv.visitCode();
+      mv.visitVarInsn(ALOAD, 0);
+      mv.visitVarInsn(ALOAD, 1);
+      mv.visitFieldInsn(PUTFIELD, "Methods$Inner", "this$0", "LMethods;");
+      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 MethodSubject lookupGeneric(DexInspector inspector) {
+    ClassSubject clazz = inspector.clazz("Methods");
+    return clazz.method(
+        "java.lang.Throwable", "generic", ImmutableList.of("java.lang.Throwable", "Methods$Inner"));
+  }
+
+  private MethodSubject lookupParameterizedReturn(DexInspector inspector) {
+    ClassSubject clazz = inspector.clazz("Methods");
+    return clazz.method(
+        "Methods$Inner", "parameterizedReturn", ImmutableList.of());
+  }
+
+  private MethodSubject lookupParameterizedArguments(DexInspector inspector) {
+    ClassSubject clazz = inspector.clazz("Methods");
+    return clazz.method(
+        "void", "parameterizedArguments", ImmutableList.of("java.lang.Throwable", "Methods$Inner"));
+  }
+
+  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(dumpMethods(signatures), Origin.unknown())
+          .addClassProgramData(dumpInner(), 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.
+    ClassSubject clazz = inspector.clazz("Methods");
+    assertThat(clazz, isRenamed());
+    assertThat(inspector.clazz("Methods$Inner"), isRenamed());
+
+    MethodSubject generic = lookupGeneric(inspector);
+    MethodSubject parameterizedReturn = lookupParameterizedReturn(inspector);
+    MethodSubject parameterizedArguments = lookupParameterizedArguments(inspector);
+    MethodSubject parametrizedThrows =
+        clazz.method("void", "parametrizedThrows", ImmutableList.of());
+
+    // Check that all methods have been renamed
+    assertThat(generic, isRenamed());
+    assertThat(parameterizedReturn, isRenamed());
+    assertThat(parameterizedArguments, isRenamed());
+    assertThat(parametrizedThrows, isRenamed());
+
+    // Test that methods have their original signature if the default was provided.
+    if (!signatures.containsKey("generic")) {
+      assertEquals(genericSignature, generic.getOriginalSignatureAttribute());
+    }
+    if (!signatures.containsKey("parameterizedReturn")) {
+      assertEquals(
+          parameterizedReturnSignature, parameterizedReturn.getOriginalSignatureAttribute());
+    }
+    if (!signatures.containsKey("parameterizedArguments")) {
+      assertEquals(
+          parameterizedArgumentsSignature, parameterizedArguments.getOriginalSignatureAttribute());
+    }
+    if (!signatures.containsKey("parametrizedThrows")) {
+      assertEquals(
+          parametrizedThrowsSignature, parametrizedThrows.getOriginalSignatureAttribute());
+    }
+
+    diagnostics.accept(checker);
+    inspect.accept(inspector);
+  }
+
+  private void testSingleMethod(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(MethodSubject method) {
+    assertThat(method, isPresent());
+    assertNull(method.getFinalSignatureAttribute());
+    assertNull(method.getOriginalSignatureAttribute());
+  }
+
+  @Test
+  public void originalJavacSignatures() throws Exception {
+    // Test using the signatures generated by javac.
+    runTest(ImmutableMap.of(), this::noWarnings, this::noInspection);
+  }
+
+  @Test
+  public void signatureEmpty() throws Exception {
+    testSingleMethod("generic", "", this::noWarnings, inspector -> {
+      noSignatureAttribute(lookupGeneric(inspector));
+    });
+  }
+
+  @Test
+  public void signatureInvalid() throws Exception {
+    testSingleMethod("generic", "X", diagnostics -> {
+      assertEquals(1, diagnostics.warnings.size());
+      DiagnosticsChecker.checkDiagnostic(diagnostics.warnings.get(0), this::isOriginUnknown,
+          "Invalid signature for method",
+          "java.lang.Throwable Methods.generic(java.lang.Throwable, Methods$Inner)",
+          "Expected ( at position 1");
+    }, inspector -> noSignatureAttribute(lookupGeneric(inspector)));
+  }
+
+  @Test
+  public void classNotFound() throws Exception {
+    String signature = "<T:LNotFound;>(TT;LAlsoNotFound<TT;>.InnerNotFound.InnerAlsoNotFound;)TT;";
+    testSingleMethod("generic", signature, this::noWarnings,
+        inspector -> {
+          ClassSubject methods = inspector.clazz("Methods");
+          MethodSubject method =
+              methods.method("java.lang.Throwable", "generic",
+                  ImmutableList.of("java.lang.Throwable", "Methods$Inner"));
+          assertThat(inspector.clazz("NotFound"), not(isPresent()));
+          assertEquals(signature, method.getOriginalSignatureAttribute());
+        });
+  }
+
+  @Test
+  public void multipleWarnings() throws Exception {
+    runTest(ImmutableMap.of(
+        "generic", "X",
+        "parameterizedReturn", "X",
+        "parameterizedArguments", "X"
+    ), diagnostics -> {
+      assertEquals(3, diagnostics.warnings.size());
+    }, inspector -> {
+      noSignatureAttribute(lookupGeneric(inspector));
+      noSignatureAttribute(lookupParameterizedReturn(inspector));
+      noSignatureAttribute(lookupParameterizedArguments(inspector));
+    });
+  }
+}
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 e7e882a..fd32ec7 100644
--- a/src/test/java/com/android/tools/r8/utils/DexInspector.java
+++ b/src/test/java/com/android/tools/r8/utils/DexInspector.java
@@ -886,7 +886,31 @@
     @Override
     public MethodSignature getOriginalSignature() {
       MethodSignature signature = getFinalSignature();
-      MemberNaming memberNaming = clazz.naming != null ? clazz.naming.lookup(signature) : null;
+      if (clazz.naming == null) {
+        return signature;
+      }
+
+      // Map the parameters and return type to original names. This is needed as the in the
+      // Proguard map the names on the left side are the original names. E.g.
+      //
+      //   X -> a
+      //     X method(X) -> a
+      //
+      // whereas the final signature is for X.a is "a (a)"
+      String[] OriginalParameters = new String[signature.parameters.length];
+      for (int i = 0; i < OriginalParameters.length; i++) {
+        String obfuscated = signature.parameters[i];
+        String original = originalToObfuscatedMapping.inverse().get(obfuscated);
+        OriginalParameters[i] = original != null ? original : obfuscated;
+      }
+      String obfuscatedReturnType = signature.type;
+      String originalReturnType = originalToObfuscatedMapping.inverse().get(obfuscatedReturnType);
+      String returnType = originalReturnType != null ? originalReturnType : obfuscatedReturnType;
+
+      MethodSignature lookupSignature =
+          new MethodSignature(signature.name, returnType, OriginalParameters);
+
+      MemberNaming memberNaming = clazz.naming.lookup(lookupSignature);
       return memberNaming != null
           ? (MethodSignature) memberNaming.getOriginalSignature()
           : signature;
@@ -1035,7 +1059,24 @@
     @Override
     public FieldSignature getOriginalSignature() {
       FieldSignature signature = getFinalSignature();
-      MemberNaming memberNaming = clazz.naming != null ? clazz.naming.lookup(signature) : null;
+      if (clazz.naming == null) {
+        return signature;
+      }
+
+      // Map the type to the original name. This is needed as the in the Proguard map the
+      // names on the left side are the original names. E.g.
+      //
+      //   X -> a
+      //     X field -> a
+      //
+      // whereas the final signature is for X.a is "a a"
+      String obfuscatedType = signature.type;
+      String originalType = originalToObfuscatedMapping.inverse().get(obfuscatedType);
+      String fieldType = originalType != null ? originalType : obfuscatedType;
+
+      FieldSignature lookupSignature = new FieldSignature(signature.name, fieldType);
+
+      MemberNaming memberNaming = clazz.naming.lookup(lookupSignature);
       return memberNaming != null
           ? (FieldSignature) memberNaming.getOriginalSignature()
           : signature;