Fix lookup order when opening directly from APK
The order should be as follows:
1. Uncompressed native library dir (if any)
2. Directly from apk (<apk>!/lib/<abi>)
3. vendor/lib:/system/lib
Bug: http://b/21647354
Bug: http://b/21667767
Bug: http://b/21726698
Bug: http://b/8076853
Change-Id: I62cd76b7e4ae927d865d7d0ee81ceb91caa54e99
diff --git a/dalvik/src/main/java/dalvik/system/BaseDexClassLoader.java b/dalvik/src/main/java/dalvik/system/BaseDexClassLoader.java
index 4124ffa..5ef00bf 100644
--- a/dalvik/src/main/java/dalvik/system/BaseDexClassLoader.java
+++ b/dalvik/src/main/java/dalvik/system/BaseDexClassLoader.java
@@ -130,24 +130,8 @@
}
result.append(directory);
}
- return result.toString();
- }
- /**
- * Returns the list of jar/apk files containing classes and
- * resources, delimited by {@code File.pathSeparator}.
- *
- * @hide
- */
- public String getDexPath() {
- StringBuilder builder = new StringBuilder();
- for (File file : pathList.getDexFiles()) {
- if (builder.length() > 0) {
- builder.append(':');
- }
- builder.append(file);
- }
- return builder.toString();
+ return result.toString();
}
@Override public String toString() {
diff --git a/dalvik/src/main/java/dalvik/system/DexPathList.java b/dalvik/src/main/java/dalvik/system/DexPathList.java
index 506a156..f0b1ffd 100644
--- a/dalvik/src/main/java/dalvik/system/DexPathList.java
+++ b/dalvik/src/main/java/dalvik/system/DexPathList.java
@@ -27,6 +27,7 @@
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;
+import java.util.zip.ZipEntry;
import libcore.io.IoUtils;
import libcore.io.Libcore;
import libcore.io.ClassPathURLStreamHandler;
@@ -48,13 +49,11 @@
*/
/*package*/ final class DexPathList {
private static final String DEX_SUFFIX = ".dex";
+ private static final String zipSeparator = "!/";
/** class definition context */
private final ClassLoader definingContext;
- /** List of dexfiles. */
- private final List<File> dexFiles;
-
/**
* List of dex/resource (class path) elements.
* Should be called pathElements, but the Facebook app uses reflection
@@ -62,9 +61,15 @@
*/
private final Element[] dexElements;
- /** List of native library directories. */
+ /** List of native library path elements. */
+ private final Element[] nativeLibraryPathElements;
+
+ /** List of application native library directories. */
private final List<File> nativeLibraryDirectories;
+ /** List of system native library directories. */
+ private final List<File> systemNativeLibraryDirectories;
+
/**
* Exceptions thrown during creation of the dexElements list.
*/
@@ -85,6 +90,7 @@
*/
public DexPathList(ClassLoader definingContext, String dexPath,
String libraryPath, File optimizedDirectory) {
+
if (definingContext == null) {
throw new NullPointerException("definingContext == null");
}
@@ -109,23 +115,46 @@
}
this.definingContext = definingContext;
+
ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>();
// save dexPath for BaseDexClassLoader
- this.dexFiles = splitDexPath(dexPath);
- this.dexElements = makeDexElements(dexFiles, optimizedDirectory,
+ this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,
suppressedExceptions);
+
+ // 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 (libraryPath):
+ // 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(libraryPath, false);
+ this.systemNativeLibraryDirectories =
+ splitPaths(System.getProperty("java.library.path"), true);
+ List<File> allNativeLibraryDirectories = new ArrayList<>(nativeLibraryDirectories);
+ allNativeLibraryDirectories.addAll(systemNativeLibraryDirectories);
+
+ this.nativeLibraryPathElements = makePathElements(allNativeLibraryDirectories,
+ suppressedExceptions);
+
if (suppressedExceptions.size() > 0) {
this.dexElementsSuppressedExceptions =
suppressedExceptions.toArray(new IOException[suppressedExceptions.size()]);
} else {
dexElementsSuppressedExceptions = null;
}
- this.nativeLibraryDirectories = splitLibraryPath(libraryPath);
}
@Override public String toString() {
+ List<File> allNativeLibraryDirectories = new ArrayList<>(nativeLibraryDirectories);
+ allNativeLibraryDirectories.addAll(systemNativeLibraryDirectories);
+
File[] nativeLibraryDirectoriesArray =
- nativeLibraryDirectories.toArray(new File[nativeLibraryDirectories.size()]);
+ allNativeLibraryDirectories.toArray(
+ new File[allNativeLibraryDirectories.size()]);
return "DexPathList[" + Arrays.toString(dexElements) +
",nativeLibraryDirectories=" + Arrays.toString(nativeLibraryDirectoriesArray) + "]";
@@ -139,38 +168,12 @@
}
/**
- * For BaseDexClassLoader.getDexPath.
- */
- public List<File> getDexFiles() {
- return dexFiles;
- }
-
- /**
* 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. (That is, directories are not included in the
- * result.)
+ * and readable files.
*/
private static List<File> splitDexPath(String path) {
- return splitPaths(path, null, false);
- }
-
- /**
- * Splits the given library directory path string into elements
- * using the path separator ({@code File.pathSeparator}, which
- * defaults to {@code ":"} on Android, appending on the elements
- * from the system library path, and pruning out any elements that
- * do not refer to existing and readable directories.
- */
- private static List<File> splitLibraryPath(String path) {
- // 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
- // 2. the VM's library path from the system property for system libraries
- //
- // This order was reversed prior to Gingerbread; see http://b/2933456.
- return splitPaths(path, System.getProperty("java.library.path"), true);
+ return splitPaths(path, false);
}
/**
@@ -182,32 +185,26 @@
* are empty or {@code null}, or all elements get pruned out, then
* this returns a zero-element list.
*/
- private static List<File> splitPaths(String path1, String path2, boolean wantDirectories) {
- List<File> result = new ArrayList<File>();
+ private static List<File> splitPaths(String searchPath, boolean directoriesOnly) {
+ List<File> result = new ArrayList<>();
- splitAndAdd(path1, wantDirectories, result);
- splitAndAdd(path2, wantDirectories, result);
- return result;
- }
-
- /**
- * Helper for {@link #splitPaths}, which does the actual splitting
- * and filtering and adding to a result.
- */
- private static void splitAndAdd(String searchPath, boolean directoriesOnly,
- List<File> resultList) {
- if (searchPath == null) {
- return;
- }
- for (String path : searchPath.split(":")) {
- try {
- StructStat sb = Libcore.os.stat(path);
- if (!directoriesOnly || S_ISDIR(sb.st_mode)) {
- resultList.add(new File(path));
+ 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;
+ }
}
- } catch (ErrnoException ignored) {
+ result.add(new File(path));
}
}
+
+ return result;
}
/**
@@ -216,22 +213,42 @@
*/
private static Element[] makeDexElements(List<File> files, File optimizedDirectory,
List<IOException> suppressedExceptions) {
- ArrayList<Element> elements = new ArrayList<Element>();
+ return makeElements(files, optimizedDirectory, suppressedExceptions, false);
+ }
+
+ /**
+ * Makes an array of directory/zip path elements, one per element of the given array.
+ */
+ private static Element[] makePathElements(List<File> files,
+ List<IOException> suppressedExceptions) {
+ return makeElements(files, null, suppressedExceptions, true);
+ }
+
+ private static Element[] makeElements(List<File> files, File optimizedDirectory,
+ List<IOException> suppressedExceptions,
+ boolean ignoreDexFiles) {
+ List<Element> elements = new ArrayList<>();
/*
* Open all files and load the (direct or contained) dex files
* up front.
*/
for (File file : files) {
File zip = null;
+ File dir = new File("");
DexFile dex = null;
+ String path = file.getPath();
String name = file.getName();
- if (file.isDirectory()) {
- // We support directories for looking up resources.
- // This is only useful for running libcore tests.
+ if (path.contains(zipSeparator)) {
+ String split[] = path.split(zipSeparator, 2);
+ zip = new File(split[0]);
+ dir = new File(split[1]);
+ } else if (file.isDirectory()) {
+ // We support directories for looking up resources and native libraries.
+ // Looking up resources in directories is useful for running libcore tests.
elements.add(new Element(file, true, null, null));
- } else if (file.isFile()){
- if (name.endsWith(DEX_SUFFIX)) {
+ } else if (file.isFile()) {
+ if (!ignoreDexFiles && name.endsWith(DEX_SUFFIX)) {
// Raw dex file (not inside a zip/jar).
try {
dex = loadDexFile(file, optimizedDirectory);
@@ -241,17 +258,19 @@
} else {
zip = file;
- try {
- dex = loadDexFile(file, optimizedDirectory);
- } 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 (!ignoreDexFiles) {
+ try {
+ dex = loadDexFile(file, optimizedDirectory);
+ } 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);
+ }
}
}
} else {
@@ -259,7 +278,7 @@
}
if ((zip != null) || (dex != null)) {
- elements.add(new Element(file, false, zip, dex));
+ elements.add(new Element(dir, false, zip, dex));
}
}
@@ -392,20 +411,23 @@
*/
public String findLibrary(String libraryName) {
String fileName = System.mapLibraryName(libraryName);
- for (File directory : nativeLibraryDirectories) {
- String path = new File(directory, fileName).getPath();
- if (IoUtils.canOpenReadOnly(path)) {
+
+ for (Element element : nativeLibraryPathElements) {
+ String path = element.findNativeLibrary(fileName);
+
+ if (path != null) {
return path;
}
}
+
return null;
}
/**
- * Element of the dex/resource file path
+ * Element of the dex/resource/native library path
*/
/*package*/ static class Element {
- private final File file;
+ private final File dir;
private final boolean isDirectory;
private final File zip;
private final DexFile dexFile;
@@ -413,8 +435,8 @@
private ClassPathURLStreamHandler urlHandler;
private boolean initialized;
- public Element(File file, boolean isDirectory, File zip, DexFile dexFile) {
- this.file = file;
+ public Element(File dir, boolean isDirectory, File zip, DexFile dexFile) {
+ this.dir = dir;
this.isDirectory = isDirectory;
this.zip = zip;
this.dexFile = dexFile;
@@ -422,9 +444,10 @@
@Override public String toString() {
if (isDirectory) {
- return "directory \"" + file + "\"";
+ return "directory \"" + dir + "\"";
} else if (zip != null) {
- return "zip file \"" + zip + "\"";
+ return "zip file \"" + zip + "\"" +
+ (dir != null && !dir.getPath().isEmpty() ? ", dir \"" + dir + "\"" : "");
} else {
return "dex file \"" + dexFile + "\"";
}
@@ -450,17 +473,40 @@
* (e.g. if the file isn't actually a zip/jar
* file).
*/
- System.logE("Unable to open zip file: " + file, ioe);
+ System.logE("Unable to open zip file: " + zip, ioe);
+ urlHandler = null;
}
}
+ public String findNativeLibrary(String name) {
+ maybeInit();
+
+ if (isDirectory) {
+ String path = new File(dir, name).getPath();
+ if (IoUtils.canOpenReadOnly(path)) {
+ return path;
+ }
+ } 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 = new File(dir, name).getPath();
+ if (urlHandler.isEntryStored(entryName)) {
+ return zip.getPath() + zipSeparator + entryName;
+ }
+ }
+
+ return null;
+ }
+
public URL findResource(String name) {
maybeInit();
// We support directories so we can run tests and/or legacy code
// that uses Class.getResource.
if (isDirectory) {
- File resourceFile = new File(file, name);
+ File resourceFile = new File(dir, name);
if (resourceFile.exists()) {
try {
return resourceFile.toURI().toURL();
diff --git a/luni/src/main/java/java/lang/Runtime.java b/luni/src/main/java/java/lang/Runtime.java
index ad549f2..f1f6438 100644
--- a/luni/src/main/java/java/lang/Runtime.java
+++ b/luni/src/main/java/java/lang/Runtime.java
@@ -358,11 +358,14 @@
*/
void loadLibrary(String libraryName, ClassLoader loader) {
if (loader != null) {
- // TODO: We shouldn't assume that we know default linker search logic.
String filename = loader.findLibrary(libraryName);
if (filename == null) {
- // The dynamic linker might still find the library by name.
- filename = System.mapLibraryName(libraryName);
+ // It's not necessarily true that the ClassLoader used
+ // System.mapLibraryName, but the default setup does, and it's
+ // misleading to say we didn't find "libMyLibrary.so" when we
+ // actually searched for "liblibMyLibrary.so.so".
+ throw new UnsatisfiedLinkError(loader + " couldn't find \"" +
+ System.mapLibraryName(libraryName) + "\"");
}
String error = doLoad(filename, loader);
if (error != null) {
@@ -424,19 +427,18 @@
} else if (loader instanceof BaseDexClassLoader) {
BaseDexClassLoader dexClassLoader = (BaseDexClassLoader) loader;
ldLibraryPath = dexClassLoader.getLdLibraryPath();
- dexPath = dexClassLoader.getDexPath();
}
// nativeLoad should be synchronized so there's only one LD_LIBRARY_PATH in use regardless
// of how many ClassLoaders are in the system, but dalvik doesn't support synchronized
// internal natives.
synchronized (this) {
- return nativeLoad(name, loader, ldLibraryPath, dexPath);
+ return nativeLoad(name, loader, ldLibraryPath);
}
}
// TODO: should be synchronized, but dalvik doesn't support synchronized internal natives.
private static native String nativeLoad(String filename, ClassLoader loader,
- String ldLibraryPath, String dexPath);
+ String ldLibraryPath);
/**
* Provides a hint to the runtime that it would be useful to attempt
diff --git a/luni/src/main/java/libcore/io/ClassPathURLStreamHandler.java b/luni/src/main/java/libcore/io/ClassPathURLStreamHandler.java
index 9c4a4e8..2f26b14 100644
--- a/luni/src/main/java/libcore/io/ClassPathURLStreamHandler.java
+++ b/luni/src/main/java/libcore/io/ClassPathURLStreamHandler.java
@@ -54,7 +54,7 @@
/**
* Returns a URL backed by this stream handler for the named resource, or {@code null} if the
- * resource cannot be found under the exact name presented.
+ * entry cannot be found under the exact name presented.
*/
public URL getEntryUrlOrNull(String entryName) {
if (jarFile.findEntry(entryName) != null) {
@@ -69,6 +69,15 @@
return null;
}
+ /**
+ * Returns true if entry with specified name exists and stored (not compressed),
+ * and false otherwise.
+ */
+ public boolean isEntryStored(String entryName) {
+ ZipEntry entry = jarFile.findEntry(entryName);
+ return entry != null && entry.getMethod() == ZipEntry.STORED;
+ }
+
@Override
protected URLConnection openConnection(URL url) throws IOException {
return new ClassPathURLConnection(url, jarFile);
diff --git a/luni/src/test/java/libcore/io/ClassPathURLStreamHandlerTest.java b/luni/src/test/java/libcore/io/ClassPathURLStreamHandlerTest.java
index 9eb92de..eb66c3a 100644
--- a/luni/src/test/java/libcore/io/ClassPathURLStreamHandlerTest.java
+++ b/luni/src/test/java/libcore/io/ClassPathURLStreamHandlerTest.java
@@ -37,6 +37,7 @@
private static final String JAR = "ClassPathURLStreamHandlerTest.jar";
private static final String ENTRY_IN_ROOT = "root.txt";
private static final String ENTRY_IN_SUBDIR = "foo/bar/baz.txt";
+ private static final String ENTRY_STORED = "stored_file.txt";
private static final String ENTRY_WITH_SPACES_ENCODED = "file%20with%20spaces.txt";
private static final String ENTRY_WITH_SPACES_UNENCODED = "file with spaces.txt";
private static final String ENTRY_THAT_NEEDS_ESCAPING = "file_with_percent20_%20.txt";
@@ -82,6 +83,16 @@
streamHandler.close();
}
+ public void testIsEntryStored() throws IOException {
+ String fileName = jarFile.getCanonicalPath();
+ ClassPathURLStreamHandler streamHandler = new ClassPathURLStreamHandler(fileName);
+
+ assertFalse(streamHandler.isEntryStored("this/file/does/not/exist.txt"));
+ // This one is compressed
+ assertFalse(streamHandler.isEntryStored(ENTRY_IN_SUBDIR));
+ assertTrue(streamHandler.isEntryStored(ENTRY_STORED));
+ }
+
public void testOpenConnection() throws Exception {
String fileName = jarFile.getCanonicalPath();
ClassPathURLStreamHandler streamHandler = new ClassPathURLStreamHandler(fileName);
diff --git a/support/src/test/java/tests/resources/ClassPathURLStreamHandlerTest.jar b/support/src/test/java/tests/resources/ClassPathURLStreamHandlerTest.jar
index db67c93..ef810a7 100644
--- a/support/src/test/java/tests/resources/ClassPathURLStreamHandlerTest.jar
+++ b/support/src/test/java/tests/resources/ClassPathURLStreamHandlerTest.jar
Binary files differ