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 32 package org.jf.dexlib2.analysis; 33 34 import com.google.common.cache.CacheBuilder; 35 import com.google.common.cache.CacheLoader; 36 import com.google.common.cache.LoadingCache; 37 import com.google.common.collect.ImmutableSet; 38 import com.google.common.collect.Iterables; 39 import com.google.common.collect.Lists; 40 import com.google.common.collect.Maps; 41 import org.jf.dexlib2.DexFileFactory; 42 import org.jf.dexlib2.analysis.reflection.ReflectionClassDef; 43 import org.jf.dexlib2.iface.ClassDef; 44 import org.jf.dexlib2.iface.DexFile; 45 import org.jf.dexlib2.immutable.ImmutableDexFile; 46 import org.jf.util.ExceptionWithContext; 47 48 import javax.annotation.Nonnull; 49 import java.io.File; 50 import java.io.IOException; 51 import java.io.Serializable; 52 import java.util.ArrayList; 53 import java.util.HashMap; 54 import java.util.regex.Matcher; 55 import java.util.regex.Pattern; 56 57 public 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