Skip to content

Commit a57877c

Browse files
authored
Retain WeakMap values only when key is reachable (#1016)
Fix an object leak when WeakMap keys are cyclically referenced by WeakMap values. Fixes: #1015
1 parent 2a12c7e commit a57877c

File tree

2 files changed

+87
-24
lines changed

2 files changed

+87
-24
lines changed

api-test.c

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,48 @@ static void two_byte_string(void)
269269
JS_FreeRuntime(rt);
270270
}
271271

272+
static void weak_map_gc_check(void)
273+
{
274+
static const char init_code[] =
275+
"const map = new WeakMap(); \
276+
function addItem() { \
277+
const k = { \
278+
text: 'a', \
279+
}; \
280+
map.set(k, {k}); \
281+
}";
282+
static const char test_code[] = "addItem()";
283+
284+
JSRuntime *rt = JS_NewRuntime();
285+
JSContext *ctx = JS_NewContext(rt);
286+
287+
JSValue ret = JS_Eval(ctx, init_code, strlen(init_code), "<input>", JS_EVAL_TYPE_GLOBAL);
288+
assert(!JS_IsException(ret));
289+
290+
JSValue ret_test = JS_Eval(ctx, test_code, strlen(test_code), "<input>", JS_EVAL_TYPE_GLOBAL);
291+
assert(!JS_IsException(ret_test));
292+
JS_RunGC(rt);
293+
JSMemoryUsage memory_usage;
294+
JS_ComputeMemoryUsage(rt, &memory_usage);
295+
296+
for (int i = 0; i < 3; i++) {
297+
JSValue ret_test2 = JS_Eval(ctx, test_code, strlen(test_code), "<input>", JS_EVAL_TYPE_GLOBAL);
298+
assert(!JS_IsException(ret_test2));
299+
JS_RunGC(rt);
300+
JSMemoryUsage memory_usage2;
301+
JS_ComputeMemoryUsage(rt, &memory_usage2);
302+
303+
assert(memory_usage.memory_used_count == memory_usage2.memory_used_count);
304+
assert(memory_usage.memory_used_size == memory_usage2.memory_used_size);
305+
JS_FreeValue(ctx, ret_test2);
306+
}
307+
308+
JS_FreeValue(ctx, ret);
309+
JS_FreeValue(ctx, ret_test);
310+
JS_FreeContext(ctx);
311+
JS_FreeRuntime(rt);
312+
}
313+
272314
int main(void)
273315
{
274316
sync_call();
@@ -278,5 +320,6 @@ int main(void)
278320
is_array();
279321
module_serde();
280322
two_byte_string();
323+
weak_map_gc_check();
281324
return 0;
282325
}

quickjs.c

Lines changed: 44 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -493,6 +493,26 @@ typedef struct JSWeakRefRecord {
493493
} u;
494494
} JSWeakRefRecord;
495495

496+
typedef struct JSMapRecord {
497+
int ref_count; /* used during enumeration to avoid freeing the record */
498+
bool empty; /* true if the record is deleted */
499+
struct JSMapState *map;
500+
struct list_head link;
501+
struct list_head hash_link;
502+
JSValue key;
503+
JSValue value;
504+
} JSMapRecord;
505+
506+
typedef struct JSMapState {
507+
bool is_weak; /* true if WeakSet/WeakMap */
508+
struct list_head records; /* list of JSMapRecord.link */
509+
uint32_t record_count;
510+
struct list_head *hash_table;
511+
uint32_t hash_size; /* must be a power of two */
512+
uint32_t record_count_threshold; /* count at which a hash table
513+
resize is needed */
514+
} JSMapState;
515+
496516
enum {
497517
JS_ATOM_TYPE_STRING = 1,
498518
JS_ATOM_TYPE_GLOBAL_SYMBOL,
@@ -1694,8 +1714,8 @@ static JSClassShortDef const js_std_class_def[] = {
16941714
{ JS_ATOM_BigInt, js_object_data_finalizer, js_object_data_mark }, /* JS_CLASS_BIG_INT */
16951715
{ JS_ATOM_Map, js_map_finalizer, js_map_mark }, /* JS_CLASS_MAP */
16961716
{ JS_ATOM_Set, js_map_finalizer, js_map_mark }, /* JS_CLASS_SET */
1697-
{ JS_ATOM_WeakMap, js_map_finalizer, js_map_mark }, /* JS_CLASS_WEAKMAP */
1698-
{ JS_ATOM_WeakSet, js_map_finalizer, js_map_mark }, /* JS_CLASS_WEAKSET */
1717+
{ JS_ATOM_WeakMap, js_map_finalizer, NULL }, /* JS_CLASS_WEAKMAP */
1718+
{ JS_ATOM_WeakSet, js_map_finalizer, NULL }, /* JS_CLASS_WEAKSET */
16991719
{ JS_ATOM_Iterator, NULL, NULL }, /* JS_CLASS_ITERATOR */
17001720
{ JS_ATOM_IteratorHelper, js_iterator_helper_finalizer, js_iterator_helper_mark }, /* JS_CLASS_ITERATOR_HELPER */
17011721
{ JS_ATOM_IteratorWrap, js_iterator_wrap_finalizer, js_iterator_wrap_mark }, /* JS_CLASS_ITERATOR_WRAP */
@@ -5783,6 +5803,22 @@ void JS_MarkValue(JSRuntime *rt, JSValueConst val, JS_MarkFunc *mark_func)
57835803
}
57845804
}
57855805

