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

@ -38,6 +38,8 @@ pub fn to_bool_vm(v: &VMValue) -> Result<bool, String> {
Err(format!("cannot coerce BoxRef({}) to bool", b.type_name()))
}
VMValue::Future(_) => Err("cannot coerce Future to bool".to_string()),
// Phase 285A0: WeakRef in boolean context is TypeError
VMValue::WeakBox(_) => Err("WeakRef in boolean context - use upgrade() first".to_string()),
}
}
@ -80,6 +82,16 @@ pub fn eq_vm(a: &VMValue, b: &VMValue) -> bool {
.downcast_ref::<crate::boxes::missing_box::MissingBox>()
.is_some()
}
// Phase 285A0: WeakBox equality
(WeakBox(wa), WeakBox(wb)) => {
match (wa.upgrade(), wb.upgrade()) {
(Some(arc_a), Some(arc_b)) => Arc::ptr_eq(&arc_a, &arc_b),
(None, None) => true, // Both dropped
_ => false,
}
}
// WeakBox == Void when dropped
(WeakBox(w), Void) | (Void, WeakBox(w)) => w.upgrade().is_none(),
_ => false,
}
}
@ -94,6 +106,7 @@ pub fn tag_of_vm(v: &VMValue) -> &'static str {
VMValue::Future(_) => "Future",
VMValue::Void => "Void",
VMValue::BoxRef(_) => "BoxRef",
VMValue::WeakBox(_) => "WeakRef", // Phase 285A0
}
}

View File

@ -173,6 +173,7 @@ impl MirInterpreter {
VMValue::Bool(_) => "BoolBox".to_string(),
VMValue::Void => "<Void>".to_string(),
VMValue::Future(_) => "<Future>".to_string(),
VMValue::WeakBox(_) => "<WeakRef>".to_string(), // Phase 285A0
};
self.box_trace_emit_call(&cls, method, args.len());
}
@ -190,6 +191,7 @@ impl MirInterpreter {
VMValue::String(_) => "String".to_string(),
VMValue::Void => "Void".to_string(),
VMValue::Future(_) => "Future".to_string(),
VMValue::WeakBox(_) => "WeakRef".to_string(), // Phase 285A0
};
eprintln!("[vm-trace] length dispatch recv_type={}", type_name);
}

View File

