classfile,passes: model runtime-visible annotations, rewrite their descriptors

This commit is contained in:
Loki 2026-10-11 13:02:26 +02:00
parent 4b14eeb40f
commit 48a5bc23a9
Signed by: boba
GPG key ID: 253067914055423B
13 changed files with 527 additions and 4 deletions

View file

@ -10,7 +10,7 @@ use ristretto_classfile::ConstantPool;
use ristretto_classfile::attributes::Attribute as RtAttr;
use super::pool::{
class_name, const_from, is_interface, kind_code, name_and_type, ref_parts, utf8,
annotation, class_name, const_from, is_interface, kind_code, name_and_type, ref_parts, utf8,
};
use crate::error::ClassfileError;
use crate::model::{Attribute, BootstrapMethod, InnerClass, MethodParameter, RecordComponent};
@ -107,6 +107,22 @@ pub(super) fn convert(attr: &RtAttr, pool: &ConstantPool<'_>) -> Result<Attribut
"Code/StackMapTable разбирает вызывающий код метода".into(),
));
}
RtAttr::RuntimeVisibleAnnotations { annotations, .. } => {
Attribute::RuntimeVisibleAnnotations(
annotations
.iter()
.map(|anno| annotation(pool, anno))
.collect::<Result<_, _>>()?,
)
}
RtAttr::RuntimeInvisibleAnnotations { annotations, .. } => {
Attribute::RuntimeInvisibleAnnotations(
annotations
.iter()
.map(|anno| annotation(pool, anno))
.collect::<Result<_, _>>()?,
)
}
other => raw(other)?,
})
}

View file

@ -4,6 +4,7 @@ use ristretto_classfile::{Constant, ConstantPool, ReferenceKind};
use crate::error::ClassfileError;
use crate::insn::{Const, InvokeKind};
use crate::model::{Annotation, AnnotationValue};
/// The class name from a `CONSTANT_Class`.
pub(super) fn class_name(pool: &ConstantPool<'_>, index: u16) -> Result<String, ClassfileError> {
@ -182,3 +183,97 @@ pub(super) fn expect_kind(
)))
}
}
/// One `annotation` structure → IR (names, not pool indices).
pub(super) fn annotation(
pool: &ConstantPool<'_>,
anno: &ristretto_classfile::attributes::Annotation,
) -> Result<Annotation, ClassfileError> {
Ok(Annotation {
descriptor: utf8(pool, anno.type_index)?,
elements: anno
.elements
.iter()
.map(|pair| Ok((utf8(pool, pair.name_index)?, element(pool, &pair.value)?)))
.collect::<Result<_, ClassfileError>>()?,
})
}
/// One `element_value` → IR, keeping the tag (HotSpot checks it).
fn element(
pool: &ConstantPool<'_>,
value: &ristretto_classfile::attributes::AnnotationElement,
) -> Result<AnnotationValue, ClassfileError> {
use ristretto_classfile::attributes::AnnotationElement as E;
Ok(match value {
E::Byte { const_value_index } => {
AnnotationValue::Byte(const_int(pool, *const_value_index)?)
}
E::Char { const_value_index } => {
AnnotationValue::Char(const_int(pool, *const_value_index)?)
}
E::Short { const_value_index } => {
AnnotationValue::Short(const_int(pool, *const_value_index)?)
}
E::Boolean { const_value_index } => {
AnnotationValue::Boolean(const_int(pool, *const_value_index)?)
}
E::Int { const_value_index } => AnnotationValue::Int(const_int(pool, *const_value_index)?),
E::Long { const_value_index } => {
AnnotationValue::Long(const_long(pool, *const_value_index)?)
}
E::Float { const_value_index } => {
AnnotationValue::Float(const_float(pool, *const_value_index)?)
}
E::Double { const_value_index } => {
AnnotationValue::Double(const_double(pool, *const_value_index)?)
}
E::String { const_value_index } => AnnotationValue::String(utf8(pool, *const_value_index)?),
E::Enum {
type_name_index,
const_name_index,
} => AnnotationValue::Enum {
descriptor: utf8(pool, *type_name_index)?,
name: utf8(pool, *const_name_index)?,
},
E::Class { class_info_index } => AnnotationValue::Class(utf8(pool, *class_info_index)?),
E::Annotation { annotation: nested } => {
AnnotationValue::Nested(Box::new(annotation(pool, nested)?))
}
E::Array { values } => AnnotationValue::Array(
values
.iter()
.map(|value| element(pool, value))
.collect::<Result<_, _>>()?,
),
})
}
/// The `CONSTANT_Integer` behind a `B`/`C`/`S`/`Z`/`I` element.
fn const_int(pool: &ConstantPool<'_>, index: u16) -> Result<i32, ClassfileError> {
match const_from(pool, index)? {
Const::Int(v) => Ok(v),
_ => Err(ClassfileError::Parse("не Integer".into())),
}
}
fn const_long(pool: &ConstantPool<'_>, index: u16) -> Result<i64, ClassfileError> {
match const_from(pool, index)? {
Const::Long(v) => Ok(v),
_ => Err(ClassfileError::Parse("не Long".into())),
}
}
fn const_float(pool: &ConstantPool<'_>, index: u16) -> Result<f32, ClassfileError> {
match const_from(pool, index)? {
Const::Float(v) => Ok(v),
_ => Err(ClassfileError::Parse("не Float".into())),
}
}
fn const_double(pool: &ConstantPool<'_>, index: u16) -> Result<f64, ClassfileError> {
match const_from(pool, index)? {
Const::Double(v) => Ok(v),
_ => Err(ClassfileError::Parse("не Double".into())),
}
}

