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