Allow symbols as WeakMap and WeakSet keys (#58)
This commit is contained in:
parent
0b09109151
commit
d2e632e77a
3 changed files with 83 additions and 49 deletions
71
quickjs.c
71
quickjs.c
|
@ -446,6 +446,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 JSMapRecord *first_weak_ref;
|
||||||
#ifdef DUMP_LEAKS
|
#ifdef DUMP_LEAKS
|
||||||
struct list_head link; /* string list */
|
struct list_head link; /* string list */
|
||||||
#endif
|
#endif
|
||||||
|
@ -1052,7 +1053,7 @@ 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 void reset_weak_ref(JSRuntime *rt, struct JSMapRecord **first_weak_ref);
|
||||||
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,
|
||||||
|
@ -2624,6 +2625,7 @@ static JSAtom __JS_NewAtom(JSRuntime *rt, JSString *str, int atom_type)
|
||||||
p->hash = h;
|
p->hash = h;
|
||||||
p->hash_next = i; /* atom_index */
|
p->hash_next = i; /* atom_index */
|
||||||
p->atom_type = atom_type;
|
p->atom_type = atom_type;
|
||||||
|
p->first_weak_ref = NULL;
|
||||||
|
|
||||||
rt->atom_count++;
|
rt->atom_count++;
|
||||||
|
|
||||||
|
@ -2718,6 +2720,9 @@ 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;
|
||||||
|
if (unlikely(p->first_weak_ref)) {
|
||||||
|
reset_weak_ref(rt, &p->first_weak_ref);
|
||||||
|
}
|
||||||
/* free the string structure */
|
/* free the string structure */
|
||||||
#ifdef DUMP_LEAKS
|
#ifdef DUMP_LEAKS
|
||||||
list_del(&p->link);
|
list_del(&p->link);
|
||||||
|
@ -5184,7 +5189,7 @@ static void free_object(JSRuntime *rt, JSObject *p)
|
||||||
p->prop = NULL;
|
p->prop = NULL;
|
||||||
|
|
||||||
if (unlikely(p->first_weak_ref)) {
|
if (unlikely(p->first_weak_ref)) {
|
||||||
reset_weak_ref(rt, p);
|
reset_weak_ref(rt, &p->first_weak_ref);
|
||||||
}
|
}
|
||||||
|
|
||||||
finalizer = rt->class_array[p->class_id].finalizer;
|
finalizer = rt->class_array[p->class_id].finalizer;
|
||||||
|
@ -43529,11 +43534,31 @@ 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 JSMapRecord **get_first_weak_ref(JSValueConst key)
|
||||||
|
{
|
||||||
|
switch (JS_VALUE_GET_TAG(key)) {
|
||||||
|
case JS_TAG_OBJECT:
|
||||||
|
{
|
||||||
|
JSObject *p = JS_VALUE_GET_OBJ(key);
|
||||||
|
return &p->first_weak_ref;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case JS_TAG_SYMBOL:
|
||||||
|
{
|
||||||
|
JSAtomStruct *p = JS_VALUE_GET_PTR(key);
|
||||||
|
return &p->first_weak_ref;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static JSMapRecord *map_add_record(JSContext *ctx, JSMapState *s,
|
static JSMapRecord *map_add_record(JSContext *ctx, JSMapState *s,
|
||||||
JSValueConst key)
|
JSValueConst key)
|
||||||
{
|
{
|
||||||
uint32_t h;
|
uint32_t h;
|
||||||
JSMapRecord *mr;
|
JSMapRecord *mr, **pmr;
|
||||||
|
|
||||||
mr = js_malloc(ctx, sizeof(*mr));
|
mr = js_malloc(ctx, sizeof(*mr));
|
||||||
if (!mr)
|
if (!mr)
|
||||||
|
@ -43542,10 +43567,10 @@ 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);
|
pmr = get_first_weak_ref(key);
|
||||||
/* Add the weak reference */
|
/* Add the weak reference */
|
||||||
mr->next_weak_ref = p->first_weak_ref;
|
mr->next_weak_ref = *pmr;
|
||||||
p->first_weak_ref = mr;
|
*pmr = mr;
|
||||||
} else {
|
} else {
|
||||||
JS_DupValue(ctx, key);
|
JS_DupValue(ctx, key);
|
||||||
}
|
}
|
||||||
|
@ -43567,10 +43592,8 @@ static JSMapRecord *map_add_record(JSContext *ctx, JSMapState *s,
|
||||||
static void delete_weak_ref(JSRuntime *rt, JSMapRecord *mr)
|
static void delete_weak_ref(JSRuntime *rt, JSMapRecord *mr)
|
||||||
{
|
{
|
||||||
JSMapRecord **pmr, *mr1;
|
JSMapRecord **pmr, *mr1;
|
||||||
JSObject *p;
|
|
||||||
|
|
||||||
p = JS_VALUE_GET_OBJ(mr->key);
|
pmr = get_first_weak_ref(mr->key);
|
||||||
pmr = &p->first_weak_ref;
|
|
||||||
for(;;) {
|
for(;;) {
|
||||||
mr1 = *pmr;
|
mr1 = *pmr;
|
||||||
assert(mr1 != NULL);
|
assert(mr1 != NULL);
|
||||||
|
@ -43614,14 +43637,14 @@ static void map_decref_record(JSRuntime *rt, JSMapRecord *mr)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void reset_weak_ref(JSRuntime *rt, JSObject *p)
|
static void reset_weak_ref(JSRuntime *rt, struct JSMapRecord **first_weak_ref)
|
||||||
{
|
{
|
||||||
JSMapRecord *mr, *mr_next;
|
JSMapRecord *mr, *mr_next;
|
||||||
JSMapState *s;
|
JSMapState *s;
|
||||||
|
|
||||||
/* first pass to remove the records from the WeakMap/WeakSet
|
/* first pass to remove the records from the WeakMap/WeakSet
|
||||||
lists */
|
lists */
|
||||||
for(mr = p->first_weak_ref; mr != NULL; mr = mr->next_weak_ref) {
|
for(mr = *first_weak_ref; mr != NULL; mr = mr->next_weak_ref) {
|
||||||
s = mr->map;
|
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 */
|
||||||
|
@ -43631,13 +43654,13 @@ static void reset_weak_ref(JSRuntime *rt, JSObject *p)
|
||||||
|
|
||||||
/* second pass to free the values to avoid modifying the weak
|
/* second pass to free the values to avoid modifying the weak
|
||||||
reference list while traversing it. */
|
reference list while traversing it. */
|
||||||
for(mr = p->first_weak_ref; mr != NULL; mr = mr_next) {
|
for(mr = *first_weak_ref; mr != NULL; mr = mr_next) {
|
||||||
mr_next = mr->next_weak_ref;
|
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 */
|
*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,
|
||||||
|
@ -43646,13 +43669,29 @@ static JSValue js_map_set(JSContext *ctx, JSValueConst this_val,
|
||||||
JSMapState *s = JS_GetOpaque2(ctx, this_val, JS_CLASS_MAP + magic);
|
JSMapState *s = JS_GetOpaque2(ctx, this_val, JS_CLASS_MAP + magic);
|
||||||
JSMapRecord *mr;
|
JSMapRecord *mr;
|
||||||
JSValueConst key, value;
|
JSValueConst key, value;
|
||||||
|
JSAtomStruct *p;
|
||||||
|
int is_set;
|
||||||
|
|
||||||
if (!s)
|
if (!s)
|
||||||
return JS_EXCEPTION;
|
return JS_EXCEPTION;
|
||||||
|
is_set = (magic & MAGIC_SET);
|
||||||
key = map_normalize_key(ctx, argv[0]);
|
key = map_normalize_key(ctx, argv[0]);
|
||||||
if (s->is_weak && !JS_IsObject(key))
|
if (s->is_weak) {
|
||||||
return JS_ThrowTypeErrorNotAnObject(ctx);
|
switch (JS_VALUE_GET_TAG(key)) {
|
||||||
if (magic & MAGIC_SET)
|
case JS_TAG_OBJECT:
|
||||||
|
break;
|
||||||
|
case JS_TAG_SYMBOL:
|
||||||
|
// Per spec: prohibit symbols registered with Symbol.for()
|
||||||
|
p = JS_VALUE_GET_PTR(key);
|
||||||
|
if (p->atom_type != JS_ATOM_TYPE_GLOBAL_SYMBOL)
|
||||||
|
break;
|
||||||
|
// fallthru
|
||||||
|
default:
|
||||||
|
return JS_ThrowTypeError(ctx, "invalid value used as %s key",
|
||||||
|
is_set ? "WeakSet" : "WeakMap");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (is_set)
|
||||||
value = JS_UNDEFINED;
|
value = JS_UNDEFINED;
|
||||||
else
|
else
|
||||||
value = argv[1];
|
value = argv[1];
|
||||||
|
|
|
@ -336,38 +336,6 @@ test262/test/built-ins/TypedArrayConstructors/internals/Set/key-is-out-of-bounds
|
||||||
test262/test/built-ins/TypedArrayConstructors/internals/Set/key-is-out-of-bounds.js:22: strict mode: Test262Error: Reflect.set(sample, "-1", 1) must return true Expected SameValue(«false», «true») to be true (Testing with Float64Array.)
|
test262/test/built-ins/TypedArrayConstructors/internals/Set/key-is-out-of-bounds.js:22: strict mode: Test262Error: Reflect.set(sample, "-1", 1) must return true Expected SameValue(«false», «true») to be true (Testing with Float64Array.)
|
||||||
test262/test/built-ins/TypedArrayConstructors/internals/Set/tonumber-value-detached-buffer.js:39: Test262Error: Expected SameValue(«false», «true») to be true (Testing with Float64Array.)
|
test262/test/built-ins/TypedArrayConstructors/internals/Set/tonumber-value-detached-buffer.js:39: Test262Error: Expected SameValue(«false», «true») to be true (Testing with Float64Array.)
|
||||||
test262/test/built-ins/TypedArrayConstructors/internals/Set/tonumber-value-detached-buffer.js:39: strict mode: Test262Error: Expected SameValue(«false», «true») to be true (Testing with Float64Array.)
|
test262/test/built-ins/TypedArrayConstructors/internals/Set/tonumber-value-detached-buffer.js:39: strict mode: Test262Error: Expected SameValue(«false», «true») to be true (Testing with Float64Array.)
|
||||||
test262/test/built-ins/WeakMap/iterable-with-symbol-keys.js:32: TypeError: not an object
|
|
||||||
test262/test/built-ins/WeakMap/iterable-with-symbol-keys.js:32: strict mode: TypeError: not an object
|
|
||||||
test262/test/built-ins/WeakMap/prototype/delete/delete-entry-with-symbol-key-initial-iterable.js:26: TypeError: not an object
|
|
||||||
test262/test/built-ins/WeakMap/prototype/delete/delete-entry-with-symbol-key-initial-iterable.js:26: strict mode: TypeError: not an object
|
|
||||||
test262/test/built-ins/WeakMap/prototype/delete/delete-entry-with-symbol-key.js:24: TypeError: not an object
|
|
||||||
test262/test/built-ins/WeakMap/prototype/delete/delete-entry-with-symbol-key.js:24: strict mode: TypeError: not an object
|
|
||||||
test262/test/built-ins/WeakMap/prototype/delete/returns-false-when-symbol-key-not-present.js:18: TypeError: not an object
|
|
||||||
test262/test/built-ins/WeakMap/prototype/delete/returns-false-when-symbol-key-not-present.js:18: strict mode: TypeError: not an object
|
|
||||||
test262/test/built-ins/WeakMap/prototype/get/returns-undefined-with-symbol-key.js:28: TypeError: not an object
|
|
||||||
test262/test/built-ins/WeakMap/prototype/get/returns-undefined-with-symbol-key.js:28: strict mode: TypeError: not an object
|
|
||||||
test262/test/built-ins/WeakMap/prototype/get/returns-value-with-symbol-key.js:22: TypeError: not an object
|
|
||||||
test262/test/built-ins/WeakMap/prototype/get/returns-value-with-symbol-key.js:22: strict mode: TypeError: not an object
|
|
||||||
test262/test/built-ins/WeakMap/prototype/has/returns-false-when-symbol-key-not-present.js:20: TypeError: not an object
|
|
||||||
test262/test/built-ins/WeakMap/prototype/has/returns-false-when-symbol-key-not-present.js:20: strict mode: TypeError: not an object
|
|
||||||
test262/test/built-ins/WeakMap/prototype/has/returns-true-when-symbol-key-present.js:19: TypeError: not an object
|
|
||||||
test262/test/built-ins/WeakMap/prototype/has/returns-true-when-symbol-key-present.js:19: strict mode: TypeError: not an object
|
|
||||||
test262/test/built-ins/WeakMap/prototype/set/adds-symbol-element.js:17: TypeError: not an object
|
|
||||||
test262/test/built-ins/WeakMap/prototype/set/adds-symbol-element.js:17: strict mode: TypeError: not an object
|
|
||||||
test262/test/built-ins/WeakSet/iterable-with-symbol-values.js:24: TypeError: not an object
|
|
||||||
test262/test/built-ins/WeakSet/iterable-with-symbol-values.js:24: strict mode: TypeError: not an object
|
|
||||||
test262/test/built-ins/WeakSet/prototype/add/adds-symbol-element.js:17: TypeError: not an object
|
|
||||||
test262/test/built-ins/WeakSet/prototype/add/adds-symbol-element.js:17: strict mode: TypeError: not an object
|
|
||||||
test262/test/built-ins/WeakSet/prototype/add/returns-this-symbol.js:18: TypeError: not an object
|
|
||||||
test262/test/built-ins/WeakSet/prototype/add/returns-this-symbol.js:18: strict mode: TypeError: not an object
|
|
||||||
test262/test/built-ins/WeakSet/prototype/add/returns-this-when-ignoring-duplicate-symbol.js:20: TypeError: not an object
|
|
||||||
test262/test/built-ins/WeakSet/prototype/add/returns-this-when-ignoring-duplicate-symbol.js:20: strict mode: TypeError: not an object
|
|
||||||
test262/test/built-ins/WeakSet/prototype/delete/delete-symbol-entry.js:22: TypeError: not an object
|
|
||||||
test262/test/built-ins/WeakSet/prototype/delete/delete-symbol-entry.js:22: strict mode: TypeError: not an object
|
|
||||||
test262/test/built-ins/WeakSet/prototype/has/returns-false-when-symbol-value-not-present.js:20: TypeError: not an object
|
|
||||||
test262/test/built-ins/WeakSet/prototype/has/returns-false-when-symbol-value-not-present.js:20: strict mode: TypeError: not an object
|
|
||||||
test262/test/built-ins/WeakSet/prototype/has/returns-true-when-symbol-value-present.js:17: TypeError: not an object
|
|
||||||
test262/test/built-ins/WeakSet/prototype/has/returns-true-when-symbol-value-present.js:17: strict mode: TypeError: not an object
|
|
||||||
test262/test/language/expressions/assignment/target-member-computed-reference-null.js:32: Test262Error: Expected a DummyError but got a TypeError
|
test262/test/language/expressions/assignment/target-member-computed-reference-null.js:32: Test262Error: Expected a DummyError but got a TypeError
|
||||||
test262/test/language/expressions/assignment/target-member-computed-reference-null.js:32: strict mode: Test262Error: Expected a DummyError but got a TypeError
|
test262/test/language/expressions/assignment/target-member-computed-reference-null.js:32: strict mode: Test262Error: Expected a DummyError but got a TypeError
|
||||||
test262/test/language/expressions/assignment/target-member-computed-reference-undefined.js:32: Test262Error: Expected a DummyError but got a TypeError
|
test262/test/language/expressions/assignment/target-member-computed-reference-undefined.js:32: Test262Error: Expected a DummyError but got a TypeError
|
||||||
|
|
|
@ -618,10 +618,20 @@ function test_map()
|
||||||
|
|
||||||
function test_weak_map()
|
function test_weak_map()
|
||||||
{
|
{
|
||||||
var a, i, n, tab, o, v, n2;
|
var a, e, i, n, tab, o, v, n2;
|
||||||
a = new WeakMap();
|
a = new WeakMap();
|
||||||
n = 10;
|
n = 10;
|
||||||
tab = [];
|
tab = [];
|
||||||
|
for (const k of [null, 42, "no", Symbol.for("forbidden")]) {
|
||||||
|
e = undefined;
|
||||||
|
try {
|
||||||
|
a.set(k, 42);
|
||||||
|
} catch (_e) {
|
||||||
|
e = _e;
|
||||||
|
}
|
||||||
|
assert(!!e);
|
||||||
|
assert(e.message, "invalid value used as WeakMap key");
|
||||||
|
}
|
||||||
for(i = 0; i < n; i++) {
|
for(i = 0; i < n; i++) {
|
||||||
v = { };
|
v = { };
|
||||||
o = { id: i };
|
o = { id: i };
|
||||||
|
@ -640,6 +650,22 @@ function test_weak_map()
|
||||||
/* the WeakMap should be empty here */
|
/* the WeakMap should be empty here */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function test_weak_set()
|
||||||
|
{
|
||||||
|
var a, e;
|
||||||
|
a = new WeakSet();
|
||||||
|
for (const k of [null, 42, "no", Symbol.for("forbidden")]) {
|
||||||
|
e = undefined;
|
||||||
|
try {
|
||||||
|
a.add(k);
|
||||||
|
} catch (_e) {
|
||||||
|
e = _e;
|
||||||
|
}
|
||||||
|
assert(!!e);
|
||||||
|
assert(e.message, "invalid value used as WeakSet key");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function test_generator()
|
function test_generator()
|
||||||
{
|
{
|
||||||
function *f() {
|
function *f() {
|
||||||
|
@ -696,4 +722,5 @@ test_regexp();
|
||||||
test_symbol();
|
test_symbol();
|
||||||
test_map();
|
test_map();
|
||||||
test_weak_map();
|
test_weak_map();
|
||||||
|
test_weak_set();
|
||||||
test_generator();
|
test_generator();
|
||||||
|
|
Loading…
Reference in a new issue