classfile: model RuntimeVisible/InvisibleParameterAnnotations (fixes ASM AIOOBE on write)

Parameter annotations were unmodeled and fell through to Raw with stale
constant-pool indices after the pool rebuild. ASM (Paper converter, Fabric
mixin transformer) then failed with ArrayIndexOutOfBoundsException in
readParameterAnnotations. Now parsed and rewritten like class-level
annotations.
This commit is contained in:
Loki 2026-10-12 13:19:12 +02:00
parent 045cd3ed9c
commit b73c72d56d
Signed by: boba
GPG key ID: 253067914055423B
5 changed files with 80 additions and 1 deletions

View file

@ -123,6 +123,36 @@ pub(super) fn convert(attr: &RtAttr, pool: &ConstantPool<'_>) -> Result<Attribut
.collect::<Result<_, _>>()?,
)
}
RtAttr::RuntimeVisibleParameterAnnotations {
parameter_annotations,
..
} => Attribute::RuntimeVisibleParameterAnnotations(
parameter_annotations
.iter()
.map(|param| {
param
.annotations
.iter()
.map(|anno| annotation(pool, anno))
.collect::<Result<_, _>>()
})
.collect::<Result<_, _>>()?,
),
RtAttr::RuntimeInvisibleParameterAnnotations {
parameter_annotations,
..
} => Attribute::RuntimeInvisibleParameterAnnotations(
parameter_annotations
.iter()
.map(|param| {
param
.annotations
.iter()
.map(|anno| annotation(pool, anno))
.collect::<Result<_, _>>()
})
.collect::<Result<_, _>>()?,
),
other => raw(other)?,
})
}

View file

@ -193,6 +193,38 @@ pub(super) fn convert(attr: &Attribute, pool: &mut Pool) -> Result<RtAttr, Class
.map(|anno| annotation(anno, pool))
.collect::<Result<_, _>>()?,
},
Attribute::RuntimeVisibleParameterAnnotations(params) => {
RtAttr::RuntimeVisibleParameterAnnotations {
name_index: pool.utf8("RuntimeVisibleParameterAnnotations")?,
parameter_annotations: params
.iter()
.map(|param| {
Ok(ristretto_classfile::attributes::ParameterAnnotation {
annotations: param
.iter()
.map(|anno| annotation(anno, pool))
.collect::<Result<_, _>>()?,
})
})
.collect::<Result<_, _>>()?,
}
}
Attribute::RuntimeInvisibleParameterAnnotations(params) => {
RtAttr::RuntimeInvisibleParameterAnnotations {
name_index: pool.utf8("RuntimeInvisibleParameterAnnotations")?,
parameter_annotations: params
.iter()
.map(|param| {
Ok(ristretto_classfile::attributes::ParameterAnnotation {
annotations: param
.iter()
.map(|anno| annotation(anno, pool))
.collect::<Result<_, _>>()?,
})
})
.collect::<Result<_, _>>()?,
}
}
Attribute::Raw { name, data } => RtAttr::Unknown {
name_index: pool.utf8(name)?,
info: data.clone(),

View file

@ -48,6 +48,12 @@ pub enum Attribute {
RuntimeVisibleAnnotations(Vec<Annotation>),
/// `RuntimeInvisibleAnnotations` (JVMS §4.7.17): CLASS-retention ones.
RuntimeInvisibleAnnotations(Vec<Annotation>),
/// `RuntimeVisibleParameterAnnotations` (JVMS §4.7.18): per-parameter
/// annotations. Parsed like the class-level ones - raw pool indices go
/// stale when the pool is rebuilt.
RuntimeVisibleParameterAnnotations(Vec<Vec<Annotation>>),
/// `RuntimeInvisibleParameterAnnotations` (JVMS §4.7.19).
RuntimeInvisibleParameterAnnotations(Vec<Vec<Annotation>>),
/// Everything else: name + raw `info` bytes (careful with pool indices).
Raw {
name: String,

View file

@ -12,6 +12,13 @@ pub(super) fn rewrite_annotations(annotations: &mut [Annotation], maps: &RenameM
}
}
/// Per-parameter annotation lists (`RuntimeVisibleParameterAnnotations`).
pub(super) fn rewrite_parameter_annotations(params: &mut [Vec<Annotation>], maps: &RenameMaps) {
for param in params.iter_mut() {
rewrite_annotations(param, maps);
}
}
fn rewrite_annotation(anno: &mut Annotation, maps: &RenameMaps) {
anno.descriptor = rewrite_descriptor(&anno.descriptor, &maps.classes);
for (_, value) in anno.elements.iter_mut() {

View file

@ -3,7 +3,7 @@
mod annotations;
mod code;
use annotations::rewrite_annotations;
use annotations::{rewrite_annotations, rewrite_parameter_annotations};
use noxis_classfile::{Attribute, ClassFile, Const, StackMapFrame, VerificationType};
use noxis_core::{Config, Context};
@ -147,6 +147,10 @@ fn rewrite_attributes(
| Attribute::RuntimeInvisibleAnnotations(annos) => {
rewrite_annotations(annos, maps);
}
Attribute::RuntimeVisibleParameterAnnotations(params)
| Attribute::RuntimeInvisibleParameterAnnotations(params) => {
rewrite_parameter_annotations(params, maps);
}
Attribute::BootstrapMethods(methods) => {
for bsm in methods.iter_mut() {
rewrite_method_ref(&mut bsm.owner, &mut bsm.name, &mut bsm.descriptor, maps);