Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions api-test.c
Original file line number Diff line number Diff line change
Expand Up @@ -473,6 +473,26 @@ static void promise_mark_as_handled(void)
JS_FreeRuntime(rt);
}

static void clear_kept_objects(void)
{
JSRuntime *rt = new_runtime();
JSContext *ctx = JS_NewContext(rt);

JSValue w = eval(ctx, "let o = {x:1}; const w = new WeakRef(o); o = null; w");
assert(JS_IsObject(w));
JSValue got = eval(ctx, "w.deref()");
assert(JS_IsObject(got));
JS_FreeValue(ctx, got);

JS_ClearKeptObjects(rt);
got = eval(ctx, "w.deref()");
assert(JS_IsUndefined(got));

JS_FreeValue(ctx, w);
JS_FreeContext(ctx);
JS_FreeRuntime(rt);
}

static void runtime_cstring_free(void)
{
JSRuntime *rt = new_runtime();
Expand Down Expand Up @@ -1294,6 +1314,7 @@ int main(void)
module_serde();
module_unhandled_rejection();
promise_mark_as_handled();
clear_kept_objects();
runtime_cstring_free();
utf16_string();
weak_map_gc_check();
Expand Down
37 changes: 37 additions & 0 deletions quickjs.c
Original file line number Diff line number Diff line change
Expand Up @@ -378,6 +378,9 @@ struct JSRuntime {
// to js_promise_constructor
JSValueLink *parent_promise;

// AddToKeptObjects list, dropped at job boundaries
JSValueLink *kept_objects;

JSHostPromiseRejectionTracker *host_promise_rejection_tracker;
void *host_promise_rejection_tracker_opaque;

Expand Down Expand Up @@ -2527,6 +2530,10 @@ int JS_ExecutePendingJob(JSRuntime *rt, JSContext **pctx)
JSValue res;
int i, ret;

/* job boundary: the host calling us means the previous script or job
finished */
JS_ClearKeptObjects(rt);

if (list_empty(&rt->job_list)) {
*pctx = NULL;
return 0;
Expand Down Expand Up @@ -2641,6 +2648,7 @@ void JS_FreeRuntime(JSRuntime *rt)

rt->in_free = true;
JS_FreeValueRT(rt, rt->current_exception);
JS_ClearKeptObjects(rt);

list_for_each_safe(el, el1, &rt->job_list) {
JSJobEntry *e = list_entry(el, JSJobEntry, link);
Expand Down Expand Up @@ -62499,6 +62507,29 @@ typedef struct JSWeakRefData {

static JSWeakRefData js_weakref_sentinel;

static int js_add_to_kept_objects(JSContext *ctx, JSValueConst value)
{
JSValueLink *link = js_malloc(ctx, sizeof(*link));
if (!link)
return -1;
link->value = js_dup(value);
link->next = ctx->rt->kept_objects;
ctx->rt->kept_objects = link;
return 0;
}

void JS_ClearKeptObjects(JSRuntime *rt)
{
JSValueLink *link = rt->kept_objects;
rt->kept_objects = NULL;
while (link) {
JSValueLink *next = link->next;
JS_FreeValueRT(rt, unsafe_unconst(link->value));
js_free_rt(rt, link);
link = next;
}
}

static void js_weakref_finalizer(JSRuntime *rt, JSValueConst val)
{
JSWeakRefData *wrd = JS_GetOpaque(val, JS_CLASS_WEAK_REF);
Expand Down Expand Up @@ -62551,6 +62582,10 @@ static JSValue js_weakref_constructor(JSContext *ctx, JSValueConst new_target,
insert_weakref_record(arg, wr);

JS_SetOpaqueInternal(obj, wrd);
if (js_add_to_kept_objects(ctx, arg)) {
JS_FreeValue(ctx, obj);
return JS_EXCEPTION;
}
return obj;
}

Expand All @@ -62561,6 +62596,8 @@ static JSValue js_weakref_deref(JSContext *ctx, JSValueConst this_val, int argc,
return JS_EXCEPTION;
if (wrd == &js_weakref_sentinel)
return JS_UNDEFINED;
if (js_add_to_kept_objects(ctx, wrd->target))
return JS_EXCEPTION;
return js_dup(wrd->target);
}

Expand Down
1 change: 1 addition & 0 deletions quickjs.h
Original file line number Diff line number Diff line change
Expand Up @@ -557,6 +557,7 @@ JS_EXTERN int JS_AddIntrinsicTypedArrays(JSContext *ctx);
JS_EXTERN int JS_AddIntrinsicPromise(JSContext *ctx);
JS_EXTERN int JS_AddIntrinsicBigInt(JSContext *ctx);
JS_EXTERN int JS_AddIntrinsicWeakRef(JSContext *ctx);
JS_EXTERN void JS_ClearKeptObjects(JSRuntime *rt);
JS_EXTERN int JS_AddPerformance(JSContext *ctx);
JS_EXTERN int JS_AddIntrinsicDOMException(JSContext *ctx);
JS_EXTERN int JS_AddIntrinsicAToB(JSContext *ctx);
Expand Down
2 changes: 1 addition & 1 deletion tests/bug652.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { assert } from "./assert.js"
const ref = new WeakRef({})
const val = ref.deref() // should not throw
assert(val, undefined)
assert(typeof val, "object") // kept alive until the end of the job
20 changes: 20 additions & 0 deletions tests/weakref-kept-objects.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { assert } from "./assert.js";

/* the constructor keeps the target alive until the end of the job */
{
let o = { x: 1 };
const w = new WeakRef(o);
o = null;
assert(typeof w.deref(), "object");
}

/* deref() keeps it too, and the kept set is cleared at job boundaries */
{
let o = { x: 2 };
const w = new WeakRef(o);
assert(w.deref().x, 2);
o = null;
assert(typeof w.deref(), "object");
await Promise.resolve();
assert(w.deref(), undefined);
}
Loading