IKVM11  11
Java SE 11 Virtual Machine for .NET
Loading...
Searching...
No Matches
ModuleDescriptor.cs
Go to the documentation of this file.
1using System;
2using System.Collections;
3using System.Collections.Generic;
4using System.Collections.Immutable;
5using System.Linq;
6using System.Text;
7
8using IKVM.ByteCode;
9using IKVM.ByteCode.Decoding;
11
13{
14
18 internal readonly partial struct ModuleDescriptor : IComparable<ModuleDescriptor>
19 {
20
21 public static bool operator ==(ModuleDescriptor x, ModuleDescriptor y) => x.Equals(y);
22
23 public static bool operator !=(ModuleDescriptor x, ModuleDescriptor y) => x.Equals(y) == false;
24
25 public static bool operator <(ModuleDescriptor x, ModuleDescriptor y) => Compare(x, y) < 0;
26
27 public static bool operator >(ModuleDescriptor x, ModuleDescriptor y) => Compare(x, y) > 0;
28
29 public static bool operator <=(ModuleDescriptor x, ModuleDescriptor y) => Compare(x, y) <= 0;
30
31 public static bool operator >=(ModuleDescriptor x, ModuleDescriptor y) => Compare(x, y) >= 0;
32
33 public static int Compare(ModuleDescriptor x, ModuleDescriptor y) => x.CompareTo(y);
34
42 public static ModuleDescriptor Read(ClassFile clazz)
43 {
44 if (clazz is null)
45 throw new ArgumentNullException(nameof(clazz));
46
47 try
48 {
49 if (clazz.Version < 53)
50 throw new InvalidModuleDescriptorException($"Unsupported class file version '{clazz.Version}'.");
51
52 if ((clazz.AccessFlags & AccessFlag.Module) != AccessFlag.Module)
53 throw new InvalidModuleDescriptorException("AccessFlags should be ACC_MODULE.");
54
55 var clazzName = clazz.Constants.Get(clazz.This).Name;
56 if (clazzName != "module-info")
57 throw new InvalidModuleDescriptorException("Class name should be 'module-info'.");
58
59 if (clazz.Super.IsNotNil)
60 throw new InvalidModuleDescriptorException("Bad super class.");
61
62 if (clazz.Fields.Count > 0)
63 throw new InvalidModuleDescriptorException("Bad fields.");
64
65 if (clazz.Methods.Count > 0)
66 throw new InvalidModuleDescriptorException("Bad methods.");
67
68 if (clazz.Interfaces.Count > 0)
69 throw new InvalidModuleDescriptorException("Bad interfaces.");
70
71 var builder = ReadModuleAttribute(clazz);
72 ReadModulePackagesAttribute(clazz, builder);
73 ReadModuleMainClassAttribute(clazz, builder);
74
75 var descriptor = builder.Build();
76
77 // various checks based on whether we are reading java.base or not
78 if (descriptor.Name == "java.base")
79 {
80 // building java.base itself: should have no requires
81 if (descriptor.Requires.Count > 0)
82 throw new InvalidModuleDescriptorException("The requires table for java.base must be 0 length.");
83 }
84 else
85 {
86 // modules must explicitely require java.base
87 if (descriptor.Requires.Any(i => i.Name == "java.base") == false)
88 throw new InvalidModuleDescriptorException("The requires table must have an entry for java.base.");
89 }
90
91 return descriptor;
92 }
93 catch (ByteCodeException e)
94 {
95 throw new InvalidModuleDescriptorException("Unable to read module descriptor.", e);
96 }
97 }
98
105 static Builder ReadModuleAttribute(ClassFile clazz)
106 {
107 var attribute = clazz.Attributes.FirstOrDefault(i => i.IsNotNil && i.Name.IsNotNil && clazz.Constants.Get(i.Name).Value == AttributeName.Module);
108 if (attribute.IsNil)
109 throw new InvalidModuleDescriptorException($"Attribute '{AttributeName.Module}' not found.");
110
111 return ReadModuleAttribute(clazz, (ModuleAttribute)attribute);
112 }
113
121 static Builder ReadModuleAttribute(ClassFile clazz, in ModuleAttribute attribute)
122 {
123 var moduleName = clazz.Constants.Get(attribute.Name).Name;
124 if (moduleName is not null)
125 moduleName = DecodeModuleName(moduleName);
126 if (moduleName is null || string.IsNullOrEmpty(moduleName))
127 throw new InvalidModuleDescriptorException("Module name not found.");
128
129 var modifiers = default(ModuleFlag);
130 if ((attribute.Flags & ModuleFlag.Open) != 0)
131 modifiers |= ModuleFlag.Open;
132 if ((attribute.Flags & ModuleFlag.Synthetic) != 0)
133 modifiers |= ModuleFlag.Synthetic;
134 if ((attribute.Flags & ModuleFlag.Mandated) != 0)
135 modifiers |= ModuleFlag.Mandated;
136
137 var builder = CreateModule(moduleName, modifiers);
138
139 if (attribute.Version.IsNotNil)
140 builder = builder.Version(clazz.Constants.Get(attribute.Version).Value);
141
142 ReadModuleRequires(clazz, attribute, builder);
143 ReadModuleExports(clazz, attribute, builder);
144 ReadModuleOpens(clazz, attribute, builder);
145 ReadModuleUses(clazz, attribute, builder);
146 ReadModuleProvides(clazz, attribute, builder);
147
148 return builder;
149 }
150
156 static void ReadModulePackagesAttribute(ClassFile clazz, Builder builder)
157 {
158 var attribute = clazz.Attributes.FirstOrDefault(i => i.IsNotNil && i.Name.IsNotNil && clazz.Constants.Get(i.Name).Value == AttributeName.ModulePackages);
159 if (attribute.IsNil)
160 return;
161
162 ReadModulePackagesAttribute(clazz, (ModulePackagesAttribute)attribute, builder);
163 }
164
171 static void ReadModulePackagesAttribute(ClassFile clazz, in ModulePackagesAttribute attribute, Builder builder)
172 {
173 foreach (var package in attribute.Packages)
174 {
175 var packageName = clazz.Constants.Get(package).Name;
176 if (packageName is not null)
177 packageName = packageName.Replace('/', '.');
178 if (packageName is null || string.IsNullOrEmpty(packageName))
179 throw new InvalidModuleDescriptorException("Bad package name on module packages.");
180
181 builder.Package(packageName);
182 }
183 }
184
190 static void ReadModuleMainClassAttribute(ClassFile clazz, Builder builder)
191 {
192 var attribute = clazz.Attributes.FirstOrDefault(i => i.IsNotNil && i.Name.IsNotNil && clazz.Constants.Get(i.Name).Value == AttributeName.ModuleMainClass);
193 if (attribute.IsNil)
194 return;
195
196 ReadModuleMainClassAttribute(clazz, (ModuleMainClassAttribute)attribute, builder);
197 }
198
205 static void ReadModuleMainClassAttribute(ClassFile clazz, in ModuleMainClassAttribute attribute, Builder builder)
206 {
207 var mainClassName = clazz.Constants.Get(attribute.MainClass).Name;
208 if (mainClassName is not null)
209 mainClassName = mainClassName.Replace('/', '.');
210 if (mainClassName is null || string.IsNullOrEmpty(mainClassName))
211 throw new InvalidModuleDescriptorException("Bad main class name on module main class attribute.");
212
213 builder.MainClass(mainClassName);
214 }
215
223 static void ReadModuleRequires(ClassFile clazz, in ModuleAttribute attribute, Builder builder)
224 {
225 foreach (var requires in attribute.Requires)
226 {
227 var moduleName = clazz.Constants.Get(requires.Module).Name;
228 if (moduleName is not null)
229 moduleName = DecodeModuleName(moduleName);
230 if (moduleName is null || string.IsNullOrEmpty(moduleName))
231 throw new InvalidModuleDescriptorException("Bad module name on requires.");
232
233 var modifiers = default(ModuleRequiresFlag);
234 if ((requires.Flag & ModuleRequiresFlag.Transitive) != 0)
235 modifiers |= ModuleRequiresFlag.Transitive;
236 if ((requires.Flag & ModuleRequiresFlag.StaticPhase) != 0)
237 modifiers |= ModuleRequiresFlag.StaticPhase;
238 if ((requires.Flag & ModuleRequiresFlag.Synthetic) != 0)
239 modifiers |= ModuleRequiresFlag.Synthetic;
240 if ((requires.Flag & ModuleRequiresFlag.Mandated) != 0)
241 modifiers |= ModuleRequiresFlag.Mandated;
242
243 if (moduleName == "java.base")
244 {
245 if ((modifiers & ModuleRequiresFlag.Synthetic) != 0)
246 throw new InvalidModuleDescriptorException("The requires entry for java.base has ACC_SYNTHETIC set");
247
248 // requires transitive java.base is illegal unless:
249 // - the major version is 53 (JDK 9), or:
250 // - the classfile is a preview classfile, or:
251 // - the module is deemed to be participating in preview
252 // (i.e. the module is a java.* module)
253 // requires static java.base is illegal unless:
254 // - the major version is 53 (JDK 9), or:
255 if (clazz.Version.Major >= 54)
256 {
257 var hasTransitive = (modifiers & ModuleRequiresFlag.Transitive) != 0;
258 var hasStatic = (modifiers & ModuleRequiresFlag.StaticPhase) != 0;
259 if ((hasTransitive && "java.se" != moduleName) || hasStatic)
260 throw new InvalidModuleDescriptorException($"The requires entry for java.base has {(hasStatic ? "ACC_STATIC_PHASE" : "ACC_TRANSITIVE")} set.");
261 }
262 }
263
264 if (requires.Version.IsNotNil)
265 {
266 var version = clazz.Constants.Get(requires.Version).Value;
267 builder = builder.Requires(modifiers, moduleName, version);
268 }
269 else
270 {
271 builder = builder.Requires(modifiers, moduleName);
272 }
273 }
274 }
275
283 static void ReadModuleExports(ClassFile clazz, in ModuleAttribute attribute, Builder builder)
284 {
285 foreach (var exports in attribute.Exports)
286 {
287 var packageName = clazz.Constants.Get(exports.Package).Name;
288 if (packageName is not null)
289 packageName = packageName.Replace('/', '.');
290 if (packageName is null || string.IsNullOrEmpty(packageName))
291 throw new InvalidModuleDescriptorException("Bad package name on module exports.");
292
293 var modifiers = default(ModuleExportsFlag);
294 if ((exports.Flags & ModuleExportsFlag.Synthetic) != 0)
295 modifiers |= ModuleExportsFlag.Synthetic;
296 if ((exports.Flags & ModuleExportsFlag.Mandated) != 0)
297 modifiers |= ModuleExportsFlag.Mandated;
298
299 builder = builder.Exports(modifiers, packageName, ToHashSet(clazz, exports.Modules));
300 }
301 }
302
310 static void ReadModuleOpens(ClassFile clazz, in ModuleAttribute attribute, Builder builder)
311 {
312 if (builder.IsOpen)
313 if (attribute.Opens.Count > 0)
314 throw new InvalidModuleDescriptorException("The opens table for an open module must be 0 length.");
315
316 foreach (var opens in attribute.Opens)
317 {
318 var packageName = clazz.Constants.Get(opens.Package).Name;
319 if (packageName is not null)
320 packageName = packageName.Replace('/', '.');
321 if (packageName is null || string.IsNullOrEmpty(packageName))
322 throw new InvalidModuleDescriptorException("Bad package name on module opens.");
323
324 var modifiers = default(ModuleOpensFlag);
325 if ((opens.Flags & ModuleOpensFlag.Synthetic) != 0)
326 modifiers |= ModuleOpensFlag.Synthetic;
327 if ((opens.Flags & ModuleOpensFlag.Mandated) != 0)
328 modifiers |= ModuleOpensFlag.Mandated;
329
330 builder = builder.Opens(opens.Flags, packageName, ToHashSet(clazz, opens.Modules));
331 }
332 }
333
341 static void ReadModuleUses(ClassFile clazz, in ModuleAttribute attribute, Builder builder)
342 {
343 foreach (var uses in attribute.Uses)
344 {
345 var className = clazz.Constants.Get(uses).Name;
346 if (className is not null)
347 className = className.Replace('/', '.');
348 if (className is null || string.IsNullOrEmpty(className))
349 throw new InvalidModuleDescriptorException("Bad class name on module uses.");
350
351 builder = builder.Uses(className);
352 }
353 }
354
362 static void ReadModuleProvides(ClassFile clazz, in ModuleAttribute attribute, Builder builder)
363 {
364 foreach (var provides in attribute.Provides)
365 {
366 var serviceName = clazz.Constants.Get(provides.Class).Name;
367 if (serviceName is not null)
368 serviceName = serviceName.Replace('/', '.');
369 if (serviceName is null || string.IsNullOrEmpty(serviceName))
370 throw new InvalidModuleDescriptorException("Bad service name on module provides.");
371
372 builder = builder.Provides(serviceName, ToArray(clazz, provides.With));
373 }
374 }
375
382 static string DecodeModuleName(string value)
383 {
384 if (value.Length == 0)
385 throw new InvalidModuleDescriptorException("Module name is zero length.");
386
387 int i = 0;
388 while (i < value.Length)
389 {
390 int cp = char.ConvertToUtf32(value, i);
391 if (cp == ':' || cp == '@' || cp < 0x20)
392 throw new InvalidModuleDescriptorException($"Module name has illegal character: U+{cp:X4}.");
393
394 // blackslash is the escape character
395 if (cp == '\\')
396 return DecodeModuleName(i, value);
397
398 i += CharCount(cp);
399 }
400
401 return value;
402 }
403
408 static string DecodeModuleName(int i, string value)
409 {
410 var sb = new ValueStringBuilder(value.Length - i);
411
412 // copy the code points that have been checked
413 int j = 0;
414 while (j < i)
415 {
416 int cp = char.ConvertToUtf32(value, j);
417 AppendUtf32(ref sb, cp);
418 j += CharCount(cp);
419 }
420
421 // decode from position i to end
422 while (i < value.Length)
423 {
424 int cp = char.ConvertToUtf32(value, i);
425 if (cp == ':' || cp == '@' || cp < 0x20)
426 {
427 throw new InvalidModuleDescriptorException($"Module name has illegal character: U+{cp:X4}.");
428 }
429
430 // blackslash is the escape character
431 if (cp == '\\')
432 {
433 j = i + CharCount(cp);
434 if (j >= value.Length)
435 throw new InvalidModuleDescriptorException("Module name has illegal escape sequence.");
436
437 int next = char.ConvertToUtf32(value, j);
438 if (next != '\\' && next != ':' && next != '@')
439 throw new InvalidModuleDescriptorException("Module name has illegal escape sequence.");
440
441 AppendUtf32(ref sb, next);
442 i += CharCount(next);
443 }
444 else
445 {
446 AppendUtf32(ref sb, cp);
447 }
448
449 i += CharCount(cp);
450 }
451
452 return sb.ToString();
453 }
454
460 static void AppendUtf32(ref ValueStringBuilder builder, int utf32)
461 {
462 var c = (Span<char>)stackalloc char[2];
463 var l = WriteCodePoint(utf32, c);
464 builder.Append(c[..l]);
465 }
466
473 static int WriteCodePoint(int utf32, Span<char> utf16)
474 {
475 if (utf32 < 0xD800 || (utf32 > 0xDFFF && utf32 < 0x10000))
476 {
477 utf16[0] = (char)utf32;
478 utf16[1] = (char)0;
479 return 1;
480 }
481
482 utf32 -= 0x010000;
483
484 utf16[0] = (char)(((0b11111111110000000000 & utf32) >> 10) + 0xD800);
485 utf16[1] = (char)(((0b00000000001111111111 & utf32) >> 00) + 0xDC00);
486
487 return 2;
488 }
489
495 static int CharCount(int utf32)
496 {
497 return utf32 < 0xD800 || (utf32 > 0xDFFF && utf32 < 0x10000) ? 2 : 1;
498 }
499
506 static ImmutableHashSet<string> ToHashSet(ClassFile clazz, in ModuleConstantHandleTable modules)
507 {
508 var hs = ImmutableHashSet.CreateBuilder<string>();
509 foreach (var i in modules)
510 if (clazz.Constants.Get(i).Name is { } name)
511 hs.Add(DecodeModuleName(name));
512
513 return hs.ToImmutable();
514 }
515
522 static ImmutableArray<string> ToArray(ClassFile clazz, in ClassConstantHandleTable classes)
523 {
524 var ar = ImmutableArray.CreateBuilder<string>(classes.Count);
525 foreach (var i in classes)
526 if (clazz.Constants.Get(i).Name is { } name)
527 ar.Add(name.Replace('/', '.'));
528
529 return ar.ToImmutable();
530 }
531
532 readonly string _name;
533 readonly ModuleVersion _version;
534 readonly bool _automatic;
535 readonly ModuleFlag _modifiers;
536 readonly ImmutableHashSet<ModuleRequires> _requires;
537 readonly ImmutableHashSet<ModuleExports> _exports;
538 readonly ImmutableHashSet<ModuleOpens> _opens;
539 readonly ImmutableHashSet<string> _uses;
540 readonly ImmutableHashSet<ModuleProvides> _provides;
541 readonly ImmutableHashSet<string> _packages;
542 readonly string? _mainClass;
543
558 internal ModuleDescriptor(
559 string name,
560 ModuleVersion version,
561 bool automatic,
562 ModuleFlag modifiers,
563 ImmutableHashSet<ModuleRequires> requires,
564 ImmutableHashSet<ModuleExports> exports,
565 ImmutableHashSet<ModuleOpens> opens,
566 ImmutableHashSet<string> uses,
567 ImmutableHashSet<ModuleProvides> provides,
568 ImmutableHashSet<string> packages,
569 string? mainClass)
570 {
571 _name = name;
572 _version = version;
573 _automatic = automatic;
574 _modifiers = modifiers;
575 _requires = requires;
576 _exports = exports;
577 _opens = opens;
578 _uses = uses;
579 _provides = provides;
580 _packages = packages;
581 _mainClass = mainClass;
582 }
583
587 public readonly string Name => _name;
588
592 public readonly ModuleVersion Version => _version;
593
597 public readonly ModuleFlag Modifiers => _modifiers;
598
602 public readonly bool IsOpen => (Modifiers & ModuleFlag.Open) != 0;
603
607 public readonly bool IsAutomatic => _automatic;
608
612 public readonly ImmutableHashSet<ModuleRequires> Requires => _requires;
613
617 public readonly ImmutableHashSet<ModuleExports> Exports => _exports;
618
622 public readonly ImmutableHashSet<ModuleOpens> Opens => _opens;
623
627 public readonly ImmutableHashSet<string> Uses => _uses;
628
632 public readonly ImmutableHashSet<ModuleProvides> Provides => _provides;
633
640 public readonly ImmutableHashSet<string> Packages => _packages;
641
645 public readonly string? MainClass => _mainClass;
646
663 public readonly int CompareTo(ModuleDescriptor other)
664 {
665 int c = _name.CompareTo(other._name);
666 if (c != 0)
667 return c;
668
669 c = _version.CompareTo(other._version);
670 if (c != 0)
671 return c;
672
673 c = _modifiers.CompareTo(other._modifiers);
674 if (c != 0)
675 return c;
676
677 c = CompareTo(_requires, other._requires);
678 if (c != 0)
679 return c;
680
681 c = CompareTo(_packages, other._packages);
682 if (c != 0)
683 return c;
684
685 c = CompareTo(_exports, other._exports);
686 if (c != 0)
687 return c;
688
689 c = CompareTo(_opens, other._opens);
690 if (c != 0)
691 return c;
692
693 c = CompareTo(_uses, other._uses);
694 if (c != 0)
695 return c;
696
697 c = CompareTo(_provides, other._provides);
698 if (c != 0)
699 return c;
700
701 c = Comparer.Default.Compare(_mainClass, other._mainClass);
702 if (c != 0)
703 return c;
704
705 return 0;
706 }
707
715 static int CompareTo<T>(ImmutableHashSet<T> s1, ImmutableHashSet<T> s2)
716 {
717 var a1 = s1.ToArray();
718 var a2 = s2.ToArray();
719 Array.Sort(a1);
720 Array.Sort(a2);
722 }
723
725 public readonly override bool Equals(object? obj)
726 {
727 return obj is ModuleDescriptor other && Equals(other);
728 }
729
735 public readonly bool Equals(in ModuleDescriptor other)
736 {
737 return
738 _name.Equals(other._name) &&
739 _version.Equals(other._version) &&
740 _modifiers.Equals(other._modifiers) &&
741 _requires.SetEquals(other._requires) &&
742 _exports.SetEquals(other._exports) &&
743 _opens.SetEquals(other._opens) &&
744 _uses.SetEquals(other._uses) &&
745 _provides.SetEquals(other._provides) &&
746 _packages.SetEquals(other._packages) &&
747 _mainClass == other._mainClass;
748 }
749
751 public readonly override int GetHashCode()
752 {
753 var hc = new HashCode();
754 hc.Add(_name);
755 hc.Add(_version);
756 hc.Add(_modifiers);
757 hc.AddRange(_requires);
758 hc.AddRange(_exports);
759 hc.AddRange(_opens);
760 hc.AddRange(_uses);
761 hc.AddRange(_provides);
762 hc.AddRange(_packages);
763 hc.Add(_mainClass);
764 return hc.ToHashCode();
765 }
766
768 public override string? ToString()
769 {
770 return _version.IsValid ? _name + " (@" + _version + ")" : _name;
771 }
772
773 }
774
775}
IKVM.Reflection.Module Module
global::java.lang.invoke.LambdaForm.Name Name
Implements an IComparer<T> which lexicographically compares a two lists.
static new readonly LexicographicListComparer< T > Default
Returns a default LexicographicListComparer<T> instance.