perf: 收紧字符串借用语义消除跨分配点 UB

This commit is contained in:
2026-08-13 16:24:00 +08:00
parent 18a0732a91
commit b7e9d18220
7 changed files with 30 additions and 12 deletions
+1 -1
View File
@@ -250,7 +250,7 @@ fn to_bigint<H: VmHost>(vm: &mut H, val: JsValue) -> Result<JsValue, JsValue> {
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),
+4 -3
View File
@@ -308,9 +308,10 @@ fn next_array_like<H: VmHost>(
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());
+4 -3
View File
@@ -192,7 +192,7 @@ pub fn module_data<H: VmHost>(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<H: VmHost>(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"),
};
+13 -3
View File
@@ -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()
}
+6
View File
@@ -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)
}
@@ -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}")
}
+1 -1
View File
@@ -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}")
}