diff --git a/TODO b/TODO index e0f6d60..7bf6e36 100644 --- a/TODO +++ b/TODO @@ -63,4 +63,4 @@ Test262o: 0/11262 errors, 463 excluded Test262o commit: 7da91bceb9ce7613f87db47ddd1292a2dda58b42 (es5-tests branch) Test262: -Result: 60/83394 errors, 3348 excluded, 6090 skipped +Result: 60/83436 errors, 3348 excluded, 6069 skipped diff --git a/quickjs-atom.h b/quickjs-atom.h index dac2df6..dc64f2f 100644 --- a/quickjs-atom.h +++ b/quickjs-atom.h @@ -147,6 +147,7 @@ DEF(flags, "flags") DEF(global, "global") DEF(unicode, "unicode") DEF(raw, "raw") +DEF(rawJSON, "rawJSON") DEF(new_target, "new.target") DEF(this_active_func, "this.active_func") DEF(home_object, "") diff --git a/quickjs.c b/quickjs.c index 0f681d9..2b33bfa 100644 --- a/quickjs.c +++ b/quickjs.c @@ -168,6 +168,7 @@ enum { JS_CLASS_REGEXP_STRING_ITERATOR, /* u.regexp_string_iterator_data */ JS_CLASS_GENERATOR, /* u.generator_data */ JS_CLASS_GLOBAL_OBJECT, /* u.global_object */ + JS_CLASS_RAWJSON, JS_CLASS_PROXY, /* u.proxy_data */ JS_CLASS_PROMISE, /* u.promise_data */ JS_CLASS_PROMISE_RESOLVE_FUNCTION, /* u.promise_function_data */ @@ -1597,6 +1598,7 @@ static JSClassShortDef const js_std_class_def[] = { { 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 */ { JS_ATOM_Object, js_global_object_finalizer, js_global_object_mark }, /* JS_CLASS_GLOBAL_OBJECT */ + { JS_ATOM_Object, NULL, NULL }, /* JS_CLASS_RAWJSON */ }; static int init_class_range(JSRuntime *rt, JSClassShortDef const *tab, @@ -21571,7 +21573,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 */ + BOOL ext_json; /* JSON parsing: true if accepting JSON superset */ GetLineColCache get_line_col_cache; } JSParseState; @@ -48629,23 +48631,189 @@ static int json_parse_expect(JSParseState *s, int tok) return json_next_token(s); } -static JSValue json_parse_value(JSParseState *s) + +typedef struct { + int count; + uint32_t hash_size; + struct JSONParseRecordEntry *entries; + uint32_t *hash_table; +} JSONParseRecordObject; + +typedef struct JSONParseRecord { + JSValue value; + union { + JSONParseRecordObject obj; + struct { + int count; + struct JSONParseRecord *elements; + } array; + struct { + uint32_t source_pos; + uint32_t source_len; + } primitive; + } u; +} JSONParseRecord; + +typedef struct JSONParseRecordEntry { + JSAtom atom; + uint32_t hash_next; + JSONParseRecord parse_record; +} JSONParseRecordEntry; + +static void json_parse_record_init_obj(JSContext *ctx, JSONParseRecord *pr, JSValueConst val) +{ + pr->value = JS_DupValue(ctx, val); + pr->u.obj.count = 0; + pr->u.obj.entries = NULL; + pr->u.obj.hash_table = NULL; + pr->u.obj.hash_size = 0; +} + +static void json_parse_record_init_array(JSContext *ctx, JSONParseRecord *pr, JSValueConst val) +{ + pr->value = JS_DupValue(ctx, val); + pr->u.array.count = 0; + pr->u.array.elements = NULL; +} + +static void json_parse_record_init_primitive(JSContext *ctx, JSONParseRecord *pr, JSValueConst val, + uint32_t source_pos, uint32_t source_len) +{ + pr->value = JS_DupValue(ctx, val); + pr->u.primitive.source_pos = source_pos; + pr->u.primitive.source_len = source_len; +} + +static int json_parse_record_resize_hash(JSContext *ctx, JSONParseRecordObject *po, uint32_t new_hash_size) +{ + uint32_t i, h, *new_hash_table; + JSONParseRecordEntry *e; + + new_hash_table = js_malloc(ctx, sizeof(new_hash_table[0]) * new_hash_size); + if (!new_hash_table) + return -1; + js_free(ctx, po->hash_table); + po->hash_table = new_hash_table; + po->hash_size = new_hash_size; + + for(i = 0; i < po->hash_size; i++) { + po->hash_table[i] = -1; + } + for(i = 0; i < po->count; i++) { + e = &po->entries[i]; + h = e->atom & (po->hash_size - 1); + e->hash_next = po->hash_table[h]; + po->hash_table[h] = i; + } + return 0; +} + +static JSONParseRecord *json_parse_record_add(JSContext *ctx, JSONParseRecord *pr, JSAtom key, int *psize) +{ + JSONParseRecordObject *po = &pr->u.obj; + JSONParseRecordEntry *e; + JSONParseRecord *pr1; + uint32_t h; + + if (js_resize_array(ctx, (void **)&po->entries, sizeof(po->entries[0]), + psize, po->count + 1)) { + return NULL; + } + /* don't use a hash table when the number of entries is small */ + if (po->count >= 8 && (po->count + 1) > po->hash_size) { + int hash_bits = 32 - clz32(po->count); + if (json_parse_record_resize_hash(ctx, po, 1 << hash_bits)) + return NULL; + } + + e = &po->entries[po->count++]; + e->atom = JS_DupAtom(ctx, key); + pr1 = &e->parse_record; + pr1->value = JS_UNDEFINED; + if (po->hash_size != 0) { + h = key & (po->hash_size - 1); + e->hash_next = po->hash_table[h]; + po->hash_table[h] = po->count - 1; + } + return pr1; +} + +static JSONParseRecord *json_parse_record_find(JSONParseRecord *pr, JSAtom key) +{ + JSONParseRecordObject *po = &pr->u.obj; + JSONParseRecordEntry *e; + uint32_t h, i; + + if (po->hash_size == 0) { + for(i = 0; i < po->count; i++) { + if (po->entries[i].atom == key) + return &po->entries[i].parse_record; + } + } else { + h = key & (po->hash_size - 1); + i = po->hash_table[h]; + while (i != -1) { + e = &po->entries[i]; + if (e->atom == key) + return &e->parse_record; + i = e->hash_next; + } + } + return NULL; +} + +static void json_free_parse_record(JSContext *ctx, JSONParseRecord *pr) +{ + int i; + if (!pr) + return; + if (JS_IsObject(pr->value)) { + if (JS_IsArray(ctx, pr->value)) { + for(i = 0; i < pr->u.array.count; i++) { + json_free_parse_record(ctx, &pr->u.array.elements[i]); + } + js_free(ctx, pr->u.array.elements); + } else { + for(i = 0; i < pr->u.obj.count; i++) { + JS_FreeAtom(ctx, pr->u.obj.entries[i].atom); + json_free_parse_record(ctx, &pr->u.obj.entries[i].parse_record); + } + js_free(ctx, pr->u.obj.entries); + js_free(ctx, pr->u.obj.hash_table); + } + } + JS_FreeValue(ctx, pr->value); + pr->value = JS_UNDEFINED; /* fail safe */ +} + +/* 'pr' can be NULL */ +static JSValue json_parse_value(JSParseState *s, JSONParseRecord *pr) { JSContext *ctx = s->ctx; JSValue val = JS_NULL; int ret; + if (pr) { + pr->value = JS_UNDEFINED; + } + switch(s->token.val) { case '{': { JSValue prop_val; JSAtom prop_name; - + JSONParseRecord *pr1; + int pr_size; + if (json_next_token(s)) goto fail; val = JS_NewObject(ctx); if (JS_IsException(val)) goto fail; + if (pr) { + json_parse_record_init_obj(ctx, pr, val); + pr_size = 0; + } if (s->token.val != '}') { for(;;) { if (s->token.val == TOK_STRING) { @@ -48662,7 +48830,14 @@ static JSValue json_parse_value(JSParseState *s) goto fail1; if (json_parse_expect(s, ':')) goto fail1; - prop_val = json_parse_value(s); + if (pr) { + pr1 = json_parse_record_add(ctx, pr, prop_name, &pr_size); + if (!pr1) + goto fail1; + } else { + pr1 = NULL; + } + prop_val = json_parse_value(s, pr1); if (JS_IsException(prop_val)) { fail1: JS_FreeAtom(ctx, prop_name); @@ -48690,16 +48865,31 @@ static JSValue json_parse_value(JSParseState *s) { JSValue el; uint32_t idx; - + JSONParseRecord *pr1; + int pr_size; + if (json_next_token(s)) goto fail; val = JS_NewArray(ctx); if (JS_IsException(val)) goto fail; + if (pr) { + json_parse_record_init_array(ctx, pr, val); + pr_size = 0; + } if (s->token.val != ']') { idx = 0; for(;;) { - el = json_parse_value(s); + if (pr) { + if (js_resize_array(ctx, (void **)&pr->u.array.elements, sizeof(pr->u.array.elements[0]), + &pr_size, pr->u.array.count + 1)) + goto fail; + pr1 = &pr->u.array.elements[pr->u.array.count++]; + pr1->value = JS_UNDEFINED; + } else { + pr1 = NULL; + } + el = json_parse_value(s, pr1); if (JS_IsException(el)) goto fail; ret = JS_DefinePropertyValueUint32(ctx, val, idx, el, JS_PROP_C_W_E); @@ -48720,11 +48910,21 @@ static JSValue json_parse_value(JSParseState *s) break; case TOK_STRING: val = JS_DupValue(ctx, s->token.u.str.str); + if (pr) { + json_parse_record_init_primitive(ctx, pr, val, + s->token.ptr - s->buf_start, + s->buf_ptr - s->token.ptr); + } if (json_next_token(s)) goto fail; break; case TOK_NUMBER: val = s->token.u.num.val; + if (pr) { + json_parse_record_init_primitive(ctx, pr, val, + s->token.ptr - s->buf_start, + s->buf_ptr - s->token.ptr); + } if (json_next_token(s)) goto fail; break; @@ -48732,8 +48932,18 @@ static JSValue json_parse_value(JSParseState *s) 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); + if (pr) { + json_parse_record_init_primitive(ctx, pr, val, + s->token.ptr - s->buf_start, + s->buf_ptr - s->token.ptr); + } } else if (s->token.u.ident.atom == JS_ATOM_null) { val = JS_NULL; + if (pr) { + json_parse_record_init_primitive(ctx, pr, val, + s->token.ptr - s->buf_start, + s->buf_ptr - s->token.ptr); + } } else if (s->token.u.ident.atom == JS_ATOM_NaN && s->ext_json) { /* Note: json5 identifier handling is ambiguous e.g. is '{ NaN: 1 }' a valid JSON5 production ? */ @@ -48758,12 +48968,13 @@ static JSValue json_parse_value(JSParseState *s) } return val; fail: + json_free_parse_record(ctx, pr); JS_FreeValue(ctx, val); return JS_EXCEPTION; } -JSValue JS_ParseJSON2(JSContext *ctx, const char *buf, size_t buf_len, - const char *filename, int flags) +JSValue JS_ParseJSON3(JSContext *ctx, const char *buf, size_t buf_len, + const char *filename, int flags, JSONParseRecord *pr) { JSParseState s1, *s = &s1; JSValue val = JS_UNDEFINED; @@ -48772,12 +48983,14 @@ JSValue JS_ParseJSON2(JSContext *ctx, const char *buf, size_t buf_len, s->ext_json = ((flags & JS_PARSE_JSON_EXT) != 0); if (json_next_token(s)) goto fail; - val = json_parse_value(s); + val = json_parse_value(s, pr); if (JS_IsException(val)) goto fail; if (s->token.val != TOK_EOF) { - if (js_parse_error(s, "unexpected data at the end")) + if (js_parse_error(s, "unexpected data at the end")) { + json_free_parse_record(ctx, pr); goto fail; + } } return val; fail: @@ -48786,17 +48999,25 @@ JSValue JS_ParseJSON2(JSContext *ctx, const char *buf, size_t buf_len, return JS_EXCEPTION; } +JSValue JS_ParseJSON2(JSContext *ctx, const char *buf, size_t buf_len, + const char *filename, int flags) +{ + return JS_ParseJSON3(ctx, buf, buf_len, filename, flags, NULL); +} + JSValue JS_ParseJSON(JSContext *ctx, const char *buf, size_t buf_len, const char *filename) { - return JS_ParseJSON2(ctx, buf, buf_len, filename, 0); + return JS_ParseJSON3(ctx, buf, buf_len, filename, 0, NULL); } +/* if pr != NULL, then pr->value = holder by construction */ static JSValue internalize_json_property(JSContext *ctx, JSValueConst holder, - JSAtom name, JSValueConst reviver) + JSAtom name, JSValueConst reviver, + const char *text_str, JSONParseRecord *pr) { - JSValue val, new_el, name_val, res; - JSValueConst args[2]; + JSValue val, new_el, name_val, res, context; + JSValueConst args[3]; int ret, is_array; uint32_t i, len = 0; JSAtom prop; @@ -48809,6 +49030,29 @@ static JSValue internalize_json_property(JSContext *ctx, JSValueConst holder, val = JS_GetProperty(ctx, holder, name); if (JS_IsException(val)) return val; + + if (pr) { + if (JS_IsArray(ctx, pr->value)) { + if (__JS_AtomIsTaggedInt(name)) { + uint32_t idx = __JS_AtomToUInt32(name); + if (idx < pr->u.array.count) { + pr = &pr->u.array.elements[idx]; + } else { + pr = NULL; + } + } + } else { + pr = json_parse_record_find(pr, name); + } + if (pr && !js_same_value(ctx, pr->value, val)) { + pr = NULL; + } + } + + context = JS_NewObject(ctx); + if (JS_IsException(context)) + goto fail; + if (JS_IsObject(val)) { is_array = JS_IsArray(ctx, val); if (is_array < 0) @@ -48829,7 +49073,7 @@ static JSValue internalize_json_property(JSContext *ctx, JSValueConst holder, } else { prop = JS_DupAtom(ctx, atoms[i].atom); } - new_el = internalize_json_property(ctx, val, prop, reviver); + new_el = internalize_json_property(ctx, val, prop, reviver, text_str, pr); if (JS_IsException(new_el)) { JS_FreeAtom(ctx, prop); goto fail; @@ -48843,6 +49087,15 @@ static JSValue internalize_json_property(JSContext *ctx, JSValueConst holder, if (ret < 0) goto fail; } + } else { + if (pr) { + new_el = JS_NewStringLen(ctx, text_str + pr->u.primitive.source_pos, + pr->u.primitive.source_len); + if (JS_IsException(new_el)) + goto fail; + if (JS_DefinePropertyValue(ctx, context, JS_ATOM_source, new_el, JS_PROP_C_W_E) < 0) + goto fail; + } } JS_FreePropertyEnum(ctx, atoms, len); atoms = NULL; @@ -48851,12 +49104,15 @@ static JSValue internalize_json_property(JSContext *ctx, JSValueConst holder, goto fail; args[0] = name_val; args[1] = val; - res = JS_Call(ctx, reviver, holder, 2, args); + args[2] = context; + res = JS_Call(ctx, reviver, holder, 3, args); JS_FreeValue(ctx, name_val); JS_FreeValue(ctx, val); + JS_FreeValue(ctx, context); return res; fail: JS_FreePropertyEnum(ctx, atoms, len); + JS_FreeValue(ctx, context); JS_FreeValue(ctx, val); return JS_EXCEPTION; } @@ -48864,37 +49120,113 @@ static JSValue internalize_json_property(JSContext *ctx, JSValueConst holder, static JSValue js_json_parse(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { - JSValue obj, root; - JSValueConst reviver; + JSValue obj; const char *str; size_t len; - + str = JS_ToCStringLen(ctx, &len, argv[0]); if (!str) return JS_EXCEPTION; - obj = JS_ParseJSON(ctx, str, len, ""); - JS_FreeCString(ctx, str); - if (JS_IsException(obj)) - return obj; if (argc > 1 && JS_IsFunction(ctx, argv[1])) { + JSONParseRecord pr_s, *pr = &pr_s, *pr1; + JSValue root; + JSValueConst reviver; + int size; + reviver = argv[1]; root = JS_NewObject(ctx); - if (JS_IsException(root)) { - JS_FreeValue(ctx, obj); - return JS_EXCEPTION; - } + if (JS_IsException(root)) + goto fail; + json_parse_record_init_obj(ctx, pr, root); + size = 0; + pr1 = json_parse_record_add(ctx, pr, JS_ATOM_empty_string, &size); + if (!pr1) + goto fail1; + + obj = JS_ParseJSON3(ctx, str, len, "", 0, pr1); + if (JS_IsException(obj)) + goto fail1; + if (JS_DefinePropertyValue(ctx, root, JS_ATOM_empty_string, obj, JS_PROP_C_W_E) < 0) { + JS_FreeValue(ctx, obj); + fail1: + json_free_parse_record(ctx, pr); JS_FreeValue(ctx, root); - return JS_EXCEPTION; + goto fail; } + obj = internalize_json_property(ctx, root, JS_ATOM_empty_string, - reviver); + reviver, str, pr); + json_free_parse_record(ctx, pr); JS_FreeValue(ctx, root); + } else { + obj = JS_ParseJSON3(ctx, str, len, "", 0, NULL); } + JS_FreeCString(ctx, str); return obj; + fail: + JS_FreeCString(ctx, str); + return JS_EXCEPTION; } +static JSValue js_json_isRawJSON(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + JSValueConst obj = argv[0]; + if (JS_VALUE_GET_TAG(obj) == JS_TAG_OBJECT) { + JSObject *p = JS_VALUE_GET_OBJ(obj); + return JS_NewBool(ctx, p->class_id == JS_CLASS_RAWJSON); + } else { + return JS_FALSE; + } +} + +static BOOL is_valid_raw_json_char(int c) +{ + return ((c >= 'a' && c <= 'z') || + (c >= '0' && c <= '9') || + c == '-' || + c == '"'); +} + +static JSValue js_json_rawJSON(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + JSValue str, res, obj; + JSString *p; + str = JS_ToString(ctx, argv[0]); + if (JS_IsException(str)) + return str; + p = JS_VALUE_GET_STRING(str); + if (p->len == 0 || + !is_valid_raw_json_char(string_get(p, 0)) || + !is_valid_raw_json_char(string_get(p, p->len - 1))) { + goto syntax_error; + } + res = js_json_parse(ctx, JS_UNDEFINED, 1, (JSValueConst *)&str); + if (JS_IsException(res)) { + syntax_error: + JS_ThrowSyntaxError(ctx, "invalid rawJSON string"); + goto fail; + } + JS_FreeValue(ctx, res); + + obj = JS_NewObjectProtoClass(ctx, JS_NULL, JS_CLASS_RAWJSON); + if (JS_IsException(obj)) + goto fail; + if (JS_DefinePropertyValue(ctx, obj, JS_ATOM_rawJSON, str, JS_PROP_ENUMERABLE) < 0) { + JS_FreeValue(ctx, obj); + return JS_EXCEPTION; + } + JS_PreventExtensions(ctx, obj); + return obj; + fail: + JS_FreeValue(ctx, str); + return JS_EXCEPTION; +} + + typedef struct JSONStringifyContext { JSValueConst replacer_func; JSValue stack; @@ -49064,11 +49396,18 @@ static int js_json_to_str(JSContext *ctx, JSONStringifyContext *jsc, if (JS_IsException(val)) goto exception; goto concat_primitive; - } else if (cl == JS_CLASS_BOOLEAN || cl == JS_CLASS_BIG_INT) - { + } else if (cl == JS_CLASS_BOOLEAN || cl == JS_CLASS_BIG_INT) { /* This will thow the same error as for the primitive object */ set_value(ctx, &val, JS_DupValue(ctx, p->u.object_data)); goto concat_primitive; + } else if (cl == JS_CLASS_RAWJSON) { + JSValue val1; + val1 = JS_GetProperty(ctx, val, JS_ATOM_rawJSON); + if (JS_IsException(val1)) + goto exception; + JS_FreeValue(ctx, val); + val = val1; + goto concat_value; } v = js_array_includes(ctx, jsc->stack, 1, (JSValueConst *)&val); if (JS_IsException(v)) @@ -49357,7 +49696,9 @@ static JSValue js_json_stringify(JSContext *ctx, JSValueConst this_val, } static const JSCFunctionListEntry js_json_funcs[] = { + JS_CFUNC_DEF("isRawJSON", 1, js_json_isRawJSON ), JS_CFUNC_DEF("parse", 2, js_json_parse ), + JS_CFUNC_DEF("rawJSON", 1, js_json_rawJSON ), JS_CFUNC_DEF("stringify", 3, js_json_stringify ), JS_PROP_STRING_DEF("[Symbol.toStringTag]", "JSON", JS_PROP_CONFIGURABLE ), }; diff --git a/test262.conf b/test262.conf index ebd92c9..ab8b0a5 100644 --- a/test262.conf +++ b/test262.conf @@ -150,7 +150,7 @@ IsHTMLDDA iterator-helpers iterator-sequencing json-modules -json-parse-with-source=skip +json-parse-with-source json-superset legacy-regexp=skip let