Skip to content

Commit f8c2041

Browse files
committed
[GR-34108] Extend boot module layer to include required modules #3821.
PullRequest: graal/9935
2 parents a15cb2b + 8d88db0 commit f8c2041

File tree

2 files changed

+223
-23
lines changed

2 files changed

+223
-23
lines changed

substratevm/src/com.oracle.svm.hosted.jdk11/src/com/oracle/svm/hosted/ModuleLayerFeature.java

Lines changed: 219 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import com.oracle.svm.core.jdk11.BootModuleLayerSupport;
3030
import com.oracle.svm.core.jdk.JDK11OrLater;
3131
import com.oracle.svm.core.util.VMError;
32+
import com.oracle.svm.util.ModuleSupport;
3233
import com.oracle.svm.util.ReflectionUtil;
3334
import org.graalvm.nativeimage.ImageSingletons;
3435
import org.graalvm.nativeimage.Platform;
@@ -45,13 +46,20 @@
4546
import java.lang.reflect.Constructor;
4647
import java.lang.reflect.Field;
4748
import java.lang.reflect.InvocationTargetException;
49+
import java.lang.reflect.Method;
4850
import java.nio.file.Path;
51+
import java.util.ArrayList;
52+
import java.util.Arrays;
53+
import java.util.HashMap;
54+
import java.util.HashSet;
4955
import java.util.List;
5056
import java.util.Map;
57+
import java.util.NoSuchElementException;
5158
import java.util.Optional;
5259
import java.util.Set;
5360
import java.util.function.Function;
5461
import java.util.stream.Collectors;
62+
import java.util.stream.Stream;
5563

5664
/**
5765
* This feature:
@@ -86,10 +94,10 @@
8694
@AutomaticFeature
8795
@Platforms(Platform.HOSTED_ONLY.class)
8896
public final class ModuleLayerFeature implements Feature {
89-
90-
private Field moduleNameToModuleField;
91-
private Field moduleParentsField;
9297
private Constructor<ModuleLayer> moduleLayerConstructor;
98+
private Field moduleLayerNameToModuleField;
99+
private Field moduleLayerParentsField;
100+
private NameToModuleSynthesizer nameToModuleSynthesizer;
93101

94102
@Override
95103
public boolean isInConfiguration(IsInConfigurationAccess access) {
@@ -99,17 +107,19 @@ public boolean isInConfiguration(IsInConfigurationAccess access) {
99107
@Override
100108
public void afterRegistration(AfterRegistrationAccess access) {
101109
ImageSingletons.add(BootModuleLayerSupport.class, new BootModuleLayerSupport());
102-
moduleNameToModuleField = ReflectionUtil.lookupField(ModuleLayer.class, "nameToModule");
103-
moduleParentsField = ReflectionUtil.lookupField(ModuleLayer.class, "parents");
104110
moduleLayerConstructor = ReflectionUtil.lookupConstructor(ModuleLayer.class, Configuration.class, List.class, Function.class);
111+
moduleLayerNameToModuleField = ReflectionUtil.lookupField(ModuleLayer.class, "nameToModule");
112+
moduleLayerParentsField = ReflectionUtil.lookupField(ModuleLayer.class, "parents");
113+
nameToModuleSynthesizer = new NameToModuleSynthesizer();
105114
}
106115

107116
@Override
108117
public void beforeAnalysis(BeforeAnalysisAccess access) {
109118
FeatureImpl.BeforeAnalysisAccessImpl accessImpl = (FeatureImpl.BeforeAnalysisAccessImpl) access;
110-
Map<String, Module> baseModules = ModuleLayer.boot().modules()
119+
Set<String> baseModules = ModuleLayer.boot().modules()
111120
.stream()
112-
.collect(Collectors.toMap(Module::getName, m -> m));
121+
.map(Module::getName)
122+
.collect(Collectors.toSet());
113123
ModuleLayer runtimeBootLayer = synthesizeRuntimeBootLayer(accessImpl.imageClassLoader, baseModules);
114124
BootModuleLayerSupport.instance().setBootLayer(runtimeBootLayer);
115125
}
@@ -119,49 +129,73 @@ public void afterAnalysis(AfterAnalysisAccess access) {
119129
FeatureImpl.AfterAnalysisAccessImpl accessImpl = (FeatureImpl.AfterAnalysisAccessImpl) access;
120130
AnalysisUniverse universe = accessImpl.getUniverse();
121131

122-
Map<String, Module> reachableModules = universe.getTypes()
132+
Stream<Module> analysisReachableModules = universe.getTypes()
123133
.stream()
124134
.filter(t -> t.isReachable() && !t.isArray())
125135
.map(t -> t.getJavaClass().getModule())
126-
.distinct()
127-
.filter(m -> m.isNamed() && !m.getDescriptor().modifiers().contains(ModuleDescriptor.Modifier.SYNTHETIC))
128-
.collect(Collectors.toMap(Module::getName, m -> m));
136+
.distinct();
137+
138+
Set<String> allReachableModules = analysisReachableModules
139+
.filter(Module::isNamed)
140+
.filter(m -> !m.getDescriptor().modifiers().contains(ModuleDescriptor.Modifier.SYNTHETIC))
141+
.flatMap(ModuleLayerFeature::extractRequiredModuleNames)
142+
.collect(Collectors.toSet());
129143

130-
ModuleLayer runtimeBootLayer = synthesizeRuntimeBootLayer(accessImpl.imageClassLoader, reachableModules);
144+
ModuleLayer runtimeBootLayer = synthesizeRuntimeBootLayer(accessImpl.imageClassLoader, allReachableModules);
131145
BootModuleLayerSupport.instance().setBootLayer(runtimeBootLayer);
132146
}
133147

134-
private ModuleLayer synthesizeRuntimeBootLayer(ImageClassLoader cl, Map<String, Module> reachableModules) {
148+
/*
149+
* Creates a stream of module names that are reachable from a given module through "requires"
150+
*/
151+
private static Stream<String> extractRequiredModuleNames(Module m) {
152+
Stream<String> requiredModules = m.getDescriptor().requires().stream().map(ModuleDescriptor.Requires::name);
153+
return Stream.concat(Stream.of(m.getName()), requiredModules);
154+
}
155+
156+
private ModuleLayer synthesizeRuntimeBootLayer(ImageClassLoader cl, Set<String> reachableModules) {
135157
Configuration cf = synthesizeRuntimeBootLayerConfiguration(cl.modulepath(), reachableModules);
136158
try {
137159
ModuleLayer runtimeBootLayer = moduleLayerConstructor.newInstance(cf, List.of(), null);
138-
patchRuntimeBootLayer(runtimeBootLayer, reachableModules);
139-
// Ensure that the lazy field ModuleLayer.modules gets set
140-
runtimeBootLayer.modules();
160+
Map<String, Module> nameToModule = nameToModuleSynthesizer.synthesizeNameToModule(runtimeBootLayer, cl.getClassLoader());
161+
patchRuntimeBootLayer(runtimeBootLayer, nameToModule);
141162
return runtimeBootLayer;
142163
} catch (InstantiationException | IllegalAccessException | InvocationTargetException ex) {
143164
throw VMError.shouldNotReachHere("Failed to synthesize the runtime boot module layer.", ex);
144165
}
145166
}
146167

