classfile,passes: model runtime-visible annotations, rewrite their descriptors
This commit is contained in:
parent
4b14eeb40f
commit
48a5bc23a9
13 changed files with 527 additions and 4 deletions
|
|
@ -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)?,
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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())),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
78
crates/noxis-classfile/src/format/write/annotations.rs
Normal file
78
crates/noxis-classfile/src/format/write/annotations.rs
Normal 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<_, _>>()?,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
|
@ -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(),
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
42
crates/noxis-classfile/src/model/annotations.rs
Normal file
42
crates/noxis-classfile/src/model/annotations.rs
Normal 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>),
|
||||
}
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -144,3 +144,124 @@ fn permitted_subclasses_round_trip() {
|
|||
let ir = round_trip(&rt);
|
||||
assert_eq!(ir.this_class, "demo/Sealed");
|
||||
}
|
||||
|
||||
/// Runtime-visible annotations on a method survive the round trip: the
|
||||
/// pool is rebuilt on write, so raw index blobs would go stale and
|
||||
/// reflection (`Method.getAnnotation`) would die with
|
||||
/// `AnnotationFormatError`/`GenericSignatureFormatError`.
|
||||
#[test]
|
||||
fn runtime_visible_annotations_round_trip() {
|
||||
use ristretto_classfile::attributes::{
|
||||
Annotation as RtAnno, AnnotationElement as RtElem, AnnotationValuePair as RtPair,
|
||||
};
|
||||
let mut pool = ConstantPool::default();
|
||||
let this = pool.add_class("demo/Anno").expect("this");
|
||||
let object = pool.add_class("java/lang/Object").expect("object");
|
||||
let name = pool.add_utf8("run").expect("name");
|
||||
let desc = pool.add_utf8("()V").expect("desc");
|
||||
let code_name = pool.add_utf8("Code").expect("code");
|
||||
let attr_name = pool.add_utf8("RuntimeVisibleAnnotations").expect("rva");
|
||||
let marker = pool.add_utf8("Ldemo/Marker;").expect("marker");
|
||||
let value_key = pool.add_utf8("value").expect("value key");
|
||||
let hello = pool.add_utf8("hello").expect("hello");
|
||||
let klass_key = pool.add_utf8("klass").expect("klass key");
|
||||
let target = pool.add_utf8("Ldemo/Target;").expect("target");
|
||||
let kind_key = pool.add_utf8("kind").expect("kind key");
|
||||
let kind_type = pool.add_utf8("Ldemo/Kind;").expect("kind type");
|
||||
let one = pool.add_utf8("ONE").expect("one");
|
||||
let nested_key = pool.add_utf8("nested").expect("nested key");
|
||||
let nested_type = pool.add_utf8("Ldemo/Nested;").expect("nested type");
|
||||
let arr_key = pool.add_utf8("arr").expect("arr key");
|
||||
let seven = pool.add_integer(7).expect("seven");
|
||||
|
||||
let annotation = RtAnno {
|
||||
type_index: marker,
|
||||
elements: vec![
|
||||
RtPair {
|
||||
name_index: value_key,
|
||||
value: RtElem::String {
|
||||
const_value_index: hello,
|
||||
},
|
||||
},
|
||||
RtPair {
|
||||
name_index: klass_key,
|
||||
value: RtElem::Class {
|
||||
class_info_index: target,
|
||||
},
|
||||
},
|
||||
RtPair {
|
||||
name_index: kind_key,
|
||||
value: RtElem::Enum {
|
||||
type_name_index: kind_type,
|
||||
const_name_index: one,
|
||||
},
|
||||
},
|
||||
RtPair {
|
||||
name_index: nested_key,
|
||||
value: RtElem::Annotation {
|
||||
annotation: RtAnno {
|
||||
type_index: nested_type,
|
||||
elements: vec![],
|
||||
},
|
||||
},
|
||||
},
|
||||
RtPair {
|
||||
name_index: arr_key,
|
||||
value: RtElem::Array {
|
||||
values: vec![
|
||||
RtElem::String {
|
||||
const_value_index: hello,
|
||||
},
|
||||
RtElem::Int {
|
||||
const_value_index: seven,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
let rt = RtClass {
|
||||
version: JAVA_21,
|
||||
constant_pool: pool,
|
||||
access_flags: ClassAccessFlags::PUBLIC,
|
||||
this_class: this,
|
||||
super_class: object,
|
||||
interfaces: vec![],
|
||||
fields: vec![],
|
||||
methods: vec![RtMethod {
|
||||
access_flags: MethodAccessFlags::PUBLIC | MethodAccessFlags::STATIC,
|
||||
name_index: name,
|
||||
descriptor_index: desc,
|
||||
attributes: vec![
|
||||
RtAttr::Code {
|
||||
name_index: code_name,
|
||||
max_stack: 0,
|
||||
max_locals: 1,
|
||||
code: vec![Rt::Return],
|
||||
exception_table: vec![],
|
||||
attributes: vec![],
|
||||
},
|
||||
RtAttr::RuntimeVisibleAnnotations {
|
||||
name_index: attr_name,
|
||||
annotations: vec![annotation],
|
||||
},
|
||||
],
|
||||
}],
|
||||
attributes: vec![],
|
||||
..RtClass::default()
|
||||
};
|
||||
|
||||
let ir = round_trip(&rt);
|
||||
let attrs = &ir.methods[0].attributes;
|
||||
let annos = attrs
|
||||
.iter()
|
||||
.find_map(|a| match a {
|
||||
noxis_classfile::Attribute::RuntimeVisibleAnnotations(list) => Some(list),
|
||||
_ => None,
|
||||
})
|
||||
.expect("annotations modeled");
|
||||
assert_eq!(annos.len(), 1);
|
||||
assert_eq!(annos[0].descriptor, "Ldemo/Marker;");
|
||||
assert_eq!(annos[0].elements.len(), 5);
|
||||
}
|
||||
|
|
|
|||
41
crates/noxis-passes/src/rename/rewrite/annotations.rs
Normal file
41
crates/noxis-passes/src/rename/rewrite/annotations.rs
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
//! Annotation rewriting: class descriptors follow the mapping, string
|
||||
//! values are data and stay put.
|
||||
|
||||
use noxis_classfile::{Annotation, AnnotationValue};
|
||||
|
||||
use super::super::assign::{RenameMaps, rewrite_descriptor};
|
||||
|
||||
/// A list of annotations (visible or invisible) - same rewrite for both.
|
||||
pub(super) fn rewrite_annotations(annotations: &mut [Annotation], maps: &RenameMaps) {
|
||||
for anno in annotations.iter_mut() {
|
||||
rewrite_annotation(anno, maps);
|
||||
}
|
||||
}
|
||||
|
||||
fn rewrite_annotation(anno: &mut Annotation, maps: &RenameMaps) {
|
||||
anno.descriptor = rewrite_descriptor(&anno.descriptor, &maps.classes);
|
||||
for (_, value) in anno.elements.iter_mut() {
|
||||
rewrite_value(value, maps);
|
||||
}
|
||||
}
|
||||
|
||||
fn rewrite_value(value: &mut AnnotationValue, maps: &RenameMaps) {
|
||||
match value {
|
||||
// The `'e'` tag: enum type descriptor moves, the constant name does not.
|
||||
AnnotationValue::Enum { descriptor, .. } => {
|
||||
*descriptor = rewrite_descriptor(descriptor, &maps.classes);
|
||||
}
|
||||
// The `'c'` tag: class-info descriptor (`LFoo;` or a primitive `I`).
|
||||
AnnotationValue::Class(desc) => {
|
||||
*desc = rewrite_descriptor(desc, &maps.classes);
|
||||
}
|
||||
AnnotationValue::Nested(anno) => rewrite_annotation(anno, maps),
|
||||
AnnotationValue::Array(values) => {
|
||||
for value in values.iter_mut() {
|
||||
rewrite_value(value, maps);
|
||||
}
|
||||
}
|
||||
// Scalars and strings hold no class references.
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,9 @@
|
|||
//! Переписывание одного класса: имена, дескрипторы, код, атрибуты.
|
||||
|
||||
mod annotations;
|
||||
mod code;
|
||||
|
||||
use annotations::rewrite_annotations;
|
||||
use noxis_classfile::{Attribute, ClassFile, Const, StackMapFrame, VerificationType};
|
||||
use noxis_core::{Config, Context};
|
||||
|
||||
|
|
@ -141,6 +143,10 @@ fn rewrite_attributes(
|
|||
}
|
||||
}
|
||||
}
|
||||
Attribute::RuntimeVisibleAnnotations(annos)
|
||||
| Attribute::RuntimeInvisibleAnnotations(annos) => {
|
||||
rewrite_annotations(annos, maps);
|
||||
}
|
||||
Attribute::BootstrapMethods(methods) => {
|
||||
for bsm in methods.iter_mut() {
|
||||
rewrite_method_ref(&mut bsm.owner, &mut bsm.name, &mut bsm.descriptor, maps);
|
||||
|
|
|
|||
100
crates/noxis-passes/src/rename/tests/annotations.rs
Normal file
100
crates/noxis-passes/src/rename/tests/annotations.rs
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
//! Annotation rewriting: descriptors follow the mapping, values do not.
|
||||
|
||||
#![allow(clippy::expect_used, clippy::unwrap_used)]
|
||||
|
||||
use super::run_rename;
|
||||
use noxis_classfile::{Annotation, AnnotationValue, Attribute, ClassBuilder, Version};
|
||||
|
||||
/// Class references inside annotations live as descriptors: a stale one
|
||||
/// makes the JVM's reflection parser fail (`AnnotationFormatError`) or
|
||||
/// resolve a ghost type. String *values* are data and stay put.
|
||||
#[test]
|
||||
fn annotation_descriptors_follow_the_mapping() {
|
||||
let mut program = super::sample();
|
||||
program.classes.push(
|
||||
ClassBuilder::new("app/Annotated", Some("java/lang/Object"))
|
||||
.version(Version::JAVA_21)
|
||||
.method_with_code(0x000a, "go", "()V", noxis_classfile::empty_code(0))
|
||||
.build(),
|
||||
);
|
||||
let annotated = program
|
||||
.classes
|
||||
.iter_mut()
|
||||
.find(|c| c.this_class == "app/Annotated")
|
||||
.expect("annotated");
|
||||
annotated
|
||||
.attributes
|
||||
.push(Attribute::RuntimeVisibleAnnotations(vec![Annotation {
|
||||
descriptor: "Lapp/Helper;".to_string(),
|
||||
elements: vec![
|
||||
(
|
||||
"kind".to_string(),
|
||||
AnnotationValue::Enum {
|
||||
descriptor: "Lapp/Helper;".to_string(),
|
||||
name: "ONE".to_string(),
|
||||
},
|
||||
),
|
||||
(
|
||||
"klass".to_string(),
|
||||
AnnotationValue::Class("Lapp/Helper;".to_string()),
|
||||
),
|
||||
(
|
||||
"nested".to_string(),
|
||||
AnnotationValue::Nested(Box::new(Annotation {
|
||||
descriptor: "Lapp/Helper;".to_string(),
|
||||
elements: vec![(
|
||||
"note".to_string(),
|
||||
AnnotationValue::String("app/Helper".to_string()),
|
||||
)],
|
||||
})),
|
||||
),
|
||||
(
|
||||
"arr".to_string(),
|
||||
AnnotationValue::Array(vec![AnnotationValue::Class(
|
||||
"[Lapp/Helper;".to_string(),
|
||||
)]),
|
||||
),
|
||||
],
|
||||
}]));
|
||||
let ctx = run_rename(&mut program);
|
||||
let helper = ctx.mapping.class("app/Helper").expect("renamed");
|
||||
let annotated_name = ctx.mapping.class("app/Annotated").expect("annotated");
|
||||
let class = program.class(annotated_name).expect("class");
|
||||
let annos = class
|
||||
.attributes
|
||||
.iter()
|
||||
.find_map(|a| match a {
|
||||
Attribute::RuntimeVisibleAnnotations(list) => Some(list),
|
||||
_ => None,
|
||||
})
|
||||
.expect("annotations");
|
||||
let anno = &annos[0];
|
||||
assert_eq!(anno.descriptor, format!("L{helper};"));
|
||||
let expect_desc = format!("L{helper};");
|
||||
assert_eq!(
|
||||
anno.elements[0].1,
|
||||
AnnotationValue::Enum {
|
||||
descriptor: expect_desc.clone(),
|
||||
name: "ONE".to_string(),
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
anno.elements[1].1,
|
||||
AnnotationValue::Class(expect_desc.clone())
|
||||
);
|
||||
match &anno.elements[2].1 {
|
||||
AnnotationValue::Nested(nested) => {
|
||||
assert_eq!(nested.descriptor, expect_desc);
|
||||
assert_eq!(
|
||||
nested.elements[0].1,
|
||||
AnnotationValue::String("app/Helper".to_string()),
|
||||
"string values are data, not references"
|
||||
);
|
||||
}
|
||||
other => panic!("nested expected, got {other:?}"),
|
||||
}
|
||||
assert_eq!(
|
||||
anno.elements[3].1,
|
||||
AnnotationValue::Array(vec![AnnotationValue::Class(format!("[L{helper};"))])
|
||||
);
|
||||
}
|
||||
|
|
@ -13,6 +13,7 @@ use noxis_core::{Config, Context, Pass, RenameConfig};
|
|||
use super::RenamePass;
|
||||
use super::assign::{RenameMaps, rewrite_descriptor};
|
||||
|
||||
mod annotations;
|
||||
mod enclosing;
|
||||
mod rewrite;
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue