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.base.Supplier; 35 import com.google.common.base.Suppliers; 36 import com.google.common.cache.CacheBuilder; 37 import com.google.common.cache.CacheLoader; 38 import com.google.common.cache.LoadingCache; 39 import com.google.common.collect.ImmutableList; 40 import com.google.common.collect.ImmutableSet; 41 import com.google.common.collect.Lists; 42 import org.jf.dexlib2.DexFileFactory; 43 import org.jf.dexlib2.DexFileFactory.DexFileNotFound; 44 import org.jf.dexlib2.DexFileFactory.MultipleDexFilesException; 45 import org.jf.dexlib2.Opcodes; 46 import org.jf.dexlib2.analysis.reflection.ReflectionClassDef; 47 import org.jf.dexlib2.dexbacked.OatFile.OatDexFile; 48 import org.jf.dexlib2.iface.ClassDef; 49 import org.jf.dexlib2.iface.DexFile; 50 import org.jf.dexlib2.immutable.ImmutableDexFile; 51 import org.jf.util.ExceptionWithContext; 52 53 import javax.annotation.Nonnull; 54 import java.io.File; 55 import java.io.IOException; 56 import java.io.Serializable; 57 import java.util.Arrays; 58 import java.util.List; 59 import java.util.regex.Matcher; 60 import java.util.regex.Pattern; 61 62 public class ClassPath { 63 @Nonnull private final TypeProto unknownClass; 64 @Nonnull private List<ClassProvider> classProviders; 65 private final boolean checkPackagePrivateAccess; 66 public final int oatVersion; 67 68 public static final int NOT_ART = -1; 69 70 /** 71 * Creates a new ClassPath instance that can load classes from the given providers 72 * 73 * @param classProviders An iterable of ClassProviders. When loading a class, these providers will be searched in 74 * order 75 */ 76 public ClassPath(ClassProvider... classProviders) throws IOException { 77 this(Arrays.asList(classProviders), false, NOT_ART); 78 } 79 80 /** 81 * Creates a new ClassPath instance that can load classes from the given providers 82 * 83 * @param classProviders An iterable of ClassProviders. When loading a class, these providers will be searched in 84 * order 85 * @param checkPackagePrivateAccess Whether checkPackagePrivateAccess is needed, enabled for ONLY early API 17 by 86 * default 87 * @param oatVersion The applicable oat version, or NOT_ART 88 */ 89 public ClassPath(@Nonnull Iterable<? extends ClassProvider> classProviders, boolean checkPackagePrivateAccess, 90 int oatVersion) { 91 // add fallbacks for certain special classes that must be present 92 unknownClass = new UnknownClassProto(this); 93 loadedClasses.put(unknownClass.getType(), unknownClass); 94 this.checkPackagePrivateAccess = checkPackagePrivateAccess; 95 this.oatVersion = oatVersion; 96 97 loadPrimitiveType("Z"); 98 loadPrimitiveType("B"); 99 loadPrimitiveType("S"); 100 loadPrimitiveType("C"); 101 loadPrimitiveType("I"); 102 loadPrimitiveType("J"); 103 loadPrimitiveType("F"); 104 loadPrimitiveType("D"); 105 loadPrimitiveType("L"); 106 107 this.classProviders = Lists.newArrayList(classProviders); 108 this.classProviders.add(getBasicClasses()); 109 } 110 111 private void loadPrimitiveType(String type) { 112 loadedClasses.put(type, new PrimitiveProto(this, type)); 113 } 114 115 private static ClassProvider getBasicClasses() { 116 // fallbacks for some special classes that we assume are present 117 return new DexClassProvider(new ImmutableDexFile(Opcodes.forApi(19), 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 public boolean isArt() { 127 return oatVersion != NOT_ART; 128 } 129 130 @Nonnull 131 public TypeProto getClass(@Nonnull 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 for (ClassProvider provider: classProviders) { 150 ClassDef classDef = provider.getClassDef(type); 151 if (classDef != null) { 152 return classDef; 153 } 154 } 155 throw new UnresolvedClassException("Could not resolve class %s", type); 156 } 157 158 @Nonnull 159 public TypeProto getUnknownClass() { 160 return unknownClass; 161 } 162 163 public boolean shouldCheckPackagePrivateAccess() { 164 return checkPackagePrivateAccess; 165 } 166 167 @Nonnull 168 public static ClassPath fromClassPath(Iterable<String> classPathDirs, Iterable<String> classPath, DexFile dexFile, 169 int api, boolean experimental) { 170 return fromClassPath(classPathDirs, classPath, dexFile, api, api == 17, experimental); 171 } 172 173 @Nonnull 174 public static ClassPath fromClassPath(Iterable<String> classPathDirs, Iterable<String> classPath, DexFile dexFile, 175 int api, boolean checkPackagePrivateAccess, boolean experimental) { 176 List<ClassProvider> providers = Lists.newArrayList(); 177 178 int oatVersion = NOT_ART; 179 180 for (String classPathEntry: classPath) { 181 List<? extends DexFile> classPathDexFiles = 182 loadClassPathEntry(classPathDirs, classPathEntry, api, experimental); 183 if (oatVersion == NOT_ART) { 184 for (DexFile classPathDexFile: classPathDexFiles) { 185 if (classPathDexFile instanceof OatDexFile) { 186 oatVersion = ((OatDexFile)classPathDexFile).getOatVersion(); 187 break; 188 } 189 } 190 } 191 for (DexFile classPathDexFile: classPathDexFiles) { 192 providers.add(new DexClassProvider(classPathDexFile)); 193 } 194 } 195 providers.add(new DexClassProvider(dexFile)); 196 return new ClassPath(providers, checkPackagePrivateAccess, oatVersion); 197 } 198 199 @Nonnull 200 public static ClassPath fromClassPath(Iterable<String> classPathDirs, Iterable<String> classPath, DexFile dexFile, 201 int api, boolean checkPackagePrivateAccess, boolean experimental, 202 int oatVersion) { 203 List<ClassProvider> providers = Lists.newArrayList(); 204 205 for (String classPathEntry: classPath) { 206 List<? extends DexFile> classPathDexFiles = 207 loadClassPathEntry(classPathDirs, classPathEntry, api, experimental); 208 for (DexFile classPathDexFile: classPathDexFiles) { 209 providers.add(new DexClassProvider(classPathDexFile)); 210 } 211 } 212 providers.add(new DexClassProvider(dexFile)); 213 return new ClassPath(providers, checkPackagePrivateAccess, oatVersion); 214 } 215 216 private static final Pattern dalvikCacheOdexPattern = Pattern.compile("@([^@]+)@classes.dex$"); 217 218 @Nonnull 219 private static List<? extends DexFile> loadClassPathEntry(@Nonnull Iterable<String> classPathDirs, 220 @Nonnull String bootClassPathEntry, int api, 221 boolean experimental) { 222 File rawEntry = new File(bootClassPathEntry); 223 // strip off the path - we only care about the filename 224 String entryName = rawEntry.getName(); 225 226 // if it's a dalvik-cache entry, grab the name of the jar/apk 227 if (entryName.endsWith("@classes.dex")) { 228 Matcher m = dalvikCacheOdexPattern.matcher(entryName); 229 230 if (!m.find()) { 231 throw new ExceptionWithContext(String.format("Cannot parse dependency value %s", bootClassPathEntry)); 232 } 233 234 entryName = m.group(1); 235 } 236 237 int extIndex = entryName.lastIndexOf("."); 238 239 String baseEntryName; 240 if (extIndex == -1) { 241 baseEntryName = entryName; 242 } else { 243 baseEntryName = entryName.substring(0, extIndex); 244 } 245 246 for (String classPathDir: classPathDirs) { 247 String[] extensions; 248 249 if (entryName.endsWith(".oat")) { 250 extensions = new String[] { ".oat" }; 251 } else { 252 extensions = new String[] { "", ".odex", ".jar", ".apk", ".zip" }; 253 } 254 255 for (String ext: extensions) { 256 File file = new File(classPathDir, baseEntryName + ext); 257 258 if (file.exists() && file.isFile()) { 259 if (!file.canRead()) { 260 System.err.println(String.format( 261 "warning: cannot open %s for reading. Will continue looking.", file.getPath())); 262 } else { 263 try { 264 return ImmutableList.of(DexFileFactory.loadDexFile(file, api, experimental)); 265 } catch (DexFileNotFound ex) { 266 // ignore and continue 267 } catch (MultipleDexFilesException ex) { 268 return ex.oatFile.getDexFiles(); 269 } catch (Exception ex) { 270 throw ExceptionWithContext.withContext(ex, 271 "Error while reading boot class path entry \"%s\"", bootClassPathEntry); 272 } 273 } 274 } 275 } 276 } 277 throw new ExceptionWithContext("Cannot locate boot class path file %s", bootClassPathEntry); 278 } 279 280 private final Supplier<OdexedFieldInstructionMapper> fieldInstructionMapperSupplier = Suppliers.memoize( 281 new Supplier<OdexedFieldInstructionMapper>() { 282 @Override public OdexedFieldInstructionMapper get() { 283 return new OdexedFieldInstructionMapper(isArt()); 284 } 285 }); 286 287 @Nonnull 288 public OdexedFieldInstructionMapper getFieldInstructionMapper() { 289 return fieldInstructionMapperSupplier.get(); 290 } 291 } 292