1#![allow(clippy::cast_possible_truncation)]
13
14use cafebabe::attributes::AttributeData;
15
16use crate::ClasspathResult;
17use crate::stub::model::{
18 AccessFlags, ModuleExports, ModuleOpens, ModuleProvides, ModuleRequires, ModuleStub,
19};
20
21use super::constants::class_name_to_fqn;
22
23#[allow(clippy::missing_errors_doc)] pub fn extract_module(class: &cafebabe::ClassFile<'_>) -> ClasspathResult<Option<ModuleStub>> {
39 let module_data = class.attributes.iter().find_map(|attr| match &attr.data {
40 AttributeData::Module(data) => Some(data),
41 _ => None,
42 });
43
44 let Some(data) = module_data else {
45 return Ok(None);
46 };
47
48 let stub = convert_module_data(data)?;
49 Ok(Some(stub))
50}
51
52fn convert_module_data(data: &cafebabe::attributes::ModuleData<'_>) -> ClasspathResult<ModuleStub> {
58 let name = class_name_to_fqn(&data.name);
59 let access = AccessFlags::new(data.access_flags.bits());
60 let version = data.version.as_ref().map(std::string::ToString::to_string);
61
62 let requires = data
63 .requires
64 .iter()
65 .map(convert_requires_entry)
66 .collect::<ClasspathResult<Vec<_>>>()?;
67
68 let exports = data
69 .exports
70 .iter()
71 .map(convert_exports_entry)
72 .collect::<ClasspathResult<Vec<_>>>()?;
73
74 let opens = data
75 .opens
76 .iter()
77 .map(convert_opens_entry)
78 .collect::<ClasspathResult<Vec<_>>>()?;
79
80 let provides = data
81 .provides
82 .iter()
83 .map(convert_provides_entry)
84 .collect::<ClasspathResult<Vec<_>>>()?;
85
86 let uses = data
87 .uses
88 .iter()
89 .map(|class_name| class_name_to_fqn(class_name))
90 .collect();
91
92 Ok(ModuleStub {
93 name,
94 access,
95 version,
96 requires,
97 exports,
98 opens,
99 provides,
100 uses,
101 })
102}
103
104#[allow(clippy::unnecessary_wraps)] fn convert_requires_entry(
107 entry: &cafebabe::attributes::ModuleRequireEntry<'_>,
108) -> ClasspathResult<ModuleRequires> {
109 Ok(ModuleRequires {
110 module_name: class_name_to_fqn(&entry.name),
111 access: AccessFlags::new(entry.flags.bits()),
112 version: entry.version.as_ref().map(std::string::ToString::to_string),
113 })
114}
115
116#[allow(clippy::unnecessary_wraps)] fn convert_exports_entry(
119 entry: &cafebabe::attributes::ModuleExportsEntry<'_>,
120) -> ClasspathResult<ModuleExports> {
121 let to_modules = entry
122 .exports_to
123 .iter()
124 .map(|m| class_name_to_fqn(m))
125 .collect();
126
127 Ok(ModuleExports {
128 package: class_name_to_fqn(&entry.package_name),
129 access: AccessFlags::new(entry.flags.bits()),
130 to_modules,
131 })
132}
133
134#[allow(clippy::unnecessary_wraps)] fn convert_opens_entry(
137 entry: &cafebabe::attributes::ModuleOpensEntry<'_>,
138) -> ClasspathResult<ModuleOpens> {
139 let to_modules = entry
140 .opens_to
141 .iter()
142 .map(|m| class_name_to_fqn(m))
143 .collect();
144
145 Ok(ModuleOpens {
146 package: class_name_to_fqn(&entry.package_name),
147 access: AccessFlags::new(entry.flags.bits()),
148 to_modules,
149 })
150}
151
152#[allow(clippy::unnecessary_wraps)] fn convert_provides_entry(
155 entry: &cafebabe::attributes::ModuleProvidesEntry<'_>,
156) -> ClasspathResult<ModuleProvides> {
157 let implementations = entry
158 .provides_with
159 .iter()
160 .map(|c| class_name_to_fqn(c))
161 .collect();
162
163 Ok(ModuleProvides {
164 service: class_name_to_fqn(&entry.service_interface_name),
165 implementations,
166 })
167}
168
169#[cfg(test)]
174mod tests {
175 use super::*;
176 use crate::ClasspathError;
177 use cafebabe::ParseOptions;
178
179 struct ModuleBuilder {
189 cp_entries: Vec<Vec<u8>>,
191 module_name_idx: u16,
193 module_flags: u16,
195 module_version_idx: u16,
197 requires: Vec<(u16, u16, u16)>,
199 exports: Vec<(u16, u16, Vec<u16>)>,
201 opens: Vec<(u16, u16, Vec<u16>)>,
203 uses: Vec<u16>,
205 provides: Vec<(u16, Vec<u16>)>,
207 }
208
209 impl ModuleBuilder {
210 fn new(module_name: &str) -> Self {
216 let mut builder = Self {
217 cp_entries: Vec::new(),
218 module_name_idx: 0,
219 module_flags: 0,
220 module_version_idx: 0,
221 requires: Vec::new(),
222 exports: Vec::new(),
223 opens: Vec::new(),
224 uses: Vec::new(),
225 provides: Vec::new(),
226 };
227
228 builder.add_utf8("module-info");
230 builder.add_class(1);
232 builder.add_utf8("java/lang/Object");
234 builder.add_class(3);
236 builder.add_utf8("Module");
238 builder.add_utf8(module_name);
240 builder.module_name_idx = builder.add_module(6);
242
243 builder
244 }
245
246 fn module_flags(mut self, flags: u16) -> Self {
249 self.module_flags = flags;
250 self
251 }
252
253 fn module_version(mut self, version: &str) -> Self {
255 self.module_version_idx = self.add_utf8(version);
256 self
257 }
258
259 fn add_utf8(&mut self, s: &str) -> u16 {
261 let mut entry = vec![1u8]; let bytes = s.as_bytes();
263 entry.extend_from_slice(&(bytes.len() as u16).to_be_bytes());
264 entry.extend_from_slice(bytes);
265 self.cp_entries.push(entry);
266 self.cp_entries.len() as u16
267 }
268
269 fn add_class(&mut self, name_idx: u16) -> u16 {
271 let mut entry = vec![7u8]; entry.extend_from_slice(&name_idx.to_be_bytes());
273 self.cp_entries.push(entry);
274 self.cp_entries.len() as u16
275 }
276
277 fn add_module(&mut self, name_idx: u16) -> u16 {
279 let mut entry = vec![19u8]; entry.extend_from_slice(&name_idx.to_be_bytes());
281 self.cp_entries.push(entry);
282 self.cp_entries.len() as u16
283 }
284
285 fn add_package(&mut self, name_idx: u16) -> u16 {
287 let mut entry = vec![20u8]; entry.extend_from_slice(&name_idx.to_be_bytes());
289 self.cp_entries.push(entry);
290 self.cp_entries.len() as u16
291 }
292
293 fn add_requires(
295 &mut self,
296 module_name: &str,
297 flags: u16,
298 version: Option<&str>,
299 ) -> &mut Self {
300 let name_idx = self.add_utf8(module_name);
301 let module_idx = self.add_module(name_idx);
302 let version_idx = version.map_or(0, |v| self.add_utf8(v));
303 self.requires.push((module_idx, flags, version_idx));
304 self
305 }
306
307 fn add_exports(
309 &mut self,
310 package_name: &str,
311 flags: u16,
312 to_modules: &[&str],
313 ) -> &mut Self {
314 let pkg_name_idx = self.add_utf8(package_name);
315 let pkg_idx = self.add_package(pkg_name_idx);
316 let to_indices: Vec<u16> = to_modules
317 .iter()
318 .map(|m| {
319 let name_idx = self.add_utf8(m);
320 self.add_module(name_idx)
321 })
322 .collect();
323 self.exports.push((pkg_idx, flags, to_indices));
324 self
325 }
326
327 fn add_opens(&mut self, package_name: &str, flags: u16, to_modules: &[&str]) -> &mut Self {
329 let pkg_name_idx = self.add_utf8(package_name);
330 let pkg_idx = self.add_package(pkg_name_idx);
331 let to_indices: Vec<u16> = to_modules
332 .iter()
333 .map(|m| {
334 let name_idx = self.add_utf8(m);
335 self.add_module(name_idx)
336 })
337 .collect();
338 self.opens.push((pkg_idx, flags, to_indices));
339 self
340 }
341
342 fn add_uses(&mut self, class_name: &str) -> &mut Self {
344 let name_idx = self.add_utf8(class_name);
345 let class_idx = self.add_class(name_idx);
346 self.uses.push(class_idx);
347 self
348 }
349
350 fn add_provides(&mut self, service_class: &str, impl_classes: &[&str]) -> &mut Self {
352 let svc_name_idx = self.add_utf8(service_class);
353 let svc_idx = self.add_class(svc_name_idx);
354 let impl_indices: Vec<u16> = impl_classes
355 .iter()
356 .map(|c| {
357 let name_idx = self.add_utf8(c);
358 self.add_class(name_idx)
359 })
360 .collect();
361 self.provides.push((svc_idx, impl_indices));
362 self
363 }
364
365 fn build(&self) -> Vec<u8> {
367 let mut bytes = Vec::new();
368
369 bytes.extend_from_slice(&0xCAFE_BABEu32.to_be_bytes());
371 bytes.extend_from_slice(&0u16.to_be_bytes());
373 bytes.extend_from_slice(&53u16.to_be_bytes());
375
376 let cp_count = self.cp_entries.len() as u16 + 1;
378 bytes.extend_from_slice(&cp_count.to_be_bytes());
379 for entry in &self.cp_entries {
380 bytes.extend_from_slice(entry);
381 }
382
383 bytes.extend_from_slice(&0x8000u16.to_be_bytes());
385 bytes.extend_from_slice(&2u16.to_be_bytes());
387 bytes.extend_from_slice(&0u16.to_be_bytes());
389 bytes.extend_from_slice(&0u16.to_be_bytes());
391 bytes.extend_from_slice(&0u16.to_be_bytes());
393 bytes.extend_from_slice(&0u16.to_be_bytes());
395
396 bytes.extend_from_slice(&1u16.to_be_bytes());
398
399 let attr_data = self.build_module_attr_data();
401 bytes.extend_from_slice(&5u16.to_be_bytes());
403 bytes.extend_from_slice(&(attr_data.len() as u32).to_be_bytes());
405 bytes.extend_from_slice(&attr_data);
406
407 bytes
408 }
409
410 fn build_module_attr_data(&self) -> Vec<u8> {
412 let mut data = Vec::new();
413
414 data.extend_from_slice(&self.module_name_idx.to_be_bytes());
416 data.extend_from_slice(&self.module_flags.to_be_bytes());
418 data.extend_from_slice(&self.module_version_idx.to_be_bytes());
420
421 data.extend_from_slice(&(self.requires.len() as u16).to_be_bytes());
423 for &(module_idx, flags, version_idx) in &self.requires {
424 data.extend_from_slice(&module_idx.to_be_bytes());
425 data.extend_from_slice(&flags.to_be_bytes());
426 data.extend_from_slice(&version_idx.to_be_bytes());
427 }
428
429 data.extend_from_slice(&(self.exports.len() as u16).to_be_bytes());
431 for (pkg_idx, flags, to_indices) in &self.exports {
432 data.extend_from_slice(&pkg_idx.to_be_bytes());
433 data.extend_from_slice(&flags.to_be_bytes());
434 data.extend_from_slice(&(to_indices.len() as u16).to_be_bytes());
435 for idx in to_indices {
436 data.extend_from_slice(&idx.to_be_bytes());
437 }
438 }
439
440 data.extend_from_slice(&(self.opens.len() as u16).to_be_bytes());
442 for (pkg_idx, flags, to_indices) in &self.opens {
443 data.extend_from_slice(&pkg_idx.to_be_bytes());
444 data.extend_from_slice(&flags.to_be_bytes());
445 data.extend_from_slice(&(to_indices.len() as u16).to_be_bytes());
446 for idx in to_indices {
447 data.extend_from_slice(&idx.to_be_bytes());
448 }
449 }
450
451 data.extend_from_slice(&(self.uses.len() as u16).to_be_bytes());
453 for idx in &self.uses {
454 data.extend_from_slice(&idx.to_be_bytes());
455 }
456
457 data.extend_from_slice(&(self.provides.len() as u16).to_be_bytes());
459 for (svc_idx, impl_indices) in &self.provides {
460 data.extend_from_slice(&svc_idx.to_be_bytes());
461 data.extend_from_slice(&(impl_indices.len() as u16).to_be_bytes());
462 for idx in impl_indices {
463 data.extend_from_slice(&idx.to_be_bytes());
464 }
465 }
466
467 data
468 }
469 }
470
471 fn parse_and_extract(bytes: &[u8]) -> ClasspathResult<Option<ModuleStub>> {
473 let mut opts = ParseOptions::default();
474 opts.parse_bytecode(false);
475 let class_file = cafebabe::parse_class_with_options(bytes, &opts).map_err(|e| {
476 ClasspathError::BytecodeParseError {
477 class_name: String::from("<test>"),
478 reason: e.to_string(),
479 }
480 })?;
481 extract_module(&class_file)
482 }
483
484 #[test]
489 fn test_java_base_module_exports() {
490 let mut builder = ModuleBuilder::new("java.base");
491 builder.add_exports("java/lang", 0, &[]);
492 builder.add_exports("java/util", 0, &[]);
493 builder.add_requires("java.base", 0x8000, Some("17")); let bytes = builder.build();
496 let stub = parse_and_extract(&bytes).unwrap().unwrap();
497
498 assert_eq!(stub.name, "java.base");
499 assert_eq!(stub.exports.len(), 2);
500 assert_eq!(stub.exports[0].package, "java.lang");
501 assert!(stub.exports[0].to_modules.is_empty()); assert_eq!(stub.exports[1].package, "java.util");
503 assert_eq!(stub.requires.len(), 1);
504 assert_eq!(stub.requires[0].module_name, "java.base");
505 assert!(stub.requires[0].access.contains(0x8000)); assert_eq!(stub.requires[0].version.as_deref(), Some("17"));
507 }
508
509 #[test]
514 fn test_requires_transitive() {
515 let mut builder = ModuleBuilder::new("com.example.app");
516 builder.add_requires("java.base", 0x8000, Some("17")); builder.add_requires("java.logging", 0x0020, None); let bytes = builder.build();
520 let stub = parse_and_extract(&bytes).unwrap().unwrap();
521
522 assert_eq!(stub.name, "com.example.app");
523 assert_eq!(stub.requires.len(), 2);
524
525 let java_base = &stub.requires[0];
526 assert_eq!(java_base.module_name, "java.base");
527 assert!(java_base.access.contains(0x8000)); let java_logging = &stub.requires[1];
530 assert_eq!(java_logging.module_name, "java.logging");
531 assert!(java_logging.access.contains(0x0020)); assert!(java_logging.version.is_none());
533 }
534
535 #[test]
540 fn test_provides_service() {
541 let mut builder = ModuleBuilder::new("com.example.provider");
542 builder.add_provides(
543 "com/example/api/Service",
544 &[
545 "com/example/impl/ServiceImpl",
546 "com/example/impl/ServiceImpl2",
547 ],
548 );
549
550 let bytes = builder.build();
551 let stub = parse_and_extract(&bytes).unwrap().unwrap();
552
553 assert_eq!(stub.provides.len(), 1);
554 assert_eq!(stub.provides[0].service, "com.example.api.Service");
555 assert_eq!(stub.provides[0].implementations.len(), 2);
556 assert_eq!(
557 stub.provides[0].implementations[0],
558 "com.example.impl.ServiceImpl"
559 );
560 assert_eq!(
561 stub.provides[0].implementations[1],
562 "com.example.impl.ServiceImpl2"
563 );
564 }
565
566 #[test]
571 fn test_opens_for_reflection() {
572 let mut builder = ModuleBuilder::new("com.example.reflective");
573 builder.add_opens("com/example/internal", 0, &[]);
575 builder.add_opens(
577 "com/example/private",
578 0,
579 &["com.example.framework", "com.example.test"],
580 );
581
582 let bytes = builder.build();
583 let stub = parse_and_extract(&bytes).unwrap().unwrap();
584
585 assert_eq!(stub.opens.len(), 2);
586
587 let open_all = &stub.opens[0];
588 assert_eq!(open_all.package, "com.example.internal");
589 assert!(open_all.to_modules.is_empty());
590
591 let open_qualified = &stub.opens[1];
592 assert_eq!(open_qualified.package, "com.example.private");
593 assert_eq!(open_qualified.to_modules.len(), 2);
594 assert_eq!(open_qualified.to_modules[0], "com.example.framework");
595 assert_eq!(open_qualified.to_modules[1], "com.example.test");
596 }
597
598 #[test]
603 fn test_uses_declarations() {
604 let mut builder = ModuleBuilder::new("com.example.consumer");
605 builder.add_uses("com/example/api/Service");
606 builder.add_uses("java/sql/Driver");
607
608 let bytes = builder.build();
609 let stub = parse_and_extract(&bytes).unwrap().unwrap();
610
611 assert_eq!(stub.uses.len(), 2);
612 assert_eq!(stub.uses[0], "com.example.api.Service");
613 assert_eq!(stub.uses[1], "java.sql.Driver");
614 }
615
616 #[test]
621 fn test_no_module_attribute_returns_none() {
622 let mut bytes = Vec::new();
624
625 bytes.extend_from_slice(&0xCAFE_BABEu32.to_be_bytes());
627 bytes.extend_from_slice(&0u16.to_be_bytes());
629 bytes.extend_from_slice(&52u16.to_be_bytes());
631
632 bytes.extend_from_slice(&5u16.to_be_bytes());
634
635 bytes.push(1);
637 let name = b"com/example/Foo";
638 bytes.extend_from_slice(&(name.len() as u16).to_be_bytes());
639 bytes.extend_from_slice(name);
640
641 bytes.push(7);
643 bytes.extend_from_slice(&1u16.to_be_bytes());
644
645 bytes.push(1);
647 let obj = b"java/lang/Object";
648 bytes.extend_from_slice(&(obj.len() as u16).to_be_bytes());
649 bytes.extend_from_slice(obj);
650
651 bytes.push(7);
653 bytes.extend_from_slice(&3u16.to_be_bytes());
654
655 bytes.extend_from_slice(&0x0021u16.to_be_bytes());
657 bytes.extend_from_slice(&2u16.to_be_bytes());
659 bytes.extend_from_slice(&4u16.to_be_bytes());
661 bytes.extend_from_slice(&0u16.to_be_bytes());
663 bytes.extend_from_slice(&0u16.to_be_bytes());
665 bytes.extend_from_slice(&0u16.to_be_bytes());
667 bytes.extend_from_slice(&0u16.to_be_bytes());
669
670 let result = parse_and_extract(&bytes).unwrap();
671 assert!(result.is_none());
672 }
673
674 #[test]
679 fn test_module_version_and_open_flag() {
680 let builder = ModuleBuilder::new("com.example.open")
681 .module_flags(0x0020) .module_version("1.0.0");
683
684 let bytes = builder.build();
685 let stub = parse_and_extract(&bytes).unwrap().unwrap();
686
687 assert_eq!(stub.name, "com.example.open");
688 assert!(stub.access.contains(0x0020)); assert_eq!(stub.version.as_deref(), Some("1.0.0"));
690 }
691
692 #[test]
697 fn test_qualified_exports() {
698 let mut builder = ModuleBuilder::new("com.example.lib");
699 builder.add_exports(
700 "com/example/internal",
701 0,
702 &["com.example.app", "com.example.test"],
703 );
704
705 let bytes = builder.build();
706 let stub = parse_and_extract(&bytes).unwrap().unwrap();
707
708 assert_eq!(stub.exports.len(), 1);
709 assert_eq!(stub.exports[0].package, "com.example.internal");
710 assert_eq!(stub.exports[0].to_modules.len(), 2);
711 assert_eq!(stub.exports[0].to_modules[0], "com.example.app");
712 assert_eq!(stub.exports[0].to_modules[1], "com.example.test");
713 }
714
715 #[test]
720 fn test_comprehensive_module() {
721 let mut builder = ModuleBuilder::new("com.example.full");
722 builder
723 .add_requires("java.base", 0x8000, Some("17"))
724 .add_requires("java.logging", 0x0020, None)
725 .add_exports("com/example/api", 0, &[])
726 .add_exports("com/example/spi", 0, &["com.example.impl"])
727 .add_opens("com/example/internal", 0, &[])
728 .add_uses("com/example/spi/Plugin")
729 .add_provides(
730 "com/example/spi/Plugin",
731 &["com/example/impl/DefaultPlugin"],
732 );
733
734 let bytes = builder.build();
735 let stub = parse_and_extract(&bytes).unwrap().unwrap();
736
737 assert_eq!(stub.name, "com.example.full");
738 assert_eq!(stub.requires.len(), 2);
739 assert_eq!(stub.exports.len(), 2);
740 assert_eq!(stub.opens.len(), 1);
741 assert_eq!(stub.uses.len(), 1);
742 assert_eq!(stub.uses[0], "com.example.spi.Plugin");
743 assert_eq!(stub.provides.len(), 1);
744 assert_eq!(stub.provides[0].service, "com.example.spi.Plugin");
745 assert_eq!(
746 stub.provides[0].implementations,
747 vec!["com.example.impl.DefaultPlugin"]
748 );
749 }
750
751 #[test]
756 fn test_empty_module() {
757 let builder = ModuleBuilder::new("com.example.empty");
758
759 let bytes = builder.build();
760 let stub = parse_and_extract(&bytes).unwrap().unwrap();
761
762 assert_eq!(stub.name, "com.example.empty");
763 assert!(stub.requires.is_empty());
764 assert!(stub.exports.is_empty());
765 assert!(stub.opens.is_empty());
766 assert!(stub.uses.is_empty());
767 assert!(stub.provides.is_empty());
768 assert!(stub.version.is_none());
769 }
770
771 #[test]
776 fn test_requires_static_phase() {
777 let mut builder = ModuleBuilder::new("com.example.compile");
778 builder.add_requires("org.checkerframework.checker.qual", 0x0040, None);
780
781 let bytes = builder.build();
782 let stub = parse_and_extract(&bytes).unwrap().unwrap();
783
784 assert_eq!(stub.requires.len(), 1);
785 assert_eq!(
786 stub.requires[0].module_name,
787 "org.checkerframework.checker.qual"
788 );
789 assert!(stub.requires[0].access.contains(0x0040)); }
791}