char/avril

(wip) why'd you have to go and make things so complicaaaaated?

git clone https://git.t4t.associates/char/avril

Charlotte Somadd message channels and staged port and stream transferse7411ff

main
10.9 KiB315 linesraw
1use std::cell::RefCell;
2
3use v8::{ValueDeserializerHelper, ValueSerializerHelper};
4
5struct Clone {
6    classify: v8::Global<v8::Function>,
7    clone_host: v8::Global<v8::Function>,
8    error: v8::Global<v8::Function>,
9    prepare_transfers: Option<v8::Global<v8::Function>>,
10    validate_transfers: Option<v8::Global<v8::Function>>,
11    commit_transfers: Option<v8::Global<v8::Function>>,
12    hosts: RefCell<Vec<v8::Global<v8::Object>>>,
13    shared: RefCell<Vec<v8::SharedRef<v8::BackingStore>>>,
14    modules: RefCell<Vec<v8::CompiledWasmModule>>,
15}
16
17impl Clone {
18    fn fail<'s>(&self, scope: &mut v8::PinScope<'s, '_>, message: &str) {
19        let message = v8::String::new(scope, message).unwrap();
20        self.throw(scope, message);
21    }
22
23    fn throw<'s>(&self, scope: &mut v8::PinScope<'s, '_>, message: v8::Local<'s, v8::String>) {
24        let error = v8::Local::new(scope, &self.error);
25        let receiver = v8::undefined(scope).into();
26        if let Some(exception) = error.call(scope, receiver, &[message.into()]) {
27            scope.throw_exception(exception);
28        }
29    }
30
31    fn kind<'s>(
32        &self,
33        scope: &mut v8::PinScope<'s, '_>,
34        value: v8::Local<'s, v8::Value>,
35    ) -> Option<u32> {
36        let classify = v8::Local::new(scope, &self.classify);
37        let receiver = v8::undefined(scope).into();
38        classify
39            .call(scope, receiver, &[value])?
40            .uint32_value(scope)
41    }
42}
43
44impl v8::ValueSerializerImpl for &Clone {
45    fn throw_data_clone_error<'s>(
46        &self,
47        scope: &mut v8::PinScope<'s, '_>,
48        message: v8::Local<'s, v8::String>,
49    ) {
50        self.throw(scope, message);
51    }
52
53    fn has_custom_host_object(&self, _: &v8::Isolate) -> bool {
54        true
55    }
56
57    fn is_host_object<'s>(
58        &self,
59        scope: &mut v8::PinScope<'s, '_>,
60        object: v8::Local<'s, v8::Object>,
61    ) -> Option<bool> {
62        self.kind(scope, object.into()).map(|kind| kind != 0)
63    }
64
65    fn write_host_object<'s>(
66        &self,
67        scope: &mut v8::PinScope<'s, '_>,
68        object: v8::Local<'s, v8::Object>,
69        serializer: &dyn v8::ValueSerializerHelper,
70    ) -> Option<bool> {
71        let clone_host = v8::Local::new(scope, &self.clone_host);
72        let receiver = v8::undefined(scope).into();
73        let value = clone_host.call(scope, receiver, &[object.into()])?;
74        let Ok(value) = v8::Local::<v8::Object>::try_from(value) else {
75            self.fail(scope, "This object cannot be cloned");
76            return None;
77        };
78        let mut hosts = self.hosts.borrow_mut();
79        let Ok(id) = u32::try_from(hosts.len()) else {
80            drop(hosts);
81            self.fail(scope, "Too many serialized host objects");
82            return None;
83        };
84        hosts.push(v8::Global::new(scope, value));
85        serializer.write_uint32(id);
86        Some(true)
87    }
88
89    fn get_shared_array_buffer_id<'s>(
90        &self,
91        scope: &mut v8::PinScope<'s, '_>,
92        buffer: v8::Local<'s, v8::SharedArrayBuffer>,
93    ) -> Option<u32> {
94        let mut shared = self.shared.borrow_mut();
95        let Ok(id) = u32::try_from(shared.len()) else {
96            drop(shared);
97            self.fail(scope, "Too many serialized shared buffers");
98            return None;
99        };
100        shared.push(buffer.get_backing_store());
101        Some(id)
102    }
103
104    fn get_wasm_module_transfer_id(
105        &self,
106        scope: &mut v8::PinScope<'_, '_>,
107        module: v8::Local<v8::WasmModuleObject>,
108    ) -> Option<u32> {
109        let mut modules = self.modules.borrow_mut();
110        let Ok(id) = u32::try_from(modules.len()) else {
111            drop(modules);
112            self.fail(scope, "Too many serialized WebAssembly modules");
113            return None;
114        };
115        modules.push(module.get_compiled_module());
116        Some(id)
117    }
118}
119
120impl v8::ValueDeserializerImpl for &Clone {
121    fn read_host_object<'s>(
122        &self,
123        scope: &mut v8::PinScope<'s, '_>,
124        deserializer: &dyn v8::ValueDeserializerHelper,
125    ) -> Option<v8::Local<'s, v8::Object>> {
126        let mut id = 0;
127        if !deserializer.read_uint32(&mut id) {
128            return None;
129        }
130        self.hosts
131            .borrow()
132            .get(id as usize)
133            .map(|object| v8::Local::new(scope, object))
134    }
135
136    fn get_shared_array_buffer_from_id<'s>(
137        &self,
138        scope: &mut v8::PinScope<'s, '_>,
139        id: u32,
140    ) -> Option<v8::Local<'s, v8::SharedArrayBuffer>> {
141        self.shared
142            .borrow()
143            .get(id as usize)
144            .map(|store| v8::SharedArrayBuffer::with_backing_store(scope, store))
145    }
146
147    fn get_wasm_module_from_id<'s>(
148        &self,
149        scope: &mut v8::PinScope<'s, '_>,
150        id: u32,
151    ) -> Option<v8::Local<'s, v8::WasmModuleObject>> {
152        v8::WasmModuleObject::from_compiled_module(scope, self.modules.borrow().get(id as usize)?)
153    }
154}
155
156pub(crate) fn clone<'s>(
157    scope: &mut v8::PinScope<'s, '_>,
158    args: v8::FunctionCallbackArguments<'s>,
159    mut result: v8::ReturnValue,
160) {
161    if let Some(value) = run(scope, args) {
162        result.set(value);
163    }
164}
165
166fn run<'s>(
167    scope: &mut v8::PinScope<'s, '_>,
168    args: v8::FunctionCallbackArguments<'s>,
169) -> Option<v8::Local<'s, v8::Value>> {
170    let (Ok(transfer), Ok(classify), Ok(clone_host), Ok(error)) = (
171        v8::Local::<v8::Array>::try_from(args.get(1)),
172        v8::Local::<v8::Function>::try_from(args.get(2)),
173        v8::Local::<v8::Function>::try_from(args.get(3)),
174        v8::Local::<v8::Function>::try_from(args.get(4)),
175    ) else {
176        let message = v8::String::new(
177            scope,
178            "Clone requires a transfer array and policy callbacks",
179        )?;
180        let error = v8::Exception::type_error(scope, message);
181        scope.throw_exception(error);
182        return None;
183    };
184    let hook_values = [args.get(5), args.get(6), args.get(7)];
185    let mut hooks = if hook_values.iter().all(|value| value.is_undefined()) {
186        [None, None, None]
187    } else {
188        let Ok(prepare) = v8::Local::<v8::Function>::try_from(hook_values[0]) else {
189            let error = v8::Exception::type_error(
190                scope,
191                v8::String::new(scope, "Invalid transfer hooks").unwrap(),
192            );
193            scope.throw_exception(error);
194            return None;
195        };
196        let Ok(validate) = v8::Local::<v8::Function>::try_from(hook_values[1]) else {
197            let error = v8::Exception::type_error(
198                scope,
199                v8::String::new(scope, "Invalid transfer hooks").unwrap(),
200            );
201            scope.throw_exception(error);
202            return None;
203        };
204        let Ok(commit) = v8::Local::<v8::Function>::try_from(hook_values[2]) else {
205            let error = v8::Exception::type_error(
206                scope,
207                v8::String::new(scope, "Invalid transfer hooks").unwrap(),
208            );
209            scope.throw_exception(error);
210            return None;
211        };
212        [
213            Some(v8::Global::new(scope, prepare)),
214            Some(v8::Global::new(scope, validate)),
215            Some(v8::Global::new(scope, commit)),
216        ]
217    };
218    let policy = Clone {
219        classify: v8::Global::new(scope, classify),
220        clone_host: v8::Global::new(scope, clone_host),
221        error: v8::Global::new(scope, error),
222        prepare_transfers: hooks[0].take(),
223        validate_transfers: hooks[1].take(),
224        commit_transfers: hooks[2].take(),
225        hosts: RefCell::new(Vec::new()),
226        shared: RefCell::new(Vec::new()),
227        modules: RefCell::new(Vec::new()),
228    };
229    let mut buffers = Vec::new();
230    let seen = v8::Set::new(scope);
231    for index in 0..transfer.length() {
232        let value = transfer.get_index(scope, index)?;
233        if seen.has(scope, value)? {
234            policy.fail(scope, "Transfer list contains a duplicate value");
235            return None;
236        }
237        seen.add(scope, value)?;
238        if let Ok(buffer) = v8::Local::<v8::ArrayBuffer>::try_from(value) {
239            buffers.push(buffer);
240        } else if policy.kind(scope, value)? != 2 {
241            policy.fail(scope, "Transfer list contains a non-transferable value");
242            return None;
243        }
244    }
245    let receiver = v8::undefined(scope).into();
246    if let Some(callback) = &policy.prepare_transfers {
247        let prepare_transfers = v8::Local::new(scope, callback);
248        prepare_transfers.call(scope, receiver, &[transfer.into()])?;
249    }
250    if let Some(callback) = &policy.validate_transfers {
251        let validate_transfers = v8::Local::new(scope, callback);
252        validate_transfers.call(scope, receiver, &[transfer.into()])?;
253    }
254    let serializer = v8::ValueSerializer::new(scope, Box::new(&policy));
255    for (id, buffer) in buffers.iter().enumerate() {
256        serializer.transfer_array_buffer(id as u32, *buffer);
257    }
258    serializer.write_header();
259    let context = scope.get_current_context();
260    if serializer.write_value(context, args.get(0)) != Some(true) {
261        return None;
262    }
263    if let Some(callback) = &policy.validate_transfers {
264        let validate_transfers = v8::Local::new(scope, callback);
265        validate_transfers.call(scope, receiver, &[transfer.into()])?;
266    }
267    let bytes = serializer.release();
268    let deserializer = v8::ValueDeserializer::new(scope, Box::new(&policy), &bytes);
269    // Detachment follows successful serialization, in transfer-list order. Getters may
270    // have resized or detached later buffers, so this phase can fail after earlier moves.
271    for (id, buffer) in buffers.iter().enumerate() {
272        if buffer.was_detached() {
273            policy.fail(scope, "Cannot transfer a detached ArrayBuffer");
274            return None;
275        }
276        // V8's Detach silently succeeds on non-detachable buffers (e.g. Wasm memory).
277        if !buffer.is_detachable() {
278            let message = v8::String::new(scope, "Cannot detach this ArrayBuffer")?;
279            let error = v8::Exception::type_error(scope, message);
280            scope.throw_exception(error);
281            return None;
282        }
283        let store = buffer.get_backing_store();
284        if buffer.detach(None) != Some(true) {
285            return None;
286        }
287        let moved = v8::ArrayBuffer::with_backing_store(scope, &store);
288        deserializer.transfer_array_buffer(id as u32, moved);
289    }
290    let value = {
291        v8::tc_scope!(let caught, scope);
292        let value = if deserializer.read_header(context) == Some(true) {
293            deserializer.read_value(context)
294        } else {
295            None
296        };
297        if value.is_none() {
298            caught.reset();
299        }
300        value
301    };
302    if value.is_none() {
303        // V8 cannot reconstruct views invalidated by a transfer-time resize.
304        policy.fail(
305            scope,
306            "not yet implemented: deserializing this cloned value",
307        );
308        return None;
309    }
310    if let Some(callback) = &policy.commit_transfers {
311        let commit_transfers = v8::Local::new(scope, callback);
312        commit_transfers.call(scope, receiver, &[transfer.into()])?;
313    }
314    value
315}