feat: 绑定全局 escape/unescape 与缺失全局桩函数
This commit is contained in:
@@ -200,6 +200,7 @@ pub struct BuiltinWorld {
|
|||||||
pub sym_search: P<JsObject>,
|
pub sym_search: P<JsObject>,
|
||||||
pub sym_split: P<JsObject>,
|
pub sym_split: P<JsObject>,
|
||||||
pub sym_iterator: P<JsObject>,
|
pub sym_iterator: P<JsObject>,
|
||||||
|
pub stub_objects: Vec<P<JsObject>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn intern_label(string_forge: &StringForge, label: &str) -> u32 {
|
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_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_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 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.
|
/// Overwrite placeholder slots (set up by make_pair) with real values.
|
||||||
/// ctor.vec[0] = constructor.prototype -> proto
|
/// ctor.vec[0] = constructor.prototype -> proto
|
||||||
@@ -380,6 +382,7 @@ impl BuiltinWorld {
|
|||||||
sym_search,
|
sym_search,
|
||||||
sym_split,
|
sym_split,
|
||||||
sym_iterator,
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@ pub mod bind_boolean;
|
|||||||
pub mod bind_date;
|
pub mod bind_date;
|
||||||
pub mod bind_error;
|
pub mod bind_error;
|
||||||
pub mod bind_function;
|
pub mod bind_function;
|
||||||
|
pub mod bind_global;
|
||||||
pub mod bind_json;
|
pub mod bind_json;
|
||||||
pub mod bind_map;
|
pub mod bind_map;
|
||||||
pub mod bind_math;
|
pub mod bind_math;
|
||||||
@@ -11,6 +12,7 @@ pub mod bind_object;
|
|||||||
pub mod bind_regexp;
|
pub mod bind_regexp;
|
||||||
pub mod bind_set;
|
pub mod bind_set;
|
||||||
pub mod bind_string;
|
pub mod bind_string;
|
||||||
|
pub mod bind_stubs;
|
||||||
pub mod bind_symbol;
|
pub mod bind_symbol;
|
||||||
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
@@ -77,7 +79,7 @@ pub(crate) fn bind_global_value(core: &Arc<KernelCore>, global: &mut JsObject, n
|
|||||||
global.bump_generation();
|
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_ptr = session.global_object().as_ptr() as *mut oxide_types::object::JsObject;
|
||||||
let global = unsafe { &mut *global_ptr };
|
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_function::bind_function(core, session, global);
|
||||||
bind_regexp::bind_regexp(core, session, global);
|
bind_regexp::bind_regexp(core, session, global);
|
||||||
bind_symbol::bind_symbol(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));
|
bind_global_value(core, global, "globalThis", JsValue::from_js_object(global_ptr));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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> {
|
fn escape_string(input: &str) -> String {
|
||||||
Ok(())
|
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");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,8 +16,8 @@ use oxide_types::value::JsValue;
|
|||||||
impl Vm {
|
impl Vm {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
let core = KernelCore::new(KernelConfig::minimal());
|
let core = KernelCore::new(KernelConfig::minimal());
|
||||||
let session = KernelSession::new(&core);
|
let mut session = KernelSession::new(&core);
|
||||||
bindings::init_kernel_builtins(&core, &session);
|
bindings::init_kernel_builtins(&core, &mut session);
|
||||||
let obj_proto = P::clone(&session.builtin_world().object_proto);
|
let obj_proto = P::clone(&session.builtin_world().object_proto);
|
||||||
Self {
|
Self {
|
||||||
regs: [JsValue::undefined(); 256],
|
regs: [JsValue::undefined(); 256],
|
||||||
@@ -51,8 +51,8 @@ impl Vm {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn with_kernel_core(core: Arc<KernelCore>) -> Self {
|
pub fn with_kernel_core(core: Arc<KernelCore>) -> Self {
|
||||||
let session = KernelSession::new(&core);
|
let mut session = KernelSession::new(&core);
|
||||||
bindings::init_kernel_builtins(&core, &session);
|
bindings::init_kernel_builtins(&core, &mut session);
|
||||||
let obj_proto = P::clone(&session.builtin_world().object_proto);
|
let obj_proto = P::clone(&session.builtin_world().object_proto);
|
||||||
Self {
|
Self {
|
||||||
regs: [JsValue::undefined(); 256],
|
regs: [JsValue::undefined(); 256],
|
||||||
@@ -86,8 +86,8 @@ impl Vm {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn full_reset(&mut self) {
|
pub fn full_reset(&mut self) {
|
||||||
let new_session = KernelSession::new(&self.kernel_core);
|
let mut new_session = KernelSession::new(&self.kernel_core);
|
||||||
bindings::init_kernel_builtins(&self.kernel_core, &new_session);
|
bindings::init_kernel_builtins(&self.kernel_core, &mut new_session);
|
||||||
self.object_prototype = P::clone(&new_session.builtin_world().object_proto);
|
self.object_prototype = P::clone(&new_session.builtin_world().object_proto);
|
||||||
self.session = new_session;
|
self.session = new_session;
|
||||||
self.clear_execution_state();
|
self.clear_execution_state();
|
||||||
|
|||||||
@@ -37,7 +37,10 @@ fn eval_val(source: &str) -> (Vm, Result<JsValue, String>) {
|
|||||||
|
|
||||||
fn to_str(vm: &Vm, val: JsValue) -> String {
|
fn to_str(vm: &Vm, val: JsValue) -> String {
|
||||||
if val.is_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 {
|
} else {
|
||||||
format!("{}", val)
|
format!("{}", val)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user