Home | History | Annotate | Download | only in dex
      1 /*
      2  * Copyright (C) 2009 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package signature.converter.dex;
     18 
     19 import java.io.IOException;
     20 import java.util.Collections;
     21 import java.util.EnumSet;
     22 import java.util.HashSet;
     23 import java.util.List;
     24 import java.util.Set;
     25 
     26 import signature.converter.Visibility;
     27 import signature.model.IClassDefinition;
     28 import signature.model.Kind;
     29 import signature.model.Modifier;
     30 import signature.model.impl.SigPackage;
     31 import signature.model.util.ModelUtil;
     32 import dex.reader.DexBuffer;
     33 import dex.reader.DexFileReader;
     34 import dex.structure.DexAnnotatedElement;
     35 import dex.structure.DexAnnotation;
     36 import dex.structure.DexAnnotationAttribute;
     37 import dex.structure.DexClass;
     38 import dex.structure.DexEncodedValue;
     39 import dex.structure.DexField;
     40 import dex.structure.DexFile;
     41 import dex.structure.DexMethod;
     42 
     43 
     44 public class DexUtil {
     45 
     46     private static final String PACKAGE_INFO = "package-info";
     47     private static final String THROWS_ANNOTATION =
     48             "Ldalvik/annotation/Throws;";
     49     private static final String SIGNATURE_ANNOTATION =
     50             "Ldalvik/annotation/Signature;";
     51     private static final String ANNOTATION_DEFAULT_ANNOTATION =
     52             "Ldalvik/annotation/AnnotationDefault;";
     53     private static final String ENCLOSING_CLASS_ANNOTATION =
     54             "Ldalvik/annotation/EnclosingClass;";
     55     private static final String ENCLOSING_METHOD_ANNOTATION =
     56             "Ldalvik/annotation/EnclosingMethod;";
     57     private static final String INNER_CLASS_ANNOTATION =
     58             "Ldalvik/annotation/InnerClass;";
     59     private static final String MEMBER_CLASS_ANNOTATION =
     60             "Ldalvik/annotation/MemberClasses;";
     61     private static final String JAVA_LANG_OBJECT = "Ljava/lang/Object;";
     62 
     63     private static final Set<String> INTERNAL_ANNOTATION_NAMES;
     64 
     65     static {
     66         Set<String> tmp = new HashSet<String>();
     67         tmp.add(THROWS_ANNOTATION);
     68         tmp.add(SIGNATURE_ANNOTATION);
     69         tmp.add(ANNOTATION_DEFAULT_ANNOTATION);
     70         tmp.add(ENCLOSING_CLASS_ANNOTATION);
     71         tmp.add(ENCLOSING_METHOD_ANNOTATION);
     72         tmp.add(INNER_CLASS_ANNOTATION);
     73         tmp.add(MEMBER_CLASS_ANNOTATION);
     74         INTERNAL_ANNOTATION_NAMES = Collections.unmodifiableSet(tmp);
     75     }
     76 
     77     private DexUtil() {
     78         // not constructable from outside
     79     }
     80 
     81     /**
     82      * "La/b/c/A;" -> "a.b.c" "LA;" -> "" empty string
     83      *
     84      * @param classIdentifier
     85      * @return the package name
     86      */
     87     public static String getPackageName(String classIdentifier) {
     88         String name = removeTrailingSemicolon(removeHeadingL(classIdentifier));
     89         return ModelUtil.getPackageName(name.replace("/", "."));
     90     }
     91 
     92     /**
     93      * "La/b/c/A;" -> "A" "LA;" -> "A"
     94      *
     95      * @param classIdentifier
     96      *            the dalvik internal identifier
     97      * @return the class name
     98      */
     99     public static String getClassName(String classIdentifier) {
    100         String name = removeTrailingSemicolon(removeHeadingL(classIdentifier));
    101         return ModelUtil.getClassName(name.replace("/", ".")).replace('$', '.');
    102     }
    103 
    104     public static String getQualifiedName(String classIdentifier) {
    105         String name = removeTrailingSemicolon(removeHeadingL(classIdentifier));
    106         return name.replace('/', '.');
    107     }
    108 
    109     private static String removeHeadingL(String className) {
    110         assert className.startsWith("L");
    111         return className.substring(1);
    112     }
    113 
    114     private static String removeTrailingSemicolon(String className) {
    115         assert className.endsWith(";");
    116         return className.substring(0, className.length() - 1);
    117     }
    118 
    119     public static String getDexName(String packageName, String className) {
    120         return "L" + packageName.replace('.', '/') + "/"
    121                 + className.replace('.', '$') + ";";
    122     }
    123 
    124     public static String getDexName(IClassDefinition sigClass) {
    125         return getDexName(sigClass.getPackageName(), sigClass.getName());
    126     }
    127 
    128     /**
    129      * Returns correct modifiers for inner classes
    130      */
    131     public static int getClassModifiers(DexClass clazz) {
    132         int modifiers = 0;
    133         if (isInnerClass(clazz)) {
    134             Integer accessFlags = (Integer) getAnnotationAttributeValue(
    135                     getAnnotation(clazz, INNER_CLASS_ANNOTATION),
    136                             "accessFlags");
    137             modifiers = accessFlags.intValue();
    138         } else {
    139             modifiers = clazz.getModifiers();
    140         }
    141         return modifiers;
    142     }
    143 
    144     /**
    145      * Returns a set containing all modifiers for the given int.
    146      *
    147      * @param mod
    148      *            the original bit coded modifiers as specified by
    149      *            {@link java.lang.reflect.Modifier}
    150      * @return a set containing {@link signature.model.Modifier} elements
    151      */
    152     public static Set<Modifier> getModifier(int mod) {
    153         Set<Modifier> modifiers = EnumSet.noneOf(Modifier.class);
    154         if (java.lang.reflect.Modifier.isAbstract(mod))
    155             modifiers.add(Modifier.ABSTRACT);
    156         if (java.lang.reflect.Modifier.isFinal(mod))
    157             modifiers.add(Modifier.FINAL);
    158         // if (java.lang.reflect.Modifier.isNative(mod))
    159         // modifiers.add(Modifier.NATIVE);
    160         if (java.lang.reflect.Modifier.isPrivate(mod))
    161             modifiers.add(Modifier.PRIVATE);
    162         if (java.lang.reflect.Modifier.isProtected(mod))
    163             modifiers.add(Modifier.PROTECTED);
    164         if (java.lang.reflect.Modifier.isPublic(mod))
    165             modifiers.add(Modifier.PUBLIC);
    166         if (java.lang.reflect.Modifier.isStatic(mod))
    167             modifiers.add(Modifier.STATIC);
    168         // if (java.lang.reflect.Modifier.isStrict(mod))
    169         // modifiers.add(Modifier.STRICT);
    170         // if (java.lang.reflect.Modifier.isSynchronized(mod))
    171         // modifiers.add(Modifier.SYNCHRONIZED);
    172         // if (java.lang.reflect.Modifier.isTransient(mod))
    173         // modifiers.add(Modifier.TRANSIENT);
    174         if (java.lang.reflect.Modifier.isVolatile(mod))
    175             modifiers.add(Modifier.VOLATILE);
    176 
    177         return modifiers;
    178     }
    179 
    180     /**
    181      * Returns true if the given class is an enumeration, false otherwise.
    182      *
    183      * @param dexClass
    184      *            the DexClass under test
    185      * @return true if the given class is an enumeration, false otherwise
    186      */
    187     public static boolean isEnum(DexClass dexClass) {
    188         return (getClassModifiers(dexClass) & 0x4000) > 0;
    189     }
    190 
    191     /**
    192      * Returns true if the given class is an interface, false otherwise.
    193      *
    194      * @param dexClass
    195      *            the DexClass under test
    196      * @return true if the given class is an interface, false otherwise
    197      */
    198     public static boolean isInterface(DexClass dexClass) {
    199         int modifiers = getClassModifiers(dexClass);
    200         return java.lang.reflect.Modifier.isInterface(modifiers);
    201     }
    202 
    203     /**
    204      * Returns true if the given class is an annotation, false otherwise.
    205      *
    206      * @param dexClass
    207      *            the DexClass under test
    208      * @return true if the given class is an annotation, false otherwise
    209      */
    210     public static boolean isAnnotation(DexClass dexClass) {
    211         return (getClassModifiers(dexClass) & 0x2000) > 0;
    212     }
    213 
    214     public static boolean isSynthetic(int modifier) {
    215         return (modifier & 0x1000) > 0;
    216     }
    217 
    218     /**
    219      * Returns the Kind of the given DexClass.
    220      *
    221      * @param dexClass
    222      *            the DexClass under test
    223      * @return the Kind of the given class
    224      */
    225     public static Kind getKind(DexClass dexClass) {
    226         // order of branches is crucial since a annotation is also an interface
    227         if (isEnum(dexClass)) {
    228             return Kind.ENUM;
    229         } else if (isAnnotation(dexClass)) {
    230             return Kind.ANNOTATION;
    231         } else if (isInterface(dexClass)) {
    232             return Kind.INTERFACE;
    233         } else {
    234             return Kind.CLASS;
    235         }
    236     }
    237 
    238     /**
    239      * Returns whether the specified annotated element has an annotation with
    240      * type "Ldalvik/annotation/Throws;".
    241      *
    242      * @param annotatedElement
    243      *            the annotated element to check
    244      * @return <code>true</code> if the given annotated element has the
    245      *         mentioned annotation, false otherwise
    246      */
    247     public static boolean declaresExceptions(
    248             DexAnnotatedElement annotatedElement) {
    249         return getAnnotation(annotatedElement, THROWS_ANNOTATION) != null;
    250     }
    251 
    252     /**
    253      * Returns the throws signature if the given element has such an annotation,
    254      * null otherwise.
    255      *
    256      * @param annotatedElement
    257      *            the annotated element
    258      * @return he generic signature if the given element has such an annotation,
    259      *         null otherwise
    260      */
    261     @SuppressWarnings("unchecked")
    262     public static String getExceptionSignature(
    263             DexAnnotatedElement annotatedElement) {
    264         DexAnnotation annotation = getAnnotation(annotatedElement,
    265                 THROWS_ANNOTATION);
    266         if (annotation != null) {
    267             List<DexEncodedValue> value =
    268                     (List<DexEncodedValue>) getAnnotationAttributeValue(
    269                             annotation, "value");
    270             return concatEncodedValues(value);
    271         }
    272         return null;
    273     }
    274 
    275     /**
    276      * Splits a list of types:
    277      * "Ljava/io/IOException;Ljava/lang/IllegalStateException;" <br>
    278      * into separate type designators: <br>
    279      * "Ljava/io/IOException;" , "Ljava/lang/IllegalStateException;"
    280      *
    281      * @param typeList
    282      *            the type list
    283      * @return a set of type designators
    284      */
    285     public static Set<String> splitTypeList(String typeList) {
    286         String[] split = typeList.split(";");
    287         Set<String> separateTypes = new HashSet<String>();
    288         for (String string : split) {
    289             separateTypes.add(string + ";");// add semicolon again
    290         }
    291         return separateTypes;
    292     }
    293 
    294     /**
    295      * Returns whether the specified annotated element has an annotation with
    296      * type "Ldalvik/annotation/Signature;".
    297      *
    298      * @param annotatedElement
    299      *            the annotated element to check
    300      * @return <code>true</code> if the given annotated element has the
    301      *         mentioned annotation, false otherwise
    302      */
    303     public static boolean hasGenericSignature(
    304             DexAnnotatedElement annotatedElement) {
    305         return getAnnotation(annotatedElement, SIGNATURE_ANNOTATION) != null;
    306     }
    307 
    308     /**
    309      * Returns the generic signature if the given element has such an
    310      * annotation, null otherwise.
    311      *
    312      * @param annotatedElement
    313      *            the annotated element
    314      * @return he generic signature if the given element has such an annotation,
    315      *         null otherwise
    316      */
    317     @SuppressWarnings("unchecked")
    318     public static String getGenericSignature(
    319             DexAnnotatedElement annotatedElement) {
    320         DexAnnotation annotation = getAnnotation(annotatedElement,
    321                 SIGNATURE_ANNOTATION);
    322         if (annotation != null) {
    323             List<DexEncodedValue> value =
    324                     (List<DexEncodedValue>) getAnnotationAttributeValue(
    325                             annotation, "value");
    326             return concatEncodedValues(value);
    327         }
    328         return null;
    329     }
    330 
    331     /**
    332      * Returns whether the specified annotated element has an annotation with
    333      * type "Ldalvik/annotation/AnnotationDefault;".
    334      *
    335      * @param annotatedElement
    336      *            the annotated element to check
    337      * @return <code>true</code> if the given annotated element has the
    338      *         mentioned annotation, false otherwise
    339      */
    340     public static boolean hasAnnotationDefaultSignature(
    341             DexAnnotatedElement annotatedElement) {
    342         return getAnnotation(
    343                 annotatedElement, ANNOTATION_DEFAULT_ANNOTATION)!= null;
    344     }
    345 
    346     /**
    347      * Returns a mapping form annotation attribute name to its default value.
    348      *
    349      * @param dexClass
    350      *            the class defining a annotation
    351      * @return a mapping form annotation attribute name to its default value
    352      */
    353     public static DexAnnotation getDefaultMappingsAnnotation(
    354             DexClass dexClass) {
    355         return getAnnotation(dexClass, ANNOTATION_DEFAULT_ANNOTATION);
    356     }
    357 
    358     /**
    359      * Returns the annotation with the specified type from the given element or
    360      * null if no such annotation is available.
    361      *
    362      * @param element
    363      *            the annotated element
    364      * @param annotationType
    365      *            the dex internal name of the annotation type
    366      * @return the annotation with the specified type or null if not present
    367      */
    368     public static DexAnnotation getAnnotation(DexAnnotatedElement element,
    369             String annotationType) {
    370         assert element != null;
    371         assert annotationType != null;
    372 
    373         for (DexAnnotation anno : element.getAnnotations()) {
    374             if (annotationType.equals(anno.getTypeName())) {
    375                 return anno;
    376             }
    377         }
    378         return null;
    379     }
    380 
    381     /**
    382      * Returns the value for the specified attribute name of the given
    383      * annotation or null if not present.
    384      *
    385      * @param annotation
    386      *            the annotation
    387      * @param attributeName
    388      *            the name of the attribute
    389      * @return the value for the specified attribute
    390      */
    391     public static Object getAnnotationAttributeValue(DexAnnotation annotation,
    392             String attributeName) {
    393         for (DexAnnotationAttribute dexAnnotationAttribute : annotation
    394                 .getAttributes()) {
    395             if (attributeName.equals(dexAnnotationAttribute.getName())) {
    396                 return dexAnnotationAttribute.getEncodedValue().getValue();
    397             }
    398         }
    399         return null;
    400     }
    401 
    402     private static String concatEncodedValues(List<DexEncodedValue> values) {
    403         StringBuilder builder = new StringBuilder();
    404         for (DexEncodedValue string : values) {
    405             builder.append(string.getValue());
    406         }
    407         return builder.toString();
    408     }
    409 
    410     /**
    411      * Returns true if the given method is a constructor, false otherwise.
    412      *
    413      * @param method
    414      *            the method to test
    415      * @return true if the given method is a constructor, false otherwise
    416      */
    417     public static boolean isConstructor(DexMethod method) {
    418         return "<init>".equals(method.getName());
    419     }
    420 
    421     /**
    422      * Returns true if the given method is a static constructor, false
    423      * otherwise.
    424      *
    425      * @param method
    426      *            the method to test
    427      * @return true if the given method is a static constructor, false otherwise
    428      */
    429     public static boolean isStaticConstructor(DexMethod method) {
    430         return "<clinit>".equals(method.getName());
    431     }
    432 
    433     public static boolean isMethod(DexMethod method) {
    434         return !isConstructor(method) && !isStaticConstructor(method);
    435     }
    436 
    437     /**
    438      * Returns the package-info class for the given package.
    439      *
    440      * @param aPackage
    441      *            the package
    442      * @return the class called "package-info" or null, if not available
    443      */
    444     public static IClassDefinition findPackageInfo(SigPackage aPackage) {
    445         for (IClassDefinition clazz : aPackage.getClasses()) {
    446             if (PACKAGE_INFO.equals(clazz.getName())) {
    447                 return clazz;
    448             }
    449         }
    450         return null;
    451     }
    452 
    453     public static boolean isPackageInfo(DexClass clazz) {
    454         return PACKAGE_INFO.equals(getClassName(clazz.getName()));
    455     }
    456 
    457     public static boolean isInternalAnnotation(DexAnnotation dexAnnotation) {
    458         return INTERNAL_ANNOTATION_NAMES.contains(dexAnnotation.getTypeName());
    459     }
    460 
    461     /**
    462      * An InnerClass annotation is attached to each class which is defined in
    463      * the lexical scope of another class's definition. Any class which has this
    464      * annotation must also have either an EnclosingClass annotation or an
    465      * EnclosingMethod annotation.
    466      */
    467     public static boolean isInnerClass(DexClass clazz) {
    468         return getAnnotation(clazz, INNER_CLASS_ANNOTATION) != null;
    469     }
    470 
    471     /**
    472      * An EnclosingClass annotation is attached to each class which is either
    473      * defined as a member of another class, per se, or is anonymous but not
    474      * defined within a method body (e.g., a synthetic inner class). Every class
    475      * that has this annotation must also have an InnerClass annotation.
    476      * Additionally, a class may not have both an EnclosingClass and an
    477      * EnclosingMethod annotation.
    478      */
    479     public static boolean isEnclosingClass(DexClass clazz) {
    480         return getAnnotation(clazz, ENCLOSING_CLASS_ANNOTATION) != null;
    481     }
    482 
    483     public static boolean declaresMemberClasses(DexClass dexClass) {
    484         return getAnnotation(dexClass, MEMBER_CLASS_ANNOTATION) != null;
    485     }
    486 
    487     @SuppressWarnings("unchecked")
    488     public static Set<String> getMemberClassNames(DexClass dexClass) {
    489         DexAnnotation annotation = getAnnotation(dexClass,
    490                 MEMBER_CLASS_ANNOTATION);
    491         List<DexEncodedValue> enclosedClasses =
    492                 (List<DexEncodedValue>) getAnnotationAttributeValue(
    493                         annotation, "value");
    494         Set<String> enclosedClassesNames = new HashSet<String>();
    495         for (DexEncodedValue string : enclosedClasses) {
    496             enclosedClassesNames.add((String) string.getValue());
    497         }
    498         return enclosedClassesNames;
    499     }
    500 
    501 
    502     public static String getEnclosingClassName(DexClass dexClass) {
    503         DexAnnotation annotation = getAnnotation(dexClass,
    504                 ENCLOSING_CLASS_ANNOTATION);
    505         String value = (String) getAnnotationAttributeValue(annotation,
    506                 "value");
    507         return value;
    508     }
    509 
    510     public static boolean convertAnyWay(DexClass dexClass) {
    511         return !isSynthetic(getClassModifiers(dexClass))
    512                 && !isAnonymousClassName(dexClass.getName())
    513                 || isPackageInfo(dexClass);
    514     }
    515 
    516     public static boolean isVisible(DexClass dexClass, Visibility visibility) {
    517         // package info is always visible
    518         if (isPackageInfo(dexClass)) {
    519             return true;
    520         }
    521 
    522         if (isDeclaredInMethod(dexClass)) {
    523             return false;
    524         }
    525 
    526         if (isAnonymousClassName(dexClass.getName())) {
    527             return false;
    528         }
    529 
    530         int modifiers = getClassModifiers(dexClass);
    531 
    532         return isVisible(modifiers, visibility);
    533     }
    534 
    535     private static boolean isDeclaredInMethod(DexClass dexClass) {
    536         return getAnnotation(dexClass, ENCLOSING_METHOD_ANNOTATION) != null;
    537     }
    538 
    539     /**
    540      * Returns whether the given dex identifier is an anonymous class name.
    541      * Format: La/b/C$1;
    542      *
    543      * @param dexName
    544      *            the name to analyze
    545      * @return whether the given dex identifier is an anonymous class name
    546      */
    547     public static boolean isAnonymousClassName(String dexName) {
    548         int index = dexName.lastIndexOf('$');
    549         return (index != 0) ? Character.isDigit(dexName.charAt(index + 1))
    550                 : false;
    551     }
    552 
    553     public static boolean isVisible(DexField dexField, Visibility visibility) {
    554         return isVisible(dexField.getModifiers(), visibility);
    555     }
    556 
    557     public static boolean isVisible(DexMethod dexMethod,
    558             Visibility visibility) {
    559         return isVisible(dexMethod.getModifiers(), visibility);
    560     }
    561 
    562     private static boolean isVisible(int modifiers, Visibility visibility) {
    563 
    564         if (isSynthetic(modifiers)) {
    565             return false;
    566         }
    567 
    568         Set<Modifier> elementModifiers = getModifier(modifiers);
    569         if (elementModifiers.contains(Modifier.PUBLIC)) {
    570             return true;
    571         } else if (elementModifiers.contains(Modifier.PROTECTED)) {
    572             return visibility == Visibility.PROTECTED
    573                     || visibility == Visibility.PACKAGE
    574                     || visibility == Visibility.PRIVATE;
    575         } else if (elementModifiers.contains(Modifier.PRIVATE)) {
    576             return visibility == Visibility.PRIVATE;
    577         } else {
    578             return visibility == Visibility.PACKAGE
    579                     || visibility == Visibility.PRIVATE;
    580         }
    581     }
    582 
    583     public static Set<DexFile> getDexFiles(Set<String> fileNames)
    584             throws IOException {
    585         Set<DexFile> parsedFiles = new HashSet<DexFile>();
    586 
    587         for (String dexFile : fileNames) {
    588             DexFileReader reader = new DexFileReader();
    589             DexBuffer dexBuffer = new DexBuffer(dexFile);
    590             parsedFiles.add(reader.read(dexBuffer));
    591         }
    592         return parsedFiles;
    593     }
    594 
    595 
    596     public static boolean isJavaLangObject(DexClass dexClass) {
    597         assert dexClass != null;
    598         return JAVA_LANG_OBJECT.equals(dexClass.getName());
    599     }
    600 }
    601