|  | // Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file | 
|  | // for details. All rights reserved. Use of this source code is governed by a | 
|  | // BSD-style license that can be found in the LICENSE file. | 
|  |  | 
|  | package com.android.tools.r8.graph; | 
|  |  | 
|  | import com.android.tools.r8.cf.CfVersion; | 
|  | 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.CfReturnVoid; | 
|  | import com.android.tools.r8.dex.CodeToKeep; | 
|  | import com.android.tools.r8.dex.IndexedItemCollection; | 
|  | import com.android.tools.r8.dex.MixedSectionCollection; | 
|  | import com.android.tools.r8.dex.code.DexInvokeDirect; | 
|  | import com.android.tools.r8.dex.code.DexReturnVoid; | 
|  | import com.android.tools.r8.graph.DexCode.Try; | 
|  | import com.android.tools.r8.graph.DexCode.TryHandler; | 
|  | import com.android.tools.r8.graph.lens.GraphLens; | 
|  | import com.android.tools.r8.graph.lens.MethodLookupResult; | 
|  | import com.android.tools.r8.graph.proto.RewrittenPrototypeDescription; | 
|  | import com.android.tools.r8.ir.code.IRCode; | 
|  | import com.android.tools.r8.ir.code.NumberGenerator; | 
|  | import com.android.tools.r8.ir.code.Position; | 
|  | import com.android.tools.r8.ir.code.Position.SyntheticPosition; | 
|  | import com.android.tools.r8.ir.code.ValueType; | 
|  | import com.android.tools.r8.ir.conversion.IRBuilder; | 
|  | import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils; | 
|  | import com.android.tools.r8.ir.conversion.MethodConversionOptions.MutableMethodConversionOptions; | 
|  | import com.android.tools.r8.ir.conversion.MethodConversionOptions.ThrowingMethodConversionOptions; | 
|  | import com.android.tools.r8.ir.conversion.SyntheticStraightLineSourceCode; | 
|  | import com.android.tools.r8.naming.NamingLens; | 
|  | import com.android.tools.r8.origin.Origin; | 
|  | import com.android.tools.r8.utils.IteratorUtils; | 
|  | import com.android.tools.r8.utils.RetracerForCodePrinting; | 
|  | import com.android.tools.r8.utils.structural.HashingVisitor; | 
|  | import com.google.common.collect.ImmutableList; | 
|  | import java.nio.ShortBuffer; | 
|  | import java.util.Arrays; | 
|  | import java.util.Iterator; | 
|  | import java.util.List; | 
|  | import java.util.function.Consumer; | 
|  | import java.util.function.Predicate; | 
|  | import org.objectweb.asm.MethodVisitor; | 
|  | import org.objectweb.asm.Opcodes; | 
|  |  | 
|  | /** | 
|  | * A piece of code on the form: | 
|  | * | 
|  | * <pre> | 
|  | *   aload_0 | 
|  | *   invoke-special LSuperClass;-><init>()V | 
|  | *   return | 
|  | * </pre> | 
|  | * | 
|  | * <p>Note that (i) {@code SuperClass} may be different from {@link java.lang.Object} and (ii) the | 
|  | * method holding this code object may have a non-empty proto. | 
|  | */ | 
|  | public class DefaultInstanceInitializerCode extends Code | 
|  | implements CfWritableCode, DexWritableCode { | 
|  |  | 
|  | private static final DefaultInstanceInitializerCode INSTANCE = | 
|  | new DefaultInstanceInitializerCode(); | 
|  |  | 
|  | private DefaultInstanceInitializerCode() {} | 
|  |  | 
|  | public static DefaultInstanceInitializerCode get() { | 
|  | return INSTANCE; | 
|  | } | 
|  |  | 
|  | public static boolean canonicalizeCodeIfPossible(AppView<?> appView, ProgramMethod method) { | 
|  | if (hasDefaultInstanceInitializerCode(method, appView)) { | 
|  | method.setCode(get(), appView); | 
|  | return true; | 
|  | } | 
|  | return false; | 
|  | } | 
|  |  | 
|  | public static void uncanonicalizeCode(AppView<?> appView, ProgramMethod method) { | 
|  | uncanonicalizeCode(appView, method, method.getHolder().getSuperType()); | 
|  | } | 
|  |  | 
|  | public static void uncanonicalizeCode( | 
|  | AppView<?> appView, ProgramMethod method, DexType superType) { | 
|  | DexEncodedMethod definition = method.getDefinition(); | 
|  | assert definition.getCode().isDefaultInstanceInitializerCode(); | 
|  | method.setCode(get().toCfCode(method, appView.dexItemFactory(), superType), appView); | 
|  | } | 
|  |  | 
|  | private static boolean hasDefaultInstanceInitializerCode( | 
|  | ProgramMethod method, AppView<?> appView) { | 
|  | if (!method.getDefinition().isInstanceInitializer()) { | 
|  | return false; | 
|  | } | 
|  | Code code = method.getDefinition().getCode(); | 
|  | if (!code.isCfCode()) { | 
|  | return false; | 
|  | } | 
|  | CfCode cfCode = code.asCfCode(); | 
|  | if (!method.getDefinition().isInstanceInitializer() | 
|  | || !cfCode.getLocalVariables().isEmpty() | 
|  | || !cfCode.getTryCatchRanges().isEmpty()) { | 
|  | return false; | 
|  | } | 
|  | if (cfCode.getInstructions().size() > 6) { | 
|  | // Default instance initializers typically have the following instruction sequence: | 
|  | // [CfLabel, CfPosition, CfLoad, CfInvoke, CfReturnVoid, CfLabel]. | 
|  | return false; | 
|  | } | 
|  | DexItemFactory dexItemFactory = appView.dexItemFactory(); | 
|  | Iterator<CfInstruction> instructionIterator = cfCode.getInstructions().iterator(); | 
|  | // Allow skipping CfPosition instructions in instance initializers that only call Object.<init>. | 
|  | Predicate<CfInstruction> instructionOfInterest = | 
|  | method.getHolder().getSuperType() == dexItemFactory.objectType | 
|  | ? instruction -> !instruction.isLabel() && !instruction.isPosition() | 
|  | : instruction -> !instruction.isLabel(); | 
|  | CfLoad load = IteratorUtils.nextUntil(instructionIterator, instructionOfInterest).asLoad(); | 
|  | if (load == null || load.getLocalIndex() != 0) { | 
|  | return false; | 
|  | } | 
|  | CfInvoke invoke = instructionIterator.next().asInvoke(); | 
|  | if (invoke == null | 
|  | || !invoke.isInvokeConstructor(dexItemFactory) | 
|  | || invoke.getMethod() != getParentConstructor(method, dexItemFactory)) { | 
|  | return false; | 
|  | } | 
|  | return instructionIterator.next().isReturnVoid(); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public Code asCode() { | 
|  | return this; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void acceptHashing(HashingVisitor visitor) { | 
|  | visitor.visitInt(getCfWritableCodeKind().hashCode()); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public IRCode buildIR( | 
|  | ProgramMethod method, | 
|  | AppView<?> appView, | 
|  | Origin origin, | 
|  | MutableMethodConversionOptions conversionOptions) { | 
|  | DexMethod originalMethod = | 
|  | appView.graphLens().getOriginalMethodSignature(method.getReference()); | 
|  | DefaultInstanceInitializerSourceCode source = | 
|  | new DefaultInstanceInitializerSourceCode(originalMethod); | 
|  | return IRBuilder.create(method, appView, source, origin).build(method, conversionOptions); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public IRCode buildInliningIR( | 
|  | ProgramMethod context, | 
|  | ProgramMethod method, | 
|  | AppView<?> appView, | 
|  | GraphLens codeLens, | 
|  | NumberGenerator valueNumberGenerator, | 
|  | Position callerPosition, | 
|  | Origin origin, | 
|  | RewrittenPrototypeDescription protoChanges) { | 
|  | DexMethod originalMethod = | 
|  | appView.graphLens().getOriginalMethodSignature(method.getReference()); | 
|  | DefaultInstanceInitializerSourceCode source = | 
|  | new DefaultInstanceInitializerSourceCode(originalMethod, callerPosition); | 
|  | return IRBuilder.createForInlining( | 
|  | method, appView, codeLens, source, origin, valueNumberGenerator, protoChanges) | 
|  | .build(context, new ThrowingMethodConversionOptions(appView.options())); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public int codeSizeInBytes() { | 
|  | return DexInvokeDirect.SIZE + DexReturnVoid.SIZE; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void collectIndexedItems( | 
|  | AppView<?> appView, | 
|  | IndexedItemCollection indexedItems, | 
|  | ProgramMethod context, | 
|  | LensCodeRewriterUtils rewriter) { | 
|  | DexMethod parentConstructor = getParentConstructor(context, rewriter.dexItemFactory()); | 
|  | MethodLookupResult lookupResult = | 
|  | appView.graphLens().lookupInvokeDirect(parentConstructor, context); | 
|  | lookupResult.getReference().collectIndexedItems(appView, indexedItems); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void collectMixedSectionItems(MixedSectionCollection mixedItems) { | 
|  | // Intentionally empty. | 
|  | } | 
|  |  | 
|  | @Override | 
|  | protected int computeHashCode() { | 
|  | return System.identityHashCode(this); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | protected boolean computeEquals(Object other) { | 
|  | return this == other; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public int estimatedDexCodeSizeUpperBoundInBytes() { | 
|  | return codeSizeInBytes(); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public CfWritableCodeKind getCfWritableCodeKind() { | 
|  | return CfWritableCodeKind.DEFAULT_INSTANCE_INITIALIZER; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public DexWritableCodeKind getDexWritableCodeKind() { | 
|  | return DexWritableCodeKind.DEFAULT_INSTANCE_INITIALIZER; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public DexDebugInfoForWriting getDebugInfoForWriting() { | 
|  | return null; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public TryHandler[] getHandlers() { | 
|  | return new TryHandler[0]; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public DexString getHighestSortingString() { | 
|  | return null; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public int getIncomingRegisterSize(ProgramMethod method) { | 
|  | return getMaxLocals(method); | 
|  | } | 
|  |  | 
|  | static DexMethod getParentConstructor(DexClassAndMethod method, DexItemFactory dexItemFactory) { | 
|  | return dexItemFactory.createInstanceInitializer(method.getHolder().getSuperType()); | 
|  | } | 
|  |  | 
|  | private int getMaxLocals(ProgramMethod method) { | 
|  | int maxLocals = method.getAccessFlags().isStatic() ? 0 : 1; | 
|  | for (DexType parameter : method.getParameters()) { | 
|  | maxLocals += parameter.getRequiredRegisters(); | 
|  | } | 
|  | return maxLocals; | 
|  | } | 
|  |  | 
|  | private int getMaxStack() { | 
|  | return 1; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public int getOutgoingRegisterSize() { | 
|  | return 1; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public int getRegisterSize(ProgramMethod method) { | 
|  | return getIncomingRegisterSize(method); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public Try[] getTries() { | 
|  | return new Try[0]; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public boolean isCfWritableCode() { | 
|  | return true; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public CfWritableCode asCfWritableCode() { | 
|  | return this; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public boolean isDexWritableCode() { | 
|  | return true; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public DexWritableCode asDexWritableCode() { | 
|  | return this; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public boolean isEmptyVoidMethod() { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public boolean isDefaultInstanceInitializerCode() { | 
|  | return true; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public DefaultInstanceInitializerCode asDefaultInstanceInitializerCode() { | 
|  | return this; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public boolean isSharedCodeObject() { | 
|  | return true; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void registerCodeReferences(ProgramMethod method, UseRegistry registry) { | 
|  | internalRegisterCodeReferences(method, registry); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void registerCodeReferencesForDesugaring(ClasspathMethod method, UseRegistry registry) { | 
|  | internalRegisterCodeReferences(method, registry); | 
|  | } | 
|  |  | 
|  | private void internalRegisterCodeReferences(DexClassAndMethod method, UseRegistry<?> registry) { | 
|  | registry.registerInvokeDirect(getParentConstructor(method, registry.dexItemFactory())); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public DexWritableCode rewriteCodeWithJumboStrings( | 
|  | ProgramMethod method, ObjectToOffsetMapping mapping, DexItemFactory factory, boolean force) { | 
|  | // Intentionally empty. This piece of code does not have any const-string instructions. | 
|  | return this; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void setCallSiteContexts(ProgramMethod method) { | 
|  | // Intentionally empty. This piece of code does not have any call sites. | 
|  | } | 
|  |  | 
|  | public CfCode toCfCode(ProgramMethod method, DexItemFactory dexItemFactory) { | 
|  | return toCfCode(method, dexItemFactory, method.getHolder().getSuperType()); | 
|  | } | 
|  |  | 
|  | public CfCode toCfCode(ProgramMethod method, DexItemFactory dexItemFactory, DexType supertype) { | 
|  | List<CfInstruction> instructions = | 
|  | Arrays.asList( | 
|  | new CfLoad(ValueType.OBJECT, 0), | 
|  | new CfInvoke( | 
|  | Opcodes.INVOKESPECIAL, dexItemFactory.createInstanceInitializer(supertype), false), | 
|  | new CfReturnVoid()); | 
|  | return new CfCode(method.getHolderType(), getMaxStack(), getMaxLocals(method), instructions); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void writeCf( | 
|  | ProgramMethod method, | 
|  | CfVersion classFileVersion, | 
|  | AppView<?> appView, | 
|  | NamingLens namingLens, | 
|  | LensCodeRewriterUtils rewriter, | 
|  | MethodVisitor visitor) { | 
|  | visitor.visitVarInsn(Opcodes.ALOAD, 0); | 
|  | visitor.visitMethodInsn( | 
|  | Opcodes.INVOKESPECIAL, | 
|  | namingLens.lookupInternalName(method.getHolder().getSuperType()), | 
|  | "<init>", | 
|  | "()V", | 
|  | false); | 
|  | visitor.visitInsn(Opcodes.RETURN); | 
|  | visitor.visitEnd(); | 
|  | visitor.visitMaxs(getMaxStack(), getMaxLocals(method)); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void writeDex( | 
|  | ShortBuffer shortBuffer, | 
|  | ProgramMethod context, | 
|  | GraphLens graphLens, | 
|  | GraphLens codeLens, | 
|  | LensCodeRewriterUtils lensCodeRewriter, | 
|  | ObjectToOffsetMapping mapping) { | 
|  | DexMethod parentConstructor = getParentConstructor(context, mapping.dexItemFactory()); | 
|  | MethodLookupResult lookupResult = graphLens.lookupInvokeDirect(parentConstructor, context); | 
|  | new DexInvokeDirect(1, lookupResult.getReference(), 0, 0, 0, 0, 0) | 
|  | .write(shortBuffer, context, graphLens, codeLens, mapping, lensCodeRewriter); | 
|  | new DexReturnVoid().write(shortBuffer, context, graphLens, codeLens, mapping, lensCodeRewriter); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void writeKeepRulesForDesugaredLibrary(CodeToKeep codeToKeep) { | 
|  | // Intentionally empty. | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public String toString() { | 
|  | return "DefaultInstanceInitializerCode"; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public String toString(DexEncodedMethod method, RetracerForCodePrinting retracer) { | 
|  | return toString(); | 
|  | } | 
|  |  | 
|  | static class DefaultInstanceInitializerSourceCode extends SyntheticStraightLineSourceCode { | 
|  |  | 
|  | DefaultInstanceInitializerSourceCode(DexMethod method) { | 
|  | this(method, null); | 
|  | } | 
|  |  | 
|  | DefaultInstanceInitializerSourceCode(DexMethod method, Position callerPosition) { | 
|  | super( | 
|  | getInstructionBuilders(), | 
|  | SyntheticPosition.builder() | 
|  | .setLine(0) | 
|  | .setMethod(method) | 
|  | .setCallerPosition(callerPosition) | 
|  | .build()); | 
|  | } | 
|  |  | 
|  | private static List<Consumer<IRBuilder>> getInstructionBuilders() { | 
|  | return ImmutableList.of( | 
|  | builder -> | 
|  | builder.add( | 
|  | com.android.tools.r8.ir.code.InvokeDirect.builder() | 
|  | .setMethod( | 
|  | getParentConstructor( | 
|  | builder.getProgramMethod(), builder.dexItemFactory())) | 
|  | .setSingleArgument(builder.getReceiverValue()) | 
|  | .build()), | 
|  | IRBuilder::addReturn); | 
|  | } | 
|  | } | 
|  | } |