2828import static jdk .graal .compiler .options .OptionStability .EXPERIMENTAL ;
2929
3030import java .lang .reflect .Modifier ;
31+ import java .util .ArrayList ;
32+ import java .util .Collections ;
3133import java .util .EnumSet ;
34+ import java .util .HashMap ;
3235import java .util .HashSet ;
36+ import java .util .List ;
37+ import java .util .Map ;
3338import java .util .Objects ;
3439import java .util .Set ;
3540import java .util .function .BooleanSupplier ;
41+ import java .util .function .Consumer ;
3642
3743import org .graalvm .collections .EconomicMap ;
3844import org .graalvm .nativeimage .Platform ;
4652import com .oracle .svm .core .configure .RuntimeConditionSet ;
4753import com .oracle .svm .core .feature .AutomaticallyRegisteredImageSingleton ;
4854import com .oracle .svm .core .hub .registry .ClassRegistries ;
55+ import com .oracle .svm .core .layeredimagesingleton .ImageSingletonLoader ;
56+ import com .oracle .svm .core .layeredimagesingleton .ImageSingletonWriter ;
4957import com .oracle .svm .core .layeredimagesingleton .LayeredImageSingletonBuilderFlags ;
5058import com .oracle .svm .core .layeredimagesingleton .LayeredImageSingletonSupport ;
5159import com .oracle .svm .core .layeredimagesingleton .MultiLayeredImageSingleton ;
52- import com .oracle .svm .core .layeredimagesingleton .UnsavedSingleton ;
5360import com .oracle .svm .core .metadata .MetadataTracer ;
5461import com .oracle .svm .core .option .HostedOptionKey ;
62+ import com .oracle .svm .core .option .LayerVerifiedOption ;
5563import com .oracle .svm .core .reflect .MissingReflectionRegistrationUtils ;
5664import com .oracle .svm .core .util .ImageHeapMap ;
5765import com .oracle .svm .core .util .VMError ;
5866
5967import jdk .graal .compiler .options .Option ;
6068
6169@ 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" ;
6376
6477 public static final class Options {
78+ @ LayerVerifiedOption (kind = LayerVerifiedOption .Kind .Changed , severity = LayerVerifiedOption .Severity .Error )//
6579 @ Option (help = "Class.forName and similar respect their class loader argument." , stability = EXPERIMENTAL )//
6680 public static final HostedOptionKey <Boolean > ClassForNameRespectsClassLoader = new HostedOptionKey <>(false );
6781 }
@@ -98,7 +112,8 @@ private static ClassForNameSupport[] layeredSingletons() {
98112 }
99113
100114 /**
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.
102117 */
103118 private final EconomicMap <String , ConditionalRuntimeValue <Object >> knownClasses ;
104119 /**
@@ -112,14 +127,36 @@ private static ClassForNameSupport[] layeredSingletons() {
112127 */
113128 private final EconomicMap <String , Throwable > knownExceptions ;
114129 /**
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.
116132 */
117133 private final EconomicMap <Class <?>, RuntimeConditionSet > unsafeInstantiatedClasses ;
118134
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+
119152 private static final Object NEGATIVE_QUERY = new Object ();
120153
121154 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 ) {
123160 knownClasses = null ;
124161 knownClassNames = ImageHeapMap .createNonLayeredMap ();
125162 knownExceptions = ImageHeapMap .createNonLayeredMap ();
@@ -129,6 +166,8 @@ public ClassForNameSupport() {
129166 knownExceptions = null ;
130167 }
131168 unsafeInstantiatedClasses = ImageHeapMap .createNonLayeredMap ();
169+ this .previousLayerClasses = previousLayerClasses ;
170+ this .previousLayerUnsafe = previousLayerUnsafe ;
132171 }
133172
134173 public static boolean respectClassLoader () {
@@ -177,15 +216,15 @@ public void registerClass(ConfigurationCondition condition, Class<?> clazz, Clas
177216 currentValue == clazz ) {
178217 currentValue = clazz ;
179218 var cond = updateConditionalValue (existingEntry , currentValue , condition );
180- knownClasses . put (name , cond );
219+ addKnownClass (name , cond );
181220 } else if (currentValue instanceof Throwable ) { // failed at linking time
182221 var cond = updateConditionalValue (existingEntry , currentValue , condition );
183222 /*
184223 * If the class has already been seen as throwing an error, we don't overwrite
185224 * this error. Nevertheless, we have to update the set of conditionals to be
186225 * correct.
187226 */
188- knownClasses . put (name , cond );
227+ addKnownClass (name , cond );
189228 } else {
190229 throw VMError .shouldNotReachHere ("""
191230 Invalid Class.forName value for %s: %s
@@ -199,6 +238,17 @@ accessible through the builder class loader, and it was already registered by na
199238 }
200239 }
201240
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+
202252 @ Platforms (HOSTED_ONLY .class )
203253 private boolean isLibGraalClass (Class <?> clazz ) {
204254 return libGraalLoader != null && clazz .getClassLoader () == libGraalLoader ;
@@ -260,19 +310,24 @@ private void registerKnownClassName(ConfigurationCondition condition, String cla
260310 public void registerUnsafeAllocated (ConfigurationCondition condition , Class <?> clazz ) {
261311 if (!clazz .isArray () && !clazz .isInterface () && !Modifier .isAbstract (clazz .getModifiers ())) {
262312 /* 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+ }
266318 }
267319 }
268320 }
269321
270322 private void updateCondition (ConfigurationCondition condition , String className , Object value ) {
271323 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 );
276331 }
277332 }
278333
@@ -468,4 +523,59 @@ public static boolean canUnsafeInstantiateAsInstance(DynamicHub hub) {
468523 public EnumSet <LayeredImageSingletonBuilderFlags > getImageBuilderFlags () {
469524 return LayeredImageSingletonBuilderFlags .ALL_ACCESS ;
470525 }
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+ }
471581}
0 commit comments