Merge "Restrict vertical class merging"
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 043af39..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)));
     }
   }
 
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/main/java/com/android/tools/r8/optimize/VisibilityBridgeRemover.java b/src/main/java/com/android/tools/r8/optimize/VisibilityBridgeRemover.java
index 5847a80..ebf110e 100644
--- a/src/main/java/com/android/tools/r8/optimize/VisibilityBridgeRemover.java
+++ b/src/main/java/com/android/tools/r8/optimize/VisibilityBridgeRemover.java
@@ -9,6 +9,7 @@
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.logging.Log;
 import com.android.tools.r8.optimize.InvokeSingleTargetExtractor.InvokeKind;
 import com.google.common.collect.Sets;
@@ -28,7 +29,8 @@
   }
 
   private void identifyBridgeMethod(DexEncodedMethod method) {
-    if (method.accessFlags.isBridge()) {
+    MethodAccessFlags accessFlags = method.accessFlags;
+    if (accessFlags.isBridge() && !accessFlags.isAbstract()) {
       InvokeSingleTargetExtractor targetExtractor = new InvokeSingleTargetExtractor();
       method.getCode().registerCodeReferences(targetExtractor);
       DexMethod target = targetExtractor.getTarget();
@@ -36,11 +38,11 @@
       // javac-generated visibility forward bridge method has same descriptor (name, signature and
       // return type).
       if (target != null && target.hasSameProtoAndName(method.method)) {
-        assert !method.accessFlags.isPrivate() && !method.accessFlags.isConstructor();
+        assert !accessFlags.isPrivate() && !accessFlags.isConstructor();
         if (kind == InvokeKind.SUPER) {
           // This is a visibility forward, so check for the direct target.
-          DexEncodedMethod targetMethod
-              = appInfo.resolveMethod(target.getHolder(), target).asSingleTarget();
+          DexEncodedMethod targetMethod =
+              appInfo.resolveMethod(target.getHolder(), target).asSingleTarget();
           if (targetMethod != null && targetMethod.accessFlags.isPublic()) {
             if (Log.ENABLED) {
               Log.info(getClass(), "Removing visibility forwarding %s -> %s", method.method,
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index 000076e..d5d0558 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -807,7 +807,9 @@
 
   public static R8Command.Builder prepareR8CommandBuilder(
       AndroidApp app, ProgramConsumer programConsumer) {
-    return R8Command.builder(app).setProgramConsumer(programConsumer);
+    return R8Command.builder(app)
+        .setProgramConsumer(programConsumer)
+        .setProguardMapConsumer(StringConsumer.emptyConsumer());
   }
 
   public static AndroidApp runR8(AndroidApp app) throws IOException {
@@ -1413,6 +1415,11 @@
     return result.stdout;
   }
 
+  public static ProcessResult runProguardRaw(
+      Path inJar, Path outJar, Path lib, Path config, Path map) throws IOException {
+    return runProguardRaw(getProguardScript(), inJar, outJar, lib, ImmutableList.of(config), map);
+  }
+
   public static ProcessResult runProguardRaw(Path inJar, Path outJar, List<Path> config, Path map)
       throws IOException {
     return runProguardRaw(getProguardScript(), inJar, outJar, config, map);
diff --git a/src/test/java/com/android/tools/r8/bridgeremoval/EmptyBridgeTest.java b/src/test/java/com/android/tools/r8/bridgeremoval/EmptyBridgeTest.java
new file mode 100644
index 0000000..160ad59
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/bridgeremoval/EmptyBridgeTest.java
@@ -0,0 +1,63 @@
+// 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.bridgeremoval;
+
+import static com.android.tools.r8.utils.DexInspectorMatchers.isPresent;
+import static org.junit.Assert.assertThat;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.jasmin.JasminBuilder;
+import com.android.tools.r8.jasmin.JasminBuilder.ClassBuilder;
+import com.android.tools.r8.utils.AndroidApp;
+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 org.junit.Test;
+
+public class EmptyBridgeTest extends TestBase {
+
+  @Test
+  public void test() throws Exception {
+    JasminBuilder jasminBuilder = new JasminBuilder();
+
+    ClassBuilder abs = jasminBuilder.addClass("Abs");
+    abs.setAccess("public abstract");
+    abs.addMethod("public bridge abstract", "emptyBridge", ImmutableList.of(), "V");
+
+    ClassBuilder cls = jasminBuilder.addClass("Main");
+    cls.addVirtualMethod("bogus", ImmutableList.of(), "V",
+        ".limit stack 3",
+        ".limit locals 2",
+        "getstatic java/lang/System/out Ljava/io/PrintStream;",
+        "ldc \"foo\"",
+        "invokevirtual java/io/PrintStream/print(Ljava/lang/String;)V",
+        "return");
+    cls.addMainMethod(
+        ".limit stack 4",
+        ".limit locals 2",
+        "new " + cls.name,
+        "dup",
+        "invokespecial " + cls.name + "/<init>()V",
+        "astore_0",
+        "aload_0",
+        "invokevirtual " + cls.name + "/bogus()V",
+        "return");
+
+    String absClassName = abs.name;
+    String mainClassName = cls.name;
+    String proguardConfig =
+        "-allowaccessmodification" + System.lineSeparator()
+        + "-keep class " + mainClassName + "{ *; }" + System.lineSeparator()
+            + "-keep class " + absClassName + "{ *; }";
+    AndroidApp processedApp = compileWithR8(jasminBuilder.build(), proguardConfig);
+
+    DexInspector inspector = new DexInspector(processedApp);
+    ClassSubject classSubject = inspector.clazz(absClassName);
+    assertThat(classSubject, isPresent());
+    MethodSubject methodSubject = classSubject.method("void", "emptyBridge", ImmutableList.of());
+    assertThat(methodSubject, isPresent());
+  }
+
+}
diff --git a/src/test/java/com/android/tools/r8/graph/InvokeSpecialTest.java b/src/test/java/com/android/tools/r8/graph/InvokeSpecialTest.java
new file mode 100644
index 0000000..2c1479e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/graph/InvokeSpecialTest.java
@@ -0,0 +1,24 @@
+// 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.graph;
+
+import com.android.tools.r8.AsmTestBase;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.graph.invokespecial.Main;
+import com.android.tools.r8.graph.invokespecial.TestClassDump;
+import org.junit.Ignore;
+import org.junit.Test;
+
+public class InvokeSpecialTest extends AsmTestBase {
+
+  @Ignore("b/110175213")
+  @Test
+  public void testInvokeSpecial() throws Exception {
+    ensureSameOutput(
+        Main.class.getCanonicalName(),
+        ToolHelper.getClassAsBytes(Main.class),
+        TestClassDump.dump());
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/graph/invokespecial/Main.java b/src/test/java/com/android/tools/r8/graph/invokespecial/Main.java
new file mode 100644
index 0000000..deb316d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/graph/invokespecial/Main.java
@@ -0,0 +1,13 @@
+// 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.graph.invokespecial;
+
+public class Main {
+
+  public static void main(String[] args) {
+    TestClass x = new TestClass();
+    x.m(true);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/graph/invokespecial/TestClass.java b/src/test/java/com/android/tools/r8/graph/invokespecial/TestClass.java
new file mode 100644
index 0000000..c08091e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/graph/invokespecial/TestClass.java
@@ -0,0 +1,15 @@
+// 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.graph.invokespecial;
+
+public class TestClass {
+
+  public void m(boolean recurse) {
+    System.out.println(recurse);
+    if (recurse) {
+      m(false);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/graph/invokespecial/TestClassDump.java b/src/test/java/com/android/tools/r8/graph/invokespecial/TestClassDump.java
new file mode 100644
index 0000000..ae336834
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/graph/invokespecial/TestClassDump.java
@@ -0,0 +1,67 @@
+// 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.graph.invokespecial;
+
+import org.objectweb.asm.AnnotationVisitor;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.FieldVisitor;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+// Generated by running ./tools/asmifier.py build/classes/test/com/android/tools/r8/graph/-
+// invokespecial/TestClass.class, and changing the invoke-virtual TestClass.m() instruction to
+// an invoke-special instruction.
+public class TestClassDump implements Opcodes {
+
+  public static byte[] dump() throws Exception {
+
+    ClassWriter cw = new ClassWriter(0);
+    FieldVisitor fv;
+    MethodVisitor mv;
+    AnnotationVisitor av0;
+
+    cw.visit(
+        V1_8,
+        ACC_PUBLIC + ACC_SUPER,
+        "com/android/tools/r8/graph/invokespecial/TestClass",
+        null,
+        "java/lang/Object",
+        null);
+
+    {
+      mv = cw.visitMethod(ACC_PUBLIC, "<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();
+    }
+    {
+      mv = cw.visitMethod(ACC_PUBLIC, "m", "(Z)V", null, null);
+      mv.visitCode();
+      mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+      mv.visitVarInsn(ILOAD, 1);
+      mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Z)V", false);
+      mv.visitVarInsn(ILOAD, 1);
+      Label l0 = new Label();
+      mv.visitJumpInsn(IFEQ, l0);
+      mv.visitVarInsn(ALOAD, 0);
+      mv.visitInsn(ICONST_0);
+      // Note: Changed from INVOKEVIRTUAL to INVOKESPECIAL.
+      mv.visitMethodInsn(
+          INVOKESPECIAL, "com/android/tools/r8/graph/invokespecial/TestClass", "m", "(Z)V", false);
+      mv.visitLabel(l0);
+      mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
+      mv.visitInsn(RETURN);
+      mv.visitMaxs(2, 2);
+      mv.visitEnd();
+    }
+    cw.visitEnd();
+
+    return cw.toByteArray();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/jasmin/JasminBuilder.java b/src/test/java/com/android/tools/r8/jasmin/JasminBuilder.java
index eb6e943..ba04454 100644
--- a/src/test/java/com/android/tools/r8/jasmin/JasminBuilder.java
+++ b/src/test/java/com/android/tools/r8/jasmin/JasminBuilder.java
@@ -195,7 +195,7 @@
       return addStaticMethod("main", ImmutableList.of("[Ljava/lang/String;"), "V", lines);
     }
 
-    private MethodSignature addMethod(
+    public MethodSignature addMethod(
         String access,
         String name,
         List<String> argumentTypes,
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 fc0436d..b9ff7ea 100644
--- a/src/test/java/com/android/tools/r8/naming/MinifierClassSignatureTest.java
+++ b/src/test/java/com/android/tools/r8/naming/MinifierClassSignatureTest.java
@@ -477,7 +477,7 @@
     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")));
   }
 
@@ -486,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")));
   }
 
@@ -495,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")));
   }
 
@@ -504,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")));
   }
@@ -528,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/naming/b72391662/B72391662.java b/src/test/java/com/android/tools/r8/naming/b72391662/B72391662.java
index c82bbc9..9a9ca34 100644
--- a/src/test/java/com/android/tools/r8/naming/b72391662/B72391662.java
+++ b/src/test/java/com/android/tools/r8/naming/b72391662/B72391662.java
@@ -4,52 +4,303 @@
 
 package com.android.tools.r8.naming.b72391662;
 
+import static com.android.tools.r8.utils.DexInspectorMatchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThat;
 
-import com.android.tools.r8.TestBase;
 import com.android.tools.r8.ToolHelper.DexVm.Version;
 import com.android.tools.r8.VmTestRunner;
 import com.android.tools.r8.VmTestRunner.IgnoreIfVmOlderThan;
 import com.android.tools.r8.naming.b72391662.subpackage.OtherPackageSuper;
 import com.android.tools.r8.naming.b72391662.subpackage.OtherPackageTestClass;
+import com.android.tools.r8.shaking.forceproguardcompatibility.ProguardCompatabilityTestBase;
 import com.android.tools.r8.utils.AndroidApp;
+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 java.util.List;
+import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
 @RunWith(VmTestRunner.class)
-public class B72391662 extends TestBase {
+public class B72391662 extends ProguardCompatabilityTestBase {
 
-  private void doTest(boolean allowAccessModification, boolean minify) throws Exception {
+  private static final List<Class> CLASSES = ImmutableList.of(
+      TestMain.class, Interface.class, Super.class, TestClass.class,
+      OtherPackageSuper.class, OtherPackageTestClass.class);
+
+  private void doTest_keepAll(
+      Shrinker shrinker,
+      String repackagePrefix,
+      boolean allowAccessModification,
+      boolean minify) throws Exception {
     Class mainClass = TestMain.class;
+    String keep = !minify ? "-keep" : "-keep,allowobfuscation";
     List<String> config = ImmutableList.of(
-        allowAccessModification ?"-allowaccessmodification" : "",
+        "-printmapping",
+        repackagePrefix != null ? "-repackageclasses '" + repackagePrefix + "'" : "",
+        allowAccessModification ? "-allowaccessmodification" : "",
         !minify ? "-dontobfuscate" : "",
         "-keep class " + mainClass.getCanonicalName() + " {",
         "  public void main(java.lang.String[]);",
         "}",
-        "-keep class " + TestClass.class.getCanonicalName() + " {",
+        keep + " class " + TestClass.class.getCanonicalName() + " {",
         "  *;",
         "}",
-        "-keep class " + OtherPackageTestClass.class.getCanonicalName() + " {",
+        keep + " class " + OtherPackageTestClass.class.getCanonicalName() + " {",
         "  *;",
-        "}"
+        "}",
+        "-dontwarn java.lang.invoke.*"
     );
 
-    AndroidApp app = readClassesAndAndriodJar(ImmutableList.of(
-        mainClass, Interface.class, Super.class, TestClass.class,
-        OtherPackageSuper.class, OtherPackageTestClass.class));
-    app = compileWithR8(app, String.join(System.lineSeparator(), config));
+    AndroidApp app = runShrinkerRaw(shrinker, CLASSES, config);
     assertEquals("123451234567\nABC\n", runOnArt(app, mainClass.getCanonicalName()));
+
+    DexInspector dexInspector =
+        isR8(shrinker) ? new DexInspector(app) : new DexInspector(app, proguardMap);
+    ClassSubject testClass = dexInspector.clazz(TestClass.class);
+    assertThat(testClass, isPresent());
+
+    // Test the totally unused method.
+    MethodSubject staticMethod = testClass.method("void", "unused", ImmutableList.of());
+    assertThat(staticMethod, isPresent());
+    assertEquals(minify, staticMethod.isRenamed());
+    if (isR8(shrinker)) {
+      assertEquals(allowAccessModification, staticMethod.getMethod().accessFlags.isPublic());
+    } else {
+      assertFalse(staticMethod.getMethod().accessFlags.isPublic());
+    }
+
+    // Test an indirectly referred method.
+    staticMethod = testClass.method("java.lang.String", "staticMethod", ImmutableList.of());
+    assertThat(staticMethod, isPresent());
+    assertEquals(minify, staticMethod.isRenamed());
+    boolean publicizeCondition = isR8(shrinker) ? allowAccessModification
+        : minify && repackagePrefix != null && allowAccessModification;
+    assertEquals(publicizeCondition, staticMethod.getMethod().accessFlags.isPublic());
   }
 
   @Test
   @IgnoreIfVmOlderThan(Version.V7_0_0)
-  public void test() throws Exception {
-    doTest(true, true);
-    doTest(true, false);
-    doTest(false, true);
-    doTest(false, false);
+  public void test_keepAll_R8() throws Exception {
+    doTest_keepAll(Shrinker.R8, "r8", true, true);
+    doTest_keepAll(Shrinker.R8, "r8", true, false);
+    doTest_keepAll(Shrinker.R8, "r8", false, true);
+    doTest_keepAll(Shrinker.R8, "r8", false, false);
+    doTest_keepAll(Shrinker.R8, null, true, true);
+    doTest_keepAll(Shrinker.R8, null, true, false);
+    doTest_keepAll(Shrinker.R8, null, false, true);
+    doTest_keepAll(Shrinker.R8, null, false, false);
+  }
+
+  @Ignore("b/92236970")
+  @Test
+  @IgnoreIfVmOlderThan(Version.V7_0_0)
+  public void test_keepAll_R8Compat() throws Exception {
+    doTest_keepAll(Shrinker.R8_COMPAT, "rc", true, true);
+    doTest_keepAll(Shrinker.R8_COMPAT, "rc", true, false);
+    doTest_keepAll(Shrinker.R8_COMPAT, "rc", false, true);
+    doTest_keepAll(Shrinker.R8_COMPAT, "rc", false, false);
+    doTest_keepAll(Shrinker.R8_COMPAT, null, true, true);
+    doTest_keepAll(Shrinker.R8_COMPAT, null, true, false);
+    doTest_keepAll(Shrinker.R8_COMPAT, null, false, true);
+    doTest_keepAll(Shrinker.R8_COMPAT, null, false, false);
+  }
+
+  @Test
+  @IgnoreIfVmOlderThan(Version.V7_0_0)
+  public void test_keepAll_Proguard6() throws Exception {
+    doTest_keepAll(Shrinker.PROGUARD6_THEN_D8, "pg", true, true);
+    doTest_keepAll(Shrinker.PROGUARD6_THEN_D8, "pg", true, false);
+    doTest_keepAll(Shrinker.PROGUARD6_THEN_D8, "pg", false, true);
+    doTest_keepAll(Shrinker.PROGUARD6_THEN_D8, "pg", false, false);
+    doTest_keepAll(Shrinker.PROGUARD6_THEN_D8, null, true, true);
+    doTest_keepAll(Shrinker.PROGUARD6_THEN_D8, null, true, false);
+    doTest_keepAll(Shrinker.PROGUARD6_THEN_D8, null, false, true);
+    doTest_keepAll(Shrinker.PROGUARD6_THEN_D8, null, false, false);
+  }
+
+  private void doTest_keepNonPublic(
+      Shrinker shrinker,
+      String repackagePrefix,
+      boolean allowAccessModification,
+      boolean minify) throws Exception {
+    Class mainClass = TestMain.class;
+    String keep = !minify ? "-keep" : "-keep,allowobfuscation";
+    List<String> config = ImmutableList.of(
+        "-printmapping",
+        repackagePrefix != null ? "-repackageclasses '" + repackagePrefix + "'" : "",
+        allowAccessModification ? "-allowaccessmodification" : "",
+        !minify ? "-dontobfuscate" : "",
+        "-keep class " + mainClass.getCanonicalName() + " {",
+        "  public void main(java.lang.String[]);",
+        "}",
+        keep + " class " + TestClass.class.getCanonicalName() + " {",
+        "  !public <methods>;",
+        "}",
+        keep + " class " + OtherPackageTestClass.class.getCanonicalName() + " {",
+        "  !public <methods>;",
+        "}",
+        "-dontwarn java.lang.invoke.*"
+    );
+
+    AndroidApp app = runShrinkerRaw(shrinker, CLASSES, config);
+    assertEquals("123451234567\nABC\n", runOnArt(app, mainClass.getCanonicalName()));
+
+    DexInspector dexInspector =
+        isR8(shrinker) ? new DexInspector(app) : new DexInspector(app, proguardMap);
+    ClassSubject testClass = dexInspector.clazz(TestClass.class);
+    assertThat(testClass, isPresent());
+
+    // Test the totally unused method.
+    MethodSubject staticMethod = testClass.method("void", "unused", ImmutableList.of());
+    assertThat(staticMethod, isPresent());
+    assertEquals(minify, staticMethod.isRenamed());
+    if (isR8(shrinker)) {
+      assertEquals(allowAccessModification, staticMethod.getMethod().accessFlags.isPublic());
+    } else {
+      assertFalse(staticMethod.getMethod().accessFlags.isPublic());
+    }
+
+    // Test an indirectly referred method.
+    staticMethod = testClass.method("java.lang.String", "staticMethod", ImmutableList.of());
+    assertThat(staticMethod, isPresent());
+    assertEquals(minify, staticMethod.isRenamed());
+    boolean publicizeCondition = isR8(shrinker) ? allowAccessModification
+        : minify && repackagePrefix != null && allowAccessModification;
+    assertEquals(publicizeCondition, staticMethod.getMethod().accessFlags.isPublic());
+  }
+
+  @Test
+  @IgnoreIfVmOlderThan(Version.V7_0_0)
+  public void test_keepNonPublic_R8() throws Exception {
+    doTest_keepNonPublic(Shrinker.R8, "r8", true, true);
+    doTest_keepNonPublic(Shrinker.R8, "r8", true, false);
+    doTest_keepNonPublic(Shrinker.R8, "r8", false, true);
+    doTest_keepNonPublic(Shrinker.R8, "r8", false, false);
+    doTest_keepNonPublic(Shrinker.R8, null, true, true);
+    doTest_keepNonPublic(Shrinker.R8, null, true, false);
+    doTest_keepNonPublic(Shrinker.R8, null, false, true);
+    doTest_keepNonPublic(Shrinker.R8, null, false, false);
+  }
+
+  @Ignore("b/92236970")
+  @Test
+  @IgnoreIfVmOlderThan(Version.V7_0_0)
+  public void test_keepNonPublic_R8Compat() throws Exception {
+    doTest_keepNonPublic(Shrinker.R8_COMPAT, "rc", true, true);
+    doTest_keepNonPublic(Shrinker.R8_COMPAT, "rc", true, false);
+    doTest_keepNonPublic(Shrinker.R8_COMPAT, "rc", false, true);
+    doTest_keepNonPublic(Shrinker.R8_COMPAT, "rc", false, false);
+    doTest_keepNonPublic(Shrinker.R8_COMPAT, null, true, true);
+    doTest_keepNonPublic(Shrinker.R8_COMPAT, null, true, false);
+    doTest_keepNonPublic(Shrinker.R8_COMPAT, null, false, true);
+    doTest_keepNonPublic(Shrinker.R8_COMPAT, null, false, false);
+  }
+
+  @Test
+  @IgnoreIfVmOlderThan(Version.V7_0_0)
+  public void test_keepNonPublic_Proguard6() throws Exception {
+    doTest_keepNonPublic(Shrinker.PROGUARD6_THEN_D8, "pg", true, true);
+    doTest_keepNonPublic(Shrinker.PROGUARD6_THEN_D8, "pg", true, false);
+    doTest_keepNonPublic(Shrinker.PROGUARD6_THEN_D8, "pg", false, true);
+    doTest_keepNonPublic(Shrinker.PROGUARD6_THEN_D8, "pg", false, false);
+    doTest_keepNonPublic(Shrinker.PROGUARD6_THEN_D8, null, true, true);
+    doTest_keepNonPublic(Shrinker.PROGUARD6_THEN_D8, null, true, false);
+    doTest_keepNonPublic(Shrinker.PROGUARD6_THEN_D8, null, false, true);
+    doTest_keepNonPublic(Shrinker.PROGUARD6_THEN_D8, null, false, false);
+  }
+
+  private void doTest_keepPublic(
+      Shrinker shrinker,
+      String repackagePrefix,
+      boolean allowAccessModification,
+      boolean minify) throws Exception {
+    Class mainClass = TestMain.class;
+    String keep = !minify ? "-keep" : "-keep,allowobfuscation";
+    List<String> config = ImmutableList.of(
+        "-printmapping",
+        repackagePrefix != null ? "-repackageclasses '" + repackagePrefix + "'" : "",
+        allowAccessModification ? "-allowaccessmodification" : "",
+        !minify ? "-dontobfuscate" : "",
+        "-keep class " + mainClass.getCanonicalName() + " {",
+        "  public void main(java.lang.String[]);",
+        "}",
+        keep + " class " + TestClass.class.getCanonicalName() + " {",
+        "  public <methods>;",
+        "}",
+        keep + " class " + OtherPackageTestClass.class.getCanonicalName() + " {",
+        "  public <methods>;",
+        "}",
+        "-dontwarn java.lang.invoke.*"
+    );
+
+    AndroidApp app = runShrinkerRaw(shrinker, CLASSES, config);
+    assertEquals("123451234567\nABC\n", runOnArt(app, mainClass.getCanonicalName()));
+
+    DexInspector dexInspector =
+        isR8(shrinker) ? new DexInspector(app) : new DexInspector(app, proguardMap);
+    ClassSubject testClass = dexInspector.clazz(TestClass.class);
+    assertThat(testClass, isPresent());
+
+    // Test the totally unused method.
+    MethodSubject staticMethod = testClass.method("void", "unused", ImmutableList.of());
+    assertThat(staticMethod, not(isPresent()));
+
+    // Test an indirectly referred method.
+    staticMethod = testClass.method("java.lang.String", "staticMethod", ImmutableList.of());
+    if (isR8(shrinker)) {
+      // Inlined.
+      assertThat(staticMethod, not(isPresent()));
+    } else {
+      assertThat(staticMethod, isPresent());
+      assertEquals(minify, staticMethod.isRenamed());
+      boolean publicizeCondition = minify && repackagePrefix != null && allowAccessModification;
+      assertEquals(publicizeCondition, staticMethod.getMethod().accessFlags.isPublic());
+    }
+  }
+
+  @Test
+  @IgnoreIfVmOlderThan(Version.V7_0_0)
+  public void test_keepPublic_R8() throws Exception {
+    doTest_keepPublic(Shrinker.R8, "r8", true, true);
+    doTest_keepPublic(Shrinker.R8, "r8", true, false);
+    doTest_keepPublic(Shrinker.R8, "r8", false, true);
+    doTest_keepPublic(Shrinker.R8, "r8", false, false);
+    doTest_keepPublic(Shrinker.R8, null, true, true);
+    doTest_keepPublic(Shrinker.R8, null, true, false);
+    doTest_keepPublic(Shrinker.R8, null, false, true);
+    doTest_keepPublic(Shrinker.R8, null, false, false);
+  }
+
+  @Ignore("b/92236970")
+  @Test
+  @IgnoreIfVmOlderThan(Version.V7_0_0)
+  public void test_keepPublic_R8Compat() throws Exception {
+    doTest_keepPublic(Shrinker.R8_COMPAT, "rc", true, true);
+    doTest_keepPublic(Shrinker.R8_COMPAT, "rc", true, false);
+    doTest_keepPublic(Shrinker.R8_COMPAT, "rc", false, true);
+    doTest_keepPublic(Shrinker.R8_COMPAT, "rc", false, false);
+    doTest_keepPublic(Shrinker.R8_COMPAT, null, true, true);
+    doTest_keepPublic(Shrinker.R8_COMPAT, null, true, false);
+    doTest_keepPublic(Shrinker.R8_COMPAT, null, false, true);
+    doTest_keepPublic(Shrinker.R8_COMPAT, null, false, false);
+  }
+
+  @Test
+  @IgnoreIfVmOlderThan(Version.V7_0_0)
+  public void test_keepPublic_Proguard6() throws Exception {
+    doTest_keepPublic(Shrinker.PROGUARD6_THEN_D8, "pg", true, true);
+    doTest_keepPublic(Shrinker.PROGUARD6_THEN_D8, "pg", true, false);
+    doTest_keepPublic(Shrinker.PROGUARD6_THEN_D8, "pg", false, true);
+    doTest_keepPublic(Shrinker.PROGUARD6_THEN_D8, "pg", false, false);
+    doTest_keepPublic(Shrinker.PROGUARD6_THEN_D8, null, true, true);
+    doTest_keepPublic(Shrinker.PROGUARD6_THEN_D8, null, true, false);
+    doTest_keepPublic(Shrinker.PROGUARD6_THEN_D8, null, false, true);
+    doTest_keepPublic(Shrinker.PROGUARD6_THEN_D8, null, false, false);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/naming/b72391662/TestClass.java b/src/test/java/com/android/tools/r8/naming/b72391662/TestClass.java
index 88efc09..f13700c 100644
--- a/src/test/java/com/android/tools/r8/naming/b72391662/TestClass.java
+++ b/src/test/java/com/android/tools/r8/naming/b72391662/TestClass.java
@@ -18,6 +18,10 @@
     return value;
   }
 
+  static void unused() {
+    System.out.println("This should be discarded unless there is a keep rule.");
+  }
+
   static String staticMethod() {
     return "1";
   }
diff --git a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ProguardCompatabilityTestBase.java b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ProguardCompatabilityTestBase.java
index 3976f89..10c64e3 100644
--- a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ProguardCompatabilityTestBase.java
+++ b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ProguardCompatabilityTestBase.java
@@ -6,24 +6,31 @@
 import static com.android.tools.r8.utils.DexInspectorMatchers.isPresent;
 import static org.hamcrest.CoreMatchers.not;
 import static org.junit.Assert.assertThat;
+import static org.junit.Assert.fail;
 
 import com.android.tools.r8.CompatProguardCommandBuilder;
 import com.android.tools.r8.DexIndexedConsumer;
 import com.android.tools.r8.R8Command;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.ProcessResult;
 import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.DexInspector;
 import com.android.tools.r8.utils.DexInspector.ClassSubject;
 import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.InternalOptions;
 import com.google.common.collect.ImmutableList;
 import java.io.File;
 import java.nio.file.Path;
 import java.util.List;
+import java.util.function.Consumer;
 
 public class ProguardCompatabilityTestBase extends TestBase {
 
+  protected Path proguardMap;
+
   public enum Shrinker {
     PROGUARD5,
     PROGUARD6,
@@ -32,6 +39,34 @@
     R8
   }
 
+  protected static boolean isR8(Shrinker shrinker) {
+    return shrinker == Shrinker.R8_COMPAT || shrinker == Shrinker.R8;
+  }
+
+  protected AndroidApp runShrinkerRaw(
+      Shrinker mode, List<Class> programClasses, List<String> proguadConfigs) throws Exception {
+    return runShrinkerRaw(
+        mode, programClasses, String.join(System.lineSeparator(), proguadConfigs));
+  }
+
+  protected AndroidApp runShrinkerRaw(
+      Shrinker mode, List<Class> programClasses, String proguardConfig) throws Exception {
+    proguardMap = File.createTempFile("proguard", ".map", temp.getRoot()).toPath();
+    switch (mode) {
+      case PROGUARD5:
+        return runProguard5Raw(programClasses, proguardConfig, proguardMap);
+      case PROGUARD6:
+        return runProguard6Raw(programClasses, proguardConfig, proguardMap);
+      case PROGUARD6_THEN_D8:
+        return runProguard6AndD8Raw(programClasses, proguardConfig, proguardMap);
+      case R8_COMPAT:
+        return runR8CompatRaw(programClasses, proguardConfig);
+      case R8:
+        return runR8Raw(programClasses, proguardConfig);
+    }
+    throw new IllegalArgumentException("Unknown shrinker: " + mode);
+  }
+
   protected DexInspector runShrinker(
       Shrinker mode, List<Class> programClasses, List<String> proguadConfigs) throws Exception {
     return runShrinker(mode, programClasses, String.join(System.lineSeparator(), proguadConfigs));
@@ -54,21 +89,38 @@
     throw new IllegalArgumentException("Unknown shrinker: " + mode);
   }
 
-  protected DexInspector runR8(List<Class> programClasses, String proguardConfig) throws Exception {
-    AndroidApp app = readClasses(programClasses);
-    R8Command.Builder builder = ToolHelper.prepareR8CommandBuilder(app);
-    builder.addProguardConfiguration(ImmutableList.of(proguardConfig), Origin.unknown());
-    return new DexInspector(ToolHelper.runR8(builder.build()));
+  protected AndroidApp runR8Raw(List<Class> programClasses, String proguardConfig)
+      throws Exception {
+    return runR8Raw(programClasses, proguardConfig, null);
   }
 
-  protected DexInspector runR8Compat(
+  protected AndroidApp runR8Raw(
+      List<Class> programClasses, String proguardConfig, Consumer<InternalOptions> configure)
+      throws Exception {
+    AndroidApp app = readClassesAndAndriodJar(programClasses);
+    R8Command.Builder builder = ToolHelper.prepareR8CommandBuilder(app);
+    builder.addProguardConfiguration(ImmutableList.of(proguardConfig), Origin.unknown());
+    return ToolHelper.runR8(builder.build(), configure);
+  }
+
+  protected DexInspector runR8(List<Class> programClasses, String proguardConfig) throws Exception {
+    return new DexInspector(runR8Raw(programClasses, proguardConfig));
+  }
+
+  protected AndroidApp runR8CompatRaw(
       List<Class> programClasses, String proguardConfig) throws Exception {
     CompatProguardCommandBuilder builder = new CompatProguardCommandBuilder(true);
     builder.addProguardConfiguration(ImmutableList.of(proguardConfig), Origin.unknown());
     programClasses.forEach(
         clazz -> builder.addProgramFiles(ToolHelper.getClassFileForTestClass(clazz)));
+    builder.addLibraryFiles(ToolHelper.getDefaultAndroidJar());
     builder.setProgramConsumer(DexIndexedConsumer.emptyConsumer());
-    return new DexInspector(ToolHelper.runR8(builder.build()));
+    return ToolHelper.runR8(builder.build());
+  }
+
+  protected DexInspector runR8Compat(
+      List<Class> programClasses, String proguardConfig) throws Exception {
+    return new DexInspector(runR8CompatRaw(programClasses, proguardConfig));
   }
 
   protected DexInspector runR8CompatKeepingMain(Class mainClass, List<Class> programClasses)
@@ -76,41 +128,79 @@
     return runR8Compat(programClasses, keepMainProguardConfiguration(mainClass));
   }
 
-  protected DexInspector runProguard5(
-      List<Class> programClasses, String proguardConfig) throws Exception {
+  protected AndroidApp runProguard5Raw(
+      List<Class> programClasses, String proguardConfig, Path proguardMap) throws Exception {
     Path proguardedJar =
         File.createTempFile("proguarded", FileUtils.JAR_EXTENSION, temp.getRoot()).toPath();
     Path proguardConfigFile = File.createTempFile("proguard", ".config", temp.getRoot()).toPath();
-    Path proguardMap = File.createTempFile("proguard", ".map", temp.getRoot()).toPath();
     FileUtils.writeTextFile(proguardConfigFile, proguardConfig);
-    ToolHelper.runProguard(
-        jarTestClasses(programClasses), proguardedJar, proguardConfigFile, proguardMap);
-    return new DexInspector(readJar(proguardedJar), proguardMap);
+    ProcessResult result = ToolHelper.runProguardRaw(
+        jarTestClasses(programClasses),
+        proguardedJar,
+        ToolHelper.getAndroidJar(AndroidApiLevel.N),
+        proguardConfigFile,
+        proguardMap);
+    if (result.exitCode != 0) {
+      fail("Proguard failed, exit code " + result.exitCode + ", stderr:\n" + result.stderr);
+    }
+    return readJar(proguardedJar);
+  }
+
+  protected DexInspector runProguard5(
+      List<Class> programClasses, String proguardConfig) throws Exception {
+    proguardMap = File.createTempFile("proguard", ".map", temp.getRoot()).toPath();
+    return new DexInspector(
+        runProguard5Raw(programClasses, proguardConfig, proguardMap), proguardMap);
+  }
+
+  protected AndroidApp runProguard6Raw(
+      List<Class> programClasses, String proguardConfig, Path proguardMap) throws Exception {
+    Path proguardedJar =
+        File.createTempFile("proguarded", FileUtils.JAR_EXTENSION, temp.getRoot()).toPath();
+    Path proguardConfigFile = File.createTempFile("proguard", ".config", temp.getRoot()).toPath();
+    FileUtils.writeTextFile(proguardConfigFile, proguardConfig);
+    ProcessResult result = ToolHelper.runProguard6Raw(
+        jarTestClasses(programClasses),
+        proguardedJar,
+        ToolHelper.getAndroidJar(AndroidApiLevel.N),
+        proguardConfigFile,
+        proguardMap);
+    if (result.exitCode != 0) {
+      fail("Proguard failed, exit code " + result.exitCode + ", stderr:\n" + result.stderr);
+    }
+    return readJar(proguardedJar);
   }
 
   protected DexInspector runProguard6(
       List<Class> programClasses, String proguardConfig) throws Exception {
+    proguardMap = File.createTempFile("proguard", ".map", temp.getRoot()).toPath();
+    return new DexInspector(
+        runProguard6Raw(programClasses, proguardConfig, proguardMap), proguardMap);
+  }
+
+  protected AndroidApp runProguard6AndD8Raw(
+      List<Class> programClasses, String proguardConfig, Path proguardMap) throws Exception {
     Path proguardedJar =
         File.createTempFile("proguarded", FileUtils.JAR_EXTENSION, temp.getRoot()).toPath();
     Path proguardConfigFile = File.createTempFile("proguard", ".config", temp.getRoot()).toPath();
-    Path proguardMap = File.createTempFile("proguard", ".map", temp.getRoot()).toPath();
     FileUtils.writeTextFile(proguardConfigFile, proguardConfig);
-    ToolHelper.runProguard6(
-        jarTestClasses(programClasses), proguardedJar, proguardConfigFile, proguardMap);
-    return new DexInspector(readJar(proguardedJar), proguardMap);
+    ProcessResult result = ToolHelper.runProguard6Raw(
+        jarTestClasses(programClasses),
+        proguardedJar,
+        ToolHelper.getAndroidJar(AndroidApiLevel.N),
+        proguardConfigFile,
+        proguardMap);
+    if (result.exitCode != 0) {
+      fail("Proguard failed, exit code " + result.exitCode + ", stderr:\n" + result.stderr);
+    }
+    return ToolHelper.runD8(readJar(proguardedJar));
   }
 
   protected DexInspector runProguard6AndD8(
       List<Class> programClasses, String proguardConfig) throws Exception {
-    Path proguardedJar =
-        File.createTempFile("proguarded", FileUtils.JAR_EXTENSION, temp.getRoot()).toPath();
-    Path proguardConfigFile = File.createTempFile("proguard", ".config", temp.getRoot()).toPath();
-    Path proguardMap = File.createTempFile("proguard", ".map", temp.getRoot()).toPath();
-    FileUtils.writeTextFile(proguardConfigFile, proguardConfig);
-    ToolHelper.runProguard6(
-        jarTestClasses(programClasses), proguardedJar, proguardConfigFile, proguardMap);
-    AndroidApp app = ToolHelper.runD8(readJar(proguardedJar));
-    return new DexInspector(app, proguardMap);
+    proguardMap = File.createTempFile("proguard", ".map", temp.getRoot()).toPath();
+    return new DexInspector(
+        runProguard6AndD8Raw(programClasses, proguardConfig, proguardMap), proguardMap);
   }
 
   protected DexInspector runProguardKeepingMain(Class mainClass, List<Class> programClasses)
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnAccessModifierTest.java b/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnAccessModifierTest.java
new file mode 100644
index 0000000..65d1763
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnAccessModifierTest.java
@@ -0,0 +1,195 @@
+// 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.shaking.ifrule;
+
+import static com.android.tools.r8.utils.DexInspectorMatchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.naming.MemberNaming.MethodSignature;
+import com.android.tools.r8.shaking.forceproguardcompatibility.ProguardCompatabilityTestBase;
+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 java.util.Collection;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class IfOnAccessModifierTest extends ProguardCompatabilityTestBase {
+    private final static List<Class> CLASSES = ImmutableList.of(
+        ClassForIf.class, ClassForSubsequent.class,
+        MainForAccessModifierTest.class);
+
+  private final Shrinker shrinker;
+  private final MethodSignature nonPublicMethod;
+  private final MethodSignature publicMethod;
+
+  public IfOnAccessModifierTest(Shrinker shrinker) {
+    this.shrinker = shrinker;
+    nonPublicMethod = new MethodSignature("nonPublicMethod", "void", ImmutableList.of());
+    publicMethod = new MethodSignature("publicMethod", "void", ImmutableList.of());
+  }
+
+  @Parameters(name = "shrinker: {0}")
+  public static Collection<Object> data() {
+    return ImmutableList.of(Shrinker.PROGUARD6, Shrinker.R8);
+  }
+
+  @Test
+  public void ifOnNonPublic_keepOnPublic() throws Exception {
+    List<String> config = ImmutableList.of(
+        "-printmapping",
+        "-repackageclasses 'top'",
+        "-allowaccessmodification",
+        "-keep class **.Main* {",
+        "  public static void callIfNonPublic();",
+        "}",
+        "-if class **.ClassForIf {",
+        "  !public <methods>;",
+        "}",
+        "-keep,allowobfuscation class **.ClassForSubsequent {",
+        "  public <methods>;",
+        "}"
+    );
+    DexInspector dexInspector = runShrinker(shrinker, CLASSES, config);
+    ClassSubject classSubject = dexInspector.clazz(ClassForIf.class);
+    assertThat(classSubject, isPresent());
+    MethodSubject methodSubject = classSubject.method(publicMethod);
+    assertThat(methodSubject, not(isPresent()));
+    methodSubject = classSubject.method(nonPublicMethod);
+    assertThat(methodSubject, isPresent());
+    assertTrue(methodSubject.getMethod().accessFlags.isPublic());
+
+    classSubject = dexInspector.clazz(ClassForSubsequent.class);
+    if (isR8(shrinker)) {
+      // TODO(b/72109068): ClassForIf#nonPublicMethod becomes public, and -if rule is not applied
+      // at the 2nd tree shaking.
+      assertThat(classSubject, not(isPresent()));
+      return;
+    }
+    assertThat(classSubject, isPresent());
+    methodSubject = classSubject.method(publicMethod);
+    assertThat(methodSubject, isPresent());
+    methodSubject = classSubject.method(nonPublicMethod);
+    assertThat(methodSubject, not(isPresent()));
+  }
+
+  @Test
+  public void ifOnNonPublic_keepOnNonPublic() throws Exception {
+    List<String> config = ImmutableList.of(
+        "-printmapping",
+        "-repackageclasses 'top'",
+        "-allowaccessmodification",
+        "-keep class **.Main* {",
+        "  public static void callIfNonPublic();",
+        "}",
+        "-if class **.ClassForIf {",
+        "  !public <methods>;",
+        "}",
+        "-keep,allowobfuscation class **.ClassForSubsequent {",
+        "  !public <methods>;",
+        "}"
+    );
+    DexInspector dexInspector = runShrinker(shrinker, CLASSES, config);
+    ClassSubject classSubject = dexInspector.clazz(ClassForIf.class);
+    assertThat(classSubject, isPresent());
+    MethodSubject methodSubject = classSubject.method(publicMethod);
+    assertThat(methodSubject, not(isPresent()));
+    methodSubject = classSubject.method(nonPublicMethod);
+    assertThat(methodSubject, isPresent());
+    assertTrue(methodSubject.getMethod().accessFlags.isPublic());
+
+    classSubject = dexInspector.clazz(ClassForSubsequent.class);
+    if (isR8(shrinker)) {
+      // TODO(b/72109068): ClassForIf#nonPublicMethod becomes public, and -if rule is not applied
+      // at the 2nd tree shaking.
+      assertThat(classSubject, not(isPresent()));
+      return;
+    }
+    assertThat(classSubject, isPresent());
+    methodSubject = classSubject.method(publicMethod);
+    assertThat(methodSubject, not(isPresent()));
+    methodSubject = classSubject.method(nonPublicMethod);
+    assertThat(methodSubject, isPresent());
+    assertFalse(methodSubject.getMethod().accessFlags.isPublic());
+  }
+
+  @Test
+  public void ifOnPublic_keepOnPublic() throws Exception {
+    List<String> config = ImmutableList.of(
+        "-printmapping",
+        "-repackageclasses 'top'",
+        "-allowaccessmodification",
+        "-keep class **.Main* {",
+        "  public static void callIfPublic();",
+        "}",
+        "-if class **.ClassForIf {",
+        "  public <methods>;",
+        "}",
+        "-keep,allowobfuscation class **.ClassForSubsequent {",
+        "  public <methods>;",
+        "}"
+    );
+    DexInspector dexInspector = runShrinker(shrinker, CLASSES, config);
+    ClassSubject classSubject = dexInspector.clazz(ClassForIf.class);
+    assertThat(classSubject, isPresent());
+    MethodSubject methodSubject = classSubject.method(publicMethod);
+    assertThat(methodSubject, isPresent());
+    methodSubject = classSubject.method(nonPublicMethod);
+    assertThat(methodSubject, not(isPresent()));
+
+    classSubject = dexInspector.clazz(ClassForSubsequent.class);
+    assertThat(classSubject, isPresent());
+    methodSubject = classSubject.method(publicMethod);
+    assertThat(methodSubject, isPresent());
+    methodSubject = classSubject.method(nonPublicMethod);
+    assertThat(methodSubject, not(isPresent()));
+  }
+
+  @Test
+  public void ifOnPublic_keepOnNonPublic() throws Exception {
+    List<String> config = ImmutableList.of(
+        "-printmapping",
+        "-repackageclasses 'top'",
+        "-allowaccessmodification",
+        "-keep class **.Main* {",
+        "  public static void callIfPublic();",
+        "}",
+        "-if class **.ClassForIf {",
+        "  public <methods>;",
+        "}",
+        "-keep,allowobfuscation class **.ClassForSubsequent {",
+        "  !public <methods>;",
+        "}"
+    );
+    DexInspector dexInspector = runShrinker(shrinker, CLASSES, config);
+    ClassSubject classSubject = dexInspector.clazz(ClassForIf.class);
+    assertThat(classSubject, isPresent());
+    MethodSubject methodSubject = classSubject.method(publicMethod);
+    assertThat(methodSubject, isPresent());
+    methodSubject = classSubject.method(nonPublicMethod);
+    assertThat(methodSubject, not(isPresent()));
+
+    classSubject = dexInspector.clazz(ClassForSubsequent.class);
+    assertThat(classSubject, isPresent());
+    methodSubject = classSubject.method(publicMethod);
+    assertThat(methodSubject, not(isPresent()));
+    methodSubject = classSubject.method(nonPublicMethod);
+    if (isR8(shrinker)) {
+      // TODO(b/72109068): if kept in the 1st tree shaking, should be kept after publicizing.
+      assertThat(methodSubject, not(isPresent()));
+      return;
+    }
+    assertThat(methodSubject, isPresent());
+    assertFalse(methodSubject.getMethod().accessFlags.isPublic());
+  }
+
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnAccessModifierTestClasses.java b/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnAccessModifierTestClasses.java
new file mode 100644
index 0000000..1b5bd75
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnAccessModifierTestClasses.java
@@ -0,0 +1,40 @@
+// 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.shaking.ifrule;
+
+class ClassForIf {
+  ClassForIf() {
+  }
+
+  synchronized void nonPublicMethod() {
+    System.out.println("ClassForIf::nonPublicMethod");
+  }
+
+  synchronized public void publicMethod() {
+    System.out.println("ClassForIf::publicMethod");
+  }
+}
+
+class ClassForSubsequent {
+  ClassForSubsequent() {
+  }
+
+  synchronized void nonPublicMethod() {
+    System.out.println("ClassForSubsequent::nonPublicMethod");
+  }
+
+  synchronized public void publicMethod() {
+    System.out.println("ClassForSubsequent::publicMethod");
+  }
+}
+
+class MainForAccessModifierTest {
+  public static void callIfNonPublic() {
+    new ClassForIf().nonPublicMethod();
+  }
+
+  public static void callIfPublic() {
+    new ClassForIf().publicMethod();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/inlining/IfRuleWithInlining.java b/src/test/java/com/android/tools/r8/shaking/ifrule/inlining/IfRuleWithInlining.java
new file mode 100644
index 0000000..14391ee
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/inlining/IfRuleWithInlining.java
@@ -0,0 +1,105 @@
+// 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.shaking.ifrule.inlining;
+
+import static com.android.tools.r8.utils.DexInspectorMatchers.isPresent;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.shaking.forceproguardcompatibility.ProguardCompatabilityTestBase;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.google.common.collect.ImmutableList;
+import java.util.Collection;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+class A {
+  static public int a() {
+    try {
+      String p = A.class.getPackage().getName();
+      Class.forName(p + ".D");
+    } catch (ClassNotFoundException e) {
+      return 2;
+    }
+    return 1;
+  }
+}
+
+class B {
+  // Depending on inlining option, this method is kept to make inlining of A.a() infeasible.
+  void x() {
+    System.out.print("" + A.a() + A.a() + A.a() + A.a() + A.a() + A.a() + A.a() + A.a());
+  }
+}
+
+class D {
+}
+
+class Main {
+  public static void main(String[] args) {
+    System.out.print("" + A.a());
+  }
+}
+
+@RunWith(Parameterized.class)
+public class IfRuleWithInlining extends ProguardCompatabilityTestBase {
+  private final static List<Class> CLASSES = ImmutableList.of(
+      A.class, B.class, D.class, Main.class);
+
+  private final Shrinker shrinker;
+  private final boolean inlineMethod;
+
+  public IfRuleWithInlining(Shrinker shrinker, boolean inlineMethod) {
+    this.shrinker = shrinker;
+    this.inlineMethod = inlineMethod;
+  }
+
+  @Parameters(name = "shrinker: {0} inlineMethod: {1}")
+  public static Collection<Object[]> data() {
+    // We don't run this on Proguard, as triggering inlining in Proguard is out of our control.
+    return ImmutableList.of(
+        new Object[]{Shrinker.R8, true},
+        new Object[]{Shrinker.R8, false}
+    );
+  }
+
+  private void check(AndroidApp app) throws Exception {
+    DexInspector inspector = new DexInspector(app);
+    ClassSubject clazzA = inspector.clazz(A.class);
+    assertThat(clazzA, isPresent());
+    // A.a might be inlined.
+    assertEquals(!inlineMethod, clazzA.method("int", "a", ImmutableList.of()).isPresent());
+    // TODO(110148109): class D should be present - inlining or not.
+    assertEquals(!inlineMethod, inspector.clazz(D.class).isPresent());
+    ProcessResult result = runOnArtRaw(app, Main.class.getName());
+    assertEquals(0, result.exitCode);
+    // TODO(110148109): Output should be the same - inlining or not.
+    assertEquals(!inlineMethod ? "1" : "2", result.stdout);
+  }
+
+  @Test
+  public void testMergedClassMethodInIfRule() throws Exception {
+    List<String> config = ImmutableList.of(
+        "-keep class **.Main { public static void main(java.lang.String[]); }",
+        inlineMethod
+            ? "-alwaysinline class **.A { int a(); }"
+            : "-keep class **.B { *; }",
+        inlineMethod
+            ? "-checkdiscard class **.A { int a(); }"
+            : "",
+        "-if class **.A { static int a(); }",
+        "-keep class **.D",
+        "-dontobfuscate"
+    );
+
+    check(runShrinkerRaw(shrinker, CLASSES, config));
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/IfRuleWithVerticalClassMerging.java b/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/IfRuleWithVerticalClassMerging.java
new file mode 100644
index 0000000..b484d73
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/IfRuleWithVerticalClassMerging.java
@@ -0,0 +1,156 @@
+// 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.shaking.ifrule.verticalclassmerging;
+
+import static com.android.tools.r8.utils.DexInspectorMatchers.isPresent;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.shaking.forceproguardcompatibility.ProguardCompatabilityTestBase;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.android.tools.r8.utils.InternalOptions;
+import com.google.common.collect.ImmutableList;
+import java.util.Collection;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+class A {
+  int x = 1;
+  int a() throws ClassNotFoundException {
+    // Class D is expected to be kept - vertical class merging or not. The -if rule say that if
+    // the method A.a is in the output, then class D is needed.
+    String p =getClass().getPackage().getName();
+    Class.forName(p + ".D");
+    return 4;
+  }
+}
+
+class B extends A {
+  int y = 2;
+  int b() {
+    return 5;
+  }
+}
+
+class C extends B {
+  int z = 3;
+  int c() {
+    return 6;
+  }
+}
+
+class D {
+}
+
+class Main {
+  public static void main(String[] args) throws ClassNotFoundException {
+    C c = new C();
+    System.out.print("" + c.x + "" + c.y + "" + c.z + "" + c.a() + "" + c.b() + "" + c.c());
+  }
+}
+
+@RunWith(Parameterized.class)
+public class IfRuleWithVerticalClassMerging extends ProguardCompatabilityTestBase {
+  private final static List<Class> CLASSES = ImmutableList.of(
+      A.class, B.class, C.class, D.class, Main.class);
+
+  private final Shrinker shrinker;
+  private final boolean enableClassMerging;
+
+  public IfRuleWithVerticalClassMerging(Shrinker shrinker, boolean enableClassMerging) {
+    this.shrinker = shrinker;
+    this.enableClassMerging = enableClassMerging;
+  }
+
+  @Parameters(name = "shrinker: {0} classMerging: {1}")
+  public static Collection<Object[]> data() {
+    // We don't run this on Proguard, as Proguard does not merge A into B.
+    return ImmutableList.of(
+        new Object[]{Shrinker.R8, true},
+        new Object[]{Shrinker.R8, false}
+    );
+  }
+
+  private void configure(InternalOptions options) {
+    options.enableClassMerging = enableClassMerging;
+  }
+
+  @Override
+  protected AndroidApp runR8Raw(
+      List<Class> programClasses, String proguardConfig) throws Exception {
+    return super.runR8Raw(programClasses, proguardConfig, this::configure);
+  }
+
+  private void check(AndroidApp app) throws Exception {
+    DexInspector inspector = new DexInspector(app);
+    ClassSubject clazzA = inspector.clazz(A.class);
+    assertEquals(!enableClassMerging, clazzA.isPresent());
+    ClassSubject clazzB = inspector.clazz(B.class);
+    assertThat(clazzB, isPresent());
+    ClassSubject clazzD = inspector.clazz(D.class);
+    // TODO(110141157): Class D should be kept - vertical class merging or not.
+    assertEquals(!enableClassMerging, clazzD.isPresent());
+
+    ProcessResult result = runOnArtRaw(app, Main.class.getName());
+    // TODO(110141157): The code should run - vertical class merging or not.
+    assertEquals(enableClassMerging ? 1 : 0, result.exitCode);
+    if (!enableClassMerging) {
+      assertEquals("123456", result.stdout);
+    }
+  }
+
+  @Test
+  public void testMergedClassInIfRule() throws Exception {
+    // Class C is kept, meaning that it will not be touched.
+    // Class A will be merged into class B.
+    List<String> config = ImmutableList.of(
+        "-keep class **.Main { public static void main(java.lang.String[]); }",
+        "-keep class **.C",
+        "-if class **.A",
+        "-keep class **.D",
+        "-dontobfuscate"
+    );
+
+    check(runShrinkerRaw(shrinker, CLASSES, config));
+  }
+
+  @Test
+  public void testMergedClassFieldInIfRule() throws Exception {
+    // Class C is kept, meaning that it will not be touched.
+    // Class A will be merged into class B.
+    // Main.main access A.x, so that field exists satisfying the if rule.
+    List<String> config = ImmutableList.of(
+        "-keep class **.Main { public static void main(java.lang.String[]); }",
+        "-keep class **.C",
+        "-if class **.A { int x; }",
+        "-keep class **.D",
+        "-dontobfuscate"
+    );
+
+    check(runShrinkerRaw(shrinker, CLASSES, config));
+  }
+
+  @Test
+  public void testMergedClassMethodInIfRule() throws Exception {
+    // Class C is kept, meaning that it will not be touched.
+    // Class A will be merged into class B.
+    // Main.main access A.a(), that method exists satisfying the if rule.
+    List<String> config = ImmutableList.of(
+        "-keep class **.Main { public static void main(java.lang.String[]); }",
+        "-keep class **.C",
+        "-if class **.A { int a(); }",
+        "-keep class **.D",
+        "-dontobfuscate"
+    );
+
+    check(runShrinkerRaw(shrinker, CLASSES, config));
+  }
+}
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;