trace: add execution route visibility + debug passthrough; phase2170 canaries; docs
- Add HAKO_TRACE_EXECUTION to trace executor route - Rust hv1_inline: stderr [trace] executor: hv1_inline (rust) - Hakovm dispatcher: stdout [trace] executor: hakovm (hako) - test_runner: trace lines for hv1_inline/core/hakovm routes - Add HAKO_VERIFY_SHOW_LOGS and HAKO_DEBUG=1 (enables both) - verify_v1_inline_file() log passthrough with numeric rc extraction - test_runner exports via HAKO_DEBUG - Canary expansion under phase2170 (state spec) - Array: push×5/10 → size, len/length alias, per‑recv/global, flow across blocks - Map: set dup-key non-increment, value_state get/has - run_all.sh: unify, remove SKIPs; all PASS - Docs - ENV_VARS.md: add Debug/Tracing toggles and examples - PLAN.md/CURRENT_TASK.md: mark 21.7 green, add Quickstart lines All changes gated by env vars; default behavior unchanged.
This commit is contained in:
@ -84,8 +84,17 @@ impl FileBox {
|
||||
}
|
||||
|
||||
pub fn write_all(&self, _buf: &[u8]) -> Result<(), String> {
|
||||
// CoreRo does not support write - Fail-Fast
|
||||
Err("Write operation not supported in read-only mode".to_string())
|
||||
// Fail-Fast by capability: consult provider caps
|
||||
let caps = self.provider
|
||||
.as_ref()
|
||||
.map(|p| p.caps())
|
||||
.or_else(|| provider_lock::get_filebox_caps())
|
||||
.unwrap_or_else(|| provider::FileCaps::read_only());
|
||||
if !caps.write {
|
||||
return Err("Write unsupported by current FileBox provider (read-only)".to_string());
|
||||
}
|
||||
// Write-capable provider not wired yet
|
||||
Err("Write supported by provider but not implemented in this build".to_string())
|
||||
}
|
||||
|
||||
/// ファイルの内容を読み取る
|
||||
@ -98,8 +107,15 @@ impl FileBox {
|
||||
|
||||
/// ファイルに内容を書き込む
|
||||
pub fn write(&self, _content: Box<dyn NyashBox>) -> Box<dyn NyashBox> {
|
||||
// Fail-Fast: CoreRo does not support write
|
||||
Box::new(StringBox::new("Error: Write operation not supported in read-only mode"))
|
||||
let caps = self.provider
|
||||
.as_ref()
|
||||
.map(|p| p.caps())
|
||||
.or_else(|| provider_lock::get_filebox_caps())
|
||||
.unwrap_or_else(|| provider::FileCaps::read_only());
|
||||
if !caps.write {
|
||||
return Box::new(StringBox::new("Error: write unsupported by provider (read-only)"));
|
||||
}
|
||||
Box::new(StringBox::new("Error: write supported but not implemented in this build"))
|
||||
}
|
||||
|
||||
/// ファイルが存在するかチェック
|
||||
@ -110,14 +126,28 @@ impl FileBox {
|
||||
|
||||
/// ファイルを削除
|
||||
pub fn delete(&self) -> Box<dyn NyashBox> {
|
||||
// Fail-Fast: CoreRo does not support delete
|
||||
Box::new(StringBox::new("Error: Delete operation not supported in read-only mode"))
|
||||
let caps = self.provider
|
||||
.as_ref()
|
||||
.map(|p| p.caps())
|
||||
.or_else(|| provider_lock::get_filebox_caps())
|
||||
.unwrap_or_else(|| provider::FileCaps::read_only());
|
||||
if !caps.write {
|
||||
return Box::new(StringBox::new("Error: delete unsupported by provider (read-only)"));
|
||||
}
|
||||
Box::new(StringBox::new("Error: delete supported but not implemented in this build"))
|
||||
}
|
||||
|
||||
/// ファイルをコピー
|
||||
pub fn copy(&self, _dest: &str) -> Box<dyn NyashBox> {
|
||||
// Fail-Fast: CoreRo does not support copy
|
||||
Box::new(StringBox::new("Error: Copy operation not supported in read-only mode"))
|
||||
let caps = self.provider
|
||||
.as_ref()
|
||||
.map(|p| p.caps())
|
||||
.or_else(|| provider_lock::get_filebox_caps())
|
||||
.unwrap_or_else(|| provider::FileCaps::read_only());
|
||||
if !caps.write {
|
||||
return Box::new(StringBox::new("Error: copy unsupported by provider (read-only)"));
|
||||
}
|
||||
Box::new(StringBox::new("Error: copy supported but not implemented in this build"))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -8,6 +8,10 @@ use std::collections::HashMap;
|
||||
/// - mir_call(Method ArrayBox.push/size/len/length) with optional per-receiver state
|
||||
/// - ret (by register id)
|
||||
pub fn run_json_v1_inline(json: &str) -> i32 {
|
||||
// Optional execution trace (stderr) for debugging
|
||||
if crate::config::env::env_bool("HAKO_TRACE_EXECUTION") {
|
||||
eprintln!("[trace] executor: hv1_inline (rust)");
|
||||
}
|
||||
// Parse JSON
|
||||
let v: Value = match serde_json::from_str(json) {
|
||||
Ok(v) => v,
|
||||
@ -31,13 +35,21 @@ pub fn run_json_v1_inline(json: &str) -> i32 {
|
||||
|
||||
// Registers and simple method state
|
||||
let mut regs: HashMap<i64, i64> = HashMap::new();
|
||||
let mut str_regs: HashMap<i64, String> = HashMap::new(); // Track string values loaded via const
|
||||
let sizestate = crate::config::env::env_bool("HAKO_VM_MIRCALL_SIZESTATE");
|
||||
let per_recv = crate::config::env::env_bool("HAKO_VM_MIRCALL_SIZESTATE_PER_RECV");
|
||||
let value_state = crate::config::env::env_bool("HAKO_VM_MIRCALL_VALUESTATE");
|
||||
// Keep Array and Map length states separate to avoid cross‑box interference
|
||||
let mut len_global_arr: i64 = 0;
|
||||
let mut len_by_recv_arr: HashMap<i64, i64> = HashMap::new();
|
||||
let mut len_global_map: i64 = 0;
|
||||
let mut len_by_recv_map: HashMap<i64, i64> = HashMap::new();
|
||||
// Map dup-key detection: track which keys have been set (per-receiver or global)
|
||||
let mut map_keys_global: std::collections::HashSet<String> = std::collections::HashSet::new();
|
||||
let mut map_keys_by_recv: HashMap<i64, std::collections::HashSet<String>> = HashMap::new();
|
||||
// Map value storage (when value_state=1)
|
||||
let mut map_vals_global: HashMap<String, i64> = HashMap::new();
|
||||
let mut map_vals_by_recv: HashMap<i64, HashMap<String, i64>> = HashMap::new();
|
||||
|
||||
fn is_size_alias(name: &str) -> bool {
|
||||
matches!(name, "size" | "len" | "length")
|
||||
@ -140,6 +152,10 @@ pub fn run_json_v1_inline(json: &str) -> i32 {
|
||||
// Prefer i64 numeric constants; otherwise stub non-numeric to 0 for inline path
|
||||
if let Some(n) = inst.get("value").and_then(|vv| vv.get("value")).and_then(Value::as_i64) {
|
||||
regs.insert(dst, n);
|
||||
} else if let Some(s) = inst.get("value").and_then(|vv| vv.get("value")).and_then(Value::as_str) {
|
||||
// Store string value for Map key tracking
|
||||
str_regs.insert(dst, s.to_string());
|
||||
regs.insert(dst, 0); // Also set numeric register to 0 for compatibility
|
||||
} else {
|
||||
regs.insert(dst, 0);
|
||||
}
|
||||
@ -205,17 +221,93 @@ pub fn run_json_v1_inline(json: &str) -> i32 {
|
||||
let method = callee.get("method").and_then(Value::as_str).unwrap_or("");
|
||||
let recv = callee.get("receiver").and_then(Value::as_i64).unwrap_or(-1);
|
||||
if method == "set" {
|
||||
if sizestate {
|
||||
if per_recv {
|
||||
let e = len_by_recv_map.entry(recv).or_insert(0);
|
||||
*e += 1;
|
||||
// Extract key from first arg (register containing string value)
|
||||
let args = if let Some(mc) = mc { mc.get("args") } else { inst.get("args") };
|
||||
let key_str = if let Some(args_arr) = args.and_then(Value::as_array) {
|
||||
if let Some(key_reg) = args_arr.get(0).and_then(Value::as_i64) {
|
||||
// Look up string value from str_regs
|
||||
str_regs.get(&key_reg).cloned().unwrap_or_default()
|
||||
} else { String::new() }
|
||||
} else { String::new() };
|
||||
|
||||
if sizestate && !key_str.is_empty() {
|
||||
let is_new_key = if per_recv {
|
||||
let keys = map_keys_by_recv.entry(recv).or_insert_with(std::collections::HashSet::new);
|
||||
keys.insert(key_str.clone())
|
||||
} else {
|
||||
len_global_map += 1;
|
||||
map_keys_global.insert(key_str.clone())
|
||||
};
|
||||
|
||||
// Only increment size if this is a new key
|
||||
if is_new_key {
|
||||
if per_recv {
|
||||
let e = len_by_recv_map.entry(recv).or_insert(0);
|
||||
*e += 1;
|
||||
} else {
|
||||
len_global_map += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// value_state: store the value
|
||||
if value_state && !key_str.is_empty() {
|
||||
if let Some(args_arr) = args.and_then(Value::as_array) {
|
||||
if let Some(val_reg) = args_arr.get(1).and_then(Value::as_i64) {
|
||||
let val = *regs.get(&val_reg).unwrap_or(&0);
|
||||
if per_recv {
|
||||
let vals = map_vals_by_recv.entry(recv).or_insert_with(HashMap::new);
|
||||
vals.insert(key_str.clone(), val);
|
||||
} else {
|
||||
map_vals_global.insert(key_str.clone(), val);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(dst) = inst.get("dst").and_then(Value::as_i64) { regs.insert(dst, 0); }
|
||||
continue;
|
||||
}
|
||||
if method == "get" && value_state {
|
||||
// Extract key and retrieve value
|
||||
let args = if let Some(mc) = mc { mc.get("args") } else { inst.get("args") };
|
||||
let key_str = if let Some(args_arr) = args.and_then(Value::as_array) {
|
||||
if let Some(key_reg) = args_arr.get(0).and_then(Value::as_i64) {
|
||||
str_regs.get(&key_reg).cloned().unwrap_or_default()
|
||||
} else { String::new() }
|
||||
} else { String::new() };
|
||||
|
||||
if !key_str.is_empty() {
|
||||
let val = if per_recv {
|
||||
map_vals_by_recv.get(&recv).and_then(|m| m.get(&key_str)).cloned().unwrap_or(0)
|
||||
} else {
|
||||
*map_vals_global.get(&key_str).unwrap_or(&0)
|
||||
};
|
||||
if let Some(dst) = inst.get("dst").and_then(Value::as_i64) { regs.insert(dst, val); }
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if method == "has" && value_state {
|
||||
// Check if key exists
|
||||
let args = if let Some(mc) = mc { mc.get("args") } else { inst.get("args") };
|
||||
let key_str = if let Some(args_arr) = args.and_then(Value::as_array) {
|
||||
if let Some(key_reg) = args_arr.get(0).and_then(Value::as_i64) {
|
||||
str_regs.get(&key_reg).cloned().unwrap_or_default()
|
||||
} else { String::new() }
|
||||
} else { String::new() };
|
||||
|
||||
let has = if !key_str.is_empty() {
|
||||
if per_recv {
|
||||
map_vals_by_recv.get(&recv).map(|m| m.contains_key(&key_str)).unwrap_or(false)
|
||||
} else {
|
||||
map_vals_global.contains_key(&key_str)
|
||||
}
|
||||
} else { false };
|
||||
|
||||
if let Some(dst) = inst.get("dst").and_then(Value::as_i64) {
|
||||
regs.insert(dst, if has { 1 } else { 0 });
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if is_size_alias(method) {
|
||||
let value = if sizestate {
|
||||
if per_recv { *len_by_recv_map.get(&recv).unwrap_or(&0) } else { len_global_map }
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::{Arc, OnceLock};
|
||||
use crate::boxes::file::provider::FileIo;
|
||||
use crate::boxes::file::provider::{FileCaps, FileIo};
|
||||
|
||||
static LOCKED: AtomicBool = AtomicBool::new(false);
|
||||
static WARN_ONCE: OnceLock<()> = OnceLock::new();
|
||||
@ -49,3 +49,8 @@ pub fn get_filebox_provider() -> Option<&'static Arc<dyn FileIo>> {
|
||||
FILEBOX_PROVIDER.get()
|
||||
}
|
||||
|
||||
/// Convenience: fetch current FileBox provider capabilities (if initialized).
|
||||
/// Returns None when no provider is registered yet.
|
||||
pub fn get_filebox_caps() -> Option<FileCaps> {
|
||||
get_filebox_provider().map(|p| p.caps())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user