1 /*
   2  * Copyright (c) 2014, 2018, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 package gc.g1.unloading.loading;
  24 
  25 import gc.g1.unloading.ExecutionTask;
  26 import gc.g1.unloading.bytecode.*;
  27 //import gc.g1.unloading.check.*;
  28 import gc.g1.unloading.check.Assertion;
  29 import gc.g1.unloading.check.ClassAssertion;
  30 import gc.g1.unloading.check.PhantomizedAssertion;
  31 
  32 import gc.g1.unloading.check.FinalizedAssertion;
  33 import gc.g1.unloading.check.PhantomizationServiceThread;
  34 import gc.g1.unloading.check.cleanup.UnusedThreadKiller;
  35 import gc.g1.unloading.classloaders.DoItYourselfClassLoader;
  36 import gc.g1.unloading.classloaders.FinalizableClassloader;
  37 import gc.g1.unloading.classloaders.JNIClassloader;
  38 import gc.g1.unloading.classloaders.ReflectionClassloader;
  39 import gc.g1.unloading.configuration.ClassloadingMethod;
  40 import gc.g1.unloading.configuration.KeepRefMode;
  41 import gc.g1.unloading.configuration.TestConfiguration;
  42 import gc.g1.unloading.keepref.*;
  43 import nsk.share.test.ExecutionController;
  44 import sun.hotspot.WhiteBox;
  45 import jdk.internal.misc.Unsafe;
  46 
  47 import java.lang.ref.*;
  48 import java.lang.reflect.Field;
  49 import java.lang.reflect.InvocationTargetException;
  50 import java.lang.reflect.Method;
  51 import java.util.Collection;
  52 import java.util.LinkedList;
  53 import java.util.Random;
  54 
  55 /**
  56  * This helper performs dirty job: loads classes, instantiate objects, performs redefinition etc...
  57  */
  58 public class ClassLoadingHelper {
  59 
  60     private static final int NATIVE_VERBOSITY = 2;
  61 
  62     private static final Object[] NO_CP_PATCHES = new Object[0];
  63 
  64     private static BytecodeFactory bf;
  65 
  66     private ExecutionController executionController;
  67 
  68     private PhantomizationServiceThread phantomizationServiceThread;
  69 
  70     private Random random;
  71 
  72     private TestConfiguration configuration;
  73 
  74     /**
  75      * Constructor that creates instance of helper. All arguments are self-explaining.
  76      * @param executionController
  77      * @param randomSeed
  78      * @param testConfiguration
  79      */
  80     public ClassLoadingHelper(ExecutionController executionController,
  81                               long randomSeed, TestConfiguration testConfiguration) {
  82         random = new Random(randomSeed);
  83         this.executionController = executionController;
  84         this.configuration = testConfiguration;
  85 
  86         phantomizationServiceThread = new PhantomizationServiceThread(executionController);
  87         Thread thread = new Thread(phantomizationServiceThread);
  88         thread.setDaemon(true);
  89         thread.start();
  90 
  91         if (configuration.isInMemoryCompilation() && !configuration.isHumongousClass() && !(configuration.getKeepRefMode() == KeepRefMode.THREAD_ITSELF)) {
  92             bf = new BytecodeGeneratorFactory(random.nextLong());
  93         } else {
  94             if (configuration.isHumongousClass()) {
  95                 bf = new BytecodeMutatorFactory(HumongousTemplateClass.class.getName());
  96             } else if (configuration.getKeepRefMode() == KeepRefMode.THREAD_ITSELF) {
  97                 bf = new BytecodeMutatorFactory(ThreadTemplateClass.class.getName());
  98             } else {
  99                 bf = new BytecodeMutatorFactory();
 100             }
 101         }
 102     }
 103 
 104     /**
 105      * Load class that's supposed to live. Method returns collection of assertions to check it will live.
 106      * @param className_
 107      * @return
 108      */
 109     public Collection<Assertion> loadClassThatGonnaLive(String className_) {
 110         Bytecode kit = bf.createBytecode(className_);
 111         String className = kit.getClassName();
 112         byte[] bytecode = kit.getBytecode();
 113         Class<?> clazz = loadClass(className, bytecode);
 114         Object object = instantiateObject(clazz);
 115         Object referenceToKeep = configuration.getWhatToKeep().decideUponRefToKeep(clazz, clazz.getClassLoader(), object);
 116 
 117         redefineIfNeeded(bytecode, clazz);
 118 
 119         warmUpClassIfNeeded(object);
 120         Assertion assertion;
 121         // The JVM prepends the host class's package to the anonymous class name.
 122         if (configuration.getClassloadingMethod() != ClassloadingMethod.ANONYMOUS_CLASSLOADER) {
 123             assertion = new ClassAssertion(className, true);
 124         } else {
 125             assertion = new ClassAssertion("gc/g1/unloading/loading/" + className, true);
 126         }
 127         switch (configuration.getKeepRefMode()) {
 128             case STRONG_REFERENCE:
 129                 assertion.keepLink(referenceToKeep);
 130                 break;
 131             case SOFT_REFERENCE:
 132                 assertion.keepLink(new SoftReference<Object>(referenceToKeep));
 133                 break;
 134             case STATIC_FIELD:
 135                 RefHolder holder1 = new InStaticFieldHolder();
 136                 assertion.keepLink(holder1.hold(referenceToKeep));
 137                 break;
 138             case STACK_LOCAL:
 139                 RefHolder holder2 = new InStackLocalHolder(); // UnusedThreadKiller
 140                 assertion.keepLink(holder2.hold(referenceToKeep));
 141                 break;
 142             case THREAD_FIELD:
 143                 RefHolder holder3 = new InThreadFieldHolder(); // UnusedThreadKiller
 144                 assertion.keepLink(holder3.hold(referenceToKeep));
 145                 break;
 146             case THREAD_ITSELF:
 147                 Thread objectThread = (Thread) object;
 148                 objectThread.setDaemon(true);
 149                 objectThread.start();
 150                 assertion.keepLink(new UnusedThreadKiller(objectThread.getId())); // UnusedThreadKiller
 151                 break;
 152             case STATIC_FIELD_OF_ROOT_CLASS:
 153                 RefHolder holder4 = new NullClassloaderHolder(random.nextLong());
 154                 Object keep = holder4.hold(referenceToKeep);
 155                 if (keep != null) {
 156                     assertion.keepLink(keep);
 157                 }
 158                 break;
 159             case JNI_GLOBAL_REF:
 160                 JNIGlobalRefHolder holder5 = new JNIGlobalRefHolder();
 161                 assertion.keepLink(holder5.hold(referenceToKeep));
 162                 break;
 163             case JNI_LOCAL_REF:
 164                 JNILocalRefHolder holder6 = new JNILocalRefHolder();
 165                 assertion.keepLink(holder6.hold(referenceToKeep));
 166                 break;
 167         }
 168 
 169         Collection<Assertion> returnValue = new LinkedList<>();
 170         returnValue.add(assertion);
 171         return returnValue;
 172     }
 173 
 174     /**
 175      * Load class that's supposed to be unloaded. Method returns collection of assertions to check it will be unloaded.
 176      * @param className_
 177      * @return
 178      */
 179     public Collection<Assertion> loadClassThatGonnaDie(String className_) {
 180         Collection<Assertion> returnValue = new LinkedList<>();
 181         Bytecode kit = bf.createBytecode(className_);
 182         String className = kit.getClassName();
 183         byte[] bytecode = kit.getBytecode();
 184         Class<?> clazz = loadClass(className, bytecode);
 185         FinalizableClassloader cl = null;
 186         if (clazz.getClassLoader() instanceof FinalizableClassloader) {
 187             cl = (FinalizableClassloader) clazz.getClassLoader();
 188         }
 189         Object object = instantiateObject(clazz);
 190         Object referenceToKeep = configuration.getWhatToKeep().decideUponRefToKeep(clazz, clazz.getClassLoader(), object);
 191 
 192         redefineIfNeeded(bytecode, clazz);
 193 
 194         warmUpClassIfNeeded(object);
 195         Assertion assertion;
 196         // The JVM prepends the host class's package to the anonymous class name.
 197         if (configuration.getClassloadingMethod() != ClassloadingMethod.ANONYMOUS_CLASSLOADER) {
 198             assertion = new ClassAssertion(className, false);
 199         } else {
 200             assertion = new ClassAssertion("gc/g1/unloading/loading/" + className, false);
 201         }
 202         switch (configuration.getReleaseRefMode()) {
 203             case WEAK:
 204                 assertion.keepLink(new WeakReference<Object>(referenceToKeep));
 205                 break;
 206             case PHANTOM:
 207                 final ReferenceQueue queue = new ReferenceQueue<Object>();
 208                 assertion.keepLink(new PhantomReference<Object>(referenceToKeep, queue));
 209                 new Thread(new ReferenceCleaningThread(executionController, queue)).start();
 210                 break;
 211         }
 212         returnValue.add(assertion);
 213 
 214         if (cl != null) {
 215             // Check that classloader will be finalized
 216             FinalizedAssertion finalizedAssertion = new FinalizedAssertion();
 217             cl.setFinalizedAssertion(finalizedAssertion);
 218             returnValue.add(finalizedAssertion);
 219 
 220             // Check that classloader will be phantomized
 221             PhantomizedAssertion phantomizedAssertion = new PhantomizedAssertion();
 222             PhantomReference phantomReference = new PhantomReference<Object>(cl, phantomizationServiceThread.getQueue());
 223             phantomizationServiceThread.add(phantomReference, phantomizedAssertion);
 224             returnValue.add(phantomizedAssertion);
 225         }
 226         return returnValue;
 227     }
 228 
 229     private void redefineIfNeeded(byte[] bytecode, Class<?> clazz) {
 230         if (configuration.isRedefineClasses()) {
 231             BytecodePatcher.patch(bytecode);
 232             makeRedefinition(NATIVE_VERBOSITY, clazz, bytecode);
 233 
 234             // This will call class's method
 235             instantiateObject(clazz);
 236         }
 237     }
 238 
 239     private Class<?> loadClass(String className, byte[] bytecode) {
 240         try {
 241             switch (configuration.getClassloadingMethod()) {
 242                 case PLAIN:
 243                     DoItYourselfClassLoader loader1 = new DoItYourselfClassLoader();
 244                     return loader1.defineClass(className, bytecode);
 245                 case REFLECTION:
 246                     return Class.forName(className, true, new ReflectionClassloader(bytecode, className));
 247                 case JNI:
 248                     return JNIClassloader.loadThroughJNI(className, bytecode);
 249                 case ANONYMOUS_CLASSLOADER:
 250                     return getUnsafe().defineAnonymousClass(ClassLoadingHelper.class, bytecode, NO_CP_PATCHES);
 251             }
 252             return null;
 253         } catch (ClassNotFoundException e) {
 254             throw new RuntimeException("Test bug!", e);
 255         }
 256     }
 257 
 258     private Object instantiateObject(Class<?> clazz) {
 259         try {
 260             Object object = clazz.newInstance();
 261 
 262             // Call method just for fun
 263             for (Method m : clazz.getMethods()) {
 264                 if (m.getName().equals("main")) {
 265                     m.invoke(object);
 266                 }
 267             }
 268             return object;
 269         } catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
 270             throw new RuntimeException("Test bug!", e);
 271         }
 272     }
 273 
 274     private void warmUpClassIfNeeded(Object object) {
 275         if (configuration.getCompilationLevel() < 1 || configuration.getCompilationNumber() == 0) {
 276             return;
 277         }
 278         Method m = null;
 279         for (Method method : object.getClass().getMethods()) {
 280             if (method.getName().equalsIgnoreCase("methodForCompilation")) {
 281                 m = method;
 282             }
 283         }
 284         WhiteBox wb = WhiteBox.getWhiteBox();
 285         if (!wb.isMethodCompilable(m)) {
 286             throw new RuntimeException("Test bug! Method occured to be not compilable. Requires investigation.");
 287         }
 288 
 289         for (int i = configuration.getCompilationNumber(); i >= 0 && executionController.continueExecution(); i--) {
 290             if (!wb.isMethodCompilable(m, configuration.getCompilationLevel())) {
 291               continue;
 292             }
 293             wb.enqueueMethodForCompilation(m, configuration.getCompilationLevel());
 294             while (!wb.isMethodCompiled(m) && executionController.continueExecution()) {
 295                 sleep(50);
 296                 try {
 297                     m.invoke(object, new Object());
 298                 } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
 299                     throw new RuntimeException("Something went wrong while compilation", e);
 300                 }
 301             }
 302             if (i > 0) {
 303                 wb.deoptimizeMethod(m);
 304             }
 305         }
 306     }
 307 
 308     native static int makeRedefinition0(int verbose, Class<?> redefClass, byte[] classBytes);
 309 
 310     private static void makeRedefinition(int verbose, Class<?> redefClass, byte[] classBytes) {
 311         new LibLoader().hashCode();
 312         if (makeRedefinition0(verbose, redefClass, classBytes) != 0) {
 313             throw new RuntimeException("Test bug: native method \"makeRedefinition\" return nonzero");
 314         }
 315     }
 316 
 317     private static void sleep(long millis) {
 318         try {
 319             Thread.sleep(millis);
 320         } catch (InterruptedException e) {
 321             throw new RuntimeException("Got InterruptedException while sleeping.", e);
 322         }
 323     }
 324 
 325     private static Unsafe getUnsafe() {
 326         return Unsafe.getUnsafe();
 327     }
 328 
 329 }
 330 
 331 class ReferenceCleaningThread extends ExecutionTask {
 332 
 333     private ReferenceQueue<?> queue;
 334 
 335     public ReferenceCleaningThread(ExecutionController executionController, ReferenceQueue<?> queue) {
 336         super(executionController);
 337         this.queue = queue;
 338     }
 339 
 340     @Override
 341     protected void task() throws Exception {
 342         Reference<?> ref = queue.remove(100);
 343         if (ref != null) {
 344             ref.clear();
 345             return;
 346         }
 347     }
 348 
 349 }