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