File Coverage

QuickJS.xs
Criterion Covered Total %
statement 371 399 92.9
branch 154 268 57.4
condition n/a
subroutine n/a
pod n/a
total 525 667 78.7


line stmt bran cond sub pod time code
1             #include "easyxs/easyxs.h"
2              
3             #include "quickjs/quickjs.h"
4             #include "quickjs/quickjs-libc.h"
5              
6             #define PERL_NS_ROOT "JavaScript::QuickJS"
7              
8             #define PERL_BOOLEAN_CLASS "Types::Serialiser::Boolean"
9              
10             #define PQJS_JSOBJECT_CLASS PERL_NS_ROOT "::JSObject"
11             #define PQJS_FUNCTION_CLASS PERL_NS_ROOT "::Function"
12             #define PQJS_REGEXP_CLASS PERL_NS_ROOT "::RegExp"
13             #define PQJS_DATE_CLASS PERL_NS_ROOT "::Date"
14              
15             typedef struct {
16             JSContext *ctx;
17             pid_t pid;
18             char* module_base_path;
19             } perl_qjs_s;
20              
21             typedef struct {
22             JSContext *ctx;
23             JSValue jsobj;
24             pid_t pid;
25             } perl_qjs_jsobj_s;
26              
27             typedef struct {
28             #ifdef MULTIPLICITY
29             tTHX aTHX;
30             #endif
31             SV** svs;
32             U32 svs_count;
33             U32 refcount;
34             JSValue regexp_jsvalue;
35             JSValue date_jsvalue;
36             } ctx_opaque_s;
37              
38             const char* __jstype_name_back[] = {
39             [JS_TAG_BIG_DECIMAL - JS_TAG_FIRST] = "big decimal",
40             [JS_TAG_BIG_INT - JS_TAG_FIRST] = "big integer",
41             [JS_TAG_BIG_FLOAT - JS_TAG_FIRST] = "big float",
42             [JS_TAG_SYMBOL - JS_TAG_FIRST] = "symbol",
43             [JS_TAG_MODULE - JS_TAG_FIRST] = "module",
44             [JS_TAG_OBJECT - JS_TAG_FIRST] = "object",
45             [JS_TAG_FLOAT64 - JS_TAG_FIRST] = "float64",
46              
47             /* Small hack to ensure we can always read: */
48             [99] = NULL,
49             };
50              
51             const char* const DATE_GETTER_FROM_IX[] = {
52             "toString",
53             "toUTCString",
54             "toGMTString",
55             "toISOString",
56             "toDateString",
57             "toTimeString",
58             "toLocaleString",
59             "toLocaleDateString",
60             "toLocaleTimeString",
61             "getTimezoneOffset",
62             "getTime",
63             "getFullYear",
64             "getUTCFullYear",
65             "getMonth",
66             "getUTCMonth",
67             "getDate",
68             "getUTCDate",
69             "getHours",
70             "getUTCHours",
71             "getMinutes",
72             "getUTCMinutes",
73             "getSeconds",
74             "getUTCSeconds",
75             "getMilliseconds",
76             "getUTCMilliseconds",
77             "getDay",
78             "getUTCDay",
79             "toJSON",
80             };
81              
82             const char* const DATE_SETTER_FROM_IX[] = {
83             "setTime",
84             "setMilliseconds",
85             "setUTCMilliseconds",
86             "setSeconds",
87             "setUTCSeconds",
88             "setMinutes",
89             "setUTCMinutes",
90             "setHours",
91             "setUTCHours",
92             "setDate",
93             "setUTCDate",
94             "setMonth",
95             "setUTCMonth",
96             "setFullYear",
97             "setUTCFullYear",
98             };
99              
100             #if defined _WIN32 || defined __CYGWIN__
101             # define PATH_SEPARATOR '\\'
102             #else
103             # define PATH_SEPARATOR '/'
104             #endif
105              
106             #define _jstype_name(typenum) __jstype_name_back[ typenum - JS_TAG_FIRST ]
107              
108             static SV* _JSValue_to_SV (pTHX_ JSContext* ctx, JSValue jsval, SV** err_svp);
109              
110 3           static inline SV* _JSValue_special_object_to_SV (pTHX_ JSContext* ctx, JSValue jsval, SV** err_svp, const char* class) {
111             assert(!*err_svp);
112              
113 3           SV* sv = exs_new_structref(perl_qjs_jsobj_s, class);
114 3           perl_qjs_jsobj_s* pqjs = exs_structref_ptr(sv);
115              
116 3           *pqjs = (perl_qjs_jsobj_s) {
117             .ctx = ctx,
118 3           .jsobj = JS_DupValue(ctx, jsval),
119 3           .pid = getpid(),
120             };
121              
122 3           ctx_opaque_s* ctxdata = JS_GetContextOpaque(ctx);
123 3           ctxdata->refcount++;
124              
125 3           return sv;
126             }
127              
128 12           static inline SV* _JSValue_object_to_SV (pTHX_ JSContext* ctx, JSValue jsval, SV** err_svp) {
129             assert(!*err_svp);
130              
131             JSPropertyEnum *tab_atom;
132             uint32_t tab_atom_count;
133              
134 12           int propnameserr = JS_GetOwnPropertyNames(ctx, &tab_atom, &tab_atom_count, jsval, JS_GPN_STRING_MASK);
135              
136             PERL_UNUSED_VAR(propnameserr);
137             assert(!propnameserr);
138              
139 12           HV* hv = newHV();
140              
141 1247 100         for(int i = 0; i < tab_atom_count; i++) {
142 1236           JSValue key = JS_AtomToString(ctx, tab_atom[i].atom);
143             STRLEN strlen;
144 1236           const char* keystr = JS_ToCStringLen(ctx, &strlen, key);
145              
146 1236           JSValue value = JS_GetProperty(ctx, jsval, tab_atom[i].atom);
147              
148 1236           SV* val_sv = _JSValue_to_SV(aTHX_ ctx, value, err_svp);
149              
150 1236 100         if (val_sv) {
151 1235           hv_store(hv, keystr, -strlen, val_sv, 0);
152             }
153              
154 1236           JS_FreeCString(ctx, keystr);
155 1236           JS_FreeValue(ctx, key);
156 1236           JS_FreeValue(ctx, value);
157 1236           JS_FreeAtom(ctx, tab_atom[i].atom);
158              
159 1236 100         if (!val_sv) break;
160             }
161              
162 12           js_free(ctx, tab_atom);
163              
164 12 100         if (*err_svp) {
165 1           SvREFCNT_dec( (SV*) hv );
166 1           return NULL;
167             }
168              
169 12           return newRV_noinc((SV*) hv);
170             }
171              
172 23           static inline SV* _JSValue_array_to_SV (pTHX_ JSContext* ctx, JSValue jsval, SV** err_svp) {
173 23           JSValue jslen = JS_GetPropertyStr(ctx, jsval, "length");
174             uint32_t len;
175 23           JS_ToUint32(ctx, &len, jslen);
176 23           JS_FreeValue(ctx, jslen);
177              
178 23           AV* av = newAV();
179              
180 23 100         if (len) {
181 18           av_fill( av, len - 1 );
182 48 100         for (uint32_t i=0; i
183 31           JSValue jsitem = JS_GetPropertyUint32(ctx, jsval, i);
184              
185 31           SV* val_sv = _JSValue_to_SV(aTHX_ ctx, jsitem, err_svp);
186              
187 31 100         if (val_sv) av_store( av, i, val_sv );
188              
189 31           JS_FreeValue(ctx, jsitem);
190              
191 31 100         if (!val_sv) break;
192             }
193             }
194              
195 23 100         if (*err_svp) {
196 1           SvREFCNT_dec((SV*) av);
197 1           return NULL;
198             }
199              
200 23           return newRV_noinc((SV*) av);
201             }
202              
203             /* NO JS exceptions allowed here! */
204 1505           static SV* _JSValue_to_SV (pTHX_ JSContext* ctx, JSValue jsval, SV** err_svp) {
205             assert(!*err_svp);
206              
207             SV* RETVAL;
208              
209 1505           int tag = JS_VALUE_GET_NORM_TAG(jsval);
210              
211             assert(tag != JS_TAG_EXCEPTION);
212              
213 1505           switch (tag) {
214             case JS_TAG_STRING:
215             STMT_START {
216             STRLEN strlen;
217 1050           const char* str = JS_ToCStringLen(ctx, &strlen, jsval);
218 1050           RETVAL = newSVpvn_flags(str, strlen, SVf_UTF8);
219 1050           JS_FreeCString(ctx, str);
220             } STMT_END;
221 1050           break;
222              
223             case JS_TAG_INT:
224 116           RETVAL = newSViv(JS_VALUE_GET_INT(jsval));
225 116           break;
226              
227             case JS_TAG_FLOAT64:
228 54           RETVAL = newSVnv(JS_VALUE_GET_FLOAT64(jsval));
229 54           break;
230              
231             case JS_TAG_BOOL:
232 11 100         RETVAL = boolSV(JS_VALUE_GET_BOOL(jsval));
233 11           break;
234              
235             case JS_TAG_NULL:
236             case JS_TAG_UNDEFINED:
237 223           RETVAL = &PL_sv_undef;
238 223           break;
239              
240             case JS_TAG_OBJECT:
241 47 100         if (JS_IsFunction(ctx, jsval)) {
242 9           load_module(
243             PERL_LOADMOD_NOIMPORT,
244             newSVpvs(PQJS_FUNCTION_CLASS),
245             NULL
246             );
247              
248 9           SV* func_sv = exs_new_structref(perl_qjs_jsobj_s, PQJS_FUNCTION_CLASS);
249 9           perl_qjs_jsobj_s* pqjs = exs_structref_ptr(func_sv);
250              
251 9           *pqjs = (perl_qjs_jsobj_s) {
252             .ctx = ctx,
253 9           .jsobj = JS_DupValue(ctx, jsval),
254 9           .pid = getpid(),
255             };
256              
257 9           ctx_opaque_s* ctxdata = JS_GetContextOpaque(ctx);
258 9           ctxdata->refcount++;
259              
260 9           RETVAL = func_sv;
261             }
262 38 100         else if (JS_IsArray(ctx, jsval)) {
263 23           RETVAL = _JSValue_array_to_SV(aTHX_ ctx, jsval, err_svp);
264             }
265             else {
266              
267 15           ctx_opaque_s* ctxdata = JS_GetContextOpaque(ctx);
268              
269 15 100         if (JS_IsInstanceOf(ctx, jsval, ctxdata->regexp_jsvalue)) {
270 2           RETVAL = _JSValue_special_object_to_SV(aTHX_ ctx, jsval, err_svp, PQJS_REGEXP_CLASS);
271             }
272 13 100         else if (JS_IsInstanceOf(ctx, jsval, ctxdata->date_jsvalue)) {
273 1           RETVAL = _JSValue_special_object_to_SV(aTHX_ ctx, jsval, err_svp, PQJS_DATE_CLASS);
274             }
275             else {
276 12           RETVAL = _JSValue_object_to_SV(aTHX_ ctx, jsval, err_svp);
277             }
278             }
279              
280 47           break;
281              
282             default:
283             STMT_START {
284 4           const char* typename = _jstype_name(tag);
285              
286 4 50         if (typename) {
287 4           *err_svp = newSVpvf("Cannot convert JS %s (QuickJS tag %d) to Perl!", typename, tag);
288             }
289             else {
290 0           *err_svp = newSVpvf("Cannot convert (unexpected) JS tag value %d to Perl!", tag);
291             }
292              
293 4           return NULL;
294             } STMT_END;
295             }
296              
297 1501           return RETVAL;
298             }
299              
300 16           static inline void _ctx_add_sv(pTHX_ JSContext* ctx, SV* sv) {
301 16           ctx_opaque_s* ctxdata = JS_GetContextOpaque(ctx);
302              
303 16           ctxdata->svs_count++;
304              
305 16 100         if (ctxdata->svs_count == 1) {
306 12 50         Newx(ctxdata->svs, ctxdata->svs_count, SV*);
307             }
308             else {
309 4 50         Renew(ctxdata->svs, ctxdata->svs_count, SV*);
310             }
311              
312 16           ctxdata->svs[ctxdata->svs_count - 1] = SvREFCNT_inc(sv);
313 16           }
314              
315             static JSValue _sv_to_jsvalue(pTHX_ JSContext* ctx, SV* value, SV** error);
316              
317             #define _MAX_ERRSV_TO_JS_TRIES 10
318              
319             /* Kind of like _sv_to_jsvalue(), but we need to handle the case where
320             the error SV’s own conversion to a JSValue fails. In that case, we
321             warn on the 1st error, then propagate the 2nd. Repeat until there are
322             no errors.
323             */
324 8           static JSValue _sv_error_to_jsvalue(pTHX_ JSContext* ctx, SV* error) {
325 8           SV* error2 = NULL;
326              
327 8           uint32_t tries = 0;
328              
329             JSValue to_js;
330              
331             while (1) {
332 9           to_js = _sv_to_jsvalue(aTHX_ ctx, error, &error2);
333              
334 9 100         if (!error2) break;
335              
336 1           warn_sv(error);
337              
338 1           tries++;
339 1 50         if (tries > _MAX_ERRSV_TO_JS_TRIES) {
340 0           warn_sv(error2);
341 0           return JS_NewString(ctx, "Failed to convert Perl error to JavaScript after " STRINGIFY(_MAX_ERRSV_TO_JS_TRIES) " tries!");
342             }
343              
344 1           error = error2;
345 1           error2 = NULL;
346 1           }
347              
348 8           return to_js;
349             }
350              
351 15           static JSValue __do_perl_callback(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, int jsmagic, JSValue *func_data) {
352              
353             #ifdef MULTIPLICITY
354             ctx_opaque_s* ctxdata = JS_GetContextOpaque(ctx);
355             pTHX = ctxdata->aTHX;
356             #endif
357              
358             PERL_UNUSED_VAR(jsmagic);
359 15           SV* cb_sv = ((SV**) func_data)[0];
360              
361 15           SV* args[argc + 1];
362 15           args[argc] = NULL;
363              
364 15           SV* error_sv = NULL;
365              
366 25 100         for (int a=0; a
367 12           args[a] = _JSValue_to_SV(aTHX_ ctx, argv[a], &error_sv);
368              
369 12 100         if (error_sv) {
370 3 100         while (--a >= 0) {
371 1           SvREFCNT_dec(args[a]);
372             }
373              
374 2           break;
375             }
376             }
377              
378 15 100         if (!error_sv) {
379 13           SV* from_perl = exs_call_sv_scalar_trapped(cb_sv, args, &error_sv);
380              
381 13 100         if (from_perl) {
382 12           JSValue to_js = _sv_to_jsvalue(aTHX_ ctx, from_perl, &error_sv);
383              
384 12           sv_2mortal(from_perl);
385              
386 12 100         if (!error_sv) return to_js;
387             }
388             }
389              
390 6           JSValue jserr = _sv_error_to_jsvalue(aTHX_ ctx, error_sv);
391              
392 15           return JS_Throw(ctx, jserr);
393             }
394              
395 98           static JSValue _sviv_to_js(pTHX_ JSContext* ctx, SV* value) {
396             if (sizeof(IV) == sizeof(int64_t)) {
397 98 100         return JS_NewInt64(ctx, (int64_t) SvIV(value));
398             }
399              
400             return JS_NewInt32(ctx, (int32_t) SvIV(value));
401             }
402              
403 1320           static JSValue _sv_to_jsvalue(pTHX_ JSContext* ctx, SV* value, SV** error_svp) {
404 1320 100         SvGETMAGIC(value);
    50          
405              
406 1320 100         switch ( exs_sv_type(value) ) {
    50          
    50          
    100          
    100          
    50          
    100          
    50          
407             case EXS_SVTYPE_UNDEF:
408 226           return JS_NULL;
409              
410             case EXS_SVTYPE_BOOLEAN:
411 0 0         return JS_NewBool(ctx, SvTRUE(value));
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
412              
413             case EXS_SVTYPE_STRING: STMT_START {
414             STRLEN len;
415 1027 100         const char* str = SvPVutf8(value, len);
416              
417 1027           return JS_NewStringLen(ctx, str, len);
418             } STMT_END;
419              
420             case EXS_SVTYPE_UV: STMT_START {
421 0 0         UV val_uv = SvUV(value);
422              
423             if (sizeof(UV) == sizeof(uint64_t)) {
424 0 0         if (val_uv > IV_MAX) {
425 0           return JS_NewFloat64(ctx, val_uv);
426             }
427             else {
428 0           return JS_NewInt64(ctx, (int64_t) val_uv);
429             }
430             }
431             else {
432             return JS_NewUint32(ctx, (uint32_t) val_uv);
433             }
434             } STMT_END;
435              
436             case EXS_SVTYPE_IV: STMT_START {
437 21           return _sviv_to_js(aTHX_ ctx, value);
438             } STMT_END;
439              
440             case EXS_SVTYPE_NV: STMT_START {
441 8 50         return JS_NewFloat64(ctx, (double) SvNV(value));
442             } STMT_END;
443              
444             case EXS_SVTYPE_REFERENCE:
445 42 100         if (sv_isobject(value)) {
446 10 100         if (sv_derived_from(value, PERL_BOOLEAN_CLASS)) {
447 4 50         return JS_NewBool(ctx, SvTRUE(SvRV(value)));
    50          
    0          
    50          
    0          
    0          
    50          
    0          
    0          
    0          
    0          
    0          
    50          
    50          
    100          
    50          
    0          
    100          
    0          
448             }
449 8 100         else if (sv_derived_from(value, PQJS_JSOBJECT_CLASS)) {
450 3           perl_qjs_jsobj_s* pqjs = exs_structref_ptr(value);
451              
452 3 100         if (LIKELY(pqjs->ctx == ctx)) {
453 2           return JS_DupValue(ctx, pqjs->jsobj);
454             }
455              
456 1           *error_svp = newSVpvf("%s for QuickJS %p given to QuickJS %p!", sv_reftype(SvRV(value), 1), pqjs->ctx, ctx);
457 1           return JS_NULL;
458             }
459              
460 5           break;
461             }
462              
463 32           switch (SvTYPE(SvRV(value))) {
464             case SVt_PVCV:
465 16           _ctx_add_sv(aTHX_ ctx, value);
466              
467             /* A hack to store our callback via the func_data pointer: */
468 16           JSValue dummy = JS_MKPTR(JS_TAG_INT, value);
469              
470 31           return JS_NewCFunctionData(
471             ctx,
472             __do_perl_callback,
473             0, 0,
474             1, &dummy
475             );
476              
477             case SVt_PVAV: STMT_START {
478 7           AV* av = (AV*) SvRV(value);
479 7           JSValue jsarray = JS_NewArray(ctx);
480 14           JS_SetPropertyStr(ctx, jsarray, "length", JS_NewUint32(ctx, 1 + av_len(av)));
481              
482 11 100         for (int32_t i=0; i <= av_len(av); i++) {
483 5           SV** svp = av_fetch(av, i, 0);
484             assert(svp);
485             assert(*svp);
486              
487 5           JSValue jsval = _sv_to_jsvalue(aTHX_ ctx, *svp, error_svp);
488              
489 5 100         if (*error_svp) {
490 1           JS_FreeValue(ctx, jsarray);
491 1           return _sv_error_to_jsvalue(aTHX_ ctx, *error_svp);
492             }
493              
494 4           JS_SetPropertyUint32(ctx, jsarray, i, jsval);
495             }
496              
497 7           return jsarray;
498             } STMT_END;
499              
500             case SVt_PVHV: STMT_START {
501 8           HV* hv = (HV*) SvRV(value);
502 8           JSValue jsobj = JS_NewObject(ctx);
503              
504 8           hv_iterinit(hv);
505              
506             HE* hvent;
507 1238 100         while ( (hvent = hv_iternext(hv)) ) {
508 1231           SV* key_sv = hv_iterkeysv(hvent);
509 1231           SV* val_sv = hv_iterval(hv, hvent);
510              
511             STRLEN keylen;
512 1231 100         const char* key = SvPVutf8(key_sv, keylen);
513              
514 1231           JSValue jsval = _sv_to_jsvalue(aTHX_ ctx, val_sv, error_svp);
515 1231 100         if (*error_svp) {
516 1           JS_FreeValue(ctx, jsobj);
517 1           return _sv_error_to_jsvalue(aTHX_ ctx, *error_svp);
518             }
519              
520 1230           JSAtom prop = JS_NewAtomLen(ctx, key, keylen);
521              
522             /* NB: ctx takes over jsval. */
523 1230           JS_DefinePropertyValue(ctx, jsobj, prop, jsval, JS_PROP_WRITABLE);
524              
525 1230           JS_FreeAtom(ctx, prop);
526             }
527              
528 8           return jsobj;
529             } STMT_END;
530              
531             default:
532 1           break;
533             }
534              
535             default:
536 1           break;
537             }
538              
539 6           *error_svp = newSVpvf("Cannot convert %" SVf " to JavaScript!", value);
540              
541 6           return JS_NULL;
542             }
543              
544 33           static JSContext* _create_new_jsctx( pTHX_ JSRuntime *rt ) {
545 33           JSContext *ctx = JS_NewContext(rt);
546              
547             ctx_opaque_s* ctxdata;
548 33           Newxz(ctxdata, 1, ctx_opaque_s);
549 33           JS_SetContextOpaque(ctx, ctxdata);
550              
551 33           JSValue global = JS_GetGlobalObject(ctx);
552              
553 33           *ctxdata = (ctx_opaque_s) {
554             .refcount = 1,
555 33           .regexp_jsvalue = JS_GetPropertyStr(ctx, global, "RegExp"),
556 33           .date_jsvalue = JS_GetPropertyStr(ctx, global, "Date"),
557             #ifdef MULTIPLICITY
558             .aTHX = aTHX,
559             #endif
560             };
561              
562 33           JS_FreeValue(ctx, global);
563              
564 33           return ctx;
565             }
566              
567 224           static inline SV* _return_jsvalue_or_croak(pTHX_ JSContext* ctx, JSValue jsret) {
568             SV* err;
569             SV* RETVAL;
570              
571 224 100         if (JS_IsException(jsret)) {
572 10           JSValue jserr = JS_GetException(ctx);
573             //err = _JSValue_to_SV(aTHX_ ctx, jserr);
574              
575             /* Ideal here is to capture all aspects of the error object,
576             including its `name` and members. But for now just give
577             a string.
578              
579             JSValue jslen = JS_GetPropertyStr(ctx, jserr, "name");
580             STRLEN namelen;
581             const char* namestr = JS_ToCStringLen(ctx, &namelen, jslen);
582             */
583              
584             STRLEN strlen;
585 10           const char* str = JS_ToCStringLen(ctx, &strlen, jserr);
586              
587 10           err = newSVpvn_flags(str, strlen, SVf_UTF8);
588              
589 10           JS_FreeCString(ctx, str);
590 10           JS_FreeValue(ctx, jserr);
591 10           RETVAL = NULL; // silence uninitialized warning
592             }
593             else {
594 214           err = NULL;
595 214           RETVAL = _JSValue_to_SV(aTHX_ ctx, jsret, &err);
596             }
597              
598 224           JS_FreeValue(ctx, jsret);
599              
600 224 100         if (err) croak_sv(err);
601              
602 212           return RETVAL;
603             }
604              
605 44           static void _free_jsctx(pTHX_ JSContext* ctx) {
606 44           ctx_opaque_s* ctxdata = JS_GetContextOpaque(ctx);
607              
608 44 100         if (--ctxdata->refcount == 0) {
609 32           JS_FreeValue(ctx, ctxdata->regexp_jsvalue);
610 32           JS_FreeValue(ctx, ctxdata->date_jsvalue);
611              
612 32           JSRuntime *rt = JS_GetRuntime(ctx);
613              
614 48 100         for (U32 i=0; isvs_count; i++) {
615 16           SvREFCNT_dec(ctxdata->svs[i]);
616             }
617              
618 32           Safefree(ctxdata);
619              
620 32           JS_FreeContext(ctx);
621 32           JS_FreeRuntime(rt);
622             }
623 44           }
624              
625 6           static JSModuleDef *pqjs_module_loader(JSContext *ctx,
626             const char *module_name, void *opaque) {
627 6           char** module_base_path_p = (char**) opaque;
628              
629 6           char* module_base_path = *module_base_path_p;
630              
631             JSModuleDef *moduledef;
632              
633 6 100         if (module_base_path) {
634 1           size_t base_path_len = strlen(module_base_path);
635 1           size_t module_name_len = strlen(module_name);
636              
637 1           char real_path[1 + base_path_len + module_name_len];
638              
639 1           memcpy(real_path, module_base_path, base_path_len);
640 1           memcpy(real_path + base_path_len, module_name, module_name_len);
641 1           real_path[base_path_len + module_name_len] = 0;
642              
643 1           moduledef = js_module_loader(ctx, real_path, NULL);
644             }
645             else {
646 5           moduledef = js_module_loader(ctx, module_name, NULL);
647             }
648              
649 6           return moduledef;
650             }
651              
652             /* These must correlate to the ALIAS values below. */
653             static const char* _REGEXP_ACCESSORS[] = {
654             "flags",
655             "dotAll",
656             "global",
657             "hasIndices",
658             "ignoreCase",
659             "multiline",
660             "source",
661             "sticky",
662             "unicode",
663             "lastIndex",
664             };
665              
666             /* These must correlate to the ALIAS values below. */
667             static const char* _FUNCTION_ACCESSORS[] = {
668             "length",
669             "name",
670             };
671              
672             #define FUNC_CALL_INITIAL_ARGS 2
673              
674 9           void _svs_to_jsvars(pTHX_ JSContext* jsctx, int32_t params_count, SV** svs, JSValue* jsvars) {
675 9           SV* error = NULL;
676              
677 17 100         for (int32_t i=0; i
678 9           SV* cur_sv = svs[i];
679              
680 9           JSValue jsval = _sv_to_jsvalue(aTHX_ jsctx, cur_sv, &error);
681              
682 9 100         if (error) {
683 1 50         while (--i >= 0) {
684 0           JS_FreeValue(jsctx, jsvars[i]);
685             }
686              
687 1           croak_sv(error);
688             }
689              
690 8           jsvars[i] = jsval;
691             }
692 8           }
693              
694             /* ---------------------------------------------------------------------- */
695              
696             MODULE = JavaScript::QuickJS PACKAGE = JavaScript::QuickJS
697              
698             PROTOTYPES: DISABLE
699              
700             SV*
701             _new (SV* classname_sv)
702             CODE:
703 33           JSRuntime *rt = JS_NewRuntime();
704 33           JS_SetHostPromiseRejectionTracker(rt, js_std_promise_rejection_tracker, NULL);
705 33           JS_SetModuleLoaderFunc(rt, NULL, js_module_loader, NULL);
706              
707 33           JSContext *ctx = _create_new_jsctx(aTHX_ rt);
708              
709 33 50         RETVAL = exs_new_structref(perl_qjs_s, SvPVbyte_nolen(classname_sv));
710 33           perl_qjs_s* pqjs = exs_structref_ptr(RETVAL);
711              
712 33           *pqjs = (perl_qjs_s) {
713             .ctx = ctx,
714 33           .pid = getpid(),
715             };
716              
717 33           JS_SetModuleLoaderFunc(
718             rt,
719             NULL,
720             pqjs_module_loader,
721 33           &pqjs->module_base_path
722             );
723              
724 33           JS_SetRuntimeInfo(rt, PERL_NS_ROOT);
725              
726             OUTPUT:
727             RETVAL
728              
729             void
730             DESTROY (SV* self_sv)
731             CODE:
732 33           perl_qjs_s* pqjs = exs_structref_ptr(self_sv);
733              
734 33 50         if (PL_dirty && pqjs->pid == getpid()) {
    0          
735 0           warn("DESTROYing %" SVf " at global destruction; memory leak likely!\n", self_sv);
736             }
737              
738 33 100         if (pqjs->module_base_path) Safefree(pqjs->module_base_path);
739              
740 33           _free_jsctx(aTHX_ pqjs->ctx);
741              
742             SV*
743             _configure (SV* self_sv, SV* max_stack_size_sv, SV* memory_limit_sv, SV* gc_threshold_sv)
744             CODE:
745 8           perl_qjs_s* pqjs = exs_structref_ptr(self_sv);
746              
747 8           JSRuntime *rt = JS_GetRuntime(pqjs->ctx);
748              
749 8 100         if (SvOK(max_stack_size_sv)) {
    50          
    50          
750 4           JS_SetMaxStackSize(rt, exs_SvUV(max_stack_size_sv));
751             }
752              
753 6 100         if (SvOK(memory_limit_sv)) {
    50          
    50          
754 4           JS_SetMemoryLimit(rt, exs_SvUV(memory_limit_sv));
755             }
756              
757 4 50         if (SvOK(gc_threshold_sv)) {
    0          
    0          
758 4           JS_SetGCThreshold(rt, exs_SvUV(gc_threshold_sv));
759             }
760              
761 2           RETVAL = SvREFCNT_inc(self_sv);
762              
763             OUTPUT:
764             RETVAL
765              
766             SV*
767             std (SV* self_sv)
768             ALIAS:
769             os = 1
770             helpers = 2
771             CODE:
772 2           perl_qjs_s* pqjs = exs_structref_ptr(self_sv);
773              
774 2           switch (ix) {
775             case 0:
776 1           js_init_module_std(pqjs->ctx, "std");
777 1           break;
778             case 1:
779 0           js_init_module_os(pqjs->ctx, "os");
780 0           break;
781             case 2:
782 1           js_std_add_helpers(pqjs->ctx, 0, NULL);
783 1           break;
784              
785             default:
786 0           croak("%s: Bad XS alias: %d\n", __func__, (int) ix);
787             }
788              
789 2           RETVAL = SvREFCNT_inc(self_sv);
790              
791             OUTPUT:
792             RETVAL
793              
794             SV*
795             unset_module_base (SV* self_sv)
796             CODE:
797 1           perl_qjs_s* pqjs = exs_structref_ptr(self_sv);
798              
799 1 50         if (pqjs->module_base_path) {
800 0           Safefree(pqjs->module_base_path);
801 0           pqjs->module_base_path = NULL;
802             }
803              
804 1           RETVAL = SvREFCNT_inc(self_sv);
805             OUTPUT:
806             RETVAL
807              
808             SV*
809             set_module_base (SV* self_sv, SV* path_sv)
810             CODE:
811 1 50         if (!SvOK(path_sv)) croak("Give a path! (Did you want unset_module_base?)");
    0          
    0          
