Print whyareyoukeeping information for failed -checkdiscarded
Bug: 122670355, 131668850
Change-Id: I0814a17ac5519e01d847ef333970936f4b90ccd9
diff --git a/src/main/java/com/android/tools/r8/GenerateMainDexList.java b/src/main/java/com/android/tools/r8/GenerateMainDexList.java
index 02d6475..e5842df 100644
--- a/src/main/java/com/android/tools/r8/GenerateMainDexList.java
+++ b/src/main/java/com/android/tools/r8/GenerateMainDexList.java
@@ -9,9 +9,9 @@
import com.android.tools.r8.graph.AppServices;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexApplication;
-import com.android.tools.r8.graph.DexReference;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.shaking.DiscardedChecker;
import com.android.tools.r8.shaking.Enqueuer;
import com.android.tools.r8.shaking.MainDexClasses;
import com.android.tools.r8.shaking.MainDexListBuilder;
@@ -24,6 +24,7 @@
import com.android.tools.r8.utils.ThreadUtils;
import com.android.tools.r8.utils.Timing;
import java.io.IOException;
+import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ExecutionException;
@@ -73,16 +74,28 @@
options.mainDexListConsumer.accept(String.join("\n", result), options.reporter);
}
- if (!mainDexRootSet.checkDiscarded.isEmpty()) {
- new DiscardedChecker(mainDexRootSet, mainDexClasses.getClasses(), appView).run();
- }
- // Print -whyareyoukeeping results if any.
- if (whyAreYouKeepingConsumer != null) {
- for (DexReference reference : mainDexRootSet.reasonAsked) {
- whyAreYouKeepingConsumer.printWhyAreYouKeeping(
- enqueuer.getGraphNode(reference), System.out);
- }
- }
+ R8.processWhyAreYouKeepingAndCheckDiscarded(
+ mainDexRootSet,
+ () -> {
+ ArrayList<DexProgramClass> classes = new ArrayList<>();
+ // TODO(b/131668850): This is not a deterministic order!
+ mainDexClasses
+ .getClasses()
+ .forEach(
+ type -> {
+ DexClass clazz = appView.definitionFor(type);
+ assert clazz.isProgramClass();
+ classes.add(clazz.asProgramClass());
+ });
+ return classes;
+ },
+ whyAreYouKeepingConsumer,
+ appView,
+ enqueuer,
+ true,
+ options,
+ timing,
+ executor);
return result;
} catch (ExecutionException e) {
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 36c1cbf..036c874 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -18,6 +18,7 @@
import com.android.tools.r8.graph.DexApplication;
import com.android.tools.r8.graph.DexCallSite;
import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexDefinition;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexReference;
@@ -94,6 +95,7 @@
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
+import java.util.function.Supplier;
/**
* The R8 compiler.
@@ -564,15 +566,30 @@
enqueuer.traceMainDex(mainDexRootSet, executorService, timing);
// Calculate the automatic main dex list according to legacy multidex constraints.
mainDexClasses = new MainDexListBuilder(mainDexBaseClasses, application).run();
- if (!mainDexRootSet.checkDiscarded.isEmpty()) {
- new DiscardedChecker(mainDexRootSet, mainDexClasses.getClasses(), appView).run();
- }
- if (whyAreYouKeepingConsumer != null) {
- for (DexReference reference : mainDexRootSet.reasonAsked) {
- whyAreYouKeepingConsumer.printWhyAreYouKeeping(
- enqueuer.getGraphNode(reference), System.out);
- }
- }
+ final MainDexClasses finalMainDexClasses = mainDexClasses;
+
+ processWhyAreYouKeepingAndCheckDiscarded(
+ mainDexRootSet,
+ () -> {
+ ArrayList<DexProgramClass> classes = new ArrayList<>();
+ // TODO(b/131668850): This is not a deterministic order!
+ finalMainDexClasses
+ .getClasses()
+ .forEach(
+ type -> {
+ DexClass clazz = appView.definitionFor(type);
+ assert clazz.isProgramClass();
+ classes.add(clazz.asProgramClass());
+ });
+ return classes;
+ },
+ whyAreYouKeepingConsumer,
+ appView,
+ enqueuer,
+ true,
+ options,
+ timing,
+ executorService);
}
appView.setAppInfo(new AppInfoWithSubtyping(application));
@@ -610,13 +627,17 @@
application,
CollectionUtils.mergeSets(prunedTypes, pruner.getRemovedClasses())));
- // Print reasons on the application after pruning, so that we reflect the actual result.
- if (whyAreYouKeepingConsumer != null) {
- for (DexReference reference : appView.rootSet().reasonAsked) {
- whyAreYouKeepingConsumer.printWhyAreYouKeeping(
- enqueuer.getGraphNode(reference), System.out);
- }
- }
+ processWhyAreYouKeepingAndCheckDiscarded(
+ appView.rootSet(),
+ () -> appView.appInfo().app().classesWithDeterministicOrder(),
+ whyAreYouKeepingConsumer,
+ appView,
+ enqueuer,
+ false,
+ options,
+ timing,
+ executorService);
+
// Remove annotations that refer to types that no longer exist.
assert classesToRetainInnerClassAttributeFor != null;
new AnnotationRemover(appView.withLiveness(), classesToRetainInnerClassAttributeFor)
@@ -636,11 +657,6 @@
application = application.builder().addToMainDexList(mainDexClasses.getClasses()).build();
}
- // Only perform discard-checking if tree-shaking is turned on.
- if (options.isShrinking() && !appView.rootSet().checkDiscarded.isEmpty()) {
- new DiscardedChecker(appView.rootSet(), application, options).run();
- }
-
// Perform minification.
NamingLens namingLens;
if (options.getProguardConfiguration().hasApplyMappingFile()) {
@@ -740,6 +756,57 @@
}
}
+ static void processWhyAreYouKeepingAndCheckDiscarded(
+ RootSet rootSet,
+ Supplier<Iterable<DexProgramClass>> classes,
+ WhyAreYouKeepingConsumer whyAreYouKeepingConsumer,
+ AppView<? extends AppInfoWithSubtyping> appView,
+ Enqueuer enqueuer,
+ boolean forMainDex,
+ InternalOptions options,
+ Timing timing,
+ ExecutorService executorService)
+ throws ExecutionException {
+ if (whyAreYouKeepingConsumer != null) {
+ for (DexReference reference : rootSet.reasonAsked) {
+ whyAreYouKeepingConsumer.printWhyAreYouKeeping(
+ enqueuer.getGraphNode(reference), System.out);
+ }
+ }
+ if (rootSet.checkDiscarded.isEmpty()) {
+ return;
+ }
+ List<DexDefinition> failed = new DiscardedChecker(rootSet, classes.get()).run();
+ if (failed.isEmpty()) {
+ return;
+ }
+ // If there is no kept-graph info, re-run the enqueueing to compute it.
+ if (whyAreYouKeepingConsumer == null) {
+ whyAreYouKeepingConsumer = new WhyAreYouKeepingConsumer(null);
+ enqueuer = new Enqueuer(appView, options, whyAreYouKeepingConsumer);
+ if (forMainDex) {
+ enqueuer.traceMainDex(rootSet, executorService, timing);
+ } else {
+ enqueuer.traceApplication(
+ rootSet,
+ options.getProguardConfiguration().getDontWarnPatterns(),
+ executorService,
+ timing);
+ }
+ }
+ for (DexDefinition definition : failed) {
+ if (!failed.isEmpty()) {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ whyAreYouKeepingConsumer.printWhyAreYouKeeping(
+ enqueuer.getGraphNode(definition.toReference()), new PrintStream(baos));
+ options.reporter.info(
+ new StringDiagnostic(
+ "Item " + definition.toSourceString() + " was not discarded.\n" + baos.toString()));
+ }
+ }
+ throw new CompilationError("Discard checks failed.");
+ }
+
private void computeKotlinInfoForProgramClasses(DexApplication application, AppView<?> appView) {
Kotlin kotlin = appView.dexItemFactory().kotlin;
Reporter reporter = options.reporter;
diff --git a/src/main/java/com/android/tools/r8/shaking/DiscardedChecker.java b/src/main/java/com/android/tools/r8/shaking/DiscardedChecker.java
index abc1e3e..ca86412 100644
--- a/src/main/java/com/android/tools/r8/shaking/DiscardedChecker.java
+++ b/src/main/java/com/android/tools/r8/shaking/DiscardedChecker.java
@@ -3,62 +3,40 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.shaking;
-import com.android.tools.r8.errors.CompilationError;
-import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexApplication;
-import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexDefinition;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexReference;
-import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.shaking.RootSetBuilder.RootSet;
-import com.android.tools.r8.utils.InternalOptions;
-import com.android.tools.r8.utils.StringDiagnostic;
import java.util.ArrayList;
+import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class DiscardedChecker {
private final Set<DexReference> checkDiscarded;
- private final List<DexProgramClass> classes;
- private boolean fail = false;
- private final InternalOptions options;
+ private final Iterable<DexProgramClass> classes;
- public DiscardedChecker(RootSet rootSet, DexApplication application, InternalOptions options) {
- this.checkDiscarded = rootSet.checkDiscarded;
- this.classes = application.classes();
- this.options = options;
+ public DiscardedChecker(RootSet rootSet, Iterable<DexProgramClass> classes) {
+ this.checkDiscarded = new HashSet<>(rootSet.checkDiscarded);
+ this.classes = classes;
}
- public DiscardedChecker(RootSet rootSet, Set<DexType> types, AppView<?> appView) {
- this.checkDiscarded = rootSet.checkDiscarded;
- this.classes = new ArrayList<>();
- types.forEach(
- type -> {
- DexClass clazz = appView.definitionFor(type);
- assert clazz.isProgramClass();
- this.classes.add(clazz.asProgramClass());
- });
- this.options = appView.options();
- }
-
- public void run() {
+ public List<DexDefinition> run() {
+ List<DexDefinition> failed = new ArrayList<>(checkDiscarded.size());
+ // TODO(b/131668850): Lookup the definition based on the reference.
for (DexProgramClass clazz : classes) {
- checkItem(clazz);
- clazz.forEachMethod(this::checkItem);
- clazz.forEachField(this::checkItem);
+ checkItem(clazz, failed);
+ clazz.forEachMethod(method -> checkItem(method, failed));
+ clazz.forEachField(field -> checkItem(field, failed));
}
- if (fail) {
- throw new CompilationError("Discard checks failed.");
- }
+ return failed;
}
- private void checkItem(DexDefinition item) {
- if (checkDiscarded.contains(item.toReference())) {
- options.reporter.info(
- new StringDiagnostic("Item " + item.toSourceString() + " was not discarded."));
- fail = true;
+ private void checkItem(DexDefinition item, List<DexDefinition> failed) {
+ DexReference reference = item.toReference();
+ if (checkDiscarded.contains(reference)) {
+ failed.add(item);
}
}
}
diff --git a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
index 7029734..4249b72 100644
--- a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
@@ -69,7 +69,7 @@
private final LinkedHashMap<DexReference, DexReference> reasonAsked = new LinkedHashMap<>();
private final Set<ProguardConfigurationRule> rulesThatUseExtendsOrImplementsWrong =
Sets.newIdentityHashSet();
- private final Set<DexReference> checkDiscarded = Sets.newIdentityHashSet();
+ private final LinkedHashMap<DexReference, DexReference> checkDiscarded = new LinkedHashMap<>();
private final Set<DexMethod> alwaysInline = Sets.newIdentityHashSet();
private final Set<DexMethod> forceInline = Sets.newIdentityHashSet();
private final Set<DexMethod> neverInline = Sets.newIdentityHashSet();
@@ -264,7 +264,7 @@
noOptimization,
noObfuscation,
ImmutableList.copyOf(reasonAsked.values()),
- checkDiscarded,
+ ImmutableList.copyOf(checkDiscarded.values()),
alwaysInline,
forceInline,
neverInline,
@@ -952,7 +952,7 @@
} else if (context instanceof ProguardAssumeValuesRule) {
assumedValues.put(item.toReference(), rule);
} else if (context instanceof ProguardCheckDiscardRule) {
- checkDiscarded.add(item.toReference());
+ checkDiscarded.computeIfAbsent(item.toReference(), i -> i);
} else if (context instanceof InlineRule) {
if (item.isDexEncodedMethod()) {
switch (((InlineRule) context).getType()) {
@@ -1032,7 +1032,7 @@
public final Set<DexReference> noOptimization;
private final Set<DexReference> noObfuscation;
public final ImmutableList<DexReference> reasonAsked;
- public final Set<DexReference> checkDiscarded;
+ public final ImmutableList<DexReference> checkDiscarded;
public final Set<DexMethod> alwaysInline;
public final Set<DexMethod> forceInline;
public final Set<DexMethod> neverInline;
@@ -1054,7 +1054,7 @@
Set<DexReference> noOptimization,
Set<DexReference> noObfuscation,
ImmutableList<DexReference> reasonAsked,
- Set<DexReference> checkDiscarded,
+ ImmutableList<DexReference> checkDiscarded,
Set<DexMethod> alwaysInline,
Set<DexMethod> forceInline,
Set<DexMethod> neverInline,
@@ -1073,7 +1073,7 @@
this.noOptimization = noOptimization;
this.noObfuscation = noObfuscation;
this.reasonAsked = reasonAsked;
- this.checkDiscarded = Collections.unmodifiableSet(checkDiscarded);
+ this.checkDiscarded = checkDiscarded;
this.alwaysInline = Collections.unmodifiableSet(alwaysInline);
this.forceInline = Collections.unmodifiableSet(forceInline);
this.neverInline = neverInline;
diff --git a/src/test/java/com/android/tools/r8/checkdiscarded/CheckDiscardedTest.java b/src/test/java/com/android/tools/r8/checkdiscarded/CheckDiscardedTest.java
index 6593110..b0c7ecf 100644
--- a/src/test/java/com/android/tools/r8/checkdiscarded/CheckDiscardedTest.java
+++ b/src/test/java/com/android/tools/r8/checkdiscarded/CheckDiscardedTest.java
@@ -3,33 +3,67 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.checkdiscarded;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.core.StringContains.containsString;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.Diagnostic;
+import com.android.tools.r8.R8FullTestBuilder;
+import com.android.tools.r8.R8TestCompileResult;
import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestDiagnosticMessages;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.checkdiscarded.testclasses.Main;
import com.android.tools.r8.checkdiscarded.testclasses.UnusedClass;
import com.android.tools.r8.checkdiscarded.testclasses.UsedClass;
import com.android.tools.r8.checkdiscarded.testclasses.WillBeGone;
import com.android.tools.r8.checkdiscarded.testclasses.WillStay;
import com.android.tools.r8.utils.InternalOptions;
-import com.google.common.collect.ImmutableList;
import java.util.List;
-import org.junit.Assert;
+import java.util.function.Consumer;
import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+@RunWith(Parameterized.class)
public class CheckDiscardedTest extends TestBase {
- private void run(boolean obfuscate, Class annotation, boolean checkMembers, boolean shouldFail)
- throws Exception {
- List<Class<?>> classes = ImmutableList.of(UnusedClass.class, UsedClass.class, Main.class);
- String proguardConfig = keepMainProguardConfiguration(Main.class, true, obfuscate)
- + checkDiscardRule(checkMembers, annotation);
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withNoneRuntime().build();
+ }
+
+ private final TestParameters parameters;
+
+ public CheckDiscardedTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ private void compile(
+ boolean obfuscate,
+ Class annotation,
+ boolean checkMembers,
+ Consumer<TestDiagnosticMessages> onCompilationFailure) {
+ R8FullTestBuilder builder = testForR8(Backend.DEX);
+ TestDiagnosticMessages diagnostics = builder.getState().getDiagnosticsMessages();
try {
- compileWithR8(classes, proguardConfig, this::noInlining);
+ R8TestCompileResult result =
+ builder
+ .addProgramClasses(UnusedClass.class, UsedClass.class, Main.class)
+ .addKeepMainRule(Main.class)
+ .addKeepRules(checkDiscardRule(checkMembers, annotation))
+ .minification(obfuscate)
+ .addOptionsModification(this::noInlining)
+ .compile();
+ assertNull(onCompilationFailure);
+ result.assertNoMessages();
} catch (CompilationFailedException e) {
- Assert.assertTrue(shouldFail);
- return;
+ onCompilationFailure.accept(diagnostics);
}
- Assert.assertFalse(shouldFail);
}
private void noInlining(InternalOptions options) {
@@ -45,27 +79,46 @@
}
@Test
- public void classesAreGone() throws Exception {
- run(false, WillBeGone.class, false, false);
- run(true, WillBeGone.class, false, false);
+ public void classesAreGone() {
+ compile(false, WillBeGone.class, false, null);
+ compile(true, WillBeGone.class, false, null);
}
@Test
- public void classesAreNotGone() throws Exception {
- run(false, WillStay.class, false, true);
- run(true, WillStay.class, false, true);
+ public void classesAreNotGone() {
+ Consumer<TestDiagnosticMessages> check =
+ diagnostics -> {
+ List<Diagnostic> infos = diagnostics.getInfos();
+ assertEquals(2, infos.size());
+ String messageUsedClass = infos.get(1).getDiagnosticMessage();
+ assertThat(messageUsedClass, containsString("UsedClass was not discarded"));
+ assertThat(messageUsedClass, containsString("is instantiated in"));
+ String messageMain = infos.get(0).getDiagnosticMessage();
+ assertThat(messageMain, containsString("Main was not discarded"));
+ assertThat(messageMain, containsString("is referenced in keep rule"));
+ };
+ compile(false, WillStay.class, false, check);
+ compile(true, WillStay.class, false, check);
}
@Test
- public void membersAreGone() throws Exception {
- run(false, WillBeGone.class, true, false);
- run(true, WillBeGone.class, true, false);
+ public void membersAreGone() {
+ compile(false, WillBeGone.class, true, null);
+ compile(true, WillBeGone.class, true, null);
}
@Test
- public void membersAreNotGone() throws Exception {
- run(false, WillStay.class, true, true);
- run(true, WillStay.class, true, true);
+ public void membersAreNotGone() {
+ Consumer<TestDiagnosticMessages> check =
+ diagnostics -> {
+ List<Diagnostic> infos = diagnostics.getInfos();
+ assertEquals(1, infos.size());
+ String message = infos.get(0).getDiagnosticMessage();
+ assertThat(message, containsString("was not discarded"));
+ assertThat(message, containsString("is invoked from"));
+ };
+ compile(false, WillStay.class, true, check);
+ compile(true, WillStay.class, true, check);
}
}