jar: pass module-info.class through as a raw resource

JPMS descriptors are never loaded by a class loader; parsing one and
rebuilding its pool left the unmodeled Module attribute with stale
indices, and ASM died in readModule on the rewritten jar (caught by the
ASM probe on Loreport, which ships META-INF/versions module-infos).
Byte-for-byte resource passthrough keeps the descriptor valid.
This commit is contained in:
Loki 2026-10-13 10:23:19 +02:00
parent 15cd31252e
commit 01afa6af37
Signed by: boba
GPG key ID: 253067914055423B

View file

@ -24,7 +24,7 @@ pub fn load(entries: Vec<Entry>) -> Result<Program, JarError> {
let mut seen: BTreeSet<String> = BTreeSet::new(); let mut seen: BTreeSet<String> = BTreeSet::new();
for entry in entries { for entry in entries {
if is_class_entry(&entry.name) { if is_class_entry(&entry.name) && !is_module_info(&entry.name) {
let class = format::parse(&entry.data)?; let class = format::parse(&entry.data)?;
if seen.insert(class.this_class.clone()) { if seen.insert(class.this_class.clone()) {
program.classes.push(class); program.classes.push(class);
@ -66,6 +66,14 @@ fn is_class_entry(name: &str) -> bool {
name.len() > ".class".len() && name.ends_with(".class") name.len() > ".class".len() && name.ends_with(".class")
} }
/// A JPMS descriptor. No class loader ever loads it; parsing would rebuild
/// its pool and leave the unmodeled `Module` attribute with stale indices
/// (ASM then dies in `readModule`), so it travels as a raw resource with
/// its bytes intact.
fn is_module_info(name: &str) -> bool {
name.ends_with("module-info.class")
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
#![allow(clippy::expect_used, clippy::unwrap_used)] #![allow(clippy::expect_used, clippy::unwrap_used)]
@ -132,4 +140,23 @@ mod tests {
assert_eq!(program.classes.len(), 1); assert_eq!(program.classes.len(), 1);
assert_eq!(program.classes[0].this_class, "demo/App"); assert_eq!(program.classes[0].this_class, "demo/App");
} }
/// JPMS descriptors are never class-loaded: they pass through as raw
/// resources (parsing would rebuild their pool and stale the `Module`
/// attribute, which stays unmodeled).
#[test]
fn module_info_passes_through_as_resource() {
let entries = vec![
Entry::new("META-INF/versions/9/module-info.class", vec![0xCA, 0xFE]),
Entry::new("notes.txt", b"hi".to_vec()),
];
let program = load(entries).expect("load");
assert!(program.classes.is_empty());
assert_eq!(program.resources.len(), 2);
assert_eq!(
program.resources[0].path,
"META-INF/versions/9/module-info.class"
);
}
} }