mirror of
https://github.com/RustPython/RustPython.git
synced 2026-10-28 18:59:04 +08:00
Preserve lone surrogates across WASM conversions (#8508)
Assisted-by: Codex:GPT-5
This commit is contained in:
1 parent
1d6c6aece0
commit
00461a094c
3 files changed
+86
-28
No files matched your search
@@ -63,7 +63,10 @@ impl serde::Serialize for PyObjectSerializer<'_> {
|
||||
seq.end()
|
||||
};
|
||||
if let Some(s) = self.pyobject.downcast_ref::<PyStr>() {
|
||||
serializer.serialize_str(s.as_ref())
|
||||
serializer.serialize_str(
|
||||
s.to_str()
|
||||
.ok_or_else(|| serde::ser::Error::custom("str contains surrogates"))?,
|
||||
)
|
||||
} else if self.pyobject.fast_isinstance(self.vm.ctx.types.float_type) {
|
||||
serializer.serialize_f64(float::get_value(self.pyobject))
|
||||
} else if self.pyobject.fast_isinstance(self.vm.ctx.types.bool_type) {
|
||||
|
||||
+73
-21
@@ -2,10 +2,13 @@
|
||||
|
||||
use crate::js_module;
|
||||
use crate::vm_class::{WASMVirtualMachine, stored_vm_from_wasm};
|
||||
use js_sys::{Array, ArrayBuffer, Object, Promise, Reflect, SyntaxError, Uint8Array};
|
||||
use js_sys::{
|
||||
Array, ArrayBuffer, JsString, Map, Object, Promise, Reflect, SyntaxError, Uint8Array,
|
||||
};
|
||||
use rustpython_common::wtf8::{Wtf8, Wtf8Buf};
|
||||
use rustpython_vm::{
|
||||
AsObject, Py, PyObjectRef, PyPayload, PyResult, TryFromBorrowedObject, VirtualMachine,
|
||||
builtins::{PyBaseException, PyBaseExceptionRef},
|
||||
builtins::{PyBaseException, PyBaseExceptionRef, PyDict, PyList, PyStr, PyTuple},
|
||||
compiler::{CompileError, ParseError, parser::LexicalErrorType, parser::ParseErrorType},
|
||||
exceptions,
|
||||
function::{ArgBytesLike, FuncArgs},
|
||||
@@ -13,6 +16,26 @@ use rustpython_vm::{
|
||||
};
|
||||
use wasm_bindgen::{JsCast, closure::Closure, prelude::*};
|
||||
|
||||
pub(crate) fn js_string_to_wtf8(value: &JsString) -> Wtf8Buf {
|
||||
Wtf8Buf::from_wide(&value.iter().collect::<Vec<_>>())
|
||||
}
|
||||
|
||||
fn wtf8_to_js_string(value: &Wtf8) -> JsString {
|
||||
const CHUNK_SIZE: usize = 8192;
|
||||
|
||||
if let Ok(value) = value.as_str() {
|
||||
return value.into();
|
||||
}
|
||||
|
||||
value
|
||||
.encode_wide()
|
||||
.collect::<Vec<_>>()
|
||||
.chunks(CHUNK_SIZE)
|
||||
.map(JsString::from_char_code)
|
||||
.collect::<Array>()
|
||||
.join("")
|
||||
}
|
||||
|
||||
#[wasm_bindgen(inline_js = r"
|
||||
export class PyError extends Error {
|
||||
constructor(info) {
|
||||
@@ -119,12 +142,9 @@ pub fn py_to_js(vm: &VirtualMachine, py_obj: PyObjectRef) -> JsValue {
|
||||
if let Some(ref kwargs) = kwargs {
|
||||
for pair in object_entries(kwargs) {
|
||||
let (key, val) = pair?;
|
||||
py_func_args.kwargs.insert(
|
||||
// JS strings coming in are UTF-16; go through Rust `String`
|
||||
// (kwargs keys are now WTF-8, so convert String -> Wtf8Buf).
|
||||
String::from(js_sys::JsString::from(key)).into(),
|
||||
js_to_py(vm, val),
|
||||
);
|
||||
py_func_args
|
||||
.kwargs
|
||||
.insert(js_string_to_wtf8(&key.into()), js_to_py(vm, val));
|
||||
}
|
||||
}
|
||||
let result = py_obj.call(py_func_args, vm);
|
||||
@@ -151,17 +171,44 @@ pub fn py_to_js(vm: &VirtualMachine, py_obj: PyObjectRef) -> JsValue {
|
||||
}
|
||||
|
||||
if let Ok(bytes) = ArgBytesLike::try_from_borrowed_object(vm, &py_obj) {
|
||||
bytes.with_ref(|bytes| unsafe {
|
||||
return bytes.with_ref(|bytes| unsafe {
|
||||
// `Uint8Array::view` is an `unsafe fn` because it provides
|
||||
// a direct view into the WASM linear memory; if you were to allocate
|
||||
// something with Rust that view would probably become invalid. It's safe
|
||||
// because we then copy the array using `Uint8Array::slice`.
|
||||
let view = Uint8Array::view(bytes);
|
||||
view.slice(0, bytes.len() as u32).into()
|
||||
})
|
||||
});
|
||||
}
|
||||
py_serde_to_js(vm, &py_obj).unwrap_or(JsValue::UNDEFINED)
|
||||
}
|
||||
|
||||
fn py_serde_to_js(
|
||||
vm: &VirtualMachine,
|
||||
py_obj: &PyObjectRef,
|
||||
) -> Result<JsValue, serde_wasm_bindgen::Error> {
|
||||
if let Some(value) = py_obj.downcast_ref::<PyStr>() {
|
||||
Ok(wtf8_to_js_string(value.as_wtf8()).into())
|
||||
} else if let Some(value) = py_obj.downcast_ref::<PyList>() {
|
||||
let array = Array::new();
|
||||
for item in value.borrow_vec().iter() {
|
||||
array.push(&py_serde_to_js(vm, item)?);
|
||||
}
|
||||
Ok(array.into())
|
||||
} else if let Some(value) = py_obj.downcast_ref::<PyTuple>() {
|
||||
let array = Array::new();
|
||||
for item in value {
|
||||
array.push(&py_serde_to_js(vm, item)?);
|
||||
}
|
||||
Ok(array.into())
|
||||
} else if let Some(value) = py_obj.downcast_ref::<PyDict>() {
|
||||
let map = Map::new();
|
||||
for (key, value) in value {
|
||||
map.set(&py_serde_to_js(vm, &key)?, &py_serde_to_js(vm, &value)?);
|
||||
}
|
||||
Ok(map.into())
|
||||
} else {
|
||||
py_serde::serialize(vm, &py_obj, &serde_wasm_bindgen::Serializer::new())
|
||||
.unwrap_or(JsValue::UNDEFINED)
|
||||
py_serde::serialize(vm, py_obj, &serde_wasm_bindgen::Serializer::new())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -199,6 +246,15 @@ pub fn js_to_py(vm: &VirtualMachine, js_val: JsValue) -> PyObjectRef {
|
||||
.map(|val| js_to_py(vm, val.expect("Iteration over array failed")))
|
||||
.collect();
|
||||
vm.ctx.new_list(elems).into()
|
||||
} else if let Some(map) = js_val.dyn_ref::<Map>() {
|
||||
let dict = vm.ctx.new_dict();
|
||||
for entry in map.entries() {
|
||||
let entry = Array::from(&entry.expect("Iteration over map failed"));
|
||||
let key = js_to_py(vm, entry.get(0));
|
||||
dict.set_item(&*key, js_to_py(vm, entry.get(1)), vm)
|
||||
.unwrap();
|
||||
}
|
||||
dict.into()
|
||||
} else if ArrayBuffer::is_view(&js_val) || js_val.is_instance_of::<ArrayBuffer>() {
|
||||
// unchecked_ref because if it's not an ArrayBuffer it could either be a TypedArray
|
||||
// or a DataView, but they all have a `buffer` property
|
||||
@@ -216,12 +272,8 @@ pub fn js_to_py(vm: &VirtualMachine, js_val: JsValue) -> PyObjectRef {
|
||||
for pair in object_entries(&Object::from(js_val)) {
|
||||
let (key, val) = pair.expect("iteration over object to not fail");
|
||||
let py_val = js_to_py(vm, val);
|
||||
dict.set_item(
|
||||
String::from(js_sys::JsString::from(key)).as_str(),
|
||||
py_val,
|
||||
vm,
|
||||
)
|
||||
.unwrap();
|
||||
dict.set_item(&*js_string_to_wtf8(&key.into()), py_val, vm)
|
||||
.unwrap();
|
||||
}
|
||||
dict.into()
|
||||
}
|
||||
@@ -232,9 +284,7 @@ pub fn js_to_py(vm: &VirtualMachine, js_val: JsValue) -> PyObjectRef {
|
||||
move |args: FuncArgs, vm: &VirtualMachine| -> PyResult {
|
||||
let this = Object::new();
|
||||
for (k, v) in args.kwargs {
|
||||
// WTF-8 -> JS string: lone surrogates in the key become U+FFFD
|
||||
// (wasm-bindgen only accepts Rust `String`); acceptable at this boundary.
|
||||
Reflect::set(&this, &k.to_string().into(), &py_to_js(vm, v))
|
||||
Reflect::set(&this, &wtf8_to_js_string(&k).into(), &py_to_js(vm, v))
|
||||
.expect("property to be settable");
|
||||
}
|
||||
let js_args = args
|
||||
@@ -253,6 +303,8 @@ pub fn js_to_py(vm: &VirtualMachine, js_val: JsValue) -> PyObjectRef {
|
||||
} else if js_val.is_undefined() {
|
||||
// Because `JSON.stringify(undefined)` returns undefined
|
||||
vm.ctx.none()
|
||||
} else if js_val.is_string() {
|
||||
vm.ctx.new_str(js_string_to_wtf8(&js_val.into())).into()
|
||||
} else {
|
||||
py_serde::deserialize(vm, serde_wasm_bindgen::Deserializer::from(js_val))
|
||||
.unwrap_or_else(|_| vm.ctx.none())
|
||||
|
||||
@@ -325,9 +325,12 @@ impl WASMVirtualMachine {
|
||||
if let Some(imports) = imports {
|
||||
for entry in convert::object_entries(&imports) {
|
||||
let (key, value) = entry?;
|
||||
let key: String = Object::from(key).to_string().into();
|
||||
attrs
|
||||
.set_item(key.as_str(), convert::js_to_py(vm, value), vm)
|
||||
.set_item(
|
||||
&*convert::js_string_to_wtf8(&key.into()),
|
||||
convert::js_to_py(vm, value),
|
||||
vm,
|
||||
)
|
||||
.into_js(vm)?;
|
||||
}
|
||||
}
|
||||
@@ -356,10 +359,10 @@ impl WASMVirtualMachine {
|
||||
let py_module = vm.new_module(&name, vm.ctx.new_dict(), None);
|
||||
for entry in convert::object_entries(&module) {
|
||||
let (key, value) = entry?;
|
||||
let key = Object::from(key).to_string();
|
||||
extend_module!(vm, &py_module, {
|
||||
String::from(key) => convert::js_to_py(vm, value),
|
||||
});
|
||||
let key = vm.ctx.new_str(convert::js_string_to_wtf8(&key.into()));
|
||||
py_module
|
||||
.set_attr(&key, convert::js_to_py(vm, value), vm)
|
||||
.into_js(vm)?;
|
||||
}
|
||||
|
||||
let sys_modules = vm.sys_module.get_attr("modules", vm).into_js(vm)?;
|
||||
|
||||
Reference in new issue
Block a user