blob: 37451f0be7f09f98fbb3479413c7358f4d0275b8 [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 static com.android.tools.r8.utils.ConsumerUtils.emptyConsumer;
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.cf.code.CfInvokeDynamic;
import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.Code;
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.DexEncodedMethod;
import com.android.tools.r8.graph.DexField;
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.LibraryMember;
import com.android.tools.r8.graph.ProgramField;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.ir.desugar.CfInstructionDesugaring;
import com.android.tools.r8.ir.desugar.CfInstructionDesugaringCollection;
import com.android.tools.r8.ir.desugar.CfInstructionDesugaringEventConsumer;
import com.android.tools.r8.ir.desugar.FreshLocalProvider;
import com.android.tools.r8.ir.desugar.LambdaDescriptor;
import com.android.tools.r8.ir.desugar.LocalStackAllocator;
import com.android.tools.r8.ir.desugar.ProgramAdditions;
import com.android.tools.r8.utils.BooleanUtils;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
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 implements CfInstructionDesugaring {
// 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";
protected final AppView<?> appView;
private final DexItemFactory dexItemFactory;
private final Map<DexType, DexType> syntheticNestConstructorTypes = new ConcurrentHashMap<>();
NestBasedAccessDesugaring(AppView<?> appView) {
this.appView = appView;
this.dexItemFactory = appView.dexItemFactory();
}
public static NestBasedAccessDesugaring create(AppView<?> appView) {
if (appView.options().shouldDesugarNests()) {
return appView.enableWholeProgramOptimizations()
? new NestBasedAccessDesugaring(appView)
: new D8NestBasedAccessDesugaring(appView);
}
return null;
}
void forEachNest(Consumer<Nest> consumer) {
forEachNest(consumer, emptyConsumer());
}
void forEachNest(Consumer<Nest> consumer, Consumer<DexClass> missingHostConsumer) {
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, missingHostConsumer);
if (nest != null) {
consumer.accept(nest);
}
}
}
private static class BridgeAndTarget<T extends DexClassAndMember<?, ?>> {
private final DexMethod bridge;
private final T target;
public BridgeAndTarget(DexMethod bridge, T target) {
this.bridge = bridge;
this.target = target;
assert bridge.holder == target.getHolderType();
}
public DexMethod getBridge() {
return bridge;
}
public T getTarget() {
return target;
}
public boolean shouldAddBridge() {
return target.isProgramMember() && target.getHolder().lookupDirectMethod(bridge) == null;
}
}
@Override
public void prepare(ProgramMethod method, ProgramAdditions programAdditions) {
method
.getDefinition()
.getCode()
.asCfCode()
.getInstructions()
.forEach(
instruction -> {
if (instruction.isFieldInstruction()) {
DexField field = instruction.asFieldInstruction().getField();
if (needsDesugaring(field, method)) {
prepareDesugarFieldInstruction(
field,
instruction.asFieldInstruction().isFieldGet(),
method,
programAdditions);
}
} else if (instruction.isInvoke()) {
DexMethod invokedMethod = instruction.asInvoke().getMethod();
if (needsDesugaring(invokedMethod, method)) {
prepareDesugarMethodInstruction(invokedMethod, method, programAdditions);
}
} else if (instruction.isInvokeDynamic()) {
// Starting from Java 17, lambda can use nest based access. We need to generate
// bridges for the targeted lambda method.
CfInvokeDynamic cfInvokeDynamic = instruction.asInvokeDynamic();
LambdaDescriptor lambdaDescriptor =
LambdaDescriptor.tryInfer(
cfInvokeDynamic.getCallSite(), appView.appInfoForDesugaring(), method);
if (lambdaDescriptor != null) {
DexMember<?, ?> member = lambdaDescriptor.implHandle.member;
if (needsDesugaring(member, method)) {
assert member.isDexMethod();
prepareDesugarMethodInstruction(member.asDexMethod(), method, programAdditions);
}
}
}
});
}
private void prepareDesugarFieldInstruction(
DexField field, boolean isGet, ProgramMethod context, ProgramAdditions programAdditions) {
BridgeAndTarget<DexClassAndField> bridgeAndTarget =
bridgeAndTargetForDesugaring(field, isGet, context);
if (bridgeAndTarget == null || !bridgeAndTarget.shouldAddBridge()) {
return;
}
programAdditions.accept(
bridgeAndTarget.getBridge(),
() ->
AccessBridgeFactory.createFieldAccessorBridge(
bridgeAndTarget.getBridge(), bridgeAndTarget.getTarget().asProgramField(), isGet));
}
private void prepareDesugarMethodInstruction(
DexMethod method, ProgramMethod context, ProgramAdditions programAdditions) {
BridgeAndTarget<DexClassAndMethod> bridgeAndTarget =
bridgeAndTargetForDesugaring(method, context);
if (bridgeAndTarget == null || !bridgeAndTarget.shouldAddBridge()) {
return;
}
programAdditions.accept(
bridgeAndTarget.getBridge(),
() ->
bridgeAndTarget.getTarget().getDefinition().isInstanceInitializer()
? AccessBridgeFactory.createInitializerAccessorBridge(
bridgeAndTarget.getBridge(),
bridgeAndTarget.getTarget().asProgramMethod(),
dexItemFactory)
: AccessBridgeFactory.createMethodAccessorBridge(
bridgeAndTarget.getBridge(),
bridgeAndTarget.getTarget().asProgramMethod(),
dexItemFactory));
}
private BridgeAndTarget<DexClassAndMethod> bridgeAndTargetForDesugaring(
DexMethod method, ProgramMethod context) {
if (!method.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(method, context);
DexClassAndMethod target = method.lookupMemberOnClass(holder);
if (target == null || !needsDesugaring(target, context)) {
return null;
}
return new BridgeAndTarget<>(getMethodBridgeReference(target), target);
}
private BridgeAndTarget<DexClassAndField> bridgeAndTargetForDesugaring(
DexField field, boolean isGet, ProgramMethod context) {
// 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(field, context);
DexClassAndField target = field.lookupMemberOnClass(holder);
if (target == null || !needsDesugaring(target, context)) {
return null;
}
return new BridgeAndTarget<>(getFieldAccessBridgeReference(target, isGet), target);
}
public boolean needsDesugaring(ProgramMethod method) {
if (!method.getHolder().isInANest() || !method.getDefinition().hasCode()) {
return false;
}
Code code = method.getDefinition().getCode();
if (code.isDexCode()) {
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));
}
@Override
public 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();
}
@Override
public Collection<CfInstruction> desugarInstruction(
CfInstruction instruction,
FreshLocalProvider freshLocalProvider,
LocalStackAllocator localStackAllocator,
CfInstructionDesugaringEventConsumer eventConsumer,
ProgramMethod context,
MethodProcessingContext methodProcessingContext,
CfInstructionDesugaringCollection desugaringCollection,
DexItemFactory dexItemFactory) {
if (instruction.isFieldInstruction()) {
return desugarFieldInstruction(instruction.asFieldInstruction(), context, eventConsumer);
}
if (instruction.isInvoke()) {
return desugarInvokeInstruction(
instruction.asInvoke(), localStackAllocator, context, eventConsumer);
}
return null;
}
private List<CfInstruction> desugarFieldInstruction(
CfFieldInstruction instruction,
ProgramMethod context,
NestBasedAccessDesugaringEventConsumer eventConsumer) {
BridgeAndTarget<DexClassAndField> bridgeAndTarget =
bridgeAndTargetForDesugaring(instruction.getField(), instruction.isFieldGet(), context);
if (bridgeAndTarget == null) {
return null;
}
// All bridges for program fields must have been added through the prepare step.
assert !bridgeAndTarget.getTarget().isProgramField()
|| bridgeAndTarget.getTarget().getHolder().lookupDirectMethod(bridgeAndTarget.getBridge())
!= null;
return ImmutableList.of(
new CfInvoke(
Opcodes.INVOKESTATIC,
bridgeAndTarget.getBridge(),
bridgeAndTarget.getTarget().getHolder().isInterface()));
}
private List<CfInstruction> desugarInvokeInstruction(
CfInvoke invoke,
LocalStackAllocator localStackAllocator,
ProgramMethod context,
NestBasedAccessDesugaringEventConsumer eventConsumer) {
DexMethod invokedMethod = invoke.getMethod();
BridgeAndTarget<DexClassAndMethod> bridgeAndTarget =
bridgeAndTargetForDesugaring(invokedMethod, context);
if (bridgeAndTarget == null) {
return null;
}
// All bridges for program methods must have been added through the prepare step.
assert !bridgeAndTarget.getTarget().isProgramMethod()
|| bridgeAndTarget.getTarget().getHolder().lookupDirectMethod(bridgeAndTarget.getBridge())
!= null;
if (bridgeAndTarget.getTarget().getDefinition().isInstanceInitializer()) {
assert !invoke.isInterface();
// Ensure room on the stack for the extra null argument.
localStackAllocator.allocateLocalStack(1);
return ImmutableList.of(
new CfConstNull(),
new CfInvoke(Opcodes.INVOKESPECIAL, bridgeAndTarget.getBridge(), false));
}
return ImmutableList.of(
new CfInvoke(Opcodes.INVOKESTATIC, bridgeAndTarget.getBridge(), 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().errorMissingNestMember(nest);
}
DexMethod ensureFieldAccessBridge(
DexClassAndField field, boolean isGet, NestBasedAccessDesugaringEventConsumer eventConsumer) {
if (field.isProgramField()) {
return ensureFieldAccessBridge(field.asProgramField(), isGet, eventConsumer);
}
if (field.isClasspathField()) {
return getFieldAccessBridgeReference(field, isGet);
}
assert field.isLibraryField();
throw reportIncompleteNest(field.asLibraryField());
}
private DexMethod ensureFieldAccessBridge(
ProgramField field, boolean isGet, NestBasedAccessDesugaringEventConsumer eventConsumer) {
DexMethod bridgeReference = getFieldAccessBridgeReference(field, isGet);
synchronized (field.getHolder().getMethodCollection()) {
ProgramMethod bridge = field.getHolder().lookupProgramMethod(bridgeReference);
if (bridge == null) {
bridge = AccessBridgeFactory.createFieldAccessorBridge(bridgeReference, field, isGet);
bridge.getHolder().addDirectMethod(bridge.getDefinition());
if (eventConsumer != null) {
if (isGet) {
eventConsumer.acceptNestFieldGetBridge(field, bridge);
} else {
eventConsumer.acceptNestFieldPutBridge(field, bridge);
}
}
}
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());
}
// This is only used for generating bridge methods for class path references.
DexMethod ensureMethodBridge(
DexClassAndMethod method, NestBasedAccessDesugaringEventConsumer eventConsumer) {
if (method.isProgramMethod()) {
return ensureMethodBridge(method.asProgramMethod(), eventConsumer);
}
if (method.isClasspathMethod()) {
return getMethodBridgeReference(method);
}
assert method.isLibraryMethod();
throw reportIncompleteNest(method.asLibraryMethod());
}
private DexMethod ensureMethodBridge(
ProgramMethod method, NestBasedAccessDesugaringEventConsumer eventConsumer) {
DexMethod bridgeReference = getMethodBridgeReference(method);
synchronized (method.getHolder().getMethodCollection()) {
ProgramMethod bridge = method.getHolder().lookupProgramMethod(bridgeReference);
if (bridge == null) {
DexEncodedMethod definition = method.getDefinition();
bridge =
definition.isInstanceInitializer()
? AccessBridgeFactory.createInitializerAccessorBridge(
bridgeReference, method, dexItemFactory)
: AccessBridgeFactory.createMethodAccessorBridge(
bridgeReference, method, dexItemFactory);
bridge.getHolder().addDirectMethod(bridge.getDefinition());
if (eventConsumer != null) {
eventConsumer.acceptNestMethodBridge(method, bridge);
}
}
}
return bridgeReference;
}
private DexMethod getMethodBridgeReference(DexClassAndMethod method) {
if (method.getDefinition().isInstanceInitializer()) {
DexType nestConstructorType =
syntheticNestConstructorTypes.computeIfAbsent(
method.getHolderType(),
holder -> {
if (method.isProgramMethod()) {
return appView
.getSyntheticItems()
.createFixedClass(
kinds -> kinds.INIT_TYPE_ARGUMENT,
method.asProgramMethod().getHolder(),
appView,
builder -> {})
.getType();
} else {
assert method.isClasspathMethod();
return appView
.getSyntheticItems()
.ensureFixedClasspathClass(
kinds -> kinds.INIT_TYPE_ARGUMENT,
method.asClasspathMethod().getHolder(),
appView,
ignored -> {},
ignored -> {})
.getType();
}
});
DexProto newProto = dexItemFactory.appendTypeToProto(method.getProto(), nestConstructorType);
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());
}
}