quickjs-tart

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

commit c5b5c3a3ca5c7b9c2501649d52fa09e927ef05b4
parent 779b20d2aefbbcb34420617c7a2a707e03ec89f1
Author: Charlie Gordon <github@chqrlie.org>
Date:   Sat, 23 Mar 2024 12:43:45 +0100

Improve `JSON.stringify`

- changed error messages
- clarify `toJSON` method usage
- simplify boxed objects handling
- for ECMA conformity, BigInt objects need a toJSON method in the prototype chain
  including boxed objects

Diffstat:
Mquickjs/quickjs.c | 82+++++++++++++++++++++++++++++++++++++++++--------------------------------------
1 file changed, 43 insertions(+), 39 deletions(-)

diff --git a/quickjs/quickjs.c b/quickjs/quickjs.c @@ -45306,7 +45306,7 @@ static JSValue json_parse_value(JSParseState *s) default: def_token: if (s->token.val == TOK_EOF) { - js_parse_error(s, "unexpected end of input"); + js_parse_error(s, "Unexpected end of JSON input"); } else { js_parse_error(s, "unexpected token: '%.*s'", (int)(s->buf_ptr - s->token.ptr), s->token.ptr); @@ -45473,22 +45473,27 @@ static JSValue js_json_check(JSContext *ctx, JSONStringifyContext *jsc, JSValue v; JSValueConst args[2]; - if (JS_IsObject(val) || - JS_IsBigInt(ctx, val) /* XXX: probably useless */ + /* check for object.toJSON method */ + /* ECMA specifies this is done only for Object and BigInt */ + /* we do it for BigFloat and BigDecimal as an extension */ + if (JS_IsObject(val) || JS_IsBigInt(ctx, val) +#ifdef CONFIG_BIGNUM + || JS_IsBigFloat(val) || JS_IsBigDecimal(val) +#endif ) { - JSValue f = JS_GetProperty(ctx, val, JS_ATOM_toJSON); - if (JS_IsException(f)) + JSValue f = JS_GetProperty(ctx, val, JS_ATOM_toJSON); + if (JS_IsException(f)) + goto exception; + if (JS_IsFunction(ctx, f)) { + v = JS_CallFree(ctx, f, val, 1, &key); + JS_FreeValue(ctx, val); + val = v; + if (JS_IsException(val)) goto exception; - if (JS_IsFunction(ctx, f)) { - v = JS_CallFree(ctx, f, val, 1, &key); - JS_FreeValue(ctx, val); - val = v; - if (JS_IsException(val)) - goto exception; - } else { - JS_FreeValue(ctx, f); - } + } else { + JS_FreeValue(ctx, f); } + } if (!JS_IsUndefined(jsc->replacer_func)) { args[0] = key; @@ -45507,12 +45512,13 @@ static JSValue js_json_check(JSContext *ctx, JSONStringifyContext *jsc, case JS_TAG_STRING: case JS_TAG_INT: case JS_TAG_FLOAT64: -#ifdef CONFIG_BIGNUM - case JS_TAG_BIG_FLOAT: -#endif case JS_TAG_BOOL: case JS_TAG_NULL: case JS_TAG_BIG_INT: +#ifdef CONFIG_BIGNUM + case JS_TAG_BIG_FLOAT: + case JS_TAG_BIG_DECIMAL: +#endif case JS_TAG_EXCEPTION: return val; default: @@ -45542,36 +45548,29 @@ static int js_json_to_str(JSContext *ctx, JSONStringifyContext *jsc, tab = JS_UNDEFINED; prop = JS_UNDEFINED; - switch (JS_VALUE_GET_NORM_TAG(val)) { - case JS_TAG_OBJECT: + if (JS_IsObject(val)) { p = JS_VALUE_GET_OBJ(val); cl = p->class_id; if (cl == JS_CLASS_STRING) { val = JS_ToStringFree(ctx, val); if (JS_IsException(val)) goto exception; - val = JS_ToQuotedStringFree(ctx, val); - if (JS_IsException(val)) - goto exception; - return string_buffer_concat_value_free(jsc->b, val); + goto concat_primitive; } else if (cl == JS_CLASS_NUMBER) { val = JS_ToNumberFree(ctx, val); if (JS_IsException(val)) goto exception; - return string_buffer_concat_value_free(jsc->b, val); - } else if (cl == JS_CLASS_BOOLEAN) { - ret = string_buffer_concat_value(jsc->b, p->u.object_data); - JS_FreeValue(ctx, val); - return ret; - } else + goto concat_primitive; + } else if (cl == JS_CLASS_BOOLEAN || cl == JS_CLASS_BIG_INT #ifdef CONFIG_BIGNUM - if (cl == JS_CLASS_BIG_FLOAT) { - return string_buffer_concat_value_free(jsc->b, val); - } else + || cl == JS_CLASS_BIG_FLOAT + || cl == JS_CLASS_BIG_DECIMAL #endif - if (cl == JS_CLASS_BIG_INT) { - JS_ThrowTypeError(ctx, "bigint are forbidden in JSON.stringify"); - goto exception; + ) + { + /* 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; } v = js_array_includes(ctx, jsc->stack, 1, (JSValueConst *)&val); if (JS_IsException(v)) @@ -45684,6 +45683,9 @@ static int js_json_to_str(JSContext *ctx, JSONStringifyContext *jsc, JS_FreeValue(ctx, indent1); JS_FreeValue(ctx, prop); return 0; + } + concat_primitive: + switch (JS_VALUE_GET_NORM_TAG(val)) { case JS_TAG_STRING: val = JS_ToQuotedStringFree(ctx, val); if (JS_IsException(val)) @@ -45695,15 +45697,17 @@ static int js_json_to_str(JSContext *ctx, JSONStringifyContext *jsc, } goto concat_value; case JS_TAG_INT: -#ifdef CONFIG_BIGNUM - case JS_TAG_BIG_FLOAT: -#endif case JS_TAG_BOOL: case JS_TAG_NULL: concat_value: return string_buffer_concat_value_free(jsc->b, val); case JS_TAG_BIG_INT: - JS_ThrowTypeError(ctx, "bigint are forbidden in JSON.stringify"); +#ifdef CONFIG_BIGNUM + case JS_TAG_BIG_FLOAT: + case JS_TAG_BIG_DECIMAL: +#endif + /* reject big numbers: use toJSON method to override */ + JS_ThrowTypeError(ctx, "Do not know how to serialize a BigInt"); goto exception; default: JS_FreeValue(ctx, val);