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