obfuskator/crates/noxis-passes/src/namegen/mod.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

182 lines
6.5 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.

//! Генерация имён: стили, анти-ключевые слова, уникальность в scope.
pub mod alphabet;
#[cfg(test)]
mod tests;
use std::collections::HashSet;
use noxis_core::{NameStyle, Rng};
use self::alphabet::{
AVOID_CLASS, AVOID_MEMBER, CONFUSABLE, DECOY_LEFT, DECOY_RIGHT, KEYWORDS, LATIN_LOWER,
LATIN_UPPER, PLAUSIBLE_NOUN, PLAUSIBLE_SUFFIX, PLAUSIBLE_VERB, UNICODE_LETTERS,
};
/// Генератор имён с гарантированной уникальностью в пределах одного scope.
pub struct NameGenerator {
rng: Rng,
style: NameStyle,
used: HashSet<String>,
/// Счётчик-страховка, если случайные варианты исчерпаны.
fallback: u64,
avoid_class: bool,
}
impl NameGenerator {
/// Генератор для указанного стиля.
pub fn new(rng: Rng, style: NameStyle) -> Self {
Self {
rng,
style,
used: HashSet::new(),
fallback: 0,
avoid_class: false,
}
}
/// Режим «имена классов»: дополнительно избегаем имён из JDK/аннотаций.
pub fn for_classes(mut self) -> Self {
self.avoid_class = true;
self
}
/// Занять конкретное имя (защита от коллизий с keep-именами).
pub fn reserve(&mut self, name: impl Into<String>) {
self.used.insert(name.into());
}
/// Следующее свободное имя.
pub fn next_name(&mut self) -> String {
for _ in 0..64 {
let candidate = self.candidate();
if self.acceptable(&candidate) {
self.used.insert(candidate.clone());
return candidate;
}
}
// Страховка: детерминированный счётчик, гарантированно уникален.
loop {
self.fallback += 1;
let candidate = format!("{}{}", self.style_prefix(), self.fallback);
if self.acceptable(&candidate) {
self.used.insert(candidate.clone());
return candidate;
}
}
}
fn style_prefix(&self) -> &'static str {
match self.style {
NameStyle::ShortLatin | NameStyle::IlO0 | NameStyle::MixedLatin => "z",
NameStyle::Unicode => "\u{043e}",
NameStyle::DecoyWords | NameStyle::Plausible => "tmp",
}
}
fn candidate(&mut self) -> String {
match self.style {
NameStyle::ShortLatin => {
let len = 1 + self.rng.below(3) as usize;
random_from(LATIN_LOWER, len, &mut self.rng)
}
NameStyle::IlO0 => {
let len = 3 + self.rng.below(5) as usize;
random_from(CONFUSABLE, len, &mut self.rng)
}
NameStyle::MixedLatin => {
let len = 6 + self.rng.below(7) as usize;
let mut out = String::with_capacity(len);
for i in 0..len {
let alpha = if self.rng.below(2) == 0 {
LATIN_LOWER
} else {
LATIN_UPPER
};
let idx = self.rng.below(alpha.len() as u64) as usize;
let ch = alpha[idx] as char;
// Первый символ — только буква (не цифра).
if i == 0 && !ch.is_ascii_alphabetic() {
out.push('a');
} else {
out.push(ch);
}
}
out
}
NameStyle::Unicode => {
let len = 4 + self.rng.below(6) as usize;
(0..len)
.map(|_| {
let idx = self.rng.below(UNICODE_LETTERS.len() as u64) as usize;
UNICODE_LETTERS[idx]
})
.collect()
}
NameStyle::DecoyWords => {
let l = DECOY_LEFT[self.rng.below(DECOY_LEFT.len() as u64) as usize];
let r = DECOY_RIGHT[self.rng.below(DECOY_RIGHT.len() as u64) as usize];
let sep = if self.rng.below(2) == 0 { "_" } else { "" };
let mut name = format!("{l}{sep}{r}");
if self.rng.below(2) == 0 {
name = capitalize(&name);
}
name
}
// Маскируется под обычный прикладной код, а не под мусор:
// классы — `NounSuffix` (`TextureCache`), члены — `verbNoun`
// (`resolveToken`) camelCase.
NameStyle::Plausible => {
let noun = PLAUSIBLE_NOUN[self.rng.below(PLAUSIBLE_NOUN.len() as u64) as usize];
if self.avoid_class {
let suffix =
PLAUSIBLE_SUFFIX[self.rng.below(PLAUSIBLE_SUFFIX.len() as u64) as usize];
format!("{noun}{suffix}")
} else {
let verb = PLAUSIBLE_VERB[self.rng.below(PLAUSIBLE_VERB.len() as u64) as usize];
format!("{verb}{noun}")
}
}
}
}
fn acceptable(&self, name: &str) -> bool {
if name.is_empty() || self.used.contains(name) {
return false;
}
if KEYWORDS.contains(&name) {
return false;
}
if self.avoid_class && AVOID_CLASS.contains(&name) {
return false;
}
if !self.avoid_class && AVOID_MEMBER.contains(&name) {
return false;
}
let Some(first) = name.chars().next() else {
return false;
};
if !first.is_alphabetic() && first != '_' && first != '$' {
return false;
}
name.chars()
.all(|c| c.is_alphanumeric() || c == '_' || c == '$')
}
}
fn random_from(alphabet: &[u8], len: usize, rng: &mut Rng) -> String {
(0..len)
.map(|_| {
let idx = rng.below(alphabet.len() as u64) as usize;
alphabet[idx] as char
})
.collect()
}
fn capitalize(s: &str) -> String {
let mut chars = s.chars();
match chars.next() {
Some(c) => c.to_uppercase().collect::<String>() + chars.as_str(),
None => String::new(),
}
}