blob: bca039528586f1a62afb124d6b8ccd7fc6f12fa4 [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.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 = DefaultOptimizationInfo.DEFAULT;
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;
}
private 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(
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 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 optimizationInfo.isPublicized();
}
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.
assert isInliningCandidate(containerType, Reason.SIMPLE, appInfo);
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) {
checkIfObsolete();
return buildInliningIR(
null, GraphLense.getIdentityLense(), options, valueNumberGenerator, null, Origin.unknown());
}
public IRCode buildInliningIR(
AppInfo appInfo,
GraphLense graphLense,
InternalOptions options,
ValueNumberGenerator valueNumberGenerator,
Position callerPosition,
Origin origin) {
checkIfObsolete();
return code.buildInliningIR(
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);
}
}
private void voidCodeOwnership() {
if (code != null) {
code.setOwner(null);
}
}
public boolean hasDebugPositions() {
checkIfObsolete();
assert code != null && code.isDexCode();
return code.asDexCode().hasDebugPositions();
}
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();
}
// Passing null as highestSortingString is save, as ConstString instructions are not allowed.
return new DexCode(Math.max(numberOfRegisters, requiredArgRegisters), requiredArgRegisters,
outRegisters, instructions, new DexCode.Try[0], new DexCode.TryHandler[0], null, null);
}
public DexEncodedMethod toEmptyThrowingMethodDex() {
checkIfObsolete();
assert !shouldNotHaveCode();
Builder builder = builder(this);
Instruction insn[] = {new Const(0, 0), new Throw(0)};
DexCode emptyThrowingCode = generateCodeFromTemplate(1, 0, insn);
builder.setCode(emptyThrowingCode);
// 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 DexEncodedMethod toEmptyThrowingMethodCf() {
checkIfObsolete();
assert !shouldNotHaveCode();
Builder builder = builder(this);
CfInstruction insn[] = {new CfConstNull(), new CfThrow()};
CfCode emptyThrowingCode =
new CfCode(
method,
1,
method.proto.parameters.size() + 1,
Arrays.asList(insn),
Collections.emptyList(),
Collections.emptyList());
builder.setCode(emptyThrowingCode);
// 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,
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);
builder.setStatic();
builder.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 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 OptimizationInfo {
public enum InlinePreference {
NeverInline, ForceInline, Default
}
private int returnedArgument = -1;
private boolean neverReturnsNull = false;
private boolean neverReturnsNormally = false;
private boolean returnsConstant = false;
private long returnedConstant = 0;
private boolean publicized = false;
private InlinePreference inlining = InlinePreference.Default;
private boolean useIdentifierNameString = false;
private boolean checksNullReceiverBeforeAnySideEffect = false;
private boolean triggersClassInitBeforeAnySideEffect = false;
// Stores information about instance methods and constructors for
// class inliner, null value indicates that the method is not eligible.
private ClassInlinerEligibility classInlinerEligibility = null;
private TrivialInitializer trivialInitializerInfo = null;
private boolean initializerEnablingJavaAssertions = false;
private ParameterUsagesInfo parametersUsages = null;
private BitSet kotlinNotNullParamHints = null;
private OptimizationInfo() {
// Intentionally left empty.
}
private OptimizationInfo(OptimizationInfo template) {
returnedArgument = template.returnedArgument;
neverReturnsNull = template.neverReturnsNull;
returnsConstant = template.returnsConstant;
returnedConstant = template.returnedConstant;
publicized = template.publicized;
inlining = template.inlining;
useIdentifierNameString = template.useIdentifierNameString;
checksNullReceiverBeforeAnySideEffect = template.checksNullReceiverBeforeAnySideEffect;
trivialInitializerInfo = template.trivialInitializerInfo;
}
public void setParameterUsages(ParameterUsagesInfo parametersUsages) {
this.parametersUsages = parametersUsages;
}
public ParameterUsage getParameterUsages(int parameter) {
return parametersUsages == null ? null : parametersUsages.getParameterUsage(parameter);
}
public BitSet getKotlinNotNullParamHints() {
return kotlinNotNullParamHints;
}
public void setKotlinNotNullParamHints(BitSet hints) {
this.kotlinNotNullParamHints = hints;
}
public boolean returnsArgument() {
return returnedArgument != -1;
}
public int getReturnedArgument() {
assert returnsArgument();
return returnedArgument;
}
public boolean neverReturnsNull() {
return neverReturnsNull;
}
public boolean neverReturnsNormally() {
return neverReturnsNormally;
}
public boolean returnsConstant() {
return returnsConstant;
}
private void setClassInlinerEligibility(ClassInlinerEligibility eligibility) {
this.classInlinerEligibility = eligibility;
}
public ClassInlinerEligibility getClassInlinerEligibility() {
return this.classInlinerEligibility;
}
private void setTrivialInitializer(TrivialInitializer info) {
this.trivialInitializerInfo = info;
}
public TrivialInitializer getTrivialInitializerInfo() {
return this.trivialInitializerInfo;
}
private void setInitializerEnablingJavaAssertions() {
this.initializerEnablingJavaAssertions = true;
}
public boolean isInitializerEnablingJavaAssertions() {
return initializerEnablingJavaAssertions;
}
public long getReturnedConstant() {
assert returnsConstant();
return returnedConstant;
}
public boolean isPublicized() {
return publicized;
}
public boolean forceInline() {
return inlining == InlinePreference.ForceInline;
}
public boolean neverInline() {
return inlining == InlinePreference.NeverInline;
}
public boolean useIdentifierNameString() {
return useIdentifierNameString;
}
public boolean checksNullReceiverBeforeAnySideEffect() {
return checksNullReceiverBeforeAnySideEffect;
}
public boolean triggersClassInitBeforeAnySideEffect() {
return triggersClassInitBeforeAnySideEffect;
}
private void markReturnsArgument(int argument) {
assert argument >= 0;
assert returnedArgument == -1 || returnedArgument == argument;
returnedArgument = argument;
}
private void markNeverReturnsNull() {
neverReturnsNull = true;
}
private void markNeverReturnsNormally() {
neverReturnsNormally = true;
}
private void markReturnsConstant(long value) {
assert !returnsConstant || returnedConstant == value;
returnsConstant = true;
returnedConstant = value;
}
private void markForceInline() {
// For concurrent scenarios we should allow the flag to be already set
assert inlining == InlinePreference.Default || inlining == InlinePreference.ForceInline;
inlining = InlinePreference.ForceInline;
}
private void unsetForceInline() {
// For concurrent scenarios we should allow the flag to be already unset
assert inlining == InlinePreference.Default || inlining == InlinePreference.ForceInline;
inlining = InlinePreference.Default;
}
private void markNeverInline() {
// For concurrent scenarios we should allow the flag to be already set
assert inlining == InlinePreference.Default || inlining == InlinePreference.NeverInline;
inlining = InlinePreference.NeverInline;
}
private void markPublicized() {
publicized = true;
}
private void unsetPublicized() {
publicized = false;
}
private void markUseIdentifierNameString() {
useIdentifierNameString = true;
}
public OptimizationInfo copy() {
return new OptimizationInfo(this);
}
private void markCheckNullReceiverBeforeAnySideEffect(boolean mark) {
checksNullReceiverBeforeAnySideEffect = mark;
}
private void markTriggerClassInitBeforeAnySideEffect(boolean mark) {
triggersClassInitBeforeAnySideEffect = mark;
}
}
private static class DefaultOptimizationInfo extends OptimizationInfo {
static final OptimizationInfo DEFAULT = new DefaultOptimizationInfo();
private DefaultOptimizationInfo() {
}
@Override
public OptimizationInfo copy() {
return this;
}
}
synchronized private OptimizationInfo ensureMutableOI() {
if (optimizationInfo == DefaultOptimizationInfo.DEFAULT) {
optimizationInfo = new OptimizationInfo();
}
return optimizationInfo;
}
synchronized public void markReturnsArgument(int argument) {
ensureMutableOI().markReturnsArgument(argument);
}
synchronized public void markNeverReturnsNull() {
ensureMutableOI().markNeverReturnsNull();
}
synchronized public void markNeverReturnsNormally() {
ensureMutableOI().markNeverReturnsNormally();
}
synchronized public void markReturnsConstant(long value) {
ensureMutableOI().markReturnsConstant(value);
}
synchronized public void setClassInlinerEligibility(ClassInlinerEligibility eligibility) {
ensureMutableOI().setClassInlinerEligibility(eligibility);
}
synchronized public void setParameterUsages(ParameterUsagesInfo parametersUsages) {
ensureMutableOI().setParameterUsages(parametersUsages);
}
synchronized public void setKotlinNotNullParamHints(BitSet hints) {
ensureMutableOI().setKotlinNotNullParamHints(hints);
}
synchronized public void setTrivialInitializer(TrivialInitializer info) {
ensureMutableOI().setTrivialInitializer(info);
}
synchronized public void setInitializerEnablingJavaAssertions() {
ensureMutableOI().setInitializerEnablingJavaAssertions();
}
synchronized public void markForceInline() {
ensureMutableOI().markForceInline();
}
synchronized public void markNeverInline() {
ensureMutableOI().markNeverInline();
}
public synchronized void unsetForceInline() {
ensureMutableOI().unsetForceInline();
}
synchronized public void markPublicized() {
ensureMutableOI().markPublicized();
}
synchronized public void unsetPublicized() {
ensureMutableOI().unsetPublicized();
}
synchronized public void markUseIdentifierNameString() {
ensureMutableOI().markUseIdentifierNameString();
}
synchronized public void markCheckNullReceiverBeforeAnySideEffect(boolean mark) {
ensureMutableOI().markCheckNullReceiverBeforeAnySideEffect(mark);
}
synchronized public void markTriggerClassInitBeforeAnySideEffect(boolean mark) {
ensureMutableOI().markTriggerClassInitBeforeAnySideEffect(mark);
}
public OptimizationInfo getOptimizationInfo() {
checkIfObsolete();
return optimizationInfo;
}
public void copyMetadataFromInlinee(DexEncodedMethod inlinee) {
checkIfObsolete();
// Record that the current method uses identifier name string if the inlinee did so.
if (inlinee.getOptimizationInfo().useIdentifierNameString()) {
markUseIdentifierNameString();
}
if (inlinee.classFileVersion > classFileVersion) {
upgradeClassFileVersion(inlinee.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 = CompilationState.NOT_PROCESSED;
private OptimizationInfo optimizationInfo = DefaultOptimizationInfo.DEFAULT;
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.copy();
classFileVersion = from.classFileVersion;
}
public void setMethod(DexMethod method) {
this.method = method;
}
public void setStatic() {
this.accessFlags.setStatic();
}
public void withoutThisParameter() {
assert code != null;
if (code.isDexCode()) {
code = code.asDexCode().withoutThisParameter();
} else {
throw new Unreachable("Code " + code.getClass().getSimpleName() + " is not supported.");
}
}
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);
}
}