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) {