Version 1.0.10
Merge: 5b4ee0a3649503e961e9be008c8276f002de6d05
CL: Change generated lambda classes to be public
Merge: 14edaf6589c0b70dfd7d2969d34fe3803b51bb9e
CL: Skip tests with class loaders on older Art versions
Merge: 9500e7425d6e9356d8198c74545ae3142f880a3f
CL: Fix trivial goto elimination for infinite loops.
Change-Id: Ifce43528487c4cb9c674f6baac7459121f60e522
Bug:
diff --git a/build.gradle b/build.gradle
index d0a5968..c9c415e 100644
--- a/build.gradle
+++ b/build.gradle
@@ -811,6 +811,15 @@
dependsOn jar_debugTestResourcesKotlin
}
+// Examples used by tests, where Android specific APIs are used.
+task buildExampleAndroidApi(type: JavaCompile) {
+ source = fileTree(dir: file("src/test/examplesAndroidApi"), include: "**/*.java")
+ destinationDir = file("build/test/examplesAndroidApi/classes")
+ classpath = files("third_party/android_jar/lib-v26/android.jar")
+ sourceCompatibility = JavaVersion.VERSION_1_8
+ targetCompatibility = JavaVersion.VERSION_1_8
+}
+
task buildExampleKotlinJars {
def kotlinSrcDir = file("src/test/examplesKotlin")
kotlinSrcDir.eachDir { dir ->
@@ -1168,6 +1177,7 @@
dependsOn buildExampleAndroidOJars
dependsOn buildExampleAndroidPJars
dependsOn buildExampleJava9Jars
+ dependsOn buildExampleAndroidApi
def examplesDir = file("src/test/examples")
def noDexTests = [
"multidex",
diff --git a/src/main/java/com/android/tools/r8/Version.java b/src/main/java/com/android/tools/r8/Version.java
index 94585ce..8127c8c 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 = "v1.0.9";
+ public static final String LABEL = "v1.0.10";
private Version() {
}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
index 007e2cd..bb29d3d 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
@@ -125,7 +125,10 @@
type,
null,
new SynthesizedOrigin("lambda desugaring", getClass()),
- ClassAccessFlags.fromDexAccessFlags(Constants.ACC_FINAL | Constants.ACC_SYNTHETIC),
+ // Make the synthesized class public, as it might end up being accessed from a different
+ // classloader (package private access is not allowed across classloaders, b/72538146).
+ ClassAccessFlags.fromDexAccessFlags(
+ Constants.ACC_FINAL | Constants.ACC_SYNTHETIC | Constants.ACC_PUBLIC),
rewriter.factory.objectType,
buildInterfaces(),
rewriter.factory.createString("lambda"),
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
index 0b3b021..bebd2e2 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
@@ -140,6 +140,15 @@
return true;
}
+ private static boolean isFallthroughBlock(BasicBlock block) {
+ for (BasicBlock pred : block.getPredecessors()) {
+ if (pred.exit().fallthroughBlock() == block) {
+ return true;
+ }
+ }
+ return false;
+ }
+
private static void collapsTrivialGoto(
IRCode code, BasicBlock block, BasicBlock nextBlock, List<BasicBlock> blocksToRemove) {
@@ -151,21 +160,18 @@
BasicBlock target = block.endOfGotoChain();
boolean needed = false;
+
if (target == null) {
// This implies we are in a loop of GOTOs. In that case, we will iteratively remove each
// trivial GOTO one-by-one until the above base case (one block targeting itself) is left.
target = block.exit().asGoto().getTarget();
- } else if (target != nextBlock) {
+ }
+
+ if (target != nextBlock) {
// Not targeting the fallthrough block, determine if we need this goto. We need it if
// a fallthrough can hit this block. That is the case if the block is the entry block
// or if one of the predecessors fall through to the block.
- needed = code.blocks.get(0) == block;
- for (BasicBlock pred : block.getPredecessors()) {
- if (pred.exit().fallthroughBlock() == block) {
- needed = true;
- break;
- }
- }
+ needed = code.blocks.get(0) == block || isFallthroughBlock(block);
}
if (!needed) {
diff --git a/src/test/examplesAndroidApi/classloader/Runner.java b/src/test/examplesAndroidApi/classloader/Runner.java
new file mode 100644
index 0000000..4660559
--- /dev/null
+++ b/src/test/examplesAndroidApi/classloader/Runner.java
@@ -0,0 +1,36 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package classloader;
+
+import java.lang.reflect.Method;
+
+import dalvik.system.PathClassLoader;
+
+// Command line application which take three arguments:
+//
+// Parent dex file
+// Child dex file
+// Main class name
+//
+// The application will create a classloader hierachy with the parent dex file above the
+// system class loader, and the child dex file above the parent dex file. The it will load the
+// Main class from the child dex file class loader and run its main method.
+public class Runner {
+ public static void main(String[] args) throws Exception {
+ String parentFile = args[0];
+ String childFile = args[1];
+ String childClassName = args[2];
+ ClassLoader parentClassLoader =
+ new PathClassLoader(parentFile, ClassLoader.getSystemClassLoader());
+ ClassLoader childClassLoader = new PathClassLoader(childFile, parentClassLoader);
+
+ Class<?> childClass = childClassLoader.loadClass(childClassName);
+ runMain(childClass, new String[0]);
+ }
+
+ private static void runMain(Class<?> clazz, String[] args) throws Exception {
+ Method m = clazz.getMethod("main", String[].class);
+ m.invoke(null, new Object[] { args });
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index e6ed94f..00b592f 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -386,11 +386,11 @@
/**
* Run application on Art with the specified main class and provided arguments.
*/
- protected String runOnArt(AndroidApp app, Class mainClass, List<String> args) throws IOException {
+ protected String runOnArt(AndroidApp app, String mainClass, List<String> args) throws IOException {
Path out = File.createTempFile("junit", ".zip", temp.getRoot()).toPath();
app.writeToZip(out, OutputMode.DexIndexed);
return ToolHelper.runArtNoVerificationErrors(
- ImmutableList.of(out.toString()), mainClass.getCanonicalName(),
+ ImmutableList.of(out.toString()), mainClass,
builder -> {
builder.appendArtOption("-ea");
for (String arg : args) {
@@ -400,6 +400,20 @@
}
/**
+ * Run application on Art with the specified main class and provided arguments.
+ */
+ protected String runOnArt(AndroidApp app, Class mainClass, List<String> args) throws IOException {
+ return runOnArt(app, mainClass.getCanonicalName(), args);
+ }
+
+ /**
+ * Run application on Art with the specified main class and provided arguments.
+ */
+ protected String runOnArt(AndroidApp app, String mainClass, String... args) throws IOException {
+ return runOnArt(app, mainClass, Arrays.asList(args));
+ }
+
+ /**
* Run a single class application on Java.
*/
protected String runOnJava(Class mainClass) throws Exception {
diff --git a/src/test/java/com/android/tools/r8/desugar/b72538146/B72538146.java b/src/test/java/com/android/tools/r8/desugar/b72538146/B72538146.java
new file mode 100644
index 0000000..bfc400d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/b72538146/B72538146.java
@@ -0,0 +1,50 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.desugar.b72538146;
+
+import static org.junit.Assert.assertEquals;
+import com.android.tools.r8.OutputMode;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.VmTestRunner;
+import com.android.tools.r8.VmTestRunner.IgnoreIfVmOlderOrEqualThan;
+import com.android.tools.r8.utils.AndroidApp;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(VmTestRunner.class)
+public class B72538146 extends TestBase {
+
+ @Test
+ @IgnoreIfVmOlderOrEqualThan(Version.V6_0_1)
+ public void test() throws Exception {
+ // Build the main app from source compiled separately using the Android API for classloading.
+ AndroidApp.Builder builder = AndroidApp.builder();
+ builder.addProgramFile(
+ Paths.get("build/test/examplesAndroidApi/classes/classloader/Runner.class"));
+ AndroidApp app = compileWithD8(builder.build());
+
+ // Compile the parent and child applications into separate dex applications.
+ Path parent = temp.newFolder("parent").toPath().resolve("classes.zip");
+ Path child = temp.newFolder("child").toPath().resolve("classes.zip");
+ AndroidApp parentApp = readClasses(
+ Parent.class,
+ Parent.Inner1.class,
+ Parent.Inner2.class,
+ Parent.Inner3.class,
+ Parent.Inner4.class);
+ compileWithD8(parentApp).write(parent, OutputMode.DexIndexed);
+
+ AndroidApp childApp = readClasses(Child.class);
+ compileWithD8(childApp).write(child, OutputMode.DexIndexed);
+
+ // Run the classloader test loading the two dex applications.
+ String result = runOnArt(app, "classloader.Runner",
+ parent.toString(), child.toString(), "com.android.tools.r8.desugar.b72538146.Child");
+ assertEquals("SUCCESS", result);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/b72538146/Child.java b/src/test/java/com/android/tools/r8/desugar/b72538146/Child.java
new file mode 100644
index 0000000..63b90f2
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/b72538146/Child.java
@@ -0,0 +1,43 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.desugar.b72538146;
+
+import com.android.tools.r8.desugar.b72538146.Parent.Inner1;
+import com.android.tools.r8.desugar.b72538146.Parent.Inner2;
+import com.android.tools.r8.desugar.b72538146.Parent.Inner4;
+import java.util.function.Supplier;
+
+public class Child {
+ /**
+ * See what happens when app/runtime code both use and call a method reference.
+ */
+ public void calling_duplicate_method_reference() {
+ Supplier<Inner1> supplier = Inner1::new;
+ supplier.get();
+ }
+
+ /**
+ * See what happens when app/runtime code both use a method reference but neither calls it.
+ */
+ public void using_duplicate_method_reference() {
+ Supplier<Inner2> supplier = Inner2::new;
+ }
+
+ /**
+ * See what happens when app code uses and calls a method reference to runtime code.
+ */
+ public void calling_method_reference() {
+ Supplier<Inner4> supplier = Inner4::new;
+ supplier.get();
+ }
+
+ public static void main(String[] args) {
+ Child instance = new Child();
+ instance.calling_duplicate_method_reference();
+ instance.using_duplicate_method_reference();
+ instance.calling_method_reference();
+ System.out.print("SUCCESS");
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/b72538146/Parent.java b/src/test/java/com/android/tools/r8/desugar/b72538146/Parent.java
new file mode 100644
index 0000000..63bc80d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/b72538146/Parent.java
@@ -0,0 +1,50 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.desugar.b72538146;
+
+import java.util.function.Supplier;
+
+public class Parent {
+ private static final Inner1 INNER_1;
+ static {
+ Supplier<Inner1> inner1Supplier = Inner1::new;
+ INNER_1 = inner1Supplier.get();
+ }
+
+ private static final Supplier<Inner2> INNER_2_SUPPLIER = Inner2::new;
+
+ private static final Inner3 INNER_3;
+ static {
+ Supplier<Inner3> inner3Supplier = Inner3::new;
+ INNER_3 = inner3Supplier.get();
+ }
+
+ public Parent() {
+ }
+
+ public static class Inner1 {
+
+ public Inner1() {
+ }
+ }
+
+ public static class Inner2 {
+
+ public Inner2() {
+ }
+ }
+
+ public static class Inner3 {
+
+ public Inner3() {
+ }
+ }
+
+ public static class Inner4 {
+
+ public Inner4() {
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/TrivialGotoEliminationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/TrivialGotoEliminationTest.java
index 8697917..f76f6f1 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/TrivialGotoEliminationTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/TrivialGotoEliminationTest.java
@@ -3,9 +3,13 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.ir.optimize;
+import com.android.tools.r8.ir.code.Argument;
import com.android.tools.r8.ir.code.BasicBlock;
import com.android.tools.r8.ir.code.ConstNumber;
+import com.android.tools.r8.ir.code.Goto;
import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.If;
+import com.android.tools.r8.ir.code.If.Type;
import com.android.tools.r8.ir.code.Instruction;
import com.android.tools.r8.ir.code.Position;
import com.android.tools.r8.ir.code.Return;
@@ -62,4 +66,71 @@
assert blocks.contains(block1);
assert blocks.contains(block2);
}
+
+ @Test
+ public void trivialGotoLoopAsFallthrough() {
+ // Setup block structure:
+ // block0:
+ // v0 <- argument
+ // if ne v0 block2
+ //
+ // block1:
+ // goto block3
+ //
+ // block2:
+ // return
+ //
+ // block3:
+ // goto block3
+ BasicBlock block2 = new BasicBlock();
+ block2.setNumber(2);
+ Instruction ret = new Return();
+ ret.setPosition(Position.none());
+ block2.add(ret);
+ block2.setFilledForTesting();
+
+ BasicBlock block3 = new BasicBlock();
+ block3.setNumber(3);
+ Instruction instruction = new Goto();
+ instruction.setPosition(Position.none());
+ block3.add(instruction);
+ block3.setFilledForTesting();
+ block3.getSuccessors().add(block3);
+
+ BasicBlock block1 = BasicBlock.createGotoBlock(1);
+ block1.getSuccessors().add(block3);
+ block1.setFilledForTesting();
+
+ BasicBlock block0 = new BasicBlock();
+ block0.setNumber(0);
+ Value value = new Value(0, ValueType.OBJECT, null);
+ instruction = new Argument(value);
+ instruction.setPosition(Position.none());
+ block0.add(instruction);
+ instruction = new If(Type.EQ, value);
+ instruction.setPosition(Position.none());
+ block0.add(instruction);
+ block0.getSuccessors().add(block2);
+ block0.getSuccessors().add(block1);
+ block0.setFilledForTesting();
+
+ block1.getPredecessors().add(block0);
+ block2.getPredecessors().add(block0);
+ block3.getPredecessors().add(block1);
+ block3.getPredecessors().add(block3);
+
+ LinkedList<BasicBlock> blocks = new LinkedList<>();
+ blocks.add(block0);
+ blocks.add(block1);
+ blocks.add(block2);
+ blocks.add(block3);
+ // Check that the goto in block0 remains. There was a bug in the trivial goto elimination
+ // that ended up removing that goto changing the code to start with the unreachable
+ // throw.
+ IRCode code = new IRCode(null, blocks, new ValueNumberGenerator(), false);
+ CodeRewriter.collapsTrivialGotos(null, code);
+ assert block0.getInstructions().get(1).isIf();
+ assert block0.getInstructions().get(1).asIf().fallthroughBlock() == block1;
+ assert blocks.containsAll(ImmutableList.of(block0, block1, block2, block3));
+ }
}