Hygienic desugaring of try-with-resource $closeResource.
Bug: 158159959
Change-Id: I0153bad42811d5ba2b051a725fa00bf6d95e5e60
diff --git a/src/main/java/com/android/tools/r8/graph/DexType.java b/src/main/java/com/android/tools/r8/graph/DexType.java
index cab5904..c4daac1 100644
--- a/src/main/java/com/android/tools/r8/graph/DexType.java
+++ b/src/main/java/com/android/tools/r8/graph/DexType.java
@@ -17,7 +17,6 @@
import com.android.tools.r8.horizontalclassmerging.SyntheticArgumentClass;
import com.android.tools.r8.ir.desugar.DesugaredLibraryRetargeter;
import com.android.tools.r8.ir.desugar.NestBasedAccessDesugaring;
-import com.android.tools.r8.ir.desugar.TwrCloseResourceRewriter;
import com.android.tools.r8.ir.optimize.ServiceLoaderRewriter;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.synthesis.SyntheticNaming;
@@ -41,7 +40,11 @@
// Bundletool is merging classes that may originate from a build with an old version of R8.
// Allow merging of classes that use names from older versions of R8.
private static List<String> OLD_SYNTHESIZED_NAMES =
- ImmutableList.of("$r8$backportedMethods$utility", "$r8$java8methods$utility", "-$$Lambda$");
+ ImmutableList.of(
+ "$r8$backportedMethods$utility",
+ "$r8$java8methods$utility",
+ "$r8$twr$utility",
+ "-$$Lambda$");
public final DexString descriptor;
private String toStringCache = null;
@@ -333,7 +336,6 @@
|| name.contains(LAMBDA_GROUP_CLASS_NAME_PREFIX) // Could collide.
|| name.contains(DISPATCH_CLASS_NAME_SUFFIX) // Shared on reference.
|| name.contains(OutlineOptions.CLASS_NAME) // Global singleton.
- || name.contains(TwrCloseResourceRewriter.UTILITY_CLASS_NAME) // Global singleton.
|| name.contains(NestBasedAccessDesugaring.NEST_CONSTRUCTOR_NAME) // Global singleton.
|| name.contains(ServiceLoaderRewriter.SERVICE_LOADER_CLASS_NAME); // Global singleton.
}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
index bc20e17..d88d48e 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
@@ -240,7 +240,9 @@
new DesugaredLibraryAPIConverter(appView, Mode.GENERATE_CALLBACKS_AND_WRAPPERS);
this.backportedMethodRewriter = new BackportedMethodRewriter(appView);
this.twrCloseResourceRewriter =
- enableTwrCloseResourceDesugaring() ? new TwrCloseResourceRewriter(appView, this) : null;
+ TwrCloseResourceRewriter.enableTwrCloseResourceDesugaring(appView.options())
+ ? new TwrCloseResourceRewriter(appView)
+ : null;
this.d8NestBasedAccessDesugaring =
options.shouldDesugarNests() ? new D8NestBasedAccessDesugaring(appView) : null;
this.lambdaMerger = null;
@@ -274,8 +276,9 @@
? new InterfaceMethodRewriter(appView, this)
: null;
this.twrCloseResourceRewriter =
- (options.desugarState == DesugarState.ON && enableTwrCloseResourceDesugaring())
- ? new TwrCloseResourceRewriter(appView, this)
+ (TwrCloseResourceRewriter.enableTwrCloseResourceDesugaring(options)
+ && !appView.enableWholeProgramOptimizations())
+ ? new TwrCloseResourceRewriter(appView)
: null;
this.backportedMethodRewriter =
(options.desugarState == DesugarState.ON && !appView.enableWholeProgramOptimizations())
@@ -382,20 +385,6 @@
this(AppView.createForD8(appInfo), timing, printer, MainDexTracingResult.NONE);
}
- private boolean enableTwrCloseResourceDesugaring() {
- return enableTryWithResourcesDesugaring() && !options.canUseTwrCloseResourceMethod();
- }
-
- private boolean enableTryWithResourcesDesugaring() {
- switch (options.tryWithResourcesDesugaring) {
- case Off:
- return false;
- case Auto:
- return !options.canUseSuppressedExceptions();
- }
- throw new Unreachable();
- }
-
private void removeLambdaDeserializationMethods() {
if (lambdaRewriter != null) {
lambdaRewriter.removeLambdaDeserializationMethods(appView.appInfo().classes());
@@ -441,11 +430,9 @@
}
}
- private void synthesizeTwrCloseResourceUtilityClass(
- Builder<?> builder, ExecutorService executorService)
- throws ExecutionException {
+ private void processTwrCloseResourceUtilityMethods() {
if (twrCloseResourceRewriter != null) {
- twrCloseResourceRewriter.synthesizeUtilityClass(builder, executorService, options);
+ twrCloseResourceRewriter.processSynthesizedMethods(this);
}
}
@@ -525,6 +512,7 @@
// Synthesize lambda classes and commit to the app in full.
synthesizeLambdaClasses(executor);
+ processTwrCloseResourceUtilityMethods();
if (appView.getSyntheticItems().hasPendingSyntheticClasses()) {
appView.setAppInfo(
new AppInfo(
@@ -536,7 +524,6 @@
}
desugarInterfaceMethods(builder, ExcludeDexResources, executor);
- synthesizeTwrCloseResourceUtilityClass(builder, executor);
processSynthesizedJava8UtilityClasses(executor);
synthesizeRetargetClass(builder, executor);
synthesizeInvokeSpecialBridges(executor);
@@ -823,7 +810,6 @@
feedback.updateVisibleOptimizationInfo();
printPhase("Utility classes synthesis");
- synthesizeTwrCloseResourceUtilityClass(builder, executorService);
processSynthesizedJava8UtilityClasses(executorService);
synthesizeRetargetClass(builder, executorService);
synthesizeEnumUnboxingUtilityMethods(executorService);
@@ -1495,7 +1481,7 @@
deadCodeRemover.run(code, timing);
assert code.isConsistentSSA();
- if (options.desugarState == DesugarState.ON && enableTryWithResourcesDesugaring()) {
+ if (options.desugarState == DesugarState.ON && options.enableTryWithResourcesDesugaring()) {
timing.begin("Rewrite Throwable suppresed methods");
codeRewriter.rewriteThrowableAddAndGetSuppressed(code);
timing.end();
@@ -1582,7 +1568,7 @@
if (twrCloseResourceRewriter != null) {
timing.begin("Rewrite TWR close");
- twrCloseResourceRewriter.rewriteMethodCode(code);
+ twrCloseResourceRewriter.rewriteIR(code);
timing.end();
}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/NeedsIRDesugarUseRegistry.java b/src/main/java/com/android/tools/r8/ir/conversion/NeedsIRDesugarUseRegistry.java
index f6ad5f3..a9bbb90 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/NeedsIRDesugarUseRegistry.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/NeedsIRDesugarUseRegistry.java
@@ -67,6 +67,13 @@
registerDesugaredLibraryAPIConverter(method);
}
+ private void registerTwrCloseResourceRewriting(DexMethod method) {
+ if (!needsDesugarging) {
+ needsDesugarging =
+ TwrCloseResourceRewriter.isTwrCloseResourceMethod(method, appView.dexItemFactory());
+ }
+ }
+
private void registerBackportedMethodRewriting(DexMethod method) {
if (!needsDesugarging) {
needsDesugarging = backportedMethodRewriter.needsDesugaring(method);
@@ -98,9 +105,7 @@
@Override
public void registerInvokeStatic(DexMethod method) {
- if (!needsDesugarging) {
- needsDesugarging = TwrCloseResourceRewriter.isSynthesizedCloseResourceMethod(method, appView);
- }
+ registerTwrCloseResourceRewriting(method);
registerBackportedMethodRewriting(method);
registerLibraryRetargeting(method, false);
registerInterfaceMethodRewriting(method);
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/TwrCloseResourceRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/TwrCloseResourceRewriter.java
index d41fd32..bf7a3ea 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/TwrCloseResourceRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/TwrCloseResourceRewriter.java
@@ -4,178 +4,141 @@
package com.android.tools.r8.ir.desugar;
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.cf.code.CfInvoke;
import com.android.tools.r8.dex.Constants;
-import com.android.tools.r8.graph.AppInfo;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.CfCode;
-import com.android.tools.r8.graph.ClassAccessFlags;
-import com.android.tools.r8.graph.DexAnnotationSet;
-import com.android.tools.r8.graph.DexApplication.Builder;
-import com.android.tools.r8.graph.DexEncodedField;
-import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexProto;
-import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.DexTypeList;
-import com.android.tools.r8.graph.GenericSignature.ClassSignature;
-import com.android.tools.r8.graph.GenericSignature.MethodTypeSignature;
import com.android.tools.r8.graph.MethodAccessFlags;
-import com.android.tools.r8.graph.ParameterAnnotationsList;
+import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.ir.code.IRCode;
-import com.android.tools.r8.ir.code.Instruction;
import com.android.tools.r8.ir.code.InstructionListIterator;
import com.android.tools.r8.ir.code.InvokeStatic;
import com.android.tools.r8.ir.conversion.IRConverter;
import com.android.tools.r8.ir.desugar.backports.BackportedMethods;
-import com.android.tools.r8.origin.SynthesizedOrigin;
-import com.android.tools.r8.shaking.MainDexClasses;
+import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
import com.android.tools.r8.utils.InternalOptions;
-import com.google.common.collect.Sets;
-import java.util.Collections;
-import java.util.Set;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.ExecutorService;
+import com.android.tools.r8.utils.InternalOptions.DesugarState;
+import com.google.common.base.Suppliers;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+import org.objectweb.asm.Opcodes;
-// Try with resources outlining processor. Handles $closeResource methods
-// synthesized by java 9 compiler.
+// Try with resources close-resource desugaring.
//
-// Works in two phases:
-// a. during method code processing finds all references to $closeResource(...) synthesized
-// by java compiler, and replaces them with references to a special utility class method.
-// b. after all methods are processed and if there was at least one method referencing
-// $closeResource(...), it synthesizes utility class with appropriate methods.
+// Rewrites $closeResource methods synthesized by java compilers to work on older DEX runtimes.
+// All invocations to $closeResource methods are rewritten to target a compiler generated version
+// that is correct on older DEX runtimes where not all types implement AutoClosable as expected.
//
// Note that we don't remove $closeResource(...) synthesized by java compiler, relying on
// tree shaking to remove them since now they should not be referenced.
-//
+// TODO(b/177401708): D8 does not tree shake so we should remove the now unused method.
public final class TwrCloseResourceRewriter {
- public static final String UTILITY_CLASS_NAME = "$r8$twr$utility";
- public static final String UTILITY_CLASS_DESCRIPTOR = "L$r8$twr$utility;";
-
private final AppView<?> appView;
- private final IRConverter converter;
+ private final DexProto twrCloseResourceProto;
+ private final List<ProgramMethod> synthesizedMethods = new ArrayList<>();
- private final DexMethod twrCloseResourceMethod;
-
- private final Set<DexProgramClass> referencingClasses = Sets.newConcurrentHashSet();
-
- public TwrCloseResourceRewriter(AppView<?> appView, IRConverter converter) {
- this.appView = appView;
- this.converter = converter;
-
- DexItemFactory dexItemFactory = appView.dexItemFactory();
- DexType twrUtilityClass = dexItemFactory.createType(UTILITY_CLASS_DESCRIPTOR);
- DexProto twrCloseResourceProto =
- dexItemFactory.createProto(
- dexItemFactory.voidType, dexItemFactory.throwableType, dexItemFactory.objectType);
- this.twrCloseResourceMethod =
- dexItemFactory.createMethod(
- twrUtilityClass, twrCloseResourceProto, dexItemFactory.twrCloseResourceMethodName);
+ public static boolean enableTwrCloseResourceDesugaring(InternalOptions options) {
+ return options.desugarState == DesugarState.ON
+ && options.enableTryWithResourcesDesugaring()
+ && !options.canUseTwrCloseResourceMethod();
}
- public static boolean isUtilityClassDescriptor(DexType type) {
- return type.descriptor.toString().equals(UTILITY_CLASS_DESCRIPTOR);
+ public TwrCloseResourceRewriter(AppView<?> appView) {
+ this.appView = appView;
+ DexItemFactory dexItemFactory = appView.dexItemFactory();
+ twrCloseResourceProto =
+ dexItemFactory.createProto(
+ dexItemFactory.voidType, dexItemFactory.throwableType, dexItemFactory.objectType);
+ }
+
+ public int rewriteCf(ProgramMethod method, Consumer<ProgramMethod> newMethodCallback) {
+ CfCode code = method.getDefinition().getCode().asCfCode();
+ List<CfInstruction> instructions = code.getInstructions();
+ Supplier<List<CfInstruction>> lazyNewInstructions =
+ Suppliers.memoize(() -> new ArrayList<>(instructions));
+ int replaced = 0;
+ int newInstructionDelta = 0;
+ for (int i = 0; i < instructions.size(); i++) {
+ CfInvoke invoke = instructions.get(i).asInvoke();
+ if (invoke == null
+ || invoke.getOpcode() != Opcodes.INVOKESTATIC
+ || !isTwrCloseResourceMethod(invoke.getMethod(), appView.dexItemFactory())) {
+ continue;
+ }
+ // Synthesize a new method.
+ ProgramMethod closeMethod = createSyntheticCloseResourceMethod(method);
+ newMethodCallback.accept(closeMethod);
+ // Rewrite the invoke to the new synthetic.
+ int newInstructionIndex = i + newInstructionDelta;
+ lazyNewInstructions
+ .get()
+ .set(
+ newInstructionIndex,
+ new CfInvoke(Opcodes.INVOKESTATIC, closeMethod.getReference(), false));
+ ++replaced;
+ }
+ if (replaced > 0) {
+ code.setInstructions(lazyNewInstructions.get());
+ }
+ return replaced;
}
// Rewrites calls to $closeResource() method. Can be invoked concurrently.
- public void rewriteMethodCode(IRCode code) {
+ public void rewriteIR(IRCode code) {
InstructionListIterator iterator = code.instructionListIterator();
- AppInfo appInfo = appView.appInfo();
while (iterator.hasNext()) {
- Instruction instruction = iterator.next();
- if (!instruction.isInvokeStatic()) {
- continue;
- }
- InvokeStatic invoke = instruction.asInvokeStatic();
- if (!isSynthesizedCloseResourceMethod(invoke.getInvokedMethod(), appView)) {
+ InvokeStatic invoke = iterator.next().asInvokeStatic();
+ if (invoke == null
+ || !isTwrCloseResourceMethod(invoke.getInvokedMethod(), appView.dexItemFactory())) {
continue;
}
- // Replace with a call to a synthetic utility with the only
- // implementation of the method $closeResource.
+ // Replace with a call to a synthetic utility.
assert invoke.outValue() == null;
assert invoke.inValues().size() == 2;
- iterator.replaceCurrentInstruction(
- new InvokeStatic(twrCloseResourceMethod, null, invoke.inValues()));
-
- // Mark as a class referencing utility class.
- referencingClasses.add(appInfo.definitionFor(code.method().getHolderType()).asProgramClass());
+ ProgramMethod closeResourceMethod = createSyntheticCloseResourceMethod(code.context());
+ InvokeStatic newInvoke =
+ new InvokeStatic(closeResourceMethod.getReference(), null, invoke.inValues());
+ iterator.replaceCurrentInstruction(newInvoke);
+ synchronized (synthesizedMethods) {
+ synthesizedMethods.add(closeResourceMethod);
+ }
}
}
- public static boolean isSynthesizedCloseResourceMethod(DexMethod method, AppView<?> appView) {
- DexMethod original = appView.graphLens().getOriginalMethodSignature(method);
- assert original != null;
- // We consider all methods of *any* class with expected name and signature
- // to be synthesized by java 9 compiler for try-with-resources, reasoning:
- //
- // * we need to look to all potential classes because the calls might be
- // moved by inlining.
- // * theoretically we could check appropriate encoded method for having
- // right attributes, but it still does not guarantee much since we also
- // need to look into code and doing this seems an overkill
- //
- DexItemFactory dexItemFactory = appView.dexItemFactory();
- return original.name == dexItemFactory.twrCloseResourceMethodName
- && original.proto == dexItemFactory.twrCloseResourceMethodProto;
+ public static boolean isTwrCloseResourceMethod(DexMethod method, DexItemFactory factory) {
+ return method.name == factory.twrCloseResourceMethodName
+ && method.proto == factory.twrCloseResourceMethodProto;
}
- public void synthesizeUtilityClass(
- Builder<?> builder, ExecutorService executorService, InternalOptions options)
- throws ExecutionException {
- if (referencingClasses.isEmpty()) {
- return;
- }
+ private ProgramMethod createSyntheticCloseResourceMethod(ProgramMethod method) {
+ return appView
+ .getSyntheticItems()
+ .createMethod(
+ SyntheticKind.TWR_CLOSE_RESOURCE,
+ method,
+ appView.dexItemFactory(),
+ methodBuilder ->
+ methodBuilder
+ .setAccessFlags(
+ MethodAccessFlags.fromSharedAccessFlags(
+ Constants.ACC_PUBLIC | Constants.ACC_STATIC | Constants.ACC_SYNTHETIC,
+ false))
+ .setProto(twrCloseResourceProto)
+ .setCode(
+ m ->
+ BackportedMethods.CloseResourceMethod_closeResourceImpl(
+ appView.options(), m)));
+ }
- // The only encoded method.
- CfCode code =
- BackportedMethods.CloseResourceMethod_closeResourceImpl(
- options, twrCloseResourceMethod);
- MethodAccessFlags flags = MethodAccessFlags.fromSharedAccessFlags(
- Constants.ACC_PUBLIC | Constants.ACC_STATIC | Constants.ACC_SYNTHETIC, false);
- DexEncodedMethod method =
- new DexEncodedMethod(
- twrCloseResourceMethod,
- flags,
- MethodTypeSignature.noSignature(),
- DexAnnotationSet.empty(),
- ParameterAnnotationsList.empty(),
- code,
- true);
-
- // Create utility class.
- DexProgramClass utilityClass =
- new DexProgramClass(
- twrCloseResourceMethod.holder,
- null,
- new SynthesizedOrigin("twr utility class", getClass()),
- ClassAccessFlags.fromSharedAccessFlags(Constants.ACC_PUBLIC | Constants.ACC_SYNTHETIC),
- appView.dexItemFactory().objectType,
- DexTypeList.empty(),
- null,
- null,
- Collections.emptyList(),
- null,
- Collections.emptyList(),
- ClassSignature.noSignature(),
- DexAnnotationSet.empty(),
- DexEncodedField.EMPTY_ARRAY,
- DexEncodedField.EMPTY_ARRAY,
- new DexEncodedMethod[] {method},
- DexEncodedMethod.EMPTY_ARRAY,
- appView.dexItemFactory().getSkipNameValidationForTesting(),
- DexProgramClass::checksumFromType,
- referencingClasses);
-
- // Process created class and method.
- AppInfo appInfo = appView.appInfo();
- MainDexClasses mainDexClasses = appInfo.getMainDexClasses();
- boolean addToMainDexList = mainDexClasses.containsAnyOf(referencingClasses);
- appInfo.addSynthesizedClass(utilityClass, addToMainDexList);
- converter.optimizeSynthesizedClass(utilityClass, executorService);
- builder.addSynthesizedClass(utilityClass);
+ public void processSynthesizedMethods(IRConverter converter) {
+ synthesizedMethods.forEach(converter::optimizeSynthesizedMethod);
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
index 8f510fb..7e75ee2 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
@@ -49,7 +49,6 @@
import com.android.tools.r8.ir.conversion.LensCodeRewriter;
import com.android.tools.r8.ir.conversion.MethodProcessor;
import com.android.tools.r8.ir.conversion.PostOptimization;
-import com.android.tools.r8.ir.desugar.TwrCloseResourceRewriter;
import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackIgnore;
import com.android.tools.r8.ir.optimize.inliner.DefaultInliningReasonStrategy;
@@ -130,9 +129,7 @@
}
if (extraNeverInlineMethods.contains(
- appView.graphLens().getOriginalMethodSignature(singleTargetReference))
- || TwrCloseResourceRewriter.isSynthesizedCloseResourceMethod(
- singleTargetReference, appView)) {
+ appView.graphLens().getOriginalMethodSignature(singleTargetReference))) {
whyAreYouNotInliningReporter.reportExtraNeverInline();
return true;
}
diff --git a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
index 73037aa..f1c5405 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -47,7 +47,6 @@
import com.android.tools.r8.ir.desugar.DesugaredLibraryAPIConverter;
import com.android.tools.r8.ir.desugar.InterfaceMethodRewriter;
import com.android.tools.r8.ir.desugar.LambdaDescriptor;
-import com.android.tools.r8.ir.desugar.TwrCloseResourceRewriter;
import com.android.tools.r8.synthesis.CommittedItems;
import com.android.tools.r8.utils.CollectionUtils;
import com.android.tools.r8.utils.InternalOptions;
@@ -473,7 +472,6 @@
|| InterfaceMethodRewriter.hasDispatchClassSuffix(type)
|| InterfaceMethodRewriter.isEmulatedLibraryClassType(type)
|| type.toDescriptorString().startsWith("Lj$/$r8$retargetLibraryMember$")
- || TwrCloseResourceRewriter.isUtilityClassDescriptor(type)
// TODO(b/150736225): Not sure how to remove these.
|| DesugaredLibraryAPIConverter.isVivifiedType(type)
: "Failed lookup of non-missing type: " + type;
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
index 90416fd..37c66c4 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -86,6 +86,7 @@
import com.android.tools.r8.ir.desugar.LambdaClass;
import com.android.tools.r8.ir.desugar.LambdaDescriptor;
import com.android.tools.r8.ir.desugar.LambdaRewriter;
+import com.android.tools.r8.ir.desugar.TwrCloseResourceRewriter;
import com.android.tools.r8.kotlin.KotlinMetadataEnqueuerExtension;
import com.android.tools.r8.logging.Log;
import com.android.tools.r8.naming.identifiernamestring.IdentifierNameStringLookupResult;
@@ -359,12 +360,14 @@
private final LambdaRewriter lambdaRewriter;
private final BackportedMethodRewriter backportRewriter;
+ private final TwrCloseResourceRewriter twrCloseResourceRewriter;
private final DesugaredLibraryConversionWrapperAnalysis desugaredLibraryWrapperAnalysis;
private final Map<DexType, Pair<LambdaClass, ProgramMethod>> lambdaClasses =
new IdentityHashMap<>();
private final ProgramMethodMap<Map<DexCallSite, LambdaClass>> lambdaCallSites =
ProgramMethodMap.create();
private final Map<DexMethod, ProgramMethod> methodsWithBackports = new IdentityHashMap<>();
+ private final Map<DexMethod, ProgramMethod> methodsWithTwrCloseResource = new IdentityHashMap<>();
private final Set<DexProgramClass> classesWithSerializableLambdas = Sets.newIdentityHashSet();
Enqueuer(
@@ -403,6 +406,10 @@
lambdaRewriter = options.desugarState == DesugarState.ON ? new LambdaRewriter(appView) : null;
backportRewriter =
options.desugarState == DesugarState.ON ? new BackportedMethodRewriter(appView) : null;
+ twrCloseResourceRewriter =
+ TwrCloseResourceRewriter.enableTwrCloseResourceDesugaring(options)
+ ? new TwrCloseResourceRewriter(appView)
+ : null;
objectAllocationInfoCollection =
ObjectAllocationInfoCollectionImpl.builder(mode.isInitialTreeShaking(), graphReporter);
@@ -1234,12 +1241,24 @@
return false;
}
+ private boolean registerCloseResource(DexMethod invokedMethod, ProgramMethod context) {
+ if (twrCloseResourceRewriter != null
+ && TwrCloseResourceRewriter.isTwrCloseResourceMethod(
+ invokedMethod, appView.dexItemFactory())) {
+ methodsWithTwrCloseResource.putIfAbsent(context.getReference(), context);
+ return true;
+ }
+ return false;
+ }
+
private void traceInvokeStatic(
DexMethod invokedMethod, ProgramMethod context, KeepReason reason) {
if (registerBackportInvoke(invokedMethod, context)) {
return;
}
-
+ if (registerCloseResource(invokedMethod, context)) {
+ return;
+ }
DexItemFactory dexItemFactory = appView.dexItemFactory();
if (dexItemFactory.classMethods.isReflectiveClassLookup(invokedMethod)
|| dexItemFactory.atomicFieldUpdaterMethods.isFieldUpdater(invokedMethod)) {
@@ -3132,6 +3151,7 @@
synthesizeLambdas(additions);
synthesizeLibraryConversionWrappers(additions);
synthesizeBackports(additions);
+ synthesizeTwrCloseResource(additions);
if (additions.isEmpty()) {
return;
}
@@ -3175,6 +3195,12 @@
}
}
+ private void synthesizeTwrCloseResource(SyntheticAdditions additions) {
+ for (ProgramMethod method : methodsWithTwrCloseResource.values()) {
+ twrCloseResourceRewriter.rewriteCf(method, additions::addLiveMethod);
+ }
+ }
+
private void synthesizeLambdas(SyntheticAdditions additions) {
if (lambdaRewriter == null || lambdaClasses.isEmpty()) {
assert lambdaCallSites.isEmpty();
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
index 644a359..27e03ce 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
@@ -27,7 +27,8 @@
BACKPORT("Backport", true),
STATIC_INTERFACE_CALL("StaticInterfaceCall", true),
TO_STRING_IF_NOT_NULL("ToStringIfNotNull", true),
- THROW_CCE_IF_NOT_NULL("ThrowCCEIfNotNull", true);
+ THROW_CCE_IF_NOT_NULL("ThrowCCEIfNotNull", true),
+ TWR_CLOSE_RESOURCE("TwrCloseResource", true);
public final String descriptor;
public final boolean isSingleSyntheticMethod;
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 c375074..d0e0a52 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -1606,6 +1606,16 @@
return !isDesugaring() || hasMinApi(AndroidApiLevel.K);
}
+ public boolean enableTryWithResourcesDesugaring() {
+ switch (tryWithResourcesDesugaring) {
+ case Off:
+ return false;
+ case Auto:
+ return !canUseSuppressedExceptions();
+ }
+ throw new Unreachable();
+ }
+
public boolean canUsePrivateInterfaceMethods() {
return !isDesugaring() || hasMinApi(AndroidApiLevel.N);
}
diff --git a/src/test/java/com/android/tools/r8/D8IncrementalRunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/D8IncrementalRunExamplesAndroidOTest.java
index f5348c3..845a5c5 100644
--- a/src/test/java/com/android/tools/r8/D8IncrementalRunExamplesAndroidOTest.java
+++ b/src/test/java/com/android/tools/r8/D8IncrementalRunExamplesAndroidOTest.java
@@ -13,7 +13,6 @@
import com.android.tools.r8.errors.Unimplemented;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.ir.desugar.InterfaceMethodRewriter;
-import com.android.tools.r8.ir.desugar.TwrCloseResourceRewriter;
import com.android.tools.r8.references.ClassReference;
import com.android.tools.r8.references.Reference;
import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
@@ -98,7 +97,7 @@
Assert.assertTrue(
descriptor.endsWith(InterfaceMethodRewriter.COMPANION_CLASS_NAME_SUFFIX + ";")
|| descriptor.endsWith(InterfaceMethodRewriter.DISPATCH_CLASS_NAME_SUFFIX + ";")
- || descriptor.equals(TwrCloseResourceRewriter.UTILITY_CLASS_DESCRIPTOR)
+ || SyntheticItemsTestUtils.isExternalTwrCloseMethod(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 e1d4c9e..bb69054 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -1702,6 +1702,10 @@
return AndroidApiLevel.L;
}
+ public static AndroidApiLevel apiLevelWithTwrCloseResourceSupport() {
+ return AndroidApiLevel.K;
+ }
+
public static boolean canUseJavaUtilObjects(TestParameters parameters) {
return parameters.isCfRuntime()
|| parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.K);
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
new file mode 100644
index 0000000..dde98e2
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/twr/TwrCloseResourceDuplicationTest.java
@@ -0,0 +1,157 @@
+// Copyright (c) 2021, 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.twr;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.ZipUtils;
+import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
+import java.io.IOException;
+import java.util.List;
+import java.util.jar.JarFile;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class TwrCloseResourceDuplicationTest extends TestBase {
+
+ static final int INPUT_CLASSES = 3;
+
+ static final String EXPECTED =
+ StringUtils.lines(
+ "foo opened 1",
+ "foo post close 1",
+ "foo opened 2",
+ "foo caught from 2: RuntimeException",
+ "foo post close 2",
+ "bar opened 1",
+ "bar post close 1",
+ "bar opened 2",
+ "bar caught from 2: RuntimeException",
+ "bar post close 2");
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
+ }
+
+ public TwrCloseResourceDuplicationTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ private String getZipFile() throws IOException {
+ return ZipUtils.ZipBuilder.builder(temp.newFile("file.zip").toPath())
+ // DEX VMs from 4.4 up-to 9.0 including, will fail if no entry is added.
+ .addBytes("entry", new byte[1])
+ .build()
+ .toString();
+ }
+
+ @Test
+ public void testJvm() throws Exception {
+ assumeTrue(parameters.isCfRuntime());
+ testForJvm()
+ .addInnerClasses(getClass())
+ .run(parameters.getRuntime(), TestClass.class, getZipFile())
+ .assertSuccessWithOutput(EXPECTED);
+ }
+
+ @Test
+ public void testD8() throws Exception {
+ testForD8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .setMinApi(parameters.getApiLevel())
+ .run(parameters.getRuntime(), TestClass.class, getZipFile())
+ .assertSuccessWithOutput(EXPECTED)
+ .inspect(
+ inspector -> {
+ // There should be exactly one synthetic class besides the three program classes.
+ int expectedSynthetics =
+ parameters.getApiLevel().isLessThan(apiLevelWithTwrCloseResourceSupport())
+ ? 1
+ : 0;
+ assertEquals(INPUT_CLASSES + expectedSynthetics, inspector.allClasses().size());
+ });
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ assumeTrue(parameters.isDexRuntime());
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(TestClass.class)
+ .addKeepClassAndMembersRules(Foo.class, Bar.class)
+ .setMinApi(parameters.getApiLevel())
+ .noMinification()
+ .run(parameters.getRuntime(), TestClass.class, getZipFile())
+ .assertSuccessWithOutput(EXPECTED)
+ .inspect(
+ inspector -> {
+ // R8 will optimize the generated methods for the two cases below where the thrown
+ // exception is known or not, thus the synthetic methods will be 2.
+ int expectedSynthetics =
+ parameters.getApiLevel().isLessThan(apiLevelWithTwrCloseResourceSupport())
+ ? 2
+ : 0;
+ List<FoundClassSubject> foundClassSubjects = inspector.allClasses();
+ assertEquals(INPUT_CLASSES + expectedSynthetics, foundClassSubjects.size());
+ });
+ }
+
+ static class Foo {
+ void foo(String name) {
+ try (JarFile f = new JarFile(name)) {
+ System.out.println("foo opened 1");
+ } catch (Exception e) {
+ System.out.println("foo caught from 1: " + e.getClass().getSimpleName());
+ } finally {
+ System.out.println("foo post close 1");
+ }
+ try (JarFile f = new JarFile(name)) {
+ System.out.println("foo opened 2");
+ throw new RuntimeException();
+ } catch (Exception e) {
+ System.out.println("foo caught from 2: " + e.getClass().getSimpleName());
+ } finally {
+ System.out.println("foo post close 2");
+ }
+ }
+ }
+
+ static class Bar {
+ void bar(String name) {
+ try (JarFile f = new JarFile(name)) {
+ System.out.println("bar opened 1");
+ } catch (Exception e) {
+ System.out.println("bar caught from 1: " + e.getClass().getSimpleName());
+ } finally {
+ System.out.println("bar post close 1");
+ }
+ try (JarFile f = new JarFile(name)) {
+ System.out.println("bar opened 2");
+ throw new RuntimeException();
+ } catch (Exception e) {
+ System.out.println("bar caught from 2: " + e.getClass().getSimpleName());
+ } finally {
+ System.out.println("bar post close 2");
+ }
+ }
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ new Foo().foo(args[0]);
+ new Bar().bar(args[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 dba85d6..438d3f9 100644
--- a/src/test/java/com/android/tools/r8/synthesis/SyntheticItemsTestUtils.java
+++ b/src/test/java/com/android/tools/r8/synthesis/SyntheticItemsTestUtils.java
@@ -63,6 +63,10 @@
reference, Phase.EXTERNAL, SyntheticKind.STATIC_INTERFACE_CALL);
}
+ public static boolean isExternalTwrCloseMethod(ClassReference reference) {
+ return SyntheticNaming.isSynthetic(reference, Phase.EXTERNAL, SyntheticKind.TWR_CLOSE_RESOURCE);
+ }
+
public static Matcher<String> containsInternalSyntheticReference() {
return containsString(SyntheticNaming.getPhaseSeparator(Phase.INTERNAL));
}