Preserve suppressed exception behavior on VMs with native support.
Bug: 195500117
Change-Id: Ie3f4a350f289ad678e25e6a243ba77b9fd23da0c
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java b/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java
index 7005bba..282f4f1 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java
@@ -162,7 +162,7 @@
@Override
public void acceptTwrCloseResourceMethod(ProgramMethod closeMethod, ProgramMethod context) {
- methodProcessor.scheduleDesugaredMethodForProcessing(closeMethod);
+ methodProcessor.scheduleMethodForProcessing(closeMethod, this);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethods.java b/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethods.java
index d7f7109..b934c40 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethods.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethods.java
@@ -14,6 +14,7 @@
import com.android.tools.r8.cf.code.CfArrayStore;
import com.android.tools.r8.cf.code.CfCheckCast;
import com.android.tools.r8.cf.code.CfCmp;
+import com.android.tools.r8.cf.code.CfConstClass;
import com.android.tools.r8.cf.code.CfConstNull;
import com.android.tools.r8.cf.code.CfConstNumber;
import com.android.tools.r8.cf.code.CfConstString;
@@ -109,6 +110,7 @@
factory.createSynthesizedType("[Ljava/lang/CharSequence;");
factory.createSynthesizedType("[Ljava/lang/Class;");
factory.createSynthesizedType("[Ljava/lang/Object;");
+ factory.createSynthesizedType("[Ljava/lang/Throwable;");
factory.createSynthesizedType("[Ljava/util/Map$Entry;");
}
@@ -511,10 +513,14 @@
CfLabel label15 = new CfLabel();
CfLabel label16 = new CfLabel();
CfLabel label17 = new CfLabel();
+ CfLabel label18 = new CfLabel();
+ CfLabel label19 = new CfLabel();
+ CfLabel label20 = new CfLabel();
+ CfLabel label21 = new CfLabel();
return new CfCode(
method.holder,
+ 6,
4,
- 3,
ImmutableList.of(
label0,
new CfLoad(ValueType.OBJECT, 1),
@@ -749,7 +755,7 @@
FrameType.initialized(options.itemFactory.objectType)
}),
new ArrayDeque<>(Arrays.asList())),
- new CfGoto(label16),
+ new CfGoto(label20),
label12,
new CfFrame(
new Int2ReferenceAVLTreeMap<>(
@@ -763,10 +769,76 @@
new CfStore(ValueType.OBJECT, 2),
label13,
new CfLoad(ValueType.OBJECT, 0),
- new CfIf(If.Type.EQ, ValueType.OBJECT, label14),
- new CfLoad(ValueType.OBJECT, 0),
- new CfGoto(label15),
+ new CfIf(If.Type.EQ, ValueType.OBJECT, label19),
label14,
+ new CfConstClass(options.itemFactory.throwableType),
+ new CfConstString(options.itemFactory.createString("addSuppressed")),
+ new CfConstNumber(1, ValueType.INT),
+ new CfNewArray(options.itemFactory.createType("[Ljava/lang/Class;")),
+ new CfStackInstruction(CfStackInstruction.Opcode.Dup),
+ new CfConstNumber(0, ValueType.INT),
+ new CfConstClass(options.itemFactory.throwableType),
+ new CfArrayStore(MemberType.OBJECT),
+ new CfInvoke(
+ 182,
+ options.itemFactory.createMethod(
+ options.itemFactory.classType,
+ options.itemFactory.createProto(
+ options.itemFactory.createType("Ljava/lang/reflect/Method;"),
+ options.itemFactory.stringType,
+ options.itemFactory.createType("[Ljava/lang/Class;")),
+ options.itemFactory.createString("getDeclaredMethod")),
+ false),
+ new CfStore(ValueType.OBJECT, 3),
+ label15,
+ new CfLoad(ValueType.OBJECT, 3),
+ new CfLoad(ValueType.OBJECT, 0),
+ new CfConstNumber(1, ValueType.INT),
+ new CfNewArray(options.itemFactory.createType("[Ljava/lang/Object;")),
+ new CfStackInstruction(CfStackInstruction.Opcode.Dup),
+ new CfConstNumber(0, ValueType.INT),
+ new CfLoad(ValueType.OBJECT, 2),
+ new CfArrayStore(MemberType.OBJECT),
+ new CfInvoke(
+ 182,
+ options.itemFactory.createMethod(
+ options.itemFactory.createType("Ljava/lang/reflect/Method;"),
+ options.itemFactory.createProto(
+ options.itemFactory.objectType,
+ options.itemFactory.objectType,
+ options.itemFactory.createType("[Ljava/lang/Object;")),
+ options.itemFactory.createString("invoke")),
+ false),
+ new CfStackInstruction(CfStackInstruction.Opcode.Pop),
+ label16,
+ new CfGoto(label18),
+ label17,
+ new CfFrame(
+ new Int2ReferenceAVLTreeMap<>(
+ new int[] {0, 1, 2},
+ new FrameType[] {
+ FrameType.initialized(options.itemFactory.throwableType),
+ FrameType.initialized(options.itemFactory.objectType),
+ FrameType.initialized(options.itemFactory.throwableType)
+ }),
+ new ArrayDeque<>(
+ Arrays.asList(
+ FrameType.initialized(
+ options.itemFactory.createType("Ljava/lang/Exception;"))))),
+ new CfStore(ValueType.OBJECT, 3),
+ label18,
+ new CfFrame(
+ new Int2ReferenceAVLTreeMap<>(
+ new int[] {0, 1, 2},
+ new FrameType[] {
+ FrameType.initialized(options.itemFactory.throwableType),
+ FrameType.initialized(options.itemFactory.objectType),
+ FrameType.initialized(options.itemFactory.throwableType)
+ }),
+ new ArrayDeque<>(Arrays.asList())),
+ new CfLoad(ValueType.OBJECT, 0),
+ new CfThrow(),
+ label19,
new CfFrame(
new Int2ReferenceAVLTreeMap<>(
new int[] {0, 1, 2},
@@ -777,19 +849,8 @@
}),
new ArrayDeque<>(Arrays.asList())),
new CfLoad(ValueType.OBJECT, 2),
- label15,
- new CfFrame(
- new Int2ReferenceAVLTreeMap<>(
- new int[] {0, 1, 2},
- new FrameType[] {
- FrameType.initialized(options.itemFactory.throwableType),
- FrameType.initialized(options.itemFactory.objectType),
- FrameType.initialized(options.itemFactory.throwableType)
- }),
- new ArrayDeque<>(
- Arrays.asList(FrameType.initialized(options.itemFactory.throwableType)))),
new CfThrow(),
- label16,
+ label20,
new CfFrame(
new Int2ReferenceAVLTreeMap<>(
new int[] {0, 1},
@@ -799,7 +860,7 @@
}),
new ArrayDeque<>(Arrays.asList())),
new CfReturnVoid(),
- label17),
+ label21),
ImmutableList.of(
new CfTryCatch(
label2,
@@ -841,7 +902,12 @@
label0,
label11,
ImmutableList.of(options.itemFactory.throwableType),
- ImmutableList.of(label12))),
+ ImmutableList.of(label12)),
+ new CfTryCatch(
+ label14,
+ label16,
+ ImmutableList.of(options.itemFactory.createType("Ljava/lang/Exception;")),
+ ImmutableList.of(label17))),
ImmutableList.of());
}
@@ -9079,6 +9145,163 @@
ImmutableList.of());
}
+ public static CfCode ThrowableMethods_addSuppressed(InternalOptions options, DexMethod method) {
+ CfLabel label0 = new CfLabel();
+ CfLabel label1 = new CfLabel();
+ CfLabel label2 = new CfLabel();
+ CfLabel label3 = new CfLabel();
+ CfLabel label4 = new CfLabel();
+ CfLabel label5 = new CfLabel();
+ return new CfCode(
+ method.holder,
+ 6,
+ 3,
+ ImmutableList.of(
+ label0,
+ new CfConstClass(options.itemFactory.throwableType),
+ new CfConstString(options.itemFactory.createString("addSuppressed")),
+ new CfConstNumber(1, ValueType.INT),
+ new CfNewArray(options.itemFactory.createType("[Ljava/lang/Class;")),
+ new CfStackInstruction(CfStackInstruction.Opcode.Dup),
+ new CfConstNumber(0, ValueType.INT),
+ new CfConstClass(options.itemFactory.throwableType),
+ new CfArrayStore(MemberType.OBJECT),
+ new CfInvoke(
+ 182,
+ options.itemFactory.createMethod(
+ options.itemFactory.classType,
+ options.itemFactory.createProto(
+ options.itemFactory.createType("Ljava/lang/reflect/Method;"),
+ options.itemFactory.stringType,
+ options.itemFactory.createType("[Ljava/lang/Class;")),
+ options.itemFactory.createString("getDeclaredMethod")),
+ false),
+ new CfStore(ValueType.OBJECT, 2),
+ label1,
+ new CfLoad(ValueType.OBJECT, 2),
+ new CfLoad(ValueType.OBJECT, 0),
+ new CfConstNumber(1, ValueType.INT),
+ new CfNewArray(options.itemFactory.createType("[Ljava/lang/Object;")),
+ new CfStackInstruction(CfStackInstruction.Opcode.Dup),
+ new CfConstNumber(0, ValueType.INT),
+ new CfLoad(ValueType.OBJECT, 1),
+ new CfArrayStore(MemberType.OBJECT),
+ new CfInvoke(
+ 182,
+ options.itemFactory.createMethod(
+ options.itemFactory.createType("Ljava/lang/reflect/Method;"),
+ options.itemFactory.createProto(
+ options.itemFactory.objectType,
+ options.itemFactory.objectType,
+ options.itemFactory.createType("[Ljava/lang/Object;")),
+ options.itemFactory.createString("invoke")),
+ false),
+ new CfStackInstruction(CfStackInstruction.Opcode.Pop),
+ label2,
+ new CfGoto(label4),
+ label3,
+ new CfFrame(
+ new Int2ReferenceAVLTreeMap<>(
+ new int[] {0, 1},
+ new FrameType[] {
+ FrameType.initialized(options.itemFactory.throwableType),
+ FrameType.initialized(options.itemFactory.throwableType)
+ }),
+ new ArrayDeque<>(
+ Arrays.asList(
+ FrameType.initialized(
+ options.itemFactory.createType("Ljava/lang/Exception;"))))),
+ new CfStore(ValueType.OBJECT, 2),
+ label4,
+ new CfFrame(
+ new Int2ReferenceAVLTreeMap<>(
+ new int[] {0, 1},
+ new FrameType[] {
+ FrameType.initialized(options.itemFactory.throwableType),
+ FrameType.initialized(options.itemFactory.throwableType)
+ }),
+ new ArrayDeque<>(Arrays.asList())),
+ new CfReturnVoid(),
+ label5),
+ ImmutableList.of(
+ new CfTryCatch(
+ label0,
+ label2,
+ ImmutableList.of(options.itemFactory.createType("Ljava/lang/Exception;")),
+ ImmutableList.of(label3))),
+ ImmutableList.of());
+ }
+
+ public static CfCode ThrowableMethods_getSuppressed(InternalOptions options, DexMethod method) {
+ CfLabel label0 = new CfLabel();
+ CfLabel label1 = new CfLabel();
+ CfLabel label2 = new CfLabel();
+ CfLabel label3 = new CfLabel();
+ CfLabel label4 = new CfLabel();
+ CfLabel label5 = new CfLabel();
+ return new CfCode(
+ method.holder,
+ 3,
+ 2,
+ ImmutableList.of(
+ label0,
+ new CfConstClass(options.itemFactory.throwableType),
+ new CfConstString(options.itemFactory.createString("getSuppressed")),
+ new CfConstNumber(0, ValueType.INT),
+ new CfNewArray(options.itemFactory.createType("[Ljava/lang/Class;")),
+ new CfInvoke(
+ 182,
+ options.itemFactory.createMethod(
+ options.itemFactory.classType,
+ options.itemFactory.createProto(
+ options.itemFactory.createType("Ljava/lang/reflect/Method;"),
+ options.itemFactory.stringType,
+ options.itemFactory.createType("[Ljava/lang/Class;")),
+ options.itemFactory.createString("getDeclaredMethod")),
+ false),
+ new CfStore(ValueType.OBJECT, 1),
+ label1,
+ new CfLoad(ValueType.OBJECT, 1),
+ new CfLoad(ValueType.OBJECT, 0),
+ new CfConstNumber(0, ValueType.INT),
+ new CfNewArray(options.itemFactory.createType("[Ljava/lang/Object;")),
+ new CfInvoke(
+ 182,
+ options.itemFactory.createMethod(
+ options.itemFactory.createType("Ljava/lang/reflect/Method;"),
+ options.itemFactory.createProto(
+ options.itemFactory.objectType,
+ options.itemFactory.objectType,
+ options.itemFactory.createType("[Ljava/lang/Object;")),
+ options.itemFactory.createString("invoke")),
+ false),
+ new CfCheckCast(options.itemFactory.createType("[Ljava/lang/Throwable;")),
+ label2,
+ new CfReturn(ValueType.OBJECT),
+ label3,
+ new CfFrame(
+ new Int2ReferenceAVLTreeMap<>(
+ new int[] {0},
+ new FrameType[] {FrameType.initialized(options.itemFactory.throwableType)}),
+ new ArrayDeque<>(
+ Arrays.asList(
+ FrameType.initialized(
+ options.itemFactory.createType("Ljava/lang/Exception;"))))),
+ new CfStore(ValueType.OBJECT, 1),
+ label4,
+ new CfConstNumber(0, ValueType.INT),
+ new CfNewArray(options.itemFactory.createType("[Ljava/lang/Throwable;")),
+ new CfReturn(ValueType.OBJECT),
+ label5),
+ ImmutableList.of(
+ new CfTryCatch(
+ label0,
+ label2,
+ ImmutableList.of(options.itemFactory.createType("Ljava/lang/Exception;")),
+ ImmutableList.of(label3))),
+ ImmutableList.of());
+ }
+
public static CfCode UnsafeMethods_compareAndSwapObject(
InternalOptions options, DexMethod method) {
CfLabel label0 = new CfLabel();
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/twr/TwrInstructionDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/twr/TwrInstructionDesugaring.java
index 716b6e2..1b5b27c 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/twr/TwrInstructionDesugaring.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/twr/TwrInstructionDesugaring.java
@@ -3,15 +3,12 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.ir.desugar.twr;
-import com.android.tools.r8.cf.code.CfConstNumber;
import com.android.tools.r8.cf.code.CfInstruction;
import com.android.tools.r8.cf.code.CfInvoke;
-import com.android.tools.r8.cf.code.CfNewArray;
-import com.android.tools.r8.cf.code.CfStackInstruction;
-import com.android.tools.r8.cf.code.CfStackInstruction.Opcode;
import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
import com.android.tools.r8.errors.CompilationError;
import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.CfCode;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexMethod;
@@ -19,7 +16,6 @@
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.MethodAccessFlags;
import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.ir.code.ValueType;
import com.android.tools.r8.ir.desugar.CfInstructionDesugaring;
import com.android.tools.r8.ir.desugar.CfInstructionDesugaringCollection;
import com.android.tools.r8.ir.desugar.CfInstructionDesugaringEventConsumer;
@@ -27,9 +23,11 @@
import com.android.tools.r8.ir.desugar.LocalStackAllocator;
import com.android.tools.r8.ir.desugar.backports.BackportedMethods;
import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
+import com.android.tools.r8.utils.InternalOptions;
import com.google.common.collect.ImmutableList;
import java.util.Collection;
-import org.jetbrains.annotations.NotNull;
+import java.util.function.BiConsumer;
+import java.util.function.BiFunction;
import org.objectweb.asm.Opcodes;
public class TwrInstructionDesugaring implements CfInstructionDesugaring {
@@ -66,58 +64,85 @@
if (isTwrCloseResourceInvoke(instruction)) {
return rewriteTwrCloseResourceInvoke(eventConsumer, context, methodProcessingContext);
}
- if (isTwrSuppressedInvoke(instruction, addSuppressed)) {
- return rewriteTwrAddSuppressedInvoke();
- }
- if (isTwrSuppressedInvoke(instruction, getSuppressed)) {
- return rewriteTwrGetSuppressedInvoke();
+ if (!appView.options().canUseSuppressedExceptions()) {
+ if (isTwrSuppressedInvoke(instruction, addSuppressed)) {
+ return rewriteTwrAddSuppressedInvoke(eventConsumer, methodProcessingContext);
+ }
+ if (isTwrSuppressedInvoke(instruction, getSuppressed)) {
+ return rewriteTwrGetSuppressedInvoke(eventConsumer, methodProcessingContext);
+ }
}
return null;
}
- private Collection<CfInstruction> rewriteTwrAddSuppressedInvoke() {
- // Remove Throwable::addSuppressed(Throwable) call.
- return ImmutableList.of(new CfStackInstruction(Opcode.Pop), new CfStackInstruction(Opcode.Pop));
+ private Collection<CfInstruction> rewriteTwrAddSuppressedInvoke(
+ CfInstructionDesugaringEventConsumer eventConsumer,
+ MethodProcessingContext methodProcessingContext) {
+ DexItemFactory factory = appView.dexItemFactory();
+ DexProto proto =
+ factory.createProto(factory.voidType, factory.throwableType, factory.throwableType);
+ return createAndCallSyntheticMethod(
+ SyntheticKind.BACKPORT,
+ proto,
+ BackportedMethods::ThrowableMethods_addSuppressed,
+ methodProcessingContext,
+ eventConsumer::acceptBackportedMethod,
+ methodProcessingContext.getMethodContext());
}
- private Collection<CfInstruction> rewriteTwrGetSuppressedInvoke() {
- // Replace call to Throwable::getSuppressed() with new Throwable[0].
- return ImmutableList.of(
- new CfStackInstruction(Opcode.Pop),
- new CfConstNumber(0, ValueType.INT),
- new CfNewArray(dexItemFactory.createArrayType(1, dexItemFactory.throwableType)));
+ private Collection<CfInstruction> rewriteTwrGetSuppressedInvoke(
+ CfInstructionDesugaringEventConsumer eventConsumer,
+ MethodProcessingContext methodProcessingContext) {
+ DexItemFactory factory = appView.dexItemFactory();
+ DexProto proto =
+ factory.createProto(
+ factory.createArrayType(1, factory.throwableType), factory.throwableType);
+ return createAndCallSyntheticMethod(
+ SyntheticKind.BACKPORT,
+ proto,
+ BackportedMethods::ThrowableMethods_getSuppressed,
+ methodProcessingContext,
+ eventConsumer::acceptBackportedMethod,
+ methodProcessingContext.getMethodContext());
}
- @NotNull
private ImmutableList<CfInstruction> rewriteTwrCloseResourceInvoke(
CfInstructionDesugaringEventConsumer eventConsumer,
ProgramMethod context,
MethodProcessingContext methodProcessingContext) {
// Synthesize a new method.
- ProgramMethod closeMethod = createSyntheticCloseResourceMethod(methodProcessingContext);
- eventConsumer.acceptTwrCloseResourceMethod(closeMethod, context);
- // Rewrite the invoke to the new synthetic.
- return ImmutableList.of(new CfInvoke(Opcodes.INVOKESTATIC, closeMethod.getReference(), false));
+ return createAndCallSyntheticMethod(
+ SyntheticKind.TWR_CLOSE_RESOURCE,
+ twrCloseResourceProto,
+ BackportedMethods::CloseResourceMethod_closeResourceImpl,
+ methodProcessingContext,
+ eventConsumer::acceptTwrCloseResourceMethod,
+ context);
}
- private ProgramMethod createSyntheticCloseResourceMethod(
- MethodProcessingContext methodProcessingContext) {
- return appView
- .getSyntheticItems()
- .createMethod(
- SyntheticKind.TWR_CLOSE_RESOURCE,
- methodProcessingContext.createUniqueContext(),
- appView,
- methodBuilder ->
- methodBuilder
- .setAccessFlags(MethodAccessFlags.createPublicStaticSynthetic())
- .setProto(twrCloseResourceProto)
- // Will be traced by the enqueuer.
- .disableAndroidApiLevelCheck()
- .setCode(
- m ->
- BackportedMethods.CloseResourceMethod_closeResourceImpl(
- appView.options(), m)));
+ private ImmutableList<CfInstruction> createAndCallSyntheticMethod(
+ SyntheticKind kind,
+ DexProto proto,
+ BiFunction<InternalOptions, DexMethod, CfCode> generator,
+ MethodProcessingContext methodProcessingContext,
+ BiConsumer<ProgramMethod, ProgramMethod> eventConsumerCallback,
+ ProgramMethod context) {
+ ProgramMethod method =
+ appView
+ .getSyntheticItems()
+ .createMethod(
+ kind,
+ methodProcessingContext.createUniqueContext(),
+ appView,
+ builder ->
+ builder
+ // Will be traced by the enqueuer.
+ .disableAndroidApiLevelCheck()
+ .setProto(proto)
+ .setAccessFlags(MethodAccessFlags.createPublicStaticSynthetic())
+ .setCode(methodSig -> generator.apply(appView.options(), methodSig)));
+ eventConsumerCallback.accept(method, context);
+ return ImmutableList.of(new CfInvoke(Opcodes.INVOKESTATIC, method.getReference(), false));
}
@Override
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index 1541441..f3dcf92 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -1920,6 +1920,7 @@
}
public boolean canUseSuppressedExceptions() {
+ // TODO(b/214239152): Suppressed exceptions are @hide from at least 4.0.1 / Android I / API 14.
return !isDesugaring() || hasMinApi(AndroidApiLevel.K);
}
diff --git a/src/test/examplesAndroidO/trywithresources/TryWithResources.java b/src/test/examplesAndroidO/trywithresources/TryWithResources.java
index ac779d6..2c407cf 100644
--- a/src/test/examplesAndroidO/trywithresources/TryWithResources.java
+++ b/src/test/examplesAndroidO/trywithresources/TryWithResources.java
@@ -36,20 +36,12 @@
dumpException(cause, indent + " cause: ");
}
- // Dump suppressed UNLESS it is a desugared code running
- // on JVM, in which case we avoid dumping suppressed, since
- // the output will be used for comparison with desugared code
- // running on device.
- if (!desugaredCodeRunningOnJvm()) {
- Throwable[] suppressed = e.getSuppressed();
- for (int i = 0; i < suppressed.length; i++) {
- dumpException(suppressed[i], indent + "supp[" + i + "]: ");
- }
+ Throwable[] suppressed = e.getSuppressed();
+ for (int i = 0; i < suppressed.length; i++) {
+ dumpException(suppressed[i], indent + "supp[" + i + "]: ");
}
}
- abstract boolean desugaredCodeRunningOnJvm();
-
// --- TEST SYMBOLS ---
static class Resource implements Closeable {
@@ -190,9 +182,7 @@
packer.act(new RuntimeException("original exception Z"));
for (Throwable unpacked : unpacker.get()) {
- if (!desugaredCodeRunningOnJvm()) {
- dumpException(unpacked);
- }
+ dumpException(unpacked);
}
}
}
diff --git a/src/test/examplesAndroidO/trywithresources/TryWithResourcesDesugaredTests.java b/src/test/examplesAndroidO/trywithresources/TryWithResourcesDesugaredTests.java
index 10a96f9..65e7221 100644
--- a/src/test/examplesAndroidO/trywithresources/TryWithResourcesDesugaredTests.java
+++ b/src/test/examplesAndroidO/trywithresources/TryWithResourcesDesugaredTests.java
@@ -4,19 +4,6 @@
package trywithresources;
public class TryWithResourcesDesugaredTests extends TryWithResources {
- private boolean isAndroid() {
- try {
- Class.forName("dalvik.system.VMRuntime");
- return true;
- } catch (Exception ignored) {
- }
- return false;
- }
-
- @Override
- boolean desugaredCodeRunningOnJvm() {
- return !isAndroid();
- }
public static void main(String[] args) throws Exception {
new TryWithResourcesDesugaredTests().test();
diff --git a/src/test/examplesAndroidO/trywithresources/TryWithResourcesNotDesugaredTests.java b/src/test/examplesAndroidO/trywithresources/TryWithResourcesNotDesugaredTests.java
index d2a05c3..8180fb1 100644
--- a/src/test/examplesAndroidO/trywithresources/TryWithResourcesNotDesugaredTests.java
+++ b/src/test/examplesAndroidO/trywithresources/TryWithResourcesNotDesugaredTests.java
@@ -4,10 +4,6 @@
package trywithresources;
public class TryWithResourcesNotDesugaredTests extends TryWithResources {
- @Override
- boolean desugaredCodeRunningOnJvm() {
- return false;
- }
public static void main(String[] args) throws Exception {
new TryWithResourcesNotDesugaredTests().test();
diff --git a/src/test/java/com/android/tools/r8/D8IncrementalRunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/D8IncrementalRunExamplesAndroidOTest.java
index dfebfd7..6006f32 100644
--- a/src/test/java/com/android/tools/r8/D8IncrementalRunExamplesAndroidOTest.java
+++ b/src/test/java/com/android/tools/r8/D8IncrementalRunExamplesAndroidOTest.java
@@ -99,6 +99,7 @@
Assert.assertTrue(
descriptor.endsWith(getCompanionClassNameSuffix() + ";")
|| SyntheticItemsTestUtils.isExternalTwrCloseMethod(reference)
+ || SyntheticItemsTestUtils.isMaybeExternalSuppressedExceptionMethod(reference)
|| SyntheticItemsTestUtils.isExternalLambda(reference)
|| SyntheticItemsTestUtils.isExternalStaticInterfaceCall(reference)
|| descriptor.equals(mainClassDescriptor));
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index 21113a9..d72151f 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -1788,6 +1788,10 @@
return AndroidApiLevel.K;
}
+ public static AndroidApiLevel apiLevelWithSuppressedExceptionsSupport() {
+ return AndroidApiLevel.K;
+ }
+
public static AndroidApiLevel apiLevelWithPcAsLineNumberSupport() {
return AndroidApiLevel.O;
}
diff --git a/src/test/java/com/android/tools/r8/desugar/suppressedexceptions/SuppressedExceptionsTest.java b/src/test/java/com/android/tools/r8/desugar/suppressedexceptions/SuppressedExceptionsTest.java
new file mode 100644
index 0000000..e1d397a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/suppressedexceptions/SuppressedExceptionsTest.java
@@ -0,0 +1,143 @@
+// Copyright (c) 2022, 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.suppressedexceptions;
+
+import static com.android.tools.r8.desugar.suppressedexceptions.TwrSuppressedExceptionsTest.getInvokesTo;
+import static com.android.tools.r8.desugar.suppressedexceptions.TwrSuppressedExceptionsTest.hasInvokesTo;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.DesugarTestConfiguration;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.IntBox;
+import com.android.tools.r8.utils.StringUtils;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class SuppressedExceptionsTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
+ }
+
+ public SuppressedExceptionsTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ public boolean runtimeHasSuppressedExceptionsSupport() {
+ // TODO(b/214239152): Update this if desugaring is changed.
+ // Despite 4.0.4 being API level 15 and add suppressed being officially added in 19 it is
+ // actually implemented. Thus, the backport implementation will use the functionality and run
+ // as expected by RI.
+ return parameters.isCfRuntime()
+ || parameters.getDexRuntimeVersion().isNewerThanOrEqual(Version.V4_0_4);
+ }
+
+ public boolean apiLevelHasSuppressedExceptionsSupport() {
+ return parameters
+ .getApiLevel()
+ .isGreaterThanOrEqualTo(apiLevelWithSuppressedExceptionsSupport());
+ }
+
+ @Test
+ public void testD8() throws Exception {
+ testForDesugaring(parameters)
+ .addProgramClasses(TestClass.class)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(
+ runtimeHasSuppressedExceptionsSupport() ? StringUtils.lines("FOO") : "NONE")
+ .inspectIf(
+ DesugarTestConfiguration::isDesugared,
+ inspector ->
+ hasInvokesTo(
+ inspector.clazz(TestClass.class).uniqueMethodWithName("main"),
+ "getSuppressed",
+ apiLevelHasSuppressedExceptionsSupport() ? 1 : 0))
+ .inspectIf(
+ DesugarTestConfiguration::isNotDesugared,
+ inspector ->
+ hasInvokesTo(
+ inspector.clazz(TestClass.class).uniqueMethodWithName("main"),
+ "getSuppressed",
+ 1));
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ assumeTrue(
+ "R8 does not desugar CF so only run the high API variant.",
+ parameters.isDexRuntime() || parameters.getApiLevel().isGreaterThan(AndroidApiLevel.B));
+ testForR8(parameters.getBackend())
+ .addInnerClasses(SuppressedExceptionsTest.class)
+ .setMinApi(parameters.getApiLevel())
+ .addKeepMainRule(TestClass.class)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(
+ runtimeHasSuppressedExceptionsSupport() ? StringUtils.lines("FOO") : "NONE")
+ .inspect(
+ inspector -> {
+ hasInvokesTo(
+ inspector.clazz(TestClass.class).uniqueMethodWithName("main"),
+ "getSuppressed",
+ apiLevelHasSuppressedExceptionsSupport() ? 1 : 0);
+ IntBox gets = new IntBox(0);
+ IntBox adds = new IntBox(0);
+ inspector.forAllClasses(
+ c ->
+ c.forAllMethods(
+ m -> {
+ gets.increment(getInvokesTo(m, "getSuppressed").size());
+ adds.increment(getInvokesTo(m, "addSuppressed").size());
+ }));
+ if (apiLevelHasSuppressedExceptionsSupport()) {
+ assertEquals(1, gets.get());
+ assertEquals(1, adds.get());
+ } else {
+ assertEquals(0, gets.get());
+ assertEquals(0, adds.get());
+ }
+ });
+ }
+
+ static class TestClass {
+
+ public static void foo() {
+ throw new RuntimeException("FOO");
+ }
+
+ public static void bar() {
+ try {
+ foo();
+ } catch (RuntimeException e) {
+ RuntimeException bar = new RuntimeException("BAR");
+ bar.addSuppressed(e);
+ throw bar;
+ }
+ }
+
+ public static void main(String[] args) {
+ try {
+ bar();
+ } catch (RuntimeException e) {
+ Throwable[] suppressed = e.getSuppressed();
+ if (suppressed.length == 0) {
+ System.out.println("NONE");
+ } else {
+ for (Throwable throwable : suppressed) {
+ System.out.println(throwable.getMessage());
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/suppressedexceptions/TwrSuppressedExceptionsTest.java b/src/test/java/com/android/tools/r8/desugar/suppressedexceptions/TwrSuppressedExceptionsTest.java
new file mode 100644
index 0000000..e80af63
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/suppressedexceptions/TwrSuppressedExceptionsTest.java
@@ -0,0 +1,188 @@
+// Copyright (c) 2022, 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.suppressedexceptions;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.DesugarTestConfiguration;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.IntBox;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.io.Closeable;
+import java.util.List;
+import java.util.stream.Collectors;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class TwrSuppressedExceptionsTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
+ }
+
+ public TwrSuppressedExceptionsTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ public boolean runtimeHasSuppressedExceptionsSupport() {
+ // TODO(b/214239152): Update this if desugaring is changed.
+ // Despite 4.0.4 being API level 15 and add suppressed being officially added in 19 it is
+ // actually implemented. Thus, the backport implementation will use the functionality and run
+ // as expected by RI.
+ return parameters.isCfRuntime()
+ || parameters.getDexRuntimeVersion().isNewerThanOrEqual(Version.V4_0_4);
+ }
+
+ public boolean apiLevelHasSuppressedExceptionsSupport() {
+ return parameters
+ .getApiLevel()
+ .isGreaterThanOrEqualTo(apiLevelWithSuppressedExceptionsSupport());
+ }
+
+ public boolean apiLevelHasTwrCloseResourceSupport() {
+ return parameters.getApiLevel().isGreaterThanOrEqualTo(apiLevelWithTwrCloseResourceSupport());
+ }
+
+ @Test
+ public void testD8() throws Exception {
+ testForDesugaring(parameters)
+ .addProgramClasses(TestClass.class, MyClosable.class)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(
+ runtimeHasSuppressedExceptionsSupport() ? StringUtils.lines("CLOSE") : "NONE")
+ .inspectIf(
+ DesugarTestConfiguration::isDesugared,
+ inspector -> {
+ ClassSubject clazz = inspector.clazz(TestClass.class);
+ hasInvokesTo(
+ clazz.uniqueMethodWithName("bar"),
+ "$closeResource",
+ apiLevelHasTwrCloseResourceSupport() ? 4 : 0);
+ if (apiLevelHasSuppressedExceptionsSupport()) {
+ hasInvokesTo(clazz.mainMethod(), "getSuppressed", 1);
+ } else {
+ inspector.forAllClasses(
+ c ->
+ c.forAllMethods(
+ m -> {
+ hasInvokesTo(m, "getSuppressed", 0);
+ hasInvokesTo(m, "addSuppressed", 0);
+ }));
+ }
+ })
+ .inspectIf(
+ DesugarTestConfiguration::isNotDesugared,
+ inspector -> {
+ ClassSubject clazz = inspector.clazz(TestClass.class);
+ hasInvokesTo(clazz.uniqueMethodWithName("bar"), "$closeResource", 4);
+ hasInvokesTo(clazz.mainMethod(), "getSuppressed", 1);
+ });
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ assumeTrue(
+ "R8 does not desugar CF so only run the high API variant.",
+ parameters.isDexRuntime() || parameters.getApiLevel().isGreaterThan(AndroidApiLevel.B));
+ testForR8(parameters.getBackend())
+ .addInnerClasses(TwrSuppressedExceptionsTest.class)
+ .setMinApi(parameters.getApiLevel())
+ .addKeepMainRule(TestClass.class)
+ // TODO(b/214250388): Don't warn about AutoClosable in synthesized code.
+ .apply(
+ b -> {
+ if (!parameters.isCfRuntime() && !apiLevelHasTwrCloseResourceSupport()) {
+ b.addDontWarn(AutoCloseable.class);
+ }
+ })
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(
+ runtimeHasSuppressedExceptionsSupport() ? StringUtils.lines("CLOSE") : "NONE")
+ .inspect(
+ inspector -> {
+ IntBox gets = new IntBox(0);
+ IntBox adds = new IntBox(0);
+ inspector.forAllClasses(
+ c ->
+ c.forAllMethods(
+ m -> {
+ gets.increment(getInvokesTo(m, "getSuppressed").size());
+ adds.increment(getInvokesTo(m, "addSuppressed").size());
+ }));
+ if (apiLevelHasSuppressedExceptionsSupport()) {
+ hasInvokesTo(inspector.clazz(TestClass.class).mainMethod(), "getSuppressed", 1);
+ assertEquals(1, gets.get());
+ assertEquals(1, adds.get());
+ } else {
+ assertEquals(0, gets.get());
+ assertEquals(0, adds.get());
+ }
+ });
+ }
+
+ public static void hasInvokesTo(MethodSubject method, String callee, int count) {
+ List<InstructionSubject> getSuppressedCalls = getInvokesTo(method, callee);
+ assertEquals(count, getSuppressedCalls.size());
+ }
+
+ public static List<InstructionSubject> getInvokesTo(MethodSubject method, String callee) {
+ return method
+ .streamInstructions()
+ .filter(i -> i.isInvoke() && i.getMethod().getName().toString().equals(callee))
+ .collect(Collectors.toList());
+ }
+
+ static class MyClosable implements Closeable {
+
+ @Override
+ public void close() {
+ throw new RuntimeException("CLOSE");
+ }
+ }
+
+ static class TestClass {
+
+ public static void foo() {
+ throw new RuntimeException("FOO");
+ }
+
+ public static void bar() {
+ // Use twr twice to have javac generate a shared $closeResource helper.
+ try (MyClosable closable = new MyClosable()) {
+ foo();
+ }
+ try (MyClosable closable = new MyClosable()) {
+ foo();
+ }
+ }
+
+ public static void main(String[] args) {
+ try {
+ bar();
+ } catch (Exception e) {
+ Throwable[] suppressed = e.getSuppressed();
+ if (suppressed.length == 0) {
+ System.out.println("NONE");
+ } else {
+ for (Throwable throwable : suppressed) {
+ System.out.println(throwable.getMessage());
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/twr/TwrCloseResourceDuplicationTest.java b/src/test/java/com/android/tools/r8/desugar/twr/TwrCloseResourceDuplicationTest.java
index 1f15b61..10d15e9 100644
--- a/src/test/java/com/android/tools/r8/desugar/twr/TwrCloseResourceDuplicationTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/twr/TwrCloseResourceDuplicationTest.java
@@ -75,10 +75,14 @@
.assertSuccessWithOutput(EXPECTED)
.inspect(
inspector -> {
- // There should be exactly one synthetic class besides the three program classes.
+ // There should be two synthetic classes besides the three program classes.
+ // One for the desugar version of TWR $closeResource and one for the
+ // Throwable.addSuppressed that is still present in the original $closeResource.
+ // TODO(b/214329923): If the original $closeResource is pruned this will decrease.
+ // TODO(b/168568827): Once we support a nested addSuppressed this will increase.
int expectedSynthetics =
parameters.getApiLevel().isLessThan(apiLevelWithTwrCloseResourceSupport())
- ? 1
+ ? 2
: 0;
assertEquals(INPUT_CLASSES + expectedSynthetics, inspector.allClasses().size());
});
@@ -91,6 +95,7 @@
.addInnerClasses(getClass())
.addKeepMainRule(TestClass.class)
.addKeepClassAndMembersRules(Foo.class, Bar.class)
+ // TODO(b/214250388): Don't warn about synthetic code.
.applyIf(
parameters.getApiLevel().isLessThan(apiLevelWithTwrCloseResourceSupport()),
builder -> builder.addDontWarn("java.lang.AutoCloseable"))
diff --git a/src/test/java/com/android/tools/r8/ir/desugar/backports/CloseResourceMethod.java b/src/test/java/com/android/tools/r8/ir/desugar/backports/CloseResourceMethod.java
index 1167e5a..fb6a937 100644
--- a/src/test/java/com/android/tools/r8/ir/desugar/backports/CloseResourceMethod.java
+++ b/src/test/java/com/android/tools/r8/ir/desugar/backports/CloseResourceMethod.java
@@ -49,9 +49,18 @@
}
}
} catch (Throwable e) {
- // NOTE: we don't call addSuppressed(...) since the call will be removed
- // by try-with-resource desugar anyways.
- throw throwable != null ? throwable : e;
+ if (throwable != null) {
+ // TODO(b/168568827): Directly call Throwable.addSuppressed once fixed.
+ try {
+ Method method = Throwable.class.getDeclaredMethod("addSuppressed", Throwable.class);
+ method.invoke(throwable, e);
+ } catch (Exception ignore) {
+ // Don't add anything when not natively supported.
+ }
+ throw throwable;
+ } else {
+ throw e;
+ }
}
}
}
diff --git a/src/test/java/com/android/tools/r8/ir/desugar/backports/GenerateBackportMethods.java b/src/test/java/com/android/tools/r8/ir/desugar/backports/GenerateBackportMethods.java
index ce75bd9..f955ca3 100644
--- a/src/test/java/com/android/tools/r8/ir/desugar/backports/GenerateBackportMethods.java
+++ b/src/test/java/com/android/tools/r8/ir/desugar/backports/GenerateBackportMethods.java
@@ -51,6 +51,7 @@
ShortMethods.class,
StreamMethods.class,
StringMethods.class,
+ ThrowableMethods.class,
UnsafeMethods.class);
protected final TestParameters parameters;
diff --git a/src/test/java/com/android/tools/r8/ir/desugar/backports/ThrowableMethods.java b/src/test/java/com/android/tools/r8/ir/desugar/backports/ThrowableMethods.java
new file mode 100644
index 0000000..ba17959
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/desugar/backports/ThrowableMethods.java
@@ -0,0 +1,29 @@
+// Copyright (c) 2022, 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.ir.desugar.backports;
+
+import java.lang.reflect.Method;
+
+public final class ThrowableMethods {
+
+ public static void addSuppressed(Throwable receiver, Throwable suppressed) {
+ try {
+ Method method = Throwable.class.getDeclaredMethod("addSuppressed", Throwable.class);
+ method.invoke(receiver, suppressed);
+ } catch (Exception e) {
+ // Don't add anything when not natively supported.
+ }
+ }
+
+ public static Throwable[] getSuppressed(Throwable receiver) {
+ try {
+ Method method = Throwable.class.getDeclaredMethod("getSuppressed");
+ return (Throwable[]) method.invoke(receiver);
+ } catch (Exception e) {
+ // Don't return any when not natively supported.
+ return new Throwable[0];
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/synthesis/SyntheticItemsTestUtils.java b/src/test/java/com/android/tools/r8/synthesis/SyntheticItemsTestUtils.java
index 6264a4b..bb46834 100644
--- a/src/test/java/com/android/tools/r8/synthesis/SyntheticItemsTestUtils.java
+++ b/src/test/java/com/android/tools/r8/synthesis/SyntheticItemsTestUtils.java
@@ -109,6 +109,11 @@
return SyntheticNaming.isSynthetic(reference, Phase.EXTERNAL, SyntheticKind.TWR_CLOSE_RESOURCE);
}
+ public static boolean isMaybeExternalSuppressedExceptionMethod(ClassReference reference) {
+ // The suppressed exception methods are grouped with the backports.
+ return SyntheticNaming.isSynthetic(reference, Phase.EXTERNAL, SyntheticKind.BACKPORT);
+ }
+
public static boolean isExternalOutlineClass(ClassReference reference) {
return SyntheticNaming.isSynthetic(reference, Phase.EXTERNAL, SyntheticKind.OUTLINE);
}