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
|
|
@ -96,8 +96,9 @@ pub(crate) fn convert(
|
||||||
owner,
|
owner,
|
||||||
name,
|
name,
|
||||||
descriptor,
|
descriptor,
|
||||||
|
itf,
|
||||||
..
|
..
|
||||||
} => invoke(*kind, owner, name, descriptor, pool)?,
|
} => invoke(*kind, owner, name, descriptor, *itf, pool)?,
|
||||||
Insn::InvokeDynamic {
|
Insn::InvokeDynamic {
|
||||||
name,
|
name,
|
||||||
descriptor,
|
descriptor,
|
||||||
|
|
|
||||||
|
|
@ -166,23 +166,28 @@ pub(super) fn convert_op(op: Convert) -> RtInsn {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// `invoke*` family: the `itf` flag is authoritative (a Static/Special
|
||||||
|
/// call may still target an interface method - `List.of`), so the pool
|
||||||
|
/// entry kind follows it, not the opcode alone.
|
||||||
pub(super) fn invoke(
|
pub(super) fn invoke(
|
||||||
kind: InvokeKind,
|
kind: InvokeKind,
|
||||||
owner: &str,
|
owner: &str,
|
||||||
name: &str,
|
name: &str,
|
||||||
descriptor: &str,
|
descriptor: &str,
|
||||||
|
itf: bool,
|
||||||
pool: &mut Pool,
|
pool: &mut Pool,
|
||||||
) -> Result<RtInsn, ClassfileError> {
|
) -> Result<RtInsn, ClassfileError> {
|
||||||
|
let itf = itf || matches!(kind, InvokeKind::Interface);
|
||||||
Ok(match kind {
|
Ok(match kind {
|
||||||
InvokeKind::Virtual => {
|
InvokeKind::Virtual => {
|
||||||
RtInsn::Invokevirtual(pool.method_ref(owner, name, descriptor, kind)?)
|
RtInsn::Invokevirtual(pool.method_ref(owner, name, descriptor, itf)?)
|
||||||
}
|
}
|
||||||
InvokeKind::Special => {
|
InvokeKind::Special => {
|
||||||
RtInsn::Invokespecial(pool.method_ref(owner, name, descriptor, kind)?)
|
RtInsn::Invokespecial(pool.method_ref(owner, name, descriptor, itf)?)
|
||||||
}
|
}
|
||||||
InvokeKind::Static => RtInsn::Invokestatic(pool.method_ref(owner, name, descriptor, kind)?),
|
InvokeKind::Static => RtInsn::Invokestatic(pool.method_ref(owner, name, descriptor, itf)?),
|
||||||
InvokeKind::Interface => {
|
InvokeKind::Interface => {
|
||||||
let index = pool.method_ref(owner, name, descriptor, kind)?;
|
let index = pool.method_ref(owner, name, descriptor, itf)?;
|
||||||
RtInsn::Invokeinterface(index, arg_count(descriptor)?)
|
RtInsn::Invokeinterface(index, arg_count(descriptor)?)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,6 @@
|
||||||
use ristretto_classfile::{Constant, ReferenceKind};
|
use ristretto_classfile::{Constant, ReferenceKind};
|
||||||
|
|
||||||
use crate::error::ClassfileError;
|
use crate::error::ClassfileError;
|
||||||
use crate::insn::InvokeKind;
|
|
||||||
|
|
||||||
use super::{Pool, write_err};
|
use super::{Pool, write_err};
|
||||||
|
|
||||||
|
|
@ -42,9 +41,8 @@ impl Pool {
|
||||||
owner: &str,
|
owner: &str,
|
||||||
name: &str,
|
name: &str,
|
||||||
descriptor: &str,
|
descriptor: &str,
|
||||||
kind: InvokeKind,
|
itf: bool,
|
||||||
) -> Result<u16, ClassfileError> {
|
) -> Result<u16, ClassfileError> {
|
||||||
let itf = matches!(kind, InvokeKind::Interface);
|
|
||||||
let key = (
|
let key = (
|
||||||
owner.to_string(),
|
owner.to_string(),
|
||||||
name.to_string(),
|
name.to_string(),
|
||||||
|
|
@ -94,7 +92,7 @@ impl Pool {
|
||||||
ClassfileError::Write(format!("некорректный reference_kind {kind}: {e}"))
|
ClassfileError::Write(format!("некорректный reference_kind {kind}: {e}"))
|
||||||
})?;
|
})?;
|
||||||
let reference = if itf {
|
let reference = if itf {
|
||||||
self.method_ref(owner, name, descriptor, InvokeKind::Interface)?
|
self.method_ref(owner, name, descriptor, true)?
|
||||||
} else if matches!(
|
} else if matches!(
|
||||||
reference_kind,
|
reference_kind,
|
||||||
ReferenceKind::GetField
|
ReferenceKind::GetField
|
||||||
|
|
@ -104,7 +102,7 @@ impl Pool {
|
||||||
) {
|
) {
|
||||||
self.field_ref(owner, name, descriptor)?
|
self.field_ref(owner, name, descriptor)?
|
||||||
} else {
|
} else {
|
||||||
self.method_ref(owner, name, descriptor, InvokeKind::Static)?
|
self.method_ref(owner, name, descriptor, false)?
|
||||||
};
|
};
|
||||||
let index = self
|
let index = self
|
||||||
.pool
|
.pool
|
||||||
|
|
|
||||||
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"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -169,3 +169,4 @@ fn round_trip(rt: &RtClass<'_>) -> ClassFile {
|
||||||
/// survive the round trip: HotSpot tolerates it, so we normalize on parse
|
/// survive the round trip: HotSpot tolerates it, so we normalize on parse
|
||||||
/// instead of dying in the backend's strict verifier on write.
|
/// instead of dying in the backend's strict verifier on write.
|
||||||
mod attrs;
|
mod attrs;
|
||||||
|
mod code;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue