ClassPath.java revision 6fc32629c25d351119395922a6eb6701f09dffa4
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 HashMap<String, ClassDef> availableClasses = Maps.newHashMap(); 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(DexFile... classPath) throws IOException { 66 this(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(Iterable<DexFile> classPath) { 75 this(Iterables.toArray(classPath, DexFile.class), false); 76 } 77 78 private ClassPath(@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 91 loadPrimitiveType("Z"); 92 loadPrimitiveType("B"); 93 loadPrimitiveType("S"); 94 loadPrimitiveType("C"); 95 loadPrimitiveType("I"); 96 loadPrimitiveType("J"); 97 loadPrimitiveType("F"); 98 loadPrimitiveType("D"); 99 loadPrimitiveType("L"); 100 101 for (DexFile dexFile: dexFiles) { 102 for (ClassDef classDef: dexFile.getClasses()) { 103 ClassDef prev = availableClasses.get(classDef.getType()); 104 if (prev == null) { 105 availableClasses.put(classDef.getType(), classDef); 106 } 107 } 108 } 109 } 110 111 private void loadPrimitiveType(String type) { 112 loadedClasses.put(type, new PrimitiveProto(this, type)); 113 } 114 115 private static DexFile getBasicClasses() { 116 // fallbacks for some special classes that we assume are present 117 return new ImmutableDexFile(ImmutableSet.of( 118 new ReflectionClassDef(Class.class), 119 new ReflectionClassDef(Cloneable.class), 120 new ReflectionClassDef(Object.class), 121 new ReflectionClassDef(Serializable.class), 122 new ReflectionClassDef(String.class), 123 new ReflectionClassDef(Throwable.class))); 124 } 125 126 @Nonnull 127 public TypeProto getClass(CharSequence type) { 128 String typeString = type.toString(); 129 TypeProto typeProto = loadedClasses.get(typeString); 130 if (typeProto != null) { 131 return typeProto; 132 } 133 134 if (type.charAt(0) == '[') { 135 typeProto = new ArrayProto(this, typeString); 136 } else { 137 typeProto = new ClassProto(this, typeString); 138 } 139 // All primitive types are preloaded into loadedClasses, so we don't need to check for that here 140 141 loadedClasses.put(typeString, typeProto); 142 return typeProto; 143 } 144 145 @Nonnull 146 public ClassDef getClassDef(String type) { 147 ClassDef ret = availableClasses.get(type); 148 if (ret == null) { 149 throw new UnresolvedClassException("Could not resolve class %s", type); 150 } 151 return ret; 152 } 153 154 @Nonnull 155 public TypeProto getUnknownClass() { 156 return unknownClass; 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(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 return DexFileFactory.loadDexFile(file, api); 211 } catch (DexFileFactory.NoClassesDexException ex) { 212 // ignore and continue 213 } catch (Exception ex) { 214 throw ExceptionWithContext.withContext(ex, 215 "Error while reading boot class path entry \"%s\"", bootClassPathEntry); 216 } 217 } 218 } 219 } 220 } 221 throw new ExceptionWithContext("Cannot locate boot class path file %s", bootClassPathEntry); 222 } 223} 224