classfile,passes: model Nest attributes as IR (fix ClassFormatError)
NestHost/NestMembers were carried as Attribute::Raw with constant-pool indices baked in. During writing the pool is rebuilt in a different order, so those indices pointed at the wrong entries and the JVM rejected the class: ClassFormatError: Nest member/host class_info_index N has bad constant type. Model them explicitly in the IR, convert on parse and write (resolving class names through the new pool), and remap their class names during rename. Add a NestMembers round-trip regression test. Verified on a real jar: all Nest ClassFormatErrors gone.
This commit is contained in:
parent
8b107cf1b3
commit
2674d7fc89
7 changed files with 145 additions and 66 deletions
|
|
@ -38,6 +38,15 @@ pub(super) fn convert(attr: &RtAttr, pool: &ConstantPool<'_>) -> Result<Attribut
|
|||
.map(|class| inner(pool, class))
|
||||
.collect::<Result<_, _>>()?,
|
||||
),
|
||||
RtAttr::NestHost {
|
||||
host_class_index, ..
|
||||
} => Attribute::NestHost(class_name(pool, *host_class_index)?),
|
||||
RtAttr::NestMembers { class_indexes, .. } => Attribute::NestMembers(
|
||||
class_indexes
|
||||
.iter()
|
||||
.map(|index| class_name(pool, *index))
|
||||
.collect::<Result<_, _>>()?,
|
||||
),
|
||||
RtAttr::EnclosingMethod {
|
||||
class_index,
|
||||
method_index,
|
||||
|
|
|
|||
|
|
@ -3,20 +3,16 @@
|
|||
//! Имена атрибутов (`SourceFile`, …) интернируются в пул здесь же — backend
|
||||
//! хранит только `name_index`, строка восстанавливается при чтении.
|
||||
|
||||
use ristretto_classfile::MethodAccessFlags;
|
||||
use ristretto_classfile::attributes::{
|
||||
Attribute as RtAttr, BootstrapMethod as RtBsm, InnerClass as RtInner, LineNumber as RtLine,
|
||||
LocalVariableTable as RtLocal, LocalVariableTypeTable as RtLocalType,
|
||||
MethodParameter as RtParam, NestedClassAccessFlags, Record as RtRecord,
|
||||
Attribute as RtAttr, LineNumber as RtLine, LocalVariableTable as RtLocal,
|
||||
LocalVariableTypeTable as RtLocalType, Record as RtRecord,
|
||||
};
|
||||
|
||||
use super::helpers;
|
||||
use super::pool::Pool;
|
||||
use crate::error::ClassfileError;
|
||||
use crate::insn::{Const, Label};
|
||||
use crate::model::{
|
||||
Attribute, BootstrapMethod, InnerClass, LineNumber, LocalVariable, LocalVariableType,
|
||||
MethodParameter,
|
||||
};
|
||||
use crate::insn::Label;
|
||||
use crate::model::{Attribute, LineNumber, LocalVariable, LocalVariableType};
|
||||
|
||||
/// Позиции таблиц кода: метки → байты текущего layout.
|
||||
pub(super) struct Positions<'a> {
|
||||
|
|
@ -135,7 +131,18 @@ pub(super) fn convert(attr: &Attribute, pool: &mut Pool) -> Result<RtAttr, Class
|
|||
name_index: pool.utf8("InnerClasses")?,
|
||||
classes: classes
|
||||
.iter()
|
||||
.map(|class| inner(class, pool))
|
||||
.map(|class| helpers::inner(class, pool))
|
||||
.collect::<Result<_, _>>()?,
|
||||
},
|
||||
Attribute::NestHost(host) => RtAttr::NestHost {
|
||||
name_index: pool.utf8("NestHost")?,
|
||||
host_class_index: pool.class(host)?,
|
||||
},
|
||||
Attribute::NestMembers(members) => RtAttr::NestMembers {
|
||||
name_index: pool.utf8("NestMembers")?,
|
||||
class_indexes: members
|
||||
.iter()
|
||||
.map(|name| pool.class(name))
|
||||
.collect::<Result<_, _>>()?,
|
||||
},
|
||||
Attribute::ConstantValue(value) => RtAttr::ConstantValue {
|
||||
|
|
@ -146,7 +153,7 @@ pub(super) fn convert(attr: &Attribute, pool: &mut Pool) -> Result<RtAttr, Class
|
|||
name_index: pool.utf8("BootstrapMethods")?,
|
||||
methods: methods
|
||||
.iter()
|
||||
.map(|method| bootstrap(method, pool))
|
||||
.map(|method| helpers::bootstrap(method, pool))
|
||||
.collect::<Result<_, _>>()?,
|
||||
},
|
||||
Attribute::EnclosingMethod { class, method } => RtAttr::EnclosingMethod {
|
||||
|
|
@ -161,7 +168,7 @@ pub(super) fn convert(attr: &Attribute, pool: &mut Pool) -> Result<RtAttr, Class
|
|||
name_index: pool.utf8("MethodParameters")?,
|
||||
parameters: params
|
||||
.iter()
|
||||
.map(|param| method_param(param, pool))
|
||||
.map(|param| helpers::method_param(param, pool))
|
||||
.collect::<Result<_, _>>()?,
|
||||
},
|
||||
Attribute::Raw { name, data } => RtAttr::Unknown {
|
||||
|
|
@ -194,57 +201,3 @@ pub(super) fn convert(attr: &Attribute, pool: &mut Pool) -> Result<RtAttr, Class
|
|||
},
|
||||
})
|
||||
}
|
||||
|
||||
fn inner(class: &InnerClass, pool: &mut Pool) -> Result<RtInner, ClassfileError> {
|
||||
Ok(RtInner {
|
||||
class_info_index: pool.class(&class.inner)?,
|
||||
outer_class_info_index: match &class.outer {
|
||||
None => 0,
|
||||
Some(name) => pool.class(name)?,
|
||||
},
|
||||
name_index: match &class.inner_name {
|
||||
None => 0,
|
||||
Some(name) => pool.utf8(name)?,
|
||||
},
|
||||
access_flags: NestedClassAccessFlags::from_bits_truncate(class.access_flags),
|
||||
})
|
||||
}
|
||||
|
||||
fn bootstrap(method: &BootstrapMethod, pool: &mut Pool) -> Result<RtBsm, ClassfileError> {
|
||||
Ok(RtBsm {
|
||||
bootstrap_method_ref: pool.method_handle(
|
||||
method.kind,
|
||||
&method.owner,
|
||||
&method.name,
|
||||
&method.descriptor,
|
||||
method.itf,
|
||||
)?,
|
||||
arguments: method
|
||||
.args
|
||||
.iter()
|
||||
.map(|arg| bootstrap_arg(arg, pool))
|
||||
.collect::<Result<_, _>>()?,
|
||||
})
|
||||
}
|
||||
|
||||
/// Аргумент bootstrap-метода: те же константы, что в `ldc`, плюс `Dynamic`.
|
||||
fn bootstrap_arg(arg: &Const, pool: &mut Pool) -> Result<u16, ClassfileError> {
|
||||
match arg {
|
||||
Const::Dynamic {
|
||||
name,
|
||||
descriptor,
|
||||
bootstrap,
|
||||
} => pool.dynamic(*bootstrap, name, descriptor),
|
||||
_ => pool.constant(arg),
|
||||
}
|
||||
}
|
||||
|
||||
fn method_param(param: &MethodParameter, pool: &mut Pool) -> Result<RtParam, ClassfileError> {
|
||||
Ok(RtParam {
|
||||
name_index: match ¶m.name {
|
||||
None => 0,
|
||||
Some(name) => pool.utf8(name)?,
|
||||
},
|
||||
access_flags: MethodAccessFlags::from_bits_truncate(param.access_flags),
|
||||
})
|
||||
}
|
||||
|
|
|
|||
76
crates/noxis-classfile/src/format/write/helpers.rs
Normal file
76
crates/noxis-classfile/src/format/write/helpers.rs
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
//! Хелперы сборки `InnerClasses`/`BootstrapMethods`/`MethodParameters`.
|
||||
//!
|
||||
//! Вынесены из `attrs`, чтобы тот держался в лимите 250 строк; логика не
|
||||
//! изменилась — только место. Каждый строит запись backend'а из элемента IR,
|
||||
//! интернируя имена/дескрипторы и резолвя индексы `CONSTANT_Class` через пул.
|
||||
|
||||
use ristretto_classfile::MethodAccessFlags;
|
||||
use ristretto_classfile::attributes::{
|
||||
BootstrapMethod as RtBsm, InnerClass as RtInner, MethodParameter as RtParam,
|
||||
NestedClassAccessFlags,
|
||||
};
|
||||
|
||||
use super::pool::Pool;
|
||||
use crate::error::ClassfileError;
|
||||
use crate::insn::Const;
|
||||
use crate::model::{BootstrapMethod, InnerClass, MethodParameter};
|
||||
|
||||
pub(super) fn inner(class: &InnerClass, pool: &mut Pool) -> Result<RtInner, ClassfileError> {
|
||||
Ok(RtInner {
|
||||
class_info_index: pool.class(&class.inner)?,
|
||||
outer_class_info_index: match &class.outer {
|
||||
None => 0,
|
||||
Some(name) => pool.class(name)?,
|
||||
},
|
||||
name_index: match &class.inner_name {
|
||||
None => 0,
|
||||
Some(name) => pool.utf8(name)?,
|
||||
},
|
||||
access_flags: NestedClassAccessFlags::from_bits_truncate(class.access_flags),
|
||||
})
|
||||
}
|
||||
|
||||
pub(super) fn bootstrap(
|
||||
method: &BootstrapMethod,
|
||||
pool: &mut Pool,
|
||||
) -> Result<RtBsm, ClassfileError> {
|
||||
Ok(RtBsm {
|
||||
bootstrap_method_ref: pool.method_handle(
|
||||
method.kind,
|
||||
&method.owner,
|
||||
&method.name,
|
||||
&method.descriptor,
|
||||
method.itf,
|
||||
)?,
|
||||
arguments: method
|
||||
.args
|
||||
.iter()
|
||||
.map(|arg| bootstrap_arg(arg, pool))
|
||||
.collect::<Result<_, _>>()?,
|
||||
})
|
||||
}
|
||||
|
||||
/// Аргумент bootstrap-метода: те же константы, что в `ldc`, плюс `Dynamic`.
|
||||
fn bootstrap_arg(arg: &Const, pool: &mut Pool) -> Result<u16, ClassfileError> {
|
||||
match arg {
|
||||
Const::Dynamic {
|
||||
name,
|
||||
descriptor,
|
||||
bootstrap,
|
||||
} => pool.dynamic(*bootstrap, name, descriptor),
|
||||
_ => pool.constant(arg),
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn method_param(
|
||||
param: &MethodParameter,
|
||||
pool: &mut Pool,
|
||||
) -> Result<RtParam, ClassfileError> {
|
||||
Ok(RtParam {
|
||||
name_index: match ¶m.name {
|
||||
None => 0,
|
||||
Some(name) => pool.utf8(name)?,
|
||||
},
|
||||
access_flags: MethodAccessFlags::from_bits_truncate(param.access_flags),
|
||||
})
|
||||
}
|
||||
|
|
@ -5,6 +5,7 @@
|
|||
|
||||
mod attrs;
|
||||
pub(crate) mod code;
|
||||
mod helpers;
|
||||
pub(crate) mod pool;
|
||||
|
||||
use ristretto_classfile::{
|
||||
|
|
|
|||
|
|
@ -19,6 +19,10 @@ pub enum Attribute {
|
|||
Deprecated,
|
||||
Synthetic,
|
||||
InnerClasses(Vec<InnerClass>),
|
||||
/// `NestHost`: класс, служащий nest-хостом (JVMS §4.7.28).
|
||||
NestHost(String),
|
||||
/// `NestMembers`: классы-члены этого nest-хоста (JVMS §4.7.29).
|
||||
NestMembers(Vec<String>),
|
||||
/// `ConstantValue` поля (`static final`): значение из пула.
|
||||
ConstantValue(Const),
|
||||
/// `BootstrapMethods` для `invokedynamic` (лямбды): метод + аргументы.
|
||||
|
|
|
|||
|
|
@ -205,6 +205,30 @@ fn bootstrap_methods_round_trip() {
|
|||
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; записанное верифицируется.
|
||||
fn round_trip(rt: &RtClass<'_>) -> ClassFile {
|
||||
let mut original = Vec::new();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue