feat: 绑定全局 escape/unescape 与缺失全局桩函数

This commit is contained in:
2026-06-14 17:21:38 +08:00
parent 51d31fe6e0
commit 8a8a6e055c
7 changed files with 203 additions and 12 deletions
+3
View File
@@ -200,6 +200,7 @@ pub struct BuiltinWorld {
pub sym_search: P<JsObject>,
pub sym_split: P<JsObject>,
pub sym_iterator: P<JsObject>,
pub stub_objects: Vec<P<JsObject>>,
}
fn intern_label(string_forge: &StringForge, label: &str) -> u32 {
@@ -290,6 +291,7 @@ impl BuiltinWorld {
let sym_search = P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null()));
let sym_split = P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null()));
let sym_iterator = P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null()));
let stub_objects = Vec::new();
/// Overwrite placeholder slots (set up by make_pair) with real values.
/// ctor.vec[0] = constructor.prototype -> proto
@@ -380,6 +382,7 @@ impl BuiltinWorld {
sym_search,
sym_split,
sym_iterator,
stub_objects,
}
}
@@ -0,0 +1,18 @@
use std::sync::Arc;
use oxide_kernel::kernel::{KernelCore, KernelSession};
use oxide_types::object::JsObject;
use crate::bindings::apply_binding_table;
pub fn bind_global(core: &Arc<KernelCore>, session: &KernelSession, global: &mut JsObject) {
apply_binding_table(
session.builtin_world(),
global,
core,
&[
("escape", crate::builtins::global::js_escape as *const (), 1),
("unescape", crate::builtins::global::js_unescape as *const (), 1),
],
);
}
@@ -0,0 +1,39 @@
use std::sync::Arc;
use oxide_kernel::kernel::{KernelCore, KernelSession};
use oxide_kernel::shape_forge::EMPTY_SHAPE_ID;
use oxide_types::mem::P;
use oxide_types::object::JsObject;
use oxide_types::value::JsValue;
use crate::bindings::{bind_global_value, configure_native_constructor};
const STUBS: [(&str, *const (), u8); 8] = [
("Proxy", crate::builtins::stubs::proxy_stub as *const (), 2),
("BigInt", crate::builtins::stubs::bigint_stub as *const (), 1),
("WeakMap", crate::builtins::stubs::weakmap_stub as *const (), 0),
("WeakSet", crate::builtins::stubs::weakset_stub as *const (), 0),
("WeakRef", crate::builtins::stubs::weakref_stub as *const (), 1),
("FinalizationRegistry", crate::builtins::stubs::finalization_registry_stub as *const (), 1),
("SharedArrayBuffer", crate::builtins::stubs::shared_array_buffer_stub as *const (), 1),
("Atomics", crate::builtins::stubs::atomics_stub as *const (), 0),
];
pub fn bind_stubs(core: &Arc<KernelCore>, session: &mut KernelSession, global: &mut JsObject) {
let builtin_world = Arc::get_mut(&mut session.builtin_world)
.expect("BuiltinWorld must be uniquely owned during init_kernel_builtins");
for (name, native_fn, arg_count) in STUBS {
let mut stub = JsObject::new_empty(
EMPTY_SHAPE_ID,
JsValue::from_js_object(builtin_world.function_proto.as_ptr() as *mut JsObject),
);
stub.set_function(true);
configure_native_constructor(&mut stub, native_fn, arg_count);
let stub = P::new(stub);
let stub_ptr = stub.as_ptr() as *mut JsObject;
bind_global_value(core, global, name, JsValue::from_js_object(stub_ptr));
builtin_world.stub_objects.push(stub);
}
}
+5 -1
View File
@@ -3,6 +3,7 @@ pub mod bind_boolean;
pub mod bind_date;
pub mod bind_error;
pub mod bind_function;
pub mod bind_global;
pub mod bind_json;
pub mod bind_map;
pub mod bind_math;
@@ -11,6 +12,7 @@ pub mod bind_object;
pub mod bind_regexp;
pub mod bind_set;
pub mod bind_string;
pub mod bind_stubs;
pub mod bind_symbol;
use std::sync::Arc;
@@ -77,7 +79,7 @@ pub(crate) fn bind_global_value(core: &Arc<KernelCore>, global: &mut JsObject, n
global.bump_generation();
}
pub fn init_kernel_builtins(core: &Arc<KernelCore>, session: &KernelSession) {
pub fn init_kernel_builtins(core: &Arc<KernelCore>, session: &mut KernelSession) {
let global_ptr = session.global_object().as_ptr() as *mut oxide_types::object::JsObject;
let global = unsafe { &mut *global_ptr };
@@ -95,6 +97,8 @@ pub fn init_kernel_builtins(core: &Arc<KernelCore>, session: &KernelSession) {
bind_function::bind_function(core, session, global);
bind_regexp::bind_regexp(core, session, global);
bind_symbol::bind_symbol(core, session, global);
bind_global::bind_global(core, session, global);
bind_stubs::bind_stubs(core, session, global);
bind_global_value(core, global, "globalThis", JsValue::from_js_object(global_ptr));
}
+128 -4
View File
@@ -1,7 +1,131 @@
use std::sync::Arc;
use crate::coercion;
use crate::native::NativeResult;
use crate::vm::Vm;
use oxide_kernel::kernel::KernelCore;
const ESCAPE_SAFE: &str = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789@*_+-./";
pub fn register_global_properties(_kernel: &Arc<KernelCore>, _bump: &bumpalo::Bump) -> Result<(), String> {
Ok(())
fn escape_string(input: &str) -> String {
let mut out = String::with_capacity(input.len());
for ch in input.chars() {
if ESCAPE_SAFE.contains(ch) {
out.push(ch);
continue;
}
let code = ch as u32;
if code <= 0xFF {
out.push('%');
out.push_str(&format!("{code:02X}"));
continue;
}
let mut buf = [0u16; 2];
for unit in ch.encode_utf16(&mut buf).iter() {
out.push_str(&format!("%u{:04X}", *unit));
}
}
out
}
fn parse_hex_u8(slice: &[u8]) -> Option<u8> {
std::str::from_utf8(slice).ok().and_then(|s| u8::from_str_radix(s, 16).ok())
}
fn parse_hex_u16(slice: &[u8]) -> Option<u16> {
std::str::from_utf8(slice).ok().and_then(|s| u16::from_str_radix(s, 16).ok())
}
fn unescape_string(input: &str) -> String {
let bytes = input.as_bytes();
let mut out = String::with_capacity(input.len());
let mut i = 0;
while i < bytes.len() {
if bytes[i] == b'%' {
if i + 6 <= bytes.len() && bytes[i + 1] == b'u' {
if let Some(unit) = parse_hex_u16(&bytes[i + 2..i + 6]) {
if (0xD800..=0xDBFF).contains(&unit)
&& i + 12 <= bytes.len()
&& bytes[i + 6] == b'%'
&& bytes[i + 7] == b'u'
{
if let Some(low) = parse_hex_u16(&bytes[i + 8..i + 12]) {
if (0xDC00..=0xDFFF).contains(&low) {
let pair = [unit, low];
let decoded = char::decode_utf16(pair).next();
match decoded {
Some(Ok(ch)) => out.push(ch),
_ => {
out.push_str(&input[i..i + 12]);
}
}
i += 12;
continue;
}
}
}
if let Some(ch) = char::from_u32(unit as u32) {
out.push(ch);
} else {
out.push_str(&input[i..i + 6]);
}
i += 6;
continue;
}
} else if i + 3 <= bytes.len() {
if let Some(value) = parse_hex_u8(&bytes[i + 1..i + 3]) {
out.push(value as char);
i += 3;
continue;
}
}
}
let ch = input[i..].chars().next().expect("valid utf-8 char boundary");
out.push(ch);
i += ch.len_utf8();
}
out
}
pub fn js_escape(vm: &mut Vm, args: &[u8]) -> NativeResult {
let input = if args.len() > 1 {
coercion::to_string(vm.kernel_core().string_forge().as_ref(), vm.reg(args[1]))
} else {
"undefined".to_string()
};
NativeResult::Ok(vm.intern(&escape_string(&input)))
}
pub fn js_unescape(vm: &mut Vm, args: &[u8]) -> NativeResult {
let input = if args.len() > 1 {
coercion::to_string(vm.kernel_core().string_forge().as_ref(), vm.reg(args[1]))
} else {
"undefined".to_string()
};
NativeResult::Ok(vm.intern(&unescape_string(&input)))
}
#[cfg(test)]
mod tests {
use super::{escape_string, unescape_string};
#[test]
fn escape_encodes_spaces_and_unicode() {
assert_eq!(escape_string("hello world"), "hello%20world");
assert_eq!(escape_string("AΩ你"), "A%u03A9%u4F60");
}
#[test]
fn unescape_decodes_percent_sequences() {
assert_eq!(unescape_string("hello%20world"), "hello world");
assert_eq!(unescape_string("%u03A9%u4F60"), "Ω你");
}
#[test]
fn unescape_keeps_invalid_sequences() {
assert_eq!(unescape_string("%uXYZ1%2G"), "%uXYZ1%2G");
}
}
+6 -6
View File
@@ -16,8 +16,8 @@ use oxide_types::value::JsValue;
impl Vm {
pub fn new() -> Self {
let core = KernelCore::new(KernelConfig::minimal());
let session = KernelSession::new(&core);
bindings::init_kernel_builtins(&core, &session);
let mut session = KernelSession::new(&core);
bindings::init_kernel_builtins(&core, &mut session);
let obj_proto = P::clone(&session.builtin_world().object_proto);
Self {
regs: [JsValue::undefined(); 256],
@@ -51,8 +51,8 @@ impl Vm {
}
pub fn with_kernel_core(core: Arc<KernelCore>) -> Self {
let session = KernelSession::new(&core);
bindings::init_kernel_builtins(&core, &session);
let mut session = KernelSession::new(&core);
bindings::init_kernel_builtins(&core, &mut session);
let obj_proto = P::clone(&session.builtin_world().object_proto);
Self {
regs: [JsValue::undefined(); 256],
@@ -86,8 +86,8 @@ impl Vm {
}
pub fn full_reset(&mut self) {
let new_session = KernelSession::new(&self.kernel_core);
bindings::init_kernel_builtins(&self.kernel_core, &new_session);
let mut new_session = KernelSession::new(&self.kernel_core);
bindings::init_kernel_builtins(&self.kernel_core, &mut new_session);
self.object_prototype = P::clone(&new_session.builtin_world().object_proto);
self.session = new_session;
self.clear_execution_state();
+4 -1
View File
@@ -37,7 +37,10 @@ fn eval_val(source: &str) -> (Vm, Result<JsValue, String>) {
fn to_str(vm: &Vm, val: JsValue) -> String {
if val.is_string() {
vm.kernel_core().string_forge().lookup(val.as_string_index()).unwrap_or_default()
vm.kernel_core()
.string_forge()
.lookup(val.as_string_index())
.unwrap_or_default()
} else {
format!("{}", val)
}