DeodexUtil.java revision ebd1b0e9c14f46cb55534cbd48084666afbdef21
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
29package org.jf.dexlib.Code.Analysis;
30
31import org.jf.dexlib.*;
32
33import java.util.ArrayList;
34import java.util.LinkedList;
35import java.util.regex.Matcher;
36import java.util.regex.Pattern;
37
38public 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