blob: 6f3aed50d2439f0ea4c0e88bc42c7ca8cb257389 [file] [log] [blame]
// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.ir.desugar;
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.MethodAccessFlags;
import com.android.tools.r8.graph.ParameterAnnotationsList;
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.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;
// Try with resources outlining processor. Handles $closeResource methods
// synthesized by java 9 compiler.
//
// 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.
//
// 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.
//
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 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 isUtilityClassDescriptor(DexType type) {
return type.descriptor.toString().equals(UTILITY_CLASS_DESCRIPTOR);
}
// Rewrites calls to $closeResource() method. Can be invoked concurrently.
public void rewriteMethodCode(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)) {
continue;
}
// Replace with a call to a synthetic utility with the only
// implementation of the method $closeResource.
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().holder()).asProgramClass());
}
}
public static boolean isSynthesizedCloseResourceMethod(DexMethod method, AppView<?> appView) {
DexMethod original = appView.graphLense().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 void synthesizeUtilityClass(
Builder<?> builder, ExecutorService executorService, InternalOptions options)
throws ExecutionException {
if (referencingClasses.isEmpty()) {
return;
}
// 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, 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(),
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();
boolean addToMainDexList =
referencingClasses.stream().anyMatch(clazz -> appInfo.isInMainDexList(clazz.type));
appInfo.addSynthesizedClass(utilityClass);
converter.optimizeSynthesizedClass(utilityClass, executorService);
builder.addSynthesizedClass(utilityClass, addToMainDexList);
}
}