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