Home | History | Annotate | Download | only in Analysis
      1 /*
      2  * [The "BSD licence"]
      3  * Copyright (c) 2010 Ben Gruver (JesusFreke)
      4  * All rights reserved.
      5  *
      6  * Redistribution and use in source and binary forms, with or without
      7  * modification, are permitted provided that the following conditions
      8  * are met:
      9  * 1. Redistributions of source code must retain the above copyright
     10  *    notice, this list of conditions and the following disclaimer.
     11  * 2. Redistributions in binary form must reproduce the above copyright
     12  *    notice, this list of conditions and the following disclaimer in the
     13  *    documentation and/or other materials provided with the distribution.
     14  * 3. The name of the author may not be used to endorse or promote products
     15  *    derived from this software without specific prior written permission.
     16  *
     17  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
     18  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
     19  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
     20  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
     21  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
     22  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
     23  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
     24  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     25  * INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
     26  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     27  */
     28 
     29 package org.jf.dexlib.Code.Analysis;
     30 
     31 import org.jf.dexlib.*;
     32 
     33 import java.util.ArrayList;
     34 import java.util.LinkedList;
     35 import java.util.regex.Matcher;
     36 import java.util.regex.Pattern;
     37 
     38 public class DeodexUtil {
     39     public static final int Virtual = 0;
     40     public static final int Direct = 1;
     41     public static final int Static = 2;
     42 
     43     private final InlineMethodResolver inlineMethodResolver;
     44 
     45     public final DexFile dexFile;
     46 
     47     public DeodexUtil(DexFile dexFile) {
     48         this.dexFile = dexFile;
     49         OdexHeader odexHeader = dexFile.getOdexHeader();
     50         if (odexHeader == null) {
     51             //if there isn't an odex header, why are we creating an DeodexUtil object?
     52             assert false;
     53             throw new RuntimeException("Cannot create a DeodexUtil object for a dex file without an odex header");
     54         }
     55         inlineMethodResolver = InlineMethodResolver.createInlineMethodResolver(this, odexHeader.version);
     56     }
     57 
     58     public DeodexUtil(DexFile dexFile, InlineMethodResolver inlineMethodResolver) {
     59         this.dexFile = dexFile;
     60         this.inlineMethodResolver = inlineMethodResolver;
     61     }
     62 
     63     public InlineMethod lookupInlineMethod(AnalyzedInstruction instruction) {
     64         return inlineMethodResolver.resolveExecuteInline(instruction);
     65     }
     66 
     67     public FieldIdItem lookupField(ClassPath.ClassDef accessingClass, ClassPath.ClassDef instanceClass,
     68                                    int fieldOffset) {
     69         ClassPath.FieldDef field = instanceClass.getInstanceField(fieldOffset);
     70         if (field == null) {
     71             return null;
     72         }
     73 
     74         return parseAndResolveField(accessingClass, instanceClass, field);
     75     }
     76 
     77     private static final Pattern shortMethodPattern = Pattern.compile("([^(]+)\\(([^)]*)\\)(.+)");
     78 
     79     public MethodIdItem lookupVirtualMethod(ClassPath.ClassDef accessingClass, ClassPath.ClassDef instanceClass,
     80                                             int methodIndex) {
     81         String method = instanceClass.getVirtualMethod(methodIndex);
     82         if (method == null) {
     83             return null;
     84         }
     85 
     86         Matcher m = shortMethodPattern.matcher(method);
     87         if (!m.matches()) {
     88             assert false;
     89             throw new RuntimeException("Invalid method descriptor: " + method);
     90         }
     91 
     92         String methodName = m.group(1);
     93         String methodParams = m.group(2);
     94         String methodRet = m.group(3);
     95 
     96         if (instanceClass instanceof ClassPath.UnresolvedClassDef) {
     97             //if this is an unresolved class, the only way getVirtualMethod could have found a method is if the virtual
     98             //method being looked up was a method on java.lang.Object.
     99             instanceClass = ClassPath.getClassDef("Ljava/lang/Object;");
    100         } else if (instanceClass.isInterface()) {
    101             instanceClass = instanceClass.getSuperclass();
    102             assert instanceClass != null;
    103         }
    104 
    105         return parseAndResolveMethod(accessingClass, instanceClass, methodName, methodParams, methodRet);
    106     }
    107 
    108     private MethodIdItem parseAndResolveMethod(ClassPath.ClassDef accessingClass, ClassPath.ClassDef definingClass,
    109                                                String methodName, String methodParams, String methodRet) {
    110         StringIdItem methodNameItem = StringIdItem.lookupStringIdItem(dexFile, methodName);
    111         if (methodNameItem == null) {
    112             return null;
    113         }
    114 
    115         LinkedList<TypeIdItem> paramList = new LinkedList<TypeIdItem>();
    116 
    117         for (int i=0; i<methodParams.length(); i++) {
    118             TypeIdItem typeIdItem;
    119 
    120             switch (methodParams.charAt(i)) {
    121                 case 'Z':
    122                 case 'B':
    123                 case 'S':
    124                 case 'C':
    125                 case 'I':
    126                 case 'J':
    127                 case 'F':
    128                 case 'D':
    129                     typeIdItem = TypeIdItem.lookupTypeIdItem(dexFile, methodParams.substring(i,i+1));
    130                     break;
    131                 case 'L':
    132                 {
    133                     int end = methodParams.indexOf(';', i);
    134                     if (end == -1) {
    135                         throw new RuntimeException("invalid parameter in the method");
    136                     }
    137 
    138                     typeIdItem = TypeIdItem.lookupTypeIdItem(dexFile, methodParams.substring(i, end+1));
    139                     i = end;
    140                     break;
    141                 }
    142                 case '[':
    143                 {
    144                     int end;
    145                     int typeStart = i+1;
    146                     while (typeStart < methodParams.length() && methodParams.charAt(typeStart) == '[') {
    147                         typeStart++;
    148                     }
    149                     switch (methodParams.charAt(typeStart)) {
    150                         case 'Z':
    151                         case 'B':
    152                         case 'S':
    153                         case 'C':
    154                         case 'I':
    155                         case 'J':
    156                         case 'F':
    157                         case 'D':
    158                             end = typeStart;
    159                             break;
    160                         case 'L':
    161                             end = methodParams.indexOf(';', typeStart);
    162                             if (end == -1) {
    163                                 throw new RuntimeException("invalid parameter in the method");
    164                             }
    165                             break;
    166                         default:
    167                             throw new RuntimeException("invalid parameter in the method");
    168                     }
    169 
    170                     typeIdItem = TypeIdItem.lookupTypeIdItem(dexFile, methodParams.substring(i, end+1));
    171                     i = end;
    172                     break;
    173                 }
    174                 default:
    175                     throw new RuntimeException("invalid parameter in the method");
    176             }
    177 
    178             if (typeIdItem == null) {
    179                 return null;
    180             }
    181             paramList.add(typeIdItem);
    182         }
    183 
    184         TypeListItem paramListItem = null;
    185         if (paramList.size() > 0) {
    186             paramListItem = TypeListItem.lookupTypeListItem(dexFile, paramList);
    187             if (paramListItem == null) {
    188                 return null;
    189             }
    190         }
    191 
    192         TypeIdItem retType = TypeIdItem.lookupTypeIdItem(dexFile, methodRet);
    193         if (retType == null) {
    194             return null;
    195         }
    196 
    197         ProtoIdItem protoItem = ProtoIdItem.lookupProtoIdItem(dexFile, retType, paramListItem);
    198         if (protoItem == null) {
    199             return null;
    200         }
    201 
    202         ClassPath.ClassDef methodClassDef = definingClass;
    203 
    204         do {
    205             TypeIdItem classTypeItem = TypeIdItem.lookupTypeIdItem(dexFile, methodClassDef.getClassType());
    206 
    207             if (classTypeItem != null) {
    208                 MethodIdItem methodIdItem = MethodIdItem.lookupMethodIdItem(dexFile, classTypeItem, protoItem, methodNameItem);
    209                 if (methodIdItem != null && checkClassAccess(accessingClass, methodClassDef)) {
    210                     return methodIdItem;
    211                 }
    212             }
    213 
    214             methodClassDef = methodClassDef.getSuperclass();
    215         } while (methodClassDef != null);
    216         return null;
    217     }
    218 
    219     private static boolean checkClassAccess(ClassPath.ClassDef accessingClass, ClassPath.ClassDef definingClass) {
    220         return definingClass.isPublic() ||
    221                 getPackage(accessingClass.getClassType()).equals(getPackage(definingClass.getClassType()));
    222     }
    223 
    224     private static String getPackage(String classRef) {
    225         int lastSlash = classRef.lastIndexOf('/');
    226         if (lastSlash < 0) {
    227             return "";
    228         }
    229         return classRef.substring(1, lastSlash);
    230     }
    231 
    232     /**
    233      *
    234      * @param accessingClass The class that contains the field reference. I.e. the class being deodexed
    235      * @param instanceClass The inferred class type  of the object that the field is being accessed on
    236      * @param field The field being accessed
    237      * @return The FieldIdItem of the resolved field
    238      */
    239     private FieldIdItem parseAndResolveField(ClassPath.ClassDef accessingClass, ClassPath.ClassDef instanceClass,
    240                                              ClassPath.FieldDef field) {
    241         String definingClass = field.definingClass;
    242         String fieldName = field.name;
    243         String fieldType = field.type;
    244 
    245         StringIdItem fieldNameItem = StringIdItem.lookupStringIdItem(dexFile, fieldName);
    246         if (fieldNameItem == null) {
    247             return null;
    248         }
    249 
    250         TypeIdItem fieldTypeItem = TypeIdItem.lookupTypeIdItem(dexFile, fieldType);
    251         if (fieldTypeItem == null) {
    252             return null;
    253         }
    254 
    255         ClassPath.ClassDef fieldClass = instanceClass;
    256 
    257         ArrayList<ClassPath.ClassDef> parents = new ArrayList<ClassPath.ClassDef>();
    258         parents.add(fieldClass);
    259 
    260         while (fieldClass != null && !fieldClass.getClassType().equals(definingClass)) {
    261             fieldClass = fieldClass.getSuperclass();
    262             parents.add(fieldClass);
    263         }
    264 
    265         for (int i=parents.size()-1; i>=0; i--) {
    266             fieldClass = parents.get(i);
    267 
    268             TypeIdItem classTypeItem = TypeIdItem.lookupTypeIdItem(dexFile, fieldClass.getClassType());
    269             if (classTypeItem == null) {
    270                 continue;
    271             }
    272 
    273             FieldIdItem fieldIdItem = FieldIdItem.lookupFieldIdItem(dexFile, classTypeItem, fieldTypeItem, fieldNameItem);
    274             if (fieldIdItem != null && checkClassAccess(accessingClass, fieldClass)) {
    275                 return fieldIdItem;
    276             }
    277         }
    278         return null;
    279     }
    280 
    281     public static class InlineMethod {
    282         public final int methodType;
    283         public final String classType;
    284         public final String methodName;
    285         public final String parameters;
    286         public final String returnType;
    287 
    288         private MethodIdItem methodIdItem = null;
    289 
    290         InlineMethod(int methodType, String classType, String methodName, String parameters,
    291                                String returnType) {
    292             this.methodType = methodType;
    293             this.classType = classType;
    294             this.methodName = methodName;
    295             this.parameters = parameters;
    296             this.returnType = returnType;
    297         }
    298 
    299         public MethodIdItem getMethodIdItem(DeodexUtil deodexUtil) {
    300             if (methodIdItem == null) {
    301                 loadMethod(deodexUtil);
    302             }
    303             return methodIdItem;
    304         }
    305 
    306         private void loadMethod(DeodexUtil deodexUtil) {
    307             ClassPath.ClassDef classDef = ClassPath.getClassDef(classType);
    308 
    309             this.methodIdItem = deodexUtil.parseAndResolveMethod(classDef, classDef, methodName, parameters,
    310                     returnType);
    311         }
    312 
    313         public String getMethodString() {
    314             return String.format("%s->%s(%s)%s", classType, methodName, parameters, returnType);
    315         }
    316     }
    317 }
    318