fix(bid-ffi): Fix HostVtable lifetime issue causing segfault

🚨 Critical memory safety fix:
- HostVtable was created on stack and destroyed after init
- Plugin stored reference to destroyed memory → NULL pointer access
- Changed to static LazyLock storage for lifetime safety

 Results:
- Segfault completely eliminated
- Plugin logging now works properly
- Type info system confirmed working
- Full E2E FileBox plugin operation successful

🔧 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Moe Charm
2025-08-18 14:10:41 +09:00
parent 7fc3adef66
commit c6c3c8e2f9
12 changed files with 607 additions and 262 deletions

View File

@ -145,56 +145,136 @@ impl NyashInterpreter {
}
}
/// 引数をTLVエンコードメソッドに応じて特殊処理
/// 引数をTLVエンコード型情報に基づく美しい実装!
fn encode_arguments_to_tlv(&mut self, arguments: &[ASTNode], method_name: &str) -> Result<Vec<u8>, RuntimeError> {
use crate::bid::tlv::TlvEncoder;
use crate::bid::registry;
let mut encoder = TlvEncoder::new();
// 特殊ケース: readメソッドは引数がなくても、サイズ引数が必要
if method_name == "read" && arguments.is_empty() {
// デフォルトで8192バイト読み取り
encoder.encode_i32(8192)
.map_err(|e| RuntimeError::InvalidOperation {
message: format!("TLV i32 encoding failed: {:?}", e),
})?;
// 型情報を取得FileBoxのみ対応、後で拡張
let type_info = registry::global()
.and_then(|reg| reg.get_method_type_info("FileBox", method_name));
// 型情報がある場合は、それに従って変換
if let Some(type_info) = type_info {
eprintln!("✨ Using type info for method '{}'", method_name);
// 引数の数をチェック
if arguments.len() != type_info.args.len() {
return Err(RuntimeError::InvalidOperation {
message: format!("{} expects {} arguments, got {}",
method_name, type_info.args.len(), arguments.len()),
});
}
// 各引数を型情報に従ってエンコード
for (i, (arg, mapping)) in arguments.iter().zip(&type_info.args).enumerate() {
eprintln!(" 🔄 Arg[{}]: {} -> {} conversion", i, mapping.from, mapping.to);
let value = self.execute_expression(arg)?;
self.encode_value_with_mapping(&mut encoder, value, mapping)?;
}
} else {
// 通常の引数エンコード
// 型情報がない場合は、従来のデフォルト動作
eprintln!("⚠️ No type info for method '{}', using default encoding", method_name);
for arg in arguments {
let value = self.execute_expression(arg)?;
// 型に応じてエンコード
if let Some(str_box) = value.as_any().downcast_ref::<StringBox>() {
// 🔍 writeメソッドなど、文字列データはBytesとしてエンコード
// プラグインは通常、文字列データをBytesタグ7で期待する
encoder.encode_bytes(str_box.value.as_bytes())
.map_err(|e| RuntimeError::InvalidOperation {
message: format!("TLV bytes encoding failed: {:?}", e),
})?;
} else if let Some(int_box) = value.as_any().downcast_ref::<crate::box_trait::IntegerBox>() {
encoder.encode_i32(int_box.value as i32)
.map_err(|e| RuntimeError::InvalidOperation {
message: format!("TLV integer encoding failed: {:?}", e),
})?;
} else if let Some(bool_box) = value.as_any().downcast_ref::<crate::box_trait::BoolBox>() {
encoder.encode_bool(bool_box.value)
.map_err(|e| RuntimeError::InvalidOperation {
message: format!("TLV bool encoding failed: {:?}", e),
})?;
} else {
// デフォルト: バイトデータとして扱う
let str_val = value.to_string_box().value;
encoder.encode_bytes(str_val.as_bytes())
.map_err(|e| RuntimeError::InvalidOperation {
message: format!("TLV default bytes encoding failed: {:?}", e),
})?;
}
self.encode_value_default(&mut encoder, value)?;
}
}
Ok(encoder.finish())
}
/// 型マッピングに基づいて値をエンコード(美しい!)
fn encode_value_with_mapping(
&self,
encoder: &mut crate::bid::tlv::TlvEncoder,
value: Box<dyn NyashBox>,
mapping: &crate::bid::ArgTypeMapping
) -> Result<(), RuntimeError> {
// determine_bid_tag()を使って適切なタグを決定
let tag = mapping.determine_bid_tag()
.ok_or_else(|| RuntimeError::InvalidOperation {
message: format!("Unsupported type mapping: {} -> {}", mapping.from, mapping.to),
})?;
// タグに応じてエンコード
match tag {
crate::bid::BidTag::String => {
let str_val = value.to_string_box().value;
encoder.encode_string(&str_val)
.map_err(|e| RuntimeError::InvalidOperation {
message: format!("TLV string encoding failed: {:?}", e),
})
}
crate::bid::BidTag::Bytes => {
let str_val = value.to_string_box().value;
encoder.encode_bytes(str_val.as_bytes())
.map_err(|e| RuntimeError::InvalidOperation {
message: format!("TLV bytes encoding failed: {:?}", e),
})
}
crate::bid::BidTag::I32 => {
if let Some(int_box) = value.as_any().downcast_ref::<crate::box_trait::IntegerBox>() {
encoder.encode_i32(int_box.value as i32)
.map_err(|e| RuntimeError::InvalidOperation {
message: format!("TLV i32 encoding failed: {:?}", e),
})
} else {
Err(RuntimeError::TypeError {
message: format!("Expected integer for {} -> i32 conversion", mapping.from),
})
}
}
crate::bid::BidTag::Bool => {
if let Some(bool_box) = value.as_any().downcast_ref::<crate::box_trait::BoolBox>() {
encoder.encode_bool(bool_box.value)
.map_err(|e| RuntimeError::InvalidOperation {
message: format!("TLV bool encoding failed: {:?}", e),
})
} else {
Err(RuntimeError::TypeError {
message: format!("Expected bool for {} -> bool conversion", mapping.from),
})
}
}
_ => Err(RuntimeError::InvalidOperation {
message: format!("Unsupported BID tag: {:?}", tag),
})
}
}
/// デフォルトエンコード(型情報がない場合のフォールバック)
fn encode_value_default(
&self,
encoder: &mut crate::bid::tlv::TlvEncoder,
value: Box<dyn NyashBox>
) -> Result<(), RuntimeError> {
if let Some(str_box) = value.as_any().downcast_ref::<StringBox>() {
encoder.encode_bytes(str_box.value.as_bytes())
.map_err(|e| RuntimeError::InvalidOperation {
message: format!("TLV bytes encoding failed: {:?}", e),
})
} else if let Some(int_box) = value.as_any().downcast_ref::<crate::box_trait::IntegerBox>() {
encoder.encode_i32(int_box.value as i32)
.map_err(|e| RuntimeError::InvalidOperation {
message: format!("TLV integer encoding failed: {:?}", e),
})
} else if let Some(bool_box) = value.as_any().downcast_ref::<crate::box_trait::BoolBox>() {
encoder.encode_bool(bool_box.value)
.map_err(|e| RuntimeError::InvalidOperation {
message: format!("TLV bool encoding failed: {:?}", e),
})
} else {
let str_val = value.to_string_box().value;
encoder.encode_bytes(str_val.as_bytes())
.map_err(|e| RuntimeError::InvalidOperation {
message: format!("TLV default bytes encoding failed: {:?}", e),
})
}
}
/// TLVレスポンスをNyashBoxに変換
fn decode_tlv_to_nyash_box(&self, response_bytes: &[u8], method_name: &str) -> Result<Box<dyn NyashBox>, RuntimeError> {
use crate::bid::tlv::TlvDecoder;