1 /*
   2  * Copyright (c) 2013, 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 
  24 //package nsk.jvmti.RedefineClasses.StressRedefine;
  25 package nsk.jvmti.RedefineClasses;
  26 
  27 
  28 import java.lang.reflect.InvocationTargetException;
  29 import java.lang.reflect.Method;
  30 import java.net.URL;
  31 import java.net.URLClassLoader;
  32 import java.util.HashMap;
  33 import java.util.LinkedList;
  34 import java.util.List;
  35 import java.util.Map;
  36 import java.util.Random;
  37 
  38 import nsk.share.TestFailure;
  39 import nsk.share.gc.GCTestBase;
  40 import nsk.share.test.ExecutionController;
  41 import nsk.share.test.Stresser;
  42 import nsk.share.test.Tests;
  43 
  44 import vm.share.InMemoryJavaCompiler;
  45 
  46 /**
  47  * There is a data structure named "dictionary" in class BlockFreelist. It stores
  48  * information about free memory blocks for further reusing. Allocation of new block goes
  49  * from dictionary only if dictionary is fat enough. (At the moment of test creation this limit is 64K.)
  50  *
  51  * This tests stresses dictionary as other test metaspace/StressDictionary does, but instead of
  52  * failing classloading this test leverages redefineClass method from jvmti.
  53  */
  54 public class StressRedefine extends GCTestBase {
  55     private static int staticMethodCallersNumber = 10;
  56     private static int nonstaticMethodCallersNumber = 10;
  57     private static int redefiningThreadsNumber = 40;
  58     private static double corruptingBytecodeProbability = .75;
  59 
  60     private static volatile Class<?> myClass;
  61     private static ExecutionController stresser;
  62     private static String[] args;
  63 
  64     private static byte[] bytecode;
  65 
  66     // This is random generator used for generating seeds for other Randoms. Setting seed
  67     // from command line sets seed for this random.
  68     static Random seedGenerator;
  69 
  70     static {
  71         try {
  72             System.loadLibrary("stressRedefine");
  73         } catch (UnsatisfiedLinkError e) {
  74             System.err.println("Could not load stressRedefine library");
  75             System.err.println("java.library.path:" +
  76                 System.getProperty("java.library.path"));
  77             throw e;
  78         }
  79     }
  80 
  81     native static int makeRedefinition(int verbose, Class<?> redefClass, byte[] classBytes);
  82 
  83     public static void main(String[] args) {
  84         StressRedefine.args = args;
  85         Tests.runTest(new StressRedefine(), args);
  86     }
  87 
  88     @Override
  89     public void run() {
  90         seedGenerator = new Random(runParams.getSeed());
  91         GenerateSourceHelper.setRandom(new Random(seedGenerator.nextLong()));
  92         stresser = new Stresser(args);
  93 
  94         for (int i = 0; i < args.length; i++ ) {
  95             if ("-staticMethodCallersNumber".equals(args[i])) {
  96                 staticMethodCallersNumber = Integer.parseInt(args[i + 1]);
  97             } else if ("-nonstaticMethodCallersNumber".equals(args[i])) {
  98                 nonstaticMethodCallersNumber = Integer.parseInt(args[i + 1]);
  99             } else if ("-redefiningThreadsNumber".equals(args[i])) {
 100                 redefiningThreadsNumber = Integer.parseInt(args[i + 1]);
 101             } else if ("-corruptingBytecodeProbability".equals(args[i])) {
 102                 corruptingBytecodeProbability = Double.parseDouble(args[i + 1]);
 103             }
 104         }
 105 
 106         //Dynamic attach if required
 107         nsk.share.jvmti.JVMTITest.commonInit(args);
 108 
 109         new StressRedefine().runIt();
 110     }
 111 
 112     private static void runMethod(Random random, String name) {
 113         while (stresser.continueExecution()) {
 114             try {
 115                 // Just for fun we transfer parameters to method
 116                 Object res = myClass.getMethod(name, double.class, int.class, Object.class)
 117                                          .invoke(null, random.nextDouble(), random.nextInt(), new Object());
 118              } catch (IllegalArgumentException | InvocationTargetException
 119                      | IllegalAccessException | NoSuchMethodException e) {
 120                  // It's okay to get exception here since we are corrupting bytecode and can't expect
 121                  // class to work properly.
 122                  System.out.println("Got expected exception: " + e.toString());
 123              }
 124         }
 125     }
 126 
 127     private static class StaticMethodCaller implements Runnable {
 128         private Random random;
 129         public StaticMethodCaller() {random = new Random(seedGenerator.nextLong());}
 130 
 131         @Override
 132         public void run() {
 133             runMethod(random, GenerateSourceHelper.STATIC_METHOD_NAME);
 134         }
 135     }
 136 
 137     private static class NonstaticMethodCaller implements Runnable {
 138         private Random random;
 139         public NonstaticMethodCaller() {random = new Random(seedGenerator.nextLong());}
 140 
 141         @Override
 142         public void run() {
 143             runMethod(random, GenerateSourceHelper.NONSTATIC_METHOD_NAME);
 144         }
 145     }
 146 
 147     private static class Worker implements Runnable {
 148         private Random random;
 149         public Worker() {random = new Random(seedGenerator.nextLong());}
 150 
 151         @Override
 152         public void run() {
 153             while (stresser.continueExecution()) {
 154                 byte[] badBytecode = bytecode.clone();
 155                 if (random.nextDouble() < corruptingBytecodeProbability) {
 156                     badBytecode[random.nextInt(bytecode.length)] = 42;
 157                 }
 158                 makeRedefinition(2, myClass, badBytecode);
 159             }
 160         }
 161     }
 162 
 163     private void runIt() {
 164         myClass = new DefiningClassLoader().defineClass(generateAndCompile());
 165         stresser.start(0);
 166 
 167         // Generate some bytecode.
 168         bytecode = generateAndCompile();
 169 
 170         List<Thread> threads = new LinkedList<Thread>();
 171         for (int i = 0; i < staticMethodCallersNumber; i++) {
 172             threads.add(new Thread(new StaticMethodCaller()));
 173         }
 174         for (int i = 0; i < nonstaticMethodCallersNumber; i++) {
 175             threads.add(new Thread(new NonstaticMethodCaller()));
 176         }
 177         for (int i = 0; i < redefiningThreadsNumber; i++) {
 178             threads.add(new Thread(new Worker()));
 179         }
 180 
 181         for (Thread thread : threads) {
 182             thread.start();
 183         }
 184         for (Thread thread : threads) {
 185             try {
 186                 thread.join();
 187             } catch (InterruptedException e) {
 188                 throw new TestFailure("Thread " + Thread.currentThread() + " was interrupted:", e);
 189             }
 190         }
 191     }
 192 
 193     private static byte[] generateAndCompile() {
 194         Map<String, CharSequence> sources = new HashMap<String, CharSequence>();
 195         sources.put(GenerateSourceHelper.CLASS_NAME, GenerateSourceHelper.generateSource());
 196         return InMemoryJavaCompiler.compile(sources).values().iterator().next();
 197     }
 198 
 199     // Auxiliary classloader. Used only once at the beginning.
 200     private static class DefiningClassLoader extends URLClassLoader {
 201         public DefiningClassLoader() {
 202             super(new URL[0]);
 203         }
 204 
 205         Class<?> defineClass(byte[] bytecode) {
 206             return defineClass(null, bytecode, 0, bytecode.length);
 207         }
 208     }
 209 }