|  | // 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.naming; | 
|  |  | 
|  | import static com.android.tools.r8.utils.DescriptorUtils.javaTypeToDescriptorIfValidJavaType; | 
|  |  | 
|  | import com.android.tools.r8.graph.AppView; | 
|  | import com.android.tools.r8.graph.DexClass; | 
|  | import com.android.tools.r8.graph.DexDefinitionSupplier; | 
|  | import com.android.tools.r8.graph.DexEncodedField; | 
|  | import com.android.tools.r8.graph.DexEncodedMethod; | 
|  | import com.android.tools.r8.graph.DexField; | 
|  | import com.android.tools.r8.graph.DexItemFactory; | 
|  | import com.android.tools.r8.graph.DexMember; | 
|  | import com.android.tools.r8.graph.DexMethod; | 
|  | import com.android.tools.r8.graph.DexReference; | 
|  | import com.android.tools.r8.graph.DexString; | 
|  | import com.android.tools.r8.graph.DexType; | 
|  | import com.android.tools.r8.graph.DexTypeList; | 
|  | import com.android.tools.r8.graph.ProgramMethod; | 
|  | import com.android.tools.r8.ir.code.ArrayPut; | 
|  | import com.android.tools.r8.ir.code.BasicBlock; | 
|  | import com.android.tools.r8.ir.code.CheckCast; | 
|  | import com.android.tools.r8.ir.code.ConstantValueUtils; | 
|  | import com.android.tools.r8.ir.code.DexItemBasedConstString; | 
|  | import com.android.tools.r8.ir.code.Instruction; | 
|  | import com.android.tools.r8.ir.code.InstructionIterator; | 
|  | import com.android.tools.r8.ir.code.InvokeMethod; | 
|  | import com.android.tools.r8.ir.code.InvokeStatic; | 
|  | import com.android.tools.r8.ir.code.InvokeVirtual; | 
|  | import com.android.tools.r8.ir.code.NewArrayEmpty; | 
|  | import com.android.tools.r8.ir.code.Value; | 
|  | import com.android.tools.r8.naming.identifiernamestring.IdentifierNameStringLookupResult; | 
|  | import com.android.tools.r8.shaking.AppInfoWithLiveness; | 
|  | import com.google.common.collect.Sets; | 
|  | import java.util.ArrayList; | 
|  | import java.util.List; | 
|  | import java.util.Set; | 
|  |  | 
|  | public final class IdentifierNameStringUtils { | 
|  |  | 
|  | /** | 
|  | * Checks if the given {@param method} is a reflection method in Java. | 
|  | * | 
|  | * @param dexItemFactory where pre-defined descriptors are retrieved | 
|  | * @param method to test | 
|  | * @return {@code true} if the given {@param method} is a reflection method in Java. | 
|  | */ | 
|  | public static boolean isReflectionMethod(DexItemFactory dexItemFactory, DexMethod method) { | 
|  | // So, why is this simply not like: | 
|  | //   return dexItemFactory.classMethods.isReflectiveClassLookup(method) | 
|  | //       || dexItemFactory.classMethods.isReflectiveMemberLookup(method) | 
|  | //       || dexItemFactory.atomicFieldUpdaterMethods.isFieldUpdater(method); | 
|  | // ? | 
|  | // That is because the counter part of other shrinkers supports users' own reflective methods | 
|  | // whose signature matches with reflection methods in Java. Hence, explicit signature matching. | 
|  | // See tests {@link IdentifierMinifierTest#test2_rule3}, | 
|  | //   {@link IdentifierNameStringMarkerTest#reflective_field_singleUseOperand_renamed}, or | 
|  | //   {@link IdentifierNameStringMarkerTest#reflective_method_singleUseOperand_renamed}. | 
|  | // | 
|  | // For java.lang.Class: | 
|  | //   (String) -> java.lang.Class | java.lang.reflect.Field | 
|  | //   (String, boolean, ClassLoader) -> java.lang.Class | 
|  | //   (String, Class[]) -> java.lang.reflect.Method | 
|  | // For java.util.concurrent.atomic.Atomic(Integer|Long)FieldUpdater: | 
|  | //   (Class, String) -> $holderType | 
|  | // For java.util.concurrent.atomic.AtomicReferenceFieldUpdater: | 
|  | //   (Class, Class, String) -> $holderType | 
|  | // For any other types: | 
|  | //   (Class, String) -> java.lang.reflect.Field | 
|  | //   (Class, String, Class[]) -> java.lang.reflect.Method | 
|  | int arity = method.getArity(); | 
|  | if (method.holder == dexItemFactory.classType) { | 
|  | // Virtual methods of java.lang.Class, such as getField, getMethod, etc. | 
|  | if (arity == 0 || arity > 3) { | 
|  | return false; | 
|  | } | 
|  | if (arity == 1) { | 
|  | if (method.proto.returnType != dexItemFactory.classType | 
|  | && method.proto.returnType != dexItemFactory.fieldType) { | 
|  | return false; | 
|  | } | 
|  | } else if (arity == 2) { | 
|  | if (method.proto.returnType != dexItemFactory.methodType) { | 
|  | return false; | 
|  | } | 
|  | } else { | 
|  | if (method.proto.returnType != dexItemFactory.classType) { | 
|  | return false; | 
|  | } | 
|  | } | 
|  | if (method.proto.parameters.values[0] != dexItemFactory.stringType) { | 
|  | return false; | 
|  | } | 
|  | if (arity == 2) { | 
|  | if (method.proto.parameters.values[1] != dexItemFactory.classArrayType) { | 
|  | return false; | 
|  | } | 
|  | } | 
|  | if (arity == 3) { | 
|  | if (method.proto.parameters.values[1] != dexItemFactory.booleanType | 
|  | && method.proto.parameters.values[2] != dexItemFactory.classLoaderType) { | 
|  | return false; | 
|  | } | 
|  | } | 
|  | } else if ( | 
|  | method.holder.descriptor == dexItemFactory.intFieldUpdaterDescriptor | 
|  | || method.holder.descriptor == dexItemFactory.longFieldUpdaterDescriptor) { | 
|  | // Atomic(Integer|Long)FieldUpdater->newUpdater(Class, String)AtomicFieldUpdater | 
|  | if (arity != 2) { | 
|  | return false; | 
|  | } | 
|  | if (method.proto.returnType != method.holder) { | 
|  | return false; | 
|  | } | 
|  | if (method.proto.parameters.values[0] != dexItemFactory.classType) { | 
|  | return false; | 
|  | } | 
|  | if (method.proto.parameters.values[1] != dexItemFactory.stringType) { | 
|  | return false; | 
|  | } | 
|  | } else if (method.holder.descriptor == dexItemFactory.referenceFieldUpdaterDescriptor) { | 
|  | // AtomicReferenceFieldUpdater->newUpdater(Class, Class, String)AtomicFieldUpdater | 
|  | if (arity != 3) { | 
|  | return false; | 
|  | } | 
|  | if (method.proto.returnType != method.holder) { | 
|  | return false; | 
|  | } | 
|  | if (method.proto.parameters.values[0] != dexItemFactory.classType) { | 
|  | return false; | 
|  | } | 
|  | if (method.proto.parameters.values[1] != dexItemFactory.classType) { | 
|  | return false; | 
|  | } | 
|  | if (method.proto.parameters.values[2] != dexItemFactory.stringType) { | 
|  | return false; | 
|  | } | 
|  | } else { | 
|  | // Methods whose first argument is of java.lang.Class type. | 
|  | if (arity != 2 && arity != 3) { | 
|  | return false; | 
|  | } | 
|  | if (arity == 2) { | 
|  | if (method.proto.returnType != dexItemFactory.fieldType) { | 
|  | return false; | 
|  | } | 
|  | } else { | 
|  | if (method.proto.returnType != dexItemFactory.methodType) { | 
|  | return false; | 
|  | } | 
|  | } | 
|  | if (method.proto.parameters.values[0] != dexItemFactory.classType) { | 
|  | return false; | 
|  | } | 
|  | if (method.proto.parameters.values[1] != dexItemFactory.stringType) { | 
|  | return false; | 
|  | } | 
|  | if (arity == 3) { | 
|  | if (method.proto.parameters.values[2] != dexItemFactory.classArrayType) { | 
|  | return false; | 
|  | } | 
|  | } | 
|  | } | 
|  | return true; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Returns true if the given invoke instruction is calling `boolean java.lang.String.equals( | 
|  | * java.lang.String)`, and one of the arguments is defined by an invoke-instruction that calls | 
|  | * `java.lang.String java.lang.Class.getName()`. | 
|  | */ | 
|  | static boolean isClassNameComparison(InvokeMethod invoke, DexItemFactory dexItemFactory) { | 
|  | return invoke.isInvokeVirtual() | 
|  | && isClassNameComparison(invoke.asInvokeVirtual(), dexItemFactory); | 
|  | } | 
|  |  | 
|  | static boolean isClassNameComparison(InvokeVirtual invoke, DexItemFactory dexItemFactory) { | 
|  | return invoke.getInvokedMethod() == dexItemFactory.stringMembers.equals | 
|  | && (isClassNameValue(invoke.getReceiver(), dexItemFactory) | 
|  | || isClassNameValue(invoke.inValues().get(1), dexItemFactory)); | 
|  | } | 
|  |  | 
|  | public static boolean isClassNameValue(Value value, DexItemFactory dexItemFactory) { | 
|  | Value root = value.getAliasedValue(); | 
|  | if (!root.isDefinedByInstructionSatisfying(Instruction::isInvokeVirtual)) { | 
|  | return false; | 
|  | } | 
|  | InvokeVirtual invoke = root.definition.asInvokeVirtual(); | 
|  | return dexItemFactory.classMethods.isReflectiveNameLookup(invoke.getInvokedMethod()); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Returns a {@link DexReference} if one of the arguments to the invoke instruction is a constant | 
|  | * string that corresponds to either a class or member name (i.e., an identifier). | 
|  | * | 
|  | * @param definitions {@link DexDefinitionSupplier} that gives access to {@link DexItemFactory}. | 
|  | * @param invoke {@link InvokeMethod} that is expected to have an identifier in its arguments. | 
|  | * @return {@link DexReference} corresponding to the first constant string argument that matches a | 
|  | *     class or member name, or {@code null} if no such constant was found. | 
|  | */ | 
|  | public static IdentifierNameStringLookupResult<?> identifyIdentifier( | 
|  | InvokeMethod invoke, DexDefinitionSupplier definitions, ProgramMethod context) { | 
|  | DexItemFactory dexItemFactory = definitions.dexItemFactory(); | 
|  | List<Value> ins = invoke.arguments(); | 
|  | // The only static calls: Class#forName, | 
|  | //   which receive either (String) or (String, boolean, ClassLoader) as ins. | 
|  | if (invoke.isInvokeStatic()) { | 
|  | InvokeStatic invokeStatic = invoke.asInvokeStatic(); | 
|  | if (dexItemFactory.classMethods.isReflectiveClassLookup(invokeStatic.getInvokedMethod())) { | 
|  | return IdentifierNameStringLookupResult.fromClassForName( | 
|  | ConstantValueUtils.getDexTypeFromClassForName(invokeStatic, definitions)); | 
|  | } | 
|  | } | 
|  |  | 
|  | if (invoke.isInvokeVirtual()) { | 
|  | InvokeVirtual invokeVirtual = invoke.asInvokeVirtual(); | 
|  | if (isClassNameComparison(invokeVirtual, dexItemFactory)) { | 
|  | int argumentIndex = getPositionOfFirstConstString(invokeVirtual); | 
|  | if (argumentIndex >= 0) { | 
|  | return IdentifierNameStringLookupResult.fromClassNameComparison( | 
|  | inferTypeFromConstStringValue( | 
|  | definitions, invokeVirtual.inValues().get(argumentIndex))); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | // All the other cases receive either (Class, String) or (Class, String, Class[]) as ins. | 
|  | if (ins.size() == 1) { | 
|  | return null; | 
|  | } | 
|  |  | 
|  | boolean isReferenceFieldUpdater = | 
|  | invoke.getReturnType().descriptor == dexItemFactory.referenceFieldUpdaterDescriptor; | 
|  | int positionOfIdentifier = isReferenceFieldUpdater ? 2 : 1; | 
|  | Value in = ins.get(positionOfIdentifier); | 
|  | if (in.isConstString()) { | 
|  | Value classValue = ins.get(0); | 
|  | if (!classValue.isConstClass()) { | 
|  | return null; | 
|  | } | 
|  | DexType holderType = classValue.getConstInstruction().asConstClass().getValue(); | 
|  | if (holderType.isArrayType()) { | 
|  | // None of the fields or methods of an array type will be renamed, since they are all | 
|  | // declared in the library. Hence there is no need to handle this case. | 
|  | return null; | 
|  | } | 
|  | DexClass holder = definitions.definitionFor(holderType, context); | 
|  | if (holder == null) { | 
|  | return null; | 
|  | } | 
|  | DexString dexString = in.getConstInstruction().asConstString().getValue(); | 
|  | int numOfParams = ins.size(); | 
|  | if (isReferenceFieldUpdater) { | 
|  | Value fieldTypeValue = ins.get(1); | 
|  | if (!fieldTypeValue.isConstClass()) { | 
|  | return null; | 
|  | } | 
|  | DexType fieldType = fieldTypeValue.getConstInstruction().asConstClass().getValue(); | 
|  | return IdentifierNameStringLookupResult.fromUncategorized( | 
|  | inferFieldInHolder(holder, dexString.toString(), fieldType)); | 
|  | } | 
|  | if (numOfParams == 2) { | 
|  | return IdentifierNameStringLookupResult.fromUncategorized( | 
|  | inferFieldInHolder(holder, dexString.toString(), null)); | 
|  | } | 
|  | assert numOfParams == 3; | 
|  | DexTypeList arguments = retrieveDexTypeListFromClassList(invoke, ins.get(2), dexItemFactory); | 
|  | if (arguments == null) { | 
|  | return null; | 
|  | } | 
|  | return IdentifierNameStringLookupResult.fromUncategorized( | 
|  | inferMethodInHolder(holder, dexString.toString(), arguments)); | 
|  | } | 
|  | if (in.isDexItemBasedConstString()) { | 
|  | DexItemBasedConstString constString = in.getConstInstruction().asDexItemBasedConstString(); | 
|  | if (constString.getItem().isDexType()) { | 
|  | return IdentifierNameStringLookupResult.fromDexTypeBasedConstString( | 
|  | constString.getItem().asDexType()); | 
|  | } | 
|  | return IdentifierNameStringLookupResult.fromDexMemberBasedConstString( | 
|  | constString.getItem().asDexMember()); | 
|  | } | 
|  | return null; | 
|  | } | 
|  |  | 
|  | static int getPositionOfFirstConstString(Instruction instruction) { | 
|  | List<Value> inValues = instruction.inValues(); | 
|  | for (int i = 0; i < inValues.size(); i++) { | 
|  | Value value = inValues.get(i).getAliasedValue(); | 
|  | if (value.isConstString() || value.isDexItemBasedConstString()) { | 
|  | return i; | 
|  | } | 
|  | } | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | static DexReference inferMemberOrTypeFromNameString( | 
|  | AppView<AppInfoWithLiveness> appView, DexString dexString) { | 
|  | // "fully.qualified.ClassName.fieldOrMethodName" | 
|  | // "fully.qualified.ClassName#fieldOrMethodName" | 
|  | DexMember<?, ?> itemBasedString = inferMemberFromNameString(appView, dexString); | 
|  | if (itemBasedString == null) { | 
|  | // "fully.qualified.ClassName" | 
|  | return inferTypeFromNameString(appView, dexString); | 
|  | } | 
|  | return itemBasedString; | 
|  | } | 
|  |  | 
|  | public static DexType inferTypeFromNameString( | 
|  | DexDefinitionSupplier definitions, DexString dexString) { | 
|  | String maybeDescriptor = javaTypeToDescriptorIfValidJavaType(dexString.toString()); | 
|  | if (maybeDescriptor != null) { | 
|  | return definitions.dexItemFactory().createType(maybeDescriptor); | 
|  | } | 
|  | return null; | 
|  | } | 
|  |  | 
|  | public static DexType inferTypeFromConstStringValue( | 
|  | DexDefinitionSupplier definitions, Value value) { | 
|  | Value root = value.getAliasedValue(); | 
|  | assert !root.isPhi(); | 
|  | assert root.isConstString() || root.isDexItemBasedConstString(); | 
|  | if (root.isConstString()) { | 
|  | return inferTypeFromNameString(definitions, root.definition.asConstString().getValue()); | 
|  | } | 
|  | if (root.isDexItemBasedConstString()) { | 
|  | DexReference reference = root.definition.asDexItemBasedConstString().getItem(); | 
|  | if (reference.isDexType()) { | 
|  | return reference.asDexType(); | 
|  | } | 
|  | } | 
|  | return null; | 
|  | } | 
|  |  | 
|  | private static DexMember<?, ?> inferMemberFromNameString( | 
|  | AppView<AppInfoWithLiveness> appView, DexString dexString) { | 
|  | String identifier = dexString.toString(); | 
|  | String typeIdentifier = null; | 
|  | String memberIdentifier = null; | 
|  | String[] items = identifier.split("#"); | 
|  | // "x#y#z" | 
|  | if (items.length > 2) { | 
|  | return null; | 
|  | } | 
|  | // "fully.qualified.ClassName#fieldOrMethodName" | 
|  | if (items.length == 2) { | 
|  | typeIdentifier = items[0]; | 
|  | memberIdentifier = items[1]; | 
|  | } else { | 
|  | int lastDot = identifier.lastIndexOf("."); | 
|  | // "fully.qualified.ClassName.fieldOrMethodName" | 
|  | if (0 < lastDot && lastDot < identifier.length() - 1) { | 
|  | typeIdentifier = identifier.substring(0, lastDot); | 
|  | memberIdentifier = identifier.substring(lastDot + 1); | 
|  | } | 
|  | } | 
|  | if (typeIdentifier == null) { | 
|  | return null; | 
|  | } | 
|  | String maybeDescriptor = javaTypeToDescriptorIfValidJavaType(typeIdentifier); | 
|  | if (maybeDescriptor == null) { | 
|  | return null; | 
|  | } | 
|  | DexType type = appView.dexItemFactory().createType(maybeDescriptor); | 
|  | // TODO(b/150736225): Should we move the identification of identifiers into the initial tracing? | 
|  | DexClass holder = appView.appInfo().definitionForWithoutExistenceAssert(type); | 
|  | if (holder == null) { | 
|  | return null; | 
|  | } | 
|  | DexMember<?, ?> itemBasedString = inferFieldInHolder(holder, memberIdentifier, null); | 
|  | if (itemBasedString == null) { | 
|  | itemBasedString = inferMethodNameInHolder(holder, memberIdentifier); | 
|  | } | 
|  | return itemBasedString; | 
|  | } | 
|  |  | 
|  | private static DexField inferFieldInHolder(DexClass holder, String name, DexType fieldType) { | 
|  | for (DexEncodedField encodedField : holder.fields()) { | 
|  | if (encodedField.getReference().name.toString().equals(name) | 
|  | && (fieldType == null || encodedField.getReference().type == fieldType)) { | 
|  | return encodedField.getReference(); | 
|  | } | 
|  | } | 
|  | return null; | 
|  | } | 
|  |  | 
|  | private static DexMethod inferMethodNameInHolder(DexClass holder, String name) { | 
|  | for (DexEncodedMethod encodedMethod : holder.methods()) { | 
|  | if (encodedMethod.getReference().name.toString().equals(name)) { | 
|  | return encodedMethod.getReference(); | 
|  | } | 
|  | } | 
|  | return null; | 
|  | } | 
|  |  | 
|  | private static DexMethod inferMethodInHolder( | 
|  | DexClass holder, String name, DexTypeList arguments) { | 
|  | assert arguments != null; | 
|  | for (DexEncodedMethod encodedMethod : holder.methods()) { | 
|  | if (encodedMethod.getReference().name.toString().equals(name) | 
|  | && encodedMethod.getReference().proto.parameters.equals(arguments)) { | 
|  | return encodedMethod.getReference(); | 
|  | } | 
|  | } | 
|  | return null; | 
|  | } | 
|  |  | 
|  | private static DexType getTypeFromConstClassOrBoxedPrimitive( | 
|  | Value value, DexItemFactory factory) { | 
|  | if (value.isPhi()) { | 
|  | return null; | 
|  | } | 
|  | if (value.isConstant() && value.getConstInstruction().isConstClass()) { | 
|  | return value.getConstInstruction().asConstClass().getValue(); | 
|  | } | 
|  | if (value.definition.isStaticGet()) { | 
|  | return factory.primitiveTypesBoxedTypeFields.boxedFieldTypeToPrimitiveType( | 
|  | value.definition.asStaticGet().getField()); | 
|  | } | 
|  | return null; | 
|  | } | 
|  |  | 
|  | // Perform a conservative evaluation of an array content of dex type values from its construction | 
|  | // until its use at a given instruction. | 
|  | private static DexType[] evaluateTypeArrayContentFromConstructionToUse( | 
|  | NewArrayEmpty newArray, | 
|  | List<CheckCast> aliases, | 
|  | int size, | 
|  | Instruction user, | 
|  | DexItemFactory factory) { | 
|  | DexType[] values = new DexType[size]; | 
|  | int remaining = size; | 
|  | Set<Instruction> users = Sets.newIdentityHashSet(); | 
|  | users.addAll(newArray.outValue().uniqueUsers()); | 
|  | for (CheckCast alias : aliases) { | 
|  | users.addAll(alias.outValue().uniqueUsers()); | 
|  | } | 
|  | // Follow the path from the array construction to the requested use collecting the constants | 
|  | // put into the array. Conservatively bail out if the content of the array cannot be statically | 
|  | // computed. | 
|  | BasicBlock block = newArray.getBlock(); | 
|  | InstructionIterator iterator = block.iterator(); | 
|  | iterator.nextUntil(i -> i == newArray); | 
|  | do { | 
|  | while (iterator.hasNext()) { | 
|  | Instruction instruction = iterator.next(); | 
|  | // Ignore instructions which do not use the array. | 
|  | if (!users.contains(instruction)) { | 
|  | continue; | 
|  | } | 
|  | if (instruction == user) { | 
|  | // Return the array content if all elements are known when hitting the user for which | 
|  | // the content was requested. | 
|  | return remaining == 0 ? values : null; | 
|  | } | 
|  | // Any other kinds of use besides array-put mean that the array escapes and its content | 
|  | // could be altered. | 
|  | if (!instruction.isArrayPut()) { | 
|  | if (instruction.isCheckCast() && aliases.contains(instruction.asCheckCast())) { | 
|  | continue; | 
|  | } | 
|  | values = new DexType[size]; | 
|  | remaining = size; | 
|  | continue; | 
|  | } | 
|  | ArrayPut arrayPut = instruction.asArrayPut(); | 
|  | if (!arrayPut.index().isConstNumber()) { | 
|  | return null; | 
|  | } | 
|  | int index = arrayPut.index().getConstInstruction().asConstNumber().getIntValue(); | 
|  | if (index < 0 || index >= values.length) { | 
|  | return null; | 
|  | } | 
|  | DexType type = getTypeFromConstClassOrBoxedPrimitive(arrayPut.value(), factory); | 
|  | if (type == null) { | 
|  | return null; | 
|  | } | 
|  | // Allow several writes to the same array element. | 
|  | if (values[index] == null) { | 
|  | remaining--; | 
|  | } | 
|  | values[index] = type; | 
|  | } | 
|  | if (!block.exit().isGoto()) { | 
|  | return null; | 
|  | } | 
|  | block = block.exit().asGoto().getTarget(); | 
|  | // Don't allow any other control flow into the sequence of blocks filling the array from | 
|  | // construction to requested use. This will also includes loopback and guarantee that | 
|  | // this will terminate without marking visited blocks. | 
|  | if (block.getPredecessors().size() != 1) { | 
|  | return null; | 
|  | } | 
|  | iterator = block.iterator(); | 
|  | } while (iterator != null); | 
|  | return null; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Visits all {@link ArrayPut}'s with the given {@param classListValue} as array and {@link Class} | 
|  | * as value. Then collects all corresponding {@link DexType}s so as to determine reflective cases. | 
|  | * | 
|  | * @param invoke the instruction that invokes a reflective method with -identifiernamestring rule | 
|  | * @param classListValue the register that holds an array of {@link Class}'s | 
|  | * @return a list of {@link DexType} that corresponds to const class in {@param classListValue} | 
|  | */ | 
|  | private static DexTypeList retrieveDexTypeListFromClassList( | 
|  | InvokeMethod invoke, Value classListValue, DexItemFactory factory) { | 
|  |  | 
|  | // The code | 
|  | //   A.class.getMethod("m", String.class, String.class) | 
|  | // results in the following Java byte code from javac: | 
|  | // | 
|  | // LDC LA;.class | 
|  | // LDC "m" | 
|  | // ICONST_2 | 
|  | // ANEWARRAY java/lang/Class | 
|  | // DUP | 
|  | // ICONST_0 | 
|  | // LDC Ljava/lang/String;.class | 
|  | // AASTORE | 
|  | // DUP | 
|  | // ICONST_1 | 
|  | // LDC Ljava/lang/String;.class | 
|  | // AASTORE | 
|  | // INVOKEVIRTUAL java/lang/Class.getMethod \ | 
|  | //     (Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method; | 
|  |  | 
|  | // Besides the code pattern above this supports a series of check-cast instructions. e.g.: | 
|  | // | 
|  | // A.class.getMethod("name", (Class<?>[]) new Class<?>[]{String.class}) | 
|  |  | 
|  | List<CheckCast> aliases = new ArrayList<>(); | 
|  | if (!classListValue.isPhi() | 
|  | && classListValue.definition.isCheckCast() | 
|  | && classListValue.definition.asCheckCast().getType() == factory.classArrayType) { | 
|  | while (!classListValue.isPhi() && classListValue.definition.isCheckCast()) { | 
|  | aliases.add(classListValue.definition.asCheckCast()); | 
|  | classListValue = classListValue.definition.asCheckCast().object(); | 
|  | } | 
|  | } | 
|  | if (classListValue.isPhi()) { | 
|  | return null; | 
|  | } | 
|  |  | 
|  | // A null argument list is an empty argument list | 
|  | if (classListValue.isZero()) { | 
|  | return DexTypeList.empty(); | 
|  | } | 
|  |  | 
|  | // Make sure this Value refers to a new array. | 
|  | if (!classListValue.definition.isNewArrayEmpty() | 
|  | || !classListValue.definition.asNewArrayEmpty().size().isConstant()) { | 
|  | return null; | 
|  | } | 
|  |  | 
|  | int size = | 
|  | classListValue | 
|  | .definition | 
|  | .asNewArrayEmpty() | 
|  | .size() | 
|  | .getConstInstruction() | 
|  | .asConstNumber() | 
|  | .getIntValue(); | 
|  | if (size == 0) { | 
|  | return DexTypeList.empty(); | 
|  | } | 
|  |  | 
|  | DexType[] arrayContent = | 
|  | evaluateTypeArrayContentFromConstructionToUse( | 
|  | classListValue.definition.asNewArrayEmpty(), aliases, size, invoke, factory); | 
|  |  | 
|  | if (arrayContent == null) { | 
|  | return null; | 
|  | } | 
|  | return new DexTypeList(arrayContent); | 
|  | } | 
|  | } |