classfile: fix condy ldc category (ldc_w vs ldc2_w by descriptor), split attrs tests

This commit is contained in:
Loki 2026-10-15 12:53:22 +02:00
parent 6dd9c220d4
commit a4450b69ba
Signed by: boba
GPG key ID: 253067914055423B
3 changed files with 169 additions and 1 deletions

View file

@ -1,267 +0,0 @@
//! Attribute round trips: bootstrap methods, nesting, flags, sealing.
#![allow(clippy::expect_used, clippy::unwrap_used)]
use super::round_trip;
use ristretto_classfile::attributes::{
Attribute as RtAttr, BootstrapMethod as RtBsm, Instruction as Rt,
};
use ristretto_classfile::{
ClassAccessFlags, ClassFile as RtClass, ConstantPool, JAVA_21, Method as RtMethod,
MethodAccessFlags, ReferenceKind,
};
/// `BootstrapMethods` + `invokedynamic` survive the round trip.
#[test]
fn bootstrap_methods_round_trip() {
let mut pool = ConstantPool::default();
let this = pool.add_class("demo/Lambda").expect("add_class");
let object = pool.add_class("java/lang/Object").expect("add_object");
let owner = pool.add_class("demo/RT").expect("owner");
let method_ref = pool.add_method_ref(owner, "boot", "()V").expect("ref");
let handle = pool
.add_method_handle(ReferenceKind::InvokeStatic, method_ref)
.expect("handle");
let arg = pool.add_string("x").expect("arg");
let bsm_name = pool.add_utf8("BootstrapMethods").expect("attr");
let name = pool.add_utf8("run").expect("name");
let desc = pool.add_utf8("()V").expect("desc");
let code_name = pool.add_utf8("Code").expect("attr");
let dynamic = pool.add_invoke_dynamic(0, "run", "()V").expect("indy");
let rt = RtClass {
version: JAVA_21,
constant_pool: pool,
access_flags: ClassAccessFlags::PUBLIC,
this_class: this,
super_class: object,
interfaces: vec![],
fields: vec![],
methods: vec![RtMethod {
access_flags: MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC,
name_index: name,
descriptor_index: desc,
attributes: vec![RtAttr::Code {
name_index: code_name,
max_stack: 1,
max_locals: 0,
code: vec![Rt::Invokedynamic(dynamic), Rt::Return],
exception_table: vec![],
attributes: vec![],
}],
}],
attributes: vec![RtAttr::BootstrapMethods {
name_index: bsm_name,
methods: vec![RtBsm {
bootstrap_method_ref: handle,
arguments: vec![arg],
}],
}],
..RtClass::default()
};
let ir = round_trip(&rt);
assert_eq!(ir.attributes.len(), 1);
assert!(format!("{:?}", ir.attributes[0]).contains("BootstrapMethods"));
}
/// `NestMembers` переживает пересборку пула (баг: `ClassFormatError`).
#[test]
fn nest_attributes_round_trip() {
let mut pool = ConstantPool::default();
let this = pool.add_class("demo/Nest").expect("c");
let object = pool.add_class("java/lang/Object").expect("o");
let member = pool.add_class("demo/Nest$Inner").expect("m");
let mn = pool.add_utf8("NestMembers").expect("n");
let mems = RtAttr::NestMembers {
name_index: mn,
class_indexes: vec![this, member],
};
let rt = RtClass {
version: JAVA_21,
constant_pool: pool,
this_class: this,
super_class: object,
attributes: vec![mems],
..RtClass::default()
};
let ir = round_trip(&rt);
assert!(format!("{:?}", ir.attributes).contains("NestMembers"));
}
/// `parse → write → parse` даёт тот же IR; записанное верифицируется.
#[test]
fn bare_interface_flags_normalize_on_parse() {
use ristretto_classfile::ClassAccessFlags;
let mut pool = ConstantPool::default();
let this = pool.add_class("demo/Bare").expect("add_class");
let object = pool.add_class("java/lang/Object").expect("add_object");
let rt = RtClass {
version: JAVA_21,
constant_pool: pool,
access_flags: ClassAccessFlags::INTERFACE,
this_class: this,
super_class: object,
interfaces: vec![],
fields: vec![],
methods: vec![],
attributes: vec![],
..RtClass::default()
};
let ir = round_trip(&rt);
assert_eq!(ir.access_flags & 0x0200, 0x0200, "still an interface");
assert_eq!(
ir.access_flags & 0x0400,
0x0400,
"abstract is implied for interfaces"
);
}
/// `PermittedSubclasses` survives the round trip: its pool indices are
/// remapped on write (not copied verbatim), or the backend rejects the
/// rebuilt pool (`bad constant type`).
#[test]
fn permitted_subclasses_round_trip() {
let mut pool = ConstantPool::default();
let this = pool.add_class("demo/Sealed").expect("add_class");
let object = pool.add_class("java/lang/Object").expect("add_object");
let permitted = pool.add_class("demo/Impl").expect("add permitted");
let attr_name = pool.add_utf8("PermittedSubclasses").expect("attr");
let rt = RtClass {
version: JAVA_21,
constant_pool: pool,
access_flags: ClassAccessFlags::PUBLIC | ClassAccessFlags::ABSTRACT,
this_class: this,
super_class: object,
interfaces: vec![],
fields: vec![],
methods: vec![],
attributes: vec![RtAttr::PermittedSubclasses {
name_index: attr_name,
class_indexes: vec![permitted],
}],
..RtClass::default()
};
let ir = round_trip(&rt);
assert_eq!(ir.this_class, "demo/Sealed");
}
/// Runtime-visible annotations on a method survive the round trip: the
/// pool is rebuilt on write, so raw index blobs would go stale and
/// reflection (`Method.getAnnotation`) would die with
/// `AnnotationFormatError`/`GenericSignatureFormatError`.
#[test]
fn runtime_visible_annotations_round_trip() {
use ristretto_classfile::attributes::{
Annotation as RtAnno, AnnotationElement as RtElem, AnnotationValuePair as RtPair,
};
let mut pool = ConstantPool::default();
let this = pool.add_class("demo/Anno").expect("this");
let object = pool.add_class("java/lang/Object").expect("object");
let name = pool.add_utf8("run").expect("name");
let desc = pool.add_utf8("()V").expect("desc");
let code_name = pool.add_utf8("Code").expect("code");
let attr_name = pool.add_utf8("RuntimeVisibleAnnotations").expect("rva");
let marker = pool.add_utf8("Ldemo/Marker;").expect("marker");
let value_key = pool.add_utf8("value").expect("value key");
let hello = pool.add_utf8("hello").expect("hello");
let klass_key = pool.add_utf8("klass").expect("klass key");
let target = pool.add_utf8("Ldemo/Target;").expect("target");
let kind_key = pool.add_utf8("kind").expect("kind key");
let kind_type = pool.add_utf8("Ldemo/Kind;").expect("kind type");
let one = pool.add_utf8("ONE").expect("one");
let nested_key = pool.add_utf8("nested").expect("nested key");
let nested_type = pool.add_utf8("Ldemo/Nested;").expect("nested type");
let arr_key = pool.add_utf8("arr").expect("arr key");
let seven = pool.add_integer(7).expect("seven");
let annotation = RtAnno {
type_index: marker,
elements: vec![
RtPair {
name_index: value_key,
value: RtElem::String {
const_value_index: hello,
},
},
RtPair {
name_index: klass_key,
value: RtElem::Class {
class_info_index: target,
},
},
RtPair {
name_index: kind_key,
value: RtElem::Enum {
type_name_index: kind_type,
const_name_index: one,
},
},
RtPair {
name_index: nested_key,
value: RtElem::Annotation {
annotation: RtAnno {
type_index: nested_type,
elements: vec![],
},
},
},
RtPair {
name_index: arr_key,
value: RtElem::Array {
values: vec![
RtElem::String {
const_value_index: hello,
},
RtElem::Int {
const_value_index: seven,
},
],
},
},
],
};
let rt = RtClass {
version: JAVA_21,
constant_pool: pool,
access_flags: ClassAccessFlags::PUBLIC,
this_class: this,
super_class: object,
interfaces: vec![],
fields: vec![],
methods: vec![RtMethod {
access_flags: MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC,
name_index: name,
descriptor_index: desc,
attributes: vec![
RtAttr::Code {
name_index: code_name,
max_stack: 0,
max_locals: 1,
code: vec![Rt::Return],
exception_table: vec![],
attributes: vec![],
},
RtAttr::RuntimeVisibleAnnotations {
name_index: attr_name,
annotations: vec![annotation],
},
],
}],
attributes: vec![],
..RtClass::default()
};
let ir = round_trip(&rt);
let attrs = &ir.methods[0].attributes;
let annos = attrs
.iter()
.find_map(|a| match a {
noxis_classfile::Attribute::RuntimeVisibleAnnotations(list) => Some(list),
_ => None,
})
.expect("annotations modeled");
assert_eq!(annos.len(), 1);
assert_eq!(annos[0].descriptor, "Ldemo/Marker;");
assert_eq!(annos[0].elements.len(), 5);
}