Only remove visibility bridges

Change-Id: Iab8b753c1fd340b2c84c935fb36a825794abe87e
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 e4892f2..6c3e715 100644
--- a/src/main/java/com/android/tools/r8/optimize/VisibilityBridgeRemover.java
+++ b/src/main/java/com/android/tools/r8/optimize/VisibilityBridgeRemover.java
@@ -35,7 +35,7 @@
         DexMethod target = targetExtractor.getTarget();
         InvokeKind kind = targetExtractor.getKind();
         if (target != null &&
-            target.proto.parameters.values.length == method.method.proto.parameters.values.length) {
+            target.proto == method.method.proto) {
           assert !method.accessFlags.isPrivate() && !method.accessFlags.isConstructor();
           if (kind == InvokeKind.SUPER) {
             // This is a visibility forward, so check for the direct target.
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index 075c569..28d4872 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -87,17 +87,32 @@
   /**
    * Compile an application with R8.
    */
+  protected AndroidApp compileWithR8(List<Class> classes)
+      throws CompilationException, ProguardRuleParserException, ExecutionException, IOException {
+    R8Command command = ToolHelper.prepareR8CommandBuilder(readClasses(classes)).build();
+    return ToolHelper.runR8(command);
+  }
+
+  /**
+   * Compile an application with R8.
+   */
   protected AndroidApp compileWithR8(AndroidApp app, Consumer<InternalOptions> optionsConsumer)
       throws CompilationException, ProguardRuleParserException, ExecutionException, IOException {
-    R8Command command =
-        ToolHelper.prepareR8CommandBuilder(app)
-            .build();
+    R8Command command = ToolHelper.prepareR8CommandBuilder(app).build();
     return ToolHelper.runR8(command, optionsConsumer);
   }
 
   /**
    * Compile an application with R8 using the supplied proguard configuration.
    */
+  protected AndroidApp compileWithR8(List<Class> classes, String proguardConfig)
+      throws CompilationException, ProguardRuleParserException, ExecutionException, IOException {
+    return compileWithR8(readClasses(classes), writeTextToTempFile(proguardConfig));
+  }
+
+  /**
+   * Compile an application with R8 using the supplied proguard configuration.
+   */
   protected AndroidApp compileWithR8(List<Class> classes, Path proguardConfig)
       throws CompilationException, ProguardRuleParserException, ExecutionException, IOException {
     return compileWithR8(readClasses(classes), proguardConfig);
@@ -129,6 +144,28 @@
   }
 
   /**
+   * Generate a Proguard configuration for keeping the "public static void main(String[])" method
+   * of the specified class.
+   */
+  public String keepMainProguardConfiguration(Class clazz) {
+    return "-keep public class " + clazz.getCanonicalName() + " {\n"
+        + "  public static void main(java.lang.String[]);\n"
+        + "}\n";
+  }
+
+  /**
+   * Generate a Proguard configuration for keeping the "public static void main(String[])" method
+   * of the specified class and specify if -allowaccessmodification and -dontobfuscate are added
+   * as well.
+   */
+  public String keepMainProguardConfiguration(
+      Class clazz, boolean allowaccessmodification, boolean obfuscate) {
+    return keepMainProguardConfiguration(clazz)
+        + (allowaccessmodification ? "-allowaccessmodification\n" : "")
+        + (obfuscate ? "" : "-dontobfuscate\n");
+  }
+
+  /**
    * Run application on Art with the specified main class.
    */
   protected String runOnArt(AndroidApp app, Class mainClass) throws IOException {
diff --git a/src/test/java/com/android/tools/r8/bridgeremoval/bridgestokeep/DataAdapter.java b/src/test/java/com/android/tools/r8/bridgeremoval/bridgestokeep/DataAdapter.java
new file mode 100644
index 0000000..eae4025
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/bridgeremoval/bridgestokeep/DataAdapter.java
@@ -0,0 +1,13 @@
+// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.bridgeremoval.bridgestokeep;
+
+public interface DataAdapter {
+
+  interface Observer extends ObservableList.Observer {
+  }
+
+  void registerObserver(Observer observer);
+}
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/bridgeremoval/bridgestokeep/KeepNonVisibilityBridgeMethodsTest.java b/src/test/java/com/android/tools/r8/bridgeremoval/bridgestokeep/KeepNonVisibilityBridgeMethodsTest.java
new file mode 100644
index 0000000..7e5b06a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/bridgeremoval/bridgestokeep/KeepNonVisibilityBridgeMethodsTest.java
@@ -0,0 +1,54 @@
+// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.bridgeremoval.bridgestokeep;
+
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.DexInspector.MethodSubject;
+import com.google.common.collect.ImmutableList;
+import java.lang.reflect.Method;
+import java.util.List;
+import org.junit.Test;
+
+public class KeepNonVisibilityBridgeMethodsTest extends TestBase {
+
+  private String keepMainAllowAccessModification(Class clazz, boolean obfuscate) {
+    return "-keep public class " + clazz.getCanonicalName() + " {\n"
+        + "  public static void main(java.lang.String[]);\n"
+        + "}\n"
+        + "-allowaccessmodification\n"
+        + (obfuscate ? "" : "-dontobfuscate\n");
+  }
+
+  private void run(boolean obfuscate) throws Exception {
+    List<Class> classes = ImmutableList.of(
+        DataAdapter.class,
+        SimpleDataAdapter.class,
+        ObservableList.class,
+        SimpleObservableList.class,
+        Main.class);
+    String proguardConfig = keepMainAllowAccessModification(Main.class, obfuscate);
+    DexInspector inspector = new DexInspector(compileWithR8(classes, proguardConfig));
+
+    // The bridge for registerObserver cannot be removed.
+    Method registerObserver =
+        SimpleDataAdapter.class.getMethod("registerObserver", DataAdapter.Observer.class);
+    MethodSubject subject = inspector.method(registerObserver);
+    assertTrue(subject.isPresent());
+    assertTrue(subject.isBridge());
+  }
+
+  @Test
+  public void testWithObfuscation() throws Exception {
+    run(true);
+  }
+
+  @Test
+  public void testWithoutObfuscation() throws Exception {
+    run(false);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/bridgeremoval/bridgestokeep/Main.java b/src/test/java/com/android/tools/r8/bridgeremoval/bridgestokeep/Main.java
new file mode 100644
index 0000000..4d80056
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/bridgeremoval/bridgestokeep/Main.java
@@ -0,0 +1,17 @@
+// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.bridgeremoval.bridgestokeep;
+
+// Reduced test case from code where removal of bridge methods caused failure.
+public class Main {
+
+  public static void registerObserver(DataAdapter dataAdapter) {
+    dataAdapter.registerObserver(null);
+  }
+
+  public static void main(String[] args) {
+    registerObserver(new SimpleDataAdapter());
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/bridgeremoval/bridgestokeep/ObservableList.java b/src/test/java/com/android/tools/r8/bridgeremoval/bridgestokeep/ObservableList.java
new file mode 100644
index 0000000..fa8fdce
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/bridgeremoval/bridgestokeep/ObservableList.java
@@ -0,0 +1,15 @@
+// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.bridgeremoval.bridgestokeep;
+
+import com.android.tools.r8.bridgeremoval.bridgestokeep.ObservableList.Observer;
+
+public interface ObservableList<O extends Observer> {
+
+  interface Observer {
+  }
+
+  void registerObserver(O observer);
+}
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/bridgeremoval/bridgestokeep/SimpleDataAdapter.java b/src/test/java/com/android/tools/r8/bridgeremoval/bridgestokeep/SimpleDataAdapter.java
new file mode 100644
index 0000000..148a61e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/bridgeremoval/bridgestokeep/SimpleDataAdapter.java
@@ -0,0 +1,17 @@
+// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.bridgeremoval.bridgestokeep;
+
+public class SimpleDataAdapter
+    extends SimpleObservableList<DataAdapter.Observer>
+    implements DataAdapter {
+
+  public SimpleDataAdapter() {
+  }
+
+  // This class has no implementation of method registerObserver(DataAdapter.Observer observer)
+  // from interface DataAdapter. There is one in SimpleObservableList<DataAdapter.Observer> so
+  // javac inserts a bridge with signature registerObserver(DataAdapter.Observer observer).
+}
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/bridgeremoval/bridgestokeep/SimpleObservableList.java b/src/test/java/com/android/tools/r8/bridgeremoval/bridgestokeep/SimpleObservableList.java
new file mode 100644
index 0000000..5fa667b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/bridgeremoval/bridgestokeep/SimpleObservableList.java
@@ -0,0 +1,15 @@
+// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.bridgeremoval.bridgestokeep;
+
+import com.android.tools.r8.bridgeremoval.bridgestokeep.ObservableList.Observer;
+
+public class SimpleObservableList<O extends Observer>
+    implements ObservableList<O> {
+
+  @Override
+  public void registerObserver(O observer) {
+  }
+}
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/bridgeremoval/bridgestoremove/Main.java b/src/test/java/com/android/tools/r8/bridgeremoval/bridgestoremove/Main.java
new file mode 100644
index 0000000..4c8ac31
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/bridgeremoval/bridgestoremove/Main.java
@@ -0,0 +1,13 @@
+// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.bridgeremoval.bridgestoremove;
+
+public class Main {
+
+  public static void main(String[] args) {
+    (new Outer()).create().method();
+    (new Outer.StaticSubClass()).method();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/bridgeremoval/bridgestoremove/Outer.java b/src/test/java/com/android/tools/r8/bridgeremoval/bridgestoremove/Outer.java
new file mode 100644
index 0000000..2e1b1bf
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/bridgeremoval/bridgestoremove/Outer.java
@@ -0,0 +1,26 @@
+// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.bridgeremoval.bridgestoremove;
+
+public class Outer {
+
+  class SuperClass {
+    public void method() { }
+  }
+
+  // As SuperClass is package private SubClass will have a bridge method for "method".
+  public class SubClass extends SuperClass { }
+
+  public SubClass create() {
+    return new SubClass();
+  }
+
+  static class StaticSuperClass {
+    public void method() { }
+  }
+
+  // As SuperClass is package private SubClass will have a bridge method for "method".
+  public static class StaticSubClass extends StaticSuperClass { }
+}
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/bridgeremoval/bridgestoremove/RemoveVisibilityBridgeMethodsTest.java b/src/test/java/com/android/tools/r8/bridgeremoval/bridgestoremove/RemoveVisibilityBridgeMethodsTest.java
new file mode 100644
index 0000000..c33d5b4
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/bridgeremoval/bridgestoremove/RemoveVisibilityBridgeMethodsTest.java
@@ -0,0 +1,41 @@
+// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.bridgeremoval.bridgestoremove;
+
+import static org.junit.Assert.assertFalse;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.utils.DexInspector;
+import com.google.common.collect.ImmutableList;
+import java.lang.reflect.Method;
+import java.util.List;
+import org.junit.Test;
+
+public class RemoveVisibilityBridgeMethodsTest extends TestBase {
+
+  private void run(boolean obfuscate) throws Exception {
+    List<Class> classes = ImmutableList.of(
+        Outer.class,
+        Main.class);
+    String proguardConfig = keepMainProguardConfiguration(Main.class, true, obfuscate);
+    DexInspector inspector = new DexInspector(compileWithR8(classes, proguardConfig));
+
+    List<Method> removedMethods = ImmutableList.of(
+        Outer.SubClass.class.getMethod("method"),
+        Outer.StaticSubClass.class.getMethod("method"));
+
+    removedMethods.forEach(method -> assertFalse(inspector.method(method).isPresent()));
+  }
+
+  @Test
+  public void testWithObfuscation() throws Exception {
+    run(true);
+  }
+
+  @Test
+  public void testWithoutObfuscation() throws Exception {
+    run(false);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/includedescriptorclasses/IncludeDescriptorClassesTest.java b/src/test/java/com/android/tools/r8/shaking/includedescriptorclasses/IncludeDescriptorClassesTest.java
index 0e24d9b..a02a186 100644
--- a/src/test/java/com/android/tools/r8/shaking/includedescriptorclasses/IncludeDescriptorClassesTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/includedescriptorclasses/IncludeDescriptorClassesTest.java
@@ -64,12 +64,6 @@
     return result;
   }
 
-  private String keepMain(Class clazz) {
-    return "-keep public class " + clazz.getCanonicalName() + " {\n"
-        + "  public static void main(java.lang.String[]);\n"
-        + "}";
-  }
-
   private class Result {
     final DexInspector inspector;
     final Set<String> classesAfterProguard;
@@ -135,7 +129,7 @@
       allClasses.add(mainClass);
 
       Path proguardConfig = writeTextToTempFile(
-          keepMain(mainClass),
+          keepMainProguardConfiguration(mainClass),
           "-keepclasseswithmembers class * {   ",
           "  <fields>;                         ",
           "  native <methods>;                 ",
@@ -158,7 +152,7 @@
   public void testKeepClassesWithMembers() throws Exception {
     for (Class mainClass : mainClasses) {
       Path proguardConfig = writeTextToTempFile(
-          keepMain(mainClass),
+          keepMainProguardConfiguration(mainClass),
           "-keepclasseswithmembers,includedescriptorclasses class * {  ",
           "  <fields>;                                                 ",
           "  native <methods>;                                         ",
@@ -181,7 +175,7 @@
   public void testKeepClassMembers() throws Exception {
     for (Class mainClass : mainClasses) {
       Path proguardConfig = writeTextToTempFile(
-          keepMain(mainClass),
+          keepMainProguardConfiguration(mainClass),
           "-keepclassmembers,includedescriptorclasses class * {  ",
           "  <fields>;                                           ",
           "  native <methods>;                                   ",
@@ -204,7 +198,7 @@
     public void testKeepClassMemberNames() throws Exception {
       for (Class mainClass : mainClasses) {
         Path proguardConfig = writeTextToTempFile(
-            keepMain(mainClass),
+            keepMainProguardConfiguration(mainClass),
             // same as -keepclassmembers,allowshrinking,includedescriptorclasses
             "-keepclassmembernames,includedescriptorclasses class * {  ",
             "  <fields>;                                               ",
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 4e661ba..61c3c14 100644
--- a/src/test/java/com/android/tools/r8/utils/DexInspector.java
+++ b/src/test/java/com/android/tools/r8/utils/DexInspector.java
@@ -66,8 +66,10 @@
 import com.google.common.collect.BiMap;
 import com.google.common.collect.ImmutableList;
 import java.io.IOException;
+import java.lang.reflect.Method;
 import java.nio.file.Path;
 import java.nio.file.Paths;
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Iterator;
 import java.util.List;
@@ -158,6 +160,9 @@
     }
   }
 
+  public ClassSubject clazz(Class clazz) {
+    return clazz(clazz.getTypeName());
+  }
 
   public ClassSubject clazz(String name) {
     ClassNaming naming = null;
@@ -188,6 +193,14 @@
     }, inspection);
   }
 
+  public MethodSubject method(Method method) {
+    ClassSubject clazz = clazz(method.getDeclaringClass());
+    if (!clazz.isPresent()) {
+      return new AbsentMethodSubject();
+    }
+    return clazz.method(method);
+  }
+
   private String getObfuscatedTypeName(String originalTypeName) {
     String obfuscatedType = null;
     if (mapping != null) {
@@ -244,6 +257,14 @@
 
     public abstract void forAllMethods(Consumer<FoundMethodSubject> inspection);
 
+    public MethodSubject method(Method method) {
+      List<String> parameters = new ArrayList<>();
+      for (Class<?> parameterType : method.getParameterTypes()) {
+        parameters.add(parameterType.getTypeName());
+      }
+      return method(method.getReturnType().getTypeName(), method.getName(), parameters);
+    }
+
     public abstract MethodSubject method(String returnType, String name, List<String> parameters);
 
     public MethodSubject method(MethodSignature signature) {
@@ -468,6 +489,11 @@
     public boolean isRenamed() {
       return naming == null || !getFinalDescriptor().equals(getOriginalDescriptor());
     }
+
+    @Override
+    public String toString() {
+      return dexClass.toSourceString();
+    }
   }
 
   public abstract class MemberSubject extends Subject {
@@ -489,6 +515,8 @@
 
     public abstract boolean isAbstract();
 
+    public abstract boolean isBridge();
+
     public abstract DexEncodedMethod getMethod();
 
     public Iterator<InstructionSubject> iterateInstructions() {
@@ -541,6 +569,11 @@
     }
 
     @Override
+    public boolean isBridge() {
+      return false;
+    }
+
+    @Override
     public DexEncodedMethod getMethod() {
       return null;
     }
@@ -602,6 +635,11 @@
     }
 
     @Override
+    public boolean isBridge() {
+      return dexMethod.accessFlags.isBridge();
+    }
+
+    @Override
     public DexEncodedMethod getMethod() {
       return dexMethod;
     }
@@ -629,6 +667,11 @@
         Predicate<InstructionSubject> filter) {
       return new FilteredInstructionIterator<>(this, filter);
     }
+
+    @Override
+    public String toString() {
+      return dexMethod.toSourceString();
+    }
   }
 
   public abstract class FieldSubject extends MemberSubject {