Gate‑C(Core) OOB strict fail‑fast; String VM handler normalization; JSON lint Stage‑B root fixes via scanner field boxing and BinOp operand slotify; docs + smokes update

This commit is contained in:
nyash-codex
2025-11-01 18:45:26 +09:00
parent c331296552
commit 47bd2d2ee2
15 changed files with 280 additions and 107 deletions

View File

@ -134,6 +134,9 @@ impl MirInterpreter {
};
self.box_trace_emit_call(&cls, method, args.len());
}
if std::env::var("NYASH_VM_TRACE").ok().as_deref() == Some("1") && method == "trim" {
eprintln!("[vm-trace] handle_box_call: method=trim (pre-dispatch)");
}
// Debug: trace length dispatch receiver type before any handler resolution
if method == "length" && std::env::var("NYASH_VM_TRACE").ok().as_deref() == Some("1") {
let recv = self.reg_load(box_val).unwrap_or(VMValue::Void);
@ -202,8 +205,14 @@ impl MirInterpreter {
trace_dispatch!(method, "instance_box");
return Ok(());
}
if std::env::var("NYASH_VM_TRACE").ok().as_deref() == Some("1") && method == "trim" {
eprintln!("[vm-trace] dispatch trying boxes_string");
}
if super::boxes_string::try_handle_string_box(self, dst, box_val, method, args)? {
trace_dispatch!(method, "string_box");
if std::env::var("NYASH_VM_TRACE").ok().as_deref() == Some("1") && method == "trim" {
eprintln!("[vm-trace] dispatch handled by boxes_string");
}
return Ok(());
}
if super::boxes_array::try_handle_array_box(self, dst, box_val, method, args)? {

View File

@ -8,94 +8,101 @@ pub(super) fn try_handle_string_box(
method: &str,
args: &[ValueId],
) -> Result<bool, VMError> {
if std::env::var("NYASH_VM_TRACE").ok().as_deref() == Some("1") {
eprintln!("[vm-trace] try_handle_string_box(method={})", method);
}
let recv = this.reg_load(box_val)?;
let recv_box_any: Box<dyn NyashBox> = match recv.clone() {
VMValue::BoxRef(b) => b.share_box(),
other => other.to_nyash_box(),
// Normalize receiver to trait-level StringBox to bridge old/new StringBox implementations
let sb_norm: crate::box_trait::StringBox = match recv.clone() {
VMValue::String(s) => crate::box_trait::StringBox::new(s),
VMValue::BoxRef(b) => b.to_string_box(),
other => other.to_nyash_box().to_string_box(),
};
if let Some(sb) = recv_box_any
.as_any()
.downcast_ref::<crate::box_trait::StringBox>()
{
match method {
"length" => {
let ret = sb.length();
if let Some(d) = dst { this.regs.insert(d, VMValue::from_nyash_box(ret)); }
return Ok(true);
// Only handle known string methods here
match method {
"length" => {
let ret = sb_norm.length();
if let Some(d) = dst { this.regs.insert(d, VMValue::from_nyash_box(ret)); }
return Ok(true);
}
"trim" => {
let ret = sb_norm.trim();
if let Some(d) = dst { this.regs.insert(d, VMValue::from_nyash_box(ret)); }
return Ok(true);
}
"indexOf" => {
// indexOf(substr) -> first index or -1
if args.len() != 1 {
return Err(VMError::InvalidInstruction("indexOf expects 1 arg".into()));
}
"indexOf" => {
// indexOf(substr) -> first index or -1
if args.len() != 1 {
return Err(VMError::InvalidInstruction("indexOf expects 1 arg".into()));
let needle = this.reg_load(args[0])?.to_string();
let idx = sb_norm.value.find(&needle).map(|i| i as i64).unwrap_or(-1);
if let Some(d) = dst { this.regs.insert(d, VMValue::Integer(idx)); }
return Ok(true);
}
"stringify" => {
// JSON-style stringify for strings: quote and escape common characters
let mut quoted = String::with_capacity(sb_norm.value.len() + 2);
quoted.push('"');
for ch in sb_norm.value.chars() {
match ch {
'"' => quoted.push_str("\\\""),
'\\' => quoted.push_str("\\\\"),
'\n' => quoted.push_str("\\n"),
'\r' => quoted.push_str("\\r"),
'\t' => quoted.push_str("\\t"),
c if c.is_control() => quoted.push(' '),
c => quoted.push(c),
}
let needle = this.reg_load(args[0])?.to_string();
let idx = sb.value.find(&needle).map(|i| i as i64).unwrap_or(-1);
if let Some(d) = dst { this.regs.insert(d, VMValue::Integer(idx)); }
return Ok(true);
}
"stringify" => {
// JSON-style stringify for strings: quote and escape common characters
let mut quoted = String::with_capacity(sb.value.len() + 2);
quoted.push('"');
for ch in sb.value.chars() {
match ch {
'"' => quoted.push_str("\\\""),
'\\' => quoted.push_str("\\\\"),
'\n' => quoted.push_str("\\n"),
'\r' => quoted.push_str("\\r"),
'\t' => quoted.push_str("\\t"),
c if c.is_control() => quoted.push(' '),
c => quoted.push(c),
}
}
quoted.push('"');
if let Some(d) = dst {
this.regs.insert(d, VMValue::from_nyash_box(Box::new(crate::box_trait::StringBox::new(quoted))));
}
return Ok(true);
quoted.push('"');
if let Some(d) = dst {
this.regs.insert(d, VMValue::from_nyash_box(Box::new(crate::box_trait::StringBox::new(quoted))));
}
"substring" => {
if args.len() != 2 {
return Err(VMError::InvalidInstruction(
"substring expects 2 args (start, end)".into(),
));
}
let s_idx = this.reg_load(args[0])?.as_integer().unwrap_or(0);
let e_idx = this.reg_load(args[1])?.as_integer().unwrap_or(0);
let len = sb.value.chars().count() as i64;
let start = s_idx.max(0).min(len) as usize;
let end = e_idx.max(start as i64).min(len) as usize;
let chars: Vec<char> = sb.value.chars().collect();
let sub: String = chars[start..end].iter().collect();
if let Some(d) = dst { this.regs.insert(d, VMValue::from_nyash_box(Box::new(crate::box_trait::StringBox::new(sub)))) ; }
return Ok(true);
return Ok(true);
}
"substring" => {
if args.len() != 2 {
return Err(VMError::InvalidInstruction(
"substring expects 2 args (start, end)".into(),
));
}
"concat" => {
if args.len() != 1 {
return Err(VMError::InvalidInstruction("concat expects 1 arg".into()));
}
let rhs = this.reg_load(args[0])?;
let new_s = format!("{}{}", sb.value, rhs.to_string());
if let Some(d) = dst { this.regs.insert(d, VMValue::from_nyash_box(Box::new(crate::box_trait::StringBox::new(new_s)))) ; }
return Ok(true);
let s_idx = this.reg_load(args[0])?.as_integer().unwrap_or(0);
let e_idx = this.reg_load(args[1])?.as_integer().unwrap_or(0);
let len = sb_norm.value.chars().count() as i64;
let start = s_idx.max(0).min(len) as usize;
let end = e_idx.max(start as i64).min(len) as usize;
let chars: Vec<char> = sb_norm.value.chars().collect();
let sub: String = chars[start..end].iter().collect();
if let Some(d) = dst { this.regs.insert(d, VMValue::from_nyash_box(Box::new(crate::box_trait::StringBox::new(sub)))) ; }
return Ok(true);
}
"concat" => {
if args.len() != 1 {
return Err(VMError::InvalidInstruction("concat expects 1 arg".into()));
}
"is_digit_char" => {
// Accept either 0-arg (use first char of receiver) or 1-arg (string/char to test)
let ch_opt = if args.is_empty() {
sb.value.chars().next()
} else if args.len() == 1 {
let s = this.reg_load(args[0])?.to_string();
s.chars().next()
} else {
return Err(VMError::InvalidInstruction("is_digit_char expects 0 or 1 arg".into()));
};
let is_digit = ch_opt.map(|c| c.is_ascii_digit()).unwrap_or(false);
if let Some(d) = dst { this.regs.insert(d, VMValue::Bool(is_digit)); }
return Ok(true);
}
"is_hex_digit_char" => {
let ch_opt = if args.is_empty() {
sb.value.chars().next()
let rhs = this.reg_load(args[0])?;
let new_s = format!("{}{}", sb_norm.value, rhs.to_string());
if let Some(d) = dst { this.regs.insert(d, VMValue::from_nyash_box(Box::new(crate::box_trait::StringBox::new(new_s)))) ; }
return Ok(true);
}
"is_digit_char" => {
// Accept either 0-arg (use first char of receiver) or 1-arg (string/char to test)
let ch_opt = if args.is_empty() {
sb_norm.value.chars().next()
} else if args.len() == 1 {
let s = this.reg_load(args[0])?.to_string();
s.chars().next()
} else {
return Err(VMError::InvalidInstruction("is_digit_char expects 0 or 1 arg".into()));
};
let is_digit = ch_opt.map(|c| c.is_ascii_digit()).unwrap_or(false);
if let Some(d) = dst { this.regs.insert(d, VMValue::Bool(is_digit)); }
return Ok(true);
}
"is_hex_digit_char" => {
let ch_opt = if args.is_empty() {
sb_norm.value.chars().next()
} else if args.len() == 1 {
let s = this.reg_load(args[0])?.to_string();
s.chars().next()
@ -105,9 +112,8 @@ pub(super) fn try_handle_string_box(
let is_hex = ch_opt.map(|c| c.is_ascii_hexdigit()).unwrap_or(false);
if let Some(d) = dst { this.regs.insert(d, VMValue::Bool(is_hex)); }
return Ok(true);
}
_ => {}
}
_ => {}
}
Ok(false)
}

View File

@ -95,6 +95,9 @@ impl MirInterpreter {
(Add, VMValue::Void, Integer(y)) | (Add, Integer(y), VMValue::Void) if tolerate => Integer(y),
(Add, VMValue::Void, Float(y)) | (Add, Float(y), VMValue::Void) if tolerate => Float(y),
(Add, String(s), VMValue::Void) | (Add, VMValue::Void, String(s)) if tolerate => String(s),
// Dev-only safety valve for Sub (guarded): treat Void as 0
(Sub, Integer(x), VMValue::Void) if tolerate => Integer(x),
(Sub, VMValue::Void, Integer(y)) if tolerate => Integer(0 - y),
(Add, Integer(x), Integer(y)) => Integer(x + y),
(Add, String(s), Integer(y)) => String(format!("{}{}", s, y)),
(Add, String(s), Float(y)) => String(format!("{}{}", s, y)),
@ -123,6 +126,12 @@ impl MirInterpreter {
(Shl, Integer(x), Integer(y)) => Integer(x.wrapping_shl(y as u32)),
(Shr, Integer(x), Integer(y)) => Integer(x.wrapping_shr(y as u32)),
(opk, va, vb) => {
if std::env::var("NYASH_VM_TRACE").ok().as_deref() == Some("1") {
eprintln!(
"[vm-trace] binop error fn={:?} op={:?} a={:?} b={:?} last_block={:?} last_inst={:?}",
self.cur_fn, opk, va, vb, self.last_block, self.last_inst
);
}
return Err(VMError::TypeError(format!(
"unsupported binop {:?} on {:?} and {:?}",
opk, va, vb