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.cache.CacheBuilder;
35import com.google.common.cache.CacheLoader;
36import com.google.common.cache.LoadingCache;
37import com.google.common.collect.ImmutableSet;
38import com.google.common.collect.Iterables;
39import com.google.common.collect.Lists;
40import com.google.common.collect.Maps;
41import org.jf.dexlib2.DexFileFactory;
42import org.jf.dexlib2.analysis.reflection.ReflectionClassDef;
43import org.jf.dexlib2.iface.ClassDef;
44import org.jf.dexlib2.iface.DexFile;
45import org.jf.dexlib2.immutable.ImmutableDexFile;
46import org.jf.util.ExceptionWithContext;
47
48import javax.annotation.Nonnull;
49import java.io.File;
50import java.io.IOException;
51import java.io.Serializable;
52import java.util.ArrayList;
53import java.util.HashMap;
54import java.util.regex.Matcher;
55import java.util.regex.Pattern;
56
57public class ClassPath {
58    @Nonnull private final TypeProto unknownClass;
59    @Nonnull private HashMap<String, ClassDef> availableClasses = Maps.newHashMap();
60    private boolean checkPackagePrivateAccess;
61
62    /**
63     * Creates a new ClassPath instance that can load classes from the given dex files
64     *
65     * @param classPath An array of DexFile objects. When loading a class, these dex files will be searched in order
66     */
67    public ClassPath(DexFile... classPath) throws IOException {
68        this(Lists.newArrayList(classPath), 15);
69    }
70
71    /**
72     * Creates a new ClassPath instance that can load classes from the given dex files
73     *
74     * @param classPath An iterable of DexFile objects. When loading a class, these dex files will be searched in order
75     * @param api API level
76     */
77    public ClassPath(@Nonnull Iterable<DexFile> classPath, int api) {
78        this(Lists.newArrayList(classPath), api == 17);
79    }
80
81    /**
82     * Creates a new ClassPath instance that can load classes from the given dex files
83     *
84     * @param classPath An iterable of DexFile objects. When loading a class, these dex files will be searched in order
85     * @param checkPackagePrivateAccess Whether checkPackagePrivateAccess is needed, enabled for ONLY early API 17 by default
86     */
87    public ClassPath(@Nonnull Iterable<DexFile> classPath, boolean checkPackagePrivateAccess) {
88        // add fallbacks for certain special classes that must be present
89        Iterable<DexFile> dexFiles = Iterables.concat(classPath, Lists.newArrayList(getBasicClasses()));
90
91        unknownClass = new UnknownClassProto(this);
92        loadedClasses.put(unknownClass.getType(), unknownClass);
93        this.checkPackagePrivateAccess = checkPackagePrivateAccess;
94
95        loadPrimitiveType("Z");
96        loadPrimitiveType("B");
97        loadPrimitiveType("S");
98        loadPrimitiveType("C");
99        loadPrimitiveType("I");
100        loadPrimitiveType("J");
101        loadPrimitiveType("F");
102        loadPrimitiveType("D");
103        loadPrimitiveType("L");
104
105        for (DexFile dexFile: dexFiles) {
106            for (ClassDef classDef: dexFile.getClasses()) {
107                ClassDef prev = availableClasses.get(classDef.getType());
108                if (prev == null) {
109                    availableClasses.put(classDef.getType(), classDef);
110                }
111            }
112        }
113    }
114
115    private void loadPrimitiveType(String type) {
116        loadedClasses.put(type, new PrimitiveProto(this, type));
117    }
118
119    private static DexFile getBasicClasses() {
120        // fallbacks for some special classes that we assume are present
121        return new ImmutableDexFile(ImmutableSet.of(
122                new ReflectionClassDef(Class.class),
123                new ReflectionClassDef(Cloneable.class),
124                new ReflectionClassDef(Object.class),
125                new ReflectionClassDef(Serializable.class),
126                new ReflectionClassDef(String.class),
127                new ReflectionClassDef(Throwable.class)));
128    }
129
130    @Nonnull
131    public TypeProto getClass(CharSequence type) {
132        return loadedClasses.getUnchecked(type.toString());
133    }
134
135    private final CacheLoader<String, TypeProto> classLoader = new CacheLoader<String, TypeProto>() {
136        @Override public TypeProto load(String type) throws Exception {
137            if (type.charAt(0) == '[') {
138                return new ArrayProto(ClassPath.this, type);
139            } else {
140                return new ClassProto(ClassPath.this, type);
141            }
142        }
143    };
144
145    @Nonnull private LoadingCache<String, TypeProto> loadedClasses = CacheBuilder.newBuilder().build(classLoader);
146
147    @Nonnull
148    public ClassDef getClassDef(String type) {
149        ClassDef ret = availableClasses.get(type);
150        if (ret == null) {
151            throw new UnresolvedClassException("Could not resolve class %s", type);
152        }
153        return ret;
154    }
155
156    @Nonnull
157    public TypeProto getUnknownClass() {
158        return unknownClass;
159    }
160
161    public boolean shouldCheckPackagePrivateAccess() {
162        return checkPackagePrivateAccess;
163    }
164
165    @Nonnull
166    public static ClassPath fromClassPath(Iterable<String> classPathDirs, Iterable<String> classPath, DexFile dexFile,
167                                          int api) {
168        return fromClassPath(classPathDirs, classPath, dexFile, api, api == 17);
169    }
170
171    @Nonnull
172    public static ClassPath fromClassPath(Iterable<String> classPathDirs, Iterable<String> classPath, DexFile dexFile,
173                                          int api, boolean checkPackagePrivateAccess) {
174        ArrayList<DexFile> dexFiles = Lists.newArrayList();
175
176        for (String classPathEntry: classPath) {
177            try {
178                dexFiles.add(loadClassPathEntry(classPathDirs, classPathEntry, api));
179            } catch (ExceptionWithContext e){}
180        }
181        dexFiles.add(dexFile);
182        return new ClassPath(dexFiles, checkPackagePrivateAccess);
183    }
184
185    private static final Pattern dalvikCacheOdexPattern = Pattern.compile("@([^@]+)@classes.dex$");
186
187    @Nonnull
188    private static DexFile loadClassPathEntry(@Nonnull Iterable<String> classPathDirs,
189                                              @Nonnull String bootClassPathEntry, int api) {
190        File rawEntry = new File(bootClassPathEntry);
191        // strip off the path - we only care about the filename
192        String entryName = rawEntry.getName();
193
194        // if it's a dalvik-cache entry, grab the name of the jar/apk
195        if (entryName.endsWith("@classes.dex")) {
196            Matcher m = dalvikCacheOdexPattern.matcher(entryName);
197
198            if (!m.find()) {
199                throw new ExceptionWithContext(String.format("Cannot parse dependency value %s", bootClassPathEntry));
200            }
201
202            entryName = m.group(1);
203        }
204
205        int extIndex = entryName.lastIndexOf(".");
206
207        String baseEntryName;
208        if (extIndex == -1) {
209            baseEntryName = entryName;
210        } else {
211            baseEntryName = entryName.substring(0, extIndex);
212        }
213
214        for (String classPathDir: classPathDirs) {
215            for (String ext: new String[]{"", ".odex", ".jar", ".apk", ".zip"}) {
216                File file = new File(classPathDir, baseEntryName + ext);
217
218                if (file.exists() && file.isFile()) {
219                    if (!file.canRead()) {
220                        System.err.println(String.format(
221                                "warning: cannot open %s for reading. Will continue looking.", file.getPath()));
222                    } else {
223                        try {
224                            return DexFileFactory.loadDexFile(file, api);
225                        } catch (DexFileFactory.NoClassesDexException ex) {
226                            // ignore and continue
227                        } catch (Exception ex) {
228                            throw ExceptionWithContext.withContext(ex,
229                                    "Error while reading boot class path entry \"%s\"", bootClassPathEntry);
230                        }
231                    }
232                }
233            }
234        }
235        throw new ExceptionWithContext("Cannot locate boot class path file %s", bootClassPathEntry);
236    }
237}
238