blob: ba7c23257fa1f9a0d19aa2cc0e83070d891c33ab [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 static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import dalvik.system.BaseDexClassLoader;
import dalvik.system.DelegateLastClassLoader;
import dalvik.system.PathClassLoader;
import java.lang.reflect.Method;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import libcore.io.Streams;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
@RunWith(JUnit4.class)
public final class BaseDexClassLoaderTest {
private static class Reporter implements BaseDexClassLoader.Reporter {
public final Map<String, String> loadedDexMapping = new HashMap<String, String>();
@Override
public void report(Map<String, String> contextMap) {
loadedDexMapping.putAll(contextMap);
}
void reset() {
loadedDexMapping.clear();
}
}
private ClassLoader pcl;
private File jar;
private File jar2;
private Reporter reporter;
// For resources that we will load in this test. We're re-using parent.jar and child.jar
// from DelegateLastClassLoaderTest for convenience.
private Map<String, File> resourcesMap;
@Before
public void setupResourcesMap() throws Exception {
resourcesMap = ClassLoaderTestSupport.setupAndCopyResources(
Arrays.asList("parent.jar", "child.jar"));
}
@After
public void cleanupResourcesMap() throws Exception {
ClassLoaderTestSupport.cleanUpResources(resourcesMap);
}
@Before
public void extractTestJars() throws Exception {
// Extract loading-test.jar from the resource.
pcl = BaseDexClassLoaderTest.class.getClassLoader();
jar = File.createTempFile("loading-test", ".jar");
try (InputStream in = pcl.getResourceAsStream("dalvik/system/loading-test.jar");
FileOutputStream out = new FileOutputStream(jar)) {
Streams.copy(in, out);
}
jar2 = File.createTempFile("loading-test2", ".jar");
try (InputStream in = pcl.getResourceAsStream("dalvik/system/loading-test2.jar");
FileOutputStream out = new FileOutputStream(jar2)) {
Streams.copy(in, out);
}
}
@Before
public void registerReporter() {
reporter = new Reporter();
BaseDexClassLoader.setReporter(reporter);
}
@After
public void unregisterReporter() {
BaseDexClassLoader.setReporter(null);
}
@After
public void deleteTestJars() throws Exception {
assertTrue(jar.delete());
assertTrue(jar2.delete());
}
@Test
public void testReporting() throws Exception {
// Load the jar file using a PathClassLoader.
BaseDexClassLoader cl1 = new PathClassLoader(jar.getPath(),
ClassLoader.getSystemClassLoader());
// Verify the reported data. The only class loader context should be two empty PCLs
// (the system class loader is a PCL)
assertEquals(Map.of(jar.getPath(), "PCL[];PCL[]"), reporter.loadedDexMapping);
}
@Test
public void testReportingUnknownLoader() throws Exception {
// Add an unknown classloader between cl1 and the system
ClassLoader unknownLoader = new ClassLoader(ClassLoader.getSystemClassLoader()) {};
BaseDexClassLoader cl1 = new PathClassLoader(jar.getPath(), unknownLoader);
// Verify the dex path gets reported, but with no class loader context due to the foreign
// class loader.
assertEquals(Map.of(jar.getPath(), "=UnsupportedClassLoaderContext="),
reporter.loadedDexMapping);
}
@Test
public void testNoReportingAfterResetting() throws Exception {
BaseDexClassLoader cl1 = new PathClassLoader(jar.getPath(),
ClassLoader.getSystemClassLoader());
assertEquals(Map.of(jar.getPath(), "PCL[];PCL[]"), reporter.loadedDexMapping);
// Check we don't report after the reporter is unregistered.
unregisterReporter();
reporter.reset();
// Load the jar file using another PathClassLoader.
BaseDexClassLoader cl2 = new PathClassLoader(jar.getPath(), pcl);
// Verify nothing reported
assertEquals(Map.<String, String>of(), reporter.loadedDexMapping);
}
@Test
public void testReporting_multipleJars() throws Exception {
// Load the jar file using a PathClassLoader.
BaseDexClassLoader cl1 = new PathClassLoader(
String.join(File.pathSeparator, jar.getPath(), jar2.getPath()),
ClassLoader.getSystemClassLoader());
// The first class loader context should be two empty PCLs (the system class loader is a
// PCL) and the second should be the same, but the bottom classloader contains the first
// jar.
assertEquals(Map.of(jar.getPath(), "PCL[];PCL[]",
jar2.getPath(), "PCL[" + jar.getPath() + "];PCL[]"),
reporter.loadedDexMapping);
}
@Test
public void testReporting_withSharedLibraries() throws Exception {
final ClassLoader parent = ClassLoader.getSystemClassLoader();
final ClassLoader sharedLoaders[] = new ClassLoader[] {
new PathClassLoader(jar2.getPath(), /* librarySearchPath */ null, parent),
};
// Reset so we don't get load reports from creating the shared library CL
reporter.reset();
BaseDexClassLoader bdcl = new PathClassLoader(jar.getPath(), null, parent, sharedLoaders);
// Verify the loaded dex file contains jar2 encoded as a shared library in its encoded class
// loader context.
assertEquals(
Map.of(jar.getPath(), "PCL[]{PCL[" + jar2.getPath() + "];PCL[]};PCL[]"),
reporter.loadedDexMapping);
}
@Test
public void testReporting_multipleJars_withSharedLibraries() throws Exception {
final ClassLoader parent = ClassLoader.getSystemClassLoader();
final String sharedJarPath = resourcesMap.get("parent.jar").getAbsolutePath();
final ClassLoader sharedLoaders[] = new ClassLoader[] {
new PathClassLoader(sharedJarPath, /* librarySearchPath */ null, parent),
};
// Reset so we don't get load reports from creating the shared library CL
reporter.reset();
BaseDexClassLoader bdcl = new PathClassLoader(
String.join(File.pathSeparator, jar.getPath(), jar2.getPath()),
null, parent, sharedLoaders);
final String contextSuffix = "{PCL[" + sharedJarPath + "];PCL[]};PCL[]";
assertEquals(Map.of(jar.getPath(), "PCL[]" + contextSuffix,
jar2.getPath(), "PCL[" + jar.getPath() + "]" + contextSuffix),
reporter.loadedDexMapping);
}
@Test
public void testReporting_emptyPath() throws Exception {
BaseDexClassLoader cl1 = new PathClassLoader("", ClassLoader.getSystemClassLoader());
assertEquals(Map.<String, String>of(), reporter.loadedDexMapping);
}
/* package */ static List<String> readResources(ClassLoader cl, String resourceName)
throws Exception {
Enumeration<URL> resources = cl.getResources(resourceName);
List<String> contents = new ArrayList<>();
while (resources.hasMoreElements()) {
URL url = resources.nextElement();
try (InputStream is = url.openStream()) {
byte[] bytes = Streams.readFully(is);
contents.add(new String(bytes, StandardCharsets.UTF_8));
}
}
return contents;
}
/* package */ static String readResource(ClassLoader cl, String resourceName) throws Exception {
InputStream in = cl.getResourceAsStream(resourceName);
if (in == null) {
return null;
}
byte[] contents = Streams.readFully(in);
return new String(contents, StandardCharsets.UTF_8);
}
private void checkResources(ClassLoader loader) throws Exception {
List<String> resources = readResources(loader, "resource.txt");
assertEquals(2, resources.size());
assertTrue(resources.contains("parent"));
assertTrue(resources.contains("child"));
resources = readResources(loader, "resource2.txt");
assertEquals(1, resources.size());
assertEquals("parent2", resources.get(0));
}
@Test
public void testGetResourceSharedLibraries1() throws Exception {
File parentPath = resourcesMap.get("parent.jar");
File childPath = resourcesMap.get("child.jar");
assertTrue(parentPath != null);
assertTrue(childPath != null);
ClassLoader parent = Object.class.getClassLoader();
ClassLoader[] sharedLibraries = {
new PathClassLoader(parentPath.getAbsolutePath(), null, parent),
new PathClassLoader(childPath.getAbsolutePath(), null, parent),
};
// PCL[]{PCL[parent.jar]#PCL[child.jar]}
ClassLoader loader = new PathClassLoader("", null, parent, sharedLibraries);
assertEquals("parent", readResource(loader, "resource.txt"));
checkResources(loader);
// DLC[]{PCL[parent.jar]#PCL[child.jar]}
loader = new DelegateLastClassLoader("", null, parent, sharedLibraries);
assertEquals("parent", readResource(loader, "resource.txt"));
checkResources(loader);
}
@Test
public void testGetResourceSharedLibraries2() throws Exception {
File parentPath = resourcesMap.get("parent.jar");
File childPath = resourcesMap.get("child.jar");
assertTrue(parentPath != null);
assertTrue(childPath != null);
ClassLoader parent = Object.class.getClassLoader();
ClassLoader[] sharedLibraries = {
new PathClassLoader(childPath.getAbsolutePath(), null, parent),
new PathClassLoader(parentPath.getAbsolutePath(), null, parent),
};
// PCL[]{PCL[child.jar]#PCL[parent.jar]}
ClassLoader loader = new PathClassLoader("", null, parent, sharedLibraries);
assertEquals("child", readResource(loader, "resource.txt"));
checkResources(loader);
// DLC[]{PCL[child.jar]#PCL[parent.jar]}
loader = new DelegateLastClassLoader("", null, parent, sharedLibraries);
assertEquals("child", readResource(loader, "resource.txt"));
checkResources(loader);
}
@Test
public void testGetResourceSharedLibraries3() throws Exception {
File parentPath = resourcesMap.get("parent.jar");
File childPath = resourcesMap.get("child.jar");
assertTrue(parentPath != null);
assertTrue(childPath != null);
ClassLoader parent = Object.class.getClassLoader();
ClassLoader[] sharedLibraryLevel2 = {
new PathClassLoader(parentPath.getAbsolutePath(), null, parent),
};
ClassLoader[] sharedLibraryLevel1 = {
new PathClassLoader(childPath.getAbsolutePath(), null, parent, sharedLibraryLevel2),
};
// PCL[]{PCL[child.jar]{PCL[parent.jar]}}
ClassLoader loader = new PathClassLoader("", null, parent, sharedLibraryLevel1);
assertEquals("parent", readResource(loader, "resource.txt"));
checkResources(loader);
// DLC[]{PCL[child.jar]{PCL[parent.jar]}}
loader = new DelegateLastClassLoader("", null, parent, sharedLibraryLevel1);
assertEquals("parent", readResource(loader, "resource.txt"));
checkResources(loader);
}
@Test
public void testGetResourceSharedLibraries4() throws Exception {
File parentPath = resourcesMap.get("parent.jar");
File childPath = resourcesMap.get("child.jar");
assertTrue(parentPath != null);
assertTrue(childPath != null);
ClassLoader parent = Object.class.getClassLoader();
ClassLoader[] sharedLibraryLevel2 = {
new PathClassLoader(childPath.getAbsolutePath(), null, parent),
};
ClassLoader[] sharedLibraryLevel1 = {
new PathClassLoader(parentPath.getAbsolutePath(), null, parent, sharedLibraryLevel2),
};
// PCL[]{PCL[parent.jar]{PCL[child.jar]}}
ClassLoader loader = new PathClassLoader("", null, parent, sharedLibraryLevel1);
assertEquals("child", readResource(loader, "resource.txt"));
checkResources(loader);
// DLC[]{PCL[parent.jar]{PCL[child.jar]}}
loader = new DelegateLastClassLoader("", null, parent, sharedLibraryLevel1);
assertEquals("child", readResource(loader, "resource.txt"));
checkResources(loader);
}
@Test
public void testGetResourceSharedLibraries5() throws Exception {
File parentPath = resourcesMap.get("parent.jar");
File childPath = resourcesMap.get("child.jar");
assertTrue(parentPath != null);
assertTrue(childPath != null);
ClassLoader parentParent = Object.class.getClassLoader();
ClassLoader parent = new PathClassLoader(parentPath.getAbsolutePath(), null, parentParent);
ClassLoader[] sharedLibrary = {
new PathClassLoader(childPath.getAbsolutePath(), null, parentParent),
};
// PCL[]{PCL[child.jar]};PCL[parent.jar]
ClassLoader pathLoader = new PathClassLoader("", null, parent, sharedLibrary);
// Check that the parent was queried first.
assertEquals("parent", readResource(pathLoader, "resource.txt"));
// DLC[]{PCL[child.jar]};PCL[parent.jar]
ClassLoader delegateLast = new DelegateLastClassLoader("", null, parent, sharedLibrary);
// Check that the shared library was queried first.
assertEquals("child", readResource(delegateLast, "resource.txt"));
}
@Test
public void testAddDexPath() throws Exception {
BaseDexClassLoader bdcl = new PathClassLoader(jar.getPath(),
ClassLoader.getSystemClassLoader());
Class test1Class = bdcl.loadClass("test.Test1");
Method testMethod = test1Class.getMethod("test", (Class[]) null);
String testResult = (String) testMethod.invoke(null, (Object[]) null);
assertEquals("blort", testResult);
// Just for completeness sake, prove that we were able to load
// the class only after addDexPath was called.
try {
bdcl.loadClass("test2.Target2");
fail();
} catch (ClassNotFoundException expected) {
}
bdcl.addDexPath(jar2.getPath());
Class target2Class = bdcl.loadClass("test2.Target2");
Method frotzMethod = target2Class.getMethod("frotz", (Class[]) null);
String frotzResult = (String) frotzMethod.invoke(null, (Object[]) null);
assertEquals("frotz", frotzResult);
}
}