MIRROR: javascript for 馃悳's, a tiny runtime with big ambitions
1#include <string.h>
2#include <stdlib.h>
3
4#include <uthash.h>
5
6#include "ant.h"
7#include "errors.h"
8#include "runtime.h"
9#include "internal.h"
10#include "descriptors.h"
11
12#include "modules/date.h"
13#include "modules/buffer.h"
14#include "modules/collections.h"
15#include "modules/domexception.h"
16#include "modules/blob.h"
17#include "modules/structured-clone.h"
18
19typedef struct sc_entry {
20 ant_value_t key;
21 ant_value_t value;
22 UT_hash_handle hh;
23} sc_entry_t;
24
25static void sc_add(sc_entry_t **table, ant_value_t key, ant_value_t value) {
26 sc_entry_t *e = calloc(1, sizeof(sc_entry_t));
27 if (!e) return;
28 e->key = key;
29 e->value = value;
30 HASH_ADD(hh, *table, key, sizeof(ant_value_t), e);
31}
32
33static ant_value_t sc_lookup(sc_entry_t **table, ant_value_t key) {
34 sc_entry_t *e;
35 HASH_FIND(hh, *table, &key, sizeof(ant_value_t), e);
36 return e ? e->value : js_mkundef();
37}
38
39static bool sc_has(sc_entry_t **table, ant_value_t key) {
40 sc_entry_t *e;
41 HASH_FIND(hh, *table, &key, sizeof(ant_value_t), e);
42 return e != NULL;
43}
44
45static void sc_free(sc_entry_t **table) {
46 sc_entry_t *e, *tmp;
47 HASH_ITER(hh, *table, e, tmp) {
48 HASH_DEL(*table, e);
49 free(e);
50 }
51}
52
53static ant_value_t sc_clone_typed_array(
54 ant_t *js, ant_value_t key, TypedArrayData *ta_data, sc_entry_t **seen
55) {
56 ant_value_t existing = sc_lookup(seen, key);
57 if (vtype(existing) != T_UNDEF) return existing;
58
59 if (!ta_data || !ta_data->buffer) return js_throw(
60 js, make_dom_exception(js, "TypedArray could not be cloned", "DataCloneError")
61 );
62
63 ArrayBufferData *new_buf = create_array_buffer_data(ta_data->byte_length);
64 if (!new_buf) return js_mkerr(js, "out of memory");
65 if (ta_data->byte_length > 0) {
66 memcpy(new_buf->data, ta_data->buffer->data + ta_data->byte_offset, ta_data->byte_length);
67 }
68
69 ant_value_t clone = create_typed_array(
70 js, ta_data->type, new_buf, 0,
71 ta_data->length, buffer_typedarray_type_name(ta_data->type)
72 );
73
74 sc_add(seen, key, clone);
75 return clone;
76}
77
78static ant_value_t sc_clone_rec(ant_t *js, ant_value_t val, sc_entry_t **seen, sc_entry_t **transfer) {
79 uint8_t t = vtype(val);
80 TypedArrayData *ta_data = NULL;
81
82 if (t == T_UNDEF || t == T_NULL || t == T_BOOL || t == T_NUM || t == T_BIGINT || t == T_STR) return val;
83 if (t == T_SYMBOL) return js_throw(js, make_dom_exception(js, "Symbol cannot be serialized", "DataCloneError"));
84
85 if (is_object_type(val)) {
86 ta_data = buffer_get_typedarray_data(val);
87 }
88
89 if (ta_data) {
90 return sc_clone_typed_array(js, val, ta_data, seen);
91 }
92
93 if (t == T_TYPEDARRAY) {
94 return sc_clone_typed_array(js, val, (TypedArrayData *)js_gettypedarray(val), seen);
95 }
96
97 if (t == T_FUNC || t == T_CFUNC)
98 return js_throw(js, make_dom_exception(js, "() => {} could not be cloned", "DataCloneError"));
99 if (t == T_PROMISE || t == T_GENERATOR)
100 return js_throw(js, make_dom_exception(js, "Value could not be cloned", "DataCloneError"));
101 if (!is_object_type(val))
102 return js_throw(js, make_dom_exception(js, "Value could not be cloned", "DataCloneError"));
103
104 ant_value_t existing = sc_lookup(seen, val);
105 if (vtype(existing) != T_UNDEF) return existing;
106
107 ArrayBufferData *abd = buffer_get_arraybuffer_data(val);
108 if (abd) {
109 if (abd && !abd->is_detached) {
110 if (transfer && sc_has(transfer, val)) {
111 ArrayBufferData *new_abd = calloc(1, sizeof(ArrayBufferData));
112 if (!new_abd) return js_mkerr(js, "out of memory");
113 new_abd->data = abd->data;
114 new_abd->length = abd->length;
115 new_abd->capacity = abd->capacity;
116 new_abd->ref_count = 1;
117 abd->data = NULL;
118 abd->length = 0;
119 abd->capacity = 0;
120 abd->is_detached = 1;
121 js_set(js, val, "byteLength", js_mknum(0));
122 ant_value_t clone = create_arraybuffer_obj(js, new_abd);
123 sc_add(seen, val, clone);
124 return clone;
125 }
126 ArrayBufferData *new_abd = create_array_buffer_data(abd->length);
127 if (!new_abd) return js_mkerr(js, "out of memory");
128 if (abd->length > 0) memcpy(new_abd->data, abd->data, abd->length);
129 ant_value_t clone = create_arraybuffer_obj(js, new_abd);
130 sc_add(seen, val, clone);
131 return clone;
132 }}
133
134 if (buffer_is_dataview(val)) {
135 DataViewData *dv = buffer_get_dataview_data(val);
136 if (!dv || !dv->buffer)
137 return js_throw(js, make_dom_exception(js, "DataView could not be cloned", "DataCloneError"));
138
139 ArrayBufferData *new_buf = create_array_buffer_data(dv->buffer->length);
140 if (!new_buf) return js_mkerr(js, "out of memory");
141 if (dv->buffer->length > 0) memcpy(new_buf->data, dv->buffer->data, dv->buffer->length);
142
143 ant_value_t ab_obj = create_arraybuffer_obj(js, new_buf);
144 ant_value_t clone = create_dataview_with_buffer(
145 js, new_buf, dv->byte_offset, dv->byte_length, ab_obj
146 );
147
148 if (is_err(clone)) {
149 free_array_buffer_data(new_buf);
150 return clone;
151 }
152
153 js_set(js, clone, "byteOffset", js_mknum((double)dv->byte_offset));
154 js_set(js, clone, "byteLength", js_mknum((double)dv->byte_length));
155
156 sc_add(seen, val, clone);
157 free_array_buffer_data(new_buf);
158 return clone;
159 }
160
161 if (t == T_ARR) {
162 ant_value_t clone = js_mkarr(js);
163 sc_add(seen, val, clone);
164
165 ant_offset_t len = js_arr_len(js, val);
166 for (ant_offset_t i = 0; i < len; i++) {
167 ant_value_t ic = sc_clone_rec(js, js_arr_get(js, val, i), seen, transfer);
168 if (is_err(ic)) return ic;
169 js_arr_push(js, clone, ic);
170 }
171
172 return clone;
173 }
174
175 ant_object_t *obj_ptr = js_obj_ptr(val);
176 if (!obj_ptr)
177 return js_throw(js, make_dom_exception(js, "Value could not be cloned", "DataCloneError"));
178
179 if (obj_ptr->type_tag == T_WEAKMAP || obj_ptr->type_tag == T_WEAKSET)
180 return js_throw(js, make_dom_exception(js, "WeakMap/WeakSet could not be cloned", "DataCloneError"));
181
182 if (obj_ptr->type_tag == T_MAP) {
183 ant_value_t clone = js_mkobj(js);
184 js_obj_ptr(clone)->type_tag = T_MAP;
185
186 ant_value_t map_proto = js_get_ctor_proto(js, "Map", 3);
187 if (is_special_object(map_proto)) js_set_proto_init(clone, map_proto);
188
189 map_entry_t **new_head = ant_calloc(sizeof(map_entry_t *));
190 if (!new_head) return js_mkerr(js, "out of memory");
191
192 *new_head = NULL;
193 js_set_slot(clone, SLOT_DATA, ANT_PTR(new_head));
194 sc_add(seen, val, clone);
195
196 map_entry_t **src_head = get_map_from_obj(val);
197 if (src_head && *src_head) {
198 map_entry_t *e, *tmp;
199 HASH_ITER(hh, *src_head, e, tmp) {
200 ant_value_t vc = sc_clone_rec(js, e->value, seen, transfer);
201 if (is_err(vc)) return vc;
202
203 map_entry_t *ne = ant_calloc(sizeof(map_entry_t));
204 if (!ne) return js_mkerr(js, "out of memory");
205
206 ne->key = (unsigned char *)strdup((const char *)e->key);
207 ne->key_val = e->key_val;
208 ne->value = vc;
209
210 HASH_ADD_KEYPTR(
211 hh, *new_head, ne->key,
212 (unsigned)strlen((const char *)ne->key), ne
213 );
214 }}
215
216 return clone;
217 }
218
219 if (obj_ptr->type_tag == T_SET) {
220 ant_value_t clone = js_mkobj(js);
221 js_obj_ptr(clone)->type_tag = T_SET;
222
223 ant_value_t set_proto = js_get_ctor_proto(js, "Set", 3);
224 if (is_special_object(set_proto)) js_set_proto_init(clone, set_proto);
225
226 set_entry_t **new_head = ant_calloc(sizeof(set_entry_t *));
227 if (!new_head) return js_mkerr(js, "out of memory");
228 *new_head = NULL;
229 js_set_slot(clone, SLOT_DATA, ANT_PTR(new_head));
230 sc_add(seen, val, clone);
231
232 set_entry_t **src_head = get_set_from_obj(val);
233 if (src_head && *src_head) {
234 set_entry_t *e, *tmp;
235 HASH_ITER(hh, *src_head, e, tmp) {
236 ant_value_t vc = sc_clone_rec(js, e->value, seen, transfer);
237 if (is_err(vc)) return vc;
238
239 set_entry_t *ne = ant_calloc(sizeof(set_entry_t));
240 if (!ne) return js_mkerr(js, "out of memory");
241
242 ne->key = (unsigned char *)strdup((const char *)e->key);
243 ne->value = vc;
244
245 HASH_ADD_KEYPTR(
246 hh, *new_head, ne->key,
247 (unsigned)strlen((const char *)ne->key), ne
248 );
249 }}
250
251 return clone;
252 }
253
254 if (js_get_slot(val, SLOT_ERROR_BRAND) == js_true) {
255 ant_value_t clone = js_mkobj(js);
256 sc_add(seen, val, clone);
257
258 const char *msg = get_str_prop(js, val, "message", 7, NULL);
259 const char *name = get_str_prop(js, val, "name", 4, NULL);
260 const char *stack = get_str_prop(js, val, "stack", 5, NULL);
261
262 if (msg) js_set(js, clone, "message", js_mkstr(js, msg, strlen(msg)));
263 if (name) js_set(js, clone, "name", js_mkstr(js, name, strlen(name)));
264 if (stack) js_set(js, clone, "stack", js_mkstr(js, stack, strlen(stack)));
265 js_set_slot(clone, SLOT_ERROR_BRAND, js_true);
266
267 ant_value_t err_type = js_get_slot(val, SLOT_ERR_TYPE);
268 if (vtype(err_type) != T_UNDEF) js_set_slot(clone, SLOT_ERR_TYPE, err_type);
269
270 return clone;
271 }
272
273 if (is_date_instance(val)) {
274 ant_value_t clone = js_mkobj(js);
275 ant_value_t date_proto = js_get_ctor_proto(js, "Date", 4);
276 if (is_object_type(date_proto)) js_set_proto_init(clone, date_proto);
277
278 js_set_slot(clone, SLOT_DATA, js_get_slot(val, SLOT_DATA));
279 js_set_slot(clone, SLOT_BRAND, js_mknum(BRAND_DATE));
280 sc_add(seen, val, clone);
281
282 return clone;
283 }
284
285 blob_data_t *bd = js_is_prototype_of(js, g_blob_proto, val) ? blob_get_data(val) : NULL;
286 if (bd) {
287 ant_value_t clone = blob_create(js, bd->data, bd->size, bd->type);
288 if (is_err(clone)) return clone;
289 sc_add(seen, val, clone);
290 if (bd->name) {
291 blob_data_t *nbd = blob_get_data(clone);
292 if (nbd) {
293 nbd->name = strdup(bd->name);
294 nbd->last_modified = bd->last_modified;
295 }
296 js_set_proto_init(clone, g_file_proto);
297 }
298 return clone;
299 }
300
301 ant_value_t clone = js_mkobj(js);
302 sc_add(seen, val, clone);
303
304 ant_iter_t iter = js_prop_iter_begin(js, val);
305 const char *key;
306 size_t key_len;
307 ant_value_t pval;
308
309 while (js_prop_iter_next(&iter, &key, &key_len, &pval)) {
310 ant_value_t pc = sc_clone_rec(js, pval, seen, transfer);
311 if (is_err(pc)) { js_prop_iter_end(&iter); return pc; }
312 js_set(js, clone, key, pc);
313 }
314
315 js_prop_iter_end(&iter);
316 return clone;
317}
318
319ant_value_t js_structured_clone(ant_t *js, ant_value_t *args, int nargs) {
320 if (nargs < 1) return js_mkundef();
321
322 sc_entry_t *transfer = NULL;
323
324 if (nargs < 2 || !is_object_type(args[1])) goto clone;
325 ant_value_t xfer_arr = js_get(js, args[1], "transfer");
326 if (vtype(xfer_arr) != T_ARR) goto clone;
327
328 ant_offset_t xfer_len = js_arr_len(js, xfer_arr);
329 for (ant_offset_t i = 0; i < xfer_len; i++) {
330 ant_value_t item = js_arr_get(js, xfer_arr, i);
331 if (is_object_type(item)) sc_add(&transfer, item, js_true);
332 }
333
334clone:;
335
336 sc_entry_t *seen = NULL;
337 ant_value_t result = sc_clone_rec(js, args[0], &seen, &transfer);
338 sc_free(&seen);
339 sc_free(&transfer);
340 return result;
341}
342
343void init_structured_clone_module(void) {
344 ant_t *js = rt->js;
345 ant_value_t global = js_glob(js);
346
347 js_set(js, global, "structuredClone", js_mkfun(js_structured_clone));
348 js_set_descriptor(js, global, "structuredClone", 15, JS_DESC_W | JS_DESC_C);
349}