|  | // Copyright (c) 2018, 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.code.CfArithmeticBinop; | 
|  | import com.android.tools.r8.cf.code.CfArrayLength; | 
|  | import com.android.tools.r8.cf.code.CfArrayLoad; | 
|  | import com.android.tools.r8.cf.code.CfArrayStore; | 
|  | import com.android.tools.r8.cf.code.CfCheckCast; | 
|  | import com.android.tools.r8.cf.code.CfCmp; | 
|  | import com.android.tools.r8.cf.code.CfConstClass; | 
|  | import com.android.tools.r8.cf.code.CfConstMethodHandle; | 
|  | import com.android.tools.r8.cf.code.CfConstMethodType; | 
|  | import com.android.tools.r8.cf.code.CfConstNull; | 
|  | import com.android.tools.r8.cf.code.CfConstNumber; | 
|  | import com.android.tools.r8.cf.code.CfConstString; | 
|  | import com.android.tools.r8.cf.code.CfFieldInstruction; | 
|  | import com.android.tools.r8.cf.code.CfFrame; | 
|  | import com.android.tools.r8.cf.code.CfFrame.FrameType; | 
|  | import com.android.tools.r8.cf.code.CfGoto; | 
|  | import com.android.tools.r8.cf.code.CfIf; | 
|  | import com.android.tools.r8.cf.code.CfIfCmp; | 
|  | import com.android.tools.r8.cf.code.CfIinc; | 
|  | import com.android.tools.r8.cf.code.CfInstanceOf; | 
|  | import com.android.tools.r8.cf.code.CfInstruction; | 
|  | import com.android.tools.r8.cf.code.CfInvoke; | 
|  | import com.android.tools.r8.cf.code.CfInvokeDynamic; | 
|  | import com.android.tools.r8.cf.code.CfJsrRet; | 
|  | import com.android.tools.r8.cf.code.CfLabel; | 
|  | import com.android.tools.r8.cf.code.CfLoad; | 
|  | import com.android.tools.r8.cf.code.CfLogicalBinop; | 
|  | import com.android.tools.r8.cf.code.CfMonitor; | 
|  | import com.android.tools.r8.cf.code.CfMultiANewArray; | 
|  | import com.android.tools.r8.cf.code.CfNeg; | 
|  | import com.android.tools.r8.cf.code.CfNew; | 
|  | import com.android.tools.r8.cf.code.CfNewArray; | 
|  | import com.android.tools.r8.cf.code.CfNop; | 
|  | import com.android.tools.r8.cf.code.CfNumberConversion; | 
|  | import com.android.tools.r8.cf.code.CfPosition; | 
|  | import com.android.tools.r8.cf.code.CfReturn; | 
|  | import com.android.tools.r8.cf.code.CfReturnVoid; | 
|  | import com.android.tools.r8.cf.code.CfStackInstruction; | 
|  | import com.android.tools.r8.cf.code.CfStore; | 
|  | import com.android.tools.r8.cf.code.CfSwitch; | 
|  | import com.android.tools.r8.cf.code.CfThrow; | 
|  | import com.android.tools.r8.cf.code.CfTryCatch; | 
|  | import com.android.tools.r8.errors.CompilationError; | 
|  | import com.android.tools.r8.errors.Unimplemented; | 
|  | import com.android.tools.r8.errors.Unreachable; | 
|  | import com.android.tools.r8.graph.CfCode.LocalVariableInfo; | 
|  | import com.android.tools.r8.graph.JarClassFileReader.ReparseContext; | 
|  | import com.android.tools.r8.ir.code.IRCode; | 
|  | import com.android.tools.r8.ir.code.If; | 
|  | import com.android.tools.r8.ir.code.MemberType; | 
|  | import com.android.tools.r8.ir.code.Monitor; | 
|  | 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.MethodProcessor; | 
|  | import com.android.tools.r8.naming.ClassNameMapper; | 
|  | import com.android.tools.r8.origin.Origin; | 
|  | import com.android.tools.r8.position.MethodPosition; | 
|  | import com.android.tools.r8.shaking.ProguardConfiguration; | 
|  | import com.android.tools.r8.shaking.ProguardKeepAttributes; | 
|  | import com.android.tools.r8.utils.InternalOptions; | 
|  | import it.unimi.dsi.fastutil.ints.Int2ReferenceAVLTreeMap; | 
|  | import it.unimi.dsi.fastutil.ints.Int2ReferenceMap; | 
|  | import it.unimi.dsi.fastutil.ints.Int2ReferenceSortedMap; | 
|  | import java.util.ArrayList; | 
|  | import java.util.Collections; | 
|  | import java.util.HashMap; | 
|  | import java.util.IdentityHashMap; | 
|  | import java.util.List; | 
|  | import java.util.Map; | 
|  | import java.util.function.BiFunction; | 
|  | import org.objectweb.asm.ClassReader; | 
|  | import org.objectweb.asm.ClassVisitor; | 
|  | import org.objectweb.asm.ConstantDynamic; | 
|  | import org.objectweb.asm.Handle; | 
|  | import org.objectweb.asm.Label; | 
|  | import org.objectweb.asm.MethodVisitor; | 
|  | import org.objectweb.asm.Opcodes; | 
|  | import org.objectweb.asm.Type; | 
|  | import org.objectweb.asm.commons.JSRInlinerAdapter; | 
|  |  | 
|  | public class LazyCfCode extends Code { | 
|  |  | 
|  | private static class JsrEncountered extends RuntimeException { | 
|  | public JsrEncountered(String s) { | 
|  | super(s); | 
|  | } | 
|  | } | 
|  |  | 
|  | public LazyCfCode( | 
|  | DexMethod method, Origin origin, ReparseContext context, JarApplicationReader application) { | 
|  | this.origin = origin; | 
|  | this.context = context; | 
|  | this.application = application; | 
|  | context.codeList.add(this); | 
|  | } | 
|  |  | 
|  | private final Origin origin; | 
|  | private JarApplicationReader application; | 
|  | private CfCode code; | 
|  | protected ReparseContext context; | 
|  | private boolean reachabilitySensitive = false; | 
|  |  | 
|  | public void markReachabilitySensitive() { | 
|  | assert code == null; | 
|  | reachabilitySensitive = true; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public boolean isCfCode() { | 
|  | return true; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public LazyCfCode asLazyCfCode() { | 
|  | return this; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public CfCode asCfCode() { | 
|  | if (code == null) { | 
|  | ReparseContext context = this.context; | 
|  | JarApplicationReader application = this.application; | 
|  | assert application != null; | 
|  | assert context != null; | 
|  | // The ClassCodeVisitor is in charge of setting this.context to null. | 
|  | try { | 
|  | parseCode(context, false); | 
|  | } catch (JsrEncountered e) { | 
|  | for (Code code : context.codeList) { | 
|  | code.asLazyCfCode().code = null; | 
|  | code.asLazyCfCode().context = context; | 
|  | code.asLazyCfCode().application = application; | 
|  | } | 
|  | try { | 
|  | parseCode(context, true); | 
|  | } catch (JsrEncountered e1) { | 
|  | throw new Unreachable(e1); | 
|  | } | 
|  | } | 
|  | assert verifyNoReparseContext(context.owner); | 
|  | } | 
|  | assert code != null; | 
|  | return code; | 
|  | } | 
|  |  | 
|  | public static class DebugParsingOptions { | 
|  | public final boolean lineInfo; | 
|  | public final boolean localInfo; | 
|  | public final int asmReaderOptions; | 
|  |  | 
|  | public DebugParsingOptions(boolean lineInfo, boolean localInfo, int asmReaderOptions) { | 
|  | this.lineInfo = lineInfo; | 
|  | this.localInfo = localInfo; | 
|  | this.asmReaderOptions = asmReaderOptions; | 
|  | } | 
|  | } | 
|  |  | 
|  | public void parseCode(ReparseContext context, boolean useJsrInliner) { | 
|  | DebugParsingOptions parsingOptions = getParsingOptions(application, reachabilitySensitive); | 
|  |  | 
|  | ClassCodeVisitor classVisitor = | 
|  | new ClassCodeVisitor( | 
|  | context.owner, | 
|  | createCodeLocator(context), | 
|  | application, | 
|  | useJsrInliner, | 
|  | origin, | 
|  | parsingOptions); | 
|  | new ClassReader(context.classCache).accept(classVisitor, parsingOptions.asmReaderOptions); | 
|  | } | 
|  |  | 
|  | private void setCode(CfCode code) { | 
|  | assert this.code == null; | 
|  | assert this.context != null; | 
|  | this.code = code; | 
|  | this.context = null; | 
|  | this.application = null; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | protected int computeHashCode() { | 
|  | throw new Unimplemented(); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | protected boolean computeEquals(Object other) { | 
|  | throw new Unimplemented(); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public boolean isEmptyVoidMethod() { | 
|  | return asCfCode().isEmptyVoidMethod(); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public int estimatedSizeForInlining() { | 
|  | return asCfCode().estimatedSizeForInlining(); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public boolean estimatedSizeForInliningAtMost(int threshold) { | 
|  | return asCfCode().estimatedSizeForInliningAtMost(threshold); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public IRCode buildIR(ProgramMethod method, AppView<?> appView, Origin origin) { | 
|  | return asCfCode().buildIR(method, appView, origin); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public IRCode buildInliningIR( | 
|  | ProgramMethod context, | 
|  | ProgramMethod method, | 
|  | AppView<?> appView, | 
|  | ValueNumberGenerator valueNumberGenerator, | 
|  | Position callerPosition, | 
|  | Origin origin, | 
|  | MethodProcessor methodProcessor) { | 
|  | return asCfCode() | 
|  | .buildInliningIR( | 
|  | context, | 
|  | method, | 
|  | appView, | 
|  | valueNumberGenerator, | 
|  | callerPosition, | 
|  | origin, | 
|  | methodProcessor); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void registerCodeReferences(ProgramMethod method, UseRegistry registry) { | 
|  | asCfCode().registerCodeReferences(method, registry); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void registerCodeReferencesForDesugaring(ClasspathMethod method, UseRegistry registry) { | 
|  | asCfCode().registerCodeReferencesForDesugaring(method, registry); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void registerArgumentReferences(DexEncodedMethod method, ArgumentUse registry) { | 
|  | asCfCode().registerArgumentReferences(method, registry); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public String toString() { | 
|  | return asCfCode().toString(); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public String toString(DexEncodedMethod method, ClassNameMapper naming) { | 
|  | return asCfCode().toString(method, naming); | 
|  | } | 
|  |  | 
|  | protected BiFunction<String, String, LazyCfCode> createCodeLocator(ReparseContext context) { | 
|  | return new DefaultCodeLocator(context, application); | 
|  | } | 
|  |  | 
|  | private static class DefaultCodeLocator implements BiFunction<String, String, LazyCfCode> { | 
|  | private final ReparseContext context; | 
|  | private final JarApplicationReader application; | 
|  | private int methodIndex = 0; | 
|  |  | 
|  | private DefaultCodeLocator(ReparseContext context, JarApplicationReader application) { | 
|  | this.context = context; | 
|  | this.application = application; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public LazyCfCode apply(String name, String desc) { | 
|  | return context.codeList.get(methodIndex++).asLazyCfCode(); | 
|  | } | 
|  | } | 
|  |  | 
|  | private static class ClassCodeVisitor extends ClassVisitor { | 
|  |  | 
|  | private final DexClass clazz; | 
|  | private final BiFunction<String, String, LazyCfCode> codeLocator; | 
|  | private final JarApplicationReader application; | 
|  | private boolean usrJsrInliner; | 
|  | private final Origin origin; | 
|  | private final DebugParsingOptions debugParsingOptions; | 
|  |  | 
|  | ClassCodeVisitor( | 
|  | DexClass clazz, | 
|  | BiFunction<String, String, LazyCfCode> codeLocator, | 
|  | JarApplicationReader application, | 
|  | boolean useJsrInliner, | 
|  | Origin origin, | 
|  | DebugParsingOptions debugParsingOptions) { | 
|  | super(InternalOptions.ASM_VERSION); | 
|  | this.clazz = clazz; | 
|  | this.codeLocator = codeLocator; | 
|  | this.application = application; | 
|  | this.usrJsrInliner = useJsrInliner; | 
|  | this.origin = origin; | 
|  | this.debugParsingOptions = debugParsingOptions; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public MethodVisitor visitMethod( | 
|  | int access, String name, String desc, String signature, String[] exceptions) { | 
|  | MethodAccessFlags flags = JarClassFileReader.createMethodAccessFlags(name, access); | 
|  | if (!flags.isAbstract() && !flags.isNative()) { | 
|  | LazyCfCode code = codeLocator.apply(name, desc); | 
|  | if (code != null) { | 
|  | DexMethod method = application.getMethod(clazz.type, name, desc); | 
|  | MethodCodeVisitor methodVisitor = | 
|  | new MethodCodeVisitor(application, method, code, origin, debugParsingOptions); | 
|  | if (!usrJsrInliner) { | 
|  | return methodVisitor; | 
|  | } | 
|  | return new JSRInlinerAdapter(methodVisitor, access, name, desc, signature, exceptions); | 
|  | } | 
|  | } | 
|  | return null; | 
|  | } | 
|  | } | 
|  |  | 
|  | private static class MethodCodeVisitor extends MethodVisitor { | 
|  | private final JarApplicationReader application; | 
|  | private final DexItemFactory factory; | 
|  | private final DebugParsingOptions debugParsingOptions; | 
|  | private int maxStack; | 
|  | private int maxLocals; | 
|  | private List<CfInstruction> instructions; | 
|  | private List<CfTryCatch> tryCatchRanges; | 
|  | private List<LocalVariableInfo> localVariables; | 
|  | private final Map<DebugLocalInfo, DebugLocalInfo> canonicalDebugLocalInfo = new HashMap<>(); | 
|  | private Map<Label, CfLabel> labelMap; | 
|  | private final LazyCfCode code; | 
|  | private final DexMethod method; | 
|  | private final Origin origin; | 
|  |  | 
|  | MethodCodeVisitor( | 
|  | JarApplicationReader application, | 
|  | DexMethod method, | 
|  | LazyCfCode code, | 
|  | Origin origin, | 
|  | DebugParsingOptions debugParsingOptions) { | 
|  | super(InternalOptions.ASM_VERSION); | 
|  | this.debugParsingOptions = debugParsingOptions; | 
|  | assert code != null; | 
|  | this.application = application; | 
|  | this.factory = application.getFactory(); | 
|  | this.code = code; | 
|  | this.method = method; | 
|  | this.origin = origin; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void visitCode() { | 
|  | maxStack = 0; | 
|  | maxLocals = 0; | 
|  | instructions = new ArrayList<>(); | 
|  | tryCatchRanges = new ArrayList<>(); | 
|  | localVariables = new ArrayList<>(); | 
|  | labelMap = new IdentityHashMap<>(); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void visitEnd() { | 
|  | if (instructions == null) { | 
|  | // Everything that is initialized at `visitCode` should be null too. | 
|  | assert tryCatchRanges == null && localVariables == null && labelMap == null; | 
|  | // This code visitor is used only if the method is neither abstract nor native, hence it | 
|  | // should have exactly one Code attribute: | 
|  | // https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.7.3 | 
|  | throw new CompilationError( | 
|  | "Absent Code attribute in method that is not native or abstract", | 
|  | origin, | 
|  | new MethodPosition(method.asMethodReference())); | 
|  | } | 
|  | code.setCode( | 
|  | new CfCode( | 
|  | method.holder, maxStack, maxLocals, instructions, tryCatchRanges, localVariables)); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void visitFrame( | 
|  | int frameType, int nLocals, Object[] localTypes, int nStack, Object[] stackTypes) { | 
|  | assert frameType == Opcodes.F_NEW; | 
|  | Int2ReferenceSortedMap<FrameType> parsedLocals = parseLocals(nLocals, localTypes); | 
|  | List<FrameType> parsedStack = parseStack(nStack, stackTypes); | 
|  | instructions.add(new CfFrame(parsedLocals, parsedStack)); | 
|  | } | 
|  |  | 
|  | private Int2ReferenceSortedMap<FrameType> parseLocals(int typeCount, Object[] asmTypes) { | 
|  | Int2ReferenceSortedMap<FrameType> types = new Int2ReferenceAVLTreeMap<>(); | 
|  | int i = 0; | 
|  | for (int j = 0; j < typeCount; j++) { | 
|  | Object localType = asmTypes[j]; | 
|  | FrameType value = getFrameType(localType); | 
|  | types.put(i++, value); | 
|  | if (value.isWide()) { | 
|  | i++; | 
|  | } | 
|  | } | 
|  | return types; | 
|  | } | 
|  |  | 
|  | private List<FrameType> parseStack(int nStack, Object[] stackTypes) { | 
|  | List<FrameType> dexStack = new ArrayList<>(nStack); | 
|  | for (int i = 0; i < nStack; i++) { | 
|  | dexStack.add(getFrameType(stackTypes[i])); | 
|  | } | 
|  | return dexStack; | 
|  | } | 
|  |  | 
|  | private FrameType getFrameType(Object localType) { | 
|  | if (localType instanceof Label) { | 
|  | return FrameType.uninitializedNew(getLabel((Label) localType)); | 
|  | } else if (localType == Opcodes.UNINITIALIZED_THIS) { | 
|  | return FrameType.uninitializedThis(); | 
|  | } else if (localType == null || localType == Opcodes.TOP) { | 
|  | return FrameType.top(); | 
|  | } else { | 
|  | return FrameType.initialized(parseAsmType(localType)); | 
|  | } | 
|  | } | 
|  |  | 
|  | private CfLabel getLabel(Label label) { | 
|  | return labelMap.computeIfAbsent(label, l -> new CfLabel()); | 
|  | } | 
|  |  | 
|  | private DexType parseAsmType(Object local) { | 
|  | assert local != null && local != Opcodes.TOP; | 
|  | if (local == Opcodes.INTEGER) { | 
|  | return factory.intType; | 
|  | } else if (local == Opcodes.FLOAT) { | 
|  | return factory.floatType; | 
|  | } else if (local == Opcodes.LONG) { | 
|  | return factory.longType; | 
|  | } else if (local == Opcodes.DOUBLE) { | 
|  | return factory.doubleType; | 
|  | } else if (local == Opcodes.NULL) { | 
|  | return DexItemFactory.nullValueType; | 
|  | } else if (local instanceof String) { | 
|  | return createTypeFromInternalType((String) local); | 
|  | } else { | 
|  | throw new Unreachable("Unexpected ASM type: " + local); | 
|  | } | 
|  | } | 
|  |  | 
|  | private DexType createTypeFromInternalType(String local) { | 
|  | assert local.indexOf('.') == -1; | 
|  | return factory.createType(Type.getObjectType(local).getDescriptor()); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void visitInsn(int opcode) { | 
|  | switch (opcode) { | 
|  | case Opcodes.NOP: | 
|  | instructions.add(new CfNop()); | 
|  | break; | 
|  | case Opcodes.ACONST_NULL: | 
|  | instructions.add(new CfConstNull()); | 
|  | break; | 
|  | case Opcodes.ICONST_M1: | 
|  | case Opcodes.ICONST_0: | 
|  | case Opcodes.ICONST_1: | 
|  | case Opcodes.ICONST_2: | 
|  | case Opcodes.ICONST_3: | 
|  | case Opcodes.ICONST_4: | 
|  | case Opcodes.ICONST_5: | 
|  | instructions.add(new CfConstNumber(opcode - Opcodes.ICONST_0, ValueType.INT)); | 
|  | break; | 
|  | case Opcodes.LCONST_0: | 
|  | case Opcodes.LCONST_1: | 
|  | instructions.add(new CfConstNumber(opcode - Opcodes.LCONST_0, ValueType.LONG)); | 
|  | break; | 
|  | case Opcodes.FCONST_0: | 
|  | case Opcodes.FCONST_1: | 
|  | case Opcodes.FCONST_2: | 
|  | instructions.add( | 
|  | new CfConstNumber( | 
|  | Float.floatToRawIntBits(opcode - Opcodes.FCONST_0), ValueType.FLOAT)); | 
|  | break; | 
|  | case Opcodes.DCONST_0: | 
|  | case Opcodes.DCONST_1: | 
|  | instructions.add( | 
|  | new CfConstNumber( | 
|  | Double.doubleToRawLongBits(opcode - Opcodes.DCONST_0), ValueType.DOUBLE)); | 
|  | break; | 
|  | case Opcodes.IALOAD: | 
|  | case Opcodes.LALOAD: | 
|  | case Opcodes.FALOAD: | 
|  | case Opcodes.DALOAD: | 
|  | case Opcodes.AALOAD: | 
|  | case Opcodes.BALOAD: | 
|  | case Opcodes.CALOAD: | 
|  | case Opcodes.SALOAD: | 
|  | instructions.add(new CfArrayLoad(getMemberTypeForOpcode(opcode))); | 
|  | break; | 
|  | case Opcodes.IASTORE: | 
|  | case Opcodes.LASTORE: | 
|  | case Opcodes.FASTORE: | 
|  | case Opcodes.DASTORE: | 
|  | case Opcodes.AASTORE: | 
|  | case Opcodes.BASTORE: | 
|  | case Opcodes.CASTORE: | 
|  | case Opcodes.SASTORE: | 
|  | instructions.add(new CfArrayStore(getMemberTypeForOpcode(opcode))); | 
|  | break; | 
|  | case Opcodes.POP: | 
|  | case Opcodes.POP2: | 
|  | case Opcodes.DUP: | 
|  | case Opcodes.DUP_X1: | 
|  | case Opcodes.DUP_X2: | 
|  | case Opcodes.DUP2: | 
|  | case Opcodes.DUP2_X1: | 
|  | case Opcodes.DUP2_X2: | 
|  | case Opcodes.SWAP: | 
|  | instructions.add(CfStackInstruction.fromAsm(opcode)); | 
|  | break; | 
|  | case Opcodes.IADD: | 
|  | case Opcodes.LADD: | 
|  | case Opcodes.FADD: | 
|  | case Opcodes.DADD: | 
|  | case Opcodes.ISUB: | 
|  | case Opcodes.LSUB: | 
|  | case Opcodes.FSUB: | 
|  | case Opcodes.DSUB: | 
|  | case Opcodes.IMUL: | 
|  | case Opcodes.LMUL: | 
|  | case Opcodes.FMUL: | 
|  | case Opcodes.DMUL: | 
|  | case Opcodes.IDIV: | 
|  | case Opcodes.LDIV: | 
|  | case Opcodes.FDIV: | 
|  | case Opcodes.DDIV: | 
|  | case Opcodes.IREM: | 
|  | case Opcodes.LREM: | 
|  | case Opcodes.FREM: | 
|  | case Opcodes.DREM: | 
|  | instructions.add(CfArithmeticBinop.fromAsm(opcode)); | 
|  | break; | 
|  | case Opcodes.INEG: | 
|  | case Opcodes.LNEG: | 
|  | case Opcodes.FNEG: | 
|  | case Opcodes.DNEG: | 
|  | instructions.add(CfNeg.fromAsm(opcode)); | 
|  | break; | 
|  | case Opcodes.ISHL: | 
|  | case Opcodes.LSHL: | 
|  | case Opcodes.ISHR: | 
|  | case Opcodes.LSHR: | 
|  | case Opcodes.IUSHR: | 
|  | case Opcodes.LUSHR: | 
|  | case Opcodes.IAND: | 
|  | case Opcodes.LAND: | 
|  | case Opcodes.IOR: | 
|  | case Opcodes.LOR: | 
|  | case Opcodes.IXOR: | 
|  | case Opcodes.LXOR: | 
|  | instructions.add(CfLogicalBinop.fromAsm(opcode)); | 
|  | break; | 
|  | case Opcodes.I2L: | 
|  | case Opcodes.I2F: | 
|  | case Opcodes.I2D: | 
|  | case Opcodes.L2I: | 
|  | case Opcodes.L2F: | 
|  | case Opcodes.L2D: | 
|  | case Opcodes.F2I: | 
|  | case Opcodes.F2L: | 
|  | case Opcodes.F2D: | 
|  | case Opcodes.D2I: | 
|  | case Opcodes.D2L: | 
|  | case Opcodes.D2F: | 
|  | case Opcodes.I2B: | 
|  | case Opcodes.I2C: | 
|  | case Opcodes.I2S: | 
|  | instructions.add(CfNumberConversion.fromAsm(opcode)); | 
|  | break; | 
|  | case Opcodes.LCMP: | 
|  | case Opcodes.FCMPL: | 
|  | case Opcodes.FCMPG: | 
|  | case Opcodes.DCMPL: | 
|  | case Opcodes.DCMPG: | 
|  | instructions.add(CfCmp.fromAsm(opcode)); | 
|  | break; | 
|  | case Opcodes.IRETURN: | 
|  | instructions.add(new CfReturn(ValueType.INT)); | 
|  | break; | 
|  | case Opcodes.LRETURN: | 
|  | instructions.add(new CfReturn(ValueType.LONG)); | 
|  | break; | 
|  | case Opcodes.FRETURN: | 
|  | instructions.add(new CfReturn(ValueType.FLOAT)); | 
|  | break; | 
|  | case Opcodes.DRETURN: | 
|  | instructions.add(new CfReturn(ValueType.DOUBLE)); | 
|  | break; | 
|  | case Opcodes.ARETURN: | 
|  | instructions.add(new CfReturn(ValueType.OBJECT)); | 
|  | break; | 
|  | case Opcodes.RETURN: | 
|  | instructions.add(new CfReturnVoid()); | 
|  | break; | 
|  | case Opcodes.ARRAYLENGTH: | 
|  | instructions.add(new CfArrayLength()); | 
|  | break; | 
|  | case Opcodes.ATHROW: | 
|  | instructions.add(new CfThrow()); | 
|  | break; | 
|  | case Opcodes.MONITORENTER: | 
|  | instructions.add(new CfMonitor(Monitor.Type.ENTER)); | 
|  | break; | 
|  | case Opcodes.MONITOREXIT: | 
|  | instructions.add(new CfMonitor(Monitor.Type.EXIT)); | 
|  | break; | 
|  | default: | 
|  | throw new Unreachable("Unknown instruction"); | 
|  | } | 
|  | } | 
|  |  | 
|  | private static MemberType getMemberTypeForOpcode(int opcode) { | 
|  | switch (opcode) { | 
|  | case Opcodes.IALOAD: | 
|  | case Opcodes.IASTORE: | 
|  | return MemberType.INT; | 
|  | case Opcodes.FALOAD: | 
|  | case Opcodes.FASTORE: | 
|  | return MemberType.FLOAT; | 
|  | case Opcodes.LALOAD: | 
|  | case Opcodes.LASTORE: | 
|  | return MemberType.LONG; | 
|  | case Opcodes.DALOAD: | 
|  | case Opcodes.DASTORE: | 
|  | return MemberType.DOUBLE; | 
|  | case Opcodes.AALOAD: | 
|  | case Opcodes.AASTORE: | 
|  | return MemberType.OBJECT; | 
|  | case Opcodes.BALOAD: | 
|  | case Opcodes.BASTORE: | 
|  | return MemberType.BOOLEAN_OR_BYTE; | 
|  | case Opcodes.CALOAD: | 
|  | case Opcodes.CASTORE: | 
|  | return MemberType.CHAR; | 
|  | case Opcodes.SALOAD: | 
|  | case Opcodes.SASTORE: | 
|  | return MemberType.SHORT; | 
|  | default: | 
|  | throw new Unreachable("Unexpected array opcode " + opcode); | 
|  | } | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void visitIntInsn(int opcode, int operand) { | 
|  | switch (opcode) { | 
|  | case Opcodes.SIPUSH: | 
|  | case Opcodes.BIPUSH: | 
|  | instructions.add(new CfConstNumber(operand, ValueType.INT)); | 
|  | break; | 
|  | case Opcodes.NEWARRAY: | 
|  | instructions.add( | 
|  | new CfNewArray(factory.createArrayType(1, arrayTypeDesc(operand, factory)))); | 
|  | break; | 
|  | default: | 
|  | throw new Unreachable("Unexpected int opcode " + opcode); | 
|  | } | 
|  | } | 
|  |  | 
|  | private static DexType arrayTypeDesc(int arrayTypeCode, DexItemFactory factory) { | 
|  | switch (arrayTypeCode) { | 
|  | case Opcodes.T_BOOLEAN: | 
|  | return factory.booleanType; | 
|  | case Opcodes.T_CHAR: | 
|  | return factory.charType; | 
|  | case Opcodes.T_FLOAT: | 
|  | return factory.floatType; | 
|  | case Opcodes.T_DOUBLE: | 
|  | return factory.doubleType; | 
|  | case Opcodes.T_BYTE: | 
|  | return factory.byteType; | 
|  | case Opcodes.T_SHORT: | 
|  | return factory.shortType; | 
|  | case Opcodes.T_INT: | 
|  | return factory.intType; | 
|  | case Opcodes.T_LONG: | 
|  | return factory.longType; | 
|  | default: | 
|  | throw new Unreachable("Unexpected array-type code " + arrayTypeCode); | 
|  | } | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void visitVarInsn(int opcode, int var) { | 
|  | ValueType type; | 
|  | switch (opcode) { | 
|  | case Opcodes.ILOAD: | 
|  | case Opcodes.ISTORE: | 
|  | type = ValueType.INT; | 
|  | break; | 
|  | case Opcodes.FLOAD: | 
|  | case Opcodes.FSTORE: | 
|  | type = ValueType.FLOAT; | 
|  | break; | 
|  | case Opcodes.LLOAD: | 
|  | case Opcodes.LSTORE: | 
|  | type = ValueType.LONG; | 
|  | break; | 
|  | case Opcodes.DLOAD: | 
|  | case Opcodes.DSTORE: | 
|  | type = ValueType.DOUBLE; | 
|  | break; | 
|  | case Opcodes.ALOAD: | 
|  | case Opcodes.ASTORE: | 
|  | type = ValueType.OBJECT; | 
|  | break; | 
|  | case Opcodes.RET: | 
|  | { | 
|  | instructions.add(new CfJsrRet(var)); | 
|  | return; | 
|  | } | 
|  | default: | 
|  | throw new Unreachable("Unexpected VarInsn opcode: " + opcode); | 
|  | } | 
|  | if (Opcodes.ILOAD <= opcode && opcode <= Opcodes.ALOAD) { | 
|  | instructions.add(new CfLoad(type, var)); | 
|  | } else { | 
|  | instructions.add(new CfStore(type, var)); | 
|  | } | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void visitTypeInsn(int opcode, String typeName) { | 
|  | DexType type = factory.createType(Type.getObjectType(typeName).getDescriptor()); | 
|  | switch (opcode) { | 
|  | case Opcodes.NEW: | 
|  | instructions.add(new CfNew(type)); | 
|  | break; | 
|  | case Opcodes.ANEWARRAY: | 
|  | instructions.add(new CfNewArray(factory.createArrayType(1, type))); | 
|  | break; | 
|  | case Opcodes.CHECKCAST: | 
|  | instructions.add(new CfCheckCast(type)); | 
|  | break; | 
|  | case Opcodes.INSTANCEOF: | 
|  | instructions.add(new CfInstanceOf(type)); | 
|  | break; | 
|  | default: | 
|  | throw new Unreachable("Unexpected TypeInsn opcode: " + opcode); | 
|  | } | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void visitFieldInsn(int opcode, String owner, String name, String desc) { | 
|  | DexField field = | 
|  | factory.createField(createTypeFromInternalType(owner), factory.createType(desc), name); | 
|  | // TODO(mathiasr): Don't require CfFieldInstruction::declaringField. It is needed for proper | 
|  | // renaming in the backend, but it is not available here in the frontend. | 
|  | instructions.add(new CfFieldInstruction(opcode, field, field)); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) { | 
|  | DexMethod method = application.getMethod(owner, name, desc); | 
|  | instructions.add(new CfInvoke(opcode, method, itf)); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void visitInvokeDynamicInsn(String name, String desc, Handle bsm, Object... bsmArgs) { | 
|  | DexCallSite callSite = | 
|  | DexCallSite.fromAsmInvokeDynamic(application, method.holder, name, desc, bsm, bsmArgs); | 
|  | instructions.add(new CfInvokeDynamic(callSite)); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void visitJumpInsn(int opcode, Label label) { | 
|  | CfLabel target = getLabel(label); | 
|  | if (Opcodes.IFEQ <= opcode && opcode <= Opcodes.IF_ACMPNE) { | 
|  | if (opcode <= Opcodes.IFLE) { | 
|  | // IFEQ, IFNE, IFLT, IFGE, IFGT, or IFLE. | 
|  | instructions.add(new CfIf(ifType(opcode), ValueType.INT, target)); | 
|  | } else { | 
|  | // IF_ICMPEQ, IF_ICMPNE, IF_ICMPLT, IF_ICMPGE, IF_ICMPGT, IF_ICMPLE, IF_ACMPEQ, or | 
|  | // IF_ACMPNE. | 
|  | ValueType valueType; | 
|  | if (opcode <= Opcodes.IF_ICMPLE) { | 
|  | valueType = ValueType.INT; | 
|  | } else { | 
|  | valueType = ValueType.OBJECT; | 
|  | } | 
|  | instructions.add(new CfIfCmp(ifType(opcode), valueType, target)); | 
|  | } | 
|  | } else { | 
|  | // GOTO, JSR, IFNULL or IFNONNULL. | 
|  | switch (opcode) { | 
|  | case Opcodes.GOTO: | 
|  | instructions.add(new CfGoto(target)); | 
|  | break; | 
|  | case Opcodes.IFNULL: | 
|  | case Opcodes.IFNONNULL: | 
|  | If.Type type = opcode == Opcodes.IFNULL ? If.Type.EQ : If.Type.NE; | 
|  | instructions.add(new CfIf(type, ValueType.OBJECT, target)); | 
|  | break; | 
|  | case Opcodes.JSR: | 
|  | throw new JsrEncountered("JSR should be handled by the ASM jsr inliner"); | 
|  | default: | 
|  | throw new Unreachable("Unexpected JumpInsn opcode: " + opcode); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | private static If.Type ifType(int opcode) { | 
|  | switch (opcode) { | 
|  | case Opcodes.IFEQ: | 
|  | case Opcodes.IF_ICMPEQ: | 
|  | case Opcodes.IF_ACMPEQ: | 
|  | return If.Type.EQ; | 
|  | case Opcodes.IFNE: | 
|  | case Opcodes.IF_ICMPNE: | 
|  | case Opcodes.IF_ACMPNE: | 
|  | return If.Type.NE; | 
|  | case Opcodes.IFLT: | 
|  | case Opcodes.IF_ICMPLT: | 
|  | return If.Type.LT; | 
|  | case Opcodes.IFGE: | 
|  | case Opcodes.IF_ICMPGE: | 
|  | return If.Type.GE; | 
|  | case Opcodes.IFGT: | 
|  | case Opcodes.IF_ICMPGT: | 
|  | return If.Type.GT; | 
|  | case Opcodes.IFLE: | 
|  | case Opcodes.IF_ICMPLE: | 
|  | return If.Type.LE; | 
|  | default: | 
|  | throw new Unreachable("Unexpected If instruction opcode: " + opcode); | 
|  | } | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void visitLabel(Label label) { | 
|  | instructions.add(getLabel(label)); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void visitLdcInsn(Object cst) { | 
|  | if (cst instanceof Type) { | 
|  | Type type = (Type) cst; | 
|  | if (type.getSort() == Type.METHOD) { | 
|  | DexProto proto = application.getProto(type.getDescriptor()); | 
|  | instructions.add(new CfConstMethodType(proto)); | 
|  | } else { | 
|  | instructions.add(new CfConstClass(factory.createType(type.getDescriptor()))); | 
|  | } | 
|  | } else if (cst instanceof String) { | 
|  | instructions.add(new CfConstString(factory.createString((String) cst))); | 
|  | } else if (cst instanceof Long) { | 
|  | instructions.add(new CfConstNumber((Long) cst, ValueType.LONG)); | 
|  | } else if (cst instanceof Double) { | 
|  | long l = Double.doubleToRawLongBits((Double) cst); | 
|  | instructions.add(new CfConstNumber(l, ValueType.DOUBLE)); | 
|  | } else if (cst instanceof Integer) { | 
|  | instructions.add(new CfConstNumber((Integer) cst, ValueType.INT)); | 
|  | } else if (cst instanceof Float) { | 
|  | long i = Float.floatToRawIntBits((Float) cst); | 
|  | instructions.add(new CfConstNumber(i, ValueType.FLOAT)); | 
|  | } else if (cst instanceof Handle) { | 
|  | instructions.add( | 
|  | new CfConstMethodHandle( | 
|  | DexMethodHandle.fromAsmHandle((Handle) cst, application, method.holder))); | 
|  | } else if (cst instanceof ConstantDynamic) { | 
|  | throw new CompilationError("Unsupported dynamic constant: " + cst.toString()); | 
|  | } else { | 
|  | throw new CompilationError("Unsupported constant: " + cst.toString()); | 
|  | } | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void visitIincInsn(int var, int increment) { | 
|  | instructions.add(new CfIinc(var, increment)); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void visitTableSwitchInsn(int min, int max, Label dflt, Label... labels) { | 
|  | assert max == min + labels.length - 1; | 
|  | ArrayList<CfLabel> targets = new ArrayList<>(labels.length); | 
|  | for (Label label : labels) { | 
|  | targets.add(getLabel(label)); | 
|  | } | 
|  | instructions.add(new CfSwitch(CfSwitch.Kind.TABLE, getLabel(dflt), new int[] {min}, targets)); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels) { | 
|  | ArrayList<CfLabel> targets = new ArrayList<>(labels.length); | 
|  | for (Label label : labels) { | 
|  | targets.add(getLabel(label)); | 
|  | } | 
|  | instructions.add(new CfSwitch(CfSwitch.Kind.LOOKUP, getLabel(dflt), keys, targets)); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void visitMultiANewArrayInsn(String desc, int dims) { | 
|  | if (!application.options.isGeneratingDex()) { | 
|  | instructions.add(new CfMultiANewArray(factory.createType(desc), dims)); | 
|  | return; | 
|  | } | 
|  | // When generating DEX code a multianewarray is desugared to a reflective creation. | 
|  | // The stack transformation is: | 
|  | //   ..., count1, ..., countN (where N = dims) | 
|  | //   -> | 
|  | //   ..., arrayref(of type : desc) | 
|  | // | 
|  | // This is unfolded to a call to java.lang.reflect.Array.newInstance to the same effect: | 
|  | // ..., count1, ..., countN | 
|  | visitLdcInsn(dims); | 
|  | // ..., count1, ..., countN, dims | 
|  | visitIntInsn(Opcodes.NEWARRAY, Opcodes.T_INT); | 
|  | // ..., count1, ..., countN, dim-array | 
|  | for (int i = dims - 1; i >= 0; i--) { | 
|  | visitInsn(Opcodes.DUP_X1); | 
|  | // ..., count1, ..., dim-array, countN, dim-array | 
|  | visitInsn(Opcodes.SWAP); | 
|  | // ..., count1, ..., dim-array, dim-array, countN | 
|  | visitLdcInsn(i); | 
|  | // ..., count1, ..., dim-array, dim-array, countN, index | 
|  | visitInsn(Opcodes.SWAP); | 
|  | // ..., count1, ..., dim-array, dim-array, index, countN | 
|  | visitInsn(Opcodes.IASTORE); | 
|  | // ..., count1, ..., dim-array | 
|  | } | 
|  | visitLdcInsn(Type.getType(desc.substring(dims))); | 
|  | // ..., dim-array, dim-member-type | 
|  | visitInsn(Opcodes.SWAP); | 
|  | // ..., dim-member-type, dim-array | 
|  | visitMethodInsn( | 
|  | Opcodes.INVOKESTATIC, | 
|  | "java/lang/reflect/Array", | 
|  | "newInstance", | 
|  | "(Ljava/lang/Class;[I)Ljava/lang/Object;", | 
|  | false); | 
|  | // ..., ref | 
|  | visitTypeInsn(Opcodes.CHECKCAST, desc); | 
|  | // ..., arrayref(of type : desc) | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void visitTryCatchBlock(Label start, Label end, Label handler, String type) { | 
|  | List<DexType> guards = | 
|  | Collections.singletonList( | 
|  | type == null ? factory.throwableType : createTypeFromInternalType(type)); | 
|  | List<CfLabel> targets = Collections.singletonList(getLabel(handler)); | 
|  | tryCatchRanges.add(new CfTryCatch(getLabel(start), getLabel(end), guards, targets)); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void visitLocalVariable( | 
|  | String name, String desc, String signature, Label start, Label end, int index) { | 
|  | if (debugParsingOptions.localInfo) { | 
|  | DebugLocalInfo debugLocalInfo = | 
|  | canonicalize( | 
|  | new DebugLocalInfo( | 
|  | factory.createString(name), | 
|  | factory.createType(desc), | 
|  | signature == null ? null : factory.createString(signature))); | 
|  | localVariables.add( | 
|  | new LocalVariableInfo(index, debugLocalInfo, getLabel(start), getLabel(end))); | 
|  | } | 
|  | } | 
|  |  | 
|  | private DebugLocalInfo canonicalize(DebugLocalInfo debugLocalInfo) { | 
|  | return canonicalDebugLocalInfo.computeIfAbsent(debugLocalInfo, o -> debugLocalInfo); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void visitLineNumber(int line, Label start) { | 
|  | if (debugParsingOptions.lineInfo) { | 
|  | instructions.add(new CfPosition(getLabel(start), new Position(line, null, method, null))); | 
|  | } | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void visitMaxs(int maxStack, int maxLocals) { | 
|  | assert maxStack >= 0; | 
|  | assert maxLocals >= 0; | 
|  | this.maxStack = maxStack; | 
|  | this.maxLocals = maxLocals; | 
|  | } | 
|  | } | 
|  |  | 
|  | private static DebugParsingOptions getParsingOptions( | 
|  | JarApplicationReader application, boolean reachabilitySensitive) { | 
|  | int parsingOptions = | 
|  | (application.options.enableCfByteCodePassThrough | 
|  | || application.options.testing.readInputStackMaps) | 
|  | ? ClassReader.EXPAND_FRAMES | 
|  | : ClassReader.SKIP_FRAMES; | 
|  |  | 
|  | ProguardConfiguration configuration = application.options.getProguardConfiguration(); | 
|  | if (configuration == null) { | 
|  | return new DebugParsingOptions(true, true, parsingOptions); | 
|  | } | 
|  | ProguardKeepAttributes keep = | 
|  | application.options.getProguardConfiguration().getKeepAttributes(); | 
|  |  | 
|  | boolean localsInfo = | 
|  | configuration.isKeepParameterNames() | 
|  | || keep.localVariableTable | 
|  | || keep.localVariableTypeTable | 
|  | || reachabilitySensitive; | 
|  | boolean lineInfo = keep.lineNumberTable; | 
|  |  | 
|  | if (!localsInfo && !lineInfo) { | 
|  | parsingOptions |= ClassReader.SKIP_DEBUG; | 
|  | } | 
|  |  | 
|  | return new DebugParsingOptions(lineInfo, localsInfo, parsingOptions); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public boolean verifyNoInputReaders() { | 
|  | assert context == null && application == null; | 
|  | return true; | 
|  | } | 
|  |  | 
|  | private static boolean verifyNoReparseContext(DexClass owner) { | 
|  | for (DexEncodedMethod method : owner.virtualMethods()) { | 
|  | Code code = method.getCode(); | 
|  | assert code == null || code.verifyNoInputReaders(); | 
|  | } | 
|  | for (DexEncodedMethod method : owner.directMethods()) { | 
|  | Code code = method.getCode(); | 
|  | assert code == null || code.verifyNoInputReaders(); | 
|  | } | 
|  | return true; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public Int2ReferenceMap<DebugLocalInfo> collectParameterInfo( | 
|  | DexEncodedMethod encodedMethod, AppView<?> appView) { | 
|  | return asCfCode().collectParameterInfo(encodedMethod, appView); | 
|  | } | 
|  | } |