28
28
import static jdk .graal .compiler .options .OptionStability .EXPERIMENTAL ;
29
29
30
30
import java .lang .reflect .Modifier ;
31
+ import java .util .ArrayList ;
32
+ import java .util .Collections ;
31
33
import java .util .EnumSet ;
34
+ import java .util .HashMap ;
32
35
import java .util .HashSet ;
36
+ import java .util .List ;
37
+ import java .util .Map ;
33
38
import java .util .Objects ;
34
39
import java .util .Set ;
35
40
import java .util .function .BooleanSupplier ;
41
+ import java .util .function .Consumer ;
36
42
37
43
import org .graalvm .collections .EconomicMap ;
38
44
import org .graalvm .nativeimage .Platform ;
46
52
import com .oracle .svm .core .configure .RuntimeConditionSet ;
47
53
import com .oracle .svm .core .feature .AutomaticallyRegisteredImageSingleton ;
48
54
import com .oracle .svm .core .hub .registry .ClassRegistries ;
55
+ import com .oracle .svm .core .layeredimagesingleton .ImageSingletonLoader ;
56
+ import com .oracle .svm .core .layeredimagesingleton .ImageSingletonWriter ;
49
57
import com .oracle .svm .core .layeredimagesingleton .LayeredImageSingletonBuilderFlags ;
50
58
import com .oracle .svm .core .layeredimagesingleton .LayeredImageSingletonSupport ;
51
59
import com .oracle .svm .core .layeredimagesingleton .MultiLayeredImageSingleton ;
52
- import com .oracle .svm .core .layeredimagesingleton .UnsavedSingleton ;
53
60
import com .oracle .svm .core .metadata .MetadataTracer ;
54
61
import com .oracle .svm .core .option .HostedOptionKey ;
62
+ import com .oracle .svm .core .option .LayerVerifiedOption ;
55
63
import com .oracle .svm .core .reflect .MissingReflectionRegistrationUtils ;
56
64
import com .oracle .svm .core .util .ImageHeapMap ;
57
65
import com .oracle .svm .core .util .VMError ;
58
66
59
67
import jdk .graal .compiler .options .Option ;
60
68
61
69
@ AutomaticallyRegisteredImageSingleton
62
- public final class ClassForNameSupport implements MultiLayeredImageSingleton , UnsavedSingleton {
70
+ public final class ClassForNameSupport implements MultiLayeredImageSingleton {
71
+
72
+ public static final String CLASSES_REGISTERED = "classes registered" ;
73
+ public static final String CLASSES_REGISTERED_STATES = "classes registered states" ;
74
+ public static final String UNSAFE_REGISTERED = "unsafe registered" ;
75
+ public static final String RESPECTS_CLASS_LOADER = "respects class loader" ;
63
76
64
77
public static final class Options {
78
+ @ LayerVerifiedOption (kind = LayerVerifiedOption .Kind .Changed , severity = LayerVerifiedOption .Severity .Error )//
65
79
@ Option (help = "Class.forName and similar respect their class loader argument." , stability = EXPERIMENTAL )//
66
80
public static final HostedOptionKey <Boolean > ClassForNameRespectsClassLoader = new HostedOptionKey <>(false );
67
81
}
@@ -98,7 +112,8 @@ private static ClassForNameSupport[] layeredSingletons() {
98
112
}
99
113
100
114
/**
101
- * The map used to collect registered classes. Not used when respecting class loaders.
115
+ * The map used to collect registered classes. Not used when respecting class loaders. This map
116
+ * only collects data for the current layer.
102
117
*/
103
118
private final EconomicMap <String , ConditionalRuntimeValue <Object >> knownClasses ;
104
119
/**
@@ -112,14 +127,36 @@ private static ClassForNameSupport[] layeredSingletons() {
112
127
*/
113
128
private final EconomicMap <String , Throwable > knownExceptions ;
114
129
/**
115
- * The map used to collect unsafe allocated classes.
130
+ * The map used to collect unsafe allocated classes. This map only collects data for the current
131
+ * layer.
116
132
*/
117
133
private final EconomicMap <Class <?>, RuntimeConditionSet > unsafeInstantiatedClasses ;
118
134
135
+ /**
136
+ * The map used to collect classes registered in previous layers. The boolean associated to each
137
+ * class is true if the registered value is complete and false in the case of a negative query.
138
+ * A complete data registered in the current layer will overwrite a negative query in previous
139
+ * layers. In this case, the data will be stored in {@link ClassForNameSupport#knownClasses} of
140
+ * the current layer and the boolean value will be changed in the map of the next extension
141
+ * layer.
142
+ */
143
+ @ Platforms (HOSTED_ONLY .class ) //
144
+ private final Map <String , Boolean > previousLayerClasses ;
145
+
146
+ /**
147
+ * The set used to collect unsafe allocated classes in previous layers.
148
+ */
149
+ @ Platforms (HOSTED_ONLY .class ) //
150
+ private final Set <String > previousLayerUnsafe ;
151
+
119
152
private static final Object NEGATIVE_QUERY = new Object ();
120
153
121
154
public ClassForNameSupport () {
122
- if (respectClassLoader ()) {
155
+ this (Map .of (), Set .of (), respectClassLoader ());
156
+ }
157
+
158
+ public ClassForNameSupport (Map <String , Boolean > previousLayerClasses , Set <String > previousLayerUnsafe , boolean respectsClassLoader ) {
159
+ if (respectsClassLoader ) {
123
160
knownClasses = null ;
124
161
knownClassNames = ImageHeapMap .createNonLayeredMap ();
125
162
knownExceptions = ImageHeapMap .createNonLayeredMap ();
@@ -129,6 +166,8 @@ public ClassForNameSupport() {
129
166
knownExceptions = null ;
130
167
}
131
168
unsafeInstantiatedClasses = ImageHeapMap .createNonLayeredMap ();
169
+ this .previousLayerClasses = previousLayerClasses ;
170
+ this .previousLayerUnsafe = previousLayerUnsafe ;
132
171
}
133
172
134
173
public static boolean respectClassLoader () {
@@ -177,15 +216,15 @@ public void registerClass(ConfigurationCondition condition, Class<?> clazz, Clas
177
216
currentValue == clazz ) {
178
217
currentValue = clazz ;
179
218
var cond = updateConditionalValue (existingEntry , currentValue , condition );
180
- knownClasses . put (name , cond );
219
+ addKnownClass (name , cond );
181
220
} else if (currentValue instanceof Throwable ) { // failed at linking time
182
221
var cond = updateConditionalValue (existingEntry , currentValue , condition );
183
222
/*
184
223
* If the class has already been seen as throwing an error, we don't overwrite
185
224
* this error. Nevertheless, we have to update the set of conditionals to be
186
225
* correct.
187
226
*/
188
- knownClasses . put (name , cond );
227
+ addKnownClass (name , cond );
189
228
} else {
190
229
throw VMError .shouldNotReachHere ("""
191
230
Invalid Class.forName value for %s: %s
@@ -199,6 +238,17 @@ accessible through the builder class loader, and it was already registered by na
199
238
}
200
239
}
201
240
241
+ private void addKnownClass (String name , ConditionalRuntimeValue <Object > cond ) {
242
+ addKnownClass (name , (map ) -> map .put (name , cond ), cond );
243
+ }
244
+
245
+ private void addKnownClass (String name , Consumer <EconomicMap <String , ConditionalRuntimeValue <Object >>> callback , ConditionalRuntimeValue <Object > cond ) {
246
+ Boolean previousLayerData = previousLayerClasses .get (name );
247
+ if (previousLayerData == null || (!previousLayerData && cond .getValueUnconditionally () != NEGATIVE_QUERY )) {
248
+ callback .accept (knownClasses );
249
+ }
250
+ }
251
+
202
252
@ Platforms (HOSTED_ONLY .class )
203
253
private boolean isLibGraalClass (Class <?> clazz ) {
204
254
return libGraalLoader != null && clazz .getClassLoader () == libGraalLoader ;
@@ -260,19 +310,24 @@ private void registerKnownClassName(ConfigurationCondition condition, String cla
260
310
public void registerUnsafeAllocated (ConfigurationCondition condition , Class <?> clazz ) {
261
311
if (!clazz .isArray () && !clazz .isInterface () && !Modifier .isAbstract (clazz .getModifiers ())) {
262
312
/* Otherwise, UNSAFE.allocateInstance results in InstantiationException */
263
- var conditionSet = unsafeInstantiatedClasses .putIfAbsent (clazz , RuntimeConditionSet .createHosted (condition ));
264
- if (conditionSet != null ) {
265
- conditionSet .addCondition (condition );
313
+ if (!previousLayerUnsafe .contains (clazz .getName ())) {
314
+ var conditionSet = unsafeInstantiatedClasses .putIfAbsent (clazz , RuntimeConditionSet .createHosted (condition ));
315
+ if (conditionSet != null ) {
316
+ conditionSet .addCondition (condition );
317
+ }
266
318
}
267
319
}
268
320
}
269
321
270
322
private void updateCondition (ConfigurationCondition condition , String className , Object value ) {
271
323
synchronized (knownClasses ) {
272
- var runtimeConditions = knownClasses .putIfAbsent (className , new ConditionalRuntimeValue <>(RuntimeConditionSet .createHosted (condition ), value ));
273
- if (runtimeConditions != null ) {
274
- runtimeConditions .getConditions ().addCondition (condition );
275
- }
324
+ var cond = new ConditionalRuntimeValue <>(RuntimeConditionSet .createHosted (condition ), value );
325
+ addKnownClass (className , (map ) -> {
326
+ var runtimeConditions = map .putIfAbsent (className , cond );
327
+ if (runtimeConditions != null ) {
328
+ runtimeConditions .getConditions ().addCondition (condition );
329
+ }
330
+ }, cond );
276
331
}
277
332
}
278
333
@@ -468,4 +523,59 @@ public static boolean canUnsafeInstantiateAsInstance(DynamicHub hub) {
468
523
public EnumSet <LayeredImageSingletonBuilderFlags > getImageBuilderFlags () {
469
524
return LayeredImageSingletonBuilderFlags .ALL_ACCESS ;
470
525
}
526
+
527
+ @ Override
528
+ public PersistFlags preparePersist (ImageSingletonWriter writer ) {
529
+ List <String > classNames = new ArrayList <>();
530
+ List <Boolean > classStates = new ArrayList <>();
531
+ Set <String > unsafeNames = new HashSet <>(previousLayerUnsafe );
532
+
533
+ var cursor = knownClasses .getEntries ();
534
+ while (cursor .advance ()) {
535
+ classNames .add (cursor .getKey ());
536
+ boolean isNegativeQuery = cursor .getValue ().getValueUnconditionally () == NEGATIVE_QUERY ;
537
+ classStates .add (!isNegativeQuery );
538
+ }
539
+
540
+ for (var entry : previousLayerClasses .entrySet ()) {
541
+ /*
542
+ * If a complete entry overwrites a negative query from a previous layer, the
543
+ * previousLayerClasses map entry needs to be skipped to register the new entry for
544
+ * extension layers.
545
+ */
546
+ if (!classNames .contains (entry .getKey ())) {
547
+ classNames .add (entry .getKey ());
548
+ classStates .add (entry .getValue ());
549
+ }
550
+ }
551
+
552
+ unsafeInstantiatedClasses .getKeys ().iterator ().forEachRemaining (c -> unsafeNames .add (c .getName ()));
553
+
554
+ writer .writeStringList (CLASSES_REGISTERED , classNames );
555
+ writer .writeBoolList (CLASSES_REGISTERED_STATES , classStates );
556
+ writer .writeStringList (UNSAFE_REGISTERED , unsafeNames .stream ().toList ());
557
+ /*
558
+ * The option is not accessible when the singleton is loaded, so the boolean needs to be
559
+ * persisted.
560
+ */
561
+ writer .writeInt (RESPECTS_CLASS_LOADER , respectClassLoader () ? 1 : 0 );
562
+
563
+ return PersistFlags .CREATE ;
564
+ }
565
+
566
+ @ SuppressWarnings ("unused" )
567
+ public static Object createFromLoader (ImageSingletonLoader loader ) {
568
+ List <String > previousLayerClassKeys = loader .readStringList (CLASSES_REGISTERED );
569
+ List <Boolean > previousLayerClassStates = loader .readBoolList (CLASSES_REGISTERED_STATES );
570
+
571
+ Map <String , Boolean > previousLayerClasses = new HashMap <>();
572
+ for (int i = 0 ; i < previousLayerClassKeys .size (); ++i ) {
573
+ previousLayerClasses .put (previousLayerClassKeys .get (i ), previousLayerClassStates .get (i ));
574
+ }
575
+
576
+ Set <String > previousLayerUnsafe = Set .copyOf (loader .readStringList (UNSAFE_REGISTERED ));
577
+ boolean respectsClassLoader = loader .readInt (RESPECTS_CLASS_LOADER ) == 1 ;
578
+
579
+ return new ClassForNameSupport (Collections .unmodifiableMap (previousLayerClasses ), previousLayerUnsafe , respectsClassLoader );
580
+ }
471
581
}
0 commit comments