quickjs-tart

quickjs-based runtime for wallet-core logic
Log | Files | Refs | README | LICENSE

commit 7c312df422572cf867f29a1d80693e8a77f7fb2a
parent 89007660998db0ee55f0ab3b34bb1a84f86fd3c4
Author: bellard <6490144+bellard@users.noreply.github.com>
Date:   Sun,  6 Sep 2020 19:10:15 +0200

2020-09-06 release
Diffstat:
MChangelog | 9+++++++++
MTODO | 4++--
MVERSION | 2+-
Mcutils.c | 21++++++++++++++++-----
Mdoc/jsbignum.html | 4++--
Mdoc/jsbignum.pdf | 0
Mdoc/quickjs.html | 28++++++++++++++++++----------
Mdoc/quickjs.pdf | 0
Mdoc/quickjs.texi | 23+++++++++++++++--------
Mlibregexp.c | 20+++++++++++++++++---
Mlibregexp.h | 1+
Mlibunicode.c | 26++++++++++++++++++++++----
Mqjs.c | 43++++++++++++++++++++++++++++---------------
Mqjsc.c | 74++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------
Mqjscalc.js | 4++--
Mquickjs-libc.c | 100++++++++++++++++++++++++++++++++++++++++++++++---------------------------------
Mquickjs-libc.h | 3++-
Mquickjs-opcode.h | 3++-
Mquickjs.c | 884+++++++++++++++++++++++++++++++++++++++++++++++++++----------------------------
Mquickjs.h | 10++++++++++
Mrun-test262.c | 11++++++++++-
Mtest262.conf | 5+++--
Mtests/test_builtin.js | 2++
Mtests/test_language.js | 19+++++++++++++++++++
Mtests/test_worker.js | 33+--------------------------------
Atests/test_worker_module.js | 31+++++++++++++++++++++++++++++++
26 files changed, 898 insertions(+), 462 deletions(-)

