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