obfuskator/crates/noxis-passes/src/rename/assign.rs
loki5512344 f08bf60635 classfile: intern constant-pool entries on write (fix LVTT/LVT ClassFormatError)
ristretto's ConstantPool never deduplicates: every add_* pushes a new entry.
HotSpot matches each LocalVariableTypeTable entry to a LocalVariableTable
entry by raw name_index, so when a name (e.g. a local's) was interned twice
the two tables got different indices and the class failed to load:
'ClassFormatError: LVTT entry ... does not match any LVT entry'.

The writer now memoizes every pool builder by its semantic key and rebuilds
composite constants (Class/NameAndType/Fieldref/Methodref/MethodHandle/
Dynamic) from our own utf8/class primitives so ristretto never mints a
shadow Utf8. This also shrinks the pool (no more ldc -> ldc_w widening that
changed code length on a pure round-trip).

Split pool.rs into a pool/ module (refs in pool/refs.rs) to respect the
file-size gate. Regression test: tests/pool asserts the written pool has no
duplicate CONSTANT_Utf8. Verified: pure parse->write of the neoforge jar
(92 classes) now loads under java -Xverify:all with 0 errors.
2026-10-08 22:55:49 +02:00

238 lines
8.6 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//! Назначение новых имён: карты, семейства виртуальных методов,
//! пул пакетов и финальное ширинирование доступа.
use std::collections::{BTreeMap, BTreeSet};
use noxis_analysis::{Analysis, ClassHierarchy};
use noxis_classfile::Program;
use noxis_core::{Config, Context, NameStyle, Rng};
use super::{ACC_PRIVATE, ACC_PROTECTED, ACC_PUBLIC, ACC_STATIC};
use crate::namegen::NameGenerator;
/// `(owner, name, descriptor)` — как в JVM-ссылке.
pub(super) type MemberKey = (String, String, String);
/// Карты переименований одного прогона: `declared` — где объявлено,
/// `alias` — наследники, которые наследуют это имя без своего объявления.
#[derive(Debug, Default)]
pub(super) struct RenameMaps {
pub(super) classes: BTreeMap<String, String>,
pub(super) methods: BTreeMap<MemberKey, String>,
pub(super) methods_alias: BTreeMap<MemberKey, String>,
pub(super) fields: BTreeMap<MemberKey, String>,
pub(super) fields_alias: BTreeMap<MemberKey, String>,
}
impl RenameMaps {
pub(super) fn method(&self, owner: &str, name: &str, desc: &str) -> Option<String> {
let key = (owner.to_string(), name.to_string(), desc.to_string());
self.methods
.get(&key)
.or_else(|| self.methods_alias.get(&key))
.cloned()
}
pub(super) fn field(&self, owner: &str, name: &str, desc: &str) -> Option<String> {
let key = (owner.to_string(), name.to_string(), desc.to_string());
self.fields
.get(&key)
.or_else(|| self.fields_alias.get(&key))
.cloned()
}
/// Псевдоними: наследник вызывает член, объявленный в предке.
pub(super) fn expand(&mut self, hierarchy: &ClassHierarchy, owners: &[String]) {
let methods: Vec<(MemberKey, String)> = self
.methods
.iter()
.map(|(k, v)| (k.clone(), v.clone()))
.collect();
for ((owner, name, desc), new_name) in &methods {
for child in owners {
if child == owner || !hierarchy.is_subclass_of(child, owner) {
continue;
}
let key = (child.clone(), name.clone(), desc.clone());
if self.methods.contains_key(&key) {
continue;
}
self.methods_alias
.entry(key)
.or_insert_with(|| new_name.clone());
}
}
let fields: Vec<(MemberKey, String)> = self
.fields
.iter()
.map(|(k, v)| (k.clone(), v.clone()))
.collect();
for ((owner, name, desc), new_name) in &fields {
for child in owners {
if child == owner || !hierarchy.is_subclass_of(child, owner) {
continue;
}
let key = (child.clone(), name.clone(), desc.clone());
if self.fields.contains_key(&key) {
continue;
}
self.fields_alias
.entry(key)
.or_insert_with(|| new_name.clone());
}
}
}
}
/// Переименование виртуальных методов «семейством»: одно имя на всю иерархию,
/// включая мостовые методы с ковариантным возвратом.
pub(super) fn rename_virtual_families(
program: &Program,
analysis: &Analysis,
maps: &mut RenameMaps,
cfg: &Config,
ctx: &mut Context,
) -> usize {
// Семейство = (имя, параметры): возврат игнорируем — так захватываем
// ковариантные возвраты и мостовые методы (`ACC_BRIDGE`) разом.
let mut families: BTreeMap<(String, String), Vec<MemberKey>> = BTreeMap::new();
let mut locked_families: BTreeSet<(String, String)> = BTreeSet::new();
for class in &program.classes {
let locked_class = analysis.is_locked(&class.this_class);
for method in &class.methods {
if method.name == "<init>" || method.name == "<clinit>" {
continue;
}
if method.access_flags & (ACC_STATIC | ACC_PRIVATE) != 0 {
continue;
}
let Some(params) = params_of(&method.descriptor) else {
continue;
};
let family = (method.name.clone(), params);
if locked_class || analysis.keep.keeps_member(&method.name) {
locked_families.insert(family.clone());
}
families.entry(family).or_default().push((
class.this_class.clone(),
method.name.clone(),
method.descriptor.clone(),
));
}
}
let mut renamed = 0usize;
let mut family_index = 0u64;
for (family_key, members) in families {
if locked_families.contains(&family_key) {
continue;
}
let mut names = NameGenerator::new(
ctx.rng_nth("rename/virtual-families", family_index),
cfg.rename.member_style,
);
family_index += 1;
for (_, name, _) in &members {
names.reserve(name.clone());
}
let new_name = names.next_name();
for key in members {
if maps.methods.contains_key(&key) {
continue;
}
maps.methods.insert(key, new_name.clone());
renamed += 1;
}
}
renamed
}
/// `(<params>)<ret>` → `(<params>)`, либо `None`, если дескриптор кривой.
fn params_of(descriptor: &str) -> Option<String> {
let close = descriptor.find(')')?;
if !descriptor.starts_with('(') {
return None;
}
Some(descriptor[..=close].to_string())
}
pub(super) fn parent_package(internal: &str) -> String {
match internal.rfind('/') {
Some(i) => internal[..i].to_string(),
None => String::new(),
}
}
pub(super) fn simple_name(internal: &str) -> String {
match internal.rfind('/') {
Some(i) => internal[i + 1..].to_string(),
None => internal.to_string(),
}
}
/// Пул случайных пакетов (2–3 сегмента); при выключенном разбросе — один пустой.
pub(super) fn generate_packages(cfg: &Config, rng: &mut Rng) -> Vec<String> {
if !cfg.rename.spread_packages {
return vec![String::new()];
}
let style = match cfg.rename.class_style {
NameStyle::Unicode => NameStyle::Unicode,
NameStyle::IlO0 => NameStyle::IlO0,
NameStyle::Plausible => NameStyle::Plausible,
_ => NameStyle::MixedLatin,
};
let mut names = NameGenerator::new(rng.clone(), style).for_classes();
let pool = cfg.rename.package_pool.max(1);
(0..pool)
.map(|_| {
let segments = 2 + rng.below(2) as usize;
let parts: Vec<String> = (0..segments).map(|_| names.next_name()).collect();
parts.join("/")
})
.collect()
}
/// Переписать дескриптор/сигнатуру: `Lpkg/Class;` → новое имя класса.
pub(super) fn rewrite_descriptor(desc: &str, classes: &BTreeMap<String, String>) -> String {
if !desc.contains('L') {
return desc.to_string();
}
let mut out = String::with_capacity(desc.len());
let mut chars = desc.chars().peekable();
while let Some(c) = chars.next() {
if c != 'L' {
out.push(c);
continue;
}
let mut name = String::new();
for c in chars.by_ref() {
if c == ';' {
break;
}
name.push(c);
}
out.push('L');
out.push_str(classes.get(&name).map_or(name.as_str(), String::as_str));
out.push(';');
}
out
}
/// Расширить доступ до `public`, чтобы переезд в другой пакет не ломал вызовы.
pub(super) fn widen_all(program: &mut Program) {
for class in program.classes.iter_mut() {
class.access_flags = widen(class.access_flags);
for field in class.fields.iter_mut() {
field.access_flags = widen(field.access_flags);
}
for method in class.methods.iter_mut() {
method.access_flags = widen(method.access_flags);
}
}
}
fn widen(flags: u16) -> u16 {
(flags & !(ACC_PRIVATE | ACC_PROTECTED)) | ACC_PUBLIC
}