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