ClassPath.java revision 64898161b3de82f44f6e1d48e3037cc15e1c5ecd
1/*
2 * Copyright 2013, Google Inc.
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are
7 * met:
8 *
9 *     * Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 *     * Redistributions in binary form must reproduce the above
12 * copyright notice, this list of conditions and the following disclaimer
13 * in the documentation and/or other materials provided with the
14 * distribution.
15 *     * Neither the name of Google Inc. nor the names of its
16 * contributors may be used to endorse or promote products derived from
17 * this software without specific prior written permission.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 */
31
32package org.jf.dexlib2.analysis;
33
34import com.google.common.collect.ImmutableSet;
35import com.google.common.collect.Iterables;
36import com.google.common.collect.Lists;
37import com.google.common.collect.Maps;
38import org.jf.dexlib2.DexFileFactory;
39import org.jf.dexlib2.analysis.reflection.ReflectionClassDef;
40import org.jf.dexlib2.iface.ClassDef;
41import org.jf.dexlib2.iface.DexFile;
42import org.jf.dexlib2.immutable.ImmutableDexFile;
43import org.jf.util.ExceptionWithContext;
44
45import javax.annotation.Nonnull;
46import java.io.File;
47import java.io.IOException;
48import java.io.Serializable;
49import java.util.ArrayList;
50import java.util.HashMap;
51import java.util.regex.Matcher;
52import java.util.regex.Pattern;
53
54public class ClassPath {
55    @Nonnull private final TypeProto unknownClass;
56    @Nonnull private DexFile[] dexFiles;
57    @Nonnull private HashMap<String, TypeProto> loadedClasses = Maps.newHashMap();
58    @Nonnull private int api;
59
60    /**
61     * Creates a new ClassPath instance that can load classes from the given dex files
62     *
63     * @param classPath An array of DexFile objects. When loading a class, these dex files will be searched in order
64     */
65    public ClassPath(int api, DexFile... classPath) throws IOException {
66        this(api, classPath, true);
67    }
68
69    /**
70     * Creates a new ClassPath instance that can load classes from the given dex files
71     *
72     * @param classPath An iterable of DexFile objects. When loading a class, these dex files will be searched in order
73     */
74    public ClassPath(int api, Iterable<DexFile> classPath) {
75        this(api, Iterables.toArray(classPath, DexFile.class), false);
76    }
77
78    private ClassPath(int api, @Nonnull DexFile[] classPath, boolean copyArray) {
79        if (copyArray) {
80            dexFiles = new DexFile[classPath.length+1];
81            System.arraycopy(classPath, 0, dexFiles, 0, classPath.length);
82            // add fallbacks for certain special classes that must be present
83            dexFiles[dexFiles.length - 1] = getBasicClasses();
84        } else {
85            dexFiles = classPath;
86        }
87
88        unknownClass = new UnknownClassProto(this);
89        loadedClasses.put(unknownClass.getType(), unknownClass);
90        this.api = api;
91
92        loadPrimitiveType("Z");
93        loadPrimitiveType("B");
94        loadPrimitiveType("S");
95        loadPrimitiveType("C");
96        loadPrimitiveType("I");
97        loadPrimitiveType("J");
98        loadPrimitiveType("F");
99        loadPrimitiveType("D");
100        loadPrimitiveType("L");
101    }
102
103    private void loadPrimitiveType(String type) {
104        loadedClasses.put(type, new PrimitiveProto(this, type));
105    }
106
107    private static DexFile getBasicClasses() {
108        // fallbacks for some special classes that we assume are present
109        return new ImmutableDexFile(ImmutableSet.of(
110                new ReflectionClassDef(Class.class),
111                new ReflectionClassDef(Cloneable.class),
112                new ReflectionClassDef(Object.class),
113                new ReflectionClassDef(Serializable.class),
114                new ReflectionClassDef(String.class),
115                new ReflectionClassDef(Throwable.class)));
116    }
117
118    @Nonnull
119    public TypeProto getClass(CharSequence type) {
120        String typeString = type.toString();
121        TypeProto typeProto = loadedClasses.get(typeString);
122        if (typeProto != null) {
123            return typeProto;
124        }
125
126        if (type.charAt(0) == '[') {
127            typeProto = new ArrayProto(this, typeString);
128        } else {
129            typeProto = new ClassProto(this, typeString);
130        }
131        // All primitive types are preloaded into loadedClasses, so we don't need to check for that here
132
133        loadedClasses.put(typeString, typeProto);
134        return typeProto;
135    }
136
137    @Nonnull
138    public ClassDef getClassDef(String type) {
139        // TODO: need a <= O(log) way to look up classes
140        for (DexFile dexFile: dexFiles) {
141            for (ClassDef classDef: dexFile.getClasses()) {
142                if (classDef.getType().equals(type)) {
143                    return classDef;
144                }
145            }
146        }
147        throw new UnresolvedClassException("Could not resolve class %s", type);
148    }
149
150    @Nonnull
151    public TypeProto getUnknownClass() {
152        return unknownClass;
153    }
154
155    public int getApi() {
156        return api;
157    }
158
159    @Nonnull
160    public static ClassPath fromClassPath(Iterable<String> classPathDirs, Iterable<String> classPath, DexFile dexFile,
161                                          int api) {
162        ArrayList<DexFile> dexFiles = Lists.newArrayList();
163
164        for (String classPathEntry: classPath) {
165            dexFiles.add(loadClassPathEntry(classPathDirs, classPathEntry, api));
166        }
167        dexFiles.add(dexFile);
168        return new ClassPath(api, dexFiles);
169    }
170
171    private static final Pattern dalvikCacheOdexPattern = Pattern.compile("@([^@]+)@classes.dex$");
172
173    @Nonnull
174    private static DexFile loadClassPathEntry(@Nonnull Iterable<String> classPathDirs,
175                                              @Nonnull String bootClassPathEntry, int api) {
176        File rawEntry = new File(bootClassPathEntry);
177        // strip off the path - we only care about the filename
178        String entryName = rawEntry.getName();
179
180        // if it's a dalvik-cache entry, grab the name of the jar/apk
181        if (entryName.endsWith("@classes.dex")) {
182            Matcher m = dalvikCacheOdexPattern.matcher(entryName);
183
184            if (!m.find()) {
185                throw new ExceptionWithContext(String.format("Cannot parse dependency value %s", bootClassPathEntry));
186            }
187
188            entryName = m.group(1);
189        }
190
191        int extIndex = entryName.lastIndexOf(".");
192
193        String baseEntryName;
194        if (extIndex == -1) {
195            baseEntryName = entryName;
196        } else {
197            baseEntryName = entryName.substring(0, extIndex);
198        }
199
200        for (String classPathDir: classPathDirs) {
201            for (String ext: new String[]{"", ".odex", ".jar", ".apk", ".zip"}) {
202                File file = new File(classPathDir, baseEntryName + ext);
203
204                if (file.exists() && file.isFile()) {
205                    if (!file.canRead()) {
206                        System.err.println(String.format(
207                                "warning: cannot open %s for reading. Will continue looking.", file.getPath()));
208                    } else {
209                        try {
210                            DexFile dexfile = DexFileFactory.loadDexFile(file, api);
211                            System.out.println(file.toString());
212                            return dexfile;
213                        } catch (DexFileFactory.NoClassesDexException ex) {
214                            // ignore and continue
215                        } catch (Exception ex) {
216                            throw ExceptionWithContext.withContext(ex,
217                                    "Error while reading boot class path entry \"%s\"", bootClassPathEntry);
218                        }
219                    }
220                }
221            }
222        }
223        throw new ExceptionWithContext("Cannot locate boot class path file %s", bootClassPathEntry);
224    }
225}
226