quickjs-tart

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

commit 10cbb55fd86a69ba7fbf8b4d5575b59b862231c2
parent 7bb7a3e78a9baa176297d71d81a42aa2072a7824
Author: Florian Dold <florian@dold.me>
Date:   Mon, 10 Mar 2025 19:09:10 +0100

Revert "quickjs: fix garbage collection / var ref bug"

This reverts commit f6b5ac20dca2e40c4eb210fb0a4548e04bf69329.

Diffstat:
Mquickjs/quickjs.c | 124+------------------------------------------------------------------------------
Dquickjs/tests/crash1.js | 16----------------
Dquickjs/tests/crash2.js | 19-------------------
Dquickjs/tests/gentest.js | 18------------------
Dquickjs/tests/promtest3.js | 44--------------------------------------------
5 files changed, 1 insertion(+), 220 deletions(-)

diff --git a/quickjs/quickjs.c b/quickjs/quickjs.c @@ -338,10 +338,6 @@ typedef struct JSStackFrame { /* only used in generators. Current stack pointer value. NULL if the function is running. */ JSValue *cur_sp; - /* only used in async/generator functions to save the variable references - that have been evicted to the heap. */ - struct JSVarRef **closed_var_refs; - int closed_var_refs_count; } JSStackFrame; typedef enum { @@ -5489,8 +5485,7 @@ void __JS_FreeValueRT(JSRuntime *rt, JSValue v) if (tag == JS_TAG_OBJECT) { JS_DumpObject(rt, JS_VALUE_GET_OBJ(v)); } else { - printf("[<short value>]"); - //JS_DumpValueShort(rt, v); + JS_DumpValueShort(rt, v); printf("\n"); } } @@ -15985,94 +15980,6 @@ static int js_op_define_class(JSContext *ctx, JSValue *sp, return -1; } -/* Re-attach variable refs to the local, - * on-stack variables when resuming an async/generator function. */ -static void reopen_var_refs(JSRuntime *rt, JSStackFrame *sf) -{ - int i; - JSVarRef *var_ref; - int var_idx; - - for (i = 0; i < sf->closed_var_refs_count; i++) { - var_ref = sf->closed_var_refs[i]; - sf->closed_var_refs[i] = NULL; - if (!var_ref) { - continue; - } - var_idx = var_ref->var_idx; - assert(var_ref->is_detached); - - var_ref->is_detached = FALSE; - - if (var_ref->is_arg) { - JS_FreeValueRT(rt, sf->arg_buf[var_idx]); - sf->arg_buf[var_idx] = var_ref->value; - var_ref->pvalue = &sf->arg_buf[var_idx]; - } else { - JS_FreeValueRT(rt, sf->var_buf[var_idx]); - sf->var_buf[var_idx] = var_ref->value; - var_ref->pvalue = &sf->var_buf[var_idx]; - } - remove_gc_object(&var_ref->header); - list_add_tail(&var_ref->header.link, &sf->var_ref_list); - free_var_ref(rt, var_ref); - } -} - -static void close_var_refs_saving(JSRuntime *rt, JSStackFrame *sf) -{ - struct list_head *el, *el1; - JSVarRef **var_refs; - JSVarRef *var_ref; - int var_idx; - int count = 0; - int i; - - if (list_empty(&sf->var_ref_list)) { - return; - } - - list_for_each_safe(el, el1, &sf->var_ref_list) { - count++; - } - var_refs = sf->closed_var_refs; - if (count > sf->closed_var_refs_count) { - var_refs = js_realloc_rt(rt, var_refs, count * sizeof(var_refs[0])); - if (!var_refs) { - // FIXME - abort(); - } - for (i = sf->closed_var_refs_count; i < count; i++) { - var_refs[i] = NULL; - } - sf->closed_var_refs = var_refs; - sf->closed_var_refs_count = count; - } - - i = 0; - list_for_each_safe(el, el1, &sf->var_ref_list) { - var_ref = list_entry(el, JSVarRef, header.link); - var_idx = var_ref->var_idx; - assert(!var_ref->is_detached); - /* move ownership from stack into varref */ - if (var_ref->is_arg) - var_ref->value = JS_DupValueRT(rt, sf->arg_buf[var_idx]); - else - var_ref->value = JS_DupValueRT(rt, sf->var_buf[var_idx]); - var_ref->pvalue = &var_ref->value; - /* the reference is no longer to a local variable */ - var_ref->is_detached = TRUE; - /* delete from stack frame's var_ref_list */ - list_del(&var_ref->header.link); - /* prevent from collecting while async/generator is still active */ - var_refs[i] = var_ref; - var_ref->header.ref_count++; - add_gc_object(rt, &var_ref->header, JS_GC_OBJ_TYPE_VAR_REF); - i++; - } - assert(list_empty(&sf->var_ref_list)); -} - static void close_var_refs(JSRuntime *rt, JSStackFrame *sf) { struct list_head *el, *el1; @@ -16082,7 +15989,6 @@ static void close_var_refs(JSRuntime *rt, JSStackFrame *sf) list_for_each_safe(el, el1, &sf->var_ref_list) { var_ref = list_entry(el, JSVarRef, header.link); var_idx = var_ref->var_idx; - assert(!var_ref->is_detached); if (var_ref->is_arg) var_ref->value = JS_DupValueRT(rt, sf->arg_buf[var_idx]); else @@ -16090,8 +15996,6 @@ static void close_var_refs(JSRuntime *rt, JSStackFrame *sf) var_ref->pvalue = &var_ref->value; /* the reference is no longer to a local variable */ var_ref->is_detached = TRUE; - /* delete from stack frame's var_ref_list */ - list_del(&var_ref->header.link); add_gc_object(rt, &var_ref->header, JS_GC_OBJ_TYPE_VAR_REF); } } @@ -16363,7 +16267,6 @@ static JSValue __JS_CallInternal(JSContext *caller_ctx, JSValueConst func_obj, pc = sf->cur_pc; sf->prev_frame = rt->current_stack_frame; rt->current_stack_frame = sf; - reopen_var_refs(rt, sf); if (s->throw_flag) goto exception; else @@ -16396,9 +16299,6 @@ static JSValue __JS_CallInternal(JSContext *caller_ctx, JSValueConst func_obj, if (js_check_stack_overflow(rt, alloca_size)) return JS_ThrowStackOverflow(caller_ctx); - sf->closed_var_refs = NULL; - sf->closed_var_refs_count = 0; - sf->js_mode = b->js_mode; arg_buf = argv; sf->arg_count = argc; @@ -18807,7 +18707,6 @@ static JSValue __JS_CallInternal(JSContext *caller_ctx, JSValueConst func_obj, generator function. */ if (b->func_kind != JS_FUNC_NORMAL) { done_generator: - close_var_refs_saving(rt, sf); sf->cur_pc = pc; sf->cur_sp = sp; } else { @@ -19224,15 +19123,6 @@ static void async_func_mark(JSRuntime *rt, JSAsyncFunctionState *s, for(sp = sf->arg_buf; sp < sf->cur_sp; sp++) JS_MarkValue(rt, *sp, mark_func); } - if (sf->closed_var_refs) - { - int i; - for (i = 0; i < sf->closed_var_refs_count; i++) { - if (sf->closed_var_refs[i]) { - mark_func(rt, &sf->closed_var_refs[i]->header); - } - } - } } static void async_func_free(JSRuntime *rt, JSAsyncFunctionState *s) @@ -19253,18 +19143,6 @@ static void async_func_free(JSRuntime *rt, JSAsyncFunctionState *s) } js_free_rt(rt, sf->arg_buf); } - if (sf->closed_var_refs) - { - int i; - for (i = 0; i < sf->closed_var_refs_count; i++) { - if (sf->closed_var_refs[i]) { - free_var_ref(rt, sf->closed_var_refs[i]); - } - } - js_free_rt(rt, sf->closed_var_refs); - sf->closed_var_refs = NULL; - sf->closed_var_refs_count = 0; - } JS_FreeValueRT(rt, sf->cur_func); JS_FreeValueRT(rt, s->this_val); } diff --git a/quickjs/tests/crash1.js b/quickjs/tests/crash1.js @@ -1,16 +0,0 @@ -import * as std from "std"; - -async function waitUntilDone(ws) { - let p = {}; - globalThis.escFun = function escFun() { return p; }; - p.promise = new Promise(function promIni() {}); - await p.promise; -} - -waitUntilDone(); - -//console.log("-------- running gc1"); -//std.gc(); -//console.log("-------- running gc2"); -//std.gc(); -//console.log("-------- end"); diff --git a/quickjs/tests/crash2.js b/quickjs/tests/crash2.js @@ -1,19 +0,0 @@ -import * as std from "std"; - -let listener; - -async function waitUntilDone(ws) { - let p2 = {}; - listener = (yn) => { - return p2; - }; - p2.promise = new Promise(() => {}); - await p2.promise; -} - -waitUntilDone(); - -console.log("---- calling gc"); -std.gc(); -console.log("---- calling listener"); -listener(); diff --git a/quickjs/tests/gentest.js b/quickjs/tests/gentest.js @@ -1,18 +0,0 @@ -function *foo() { - let x = 42; - console.log("first yield"); - yield () => x; - console.log("second yield"); - yield v => { x = v; }; - console.log("my x", x); -} - -const g = foo(); - -const fun1 = g.next().value; -const fun2 = g.next().value; - -console.log(fun1()); -fun2(41); -console.log(fun1()); -g.next(); diff --git a/quickjs/tests/promtest3.js b/quickjs/tests/promtest3.js @@ -1,44 +0,0 @@ -// Running this file crashes vanilla quickjs-2021-03-27 -// due to a use-after-free bug. -// That happens because innerFun keeps a reference -// to the local variable p, but p gets erroneously -// garbage-collected. -// The outerFun infinitely waits for the promise -// to finish. Thus, its local variables are kept on the -// stack. Thus innerFun doesn't directly reference -// outerFun, and outerFun gets collected -// by the cycle collector. - -import * as os from "os"; -import * as std from "std"; - -let f; - -let promise = new Promise(() => {}); - -let outerFun = async function outerFun() { - let p = { foo: "bar" }; - function innerFun() { - console.log(p.foo); - } - f = innerFun; - // Wait indefinitely - await promise; -} -outerFun(); - -// directly - -//console.log("cleaning up"); -//console.log("deleting promise"); -//promise = undefined; -//console.log("deleting outerFun"); -//outerFun = undefined; -//console.log("deleting f"); -//f = undefined; -//console.log("exiting"); -//std.exit(42); - - -// via timer -os.setTimeout(() => { console.log("calling GC"); std.gc(); f(); }, 0);