feat(parser): Phase 285A1.4 & A1.5 - Weak field sugar + Parser hang fix

A1.4: Add sugar syntax `public weak parent` ≡ `public { weak parent }`
A1.5: Fix parser hang on unsupported `param: Type` syntax

Key changes:
- A1.4: Extend visibility parser to handle weak modifier (fields.rs)
- A1.5: Shared helper `parse_param_name_list()` with progress-zero detection
- A1.5: Fix 6 vulnerable parameter parsing loops (methods, constructors, functions)
- Tests: Sugar syntax (OK/NG), parser hang (timeout-based)
- Docs: lifecycle.md, EBNF.md, phase-285a1-boxification.md

Additional changes:
- weak() builtin implementation (handlers/weak.rs)
- Leak tracking improvements (leak_tracker.rs)
- Documentation updates (lifecycle, types, memory-finalization, etc.)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
2025-12-24 07:44:50 +09:00
parent a47f850d02
commit ab76e39036
60 changed files with 2099 additions and 454 deletions

View File

@ -511,6 +511,14 @@ pub extern "C" fn nyrt_host_call_slot(
crate::backend::vm::VMValue::Void => {
Box::new(crate::box_trait::VoidBox::new())
}
// Phase 285A0: WeakBox upgrade or void
crate::backend::vm::VMValue::WeakBox(w) => {
if let Some(arc) = w.upgrade() {
arc.share_box()
} else {
Box::new(crate::box_trait::VoidBox::new())
}
}
};
let out = map.has(key_box);
let vmv = crate::backend::vm::VMValue::from_nyash_box(out);
@ -539,6 +547,14 @@ pub extern "C" fn nyrt_host_call_slot(
crate::backend::vm::VMValue::Void => {
Box::new(crate::box_trait::VoidBox::new())
}
// Phase 285A0: WeakBox upgrade or void
crate::backend::vm::VMValue::WeakBox(w) => {
if let Some(arc) = w.upgrade() {
arc.share_box()
} else {
Box::new(crate::box_trait::VoidBox::new())
}
}
};
let out = map.get(key_box);
let vmv = crate::backend::vm::VMValue::from_nyash_box(out);
@ -567,6 +583,14 @@ pub extern "C" fn nyrt_host_call_slot(
crate::backend::vm::VMValue::Void => {
Box::new(crate::box_trait::VoidBox::new())
}
// Phase 285A0: WeakBox upgrade or void
crate::backend::vm::VMValue::WeakBox(w) => {
if let Some(arc) = w.upgrade() {
arc.share_box()
} else {
Box::new(crate::box_trait::VoidBox::new())
}
}
};
let val_box: Box<dyn NyashBox> = match argv[1].clone() {
crate::backend::vm::VMValue::Integer(i) => {
@ -586,6 +610,14 @@ pub extern "C" fn nyrt_host_call_slot(
crate::backend::vm::VMValue::Void => {
Box::new(crate::box_trait::VoidBox::new())
}
// Phase 285A0: WeakBox upgrade or void
crate::backend::vm::VMValue::WeakBox(w) => {
if let Some(arc) = w.upgrade() {
arc.share_box()
} else {
Box::new(crate::box_trait::VoidBox::new())
}
}
};
let out = map.set(key_box, val_box);
let vmv = crate::backend::vm::VMValue::from_nyash_box(out);

View File

@ -1,10 +1,41 @@
//! Leak Tracker - Exit-time diagnostics for strong references still held
//!
//! Phase 285: Extended to report all global roots (modules, host_handles, plugin boxes).
//!
//! ## Environment Variable
//!
//! - `NYASH_LEAK_LOG=1` - Summary counts only
//! - `NYASH_LEAK_LOG=2` - Verbose (include names/entries, truncated to first 10)
//!
//! ## Output Format
//!
//! ```text
//! [lifecycle/leak] Roots still held at exit:
//! [lifecycle/leak] modules: 3
//! [lifecycle/leak] host_handles: 5
//! [lifecycle/leak] plugin_boxes: 2
//! ```
use crate::runtime::get_global_ring0;
use once_cell::sync::Lazy;
use std::collections::HashMap;
use std::sync::Mutex;
static ENABLED: Lazy<bool> =
Lazy::new(|| std::env::var("NYASH_LEAK_LOG").unwrap_or_default() == "1");
/// Leak log level: 0 = off, 1 = summary, 2 = verbose
static LEVEL: Lazy<u8> = Lazy::new(|| {
match std::env::var("NYASH_LEAK_LOG")
.unwrap_or_default()
.as_str()
{
"1" => 1,
"2" => 2,
_ => 0,
}
});
/// Backward compatibility: enabled if level >= 1
static ENABLED: Lazy<bool> = Lazy::new(|| *LEVEL >= 1);
static LEAKS: Lazy<Mutex<HashMap<(String, u32), &'static str>>> =
Lazy::new(|| Mutex::new(HashMap::new()));
@ -38,21 +69,97 @@ impl Drop for Reporter {
if !*ENABLED {
return;
}
let m = LEAKS.lock().unwrap();
if m.is_empty() {
return;
}
get_global_ring0().log.warn(&format!(
"[leak] Detected {} non-finalized plugin boxes:",
m.len()
));
for ((ty, id), _) in m.iter() {
get_global_ring0().log.warn(&format!(
" - {}(id={}) not finalized (missing fini or scope)",
ty, id
));
}
emit_leak_report();
}
}
static REPORTER: Lazy<Reporter> = Lazy::new(|| Reporter);
/// Emit exit-time leak report (Phase 285)
///
/// Called automatically on program exit via Reporter::drop.
/// Can also be called manually for testing.
pub fn emit_leak_report() {
let level = *LEVEL;
if level == 0 {
return;
}
let ring0 = get_global_ring0();
// Collect root counts
let modules = crate::runtime::modules_registry::snapshot_names_and_strings();
let host_handles = crate::runtime::host_handles::snapshot();
let plugin_boxes = LEAKS.lock().map(|m| m.len()).unwrap_or(0);
let modules_count = modules.len();
let host_handles_count = host_handles.len();
// Only print if there's something to report
if modules_count == 0 && host_handles_count == 0 && plugin_boxes == 0 {
return;
}
// Summary header
ring0
.log
.warn("[lifecycle/leak] Roots still held at exit:");
// Summary counts
if modules_count > 0 {
ring0
.log
.warn(&format!("[lifecycle/leak] modules: {}", modules_count));
}
if host_handles_count > 0 {
ring0.log.warn(&format!(
"[lifecycle/leak] host_handles: {}",
host_handles_count
));
}
if plugin_boxes > 0 {
ring0
.log
.warn(&format!("[lifecycle/leak] plugin_boxes: {}", plugin_boxes));
}
// Verbose details (level 2)
if level >= 2 {
const MAX_ENTRIES: usize = 10;
// Module names
if !modules.is_empty() {
ring0.log.warn("[lifecycle/leak] module names:");
for (i, (name, _value)) in modules.iter().take(MAX_ENTRIES).enumerate() {
ring0
.log
.warn(&format!("[lifecycle/leak] [{}] {}", i, name));
}
if modules.len() > MAX_ENTRIES {
ring0.log.warn(&format!(
"[lifecycle/leak] ... and {} more",
modules.len() - MAX_ENTRIES
));
}
}
// Plugin box details
if plugin_boxes > 0 {
ring0.log.warn("[lifecycle/leak] plugin box details:");
if let Ok(m) = LEAKS.lock() {
for (i, ((ty, id), _)) in m.iter().take(MAX_ENTRIES).enumerate() {
ring0.log.warn(&format!(
"[lifecycle/leak] [{}] {}(id={}) not finalized",
i, ty, id
));
}
if m.len() > MAX_ENTRIES {
ring0.log.warn(&format!(
"[lifecycle/leak] ... and {} more",
m.len() - MAX_ENTRIES
));
}
}
}
}
}