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));
+  }
 }