1 /*
2  * Copyright (c) 2021, 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 
25 /*
26  * @test
27  * @bug 8251945
28  * @summary Ensure no race on installing class loader data
29  *
30  * @run main/othervm Test
31  */
32 import jdk.jfr.Event;
33 import java.util.concurrent.BrokenBarrierException;
34 import java.io.InputStream;
35 import java.io.IOException;
36 import java.io.UncheckedIOException;
37 import java.io.OutputStream;
38 import java.io.ByteArrayOutputStream;
39 import java.util.concurrent.ExecutorService;
40 import java.util.concurrent.CyclicBarrier;
41 import java.util.concurrent.Executors;
42 
43 public final class Test
44 {
45     private static final int ITERATIONS = Integer.getInteger("iterations", 10000);
46     private volatile ClassLoader nextLoader;
47 
main(final String[] args)48     public static void main(final String[] args) {
49         new Test().crash();
50     }
51 
crash()52     public void crash() {
53         final byte[] runnableClass = loadBytecode("Test$TestRunnable");
54         final byte[] eventClass = loadBytecode("Test$TestRunnable$RunnableEvent");
55         final int numberOfThreads = Runtime.getRuntime().availableProcessors();
56         if (numberOfThreads < 1) {
57             throw new IllegalStateException("requies more than one thread");
58         }
59         final ExecutorService threadPool = Executors.newFixedThreadPool(numberOfThreads);
60         final CyclicBarrier cyclicBarrier = new CyclicBarrier(numberOfThreads, () -> this.nextLoader = new PredefinedClassLoader(runnableClass, eventClass));
61         for (int i = 0; i < numberOfThreads; ++i) {
62             threadPool.submit(new LoadingRunnable(cyclicBarrier));
63         }
64         threadPool.shutdown();
65     }
66 
loadTestRunnable(final ClassLoader classLoader)67     Runnable loadTestRunnable(final ClassLoader classLoader) {
68         try {
69             return (Runnable)Class.forName("Test$TestRunnable", true, classLoader).asSubclass(Runnable.class).getConstructor((Class<?>[])new Class[0]).newInstance(new Object[0]);
70         }
71         catch (ReflectiveOperationException e) {
72             throw new RuntimeException("could not load runnable", e);
73         }
74     }
75 
loadBytecode(final String className)76     private static byte[] loadBytecode(final String className) {
77         final String resource = toResourceName(className);
78         final ByteArrayOutputStream buffer = new ByteArrayOutputStream();
79         try {
80             final InputStream inputStream = Test.class.getClassLoader().getResourceAsStream(resource);
81             try {
82                 inputStream.transferTo(buffer);
83                 if (inputStream != null) {
84                     inputStream.close();
85                 }
86             }
87             catch (Throwable t) {
88                 if (inputStream != null) {
89                     try {
90                         inputStream.close();
91                     }
92                     catch (Throwable exception) {
93                         t.addSuppressed(exception);
94                     }
95                 }
96                 throw t;
97             }
98         }
99         catch (IOException e) {
100             throw new UncheckedIOException(className, e);
101         }
102         return buffer.toByteArray();
103     }
104 
toResourceName(final String className)105     private static String toResourceName(final String className) {
106         return className.replace('.', '/') + ".class";
107     }
108 
109     final class LoadingRunnable implements Runnable
110     {
111         private final CyclicBarrier barrier;
112 
LoadingRunnable(final CyclicBarrier barrier)113         LoadingRunnable(final CyclicBarrier barrier) {
114             this.barrier = barrier;
115         }
116 
117         @Override
run()118         public void run() {
119             int itr = 0;
120             try {
121                 while (itr++ < ITERATIONS) {
122                     this.barrier.await();
123                     final Runnable runnable = Test.this.loadTestRunnable(Test.this.nextLoader);
124                     runnable.run();
125                 }
126             }
127             catch (InterruptedException | BrokenBarrierException ex) {
128                 final Exception e = ex;
129             }
130         }
131     }
132 
133     static final class PredefinedClassLoader extends ClassLoader
134     {
135         private final byte[] runnableClass;
136         private final byte[] eventClass;
137 
PredefinedClassLoader(final byte[] runnableClass, final byte[] eventClass)138         PredefinedClassLoader(final byte[] runnableClass, final byte[] eventClass) {
139             super(null);
140             this.runnableClass = runnableClass;
141             this.eventClass = eventClass;
142         }
143 
144         @Override
loadClass(final String className, final boolean resolve)145         protected Class<?> loadClass(final String className, final boolean resolve) throws ClassNotFoundException {
146             final Class<?> loadedClass = this.findLoadedClass(className);
147             if (loadedClass != null) {
148                 if (resolve) {
149                     this.resolveClass(loadedClass);
150                 }
151                 return loadedClass;
152             }
153             if (className.equals("Test$TestRunnable")) {
154                 return this.loadClassFromByteArray(className, resolve, this.runnableClass);
155             }
156             if (className.equals("Test$TestRunnable$RunnableEvent")) {
157                 return this.loadClassFromByteArray(className, resolve, this.eventClass);
158             }
159             return super.loadClass(className, resolve);
160         }
161 
loadClassFromByteArray(final String className, final boolean resolve, final byte[] byteCode)162         private Class<?> loadClassFromByteArray(final String className, final boolean resolve, final byte[] byteCode) throws ClassNotFoundException {
163             Class<?> clazz;
164             try {
165                 synchronized (getClassLoadingLock(className)) {
166                     clazz = this.defineClass(className, byteCode, 0, byteCode.length);
167                 }
168             }
169             catch (LinkageError e) {
170                 clazz = this.findLoadedClass(className);
171             }
172             if (resolve) {
173                 this.resolveClass(clazz);
174             }
175             return clazz;
176         }
177     }
178 
179     public static final class TestRunnable implements Runnable
180     {
181         @Override
run()182         public void run() {
183             final RunnableEvent event = new RunnableEvent();
184             event.setRunnableClassName("TestRunnable");
185             event.begin();
186             event.end();
187             event.commit();
188         }
189 
190         public static class RunnableEvent extends Event
191         {
192             private String runnableClassName;
193 
getRunnableClassName()194             String getRunnableClassName() {
195                 return this.runnableClassName;
196             }
197 
setRunnableClassName(final String operationName)198             void setRunnableClassName(final String operationName) {
199                 this.runnableClassName = operationName;
200             }
201         }
202     }
203 }
204