mirror of
https://github.com/bellard/quickjs.git
synced 2025-05-12 03:05:55 +08:00
reworked weak references so that cycles are (hopefully) correctly handled - added Symbol as WeakMap key, WeakRef and FinalizationRegistry
This commit is contained in:
parent
bf164d640f
commit
8b5b1277ad
2
TODO
2
TODO
@ -62,6 +62,6 @@ Optimization ideas:
|
||||
Test262o: 0/11262 errors, 463 excluded
|
||||
Test262o commit: 7da91bceb9ce7613f87db47ddd1292a2dda58b42 (es5-tests branch)
|
||||
|
||||
Result: 39/76768 errors, 3147 excluded, 7010 skipped
|
||||
Result: 39/76964 errors, 3147 excluded, 6912 skipped
|
||||
Test262 commit: 56e77d6325067a545ea7e8ff5be5d9284334e33c
|
||||
|
||||
|
@ -258,10 +258,6 @@ The following features are not supported yet:
|
||||
|
||||
@item Tail calls@footnote{We believe the current specification of tails calls is too complicated and presents limited practical interests.}
|
||||
|
||||
@item WeakRef and FinalizationRegistry objects
|
||||
|
||||
@item Symbols as WeakMap keys
|
||||
|
||||
@end itemize
|
||||
|
||||
@subsection ECMA402
|
||||
|
@ -210,6 +210,8 @@ DEF(Float32Array, "Float32Array")
|
||||
DEF(Float64Array, "Float64Array")
|
||||
DEF(DataView, "DataView")
|
||||
DEF(BigInt, "BigInt")
|
||||
DEF(WeakRef, "WeakRef")
|
||||
DEF(FinalizationRegistry, "FinalizationRegistry")
|
||||
DEF(Map, "Map")
|
||||
DEF(Set, "Set") /* Map + 1 */
|
||||
DEF(WeakMap, "WeakMap") /* Map + 2 */
|
||||
|
602
quickjs.c
602
quickjs.c
@ -171,7 +171,9 @@ enum {
|
||||
JS_CLASS_ASYNC_FROM_SYNC_ITERATOR, /* u.async_from_sync_iterator_data */
|
||||
JS_CLASS_ASYNC_GENERATOR_FUNCTION, /* u.func */
|
||||
JS_CLASS_ASYNC_GENERATOR, /* u.async_generator_data */
|
||||
|
||||
JS_CLASS_WEAK_REF,
|
||||
JS_CLASS_FINALIZATION_REGISTRY,
|
||||
|
||||
JS_CLASS_INIT_COUNT, /* last entry for predefined classes */
|
||||
};
|
||||
|
||||
@ -251,6 +253,7 @@ struct JSRuntime {
|
||||
struct list_head tmp_obj_list; /* used during GC */
|
||||
JSGCPhaseEnum gc_phase : 8;
|
||||
size_t malloc_gc_threshold;
|
||||
struct list_head weakref_list; /* list of JSWeakRefHeader.link */
|
||||
#ifdef DUMP_LEAKS
|
||||
struct list_head string_list; /* list of JSString.link */
|
||||
#endif
|
||||
@ -342,6 +345,17 @@ struct JSGCObjectHeader {
|
||||
struct list_head link;
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
JS_WEAKREF_TYPE_MAP,
|
||||
JS_WEAKREF_TYPE_WEAKREF,
|
||||
JS_WEAKREF_TYPE_FINREC,
|
||||
} JSWeakRefHeaderTypeEnum;
|
||||
|
||||
typedef struct {
|
||||
struct list_head link;
|
||||
JSWeakRefHeaderTypeEnum weakref_type;
|
||||
} JSWeakRefHeader;
|
||||
|
||||
typedef struct JSVarRef {
|
||||
union {
|
||||
JSGCObjectHeader header; /* must come first */
|
||||
@ -467,11 +481,6 @@ enum {
|
||||
JS_ATOM_TYPE_PRIVATE,
|
||||
};
|
||||
|
||||
enum {
|
||||
JS_ATOM_HASH_SYMBOL,
|
||||
JS_ATOM_HASH_PRIVATE,
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
JS_ATOM_KIND_STRING,
|
||||
JS_ATOM_KIND_SYMBOL,
|
||||
@ -479,13 +488,14 @@ typedef enum {
|
||||
} JSAtomKindEnum;
|
||||
|
||||
#define JS_ATOM_HASH_MASK ((1 << 30) - 1)
|
||||
#define JS_ATOM_HASH_PRIVATE JS_ATOM_HASH_MASK
|
||||
|
||||
struct JSString {
|
||||
JSRefCountHeader header; /* must come first, 32-bit */
|
||||
uint32_t len : 31;
|
||||
uint8_t is_wide_char : 1; /* 0 = 8 bits, 1 = 16 bits characters */
|
||||
/* for JS_ATOM_TYPE_SYMBOL: hash = 0, atom_type = 3,
|
||||
for JS_ATOM_TYPE_PRIVATE: hash = 1, atom_type = 3
|
||||
/* for JS_ATOM_TYPE_SYMBOL: hash = weakref_count, atom_type = 3,
|
||||
for JS_ATOM_TYPE_PRIVATE: hash = JS_ATOM_HASH_PRIVATE, atom_type = 3
|
||||
XXX: could change encoding to have one more bit in hash */
|
||||
uint32_t hash : 30;
|
||||
uint8_t atom_type : 2; /* != 0 if atom, JS_ATOM_TYPE_x */
|
||||
@ -907,12 +917,12 @@ struct JSObject {
|
||||
uint16_t class_id; /* see JS_CLASS_x */
|
||||
};
|
||||
};
|
||||
/* byte offsets: 16/24 */
|
||||
/* count the number of weak references to this object. The object
|
||||
structure is freed only if header.ref_count = 0 and
|
||||
weakref_count = 0 */
|
||||
uint32_t weakref_count;
|
||||
JSShape *shape; /* prototype and property names + flag */
|
||||
JSProperty *prop; /* array of properties */
|
||||
/* byte offsets: 24/40 */
|
||||
struct JSMapRecord *first_weak_ref; /* XXX: use a bit and an external hash table? */
|
||||
/* byte offsets: 28/48 */
|
||||
union {
|
||||
void *opaque;
|
||||
struct JSBoundFunction *bound_function; /* JS_CLASS_BOUND_FUNCTION */
|
||||
@ -969,7 +979,6 @@ struct JSObject {
|
||||
JSRegExp regexp; /* JS_CLASS_REGEXP: 8/16 bytes */
|
||||
JSValue object_data; /* for JS_SetObjectData(): 8/16/16 bytes */
|
||||
} u;
|
||||
/* byte sizes: 40/48/72 */
|
||||
};
|
||||
|
||||
enum {
|
||||
@ -1153,7 +1162,6 @@ static int JS_CreateProperty(JSContext *ctx, JSObject *p,
|
||||
int flags);
|
||||
static int js_string_memcmp(const JSString *p1, int pos1, const JSString *p2,
|
||||
int pos2, int len);
|
||||
static void reset_weak_ref(JSRuntime *rt, JSObject *p);
|
||||
static JSValue js_array_buffer_constructor3(JSContext *ctx,
|
||||
JSValueConst new_target,
|
||||
uint64_t len, JSClassID class_id,
|
||||
@ -1248,6 +1256,10 @@ static JSValue JS_InstantiateFunctionListItem2(JSContext *ctx, JSObject *p,
|
||||
JSAtom atom, void *opaque);
|
||||
static JSValue js_object_groupBy(JSContext *ctx, JSValueConst this_val,
|
||||
int argc, JSValueConst *argv, int is_map);
|
||||
static void map_delete_weakrefs(JSRuntime *rt, JSWeakRefHeader *wh);
|
||||
static void weakref_delete_weakref(JSRuntime *rt, JSWeakRefHeader *wh);
|
||||
static void finrec_delete_weakref(JSRuntime *rt, JSWeakRefHeader *wh);
|
||||
static void JS_RunGCInternal(JSRuntime *rt, BOOL remove_weak_objects);
|
||||
|
||||
static const JSClassExoticMethods js_arguments_exotic_methods;
|
||||
static const JSClassExoticMethods js_string_exotic_methods;
|
||||
@ -1548,6 +1560,7 @@ JSRuntime *JS_NewRuntime2(const JSMallocFunctions *mf, void *opaque)
|
||||
init_list_head(&rt->gc_obj_list);
|
||||
init_list_head(&rt->gc_zero_ref_count_list);
|
||||
rt->gc_phase = JS_GC_PHASE_NONE;
|
||||
init_list_head(&rt->weakref_list);
|
||||
|
||||
#ifdef DUMP_LEAKS
|
||||
init_list_head(&rt->string_list);
|
||||
@ -1848,7 +1861,9 @@ void JS_FreeRuntime(JSRuntime *rt)
|
||||
}
|
||||
init_list_head(&rt->job_list);
|
||||
|
||||
JS_RunGC(rt);
|
||||
/* don't remove the weak objects to avoid create new jobs with
|
||||
FinalizationRegistry */
|
||||
JS_RunGCInternal(rt, FALSE);
|
||||
|
||||
#ifdef DUMP_LEAKS
|
||||
/* leaking objects */
|
||||
@ -1890,6 +1905,7 @@ void JS_FreeRuntime(JSRuntime *rt)
|
||||
}
|
||||
#endif
|
||||
assert(list_empty(&rt->gc_obj_list));
|
||||
assert(list_empty(&rt->weakref_list));
|
||||
|
||||
/* free the classes */
|
||||
for(i = 0; i < rt->class_count; i++) {
|
||||
@ -1934,7 +1950,7 @@ void JS_FreeRuntime(JSRuntime *rt)
|
||||
printf(")");
|
||||
break;
|
||||
case JS_ATOM_TYPE_SYMBOL:
|
||||
if (p->hash == JS_ATOM_HASH_SYMBOL) {
|
||||
if (p->hash != JS_ATOM_HASH_PRIVATE) {
|
||||
printf("Symbol(");
|
||||
JS_DumpString(rt, p);
|
||||
printf(")");
|
||||
@ -2065,6 +2081,7 @@ JSContext *JS_NewContext(JSRuntime *rt)
|
||||
JS_AddIntrinsicMapSet(ctx);
|
||||
JS_AddIntrinsicTypedArrays(ctx);
|
||||
JS_AddIntrinsicPromise(ctx);
|
||||
JS_AddIntrinsicWeakRef(ctx);
|
||||
return ctx;
|
||||
}
|
||||
|
||||
@ -2542,14 +2559,10 @@ static JSAtomKindEnum JS_AtomGetKind(JSContext *ctx, JSAtom v)
|
||||
case JS_ATOM_TYPE_GLOBAL_SYMBOL:
|
||||
return JS_ATOM_KIND_SYMBOL;
|
||||
case JS_ATOM_TYPE_SYMBOL:
|
||||
switch(p->hash) {
|
||||
case JS_ATOM_HASH_SYMBOL:
|
||||
return JS_ATOM_KIND_SYMBOL;
|
||||
case JS_ATOM_HASH_PRIVATE:
|
||||
if (p->hash == JS_ATOM_HASH_PRIVATE)
|
||||
return JS_ATOM_KIND_PRIVATE;
|
||||
default:
|
||||
abort();
|
||||
}
|
||||
else
|
||||
return JS_ATOM_KIND_SYMBOL;
|
||||
default:
|
||||
abort();
|
||||
}
|
||||
@ -2619,7 +2632,7 @@ static JSAtom __JS_NewAtom(JSRuntime *rt, JSString *str, int atom_type)
|
||||
} else {
|
||||
h1 = 0; /* avoid warning */
|
||||
if (atom_type == JS_ATOM_TYPE_SYMBOL) {
|
||||
h = JS_ATOM_HASH_SYMBOL;
|
||||
h = 0;
|
||||
} else {
|
||||
h = JS_ATOM_HASH_PRIVATE;
|
||||
atom_type = JS_ATOM_TYPE_SYMBOL;
|
||||
@ -2812,7 +2825,13 @@ static void JS_FreeAtomStruct(JSRuntime *rt, JSAtomStruct *p)
|
||||
#ifdef DUMP_LEAKS
|
||||
list_del(&p->link);
|
||||
#endif
|
||||
js_free_rt(rt, p);
|
||||
if (p->atom_type == JS_ATOM_TYPE_SYMBOL &&
|
||||
p->hash != JS_ATOM_HASH_PRIVATE && p->hash != 0) {
|
||||
/* live weak references are still present on this object: keep
|
||||
it */
|
||||
} else {
|
||||
js_free_rt(rt, p);
|
||||
}
|
||||
rt->atom_count--;
|
||||
assert(rt->atom_count >= 0);
|
||||
}
|
||||
@ -3157,7 +3176,7 @@ static BOOL JS_AtomSymbolHasDescription(JSContext *ctx, JSAtom v)
|
||||
return FALSE;
|
||||
p = rt->atom_array[v];
|
||||
return (((p->atom_type == JS_ATOM_TYPE_SYMBOL &&
|
||||
p->hash == JS_ATOM_HASH_SYMBOL) ||
|
||||
p->hash != JS_ATOM_HASH_PRIVATE) ||
|
||||
p->atom_type == JS_ATOM_TYPE_GLOBAL_SYMBOL) &&
|
||||
!(p->len == 0 && p->is_wide_char != 0));
|
||||
}
|
||||
@ -5045,7 +5064,7 @@ static JSValue JS_NewObjectFromShape(JSContext *ctx, JSShape *sh, JSClassID clas
|
||||
p->is_uncatchable_error = 0;
|
||||
p->tmp_mark = 0;
|
||||
p->is_HTMLDDA = 0;
|
||||
p->first_weak_ref = NULL;
|
||||
p->weakref_count = 0;
|
||||
p->u.opaque = NULL;
|
||||
p->shape = sh;
|
||||
p->prop = js_malloc(ctx, sizeof(JSProperty) * sh->prop_size);
|
||||
@ -5723,10 +5742,6 @@ static void free_object(JSRuntime *rt, JSObject *p)
|
||||
p->shape = NULL;
|
||||
p->prop = NULL;
|
||||
|
||||
if (unlikely(p->first_weak_ref)) {
|
||||
reset_weak_ref(rt, p);
|
||||
}
|
||||
|
||||
finalizer = rt->class_array[p->class_id].finalizer;
|
||||
if (finalizer)
|
||||
(*finalizer)(rt, JS_MKPTR(JS_TAG_OBJECT, p));
|
||||
@ -5738,10 +5753,19 @@ static void free_object(JSRuntime *rt, JSObject *p)
|
||||
p->u.func.home_object = NULL;
|
||||
|
||||
remove_gc_object(&p->header);
|
||||
if (rt->gc_phase == JS_GC_PHASE_REMOVE_CYCLES && p->header.ref_count != 0) {
|
||||
list_add_tail(&p->header.link, &rt->gc_zero_ref_count_list);
|
||||
if (rt->gc_phase == JS_GC_PHASE_REMOVE_CYCLES) {
|
||||
if (p->header.ref_count == 0 && p->weakref_count == 0) {
|
||||
js_free_rt(rt, p);
|
||||
} else {
|
||||
/* keep the object structure because there are may be
|
||||
references to it */
|
||||
list_add_tail(&p->header.link, &rt->gc_zero_ref_count_list);
|
||||
}
|
||||
} else {
|
||||
js_free_rt(rt, p);
|
||||
/* keep the object structure in case there are weak references to it */
|
||||
if (p->weakref_count == 0) {
|
||||
js_free_rt(rt, p);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -5859,6 +5883,36 @@ void __JS_FreeValue(JSContext *ctx, JSValue v)
|
||||
|
||||
/* garbage collection */
|
||||
|
||||
static void gc_remove_weak_objects(JSRuntime *rt)
|
||||
{
|
||||
struct list_head *el;
|
||||
|
||||
/* add the freed objects to rt->gc_zero_ref_count_list so that
|
||||
rt->weakref_list is not modified while we traverse it */
|
||||
rt->gc_phase = JS_GC_PHASE_DECREF;
|
||||
|
||||
list_for_each(el, &rt->weakref_list) {
|
||||
JSWeakRefHeader *wh = list_entry(el, JSWeakRefHeader, link);
|
||||
switch(wh->weakref_type) {
|
||||
case JS_WEAKREF_TYPE_MAP:
|
||||
map_delete_weakrefs(rt, wh);
|
||||
break;
|
||||
case JS_WEAKREF_TYPE_WEAKREF:
|
||||
weakref_delete_weakref(rt, wh);
|
||||
break;
|
||||
case JS_WEAKREF_TYPE_FINREC:
|
||||
finrec_delete_weakref(rt, wh);
|
||||
break;
|
||||
default:
|
||||
abort();
|
||||
}
|
||||
}
|
||||
|
||||
rt->gc_phase = JS_GC_PHASE_NONE;
|
||||
/* free the freed objects here. */
|
||||
free_zero_refcount(rt);
|
||||
}
|
||||
|
||||
static void add_gc_object(JSRuntime *rt, JSGCObjectHeader *h,
|
||||
JSGCObjectTypeEnum type)
|
||||
{
|
||||
@ -6109,14 +6163,27 @@ static void gc_free_cycles(JSRuntime *rt)
|
||||
assert(p->gc_obj_type == JS_GC_OBJ_TYPE_JS_OBJECT ||
|
||||
p->gc_obj_type == JS_GC_OBJ_TYPE_FUNCTION_BYTECODE ||
|
||||
p->gc_obj_type == JS_GC_OBJ_TYPE_ASYNC_FUNCTION);
|
||||
js_free_rt(rt, p);
|
||||
if (p->gc_obj_type == JS_GC_OBJ_TYPE_JS_OBJECT &&
|
||||
((JSObject *)p)->weakref_count != 0) {
|
||||
/* keep the object because there are weak references to it */
|
||||
p->mark = 0;
|
||||
} else {
|
||||
js_free_rt(rt, p);
|
||||
}
|
||||
}
|
||||
|
||||
init_list_head(&rt->gc_zero_ref_count_list);
|
||||
}
|
||||
|
||||
void JS_RunGC(JSRuntime *rt)
|
||||
static void JS_RunGCInternal(JSRuntime *rt, BOOL remove_weak_objects)
|
||||
{
|
||||
if (remove_weak_objects) {
|
||||
/* free the weakly referenced object or symbol structures, delete
|
||||
the associated Map/Set entries and queue the finalization
|
||||
registry callbacks. */
|
||||
gc_remove_weak_objects(rt);
|
||||
}
|
||||
|
||||
/* decrement the reference of the children of each object. mark =
|
||||
1 after this pass. */
|
||||
gc_decref(rt);
|
||||
@ -6128,6 +6195,11 @@ void JS_RunGC(JSRuntime *rt)
|
||||
gc_free_cycles(rt);
|
||||
}
|
||||
|
||||
void JS_RunGC(JSRuntime *rt)
|
||||
{
|
||||
JS_RunGCInternal(rt, TRUE);
|
||||
}
|
||||
|
||||
/* Return false if not an object or if the object has already been
|
||||
freed (zombie objects are visible in finalizers when freeing
|
||||
cycles). */
|
||||
@ -46481,9 +46553,7 @@ static const JSCFunctionListEntry js_symbol_funcs[] = {
|
||||
|
||||
typedef struct JSMapRecord {
|
||||
int ref_count; /* used during enumeration to avoid freeing the record */
|
||||
BOOL empty; /* TRUE if the record is deleted */
|
||||
struct JSMapState *map;
|
||||
struct JSMapRecord *next_weak_ref;
|
||||
BOOL empty : 8; /* TRUE if the record is deleted */
|
||||
struct list_head link;
|
||||
struct JSMapRecord *hash_next;
|
||||
JSValue key;
|
||||
@ -46498,8 +46568,82 @@ typedef struct JSMapState {
|
||||
uint32_t hash_size; /* must be a power of two */
|
||||
uint32_t record_count_threshold; /* count at which a hash table
|
||||
resize is needed */
|
||||
JSWeakRefHeader weakref_header; /* only used if is_weak = TRUE */
|
||||
} JSMapState;
|
||||
|
||||
static BOOL js_weakref_is_target(JSValueConst val)
|
||||
{
|
||||
switch (JS_VALUE_GET_TAG(val)) {
|
||||
case JS_TAG_OBJECT:
|
||||
return TRUE;
|
||||
case JS_TAG_SYMBOL:
|
||||
{
|
||||
JSAtomStruct *p = JS_VALUE_GET_PTR(val);
|
||||
if (p->atom_type == JS_ATOM_TYPE_SYMBOL &&
|
||||
p->hash != JS_ATOM_HASH_PRIVATE)
|
||||
return TRUE;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* JS_UNDEFINED is considered as a live weakref */
|
||||
static BOOL js_weakref_is_live(JSValueConst val)
|
||||
{
|
||||
int *pref_count;
|
||||
if (JS_IsUndefined(val))
|
||||
return TRUE;
|
||||
pref_count = JS_VALUE_GET_PTR(val);
|
||||
return (*pref_count != 0);
|
||||
}
|
||||
|
||||
/* 'val' can be JS_UNDEFINED */
|
||||
static void js_weakref_free(JSRuntime *rt, JSValue val)
|
||||
{
|
||||
if (JS_VALUE_GET_TAG(val) == JS_TAG_OBJECT) {
|
||||
JSObject *p = JS_VALUE_GET_OBJ(val);
|
||||
assert(p->weakref_count >= 1);
|
||||
p->weakref_count--;
|
||||
if (p->weakref_count == 0 && p->header.ref_count == 0) {
|
||||
if (rt->gc_phase == JS_GC_PHASE_REMOVE_CYCLES && p->header.mark) {
|
||||
/* cannot remove now the structure if it is involved in a cycle */
|
||||
} else {
|
||||
/* can remove the dummy structure */
|
||||
js_free_rt(rt, p);
|
||||
}
|
||||
}
|
||||
} else if (JS_VALUE_GET_TAG(val) == JS_TAG_SYMBOL) {
|
||||
JSString *p = JS_VALUE_GET_STRING(val);
|
||||
assert(p->hash >= 1);
|
||||
p->hash--;
|
||||
if (p->hash == 0 && p->header.ref_count == 0) {
|
||||
/* can remove the dummy structure */
|
||||
js_free_rt(rt, p);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* val must be an object, a symbol or undefined (see
|
||||
js_weakref_is_target). */
|
||||
static JSValue js_weakref_new(JSContext *ctx, JSValueConst val)
|
||||
{
|
||||
if (JS_VALUE_GET_TAG(val) == JS_TAG_OBJECT) {
|
||||
JSObject *p = JS_VALUE_GET_OBJ(val);
|
||||
p->weakref_count++;
|
||||
} else if (JS_VALUE_GET_TAG(val) == JS_TAG_SYMBOL) {
|
||||
JSString *p = JS_VALUE_GET_STRING(val);
|
||||
/* XXX: could return an exception if too many references */
|
||||
assert(p->hash < JS_ATOM_HASH_MASK - 2);
|
||||
p->hash++;
|
||||
} else {
|
||||
assert(JS_IsUndefined(val));
|
||||
}
|
||||
return (JSValue)val;
|
||||
}
|
||||
|
||||
#define MAGIC_SET (1 << 0)
|
||||
#define MAGIC_WEAK (1 << 1)
|
||||
|
||||
@ -46521,6 +46665,10 @@ static JSValue js_map_constructor(JSContext *ctx, JSValueConst new_target,
|
||||
goto fail;
|
||||
init_list_head(&s->records);
|
||||
s->is_weak = is_weak;
|
||||
if (is_weak) {
|
||||
s->weakref_header.weakref_type = JS_WEAKREF_TYPE_MAP;
|
||||
list_add_tail(&s->weakref_header.link, &ctx->rt->weakref_list);
|
||||
}
|
||||
JS_SetOpaque(obj, s);
|
||||
s->hash_size = 1;
|
||||
s->hash_table = js_mallocz(ctx, sizeof(s->hash_table[0]) * s->hash_size);
|
||||
@ -46687,8 +46835,12 @@ static JSMapRecord *map_find_record(JSContext *ctx, JSMapState *s,
|
||||
uint32_t h;
|
||||
h = map_hash_key(key) & (s->hash_size - 1);
|
||||
for(mr = s->hash_table[h]; mr != NULL; mr = mr->hash_next) {
|
||||
if (js_same_value_zero(ctx, mr->key, key))
|
||||
return mr;
|
||||
if (mr->empty || (s->is_weak && !js_weakref_is_live(mr->key))) {
|
||||
/* cannot match */
|
||||
} else {
|
||||
if (js_same_value_zero(ctx, mr->key, key))
|
||||
return mr;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
@ -46714,7 +46866,8 @@ static void map_hash_resize(JSContext *ctx, JSMapState *s)
|
||||
|
||||
list_for_each(el, &s->records) {
|
||||
mr = list_entry(el, JSMapRecord, link);
|
||||
if (!mr->empty) {
|
||||
if (mr->empty || (s->is_weak && !js_weakref_is_live(mr->key))) {
|
||||
} else {
|
||||
h = map_hash_key(mr->key) & (new_hash_size - 1);
|
||||
mr->hash_next = new_hash_table[h];
|
||||
new_hash_table[h] = mr;
|
||||
@ -46735,17 +46888,12 @@ static JSMapRecord *map_add_record(JSContext *ctx, JSMapState *s,
|
||||
if (!mr)
|
||||
return NULL;
|
||||
mr->ref_count = 1;
|
||||
mr->map = s;
|
||||
mr->empty = FALSE;
|
||||
if (s->is_weak) {
|
||||
JSObject *p = JS_VALUE_GET_OBJ(key);
|
||||
/* Add the weak reference */
|
||||
mr->next_weak_ref = p->first_weak_ref;
|
||||
p->first_weak_ref = mr;
|
||||
mr->key = js_weakref_new(ctx, key);
|
||||
} else {
|
||||
JS_DupValue(ctx, key);
|
||||
mr->key = JS_DupValue(ctx, key);
|
||||
}
|
||||
mr->key = (JSValue)key;
|
||||
h = map_hash_key(key) & (s->hash_size - 1);
|
||||
mr->hash_next = s->hash_table[h];
|
||||
s->hash_table[h] = mr;
|
||||
@ -46757,27 +46905,6 @@ static JSMapRecord *map_add_record(JSContext *ctx, JSMapState *s,
|
||||
return mr;
|
||||
}
|
||||
|
||||
/* Remove the weak reference from the object weak
|
||||
reference list. we don't use a doubly linked list to
|
||||
save space, assuming a given object has few weak
|
||||
references to it */
|
||||
static void delete_weak_ref(JSRuntime *rt, JSMapRecord *mr)
|
||||
{
|
||||
JSMapRecord **pmr, *mr1;
|
||||
JSObject *p;
|
||||
|
||||
p = JS_VALUE_GET_OBJ(mr->key);
|
||||
pmr = &p->first_weak_ref;
|
||||
for(;;) {
|
||||
mr1 = *pmr;
|
||||
assert(mr1 != NULL);
|
||||
if (mr1 == mr)
|
||||
break;
|
||||
pmr = &mr1->next_weak_ref;
|
||||
}
|
||||
*pmr = mr1->next_weak_ref;
|
||||
}
|
||||
|
||||
/* warning: the record must be removed from the hash table before */
|
||||
static void map_delete_record(JSRuntime *rt, JSMapState *s, JSMapRecord *mr)
|
||||
{
|
||||
@ -46785,7 +46912,7 @@ static void map_delete_record(JSRuntime *rt, JSMapState *s, JSMapRecord *mr)
|
||||
return;
|
||||
|
||||
if (s->is_weak) {
|
||||
delete_weak_ref(rt, mr);
|
||||
js_weakref_free(rt, mr->key);
|
||||
} else {
|
||||
JS_FreeValueRT(rt, mr->key);
|
||||
}
|
||||
@ -46812,45 +46939,17 @@ static void map_decref_record(JSRuntime *rt, JSMapRecord *mr)
|
||||
}
|
||||
}
|
||||
|
||||
static void reset_weak_ref(JSRuntime *rt, JSObject *p)
|
||||
static void map_delete_weakrefs(JSRuntime *rt, JSWeakRefHeader *wh)
|
||||
{
|
||||
JSMapRecord *mr, *mr_next, **pmr, *mr1;
|
||||
JSMapState *s;
|
||||
uint32_t h;
|
||||
JSValue key;
|
||||
|
||||
/* first pass to remove the records from the WeakMap/WeakSet
|
||||
lists */
|
||||
key = JS_MKPTR(JS_TAG_OBJECT, p);
|
||||
for(mr = p->first_weak_ref; mr != NULL; mr = mr->next_weak_ref) {
|
||||
s = mr->map;
|
||||
assert(s->is_weak);
|
||||
assert(!mr->empty); /* no iterator on WeakMap/WeakSet */
|
||||
JSMapState *s = container_of(wh, JSMapState, weakref_header);
|
||||
struct list_head *el, *el1;
|
||||
|
||||
/* remove the record from hash table */
|
||||
h = map_hash_key(key) & (s->hash_size - 1);
|
||||
pmr = &s->hash_table[h];
|
||||
for(;;) {
|
||||
mr1 = *pmr;
|
||||
assert(mr1 != NULL);
|
||||
if (mr1 == mr)
|
||||
break;
|
||||
pmr = &mr1->hash_next;
|
||||
list_for_each_safe(el, el1, &s->records) {
|
||||
JSMapRecord *mr = list_entry(el, JSMapRecord, link);
|
||||
if (!js_weakref_is_live(mr->key)) {
|
||||
map_delete_record(rt, s, mr);
|
||||
}
|
||||
*pmr = mr->hash_next;
|
||||
|
||||
list_del(&mr->link);
|
||||
}
|
||||
|
||||
/* second pass to free the values to avoid modifying the weak
|
||||
reference list while traversing it. */
|
||||
for(mr = p->first_weak_ref; mr != NULL; mr = mr_next) {
|
||||
mr_next = mr->next_weak_ref;
|
||||
JS_FreeValueRT(rt, mr->value);
|
||||
js_free_rt(rt, mr);
|
||||
}
|
||||
|
||||
p->first_weak_ref = NULL; /* fail safe */
|
||||
}
|
||||
|
||||
static JSValue js_map_set(JSContext *ctx, JSValueConst this_val,
|
||||
@ -46863,8 +46962,8 @@ static JSValue js_map_set(JSContext *ctx, JSValueConst this_val,
|
||||
if (!s)
|
||||
return JS_EXCEPTION;
|
||||
key = map_normalize_key(ctx, argv[0]);
|
||||
if (s->is_weak && !JS_IsObject(key))
|
||||
return JS_ThrowTypeErrorNotAnObject(ctx);
|
||||
if (s->is_weak && !js_weakref_is_target(key))
|
||||
return JS_ThrowTypeError(ctx, "invalid value used as %s key", (magic & MAGIC_SET) ? "WeakSet" : "WeakMap");
|
||||
if (magic & MAGIC_SET)
|
||||
value = JS_UNDEFINED;
|
||||
else
|
||||
@ -46930,8 +47029,12 @@ static JSValue js_map_delete(JSContext *ctx, JSValueConst this_val,
|
||||
mr = *pmr;
|
||||
if (mr == NULL)
|
||||
return JS_FALSE;
|
||||
if (js_same_value_zero(ctx, mr->key, key))
|
||||
break;
|
||||
if (mr->empty || (s->is_weak && !js_weakref_is_live(mr->key))) {
|
||||
/* not valid */
|
||||
} else {
|
||||
if (js_same_value_zero(ctx, mr->key, key))
|
||||
break;
|
||||
}
|
||||
pmr = &mr->hash_next;
|
||||
}
|
||||
|
||||
@ -47151,7 +47254,7 @@ static void js_map_finalizer(JSRuntime *rt, JSValue val)
|
||||
mr = list_entry(el, JSMapRecord, link);
|
||||
if (!mr->empty) {
|
||||
if (s->is_weak)
|
||||
delete_weak_ref(rt, mr);
|
||||
js_weakref_free(rt, mr->key);
|
||||
else
|
||||
JS_FreeValueRT(rt, mr->key);
|
||||
JS_FreeValueRT(rt, mr->value);
|
||||
@ -47159,6 +47262,9 @@ static void js_map_finalizer(JSRuntime *rt, JSValue val)
|
||||
js_free_rt(rt, mr);
|
||||
}
|
||||
js_free_rt(rt, s->hash_table);
|
||||
if (s->is_weak) {
|
||||
list_del(&s->weakref_header.link);
|
||||
}
|
||||
js_free_rt(rt, s);
|
||||
}
|
||||
}
|
||||
@ -53717,3 +53823,277 @@ void JS_AddIntrinsicTypedArrays(JSContext *ctx)
|
||||
JS_AddIntrinsicAtomics(ctx);
|
||||
#endif
|
||||
}
|
||||
|
||||
/* WeakRef */
|
||||
|
||||
typedef struct JSWeakRefData {
|
||||
JSWeakRefHeader weakref_header;
|
||||
JSValue target;
|
||||
} JSWeakRefData;
|
||||
|
||||
static void js_weakref_finalizer(JSRuntime *rt, JSValue val)
|
||||
{
|
||||
JSWeakRefData *wrd = JS_GetOpaque(val, JS_CLASS_WEAK_REF);
|
||||
if (!wrd)
|
||||
return;
|
||||
js_weakref_free(rt, wrd->target);
|
||||
list_del(&wrd->weakref_header.link);
|
||||
js_free_rt(rt, wrd);
|
||||
}
|
||||
|
||||
static void weakref_delete_weakref(JSRuntime *rt, JSWeakRefHeader *wh)
|
||||
{
|
||||
JSWeakRefData *wrd = container_of(wh, JSWeakRefData, weakref_header);
|
||||
|
||||
if (!js_weakref_is_live(wrd->target)) {
|
||||
js_weakref_free(rt, wrd->target);
|
||||
wrd->target = JS_UNDEFINED;
|
||||
}
|
||||
}
|
||||
|
||||
static JSValue js_weakref_constructor(JSContext *ctx, JSValueConst new_target,
|
||||
int argc, JSValueConst *argv)
|
||||
{
|
||||
JSValueConst arg;
|
||||
JSValue obj;
|
||||
|
||||
if (JS_IsUndefined(new_target))
|
||||
return JS_ThrowTypeError(ctx, "constructor requires 'new'");
|
||||
arg = argv[0];
|
||||
if (!js_weakref_is_target(arg))
|
||||
return JS_ThrowTypeError(ctx, "invalid target");
|
||||
obj = js_create_from_ctor(ctx, new_target, JS_CLASS_WEAK_REF);
|
||||
if (JS_IsException(obj))
|
||||
return JS_EXCEPTION;
|
||||
JSWeakRefData *wrd = js_mallocz(ctx, sizeof(*wrd));
|
||||
if (!wrd) {
|
||||
JS_FreeValue(ctx, obj);
|
||||
return JS_EXCEPTION;
|
||||
}
|
||||
wrd->target = js_weakref_new(ctx, arg);
|
||||
wrd->weakref_header.weakref_type = JS_WEAKREF_TYPE_WEAKREF;
|
||||
list_add_tail(&wrd->weakref_header.link, &ctx->rt->weakref_list);
|
||||
JS_SetOpaque(obj, wrd);
|
||||
return obj;
|
||||
}
|
||||
|
||||
static JSValue js_weakref_deref(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
|
||||
{
|
||||
JSWeakRefData *wrd = JS_GetOpaque2(ctx, this_val, JS_CLASS_WEAK_REF);
|
||||
if (!wrd)
|
||||
return JS_EXCEPTION;
|
||||
return JS_DupValue(ctx, wrd->target);
|
||||
}
|
||||
|
||||
static const JSCFunctionListEntry js_weakref_proto_funcs[] = {
|
||||
JS_CFUNC_DEF("deref", 0, js_weakref_deref ),
|
||||
JS_PROP_STRING_DEF("[Symbol.toStringTag]", "WeakRef", JS_PROP_CONFIGURABLE ),
|
||||
};
|
||||
|
||||
static const JSClassShortDef js_weakref_class_def[] = {
|
||||
{ JS_ATOM_WeakRef, js_weakref_finalizer, NULL }, /* JS_CLASS_WEAK_REF */
|
||||
};
|
||||
|
||||
typedef struct JSFinRecEntry {
|
||||
struct list_head link;
|
||||
JSValue target;
|
||||
JSValue held_val;
|
||||
JSValue token;
|
||||
} JSFinRecEntry;
|
||||
|
||||
typedef struct JSFinalizationRegistryData {
|
||||
JSWeakRefHeader weakref_header;
|
||||
struct list_head entries; /* list of JSFinRecEntry.link */
|
||||
JSContext *ctx;
|
||||
JSValue cb;
|
||||
} JSFinalizationRegistryData;
|
||||
|
||||
static void js_finrec_finalizer(JSRuntime *rt, JSValue val)
|
||||
{
|
||||
JSFinalizationRegistryData *frd = JS_GetOpaque(val, JS_CLASS_FINALIZATION_REGISTRY);
|
||||
if (frd) {
|
||||
struct list_head *el, *el1;
|
||||
list_for_each_safe(el, el1, &frd->entries) {
|
||||
JSFinRecEntry *fre = list_entry(el, JSFinRecEntry, link);
|
||||
js_weakref_free(rt, fre->target);
|
||||
js_weakref_free(rt, fre->token);
|
||||
JS_FreeValueRT(rt, fre->held_val);
|
||||
js_free_rt(rt, fre);
|
||||
}
|
||||
JS_FreeValueRT(rt, frd->cb);
|
||||
list_del(&frd->weakref_header.link);
|
||||
js_free_rt(rt, frd);
|
||||
}
|
||||
}
|
||||
|
||||
static void js_finrec_mark(JSRuntime *rt, JSValueConst val,
|
||||
JS_MarkFunc *mark_func)
|
||||
{
|
||||
JSFinalizationRegistryData *frd = JS_GetOpaque(val, JS_CLASS_FINALIZATION_REGISTRY);
|
||||
struct list_head *el;
|
||||
if (frd) {
|
||||
list_for_each(el, &frd->entries) {
|
||||
JSFinRecEntry *fre = list_entry(el, JSFinRecEntry, link);
|
||||
JS_MarkValue(rt, fre->held_val, mark_func);
|
||||
}
|
||||
JS_MarkValue(rt, frd->cb, mark_func);
|
||||
}
|
||||
}
|
||||
|
||||
static JSValue js_finrec_job(JSContext *ctx, int argc, JSValueConst *argv)
|
||||
{
|
||||
return JS_Call(ctx, argv[0], JS_UNDEFINED, 1, &argv[1]);
|
||||
}
|
||||
|
||||
static void finrec_delete_weakref(JSRuntime *rt, JSWeakRefHeader *wh)
|
||||
{
|
||||
JSFinalizationRegistryData *frd = container_of(wh, JSFinalizationRegistryData, weakref_header);
|
||||
struct list_head *el, *el1;
|
||||
|
||||
list_for_each_safe(el, el1, &frd->entries) {
|
||||
JSFinRecEntry *fre = list_entry(el, JSFinRecEntry, link);
|
||||
|
||||
if (!js_weakref_is_live(fre->token)) {
|
||||
js_weakref_free(rt, fre->token);
|
||||
fre->token = JS_UNDEFINED;
|
||||
}
|
||||
|
||||
if (!js_weakref_is_live(fre->target)) {
|
||||
JSValueConst args[2];
|
||||
args[0] = frd->cb;
|
||||
args[1] = fre->held_val;
|
||||
JS_EnqueueJob(frd->ctx, js_finrec_job, 2, args);
|
||||
|
||||
js_weakref_free(rt, fre->target);
|
||||
js_weakref_free(rt, fre->token);
|
||||
JS_FreeValueRT(rt, fre->held_val);
|
||||
list_del(&fre->link);
|
||||
js_free_rt(rt, fre);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static JSValue js_finrec_constructor(JSContext *ctx, JSValueConst new_target,
|
||||
int argc, JSValueConst *argv)
|
||||
{
|
||||
JSValueConst cb;
|
||||
JSValue obj;
|
||||
JSFinalizationRegistryData *frd;
|
||||
|
||||
if (JS_IsUndefined(new_target))
|
||||
return JS_ThrowTypeError(ctx, "constructor requires 'new'");
|
||||
cb = argv[0];
|
||||
if (!JS_IsFunction(ctx, cb))
|
||||
return JS_ThrowTypeError(ctx, "argument must be a function");
|
||||
|
||||
obj = js_create_from_ctor(ctx, new_target, JS_CLASS_FINALIZATION_REGISTRY);
|
||||
if (JS_IsException(obj))
|
||||
return JS_EXCEPTION;
|
||||
frd = js_mallocz(ctx, sizeof(*frd));
|
||||
if (!frd) {
|
||||
JS_FreeValue(ctx, obj);
|
||||
return JS_EXCEPTION;
|
||||
}
|
||||
frd->weakref_header.weakref_type = JS_WEAKREF_TYPE_FINREC;
|
||||
list_add_tail(&frd->weakref_header.link, &ctx->rt->weakref_list);
|
||||
init_list_head(&frd->entries);
|
||||
frd->ctx = ctx; /* XXX: JS_DupContext() ? */
|
||||
frd->cb = JS_DupValue(ctx, cb);
|
||||
JS_SetOpaque(obj, frd);
|
||||
return obj;
|
||||
}
|
||||
|
||||
static JSValue js_finrec_register(JSContext *ctx, JSValueConst this_val,
|
||||
int argc, JSValueConst *argv)
|
||||
{
|
||||
JSValueConst target, held_val, token;
|
||||
JSFinalizationRegistryData *frd;
|
||||
JSFinRecEntry *fre;
|
||||
|
||||
frd = JS_GetOpaque2(ctx, this_val, JS_CLASS_FINALIZATION_REGISTRY);
|
||||
if (!frd)
|
||||
return JS_EXCEPTION;
|
||||
target = argv[0];
|
||||
held_val = argv[1];
|
||||
token = argc > 2 ? argv[2] : JS_UNDEFINED;
|
||||
|
||||
if (!js_weakref_is_target(target))
|
||||
return JS_ThrowTypeError(ctx, "invalid target");
|
||||
if (js_same_value(ctx, target, held_val))
|
||||
return JS_ThrowTypeError(ctx, "held value cannot be the target");
|
||||
if (!JS_IsUndefined(token) && !js_weakref_is_target(token))
|
||||
return JS_ThrowTypeError(ctx, "invalid unregister token");
|
||||
fre = js_malloc(ctx, sizeof(*fre));
|
||||
if (!fre)
|
||||
return JS_EXCEPTION;
|
||||
fre->target = js_weakref_new(ctx, target);
|
||||
fre->held_val = JS_DupValue(ctx, held_val);
|
||||
fre->token = js_weakref_new(ctx, token);
|
||||
list_add_tail(&fre->link, &frd->entries);
|
||||
return JS_UNDEFINED;
|
||||
}
|
||||
|
||||
static JSValue js_finrec_unregister(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
|
||||
{
|
||||
JSFinalizationRegistryData *frd = JS_GetOpaque2(ctx, this_val, JS_CLASS_FINALIZATION_REGISTRY);
|
||||
JSValueConst token;
|
||||
BOOL removed;
|
||||
struct list_head *el, *el1;
|
||||
|
||||
if (!frd)
|
||||
return JS_EXCEPTION;
|
||||
token = argv[0];
|
||||
if (!js_weakref_is_target(token))
|
||||
return JS_ThrowTypeError(ctx, "invalid unregister token");
|
||||
|
||||
removed = FALSE;
|
||||
list_for_each_safe(el, el1, &frd->entries) {
|
||||
JSFinRecEntry *fre = list_entry(el, JSFinRecEntry, link);
|
||||
if (js_same_value(ctx, fre->token, token)) {
|
||||
js_weakref_free(ctx->rt, fre->target);
|
||||
js_weakref_free(ctx->rt, fre->token);
|
||||
JS_FreeValue(ctx, fre->held_val);
|
||||
list_del(&fre->link);
|
||||
js_free(ctx, fre);
|
||||
removed = TRUE;
|
||||
}
|
||||
}
|
||||
return JS_NewBool(ctx, removed);
|
||||
}
|
||||
|
||||
static const JSCFunctionListEntry js_finrec_proto_funcs[] = {
|
||||
JS_CFUNC_DEF("register", 2, js_finrec_register ),
|
||||
JS_CFUNC_DEF("unregister", 1, js_finrec_unregister ),
|
||||
JS_PROP_STRING_DEF("[Symbol.toStringTag]", "FinalizationRegistry", JS_PROP_CONFIGURABLE ),
|
||||
};
|
||||
|
||||
static const JSClassShortDef js_finrec_class_def[] = {
|
||||
{ JS_ATOM_FinalizationRegistry, js_finrec_finalizer, js_finrec_mark }, /* JS_CLASS_FINALIZATION_REGISTRY */
|
||||
};
|
||||
|
||||
void JS_AddIntrinsicWeakRef(JSContext *ctx)
|
||||
{
|
||||
JSRuntime *rt = ctx->rt;
|
||||
|
||||
/* WeakRef */
|
||||
if (!JS_IsRegisteredClass(rt, JS_CLASS_WEAK_REF)) {
|
||||
init_class_range(rt, js_weakref_class_def, JS_CLASS_WEAK_REF,
|
||||
countof(js_weakref_class_def));
|
||||
}
|
||||
ctx->class_proto[JS_CLASS_WEAK_REF] = JS_NewObject(ctx);
|
||||
JS_SetPropertyFunctionList(ctx, ctx->class_proto[JS_CLASS_WEAK_REF],
|
||||
js_weakref_proto_funcs,
|
||||
countof(js_weakref_proto_funcs));
|
||||
JS_NewGlobalCConstructor(ctx, "WeakRef", js_weakref_constructor, 1, ctx->class_proto[JS_CLASS_WEAK_REF]);
|
||||
|
||||
/* FinalizationRegistry */
|
||||
if (!JS_IsRegisteredClass(rt, JS_CLASS_FINALIZATION_REGISTRY)) {
|
||||
init_class_range(rt, js_finrec_class_def, JS_CLASS_FINALIZATION_REGISTRY,
|
||||
countof(js_finrec_class_def));
|
||||
}
|
||||
ctx->class_proto[JS_CLASS_FINALIZATION_REGISTRY] = JS_NewObject(ctx);
|
||||
JS_SetPropertyFunctionList(ctx, ctx->class_proto[JS_CLASS_FINALIZATION_REGISTRY],
|
||||
js_finrec_proto_funcs,
|
||||
countof(js_finrec_proto_funcs));
|
||||
JS_NewGlobalCConstructor(ctx, "FinalizationRegistry", js_finrec_constructor, 1, ctx->class_proto[JS_CLASS_FINALIZATION_REGISTRY]);
|
||||
}
|
||||
|
@ -405,6 +405,7 @@ void JS_AddIntrinsicProxy(JSContext *ctx);
|
||||
void JS_AddIntrinsicMapSet(JSContext *ctx);
|
||||
void JS_AddIntrinsicTypedArrays(JSContext *ctx);
|
||||
void JS_AddIntrinsicPromise(JSContext *ctx);
|
||||
void JS_AddIntrinsicWeakRef(JSContext *ctx);
|
||||
|
||||
JSValue js_string_codePointRange(JSContext *ctx, JSValueConst this_val,
|
||||
int argc, JSValueConst *argv);
|
||||
|
@ -107,7 +107,7 @@ Error.isError=skip
|
||||
explicit-resource-management=skip
|
||||
exponentiation
|
||||
export-star-as-namespace-from-module
|
||||
FinalizationRegistry=skip
|
||||
FinalizationRegistry
|
||||
Float16Array=skip
|
||||
Float32Array
|
||||
Float64Array
|
||||
@ -217,7 +217,7 @@ Symbol.split
|
||||
Symbol.toPrimitive
|
||||
Symbol.toStringTag
|
||||
Symbol.unscopables
|
||||
symbols-as-weakmap-keys=skip
|
||||
symbols-as-weakmap-keys
|
||||
tail-call-optimization=skip
|
||||
template
|
||||
Temporal=skip
|
||||
@ -231,7 +231,7 @@ Uint8Array
|
||||
uint8array-base64=skip
|
||||
Uint8ClampedArray
|
||||
WeakMap
|
||||
WeakRef=skip
|
||||
WeakRef
|
||||
WeakSet
|
||||
well-formed-json-stringify
|
||||
|
||||
|
@ -789,22 +789,70 @@ function test_weak_map()
|
||||
tab = [];
|
||||
for(i = 0; i < n; i++) {
|
||||
v = { };
|
||||
o = { id: i };
|
||||
if (i & 1)
|
||||
o = Symbol("x" + i);
|
||||
else
|
||||
o = { id: i };
|
||||
tab[i] = [o, v];
|
||||
a.set(o, v);
|
||||
}
|
||||
o = null;
|
||||
|
||||
n2 = n >> 1;
|
||||
n2 = 5;
|
||||
for(i = 0; i < n2; i++) {
|
||||
a.delete(tab[i][0]);
|
||||
}
|
||||
for(i = n2; i < n; i++) {
|
||||
tab[i][0] = null; /* should remove the object from the WeakMap too */
|
||||
}
|
||||
std.gc();
|
||||
/* the WeakMap should be empty here */
|
||||
}
|
||||
|
||||
function test_weak_ref()
|
||||
{
|
||||
var w1, w2, o, i;
|
||||
|
||||
for(i = 0; i < 2; i++) {
|
||||
if (i == 0)
|
||||
o = { };
|
||||
else
|
||||
o = Symbol("x");
|
||||
w1 = new WeakRef(o);
|
||||
assert(w1.deref(), o);
|
||||
w2 = new WeakRef(o);
|
||||
assert(w2.deref(), o);
|
||||
|
||||
o = null;
|
||||
std.gc();
|
||||
assert(w1.deref(), undefined);
|
||||
assert(w2.deref(), undefined);
|
||||
}
|
||||
}
|
||||
|
||||
function test_finalization_registry()
|
||||
{
|
||||
{
|
||||
let expected = {};
|
||||
let actual;
|
||||
let finrec = new FinalizationRegistry(v => { actual = v });
|
||||
finrec.register({}, expected);
|
||||
os.setTimeout(() => {
|
||||
assert(actual, expected);
|
||||
}, 0);
|
||||
}
|
||||
{
|
||||
let expected = 42;
|
||||
let actual;
|
||||
let finrec = new FinalizationRegistry(v => { actual = v });
|
||||
finrec.register({}, expected);
|
||||
os.setTimeout(() => {
|
||||
assert(actual, expected);
|
||||
}, 0);
|
||||
}
|
||||
std.gc();
|
||||
}
|
||||
|
||||
function test_generator()
|
||||
{
|
||||
function *f() {
|
||||
@ -905,5 +953,7 @@ test_regexp();
|
||||
test_symbol();
|
||||
test_map();
|
||||
test_weak_map();
|
||||
test_weak_ref();
|
||||
test_finalization_registry();
|
||||
test_generator();
|
||||
test_rope();
|
||||
|
Loading…
x
Reference in New Issue
Block a user