/* * 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; }