classfile,passes: model RuntimeVisibleTypeAnnotations and AnnotationDefault

Type annotations (method- and code-level) and annotation defaults were
passed through as Raw bytes with the original pool indices; the writer
rebuilds the pool, so those indices pointed at the wrong utf8 or past
the pool end. HotSpot tolerates the garbage (lazy reflection parsing),
but ASM's ClassReader dies with ArrayIndexOutOfBoundsException while
Paper's plugin converter reads every method - 76 of 275 LoParkour
classes failed to convert. Target_info and type_path carry no pool
references and stay opaque bytes; type descriptors and element values
are modeled and rewritten by rename like plain annotations.
This commit is contained in:
Loki 2026-10-13 10:17:10 +02:00
parent 3f92b72c8b
commit 15cd31252e
Signed by: boba
GPG key ID: 253067914055423B
14 changed files with 654 additions and 224 deletions

View file

@ -1,7 +1,7 @@
//! Annotation rewriting: class descriptors follow the mapping, string
//! values are data and stay put.
use noxis_classfile::{Annotation, AnnotationValue};
use noxis_classfile::{Annotation, AnnotationValue, TypeAnnotation};
use super::super::assign::{RenameMaps, rewrite_descriptor};
@ -19,6 +19,18 @@ pub(super) fn rewrite_parameter_annotations(params: &mut [Vec<Annotation>], maps
}
}
/// Type-use annotations (`RuntimeVisibleTypeAnnotations` and friends):
/// the annotated type descriptor follows the mapping, the opaque
/// `target_info`/`type_path` bytes carry no pool references at all.
pub(super) fn rewrite_type_annotations(annos: &mut [TypeAnnotation], maps: &RenameMaps) {
for anno in annos.iter_mut() {
anno.descriptor = rewrite_descriptor(&anno.descriptor, &maps.classes);
for (_, value) in anno.elements.iter_mut() {
rewrite_value(value, maps);
}
}
}
fn rewrite_annotation(anno: &mut Annotation, maps: &RenameMaps) {
anno.descriptor = rewrite_descriptor(&anno.descriptor, &maps.classes);
for (_, value) in anno.elements.iter_mut() {
@ -26,7 +38,7 @@ fn rewrite_annotation(anno: &mut Annotation, maps: &RenameMaps) {
}
}
fn rewrite_value(value: &mut AnnotationValue, maps: &RenameMaps) {
pub(super) 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, .. } => {

View file

@ -0,0 +1,58 @@
//! Constant-pool references: owners and descriptors follow the mapping.
use noxis_classfile::Const;
use super::super::assign::{RenameMaps, rewrite_descriptor};
/// A method reference (invoke/bootstrap/method-handle): the owner follows
/// class renames, the name follows member renames, the descriptor is
/// rewritten. Array owners (`[Lpkg/C;` - e.g. `clone` on arrays) name the
/// element class inside a descriptor, so they take the descriptor path.
pub(super) fn rewrite_method_ref(
owner: &mut String,
name: &mut String,
descriptor: &mut String,
maps: &RenameMaps,
) {
if owner.starts_with('[') {
*owner = rewrite_descriptor(owner, &maps.classes);
} else {
if let Some(new_name) = maps.method(owner, name, descriptor) {
*name = new_name;
}
if let Some(mapped) = maps.classes.get(owner.as_str()) {
*owner = mapped.clone();
}
}
*descriptor = rewrite_descriptor(descriptor, &maps.classes);
}
/// A constant-pool reference inside code or bootstrap arguments: class
/// literals (plain or array), method types, method handles, condy payloads.
/// Anything else (numbers, strings) has no references to follow.
pub(super) fn rewrite_const(value: &mut Const, maps: &RenameMaps) {
match value {
Const::Class(name) => {
if name.starts_with('[') {
*name = rewrite_descriptor(name, &maps.classes);
} else if let Some(mapped) = maps.classes.get(name.as_str()) {
*name = mapped.clone();
}
}
Const::MethodType(descriptor) => {
*descriptor = rewrite_descriptor(descriptor, &maps.classes);
}
Const::MethodHandle {
owner,
name,
descriptor,
..
} => {
rewrite_method_ref(owner, name, descriptor, maps);
}
Const::Dynamic { descriptor, .. } => {
*descriptor = rewrite_descriptor(descriptor, &maps.classes);
}
_ => {}
}
}

View file

@ -2,9 +2,13 @@
mod annotations;
mod code;
mod consts;
use annotations::{rewrite_annotations, rewrite_parameter_annotations};
use noxis_classfile::{Attribute, ClassFile, Const, StackMapFrame, VerificationType};
use annotations::{
rewrite_annotations, rewrite_parameter_annotations, rewrite_type_annotations, rewrite_value,
};
use consts::{rewrite_const, rewrite_method_ref};
use noxis_classfile::{Attribute, ClassFile, StackMapFrame, VerificationType};
use noxis_core::{Config, Context};
use super::assign::{RenameMaps, rewrite_descriptor};
@ -151,6 +155,11 @@ fn rewrite_attributes(
| Attribute::RuntimeInvisibleParameterAnnotations(params) => {
rewrite_parameter_annotations(params, maps);
}
Attribute::RuntimeVisibleTypeAnnotations(annos)
| Attribute::RuntimeInvisibleTypeAnnotations(annos) => {
rewrite_type_annotations(annos, maps);
}
Attribute::AnnotationDefault(value) => rewrite_value(value, maps),
Attribute::BootstrapMethods(methods) => {
for bsm in methods.iter_mut() {
rewrite_method_ref(&mut bsm.owner, &mut bsm.name, &mut bsm.descriptor, maps);
@ -190,59 +199,6 @@ fn rewrite_attributes(
}
}
/// A method reference (invoke/bootstrap/method-handle): the owner follows
/// class renames, the name follows member renames, the descriptor is
/// rewritten. Array owners (`[Lpkg/C;` - e.g. `clone` on arrays) name the
/// element class inside a descriptor, so they take the descriptor path.
fn rewrite_method_ref(
owner: &mut String,
name: &mut String,
descriptor: &mut String,
maps: &RenameMaps,
) {
if owner.starts_with('[') {
*owner = rewrite_descriptor(owner, &maps.classes);
} else {
if let Some(new_name) = maps.method(owner, name, descriptor) {
*name = new_name;
}
if let Some(mapped) = maps.classes.get(owner.as_str()) {
*owner = mapped.clone();
}
}
*descriptor = rewrite_descriptor(descriptor, &maps.classes);
}
/// A constant-pool reference inside code or bootstrap arguments: class
/// literals (plain or array), method types, method handles, condy payloads.
/// Anything else (numbers, strings) has no references to follow.
fn rewrite_const(value: &mut Const, maps: &RenameMaps) {
match value {
Const::Class(name) => {
if name.starts_with('[') {
*name = rewrite_descriptor(name, &maps.classes);
} else if let Some(mapped) = maps.classes.get(name.as_str()) {
*name = mapped.clone();
}
}
Const::MethodType(descriptor) => {
*descriptor = rewrite_descriptor(descriptor, &maps.classes);
}
Const::MethodHandle {
owner,
name,
descriptor,
..
} => {
rewrite_method_ref(owner, name, descriptor, maps);
}
Const::Dynamic { descriptor, .. } => {
*descriptor = rewrite_descriptor(descriptor, &maps.classes);
}
_ => {}
}
}
/// Имена классов внутри одного кадра (`Object(..)` в locals/stack).
fn rewrite_frame(frame: &mut StackMapFrame, maps: &RenameMaps) {
match frame {