Compare commits

...

5 Commits

Author SHA1 Message Date
Nick Vatamaniuc
8b43bae10b
Run test262 tests in CI
Run test262 tests for Linux, macOS and Cosmopolitan

Add a Makefile `test2-bootstrap` helper to clone and patch test262 tests at
a particular commit. Running with the latest commit noticed a few tests were
failing and added them to the errors list.

The CI environment, especially for macOS, was a bit more unpredictable and was
failing in atomics tests due to lower timeouts in the `atomicsHelper` so double
the timeouts. The time didn't worsen too much. It still takes about 2-3 min.
2025-09-17 18:46:55 -04:00
Fabrice Bellard
fa628f8c52 new release 2025-09-13 10:51:18 +02:00
Fabrice Bellard
391cd3feff Fix crash on failure to read bytecode (penneryu) 2025-09-13 10:22:24 +02:00
Fabrice Bellard
9f6c190731 more efficient handling of strings in JSON.stringify() 2025-09-10 18:40:10 +02:00
Fabrice Bellard
8a0a6e92d2 better pretty printing of strings - removed String.prototype.__quote() 2025-09-10 18:25:34 +02:00
8 changed files with 166 additions and 109 deletions

View File

@ -36,6 +36,10 @@ jobs:
- name: Run microbench - name: Run microbench
run: | run: |
make microbench make microbench
- name: Run test262
run: |
make test2-bootstrap
make test2
linux-lto: linux-lto:
name: Linux LTO name: Linux LTO
@ -138,6 +142,10 @@ jobs:
- name: Run built-in tests - name: Run built-in tests
run: | run: |
make test make test
- name: Run test262
run: |
make test2-bootstrap
make test2
macos-asan: macos-asan:
runs-on: macos-latest runs-on: macos-latest
@ -172,7 +180,12 @@ jobs:
- name: Build + test - name: Build + test
uses: vmactions/freebsd-vm@v1 uses: vmactions/freebsd-vm@v1
with: with:
# This VM has a tendency to get stuck in the "VM is booting" cycle
# for quite a while, so set a shorter timeout
timeout-minutes: 15
usesh: true usesh: true
copyback: false
mem: 16384
prepare: | prepare: |
pkg install -y gmake pkg install -y gmake
run: | run: |
@ -202,6 +215,10 @@ jobs:
- name: Run built-in tests - name: Run built-in tests
run: | run: |
make CONFIG_COSMO=y test make CONFIG_COSMO=y test
- name: Run test262
run: |
make test2-bootstrap
make CONFIG_COSMO=y test2
mingw-windows: mingw-windows:
name: MinGW Windows target name: MinGW Windows target

View File

@ -1,3 +1,5 @@
2025-09-13:
- added JSON modules and import attributes - added JSON modules and import attributes
- added JS_PrintValue() API - added JS_PrintValue() API
- qjs: pretty print objects in print() and console.log() - qjs: pretty print objects in print() and console.log()
@ -12,6 +14,7 @@
accept JSON5 modules accept JSON5 modules
- added JS_FreePropertyEnum() and JS_AtomToCStringLen() API - added JS_FreePropertyEnum() and JS_AtomToCStringLen() API
- added Error.isError() - added Error.isError()
- misc bug fixes
2025-04-26: 2025-04-26:

View File

@ -54,6 +54,10 @@ PREFIX?=/usr/local
# use UB sanitizer # use UB sanitizer
#CONFIG_UBSAN=y #CONFIG_UBSAN=y
# TEST262 bootstrap config: commit id and shallow "since" parameter
TEST262_COMMIT?=af3d908437b0912513a594e7167f17658e72d88b
TEST262_SINCE?=2025-09-14
OBJDIR=.obj OBJDIR=.obj
ifdef CONFIG_ASAN ifdef CONFIG_ASAN
@ -464,6 +468,15 @@ stats: qjs$(EXE)
microbench: qjs$(EXE) microbench: qjs$(EXE)
$(WINE) ./qjs$(EXE) --std tests/microbench.js $(WINE) ./qjs$(EXE) --std tests/microbench.js
ifeq ($(wildcard test262/features.txt),)
test2-bootstrap:
git clone --single-branch --shallow-since=$(TEST262_SINCE) https://github.com/tc39/test262.git
(cd test262 && git checkout -q $(TEST262_COMMIT) && patch -p1 < ../tests/test262.patch && cd ..)
else
test2-bootstrap:
(cd test262 && git fetch && git reset --hard $(TEST262_COMMIT) && patch -p1 < ../tests/test262.patch && cd ..)
endif
ifeq ($(wildcard test262o/tests.txt),) ifeq ($(wildcard test262o/tests.txt),)
test2o test2o-update: test2o test2o-update:
@echo test262o tests not installed @echo test262o tests not installed

