From 3deee48b5096fb2f97cb8f2b5fb88d52d589d4bf Mon Sep 17 00:00:00 2001 From: Russel Einsla Date: Wed, 4 Mar 2026 18:06:57 -0500 Subject: [PATCH 1/8] Add last_var_atom for better error messages Added last_var_atom to JSRuntime and JSClass structures to store the last accessed variable atom for improved error messages. Updated various functions to utilize last_var_atom for more informative error handling. --- quickjs.c | 167 ++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 118 insertions(+), 49 deletions(-) diff --git a/quickjs.c b/quickjs.c index c1469e310..74ac2bfa1 100644 --- a/quickjs.c +++ b/quickjs.c @@ -350,6 +350,7 @@ struct JSRuntime { void *user_opaque; void *libc_opaque; JSRuntimeFinalizerState *finalizers; + JSAtom last_var_atom; /* stores the last accessed variable atom for better error messages */ }; struct JSClass { @@ -535,6 +536,7 @@ struct JSContext { const char *input, size_t input_len, const char *filename, int line, int flags, int scope_idx); void *user_opaque; + JSAtom last_var_atom; /* stores the last accessed variable atom for better error messages */ }; typedef union JSFloat64Union { @@ -1989,6 +1991,7 @@ JSRuntime *JS_NewRuntime2(const JSMallocFunctions *mf, void *opaque) JS_UpdateStackTop(rt); rt->current_exception = JS_UNINITIALIZED; + rt->last_var_atom = JS_ATOM_NULL; return rt; fail: @@ -8058,6 +8061,17 @@ static JSValue JS_ThrowTypeErrorNotAFunction(JSContext *ctx) return JS_ThrowTypeError(ctx, "not a function"); } +static JSValue JS_ThrowTypeErrorNotAFunctionAtom(JSContext *ctx, JSAtom atom) +{ + if (atom == JS_ATOM_NULL) { + /* fallback if no atom provided */ + return JS_ThrowTypeError(ctx, "not a function"); + } + char buf[ATOM_GET_STR_BUF_SIZE]; + JS_AtomGetStr(ctx, buf, sizeof(buf), atom); + return JS_ThrowTypeError(ctx, "[SUCCESS] %s is not a function", buf); +} + static JSValue JS_ThrowTypeErrorNotAnObject(JSContext *ctx) { return JS_ThrowTypeError(ctx, "not an object"); @@ -17354,7 +17368,14 @@ static JSValue JS_CallInternal(JSContext *caller_ctx, JSValueConst func_obj, call_func = rt->class_array[p->class_id].call; if (!call_func) { not_a_function: - return JS_ThrowTypeErrorNotAFunction(caller_ctx); + // printf("DEBUG: not_a_function reached, rt->last_var_atom=%d\n", (int)rt->last_var_atom); + fflush(stdout); + if (rt->last_var_atom != JS_ATOM_NULL) { + JSAtom atom = rt->last_var_atom; + rt->last_var_atom = JS_ATOM_NULL; /* clear for next call */ + return JS_ThrowTypeErrorNotAFunctionAtom(caller_ctx, atom); + } + return JS_ThrowTypeError(ctx, "[ATOM_NULL_DEBUG] not a function"); } return call_func(caller_ctx, func_obj, this_obj, argc, argv, flags); @@ -17771,6 +17792,8 @@ static JSValue JS_CallInternal(JSContext *caller_ctx, JSValueConst func_obj, CASE(OP_call): CASE(OP_tail_call): { + // printf("DEBUG: OP_call, rt->last_var_atom=%d\n", (int)rt->last_var_atom); + fflush(stdout); call_argc = get_u16(pc); pc += 2; goto has_call_argc; @@ -18060,6 +18083,9 @@ static JSValue JS_CallInternal(JSContext *caller_ctx, JSValueConst func_obj, atom = get_u32(pc); pc += 4; sf->cur_pc = pc; + rt->last_var_atom = atom; /* store atom for error messages */ + // printf("DEBUG: OP_get_var%s atom=%d\n", opcode == OP_get_var ? "" : "_undef", (int)atom); + // fflush(stdout); val = JS_GetGlobalVar(ctx, atom, opcode - OP_get_var_undef); if (unlikely(JS_IsException(val))) @@ -18125,6 +18151,8 @@ static JSValue JS_CallInternal(JSContext *caller_ctx, JSValueConst func_obj, int idx; idx = get_u16(pc); pc += 2; + rt->last_var_atom = b->vardefs[idx].var_name; /* store atom for error messages */ + // printf("DEBUG: OP_get_loc idx=%d, atom=%d\n", idx, (int)rt->last_var_atom); sp[0] = js_dup(var_buf[idx]); sp++; } @@ -18173,7 +18201,7 @@ static JSValue JS_CallInternal(JSContext *caller_ctx, JSValueConst func_obj, } BREAK; - CASE(OP_get_loc8): *sp++ = js_dup(var_buf[*pc++]); BREAK; + CASE(OP_get_loc8): rt->last_var_atom = b->vardefs[*pc].var_name; BREAK; CASE(OP_put_loc8): set_value(ctx, &var_buf[*pc++], *--sp); BREAK; CASE(OP_set_loc8): set_value(ctx, &var_buf[*pc++], js_dup(sp[-1])); BREAK; @@ -18182,13 +18210,14 @@ static JSValue JS_CallInternal(JSContext *caller_ctx, JSValueConst func_obj, // making them ideal candidates for opcode fusion. CASE(OP_get_loc0_loc1): *sp++ = js_dup(var_buf[0]); + rt->last_var_atom = b->vardefs[1].var_name; *sp++ = js_dup(var_buf[1]); BREAK; - CASE(OP_get_loc0): *sp++ = js_dup(var_buf[0]); BREAK; - CASE(OP_get_loc1): *sp++ = js_dup(var_buf[1]); BREAK; - CASE(OP_get_loc2): *sp++ = js_dup(var_buf[2]); BREAK; - CASE(OP_get_loc3): *sp++ = js_dup(var_buf[3]); BREAK; + CASE(OP_get_loc0): rt->last_var_atom = b->vardefs[0].var_name; *sp++ = js_dup(var_buf[0]); BREAK; + CASE(OP_get_loc1): rt->last_var_atom = b->vardefs[1].var_name; *sp++ = js_dup(var_buf[1]); BREAK; + CASE(OP_get_loc2): rt->last_var_atom = b->vardefs[2].var_name; *sp++ = js_dup(var_buf[2]); BREAK; + CASE(OP_get_loc3): rt->last_var_atom = b->vardefs[3].var_name; *sp++ = js_dup(var_buf[3]); BREAK; CASE(OP_put_loc0): set_value(ctx, &var_buf[0], *--sp); BREAK; CASE(OP_put_loc1): set_value(ctx, &var_buf[1], *--sp); BREAK; CASE(OP_put_loc2): set_value(ctx, &var_buf[2], *--sp); BREAK; @@ -18209,10 +18238,47 @@ static JSValue JS_CallInternal(JSContext *caller_ctx, JSValueConst func_obj, CASE(OP_set_arg1): set_value(ctx, &arg_buf[1], js_dup(sp[-1])); BREAK; CASE(OP_set_arg2): set_value(ctx, &arg_buf[2], js_dup(sp[-1])); BREAK; CASE(OP_set_arg3): set_value(ctx, &arg_buf[3], js_dup(sp[-1])); BREAK; - CASE(OP_get_var_ref0): *sp++ = js_dup(*var_refs[0]->pvalue); BREAK; - CASE(OP_get_var_ref1): *sp++ = js_dup(*var_refs[1]->pvalue); BREAK; - CASE(OP_get_var_ref2): *sp++ = js_dup(*var_refs[2]->pvalue); BREAK; - CASE(OP_get_var_ref3): *sp++ = js_dup(*var_refs[3]->pvalue); BREAK; + CASE(OP_get_var_ref0): + { + /* printf("DEBUG: OP_get_var_ref0 called, b=%p, closure_var=%p, closure_var_count=%d\n", b, b ? b->closure_var : NULL, b ? (int)b->closure_var_count : 0); */ + *sp++ = js_dup(*var_refs[0]->pvalue); + if (b && b->vardefs && 0 < b->arg_count + b->var_count) { + rt->last_var_atom = b->vardefs[0].var_name; + /* printf("DEBUG: OP_get_var_ref0 atom=%d\n", (int)rt->last_var_atom); */ + } else if (b && b->closure_var && 0 < b->closure_var_count) { + rt->last_var_atom = b->closure_var[0].var_name; + /* printf("DEBUG: OP_get_var_ref0 closure atom=%d\n", (int)rt->last_var_atom); */ + } + } + BREAK; + + CASE(OP_get_var_ref1): + { + *sp++ = js_dup(*var_refs[1]->pvalue); + if (b && b->vardefs && 1 < b->arg_count + b->var_count) { + rt->last_var_atom = b->vardefs[1].var_name; + /* printf("DEBUG: OP_get_var_ref1 atom=%d\n", (int)rt->last_var_atom); */ + } + } + BREAK; + + CASE(OP_get_var_ref2): + { + *sp++ = js_dup(*var_refs[2]->pvalue); + if (b && b->vardefs && 2 < b->arg_count + b->var_count) { + rt->last_var_atom = b->vardefs[2].var_name; + /* printf("DEBUG: OP_get_var_ref2 atom=%d\n", (int)rt->last_var_atom); */ + } + } + BREAK; + CASE(OP_get_var_ref3): + { + *sp++ = js_dup(*var_refs[3]->pvalue); + if (b && b->vardefs && 3 < b->arg_count + b->var_count) { + rt->last_var_atom = b->vardefs[3].var_name; + } + } + BREAK; CASE(OP_put_var_ref0): set_value(ctx, var_refs[0]->pvalue, *--sp); BREAK; CASE(OP_put_var_ref1): set_value(ctx, var_refs[1]->pvalue, *--sp); BREAK; CASE(OP_put_var_ref2): set_value(ctx, var_refs[2]->pvalue, *--sp); BREAK; @@ -18228,10 +18294,26 @@ static JSValue JS_CallInternal(JSContext *caller_ctx, JSValueConst func_obj, JSValue val; idx = get_u16(pc); pc += 2; + // printf("DEBUG: OP_get_var_ref idx=%d\n", idx); + val = *var_refs[idx]->pvalue; sp[0] = js_dup(val); sp++; + + /* store atom for error messages */ + if (b && b->vardefs && idx < b->arg_count + b->var_count) { + rt->last_var_atom = b->vardefs[idx].var_name; + // printf("DEBUG: OP_get_var_ref idx=%d atom=%d\n", idx, (int)rt->last_var_atom); + } else if (b && b->closure_var && idx >= b->arg_count + b->var_count) { + int closure_idx = idx - (b->arg_count + b->var_count); + if (closure_idx < b->closure_var_count) { + rt->last_var_atom = b->closure_var[closure_idx].var_name; + // printf("DEBUG: OP_get_var_ref idx=%d closure atom=%d\n", idx, (int)rt->last_var_atom); + + } + } } + BREAK; CASE(OP_put_var_ref): { @@ -19815,6 +19897,7 @@ static JSValue JS_CallInternal(JSContext *caller_ctx, JSValueConst func_obj, } switch (opcode) { case OP_with_get_var: + rt->last_var_atom = atom; /* store atom for error messages */ val = JS_GetProperty(ctx, obj, atom); if (unlikely(JS_IsException(val))) goto exception; @@ -20099,6 +20182,11 @@ static JSValue JS_CallConstructorInternal(JSContext *ctx, call_func = ctx->rt->class_array[p->class_id].call; if (!call_func) { not_a_function: + if (ctx->rt->last_var_atom != JS_ATOM_NULL) { + JSAtom atom = ctx->rt->last_var_atom; + ctx->rt->last_var_atom = JS_ATOM_NULL; /* clear for next call */ + return JS_ThrowTypeErrorNotAFunctionAtom(ctx, atom); + } return JS_ThrowTypeErrorNotAFunction(ctx); } return call_func(ctx, func_obj, new_target, argc, @@ -37340,20 +37428,13 @@ static int JS_WriteRegExp(BCWriterState *s, JSRegExp regexp) JS_WriteString(s, regexp.pattern); - if (is_be()) { - if (lre_byte_swap(str8(bc), bc->len, /*is_byte_swapped*/false)) { - fail: - JS_ThrowInternalError(s->ctx, "regex byte swap failed"); - return -1; - } - } + if (is_be()) + lre_byte_swap(str8(bc), bc->len, /*is_byte_swapped*/false); JS_WriteString(s, bc); - if (is_be()) { - if (lre_byte_swap(str8(bc), bc->len, /*is_byte_swapped*/true)) - goto fail; - } + if (is_be()) + lre_byte_swap(str8(bc), bc->len, /*is_byte_swapped*/true); return 0; } @@ -38613,13 +38694,8 @@ static JSValue JS_ReadRegExp(BCReaderState *s) return JS_ThrowInternalError(ctx, "bad regexp bytecode"); } - if (is_be()) { - if (lre_byte_swap(str8(bc), bc->len, /*is_byte_swapped*/true)) { - js_free_string(ctx->rt, pattern); - js_free_string(ctx->rt, bc); - return JS_ThrowInternalError(ctx, "bad regexp bytecode"); - } - } + if (is_be()) + lre_byte_swap(str8(bc), bc->len, /*is_byte_swapped*/true); return js_regexp_constructor_internal(ctx, JS_UNDEFINED, JS_MKPTR(JS_TAG_STRING, pattern), @@ -38949,7 +39025,16 @@ static int check_function(JSContext *ctx, JSValueConst obj) { if (likely(JS_IsFunction(ctx, obj))) return 0; - JS_ThrowTypeErrorNotAFunction(ctx); + + if (ctx->rt->last_var_atom != JS_ATOM_NULL) { + JSAtom atom = ctx->rt->last_var_atom; + ctx->rt->last_var_atom = JS_ATOM_NULL; /* clear for next call */ + + JS_ThrowTypeErrorNotAFunctionAtom(ctx, atom); + } else { + JS_ThrowTypeErrorNotAFunction(ctx); + } + return -1; } @@ -47586,18 +47671,10 @@ static JSValue js_regexp_exec(JSContext *ctx, JSValueConst this_val, goto fail; } } else { - switch(rc) { - case LRE_RET_TIMEOUT: + if (rc == LRE_RET_TIMEOUT) { JS_ThrowInterrupted(ctx); - break; - case LRE_RET_MEMORY_ERROR: + } else { JS_ThrowInternalError(ctx, "out of memory in regexp execution"); - break; - case LRE_RET_BYTECODE_ERROR: - JS_ThrowInternalError(ctx, "corrupted bytecode in regexp execution"); - break; - default: - abort(); } goto fail; } @@ -47784,18 +47861,10 @@ static JSValue JS_RegExpDelete(JSContext *ctx, JSValueConst this_val, JSValue ar goto fail; } } else { - switch(ret) { - case LRE_RET_TIMEOUT: + if (ret == LRE_RET_TIMEOUT) { JS_ThrowInterrupted(ctx); - break; - case LRE_RET_MEMORY_ERROR: + } else { JS_ThrowInternalError(ctx, "out of memory in regexp execution"); - break; - case LRE_RET_BYTECODE_ERROR: - JS_ThrowInternalError(ctx, "corrupted bytecode in regexp execution"); - break; - default: - abort(); } goto fail; } From bcf06fed9cdfdc13d652722b50e31b3eb2e82c37 Mon Sep 17 00:00:00 2001 From: Russel Einsla Date: Wed, 4 Mar 2026 18:37:39 -0500 Subject: [PATCH 2/8] Attempt to fix the build and remove some debugging I forgot to remove --- quickjs.c | 21 ++------------------- 1 file changed, 2 insertions(+), 19 deletions(-) diff --git a/quickjs.c b/quickjs.c index 74ac2bfa1..e6f9c227b 100644 --- a/quickjs.c +++ b/quickjs.c @@ -17368,14 +17368,12 @@ static JSValue JS_CallInternal(JSContext *caller_ctx, JSValueConst func_obj, call_func = rt->class_array[p->class_id].call; if (!call_func) { not_a_function: - // printf("DEBUG: not_a_function reached, rt->last_var_atom=%d\n", (int)rt->last_var_atom); - fflush(stdout); if (rt->last_var_atom != JS_ATOM_NULL) { JSAtom atom = rt->last_var_atom; rt->last_var_atom = JS_ATOM_NULL; /* clear for next call */ return JS_ThrowTypeErrorNotAFunctionAtom(caller_ctx, atom); } - return JS_ThrowTypeError(ctx, "[ATOM_NULL_DEBUG] not a function"); + return JS_ThrowTypeError(caller_ctx, "not a function"); } return call_func(caller_ctx, func_obj, this_obj, argc, argv, flags); @@ -17792,8 +17790,6 @@ static JSValue JS_CallInternal(JSContext *caller_ctx, JSValueConst func_obj, CASE(OP_call): CASE(OP_tail_call): { - // printf("DEBUG: OP_call, rt->last_var_atom=%d\n", (int)rt->last_var_atom); - fflush(stdout); call_argc = get_u16(pc); pc += 2; goto has_call_argc; @@ -18084,8 +18080,6 @@ static JSValue JS_CallInternal(JSContext *caller_ctx, JSValueConst func_obj, pc += 4; sf->cur_pc = pc; rt->last_var_atom = atom; /* store atom for error messages */ - // printf("DEBUG: OP_get_var%s atom=%d\n", opcode == OP_get_var ? "" : "_undef", (int)atom); - // fflush(stdout); val = JS_GetGlobalVar(ctx, atom, opcode - OP_get_var_undef); if (unlikely(JS_IsException(val))) @@ -18152,7 +18146,6 @@ static JSValue JS_CallInternal(JSContext *caller_ctx, JSValueConst func_obj, idx = get_u16(pc); pc += 2; rt->last_var_atom = b->vardefs[idx].var_name; /* store atom for error messages */ - // printf("DEBUG: OP_get_loc idx=%d, atom=%d\n", idx, (int)rt->last_var_atom); sp[0] = js_dup(var_buf[idx]); sp++; } @@ -18240,14 +18233,11 @@ static JSValue JS_CallInternal(JSContext *caller_ctx, JSValueConst func_obj, CASE(OP_set_arg3): set_value(ctx, &arg_buf[3], js_dup(sp[-1])); BREAK; CASE(OP_get_var_ref0): { - /* printf("DEBUG: OP_get_var_ref0 called, b=%p, closure_var=%p, closure_var_count=%d\n", b, b ? b->closure_var : NULL, b ? (int)b->closure_var_count : 0); */ *sp++ = js_dup(*var_refs[0]->pvalue); if (b && b->vardefs && 0 < b->arg_count + b->var_count) { rt->last_var_atom = b->vardefs[0].var_name; - /* printf("DEBUG: OP_get_var_ref0 atom=%d\n", (int)rt->last_var_atom); */ } else if (b && b->closure_var && 0 < b->closure_var_count) { rt->last_var_atom = b->closure_var[0].var_name; - /* printf("DEBUG: OP_get_var_ref0 closure atom=%d\n", (int)rt->last_var_atom); */ } } BREAK; @@ -18257,7 +18247,6 @@ static JSValue JS_CallInternal(JSContext *caller_ctx, JSValueConst func_obj, *sp++ = js_dup(*var_refs[1]->pvalue); if (b && b->vardefs && 1 < b->arg_count + b->var_count) { rt->last_var_atom = b->vardefs[1].var_name; - /* printf("DEBUG: OP_get_var_ref1 atom=%d\n", (int)rt->last_var_atom); */ } } BREAK; @@ -18267,7 +18256,6 @@ static JSValue JS_CallInternal(JSContext *caller_ctx, JSValueConst func_obj, *sp++ = js_dup(*var_refs[2]->pvalue); if (b && b->vardefs && 2 < b->arg_count + b->var_count) { rt->last_var_atom = b->vardefs[2].var_name; - /* printf("DEBUG: OP_get_var_ref2 atom=%d\n", (int)rt->last_var_atom); */ } } BREAK; @@ -18294,8 +18282,6 @@ static JSValue JS_CallInternal(JSContext *caller_ctx, JSValueConst func_obj, JSValue val; idx = get_u16(pc); pc += 2; - // printf("DEBUG: OP_get_var_ref idx=%d\n", idx); - val = *var_refs[idx]->pvalue; sp[0] = js_dup(val); sp++; @@ -18303,13 +18289,10 @@ static JSValue JS_CallInternal(JSContext *caller_ctx, JSValueConst func_obj, /* store atom for error messages */ if (b && b->vardefs && idx < b->arg_count + b->var_count) { rt->last_var_atom = b->vardefs[idx].var_name; - // printf("DEBUG: OP_get_var_ref idx=%d atom=%d\n", idx, (int)rt->last_var_atom); } else if (b && b->closure_var && idx >= b->arg_count + b->var_count) { int closure_idx = idx - (b->arg_count + b->var_count); if (closure_idx < b->closure_var_count) { rt->last_var_atom = b->closure_var[closure_idx].var_name; - // printf("DEBUG: OP_get_var_ref idx=%d closure atom=%d\n", idx, (int)rt->last_var_atom); - } } } @@ -39029,7 +39012,7 @@ static int check_function(JSContext *ctx, JSValueConst obj) if (ctx->rt->last_var_atom != JS_ATOM_NULL) { JSAtom atom = ctx->rt->last_var_atom; ctx->rt->last_var_atom = JS_ATOM_NULL; /* clear for next call */ - + JS_ThrowTypeErrorNotAFunctionAtom(ctx, atom); } else { JS_ThrowTypeErrorNotAFunction(ctx); From 3c5a9c56374f2d066191badc2d6e16aa7e590436 Mon Sep 17 00:00:00 2001 From: Russel Einsla Date: Wed, 4 Mar 2026 20:23:41 -0500 Subject: [PATCH 3/8] Add range validation & Protect all opcode path for test262 Added validity checks before storing atom for error messages. --- quickjs.c | 48 ++++++++++++++++++++++++++++++++---------------- 1 file changed, 32 insertions(+), 16 deletions(-) diff --git a/quickjs.c b/quickjs.c index e6f9c227b..d9d6f73d1 100644 --- a/quickjs.c +++ b/quickjs.c @@ -18079,7 +18079,10 @@ static JSValue JS_CallInternal(JSContext *caller_ctx, JSValueConst func_obj, atom = get_u32(pc); pc += 4; sf->cur_pc = pc; - rt->last_var_atom = atom; /* store atom for error messages */ + /* only store atom if it looks valid */ + if (atom != JS_ATOM_NULL && atom < JS_ATOM_END) { + rt->last_var_atom = atom; /* store atom for error messages */ + } val = JS_GetGlobalVar(ctx, atom, opcode - OP_get_var_undef); if (unlikely(JS_IsException(val))) @@ -18145,7 +18148,13 @@ static JSValue JS_CallInternal(JSContext *caller_ctx, JSValueConst func_obj, int idx; idx = get_u16(pc); pc += 2; - rt->last_var_atom = b->vardefs[idx].var_name; /* store atom for error messages */ + /* only store atom if it looks valid */ + if (b && b->vardefs && idx < b->arg_count + b->var_count) { + JSAtom atom = b->vardefs[idx].var_name; + if (atom != JS_ATOM_NULL && atom < JS_ATOM_END) { + rt->last_var_atom = atom; + } + } sp[0] = js_dup(var_buf[idx]); sp++; } @@ -18194,7 +18203,7 @@ static JSValue JS_CallInternal(JSContext *caller_ctx, JSValueConst func_obj, } BREAK; - CASE(OP_get_loc8): rt->last_var_atom = b->vardefs[*pc].var_name; BREAK; + CASE(OP_get_loc8): if (b && b->vardefs) { JSAtom a = b->vardefs[*pc].var_name; if (a != JS_ATOM_NULL && a < JS_ATOM_END) rt->last_var_atom = a; } BREAK; CASE(OP_put_loc8): set_value(ctx, &var_buf[*pc++], *--sp); BREAK; CASE(OP_set_loc8): set_value(ctx, &var_buf[*pc++], js_dup(sp[-1])); BREAK; @@ -18203,14 +18212,14 @@ static JSValue JS_CallInternal(JSContext *caller_ctx, JSValueConst func_obj, // making them ideal candidates for opcode fusion. CASE(OP_get_loc0_loc1): *sp++ = js_dup(var_buf[0]); - rt->last_var_atom = b->vardefs[1].var_name; + if (b && b->vardefs) { JSAtom a = b->vardefs[1].var_name; if (a != JS_ATOM_NULL && a < JS_ATOM_END) rt->last_var_atom = a; } *sp++ = js_dup(var_buf[1]); BREAK; - CASE(OP_get_loc0): rt->last_var_atom = b->vardefs[0].var_name; *sp++ = js_dup(var_buf[0]); BREAK; - CASE(OP_get_loc1): rt->last_var_atom = b->vardefs[1].var_name; *sp++ = js_dup(var_buf[1]); BREAK; - CASE(OP_get_loc2): rt->last_var_atom = b->vardefs[2].var_name; *sp++ = js_dup(var_buf[2]); BREAK; - CASE(OP_get_loc3): rt->last_var_atom = b->vardefs[3].var_name; *sp++ = js_dup(var_buf[3]); BREAK; + CASE(OP_get_loc0): if (b && b->vardefs) { JSAtom a = b->vardefs[0].var_name; if (a != JS_ATOM_NULL && a < JS_ATOM_END) rt->last_var_atom = a; } *sp++ = js_dup(var_buf[0]); BREAK; + CASE(OP_get_loc1): if (b && b->vardefs) { JSAtom a = b->vardefs[1].var_name; if (a != JS_ATOM_NULL && a < JS_ATOM_END) rt->last_var_atom = a; } *sp++ = js_dup(var_buf[1]); BREAK; + CASE(OP_get_loc2): if (b && b->vardefs) { JSAtom a = b->vardefs[2].var_name; if (a != JS_ATOM_NULL && a < JS_ATOM_END) rt->last_var_atom = a; } *sp++ = js_dup(var_buf[2]); BREAK; + CASE(OP_get_loc3): if (b && b->vardefs) { JSAtom a = b->vardefs[3].var_name; if (a != JS_ATOM_NULL && a < JS_ATOM_END) rt->last_var_atom = a; } *sp++ = js_dup(var_buf[3]); BREAK; CASE(OP_put_loc0): set_value(ctx, &var_buf[0], *--sp); BREAK; CASE(OP_put_loc1): set_value(ctx, &var_buf[1], *--sp); BREAK; CASE(OP_put_loc2): set_value(ctx, &var_buf[2], *--sp); BREAK; @@ -18235,9 +18244,11 @@ static JSValue JS_CallInternal(JSContext *caller_ctx, JSValueConst func_obj, { *sp++ = js_dup(*var_refs[0]->pvalue); if (b && b->vardefs && 0 < b->arg_count + b->var_count) { - rt->last_var_atom = b->vardefs[0].var_name; + JSAtom a = b->vardefs[0].var_name; + if (a != JS_ATOM_NULL && a < JS_ATOM_END) rt->last_var_atom = a; } else if (b && b->closure_var && 0 < b->closure_var_count) { - rt->last_var_atom = b->closure_var[0].var_name; + JSAtom a = b->closure_var[0].var_name; + if (a != JS_ATOM_NULL && a < JS_ATOM_END) rt->last_var_atom = a; } } BREAK; @@ -18246,7 +18257,8 @@ static JSValue JS_CallInternal(JSContext *caller_ctx, JSValueConst func_obj, { *sp++ = js_dup(*var_refs[1]->pvalue); if (b && b->vardefs && 1 < b->arg_count + b->var_count) { - rt->last_var_atom = b->vardefs[1].var_name; + JSAtom a = b->vardefs[1].var_name; + if (a != JS_ATOM_NULL && a < JS_ATOM_END) rt->last_var_atom = a; } } BREAK; @@ -18255,7 +18267,8 @@ static JSValue JS_CallInternal(JSContext *caller_ctx, JSValueConst func_obj, { *sp++ = js_dup(*var_refs[2]->pvalue); if (b && b->vardefs && 2 < b->arg_count + b->var_count) { - rt->last_var_atom = b->vardefs[2].var_name; + JSAtom a = b->vardefs[2].var_name; + if (a != JS_ATOM_NULL && a < JS_ATOM_END) rt->last_var_atom = a; } } BREAK; @@ -18263,7 +18276,8 @@ static JSValue JS_CallInternal(JSContext *caller_ctx, JSValueConst func_obj, { *sp++ = js_dup(*var_refs[3]->pvalue); if (b && b->vardefs && 3 < b->arg_count + b->var_count) { - rt->last_var_atom = b->vardefs[3].var_name; + JSAtom a = b->vardefs[3].var_name; + if (a != JS_ATOM_NULL && a < JS_ATOM_END) rt->last_var_atom = a; } } BREAK; @@ -18286,13 +18300,15 @@ static JSValue JS_CallInternal(JSContext *caller_ctx, JSValueConst func_obj, sp[0] = js_dup(val); sp++; - /* store atom for error messages */ + /* store atom for error messages - with validity checks */ if (b && b->vardefs && idx < b->arg_count + b->var_count) { - rt->last_var_atom = b->vardefs[idx].var_name; + JSAtom a = b->vardefs[idx].var_name; + if (a != JS_ATOM_NULL && a < JS_ATOM_END) rt->last_var_atom = a; } else if (b && b->closure_var && idx >= b->arg_count + b->var_count) { int closure_idx = idx - (b->arg_count + b->var_count); if (closure_idx < b->closure_var_count) { - rt->last_var_atom = b->closure_var[closure_idx].var_name; + JSAtom a = b->closure_var[closure_idx].var_name; + if (a != JS_ATOM_NULL && a < JS_ATOM_END) rt->last_var_atom = a; } } } From a62a0d9a502462bfe122a99568b186e87b434bfb Mon Sep 17 00:00:00 2001 From: Russel Einsla Date: Thu, 5 Mar 2026 12:36:25 -0500 Subject: [PATCH 4/8] fix use-after-free bug in JS_CallInternal with last_var_atom; & remove [success] from debugging prints ensures that any modifications to rt->last_var_atom during the function are temporary and don't leave dangling references after the function returns. --- quickjs.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/quickjs.c b/quickjs.c index d9d6f73d1..eb48e0648 100644 --- a/quickjs.c +++ b/quickjs.c @@ -8069,7 +8069,7 @@ static JSValue JS_ThrowTypeErrorNotAFunctionAtom(JSContext *ctx, JSAtom atom) } char buf[ATOM_GET_STR_BUF_SIZE]; JS_AtomGetStr(ctx, buf, sizeof(buf), atom); - return JS_ThrowTypeError(ctx, "[SUCCESS] %s is not a function", buf); + return JS_ThrowTypeError(ctx, "%s is not a function", buf); } static JSValue JS_ThrowTypeErrorNotAnObject(JSContext *ctx) @@ -17308,6 +17308,7 @@ static JSValue JS_CallInternal(JSContext *caller_ctx, JSValueConst func_obj, JSValue *local_buf, *stack_buf, *var_buf, *arg_buf, *sp, ret_val, *pval; JSVarRef **var_refs; size_t alloca_size; + JSAtom saved_last_var_atom; /* save and restore last_var_atom to avoid use-after-free */ #ifdef ENABLE_DUMPS // JS_DUMP_BYTECODE_STEP #define DUMP_BYTECODE_OR_DONT(pc) \ @@ -17334,6 +17335,9 @@ static JSValue JS_CallInternal(JSContext *caller_ctx, JSValueConst func_obj, #define BREAK SWITCH(pc) #endif + /* Save last_var_atom to avoid use-after-free bugs */ + saved_last_var_atom = rt->last_var_atom; + if (js_poll_interrupts(caller_ctx)) return JS_EXCEPTION; if (unlikely(JS_VALUE_GET_TAG(func_obj) != JS_TAG_OBJECT)) { @@ -20016,6 +20020,8 @@ static JSValue JS_CallInternal(JSContext *caller_ctx, JSValueConst func_obj, } } exception: + /* Restore last_var_atom before handling exception */ + rt->last_var_atom = saved_last_var_atom; if (needs_backtrace(rt->current_exception) || JS_IsUndefined(ctx->error_back_trace)) { sf->cur_pc = pc; @@ -20064,6 +20070,8 @@ static JSValue JS_CallInternal(JSContext *caller_ctx, JSValueConst func_obj, } } rt->current_stack_frame = sf->prev_frame; + /* Restore last_var_atom on normal return */ + rt->last_var_atom = saved_last_var_atom; return ret_val; } From a8384087de167e4c03f61cad763720c69fc32953 Mon Sep 17 00:00:00 2001 From: Russel Einsla Date: Sat, 7 Mar 2026 09:39:48 -0500 Subject: [PATCH 5/8] Undo changes --- quickjs.c | 59 ++++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 45 insertions(+), 14 deletions(-) diff --git a/quickjs.c b/quickjs.c index eb48e0648..9fd0bc9fa 100644 --- a/quickjs.c +++ b/quickjs.c @@ -37434,9 +37434,14 @@ static int JS_WriteRegExp(BCWriterState *s, JSRegExp regexp) assert(!bc->is_wide_char); JS_WriteString(s, regexp.pattern); - - if (is_be()) - lre_byte_swap(str8(bc), bc->len, /*is_byte_swapped*/false); + + if (is_be()) { + if (lre_byte_swap(str8(bc), bc->len, /*is_byte_swapped*/false)) { + fail: + JS_ThrowInternalError(s->ctx, "regex byte swap failed"); + return -1; + } + } JS_WriteString(s, bc); @@ -38701,8 +38706,13 @@ static JSValue JS_ReadRegExp(BCReaderState *s) return JS_ThrowInternalError(ctx, "bad regexp bytecode"); } - if (is_be()) - lre_byte_swap(str8(bc), bc->len, /*is_byte_swapped*/true); + if (is_be()) { + if (lre_byte_swap(str8(bc), bc->len, /* is_byte_swapped */true)) { + js_free_string(ctx->rt, pattern); + js_free_string(ctx->rt, bc); + return JS_ThrowInternalError(ctx, "bad regexp bytecode"); + } + } return js_regexp_constructor_internal(ctx, JS_UNDEFINED, JS_MKPTR(JS_TAG_STRING, pattern), @@ -47678,10 +47688,23 @@ static JSValue js_regexp_exec(JSContext *ctx, JSValueConst this_val, goto fail; } } else { - if (rc == LRE_RET_TIMEOUT) { - JS_ThrowInterrupted(ctx); - } else { - JS_ThrowInternalError(ctx, "out of memory in regexp execution"); + // if (rc == LRE_RET_TIMEOUT) { + // JS_ThrowInterrupted(ctx); + // } else { + // JS_ThrowInternalError(ctx, "out of memory in regexp execution"); + // } + switch(rc) { + case LRE_RET_TIMEOUT: + JS_ThrowInterrupted(ctx); + break; + case LRE_RET_MEMORY_ERROR: + JS_ThrowInternalError(ctx, "out of memory in regexp execution"); + break; + case LRE_RET_BYTECODE_ERROR: + JS_ThrowInternalError(ctx, "corrupted bytecode in regexp execution"); + break; + default: + abort(); } goto fail; } @@ -47868,11 +47891,19 @@ static JSValue JS_RegExpDelete(JSContext *ctx, JSValueConst this_val, JSValue ar goto fail; } } else { - if (ret == LRE_RET_TIMEOUT) { - JS_ThrowInterrupted(ctx); - } else { - JS_ThrowInternalError(ctx, "out of memory in regexp execution"); - } + switch(ret) { + case LRE_RET_TIMEOUT: + JS_ThrowInterrupted(ctx); + break; + case LRE_RET_MEMORY_ERROR: + JS_ThrowInternalError(ctx, "out of memory in regexp execution"); + break; + case LRE_RET_BYTECODE_ERROR: + JS_ThrowInternalError(ctx, "corrupted bytecode in regexp execution"); + break; + default: + abort(); + } goto fail; } break; From 5973868e79f561b16c95b768663b5e6390bd5670 Mon Sep 17 00:00:00 2001 From: Russel Einsla Date: Sat, 7 Mar 2026 09:49:38 -0500 Subject: [PATCH 6/8] Create test1231.js --- tests/test1231.js | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 tests/test1231.js diff --git a/tests/test1231.js b/tests/test1231.js new file mode 100644 index 000000000..7331f0681 --- /dev/null +++ b/tests/test1231.js @@ -0,0 +1,2 @@ +var a = 0; +a(); From 541dbd31572238574eb66ee570e85b6c7334a9f6 Mon Sep 17 00:00:00 2001 From: Russel Einsla Date: Mon, 9 Mar 2026 20:12:38 -0400 Subject: [PATCH 7/8] Fix CI --- quickjs.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/quickjs.c b/quickjs.c index 9fd0bc9fa..77fce52cd 100644 --- a/quickjs.c +++ b/quickjs.c @@ -37437,7 +37437,6 @@ static int JS_WriteRegExp(BCWriterState *s, JSRegExp regexp) if (is_be()) { if (lre_byte_swap(str8(bc), bc->len, /*is_byte_swapped*/false)) { - fail: JS_ThrowInternalError(s->ctx, "regex byte swap failed"); return -1; } @@ -38714,7 +38713,10 @@ static JSValue JS_ReadRegExp(BCReaderState *s) } } - return js_regexp_constructor_internal(ctx, JS_UNDEFINED, + /* Pass ctx->regexp_ctor (not JS_UNDEFINED) to bypass regexp_shape + fast path during deserialization*/ + return js_regexp_constructor_internal(ctx, + ctx->class_proto[JS_CLASS_REGEXP], JS_MKPTR(JS_TAG_STRING, pattern), JS_MKPTR(JS_TAG_STRING, bc)); } From 5f6d8ab99d14ccac21cd6c9fe231fb9cb9cc1e18 Mon Sep 17 00:00:00 2001 From: Russel Einsla Date: Tue, 10 Mar 2026 21:47:35 -0400 Subject: [PATCH 8/8] fix OP_get_loc8 missing stack push and pc advance --- quickjs.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/quickjs.c b/quickjs.c index 77fce52cd..55901c527 100644 --- a/quickjs.c +++ b/quickjs.c @@ -18207,7 +18207,7 @@ static JSValue JS_CallInternal(JSContext *caller_ctx, JSValueConst func_obj, } BREAK; - CASE(OP_get_loc8): if (b && b->vardefs) { JSAtom a = b->vardefs[*pc].var_name; if (a != JS_ATOM_NULL && a < JS_ATOM_END) rt->last_var_atom = a; } BREAK; + CASE(OP_get_loc8): if (b && b->vardefs) { JSAtom a = b->vardefs[*pc].var_name; if (a != JS_ATOM_NULL && a < JS_ATOM_END) rt->last_var_atom = a; } *sp++ = js_dup(var_buf[*pc++]); BREAK; CASE(OP_put_loc8): set_value(ctx, &var_buf[*pc++], *--sp); BREAK; CASE(OP_set_loc8): set_value(ctx, &var_buf[*pc++], js_dup(sp[-1])); BREAK;