classfile: writer honors itf flag for invokestatic on interfaces
This commit is contained in:
parent
5901c9eb69
commit
4b14eeb40f
5 changed files with 123 additions and 10 deletions
108
crates/noxis-classfile/tests/roundtrip/code.rs
Normal file
108
crates/noxis-classfile/tests/roundtrip/code.rs
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
//! Code-level round trips that the IR must survive.
|
||||
|
||||
use noxis_classfile::Insn;
|
||||
use noxis_classfile::format;
|
||||
use ristretto_classfile::attributes::{Attribute as RtAttr, Instruction as Rt};
|
||||
use ristretto_classfile::{
|
||||
ClassAccessFlags, ClassFile as RtClass, ConstantPool, JAVA_21, Method as RtMethod,
|
||||
MethodAccessFlags,
|
||||
};
|
||||
|
||||
/// `invokestatic` on an *interface* method (`List.of`) needs a
|
||||
/// `CONSTANT_InterfaceMethodref`: writing a plain `Methodref` passes the
|
||||
/// verifier's structural check but HotSpot rejects the linkage with
|
||||
/// `IncompatibleClassChangeError` at first call.
|
||||
#[test]
|
||||
fn invokestatic_interface_method_ref_round_trip() {
|
||||
let mut pool = ConstantPool::default();
|
||||
let this = pool.add_class("demo/IfStatic").expect("this");
|
||||
let object = pool.add_class("java/lang/Object").expect("object");
|
||||
let name = pool.add_utf8("main").expect("name");
|
||||
let desc = pool.add_utf8("([Ljava/lang/String;)V").expect("desc");
|
||||
let code_name = pool.add_utf8("Code").expect("code name");
|
||||
let list_class = pool.add_class("java/util/List").expect("list");
|
||||
let of = pool
|
||||
.add_interface_method_ref(
|
||||
list_class,
|
||||
"of",
|
||||
"(Ljava/lang/Object;Ljava/lang/Object;)Ljava/util/List;",
|
||||
)
|
||||
.expect("interface ref");
|
||||
|
||||
let code = vec![
|
||||
Rt::Aload_0,
|
||||
Rt::Aload_0,
|
||||
Rt::Invokestatic(of),
|
||||
Rt::Pop,
|
||||
Rt::Return,
|
||||
];
|
||||
let rt = RtClass {
|
||||
version: JAVA_21,
|
||||
constant_pool: pool,
|
||||
access_flags: ClassAccessFlags::PUBLIC | ClassAccessFlags::SUPER,
|
||||
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: 2,
|
||||
max_locals: 1,
|
||||
code,
|
||||
exception_table: vec![],
|
||||
attributes: vec![],
|
||||
}],
|
||||
}],
|
||||
attributes: vec![],
|
||||
..RtClass::default()
|
||||
};
|
||||
|
||||
let mut original = Vec::new();
|
||||
rt.to_bytes(&mut original).expect("to_bytes");
|
||||
let parsed = format::parse(&original).expect("parse");
|
||||
let method = &parsed.methods[0];
|
||||
let invoke = method
|
||||
.code
|
||||
.as_ref()
|
||||
.expect("code")
|
||||
.insns
|
||||
.iter()
|
||||
.find_map(|i| match i {
|
||||
Insn::Invoke {
|
||||
kind, owner, itf, ..
|
||||
} => Some((*kind, owner.clone(), *itf)),
|
||||
_ => None,
|
||||
})
|
||||
.expect("invokestatic");
|
||||
assert!(invoke.2, "parse must keep the interface flag");
|
||||
|
||||
let written = format::write(&parsed).expect("write");
|
||||
let reparsed = format::parse(&written).expect("re-parse");
|
||||
let method = &reparsed.methods[0];
|
||||
let invoke = method
|
||||
.code
|
||||
.as_ref()
|
||||
.expect("code")
|
||||
.insns
|
||||
.iter()
|
||||
.find_map(|i| match i {
|
||||
Insn::Invoke {
|
||||
kind, owner, itf, ..
|
||||
} => Some((*kind, owner.clone(), *itf)),
|
||||
_ => None,
|
||||
})
|
||||
.expect("invokestatic");
|
||||
assert_eq!(
|
||||
invoke,
|
||||
(
|
||||
noxis_classfile::InvokeKind::Static,
|
||||
"java/util/List".to_string(),
|
||||
true
|
||||
),
|
||||
"InterfaceMethodref must survive write"
|
||||
);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue