| // 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.utils; | |
| import static com.android.tools.r8.utils.FileUtils.CLASS_EXTENSION; | |
| import static com.android.tools.r8.utils.FileUtils.MODULES_PREFIX; | |
| import com.android.tools.r8.dex.Constants; | |
| import com.android.tools.r8.errors.CompilationError; | |
| import com.android.tools.r8.errors.InvalidDescriptorException; | |
| import com.android.tools.r8.errors.Unreachable; | |
| import com.android.tools.r8.graph.DexString; | |
| import com.android.tools.r8.graph.DexType; | |
| import com.android.tools.r8.naming.ClassNameMapper; | |
| import com.google.common.collect.ImmutableMap; | |
| import java.io.File; | |
| import java.nio.file.Path; | |
| import java.util.Map; | |
| import java.util.function.Function; | |
| public class DescriptorUtils { | |
| public static final char DESCRIPTOR_PACKAGE_SEPARATOR = '/'; | |
| public static final char JAVA_PACKAGE_SEPARATOR = '.'; | |
| public static final char INNER_CLASS_SEPARATOR = '$'; | |
| private static final Map<String, String> typeNameToLetterMap = | |
| ImmutableMap.<String, String>builder() | |
| .put("void", "V") | |
| .put("boolean", "Z") | |
| .put("byte", "B") | |
| .put("short", "S") | |
| .put("char", "C") | |
| .put("int", "I") | |
| .put("long", "J") | |
| .put("float", "F") | |
| .put("double", "D") | |
| .build(); | |
| private static String internalToDescriptor( | |
| String typeName, boolean shorty, boolean ignorePrimitives) { | |
| String descriptor = null; | |
| if (!ignorePrimitives) { | |
| descriptor = typeNameToLetterMap.get(typeName); | |
| } | |
| if (descriptor != null) { | |
| return descriptor; | |
| } | |
| // Must be some array or object type. | |
| if (shorty) { | |
| return "L"; | |
| } | |
| if (typeName.endsWith("[]")) { | |
| return "[" + internalToDescriptor( | |
| typeName.substring(0, typeName.length() - 2), shorty, ignorePrimitives); | |
| } | |
| // Must be an object type. | |
| return "L" + typeName.replace(JAVA_PACKAGE_SEPARATOR, DESCRIPTOR_PACKAGE_SEPARATOR) + ";"; | |
| } | |
| public static String mapTypeName(String typeName, Function<String, String> typeNameMapper) { | |
| int arrayDimensions = computeArrayDimensionForTypeName(typeName); | |
| if (arrayDimensions > 0) { | |
| typeName = typeName.substring(0, typeName.length() - (arrayDimensions * 2)); | |
| } | |
| String mappedTypeName = typeNameMapper.apply(typeName); | |
| if (arrayDimensions == 0) { | |
| return mappedTypeName; | |
| } | |
| StringBuilder builder = new StringBuilder(mappedTypeName); | |
| for (int i = 0; i < arrayDimensions; i++) { | |
| builder.append("[]"); | |
| } | |
| return builder.toString(); | |
| } | |
| private static int computeArrayDimensionForTypeName(String typeName) { | |
| int arrayDim = 0; | |
| for (int i = typeName.length() - 2; i > 0; i -= 2) { | |
| if (typeName.charAt(i) == '[' && typeName.charAt(i + 1) == ']') { | |
| arrayDim += 1; | |
| } | |
| } | |
| return arrayDim; | |
| } | |
| /** | |
| * Convert a Java type name to a descriptor string. | |
| * | |
| * @param typeName the java type name | |
| * @return the descriptor string | |
| */ | |
| public static String javaTypeToDescriptor(String typeName) { | |
| assert typeName.indexOf(DESCRIPTOR_PACKAGE_SEPARATOR) == -1; | |
| return internalToDescriptor(typeName, false, false); | |
| } | |
| public static String javaClassToDescriptor(Class<?> clazz) { | |
| return javaTypeToDescriptor(clazz.getTypeName()); | |
| } | |
| /** | |
| * Convert a Java type name to a descriptor string ignoring primitive types. | |
| * | |
| * Ignoring primitives mean that type named like int and long are considered class names, will | |
| * return Lint; and Llong; respectively instead of I and J. These are not legal Java class names, | |
| * but valid on the JVM and minification/obfuscation can generate them. | |
| * | |
| * @param typeName the java type name | |
| * @return the descriptor string | |
| */ | |
| public static String javaTypeToDescriptorIgnorePrimitives(String typeName) { | |
| assert typeName.indexOf(DESCRIPTOR_PACKAGE_SEPARATOR) == -1; | |
| return internalToDescriptor(typeName, false, true); | |
| } | |
| /** | |
| * Convert a Java type name to a descriptor string only if the given {@param typeName} is valid. | |
| * | |
| * @param typeName the java type name | |
| * @return the descriptor string if {@param typeName} is not valid or null otherwise | |
| */ | |
| public static String javaTypeToDescriptorIfValidJavaType(String typeName) { | |
| if (isValidJavaType(typeName)) { | |
| return javaTypeToDescriptor(typeName); | |
| } | |
| return null; | |
| } | |
| /** | |
| * Produces an array descriptor having the number of dimensions specified and the | |
| * baseTypeDescriptor as base. | |
| * | |
| * @param dimensions number of dimensions | |
| * @param baseTypeDescriptor the base type | |
| * @return the descriptor string | |
| */ | |
| public static String toArrayDescriptor(int dimensions, String baseTypeDescriptor) { | |
| StringBuilder sb = new StringBuilder(); | |
| for (int i = 0; i < dimensions; i++) { | |
| sb.append('['); | |
| } | |
| sb.append(baseTypeDescriptor); | |
| return sb.toString(); | |
| } | |
| public static String toBaseDescriptor(String descriptor) { | |
| int lastArrayStartCharacterIndex = -1; | |
| while (true) { | |
| int candidateIndex = lastArrayStartCharacterIndex + 1; | |
| if (candidateIndex >= descriptor.length() || descriptor.charAt(candidateIndex) != '[') { | |
| break; | |
| } | |
| lastArrayStartCharacterIndex = candidateIndex; | |
| } | |
| return lastArrayStartCharacterIndex >= 0 | |
| ? descriptor.substring(lastArrayStartCharacterIndex + 1) | |
| : descriptor; | |
| } | |
| /** | |
| * Determine the given {@param typeName} is a valid jvms binary name or not (jvms 4.2.1). | |
| * | |
| * @param typeName the jvms binary name | |
| * @return true if and only if the given type name is valid jvms binary name | |
| */ | |
| public static boolean isValidJavaType(String typeName) { | |
| if (typeName.length() == 0) { | |
| return false; | |
| } | |
| char last = 0; | |
| for (int i = 0; i < typeName.length(); i++) { | |
| char c = typeName.charAt(i); | |
| if (c == ';' || | |
| c == '[' || | |
| c == '/') { | |
| return false; | |
| } | |
| if (c == '.' && (i == 0 || last == '.')) { | |
| return false; | |
| } | |
| last = c; | |
| } | |
| return true; | |
| } | |
| /** | |
| * Convert a Java type name to a shorty descriptor string. | |
| * | |
| * @param typeName the java type name | |
| * @return the shorty descriptor string | |
| */ | |
| public static String javaTypeToShorty(String typeName) { | |
| return internalToDescriptor(typeName, true, false); | |
| } | |
| /** | |
| * Convert a type descriptor to a Java type name. | |
| * | |
| * @param descriptor type descriptor | |
| * @return Java type name | |
| */ | |
| public static String descriptorToJavaType(String descriptor) { | |
| return descriptorToJavaType(descriptor, null); | |
| } | |
| /** | |
| * Convert a class type descriptor to an ASM internal name. | |
| * | |
| * @param descriptor type descriptor | |
| * @return Java type name | |
| */ | |
| public static String descriptorToInternalName(String descriptor) { | |
| switch (descriptor.charAt(0)) { | |
| case '[': | |
| return descriptor; | |
| case 'L': | |
| return descriptor.substring(1, descriptor.length() - 1); | |
| default: | |
| throw new Unreachable("Not array or class type"); | |
| } | |
| } | |
| /** | |
| * Convert a descriptor to a classifier in Kotlin metadata | |
| * @param descriptor like "Lorg/foo/bar/Baz$Nested;" | |
| * @return className "org/foo/bar/Baz.Nested" | |
| */ | |
| public static String descriptorToKotlinClassifier(String descriptor) { | |
| final String classifier = | |
| getBinaryNameFromDescriptor(descriptor) | |
| .replace(INNER_CLASS_SEPARATOR, JAVA_PACKAGE_SEPARATOR); | |
| if (descriptor.startsWith("Lj$/")) { | |
| assert classifier.startsWith("j./"); | |
| return "j$/" + classifier.substring(3); | |
| } | |
| return classifier; | |
| } | |
| /** | |
| * Convert a type descriptor to a Java type name. Will also deobfuscate class names if a | |
| * class mapper is provided. | |
| * | |
| * @param descriptor type descriptor | |
| * @param classNameMapper class name mapper for mapping obfuscated class names | |
| * @return Java type name | |
| */ | |
| public static String descriptorToJavaType(String descriptor, ClassNameMapper classNameMapper) { | |
| char c = descriptor.charAt(0); | |
| switch (c) { | |
| case 'L': | |
| assert descriptor.charAt(descriptor.length() - 1) == ';'; | |
| String clazz = descriptor.substring(1, descriptor.length() - 1) | |
| .replace(DESCRIPTOR_PACKAGE_SEPARATOR, JAVA_PACKAGE_SEPARATOR); | |
| String originalName = | |
| classNameMapper == null ? clazz : classNameMapper.deobfuscateClassName(clazz); | |
| return originalName; | |
| case '[': | |
| return descriptorToJavaType(descriptor.substring(1), classNameMapper) + "[]"; | |
| default: | |
| return primitiveDescriptorToJavaType(c); | |
| } | |
| } | |
| public static boolean isPrimitiveDescriptor(String descriptor) { | |
| if (descriptor.length() != 1) { | |
| return false; | |
| } | |
| return isPrimitiveType(descriptor.charAt(0)); | |
| } | |
| public static boolean isPrimitiveType(char c) { | |
| return c == 'Z' || c == 'B' || c == 'S' || c == 'C' || c == 'I' || c == 'F' || c == 'J' | |
| || c == 'D'; | |
| } | |
| public static boolean isVoidDescriptor(String descriptor) { | |
| return descriptor.length() == 1 && isVoidType(descriptor.charAt(0)); | |
| } | |
| public static boolean isVoidType(char c) { | |
| return c == 'V'; | |
| } | |
| public static boolean isArrayDescriptor(String descriptor) { | |
| if (descriptor.length() < 2) { | |
| return false; | |
| } | |
| if (descriptor.charAt(0) == '[') { | |
| return isDescriptor(descriptor.substring(1)); | |
| } | |
| return false; | |
| } | |
| public static boolean isDescriptor(String descriptor) { | |
| return isClassDescriptor(descriptor) | |
| || isPrimitiveDescriptor(descriptor) | |
| || isArrayDescriptor(descriptor); | |
| } | |
| public static String primitiveDescriptorToJavaType(char primitive) { | |
| switch (primitive) { | |
| case 'V': | |
| return "void"; | |
| case 'Z': | |
| return "boolean"; | |
| case 'B': | |
| return "byte"; | |
| case 'S': | |
| return "short"; | |
| case 'C': | |
| return "char"; | |
| case 'I': | |
| return "int"; | |
| case 'J': | |
| return "long"; | |
| case 'F': | |
| return "float"; | |
| case 'D': | |
| return "double"; | |
| default: | |
| throw new Unreachable("Unknown type " + primitive); | |
| } | |
| } | |
| public static String primitiveDescriptorToBoxedInternalName(char primitive) { | |
| switch (primitive) { | |
| case 'V': | |
| return "java/lang/Void"; | |
| case 'Z': | |
| return "java/lang/Boolean"; | |
| case 'B': | |
| return "java/lang/Byte"; | |
| case 'S': | |
| return "java/lang/Short"; | |
| case 'C': | |
| return "java/lang/Character"; | |
| case 'I': | |
| return "java/lang/Integer"; | |
| case 'J': | |
| return "java/lang/Long"; | |
| case 'F': | |
| return "java/lang/Float"; | |
| case 'D': | |
| return "java/lang/Double"; | |
| default: | |
| throw new Unreachable("Unknown type " + primitive); | |
| } | |
| } | |
| /** | |
| * Get unqualified class name from its descriptor. | |
| * | |
| * @param classDescriptor a class descriptor i.e. "Ljava/lang/Object;" or "La/b/C$D;" | |
| * @return class name i.e. "Object" or "C$D" (not "D") | |
| */ | |
| public static String getUnqualifiedClassNameFromDescriptor(String classDescriptor) { | |
| return getUnqualifiedClassNameFromBinaryName(getClassBinaryNameFromDescriptor(classDescriptor)); | |
| } | |
| /** | |
| * Get class name from its descriptor. | |
| * | |
| * @param classDescriptor a class descriptor i.e. "Ljava/lang/Object;" | |
| * @return full class name i.e. "java.lang.Object" | |
| */ | |
| public static String getClassNameFromDescriptor(String classDescriptor) { | |
| return getClassBinaryNameFromDescriptor(classDescriptor) | |
| .replace(DESCRIPTOR_PACKAGE_SEPARATOR, JAVA_PACKAGE_SEPARATOR); | |
| } | |
| /** | |
| * Get the simple class name from its descriptor. | |
| * | |
| * @param classDescriptor a class descriptor i.e. "Ljava/lang/Object;" | |
| * @return simple class name i.e. "Object" | |
| */ | |
| public static String getSimpleClassNameFromDescriptor(String classDescriptor) { | |
| return classDescriptor.substring( | |
| getSimpleClassNameIndex(classDescriptor), classDescriptor.length() - 1); | |
| } | |
| /** | |
| * Replace the simple class name from its descriptor with a new simple name. | |
| * | |
| * @param classDescriptor a class descriptor i.e. "Ljava/lang/Object;" | |
| * @param newSimpleName a new simple name e.g. "NewObject" | |
| * @return updated class descriptor i.e. "Ljava/lang/NewObject;" | |
| */ | |
| public static String replaceSimpleClassNameInDescriptor( | |
| String classDescriptor, String newSimpleName) { | |
| return "L" | |
| + classDescriptor.substring(1, getSimpleClassNameIndex(classDescriptor)) | |
| + newSimpleName | |
| + ";"; | |
| } | |
| /** | |
| * Finds the index of the simple class name in its descriptor. | |
| * | |
| * @param classDescriptor a class descriptor i.e. "Ljava/lang/Object;" | |
| * @return the index of the simple name i.e. 11. | |
| */ | |
| private static int getSimpleClassNameIndex(String classDescriptor) { | |
| return Integer.max(classDescriptor.lastIndexOf("/"), 0) + 1; | |
| } | |
| /** | |
| * Get canonical class name from its descriptor. | |
| * | |
| * @param classDescriptor a class descriptor i.e. "La/b/C$D;" | |
| * @return canonical class name i.e. "a.b.C.D" | |
| */ | |
| public static String getCanonicalNameFromDescriptor(String classDescriptor) { | |
| return getClassNameFromDescriptor(classDescriptor) | |
| .replace(INNER_CLASS_SEPARATOR, JAVA_PACKAGE_SEPARATOR); | |
| } | |
| /** | |
| * Convert class to a binary name. | |
| * | |
| * @param clazz a java.lang.Class reference | |
| * @return class binary name i.e. "java/lang/Object" | |
| */ | |
| public static String getClassBinaryName(Class<?> clazz) { | |
| return getBinaryNameFromJavaType(clazz.getTypeName()); | |
| } | |
| /** | |
| * Get package java name from a class descriptor. | |
| * | |
| * @param descriptor a class descriptor i.e. "Ljava/lang/Object;" | |
| * @return java package name i.e. "java.lang" | |
| */ | |
| public static String getPackageNameFromDescriptor(String descriptor) { | |
| return getPackageNameFromBinaryName(getClassBinaryNameFromDescriptor(descriptor)); | |
| } | |
| /** | |
| * Convert class descriptor to a binary name. | |
| * | |
| * @param classDescriptor a class descriptor i.e. "Ljava/lang/Object;" | |
| * @return class binary name i.e. "java/lang/Object" | |
| */ | |
| public static String getClassBinaryNameFromDescriptor(String classDescriptor) { | |
| assert isClassDescriptor(classDescriptor) : "Invalid class descriptor " | |
| + classDescriptor; | |
| return classDescriptor.substring(1, classDescriptor.length() - 1); | |
| } | |
| /** | |
| * Get package java name from a class type name. | |
| * | |
| * @param typeName a class descriptor i.e. "java.lang.Object" | |
| * @return java package name i.e. "java.lang" | |
| */ | |
| public static String getPackageNameFromTypeName(String typeName) { | |
| return getPackageNameFromBinaryName( | |
| getClassBinaryNameFromDescriptor(javaTypeToDescriptor(typeName))); | |
| } | |
| /** | |
| * Convert package name to a binary name. | |
| * | |
| * @param packageName a package name i.e., "java.lang" | |
| * @return java package name in a binary name format, i.e., java/lang | |
| */ | |
| public static String getPackageBinaryNameFromJavaType(String packageName) { | |
| return packageName.replace(JAVA_PACKAGE_SEPARATOR, DESCRIPTOR_PACKAGE_SEPARATOR); | |
| } | |
| /** | |
| * Convert class name to a binary name. | |
| * | |
| * @param className a package name i.e., "java.lang.Object" | |
| * @return java class name in a binary name format, i.e., java/lang/Object | |
| */ | |
| public static String getBinaryNameFromJavaType(String className) { | |
| return className.replace(JAVA_PACKAGE_SEPARATOR, DESCRIPTOR_PACKAGE_SEPARATOR); | |
| } | |
| public static String getJavaTypeFromBinaryName(String className) { | |
| return className.replace(DESCRIPTOR_PACKAGE_SEPARATOR, JAVA_PACKAGE_SEPARATOR); | |
| } | |
| public static String getBinaryNameFromDescriptor(String classDescriptor) { | |
| assert isClassDescriptor(classDescriptor); | |
| return classDescriptor.substring(1, classDescriptor.length() - 1); | |
| } | |
| /** | |
| * Convert a class binary name to a descriptor. | |
| * | |
| * @param typeBinaryName class binary name i.e. "java/lang/Object" | |
| * @return a class descriptor i.e. "Ljava/lang/Object;" | |
| */ | |
| public static String getDescriptorFromClassBinaryName(String typeBinaryName) { | |
| assert typeBinaryName != null; | |
| return 'L' + typeBinaryName + ';'; | |
| } | |
| /** | |
| * Convert a fully qualified name of a classifier in Kotlin metadata to a descriptor. | |
| * @param className "org/foo/bar/Baz.Nested" | |
| * @return a class descriptor like "Lorg/foo/bar/Baz$Nested;" | |
| */ | |
| public static String getDescriptorFromKotlinClassifier(String className) { | |
| assert className != null; | |
| assert !className.contains("[") : className; | |
| return 'L' + className.replace(JAVA_PACKAGE_SEPARATOR, INNER_CLASS_SEPARATOR) + ';'; | |
| } | |
| /** | |
| * Get unqualified class name from its binary name. | |
| * | |
| * @param classBinaryName a class binary name i.e. "java/lang/Object" or "a/b/C$Inner" | |
| * @return class name i.e. "Object" or "C$Inner" (not "Inner") | |
| * | |
| * Note that we cannot rely on $ separator in binary name or descriptor because a class, which is | |
| * not a member or local class, can still contain $ in its name. For the correct retrieval of the | |
| * simple name of member or local classes, use the inner name in the inner-class attribute (or | |
| * refer to ReflectionOptimizer#computeClassName as an example). | |
| */ | |
| public static String getUnqualifiedClassNameFromBinaryName(String classBinaryName) { | |
| int simpleNameIndex = classBinaryName.lastIndexOf(DESCRIPTOR_PACKAGE_SEPARATOR); | |
| return (simpleNameIndex < 0) ? classBinaryName : classBinaryName.substring(simpleNameIndex + 1); | |
| } | |
| public static String computeInnerClassSeparator( | |
| DexType outerClass, DexType innerClass, DexString innerName) { | |
| assert innerClass != null; | |
| // Filter out non-member classes ahead. | |
| if (outerClass == null || innerName == null) { | |
| return String.valueOf(INNER_CLASS_SEPARATOR); | |
| } | |
| return computeInnerClassSeparator( | |
| outerClass.getInternalName(), innerClass.getInternalName(), innerName.toString()); | |
| } | |
| public static String computeInnerClassSeparator( | |
| String outerDescriptor, String innerDescriptor, String innerName) { | |
| assert innerName != null && !innerName.isEmpty(); | |
| // outer-internal<separator>inner-name == inner-internal | |
| if (outerDescriptor.length() + innerName.length() > innerDescriptor.length()) { | |
| return null; | |
| } | |
| String separator = | |
| innerDescriptor.substring( | |
| outerDescriptor.length(), innerDescriptor.length() - innerName.length()); | |
| // Any non-$ separator results in a runtime exception in getCanonicalName. | |
| if (!separator.startsWith(String.valueOf(INNER_CLASS_SEPARATOR))) { | |
| return null; | |
| } | |
| return separator; | |
| } | |
| public static boolean isClassDescriptor(String descriptor) { | |
| char[] buffer = descriptor.toCharArray(); | |
| int length = buffer.length; | |
| if (length < 3 || buffer[0] != 'L') { | |
| return false; | |
| } | |
| int pos = 1; | |
| char ch; | |
| do { | |
| // First letter of an Ident (an Ident can't be empty) | |
| if (pos >= length) { | |
| return false; | |
| } | |
| ch = buffer[pos++]; | |
| if (isInvalidChar(ch) || ch == DESCRIPTOR_PACKAGE_SEPARATOR || ch == ';') { | |
| return false; | |
| } | |
| // Next letters of an Ident | |
| do { | |
| if (pos >= length) { | |
| return false; | |
| } | |
| ch = buffer[pos++]; | |
| if (isInvalidChar(ch)) { | |
| return false; | |
| } | |
| } while (ch != DESCRIPTOR_PACKAGE_SEPARATOR && ch != ';'); | |
| } while (ch != ';'); | |
| return pos == length; | |
| } | |
| /** | |
| * Get package java name from a class binary name | |
| * | |
| * @param classBinaryName a class binary name i.e. "java/lang/Object" | |
| * @return java package name i.e. "java.lang" | |
| */ | |
| public static String getPackageNameFromBinaryName(String classBinaryName) { | |
| int nameIndex = classBinaryName.lastIndexOf(DESCRIPTOR_PACKAGE_SEPARATOR); | |
| return (nameIndex < 0) ? "" : classBinaryName.substring(0, nameIndex) | |
| .replace(DESCRIPTOR_PACKAGE_SEPARATOR, JAVA_PACKAGE_SEPARATOR); | |
| } | |
| private static boolean isInvalidChar(char ch) { | |
| switch (ch) { | |
| case JAVA_PACKAGE_SEPARATOR: | |
| case '[': | |
| return true; | |
| default: | |
| return false; | |
| } | |
| } | |
| /** | |
| * Guess class descriptor from location of the class file on the file system | |
| * | |
| * @param name Path of the file to convert to the corresponding descriptor | |
| * @return java class descriptor | |
| */ | |
| public static String guessTypeDescriptor(Path name) { | |
| String fileName = name.toString(); | |
| if (File.separatorChar != '/') { | |
| fileName = fileName.replace(File.separatorChar, '/'); | |
| } | |
| return guessTypeDescriptor(fileName); | |
| } | |
| /** | |
| * Guess class descriptor from location of the class file. This method assumes that the | |
| * name uses '/' as the separator. Therefore, this should not be the name of a file | |
| * on a file system. | |
| * | |
| * @param name the location of the class file to convert to descriptor | |
| * @return java class descriptor | |
| */ | |
| public static String guessTypeDescriptor(String name) { | |
| assert name != null; | |
| assert name.endsWith(CLASS_EXTENSION) : | |
| "Name " + name + " must have " + CLASS_EXTENSION + " suffix"; | |
| String descriptor = name.substring(0, name.length() - CLASS_EXTENSION.length()); | |
| if (descriptor.indexOf(JAVA_PACKAGE_SEPARATOR) != -1) { | |
| throw new CompilationError("Unexpected class file name: " + name); | |
| } | |
| return 'L' + descriptor + ';'; | |
| } | |
| public static boolean isValidMethodName(String string) { | |
| if (string.isEmpty()) { | |
| return false; | |
| } | |
| // According to https://source.android.com/devices/tech/dalvik/dex-format#membername | |
| // '<' SimpleName '>' should be valid. However, the art verifier only allows <init> | |
| // and <clinit> which is reasonable. | |
| if ((string.charAt(0) == '<') | |
| && (string.equals(Constants.INSTANCE_INITIALIZER_NAME) | |
| || string.equals(Constants.CLASS_INITIALIZER_NAME))) { | |
| return true; | |
| } | |
| int cp; | |
| for (int i = 0; i < string.length(); i += Character.charCount(cp)) { | |
| cp = string.codePointAt(i); | |
| if (!IdentifierUtils.isRelaxedDexIdentifierPart(cp)) { | |
| return false; | |
| } | |
| } | |
| return true; | |
| } | |
| public static boolean isValidFieldName(String string) { | |
| if (string.isEmpty()) { | |
| return false; | |
| } | |
| int start = 0; | |
| int end = string.length(); | |
| if (string.charAt(0) == '<') { | |
| if (string.charAt(end - 1) == '>') { | |
| start = 1; | |
| --end; | |
| } else { | |
| return false; | |
| } | |
| } | |
| int cp; | |
| for (int i = start; i < end; i += Character.charCount(cp)) { | |
| cp = string.codePointAt(i); | |
| if (!IdentifierUtils.isRelaxedDexIdentifierPart(cp)) { | |
| return false; | |
| } | |
| } | |
| return true; | |
| } | |
| public static boolean isValidDescriptor(String descriptor) { | |
| return isValidArrayDescriptor(descriptor) | |
| || isValidClassDescriptor(descriptor) | |
| || isPrimitiveDescriptor(descriptor) | |
| || isVoidDescriptor(descriptor); | |
| } | |
| public static boolean isValidArrayDescriptor(String descriptor) { | |
| if (descriptor.isEmpty() || descriptor.charAt(0) != '[') { | |
| return false; | |
| } | |
| return isValidDescriptor(toBaseDescriptor(descriptor)); | |
| } | |
| public static boolean isValidClassDescriptor(String string) { | |
| if (string.length() < 3 | |
| || string.charAt(0) != 'L' | |
| || string.charAt(string.length() - 1) != ';') { | |
| return false; | |
| } | |
| if (string.charAt(1) == '/' || string.charAt(string.length() - 2) == '/') { | |
| return false; | |
| } | |
| int cp; | |
| for (int i = 1; i < string.length() - 1; i += Character.charCount(cp)) { | |
| cp = string.codePointAt(i); | |
| if (cp != '/' && !IdentifierUtils.isRelaxedDexIdentifierPart(cp)) { | |
| return false; | |
| } | |
| } | |
| return true; | |
| } | |
| public static boolean isValidBinaryName(String binaryName) { | |
| return isValidJavaType( | |
| binaryName.replace(DESCRIPTOR_PACKAGE_SEPARATOR, JAVA_PACKAGE_SEPARATOR)); | |
| } | |
| /** | |
| * Computes the inner name from the outer- and inner descriptors. If outer is not a prefix of the | |
| * inner descriptor null is returned. Do not use this method if the relationship between inner and | |
| * outer is not reflected in the name. | |
| * | |
| * @param outerDescriptor the outer descriptor, such as Lfoo/bar/Baz; | |
| * @param innerDescriptor the inner descriptor, such as Lfoo/bar/Baz$Qux; | |
| * @return the inner name or null, i.e. Qux in the example above | |
| */ | |
| public static String getInnerClassName(String outerDescriptor, String innerDescriptor) { | |
| if (innerDescriptor.length() <= outerDescriptor.length()) { | |
| return null; | |
| } | |
| String prefix = | |
| outerDescriptor.substring(0, outerDescriptor.length() - 1) + INNER_CLASS_SEPARATOR; | |
| if (innerDescriptor.startsWith(prefix)) { | |
| return innerDescriptor.substring(prefix.length(), innerDescriptor.length() - 1); | |
| } | |
| return null; | |
| } | |
| public static class ModuleAndDescriptor { | |
| private final String module; | |
| private final String descriptor; | |
| ModuleAndDescriptor(String module, String descriptor) { | |
| this.module = module; | |
| this.descriptor = descriptor; | |
| } | |
| public String getModule() { | |
| return module; | |
| } | |
| public String getDescriptor() { | |
| return descriptor; | |
| } | |
| } | |
| /** | |
| * Guess module and class descriptor from the location of a class file in a jrt file system. | |
| * | |
| * @param name the location in a jrt file system of the class file to convert to descriptor | |
| * @return module and java class descriptor | |
| */ | |
| public static ModuleAndDescriptor guessJrtModuleAndTypeDescriptor(String name) { | |
| assert name != null; | |
| assert name.endsWith(CLASS_EXTENSION) | |
| : "Name " + name + " must have " + CLASS_EXTENSION + " suffix"; | |
| assert name.startsWith(MODULES_PREFIX) | |
| : "Name " + name + " must have " + MODULES_PREFIX + " prefix"; | |
| assert name.charAt(MODULES_PREFIX.length()) == '/'; | |
| int moduleNameEnd = name.indexOf('/', MODULES_PREFIX.length() + 1); | |
| String module = name.substring(MODULES_PREFIX.length() + 1, moduleNameEnd); | |
| String descriptor = name.substring(moduleNameEnd + 1, name.length() - CLASS_EXTENSION.length()); | |
| if (descriptor.indexOf(JAVA_PACKAGE_SEPARATOR) != -1) { | |
| throw new CompilationError("Unexpected class file name: " + name); | |
| } | |
| return new ModuleAndDescriptor(module, 'L' + descriptor + ';'); | |
| } | |
| public static String getPathFromDescriptor(String descriptor) { | |
| // We are quite loose on names here to support testing illegal names, too. | |
| assert descriptor.startsWith("L"); | |
| assert descriptor.endsWith(";"); | |
| return descriptor.substring(1, descriptor.length() - 1) + ".class"; | |
| } | |
| public static String getPathFromJavaType(Class<?> clazz) { | |
| return getPathFromJavaType(clazz.getTypeName()); | |
| } | |
| public static String getPathFromJavaType(String typeName) { | |
| assert isValidJavaType(typeName); | |
| return typeName.replace(JAVA_PACKAGE_SEPARATOR, DESCRIPTOR_PACKAGE_SEPARATOR) + ".class"; | |
| } | |
| public static String getClassFileName(String classDescriptor) { | |
| assert classDescriptor != null && isClassDescriptor(classDescriptor); | |
| return getClassBinaryNameFromDescriptor(classDescriptor) + CLASS_EXTENSION; | |
| } | |
| public static String getReturnTypeDescriptor(final String methodDescriptor) { | |
| assert methodDescriptor.indexOf(')') != -1; | |
| return methodDescriptor.substring(methodDescriptor.indexOf(')') + 1); | |
| } | |
| public static String getShortyDescriptor(String descriptor) { | |
| if (descriptor.length() == 1) { | |
| return descriptor; | |
| } | |
| assert descriptor.charAt(0) == 'L' || descriptor.charAt(0) == '['; | |
| return "L"; | |
| } | |
| public static String[] getArgumentTypeDescriptors(final String methodDescriptor) { | |
| String[] argDescriptors = new String[getArgumentCount(methodDescriptor)]; | |
| int charIdx = 1; | |
| char c; | |
| int argIdx = 0; | |
| int startType; | |
| while ((c = methodDescriptor.charAt(charIdx)) != ')') { | |
| switch (c) { | |
| case 'V': | |
| throw new InvalidDescriptorException(methodDescriptor); | |
| case 'Z': | |
| case 'C': | |
| case 'B': | |
| case 'S': | |
| case 'I': | |
| case 'F': | |
| case 'J': | |
| case 'D': | |
| argDescriptors[argIdx++] = Character.toString(c); | |
| break; | |
| case '[': | |
| startType = charIdx; | |
| while (methodDescriptor.charAt(++charIdx) == '[') | |
| ; | |
| if (methodDescriptor.charAt(charIdx) == 'L') { | |
| while (methodDescriptor.charAt(++charIdx) != ';') | |
| ; | |
| } | |
| argDescriptors[argIdx++] = methodDescriptor.substring(startType, charIdx + 1); | |
| break; | |
| case 'L': | |
| startType = charIdx; | |
| while (methodDescriptor.charAt(++charIdx) != ';') | |
| ; | |
| argDescriptors[argIdx++] = methodDescriptor.substring(startType, charIdx + 1); | |
| break; | |
| default: | |
| throw new InvalidDescriptorException(methodDescriptor); | |
| } | |
| charIdx++; | |
| } | |
| return argDescriptors; | |
| } | |
| public static int getArgumentCount(final String methodDescriptor) { | |
| int length = methodDescriptor.length(); | |
| int charIdx = 1; | |
| char c; | |
| int argCount = 0; | |
| while (charIdx < length && (c = methodDescriptor.charAt(charIdx++)) != ')') { | |
| if (c == 'L') { | |
| while (charIdx < length && methodDescriptor.charAt(charIdx++) != ';') | |
| ; | |
| // Check if the inner loop found ';' within the boundary. | |
| if (charIdx >= length || methodDescriptor.charAt(charIdx - 1) != ';') { | |
| throw new InvalidDescriptorException(methodDescriptor); | |
| } | |
| argCount++; | |
| } else if (c != '[') { | |
| argCount++; | |
| } | |
| } | |
| // Check if the outer loop found ')' within the boundary. | |
| if (charIdx >= length || methodDescriptor.charAt(charIdx - 1) != ')') { | |
| throw new InvalidDescriptorException(methodDescriptor); | |
| } | |
| return argCount; | |
| } | |
| } |