blob: b78757d832f7a8073cc81781edf163d7c25f4ea7 [file] [log] [blame]
// 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.ir.desugar;
import com.android.tools.r8.dex.Constants;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.ClassAccessFlags;
import com.android.tools.r8.graph.DexAnnotationSet;
import com.android.tools.r8.graph.DexApplication;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexField;
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.DexString;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.DexTypeList;
import com.android.tools.r8.graph.NestMemberClassAttribute;
import com.android.tools.r8.graph.UseRegistry;
import com.android.tools.r8.ir.code.Invoke;
import com.android.tools.r8.origin.SynthesizedOrigin;
import com.android.tools.r8.utils.BooleanUtils;
import com.android.tools.r8.utils.Pair;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
// NestBasedAccessDesugaring contains common code between the two subclasses
// which are specialized for d8 and r8
public abstract class NestBasedAccessDesugaring {
// Short names to avoid creating long strings
public static final String NEST_ACCESS_NAME_PREFIX = "-$$Nest$";
private static final String NEST_ACCESS_METHOD_NAME_PREFIX = NEST_ACCESS_NAME_PREFIX + "m";
private static final String NEST_ACCESS_STATIC_METHOD_NAME_PREFIX =
NEST_ACCESS_NAME_PREFIX + "sm";
private static final String NEST_ACCESS_FIELD_GET_NAME_PREFIX = NEST_ACCESS_NAME_PREFIX + "fget";
private static final String NEST_ACCESS_STATIC_GET_FIELD_NAME_PREFIX =
NEST_ACCESS_NAME_PREFIX + "sfget";
private static final String NEST_ACCESS_FIELD_PUT_NAME_PREFIX = NEST_ACCESS_NAME_PREFIX + "fput";
private static final String NEST_ACCESS_STATIC_PUT_FIELD_NAME_PREFIX =
NEST_ACCESS_NAME_PREFIX + "sfput";
public static final String NEST_CONSTRUCTOR_NAME = NEST_ACCESS_NAME_PREFIX + "Constructor";
private static final String FULL_NEST_CONTRUCTOR_NAME = "L" + NEST_CONSTRUCTOR_NAME + ";";
protected final AppView<?> appView;
// Following maps are there to avoid creating the bridges multiple times
// and remember the bridges to add once the nests are processed.
final Map<DexMethod, DexEncodedMethod> bridges = new ConcurrentHashMap<>();
final Map<DexField, DexEncodedMethod> getFieldBridges = new ConcurrentHashMap<>();
final Map<DexField, DexEncodedMethod> putFieldBridges = new ConcurrentHashMap<>();
// Common single empty class for nest based private constructors
private final DexProgramClass nestConstructor;
private boolean nestConstructorUsed = false;
NestBasedAccessDesugaring(AppView<?> appView) {
this.appView = appView;
this.nestConstructor = createNestAccessConstructor();
}
DexType getNestConstructorType() {
assert nestConstructor != null;
return nestConstructor.type;
}
abstract void reportMissingNestHost(DexClass clazz);
abstract void reportIncompleteNest(List<DexType> nest);
DexClass definitionFor(DexType type) {
return appView.definitionFor(appView.graphLense().lookupType(type));
}
private DexEncodedMethod definitionFor(
DexMethod method, DexMethod context, Invoke.Type invokeType) {
return appView.definitionFor(
appView.graphLense().lookupMethod(method, context, invokeType).getMethod());
}
private DexEncodedField definitionFor(DexField field) {
return appView.definitionFor(appView.graphLense().lookupField(field));
}
// Extract the list of types in the programClass' nest, of host hostClass
private Pair<DexClass, List<DexType>> extractNest(DexClass clazz) {
assert clazz != null;
DexClass hostClass = clazz.isNestHost() ? clazz : definitionFor(clazz.getNestHost());
if (hostClass == null) {
reportMissingNestHost(clazz);
// Missing nest host means the class is considered as not being part of a nest.
clazz.clearNestHost();
return null;
}
List<DexType> classesInNest =
new ArrayList<>(hostClass.getNestMembersClassAttributes().size() + 1);
for (NestMemberClassAttribute nestmate : hostClass.getNestMembersClassAttributes()) {
classesInNest.add(nestmate.getNestMember());
}
classesInNest.add(hostClass.type);
return new Pair<>(hostClass, classesInNest);
}
Future<?> asyncProcessNest(DexClass clazz, ExecutorService executorService) {
return executorService.submit(
() -> {
Pair<DexClass, List<DexType>> nest = extractNest(clazz);
// Nest is null when nest host is missing, we do nothing in this case.
if (nest != null) {
processNest(nest.getFirst(), nest.getSecond());
}
return null; // we want a Callable not a Runnable to be able to throw
});
}
private void processNest(DexClass host, List<DexType> nest) {
boolean reported = false;
for (DexType type : nest) {
DexClass clazz = definitionFor(type);
if (clazz == null) {
if (!reported) {
reportIncompleteNest(nest);
reported = true;
}
} else {
reportDesugarDependencies(host, clazz);
if (shouldProcessClassInNest(clazz, nest)) {
NestBasedAccessDesugaringUseRegistry registry =
new NestBasedAccessDesugaringUseRegistry(clazz);
for (DexEncodedMethod method : clazz.methods()) {
registry.setContext(method.method);
method.registerCodeReferences(registry);
}
}
}
}
}
private void reportDesugarDependencies(DexClass host, DexClass clazz) {
if (host == clazz) {
return;
}
if (host.isProgramClass()) {
InterfaceMethodRewriter.reportDependencyEdge(host.asProgramClass(), clazz, appView.options());
}
if (clazz.isProgramClass()) {
InterfaceMethodRewriter.reportDependencyEdge(clazz.asProgramClass(), host, appView.options());
}
}
protected abstract boolean shouldProcessClassInNest(DexClass clazz, List<DexType> nest);
private DexProgramClass createNestAccessConstructor() {
return new DexProgramClass(
appView.dexItemFactory().createType(FULL_NEST_CONTRUCTOR_NAME),
null,
new SynthesizedOrigin("Nest based access desugaring", getClass()),
// Make the synthesized class public since shared in the whole program.
ClassAccessFlags.fromDexAccessFlags(
Constants.ACC_FINAL | Constants.ACC_SYNTHETIC | Constants.ACC_PUBLIC),
appView.dexItemFactory().objectType,
DexTypeList.empty(),
appView.dexItemFactory().createString("nest"),
null,
Collections.emptyList(),
null,
Collections.emptyList(),
DexAnnotationSet.empty(),
DexEncodedField.EMPTY_ARRAY,
DexEncodedField.EMPTY_ARRAY,
DexEncodedMethod.EMPTY_ARRAY,
DexEncodedMethod.EMPTY_ARRAY,
appView.dexItemFactory().getSkipNameValidationForTesting(),
DexProgramClass::checksumFromType);
}
void synthesizeNestConstructor(DexApplication.Builder<?> builder) {
if (nestConstructorUsed) {
appView.appInfo().addSynthesizedClass(nestConstructor);
builder.addSynthesizedClass(nestConstructor, true);
}
}
public static boolean isNestConstructor(DexType type) {
return type.getName().equals(NEST_CONSTRUCTOR_NAME);
}
private DexString computeMethodBridgeName(DexEncodedMethod method) {
String methodName = method.method.name.toString();
String fullName;
if (method.isStatic()) {
fullName = NEST_ACCESS_STATIC_METHOD_NAME_PREFIX + methodName;
} else {
fullName = NEST_ACCESS_METHOD_NAME_PREFIX + methodName;
}
return appView.dexItemFactory().createString(fullName);
}
private DexString computeFieldBridgeName(DexEncodedField field, boolean isGet) {
String fieldName = field.field.name.toString();
String fullName;
if (isGet && !field.isStatic()) {
fullName = NEST_ACCESS_FIELD_GET_NAME_PREFIX + fieldName;
} else if (isGet) {
fullName = NEST_ACCESS_STATIC_GET_FIELD_NAME_PREFIX + fieldName;
} else if (!field.isStatic()) {
fullName = NEST_ACCESS_FIELD_PUT_NAME_PREFIX + fieldName;
} else {
fullName = NEST_ACCESS_STATIC_PUT_FIELD_NAME_PREFIX + fieldName;
}
return appView.dexItemFactory().createString(fullName);
}
private DexMethod computeMethodBridge(DexEncodedMethod encodedMethod) {
DexMethod method = encodedMethod.method;
DexProto proto =
encodedMethod.accessFlags.isStatic()
? method.proto
: appView.dexItemFactory().prependTypeToProto(method.holder, method.proto);
return appView
.dexItemFactory()
.createMethod(method.holder, proto, computeMethodBridgeName(encodedMethod));
}
private DexMethod computeInitializerBridge(DexMethod method) {
DexProto newProto =
appView.dexItemFactory().appendTypeToProto(method.proto, nestConstructor.type);
return appView.dexItemFactory().createMethod(method.holder, newProto, method.name);
}
private DexMethod computeFieldBridge(DexEncodedField field, boolean isGet) {
DexType holderType = field.field.holder;
DexType fieldType = field.field.type;
int bridgeParameterCount =
BooleanUtils.intValue(!field.isStatic()) + BooleanUtils.intValue(!isGet);
DexType[] parameters = new DexType[bridgeParameterCount];
if (!isGet) {
parameters[parameters.length - 1] = fieldType;
}
if (!field.isStatic()) {
parameters[0] = holderType;
}
DexType returnType = isGet ? fieldType : appView.dexItemFactory().voidType;
DexProto proto = appView.dexItemFactory().createProto(returnType, parameters);
return appView
.dexItemFactory()
.createMethod(holderType, proto, computeFieldBridgeName(field, isGet));
}
boolean invokeRequiresRewriting(DexEncodedMethod method, DexClass contextClass) {
assert method != null;
// Rewrite only when targeting other nest members private fields.
if (!method.accessFlags.isPrivate() || method.method.holder == contextClass.type) {
return false;
}
DexClass methodHolder = definitionFor(method.method.holder);
assert methodHolder != null; // from encodedMethod
return methodHolder.getNestHost() == contextClass.getNestHost();
}
boolean fieldAccessRequiresRewriting(DexEncodedField field, DexClass contextClass) {
assert field != null;
// Rewrite only when targeting other nest members private fields.
if (!field.accessFlags.isPrivate() || field.field.holder == contextClass.type) {
return false;
}
DexClass fieldHolder = definitionFor(field.field.holder);
assert fieldHolder != null; // from encodedField
return fieldHolder.getNestHost() == contextClass.getNestHost();
}
private boolean holderRequiresBridge(DexClass holder) {
// Bridges are added on program classes only.
// Bridges on class paths are added in different compilation units.
if (holder.isProgramClass()) {
return false;
} else if (holder.isClasspathClass()) {
return true;
}
assert holder.isLibraryClass();
Pair<DexClass, List<DexType>> nest = extractNest(holder);
assert nest != null : "Should be a compilation error if missing nest host on library class.";
reportIncompleteNest(nest.getSecond());
throw new Unreachable(
"Incomplete nest due to missing library class should raise a compilation error.");
}
DexMethod ensureFieldAccessBridge(DexEncodedField field, boolean isGet) {
DexClass holder = definitionFor(field.field.holder);
assert holder != null;
DexMethod bridgeMethod = computeFieldBridge(field, isGet);
if (holderRequiresBridge(holder)) {
return bridgeMethod;
}
// The map is used to avoid creating multiple times the bridge
// and remembers the bridges to add.
Map<DexField, DexEncodedMethod> fieldMap = isGet ? getFieldBridges : putFieldBridges;
fieldMap.computeIfAbsent(
field.field,
k ->
DexEncodedMethod.createFieldAccessorBridge(
new DexFieldWithAccess(field, isGet), holder, bridgeMethod));
return bridgeMethod;
}
DexMethod ensureInvokeBridge(DexEncodedMethod method) {
// We add bridges only when targeting other nest members.
DexClass holder = definitionFor(method.method.holder);
assert holder != null;
DexMethod bridgeMethod;
if (method.isInstanceInitializer()) {
nestConstructorUsed = true;
bridgeMethod = computeInitializerBridge(method.method);
} else {
bridgeMethod = computeMethodBridge(method);
}
if (holderRequiresBridge(holder)) {
return bridgeMethod;
}
// The map is used to avoid creating multiple times the bridge
// and remembers the bridges to add.
bridges.computeIfAbsent(
method.method,
k ->
method.isInstanceInitializer()
? method.toInitializerForwardingBridge(holder, bridgeMethod)
: method.toStaticForwardingBridge(holder, computeMethodBridge(method)));
return bridgeMethod;
}
protected class NestBasedAccessDesugaringUseRegistry extends UseRegistry {
private final DexClass currentClass;
private DexMethod context;
NestBasedAccessDesugaringUseRegistry(DexClass currentClass) {
super(appView.options().itemFactory);
this.currentClass = currentClass;
}
public void setContext(DexMethod context) {
this.context = context;
}
private boolean registerInvoke(DexMethod method, Invoke.Type invokeType) {
// Calls to non class type are not done through nest based access control.
// Work-around for calls to enum.clone().
if (!method.holder.isClassType()) {
return false;
}
DexEncodedMethod encodedMethod = definitionFor(method, context, invokeType);
if (encodedMethod != null && invokeRequiresRewriting(encodedMethod, currentClass)) {
ensureInvokeBridge(encodedMethod);
return true;
}
return false;
}
private boolean registerFieldAccess(DexField field, boolean isGet) {
DexEncodedField encodedField = definitionFor(field);
if (encodedField != null && fieldAccessRequiresRewriting(encodedField, currentClass)) {
ensureFieldAccessBridge(encodedField, isGet);
return true;
}
return false;
}
@Override
public boolean registerInitClass(DexType clazz) {
// Nothing to do since we always use a public field for initializing the class.
return true;
}
@Override
public boolean registerInvokeVirtual(DexMethod method) {
// Calls to class nest mate private methods are targeted by invokeVirtual in jdk11.
// The spec recommends to do so, but do not enforce it, hence invokeDirect is also registered.
return registerInvoke(method, Invoke.Type.VIRTUAL);
}
@Override
public boolean registerInvokeDirect(DexMethod method) {
return registerInvoke(method, Invoke.Type.DIRECT);
}
@Override
public boolean registerInvokeStatic(DexMethod method) {
return registerInvoke(method, Invoke.Type.STATIC);
}
@Override
public boolean registerInvokeInterface(DexMethod method) {
// Calls to interface nest mate private methods are targeted by invokeInterface in jdk11.
// The spec recommends to do so, but do not enforce it, hence invokeDirect is also registered.
return registerInvoke(method, Invoke.Type.INTERFACE);
}
@Override
public boolean registerInvokeSuper(DexMethod method) {
return registerInvoke(method, Invoke.Type.SUPER);
}
@Override
public boolean registerInstanceFieldWrite(DexField field) {
return registerFieldAccess(field, false);
}
@Override
public boolean registerInstanceFieldRead(DexField field) {
return registerFieldAccess(field, true);
}
@Override
public boolean registerNewInstance(DexType type) {
// Unrelated to access based control.
// The <init> method has to be rewritten instead
// and <init> is called through registerInvoke.
return false;
}
@Override
public boolean registerStaticFieldRead(DexField field) {
return registerFieldAccess(field, true);
}
@Override
public boolean registerStaticFieldWrite(DexField field) {
return registerFieldAccess(field, false);
}
@Override
public boolean registerTypeReference(DexType type) {
// Unrelated to access based control.
return false;
}
}
public static final class DexFieldWithAccess {
private final DexEncodedField field;
private final boolean isGet;
DexFieldWithAccess(DexEncodedField field, boolean isGet) {
this.field = field;
this.isGet = isGet;
}
@Override
public int hashCode() {
return Objects.hash(field, isGet);
}
@Override
public boolean equals(Object o) {
if (o == null) {
return false;
}
if (getClass() != o.getClass()) {
return false;
}
DexFieldWithAccess other = (DexFieldWithAccess) o;
return isGet == other.isGet && field == other.field;
}
public boolean isGet() {
return isGet;
}
public boolean isStatic() {
return field.accessFlags.isStatic();
}
public boolean isPut() {
return !isGet();
}
public boolean isInstance() {
return !isStatic();
}
public boolean isStaticGet() {
return isStatic() && isGet();
}
public boolean isStaticPut() {
return isStatic() && isPut();
}
public boolean isInstanceGet() {
return isInstance() && isGet();
}
public boolean isInstancePut() {
return isInstance() && isPut();
}
public DexType getType() {
return field.field.type;
}
public DexType getHolder() {
return field.field.holder;
}
public DexField getField() {
return field.field;
}
}
}