View file

@ -0,0 +1,78 @@
//! Runtime-visible/invisible annotations: IR → backend structures.
use ristretto_classfile::attributes::{
Annotation as RtAnno, AnnotationElement as RtElem, AnnotationValuePair as RtPair,
};
use super::pool::Pool;
use crate::error::ClassfileError;
use crate::insn::Const;
use crate::model::{Annotation, AnnotationValue};
/// One IR annotation → backend structure (fresh pool indices).
pub(super) fn annotation(anno: &Annotation, pool: &mut Pool) -> Result<RtAnno, ClassfileError> {
Ok(RtAnno {
type_index: pool.utf8(&anno.descriptor)?,
elements: anno
.elements
.iter()
.map(|(name, value)| {
Ok(RtPair {
name_index: pool.utf8(name)?,
value: element(value, pool)?,
})
})
.collect::<Result<_, ClassfileError>>()?,
})
}
/// One IR element value → backend structure, tag preserved.
fn element(value: &AnnotationValue, pool: &mut Pool) -> Result<RtElem, ClassfileError> {
fn int(v: i32, pool: &mut Pool) -> Result<u16, ClassfileError> {
pool.constant(&Const::Int(v))
}
Ok(match value {
AnnotationValue::Byte(v) => RtElem::Byte {
const_value_index: int(*v, pool)?,
},
AnnotationValue::Char(v) => RtElem::Char {
const_value_index: int(*v, pool)?,
},
AnnotationValue::Short(v) => RtElem::Short {
const_value_index: int(*v, pool)?,
},
AnnotationValue::Boolean(v) => RtElem::Boolean {
const_value_index: int(*v, pool)?,
},
AnnotationValue::Int(v) => RtElem::Int {
const_value_index: int(*v, pool)?,
},
AnnotationValue::Long(v) => RtElem::Long {
const_value_index: pool.constant(&Const::Long(*v))?,
},
AnnotationValue::Float(v) => RtElem::Float {
const_value_index: pool.constant(&Const::Float(*v))?,
},
AnnotationValue::Double(v) => RtElem::Double {
const_value_index: pool.constant(&Const::Double(*v))?,
},
AnnotationValue::String(v) => RtElem::String {
const_value_index: pool.utf8(v)?,
},
AnnotationValue::Enum { descriptor, name } => RtElem::Enum {
type_name_index: pool.utf8(descriptor)?,
const_name_index: pool.utf8(name)?,
},
AnnotationValue::Class(desc) => RtElem::Class {
class_info_index: pool.utf8(desc)?,
},
AnnotationValue::Nested(nested) => RtElem::Annotation {
annotation: annotation(nested, pool)?,
},
AnnotationValue::Array(values) => RtElem::Array {
values: values
.iter()
.map(|value| element(value, pool))
.collect::<Result<_, _>>()?,
},
})
}

View file