@ -19,6 +19,7 @@ pub(super) fn try_handle_object_fields(
VV::Void => NV::Void,
VV::Future(_) => NV::Void, // not expected in fields
VV::BoxRef(_) => NV::Void, // store minimal; complex object fields are not required here
VV::WeakBox(_) => NV::Void, // Phase 285A0: WeakBox not expected in this context
}
}
fn nv_to_vm(v: &crate::value::NyashValue) -> VMValue {
@ -94,6 +95,7 @@ pub(super) fn try_handle_object_fields(
Ok(VMValue::String(_)) => "String".to_string(),
Ok(VMValue::Void) => "Void".to_string(),
Ok(VMValue::Future(_)) => "Future".to_string(),
Ok(VMValue::WeakBox(_)) => "WeakRef".to_string(), // Phase 285A0
Err(_) => "<err>".to_string(),
};
eprintln!("[vm-trace] getField recv_kind={}", rk);
@ -229,6 +231,7 @@ pub(super) fn try_handle_object_fields(
VMValue::BoxRef(_) => "BoxRef",
VMValue::Void => "Void",
VMValue::Future(_) => "Future",
VMValue::WeakBox(_) => "WeakRef", // Phase 285A0
};
this.box_trace_emit_get(&inst.class_name, &fname, kind);
}
@ -339,6 +342,7 @@ pub(super) fn try_handle_object_fields(
VMValue::BoxRef(b) => b.type_name(),
VMValue::Void => "Void",
VMValue::Future(_) => "Future",
VMValue::WeakBox(_) => "WeakRef", // Phase 285A0
};
// class name unknown here; use receiver type name if possible
let cls = match this.reg_load(actual_box_val).unwrap_or(VMValue::Void) {
@ -438,6 +442,7 @@ pub(super) fn try_handle_object_fields(
VMValue::BoxRef(b) => b.type_name(),
VMValue::Void => "Void",
VMValue::Future(_) => "Future",
VMValue::WeakBox(_) => "WeakRef", // Phase 285A0
};
let cls = match this.reg_load(actual_box_val).unwrap_or(VMValue::Void) {
VMValue::BoxRef(b) => {

View File

@ -458,6 +458,7 @@ impl MirInterpreter {
VMValue::Void => "Void",
VMValue::Future(_) => "Future",
VMValue::BoxRef(bx) => bx.type_name(),
VMValue::WeakBox(_) => "WeakRef", // Phase 285A0
};
// 2. Lookup type in TypeRegistry and get slot

View File

@ -24,6 +24,7 @@ mod externals;
mod memory;
mod misc;
mod type_ops;
mod weak; // Phase 285A0: WeakRef handlers
impl MirInterpreter {
pub(super) fn execute_instruction(&mut self, inst: &MirInstruction) -> Result<(), VMError> {
@ -118,6 +119,17 @@ impl MirInterpreter {
};
self.regs.insert(*dst, selected_val);
}
// Phase 285A0: WeakRef handlers (delegated to weak.rs)
MirInstruction::WeakNew { dst, box_val } => {
self.handle_weak_new(*dst, *box_val)?;
}
MirInstruction::WeakLoad { dst, weak_ref } => {
self.handle_weak_load(*dst, *weak_ref)?;
}
MirInstruction::WeakRef { dst, op, value } => match op {
WeakRefOp::New => self.handle_weak_new(*dst, *value)?,
WeakRefOp::Load => self.handle_weak_load(*dst, *value)?,
}
MirInstruction::BarrierRead { .. }
| MirInstruction::BarrierWrite { .. }
| MirInstruction::Barrier { .. }

View File

@ -0,0 +1,59 @@
//! Phase 285A0: WeakRef handlers - 弱参照の作成とアップグレード
//!
//! SSOT: docs/reference/language/lifecycle.md:179
//!
//! WeakRef は強参照サイクルを避けるための非所有参照です。
//! - `weak(x)` → WeakNew: BoxRef から WeakRef を作成
//! - `w.weak_to_strong()` → WeakLoad: WeakRef から BoxRef へアップグレード(失敗時は null/Void
use super::*;
impl MirInterpreter {
/// WeakNew: BoxRef → WeakRef 変換
///
/// # Arguments
/// * `dst` - 結果を格納する ValueId
/// * `box_val` - 変換元の Box ValueId
///
/// # Returns
/// * `Result<(), VMError>` - 成功時は Ok、失敗時は Err
///
/// # Errors
/// * `box_val` が BoxRef でない場合はエラー
pub(super) fn handle_weak_new(
&mut self,
dst: ValueId,
box_val: ValueId,
) -> Result<(), VMError> {
let box_value = self.reg_load(box_val)?;
let weak_value = box_value
.downgrade_to_weak()
.ok_or_else(|| self.err_invalid("WeakNew: target is not a Box"))?;
self.regs.insert(dst, weak_value);
Ok(())
}
/// WeakLoad: WeakRef → BoxRef | null (= Void) アップグレード
///
/// # Arguments
/// * `dst` - 結果を格納する ValueId
/// * `weak_ref` - WeakRef ValueId
///
/// # Returns
/// * `Result<(), VMError>` - 成功時は Ok、失敗時は Err
///
/// # Note
/// - SSOT: upgrade failure returns null (= Void in VM) - lifecycle.md:179
/// - ターゲットが既に drop された場合や Dead 状態の場合は Void を返す
pub(super) fn handle_weak_load(
&mut self,
dst: ValueId,
weak_ref: ValueId,
) -> Result<(), VMError> {
let weak_value = self.reg_load(weak_ref)?;
// upgrade_weak() が None を返した場合は Voidnull
let result = weak_value.upgrade_weak().unwrap_or(VMValue::Void);
self.regs.insert(dst, result);
Ok(())
}
}

View File

@ -436,6 +436,7 @@ impl MirInterpreter {
("BoxRef", b.type_name().to_string(), tag)
}
}
VMValue::WeakBox(_) => ("WeakRef", "".to_string(), None), // Phase 285A0
};
if let Some(tag) = nullish {
eprintln!(

View File

@ -14,7 +14,7 @@ pub(super) use crate::backend::abi_util::{eq_vm, to_bool_vm};
pub(super) use crate::backend::vm::{VMError, VMValue};
pub(super) use crate::mir::{
BasicBlockId, BinaryOp, Callee, CompareOp, ConstValue, MirFunction, MirInstruction, MirModule,
MirType, TypeOpKind, ValueId,
MirType, TypeOpKind, ValueId, WeakRefOp,
};
mod exec;

View File

@ -61,6 +61,7 @@ impl MirInterpreter {
))
}
VMValue::Future(_) => "Future",
VMValue::WeakBox(_) => "WeakRef", // Phase 285A0
};
Err(self.err_type_mismatch("load_as_int", "Integer", type_name))
}
@ -93,6 +94,7 @@ impl MirInterpreter {
return Err(self.err_type_mismatch("load_as_bool", "Bool", &b.type_name()))
}
VMValue::Future(_) => "Future",
VMValue::WeakBox(_) => "WeakRef", // Phase 285A0
};
Err(self.err_type_mismatch("load_as_bool", "Bool", type_name))
}

