1 /*
  2  * Copyright Amazon.com Inc. 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 
 25 /*
 26  * @test
 27  * @bug 8277444
 28  *
 29  * @library /test/lib
 30  * @compile SimpleIdentityTransformer.java
 31  * @run shell MakeJAR.sh retransformAgent
 32  * @run main/othervm -javaagent:retransformAgent.jar RetransformBigClassTest
 33  */
 34 
 35 import jdk.test.lib.compiler.InMemoryJavaCompiler;
 36 
 37 /*
 38  * JvmtiClassFileReconstituter::copy_bytecodes restores bytecodes rewritten
 39  * by the linking process. It is used by RetransformClasses.
 40  * JDK-8277444 is a data race between copy_bytecodes and the linking process.
 41  * This test puts the linking process in one thread and the retransforming process
 42  * in another thread. The test uses Class.forName("BigClass", false, classLoader)
 43  * which does not link the class. When the class is used, the linking process starts.
 44  * In another thread retransforming of the class is happening.
 45  * We generate a class with big methods. A number of methods and their size are
 46  * chosen to make the linking and retransforming processes run concurrently.
 47  * We delay the retransforming process to follow the linking process.
 48  * If there is no synchronization between the processes, a data race will happen.
 49  */
 50 public class RetransformBigClassTest extends AInstrumentationTestCase {
 51 
 52     private static final Object LOCK = new Object();
 53     private static final int COUNTER_INC_COUNT            = 2000; // A number of 'c+=1;' statements in methods of a class.
 54     private static final int MIN_LINK_TIME_MS             = 60;   // Large enough so the linking and retransforming processes run in parallel.
 55     private static final int RETRANSFORM_CLASSES_DELAY_MS = 37;   // We manage to create a data race when a delay is in the range 0.52x - 0.62x of MIN_LINK_TIME_MS.
 56 
 57     private static Class<?> bigClass;
 58     private static byte[] bigClassBytecode;
 59 
 60     private Thread retransformThread;
 61 
 62     RetransformBigClassTest() {
 63         super("RetransformBigClassTest");
 64     }
 65 
 66     public static void main(String[] args) throws Throwable {
 67         new RetransformBigClassTest().runTest();
 68     }
 69 
 70     protected final void doRunTest() throws Throwable {
 71         ClassLoader classLoader = new ClassLoader() {
 72                 @Override
 73                 protected Class<?> findClass(String name) throws ClassNotFoundException {
 74                     if (name.equals("BigClass")) {
 75                         return defineClass(name, bigClassBytecode, 0, bigClassBytecode.length);
 76                     }
 77 
 78                     return super.findClass(name);
 79                 }
 80         };
 81         synchronized (LOCK) {
 82             bigClass = Class.forName("BigClass", false, classLoader);
 83             LOCK.notify();
 84         }
 85         // Make a use of the BigClass
 86         assertTrue(bigClass.getConstructor().newInstance().hashCode() != 0);
 87         retransformThread.join();
 88     }
 89 
 90     private byte[] createClassBytecode(String className, int methodCount) throws Exception {
 91         String methodBody = "";
 92         for (int j = 0; j < COUNTER_INC_COUNT; j++) {
 93             methodBody += "c+=1;";
 94         }
 95 
 96         String classSrc = "public class " + className + " { int c;";
 97 
 98         for (int i = 0; i < methodCount; i++) {
 99             classSrc += "\npublic void m" + i + "(){";
100             classSrc += methodBody;
101             classSrc += "\n}";
102         }
103         classSrc += "\n}";
104 
105         return InMemoryJavaCompiler.compile(className, classSrc);
106     }
107 
108     // We need a number of methods such that the linking time is greater than
109     // or equal to MIN_LINK_TIME_MS.
110     // We create a class having 5 methods and trigger the linking process.
111     // We measure the time taken and use it to calculate the needed number.
112     private int findMethodCount() throws Exception {
113         int methodCount = 5;
114         final String className = "BigClass" + methodCount;
115         final byte[] bytecode = createClassBytecode(className, methodCount);
116         ClassLoader classLoader = new ClassLoader() {
117             @Override
118             protected Class<?> findClass(String name) throws ClassNotFoundException {
119                 if (name.equals(className)) {
120                     return defineClass(name, bytecode, 0, bytecode.length);
121                 }
122 
123                 return super.findClass(name);
124             }
125         };
126         var bigClass = Class.forName(className, false, classLoader);
127         long startTime = System.nanoTime();
128         assertTrue(bigClass.getConstructor().newInstance().hashCode() != 0);
129         double linkTimeMs = (System.nanoTime() - startTime) / 1000000.0;
130         System.out.println("Link time for a class with " + methodCount + " methods each having " + COUNTER_INC_COUNT + " counter increments: " + Math.round(linkTimeMs));
131         if (linkTimeMs < MIN_LINK_TIME_MS) {
132           methodCount = (int)Math.round((MIN_LINK_TIME_MS * methodCount) / linkTimeMs);
133         }
134         System.out.println("The number of methods to exceed " + MIN_LINK_TIME_MS + " ms linking time: " + methodCount);
135         return methodCount;
136     }
137 
138     @Override
139     protected void setUp() throws Exception {
140         super.setUp();
141 
142         bigClassBytecode = createClassBytecode("BigClass", findMethodCount());
143         fInst.addTransformer(new SimpleIdentityTransformer());
144         retransformThread = new Thread(() -> {
145             try {
146                 synchronized (LOCK) {
147                     while (bigClass == null) {
148                         System.out.println("[retransformThread]: Waiting for bigClass");
149                         LOCK.wait();
150                     }
151                 }
152                 Thread.sleep(RETRANSFORM_CLASSES_DELAY_MS);
153                 fInst.retransformClasses(bigClass);
154             } catch (Exception e) {
155                 e.printStackTrace();
156             }
157         });
158         retransformThread.start();
159         Thread.sleep(100);
160     }
161 }