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.net.URL;
 30 import java.net.URLClassLoader;
 31 import java.util.HashMap;
 32 import java.util.LinkedList;
 33 import java.util.List;
 34 import java.util.Map;
 35 import java.util.Random;
 36 import java.util.concurrent.ThreadFactory;
 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     private static boolean virtualThreads = false;
 60 
 61     private static volatile Class<?> myClass;
 62     private static ExecutionController stresser;
 63     private static String[] args;
 64 
 65     private static byte[] bytecode;
 66 
 67     // This is random generator used for generating seeds for other Randoms. Setting seed
 68     // from command line sets seed for this random.
 69     static Random seedGenerator;
 70 
 71     static {
 72         try {
 73             System.loadLibrary("stressRedefine");
 74         } catch (UnsatisfiedLinkError e) {
 75             System.err.println("Could not load stressRedefine library");
 76             System.err.println("java.library.path:" +
 77                 System.getProperty("java.library.path"));
 78             throw e;
 79         }
 80     }
 81 
 82     native static int makeRedefinition(int verbose, Class<?> redefClass, byte[] classBytes);
 83 
 84     public static void main(String[] args) {
 85         StressRedefine.args = args;
 86         Tests.runTest(new StressRedefine(), args);
 87     }
 88 
 89     @Override
 90     public void run() {
 91         seedGenerator = new Random(runParams.getSeed());
 92         GenerateSourceHelper.setRandom(new Random(seedGenerator.nextLong()));
 93         stresser = new Stresser(args);
 94 
 95         for (int i = 0; i < args.length; i++ ) {
 96             if ("-staticMethodCallersNumber".equals(args[i])) {
 97                 staticMethodCallersNumber = Integer.parseInt(args[i + 1]);
 98             } else if ("-nonstaticMethodCallersNumber".equals(args[i])) {
 99                 nonstaticMethodCallersNumber = Integer.parseInt(args[i + 1]);
100             } else if ("-redefiningThreadsNumber".equals(args[i])) {
101                 redefiningThreadsNumber = Integer.parseInt(args[i + 1]);
102             } else if ("-corruptingBytecodeProbability".equals(args[i])) {
103                 corruptingBytecodeProbability = Double.parseDouble(args[i + 1]);
104             } else if ("-virtualThreads".equals(args[i])) {
105                 virtualThreads = true;
106             }
107         }
108 
109         //Dynamic attach if required
110         nsk.share.jvmti.JVMTITest.commonInit(args);
111 
112         new StressRedefine().runIt();
113     }
114 
115     private static void runMethod(Random random, String name) {
116         while (stresser.continueExecution()) {
117             try {
118                 // Just for fun we transfer parameters to method
119                 Object res = myClass.getMethod(name, double.class, int.class, Object.class)
120                                          .invoke(myClass.newInstance(), random.nextDouble(), random.nextInt(), new Object());
121              } catch (IllegalArgumentException | InvocationTargetException | InstantiationException
122                      | IllegalAccessException | NoSuchMethodException e) {
123                  // It's okay to get exception here since we are corrupting bytecode and can't expect
124                  // class to work properly.
125                  System.out.println("Got expected exception: " + e.toString());
126              }
127         }
128     }
129 
130     private static class StaticMethodCaller implements Runnable {
131         private Random random;
132         public StaticMethodCaller() {random = new Random(seedGenerator.nextLong());}
133 
134         @Override
135         public void run() {
136             runMethod(random, GenerateSourceHelper.STATIC_METHOD_NAME);
137         }
138     }
139 
140     private static class NonstaticMethodCaller implements Runnable {
141         private Random random;
142         public NonstaticMethodCaller() {random = new Random(seedGenerator.nextLong());}
143 
144         @Override
145         public void run() {
146             runMethod(random, GenerateSourceHelper.NONSTATIC_METHOD_NAME);
147         }
148     }
149 
150     private static class Worker implements Runnable {
151         private Random random;
152         public Worker() {random = new Random(seedGenerator.nextLong());}
153 
154         @Override
155         public void run() {
156             while (stresser.continueExecution()) {
157                 byte[] badBytecode = bytecode.clone();
158                 if (random.nextDouble() < corruptingBytecodeProbability) {
159                     badBytecode[random.nextInt(bytecode.length)] = 42;
160                 }
161                 makeRedefinition(2, myClass, badBytecode);
162             }
163         }
164     }
165 
166     private void runIt() {
167         myClass = new DefiningClassLoader().defineClass(generateAndCompile());
168         stresser.start(0);
169 
170         // Generate some bytecode.
171         bytecode = generateAndCompile();
172 
173         List<Thread> threads = new LinkedList<Thread>();
174         var threadFactory = virtualThreads ? virtualThreadFactory() : platformThreadFactory();
175         for (int i = 0; i < staticMethodCallersNumber; i++) {
176             threads.add(threadFactory.newThread(new StaticMethodCaller()));
177         }
178         for (int i = 0; i < nonstaticMethodCallersNumber; i++) {
179             threads.add(threadFactory.newThread(new NonstaticMethodCaller()));
180         }
181         for (int i = 0; i < redefiningThreadsNumber; i++) {
182             threads.add(threadFactory.newThread(new Worker()));
183         }
184 
185         for (Thread thread : threads) {
186             thread.start();
187         }
188         for (Thread thread : threads) {
189             try {
190                 thread.join();
191             } catch (InterruptedException e) {
192                 throw new TestFailure("Thread " + Thread.currentThread() + " was interrupted:", e);
193             }
194         }
195     }
196 
197     private static ThreadFactory platformThreadFactory() {
198         return task -> new Thread(task);
199     }
200 
201     private static ThreadFactory virtualThreadFactory() {
202         try {
203             Object builder = Thread.class.getMethod("ofVirtual").invoke(null);
204             Class<?> clazz = Class.forName("java.lang.Thread$Builder");
205             java.lang.reflect.Method factory = clazz.getMethod("factory");
206             return (ThreadFactory) factory.invoke(builder);
207         } catch (RuntimeException | Error e) {
208             throw e;
209         } catch (Exception e) {
210             throw new RuntimeException(e);
211         }
212     }
213 
214     private static byte[] generateAndCompile() {
215         Map<String, CharSequence> sources = new HashMap<String, CharSequence>();
216         sources.put(GenerateSourceHelper.CLASS_NAME, GenerateSourceHelper.generateSource());
217         return InMemoryJavaCompiler.compile(sources).values().iterator().next();
218     }
219 
220     // Auxiliary classloader. Used only once at the beginning.
221     private static class DefiningClassLoader extends URLClassLoader {
222         public DefiningClassLoader() {
223             super(new URL[0]);
224         }
225 
226         Class<?> defineClass(byte[] bytecode) {
227             return defineClass(null, bytecode, 0, bytecode.length);
228         }
229     }
230 }