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))
|
.map(|class| inner(pool, class))
|
||||||
.collect::<Result<_, _>>()?,
|
.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 {
|
RtAttr::EnclosingMethod {
|
||||||
class_index,
|
class_index,
|
||||||
method_index,
|
method_index,
|
||||||
|
|
|
||||||
|
|
@ -3,20 +3,16 @@
|
||||||
//! Имена атрибутов (`SourceFile`, …) интернируются в пул здесь же — backend
|
//! Имена атрибутов (`SourceFile`, …) интернируются в пул здесь же — backend
|
||||||
//! хранит только `name_index`, строка восстанавливается при чтении.
|
//! хранит только `name_index`, строка восстанавливается при чтении.
|
||||||
|
|
||||||
use ristretto_classfile::MethodAccessFlags;
|
|
||||||
use ristretto_classfile::attributes::{
|
use ristretto_classfile::attributes::{
|
||||||
Attribute as RtAttr, BootstrapMethod as RtBsm, InnerClass as RtInner, LineNumber as RtLine,
|
Attribute as RtAttr, LineNumber as RtLine, LocalVariableTable as RtLocal,
|
||||||
LocalVariableTable as RtLocal, LocalVariableTypeTable as RtLocalType,
|
LocalVariableTypeTable as RtLocalType, Record as RtRecord,
|
||||||
MethodParameter as RtParam, NestedClassAccessFlags, Record as RtRecord,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use super::helpers;
|
||||||
use super::pool::Pool;
|
use super::pool::Pool;
|
||||||
use crate::error::ClassfileError;
|
use crate::error::ClassfileError;
|
||||||
use crate::insn::{Const, Label};
|
use crate::insn::Label;
|
||||||
use crate::model::{
|
use crate::model::{Attribute, LineNumber, LocalVariable, LocalVariableType};
|
||||||
Attribute, BootstrapMethod, InnerClass, LineNumber, LocalVariable, LocalVariableType,
|
|
||||||
MethodParameter,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Позиции таблиц кода: метки → байты текущего layout.
|
/// Позиции таблиц кода: метки → байты текущего layout.
|
||||||
pub(super) struct Positions<'a> {
|
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")?,
|
name_index: pool.utf8("InnerClasses")?,
|
||||||
classes: classes
|
classes: classes
|
||||||
.iter()
|
.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<_, _>>()?,
|
.collect::<Result<_, _>>()?,
|
||||||
},
|
},
|
||||||
Attribute::ConstantValue(value) => RtAttr::ConstantValue {
|
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")?,
|
name_index: pool.utf8("BootstrapMethods")?,
|
||||||
methods: methods
|
methods: methods
|
||||||
.iter()
|
.iter()
|
||||||
.map(|method| bootstrap(method, pool))
|
.map(|method| helpers::bootstrap(method, pool))
|
||||||
.collect::<Result<_, _>>()?,
|
.collect::<Result<_, _>>()?,
|
||||||
},
|
},
|
||||||
Attribute::EnclosingMethod { class, method } => RtAttr::EnclosingMethod {
|
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")?,
|
name_index: pool.utf8("MethodParameters")?,
|
||||||
parameters: params
|
parameters: params
|
||||||
.iter()
|
.iter()
|
||||||
.map(|param| method_param(param, pool))
|
.map(|param| helpers::method_param(param, pool))
|
||||||
.collect::<Result<_, _>>()?,
|
.collect::<Result<_, _>>()?,
|
||||||
},
|
},
|
||||||
Attribute::Raw { name, data } => RtAttr::Unknown {
|
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;
|
mod attrs;
|
||||||
pub(crate) mod code;
|
pub(crate) mod code;
|
||||||
|
mod helpers;
|
||||||
pub(crate) mod pool;
|
pub(crate) mod pool;
|
||||||
|
|
||||||
use ristretto_classfile::{
|
use ristretto_classfile::{
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,10 @@ pub enum Attribute {
|
||||||
Deprecated,
|
Deprecated,
|
||||||
Synthetic,
|
Synthetic,
|
||||||
InnerClasses(Vec<InnerClass>),
|
InnerClasses(Vec<InnerClass>),
|
||||||
|
/// `NestHost`: класс, служащий nest-хостом (JVMS §4.7.28).
|
||||||
|
NestHost(String),
|
||||||
|
/// `NestMembers`: классы-члены этого nest-хоста (JVMS §4.7.29).
|
||||||
|
NestMembers(Vec<String>),
|
||||||
/// `ConstantValue` поля (`static final`): значение из пула.
|
/// `ConstantValue` поля (`static final`): значение из пула.
|
||||||
ConstantValue(Const),
|
ConstantValue(Const),
|
||||||
/// `BootstrapMethods` для `invokedynamic` (лямбды): метод + аргументы.
|
/// `BootstrapMethods` для `invokedynamic` (лямбды): метод + аргументы.
|
||||||
|
|
|
||||||
|
|
@ -205,6 +205,30 @@ fn bootstrap_methods_round_trip() {
|
||||||
assert!(format!("{:?}", ir.attributes[0]).contains("BootstrapMethods"));
|
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; записанное верифицируется.
|
/// `parse → write → parse` даёт тот же IR; записанное верифицируется.
|
||||||
fn round_trip(rt: &RtClass<'_>) -> ClassFile {
|
fn round_trip(rt: &RtClass<'_>) -> ClassFile {
|
||||||
let mut original = Vec::new();
|
let mut original = Vec::new();
|
||||||
|
|
|
||||||
|
|
@ -122,6 +122,18 @@ fn rewrite_attributes(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Attribute::NestHost(host) => {
|
||||||
|
if let Some(mapped) = maps.classes.get(host.as_str()) {
|
||||||
|
*host = mapped.clone();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Attribute::NestMembers(members) => {
|
||||||
|
for name in members.iter_mut() {
|
||||||
|
if let Some(mapped) = maps.classes.get(name.as_str()) {
|
||||||
|
*name = mapped.clone();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Attribute::LocalVariableTable(vars) => {
|
Attribute::LocalVariableTable(vars) => {
|
||||||
for var in vars.iter_mut() {
|
for var in vars.iter_mut() {
|
||||||
var.descriptor = rewrite_descriptor(&var.descriptor, &maps.classes);
|
var.descriptor = rewrite_descriptor(&var.descriptor, &maps.classes);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue