blob: ef87ee5607043a5552a55c60753637e052cda630 [file] [log] [blame]
/*
* Copyright (C) 2016 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 libcore.dalvik.system;
import java.lang.reflect.Method;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.file.Files;
import libcore.io.Streams;
import junit.framework.TestCase;
import dalvik.system.BaseDexClassLoader;
import dalvik.system.InMemoryDexClassLoader;
/**
* Tests for the class {@link InMemoryDexClassLoader}.
*/
public class InMemoryDexClassLoaderTest extends TestCase {
private static final String PACKAGE_PATH = "dalvik/system/";
private File srcDir;
private File dex1;
private File dex2;
protected void setUp() throws Exception {
srcDir = File.createTempFile("src", "");
assertTrue(srcDir.delete());
assertTrue(srcDir.mkdirs());
dex1 = new File(srcDir, "loading-test.dex");
dex2 = new File(srcDir, "loading-test2.dex");
copyResource("loading-test.dex", dex1);
copyResource("loading-test2.dex", dex2);
}
protected void tearDown() {
cleanUpDir(srcDir);
}
private static void cleanUpDir(File dir) {
if (!dir.isDirectory()) {
return;
}
File[] files = dir.listFiles();
for (File file : files) {
if (file.isDirectory()) {
cleanUpDir(file);
} else {
assertTrue(file.delete());
}
}
assertTrue(dir.delete());
}
/**
* Copy a resource in the package directory to the indicated
* target file.
*/
private static void copyResource(String resourceName,
File destination) throws IOException {
ClassLoader loader = InMemoryDexClassLoaderTest.class.getClassLoader();
InputStream in = loader.getResourceAsStream(PACKAGE_PATH + resourceName);
if (in == null) {
throw new IllegalStateException("Resource not found: " + PACKAGE_PATH + resourceName);
}
try (FileOutputStream out = new FileOutputStream(destination)) {
Streams.copy(in, out);
} finally {
in.close();
}
}
/**
* Helper to construct a direct ByteBuffer with the contents of a given file.
*
* Constructs a new direct ByteBuffer and inserts {@code paddingBefore} amount of
* zero padding followed by the contents of {@code file}. The buffer's position is
* set to the beginning of the file's data.
*
* @param file The file to be read
* @param paddingBefore Number of zero bytes to be inserted at the beginning of the buffer.
*/
private static ByteBuffer readFileToByteBufferDirect(File file, int paddingBefore)
throws IOException {
try (RandomAccessFile raf = new RandomAccessFile(file, "r")) {
ByteBuffer buffer = ByteBuffer.allocateDirect(paddingBefore + (int)file.length());
buffer.put(new byte[paddingBefore]);
int done = 0;
while (done != file.length()) {
done += raf.getChannel().read(buffer);
}
buffer.rewind();
buffer.position(paddingBefore);
return buffer;
}
}
/**
* Helper to construct a direct ByteBuffer with the contents of a given file.
*
* Constructs a new direct ByteBuffer and the contents of {@code file}. The buffer's
* position is zero.
*
* @param file The file to be read
*/
private static ByteBuffer readFileToByteBufferDirect(File file) throws IOException {
return readFileToByteBufferDirect(file, /* paddingBefore */ 0);
}
/**
* Helper to construct an indirect ByteBuffer with the contents of a given file.
*
* Constructs a new indirect ByteBuffer and inserts {@code paddingBefore} amount of
* zero padding followed by the contents of {@code file}. The buffer's position is
* set to the beginning of the file's data.
*
* @param file The file to be read
* @param paddingBefore Number of zero bytes to be inserted at the beginning of the buffer.
*/
private static ByteBuffer readFileToByteBufferIndirect(File file, int paddingBefore)
throws IOException {
ByteBuffer direct = readFileToByteBufferDirect(file, paddingBefore);
direct.rewind();
byte[] array = new byte[direct.limit()];
direct.get(array);
ByteBuffer buf = ByteBuffer.wrap(array);
buf.position(paddingBefore);
return buf;
}
/**
* Helper to construct an indirect ByteBuffer with the contents of a given file.
*
* Constructs a new indirect ByteBuffer and the contents of {@code file}. The buffer's
* position is zero.
*
* @param file The file to be read
*/
private static ByteBuffer readFileToByteBufferIndirect(File file) throws IOException {
return readFileToByteBufferIndirect(file, /* paddingBefore */ 0);
}
/**
* Helper to construct a InMemoryDexClassLoader instance to test.
*
* Creates InMemoryDexClassLoader from ByteBuffer instances that are
* direct allocated.
*
* @param files The .dex files to use for the class path.
*/
private ClassLoader createLoaderDirect(File... files) throws IOException {
assertNotNull(files);
assertTrue(files.length > 0);
ClassLoader result = ClassLoader.getSystemClassLoader();
for (int i = 0; i < files.length; ++i) {
ByteBuffer buffer = readFileToByteBufferDirect(files[i]);
result = new InMemoryDexClassLoader(new ByteBuffer[] { buffer }, result);
}
return result;
}
/**
* Helper to construct a InMemoryDexClassLoader instance to test.
*
* Creates InMemoryDexClassLoader from ByteBuffer instances that are
* heap allocated.
*
* @param files The .dex files to use for the class path.
*/
private ClassLoader createLoaderIndirect(File... files) throws IOException {
assertNotNull(files);
assertTrue(files.length > 0);
ClassLoader result = ClassLoader.getSystemClassLoader();
for (int i = 0; i < files.length; ++i) {
ByteBuffer buffer = readFileToByteBufferIndirect(files[i]);
result = new InMemoryDexClassLoader(new ByteBuffer[] { buffer }, result);
}
return result;
}
/**
* Helper to construct a new InMemoryDexClassLoader via direct
* ByteBuffer instances.
*
* @param className The name of the class of the method to call.
* @param methodName The name of the method to call.
* @param files The .dex or .jar files to use for the class path.
*/
private Object createLoaderDirectAndCallMethod(
String className, String methodName, File... files)
throws IOException, ReflectiveOperationException {
ClassLoader cl = createLoaderDirect(files);
Class c = cl.loadClass(className);
Method m = c.getMethod(methodName, (Class[]) null);
assertNotNull(m);
return m.invoke(null, (Object[]) null);
}
/**
* Helper to construct a new InMemoryDexClassLoader via indirect
* ByteBuffer instances.
*
* @param className The name of the class of the method to call.
* @param methodName The name of the method to call.
* @param files The .dex or .jar files to use for the class path.
*/
private Object createLoaderIndirectAndCallMethod(
String className, String methodName, File... files)
throws IOException, ReflectiveOperationException {
ClassLoader cl = createLoaderIndirect(files);
Class c = cl.loadClass(className);
Method m = c.getMethod(methodName, (Class[]) null);
assertNotNull(m);
return m.invoke(null, (Object[]) null);
}
/**
* Creates an InMemoryDexClassLoader using the content of {@code dex} and with a
* library path of {@code applicationLibPath}. The parent classloader is the boot
* classloader.
*
* @param dex The .dex file to be loaded.
* @param applicationLibPath Library search path of the new class loader.
*/
private static InMemoryDexClassLoader createLoaderWithLibPath(File dex, File applicationLibPath)
throws IOException {
return new InMemoryDexClassLoader(
new ByteBuffer[] { readFileToByteBufferIndirect(dex) },
applicationLibPath.toString(), null);
}
// ONE_DEX with direct ByteBuffer.
public void test_oneDexDirect_simpleUse() throws Exception {
String result = (String) createLoaderDirectAndCallMethod("test.Test1", "test", dex1);
assertSame("blort", result);
}
public void test_oneDexDirect_constructor() throws Exception {
createLoaderDirectAndCallMethod("test.TestMethods", "test_constructor", dex1);
}
public void test_oneDexDirect_callStaticMethod() throws Exception {
createLoaderDirectAndCallMethod("test.TestMethods", "test_callStaticMethod", dex1);
}
public void test_oneDexDirect_getStaticVariable() throws Exception {
createLoaderDirectAndCallMethod("test.TestMethods", "test_getStaticVariable", dex1);
}
public void test_oneDexDirect_callInstanceMethod() throws Exception {
createLoaderDirectAndCallMethod("test.TestMethods", "test_callInstanceMethod", dex1);
}
public void test_oneDexDirect_getInstanceVariable() throws Exception {
createLoaderDirectAndCallMethod("test.TestMethods", "test_getInstanceVariable", dex1);
}
// ONE_DEX with non-direct ByteBuffer.
public void test_oneDexIndirect_simpleUse() throws Exception {
String result = (String) createLoaderIndirectAndCallMethod("test.Test1", "test", dex1);
assertSame("blort", result);
}
public void test_oneDexIndirect_constructor() throws Exception {
createLoaderIndirectAndCallMethod("test.TestMethods", "test_constructor", dex1);
}
public void test_oneDexIndirect_callStaticMethod() throws Exception {
createLoaderIndirectAndCallMethod("test.TestMethods", "test_callStaticMethod", dex1);
}
public void test_oneDexIndirect_getStaticVariable() throws Exception {
createLoaderIndirectAndCallMethod("test.TestMethods", "test_getStaticVariable", dex1);
}
public void test_oneDexIndirect_callInstanceMethod() throws Exception {
createLoaderIndirectAndCallMethod("test.TestMethods", "test_callInstanceMethod", dex1);
}
public void test_oneDexIndirect_getInstanceVariable() throws Exception {
createLoaderIndirectAndCallMethod("test.TestMethods", "test_getInstanceVariable", dex1);
}
// TWO_DEX with direct ByteBuffer
public void test_twoDexDirect_simpleUse() throws Exception {
String result = (String) createLoaderDirectAndCallMethod("test.Test1", "test", dex1, dex2);
assertSame("blort", result);
}
public void test_twoDexDirect_constructor() throws Exception {
createLoaderDirectAndCallMethod("test.TestMethods", "test_constructor", dex1, dex2);
}
public void test_twoDexDirect_callStaticMethod() throws Exception {
createLoaderDirectAndCallMethod("test.TestMethods", "test_callStaticMethod", dex1, dex2);
}
public void test_twoDexDirect_getStaticVariable() throws Exception {
createLoaderDirectAndCallMethod("test.TestMethods", "test_getStaticVariable", dex1, dex2);
}
public void test_twoDexDirect_callInstanceMethod() throws Exception {
createLoaderDirectAndCallMethod("test.TestMethods", "test_callInstanceMethod", dex1, dex2);
}
public void test_twoDexDirect_getInstanceVariable() throws Exception {
createLoaderDirectAndCallMethod(
"test.TestMethods", "test_getInstanceVariable", dex1, dex2);
}
public void test_twoDexDirect_target2_static_method() throws Exception {
String result =
(String) createLoaderDirectAndCallMethod("test2.Target2", "frotz", dex1, dex2);
assertSame("frotz", result);
}
public void test_twoDexDirect_diff_constructor() throws Exception {
// NB Ordering dex2 then dex1 as classloader's are nested and
// each only supports a single DEX image. The
// test.TestMethods.test_diff* methods depend on dex2 hence
// ordering.
createLoaderDirectAndCallMethod("test.TestMethods", "test_diff_constructor", dex2, dex1);
}
public void test_twoDexDirect_diff_callStaticMethod() throws Exception {
// NB See comment in test_twoDexDirect_diff_constructor.
createLoaderDirectAndCallMethod(
"test.TestMethods", "test_diff_callStaticMethod", dex2, dex1);
}
public void test_twoDexDirect_diff_getStaticVariable() throws Exception {
// NB See comment in test_twoDexDirect_diff_constructor.
createLoaderDirectAndCallMethod(
"test.TestMethods", "test_diff_getStaticVariable", dex2, dex1);
}
public void test_twoDexDirect_diff_callInstanceMethod() throws Exception {
// NB See comment in test_twoDexDirect_diff_constructor.
createLoaderDirectAndCallMethod(
"test.TestMethods", "test_diff_callInstanceMethod", dex2, dex1);
}
public void test_twoDexDirect_diff_getInstanceVariable() throws Exception {
// NB See comment in test_twoDexDirect_diff_constructor.
createLoaderDirectAndCallMethod(
"test.TestMethods", "test_diff_getInstanceVariable", dex2, dex1);
}
public void testLibraryPath() throws IOException {
File applicationLibPath = new File(srcDir, "applicationLibPath");
File applicationLib = makeEmptyFile(applicationLibPath, "libtestlibpath.so");
InMemoryDexClassLoader classLoader = createLoaderWithLibPath(dex1, applicationLibPath);
String path = classLoader.findLibrary("testlibpath");
assertEquals(applicationLib.toString(), path);
}
public void testLibraryPathSearchOrder() throws IOException {
File systemLibPath = new File(srcDir, "systemLibPath");
File applicationLibPath = new File(srcDir, "applicationLibPath");
makeEmptyFile(systemLibPath, "libduplicated.so");
File applicationLib = makeEmptyFile(applicationLibPath, "libduplicated.so");
System.setProperty("java.library.path", systemLibPath.toString());
InMemoryDexClassLoader classLoader = createLoaderWithLibPath(dex1, applicationLibPath);
String path = classLoader.findLibrary("duplicated");
assertEquals(applicationLib.toString(), path);
}
public void testNullParent() throws IOException, ClassNotFoundException {
// Other tests set up InMemoryDexClassLoader with the system class loader
// as parent. Test that passing {@code null} works too (b/120603906).
InMemoryDexClassLoader classLoader = new InMemoryDexClassLoader(
new ByteBuffer[] { readFileToByteBufferIndirect(dex1) },
/* parent */ null);
// Try to load a class from the boot class loader.
Class<?> objectClass = classLoader.loadClass("java.lang.Object");
assertEquals(objectClass, Object.class);
// Try to load a class from this class loader.
classLoader.loadClass("test.TestMethods");
}
public void testNonZeroBufferOffsetDirect() throws IOException, ClassNotFoundException {
// Arbitrary amount of padding to prove a non-zero buffer position is supported.
int paddingBefore = 13;
InMemoryDexClassLoader classLoader = new InMemoryDexClassLoader(
new ByteBuffer[] { readFileToByteBufferDirect(dex1, paddingBefore) },
/* parent */ null);
classLoader.loadClass("test.TestMethods");
}
public void testNonZeroBufferOffsetIndirect() throws IOException, ClassNotFoundException {
// Arbitrary amount of padding to prove a non-zero buffer position is supported.
int paddingBefore = 13;
InMemoryDexClassLoader classLoader = new InMemoryDexClassLoader(
new ByteBuffer[] { readFileToByteBufferIndirect(dex1, paddingBefore) },
/* parent */ null);
classLoader.loadClass("test.TestMethods");
}
private static File makeEmptyFile(File directory, String name) throws IOException {
assertTrue(directory.mkdirs());
File result = new File(directory, name);
FileOutputStream stream = new FileOutputStream(result);
stream.close();
assertTrue(result.exists());
return result;
}
}