mirror of
https://github.com/bellard/quickjs.git
synced 2024-11-26 15:48:11 +08:00
Add WeakRef implementation
1. Refactor JSObject weak reference list to support different kind of weak reference 2. Support Symbol as WeakMap key
This commit is contained in:
parent
012451d5f3
commit
2ecaf6cf97
@ -281,9 +281,7 @@ 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 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 FinalizationRegistry objects
|
||||||
|
|
||||||
@item Symbols as WeakMap keys
|
|
||||||
|
|
||||||
@end itemize
|
@end itemize
|
||||||
|
|
||||||
|
1
qjsc.c
1
qjsc.c
@ -77,6 +77,7 @@ static const FeatureEntry feature_list[] = {
|
|||||||
#define FE_MODULE_LOADER 9
|
#define FE_MODULE_LOADER 9
|
||||||
{ "module-loader", NULL },
|
{ "module-loader", NULL },
|
||||||
{ "bigint", "BigInt" },
|
{ "bigint", "BigInt" },
|
||||||
|
{ "weakref", "WeakRef" },
|
||||||
};
|
};
|
||||||
|
|
||||||
void namelist_add(namelist_t *lp, const char *name, const char *short_name,
|
void namelist_add(namelist_t *lp, const char *name, const char *short_name,
|
||||||
|
@ -269,5 +269,6 @@ DEF(Symbol_asyncIterator, "Symbol.asyncIterator")
|
|||||||
#ifdef CONFIG_BIGNUM
|
#ifdef CONFIG_BIGNUM
|
||||||
DEF(Symbol_operatorSet, "Symbol.operatorSet")
|
DEF(Symbol_operatorSet, "Symbol.operatorSet")
|
||||||
#endif
|
#endif
|
||||||
|
DEF(WeakRef, "WeakRef")
|
||||||
|
|
||||||
#endif /* DEF */
|
#endif /* DEF */
|
||||||
|
352
quickjs.c
352
quickjs.c
@ -176,6 +176,7 @@ enum {
|
|||||||
JS_CLASS_ASYNC_FROM_SYNC_ITERATOR, /* u.async_from_sync_iterator_data */
|
JS_CLASS_ASYNC_FROM_SYNC_ITERATOR, /* u.async_from_sync_iterator_data */
|
||||||
JS_CLASS_ASYNC_GENERATOR_FUNCTION, /* u.func */
|
JS_CLASS_ASYNC_GENERATOR_FUNCTION, /* u.func */
|
||||||
JS_CLASS_ASYNC_GENERATOR, /* u.async_generator_data */
|
JS_CLASS_ASYNC_GENERATOR, /* u.async_generator_data */
|
||||||
|
JS_CLASS_WEAKREF, /* u.weakref */
|
||||||
|
|
||||||
JS_CLASS_INIT_COUNT, /* last entry for predefined classes */
|
JS_CLASS_INIT_COUNT, /* last entry for predefined classes */
|
||||||
};
|
};
|
||||||
@ -494,6 +495,7 @@ struct JSString {
|
|||||||
uint32_t hash : 30;
|
uint32_t hash : 30;
|
||||||
uint8_t atom_type : 2; /* != 0 if atom, JS_ATOM_TYPE_x */
|
uint8_t atom_type : 2; /* != 0 if atom, JS_ATOM_TYPE_x */
|
||||||
uint32_t hash_next; /* atom_index for JS_ATOM_TYPE_SYMBOL */
|
uint32_t hash_next; /* atom_index for JS_ATOM_TYPE_SYMBOL */
|
||||||
|
struct list_head *weak_ref_list; /* list of JSWeakRecord */ /* XXX: use a bit and an external hash table? */
|
||||||
#ifdef DUMP_LEAKS
|
#ifdef DUMP_LEAKS
|
||||||
struct list_head link; /* string list */
|
struct list_head link; /* string list */
|
||||||
#endif
|
#endif
|
||||||
@ -883,6 +885,30 @@ struct JSShape {
|
|||||||
JSShapeProperty prop[0]; /* prop_size elements */
|
JSShapeProperty prop[0]; /* prop_size elements */
|
||||||
};
|
};
|
||||||
|
|
||||||
|
typedef struct JSWeakRecord {
|
||||||
|
/* operations for given type of weak record, this actually act as a vtable */
|
||||||
|
const struct JSWeakRecordOperations *operations;
|
||||||
|
|
||||||
|
struct list_head list;
|
||||||
|
} JSWeakRecord;
|
||||||
|
|
||||||
|
typedef struct JSWeakRecordOperations {
|
||||||
|
/* first pass to remove the records from the weak_ref_list
|
||||||
|
note: implementation CAN NOT touch js context here, eg. new/free js object
|
||||||
|
*/
|
||||||
|
void (*reset_weak_ref_first_pass)(JSRuntime *rt, struct JSWeakRecord *wr);
|
||||||
|
/* second pass to free the values to avoid modifying the weak
|
||||||
|
reference list while traversing it.
|
||||||
|
note: implementation is responsible to free node in here. */
|
||||||
|
void (*reset_weak_ref_second_pass)(JSRuntime *rt, struct JSWeakRecord *wr);
|
||||||
|
} JSWeakRecordOperations;
|
||||||
|
|
||||||
|
typedef struct JSWeakRef {
|
||||||
|
JSWeakRecord record;
|
||||||
|
JSObject *this_obj;
|
||||||
|
JSValueConst target;
|
||||||
|
} JSWeakRef;
|
||||||
|
|
||||||
struct JSObject {
|
struct JSObject {
|
||||||
union {
|
union {
|
||||||
JSGCObjectHeader header;
|
JSGCObjectHeader header;
|
||||||
@ -905,7 +931,7 @@ struct JSObject {
|
|||||||
JSShape *shape; /* prototype and property names + flag */
|
JSShape *shape; /* prototype and property names + flag */
|
||||||
JSProperty *prop; /* array of properties */
|
JSProperty *prop; /* array of properties */
|
||||||
/* byte offsets: 24/40 */
|
/* byte offsets: 24/40 */
|
||||||
struct JSMapRecord *first_weak_ref; /* XXX: use a bit and an external hash table? */
|
struct list_head *weak_ref_list; /* list of JSWeakRecord */ /* XXX: use a bit and an external hash table? */
|
||||||
/* byte offsets: 28/48 */
|
/* byte offsets: 28/48 */
|
||||||
union {
|
union {
|
||||||
void *opaque;
|
void *opaque;
|
||||||
@ -929,6 +955,7 @@ struct JSObject {
|
|||||||
struct JSAsyncFunctionState *async_function_data; /* JS_CLASS_ASYNC_FUNCTION_RESOLVE, JS_CLASS_ASYNC_FUNCTION_REJECT */
|
struct JSAsyncFunctionState *async_function_data; /* JS_CLASS_ASYNC_FUNCTION_RESOLVE, JS_CLASS_ASYNC_FUNCTION_REJECT */
|
||||||
struct JSAsyncFromSyncIteratorData *async_from_sync_iterator_data; /* JS_CLASS_ASYNC_FROM_SYNC_ITERATOR */
|
struct JSAsyncFromSyncIteratorData *async_from_sync_iterator_data; /* JS_CLASS_ASYNC_FROM_SYNC_ITERATOR */
|
||||||
struct JSAsyncGeneratorData *async_generator_data; /* JS_CLASS_ASYNC_GENERATOR */
|
struct JSAsyncGeneratorData *async_generator_data; /* JS_CLASS_ASYNC_GENERATOR */
|
||||||
|
struct JSWeakRef *weakref; /* JS_CLASS_WEAKREF */
|
||||||
struct { /* JS_CLASS_BYTECODE_FUNCTION: 12/24 bytes */
|
struct { /* JS_CLASS_BYTECODE_FUNCTION: 12/24 bytes */
|
||||||
/* also used by JS_CLASS_GENERATOR_FUNCTION, JS_CLASS_ASYNC_FUNCTION and JS_CLASS_ASYNC_GENERATOR_FUNCTION */
|
/* also used by JS_CLASS_GENERATOR_FUNCTION, JS_CLASS_ASYNC_FUNCTION and JS_CLASS_ASYNC_GENERATOR_FUNCTION */
|
||||||
struct JSFunctionBytecode *function_bytecode;
|
struct JSFunctionBytecode *function_bytecode;
|
||||||
@ -1184,7 +1211,6 @@ static int JS_CreateProperty(JSContext *ctx, JSObject *p,
|
|||||||
JSValueConst getter, JSValueConst setter,
|
JSValueConst getter, JSValueConst setter,
|
||||||
int flags);
|
int flags);
|
||||||
static int js_string_memcmp(const JSString *p1, const JSString *p2, int len);
|
static int js_string_memcmp(const JSString *p1, const JSString *p2, int len);
|
||||||
static void reset_weak_ref(JSRuntime *rt, JSObject *p);
|
|
||||||
static JSValue js_array_buffer_constructor3(JSContext *ctx,
|
static JSValue js_array_buffer_constructor3(JSContext *ctx,
|
||||||
JSValueConst new_target,
|
JSValueConst new_target,
|
||||||
uint64_t len, JSClassID class_id,
|
uint64_t len, JSClassID class_id,
|
||||||
@ -1279,6 +1305,21 @@ static JSValue JS_InstantiateFunctionListItem2(JSContext *ctx, JSObject *p,
|
|||||||
JSAtom atom, void *opaque);
|
JSAtom atom, void *opaque);
|
||||||
static JSValue js_object_groupBy(JSContext *ctx, JSValueConst this_val,
|
static JSValue js_object_groupBy(JSContext *ctx, JSValueConst this_val,
|
||||||
int argc, JSValueConst *argv, int is_map);
|
int argc, JSValueConst *argv, int is_map);
|
||||||
|
static void js_init_weak_record(struct JSWeakRecord *wr, const struct JSWeakRecordOperations* operations);
|
||||||
|
struct list_head **js_get_target_weak_ref_list(JSContext *ctx, JSValueConst target);
|
||||||
|
static BOOL js_add_weak_ref(JSContext *ctx, struct list_head **weak_ref_list, JSWeakRecord *wr);
|
||||||
|
static void js_unlink_weak_ref(JSWeakRecord *wr);
|
||||||
|
static void js_reset_weak_ref(JSRuntime *rt, struct list_head **weak_ref_list);
|
||||||
|
static inline void js_object_reset_weak_ref(JSRuntime *rt, JSObject *p)
|
||||||
|
{
|
||||||
|
assert(p->weak_ref_list);
|
||||||
|
js_reset_weak_ref(rt, &p->weak_ref_list);
|
||||||
|
}
|
||||||
|
static inline void js_atom_reset_weak_ref(JSRuntime *rt, JSAtomStruct *p)
|
||||||
|
{
|
||||||
|
assert(p->weak_ref_list);
|
||||||
|
js_reset_weak_ref(rt, &p->weak_ref_list);
|
||||||
|
}
|
||||||
|
|
||||||
static const JSClassExoticMethods js_arguments_exotic_methods;
|
static const JSClassExoticMethods js_arguments_exotic_methods;
|
||||||
static const JSClassExoticMethods js_string_exotic_methods;
|
static const JSClassExoticMethods js_string_exotic_methods;
|
||||||
@ -1901,6 +1942,7 @@ static JSString *js_alloc_string_rt(JSRuntime *rt, int max_len, int is_wide_char
|
|||||||
str->atom_type = 0;
|
str->atom_type = 0;
|
||||||
str->hash = 0; /* optional but costless */
|
str->hash = 0; /* optional but costless */
|
||||||
str->hash_next = 0; /* optional */
|
str->hash_next = 0; /* optional */
|
||||||
|
str->weak_ref_list = NULL;
|
||||||
#ifdef DUMP_LEAKS
|
#ifdef DUMP_LEAKS
|
||||||
list_add_tail(&str->link, &rt->string_list);
|
list_add_tail(&str->link, &rt->string_list);
|
||||||
#endif
|
#endif
|
||||||
@ -2070,6 +2112,10 @@ void JS_FreeRuntime(JSRuntime *rt)
|
|||||||
for(i = 0; i < rt->atom_size; i++) {
|
for(i = 0; i < rt->atom_size; i++) {
|
||||||
JSAtomStruct *p = rt->atom_array[i];
|
JSAtomStruct *p = rt->atom_array[i];
|
||||||
if (!atom_is_free(p)) {
|
if (!atom_is_free(p)) {
|
||||||
|
/* reset weak reference */
|
||||||
|
if (p->weak_ref_list) {
|
||||||
|
js_atom_reset_weak_ref(rt, p);
|
||||||
|
}
|
||||||
#ifdef DUMP_LEAKS
|
#ifdef DUMP_LEAKS
|
||||||
list_del(&p->link);
|
list_del(&p->link);
|
||||||
#endif
|
#endif
|
||||||
@ -2179,6 +2225,7 @@ JSContext *JS_NewContext(JSRuntime *rt)
|
|||||||
JS_AddIntrinsicTypedArrays(ctx);
|
JS_AddIntrinsicTypedArrays(ctx);
|
||||||
JS_AddIntrinsicPromise(ctx);
|
JS_AddIntrinsicPromise(ctx);
|
||||||
JS_AddIntrinsicBigInt(ctx);
|
JS_AddIntrinsicBigInt(ctx);
|
||||||
|
JS_AddIntrinsicWeakRef(ctx);
|
||||||
return ctx;
|
return ctx;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2769,6 +2816,7 @@ static JSAtom __JS_NewAtom(JSRuntime *rt, JSString *str, int atom_type)
|
|||||||
}
|
}
|
||||||
p->header.ref_count = 1; /* not refcounted */
|
p->header.ref_count = 1; /* not refcounted */
|
||||||
p->atom_type = JS_ATOM_TYPE_SYMBOL;
|
p->atom_type = JS_ATOM_TYPE_SYMBOL;
|
||||||
|
p->weak_ref_list = NULL;
|
||||||
#ifdef DUMP_LEAKS
|
#ifdef DUMP_LEAKS
|
||||||
list_add_tail(&p->link, &rt->string_list);
|
list_add_tail(&p->link, &rt->string_list);
|
||||||
#endif
|
#endif
|
||||||
@ -2802,6 +2850,7 @@ static JSAtom __JS_NewAtom(JSRuntime *rt, JSString *str, int atom_type)
|
|||||||
p->header.ref_count = 1;
|
p->header.ref_count = 1;
|
||||||
p->is_wide_char = str->is_wide_char;
|
p->is_wide_char = str->is_wide_char;
|
||||||
p->len = str->len;
|
p->len = str->len;
|
||||||
|
p->weak_ref_list = NULL;
|
||||||
#ifdef DUMP_LEAKS
|
#ifdef DUMP_LEAKS
|
||||||
list_add_tail(&p->link, &rt->string_list);
|
list_add_tail(&p->link, &rt->string_list);
|
||||||
#endif
|
#endif
|
||||||
@ -2816,6 +2865,7 @@ static JSAtom __JS_NewAtom(JSRuntime *rt, JSString *str, int atom_type)
|
|||||||
p->header.ref_count = 1;
|
p->header.ref_count = 1;
|
||||||
p->is_wide_char = 1; /* Hack to represent NULL as a JSString */
|
p->is_wide_char = 1; /* Hack to represent NULL as a JSString */
|
||||||
p->len = 0;
|
p->len = 0;
|
||||||
|
p->weak_ref_list = NULL;
|
||||||
#ifdef DUMP_LEAKS
|
#ifdef DUMP_LEAKS
|
||||||
list_add_tail(&p->link, &rt->string_list);
|
list_add_tail(&p->link, &rt->string_list);
|
||||||
#endif
|
#endif
|
||||||
@ -2924,6 +2974,12 @@ static void JS_FreeAtomStruct(JSRuntime *rt, JSAtomStruct *p)
|
|||||||
/* insert in free atom list */
|
/* insert in free atom list */
|
||||||
rt->atom_array[i] = atom_set_free(rt->atom_free_index);
|
rt->atom_array[i] = atom_set_free(rt->atom_free_index);
|
||||||
rt->atom_free_index = i;
|
rt->atom_free_index = i;
|
||||||
|
|
||||||
|
/* release WeakRecord referencing to p */
|
||||||
|
if (unlikely(p->weak_ref_list)) {
|
||||||
|
js_atom_reset_weak_ref(rt, p);
|
||||||
|
}
|
||||||
|
|
||||||
/* free the string structure */
|
/* free the string structure */
|
||||||
#ifdef DUMP_LEAKS
|
#ifdef DUMP_LEAKS
|
||||||
list_del(&p->link);
|
list_del(&p->link);
|
||||||
@ -4771,7 +4827,7 @@ static JSValue JS_NewObjectFromShape(JSContext *ctx, JSShape *sh, JSClassID clas
|
|||||||
p->is_uncatchable_error = 0;
|
p->is_uncatchable_error = 0;
|
||||||
p->tmp_mark = 0;
|
p->tmp_mark = 0;
|
||||||
p->is_HTMLDDA = 0;
|
p->is_HTMLDDA = 0;
|
||||||
p->first_weak_ref = NULL;
|
p->weak_ref_list = NULL;
|
||||||
p->u.opaque = NULL;
|
p->u.opaque = NULL;
|
||||||
p->shape = sh;
|
p->shape = sh;
|
||||||
p->prop = js_malloc(ctx, sizeof(JSProperty) * sh->prop_size);
|
p->prop = js_malloc(ctx, sizeof(JSProperty) * sh->prop_size);
|
||||||
@ -5459,8 +5515,9 @@ static void free_object(JSRuntime *rt, JSObject *p)
|
|||||||
p->shape = NULL;
|
p->shape = NULL;
|
||||||
p->prop = NULL;
|
p->prop = NULL;
|
||||||
|
|
||||||
if (unlikely(p->first_weak_ref)) {
|
/* release WeakRecord referencing to p */
|
||||||
reset_weak_ref(rt, p);
|
if (unlikely(p->weak_ref_list)) {
|
||||||
|
js_object_reset_weak_ref(rt, p);
|
||||||
}
|
}
|
||||||
|
|
||||||
finalizer = rt->class_array[p->class_id].finalizer;
|
finalizer = rt->class_array[p->class_id].finalizer;
|
||||||
@ -46971,13 +47028,229 @@ static const JSCFunctionListEntry js_symbol_funcs[] = {
|
|||||||
JS_CFUNC_DEF("keyFor", 1, js_symbol_keyFor ),
|
JS_CFUNC_DEF("keyFor", 1, js_symbol_keyFor ),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/* JSObject/Atom weak support */
|
||||||
|
static void js_init_weak_record(struct JSWeakRecord *wr, const struct JSWeakRecordOperations* operations)
|
||||||
|
{
|
||||||
|
assert(operations);
|
||||||
|
init_list_head(&wr->list);
|
||||||
|
wr->operations = operations;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* find the weak_ref_list of a Object or Symbol/Atom,
|
||||||
|
on failure: return NULL and throw exception */
|
||||||
|
struct list_head **js_get_target_weak_ref_list(JSContext *ctx, JSValueConst target)
|
||||||
|
{
|
||||||
|
/* the spec says: Thrown if target is not an object or a non-registered symbol. */
|
||||||
|
if (JS_IsSymbol(target)) {
|
||||||
|
JSAtomStruct *p = JS_VALUE_GET_PTR(target);
|
||||||
|
if (p->atom_type == JS_ATOM_TYPE_GLOBAL_SYMBOL) {
|
||||||
|
JS_ThrowTypeError(ctx, "registered symbol is not allowed");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
return &p->weak_ref_list;
|
||||||
|
} else if (JS_IsObject(target)) {
|
||||||
|
JSObject *p = JS_VALUE_GET_OBJ(target);
|
||||||
|
return &p->weak_ref_list;
|
||||||
|
} else {
|
||||||
|
JS_ThrowTypeError(ctx, "not a valid target");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Add weak record to object's weak reference list, on failure return FALSE and throw exception */
|
||||||
|
static BOOL js_add_weak_ref(JSContext *ctx, struct list_head **weak_ref_list, JSWeakRecord *wr)
|
||||||
|
{
|
||||||
|
if(likely(!*weak_ref_list)) {
|
||||||
|
struct list_head *list = js_malloc(ctx, sizeof(*list));
|
||||||
|
if (unlikely(!list)) {
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
init_list_head(list);
|
||||||
|
*weak_ref_list = list;
|
||||||
|
}
|
||||||
|
list_add(&wr->list, *weak_ref_list);
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* unlink the weak reference from the object weak reference list
|
||||||
|
eg. JSObject.u.weak_ref_list or JSString.weak_ref_list */
|
||||||
|
static void js_unlink_weak_ref(JSWeakRecord *wr)
|
||||||
|
{
|
||||||
|
list_del(&wr->list);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* reset object weak reference record, and do clean up */
|
||||||
|
static void js_reset_weak_ref(JSRuntime *rt, struct list_head **weak_ref_list)
|
||||||
|
{
|
||||||
|
struct list_head *list = *weak_ref_list;
|
||||||
|
struct list_head *el, *el1;
|
||||||
|
|
||||||
|
/* first pass to remove the records from the Weak... lists */
|
||||||
|
list_for_each(el, list) {
|
||||||
|
JSWeakRecord *wr = list_entry(el, JSWeakRecord, list);
|
||||||
|
assert(wr->operations);
|
||||||
|
wr->operations->reset_weak_ref_first_pass(rt, wr);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* second pass to free the values to avoid modifying the weak
|
||||||
|
reference list while traversing it. */
|
||||||
|
list_for_each_safe(el, el1, list) {
|
||||||
|
JSWeakRecord *wr = list_entry(el, JSWeakRecord, list);
|
||||||
|
list_del(el);
|
||||||
|
|
||||||
|
assert(wr->operations);
|
||||||
|
wr->operations->reset_weak_ref_second_pass(rt, wr);
|
||||||
|
}
|
||||||
|
|
||||||
|
js_free_rt(rt, list);
|
||||||
|
*weak_ref_list = NULL; /* fail safe */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* WeakRef support */
|
||||||
|
static void js_weakref_reset_weak_ref_first_pass(JSRuntime *rt, JSWeakRecord *wr)
|
||||||
|
{
|
||||||
|
JSWeakRef *ref = container_of(wr, JSWeakRef, record);
|
||||||
|
JSObject *this_obj = ref->this_obj;
|
||||||
|
|
||||||
|
assert(this_obj->u.weakref == ref);
|
||||||
|
assert(!JS_IsUndefined(ref->target));
|
||||||
|
|
||||||
|
/* target object deleted, reset weakref */
|
||||||
|
this_obj->u.weakref = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void js_weakref_reset_weak_ref_second_pass(JSRuntime *rt, JSWeakRecord *wr)
|
||||||
|
{
|
||||||
|
/* delete record */
|
||||||
|
JSWeakRef *ref = container_of(wr, JSWeakRef, record);
|
||||||
|
js_free_rt(rt, ref);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct JSWeakRecordOperations js_weak_ref_operations = {
|
||||||
|
js_weakref_reset_weak_ref_first_pass,
|
||||||
|
js_weakref_reset_weak_ref_second_pass
|
||||||
|
};
|
||||||
|
|
||||||
|
static JSValue js_new_weak_ref_internal(JSContext *ctx, JSValueConst ctor, JSValueConst target)
|
||||||
|
{
|
||||||
|
JSValue weak_ref = JS_UNDEFINED;
|
||||||
|
struct list_head **weak_ref_list = NULL;
|
||||||
|
|
||||||
|
weak_ref_list = js_get_target_weak_ref_list(ctx, target);
|
||||||
|
if (!weak_ref_list) {
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
weak_ref = js_create_from_ctor(ctx, ctor, JS_CLASS_WEAKREF);
|
||||||
|
if (!JS_IsException(weak_ref)) {
|
||||||
|
JSObject *obj = JS_VALUE_GET_OBJ(weak_ref);
|
||||||
|
JSWeakRef *ref = js_malloc(ctx, sizeof(*ref));
|
||||||
|
if (!ref) {
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
js_init_weak_record(&ref->record, &js_weak_ref_operations);
|
||||||
|
ref->this_obj = obj;
|
||||||
|
ref->target = target;
|
||||||
|
obj->u.weakref = ref;
|
||||||
|
|
||||||
|
if (!js_add_weak_ref(ctx, weak_ref_list, &ref->record)) {
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return weak_ref;
|
||||||
|
|
||||||
|
fail:
|
||||||
|
JS_FreeValue(ctx, weak_ref);
|
||||||
|
return JS_EXCEPTION;
|
||||||
|
}
|
||||||
|
|
||||||
|
JSValue JS_NewWeakRef(JSContext *ctx, JSValueConst target)
|
||||||
|
{
|
||||||
|
if (unlikely(!JS_IsRegisteredClass(ctx->rt, JS_CLASS_WEAKREF))) {
|
||||||
|
return JS_ThrowInternalError(ctx, "WeakRef intrinsic is not added");
|
||||||
|
}
|
||||||
|
return js_new_weak_ref_internal(ctx, JS_UNDEFINED, target);
|
||||||
|
}
|
||||||
|
|
||||||
|
JSValue JS_DerefWeakRef(JSContext *ctx, JSValueConst weakref)
|
||||||
|
{
|
||||||
|
JSObject *p;
|
||||||
|
if (!JS_IsObject(weakref)) {
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
p = JS_VALUE_GET_OBJ(weakref);
|
||||||
|
if (p->class_id != JS_CLASS_WEAKREF) {
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (p->u.weakref) {
|
||||||
|
JSValueConst target = p->u.weakref->target;
|
||||||
|
return JS_DupValue(ctx, target);
|
||||||
|
}
|
||||||
|
|
||||||
|
return JS_UNDEFINED;
|
||||||
|
fail:
|
||||||
|
return JS_ThrowTypeError(ctx, "not a WeakRef");
|
||||||
|
}
|
||||||
|
|
||||||
|
static JSValue js_weakref_constructor(JSContext *ctx, JSValueConst new_target,
|
||||||
|
int argc, JSValueConst *argv)
|
||||||
|
{
|
||||||
|
return js_new_weak_ref_internal(ctx, new_target, argv[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
static JSValue js_weakref_deref(JSContext *ctx, JSValueConst this_val,
|
||||||
|
int argc, JSValueConst *argv)
|
||||||
|
{
|
||||||
|
return JS_DerefWeakRef(ctx, this_val);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void js_weakref_finalizer(JSRuntime *rt, JSValue val)
|
||||||
|
{
|
||||||
|
JSObject *p = JS_VALUE_GET_OBJ(val);
|
||||||
|
JSWeakRef *ref = p->u.weakref;
|
||||||
|
if (ref) {
|
||||||
|
JSWeakRecord *record = &ref->record;
|
||||||
|
js_unlink_weak_ref(record);
|
||||||
|
js_free_rt(rt, ref);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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_weak_class_def[] = {
|
||||||
|
{ JS_ATOM_WeakRef, js_weakref_finalizer, NULL },
|
||||||
|
};
|
||||||
|
|
||||||
|
void JS_AddIntrinsicWeakRef(JSContext *ctx)
|
||||||
|
{
|
||||||
|
JSValue proto;
|
||||||
|
|
||||||
|
if (!JS_IsRegisteredClass(ctx->rt, JS_CLASS_WEAKREF)) {
|
||||||
|
init_class_range(ctx->rt, js_weak_class_def, JS_CLASS_WEAKREF,
|
||||||
|
countof(js_weak_class_def));
|
||||||
|
}
|
||||||
|
|
||||||
|
proto = JS_NewObject(ctx);
|
||||||
|
ctx->class_proto[JS_CLASS_WEAKREF] = proto;
|
||||||
|
JS_SetPropertyFunctionList(ctx, proto,
|
||||||
|
js_weakref_proto_funcs,
|
||||||
|
countof(js_weakref_proto_funcs));
|
||||||
|
JS_NewGlobalCConstructorOnly(ctx, "WeakRef",
|
||||||
|
js_weakref_constructor, 1,
|
||||||
|
proto);
|
||||||
|
}
|
||||||
|
|
||||||
/* Set/Map/WeakSet/WeakMap */
|
/* Set/Map/WeakSet/WeakMap */
|
||||||
|
|
||||||
typedef struct JSMapRecord {
|
typedef struct JSMapRecord {
|
||||||
int ref_count; /* used during enumeration to avoid freeing the record */
|
int ref_count; /* used during enumeration to avoid freeing the record */
|
||||||
BOOL empty; /* TRUE if the record is deleted */
|
BOOL empty; /* TRUE if the record is deleted */
|
||||||
struct JSMapState *map;
|
struct JSMapState *map;
|
||||||
struct JSMapRecord *next_weak_ref;
|
JSWeakRecord weak_record; /* managed by JSObject.weak_ref_list */
|
||||||
struct list_head link;
|
struct list_head link;
|
||||||
struct list_head hash_link;
|
struct list_head hash_link;
|
||||||
JSValue key;
|
JSValue key;
|
||||||
@ -47205,6 +47478,14 @@ static void map_hash_resize(JSContext *ctx, JSMapState *s)
|
|||||||
s->record_count_threshold = new_hash_size * 2;
|
s->record_count_threshold = new_hash_size * 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void js_map_reset_weak_ref_first_pass(JSRuntime *rt, JSWeakRecord *wr);
|
||||||
|
static void js_map_reset_weak_ref_second_pass(JSRuntime *rt, JSWeakRecord *wr);
|
||||||
|
|
||||||
|
static const JSWeakRecordOperations js_map_weak_operations = {
|
||||||
|
js_map_reset_weak_ref_first_pass,
|
||||||
|
js_map_reset_weak_ref_second_pass,
|
||||||
|
};
|
||||||
|
|
||||||
static JSMapRecord *map_add_record(JSContext *ctx, JSMapState *s,
|
static JSMapRecord *map_add_record(JSContext *ctx, JSMapState *s,
|
||||||
JSValueConst key)
|
JSValueConst key)
|
||||||
{
|
{
|
||||||
@ -47218,10 +47499,13 @@ static JSMapRecord *map_add_record(JSContext *ctx, JSMapState *s,
|
|||||||
mr->map = s;
|
mr->map = s;
|
||||||
mr->empty = FALSE;
|
mr->empty = FALSE;
|
||||||
if (s->is_weak) {
|
if (s->is_weak) {
|
||||||
JSObject *p = JS_VALUE_GET_OBJ(key);
|
struct list_head **weak_ref_list = js_get_target_weak_ref_list(ctx, key);
|
||||||
|
js_init_weak_record(&mr->weak_record, &js_map_weak_operations);
|
||||||
/* Add the weak reference */
|
/* Add the weak reference */
|
||||||
mr->next_weak_ref = p->first_weak_ref;
|
if (!weak_ref_list || !js_add_weak_ref(ctx, weak_ref_list, &mr->weak_record)) {
|
||||||
p->first_weak_ref = mr;
|
js_free(ctx, mr);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
JS_DupValue(ctx, key);
|
JS_DupValue(ctx, key);
|
||||||
}
|
}
|
||||||
@ -47236,34 +47520,13 @@ static JSMapRecord *map_add_record(JSContext *ctx, JSMapState *s,
|
|||||||
return mr;
|
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void map_delete_record(JSRuntime *rt, JSMapState *s, JSMapRecord *mr)
|
static void map_delete_record(JSRuntime *rt, JSMapState *s, JSMapRecord *mr)
|
||||||
{
|
{
|
||||||
if (mr->empty)
|
if (mr->empty)
|
||||||
return;
|
return;
|
||||||
list_del(&mr->hash_link);
|
list_del(&mr->hash_link);
|
||||||
if (s->is_weak) {
|
if (s->is_weak) {
|
||||||
delete_weak_ref(rt, mr);
|
js_unlink_weak_ref(&mr->weak_record);
|
||||||
} else {
|
} else {
|
||||||
JS_FreeValueRT(rt, mr->key);
|
JS_FreeValueRT(rt, mr->key);
|
||||||
}
|
}
|
||||||
@ -47290,30 +47553,21 @@ static void map_decref_record(JSRuntime *rt, JSMapRecord *mr)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void reset_weak_ref(JSRuntime *rt, JSObject *p)
|
static void js_map_reset_weak_ref_first_pass(JSRuntime *rt, JSWeakRecord *wr)
|
||||||
{
|
{
|
||||||
JSMapRecord *mr, *mr_next;
|
JSMapRecord *mr = container_of(wr, JSMapRecord, weak_record);
|
||||||
JSMapState *s;
|
JSMapState *s = mr->map;
|
||||||
|
|
||||||
/* first pass to remove the records from the WeakMap/WeakSet
|
|
||||||
lists */
|
|
||||||
for(mr = p->first_weak_ref; mr != NULL; mr = mr->next_weak_ref) {
|
|
||||||
s = mr->map;
|
|
||||||
assert(s->is_weak);
|
assert(s->is_weak);
|
||||||
assert(!mr->empty); /* no iterator on WeakMap/WeakSet */
|
assert(!mr->empty); /* no iterator on WeakMap/WeakSet */
|
||||||
list_del(&mr->hash_link);
|
list_del(&mr->hash_link);
|
||||||
list_del(&mr->link);
|
list_del(&mr->link);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* second pass to free the values to avoid modifying the weak
|
static void js_map_reset_weak_ref_second_pass(JSRuntime *rt, JSWeakRecord *wr)
|
||||||
reference list while traversing it. */
|
{
|
||||||
for(mr = p->first_weak_ref; mr != NULL; mr = mr_next) {
|
JSMapRecord *mr = container_of(wr, JSMapRecord, weak_record);
|
||||||
mr_next = mr->next_weak_ref;
|
|
||||||
JS_FreeValueRT(rt, mr->value);
|
JS_FreeValueRT(rt, mr->value);
|
||||||
js_free_rt(rt, mr);
|
js_free_rt(rt, mr);
|
||||||
}
|
|
||||||
|
|
||||||
p->first_weak_ref = NULL; /* fail safe */
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static JSValue js_map_set(JSContext *ctx, JSValueConst this_val,
|
static JSValue js_map_set(JSContext *ctx, JSValueConst this_val,
|
||||||
@ -47326,8 +47580,6 @@ static JSValue js_map_set(JSContext *ctx, JSValueConst this_val,
|
|||||||
if (!s)
|
if (!s)
|
||||||
return JS_EXCEPTION;
|
return JS_EXCEPTION;
|
||||||
key = map_normalize_key(ctx, argv[0]);
|
key = map_normalize_key(ctx, argv[0]);
|
||||||
if (s->is_weak && !JS_IsObject(key))
|
|
||||||
return JS_ThrowTypeErrorNotAnObject(ctx);
|
|
||||||
if (magic & MAGIC_SET)
|
if (magic & MAGIC_SET)
|
||||||
value = JS_UNDEFINED;
|
value = JS_UNDEFINED;
|
||||||
else
|
else
|
||||||
@ -47597,7 +47849,7 @@ static void js_map_finalizer(JSRuntime *rt, JSValue val)
|
|||||||
mr = list_entry(el, JSMapRecord, link);
|
mr = list_entry(el, JSMapRecord, link);
|
||||||
if (!mr->empty) {
|
if (!mr->empty) {
|
||||||
if (s->is_weak)
|
if (s->is_weak)
|
||||||
delete_weak_ref(rt, mr);
|
js_unlink_weak_ref(&mr->weak_record);
|
||||||
else
|
else
|
||||||
JS_FreeValueRT(rt, mr->key);
|
JS_FreeValueRT(rt, mr->key);
|
||||||
JS_FreeValueRT(rt, mr->value);
|
JS_FreeValueRT(rt, mr->value);
|
||||||
|
@ -376,6 +376,7 @@ void JS_AddIntrinsicPromise(JSContext *ctx);
|
|||||||
void JS_AddIntrinsicBigInt(JSContext *ctx);
|
void JS_AddIntrinsicBigInt(JSContext *ctx);
|
||||||
void JS_AddIntrinsicBigFloat(JSContext *ctx);
|
void JS_AddIntrinsicBigFloat(JSContext *ctx);
|
||||||
void JS_AddIntrinsicBigDecimal(JSContext *ctx);
|
void JS_AddIntrinsicBigDecimal(JSContext *ctx);
|
||||||
|
void JS_AddIntrinsicWeakRef(JSContext *ctx);
|
||||||
/* enable operator overloading */
|
/* enable operator overloading */
|
||||||
void JS_AddIntrinsicOperators(JSContext *ctx);
|
void JS_AddIntrinsicOperators(JSContext *ctx);
|
||||||
/* enable "use math" */
|
/* enable "use math" */
|
||||||
@ -1077,6 +1078,10 @@ int JS_SetModuleExport(JSContext *ctx, JSModuleDef *m, const char *export_name,
|
|||||||
int JS_SetModuleExportList(JSContext *ctx, JSModuleDef *m,
|
int JS_SetModuleExportList(JSContext *ctx, JSModuleDef *m,
|
||||||
const JSCFunctionListEntry *tab, int len);
|
const JSCFunctionListEntry *tab, int len);
|
||||||
|
|
||||||
|
/* WeakRef support */
|
||||||
|
JSValue JS_NewWeakRef(JSContext *ctx, JSValueConst target);
|
||||||
|
JSValue JS_DerefWeakRef(JSContext *ctx, JSValueConst weakref);
|
||||||
|
|
||||||
#undef js_unlikely
|
#undef js_unlikely
|
||||||
#undef js_force_inline
|
#undef js_force_inline
|
||||||
|
|
||||||
|
@ -204,7 +204,7 @@ Uint32Array
|
|||||||
Uint8Array
|
Uint8Array
|
||||||
Uint8ClampedArray
|
Uint8ClampedArray
|
||||||
WeakMap
|
WeakMap
|
||||||
WeakRef=skip
|
WeakRef
|
||||||
WeakSet
|
WeakSet
|
||||||
well-formed-json-stringify
|
well-formed-json-stringify
|
||||||
|
|
||||||
|
@ -779,6 +779,59 @@ function test_weak_map()
|
|||||||
tab[i][0] = null; /* should remove the object from the WeakMap too */
|
tab[i][0] = null; /* should remove the object from the WeakMap too */
|
||||||
}
|
}
|
||||||
/* the WeakMap should be empty here */
|
/* the WeakMap should be empty here */
|
||||||
|
|
||||||
|
// test symbol as key
|
||||||
|
var symbol_key = Symbol("key");
|
||||||
|
a = new WeakMap();
|
||||||
|
a.set(symbol_key, "hello");
|
||||||
|
assert(a.has(symbol_key), true);
|
||||||
|
assert(a.get(symbol_key), "hello");
|
||||||
|
|
||||||
|
symbol_key = undefined;
|
||||||
|
assert(a.has(symbol_key), false);
|
||||||
|
assert(a.get(symbol_key), undefined);
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_weak_ref()
|
||||||
|
{
|
||||||
|
var obj = {};
|
||||||
|
var ref = new WeakRef(obj);
|
||||||
|
var ref2 = new WeakRef(obj);
|
||||||
|
|
||||||
|
assert(ref.deref(), obj);
|
||||||
|
assert(ref2.deref(), obj);
|
||||||
|
|
||||||
|
obj = undefined;
|
||||||
|
/* weak ref should be released */
|
||||||
|
assert(ref.deref(), undefined);
|
||||||
|
assert(ref2.deref(), undefined);
|
||||||
|
|
||||||
|
// symbol
|
||||||
|
var sym = Symbol("sym")
|
||||||
|
var ref3 = new WeakRef(sym);
|
||||||
|
assert(ref3.deref(), sym);
|
||||||
|
new WeakRef(Symbol.hasInstance);
|
||||||
|
|
||||||
|
sym = undefined;
|
||||||
|
assert(ref3.deref(), undefined);
|
||||||
|
|
||||||
|
function should_fail(block) {
|
||||||
|
try {
|
||||||
|
block()
|
||||||
|
} catch (e) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
throw_error("weak ref should throw exception, but it not");
|
||||||
|
}
|
||||||
|
|
||||||
|
should_fail(() => new WeakRef("string"));
|
||||||
|
should_fail(() => new WeakRef(/* number */0));
|
||||||
|
should_fail(() => new WeakRef(/* registered symbol */ Symbol.for("test")));
|
||||||
|
|
||||||
|
/* cyclic ref */
|
||||||
|
obj = {};
|
||||||
|
ref = new WeakRef(obj);
|
||||||
|
obj["x"] = ref;
|
||||||
}
|
}
|
||||||
|
|
||||||
function test_generator()
|
function test_generator()
|
||||||
@ -855,4 +908,5 @@ test_regexp();
|
|||||||
test_symbol();
|
test_symbol();
|
||||||
test_map();
|
test_map();
|
||||||
test_weak_map();
|
test_weak_map();
|
||||||
|
test_weak_ref();
|
||||||
test_generator();
|
test_generator();
|
||||||
|
Loading…
Reference in New Issue
Block a user