From da1f3744af7ce66adf851edb6481e58102316711 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 6 Aug 2026 17:04:54 +0000 Subject: [PATCH 1/3] Return the class name atom from JS_GetClassName() JS_GetClassName() duplicates and returns rt->class_array[class_id].class_id, the numeric JSClassID, where a JSAtom is expected. The two fields are adjacent in JSClass and both 32-bit, so the mistake compiles cleanly, but the value handed to JS_DupAtomRT() is a class id reinterpreted as an atom: for a class id below JS_ATOM_END it bumps the refcount of an unrelated predefined atom and returns it, and above that it indexes into the runtime atom array out of any relation to the class. Return .class_name instead. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_014mv33YvfHz7t9mmkituBnn --- quickjs.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/quickjs.c b/quickjs.c index bbac00c33..53ce24862 100644 --- a/quickjs.c +++ b/quickjs.c @@ -4178,7 +4178,7 @@ bool JS_IsRegisteredClass(JSRuntime *rt, JSClassID class_id) JSAtom JS_GetClassName(JSRuntime *rt, JSClassID class_id) { if (JS_IsRegisteredClass(rt, class_id)) { - return JS_DupAtomRT(rt, rt->class_array[class_id].class_id); + return JS_DupAtomRT(rt, rt->class_array[class_id].class_name); } else { return JS_ATOM_NULL; } From 6dacaf580905ad3edb7c17a12b84634fa8d4d1e3 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 7 Aug 2026 08:11:17 +0000 Subject: [PATCH 2/3] Test JS_GetClassName() Cover the API in api-test.c: the name of a class registered from C, the names of a spread of built-in classes reached through JS_GetClassID(), and JS_ATOM_NULL for class ids with no class behind them. Both name checks fail against the bug they guard against, where the class id was returned in place of the class name atom. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01QbX6zyXo3DgDtS3rRPCxBY --- api-test.c | 82 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) diff --git a/api-test.c b/api-test.c index ef38a7f98..ac180c66c 100644 --- a/api-test.c +++ b/api-test.c @@ -1822,6 +1822,87 @@ static void transfer_default_managed_array_buffer(void) JS_FreeRuntime(rt); } +static void get_class_name(void) +{ + static const struct { + const char *code; + const char *name; + } builtins[] = { + { "({})", "Object" }, + { "[]", "Array" }, + { "new Error()", "Error" }, + { "new Date()", "Date" }, + { "/re/", "RegExp" }, + { "new Map()", "Map" }, + { "new ArrayBuffer(0)", "ArrayBuffer" }, + { "new Uint8Array(0)", "Uint8Array" }, + { "(function(){})", "Function" }, + }; + JSClassDef def = (JSClassDef){ .class_name = "MyClass" }; + JSClassID class_id, unregistered_class_id; + JSAtom atom, expected; + JSRuntime *rt; + JSContext *ctx; + const char *s; + JSValue obj; + size_t i; + + rt = new_runtime(); + class_id = 0; + JS_NewClassID(rt, &class_id); + assert(0 == JS_NewClass(rt, class_id, &def)); + + /* handed out by JS_NewClassID() but never passed to JS_NewClass() */ + unregistered_class_id = 0; + JS_NewClassID(rt, &unregistered_class_id); + + ctx = JS_NewContext(rt); + + /* the name of a class registered from C, not its class id */ + expected = JS_NewAtom(ctx, "MyClass"); + atom = JS_GetClassName(rt, class_id); + assert(atom == expected); + s = JS_AtomToCString(ctx, atom); + assert(s); + assert(!strcmp(s, "MyClass")); + JS_FreeCString(ctx, s); + JS_FreeAtom(ctx, atom); + JS_FreeAtom(ctx, expected); + + /* the names of the built-in classes */ + for (i = 0; i < countof(builtins); i++) { + obj = eval(ctx, builtins[i].code); + assert(!JS_IsException(obj)); + atom = JS_GetClassName(rt, JS_GetClassID(obj)); + assert(atom != JS_ATOM_NULL); + s = JS_AtomToCString(ctx, atom); + assert(s); + assert(!strcmp(s, builtins[i].name)); + JS_FreeCString(ctx, s); + JS_FreeAtom(ctx, atom); + JS_FreeValue(ctx, obj); + } + + /* class ids without a registered class have no name */ + assert(JS_ATOM_NULL == JS_GetClassName(rt, JS_INVALID_CLASS_ID)); + assert(JS_ATOM_NULL == JS_GetClassName(rt, unregistered_class_id)); + assert(JS_ATOM_NULL == JS_GetClassName(rt, class_id + 4096)); + + /* the caller owns the returned atom; the class keeps its own reference, + so releasing it repeatedly doesn't free the name out from under it */ + for (i = 0; i < 64; i++) + JS_FreeAtom(ctx, JS_GetClassName(rt, class_id)); + atom = JS_GetClassName(rt, class_id); + s = JS_AtomToCString(ctx, atom); + assert(s); + assert(!strcmp(s, "MyClass")); + JS_FreeCString(ctx, s); + JS_FreeAtom(ctx, atom); + + JS_FreeContext(ctx); + JS_FreeRuntime(rt); +} + int main(void) { cfunctions(); @@ -1855,5 +1936,6 @@ int main(void) transfer_external_array_buffer(); resize_external_array_buffer(); transfer_default_managed_array_buffer(); + get_class_name(); return 0; } From 29d1c827429f375f8a8a0fc37bd6b40f07194fd5 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 7 Aug 2026 09:47:39 +0000 Subject: [PATCH 3/3] Extend the JS_GetClassName() test to the whole class table Returning the class id produced a valid-looking atom either way: an unrelated predefined atom for a small id, or the id spelled out in decimal for a large one. Rather than trusting a handful of named built-ins to cover both shapes, walk every registered class id and check the name is neither the id in decimal nor anything else starting with a digit. A few internal classes are deliberately unnamed, so an empty name is allowed. Also checks that two classes sharing a name share the interned atom, and that the name does not depend on which context asks. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01K6eRbuuuCujKgQkrHgvMrc --- api-test.c | 58 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/api-test.c b/api-test.c index ac180c66c..2d2963f98 100644 --- a/api-test.c +++ b/api-test.c @@ -2,6 +2,7 @@ #undef NDEBUG #endif #include +#include #include #include #include "quickjs.h" @@ -1899,6 +1900,63 @@ static void get_class_name(void) JS_FreeCString(ctx, s); JS_FreeAtom(ctx, atom); + /* every registered class has a name, and it is a name rather than a + number: returning the class id instead produced a valid-looking atom, + either an unrelated predefined one for a small id or the id spelled out + in decimal for a large one, so check the whole table rather than the + handful of classes spelled out above */ + { + JSClassID id; + int registered = 0; + + for (id = 1; id < class_id + 16; id++) { + char decimal[32]; + if (!JS_IsRegisteredClass(rt, id)) + continue; + registered++; + atom = JS_GetClassName(rt, id); + assert(atom != JS_ATOM_NULL); + s = JS_AtomToCString(ctx, atom); + assert(s); + // a few internal classes are deliberately unnamed, but none is + // named after a number + snprintf(decimal, sizeof(decimal), "%u", (unsigned)id); + assert(strcmp(s, decimal)); + assert(s[0] < '0' || s[0] > '9'); + JS_FreeCString(ctx, s); + JS_FreeAtom(ctx, atom); + } + /* the built-ins alone are far more than this */ + assert(registered > 20); + } + + /* two classes sharing a name share the interned atom, and the name does + not depend on which context asks */ + { + JSClassDef def2 = (JSClassDef){ .class_name = "MyClass" }; + JSClassID class_id2 = 0; + JSContext *ctx2; + JSAtom a1, a2; + + JS_NewClassID(rt, &class_id2); + assert(0 == JS_NewClass(rt, class_id2, &def2)); + assert(class_id2 != class_id); + + a1 = JS_GetClassName(rt, class_id); + a2 = JS_GetClassName(rt, class_id2); + assert(a1 == a2); + JS_FreeAtom(ctx, a1); + JS_FreeAtom(ctx, a2); + + ctx2 = JS_NewContext(rt); + a1 = JS_GetClassName(rt, class_id); + a2 = JS_GetClassName(rt, class_id); + assert(a1 == a2); + JS_FreeAtom(ctx2, a1); + JS_FreeAtom(ctx2, a2); + JS_FreeContext(ctx2); + } + JS_FreeContext(ctx); JS_FreeRuntime(rt); }