5806+
static void mark_weak_map_value(JSRuntime *rt, JSWeakRefRecord *first_weak_ref, JS_MarkFunc *mark_func) {
5807+
JSWeakRefRecord *wr;
5808+
JSMapRecord *mr;
5809+
JSMapState *s;
5810+
5811+
for (wr = first_weak_ref; wr != NULL; wr = wr->next_weak_ref) {
5812+
if (wr->kind == JS_WEAK_REF_KIND_MAP) {
5813+
mr = wr->u.map_record;
5814+
s = mr->map;
5815+
assert(s->is_weak);
5816+
assert(!mr->empty); /* no iterator on WeakMap/WeakSet */
5817+
JS_MarkValue(rt, mr->value, mark_func);
5818+
}
5819+
}
5820+
}
5821+
57865822
static void mark_children(JSRuntime *rt, JSGCObjectHeader *gp,
57875823
JS_MarkFunc *mark_func)
57885824
{
@@ -5822,6 +5858,10 @@ static void mark_children(JSRuntime *rt, JSGCObjectHeader *gp,
58225858
prs++;
58235859
}
58245860

5861+
if (unlikely(p->first_weak_ref)) {
5862+
mark_weak_map_value(rt, p->first_weak_ref, mark_func);
5863+
}
5864+
58255865
if (p->class_id != JS_CLASS_OBJECT) {
58265866
JSClassGCMark *gc_mark;
58275867
gc_mark = rt->class_array[p->class_id].gc_mark;
@@ -48433,26 +48473,6 @@ static const JSCFunctionListEntry js_symbol_funcs[] = {
4843348473

4843448474
/* Set/Map/WeakSet/WeakMap */
4843548475

48436-
typedef struct JSMapRecord {
48437-
int ref_count; /* used during enumeration to avoid freeing the record */
48438-
bool empty; /* true if the record is deleted */
48439-
struct JSMapState *map;
48440-
struct list_head link;
48441-
struct list_head hash_link;
48442-
JSValue key;
48443-
JSValue value;
48444-
} JSMapRecord;
48445-
48446-
typedef struct JSMapState {
48447-
bool is_weak; /* true if WeakSet/WeakMap */
48448-
struct list_head records; /* list of JSMapRecord.link */
48449-
uint32_t record_count;
48450-
struct list_head *hash_table;
48451-
uint32_t hash_size; /* must be a power of two */
48452-
uint32_t record_count_threshold; /* count at which a hash table
48453-
resize is needed */
48454-
} JSMapState;
48455-
4845648476
#define MAGIC_SET (1 << 0)
4845748477
#define MAGIC_WEAK (1 << 1)
4845848478

@@ -49056,10 +49076,10 @@ static void js_map_mark(JSRuntime *rt, JSValueConst val,
4905649076

4905749077
s = p->u.map_state;
4905849078
if (s) {
49079+
assert(!s->is_weak);
4905949080
list_for_each(el, &s->records) {
4906049081
mr = list_entry(el, JSMapRecord, link);
49061-
if (!s->is_weak)
49062-
JS_MarkValue(rt, mr->key, mark_func);
49082+
JS_MarkValue(rt, mr->key, mark_func);
4906349083
JS_MarkValue(rt, mr->value, mark_func);
4906449084
}
4906549085
}

0 commit comments

Comments
 (0)