/* * Copyright (C) 2009 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package signature.converter.dex; import java.io.IOException; import java.util.Collections; import java.util.EnumSet; import java.util.HashSet; import java.util.List; import java.util.Set; import signature.converter.Visibility; import signature.model.IClassDefinition; import signature.model.Kind; import signature.model.Modifier; import signature.model.impl.SigPackage; import signature.model.util.ModelUtil; import dex.reader.DexBuffer; import dex.reader.DexFileReader; import dex.structure.DexAnnotatedElement; import dex.structure.DexAnnotation; import dex.structure.DexAnnotationAttribute; import dex.structure.DexClass; import dex.structure.DexEncodedValue; import dex.structure.DexField; import dex.structure.DexFile; import dex.structure.DexMethod; public class DexUtil { private static final String PACKAGE_INFO = "package-info"; private static final String THROWS_ANNOTATION = "Ldalvik/annotation/Throws;"; private static final String SIGNATURE_ANNOTATION = "Ldalvik/annotation/Signature;"; private static final String ANNOTATION_DEFAULT_ANNOTATION = "Ldalvik/annotation/AnnotationDefault;"; private static final String ENCLOSING_CLASS_ANNOTATION = "Ldalvik/annotation/EnclosingClass;"; private static final String ENCLOSING_METHOD_ANNOTATION = "Ldalvik/annotation/EnclosingMethod;"; private static final String INNER_CLASS_ANNOTATION = "Ldalvik/annotation/InnerClass;"; private static final String MEMBER_CLASS_ANNOTATION = "Ldalvik/annotation/MemberClasses;"; private static final String JAVA_LANG_OBJECT = "Ljava/lang/Object;"; private static final Set<String> INTERNAL_ANNOTATION_NAMES; static { Set<String> tmp = new HashSet<String>(); tmp.add(THROWS_ANNOTATION); tmp.add(SIGNATURE_ANNOTATION); tmp.add(ANNOTATION_DEFAULT_ANNOTATION); tmp.add(ENCLOSING_CLASS_ANNOTATION); tmp.add(ENCLOSING_METHOD_ANNOTATION); tmp.add(INNER_CLASS_ANNOTATION); tmp.add(MEMBER_CLASS_ANNOTATION); INTERNAL_ANNOTATION_NAMES = Collections.unmodifiableSet(tmp); } private DexUtil() { // not constructable from outside } /** * "La/b/c/A;" -> "a.b.c" "LA;" -> "" empty string * * @param classIdentifier * @return the package name */ public static String getPackageName(String classIdentifier) { String name = removeTrailingSemicolon(removeHeadingL(classIdentifier)); return ModelUtil.getPackageName(name.replace("/", ".")); } /** * "La/b/c/A;" -> "A" "LA;" -> "A" * * @param classIdentifier * the dalvik internal identifier * @return the class name */ public static String getClassName(String classIdentifier) { String name = removeTrailingSemicolon(removeHeadingL(classIdentifier)); return ModelUtil.getClassName(name.replace("/", ".")).replace('$', '.'); } public static String getQualifiedName(String classIdentifier) { String name = removeTrailingSemicolon(removeHeadingL(classIdentifier)); return name.replace('/', '.'); } private static String removeHeadingL(String className) { assert className.startsWith("L"); return className.substring(1); } private static String removeTrailingSemicolon(String className) { assert className.endsWith(";"); return className.substring(0, className.length() - 1); } public static String getDexName(String packageName, String className) { return "L" + packageName.replace('.', '/') + "/" + className.replace('.', '$') + ";"; } public static String getDexName(IClassDefinition sigClass) { return getDexName(sigClass.getPackageName(), sigClass.getName()); } /** * Returns correct modifiers for inner classes */ public static int getClassModifiers(DexClass clazz) { int modifiers = 0; if (isInnerClass(clazz)) { Integer accessFlags = (Integer) getAnnotationAttributeValue( getAnnotation(clazz, INNER_CLASS_ANNOTATION), "accessFlags"); modifiers = accessFlags.intValue(); } else { modifiers = clazz.getModifiers(); } return modifiers; } /** * Returns a set containing all modifiers for the given int. * * @param mod * the original bit coded modifiers as specified by * {@link java.lang.reflect.Modifier} * @return a set containing {@link signature.model.Modifier} elements */ public static Set<Modifier> getModifier(int mod) { Set<Modifier> modifiers = EnumSet.noneOf(Modifier.class); if (java.lang.reflect.Modifier.isAbstract(mod)) modifiers.add(Modifier.ABSTRACT); if (java.lang.reflect.Modifier.isFinal(mod)) modifiers.add(Modifier.FINAL); // if (java.lang.reflect.Modifier.isNative(mod)) // modifiers.add(Modifier.NATIVE); if (java.lang.reflect.Modifier.isPrivate(mod)) modifiers.add(Modifier.PRIVATE); if (java.lang.reflect.Modifier.isProtected(mod)) modifiers.add(Modifier.PROTECTED); if (java.lang.reflect.Modifier.isPublic(mod)) modifiers.add(Modifier.PUBLIC); if (java.lang.reflect.Modifier.isStatic(mod)) modifiers.add(Modifier.STATIC); // if (java.lang.reflect.Modifier.isStrict(mod)) // modifiers.add(Modifier.STRICT); // if (java.lang.reflect.Modifier.isSynchronized(mod)) // modifiers.add(Modifier.SYNCHRONIZED); // if (java.lang.reflect.Modifier.isTransient(mod)) // modifiers.add(Modifier.TRANSIENT); if (java.lang.reflect.Modifier.isVolatile(mod)) modifiers.add(Modifier.VOLATILE); return modifiers; } /** * Returns true if the given class is an enumeration, false otherwise. * * @param dexClass * the DexClass under test * @return true if the given class is an enumeration, false otherwise */ public static boolean isEnum(DexClass dexClass) { return (getClassModifiers(dexClass) & 0x4000) > 0; } /** * Returns true if the given class is an interface, false otherwise. * * @param dexClass * the DexClass under test * @return true if the given class is an interface, false otherwise */ public static boolean isInterface(DexClass dexClass) { int modifiers = getClassModifiers(dexClass); return java.lang.reflect.Modifier.isInterface(modifiers); } /** * Returns true if the given class is an annotation, false otherwise. * * @param dexClass * the DexClass under test * @return true if the given class is an annotation, false otherwise */ public static boolean isAnnotation(DexClass dexClass) { return (getClassModifiers(dexClass) & 0x2000) > 0; } public static boolean isSynthetic(int modifier) { return (modifier & 0x1000) > 0; } /** * Returns the Kind of the given DexClass. * * @param dexClass * the DexClass under test * @return the Kind of the given class */ public static Kind getKind(DexClass dexClass) { // order of branches is crucial since a annotation is also an interface if (isEnum(dexClass)) { return Kind.ENUM; } else if (isAnnotation(dexClass)) { return Kind.ANNOTATION; } else if (isInterface(dexClass)) { return Kind.INTERFACE; } else { return Kind.CLASS; } } /** * Returns whether the specified annotated element has an annotation with * type "Ldalvik/annotation/Throws;". * * @param annotatedElement * the annotated element to check * @return <code>true</code> if the given annotated element has the * mentioned annotation, false otherwise */ public static boolean declaresExceptions( DexAnnotatedElement annotatedElement) { return getAnnotation(annotatedElement, THROWS_ANNOTATION) != null; } /** * Returns the throws signature if the given element has such an annotation, * null otherwise. * * @param annotatedElement * the annotated element * @return he generic signature if the given element has such an annotation, * null otherwise */ @SuppressWarnings("unchecked") public static String getExceptionSignature( DexAnnotatedElement annotatedElement) { DexAnnotation annotation = getAnnotation(annotatedElement, THROWS_ANNOTATION); if (annotation != null) { List<DexEncodedValue> value = (List<DexEncodedValue>) getAnnotationAttributeValue( annotation, "value"); return concatEncodedValues(value); } return null; } /** * Splits a list of types: * "Ljava/io/IOException;Ljava/lang/IllegalStateException;" <br> * into separate type designators: <br> * "Ljava/io/IOException;" , "Ljava/lang/IllegalStateException;" * * @param typeList * the type list * @return a set of type designators */ public static Set<String> splitTypeList(String typeList) { String[] split = typeList.split(";"); Set<String> separateTypes = new HashSet<String>(); for (String string : split) { separateTypes.add(string + ";");// add semicolon again } return separateTypes; } /** * Returns whether the specified annotated element has an annotation with * type "Ldalvik/annotation/Signature;". * * @param annotatedElement * the annotated element to check * @return <code>true</code> if the given annotated element has the * mentioned annotation, false otherwise */ public static boolean hasGenericSignature( DexAnnotatedElement annotatedElement) { return getAnnotation(annotatedElement, SIGNATURE_ANNOTATION) != null; } /** * Returns the generic signature if the given element has such an * annotation, null otherwise. * * @param annotatedElement * the annotated element * @return he generic signature if the given element has such an annotation, * null otherwise */ @SuppressWarnings("unchecked") public static String getGenericSignature( DexAnnotatedElement annotatedElement) { DexAnnotation annotation = getAnnotation(annotatedElement, SIGNATURE_ANNOTATION); if (annotation != null) { List<DexEncodedValue> value = (List<DexEncodedValue>) getAnnotationAttributeValue( annotation, "value"); return concatEncodedValues(value); } return null; } /** * Returns whether the specified annotated element has an annotation with * type "Ldalvik/annotation/AnnotationDefault;". * * @param annotatedElement * the annotated element to check * @return <code>true</code> if the given annotated element has the * mentioned annotation, false otherwise */ public static boolean hasAnnotationDefaultSignature( DexAnnotatedElement annotatedElement) { return getAnnotation( annotatedElement, ANNOTATION_DEFAULT_ANNOTATION)!= null; } /** * Returns a mapping form annotation attribute name to its default value. * * @param dexClass * the class defining a annotation * @return a mapping form annotation attribute name to its default value */ public static DexAnnotation getDefaultMappingsAnnotation( DexClass dexClass) { return getAnnotation(dexClass, ANNOTATION_DEFAULT_ANNOTATION); } /** * Returns the annotation with the specified type from the given element or * null if no such annotation is available. * * @param element * the annotated element * @param annotationType * the dex internal name of the annotation type * @return the annotation with the specified type or null if not present */ public static DexAnnotation getAnnotation(DexAnnotatedElement element, String annotationType) { assert element != null; assert annotationType != null; for (DexAnnotation anno : element.getAnnotations()) { if (annotationType.equals(anno.getTypeName())) { return anno; } } return null; } /** * Returns the value for the specified attribute name of the given * annotation or null if not present. * * @param annotation * the annotation * @param attributeName * the name of the attribute * @return the value for the specified attribute */ public static Object getAnnotationAttributeValue(DexAnnotation annotation, String attributeName) { for (DexAnnotationAttribute dexAnnotationAttribute : annotation .getAttributes()) { if (attributeName.equals(dexAnnotationAttribute.getName())) { return dexAnnotationAttribute.getEncodedValue().getValue(); } } return null; } private static String concatEncodedValues(List<DexEncodedValue> values) { StringBuilder builder = new StringBuilder(); for (DexEncodedValue string : values) { builder.append(string.getValue()); } return builder.toString(); } /** * Returns true if the given method is a constructor, false otherwise. * * @param method * the method to test * @return true if the given method is a constructor, false otherwise */ public static boolean isConstructor(DexMethod method) { return "<init>".equals(method.getName()); } /** * Returns true if the given method is a static constructor, false * otherwise. * * @param method * the method to test * @return true if the given method is a static constructor, false otherwise */ public static boolean isStaticConstructor(DexMethod method) { return "<clinit>".equals(method.getName()); } public static boolean isMethod(DexMethod method) { return !isConstructor(method) && !isStaticConstructor(method); } /** * Returns the package-info class for the given package. * * @param aPackage * the package * @return the class called "package-info" or null, if not available */ public static IClassDefinition findPackageInfo(SigPackage aPackage) { for (IClassDefinition clazz : aPackage.getClasses()) { if (PACKAGE_INFO.equals(clazz.getName())) { return clazz; } } return null; } public static boolean isPackageInfo(DexClass clazz) { return PACKAGE_INFO.equals(getClassName(clazz.getName())); } public static boolean isInternalAnnotation(DexAnnotation dexAnnotation) { return INTERNAL_ANNOTATION_NAMES.contains(dexAnnotation.getTypeName()); } /** * An InnerClass annotation is attached to each class which is defined in * the lexical scope of another class's definition. Any class which has this * annotation must also have either an EnclosingClass annotation or an * EnclosingMethod annotation. */ public static boolean isInnerClass(DexClass clazz) { return getAnnotation(clazz, INNER_CLASS_ANNOTATION) != null; } /** * An EnclosingClass annotation is attached to each class which is either * defined as a member of another class, per se, or is anonymous but not * defined within a method body (e.g., a synthetic inner class). Every class * that has this annotation must also have an InnerClass annotation. * Additionally, a class may not have both an EnclosingClass and an * EnclosingMethod annotation. */ public static boolean isEnclosingClass(DexClass clazz) { return getAnnotation(clazz, ENCLOSING_CLASS_ANNOTATION) != null; } public static boolean declaresMemberClasses(DexClass dexClass) { return getAnnotation(dexClass, MEMBER_CLASS_ANNOTATION) != null; } @SuppressWarnings("unchecked") public static Set<String> getMemberClassNames(DexClass dexClass) { DexAnnotation annotation = getAnnotation(dexClass, MEMBER_CLASS_ANNOTATION); List<DexEncodedValue> enclosedClasses = (List<DexEncodedValue>) getAnnotationAttributeValue( annotation, "value"); Set<String> enclosedClassesNames = new HashSet<String>(); for (DexEncodedValue string : enclosedClasses) { enclosedClassesNames.add((String) string.getValue()); } return enclosedClassesNames; } public static String getEnclosingClassName(DexClass dexClass) { DexAnnotation annotation = getAnnotation(dexClass, ENCLOSING_CLASS_ANNOTATION); String value = (String) getAnnotationAttributeValue(annotation, "value"); return value; } public static boolean convertAnyWay(DexClass dexClass) { return !isSynthetic(getClassModifiers(dexClass)) && !isAnonymousClassName(dexClass.getName()) || isPackageInfo(dexClass); } public static boolean isVisible(DexClass dexClass, Visibility visibility) { // package info is always visible if (isPackageInfo(dexClass)) { return true; } if (isDeclaredInMethod(dexClass)) { return false; } if (isAnonymousClassName(dexClass.getName())) { return false; } int modifiers = getClassModifiers(dexClass); return isVisible(modifiers, visibility); } private static boolean isDeclaredInMethod(DexClass dexClass) { return getAnnotation(dexClass, ENCLOSING_METHOD_ANNOTATION) != null; } /** * Returns whether the given dex identifier is an anonymous class name. * Format: La/b/C$1; * * @param dexName * the name to analyze * @return whether the given dex identifier is an anonymous class name */ public static boolean isAnonymousClassName(String dexName) { int index = dexName.lastIndexOf('$'); return (index != 0) ? Character.isDigit(dexName.charAt(index + 1)) : false; } public static boolean isVisible(DexField dexField, Visibility visibility) { return isVisible(dexField.getModifiers(), visibility); } public static boolean isVisible(DexMethod dexMethod, Visibility visibility) { return isVisible(dexMethod.getModifiers(), visibility); } private static boolean isVisible(int modifiers, Visibility visibility) { if (isSynthetic(modifiers)) { return false; } Set<Modifier> elementModifiers = getModifier(modifiers); if (elementModifiers.contains(Modifier.PUBLIC)) { return true; } else if (elementModifiers.contains(Modifier.PROTECTED)) { return visibility == Visibility.PROTECTED || visibility == Visibility.PACKAGE || visibility == Visibility.PRIVATE; } else if (elementModifiers.contains(Modifier.PRIVATE)) { return visibility == Visibility.PRIVATE; } else { return visibility == Visibility.PACKAGE || visibility == Visibility.PRIVATE; } } public static Set<DexFile> getDexFiles(Set<String> fileNames) throws IOException { Set<DexFile> parsedFiles = new HashSet<DexFile>(); for (String dexFile : fileNames) { DexFileReader reader = new DexFileReader(); DexBuffer dexBuffer = new DexBuffer(dexFile); parsedFiles.add(reader.read(dexBuffer)); } return parsedFiles; } public static boolean isJavaLangObject(DexClass dexClass) { assert dexClass != null; return JAVA_LANG_OBJECT.equals(dexClass.getName()); } }