From 1647303fd0b5c1dd4b41981460616c7594b08520 Mon Sep 17 00:00:00 2001 From: Ben Noordhuis Date: Mon, 6 Oct 2025 21:26:16 +0200 Subject: [PATCH 1/2] Test that global object prototype can be changed Also fix JS_SetPrototype's function prototype because it suggested it took ownership of the prototype object (i.e., was responsible for freeing it) when it doesn't. --- api-test.c | 27 +++++++++++++++++++++++++++ quickjs.c | 2 +- quickjs.h | 2 +- 3 files changed, 29 insertions(+), 2 deletions(-) diff --git a/api-test.c b/api-test.c index 950b4156c..912029778 100644 --- a/api-test.c +++ b/api-test.c @@ -545,6 +545,32 @@ static void new_errors(void) JS_FreeRuntime(rt); } +static void global_object_prototype(void) +{ + JSRuntime *rt = JS_NewRuntime(); + JSContext *ctx = JS_NewContext(rt); + JSValue proto = JS_NewObject(ctx); + assert(JS_IsObject(proto)); + JSCFunctionListEntry prop = JS_PROP_INT32_DEF("answer", 42, JS_PROP_C_W_E); + JS_SetPropertyFunctionList(ctx, proto, &prop, 1); + JSValue global_object = JS_GetGlobalObject(ctx); + int res = JS_SetPrototype(ctx, global_object, proto); + assert(res == true); + JS_FreeValue(ctx, global_object); + JS_FreeValue(ctx, proto); + static const char code[] = "answer"; + JSValue ret = JS_Eval(ctx, code, strlen(code), "*", JS_EVAL_TYPE_GLOBAL); + assert(!JS_IsException(ret)); + assert(JS_IsNumber(ret)); + int32_t answer; + res = JS_ToInt32(ctx, &answer, ret); + assert(res == 0); + assert(answer == 42); + JS_FreeValue(ctx, ret); + JS_FreeContext(ctx); + JS_FreeRuntime(rt); +} + int main(void) { sync_call(); @@ -558,5 +584,6 @@ int main(void) promise_hook(); dump_memory_usage(); new_errors(); + global_object_prototype(); return 0; } diff --git a/quickjs.c b/quickjs.c index 7c5fd6643..25e0f51b5 100644 --- a/quickjs.c +++ b/quickjs.c @@ -7362,7 +7362,7 @@ static int JS_SetPrototypeInternal(JSContext *ctx, JSValueConst obj, } /* return -1 (exception) or true/false */ -int JS_SetPrototype(JSContext *ctx, JSValueConst obj, JSValue proto_val) +int JS_SetPrototype(JSContext *ctx, JSValueConst obj, JSValueConst proto_val) { return JS_SetPrototypeInternal(ctx, obj, proto_val, true); } diff --git a/quickjs.h b/quickjs.h index f8f2d586b..dadd8d059 100644 --- a/quickjs.h +++ b/quickjs.h @@ -882,7 +882,7 @@ JS_EXTERN int JS_HasProperty(JSContext *ctx, JSValueConst this_obj, JSAtom prop) JS_EXTERN int JS_IsExtensible(JSContext *ctx, JSValueConst obj); JS_EXTERN int JS_PreventExtensions(JSContext *ctx, JSValueConst obj); JS_EXTERN int JS_DeleteProperty(JSContext *ctx, JSValueConst obj, JSAtom prop, int flags); -JS_EXTERN int JS_SetPrototype(JSContext *ctx, JSValueConst obj, JSValue proto_val); +JS_EXTERN int JS_SetPrototype(JSContext *ctx, JSValueConst obj, JSValueConst proto_val); JS_EXTERN JSValue JS_GetPrototype(JSContext *ctx, JSValueConst val); JS_EXTERN int JS_GetLength(JSContext *ctx, JSValueConst obj, int64_t *pres); JS_EXTERN int JS_SetLength(JSContext *ctx, JSValueConst obj, int64_t len); From accb790eb64ed15ecc4abc43f97a2feb8d7f74dc Mon Sep 17 00:00:00 2001 From: Ben Noordhuis Date: Tue, 7 Oct 2025 09:03:05 +0200 Subject: [PATCH 2/2] squash! also test exotic object as prototype --- api-test.c | 100 ++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 80 insertions(+), 20 deletions(-) diff --git a/api-test.c b/api-test.c index 912029778..9bd3b6344 100644 --- a/api-test.c +++ b/api-test.c @@ -545,30 +545,90 @@ static void new_errors(void) JS_FreeRuntime(rt); } +static int gop_get_own_property(JSContext *ctx, JSPropertyDescriptor *desc, + JSValueConst obj, JSAtom prop) +{ + const char *name; + int found; + + found = 0; + name = JS_AtomToCString(ctx, prop); + if (!name) + return -1; + if (!strcmp(name, "answer")) { + found = 1; + *desc = (JSPropertyDescriptor){ + .value = JS_NewInt32(ctx, 42), + .flags = JS_PROP_C_W_E | JS_PROP_HAS_VALUE, + }; + } + JS_FreeCString(ctx, name); + return found; +} + static void global_object_prototype(void) { - JSRuntime *rt = JS_NewRuntime(); - JSContext *ctx = JS_NewContext(rt); - JSValue proto = JS_NewObject(ctx); - assert(JS_IsObject(proto)); - JSCFunctionListEntry prop = JS_PROP_INT32_DEF("answer", 42, JS_PROP_C_W_E); - JS_SetPropertyFunctionList(ctx, proto, &prop, 1); - JSValue global_object = JS_GetGlobalObject(ctx); - int res = JS_SetPrototype(ctx, global_object, proto); - assert(res == true); - JS_FreeValue(ctx, global_object); - JS_FreeValue(ctx, proto); static const char code[] = "answer"; - JSValue ret = JS_Eval(ctx, code, strlen(code), "*", JS_EVAL_TYPE_GLOBAL); - assert(!JS_IsException(ret)); - assert(JS_IsNumber(ret)); + JSValue global_object, proto, ret; + JSClassID class_id; + JSRuntime *rt; + JSContext *ctx; int32_t answer; - res = JS_ToInt32(ctx, &answer, ret); - assert(res == 0); - assert(answer == 42); - JS_FreeValue(ctx, ret); - JS_FreeContext(ctx); - JS_FreeRuntime(rt); + int res; + + { + rt = JS_NewRuntime(); + ctx = JS_NewContext(rt); + proto = JS_NewObject(ctx); + assert(JS_IsObject(proto)); + JSCFunctionListEntry prop = JS_PROP_INT32_DEF("answer", 42, JS_PROP_C_W_E); + JS_SetPropertyFunctionList(ctx, proto, &prop, 1); + global_object = JS_GetGlobalObject(ctx); + res = JS_SetPrototype(ctx, global_object, proto); + assert(res == true); + JS_FreeValue(ctx, global_object); + JS_FreeValue(ctx, proto); + ret = JS_Eval(ctx, code, strlen(code), "*", JS_EVAL_TYPE_GLOBAL); + assert(!JS_IsException(ret)); + assert(JS_IsNumber(ret)); + res = JS_ToInt32(ctx, &answer, ret); + assert(res == 0); + assert(answer == 42); + JS_FreeValue(ctx, ret); + JS_FreeContext(ctx); + JS_FreeRuntime(rt); + } + { + JSClassExoticMethods exotic = (JSClassExoticMethods){ + .get_own_property = gop_get_own_property, + }; + JSClassDef def = (JSClassDef){ + .class_name = "Global Object", + .exotic = &exotic, + }; + rt = JS_NewRuntime(); + class_id = 0; + JS_NewClassID(rt, &class_id); + res = JS_NewClass(rt, class_id, &def); + assert(res == 0); + ctx = JS_NewContext(rt); + proto = JS_NewObjectClass(ctx, class_id); + assert(JS_IsObject(proto)); + global_object = JS_GetGlobalObject(ctx); + res = JS_SetPrototype(ctx, global_object, proto); + assert(res == true); + JS_FreeValue(ctx, global_object); + JS_FreeValue(ctx, proto); + ret = JS_Eval(ctx, code, strlen(code), "*", JS_EVAL_TYPE_GLOBAL); + assert(!JS_IsException(ret)); + assert(JS_IsNumber(ret)); + res = JS_ToInt32(ctx, &answer, ret); + assert(res == 0); + assert(answer == 42); + JS_FreeValue(ctx, ret); + JS_FreeContext(ctx); + JS_FreeRuntime(rt); + } } int main(void)