From e62bc54f08f5c0aa48932b41433df6e650e876cf Mon Sep 17 00:00:00 2001 From: pjh456 <147148383@qq.com> Date: Thu, 13 Aug 2026 16:24:00 +0800 Subject: [PATCH] =?UTF-8?q?perf:=20=E6=94=B6=E7=B4=A7=E5=AD=97=E7=AC=A6?= =?UTF-8?q?=E4=B8=B2=E5=80=9F=E7=94=A8=E8=AF=AD=E4=B9=89=E6=B6=88=E9=99=A4?= =?UTF-8?q?=E8=B7=A8=E5=88=86=E9=85=8D=E7=82=B9=20UB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/oxide_builtins/src/bigint.rs | 2 +- crates/oxide_builtins/src/iterator.rs | 7 ++++--- crates/oxide_builtins/src/module.rs | 7 ++++--- crates/oxide_runtime_api/src/lib.rs | 16 +++++++++++++--- crates/oxide_vm/src/vm.rs | 6 ++++++ crates/oxide_vm/tests/function_length_tests.rs | 2 +- crates/oxide_vm/tests/rest_params_tests.rs | 2 +- 7 files changed, 30 insertions(+), 12 deletions(-) diff --git a/crates/oxide_builtins/src/bigint.rs b/crates/oxide_builtins/src/bigint.rs index 6fe41a8..1df5fe0 100644 --- a/crates/oxide_builtins/src/bigint.rs +++ b/crates/oxide_builtins/src/bigint.rs @@ -250,7 +250,7 @@ fn to_bigint(vm: &mut H, val: JsValue) -> Result { return Err(crate::error::create_type_error(vm, "Cannot convert a Number value to a BigInt")); } if val.is_string() { - let s = unsafe { oxide_runtime_api::string_data(val) }.to_string(); + let s = oxide_runtime_api::to_string(val); return match string_to_bigint(vm, &s) { Ok(v) => Ok(v), Err(e) => Err(e), diff --git a/crates/oxide_builtins/src/iterator.rs b/crates/oxide_builtins/src/iterator.rs index bcc02c9..67ddc6a 100644 --- a/crates/oxide_builtins/src/iterator.rs +++ b/crates/oxide_builtins/src/iterator.rs @@ -308,9 +308,10 @@ fn next_array_like( let index = current_index(vm, wrapper, index_si); let byteoff_si = vm.kernel_core().perm_interner().intern(BYTEOFF_PROP).0; let byteoff = current_index(vm, wrapper, byteoff_si); - let source = unsafe { &*inner.as_string_ptr() }.as_str(); - let rest = &source[byteoff..]; - if let Some(ch) = rest.chars().next() { + // 源串裸指针借用压缩到单个表达式:ch 是 Copy 的 char,不携带借用, + // 之后对 VM 状态的可变访问不再与源串借用共存。 + let ch = unsafe { &*inner.as_string_ptr() }.as_str()[byteoff..].chars().next(); + if let Some(ch) = ch { vm.set_or_create_prop_value(wrapper, byteoff_si, JsValue::int((byteoff + ch.len_utf8()) as i32)); vm.set_or_create_prop_value(wrapper, index_si, JsValue::int((index + 1) as i32)); let value = vm.new_string(&ch.to_string()); diff --git a/crates/oxide_builtins/src/module.rs b/crates/oxide_builtins/src/module.rs index 1007cf2..8e05cbc 100644 --- a/crates/oxide_builtins/src/module.rs +++ b/crates/oxide_builtins/src/module.rs @@ -192,7 +192,7 @@ pub fn module_data(vm: &mut H, args: &[u8]) -> NativeResult { if !kind_val.is_string() || !content_val.is_string() { return type_error(vm, "__moduleData: kind/content must be strings"); } - let kind = unsafe { oxide_runtime_api::string_data(kind_val) }.to_string(); + let kind = oxide_runtime_api::to_string(kind_val); let default_val = match kind.as_str() { "json" => { // 复用 JSON.parse 的 serde 管线:args[1] 即内容字符串。 @@ -204,8 +204,9 @@ pub fn module_data(vm: &mut H, args: &[u8]) -> NativeResult { } } "text" => { - let text = unsafe { oxide_runtime_api::string_data(content_val) }; - vm.new_string(text) + // 经借用路径拷贝出 owned String 后再入堆,避免借用跨过 new_string 分配点。 + let text = vm.string_ref(content_val).to_string(); + vm.new_string_owned(text) } _ => return type_error(vm, "__moduleData: unsupported data kind"), }; diff --git a/crates/oxide_runtime_api/src/lib.rs b/crates/oxide_runtime_api/src/lib.rs index be72135..b700e77 100644 --- a/crates/oxide_runtime_api/src/lib.rs +++ b/crates/oxide_runtime_api/src/lib.rs @@ -77,6 +77,13 @@ pub trait VmHost { fn new_string(&mut self, s: &str) -> JsValue; /// move 接收 `String` 创建会话字符串,避免一次整串克隆。 fn new_string_owned(&mut self, s: String) -> JsValue; + /// 借出字符串值的文本内容,生命周期绑定到 `&self` 借用。 + /// + /// perm 字符串由内核持有、永不释放;session 字符串只在 `&mut self` 路径 + /// (`new_string`/`new_string_owned`/`maybe_collect_session_gc`)释放,`&self` + /// 借用与 `&mut` 互斥由编译器强制,借用期内该字符串不会回收。实现内部 + /// `unsafe` 解引用,调用方须保证 `val` 为字符串值。 + fn string_ref(&self, val: JsValue) -> &str; /// 分配 BigInt 值(num_bigint::BigInt box 登记到 VM,返回携带指针的 `JsValue`)。 fn new_bigint(&mut self, v: num_bigint::BigInt) -> JsValue; /// 读取 BigInt 值;调用方须保证 `val.is_bigint()`。 @@ -149,12 +156,15 @@ pub fn format_error_message(name: &str, msg: &str) -> String { } } -/// 借出字符串值的文本内容。 +/// 借出字符串值的文本内容,生命周期为 `'static`。仅限同一函数内即时消费。 /// /// # Safety -/// `val` 必须是字符串 `JsValue`,且其 `JsString` 指针存活。 +/// 调用方必须保证:从取用返回值到最后一次使用之间,不发生任何可能释放该字符串 +/// 的操作(分配、GC、`full_reset`)——session 字符串在执行期 GC 或 reset 时会被 +/// 释放,跨分配点持有引用即悬垂。需要跨分配点消费的场景改用 [`VmHost::string_ref`] +/// 或公开的 [`to_string`] owned 路径。 #[inline] -pub unsafe fn string_data(val: JsValue) -> &'static str { +pub(crate) unsafe fn string_data(val: JsValue) -> &'static str { (*val.as_string_ptr()).as_str() } diff --git a/crates/oxide_vm/src/vm.rs b/crates/oxide_vm/src/vm.rs index fa58d04..ab1596b 100644 --- a/crates/oxide_vm/src/vm.rs +++ b/crates/oxide_vm/src/vm.rs @@ -1808,6 +1808,12 @@ impl oxide_runtime_api::VmHost for Vm { fn new_string_owned(&mut self, s: String) -> JsValue { Vm::new_string_owned(self, s) } + fn string_ref(&self, val: JsValue) -> &str { + // SAFETY: 调用方保证 val 为字符串值。perm 串由内核持有永不释放;session + // 串仅经 &mut self 路径(new_string/new_string_owned/GC)释放,此处 &self + // 借用期间编译器强制不存在 &mut 存续,字符串不会在借用期内回收。 + unsafe { (*val.as_string_ptr()).as_str() } + } fn new_bigint(&mut self, v: num_bigint::BigInt) -> JsValue { Vm::new_bigint(self, v) } diff --git a/crates/oxide_vm/tests/function_length_tests.rs b/crates/oxide_vm/tests/function_length_tests.rs index 6b7a5d4..39215de 100644 --- a/crates/oxide_vm/tests/function_length_tests.rs +++ b/crates/oxide_vm/tests/function_length_tests.rs @@ -22,7 +22,7 @@ fn run(module: &CompiledModule) -> JsValue { fn eval_str(source: &str) -> String { let v = eval(source); if v.is_string() { - unsafe { oxide_runtime_api::string_data(v) }.to_string() + oxide_runtime_api::to_string(v) } else { format!("{v}") } diff --git a/crates/oxide_vm/tests/rest_params_tests.rs b/crates/oxide_vm/tests/rest_params_tests.rs index 20f82b7..26822d8 100644 --- a/crates/oxide_vm/tests/rest_params_tests.rs +++ b/crates/oxide_vm/tests/rest_params_tests.rs @@ -28,7 +28,7 @@ fn eval_int(source: &str) -> i32 { fn eval_str(source: &str) -> String { let v = eval(source); if v.is_string() { - unsafe { oxide_runtime_api::string_data(v) }.to_string() + oxide_runtime_api::to_string(v) } else { format!("{v}") }