Skip to content

Commit 092078e

Browse files
ArrayingDavid Simms
authored andcommitted
8327257: [lworld] Tests are needed to stress class preloading with concurrent class loadings
Reviewed-by: dsimms
1 parent 3e84ee6 commit 092078e

11 files changed

+893
-0
lines changed
Lines changed: 289 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,289 @@
1+
/*
2+
* Copyright (c) 2025, 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+
* @summary Sanity test for BigClassTreeClassLoader
28+
* @enablePreview
29+
* @run main BigClassTreeClassLoader
30+
*/
31+
32+
import java.lang.classfile.*;
33+
import java.lang.classfile.attribute.*;
34+
import java.lang.classfile.constantpool.*;
35+
import java.lang.constant.*;
36+
import java.util.Arrays;
37+
import java.util.Map;
38+
import java.util.HashMap;
39+
import java.util.Optional;
40+
import java.util.function.Consumer;
41+
42+
import static java.lang.classfile.ClassFile.*;
43+
import static java.lang.constant.ConstantDescs.*;
44+
45+
// A classloader that will generate a big value class inheritance tree (depth,
46+
// and possibly breadth) of classes on the fly. For example, with a
47+
// maximum depth limit of 3, one can load "Gen3" via this classloader,
48+
// which will generate the following:
49+
//
50+
// public value class Gen2 --> Gen1 --> Gen0 --> java.lang.Object
51+
//
52+
// Optionally, a long field chain can also be generated in one of the Gen classes.
53+
// This creates a chain of Field value classes which have other Field objects as
54+
// fields up to the maximum depth. Class depth = field width. Only the Gen's field
55+
// is static, the rest are not. For example, with a maximum depth limit of 3 and
56+
// field at 1, this classloader will generate the following:
57+
//
58+
// public value class Gen2 --> Gen1 --> Gen0 --> java.lang.Object
59+
// | public static Field2 theField;
60+
// public value class Field2
61+
// | theField
62+
// Field1
63+
// | theField
64+
// Field0
65+
//
66+
// Field0 will have a field theField of java.lang.Object. It is possible to change
67+
// both the field class as well as the superclass of Field0 to introduce interesting
68+
// class circularity.
69+
//
70+
// This classloader is parallel capable. It uses the built in classloading lock via
71+
// loadClass to ensure that it defines a given GenX or FieldX only once.
72+
public class BigClassTreeClassLoader extends ClassLoader {
73+
74+
// Sanity test, this should never fail.
75+
public static void main(String[] args) throws ClassNotFoundException {
76+
var fields = new FieldGeneration(1, Optional.empty(), Optional.empty());
77+
Class.forName("Gen2", false, new BigClassTreeClassLoader(3, fields));
78+
}
79+
80+
// A field generation strategy that disables field generation.
81+
public static FieldGeneration NO_FIELD_GEN = new FieldGeneration(-1, Optional.empty(), Optional.empty());
82+
83+
// A sane depth/width limit.
84+
private static final int SANE_LIMIT = 100;
85+
86+
// We want to perform different things depending on what kind of class we are
87+
// generating. Therefore, we utilize a strategy pattern.
88+
private final Strategy[] availableStrategies;
89+
90+
// A store of all the classes already defined. Existing classes must be reused
91+
// otherwise an exception will be raised.
92+
private final Map<String, Class<?>> defined;
93+
94+
private final int limitInclusive;
95+
96+
// Create the generator with no fields.
97+
public BigClassTreeClassLoader(int depthLimitInclusive) {
98+
this(depthLimitInclusive, NO_FIELD_GEN);
99+
}
100+
101+
// Create the generator with fields.
102+
public BigClassTreeClassLoader(int depthLimitInclusive,
103+
FieldGeneration fields) {
104+
if (depthLimitInclusive < 0 || depthLimitInclusive > SANE_LIMIT) {
105+
throw new IllegalArgumentException("depth limit beyond sane bounds");
106+
}
107+
// Make it compatible with zero indices.
108+
this.limitInclusive = depthLimitInclusive - 1;
109+
this.defined = new HashMap<>();
110+
if (fields.index > limitInclusive) {
111+
throw new IllegalArgumentException("field generation index invalid");
112+
}
113+
this.availableStrategies = new Strategy[] { new GenStrategy(fields.index), new FieldStrategy(fields) };
114+
// Finally, register as a parallel capable classloader for stress tests.
115+
if(!registerAsParallelCapable() || !isRegisteredAsParallelCapable()) {
116+
throw new IllegalStateException("could not register parallel classloader");
117+
}
118+
}
119+
120+
// The index X means GenX will have the field. Set to -1 to disable.
121+
// The furthest chained field Field0 can have an optional superclass/declared field.
122+
public static record FieldGeneration (int index,
123+
Optional<String> deepestParentClass,
124+
Optional<String> deepestFieldClass) {}
125+
126+
// We will bottom-up generate a class tree. It knows what to do for a
127+
// specific class based on the provided name. This is not thread-safe itself,
128+
// but since it is called safely via a synchronized block in loadClass,
129+
// adding custom synchronization primitives can yield in a deadlock.
130+
public Class<?> findClass(final String name) throws ClassNotFoundException {
131+
// We only generate classes starting with our known prefix.
132+
final Strategy strategy = Arrays.stream(availableStrategies)
133+
.filter(st -> name.startsWith(st.prefix()))
134+
.findFirst()
135+
.orElseThrow(ClassNotFoundException::new);
136+
// Derive the correct parameters (or error).
137+
String prefix = strategy.prefix();
138+
int depth;
139+
try {
140+
String itersString = name.substring(prefix.length());
141+
depth = Integer.parseInt(itersString);
142+
// Some bounds sanity checking.
143+
if (depth < 0 || depth > limitInclusive) {
144+
throw new IllegalArgumentException("attempting to generate beyond limits");
145+
}
146+
} catch (IllegalArgumentException | IndexOutOfBoundsException e) {
147+
throw new ClassNotFoundException("can't generate class since it does not conform to limits", e);
148+
}
149+
// If we have already generated this class, reuse it.
150+
Class<?> clazz = defined.get(name);
151+
if (clazz != null) {
152+
return clazz;
153+
}
154+
// Make the actual and define it.
155+
clazz = makeClass(name,
156+
strategy.parent(depth),
157+
strategy.flags(limitInclusive, depth),
158+
clb -> strategy.process(limitInclusive, depth, clb),
159+
cob -> strategy.constructorPre(limitInclusive, depth, cob)
160+
);
161+
return clazz;
162+
}
163+
164+
private interface Strategy {
165+
String prefix();
166+
String parent(int depth);
167+
int flags(int limitInclusive, int depth);
168+
void process(int limitInclusive, int depth, ClassBuilder builder);
169+
default void constructorPre(int limitInclusive, int depth, CodeBuilder builder) {}
170+
}
171+
172+
// The Gen classes generate classes that have a large inheritance tree.
173+
// GenX has Gen(X-1) as a superclass. Gen0 inherits from Object.
174+
private static final class GenStrategy implements Strategy {
175+
private final int fieldIndex;
176+
177+
public GenStrategy(int fieldIndex) {
178+
this.fieldIndex = fieldIndex;
179+
}
180+
181+
public String prefix() {
182+
return "Gen";
183+
}
184+
185+
public String parent(int depth) {
186+
return depth == 0 ? Object.class.getName() : prefix() + (depth - 1);
187+
}
188+
189+
public int flags(int limitInclusive, int depth) {
190+
return depth == limitInclusive ? ACC_FINAL : ACC_ABSTRACT;
191+
}
192+
193+
public void process(int limitInclusive, int depth, ClassBuilder builder) {
194+
// Is this the generation that will have the field chain?
195+
if (depth == fieldIndex) {
196+
ClassDesc fieldClass = ClassDesc.of(FieldStrategy.PREFIX + "" + limitInclusive);
197+
// We use an uninitialized static field to denote the outermost Field class.
198+
builder.withField("theField", fieldClass, ACC_PUBLIC | ACC_STATIC)
199+
.with(LoadableDescriptorsAttribute.of(builder.constantPool().utf8Entry(fieldClass)));
200+
}
201+
}
202+
}
203+
204+
// The field strategy allows generating deep fields, including potential circularity.
205+
// FieldX has Field(X-1) as a field. Field0 is special as it can inherit from something
206+
// other than Object, and contain a custom field.
207+
private static final class FieldStrategy implements Strategy {
208+
public static final String PREFIX = "Field";
209+
private final FieldGeneration fields;
210+
211+
public FieldStrategy(FieldGeneration fields) {
212+
this.fields = fields;
213+
}
214+
215+
public String prefix() {
216+
return PREFIX;
217+
}
218+
219+
public String parent(int depth) {
220+
// Only the deepest class has a custom parent.
221+
return fields.deepestParentClass().filter(_ -> depth == 0).orElse(Object.class.getName());
222+
}
223+
224+
public int flags(int limitInclusive, int depth) {
225+
// Every field class is final.
226+
return ACC_FINAL;
227+
}
228+
229+
public void process(int limitInclusive, int depth, ClassBuilder builder) {
230+
ClassDesc fieldClass = computeFieldClass(depth);
231+
if (depth != 0) {
232+
builder.with(LoadableDescriptorsAttribute.of(builder.constantPool().utf8Entry(fieldClass)));
233+
}
234+
// The field is non-static, final, and therefore needs ACC_STRICT_INIT
235+
builder.withField("theField", fieldClass, ACC_PUBLIC | ACC_FINAL | ACC_STRICT_INIT);
236+
}
237+
238+
public void constructorPre(int limitInclusive, int depth, CodeBuilder builder) {
239+
ClassDesc thisField = ClassDesc.of(prefix() + "" + depth);
240+
ClassDesc fieldClass = computeFieldClass(depth);
241+
// We need to make sure to initialize the field as the first thing in the constructor.
242+
builder.aload(0)
243+
.aconst_null()
244+
.putfield(thisField, "theField", fieldClass);
245+
}
246+
247+
private ClassDesc computeFieldClass(int depth) {
248+
if (depth == 0) {
249+
return ClassDesc.of(fields.deepestFieldClass().orElse(Object.class.getName()));
250+
} else {
251+
return ClassDesc.of(prefix() + (depth - 1));
252+
}
253+
}
254+
}
255+
256+
// Make the class. Not thread-safe, should be called when obtaining a
257+
// classloading lock for the particular class.
258+
private Class<?> makeClass(String thisGen,
259+
String parentGen,
260+
int addFlags,
261+
Consumer<ClassBuilder> classBuilder,
262+
Consumer<CodeBuilder> constructorBuilder) {
263+
ClassDesc parentDesc = ClassDesc.of(parentGen);
264+
// A class that has itself as a loadable descriptor.
265+
byte[] bytes = ClassFile.of().build(ClassDesc.of(thisGen), clb -> {
266+
clb
267+
// Use Valhalla version.
268+
.withVersion(latestMajorVersion(), PREVIEW_MINOR_VERSION)
269+
// Explicitly do not add ACC_SUPER or ACC_ABSTRACT.
270+
.withFlags(ACC_PUBLIC | addFlags)
271+
// Not strictly necessary for java/lang/Object.
272+
.withSuperclass(parentDesc)
273+
// Make sure to init the correct superclass.
274+
.withMethodBody(INIT_NAME, MTD_void, ACC_PUBLIC, cob -> {
275+
constructorBuilder.accept(cob);
276+
cob.aload(0)
277+
.invokespecial(parentDesc, INIT_NAME, MTD_void)
278+
.return_();
279+
});
280+
// Do the additional things defined by the strategy.
281+
classBuilder.accept(clb);
282+
});
283+
// Define the actual class and register it.
284+
Class<?> clazz = defineClass(thisGen, bytes, 0, bytes.length);
285+
defined.put(thisGen, clazz);
286+
return clazz;
287+
}
288+
289+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
class Child {
2+
0xCAFEBABE;
3+
65535; // minor version
4+
70; // version
5+
[] { // Constant Pool
6+
; // first element is empty
7+
Utf8 "Child"; // #1
8+
class #1; // #2
9+
Utf8 "Parent"; // #3
10+
class #3; // #4
11+
Utf8 "<init>"; // #5
12+
Utf8 "()V"; // #6
13+
NameAndType #5 #6; // #7
14+
Method #4 #7; // #8
15+
Utf8 "Code"; // #9
16+
} // Constant Pool
17+
18+
0x0011; // access NOTE: this is a value class, which cannot inherit from identity
19+
#2;// this_cpx
20+
#4;// super_cpx
21+
22+
[] { // Interfaces
23+
} // Interfaces
24+
25+
[] { // Fields
26+
} // Fields
27+
28+
[] { // Methods
29+
{ // method
30+
0x0001; // access
31+
#5; // name_index
32+
#6; // descriptor_index
33+
[] { // Attributes
34+
Attr(#9) { // Code
35+
1; // max_stack
36+
1; // max_locals
37+
Bytes[]{
38+
0x2AB70008B1;
39+
}
40+
[] { // Traps
41+
} // end Traps
42+
[] { // Attributes
43+
} // Attributes
44+
} // end Code
45+
} // Attributes
46+
}
47+
} // Methods
48+
49+
[] { // Attributes
50+
} // Attributes
51+
} // end class Child

0 commit comments

Comments
 (0)