View File

@ -8,7 +8,7 @@
use crate::ast::Span;
use crate::box_trait::{BoolBox, IntegerBox, NyashBox, StringBox, VoidBox};
use crate::mir::{BasicBlockId, ConstValue};
use std::sync::Arc;
use std::sync::{Arc, Weak};
/// VM execution error
#[derive(Debug)]
@ -90,7 +90,7 @@ impl std::fmt::Display for VMError {
impl std::error::Error for VMError {}
/// VM value representation
#[derive(Debug, Clone)]
#[derive(Clone)]
pub enum VMValue {
Integer(i64),
Float(f64),
@ -99,6 +99,30 @@ pub enum VMValue {
Future(crate::boxes::future::FutureBox),
Void,
BoxRef(Arc<dyn NyashBox>),
/// Phase 285A0: Weak reference to a Box (non-owning)
WeakBox(Weak<dyn NyashBox>),
}
// Manual Debug implementation for WeakBox
impl std::fmt::Debug for VMValue {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
VMValue::Integer(i) => write!(f, "Integer({})", i),
VMValue::Float(v) => write!(f, "Float({})", v),
VMValue::Bool(b) => write!(f, "Bool({})", b),
VMValue::String(s) => write!(f, "String({:?})", s),
VMValue::Future(_) => write!(f, "Future(...)"),
VMValue::Void => write!(f, "Void"),
VMValue::BoxRef(arc) => write!(f, "BoxRef({})", arc.type_name()),
VMValue::WeakBox(weak) => {
if weak.upgrade().is_some() {
write!(f, "WeakBox(alive)")
} else {
write!(f, "WeakBox(dropped)")
}
}
}
}
}
// Manual PartialEq implementation to avoid requiring PartialEq on FutureBox
@ -112,6 +136,14 @@ impl PartialEq for VMValue {
(VMValue::Void, VMValue::Void) => true,
(VMValue::Future(_), VMValue::Future(_)) => false,
(VMValue::BoxRef(_), VMValue::BoxRef(_)) => false,
// Phase 285A0: WeakBox equality (compare by pointer if both alive)
(VMValue::WeakBox(a), VMValue::WeakBox(b)) => {
match (a.upgrade(), b.upgrade()) {
(Some(arc_a), Some(arc_b)) => Arc::ptr_eq(&arc_a, &arc_b),
(None, None) => true, // Both dropped
_ => false,
}
}
_ => false,
}
}
@ -128,6 +160,14 @@ impl VMValue {
VMValue::Future(f) => Box::new(f.clone()),
VMValue::Void => Box::new(VoidBox::new()),
VMValue::BoxRef(arc_box) => arc_box.share_box(),
VMValue::WeakBox(weak) => {
// Upgrade or return void if dropped
if let Some(arc) = weak.upgrade() {
arc.share_box()
} else {
Box::new(VoidBox::new())
}
}
}
}
@ -141,6 +181,32 @@ impl VMValue {
VMValue::Future(f) => f.to_string_box().value,
VMValue::Void => "void".to_string(),
VMValue::BoxRef(arc_box) => arc_box.to_string_box().value,
VMValue::WeakBox(weak) => {
if weak.upgrade().is_some() {
"WeakRef(alive)".to_string()
} else {
"WeakRef(dropped)".to_string()
}
}
}
}
/// Phase 285A0: Downgrade a strong BoxRef to a weak reference
/// Returns None if not a BoxRef
pub fn downgrade_to_weak(&self) -> Option<VMValue> {
match self {
VMValue::BoxRef(arc) => Some(VMValue::WeakBox(Arc::downgrade(arc))),
_ => None,
}
}
/// Phase 285A0: Upgrade a weak reference to a strong BoxRef
/// Returns Some(BoxRef) if target is alive, None if dropped
pub fn upgrade_weak(&self) -> Option<VMValue> {
match self {
VMValue::WeakBox(weak) => weak.upgrade().map(VMValue::BoxRef),
// Non-weak values: return self (already strong)
_ => Some(self.clone()),
}
}
@ -186,6 +252,10 @@ impl VMValue {
VMValue::Float(f) => Ok(*f != 0.0),
VMValue::String(s) => Ok(!s.is_empty()),
VMValue::Future(_) => Ok(true),
// Phase 285A0: WeakBox truthiness is TypeError (SSOT: types.md:26)
VMValue::WeakBox(_) => Err(VMError::TypeError(
"WeakRef cannot be used in boolean context; use upgrade() first".to_string(),
)),
}
}

