| // 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_NEST; |
| 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.DefaultMethodOptimizationInfoImpl.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_NEST, |
| /** Code contains invoke super */ |
| PROCESSED_INLINING_CANDIDATE_SAME_CLASS |
| } |
| |
| public static final DexEncodedMethod[] EMPTY_ARRAY = {}; |
| public static final DexEncodedMethod SENTINEL = |
| new DexEncodedMethod(null, null, null, ParameterAnnotationsList.empty(), 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 MethodOptimizationInfo optimizationInfo = |
| DefaultMethodOptimizationInfoImpl.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(); |
| assert parameterAnnotationsList != null; |
| } |
| |
| 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 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_NEST: |
| return ConstraintWithTarget.sameNest(containerType, method.holder, appInfo); |
| 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 SAMENEST: |
| compilationState = PROCESSED_INLINING_CANDIDATE_SAME_NEST; |
| 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 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, DexMethod newMethod) { |
| assert accessFlags.isPrivate() |
| : "Expected to create bridge for private constructor as part of nest-based access" |
| + " desugaring"; |
| Builder builder = builder(this); |
| builder.setMethod(newMethod); |
| ForwardMethodSourceCode.Builder forwardSourceCodeBuilder = |
| ForwardMethodSourceCode.builder(newMethod); |
| forwardSourceCodeBuilder |
| .setReceiver(holder.type) |
| .setTargetReceiver(holder.type) |
| .setTarget(method) |
| .setInvokeType(Invoke.Type.DIRECT) |
| .setExtraNullParameter(); |
| builder.setCode( |
| new SynthesizedCode( |
| forwardSourceCodeBuilder::build, registry -> registry.registerInvokeDirect(method))); |
| assert !builder.accessFlags.isStatic(); |
| assert !holder.isInterface(); |
| builder.accessFlags.unsetPrivate(); |
| builder.accessFlags.setSynthetic(); |
| builder.accessFlags.setConstructor(); |
| return builder.build(); |
| } |
| |
| public static DexEncodedMethod createFieldAccessorBridge( |
| DexFieldWithAccess fieldWithAccess, DexClass holder, DexMethod newMethod) { |
| assert holder.type == fieldWithAccess.getHolder(); |
| 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, DexMethod newMethod) { |
| assert accessFlags.isPrivate() |
| : "Expected to create bridge for private method as part of nest-based access desugaring"; |
| Builder builder = builder(this); |
| builder.setMethod(newMethod); |
| ForwardMethodSourceCode.Builder forwardSourceCodeBuilder = |
| ForwardMethodSourceCode.builder(newMethod); |
| forwardSourceCodeBuilder |
| .setTargetReceiver(accessFlags.isStatic() ? null : method.holder) |
| .setTarget(method) |
| .setInvokeType(accessFlags.isStatic() ? Invoke.Type.STATIC : Invoke.Type.DIRECT) |
| .setIsInterface(holder.isInterface()); |
| builder.setCode( |
| new SynthesizedCode( |
| forwardSourceCodeBuilder::build, |
| 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); |
| ForwardMethodSourceCode.Builder forwardSourceCodeBuilder = |
| ForwardMethodSourceCode.builder(newMethod); |
| forwardSourceCodeBuilder |
| .setReceiver(accessFlags.isStatic() ? null : holder.type) |
| .setTargetReceiver(accessFlags.isStatic() ? null : method.holder) |
| .setTarget(method) |
| .setInvokeType(type) |
| .setIsInterface(target.isInterface()); |
| builder.setCode( |
| new SynthesizedCode( |
| forwardSourceCodeBuilder::build, |
| 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 DefaultMethodOptimizationInfoImpl implements MethodOptimizationInfo { |
| public static final MethodOptimizationInfo DEFAULT_INSTANCE = |
| new DefaultMethodOptimizationInfoImpl(); |
| |
| 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 DefaultMethodOptimizationInfoImpl() {} |
| |
| @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 hasBeenInlinedIntoSingleCallSite() { |
| return false; |
| } |
| |
| @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 boolean returnValueHasBeenPropagated() { |
| return false; |
| } |
| |
| @Override |
| public UpdatableMethodOptimizationInfo mutableCopy() { |
| return new MethodOptimizationInfoImpl(); |
| } |
| } |
| |
| public static class MethodOptimizationInfoImpl implements UpdatableMethodOptimizationInfo { |
| |
| private boolean hasBeenInlinedIntoSingleCallSite = false; |
| private Set<DexType> initializedClassesOnNormalExit = |
| DefaultMethodOptimizationInfoImpl.UNKNOWN_INITIALIZED_CLASSES_ON_NORMAL_EXIT; |
| private int returnedArgument = DefaultMethodOptimizationInfoImpl.UNKNOWN_RETURNED_ARGUMENT; |
| private boolean mayHaveSideEffects = |
| DefaultMethodOptimizationInfoImpl.UNKNOWN_MAY_HAVE_SIDE_EFFECTS; |
| private boolean neverReturnsNull = DefaultMethodOptimizationInfoImpl.UNKNOWN_NEVER_RETURNS_NULL; |
| private boolean neverReturnsNormally = |
| DefaultMethodOptimizationInfoImpl.UNKNOWN_NEVER_RETURNS_NORMALLY; |
| private boolean returnsConstantNumber = |
| DefaultMethodOptimizationInfoImpl.UNKNOWN_RETURNS_CONSTANT; |
| private long returnedConstantNumber = |
| DefaultMethodOptimizationInfoImpl.UNKNOWN_RETURNED_CONSTANT_NUMBER; |
| private boolean returnsConstantString = |
| DefaultMethodOptimizationInfoImpl.UNKNOWN_RETURNS_CONSTANT; |
| private DexString returnedConstantString = |
| DefaultMethodOptimizationInfoImpl.UNKNOWN_RETURNED_CONSTANT_STRING; |
| private TypeLatticeElement returnsObjectOfType = UNKNOWN_TYPE; |
| private InlinePreference inlining = InlinePreference.Default; |
| private boolean useIdentifierNameString = |
| DefaultMethodOptimizationInfoImpl.DOES_NOT_USE_IDNETIFIER_NAME_STRING; |
| private boolean checksNullReceiverBeforeAnySideEffect = |
| DefaultMethodOptimizationInfoImpl.UNKNOWN_CHECKS_NULL_RECEIVER_BEFORE_ANY_SIDE_EFFECT; |
| private boolean triggersClassInitBeforeAnySideEffect = |
| DefaultMethodOptimizationInfoImpl.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 = |
| DefaultMethodOptimizationInfoImpl.UNKNOWN_CLASS_INLINER_ELIGIBILITY; |
| private TrivialInitializer trivialInitializerInfo = |
| DefaultMethodOptimizationInfoImpl.UNKNOWN_TRIVIAL_INITIALIZER; |
| private boolean initializerEnablingJavaAssertions = |
| DefaultMethodOptimizationInfoImpl.UNKNOWN_INITIALIZER_ENABLING_JAVA_ASSERTIONS; |
| private ParameterUsagesInfo parametersUsages = |
| DefaultMethodOptimizationInfoImpl.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 boolean returnValueHasBeenPropagated = false; |
| |
| private MethodOptimizationInfoImpl() { |
| // Intentionally left empty, just use the default values. |
| } |
| |
| private MethodOptimizationInfoImpl(MethodOptimizationInfoImpl 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 hasBeenInlinedIntoSingleCallSite() { |
| return hasBeenInlinedIntoSingleCallSite; |
| } |
| |
| @Override |
| public void markInlinedIntoSingleCallSite() { |
| hasBeenInlinedIntoSingleCallSite = true; |
| } |
| |
| @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 void markAsPropagated() { |
| returnValueHasBeenPropagated = true; |
| } |
| |
| @Override |
| public boolean returnValueHasBeenPropagated() { |
| return returnValueHasBeenPropagated; |
| } |
| |
| @Override |
| public UpdatableMethodOptimizationInfo mutableCopy() { |
| assert this != DefaultMethodOptimizationInfoImpl.DEFAULT_INSTANCE; |
| return new MethodOptimizationInfoImpl(this); |
| } |
| } |
| |
| public MethodOptimizationInfo getOptimizationInfo() { |
| checkIfObsolete(); |
| return optimizationInfo; |
| } |
| |
| public synchronized UpdatableMethodOptimizationInfo getMutableOptimizationInfo() { |
| checkIfObsolete(); |
| if (optimizationInfo == DefaultMethodOptimizationInfoImpl.DEFAULT_INSTANCE) { |
| optimizationInfo = optimizationInfo.mutableCopy(); |
| } |
| return (UpdatableMethodOptimizationInfo) optimizationInfo; |
| } |
| |
| public void setOptimizationInfo(UpdatableMethodOptimizationInfo 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 MethodOptimizationInfo 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 { |
| // If the there are missing parameter annotations populate these when creating the builder. |
| parameterAnnotations = |
| from.parameterAnnotationsList.withParameterCount(method.proto.parameters.size()); |
| } |
| } |
| |
| 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 = DefaultMethodOptimizationInfoImpl.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); |
| } |
| |
| } |