@ -8,6 +8,7 @@ use ristretto_classfile::attributes::{
LocalVariableTypeTable as RtLocalType, Record as RtRecord,
};
use super::annotations::annotation;
use super::helpers;
use super::pool::Pool;
use crate::error::ClassfileError;
@ -178,6 +179,20 @@ pub(super) fn convert(attr: &Attribute, pool: &mut Pool) -> Result<RtAttr, Class
.map(|param| helpers::method_param(param, pool))
.collect::<Result<_, _>>()?,
},
Attribute::RuntimeVisibleAnnotations(annos) => RtAttr::RuntimeVisibleAnnotations {
name_index: pool.utf8("RuntimeVisibleAnnotations")?,
annotations: annos
.iter()
.map(|anno| annotation(anno, pool))
.collect::<Result<_, _>>()?,
},
Attribute::RuntimeInvisibleAnnotations(annos) => RtAttr::RuntimeInvisibleAnnotations {
name_index: pool.utf8("RuntimeInvisibleAnnotations")?,
annotations: annos
.iter()
.map(|anno| annotation(anno, pool))
.collect::<Result<_, _>>()?,
},
Attribute::Raw { name, data } => RtAttr::Unknown {
name_index: pool.utf8(name)?,
info: data.clone(),

View file

@ -3,6 +3,7 @@
//! The pool is built from scratch by a deterministic walk; the backend recomputes
//! instruction numbers into byte offsets and verifies the result (`verify`).
mod annotations;
mod attrs;
pub(crate) mod code;
mod helpers;

View file

@ -23,9 +23,9 @@ pub use insn::{
};
pub use model::builder::{ClassBuilder, empty_code};
pub use model::{
Attribute, BootstrapMethod, ClassFile, Code, ExceptionHandler, Field, InnerClass, LineNumber,
LocalVariable, LocalVariableType, Method, MethodParameter, Program, RecordComponent, Resource,
Version,
Annotation, AnnotationValue, Attribute, BootstrapMethod, ClassFile, Code, ExceptionHandler,
Field, InnerClass, LineNumber, LocalVariable, LocalVariableType, Method, MethodParameter,
Program, RecordComponent, Resource, Version,
};
/// Safety budget: a method body that would grow past this many instructions

View file

@ -0,0 +1,42 @@
//! Runtime-visible/invisible annotations: names instead of pool indices.
//!
//! Raw `info` blobs go stale the moment the pool is rebuilt on write, and
//! the JVM's reflection parser then dies with `AnnotationFormatError` or
//! reads neighboring attribute bytes as signatures.
/// One `annotation` structure: the type descriptor + element-value pairs.
#[derive(Debug, Clone, PartialEq)]
pub struct Annotation {
/// The annotation type as a field descriptor (`Lpkg/Ann;`).
pub descriptor: String,
/// `(element name, value)` pairs in class-file order.
pub elements: Vec<(String, AnnotationValue)>,
}
/// A single `element_value`: one variant per tag, so `B`/`C`/`S`/`Z`
/// keep their identity (the pool stores them all as `CONSTANT_Integer`,
/// but HotSpot checks the tag against the annotation member's type).
#[derive(Debug, Clone, PartialEq)]
pub enum AnnotationValue {
Byte(i32),
Char(i32),
Double(f64),
Float(f32),
Int(i32),
Long(i64),
Short(i32),
Boolean(i32),
/// The `s` tag: a `CONSTANT_Utf8`, not a `CONSTANT_String`.
String(String),
/// The `e` tag: enum type descriptor + constant name.
Enum {
descriptor: String,
name: String,
},
/// The `c` tag: a class-info descriptor (`Ljava/lang/String;` or `I`).
Class(String),
/// The `@` tag: a nested annotation.
Nested(Box<Annotation>),
/// The `[` tag: array of values.
Array(Vec<AnnotationValue>),
}

View file

@ -1,8 +1,10 @@
//! The "program" structure - the unit of work for the pipeline.
pub mod annotations;
pub mod builder;
pub mod version;
pub use annotations::{Annotation, AnnotationValue};
pub use version::Version;
use crate::format::StackMapFrame;
@ -41,6 +43,11 @@ pub enum Attribute {
Record(Vec<RecordComponent>),
/// `LocalVariableTypeTable`: generic local signatures (like LVT).
LocalVariableTypeTable(Vec<LocalVariableType>),
/// `RuntimeVisibleAnnotations` (JVMS §4.7.16): parsed, not raw bytes -
/// reflection reads them at runtime and raw indices go stale on write.
RuntimeVisibleAnnotations(Vec<Annotation>),
/// `RuntimeInvisibleAnnotations` (JVMS §4.7.17): CLASS-retention ones.
RuntimeInvisibleAnnotations(Vec<Annotation>),
/// Everything else: name + raw `info` bytes (careful with pool indices).
Raw {
name: String,