View File

@ -53,7 +53,13 @@ impl MirBuilder {
return self.build_str_normalization(arg_values[0]);
}
// 5. Determine call route (unified vs legacy)
// 5. Phase 285A0: weak(x) → WeakNew lowering
// SSOT: docs/reference/language/lifecycle.md:171 - weak(x) returns WeakRef
if name == "weak" && arg_values.len() == 1 {
return self.emit_weak_new(arg_values[0]);
}
// 6. Determine call route (unified vs legacy)
let use_unified = super::call_unified::is_unified_call_enabled()
&& (super::super::call_resolution::is_builtin_function(&name)
|| super::super::call_resolution::is_extern_function(&name));

View File

@ -208,7 +208,7 @@ fn has_control_flow_recursive_p3(node: &ASTNode) -> bool {
#[cfg(test)]
mod tests {
use super::*;
use crate::ast::{LiteralValue, Span};
use crate::ast::{BinaryOperator, LiteralValue, Span};
#[test]
fn test_extract_if_phi_success() {

View File

@ -200,6 +200,17 @@ impl MirBuilder {
method: String,
arguments: &[ASTNode],
) -> Result<ValueId, String> {
// Phase 285A0.1: WeakRef.weak_to_strong() → WeakLoad
// SSOT: docs/reference/language/lifecycle.md:179 - weak_to_strong() returns Box | null
if method == "weak_to_strong" && arguments.is_empty() {
return self.emit_weak_load(object_value);
}
// Phase 285A0.1: upgrade() is deprecated - Fail-Fast
if method == "upgrade" && arguments.is_empty() {
return Err("WeakRef uses weak_to_strong(), not upgrade()".to_string());
}
// Build argument values
let mut arg_values = Vec::new();
for arg in arguments {

View File

@ -340,6 +340,14 @@ impl JoinValue {
crate::backend::VMValue::Future(_) => {
Err(JoinIrOpError::new("Future not supported in JoinValue"))
}
// Phase 285A0: Upgrade WeakBox before conversion
crate::backend::VMValue::WeakBox(w) => {
if let Some(arc) = w.upgrade() {
Ok(JoinValue::BoxRef(arc))
} else {
Ok(JoinValue::Unit) // Dropped weak → Unit (null)
}
}
}
}
}

View File

@ -5,6 +5,8 @@
* Extracted from parser/mod.rs as part of modularization
*/
pub(crate) mod params;
use super::ParseError;
use crate::ast::Span;
use crate::tokenizer::{Token, TokenType};

View File

@ -0,0 +1,92 @@
/*!
* Parameter Parsing Utilities
*
* Phase 285A1.5: Common helper for parameter name list parsing
* Prevents infinite parser hangs on unsupported parameter type annotations
*/
use super::ParserUtils;
use crate::parser::{NyashParser, ParseError};
use crate::tokenizer::TokenType;
/// Parse parameter name list with Fail-Fast on unexpected tokens
///
/// Parses: IDENT (',' IDENT)*
/// Rejects: Type annotations, unexpected tokens, malformed comma sequences
///
/// # Arguments
/// * `p` - Mutable reference to NyashParser
/// * `context` - Context string for error messages ("method", "constructor", "function", etc.)
///
/// # Returns
/// * `Ok(Vec<String>)` - List of parameter names
/// * `Err(ParseError)` - Parse error with context-aware message
///
/// # Features
/// * Progress-zero detection: Tracks token position, errors if stuck
/// * Explicit token handling: All token types explicitly matched
/// * Fail-Fast: Either advances or errors (no infinite loop possible)
/// * Unified error messages: Single source of truth for error text
pub(crate) fn parse_param_name_list(
p: &mut NyashParser,
context: &str,
) -> Result<Vec<String>, ParseError> {
let mut params = Vec::new();
let mut last_token_position: Option<(usize, usize)> = None;
while !p.match_token(&TokenType::RPAREN) && !p.is_at_end() {
// Progress-zero detection: same position twice → infinite loop risk
let current_position = p.current_position();
if let Some(last_pos) = last_token_position {
if current_position == last_pos {
return Err(ParseError::InfiniteLoop {
location: format!("{} parameter list", context),
token: p.current_token().token_type.clone(),
line: p.current_token().line,
});
}
}
last_token_position = Some(current_position);
match &p.current_token().token_type {
TokenType::IDENTIFIER(param) => {
params.push(param.clone());
p.advance();
if p.match_token(&TokenType::COMMA) {
p.advance();
} else if !p.match_token(&TokenType::RPAREN) {
return Err(ParseError::UnexpectedToken {
found: p.current_token().token_type.clone(),
expected: format!(
"',' or ')' in {} parameter list. \
Note: Parameter type annotations are not supported.",
context
),
line: p.current_token().line,
});
}
}
TokenType::COMMA => {
return Err(ParseError::UnexpectedToken {
found: TokenType::COMMA,
expected: format!("parameter name in {} parameter list", context),
line: p.current_token().line,
});
}
_ => {
return Err(ParseError::UnexpectedToken {
found: p.current_token().token_type.clone(),
expected: format!(
"parameter name or ')' in {} parameter list. \
Note: Parameter type annotations are not supported.",
context
),
line: p.current_token().line,
});
}
}
}
Ok(params)
}

View File

@ -23,17 +23,10 @@ pub(crate) fn try_parse_constructor(
let name = "init".to_string();
p.advance(); // consume 'init'
p.consume(TokenType::LPAREN)?;
let mut params = Vec::new();
while !p.match_token(&TokenType::RPAREN) && !p.is_at_end() {
crate::must_advance!(p, _unused, "constructor parameter parsing");
if let TokenType::IDENTIFIER(param) = &p.current_token().token_type {
params.push(param.clone());
p.advance();
}
if p.match_token(&TokenType::COMMA) {
p.advance();
}
}
// Phase 285A1.5: Use shared helper to prevent parser hangs on unsupported type annotations
let params = crate::parser::common::params::parse_param_name_list(p, "constructor (init)")?;
p.consume(TokenType::RPAREN)?;
let mut body = p.parse_block_statements()?;
// Optional postfix catch/cleanup (method-level gate)
@ -97,17 +90,10 @@ pub(crate) fn try_parse_constructor(
let name = "pack".to_string();
p.advance(); // consume 'pack'
p.consume(TokenType::LPAREN)?;
let mut params = Vec::new();
while !p.match_token(&TokenType::RPAREN) && !p.is_at_end() {
crate::must_advance!(p, _unused, "pack parameter parsing");
if let TokenType::IDENTIFIER(param) = &p.current_token().token_type {
params.push(param.clone());
p.advance();
}
if p.match_token(&TokenType::COMMA) {
p.advance();
}
}
// Phase 285A1.5: Use shared helper to prevent parser hangs on unsupported type annotations
let params = crate::parser::common::params::parse_param_name_list(p, "constructor (pack)")?;
p.consume(TokenType::RPAREN)?;
let body = p.parse_block_statements()?;
let node = ASTNode::FunctionDeclaration {
@ -134,17 +120,10 @@ pub(crate) fn try_parse_constructor(
let name = "birth".to_string();
p.advance(); // consume 'birth'
p.consume(TokenType::LPAREN)?;
let mut params = Vec::new();
while !p.match_token(&TokenType::RPAREN) && !p.is_at_end() {
crate::must_advance!(p, _unused, "birth parameter parsing");
if let TokenType::IDENTIFIER(param) = &p.current_token().token_type {
params.push(param.clone());
p.advance();
}
if p.match_token(&TokenType::COMMA) {
p.advance();
}
}
// Phase 285A1.5: Use shared helper to prevent parser hangs on unsupported type annotations
let params = crate::parser::common::params::parse_param_name_list(p, "constructor (birth)")?;
p.consume(TokenType::RPAREN)?;
let body = p.parse_block_statements()?;
let node = ASTNode::FunctionDeclaration {

View File

@ -151,6 +151,35 @@ pub(crate) fn try_parse_visibility_block_or_single(
p.consume(TokenType::RBRACE)?;
return Ok(true);
}
// Phase 285A1.4: Sugar syntax - public weak parent, private weak parent
if p.match_token(&TokenType::WEAK) {
p.advance(); // consume WEAK only
// Read field name (reuse existing pattern)
if let TokenType::IDENTIFIER(fname) = &p.current_token().token_type {
let fname = fname.clone();
p.advance(); // consume IDENTIFIER
// Delegate to existing weak field parser (handles type annotation, etc.)
parse_weak_field(p, fname.clone(), methods, fields, weak_fields)?;
// Register with visibility tracking
if visibility == "public" {
public_fields.push(fname);
} else {
private_fields.push(fname);
}
*last_method_name = None; // Reset method context (Phase 285A1.4)
return Ok(true);
} else {
return Err(ParseError::UnexpectedToken {
expected: "field name after 'weak' in visibility context".to_string(),
found: p.current_token().token_type.clone(),
line: p.current_token().line,
});
}
}
if let TokenType::IDENTIFIER(n) = &p.current_token().token_type {
let fname = n.clone();
p.advance();

View File

@ -17,17 +17,8 @@ pub(crate) fn try_parse_method(
}
p.advance(); // consume '('
let mut params = Vec::new();
while !p.match_token(&TokenType::RPAREN) && !p.is_at_end() {
crate::must_advance!(p, _unused, "method parameter parsing");
if let TokenType::IDENTIFIER(param) = &p.current_token().token_type {
params.push(param.clone());
p.advance();
}
if p.match_token(&TokenType::COMMA) {
p.advance();
}
}
// Phase 285A1.5: Use shared helper to prevent parser hangs on unsupported type annotations
let params = crate::parser::common::params::parse_param_name_list(p, "method")?;
p.consume(TokenType::RPAREN)?;
let mut body = p.parse_block_statements()?;

View File

@ -313,6 +313,19 @@ impl NyashParser {
span: Span::unknown(),
})
}
// Phase 285A0: weak(expr) → WeakRef creation
// SSOT: docs/reference/language/lifecycle.md:171
TokenType::WEAK => {
self.advance();
self.consume(TokenType::LPAREN)?;
let argument = self.parse_expression()?;
self.consume(TokenType::RPAREN)?;
Ok(ASTNode::FunctionCall {
name: "weak".to_string(),
arguments: vec![argument],
span: Span::unknown(),
})
}
_ => {
let line = self.current_token().line;
Err(ParseError::InvalidExpression { line })

View File

@ -29,27 +29,9 @@ impl NyashParser {
// パラメータリストをパース
self.consume(TokenType::LPAREN)?;
let mut params = Vec::new();
while !self.match_token(&TokenType::RPAREN) && !self.is_at_end() {
must_advance!(self, _unused, "function declaration parameter parsing");
if let TokenType::IDENTIFIER(param) = &self.current_token().token_type {
params.push(param.clone());
self.advance();
if self.match_token(&TokenType::COMMA) {
self.advance();
}
} else if !self.match_token(&TokenType::RPAREN) {
let line = self.current_token().line;
return Err(ParseError::UnexpectedToken {
found: self.current_token().token_type.clone(),
expected: "parameter name".to_string(),
line,
});
}
}
// Phase 285A1.5: Use shared helper to prevent parser hangs on unsupported type annotations
let params = crate::parser::common::params::parse_param_name_list(self, "function")?;
self.consume(TokenType::RPAREN)?;

View File

@ -67,27 +67,9 @@ impl NyashParser {
// パラメータリストをパース
self.consume(TokenType::LPAREN)?;
let mut params = Vec::new();
while !self.match_token(&TokenType::RPAREN) && !self.is_at_end() {
must_advance!(self, _unused, "static function parameter parsing");
if let TokenType::IDENTIFIER(param) = &self.current_token().token_type {
params.push(param.clone());
self.advance();
if self.match_token(&TokenType::COMMA) {
self.advance();
}
} else if !self.match_token(&TokenType::RPAREN) {
let line = self.current_token().line;
return Err(ParseError::UnexpectedToken {
found: self.current_token().token_type.clone(),
expected: "parameter name".to_string(),
line,
});
}
}
// Phase 285A1.5: Use shared helper to prevent parser hangs on unsupported type annotations
let params = crate::parser::common::params::parse_param_name_list(self, "static method")?;
self.consume(TokenType::RPAREN)?;

View File

@ -642,6 +642,9 @@ impl NyashRunner {
.debug(&format!("[runner/vm] exit_code={}", exit_code));
}
// Phase 285: Emit leak report before exit (if enabled)
crate::runtime::leak_tracker::emit_leak_report();
// Exit with the return value as exit code
process::exit(exit_code);
}

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
));
}
}
}
}
}