wine/dlls/jscript/set.c
Gabriel Ivăncescu 1b14d7b46d jscript: Implement WeakMap.has().
Signed-off-by: Gabriel Ivăncescu <gabrielopcode@gmail.com>
2023-07-20 21:48:39 +02:00

970 lines
27 KiB
C

/*
* Copyright 2021 Jacek Caban for CodeWeavers
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
*/
#include <assert.h>
#include <math.h>
#include "jscript.h"
#include "wine/rbtree.h"
#include "wine/debug.h"
WINE_DEFAULT_DEBUG_CHANNEL(jscript);
typedef struct {
jsdisp_t dispex;
struct wine_rb_tree map;
struct list entries;
size_t size;
} MapInstance;
struct jsval_map_entry {
struct wine_rb_entry entry;
jsval_t key;
jsval_t value;
/*
* We need to maintain a list as well to support traversal in forEach.
* If the entry is removed while being processed by forEach, it's
* still kept in the list and released later, when it's safe.
*/
struct list list_entry;
unsigned int ref;
BOOL deleted;
};
static int jsval_map_compare(const void *k, const struct wine_rb_entry *e)
{
const struct jsval_map_entry *entry = WINE_RB_ENTRY_VALUE(e, const struct jsval_map_entry, entry);
const jsval_t *key = k;
union {
double d;
INT64 n;
} bits1, bits2;
if(jsval_type(entry->key) != jsval_type(*key))
return (int)jsval_type(entry->key) - (int)jsval_type(*key);
switch(jsval_type(*key)) {
case JSV_UNDEFINED:
case JSV_NULL:
return 0;
case JSV_OBJECT:
if(get_object(*key) == get_object(entry->key)) return 0;
return get_object(*key) < get_object(entry->key) ? -1 : 1;
case JSV_STRING:
return jsstr_cmp(get_string(*key), get_string(entry->key));
case JSV_NUMBER:
if(isnan(get_number(*key))) return isnan(get_number(entry->key)) ? 0 : -1;
if(isnan(get_number(entry->key))) return 1;
/* native treats -0 differently than 0, so need to compare bitwise */
bits1.d = get_number(*key);
bits2.d = get_number(entry->key);
return (bits1.n == bits2.n) ? 0 : (bits1.n < bits2.n ? -1 : 1);
case JSV_BOOL:
if(get_bool(*key) == get_bool(entry->key)) return 0;
return get_bool(*key) ? 1 : -1;
default:
assert(0);
return 0;
}
}
static HRESULT get_map_this(script_ctx_t *ctx, jsval_t vthis, MapInstance **ret)
{
jsdisp_t *jsdisp;
if(!is_object_instance(vthis))
return JS_E_OBJECT_EXPECTED;
if(!(jsdisp = to_jsdisp(get_object(vthis))) || !is_class(jsdisp, JSCLASS_MAP)) {
WARN("not a Map object passed as 'this'\n");
return throw_error(ctx, JS_E_WRONG_THIS, L"Map");
}
*ret = CONTAINING_RECORD(jsdisp, MapInstance, dispex);
return S_OK;
}
static HRESULT get_set_this(script_ctx_t *ctx, jsval_t vthis, MapInstance **ret)
{
jsdisp_t *jsdisp;
if(!is_object_instance(vthis))
return JS_E_OBJECT_EXPECTED;
if(!(jsdisp = to_jsdisp(get_object(vthis))) || !is_class(jsdisp, JSCLASS_SET)) {
WARN("not a Set object passed as 'this'\n");
return throw_error(ctx, JS_E_WRONG_THIS, L"Set");
}
*ret = CONTAINING_RECORD(jsdisp, MapInstance, dispex);
return S_OK;
}
static struct jsval_map_entry *get_map_entry(MapInstance *map, jsval_t key)
{
struct wine_rb_entry *entry;
if(!(entry = wine_rb_get(&map->map, &key))) return NULL;
return CONTAINING_RECORD(entry, struct jsval_map_entry, entry);
}
static void grab_map_entry(struct jsval_map_entry *entry)
{
entry->ref++;
}
static void release_map_entry(struct jsval_map_entry *entry)
{
if(--entry->ref) return;
jsval_release(entry->key);
jsval_release(entry->value);
list_remove(&entry->list_entry);
free(entry);
}
static void delete_map_entry(MapInstance *map, struct jsval_map_entry *entry)
{
map->size--;
wine_rb_remove(&map->map, &entry->entry);
entry->deleted = TRUE;
release_map_entry(entry);
}
static HRESULT set_map_entry(MapInstance *map, jsval_t key, jsval_t value, jsval_t *r)
{
struct jsval_map_entry *entry;
HRESULT hres;
if((entry = get_map_entry(map, key))) {
jsval_t val;
hres = jsval_copy(value, &val);
if(FAILED(hres))
return hres;
jsval_release(entry->value);
entry->value = val;
}else {
if(!(entry = calloc(1, sizeof(*entry)))) return E_OUTOFMEMORY;
hres = jsval_copy(key, &entry->key);
if(SUCCEEDED(hres)) {
hres = jsval_copy(value, &entry->value);
if(FAILED(hres))
jsval_release(entry->key);
}
if(FAILED(hres)) {
free(entry);
return hres;
}
grab_map_entry(entry);
wine_rb_put(&map->map, &entry->key, &entry->entry);
list_add_tail(&map->entries, &entry->list_entry);
map->size++;
}
if(r) *r = jsval_undefined();
return S_OK;
}
static HRESULT iterate_map(MapInstance *map, script_ctx_t *ctx, unsigned argc, jsval_t *argv, jsval_t *r)
{
struct list *iter = list_head(&map->entries);
jsval_t context_this = jsval_undefined();
HRESULT hres;
if(!argc || !is_object_instance(argv[0])) {
FIXME("invalid callback %s\n", debugstr_jsval(argc ? argv[0] : jsval_undefined()));
return E_FAIL;
}
if(argc > 1)
context_this = argv[1];
while(iter) {
struct jsval_map_entry *entry = LIST_ENTRY(iter, struct jsval_map_entry, list_entry);
jsval_t args[3], v;
if(entry->deleted) {
iter = list_next(&map->entries, iter);
continue;
}
args[0] = entry->value;
args[1] = entry->key;
args[2] = jsval_obj(&map->dispex);
grab_map_entry(entry);
hres = disp_call_value(ctx, get_object(argv[0]), context_this, DISPATCH_METHOD, ARRAY_SIZE(args), args, &v);
iter = list_next(&map->entries, iter);
release_map_entry(entry);
if(FAILED(hres))
return hres;
jsval_release(v);
}
if(r) *r = jsval_undefined();
return S_OK;
}
static HRESULT Map_clear(script_ctx_t *ctx, jsval_t vthis, WORD flags, unsigned argc, jsval_t *argv,
jsval_t *r)
{
struct jsval_map_entry *entry, *entry2;
MapInstance *map;
HRESULT hres;
hres = get_map_this(ctx, vthis, &map);
if(FAILED(hres))
return hres;
TRACE("%p\n", map);
LIST_FOR_EACH_ENTRY_SAFE(entry, entry2, &map->entries, struct jsval_map_entry, list_entry)
delete_map_entry(map, entry);
if(r) *r = jsval_undefined();
return S_OK;
}
static HRESULT Map_delete(script_ctx_t *ctx, jsval_t vthis, WORD flags, unsigned argc, jsval_t *argv,
jsval_t *r)
{
jsval_t key = argc >= 1 ? argv[0] : jsval_undefined();
struct jsval_map_entry *entry;
MapInstance *map;
HRESULT hres;
hres = get_map_this(ctx, vthis, &map);
if(FAILED(hres))
return hres;
TRACE("%p (%s)\n", map, debugstr_jsval(key));
if((entry = get_map_entry(map, key))) delete_map_entry(map, entry);
if(r) *r = jsval_bool(!!entry);
return S_OK;
}
static HRESULT Map_forEach(script_ctx_t *ctx, jsval_t vthis, WORD flags, unsigned argc, jsval_t *argv,
jsval_t *r)
{
MapInstance *map;
HRESULT hres;
hres = get_map_this(ctx, vthis, &map);
if(FAILED(hres))
return hres;
TRACE("%p (%s)\n", map, debugstr_jsval(argc >= 1 ? argv[0] : jsval_undefined()));
return iterate_map(map, ctx, argc, argv, r);
}
static HRESULT Map_get(script_ctx_t *ctx, jsval_t vthis, WORD flags, unsigned argc, jsval_t *argv,
jsval_t *r)
{
jsval_t key = argc >= 1 ? argv[0] : jsval_undefined();
struct jsval_map_entry *entry;
MapInstance *map;
HRESULT hres;
hres = get_map_this(ctx, vthis, &map);
if(FAILED(hres))
return hres;
TRACE("%p (%s)\n", map, debugstr_jsval(key));
if(!(entry = get_map_entry(map, key))) {
if(r) *r = jsval_undefined();
return S_OK;
}
return r ? jsval_copy(entry->value, r) : S_OK;
}
static HRESULT Map_set(script_ctx_t *ctx, jsval_t vthis, WORD flags, unsigned argc, jsval_t *argv,
jsval_t *r)
{
jsval_t key = argc >= 1 ? argv[0] : jsval_undefined();
jsval_t value = argc >= 2 ? argv[1] : jsval_undefined();
MapInstance *map;
HRESULT hres;
hres = get_map_this(ctx, vthis, &map);
if(FAILED(hres))
return hres;
TRACE("%p (%s %s)\n", map, debugstr_jsval(key), debugstr_jsval(value));
return set_map_entry(map, key, value, r);
}
static HRESULT Map_has(script_ctx_t *ctx, jsval_t vthis, WORD flags, unsigned argc, jsval_t *argv,
jsval_t *r)
{
jsval_t key = argc >= 1 ? argv[0] : jsval_undefined();
struct jsval_map_entry *entry;
MapInstance *map;
HRESULT hres;
hres = get_map_this(ctx, vthis, &map);
if(FAILED(hres))
return hres;
TRACE("%p (%s)\n", map, debugstr_jsval(key));
entry = get_map_entry(map, key);
if(r) *r = jsval_bool(!!entry);
return S_OK;
}
static HRESULT Map_get_size(script_ctx_t *ctx, jsdisp_t *jsthis, jsval_t *r)
{
MapInstance *map = (MapInstance*)jsthis;
TRACE("%p\n", map);
*r = jsval_number(map->size);
return S_OK;
}
static HRESULT Map_value(script_ctx_t *ctx, jsval_t vthis, WORD flags, unsigned argc, jsval_t *argv,
jsval_t *r)
{
FIXME("\n");
return E_NOTIMPL;
}
static void Map_destructor(jsdisp_t *dispex)
{
MapInstance *map = (MapInstance*)dispex;
while(!list_empty(&map->entries)) {
struct jsval_map_entry *entry = LIST_ENTRY(list_head(&map->entries),
struct jsval_map_entry, list_entry);
assert(!entry->deleted);
release_map_entry(entry);
}
free(map);
}
static HRESULT Map_gc_traverse(struct gc_ctx *gc_ctx, enum gc_traverse_op op, jsdisp_t *dispex)
{
MapInstance *map = (MapInstance*)dispex;
struct jsval_map_entry *entry, *entry2;
HRESULT hres;
if(op == GC_TRAVERSE_UNLINK) {
LIST_FOR_EACH_ENTRY_SAFE(entry, entry2, &map->entries, struct jsval_map_entry, list_entry)
release_map_entry(entry);
wine_rb_destroy(&map->map, NULL, NULL);
return S_OK;
}
LIST_FOR_EACH_ENTRY(entry, &map->entries, struct jsval_map_entry, list_entry) {
hres = gc_process_linked_val(gc_ctx, op, dispex, &entry->key);
if(FAILED(hres))
return hres;
hres = gc_process_linked_val(gc_ctx, op, dispex, &entry->value);
if(FAILED(hres))
return hres;
}
return S_OK;
}
static const builtin_prop_t Map_prototype_props[] = {
{L"clear", Map_clear, PROPF_METHOD},
{L"delete" , Map_delete, PROPF_METHOD|1},
{L"forEach", Map_forEach, PROPF_METHOD|1},
{L"get", Map_get, PROPF_METHOD|1},
{L"has", Map_has, PROPF_METHOD|1},
{L"set", Map_set, PROPF_METHOD|2},
};
static const builtin_prop_t Map_props[] = {
{L"size", NULL,0, Map_get_size, builtin_set_const},
};
static const builtin_info_t Map_prototype_info = {
JSCLASS_OBJECT,
Map_value,
ARRAY_SIZE(Map_prototype_props),
Map_prototype_props,
NULL,
NULL
};
static const builtin_info_t Map_info = {
JSCLASS_MAP,
Map_value,
ARRAY_SIZE(Map_props),
Map_props,
Map_destructor,
NULL,
NULL,
NULL,
NULL,
Map_gc_traverse
};
static HRESULT Map_constructor(script_ctx_t *ctx, jsval_t vthis, WORD flags, unsigned argc, jsval_t *argv,
jsval_t *r)
{
MapInstance *map;
HRESULT hres;
switch(flags) {
case DISPATCH_CONSTRUCT:
TRACE("\n");
if(!r)
return S_OK;
if(!(map = calloc(1, sizeof(*map))))
return E_OUTOFMEMORY;
hres = init_dispex(&map->dispex, ctx, &Map_info, ctx->map_prototype);
if(FAILED(hres))
return hres;
wine_rb_init(&map->map, jsval_map_compare);
list_init(&map->entries);
*r = jsval_obj(&map->dispex);
return S_OK;
case DISPATCH_METHOD:
return throw_error(ctx, JS_E_WRONG_THIS, L"Map");
default:
FIXME("unimplemented flags %x\n", flags);
return E_NOTIMPL;
}
}
static HRESULT Set_add(script_ctx_t *ctx, jsval_t vthis, WORD flags, unsigned argc, jsval_t *argv,
jsval_t *r)
{
jsval_t key = argc ? argv[0] : jsval_undefined();
MapInstance *set;
HRESULT hres;
hres = get_set_this(ctx, vthis, &set);
if(FAILED(hres))
return hres;
TRACE("%p (%s)\n", set, debugstr_jsval(key));
return set_map_entry(set, key, key, r);
}
static HRESULT Set_clear(script_ctx_t *ctx, jsval_t vthis, WORD flags, unsigned argc, jsval_t *argv,
jsval_t *r)
{
struct jsval_map_entry *entry, *entry2;
MapInstance *set;
HRESULT hres;
hres = get_set_this(ctx, vthis, &set);
if(FAILED(hres))
return hres;
TRACE("%p\n", set);
LIST_FOR_EACH_ENTRY_SAFE(entry, entry2, &set->entries, struct jsval_map_entry, list_entry)
delete_map_entry(set, entry);
if(r) *r = jsval_undefined();
return S_OK;
}
static HRESULT Set_delete(script_ctx_t *ctx, jsval_t vthis, WORD flags, unsigned argc, jsval_t *argv,
jsval_t *r)
{
jsval_t key = argc ? argv[0] : jsval_undefined();
struct jsval_map_entry *entry;
MapInstance *set;
HRESULT hres;
hres = get_set_this(ctx, vthis, &set);
if(FAILED(hres))
return hres;
TRACE("%p (%s)\n", set, debugstr_jsval(key));
if((entry = get_map_entry(set, key))) delete_map_entry(set, entry);
if(r) *r = jsval_bool(!!entry);
return S_OK;
}
static HRESULT Set_forEach(script_ctx_t *ctx, jsval_t vthis, WORD flags, unsigned argc, jsval_t *argv,
jsval_t *r)
{
MapInstance *set;
HRESULT hres;
hres = get_set_this(ctx, vthis, &set);
if(FAILED(hres))
return hres;
TRACE("%p (%s)\n", set, debugstr_jsval(argc ? argv[0] : jsval_undefined()));
return iterate_map(set, ctx, argc, argv, r);
}
static HRESULT Set_has(script_ctx_t *ctx, jsval_t vthis, WORD flags, unsigned argc, jsval_t *argv,
jsval_t *r)
{
jsval_t key = argc ? argv[0] : jsval_undefined();
struct jsval_map_entry *entry;
MapInstance *set;
HRESULT hres;
hres = get_set_this(ctx, vthis, &set);
if(FAILED(hres))
return hres;
TRACE("%p (%s)\n", set, debugstr_jsval(key));
entry = get_map_entry(set, key);
if(r) *r = jsval_bool(!!entry);
return S_OK;
}
static HRESULT Set_value(script_ctx_t *ctx, jsval_t vthis, WORD flags, unsigned argc, jsval_t *argv,
jsval_t *r)
{
FIXME("\n");
return E_NOTIMPL;
}
static const builtin_prop_t Set_prototype_props[] = {
{L"add", Set_add, PROPF_METHOD|1},
{L"clear", Set_clear, PROPF_METHOD},
{L"delete" , Set_delete, PROPF_METHOD|1},
{L"forEach", Set_forEach, PROPF_METHOD|1},
{L"has", Set_has, PROPF_METHOD|1},
};
static const builtin_info_t Set_prototype_info = {
JSCLASS_OBJECT,
Set_value,
ARRAY_SIZE(Set_prototype_props),
Set_prototype_props,
NULL,
NULL
};
static const builtin_info_t Set_info = {
JSCLASS_SET,
Set_value,
ARRAY_SIZE(Map_props),
Map_props,
Map_destructor,
NULL,
NULL,
NULL,
NULL,
Map_gc_traverse
};
static HRESULT Set_constructor(script_ctx_t *ctx, jsval_t vthis, WORD flags, unsigned argc, jsval_t *argv,
jsval_t *r)
{
MapInstance *set;
HRESULT hres;
switch(flags) {
case DISPATCH_CONSTRUCT:
TRACE("\n");
if(!r)
return S_OK;
if(!(set = calloc(1, sizeof(*set))))
return E_OUTOFMEMORY;
hres = init_dispex(&set->dispex, ctx, &Set_info, ctx->set_prototype);
if(FAILED(hres))
return hres;
wine_rb_init(&set->map, jsval_map_compare);
list_init(&set->entries);
*r = jsval_obj(&set->dispex);
return S_OK;
case DISPATCH_METHOD:
return throw_error(ctx, JS_E_WRONG_THIS, L"Set");
default:
FIXME("unimplemented flags %x\n", flags);
return E_NOTIMPL;
}
}
typedef struct {
jsdisp_t dispex;
struct rb_tree map;
} WeakMapInstance;
static int weakmap_compare(const void *k, const struct rb_entry *e)
{
ULONG_PTR a = (ULONG_PTR)k, b = (ULONG_PTR)RB_ENTRY_VALUE(e, const struct weakmap_entry, entry)->key;
return (a > b) - (a < b);
}
static HRESULT get_weakmap_this(script_ctx_t *ctx, jsval_t vthis, WeakMapInstance **ret)
{
jsdisp_t *jsdisp;
if(!is_object_instance(vthis))
return JS_E_OBJECT_EXPECTED;
if(!(jsdisp = to_jsdisp(get_object(vthis))) || !is_class(jsdisp, JSCLASS_WEAKMAP)) {
WARN("not a WeakMap object passed as 'this'\n");
throw_error(ctx, JS_E_WRONG_THIS, L"WeakMap");
return DISP_E_EXCEPTION;
}
*ret = CONTAINING_RECORD(jsdisp, WeakMapInstance, dispex);
return S_OK;
}
static struct weakmap_entry *get_weakmap_entry(WeakMapInstance *weakmap, jsdisp_t *key)
{
struct rb_entry *entry;
if(!(entry = rb_get(&weakmap->map, key))) return NULL;
return CONTAINING_RECORD(entry, struct weakmap_entry, entry);
}
void remove_weakmap_entry(struct weakmap_entry *entry)
{
WeakMapInstance *weakmap = (WeakMapInstance*)entry->weakmap;
struct list *next = entry->weak_refs_entry.next;
if(next->next != &entry->weak_refs_entry)
list_remove(&entry->weak_refs_entry);
else {
struct weak_refs_entry *weak_refs_entry = LIST_ENTRY(next, struct weak_refs_entry, list);
entry->key->has_weak_refs = FALSE;
rb_remove(&entry->key->ctx->weak_refs, &weak_refs_entry->entry);
free(weak_refs_entry);
}
rb_remove(&weakmap->map, &entry->entry);
jsval_release(entry->value);
free(entry);
}
static HRESULT WeakMap_clear(script_ctx_t *ctx, jsval_t vthis, WORD flags, unsigned argc, jsval_t *argv,
jsval_t *r)
{
WeakMapInstance *weakmap;
HRESULT hres;
hres = get_weakmap_this(ctx, vthis, &weakmap);
if(FAILED(hres))
return hres;
TRACE("%p\n", weakmap);
while(weakmap->map.root)
remove_weakmap_entry(RB_ENTRY_VALUE(weakmap->map.root, struct weakmap_entry, entry));
if(r) *r = jsval_undefined();
return S_OK;
}
static HRESULT WeakMap_delete(script_ctx_t *ctx, jsval_t vthis, WORD flags, unsigned argc, jsval_t *argv,
jsval_t *r)
{
jsdisp_t *key = (argc >= 1 && is_object_instance(argv[0])) ? to_jsdisp(get_object(argv[0])) : NULL;
struct weakmap_entry *entry;
WeakMapInstance *weakmap;
HRESULT hres;
hres = get_weakmap_this(ctx, vthis, &weakmap);
if(FAILED(hres))
return hres;
TRACE("%p (%p)\n", weakmap, key);
if((entry = get_weakmap_entry(weakmap, key)))
remove_weakmap_entry(entry);
if(r) *r = jsval_bool(!!entry);
return S_OK;
}
static HRESULT WeakMap_get(script_ctx_t *ctx, jsval_t vthis, WORD flags, unsigned argc, jsval_t *argv,
jsval_t *r)
{
jsdisp_t *key = (argc >= 1 && is_object_instance(argv[0])) ? to_jsdisp(get_object(argv[0])) : NULL;
struct weakmap_entry *entry;
WeakMapInstance *weakmap;
HRESULT hres;
hres = get_weakmap_this(ctx, vthis, &weakmap);
if(FAILED(hres))
return hres;
TRACE("%p (%p)\n", weakmap, key);
if(!(entry = get_weakmap_entry(weakmap, key))) {
if(r) *r = jsval_undefined();
return S_OK;
}
return r ? jsval_copy(entry->value, r) : S_OK;
}
static HRESULT WeakMap_set(script_ctx_t *ctx, jsval_t vthis, WORD flags, unsigned argc, jsval_t *argv,
jsval_t *r)
{
jsdisp_t *key = (argc >= 1 && is_object_instance(argv[0])) ? to_jsdisp(get_object(argv[0])) : NULL;
jsval_t value = argc >= 2 ? argv[1] : jsval_undefined();
struct weakmap_entry *entry;
WeakMapInstance *weakmap;
HRESULT hres;
hres = get_weakmap_this(ctx, vthis, &weakmap);
if(FAILED(hres))
return hres;
TRACE("%p (%p %s)\n", weakmap, key, debugstr_jsval(value));
if(!key)
return JS_E_KEY_NOT_OBJECT;
if(key->ctx != ctx) {
FIXME("different ctx not supported\n");
return JS_E_KEY_NOT_OBJECT;
}
if((entry = get_weakmap_entry(weakmap, key))) {
jsval_t val;
hres = jsval_copy(value, &val);
if(FAILED(hres))
return hres;
jsval_release(entry->value);
entry->value = val;
}else {
struct weak_refs_entry *weak_refs_entry;
if(!(entry = malloc(sizeof(*entry))))
return E_OUTOFMEMORY;
hres = jsval_copy(value, &entry->value);
if(FAILED(hres)) {
free(entry);
return hres;
}
if(key->has_weak_refs)
weak_refs_entry = RB_ENTRY_VALUE(rb_get(&ctx->weak_refs, key), struct weak_refs_entry, entry);
else {
if(!(weak_refs_entry = malloc(sizeof(*weak_refs_entry)))) {
jsval_release(entry->value);
free(entry);
return E_OUTOFMEMORY;
}
rb_put(&ctx->weak_refs, key, &weak_refs_entry->entry);
list_init(&weak_refs_entry->list);
key->has_weak_refs = TRUE;
}
list_add_tail(&weak_refs_entry->list, &entry->weak_refs_entry);
entry->key = key;
entry->weakmap = &weakmap->dispex;
rb_put(&weakmap->map, key, &entry->entry);
}
if(r) *r = jsval_undefined();
return S_OK;
}
static HRESULT WeakMap_has(script_ctx_t *ctx, jsval_t vthis, WORD flags, unsigned argc, jsval_t *argv,
jsval_t *r)
{
jsdisp_t *key = (argc >= 1 && is_object_instance(argv[0])) ? to_jsdisp(get_object(argv[0])) : NULL;
WeakMapInstance *weakmap;
HRESULT hres;
hres = get_weakmap_this(ctx, vthis, &weakmap);
if(FAILED(hres))
return hres;
TRACE("%p (%p)\n", weakmap, key);
if(r) *r = jsval_bool(!!get_weakmap_entry(weakmap, key));
return S_OK;
}
static HRESULT WeakMap_value(script_ctx_t *ctx, jsval_t vthis, WORD flags, unsigned argc, jsval_t *argv,
jsval_t *r)
{
FIXME("\n");
return E_NOTIMPL;
}
static void WeakMap_destructor(jsdisp_t *dispex)
{
WeakMapInstance *weakmap = (WeakMapInstance*)dispex;
while(weakmap->map.root)
remove_weakmap_entry(RB_ENTRY_VALUE(weakmap->map.root, struct weakmap_entry, entry));
free(weakmap);
}
static HRESULT WeakMap_gc_traverse(struct gc_ctx *gc_ctx, enum gc_traverse_op op, jsdisp_t *dispex)
{
WeakMapInstance *weakmap = (WeakMapInstance*)dispex;
struct weakmap_entry *entry;
HRESULT hres;
if(op == GC_TRAVERSE_UNLINK) {
while(weakmap->map.root)
remove_weakmap_entry(RB_ENTRY_VALUE(weakmap->map.root, struct weakmap_entry, entry));
return S_OK;
}
RB_FOR_EACH_ENTRY(entry, &weakmap->map, struct weakmap_entry, entry) {
/* Only traverse the values if the key turned out to be alive, which means it might not have traversed
the associated values with it from this WeakMap yet (because it wasn't considered alive back then).
We need both the key and the WeakMap for the entry to actually be accessible (and thus traversed). */
if(op == GC_TRAVERSE && entry->key->gc_marked)
continue;
hres = gc_process_linked_val(gc_ctx, op, dispex, &entry->value);
if(FAILED(hres))
return hres;
}
return S_OK;
}
static const builtin_prop_t WeakMap_prototype_props[] = {
{L"clear", WeakMap_clear, PROPF_METHOD},
{L"delete", WeakMap_delete, PROPF_METHOD|1},
{L"get", WeakMap_get, PROPF_METHOD|1},
{L"has", WeakMap_has, PROPF_METHOD|1},
{L"set", WeakMap_set, PROPF_METHOD|2},
};
static const builtin_info_t WeakMap_prototype_info = {
JSCLASS_OBJECT,
WeakMap_value,
ARRAY_SIZE(WeakMap_prototype_props),
WeakMap_prototype_props,
NULL,
NULL
};
static const builtin_info_t WeakMap_info = {
JSCLASS_WEAKMAP,
WeakMap_value,
0,
NULL,
WeakMap_destructor,
NULL,
NULL,
NULL,
NULL,
WeakMap_gc_traverse
};
static HRESULT WeakMap_constructor(script_ctx_t *ctx, jsval_t vthis, WORD flags, unsigned argc, jsval_t *argv,
jsval_t *r)
{
WeakMapInstance *weakmap;
HRESULT hres;
switch(flags) {
case DISPATCH_CONSTRUCT:
TRACE("\n");
if(!r)
return S_OK;
if(!(weakmap = calloc(1, sizeof(*weakmap))))
return E_OUTOFMEMORY;
hres = init_dispex(&weakmap->dispex, ctx, &WeakMap_info, ctx->weakmap_prototype);
if(FAILED(hres))
return hres;
rb_init(&weakmap->map, weakmap_compare);
*r = jsval_obj(&weakmap->dispex);
return S_OK;
case DISPATCH_METHOD:
return throw_error(ctx, JS_E_WRONG_THIS, L"WeakMap");
default:
FIXME("unimplemented flags %x\n", flags);
return E_NOTIMPL;
}
}
HRESULT init_set_constructor(script_ctx_t *ctx)
{
jsdisp_t *constructor;
HRESULT hres;
if(ctx->version < SCRIPTLANGUAGEVERSION_ES6)
return S_OK;
hres = create_dispex(ctx, &Set_prototype_info, ctx->object_prototype, &ctx->set_prototype);
if(FAILED(hres))
return hres;
hres = create_builtin_constructor(ctx, Set_constructor, L"Set", NULL,
PROPF_CONSTR, ctx->set_prototype, &constructor);
if(FAILED(hres))
return hres;
hres = jsdisp_define_data_property(ctx->global, L"Set", PROPF_WRITABLE,
jsval_obj(constructor));
jsdisp_release(constructor);
if(FAILED(hres))
return hres;
hres = create_dispex(ctx, &Map_prototype_info, ctx->object_prototype, &ctx->map_prototype);
if(FAILED(hres))
return hres;
hres = create_builtin_constructor(ctx, Map_constructor, L"Map", NULL,
PROPF_CONSTR, ctx->map_prototype, &constructor);
if(FAILED(hres))
return hres;
hres = jsdisp_define_data_property(ctx->global, L"Map", PROPF_WRITABLE,
jsval_obj(constructor));
jsdisp_release(constructor);
if(FAILED(hres))
return hres;
hres = create_dispex(ctx, &WeakMap_prototype_info, ctx->object_prototype, &ctx->weakmap_prototype);
if(FAILED(hres))
return hres;
hres = create_builtin_constructor(ctx, WeakMap_constructor, L"WeakMap", NULL,
PROPF_CONSTR, ctx->weakmap_prototype, &constructor);
if(FAILED(hres))
return hres;
hres = jsdisp_define_data_property(ctx->global, L"WeakMap", PROPF_WRITABLE,
jsval_obj(constructor));
jsdisp_release(constructor);
return hres;
}