From d4411aeb5c2ec64c669d04d57046553a796c9042 Mon Sep 17 00:00:00 2001 From: Ben Noordhuis Date: Sun, 18 Aug 2024 22:12:04 +0200 Subject: [PATCH] Support (de)serializing Map and Set objects Fixes: https://github.com/quickjs-ng/quickjs/issues/482 --- gen/function_source.c | 2 +- gen/hello.c | 2 +- gen/hello_module.c | 4 +- gen/repl.c | 2 +- gen/test_fib.c | 2 +- quickjs.c | 117 +++++++++++++++++++++++++++++++++++++++++- tests/test_bjson.js | 24 +++++++++ 7 files changed, 146 insertions(+), 7 deletions(-) diff --git a/gen/function_source.c b/gen/function_source.c index f0122090c..85705da32 100644 --- a/gen/function_source.c +++ b/gen/function_source.c @@ -5,7 +5,7 @@ const uint32_t qjsc_function_source_size = 384; const uint8_t qjsc_function_source[384] = { - 0x0c, 0x06, 0x0c, 0x61, 0x63, 0x74, 0x75, 0x61, + 0x0d, 0x06, 0x0c, 0x61, 0x63, 0x74, 0x75, 0x61, 0x6c, 0x02, 0x66, 0x30, 0x74, 0x65, 0x73, 0x74, 0x73, 0x2f, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x73, 0x6f, 0x75, 0x72, 0x63, diff --git a/gen/hello.c b/gen/hello.c index 6389825ff..e131cfa55 100644 --- a/gen/hello.c +++ b/gen/hello.c @@ -5,7 +5,7 @@ const uint32_t qjsc_hello_size = 89; const uint8_t qjsc_hello[89] = { - 0x0c, 0x04, 0x0e, 0x63, 0x6f, 0x6e, 0x73, 0x6f, + 0x0d, 0x04, 0x0e, 0x63, 0x6f, 0x6e, 0x73, 0x6f, 0x6c, 0x65, 0x06, 0x6c, 0x6f, 0x67, 0x16, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x57, 0x6f, 0x72, 0x6c, 0x64, 0x22, 0x65, 0x78, 0x61, 0x6d, 0x70, diff --git a/gen/hello_module.c b/gen/hello_module.c index be72121c3..98a6765fa 100644 --- a/gen/hello_module.c +++ b/gen/hello_module.c @@ -5,7 +5,7 @@ const uint32_t qjsc_fib_module_size = 311; const uint8_t qjsc_fib_module[311] = { - 0x0c, 0x03, 0x2c, 0x65, 0x78, 0x61, 0x6d, 0x70, + 0x0d, 0x03, 0x2c, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x73, 0x2f, 0x66, 0x69, 0x62, 0x5f, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x2e, 0x6a, 0x73, 0x06, 0x66, 0x69, 0x62, 0x02, 0x6e, 0x0d, @@ -49,7 +49,7 @@ const uint8_t qjsc_fib_module[311] = { const uint32_t qjsc_hello_module_size = 178; const uint8_t qjsc_hello_module[178] = { - 0x0c, 0x07, 0x30, 0x65, 0x78, 0x61, 0x6d, 0x70, + 0x0d, 0x07, 0x30, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x73, 0x2f, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x5f, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x2e, 0x6a, 0x73, 0x1e, 0x2e, 0x2f, 0x66, 0x69, diff --git a/gen/repl.c b/gen/repl.c index 8e3310661..9bdd1f831 100644 --- a/gen/repl.c +++ b/gen/repl.c @@ -5,7 +5,7 @@ const uint32_t qjsc_repl_size = 22730; const uint8_t qjsc_repl[22730] = { - 0x0c, 0x92, 0x04, 0x0e, 0x72, 0x65, 0x70, 0x6c, + 0x0d, 0x92, 0x04, 0x0e, 0x72, 0x65, 0x70, 0x6c, 0x2e, 0x6a, 0x73, 0x06, 0x73, 0x74, 0x64, 0x04, 0x6f, 0x73, 0x0a, 0x62, 0x6a, 0x73, 0x6f, 0x6e, 0x02, 0x67, 0x10, 0x69, 0x73, 0x46, 0x69, 0x6e, diff --git a/gen/test_fib.c b/gen/test_fib.c index d37dd9fb3..53c431bf1 100644 --- a/gen/test_fib.c +++ b/gen/test_fib.c @@ -5,7 +5,7 @@ const uint32_t qjsc_test_fib_size = 167; const uint8_t qjsc_test_fib[167] = { - 0x0c, 0x07, 0x28, 0x65, 0x78, 0x61, 0x6d, 0x70, + 0x0d, 0x07, 0x28, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x73, 0x2f, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x66, 0x69, 0x62, 0x2e, 0x6a, 0x73, 0x10, 0x2e, 0x2f, 0x66, 0x69, 0x62, 0x2e, 0x73, 0x6f, diff --git a/quickjs.c b/quickjs.c index 527fea87c..a1fc3615e 100644 --- a/quickjs.c +++ b/quickjs.c @@ -33114,9 +33114,11 @@ typedef enum BCTagEnum { BC_TAG_DATE, BC_TAG_OBJECT_VALUE, BC_TAG_OBJECT_REFERENCE, + BC_TAG_MAP, + BC_TAG_SET, } BCTagEnum; -#define BC_VERSION 12 +#define BC_VERSION 13 typedef struct BCWriterState { JSContext *ctx; @@ -33757,6 +33759,9 @@ static int JS_WriteRegExp(BCWriterState *s, JSRegExp regexp) return 0; } +static int JS_WriteMap(BCWriterState *s, struct JSMapState *map_state); +static int JS_WriteSet(BCWriterState *s, struct JSMapState *map_state); + static int JS_WriteObjectRec(BCWriterState *s, JSValue obj) { uint32_t tag; @@ -33860,6 +33865,14 @@ static int JS_WriteObjectRec(BCWriterState *s, JSValue obj) bc_put_u8(s, BC_TAG_OBJECT_VALUE); ret = JS_WriteObjectRec(s, p->u.object_data); break; + case JS_CLASS_MAP: + bc_put_u8(s, BC_TAG_MAP); + ret = JS_WriteMap(s, p->u.map_state); + break; + case JS_CLASS_SET: + bc_put_u8(s, BC_TAG_SET); + ret = JS_WriteSet(s, p->u.map_state); + break; default: if (p->class_id >= JS_CLASS_UINT8C_ARRAY && p->class_id <= JS_CLASS_FLOAT64_ARRAY) { @@ -34996,6 +35009,9 @@ static JSValue JS_ReadObjectValue(BCReaderState *s) return JS_EXCEPTION; } +static JSValue JS_ReadMap(BCReaderState *s); +static JSValue JS_ReadSet(BCReaderState *s); + static JSValue JS_ReadObjectRec(BCReaderState *s) { JSContext *ctx = s->ctx; @@ -35103,6 +35119,12 @@ static JSValue JS_ReadObjectRec(BCReaderState *s) obj = js_dup(JS_MKPTR(JS_TAG_OBJECT, s->objects[val])); } break; + case BC_TAG_MAP: + obj = JS_ReadMap(s); + break; + case BC_TAG_SET: + obj = JS_ReadSet(s); + break; default: invalid_tag: return JS_ThrowSyntaxError(ctx, "invalid tag (tag=%d pos=%u)", @@ -46026,6 +46048,99 @@ static JSValue js_map_iterator_next(JSContext *ctx, JSValue this_val, } } +static JSValue js_map_read(BCReaderState *s, int magic) +{ + JSContext *ctx = s->ctx; + JSValue obj, rv, argv[2]; + uint32_t i, prop_count; + + argv[0] = JS_UNDEFINED; + argv[1] = JS_UNDEFINED; + obj = js_map_constructor(ctx, JS_UNDEFINED, 0, NULL, magic); + if (JS_IsException(obj)) + return JS_EXCEPTION; + 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++) { + argv[0] = JS_ReadObjectRec(s); + if (JS_IsException(argv[0])) + goto fail; + if (!(magic & MAGIC_SET)) { + argv[1] = JS_ReadObjectRec(s); + if (JS_IsException(argv[1])) + goto fail; + } + rv = js_map_set(ctx, obj, countof(argv), argv, magic); + if (JS_IsException(rv)) + goto fail; + JS_FreeValue(ctx, rv); + JS_FreeValue(ctx, argv[0]); + JS_FreeValue(ctx, argv[1]); + argv[0] = JS_UNDEFINED; + argv[1] = JS_UNDEFINED; + } + return obj; + fail: + JS_FreeValue(ctx, obj); + JS_FreeValue(ctx, argv[0]); + JS_FreeValue(ctx, argv[1]); + return JS_EXCEPTION; +} + +static int js_map_write(BCWriterState *s, struct JSMapState *map_state, + int magic) +{ + struct list_head *el; + JSMapRecord *mr; + uint32_t count; + + count = 0; + + if (map_state) { + list_for_each(el, &map_state->records) { + count++; + } + } + + bc_put_leb128(s, count); + + if (map_state) { + list_for_each(el, &map_state->records) { + mr = list_entry(el, JSMapRecord, link); + if (JS_WriteObjectRec(s, mr->key)) + return -1; + // mr->value is always JS_UNDEFINED for sets + if (!(magic & MAGIC_SET)) + if (JS_WriteObjectRec(s, mr->value)) + return -1; + } + } + + return 0; +} + +static JSValue JS_ReadMap(BCReaderState *s) +{ + return js_map_read(s, 0); +} + +static JSValue JS_ReadSet(BCReaderState *s) +{ + return js_map_read(s, MAGIC_SET); +} + +static int JS_WriteMap(BCWriterState *s, struct JSMapState *map_state) +{ + return js_map_write(s, map_state, 0); +} + +static int JS_WriteSet(BCWriterState *s, struct JSMapState *map_state) +{ + return js_map_write(s, map_state, MAGIC_SET); +} + static const JSCFunctionListEntry js_map_funcs[] = { JS_CFUNC_DEF("groupBy", 2, js_map_groupBy ), JS_CGETSET_DEF("[Symbol.species]", js_get_this, NULL ), diff --git a/tests/test_bjson.js b/tests/test_bjson.js index 685ec1c74..e12919b61 100644 --- a/tests/test_bjson.js +++ b/tests/test_bjson.js @@ -155,6 +155,28 @@ function bjson_test_regexp() assert("sup dog".match(r).groups["𝓓𝓸𝓰"], "dog"); } +function bjson_test_map() +{ + var buf, r, xs; + + xs = [["key", "value"]]; + buf = bjson.write(new Map(xs)); + r = bjson.read(buf, 0, buf.byteLength); + assert(r instanceof Map); + assert([...r].toString(), xs.toString()); +} + +function bjson_test_set() +{ + var buf, r, xs; + + xs = ["one", "two", "three"]; + buf = bjson.write(new Set(xs)); + r = bjson.read(buf, 0, buf.byteLength); + assert(r instanceof Set); + assert([...r].toString(), xs.toString()); +} + function bjson_test_all() { var obj; @@ -184,6 +206,8 @@ function bjson_test_all() bjson_test_reference(); bjson_test_regexp(); + bjson_test_map(); + bjson_test_set(); } bjson_test_all();