812              
813 1           perl_qjs_s* pqjs = exs_structref_ptr(self_sv);
814              
815 1           const char* path = exs_SvPVbyte_nolen(path_sv);
816              
817 1           size_t path_len = strlen(path);
818              
819 1 50         if (pqjs->module_base_path) {
820 0           Renew(pqjs->module_base_path, 2 + path_len, char);
821             }
822             else {
823 1           Newx(pqjs->module_base_path, 2 + path_len, char);
824             }
825              
826 1           Copy(path, pqjs->module_base_path, 2 + path_len, char);
827              
828             /** If the given path is “/foo/bar”, we store “/foo/bar/”.
829             This means if “/foo/bar/” is given we store “/foo/bar//”,
830             which is ugly but should work on all supported platforms.
831             */
832 1           pqjs->module_base_path[path_len] = PATH_SEPARATOR;
833 1           pqjs->module_base_path[1 + path_len] = 0;
834              
835 1           RETVAL = SvREFCNT_inc(self_sv);
836             OUTPUT:
837             RETVAL
838              
839             SV*
840             set_globals (SV* self_sv, ...)
841             CODE:
842 40 50         if (items < 2) croak("Need at least 1 key/value pair.");
843              
844 40 50         if (!(items % 2)) croak("Need an even list of key/value pairs.");
845              
846 40           I32 valscount = (items - 1) >> 1;
847              
848 40           perl_qjs_s* pqjs = exs_structref_ptr(self_sv);
849              
850             SV* jsname_sv, *value_sv;
851              
852 40           SV* error = NULL;
853              
854 40           JSAtom jsnames[valscount];
855 40           JSValue jsvals[valscount];
856              
857 83 100         for (int i=0; i < valscount; i++) {
858 45           jsname_sv = ST( 1 + (i << 1) );
859 45           value_sv = ST( 2 + (i << 1) );
860              
861             STRLEN jsnamelen;
862 45 100         const char* jsname_str = SvPVutf8(jsname_sv, jsnamelen);
863              
864 45           JSValue jsval = _sv_to_jsvalue(aTHX_ pqjs->ctx, value_sv, &error);
865              
866 45 100         if (error) {
867 2 50         while (i-- > 0) {
868 0           JS_FreeAtom(pqjs->ctx, jsnames[i]);
869 0           JS_FreeValue(pqjs->ctx, jsvals[i]);
870             }
871              
872 2           croak_sv(error);
873             }
874              
875 43           jsnames[i] = JS_NewAtomLen(pqjs->ctx, jsname_str, jsnamelen);
876 43           jsvals[i] = jsval;
877             }
878              
879 38           JSValue jsglobal = JS_GetGlobalObject(pqjs->ctx);
880              
881 81 100         for (int i=0; i < valscount; i++) {
882             /* NB: ctx takes over jsval. */
883 43           JS_DefinePropertyValue(pqjs->ctx, jsglobal, jsnames[i], jsvals[i], JS_PROP_WRITABLE);
884 43           JS_FreeAtom(pqjs->ctx, jsnames[i]);
885             }
886              
887 38           JS_FreeValue(pqjs->ctx, jsglobal);
888              
889 38           RETVAL = SvREFCNT_inc(self_sv);
890              
891             OUTPUT:
892             RETVAL
893              
894             SV*
895             eval (SV* self_sv, SV* js_code_sv)
896             ALIAS:
897             eval_module = 1
898             CODE:
899 114           perl_qjs_s* pqjs = exs_structref_ptr(self_sv);
900 114           JSContext *ctx = pqjs->ctx;
901              
902             STRLEN js_code_len;
903 114 100         const char* js_code = SvPVutf8(js_code_sv, js_code_len);
904              
905 114           int eval_flags = ix ? JS_EVAL_TYPE_MODULE : JS_EVAL_TYPE_GLOBAL;
906 114           eval_flags |= JS_EVAL_FLAG_STRICT;
907              
908 114           JSValue jsret = JS_Eval(ctx, js_code, js_code_len, "", eval_flags);
909              
910             // In eval_module()’s case we want to croak if there was
911             // an exception. If not we’ll just discard the return value
912 114           RETVAL = _return_jsvalue_or_croak(aTHX_ ctx, jsret);
913              
914 102 100         if (ix) {
915              
916             // The only thing a JS module should return is null.
917             // If that’s ever not the case, warn:
918 3 50         if ( UNLIKELY(RETVAL != &PL_sv_undef) ) {
919 0           warn("UNEXPECTED: Discarding non-null return from ES module: %" SVf, RETVAL);
920 0           SvREFCNT_dec(RETVAL);
921             }
922              
923 3           RETVAL = SvREFCNT_inc(self_sv);
924             }
925              
926             OUTPUT:
927             RETVAL
928              
929             SV*
930             await (SV* self_sv)
931             CODE:
932 2           perl_qjs_s* pqjs = exs_structref_ptr(self_sv);
933 2           JSContext *ctx = pqjs->ctx;
934              
935 2           js_std_loop(ctx);
936              
937 2           RETVAL = SvREFCNT_inc(self_sv);
938              
939             OUTPUT:
940             RETVAL
941              
942             # ----------------------------------------------------------------------
943              
944             MODULE = JavaScript::QuickJS PACKAGE = JavaScript::QuickJS::Date
945              
946             SV*
947             setTime (SV* self_sv, SV* num_sv)
948             ALIAS:
949             setMilliseconds = 1
950             setUTCMilliseconds = 2
951             setSeconds = 3
952             setUTCSeconds = 4
953             setMinutes = 5
954             setUTCMinutes = 6
955             setHours = 7
956             setUTCHours = 8
957             setDate = 9
958             setUTCDate = 10
959             setMonth = 11
960             setUTCMonth = 12
961             setFullYear = 13
962             setUTCFullYear = 14
963              
964             CODE:
965 28           const char* setter_name = DATE_SETTER_FROM_IX[ix];
966              
967 28           perl_qjs_jsobj_s* pqjs = exs_structref_ptr(self_sv);
968 28           JSContext *ctx = pqjs->ctx;
969              
970 28           JSAtom prop = JS_NewAtom(ctx, setter_name);
971 28           JSValue arg = _sviv_to_js(aTHX_ ctx, num_sv);
972              
973 28           JSValue jsret = JS_Invoke(
974             ctx,
975             pqjs->jsobj,
976             prop,
977             1,
978             &arg
979             );
980              
981 28           JS_FreeAtom(ctx, prop);
982 28           JS_FreeValue(ctx, arg);
983              
984 28           RETVAL = _return_jsvalue_or_croak(aTHX_ pqjs->ctx, jsret);
985              
986             OUTPUT:
987             RETVAL
988              
989             SV*
990             toString (SV* self_sv, ...)
991             ALIAS:
992             toUTCString = 1
993             toGMTString = 2
994             toISOString = 3
995             toDateString = 4
996             toTimeString = 5
997             toLocaleString = 6
998             toLocaleDateString = 7
999             toLocaleTimeString = 8
1000             getTimezoneOffset = 9
1001             getTime = 10
1002             getFullYear = 11
1003             getUTCFullYear = 12
1004             getMonth = 13
1005             getUTCMonth = 14
1006             getDate = 15
1007             getUTCDate = 16
1008             getHours = 17
1009             getUTCHours = 18
1010             getMinutes = 19
1011             getUTCMinutes = 20
1012             getSeconds = 21
1013             getUTCSeconds = 22
1014             getMilliseconds = 23
1015             getUTCMilliseconds = 24
1016             getDay = 25
1017             getUTCDay = 26
1018             toJSON = 27
1019             CODE:
1020 70           perl_qjs_jsobj_s* pqjs = exs_structref_ptr(self_sv);
1021 70           JSContext *ctx = pqjs->ctx;
1022              
1023 70           JSAtom prop = JS_NewAtom(ctx, DATE_GETTER_FROM_IX[ix]);
1024              
1025             JSValue jsret;
1026              
1027 70 50         if (items > 1) {
1028 0           int params_count = items - 1;
1029              
1030 0           JSValue jsargs[params_count];
1031              
1032 0           _svs_to_jsvars(aTHX_ ctx, params_count, &ST(1), jsargs);
1033              
1034 0           jsret = JS_Invoke(
1035             ctx,
1036             pqjs->jsobj,
1037             prop,
1038             params_count,
1039 0           jsargs
1040             );
1041              
1042 0 0         for (uint32_t i=0; i
1043 0           JS_FreeValue(ctx, jsargs[i]);
1044             }
1045             }
1046             else {
1047 70           jsret = JS_Invoke(
1048             ctx,
1049             pqjs->jsobj,
1050             prop,
1051             0,
1052             NULL
1053             );
1054             }
1055              
1056 70           JS_FreeAtom(ctx, prop);
1057              
1058 70           RETVAL = _return_jsvalue_or_croak(aTHX_ pqjs->ctx, jsret);
1059              
1060             OUTPUT:
1061             RETVAL
1062              
1063             # ----------------------------------------------------------------------
1064              
1065             MODULE = JavaScript::QuickJS PACKAGE = JavaScript::QuickJS::RegExp
1066              
1067             SV*
1068             exec (SV* self_sv, SV* specimen_sv)
1069             ALIAS:
1070             test = 1
1071             CODE:
1072 4           perl_qjs_jsobj_s* pqjs = exs_structref_ptr(self_sv);
1073 4           JSContext *ctx = pqjs->ctx;
1074              
1075             STRLEN specimen_len;
1076 4 50         const char* specimen = SvPVutf8(specimen_sv, specimen_len);
1077              
1078             /* TODO: optimize? */
1079 4 100         JSAtom prop = JS_NewAtom(ctx, ix ? "test" : "exec");
1080              
1081 4           JSValue specimen_js = JS_NewStringLen(ctx, specimen, specimen_len);
1082              
1083 4           JSValue jsret = JS_Invoke(
1084             ctx,
1085             pqjs->jsobj,
1086             prop,
1087             1,
1088             &specimen_js
1089             );
1090              
1091 4           JS_FreeValue(ctx, specimen_js);
1092 4           JS_FreeAtom(ctx, prop);
1093              
1094 4           RETVAL = _return_jsvalue_or_croak(aTHX_ pqjs->ctx, jsret);
1095              
1096             OUTPUT:
1097             RETVAL
1098              
1099             SV*
1100             flags( SV* self_sv)
1101             ALIAS:
1102             dotAll = 1
1103             global = 2
1104             hasIndices = 3
1105             ignoreCase = 4
1106             multiline = 5
1107             source = 6
1108             sticky = 7
1109             unicode = 8
1110             lastIndex = 9
1111             CODE:
1112 8           perl_qjs_jsobj_s* pqjs = exs_structref_ptr(self_sv);
1113              
1114 8           JSValue myret = JS_GetPropertyStr(pqjs->ctx, pqjs->jsobj, _REGEXP_ACCESSORS[ix]);
1115              
1116 8           SV* err = NULL;
1117              
1118 8           RETVAL = _JSValue_to_SV(aTHX_ pqjs->ctx, myret, &err);
1119              
1120 8           JS_FreeValue(pqjs->ctx, myret);
1121              
1122 8 50         if (err) croak_sv(err);
1123              
1124             OUTPUT:
1125             RETVAL
1126              
1127             # ----------------------------------------------------------------------
1128              
1129             MODULE = JavaScript::QuickJS PACKAGE = JavaScript::QuickJS::JSObject
1130              
1131             void
1132             DESTROY( SV* self_sv )
1133             CODE:
1134 11           perl_qjs_jsobj_s* pqjs = exs_structref_ptr(self_sv);
1135              
1136 11 50         if (PL_dirty && pqjs->pid == getpid()) {
    0          
1137 0           warn("DESTROYing %" SVf " at global destruction; memory leak likely!\n", self_sv);
1138             }
1139              
1140 11           JS_FreeValue(pqjs->ctx, pqjs->jsobj);
1141              
1142 11           _free_jsctx(aTHX_ pqjs->ctx);
1143              
1144             # ----------------------------------------------------------------------
1145              
1146             MODULE = JavaScript::QuickJS PACKAGE = JavaScript::QuickJS::Function
1147              
1148             SV*
1149             _give_self( SV* self_sv, ... )
1150             CODE:
1151 1           RETVAL = SvREFCNT_inc(self_sv);
1152             OUTPUT:
1153             RETVAL
1154              
1155             SV*
1156             length( SV* self_sv)
1157             ALIAS:
1158             name = 1
1159             CODE:
1160 4           perl_qjs_jsobj_s* pqjs = exs_structref_ptr(self_sv);
1161              
1162 4           JSValue myret = JS_GetPropertyStr(pqjs->ctx, pqjs->jsobj, _FUNCTION_ACCESSORS[ix]);
1163              
1164 4           SV* err = NULL;
1165              
1166 4           RETVAL = _JSValue_to_SV(aTHX_ pqjs->ctx, myret, &err);
1167              
1168 4           JS_FreeValue(pqjs->ctx, myret);
1169              
1170 4 50         if (err) croak_sv(err);
1171              
1172             OUTPUT:
1173             RETVAL
1174              
1175             SV*
1176             call( SV* self_sv, SV* this_sv=&PL_sv_undef, ... )
1177             CODE:
1178 9           perl_qjs_jsobj_s* pqjs = exs_structref_ptr(self_sv);
1179              
1180 9           U32 params_count = items - FUNC_CALL_INITIAL_ARGS;
1181              
1182 9           SV* error = NULL;
1183              
1184 9           JSValue thisjs = _sv_to_jsvalue(aTHX_ pqjs->ctx, this_sv, &error);
1185 9 50         if (error) croak_sv(error);
1186              
1187 9           JSValue jsvars[params_count];
1188              
1189 9           _svs_to_jsvars( aTHX_ pqjs->ctx, params_count, &ST(FUNC_CALL_INITIAL_ARGS), jsvars );
1190              
1191 8           JSValue jsret = JS_Call(pqjs->ctx, pqjs->jsobj, thisjs, params_count, jsvars);
1192              
1193 8           RETVAL = _return_jsvalue_or_croak(aTHX_ pqjs->ctx, jsret);
1194              
1195 8           JS_FreeValue(pqjs->ctx, thisjs);
1196              
1197 16 100         for (uint32_t i=0; i
1198 8           JS_FreeValue(pqjs->ctx, jsvars[i]);
1199             }
1200              
1201             OUTPUT:
1202             RETVAL