920 lines
35 KiB
Java
920 lines
35 KiB
Java
/*
|
|
* Copyright (C) 2011 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
package dalvik.system;
|
|
|
|
import android.compat.annotation.UnsupportedAppUsage;
|
|
import android.system.ErrnoException;
|
|
import android.system.StructStat;
|
|
|
|
import java.io.File;
|
|
import java.io.IOException;
|
|
import java.lang.reflect.Array;
|
|
import java.net.MalformedURLException;
|
|
import java.net.URL;
|
|
import java.nio.ByteBuffer;
|
|
import java.util.ArrayList;
|
|
import java.util.Arrays;
|
|
import java.util.Collection;
|
|
import java.util.Collections;
|
|
import java.util.Enumeration;
|
|
import java.util.List;
|
|
import java.util.Objects;
|
|
import libcore.io.ClassPathURLStreamHandler;
|
|
import libcore.io.IoUtils;
|
|
import libcore.io.Libcore;
|
|
|
|
import static android.system.OsConstants.S_ISDIR;
|
|
|
|
/**
|
|
* A pair of lists of entries, associated with a {@code ClassLoader}.
|
|
* One of the lists is a dex/resource path — typically referred
|
|
* to as a "class path" — list, and the other names directories
|
|
* containing native code libraries. Class path entries may be any of:
|
|
* a {@code .jar} or {@code .zip} file containing an optional
|
|
* top-level {@code classes.dex} file as well as arbitrary resources,
|
|
* or a plain {@code .dex} file (with no possibility of associated
|
|
* resources).
|
|
*
|
|
* <p>This class also contains methods to use these lists to look up
|
|
* classes and resources.</p>
|
|
*
|
|
* @hide
|
|
*/
|
|
public final class DexPathList {
|
|
private static final String DEX_SUFFIX = ".dex";
|
|
private static final String zipSeparator = "!/";
|
|
|
|
/** class definition context */
|
|
@UnsupportedAppUsage
|
|
private final ClassLoader definingContext;
|
|
|
|
/**
|
|
* List of dex/resource (class path) elements.
|
|
* Should be called pathElements, but the Facebook app uses reflection
|
|
* to modify 'dexElements' (http://b/7726934).
|
|
*/
|
|
@UnsupportedAppUsage
|
|
private Element[] dexElements;
|
|
|
|
/** List of native library path elements. */
|
|
// Some applications rely on this field being an array or we'd use a final list here
|
|
@UnsupportedAppUsage
|
|
/* package visible for testing */ NativeLibraryElement[] nativeLibraryPathElements;
|
|
|
|
/** List of application native library directories. */
|
|
@UnsupportedAppUsage
|
|
private final List<File> nativeLibraryDirectories;
|
|
|
|
/** List of system native library directories. */
|
|
@UnsupportedAppUsage
|
|
private final List<File> systemNativeLibraryDirectories;
|
|
|
|
/**
|
|
* Exceptions thrown during creation of the dexElements list.
|
|
*/
|
|
@UnsupportedAppUsage
|
|
private IOException[] dexElementsSuppressedExceptions;
|
|
|
|
private List<File> getAllNativeLibraryDirectories() {
|
|
List<File> allNativeLibraryDirectories = new ArrayList<>(nativeLibraryDirectories);
|
|
allNativeLibraryDirectories.addAll(systemNativeLibraryDirectories);
|
|
return allNativeLibraryDirectories;
|
|
}
|
|
|
|
/**
|
|
* Construct an instance.
|
|
*
|
|
* @param definingContext the context in which any as-yet unresolved
|
|
* classes should be defined
|
|
*
|
|
* @param dexFiles the bytebuffers containing the dex files that we should load classes from.
|
|
*/
|
|
public DexPathList(ClassLoader definingContext, String librarySearchPath) {
|
|
if (definingContext == null) {
|
|
throw new NullPointerException("definingContext == null");
|
|
}
|
|
|
|
this.definingContext = definingContext;
|
|
this.nativeLibraryDirectories = splitPaths(librarySearchPath, false);
|
|
this.systemNativeLibraryDirectories =
|
|
splitPaths(System.getProperty("java.library.path"), true);
|
|
this.nativeLibraryPathElements = makePathElements(getAllNativeLibraryDirectories());
|
|
}
|
|
|
|
/**
|
|
* Constructs an instance.
|
|
*
|
|
* @param definingContext the context in which any as-yet unresolved
|
|
* classes should be defined
|
|
* @param dexPath list of dex/resource path elements, separated by
|
|
* {@code File.pathSeparator}
|
|
* @param librarySearchPath list of native library directory path elements,
|
|
* separated by {@code File.pathSeparator}
|
|
* @param optimizedDirectory directory where optimized {@code .dex} files
|
|
* should be found and written to, or {@code null} to use the default
|
|
* system directory for same
|
|
*/
|
|
@UnsupportedAppUsage
|
|
public DexPathList(ClassLoader definingContext, String dexPath,
|
|
String librarySearchPath, File optimizedDirectory) {
|
|
this(definingContext, dexPath, librarySearchPath, optimizedDirectory, false);
|
|
}
|
|
|
|
DexPathList(ClassLoader definingContext, String dexPath,
|
|
String librarySearchPath, File optimizedDirectory, boolean isTrusted) {
|
|
if (definingContext == null) {
|
|
throw new NullPointerException("definingContext == null");
|
|
}
|
|
|
|
if (dexPath == null) {
|
|
throw new NullPointerException("dexPath == null");
|
|
}
|
|
|
|
if (optimizedDirectory != null) {
|
|
if (!optimizedDirectory.exists()) {
|
|
throw new IllegalArgumentException(
|
|
"optimizedDirectory doesn't exist: "
|
|
+ optimizedDirectory);
|
|
}
|
|
|
|
if (!(optimizedDirectory.canRead()
|
|
&& optimizedDirectory.canWrite())) {
|
|
throw new IllegalArgumentException(
|
|
"optimizedDirectory not readable/writable: "
|
|
+ optimizedDirectory);
|
|
}
|
|
}
|
|
|
|
this.definingContext = definingContext;
|
|
|
|
ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>();
|
|
// save dexPath for BaseDexClassLoader
|
|
this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,
|
|
suppressedExceptions, definingContext, isTrusted);
|
|
|
|
// Native libraries may exist in both the system and
|
|
// application library paths, and we use this search order:
|
|
//
|
|
// 1. This class loader's library path for application libraries (librarySearchPath):
|
|
// 1.1. Native library directories
|
|
// 1.2. Path to libraries in apk-files
|
|
// 2. The VM's library path from the system property for system libraries
|
|
// also known as java.library.path
|
|
//
|
|
// This order was reversed prior to Gingerbread; see http://b/2933456.
|
|
this.nativeLibraryDirectories = splitPaths(librarySearchPath, false);
|
|
this.systemNativeLibraryDirectories =
|
|
splitPaths(System.getProperty("java.library.path"), true);
|
|
this.nativeLibraryPathElements = makePathElements(getAllNativeLibraryDirectories());
|
|
|
|
if (suppressedExceptions.size() > 0) {
|
|
this.dexElementsSuppressedExceptions =
|
|
suppressedExceptions.toArray(new IOException[suppressedExceptions.size()]);
|
|
} else {
|
|
dexElementsSuppressedExceptions = null;
|
|
}
|
|
}
|
|
|
|
@Override public String toString() {
|
|
return "DexPathList[" + Arrays.toString(dexElements) +
|
|
",nativeLibraryDirectories=" +
|
|
Arrays.toString(getAllNativeLibraryDirectories().toArray()) + "]";
|
|
}
|
|
|
|
/**
|
|
* For BaseDexClassLoader.getLdLibraryPath.
|
|
*/
|
|
public List<File> getNativeLibraryDirectories() {
|
|
return nativeLibraryDirectories;
|
|
}
|
|
|
|
/**
|
|
* Adds a new path to this instance
|
|
* @param dexPath list of dex/resource path element, separated by
|
|
* {@code File.pathSeparator}
|
|
* @param optimizedDirectory directory where optimized {@code .dex} files
|
|
* should be found and written to, or {@code null} to use the default
|
|
* system directory for same
|
|
*/
|
|
@UnsupportedAppUsage
|
|
public void addDexPath(String dexPath, File optimizedDirectory) {
|
|
addDexPath(dexPath, optimizedDirectory, false);
|
|
}
|
|
|
|
public void addDexPath(String dexPath, File optimizedDirectory, boolean isTrusted) {
|
|
final List<IOException> suppressedExceptionList = new ArrayList<IOException>();
|
|
final Element[] newElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,
|
|
suppressedExceptionList, definingContext, isTrusted);
|
|
|
|
if (newElements != null && newElements.length > 0) {
|
|
dexElements = concat(Element.class, dexElements, newElements);
|
|
}
|
|
|
|
if (suppressedExceptionList.size() > 0) {
|
|
final IOException[] newSuppExceptions = suppressedExceptionList.toArray(
|
|
new IOException[suppressedExceptionList.size()]);
|
|
dexElementsSuppressedExceptions = dexElementsSuppressedExceptions != null
|
|
? concat(IOException.class, dexElementsSuppressedExceptions, newSuppExceptions)
|
|
: newSuppExceptions;
|
|
}
|
|
}
|
|
|
|
private static<T> T[] concat(Class<T> componentType, T[] inputA, T[] inputB) {
|
|
T[] output = (T[]) Array.newInstance(componentType, inputA.length + inputB.length);
|
|
System.arraycopy(inputA, 0, output, 0, inputA.length);
|
|
System.arraycopy(inputB, 0, output, inputA.length, inputB.length);
|
|
return output;
|
|
}
|
|
|
|
/**
|
|
* For InMemoryDexClassLoader. Initializes {@code dexElements} with dex files
|
|
* loaded from {@code dexFiles} buffers.
|
|
*
|
|
* @param dexFiles ByteBuffers containing raw dex data. Apks are not supported.
|
|
*/
|
|
/* package */ void initByteBufferDexPath(ByteBuffer[] dexFiles) {
|
|
if (dexFiles == null) {
|
|
throw new NullPointerException("dexFiles == null");
|
|
}
|
|
if (Arrays.stream(dexFiles).anyMatch(v -> v == null)) {
|
|
throw new NullPointerException("dexFiles contains a null Buffer!");
|
|
}
|
|
if (dexElements != null || dexElementsSuppressedExceptions != null) {
|
|
throw new IllegalStateException("Should only be called once");
|
|
}
|
|
|
|
final List<IOException> suppressedExceptions = new ArrayList<IOException>();
|
|
|
|
try {
|
|
Element[] null_elements = null;
|
|
DexFile dex = new DexFile(dexFiles, definingContext, null_elements);
|
|
dexElements = new Element[] { new Element(dex) };
|
|
} catch (IOException suppressed) {
|
|
System.logE("Unable to load dex files", suppressed);
|
|
suppressedExceptions.add(suppressed);
|
|
dexElements = new Element[0];
|
|
}
|
|
|
|
if (suppressedExceptions.size() > 0) {
|
|
dexElementsSuppressedExceptions = suppressedExceptions.toArray(
|
|
new IOException[suppressedExceptions.size()]);
|
|
}
|
|
}
|
|
|
|
/* package */ void maybeRunBackgroundVerification(ClassLoader loader) {
|
|
// Spawn background thread to verify all classes and cache verification results.
|
|
// Must be called *after* `this.dexElements` has been initialized and `loader.pathList`
|
|
// has been set for ART to find its classes (the fields are hardcoded in ART and dex
|
|
// files iterated over in the order of the array).
|
|
// We only spawn the background thread if the bytecode is not backed by an oat
|
|
// file, i.e. this is the first time this bytecode is being loaded and/or
|
|
// verification results have not been cached yet.
|
|
for (Element element : dexElements) {
|
|
if (element.dexFile != null && !element.dexFile.isBackedByOatFile()) {
|
|
element.dexFile.verifyInBackground(loader);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Splits the given dex path string into elements using the path
|
|
* separator, pruning out any elements that do not refer to existing
|
|
* and readable files.
|
|
*/
|
|
private static List<File> splitDexPath(String path) {
|
|
return splitPaths(path, false);
|
|
}
|
|
|
|
/**
|
|
* Splits the given path strings into file elements using the path
|
|
* separator, combining the results and filtering out elements
|
|
* that don't exist, aren't readable, or aren't either a regular
|
|
* file or a directory (as specified). Either string may be empty
|
|
* or {@code null}, in which case it is ignored. If both strings
|
|
* are empty or {@code null}, or all elements get pruned out, then
|
|
* this returns a zero-element list.
|
|
*/
|
|
@UnsupportedAppUsage
|
|
private static List<File> splitPaths(String searchPath, boolean directoriesOnly) {
|
|
List<File> result = new ArrayList<>();
|
|
|
|
if (searchPath != null) {
|
|
for (String path : searchPath.split(File.pathSeparator)) {
|
|
if (directoriesOnly) {
|
|
try {
|
|
StructStat sb = Libcore.os.stat(path);
|
|
if (!S_ISDIR(sb.st_mode)) {
|
|
continue;
|
|
}
|
|
} catch (ErrnoException ignored) {
|
|
continue;
|
|
}
|
|
}
|
|
result.add(new File(path));
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
// This method is not used anymore. Kept around only because there are many legacy users of it.
|
|
@SuppressWarnings("unused")
|
|
@UnsupportedAppUsage
|
|
public static Element[] makeInMemoryDexElements(ByteBuffer[] dexFiles,
|
|
List<IOException> suppressedExceptions) {
|
|
Element[] elements = new Element[dexFiles.length];
|
|
int elementPos = 0;
|
|
for (ByteBuffer buf : dexFiles) {
|
|
try {
|
|
DexFile dex = new DexFile(new ByteBuffer[] { buf }, /* classLoader */ null,
|
|
/* dexElements */ null);
|
|
elements[elementPos++] = new Element(dex);
|
|
} catch (IOException suppressed) {
|
|
System.logE("Unable to load dex file: " + buf, suppressed);
|
|
suppressedExceptions.add(suppressed);
|
|
}
|
|
}
|
|
if (elementPos != elements.length) {
|
|
elements = Arrays.copyOf(elements, elementPos);
|
|
}
|
|
return elements;
|
|
}
|
|
|
|
/**
|
|
* Makes an array of dex/resource path elements, one per element of
|
|
* the given array.
|
|
*/
|
|
@UnsupportedAppUsage
|
|
private static Element[] makeDexElements(List<File> files, File optimizedDirectory,
|
|
List<IOException> suppressedExceptions, ClassLoader loader) {
|
|
return makeDexElements(files, optimizedDirectory, suppressedExceptions, loader, false);
|
|
}
|
|
|
|
|
|
private static Element[] makeDexElements(List<File> files, File optimizedDirectory,
|
|
List<IOException> suppressedExceptions, ClassLoader loader, boolean isTrusted) {
|
|
Element[] elements = new Element[files.size()];
|
|
int elementsPos = 0;
|
|
/*
|
|
* Open all files and load the (direct or contained) dex files up front.
|
|
*/
|
|
for (File file : files) {
|
|
if (file.isDirectory()) {
|
|
// We support directories for looking up resources. Looking up resources in
|
|
// directories is useful for running libcore tests.
|
|
elements[elementsPos++] = new Element(file);
|
|
} else if (file.isFile()) {
|
|
String name = file.getName();
|
|
|
|
DexFile dex = null;
|
|
if (name.endsWith(DEX_SUFFIX)) {
|
|
// Raw dex file (not inside a zip/jar).
|
|
try {
|
|
dex = loadDexFile(file, optimizedDirectory, loader, elements);
|
|
if (dex != null) {
|
|
elements[elementsPos++] = new Element(dex, null);
|
|
}
|
|
} catch (IOException suppressed) {
|
|
System.logE("Unable to load dex file: " + file, suppressed);
|
|
suppressedExceptions.add(suppressed);
|
|
}
|
|
} else {
|
|
try {
|
|
dex = loadDexFile(file, optimizedDirectory, loader, elements);
|
|
} catch (IOException suppressed) {
|
|
/*
|
|
* IOException might get thrown "legitimately" by the DexFile constructor if
|
|
* the zip file turns out to be resource-only (that is, no classes.dex file
|
|
* in it).
|
|
* Let dex == null and hang on to the exception to add to the tea-leaves for
|
|
* when findClass returns null.
|
|
*/
|
|
suppressedExceptions.add(suppressed);
|
|
}
|
|
|
|
if (dex == null) {
|
|
elements[elementsPos++] = new Element(file);
|
|
} else {
|
|
elements[elementsPos++] = new Element(dex, file);
|
|
}
|
|
}
|
|
if (dex != null && isTrusted) {
|
|
dex.setTrusted();
|
|
}
|
|
} else {
|
|
System.logW("ClassLoader referenced unknown path: " + file);
|
|
}
|
|
}
|
|
if (elementsPos != elements.length) {
|
|
elements = Arrays.copyOf(elements, elementsPos);
|
|
}
|
|
return elements;
|
|
}
|
|
|
|
/**
|
|
* Constructs a {@code DexFile} instance, as appropriate depending on whether
|
|
* {@code optimizedDirectory} is {@code null}. An application image file may be associated with
|
|
* the {@code loader} if it is not null.
|
|
*/
|
|
@UnsupportedAppUsage
|
|
private static DexFile loadDexFile(File file, File optimizedDirectory, ClassLoader loader,
|
|
Element[] elements)
|
|
throws IOException {
|
|
if (optimizedDirectory == null) {
|
|
return new DexFile(file, loader, elements);
|
|
} else {
|
|
String optimizedPath = optimizedPathFor(file, optimizedDirectory);
|
|
return DexFile.loadDex(file.getPath(), optimizedPath, 0, loader, elements);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Converts a dex/jar file path and an output directory to an
|
|
* output file path for an associated optimized dex file.
|
|
*/
|
|
private static String optimizedPathFor(File path,
|
|
File optimizedDirectory) {
|
|
/*
|
|
* Get the filename component of the path, and replace the
|
|
* suffix with ".dex" if that's not already the suffix.
|
|
*
|
|
* We don't want to use ".odex", because the build system uses
|
|
* that for files that are paired with resource-only jar
|
|
* files. If the VM can assume that there's no classes.dex in
|
|
* the matching jar, it doesn't need to open the jar to check
|
|
* for updated dependencies, providing a slight performance
|
|
* boost at startup. The use of ".dex" here matches the use on
|
|
* files in /data/dalvik-cache.
|
|
*/
|
|
String fileName = path.getName();
|
|
if (!fileName.endsWith(DEX_SUFFIX)) {
|
|
int lastDot = fileName.lastIndexOf(".");
|
|
if (lastDot < 0) {
|
|
fileName += DEX_SUFFIX;
|
|
} else {
|
|
StringBuilder sb = new StringBuilder(lastDot + 4);
|
|
sb.append(fileName, 0, lastDot);
|
|
sb.append(DEX_SUFFIX);
|
|
fileName = sb.toString();
|
|
}
|
|
}
|
|
|
|
File result = new File(optimizedDirectory, fileName);
|
|
return result.getPath();
|
|
}
|
|
|
|
/*
|
|
* TODO (dimitry): Revert after apps stops relying on the existence of this
|
|
* method (see http://b/21957414 and http://b/26317852 for details)
|
|
*/
|
|
@UnsupportedAppUsage
|
|
@SuppressWarnings("unused")
|
|
private static Element[] makePathElements(List<File> files, File optimizedDirectory,
|
|
List<IOException> suppressedExceptions) {
|
|
return makeDexElements(files, optimizedDirectory, suppressedExceptions, null);
|
|
}
|
|
|
|
/**
|
|
* Makes an array of directory/zip path elements for the native library search path, one per
|
|
* element of the given array.
|
|
*/
|
|
@UnsupportedAppUsage
|
|
private static NativeLibraryElement[] makePathElements(List<File> files) {
|
|
NativeLibraryElement[] elements = new NativeLibraryElement[files.size()];
|
|
int elementsPos = 0;
|
|
for (File file : files) {
|
|
String path = file.getPath();
|
|
|
|
if (path.contains(zipSeparator)) {
|
|
String split[] = path.split(zipSeparator, 2);
|
|
File zip = new File(split[0]);
|
|
String dir = split[1];
|
|
elements[elementsPos++] = new NativeLibraryElement(zip, dir);
|
|
} else if (file.isDirectory()) {
|
|
// We support directories for looking up native libraries.
|
|
elements[elementsPos++] = new NativeLibraryElement(file);
|
|
}
|
|
}
|
|
if (elementsPos != elements.length) {
|
|
elements = Arrays.copyOf(elements, elementsPos);
|
|
}
|
|
return elements;
|
|
}
|
|
|
|
/**
|
|
* Finds the named class in one of the dex files pointed at by
|
|
* this instance. This will find the one in the earliest listed
|
|
* path element. If the class is found but has not yet been
|
|
* defined, then this method will define it in the defining
|
|
* context that this instance was constructed with.
|
|
*
|
|
* @param name of class to find
|
|
* @param suppressed exceptions encountered whilst finding the class
|
|
* @return the named class or {@code null} if the class is not
|
|
* found in any of the dex files
|
|
*/
|
|
public Class<?> findClass(String name, List<Throwable> suppressed) {
|
|
for (Element element : dexElements) {
|
|
Class<?> clazz = element.findClass(name, definingContext, suppressed);
|
|
if (clazz != null) {
|
|
return clazz;
|
|
}
|
|
}
|
|
|
|
if (dexElementsSuppressedExceptions != null) {
|
|
suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Finds the named resource in one of the zip/jar files pointed at
|
|
* by this instance. This will find the one in the earliest listed
|
|
* path element.
|
|
*
|
|
* @return a URL to the named resource or {@code null} if the
|
|
* resource is not found in any of the zip/jar files
|
|
*/
|
|
public URL findResource(String name) {
|
|
for (Element element : dexElements) {
|
|
URL url = element.findResource(name);
|
|
if (url != null) {
|
|
return url;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Finds all the resources with the given name, returning an
|
|
* enumeration of them. If there are no resources with the given
|
|
* name, then this method returns an empty enumeration.
|
|
*/
|
|
public Enumeration<URL> findResources(String name) {
|
|
ArrayList<URL> result = new ArrayList<URL>();
|
|
|
|
for (Element element : dexElements) {
|
|
URL url = element.findResource(name);
|
|
if (url != null) {
|
|
result.add(url);
|
|
}
|
|
}
|
|
|
|
return Collections.enumeration(result);
|
|
}
|
|
|
|
/**
|
|
* Finds the named native code library on any of the library
|
|
* directories pointed at by this instance. This will find the
|
|
* one in the earliest listed directory, ignoring any that are not
|
|
* readable regular files.
|
|
*
|
|
* @return the complete path to the library or {@code null} if no
|
|
* library was found
|
|
*/
|
|
public String findLibrary(String libraryName) {
|
|
String fileName = System.mapLibraryName(libraryName);
|
|
|
|
for (NativeLibraryElement element : nativeLibraryPathElements) {
|
|
String path = element.findNativeLibrary(fileName);
|
|
|
|
if (path != null) {
|
|
return path;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Returns the list of all individual dex files paths from the current list.
|
|
* The list will contain only file paths (i.e. no directories).
|
|
*/
|
|
/*package*/ List<String> getDexPaths() {
|
|
List<String> dexPaths = new ArrayList<String>();
|
|
for (Element e : dexElements) {
|
|
String dexPath = e.getDexPath();
|
|
if (dexPath != null) {
|
|
// Add the element to the list only if it is a file. A null dex path signals the
|
|
// element is a resource directory or an in-memory dex file.
|
|
dexPaths.add(dexPath);
|
|
}
|
|
}
|
|
return dexPaths;
|
|
}
|
|
|
|
/**
|
|
* Adds a collection of library paths from which to load native libraries. Paths can be absolute
|
|
* native library directories (i.e. /data/app/foo/lib/arm64) or apk references (i.e.
|
|
* /data/app/foo/base.apk!/lib/arm64).
|
|
*
|
|
* Note: This method will attempt to dedupe elements.
|
|
* Note: This method replaces the value of {@link #nativeLibraryPathElements}
|
|
*/
|
|
@UnsupportedAppUsage
|
|
public void addNativePath(Collection<String> libPaths) {
|
|
if (libPaths.isEmpty()) {
|
|
return;
|
|
}
|
|
List<File> libFiles = new ArrayList<>(libPaths.size());
|
|
for (String path : libPaths) {
|
|
libFiles.add(new File(path));
|
|
}
|
|
ArrayList<NativeLibraryElement> newPaths =
|
|
new ArrayList<>(nativeLibraryPathElements.length + libPaths.size());
|
|
newPaths.addAll(Arrays.asList(nativeLibraryPathElements));
|
|
for (NativeLibraryElement element : makePathElements(libFiles)) {
|
|
if (!newPaths.contains(element)) {
|
|
newPaths.add(element);
|
|
}
|
|
}
|
|
nativeLibraryPathElements = newPaths.toArray(new NativeLibraryElement[newPaths.size()]);
|
|
}
|
|
|
|
/**
|
|
* Element of the dex/resource path. Note: should be called DexElement, but apps reflect on
|
|
* this.
|
|
*/
|
|
/*package*/ static class Element {
|
|
/**
|
|
* A file denoting a zip file (in case of a resource jar or a dex jar), or a directory
|
|
* (only when dexFile is null).
|
|
*/
|
|
@UnsupportedAppUsage
|
|
private final File path;
|
|
/** Whether {@code path.isDirectory()}, or {@code null} if {@code path == null}. */
|
|
private final Boolean pathIsDirectory;
|
|
|
|
@UnsupportedAppUsage
|
|
private final DexFile dexFile;
|
|
|
|
private ClassPathURLStreamHandler urlHandler;
|
|
private boolean initialized;
|
|
|
|
/**
|
|
* Element encapsulates a dex file. This may be a plain dex file (in which case dexZipPath
|
|
* should be null), or a jar (in which case dexZipPath should denote the zip file).
|
|
*/
|
|
@UnsupportedAppUsage
|
|
public Element(DexFile dexFile, File dexZipPath) {
|
|
if (dexFile == null && dexZipPath == null) {
|
|
throw new NullPointerException("Either dexFile or path must be non-null");
|
|
}
|
|
this.dexFile = dexFile;
|
|
this.path = dexZipPath;
|
|
// Do any I/O in the constructor so we don't have to do it elsewhere, eg. toString().
|
|
this.pathIsDirectory = (path == null) ? null : path.isDirectory();
|
|
}
|
|
|
|
public Element(DexFile dexFile) {
|
|
this(dexFile, null);
|
|
}
|
|
|
|
public Element(File path) {
|
|
this(null, path);
|
|
}
|
|
|
|
/**
|
|
* Constructor for a bit of backwards compatibility. Some apps use reflection into
|
|
* internal APIs. Warn, and emulate old behavior if we can. See b/33399341.
|
|
*
|
|
* @deprecated The Element class has been split. Use new Element constructors for
|
|
* classes and resources, and NativeLibraryElement for the library
|
|
* search path.
|
|
*/
|
|
@UnsupportedAppUsage
|
|
@Deprecated
|
|
public Element(File dir, boolean isDirectory, File zip, DexFile dexFile) {
|
|
this(dir != null ? null : dexFile, dir != null ? dir : zip);
|
|
System.err.println("Warning: Using deprecated Element constructor. Do not use internal"
|
|
+ " APIs, this constructor will be removed in the future.");
|
|
if (dir != null && (zip != null || dexFile != null)) {
|
|
throw new IllegalArgumentException("Using dir and zip|dexFile no longer"
|
|
+ " supported.");
|
|
}
|
|
if (isDirectory && (zip != null || dexFile != null)) {
|
|
throw new IllegalArgumentException("Unsupported argument combination.");
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Returns the dex path of this element or null if the element refers to a directory.
|
|
*/
|
|
private String getDexPath() {
|
|
if (path != null) {
|
|
return path.isDirectory() ? null : path.getAbsolutePath();
|
|
} else if (dexFile != null) {
|
|
// DexFile.getName() returns the path of the dex file.
|
|
return dexFile.getName();
|
|
}
|
|
return null;
|
|
}
|
|
|
|
@Override
|
|
public String toString() {
|
|
if (dexFile == null) {
|
|
return (pathIsDirectory ? "directory \"" : "zip file \"") + path + "\"";
|
|
} else if (path == null) {
|
|
return "dex file \"" + dexFile + "\"";
|
|
} else {
|
|
return "zip file \"" + path + "\"";
|
|
}
|
|
}
|
|
|
|
public synchronized void maybeInit() {
|
|
if (initialized) {
|
|
return;
|
|
}
|
|
|
|
if (path == null || pathIsDirectory) {
|
|
initialized = true;
|
|
return;
|
|
}
|
|
|
|
try {
|
|
// Disable zip path validation for loading APKs as it does not pose a risk of the
|
|
// zip path traversal vulnerability.
|
|
urlHandler = new ClassPathURLStreamHandler(path.getPath(),
|
|
/* enableZipPathValidator */ false);
|
|
} catch (IOException ioe) {
|
|
/*
|
|
* Note: ZipException (a subclass of IOException)
|
|
* might get thrown by the ZipFile constructor
|
|
* (e.g. if the file isn't actually a zip/jar
|
|
* file).
|
|
*/
|
|
System.logE("Unable to open zip file: " + path, ioe);
|
|
urlHandler = null;
|
|
}
|
|
|
|
// Mark this element as initialized only after we've successfully created
|
|
// the associated ClassPathURLStreamHandler. That way, we won't leave this
|
|
// element in an inconsistent state if an exception is thrown during initialization.
|
|
//
|
|
// See b/35633614.
|
|
initialized = true;
|
|
}
|
|
|
|
public Class<?> findClass(String name, ClassLoader definingContext,
|
|
List<Throwable> suppressed) {
|
|
return dexFile != null ? dexFile.loadClassBinaryName(name, definingContext, suppressed)
|
|
: null;
|
|
}
|
|
|
|
public URL findResource(String name) {
|
|
maybeInit();
|
|
|
|
if (urlHandler != null) {
|
|
return urlHandler.getEntryUrlOrNull(name);
|
|
}
|
|
|
|
// We support directories so we can run tests and/or legacy code
|
|
// that uses Class.getResource.
|
|
if (path != null && path.isDirectory()) {
|
|
File resourceFile = new File(path, name);
|
|
if (resourceFile.exists()) {
|
|
try {
|
|
return resourceFile.toURI().toURL();
|
|
} catch (MalformedURLException ex) {
|
|
throw new RuntimeException(ex);
|
|
}
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Element of the native library path
|
|
*/
|
|
/*package*/ static class NativeLibraryElement {
|
|
/**
|
|
* A file denoting a directory or zip file.
|
|
*/
|
|
@UnsupportedAppUsage
|
|
private final File path;
|
|
|
|
/**
|
|
* If path denotes a zip file, this denotes a base path inside the zip.
|
|
*/
|
|
private final String zipDir;
|
|
|
|
private ClassPathURLStreamHandler urlHandler;
|
|
private boolean initialized;
|
|
|
|
@UnsupportedAppUsage
|
|
public NativeLibraryElement(File dir) {
|
|
this.path = dir;
|
|
this.zipDir = null;
|
|
|
|
// We should check whether path is a directory, but that is non-eliminatable overhead.
|
|
}
|
|
|
|
public NativeLibraryElement(File zip, String zipDir) {
|
|
this.path = zip;
|
|
this.zipDir = zipDir;
|
|
|
|
// Simple check that should be able to be eliminated by inlining. We should also
|
|
// check whether path is a file, but that is non-eliminatable overhead.
|
|
if (zipDir == null) {
|
|
throw new IllegalArgumentException();
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public String toString() {
|
|
if (zipDir == null) {
|
|
return "directory \"" + path + "\"";
|
|
} else {
|
|
return "zip file \"" + path + "\"" +
|
|
(!zipDir.isEmpty() ? ", dir \"" + zipDir + "\"" : "");
|
|
}
|
|
}
|
|
|
|
public synchronized void maybeInit() {
|
|
if (initialized) {
|
|
return;
|
|
}
|
|
|
|
if (zipDir == null) {
|
|
initialized = true;
|
|
return;
|
|
}
|
|
|
|
try {
|
|
// Disable zip path validation for loading APKs as it does not pose a risk of the
|
|
// zip path traversal vulnerability.
|
|
urlHandler = new ClassPathURLStreamHandler(path.getPath(),
|
|
/* enableZipPathValidator */ false);
|
|
} catch (IOException ioe) {
|
|
/*
|
|
* Note: ZipException (a subclass of IOException)
|
|
* might get thrown by the ZipFile constructor
|
|
* (e.g. if the file isn't actually a zip/jar
|
|
* file).
|
|
*/
|
|
System.logE("Unable to open zip file: " + path, ioe);
|
|
urlHandler = null;
|
|
}
|
|
|
|
// Mark this element as initialized only after we've successfully created
|
|
// the associated ClassPathURLStreamHandler. That way, we won't leave this
|
|
// element in an inconsistent state if an exception is thrown during initialization.
|
|
//
|
|
// See b/35633614.
|
|
initialized = true;
|
|
}
|
|
|
|
public String findNativeLibrary(String name) {
|
|
maybeInit();
|
|
|
|
if (zipDir == null) {
|
|
String entryPath = new File(path, name).getPath();
|
|
if (IoUtils.canOpenReadOnly(entryPath)) {
|
|
return entryPath;
|
|
}
|
|
} else if (urlHandler != null) {
|
|
// Having a urlHandler means the element has a zip file.
|
|
// In this case Android supports loading the library iff
|
|
// it is stored in the zip uncompressed.
|
|
String entryName = zipDir + '/' + name;
|
|
if (urlHandler.isEntryStored(entryName)) {
|
|
return path.getPath() + zipSeparator + entryName;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
@Override
|
|
public boolean equals(Object o) {
|
|
if (this == o) return true;
|
|
if (!(o instanceof NativeLibraryElement)) return false;
|
|
NativeLibraryElement that = (NativeLibraryElement) o;
|
|
return Objects.equals(path, that.path) &&
|
|
Objects.equals(zipDir, that.zipDir);
|
|
}
|
|
|
|
@Override
|
|
public int hashCode() {
|
|
return Objects.hash(path, zipDir);
|
|
}
|
|
}
|
|
}
|