IKVM11  11
Java SE 11 Virtual Machine for .NET
Loading...
Searching...
No Matches
ModuleResolver.cs
Go to the documentation of this file.
1using System;
2using System.Collections.Generic;
3using System.Collections.Immutable;
4using System.Diagnostics.CodeAnalysis;
5using System.Linq;
6
7using IKVM.ByteCode;
9
11{
12
16 sealed class ModuleResolver
17 {
18
19 readonly IModuleFinder _beforeFinder;
20 readonly ImmutableArray<ModuleConfiguration> _parents;
21 readonly IModuleFinder _afterFinder;
22 readonly string? _targetPlatform;
23 readonly Dictionary<string, ModuleReference> _nameToReference = new();
24
25 bool _haveAllAutomaticModules;
26
33 public ModuleResolver(IModuleFinder beforeFinder, ImmutableArray<ModuleConfiguration> parents, IModuleFinder afterFinder)
34 {
35 _beforeFinder = beforeFinder;
36 _parents = parents;
37 _afterFinder = afterFinder;
38
39 foreach (var parent in _parents)
40 {
41 var value = parent.TargetPlatform;
42 if (value is not null)
43 {
44 if (_targetPlatform is null)
45 {
46 _targetPlatform = value;
47 }
48 else
49 {
50 if (value != _targetPlatform)
51 throw new ArgumentException($"Parents have conflicting constraints on target platform: {_targetPlatform}, {value}.", nameof(parents));
52 }
53 }
54 }
55 }
56
60 public string? TargetPlatform => _targetPlatform;
61
67 internal ModuleResolver Resolve(ImmutableArray<string> roots)
68 {
69 var q = new ArrayDeque<ModuleDescriptor>();
70
71 foreach (var root in roots)
72 {
73 var mref = FindWithBeforeFinder(root);
74 if (mref is null)
75 {
76 if (FindInParent(root) is not null)
77 continue;
78
79 mref = FindWithAfterFinder(root);
80 if (mref is null)
81 throw new FindException($"Module {root} not found.");
82 }
83
84 AddFoundModule(mref);
85 q.InsertFirst(mref.Descriptor);
86 }
87
88 Resolve(q);
89
90 return this;
91 }
92
100 HashSet<ModuleDescriptor> Resolve(ArrayDeque<ModuleDescriptor> q)
101 {
102 var resolved = new HashSet<ModuleDescriptor>();
103
104 while (q.IsEmpty == false)
105 {
106 var descriptor = q.RemoveFirst();
107 if (_nameToReference.ContainsKey(descriptor.Name) == false)
108 throw new InvalidOperationException();
109
110 // if the module is an automatic module then all automatic modules need to be resolved
111 if (descriptor.IsAutomatic && !_haveAllAutomaticModules)
112 {
113 foreach (var mref in AddFoundAutomaticModules())
114 q.InsertLast(mref.Descriptor);
115
116 _haveAllAutomaticModules = true;
117 }
118
119 // process dependences
120 foreach (var requires in descriptor.Requires)
121 {
122 // only required at compile-time
123 if ((requires.Modifiers & ByteCode.ModuleRequiresFlag.StaticPhase) != 0)
124 continue;
125
126 var dn = requires.Name;
127
128 // find dependence
129 var mref = FindWithBeforeFinder(dn);
130 if (mref == null)
131 {
132 if (FindInParent(dn) != null)
133 continue; // dependence is in parent
134
135 mref = FindWithAfterFinder(dn);
136 if (mref == null)
137 throw new FindException($"Module {dn} not found, required by {descriptor}.");
138 }
139
140 if (_nameToReference.ContainsKey(dn) == false)
141 {
142 AddFoundModule(mref);
143 q.InsertLast(mref.Descriptor);
144 }
145
146 }
147
148 resolved.Add(descriptor);
149 }
150
151 return resolved;
152 }
153
158 HashSet<ModuleReference> AddFoundAutomaticModules()
159 {
160 var result = new HashSet<ModuleReference>();
161
162 foreach (var mref in FindAll())
163 {
164 var mn = mref.Descriptor.Name;
165 if (mref.Descriptor.IsAutomatic && !_nameToReference.ContainsKey(mn))
166 {
167 AddFoundModule(mref);
168 result.Add(mref);
169 }
170 }
171
172 return result;
173 }
174
180 void AddFoundModule(ModuleReference mref)
181 {
182 _nameToReference.Add(mref.Descriptor.Name, mref);
183 }
184
190 internal ImmutableDictionary<ResolvedModule, ImmutableHashSet<ResolvedModule>> Finish(ModuleConfiguration cf)
191 {
192 DetectCycles();
193 CheckHashes();
194 var graph = MakeGraph(cf);
195 CheckExportSuppliers(graph);
196 return graph;
197 }
198
202 void DetectCycles()
203 {
204 var visited = new HashSet<ModuleDescriptor>();
205 var visitedPath = new Stack<ModuleDescriptor>();
206 var visitedPathSet = new HashSet<ModuleDescriptor>();
207
208 foreach (var mref in _nameToReference.Values)
209 Visit(mref.Descriptor, visited, visitedPath, visitedPathSet);
210 }
211
217 void Visit(ModuleDescriptor descriptor, HashSet<ModuleDescriptor> visited, Stack<ModuleDescriptor> visitedPath, HashSet<ModuleDescriptor> visitedPathSet)
218 {
219 if (visited.Contains(descriptor) == false)
220 {
221 var added = visitedPathSet.Add(descriptor);
222 if (added == false)
223 throw new InvalidOperationException($"Cycle detected: {CycleToString(descriptor, visitedPath)}.");
224
225 visitedPath.Push(descriptor);
226
227 // visit the requires branch to detect cycles
228 foreach (var requires in descriptor.Requires)
229 {
230 if (_nameToReference.TryGetValue(requires.Name, out var mref))
231 {
232 var other = mref.Descriptor;
233 if (other != descriptor)
234 Visit(other, visited, visitedPath, visitedPathSet);
235 }
236 }
237
238 visitedPathSet.Remove(descriptor);
239 visitedPath.Pop();
240
241 visited.Add(descriptor);
242 }
243 }
244
251 string CycleToString(ModuleDescriptor descriptor, Stack<ModuleDescriptor> visitedPath)
252 {
253 // existing path with current descriptor appended
254 var l = new List<ModuleDescriptor>(visitedPath);
255 l.Add(descriptor);
256 var p = l.IndexOf(descriptor);
257
258 return string.Join(" -> ", l.Skip(p).Select(i => i.Name));
259 }
260
264 void CheckHashes()
265 {
266 foreach (var mref in _nameToReference.Values)
267 {
268 continue; // TODO implement hashes
269 }
270 }
271
282 ImmutableDictionary<ResolvedModule, ImmutableHashSet<ResolvedModule>> MakeGraph(ModuleConfiguration cf)
283 {
284 // the "reads" graph starts as a module dependence graph and is iteratively updated to be the readability graph
285 var g1 = ImmutableDictionary.CreateBuilder<ResolvedModule, ImmutableHashSet<ResolvedModule>>();
286
287 // need "requires transitive" from the modules in parent configurations
288 // as there may be selected modules that have a dependency on modules in
289 // the parent configuration.
290 var g2 = _parents
291 .SelectMany(i => i.All())
292 .Distinct()
293 .SelectMany(c => c.Modules
294 .SelectMany(m1 => m1.Descriptor.Requires
295 .Where(r => (r.Modifiers & ModuleRequiresFlag.Transitive) != 0)
296 .Select(r => c.FindModule(r.Name))
297 .Where(m2 => m2 != null)
298 .ToDictionary(m2 => m1, m2 => m2)))
299 .GroupBy(i => i.Key)
300 .ToDictionary(i => i.Key, i => i.Select(j => j.Value!).ToHashSet()!);
301
302 var nameToResolved = new Dictionary<string, ResolvedModule>(_nameToReference.Count);
303
304 foreach (var mref in _nameToReference.Values)
305 {
306 var desc = mref.Descriptor;
307 var name = mref.Descriptor.Name;
308
309 var m1 = GetOrAddResolvedModule(nameToResolved, name, cf, mref);
310
311 var reads = ImmutableHashSet.CreateBuilder<ResolvedModule>();
312 var requiresTransitive = new HashSet<ResolvedModule>();
313
314 foreach (var requires in desc.Requires)
315 {
316 var dn = requires.Name;
317
318 ResolvedModule? m2;
319 if (_nameToReference.TryGetValue(dn, out var mref2))
320 {
321 m2 = GetOrAddResolvedModule(nameToResolved, dn, cf, mref2);
322 }
323 else
324 {
325 m2 = FindInParent(dn);
326 if (m2 is null)
327 continue;
328
329 if (m2.Descriptor.IsAutomatic)
330 foreach (var d in m2.Reads)
331 if (d.Descriptor.IsAutomatic)
332 reads.Add(d);
333 }
334
335 reads.Add(m2);
336
337 if ((requires.Modifiers & ModuleRequiresFlag.Transitive) != 0)
338 requiresTransitive.Add(m2);
339 }
340
341 // automatic modules read all selected modules and all modules in parent configurations
342 if (desc.IsAutomatic)
343 {
344 foreach (var mref2 in _nameToReference.Values)
345 {
346 var desc2 = mref2.Descriptor;
347 var name2 = desc2.Name;
348
349 if (name != name2)
350 {
351 var m2 = GetOrAddResolvedModule(nameToResolved, name2, cf, mref2);
352 reads.Add(m2);
353 if (desc2.IsAutomatic)
354 requiresTransitive.Add(m2);
355 }
356 }
357
358 // reads all module sin parent configurations
359 foreach (var parent in _parents)
360 {
361 foreach (var m in parent.All().SelectMany(i => i.Modules))
362 {
363 reads.Add(m);
364 if (m.Reference.Descriptor.IsAutomatic)
365 requiresTransitive.Add(m);
366 }
367 }
368 }
369
370 g1.Add(m1, reads.ToImmutable());
371 g2.Add(m1, requiresTransitive!);
372 }
373
374 var staging = new List<ResolvedModule>();
375 var changed = false;
376 do
377 {
378 changed = false;
379
380 foreach (var e in g1)
381 {
382 var m1 = e.Key;
383
384 // automatic module already reads all selected modules so nothing to propagate
385 if (m1.Descriptor.IsAutomatic == false)
386 {
387 foreach (var m2 in e.Value)
388 if (g2.TryGetValue(m2, out var m2RequiresTransitive))
389 foreach (var m3 in m2RequiresTransitive)
390 if (e.Value.Contains(m3) == false)
391 staging.Add(m3);
392
393 if (staging.Count > 0)
394 {
395 foreach (var i in staging)
396 e.Value.Add(i);
397
398 staging.Clear();
399 changed = true;
400 }
401 }
402 }
403 }
404 while (changed);
405
406 return g1.ToImmutable();
407 }
408
417 ResolvedModule GetOrAddResolvedModule(Dictionary<string, ResolvedModule> map, string name, ModuleConfiguration cf, ModuleReference mref)
418 {
419 if (map.TryGetValue(name, out var m) == false)
420 {
421 m = new ResolvedModule(cf, mref);
422 map.Add(name, m);
423 }
424
425 return m;
426 }
427
442 void CheckExportSuppliers(ImmutableDictionary<ResolvedModule, ImmutableHashSet<ResolvedModule>> graph)
443 {
444 foreach (var e in graph)
445 {
446 var desc1 = e.Key.Descriptor;
447 var name1 = desc1.Name;
448
449 // the names of the modules that are read (including self)
450 var names = new HashSet<string>();
451 names.Add(name1);
452
453 // the map of packages that are local or exported to descriptor1
454 var packageToExporter = new Dictionary<string, ModuleDescriptor>();
455
456 // local packages
457 var packages = desc1.Packages;
458 foreach (var pn in packages)
459 packageToExporter.Add(pn, desc1);
460
461 // descriptor1 reads descriptor2
462 foreach (var endpoint in e.Value)
463 {
464 var desc2 = endpoint.Descriptor;
465 var name2 = desc2.Name;
466
467 if (desc2 != desc1 && names.Add(name2) == false)
468 {
469 if (name2 == name1)
470 throw new InvalidOperationException($"Module {name1} reads another module named {name1}.");
471 else
472 throw new InvalidOperationException($"Module {name1} reads more than on module named {name2}.");
473 }
474
475 if (desc2.IsAutomatic)
476 {
477 // automatic modules read self and export all packages
478 if (desc2 != desc1)
479 foreach (var source in desc2.Packages)
480 if (TryAdd(packageToExporter, source, desc2, out var supplier) == false)
481 throw FailTwoSuppliers(desc1, source, desc2, supplier); // descriptor2 and 'supplier' export source to descriptor1
482 }
483 else
484 {
485 foreach (var export in desc2.Exports)
486 {
487 if (export.IsQualified)
488 if (export.Targets.Contains(desc1.Name) == false)
489 continue;
490
491 if (TryAdd(packageToExporter, export.Source, desc2, out var supplier) == false)
492 throw FailTwoSuppliers(desc1, export.Source, desc2, supplier); // descriptor2 and 'supplier' export source to descriptor1
493 }
494 }
495 }
496
497 // uses/provides checks not applicable to automatic modules
498 if (desc1.IsAutomatic == false)
499 {
500 // uses S
501 foreach (var service in desc1.Uses)
502 {
503 var pn = PackageName(service);
504 if (packageToExporter.ContainsKey(pn) == false && RequiresStaticMissingModule(desc1, e.Value) == false)
505 throw new ResolutionException($"Module {desc1.Name} uses {service} but does not read a module that exports {pn} to {desc1.Name}.");
506 }
507
508 // provides S
509 foreach (var provides in desc1.Provides)
510 {
511 var pn = PackageName(provides.Service);
512 if (packageToExporter.ContainsKey(pn) == false && RequiresStaticMissingModule(desc1, e.Value) == false)
513 throw new ResolutionException($"Module {desc1.Name} provides {provides.Service} but does not read a module that exports {pn} to {desc1.Name}.");
514 }
515 }
516 }
517 }
518
526 bool RequiresStaticMissingModule(ModuleDescriptor descriptor, ImmutableHashSet<ResolvedModule> reads)
527 {
528 var moduleNames = reads
529 .Select(i => i.Name)
530 .ToHashSet();
531
532 foreach (var r in descriptor.Requires)
533 if ((r.Modifiers & ByteCode.ModuleRequiresFlag.StaticPhase) != 0 && moduleNames.Contains(r.Name) == false)
534 return true;
535
536 foreach (var rm in reads)
537 foreach (var r in rm.Descriptor.Requires)
538 if ((r.Modifiers & ModuleRequiresFlag.StaticPhase) != 0 && (r.Modifiers & ModuleRequiresFlag.Transitive) != 0 && moduleNames.Contains(r.Name) == false)
539 return true;
540
541 return false;
542 }
543
549 ResolvedModule? FindInParent(string mn)
550 {
551 foreach (var parent in _parents)
552 if (parent.FindModule(mn) is { } m)
553 return m;
554
555 return null;
556 }
557
563 ModuleReference? FindWithBeforeFinder(string mn)
564 {
565 return _beforeFinder.Find(mn);
566 }
567
573 ModuleReference? FindWithAfterFinder(string mn)
574 {
575 return _afterFinder.Find(mn);
576 }
577
582 ImmutableHashSet<ModuleReference> FindAll()
583 {
584 var bModules = _beforeFinder.FindAll();
585 var aModules = _afterFinder.FindAll();
586
587 if (aModules.IsEmpty)
588 return bModules;
589
590 if (bModules.IsEmpty && _parents.IsEmpty)
591 return aModules;
592
593 var result = ImmutableHashSet.CreateBuilder<ModuleReference>();
594 foreach (var mref in aModules)
595 if (_beforeFinder.Find(mref.Descriptor.Name) is null && FindInParent(mref.Descriptor.Name) is null)
596 result.Add(mref);
597
598 return result.ToImmutable();
599 }
600
606 string PackageName(string className)
607 {
608 int index = className.LastIndexOf('.');
609 return index == -1 ? "" : className.Substring(0, index);
610 }
611
622 bool TryAdd<TKey, TValue>(IDictionary<TKey, TValue> self, TKey key, TValue value, [MaybeNullWhen(false)] out TValue existing)
623 {
624 if (self.TryGetValue(key, out existing))
625 return false;
626
627 self.Add(key, value);
628 existing = value;
629 return true;
630 }
631
642 Exception FailTwoSuppliers(ModuleDescriptor descriptor, string source, ModuleDescriptor supplier1, ModuleDescriptor supplier2)
643 {
644 if (supplier2 == descriptor)
645 {
646 var tmp = supplier1;
647 supplier1 = supplier2;
648 supplier2 = tmp;
649 }
650
651 if (supplier1 == descriptor)
652 {
653 return new ResolutionException($"Module {descriptor.Name} contains package {source}, module {supplier2.Name} exports package {source} to {descriptor.Name}.");
654 }
655 else
656 {
657 return new ResolutionException($"Modules {supplier1.Name} and {supplier2.Name} export package {source} to module {descriptor.Name}.");
658 }
659 }
660
661 }
662
663}
The resolver used by ModuleConfiguration and ModuleConfiguration.Resolve(IModuleFinder,...
ModuleResolver(IModuleFinder beforeFinder, ImmutableArray< ModuleConfiguration > parents, IModuleFinder afterFinder)
Initializes a new instance.
string? TargetPlatform
Gets the constraint on the target platform.