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