diff --git a/Changelog b/Changelog @@ -1,3 +1,12 @@ +2020-09-06: + +- added logical assignment operators +- added IsHTMLDDA support +- faster for-of loops +- os.Worker now takes a module filename as parameter +- qjsc: added -D option to compile dynamically loaded modules or workers +- misc bug fixes + 2020-07-05: - modified JS_GetPrototype() to return a live value diff --git a/TODO b/TODO @@ -74,5 +74,5 @@ REPL: Test262o: 0/11262 errors, 463 excluded Test262o commit: 7da91bceb9ce7613f87db47ddd1292a2dda58b42 (es5-tests branch) -Test262: 30/71095 errors, 870 excluded, 549 skipped -Test262 commit: 281eb10b2844929a7c0ac04527f5b42ce56509fd +Test262: 30/71748 errors, 868 excluded, 474 skipped +Test262 commit: 24c67328062383079ada85f4d253eb0526fd209b diff --git a/VERSION b/VERSION @@ -1 +1 @@ -2020-07-05 +2020-09-06 diff --git a/cutils.c b/cutils.c @@ -258,19 +258,30 @@ int unicode_from_utf8(const uint8_t *p, int max_len, const uint8_t **pp) return c; } switch(c) { - case 0xc0 ... 0xdf: + case 0xc0: case 0xc1: case 0xc2: case 0xc3: + case 0xc4: case 0xc5: case 0xc6: case 0xc7: + case 0xc8: case 0xc9: case 0xca: case 0xcb: + case 0xcc: case 0xcd: case 0xce: case 0xcf: + case 0xd0: case 0xd1: case 0xd2: case 0xd3: + case 0xd4: case 0xd5: case 0xd6: case 0xd7: + case 0xd8: case 0xd9: case 0xda: case 0xdb: + case 0xdc: case 0xdd: case 0xde: case 0xdf: l = 1; break; - case 0xe0 ... 0xef: + case 0xe0: case 0xe1: case 0xe2: case 0xe3: + case 0xe4: case 0xe5: case 0xe6: case 0xe7: + case 0xe8: case 0xe9: case 0xea: case 0xeb: + case 0xec: case 0xed: case 0xee: case 0xef: l = 2; break; - case 0xf0 ... 0xf7: + case 0xf0: case 0xf1: case 0xf2: case 0xf3: + case 0xf4: case 0xf5: case 0xf6: case 0xf7: l = 3; break; - case 0xf8 ... 0xfb: + case 0xf8: case 0xf9: case 0xfa: case 0xfb: l = 4; break; - case 0xfc ... 0xfd: + case 0xfc: case 0xfd: l = 5; break; default: diff --git a/doc/jsbignum.html b/doc/jsbignum.html @@ -1,8 +1,7 @@ <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> -<!-- Created by GNU Texinfo 6.5, http://www.gnu.org/software/texinfo/ --> +<!-- Created by GNU Texinfo 6.1, http://www.gnu.org/software/texinfo/ --> <head> -<meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <title>Javascript Bignum Extensions</title> <meta name="description" content="Javascript Bignum Extensions"> @@ -10,6 +9,7 @@ <meta name="resource-type" content="document"> <meta name="distribution" content="global"> <meta name="Generator" content="makeinfo"> +<meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <link href="#SEC_Contents" rel="contents" title="Table of Contents"> <style type="text/css"> <!-- diff --git a/doc/jsbignum.pdf b/doc/jsbignum.pdf Binary files differ. diff --git a/doc/quickjs.html b/doc/quickjs.html @@ -1,8 +1,7 @@ <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> -<!-- Created by GNU Texinfo 6.5, http://www.gnu.org/software/texinfo/ --> +<!-- Created by GNU Texinfo 6.1, http://www.gnu.org/software/texinfo/ --> <head> -<meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <title>QuickJS Javascript Engine</title> <meta name="description" content="QuickJS Javascript Engine"> @@ -10,6 +9,7 @@ <meta name="resource-type" content="document"> <meta name="distribution" content="global"> <meta name="Generator" content="makeinfo"> +<meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <link href="#SEC_Contents" rel="contents" title="Table of Contents"> <style type="text/css"> <!-- @@ -237,7 +237,7 @@ source is <code>import</code>. </dd> <dt><code>--bignum</code></dt> <dd><p>Enable the bignum extensions: BigDecimal object, BigFloat object and -the <code>&quot;use bigint&quot;</code> and <code>&quot;use math&quot;</code> directives. +the <code>&quot;use math&quot;</code> directive. </p> </dd> <dt><code>-I file</code></dt> @@ -293,6 +293,13 @@ executable file. <dd><p>Compile as Javascript module (default=autodetect). </p> </dd> +<dt><code>-D module_name</code></dt> +<dd><p>Compile a dynamically loaded module and its dependencies. This option +is needed when your code uses the <code>import</code> keyword or the +<code>os.Worker</code> constructor because the compiler cannot statically +find the name of the dynamically loaded modules. +</p> +</dd> <dt><code>-M module_name[,cname]</code></dt> <dd><p>Add initialization code for an external C module. See the <code>c_module</code> example. @@ -314,7 +321,7 @@ when the <code>-fno-x</code> options are used. </dd> <dt><code>-fbignum</code></dt> <dd><p>Enable the bignum extensions: BigDecimal object, BigFloat object and -the <code>&quot;use bigint&quot;</code> and <code>&quot;use math&quot;</code> directives. +the <code>&quot;use math&quot;</code> directive. </p> </dd> </dl> @@ -989,13 +996,14 @@ to the timer. <code>&quot;win32&quot;</code> or <code>&quot;js&quot;</code>. </p> </dd> -<dt><code>Worker(source)</code></dt> +<dt><code>Worker(module_filename)</code></dt> <dd><p>Constructor to create a new thread (worker) with an API close to the -<code>WebWorkers</code>. <code>source</code> is a string containing the module -source which is executed in the newly created thread. Threads normally -don&rsquo;t share any data and communicate between each other with -messages. Nested workers are not supported. An example is available in -<samp>tests/test_worker.js</samp>. +<code>WebWorkers</code>. <code>module_filename</code> is a string specifying the +module filename which is executed in the newly created thread. As for +dynamically imported module, it is relative to the current script or +module path. Threads normally don&rsquo;t share any data and communicate +between each other with messages. Nested workers are not supported. An +example is available in <samp>tests/test_worker.js</samp>. </p> <p>The worker class has the following static properties: </p> diff --git a/doc/quickjs.pdf b/doc/quickjs.pdf Binary files differ. diff --git a/doc/quickjs.texi b/doc/quickjs.texi @@ -120,7 +120,7 @@ Load as ES6 script (default=autodetect). @item --bignum Enable the bignum extensions: BigDecimal object, BigFloat object and -the @code{"use bigint"} and @code{"use math"} directives. +the @code{"use math"} directive. @item -I file @item --include file @@ -167,6 +167,12 @@ Set the C name of the generated data. @item -m Compile as Javascript module (default=autodetect). +@item -D module_name +Compile a dynamically loaded module and its dependencies. This option +is needed when your code uses the @code{import} keyword or the +@code{os.Worker} constructor because the compiler cannot statically +find the name of the dynamically loaded modules. + @item -M module_name[,cname] Add initialization code for an external C module. See the @code{c_module} example. @@ -184,7 +190,7 @@ Disable selected language features to produce a smaller executable file. @item -fbignum Enable the bignum extensions: BigDecimal object, BigFloat object and -the @code{"use bigint"} and @code{"use math"} directives. +the @code{"use math"} directive. @end table @@ -764,13 +770,14 @@ Cancel a timer. Return a string representing the platform: @code{"linux"}, @code{"darwin"}, @code{"win32"} or @code{"js"}. -@item Worker(source) +@item Worker(module_filename) Constructor to create a new thread (worker) with an API close to the -@code{WebWorkers}. @code{source} is a string containing the module -source which is executed in the newly created thread. Threads normally -don't share any data and communicate between each other with -messages. Nested workers are not supported. An example is available in -@file{tests/test_worker.js}. +@code{WebWorkers}. @code{module_filename} is a string specifying the +module filename which is executed in the newly created thread. As for +dynamically imported module, it is relative to the current script or +module path. Threads normally don't share any data and communicate +between each other with messages. Nested workers are not supported. An +example is available in @file{tests/test_worker.js}. The worker class has the following static properties: diff --git a/libregexp.c b/libregexp.c @@ -569,7 +569,8 @@ int lre_parse_escape(const uint8_t **pp, int allow_utf16) } } break; - case '0' ... '7': + case '0': case '1': case '2': case '3': + case '4': case '5': case '6': case '7': c -= '0'; if (allow_utf16 == 2) { /* only accept \0 not followed by digit */ @@ -1410,7 +1411,9 @@ static int re_parse_term(REParseState *s, BOOL is_backward_dir) } } goto normal_char; - case '1' ... '9': + case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': + case '9': { const uint8_t *q = ++p; @@ -1434,7 +1437,7 @@ static int re_parse_term(REParseState *s, BOOL is_backward_dir) } goto normal_char; } - return re_parse_error(s, "back reference out of range in reguar expression"); + return re_parse_error(s, "back reference out of range in regular expression"); } emit_back_reference: last_atom_start = s->byte_code.size; @@ -2533,6 +2536,17 @@ int lre_get_flags(const uint8_t *bc_buf) return bc_buf[RE_HEADER_FLAGS]; } +/* Return NULL if no group names. Otherwise, return a pointer to + 'capture_count - 1' zero terminated UTF-8 strings. */ +const char *lre_get_groupnames(const uint8_t *bc_buf) +{ + uint32_t re_bytecode_len; + if ((lre_get_flags(bc_buf) & LRE_FLAG_NAMED_GROUPS) == 0) + return NULL; + re_bytecode_len = get_u32(bc_buf + 3); + return (const char *)(bc_buf + 7 + re_bytecode_len); +} + #ifdef TEST BOOL lre_check_stack_overflow(void *opaque, size_t alloca_size) diff --git a/libregexp.h b/libregexp.h @@ -44,6 +44,7 @@ uint8_t *lre_compile(int *plen, char *error_msg, int error_msg_size, void *opaque); int lre_get_capture_count(const uint8_t *bc_buf); int lre_get_flags(const uint8_t *bc_buf); +const char *lre_get_groupnames(const uint8_t *bc_buf); int lre_exec(uint8_t **capture, const uint8_t *bc_buf, const uint8_t *cbuf, int cindex, int clen, int cbuf_type, void *opaque); diff --git a/libunicode.c b/libunicode.c @@ -527,7 +527,13 @@ static int unicode_decomp_entry(uint32_t *res, uint32_t c, } else { d = unicode_decomp_data + unicode_decomp_table2[idx]; switch(type) { - case DECOMP_TYPE_L1 ... DECOMP_TYPE_L7: + case DECOMP_TYPE_L1: + case DECOMP_TYPE_L2: + case DECOMP_TYPE_L3: + case DECOMP_TYPE_L4: + case DECOMP_TYPE_L5: + case DECOMP_TYPE_L6: + case DECOMP_TYPE_L7: l = type - DECOMP_TYPE_L1 + 1; d += (c - code) * l * 2; for(i = 0; i < l; i++) { @@ -535,7 +541,8 @@ static int unicode_decomp_entry(uint32_t *res, uint32_t c, return 0; } return l; - case DECOMP_TYPE_LL1 ... DECOMP_TYPE_LL2: + case DECOMP_TYPE_LL1: + case DECOMP_TYPE_LL2: { uint32_t k, p; l = type - DECOMP_TYPE_LL1 + 1; @@ -551,7 +558,11 @@ static int unicode_decomp_entry(uint32_t *res, uint32_t c, } } return l; - case DECOMP_TYPE_S1 ... DECOMP_TYPE_S5: + case DECOMP_TYPE_S1: + case DECOMP_TYPE_S2: + case DECOMP_TYPE_S3: + case DECOMP_TYPE_S4: + case DECOMP_TYPE_S5: l = type - DECOMP_TYPE_S1 + 1; d += (c - code) * l; for(i = 0; i < l; i++) { @@ -582,7 +593,14 @@ static int unicode_decomp_entry(uint32_t *res, uint32_t c, case DECOMP_TYPE_B18: l = 18; goto decomp_type_b; - case DECOMP_TYPE_B1 ... DECOMP_TYPE_B8: + case DECOMP_TYPE_B1: + case DECOMP_TYPE_B2: + case DECOMP_TYPE_B3: + case DECOMP_TYPE_B4: + case DECOMP_TYPE_B5: + case DECOMP_TYPE_B6: + case DECOMP_TYPE_B7: + case DECOMP_TYPE_B8: l = type - DECOMP_TYPE_B1 + 1; decomp_type_b: { diff --git a/qjs.c b/qjs.c @@ -46,6 +46,7 @@ extern const uint32_t qjsc_repl_size; #ifdef CONFIG_BIGNUM extern const uint8_t qjsc_qjscalc[]; extern const uint32_t qjsc_qjscalc_size; +static int bignum_ext; #endif static int eval_buf(JSContext *ctx, const void *buf, int buf_len, @@ -101,6 +102,27 @@ static int eval_file(JSContext *ctx, const char *filename, int module) return ret; } +/* also used to initialize the worker context */ +static JSContext *JS_NewCustomContext(JSRuntime *rt) +{ + JSContext *ctx; + ctx = JS_NewContext(rt); + if (!ctx) + return NULL; +#ifdef CONFIG_BIGNUM + if (bignum_ext) { + JS_AddIntrinsicBigFloat(ctx); + JS_AddIntrinsicBigDecimal(ctx); + JS_AddIntrinsicOperators(ctx); + JS_EnableBignumExt(ctx, TRUE); + } +#endif + /* system modules */ + js_init_module_std(ctx, "std"); + js_init_module_os(ctx, "os"); + return ctx; +} + #if defined(__APPLE__) #define MALLOC_OVERHEAD 0 #else @@ -294,7 +316,7 @@ int main(int argc, char **argv) char *include_list[32]; int i, include_count = 0; #ifdef CONFIG_BIGNUM - int load_jscalc, bignum_ext = 0; + int load_jscalc; #endif size_t stack_size = 0; @@ -426,6 +448,9 @@ int main(int argc, char **argv) } } + if (load_jscalc) + bignum_ext = 1; + if (trace_memory) { js_trace_malloc_init(&trace_data); rt = JS_NewRuntime2(&trace_mf, &trace_data); @@ -440,22 +465,14 @@ int main(int argc, char **argv) JS_SetMemoryLimit(rt, memory_limit); if (stack_size != 0) JS_SetMaxStackSize(rt, stack_size); + js_std_set_worker_new_context_func(JS_NewCustomContext); js_std_init_handlers(rt); - ctx = JS_NewContext(rt); + ctx = JS_NewCustomContext(rt); if (!ctx) { fprintf(stderr, "qjs: cannot allocate JS context\n"); exit(2); } -#ifdef CONFIG_BIGNUM - if (bignum_ext || load_jscalc) { - JS_AddIntrinsicBigFloat(ctx); - JS_AddIntrinsicBigDecimal(ctx); - JS_AddIntrinsicOperators(ctx); - JS_EnableBignumExt(ctx, TRUE); - } -#endif - /* loader for ES6 modules */ JS_SetModuleLoaderFunc(rt, NULL, js_module_loader, NULL); @@ -472,10 +489,6 @@ int main(int argc, char **argv) #endif js_std_add_helpers(ctx, argc - optind, argv + optind); - /* system modules */ - js_init_module_std(ctx, "std"); - js_init_module_os(ctx, "os"); - /* make 'std' and 'os' visible to non module code */ if (load_std) { const char *str = "import * as std from 'std';\n" diff --git a/qjsc.c b/qjsc.c @@ -326,6 +326,7 @@ static const char main_c_template1[] = " JSRuntime *rt;\n" " JSContext *ctx;\n" " rt = JS_NewRuntime();\n" + " js_std_set_worker_new_context_func(JS_NewCustomContext);\n" " js_std_init_handlers(rt);\n" ; @@ -349,6 +350,7 @@ void help(void) "-o output set the output filename\n" "-N cname set the C name of the generated data\n" "-m compile as Javascript module (default=autodetect)\n" + "-D module_name compile a dynamically loaded module or worker\n" "-M module_name[,cname] add initialization code for an external C module\n" "-x byte swapped output\n" "-p prefix set the prefix of the generated C names\n" @@ -494,6 +496,7 @@ int main(int argc, char **argv) #ifdef CONFIG_BIGNUM BOOL bignum_ext = FALSE; #endif + namelist_t dynamic_module_list; out_filename = NULL; output_type = OUTPUT_EXECUTABLE; @@ -504,13 +507,14 @@ int main(int argc, char **argv) verbose = 0; use_lto = FALSE; stack_size = 0; + memset(&dynamic_module_list, 0, sizeof(dynamic_module_list)); /* add system modules */ namelist_add(&cmodule_list, "std", "std", 0); namelist_add(&cmodule_list, "os", "os", 0); for(;;) { - c = getopt(argc, argv, "ho:cN:f:mxevM:p:S:"); + c = getopt(argc, argv, "ho:cN:f:mxevM:p:S:D:"); if (c == -1) break; switch(c) { @@ -576,6 +580,9 @@ int main(int argc, char **argv) namelist_add(&cmodule_list, path, cname, 0); } break; + case 'D': + namelist_add(&dynamic_module_list, optarg, NULL, 0); + break; case 'x': byte_swap = TRUE; break; @@ -656,22 +663,22 @@ int main(int argc, char **argv) cname = NULL; } - if (output_type != OUTPUT_C) { - fputs(main_c_template1, fo); - fprintf(fo, " ctx = JS_NewContextRaw(rt);\n"); - - if (stack_size != 0) { - fprintf(fo, " JS_SetMaxStackSize(rt, %u);\n", - (unsigned int)stack_size); - } - - /* add the module loader if necessary */ - if (feature_bitmap & (1 << FE_MODULE_LOADER)) { - fprintf(fo, " JS_SetModuleLoaderFunc(rt, NULL, js_module_loader, NULL);\n"); + for(i = 0; i < dynamic_module_list.count; i++) { + if (!jsc_module_loader(ctx, dynamic_module_list.array[i].name, NULL)) { + fprintf(stderr, "Could not load dynamic module '%s'\n", + dynamic_module_list.array[i].name); + exit(1); } - + } + + if (output_type != OUTPUT_C) { + fprintf(fo, + "static JSContext *JS_NewCustomContext(JSRuntime *rt)\n" + "{\n" + " JSContext *ctx = JS_NewContextRaw(rt);\n" + " if (!ctx)\n" + " return NULL;\n"); /* add the basic objects */ - fprintf(fo, " JS_AddIntrinsicBaseObjects(ctx);\n"); for(i = 0; i < countof(feature_list); i++) { if ((feature_bitmap & ((uint64_t)1 << i)) && @@ -689,8 +696,8 @@ int main(int argc, char **argv) " JS_EnableBignumExt(ctx, 1);\n"); } #endif - fprintf(fo, " js_std_add_helpers(ctx, argc, argv);\n"); - + /* add the precompiled modules (XXX: could modify the module + loader instead) */ for(i = 0; i < init_module_list.count; i++) { namelist_entry_t *e = &init_module_list.array[i]; /* initialize the static C modules */ @@ -702,12 +709,39 @@ int main(int argc, char **argv) " }\n", e->short_name, e->short_name, e->name); } + for(i = 0; i < cname_list.count; i++) { + namelist_entry_t *e = &cname_list.array[i]; + if (e->flags) { + fprintf(fo, " js_std_eval_binary(ctx, %s, %s_size, 1);\n", + e->name, e->name); + } + } + fprintf(fo, + " return ctx;\n" + "}\n\n"); + + fputs(main_c_template1, fo); + + if (stack_size != 0) { + fprintf(fo, " JS_SetMaxStackSize(rt, %u);\n", + (unsigned int)stack_size); + } + + /* add the module loader if necessary */ + if (feature_bitmap & (1 << FE_MODULE_LOADER)) { + fprintf(fo, " JS_SetModuleLoaderFunc(rt, NULL, js_module_loader, NULL);\n"); + } + + fprintf(fo, + " ctx = JS_NewCustomContext(rt);\n" + " js_std_add_helpers(ctx, argc, argv);\n"); for(i = 0; i < cname_list.count; i++) { namelist_entry_t *e = &cname_list.array[i]; - fprintf(fo, " js_std_eval_binary(ctx, %s, %s_size, %s);\n", - e->name, e->name, - e->flags ? "1" : "0"); + if (!e->flags) { + fprintf(fo, " js_std_eval_binary(ctx, %s, %s_size, 0);\n", + e->name, e->name); + } } fputs(main_c_template2, fo); } diff --git a/qjscalc.js b/qjscalc.js @@ -1826,7 +1826,7 @@ var Integer, Float, Fraction, Complex, Mod, Polynomial, PolyMod, RationalFunctio j = emin + i; if (j == -1) { if (a[i] != 0) - throw RangError("cannot represent integ(1/X)"); + throw RangeError("cannot represent integ(1/X)"); } else { r[i] = a[i] / (j + 1); } @@ -1853,7 +1853,7 @@ var Integer, Float, Fraction, Complex, Mod, Polynomial, PolyMod, RationalFunctio log() { var a = this, r; if (a.emin != 0) - throw Range("log argument must have a non zero constant term"); + throw RangeError("log argument must have a non zero constant term"); r = integ(deriv(a) / a); /* add the constant term */ r += global.log(a[0]); diff --git a/quickjs-libc.c b/quickjs-libc.c @@ -2993,9 +2993,8 @@ typedef struct { } JSWorkerData; typedef struct { - /* source code of the worker */ - char *eval_buf; - size_t eval_buf_len; + char *filename; /* module filename */ + char *basename; /* module base name */ JSWorkerMessagePipe *recv_pipe, *send_pipe; } WorkerFuncArgs; @@ -3005,6 +3004,7 @@ typedef struct { } JSSABHeader; static JSClassID js_worker_class_id; +static JSContext *(*js_worker_new_context_func)(JSRuntime *rt); static int atomic_add_int(int *ptr, int v) { @@ -3136,7 +3136,6 @@ static void *worker_func(void *opaque) JSRuntime *rt; JSThreadState *ts; JSContext *ctx; - JSValue retval; rt = JS_NewRuntime(); if (rt == NULL) { @@ -3145,12 +3144,16 @@ static void *worker_func(void *opaque) } js_std_init_handlers(rt); + JS_SetModuleLoaderFunc(rt, NULL, js_module_loader, NULL); + /* set the pipe to communicate with the parent */ ts = JS_GetRuntimeOpaque(rt); ts->recv_pipe = args->recv_pipe; ts->send_pipe = args->send_pipe; - ctx = JS_NewContext(rt); + /* function pointer to avoid linking the whole JS_NewContext() if + not needed */ + ctx = js_worker_new_context_func(rt); if (ctx == NULL) { fprintf(stderr, "JS_NewContext failure"); } @@ -3159,18 +3162,11 @@ static void *worker_func(void *opaque) js_std_add_helpers(ctx, -1, NULL); - /* system modules */ - js_init_module_std(ctx, "std"); - js_init_module_os(ctx, "os"); - - retval = JS_Eval(ctx, args->eval_buf, args->eval_buf_len, - "<worker>", JS_EVAL_TYPE_MODULE); - free(args->eval_buf); - free(args); - - if (JS_IsException(retval)) + if (!JS_RunModule(ctx, args->basename, args->filename)) js_std_dump_error(ctx); - JS_FreeValue(ctx, retval); + free(args->filename); + free(args->basename); + free(args); js_std_loop(ctx); @@ -3216,52 +3212,53 @@ static JSValue js_worker_ctor(JSContext *ctx, JSValueConst new_target, int argc, JSValueConst *argv) { JSRuntime *rt = JS_GetRuntime(ctx); - WorkerFuncArgs *args; - const char *str; - size_t str_len; + WorkerFuncArgs *args = NULL; pthread_t tid; pthread_attr_t attr; JSValue obj = JS_UNDEFINED; int ret; + const char *filename = NULL, *basename; + JSAtom basename_atom; /* XXX: in order to avoid problems with resource liberation, we don't support creating workers inside workers */ if (!is_main_thread(rt)) return JS_ThrowTypeError(ctx, "cannot create a worker inside a worker"); + + /* base name, assuming the calling function is a normal JS + function */ + basename_atom = JS_GetScriptOrModuleName(ctx, 1); + if (basename_atom == JS_ATOM_NULL) { + return JS_ThrowTypeError(ctx, "could not determine calling script or module name"); + } + basename = JS_AtomToCString(ctx, basename_atom); + JS_FreeAtom(ctx, basename_atom); + if (!basename) + goto fail; - /* script source */ - - str = JS_ToCStringLen(ctx, &str_len, argv[0]); - if (!str) - return JS_EXCEPTION; + /* module name */ + filename = JS_ToCString(ctx, argv[0]); + if (!filename) + goto fail; args = malloc(sizeof(*args)); - if (!args) { - JS_ThrowOutOfMemory(ctx); - goto fail; - } + if (!args) + goto oom_fail; memset(args, 0, sizeof(*args)); - args->eval_buf = malloc(str_len + 1); - if (!args->eval_buf) { - JS_ThrowOutOfMemory(ctx); - goto fail; - } - memcpy(args->eval_buf, str, str_len + 1); - args->eval_buf_len = str_len; - JS_FreeCString(ctx, str); - str = NULL; + args->filename = strdup(filename); + args->basename = strdup(basename); /* ports */ args->recv_pipe = js_new_message_pipe(); if (!args->recv_pipe) - goto fail; + goto oom_fail; args->send_pipe = js_new_message_pipe(); if (!args->send_pipe) - goto fail; + goto oom_fail; obj = js_worker_ctor_internal(ctx, new_target, args->send_pipe, args->recv_pipe); - if (JS_IsUndefined(obj)) + if (JS_IsException(obj)) goto fail; pthread_attr_init(&attr); @@ -3273,11 +3270,17 @@ static JSValue js_worker_ctor(JSContext *ctx, JSValueConst new_target, JS_ThrowTypeError(ctx, "could not create worker"); goto fail; } + JS_FreeCString(ctx, basename); + JS_FreeCString(ctx, filename); return obj; + oom_fail: + JS_ThrowOutOfMemory(ctx); fail: - JS_FreeCString(ctx, str); + JS_FreeCString(ctx, basename); + JS_FreeCString(ctx, filename); if (args) { - free(args->eval_buf); + free(args->filename); + free(args->basename); js_free_message_pipe(args->recv_pipe); js_free_message_pipe(args->send_pipe); free(args); @@ -3417,6 +3420,13 @@ static const JSCFunctionListEntry js_worker_proto_funcs[] = { #endif /* USE_WORKER */ +void js_std_set_worker_new_context_func(JSContext *(*func)(JSRuntime *rt)) +{ +#ifdef USE_WORKER + js_worker_new_context_func = func; +#endif +} + #if defined(_WIN32) #define OS_PLATFORM "win32" #elif defined(__APPLE__) @@ -3668,6 +3678,12 @@ void js_std_free_handlers(JSRuntime *rt) free_timer(rt, th); } +#ifdef USE_WORKER + /* XXX: free port_list ? */ + js_free_message_pipe(ts->recv_pipe); + js_free_message_pipe(ts->send_pipe); +#endif + free(ts); JS_SetRuntimeOpaque(rt, NULL); /* fail safe */ } diff --git a/quickjs-libc.h b/quickjs-libc.h @@ -50,7 +50,8 @@ void js_std_eval_binary(JSContext *ctx, const uint8_t *buf, size_t buf_len, void js_std_promise_rejection_tracker(JSContext *ctx, JSValueConst promise, JSValueConst reason, JS_BOOL is_handled, void *opaque); - +void js_std_set_worker_new_context_func(JSContext *(*func)(JSRuntime *rt)); + #ifdef __cplusplus } /* extern "C" { */ #endif diff --git a/quickjs-opcode.h b/quickjs-opcode.h @@ -359,7 +359,8 @@ DEF( call3, 1, 1, 1, npopx) DEF( is_undefined, 1, 1, 1, none) DEF( is_null, 1, 1, 1, none) -DEF( is_function, 1, 1, 1, none) +DEF(typeof_is_undefined, 1, 1, 1, none) +DEF( typeof_is_function, 1, 1, 1, none) #endif #undef DEF diff --git a/quickjs.c b/quickjs.c @@ -825,8 +825,8 @@ typedef struct JSShapeProperty { } JSShapeProperty; struct JSShape { - uint32_t prop_hash_end[0]; /* hash table of size hash_mask + 1 - before the start of the structure. */ + /* hash table of size hash_mask + 1 before the start of the + structure (see prop_hash_end()). */ JSGCObjectHeader header; /* true if the shape is inserted in the shape hash table. If not, JSShape.hash is not valid */ @@ -859,6 +859,7 @@ struct JSObject { uint8_t is_constructor : 1; /* TRUE if object is a constructor function */ uint8_t is_uncatchable_error : 1; /* if TRUE, error is not catchable */ uint8_t tmp_mark : 1; /* used in JS_WriteObjectRec() */ + uint8_t is_HTMLDDA : 1; /* specific annex B IsHtmlDDA behavior */ uint16_t class_id; /* see JS_CLASS_x */ }; }; @@ -931,7 +932,7 @@ struct JSObject { /* byte sizes: 40/48/72 */ }; enum { - JS_ATOM_NULL, + __JS_ATOM_NULL = JS_ATOM_NULL, #define DEF(name, str) JS_ATOM_ ## name, #include "quickjs-atom.h" #undef DEF @@ -4198,9 +4199,14 @@ static inline JSShape *get_shape_from_alloc(void *sh_alloc, size_t hash_size) return (JSShape *)(void *)((uint32_t *)sh_alloc + hash_size); } +static inline uint32_t *prop_hash_end(JSShape *sh) +{ + return (uint32_t *)sh; +} + static inline void *get_alloc_from_shape(JSShape *sh) { - return sh->prop_hash_end - ((intptr_t)sh->prop_hash_mask + 1); + return prop_hash_end(sh) - ((intptr_t)sh->prop_hash_mask + 1); } static inline JSShapeProperty *get_shape_prop(JSShape *sh) @@ -4311,7 +4317,7 @@ static no_inline JSShape *js_new_shape2(JSContext *ctx, JSObject *proto, if (proto) JS_DupValue(ctx, JS_MKPTR(JS_TAG_OBJECT, proto)); sh->proto = proto; - memset(sh->prop_hash_end - hash_size, 0, sizeof(sh->prop_hash_end[0]) * + memset(prop_hash_end(sh) - hash_size, 0, sizeof(prop_hash_end(sh)[0]) * hash_size); sh->prop_hash_mask = hash_size - 1; sh->prop_size = prop_size; @@ -4440,13 +4446,13 @@ static no_inline int resize_properties(JSContext *ctx, JSShape **psh, list_add_tail(&sh->header.link, &ctx->rt->gc_obj_list); new_hash_mask = new_hash_size - 1; sh->prop_hash_mask = new_hash_mask; - memset(sh->prop_hash_end - new_hash_size, 0, - sizeof(sh->prop_hash_end[0]) * new_hash_size); + memset(prop_hash_end(sh) - new_hash_size, 0, + sizeof(prop_hash_end(sh)[0]) * new_hash_size); for(i = 0, pr = sh->prop; i < sh->prop_count; i++, pr++) { if (pr->atom != JS_ATOM_NULL) { h = ((uintptr_t)pr->atom & new_hash_mask); - pr->hash_next = sh->prop_hash_end[-h - 1]; - sh->prop_hash_end[-h - 1] = i + 1; + pr->hash_next = prop_hash_end(sh)[-h - 1]; + prop_hash_end(sh)[-h - 1] = i + 1; } } js_free(ctx, get_alloc_from_shape(old_sh)); @@ -4500,8 +4506,8 @@ static int compact_properties(JSContext *ctx, JSObject *p) memcpy(sh, old_sh, sizeof(JSShape)); list_add_tail(&sh->header.link, &ctx->rt->gc_obj_list); - memset(sh->prop_hash_end - new_hash_size, 0, - sizeof(sh->prop_hash_end[0]) * new_hash_size); + memset(prop_hash_end(sh) - new_hash_size, 0, + sizeof(prop_hash_end(sh)[0]) * new_hash_size); j = 0; old_pr = old_sh->prop; @@ -4512,8 +4518,8 @@ static int compact_properties(JSContext *ctx, JSObject *p) pr->atom = old_pr->atom; pr->flags = old_pr->flags; h = ((uintptr_t)old_pr->atom & new_hash_mask); - pr->hash_next = sh->prop_hash_end[-h - 1]; - sh->prop_hash_end[-h - 1] = j + 1; + pr->hash_next = prop_hash_end(sh)[-h - 1]; + prop_hash_end(sh)[-h - 1] = j + 1; prop[j] = prop[i]; j++; pr++; @@ -4575,8 +4581,8 @@ static int add_shape_property(JSContext *ctx, JSShape **psh, /* add in hash table */ hash_mask = sh->prop_hash_mask; h = atom & hash_mask; - pr->hash_next = sh->prop_hash_end[-h - 1]; - sh->prop_hash_end[-h - 1] = sh->prop_count; + pr->hash_next = prop_hash_end(sh)[-h - 1]; + prop_hash_end(sh)[-h - 1] = sh->prop_count; return 0; } @@ -4693,6 +4699,7 @@ static JSValue JS_NewObjectFromShape(JSContext *ctx, JSShape *sh, JSClassID clas p->is_constructor = 0; p->is_uncatchable_error = 0; p->tmp_mark = 0; + p->is_HTMLDDA = 0; p->first_weak_ref = NULL; p->u.opaque = NULL; p->shape = sh; @@ -4731,7 +4738,19 @@ static JSValue JS_NewObjectFromShape(JSContext *ctx, JSShape *sh, JSClassID clas p->prop[0].u.value = JS_UNDEFINED; break; case JS_CLASS_ARGUMENTS: - case JS_CLASS_UINT8C_ARRAY ... JS_CLASS_FLOAT64_ARRAY: + case JS_CLASS_UINT8C_ARRAY: + case JS_CLASS_INT8_ARRAY: + case JS_CLASS_UINT8_ARRAY: + case JS_CLASS_INT16_ARRAY: + case JS_CLASS_UINT16_ARRAY: + case JS_CLASS_INT32_ARRAY: + case JS_CLASS_UINT32_ARRAY: +#ifdef CONFIG_BIGNUM + case JS_CLASS_BIG_INT64_ARRAY: + case JS_CLASS_BIG_UINT64_ARRAY: +#endif + case JS_CLASS_FLOAT32_ARRAY: + case JS_CLASS_FLOAT64_ARRAY: p->is_exotic = 1; p->fast_array = 1; p->u.array.u.ptr = NULL; @@ -5133,7 +5152,7 @@ static force_inline JSShapeProperty *find_own_property1(JSObject *p, intptr_t h; sh = p->shape; h = (uintptr_t)atom & sh->prop_hash_mask; - h = sh->prop_hash_end[-h - 1]; + h = prop_hash_end(sh)[-h - 1]; prop = get_shape_prop(sh); while (h) { pr = &prop[h - 1]; @@ -5154,7 +5173,7 @@ static force_inline JSShapeProperty *find_own_property(JSProperty **ppr, intptr_t h; sh = p->shape; h = (uintptr_t)atom & sh->prop_hash_mask; - h = sh->prop_hash_end[-h - 1]; + h = prop_hash_end(sh)[-h - 1]; prop = get_shape_prop(sh); while (h) { pr = &prop[h - 1]; @@ -7994,7 +8013,7 @@ static int delete_property(JSContext *ctx, JSObject *p, JSAtom atom) redo: sh = p->shape; h1 = atom & sh->prop_hash_mask; - h = sh->prop_hash_end[-h1 - 1]; + h = prop_hash_end(sh)[-h1 - 1]; prop = get_shape_prop(sh); lpr = NULL; lpr_idx = 0; /* prevent warning */ @@ -8015,7 +8034,7 @@ static int delete_property(JSContext *ctx, JSObject *p, JSAtom atom) lpr = get_shape_prop(sh) + lpr_idx; lpr->hash_next = pr->hash_next; } else { - sh->prop_hash_end[-h1 - 1] = pr->hash_next; + prop_hash_end(sh)[-h1 - 1] = pr->hash_next; } sh->deleted_prop_count++; /* free the entry */ @@ -8095,15 +8114,16 @@ static int call_setter(JSContext *ctx, JSObject *setter, } /* set the array length and remove the array elements if necessary. */ -static int set_array_length(JSContext *ctx, JSObject *p, JSProperty *prop, - JSValue val, int flags) +static int set_array_length(JSContext *ctx, JSObject *p, JSValue val, int flags) { uint32_t len, idx, cur_len; int i, ret; + /* Note: this call can reallocate the properties of 'p' */ ret = JS_ToArrayLengthFree(ctx, &len, val); if (ret) return -1; + if (likely(p->fast_array)) { uint32_t old_len = p->u.array.count; if (len < old_len) { @@ -8112,11 +8132,11 @@ static int set_array_length(JSContext *ctx, JSObject *p, JSProperty *prop, } p->u.array.count = len; } - prop->u.value = JS_NewUint32(ctx, len); + p->prop[0].u.value = JS_NewUint32(ctx, len); } else { /* Note: length is always a uint32 because the object is an array */ - JS_ToUint32(ctx, &cur_len, prop->u.value); + JS_ToUint32(ctx, &cur_len, p->prop[0].u.value); if (len < cur_len) { uint32_t d; JSShape *sh; @@ -8366,7 +8386,7 @@ retry: (JS_PROP_LENGTH | JS_PROP_WRITABLE)) { assert(p->class_id == JS_CLASS_ARRAY); assert(prop == JS_ATOM_length); - return set_array_length(ctx, p, pr, val, flags); + return set_array_length(ctx, p, val, flags); } else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_GETSET) { return call_setter(ctx, pr->u.getset.setter, this_obj, val, flags); } else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_VARREF) { @@ -9047,7 +9067,7 @@ int JS_DefineProperty(JSContext *ctx, JSValueConst this_obj, } if (prs->flags & JS_PROP_LENGTH) { if (flags & JS_PROP_HAS_VALUE) { - res = set_array_length(ctx, p, pr, JS_DupValue(ctx, val), + res = set_array_length(ctx, p, JS_DupValue(ctx, val), flags); } else { res = TRUE; @@ -9806,6 +9826,24 @@ static JSValue JS_ToPrimitive(JSContext *ctx, JSValueConst val, int hint) return JS_ToPrimitiveFree(ctx, JS_DupValue(ctx, val), hint); } +void JS_SetIsHTMLDDA(JSContext *ctx, JSValueConst obj) +{ + JSObject *p; + if (JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT) + return; + p = JS_VALUE_GET_OBJ(obj); + p->is_HTMLDDA = TRUE; +} + +static inline BOOL JS_IsHTMLDDA(JSContext *ctx, JSValueConst obj) +{ + JSObject *p; + if (JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT) + return FALSE; + p = JS_VALUE_GET_OBJ(obj); + return p->is_HTMLDDA; +} + static int JS_ToBoolFree(JSContext *ctx, JSValue val) { uint32_t tag = JS_VALUE_GET_TAG(val); @@ -9843,6 +9881,15 @@ static int JS_ToBoolFree(JSContext *ctx, JSValue val) return ret; } #endif + case JS_TAG_OBJECT: + { + JSObject *p = JS_VALUE_GET_OBJ(val); + BOOL ret; + ret = !p->is_HTMLDDA; + JS_FreeValue(ctx, val); + return ret; + } + break; default: if (JS_TAG_IS_FLOAT64(tag)) { double d = JS_VALUE_GET_FLOAT64(val); @@ -11602,7 +11649,19 @@ static __maybe_unused void JS_DumpObject(JSRuntime *rt, JSObject *p) case JS_CLASS_ARGUMENTS: JS_DumpValueShort(rt, p->u.array.u.values[i]); break; - case JS_CLASS_UINT8C_ARRAY ... JS_CLASS_FLOAT64_ARRAY: + case JS_CLASS_UINT8C_ARRAY: + case JS_CLASS_INT8_ARRAY: + case JS_CLASS_UINT8_ARRAY: + case JS_CLASS_INT16_ARRAY: + case JS_CLASS_UINT16_ARRAY: + case JS_CLASS_INT32_ARRAY: + case JS_CLASS_UINT32_ARRAY: +#ifdef CONFIG_BIGNUM + case JS_CLASS_BIG_INT64_ARRAY: + case JS_CLASS_BIG_UINT64_ARRAY: +#endif + case JS_CLASS_FLOAT32_ARRAY: + case JS_CLASS_FLOAT64_ARRAY: { int size = 1 << typed_array_size_log2(p->class_id); const uint8_t *b = p->u.array.u.uint8_ptr + i * size; @@ -13668,33 +13727,8 @@ static no_inline int js_relational_slow(JSContext *ctx, JSValue *sp, JS_FreeValue(ctx, op2); } else if ((tag1 <= JS_TAG_NULL || tag1 == JS_TAG_FLOAT64) && (tag2 <= JS_TAG_NULL || tag2 == JS_TAG_FLOAT64)) { - /* can use floating point comparison */ - double d1, d2; - if (tag1 == JS_TAG_FLOAT64) { - d1 = JS_VALUE_GET_FLOAT64(op1); - } else { - d1 = JS_VALUE_GET_INT(op1); - } - if (tag2 == JS_TAG_FLOAT64) { - d2 = JS_VALUE_GET_FLOAT64(op2); - } else { - d2 = JS_VALUE_GET_INT(op2); - } - switch(op) { - case OP_lt: - res = (d1 < d2); /* if NaN return false */ - break; - case OP_lte: - res = (d1 <= d2); /* if NaN return false */ - break; - case OP_gt: - res = (d1 > d2); /* if NaN return false */ - break; - default: - case OP_gte: - res = (d1 >= d2); /* if NaN return false */ - break; - } + /* fast path for float64/int */ + goto float64_compare; } else { if (((tag1 == JS_TAG_BIG_INT && tag2 == JS_TAG_STRING) || (tag2 == JS_TAG_BIG_INT && tag1 == JS_TAG_STRING)) && @@ -13727,20 +13761,51 @@ static no_inline int js_relational_slow(JSContext *ctx, JSValue *sp, } } - if (JS_VALUE_GET_TAG(op1) == JS_TAG_BIG_DECIMAL || - JS_VALUE_GET_TAG(op2) == JS_TAG_BIG_DECIMAL) { + tag1 = JS_VALUE_GET_NORM_TAG(op1); + tag2 = JS_VALUE_GET_NORM_TAG(op2); + + if (tag1 == JS_TAG_BIG_DECIMAL || tag2 == JS_TAG_BIG_DECIMAL) { res = ctx->rt->bigdecimal_ops.compare(ctx, op, op1, op2); if (res < 0) goto exception; - } else if (JS_VALUE_GET_TAG(op1) == JS_TAG_BIG_FLOAT || - JS_VALUE_GET_TAG(op2) == JS_TAG_BIG_FLOAT) { + } else if (tag1 == JS_TAG_BIG_FLOAT || tag2 == JS_TAG_BIG_FLOAT) { res = ctx->rt->bigfloat_ops.compare(ctx, op, op1, op2); if (res < 0) goto exception; - } else { + } else if (tag1 == JS_TAG_BIG_INT || tag2 == JS_TAG_BIG_INT) { res = ctx->rt->bigint_ops.compare(ctx, op, op1, op2); if (res < 0) goto exception; + } else { + double d1, d2; + + float64_compare: + /* can use floating point comparison */ + if (tag1 == JS_TAG_FLOAT64) { + d1 = JS_VALUE_GET_FLOAT64(op1); + } else { + d1 = JS_VALUE_GET_INT(op1); + } + if (tag2 == JS_TAG_FLOAT64) { + d2 = JS_VALUE_GET_FLOAT64(op2); + } else { + d2 = JS_VALUE_GET_INT(op2); + } + switch(op) { + case OP_lt: + res = (d1 < d2); /* if NaN return false */ + break; + case OP_lte: + res = (d1 <= d2); /* if NaN return false */ + break; + case OP_gt: + res = (d1 > d2); /* if NaN return false */ + break; + default: + case OP_gte: + res = (d1 >= d2); /* if NaN return false */ + break; + } } } done: @@ -13895,9 +13960,17 @@ static no_inline __exception int js_eq_slow(JSContext *ctx, JSValue *sp, } goto redo; } else { + /* IsHTMLDDA object is equivalent to undefined for '==' and '!=' */ + if ((JS_IsHTMLDDA(ctx, op1) && + (tag2 == JS_TAG_NULL || tag2 == JS_TAG_UNDEFINED)) || + (JS_IsHTMLDDA(ctx, op2) && + (tag1 == JS_TAG_NULL || tag1 == JS_TAG_UNDEFINED))) { + res = TRUE; + } else { + res = FALSE; + } JS_FreeValue(ctx, op1); JS_FreeValue(ctx, op2); - res = FALSE; } done: sp[-2] = JS_NewBool(ctx, res ^ is_neq); @@ -14348,9 +14421,17 @@ static no_inline __exception int js_eq_slow(JSContext *ctx, JSValue *sp, } goto redo; } else { + /* IsHTMLDDA object is equivalent to undefined for '==' and '!=' */ + if ((JS_IsHTMLDDA(ctx, op1) && + (tag2 == JS_TAG_NULL || tag2 == JS_TAG_UNDEFINED)) || + (JS_IsHTMLDDA(ctx, op2) && + (tag1 == JS_TAG_NULL || tag1 == JS_TAG_UNDEFINED))) { + res = TRUE; + } else { + res = FALSE; + } JS_FreeValue(ctx, op1); JS_FreeValue(ctx, op2); - res = FALSE; } sp[-2] = JS_NewBool(ctx, res ^ is_neq); return 0; @@ -14631,7 +14712,7 @@ static __exception int js_operator_instanceof(JSContext *ctx, JSValue *sp) return 0; } -static __exception int js_operator_typeof(JSContext *ctx, JSValue op1) +static __exception int js_operator_typeof(JSContext *ctx, JSValueConst op1) { JSAtom atom; uint32_t tag; @@ -14663,10 +14744,16 @@ static __exception int js_operator_typeof(JSContext *ctx, JSValue op1) atom = JS_ATOM_string; break; case JS_TAG_OBJECT: - if (JS_IsFunction(ctx, op1)) - atom = JS_ATOM_function; - else - goto obj_type; + { + JSObject *p; + p = JS_VALUE_GET_OBJ(op1); + if (unlikely(p->is_HTMLDDA)) + atom = JS_ATOM_undefined; + else if (JS_IsFunction(ctx, op1)) + atom = JS_ATOM_function; + else + goto obj_type; + } break; case JS_TAG_NULL: obj_type: @@ -15231,7 +15318,10 @@ static __exception int js_for_of_start(JSContext *ctx, JSValue *sp, return 0; } -/* enum_rec -> enum_rec value done */ +/* enum_rec [objs] -> enum_rec [objs] value done. There are 'offset' + objs. If 'done' is true or in case of exception, 'enum_rec' is set + to undefined. If 'done' is true, 'value' is always set to + undefined. */ static __exception int js_for_of_next(JSContext *ctx, JSValue *sp, int offset) { JSValue value = JS_UNDEFINED; @@ -15246,8 +15336,12 @@ static __exception int js_for_of_next(JSContext *ctx, JSValue *sp, int offset) /* replace the iteration object with undefined */ JS_FreeValue(ctx, sp[offset]); sp[offset] = JS_UNDEFINED; - if (done < 0) + if (done < 0) { return -1; + } else { + JS_FreeValue(ctx, value); + value = JS_UNDEFINED; + } } } sp[0] = value; @@ -15363,10 +15457,10 @@ static BOOL js_get_fast_array(JSContext *ctx, JSValueConst obj, static __exception int js_append_enumerate(JSContext *ctx, JSValue *sp) { JSValue iterator, enumobj, method, value; - int pos, is_array_iterator; + int is_array_iterator; JSValue *arrp; - uint32_t i, count32; - + uint32_t i, count32, pos; + if (JS_VALUE_GET_TAG(sp[-2]) != JS_TAG_INT) { JS_ThrowInternalError(ctx, "invalid index for append"); return -1; @@ -15399,24 +15493,21 @@ static __exception int js_append_enumerate(JSContext *ctx, JSValue *sp) if (is_array_iterator && JS_IsCFunction(ctx, method, (JSCFunction *)js_array_iterator_next, 0) && js_get_fast_array(ctx, sp[-1], &arrp, &count32)) { - int64_t len; - /* Handle fast arrays explicitly */ - if (js_get_length64(ctx, &len, sp[-1])) + uint32_t len; + if (js_get_length32(ctx, &len, sp[-1])) goto exception; + /* if len > count32, the elements >= count32 might be read in + the prototypes and might have side effects */ + if (len != count32) + goto general_case; + /* Handle fast arrays explicitly */ for (i = 0; i < count32; i++) { if (JS_DefinePropertyValueUint32(ctx, sp[-3], pos++, JS_DupValue(ctx, arrp[i]), JS_PROP_C_W_E) < 0) goto exception; } - if (len > count32) { - /* This is not strictly correct because the trailing elements are - empty instead of undefined. Append undefined entries instead. - */ - pos += len - count32; - if (JS_SetProperty(ctx, sp[-3], JS_ATOM_length, JS_NewUint32(ctx, pos)) < 0) - goto exception; - } } else { + general_case: for (;;) { BOOL done; value = JS_IteratorNext(ctx, enumobj, method, 0, NULL, &done); @@ -15430,6 +15521,7 @@ static __exception int js_append_enumerate(JSContext *ctx, JSValue *sp) goto exception; } } + /* Note: could raise an error if too many elements */ sp[-2] = JS_NewInt32(ctx, pos); JS_FreeValue(ctx, enumobj); JS_FreeValue(ctx, method); @@ -17742,47 +17834,42 @@ static JSValue JS_CallInternal(JSContext *caller_ctx, JSValueConst func_obj, BREAK; CASE(OP_add_loc): { - JSValue ops[2]; + JSValue *pv; int idx; idx = *pc; pc += 1; - ops[0] = var_buf[idx]; - ops[1] = sp[-1]; - if (likely(JS_VALUE_IS_BOTH_INT(ops[0], ops[1]))) { + pv = &var_buf[idx]; + if (likely(JS_VALUE_IS_BOTH_INT(*pv, sp[-1]))) { int64_t r; - r = (int64_t)JS_VALUE_GET_INT(ops[0]) + JS_VALUE_GET_INT(ops[1]); + r = (int64_t)JS_VALUE_GET_INT(*pv) + + JS_VALUE_GET_INT(sp[-1]); if (unlikely((int)r != r)) goto add_loc_slow; - var_buf[idx] = JS_NewInt32(ctx, r); + *pv = JS_NewInt32(ctx, r); sp--; - } else if (JS_VALUE_GET_TAG(ops[0]) == JS_TAG_STRING) { + } else if (JS_VALUE_GET_TAG(*pv) == JS_TAG_STRING) { + JSValue op1; + op1 = sp[-1]; sp--; - ops[1] = JS_ToPrimitiveFree(ctx, ops[1], HINT_NONE); - if (JS_IsException(ops[1])) { + op1 = JS_ToPrimitiveFree(ctx, op1, HINT_NONE); + if (JS_IsException(op1)) goto exception; - } - /* XXX: should not modify the variable in case of - exception */ - ops[0] = JS_ConcatString(ctx, ops[0], ops[1]); - if (JS_IsException(ops[0])) { - var_buf[idx] = JS_UNDEFINED; + op1 = JS_ConcatString(ctx, JS_DupValue(ctx, *pv), op1); + if (JS_IsException(op1)) goto exception; - } - var_buf[idx] = ops[0]; + set_value(ctx, pv, op1); } else { + JSValue ops[2]; add_loc_slow: - /* XXX: should not modify the variable in case of - exception */ - sp--; /* In case of exception, js_add_slow frees ops[0] - and ops[1]. */ - /* XXX: change API */ - if (js_add_slow(ctx, ops + 2)) { - var_buf[idx] = JS_UNDEFINED; + and ops[1], so we must duplicate *pv */ + ops[0] = JS_DupValue(ctx, *pv); + ops[1] = sp[-1]; + sp--; + if (js_add_slow(ctx, ops + 2)) goto exception; - } - var_buf[idx] = ops[0]; + set_value(ctx, pv, ops[0]); } } BREAK; @@ -18427,7 +18514,15 @@ static JSValue JS_CallInternal(JSContext *caller_ctx, JSValueConst func_obj, } else { goto free_and_set_false; } - CASE(OP_is_function): + /* XXX: could merge to a single opcode */ + CASE(OP_typeof_is_undefined): + /* different from OP_is_undefined because of isHTMLDDA */ + if (js_operator_typeof(ctx, sp[-1]) == JS_ATOM_undefined) { + goto free_and_set_true; + } else { + goto free_and_set_false; + } + CASE(OP_typeof_is_function): if (js_operator_typeof(ctx, sp[-1]) == JS_ATOM_function) { goto free_and_set_true; } else { @@ -19656,6 +19751,9 @@ enum { TOK_MATH_POW_ASSIGN, #endif TOK_POW_ASSIGN, + TOK_LAND_ASSIGN, + TOK_LOR_ASSIGN, + TOK_DOUBLE_QUESTION_MARK_ASSIGN, TOK_DEC, TOK_INC, TOK_SHL, @@ -20015,11 +20113,14 @@ static void free_token(JSParseState *s, JSToken *token) JS_FreeValue(s->ctx, token->u.regexp.flags); break; case TOK_IDENT: - case TOK_FIRST_KEYWORD ... TOK_LAST_KEYWORD: case TOK_PRIVATE_NAME: JS_FreeAtom(s->ctx, token->u.ident.atom); break; default: + if (token->val >= TOK_FIRST_KEYWORD && + token->val <= TOK_LAST_KEYWORD) { + JS_FreeAtom(s->ctx, token->u.ident.atom); + } break; } } @@ -20233,6 +20334,8 @@ static __exception int js_parse_string(JSParseState *s, int sep, } if (c == '\\') { c = *p; + /* XXX: need a specific JSON case to avoid + accepting invalid escapes */ switch(c) { case '\0': if (p >= s->buf_end) @@ -20256,18 +20359,23 @@ static __exception int js_parse_string(JSParseState *s, int sep, s->line_num++; continue; default: - if (c >= '0' && c <= '7') { + if (c >= '0' && c <= '9') { if (!s->cur_func) - goto invalid_octal; /* JSON case */ + goto invalid_escape; /* JSON case */ if (!(s->cur_func->js_mode & JS_MODE_STRICT) && sep != '`') goto parse_escape; if (c == '0' && !(p[1] >= '0' && p[1] <= '9')) { p++; c = '\0'; } else { - invalid_octal: - if (do_throw) - js_parse_error(s, "invalid octal syntax in strict mode"); + if (c >= '8' || sep == '`') { + /* Note: according to ES2021, \8 and \9 are not + accepted in strict mode or in templates. */ + goto invalid_escape; + } else { + if (do_throw) + js_parse_error(s, "octal escape sequences are not allowed in strict mode"); + } goto fail; } } else if (c >= 0x80) { @@ -20284,6 +20392,7 @@ static __exception int js_parse_string(JSParseState *s, int sep, parse_escape: ret = lre_parse_escape(&p, TRUE); if (ret == -1) { + invalid_escape: if (do_throw) js_parse_error(s, "malformed escape sequence in string literal"); goto fail; @@ -20634,8 +20743,20 @@ static __exception int next_token(JSParseState *s) } } goto def_token; - case 'a' ... 'z': - case 'A' ... 'Z': + case 'a': case 'b': case 'c': case 'd': + case 'e': case 'f': case 'g': case 'h': + case 'i': case 'j': case 'k': case 'l': + case 'm': case 'n': case 'o': case 'p': + case 'q': case 'r': case 's': case 't': + case 'u': case 'v': case 'w': case 'x': + case 'y': case 'z': + case 'A': case 'B': case 'C': case 'D': + case 'E': case 'F': case 'G': case 'H': + case 'I': case 'J': case 'K': case 'L': + case 'M': case 'N': case 'O': case 'P': + case 'Q': case 'R': case 'S': case 'T': + case 'U': case 'V': case 'W': case 'X': + case 'Y': case 'Z': case '_': case '$': /* identifier */ @@ -20717,7 +20838,9 @@ static __exception int next_token(JSParseState *s) goto fail; } goto parse_number; - case '1' ... '9': + case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': + case '9': /* number */ parse_number: { @@ -20887,8 +21010,13 @@ static __exception int next_token(JSParseState *s) p += 2; s->token.val = TOK_AND_ASSIGN; } else if (p[1] == '&') { - p += 2; - s->token.val = TOK_LAND; + if (p[2] == '=') { + p += 3; + s->token.val = TOK_LAND_ASSIGN; + } else { + p += 2; + s->token.val = TOK_LAND; + } } else { goto def_token; } @@ -20934,16 +21062,26 @@ static __exception int next_token(JSParseState *s) p += 2; s->token.val = TOK_OR_ASSIGN; } else if (p[1] == '|') { - p += 2; - s->token.val = TOK_LOR; + if (p[2] == '=') { + p += 3; + s->token.val = TOK_LOR_ASSIGN; + } else { + p += 2; + s->token.val = TOK_LOR; + } } else { goto def_token; } break; case '?': if (p[1] == '?') { - p += 2; - s->token.val = TOK_DOUBLE_QUESTION_MARK; + if (p[2] == '=') { + p += 3; + s->token.val = TOK_DOUBLE_QUESTION_MARK_ASSIGN; + } else { + p += 2; + s->token.val = TOK_DOUBLE_QUESTION_MARK; + } } else if (p[1] == '.' && !(p[2] >= '0' && p[2] <= '9')) { p += 2; s->token.val = TOK_QUESTION_MARK_DOT; @@ -21135,8 +21273,20 @@ static __exception int json_next_token(JSParseState *s) goto def_token; } break; - case 'a' ... 'z': - case 'A' ... 'Z': + case 'a': case 'b': case 'c': case 'd': + case 'e': case 'f': case 'g': case 'h': + case 'i': case 'j': case 'k': case 'l': + case 'm': case 'n': case 'o': case 'p': + case 'q': case 'r': case 's': case 't': + case 'u': case 'v': case 'w': case 'x': + case 'y': case 'z': + case 'A': case 'B': case 'C': case 'D': + case 'E': case 'F': case 'G': case 'H': + case 'I': case 'J': case 'K': case 'L': + case 'M': case 'N': case 'O': case 'P': + case 'Q': case 'R': case 'S': case 'T': + case 'U': case 'V': case 'W': case 'X': + case 'Y': case 'Z': case '_': case '$': /* identifier : only pure ascii characters are accepted */ @@ -21161,7 +21311,9 @@ static __exception int json_next_token(JSParseState *s) if (!is_digit(p[1])) goto def_token; goto parse_number; - case '1' ... '9': + case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': + case '9': /* number */ parse_number: { @@ -23267,15 +23419,6 @@ static BOOL has_with_scope(JSFunctionDef *s, int scope_level) return FALSE; } -typedef struct JSLValue { - int opcode; - int scope; - int label; - int depth; - int tok; - JSAtom name; -} JSLValue; - static __exception int get_lvalue(JSParseState *s, int *popcode, int *pscope, JSAtom *pname, int *plabel, int *pdepth, BOOL keep, int tok) @@ -23400,84 +23543,112 @@ static __exception int get_lvalue(JSParseState *s, int *popcode, int *pscope, return 0; } -/* if special = TRUE: specific post inc/dec case */ -/* name has a live reference */ +typedef enum { + PUT_LVALUE_NOKEEP, /* [depth] v -> */ + PUT_LVALUE_NOKEEP_DEPTH, /* [depth] v -> , keep depth (currently + just disable optimizations) */ + PUT_LVALUE_KEEP_TOP, /* [depth] v -> v */ + PUT_LVALUE_KEEP_SECOND, /* [depth] v0 v -> v0 */ + PUT_LVALUE_NOKEEP_BOTTOM, /* v [depth] -> */ +} PutLValueEnum; + +/* name has a live reference. 'is_let' is only used with opcode = + OP_scope_get_var which is never generated by get_lvalue(). */ static void put_lvalue(JSParseState *s, int opcode, int scope, - JSAtom name, int label, BOOL special) + JSAtom name, int label, PutLValueEnum special, + BOOL is_let) { switch(opcode) { case OP_get_field: - if (!special) - emit_op(s, OP_insert2); /* obj v -> v obj v */ - else - emit_op(s, OP_perm3); /* obj v0 v -> v0 obj v */ - emit_op(s, OP_put_field); - emit_u32(s, name); /* name has refcount */ - break; case OP_scope_get_private_field: - if (!special) + /* depth = 1 */ + switch(special) { + case PUT_LVALUE_NOKEEP: + case PUT_LVALUE_NOKEEP_DEPTH: + break; + case PUT_LVALUE_KEEP_TOP: emit_op(s, OP_insert2); /* obj v -> v obj v */ - else + break; + case PUT_LVALUE_KEEP_SECOND: emit_op(s, OP_perm3); /* obj v0 v -> v0 obj v */ - emit_op(s, OP_scope_put_private_field); - emit_u32(s, name); /* name has refcount */ - emit_u16(s, scope); + break; + case PUT_LVALUE_NOKEEP_BOTTOM: + emit_op(s, OP_swap); + break; + default: + abort(); + } break; case OP_get_array_el: - if (!special) - emit_op(s, OP_insert3); /* obj prop v -> v obj prop v */ - else - emit_op(s, OP_perm4); /* obj prop v0 v -> v0 obj prop v */ - emit_op(s, OP_put_array_el); - break; case OP_get_ref_value: - JS_FreeAtom(s->ctx, name); - emit_label(s, label); - if (!special) + /* depth = 2 */ + if (opcode == OP_get_ref_value) { + JS_FreeAtom(s->ctx, name); + emit_label(s, label); + } + switch(special) { + case PUT_LVALUE_NOKEEP: + emit_op(s, OP_nop); /* will trigger optimization */ + break; + case PUT_LVALUE_NOKEEP_DEPTH: + break; + case PUT_LVALUE_KEEP_TOP: emit_op(s, OP_insert3); /* obj prop v -> v obj prop v */ - else + break; + case PUT_LVALUE_KEEP_SECOND: emit_op(s, OP_perm4); /* obj prop v0 v -> v0 obj prop v */ - emit_op(s, OP_put_ref_value); + break; + case PUT_LVALUE_NOKEEP_BOTTOM: + emit_op(s, OP_rot3l); + break; + default: + abort(); + } break; case OP_get_super_value: - if (!special) + /* depth = 3 */ + switch(special) { + case PUT_LVALUE_NOKEEP: + case PUT_LVALUE_NOKEEP_DEPTH: + break; + case PUT_LVALUE_KEEP_TOP: emit_op(s, OP_insert4); /* this obj prop v -> v this obj prop v */ - else + break; + case PUT_LVALUE_KEEP_SECOND: emit_op(s, OP_perm5); /* this obj prop v0 v -> v0 this obj prop v */ - emit_op(s, OP_put_super_value); + break; + case PUT_LVALUE_NOKEEP_BOTTOM: + emit_op(s, OP_rot4l); + break; + default: + abort(); + } break; default: - abort(); + break; } -} - -static void put_lvalue_nokeep(JSParseState *s, int opcode, int scope, - JSAtom name, int label, int var_tok) -{ + switch(opcode) { case OP_scope_get_var: /* val -- */ - emit_op(s, (var_tok == TOK_CONST || var_tok == TOK_LET) ? - OP_scope_put_var_init : OP_scope_put_var); + assert(special == PUT_LVALUE_NOKEEP || + special == PUT_LVALUE_NOKEEP_DEPTH); + emit_op(s, is_let ? OP_scope_put_var_init : OP_scope_put_var); emit_u32(s, name); /* has refcount */ emit_u16(s, scope); break; - case OP_get_field: /* obj val -- */ + case OP_get_field: emit_op(s, OP_put_field); - emit_u32(s, name); /* has refcount */ + emit_u32(s, name); /* name has refcount */ break; case OP_scope_get_private_field: emit_op(s, OP_scope_put_private_field); - emit_u32(s, name); /* has refcount */ + emit_u32(s, name); /* name has refcount */ emit_u16(s, scope); break; - case OP_get_array_el: /* obj prop val -- */ + case OP_get_array_el: emit_op(s, OP_put_array_el); break; - case OP_get_ref_value: /* obj prop val -- */ - /* XXX: currently this reference is never optimized */ - JS_FreeAtom(s->ctx, name); - emit_label(s, label); - //emit_op(s, OP_nop); /* emit 2 bytes for optimizer */ + case OP_get_ref_value: emit_op(s, OP_put_ref_value); break; case OP_get_super_value: @@ -23869,7 +24040,9 @@ static int js_parse_destructuring_element(JSParseState *s, int tok, int is_arg, emit_label(s, label_hasval); } /* store value into lvalue object */ - put_lvalue_nokeep(s, opcode, scope, var_name, label_lvalue, tok); + put_lvalue(s, opcode, scope, var_name, label_lvalue, + PUT_LVALUE_NOKEEP_DEPTH, + (tok == TOK_CONST || tok == TOK_LET)); if (s->token.val == '}') break; /* accept a trailing comma before the '}' */ @@ -23969,8 +24142,9 @@ static int js_parse_destructuring_element(JSParseState *s, int tok, int is_arg, emit_label(s, label_hasval); } /* store value into lvalue object */ - put_lvalue_nokeep(s, opcode, scope, var_name, - label_lvalue, tok); + put_lvalue(s, opcode, scope, var_name, + label_lvalue, PUT_LVALUE_NOKEEP_DEPTH, + (tok == TOK_CONST || tok == TOK_LET)); } if (s->token.val == ']') break; @@ -24786,7 +24960,8 @@ static __exception int js_parse_unary(JSParseState *s, int exponentiation_flag) if (get_lvalue(s, &opcode, &scope, &name, &label, NULL, TRUE, op)) return -1; emit_op(s, OP_dec + op - TOK_DEC); - put_lvalue(s, opcode, scope, name, label, FALSE); + put_lvalue(s, opcode, scope, name, label, PUT_LVALUE_KEEP_TOP, + FALSE); } break; case TOK_TYPEOF: @@ -24834,7 +25009,8 @@ static __exception int js_parse_unary(JSParseState *s, int exponentiation_flag) if (get_lvalue(s, &opcode, &scope, &name, &label, NULL, TRUE, op)) return -1; emit_op(s, OP_post_dec + op - TOK_DEC); - put_lvalue(s, opcode, scope, name, label, TRUE); + put_lvalue(s, opcode, scope, name, label, PUT_LVALUE_KEEP_SECOND, + FALSE); if (next_token(s)) return -1; } @@ -25298,7 +25474,61 @@ static __exception int js_parse_assign_expr(JSParseState *s, BOOL in_accepted) #endif emit_op(s, op); } - put_lvalue(s, opcode, scope, name, label, FALSE); + put_lvalue(s, opcode, scope, name, label, PUT_LVALUE_KEEP_TOP, FALSE); + } else if (op >= TOK_LAND_ASSIGN && op <= TOK_DOUBLE_QUESTION_MARK_ASSIGN) { + int label, label1, depth_lvalue, label2; + + if (next_token(s)) + return -1; + if (get_lvalue(s, &opcode, &scope, &name, &label, + &depth_lvalue, TRUE, op) < 0) + return -1; + + emit_op(s, OP_dup); + if (op == TOK_DOUBLE_QUESTION_MARK_ASSIGN) + emit_op(s, OP_is_undefined_or_null); + label1 = emit_goto(s, op == TOK_LOR_ASSIGN ? OP_if_true : OP_if_false, + -1); + emit_op(s, OP_drop); + + if (js_parse_assign_expr(s, in_accepted)) { + JS_FreeAtom(s->ctx, name); + return -1; + } + + if (opcode == OP_get_ref_value && name == name0) { + set_object_name(s, name); + } + + switch(depth_lvalue) { + case 1: + emit_op(s, OP_insert2); + break; + case 2: + emit_op(s, OP_insert3); + break; + case 3: + emit_op(s, OP_insert4); + break; + default: + abort(); + } + + /* XXX: we disable the OP_put_ref_value optimization by not + using put_lvalue() otherwise depth_lvalue is not correct */ + put_lvalue(s, opcode, scope, name, label, PUT_LVALUE_NOKEEP_DEPTH, + FALSE); + label2 = emit_goto(s, OP_goto, -1); + + emit_label(s, label1); + + /* remove the lvalue stack entries */ + while (depth_lvalue != 0) { + emit_op(s, OP_nip); + depth_lvalue--; + } + + emit_label(s, label2); } return 0; } @@ -25563,8 +25793,8 @@ static __exception int js_parse_var(JSParseState *s, BOOL in_accepted, int tok, goto var_error; } set_object_name(s, name); - put_lvalue(s, opcode, scope, name1, label, FALSE); - emit_op(s, OP_drop); + put_lvalue(s, opcode, scope, name1, label, + PUT_LVALUE_NOKEEP, FALSE); } else { if (js_parse_assign_expr(s, in_accepted)) goto var_error; @@ -25751,29 +25981,14 @@ static __exception int js_parse_for_in_of(JSParseState *s, int label_name, if (js_parse_destructuring_element(s, 0, 0, TRUE, skip_bits & SKIP_HAS_ELLIPSIS, TRUE)) return -1; } else { - int lvalue_label, depth; + int lvalue_label; if (js_parse_postfix_expr(s, TRUE)) return -1; if (get_lvalue(s, &opcode, &scope, &var_name, &lvalue_label, - &depth, FALSE, TOK_FOR)) + NULL, FALSE, TOK_FOR)) return -1; - /* swap value and lvalue object and store it into lvalue object */ - /* enum_rec val [depth] -- enum_rec */ - switch(depth) { - case 1: - emit_op(s, OP_swap); - break; - case 2: - emit_op(s, OP_rot3l); - break; - case 3: - emit_op(s, OP_rot4l); - break; - default: - abort(); - } - put_lvalue_nokeep(s, opcode, scope, var_name, lvalue_label, - TOK_FOR /* not used */); + put_lvalue(s, opcode, scope, var_name, lvalue_label, + PUT_LVALUE_NOKEEP_BOTTOM, FALSE); } var_name = JS_ATOM_NULL; } @@ -26933,32 +27148,20 @@ static JSModuleDef *js_find_loaded_module(JSContext *ctx, JSAtom name) /* return NULL in case of exception (e.g. module could not be loaded) */ static JSModuleDef *js_host_resolve_imported_module(JSContext *ctx, - JSAtom base_module_name, - JSAtom module_name1) + const char *base_cname, + const char *cname1) { JSRuntime *rt = ctx->rt; JSModuleDef *m; char *cname; - const char *base_cname, *cname1; JSAtom module_name; - base_cname = JS_AtomToCString(ctx, base_module_name); - if (!base_cname) - return NULL; - cname1 = JS_AtomToCString(ctx, module_name1); - if (!cname1) { - JS_FreeCString(ctx, base_cname); - return NULL; - } - if (!rt->module_normalize_func) { cname = js_default_module_normalize_name(ctx, base_cname, cname1); } else { cname = rt->module_normalize_func(ctx, base_cname, cname1, rt->module_loader_opaque); } - JS_FreeCString(ctx, base_cname); - JS_FreeCString(ctx, cname1); if (!cname) return NULL; @@ -26992,6 +27195,27 @@ static JSModuleDef *js_host_resolve_imported_module(JSContext *ctx, return m; } +static JSModuleDef *js_host_resolve_imported_module_atom(JSContext *ctx, + JSAtom base_module_name, + JSAtom module_name1) +{ + const char *base_cname, *cname; + JSModuleDef *m; + + base_cname = JS_AtomToCString(ctx, base_module_name); + if (!base_cname) + return NULL; + cname = JS_AtomToCString(ctx, module_name1); + if (!cname) { + JS_FreeCString(ctx, base_cname); + return NULL; + } + m = js_host_resolve_imported_module(ctx, base_cname, cname); + JS_FreeCString(ctx, base_cname); + JS_FreeCString(ctx, cname); + return m; +} + typedef struct JSResolveEntry { JSModuleDef *module; JSAtom name; @@ -27435,8 +27659,8 @@ static int js_resolve_module(JSContext *ctx, JSModuleDef *m) /* resolve each requested module */ for(i = 0; i < m->req_module_entries_count; i++) { JSReqModuleEntry *rme = &m->req_module_entries[i]; - m1 = js_host_resolve_imported_module(ctx, m->module_name, - rme->module_name); + m1 = js_host_resolve_imported_module_atom(ctx, m->module_name, + rme->module_name); if (!m1) return -1; rme->module = m1; @@ -27711,8 +27935,9 @@ static int js_instantiate_module(JSContext *ctx, JSModuleDef *m) return -1; } -/* warning: the returned atom is not allocated */ -static JSAtom js_get_script_or_module_name(JSContext *ctx) +/* return JS_ATOM_NULL if the name cannot be found. Only works with + not striped bytecode functions. */ +JSAtom JS_GetScriptOrModuleName(JSContext *ctx, int n_stack_levels) { JSStackFrame *sf; JSFunctionBytecode *b; @@ -27721,14 +27946,22 @@ static JSAtom js_get_script_or_module_name(JSContext *ctx) function. It does not work for eval(). Need to add a ScriptOrModule info in JSFunctionBytecode */ sf = ctx->rt->current_stack_frame; - assert(sf != NULL); - assert(JS_VALUE_GET_TAG(sf->cur_func) == JS_TAG_OBJECT); + if (!sf) + return JS_ATOM_NULL; + while (n_stack_levels-- > 0) { + sf = sf->prev_frame; + if (!sf) + return JS_ATOM_NULL; + } + if (JS_VALUE_GET_TAG(sf->cur_func) != JS_TAG_OBJECT) + return JS_ATOM_NULL; p = JS_VALUE_GET_OBJ(sf->cur_func); - assert(js_class_has_bytecode(p->class_id)); + if (!js_class_has_bytecode(p->class_id)) + return JS_ATOM_NULL; b = p->u.func.function_bytecode; if (!b->has_debug) return JS_ATOM_NULL; - return b->debug.filename; + return JS_DupAtom(ctx, b->debug.filename); } JSAtom JS_GetModuleName(JSContext *ctx, JSModuleDef *m) @@ -27755,13 +27988,14 @@ static JSValue js_import_meta(JSContext *ctx) JSAtom filename; JSModuleDef *m; - filename = js_get_script_or_module_name(ctx); + filename = JS_GetScriptOrModuleName(ctx, 0); if (filename == JS_ATOM_NULL) goto fail; /* XXX: inefficient, need to add a module or script pointer in JSFunctionBytecode */ m = js_find_loaded_module(ctx, filename); + JS_FreeAtom(ctx, filename); if (!m) { fail: JS_ThrowTypeError(ctx, "import.meta not supported in this context"); @@ -27770,6 +28004,31 @@ static JSValue js_import_meta(JSContext *ctx) return JS_GetImportMeta(ctx, m); } +/* used by os.Worker() and import() */ +JSModuleDef *JS_RunModule(JSContext *ctx, const char *basename, + const char *filename) +{ + JSModuleDef *m; + JSValue ret, func_obj; + + m = js_host_resolve_imported_module(ctx, basename, filename); + if (!m) + return NULL; + + if (js_resolve_module(ctx, m) < 0) { + js_free_modules(ctx, JS_FREE_MODULE_NOT_RESOLVED); + return NULL; + } + + /* Evaluate the module code */ + func_obj = JS_DupValue(ctx, JS_MKPTR(JS_TAG_MODULE, m)); + ret = JS_EvalFunction(ctx, func_obj); + if (JS_IsException(ret)) + return NULL; + JS_FreeValue(ctx, ret); + return m; +} + static JSValue js_dynamic_import_job(JSContext *ctx, int argc, JSValueConst *argv) { @@ -27777,53 +28036,36 @@ static JSValue js_dynamic_import_job(JSContext *ctx, JSValueConst basename_val = argv[2]; JSValueConst specifier = argv[3]; JSModuleDef *m; - JSAtom basename = JS_ATOM_NULL, filename; - JSValue specifierString, ret, func_obj, err, ns; + const char *basename = NULL, *filename; + JSValue ret, err, ns; if (!JS_IsString(basename_val)) { JS_ThrowTypeError(ctx, "no function filename for import()"); goto exception; } - basename = JS_ValueToAtom(ctx, basename_val); - if (basename == JS_ATOM_NULL) + basename = JS_ToCString(ctx, basename_val); + if (!basename) goto exception; - specifierString = JS_ToString(ctx, specifier); - if (JS_IsException(specifierString)) - goto exception; - filename = JS_ValueToAtom(ctx, specifierString); - JS_FreeValue(ctx, specifierString); - if (filename == JS_ATOM_NULL) + filename = JS_ToCString(ctx, specifier); + if (!filename) goto exception; - m = js_host_resolve_imported_module(ctx, basename, filename); - JS_FreeAtom(ctx, filename); - if (!m) { - goto exception; - } - - if (js_resolve_module(ctx, m) < 0) { - js_free_modules(ctx, JS_FREE_MODULE_NOT_RESOLVED); - goto exception; - } - - /* Evaluate the module code */ - func_obj = JS_DupValue(ctx, JS_MKPTR(JS_TAG_MODULE, m)); - ret = JS_EvalFunction(ctx, func_obj); - if (JS_IsException(ret)) + m = JS_RunModule(ctx, basename, filename); + JS_FreeCString(ctx, filename); + if (!m) goto exception; - JS_FreeValue(ctx, ret); /* return the module namespace */ ns = js_get_module_ns(ctx, m); - if (JS_IsException(ret)) + if (JS_IsException(ns)) goto exception; ret = JS_Call(ctx, resolving_funcs[0], JS_UNDEFINED, 1, (JSValueConst *)&ns); JS_FreeValue(ctx, ret); /* XXX: what to do if exception ? */ JS_FreeValue(ctx, ns); - JS_FreeAtom(ctx, basename); + JS_FreeCString(ctx, basename); return JS_UNDEFINED; exception: @@ -27832,7 +28074,7 @@ static JSValue js_dynamic_import_job(JSContext *ctx, 1, (JSValueConst *)&err); JS_FreeValue(ctx, ret); /* XXX: what to do if exception ? */ JS_FreeValue(ctx, err); - JS_FreeAtom(ctx, basename); + JS_FreeCString(ctx, basename); return JS_UNDEFINED; } @@ -27842,11 +28084,12 @@ static JSValue js_dynamic_import(JSContext *ctx, JSValueConst specifier) JSValue promise, resolving_funcs[2], basename_val; JSValueConst args[4]; - basename = js_get_script_or_module_name(ctx); + basename = JS_GetScriptOrModuleName(ctx, 0); if (basename == JS_ATOM_NULL) basename_val = JS_NULL; else basename_val = JS_AtomToValue(ctx, basename); + JS_FreeAtom(ctx, basename); if (JS_IsException(basename_val)) return basename_val; @@ -28957,7 +29200,7 @@ static BOOL can_opt_put_ref_value(const uint8_t *bc_buf, int pos) return (bc_buf[pos + 1] == OP_put_ref_value && (opcode == OP_insert3 || opcode == OP_perm4 || - //opcode == OP_nop || + opcode == OP_nop || opcode == OP_rot3l)); } @@ -28967,7 +29210,7 @@ static BOOL can_opt_put_global_ref_value(const uint8_t *bc_buf, int pos) return (bc_buf[pos + 1] == OP_put_ref_value && (opcode == OP_insert3 || opcode == OP_perm4 || - //opcode == OP_nop || + opcode == OP_nop || opcode == OP_rot3l)); } @@ -29049,20 +29292,22 @@ static int optimize_scope_make_global_ref(JSContext *ctx, JSFunctionDef *s, end_pos = label_pos + 2; op = bc_buf[label_pos]; if (is_strict) { - switch(op) { - case OP_insert3: - op = OP_insert2; - break; - case OP_perm4: - op = OP_perm3; - break; - case OP_rot3l: - op = OP_swap; - break; - default: - abort(); + if (op != OP_nop) { + switch(op) { + case OP_insert3: + op = OP_insert2; + break; + case OP_perm4: + op = OP_perm3; + break; + case OP_rot3l: + op = OP_swap; + break; + default: + abort(); + } + bc_buf[pos++] = op; } - bc_buf[pos++] = op; } else { if (op == OP_insert3) bc_buf[pos++] = OP_dup; @@ -31567,20 +31812,20 @@ static __exception int resolve_labels(JSContext *ctx, JSFunctionDef *s) } goto no_change; +#if SHORT_OPCODES case OP_typeof: if (OPTIMIZE) { /* simplify typeof tests */ if (code_match(&cc, pos_next, OP_push_atom_value, M4(OP_strict_eq, OP_strict_neq, OP_eq, OP_neq), -1)) { if (cc.line_num >= 0) line_num = cc.line_num; int op1 = (cc.op == OP_strict_eq || cc.op == OP_eq) ? OP_strict_eq : OP_strict_neq; -#if SHORT_OPCODES int op2 = -1; switch (cc.atom) { case JS_ATOM_undefined: - op2 = OP_is_undefined; + op2 = OP_typeof_is_undefined; break; case JS_ATOM_function: - op2 = OP_is_function; + op2 = OP_typeof_is_function; break; } if (op2 >= 0) { @@ -31604,19 +31849,10 @@ static __exception int resolve_labels(JSContext *ctx, JSFunctionDef *s) goto has_label; } } -#endif - if (cc.atom == JS_ATOM_undefined) { - /* transform typeof(s) == "undefined" into s === void 0 */ - add_pc2line_info(s, bc_out.size, line_num); - dbuf_putc(&bc_out, OP_undefined); - dbuf_putc(&bc_out, op1); - JS_FreeAtom(ctx, cc.atom); - pos_next = cc.pos; - break; - } } } goto no_change; +#endif default: no_change: @@ -37067,10 +37303,9 @@ static JSValue js_function_bind(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { JSBoundFunction *bf; - JSValue func_obj, name1; + JSValue func_obj, name1, len_val; JSObject *p; - int arg_count, i; - uint32_t len1; + int arg_count, i, ret; if (check_function(ctx, this_val)) return JS_EXCEPTION; @@ -37093,6 +37328,44 @@ static JSValue js_function_bind(JSContext *ctx, JSValueConst this_val, } p->u.bound_function = bf; + /* XXX: the spec could be simpler by only using GetOwnProperty */ + ret = JS_GetOwnProperty(ctx, NULL, this_val, JS_ATOM_length); + if (ret < 0) + goto exception; + if (!ret) { + len_val = JS_NewInt32(ctx, 0); + } else { + len_val = JS_GetProperty(ctx, this_val, JS_ATOM_length); + if (JS_IsException(len_val)) + goto exception; + if (JS_VALUE_GET_TAG(len_val) == JS_TAG_INT) { + /* most common case */ + int len1 = JS_VALUE_GET_INT(len_val); + if (len1 <= arg_count) + len1 = 0; + else + len1 -= arg_count; + len_val = JS_NewInt32(ctx, len1); + } else if (JS_VALUE_GET_NORM_TAG(len_val) == JS_TAG_FLOAT64) { + double d = JS_VALUE_GET_FLOAT64(len_val); + if (isnan(d)) { + d = 0.0; + } else { + d = trunc(d); + if (d <= (double)arg_count) + d = 0.0; + else + d -= (double)arg_count; /* also converts -0 to +0 */ + } + len_val = JS_NewFloat64(ctx, d); + } else { + JS_FreeValue(ctx, len_val); + len_val = JS_NewInt32(ctx, 0); + } + } + JS_DefinePropertyValue(ctx, func_obj, JS_ATOM_length, + len_val, JS_PROP_CONFIGURABLE); + name1 = JS_GetProperty(ctx, this_val, JS_ATOM_name); if (JS_IsException(name1)) goto exception; @@ -37105,15 +37378,6 @@ static JSValue js_function_bind(JSContext *ctx, JSValueConst this_val, goto exception; JS_DefinePropertyValue(ctx, func_obj, JS_ATOM_name, name1, JS_PROP_CONFIGURABLE); - if (js_get_length32(ctx, &len1, this_val)) - goto exception; - if (len1 <= (uint32_t)arg_count) - len1 = 0; - else - len1 -= arg_count; - JS_DefinePropertyValue(ctx, func_obj, JS_ATOM_length, - JS_NewUint32(ctx, len1), - JS_PROP_CONFIGURABLE); return func_obj; exception: JS_FreeValue(ctx, func_obj); @@ -40455,10 +40719,6 @@ static JSValue js_string_pad(JSContext *ctx, JSValueConst this_val, len = p->len; if (len >= n) return str; - if (n > JS_STRING_LEN_MAX) { - JS_ThrowInternalError(ctx, "string too long"); - goto fail2; - } if (argc > 1 && !JS_IsUndefined(argv[1])) { v = JS_ToString(ctx, argv[1]); if (JS_IsException(v)) @@ -40473,6 +40733,10 @@ static JSValue js_string_pad(JSContext *ctx, JSValueConst this_val, p1 = NULL; } } + if (n > JS_STRING_LEN_MAX) { + JS_ThrowInternalError(ctx, "string too long"); + goto fail2; + } if (string_buffer_init(ctx, b, n)) goto fail3; n -= len; @@ -41932,14 +42196,11 @@ static JSValue js_regexp_exec(JSContext *ctx, JSValueConst this_val, if (JS_IsException(obj)) goto fail; prop_flags = JS_PROP_C_W_E | JS_PROP_THROW; - group_name_ptr = NULL; - if (re_flags & LRE_FLAG_NAMED_GROUPS) { - uint32_t re_bytecode_len; + group_name_ptr = lre_get_groupnames(re_bytecode); + if (group_name_ptr) { groups = JS_NewObjectProto(ctx, JS_NULL); if (JS_IsException(groups)) goto fail; - re_bytecode_len = get_u32(re_bytecode + 3); - group_name_ptr = (char *)(re_bytecode + 7 + re_bytecode_len); } for(i = 0; i < capture_count; i++) { @@ -43724,6 +43985,7 @@ static const JSCFunctionListEntry js_reflect_funcs[] = { JS_CFUNC_MAGIC_DEF("preventExtensions", 1, js_object_preventExtensions, 1 ), JS_CFUNC_DEF("set", 3, js_reflect_set ), JS_CFUNC_DEF("setPrototypeOf", 2, js_reflect_setPrototypeOf ), + JS_PROP_STRING_DEF("[Symbol.toStringTag]", "Reflect", JS_PROP_CONFIGURABLE ), }; static const JSCFunctionListEntry js_reflect_obj[] = { diff --git a/quickjs.h b/quickjs.h @@ -412,6 +412,8 @@ void JS_ComputeMemoryUsage(JSRuntime *rt, JSMemoryUsage *s); void JS_DumpMemoryUsage(FILE *fp, const JSMemoryUsage *s, JSRuntime *rt); /* atom support */ +#define JS_ATOM_NULL 0 + JSAtom JS_NewAtomLen(JSContext *ctx, const char *str, size_t len); JSAtom JS_NewAtom(JSContext *ctx, const char *str); JSAtom JS_NewAtomUInt32(JSContext *ctx, uint32_t n); @@ -835,6 +837,8 @@ typedef int JSInterruptHandler(JSRuntime *rt, void *opaque); void JS_SetInterruptHandler(JSRuntime *rt, JSInterruptHandler *cb, void *opaque); /* if can_block is TRUE, Atomics.wait() can be used */ void JS_SetCanBlock(JSRuntime *rt, JS_BOOL can_block); +/* set the [IsHTMLDDA] internal slot */ +void JS_SetIsHTMLDDA(JSContext *ctx, JSValueConst obj); typedef struct JSModuleDef JSModuleDef; @@ -886,6 +890,12 @@ JSValue JS_ReadObject(JSContext *ctx, const uint8_t *buf, size_t buf_len, returns a module. */ int JS_ResolveModule(JSContext *ctx, JSValueConst obj); +/* only exported for os.Worker() */ +JSAtom JS_GetScriptOrModuleName(JSContext *ctx, int n_stack_levels); +/* only exported for os.Worker() */ +JSModuleDef *JS_RunModule(JSContext *ctx, const char *basename, + const char *filename); + /* C function definition */ typedef enum JSCFunctionEnum { /* XXX: should rename for namespace isolation */ JS_CFUNC_generic, diff --git a/run-test262.c b/run-test262.c @@ -743,10 +743,16 @@ static JSValue js_createRealm(JSContext *ctx, JSValue this_val, return ret; } +static JSValue js_IsHTMLDDA(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + return JS_NULL; +} + static JSValue add_helpers1(JSContext *ctx) { JSValue global_obj; - JSValue obj262; + JSValue obj262, obj; global_obj = JS_GetGlobalObject(ctx); @@ -773,6 +779,9 @@ static JSValue add_helpers1(JSContext *ctx) JS_SetPropertyStr(ctx, obj262, "createRealm", JS_NewCFunction(ctx, js_createRealm, "createRealm", 0)); + obj = JS_NewCFunction(ctx, js_IsHTMLDDA, "IsHTMLDDA", 0); + JS_SetIsHTMLDDA(ctx, obj); + JS_SetPropertyStr(ctx, obj262, "IsHTMLDDA", obj); JS_SetPropertyStr(ctx, global_obj, "$262", JS_DupValue(ctx, obj262)); diff --git a/test262.conf b/test262.conf @@ -68,6 +68,7 @@ class-methods-private class-static-fields-public class-static-fields-private class-static-methods-private +cleanupSome=skip coalesce-expression computed-property-names const @@ -100,10 +101,10 @@ host-gc-required=skip import.meta Int32Array Int8Array -IsHTMLDDA=skip +IsHTMLDDA json-superset let -logical-assignment-operators=skip +logical-assignment-operators Map new.target numeric-separator-literal diff --git a/tests/test_builtin.js b/tests/test_builtin.js @@ -277,6 +277,8 @@ function test_string() assert("aaaa".split("aaaaa", 1), [ "aaaa" ]); assert(eval('"\0"'), "\0"); + + assert("abc".padStart(Infinity, ""), "abc"); } function test_math() diff --git a/tests/test_language.js b/tests/test_language.js @@ -359,6 +359,23 @@ function test_labels() while (0) x: { break x; }; } +function test_destructuring() +{ + function * g () { return 0; }; + var [x] = g(); + assert(x, void 0); +} + +function test_spread() +{ + var x; + x = [1, 2, ...[3, 4]]; + assert(x.toString(), "1,2,3,4"); + + x = [ ...[ , ] ]; + assert(Object.getOwnPropertyNames(x).toString(), "0,length"); +} + test_op1(); test_cvt(); test_eq(); @@ -373,3 +390,5 @@ test_template_skip(); test_object_literal(); test_regexp_skip(); test_labels(); +test_destructuring(); +test_spread(); diff --git a/tests/test_worker.js b/tests/test_worker.js @@ -25,38 +25,7 @@ function test_worker() { var counter; - /* Note: can use std.loadFile() to read from a file */ - worker = new os.Worker(` - import * as std from "std"; - import * as os from "os"; - - var parent = os.Worker.parent; - - function handle_msg(e) { - var ev = e.data; -// print("child_recv", JSON.stringify(ev)); - switch(ev.type) { - case "abort": - parent.postMessage({ type: "done" }); - break; - case "sab": - /* modify the SharedArrayBuffer */ - ev.buf[2] = 10; - parent.postMessage({ type: "sab_done", buf: ev.buf }); - break; - } - } - - function worker_main() { - var i; - - parent.onmessage = handle_msg; - for(i = 0; i < 10; i++) { - parent.postMessage({ type: "num", num: i }); - } - } - worker_main(); -`); + worker = new os.Worker("./test_worker_module.js"); counter = 0; worker.onmessage = function (e) { diff --git a/tests/test_worker_module.js b/tests/test_worker_module.js @@ -0,0 +1,31 @@ +/* Worker code for test_worker.js */ +import * as std from "std"; +import * as os from "os"; + +var parent = os.Worker.parent; + +function handle_msg(e) { + var ev = e.data; + // print("child_recv", JSON.stringify(ev)); + switch(ev.type) { + case "abort": + parent.postMessage({ type: "done" }); + break; + case "sab": + /* modify the SharedArrayBuffer */ + ev.buf[2] = 10; + parent.postMessage({ type: "sab_done", buf: ev.buf }); + break; + } +} + +function worker_main() { + var i; + + parent.onmessage = handle_msg; + for(i = 0; i < 10; i++) { + parent.postMessage({ type: "num", num: i }); + } +} + +worker_main();