147-
private static Configuration synthesizeRuntimeBootLayerConfiguration(List<Path> mp, Map<String, Module> reachableModules) {
168+
private static Configuration synthesizeRuntimeBootLayerConfiguration(List<Path> mp, Set<String> reachableModules) {
148169
ModuleFinder beforeFinder = new BootModuleLayerModuleFinder();
149170
ModuleFinder afterFinder = ModuleFinder.of(mp.toArray(Path[]::new));
150-
Set<String> roots = reachableModules.keySet();
171+
151172
try {
152-
return Configuration.empty().resolve(beforeFinder, afterFinder, roots);
173+
ModuleFinder composed = ModuleFinder.compose(beforeFinder, afterFinder);
174+
List<String> missingModules = new ArrayList<>();
175+
for (String module : reachableModules) {
176+
Optional<ModuleReference> mref = composed.find(module);
177+
if (mref.isEmpty()) {
178+
missingModules.add(module);
179+
}
180+
}
181+
reachableModules.removeAll(missingModules);
182+
183+
return Configuration.empty().resolve(beforeFinder, afterFinder, reachableModules);
153184
} catch (FindException | ResolutionException | SecurityException ex) {
154185
throw VMError.shouldNotReachHere("Failed to synthesize the runtime boot module layer configuration.", ex);
155186
}
156187
}
157188

158-
private void patchRuntimeBootLayer(ModuleLayer runtimeBootLayer, Map<String, Module> reachableModules) {
189+
private void patchRuntimeBootLayer(ModuleLayer runtimeBootLayer, Map<String, Module> nameToModule) {
159190
try {
160-
moduleNameToModuleField.set(runtimeBootLayer, reachableModules);
161-
moduleParentsField.set(runtimeBootLayer, List.of(ModuleLayer.empty()));
191+
moduleLayerNameToModuleField.set(runtimeBootLayer, nameToModule);
192+
moduleLayerParentsField.set(runtimeBootLayer, List.of(ModuleLayer.empty()));
162193
} catch (IllegalAccessException ex) {
163194
throw VMError.shouldNotReachHere("Failed to patch the runtime boot module layer.", ex);
164195
}
196+
197+
// Ensure that the lazy modules field gets set
198+
runtimeBootLayer.modules();
165199
}
166200

167201
static class BootModuleLayerModuleFinder implements ModuleFinder {
@@ -184,4 +218,167 @@ public Set<ModuleReference> findAll() {
184218
.collect(Collectors.toSet());
185219
}
186220
}
221+
222+
private static final class NameToModuleSynthesizer {
223+
private final Module everyoneModule;
224+
private final Set<Module> everyoneSet;
225+
private final Constructor<Module> moduleConstructor;
226+
private final Field moduleLayerField;
227+
private final Field moduleReadsField;
228+
private final Field moduleOpenPackagesField;
229+
private final Field moduleExportedPackagesField;
230+
private final Method moduleFindModuleMethod;
231+
232+
NameToModuleSynthesizer() {
233+
Method classGetDeclaredMethods0Method = ReflectionUtil.lookupMethod(Class.class, "getDeclaredFields0", boolean.class);
234+
try {
235+
ModuleSupport.openModuleByClass(Module.class, ModuleLayerFeature.class);
236+
Field[] moduleClassFields = (Field[]) classGetDeclaredMethods0Method.invoke(Module.class, false);
237+
238+
Field everyoneModuleField = findFieldByName(moduleClassFields, "EVERYONE_MODULE");
239+
everyoneModuleField.setAccessible(true);
240+
everyoneModule = (Module) everyoneModuleField.get(null);
241+
242+
moduleLayerField = findFieldByName(moduleClassFields, "layer");
243+
moduleReadsField = findFieldByName(moduleClassFields, "reads");
244+
moduleOpenPackagesField = findFieldByName(moduleClassFields, "openPackages");
245+
moduleExportedPackagesField = findFieldByName(moduleClassFields, "exportedPackages");
246+
moduleLayerField.setAccessible(true);
247+
moduleReadsField.setAccessible(true);
248+
moduleOpenPackagesField.setAccessible(true);
249+
moduleExportedPackagesField.setAccessible(true);
250+
} catch (ReflectiveOperationException | NoSuchElementException ex) {
251+
throw VMError.shouldNotReachHere("Failed to find the value of EVERYONE_MODULE field of Module class.", ex);
252+
}
253+
everyoneSet = Set.of(everyoneModule);
254+
moduleConstructor = ReflectionUtil.lookupConstructor(Module.class, ClassLoader.class, ModuleDescriptor.class);
255+
moduleFindModuleMethod = ReflectionUtil.lookupMethod(Module.class, "findModule", String.class, Map.class, Map.class, List.class);
256+
}
257+
258+
private static Field findFieldByName(Field[] fields, String name) {
259+
return Arrays.stream(fields).filter(f -> f.getName().equals(name)).findAny().get();
260+
}
261+
262+
/**
263+
* This method creates Module instances that will populate the runtime boot module layer of
264+
* the image. This implementation is copy-pasted from Module#defineModules(Configuration,
265+
* Function, ModuleLayer) with few simplifications (removing multiple classloader support)
266+
* and removal of VM state updates (otherwise we would be re-defining modules to the host
267+
* VM).
268+
*/
269+
Map<String, Module> synthesizeNameToModule(ModuleLayer runtimeBootLayer, ClassLoader cl)
270+
throws IllegalAccessException, InvocationTargetException, InstantiationException {
271+
Configuration cf = runtimeBootLayer.configuration();
272+
273+
int cap = (int) (cf.modules().size() / 0.75f + 1.0f);
274+
Map<String, Module> nameToModule = new HashMap<>(cap);
275+
276+
/*
277+
* Remove mapping of modules to classloaders. Create module instances without defining
278+
* them to the VM
279+
*/
280+
for (ResolvedModule resolvedModule : cf.modules()) {
281+
ModuleReference mref = resolvedModule.reference();
282+
ModuleDescriptor descriptor = mref.descriptor();
283+
String name = descriptor.name();
284+
Module m = moduleConstructor.newInstance(cl, descriptor);
285+
moduleLayerField.set(m, runtimeBootLayer);
286+
nameToModule.put(name, m);
287+
}
288+
289+
/*
290+
* Setup readability and exports/opens. This part is unchanged, save for field setters
291+
* and VM update removals
292+
*/
293+
for (ResolvedModule resolvedModule : cf.modules()) {
294+
ModuleReference mref = resolvedModule.reference();
295+
ModuleDescriptor descriptor = mref.descriptor();
296+
297+
String mn = descriptor.name();
298+
Module m = nameToModule.get(mn);
299+
assert m != null;
300+
301+
Set<Module> reads = new HashSet<>();
302+
for (ResolvedModule other : resolvedModule.reads()) {
303+
Module m2 = nameToModule.get(other.name());
304+
reads.add(m2);
305+
}
306+
moduleReadsField.set(m, reads);
307+
308+
if (!descriptor.isOpen() && !descriptor.isAutomatic()) {
309+
if (descriptor.opens().isEmpty()) {
310+
Map<String, Set<Module>> exportedPackages = new HashMap<>();
311+
for (ModuleDescriptor.Exports exports : m.getDescriptor().exports()) {
312+
String source = exports.source();
313+
if (exports.isQualified()) {
314+
Set<Module> targets = new HashSet<>();
315+
for (String target : exports.targets()) {
316+
Module m2 = nameToModule.get(target);
317+
if (m2 != null) {
318+
targets.add(m2);
319+
}
320+
}
321+
if (!targets.isEmpty()) {
322+
exportedPackages.put(source, targets);
323+
}
324+
} else {
325+
exportedPackages.put(source, everyoneSet);
326+
}
327+
}
328+
moduleExportedPackagesField.set(m, exportedPackages);
329+
} else {
330+
Map<String, Set<Module>> openPackages = new HashMap<>();
331+
Map<String, Set<Module>> exportedPackages = new HashMap<>();
332+
for (ModuleDescriptor.Opens opens : descriptor.opens()) {
333+
String source = opens.source();
334+
if (opens.isQualified()) {
335+
Set<Module> targets = new HashSet<>();
336+
for (String target : opens.targets()) {
337+
Module m2 = (Module) moduleFindModuleMethod.invoke(null, target, Map.of(), nameToModule, runtimeBootLayer.parents());
338+
if (m2 != null) {
339+
targets.add(m2);
340+
}
341+
}
342+
if (!targets.isEmpty()) {
343+
openPackages.put(source, targets);
344+
}
345+
} else {
346+
openPackages.put(source, everyoneSet);
347+
}
348+
}
349+
350+
for (ModuleDescriptor.Exports exports : descriptor.exports()) {
351+
String source = exports.source();
352+
Set<Module> openToTargets = openPackages.get(source);
353+
if (openToTargets != null && openToTargets.contains(everyoneModule)) {
354+
continue;
355+
}
356+
357+
if (exports.isQualified()) {
358+
Set<Module> targets = new HashSet<>();
359+
for (String target : exports.targets()) {
360+
Module m2 = (Module) moduleFindModuleMethod.invoke(null, target, Map.of(), nameToModule, runtimeBootLayer.parents());
361+
if (m2 != null) {
362+
if (openToTargets == null || !openToTargets.contains(m2)) {
363+
targets.add(m2);
364+
}
365+
}
366+
}
367+
if (!targets.isEmpty()) {
368+
exportedPackages.put(source, targets);
369+
}
370+
} else {
371+
exportedPackages.put(source, everyoneSet);
372+
}
373+
}
374+
375+
moduleOpenPackagesField.set(m, openPackages);
376+
moduleExportedPackagesField.set(m, exportedPackages);
377+
}
378+
}
379+
}
380+
381+
return nameToModule;
382+
}
383+
}
187384
}

substratevm/src/com.oracle.svm.util/src/com/oracle/svm/util/ReflectionUtil.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,10 @@
3131
import java.lang.reflect.Method;
3232

3333
/**
34-
* This class contains utility methods for commonly used reflection functionality.
34+
* This class contains utility methods for commonly used reflection functionality. Note that lookups
35+
* will not work on JDK 17 in cases when the field/method is filtered. See
36+
* jdk.internal.reflect.Reflection#fieldFilterMap for more information or
37+
* com.oracle.svm.hosted.ModuleLayerFeature for an example of a workaround in such cases.
3538
*/
3639
public final class ReflectionUtil {
3740

0 commit comments

Comments
 (0)