1 /* 2 * Copyright (C) 2011 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package dalvik.system; 18 19 import android.compat.annotation.UnsupportedAppUsage; 20 import android.system.ErrnoException; 21 import android.system.StructStat; 22 23 import java.io.File; 24 import java.io.IOException; 25 import java.lang.reflect.Array; 26 import java.net.MalformedURLException; 27 import java.net.URL; 28 import java.nio.ByteBuffer; 29 import java.util.ArrayList; 30 import java.util.Arrays; 31 import java.util.Collection; 32 import java.util.Collections; 33 import java.util.Enumeration; 34 import java.util.List; 35 import java.util.Objects; 36 import libcore.io.ClassPathURLStreamHandler; 37 import libcore.io.IoUtils; 38 import libcore.io.Libcore; 39 40 import static android.system.OsConstants.S_ISDIR; 41 42 /** 43 * A pair of lists of entries, associated with a {@code ClassLoader}. 44 * One of the lists is a dex/resource path — typically referred 45 * to as a "class path" — list, and the other names directories 46 * containing native code libraries. Class path entries may be any of: 47 * a {@code .jar} or {@code .zip} file containing an optional 48 * top-level {@code classes.dex} file as well as arbitrary resources, 49 * or a plain {@code .dex} file (with no possibility of associated 50 * resources). 51 * 52 * <p>This class also contains methods to use these lists to look up 53 * classes and resources.</p> 54 * 55 * @hide 56 */ 57 public final class DexPathList { 58 private static final String DEX_SUFFIX = ".dex"; 59 private static final String zipSeparator = "!/"; 60 61 /** class definition context */ 62 @UnsupportedAppUsage 63 private final ClassLoader definingContext; 64 65 /** 66 * List of dex/resource (class path) elements. 67 * Should be called pathElements, but the Facebook app uses reflection 68 * to modify 'dexElements' (http://b/7726934). 69 */ 70 @UnsupportedAppUsage 71 private Element[] dexElements; 72 73 /** List of native library path elements. */ 74 // Some applications rely on this field being an array or we'd use a final list here 75 @UnsupportedAppUsage 76 /* package visible for testing */ NativeLibraryElement[] nativeLibraryPathElements; 77 78 /** List of application native library directories. */ 79 @UnsupportedAppUsage 80 private final List<File> nativeLibraryDirectories; 81 82 /** List of system native library directories. */ 83 @UnsupportedAppUsage 84 private final List<File> systemNativeLibraryDirectories; 85 86 /** 87 * Exceptions thrown during creation of the dexElements list. 88 */ 89 @UnsupportedAppUsage 90 private IOException[] dexElementsSuppressedExceptions; 91 getAllNativeLibraryDirectories()92 private List<File> getAllNativeLibraryDirectories() { 93 List<File> allNativeLibraryDirectories = new ArrayList<>(nativeLibraryDirectories); 94 allNativeLibraryDirectories.addAll(systemNativeLibraryDirectories); 95 return allNativeLibraryDirectories; 96 } 97 98 /** 99 * Construct an instance. 100 * 101 * @param definingContext the context in which any as-yet unresolved 102 * classes should be defined 103 * 104 * @param dexFiles the bytebuffers containing the dex files that we should load classes from. 105 */ DexPathList(ClassLoader definingContext, String librarySearchPath)106 public DexPathList(ClassLoader definingContext, String librarySearchPath) { 107 if (definingContext == null) { 108 throw new NullPointerException("definingContext == null"); 109 } 110 111 this.definingContext = definingContext; 112 this.nativeLibraryDirectories = splitPaths(librarySearchPath, false); 113 this.systemNativeLibraryDirectories = 114 splitPaths(System.getProperty("java.library.path"), true); 115 this.nativeLibraryPathElements = makePathElements(getAllNativeLibraryDirectories()); 116 } 117 118 /** 119 * Constructs an instance. 120 * 121 * @param definingContext the context in which any as-yet unresolved 122 * classes should be defined 123 * @param dexPath list of dex/resource path elements, separated by 124 * {@code File.pathSeparator} 125 * @param librarySearchPath list of native library directory path elements, 126 * separated by {@code File.pathSeparator} 127 * @param optimizedDirectory directory where optimized {@code .dex} files 128 * should be found and written to, or {@code null} to use the default 129 * system directory for same 130 */ 131 @UnsupportedAppUsage DexPathList(ClassLoader definingContext, String dexPath, String librarySearchPath, File optimizedDirectory)132 public DexPathList(ClassLoader definingContext, String dexPath, 133 String librarySearchPath, File optimizedDirectory) { 134 this(definingContext, dexPath, librarySearchPath, optimizedDirectory, false); 135 } 136 DexPathList(ClassLoader definingContext, String dexPath, String librarySearchPath, File optimizedDirectory, boolean isTrusted)137 DexPathList(ClassLoader definingContext, String dexPath, 138 String librarySearchPath, File optimizedDirectory, boolean isTrusted) { 139 if (definingContext == null) { 140 throw new NullPointerException("definingContext == null"); 141 } 142 143 if (dexPath == null) { 144 throw new NullPointerException("dexPath == null"); 145 } 146 147 if (optimizedDirectory != null) { 148 if (!optimizedDirectory.exists()) { 149 throw new IllegalArgumentException( 150 "optimizedDirectory doesn't exist: " 151 + optimizedDirectory); 152 } 153 154 if (!(optimizedDirectory.canRead() 155 && optimizedDirectory.canWrite())) { 156 throw new IllegalArgumentException( 157 "optimizedDirectory not readable/writable: " 158 + optimizedDirectory); 159 } 160 } 161 162 this.definingContext = definingContext; 163 164 ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>(); 165 // save dexPath for BaseDexClassLoader 166 this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory, 167 suppressedExceptions, definingContext, isTrusted); 168 169 // Native libraries may exist in both the system and 170 // application library paths, and we use this search order: 171 // 172 // 1. This class loader's library path for application libraries (librarySearchPath): 173 // 1.1. Native library directories 174 // 1.2. Path to libraries in apk-files 175 // 2. The VM's library path from the system property for system libraries 176 // also known as java.library.path 177 // 178 // This order was reversed prior to Gingerbread; see http://b/2933456. 179 this.nativeLibraryDirectories = splitPaths(librarySearchPath, false); 180 this.systemNativeLibraryDirectories = 181 splitPaths(System.getProperty("java.library.path"), true); 182 this.nativeLibraryPathElements = makePathElements(getAllNativeLibraryDirectories()); 183 184 if (suppressedExceptions.size() > 0) { 185 this.dexElementsSuppressedExceptions = 186 suppressedExceptions.toArray(new IOException[suppressedExceptions.size()]); 187 } else { 188 dexElementsSuppressedExceptions = null; 189 } 190 } 191 toString()192 @Override public String toString() { 193 return "DexPathList[" + Arrays.toString(dexElements) + 194 ",nativeLibraryDirectories=" + 195 Arrays.toString(getAllNativeLibraryDirectories().toArray()) + "]"; 196 } 197 198 /** 199 * For BaseDexClassLoader.getLdLibraryPath. 200 */ getNativeLibraryDirectories()201 public List<File> getNativeLibraryDirectories() { 202 return nativeLibraryDirectories; 203 } 204 205 /** 206 * Adds a new path to this instance 207 * @param dexPath list of dex/resource path element, separated by 208 * {@code File.pathSeparator} 209 * @param optimizedDirectory directory where optimized {@code .dex} files 210 * should be found and written to, or {@code null} to use the default 211 * system directory for same 212 */ 213 @UnsupportedAppUsage addDexPath(String dexPath, File optimizedDirectory)214 public void addDexPath(String dexPath, File optimizedDirectory) { 215 addDexPath(dexPath, optimizedDirectory, false); 216 } 217 addDexPath(String dexPath, File optimizedDirectory, boolean isTrusted)218 public void addDexPath(String dexPath, File optimizedDirectory, boolean isTrusted) { 219 final List<IOException> suppressedExceptionList = new ArrayList<IOException>(); 220 final Element[] newElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory, 221 suppressedExceptionList, definingContext, isTrusted); 222 223 if (newElements != null && newElements.length > 0) { 224 dexElements = concat(Element.class, dexElements, newElements); 225 } 226 227 if (suppressedExceptionList.size() > 0) { 228 final IOException[] newSuppExceptions = suppressedExceptionList.toArray( 229 new IOException[suppressedExceptionList.size()]); 230 dexElementsSuppressedExceptions = dexElementsSuppressedExceptions != null 231 ? concat(IOException.class, dexElementsSuppressedExceptions, newSuppExceptions) 232 : newSuppExceptions; 233 } 234 } 235 concat(Class<T> componentType, T[] inputA, T[] inputB)236 private static<T> T[] concat(Class<T> componentType, T[] inputA, T[] inputB) { 237 T[] output = (T[]) Array.newInstance(componentType, inputA.length + inputB.length); 238 System.arraycopy(inputA, 0, output, 0, inputA.length); 239 System.arraycopy(inputB, 0, output, inputA.length, inputB.length); 240 return output; 241 } 242 243 /** 244 * For InMemoryDexClassLoader. Initializes {@code dexElements} with dex files 245 * loaded from {@code dexFiles} buffers. 246 * 247 * @param dexFiles ByteBuffers containing raw dex data. Apks are not supported. 248 */ initByteBufferDexPath(ByteBuffer[] dexFiles)249 /* package */ void initByteBufferDexPath(ByteBuffer[] dexFiles) { 250 if (dexFiles == null) { 251 throw new NullPointerException("dexFiles == null"); 252 } 253 if (Arrays.stream(dexFiles).anyMatch(v -> v == null)) { 254 throw new NullPointerException("dexFiles contains a null Buffer!"); 255 } 256 if (dexElements != null || dexElementsSuppressedExceptions != null) { 257 throw new IllegalStateException("Should only be called once"); 258 } 259 260 final List<IOException> suppressedExceptions = new ArrayList<IOException>(); 261 262 try { 263 Element[] null_elements = null; 264 DexFile dex = new DexFile(dexFiles, definingContext, null_elements); 265 // Capture class loader context from *before* `dexElements` is set (see comment below). 266 String classLoaderContext = dex.isBackedByOatFile() 267 ? null : DexFile.getClassLoaderContext(definingContext, null_elements); 268 dexElements = new Element[] { new Element(dex) }; 269 // Spawn background thread to verify all classes and cache verification results. 270 // Must be called *after* `dexElements` has been initialized for ART to find 271 // its classes (the field is hardcoded in ART and dex files iterated over in 272 // the order of the array), but with class loader context from *before* 273 // `dexElements` was set because that is what it will be compared against next 274 // time the same bytecode is loaded. 275 // We only spawn the background thread if the bytecode is not backed by an oat 276 // file, i.e. this is the first time this bytecode is being loaded and/or 277 // verification results have not been cached yet. Skip spawning the thread on 278 // all subsequent loads of the same bytecode in the same class loader context. 279 if (classLoaderContext != null) { 280 dex.verifyInBackground(definingContext, classLoaderContext); 281 } 282 } catch (IOException suppressed) { 283 System.logE("Unable to load dex files", suppressed); 284 suppressedExceptions.add(suppressed); 285 dexElements = new Element[0]; 286 } 287 288 if (suppressedExceptions.size() > 0) { 289 dexElementsSuppressedExceptions = suppressedExceptions.toArray( 290 new IOException[suppressedExceptions.size()]); 291 } 292 } 293 294 /** 295 * Splits the given dex path string into elements using the path 296 * separator, pruning out any elements that do not refer to existing 297 * and readable files. 298 */ splitDexPath(String path)299 private static List<File> splitDexPath(String path) { 300 return splitPaths(path, false); 301 } 302 303 /** 304 * Splits the given path strings into file elements using the path 305 * separator, combining the results and filtering out elements 306 * that don't exist, aren't readable, or aren't either a regular 307 * file or a directory (as specified). Either string may be empty 308 * or {@code null}, in which case it is ignored. If both strings 309 * are empty or {@code null}, or all elements get pruned out, then 310 * this returns a zero-element list. 311 */ 312 @UnsupportedAppUsage splitPaths(String searchPath, boolean directoriesOnly)313 private static List<File> splitPaths(String searchPath, boolean directoriesOnly) { 314 List<File> result = new ArrayList<>(); 315 316 if (searchPath != null) { 317 for (String path : searchPath.split(File.pathSeparator)) { 318 if (directoriesOnly) { 319 try { 320 StructStat sb = Libcore.os.stat(path); 321 if (!S_ISDIR(sb.st_mode)) { 322 continue; 323 } 324 } catch (ErrnoException ignored) { 325 continue; 326 } 327 } 328 result.add(new File(path)); 329 } 330 } 331 332 return result; 333 } 334 335 // This method is not used anymore. Kept around only because there are many legacy users of it. 336 @SuppressWarnings("unused") 337 @UnsupportedAppUsage makeInMemoryDexElements(ByteBuffer[] dexFiles, List<IOException> suppressedExceptions)338 public static Element[] makeInMemoryDexElements(ByteBuffer[] dexFiles, 339 List<IOException> suppressedExceptions) { 340 Element[] elements = new Element[dexFiles.length]; 341 int elementPos = 0; 342 for (ByteBuffer buf : dexFiles) { 343 try { 344 DexFile dex = new DexFile(new ByteBuffer[] { buf }, /* classLoader */ null, 345 /* dexElements */ null); 346 elements[elementPos++] = new Element(dex); 347 } catch (IOException suppressed) { 348 System.logE("Unable to load dex file: " + buf, suppressed); 349 suppressedExceptions.add(suppressed); 350 } 351 } 352 if (elementPos != elements.length) { 353 elements = Arrays.copyOf(elements, elementPos); 354 } 355 return elements; 356 } 357 358 /** 359 * Makes an array of dex/resource path elements, one per element of 360 * the given array. 361 */ 362 @UnsupportedAppUsage makeDexElements(List<File> files, File optimizedDirectory, List<IOException> suppressedExceptions, ClassLoader loader)363 private static Element[] makeDexElements(List<File> files, File optimizedDirectory, 364 List<IOException> suppressedExceptions, ClassLoader loader) { 365 return makeDexElements(files, optimizedDirectory, suppressedExceptions, loader, false); 366 } 367 368 makeDexElements(List<File> files, File optimizedDirectory, List<IOException> suppressedExceptions, ClassLoader loader, boolean isTrusted)369 private static Element[] makeDexElements(List<File> files, File optimizedDirectory, 370 List<IOException> suppressedExceptions, ClassLoader loader, boolean isTrusted) { 371 Element[] elements = new Element[files.size()]; 372 int elementsPos = 0; 373 /* 374 * Open all files and load the (direct or contained) dex files up front. 375 */ 376 for (File file : files) { 377 if (file.isDirectory()) { 378 // We support directories for looking up resources. Looking up resources in 379 // directories is useful for running libcore tests. 380 elements[elementsPos++] = new Element(file); 381 } else if (file.isFile()) { 382 String name = file.getName(); 383 384 DexFile dex = null; 385 if (name.endsWith(DEX_SUFFIX)) { 386 // Raw dex file (not inside a zip/jar). 387 try { 388 dex = loadDexFile(file, optimizedDirectory, loader, elements); 389 if (dex != null) { 390 elements[elementsPos++] = new Element(dex, null); 391 } 392 } catch (IOException suppressed) { 393 System.logE("Unable to load dex file: " + file, suppressed); 394 suppressedExceptions.add(suppressed); 395 } 396 } else { 397 try { 398 dex = loadDexFile(file, optimizedDirectory, loader, elements); 399 } catch (IOException suppressed) { 400 /* 401 * IOException might get thrown "legitimately" by the DexFile constructor if 402 * the zip file turns out to be resource-only (that is, no classes.dex file 403 * in it). 404 * Let dex == null and hang on to the exception to add to the tea-leaves for 405 * when findClass returns null. 406 */ 407 suppressedExceptions.add(suppressed); 408 } 409 410 if (dex == null) { 411 elements[elementsPos++] = new Element(file); 412 } else { 413 elements[elementsPos++] = new Element(dex, file); 414 } 415 } 416 if (dex != null && isTrusted) { 417 dex.setTrusted(); 418 } 419 } else { 420 System.logW("ClassLoader referenced unknown path: " + file); 421 } 422 } 423 if (elementsPos != elements.length) { 424 elements = Arrays.copyOf(elements, elementsPos); 425 } 426 return elements; 427 } 428 429 /** 430 * Constructs a {@code DexFile} instance, as appropriate depending on whether 431 * {@code optimizedDirectory} is {@code null}. An application image file may be associated with 432 * the {@code loader} if it is not null. 433 */ 434 @UnsupportedAppUsage loadDexFile(File file, File optimizedDirectory, ClassLoader loader, Element[] elements)435 private static DexFile loadDexFile(File file, File optimizedDirectory, ClassLoader loader, 436 Element[] elements) 437 throws IOException { 438 if (optimizedDirectory == null) { 439 return new DexFile(file, loader, elements); 440 } else { 441 String optimizedPath = optimizedPathFor(file, optimizedDirectory); 442 return DexFile.loadDex(file.getPath(), optimizedPath, 0, loader, elements); 443 } 444 } 445 446 /** 447 * Converts a dex/jar file path and an output directory to an 448 * output file path for an associated optimized dex file. 449 */ optimizedPathFor(File path, File optimizedDirectory)450 private static String optimizedPathFor(File path, 451 File optimizedDirectory) { 452 /* 453 * Get the filename component of the path, and replace the 454 * suffix with ".dex" if that's not already the suffix. 455 * 456 * We don't want to use ".odex", because the build system uses 457 * that for files that are paired with resource-only jar 458 * files. If the VM can assume that there's no classes.dex in 459 * the matching jar, it doesn't need to open the jar to check 460 * for updated dependencies, providing a slight performance 461 * boost at startup. The use of ".dex" here matches the use on 462 * files in /data/dalvik-cache. 463 */ 464 String fileName = path.getName(); 465 if (!fileName.endsWith(DEX_SUFFIX)) { 466 int lastDot = fileName.lastIndexOf("."); 467 if (lastDot < 0) { 468 fileName += DEX_SUFFIX; 469 } else { 470 StringBuilder sb = new StringBuilder(lastDot + 4); 471 sb.append(fileName, 0, lastDot); 472 sb.append(DEX_SUFFIX); 473 fileName = sb.toString(); 474 } 475 } 476 477 File result = new File(optimizedDirectory, fileName); 478 return result.getPath(); 479 } 480 481 /* 482 * TODO (dimitry): Revert after apps stops relying on the existence of this 483 * method (see http://b/21957414 and http://b/26317852 for details) 484 */ 485 @UnsupportedAppUsage 486 @SuppressWarnings("unused") makePathElements(List<File> files, File optimizedDirectory, List<IOException> suppressedExceptions)487 private static Element[] makePathElements(List<File> files, File optimizedDirectory, 488 List<IOException> suppressedExceptions) { 489 return makeDexElements(files, optimizedDirectory, suppressedExceptions, null); 490 } 491 492 /** 493 * Makes an array of directory/zip path elements for the native library search path, one per 494 * element of the given array. 495 */ 496 @UnsupportedAppUsage makePathElements(List<File> files)497 private static NativeLibraryElement[] makePathElements(List<File> files) { 498 NativeLibraryElement[] elements = new NativeLibraryElement[files.size()]; 499 int elementsPos = 0; 500 for (File file : files) { 501 String path = file.getPath(); 502 503 if (path.contains(zipSeparator)) { 504 String split[] = path.split(zipSeparator, 2); 505 File zip = new File(split[0]); 506 String dir = split[1]; 507 elements[elementsPos++] = new NativeLibraryElement(zip, dir); 508 } else if (file.isDirectory()) { 509 // We support directories for looking up native libraries. 510 elements[elementsPos++] = new NativeLibraryElement(file); 511 } 512 } 513 if (elementsPos != elements.length) { 514 elements = Arrays.copyOf(elements, elementsPos); 515 } 516 return elements; 517 } 518 519 /** 520 * Finds the named class in one of the dex files pointed at by 521 * this instance. This will find the one in the earliest listed 522 * path element. If the class is found but has not yet been 523 * defined, then this method will define it in the defining 524 * context that this instance was constructed with. 525 * 526 * @param name of class to find 527 * @param suppressed exceptions encountered whilst finding the class 528 * @return the named class or {@code null} if the class is not 529 * found in any of the dex files 530 */ findClass(String name, List<Throwable> suppressed)531 public Class<?> findClass(String name, List<Throwable> suppressed) { 532 for (Element element : dexElements) { 533 Class<?> clazz = element.findClass(name, definingContext, suppressed); 534 if (clazz != null) { 535 return clazz; 536 } 537 } 538 539 if (dexElementsSuppressedExceptions != null) { 540 suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions)); 541 } 542 return null; 543 } 544 545 /** 546 * Finds the named resource in one of the zip/jar files pointed at 547 * by this instance. This will find the one in the earliest listed 548 * path element. 549 * 550 * @return a URL to the named resource or {@code null} if the 551 * resource is not found in any of the zip/jar files 552 */ findResource(String name)553 public URL findResource(String name) { 554 for (Element element : dexElements) { 555 URL url = element.findResource(name); 556 if (url != null) { 557 return url; 558 } 559 } 560 561 return null; 562 } 563 564 /** 565 * Finds all the resources with the given name, returning an 566 * enumeration of them. If there are no resources with the given 567 * name, then this method returns an empty enumeration. 568 */ findResources(String name)569 public Enumeration<URL> findResources(String name) { 570 ArrayList<URL> result = new ArrayList<URL>(); 571 572 for (Element element : dexElements) { 573 URL url = element.findResource(name); 574 if (url != null) { 575 result.add(url); 576 } 577 } 578 579 return Collections.enumeration(result); 580 } 581 582 /** 583 * Finds the named native code library on any of the library 584 * directories pointed at by this instance. This will find the 585 * one in the earliest listed directory, ignoring any that are not 586 * readable regular files. 587 * 588 * @return the complete path to the library or {@code null} if no 589 * library was found 590 */ findLibrary(String libraryName)591 public String findLibrary(String libraryName) { 592 String fileName = System.mapLibraryName(libraryName); 593 594 for (NativeLibraryElement element : nativeLibraryPathElements) { 595 String path = element.findNativeLibrary(fileName); 596 597 if (path != null) { 598 return path; 599 } 600 } 601 602 return null; 603 } 604 605 /** 606 * Returns the list of all individual dex files paths from the current list. 607 * The list will contain only file paths (i.e. no directories). 608 */ getDexPaths()609 /*package*/ List<String> getDexPaths() { 610 List<String> dexPaths = new ArrayList<String>(); 611 for (Element e : dexElements) { 612 String dexPath = e.getDexPath(); 613 if (dexPath != null) { 614 // Add the element to the list only if it is a file. A null dex path signals the 615 // element is a resource directory or an in-memory dex file. 616 dexPaths.add(dexPath); 617 } 618 } 619 return dexPaths; 620 } 621 622 /** 623 * Adds a collection of library paths from which to load native libraries. Paths can be absolute 624 * native library directories (i.e. /data/app/foo/lib/arm64) or apk references (i.e. 625 * /data/app/foo/base.apk!/lib/arm64). 626 * 627 * Note: This method will attempt to dedupe elements. 628 * Note: This method replaces the value of {@link #nativeLibraryPathElements} 629 */ 630 @UnsupportedAppUsage addNativePath(Collection<String> libPaths)631 public void addNativePath(Collection<String> libPaths) { 632 if (libPaths.isEmpty()) { 633 return; 634 } 635 List<File> libFiles = new ArrayList<>(libPaths.size()); 636 for (String path : libPaths) { 637 libFiles.add(new File(path)); 638 } 639 ArrayList<NativeLibraryElement> newPaths = 640 new ArrayList<>(nativeLibraryPathElements.length + libPaths.size()); 641 newPaths.addAll(Arrays.asList(nativeLibraryPathElements)); 642 for (NativeLibraryElement element : makePathElements(libFiles)) { 643 if (!newPaths.contains(element)) { 644 newPaths.add(element); 645 } 646 } 647 nativeLibraryPathElements = newPaths.toArray(new NativeLibraryElement[newPaths.size()]); 648 } 649 650 /** 651 * Element of the dex/resource path. Note: should be called DexElement, but apps reflect on 652 * this. 653 */ 654 /*package*/ static class Element { 655 /** 656 * A file denoting a zip file (in case of a resource jar or a dex jar), or a directory 657 * (only when dexFile is null). 658 */ 659 @UnsupportedAppUsage 660 private final File path; 661 /** Whether {@code path.isDirectory()}, or {@code null} if {@code path == null}. */ 662 private final Boolean pathIsDirectory; 663 664 @UnsupportedAppUsage 665 private final DexFile dexFile; 666 667 private ClassPathURLStreamHandler urlHandler; 668 private boolean initialized; 669 670 /** 671 * Element encapsulates a dex file. This may be a plain dex file (in which case dexZipPath 672 * should be null), or a jar (in which case dexZipPath should denote the zip file). 673 */ 674 @UnsupportedAppUsage Element(DexFile dexFile, File dexZipPath)675 public Element(DexFile dexFile, File dexZipPath) { 676 if (dexFile == null && dexZipPath == null) { 677 throw new NullPointerException("Either dexFile or path must be non-null"); 678 } 679 this.dexFile = dexFile; 680 this.path = dexZipPath; 681 // Do any I/O in the constructor so we don't have to do it elsewhere, eg. toString(). 682 this.pathIsDirectory = (path == null) ? null : path.isDirectory(); 683 } 684 Element(DexFile dexFile)685 public Element(DexFile dexFile) { 686 this(dexFile, null); 687 } 688 Element(File path)689 public Element(File path) { 690 this(null, path); 691 } 692 693 /** 694 * Constructor for a bit of backwards compatibility. Some apps use reflection into 695 * internal APIs. Warn, and emulate old behavior if we can. See b/33399341. 696 * 697 * @deprecated The Element class has been split. Use new Element constructors for 698 * classes and resources, and NativeLibraryElement for the library 699 * search path. 700 */ 701 @UnsupportedAppUsage 702 @Deprecated Element(File dir, boolean isDirectory, File zip, DexFile dexFile)703 public Element(File dir, boolean isDirectory, File zip, DexFile dexFile) { 704 this(dir != null ? null : dexFile, dir != null ? dir : zip); 705 System.err.println("Warning: Using deprecated Element constructor. Do not use internal" 706 + " APIs, this constructor will be removed in the future."); 707 if (dir != null && (zip != null || dexFile != null)) { 708 throw new IllegalArgumentException("Using dir and zip|dexFile no longer" 709 + " supported."); 710 } 711 if (isDirectory && (zip != null || dexFile != null)) { 712 throw new IllegalArgumentException("Unsupported argument combination."); 713 } 714 } 715 716 /* 717 * Returns the dex path of this element or null if the element refers to a directory. 718 */ getDexPath()719 private String getDexPath() { 720 if (path != null) { 721 return path.isDirectory() ? null : path.getAbsolutePath(); 722 } else if (dexFile != null) { 723 // DexFile.getName() returns the path of the dex file. 724 return dexFile.getName(); 725 } 726 return null; 727 } 728 729 @Override toString()730 public String toString() { 731 if (dexFile == null) { 732 return (pathIsDirectory ? "directory \"" : "zip file \"") + path + "\""; 733 } else if (path == null) { 734 return "dex file \"" + dexFile + "\""; 735 } else { 736 return "zip file \"" + path + "\""; 737 } 738 } 739 maybeInit()740 public synchronized void maybeInit() { 741 if (initialized) { 742 return; 743 } 744 745 if (path == null || pathIsDirectory) { 746 initialized = true; 747 return; 748 } 749 750 try { 751 urlHandler = new ClassPathURLStreamHandler(path.getPath()); 752 } catch (IOException ioe) { 753 /* 754 * Note: ZipException (a subclass of IOException) 755 * might get thrown by the ZipFile constructor 756 * (e.g. if the file isn't actually a zip/jar 757 * file). 758 */ 759 System.logE("Unable to open zip file: " + path, ioe); 760 urlHandler = null; 761 } 762 763 // Mark this element as initialized only after we've successfully created 764 // the associated ClassPathURLStreamHandler. That way, we won't leave this 765 // element in an inconsistent state if an exception is thrown during initialization. 766 // 767 // See b/35633614. 768 initialized = true; 769 } 770 findClass(String name, ClassLoader definingContext, List<Throwable> suppressed)771 public Class<?> findClass(String name, ClassLoader definingContext, 772 List<Throwable> suppressed) { 773 return dexFile != null ? dexFile.loadClassBinaryName(name, definingContext, suppressed) 774 : null; 775 } 776 findResource(String name)777 public URL findResource(String name) { 778 maybeInit(); 779 780 if (urlHandler != null) { 781 return urlHandler.getEntryUrlOrNull(name); 782 } 783 784 // We support directories so we can run tests and/or legacy code 785 // that uses Class.getResource. 786 if (path != null && path.isDirectory()) { 787 File resourceFile = new File(path, name); 788 if (resourceFile.exists()) { 789 try { 790 return resourceFile.toURI().toURL(); 791 } catch (MalformedURLException ex) { 792 throw new RuntimeException(ex); 793 } 794 } 795 } 796 797 return null; 798 } 799 } 800 801 /** 802 * Element of the native library path 803 */ 804 /*package*/ static class NativeLibraryElement { 805 /** 806 * A file denoting a directory or zip file. 807 */ 808 @UnsupportedAppUsage 809 private final File path; 810 811 /** 812 * If path denotes a zip file, this denotes a base path inside the zip. 813 */ 814 private final String zipDir; 815 816 private ClassPathURLStreamHandler urlHandler; 817 private boolean initialized; 818 819 @UnsupportedAppUsage NativeLibraryElement(File dir)820 public NativeLibraryElement(File dir) { 821 this.path = dir; 822 this.zipDir = null; 823 824 // We should check whether path is a directory, but that is non-eliminatable overhead. 825 } 826 NativeLibraryElement(File zip, String zipDir)827 public NativeLibraryElement(File zip, String zipDir) { 828 this.path = zip; 829 this.zipDir = zipDir; 830 831 // Simple check that should be able to be eliminated by inlining. We should also 832 // check whether path is a file, but that is non-eliminatable overhead. 833 if (zipDir == null) { 834 throw new IllegalArgumentException(); 835 } 836 } 837 838 @Override toString()839 public String toString() { 840 if (zipDir == null) { 841 return "directory \"" + path + "\""; 842 } else { 843 return "zip file \"" + path + "\"" + 844 (!zipDir.isEmpty() ? ", dir \"" + zipDir + "\"" : ""); 845 } 846 } 847 maybeInit()848 public synchronized void maybeInit() { 849 if (initialized) { 850 return; 851 } 852 853 if (zipDir == null) { 854 initialized = true; 855 return; 856 } 857 858 try { 859 urlHandler = new ClassPathURLStreamHandler(path.getPath()); 860 } catch (IOException ioe) { 861 /* 862 * Note: ZipException (a subclass of IOException) 863 * might get thrown by the ZipFile constructor 864 * (e.g. if the file isn't actually a zip/jar 865 * file). 866 */ 867 System.logE("Unable to open zip file: " + path, ioe); 868 urlHandler = null; 869 } 870 871 // Mark this element as initialized only after we've successfully created 872 // the associated ClassPathURLStreamHandler. That way, we won't leave this 873 // element in an inconsistent state if an exception is thrown during initialization. 874 // 875 // See b/35633614. 876 initialized = true; 877 } 878 findNativeLibrary(String name)879 public String findNativeLibrary(String name) { 880 maybeInit(); 881 882 if (zipDir == null) { 883 String entryPath = new File(path, name).getPath(); 884 if (IoUtils.canOpenReadOnly(entryPath)) { 885 return entryPath; 886 } 887 } else if (urlHandler != null) { 888 // Having a urlHandler means the element has a zip file. 889 // In this case Android supports loading the library iff 890 // it is stored in the zip uncompressed. 891 String entryName = zipDir + '/' + name; 892 if (urlHandler.isEntryStored(entryName)) { 893 return path.getPath() + zipSeparator + entryName; 894 } 895 } 896 897 return null; 898 } 899 900 @Override equals(Object o)901 public boolean equals(Object o) { 902 if (this == o) return true; 903 if (!(o instanceof NativeLibraryElement)) return false; 904 NativeLibraryElement that = (NativeLibraryElement) o; 905 return Objects.equals(path, that.path) && 906 Objects.equals(zipDir, that.zipDir); 907 } 908 909 @Override hashCode()910 public int hashCode() { 911 return Objects.hash(path, zipDir); 912 } 913 } 914 } 915