Skip to content

Commit b817096

Browse files
committed
[GR-66292] Deduplicate ClassForNameSupport data in extension layers
PullRequest: graal/21186
2 parents 7255e6e + 33367e4 commit b817096

File tree

1 file changed

+124
-14
lines changed

1 file changed

+124
-14
lines changed

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/ClassForNameSupport.java

Lines changed: 124 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,17 @@
2828
import static jdk.graal.compiler.options.OptionStability.EXPERIMENTAL;
2929

3030
import java.lang.reflect.Modifier;
31+
import java.util.ArrayList;
32+
import java.util.Collections;
3133
import java.util.EnumSet;
34+
import java.util.HashMap;
3235
import java.util.HashSet;
36+
import java.util.List;
37+
import java.util.Map;
3338
import java.util.Objects;
3439
import java.util.Set;
3540
import java.util.function.BooleanSupplier;
41+
import java.util.function.Consumer;
3642

3743
import org.graalvm.collections.EconomicMap;
3844
import org.graalvm.nativeimage.Platform;
@@ -46,22 +52,30 @@
4652
import com.oracle.svm.core.configure.RuntimeConditionSet;
4753
import com.oracle.svm.core.feature.AutomaticallyRegisteredImageSingleton;
4854
import com.oracle.svm.core.hub.registry.ClassRegistries;
55+
import com.oracle.svm.core.layeredimagesingleton.ImageSingletonLoader;
56+
import com.oracle.svm.core.layeredimagesingleton.ImageSingletonWriter;
4957
import com.oracle.svm.core.layeredimagesingleton.LayeredImageSingletonBuilderFlags;
5058
import com.oracle.svm.core.layeredimagesingleton.LayeredImageSingletonSupport;
5159
import com.oracle.svm.core.layeredimagesingleton.MultiLayeredImageSingleton;
52-
import com.oracle.svm.core.layeredimagesingleton.UnsavedSingleton;
5360
import com.oracle.svm.core.metadata.MetadataTracer;
5461
import com.oracle.svm.core.option.HostedOptionKey;
62+
import com.oracle.svm.core.option.LayerVerifiedOption;
5563
import com.oracle.svm.core.reflect.MissingReflectionRegistrationUtils;
5664
import com.oracle.svm.core.util.ImageHeapMap;
5765
import com.oracle.svm.core.util.VMError;
5866

5967
import 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

Comments
 (0)