classfile: fix condy ldc category (ldc_w vs ldc2_w by descriptor), split attrs tests
This commit is contained in:
parent
6dd9c220d4
commit
a4450b69ba
3 changed files with 169 additions and 1 deletions
154
crates/noxis-classfile/tests/roundtrip/attrs/condy.rs
Normal file
154
crates/noxis-classfile/tests/roundtrip/attrs/condy.rs
Normal file
|
|
@ -0,0 +1,154 @@
|
|||
//! `CONSTANT_Dynamic` (condy) round trips: the writer must be able to emit a
|
||||
//! brand-new `ldc` on a `Const::Dynamic`, not just pass one through on read.
|
||||
//!
|
||||
//! Why this matters: a generic sandbox-based deobfuscator (narumii/SSVM)
|
||||
//! confirmed empirically (`todo.md`, 2026-10-14) resolves plain arithmetic
|
||||
//! and runtime-decrypt calls, but does not resolve `invokedynamic`/condy at
|
||||
//! all - 0 changes on a condy-only test class. Strings/Constants moving to
|
||||
//! condy is the first concrete fix that follows from that finding.
|
||||
|
||||
#![allow(clippy::expect_used, clippy::unwrap_used)]
|
||||
|
||||
use super::super::round_trip;
|
||||
use noxis_classfile::{
|
||||
Attribute, BootstrapMethod, ClassBuilder, Code, Const, Insn, Version, format,
|
||||
};
|
||||
use ristretto_classfile::attributes::{
|
||||
Attribute as RtAttr, BootstrapMethod as RtBsm, Instruction as Rt,
|
||||
};
|
||||
use ristretto_classfile::{
|
||||
ClassAccessFlags, ClassFile as RtClass, ConstantPool, JAVA_21, Method as RtMethod,
|
||||
MethodAccessFlags, ReferenceKind,
|
||||
};
|
||||
|
||||
/// `CONSTANT_Dynamic` (condy, `ldc`) survives the round trip - needed for
|
||||
/// passes that emit strings/constants through a bootstrap method instead of
|
||||
/// a plain `ldc` literal.
|
||||
#[test]
|
||||
fn condy_ldc_round_trip() {
|
||||
let mut pool = ConstantPool::default();
|
||||
let this = pool.add_class("demo/Condy").expect("add_class");
|
||||
let object = pool.add_class("java/lang/Object").expect("add_object");
|
||||
let owner = pool.add_class("demo/RT").expect("owner");
|
||||
let method_ref = pool
|
||||
.add_method_ref(
|
||||
owner,
|
||||
"boot",
|
||||
"(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/Class;)Ljava/lang/String;",
|
||||
)
|
||||
.expect("ref");
|
||||
let handle = pool
|
||||
.add_method_handle(ReferenceKind::InvokeStatic, method_ref)
|
||||
.expect("handle");
|
||||
let bsm_name = pool.add_utf8("BootstrapMethods").expect("attr");
|
||||
let name = pool.add_utf8("run").expect("name");
|
||||
let desc = pool.add_utf8("()Ljava/lang/String;").expect("desc");
|
||||
let code_name = pool.add_utf8("Code").expect("attr");
|
||||
let dynamic = pool
|
||||
.add_dynamic(0, "_", "Ljava/lang/String;")
|
||||
.expect("condy");
|
||||
|
||||
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: 1,
|
||||
max_locals: 0,
|
||||
code: vec![Rt::Ldc_w(dynamic), Rt::Areturn],
|
||||
exception_table: vec![],
|
||||
attributes: vec![],
|
||||
}],
|
||||
}],
|
||||
attributes: vec![RtAttr::BootstrapMethods {
|
||||
name_index: bsm_name,
|
||||
methods: vec![RtBsm {
|
||||
bootstrap_method_ref: handle,
|
||||
arguments: vec![],
|
||||
}],
|
||||
}],
|
||||
..RtClass::default()
|
||||
};
|
||||
|
||||
let ir = round_trip(&rt);
|
||||
assert_eq!(ir.attributes.len(), 1);
|
||||
assert!(format!("{:?}", ir.attributes[0]).contains("BootstrapMethods"));
|
||||
let code = ir.methods[0].code.as_ref().expect("code");
|
||||
assert!(
|
||||
format!("{:?}", code.insns).contains("Dynamic"),
|
||||
"condy ldc должен пережить round-trip как Const::Dynamic: {:?}",
|
||||
code.insns
|
||||
);
|
||||
}
|
||||
|
||||
/// Регрессия: писатель раньше эмитил `Const::Dynamic` ВСЕГДА через
|
||||
/// `ldc2_w` (категория 2 - long/double, 2 слота стека), даже когда BSM
|
||||
/// возвращает `String` (категория 1). `ldc2_w` на ссылочном типе -
|
||||
/// категория инструкции не совпадает с категорией значения, кадры стека
|
||||
/// и верификатор на реальной JVM это не пропустят. Категория должна
|
||||
/// читаться из дескриптора condy-константы (JVMS §4.4.10), не быть
|
||||
/// захардкожена.
|
||||
#[test]
|
||||
fn condy_string_writes_ldc_w_not_ldc2_w() {
|
||||
let class = ClassBuilder::new("demo/CondyOp", None)
|
||||
.version(Version::JAVA_21)
|
||||
.attribute(Attribute::BootstrapMethods(vec![BootstrapMethod {
|
||||
kind: 6, // InvokeStatic
|
||||
owner: "demo/RT".to_string(),
|
||||
name: "boot".to_string(),
|
||||
descriptor: "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/Class;)Ljava/lang/String;"
|
||||
.to_string(),
|
||||
itf: false,
|
||||
args: vec![],
|
||||
}]))
|
||||
.method_with_code(
|
||||
0x0009,
|
||||
"run",
|
||||
"()Ljava/lang/String;",
|
||||
Code {
|
||||
max_stack: 1,
|
||||
max_locals: 0,
|
||||
insns: vec![
|
||||
Insn::Const(Const::Dynamic {
|
||||
name: "_".to_string(),
|
||||
descriptor: "Ljava/lang/String;".to_string(),
|
||||
bootstrap: 0,
|
||||
}),
|
||||
Insn::AReturn,
|
||||
],
|
||||
exception_table: Vec::new(),
|
||||
frames: Vec::new(),
|
||||
attributes: Vec::new(),
|
||||
},
|
||||
)
|
||||
.build();
|
||||
|
||||
let bytes = format::write(&class).expect("write");
|
||||
let written = RtClass::from_bytes(&bytes).expect("parse back");
|
||||
let method = &written.methods[0];
|
||||
let RtAttr::Code { code, .. } = method
|
||||
.attributes
|
||||
.iter()
|
||||
.find(|a| matches!(a, RtAttr::Code { .. }))
|
||||
.expect("Code attribute")
|
||||
else {
|
||||
unreachable!()
|
||||
};
|
||||
assert!(
|
||||
matches!(code.first(), Some(Rt::Ldc_w(_))),
|
||||
"condy String должен писаться через ldc_w (категория 1), не ldc2_w: {code:?}"
|
||||
);
|
||||
assert!(
|
||||
!code.iter().any(|insn| matches!(insn, Rt::Ldc2_w(_))),
|
||||
"ldc2_w не должен появляться для condy-строки: {code:?}"
|
||||
);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue