chore: Phase 25.1 完了 - LoopForm v2/Stage1 CLI/環境変数削減 + Phase 26-D からの変更

Phase 25.1 完了成果:
-  LoopForm v2 テスト・ドキュメント・コメント完備
  - 4ケース(A/B/C/D)完全テストカバレッジ
  - 最小再現ケース作成(SSAバグ調査用)
  - SSOT文書作成(loopform_ssot.md)
  - 全ソースに [LoopForm] コメントタグ追加

-  Stage-1 CLI デバッグ環境構築
  - stage1_cli.hako 実装
  - stage1_bridge.rs ブリッジ実装
  - デバッグツール作成(stage1_debug.sh/stage1_minimal.sh)
  - アーキテクチャ改善提案文書

-  環境変数削減計画策定
  - 25変数の完全調査・分類
  - 6段階削減ロードマップ(25→5、80%削減)
  - 即時削除可能変数特定(NYASH_CONFIG/NYASH_DEBUG)

Phase 26-D からの累積変更:
- PHI実装改善(ExitPhiBuilder/HeaderPhiBuilder等)
- MIRビルダーリファクタリング
- 型伝播・最適化パス改善
- その他約300ファイルの累積変更

🎯 技術的成果:
- SSAバグ根本原因特定(条件分岐内loop変数変更)
- Region+next_iパターン適用完了(UsingCollectorBox等)
- LoopFormパターン文書化・テスト化完了
- セルフホスティング基盤強化

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: ChatGPT <noreply@openai.com>
Co-Authored-By: Task Assistant <task@anthropic.com>
This commit is contained in:
nyash-codex
2025-11-21 06:25:17 +09:00
parent baf028a94f
commit f9d100ce01
366 changed files with 14322 additions and 5236 deletions

View File

@ -17,4 +17,3 @@ pub fn warn_nyash_toml_used_once() {
"[deprecate] using nyash.toml; please rename to hako.toml",
);
}

View File

@ -35,9 +35,13 @@ pub struct CountingGc {
}
impl CountingGc {
pub fn new() -> Self { Self::new_with_mode(crate::runtime::gc_mode::GcMode::RcCycle) }
pub fn new() -> Self {
Self::new_with_mode(crate::runtime::gc_mode::GcMode::RcCycle)
}
pub fn new_with_mode(mode: crate::runtime::gc_mode::GcMode) -> Self {
Self { inner: crate::runtime::gc_controller::GcController::new(mode) }
Self {
inner: crate::runtime::gc_controller::GcController::new(mode),
}
}
pub fn snapshot(&self) -> (u64, u64, u64) {
self.inner.snapshot()

View File

@ -84,8 +84,14 @@ impl GcHooks for GcController {
if sp_hit || bytes_hit {
// Record reason flags for diagnostics
let mut flags: u64 = 0;
if sp_hit { flags |= 1; self.collect_by_sp.fetch_add(1, Ordering::Relaxed); }
if bytes_hit { flags |= 2; self.collect_by_alloc.fetch_add(1, Ordering::Relaxed); }
if sp_hit {
flags |= 1;
self.collect_by_sp.fetch_add(1, Ordering::Relaxed);
}
if bytes_hit {
flags |= 2;
self.collect_by_alloc.fetch_add(1, Ordering::Relaxed);
}
self.trial_reason_last.store(flags, Ordering::Relaxed);
self.run_trial_collection();
}
@ -114,8 +120,7 @@ impl GcHooks for GcController {
}
self.alloc_count.fetch_add(1, Ordering::Relaxed);
self.alloc_bytes.fetch_add(bytes, Ordering::Relaxed);
self.bytes_since_last
.fetch_add(bytes, Ordering::Relaxed);
self.bytes_since_last.fetch_add(bytes, Ordering::Relaxed);
}
}
@ -202,5 +207,7 @@ impl GcController {
pub fn trial_duration_last_ms(&self) -> u64 {
self.trial_duration_last_ms.load(Ordering::Relaxed)
}
pub fn trial_reason_last_bits(&self) -> u64 { self.trial_reason_last.load(Ordering::Relaxed) }
pub fn trial_reason_last_bits(&self) -> u64 {
self.trial_reason_last.load(Ordering::Relaxed)
}
}

View File

@ -31,4 +31,3 @@ impl GcMode {
}
}
}

View File

@ -32,4 +32,3 @@ pub fn trace_children(obj: &dyn NyashBox, visit: &mut dyn FnMut(Arc<dyn NyashBox
return;
}
}

View File

@ -152,7 +152,9 @@ pub fn pop_task_scope() {
if st.scope_depth > 0 {
st.scope_depth -= 1;
}
if st.scope_depth == 0 { do_join = true; }
if st.scope_depth == 0 {
do_join = true;
}
// Pop explicit group for this scope
popped = st.group_stack.pop();
}

View File

@ -3,6 +3,7 @@
//! プラグインシステムとBox管理の中核
pub mod box_registry;
pub mod deprecations;
pub mod gc;
pub mod gc_controller;
pub mod gc_mode;
@ -10,19 +11,18 @@ pub mod gc_trace;
pub mod global_hooks;
pub mod leak_tracker;
pub mod nyash_runtime;
pub mod observe; // Lightweight observability flags (OOB etc.)
pub mod plugin_config;
pub mod plugin_ffi_common;
pub mod plugin_loader_unified;
pub mod plugin_loader_v2;
pub mod scheduler;
pub mod semantics;
pub mod unified_registry;
pub mod provider_lock;
pub mod provider_verify;
pub mod observe; // Lightweight observability flags (OOB etc.)
pub mod deprecations; // Deprecation warnings with warn-once guards
// pub mod plugin_box; // legacy - 古いPluginBox
// pub mod plugin_loader; // legacy - Host VTable使用
pub mod scheduler;
pub mod semantics;
pub mod unified_registry; // Deprecation warnings with warn-once guards
// pub mod plugin_box; // legacy - 古いPluginBox
// pub mod plugin_loader; // legacy - Host VTable使用
pub mod extern_registry; // ExternCall (env.*) 登録・診断用レジストリ
pub mod host_api; // C ABI: plugins -> host 逆呼び出しAPITLSでVMに橋渡し
pub mod host_handles; // C ABI(TLV) 向け HostHandle レジストリ(ユーザー/内蔵Box受け渡し

View File

@ -19,4 +19,3 @@ pub fn mark_oob() {
pub fn oob_seen() -> bool {
OOB_SEEN.load(Ordering::Relaxed)
}

View File

@ -83,7 +83,12 @@ impl PluginHost {
/// Load a single library directly from path for `using kind="dylib"` autoload.
/// Boxes list is best-effort (may be empty). When empty, TypeBox FFI is used to resolve metadata.
pub fn load_library_direct(&self, lib_name: &str, path: &str, boxes: &[String]) -> BidResult<()> {
pub fn load_library_direct(
&self,
lib_name: &str,
path: &str,
boxes: &[String],
) -> BidResult<()> {
// If caller didn't provide box names, try to infer from nyash_box.toml
let inferred_boxes: Vec<String> = if boxes.is_empty() {
let nyb = std::path::Path::new(path)
@ -94,7 +99,11 @@ impl PluginHost {
} else {
Vec::new()
};
let effective_boxes: Vec<String> = if boxes.is_empty() { inferred_boxes } else { boxes.to_vec() };
let effective_boxes: Vec<String> = if boxes.is_empty() {
inferred_boxes
} else {
boxes.to_vec()
};
let def = crate::config::nyash_toml_v2::LibraryDefinition {
boxes: effective_boxes.clone(),
path: path.to_string(),
@ -105,15 +114,29 @@ impl PluginHost {
if l.config.is_none() {
let mut cfg = NyashConfigV2 {
libraries: std::collections::HashMap::new(),
plugin_paths: crate::config::nyash_toml_v2::PluginPaths { search_paths: vec![] },
plugin_paths: crate::config::nyash_toml_v2::PluginPaths {
search_paths: vec![],
},
plugins: std::collections::HashMap::new(),
box_types: std::collections::HashMap::new(),
};
cfg.libraries.insert(lib_name.to_string(), crate::config::nyash_toml_v2::LibraryDefinition { boxes: def.boxes.clone(), path: def.path.clone() });
cfg.libraries.insert(
lib_name.to_string(),
crate::config::nyash_toml_v2::LibraryDefinition {
boxes: def.boxes.clone(),
path: def.path.clone(),
},
);
l.config = Some(cfg);
// No dedicated config file; keep config_path None and rely on box_specs fallback
} else if let Some(cfg) = l.config.as_mut() {
cfg.libraries.insert(lib_name.to_string(), crate::config::nyash_toml_v2::LibraryDefinition { boxes: def.boxes.clone(), path: def.path.clone() });
cfg.libraries.insert(
lib_name.to_string(),
crate::config::nyash_toml_v2::LibraryDefinition {
boxes: def.boxes.clone(),
path: def.path.clone(),
},
);
}
// Load the library now
l.load_plugin_direct(lib_name, &def)?;
@ -146,7 +169,8 @@ impl PluginHost {
if let Some((lib_name, _lib_def)) = cfg.find_library_for_box(box_type) {
if let Some(box_conf) = cfg.get_box_config(lib_name, box_type, &toml_value) {
// Prefer config mapping; fallback to loader's TypeBox resolve(name)
let (method_id, returns_result) = if let Some(m) = box_conf.methods.get(method_name) {
let (method_id, returns_result) = if let Some(m) = box_conf.methods.get(method_name)
{
(m.method_id, m.returns_result)
} else {
let l = self.loader.read().unwrap();
@ -177,7 +201,13 @@ impl PluginHost {
if let Some(entry) = bm.get(method_name) {
// Support both { method_id = N } and bare integer in the future
let (method_id, returns_result) = if let Some(mid) = entry.get("method_id") {
(mid.as_integer().unwrap_or(0) as u32, entry.get("returns_result").and_then(|b| b.as_bool()).unwrap_or(false))
(
mid.as_integer().unwrap_or(0) as u32,
entry
.get("returns_result")
.and_then(|b| b.as_bool())
.unwrap_or(false),
)
} else if let Some(mid) = entry.as_integer() {
(mid as u32, false)
} else {
@ -266,7 +296,9 @@ impl PluginHost {
}
// Library-backed path
if let Some((lib_name, _)) = cfg.find_library_for_box(box_type) {
if let Some(box_conf) = cfg.get_box_config(lib_name, box_type, &toml_value) {
if let Some(box_conf) =
cfg.get_box_config(lib_name, box_type, &toml_value)
{
if let Some(m) = box_conf.methods.get(method_name) {
return m.returns_result;
}
@ -331,28 +363,50 @@ impl PluginHost {
/// 2) Top-level tables that look like box sections (have `type_id` or `methods`/`lifecycle`)
fn infer_box_names_from_nyash_box(nyb_path: &std::path::Path) -> Vec<String> {
let mut out: Vec<String> = Vec::new();
if !nyb_path.exists() { return out; }
let Ok(text) = std::fs::read_to_string(nyb_path) else { return out; };
let Ok(doc) = toml::from_str::<toml::Value>(&text) else { return out; };
if !nyb_path.exists() {
return out;
}
let Ok(text) = std::fs::read_to_string(nyb_path) else {
return out;
};
let Ok(doc) = toml::from_str::<toml::Value>(&text) else {
return out;
};
// 1) explicit provides
if let Some(arr) = doc.get("provides").and_then(|v| v.get("boxes")).and_then(|v| v.as_array()) {
for v in arr { if let Some(s) = v.as_str() { out.push(s.to_string()); } }
out.sort(); out.dedup();
if !out.is_empty() { return out; }
if let Some(arr) = doc
.get("provides")
.and_then(|v| v.get("boxes"))
.and_then(|v| v.as_array())
{
for v in arr {
if let Some(s) = v.as_str() {
out.push(s.to_string());
}
}
out.sort();
out.dedup();
if !out.is_empty() {
return out;
}
}
// 2) heuristic: tables with type_id or lifecycle/methods
if let Some(tbl) = doc.as_table() {
for (k, v) in tbl.iter() {
if k == "box" || k == "implementation" || k == "artifacts" || k == "provides" { continue; }
if k == "box" || k == "implementation" || k == "artifacts" || k == "provides" {
continue;
}
if let Some(t) = v.as_table() {
let looks_like_box = t.get("type_id").is_some()
|| t.get("methods").is_some()
|| t.get("lifecycle").is_some();
if looks_like_box { out.push(k.clone()); }
if looks_like_box {
out.push(k.clone());
}
}
}
}
out.sort(); out.dedup();
out.sort();
out.dedup();
out
}

View File

@ -245,16 +245,21 @@ pub fn handle_box_introspect(
"kind" => {
let value = args.get(0).ok_or(BidError::PluginError)?;
let info = build_box_info(value.as_ref());
if std::env::var("NYASH_BOX_INTROSPECT_TRACE")
.ok()
.as_deref() == Some("1")
{
if std::env::var("NYASH_BOX_INTROSPECT_TRACE").ok().as_deref() == Some("1") {
eprintln!(
"[box_introspect:plugin] kind={} type_name={} is_map={} is_array={}",
info.get(Box::new(StringBox::new("kind"))).to_string_box().value,
info.get(Box::new(StringBox::new("type_name"))).to_string_box().value,
info.get(Box::new(StringBox::new("is_map"))).to_string_box().value,
info.get(Box::new(StringBox::new("is_array"))).to_string_box().value,
info.get(Box::new(StringBox::new("kind")))
.to_string_box()
.value,
info.get(Box::new(StringBox::new("type_name")))
.to_string_box()
.value,
info.get(Box::new(StringBox::new("is_map")))
.to_string_box()
.value,
info.get(Box::new(StringBox::new("is_array")))
.to_string_box()
.value,
);
}
Ok(Some(Box::new(info)))

View File

@ -3,8 +3,8 @@
use crate::bid::{BidError, BidResult};
use crate::box_trait::NyashBox;
use crate::runtime::plugin_loader_v2::enabled::PluginLoaderV2;
use std::sync::Arc;
use std::env;
use std::sync::Arc;
fn dbg_on() -> bool {
std::env::var("PLUGIN_DEBUG").is_ok()
@ -49,25 +49,36 @@ impl PluginLoaderV2 {
}
// Optional C-core probe (design): emit tag and optionally call into c-core when enabled
if env::var("HAKO_C_CORE_ENABLE").ok().as_deref() == Some("1") && should_route_ccore(box_type, method_name) {
if env::var("HAKO_C_CORE_ENABLE").ok().as_deref() == Some("1")
&& should_route_ccore(box_type, method_name)
{
eprintln!("[c-core:invoke:{}.{}]", box_type, method_name);
#[cfg(feature = "c-core")]
{
// MapBox.set: call C-core stub (no-op) with available info
if box_type == "MapBox" && method_name == "set" {
let key = args.get(0).map(|b| b.to_string_box().value).unwrap_or_default();
let val = args.get(1).map(|b| b.to_string_box().value).unwrap_or_default();
let key = args
.get(0)
.map(|b| b.to_string_box().value)
.unwrap_or_default();
let val = args
.get(1)
.map(|b| b.to_string_box().value)
.unwrap_or_default();
let _ = nyash_c_core::core_map_set(type_id as i32, instance_id, &key, &val);
} else if box_type == "ArrayBox" && method_name == "push" {
// For design stage, pass 0 (we don't rely on c-core result)
let _ = nyash_c_core::core_array_push(type_id as i32, instance_id, 0);
} else if box_type == "ArrayBox" && method_name == "get" {
let _ = nyash_c_core::core_array_get(type_id as i32, instance_id, 0);
} else if box_type == "ArrayBox" && (method_name == "size" || method_name == "len" || method_name == "length") {
} else if box_type == "ArrayBox"
&& (method_name == "size" || method_name == "len" || method_name == "length")
{
let _ = nyash_c_core::core_array_len(type_id as i32, instance_id);
} else {
// Generic probe
let _ = nyash_c_core::core_probe_invoke(box_type, method_name, args.len() as i32);
let _ =
nyash_c_core::core_probe_invoke(box_type, method_name, args.len() as i32);
}
}
}
@ -120,8 +131,12 @@ fn should_trace_tlv_shim(box_type: &str, method: &str) -> bool {
let key = format!("{}.{}", box_type, method);
for pat in flt.split(',') {
let p = pat.trim();
if p.is_empty() { continue; }
if p == method || p == key { return true; }
if p.is_empty() {
continue;
}
if p == method || p == key {
return true;
}
}
return false;
}
@ -135,8 +150,12 @@ fn should_trace_cwrap(box_type: &str, method: &str) -> bool {
let key = format!("{}.{}", box_type, method);
for pat in flt.split(',') {
let p = pat.trim();
if p.is_empty() { continue; }
if p == method || p == key { return true; }
if p.is_empty() {
continue;
}
if p == method || p == key {
return true;
}
}
return false;
}
@ -149,8 +168,12 @@ fn should_trace_call(target: &str, method: &str) -> bool {
let key = format!("{}.{}", target, method);
for pat in flt.split(',') {
let p = pat.trim();
if p.is_empty() { continue; }
if p == method || p == key { return true; }
if p.is_empty() {
continue;
}
if p == method || p == key {
return true;
}
}
return false;
}
@ -162,8 +185,12 @@ fn should_route_ccore(box_type: &str, method: &str) -> bool {
let key = format!("{}.{}", box_type, method);
for pat in flt.split(',') {
let p = pat.trim();
if p.is_empty() { continue; }
if p == method || p == key { return true; }
if p.is_empty() {
continue;
}
if p == method || p == key {
return true;
}
}
return false;
}
@ -203,9 +230,7 @@ fn resolve_type_info(loader: &PluginLoaderV2, box_type: &str) -> BidResult<(Stri
/// Decode TLV result into a NyashBox
fn decode_tlv_result(box_type: &str, data: &[u8]) -> BidResult<Option<Box<dyn NyashBox>>> {
if let Some((tag, _sz, payload)) =
crate::runtime::plugin_ffi_common::decode::tlv_first(data)
{
if let Some((tag, _sz, payload)) = crate::runtime::plugin_ffi_common::decode::tlv_first(data) {
let bx: Box<dyn NyashBox> = match tag {
1 => Box::new(crate::box_trait::BoolBox::new(
crate::runtime::plugin_ffi_common::decode::bool(payload).unwrap_or(false),

View File

@ -2,7 +2,10 @@
use crate::bid::{BidError, BidResult};
use crate::box_trait::NyashBox;
use crate::runtime::plugin_loader_v2::enabled::{PluginLoaderV2, types::{PluginBoxV2, PluginHandleInner}};
use crate::runtime::plugin_loader_v2::enabled::{
types::{PluginBoxV2, PluginHandleInner},
PluginLoaderV2,
};
use std::sync::Arc;
fn dbg_on() -> bool {
@ -88,10 +91,7 @@ impl PluginLoaderV2 {
}
/// Resolve box IDs (type_id, birth_id, fini_id) from configuration or specs
fn resolve_box_ids(
loader: &PluginLoaderV2,
box_type: &str,
) -> BidResult<(u32, u32, Option<u32>)> {
fn resolve_box_ids(loader: &PluginLoaderV2, box_type: &str) -> BidResult<(u32, u32, Option<u32>)> {
let (mut type_id_opt, mut birth_id_opt, mut fini_id) = (None, None, None);
// Try config mapping first (when available)
@ -138,4 +138,4 @@ fn resolve_box_ids(
let birth_id = birth_id_opt.ok_or(BidError::InvalidMethod)?;
Ok((type_id, birth_id, fini_id))
}
}

View File

@ -4,10 +4,13 @@ use crate::runtime::plugin_loader_v2::enabled::{errors, host_bridge, types};
pub(super) fn prebirth_singletons(loader: &PluginLoaderV2) -> BidResult<()> {
let config = loader.config.as_ref().ok_or(BidError::PluginError)?;
let cfg_path = loader
.config_path
.as_deref()
.unwrap_or_else(|| if std::path::Path::new("hako.toml").exists() { "hako.toml" } else { "nyash.toml" });
let cfg_path = loader.config_path.as_deref().unwrap_or_else(|| {
if std::path::Path::new("hako.toml").exists() {
"hako.toml"
} else {
"nyash.toml"
}
});
if cfg_path == "nyash.toml" && !std::path::Path::new("hako.toml").exists() {
crate::runtime::deprecations::warn_nyash_toml_used_once();
}
@ -38,10 +41,13 @@ pub(super) fn ensure_singleton_handle(
{
return Ok(());
}
let cfg_path = loader
.config_path
.as_deref()
.unwrap_or_else(|| if std::path::Path::new("hako.toml").exists() { "hako.toml" } else { "nyash.toml" });
let cfg_path = loader.config_path.as_deref().unwrap_or_else(|| {
if std::path::Path::new("hako.toml").exists() {
"hako.toml"
} else {
"nyash.toml"
}
});
if cfg_path == "nyash.toml" && !std::path::Path::new("hako.toml").exists() {
crate::runtime::deprecations::warn_nyash_toml_used_once();
}

View File

@ -11,17 +11,22 @@ impl PluginLoaderV2 {
pub(crate) fn resolve_method_id(&self, box_type: &str, method_name: &str) -> BidResult<u32> {
// First try config mapping
if let Some(cfg) = self.config.as_ref() {
let cfg_path = self
.config_path
.as_deref()
.unwrap_or_else(|| if std::path::Path::new("hako.toml").exists() { "hako.toml" } else { "nyash.toml" });
let cfg_path = self.config_path.as_deref().unwrap_or_else(|| {
if std::path::Path::new("hako.toml").exists() {
"hako.toml"
} else {
"nyash.toml"
}
});
if cfg_path == "nyash.toml" && !std::path::Path::new("hako.toml").exists() {
crate::runtime::deprecations::warn_nyash_toml_used_once();
}
// Load and parse TOML
let toml_content = std::fs::read_to_string(cfg_path).map_err(|_| BidError::PluginError)?;
let toml_value: toml::Value = toml::from_str(&toml_content).map_err(|_| BidError::PluginError)?;
let toml_content =
std::fs::read_to_string(cfg_path).map_err(|_| BidError::PluginError)?;
let toml_value: toml::Value =
toml::from_str(&toml_content).map_err(|_| BidError::PluginError)?;
// Find library for box
if let Some((lib_name, _)) = cfg.find_library_for_box(box_type) {
@ -75,10 +80,13 @@ impl PluginLoaderV2 {
/// Check if a method returns a Result type
pub fn method_returns_result(&self, box_type: &str, method_name: &str) -> bool {
if let Some(cfg) = self.config.as_ref() {
let cfg_path = self
.config_path
.as_deref()
.unwrap_or_else(|| if std::path::Path::new("hako.toml").exists() { "hako.toml" } else { "nyash.toml" });
let cfg_path = self.config_path.as_deref().unwrap_or_else(|| {
if std::path::Path::new("hako.toml").exists() {
"hako.toml"
} else {
"nyash.toml"
}
});
if cfg_path == "nyash.toml" && !std::path::Path::new("hako.toml").exists() {
crate::runtime::deprecations::warn_nyash_toml_used_once();
}
@ -86,7 +94,8 @@ impl PluginLoaderV2 {
if let Ok(toml_content) = std::fs::read_to_string(cfg_path) {
if let Ok(toml_value) = toml::from_str::<toml::Value>(&toml_content) {
if let Some((lib_name, _)) = cfg.find_library_for_box(box_type) {
if let Some(box_conf) = cfg.get_box_config(lib_name, box_type, &toml_value) {
if let Some(box_conf) = cfg.get_box_config(lib_name, box_type, &toml_value)
{
if let Some(method_spec) = box_conf.methods.get(method_name) {
return method_spec.returns_result;
}
@ -107,10 +116,13 @@ impl PluginLoaderV2 {
method_name: &str,
) -> BidResult<(u32, u32, bool)> {
let cfg = self.config.as_ref().ok_or(BidError::PluginError)?;
let cfg_path = self
.config_path
.as_deref()
.unwrap_or_else(|| if std::path::Path::new("hako.toml").exists() { "hako.toml" } else { "nyash.toml" });
let cfg_path = self.config_path.as_deref().unwrap_or_else(|| {
if std::path::Path::new("hako.toml").exists() {
"hako.toml"
} else {
"nyash.toml"
}
});
if cfg_path == "nyash.toml" && !std::path::Path::new("hako.toml").exists() {
crate::runtime::deprecations::warn_nyash_toml_used_once();
}

View File

@ -8,13 +8,13 @@ mod loader;
mod method_resolver;
mod types;
pub use extern_functions::handle_box_introspect;
pub use globals::{get_global_loader_v2, init_global_loader_v2, shutdown_plugins_v2};
pub use loader::PluginLoaderV2;
pub use types::{
construct_plugin_box, make_plugin_box_v2, NyashTypeBoxFfi, PluginBoxMetadata, PluginBoxV2,
PluginHandleInner,
};
pub use extern_functions::handle_box_introspect;
pub fn metadata_for_type_id(type_id: u32) -> Option<PluginBoxMetadata> {
let loader = get_global_loader_v2();

View File

@ -5,29 +5,38 @@
* 既定では挙動を変えず、環境変数により警告/エラー化のみ可能にする。
*/
use crate::boxes::file::provider::{FileCaps, FileIo};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, OnceLock};
use crate::boxes::file::provider::{FileCaps, FileIo};
static LOCKED: AtomicBool = AtomicBool::new(false);
static WARN_ONCE: OnceLock<()> = OnceLock::new();
static FILEBOX_PROVIDER: OnceLock<Arc<dyn FileIo>> = OnceLock::new();
/// Return true when providers are locked
pub fn is_locked() -> bool { LOCKED.load(Ordering::Relaxed) }
pub fn is_locked() -> bool {
LOCKED.load(Ordering::Relaxed)
}
/// Lock providers (idempotent)
pub fn lock_providers() { LOCKED.store(true, Ordering::Relaxed); }
pub fn lock_providers() {
LOCKED.store(true, Ordering::Relaxed);
}
/// Guard called before creating a new box instance.
/// Default: no-op. When NYASH_PROVIDER_LOCK_STRICT=1, returns Err if not locked.
/// When NYASH_PROVIDER_LOCK_WARN=1, prints a warning once.
pub fn guard_before_new_box(box_type: &str) -> Result<(), String> {
if is_locked() { return Ok(()); }
if is_locked() {
return Ok(());
}
let strict = std::env::var("NYASH_PROVIDER_LOCK_STRICT").ok().as_deref() == Some("1");
let warn = std::env::var("NYASH_PROVIDER_LOCK_WARN").ok().as_deref() == Some("1");
if strict {
return Err(format!("E_PROVIDER_NOT_LOCKED: attempted to create '{}' before Provider Lock", box_type));
return Err(format!(
"E_PROVIDER_NOT_LOCKED: attempted to create '{}' before Provider Lock",
box_type
));
}
if warn {
// Print once per process
@ -40,7 +49,8 @@ pub fn guard_before_new_box(box_type: &str) -> Result<(), String> {
/// Set the global FileBox provider (can only be called once)
pub fn set_filebox_provider(provider: Arc<dyn FileIo>) -> Result<(), String> {
FILEBOX_PROVIDER.set(provider)
FILEBOX_PROVIDER
.set(provider)
.map_err(|_| "FileBox provider already set".to_string())
}

View File

@ -26,7 +26,9 @@ fn parse_required_methods(spec: &str) -> HashMap<String, Vec<String>> {
let mut map = HashMap::new();
for part in spec.split(';') {
let p = part.trim();
if p.is_empty() { continue; }
if p.is_empty() {
continue;
}
if let Some((ty, rest)) = p.split_once(':') {
let methods: Vec<String> = rest
.split(',')
@ -44,15 +46,30 @@ fn parse_required_methods(spec: &str) -> HashMap<String, Vec<String>> {
fn load_required_methods_from_toml() -> HashMap<String, Vec<String>> {
let mut map: HashMap<String, Vec<String>> = HashMap::new();
let text = match std::fs::read_to_string("nyash.toml") { Ok(s) => s, Err(_) => return map };
let doc: toml::Value = match toml::from_str(&text) { Ok(v) => v, Err(_) => return map };
let text = match std::fs::read_to_string("nyash.toml") {
Ok(s) => s,
Err(_) => return map,
};
let doc: toml::Value = match toml::from_str(&text) {
Ok(v) => v,
Err(_) => return map,
};
// Helper: add entry if array-of-strings
let mut add_arr = |ty: &str, arr: &toml::Value| {
if let Some(a) = arr.as_array() {
let mut v: Vec<String> = Vec::new();
for e in a { if let Some(s) = e.as_str() { let s = s.trim(); if !s.is_empty() { v.push(s.to_string()); } } }
if !v.is_empty() { map.insert(ty.to_string(), v); }
for e in a {
if let Some(s) = e.as_str() {
let s = s.trim();
if !s.is_empty() {
v.push(s.to_string());
}
}
}
if !v.is_empty() {
map.insert(ty.to_string(), v);
}
}
};
@ -61,8 +78,15 @@ fn load_required_methods_from_toml() -> HashMap<String, Vec<String>> {
if let Some(req) = vrfy.get("required_methods") {
if let Some(tbl) = req.as_table() {
for (k, v) in tbl.iter() {
if v.is_array() { add_arr(k, v); continue; }
if let Some(t) = v.as_table() { if let Some(m) = t.get("methods") { add_arr(k, m); } }
if v.is_array() {
add_arr(k, v);
continue;
}
if let Some(t) = v.as_table() {
if let Some(m) = t.get("methods") {
add_arr(k, m);
}
}
}
}
}
@ -72,7 +96,11 @@ fn load_required_methods_from_toml() -> HashMap<String, Vec<String>> {
if let Some(types) = doc.get("types") {
if let Some(tbl) = types.as_table() {
for (k, v) in tbl.iter() {
if let Some(t) = v.as_table() { if let Some(m) = t.get("required_methods") { add_arr(k, m); } }
if let Some(t) = v.as_table() {
if let Some(m) = t.get("required_methods") {
add_arr(k, m);
}
}
}
}
}
@ -83,16 +111,22 @@ fn load_required_methods_from_toml() -> HashMap<String, Vec<String>> {
pub fn verify_from_env() -> Result<(), String> {
let mode = std::env::var("NYASH_PROVIDER_VERIFY").unwrap_or_default();
let mode = mode.to_ascii_lowercase();
if !(mode == "warn" || mode == "strict") { return Ok(()); }
if !(mode == "warn" || mode == "strict") {
return Ok(());
}
// Merge: nyash.toml + env override
let mut req = load_required_methods_from_toml();
let spec = std::env::var("NYASH_VERIFY_REQUIRED_METHODS").unwrap_or_default();
if !spec.trim().is_empty() {
let env_map = parse_required_methods(&spec);
for (k, v) in env_map { req.entry(k).or_default().extend(v); }
for (k, v) in env_map {
req.entry(k).or_default().extend(v);
}
}
if req.is_empty() {
return Ok(());
}
if req.is_empty() { return Ok(()); }
let host = crate::runtime::plugin_loader_unified::get_global_plugin_host();
let host = host.read().unwrap();
@ -107,11 +141,18 @@ pub fn verify_from_env() -> Result<(), String> {
}
}
if failures.is_empty() { return Ok(()); }
if failures.is_empty() {
return Ok(());
}
let msg = format!(
"Provider verify failed ({}): missing methods: {}",
mode,
failures.join(", ")
);
if mode == "warn" { eprintln!("[provider-verify][warn] {}", msg); Ok(()) } else { Err(msg) }
if mode == "warn" {
eprintln!("[provider-verify][warn] {}", msg);
Ok(())
} else {
Err(msg)
}
}