blob: bab308ef41a1e688532eac597189c674d487693b [file] [log] [blame]
// 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.ir.desugar.nest;
import com.android.tools.r8.cf.code.CfConstNull;
import com.android.tools.r8.cf.code.CfFieldInstruction;
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.errors.Unreachable;
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.Code;
import com.android.tools.r8.graph.DexAnnotationSet;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexClassAndField;
import com.android.tools.r8.graph.DexClassAndMember;
import com.android.tools.r8.graph.DexClassAndMethod;
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.DexMember;
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.GenericSignature.ClassSignature;
import com.android.tools.r8.graph.LibraryMember;
import com.android.tools.r8.graph.ProgramField;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.origin.SynthesizedOrigin;
import com.android.tools.r8.utils.BooleanUtils;
import com.android.tools.r8.utils.ListUtils;
import com.android.tools.r8.utils.StringDiagnostic;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.function.Consumer;
import org.objectweb.asm.Opcodes;
// NestBasedAccessDesugaring contains common code between the two subclasses
// which are specialized for d8 and r8
public 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";
protected final AppView<?> appView;
private final DexItemFactory dexItemFactory;
// Common single empty class for nest based private constructors
private DexProgramClass nestConstructor;
private boolean nestConstructorUsed;
public NestBasedAccessDesugaring(AppView<?> appView) {
this.appView = appView;
this.dexItemFactory = appView.dexItemFactory();
}
void forEachNest(Consumer<Nest> consumer) {
Set<DexType> seenNestHosts = Sets.newIdentityHashSet();
for (DexProgramClass clazz : appView.appInfo().classes()) {
if (!clazz.isInANest() || !seenNestHosts.add(clazz.getNestHost())) {
continue;
}
Nest nest = Nest.create(appView, clazz);
if (nest != null) {
consumer.accept(nest);
}
}
}
public boolean needsDesugaring(ProgramMethod method) {
if (!method.getHolder().isInANest() || !method.getDefinition().hasCode()) {
return false;
}
Code code = method.getDefinition().getCode();
if (code.isDexCode()) {
assert appView.testing().allowDexInputForTesting;
return false;
}
if (!code.isCfCode()) {
throw new Unreachable("Unexpected attempt to determine if non-CF code needs desugaring");
}
return Iterables.any(
code.asCfCode().getInstructions(), instruction -> needsDesugaring(instruction, method));
}
private boolean needsDesugaring(CfInstruction instruction, ProgramMethod context) {
if (instruction.isFieldInstruction()) {
return needsDesugaring(instruction.asFieldInstruction().getField(), context);
}
if (instruction.isInvoke()) {
return needsDesugaring(instruction.asInvoke().getMethod(), context);
}
return false;
}
public boolean needsDesugaring(DexMember<?, ?> memberReference, ProgramMethod context) {
if (!context.getHolder().isInANest() || !memberReference.getHolderType().isClassType()) {
return false;
}
DexClass holder = appView.definitionForHolder(memberReference, context);
DexClassAndMember<?, ?> member = memberReference.lookupMemberOnClass(holder);
return member != null && needsDesugaring(member, context);
}
public boolean needsDesugaring(DexClassAndMember<?, ?> member, DexClassAndMethod context) {
return member.getAccessFlags().isPrivate()
&& member.getHolderType() != context.getHolderType()
&& member.getHolder().isInANest()
&& member.getHolder().getNestHost() == context.getHolder().getNestHost();
}
public boolean desugar(ProgramMethod method) {
return desugar(method, null);
}
public boolean desugar(ProgramMethod method, NestBridgeConsumer bridgeConsumer) {
if (!method.getHolder().isInANest()) {
return false;
}
Code code = method.getDefinition().getCode();
if (!code.isCfCode()) {
appView
.options()
.reporter
.error(
new StringDiagnostic(
"Unsupported attempt to desugar non-CF code",
method.getOrigin(),
method.getPosition()));
return false;
}
CfCode cfCode = code.asCfCode();
List<CfInstruction> desugaredInstructions =
ListUtils.flatMap(
cfCode.getInstructions(),
instruction -> desugarInstruction(instruction, method, bridgeConsumer),
null);
if (desugaredInstructions != null) {
cfCode.setInstructions(desugaredInstructions);
return true;
}
return false;
}
public List<CfInstruction> desugarInstruction(
CfInstruction instruction, ProgramMethod context, NestBridgeConsumer bridgeConsumer) {
if (instruction.isFieldInstruction()) {
return desugarFieldInstruction(instruction.asFieldInstruction(), context, bridgeConsumer);
}
if (instruction.isInvoke()) {
return desugarInvokeInstruction(instruction.asInvoke(), context, bridgeConsumer);
}
return null;
}
private List<CfInstruction> desugarFieldInstruction(
CfFieldInstruction instruction, ProgramMethod context, NestBridgeConsumer bridgeConsumer) {
// Since we only need to desugar accesses to private fields, and all accesses to private
// fields must be accessing the private field directly on its holder, we can lookup the
// field on the holder instead of resolving the field.
DexClass holder = appView.definitionForHolder(instruction.getField(), context);
DexClassAndField field = instruction.getField().lookupMemberOnClass(holder);
if (field == null || !needsDesugaring(field, context)) {
return null;
}
DexMethod bridge = ensureFieldAccessBridge(field, instruction.isFieldGet(), bridgeConsumer);
return ImmutableList.of(
new CfInvoke(Opcodes.INVOKESTATIC, bridge, field.getHolder().isInterface()));
}
private List<CfInstruction> desugarInvokeInstruction(
CfInvoke invoke, ProgramMethod context, NestBridgeConsumer bridgeConsumer) {
DexMethod invokedMethod = invoke.getMethod();
if (!invokedMethod.getHolderType().isClassType()) {
return null;
}
// Since we only need to desugar accesses to private methods, and all accesses to private
// methods must be accessing the private method directly on its holder, we can lookup the
// method on the holder instead of resolving the method.
DexClass holder = appView.definitionForHolder(invokedMethod, context);
DexClassAndMethod target = invokedMethod.lookupMemberOnClass(holder);
if (target == null || !needsDesugaring(target, context)) {
return null;
}
DexMethod bridge = ensureMethodBridge(target, bridgeConsumer);
if (target.getDefinition().isInstanceInitializer()) {
assert !invoke.isInterface();
return ImmutableList.of(
new CfConstNull(), new CfInvoke(Opcodes.INVOKESPECIAL, bridge, false));
}
return ImmutableList.of(new CfInvoke(Opcodes.INVOKESTATIC, bridge, invoke.isInterface()));
}
private RuntimeException reportIncompleteNest(LibraryMember<?, ?> member) {
Nest nest = Nest.create(appView, member.getHolder());
assert nest != null : "Should be a compilation error if missing nest host on library class.";
throw appView.options().errorMissingClassIncompleteNest(nest);
}
private DexProgramClass createNestAccessConstructor() {
// TODO(b/176900254): ensure hygienic synthetic class.
return new DexProgramClass(
dexItemFactory.nestConstructorType,
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),
dexItemFactory.objectType,
DexTypeList.empty(),
dexItemFactory.createString("nest"),
null,
Collections.emptyList(),
null,
Collections.emptyList(),
ClassSignature.noSignature(),
DexAnnotationSet.empty(),
DexEncodedField.EMPTY_ARRAY,
DexEncodedField.EMPTY_ARRAY,
DexEncodedMethod.EMPTY_ARRAY,
DexEncodedMethod.EMPTY_ARRAY,
dexItemFactory.getSkipNameValidationForTesting(),
DexProgramClass::checksumFromType);
}
public DexProgramClass synthesizeNestConstructor() {
if (nestConstructorUsed && nestConstructor == null) {
nestConstructor = createNestAccessConstructor();
return nestConstructor;
}
return null;
}
DexMethod ensureFieldAccessBridge(
DexClassAndField field, boolean isGet, NestBridgeConsumer bridgeConsumer) {
if (field.isProgramField()) {
return ensureFieldAccessBridge(field.asProgramField(), isGet, bridgeConsumer);
}
if (field.isClasspathField()) {
return getFieldAccessBridgeReference(field, isGet);
}
assert field.isLibraryField();
throw reportIncompleteNest(field.asLibraryField());
}
private DexMethod ensureFieldAccessBridge(
ProgramField field, boolean isGet, NestBridgeConsumer bridgeConsumer) {
DexMethod bridgeReference = getFieldAccessBridgeReference(field, isGet);
synchronized (field.getHolder().getMethodCollection()) {
ProgramMethod bridge = field.getHolder().lookupProgramMethod(bridgeReference);
if (bridge == null) {
bridge = DexEncodedMethod.createFieldAccessorBridge(field, isGet, bridgeReference);
bridge.getHolder().addDirectMethod(bridge.getDefinition());
if (bridgeConsumer != null) {
bridgeConsumer.acceptFieldBridge(field, bridge, isGet);
}
}
return bridge.getReference();
}
}
private DexMethod getFieldAccessBridgeReference(DexClassAndField field, boolean isGet) {
int bridgeParameterCount =
BooleanUtils.intValue(!field.getAccessFlags().isStatic()) + BooleanUtils.intValue(!isGet);
DexType[] parameters = new DexType[bridgeParameterCount];
if (!isGet) {
parameters[parameters.length - 1] = field.getType();
}
if (!field.getAccessFlags().isStatic()) {
parameters[0] = field.getHolderType();
}
DexType returnType = isGet ? field.getType() : dexItemFactory.voidType;
DexProto proto = dexItemFactory.createProto(returnType, parameters);
return dexItemFactory.createMethod(
field.getHolderType(), proto, getFieldAccessBridgeName(field, isGet));
}
private DexString getFieldAccessBridgeName(DexClassAndField field, boolean isGet) {
String prefix;
if (isGet && !field.getAccessFlags().isStatic()) {
prefix = NEST_ACCESS_FIELD_GET_NAME_PREFIX;
} else if (isGet) {
prefix = NEST_ACCESS_STATIC_GET_FIELD_NAME_PREFIX;
} else if (!field.getAccessFlags().isStatic()) {
prefix = NEST_ACCESS_FIELD_PUT_NAME_PREFIX;
} else {
prefix = NEST_ACCESS_STATIC_PUT_FIELD_NAME_PREFIX;
}
return dexItemFactory.createString(prefix + field.getName().toString());
}
DexMethod ensureMethodBridge(DexClassAndMethod method, NestBridgeConsumer bridgeConsumer) {
if (method.isProgramMethod()) {
return ensureMethodBridge(method.asProgramMethod(), bridgeConsumer);
}
if (method.isClasspathMethod()) {
return getMethodBridgeReference(method);
}
assert method.isLibraryMethod();
throw reportIncompleteNest(method.asLibraryMethod());
}
private DexMethod ensureMethodBridge(ProgramMethod method, NestBridgeConsumer bridgeConsumer) {
DexMethod bridgeReference = getMethodBridgeReference(method);
synchronized (method.getHolder().getMethodCollection()) {
ProgramMethod bridge = method.getHolder().lookupProgramMethod(bridgeReference);
if (bridge == null) {
DexEncodedMethod definition = method.getDefinition();
bridge =
definition.isInstanceInitializer()
? definition.toInitializerForwardingBridge(
method.getHolder(), bridgeReference, dexItemFactory)
: definition.toStaticForwardingBridge(
method.getHolder(), bridgeReference, dexItemFactory);
bridge.getHolder().addDirectMethod(bridge.getDefinition());
if (bridgeConsumer != null) {
bridgeConsumer.acceptMethodBridge(method, bridge);
}
}
}
return bridgeReference;
}
private DexMethod getMethodBridgeReference(DexClassAndMethod method) {
if (method.getDefinition().isInstanceInitializer()) {
DexProto newProto =
dexItemFactory.appendTypeToProto(method.getProto(), dexItemFactory.nestConstructorType);
nestConstructorUsed = true;
return method.getReference().withProto(newProto, dexItemFactory);
}
DexProto proto =
method.getAccessFlags().isStatic()
? method.getProto()
: dexItemFactory.prependHolderToProto(method.getReference());
return dexItemFactory.createMethod(method.getHolderType(), proto, getMethodBridgeName(method));
}
private DexString getMethodBridgeName(DexClassAndMethod method) {
String prefix =
method.getAccessFlags().isStatic()
? NEST_ACCESS_STATIC_METHOD_NAME_PREFIX
: NEST_ACCESS_METHOD_NAME_PREFIX;
return dexItemFactory.createString(prefix + method.getName().toString());
}
}