Merge "Improve information in GenericSignatureFormatError"
diff --git a/src/main/java/com/android/tools/r8/D8.java b/src/main/java/com/android/tools/r8/D8.java
index 250f2e9..1db187e 100644
--- a/src/main/java/com/android/tools/r8/D8.java
+++ b/src/main/java/com/android/tools/r8/D8.java
@@ -17,7 +17,6 @@
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.CfgPrinter;
 import com.android.tools.r8.utils.ExceptionUtils;
-import com.android.tools.r8.utils.FlagFile;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.ThreadUtils;
 import com.android.tools.r8.utils.Timing;
@@ -94,9 +93,8 @@
         });
   }
 
-  private static void run(String[] args) throws CompilationFailedException, IOException {
-    String[] expandedArgs = FlagFile.expandFlagFiles(args);
-    D8Command command = D8Command.parse(expandedArgs, CommandLineOrigin.INSTANCE).build();
+  private static void run(String[] args) throws CompilationFailedException {
+    D8Command command = D8Command.parse(args, CommandLineOrigin.INSTANCE).build();
     if (command.isPrintHelp()) {
       System.out.println(USAGE_MESSAGE);
       return;
diff --git a/src/main/java/com/android/tools/r8/D8Command.java b/src/main/java/com/android/tools/r8/D8Command.java
index 4c65134..6f956db 100644
--- a/src/main/java/com/android/tools/r8/D8Command.java
+++ b/src/main/java/com/android/tools/r8/D8Command.java
@@ -7,6 +7,7 @@
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.FlagFile;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.Reporter;
 import com.android.tools.r8.utils.StringDiagnostic;
@@ -215,9 +216,10 @@
     Path outputPath = null;
     OutputMode outputMode = null;
     boolean hasDefinedApiLevel = false;
+    String[] expandedArgs = FlagFile.expandFlagFiles(args, builder.getReporter());
     try {
-      for (int i = 0; i < args.length; i++) {
-        String arg = args[i].trim();
+      for (int i = 0; i < expandedArgs.length; i++) {
+        String arg = expandedArgs[i].trim();
         if (arg.length() == 0) {
           continue;
         } else if (arg.equals("--help")) {
@@ -243,7 +245,7 @@
         } else if (arg.equals("--file-per-class")) {
           outputMode = OutputMode.DexFilePerClassFile;
         } else if (arg.equals("--output")) {
-          String output = args[++i];
+          String output = expandedArgs[++i];
           if (outputPath != null) {
             builder.getReporter().error(new StringDiagnostic(
                 "Cannot output both to '" + outputPath.toString() + "' and '" + output + "'",
@@ -252,15 +254,15 @@
           }
           outputPath = Paths.get(output);
         } else if (arg.equals("--lib")) {
-          builder.addLibraryFiles(Paths.get(args[++i]));
+          builder.addLibraryFiles(Paths.get(expandedArgs[++i]));
         } else if (arg.equals("--classpath")) {
-          builder.addClasspathFiles(Paths.get(args[++i]));
+          builder.addClasspathFiles(Paths.get(expandedArgs[++i]));
         } else if (arg.equals("--main-dex-list")) {
-          builder.addMainDexListFiles(Paths.get(args[++i]));
+          builder.addMainDexListFiles(Paths.get(expandedArgs[++i]));
         } else if (arg.equals("--optimize-multidex-for-linearalloc")) {
           builder.setOptimizeMultidexForLinearAlloc(true);
         } else if (arg.equals("--min-api")) {
-          hasDefinedApiLevel = parseMinApi(builder, args[++i], hasDefinedApiLevel, origin);
+          hasDefinedApiLevel = parseMinApi(builder, expandedArgs[++i], hasDefinedApiLevel, origin);
         } else if (arg.equals("--intermediate")) {
           builder.setIntermediate(true);
         } else if (arg.equals("--no-desugaring")) {
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 8e1722e..16c9de8 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -51,7 +51,6 @@
 import com.android.tools.r8.utils.CfgPrinter;
 import com.android.tools.r8.utils.ExceptionUtils;
 import com.android.tools.r8.utils.FileUtils;
-import com.android.tools.r8.utils.FlagFile;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.InternalOptions.LineNumberOptimization;
 import com.android.tools.r8.utils.LineNumberOptimizer;
@@ -534,9 +533,8 @@
     }
   }
 
-  private static void run(String[] args) throws CompilationFailedException, IOException {
-    String[] expandedArgs = FlagFile.expandFlagFiles(args);
-    R8Command command = R8Command.parse(expandedArgs, CommandLineOrigin.INSTANCE).build();
+  private static void run(String[] args) throws CompilationFailedException {
+    R8Command command = R8Command.parse(args, CommandLineOrigin.INSTANCE).build();
     if (command.isPrintHelp()) {
       System.out.println(USAGE_MESSAGE);
       return;
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index ced17d4..4fc0590 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -18,6 +18,7 @@
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.ExceptionDiagnostic;
 import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.FlagFile;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.InternalOptions.LineNumberOptimization;
 import com.android.tools.r8.utils.Reporter;
@@ -513,8 +514,9 @@
       Origin argsOrigin,
       Builder builder,
       ParseState state) {
-    for (int i = 0; i < args.length; i++) {
-      String arg = args[i].trim();
+    String[] expandedArgs = FlagFile.expandFlagFiles(args, builder.getReporter());
+    for (int i = 0; i < expandedArgs.length; i++) {
+      String arg = expandedArgs[i].trim();
       if (arg.length() == 0) {
         continue;
       } else if (arg.equals("--help")) {
@@ -546,7 +548,7 @@
         }
         state.outputMode = OutputMode.ClassFile;
       } else if (arg.equals("--output")) {
-        String outputPath = args[++i];
+        String outputPath = expandedArgs[++i];
         if (state.outputPath != null) {
           builder.getReporter().error(new StringDiagnostic(
               "Cannot output both to '"
@@ -558,10 +560,10 @@
         }
         state.outputPath = Paths.get(outputPath);
       } else if (arg.equals("--lib")) {
-        builder.addLibraryFiles(Paths.get(args[++i]));
+        builder.addLibraryFiles(Paths.get(expandedArgs[++i]));
       } else if (arg.equals("--min-api")) {
         state.hasDefinedApiLevel =
-            parseMinApi(builder, args[++i], state.hasDefinedApiLevel, argsOrigin);
+            parseMinApi(builder, expandedArgs[++i], state.hasDefinedApiLevel, argsOrigin);
       } else if (arg.equals("--no-tree-shaking")) {
         builder.setDisableTreeShaking(true);
       } else if (arg.equals("--no-minification")) {
@@ -569,17 +571,17 @@
       } else if (arg.equals("--no-desugaring")) {
         builder.setDisableDesugaring(true);
       } else if (arg.equals("--main-dex-rules")) {
-        builder.addMainDexRulesFiles(Paths.get(args[++i]));
+        builder.addMainDexRulesFiles(Paths.get(expandedArgs[++i]));
       } else if (arg.equals("--main-dex-list")) {
-        builder.addMainDexListFiles(Paths.get(args[++i]));
+        builder.addMainDexListFiles(Paths.get(expandedArgs[++i]));
       } else if (arg.equals("--main-dex-list-output")) {
-        builder.setMainDexListOutputPath(Paths.get(args[++i]));
+        builder.setMainDexListOutputPath(Paths.get(expandedArgs[++i]));
       } else if (arg.equals("--optimize-multidex-for-linearalloc")) {
         builder.setOptimizeMultidexForLinearAlloc(true);
       } else if (arg.equals("--pg-conf")) {
-        builder.addProguardConfigurationFiles(Paths.get(args[++i]));
+        builder.addProguardConfigurationFiles(Paths.get(expandedArgs[++i]));
       } else if (arg.equals("--pg-map-output")) {
-        builder.setProguardMapOutputPath(Paths.get(args[++i]));
+        builder.setProguardMapOutputPath(Paths.get(expandedArgs[++i]));
       } else {
         if (arg.startsWith("--")) {
           builder.getReporter().error(new StringDiagnostic("Unknown option: " + arg, argsOrigin));
diff --git a/src/main/java/com/android/tools/r8/graph/AccessFlags.java b/src/main/java/com/android/tools/r8/graph/AccessFlags.java
index 324362a..626e5cc 100644
--- a/src/main/java/com/android/tools/r8/graph/AccessFlags.java
+++ b/src/main/java/com/android/tools/r8/graph/AccessFlags.java
@@ -68,6 +68,25 @@
     return flags;
   }
 
+  public boolean isMoreVisibleThan(AccessFlags other) {
+    return visibilityOrdinal() > other.visibilityOrdinal();
+  }
+
+  private int visibilityOrdinal() {
+    // public > protected > package > private
+    if (isPublic()) {
+      return 3;
+    }
+    if (isProtected()) {
+      return 2;
+    }
+    if (isPrivate()) {
+      return 0;
+    }
+    // Package-private
+    return 1;
+  }
+
   public boolean isPublic() {
     return isSet(Constants.ACC_PUBLIC);
   }
diff --git a/src/main/java/com/android/tools/r8/shaking/AbstractMethodRemover.java b/src/main/java/com/android/tools/r8/shaking/AbstractMethodRemover.java
index e4e2ed7..610bfd3 100644
--- a/src/main/java/com/android/tools/r8/shaking/AbstractMethodRemover.java
+++ b/src/main/java/com/android/tools/r8/shaking/AbstractMethodRemover.java
@@ -52,7 +52,7 @@
     List<DexEncodedMethod> methods = null;
     for (int i = 0; i < virtualMethods.length; i++) {
       DexEncodedMethod method = virtualMethods[i];
-      if (scope.addMethod(method.method) || !method.accessFlags.isAbstract()) {
+      if (scope.addMethodIfMoreVisible(method) || !method.accessFlags.isAbstract()) {
         if (methods != null) {
           methods.add(method);
         }
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
index 27b2da7..d718026 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -739,7 +739,6 @@
       transitionNonAbstractMethodsToLiveAndShadow(
           reachableMethods.getItems(), instantiatedType, seen.newNestedScope());
     }
-    seen = seen.newNestedScope();
     for (DexType subInterface : clazz.interfaces.values) {
       transitionDefaultMethodsForInstantiatedClass(subInterface, instantiatedType, seen);
     }
@@ -748,7 +747,7 @@
   private void transitionNonAbstractMethodsToLiveAndShadow(Iterable<DexEncodedMethod> reachable,
       DexType instantiatedType, ScopedDexMethodSet seen) {
     for (DexEncodedMethod encodedMethod : reachable) {
-      if (seen.addMethod(encodedMethod.method)) {
+      if (seen.addMethod(encodedMethod)) {
         // Abstract methods do shadow implementations but they cannot be live, as they have no
         // code.
         if (!encodedMethod.accessFlags.isAbstract()) {
diff --git a/src/main/java/com/android/tools/r8/shaking/ScopedDexMethodSet.java b/src/main/java/com/android/tools/r8/shaking/ScopedDexMethodSet.java
index 39f07ee..3587cf4 100644
--- a/src/main/java/com/android/tools/r8/shaking/ScopedDexMethodSet.java
+++ b/src/main/java/com/android/tools/r8/shaking/ScopedDexMethodSet.java
@@ -3,19 +3,20 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking;
 
+import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.utils.MethodSignatureEquivalence;
 import com.google.common.base.Equivalence;
 import com.google.common.base.Equivalence.Wrapper;
-import java.util.HashSet;
-import java.util.Set;
+import java.util.HashMap;
+import java.util.Map;
 
 class ScopedDexMethodSet {
 
   private static final Equivalence<DexMethod> METHOD_EQUIVALENCE = MethodSignatureEquivalence.get();
 
   private final ScopedDexMethodSet parent;
-  private final Set<Wrapper<DexMethod>> items = new HashSet<>();
+  private final Map<Wrapper<DexMethod>, DexEncodedMethod> items = new HashMap<>();
 
   public ScopedDexMethodSet() {
     this(null);
@@ -29,14 +30,32 @@
     return new ScopedDexMethodSet(this);
   }
 
-  private boolean contains(Wrapper<DexMethod> item) {
-    return items.contains(item)
-        || ((parent != null) && parent.contains(item));
+  private DexEncodedMethod lookup(Wrapper<DexMethod> item) {
+    DexEncodedMethod ownMethod = items.get(item);
+    return ownMethod != null ? ownMethod : (parent != null ? parent.lookup(item) : null);
   }
 
-  public boolean addMethod(DexMethod method) {
-    Wrapper<DexMethod> wrapped = METHOD_EQUIVALENCE.wrap(method);
-    return !contains(wrapped) && items.add(wrapped);
+  private boolean contains(Wrapper<DexMethod> item) {
+    return lookup(item) != null;
+  }
+
+  public boolean addMethod(DexEncodedMethod method) {
+    Wrapper<DexMethod> wrapped = METHOD_EQUIVALENCE.wrap(method.method);
+    if (contains(wrapped)) {
+      return false;
+    }
+    items.put(wrapped, method);
+    return true;
+  }
+
+  public boolean addMethodIfMoreVisible(DexEncodedMethod method) {
+    Wrapper<DexMethod> wrapped = METHOD_EQUIVALENCE.wrap(method.method);
+    DexEncodedMethod existing = lookup(wrapped);
+    if (existing == null || method.accessFlags.isMoreVisibleThan(existing.accessFlags)) {
+      items.put(wrapped, method);
+      return true;
+    }
+    return false;
   }
 
   public ScopedDexMethodSet getParent() {
diff --git a/src/main/java/com/android/tools/r8/utils/ExceptionUtils.java b/src/main/java/com/android/tools/r8/utils/ExceptionUtils.java
index 577632a..9c7f525 100644
--- a/src/main/java/com/android/tools/r8/utils/ExceptionUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/ExceptionUtils.java
@@ -74,7 +74,7 @@
   }
 
   public interface MainAction {
-    void run() throws CompilationFailedException, IOException;
+    void run() throws CompilationFailedException;
   }
 
   public static void withMainProgramHandler(MainAction action) {
@@ -84,7 +84,7 @@
       // Detail of the errors were already reported
       System.err.println("Compilation failed");
       System.exit(STATUS_ERROR);
-    } catch (RuntimeException  | IOException e) {
+    } catch (RuntimeException e) {
       System.err.println("Compilation failed with an internal error.");
       Throwable cause = e.getCause() == null ? e : e.getCause();
       cause.printStackTrace();
diff --git a/src/main/java/com/android/tools/r8/utils/FlagFile.java b/src/main/java/com/android/tools/r8/utils/FlagFile.java
index 2354066..4791217 100644
--- a/src/main/java/com/android/tools/r8/utils/FlagFile.java
+++ b/src/main/java/com/android/tools/r8/utils/FlagFile.java
@@ -4,18 +4,41 @@
 
 package com.android.tools.r8.utils;
 
+import com.android.tools.r8.origin.Origin;
 import java.io.IOException;
 import java.nio.file.Files;
+import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.ArrayList;
 import java.util.List;
 
 public class FlagFile {
-  public static String[] expandFlagFiles(String[] args) throws IOException {
+
+  private static class FlagFileOrigin extends Origin {
+    private final Path path;
+
+    protected FlagFileOrigin(Path path) {
+      super(Origin.root());
+      this.path = path;
+    }
+
+    @Override
+    public String part() {
+      return "flag file argument: '@" + path + "'";
+    }
+  }
+
+  public static String[] expandFlagFiles(String[] args, Reporter reporter) {
     List<String> flags = new ArrayList<>(args.length);
     for (String arg : args) {
       if (arg.startsWith("@")) {
-        flags.addAll(Files.readAllLines(Paths.get(arg.substring(1))));
+        Path flagFilePath = Paths.get(arg.substring(1));
+        try {
+          flags.addAll(Files.readAllLines(flagFilePath));
+        } catch (IOException e) {
+          Origin origin = new FlagFileOrigin(flagFilePath);
+          reporter.error(new ExceptionDiagnostic(e, origin));
+        }
       } else {
         flags.add(arg);
       }
diff --git a/src/test/examples/abstractmethodremoval/AbstractMethodRemoval.java b/src/test/examples/abstractmethodremoval/AbstractMethodRemoval.java
new file mode 100644
index 0000000..1da772d
--- /dev/null
+++ b/src/test/examples/abstractmethodremoval/AbstractMethodRemoval.java
@@ -0,0 +1,34 @@
+// 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 abstractmethodremoval;
+
+import abstractmethodremoval.a.PackageBase;
+import abstractmethodremoval.a.Public;
+import abstractmethodremoval.b.Impl1;
+import abstractmethodremoval.b.Impl2;
+
+public class AbstractMethodRemoval {
+
+  private static int dummy;
+
+  public static void main(String[] args) {
+    dummy = args.length;
+    invokeFoo(new Impl1());
+    invokeFoo(new Impl2());
+    invokeFoo(new Impl2());
+    PackageBase.invokeFoo(new Impl1());
+    PackageBase.invokeFoo(new Impl2());
+    PackageBase.invokeFoo(new Impl2());
+  }
+
+  private static void invokeFoo(Public aPublic) {
+    // Enough instructions to avoid inlining.
+    aPublic.foo(dummy() + dummy() + dummy() + dummy() + dummy() + dummy() + dummy() + dummy());
+  }
+
+  private static int dummy() {
+    // Enough instructions to avoid inlining.
+    return dummy + dummy + dummy + dummy + dummy + dummy + dummy + dummy + dummy + dummy + dummy;
+  }
+}
diff --git a/src/test/examples/abstractmethodremoval/a/PackageBase.java b/src/test/examples/abstractmethodremoval/a/PackageBase.java
new file mode 100644
index 0000000..1075b57
--- /dev/null
+++ b/src/test/examples/abstractmethodremoval/a/PackageBase.java
@@ -0,0 +1,12 @@
+// 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 abstractmethodremoval.a;
+
+public abstract class PackageBase {
+  abstract void foo(int i);
+
+  public static void invokeFoo(PackageBase o) {
+    o.foo(0);
+  }
+}
diff --git a/src/test/examples/abstractmethodremoval/a/Public.java b/src/test/examples/abstractmethodremoval/a/Public.java
new file mode 100644
index 0000000..492ab87
--- /dev/null
+++ b/src/test/examples/abstractmethodremoval/a/Public.java
@@ -0,0 +1,9 @@
+// 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 abstractmethodremoval.a;
+
+public abstract class Public extends PackageBase {
+  @Override
+  public abstract void foo(int i);
+}
diff --git a/src/test/examples/abstractmethodremoval/b/Impl1.java b/src/test/examples/abstractmethodremoval/b/Impl1.java
new file mode 100644
index 0000000..bfdf4d1
--- /dev/null
+++ b/src/test/examples/abstractmethodremoval/b/Impl1.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 abstractmethodremoval.b;
+
+import abstractmethodremoval.a.Public;
+
+public class Impl1 extends Public {
+  @Override
+  public void foo(int i) {
+    System.out.println("Impl1.foo(" + i + ")");
+  }
+}
diff --git a/src/test/examples/abstractmethodremoval/b/Impl2.java b/src/test/examples/abstractmethodremoval/b/Impl2.java
new file mode 100644
index 0000000..a92e2a1
--- /dev/null
+++ b/src/test/examples/abstractmethodremoval/b/Impl2.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 abstractmethodremoval.b;
+
+import abstractmethodremoval.a.Public;
+
+public class Impl2 extends Public {
+  @Override
+  public void foo(int i) {
+    System.out.println("Impl2.foo(" + i + ")");
+  }
+}
diff --git a/src/test/examples/abstractmethodremoval/keep-rules.txt b/src/test/examples/abstractmethodremoval/keep-rules.txt
new file mode 100644
index 0000000..ec37858
--- /dev/null
+++ b/src/test/examples/abstractmethodremoval/keep-rules.txt
@@ -0,0 +1 @@
+-keep public class abstractmethodremoval.AbstractMethodRemoval { public static void main(...); }
diff --git a/src/test/java/com/android/tools/r8/D8CommandTest.java b/src/test/java/com/android/tools/r8/D8CommandTest.java
index 5576058..1b846a5 100644
--- a/src/test/java/com/android/tools/r8/D8CommandTest.java
+++ b/src/test/java/com/android/tools/r8/D8CommandTest.java
@@ -9,6 +9,7 @@
 import static java.nio.file.StandardOpenOption.CREATE_NEW;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertTrue;
 
 import com.android.sdklib.AndroidVersion;
@@ -99,7 +100,7 @@
     FileUtils.writeTextFile(
         flagsFile,
         "--output",
-        output.toString(),
+        "output.zip",
         "--min-api",
         "24",
         input.toString());
@@ -110,6 +111,19 @@
     assertEquals(Tool.D8, marker.getTool());
   }
 
+  @Test(expected=CompilationFailedException.class)
+  public void nonExistingFlagsFile() throws Throwable {
+    Path working = temp.getRoot().toPath();
+    Path flags = working.resolve("flags.txt").toAbsolutePath();
+    assertNotEquals(0, ToolHelper.forkR8(working, "@flags.txt").exitCode);
+    DiagnosticsChecker.checkErrorsContains("File not found", handler ->
+        D8.run(
+            D8Command.parse(
+                new String[] { "@" + flags.toString() },
+                EmbeddedOrigin.INSTANCE,
+                handler).build()));
+  }
+
   @Test
   public void printsHelpOnNoInput() throws Throwable {
     ProcessResult result = ToolHelper.forkD8(temp.getRoot().toPath());
diff --git a/src/test/java/com/android/tools/r8/DiagnosticsChecker.java b/src/test/java/com/android/tools/r8/DiagnosticsChecker.java
index 2265c3e..2f45ae2 100644
--- a/src/test/java/com/android/tools/r8/DiagnosticsChecker.java
+++ b/src/test/java/com/android/tools/r8/DiagnosticsChecker.java
@@ -49,13 +49,14 @@
     try {
       runner.run(handler);
     } catch (CompilationFailedException e) {
+      List<String> messages = ListUtils.map(handler.errors, Diagnostic::getDiagnosticMessage);
       System.out.println("Expecting match for '" + snippet + "'");
-      System.out.println("StdErr:\n" + handler.errors);
+      System.out.println("StdErr:\n" + messages);
       assertTrue(
           "Expected to find snippet '"
               + snippet
               + "' in error messages:\n"
-              + String.join("\n", ListUtils.map(handler.errors, Diagnostic::getDiagnosticMessage)),
+              + String.join("\n", messages),
           handler.errors.stream().anyMatch(d -> d.getDiagnosticMessage().contains(snippet)));
       throw e;
     }
diff --git a/src/test/java/com/android/tools/r8/R8CommandTest.java b/src/test/java/com/android/tools/r8/R8CommandTest.java
index c1072bd..fbd88be 100644
--- a/src/test/java/com/android/tools/r8/R8CommandTest.java
+++ b/src/test/java/com/android/tools/r8/R8CommandTest.java
@@ -101,7 +101,7 @@
     FileUtils.writeTextFile(
         flagsFile,
         "--output",
-        output.toString(),
+        "output.zip",
         "--min-api",
         "24",
         "--lib",
@@ -114,6 +114,20 @@
     assertEquals(Tool.R8, marker.getTool());
   }
 
+
+  @Test(expected=CompilationFailedException.class)
+  public void nonExistingFlagsFile() throws Throwable {
+    Path working = temp.getRoot().toPath();
+    Path flags = working.resolve("flags.txt").toAbsolutePath();
+    assertNotEquals(0, ToolHelper.forkR8(working, "@flags.txt").exitCode);
+    DiagnosticsChecker.checkErrorsContains("File not found", handler ->
+        R8.run(
+            R8Command.parse(
+                new String[] { "@" + flags.toString() },
+                EmbeddedOrigin.INSTANCE,
+                handler).build()));
+  }
+
   @Test
   public void printsHelpOnNoInput() throws Throwable {
     ProcessResult result = ToolHelper.forkR8(temp.getRoot().toPath());
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesTest.java b/src/test/java/com/android/tools/r8/R8RunExamplesTest.java
index 913278a..2ac50a6 100644
--- a/src/test/java/com/android/tools/r8/R8RunExamplesTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunExamplesTest.java
@@ -27,6 +27,7 @@
   @Parameters(name = "{0}_{1}_{2}_{3}_{5}_{6}")
   public static Collection<String[]> data() {
     String[] tests = {
+        "abstractmethodremoval.AbstractMethodRemoval",
         "arithmetic.Arithmetic",
         "arrayaccess.ArrayAccess",
         "barray.BArray",
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAbstractMethodRemovalTest.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAbstractMethodRemovalTest.java
new file mode 100644
index 0000000..a5b13a7
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAbstractMethodRemovalTest.java
@@ -0,0 +1,49 @@
+// 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.examples;
+
+import com.android.tools.r8.TestBase.MinifyMode;
+import com.android.tools.r8.shaking.TreeShakingTest;
+import com.google.common.collect.ImmutableList;
+import java.util.ArrayList;
+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 TreeShakingAbstractMethodRemovalTest extends TreeShakingTest {
+
+  @Parameters(name = "mode:{0}-{1} minify:{2}")
+  public static Collection<Object[]> data() {
+    List<Object[]> parameters = new ArrayList<>();
+    for (MinifyMode minify : MinifyMode.values()) {
+      parameters.add(new Object[] {Frontend.JAR, Backend.CF, minify});
+      parameters.add(new Object[] {Frontend.JAR, Backend.DEX, minify});
+      parameters.add(new Object[] {Frontend.DEX, Backend.DEX, minify});
+    }
+    return parameters;
+  }
+
+  public TreeShakingAbstractMethodRemovalTest(
+      Frontend frontend, Backend backend, MinifyMode minify) {
+    super(
+        "examples/abstractmethodremoval",
+        "abstractmethodremoval.AbstractMethodRemoval",
+        frontend,
+        backend,
+        minify);
+  }
+
+  @Test
+  public void test() throws Exception {
+    runTest(
+        null,
+        null,
+        null,
+        ImmutableList.of("src/test/examples/abstractmethodremoval/keep-rules.txt"));
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/smali/IfZeroObjectTest.java b/src/test/java/com/android/tools/r8/smali/IfZeroObjectTest.java
new file mode 100644
index 0000000..ebcd835
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/smali/IfZeroObjectTest.java
@@ -0,0 +1,68 @@
+// 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.smali;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import org.junit.Test;
+
+public class IfZeroObjectTest extends SmaliTestBase {
+
+  public static final String CLASS = "Test";
+
+  @Test
+  public void testObjectIfs() throws Throwable {
+    ProcessResult result = testIfs("ExpectedToPass", Arrays.asList(
+        "eqz",
+        "nez"));
+    assertEquals(result.toString(), 0, result.exitCode);
+  }
+
+  @Test
+  public void testNumericIfs() throws Throwable {
+    ProcessResult result = testIfs("ExpectedToFail", Arrays.asList(
+        "ltz",
+        "gez",
+        "gtz",
+        "lez"));
+    assertEquals(result.toString(), 1, result.exitCode);
+    assertTrue("Did not find 'Verification error' in " + result.stderr,
+        result.stderr.contains("Verification error") || result.stderr.contains("VerifyError"));
+  }
+
+  private ProcessResult testIfs(String clazz, List<String> ifZeroOps) throws Throwable {
+
+    SmaliBuilder builder = new SmaliBuilder(clazz);
+    for (String ifZeroOp : ifZeroOps) {
+      builder.addStaticMethod("int", "if" + ifZeroOp, Collections.singletonList("java.lang.Object"),
+          1,
+          "    if-" + ifZeroOp + " p0, :L",
+          "    const v0, 0",
+          "    return v0",
+          "  :L",
+          "    const v0, 1",
+          "    return v0"
+      );
+    }
+
+    List<String> main = new ArrayList<>();
+    main.add("  sget-object         v0, Ljava/lang/System;->out:Ljava/io/PrintStream;");
+    for (String ifZeroOp : ifZeroOps) {
+      main.add("  invoke-static { p0 }, L" + clazz + ";->if" + ifZeroOp + "(Ljava/lang/Object;)I");
+      main.add("  move-result v1");
+      main.add("  invoke-virtual { v0, v1 }, Ljava/io/PrintStream;->print(I)V");
+    }
+    main.add("  return-void");
+    builder.addMainMethod(2, main.toArray(new String[0]));
+
+    return runOnArtRaw(builder.build(), clazz);
+  }
+}