Merge "Handle default methods properly during tree shaking."
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java b/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
index a2caf1d..bf4dd3f 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
@@ -193,18 +193,28 @@
     return holder.accessFlags.isAbstract();
   }
 
+  private boolean holderIsInterface(Descriptor<?, ?> desc) {
+    DexClass holder = definitionFor(desc.getHolder());
+    return holder.accessFlags.isInterface();
+  }
+
   // For mapping invoke interface instruction to target methods.
   public Set<DexEncodedMethod> lookupInterfaceTargets(DexMethod method) {
-    Set<DexEncodedMethod> result = new HashSet<>();
     Set<DexType> set = subtypes(method.holder);
-    if (set != null) {
-      for (DexType type : set) {
-        DexClass clazz = definitionFor(type);
-        if (!clazz.isInterface()) {
-          DexEncodedMethod targetMethod = lookupVirtualTarget(type, method);
-          if (targetMethod != null) {
-            result.add(targetMethod);
-          }
+    if (set == null) {
+      return Collections.emptySet();
+    }
+    assert holderIsInterface(method);
+    Set<DexEncodedMethod> result = new HashSet<>();
+    for (DexType type : set) {
+      DexClass clazz = definitionFor(type);
+      // Default methods are looked up when looking at a specific subtype that does not
+      // override them, so we ignore interfaces here. Otherwise, we would look up default methods
+      // that are factually never used.
+      if (!clazz.isInterface()) {
+        DexEncodedMethod targetMethod = lookupVirtualTarget(type, method);
+        if (targetMethod != null) {
+          result.add(targetMethod);
         }
       }
     }
@@ -212,9 +222,8 @@
   }
 
   public DexEncodedMethod lookupSingleInterfaceTarget(DexMethod method) {
-    assert method != null;
     DexClass holder = definitionFor(method.holder);
-    if ((holder == null) || holder.isLibraryClass()) {
+    if ((holder == null) || holder.isLibraryClass() || !holder.accessFlags.isInterface()) {
       return null;
     }
     DexEncodedMethod result = null;
@@ -222,6 +231,9 @@
     if (set != null) {
       for (DexType type : set) {
         DexClass clazz = definitionFor(type);
+        // Default methods are looked up when looking at a specific subtype that does not
+        // override them, so we ignore interfaces here. Otherwise, we would look up default methods
+        // that are factually never used.
         if (!clazz.isInterface()) {
           DexEncodedMethod t = lookupVirtualTarget(type, method);
           if (t != null) {
diff --git a/src/main/java/com/android/tools/r8/graph/DexType.java b/src/main/java/com/android/tools/r8/graph/DexType.java
index 5aaf308..478af70 100644
--- a/src/main/java/com/android/tools/r8/graph/DexType.java
+++ b/src/main/java/com/android/tools/r8/graph/DexType.java
@@ -156,7 +156,7 @@
 
   /**
    * Apply the given function to all classes that directly extend this class.
-   *
+   * <p>
    * If this class is an interface, then this method will visit all sub-interfaces. This deviates
    * from the dex-file encoding, where subinterfaces "implement" their super interfaces. However,
    * it is consistent with the source language.
@@ -185,7 +185,7 @@
 
   /**
    * Apply the given function to all classes that directly implement this interface.
-   *
+   * <p>
    * The implementation does not consider how the hierarchy is encoded in the dex file, where
    * interfaces "implement" their super interfaces. Instead it takes the view of the source
    * language, where interfaces "extend" their superinterface.
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 fed236a..e4e2ed7 100644
--- a/src/main/java/com/android/tools/r8/shaking/AbstractMethodRemover.java
+++ b/src/main/java/com/android/tools/r8/shaking/AbstractMethodRemover.java
@@ -6,16 +6,10 @@
 import com.android.tools.r8.graph.AppInfoWithSubtyping;
 import com.android.tools.r8.graph.DexClass;
 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.logging.Log;
-import com.android.tools.r8.utils.MethodSignatureEquivalence;
-import com.google.common.base.Equivalence;
-import com.google.common.base.Equivalence.Wrapper;
 import java.util.ArrayList;
-import java.util.HashSet;
 import java.util.List;
-import java.util.Set;
 
 /**
  * Removes abstract methods if they only shadow methods of the same signature in a superclass.
@@ -29,20 +23,20 @@
 public class AbstractMethodRemover {
 
   private final AppInfoWithSubtyping appInfo;
-  private ScopedDexItemSet scope;
+  private ScopedDexMethodSet scope = new ScopedDexMethodSet();
 
   public AbstractMethodRemover(AppInfoWithSubtyping appInfo) {
     this.appInfo = appInfo;
   }
 
   public void run() {
-    assert scope == null;
+    assert scope.getParent() == null;
     processClass(appInfo.dexItemFactory.objectType);
   }
 
   private void processClass(DexType type) {
     DexClass holder = appInfo.definitionFor(type);
-    scope = new ScopedDexItemSet(scope);
+    scope = scope.newNestedScope();
     if (holder != null && !holder.isLibraryClass()) {
       holder.setVirtualMethods(processMethods(holder.virtualMethods()));
     }
@@ -77,29 +71,4 @@
     return methods == null ? virtualMethods : methods.toArray(new DexEncodedMethod[methods.size()]);
   }
 
-  private static class ScopedDexItemSet {
-
-    private static Equivalence<DexMethod> METHOD_EQUIVALENCE = MethodSignatureEquivalence.get();
-
-    private final ScopedDexItemSet parent;
-    private final Set<Wrapper<DexMethod>> items = new HashSet<>();
-
-    private ScopedDexItemSet(ScopedDexItemSet parent) {
-      this.parent = parent;
-    }
-
-    private boolean contains(Wrapper<DexMethod> item) {
-      return items.contains(item)
-          || ((parent != null) && parent.contains(item));
-    }
-
-    boolean addMethod(DexMethod method) {
-      Wrapper<DexMethod> wrapped = METHOD_EQUIVALENCE.wrap(method);
-      return !contains(wrapped) && items.add(wrapped);
-    }
-
-    ScopedDexItemSet getParent() {
-      return parent;
-    }
-  }
 }
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 4c30d36..41d06fc 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -31,9 +31,7 @@
 import com.android.tools.r8.logging.Log;
 import com.android.tools.r8.shaking.RootSetBuilder.RootSet;
 import com.android.tools.r8.utils.InternalOptions;
-import com.android.tools.r8.utils.MethodSignatureEquivalence;
 import com.android.tools.r8.utils.Timing;
-import com.google.common.base.Equivalence.Wrapper;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.ImmutableSortedSet;
@@ -160,12 +158,6 @@
    */
   private final Map<DexType, Set<DexAnnotation>> deferredAnnotations = new IdentityHashMap<>();
 
-  /**
-   * Methods that have been seen as not reachable due to related classes not being instantiated.
-   * As more instantiated types are collected this set needs to be re-visited.
-   */
-  private List<DexEncodedMethod> pendingAdditionalInstantiatedTypes = new ArrayList<>();
-
   public Enqueuer(AppInfoWithSubtyping appInfo, InternalOptions options) {
     this.appInfo = appInfo;
     this.options = options;
@@ -510,11 +502,13 @@
    *
    * <p>Only methods that are visible in this type are considered. That is, only those methods that
    * are either defined directly on this type or that are defined on a supertype but are not
-   * shadowed by another inherited method.
+   * shadowed by another inherited method. Furthermore, default methods from implemented interfaces
+   * that are not otherwise shadowed are considered, too.
    */
-  private void transitionMethodsForInstantiatedClass(DexType type) {
-    Set<Wrapper<DexMethod>> seen = new HashSet<>();
-    MethodSignatureEquivalence equivalence = MethodSignatureEquivalence.get();
+  private void transitionMethodsForInstantiatedClass(DexType instantiatedType) {
+    ScopedDexMethodSet seen = new ScopedDexMethodSet();
+    Set<DexType> interfaces = Sets.newIdentityHashSet();
+    DexType type = instantiatedType;
     do {
       DexClass clazz = appInfo.definitionFor(type);
       if (clazz == null) {
@@ -525,15 +519,52 @@
       SetWithReason<DexEncodedMethod> reachableMethods = reachableVirtualMethods.get(type);
       if (reachableMethods != null) {
         for (DexEncodedMethod encodedMethod : reachableMethods.getItems()) {
-          Wrapper<DexMethod> ignoringClass = equivalence.wrap(encodedMethod.method);
-          if (!seen.contains(ignoringClass)) {
-            seen.add(ignoringClass);
-            markVirtualMethodAsLive(encodedMethod, KeepReason.reachableFromLiveType(type));
+          if (seen.addMethod(encodedMethod.method)) {
+            markVirtualMethodAsLive(encodedMethod,
+                KeepReason.reachableFromLiveType(instantiatedType));
           }
         }
       }
+      Collections.addAll(interfaces, clazz.interfaces.values);
       type = clazz.superType;
     } while (type != null && !instantiatedTypes.contains(type));
+    // The set now contains all virtual methods on the type and its supertype that are reachable.
+    // In a second step, we now look at interfaces. We have to do this in this order due to JVM
+    // semantics for default methods. A default method is only reachable if it is not overridden in
+    // any superclass. Also, it is not defined which default method is chosen if multiple
+    // interfaces define the same default method. Hence, for every interface (direct or indirect),
+    // we have to look at the interface chain and mark default methods as reachable, not taking
+    // the shadowing of other interface chains into account.
+    // See https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-5.html#jvms-5.4.3.3
+    for (DexType iface : interfaces) {
+      DexClass clazz = appInfo.definitionFor(iface);
+      if (clazz == null) {
+        reportMissingClass(iface);
+        // TODO(herhut): In essence, our subtyping chain is broken here. Handle that case better.
+        break;
+      }
+      transitionDefaultMethodsForInstantiatedClass(iface, instantiatedType, seen);
+    }
+  }
+
+  private void transitionDefaultMethodsForInstantiatedClass(DexType iface, DexType instantiatedType,
+      ScopedDexMethodSet seen) {
+    DexClass clazz = appInfo.definitionFor(iface);
+    assert clazz.accessFlags.isInterface();
+    SetWithReason<DexEncodedMethod> reachableMethods = reachableVirtualMethods.get(iface);
+    if (reachableMethods != null) {
+      seen = seen.newNestedScope();
+      for (DexEncodedMethod encodedMethod : reachableMethods.getItems()) {
+        assert !encodedMethod.accessFlags.isAbstract();
+        if (seen.addMethod(encodedMethod.method)) {
+          markVirtualMethodAsLive(encodedMethod,
+              KeepReason.reachableFromLiveType(instantiatedType));
+        }
+      }
+      for (DexType subInterface : clazz.interfaces.values) {
+        transitionDefaultMethodsForInstantiatedClass(subInterface, instantiatedType, seen);
+      }
+    }
   }
 
   /**
@@ -685,39 +716,33 @@
       SetWithReason<DexEncodedMethod> reachable = reachableVirtualMethods
           .computeIfAbsent(encodedMethod.method.holder, (ignore) -> new SetWithReason<>());
       if (reachable.add(encodedMethod, reason)) {
-        handleIsInstantiatedOrHasInstantiatedSubtype(encodedMethod);
-      }
-    }
-  }
-
-  private void handleIsInstantiatedOrHasInstantiatedSubtype(DexEncodedMethod encodedMethod) {
-    // If the holder type is instantiated, the method is live. Otherwise check whether we find
-    // a subtype that does not shadow this methods but is instantiated.
-    // Note that library classes are always considered instantiated, as we do not know where
-    // they are instantiated.
-    if (isInstantiatedOrHasInstantiatedSubtype(encodedMethod.method.holder)) {
-      if (instantiatedTypes.contains(encodedMethod.method.holder)) {
-        markVirtualMethodAsLive(encodedMethod,
-            KeepReason.reachableFromLiveType(encodedMethod.method.holder));
-      } else {
-        Deque<DexType> worklist = new ArrayDeque<>();
-        fillWorkList(worklist, encodedMethod.method.holder);
-        while (!worklist.isEmpty()) {
-          DexType current = worklist.pollFirst();
-          DexClass currentHolder = appInfo.definitionFor(current);
-          if (currentHolder == null
-              || currentHolder.findVirtualTarget(encodedMethod.method) != null) {
-            continue;
+        // If the holder type is instantiated, the method is live. Otherwise check whether we find
+        // a subtype that does not shadow this methods but is instantiated.
+        // Note that library classes are always considered instantiated, as we do not know where
+        // they are instantiated.
+        if (isInstantiatedOrHasInstantiatedSubtype(encodedMethod.method.holder)) {
+          if (instantiatedTypes.contains(encodedMethod.method.holder)) {
+            markVirtualMethodAsLive(encodedMethod,
+                KeepReason.reachableFromLiveType(encodedMethod.method.holder));
+          } else {
+            Deque<DexType> worklist = new ArrayDeque<>();
+            fillWorkList(worklist, encodedMethod.method.holder);
+            while (!worklist.isEmpty()) {
+              DexType current = worklist.pollFirst();
+              DexClass currentHolder = appInfo.definitionFor(current);
+              if (currentHolder == null
+                  || currentHolder.findVirtualTarget(encodedMethod.method) != null) {
+                continue;
+              }
+              if (instantiatedTypes.contains(current)) {
+                markVirtualMethodAsLive(encodedMethod, KeepReason.reachableFromLiveType(current));
+                break;
+              }
+              fillWorkList(worklist, current);
+            }
           }
-          if (instantiatedTypes.contains(current)) {
-            markVirtualMethodAsLive(encodedMethod, KeepReason.reachableFromLiveType(current));
-            break;
-          }
-          fillWorkList(worklist, current);
         }
       }
-    } else {
-      pendingAdditionalInstantiatedTypes.add(encodedMethod);
     }
   }
 
@@ -790,22 +815,38 @@
   private AppInfoWithLiveness trace(Timing timing) {
     timing.begin("Grow the tree.");
     try {
-      int instantiatedTypesCount = 0;
-      while (true) {
-        doTrace();
-        // If methods where not considered due to relevant types not being instantiated reconsider
-        // if more instantiated types where collected.
-        if (pendingAdditionalInstantiatedTypes.size() > 0
-            && instantiatedTypes.items.size() > instantiatedTypesCount) {
-          instantiatedTypesCount = instantiatedTypes.items.size();
-          List<DexEncodedMethod> reconsider = pendingAdditionalInstantiatedTypes;
-          pendingAdditionalInstantiatedTypes = new ArrayList<>();
-          reconsider.forEach(this::handleIsInstantiatedOrHasInstantiatedSubtype);
-        } else {
-          break;
+      while (!workList.isEmpty()) {
+        Action action = workList.poll();
+        switch (action.kind) {
+          case MARK_INSTANTIATED:
+            processNewlyInstantiatedClass((DexClass) action.target, action.reason);
+            break;
+          case MARK_REACHABLE_FIELD:
+            markFieldAsReachable((DexField) action.target, action.reason);
+            break;
+          case MARK_REACHABLE_VIRTUAL:
+            markVirtualMethodAsReachable((DexMethod) action.target, false, action.reason);
+            break;
+          case MARK_REACHABLE_INTERFACE:
+            markVirtualMethodAsReachable((DexMethod) action.target, true, action.reason);
+            break;
+          case MARK_REACHABLE_SUPER:
+            markSuperMethodAsReachable((DexMethod) action.target,
+                (DexEncodedMethod) action.context);
+            break;
+          case MARK_METHOD_KEPT:
+            markMethodAsKept((DexEncodedMethod) action.target, action.reason);
+            break;
+          case MARK_FIELD_KEPT:
+            markFieldAsKept((DexEncodedField) action.target, action.reason);
+            break;
+          case MARK_METHOD_LIVE:
+            processNewlyLiveMethod(((DexEncodedMethod) action.target), action.reason);
+            break;
+          default:
+            throw new IllegalArgumentException(action.kind.toString());
         }
       }
-
       if (Log.ENABLED) {
         Set<DexEncodedMethod> allLive = Sets.newIdentityHashSet();
         for (Entry<DexType, SetWithReason<DexEncodedMethod>> entry : reachableVirtualMethods
@@ -833,41 +874,6 @@
     return new AppInfoWithLiveness(appInfo, this);
   }
 
-  private void doTrace() {
-    while (!workList.isEmpty()) {
-      Action action = workList.poll();
-      switch (action.kind) {
-        case MARK_INSTANTIATED:
-          processNewlyInstantiatedClass((DexClass) action.target, action.reason);
-          break;
-        case MARK_REACHABLE_FIELD:
-          markFieldAsReachable((DexField) action.target, action.reason);
-          break;
-        case MARK_REACHABLE_VIRTUAL:
-          markVirtualMethodAsReachable((DexMethod) action.target, false, action.reason);
-          break;
-        case MARK_REACHABLE_INTERFACE:
-          markVirtualMethodAsReachable((DexMethod) action.target, true, action.reason);
-          break;
-        case MARK_REACHABLE_SUPER:
-          markSuperMethodAsReachable((DexMethod) action.target,
-              (DexEncodedMethod) action.context);
-          break;
-        case MARK_METHOD_KEPT:
-          markMethodAsKept((DexEncodedMethod) action.target, action.reason);
-          break;
-        case MARK_FIELD_KEPT:
-          markFieldAsKept((DexEncodedField) action.target, action.reason);
-          break;
-        case MARK_METHOD_LIVE:
-          processNewlyLiveMethod(((DexEncodedMethod) action.target), action.reason);
-          break;
-        default:
-          throw new IllegalArgumentException(action.kind.toString());
-      }
-    }
-  }
-
   private void markMethodAsKept(DexEncodedMethod target, KeepReason reason) {
     DexClass holder = appInfo.definitionFor(target.method.holder);
     // If this method no longer has a corresponding class then we have shaken it away before.
diff --git a/src/main/java/com/android/tools/r8/shaking/ScopedDexMethodSet.java b/src/main/java/com/android/tools/r8/shaking/ScopedDexMethodSet.java
new file mode 100644
index 0000000..612b88a
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/shaking/ScopedDexMethodSet.java
@@ -0,0 +1,45 @@
+// 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.shaking;
+
+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;
+
+class ScopedDexMethodSet {
+
+  private static Equivalence<DexMethod> METHOD_EQUIVALENCE = MethodSignatureEquivalence.get();
+
+  private final ScopedDexMethodSet parent;
+  private final Set<Wrapper<DexMethod>> items = new HashSet<>();
+
+  public ScopedDexMethodSet() {
+    this(null);
+  }
+
+  private ScopedDexMethodSet(ScopedDexMethodSet parent) {
+    this.parent = parent;
+  }
+
+  public ScopedDexMethodSet newNestedScope() {
+    return new ScopedDexMethodSet(this);
+  }
+
+  private boolean contains(Wrapper<DexMethod> item) {
+    return items.contains(item)
+        || ((parent != null) && parent.contains(item));
+  }
+
+  public boolean addMethod(DexMethod method) {
+    Wrapper<DexMethod> wrapped = METHOD_EQUIVALENCE.wrap(method);
+    return !contains(wrapped) && items.add(wrapped);
+  }
+
+  public ScopedDexMethodSet getParent() {
+    return parent;
+  }
+}
diff --git a/src/test/examplesAndroidN/shaking/InterfaceWithDefault.java b/src/test/examplesAndroidN/shaking/InterfaceWithDefault.java
new file mode 100644
index 0000000..f32a754
--- /dev/null
+++ b/src/test/examplesAndroidN/shaking/InterfaceWithDefault.java
@@ -0,0 +1,11 @@
+// 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 shaking;
+
+public interface InterfaceWithDefault {
+
+  default void foo() {
+    System.out.println("Default method foo");
+  }
+}
diff --git a/src/test/examplesAndroidN/shaking/OtherInterface.java b/src/test/examplesAndroidN/shaking/OtherInterface.java
new file mode 100644
index 0000000..8831a1d
--- /dev/null
+++ b/src/test/examplesAndroidN/shaking/OtherInterface.java
@@ -0,0 +1,9 @@
+// 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 shaking;
+
+public interface OtherInterface {
+
+  void bar();
+}
diff --git a/src/test/examplesAndroidN/shaking/OtherInterfaceWithDefault.java b/src/test/examplesAndroidN/shaking/OtherInterfaceWithDefault.java
new file mode 100644
index 0000000..8d4dec6
--- /dev/null
+++ b/src/test/examplesAndroidN/shaking/OtherInterfaceWithDefault.java
@@ -0,0 +1,12 @@
+// 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 shaking;
+
+public interface OtherInterfaceWithDefault extends OtherInterface {
+
+  @Override
+  default void bar() {
+    System.out.println("bar from OtherInterfaceWithDefault");
+  }
+}
diff --git a/src/test/examplesAndroidN/shaking/Shaking.java b/src/test/examplesAndroidN/shaking/Shaking.java
new file mode 100644
index 0000000..353e9b4
--- /dev/null
+++ b/src/test/examplesAndroidN/shaking/Shaking.java
@@ -0,0 +1,16 @@
+// 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 shaking;
+
+public class Shaking {
+
+  public static void main(String... args) {
+    SubClassOne anInstance = new SubClassOne();
+    invokeFooOnInterface(anInstance);
+  }
+
+  private static void invokeFooOnInterface(InterfaceWithDefault anInstance) {
+    anInstance.foo();
+  }
+}
diff --git a/src/test/examplesAndroidN/shaking/SubClassOne.java b/src/test/examplesAndroidN/shaking/SubClassOne.java
new file mode 100644
index 0000000..7dfcf8d
--- /dev/null
+++ b/src/test/examplesAndroidN/shaking/SubClassOne.java
@@ -0,0 +1,29 @@
+// 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 shaking;
+
+public class SubClassOne implements InterfaceWithDefault, OtherInterfaceWithDefault {
+
+  @Override
+  public void foo() {
+    System.out.println("Method foo from SubClassOne");
+    makeSubClassTwoLive().foo();
+    asOtherInterface().bar();
+  }
+
+  private OtherInterface asOtherInterface() {
+    return new SubClassTwo();
+  }
+
+  @Override
+  public void bar() {
+    System.out.println("Method bar from SubClassOne");
+  }
+
+  private InterfaceWithDefault makeSubClassTwoLive() {
+    // Once we see this method, SubClassTwo will be live. This should also make the default method
+    // in the interface live, as SubClassTwo does not override it.
+    return new SubClassTwo();
+  }
+}
diff --git a/src/test/examplesAndroidN/shaking/SubClassTwo.java b/src/test/examplesAndroidN/shaking/SubClassTwo.java
new file mode 100644
index 0000000..95b2492
--- /dev/null
+++ b/src/test/examplesAndroidN/shaking/SubClassTwo.java
@@ -0,0 +1,8 @@
+// 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 shaking;
+
+public class SubClassTwo implements InterfaceWithDefault, OtherInterfaceWithDefault {
+  // Intentionally left empty.
+}
diff --git a/src/test/examplesAndroidN/shaking/keep-rules.txt b/src/test/examplesAndroidN/shaking/keep-rules.txt
new file mode 100644
index 0000000..791cebf
--- /dev/null
+++ b/src/test/examplesAndroidN/shaking/keep-rules.txt
@@ -0,0 +1,9 @@
+# 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.
+
+# Keep the application entry point. Get rid of everything that is not
+# reachable from there.
+-keep public class shaking.Shaking {
+  public static void main(...);
+}
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index 5753d84..54750e5 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -58,15 +58,17 @@
 public class ToolHelper {
 
   public static final String BUILD_DIR = "build/";
-  public static final String EXAMPLES_DIR = "src/test/examples/";
-  public static final String EXAMPLES_ANDROID_O_DIR = "src/test/examplesAndroidO/";
-  public static final String EXAMPLES_ANDROID_P_DIR = "src/test/examplesAndroidP/";
-  public static final String EXAMPLES_BUILD_DIR = BUILD_DIR + "test/examples/";
-  public static final String EXAMPLES_ANDROID_N_BUILD_DIR = BUILD_DIR + "test/examplesAndroidN/";
-  public static final String EXAMPLES_ANDROID_O_BUILD_DIR = BUILD_DIR + "test/examplesAndroidO/";
-  public static final String EXAMPLES_ANDROID_P_BUILD_DIR = BUILD_DIR + "test/examplesAndroidP/";
-  public static final String EXAMPLES_JAVA9_BUILD_DIR = BUILD_DIR + "test/examplesJava9/";
-  public static final String SMALI_BUILD_DIR = BUILD_DIR + "test/smali/";
+  public static final String TESTS_DIR = "src/test/";
+  public static final String EXAMPLES_DIR = TESTS_DIR + "examples/";
+  public static final String EXAMPLES_ANDROID_O_DIR = TESTS_DIR + "examplesAndroidO/";
+  public static final String EXAMPLES_ANDROID_P_DIR = TESTS_DIR + "examplesAndroidP/";
+  public static final String TESTS_BUILD_DIR = BUILD_DIR + "test/";
+  public static final String EXAMPLES_BUILD_DIR = TESTS_BUILD_DIR + "examples/";
+  public static final String EXAMPLES_ANDROID_N_BUILD_DIR = TESTS_BUILD_DIR + "examplesAndroidN/";
+  public static final String EXAMPLES_ANDROID_O_BUILD_DIR = TESTS_BUILD_DIR + "examplesAndroidO/";
+  public static final String EXAMPLES_ANDROID_P_BUILD_DIR = TESTS_BUILD_DIR + "examplesAndroidP/";
+  public static final String EXAMPLES_JAVA9_BUILD_DIR = TESTS_BUILD_DIR + "examplesJava9/";
+  public static final String SMALI_BUILD_DIR = TESTS_BUILD_DIR + "smali/";
 
   public static final String LINE_SEPARATOR = StringUtils.LINE_SEPARATOR;
   public final static String PATH_SEPARATOR = File.pathSeparator;
diff --git a/src/test/java/com/android/tools/r8/shaking/TreeShakingTest.java b/src/test/java/com/android/tools/r8/shaking/TreeShakingTest.java
index 756c93c..081b083 100644
--- a/src/test/java/com/android/tools/r8/shaking/TreeShakingTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/TreeShakingTest.java
@@ -29,6 +29,7 @@
 import com.google.common.collect.Lists;
 import java.io.File;
 import java.io.IOException;
+import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.ArrayList;
@@ -61,27 +62,31 @@
       .of(ANDROID_JAR, ToolHelper.EXAMPLES_BUILD_DIR + "shakinglib.jar"), Paths::get);
   private static final String EMPTY_FLAGS = "src/test/proguard/valid/empty.flags";
   private static final Set<String> IGNORED_FLAGS = ImmutableSet.of(
-      "minification:conflict-mapping.txt",
-      "minification:keep-rules-apply-conflict-mapping.txt"
+      "examples/minification:conflict-mapping.txt",
+      "examples/minification:keep-rules-apply-conflict-mapping.txt"
   );
   private static final Set<String> IGNORED = ImmutableSet.of(
       // there's no point in running those without obfuscation
-      "shaking1:keep-rules-repackaging.txt:DEX:NONE",
-      "shaking1:keep-rules-repackaging.txt:JAR:NONE",
-      "shaking16:keep-rules-1.txt:DEX:NONE",
-      "shaking16:keep-rules-1.txt:JAR:NONE",
-      "shaking16:keep-rules-2.txt:DEX:NONE",
-      "shaking16:keep-rules-2.txt:JAR:NONE",
-      "shaking15:keep-rules.txt:DEX:NONE",
-      "shaking15:keep-rules.txt:JAR:NONE",
-      "minifygeneric:keep-rules.txt:DEX:NONE",
-      "minifygeneric:keep-rules.txt:JAR:NONE",
-      "minifygenericwithinner:keep-rules.txt:DEX:NONE",
-      "minifygenericwithinner:keep-rules.txt:JAR:NONE"
+      "examples/shaking1:keep-rules-repackaging.txt:DEX:NONE",
+      "examples/shaking1:keep-rules-repackaging.txt:JAR:NONE",
+      "examples/shaking16:keep-rules-1.txt:DEX:NONE",
+      "examples/shaking16:keep-rules-1.txt:JAR:NONE",
+      "examples/shaking16:keep-rules-2.txt:DEX:NONE",
+      "examples/shaking16:keep-rules-2.txt:JAR:NONE",
+      "examples/shaking15:keep-rules.txt:DEX:NONE",
+      "examples/shaking15:keep-rules.txt:JAR:NONE",
+      "examples/minifygeneric:keep-rules.txt:DEX:NONE",
+      "examples/minifygeneric:keep-rules.txt:JAR:NONE",
+      "examples/minifygenericwithinner:keep-rules.txt:DEX:NONE",
+      "examples/minifygenericwithinner:keep-rules.txt:JAR:NONE",
+      // No prebuild DEX files for AndroidN
+      "examplesAndroidN/shaking:keep-rules.txt:DEX:NONE",
+      "examplesAndroidN/shaking:keep-rules.txt:DEX:JAVA",
+      "examplesAndroidN/shaking:keep-rules.txt:DEX:AGGRESSIVE"
   );
 
   // TODO(65355452): Reenable or remove inlining tests.
-  private static Set<String> SKIPPED = ImmutableSet.of("inlining");
+  private static Set<String> SKIPPED = ImmutableSet.of("examples/inlining");
 
   private final MinifyMode minify;
 
@@ -107,11 +112,11 @@
       BiConsumer<String, String> outputComparator,
       BiConsumer<DexInspector, DexInspector> dexComparator) {
     this.kind = kind;
-    originalDex = ToolHelper.EXAMPLES_BUILD_DIR + test + "/classes.dex";
+    originalDex = ToolHelper.TESTS_BUILD_DIR + test + "/classes.dex";
     if (kind == Frontend.DEX) {
       this.programFile = originalDex;
     } else {
-      this.programFile = ToolHelper.EXAMPLES_BUILD_DIR + test + ".jar";
+      this.programFile = ToolHelper.TESTS_BUILD_DIR + test + ".jar";
     }
     this.mainClass = mainClass;
     this.keepRulesFiles = keepRulesFiles;
@@ -622,160 +627,166 @@
   public static Collection<Object[]> data() {
     List<String> tests = Arrays
         .asList(
-            "shaking1",
-            "shaking2",
-            "shaking3",
-            "shaking4",
-            "shaking5",
-            "shaking6",
-            "shaking7",
-            "shaking8",
-            "shaking9",
-            "shaking10",
-            "shaking11",
-            "shaking12",
-            "shaking13",
-            "shaking14",
-            "shaking15",
-            "shaking16",
-            "shaking17",
-            "minification",
-            "minifygeneric",
-            "minifygenericwithinner",
-            "assumenosideeffects1",
-            "assumenosideeffects2",
-            "assumenosideeffects3",
-            "assumenosideeffects4",
-            "assumenosideeffects5",
-            "assumevalues1",
-            "assumevalues2",
-            "assumevalues3",
-            "assumevalues4",
-            "assumevalues5",
-            "annotationremoval",
-            "memberrebinding2",
-            "memberrebinding3",
-            "simpleproto1",
-            "simpleproto2",
-            "simpleproto3",
-            "nestedproto1",
-            "nestedproto2",
-            "enumproto",
-            "repeatedproto",
-            "oneofproto");
+            "examples/shaking1",
+            "examples/shaking2",
+            "examples/shaking3",
+            "examples/shaking4",
+            "examples/shaking5",
+            "examples/shaking6",
+            "examples/shaking7",
+            "examples/shaking8",
+            "examples/shaking9",
+            "examples/shaking10",
+            "examples/shaking11",
+            "examples/shaking12",
+            "examples/shaking13",
+            "examples/shaking14",
+            "examples/shaking15",
+            "examples/shaking16",
+            "examples/shaking17",
+            "examples/minification",
+            "examples/minifygeneric",
+            "examples/minifygenericwithinner",
+            "examples/assumenosideeffects1",
+            "examples/assumenosideeffects2",
+            "examples/assumenosideeffects3",
+            "examples/assumenosideeffects4",
+            "examples/assumenosideeffects5",
+            "examples/assumevalues1",
+            "examples/assumevalues2",
+            "examples/assumevalues3",
+            "examples/assumevalues4",
+            "examples/assumevalues5",
+            "examples/annotationremoval",
+            "examples/memberrebinding2",
+            "examples/memberrebinding3",
+            "examples/simpleproto1",
+            "examples/simpleproto2",
+            "examples/simpleproto3",
+            "examples/nestedproto1",
+            "examples/nestedproto2",
+            "examples/enumproto",
+            "examples/repeatedproto",
+            "examples/oneofproto",
+            "examplesAndroidN/shaking");
 
     // Keys can be the name of the test or the name of the test followed by a colon and the name
     // of the keep file.
     Map<String, Consumer<DexInspector>> inspections = new HashMap<>();
-    inspections.put("shaking1:keep-rules.txt", TreeShakingTest::shaking1HasNoClassUnused);
+    inspections.put("examples/shaking1:keep-rules.txt", TreeShakingTest::shaking1HasNoClassUnused);
+    inspections.put("examples/shaking1:keep-rules-repackaging.txt",
+        TreeShakingTest::shaking1IsCorrectlyRepackaged);
     inspections
-        .put("shaking1:keep-rules-repackaging.txt", TreeShakingTest::shaking1IsCorrectlyRepackaged);
-    inspections.put("shaking2:keep-rules.txt", TreeShakingTest::shaking2SuperClassIsAbstract);
-    inspections.put("shaking3:keep-by-tag.txt", TreeShakingTest::shaking3HasNoClassB);
-    inspections.put("shaking3:keep-by-tag-default.txt", TreeShakingTest::shaking3HasNoClassB);
-    inspections.put("shaking3:keep-by-tag-with-pattern.txt", TreeShakingTest::shaking3HasNoClassB);
-    inspections.put("shaking3:keep-by-tag-via-interface.txt", TreeShakingTest::shaking3HasNoClassB);
-    inspections.put("shaking3:keep-by-tag-on-method.txt", TreeShakingTest::shaking3HasNoClassB);
+        .put("examples/shaking2:keep-rules.txt", TreeShakingTest::shaking2SuperClassIsAbstract);
+    inspections.put("examples/shaking3:keep-by-tag.txt", TreeShakingTest::shaking3HasNoClassB);
     inspections
-        .put("shaking3:keep-no-abstract-classes.txt", TreeShakingTest::shaking3HasNoPrivateClass);
-    inspections.put("shaking5", TreeShakingTest::shaking5Inspection);
-    inspections.put("shaking6:keep-public.txt", TreeShakingTest::hasNoPrivateMethods);
-    inspections.put("shaking6:keep-non-public.txt", TreeShakingTest::hasNoPublicMethodsButPrivate);
+        .put("examples/shaking3:keep-by-tag-default.txt", TreeShakingTest::shaking3HasNoClassB);
+    inspections.put("examples/shaking3:keep-by-tag-with-pattern.txt",
+        TreeShakingTest::shaking3HasNoClassB);
+    inspections.put("examples/shaking3:keep-by-tag-via-interface.txt",
+        TreeShakingTest::shaking3HasNoClassB);
     inspections
-        .put("shaking6:keep-justAMethod-public.txt", TreeShakingTest::hasNoPrivateJustAMethod);
-    inspections.put("shaking6:keep-justAMethod-OnInt.txt", TreeShakingTest::hasOnlyIntJustAMethod);
+        .put("examples/shaking3:keep-by-tag-on-method.txt", TreeShakingTest::shaking3HasNoClassB);
+    inspections.put("examples/shaking3:keep-no-abstract-classes.txt",
+        TreeShakingTest::shaking3HasNoPrivateClass);
+    inspections.put("examples/shaking5", TreeShakingTest::shaking5Inspection);
+    inspections.put("examples/shaking6:keep-public.txt", TreeShakingTest::hasNoPrivateMethods);
+    inspections.put("examples/shaking6:keep-non-public.txt",
+        TreeShakingTest::hasNoPublicMethodsButPrivate);
+    inspections.put("examples/shaking6:keep-justAMethod-public.txt",
+        TreeShakingTest::hasNoPrivateJustAMethod);
+    inspections.put("examples/shaking6:keep-justAMethod-OnInt.txt",
+        TreeShakingTest::hasOnlyIntJustAMethod);
+    inspections.put("examples/shaking7:keep-public-fields.txt",
+        TreeShakingTest::shaking7HasOnlyPublicFields);
+    inspections.put("examples/shaking7:keep-double-fields.txt",
+        TreeShakingTest::shaking7HasOnlyDoubleFields);
+    inspections.put("examples/shaking7:keep-public-theDoubleField-fields.txt",
+        TreeShakingTest::shaking7HasOnlyPublicFieldsNamedTheDoubleField);
+    inspections.put("examples/shaking7:keep-public-theIntField-fields.txt",
+        TreeShakingTest::shaking7HasOnlyPublicFieldsNamedTheIntField);
+    inspections.put("examples/shaking8:keep-rules.txt",
+        TreeShakingTest::shaking8ThingClassIsAbstractAndEmpty);
     inspections
-        .put("shaking7:keep-public-fields.txt", TreeShakingTest::shaking7HasOnlyPublicFields);
+        .put("examples/shaking9:keep-rules.txt", TreeShakingTest::shaking9OnlySuperMethodsKept);
     inspections
-        .put("shaking7:keep-double-fields.txt", TreeShakingTest::shaking7HasOnlyDoubleFields);
-    inspections
-        .put("shaking7:keep-public-theDoubleField-fields.txt",
-            TreeShakingTest::shaking7HasOnlyPublicFieldsNamedTheDoubleField);
-    inspections
-        .put("shaking7:keep-public-theIntField-fields.txt",
-            TreeShakingTest::shaking7HasOnlyPublicFieldsNamedTheIntField);
-    inspections
-        .put("shaking8:keep-rules.txt", TreeShakingTest::shaking8ThingClassIsAbstractAndEmpty);
-    inspections
-        .put("shaking9:keep-rules.txt", TreeShakingTest::shaking9OnlySuperMethodsKept);
-    inspections
-        .put("shaking11:keep-rules.txt", TreeShakingTest::shaking11OnlyOneClassKept);
-    inspections
-        .put("shaking11:keep-rules-keep-method.txt", TreeShakingTest::shaking11BothMethodsKept);
-    inspections.put("shaking12:keep-rules.txt",
+        .put("examples/shaking11:keep-rules.txt", TreeShakingTest::shaking11OnlyOneClassKept);
+    inspections.put("examples/shaking11:keep-rules-keep-method.txt",
+        TreeShakingTest::shaking11BothMethodsKept);
+    inspections.put("examples/shaking12:keep-rules.txt",
             TreeShakingTest::shaking12OnlyInstantiatedClassesHaveConstructors);
-    inspections.put("shaking13:keep-rules.txt",
+    inspections.put("examples/shaking13:keep-rules.txt",
             TreeShakingTest::shaking13EnsureFieldWritesCorrect);
-    inspections.put("shaking14:keep-rules.txt",
+    inspections.put("examples/shaking14:keep-rules.txt",
             TreeShakingTest::shaking14EnsureRightStaticMethodsLive);
-    inspections.put("shaking15:keep-rules.txt", TreeShakingTest::shaking15testDictionary);
-    inspections.put("shaking17:keep-rules.txt", TreeShakingTest::abstractMethodRemains);
-    inspections.put("annotationremoval:keep-rules.txt",
+    inspections.put("examples/shaking15:keep-rules.txt", TreeShakingTest::shaking15testDictionary);
+    inspections.put("examples/shaking17:keep-rules.txt", TreeShakingTest::abstractMethodRemains);
+    inspections.put("examples/annotationremoval:keep-rules.txt",
             TreeShakingTest::annotationRemovalHasNoInnerClassAnnotations);
-    inspections.put("annotationremoval:keep-rules-keep-innerannotation.txt",
+    inspections.put("examples/annotationremoval:keep-rules-keep-innerannotation.txt",
             TreeShakingTest::annotationRemovalHasAllInnerClassAnnotations);
+    inspections.put("examples/simpleproto1:keep-rules.txt",
+        TreeShakingTest::simpleproto1UnusedFieldIsGone);
+    inspections.put("examples/simpleproto2:keep-rules.txt",
+        TreeShakingTest::simpleproto2UnusedFieldsAreGone);
+    inspections.put("examples/nestedproto1:keep-rules.txt",
+        TreeShakingTest::nestedproto1UnusedFieldsAreGone);
+    inspections.put("examples/nestedproto2:keep-rules.txt",
+        TreeShakingTest::nestedproto2UnusedFieldsAreGone);
     inspections
-        .put("simpleproto1:keep-rules.txt", TreeShakingTest::simpleproto1UnusedFieldIsGone);
+        .put("examples/enumproto:keep-rules.txt", TreeShakingTest::enumprotoUnusedFieldsAreGone);
     inspections
-        .put("simpleproto2:keep-rules.txt", TreeShakingTest::simpleproto2UnusedFieldsAreGone);
+        .put("examples/repeatedproto:keep-rules.txt", TreeShakingTest::repeatedUnusedFieldsAreGone);
     inspections
-        .put("nestedproto1:keep-rules.txt", TreeShakingTest::nestedproto1UnusedFieldsAreGone);
-    inspections
-        .put("nestedproto2:keep-rules.txt", TreeShakingTest::nestedproto2UnusedFieldsAreGone);
-    inspections
-        .put("enumproto:keep-rules.txt", TreeShakingTest::enumprotoUnusedFieldsAreGone);
-    inspections
-        .put("repeatedproto:keep-rules.txt", TreeShakingTest::repeatedUnusedFieldsAreGone);
-    inspections
-        .put("oneofproto:keep-rules.txt", TreeShakingTest::oneofprotoUnusedFieldsAreGone);
+        .put("examples/oneofproto:keep-rules.txt", TreeShakingTest::oneofprotoUnusedFieldsAreGone);
 
     // Keys can be the name of the test or the name of the test followed by a colon and the name
     // of the keep file.
     Map<String, Collection<List<String>>> optionalRules = new HashMap<>();
-    optionalRules.put("shaking1", ImmutableList.of(
+    optionalRules.put("examples/shaking1", ImmutableList.of(
         Collections.singletonList(EMPTY_FLAGS),
         Lists.newArrayList(EMPTY_FLAGS, EMPTY_FLAGS)));
     List<Object[]> testCases = new ArrayList<>();
 
     Map<String, BiConsumer<String, String>> outputComparators = new HashMap<>();
     outputComparators
-        .put("assumenosideeffects1",
+        .put("examples/assumenosideeffects1",
             TreeShakingTest::assumenosideeffects1CheckOutput);
     outputComparators
-        .put("assumenosideeffects2",
+        .put("examples/assumenosideeffects2",
             TreeShakingTest::assumenosideeffects2CheckOutput);
     outputComparators
-        .put("assumenosideeffects3",
+        .put("examples/assumenosideeffects3",
             TreeShakingTest::assumenosideeffects3CheckOutput);
     outputComparators
-        .put("assumenosideeffects4",
+        .put("examples/assumenosideeffects4",
             TreeShakingTest::assumenosideeffects4CheckOutput);
     outputComparators
-        .put("assumenosideeffects5",
+        .put("examples/assumenosideeffects5",
             TreeShakingTest::assumenosideeffects5CheckOutput);
     outputComparators
-        .put("assumevalues1",
+        .put("examples/assumevalues1",
             TreeShakingTest::assumevalues1CheckOutput);
     outputComparators
-        .put("assumevalues2",
+        .put("examples/assumevalues2",
             TreeShakingTest::assumevalues2CheckOutput);
     outputComparators
-        .put("assumevalues3",
+        .put("examples/assumevalues3",
             TreeShakingTest::assumevalues3CheckOutput);
     outputComparators
-        .put("assumevalues4",
+        .put("examples/assumevalues4",
             TreeShakingTest::assumevalues4CheckOutput);
     outputComparators
-        .put("assumevalues5",
+        .put("examples/assumevalues5",
             TreeShakingTest::assumevalues5CheckOutput);
 
     Map<String, BiConsumer<DexInspector, DexInspector>> dexComparators = new HashMap<>();
     dexComparators
-        .put("shaking1:keep-rules-dont-shrink.txt", TreeShakingTest::checkSameStructure);
+        .put("examples/shaking1:keep-rules-dont-shrink.txt", TreeShakingTest::checkSameStructure);
     dexComparators
-        .put("shaking2:keep-rules-dont-shrink.txt", TreeShakingTest::checkSameStructure);
+        .put("examples/shaking2:keep-rules-dont-shrink.txt", TreeShakingTest::checkSameStructure);
     dexComparators
-        .put("shaking4:keep-rules-dont-shrink.txt", TreeShakingTest::checkSameStructure);
+        .put("examples/shaking4:keep-rules-dont-shrink.txt", TreeShakingTest::checkSameStructure);
 
     Set<String> usedInspections = new HashSet<>();
     Set<String> usedOptionalRules = new HashSet<>();
@@ -784,7 +795,7 @@
 
     for (String test : tests) {
       String mainClass = deriveMainClass(test);
-      File[] keepFiles = new File(ToolHelper.EXAMPLES_DIR + "/" + test)
+      File[] keepFiles = new File(ToolHelper.TESTS_DIR + test)
           .listFiles(file -> file.isFile() && file.getName().endsWith(".txt"));
       for (File keepFile : keepFiles) {
         String keepName = keepFile.getName();
@@ -863,12 +874,13 @@
   }
 
   private static String deriveMainClass(String testName) {
+    String testBaseName = testName.substring(testName.lastIndexOf('/') + 1);
     StringBuilder mainClass = new StringBuilder(testName.length() * 2 + 1);
-    mainClass.append(testName);
+    mainClass.append(testBaseName);
     mainClass.append('.');
-    mainClass.append(Character.toUpperCase(testName.charAt(0)));
-    for (int i = 1; i < testName.length(); i++) {
-      char next = testName.charAt(i);
+    mainClass.append(Character.toUpperCase(testBaseName.charAt(0)));
+    for (int i = 1; i < testBaseName.length(); i++) {
+      char next = testBaseName.charAt(i);
       if (!Character.isAlphabetic(next)) {
         break;
       }
@@ -888,24 +900,32 @@
       builder.appendClasspath(ToolHelper.EXAMPLES_BUILD_DIR + "shakinglib/classes.dex");
     };
 
-    if (outputComparator != null) {
-      String output1 = ToolHelper.runArtNoVerificationErrors(
-          Collections.singletonList(originalDex), mainClass, extraArtArgs, null);
-      String output2 = ToolHelper.runArtNoVerificationErrors(
-          Collections.singletonList(generated.toString()), mainClass, extraArtArgs, null);
-      outputComparator.accept(output1, output2);
-    } else {
-      String output = ToolHelper.checkArtOutputIdentical(Collections.singletonList(originalDex),
-          Collections.singletonList(generated.toString()), mainClass,
-          extraArtArgs, null);
-    }
+    if (Files.exists(Paths.get(originalDex))) {
+      if (outputComparator != null) {
+        String output1 = ToolHelper.runArtNoVerificationErrors(
+            Collections.singletonList(originalDex), mainClass, extraArtArgs, null);
+        String output2 = ToolHelper.runArtNoVerificationErrors(
+            Collections.singletonList(generated.toString()), mainClass, extraArtArgs, null);
+        outputComparator.accept(output1, output2);
+      } else {
+        ToolHelper.checkArtOutputIdentical(Collections.singletonList(originalDex),
+            Collections.singletonList(generated.toString()), mainClass,
+            extraArtArgs, null);
+      }
 
-    if (dexComparator != null) {
-      DexInspector ref = new DexInspector(Paths.get(originalDex));
-      DexInspector inspector = new DexInspector(generated,
-          minify.isMinify() ? temp.getRoot().toPath().resolve(DEFAULT_PROGUARD_MAP_FILE).toString()
-              : null);
-      dexComparator.accept(ref, inspector);
+      if (dexComparator != null) {
+        DexInspector ref = new DexInspector(Paths.get(originalDex));
+        DexInspector inspector = new DexInspector(generated,
+            minify.isMinify() ? temp.getRoot().toPath().resolve(DEFAULT_PROGUARD_MAP_FILE)
+                .toString()
+                : null);
+        dexComparator.accept(ref, inspector);
+      }
+    } else {
+      Assert.assertNull(outputComparator);
+      Assert.assertNull(dexComparator);
+      ToolHelper.runArtNoVerificationErrors(
+          Collections.singletonList(generated.toString()), mainClass, extraArtArgs, null);
     }
 
     if (inspection != null) {