diff options
author | bellard <6490144+bellard@users.noreply.github.com> | 2020-09-06 19:07:30 +0200 |
---|---|---|
committer | bellard <6490144+bellard@users.noreply.github.com> | 2020-09-06 19:07:30 +0200 |
commit | 89007660998db0ee55f0ab3b34bb1a84f86fd3c4 (patch) | |
tree | 84311e5628175a91035ac2a62b16726650f2b62e | |
parent | 1722758717730ac0838418f142d82ca3cff4ad4b (diff) | |
download | quickjs-89007660998db0ee55f0ab3b34bb1a84f86fd3c4.tar.gz quickjs-89007660998db0ee55f0ab3b34bb1a84f86fd3c4.zip |
2020-07-05 release
-rw-r--r-- | Changelog | 9 | ||||
-rw-r--r-- | Makefile | 27 | ||||
-rw-r--r-- | TODO | 8 | ||||
-rw-r--r-- | VERSION | 2 | ||||
-rw-r--r-- | cutils.h | 4 | ||||
-rw-r--r-- | doc/jsbignum.html | 4 | ||||
-rw-r--r-- | doc/jsbignum.pdf | bin | 153057 -> 153049 bytes | |||
-rw-r--r-- | doc/quickjs.html | 95 | ||||
-rw-r--r-- | doc/quickjs.pdf | bin | 163753 -> 165612 bytes | |||
-rw-r--r-- | doc/quickjs.texi | 70 | ||||
-rw-r--r-- | examples/point.c | 2 | ||||
-rw-r--r-- | libbf.c | 75 | ||||
-rw-r--r-- | libregexp.c | 49 | ||||
-rw-r--r-- | qjs.c | 15 | ||||
-rw-r--r-- | qjsc.c | 17 | ||||
-rw-r--r-- | quickjs-atom.h | 1 | ||||
-rw-r--r-- | quickjs-libc.c | 1322 | ||||
-rw-r--r-- | quickjs-libc.h | 9 | ||||
-rw-r--r-- | quickjs.c | 3311 | ||||
-rw-r--r-- | quickjs.h | 29 | ||||
-rw-r--r-- | repl.js | 72 | ||||
-rw-r--r-- | test262.conf | 1 | ||||
-rw-r--r-- | test262_errors.txt | 6 | ||||
-rw-r--r-- | tests/bjson.c | 16 | ||||
-rw-r--r-- | tests/microbench.js | 14 | ||||
-rw-r--r-- | tests/test_bjson.js | 80 | ||||
-rw-r--r-- | tests/test_builtin.js | 4 | ||||
-rw-r--r-- | tests/test_language.js (renamed from tests/test_op.js) | 16 | ||||
-rw-r--r-- | tests/test_std.js | 21 | ||||
-rw-r--r-- | tests/test_worker.js | 93 |
30 files changed, 3709 insertions, 1663 deletions
@@ -1,3 +1,12 @@ +2020-07-05: + +- modified JS_GetPrototype() to return a live value +- REPL: support unicode characters larger than 16 bits +- added os.Worker +- improved object serialization +- added std.parseExtJSON +- misc bug fixes + 2020-04-12: - added cross realm support @@ -99,6 +99,10 @@ DEFINES:=-D_GNU_SOURCE -DCONFIG_VERSION=\"$(shell cat VERSION)\" ifdef CONFIG_BIGNUM DEFINES+=-DCONFIG_BIGNUM endif +ifdef CONFIG_WIN32 +DEFINES+=-D__USE_MINGW_ANSI_STDIO # for standard snprintf behavior +endif + CFLAGS+=$(DEFINES) CFLAGS_DEBUG=$(CFLAGS) -O0 CFLAGS_SMALL=$(CFLAGS) -Os @@ -115,8 +119,8 @@ CFLAGS+=-p LDFLAGS+=-p endif ifdef CONFIG_ASAN -CFLAGS+=-fsanitize=address -LDFLAGS+=-fsanitize=address +CFLAGS+=-fsanitize=address -fno-omit-frame-pointer +LDFLAGS+=-fsanitize=address -fno-omit-frame-pointer endif ifdef CONFIG_WIN32 LDEXPORT= @@ -166,9 +170,10 @@ QJS_LIB_OBJS+=$(OBJDIR)/libbf.o QJS_OBJS+=$(OBJDIR)/qjscalc.o endif +HOST_LIBS=-lm -ldl -lpthread LIBS=-lm ifndef CONFIG_WIN32 -LIBS+=-ldl +LIBS+=-ldl -lpthread endif $(OBJDIR): @@ -187,7 +192,7 @@ ifneq ($(CROSS_PREFIX),) $(QJSC): $(OBJDIR)/qjsc.host.o \ $(patsubst %.o, %.host.o, $(QJS_LIB_OBJS)) - $(HOST_CC) $(LDFLAGS) -o $@ $^ $(LIBS) + $(HOST_CC) $(LDFLAGS) -o $@ $^ $(HOST_LIBS) endif #CROSS_PREFIX @@ -239,13 +244,13 @@ libunicode-table.h: unicode_gen endif run-test262: $(OBJDIR)/run-test262.o $(QJS_LIB_OBJS) - $(CC) $(LDFLAGS) -o $@ $^ $(LIBS) -lpthread + $(CC) $(LDFLAGS) -o $@ $^ $(LIBS) run-test262-debug: $(patsubst %.o, %.debug.o, $(OBJDIR)/run-test262.o $(QJS_LIB_OBJS)) - $(CC) $(LDFLAGS) -o $@ $^ $(LIBS) -lpthread + $(CC) $(LDFLAGS) -o $@ $^ $(LIBS) run-test262-32: $(patsubst %.o, %.m32.o, $(OBJDIR)/run-test262.o $(QJS_LIB_OBJS)) - $(CC) -m32 $(LDFLAGS) -o $@ $^ $(LIBS) -lpthread + $(CC) -m32 $(LDFLAGS) -o $@ $^ $(LIBS) # object suffix order: nolto, [m32|m32s] @@ -285,7 +290,7 @@ unicode_gen: $(OBJDIR)/unicode_gen.host.o $(OBJDIR)/cutils.host.o libunicode.c u clean: rm -f repl.c qjscalc.c out.c rm -f *.a *.o *.d *~ jscompress unicode_gen regexp_test $(PROGS) - rm -f hello.c hello_module.c test_fib.c + rm -f hello.c test_fib.c rm -f examples/*.so tests/*.so rm -rf $(OBJDIR)/ *.dSYM/ qjs-debug rm -rf run-test262-debug run-test262-32 @@ -379,10 +384,11 @@ endif test: qjs ./qjs tests/test_closure.js - ./qjs tests/test_op.js + ./qjs tests/test_language.js ./qjs tests/test_builtin.js ./qjs tests/test_loop.js ./qjs tests/test_std.js + ./qjs tests/test_worker.js ifndef CONFIG_DARWIN ifdef CONFIG_BIGNUM ./qjs --bignum tests/test_bjson.js @@ -398,10 +404,11 @@ ifdef CONFIG_BIGNUM endif ifdef CONFIG_M32 ./qjs32 tests/test_closure.js - ./qjs32 tests/test_op.js + ./qjs32 tests/test_language.js ./qjs32 tests/test_builtin.js ./qjs32 tests/test_loop.js ./qjs32 tests/test_std.js + ./qjs32 tests/test_worker.js ifdef CONFIG_BIGNUM ./qjs32 --bignum tests/test_op_overloading.js ./qjs32 --bignum tests/test_bignum.js @@ -1,4 +1,5 @@ Misc: +- use realpath in module name normalizer and put it in quickjs-libc - use custom printf to avoid C library compatibility issues - rename CONFIG_ALL_UNICODE, CONFIG_BIGNUM, CONFIG_ATOMICS, CONFIG_CHECK_JSVALUE ? - unify coding style and naming conventions @@ -56,12 +57,12 @@ Extensions: handle #if, #ifdef, #line, limited support for #define - get rid of __loadScript, use more common name - BSD sockets -- Workers REPL: - debugger - readline: support MS Windows terminal - readline: handle dynamic terminal resizing +- readline: handle double width unicode characters - multiline editing - runtime object and function inspectors - interactive object browser @@ -73,6 +74,5 @@ REPL: Test262o: 0/11262 errors, 463 excluded Test262o commit: 7da91bceb9ce7613f87db47ddd1292a2dda58b42 (es5-tests branch) -Test262: 28/70829 errors, 877 excluded, 425 skipped -Test262 commit: 4a8e49b3ca7f9f74a4cafe6621ff9ba548ccc353 - +Test262: 30/71095 errors, 870 excluded, 549 skipped +Test262 commit: 281eb10b2844929a7c0ac04527f5b42ce56509fd @@ -1 +1 @@ -2020-04-12 +2020-07-05 @@ -268,6 +268,10 @@ void dbuf_free(DynBuf *s); static inline BOOL dbuf_error(DynBuf *s) { return s->error; } +static inline void dbuf_set_error(DynBuf *s) +{ + s->error = TRUE; +} #define UTF8_CHAR_LEN_MAX 6 diff --git a/doc/jsbignum.html b/doc/jsbignum.html index ab31612..62c0287 100644 --- a/doc/jsbignum.html +++ b/doc/jsbignum.html @@ -1,7 +1,8 @@ <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> -<!-- Created by GNU Texinfo 6.1, http://www.gnu.org/software/texinfo/ --> +<!-- Created by GNU Texinfo 6.5, 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"> @@ -9,7 +10,6 @@ <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 differindex 442a8c0..7ca15ea 100644 --- a/doc/jsbignum.pdf +++ b/doc/jsbignum.pdf diff --git a/doc/quickjs.html b/doc/quickjs.html index 550d2a6..554f391 100644 --- a/doc/quickjs.html +++ b/doc/quickjs.html @@ -1,7 +1,8 @@ <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> -<!-- Created by GNU Texinfo 6.1, http://www.gnu.org/software/texinfo/ --> +<!-- Created by GNU Texinfo 6.5, 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"> @@ -9,7 +10,6 @@ <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"> <!-- @@ -73,10 +73,9 @@ ul.no-bullet {list-style: none} <li><a name="toc-Language-support" href="#Language-support">3.1 Language support</a> <ul class="no-bullet"> <li><a name="toc-ES2020-support" href="#ES2020-support">3.1.1 ES2020 support</a></li> - <li><a name="toc-JSON" href="#JSON">3.1.2 JSON</a></li> - <li><a name="toc-ECMA402" href="#ECMA402">3.1.3 ECMA402</a></li> - <li><a name="toc-Extensions" href="#Extensions">3.1.4 Extensions</a></li> - <li><a name="toc-Mathematical-extensions" href="#Mathematical-extensions">3.1.5 Mathematical extensions</a></li> + <li><a name="toc-ECMA402" href="#ECMA402">3.1.2 ECMA402</a></li> + <li><a name="toc-Extensions" href="#Extensions">3.1.3 Extensions</a></li> + <li><a name="toc-Mathematical-extensions" href="#Mathematical-extensions">3.1.4 Mathematical extensions</a></li> </ul></li> <li><a name="toc-Modules" href="#Modules">3.2 Modules</a></li> <li><a name="toc-Standard-library" href="#Standard-library">3.3 Standard library</a> @@ -407,18 +406,13 @@ B (legacy web compatibility) and the Unicode related features. </li></ul> -<a name="JSON"></a> -<h4 class="subsection">3.1.2 JSON</h4> - -<p>The JSON parser is currently more tolerant than the specification. -</p> <a name="ECMA402"></a> -<h4 class="subsection">3.1.3 ECMA402</h4> +<h4 class="subsection">3.1.2 ECMA402</h4> <p>ECMA402 (Internationalization API) is not supported. </p> <a name="Extensions"></a> -<h4 class="subsection">3.1.4 Extensions</h4> +<h4 class="subsection">3.1.3 Extensions</h4> <ul> <li> The directive <code>"use strip"</code> indicates that the debug information (including the source code of the functions) should not be retained to save memory. As <code>"use strict"</code>, the directive can be global to a script or local to a function. @@ -428,7 +422,7 @@ B (legacy web compatibility) and the Unicode related features. </li></ul> <a name="Mathematical-extensions"></a> -<h4 class="subsection">3.1.5 Mathematical extensions</h4> +<h4 class="subsection">3.1.4 Mathematical extensions</h4> <p>The mathematical extensions are fully backward compatible with standard Javascript. See <code>jsbignum.pdf</code> for more information. @@ -557,7 +551,7 @@ no error occured. </p> </dd> <dt><code>printf(fmt, ...args)</code></dt> -<dd><p>Equivalent to <code>std.out.printf(fmt, ...args)</code> +<dd><p>Equivalent to <code>std.out.printf(fmt, ...args)</code>. </p> </dd> <dt><code>sprintf(fmt, ...args)</code></dt> @@ -637,6 +631,21 @@ optional properties: </dl> </dd> +<dt><code>parseExtJSON(str)</code></dt> +<dd> +<p>Parse <code>str</code> using a superset of <code>JSON.parse</code>. The + following extensions are accepted: +</p> +<ul> +<li> Single line and multiline comments + </li><li> unquoted properties (ASCII-only Javascript identifiers) + </li><li> trailing comma in array and object definitions + </li><li> single quoted strings + </li><li> <code>\f</code> and <code>\v</code> are accepted as space characters + </li><li> leading plus in numbers + </li><li> octal (<code>0o</code> prefix) and hexadecimal (<code>0x</code> prefix) numbers + </li></ul> +</dd> </dl> <p>FILE prototype: @@ -649,8 +658,14 @@ optional properties: <dd><p>Outputs the string with the UTF-8 encoding. </p></dd> <dt><code>printf(fmt, ...args)</code></dt> -<dd><p>Formatted printf, same formats as the libc printf. -</p></dd> +<dd><p>Formatted printf. +</p> +<p>The same formats as the standard C library <code>printf</code> are +supported. Integer format types (e.g. <code>%d</code>) truncate the Numbers +or BigInts to 32 bits. Use the <code>l</code> modifier (e.g. <code>%ld</code>) to +truncate to 64 bits. +</p> +</dd> <dt><code>flush()</code></dt> <dd><p>Flush the buffered file. </p></dd> @@ -718,6 +733,7 @@ is read up its end. </li><li> signals </li><li> timers </li><li> asynchronous I/O +</li><li> workers (threads) </li></ul> <p>The OS functions usually return 0 if OK or an OS specific negative @@ -869,7 +885,7 @@ the handler. <dd><p>Call the function <code>func</code> when the signal <code>signal</code> happens. Only a single handler per signal number is supported. Use <code>null</code> to set the default handler or <code>undefined</code> to ignore -the signal. +the signal. Signal handlers can only be defined in the main thread. </p> </dd> <dt><code>SIGINT</code></dt> @@ -973,6 +989,49 @@ to the timer. <code>"win32"</code> or <code>"js"</code>. </p> </dd> +<dt><code>Worker(source)</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’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> +<dl compact="compact"> +<dt><code>parent</code></dt> +<dd><p>In the created worker, <code>Worker.parent</code> represents the parent + worker and is used to send or receive messages. + </p></dd> +</dl> + +<p>The worker instances have the following properties: +</p> +<dl compact="compact"> +<dt><code>postMessage(msg)</code></dt> +<dd> +<p>Send a message to the corresponding worker. <code>msg</code> is cloned in + the destination worker using an algorithm similar to the <code>HTML</code> + structured clone algorithm. <code>SharedArrayBuffer</code> are shared + between workers. +</p> +<p>Current limitations: <code>Map</code> and <code>Set</code> are not supported + yet. +</p> +</dd> +<dt><code>onmessage</code></dt> +<dd> +<p>Getter and setter. Set a function which is called each time a + message is received. The function is called with a single + argument. It is an object with a <code>data</code> property containing the + received message. The thread is not terminated if there is at least + one non <code>null</code> <code>onmessage</code> handler. +</p> +</dd> +</dl> + +</dd> </dl> <a name="QuickJS-C-API"></a> diff --git a/doc/quickjs.pdf b/doc/quickjs.pdf Binary files differindex 1cba474..04db9d2 100644 --- a/doc/quickjs.pdf +++ b/doc/quickjs.pdf diff --git a/doc/quickjs.texi b/doc/quickjs.texi index 04156af..17c1b9c 100644 --- a/doc/quickjs.texi +++ b/doc/quickjs.texi @@ -272,10 +272,6 @@ The following features are not supported yet: @end itemize -@subsection JSON - -The JSON parser is currently more tolerant than the specification. - @subsection ECMA402 ECMA402 (Internationalization API) is not supported. @@ -405,7 +401,7 @@ no error occured. Equivalent to @code{std.out.puts(str)}. @item printf(fmt, ...args) -Equivalent to @code{std.out.printf(fmt, ...args)} +Equivalent to @code{std.out.printf(fmt, ...args)}. @item sprintf(fmt, ...args) Equivalent to the libc sprintf(). @@ -474,6 +470,20 @@ optional properties: @end table +@item parseExtJSON(str) + + Parse @code{str} using a superset of @code{JSON.parse}. The + following extensions are accepted: + + @itemize + @item Single line and multiline comments + @item unquoted properties (ASCII-only Javascript identifiers) + @item trailing comma in array and object definitions + @item single quoted strings + @item @code{\f} and @code{\v} are accepted as space characters + @item leading plus in numbers + @item octal (@code{0o} prefix) and hexadecimal (@code{0x} prefix) numbers + @end itemize @end table FILE prototype: @@ -484,7 +494,13 @@ Close the file. Return 0 if OK or @code{-errno} in case of I/O error. @item puts(str) Outputs the string with the UTF-8 encoding. @item printf(fmt, ...args) -Formatted printf, same formats as the libc printf. +Formatted printf. + +The same formats as the standard C library @code{printf} are +supported. Integer format types (e.g. @code{%d}) truncate the Numbers +or BigInts to 32 bits. Use the @code{l} modifier (e.g. @code{%ld}) to +truncate to 64 bits. + @item flush() Flush the buffered file. @item seek(offset, whence) @@ -537,6 +553,7 @@ The @code{os} module provides Operating System specific functions: @item signals @item timers @item asynchronous I/O +@item workers (threads) @end itemize The OS functions usually return 0 if OK or an OS specific negative @@ -664,7 +681,7 @@ the handler. Call the function @code{func} when the signal @code{signal} happens. Only a single handler per signal number is supported. Use @code{null} to set the default handler or @code{undefined} to ignore -the signal. +the signal. Signal handlers can only be defined in the main thread. @item SIGINT @item SIGABRT @@ -747,6 +764,45 @@ Cancel a timer. Return a string representing the platform: @code{"linux"}, @code{"darwin"}, @code{"win32"} or @code{"js"}. +@item Worker(source) +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}. + +The worker class has the following static properties: + + @table @code + @item parent + In the created worker, @code{Worker.parent} represents the parent + worker and is used to send or receive messages. + @end table + +The worker instances have the following properties: + + @table @code + @item postMessage(msg) + + Send a message to the corresponding worker. @code{msg} is cloned in + the destination worker using an algorithm similar to the @code{HTML} + structured clone algorithm. @code{SharedArrayBuffer} are shared + between workers. + + Current limitations: @code{Map} and @code{Set} are not supported + yet. + + @item onmessage + + Getter and setter. Set a function which is called each time a + message is received. The function is called with a single + argument. It is an object with a @code{data} property containing the + received message. The thread is not terminated if there is at least + one non @code{null} @code{onmessage} handler. + + @end table + @end table @section QuickJS C API diff --git a/examples/point.c b/examples/point.c index 049d135..fbe2ce1 100644 --- a/examples/point.c +++ b/examples/point.c @@ -130,11 +130,11 @@ static int js_point_init(JSContext *ctx, JSModuleDef *m) point_proto = JS_NewObject(ctx); JS_SetPropertyFunctionList(ctx, point_proto, js_point_proto_funcs, countof(js_point_proto_funcs)); - JS_SetClassProto(ctx, js_point_class_id, point_proto); point_class = JS_NewCFunction2(ctx, js_point_ctor, "Point", 2, JS_CFUNC_constructor, 0); /* set proto.constructor and ctor.prototype */ JS_SetConstructor(ctx, point_class, point_proto); + JS_SetClassProto(ctx, js_point_class_id, point_proto); JS_SetModuleExport(ctx, m, "Point", point_class); return 0; @@ -3370,12 +3370,14 @@ slimb_t bf_mul_log2_radix(slimb_t a1, unsigned int radix, int is_inv, } /* 'n' is the number of output limbs */ -static void bf_integer_to_radix_rec(bf_t *pow_tab, - limb_t *out, const bf_t *a, limb_t n, - int level, limb_t n0, limb_t radixl, - unsigned int radixl_bits) +static int bf_integer_to_radix_rec(bf_t *pow_tab, + limb_t *out, const bf_t *a, limb_t n, + int level, limb_t n0, limb_t radixl, + unsigned int radixl_bits) { limb_t n1, n2, q_prec; + int ret; + assert(n >= 1); if (n == 1) { out[0] = get_bits(a->tab, a->len, a->len * LIMB_BITS - a->expn); @@ -3402,63 +3404,81 @@ static void bf_integer_to_radix_rec(bf_t *pow_tab, n1 = n - n2; B = &pow_tab[2 * level]; B_inv = &pow_tab[2 * level + 1]; + ret = 0; if (B->len == 0) { /* compute BASE^n2 */ - bf_pow_ui_ui(B, radixl, n2, BF_PREC_INF, BF_RNDZ); + ret |= bf_pow_ui_ui(B, radixl, n2, BF_PREC_INF, BF_RNDZ); /* we use enough bits for the maximum possible 'n1' value, i.e. n2 + 1 */ - bf_set_ui(&R, 1); - bf_div(B_inv, &R, B, (n2 + 1) * radixl_bits + 2, BF_RNDN); + ret |= bf_set_ui(&R, 1); + ret |= bf_div(B_inv, &R, B, (n2 + 1) * radixl_bits + 2, BF_RNDN); } // printf("%d: n1=% " PRId64 " n2=%" PRId64 "\n", level, n1, n2); q_prec = n1 * radixl_bits; - bf_mul(&Q, a, B_inv, q_prec, BF_RNDN); - bf_rint(&Q, BF_RNDZ); + ret |= bf_mul(&Q, a, B_inv, q_prec, BF_RNDN); + ret |= bf_rint(&Q, BF_RNDZ); - bf_mul(&R, &Q, B, BF_PREC_INF, BF_RNDZ); - bf_sub(&R, a, &R, BF_PREC_INF, BF_RNDZ); + ret |= bf_mul(&R, &Q, B, BF_PREC_INF, BF_RNDZ); + ret |= bf_sub(&R, a, &R, BF_PREC_INF, BF_RNDZ); + + if (ret & BF_ST_MEM_ERROR) + goto fail; /* adjust if necessary */ q_add = 0; while (R.sign && R.len != 0) { - bf_add(&R, &R, B, BF_PREC_INF, BF_RNDZ); + if (bf_add(&R, &R, B, BF_PREC_INF, BF_RNDZ)) + goto fail; q_add--; } while (bf_cmpu(&R, B) >= 0) { - bf_sub(&R, &R, B, BF_PREC_INF, BF_RNDZ); + if (bf_sub(&R, &R, B, BF_PREC_INF, BF_RNDZ)) + goto fail; q_add++; } if (q_add != 0) { - bf_add_si(&Q, &Q, q_add, BF_PREC_INF, BF_RNDZ); + if (bf_add_si(&Q, &Q, q_add, BF_PREC_INF, BF_RNDZ)) + goto fail; + } + if (bf_integer_to_radix_rec(pow_tab, out + n2, &Q, n1, level + 1, n0, + radixl, radixl_bits)) + goto fail; + if (bf_integer_to_radix_rec(pow_tab, out, &R, n2, level + 1, n0, + radixl, radixl_bits)) { + fail: + bf_delete(&Q); + bf_delete(&R); + return -1; } - bf_integer_to_radix_rec(pow_tab, out + n2, &Q, n1, level + 1, n0, - radixl, radixl_bits); - bf_integer_to_radix_rec(pow_tab, out, &R, n2, level + 1, n0, - radixl, radixl_bits); bf_delete(&Q); bf_delete(&R); } + return 0; } -static void bf_integer_to_radix(bf_t *r, const bf_t *a, limb_t radixl) +/* return 0 if OK != 0 if memory error */ +static int bf_integer_to_radix(bf_t *r, const bf_t *a, limb_t radixl) { bf_context_t *s = r->ctx; limb_t r_len; bf_t *pow_tab; - int i, pow_tab_len; + int i, pow_tab_len, ret; r_len = r->len; pow_tab_len = (ceil_log2(r_len) + 2) * 2; /* XXX: check */ pow_tab = bf_malloc(s, sizeof(pow_tab[0]) * pow_tab_len); + if (!pow_tab) + return -1; for(i = 0; i < pow_tab_len; i++) bf_init(r->ctx, &pow_tab[i]); - bf_integer_to_radix_rec(pow_tab, r->tab, a, r_len, 0, r_len, radixl, - ceil_log2(radixl)); + ret = bf_integer_to_radix_rec(pow_tab, r->tab, a, r_len, 0, r_len, radixl, + ceil_log2(radixl)); for(i = 0; i < pow_tab_len; i++) { bf_delete(&pow_tab[i]); } bf_free(s, pow_tab); + return ret; } /* a must be >= 0. 'P' is the wanted number of digits in radix @@ -3625,8 +3645,14 @@ static void output_digits(DynBuf *s, const bf_t *a1, int radix, limb_t n_digits, a = &a_s; bf_init(a1->ctx, a); n = (n_digits + digits_per_limb - 1) / digits_per_limb; - bf_resize(a, n); - bf_integer_to_radix(a, a1, radixl); + if (bf_resize(a, n)) { + dbuf_set_error(s); + goto done; + } + if (bf_integer_to_radix(a, a1, radixl)) { + dbuf_set_error(s); + goto done; + } radix_bits = 0; pos = n; pos_incr = 1; @@ -3659,6 +3685,7 @@ static void output_digits(DynBuf *s, const bf_t *a1, int radix, limb_t n_digits, buf_pos += l; i += l; } + done: if (a != a1) bf_delete(a); } diff --git a/libregexp.c b/libregexp.c index 7f6eef4..b455bf2 100644 --- a/libregexp.c +++ b/libregexp.c @@ -110,12 +110,14 @@ static inline int is_digit(int c) { return c >= '0' && c <= '9'; } -/* insert 'len' bytes at position 'pos' */ -static void dbuf_insert(DynBuf *s, int pos, int len) +/* insert 'len' bytes at position 'pos'. Return < 0 if error. */ +static int dbuf_insert(DynBuf *s, int pos, int len) { - dbuf_realloc(s, s->size + len); + if (dbuf_realloc(s, s->size + len)) + return -1; memmove(s->buf + pos + len, s->buf + pos, s->size - pos); s->size += len; + return 0; } /* canonicalize with the specific JS regexp rules */ @@ -434,6 +436,11 @@ static int __attribute__((format(printf, 2, 3))) re_parse_error(REParseState *s, return -1; } +static int re_parse_out_of_memory(REParseState *s) +{ + return re_parse_error(s, "out of memory"); +} + /* If allow_overflow is false, return -1 in case of overflow. Otherwise return INT32_MAX. */ static int parse_digits(const uint8_t **pp, BOOL allow_overflow) @@ -693,7 +700,7 @@ static int parse_unicode_property(REParseState *s, CharRange *cr, *pp = p; return 0; out_of_memory: - return re_parse_error(s, "out of memory"); + return re_parse_out_of_memory(s); } #endif /* CONFIG_ALL_UNICODE */ @@ -928,7 +935,7 @@ static int re_parse_char_class(REParseState *s, const uint8_t **pp) *pp = p; return 0; memory_error: - re_parse_error(s, "out of memory"); + re_parse_out_of_memory(s); fail: cr_free(cr); return -1; @@ -1295,6 +1302,8 @@ static int re_parse_term(REParseState *s, BOOL is_backward_dir) return -1; re_emit_op(s, REOP_match); /* jump after the 'match' after the lookahead is successful */ + if (dbuf_error(&s->byte_code)) + return -1; put_u32(s->byte_code.buf + pos, s->byte_code.size - (pos + 4)); } else if (p[2] == '<') { p += 3; @@ -1548,12 +1557,15 @@ static int re_parse_term(REParseState *s, BOOL is_backward_dir) if (quant_max > 0) { /* specific optimization for simple quantifiers */ + if (dbuf_error(&s->byte_code)) + goto out_of_memory; len = re_is_simple_quantifier(s->byte_code.buf + last_atom_start, s->byte_code.size - last_atom_start); if (len > 0) { re_emit_op(s, REOP_match); - dbuf_insert(&s->byte_code, last_atom_start, 17); + if (dbuf_insert(&s->byte_code, last_atom_start, 17)) + goto out_of_memory; pos = last_atom_start; s->byte_code.buf[pos++] = REOP_simple_greedy_quant; put_u32(&s->byte_code.buf[pos], @@ -1569,6 +1581,8 @@ static int re_parse_term(REParseState *s, BOOL is_backward_dir) } } + if (dbuf_error(&s->byte_code)) + goto out_of_memory; add_zero_advance_check = (re_check_advance(s->byte_code.buf + last_atom_start, s->byte_code.size - last_atom_start) == 0); } else { @@ -1582,7 +1596,8 @@ static int re_parse_term(REParseState *s, BOOL is_backward_dir) /* need to reset the capture in case the atom is not executed */ if (last_capture_count != s->capture_count) { - dbuf_insert(&s->byte_code, last_atom_start, 3); + if (dbuf_insert(&s->byte_code, last_atom_start, 3)) + goto out_of_memory; s->byte_code.buf[last_atom_start++] = REOP_save_reset; s->byte_code.buf[last_atom_start++] = last_capture_count; s->byte_code.buf[last_atom_start++] = s->capture_count - 1; @@ -1590,12 +1605,14 @@ static int re_parse_term(REParseState *s, BOOL is_backward_dir) if (quant_max == 0) { s->byte_code.size = last_atom_start; } else if (quant_max == 1) { - dbuf_insert(&s->byte_code, last_atom_start, 5); + if (dbuf_insert(&s->byte_code, last_atom_start, 5)) + goto out_of_memory; s->byte_code.buf[last_atom_start] = REOP_split_goto_first + greedy; put_u32(s->byte_code.buf + last_atom_start + 1, len); } else if (quant_max == INT32_MAX) { - dbuf_insert(&s->byte_code, last_atom_start, 5 + add_zero_advance_check); + if (dbuf_insert(&s->byte_code, last_atom_start, 5 + add_zero_advance_check)) + goto out_of_memory; s->byte_code.buf[last_atom_start] = REOP_split_goto_first + greedy; put_u32(s->byte_code.buf + last_atom_start + 1, @@ -1611,7 +1628,8 @@ static int re_parse_term(REParseState *s, BOOL is_backward_dir) re_emit_goto(s, REOP_goto, last_atom_start); } } else { - dbuf_insert(&s->byte_code, last_atom_start, 10); + if (dbuf_insert(&s->byte_code, last_atom_start, 10)) + goto out_of_memory; pos = last_atom_start; s->byte_code.buf[pos++] = REOP_push_i32; put_u32(s->byte_code.buf + pos, quant_max); @@ -1629,7 +1647,8 @@ static int re_parse_term(REParseState *s, BOOL is_backward_dir) if (quant_min == 1) { /* nothing to add */ } else { - dbuf_insert(&s->byte_code, last_atom_start, 5); + if (dbuf_insert(&s->byte_code, last_atom_start, 5)) + goto out_of_memory; s->byte_code.buf[last_atom_start] = REOP_push_i32; put_u32(s->byte_code.buf + last_atom_start + 1, quant_min); @@ -1670,6 +1689,8 @@ static int re_parse_term(REParseState *s, BOOL is_backward_dir) done: s->buf_ptr = p; return 0; + out_of_memory: + return re_parse_out_of_memory(s); } static int re_parse_alternative(REParseState *s, BOOL is_backward_dir) @@ -1719,7 +1740,9 @@ static int re_parse_disjunction(REParseState *s, BOOL is_backward_dir) len = s->byte_code.size - start; /* insert a split before the first alternative */ - dbuf_insert(&s->byte_code, start, 5); + if (dbuf_insert(&s->byte_code, start, 5)) { + return re_parse_out_of_memory(s); + } s->byte_code.buf[start] = REOP_split_next_first; put_u32(s->byte_code.buf + start + 1, len + 5); @@ -1844,7 +1867,7 @@ uint8_t *lre_compile(int *plen, char *error_msg, int error_msg_size, } if (dbuf_error(&s->byte_code)) { - re_parse_error(s, "out of memory"); + re_parse_out_of_memory(s); goto error; } @@ -270,6 +270,7 @@ void help(void) "-T --trace trace memory allocation\n" "-d --dump dump the memory usage stats\n" " --memory-limit n limit the memory usage to 'n' bytes\n" + " --stack-size n limit the stack size to 'n' bytes\n" " --unhandled-rejection dump unhandled promise rejections\n" "-q --quit just instantiate the interpreter and quit\n"); exit(1); @@ -295,7 +296,8 @@ int main(int argc, char **argv) #ifdef CONFIG_BIGNUM int load_jscalc, bignum_ext = 0; #endif - + size_t stack_size = 0; + #ifdef CONFIG_BIGNUM /* load jscalc runtime if invoked as 'qjscalc' */ { @@ -407,6 +409,14 @@ int main(int argc, char **argv) memory_limit = (size_t)strtod(argv[optind++], NULL); continue; } + if (!strcmp(longopt, "stack-size")) { + if (optind >= argc) { + fprintf(stderr, "expecting stack size"); + exit(1); + } + stack_size = (size_t)strtod(argv[optind++], NULL); + continue; + } if (opt) { fprintf(stderr, "qjs: unknown option '-%c'\n", opt); } else { @@ -428,6 +438,9 @@ int main(int argc, char **argv) } if (memory_limit != 0) JS_SetMemoryLimit(rt, memory_limit); + if (stack_size != 0) + JS_SetMaxStackSize(rt, stack_size); + js_std_init_handlers(rt); ctx = JS_NewContext(rt); if (!ctx) { fprintf(stderr, "qjs: cannot allocate JS context\n"); @@ -326,6 +326,7 @@ static const char main_c_template1[] = " JSRuntime *rt;\n" " JSContext *ctx;\n" " rt = JS_NewRuntime();\n" + " js_std_init_handlers(rt);\n" ; static const char main_c_template2[] = @@ -351,7 +352,8 @@ void help(void) "-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" - ); + "-S n set the maximum stack size to 'n' bytes (default=%d)\n", + JS_DEFAULT_STACK_SIZE); #ifdef CONFIG_LTO { int i; @@ -447,6 +449,7 @@ static int output_executable(const char *out_filename, const char *cfilename, *arg++ = libjsname; *arg++ = "-lm"; *arg++ = "-ldl"; + *arg++ = "-lpthread"; *arg = NULL; if (verbose) { @@ -487,6 +490,7 @@ int main(int argc, char **argv) BOOL use_lto; int module; OutputTypeEnum output_type; + size_t stack_size; #ifdef CONFIG_BIGNUM BOOL bignum_ext = FALSE; #endif @@ -499,13 +503,14 @@ int main(int argc, char **argv) byte_swap = FALSE; verbose = 0; use_lto = FALSE; + stack_size = 0; /* 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:"); + c = getopt(argc, argv, "ho:cN:f:mxevM:p:S:"); if (c == -1) break; switch(c) { @@ -580,6 +585,9 @@ int main(int argc, char **argv) case 'p': c_ident_prefix = optarg; break; + case 'S': + stack_size = (size_t)strtod(optarg, NULL); + break; default: break; } @@ -652,6 +660,11 @@ int main(int argc, char **argv) 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"); diff --git a/quickjs-atom.h b/quickjs-atom.h index 501aa1f..a353ad4 100644 --- a/quickjs-atom.h +++ b/quickjs-atom.h @@ -82,6 +82,7 @@ DEF(length, "length") DEF(fileName, "fileName") DEF(lineNumber, "lineNumber") DEF(message, "message") +DEF(errors, "errors") DEF(stack, "stack") DEF(name, "name") DEF(toString, "toString") diff --git a/quickjs-libc.c b/quickjs-libc.c index 4e6d221..eda23c7 100644 --- a/quickjs-libc.c +++ b/quickjs-libc.c @@ -40,28 +40,38 @@ #if defined(_WIN32) #include <windows.h> #include <conio.h> +#include <utime.h> #else #include <dlfcn.h> #include <termios.h> #include <sys/ioctl.h> #include <sys/wait.h> + #if defined(__APPLE__) typedef sig_t sighandler_t; +#if !defined(environ) +#include <crt_externs.h> +#define environ (*_NSGetEnviron()) +#endif +#endif /* __APPLE__ */ + #endif + +#if !defined(_WIN32) +/* enable the os.Worker API. IT relies on POSIX threads */ +#define USE_WORKER +#endif + +#ifdef USE_WORKER +#include <pthread.h> +#include <stdatomic.h> #endif #include "cutils.h" #include "list.h" #include "quickjs-libc.h" -static void js_std_dbuf_init(JSContext *ctx, DynBuf *s) -{ - dbuf_init2(s, JS_GetRuntime(ctx), (DynBufReallocFunc *)js_realloc_rt); -} - /* TODO: - - add worker - - add minimal VT100 emulation for win32 - add socket calls */ @@ -84,14 +94,54 @@ typedef struct { JSValue func; } JSOSTimer; -/* initialize the lists so js_std_free_handlers() can always be called */ -static struct list_head os_rw_handlers = LIST_HEAD_INIT(os_rw_handlers); -static struct list_head os_signal_handlers = LIST_HEAD_INIT(os_signal_handlers); -static struct list_head os_timers = LIST_HEAD_INIT(os_timers); +typedef struct { + struct list_head link; + uint8_t *data; + size_t data_len; + /* list of SharedArrayBuffers, necessary to free the message */ + uint8_t **sab_tab; + size_t sab_tab_len; +} JSWorkerMessage; + +typedef struct { + int ref_count; +#ifdef USE_WORKER + pthread_mutex_t mutex; +#endif + struct list_head msg_queue; /* list of JSWorkerMessage.link */ + int read_fd; + int write_fd; +} JSWorkerMessagePipe; + +typedef struct { + struct list_head link; + JSWorkerMessagePipe *recv_pipe; + JSValue on_message_func; +} JSWorkerMessageHandler; + +typedef struct JSThreadState { + struct list_head os_rw_handlers; /* list of JSOSRWHandler.link */ + struct list_head os_signal_handlers; /* list JSOSSignalHandler.link */ + struct list_head os_timers; /* list of JSOSTimer.link */ + struct list_head port_list; /* list of JSWorkerMessageHandler.link */ + int eval_script_recurse; /* only used in the main thread */ + /* not used in the main thread */ + JSWorkerMessagePipe *recv_pipe, *send_pipe; +} JSThreadState; + static uint64_t os_pending_signals; -static int eval_script_recurse; static int (*os_poll_func)(JSContext *ctx); +static void js_std_dbuf_init(JSContext *ctx, DynBuf *s) +{ + dbuf_init2(s, JS_GetRuntime(ctx), (DynBufReallocFunc *)js_realloc_rt); +} + +static BOOL my_isdigit(int c) +{ + return (c >= '0' && c <= '9'); +} + static JSValue js_printf_internal(JSContext *ctx, int argc, JSValueConst *argv, FILE *fp) { @@ -103,14 +153,12 @@ static JSValue js_printf_internal(JSContext *ctx, const uint8_t *fmt, *fmt_end; const uint8_t *p; char *q; - int i, c, len; + int i, c, len, mod; size_t fmt_len; int32_t int32_arg; int64_t int64_arg; double double_arg; const char *string_arg; - enum { PART_FLAGS, PART_WIDTH, PART_DOT, PART_PREC, PART_MODIFIER } part; - int modsize; /* Use indirect call to dbuf_printf to prevent gcc warning */ int (*dbuf_printf_fun)(DynBuf *s, const char *fmt, ...) = (void*)dbuf_printf; @@ -132,150 +180,162 @@ static JSValue js_printf_internal(JSContext *ctx, break; q = fmtbuf; *q++ = *fmt++; /* copy '%' */ - part = PART_FLAGS; - modsize = 0; - for (;;) { - if (q >= fmtbuf + sizeof(fmtbuf) - 1) - goto invalid; - - c = *fmt++; - *q++ = c; - *q = '\0'; - - switch (c) { - case '1': case '2': case '3': - case '4': case '5': case '6': - case '7': case '8': case '9': - if (part != PART_PREC) { - if (part <= PART_WIDTH) - part = PART_WIDTH; - else - goto invalid; - } - continue; - - case '0': case '#': case '+': case '-': case ' ': case '\'': - if (part > PART_FLAGS) - goto invalid; - continue; - - case '.': - if (part > PART_DOT) + + /* flags */ + for(;;) { + c = *fmt; + if (c == '0' || c == '#' || c == '+' || c == '-' || c == ' ' || + c == '\'') { + if (q >= fmtbuf + sizeof(fmtbuf) - 1) goto invalid; - part = PART_DOT; - continue; - - case '*': - if (part < PART_WIDTH) - part = PART_DOT; - else if (part == PART_DOT) - part = PART_MODIFIER; - else + *q++ = c; + fmt++; + } else { + break; + } + } + /* width */ + if (*fmt == '*') { + if (i >= argc) + goto missing; + if (JS_ToInt32(ctx, &int32_arg, argv[i++])) + goto fail; + q += snprintf(q, fmtbuf + sizeof(fmtbuf) - q, "%d", int32_arg); + fmt++; + } else { + while (my_isdigit(*fmt)) { + if (q >= fmtbuf + sizeof(fmtbuf) - 1) goto invalid; - + *q++ = *fmt++; + } + } + if (*fmt == '.') { + if (q >= fmtbuf + sizeof(fmtbuf) - 1) + goto invalid; + *q++ = *fmt++; + if (*fmt == '*') { if (i >= argc) goto missing; - if (JS_ToInt32(ctx, &int32_arg, argv[i++])) goto fail; q += snprintf(q, fmtbuf + sizeof(fmtbuf) - q, "%d", int32_arg); - continue; - - case 'h': - if (modsize != 0 && modsize != -1) - goto invalid; - modsize--; - part = PART_MODIFIER; - continue; - case 'l': - q--; - if (modsize != 0 && modsize != 1) - goto invalid; - modsize++; - part = PART_MODIFIER; - continue; - - case 'c': - if (i >= argc) - goto missing; - if (JS_IsString(argv[i])) { - string_arg = JS_ToCString(ctx, argv[i++]); - if (!string_arg) - goto fail; - int32_arg = unicode_from_utf8((uint8_t *)string_arg, UTF8_CHAR_LEN_MAX, &p); - JS_FreeCString(ctx, string_arg); - } else { - if (JS_ToInt32(ctx, &int32_arg, argv[i++])) - goto fail; - } - /* handle utf-8 encoding explicitly */ - if ((unsigned)int32_arg > 0x10FFFF) - int32_arg = 0xFFFD; - /* ignore conversion flags, width and precision */ - len = unicode_to_utf8(cbuf, int32_arg); - dbuf_put(&dbuf, cbuf, len); - break; - - case 'd': - case 'i': - case 'o': - case 'u': - case 'x': - case 'X': - if (i >= argc) - goto missing; - if (JS_ToInt64Ext(ctx, &int64_arg, argv[i++])) - goto fail; - if (modsize > 0) { - q[1] = q[-1]; - q[-1] = q[0] = 'l'; - q[2] = '\0'; - dbuf_printf_fun(&dbuf, fmtbuf, (long long)int64_arg); - } else { - dbuf_printf_fun(&dbuf, fmtbuf, (int)int64_arg); + fmt++; + } else { + while (my_isdigit(*fmt)) { + if (q >= fmtbuf + sizeof(fmtbuf) - 1) + goto invalid; + *q++ = *fmt++; } - break; + } + } - case 's': - if (i >= argc) - goto missing; - /* XXX: handle strings containing null characters */ + /* we only support the "l" modifier for 64 bit numbers */ + mod = ' '; + if (*fmt == 'l') { + mod = *fmt++; + } + + /* type */ + c = *fmt++; + if (q >= fmtbuf + sizeof(fmtbuf) - 1) + goto invalid; + *q++ = c; + *q = '\0'; + + switch (c) { + case 'c': + if (i >= argc) + goto missing; + if (JS_IsString(argv[i])) { string_arg = JS_ToCString(ctx, argv[i++]); if (!string_arg) goto fail; - dbuf_printf_fun(&dbuf, fmtbuf, string_arg); + int32_arg = unicode_from_utf8((uint8_t *)string_arg, UTF8_CHAR_LEN_MAX, &p); JS_FreeCString(ctx, string_arg); - break; - - case 'e': - case 'f': - case 'g': - case 'a': - case 'E': - case 'F': - case 'G': - case 'A': - if (i >= argc) - goto missing; - if (JS_ToFloat64(ctx, &double_arg, argv[i++])) + } else { + if (JS_ToInt32(ctx, &int32_arg, argv[i++])) goto fail; - dbuf_printf_fun(&dbuf, fmtbuf, double_arg); - break; - - case '%': - dbuf_putc(&dbuf, '%'); - break; + } + /* handle utf-8 encoding explicitly */ + if ((unsigned)int32_arg > 0x10FFFF) + int32_arg = 0xFFFD; + /* ignore conversion flags, width and precision */ + len = unicode_to_utf8(cbuf, int32_arg); + dbuf_put(&dbuf, cbuf, len); + break; + + case 'd': + case 'i': + case 'o': + case 'u': + case 'x': + case 'X': + if (i >= argc) + goto missing; + if (JS_ToInt64Ext(ctx, &int64_arg, argv[i++])) + goto fail; + if (mod == 'l') { + /* 64 bit number */ +#if defined(_WIN32) + if (q >= fmtbuf + sizeof(fmtbuf) - 3) + goto invalid; + q[2] = q[-1]; + q[-1] = 'I'; + q[0] = '6'; + q[1] = '4'; + q[3] = '\0'; + dbuf_printf_fun(&dbuf, fmtbuf, (int64_t)int64_arg); +#else + if (q >= fmtbuf + sizeof(fmtbuf) - 2) + goto invalid; + q[1] = q[-1]; + q[-1] = q[0] = 'l'; + q[2] = '\0'; + dbuf_printf_fun(&dbuf, fmtbuf, (long long)int64_arg); +#endif + } else { + dbuf_printf_fun(&dbuf, fmtbuf, (int)int64_arg); + } + break; - default: - /* XXX: should support an extension mechanism */ - invalid: - JS_ThrowTypeError(ctx, "invalid conversion specifier in format string"); + case 's': + if (i >= argc) + goto missing; + /* XXX: handle strings containing null characters */ + string_arg = JS_ToCString(ctx, argv[i++]); + if (!string_arg) goto fail; - missing: - JS_ThrowReferenceError(ctx, "missing argument for conversion specifier"); + dbuf_printf_fun(&dbuf, fmtbuf, string_arg); + JS_FreeCString(ctx, string_arg); + break; + + case 'e': + case 'f': + case 'g': + case 'a': + case 'E': + case 'F': + case 'G': + case 'A': + if (i >= argc) + goto missing; + if (JS_ToFloat64(ctx, &double_arg, argv[i++])) goto fail; - } + dbuf_printf_fun(&dbuf, fmtbuf, double_arg); + break; + + case '%': + dbuf_putc(&dbuf, '%'); break; + + default: + /* XXX: should support an extension mechanism */ + invalid: + JS_ThrowTypeError(ctx, "invalid conversion specifier in format string"); + goto fail; + missing: + JS_ThrowReferenceError(ctx, "missing argument for conversion specifier"); + goto fail; } } JS_FreeCString(ctx, fmt_str); @@ -593,6 +653,8 @@ static int get_bool_option(JSContext *ctx, BOOL *pbool, static JSValue js_evalScript(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { + JSRuntime *rt = JS_GetRuntime(ctx); + JSThreadState *ts = JS_GetRuntimeOpaque(rt); const char *str; size_t len; JSValue ret; @@ -610,7 +672,7 @@ static JSValue js_evalScript(JSContext *ctx, JSValueConst this_val, str = JS_ToCStringLen(ctx, &len, argv[0]); if (!str) return JS_EXCEPTION; - if (++eval_script_recurse == 1) { + if (!ts->recv_pipe && ++ts->eval_script_recurse == 1) { /* install the interrupt handler */ JS_SetInterruptHandler(JS_GetRuntime(ctx), interrupt_handler, NULL); } @@ -619,7 +681,7 @@ static JSValue js_evalScript(JSContext *ctx, JSValueConst this_val, flags |= JS_EVAL_FLAG_BACKTRACE_BARRIER; ret = JS_Eval(ctx, str, len, "<evalScript>", flags); JS_FreeCString(ctx, str); - if (--eval_script_recurse == 0) { + if (!ts->recv_pipe && --ts->eval_script_recurse == 0) { /* remove the interrupt handler */ JS_SetInterruptHandler(JS_GetRuntime(ctx), NULL, NULL); os_pending_signals &= ~((uint64_t)1 << SIGINT); @@ -669,6 +731,21 @@ static JSValue js_std_strerror(JSContext *ctx, JSValueConst this_val, return JS_NewString(ctx, strerror(err)); } +static JSValue js_std_parseExtJSON(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + JSValue obj; + const char *str; + size_t len; + + str = JS_ToCStringLen(ctx, &len, argv[0]); + if (!str) + return JS_EXCEPTION; + obj = JS_ParseJSON2(ctx, str, len, "<input>", JS_PARSE_JSON_EXT); + JS_FreeCString(ctx, str); + return obj; +} + static JSValue js_new_std_file(JSContext *ctx, FILE *f, BOOL close_in_finalizer, BOOL is_popen) @@ -1321,6 +1398,7 @@ static const JSCFunctionListEntry js_std_funcs[] = { JS_CFUNC_DEF("urlGet", 1, js_std_urlGet ), JS_CFUNC_DEF("loadFile", 1, js_std_loadFile ), JS_CFUNC_DEF("strerror", 1, js_std_strerror ), + JS_CFUNC_DEF("parseExtJSON", 1, js_std_parseExtJSON ), /* FILE I/O */ JS_CFUNC_DEF("open", 2, js_std_open ), @@ -1626,11 +1704,18 @@ static JSValue js_os_rename(JSContext *ctx, JSValueConst this_val, return JS_NewInt32(ctx, ret); } -static JSOSRWHandler *find_rh(int fd) +static BOOL is_main_thread(JSRuntime *rt) +{ + JSThreadState *ts = JS_GetRuntimeOpaque(rt); + return !ts->recv_pipe; +} + +static JSOSRWHandler *find_rh(JSThreadState *ts, int fd) { JSOSRWHandler *rh; struct list_head *el; - list_for_each(el, &os_rw_handlers) { + + list_for_each(el, &ts->os_rw_handlers) { rh = list_entry(el, JSOSRWHandler, link); if (rh->fd == fd) return rh; @@ -1651,6 +1736,8 @@ static void free_rw_handler(JSRuntime *rt, JSOSRWHandler *rh) static JSValue js_os_setReadHandler(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, int magic) { + JSRuntime *rt = JS_GetRuntime(ctx); + JSThreadState *ts = JS_GetRuntimeOpaque(rt); JSOSRWHandler *rh; int fd; JSValueConst func; @@ -1659,7 +1746,7 @@ static JSValue js_os_setReadHandler(JSContext *ctx, JSValueConst this_val, return JS_EXCEPTION; func = argv[1]; if (JS_IsNull(func)) { - rh = find_rh(fd); + rh = find_rh(ts, fd); if (rh) { JS_FreeValue(ctx, rh->rw_func[magic]); rh->rw_func[magic] = JS_NULL; @@ -1672,7 +1759,7 @@ static JSValue js_os_setReadHandler(JSContext *ctx, JSValueConst this_val, } else { if (!JS_IsFunction(ctx, func)) return JS_ThrowTypeError(ctx, "not a function"); - rh = find_rh(fd); + rh = find_rh(ts, fd); if (!rh) { rh = js_mallocz(ctx, sizeof(*rh)); if (!rh) @@ -1680,7 +1767,7 @@ static JSValue js_os_setReadHandler(JSContext *ctx, JSValueConst this_val, rh->fd = fd; rh->rw_func[0] = JS_NULL; rh->rw_func[1] = JS_NULL; - list_add_tail(&rh->link, &os_rw_handlers); + list_add_tail(&rh->link, &ts->os_rw_handlers); } JS_FreeValue(ctx, rh->rw_func[magic]); rh->rw_func[magic] = JS_DupValue(ctx, func); @@ -1688,11 +1775,11 @@ static JSValue js_os_setReadHandler(JSContext *ctx, JSValueConst this_val, return JS_UNDEFINED; } -static JSOSSignalHandler *find_sh(int sig_num) +static JSOSSignalHandler *find_sh(JSThreadState *ts, int sig_num) { JSOSSignalHandler *sh; struct list_head *el; - list_for_each(el, &os_signal_handlers) { + list_for_each(el, &ts->os_signal_handlers) { sh = list_entry(el, JSOSSignalHandler, link); if (sh->sig_num == sig_num) return sh; @@ -1719,10 +1806,15 @@ typedef void (*sighandler_t)(int sig_num); static JSValue js_os_signal(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { + JSRuntime *rt = JS_GetRuntime(ctx); + JSThreadState *ts = JS_GetRuntimeOpaque(rt); JSOSSignalHandler *sh; uint32_t sig_num; JSValueConst func; sighandler_t handler; + + if (!is_main_thread(rt)) + return JS_ThrowTypeError(ctx, "signal handler can only be set in the main thread"); if (JS_ToUint32(ctx, &sig_num, argv[0])) return JS_EXCEPTION; @@ -1731,7 +1823,7 @@ static JSValue js_os_signal(JSContext *ctx, JSValueConst this_val, func = argv[1]; /* func = null: SIG_DFL, func = undefined, SIG_IGN */ if (JS_IsNull(func) || JS_IsUndefined(func)) { - sh = find_sh(sig_num); + sh = find_sh(ts, sig_num); if (sh) { free_sh(JS_GetRuntime(ctx), sh); } @@ -1743,13 +1835,13 @@ static JSValue js_os_signal(JSContext *ctx, JSValueConst this_val, } else { if (!JS_IsFunction(ctx, func)) return JS_ThrowTypeError(ctx, "not a function"); - sh = find_sh(sig_num); + sh = find_sh(ts, sig_num); if (!sh) { sh = js_mallocz(ctx, sizeof(*sh)); if (!sh) return JS_EXCEPTION; sh->sig_num = sig_num; - list_add_tail(&sh->link, &os_signal_handlers); + list_add_tail(&sh->link, &ts->os_signal_handlers); } JS_FreeValue(ctx, sh->func); sh->func = JS_DupValue(ctx, func); @@ -1813,6 +1905,8 @@ static void js_os_timer_mark(JSRuntime *rt, JSValueConst val, static JSValue js_os_setTimeout(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { + JSRuntime *rt = JS_GetRuntime(ctx); + JSThreadState *ts = JS_GetRuntimeOpaque(rt); int64_t delay; JSValueConst func; JSOSTimer *th; @@ -1834,7 +1928,7 @@ static JSValue js_os_setTimeout(JSContext *ctx, JSValueConst this_val, th->has_object = TRUE; th->timeout = get_time_ms() + delay; th->func = JS_DupValue(ctx, func); - list_add_tail(&th->link, &os_timers); + list_add_tail(&th->link, &ts->os_timers); JS_SetOpaque(obj, th); return obj; } @@ -1872,6 +1966,8 @@ static void call_handler(JSContext *ctx, JSValueConst func) static int js_os_poll(JSContext *ctx) { + JSRuntime *rt = JS_GetRuntime(ctx); + JSThreadState *ts = JS_GetRuntimeOpaque(rt); int min_delay, console_fd; int64_t cur_time, delay; JSOSRWHandler *rh; @@ -1879,14 +1975,14 @@ static int js_os_poll(JSContext *ctx) /* XXX: handle signals if useful */ - if (list_empty(&os_rw_handlers) && list_empty(&os_timers)) + if (list_empty(&ts->os_rw_handlers) && list_empty(&ts->os_timers)) return -1; /* no more events */ /* XXX: only timers and basic console input are supported */ - if (!list_empty(&os_timers)) { + if (!list_empty(&ts->os_timers)) { cur_time = get_time_ms(); min_delay = 10000; - list_for_each(el, &os_timers) { + list_for_each(el, &ts->os_timers) { JSOSTimer *th = list_entry(el, JSOSTimer, link); delay = th->timeout - cur_time; if (delay <= 0) { @@ -1894,9 +1990,9 @@ static int js_os_poll(JSContext *ctx) /* the timer expired */ func = th->func; th->func = JS_UNDEFINED; - unlink_timer(JS_GetRuntime(ctx), th); + unlink_timer(rt, th); if (!th->has_object) - free_timer(JS_GetRuntime(ctx), th); + free_timer(rt, th); call_handler(ctx, func); JS_FreeValue(ctx, func); return 0; @@ -1909,7 +2005,7 @@ static int js_os_poll(JSContext *ctx) } console_fd = -1; - list_for_each(el, &os_rw_handlers) { + list_for_each(el, &ts->os_rw_handlers) { rh = list_entry(el, JSOSRWHandler, link); if (rh->fd == 0 && !JS_IsNull(rh->rw_func[0])) { console_fd = rh->fd; @@ -1927,7 +2023,7 @@ static int js_os_poll(JSContext *ctx) handle = (HANDLE)_get_osfhandle(console_fd); ret = WaitForSingleObject(handle, ti); if (ret == WAIT_OBJECT_0) { - list_for_each(el, &os_rw_handlers) { + list_for_each(el, &ts->os_rw_handlers) { rh = list_entry(el, JSOSRWHandler, link); if (rh->fd == console_fd && !JS_IsNull(rh->rw_func[0])) { call_handler(ctx, rh->rw_func[0]); @@ -1942,8 +2038,88 @@ static int js_os_poll(JSContext *ctx) return 0; } #else + +#ifdef USE_WORKER + +static void js_free_message(JSWorkerMessage *msg); + +/* return 1 if a message was handled, 0 if no message */ +static int handle_posted_message(JSRuntime *rt, JSContext *ctx, + JSWorkerMessageHandler *port) +{ + JSWorkerMessagePipe *ps = port->recv_pipe; + int ret; + struct list_head *el; + JSWorkerMessage *msg; + JSValue obj, data_obj, func, retval; + + pthread_mutex_lock(&ps->mutex); + if (!list_empty(&ps->msg_queue)) { + el = ps->msg_queue.next; + msg = list_entry(el, JSWorkerMessage, link); + + /* remove the message from the queue */ + list_del(&msg->link); + + if (list_empty(&ps->msg_queue)) { + uint8_t buf[16]; + int ret; + for(;;) { + ret = read(ps->read_fd, buf, sizeof(buf)); + if (ret >= 0) + break; + if (errno != EAGAIN && errno != EINTR) + break; + } + } + + pthread_mutex_unlock(&ps->mutex); + + data_obj = JS_ReadObject(ctx, msg->data, msg->data_len, + JS_READ_OBJ_SAB | JS_READ_OBJ_REFERENCE); + + js_free_message(msg); + + if (JS_IsException(data_obj)) + goto fail; + obj = JS_NewObject(ctx); + if (JS_IsException(obj)) { + JS_FreeValue(ctx, data_obj); + goto fail; + } + JS_DefinePropertyValueStr(ctx, obj, "data", data_obj, JS_PROP_C_W_E); + + /* 'func' might be destroyed when calling itself (if it frees the + handler), so must take extra care */ + func = JS_DupValue(ctx, port->on_message_func); + retval = JS_Call(ctx, func, JS_UNDEFINED, 1, (JSValueConst *)&obj); + JS_FreeValue(ctx, obj); + JS_FreeValue(ctx, func); + if (JS_IsException(retval)) { + fail: + js_std_dump_error(ctx); + } else { + JS_FreeValue(ctx, retval); + } + ret = 1; + } else { + pthread_mutex_unlock(&ps->mutex); + ret = 0; + } + return ret; +} +#else +static int handle_posted_message(JSRuntime *rt, JSContext *ctx, + JSWorkerMessageHandler *port) +{ + return 0; +} +#endif + static int js_os_poll(JSContext *ctx) { + JSRuntime *rt = JS_GetRuntime(ctx); + JSThreadState *ts = JS_GetRuntimeOpaque(rt); int ret, fd_max, min_delay; int64_t cur_time, delay; fd_set rfds, wfds; @@ -1951,11 +2127,13 @@ static int js_os_poll(JSContext *ctx) struct list_head *el; struct timeval tv, *tvp; - if (unlikely(os_pending_signals != 0)) { + /* only check signals in the main thread */ + if (!ts->recv_pipe && + unlikely(os_pending_signals != 0)) { JSOSSignalHandler *sh; uint64_t mask; - list_for_each(el, &os_signal_handlers) { + list_for_each(el, &ts->os_signal_handlers) { sh = list_entry(el, JSOSSignalHandler, link); mask = (uint64_t)1 << sh->sig_num; if (os_pending_signals & mask) { @@ -1965,14 +2143,15 @@ static int js_os_poll(JSContext *ctx) } } } - - if (list_empty(&os_rw_handlers) && list_empty(&os_timers)) + + if (list_empty(&ts->os_rw_handlers) && list_empty(&ts->os_timers) && + list_empty(&ts->port_list)) return -1; /* no more events */ - if (!list_empty(&os_timers)) { + if (!list_empty(&ts->os_timers)) { cur_time = get_time_ms(); min_delay = 10000; - list_for_each(el, &os_timers) { + list_for_each(el, &ts->os_timers) { JSOSTimer *th = list_entry(el, JSOSTimer, link); delay = th->timeout - cur_time; if (delay <= 0) { @@ -1980,9 +2159,9 @@ static int js_os_poll(JSContext *ctx) /* the timer expired */ func = th->func; th->func = JS_UNDEFINED; - unlink_timer(JS_GetRuntime(ctx), th); + unlink_timer(rt, th); if (!th->has_object) - free_timer(JS_GetRuntime(ctx), th); + free_timer(rt, th); call_handler(ctx, func); JS_FreeValue(ctx, func); return 0; @@ -2000,7 +2179,7 @@ static int js_os_poll(JSContext *ctx) FD_ZERO(&rfds); FD_ZERO(&wfds); fd_max = -1; - list_for_each(el, &os_rw_handlers) { + list_for_each(el, &ts->os_rw_handlers) { rh = list_entry(el, JSOSRWHandler, link); fd_max = max_int(fd_max, rh->fd); if (!JS_IsNull(rh->rw_func[0])) @@ -2008,25 +2187,46 @@ static int js_os_poll(JSContext *ctx) if (!JS_IsNull(rh->rw_func[1])) FD_SET(rh->fd, &wfds); } - + + list_for_each(el, &ts->port_list) { + JSWorkerMessageHandler *port = list_entry(el, JSWorkerMessageHandler, link); + if (!JS_IsNull(port->on_message_func)) { + JSWorkerMessagePipe *ps = port->recv_pipe; + fd_max = max_int(fd_max, ps->read_fd); + FD_SET(ps->read_fd, &rfds); + } + } + ret = select(fd_max + 1, &rfds, &wfds, NULL, tvp); if (ret > 0) { - list_for_each(el, &os_rw_handlers) { + list_for_each(el, &ts->os_rw_handlers) { rh = list_entry(el, JSOSRWHandler, link); if (!JS_IsNull(rh->rw_func[0]) && FD_ISSET(rh->fd, &rfds)) { call_handler(ctx, rh->rw_func[0]); /* must stop because the list may have been modified */ - break; + goto done; } - if (!JS_IsNull(rh->rw_func[1])) { - FD_SET(rh->fd, &wfds); + if (!JS_IsNull(rh->rw_func[1]) && + FD_ISSET(rh->fd, &wfds)) { call_handler(ctx, rh->rw_func[1]); /* must stop because the list may have been modified */ - break; + goto done; + } + } + + list_for_each(el, &ts->port_list) { + JSWorkerMessageHandler *port = list_entry(el, JSWorkerMessageHandler, link); + if (!JS_IsNull(port->on_message_func)) { + JSWorkerMessagePipe *ps = port->recv_pipe; + if (FD_ISSET(ps->read_fd, &rfds)) { + if (handle_posted_message(rt, ctx, port)) + goto done; + } } } } + done: return 0; } #endif /* !_WIN32 */ @@ -2071,8 +2271,6 @@ static JSValue js_os_getcwd(JSContext *ctx, JSValueConst this_val, return make_string_error(ctx, buf, err); } -#if !defined(_WIN32) - static JSValue js_os_chdir(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { @@ -2087,28 +2285,6 @@ static JSValue js_os_chdir(JSContext *ctx, JSValueConst this_val, return JS_NewInt32(ctx, err); } -/* return [path, errorcode] */ -static JSValue js_os_realpath(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - const char *path; - char buf[PATH_MAX], *res; - int err; - - path = JS_ToCString(ctx, argv[0]); - if (!path) - return JS_EXCEPTION; - res = realpath(path, buf); - JS_FreeCString(ctx, path); - if (!res) { - buf[0] = '\0'; - err = errno; - } else { - err = 0; - } - return make_string_error(ctx, buf, err); -} - static JSValue js_os_mkdir(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { @@ -2124,15 +2300,66 @@ static JSValue js_os_mkdir(JSContext *ctx, JSValueConst this_val, path = JS_ToCString(ctx, argv[0]); if (!path) return JS_EXCEPTION; +#if defined(_WIN32) + (void)mode; + ret = js_get_errno(mkdir(path)); +#else ret = js_get_errno(mkdir(path, mode)); +#endif JS_FreeCString(ctx, path); return JS_NewInt32(ctx, ret); } +/* return [array, errorcode] */ +static JSValue js_os_readdir(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + const char *path; + DIR *f; + struct dirent *d; + JSValue obj; + int err; + uint32_t len; + + path = JS_ToCString(ctx, argv[0]); + if (!path) + return JS_EXCEPTION; + obj = JS_NewArray(ctx); + if (JS_IsException(obj)) { + JS_FreeCString(ctx, path); + return JS_EXCEPTION; + } + f = opendir(path); + if (!f) + err = errno; + else + err = 0; + JS_FreeCString(ctx, path); + if (!f) + goto done; + len = 0; + for(;;) { + errno = 0; + d = readdir(f); + if (!d) { + err = errno; + break; + } + JS_DefinePropertyValueUint32(ctx, obj, len++, + JS_NewString(ctx, d->d_name), + JS_PROP_C_W_E); + } + closedir(f); + done: + return make_obj_error(ctx, obj, err); +} + +#if !defined(_WIN32) static int64_t timespec_to_ms(const struct timespec *tv) { return (int64_t)tv->tv_sec * 1000 + (tv->tv_nsec / 1000000); } +#endif /* return [obj, errcode] */ static JSValue js_os_stat(JSContext *ctx, JSValueConst this_val, @@ -2146,10 +2373,14 @@ static JSValue js_os_stat(JSContext *ctx, JSValueConst this_val, path = JS_ToCString(ctx, argv[0]); if (!path) return JS_EXCEPTION; +#if defined(_WIN32) + res = stat(path, &st); +#else if (is_lstat) res = lstat(path, &st); else res = stat(path, &st); +#endif JS_FreeCString(ctx, path); if (res < 0) { err = errno; @@ -2183,10 +2414,22 @@ static JSValue js_os_stat(JSContext *ctx, JSValueConst this_val, JS_DefinePropertyValueStr(ctx, obj, "size", JS_NewInt64(ctx, st.st_size), JS_PROP_C_W_E); +#if !defined(_WIN32) JS_DefinePropertyValueStr(ctx, obj, "blocks", JS_NewInt64(ctx, st.st_blocks), JS_PROP_C_W_E); -#if defined(__APPLE__) +#endif +#if defined(_WIN32) + JS_DefinePropertyValueStr(ctx, obj, "atime", + JS_NewInt64(ctx, (int64_t)st.st_atime * 1000), + JS_PROP_C_W_E); + JS_DefinePropertyValueStr(ctx, obj, "mtime", + JS_NewInt64(ctx, (int64_t)st.st_mtime * 1000), + JS_PROP_C_W_E); + JS_DefinePropertyValueStr(ctx, obj, "ctime", + JS_NewInt64(ctx, (int64_t)st.st_ctime * 1000), + JS_PROP_C_W_E); +#elif defined(__APPLE__) JS_DefinePropertyValueStr(ctx, obj, "atime", JS_NewInt64(ctx, timespec_to_ms(&st.st_atimespec)), JS_PROP_C_W_E); @@ -2211,6 +2454,71 @@ static JSValue js_os_stat(JSContext *ctx, JSValueConst this_val, return make_obj_error(ctx, obj, err); } +#if !defined(_WIN32) +static void ms_to_timeval(struct timeval *tv, uint64_t v) +{ + tv->tv_sec = v / 1000; + tv->tv_usec = (v % 1000) * 1000; +} +#endif + +static JSValue js_os_utimes(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + const char *path; + int64_t atime, mtime; + int ret; + + if (JS_ToInt64(ctx, &atime, argv[1])) + return JS_EXCEPTION; + if (JS_ToInt64(ctx, &mtime, argv[2])) + return JS_EXCEPTION; + path = JS_ToCString(ctx, argv[0]); + if (!path) + return JS_EXCEPTION; +#if defined(_WIN32) + { + struct _utimbuf times; + times.actime = atime / 1000; + times.modtime = mtime / 1000; + ret = js_get_errno(_utime(path, ×)); + } +#else + { + struct timeval times[2]; + ms_to_timeval(×[0], atime); + ms_to_timeval(×[1], mtime); + ret = js_get_errno(utimes(path, times)); + } +#endif + JS_FreeCString(ctx, path); + return JS_NewInt32(ctx, ret); +} + +#if !defined(_WIN32) + +/* return [path, errorcode] */ +static JSValue js_os_realpath(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + const char *path; + char buf[PATH_MAX], *res; + int err; + + path = JS_ToCString(ctx, argv[0]); + if (!path) + return JS_EXCEPTION; + res = realpath(path, buf); + JS_FreeCString(ctx, path); + if (!res) { + buf[0] = '\0'; + err = errno; + } else { + err = 0; + } + return make_string_error(ctx, buf, err); +} + static JSValue js_os_symlink(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { @@ -2255,78 +2563,6 @@ static JSValue js_os_readlink(JSContext *ctx, JSValueConst this_val, return make_string_error(ctx, buf, err); } -/* return [array, errorcode] */ -static JSValue js_os_readdir(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - const char *path; - DIR *f; - struct dirent *d; - JSValue obj; - int err; - uint32_t len; - - path = JS_ToCString(ctx, argv[0]); - if (!path) - return JS_EXCEPTION; - obj = JS_NewArray(ctx); - if (JS_IsException(obj)) { - JS_FreeCString(ctx, path); - return JS_EXCEPTION; - } - f = opendir(path); - if (!f) - err = errno; - else - err = 0; - JS_FreeCString(ctx, path); - if (!f) - goto done; - len = 0; - for(;;) { - errno = 0; - d = readdir(f); - if (!d) { - err = errno; - break; - } - JS_DefinePropertyValueUint32(ctx, obj, len++, - JS_NewString(ctx, d->d_name), - JS_PROP_C_W_E); - } - closedir(f); - done: - return make_obj_error(ctx, obj, err); -} - -static void ms_to_timeval(struct timeval *tv, uint64_t v) -{ - tv->tv_sec = v / 1000; - tv->tv_usec = (v % 1000) * 1000; -} - -static JSValue js_os_utimes(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - const char *path; - int64_t atime, mtime; - int ret; - struct timeval times[2]; - - if (JS_ToInt64(ctx, &atime, argv[1])) - return JS_EXCEPTION; - if (JS_ToInt64(ctx, &mtime, argv[2])) - return JS_EXCEPTION; - path = JS_ToCString(ctx, argv[0]); - if (!path) - return JS_EXCEPTION; - ms_to_timeval(×[0], atime); - ms_to_timeval(×[1], mtime); - ret = js_get_errno(utimes(path, times)); - JS_FreeCString(ctx, path); - return JS_NewInt32(ctx, ret); -} - static char **build_envp(JSContext *ctx, JSValueConst obj) { uint32_t len, i; @@ -2404,7 +2640,7 @@ static int my_execvpe(const char *filename, char **argv, char **envp) path = getenv("PATH"); if (!path) - path = "/bin:/usr/bin"; + path = (char *)"/bin:/usr/bin"; eacces_error = FALSE; p = path; for(p = path; p != NULL; p = p_next) { @@ -2746,6 +2982,441 @@ static JSValue js_os_dup2(JSContext *ctx, JSValueConst this_val, #endif /* !_WIN32 */ +#ifdef USE_WORKER + +/* Worker */ + +typedef struct { + JSWorkerMessagePipe *recv_pipe; + JSWorkerMessagePipe *send_pipe; + JSWorkerMessageHandler *msg_handler; +} JSWorkerData; + +typedef struct { + /* source code of the worker */ + char *eval_buf; + size_t eval_buf_len; + JSWorkerMessagePipe *recv_pipe, *send_pipe; +} WorkerFuncArgs; + +typedef struct { + int ref_count; + uint64_t buf[0]; +} JSSABHeader; + +static JSClassID js_worker_class_id; + +static int atomic_add_int(int *ptr, int v) +{ + return atomic_fetch_add((_Atomic(uint32_t) *)ptr, v) + v; +} + +/* shared array buffer allocator */ +static void *js_sab_alloc(void *opaque, size_t size) +{ + JSSABHeader *sab; + sab = malloc(sizeof(JSSABHeader) + size); + if (!sab) + return NULL; + sab->ref_count = 1; + return sab->buf; +} + +static void js_sab_free(void *opaque, void *ptr) +{ + JSSABHeader *sab; + int ref_count; + sab = (JSSABHeader *)((uint8_t *)ptr - sizeof(JSSABHeader)); + ref_count = atomic_add_int(&sab->ref_count, -1); + assert(ref_count >= 0); + if (ref_count == 0) { + free(sab); + } +} + +static void js_sab_dup(void *opaque, void *ptr) +{ + JSSABHeader *sab; + sab = (JSSABHeader *)((uint8_t *)ptr - sizeof(JSSABHeader)); + atomic_add_int(&sab->ref_count, 1); +} + +static JSWorkerMessagePipe *js_new_message_pipe(void) +{ + JSWorkerMessagePipe *ps; + int pipe_fds[2]; + + if (pipe(pipe_fds) < 0) + return NULL; + + ps = malloc(sizeof(*ps)); + if (!ps) { + close(pipe_fds[0]); + close(pipe_fds[1]); + return NULL; + } + ps->ref_count = 1; + init_list_head(&ps->msg_queue); + pthread_mutex_init(&ps->mutex, NULL); + ps->read_fd = pipe_fds[0]; + ps->write_fd = pipe_fds[1]; + return ps; +} + +static JSWorkerMessagePipe *js_dup_message_pipe(JSWorkerMessagePipe *ps) +{ + atomic_add_int(&ps->ref_count, 1); + return ps; +} + +static void js_free_message(JSWorkerMessage *msg) +{ + size_t i; + /* free the SAB */ + for(i = 0; i < msg->sab_tab_len; i++) { + js_sab_free(NULL, msg->sab_tab[i]); + } + free(msg->sab_tab); + free(msg->data); + free(msg); +} + +static void js_free_message_pipe(JSWorkerMessagePipe *ps) +{ + struct list_head *el, *el1; + JSWorkerMessage *msg; + int ref_count; + + if (!ps) + return; + + ref_count = atomic_add_int(&ps->ref_count, -1); + assert(ref_count >= 0); + if (ref_count == 0) { + list_for_each_safe(el, el1, &ps->msg_queue) { + msg = list_entry(el, JSWorkerMessage, link); + js_free_message(msg); + } + pthread_mutex_destroy(&ps->mutex); + close(ps->read_fd); + close(ps->write_fd); + free(ps); + } +} + +static void js_free_port(JSRuntime *rt, JSWorkerMessageHandler *port) +{ + if (port) { + js_free_message_pipe(port->recv_pipe); + JS_FreeValueRT(rt, port->on_message_func); + list_del(&port->link); + js_free_rt(rt, port); + } +} + +static void js_worker_finalizer(JSRuntime *rt, JSValue val) +{ + JSWorkerData *worker = JS_GetOpaque(val, js_worker_class_id); + if (worker) { + js_free_message_pipe(worker->recv_pipe); + js_free_message_pipe(worker->send_pipe); + js_free_port(rt, worker->msg_handler); + js_free_rt(rt, worker); + } +} + +static JSClassDef js_worker_class = { + "Worker", + .finalizer = js_worker_finalizer, +}; + +static void *worker_func(void *opaque) +{ + WorkerFuncArgs *args = opaque; + JSRuntime *rt; + JSThreadState *ts; + JSContext *ctx; + JSValue retval; + + rt = JS_NewRuntime(); + if (rt == NULL) { + fprintf(stderr, "JS_NewRuntime failure"); + exit(1); + } + js_std_init_handlers(rt); + + /* 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); + if (ctx == NULL) { + fprintf(stderr, "JS_NewContext failure"); + } + + JS_SetCanBlock(rt, TRUE); + + 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)) + js_std_dump_error(ctx); + JS_FreeValue(ctx, retval); + + js_std_loop(ctx); + + JS_FreeContext(ctx); + js_std_free_handlers(rt); + JS_FreeRuntime(rt); + return NULL; +} + +static JSValue js_worker_ctor_internal(JSContext *ctx, JSValueConst new_target, + JSWorkerMessagePipe *recv_pipe, + JSWorkerMessagePipe *send_pipe) +{ + JSValue obj = JS_UNDEFINED, proto; + JSWorkerData *s; + + /* create the object */ + if (JS_IsUndefined(new_target)) { + proto = JS_GetClassProto(ctx, js_worker_class_id); + } else { + proto = JS_GetPropertyStr(ctx, new_target, "prototype"); + if (JS_IsException(proto)) + goto fail; + } + obj = JS_NewObjectProtoClass(ctx, proto, js_worker_class_id); + JS_FreeValue(ctx, proto); + if (JS_IsException(obj)) + goto fail; + s = js_mallocz(ctx, sizeof(*s)); + if (!s) + goto fail; + s->recv_pipe = js_dup_message_pipe(recv_pipe); + s->send_pipe = js_dup_message_pipe(send_pipe); + + JS_SetOpaque(obj, s); + return obj; + fail: + JS_FreeValue(ctx, obj); + return JS_EXCEPTION; +} + +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; + pthread_t tid; + pthread_attr_t attr; + JSValue obj = JS_UNDEFINED; + int ret; + + /* 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"); + + /* script source */ + + str = JS_ToCStringLen(ctx, &str_len, argv[0]); + if (!str) + return JS_EXCEPTION; + + args = malloc(sizeof(*args)); + if (!args) { + JS_ThrowOutOfMemory(ctx); + goto 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; + + /* ports */ + args->recv_pipe = js_new_message_pipe(); + if (!args->recv_pipe) + goto fail; + args->send_pipe = js_new_message_pipe(); + if (!args->send_pipe) + goto fail; + + obj = js_worker_ctor_internal(ctx, new_target, + args->send_pipe, args->recv_pipe); + if (JS_IsUndefined(obj)) + goto fail; + + pthread_attr_init(&attr); + /* no join at the end */ + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); + ret = pthread_create(&tid, &attr, worker_func, args); + pthread_attr_destroy(&attr); + if (ret != 0) { + JS_ThrowTypeError(ctx, "could not create worker"); + goto fail; + } + return obj; + fail: + JS_FreeCString(ctx, str); + if (args) { + free(args->eval_buf); + js_free_message_pipe(args->recv_pipe); + js_free_message_pipe(args->send_pipe); + free(args); + } + JS_FreeValue(ctx, obj); + return JS_EXCEPTION; +} + +static JSValue js_worker_postMessage(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + JSWorkerData *worker = JS_GetOpaque2(ctx, this_val, js_worker_class_id); + JSWorkerMessagePipe *ps; + size_t data_len, sab_tab_len, i; + uint8_t *data; + JSWorkerMessage *msg; + uint8_t **sab_tab; + + if (!worker) + return JS_EXCEPTION; + + data = JS_WriteObject2(ctx, &data_len, argv[0], + JS_WRITE_OBJ_SAB | JS_WRITE_OBJ_REFERENCE, + &sab_tab, &sab_tab_len); + if (!data) + return JS_EXCEPTION; + + msg = malloc(sizeof(*msg)); + if (!msg) + goto fail; + msg->data = NULL; + msg->sab_tab = NULL; + + /* must reallocate because the allocator may be different */ + msg->data = malloc(data_len); + if (!msg->data) + goto fail; + memcpy(msg->data, data, data_len); + msg->data_len = data_len; + + msg->sab_tab = malloc(sizeof(msg->sab_tab[0]) * sab_tab_len); + if (!msg->sab_tab) + goto fail; + memcpy(msg->sab_tab, sab_tab, sizeof(msg->sab_tab[0]) * sab_tab_len); + msg->sab_tab_len = sab_tab_len; + + js_free(ctx, data); + js_free(ctx, sab_tab); + + /* increment the SAB reference counts */ + for(i = 0; i < msg->sab_tab_len; i++) { + js_sab_dup(NULL, msg->sab_tab[i]); + } + + ps = worker->send_pipe; + pthread_mutex_lock(&ps->mutex); + /* indicate that data is present */ + if (list_empty(&ps->msg_queue)) { + uint8_t ch = '\0'; + int ret; + for(;;) { + ret = write(ps->write_fd, &ch, 1); + if (ret == 1) + break; + if (ret < 0 && (errno != EAGAIN || errno != EINTR)) + break; + } + } + list_add_tail(&msg->link, &ps->msg_queue); + pthread_mutex_unlock(&ps->mutex); + return JS_UNDEFINED; + fail: + if (msg) { + free(msg->data); + free(msg->sab_tab); + free(msg); + } + js_free(ctx, data); + js_free(ctx, sab_tab); + return JS_EXCEPTION; + +} + +static JSValue js_worker_set_onmessage(JSContext *ctx, JSValueConst this_val, + JSValueConst func) +{ + JSRuntime *rt = JS_GetRuntime(ctx); + JSThreadState *ts = JS_GetRuntimeOpaque(rt); + JSWorkerData *worker = JS_GetOpaque2(ctx, this_val, js_worker_class_id); + JSWorkerMessageHandler *port; + + if (!worker) + return JS_EXCEPTION; + + port = worker->msg_handler; + if (JS_IsNull(func)) { + if (port) { + js_free_port(rt, port); + worker->msg_handler = NULL; + } + } else { + if (!JS_IsFunction(ctx, func)) + return JS_ThrowTypeError(ctx, "not a function"); + if (!port) { + port = js_mallocz(ctx, sizeof(*port)); + if (!port) + return JS_EXCEPTION; + port->recv_pipe = js_dup_message_pipe(worker->recv_pipe); + port->on_message_func = JS_NULL; + list_add_tail(&port->link, &ts->port_list); + worker->msg_handler = port; + } + JS_FreeValue(ctx, port->on_message_func); + port->on_message_func = JS_DupValue(ctx, func); + } + return JS_UNDEFINED; +} + +static JSValue js_worker_get_onmessage(JSContext *ctx, JSValueConst this_val) +{ + JSWorkerData *worker = JS_GetOpaque2(ctx, this_val, js_worker_class_id); + JSWorkerMessageHandler *port; + if (!worker) + return JS_EXCEPTION; + port = worker->msg_handler; + if (port) { + return JS_DupValue(ctx, port->on_message_func); + } else { + return JS_NULL; + } +} + +static const JSCFunctionListEntry js_worker_proto_funcs[] = { + JS_CFUNC_DEF("postMessage", 1, js_worker_postMessage ), + JS_CGETSET_DEF("onmessage", js_worker_get_onmessage, js_worker_set_onmessage ), +}; + +#endif /* USE_WORKER */ + #if defined(_WIN32) #define OS_PLATFORM "win32" #elif defined(__APPLE__) @@ -2806,12 +3477,9 @@ static const JSCFunctionListEntry js_os_funcs[] = { JS_CFUNC_DEF("clearTimeout", 1, js_os_clearTimeout ), JS_PROP_STRING_DEF("platform", OS_PLATFORM, 0 ), JS_CFUNC_DEF("getcwd", 0, js_os_getcwd ), -#if !defined(_WIN32) JS_CFUNC_DEF("chdir", 0, js_os_chdir ), - JS_CFUNC_DEF("realpath", 1, js_os_realpath ), JS_CFUNC_DEF("mkdir", 1, js_os_mkdir ), - JS_CFUNC_MAGIC_DEF("stat", 1, js_os_stat, 0 ), - JS_CFUNC_MAGIC_DEF("lstat", 1, js_os_stat, 1 ), + JS_CFUNC_DEF("readdir", 1, js_os_readdir ), /* st_mode constants */ OS_FLAG(S_IFMT), OS_FLAG(S_IFIFO), @@ -2819,14 +3487,19 @@ static const JSCFunctionListEntry js_os_funcs[] = { OS_FLAG(S_IFDIR), OS_FLAG(S_IFBLK), OS_FLAG(S_IFREG), +#if !defined(_WIN32) OS_FLAG(S_IFSOCK), OS_FLAG(S_IFLNK), OS_FLAG(S_ISGID), OS_FLAG(S_ISUID), +#endif + JS_CFUNC_MAGIC_DEF("stat", 1, js_os_stat, 0 ), + JS_CFUNC_DEF("utimes", 3, js_os_utimes ), +#if !defined(_WIN32) + JS_CFUNC_MAGIC_DEF("lstat", 1, js_os_stat, 1 ), + JS_CFUNC_DEF("realpath", 1, js_os_realpath ), JS_CFUNC_DEF("symlink", 2, js_os_symlink ), JS_CFUNC_DEF("readlink", 1, js_os_readlink ), - JS_CFUNC_DEF("readdir", 1, js_os_readdir ), - JS_CFUNC_DEF("utimes", 3, js_os_utimes ), JS_CFUNC_DEF("exec", 1, js_os_exec ), JS_CFUNC_DEF("waitpid", 2, js_os_waitpid ), OS_FLAG(WNOHANG), @@ -2845,7 +3518,35 @@ static int js_os_init(JSContext *ctx, JSModuleDef *m) /* OSTimer class */ JS_NewClassID(&js_os_timer_class_id); JS_NewClass(JS_GetRuntime(ctx), js_os_timer_class_id, &js_os_timer_class); - + +#ifdef USE_WORKER + { + JSRuntime *rt = JS_GetRuntime(ctx); + JSThreadState *ts = JS_GetRuntimeOpaque(rt); + JSValue proto, obj; + /* Worker class */ + JS_NewClassID(&js_worker_class_id); + JS_NewClass(JS_GetRuntime(ctx), js_worker_class_id, &js_worker_class); + proto = JS_NewObject(ctx); + JS_SetPropertyFunctionList(ctx, proto, js_worker_proto_funcs, countof(js_worker_proto_funcs)); + + obj = JS_NewCFunction2(ctx, js_worker_ctor, "Worker", 1, + JS_CFUNC_constructor, 0); + JS_SetConstructor(ctx, obj, proto); + + JS_SetClassProto(ctx, js_worker_class_id, proto); + + /* set 'Worker.parent' if necessary */ + if (ts->recv_pipe && ts->send_pipe) { + JS_DefinePropertyValueStr(ctx, obj, "parent", + js_worker_ctor_internal(ctx, JS_UNDEFINED, ts->recv_pipe, ts->send_pipe), + JS_PROP_C_W_E); + } + + JS_SetModuleExport(ctx, m, "Worker", obj); + } +#endif /* USE_WORKER */ + return JS_SetModuleExportList(ctx, m, js_os_funcs, countof(js_os_funcs)); } @@ -2857,6 +3558,9 @@ JSModuleDef *js_init_module_os(JSContext *ctx, const char *module_name) if (!m) return NULL; JS_AddModuleExportList(ctx, m, js_os_funcs, countof(js_os_funcs)); +#ifdef USE_WORKER + JS_AddModuleExport(ctx, m, "Worker"); +#endif return m; } @@ -2896,46 +3600,76 @@ void js_std_add_helpers(JSContext *ctx, int argc, char **argv) JS_SetPropertyStr(ctx, global_obj, "console", console); /* same methods as the mozilla JS shell */ - args = JS_NewArray(ctx); - for(i = 0; i < argc; i++) { - JS_SetPropertyUint32(ctx, args, i, JS_NewString(ctx, argv[i])); + if (argc >= 0) { + args = JS_NewArray(ctx); + for(i = 0; i < argc; i++) { + JS_SetPropertyUint32(ctx, args, i, JS_NewString(ctx, argv[i])); + } + JS_SetPropertyStr(ctx, global_obj, "scriptArgs", args); } - JS_SetPropertyStr(ctx, global_obj, "scriptArgs", args); - + JS_SetPropertyStr(ctx, global_obj, "print", JS_NewCFunction(ctx, js_print, "print", 1)); JS_SetPropertyStr(ctx, global_obj, "__loadScript", JS_NewCFunction(ctx, js_loadScript, "__loadScript", 1)); JS_FreeValue(ctx, global_obj); +} - /* XXX: not multi-context */ - init_list_head(&os_rw_handlers); - init_list_head(&os_signal_handlers); - init_list_head(&os_timers); - os_pending_signals = 0; +void js_std_init_handlers(JSRuntime *rt) +{ + JSThreadState *ts; + + ts = malloc(sizeof(*ts)); + if (!ts) { + fprintf(stderr, "Could not allocate memory for the worker"); + exit(1); + } + memset(ts, 0, sizeof(*ts)); + init_list_head(&ts->os_rw_handlers); + init_list_head(&ts->os_signal_handlers); + init_list_head(&ts->os_timers); + init_list_head(&ts->port_list); + + JS_SetRuntimeOpaque(rt, ts); + +#ifdef USE_WORKER + /* set the SharedArrayBuffer memory handlers */ + { + JSSharedArrayBufferFunctions sf; + memset(&sf, 0, sizeof(sf)); + sf.sab_alloc = js_sab_alloc; + sf.sab_free = js_sab_free; + sf.sab_dup = js_sab_dup; + JS_SetSharedArrayBufferFunctions(rt, &sf); + } +#endif } void js_std_free_handlers(JSRuntime *rt) { + JSThreadState *ts = JS_GetRuntimeOpaque(rt); struct list_head *el, *el1; - list_for_each_safe(el, el1, &os_rw_handlers) { + list_for_each_safe(el, el1, &ts->os_rw_handlers) { JSOSRWHandler *rh = list_entry(el, JSOSRWHandler, link); free_rw_handler(rt, rh); } - list_for_each_safe(el, el1, &os_signal_handlers) { + list_for_each_safe(el, el1, &ts->os_signal_handlers) { JSOSSignalHandler *sh = list_entry(el, JSOSSignalHandler, link); free_sh(rt, sh); } - list_for_each_safe(el, el1, &os_timers) { + list_for_each_safe(el, el1, &ts->os_timers) { JSOSTimer *th = list_entry(el, JSOSTimer, link); unlink_timer(rt, th); if (!th->has_object) free_timer(rt, th); } + + free(ts); + JS_SetRuntimeOpaque(rt, NULL); /* fail safe */ } static void js_dump_obj(JSContext *ctx, FILE *f, JSValueConst val) diff --git a/quickjs-libc.h b/quickjs-libc.h index 93a53da..b105028 100644 --- a/quickjs-libc.h +++ b/quickjs-libc.h @@ -29,10 +29,15 @@ #include "quickjs.h" +#ifdef __cplusplus +extern "C" { +#endif + JSModuleDef *js_init_module_std(JSContext *ctx, const char *module_name); JSModuleDef *js_init_module_os(JSContext *ctx, const char *module_name); void js_std_add_helpers(JSContext *ctx, int argc, char **argv); void js_std_loop(JSContext *ctx); +void js_std_init_handlers(JSRuntime *rt); void js_std_free_handlers(JSRuntime *rt); void js_std_dump_error(JSContext *ctx); uint8_t *js_load_file(JSContext *ctx, size_t *pbuf_len, const char *filename); @@ -46,4 +51,8 @@ void js_std_promise_rejection_tracker(JSContext *ctx, JSValueConst promise, JSValueConst reason, JS_BOOL is_handled, void *opaque); +#ifdef __cplusplus +} /* extern "C" { */ +#endif + #endif /* QUICKJS_LIBC_H */ @@ -71,6 +71,12 @@ #define CONFIG_ATOMICS #endif +#if !defined(EMSCRIPTEN) +/* enable stack limitation */ +#define CONFIG_STACK_CHECK +#endif + + /* dump object free */ //#define DUMP_FREE //#define DUMP_CLOSURE @@ -281,7 +287,9 @@ struct JSRuntime { void *module_loader_opaque; BOOL can_block : 8; /* TRUE if Atomics.wait can block */ - + /* used to allocate, free and clone SharedArrayBuffers */ + JSSharedArrayBufferFunctions sab_funcs; + /* Shape hash table */ int shape_hash_bits; int shape_hash_size; @@ -392,7 +400,7 @@ typedef struct JSBigDecimal { typedef enum { JS_AUTOINIT_ID_PROTOTYPE, JS_AUTOINIT_ID_MODULE_NS, - JS_AUTOINIT_ID_PROP, /* must be last */ + JS_AUTOINIT_ID_PROP, } JSAutoInitIDEnum; /* must be large enough to have a negligible runtime cost and small @@ -625,7 +633,6 @@ typedef struct JSRegExp { typedef struct JSProxyData { JSValue target; JSValue handler; - JSValue proto; uint8_t is_func; uint8_t is_revoked; } JSProxyData; @@ -770,6 +777,7 @@ struct JSModuleDef { JSValue func_obj; /* only used for JS modules */ JSModuleInitFunc *init_func; /* only used for C modules */ BOOL resolved : 8; + BOOL func_created : 8; BOOL instantiated : 8; BOOL evaluated : 8; BOOL eval_mark : 8; /* temporary use during js_evaluate_module() */ @@ -799,10 +807,8 @@ typedef struct JSProperty { struct { /* JS_PROP_AUTOINIT */ /* in order to use only 2 pointers, we compress the realm and the init function pointer */ - union { - JSContext *realm; /* for JS_AUTOINIT_ID_PROP */ - uintptr_t init_id; /* JS_AUTOINIT_ID_x */ - } u; + uintptr_t realm_and_id; /* realm and init_id (JS_AUTOINIT_ID_x) + in the 2 low bits */ void *opaque; } init; } u; @@ -832,11 +838,13 @@ struct JSShape { uint32_t hash; /* current hash value */ uint32_t prop_hash_mask; int prop_size; /* allocated properties */ - int prop_count; + int prop_count; /* include deleted properties */ + int deleted_prop_count; JSShape *shape_hash_next; /* in JSRuntime.shape_hash[h] list */ JSObject *proto; JSShapeProperty prop[0]; /* prop_size elements */ }; + struct JSObject { union { JSGCObjectHeader header; @@ -850,7 +858,6 @@ struct JSObject { uint8_t fast_array : 1; /* TRUE if u.array is used for get/put */ 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 is_class : 1; /* TRUE if object is a class constructor */ uint8_t tmp_mark : 1; /* used in JS_WriteObjectRec() */ uint16_t class_id; /* see JS_CLASS_x */ }; @@ -1124,7 +1131,7 @@ static bfdec_t *JS_ToBigDecimal(JSContext *ctx, JSValueConst val); #endif JSValue JS_ThrowOutOfMemory(JSContext *ctx); static JSValue JS_ThrowTypeErrorRevokedProxy(JSContext *ctx); -static JSValueConst js_proxy_getPrototypeOf(JSContext *ctx, JSValueConst obj); +static JSValue js_proxy_getPrototypeOf(JSContext *ctx, JSValueConst obj); static int js_proxy_setPrototypeOf(JSContext *ctx, JSValueConst obj, JSValueConst proto_val, BOOL throw_flag); static int js_proxy_isExtensible(JSContext *ctx, JSValueConst obj); @@ -1136,6 +1143,17 @@ static int JS_CreateProperty(JSContext *ctx, JSObject *p, int flags); static int js_string_memcmp(const JSString *p1, const JSString *p2, int len); static void reset_weak_ref(JSRuntime *rt, JSObject *p); +static JSValue js_array_buffer_constructor3(JSContext *ctx, + JSValueConst new_target, + uint64_t len, JSClassID class_id, + uint8_t *buf, + JSFreeArrayBufferDataFunc *free_func, + void *opaque, BOOL alloc_flag); +static JSArrayBuffer *js_get_array_buffer(JSContext *ctx, JSValueConst obj); +static JSValue js_typed_array_constructor(JSContext *ctx, + JSValueConst this_val, + int argc, JSValueConst *argv, + int classid); static BOOL typed_array_is_detached(JSContext *ctx, JSObject *p); static uint32_t typed_array_get_length(JSContext *ctx, JSObject *p); static JSValue JS_ThrowTypeErrorDetachedArrayBuffer(JSContext *ctx); @@ -1212,6 +1230,7 @@ static int js_module_ns_autoinit(JSContext *ctx, JSObject *p, JSAtom atom, void *opaque); static int JS_InstantiateFunctionListItem(JSContext *ctx, JSObject *p, JSAtom atom, void *opaque); +void JS_SetUncatchableError(JSContext *ctx, JSValueConst val, BOOL flag); static const JSClassExoticMethods js_arguments_exotic_methods; static const JSClassExoticMethods js_string_exotic_methods; @@ -1361,6 +1380,33 @@ char *js_strdup(JSContext *ctx, const char *str) return js_strndup(ctx, str, strlen(str)); } +static no_inline int js_realloc_array(JSContext *ctx, void **parray, + int elem_size, int *psize, int req_size) +{ + int new_size; + size_t slack; + void *new_array; + /* XXX: potential arithmetic overflow */ + new_size = max_int(req_size, *psize * 3 / 2); + new_array = js_realloc2(ctx, *parray, new_size * elem_size, &slack); + if (!new_array) + return -1; + new_size += slack / elem_size; + *psize = new_size; + *parray = new_array; + return 0; +} + +/* resize the array and update its size if req_size > *psize */ +static inline int js_resize_array(JSContext *ctx, void **parray, int elem_size, + int *psize, int req_size) +{ + if (unlikely(req_size > *psize)) + return js_realloc_array(ctx, parray, elem_size, psize, req_size); + else + return 0; +} + static inline void js_dbuf_init(JSContext *ctx, DynBuf *s) { dbuf_init2(s, ctx->rt, (DynBufReallocFunc *)js_realloc_rt); @@ -1379,7 +1425,7 @@ typedef struct JSClassShortDef { static JSClassShortDef const js_std_class_def[] = { { JS_ATOM_Object, NULL, NULL }, /* JS_CLASS_OBJECT */ { JS_ATOM_Array, js_array_finalizer, js_array_mark }, /* JS_CLASS_ARRAY */ - { JS_ATOM_Error, js_object_data_finalizer, js_object_data_mark }, /* JS_CLASS_ERROR */ + { JS_ATOM_Error, NULL, NULL }, /* JS_CLASS_ERROR */ { JS_ATOM_Number, js_object_data_finalizer, js_object_data_mark }, /* JS_CLASS_NUMBER */ { JS_ATOM_String, js_object_data_finalizer, js_object_data_mark }, /* JS_CLASS_STRING */ { JS_ATOM_Boolean, js_object_data_finalizer, js_object_data_mark }, /* JS_CLASS_BOOLEAN */ @@ -1426,7 +1472,7 @@ static JSClassShortDef const js_std_class_def[] = { { JS_ATOM_Set_Iterator, js_map_iterator_finalizer, js_map_iterator_mark }, /* JS_CLASS_SET_ITERATOR */ { JS_ATOM_Array_Iterator, js_array_iterator_finalizer, js_array_iterator_mark }, /* JS_CLASS_ARRAY_ITERATOR */ { JS_ATOM_String_Iterator, js_array_iterator_finalizer, js_array_iterator_mark }, /* JS_CLASS_STRING_ITERATOR */ - { JS_ATOM_RegExp_String_Iterator, js_regexp_string_iterator_finalizer, js_regexp_string_iterator_mark }, /* JS_CLASS_STRING_ITERATOR */ + { JS_ATOM_RegExp_String_Iterator, js_regexp_string_iterator_finalizer, js_regexp_string_iterator_mark }, /* JS_CLASS_REGEXP_STRING_ITERATOR */ { JS_ATOM_Generator, js_generator_finalizer, js_generator_mark }, /* JS_CLASS_GENERATOR */ }; @@ -1505,8 +1551,8 @@ static void set_dummy_numeric_ops(JSNumericOperations *ops) #endif /* CONFIG_BIGNUM */ -#if defined(EMSCRIPTEN) -/* currently no stack limitation */ +#if !defined(CONFIG_STACK_CHECK) +/* no stack limitation */ static inline uint8_t *js_get_stack_pointer(void) { return NULL; @@ -1729,6 +1775,12 @@ void JS_SetCanBlock(JSRuntime *rt, BOOL can_block) rt->can_block = can_block; } +void JS_SetSharedArrayBufferFunctions(JSRuntime *rt, + const JSSharedArrayBufferFunctions *sf) +{ + rt->sab_funcs = *sf; +} + /* return 0 if OK, < 0 if exception */ int JS_EnqueueJob(JSContext *ctx, JSJobFunc *job_func, int argc, JSValueConst *argv) @@ -4262,9 +4314,10 @@ static no_inline JSShape *js_new_shape2(JSContext *ctx, JSObject *proto, memset(sh->prop_hash_end - hash_size, 0, sizeof(sh->prop_hash_end[0]) * hash_size); sh->prop_hash_mask = hash_size - 1; - sh->prop_count = 0; sh->prop_size = prop_size; - + sh->prop_count = 0; + sh->deleted_prop_count = 0; + /* insert in the hash table */ sh->hash = shape_initial_hash(proto); sh->is_hashed = TRUE; @@ -4415,6 +4468,74 @@ static no_inline int resize_properties(JSContext *ctx, JSShape **psh, return 0; } +/* remove the deleted properties. */ +static int compact_properties(JSContext *ctx, JSObject *p) +{ + JSShape *sh, *old_sh; + void *sh_alloc; + intptr_t h; + uint32_t new_hash_size, i, j, new_hash_mask, new_size; + JSShapeProperty *old_pr, *pr; + JSProperty *prop, *new_prop; + + sh = p->shape; + assert(!sh->is_hashed); + + new_size = max_int(JS_PROP_INITIAL_SIZE, + sh->prop_count - sh->deleted_prop_count); + assert(new_size <= sh->prop_size); + + new_hash_size = sh->prop_hash_mask + 1; + while ((new_hash_size / 2) >= new_size) + new_hash_size = new_hash_size / 2; + new_hash_mask = new_hash_size - 1; + + /* resize the hash table and the properties */ + old_sh = sh; + sh_alloc = js_malloc(ctx, get_shape_size(new_hash_size, new_size)); + if (!sh_alloc) + return -1; + sh = get_shape_from_alloc(sh_alloc, new_hash_size); + list_del(&old_sh->header.link); + 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); + + j = 0; + old_pr = old_sh->prop; + pr = sh->prop; + prop = p->prop; + for(i = 0; i < sh->prop_count; i++) { + if (old_pr->atom != JS_ATOM_NULL) { + 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; + prop[j] = prop[i]; + j++; + pr++; + } + old_pr++; + } + assert(j == (sh->prop_count - sh->deleted_prop_count)); + sh->prop_hash_mask = new_hash_mask; + sh->prop_size = new_size; + sh->deleted_prop_count = 0; + sh->prop_count = j; + + p->shape = sh; + js_free(ctx, get_alloc_from_shape(old_sh)); + + /* reduce the size of the object properties */ + new_prop = js_realloc(ctx, p->prop, sizeof(new_prop[0]) * new_size); + if (new_prop) + p->prop = new_prop; + return 0; +} + static int add_shape_property(JSContext *ctx, JSShape **psh, JSObject *p, JSAtom atom, int prop_flags) { @@ -4571,7 +4692,6 @@ static JSValue JS_NewObjectFromShape(JSContext *ctx, JSShape *sh, JSClassID clas p->fast_array = 0; p->is_constructor = 0; p->is_uncatchable_error = 0; - p->is_class = 0; p->tmp_mark = 0; p->first_weak_ref = NULL; p->u.opaque = NULL; @@ -4621,7 +4741,6 @@ static JSValue JS_NewObjectFromShape(JSContext *ctx, JSShape *sh, JSClassID clas p->u.array.u.ptr = NULL; p->u.array.count = 0; break; - case JS_CLASS_ERROR: case JS_CLASS_NUMBER: case JS_CLASS_STRING: case JS_CLASS_BOOLEAN: @@ -4967,19 +5086,25 @@ JSValue JS_NewCFunctionData(JSContext *ctx, JSCFunctionData *func, return func_obj; } +static JSContext *js_autoinit_get_realm(JSProperty *pr) +{ + return (JSContext *)(pr->u.init.realm_and_id & ~3); +} + +static JSAutoInitIDEnum js_autoinit_get_id(JSProperty *pr) +{ + return pr->u.init.realm_and_id & 3; +} + static void js_autoinit_free(JSRuntime *rt, JSProperty *pr) { - if (pr->u.init.u.init_id >= JS_AUTOINIT_ID_PROP) { - JS_FreeContext(pr->u.init.u.realm); - } + JS_FreeContext(js_autoinit_get_realm(pr)); } static void js_autoinit_mark(JSRuntime *rt, JSProperty *pr, JS_MarkFunc *mark_func) { - if (pr->u.init.u.init_id >= JS_AUTOINIT_ID_PROP) { - mark_func(rt, &pr->u.init.u.realm->header); - } + mark_func(rt, &js_autoinit_get_realm(pr)->header); } static void free_property(JSRuntime *rt, JSProperty *pr, int prop_flags) @@ -6331,7 +6456,7 @@ static void build_backtrace(JSContext *ctx, JSValueConst error_obj, backtrace_barrier = FALSE; if (js_class_has_bytecode(p->class_id)) { JSFunctionBytecode *b; - char atom_buf[ATOM_GET_STR_BUF_SIZE]; + const char *atom_str; int line_num1; b = p->u.func.function_bytecode; @@ -6339,9 +6464,10 @@ static void build_backtrace(JSContext *ctx, JSValueConst error_obj, if (b->has_debug) { line_num1 = find_line_num(ctx, b, sf->cur_pc - b->byte_code_buf - 1); + atom_str = JS_AtomToCString(ctx, b->debug.filename); dbuf_printf(&dbuf, " (%s", - JS_AtomGetStr(ctx, atom_buf, sizeof(atom_buf), - b->debug.filename)); + atom_str ? atom_str : "<null>"); + JS_FreeCString(ctx, atom_str); if (line_num1 != -1) dbuf_printf(&dbuf, ":%d", line_num1); dbuf_putc(&dbuf, ')'); @@ -6459,13 +6585,32 @@ static int __attribute__((format(printf, 3, 4))) JS_ThrowTypeErrorOrFalse(JSCont } } +/* never use it directly */ +static JSValue __attribute__((format(printf, 3, 4))) __JS_ThrowTypeErrorAtom(JSContext *ctx, JSAtom atom, const char *fmt, ...) +{ + char buf[ATOM_GET_STR_BUF_SIZE]; + return JS_ThrowTypeError(ctx, fmt, + JS_AtomGetStr(ctx, buf, sizeof(buf), atom)); +} + +/* never use it directly */ +static JSValue __attribute__((format(printf, 3, 4))) __JS_ThrowSyntaxErrorAtom(JSContext *ctx, JSAtom atom, const char *fmt, ...) +{ + char buf[ATOM_GET_STR_BUF_SIZE]; + return JS_ThrowSyntaxError(ctx, fmt, + JS_AtomGetStr(ctx, buf, sizeof(buf), atom)); +} + +/* %s is replaced by 'atom'. The macro is used so that gcc can check + the format string. */ +#define JS_ThrowTypeErrorAtom(ctx, fmt, atom) __JS_ThrowTypeErrorAtom(ctx, atom, fmt, "") +#define JS_ThrowSyntaxErrorAtom(ctx, fmt, atom) __JS_ThrowSyntaxErrorAtom(ctx, atom, fmt, "") + static int JS_ThrowTypeErrorReadOnly(JSContext *ctx, int flags, JSAtom atom) { if ((flags & JS_PROP_THROW) || ((flags & JS_PROP_THROW_STRICT) && is_strict_mode(ctx))) { - char buf[ATOM_GET_STR_BUF_SIZE]; - JS_ThrowTypeError(ctx, "%s is read-only", - JS_AtomGetStr(ctx, buf, sizeof(buf), atom)); + JS_ThrowTypeErrorAtom(ctx, "'%s' is read-only", atom); return -1; } else { return FALSE; @@ -6534,7 +6679,7 @@ static JSValue JS_ThrowTypeErrorNotASymbol(JSContext *ctx) static JSValue JS_ThrowReferenceErrorNotDefined(JSContext *ctx, JSAtom name) { char buf[ATOM_GET_STR_BUF_SIZE]; - return JS_ThrowReferenceError(ctx, "%s is not defined", + return JS_ThrowReferenceError(ctx, "'%s' is not defined", JS_AtomGetStr(ctx, buf, sizeof(buf), name)); } @@ -6549,11 +6694,33 @@ static JSValue JS_ThrowReferenceErrorUninitialized(JSContext *ctx, JSAtom name) static JSValue JS_ThrowTypeErrorInvalidClass(JSContext *ctx, int class_id) { JSRuntime *rt = ctx->rt; - char buf[ATOM_GET_STR_BUF_SIZE]; JSAtom name; name = rt->class_array[class_id].class_name; - return JS_ThrowTypeError(ctx, "%s object expected", - JS_AtomGetStr(ctx, buf, sizeof(buf), name)); + return JS_ThrowTypeErrorAtom(ctx, "%s object expected", name); +} + +static no_inline __exception int __js_poll_interrupts(JSContext *ctx) +{ + JSRuntime *rt = ctx->rt; + ctx->interrupt_counter = JS_INTERRUPT_COUNTER_INIT; + if (rt->interrupt_handler) { + if (rt->interrupt_handler(rt, rt->interrupt_opaque)) { + /* XXX: should set a specific flag to avoid catching */ + JS_ThrowInternalError(ctx, "interrupted"); + JS_SetUncatchableError(ctx, ctx->rt->current_exception, TRUE); + return -1; + } + } + return 0; +} + +static inline __exception int js_poll_interrupts(JSContext *ctx) +{ + if (unlikely(--ctx->interrupt_counter <= 0)) { + return __js_poll_interrupts(ctx); + } else { + return 0; + } } /* return -1 (exception) or TRUE/FALSE */ @@ -6633,11 +6800,9 @@ int JS_SetPrototype(JSContext *ctx, JSValueConst obj, JSValueConst proto_val) return JS_SetPrototypeInternal(ctx, obj, proto_val, TRUE); } -/* Return an Object, JS_NULL or JS_EXCEPTION in case of Proxy object. */ -JSValueConst JS_GetPrototype(JSContext *ctx, JSValueConst val) +/* Only works for primitive types, otherwise return JS_NULL. */ +static JSValueConst JS_GetPrototypePrimitive(JSContext *ctx, JSValueConst val) { - JSObject *p; - switch(JS_VALUE_GET_NORM_TAG(val)) { #ifdef CONFIG_BIGNUM case JS_TAG_BIG_INT: @@ -6664,26 +6829,45 @@ JSValueConst JS_GetPrototype(JSContext *ctx, JSValueConst val) val = ctx->class_proto[JS_CLASS_SYMBOL]; break; case JS_TAG_OBJECT: - p = JS_VALUE_GET_OBJ(val); + case JS_TAG_NULL: + case JS_TAG_UNDEFINED: + default: + val = JS_NULL; + break; + } + return val; +} + +/* Return an Object, JS_NULL or JS_EXCEPTION in case of Proxy object. */ +JSValue JS_GetPrototype(JSContext *ctx, JSValueConst obj) +{ + JSValue val; + if (JS_VALUE_GET_TAG(obj) == JS_TAG_OBJECT) { + JSObject *p; + p = JS_VALUE_GET_OBJ(obj); if (unlikely(p->class_id == JS_CLASS_PROXY)) { - val = js_proxy_getPrototypeOf(ctx, val); + val = js_proxy_getPrototypeOf(ctx, obj); } else { p = p->shape->proto; if (!p) val = JS_NULL; else - val = JS_MKPTR(JS_TAG_OBJECT, p); + val = JS_DupValue(ctx, JS_MKPTR(JS_TAG_OBJECT, p)); } - break; - case JS_TAG_NULL: - case JS_TAG_UNDEFINED: - default: - val = JS_NULL; - break; + } else { + val = JS_DupValue(ctx, JS_GetPrototypePrimitive(ctx, obj)); } return val; } +static JSValue JS_GetPrototypeFree(JSContext *ctx, JSValue obj) +{ + JSValue obj1; + obj1 = JS_GetPrototype(ctx, obj); + JS_FreeValue(ctx, obj); + return obj1; +} + /* return TRUE, FALSE or (-1) in case of exception */ static int JS_OrdinaryIsInstanceOf(JSContext *ctx, JSValueConst val, JSValueConst obj) @@ -6704,7 +6888,6 @@ static int JS_OrdinaryIsInstanceOf(JSContext *ctx, JSValueConst val, /* Only explicitly boxed values are instances of constructors */ if (JS_VALUE_GET_TAG(val) != JS_TAG_OBJECT) return FALSE; - ret = FALSE; obj_proto = JS_GetProperty(ctx, obj, JS_ATOM_prototype); if (JS_VALUE_GET_TAG(obj_proto) != JS_TAG_OBJECT) { if (!JS_IsException(obj_proto)) @@ -6717,19 +6900,36 @@ static int JS_OrdinaryIsInstanceOf(JSContext *ctx, JSValueConst val, for(;;) { proto1 = p->shape->proto; if (!proto1) { - if (p->class_id == JS_CLASS_PROXY) { - JSValueConst proto_val; - proto_val = JS_GetPrototype(ctx, JS_MKPTR(JS_TAG_OBJECT, (JSObject *)p)); - if (JS_IsException(proto_val)) { - ret = -1; - goto done; + /* slow case if proxy in the prototype chain */ + if (unlikely(p->class_id == JS_CLASS_PROXY)) { + JSValue obj1; + obj1 = JS_DupValue(ctx, JS_MKPTR(JS_TAG_OBJECT, (JSObject *)p)); + for(;;) { + obj1 = JS_GetPrototypeFree(ctx, obj1); + if (JS_IsException(obj1)) { + ret = -1; + break; + } + if (JS_IsNull(obj1)) { + ret = FALSE; + break; + } + if (proto == JS_VALUE_GET_OBJ(obj1)) { + JS_FreeValue(ctx, obj1); + ret = TRUE; + break; + } + /* must check for timeout to avoid infinite loop */ + if (js_poll_interrupts(ctx)) { + JS_FreeValue(ctx, obj1); + ret = -1; + break; + } } - proto1 = JS_VALUE_GET_OBJ(proto_val); - if (!proto1) - break; } else { - break; + ret = FALSE; } + break; } p = proto1; if (proto == p) { @@ -6767,28 +6967,24 @@ int JS_IsInstanceOf(JSContext *ctx, JSValueConst val, JSValueConst obj) return JS_OrdinaryIsInstanceOf(ctx, val, obj); } -static int JS_AutoInitProperty(JSContext *ctx, JSObject *p, JSAtom prop, JSProperty *pr) +typedef int JSAutoInitFunc(JSContext *ctx, JSObject *p, JSAtom atom, void *opaque); + +static JSAutoInitFunc *js_autoinit_func_table[] = { + js_instantiate_prototype, /* JS_AUTOINIT_ID_PROTOTYPE */ + js_module_ns_autoinit, /* JS_AUTOINIT_ID_MODULE_NS */ + JS_InstantiateFunctionListItem, /* JS_AUTOINIT_ID_PROP */ +}; + +static int JS_AutoInitProperty(JSContext *ctx, JSObject *p, JSAtom prop, + JSProperty *pr) { int ret; JSContext *realm; - - if (pr->u.init.u.init_id >= JS_AUTOINIT_ID_PROP) { - realm = pr->u.init.u.realm; - ret = JS_InstantiateFunctionListItem(realm, p, prop, pr->u.init.opaque); - if (ret) - return ret; - } else { - switch(pr->u.init.u.init_id) { - case JS_AUTOINIT_ID_PROTOTYPE: - ret = js_instantiate_prototype(ctx, p, prop, pr->u.init.opaque); - break; - case JS_AUTOINIT_ID_MODULE_NS: - ret = js_module_ns_autoinit(ctx, p, prop, pr->u.init.opaque); - break; - default: - abort(); - } - } + JSAutoInitFunc *func; + + realm = js_autoinit_get_realm(pr); + func = js_autoinit_func_table[js_autoinit_get_id(pr)]; + ret = func(realm, p, prop, pr->u.init.opaque); return ret; } @@ -6805,8 +7001,9 @@ JSValue JS_GetPropertyInternal(JSContext *ctx, JSValueConst obj, if (unlikely(tag != JS_TAG_OBJECT)) { switch(tag) { case JS_TAG_NULL: + return JS_ThrowTypeErrorAtom(ctx, "cannot read property '%s' of null", prop); case JS_TAG_UNDEFINED: - return JS_ThrowTypeError(ctx, "value has no property"); + return JS_ThrowTypeErrorAtom(ctx, "cannot read property '%s' of undefined", prop); case JS_TAG_EXCEPTION: return JS_EXCEPTION; case JS_TAG_STRING: @@ -6831,7 +7028,7 @@ JSValue JS_GetPropertyInternal(JSContext *ctx, JSValueConst obj, break; } /* cannot raise an exception */ - p = JS_VALUE_GET_OBJ(JS_GetPrototype(ctx, obj)); + p = JS_VALUE_GET_OBJ(JS_GetPrototypePrimitive(ctx, obj)); if (!p) return JS_UNDEFINED; } else { @@ -6896,15 +7093,25 @@ JSValue JS_GetPropertyInternal(JSContext *ctx, JSValueConst obj, const JSClassExoticMethods *em = ctx->rt->class_array[p->class_id].exotic; if (em) { if (em->get_property) { + JSValue obj1, retval; /* XXX: should pass throw_ref_error */ - return em->get_property(ctx, JS_MKPTR(JS_TAG_OBJECT, p), - prop, this_obj); + /* Note: if 'p' is a prototype, it can be + freed in the called function */ + obj1 = JS_DupValue(ctx, JS_MKPTR(JS_TAG_OBJECT, p)); + retval = em->get_property(ctx, obj1, prop, this_obj); + JS_FreeValue(ctx, obj1); + return retval; } if (em->get_own_property) { JSPropertyDescriptor desc; int ret; + JSValue obj1; - ret = em->get_own_property(ctx, &desc, JS_MKPTR(JS_TAG_OBJECT, p), prop); + /* Note: if 'p' is a prototype, it can be + freed in the called function */ + obj1 = JS_DupValue(ctx, JS_MKPTR(JS_TAG_OBJECT, p)); + ret = em->get_own_property(ctx, &desc, obj1, prop); + JS_FreeValue(ctx, obj1); if (ret < 0) return JS_EXCEPTION; if (ret) { @@ -6932,9 +7139,8 @@ JSValue JS_GetPropertyInternal(JSContext *ctx, JSValueConst obj, static JSValue JS_ThrowTypeErrorPrivateNotFound(JSContext *ctx, JSAtom atom) { - char buf[ATOM_GET_STR_BUF_SIZE]; - return JS_ThrowTypeError(ctx, "private class field %s does not exist", - JS_AtomGetStr(ctx, buf, sizeof(buf), atom)); + return JS_ThrowTypeErrorAtom(ctx, "private class field '%s' does not exist", + atom); } /* Private fields can be added even on non extensible objects or @@ -6960,9 +7166,8 @@ static int JS_DefinePrivateField(JSContext *ctx, JSValueConst obj, p = JS_VALUE_GET_OBJ(obj); prs = find_own_property(&pr, p, prop); if (prs) { - char buf[ATOM_GET_STR_BUF_SIZE]; - JS_ThrowTypeError(ctx, "private class field %s already exists", - JS_AtomGetStr(ctx, buf, sizeof(buf), prop)); + JS_ThrowTypeErrorAtom(ctx, "private class field '%s' already exists", + prop); goto fail; } pr = add_property(ctx, p, prop, JS_PROP_C_W_E); @@ -7211,7 +7416,9 @@ static int __exception JS_GetOwnPropertyNamesInternal(JSContext *ctx, JS_ThrowTypeErrorDetachedArrayBuffer(ctx); return -1; } - num_keys_count += p->u.array.count; + if (flags & JS_GPN_STRING_MASK) { + num_keys_count += p->u.array.count; + } } else { const JSClassExoticMethods *em = ctx->rt->class_array[p->class_id].exotic; if (em && em->get_own_property_names) { @@ -7293,15 +7500,17 @@ static int __exception JS_GetOwnPropertyNamesInternal(JSContext *ctx, if (p->is_exotic) { if (p->fast_array) { - for(i = 0; i < p->u.array.count; i++) { - tab_atom[num_index].atom = __JS_AtomFromUInt32(i); - if (tab_atom[num_index].atom == JS_ATOM_NULL) { - js_free_prop_enum(ctx, tab_exotic, exotic_count); - js_free_prop_enum(ctx, tab_atom, num_index); - return -1; + if (flags & JS_GPN_STRING_MASK) { + for(i = 0; i < p->u.array.count; i++) { + tab_atom[num_index].atom = __JS_AtomFromUInt32(i); + if (tab_atom[num_index].atom == JS_ATOM_NULL) { + js_free_prop_enum(ctx, tab_exotic, exotic_count); + js_free_prop_enum(ctx, tab_atom, num_index); + return -1; + } + tab_atom[num_index].is_enumerable = TRUE; + num_index++; } - tab_atom[num_index].is_enumerable = TRUE; - num_index++; } } if (exotic_count > 0) { @@ -7492,6 +7701,7 @@ int JS_HasProperty(JSContext *ctx, JSValueConst obj, JSAtom prop) { JSObject *p; int ret; + JSValue obj1; if (unlikely(JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT)) return FALSE; @@ -7499,10 +7709,18 @@ int JS_HasProperty(JSContext *ctx, JSValueConst obj, JSAtom prop) for(;;) { if (p->is_exotic) { const JSClassExoticMethods *em = ctx->rt->class_array[p->class_id].exotic; - if (em && em->has_property) - return em->has_property(ctx, JS_MKPTR(JS_TAG_OBJECT, p), prop); + if (em && em->has_property) { + /* has_property can free the prototype */ + obj1 = JS_DupValue(ctx, JS_MKPTR(JS_TAG_OBJECT, p)); + ret = em->has_property(ctx, obj1, prop); + JS_FreeValue(ctx, obj1); + return ret; + } } + /* JS_GetOwnPropertyInternal can free the prototype */ + JS_DupValue(ctx, JS_MKPTR(JS_TAG_OBJECT, p)); ret = JS_GetOwnPropertyInternal(ctx, NULL, p, prop); + JS_FreeValue(ctx, JS_MKPTR(JS_TAG_OBJECT, p)); if (ret != 0) return ret; if (p->class_id >= JS_CLASS_UINT8C_ARRAY && @@ -7799,6 +8017,7 @@ static int delete_property(JSContext *ctx, JSObject *p, JSAtom atom) } else { sh->prop_hash_end[-h1 - 1] = pr->hash_next; } + sh->deleted_prop_count++; /* free the entry */ pr1 = &p->prop[h - 1]; free_property(ctx->rt, pr1, pr->flags); @@ -7807,6 +8026,12 @@ static int delete_property(JSContext *ctx, JSObject *p, JSAtom atom) pr->flags = 0; pr->atom = JS_ATOM_NULL; pr1->u.value = JS_UNDEFINED; + + /* compact the properties if too many deleted properties */ + if (sh->deleted_prop_count >= 8 && + sh->deleted_prop_count >= ((unsigned)sh->prop_count / 2)) { + compact_properties(ctx, p); + } return TRUE; } lpr = pr; @@ -8016,8 +8241,12 @@ static int JS_SetPropertyGeneric(JSContext *ctx, if (p->is_exotic) { const JSClassExoticMethods *em = ctx->rt->class_array[p->class_id].exotic; if (em && em->set_property) { - ret = em->set_property(ctx, JS_MKPTR(JS_TAG_OBJECT, p), prop, + JSValue obj1; + /* set_property can free the prototype */ + obj1 = JS_DupValue(ctx, JS_MKPTR(JS_TAG_OBJECT, p)); + ret = em->set_property(ctx, obj1, prop, val, this_obj, flags); + JS_FreeValue(ctx, obj1); JS_FreeValue(ctx, val); return ret; } @@ -8110,14 +8339,17 @@ int JS_SetPropertyInternal(JSContext *ctx, JSValueConst this_obj, if (unlikely(tag != JS_TAG_OBJECT)) { switch(tag) { case JS_TAG_NULL: + JS_FreeValue(ctx, val); + JS_ThrowTypeErrorAtom(ctx, "cannot set property '%s' of null", prop); + return -1; case JS_TAG_UNDEFINED: JS_FreeValue(ctx, val); - JS_ThrowTypeError(ctx, "value has no property"); + JS_ThrowTypeErrorAtom(ctx, "cannot set property '%s' of undefined", prop); return -1; default: /* even on a primitive type we can have setters on the prototype */ p = NULL; - p1 = JS_VALUE_GET_OBJ(JS_GetPrototype(ctx, this_obj)); + p1 = JS_VALUE_GET_OBJ(JS_GetPrototypePrimitive(ctx, this_obj)); goto prototype_lookup; } } @@ -8195,15 +8427,22 @@ retry: } else { const JSClassExoticMethods *em = ctx->rt->class_array[p1->class_id].exotic; if (em) { + JSValue obj1; if (em->set_property) { - ret = em->set_property(ctx, JS_MKPTR(JS_TAG_OBJECT, p1), prop, + /* set_property can free the prototype */ + obj1 = JS_DupValue(ctx, JS_MKPTR(JS_TAG_OBJECT, p1)); + ret = em->set_property(ctx, obj1, prop, val, this_obj, flags); + JS_FreeValue(ctx, obj1); JS_FreeValue(ctx, val); return ret; } if (em->get_own_property) { + /* get_own_property can free the prototype */ + obj1 = JS_DupValue(ctx, JS_MKPTR(JS_TAG_OBJECT, p1)); ret = em->get_own_property(ctx, &desc, - JS_MKPTR(JS_TAG_OBJECT, p1), prop); + obj1, prop); + JS_FreeValue(ctx, obj1); if (ret < 0) { JS_FreeValue(ctx, val); return ret; @@ -8973,11 +9212,10 @@ static int JS_DefineAutoInitProperty(JSContext *ctx, JSValueConst this_obj, pr = add_property(ctx, p, prop, (flags & JS_PROP_C_W_E) | JS_PROP_AUTOINIT); if (unlikely(!pr)) return -1; - if (id == JS_AUTOINIT_ID_PROP) { - pr->u.init.u.realm = JS_DupContext(ctx); - } else { - pr->u.init.u.init_id = id; - } + pr->u.init.realm_and_id = (uintptr_t)JS_DupContext(ctx); + assert((pr->u.init.realm_and_id & 3) == 0); + assert(id <= 3); + pr->u.init.realm_and_id |= id; pr->u.init.opaque = opaque; return TRUE; } @@ -9114,9 +9352,7 @@ static int JS_DefineObjectNameComputed(JSContext *ctx, JSValueConst obj, static JSValue JS_ThrowSyntaxErrorVarRedeclaration(JSContext *ctx, JSAtom prop) { - char buf[ATOM_GET_STR_BUF_SIZE]; - return JS_ThrowSyntaxError(ctx, "redeclaration of %s", - JS_AtomGetStr(ctx, buf, sizeof(buf), prop)); + return JS_ThrowSyntaxErrorAtom(ctx, "redeclaration of '%s'", prop); } /* flags is 0, DEFINE_GLOBAL_LEX_VAR or DEFINE_GLOBAL_FUNC_VAR */ @@ -9125,7 +9361,6 @@ static int JS_CheckDefineGlobalVar(JSContext *ctx, JSAtom prop, int flags) { JSObject *p; JSShapeProperty *prs; - char buf[ATOM_GET_STR_BUF_SIZE]; p = JS_VALUE_GET_OBJ(ctx->global_obj); prs = find_own_property1(p, prop); @@ -9143,8 +9378,8 @@ static int JS_CheckDefineGlobalVar(JSContext *ctx, JSAtom prop, int flags) ((prs->flags & (JS_PROP_WRITABLE | JS_PROP_ENUMERABLE)) != (JS_PROP_WRITABLE | JS_PROP_ENUMERABLE)))) { define_error: - JS_ThrowTypeError(ctx, "cannot define variable %s", - JS_AtomGetStr(ctx, buf, sizeof(buf), prop)); + JS_ThrowTypeErrorAtom(ctx, "cannot define variable '%s'", + prop); return -1; } } @@ -11395,7 +11630,9 @@ static __maybe_unused void JS_DumpObject(JSRuntime *rt, JSObject *p) } else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_VARREF) { printf("[varref %p]", (void *)pr->u.var_ref); } else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_AUTOINIT) { - printf("[autoinit %p %p]", (void *)pr->u.init.u.realm, + printf("[autoinit %p %d %p]", + (void *)js_autoinit_get_realm(pr), + js_autoinit_get_id(pr), (void *)pr->u.init.opaque); } else { JS_DumpValueShort(rt, pr->u.value); @@ -15726,30 +15963,6 @@ static JSValue js_call_bound_function(JSContext *ctx, JSValueConst func_obj, } } -static no_inline __exception int __js_poll_interrupts(JSContext *ctx) -{ - JSRuntime *rt = ctx->rt; - ctx->interrupt_counter = JS_INTERRUPT_COUNTER_INIT; - if (rt->interrupt_handler) { - if (rt->interrupt_handler(rt, rt->interrupt_opaque)) { - /* XXX: should set a specific flag to avoid catching */ - JS_ThrowInternalError(ctx, "interrupted"); - JS_SetUncatchableError(ctx, ctx->rt->current_exception, TRUE); - return -1; - } - } - return 0; -} - -static inline __exception int js_poll_interrupts(JSContext *ctx) -{ - if (unlikely(--ctx->interrupt_counter <= 0)) { - return __js_poll_interrupts(ctx); - } else { - return 0; - } -} - /* argument of OP_special_object */ typedef enum { OP_SPECIAL_OBJECT_ARGUMENTS, @@ -16440,7 +16653,7 @@ static JSValue JS_CallInternal(JSContext *caller_ctx, JSValueConst func_obj, CASE(OP_get_super): { JSValue proto; - proto = JS_DupValue(ctx, JS_GetPrototype(ctx, sp[-1])); + proto = JS_GetPrototype(ctx, sp[-1]); if (JS_IsException(proto)) goto exception; JS_FreeValue(ctx, sp[-1]); @@ -18543,6 +18756,9 @@ static JSValue async_func_resume(JSContext *ctx, JSAsyncFunctionState *s) { JSValue func_obj; + if (js_check_stack_overflow(ctx->rt, 0)) + return JS_ThrowStackOverflow(ctx); + /* the tag does not matter provided it is not an object */ func_obj = JS_MKPTR(JS_TAG_INT, s); return JS_CallInternal(ctx, func_obj, s->this_val, JS_UNDEFINED, @@ -19670,8 +19886,8 @@ typedef struct JSFunctionDef { /* constant pool (strings, functions, numbers) */ JSValue *cpool; - uint32_t cpool_count; - uint32_t cpool_size; + int cpool_count; + int cpool_size; /* list of variables in the closure */ int closure_var_count; @@ -19741,6 +19957,7 @@ typedef struct JSParseState { JSFunctionDef *cur_func; BOOL is_module; /* parsing a module */ BOOL allow_html_comments; + BOOL ext_json; /* true if accepting JSON superset */ } JSParseState; typedef struct JSOpCode { @@ -20308,23 +20525,18 @@ static __exception int next_token(JSParseState *s) c = *p; switch(c) { case 0: - s->token.val = TOK_EOF; - break; - case '`': - if (!s->cur_func) { - /* JSON does not accept templates */ + if (p >= s->buf_end) { + s->token.val = TOK_EOF; + } else { goto def_token; } + break; + case '`': if (js_parse_template_part(s, p + 1)) goto fail; p = s->buf_ptr; break; case '\'': - if (!s->cur_func) { - /* JSON does not accept single quoted strings */ - goto def_token; - } - /* fall through */ case '\"': if (js_parse_string(s, c, TRUE, p + 1, &s->token, &p)) goto fail; @@ -20342,11 +20554,6 @@ static __exception int next_token(JSParseState *s) goto redo; case '\f': case '\v': - if (!s->cur_func) { - /* JSONWhitespace does not match <VT>, nor <FF> */ - goto def_token; - } - /* fall through */ case ' ': case '\t': p++; @@ -20443,16 +20650,15 @@ static __exception int next_token(JSParseState *s) s->token.u.ident.is_reserved = FALSE; if (s->token.u.ident.atom <= JS_ATOM_LAST_KEYWORD || (s->token.u.ident.atom <= JS_ATOM_LAST_STRICT_KEYWORD && - s->cur_func && (s->cur_func->js_mode & JS_MODE_STRICT)) || - (s->token.u.ident.atom == JS_ATOM_yield && s->cur_func && + (s->cur_func->js_mode & JS_MODE_STRICT)) || + (s->token.u.ident.atom == JS_ATOM_yield && ((s->cur_func->func_kind & JS_FUNC_GENERATOR) || (s->cur_func->func_type == JS_PARSE_FUNC_ARROW && !s->cur_func->in_function_body && s->cur_func->parent && (s->cur_func->parent->func_kind & JS_FUNC_GENERATOR)))) || (s->token.u.ident.atom == JS_ATOM_await && (s->is_module || - (s->cur_func && - ((s->cur_func->func_kind & JS_FUNC_ASYNC) || + (((s->cur_func->func_kind & JS_FUNC_ASYNC) || (s->cur_func->func_type == JS_PARSE_FUNC_ARROW && !s->cur_func->in_function_body && s->cur_func->parent && (s->cur_func->parent->func_kind & JS_FUNC_ASYNC))))))) { @@ -20505,9 +20711,8 @@ static __exception int next_token(JSParseState *s) } break; case '0': - /* in strict or JSON parsing mode, octal literals are not accepted */ - if (is_digit(p[1]) && (!s->cur_func || - (s->cur_func->js_mode & JS_MODE_STRICT))) { + /* in strict mode, octal literals are not accepted */ + if (is_digit(p[1]) && (s->cur_func->js_mode & JS_MODE_STRICT)) { js_parse_error(s, "octal literals are deprecated in strict mode"); goto fail; } @@ -20519,22 +20724,17 @@ static __exception int next_token(JSParseState *s) JSValue ret; const uint8_t *p1; int flags, radix; - if (!s->cur_func) { - flags = 0; - radix = 10; - } else { - flags = ATOD_ACCEPT_BIN_OCT | ATOD_ACCEPT_LEGACY_OCTAL | - ATOD_ACCEPT_UNDERSCORES; + flags = ATOD_ACCEPT_BIN_OCT | ATOD_ACCEPT_LEGACY_OCTAL | + ATOD_ACCEPT_UNDERSCORES; #ifdef CONFIG_BIGNUM - flags |= ATOD_ACCEPT_SUFFIX; - if (s->cur_func->js_mode & JS_MODE_MATH) { - flags |= ATOD_MODE_BIGINT; - if (s->cur_func->js_mode & JS_MODE_MATH) - flags |= ATOD_TYPE_BIG_FLOAT; - } -#endif - radix = 0; + flags |= ATOD_ACCEPT_SUFFIX; + if (s->cur_func->js_mode & JS_MODE_MATH) { + flags |= ATOD_MODE_BIGINT; + if (s->cur_func->js_mode & JS_MODE_MATH) + flags |= ATOD_TYPE_BIG_FLOAT; } +#endif + radix = 0; #ifdef CONFIG_BIGNUM s->token.u.num.exponent = 0; ret = js_atof2(s->ctx, (const char *)p, (const char **)&p, radix, @@ -20699,7 +20899,7 @@ static __exception int next_token(JSParseState *s) case '^': if (p[1] == '=') { p += 2; - if (s->cur_func && (s->cur_func->js_mode & JS_MODE_MATH)) + if (s->cur_func->js_mode & JS_MODE_MATH) s->token.val = TOK_MATH_POW_ASSIGN; else s->token.val = TOK_XOR_ASSIGN; @@ -20713,7 +20913,7 @@ static __exception int next_token(JSParseState *s) } } else { p++; - if (s->cur_func && (s->cur_func->js_mode & JS_MODE_MATH)) + if (s->cur_func->js_mode & JS_MODE_MATH) s->token.val = TOK_MATH_POW; else s->token.val = '^'; @@ -20758,22 +20958,12 @@ static __exception int next_token(JSParseState *s) switch(c) { case CP_PS: case CP_LS: - if (!s->cur_func) { - /* <PS> and <LS> are not JSONWhitespace */ - goto def_token; - } else { - /* XXX: should avoid incrementing line_number, but - needed to handle HTML comments */ - goto line_terminator; - } + /* XXX: should avoid incrementing line_number, but + needed to handle HTML comments */ + goto line_terminator; default: if (lre_is_space(c)) { - if (!s->cur_func) { - /* category z spaces are not JSONWhitespace */ - goto def_token; - } else { - goto redo; - } + goto redo; } else if (lre_js_is_ident_first(c)) { ident_has_escape = FALSE; goto has_ident; @@ -20798,6 +20988,220 @@ static __exception int next_token(JSParseState *s) return -1; } +/* 'c' is the first character. Return JS_ATOM_NULL in case of error */ +static JSAtom json_parse_ident(JSParseState *s, const uint8_t **pp, int c) +{ + const uint8_t *p; + char ident_buf[128], *buf; + size_t ident_size, ident_pos; + JSAtom atom; + + p = *pp; + buf = ident_buf; + ident_size = sizeof(ident_buf); + ident_pos = 0; + for(;;) { + buf[ident_pos++] = c; + c = *p; + if (c >= 128 || + !((lre_id_continue_table_ascii[c >> 5] >> (c & 31)) & 1)) + break; + p++; + if (unlikely(ident_pos >= ident_size - UTF8_CHAR_LEN_MAX)) { + if (ident_realloc(s->ctx, &buf, &ident_size, ident_buf)) { + atom = JS_ATOM_NULL; + goto done; + } + } + } + atom = JS_NewAtomLen(s->ctx, buf, ident_pos); + done: + if (unlikely(buf != ident_buf)) + js_free(s->ctx, buf); + *pp = p; + return atom; +} + +static __exception int json_next_token(JSParseState *s) +{ + const uint8_t *p; + int c; + JSAtom atom; + + if (js_check_stack_overflow(s->ctx->rt, 0)) { + return js_parse_error(s, "stack overflow"); + } + + free_token(s, &s->token); + + p = s->last_ptr = s->buf_ptr; + s->last_line_num = s->token.line_num; + redo: + s->token.line_num = s->line_num; + s->token.ptr = p; + c = *p; + switch(c) { + case 0: + if (p >= s->buf_end) { + s->token.val = TOK_EOF; + } else { + goto def_token; + } + break; + case '\'': + if (!s->ext_json) { + /* JSON does not accept single quoted strings */ + goto def_token; + } + /* fall through */ + case '\"': + if (js_parse_string(s, c, TRUE, p + 1, &s->token, &p)) + goto fail; + break; + case '\r': /* accept DOS and MAC newline sequences */ + if (p[1] == '\n') { + p++; + } + /* fall thru */ + case '\n': + p++; + s->line_num++; + goto redo; + case '\f': + case '\v': + if (!s->ext_json) { + /* JSONWhitespace does not match <VT>, nor <FF> */ + goto def_token; + } + /* fall through */ + case ' ': + case '\t': + p++; + goto redo; + case '/': + if (!s->ext_json) { + /* JSON does not accept comments */ + goto def_token; + } + if (p[1] == '*') { + /* comment */ + p += 2; + for(;;) { + if (*p == '\0' && p >= s->buf_end) { + js_parse_error(s, "unexpected end of comment"); + goto fail; + } + if (p[0] == '*' && p[1] == '/') { + p += 2; + break; + } + if (*p == '\n') { + s->line_num++; + p++; + } else if (*p == '\r') { + p++; + } else if (*p >= 0x80) { + c = unicode_from_utf8(p, UTF8_CHAR_LEN_MAX, &p); + if (c == -1) { + p++; /* skip invalid UTF-8 */ + } + } else { + p++; + } + } + goto redo; + } else if (p[1] == '/') { + /* line comment */ + p += 2; + for(;;) { + if (*p == '\0' && p >= s->buf_end) + break; + if (*p == '\r' || *p == '\n') + break; + if (*p >= 0x80) { + c = unicode_from_utf8(p, UTF8_CHAR_LEN_MAX, &p); + /* LS or PS are considered as line terminator */ + if (c == CP_LS || c == CP_PS) { + break; + } else if (c == -1) { + p++; /* skip invalid UTF-8 */ + } + } else { + p++; + } + } + goto redo; + } else { + goto def_token; + } + break; + case 'a' ... 'z': + case 'A' ... 'Z': + case '_': + case '$': + /* identifier : only pure ascii characters are accepted */ + p++; + atom = json_parse_ident(s, &p, c); + if (atom == JS_ATOM_NULL) + goto fail; + s->token.u.ident.atom = atom; + s->token.u.ident.has_escape = FALSE; + s->token.u.ident.is_reserved = FALSE; + s->token.val = TOK_IDENT; + break; + case '+': + if (!s->ext_json || !is_digit(p[1])) + goto def_token; + goto parse_number; + case '0': + if (is_digit(p[1])) + goto def_token; + goto parse_number; + case '-': + if (!is_digit(p[1])) + goto def_token; + goto parse_number; + case '1' ... '9': + /* number */ + parse_number: + { + JSValue ret; + int flags, radix; + if (!s->ext_json) { + flags = 0; + radix = 10; + } else { + flags = ATOD_ACCEPT_BIN_OCT; + radix = 0; + } + ret = js_atof(s->ctx, (const char *)p, (const char **)&p, radix, + flags); + if (JS_IsException(ret)) + goto fail; + s->token.val = TOK_NUMBER; + s->token.u.num.val = ret; + } + break; + default: + if (c >= 128) { + js_parse_error(s, "unexpected character"); + goto fail; + } + def_token: + s->token.val = c; + p++; + break; + } + s->buf_ptr = p; + + // dump_token(s, &s->token); + return 0; + + fail: + s->token.val = TOK_ERROR; + return -1; +} + /* only used for ':' and '=>', 'let' or 'function' look-ahead. *pp is only set if TOK_IMPORT is returned */ /* XXX: handle all unicode cases */ @@ -20858,6 +21262,12 @@ static int simple_next_token(const uint8_t **pp, BOOL no_line_terminator) } } else if (c == 'o' && *p == 'f' && !lre_js_is_ident_next(p[1])) { return TOK_OF; + } else if (c == 'e' && + p[0] == 'x' && p[1] == 'p' && p[2] == 'o' && + p[3] == 'r' && p[4] == 't' && + !lre_js_is_ident_next(p[5])) { + *pp = p + 5; + return TOK_EXPORT; } else if (c == 'f' && p[0] == 'u' && p[1] == 'n' && p[2] == 'c' && p[3] == 't' && p[4] == 'i' && p[5] == 'o' && p[6] == 'n' && !lre_js_is_ident_next(p[7])) { @@ -20881,16 +21291,21 @@ static int peek_token(JSParseState *s, BOOL no_line_terminator) (heuristic). 'input' must be a zero terminated. Heuristic: skip comments and expect 'import' keyword not followed - by '(' or '.' + by '(' or '.' or export keyword. */ BOOL JS_DetectModule(const char *input, size_t input_len) { const uint8_t *p = (const uint8_t *)input; int tok; - if (simple_next_token(&p, FALSE) != TOK_IMPORT) + switch(simple_next_token(&p, FALSE)) { + case TOK_IMPORT: + tok = simple_next_token(&p, FALSE); + return (tok != '.' && tok != '('); + case TOK_EXPORT: + return TRUE; + default: return FALSE; - tok = simple_next_token(&p, FALSE); - return (tok != '.' && tok != '('); + } } static inline int get_prev_opcode(JSFunctionDef *fd) { @@ -20974,20 +21389,10 @@ static int new_label_fd(JSFunctionDef *fd, int label) LabelSlot *ls; if (label < 0) { - if (fd->label_count >= fd->label_size) { - int new_size; - size_t slack; - LabelSlot *new_tab; - - /* XXX: potential arithmetic overflow */ - new_size = fd->label_size * 3 / 2 + 4; - new_tab = js_realloc2(fd->ctx, fd->label_slots, new_size * sizeof(*new_tab), &slack); - if (!new_tab) - return -1; - new_size += slack / sizeof(*new_tab); - fd->label_slots = new_tab; - fd->label_size = new_size; - } + if (js_resize_array(fd->ctx, (void *)&fd->label_slots, + sizeof(fd->label_slots[0]), + &fd->label_size, fd->label_count + 1)) + return -1; label = fd->label_count++; ls = &fd->label_slots[label]; ls->ref_count = 0; @@ -21035,19 +21440,10 @@ static int emit_goto(JSParseState *s, int opcode, int label) static int cpool_add(JSParseState *s, JSValue val) { JSFunctionDef *fd = s->cur_func; - if (fd->cpool_count >= fd->cpool_size) { - int new_size; - size_t slack; - JSValue *new_tab; - /* XXX: potential arithmetic overflow */ - new_size = max_int(fd->cpool_count + 1, fd->cpool_size * 3 / 2); - new_tab = js_realloc2(s->ctx, fd->cpool, new_size * sizeof(JSValue), &slack); - if (!new_tab) - return -1; - new_size += slack / sizeof(*new_tab); - fd->cpool = new_tab; - fd->cpool_size = new_size; - } + + if (js_resize_array(s->ctx, (void *)&fd->cpool, sizeof(fd->cpool[0]), + &fd->cpool_size, fd->cpool_count + 1)) + return -1; fd->cpool[fd->cpool_count++] = val; return fd->cpool_count - 1; } @@ -21246,18 +21642,9 @@ static int add_var(JSContext *ctx, JSFunctionDef *fd, JSAtom name) JS_ThrowInternalError(ctx, "too many local variables"); return -1; } - if ((fd->var_count + 1) > fd->var_size) { - int new_size; - size_t slack; - JSVarDef *new_buf; - new_size = max_int(fd->var_count + 1, fd->var_size * 3 / 2); - new_buf = js_realloc2(ctx, fd->vars, new_size * sizeof(*fd->vars), &slack); - if (!new_buf) - return -1; - new_size += slack / sizeof(*new_buf); - fd->vars = new_buf; - fd->var_size = new_size; - } + if (js_resize_array(ctx, (void **)&fd->vars, sizeof(fd->vars[0]), + &fd->var_size, fd->var_count + 1)) + return -1; vd = &fd->vars[fd->var_count++]; memset(vd, 0, sizeof(*vd)); vd->var_name = JS_DupAtom(ctx, name); @@ -21309,18 +21696,9 @@ static int add_arg(JSContext *ctx, JSFunctionDef *fd, JSAtom name) JS_ThrowInternalError(ctx, "too many arguments"); return -1; } - if ((fd->arg_count + 1) > fd->arg_size) { - int new_size; - size_t slack; - JSVarDef *new_buf; - new_size = max_int(fd->arg_count + 1, fd->arg_size * 3 / 2); - new_buf = js_realloc2(ctx, fd->args, new_size * sizeof(*fd->args), &slack); - if (!new_buf) - return -1; - new_size += slack / sizeof(*new_buf); - fd->args = new_buf; - fd->arg_size = new_size; - } + if (js_resize_array(ctx, (void **)&fd->args, sizeof(fd->args[0]), + &fd->arg_size, fd->arg_count + 1)) + return -1; vd = &fd->args[fd->arg_count++]; memset(vd, 0, sizeof(*vd)); vd->var_name = JS_DupAtom(ctx, name); @@ -21335,19 +21713,10 @@ static JSHoistedDef *add_hoisted_def(JSContext *ctx, { JSHoistedDef *hf; - if (s->hoisted_def_count >= s->hoisted_def_size) { - int new_size; - size_t slack; - JSHoistedDef *new_tab; - new_size = max_int(s->hoisted_def_count + 1, - s->hoisted_def_size * 3 / 2); - new_tab = js_realloc2(ctx, s->hoisted_def, new_size * sizeof(s->hoisted_def[0]), &slack); - if (!new_tab) - return NULL; - new_size += slack / sizeof(*new_tab); - s->hoisted_def = new_tab; - s->hoisted_def_size = new_size; - } + if (js_resize_array(ctx, (void **)&s->hoisted_def, + sizeof(s->hoisted_def[0]), + &s->hoisted_def_size, s->hoisted_def_count + 1)) + return NULL; hf = &s->hoisted_def[s->hoisted_def_count++]; hf->cpool_idx = cpool_idx; hf->force_init = 0; @@ -21639,7 +22008,7 @@ static __exception int js_parse_template(JSParseState *s, int call, int *argc) goto done; if (next_token(s)) return -1; - if (js_parse_assign_expr(s, TRUE)) + if (js_parse_expr(s)) return -1; depth++; if (s->token.val != '}') { @@ -21647,7 +22016,8 @@ static __exception int js_parse_template(JSParseState *s, int call, int *argc) } /* XXX: should convert to string at this stage? */ free_token(s, &s->token); - /* Resume TOK_TEMPLATE parsing (s->token.line_num and s->token.ptr are OK) */ + /* Resume TOK_TEMPLATE parsing (s->token.line_num and + * s->token.ptr are OK) */ s->got_lf = FALSE; s->last_line_num = s->token.line_num; if (js_parse_template_part(s, s->buf_ptr)) @@ -21875,7 +22245,7 @@ static int js_parse_skip_parens_token(JSParseState *s, int *pbits, BOOL no_line_ size_t level = 0; JSParsePos pos; int last_tok, tok = TOK_EOF; - int tok_len, bits = 0; + int c, tok_len, bits = 0; /* protect from underflow */ state[level++] = 0; @@ -21900,8 +22270,30 @@ static int js_parse_skip_parens_token(JSParseState *s, int *pbits, BOOL no_line_ goto done; break; case '}': - if (state[--level] != '{') + c = state[--level]; + if (c == '`') { + /* continue the parsing of the template */ + free_token(s, &s->token); + /* Resume TOK_TEMPLATE parsing (s->token.line_num and + * s->token.ptr are OK) */ + s->got_lf = FALSE; + s->last_line_num = s->token.line_num; + if (js_parse_template_part(s, s->buf_ptr)) + goto done; + goto handle_template; + } else if (c != '{') { goto done; + } + break; + case TOK_TEMPLATE: + handle_template: + if (s->token.u.str.sep != '`') { + /* '${' inside the template : closing '}' and continue + parsing the template */ + if (level >= sizeof(state)) + goto done; + state[level++] = '`'; + } break; case TOK_EOF: goto done; @@ -24005,7 +24397,7 @@ static __exception int js_parse_postfix_expr(JSParseState *s, BOOL accept_lparen int scope; name = get_u32(fd->byte_code.buf + fd->last_opcode_pos + 1); scope = get_u16(fd->byte_code.buf + fd->last_opcode_pos + 5); - if (name == JS_ATOM_eval && call_type == FUNC_CALL_NORMAL) { + if (name == JS_ATOM_eval && call_type == FUNC_CALL_NORMAL && !has_optional_chain) { /* direct 'eval' */ opcode = OP_eval; } else { @@ -26318,26 +26710,6 @@ static void js_free_module_def(JSContext *ctx, JSModuleDef *m) js_free(ctx, m); } -static int js_resize_array(JSContext *ctx, void **parray, int elem_size, - int *psize, int *pcount, int new_count) -{ - if (unlikely(new_count > *psize)) { - int new_size; - size_t slack; - void *new_array; - /* XXX: potential arithmetic overflow */ - new_size = max_int(new_count, *psize * 3 / 2); - new_array = js_realloc2(ctx, *parray, new_size * elem_size, &slack); - if (!new_array) - return -1; - new_size += slack / elem_size; - *psize = new_size; - *parray = new_array; - } - *pcount = new_count; - return 0; -} - static int add_req_module_entry(JSContext *ctx, JSModuleDef *m, JSAtom module_name) { @@ -26353,11 +26725,10 @@ static int add_req_module_entry(JSContext *ctx, JSModuleDef *m, if (js_resize_array(ctx, (void **)&m->req_module_entries, sizeof(JSReqModuleEntry), - &m->req_module_entries_size, &m->req_module_entries_count, + &m->req_module_entries_size, m->req_module_entries_count + 1)) return -1; - i = m->req_module_entries_count - 1; - rme = &m->req_module_entries[i]; + rme = &m->req_module_entries[m->req_module_entries_count++]; rme->module_name = JS_DupAtom(ctx, module_name); rme->module = NULL; return i; @@ -26389,18 +26760,17 @@ static JSExportEntry *add_export_entry2(JSContext *ctx, js_parse_error(s, "duplicate exported name '%s'", JS_AtomGetStr(ctx, buf1, sizeof(buf1), export_name)); } else { - JS_ThrowSyntaxError(ctx, "duplicate exported name '%s'", - JS_AtomGetStr(ctx, buf1, sizeof(buf1), export_name)); + JS_ThrowSyntaxErrorAtom(ctx, "duplicate exported name '%s'", export_name); } return NULL; } if (js_resize_array(ctx, (void **)&m->export_entries, sizeof(JSExportEntry), - &m->export_entries_size, &m->export_entries_count, + &m->export_entries_size, m->export_entries_count + 1)) return NULL; - me = &m->export_entries[m->export_entries_count - 1]; + me = &m->export_entries[m->export_entries_count++]; memset(me, 0, sizeof(*me)); me->local_name = JS_DupAtom(ctx, local_name); me->export_name = JS_DupAtom(ctx, export_name); @@ -26423,10 +26793,10 @@ static int add_star_export_entry(JSContext *ctx, JSModuleDef *m, if (js_resize_array(ctx, (void **)&m->star_export_entries, sizeof(JSStarExportEntry), - &m->star_export_entries_size, &m->star_export_entries_count, + &m->star_export_entries_size, m->star_export_entries_count + 1)) return -1; - se = &m->star_export_entries[m->star_export_entries_count - 1]; + se = &m->star_export_entries[m->star_export_entries_count++]; se->req_module_idx = req_module_idx; return 0; } @@ -26652,10 +27022,9 @@ static int add_resolve_entry(JSContext *ctx, JSResolveState *s, if (js_resize_array(ctx, (void **)&s->array, sizeof(JSResolveEntry), - &s->size, &s->count, - s->count + 1)) + &s->size, s->count + 1)) return -1; - re = &s->array[s->count - 1]; + re = &s->array[s->count++]; re->module = m; re->name = JS_DupAtom(ctx, name); return 0; @@ -26780,17 +27149,17 @@ static void js_resolve_export_throw_error(JSContext *ctx, break; default: case JS_RESOLVE_RES_NOT_FOUND: - JS_ThrowSyntaxError(ctx, "export '%s' in module '%s' is ambiguous", + JS_ThrowSyntaxError(ctx, "Could not find export '%s' in module '%s'", JS_AtomGetStr(ctx, buf1, sizeof(buf1), export_name), JS_AtomGetStr(ctx, buf2, sizeof(buf2), m->module_name)); break; case JS_RESOLVE_RES_CIRCULAR: - JS_ThrowSyntaxError(ctx, "Could not find export '%s' in module '%s'", + JS_ThrowSyntaxError(ctx, "circular reference when looking for export '%s' in module '%s'", JS_AtomGetStr(ctx, buf1, sizeof(buf1), export_name), JS_AtomGetStr(ctx, buf2, sizeof(buf2), m->module_name)); break; case JS_RESOLVE_RES_AMBIGUOUS: - JS_ThrowSyntaxError(ctx, "circular reference when looking for export '%s' in module '%s'", + JS_ThrowSyntaxError(ctx, "export '%s' in module '%s' is ambiguous", JS_AtomGetStr(ctx, buf1, sizeof(buf1), export_name), JS_AtomGetStr(ctx, buf2, sizeof(buf2), m->module_name)); break; @@ -26847,9 +27216,9 @@ static __exception int get_exported_names(JSContext *ctx, return 0; } if (js_resize_array(ctx, (void **)&s->modules, sizeof(s->modules[0]), - &s->modules_size, &s->modules_count, s->modules_count + 1)) + &s->modules_size, s->modules_count + 1)) return -1; - s->modules[s->modules_count - 1] = m; + s->modules[s->modules_count++] = m; for(i = 0; i < m->export_entries_count; i++) { JSExportEntry *me = &m->export_entries[i]; @@ -26858,9 +27227,10 @@ static __exception int get_exported_names(JSContext *ctx, j = find_exported_name(s, me->export_name); if (j < 0) { if (js_resize_array(ctx, (void **)&s->exported_names, sizeof(s->exported_names[0]), - &s->exported_names_size, &s->exported_names_count, s->exported_names_count + 1)) + &s->exported_names_size, + s->exported_names_count + 1)) return -1; - en = &s->exported_names[s->exported_names_count - 1]; + en = &s->exported_names[s->exported_names_count++]; en->export_name = me->export_name; /* avoid a second lookup for simple module exports */ if (from_star || me->export_type != JS_EXPORT_TYPE_LOCAL) @@ -27096,7 +27466,7 @@ static JSVarRef *js_create_module_var(JSContext *ctx, BOOL is_lexical) } /* Create the <eval> function associated with the module */ -static int js_create_module_function(JSContext *ctx, JSModuleDef *m) +static int js_create_module_bytecode_function(JSContext *ctx, JSModuleDef *m) { JSFunctionBytecode *b; int i; @@ -27147,20 +27517,15 @@ static int js_create_module_function(JSContext *ctx, JSModuleDef *m) return -1; } -/* Prepare a module to be executed by resolving all the imported - variables. */ -static int js_instantiate_module(JSContext *ctx, JSModuleDef *m) +/* must be done before js_instantiate_module() because of cyclic references */ +static int js_create_module_function(JSContext *ctx, JSModuleDef *m) { - int i; - JSImportEntry *mi; - JSModuleDef *m1; - JSVarRef **var_refs, *var_ref; - JSObject *p; BOOL is_c_module; - - if (m->instantiated) + int i; + JSVarRef *var_ref; + + if (m->func_created) return 0; - m->instantiated = TRUE; is_c_module = (m->init_func != NULL); @@ -27171,14 +27536,49 @@ static int js_instantiate_module(JSContext *ctx, JSModuleDef *m) if (me->export_type == JS_EXPORT_TYPE_LOCAL) { var_ref = js_create_module_var(ctx, FALSE); if (!var_ref) - goto fail; + return -1; me->u.local.var_ref = var_ref; } } } else { - if (js_create_module_function(ctx, m) < 0) - goto fail; + if (js_create_module_bytecode_function(ctx, m)) + return -1; + } + m->func_created = TRUE; + + /* do it on the dependencies */ + + for(i = 0; i < m->req_module_entries_count; i++) { + JSReqModuleEntry *rme = &m->req_module_entries[i]; + if (js_create_module_function(ctx, rme->module) < 0) + return -1; + } + + return 0; +} + + +/* Prepare a module to be executed by resolving all the imported + variables. */ +static int js_instantiate_module(JSContext *ctx, JSModuleDef *m) +{ + int i; + JSImportEntry *mi; + JSModuleDef *m1; + JSVarRef **var_refs, *var_ref; + JSObject *p; + BOOL is_c_module; + + if (m->instantiated) + return 0; + m->instantiated = TRUE; + +#ifdef DUMP_MODULE_RESOLVE + { + char buf1[ATOM_GET_STR_BUF_SIZE]; + printf("start instantiating module '%s':\n", JS_AtomGetStr(ctx, buf1, sizeof(buf1), m->module_name)); } +#endif for(i = 0; i < m->req_module_entries_count; i++) { JSReqModuleEntry *rme = &m->req_module_entries[i]; @@ -27221,6 +27621,8 @@ static int js_instantiate_module(JSContext *ctx, JSModuleDef *m) } #endif + is_c_module = (m->init_func != NULL); + if (!is_c_module) { p = JS_VALUE_GET_OBJ(m->func_obj); var_refs = p->u.func.var_refs; @@ -27740,10 +28142,10 @@ static int add_import(JSParseState *s, JSModuleDef *m, return -1; if (js_resize_array(ctx, (void **)&m->import_entries, sizeof(JSImportEntry), - &m->import_entries_size, &m->import_entries_count, + &m->import_entries_size, m->import_entries_count + 1)) return -1; - mi = &m->import_entries[m->import_entries_count - 1]; + mi = &m->import_entries[m->import_entries_count++]; mi->import_name = JS_DupAtom(ctx, import_name); mi->var_idx = var_idx; return 0; @@ -28474,20 +28876,10 @@ static int add_closure_var(JSContext *ctx, JSFunctionDef *s, return -1; } - if (s->closure_var_count >= s->closure_var_size) { - JSClosureVar *new_tab; - int new_size; - size_t slack; - new_size = max_int(s->closure_var_count + 1, - s->closure_var_size * 3 / 2); - new_tab = js_realloc2(ctx, s->closure_var, - new_size * sizeof(JSClosureVar), &slack); - if (!new_tab) - return -1; - new_size += slack / sizeof(*new_tab); - s->closure_var = new_tab; - s->closure_var_size = new_size; - } + if (js_resize_array(ctx, (void **)&s->closure_var, + sizeof(s->closure_var[0]), + &s->closure_var_size, s->closure_var_count + 1)) + return -1; cv = &s->closure_var[s->closure_var_count++]; cv->is_local = is_local; cv->is_arg = is_arg; @@ -29243,8 +29635,6 @@ static int resolve_scope_private_field1(JSContext *ctx, } scope_level = fd->parent_scope_level; if (!fd->parent) { - char buf[ATOM_GET_STR_BUF_SIZE]; - if (fd->is_eval) { /* closure of the eval function (top level) */ for (idx = 0; idx < fd->closure_var_count; idx++) { @@ -29267,8 +29657,8 @@ static int resolve_scope_private_field1(JSContext *ctx, } } /* XXX: no line number info */ - JS_ThrowSyntaxError(ctx, "undefined private field %s", - JS_AtomGetStr(ctx, buf, sizeof(buf), var_name)); + JS_ThrowSyntaxErrorAtom(ctx, "undefined private field '%s'", + var_name); return -1; } else { fd = fd->parent; @@ -31535,9 +31925,8 @@ static int add_module_variables(JSContext *ctx, JSFunctionDef *fd) if (me->export_type == JS_EXPORT_TYPE_LOCAL) { idx = find_closure_var(ctx, fd, me->local_name); if (idx < 0) { - char buf1[ATOM_GET_STR_BUF_SIZE]; - JS_ThrowSyntaxError(ctx, "exported variable '%s' does not exist", - JS_AtomGetStr(ctx, buf1, sizeof(buf1), me->local_name)); + JS_ThrowSyntaxErrorAtom(ctx, "exported variable '%s' does not exist", + me->local_name); return -1; } me->u.local.var_idx = idx; @@ -32559,6 +32948,8 @@ static JSValue JS_EvalFunctionInternal(JSContext *ctx, JSValue fun_obj, m = JS_VALUE_GET_PTR(fun_obj); /* the module refcount should be >= 2 */ JS_FreeValue(ctx, fun_obj); + if (js_create_module_function(ctx, m) < 0) + goto fail; if (js_instantiate_module(ctx, m) < 0) goto fail; ret_val = js_evaluate_module(ctx, m); @@ -32770,6 +33161,110 @@ int JS_ResolveModule(JSContext *ctx, JSValueConst obj) } /*******************************************************************/ +/* object list */ + +typedef struct { + JSObject *obj; + uint32_t hash_next; /* -1 if no next entry */ +} JSObjectListEntry; + +/* XXX: reuse it to optimize weak references */ +typedef struct { + JSObjectListEntry *object_tab; + int object_count; + int object_size; + uint32_t *hash_table; + uint32_t hash_size; +} JSObjectList; + +static void js_object_list_init(JSObjectList *s) +{ + memset(s, 0, sizeof(*s)); +} + +static uint32_t js_object_list_get_hash(JSObject *p, uint32_t hash_size) +{ + return ((uintptr_t)p * 3163) & (hash_size - 1); +} + +static int js_object_list_resize_hash(JSContext *ctx, JSObjectList *s, + uint32_t new_hash_size) +{ + JSObjectListEntry *e; + uint32_t i, h, *new_hash_table; + + new_hash_table = js_malloc(ctx, sizeof(new_hash_table[0]) * new_hash_size); + if (!new_hash_table) + return -1; + js_free(ctx, s->hash_table); + s->hash_table = new_hash_table; + s->hash_size = new_hash_size; + + for(i = 0; i < s->hash_size; i++) { + s->hash_table[i] = -1; + } + for(i = 0; i < s->object_count; i++) { + e = &s->object_tab[i]; + h = js_object_list_get_hash(e->obj, s->hash_size); + e->hash_next = s->hash_table[h]; + s->hash_table[h] = i; + } + return 0; +} + +/* the reference count of 'obj' is not modified. Return 0 if OK, -1 if + memory error */ +static int js_object_list_add(JSContext *ctx, JSObjectList *s, JSObject *obj) +{ + JSObjectListEntry *e; + uint32_t h, new_hash_size; + + if (js_resize_array(ctx, (void *)&s->object_tab, + sizeof(s->object_tab[0]), + &s->object_size, s->object_count + 1)) + return -1; + if (unlikely((s->object_count + 1) >= s->hash_size)) { + new_hash_size = max_uint32(s->hash_size, 4); + while (new_hash_size <= s->object_count) + new_hash_size *= 2; + if (js_object_list_resize_hash(ctx, s, new_hash_size)) + return -1; + } + e = &s->object_tab[s->object_count++]; + h = js_object_list_get_hash(obj, s->hash_size); + e->obj = obj; + e->hash_next = s->hash_table[h]; + s->hash_table[h] = s->object_count - 1; + return 0; +} + +/* return -1 if not present or the object index */ +static int js_object_list_find(JSContext *ctx, JSObjectList *s, JSObject *obj) +{ + JSObjectListEntry *e; + uint32_t h, p; + + /* must test empty size because there is no hash table */ + if (s->object_count == 0) + return -1; + h = js_object_list_get_hash(obj, s->hash_size); + p = s->hash_table[h]; + while (p != -1) { + e = &s->object_tab[p]; + if (e->obj == obj) + return p; + p = e->hash_next; + } + return -1; +} + +static void js_object_list_end(JSContext *ctx, JSObjectList *s) +{ + js_free(ctx, s->object_tab); + js_free(ctx, s->hash_table); +} + +/*******************************************************************/ /* binary object writer & reader */ typedef enum BCTagEnum { @@ -32788,6 +33283,12 @@ typedef enum BCTagEnum { BC_TAG_TEMPLATE_OBJECT, BC_TAG_FUNCTION_BYTECODE, BC_TAG_MODULE, + BC_TAG_TYPED_ARRAY, + BC_TAG_ARRAY_BUFFER, + BC_TAG_SHARED_ARRAY_BUFFER, + BC_TAG_DATE, + BC_TAG_OBJECT_VALUE, + BC_TAG_OBJECT_REFERENCE, } BCTagEnum; #ifdef CONFIG_BIGNUM @@ -32805,14 +33306,21 @@ typedef enum BCTagEnum { typedef struct BCWriterState { JSContext *ctx; DynBuf dbuf; - BOOL byte_swap; - BOOL allow_bytecode; + BOOL byte_swap : 8; + BOOL allow_bytecode : 8; + BOOL allow_sab : 8; + BOOL allow_reference : 8; uint32_t first_atom; uint32_t *atom_to_idx; int atom_to_idx_size; JSAtom *idx_to_atom; int idx_to_atom_count; int idx_to_atom_size; + uint8_t **sab_tab; + int sab_tab_len; + int sab_tab_size; + /* list of referenced objects (used if allow_reference = TRUE) */ + JSObjectList object_list; } BCWriterState; #ifdef DUMP_READ_OBJECT @@ -32833,6 +33341,12 @@ static const char * const bc_tag_str[] = { "template", "function", "module", + "TypedArray", + "ArrayBuffer", + "SharedArrayBuffer", + "Date", + "ObjectValue", + "ObjectReference", }; #endif @@ -32892,36 +33406,20 @@ static int bc_atom_to_idx(BCWriterState *s, uint32_t *pres, JSAtom atom) return 0; } if (atom >= s->atom_to_idx_size) { - size_t new_size, i, slack; - uint32_t *new_tab; - /* XXX: potential arithmetic overflow */ - new_size = s->atom_to_idx_size * 3 / 2; - if ((atom + 1) > new_size) - new_size = atom + 1; - new_tab = js_realloc2(s->ctx, s->atom_to_idx, - new_size * sizeof(s->atom_to_idx[0]), &slack); - if (!new_tab) - goto fail; - new_size += slack / sizeof(*new_tab); - for(i = s->atom_to_idx_size; i < new_size; i++) - new_tab[i] = 0; - s->atom_to_idx = new_tab; - s->atom_to_idx_size = new_size; - } - if ((s->idx_to_atom_count + 1) > s->idx_to_atom_size) { - size_t new_size, slack; - JSAtom *new_tab; - new_size = s->idx_to_atom_size * 3 / 2; - if ((s->idx_to_atom_count + 1) > new_size) - new_size = s->idx_to_atom_count + 1; - new_tab = js_realloc2(s->ctx, s->idx_to_atom, - new_size * sizeof(s->idx_to_atom[0]), &slack); - if (!new_tab) - goto fail; - new_size += slack / sizeof(*new_tab); - s->idx_to_atom = new_tab; - s->idx_to_atom_size = new_size; + int old_size, i; + old_size = s->atom_to_idx_size; + if (js_resize_array(s->ctx, (void **)&s->atom_to_idx, + sizeof(s->atom_to_idx[0]), &s->atom_to_idx_size, + atom + 1)) + return -1; + /* XXX: could add a specific js_resize_array() function to do it */ + for(i = old_size; i < s->atom_to_idx_size; i++) + s->atom_to_idx[i] = 0; } + if (js_resize_array(s->ctx, (void **)&s->idx_to_atom, + sizeof(s->idx_to_atom[0]), + &s->idx_to_atom_size, s->idx_to_atom_count + 1)) + goto fail; v = s->idx_to_atom_count++; s->idx_to_atom[v] = atom + s->first_atom; @@ -33204,10 +33702,276 @@ static int JS_WriteBigNum(BCWriterState *s, JSValueConst obj) } #endif /* CONFIG_BIGNUM */ +static int JS_WriteObjectRec(BCWriterState *s, JSValueConst obj); + +static int JS_WriteFunctionTag(BCWriterState *s, JSValueConst obj) +{ + JSFunctionBytecode *b = JS_VALUE_GET_PTR(obj); + uint32_t flags; + int idx, i; + + bc_put_u8(s, BC_TAG_FUNCTION_BYTECODE); + flags = idx = 0; + bc_set_flags(&flags, &idx, b->has_prototype, 1); + bc_set_flags(&flags, &idx, b->has_simple_parameter_list, 1); + bc_set_flags(&flags, &idx, b->is_derived_class_constructor, 1); + bc_set_flags(&flags, &idx, b->need_home_object, 1); + bc_set_flags(&flags, &idx, b->func_kind, 2); + bc_set_flags(&flags, &idx, b->new_target_allowed, 1); + bc_set_flags(&flags, &idx, b->super_call_allowed, 1); + bc_set_flags(&flags, &idx, b->super_allowed, 1); + bc_set_flags(&flags, &idx, b->arguments_allowed, 1); + bc_set_flags(&flags, &idx, b->has_debug, 1); + bc_set_flags(&flags, &idx, b->backtrace_barrier, 1); + assert(idx <= 16); + bc_put_u16(s, flags); + bc_put_u8(s, b->js_mode); + bc_put_atom(s, b->func_name); + + bc_put_leb128(s, b->arg_count); + bc_put_leb128(s, b->var_count); + bc_put_leb128(s, b->defined_arg_count); + bc_put_leb128(s, b->stack_size); + bc_put_leb128(s, b->closure_var_count); + bc_put_leb128(s, b->cpool_count); + bc_put_leb128(s, b->byte_code_len); + if (b->vardefs) { + bc_put_leb128(s, b->arg_count + b->var_count); + for(i = 0; i < b->arg_count + b->var_count; i++) { + JSVarDef *vd = &b->vardefs[i]; + bc_put_atom(s, vd->var_name); + bc_put_leb128(s, vd->scope_level); + bc_put_leb128(s, vd->scope_next + 1); + flags = idx = 0; + bc_set_flags(&flags, &idx, vd->var_kind, 4); + bc_set_flags(&flags, &idx, vd->is_func_var, 1); + bc_set_flags(&flags, &idx, vd->is_const, 1); + bc_set_flags(&flags, &idx, vd->is_lexical, 1); + bc_set_flags(&flags, &idx, vd->is_captured, 1); + assert(idx <= 8); + bc_put_u8(s, flags); + } + } else { + bc_put_leb128(s, 0); + } + + for(i = 0; i < b->closure_var_count; i++) { + JSClosureVar *cv = &b->closure_var[i]; + bc_put_atom(s, cv->var_name); + bc_put_leb128(s, cv->var_idx); + flags = idx = 0; + bc_set_flags(&flags, &idx, cv->is_local, 1); + bc_set_flags(&flags, &idx, cv->is_arg, 1); + bc_set_flags(&flags, &idx, cv->is_const, 1); + bc_set_flags(&flags, &idx, cv->is_lexical, 1); + bc_set_flags(&flags, &idx, cv->var_kind, 4); + assert(idx <= 8); + bc_put_u8(s, flags); + } + + if (JS_WriteFunctionBytecode(s, b->byte_code_buf, b->byte_code_len)) + goto fail; + + if (b->has_debug) { + bc_put_atom(s, b->debug.filename); + bc_put_leb128(s, b->debug.line_num); + bc_put_leb128(s, b->debug.pc2line_len); + dbuf_put(&s->dbuf, b->debug.pc2line_buf, b->debug.pc2line_len); + } + + for(i = 0; i < b->cpool_count; i++) { + if (JS_WriteObjectRec(s, b->cpool[i])) + goto fail; + } + return 0; + fail: + return -1; +} + +static int JS_WriteModule(BCWriterState *s, JSValueConst obj) +{ + JSModuleDef *m = JS_VALUE_GET_PTR(obj); + int i; + + bc_put_u8(s, BC_TAG_MODULE); + bc_put_atom(s, m->module_name); + + bc_put_leb128(s, m->req_module_entries_count); + for(i = 0; i < m->req_module_entries_count; i++) { + JSReqModuleEntry *rme = &m->req_module_entries[i]; + bc_put_atom(s, rme->module_name); + } + + bc_put_leb128(s, m->export_entries_count); + for(i = 0; i < m->export_entries_count; i++) { + JSExportEntry *me = &m->export_entries[i]; + bc_put_u8(s, me->export_type); + if (me->export_type == JS_EXPORT_TYPE_LOCAL) { + bc_put_leb128(s, me->u.local.var_idx); + } else { + bc_put_leb128(s, me->u.req_module_idx); + bc_put_atom(s, me->local_name); + } + bc_put_atom(s, me->export_name); + } + + bc_put_leb128(s, m->star_export_entries_count); + for(i = 0; i < m->star_export_entries_count; i++) { + JSStarExportEntry *se = &m->star_export_entries[i]; + bc_put_leb128(s, se->req_module_idx); + } + + bc_put_leb128(s, m->import_entries_count); + for(i = 0; i < m->import_entries_count; i++) { + JSImportEntry *mi = &m->import_entries[i]; + bc_put_leb128(s, mi->var_idx); + bc_put_atom(s, mi->import_name); + bc_put_leb128(s, mi->req_module_idx); + } + + if (JS_WriteObjectRec(s, m->func_obj)) + goto fail; + return 0; + fail: + return -1; +} + +static int JS_WriteArray(BCWriterState *s, JSValueConst obj) +{ + JSObject *p = JS_VALUE_GET_OBJ(obj); + uint32_t i, len; + JSValue val; + int ret; + BOOL is_template; + + if (s->allow_bytecode && !p->extensible) { + /* not extensible array: we consider it is a + template when we are saving bytecode */ + bc_put_u8(s, BC_TAG_TEMPLATE_OBJECT); + is_template = TRUE; + } else { + bc_put_u8(s, BC_TAG_ARRAY); + is_template = FALSE; + } + if (js_get_length32(s->ctx, &len, obj)) + goto fail1; + bc_put_leb128(s, len); + for(i = 0; i < len; i++) { + val = JS_GetPropertyUint32(s->ctx, obj, i); + if (JS_IsException(val)) + goto fail1; + ret = JS_WriteObjectRec(s, val); + JS_FreeValue(s->ctx, val); + if (ret) + goto fail1; + } + if (is_template) { + val = JS_GetProperty(s->ctx, obj, JS_ATOM_raw); + if (JS_IsException(val)) + goto fail1; + ret = JS_WriteObjectRec(s, val); + JS_FreeValue(s->ctx, val); + if (ret) + goto fail1; + } + return 0; + fail1: + return -1; +} + +static int JS_WriteObjectTag(BCWriterState *s, JSValueConst obj) +{ + JSObject *p = JS_VALUE_GET_OBJ(obj); + uint32_t i, prop_count; + JSShape *sh; + JSShapeProperty *pr; + int pass; + JSAtom atom; + + bc_put_u8(s, BC_TAG_OBJECT); + prop_count = 0; + sh = p->shape; + for(pass = 0; pass < 2; pass++) { + if (pass == 1) + bc_put_leb128(s, prop_count); + for(i = 0, pr = get_shape_prop(sh); i < sh->prop_count; i++, pr++) { + atom = pr->atom; + if (atom != JS_ATOM_NULL && + JS_AtomIsString(s->ctx, atom) && + (pr->flags & JS_PROP_ENUMERABLE)) { + if (pr->flags & JS_PROP_TMASK) { + JS_ThrowTypeError(s->ctx, "only value properties are supported"); + goto fail; + } + if (pass == 0) { + prop_count++; + } else { + bc_put_atom(s, atom); + if (JS_WriteObjectRec(s, p->prop[i].u.value)) + goto fail; + } + } + } + } + return 0; + fail: + return -1; +} + +static int JS_WriteTypedArray(BCWriterState *s, JSValueConst obj) +{ + JSObject *p = JS_VALUE_GET_OBJ(obj); + JSTypedArray *ta = p->u.typed_array; + + bc_put_u8(s, BC_TAG_TYPED_ARRAY); + bc_put_u8(s, p->class_id - JS_CLASS_UINT8C_ARRAY); + bc_put_leb128(s, p->u.array.count); + bc_put_leb128(s, ta->offset); + if (JS_WriteObjectRec(s, JS_MKPTR(JS_TAG_OBJECT, ta->buffer))) + return -1; + return 0; +} + +static int JS_WriteArrayBuffer(BCWriterState *s, JSValueConst obj) +{ + JSObject *p = JS_VALUE_GET_OBJ(obj); + JSArrayBuffer *abuf = p->u.array_buffer; + if (abuf->detached) { + JS_ThrowTypeErrorDetachedArrayBuffer(s->ctx); + return -1; + } + bc_put_u8(s, BC_TAG_ARRAY_BUFFER); + bc_put_leb128(s, abuf->byte_length); + dbuf_put(&s->dbuf, abuf->data, abuf->byte_length); + return 0; +} + +static int JS_WriteSharedArrayBuffer(BCWriterState *s, JSValueConst obj) +{ + JSObject *p = JS_VALUE_GET_OBJ(obj); + JSArrayBuffer *abuf = p->u.array_buffer; + assert(!abuf->detached); /* SharedArrayBuffer are never detached */ + bc_put_u8(s, BC_TAG_SHARED_ARRAY_BUFFER); + bc_put_leb128(s, abuf->byte_length); + bc_put_u64(s, (uintptr_t)abuf->data); + if (js_resize_array(s->ctx, (void **)&s->sab_tab, sizeof(s->sab_tab[0]), + &s->sab_tab_size, s->sab_tab_len + 1)) + return -1; + /* keep the SAB pointer so that the user can clone it or free it */ + s->sab_tab[s->sab_tab_len++] = abuf->data; + return 0; +} + static int JS_WriteObjectRec(BCWriterState *s, JSValueConst obj) { - uint32_t tag = JS_VALUE_GET_NORM_TAG(obj); + uint32_t tag; + + if (js_check_stack_overflow(s->ctx->rt, 0)) { + JS_ThrowStackOverflow(s->ctx); + return -1; + } + tag = JS_VALUE_GET_NORM_TAG(obj); switch(tag) { case JS_TAG_NULL: bc_put_u8(s, BC_TAG_NULL); @@ -33238,218 +34002,82 @@ static int JS_WriteObjectRec(BCWriterState *s, JSValueConst obj) } break; case JS_TAG_FUNCTION_BYTECODE: - { - JSFunctionBytecode *b = JS_VALUE_GET_PTR(obj); - uint32_t flags; - int idx, i; - - if (!s->allow_bytecode) - goto invalid_tag; - bc_put_u8(s, BC_TAG_FUNCTION_BYTECODE); - flags = idx = 0; - bc_set_flags(&flags, &idx, b->has_prototype, 1); - bc_set_flags(&flags, &idx, b->has_simple_parameter_list, 1); - bc_set_flags(&flags, &idx, b->is_derived_class_constructor, 1); - bc_set_flags(&flags, &idx, b->need_home_object, 1); - bc_set_flags(&flags, &idx, b->func_kind, 2); - bc_set_flags(&flags, &idx, b->new_target_allowed, 1); - bc_set_flags(&flags, &idx, b->super_call_allowed, 1); - bc_set_flags(&flags, &idx, b->super_allowed, 1); - bc_set_flags(&flags, &idx, b->arguments_allowed, 1); - bc_set_flags(&flags, &idx, b->has_debug, 1); - bc_set_flags(&flags, &idx, b->backtrace_barrier, 1); - assert(idx <= 16); - bc_put_u16(s, flags); - bc_put_u8(s, b->js_mode); - bc_put_atom(s, b->func_name); - - bc_put_leb128(s, b->arg_count); - bc_put_leb128(s, b->var_count); - bc_put_leb128(s, b->defined_arg_count); - bc_put_leb128(s, b->stack_size); - bc_put_leb128(s, b->closure_var_count); - bc_put_leb128(s, b->cpool_count); - bc_put_leb128(s, b->byte_code_len); - if (b->vardefs) { - bc_put_leb128(s, b->arg_count + b->var_count); - for(i = 0; i < b->arg_count + b->var_count; i++) { - JSVarDef *vd = &b->vardefs[i]; - bc_put_atom(s, vd->var_name); - bc_put_leb128(s, vd->scope_level); - bc_put_leb128(s, vd->scope_next + 1); - flags = idx = 0; - bc_set_flags(&flags, &idx, vd->var_kind, 4); - bc_set_flags(&flags, &idx, vd->is_func_var, 1); - bc_set_flags(&flags, &idx, vd->is_const, 1); - bc_set_flags(&flags, &idx, vd->is_lexical, 1); - bc_set_flags(&flags, &idx, vd->is_captured, 1); - assert(idx <= 8); - bc_put_u8(s, flags); - } - } else { - bc_put_leb128(s, 0); - } - - for(i = 0; i < b->closure_var_count; i++) { - JSClosureVar *cv = &b->closure_var[i]; - bc_put_atom(s, cv->var_name); - bc_put_leb128(s, cv->var_idx); - flags = idx = 0; - bc_set_flags(&flags, &idx, cv->is_local, 1); - bc_set_flags(&flags, &idx, cv->is_arg, 1); - bc_set_flags(&flags, &idx, cv->is_const, 1); - bc_set_flags(&flags, &idx, cv->is_lexical, 1); - bc_set_flags(&flags, &idx, cv->var_kind, 4); - assert(idx <= 8); - bc_put_u8(s, flags); - } - - if (JS_WriteFunctionBytecode(s, b->byte_code_buf, b->byte_code_len)) - goto fail; - - if (b->has_debug) { - bc_put_atom(s, b->debug.filename); - bc_put_leb128(s, b->debug.line_num); - bc_put_leb128(s, b->debug.pc2line_len); - dbuf_put(&s->dbuf, b->debug.pc2line_buf, b->debug.pc2line_len); - } - - for(i = 0; i < b->cpool_count; i++) { - if (JS_WriteObjectRec(s, b->cpool[i])) - goto fail; - } - } + if (!s->allow_bytecode) + goto invalid_tag; + if (JS_WriteFunctionTag(s, obj)) + goto fail; break; case JS_TAG_MODULE: - { - JSModuleDef *m = JS_VALUE_GET_PTR(obj); - int i; - - if (!s->allow_bytecode) - goto invalid_tag; - bc_put_u8(s, BC_TAG_MODULE); - bc_put_atom(s, m->module_name); - - bc_put_leb128(s, m->req_module_entries_count); - for(i = 0; i < m->req_module_entries_count; i++) { - JSReqModuleEntry *rme = &m->req_module_entries[i]; - bc_put_atom(s, rme->module_name); - } - - bc_put_leb128(s, m->export_entries_count); - for(i = 0; i < m->export_entries_count; i++) { - JSExportEntry *me = &m->export_entries[i]; - bc_put_u8(s, me->export_type); - if (me->export_type == JS_EXPORT_TYPE_LOCAL) { - bc_put_leb128(s, me->u.local.var_idx); - } else { - bc_put_leb128(s, me->u.req_module_idx); - bc_put_atom(s, me->local_name); - } - bc_put_atom(s, me->export_name); - } - - bc_put_leb128(s, m->star_export_entries_count); - for(i = 0; i < m->star_export_entries_count; i++) { - JSStarExportEntry *se = &m->star_export_entries[i]; - bc_put_leb128(s, se->req_module_idx); - } - - bc_put_leb128(s, m->import_entries_count); - for(i = 0; i < m->import_entries_count; i++) { - JSImportEntry *mi = &m->import_entries[i]; - bc_put_leb128(s, mi->var_idx); - bc_put_atom(s, mi->import_name); - bc_put_leb128(s, mi->req_module_idx); - } - - if (JS_WriteObjectRec(s, m->func_obj)) - goto fail; - } + if (!s->allow_bytecode) + goto invalid_tag; + if (JS_WriteModule(s, obj)) + goto fail; break; case JS_TAG_OBJECT: { JSObject *p = JS_VALUE_GET_OBJ(obj); - uint32_t i, prop_count, len; - JSShape *sh; - JSShapeProperty *pr; - JSValue val; - int ret, pass; - BOOL is_template; - JSAtom atom; - - if (p->class_id != JS_CLASS_ARRAY && - p->class_id != JS_CLASS_OBJECT) { - JS_ThrowTypeError(s->ctx, "unsupported object class"); - goto fail; - } - if (p->tmp_mark) { - JS_ThrowTypeError(s->ctx, "circular reference"); - goto fail; - } - p->tmp_mark = 1; - if (p->class_id == JS_CLASS_ARRAY) { - if (s->allow_bytecode && !p->extensible) { - /* not extensible array: we consider it is a - template when we are saving bytecode */ - bc_put_u8(s, BC_TAG_TEMPLATE_OBJECT); - is_template = TRUE; + int ret, idx; + + if (s->allow_reference) { + idx = js_object_list_find(s->ctx, &s->object_list, p); + if (idx >= 0) { + bc_put_u8(s, BC_TAG_OBJECT_REFERENCE); + bc_put_leb128(s, idx); + break; } else { - bc_put_u8(s, BC_TAG_ARRAY); - is_template = FALSE; - } - if (js_get_length32(s->ctx, &len, obj)) - goto fail1; - bc_put_leb128(s, len); - for(i = 0; i < len; i++) { - val = JS_GetPropertyUint32(s->ctx, obj, i); - if (JS_IsException(val)) - goto fail1; - ret = JS_WriteObjectRec(s, val); - JS_FreeValue(s->ctx, val); - if (ret) - goto fail1; - } - if (is_template) { - val = JS_GetProperty(s->ctx, obj, JS_ATOM_raw); - if (JS_IsException(val)) - goto fail1; - ret = JS_WriteObjectRec(s, val); - JS_FreeValue(s->ctx, val); - if (ret) - goto fail1; + if (js_object_list_add(s->ctx, &s->object_list, p)) + goto fail; } } else { - bc_put_u8(s, BC_TAG_OBJECT); - prop_count = 0; - sh = p->shape; - for(pass = 0; pass < 2; pass++) { - if (pass == 1) - bc_put_leb128(s, prop_count); - for(i = 0, pr = get_shape_prop(sh); i < sh->prop_count; i++, pr++) { - atom = pr->atom; - if (atom != JS_ATOM_NULL && - JS_AtomIsString(s->ctx, atom) && - (pr->flags & JS_PROP_ENUMERABLE)) { - if (pr->flags & JS_PROP_TMASK) { - JS_ThrowTypeError(s->ctx, "only value properties are supported"); - goto fail; - } - if (pass == 0) { - prop_count++; - } else { - bc_put_atom(s, atom); - if (JS_WriteObjectRec(s, p->prop[i].u.value)) { - fail1: - p->tmp_mark = 0; - goto fail; - } - } - } - } + if (p->tmp_mark) { + JS_ThrowTypeError(s->ctx, "circular reference"); + goto fail; + } + p->tmp_mark = 1; + } + switch(p->class_id) { + case JS_CLASS_ARRAY: + ret = JS_WriteArray(s, obj); + break; + case JS_CLASS_OBJECT: + ret = JS_WriteObjectTag(s, obj); + break; + case JS_CLASS_ARRAY_BUFFER: + ret = JS_WriteArrayBuffer(s, obj); + break; + case JS_CLASS_SHARED_ARRAY_BUFFER: + if (!s->allow_sab) + goto invalid_tag; + ret = JS_WriteSharedArrayBuffer(s, obj); + break; + case JS_CLASS_DATE: + bc_put_u8(s, BC_TAG_DATE); + ret = JS_WriteObjectRec(s, p->u.object_data); + break; + case JS_CLASS_NUMBER: + case JS_CLASS_STRING: + case JS_CLASS_BOOLEAN: +#ifdef CONFIG_BIGNUM + case JS_CLASS_BIG_INT: + case JS_CLASS_BIG_FLOAT: + case JS_CLASS_BIG_DECIMAL: +#endif + bc_put_u8(s, BC_TAG_OBJECT_VALUE); + ret = JS_WriteObjectRec(s, p->u.object_data); + break; + default: + if (p->class_id >= JS_CLASS_UINT8C_ARRAY && + p->class_id <= JS_CLASS_FLOAT64_ARRAY) { + ret = JS_WriteTypedArray(s, obj); + } else { + JS_ThrowTypeError(s->ctx, "unsupported object class"); + ret = -1; } + break; } p->tmp_mark = 0; + if (ret) + goto fail; } break; #ifdef CONFIG_BIGNUM @@ -33511,8 +34139,8 @@ static int JS_WriteObjectAtoms(BCWriterState *s) return -1; } -uint8_t *JS_WriteObject(JSContext *ctx, size_t *psize, JSValueConst obj, - int flags) +uint8_t *JS_WriteObject2(JSContext *ctx, size_t *psize, JSValueConst obj, + int flags, uint8_t ***psab_tab, size_t *psab_tab_len) { BCWriterState ss, *s = &ss; @@ -33521,29 +34149,48 @@ uint8_t *JS_WriteObject(JSContext *ctx, size_t *psize, JSValueConst obj, /* XXX: byte swapped output is untested */ s->byte_swap = ((flags & JS_WRITE_OBJ_BSWAP) != 0); s->allow_bytecode = ((flags & JS_WRITE_OBJ_BYTECODE) != 0); + s->allow_sab = ((flags & JS_WRITE_OBJ_SAB) != 0); + s->allow_reference = ((flags & JS_WRITE_OBJ_REFERENCE) != 0); /* XXX: could use a different version when bytecode is included */ if (s->allow_bytecode) s->first_atom = JS_ATOM_END; else s->first_atom = 1; js_dbuf_init(ctx, &s->dbuf); - + js_object_list_init(&s->object_list); + if (JS_WriteObjectRec(s, obj)) goto fail; if (JS_WriteObjectAtoms(s)) goto fail; + js_object_list_end(ctx, &s->object_list); js_free(ctx, s->atom_to_idx); js_free(ctx, s->idx_to_atom); *psize = s->dbuf.size; + if (psab_tab) + *psab_tab = s->sab_tab; + if (psab_tab_len) + *psab_tab_len = s->sab_tab_len; return s->dbuf.buf; fail: + js_object_list_end(ctx, &s->object_list); js_free(ctx, s->atom_to_idx); js_free(ctx, s->idx_to_atom); dbuf_free(&s->dbuf); *psize = 0; + if (psab_tab) + *psab_tab = NULL; + if (psab_tab_len) + *psab_tab_len = 0; return NULL; } +uint8_t *JS_WriteObject(JSContext *ctx, size_t *psize, JSValueConst obj, + int flags) +{ + return JS_WriteObject2(ctx, psize, obj, flags, NULL, NULL); +} + typedef struct BCReaderState { JSContext *ctx; const uint8_t *buf_start, *ptr, *buf_end; @@ -33551,8 +34198,15 @@ typedef struct BCReaderState { uint32_t idx_to_atom_count; JSAtom *idx_to_atom; int error_state; - BOOL allow_bytecode; - BOOL is_rom_data; + BOOL allow_sab : 8; + BOOL allow_bytecode : 8; + BOOL is_rom_data : 8; + BOOL allow_reference : 8; + /* object references */ + JSObject **objects; + int objects_count; + int objects_size; + #ifdef DUMP_READ_OBJECT const uint8_t *ptr_last; int level; @@ -33756,7 +34410,7 @@ static JSString *JS_ReadString(BCReaderState *s) p->u.str8[size] = '\0'; /* add the trailing zero for 8 bit strings */ } #ifdef DUMP_READ_OBJECT - bc_read_trace(s, "string: "); JS_DumpString(s->ctx->rt, p); printf("\n"); + JS_DumpString(s->ctx->rt, p); printf("\n"); #endif return p; } @@ -33838,8 +34492,6 @@ static JSValue JS_ReadBigNum(BCReaderState *s, int tag) bf_t *a; int bpos, d; - bc_read_trace(s, "%s {\n", bc_tag_str[tag]); - p = js_new_bf(s->ctx); if (!p) goto fail; @@ -33961,28 +34613,560 @@ static JSValue JS_ReadBigNum(BCReaderState *s, int tag) } #endif /* CONFIG_BIGNUM */ +static JSValue JS_ReadObjectRec(BCReaderState *s); + +static int BC_add_object_ref1(BCReaderState *s, JSObject *p) +{ + if (s->allow_reference) { + if (js_resize_array(s->ctx, (void *)&s->objects, + sizeof(s->objects[0]), + &s->objects_size, s->objects_count + 1)) + return -1; + s->objects[s->objects_count++] = p; + } + return 0; +} + +static int BC_add_object_ref(BCReaderState *s, JSValueConst obj) +{ + return BC_add_object_ref1(s, JS_VALUE_GET_OBJ(obj)); +} + +static JSValue JS_ReadFunctionTag(BCReaderState *s) +{ + JSContext *ctx = s->ctx; + JSFunctionBytecode bc, *b; + JSValue obj = JS_UNDEFINED; + uint16_t v16; + uint8_t v8; + int idx, i, local_count; + int function_size, cpool_offset, byte_code_offset; + int closure_var_offset, vardefs_offset; + + memset(&bc, 0, sizeof(bc)); + bc.header.ref_count = 1; + //bc.gc_header.mark = 0; + + if (bc_get_u16(s, &v16)) + goto fail; + idx = 0; + bc.has_prototype = bc_get_flags(v16, &idx, 1); + bc.has_simple_parameter_list = bc_get_flags(v16, &idx, 1); + bc.is_derived_class_constructor = bc_get_flags(v16, &idx, 1); + bc.need_home_object = bc_get_flags(v16, &idx, 1); + bc.func_kind = bc_get_flags(v16, &idx, 2); + bc.new_target_allowed = bc_get_flags(v16, &idx, 1); + bc.super_call_allowed = bc_get_flags(v16, &idx, 1); + bc.super_allowed = bc_get_flags(v16, &idx, 1); + bc.arguments_allowed = bc_get_flags(v16, &idx, 1); + bc.has_debug = bc_get_flags(v16, &idx, 1); + bc.backtrace_barrier = bc_get_flags(v16, &idx, 1); + bc.read_only_bytecode = s->is_rom_data; + if (bc_get_u8(s, &v8)) + goto fail; + bc.js_mode = v8; + if (bc_get_atom(s, &bc.func_name)) //@ atom leak if failure + goto fail; + if (bc_get_leb128_u16(s, &bc.arg_count)) + goto fail; + if (bc_get_leb128_u16(s, &bc.var_count)) + goto fail; + if (bc_get_leb128_u16(s, &bc.defined_arg_count)) + goto fail; + if (bc_get_leb128_u16(s, &bc.stack_size)) + goto fail; + if (bc_get_leb128_int(s, &bc.closure_var_count)) + goto fail; + if (bc_get_leb128_int(s, &bc.cpool_count)) + goto fail; + if (bc_get_leb128_int(s, &bc.byte_code_len)) + goto fail; + if (bc_get_leb128_int(s, &local_count)) + goto fail; + + if (bc.has_debug) { + function_size = sizeof(*b); + } else { + function_size = offsetof(JSFunctionBytecode, debug); + } + cpool_offset = function_size; + function_size += bc.cpool_count * sizeof(*bc.cpool); + vardefs_offset = function_size; + function_size += local_count * sizeof(*bc.vardefs); + closure_var_offset = function_size; + function_size += bc.closure_var_count * sizeof(*bc.closure_var); + byte_code_offset = function_size; + if (!bc.read_only_bytecode) { + function_size += bc.byte_code_len; + } + + b = js_mallocz(ctx, function_size); + if (!b) + return JS_EXCEPTION; + + memcpy(b, &bc, offsetof(JSFunctionBytecode, debug)); + b->header.ref_count = 1; + add_gc_object(ctx->rt, &b->header, JS_GC_OBJ_TYPE_FUNCTION_BYTECODE); + + obj = JS_MKPTR(JS_TAG_FUNCTION_BYTECODE, b); + +#ifdef DUMP_READ_OBJECT + bc_read_trace(s, "name: "); print_atom(s->ctx, b->func_name); printf("\n"); +#endif + bc_read_trace(s, "args=%d vars=%d defargs=%d closures=%d cpool=%d\n", + b->arg_count, b->var_count, b->defined_arg_count, + b->closure_var_count, b->cpool_count); + bc_read_trace(s, "stack=%d bclen=%d locals=%d\n", + b->stack_size, b->byte_code_len, local_count); + + if (local_count != 0) { + bc_read_trace(s, "vars {\n"); + b->vardefs = (void *)((uint8_t*)b + vardefs_offset); + for(i = 0; i < local_count; i++) { + JSVarDef *vd = &b->vardefs[i]; + if (bc_get_atom(s, &vd->var_name)) + goto fail; + if (bc_get_leb128_int(s, &vd->scope_level)) + goto fail; + if (bc_get_leb128_int(s, &vd->scope_next)) + goto fail; + vd->scope_next--; + if (bc_get_u8(s, &v8)) + goto fail; + idx = 0; + vd->var_kind = bc_get_flags(v8, &idx, 4); + vd->is_func_var = bc_get_flags(v8, &idx, 1); + vd->is_const = bc_get_flags(v8, &idx, 1); + vd->is_lexical = bc_get_flags(v8, &idx, 1); + vd->is_captured = bc_get_flags(v8, &idx, 1); +#ifdef DUMP_READ_OBJECT + bc_read_trace(s, "name: "); print_atom(s->ctx, vd->var_name); printf("\n"); +#endif + } + bc_read_trace(s, "}\n"); + } + if (b->closure_var_count != 0) { + bc_read_trace(s, "closure vars {\n"); + b->closure_var = (void *)((uint8_t*)b + closure_var_offset); + for(i = 0; i < b->closure_var_count; i++) { + JSClosureVar *cv = &b->closure_var[i]; + int var_idx; + if (bc_get_atom(s, &cv->var_name)) + goto fail; + if (bc_get_leb128_int(s, &var_idx)) + goto fail; + cv->var_idx = var_idx; + if (bc_get_u8(s, &v8)) + goto fail; + idx = 0; + cv->is_local = bc_get_flags(v8, &idx, 1); + cv->is_arg = bc_get_flags(v8, &idx, 1); + cv->is_const = bc_get_flags(v8, &idx, 1); + cv->is_lexical = bc_get_flags(v8, &idx, 1); + cv->var_kind = bc_get_flags(v8, &idx, 4); +#ifdef DUMP_READ_OBJECT + bc_read_trace(s, "name: "); print_atom(s->ctx, cv->var_name); printf("\n"); +#endif + } + bc_read_trace(s, "}\n"); + } + { + bc_read_trace(s, "bytecode {\n"); + if (JS_ReadFunctionBytecode(s, b, byte_code_offset, b->byte_code_len)) + goto fail; + bc_read_trace(s, "}\n"); + } + if (b->has_debug) { + /* read optional debug information */ + bc_read_trace(s, "debug {\n"); + if (bc_get_atom(s, &b->debug.filename)) + goto fail; + if (bc_get_leb128_int(s, &b->debug.line_num)) + goto fail; + if (bc_get_leb128_int(s, &b->debug.pc2line_len)) + goto fail; + if (b->debug.pc2line_len) { + b->debug.pc2line_buf = js_mallocz(ctx, b->debug.pc2line_len); + if (!b->debug.pc2line_buf) + goto fail; + if (bc_get_buf(s, b->debug.pc2line_buf, b->debug.pc2line_len)) + goto fail; + } +#ifdef DUMP_READ_OBJECT + bc_read_trace(s, "filename: "); print_atom(s->ctx, b->debug.filename); printf("\n"); +#endif + bc_read_trace(s, "}\n"); + } + if (b->cpool_count != 0) { + bc_read_trace(s, "cpool {\n"); + b->cpool = (void *)((uint8_t*)b + cpool_offset); + for(i = 0; i < b->cpool_count; i++) { + JSValue val; + val = JS_ReadObjectRec(s); + if (JS_IsException(val)) + goto fail; + b->cpool[i] = val; + } + bc_read_trace(s, "}\n"); + } + b->realm = JS_DupContext(ctx); + return obj; + fail: + JS_FreeValue(ctx, obj); + return JS_EXCEPTION; +} + +static JSValue JS_ReadModule(BCReaderState *s) +{ + JSContext *ctx = s->ctx; + JSValue obj; + JSModuleDef *m = NULL; + JSAtom module_name; + int i; + uint8_t v8; + + if (bc_get_atom(s, &module_name)) + goto fail; +#ifdef DUMP_READ_OBJECT + bc_read_trace(s, "name: "); print_atom(s->ctx, module_name); printf("\n"); +#endif + m = js_new_module_def(ctx, module_name); + if (!m) + goto fail; + obj = JS_DupValue(ctx, JS_MKPTR(JS_TAG_MODULE, m)); + if (bc_get_leb128_int(s, &m->req_module_entries_count)) + goto fail; + if (m->req_module_entries_count != 0) { + m->req_module_entries_size = m->req_module_entries_count; + m->req_module_entries = js_mallocz(ctx, sizeof(m->req_module_entries[0]) * m->req_module_entries_size); + if (!m->req_module_entries) + goto fail; + for(i = 0; i < m->req_module_entries_count; i++) { + JSReqModuleEntry *rme = &m->req_module_entries[i]; + if (bc_get_atom(s, &rme->module_name)) + goto fail; + } + } + + if (bc_get_leb128_int(s, &m->export_entries_count)) + goto fail; + if (m->export_entries_count != 0) { + m->export_entries_size = m->export_entries_count; + m->export_entries = js_mallocz(ctx, sizeof(m->export_entries[0]) * m->export_entries_size); + if (!m->export_entries) + goto fail; + for(i = 0; i < m->export_entries_count; i++) { + JSExportEntry *me = &m->export_entries[i]; + if (bc_get_u8(s, &v8)) + goto fail; + me->export_type = v8; + if (me->export_type == JS_EXPORT_TYPE_LOCAL) { + if (bc_get_leb128_int(s, &me->u.local.var_idx)) + goto fail; + } else { + if (bc_get_leb128_int(s, &me->u.req_module_idx)) + goto fail; + if (bc_get_atom(s, &me->local_name)) + goto fail; + } + if (bc_get_atom(s, &me->export_name)) + goto fail; + } + } + + if (bc_get_leb128_int(s, &m->star_export_entries_count)) + goto fail; + if (m->star_export_entries_count != 0) { + m->star_export_entries_size = m->star_export_entries_count; + m->star_export_entries = js_mallocz(ctx, sizeof(m->star_export_entries[0]) * m->star_export_entries_size); + if (!m->star_export_entries) + goto fail; + for(i = 0; i < m->star_export_entries_count; i++) { + JSStarExportEntry *se = &m->star_export_entries[i]; + if (bc_get_leb128_int(s, &se->req_module_idx)) + goto fail; + } + } + + if (bc_get_leb128_int(s, &m->import_entries_count)) + goto fail; + if (m->import_entries_count != 0) { + m->import_entries_size = m->import_entries_count; + m->import_entries = js_mallocz(ctx, sizeof(m->import_entries[0]) * m->import_entries_size); + if (!m->import_entries) + goto fail; + for(i = 0; i < m->import_entries_count; i++) { + JSImportEntry *mi = &m->import_entries[i]; + if (bc_get_leb128_int(s, &mi->var_idx)) + goto fail; + if (bc_get_atom(s, &mi->import_name)) + goto fail; + if (bc_get_leb128_int(s, &mi->req_module_idx)) + goto fail; + } + } + + m->func_obj = JS_ReadObjectRec(s); + if (JS_IsException(m->func_obj)) + goto fail; + return obj; + fail: + if (m) { + js_free_module_def(ctx, m); + } + return JS_EXCEPTION; +} + +static JSValue JS_ReadObjectTag(BCReaderState *s) +{ + JSContext *ctx = s->ctx; + JSValue obj; + uint32_t prop_count, i; + JSAtom atom; + JSValue val; + int ret; + + obj = JS_NewObject(ctx); + if (BC_add_object_ref(s, obj)) + goto fail; + if (bc_get_leb128(s, &prop_count)) + goto fail; + for(i = 0; i < prop_count; i++) { + if (bc_get_atom(s, &atom)) + goto fail; +#ifdef DUMP_READ_OBJECT + bc_read_trace(s, "propname: "); print_atom(s->ctx, atom); printf("\n"); +#endif + val = JS_ReadObjectRec(s); + if (JS_IsException(val)) { + JS_FreeAtom(ctx, atom); + goto fail; + } + ret = JS_DefinePropertyValue(ctx, obj, atom, val, JS_PROP_C_W_E); + JS_FreeAtom(ctx, atom); + if (ret < 0) + goto fail; + } + return obj; + fail: + JS_FreeValue(ctx, obj); + return JS_EXCEPTION; +} + +static JSValue JS_ReadArray(BCReaderState *s, int tag) +{ + JSContext *ctx = s->ctx; + JSValue obj; + uint32_t len, i; + JSValue val; + int ret, prop_flags; + BOOL is_template; + + obj = JS_NewArray(ctx); + if (BC_add_object_ref(s, obj)) + goto fail; + is_template = (tag == BC_TAG_TEMPLATE_OBJECT); + if (bc_get_leb128(s, &len)) + goto fail; + for(i = 0; i < len; i++) { + val = JS_ReadObjectRec(s); + if (JS_IsException(val)) + goto fail; + if (is_template) + prop_flags = JS_PROP_ENUMERABLE; + else + prop_flags = JS_PROP_C_W_E; + ret = JS_DefinePropertyValueUint32(ctx, obj, i, val, + prop_flags); + if (ret < 0) + goto fail; + } + if (is_template) { + val = JS_ReadObjectRec(s); + if (JS_IsException(val)) + goto fail; + if (!JS_IsUndefined(val)) { + ret = JS_DefinePropertyValue(ctx, obj, JS_ATOM_raw, val, 0); + if (ret < 0) + goto fail; + } + JS_PreventExtensions(ctx, obj); + } + return obj; + fail: + JS_FreeValue(ctx, obj); + return JS_EXCEPTION; +} + +static JSValue JS_ReadTypedArray(BCReaderState *s) +{ + JSContext *ctx = s->ctx; + JSValue obj = JS_UNDEFINED, array_buffer = JS_UNDEFINED; + uint8_t array_tag; + JSValueConst args[3]; + uint32_t offset, len, idx; + + if (bc_get_u8(s, &array_tag)) + return JS_EXCEPTION; + if (array_tag >= JS_TYPED_ARRAY_COUNT) + return JS_ThrowTypeError(ctx, "invalid typed array"); + if (bc_get_leb128(s, &len)) + return JS_EXCEPTION; + if (bc_get_leb128(s, &offset)) + return JS_EXCEPTION; + /* XXX: this hack could be avoided if the typed array could be + created before the array buffer */ + idx = s->objects_count; + if (BC_add_object_ref1(s, NULL)) + goto fail; + array_buffer = JS_ReadObjectRec(s); + if (JS_IsException(array_buffer)) + return JS_EXCEPTION; + if (!js_get_array_buffer(ctx, array_buffer)) { + JS_FreeValue(ctx, array_buffer); + return JS_EXCEPTION; + } + args[0] = array_buffer; + args[1] = JS_NewInt64(ctx, offset); + args[2] = JS_NewInt64(ctx, len); + obj = js_typed_array_constructor(ctx, JS_UNDEFINED, + 3, args, + JS_CLASS_UINT8C_ARRAY + array_tag); + if (JS_IsException(obj)) + goto fail; + if (s->allow_reference) { + s->objects[idx] = JS_VALUE_GET_OBJ(obj); + } + JS_FreeValue(ctx, array_buffer); + return obj; + fail: + JS_FreeValue(ctx, array_buffer); + JS_FreeValue(ctx, obj); + return JS_EXCEPTION; +} + +static JSValue JS_ReadArrayBuffer(BCReaderState *s) +{ + JSContext *ctx = s->ctx; + uint32_t byte_length; + JSValue obj; + + if (bc_get_leb128(s, &byte_length)) + return JS_EXCEPTION; + if (unlikely(s->buf_end - s->ptr < byte_length)) { + bc_read_error_end(s); + return JS_EXCEPTION; + } + obj = JS_NewArrayBufferCopy(ctx, s->ptr, byte_length); + if (JS_IsException(obj)) + goto fail; + if (BC_add_object_ref(s, obj)) + goto fail; + s->ptr += byte_length; + return obj; + fail: + JS_FreeValue(ctx, obj); + return JS_EXCEPTION; +} + +static JSValue JS_ReadSharedArrayBuffer(BCReaderState *s) +{ + JSContext *ctx = s->ctx; + uint32_t byte_length; + uint8_t *data_ptr; + JSValue obj; + uint64_t u64; + + if (bc_get_leb128(s, &byte_length)) + return JS_EXCEPTION; + if (bc_get_u64(s, &u64)) + return JS_EXCEPTION; + data_ptr = (uint8_t *)(uintptr_t)u64; + /* the SharedArrayBuffer is cloned */ + obj = js_array_buffer_constructor3(ctx, JS_UNDEFINED, byte_length, + JS_CLASS_SHARED_ARRAY_BUFFER, + data_ptr, + NULL, NULL, FALSE); + if (JS_IsException(obj)) + goto fail; + if (BC_add_object_ref(s, obj)) + goto fail; + return obj; + fail: + JS_FreeValue(ctx, obj); + return JS_EXCEPTION; +} + +static JSValue JS_ReadDate(BCReaderState *s) +{ + JSContext *ctx = s->ctx; + JSValue val, obj = JS_UNDEFINED; + + val = JS_ReadObjectRec(s); + if (JS_IsException(val)) + goto fail; + if (!JS_IsNumber(val)) { + JS_ThrowTypeError(ctx, "Number tag expected for date"); + goto fail; + } + obj = JS_NewObjectProtoClass(ctx, ctx->class_proto[JS_CLASS_DATE], + JS_CLASS_DATE); + if (JS_IsException(obj)) + goto fail; + if (BC_add_object_ref(s, obj)) + goto fail; + JS_SetObjectData(ctx, obj, val); + return obj; + fail: + JS_FreeValue(ctx, val); + JS_FreeValue(ctx, obj); + return JS_EXCEPTION; +} + +static JSValue JS_ReadObjectValue(BCReaderState *s) +{ + JSContext *ctx = s->ctx; + JSValue val, obj = JS_UNDEFINED; + + val = JS_ReadObjectRec(s); + if (JS_IsException(val)) + goto fail; + obj = JS_ToObject(ctx, val); + if (JS_IsException(obj)) + goto fail; + if (BC_add_object_ref(s, obj)) + goto fail; + JS_FreeValue(ctx, val); + return obj; + fail: + JS_FreeValue(ctx, val); + JS_FreeValue(ctx, obj); + return JS_EXCEPTION; +} + static JSValue JS_ReadObjectRec(BCReaderState *s) { JSContext *ctx = s->ctx; uint8_t tag; JSValue obj = JS_UNDEFINED; - JSModuleDef *m = NULL; + + if (js_check_stack_overflow(ctx->rt, 0)) + return JS_ThrowStackOverflow(ctx); if (bc_get_u8(s, &tag)) return JS_EXCEPTION; + bc_read_trace(s, "%s {\n", bc_tag_str[tag]); + switch(tag) { case BC_TAG_NULL: - bc_read_trace(s, "null\n"); obj = JS_NULL; break; case BC_TAG_UNDEFINED: - bc_read_trace(s, "undefined\n"); obj = JS_UNDEFINED; break; case BC_TAG_BOOL_FALSE: case BC_TAG_BOOL_TRUE: - bc_read_trace(s, "%s\n", bc_tag_str[tag]); obj = JS_NewBool(ctx, tag - BC_TAG_BOOL_FALSE); break; case BC_TAG_INT32: @@ -33990,7 +35174,7 @@ static JSValue JS_ReadObjectRec(BCReaderState *s) int32_t val; if (bc_get_sleb128(s, &val)) return JS_EXCEPTION; - bc_read_trace(s, "int32 %d\n", val); + bc_read_trace(s, "%d\n", val); obj = JS_NewInt32(ctx, val); } break; @@ -33999,7 +35183,7 @@ static JSValue JS_ReadObjectRec(BCReaderState *s) JSFloat64Union u; if (bc_get_u64(s, &u.u64)) return JS_EXCEPTION; - bc_read_trace(s, "float64 %g\n", u.d); + bc_read_trace(s, "%g\n", u.d); obj = __JS_NewFloat64(ctx, u.d); } break; @@ -34013,378 +35197,68 @@ static JSValue JS_ReadObjectRec(BCReaderState *s) } break; case BC_TAG_FUNCTION_BYTECODE: - { - JSFunctionBytecode bc, *b; - uint16_t v16; - uint8_t v8; - int idx, i, local_count; - int function_size, cpool_offset, byte_code_offset; - int closure_var_offset, vardefs_offset; - - if (!s->allow_bytecode) - goto invalid_tag; - bc_read_trace(s, "%s {\n", bc_tag_str[tag]); - - memset(&bc, 0, sizeof(bc)); - bc.header.ref_count = 1; - //bc.gc_header.mark = 0; - - if (bc_get_u16(s, &v16)) - goto fail; - idx = 0; - bc.has_prototype = bc_get_flags(v16, &idx, 1); - bc.has_simple_parameter_list = bc_get_flags(v16, &idx, 1); - bc.is_derived_class_constructor = bc_get_flags(v16, &idx, 1); - bc.need_home_object = bc_get_flags(v16, &idx, 1); - bc.func_kind = bc_get_flags(v16, &idx, 2); - bc.new_target_allowed = bc_get_flags(v16, &idx, 1); - bc.super_call_allowed = bc_get_flags(v16, &idx, 1); - bc.super_allowed = bc_get_flags(v16, &idx, 1); - bc.arguments_allowed = bc_get_flags(v16, &idx, 1); - bc.has_debug = bc_get_flags(v16, &idx, 1); - bc.backtrace_barrier = bc_get_flags(v16, &idx, 1); - bc.read_only_bytecode = s->is_rom_data; - if (bc_get_u8(s, &v8)) - goto fail; - bc.js_mode = v8; - if (bc_get_atom(s, &bc.func_name)) //@ atom leak if failure - goto fail; - if (bc_get_leb128_u16(s, &bc.arg_count)) - goto fail; - if (bc_get_leb128_u16(s, &bc.var_count)) - goto fail; - if (bc_get_leb128_u16(s, &bc.defined_arg_count)) - goto fail; - if (bc_get_leb128_u16(s, &bc.stack_size)) - goto fail; - if (bc_get_leb128_int(s, &bc.closure_var_count)) - goto fail; - if (bc_get_leb128_int(s, &bc.cpool_count)) - goto fail; - if (bc_get_leb128_int(s, &bc.byte_code_len)) - goto fail; - if (bc_get_leb128_int(s, &local_count)) - goto fail; - - if (bc.has_debug) { - function_size = sizeof(*b); - } else { - function_size = offsetof(JSFunctionBytecode, debug); - } - cpool_offset = function_size; - function_size += bc.cpool_count * sizeof(*bc.cpool); - vardefs_offset = function_size; - function_size += local_count * sizeof(*bc.vardefs); - closure_var_offset = function_size; - function_size += bc.closure_var_count * sizeof(*bc.closure_var); - byte_code_offset = function_size; - if (!bc.read_only_bytecode) { - function_size += bc.byte_code_len; - } - - b = js_mallocz(ctx, function_size); - if (!b) - return JS_EXCEPTION; - - memcpy(b, &bc, offsetof(JSFunctionBytecode, debug)); - b->header.ref_count = 1; - add_gc_object(ctx->rt, &b->header, JS_GC_OBJ_TYPE_FUNCTION_BYTECODE); - - obj = JS_MKPTR(JS_TAG_FUNCTION_BYTECODE, b); - -#ifdef DUMP_READ_OBJECT - bc_read_trace(s, "name: "); print_atom(s->ctx, b->func_name); printf("\n"); -#endif - bc_read_trace(s, "args=%d vars=%d defargs=%d closures=%d cpool=%d\n", - b->arg_count, b->var_count, b->defined_arg_count, - b->closure_var_count, b->cpool_count); - bc_read_trace(s, "stack=%d bclen=%d locals=%d\n", - b->stack_size, b->byte_code_len, local_count); - - if (local_count != 0) { - bc_read_trace(s, "vars {\n"); - b->vardefs = (void *)((uint8_t*)b + vardefs_offset); - for(i = 0; i < local_count; i++) { - JSVarDef *vd = &b->vardefs[i]; - if (bc_get_atom(s, &vd->var_name)) - goto fail; - if (bc_get_leb128_int(s, &vd->scope_level)) - goto fail; - if (bc_get_leb128_int(s, &vd->scope_next)) - goto fail; - vd->scope_next--; - if (bc_get_u8(s, &v8)) - goto fail; - idx = 0; - vd->var_kind = bc_get_flags(v8, &idx, 4); - vd->is_func_var = bc_get_flags(v8, &idx, 1); - vd->is_const = bc_get_flags(v8, &idx, 1); - vd->is_lexical = bc_get_flags(v8, &idx, 1); - vd->is_captured = bc_get_flags(v8, &idx, 1); -#ifdef DUMP_READ_OBJECT - bc_read_trace(s, "name: "); print_atom(s->ctx, vd->var_name); printf("\n"); -#endif - } - bc_read_trace(s, "}\n"); - } - if (b->closure_var_count != 0) { - bc_read_trace(s, "closure vars {\n"); - b->closure_var = (void *)((uint8_t*)b + closure_var_offset); - for(i = 0; i < b->closure_var_count; i++) { - JSClosureVar *cv = &b->closure_var[i]; - int var_idx; - if (bc_get_atom(s, &cv->var_name)) - goto fail; - if (bc_get_leb128_int(s, &var_idx)) - goto fail; - cv->var_idx = var_idx; - if (bc_get_u8(s, &v8)) - goto fail; - idx = 0; - cv->is_local = bc_get_flags(v8, &idx, 1); - cv->is_arg = bc_get_flags(v8, &idx, 1); - cv->is_const = bc_get_flags(v8, &idx, 1); - cv->is_lexical = bc_get_flags(v8, &idx, 1); - cv->var_kind = bc_get_flags(v8, &idx, 4); -#ifdef DUMP_READ_OBJECT - bc_read_trace(s, "name: "); print_atom(s->ctx, cv->var_name); printf("\n"); -#endif - } - bc_read_trace(s, "}\n"); - } - { - bc_read_trace(s, "bytecode {\n"); - if (JS_ReadFunctionBytecode(s, b, byte_code_offset, b->byte_code_len)) - goto fail; - bc_read_trace(s, "}\n"); - } - if (b->has_debug) { - /* read optional debug information */ - bc_read_trace(s, "debug {\n"); - if (bc_get_atom(s, &b->debug.filename)) - goto fail; - if (bc_get_leb128_int(s, &b->debug.line_num)) - goto fail; - if (bc_get_leb128_int(s, &b->debug.pc2line_len)) - goto fail; - if (b->debug.pc2line_len) { - b->debug.pc2line_buf = js_mallocz(ctx, b->debug.pc2line_len); - if (!b->debug.pc2line_buf) - goto fail; - if (bc_get_buf(s, b->debug.pc2line_buf, b->debug.pc2line_len)) - goto fail; - } -#ifdef DUMP_READ_OBJECT - bc_read_trace(s, "filename: "); print_atom(s->ctx, b->debug.filename); printf("\n"); -#endif - bc_read_trace(s, "}\n"); - } - if (b->cpool_count != 0) { - bc_read_trace(s, "cpool {\n"); - b->cpool = (void *)((uint8_t*)b + cpool_offset); - for(i = 0; i < b->cpool_count; i++) { - JSValue val; - val = JS_ReadObjectRec(s); - if (JS_IsException(val)) - goto fail; - b->cpool[i] = val; - } - bc_read_trace(s, "}\n"); - } - bc_read_trace(s, "}\n"); - b->realm = JS_DupContext(ctx); - } + if (!s->allow_bytecode) + goto invalid_tag; + obj = JS_ReadFunctionTag(s); break; case BC_TAG_MODULE: - { - JSAtom module_name; - int i; - uint8_t v8; - - if (!s->allow_bytecode) - goto invalid_tag; - bc_read_trace(s, "%s {\n", bc_tag_str[tag]); - if (bc_get_atom(s, &module_name)) - goto fail; -#ifdef DUMP_READ_OBJECT - bc_read_trace(s, "name: "); print_atom(s->ctx, module_name); printf("\n"); -#endif - m = js_new_module_def(ctx, module_name); - if (!m) - goto fail; - obj = JS_DupValue(ctx, JS_MKPTR(JS_TAG_MODULE, m)); - if (bc_get_leb128_int(s, &m->req_module_entries_count)) - goto fail; - if (m->req_module_entries_count != 0) { - m->req_module_entries_size = m->req_module_entries_count; - m->req_module_entries = js_mallocz(ctx, sizeof(m->req_module_entries[0]) * m->req_module_entries_size); - if (!m->req_module_entries) - goto fail; - for(i = 0; i < m->req_module_entries_count; i++) { - JSReqModuleEntry *rme = &m->req_module_entries[i]; - if (bc_get_atom(s, &rme->module_name)) - goto fail; - } - } - - if (bc_get_leb128_int(s, &m->export_entries_count)) - goto fail; - if (m->export_entries_count != 0) { - m->export_entries_size = m->export_entries_count; - m->export_entries = js_mallocz(ctx, sizeof(m->export_entries[0]) * m->export_entries_size); - if (!m->export_entries) - goto fail; - for(i = 0; i < m->export_entries_count; i++) { - JSExportEntry *me = &m->export_entries[i]; - if (bc_get_u8(s, &v8)) - goto fail; - me->export_type = v8; - if (me->export_type == JS_EXPORT_TYPE_LOCAL) { - if (bc_get_leb128_int(s, &me->u.local.var_idx)) - goto fail; - } else { - if (bc_get_leb128_int(s, &me->u.req_module_idx)) - goto fail; - if (bc_get_atom(s, &me->local_name)) - goto fail; - } - if (bc_get_atom(s, &me->export_name)) - goto fail; - } - } - - if (bc_get_leb128_int(s, &m->star_export_entries_count)) - goto fail; - if (m->star_export_entries_count != 0) { - m->star_export_entries_size = m->star_export_entries_count; - m->star_export_entries = js_mallocz(ctx, sizeof(m->star_export_entries[0]) * m->star_export_entries_size); - if (!m->star_export_entries) - goto fail; - for(i = 0; i < m->star_export_entries_count; i++) { - JSStarExportEntry *se = &m->star_export_entries[i]; - if (bc_get_leb128_int(s, &se->req_module_idx)) - goto fail; - } - } - - if (bc_get_leb128_int(s, &m->import_entries_count)) - goto fail; - if (m->import_entries_count != 0) { - m->import_entries_size = m->import_entries_count; - m->import_entries = js_mallocz(ctx, sizeof(m->import_entries[0]) * m->import_entries_size); - if (!m->import_entries) - goto fail; - for(i = 0; i < m->import_entries_count; i++) { - JSImportEntry *mi = &m->import_entries[i]; - if (bc_get_leb128_int(s, &mi->var_idx)) - goto fail; - if (bc_get_atom(s, &mi->import_name)) - goto fail; - if (bc_get_leb128_int(s, &mi->req_module_idx)) - goto fail; - } - } - - m->func_obj = JS_ReadObjectRec(s); - if (JS_IsException(m->func_obj)) - goto fail; - bc_read_trace(s, "}\n"); - } + if (!s->allow_bytecode) + goto invalid_tag; + obj = JS_ReadModule(s); break; case BC_TAG_OBJECT: - { - uint32_t prop_count, i; - JSAtom atom; - JSValue val; - int ret; - - bc_read_trace(s, "%s {\n", bc_tag_str[tag]); - - obj = JS_NewObject(ctx); - if (bc_get_leb128(s, &prop_count)) - goto fail; - for(i = 0; i < prop_count; i++) { - if (bc_get_atom(s, &atom)) - goto fail; -#ifdef DUMP_READ_OBJECT - bc_read_trace(s, "propname: "); print_atom(s->ctx, atom); printf("\n"); -#endif - val = JS_ReadObjectRec(s); - if (JS_IsException(val)) { - JS_FreeAtom(ctx, atom); - goto fail; - } - ret = JS_DefinePropertyValue(ctx, obj, atom, val, JS_PROP_C_W_E); - JS_FreeAtom(ctx, atom); - if (ret < 0) - goto fail; - } - bc_read_trace(s, "}\n"); - } + obj = JS_ReadObjectTag(s); break; case BC_TAG_ARRAY: case BC_TAG_TEMPLATE_OBJECT: - { - uint32_t len, i; - JSValue val; - int ret, prop_flags; - BOOL is_template; - - bc_read_trace(s, "%s {\n", bc_tag_str[tag]); - - obj = JS_NewArray(ctx); - is_template = (tag == BC_TAG_TEMPLATE_OBJECT); - if (bc_get_leb128(s, &len)) - goto fail; - for(i = 0; i < len; i++) { - val = JS_ReadObjectRec(s); - if (JS_IsException(val)) - goto fail; - if (is_template) - prop_flags = JS_PROP_ENUMERABLE; - else - prop_flags = JS_PROP_C_W_E; - ret = JS_DefinePropertyValueUint32(ctx, obj, i, val, - prop_flags); - if (ret < 0) - goto fail; - } - if (is_template) { - val = JS_ReadObjectRec(s); - if (JS_IsException(val)) - goto fail; - if (!JS_IsUndefined(val)) { - ret = JS_DefinePropertyValue(ctx, obj, JS_ATOM_raw, val, 0); - if (ret < 0) - goto fail; - } - JS_PreventExtensions(ctx, obj); - } - bc_read_trace(s, "}\n"); - } + obj = JS_ReadArray(s, tag); + break; + case BC_TAG_TYPED_ARRAY: + obj = JS_ReadTypedArray(s); + break; + case BC_TAG_ARRAY_BUFFER: + obj = JS_ReadArrayBuffer(s); + break; + case BC_TAG_SHARED_ARRAY_BUFFER: + if (!s->allow_sab || !ctx->rt->sab_funcs.sab_dup) + goto invalid_tag; + obj = JS_ReadSharedArrayBuffer(s); + break; + case BC_TAG_DATE: + obj = JS_ReadDate(s); + break; + case BC_TAG_OBJECT_VALUE: + obj = JS_ReadObjectValue(s); break; #ifdef CONFIG_BIGNUM case BC_TAG_BIG_INT: case BC_TAG_BIG_FLOAT: case BC_TAG_BIG_DECIMAL: obj = JS_ReadBigNum(s, tag); - if (JS_IsException(obj)) - goto fail; break; #endif + case BC_TAG_OBJECT_REFERENCE: + { + uint32_t val; + if (!s->allow_reference) + return JS_ThrowSyntaxError(ctx, "object references are not allowed"); + if (bc_get_leb128(s, &val)) + return JS_EXCEPTION; + bc_read_trace(s, "%u\n", val); + if (val >= s->objects_count) { + return JS_ThrowSyntaxError(ctx, "invalid object reference (%u >= %u)", + val, s->objects_count); + } + obj = JS_DupValue(ctx, JS_MKPTR(JS_TAG_OBJECT, s->objects[val])); + } + break; default: invalid_tag: return JS_ThrowSyntaxError(ctx, "invalid tag (tag=%d pos=%u)", tag, (unsigned int)(s->ptr - s->buf_start)); } + bc_read_trace(s, "}\n"); return obj; - fail: - if (m) { - js_free_module_def(ctx, m); - } else { - JS_FreeValue(ctx, obj); - } - return JS_EXCEPTION; } static int JS_ReadObjectAtoms(BCReaderState *s) @@ -34437,10 +35311,11 @@ static void bc_reader_free(BCReaderState *s) } js_free(s->ctx, s->idx_to_atom); } + js_free(s->ctx, s->objects); } JSValue JS_ReadObject(JSContext *ctx, const uint8_t *buf, size_t buf_len, - int flags) + int flags) { BCReaderState ss, *s = &ss; JSValue obj; @@ -34455,6 +35330,8 @@ JSValue JS_ReadObject(JSContext *ctx, const uint8_t *buf, size_t buf_len, s->ptr = buf; s->allow_bytecode = ((flags & JS_READ_OBJ_BYTECODE) != 0); s->is_rom_data = ((flags & JS_READ_OBJ_ROM_DATA) != 0); + s->allow_sab = ((flags & JS_READ_OBJ_SAB) != 0); + s->allow_reference = ((flags & JS_READ_OBJ_REFERENCE) != 0); if (s->allow_bytecode) s->first_atom = JS_ATOM_END; else @@ -34944,7 +35821,7 @@ static __exception int JS_ObjectDefineProperties(JSContext *ctx, if (JS_IsException(props)) return -1; p = JS_VALUE_GET_OBJ(props); - if (JS_GetOwnPropertyNamesInternal(ctx, &atoms, &len, p, JS_GPN_ENUM_ONLY | JS_GPN_STRING_MASK) < 0) + if (JS_GetOwnPropertyNamesInternal(ctx, &atoms, &len, p, JS_GPN_ENUM_ONLY | JS_GPN_STRING_MASK | JS_GPN_SYMBOL_MASK) < 0) goto exception; for(i = 0; i < len; i++) { JS_FreeValue(ctx, desc); @@ -35021,7 +35898,7 @@ static JSValue js_object_getPrototypeOf(JSContext *ctx, JSValueConst this_val, JS_VALUE_GET_TAG(val) == JS_TAG_UNDEFINED) return JS_ThrowTypeErrorNotAnObject(ctx); } - return JS_DupValue(ctx, JS_GetPrototype(ctx, val)); + return JS_GetPrototype(ctx, val); } static JSValue js_object_setPrototypeOf(JSContext *ctx, JSValueConst this_val, @@ -35815,7 +36692,7 @@ static JSValue js_object_get___proto__(JSContext *ctx, JSValueConst this_val) val = JS_ToObject(ctx, this_val); if (JS_IsException(val)) return val; - ret = JS_DupValue(ctx, JS_GetPrototype(ctx, val)); + ret = JS_GetPrototype(ctx, val); JS_FreeValue(ctx, val); return ret; } @@ -35836,9 +36713,9 @@ static JSValue js_object_set___proto__(JSContext *ctx, JSValueConst this_val, static JSValue js_object_isPrototypeOf(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { - JSValue obj; + JSValue obj, v1; JSValueConst v; - int max_depth = 1000, res = -1; + int res; v = argv[0]; if (!JS_IsObject(v)) @@ -35846,27 +36723,29 @@ static JSValue js_object_isPrototypeOf(JSContext *ctx, JSValueConst this_val, obj = JS_ToObject(ctx, this_val); if (JS_IsException(obj)) return JS_EXCEPTION; - while (--max_depth > 0) { - v = JS_GetPrototype(ctx, v); - if (JS_IsException(v)) + v1 = JS_DupValue(ctx, v); + for(;;) { + v1 = JS_GetPrototypeFree(ctx, v1); + if (JS_IsException(v1)) goto exception; - if (JS_IsNull(v)) { + if (JS_IsNull(v1)) { res = FALSE; break; } - if (js_strict_eq2(ctx, JS_DupValue(ctx, obj), JS_DupValue(ctx, v), - JS_EQ_STRICT)) { + if (JS_VALUE_GET_OBJ(obj) == JS_VALUE_GET_OBJ(v1)) { res = TRUE; break; } + /* avoid infinite loop (possible with proxies) */ + if (js_poll_interrupts(ctx)) + goto exception; } + JS_FreeValue(ctx, v1); JS_FreeValue(ctx, obj); - if (res < 0) - return JS_ThrowInternalError(ctx, "prototype chain cycle"); - else - return JS_NewBool(ctx, res); + return JS_NewBool(ctx, res); exception: + JS_FreeValue(ctx, v1); JS_FreeValue(ctx, obj); return JS_EXCEPTION; } @@ -35906,7 +36785,6 @@ static JSValue js_object___lookupGetter__(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, int setter) { JSValue obj, res = JS_EXCEPTION; - JSValueConst v; JSAtom prop = JS_ATOM_NULL; JSPropertyDescriptor desc; int has_prop; @@ -35918,8 +36796,8 @@ static JSValue js_object___lookupGetter__(JSContext *ctx, JSValueConst this_val, if (unlikely(prop == JS_ATOM_NULL)) goto exception; - for (v = obj;;) { - has_prop = JS_GetOwnPropertyInternal(ctx, &desc, JS_VALUE_GET_OBJ(v), prop); + for (;;) { + has_prop = JS_GetOwnPropertyInternal(ctx, &desc, JS_VALUE_GET_OBJ(obj), prop); if (has_prop < 0) goto exception; if (has_prop) { @@ -35930,13 +36808,16 @@ static JSValue js_object___lookupGetter__(JSContext *ctx, JSValueConst this_val, js_free_desc(ctx, &desc); break; } - v = JS_GetPrototype(ctx, v); - if (JS_IsException(v)) + obj = JS_GetPrototypeFree(ctx, obj); + if (JS_IsException(obj)) goto exception; - if (JS_IsNull(v)) { + if (JS_IsNull(obj)) { res = JS_UNDEFINED; break; } + /* avoid infinite loop (possible with proxies) */ + if (js_poll_interrupts(ctx)) + goto exception; } exception: @@ -36002,59 +36883,12 @@ static JSValue js_function_proto(JSContext *ctx, JSValueConst this_val, return JS_UNDEFINED; } -/* insert a '\n' at unicode character position 'pos' in the source of - 'func_obj' */ -static int patch_function_constructor_source(JSContext *ctx, - JSValueConst func_obj, - size_t pos) -{ - JSObject *p; - JSFunctionBytecode *b; - char *r, *r_end, *new_source; - int c; - size_t idx, len; - - if (JS_VALUE_GET_TAG(func_obj) != JS_TAG_OBJECT) - return 0; - p = JS_VALUE_GET_OBJ(func_obj); - if (!js_class_has_bytecode(p->class_id)) - return 0; - b = p->u.func.function_bytecode; - if (!b->has_debug) - return 0; - r = b->debug.source; - r_end = b->debug.source + b->debug.source_len; - idx = 0; - while (r < r_end) { - if (idx == pos) { - /* add the '\n' */ - new_source = js_realloc(ctx, b->debug.source, - b->debug.source_len + 2); - if (!new_source) - return -1; - len = r - b->debug.source; - memmove(new_source + len + 1, new_source + len, - b->debug.source_len + 1 - len); - new_source[len] = '\n'; - b->debug.source = new_source; - b->debug.source_len++; - break; - } - c = unicode_from_utf8((const uint8_t *)r, UTF8_CHAR_LEN_MAX, - (const uint8_t **)&r); - if (c < 0) - break; - idx++; - } - return 0; -} - /* XXX: add a specific eval mode so that Function("}), ({") is rejected */ static JSValue js_function_constructor(JSContext *ctx, JSValueConst new_target, int argc, JSValueConst *argv, int magic) { JSFunctionKindEnum func_kind = magic; - int i, n, ret, func_start_pos; + int i, n, ret; JSValue s, proto, obj = JS_UNDEFINED; StringBuffer b_s, *b = &b_s; @@ -36079,13 +36913,7 @@ static JSValue js_function_constructor(JSContext *ctx, JSValueConst new_target, if (string_buffer_concat_value(b, argv[i])) goto fail; } - string_buffer_puts8(b, "\n) {"); - /* Annex B HTML comments: We don't add a '\n' after "{" so that - "-->" is not considered as an HTML comment. It is necessary - because in the spec the function body is parsed separately. */ - /* XXX: find a simpler way or be deliberately incompatible to - simplify the code ? */ - func_start_pos = b->len - 1; /* the leading '(' is not in the source */ + string_buffer_puts8(b, "\n) {\n"); if (n >= 0) { if (string_buffer_concat_value(b, argv[n])) goto fail; @@ -36099,8 +36927,6 @@ static JSValue js_function_constructor(JSContext *ctx, JSValueConst new_target, JS_FreeValue(ctx, s); if (JS_IsException(obj)) goto fail1; - if (patch_function_constructor_source(ctx, obj, func_start_pos) < 0) - goto fail1; if (!JS_IsUndefined(new_target)) { /* set the prototype */ proto = JS_GetProperty(ctx, new_target, JS_ATOM_prototype); @@ -36315,27 +37141,22 @@ static JSValue js_function_toString(JSContext *ctx, JSValueConst this_val, JSValue name; const char *pref, *suff; - if (p->is_class) { - pref = "class "; - suff = " {\n [native code]\n}"; - } else { - switch(func_kind) { - default: - case JS_FUNC_NORMAL: - pref = "function "; - break; - case JS_FUNC_GENERATOR: - pref = "function *"; - break; - case JS_FUNC_ASYNC: - pref = "async function "; - break; - case JS_FUNC_ASYNC_GENERATOR: - pref = "async function *"; - break; - } - suff = "() {\n [native code]\n}"; + switch(func_kind) { + default: + case JS_FUNC_NORMAL: + pref = "function "; + break; + case JS_FUNC_GENERATOR: + pref = "function *"; + break; + case JS_FUNC_ASYNC: + pref = "async function "; + break; + case JS_FUNC_ASYNC_GENERATOR: + pref = "async function *"; + break; } + suff = "() {\n [native code]\n}"; name = JS_GetProperty(ctx, this_val, JS_ATOM_name); if (JS_IsUndefined(name)) name = JS_AtomToString(ctx, JS_ATOM_empty_string); @@ -36435,12 +37256,6 @@ static JSValue js_error_constructor(JSContext *ctx, JSValueConst new_target, if (JS_IsException(obj)) return obj; if (magic == JS_AGGREGATE_ERROR) { - JSObject *p; - JSValue error_list = iterator_to_array(ctx, argv[0]); - if (JS_IsException(error_list)) - goto exception; - p = JS_VALUE_GET_OBJ(obj); - p->u.object_data = error_list; message = argv[1]; } else { message = argv[0]; @@ -36448,17 +37263,26 @@ static JSValue js_error_constructor(JSContext *ctx, JSValueConst new_target, if (!JS_IsUndefined(message)) { msg = JS_ToString(ctx, message); - if (unlikely(JS_IsException(msg))) { - exception: - JS_FreeValue(ctx, obj); - return JS_EXCEPTION; - } + if (unlikely(JS_IsException(msg))) + goto exception; JS_DefinePropertyValue(ctx, obj, JS_ATOM_message, msg, JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); } + + if (magic == JS_AGGREGATE_ERROR) { + JSValue error_list = iterator_to_array(ctx, argv[0]); + if (JS_IsException(error_list)) + goto exception; + JS_DefinePropertyValue(ctx, obj, JS_ATOM_errors, error_list, + JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); + } + /* skip the Error() function in the backtrace */ build_backtrace(ctx, obj, NULL, 0, JS_BACKTRACE_FLAG_SKIP_FIRST_LEVEL); return obj; + exception: + JS_FreeValue(ctx, obj); + return JS_EXCEPTION; } static JSValue js_error_toString(JSContext *ctx, JSValueConst this_val, @@ -36498,56 +37322,22 @@ static const JSCFunctionListEntry js_error_proto_funcs[] = { /* AggregateError */ -/* used by C code. 'errors' must be a fast array. */ +/* used by C code. */ static JSValue js_aggregate_error_constructor(JSContext *ctx, JSValueConst errors) { JSValue obj; - JSObject *p; obj = JS_NewObjectProtoClass(ctx, ctx->native_error_proto[JS_AGGREGATE_ERROR], JS_CLASS_ERROR); if (JS_IsException(obj)) return obj; - p = JS_VALUE_GET_OBJ(obj); - p->u.object_data = JS_DupValue(ctx, errors); + JS_DefinePropertyValue(ctx, obj, JS_ATOM_errors, JS_DupValue(ctx, errors), + JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); return obj; } -static JSValue js_aggregate_error_errors(JSContext *ctx, JSValueConst this_val) -{ - JSObject *p; - JSValue r, *arr; - uint32_t len, i; - - if (JS_VALUE_GET_TAG(this_val) != JS_TAG_OBJECT) - goto invalid_type; - p = JS_VALUE_GET_OBJ(this_val); - if (p->class_id != JS_CLASS_ERROR) - goto invalid_type; - if (!js_get_fast_array(ctx, p->u.object_data, &arr, &len)) { - invalid_type: - return JS_ThrowTypeError(ctx, "not an AggregateError"); - } - r = JS_NewArray(ctx); - if (JS_IsException(r)) - goto exception; - for(i = 0; i < len; i++) { - if (JS_DefinePropertyValueInt64(ctx, r, i, JS_DupValue(ctx, arr[i]), - JS_PROP_C_W_E | JS_PROP_THROW) < 0) - goto exception; - } - return r; - exception: - JS_FreeValue(ctx, r); - return JS_EXCEPTION; -} - -static const JSCFunctionListEntry js_aggregate_error_proto_funcs[] = { - JS_CGETSET_DEF("errors", js_aggregate_error_errors, NULL), -}; - /* Array */ static int JS_CopySubArray(JSContext *ctx, @@ -39130,22 +39920,25 @@ static JSValue js_string_includes(JSContext *ctx, JSValueConst this_val, p1 = JS_VALUE_GET_STRING(v); len = p->len; v_len = p1->len; - pos = (magic & 2) ? len : 0; + pos = (magic == 2) ? len : 0; if (argc > 1 && !JS_IsUndefined(argv[1])) { if (JS_ToInt32Clamp(ctx, &pos, argv[1], 0, len, 0)) goto fail; } len -= v_len; - start = pos; - stop = len; - if (magic & 1) { - stop = pos; - } - if (magic & 2) { - pos -= v_len; + ret = 0; + if (magic == 0) { + start = pos; + stop = len; + } else { + if (magic == 1) { + if (pos > len) + goto done; + } else { + pos -= v_len; + } start = stop = pos; } - ret = 0; if (start >= 0 && start <= stop) { for (i = start;; i++) { if (!string_cmp(p, p1, i, 0, v_len)) { @@ -39156,6 +39949,7 @@ static JSValue js_string_includes(JSContext *ctx, JSValueConst this_val, break; } } + done: JS_FreeValue(ctx, str); JS_FreeValue(ctx, v); return JS_NewBool(ctx, ret); @@ -40372,46 +41166,23 @@ static double js_math_round(double a) static JSValue js_math_hypot(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { - double r, a, b; + double r, a; int i; - if (argc == 2) { - /* use the more precise built-in function when possible */ - if (JS_ToFloat64(ctx, &a, argv[0])) - return JS_EXCEPTION; - if (JS_ToFloat64(ctx, &b, argv[1])) - return JS_EXCEPTION; - r = hypot(a, b); - } else if (argc == 0) { - r = 0; - } else { - double *tab, a_max; - tab = js_malloc(ctx, sizeof(tab[0]) * argc); - if (!tab) + r = 0; + if (argc > 0) { + if (JS_ToFloat64(ctx, &r, argv[0])) return JS_EXCEPTION; - /* avoid overflow by taking the maximum */ - a_max = 0; - for(i = 0; i < argc; i++) { - if (JS_ToFloat64(ctx, &a, argv[i])) { - js_free(ctx, tab); - return JS_EXCEPTION; - } - a = fabs(a); - tab[i] = a; - if (a > a_max) - a_max = a; - } - if (a_max == 0 || !isfinite(a_max)) { - r = a_max; + if (argc == 1) { + r = fabs(r); } else { - r = 0; - for(i = 0; i < argc; i++) { - a = tab[i] / a_max; - r += a * a; + /* use the built-in function to minimize precision loss */ + for (i = 1; i < argc; i++) { + if (JS_ToFloat64(ctx, &a, argv[i])) + return JS_EXCEPTION; + r = hypot(r, a); } - r = a_max * sqrt(r); } - js_free(ctx, tab); } return JS_NewFloat64(ctx, r); } @@ -42093,58 +42864,69 @@ void JS_AddIntrinsicRegExp(JSContext *ctx) /* JSON */ -/* XXX: this parser is less strict than the JSON standard because we - reuse the Javascript tokenizer. It could be improved by adding a - specific JSON parse flag. */ +static int json_parse_expect(JSParseState *s, int tok) +{ + if (s->token.val != tok) { + /* XXX: dump token correctly in all cases */ + return js_parse_error(s, "expecting '%c'", tok); + } + return json_next_token(s); +} + static JSValue json_parse_value(JSParseState *s) { JSContext *ctx = s->ctx; JSValue val = JS_NULL; - BOOL is_neg; int ret; switch(s->token.val) { case '{': { - JSValue prop_val, prop_str; - - if (next_token(s)) + JSValue prop_val; + JSAtom prop_name; + + if (json_next_token(s)) goto fail; val = JS_NewObject(ctx); if (JS_IsException(val)) goto fail; if (s->token.val != '}') { for(;;) { - if (s->token.val != TOK_STRING) { + if (s->token.val == TOK_STRING) { + prop_name = JS_ValueToAtom(ctx, s->token.u.str.str); + if (prop_name == JS_ATOM_NULL) + goto fail; + } else if (s->ext_json && s->token.val == TOK_IDENT) { + prop_name = JS_DupAtom(ctx, s->token.u.ident.atom); + } else { js_parse_error(s, "expecting property name"); goto fail; } - prop_str = JS_DupValue(ctx, s->token.u.str.str); - if (next_token(s)) { - JS_FreeValue(ctx, prop_str); - goto fail; - } - if (js_parse_expect(s, ':')) { - JS_FreeValue(ctx, prop_str); - goto fail; - } + if (json_next_token(s)) + goto fail1; + if (json_parse_expect(s, ':')) + goto fail1; prop_val = json_parse_value(s); if (JS_IsException(prop_val)) { - JS_FreeValue(ctx, prop_str); + fail1: + JS_FreeAtom(ctx, prop_name); goto fail; } - ret = JS_DefinePropertyValueValue(ctx, val, prop_str, - prop_val, JS_PROP_C_W_E); + ret = JS_DefinePropertyValue(ctx, val, prop_name, + prop_val, JS_PROP_C_W_E); + JS_FreeAtom(ctx, prop_name); if (ret < 0) goto fail; if (s->token.val != ',') break; - if (next_token(s)) + if (json_next_token(s)) goto fail; + if (s->ext_json && s->token.val == '}') + break; } } - if (js_parse_expect(s, '}')) + if (json_parse_expect(s, '}')) goto fail; } break; @@ -42153,7 +42935,7 @@ static JSValue json_parse_value(JSParseState *s) JSValue el; uint32_t idx; - if (next_token(s)) + if (json_next_token(s)) goto fail; val = JS_NewArray(ctx); if (JS_IsException(val)) @@ -42169,52 +42951,41 @@ static JSValue json_parse_value(JSParseState *s) goto fail; if (s->token.val != ',') break; - if (next_token(s)) + if (json_next_token(s)) goto fail; idx++; + if (s->ext_json && s->token.val == ']') + break; } } - if (js_parse_expect(s, ']')) + if (json_parse_expect(s, ']')) goto fail; } break; case TOK_STRING: val = JS_DupValue(ctx, s->token.u.str.str); - if (next_token(s)) + if (json_next_token(s)) goto fail; break; case TOK_NUMBER: - is_neg = 0; - goto number; - case '-': - if (next_token(s)) - goto fail; - if (s->token.val != TOK_NUMBER) { - js_parse_error(s, "number expected"); - goto fail; - } - is_neg = 1; - number: val = s->token.u.num.val; - if (is_neg) { - double d; - JS_ToFloat64(ctx, &d, val); /* no exception possible */ - val = JS_NewFloat64(ctx, -d); - } - if (next_token(s)) - goto fail; - break; - case TOK_FALSE: - case TOK_TRUE: - val = JS_NewBool(ctx, s->token.val - TOK_FALSE); - if (next_token(s)) + if (json_next_token(s)) goto fail; break; - case TOK_NULL: - if (next_token(s)) + case TOK_IDENT: + if (s->token.u.ident.atom == JS_ATOM_false || + s->token.u.ident.atom == JS_ATOM_true) { + val = JS_NewBool(ctx, (s->token.u.ident.atom == JS_ATOM_true)); + } else if (s->token.u.ident.atom == JS_ATOM_null) { + val = JS_NULL; + } else { + goto def_token; + } + if (json_next_token(s)) goto fail; break; default: + def_token: if (s->token.val == TOK_EOF) { js_parse_error(s, "unexpected end of input"); } else { @@ -42229,15 +43000,15 @@ static JSValue json_parse_value(JSParseState *s) return JS_EXCEPTION; } -JSValue JS_ParseJSON(JSContext *ctx, const char *buf, size_t buf_len, - const char *filename) +JSValue JS_ParseJSON2(JSContext *ctx, const char *buf, size_t buf_len, + const char *filename, int flags) { JSParseState s1, *s = &s1; - JSValue val; + JSValue val = JS_UNDEFINED; js_parse_init(ctx, s, buf, buf_len, filename); - - if (next_token(s)) + s->ext_json = ((flags & JS_PARSE_JSON_EXT) != 0); + if (json_next_token(s)) goto fail; val = json_parse_value(s); if (JS_IsException(val)) @@ -42248,10 +43019,17 @@ JSValue JS_ParseJSON(JSContext *ctx, const char *buf, size_t buf_len, } return val; fail: + JS_FreeValue(ctx, val); free_token(s, &s->token); return JS_EXCEPTION; } +JSValue JS_ParseJSON(JSContext *ctx, const char *buf, size_t buf_len, + const char *filename) +{ + return JS_ParseJSON2(ctx, buf, buf_len, filename, 0); +} + static JSValue internalize_json_property(JSContext *ctx, JSValueConst holder, JSAtom name, JSValueConst reviver) { @@ -42960,7 +43738,6 @@ static void js_proxy_finalizer(JSRuntime *rt, JSValue val) if (s) { JS_FreeValueRT(rt, s->target); JS_FreeValueRT(rt, s->handler); - JS_FreeValueRT(rt, s->proto); js_free_rt(rt, s); } } @@ -42972,7 +43749,6 @@ static void js_proxy_mark(JSRuntime *rt, JSValueConst val, if (s) { JS_MarkValue(rt, s->target, mark_func); JS_MarkValue(rt, s->handler, mark_func); - JS_MarkValue(rt, s->proto, mark_func); } } @@ -43007,17 +43783,12 @@ static JSProxyData *get_proxy_method(JSContext *ctx, JSValue *pmethod, return s; } -static JSValueConst js_proxy_getPrototypeOf(JSContext *ctx, JSValueConst obj) +static JSValue js_proxy_getPrototypeOf(JSContext *ctx, JSValueConst obj) { JSProxyData *s; - JSValue method, ret; - JSValueConst proto1; + JSValue method, ret, proto1; int res; - /* must check for timeout to avoid infinite loop in instanceof */ - if (js_poll_interrupts(ctx)) - return JS_EXCEPTION; - s = get_proxy_method(ctx, &method, obj, JS_ATOM_getPrototypeOf); if (!s) return JS_EXCEPTION; @@ -43043,22 +43814,22 @@ static JSValueConst js_proxy_getPrototypeOf(JSContext *ctx, JSValueConst obj) return JS_EXCEPTION; } if (JS_VALUE_GET_OBJ(proto1) != JS_VALUE_GET_OBJ(ret)) { + JS_FreeValue(ctx, proto1); fail: JS_FreeValue(ctx, ret); return JS_ThrowTypeError(ctx, "proxy: inconsistent prototype"); } + JS_FreeValue(ctx, proto1); } - /* store the prototype in the proxy so that its refcount is at least 1 */ - set_value(ctx, &s->proto, ret); - return (JSValueConst)ret; + return ret; } static int js_proxy_setPrototypeOf(JSContext *ctx, JSValueConst obj, JSValueConst proto_val, BOOL throw_flag) { JSProxyData *s; - JSValue method, ret; - JSValueConst args[2], proto1; + JSValue method, ret, proto1; + JSValueConst args[2]; BOOL res; int res2; @@ -43089,9 +43860,11 @@ static int js_proxy_setPrototypeOf(JSContext *ctx, JSValueConst obj, if (JS_IsException(proto1)) return -1; if (JS_VALUE_GET_OBJ(proto_val) != JS_VALUE_GET_OBJ(proto1)) { + JS_FreeValue(ctx, proto1); JS_ThrowTypeError(ctx, "proxy: inconsistent prototype"); return -1; } + JS_FreeValue(ctx, proto1); } return TRUE; } @@ -43829,7 +44602,6 @@ static JSValue js_proxy_constructor(JSContext *ctx, JSValueConst this_val, } s->target = JS_DupValue(ctx, target); s->handler = JS_DupValue(ctx, handler); - s->proto = JS_NULL; s->is_func = JS_IsFunction(ctx, target); s->is_revoked = FALSE; JS_SetOpaque(obj, s); @@ -45414,10 +46186,10 @@ static JSValue js_promise_all_resolve_element(JSContext *ctx, static JSValue js_promise_all(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, int magic) { - JSValue result_promise, resolving_funcs[2], iter, item, next_promise, ret; + JSValue result_promise, resolving_funcs[2], item, next_promise, ret; JSValue next_method = JS_UNDEFINED, values = JS_UNDEFINED; JSValue resolve_element_env = JS_UNDEFINED, resolve_element, reject_element; - JSValue promise_resolve = JS_UNDEFINED; + JSValue promise_resolve = JS_UNDEFINED, iter = JS_UNDEFINED; JSValueConst then_args[2], resolve_element_data[5]; BOOL done; int index, is_zero, is_promise_any = (magic == PROMISE_MAGIC_any); @@ -45427,6 +46199,10 @@ static JSValue js_promise_all(JSContext *ctx, JSValueConst this_val, result_promise = js_new_promise_capability(ctx, resolving_funcs, this_val); if (JS_IsException(result_promise)) return result_promise; + promise_resolve = JS_GetProperty(ctx, this_val, JS_ATOM_resolve); + if (JS_IsException(promise_resolve) || + check_function(ctx, promise_resolve)) + goto fail_reject; iter = JS_GetIterator(ctx, argv[0], FALSE); if (JS_IsException(iter)) { JSValue error; @@ -45454,11 +46230,6 @@ static JSValue js_promise_all(JSContext *ctx, JSValueConst this_val, JS_PROP_CONFIGURABLE | JS_PROP_ENUMERABLE | JS_PROP_WRITABLE) < 0) goto fail_reject; - promise_resolve = JS_GetProperty(ctx, this_val, JS_ATOM_resolve); - if (JS_IsException(promise_resolve) || - check_function(ctx, promise_resolve)) - goto fail_reject1; - index = 0; for(;;) { /* XXX: conformance: should close the iterator if error on 'done' @@ -45560,8 +46331,8 @@ static JSValue js_promise_all(JSContext *ctx, JSValueConst this_val, static JSValue js_promise_race(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { - JSValue result_promise, resolving_funcs[2], iter, item, next_promise, ret; - JSValue next_method = JS_UNDEFINED; + JSValue result_promise, resolving_funcs[2], item, next_promise, ret; + JSValue next_method = JS_UNDEFINED, iter = JS_UNDEFINED; JSValue promise_resolve = JS_UNDEFINED; BOOL done; @@ -45570,6 +46341,10 @@ static JSValue js_promise_race(JSContext *ctx, JSValueConst this_val, result_promise = js_new_promise_capability(ctx, resolving_funcs, this_val); if (JS_IsException(result_promise)) return result_promise; + promise_resolve = JS_GetProperty(ctx, this_val, JS_ATOM_resolve); + if (JS_IsException(promise_resolve) || + check_function(ctx, promise_resolve)) + goto fail_reject; iter = JS_GetIterator(ctx, argv[0], FALSE); if (JS_IsException(iter)) { JSValue error; @@ -45586,11 +46361,6 @@ static JSValue js_promise_race(JSContext *ctx, JSValueConst this_val, if (JS_IsException(next_method)) goto fail_reject; - promise_resolve = JS_GetProperty(ctx, this_val, JS_ATOM_resolve); - if (JS_IsException(promise_resolve) || - check_function(ctx, promise_resolve)) - goto fail_reject1; - for(;;) { /* XXX: conformance: should close the iterator if error on 'done' access, but not on 'value' access */ @@ -49385,9 +50155,6 @@ static void JS_AddIntrinsicBasicObjects(JSContext *ctx) JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); ctx->native_error_proto[i] = proto; } - JS_SetPropertyFunctionList(ctx, ctx->native_error_proto[JS_AGGREGATE_ERROR], - js_aggregate_error_proto_funcs, - countof(js_aggregate_error_proto_funcs)); /* the array prototype is an array */ ctx->class_proto[JS_CLASS_ARRAY] = @@ -49621,6 +50388,7 @@ static JSValue js_array_buffer_constructor3(JSContext *ctx, JSFreeArrayBufferDataFunc *free_func, void *opaque, BOOL alloc_flag) { + JSRuntime *rt = ctx->rt; JSValue obj; JSArrayBuffer *abuf = NULL; @@ -49637,11 +50405,24 @@ static JSValue js_array_buffer_constructor3(JSContext *ctx, goto fail; abuf->byte_length = len; if (alloc_flag) { - /* the allocation must be done after the object creation */ - abuf->data = js_mallocz(ctx, max_int(len, 1)); - if (!abuf->data) - goto fail; + if (class_id == JS_CLASS_SHARED_ARRAY_BUFFER && + rt->sab_funcs.sab_alloc) { + abuf->data = rt->sab_funcs.sab_alloc(rt->sab_funcs.sab_opaque, + max_int(len, 1)); + if (!abuf->data) + goto fail; + memset(abuf->data, 0, len); + } else { + /* the allocation must be done after the object creation */ + abuf->data = js_mallocz(ctx, max_int(len, 1)); + if (!abuf->data) + goto fail; + } } else { + if (class_id == JS_CLASS_SHARED_ARRAY_BUFFER && + rt->sab_funcs.sab_dup) { + rt->sab_funcs.sab_dup(rt->sab_funcs.sab_opaque, buf); + } abuf->data = buf; } init_list_head(&abuf->array_list); @@ -49731,8 +50512,12 @@ static void js_array_buffer_finalizer(JSRuntime *rt, JSValue val) array finalizers using it, so abuf->array_list is not necessarily empty. */ // assert(list_empty(&abuf->array_list)); - if (abuf->free_func) - abuf->free_func(rt, abuf->opaque, abuf->data); + if (abuf->shared && rt->sab_funcs.sab_free) { + rt->sab_funcs.sab_free(rt->sab_funcs.sab_opaque, abuf->data); + } else { + if (abuf->free_func) + abuf->free_func(rt, abuf->opaque, abuf->data); + } js_free_rt(rt, abuf); } } @@ -50208,11 +50993,6 @@ static JSValue js_typed_array___getLength(JSContext *ctx, } #endif -static JSValue js_typed_array_constructor(JSContext *ctx, - JSValueConst this_val, - int argc, JSValueConst *argv, - int classid); - static JSValue js_typed_array_create(JSContext *ctx, JSValueConst ctor, int argc, JSValueConst *argv) { @@ -51967,9 +52747,10 @@ typedef enum AtomicsOpEnum { } AtomicsOpEnum; static void *js_atomics_get_ptr(JSContext *ctx, + JSArrayBuffer **pabuf, int *psize_log2, JSClassID *pclass_id, JSValueConst obj, JSValueConst idx_val, - BOOL is_waitable) + int is_waitable) { JSObject *p; JSTypedArray *ta; @@ -52004,18 +52785,27 @@ static void *js_atomics_get_ptr(JSContext *ctx, ta = p->u.typed_array; abuf = ta->buffer->u.array_buffer; if (!abuf->shared) { - JS_ThrowTypeError(ctx, "not a SharedArrayBuffer TypedArray"); - return NULL; + if (is_waitable == 2) { + JS_ThrowTypeError(ctx, "not a SharedArrayBuffer TypedArray"); + return NULL; + } + if (abuf->detached) { + JS_ThrowTypeErrorDetachedArrayBuffer(ctx); + return NULL; + } } if (JS_ToIndex(ctx, &idx, idx_val)) { return NULL; } + /* if the array buffer is detached, p->u.array.count = 0 */ if (idx >= p->u.array.count) { JS_ThrowRangeError(ctx, "out-of-bound access"); return NULL; } size_log2 = typed_array_size_log2(p->class_id); ptr = p->u.array.u.uint8_ptr + ((uintptr_t)idx << size_log2); + if (pabuf) + *pabuf = abuf; if (psize_log2) *psize_log2 = size_log2; if (pclass_id) @@ -52036,17 +52826,18 @@ static JSValue js_atomics_op(JSContext *ctx, void *ptr; JSValue ret; JSClassID class_id; + JSArrayBuffer *abuf; - ptr = js_atomics_get_ptr(ctx, &size_log2, &class_id, - argv[0], argv[1], FALSE); + ptr = js_atomics_get_ptr(ctx, &abuf, &size_log2, &class_id, + argv[0], argv[1], 0); if (!ptr) return JS_EXCEPTION; rep_val = 0; if (op == ATOMICS_OP_LOAD) { v = 0; - } else + } else { #ifdef CONFIG_BIGNUM - if (size_log2 == 3) { + if (size_log2 == 3) { int64_t v64; if (JS_ToBigInt64(ctx, &v64, argv[2])) return JS_EXCEPTION; @@ -52056,19 +52847,23 @@ static JSValue js_atomics_op(JSContext *ctx, return JS_EXCEPTION; rep_val = v64; } - } else + } else #endif - { - uint32_t v32; - if (JS_ToUint32(ctx, &v32, argv[2])) - return JS_EXCEPTION; - v = v32; - if (op == ATOMICS_OP_COMPARE_EXCHANGE) { - if (JS_ToUint32(ctx, &v32, argv[3])) - return JS_EXCEPTION; - rep_val = v32; - } + { + uint32_t v32; + if (JS_ToUint32(ctx, &v32, argv[2])) + return JS_EXCEPTION; + v = v32; + if (op == ATOMICS_OP_COMPARE_EXCHANGE) { + if (JS_ToUint32(ctx, &v32, argv[3])) + return JS_EXCEPTION; + rep_val = v32; + } + } + if (abuf->detached) + return JS_ThrowTypeErrorDetachedArrayBuffer(ctx); } + switch(op | (size_log2 << 3)) { #ifdef CONFIG_BIGNUM @@ -52195,8 +52990,10 @@ static JSValue js_atomics_store(JSContext *ctx, int size_log2; void *ptr; JSValue ret; + JSArrayBuffer *abuf; - ptr = js_atomics_get_ptr(ctx, &size_log2, NULL, argv[0], argv[1], FALSE); + ptr = js_atomics_get_ptr(ctx, &abuf, &size_log2, NULL, + argv[0], argv[1], 0); if (!ptr) return JS_EXCEPTION; #ifdef CONFIG_BIGNUM @@ -52209,6 +53006,8 @@ static JSValue js_atomics_store(JSContext *ctx, JS_FreeValue(ctx, ret); return JS_EXCEPTION; } + if (abuf->detached) + return JS_ThrowTypeErrorDetachedArrayBuffer(ctx); atomic_store((_Atomic(uint64_t) *)ptr, v64); } else #endif @@ -52222,6 +53021,8 @@ static JSValue js_atomics_store(JSContext *ctx, JS_FreeValue(ctx, ret); return JS_EXCEPTION; } + if (abuf->detached) + return JS_ThrowTypeErrorDetachedArrayBuffer(ctx); switch(size_log2) { case 0: atomic_store((_Atomic(uint8_t) *)ptr, v); @@ -52278,7 +53079,8 @@ static JSValue js_atomics_wait(JSContext *ctx, int ret, size_log2, res; double d; - ptr = js_atomics_get_ptr(ctx, &size_log2, NULL, argv[0], argv[1], TRUE); + ptr = js_atomics_get_ptr(ctx, NULL, &size_log2, NULL, + argv[0], argv[1], 2); if (!ptr) return JS_EXCEPTION; #ifdef CONFIG_BIGNUM @@ -52357,8 +53159,9 @@ static JSValue js_atomics_notify(JSContext *ctx, int32_t count, n; void *ptr; JSAtomicsWaiter *waiter; + JSArrayBuffer *abuf; - ptr = js_atomics_get_ptr(ctx, NULL, NULL, argv[0], argv[1], TRUE); + ptr = js_atomics_get_ptr(ctx, &abuf, NULL, NULL, argv[0], argv[1], 1); if (!ptr) return JS_EXCEPTION; @@ -52368,9 +53171,11 @@ static JSValue js_atomics_notify(JSContext *ctx, if (JS_ToInt32Clamp(ctx, &count, argv[2], 0, INT32_MAX, 0)) return JS_EXCEPTION; } + if (abuf->detached) + return JS_ThrowTypeErrorDetachedArrayBuffer(ctx); n = 0; - if (count > 0) { + if (abuf->shared && count > 0) { pthread_mutex_lock(&js_atomics_mutex); init_list_head(&waiter_list); list_for_each_safe(el, el1, &js_atomics_waiter_list) { @@ -498,7 +498,7 @@ int JS_IsRegisteredClass(JSRuntime *rt, JSClassID class_id); static js_force_inline JSValue JS_NewBool(JSContext *ctx, JS_BOOL val) { - return JS_MKVAL(JS_TAG_BOOL, val); + return JS_MKVAL(JS_TAG_BOOL, (val != 0)); } static js_force_inline JSValue JS_NewInt32(JSContext *ctx, int32_t val) @@ -746,7 +746,7 @@ int JS_IsExtensible(JSContext *ctx, JSValueConst obj); int JS_PreventExtensions(JSContext *ctx, JSValueConst obj); int JS_DeleteProperty(JSContext *ctx, JSValueConst obj, JSAtom prop, int flags); int JS_SetPrototype(JSContext *ctx, JSValueConst obj, JSValueConst proto_val); -JSValueConst JS_GetPrototype(JSContext *ctx, JSValueConst val); +JSValue JS_GetPrototype(JSContext *ctx, JSValueConst val); #define JS_GPN_STRING_MASK (1 << 0) #define JS_GPN_SYMBOL_MASK (1 << 1) @@ -796,6 +796,9 @@ void *JS_GetOpaque2(JSContext *ctx, JSValueConst obj, JSClassID class_id); /* 'buf' must be zero terminated i.e. buf[buf_len] = '\0'. */ JSValue JS_ParseJSON(JSContext *ctx, const char *buf, size_t buf_len, const char *filename); +#define JS_PARSE_JSON_EXT (1 << 0) /* allow extended JSON */ +JSValue JS_ParseJSON2(JSContext *ctx, const char *buf, size_t buf_len, + const char *filename, int flags); JSValue JS_JSONStringify(JSContext *ctx, JSValueConst obj, JSValueConst replacer, JSValueConst space0); @@ -810,6 +813,14 @@ JSValue JS_GetTypedArrayBuffer(JSContext *ctx, JSValueConst obj, size_t *pbyte_offset, size_t *pbyte_length, size_t *pbytes_per_element); +typedef struct { + void *(*sab_alloc)(void *opaque, size_t size); + void (*sab_free)(void *opaque, void *ptr); + void (*sab_dup)(void *opaque, void *ptr); + void *sab_opaque; +} JSSharedArrayBufferFunctions; +void JS_SetSharedArrayBufferFunctions(JSRuntime *rt, + const JSSharedArrayBufferFunctions *sf); JSValue JS_NewPromiseCapability(JSContext *ctx, JSValue *resolving_funcs); @@ -853,14 +864,24 @@ JS_BOOL JS_IsJobPending(JSRuntime *rt); int JS_ExecutePendingJob(JSRuntime *rt, JSContext **pctx); /* Object Writer/Reader (currently only used to handle precompiled code) */ -#define JS_WRITE_OBJ_BYTECODE (1 << 0) /* allow function/module */ -#define JS_WRITE_OBJ_BSWAP (1 << 1) /* byte swapped output */ +#define JS_WRITE_OBJ_BYTECODE (1 << 0) /* allow function/module */ +#define JS_WRITE_OBJ_BSWAP (1 << 1) /* byte swapped output */ +#define JS_WRITE_OBJ_SAB (1 << 2) /* allow SharedArrayBuffer */ +#define JS_WRITE_OBJ_REFERENCE (1 << 3) /* allow object references to + encode arbitrary object + graph */ uint8_t *JS_WriteObject(JSContext *ctx, size_t *psize, JSValueConst obj, int flags); +uint8_t *JS_WriteObject2(JSContext *ctx, size_t *psize, JSValueConst obj, + int flags, uint8_t ***psab_tab, size_t *psab_tab_len); + #define JS_READ_OBJ_BYTECODE (1 << 0) /* allow function/module */ #define JS_READ_OBJ_ROM_DATA (1 << 1) /* avoid duplicating 'buf' data */ +#define JS_READ_OBJ_SAB (1 << 2) /* allow SharedArrayBuffer */ +#define JS_READ_OBJ_REFERENCE (1 << 3) /* allow object references */ JSValue JS_ReadObject(JSContext *ctx, const uint8_t *buf, size_t buf_len, int flags); + /* load the dependencies of the module 'obj'. Useful when JS_ReadObject() returns a module. */ int JS_ResolveModule(JSContext *ctx, JSValueConst obj); @@ -209,6 +209,29 @@ import * as os from "os"; (is_alpha(c) || is_digit(c) || c == '_' || c == '$'); } + function ucs_length(str) { + var len, c, i, str_len = str.length; + len = 0; + /* we never count the trailing surrogate to have the + following property: ucs_length(str) = + ucs_length(str.substring(0, a)) + ucs_length(str.substring(a, + str.length)) for 0 <= a <= str.length */ + for(i = 0; i < str_len; i++) { + c = str.charCodeAt(i); + if (c < 0xdc00 || c >= 0xe000) + len++; + } + return len; + } + + function is_trailing_surrogate(c) { + var d; + if (typeof c !== "string") + return false; + d = c.codePointAt(0); /* can be NaN if empty string */ + return d >= 0xdc00 && d < 0xe000; + } + function is_balanced(a, b) { switch (a + b) { case "()": @@ -235,6 +258,7 @@ import * as os from "os"; std.puts("\x1b[" + ((n != 1) ? n : "") + code); } + /* XXX: handle double-width characters */ function move_cursor(delta) { var i, l; if (delta > 0) { @@ -269,15 +293,16 @@ import * as os from "os"; } function update() { - var i; - + var i, cmd_len; + /* cursor_pos is the position in 16 bit characters inside the + UTF-16 string 'cmd' */ if (cmd != last_cmd) { if (!show_colors && last_cmd.substring(0, last_cursor_pos) == cmd.substring(0, last_cursor_pos)) { /* optimize common case */ std.puts(cmd.substring(last_cursor_pos)); } else { /* goto the start of the line */ - move_cursor(-last_cursor_pos); + move_cursor(-ucs_length(last_cmd.substring(0, last_cursor_pos))); if (show_colors) { var str = mexpr ? mexpr + '\n' + cmd : cmd; var start = str.length - cmd.length; @@ -287,8 +312,7 @@ import * as os from "os"; std.puts(cmd); } } - /* Note: assuming no surrogate pairs */ - term_cursor_x = (term_cursor_x + cmd.length) % term_width; + term_cursor_x = (term_cursor_x + ucs_length(cmd)) % term_width; if (term_cursor_x == 0) { /* show the cursor on the next line */ std.puts(" \x08"); @@ -298,7 +322,11 @@ import * as os from "os"; last_cmd = cmd; last_cursor_pos = cmd.length; } - move_cursor(cursor_pos - last_cursor_pos); + if (cursor_pos > last_cursor_pos) { + move_cursor(ucs_length(cmd.substring(last_cursor_pos, cursor_pos))); + } else if (cursor_pos < last_cursor_pos) { + move_cursor(-ucs_length(cmd.substring(cursor_pos, last_cursor_pos))); + } last_cursor_pos = cursor_pos; std.out.flush(); } @@ -333,13 +361,19 @@ import * as os from "os"; } function forward_char() { - if (cursor_pos < cmd.length) + if (cursor_pos < cmd.length) { cursor_pos++; + while (is_trailing_surrogate(cmd.charAt(cursor_pos))) + cursor_pos++; + } } function backward_char() { - if (cursor_pos > 0) + if (cursor_pos > 0) { cursor_pos--; + while (is_trailing_surrogate(cmd.charAt(cursor_pos))) + cursor_pos--; + } } function skip_word_forward(pos) { @@ -419,8 +453,18 @@ import * as os from "os"; } function delete_char_dir(dir) { - var start = cursor_pos - (dir < 0); - var end = start + 1; + var start, end; + + start = cursor_pos; + if (dir < 0) { + start--; + while (is_trailing_surrogate(cmd.charAt(start))) + start--; + } + end = start + 1; + while (is_trailing_surrogate(cmd.charAt(end))) + end++; + if (start >= 0 && start < cmd.length) { if (last_fun === kill_region) { kill_region(start, end, dir); @@ -752,7 +796,7 @@ import * as os from "os"; function readline_print_prompt() { std.puts(prompt); - term_cursor_x = prompt.length % term_width; + term_cursor_x = ucs_length(prompt) % term_width; last_cmd = ""; last_cursor_pos = 0; } @@ -785,7 +829,7 @@ import * as os from "os"; function handle_char(c1) { var c; - c = String.fromCharCode(c1); + c = String.fromCodePoint(c1); switch(readline_state) { case 0: if (c == '\x1b') { /* '^[' - ESC */ @@ -825,7 +869,7 @@ import * as os from "os"; var fun; if (quote_flag) { - if (keys.length === 1) + if (ucs_length(keys) === 1) insert(keys); quote_flag = false; } else if (fun = commands[keys]) { @@ -845,7 +889,7 @@ import * as os from "os"; return; } last_fun = this_fun; - } else if (keys.length === 1 && keys >= ' ') { + } else if (ucs_length(keys) === 1 && keys >= ' ') { insert(keys); last_fun = insert; } else { diff --git a/test262.conf b/test262.conf index 4971dd8..17abf1e 100644 --- a/test262.conf +++ b/test262.conf @@ -58,6 +58,7 @@ arrow-function async-functions async-iteration Atomics +Atomics.waitAsync=skip BigInt caller class diff --git a/test262_errors.txt b/test262_errors.txt index 024f357..d00d4f1 100644 --- a/test262_errors.txt +++ b/test262_errors.txt @@ -2,6 +2,8 @@ test262/test/built-ins/Function/internals/Construct/derived-this-uninitialized-r test262/test/built-ins/Function/internals/Construct/derived-this-uninitialized-realm.js:20: strict mode: Test262Error: Expected a ReferenceError but got a ReferenceError test262/test/built-ins/Proxy/ownKeys/trap-is-undefined-target-is-proxy.js:29: Test262Error: Expected [0, length, foo, Symbol()] and [Symbol(), length, foo, 0] to have the same contents. test262/test/built-ins/Proxy/ownKeys/trap-is-undefined-target-is-proxy.js:29: strict mode: Test262Error: Expected [0, length, foo, Symbol()] and [Symbol(), length, foo, 0] to have the same contents. +test262/test/built-ins/RegExp/named-groups/non-unicode-property-names-valid.js:46: SyntaxError: invalid group name +test262/test/built-ins/RegExp/named-groups/non-unicode-property-names-valid.js:46: strict mode: SyntaxError: invalid group name test262/test/language/expressions/arrow-function/eval-var-scope-syntax-err.js:47: Test262Error: Expected a SyntaxError to be thrown but no exception was thrown at all test262/test/language/expressions/async-arrow-function/eval-var-scope-syntax-err.js:49: TypeError: $DONE() not called test262/test/language/expressions/async-function/named-eval-var-scope-syntax-err.js:33: TypeError: $DONE() not called @@ -18,8 +20,8 @@ test262/test/language/expressions/object/method-definition/async-gen-meth-eval-v test262/test/language/expressions/object/method-definition/async-meth-eval-var-scope-syntax-err.js:36: TypeError: $DONE() not called test262/test/language/expressions/object/method-definition/gen-meth-eval-var-scope-syntax-err.js:54: Test262Error: Expected a SyntaxError to be thrown but no exception was thrown at all test262/test/language/expressions/object/method-definition/meth-eval-var-scope-syntax-err.js:50: Test262Error: Expected a SyntaxError to be thrown but no exception was thrown at all -test262/test/language/expressions/optional-chaining/optional-call-preserves-this.js:21: TypeError: value has no property -test262/test/language/expressions/optional-chaining/optional-call-preserves-this.js:15: strict mode: TypeError: value has no property +test262/test/language/expressions/optional-chaining/optional-call-preserves-this.js:21: TypeError: cannot read property 'c' of undefined +test262/test/language/expressions/optional-chaining/optional-call-preserves-this.js:15: strict mode: TypeError: cannot read property '_b' of undefined test262/test/language/statements/async-function/eval-var-scope-syntax-err.js:33: TypeError: $DONE() not called test262/test/language/statements/async-generator/eval-var-scope-syntax-err.js:28: Test262Error: Expected a SyntaxError to be thrown but no exception was thrown at all test262/test/language/statements/class/elements/grammar-private-field-optional-chaining.js:26: SyntaxError: expecting field name diff --git a/tests/bjson.c b/tests/bjson.c index 9631300..8e52741 100644 --- a/tests/bjson.c +++ b/tests/bjson.c @@ -31,6 +31,7 @@ static JSValue js_bjson_read(JSContext *ctx, JSValueConst this_val, uint64_t pos, len; JSValue obj; size_t size; + int flags; if (JS_ToIndex(ctx, &pos, argv[1])) return JS_EXCEPTION; @@ -41,7 +42,10 @@ static JSValue js_bjson_read(JSContext *ctx, JSValueConst this_val, return JS_EXCEPTION; if (pos + len > size) return JS_ThrowRangeError(ctx, "array buffer overflow"); - obj = JS_ReadObject(ctx, buf + pos, len, 0); + flags = 0; + if (JS_ToBool(ctx, argv[3])) + flags |= JS_READ_OBJ_REFERENCE; + obj = JS_ReadObject(ctx, buf + pos, len, flags); return obj; } @@ -51,8 +55,12 @@ static JSValue js_bjson_write(JSContext *ctx, JSValueConst this_val, size_t len; uint8_t *buf; JSValue array; + int flags; - buf = JS_WriteObject(ctx, &len, argv[0], 0); + flags = 0; + if (JS_ToBool(ctx, argv[1])) + flags |= JS_WRITE_OBJ_REFERENCE; + buf = JS_WriteObject(ctx, &len, argv[0], flags); if (!buf) return JS_EXCEPTION; array = JS_NewArrayBufferCopy(ctx, buf, len); @@ -61,8 +69,8 @@ static JSValue js_bjson_write(JSContext *ctx, JSValueConst this_val, } static const JSCFunctionListEntry js_bjson_funcs[] = { - JS_CFUNC_DEF("read", 3, js_bjson_read ), - JS_CFUNC_DEF("write", 1, js_bjson_write ), + JS_CFUNC_DEF("read", 4, js_bjson_read ), + JS_CFUNC_DEF("write", 2, js_bjson_write ), }; static int js_bjson_init(JSContext *ctx, JSModuleDef *m) diff --git a/tests/microbench.js b/tests/microbench.js index 0f05584..1c5f52d 100644 --- a/tests/microbench.js +++ b/tests/microbench.js @@ -228,6 +228,19 @@ function prop_create(n) return n * 4; } +function prop_delete(n) +{ + var obj, j; + obj = {}; + for(j = 0; j < n; j++) { + obj[j] = 1; + } + for(j = 0; j < n; j++) { + delete obj[j]; + } + return n; +} + function array_read(n) { var tab, len, sum, i, j; @@ -945,6 +958,7 @@ function main(argc, argv, g) prop_read, prop_write, prop_create, + prop_delete, array_read, array_write, array_prop_create, diff --git a/tests/test_bjson.js b/tests/test_bjson.js index a6196df..fcbb8e6 100644 --- a/tests/test_bjson.js +++ b/tests/test_bjson.js @@ -1,12 +1,20 @@ import * as bjson from "./bjson.so"; -function assert(b, str) -{ - if (b) { +function assert(actual, expected, message) { + if (arguments.length == 1) + expected = true; + + if (actual === expected) return; - } else { - throw Error("assertion failed: " + str); - } + + if (actual !== null && expected !== null + && typeof actual == 'object' && typeof expected == 'object' + && actual.toString() === expected.toString()) + return; + + throw Error("assertion failed: got |" + actual + "|" + + ", expected |" + expected + "|" + + (message ? " (" + message + ")" : "")); } function toHex(a) @@ -24,6 +32,20 @@ function toHex(a) return s; } +function isArrayLike(a) +{ + return Array.isArray(a) || + (a instanceof Uint8ClampedArray) || + (a instanceof Uint8Array) || + (a instanceof Uint16Array) || + (a instanceof Uint32Array) || + (a instanceof Int8Array) || + (a instanceof Int16Array) || + (a instanceof Int32Array) || + (a instanceof Float32Array) || + (a instanceof Float64Array); +} + function toStr(a) { var s, i, props, prop; @@ -32,7 +54,15 @@ function toStr(a) case "object": if (a === null) return "null"; - if (Array.isArray(a)) { + if (a instanceof Date) { + s = "Date(" + toStr(a.valueOf()) + ")"; + } else if (a instanceof Number) { + s = "Number(" + toStr(a.valueOf()) + ")"; + } else if (a instanceof String) { + s = "String(" + toStr(a.valueOf()) + ")"; + } else if (a instanceof Boolean) { + s = "Boolean(" + toStr(a.valueOf()) + ")"; + } else if (isArrayLike(a)) { s = "["; for(i = 0; i < a.length; i++) { if (i != 0) @@ -85,6 +115,35 @@ function bjson_test(a) } } +/* test multiple references to an object including circular + references */ +function bjson_test_reference() +{ + var array, buf, i, n, array_buffer; + n = 16; + array = []; + for(i = 0; i < n; i++) + array[i] = {}; + array_buffer = new ArrayBuffer(n); + for(i = 0; i < n; i++) { + array[i].next = array[(i + 1) % n]; + array[i].idx = i; + array[i].typed_array = new Uint8Array(array_buffer, i, 1); + } + buf = bjson.write(array, true); + + array = bjson.read(buf, 0, buf.byteLength, true); + + /* check the result */ + for(i = 0; i < n; i++) { + assert(array[i].next, array[(i + 1) % n]); + assert(array[i].idx, i); + assert(array[i].typed_array.buffer, array_buffer); + assert(array[i].typed_array.length, 1); + assert(array[i].typed_array.byteOffset, i); + } +} + function bjson_test_all() { var obj; @@ -111,6 +170,11 @@ function bjson_test_all() BigDecimal("1.233e-1000")]); } + bjson_test([new Date(1234), new String("abc"), new Number(-12.1), new Boolean(true)]); + + bjson_test(new Int32Array([123123, 222111, -32222])); + bjson_test(new Float64Array([123123, 222111.5])); + /* tested with a circular reference */ obj = {}; obj.x = obj; @@ -120,6 +184,8 @@ function bjson_test_all() } catch(e) { assert(e instanceof TypeError); } + + bjson_test_reference(); } bjson_test_all(); diff --git a/tests/test_builtin.js b/tests/test_builtin.js index bbb6629..0ce7b8b 100644 --- a/tests/test_builtin.js +++ b/tests/test_builtin.js @@ -287,6 +287,10 @@ function test_math() assert(Math.ceil(a), 2); assert(Math.imul(0x12345678, 123), -1088058456); assert(Math.fround(0.1), 0.10000000149011612); + assert(Math.hypot() == 0); + assert(Math.hypot(-2) == 2); + assert(Math.hypot(3, 4) == 5); + assert(Math.abs(Math.hypot(3, 4, 5) - 7.0710678118654755) <= 1e-15); } function test_number() diff --git a/tests/test_op.js b/tests/test_language.js index 08656f2..24ddb83 100644 --- a/tests/test_op.js +++ b/tests/test_language.js @@ -311,10 +311,21 @@ function test_template() var a, b; b = 123; a = `abc${b}d`; - assert(a === "abc123d"); + assert(a, "abc123d"); a = String.raw `abc${b}d`; - assert(a === "abc123d"); + assert(a, "abc123d"); + + a = "aaa"; + b = "bbb"; + assert(`aaa${a, b}ccc`, "aaabbbccc"); +} + +function test_template_skip() +{ + var a = "Bar"; + var { b = `${a + `a${a}` }baz` } = {}; + assert(b, "BaraBarbaz"); } function test_object_literal() @@ -358,6 +369,7 @@ test_prototype(); test_arguments(); test_class(); test_template(); +test_template_skip(); test_object_literal(); test_regexp_skip(); test_labels(); diff --git a/tests/test_std.js b/tests/test_std.js index 54917c9..3ea6e34 100644 --- a/tests/test_std.js +++ b/tests/test_std.js @@ -26,6 +26,12 @@ try { std.loadScript("test_assert.js"); } catch(e) {} function test_printf() { assert(std.sprintf("a=%d s=%s", 123, "abc"), "a=123 s=abc"); + assert(std.sprintf("%010d", 123), "0000000123"); + assert(std.sprintf("%x", -2), "fffffffe"); + assert(std.sprintf("%lx", -2), "fffffffffffffffe"); + assert(std.sprintf("%10.1f", 2.1), " 2.1"); + assert(std.sprintf("%*.*f", 10, 2, -2.13), " -2.13"); + assert(std.sprintf("%#lx", 0x7fffffffffffffffn), "0x7fffffffffffffff"); } function test_file1() @@ -119,6 +125,20 @@ function test_popen() os.remove(fname); } +function test_ext_json() +{ + var expected, input, obj; + expected = '{"x":false,"y":true,"z2":null,"a":[1,8,160],"s":"str"}'; + input = `{ "x":false, /*comments are allowed */ + "y":true, // also a comment + z2:null, // unquoted property names + "a":[+1,0o10,0xa0,], // plus prefix, octal, hexadecimal + "s":"str",} // trailing comma in objects and arrays + `; + obj = std.parseExtJSON(input); + assert(JSON.stringify(obj), expected); +} + function test_os() { var fd, fpath, fname, fdir, buf, buf2, i, files, err, fdate, st, link_path; @@ -258,3 +278,4 @@ test_popen(); test_os(); test_os_exec(); test_timer(); +test_ext_json(); diff --git a/tests/test_worker.js b/tests/test_worker.js new file mode 100644 index 0000000..0a4b3dc --- /dev/null +++ b/tests/test_worker.js @@ -0,0 +1,93 @@ +/* os.Worker API test */ +import * as std from "std"; +import * as os from "os"; + +function assert(actual, expected, message) { + if (arguments.length == 1) + expected = true; + + if (actual === expected) + return; + + if (actual !== null && expected !== null + && typeof actual == 'object' && typeof expected == 'object' + && actual.toString() === expected.toString()) + return; + + throw Error("assertion failed: got |" + actual + "|" + + ", expected |" + expected + "|" + + (message ? " (" + message + ")" : "")); +} + +var worker; + +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(); +`); + + counter = 0; + worker.onmessage = function (e) { + var ev = e.data; +// print("recv", JSON.stringify(ev)); + switch(ev.type) { + case "num": + assert(ev.num, counter); + counter++; + if (counter == 10) { + /* test SharedArrayBuffer modification */ + let sab = new SharedArrayBuffer(10); + let buf = new Uint8Array(sab); + worker.postMessage({ type: "sab", buf: buf }); + } + break; + case "sab_done": + { + let buf = ev.buf; + /* check that the SharedArrayBuffer was modified */ + assert(buf[2], 10); + worker.postMessage({ type: "abort" }); + } + break; + case "done": + /* terminate */ + worker.onmessage = null; + break; + } + }; +} + + +test_worker(); |