Try catch in desugared APIs

Bug: 134732760
Change-Id: I4658598b2cec94b9cfc88ba2e71ae4ebfeff8922
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryAPIConverter.java b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryAPIConverter.java
index b9a758a..881749c 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryAPIConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryAPIConverter.java
@@ -15,6 +15,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.analysis.type.Nullability;
 import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.InstructionListIterator;
@@ -33,6 +34,7 @@
 import java.util.HashMap;
 import java.util.LinkedList;
 import java.util.List;
+import java.util.ListIterator;
 import java.util.Map;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
@@ -75,27 +77,31 @@
 
     generateCallBackIfNeeded(code);
 
-    InstructionListIterator iterator = code.instructionListIterator();
-    while (iterator.hasNext()) {
-      Instruction instruction = iterator.next();
-      if (!instruction.isInvokeMethod()) {
-        continue;
-      }
-      InvokeMethod invokeMethod = instruction.asInvokeMethod();
-      DexMethod invokedMethod = invokeMethod.getInvokedMethod();
-      // Rewritting is required only on calls to library methods which are not desugared.
-      if (appView.rewritePrefix.hasRewrittenType(invokedMethod.holder)
-          || invokedMethod.holder.isArrayType()) {
-        continue;
-      }
-      DexClass dexClass = appView.definitionFor(invokedMethod.holder);
-      if (dexClass == null || !dexClass.isLibraryClass()) {
-        continue;
-      }
-      // Library methods do not understand desugared types, hence desugared types have to be
-      // converted around non desugared library calls for the invoke to resolve.
-      if (appView.rewritePrefix.hasRewrittenTypeInSignature(invokedMethod.proto)) {
-        rewriteLibraryInvoke(code, invokeMethod, iterator);
+    ListIterator<BasicBlock> blockIterator = code.listIterator();
+    while (blockIterator.hasNext()) {
+      BasicBlock block = blockIterator.next();
+      InstructionListIterator iterator = block.listIterator(code);
+      while (iterator.hasNext()) {
+        Instruction instruction = iterator.next();
+        if (!instruction.isInvokeMethod()) {
+          continue;
+        }
+        InvokeMethod invokeMethod = instruction.asInvokeMethod();
+        DexMethod invokedMethod = invokeMethod.getInvokedMethod();
+        // Rewriting is required only on calls to library methods which are not desugared.
+        if (appView.rewritePrefix.hasRewrittenType(invokedMethod.holder)
+            || invokedMethod.holder.isArrayType()) {
+          continue;
+        }
+        DexClass dexClass = appView.definitionFor(invokedMethod.holder);
+        if (dexClass == null || !dexClass.isLibraryClass()) {
+          continue;
+        }
+        // Library methods do not understand desugared types, hence desugared types have to be
+        // converted around non desugared library calls for the invoke to resolve.
+        if (appView.rewritePrefix.hasRewrittenTypeInSignature(invokedMethod.proto)) {
+          rewriteLibraryInvoke(code, invokeMethod, iterator, blockIterator);
+        }
       }
     }
   }
@@ -231,7 +237,10 @@
   }
 
   private void rewriteLibraryInvoke(
-      IRCode code, InvokeMethod invokeMethod, InstructionListIterator iterator) {
+      IRCode code,
+      InvokeMethod invokeMethod,
+      InstructionListIterator iterator,
+      ListIterator<BasicBlock> blockIterator) {
     DexMethod invokedMethod = invokeMethod.getInvokedMethod();
 
     // Create return conversion if required.
@@ -310,6 +319,41 @@
       returnConversion.setPosition(invokeMethod.getPosition());
       iterator.add(returnConversion);
     }
