added JSON.parse source text access
Some checks failed
ci / Linux (Ubuntu) (push) Has been cancelled
ci / Linux LTO (push) Has been cancelled
ci / Linux 32bit (push) Has been cancelled
ci / linux-asan (push) Has been cancelled
ci / linux-msan (push) Has been cancelled
ci / linux-ubsan (push) Has been cancelled
ci / macOS (push) Has been cancelled
ci / macos-asan (push) Has been cancelled
ci / macos-ubsan (push) Has been cancelled
ci / freebsd (push) Has been cancelled
ci / Cosmopolitan (push) Has been cancelled
ci / MinGW Windows target (push) Has been cancelled
ci / Windows MSYS2 (push) Has been cancelled
ci / qemu-alpine (linux/386) (push) Has been cancelled
ci / qemu-alpine (linux/arm/v6) (push) Has been cancelled
ci / qemu-alpine (linux/arm/v7) (push) Has been cancelled
ci / qemu-alpine (linux/arm64) (push) Has been cancelled
ci / qemu-alpine (linux/ppc64le) (push) Has been cancelled
ci / qemu-alpine (linux/riscv64) (push) Has been cancelled
ci / qemu-alpine (linux/s390x) (push) Has been cancelled

This commit is contained in:
Fabrice Bellard 2026-03-23 18:45:52 +01:00
parent a31dcef98c
commit d7ae12ae71
4 changed files with 375 additions and 33 deletions

2
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

@ -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, "<home_object>")

403
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, "<input>");
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, "<input>", 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, "<input>", 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 ),
};

@ -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