/*
* 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.
*/
import java.lang.ref.WeakReference;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
public class Main {
public static void main(String[] args) throws Exception {
try {
// Check if we're running dalvik or RI.
Class<?> class_loader_class = Class.forName("dalvik.system.PathClassLoader");
System.loadLibrary(args[0]);
} catch (ClassNotFoundException e) {
usingRI = true;
// Add expected JNI_OnLoad log line to match expected.txt.
System.out.println("JNI_OnLoad called");
}
testClearDexCache();
testMultiDex();
testRacyLoader();
testRacyLoader2();
testMisbehavingLoader();
testRacyMisbehavingLoader();
testRacyMisbehavingLoader2();
}
private static void testClearDexCache() throws Exception {
DelegatingLoader delegating_loader = createDelegatingLoader();
Class<?> helper = delegating_loader.loadClass("Helper1");
WeakReference<Class<?>> weak_test1 = wrapHelperGet(helper);
changeInner(delegating_loader);
clearResolvedTypes(helper);
Runtime.getRuntime().gc();
WeakReference<Class<?>> weak_test2 = wrapHelperGet(helper);
Runtime.getRuntime().gc();
Class<?> test1 = weak_test1.get();
if (test1 == null) {
System.out.println("test1 disappeared");
}
Class<?> test2 = weak_test2.get();
if (test2 == null) {
System.out.println("test2 disappeared");
}
if (test1 != test2) {
System.out.println("test1 != test2");
}
System.out.println("testClearDexCache done");
}
private static void testMultiDex() throws Exception {
DelegatingLoader delegating_loader = createDelegatingLoader();
Class<?> helper1 = delegating_loader.loadClass("Helper1");
WeakReference<Class<?>> weak_test1 = wrapHelperGet(helper1);
changeInner(delegating_loader);
Class<?> helper2 = delegating_loader.loadClass("Helper2");
WeakReference<Class<?>> weak_test2 = wrapHelperGet(helper2);
Runtime.getRuntime().gc();
Class<?> test1 = weak_test1.get();
if (test1 == null) {
System.out.println("test1 disappeared");
}
Class<?> test2 = weak_test2.get();
if (test2 == null) {
System.out.println("test2 disappeared");
}
if (test1 != test2) {
System.out.println("test1 != test2");
}
System.out.println("testMultiDex done");
}
private static void testMisbehavingLoader() throws Exception {
ClassLoader system_loader = ClassLoader.getSystemClassLoader();
DefiningLoader defining_loader = new DefiningLoader(system_loader);
MisbehavingLoader misbehaving_loader =
new MisbehavingLoader(system_loader, defining_loader);
Class<?> helper = misbehaving_loader.loadClass("Helper1");
try {
WeakReference<Class<?>> weak_test = wrapHelperGet(helper);
} catch (InvocationTargetException ite) {
String message = ite.getCause().getMessage();
if (usingRI && "Test".equals(message)) {
// Replace RI message with dalvik message to match expected.txt.
message = "Initiating class loader of type " +
misbehaving_loader.getClass().getName() +
" returned class Helper2 instead of Test.";
}
System.out.println(ite.getCause().getClass().getName() + ": " + message);
}
System.out.println("testMisbehavingLoader done");
}
private static void testRacyLoader() throws Exception {
final ClassLoader system_loader = ClassLoader.getSystemClassLoader();
final Thread[] threads = new Thread[4];
final Object[] results = new Object[threads.length];
final RacyLoader racy_loader = new RacyLoader(system_loader, threads.length);
final Class<?> helper1 = racy_loader.loadClass("Helper1");
skipVerification(helper1); // Avoid class loading during verification.
for (int i = 0; i != threads.length; ++i) {
final int my_index = i;
Thread t = new Thread() {
public void run() {
try {
Method get = helper1.getDeclaredMethod("get");
results[my_index] = get.invoke(null);
} catch (InvocationTargetException ite) {
results[my_index] = ite.getCause();
} catch (Throwable t) {
results[my_index] = t;
}
}
};
t.start();
threads[i] = t;
}
for (Thread t : threads) {
t.join();
}
dumpResultStats(results, 1);
System.out.println("testRacyLoader done");
}
private static void testRacyLoader2() throws Exception {
final ClassLoader system_loader = ClassLoader.getSystemClassLoader();
final Thread[] threads = new Thread[4];
final Object[] results = new Object[threads.length];
final RacyLoader racy_loader = new RacyLoader(system_loader, threads.length);
final Class<?> helper1 = racy_loader.loadClass("Helper1");
skipVerification(helper1); // Avoid class loading during verification.
final Class<?> helper3 = racy_loader.loadClass("Helper3");
skipVerification(helper3); // Avoid class loading during verification.
for (int i = 0; i != threads.length; ++i) {
final int my_index = i;
Thread t = new Thread() {
public void run() {
try {
Class<?> helper = (my_index < threads.length / 2) ? helper1 : helper3;
Method get = helper.getDeclaredMethod("get");
results[my_index] = get.invoke(null);
} catch (InvocationTargetException ite) {
results[my_index] = ite.getCause();
} catch (Throwable t) {
results[my_index] = t;
}
}
};
t.start();
threads[i] = t;
}
for (Thread t : threads) {
t.join();
}
dumpResultStats(results, 2);
System.out.println("testRacyLoader2 done");
}
private static void testRacyMisbehavingLoader() throws Exception {
final ClassLoader system_loader = ClassLoader.getSystemClassLoader();
final Thread[] threads = new Thread[4];
final Object[] results = new Object[threads.length];
final RacyMisbehavingLoader racy_loader =
new RacyMisbehavingLoader(system_loader, threads.length, false);
final Class<?> helper1 = racy_loader.loadClass("RacyMisbehavingHelper");
skipVerification(helper1); // Avoid class loading during verification.
for (int i = 0; i != threads.length; ++i) {
final int my_index = i;
Thread t = new Thread() {
public void run() {
try {
Method get = helper1.getDeclaredMethod("get");
results[my_index] = get.invoke(null);
} catch (InvocationTargetException ite) {
results[my_index] = ite.getCause();
} catch (Throwable t) {
results[my_index] = t;
}
}
};
t.start();
threads[i] = t;
}
for (Thread t : threads) {
t.join();
}
dumpResultStats(results, 1);
System.out.println("testRacyMisbehavingLoader done");
}
private static void testRacyMisbehavingLoader2() throws Exception {
final ClassLoader system_loader = ClassLoader.getSystemClassLoader();
final Thread[] threads = new Thread[4];
final Object[] results = new Object[threads.length];
final RacyMisbehavingLoader racy_loader =
new RacyMisbehavingLoader(system_loader, threads.length, true);
final Class<?> helper1 = racy_loader.loadClass("RacyMisbehavingHelper");
skipVerification(helper1); // Avoid class loading during verification.
for (int i = 0; i != threads.length; ++i) {
final int my_index = i;
Thread t = new Thread() {
public void run() {
try {
Method get = helper1.getDeclaredMethod("get");
results[my_index] = get.invoke(null);
} catch (InvocationTargetException ite) {
results[my_index] = ite.getCause();
} catch (Throwable t) {
results[my_index] = t;
}
}
};
t.start();
threads[i] = t;
}
for (Thread t : threads) {
t.join();
}
dumpResultStats(results, 1);
System.out.println("testRacyMisbehavingLoader2 done");
}
private static void dumpResultStats(Object[] results, int expected_unique) throws Exception {
int throwables = 0;
int classes = 0;
int unique_classes = 0;
for (int i = 0; i != results.length; ++i) {
Object r = results[i];
if (r instanceof Throwable) {
++throwables;
System.out.println(((Throwable) r).getMessage());
} else if (isClassPair(r)) {
printPair(r);
Object ref = getSecond(r);
++classes;
++unique_classes;
for (int j = 0; j != i; ++j) {
Object rj = results[j];
if (isClassPair(results[j]) && getSecond(results[j]) == ref) {
--unique_classes;
break;
}
}
}
}
System.out.println("total: " + results.length);
System.out.println(" throwables: " + throwables);
System.out.println(" classes: " + classes
+ " (" + unique_classes + " unique)");
if (expected_unique != unique_classes) {
System.out.println("MISMATCH with expected_unique: " + expected_unique);
ArrayList<Class<?>> list = new ArrayList<Class<?>>();
for (int i = 0; i != results.length; ++i) {
Object r = results[i];
if (isClassPair(r)) {
list.add(getSecond(r));
}
}
nativeDumpClasses(list.toArray());
}
}
private static DelegatingLoader createDelegatingLoader() {
ClassLoader system_loader = ClassLoader.getSystemClassLoader();
DefiningLoader defining_loader = new DefiningLoader(system_loader);
return new DelegatingLoader(system_loader, defining_loader);
}
private static void changeInner(DelegatingLoader delegating_loader) {
ClassLoader system_loader = ClassLoader.getSystemClassLoader();
DefiningLoader defining_loader = new DefiningLoader(system_loader);
delegating_loader.resetDefiningLoader(defining_loader);
}
private static WeakReference<Class<?>> wrapHelperGet(Class<?> helper) throws Exception {
Method get = helper.getDeclaredMethod("get");
Object pair = get.invoke(null);
printPair(pair);
return new WeakReference<Class<?>>(getSecond(pair));
}
private static void printPair(Object pair) throws Exception {
Method print = pair.getClass().getDeclaredMethod("print");
print.invoke(pair);
}
private static Class<?> getSecond(Object pair) throws Exception {
Field second = pair.getClass().getDeclaredField("second");
return (Class<?>) second.get(pair);
}
private static boolean isClassPair(Object r) {
return r != null && r.getClass().getName().equals("ClassPair");
}
public static void clearResolvedTypes(Class<?> c) {
if (!usingRI) {
nativeClearResolvedTypes(c);
}
}
// Skip verification of a class on ART. Verification can cause classes to be loaded
// while holding a lock on the class being verified and holding that lock can interfere
// with the intent of the "racy" tests. In these tests we're waiting in the loadClass()
// for all the tested threads to synchronize and they cannot reach that point if they
// are waiting for the class lock on ClassLinker::InitializeClass(Helper1/Helper3).
public static void skipVerification(Class<?> c) {
if (!usingRI) {
nativeSkipVerification(c);
}
}
public static native void nativeClearResolvedTypes(Class<?> c);
public static native void nativeSkipVerification(Class<?> c);
public static native void nativeDumpClasses(Object[] array);
static boolean usingRI = false;
}