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 dexElements = new Element[] { new Element(dex) }; 266 } catch (IOException suppressed) { 267 System.logE("Unable to load dex files", suppressed); 268 suppressedExceptions.add(suppressed); 269 dexElements = new Element[0]; 270 } 271 272 if (suppressedExceptions.size() > 0) { 273 dexElementsSuppressedExceptions = suppressedExceptions.toArray( 274 new IOException[suppressedExceptions.size()]); 275 } 276 } 277 maybeRunBackgroundVerification(ClassLoader loader)278 /* package */ void maybeRunBackgroundVerification(ClassLoader loader) { 279 // Spawn background thread to verify all classes and cache verification results. 280 // Must be called *after* `this.dexElements` has been initialized and `loader.pathList` 281 // has been set for ART to find its classes (the fields are hardcoded in ART and dex 282 // files iterated over in the order of the array). 283 // We only spawn the background thread if the bytecode is not backed by an oat 284 // file, i.e. this is the first time this bytecode is being loaded and/or 285 // verification results have not been cached yet. 286 for (Element element : dexElements) { 287 if (element.dexFile != null && !element.dexFile.isBackedByOatFile()) { 288 element.dexFile.verifyInBackground(loader); 289 } 290 } 291 } 292 293 /** 294 * Splits the given dex path string into elements using the path 295 * separator, pruning out any elements that do not refer to existing 296 * and readable files. 297 */ splitDexPath(String path)298 private static List<File> splitDexPath(String path) { 299 return splitPaths(path, false); 300 } 301 302 /** 303 * Splits the given path strings into file elements using the path 304 * separator, combining the results and filtering out elements 305 * that don't exist, aren't readable, or aren't either a regular 306 * file or a directory (as specified). Either string may be empty 307 * or {@code null}, in which case it is ignored. If both strings 308 * are empty or {@code null}, or all elements get pruned out, then 309 * this returns a zero-element list. 310 */ 311 @UnsupportedAppUsage splitPaths(String searchPath, boolean directoriesOnly)312 private static List<File> splitPaths(String searchPath, boolean directoriesOnly) { 313 List<File> result = new ArrayList<>(); 314 315 if (searchPath != null) { 316 for (String path : searchPath.split(File.pathSeparator)) { 317 if (directoriesOnly) { 318 try { 319 StructStat sb = Libcore.os.stat(path); 320 if (!S_ISDIR(sb.st_mode)) { 321 continue; 322 } 323 } catch (ErrnoException ignored) { 324 continue; 325 } 326 } 327 result.add(new File(path)); 328 } 329 } 330 331 return result; 332 } 333 334 // This method is not used anymore. Kept around only because there are many legacy users of it. 335 @SuppressWarnings("unused") 336 @UnsupportedAppUsage makeInMemoryDexElements(ByteBuffer[] dexFiles, List<IOException> suppressedExceptions)337 public static Element[] makeInMemoryDexElements(ByteBuffer[] dexFiles, 338 List<IOException> suppressedExceptions) { 339 Element[] elements = new Element[dexFiles.length]; 340 int elementPos = 0; 341 for (ByteBuffer buf : dexFiles) { 342 try { 343 DexFile dex = new DexFile(new ByteBuffer[] { buf }, /* classLoader */ null, 344 /* dexElements */ null); 345 elements[elementPos++] = new Element(dex); 346 } catch (IOException suppressed) { 347 System.logE("Unable to load dex file: " + buf, suppressed); 348 suppressedExceptions.add(suppressed); 349 } 350 } 351 if (elementPos != elements.length) { 352 elements = Arrays.copyOf(elements, elementPos); 353 } 354 return elements; 355 } 356 357 /** 358 * Makes an array of dex/resource path elements, one per element of 359 * the given array. 360 */ 361 @UnsupportedAppUsage makeDexElements(List<File> files, File optimizedDirectory, List<IOException> suppressedExceptions, ClassLoader loader)362 private static Element[] makeDexElements(List<File> files, File optimizedDirectory, 363 List<IOException> suppressedExceptions, ClassLoader loader) { 364 return makeDexElements(files, optimizedDirectory, suppressedExceptions, loader, false); 365 } 366 367 makeDexElements(List<File> files, File optimizedDirectory, List<IOException> suppressedExceptions, ClassLoader loader, boolean isTrusted)368 private static Element[] makeDexElements(List<File> files, File optimizedDirectory, 369 List<IOException> suppressedExceptions, ClassLoader loader, boolean isTrusted) { 370 Element[] elements = new Element[files.size()]; 371 int elementsPos = 0; 372 /* 373 * Open all files and load the (direct or contained) dex files up front. 374 */ 375 for (File file : files) { 376 if (file.isDirectory()) { 377 // We support directories for looking up resources. Looking up resources in 378 // directories is useful for running libcore tests. 379 elements[elementsPos++] = new Element(file); 380 } else if (file.isFile()) { 381 String name = file.getName(); 382 383 DexFile dex = null; 384 if (name.endsWith(DEX_SUFFIX)) { 385 // Raw dex file (not inside a zip/jar). 386 try { 387 dex = loadDexFile(file, optimizedDirectory, loader, elements); 388 if (dex != null) { 389 elements[elementsPos++] = new Element(dex, null); 390 } 391 } catch (IOException suppressed) { 392 System.logE("Unable to load dex file: " + file, suppressed); 393 suppressedExceptions.add(suppressed); 394 } 395 } else { 396 try { 397 dex = loadDexFile(file, optimizedDirectory, loader, elements); 398 } catch (IOException suppressed) { 399 /* 400 * IOException might get thrown "legitimately" by the DexFile constructor if 401 * the zip file turns out to be resource-only (that is, no classes.dex file 402 * in it). 403 * Let dex == null and hang on to the exception to add to the tea-leaves for 404 * when findClass returns null. 405 */ 406 suppressedExceptions.add(suppressed); 407 } 408 409 if (dex == null) { 410 elements[elementsPos++] = new Element(file); 411 } else { 412 elements[elementsPos++] = new Element(dex, file); 413 } 414 } 415 if (dex != null && isTrusted) { 416 dex.setTrusted(); 417 } 418 } else { 419 System.logW("ClassLoader referenced unknown path: " + file); 420 } 421 } 422 if (elementsPos != elements.length) { 423 elements = Arrays.copyOf(elements, elementsPos); 424 } 425 return elements; 426 } 427 428 /** 429 * Constructs a {@code DexFile} instance, as appropriate depending on whether 430 * {@code optimizedDirectory} is {@code null}. An application image file may be associated with 431 * the {@code loader} if it is not null. 432 */ 433 @UnsupportedAppUsage loadDexFile(File file, File optimizedDirectory, ClassLoader loader, Element[] elements)434 private static DexFile loadDexFile(File file, File optimizedDirectory, ClassLoader loader, 435 Element[] elements) 436 throws IOException { 437 if (optimizedDirectory == null) { 438 return new DexFile(file, loader, elements); 439 } else { 440 String optimizedPath = optimizedPathFor(file, optimizedDirectory); 441 return DexFile.loadDex(file.getPath(), optimizedPath, 0, loader, elements); 442 } 443 } 444 445 /** 446 * Converts a dex/jar file path and an output directory to an 447 * output file path for an associated optimized dex file. 448 */ optimizedPathFor(File path, File optimizedDirectory)449 private static String optimizedPathFor(File path, 450 File optimizedDirectory) { 451 /* 452 * Get the filename component of the path, and replace the 453 * suffix with ".dex" if that's not already the suffix. 454 * 455 * We don't want to use ".odex", because the build system uses 456 * that for files that are paired with resource-only jar 457 * files. If the VM can assume that there's no classes.dex in 458 * the matching jar, it doesn't need to open the jar to check 459 * for updated dependencies, providing a slight performance 460 * boost at startup. The use of ".dex" here matches the use on 461 * files in /data/dalvik-cache. 462 */ 463 String fileName = path.getName(); 464 if (!fileName.endsWith(DEX_SUFFIX)) { 465 int lastDot = fileName.lastIndexOf("."); 466 if (lastDot < 0) { 467 fileName += DEX_SUFFIX; 468 } else { 469 StringBuilder sb = new StringBuilder(lastDot + 4); 470 sb.append(fileName, 0, lastDot); 471 sb.append(DEX_SUFFIX); 472 fileName = sb.toString(); 473 } 474 } 475 476 File result = new File(optimizedDirectory, fileName); 477 return result.getPath(); 478 } 479 480 /* 481 * TODO (dimitry): Revert after apps stops relying on the existence of this 482 * method (see http://b/21957414 and http://b/26317852 for details) 483 */ 484 @UnsupportedAppUsage 485 @SuppressWarnings("unused") makePathElements(List<File> files, File optimizedDirectory, List<IOException> suppressedExceptions)486 private static Element[] makePathElements(List<File> files, File optimizedDirectory, 487 List<IOException> suppressedExceptions) { 488 return makeDexElements(files, optimizedDirectory, suppressedExceptions, null); 489 } 490 491 /** 492 * Makes an array of directory/zip path elements for the native library search path, one per 493 * element of the given array. 494 */ 495 @UnsupportedAppUsage makePathElements(List<File> files)496 private static NativeLibraryElement[] makePathElements(List<File> files) { 497 NativeLibraryElement[] elements = new NativeLibraryElement[files.size()]; 498 int elementsPos = 0; 499 for (File file : files) { 500 String path = file.getPath(); 501 502 if (path.contains(zipSeparator)) { 503 String split[] = path.split(zipSeparator, 2); 504 File zip = new File(split[0]); 505 String dir = split[1]; 506 elements[elementsPos++] = new NativeLibraryElement(zip, dir); 507 } else if (file.isDirectory()) { 508 // We support directories for looking up native libraries. 509 elements[elementsPos++] = new NativeLibraryElement(file); 510 } 511 } 512 if (elementsPos != elements.length) { 513 elements = Arrays.copyOf(elements, elementsPos); 514 } 515 return elements; 516 } 517 518 /** 519 * Finds the named class in one of the dex files pointed at by 520 * this instance. This will find the one in the earliest listed 521 * path element. If the class is found but has not yet been 522 * defined, then this method will define it in the defining 523 * context that this instance was constructed with. 524 * 525 * @param name of class to find 526 * @param suppressed exceptions encountered whilst finding the class 527 * @return the named class or {@code null} if the class is not 528 * found in any of the dex files 529 */ findClass(String name, List<Throwable> suppressed)530 public Class<?> findClass(String name, List<Throwable> suppressed) { 531 for (Element element : dexElements) { 532 Class<?> clazz = element.findClass(name, definingContext, suppressed); 533 if (clazz != null) { 534 return clazz; 535 } 536 } 537 538 if (dexElementsSuppressedExceptions != null) { 539 suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions)); 540 } 541 return null; 542 } 543 544 /** 545 * Finds the named resource in one of the zip/jar files pointed at 546 * by this instance. This will find the one in the earliest listed 547 * path element. 548 * 549 * @return a URL to the named resource or {@code null} if the 550 * resource is not found in any of the zip/jar files 551 */ findResource(String name)552 public URL findResource(String name) { 553 for (Element element : dexElements) { 554 URL url = element.findResource(name); 555 if (url != null) { 556 return url; 557 } 558 } 559 560 return null; 561 } 562 563 /** 564 * Finds all the resources with the given name, returning an 565 * enumeration of them. If there are no resources with the given 566 * name, then this method returns an empty enumeration. 567 */ findResources(String name)568 public Enumeration<URL> findResources(String name) { 569 ArrayList<URL> result = new ArrayList<URL>(); 570 571 for (Element element : dexElements) { 572 URL url = element.findResource(name); 573 if (url != null) { 574 result.add(url); 575 } 576 } 577 578 return Collections.enumeration(result); 579 } 580 581 /** 582 * Finds the named native code library on any of the library 583 * directories pointed at by this instance. This will find the 584 * one in the earliest listed directory, ignoring any that are not 585 * readable regular files. 586 * 587 * @return the complete path to the library or {@code null} if no 588 * library was found 589 */ findLibrary(String libraryName)590 public String findLibrary(String libraryName) { 591 String fileName = System.mapLibraryName(libraryName); 592 593 for (NativeLibraryElement element : nativeLibraryPathElements) { 594 String path = element.findNativeLibrary(fileName); 595 596 if (path != null) { 597 return path; 598 } 599 } 600 601 return null; 602 } 603 604 /** 605 * Returns the list of all individual dex files paths from the current list. 606 * The list will contain only file paths (i.e. no directories). 607 */ getDexPaths()608 /*package*/ List<String> getDexPaths() { 609 List<String> dexPaths = new ArrayList<String>(); 610 for (Element e : dexElements) { 611 String dexPath = e.getDexPath(); 612 if (dexPath != null) { 613 // Add the element to the list only if it is a file. A null dex path signals the 614 // element is a resource directory or an in-memory dex file. 615 dexPaths.add(dexPath); 616 } 617 } 618 return dexPaths; 619 } 620 621 /** 622 * Adds a collection of library paths from which to load native libraries. Paths can be absolute 623 * native library directories (i.e. /data/app/foo/lib/arm64) or apk references (i.e. 624 * /data/app/foo/base.apk!/lib/arm64). 625 * 626 * Note: This method will attempt to dedupe elements. 627 * Note: This method replaces the value of {@link #nativeLibraryPathElements} 628 */ 629 @UnsupportedAppUsage addNativePath(Collection<String> libPaths)630 public void addNativePath(Collection<String> libPaths) { 631 if (libPaths.isEmpty()) { 632 return; 633 } 634 List<File> libFiles = new ArrayList<>(libPaths.size()); 635 for (String path : libPaths) { 636 libFiles.add(new File(path)); 637 } 638 ArrayList<NativeLibraryElement> newPaths = 639 new ArrayList<>(nativeLibraryPathElements.length + libPaths.size()); 640 newPaths.addAll(Arrays.asList(nativeLibraryPathElements)); 641 for (NativeLibraryElement element : makePathElements(libFiles)) { 642 if (!newPaths.contains(element)) { 643 newPaths.add(element); 644 } 645 } 646 nativeLibraryPathElements = newPaths.toArray(new NativeLibraryElement[newPaths.size()]); 647 } 648 649 /** 650 * Element of the dex/resource path. Note: should be called DexElement, but apps reflect on 651 * this. 652 */ 653 /*package*/ static class Element { 654 /** 655 * A file denoting a zip file (in case of a resource jar or a dex jar), or a directory 656 * (only when dexFile is null). 657 */ 658 @UnsupportedAppUsage 659 private final File path; 660 /** Whether {@code path.isDirectory()}, or {@code null} if {@code path == null}. */ 661 private final Boolean pathIsDirectory; 662 663 @UnsupportedAppUsage 664 private final DexFile dexFile; 665 666 private ClassPathURLStreamHandler urlHandler; 667 private boolean initialized; 668 669 /** 670 * Element encapsulates a dex file. This may be a plain dex file (in which case dexZipPath 671 * should be null), or a jar (in which case dexZipPath should denote the zip file). 672 */ 673 @UnsupportedAppUsage Element(DexFile dexFile, File dexZipPath)674 public Element(DexFile dexFile, File dexZipPath) { 675 if (dexFile == null && dexZipPath == null) { 676 throw new NullPointerException("Either dexFile or path must be non-null"); 677 } 678 this.dexFile = dexFile; 679 this.path = dexZipPath; 680 // Do any I/O in the constructor so we don't have to do it elsewhere, eg. toString(). 681 this.pathIsDirectory = (path == null) ? null : path.isDirectory(); 682 } 683 Element(DexFile dexFile)684 public Element(DexFile dexFile) { 685 this(dexFile, null); 686 } 687 Element(File path)688 public Element(File path) { 689 this(null, path); 690 } 691 692 /** 693 * Constructor for a bit of backwards compatibility. Some apps use reflection into 694 * internal APIs. Warn, and emulate old behavior if we can. See b/33399341. 695 * 696 * @deprecated The Element class has been split. Use new Element constructors for 697 * classes and resources, and NativeLibraryElement for the library 698 * search path. 699 */ 700 @UnsupportedAppUsage 701 @Deprecated Element(File dir, boolean isDirectory, File zip, DexFile dexFile)702 public Element(File dir, boolean isDirectory, File zip, DexFile dexFile) { 703 this(dir != null ? null : dexFile, dir != null ? dir : zip); 704 System.err.println("Warning: Using deprecated Element constructor. Do not use internal" 705 + " APIs, this constructor will be removed in the future."); 706 if (dir != null && (zip != null || dexFile != null)) { 707 throw new IllegalArgumentException("Using dir and zip|dexFile no longer" 708 + " supported."); 709 } 710 if (isDirectory && (zip != null || dexFile != null)) { 711 throw new IllegalArgumentException("Unsupported argument combination."); 712 } 713 } 714 715 /* 716 * Returns the dex path of this element or null if the element refers to a directory. 717 */ getDexPath()718 private String getDexPath() { 719 if (path != null) { 720 return path.isDirectory() ? null : path.getAbsolutePath(); 721 } else if (dexFile != null) { 722 // DexFile.getName() returns the path of the dex file. 723 return dexFile.getName(); 724 } 725 return null; 726 } 727 728 @Override toString()729 public String toString() { 730 if (dexFile == null) { 731 return (pathIsDirectory ? "directory \"" : "zip file \"") + path + "\""; 732 } else if (path == null) { 733 return "dex file \"" + dexFile + "\""; 734 } else { 735 return "zip file \"" + path + "\""; 736 } 737 } 738 maybeInit()739 public synchronized void maybeInit() { 740 if (initialized) { 741 return; 742 } 743 744 if (path == null || pathIsDirectory) { 745 initialized = true; 746 return; 747 } 748 749 try { 750 urlHandler = new ClassPathURLStreamHandler(path.getPath()); 751 } catch (IOException ioe) { 752 /* 753 * Note: ZipException (a subclass of IOException) 754 * might get thrown by the ZipFile constructor 755 * (e.g. if the file isn't actually a zip/jar 756 * file). 757 */ 758 System.logE("Unable to open zip file: " + path, ioe); 759 urlHandler = null; 760 } 761 762 // Mark this element as initialized only after we've successfully created 763 // the associated ClassPathURLStreamHandler. That way, we won't leave this 764 // element in an inconsistent state if an exception is thrown during initialization. 765 // 766 // See b/35633614. 767 initialized = true; 768 } 769 findClass(String name, ClassLoader definingContext, List<Throwable> suppressed)770 public Class<?> findClass(String name, ClassLoader definingContext, 771 List<Throwable> suppressed) { 772 return dexFile != null ? dexFile.loadClassBinaryName(name, definingContext, suppressed) 773 : null; 774 } 775 findResource(String name)776 public URL findResource(String name) { 777 maybeInit(); 778 779 if (urlHandler != null) { 780 return urlHandler.getEntryUrlOrNull(name); 781 } 782 783 // We support directories so we can run tests and/or legacy code 784 // that uses Class.getResource. 785 if (path != null && path.isDirectory()) { 786 File resourceFile = new File(path, name); 787 if (resourceFile.exists()) { 788 try { 789 return resourceFile.toURI().toURL(); 790 } catch (MalformedURLException ex) { 791 throw new RuntimeException(ex); 792 } 793 } 794 } 795 796 return null; 797 } 798 } 799 800 /** 801 * Element of the native library path 802 */ 803 /*package*/ static class NativeLibraryElement { 804 /** 805 * A file denoting a directory or zip file. 806 */ 807 @UnsupportedAppUsage 808 private final File path; 809 810 /** 811 * If path denotes a zip file, this denotes a base path inside the zip. 812 */ 813 private final String zipDir; 814 815 private ClassPathURLStreamHandler urlHandler; 816 private boolean initialized; 817 818 @UnsupportedAppUsage NativeLibraryElement(File dir)819 public NativeLibraryElement(File dir) { 820 this.path = dir; 821 this.zipDir = null; 822 823 // We should check whether path is a directory, but that is non-eliminatable overhead. 824 } 825 NativeLibraryElement(File zip, String zipDir)826 public NativeLibraryElement(File zip, String zipDir) { 827 this.path = zip; 828 this.zipDir = zipDir; 829 830 // Simple check that should be able to be eliminated by inlining. We should also 831 // check whether path is a file, but that is non-eliminatable overhead. 832 if (zipDir == null) { 833 throw new IllegalArgumentException(); 834 } 835 } 836 837 @Override toString()838 public String toString() { 839 if (zipDir == null) { 840 return "directory \"" + path + "\""; 841 } else { 842 return "zip file \"" + path + "\"" + 843 (!zipDir.isEmpty() ? ", dir \"" + zipDir + "\"" : ""); 844 } 845 } 846 maybeInit()847 public synchronized void maybeInit() { 848 if (initialized) { 849 return; 850 } 851 852 if (zipDir == null) { 853 initialized = true; 854 return; 855 } 856 857 try { 858 urlHandler = new ClassPathURLStreamHandler(path.getPath()); 859 } catch (IOException ioe) { 860 /* 861 * Note: ZipException (a subclass of IOException) 862 * might get thrown by the ZipFile constructor 863 * (e.g. if the file isn't actually a zip/jar 864 * file). 865 */ 866 System.logE("Unable to open zip file: " + path, ioe); 867 urlHandler = null; 868 } 869 870 // Mark this element as initialized only after we've successfully created 871 // the associated ClassPathURLStreamHandler. That way, we won't leave this 872 // element in an inconsistent state if an exception is thrown during initialization. 873 // 874 // See b/35633614. 875 initialized = true; 876 } 877 findNativeLibrary(String name)878 public String findNativeLibrary(String name) { 879 maybeInit(); 880 881 if (zipDir == null) { 882 String entryPath = new File(path, name).getPath(); 883 if (IoUtils.canOpenReadOnly(entryPath)) { 884 return entryPath; 885 } 886 } else if (urlHandler != null) { 887 // Having a urlHandler means the element has a zip file. 888 // In this case Android supports loading the library iff 889 // it is stored in the zip uncompressed. 890 String entryName = zipDir + '/' + name; 891 if (urlHandler.isEntryStored(entryName)) { 892 return path.getPath() + zipSeparator + entryName; 893 } 894 } 895 896 return null; 897 } 898 899 @Override equals(Object o)900 public boolean equals(Object o) { 901 if (this == o) return true; 902 if (!(o instanceof NativeLibraryElement)) return false; 903 NativeLibraryElement that = (NativeLibraryElement) o; 904 return Objects.equals(path, that.path) && 905 Objects.equals(zipDir, that.zipDir); 906 } 907 908 @Override hashCode()909 public int hashCode() { 910 return Objects.hash(path, zipDir); 911 } 912 } 913 } 914