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.
This commit is contained in:
parent
a2e4c69210
commit
f08bf60635
10 changed files with 509 additions and 206 deletions
|
|
@ -117,3 +117,40 @@ pub(super) const DECOY_RIGHT: &[&str] = &[
|
|||
"mirror", "lantern", "harbor", "ember", "shadow", "quarry", "meadow", "cipher", "anchor",
|
||||
"ripple",
|
||||
];
|
||||
|
||||
/// Стиль `Plausible`: домен-нейтральные существительные для классов/полей —
|
||||
/// похожи на обычный прикладной код, не привязаны к конкретному проекту.
|
||||
pub(super) const PLAUSIBLE_NOUN: &[&str] = &[
|
||||
"Texture", "Event", "State", "Render", "Asset", "Config", "Session", "Cache", "Buffer",
|
||||
"Socket", "Stream", "Widget", "Layout", "Theme", "Profile", "Metric", "Token", "Handle",
|
||||
"Context", "Registry", "Request", "Response", "Payload", "Resource", "Snapshot", "Pipeline",
|
||||
"Queue", "Slot", "Frame", "Entry",
|
||||
];
|
||||
/// Суффиксы ролей для классов (`NounSuffix`, напр. `TextureCache`).
|
||||
pub(super) const PLAUSIBLE_SUFFIX: &[&str] = &[
|
||||
"Manager",
|
||||
"Handler",
|
||||
"Service",
|
||||
"Provider",
|
||||
"Factory",
|
||||
"Builder",
|
||||
"Cache",
|
||||
"Store",
|
||||
"Loader",
|
||||
"Parser",
|
||||
"Worker",
|
||||
"Controller",
|
||||
"Adapter",
|
||||
"Wrapper",
|
||||
"Registry",
|
||||
"Resolver",
|
||||
"Validator",
|
||||
"Processor",
|
||||
"Context",
|
||||
"Listener",
|
||||
];
|
||||
/// Глаголы для методов/полей (`verbNoun`, напр. `resolveToken`).
|
||||
pub(super) const PLAUSIBLE_VERB: &[&str] = &[
|
||||
"get", "set", "update", "process", "handle", "compute", "resolve", "load", "build", "parse",
|
||||
"fetch", "apply", "validate", "clear", "reset", "sync", "flush", "bind", "attach", "detach",
|
||||
];
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
//! Генерация имён: стили, анти-ключевые слова, уникальность в scope.
|
||||
|
||||
pub mod alphabet;
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
use std::collections::HashSet;
|
||||
|
||||
|
|
@ -8,7 +10,7 @@ use noxis_core::{NameStyle, Rng};
|
|||
|
||||
use self::alphabet::{
|
||||
AVOID_CLASS, AVOID_MEMBER, CONFUSABLE, DECOY_LEFT, DECOY_RIGHT, KEYWORDS, LATIN_LOWER,
|
||||
LATIN_UPPER, UNICODE_LETTERS,
|
||||
LATIN_UPPER, PLAUSIBLE_NOUN, PLAUSIBLE_SUFFIX, PLAUSIBLE_VERB, UNICODE_LETTERS,
|
||||
};
|
||||
|
||||
/// Генератор имён с гарантированной уникальностью в пределах одного scope.
|
||||
|
|
@ -68,7 +70,7 @@ impl NameGenerator {
|
|||
match self.style {
|
||||
NameStyle::ShortLatin | NameStyle::IlO0 | NameStyle::MixedLatin => "z",
|
||||
NameStyle::Unicode => "\u{043e}",
|
||||
NameStyle::DecoyWords => "tmp",
|
||||
NameStyle::DecoyWords | NameStyle::Plausible => "tmp",
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -121,6 +123,20 @@ impl NameGenerator {
|
|||
}
|
||||
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}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -164,52 +180,3 @@ fn capitalize(s: &str) -> String {
|
|||
None => String::new(),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#![allow(clippy::expect_used, clippy::unwrap_used)]
|
||||
use super::alphabet::{AVOID_MEMBER, KEYWORDS};
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn names_are_unique_and_valid() {
|
||||
let mut names = NameGenerator::new(Rng::from_seed(1), NameStyle::ShortLatin);
|
||||
let mut seen = HashSet::new();
|
||||
for _ in 0..500 {
|
||||
let name = names.next_name();
|
||||
assert!(seen.insert(name.clone()), "повтор имени {name}");
|
||||
assert!(!KEYWORDS.contains(&name.as_str()));
|
||||
assert!(name.starts_with(|c: char| c.is_ascii_alphabetic()));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn avoids_reserved_member_names() {
|
||||
let mut names = NameGenerator::new(Rng::from_seed(2), NameStyle::DecoyWords);
|
||||
for _ in 0..500 {
|
||||
let name = names.next_name();
|
||||
assert!(!AVOID_MEMBER.contains(&name.as_str()));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unicode_style_produces_identifier_valid_names() {
|
||||
let mut names = NameGenerator::new(Rng::from_seed(3), NameStyle::Unicode);
|
||||
for _ in 0..100 {
|
||||
let name = names.next_name();
|
||||
assert!(
|
||||
name.chars()
|
||||
.all(|c| c.is_alphanumeric() || c == '_' || c == '$')
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reserved_names_never_reissued() {
|
||||
let mut names = NameGenerator::new(Rng::from_seed(4), NameStyle::ShortLatin);
|
||||
names.reserve("abc");
|
||||
for _ in 0..300 {
|
||||
assert_ne!(names.next_name(), "abc");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
76
crates/noxis-passes/src/namegen/tests.rs
Normal file
76
crates/noxis-passes/src/namegen/tests.rs
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
//! Тесты генератора имён: уникальность, валидность, форма по стилю.
|
||||
|
||||
#![allow(clippy::expect_used, clippy::unwrap_used)]
|
||||
|
||||
use std::collections::HashSet;
|
||||
|
||||
use noxis_core::{NameStyle, Rng};
|
||||
|
||||
use super::NameGenerator;
|
||||
use super::alphabet::{AVOID_MEMBER, KEYWORDS};
|
||||
|
||||
#[test]
|
||||
fn names_are_unique_and_valid() {
|
||||
let mut names = NameGenerator::new(Rng::from_seed(1), NameStyle::ShortLatin);
|
||||
let mut seen = HashSet::new();
|
||||
for _ in 0..500 {
|
||||
let name = names.next_name();
|
||||
assert!(seen.insert(name.clone()), "повтор имени {name}");
|
||||
assert!(!KEYWORDS.contains(&name.as_str()));
|
||||
assert!(name.starts_with(|c: char| c.is_ascii_alphabetic()));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn avoids_reserved_member_names() {
|
||||
let mut names = NameGenerator::new(Rng::from_seed(2), NameStyle::DecoyWords);
|
||||
for _ in 0..500 {
|
||||
let name = names.next_name();
|
||||
assert!(!AVOID_MEMBER.contains(&name.as_str()));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unicode_style_produces_identifier_valid_names() {
|
||||
let mut names = NameGenerator::new(Rng::from_seed(3), NameStyle::Unicode);
|
||||
for _ in 0..100 {
|
||||
let name = names.next_name();
|
||||
assert!(
|
||||
name.chars()
|
||||
.all(|c| c.is_alphanumeric() || c == '_' || c == '$')
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reserved_names_never_reissued() {
|
||||
let mut names = NameGenerator::new(Rng::from_seed(4), NameStyle::ShortLatin);
|
||||
names.reserve("abc");
|
||||
for _ in 0..300 {
|
||||
assert_ne!(names.next_name(), "abc");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn plausible_class_names_look_like_noun_suffix() {
|
||||
let mut names = NameGenerator::new(Rng::from_seed(5), NameStyle::Plausible).for_classes();
|
||||
for _ in 0..50 {
|
||||
let name = names.next_name();
|
||||
assert!(name.starts_with(|c: char| c.is_ascii_uppercase()), "{name}");
|
||||
// Два слова подряд слиплись в CamelCase без разделителей.
|
||||
assert!(
|
||||
name.chars().filter(|c| c.is_ascii_uppercase()).count() >= 2,
|
||||
"{name}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn plausible_member_names_look_like_verb_noun() {
|
||||
let mut names = NameGenerator::new(Rng::from_seed(6), NameStyle::Plausible);
|
||||
for _ in 0..50 {
|
||||
let name = names.next_name();
|
||||
assert!(name.starts_with(|c: char| c.is_ascii_lowercase()), "{name}");
|
||||
assert!(!KEYWORDS.contains(&name.as_str()));
|
||||
}
|
||||
}
|
||||
|
|
@ -180,6 +180,7 @@ pub(super) fn generate_packages(cfg: &Config, rng: &mut Rng) -> Vec<String> {
|
|||
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();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue