blob: d2f8e2d5919811062aef815af3ef5bdd8ae8681e [file] [log] [blame]
// Copyright (c) 2016, 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.graph;
import static com.android.tools.r8.graph.DexEncodedMethod.CompilationState.PROCESSED_INLINING_CANDIDATE_ANY;
import static com.android.tools.r8.graph.DexEncodedMethod.CompilationState.PROCESSED_INLINING_CANDIDATE_SAME_CLASS;
import static com.android.tools.r8.graph.DexEncodedMethod.CompilationState.PROCESSED_INLINING_CANDIDATE_SAME_PACKAGE;
import static com.android.tools.r8.graph.DexEncodedMethod.CompilationState.PROCESSED_INLINING_CANDIDATE_SUBCLASS;
import static com.android.tools.r8.graph.DexEncodedMethod.CompilationState.PROCESSED_NOT_INLINING_CANDIDATE;
import static com.android.tools.r8.graph.DexEncodedMethod.DefaultOptimizationInfoImpl.UNKNOWN_TYPE;
import com.android.tools.r8.OptionalBool;
import com.android.tools.r8.cf.code.CfConstNull;
import com.android.tools.r8.cf.code.CfConstString;
import com.android.tools.r8.cf.code.CfInstruction;
import com.android.tools.r8.cf.code.CfInvoke;
import com.android.tools.r8.cf.code.CfLoad;
import com.android.tools.r8.cf.code.CfNew;
import com.android.tools.r8.cf.code.CfStackInstruction;
import com.android.tools.r8.cf.code.CfStackInstruction.Opcode;
import com.android.tools.r8.cf.code.CfStore;
import com.android.tools.r8.cf.code.CfThrow;
import com.android.tools.r8.code.Const;
import com.android.tools.r8.code.ConstString;
import com.android.tools.r8.code.Instruction;
import com.android.tools.r8.code.InvokeDirect;
import com.android.tools.r8.code.InvokeStatic;
import com.android.tools.r8.code.NewInstance;
import com.android.tools.r8.code.Throw;
import com.android.tools.r8.dex.Constants;
import com.android.tools.r8.dex.IndexedItemCollection;
import com.android.tools.r8.dex.JumboStringRewriter;
import com.android.tools.r8.dex.MethodToCodeObjectMapping;
import com.android.tools.r8.dex.MixedSectionCollection;
import com.android.tools.r8.errors.InternalCompilerError;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AppInfo.ResolutionResult;
import com.android.tools.r8.graph.ParameterUsagesInfo.ParameterUsage;
import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.Invoke;
import com.android.tools.r8.ir.code.Position;
import com.android.tools.r8.ir.code.ValueNumberGenerator;
import com.android.tools.r8.ir.code.ValueType;
import com.android.tools.r8.ir.conversion.DexBuilder;
import com.android.tools.r8.ir.desugar.NestBasedAccessDesugaring.DexFieldWithAccess;
import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
import com.android.tools.r8.ir.optimize.Inliner.Reason;
import com.android.tools.r8.ir.regalloc.RegisterAllocator;
import com.android.tools.r8.ir.synthetic.FieldAccessorSourceCode;
import com.android.tools.r8.ir.synthetic.ForwardMethodSourceCode;
import com.android.tools.r8.ir.synthetic.SynthesizedCode;
import com.android.tools.r8.logging.Log;
import com.android.tools.r8.naming.ClassNameMapper;
import com.android.tools.r8.naming.MemberNaming.MethodSignature;
import com.android.tools.r8.naming.MemberNaming.Signature;
import com.android.tools.r8.naming.NamingLens;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.utils.InternalOptions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.BitSet;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.IntPredicate;
import org.objectweb.asm.Opcodes;
public class DexEncodedMethod extends KeyedDexItem<DexMethod> implements ResolutionResult {
public static final String CONFIGURATION_DEBUGGING_PREFIX = "Shaking error: Missing method in ";
/**
* Encodes the processing state of a method.
*
* <p>We also use this enum to encode under what constraints a method may be inlined.
*/
// TODO(b/128967328): Need to extend this to a state with the context.
public enum CompilationState {
/**
* Has not been processed, yet.
*/
NOT_PROCESSED,
/**
* Has been processed but cannot be inlined due to instructions that are not supported.
*/
PROCESSED_NOT_INLINING_CANDIDATE,
/**
* Code only contains instructions that access public entities and can this be inlined into any
* context.
*/
PROCESSED_INLINING_CANDIDATE_ANY,
/**
* Code also contains instructions that access protected entities that reside in a different
* package and hence require subclass relationship to be visible.
*/
PROCESSED_INLINING_CANDIDATE_SUBCLASS,
/**
* Code contains instructions that reference package private entities or protected entities from
* the same package.
*/
PROCESSED_INLINING_CANDIDATE_SAME_PACKAGE,
/**
* Code contains instructions that reference private entities.
*/
PROCESSED_INLINING_CANDIDATE_SAME_CLASS,
}
public static final DexEncodedMethod[] EMPTY_ARRAY = {};
public static final DexEncodedMethod SENTINEL =
new DexEncodedMethod(null, null, null, null, null);
public final DexMethod method;
public final MethodAccessFlags accessFlags;
public DexAnnotationSet annotations;
public ParameterAnnotationsList parameterAnnotationsList;
private Code code;
// TODO(b/128967328): towards finer-grained inlining constraints,
// we need to maintain a set of states with (potentially different) contexts.
private CompilationState compilationState = CompilationState.NOT_PROCESSED;
private OptimizationInfo optimizationInfo = DefaultOptimizationInfoImpl.DEFAULT_INSTANCE;
private int classFileVersion = -1;
private DexEncodedMethod defaultInterfaceMethodImplementation = null;
private OptionalBool isLibraryMethodOverride = OptionalBool.unknown();
// This flag indicates the current instance is no longer up-to-date as another instance was
// created based on this. Any further (public) operations on this instance will raise an error
// to catch potential bugs due to the inconsistency (e.g., http://b/111893131)
// Any newly added `public` method should check if `this` instance is obsolete.
private boolean obsolete = false;
private void checkIfObsolete() {
assert !obsolete;
}
public boolean isObsolete() {
// Do not be cheating. This util can be used only if you're going to do appropriate action,
// e.g., using GraphLense#mapDexEncodedMethod to look up the correct, up-to-date instance.
return obsolete;
}
public void setObsolete() {
// By assigning an Exception, you can see when/how this instance becomes obsolete v.s.
// when/how such obsolete instance is used.
obsolete = true;
}
public DexEncodedMethod getDefaultInterfaceMethodImplementation() {
return defaultInterfaceMethodImplementation;
}
public void setDefaultInterfaceMethodImplementation(DexEncodedMethod implementation) {
assert defaultInterfaceMethodImplementation == null;
assert implementation != null;
assert code != null;
assert code == implementation.getCode();
accessFlags.setAbstract();
removeCode();
defaultInterfaceMethodImplementation = implementation;
}
/**
* Flags this method as no longer being obsolete.
*
* Example use case: The vertical class merger optimistically merges two classes before it is
* guaranteed that the two classes can be merged. In this process, methods are moved from the
* source class to the target class using {@link #toTypeSubstitutedMethod(DexMethod)}, which
* causes the original methods of the source class to become obsolete. If vertical class merging
* is aborted, the original methods of the source class needs to be marked as not being obsolete.
*/
public void unsetObsolete() {
obsolete = false;
}
public DexEncodedMethod(
DexMethod method,
MethodAccessFlags accessFlags,
DexAnnotationSet annotations,
ParameterAnnotationsList parameterAnnotationsList,
Code code) {
this.method = method;
this.accessFlags = accessFlags;
this.annotations = annotations;
this.parameterAnnotationsList = parameterAnnotationsList;
this.code = code;
assert code == null || !shouldNotHaveCode();
}
public DexEncodedMethod(
DexMethod method,
MethodAccessFlags flags,
DexAnnotationSet annotationSet,
ParameterAnnotationsList annotationsList,
Code code,
int classFileVersion) {
this(method, flags, annotationSet, annotationsList, code);
this.classFileVersion = classFileVersion;
}
public OptionalBool isLibraryMethodOverride() {
return isLibraryMethodOverride;
}
public void setLibraryMethodOverride() {
assert isLibraryMethodOverride.isUnknown() || isLibraryMethodOverride.isTrue()
: "Method `"
+ method.toSourceString()
+ "` went from not overriding a library method to overriding a library method";
isLibraryMethodOverride = OptionalBool.of(true);
}
public boolean isProgramMethod(DexDefinitionSupplier definitions) {
if (method.holder.isClassType()) {
DexClass clazz = definitions.definitionFor(method.holder);
return clazz != null && clazz.isProgramClass();
}
return false;
}
public boolean isProcessed() {
checkIfObsolete();
return compilationState != CompilationState.NOT_PROCESSED;
}
public boolean isAbstract() {
return accessFlags.isAbstract();
}
public boolean isFinal() {
return accessFlags.isFinal();
}
public boolean isInitializer() {
checkIfObsolete();
return isInstanceInitializer() || isClassInitializer();
}
public boolean isInstanceInitializer() {
checkIfObsolete();
return accessFlags.isConstructor() && !accessFlags.isStatic();
}
public boolean isDefaultInitializer() {
checkIfObsolete();
return isInstanceInitializer() && method.proto.parameters.isEmpty();
}
public boolean isClassInitializer() {
checkIfObsolete();
return accessFlags.isConstructor() && accessFlags.isStatic();
}
/**
* Returns true if this method can be invoked via invoke-virtual, invoke-super or
* invoke-interface.
*/
public boolean isVirtualMethod() {
checkIfObsolete();
return !accessFlags.isStatic() && !accessFlags.isPrivate() && !accessFlags.isConstructor();
}
/**
* Returns true if this method can be invoked via invoke-virtual, invoke-super or invoke-interface
* and is non-abstract.
*/
public boolean isNonAbstractVirtualMethod() {
checkIfObsolete();
return isVirtualMethod() && !accessFlags.isAbstract();
}
public boolean isNonAbstractNonNativeMethod() {
checkIfObsolete();
return !accessFlags.isAbstract() && !accessFlags.isNative();
}
public boolean isPublicized() {
checkIfObsolete();
return accessFlags.isPromotedToPublic();
}
public boolean isPublicMethod() {
checkIfObsolete();
return accessFlags.isPublic();
}
public boolean isPrivateMethod() {
checkIfObsolete();
return accessFlags.isPrivate();
}
/**
* Returns true if this method can be invoked via invoke-direct.
*/
public boolean isDirectMethod() {
checkIfObsolete();
return (accessFlags.isPrivate() || accessFlags.isConstructor()) && !accessFlags.isStatic();
}
@Override
public boolean isStatic() {
checkIfObsolete();
return accessFlags.isStatic();
}
@Override
public boolean isStaticMember() {
checkIfObsolete();
return isStatic();
}
/**
* Returns true if this method is synthetic.
*/
public boolean isSyntheticMethod() {
checkIfObsolete();
return accessFlags.isSynthetic();
}
public boolean isInliningCandidate(
DexEncodedMethod container, Reason inliningReason, AppInfoWithSubtyping appInfo) {
checkIfObsolete();
return isInliningCandidate(container.method.holder, inliningReason, appInfo);
}
public boolean isInliningCandidate(
DexType containerType, Reason inliningReason, AppInfoWithSubtyping appInfo) {
checkIfObsolete();
if (isClassInitializer()) {
// This will probably never happen but never inline a class initializer.
return false;
}
if (inliningReason == Reason.FORCE) {
// Make sure we would be able to inline this normally.
if (!isInliningCandidate(containerType, Reason.SIMPLE, appInfo)) {
// If not, raise a flag, because some optimizations that depend on force inlining would
// silently produce an invalid code, which is worse than an internal error.
throw new InternalCompilerError("FORCE inlining on non-inlinable: " + toSourceString());
}
return true;
}
// TODO(b/128967328): inlining candidate should satisfy all states if multiple states are there.
switch (compilationState) {
case PROCESSED_INLINING_CANDIDATE_ANY:
return true;
case PROCESSED_INLINING_CANDIDATE_SUBCLASS:
return appInfo.isSubtype(containerType, method.holder);
case PROCESSED_INLINING_CANDIDATE_SAME_PACKAGE:
return containerType.isSamePackage(method.holder);
case PROCESSED_INLINING_CANDIDATE_SAME_CLASS:
return containerType == method.holder;
default:
return false;
}
}
public boolean markProcessed(ConstraintWithTarget state) {
checkIfObsolete();
CompilationState prevCompilationState = compilationState;
switch (state.constraint) {
case ALWAYS:
compilationState = PROCESSED_INLINING_CANDIDATE_ANY;
break;
case SUBCLASS:
compilationState = PROCESSED_INLINING_CANDIDATE_SUBCLASS;
break;
case PACKAGE:
compilationState = PROCESSED_INLINING_CANDIDATE_SAME_PACKAGE;
break;
case SAMECLASS:
compilationState = PROCESSED_INLINING_CANDIDATE_SAME_CLASS;
break;
case NEVER:
compilationState = PROCESSED_NOT_INLINING_CANDIDATE;
break;
}
return prevCompilationState != compilationState;
}
public void markNotProcessed() {
checkIfObsolete();
compilationState = CompilationState.NOT_PROCESSED;
}
public IRCode buildIR(AppView<?> appView, Origin origin) {
checkIfObsolete();
return code == null ? null : code.buildIR(this, appView, origin);
}
public IRCode buildInliningIR(
DexEncodedMethod context,
AppView<?> appView,
ValueNumberGenerator valueNumberGenerator,
Position callerPosition,
Origin origin) {
checkIfObsolete();
return code.buildInliningIR(
context, this, appView, valueNumberGenerator, callerPosition, origin);
}
public void setCode(Code code) {
checkIfObsolete();
this.code = code;
}
public void setCode(IRCode ir, RegisterAllocator registerAllocator, InternalOptions options) {
checkIfObsolete();
final DexBuilder builder = new DexBuilder(ir, registerAllocator);
setCode(builder.build());
}
@Override
public String toString() {
checkIfObsolete();
return "Encoded method " + method;
}
@Override
public void collectIndexedItems(IndexedItemCollection indexedItems,
DexMethod method, int instructionOffset) {
checkIfObsolete();
this.method.collectIndexedItems(indexedItems);
if (code != null) {
code.collectIndexedItems(indexedItems, this.method);
}
annotations.collectIndexedItems(indexedItems);
parameterAnnotationsList.collectIndexedItems(indexedItems);
}
@Override
void collectMixedSectionItems(MixedSectionCollection mixedItems) {
mixedItems.visit(this);
}
public void collectMixedSectionItemsWithCodeMapping(
MixedSectionCollection mixedItems, MethodToCodeObjectMapping mapping) {
DexCode code = mapping.getCode(this);
if (code != null) {
code.collectMixedSectionItems(mixedItems);
}
annotations.collectMixedSectionItems(mixedItems);
parameterAnnotationsList.collectMixedSectionItems(mixedItems);
}
public boolean shouldNotHaveCode() {
return accessFlags.isAbstract() || accessFlags.isNative();
}
public boolean hasCode() {
return code != null;
}
public Code getCode() {
checkIfObsolete();
return code;
}
public void removeCode() {
checkIfObsolete();
code = null;
}
public int getClassFileVersion() {
checkIfObsolete();
assert classFileVersion >= 0;
return classFileVersion;
}
public boolean hasClassFileVersion() {
checkIfObsolete();
return classFileVersion >= 0;
}
public void upgradeClassFileVersion(int version) {
checkIfObsolete();
assert version >= 0;
assert !hasClassFileVersion() || version >= getClassFileVersion();
classFileVersion = version;
}
public String qualifiedName() {
checkIfObsolete();
return method.qualifiedName();
}
public String descriptor() {
checkIfObsolete();
return descriptor(NamingLens.getIdentityLens());
}
public String descriptor(NamingLens namingLens) {
checkIfObsolete();
StringBuilder builder = new StringBuilder();
builder.append("(");
for (DexType type : method.proto.parameters.values) {
builder.append(namingLens.lookupDescriptor(type).toString());
}
builder.append(")");
builder.append(namingLens.lookupDescriptor(method.proto.returnType).toString());
return builder.toString();
}
public String toSmaliString(ClassNameMapper naming) {
checkIfObsolete();
StringBuilder builder = new StringBuilder();
builder.append(".method ");
builder.append(accessFlags.toSmaliString());
builder.append(" ");
builder.append(method.name.toSmaliString());
builder.append(method.proto.toSmaliString());
builder.append("\n");
if (code != null) {
DexCode dexCode = code.asDexCode();
builder.append(" .registers ");
builder.append(dexCode.registerSize);
builder.append("\n\n");
builder.append(dexCode.toSmaliString(naming));
}
builder.append(".end method\n");
return builder.toString();
}
@Override
public String toSourceString() {
checkIfObsolete();
return method.toSourceString();
}
public DexEncodedMethod toAbstractMethod() {
checkIfObsolete();
// 'final' wants this to be *not* overridden, while 'abstract' wants this to be implemented in
// a subtype, i.e., self contradict.
assert !accessFlags.isFinal();
accessFlags.setAbstract();
this.code = null;
return this;
}
public IRCode buildEmptyThrowingIRCode(AppView<?> appView, Origin origin) {
DexCode emptyThrowingDexCode = buildEmptyThrowingDexCode();
return emptyThrowingDexCode.buildIR(this, appView, origin);
}
/**
* Generates a {@link DexCode} object for the given instructions.
*/
private DexCode generateCodeFromTemplate(
int numberOfRegisters, int outRegisters, Instruction... instructions) {
int offset = 0;
for (Instruction instruction : instructions) {
instruction.setOffset(offset);
offset += instruction.getSize();
}
int requiredArgRegisters = accessFlags.isStatic() ? 0 : 1;
for (DexType type : method.proto.parameters.values) {
requiredArgRegisters += ValueType.fromDexType(type).requiredRegisters();
}
return new DexCode(
Math.max(numberOfRegisters, requiredArgRegisters),
requiredArgRegisters,
outRegisters,
instructions,
new DexCode.Try[0],
new DexCode.TryHandler[0],
null);
}
public DexCode buildEmptyThrowingDexCode() {
Instruction insn[] = {new Const(0, 0), new Throw(0)};
return generateCodeFromTemplate(1, 0, insn);
}
public DexEncodedMethod toEmptyThrowingMethod(InternalOptions options) {
return options.isGeneratingClassFiles()
? toEmptyThrowingMethodCf()
: toEmptyThrowingMethodDex();
}
public DexEncodedMethod toEmptyThrowingMethodDex() {
checkIfObsolete();
assert !shouldNotHaveCode();
Builder builder = builder(this);
builder.setCode(buildEmptyThrowingDexCode());
// Note that we are not marking this instance obsolete, since this util is only used by
// TreePruner while keeping non-live yet targeted, empty method. Such method can be retrieved
// again only during the 2nd round of tree sharking, and seeing an obsolete empty body v.s.
// seeing this empty throwing code do not matter.
// If things are changed, the cure point is obsolete instances inside RootSet.
return builder.build();
}
public CfCode buildEmptyThrowingCfCode() {
CfInstruction insn[] = {new CfConstNull(), new CfThrow()};
return new CfCode(
1,
method.proto.parameters.size() + 1,
Arrays.asList(insn),
Collections.emptyList(),
Collections.emptyList());
}
public DexEncodedMethod toEmptyThrowingMethodCf() {
checkIfObsolete();
assert !shouldNotHaveCode();
Builder builder = builder(this);
builder.setCode(buildEmptyThrowingCfCode());
// Note that we are not marking this instance obsolete:
// refer to Dex-backend version of this method above.
return builder.build();
}
public DexEncodedMethod toMethodThatLogsError(AppView<?> appView) {
if (appView.options().isGeneratingDex()) {
return toMethodThatLogsErrorDexCode(appView.dexItemFactory());
} else {
return toMethodThatLogsErrorCfCode(appView.dexItemFactory());
}
}
private DexEncodedMethod toMethodThatLogsErrorDexCode(DexItemFactory itemFactory) {
checkIfObsolete();
Signature signature = MethodSignature.fromDexMethod(method);
DexString message =
itemFactory.createString(
CONFIGURATION_DEBUGGING_PREFIX + method.holder.toSourceString() + ": " + signature);
DexString tag = itemFactory.createString("[R8]");
DexType[] args = {itemFactory.stringType, itemFactory.stringType};
DexProto proto = itemFactory.createProto(itemFactory.intType, args);
DexMethod logMethod =
itemFactory.createMethod(
itemFactory.createType("Landroid/util/Log;"), proto, itemFactory.createString("e"));
DexType exceptionType = itemFactory.createType("Ljava/lang/RuntimeException;");
DexMethod exceptionInitMethod =
itemFactory.createMethod(
exceptionType,
itemFactory.createProto(itemFactory.voidType, itemFactory.stringType),
itemFactory.constructorMethodName);
DexCode code =
generateCodeFromTemplate(
2,
2,
new ConstString(0, tag),
new ConstString(1, message),
new InvokeStatic(2, logMethod, 0, 1, 0, 0, 0),
new NewInstance(0, exceptionType),
new InvokeDirect(2, exceptionInitMethod, 0, 1, 0, 0, 0),
new Throw(0));
Builder builder = builder(this);
builder.setCode(code);
setObsolete();
return builder.build();
}
private DexEncodedMethod toMethodThatLogsErrorCfCode(DexItemFactory itemFactory) {
checkIfObsolete();
Signature signature = MethodSignature.fromDexMethod(method);
DexString message =
itemFactory.createString(
CONFIGURATION_DEBUGGING_PREFIX + method.holder.toSourceString() + ": " + signature);
DexString tag = itemFactory.createString("[R8]");
DexType logger = itemFactory.createType("Ljava/util/logging/Logger;");
DexMethod getLogger =
itemFactory.createMethod(
logger,
itemFactory.createProto(logger, itemFactory.stringType),
itemFactory.createString("getLogger"));
DexMethod severe =
itemFactory.createMethod(
logger,
itemFactory.createProto(itemFactory.voidType, itemFactory.stringType),
itemFactory.createString("severe"));
DexType exceptionType = itemFactory.createType("Ljava/lang/RuntimeException;");
DexMethod exceptionInitMethod =
itemFactory.createMethod(
exceptionType,
itemFactory.createProto(itemFactory.voidType, itemFactory.stringType),
itemFactory.constructorMethodName);
int locals = method.proto.parameters.size() + 1;
if (!isStaticMember()) {
// Consider `this` pointer
locals++;
}
ImmutableList.Builder<CfInstruction> instructionBuilder = ImmutableList.builder();
instructionBuilder
.add(new CfConstString(tag))
.add(new CfInvoke(Opcodes.INVOKESTATIC, getLogger, false))
.add(new CfStore(ValueType.OBJECT, locals - 1))
.add(new CfLoad(ValueType.OBJECT, locals - 1))
.add(new CfConstString(message))
.add(new CfInvoke(Opcodes.INVOKEVIRTUAL, severe, false))
.add(new CfNew(exceptionType))
.add(new CfStackInstruction(Opcode.Dup))
.add(new CfConstString(message))
.add(new CfInvoke(Opcodes.INVOKESPECIAL, exceptionInitMethod, false))
.add(new CfThrow());
CfCode code =
new CfCode(
3,
locals,
instructionBuilder.build(),
Collections.emptyList(),
Collections.emptyList());
Builder builder = builder(this);
builder.setCode(code);
setObsolete();
return builder.build();
}
public DexEncodedMethod toTypeSubstitutedMethod(DexMethod method) {
checkIfObsolete();
return toTypeSubstitutedMethod(method, null);
}
public DexEncodedMethod toTypeSubstitutedMethod(DexMethod method, Consumer<Builder> consumer) {
checkIfObsolete();
if (this.method == method) {
return this;
}
Builder builder = builder(this);
builder.setMethod(method);
// TODO(b/112847660): Fix type fixers that use this method: Class staticizer
// TODO(b/112847660): Fix type fixers that use this method: Uninstantiated type optimization
// TODO(b/112847660): Fix type fixers that use this method: Unused argument removal
// TODO(b/112847660): Fix type fixers that use this method: Vertical class merger
// setObsolete();
if (consumer != null) {
consumer.accept(builder);
}
return builder.build();
}
public DexEncodedMethod toRenamedMethod(DexString name, DexItemFactory factory) {
checkIfObsolete();
if (method.name == name) {
return this;
}
DexMethod newMethod = factory.createMethod(method.holder, method.proto, name);
Builder builder = builder(this);
builder.setMethod(newMethod);
setObsolete();
return builder.build();
}
public DexEncodedMethod toInitializerForwardingBridge(
DexClass holder, DexDefinitionSupplier definitions, DexProgramClass nestConstructor) {
assert accessFlags.isPrivate()
: "Expected to create bridge for private constructor as part of nest-based access"
+ " desugaring";
DexProto newProto =
definitions.dexItemFactory().appendTypeToProto(method.proto, nestConstructor.type);
DexMethod newMethod =
definitions.dexItemFactory().createMethod(method.holder, newProto, method.name);
Builder builder = builder(this);
builder.setMethod(newMethod);
builder.setCode(
new SynthesizedCode(
callerPosition ->
new ForwardMethodSourceCode(
holder.type,
newMethod,
newMethod,
holder.type,
method,
Invoke.Type.DIRECT,
callerPosition,
holder.isInterface(),
false,
true),
registry -> registry.registerInvokeDirect(method)));
assert !builder.accessFlags.isStatic();
builder.accessFlags.unsetPrivate();
builder.accessFlags.setSynthetic();
builder.accessFlags.setConstructor();
return builder.build();
}
public static DexEncodedMethod createFieldAccessorBridge(
DexFieldWithAccess fieldWithAccess,
DexClass holder,
DexDefinitionSupplier definitions,
DexString newName) {
assert holder.type == fieldWithAccess.getHolder();
DexItemFactory dexItemFactory = definitions.dexItemFactory();
DexType[] parameters = new DexType[fieldWithAccess.bridgeParameterCount()];
if (fieldWithAccess.isPut()) {
parameters[parameters.length - 1] = fieldWithAccess.getType();
}
if (fieldWithAccess.isInstance()) {
parameters[0] = holder.type;
}
DexType returnType =
fieldWithAccess.isGet() ? fieldWithAccess.getType() : dexItemFactory.voidType;
DexProto proto = dexItemFactory.createProto(returnType, parameters);
DexMethod newMethod = dexItemFactory.createMethod(holder.type, proto, newName);
MethodAccessFlags accessFlags =
MethodAccessFlags.fromSharedAccessFlags(
Constants.ACC_SYNTHETIC
| Constants.ACC_STATIC
| (holder.isInterface() ? Constants.ACC_PUBLIC : 0),
false);
Code code =
new SynthesizedCode(
callerPosition ->
new FieldAccessorSourceCode(
null, newMethod, callerPosition, newMethod, fieldWithAccess),
registry -> {
if (fieldWithAccess.isInstanceGet()) {
registry.registerInstanceFieldRead(fieldWithAccess.getField());
} else if (fieldWithAccess.isStaticGet()) {
registry.registerStaticFieldRead(fieldWithAccess.getField());
} else if (fieldWithAccess.isInstancePut()) {
registry.registerInstanceFieldWrite(fieldWithAccess.getField());
} else {
assert fieldWithAccess.isStaticPut();
registry.registerStaticFieldWrite(fieldWithAccess.getField());
}
});
return new DexEncodedMethod(
newMethod, accessFlags, DexAnnotationSet.empty(), ParameterAnnotationsList.empty(), code);
}
public DexEncodedMethod toStaticForwardingBridge(
DexClass holder, DexDefinitionSupplier definitions, DexString newName) {
assert accessFlags.isPrivate()
: "Expected to create bridge for private method as part of nest-based access desugaring";
DexProto proto =
accessFlags.isStatic()
? method.proto
: definitions.dexItemFactory().prependTypeToProto(holder.type, method.proto);
DexMethod newMethod = definitions.dexItemFactory().createMethod(holder.type, proto, newName);
Builder builder = builder(this);
builder.setMethod(newMethod);
builder.setCode(
new SynthesizedCode(
callerPosition ->
new ForwardMethodSourceCode(
null,
newMethod,
newMethod,
accessFlags.isStatic() ? null : method.holder,
method,
accessFlags.isStatic() ? Invoke.Type.STATIC : Invoke.Type.DIRECT,
callerPosition,
holder.isInterface()),
registry -> {
if (accessFlags.isStatic()) {
registry.registerInvokeStatic(method);
} else {
registry.registerInvokeDirect(method);
}
}));
builder.accessFlags.setSynthetic();
builder.accessFlags.setStatic();
builder.accessFlags.unsetPrivate();
if (holder.isInterface()) {
builder.accessFlags.setPublic();
}
return builder.build();
}
public DexEncodedMethod toForwardingMethod(DexClass holder, DexDefinitionSupplier definitions) {
checkIfObsolete();
// Clear the final flag, as this method is now overwritten. Do this before creating the builder
// for the forwarding method, as the forwarding method will copy the access flags from this,
// and if different forwarding methods are created in different subclasses the first could be
// final.
accessFlags.demoteFromFinal();
DexMethod newMethod =
definitions.dexItemFactory().createMethod(holder.type, method.proto, method.name);
Invoke.Type type = accessFlags.isStatic() ? Invoke.Type.STATIC : Invoke.Type.SUPER;
Builder builder = builder(this);
builder.setMethod(newMethod);
if (accessFlags.isAbstract()) {
// If the forwarding target is abstract, we can just create an abstract method. While it
// will not actually forward, it will create the same exception when hit at runtime.
builder.accessFlags.setAbstract();
} else {
// Create code that forwards the call to the target.
DexClass target = definitions.definitionFor(method.holder);
builder.setCode(
new SynthesizedCode(
callerPosition ->
new ForwardMethodSourceCode(
accessFlags.isStatic() ? null : holder.type,
newMethod,
newMethod,
accessFlags.isStatic() ? null : method.holder,
method,
type,
callerPosition,
target.isInterface()),
registry -> {
if (accessFlags.isStatic()) {
registry.registerInvokeStatic(method);
} else {
registry.registerInvokeSuper(method);
}
}));
builder.accessFlags.setBridge();
}
builder.accessFlags.setSynthetic();
// Note that we are not marking this instance obsolete, since it is not: the newly synthesized
// forwarding method has a separate code that literally forwards to the current method.
return builder.build();
}
public DexEncodedMethod toStaticMethodWithoutThis() {
checkIfObsolete();
assert !accessFlags.isStatic();
Builder builder =
builder(this).promoteToStatic().unsetOptimizationInfo().withoutThisParameter();
DexEncodedMethod method = builder.build();
method.copyMetadata(this);
setObsolete();
return method;
}
/** Rewrites the code in this method to have JumboString bytecode if required by mapping. */
public DexCode rewriteCodeWithJumboStrings(
ObjectToOffsetMapping mapping, DexItemFactory factory, boolean force) {
checkIfObsolete();
assert code == null || code.isDexCode();
if (code == null) {
return null;
}
DexCode code = this.code.asDexCode();
DexString firstJumboString = null;
if (force) {
firstJumboString = mapping.getFirstString();
} else {
assert code.highestSortingString != null
|| Arrays.stream(code.instructions).noneMatch(Instruction::isConstString);
assert Arrays.stream(code.instructions).noneMatch(Instruction::isDexItemBasedConstString);
if (code.highestSortingString != null
&& mapping.getOffsetFor(code.highestSortingString) > Constants.MAX_NON_JUMBO_INDEX) {
firstJumboString = mapping.getFirstJumboString();
}
}
if (firstJumboString != null) {
JumboStringRewriter rewriter = new JumboStringRewriter(this, firstJumboString, factory);
return rewriter.rewrite();
}
return code;
}
public String codeToString() {
checkIfObsolete();
return code == null ? "<no code>" : code.toString(this, null);
}
@Override
public DexMethod getKey() {
// Here, we can't check if the current instance of DexEncodedMethod is obsolete
// because itself can be used as a key while making mappings to avoid using obsolete instances.
return method;
}
@Override
public DexReference toReference() {
checkIfObsolete();
return method;
}
@Override
public boolean isDexEncodedMethod() {
checkIfObsolete();
return true;
}
@Override
public DexEncodedMethod asDexEncodedMethod() {
checkIfObsolete();
return this;
}
public boolean hasAnnotation() {
checkIfObsolete();
return !annotations.isEmpty() || !parameterAnnotationsList.isEmpty();
}
public void registerCodeReferences(UseRegistry registry) {
checkIfObsolete();
if (code != null) {
if (Log.ENABLED) {
Log.verbose(getClass(), "Registering definitions reachable from `%s`.", method);
}
code.registerCodeReferences(this, registry);
}
}
public static int slowCompare(DexEncodedMethod m1, DexEncodedMethod m2) {
return m1.method.slowCompareTo(m2.method);
}
public static class ClassInlinerEligibility {
public final boolean returnsReceiver;
public ClassInlinerEligibility(boolean returnsReceiver) {
this.returnsReceiver = returnsReceiver;
}
}
public static class TrivialInitializer {
private TrivialInitializer() {
}
// Defines instance trivial initialized, see details in comments
// to CodeRewriter::computeInstanceInitializerInfo(...)
public static final class TrivialInstanceInitializer extends TrivialInitializer {
public static final TrivialInstanceInitializer INSTANCE =
new TrivialInstanceInitializer();
}
// Defines class trivial initialized, see details in comments
// to CodeRewriter::computeClassInitializerInfo(...)
public static final class TrivialClassInitializer extends TrivialInitializer {
public final DexField field;
public TrivialClassInitializer(DexField field) {
this.field = field;
}
}
}
public static class DefaultOptimizationInfoImpl implements OptimizationInfo {
public static final OptimizationInfo DEFAULT_INSTANCE = new DefaultOptimizationInfoImpl();
public static Set<DexType> UNKNOWN_INITIALIZED_CLASSES_ON_NORMAL_EXIT = ImmutableSet.of();
public static int UNKNOWN_RETURNED_ARGUMENT = -1;
public static boolean UNKNOWN_NEVER_RETURNS_NULL = false;
public static boolean UNKNOWN_NEVER_RETURNS_NORMALLY = false;
public static boolean UNKNOWN_RETURNS_CONSTANT = false;
public static long UNKNOWN_RETURNED_CONSTANT_NUMBER = 0;
public static DexString UNKNOWN_RETURNED_CONSTANT_STRING = null;
public static TypeLatticeElement UNKNOWN_TYPE = null;
public static boolean DOES_NOT_USE_IDNETIFIER_NAME_STRING = false;
public static boolean UNKNOWN_CHECKS_NULL_RECEIVER_BEFORE_ANY_SIDE_EFFECT = false;
public static boolean UNKNOWN_TRIGGERS_CLASS_INIT_BEFORE_ANY_SIDE_EFFECT = false;
public static ClassInlinerEligibility UNKNOWN_CLASS_INLINER_ELIGIBILITY = null;
public static TrivialInitializer UNKNOWN_TRIVIAL_INITIALIZER = null;
public static boolean UNKNOWN_INITIALIZER_ENABLING_JAVA_ASSERTIONS = false;
public static ParameterUsagesInfo UNKNOWN_PARAMETER_USAGE_INFO = null;
public static boolean UNKNOWN_MAY_HAVE_SIDE_EFFECTS = true;
public static BitSet NO_NULL_PARAMETER_OR_THROW_FACTS = null;
public static BitSet NO_NULL_PARAMETER_ON_NORMAL_EXITS_FACTS = null;
private DefaultOptimizationInfoImpl() {}
@Override
public TypeLatticeElement getDynamicReturnType() {
return UNKNOWN_TYPE;
}
@Override
public Set<DexType> getInitializedClassesOnNormalExit() {
return UNKNOWN_INITIALIZED_CLASSES_ON_NORMAL_EXIT;
}
@Override
public TrivialInitializer getTrivialInitializerInfo() {
return UNKNOWN_TRIVIAL_INITIALIZER;
}
@Override
public ParameterUsage getParameterUsages(int parameter) {
assert UNKNOWN_PARAMETER_USAGE_INFO == null;
return null;
}
@Override
public BitSet getNonNullParamOrThrow() {
return NO_NULL_PARAMETER_OR_THROW_FACTS;
}
@Override
public BitSet getNonNullParamOnNormalExits() {
return NO_NULL_PARAMETER_ON_NORMAL_EXITS_FACTS;
}
@Override
public boolean isReachabilitySensitive() {
return false;
}
@Override
public boolean returnsArgument() {
return false;
}
@Override
public int getReturnedArgument() {
assert returnsArgument();
return UNKNOWN_RETURNED_ARGUMENT;
}
@Override
public boolean neverReturnsNull() {
return UNKNOWN_NEVER_RETURNS_NULL;
}
@Override
public boolean neverReturnsNormally() {
return UNKNOWN_NEVER_RETURNS_NORMALLY;
}
@Override
public boolean returnsConstant() {
return UNKNOWN_RETURNS_CONSTANT;
}
@Override
public boolean returnsConstantNumber() {
return UNKNOWN_RETURNS_CONSTANT;
}
@Override
public boolean returnsConstantString() {
return UNKNOWN_RETURNS_CONSTANT;
}
@Override
public ClassInlinerEligibility getClassInlinerEligibility() {
return UNKNOWN_CLASS_INLINER_ELIGIBILITY;
}
@Override
public long getReturnedConstantNumber() {
assert returnsConstantNumber();
return UNKNOWN_RETURNED_CONSTANT_NUMBER;
}
@Override
public DexString getReturnedConstantString() {
assert returnsConstantString();
return UNKNOWN_RETURNED_CONSTANT_STRING;
}
@Override
public boolean isInitializerEnablingJavaAssertions() {
return UNKNOWN_INITIALIZER_ENABLING_JAVA_ASSERTIONS;
}
@Override
public boolean useIdentifierNameString() {
return DOES_NOT_USE_IDNETIFIER_NAME_STRING;
}
@Override
public boolean forceInline() {
return false;
}
@Override
public boolean neverInline() {
return false;
}
@Override
public boolean checksNullReceiverBeforeAnySideEffect() {
return UNKNOWN_CHECKS_NULL_RECEIVER_BEFORE_ANY_SIDE_EFFECT;
}
@Override
public boolean triggersClassInitBeforeAnySideEffect() {
return UNKNOWN_TRIGGERS_CLASS_INIT_BEFORE_ANY_SIDE_EFFECT;
}
@Override
public boolean mayHaveSideEffects() {
return UNKNOWN_MAY_HAVE_SIDE_EFFECTS;
}
@Override
public UpdatableOptimizationInfo mutableCopy() {
return new OptimizationInfoImpl();
}
}
public static class OptimizationInfoImpl implements UpdatableOptimizationInfo {
private Set<DexType> initializedClassesOnNormalExit =
DefaultOptimizationInfoImpl.UNKNOWN_INITIALIZED_CLASSES_ON_NORMAL_EXIT;
private int returnedArgument = DefaultOptimizationInfoImpl.UNKNOWN_RETURNED_ARGUMENT;
private boolean mayHaveSideEffects = DefaultOptimizationInfoImpl.UNKNOWN_MAY_HAVE_SIDE_EFFECTS;
private boolean neverReturnsNull = DefaultOptimizationInfoImpl.UNKNOWN_NEVER_RETURNS_NULL;
private boolean neverReturnsNormally =
DefaultOptimizationInfoImpl.UNKNOWN_NEVER_RETURNS_NORMALLY;
private boolean returnsConstantNumber = DefaultOptimizationInfoImpl.UNKNOWN_RETURNS_CONSTANT;
private long returnedConstantNumber =
DefaultOptimizationInfoImpl.UNKNOWN_RETURNED_CONSTANT_NUMBER;
private boolean returnsConstantString = DefaultOptimizationInfoImpl.UNKNOWN_RETURNS_CONSTANT;
private DexString returnedConstantString =
DefaultOptimizationInfoImpl.UNKNOWN_RETURNED_CONSTANT_STRING;
private TypeLatticeElement returnsObjectOfType = UNKNOWN_TYPE;
private InlinePreference inlining = InlinePreference.Default;
private boolean useIdentifierNameString =
DefaultOptimizationInfoImpl.DOES_NOT_USE_IDNETIFIER_NAME_STRING;
private boolean checksNullReceiverBeforeAnySideEffect =
DefaultOptimizationInfoImpl.UNKNOWN_CHECKS_NULL_RECEIVER_BEFORE_ANY_SIDE_EFFECT;
private boolean triggersClassInitBeforeAnySideEffect =
DefaultOptimizationInfoImpl.UNKNOWN_TRIGGERS_CLASS_INIT_BEFORE_ANY_SIDE_EFFECT;
// Stores information about instance methods and constructors for
// class inliner, null value indicates that the method is not eligible.
private ClassInlinerEligibility classInlinerEligibility =
DefaultOptimizationInfoImpl.UNKNOWN_CLASS_INLINER_ELIGIBILITY;
private TrivialInitializer trivialInitializerInfo =
DefaultOptimizationInfoImpl.UNKNOWN_TRIVIAL_INITIALIZER;
private boolean initializerEnablingJavaAssertions =
DefaultOptimizationInfoImpl.UNKNOWN_INITIALIZER_ENABLING_JAVA_ASSERTIONS;
private ParameterUsagesInfo parametersUsages =
DefaultOptimizationInfoImpl.UNKNOWN_PARAMETER_USAGE_INFO;
// Stores information about nullability hint per parameter. If set, that means, the method
// somehow (e.g., null check, such as arg != null, or using checkParameterIsNotNull) ensures
// the corresponding parameter is not null, or throws NPE before any other side effects.
// This info is used by {@link UninstantiatedTypeOptimization#rewriteInvoke} that replaces an
// invocation with null throwing code if an always-null argument is passed. Also used by Inliner
// to give a credit to null-safe code, e.g., Kotlin's null safe argument.
// Note that this bit set takes into account the receiver for instance methods.
private BitSet nonNullParamOrThrow = null;
// Stores information about nullability facts per parameter. If set, that means, the method
// somehow (e.g., null check, such as arg != null, or NPE-throwing instructions such as array
// access or another invocation) ensures the corresponding parameter is not null, and that is
// guaranteed until the normal exits. That is, if the invocation of this method is finished
// normally, the recorded parameter is definitely not null. These facts are used to propagate
// non-null information through {@link NonNullTracker}.
// Note that this bit set takes into account the receiver for instance methods.
private BitSet nonNullParamOnNormalExits = null;
private boolean reachabilitySensitive = false;
private OptimizationInfoImpl() {
// Intentionally left empty, just use the default values.
}
private OptimizationInfoImpl(OptimizationInfoImpl template) {
returnedArgument = template.returnedArgument;
neverReturnsNull = template.neverReturnsNull;
neverReturnsNormally = template.neverReturnsNormally;
returnsConstantNumber = template.returnsConstantNumber;
returnedConstantNumber = template.returnedConstantNumber;
returnsConstantString = template.returnsConstantString;
returnedConstantString = template.returnedConstantString;
inlining = template.inlining;
useIdentifierNameString = template.useIdentifierNameString;
checksNullReceiverBeforeAnySideEffect = template.checksNullReceiverBeforeAnySideEffect;
triggersClassInitBeforeAnySideEffect = template.triggersClassInitBeforeAnySideEffect;
classInlinerEligibility = template.classInlinerEligibility;
trivialInitializerInfo = template.trivialInitializerInfo;
initializerEnablingJavaAssertions = template.initializerEnablingJavaAssertions;
parametersUsages = template.parametersUsages;
nonNullParamOrThrow = template.nonNullParamOrThrow;
nonNullParamOnNormalExits = template.nonNullParamOnNormalExits;
reachabilitySensitive = template.reachabilitySensitive;
}
@Override
public TypeLatticeElement getDynamicReturnType() {
return returnsObjectOfType;
}
@Override
public Set<DexType> getInitializedClassesOnNormalExit() {
return initializedClassesOnNormalExit;
}
@Override
public TrivialInitializer getTrivialInitializerInfo() {
return trivialInitializerInfo;
}
@Override
public ParameterUsage getParameterUsages(int parameter) {
return parametersUsages == null ? null : parametersUsages.getParameterUsage(parameter);
}
@Override
public BitSet getNonNullParamOrThrow() {
return nonNullParamOrThrow;
}
@Override
public BitSet getNonNullParamOnNormalExits() {
return nonNullParamOnNormalExits;
}
@Override
public boolean isReachabilitySensitive() {
return reachabilitySensitive;
}
@Override
public boolean returnsArgument() {
return returnedArgument != -1;
}
@Override
public int getReturnedArgument() {
assert returnsArgument();
return returnedArgument;
}
@Override
public boolean neverReturnsNull() {
return neverReturnsNull;
}
@Override
public boolean neverReturnsNormally() {
return neverReturnsNormally;
}
@Override
public boolean returnsConstant() {
assert !(returnsConstantNumber && returnsConstantString);
return returnsConstantNumber || returnsConstantString;
}
@Override
public boolean returnsConstantNumber() {
return returnsConstantNumber;
}
@Override
public boolean returnsConstantString() {
return returnsConstantString;
}
@Override
public ClassInlinerEligibility getClassInlinerEligibility() {
return classInlinerEligibility;
}
@Override
public long getReturnedConstantNumber() {
assert returnsConstant();
return returnedConstantNumber;
}
@Override
public DexString getReturnedConstantString() {
assert returnsConstant();
return returnedConstantString;
}
@Override
public boolean isInitializerEnablingJavaAssertions() {
return initializerEnablingJavaAssertions;
}
@Override
public boolean useIdentifierNameString() {
return useIdentifierNameString;
}
@Override
public boolean forceInline() {
return inlining == InlinePreference.ForceInline;
}
@Override
public boolean neverInline() {
return inlining == InlinePreference.NeverInline;
}
@Override
public boolean checksNullReceiverBeforeAnySideEffect() {
return checksNullReceiverBeforeAnySideEffect;
}
@Override
public boolean triggersClassInitBeforeAnySideEffect() {
return triggersClassInitBeforeAnySideEffect;
}
@Override
public boolean mayHaveSideEffects() {
return mayHaveSideEffects;
}
@Override
public void setParameterUsages(ParameterUsagesInfo parametersUsages) {
this.parametersUsages = parametersUsages;
}
@Override
public void setNonNullParamOrThrow(BitSet facts) {
this.nonNullParamOrThrow = facts;
}
@Override
public void setNonNullParamOnNormalExits(BitSet facts) {
this.nonNullParamOnNormalExits = facts;
}
@Override
public void setReachabilitySensitive(boolean reachabilitySensitive) {
this.reachabilitySensitive = reachabilitySensitive;
}
@Override
public void setClassInlinerEligibility(ClassInlinerEligibility eligibility) {
this.classInlinerEligibility = eligibility;
}
@Override
public void setTrivialInitializer(TrivialInitializer info) {
this.trivialInitializerInfo = info;
}
@Override
public void setInitializerEnablingJavaAssertions() {
this.initializerEnablingJavaAssertions = true;
}
@Override
public void markInitializesClassesOnNormalExit(Set<DexType> initializedClassesOnNormalExit) {
this.initializedClassesOnNormalExit = initializedClassesOnNormalExit;
}
@Override
public void markReturnsArgument(int argument) {
assert argument >= 0;
assert returnedArgument == -1 || returnedArgument == argument;
returnedArgument = argument;
}
@Override
public void markMayNotHaveSideEffects() {
mayHaveSideEffects = false;
}
@Override
public void markNeverReturnsNull() {
neverReturnsNull = true;
}
@Override
public void markNeverReturnsNormally() {
neverReturnsNormally = true;
}
@Override
public void markReturnsConstantNumber(long value) {
assert !returnsConstantString;
assert !returnsConstantNumber || returnedConstantNumber == value;
returnsConstantNumber = true;
returnedConstantNumber = value;
}
@Override
public void markReturnsConstantString(DexString value) {
assert !returnsConstantNumber;
assert !returnsConstantString || returnedConstantString == value;
returnsConstantString = true;
returnedConstantString = value;
}
@Override
public void markReturnsObjectOfType(TypeLatticeElement type) {
assert type != null;
assert returnsObjectOfType == UNKNOWN_TYPE || returnsObjectOfType == type;
returnsObjectOfType = type;
}
@Override
public void markForceInline() {
// For concurrent scenarios we should allow the flag to be already set
assert inlining == InlinePreference.Default || inlining == InlinePreference.ForceInline;
inlining = InlinePreference.ForceInline;
}
@Override
public void unsetForceInline() {
// For concurrent scenarios we should allow the flag to be already unset
assert inlining == InlinePreference.Default || inlining == InlinePreference.ForceInline;
inlining = InlinePreference.Default;
}
@Override
public void markNeverInline() {
// For concurrent scenarios we should allow the flag to be already set
assert inlining == InlinePreference.Default || inlining == InlinePreference.NeverInline;
inlining = InlinePreference.NeverInline;
}
@Override
public void markUseIdentifierNameString() {
useIdentifierNameString = true;
}
@Override
public void markCheckNullReceiverBeforeAnySideEffect(boolean mark) {
checksNullReceiverBeforeAnySideEffect = mark;
}
@Override
public void markTriggerClassInitBeforeAnySideEffect(boolean mark) {
triggersClassInitBeforeAnySideEffect = mark;
}
@Override
public UpdatableOptimizationInfo mutableCopy() {
assert this != DefaultOptimizationInfoImpl.DEFAULT_INSTANCE;
return new OptimizationInfoImpl(this);
}
}
public OptimizationInfo getOptimizationInfo() {
checkIfObsolete();
return optimizationInfo;
}
public synchronized UpdatableOptimizationInfo getMutableOptimizationInfo() {
checkIfObsolete();
if (optimizationInfo == DefaultOptimizationInfoImpl.DEFAULT_INSTANCE) {
optimizationInfo = optimizationInfo.mutableCopy();
}
return (UpdatableOptimizationInfo) optimizationInfo;
}
public void setOptimizationInfo(UpdatableOptimizationInfo info) {
checkIfObsolete();
optimizationInfo = info;
}
public void copyMetadata(DexEncodedMethod from) {
checkIfObsolete();
// Record that the current method uses identifier name string if the inlinee did so.
if (from.getOptimizationInfo().useIdentifierNameString()) {
getMutableOptimizationInfo().markUseIdentifierNameString();
}
if (from.classFileVersion > classFileVersion) {
upgradeClassFileVersion(from.getClassFileVersion());
}
}
private static Builder builder(DexEncodedMethod from) {
return new Builder(from);
}
public static class Builder {
private DexMethod method;
private final MethodAccessFlags accessFlags;
private final DexAnnotationSet annotations;
private ParameterAnnotationsList parameterAnnotations;
private Code code;
private CompilationState compilationState;
private OptimizationInfo optimizationInfo;
private final int classFileVersion;
private Builder(DexEncodedMethod from) {
// Copy all the mutable state of a DexEncodedMethod here.
method = from.method;
accessFlags = from.accessFlags.copy();
annotations = from.annotations;
code = from.code;
compilationState = from.compilationState;
optimizationInfo = from.optimizationInfo.mutableCopy();
classFileVersion = from.classFileVersion;
if (from.parameterAnnotationsList.isEmpty()
|| from.parameterAnnotationsList.size() == method.proto.parameters.size()) {
parameterAnnotations = from.parameterAnnotationsList;
} else {
assert false
: "Parameter annotations does not match proto of method `"
+ method.toSourceString()
+ "` (was: "
+ parameterAnnotations
+ ")";
parameterAnnotations = ParameterAnnotationsList.empty();
}
}
public void setMethod(DexMethod method) {
this.method = method;
}
public Builder setParameterAnnotations(ParameterAnnotationsList parameterAnnotations) {
this.parameterAnnotations = parameterAnnotations;
return this;
}
public Builder removeParameterAnnotations(IntPredicate predicate) {
if (parameterAnnotations.isEmpty()) {
// Nothing to do.
return this;
}
List<DexAnnotationSet> newParameterAnnotations = new ArrayList<>();
int newNumberOfMissingParameterAnnotations = 0;
for (int oldIndex = 0; oldIndex < parameterAnnotations.size(); oldIndex++) {
if (!predicate.test(oldIndex)) {
if (parameterAnnotations.isMissing(oldIndex)) {
newNumberOfMissingParameterAnnotations++;
} else {
newParameterAnnotations.add(parameterAnnotations.get(oldIndex));
}
}
}
if (newParameterAnnotations.isEmpty()) {
return setParameterAnnotations(ParameterAnnotationsList.empty());
}
return setParameterAnnotations(
new ParameterAnnotationsList(
newParameterAnnotations.toArray(DexAnnotationSet.EMPTY_ARRAY),
newNumberOfMissingParameterAnnotations));
}
public Builder promoteToStatic() {
this.accessFlags.promoteToStatic();
return this;
}
public Builder unsetOptimizationInfo() {
optimizationInfo = DefaultOptimizationInfoImpl.DEFAULT_INSTANCE;
return this;
}
public Builder withoutThisParameter() {
assert code != null;
if (code.isDexCode()) {
code = code.asDexCode().withoutThisParameter();
} else {
throw new Unreachable("Code " + code.getClass().getSimpleName() + " is not supported.");
}
return this;
}
public void setCode(Code code) {
this.code = code;
}
public DexEncodedMethod build() {
assert method != null;
assert accessFlags != null;
assert annotations != null;
assert parameterAnnotations != null;
assert parameterAnnotations.isEmpty()
|| parameterAnnotations.size() == method.proto.parameters.size();
DexEncodedMethod result =
new DexEncodedMethod(
method, accessFlags, annotations, parameterAnnotations, code, classFileVersion);
result.compilationState = compilationState;
result.optimizationInfo = optimizationInfo;
return result;
}
}
@Override
public DexEncodedMethod asResultOfResolve() {
checkIfObsolete();
return this;
}
@Override
public DexEncodedMethod asSingleTarget() {
checkIfObsolete();
return this;
}
@Override
public boolean hasSingleTarget() {
checkIfObsolete();
return true;
}
@Override
public List<DexEncodedMethod> asListOfTargets() {
checkIfObsolete();
return Collections.singletonList(this);
}
@Override
public void forEachTarget(Consumer<DexEncodedMethod> consumer) {
checkIfObsolete();
consumer.accept(this);
}
}