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:
Loki 2026-10-08 22:26:58 +02:00
parent 8b107cf1b3
commit 2674d7fc89
7 changed files with 145 additions and 66 deletions

View file

@ -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,

View file

@ -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 &param.name {
None => 0,
Some(name) => pool.utf8(name)?,
},
access_flags: MethodAccessFlags::from_bits_truncate(param.access_flags),
})
}

View 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 &param.name {
None => 0,
Some(name) => pool.utf8(name)?,
},
access_flags: MethodAccessFlags::from_bits_truncate(param.access_flags),
})
}

View file

@ -5,6 +5,7 @@
mod attrs;
pub(crate) mod code;
mod helpers;
pub(crate) mod pool;
use ristretto_classfile::{

View file

@ -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` (лямбды): метод + аргументы.

View file

@ -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();

View file

@ -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) => {
for var in vars.iter_mut() {
var.descriptor = rewrite_descriptor(&var.descriptor, &maps.classes);