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