Version 1.4.54
Cherry-pick: Ensure that use of -whyareyoukeeping does not cause compilation to fail.
CL: https://r8-review.googlesource.com/c/r8/+/34541
Cherry-pick: Allow functions with throwing clauses in testing map and apply.
CL: https://r8-review.googlesource.com/c/r8/+/34533
Cherry-pick: Add map and apply to the testing builders.
CL: https://r8-review.googlesource.com/c/r8/+/34473
Bug: 124655989
Change-Id: I063fb05fc18956efb9aae80ad1f061fef2e28839
diff --git a/src/main/java/com/android/tools/r8/Version.java b/src/main/java/com/android/tools/r8/Version.java
index 1f49155..543f16a 100644
--- a/src/main/java/com/android/tools/r8/Version.java
+++ b/src/main/java/com/android/tools/r8/Version.java
@@ -11,7 +11,7 @@
// This field is accessed from release scripts using simple pattern matching.
// Therefore, changing this field could break our release scripts.
- public static final String LABEL = "1.4.53";
+ public static final String LABEL = "1.4.54";
private Version() {
}
diff --git a/src/main/java/com/android/tools/r8/experimental/graphinfo/GraphEdgeInfo.java b/src/main/java/com/android/tools/r8/experimental/graphinfo/GraphEdgeInfo.java
index ca3a732..3e55242 100644
--- a/src/main/java/com/android/tools/r8/experimental/graphinfo/GraphEdgeInfo.java
+++ b/src/main/java/com/android/tools/r8/experimental/graphinfo/GraphEdgeInfo.java
@@ -3,10 +3,14 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.experimental.graphinfo;
-import com.android.tools.r8.errors.Unreachable;
-
public class GraphEdgeInfo {
+ private static GraphEdgeInfo UNKNOWN = new GraphEdgeInfo(EdgeKind.Unknown);
+
+ public static GraphEdgeInfo unknown() {
+ return UNKNOWN;
+ }
+
// TODO(b/120959039): Simplify these. Most of the information is present in the source node.
public enum EdgeKind {
// Prioritized list of edge types.
@@ -23,7 +27,8 @@
ReachableFromLiveType,
ReferencedInAnnotation,
IsLibraryMethod,
- MethodHandleUseFrom
+ MethodHandleUseFrom,
+ Unknown
}
private final EdgeKind kind;
@@ -66,7 +71,10 @@
case MethodHandleUseFrom:
return "referenced by method handle";
default:
- throw new Unreachable("Unexpected edge kind: " + edgeKind());
+ assert false : "Unknown edge kind: " + edgeKind();
+ // fall through
+ case Unknown:
+ return "kept for unknown reasons";
}
}
diff --git a/src/main/java/com/android/tools/r8/experimental/graphinfo/GraphNode.java b/src/main/java/com/android/tools/r8/experimental/graphinfo/GraphNode.java
index b99485a..12661c0 100644
--- a/src/main/java/com/android/tools/r8/experimental/graphinfo/GraphNode.java
+++ b/src/main/java/com/android/tools/r8/experimental/graphinfo/GraphNode.java
@@ -8,12 +8,38 @@
@Keep
public abstract class GraphNode {
+ private static final GraphNode CYCLE =
+ new GraphNode(false) {
+ @Override
+ public boolean equals(Object o) {
+ return o == this;
+ }
+
+ @Override
+ public int hashCode() {
+ return 0;
+ }
+
+ @Override
+ public String toString() {
+ return "cycle";
+ }
+ };
+
private final boolean isLibraryNode;
public GraphNode(boolean isLibraryNode) {
this.isLibraryNode = isLibraryNode;
}
+ public static GraphNode cycle() {
+ return CYCLE;
+ }
+
+ public final boolean isCycle() {
+ return this == cycle();
+ }
+
public boolean isLibraryNode() {
return isLibraryNode;
}
diff --git a/src/main/java/com/android/tools/r8/shaking/WhyAreYouKeepingConsumer.java b/src/main/java/com/android/tools/r8/shaking/WhyAreYouKeepingConsumer.java
index 89a6f0e..d56e455 100644
--- a/src/main/java/com/android/tools/r8/shaking/WhyAreYouKeepingConsumer.java
+++ b/src/main/java/com/android/tools/r8/shaking/WhyAreYouKeepingConsumer.java
@@ -3,7 +3,6 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.shaking;
-import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.experimental.graphinfo.ClassGraphNode;
import com.android.tools.r8.experimental.graphinfo.FieldGraphNode;
import com.android.tools.r8.experimental.graphinfo.GraphConsumer;
@@ -32,6 +31,7 @@
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.Set;
/**
@@ -125,50 +125,53 @@
out.println(DescriptorUtils.descriptorToJavaType(clazz.getDescriptor()));
}
- public List<Pair<GraphNode, GraphEdgeInfo>> findShortestPathTo(final GraphNode node) {
+ private List<Pair<GraphNode, GraphEdgeInfo>> findShortestPathTo(final GraphNode node) {
if (node == null) {
return null;
}
- Deque<GraphPath> queue;
- {
- Map<GraphNode, Set<GraphEdgeInfo>> sources = getSourcesTargeting(node);
- if (sources == null) {
- // The node is not targeted at all (it is not reachable).
- return null;
- }
- queue = new LinkedList<>();
- for (GraphNode source : sources.keySet()) {
- queue.addLast(new GraphPath(source, null));
- }
- }
Map<GraphNode, GraphNode> seen = new IdentityHashMap<>();
- while (!queue.isEmpty()) {
- GraphPath path = queue.removeFirst();
- Map<GraphNode, Set<GraphEdgeInfo>> sources = getSourcesTargeting(path.node);
+ Deque<GraphPath> queue = new LinkedList<>();
+ GraphPath path = null;
+ GraphNode current = node;
+ while (true) {
+ Map<GraphNode, Set<GraphEdgeInfo>> sources = getSourcesTargeting(current);
if (sources == null) {
+ // We have reached a root or the current node is not targeted at all.
return getCanonicalPath(path, node);
}
+ assert !sources.isEmpty();
for (GraphNode source : sources.keySet()) {
- if (seen.containsKey(source)) {
- continue;
+ if (!seen.containsKey(source)) {
+ seen.put(source, source);
+ queue.addLast(new GraphPath(source, path));
}
- seen.put(source, source);
- queue.addLast(new GraphPath(source, path));
}
+ // The source set was not empty, thus we don't have a real root, but all sources are seen!
+ if (queue.isEmpty()) {
+ return getCanonicalPath(new GraphPath(GraphNode.cycle(), path), node);
+ }
+ path = queue.removeFirst();
+ current = path.node;
}
- throw new Unreachable("Failed to find a root from node: " + node);
}
// Convert a internal path representation to the external API and compute the edge reasons.
private List<Pair<GraphNode, GraphEdgeInfo>> getCanonicalPath(
GraphPath path, GraphNode endTarget) {
- assert path != null;
+ if (path == null) {
+ // If there is no path to endTarget, treat it as not kept by returning a null path.
+ return null;
+ }
List<Pair<GraphNode, GraphEdgeInfo>> canonical = new ArrayList<>();
while (path.path != null) {
GraphNode source = path.node;
- GraphNode target = path.path.node;
- Set<GraphEdgeInfo> infos = getSourcesTargeting(target).get(source);
- canonical.add(new Pair<>(source, getCanonicalInfo(infos)));
+ if (source.isCycle()) {
+ canonical.add(new Pair<>(source, new GraphEdgeInfo(EdgeKind.Unknown)));
+ } else {
+ GraphNode target = path.path.node;
+ Set<GraphEdgeInfo> infos = getSourcesTargeting(target).get(source);
+ canonical.add(new Pair<>(source, getCanonicalInfo(infos)));
+ }
path = path.path;
}
Set<GraphEdgeInfo> infos = getSourcesTargeting(endTarget).get(path.node);
@@ -186,7 +189,8 @@
}
}
}
- throw new Unreachable("Unexpected empty set of graph edge info");
+ assert false : "Unexpected empty set of graph edge info";
+ return GraphEdgeInfo.unknown();
}
private void printEdge(GraphNode node, GraphEdgeInfo info, Formatter formatter) {
@@ -225,7 +229,11 @@
? keepRuleNode.getContent()
: keepRuleNode.getOrigin() + ":" + shortPositionInfo(keepRuleNode.getPosition());
}
- throw new Unreachable("Unexpected graph node type: " + node);
+ if (node == GraphNode.cycle()) {
+ return "only cyclic dependencies remain, failed to determine a path from a keep rule";
+ }
+ assert false : "Unexpected graph node type: " + node;
+ return Objects.toString(node);
}
private void addNodeMessage(GraphNode node, Formatter formatter) {
diff --git a/src/test/java/com/android/tools/r8/TestBaseResult.java b/src/test/java/com/android/tools/r8/TestBaseResult.java
index e7623a3..11b7bd3 100644
--- a/src/test/java/com/android/tools/r8/TestBaseResult.java
+++ b/src/test/java/com/android/tools/r8/TestBaseResult.java
@@ -5,6 +5,7 @@
package com.android.tools.r8;
public abstract class TestBaseResult<CR extends TestBaseResult<CR, RR>, RR extends TestRunResult> {
+
final TestState state;
TestBaseResult(TestState state) {
@@ -12,4 +13,13 @@
}
public abstract CR self();
+
+ public <S> S map(ThrowableFunction<CR, S> fn) {
+ return fn.applyWithRuntimeException(self());
+ }
+
+ public <T extends Throwable> CR apply(ThrowableConsumer<CR> fn) {
+ fn.acceptWithRuntimeException(self());
+ return self();
+ }
}
diff --git a/src/test/java/com/android/tools/r8/TestBuilder.java b/src/test/java/com/android/tools/r8/TestBuilder.java
index c67c05c..cee2519 100644
--- a/src/test/java/com/android/tools/r8/TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestBuilder.java
@@ -24,6 +24,15 @@
abstract T self();
+ public <S> S map(ThrowableFunction<T, S> fn) {
+ return fn.applyWithRuntimeException(self());
+ }
+
+ public T apply(ThrowableConsumer<T> fn) {
+ fn.acceptWithRuntimeException(self());
+ return self();
+ }
+
public abstract RR run(String mainClass)
throws IOException, CompilationFailedException;
diff --git a/src/test/java/com/android/tools/r8/TestRunResult.java b/src/test/java/com/android/tools/r8/TestRunResult.java
index 65a57e6..b752bce 100644
--- a/src/test/java/com/android/tools/r8/TestRunResult.java
+++ b/src/test/java/com/android/tools/r8/TestRunResult.java
@@ -9,12 +9,12 @@
import static org.junit.Assert.assertThat;
import com.android.tools.r8.ToolHelper.ProcessResult;
-import com.android.tools.r8.graph.invokesuper.Consumer;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import java.io.IOException;
import java.io.PrintStream;
import java.util.concurrent.ExecutionException;
+import java.util.function.Consumer;
import java.util.function.Function;
import org.hamcrest.Matcher;
@@ -29,6 +29,19 @@
abstract RR self();
+ public <S> S map(Function<RR, S> fn) {
+ return fn.apply(self());
+ }
+
+ public RR apply(Consumer<RR> fn) {
+ fn.accept(self());
+ return self();
+ }
+
+ public AndroidApp app() {
+ return app;
+ }
+
public String getStdOut() {
return result.stdout;
}
@@ -70,10 +83,6 @@
return self();
}
- public <R> R map(Function<RR, R> mapper) {
- return mapper.apply(self());
- }
-
public CodeInspector inspector() throws IOException, ExecutionException {
// Inspection post run implies success. If inspection of an invalid program is needed it should
// be done on the compilation result or on the input.
diff --git a/src/test/java/com/android/tools/r8/ThrowableConsumer.java b/src/test/java/com/android/tools/r8/ThrowableConsumer.java
new file mode 100644
index 0000000..664238e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ThrowableConsumer.java
@@ -0,0 +1,23 @@
+// Copyright (c) 2019, 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;
+
+import java.util.function.Function;
+
+public interface ThrowableConsumer<Formal> {
+ void accept(Formal formal) throws Throwable;
+
+ default <T extends Throwable> void acceptWithHandler(
+ Formal formal, Function<Throwable, T> handler) throws T {
+ try {
+ accept(formal);
+ } catch (Throwable e) {
+ throw handler.apply(e);
+ }
+ }
+
+ default void acceptWithRuntimeException(Formal formal) {
+ acceptWithHandler(formal, RuntimeException::new);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ThrowableFunction.java b/src/test/java/com/android/tools/r8/ThrowableFunction.java
new file mode 100644
index 0000000..b97d42b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ThrowableFunction.java
@@ -0,0 +1,23 @@
+// Copyright (c) 2019, 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;
+
+import java.util.function.Function;
+
+public interface ThrowableFunction<Formal, Return> {
+ Return apply(Formal formal) throws Throwable;
+
+ default <T extends Throwable> Return applyWithHandler(
+ Formal formal, Function<Throwable, T> handler) throws T {
+ try {
+ return apply(formal);
+ } catch (Throwable e) {
+ throw handler.apply(e);
+ }
+ }
+
+ default Return applyWithRuntimeException(Formal formal) {
+ return applyWithHandler(formal, RuntimeException::new);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptViaClassInitializerTestRunner.java b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptViaClassInitializerTestRunner.java
new file mode 100644
index 0000000..344721f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptViaClassInitializerTestRunner.java
@@ -0,0 +1,126 @@
+// Copyright (c) 2019, 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.keptgraph;
+
+import static com.android.tools.r8.references.Reference.classFromClass;
+import static com.android.tools.r8.references.Reference.methodFromMethod;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.VmTestRunner.IgnoreIfVmOlderThan;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.shaking.WhyAreYouKeepingConsumer;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.graphinspector.GraphInspector;
+import com.android.tools.r8.utils.graphinspector.GraphInspector.QueryNode;
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+import java.util.function.Supplier;
+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 KeptViaClassInitializerTestRunner extends TestBase {
+
+ @NeverMerge
+ @NeverClassInline
+ public static class A {
+
+ @Override
+ public String toString() {
+ return "I'm an A";
+ }
+ }
+
+ @NeverMerge
+ @NeverClassInline
+ public enum T {
+ A(A::new);
+
+ private final Supplier<Object> factory;
+
+ T(Supplier<Object> factory) {
+ this.factory = factory;
+ }
+
+ public Object create() {
+ return factory.get();
+ }
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ System.out.println(T.A.create());
+ }
+ }
+
+ private static final Class<?> CLASS = Main.class;
+ private static final String EXPECTED = StringUtils.lines("I'm an A");
+
+ private final Backend backend;
+
+ @Parameters(name = "{0}")
+ public static Backend[] data() {
+ return Backend.values();
+ }
+
+ public KeptViaClassInitializerTestRunner(Backend backend) {
+ this.backend = backend;
+ }
+
+ @Test
+ @IgnoreIfVmOlderThan(Version.V7_0_0)
+ public void testKeptMethod() throws Exception {
+ MethodReference mainMethod =
+ methodFromMethod(Main.class.getDeclaredMethod("main", String[].class));
+
+ WhyAreYouKeepingConsumer consumer = new WhyAreYouKeepingConsumer(null);
+ GraphInspector inspector =
+ testForR8(backend)
+ .enableGraphInspector(consumer)
+ .enableInliningAnnotations()
+ .addProgramClassesAndInnerClasses(Main.class, A.class, T.class)
+ .addKeepMethodRules(mainMethod)
+ .apply(
+ b -> {
+ if (backend == Backend.DEX) {
+ b.setMinApi(AndroidApiLevel.N);
+ b.addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.N));
+ }
+ })
+ .run(Main.class)
+ .assertSuccessWithOutput(EXPECTED)
+ .graphInspector();
+
+ // The only root should be the keep main-method rule.
+ assertEquals(1, inspector.getRoots().size());
+ QueryNode root = inspector.rule(Origin.unknown(), 1, 1).assertRoot();
+
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ consumer.printWhyAreYouKeeping(classFromClass(A.class), new PrintStream(baos));
+
+ // TODO(b/124501298): Currently the rooted path is not found.
+ assertThat(baos.toString(), containsString("is kept for unknown reason"));
+
+ // TODO(b/124499108): Currently synthetic lambda classes are referenced,
+ // should be their originating context.
+ if (backend == Backend.DEX) {
+ assertThat(baos.toString(), containsString("-$$Lambda$"));
+ } else {
+ assertThat(baos.toString(), not(containsString("-$$Lambda$")));
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/keptgraph/WhyAreYouKeepingAllTest.java b/src/test/java/com/android/tools/r8/shaking/keptgraph/WhyAreYouKeepingAllTest.java
new file mode 100644
index 0000000..da94e04
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/keptgraph/WhyAreYouKeepingAllTest.java
@@ -0,0 +1,44 @@
+// Copyright (c) 2019, 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.keptgraph;
+
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.utils.StringUtils;
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import org.junit.Test;
+
+/**
+ * Run compiling R8 with R8 using a match-all -whyareyoukeeping rule to check that it does not cause
+ * compilation to fail.
+ */
+public class WhyAreYouKeepingAllTest extends TestBase {
+
+ private static final Path MAIN_KEEP = Paths.get("src/main/keep.txt");
+
+ private static final String WHY_ARE_YOU_KEEPING_ALL = StringUtils.lines(
+ "-whyareyoukeeping class ** { *; }",
+ "-whyareyoukeeping @interface ** { *; }"
+ );
+
+ @Test
+ public void test() throws Throwable {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ testForR8(Backend.CF)
+ .addProgramFiles(ToolHelper.R8_WITH_RELOCATED_DEPS_JAR)
+ .addKeepRuleFiles(MAIN_KEEP)
+ .addKeepRules(WHY_ARE_YOU_KEEPING_ALL)
+ .redirectStdOut(new PrintStream(baos))
+ .compile();
+ assertThat(baos.toString(), containsString("referenced in keep rule"));
+ // TODO(b/124655065): We should always know the reason for keeping.
+ assertThat(baos.toString(), containsString("kept for unknown reasons"));
+ }
+}