blob: 4db858be59cc10b74e5826793b2cea8a8a5914f7 [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 com.android.tools.r8.cf.code.CfConstNull;
import com.android.tools.r8.cf.code.CfInstruction;
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.ConstStringJumbo;
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.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.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.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.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 java.util.Arrays;
import java.util.BitSet;
import java.util.Collections;
import java.util.List;
import java.util.function.Consumer;
public class DexEncodedMethod extends KeyedDexItem<DexMethod> implements ResolutionResult {
/**
* 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/111080693): 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 = new DexEncodedMethod[]{};
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/111080693): 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;
// 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;
}
/**
* 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();
setCodeOwnership();
}
public DexEncodedMethod(
DexMethod method,
MethodAccessFlags flags,
DexAnnotationSet annotationSet,
ParameterAnnotationsList annotationsList,
Code code,
int classFileVersion) {
this(method, flags, annotationSet, annotationsList, code);
this.classFileVersion = classFileVersion;
}
public boolean isProcessed() {
checkIfObsolete();
return compilationState != CompilationState.NOT_PROCESSED;
}
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 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.getHolder(), 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/111080693): 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 containerType.isSubtypeOf(method.getHolder(), appInfo);
case PROCESSED_INLINING_CANDIDATE_SAME_PACKAGE:
return containerType.isSamePackage(method.getHolder());
case PROCESSED_INLINING_CANDIDATE_SAME_CLASS:
return containerType == method.getHolder();
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(
AppInfo appInfo, GraphLense graphLense, InternalOptions options, Origin origin) {
checkIfObsolete();
return code == null ? null : code.buildIR(this, appInfo, graphLense, options, origin);
}
public IRCode buildInliningIRForTesting(
InternalOptions options, ValueNumberGenerator valueNumberGenerator, AppInfo appInfo) {
checkIfObsolete();
return buildInliningIR(
this,
appInfo,
GraphLense.getIdentityLense(),
options,
valueNumberGenerator,
null,
Origin.unknown());
}
public IRCode buildInliningIR(
DexEncodedMethod context,
AppInfo appInfo,
GraphLense graphLense,
InternalOptions options,
ValueNumberGenerator valueNumberGenerator,
Position callerPosition,
Origin origin) {
checkIfObsolete();
return code.buildInliningIR(
context, this, appInfo, graphLense, options, valueNumberGenerator, callerPosition, origin);
}
public void setCode(Code code) {
checkIfObsolete();
voidCodeOwnership();
this.code = code;
setCodeOwnership();
}
public void setCode(IRCode ir, RegisterAllocator registerAllocator, InternalOptions options) {
checkIfObsolete();
final DexBuilder builder = new DexBuilder(ir, registerAllocator, options);
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) {
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();
voidCodeOwnership();
code = null;
}
private void setCodeOwnership() {
if (code != null) {
code.setOwner(this);
}
}
public void voidCodeOwnership() {
if (code != null) {
code.setOwner(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();
voidCodeOwnership();
this.code = null;
return this;
}
/**
* Generates a {@link DexCode} object for the given instructions.
* <p>
* As the code object is produced outside of the normal compilation cycle, it has to use {@link
* ConstStringJumbo} to reference string constants. Hence, code produced form these templates
* might incur a size overhead.
*/
private DexCode generateCodeFromTemplate(
int numberOfRegisters, int outRegisters, Instruction... instructions) {
int offset = 0;
for (Instruction instruction : instructions) {
assert !(instruction instanceof ConstString);
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 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(
method,
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(DexItemFactory itemFactory) {
checkIfObsolete();
Signature signature = MethodSignature.fromDexMethod(method);
// TODO(herhut): Construct this out of parts to enable reuse, maybe even using descriptors.
DexString message = itemFactory.createString(
"Shaking error: Missing method in " + method.holder.toSourceString() + ": "
+ signature);
DexString tag = itemFactory.createString("TOIGHTNESS");
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;
if (isInstanceInitializer()) {
// The Java VM Spec requires that a constructor calls an initializer from the super class
// or another constructor from the current class. For simplicity we do the latter by just
// calling ourself. This is ok, as the constructor always throws before the recursive call.
code = generateCodeFromTemplate(3, 2, new ConstStringJumbo(0, tag),
new ConstStringJumbo(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),
new InvokeDirect(1, method, 2, 0, 0, 0, 0));
} else {
// These methods might not get registered for jumbo string processing, therefore we always
// use the jumbo string encoding for the const string instruction.
code = generateCodeFromTemplate(2, 2, new ConstStringJumbo(0, tag),
new ConstStringJumbo(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();
}
public DexEncodedMethod toTypeSubstitutedMethod(DexMethod method) {
checkIfObsolete();
if (this.method == method) {
return this;
}
Builder builder = builder(this);
builder.setMethod(method);
// TODO(b/112847660): Fix type fixers that use this method: ProguardMapApplier
// TODO(b/112847660): Fix type fixers that use this method: Staticizer
// TODO(b/112847660): Fix type fixers that use this method: VerticalClassMerger
// setObsolete();
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 toForwardingMethod(DexClass holder, DexItemFactory itemFactory) {
checkIfObsolete();
assert accessFlags.isPublic();
// 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.unsetFinal();
DexMethod newMethod = itemFactory.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.
builder.setCode(
new SynthesizedCode(
callerPosition ->
new ForwardMethodSourceCode(
accessFlags.isStatic() ? null : holder.type,
newMethod,
newMethod,
accessFlags.isStatic() ? null : method.holder,
method,
type,
callerPosition),
registry -> {
if (accessFlags.isStatic()) {
registry.registerInvokeStatic(method);
} else {
registry.registerInvokeSuper(method);
}
}));
}
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).setStatic().unsetOptimizationInfo().withoutThisParameter();
setObsolete();
return builder.build();
}
/**
* Rewrites the code in this method to have JumboString bytecode if required by mapping.
* <p>
* Synchronized such that it can be called concurrently for different mappings. As a side-effect,
* this will also update the highestSortingString to the index of the strings up to which the code
* was rewritten to avoid rewriting again unless needed.
*/
public synchronized void rewriteCodeWithJumboStrings(
ObjectToOffsetMapping mapping, DexApplication application, boolean force) {
checkIfObsolete();
assert code == null || code.isDexCode();
if (code == null) {
return;
}
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, application.dexItemFactory);
rewriter.rewrite();
}
}
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(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 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 int UNKNOWN_RETURNED_CONSTANT = -1;
public static boolean NOT_PUBLICZED = false;
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 BitSet NO_NULL_PARAMETER_OR_THROW_FACTS = null;
public static BitSet NO_NULL_PARAMETER_ON_NORMAL_EXITS_FACTS = null;
private DefaultOptimizationInfoImpl() {}
@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 ClassInlinerEligibility getClassInlinerEligibility() {
return UNKNOWN_CLASS_INLINER_ELIGIBILITY;
}
@Override
public long getReturnedConstant() {
assert returnsConstant();
return 0;
}
@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 UpdatableOptimizationInfo mutableCopy() {
return new OptimizationInfoImpl();
}
}
public static class OptimizationInfoImpl implements UpdatableOptimizationInfo {
private int returnedArgument = DefaultOptimizationInfoImpl.UNKNOWN_RETURNED_ARGUMENT;
private boolean neverReturnsNull = DefaultOptimizationInfoImpl.UNKNOWN_NEVER_RETURNS_NULL;
private boolean neverReturnsNormally =
DefaultOptimizationInfoImpl.UNKNOWN_NEVER_RETURNS_NORMALLY;
private boolean returnsConstant = DefaultOptimizationInfoImpl.UNKNOWN_RETURNS_CONSTANT;
private long returnedConstant = DefaultOptimizationInfoImpl.UNKNOWN_RETURNED_CONSTANT;
private boolean publicized = DefaultOptimizationInfoImpl.NOT_PUBLICZED;
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;
returnsConstant = template.returnsConstant;
returnedConstant = template.returnedConstant;
publicized = template.publicized;
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 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() {
return returnsConstant;
}
@Override
public ClassInlinerEligibility getClassInlinerEligibility() {
return classInlinerEligibility;
}
@Override
public long getReturnedConstant() {
assert returnsConstant();
return returnedConstant;
}
@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 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 markReturnsArgument(int argument) {
assert argument >= 0;
assert returnedArgument == -1 || returnedArgument == argument;
returnedArgument = argument;
}
@Override
public void markNeverReturnsNull() {
neverReturnsNull = true;
}
@Override
public void markNeverReturnsNormally() {
neverReturnsNormally = true;
}
@Override
public void markReturnsConstant(long value) {
assert !returnsConstant || returnedConstant == value;
returnsConstant = true;
returnedConstant = value;
}
@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 markPublicized() {
publicized = true;
}
@Override
public void unsetPublicized() {
publicized = false;
}
@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);
}
private static class Builder {
private DexMethod method;
private final MethodAccessFlags accessFlags;
private final DexAnnotationSet annotations;
private final 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;
parameterAnnotations = from.parameterAnnotationsList;
code = from.code;
compilationState = from.compilationState;
optimizationInfo = from.optimizationInfo.mutableCopy();
classFileVersion = from.classFileVersion;
}
public void setMethod(DexMethod method) {
this.method = method;
}
public Builder setStatic() {
this.accessFlags.setStatic();
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;
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);
}
}