+
+    // If the invoke is in a try-catch, since all conversions can throw, the basic block needs
+    // to be split in between each invoke...
+    if (newInvokeMethod.getBlock().hasCatchHandlers()) {
+      splitIfCatchHandlers(code, newInvokeMethod.getBlock(), blockIterator);
+    }
+  }
+
+  private void splitIfCatchHandlers(
+      IRCode code,
+      BasicBlock blockWithIncorrectThrowingInstructions,
+      ListIterator<BasicBlock> blockIterator) {
+    InstructionListIterator instructionsIterator =
+        blockWithIncorrectThrowingInstructions.listIterator(code);
+    BasicBlock currentBlock = blockWithIncorrectThrowingInstructions;
+    while (currentBlock != null && instructionsIterator.hasNext()) {
+      Instruction throwingInstruction =
+          instructionsIterator.nextUntil(Instruction::instructionTypeCanThrow);
+      BasicBlock nextBlock;
+      if (throwingInstruction != null) {
+        nextBlock = instructionsIterator.split(code, blockIterator);
+        // Back up to before the split before inserting catch handlers.
+        blockIterator.previous();
+        nextBlock.copyCatchHandlers(code, blockIterator, currentBlock, appView.options());
+        BasicBlock b = blockIterator.next();
+        assert b == nextBlock;
+        // Switch iteration to the split block.
+        instructionsIterator = nextBlock.listIterator(code);
+        currentBlock = nextBlock;
+      } else {
+        assert !instructionsIterator.hasNext();
+        instructionsIterator = null;
+        currentBlock = null;
+      }
+    }
   }
 
   private Instruction createParameterConversion(
diff --git a/src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/TryCatchTimeConversionTest.java b/src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/TryCatchTimeConversionTest.java
new file mode 100644
index 0000000..7fdae41
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/TryCatchTimeConversionTest.java
@@ -0,0 +1,211 @@
+// 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.desugar.corelib.conversionTests;
+
+import com.android.tools.r8.TestRuntime.DexRuntime;
+import com.android.tools.r8.ToolHelper.DexVm;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.StringUtils;
+import java.nio.file.Path;
+import java.time.ZoneId;
+import org.junit.Test;
+
+public class TryCatchTimeConversionTest extends APIConversionTestBase {
+
+  @Test
+  public void testBaseline() throws Exception {
+    Path customLib = testForD8().addProgramClasses(CustomLibClass.class).compile().writeToZip();
+    testForD8()
+        .setMinApi(AndroidApiLevel.B)
+        .addProgramClasses(BaselineExecutor.class)
+        .addLibraryClasses(CustomLibClass.class)
+        .enableCoreLibraryDesugaring(AndroidApiLevel.B)
+        .compile()
+        .addDesugaredCoreLibraryRunClassPath(
+            this::buildDesugaredLibraryWithConversionExtension, AndroidApiLevel.B)
+        .addRunClasspathFiles(customLib)
+        .run(new DexRuntime(DexVm.ART_9_0_0_HOST), BaselineExecutor.class)
+        .assertSuccessWithOutput(StringUtils.lines("GMT", "GMT", "GMT", "GMT", "GMT"));
+  }
+
+  @Test
+  public void testTryCatch() throws Exception {
+    Path customLib = testForD8().addProgramClasses(CustomLibClass.class).compile().writeToZip();
+    testForD8()
+        .setMinApi(AndroidApiLevel.B)
+        .addProgramClasses(TryCatchExecutor.class)
+        .addLibraryClasses(CustomLibClass.class)
+        .enableCoreLibraryDesugaring(AndroidApiLevel.B)
+        .compile()
+        .addDesugaredCoreLibraryRunClassPath(
+            this::buildDesugaredLibraryWithConversionExtension, AndroidApiLevel.B)
+        .addRunClasspathFiles(customLib)
+        .run(new DexRuntime(DexVm.ART_9_0_0_HOST), TryCatchExecutor.class)
+        .assertSuccessWithOutput(
+            StringUtils.lines("GMT", "GMT", "GMT", "GMT", "GMT", "Exception caught"));
+  }
+
+  @SuppressWarnings("WeakerAccess")
+  static class BaselineExecutor {
+
+    private static final String ZONE_ID = "GMT";
+
+    public static void main(String[] args) {
+      returnOnly();
+      oneParameter();
+      twoParameters();
+      oneParameterReturn();
+      twoParametersReturn();
+    }
+
+    public static void returnOnly() {
+      ZoneId returnOnly = CustomLibClass.returnOnly();
+      System.out.println(returnOnly.getId());
+    }
+
+    public static void oneParameterReturn() {
+      ZoneId z1 = ZoneId.of(ZONE_ID);
+      ZoneId oneParam = CustomLibClass.oneParameterReturn(z1);
+      System.out.println(oneParam.getId());
+    }
+
+    public static void twoParametersReturn() {
+      ZoneId z1 = ZoneId.of(ZONE_ID);
+      ZoneId z2 = ZoneId.of(ZONE_ID);
+      ZoneId twoParam = CustomLibClass.twoParametersReturn(z1, z2);
+      System.out.println(twoParam.getId());
+    }
+
+    public static void oneParameter() {
+      ZoneId z1 = ZoneId.of(ZONE_ID);
+      String res = CustomLibClass.oneParameter(z1);
+      System.out.println(res);
+    }
+
+    public static void twoParameters() {
+      ZoneId z1 = ZoneId.of(ZONE_ID);
+      ZoneId z2 = ZoneId.of(ZONE_ID);
+      String res = CustomLibClass.twoParameters(z1, z2);
+      System.out.println(res);
+    }
+  }
+
+  @SuppressWarnings("WeakerAccess")
+  static class TryCatchExecutor {
+
+    private static final String ZONE_ID = "GMT";
+
+    public static void main(String[] args) {
+      returnOnly();
+      oneParameter();
+      twoParameters();
+      oneParameterReturn();
+      twoParametersReturn();
+      twoParametersThrow();
+    }
+
+    public static void returnOnly() {
+      ZoneId returnOnly;
+      try {
+        returnOnly = CustomLibClass.returnOnly();
+      } catch (Exception e) {
+        throw new RuntimeException("Test failed.");
+      }
+      System.out.println(returnOnly.getId());
+    }
+
+    public static void oneParameterReturn() {
+      ZoneId z1 = ZoneId.of(ZONE_ID);
+      ZoneId oneParam;
+      try {
+        oneParam = CustomLibClass.oneParameterReturn(z1);
+      } catch (Exception e) {
+        throw new RuntimeException("Test failed.");
+      }
+      System.out.println(oneParam.getId());
+    }
+
+    public static void twoParametersReturn() {
+      ZoneId z1 = ZoneId.of(ZONE_ID);
+      ZoneId z2 = ZoneId.of(ZONE_ID);
+      ZoneId twoParam;
+      try {
+        twoParam = CustomLibClass.twoParametersReturn(z1, z2);
+      } catch (Exception e) {
+        throw new RuntimeException("Test failed.");
+      }
+      System.out.println(twoParam.getId());
+    }
+
+    public static void oneParameter() {
+      ZoneId z1 = ZoneId.of(ZONE_ID);
+      String res;
+      try {
+        res = CustomLibClass.oneParameter(z1);
+      } catch (Exception e) {
+        throw new RuntimeException("Test failed.");
+      }
+      System.out.println(res);
+    }
+
+    public static void twoParameters() {
+      ZoneId z1 = ZoneId.of(ZONE_ID);
+      ZoneId z2 = ZoneId.of(ZONE_ID);
+      String res;
+      try {
+        res = CustomLibClass.twoParameters(z1, z2);
+      } catch (Exception e) {
+        throw new RuntimeException("Test failed.");
+      }
+      System.out.println(res);
+    }
+
+    @SuppressWarnings("ResultOfMethodCallIgnored")
+    public static void twoParametersThrow() {
+      ZoneId z1 = ZoneId.of(ZONE_ID);
+      ZoneId z2 = ZoneId.of(ZONE_ID);
+      try {
+        CustomLibClass.twoParametersThrow(z1, z2);
+        throw new RuntimeException("Test failed.");
+      } catch (ArithmeticException e) {
+        System.out.println("Exception caught");
+      }
+    }
+  }
+
+  // This class will be put at compilation time as library and on the runtime class path.
+  // This class is convenient for easy testing. Each method plays the role of methods in the
+  // platform APIs for which argument/return values need conversion.
+  @SuppressWarnings("WeakerAccess")
+  static class CustomLibClass {
+
+    private static final String ZONE_ID = "GMT";
+
+    public static ZoneId returnOnly() {
+      return ZoneId.of(ZONE_ID);
+    }
+
+    public static ZoneId oneParameterReturn(ZoneId z1) {
+      return z1;
+    }
+
+    public static ZoneId twoParametersReturn(ZoneId z1, ZoneId z2) {
+      return z1;
+    }
+
+    public static String oneParameter(ZoneId z1) {
+      return z1.getId();
+    }
+
+    public static String twoParameters(ZoneId z1, ZoneId z2) {
+      return z1.getId();
+    }
+
+    @SuppressWarnings({"divzero", "NumericOverflow", "UnusedReturnValue"})
+    public static String twoParametersThrow(ZoneId z1, ZoneId z2) {
+      return "" + (1 / 0);
+    }
+  }
+}