View File

@ -1 +1 @@
2025-04-26 2025-09-13

221
quickjs.c
View File

@ -13002,73 +13002,6 @@ static JSValue JS_ToStringCheckObject(JSContext *ctx, JSValueConst val)
return JS_ToString(ctx, val); return JS_ToString(ctx, val);
} }
static JSValue JS_ToQuotedString(JSContext *ctx, JSValueConst val1)
{
JSValue val;
JSString *p;
int i;
uint32_t c;
StringBuffer b_s, *b = &b_s;
char buf[16];
val = JS_ToStringCheckObject(ctx, val1);
if (JS_IsException(val))
return val;
p = JS_VALUE_GET_STRING(val);
if (string_buffer_init(ctx, b, p->len + 2))
goto fail;
if (string_buffer_putc8(b, '\"'))
goto fail;
for(i = 0; i < p->len; ) {
c = string_getc(p, &i);
switch(c) {
case '\t':
c = 't';
goto quote;
case '\r':
c = 'r';
goto quote;
case '\n':
c = 'n';
goto quote;
case '\b':
c = 'b';
goto quote;
case '\f':
c = 'f';
goto quote;
case '\"':
case '\\':
quote:
if (string_buffer_putc8(b, '\\'))
goto fail;
if (string_buffer_putc8(b, c))
goto fail;
break;
default:
if (c < 32 || is_surrogate(c)) {
snprintf(buf, sizeof(buf), "\\u%04x", c);
if (string_buffer_puts8(b, buf))
goto fail;
} else {
if (string_buffer_putc(b, c))
goto fail;
}
break;
}
}
if (string_buffer_putc8(b, '\"'))
goto fail;
JS_FreeValue(ctx, val);
return string_buffer_end(b);
fail:
JS_FreeValue(ctx, val);
string_buffer_free(b);
return JS_EXCEPTION;
}
#define JS_PRINT_MAX_DEPTH 8 #define JS_PRINT_MAX_DEPTH 8
typedef struct { typedef struct {
@ -13126,18 +13059,61 @@ static uint32_t js_string_get_length(JSValueConst val)
} }
} }
static void js_dump_char(JSPrintValueState *s, int c, int sep) /* pretty print the first 'len' characters of 'p' */
static void js_print_string1(JSPrintValueState *s, JSString *p, int len, int sep)
{ {
if (c == sep || c == '\\') { uint8_t buf[UTF8_CHAR_LEN_MAX];
int l, i, c, c1;
for(i = 0; i < len; i++) {
c = string_get(p, i);
switch(c) {
case '\t':
c = 't';
goto quote;
case '\r':
c = 'r';
goto quote;
case '\n':
c = 'n';
goto quote;
case '\b':
c = 'b';
goto quote;
case '\f':
c = 'f';
goto quote;
case '\\':
quote:
js_putc(s, '\\'); js_putc(s, '\\');
js_putc(s, c); js_putc(s, c);
} else if (c >= ' ' && c <= 126) { break;
default:
if (c == sep)
goto quote;
if (c >= 32 && c <= 126) {
js_putc(s, c); js_putc(s, c);
} else if (c == '\n') { } else if (c < 32 ||
js_putc(s, '\\'); (c >= 0x7f && c <= 0x9f)) {
js_putc(s, 'n'); escape:
} else {
js_printf(s, "\\u%04x", c); js_printf(s, "\\u%04x", c);
} else {
if (is_hi_surrogate(c)) {
if ((i + 1) >= len)
goto escape;
c1 = string_get(p, i + 1);
if (!is_lo_surrogate(c1))
goto escape;
i++;
c = from_surrogate(c, c1);
} else if (is_lo_surrogate(c)) {
goto escape;
}
l = unicode_to_utf8(buf, c);
s->write_func(s->write_opaque, (char *)buf, l);
}
break;
}
} }
} }
@ -13146,12 +13122,10 @@ static void js_print_string_rec(JSPrintValueState *s, JSValueConst val,
{ {
if (JS_VALUE_GET_TAG(val) == JS_TAG_STRING) { if (JS_VALUE_GET_TAG(val) == JS_TAG_STRING) {
JSString *p = JS_VALUE_GET_STRING(val); JSString *p = JS_VALUE_GET_STRING(val);
uint32_t i, len; uint32_t len;
if (pos < s->options.max_string_length) { if (pos < s->options.max_string_length) {
len = min_uint32(p->len, s->options.max_string_length - pos); len = min_uint32(p->len, s->options.max_string_length - pos);
for(i = 0; i < len; i++) { js_print_string1(s, p, len, sep);
js_dump_char(s, string_get(p, i), sep);
}
} }
} else if (JS_VALUE_GET_TAG(val) == JS_TAG_STRING_ROPE) { } else if (JS_VALUE_GET_TAG(val) == JS_TAG_STRING_ROPE) {
JSStringRope *r = JS_VALUE_GET_PTR(val); JSStringRope *r = JS_VALUE_GET_PTR(val);
@ -13224,9 +13198,7 @@ static void js_print_atom(JSPrintValueState *s, JSAtom atom)
} }
} else { } else {
js_putc(s, '"'); js_putc(s, '"');
for(i = 0; i < p->len; i++) { js_print_string1(s, p, p->len, '\"');
js_dump_char(s, string_get(p, i), '\"');
}
js_putc(s, '"'); js_putc(s, '"');
} }
} }
@ -34952,6 +34924,7 @@ static void free_function_bytecode(JSRuntime *rt, JSFunctionBytecode *b)
JS_AtomGetStrRT(rt, buf, sizeof(buf), b->func_name)); JS_AtomGetStrRT(rt, buf, sizeof(buf), b->func_name));
} }
#endif #endif
if (b->byte_code_buf)
free_bytecode_atoms(rt, b->byte_code_buf, b->byte_code_len, TRUE); free_bytecode_atoms(rt, b->byte_code_buf, b->byte_code_len, TRUE);
if (b->vardefs) { if (b->vardefs) {
@ -43859,12 +43832,6 @@ static JSValue js_string_trim(JSContext *ctx, JSValueConst this_val,
return ret; return ret;
} }
static JSValue js_string___quote(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv)
{
return JS_ToQuotedString(ctx, this_val);
}
/* return 0 if before the first char */ /* return 0 if before the first char */
static int string_prevc(JSString *p, int *pidx) static int string_prevc(JSString *p, int *pidx)
{ {
@ -44352,7 +44319,6 @@ static const JSCFunctionListEntry js_string_proto_funcs[] = {
JS_ALIAS_DEF("trimLeft", "trimStart" ), JS_ALIAS_DEF("trimLeft", "trimStart" ),
JS_CFUNC_DEF("toString", 0, js_string_toString ), JS_CFUNC_DEF("toString", 0, js_string_toString ),
JS_CFUNC_DEF("valueOf", 0, js_string_toString ), JS_CFUNC_DEF("valueOf", 0, js_string_toString ),
JS_CFUNC_DEF("__quote", 1, js_string___quote ),
JS_CFUNC_MAGIC_DEF("toLowerCase", 0, js_string_toLowerCase, 1 ), JS_CFUNC_MAGIC_DEF("toLowerCase", 0, js_string_toLowerCase, 1 ),
JS_CFUNC_MAGIC_DEF("toUpperCase", 0, js_string_toLowerCase, 0 ), JS_CFUNC_MAGIC_DEF("toUpperCase", 0, js_string_toLowerCase, 0 ),
JS_CFUNC_MAGIC_DEF("toLocaleLowerCase", 0, js_string_toLowerCase, 1 ), JS_CFUNC_MAGIC_DEF("toLocaleLowerCase", 0, js_string_toLowerCase, 1 ),
@ -46667,10 +46633,72 @@ typedef struct JSONStringifyContext {
StringBuffer *b; StringBuffer *b;
} JSONStringifyContext; } JSONStringifyContext;
static JSValue JS_ToQuotedStringFree(JSContext *ctx, JSValue val) { static int JS_ToQuotedString(JSContext *ctx, StringBuffer *b, JSValueConst val1)
JSValue r = JS_ToQuotedString(ctx, val); {
JSValue val;
JSString *p;
int i;
uint32_t c;
char buf[16];
val = JS_ToStringCheckObject(ctx, val1);
if (JS_IsException(val))
return -1;
p = JS_VALUE_GET_STRING(val);
if (string_buffer_putc8(b, '\"'))
goto fail;
for(i = 0; i < p->len; ) {
c = string_getc(p, &i);
switch(c) {
case '\t':
c = 't';
goto quote;
case '\r':
c = 'r';
goto quote;
case '\n':
c = 'n';
goto quote;
case '\b':
c = 'b';
goto quote;
case '\f':
c = 'f';
goto quote;
case '\"':
case '\\':
quote:
if (string_buffer_putc8(b, '\\'))
goto fail;
if (string_buffer_putc8(b, c))
goto fail;
break;
default:
if (c < 32 || is_surrogate(c)) {
snprintf(buf, sizeof(buf), "\\u%04x", c);
if (string_buffer_puts8(b, buf))
goto fail;
} else {
if (string_buffer_putc(b, c))
goto fail;
}
break;
}
}
if (string_buffer_putc8(b, '\"'))
goto fail;
JS_FreeValue(ctx, val); JS_FreeValue(ctx, val);
return r; return 0;
fail:
JS_FreeValue(ctx, val);
return -1;
}
static int JS_ToQuotedStringFree(JSContext *ctx, StringBuffer *b, JSValue val) {
int ret = JS_ToQuotedString(ctx, b, val);
JS_FreeValue(ctx, val);
return ret;
} }
static JSValue js_json_check(JSContext *ctx, JSONStringifyContext *jsc, static JSValue js_json_check(JSContext *ctx, JSONStringifyContext *jsc,
@ -46853,13 +46881,11 @@ static int js_json_to_str(JSContext *ctx, JSONStringifyContext *jsc,
if (!JS_IsUndefined(v)) { if (!JS_IsUndefined(v)) {
if (has_content) if (has_content)
string_buffer_putc8(jsc->b, ','); string_buffer_putc8(jsc->b, ',');
prop = JS_ToQuotedStringFree(ctx, prop); string_buffer_concat_value(jsc->b, sep);
if (JS_IsException(prop)) { if (JS_ToQuotedString(ctx, jsc->b, prop)) {
JS_FreeValue(ctx, v); JS_FreeValue(ctx, v);
goto exception; goto exception;
} }
string_buffer_concat_value(jsc->b, sep);
string_buffer_concat_value(jsc->b, prop);
string_buffer_putc8(jsc->b, ':'); string_buffer_putc8(jsc->b, ':');
string_buffer_concat_value(jsc->b, sep1); string_buffer_concat_value(jsc->b, sep1);
if (js_json_to_str(ctx, jsc, val, v, indent1)) if (js_json_to_str(ctx, jsc, val, v, indent1))
@ -46887,10 +46913,7 @@ static int js_json_to_str(JSContext *ctx, JSONStringifyContext *jsc,
switch (JS_VALUE_GET_NORM_TAG(val)) { switch (JS_VALUE_GET_NORM_TAG(val)) {
case JS_TAG_STRING: case JS_TAG_STRING:
case JS_TAG_STRING_ROPE: case JS_TAG_STRING_ROPE:
val = JS_ToQuotedStringFree(ctx, val); return JS_ToQuotedStringFree(ctx, jsc->b, val);
if (JS_IsException(val))
goto exception;
goto concat_value;
case JS_TAG_FLOAT64: case JS_TAG_FLOAT64:
if (!isfinite(JS_VALUE_GET_FLOAT64(val))) { if (!isfinite(JS_VALUE_GET_FLOAT64(val))) {
val = JS_NULL; val = JS_NULL;

View File

@ -116,6 +116,7 @@ for-of
generators generators
globalThis globalThis
hashbang hashbang
immutable-arraybuffer=skip
import-attributes import-attributes
import-defer=skip import-defer=skip
import.meta import.meta

View File

@ -14,9 +14,9 @@ index 9828b15..4a5919d 100644
+// small: 200, +// small: 200,
+// long: 1000, +// long: 1000,
+// huge: 10000, +// huge: 10000,
+ yield: 20, + yield: 40,
+ small: 20, + small: 40,
+ long: 100, + long: 200,
+ huge: 1000, + huge: 1000,
}; };

View File

@ -86,7 +86,7 @@ function toStr(a)
case "undefined": case "undefined":
return "undefined"; return "undefined";
case "string": case "string":
return a.__quote(); return JSON.stringify(a);
case "number": case "number":
if (a == 0 && 1 / a < 0) if (a == 0 && 1 / a < 0)
return "-0"; return "-0";