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