Commit e8ee6dda authored by Gabriel Ivăncescu's avatar Gabriel Ivăncescu Committed by Alexandre Julliard

jscript: Implement a Garbage Collector to deal with circular references.

Implement a basic GC based on the mark-and-sweep algorithm, without requiring manually specifying "roots", which vastly simplifies the code. Signed-off-by: 's avatarGabriel Ivăncescu <gabrielopcode@gmail.com>
parent b0db79d7
......@@ -666,6 +666,327 @@ static HRESULT fill_protrefs(jsdisp_t *This)
return S_OK;
}
static void unlink_props(jsdisp_t *jsdisp)
{
dispex_prop_t *prop = jsdisp->props, *end;
for(end = prop + jsdisp->prop_cnt; prop < end; prop++) {
switch(prop->type) {
case PROP_DELETED:
case PROP_PROTREF:
continue;
case PROP_JSVAL:
jsval_release(prop->u.val);
break;
case PROP_ACCESSOR:
if(prop->u.accessor.getter)
jsdisp_release(prop->u.accessor.getter);
if(prop->u.accessor.setter)
jsdisp_release(prop->u.accessor.setter);
break;
default:
break;
}
prop->type = PROP_DELETED;
}
}
/*
* To deal with circular refcounts, a basic Garbage Collector is used with a variant of the
* mark-and-sweep algorithm that doesn't require knowing or traversing any specific "roots".
* This works based on the assumption that circular references can only happen when objects
* end up pointing to each other, and each other alone, without any external refs.
*
* An "external ref" is a ref to the object that's not from any other object. Example of such
* refs can be local variables, the script ctx (which keeps a ref to the global object), etc.
*
* At a high level, there are 3 logical passes done on the entire list of objects:
*
* 1. Speculatively decrease refcounts of each linked-to-object from each object. This ensures
* that the only remaining refcount on each object is the number of "external refs" to it.
* At the same time, mark all of the objects so that they can be potentially collected.
*
* 2. For each object with a non-zero "external refcount", clear the mark from step 1, and
* recursively traverse all linked objects from it, clearing their marks as well (regardless
* of their refcount), stopping a given path when the object is unmarked (and then going back
* up the GC stack). This basically unmarks all of the objects with "external refcounts"
* and those accessible from them, and only the leaked dangling objects will still be marked.
*
* 3. For each object that is marked, unlink all of the objects linked from it, because they
* are dangling in a circular refcount and not accessible. This should release them.
*
* During unlinking (GC_TRAVERSE_UNLINK), it is important that we unlink *all* linked objects
* from the object, to be certain that releasing the object later will not delete any other
* objects. Otherwise calculating the "next" object in the list becomes impossible.
*
* This collection process has to be done periodically, but can be pretty expensive so there
* has to be a balance between reclaiming dangling objects and performance.
*
*/
struct gc_stack_chunk {
jsdisp_t *objects[1020];
struct gc_stack_chunk *prev;
};
struct gc_ctx {
struct gc_stack_chunk *chunk;
struct gc_stack_chunk *next;
unsigned idx;
};
static HRESULT gc_stack_push(struct gc_ctx *gc_ctx, jsdisp_t *obj)
{
if(!gc_ctx->idx) {
if(gc_ctx->next)
gc_ctx->chunk = gc_ctx->next;
else {
struct gc_stack_chunk *prev, *tmp = malloc(sizeof(*tmp));
if(!tmp)
return E_OUTOFMEMORY;
prev = gc_ctx->chunk;
gc_ctx->chunk = tmp;
gc_ctx->chunk->prev = prev;
}
gc_ctx->idx = ARRAY_SIZE(gc_ctx->chunk->objects);
gc_ctx->next = NULL;
}
gc_ctx->chunk->objects[--gc_ctx->idx] = obj;
return S_OK;
}
static jsdisp_t *gc_stack_pop(struct gc_ctx *gc_ctx)
{
jsdisp_t *obj = gc_ctx->chunk->objects[gc_ctx->idx];
if(++gc_ctx->idx == ARRAY_SIZE(gc_ctx->chunk->objects)) {
free(gc_ctx->next);
gc_ctx->next = gc_ctx->chunk;
gc_ctx->chunk = gc_ctx->chunk->prev;
gc_ctx->idx = 0;
}
return obj;
}
HRESULT gc_run(script_ctx_t *ctx)
{
/* Save original refcounts in a linked list of chunks */
struct chunk
{
struct chunk *next;
LONG ref[1020];
} *head, *chunk;
jsdisp_t *obj, *obj2, *link, *link2;
dispex_prop_t *prop, *props_end;
struct gc_ctx gc_ctx = { 0 };
unsigned chunk_idx = 0;
HRESULT hres = S_OK;
struct list *iter;
/* Prevent recursive calls from side-effects during unlinking (e.g. CollectGarbage from host object's Release) */
if(ctx->gc_is_unlinking)
return S_OK;
if(!(head = malloc(sizeof(*head))))
return E_OUTOFMEMORY;
head->next = NULL;
chunk = head;
/* 1. Save actual refcounts and decrease them speculatively as-if we unlinked the objects */
LIST_FOR_EACH_ENTRY(obj, &ctx->objects, jsdisp_t, entry) {
if(chunk_idx == ARRAY_SIZE(chunk->ref)) {
if(!(chunk->next = malloc(sizeof(*chunk)))) {
do {
chunk = head->next;
free(head);
head = chunk;
} while(head);
return E_OUTOFMEMORY;
}
chunk = chunk->next, chunk_idx = 0;
chunk->next = NULL;
}
chunk->ref[chunk_idx++] = obj->ref;
}
LIST_FOR_EACH_ENTRY(obj, &ctx->objects, jsdisp_t, entry) {
for(prop = obj->props, props_end = prop + obj->prop_cnt; prop < props_end; prop++) {
switch(prop->type) {
case PROP_JSVAL:
if(is_object_instance(prop->u.val) && (link = to_jsdisp(get_object(prop->u.val))) && link->ctx == ctx)
link->ref--;
break;
case PROP_ACCESSOR:
if(prop->u.accessor.getter && prop->u.accessor.getter->ctx == ctx)
prop->u.accessor.getter->ref--;
if(prop->u.accessor.setter && prop->u.accessor.setter->ctx == ctx)
prop->u.accessor.setter->ref--;
break;
default:
break;
}
}
if(obj->prototype && obj->prototype->ctx == ctx)
obj->prototype->ref--;
if(obj->builtin_info->gc_traverse)
obj->builtin_info->gc_traverse(&gc_ctx, GC_TRAVERSE_SPECULATIVELY, obj);
obj->gc_marked = TRUE;
}
/* 2. Clear mark on objects with non-zero "external refcount" and all objects accessible from them */
LIST_FOR_EACH_ENTRY(obj, &ctx->objects, jsdisp_t, entry) {
if(!obj->ref || !obj->gc_marked)
continue;
hres = gc_stack_push(&gc_ctx, NULL);
if(FAILED(hres))
break;
obj2 = obj;
do
{
obj2->gc_marked = FALSE;
for(prop = obj2->props, props_end = prop + obj2->prop_cnt; prop < props_end; prop++) {
switch(prop->type) {
case PROP_JSVAL:
if(!is_object_instance(prop->u.val))
continue;
link = to_jsdisp(get_object(prop->u.val));
link2 = NULL;
break;
case PROP_ACCESSOR:
link = prop->u.accessor.getter;
link2 = prop->u.accessor.setter;
break;
default:
continue;
}
if(link && link->gc_marked && link->ctx == ctx) {
hres = gc_stack_push(&gc_ctx, link);
if(FAILED(hres))
break;
}
if(link2 && link2->gc_marked && link2->ctx == ctx) {
hres = gc_stack_push(&gc_ctx, link2);
if(FAILED(hres))
break;
}
}
if(FAILED(hres))
break;
if(obj2->prototype && obj2->prototype->gc_marked && obj2->prototype->ctx == ctx) {
hres = gc_stack_push(&gc_ctx, obj2->prototype);
if(FAILED(hres))
break;
}
if(obj2->builtin_info->gc_traverse) {
hres = obj2->builtin_info->gc_traverse(&gc_ctx, GC_TRAVERSE, obj2);
if(FAILED(hres))
break;
}
do obj2 = gc_stack_pop(&gc_ctx); while(obj2 && !obj2->gc_marked);
} while(obj2);
if(FAILED(hres)) {
do obj2 = gc_stack_pop(&gc_ctx); while(obj2);
break;
}
}
free(gc_ctx.next);
/* Restore */
chunk = head, chunk_idx = 0;
LIST_FOR_EACH_ENTRY(obj, &ctx->objects, jsdisp_t, entry) {
obj->ref = chunk->ref[chunk_idx++];
if(chunk_idx == ARRAY_SIZE(chunk->ref)) {
struct chunk *next = chunk->next;
free(chunk);
chunk = next, chunk_idx = 0;
}
}
free(chunk);
if(FAILED(hres))
return hres;
/* 3. Remove all the links from the marked objects, since they are dangling */
ctx->gc_is_unlinking = TRUE;
iter = list_head(&ctx->objects);
while(iter) {
obj = LIST_ENTRY(iter, jsdisp_t, entry);
if(!obj->gc_marked) {
iter = list_next(&ctx->objects, iter);
continue;
}
/* Grab it since it gets removed when unlinked */
jsdisp_addref(obj);
unlink_props(obj);
if(obj->prototype) {
jsdisp_release(obj->prototype);
obj->prototype = NULL;
}
if(obj->builtin_info->gc_traverse)
obj->builtin_info->gc_traverse(&gc_ctx, GC_TRAVERSE_UNLINK, obj);
/* Releasing unlinked object should not delete any other object,
so we can safely obtain the next pointer now */
iter = list_next(&ctx->objects, iter);
jsdisp_release(obj);
}
ctx->gc_is_unlinking = FALSE;
return S_OK;
}
HRESULT gc_process_linked_obj(struct gc_ctx *gc_ctx, enum gc_traverse_op op, jsdisp_t *obj, jsdisp_t *link, void **unlink_ref)
{
if(op == GC_TRAVERSE_UNLINK) {
*unlink_ref = NULL;
jsdisp_release(link);
return S_OK;
}
if(link->ctx != obj->ctx)
return S_OK;
if(op == GC_TRAVERSE_SPECULATIVELY)
link->ref--;
else if(link->gc_marked)
return gc_stack_push(gc_ctx, link);
return S_OK;
}
HRESULT gc_process_linked_val(struct gc_ctx *gc_ctx, enum gc_traverse_op op, jsdisp_t *obj, jsval_t *link)
{
jsdisp_t *jsdisp;
if(op == GC_TRAVERSE_UNLINK) {
jsval_t val = *link;
*link = jsval_undefined();
jsval_release(val);
return S_OK;
}
if(!is_object_instance(*link) || !(jsdisp = to_jsdisp(get_object(*link))) || jsdisp->ctx != obj->ctx)
return S_OK;
if(op == GC_TRAVERSE_SPECULATIVELY)
jsdisp->ref--;
else if(jsdisp->gc_marked)
return gc_stack_push(gc_ctx, jsdisp);
return S_OK;
}
struct typeinfo_func {
dispex_prop_t *prop;
function_code_t *code;
......@@ -1848,6 +2169,7 @@ HRESULT init_dispex(jsdisp_t *dispex, script_ctx_t *ctx, const builtin_info_t *b
script_addref(ctx);
dispex->ctx = ctx;
list_add_tail(&ctx->objects, &dispex->entry);
return S_OK;
}
......@@ -1882,6 +2204,8 @@ void jsdisp_free(jsdisp_t *obj)
{
dispex_prop_t *prop;
list_remove(&obj->entry);
TRACE("(%p)\n", obj);
for(prop = obj->props; prop < obj->props+obj->prop_cnt; prop++) {
......
......@@ -434,12 +434,40 @@ static void scope_destructor(jsdisp_t *dispex)
free(scope);
}
static HRESULT scope_gc_traverse(struct gc_ctx *gc_ctx, enum gc_traverse_op op, jsdisp_t *dispex)
{
scope_chain_t *scope = CONTAINING_RECORD(dispex, scope_chain_t, dispex);
HRESULT hres;
if(scope->next) {
hres = gc_process_linked_obj(gc_ctx, op, dispex, &scope->next->dispex, (void**)&scope->next);
if(FAILED(hres))
return hres;
}
if(op == GC_TRAVERSE_UNLINK) {
IDispatch *obj = scope->obj;
if(obj) {
scope->obj = NULL;
IDispatch_Release(obj);
}
return S_OK;
}
return scope->jsobj ? gc_process_linked_obj(gc_ctx, op, dispex, scope->jsobj, (void**)&scope->obj) : S_OK;
}
static const builtin_info_t scope_info = {
JSCLASS_NONE,
NULL,
0,
NULL,
scope_destructor,
NULL,
NULL,
NULL,
NULL,
scope_gc_traverse
};
static HRESULT scope_push(script_ctx_t *ctx, scope_chain_t *scope, jsdisp_t *jsobj, IDispatch *obj, scope_chain_t **ret)
......
......@@ -88,6 +88,11 @@ static void Enumerator_destructor(jsdisp_t *dispex)
free(dispex);
}
static HRESULT Enumerator_gc_traverse(struct gc_ctx *gc_ctx, enum gc_traverse_op op, jsdisp_t *dispex)
{
return gc_process_linked_val(gc_ctx, op, dispex, &enumerator_from_jsdisp(dispex)->item);
}
static HRESULT Enumerator_atEnd(script_ctx_t *ctx, jsval_t vthis, WORD flags, unsigned argc, jsval_t *argv,
jsval_t *r)
{
......@@ -189,7 +194,11 @@ static const builtin_info_t EnumeratorInst_info = {
0,
NULL,
Enumerator_destructor,
NULL
NULL,
NULL,
NULL,
NULL,
Enumerator_gc_traverse
};
static HRESULT alloc_enumerator(script_ctx_t *ctx, jsdisp_t *object_prototype, EnumeratorInstance **ret)
......
......@@ -39,6 +39,7 @@ struct _function_vtbl_t {
HRESULT (*toString)(FunctionInstance*,jsstr_t**);
function_code_t* (*get_code)(FunctionInstance*);
void (*destructor)(FunctionInstance*);
HRESULT (*gc_traverse)(struct gc_ctx*,enum gc_traverse_op,FunctionInstance*);
};
typedef struct {
......@@ -72,6 +73,11 @@ typedef struct {
static HRESULT create_bind_function(script_ctx_t*,FunctionInstance*,jsval_t,unsigned,jsval_t*,jsdisp_t**r);
static HRESULT no_gc_traverse(struct gc_ctx *gc_ctx, enum gc_traverse_op op, FunctionInstance *function)
{
return S_OK;
}
static inline FunctionInstance *function_from_jsdisp(jsdisp_t *jsdisp)
{
return CONTAINING_RECORD(jsdisp, FunctionInstance, dispex);
......@@ -108,7 +114,8 @@ static void Arguments_destructor(jsdisp_t *jsdisp)
free(arguments->buf);
}
jsdisp_release(&arguments->function->function.dispex);
if(arguments->function)
jsdisp_release(&arguments->function->function.dispex);
free(arguments);
}
......@@ -166,6 +173,23 @@ static HRESULT Arguments_idx_put(jsdisp_t *jsdisp, unsigned idx, jsval_t val)
arguments->function->func_code->params[idx], val);
}
static HRESULT Arguments_gc_traverse(struct gc_ctx *gc_ctx, enum gc_traverse_op op, jsdisp_t *jsdisp)
{
ArgumentsInstance *arguments = arguments_from_jsdisp(jsdisp);
HRESULT hres;
unsigned i;
if(arguments->buf) {
for(i = 0; i < arguments->argc; i++) {
hres = gc_process_linked_val(gc_ctx, op, jsdisp, &arguments->buf[i]);
if(FAILED(hres))
return hres;
}
}
return gc_process_linked_obj(gc_ctx, op, jsdisp, &arguments->function->function.dispex, (void**)&arguments->function);
}
static const builtin_info_t Arguments_info = {
JSCLASS_ARGUMENTS,
Arguments_value,
......@@ -174,7 +198,8 @@ static const builtin_info_t Arguments_info = {
NULL,
Arguments_idx_length,
Arguments_idx_get,
Arguments_idx_put
Arguments_idx_put,
Arguments_gc_traverse
};
HRESULT setup_arguments_object(script_ctx_t *ctx, call_frame_t *frame)
......@@ -548,6 +573,12 @@ static void Function_destructor(jsdisp_t *dispex)
free(function);
}
static HRESULT Function_gc_traverse(struct gc_ctx *gc_ctx, enum gc_traverse_op op, jsdisp_t *dispex)
{
FunctionInstance *function = function_from_jsdisp(dispex);
return function->vtbl->gc_traverse(gc_ctx, op, function);
}
static const builtin_prop_t Function_props[] = {
{L"apply", Function_apply, PROPF_METHOD|2},
{L"arguments", NULL, 0, Function_get_arguments},
......@@ -563,7 +594,11 @@ static const builtin_info_t Function_info = {
ARRAY_SIZE(Function_props),
Function_props,
Function_destructor,
NULL
NULL,
NULL,
NULL,
NULL,
Function_gc_traverse
};
static const builtin_prop_t FunctionInst_props[] = {
......@@ -577,7 +612,11 @@ static const builtin_info_t FunctionInst_info = {
ARRAY_SIZE(FunctionInst_props),
FunctionInst_props,
Function_destructor,
NULL
NULL,
NULL,
NULL,
NULL,
Function_gc_traverse
};
static HRESULT create_function(script_ctx_t *ctx, const builtin_info_t *builtin_info, const function_vtbl_t *vtbl, size_t size,
......@@ -657,7 +696,8 @@ static const function_vtbl_t NativeFunctionVtbl = {
NativeFunction_call,
NativeFunction_toString,
NativeFunction_get_code,
NativeFunction_destructor
NativeFunction_destructor,
no_gc_traverse
};
HRESULT create_builtin_function(script_ctx_t *ctx, builtin_invoke_t value_proc, const WCHAR *name,
......@@ -776,11 +816,22 @@ static void InterpretedFunction_destructor(FunctionInstance *func)
scope_release(function->scope_chain);
}
static HRESULT InterpretedFunction_gc_traverse(struct gc_ctx *gc_ctx, enum gc_traverse_op op, FunctionInstance *func)
{
InterpretedFunction *function = (InterpretedFunction*)func;
if(!function->scope_chain)
return S_OK;
return gc_process_linked_obj(gc_ctx, op, &function->function.dispex, &function->scope_chain->dispex,
(void**)&function->scope_chain);
}
static const function_vtbl_t InterpretedFunctionVtbl = {
InterpretedFunction_call,
InterpretedFunction_toString,
InterpretedFunction_get_code,
InterpretedFunction_destructor
InterpretedFunction_destructor,
InterpretedFunction_gc_traverse
};
HRESULT create_source_function(script_ctx_t *ctx, bytecode_t *code, function_code_t *func_code,
......@@ -870,15 +921,36 @@ static void BindFunction_destructor(FunctionInstance *func)
for(i = 0; i < function->argc; i++)
jsval_release(function->args[i]);
jsdisp_release(&function->target->dispex);
if(function->target)
jsdisp_release(&function->target->dispex);
jsval_release(function->this);
}
static HRESULT BindFunction_gc_traverse(struct gc_ctx *gc_ctx, enum gc_traverse_op op, FunctionInstance *func)
{
BindFunction *function = (BindFunction*)func;
HRESULT hres;
unsigned i;
for(i = 0; i < function->argc; i++) {
hres = gc_process_linked_val(gc_ctx, op, &function->function.dispex, &function->args[i]);
if(FAILED(hres))
return hres;
}
hres = gc_process_linked_obj(gc_ctx, op, &function->function.dispex, &function->target->dispex, (void**)&function->target);
if(FAILED(hres))
return hres;
return gc_process_linked_val(gc_ctx, op, &function->function.dispex, &function->this);
}
static const function_vtbl_t BindFunctionVtbl = {
BindFunction_call,
BindFunction_toString,
BindFunction_get_code,
BindFunction_destructor
BindFunction_destructor,
BindFunction_gc_traverse
};
static HRESULT create_bind_function(script_ctx_t *ctx, FunctionInstance *target, jsval_t bound_this, unsigned argc,
......
......@@ -495,6 +495,8 @@ static void decrease_state(JScript *This, SCRIPTSTATE state)
}
script_globals_release(This->ctx);
gc_run(This->ctx);
/* FALLTHROUGH */
case SCRIPTSTATE_UNINITIALIZED:
change_state(This, state);
......@@ -734,6 +736,7 @@ static HRESULT WINAPI JScript_SetScriptSite(IActiveScript *iface,
ctx->html_mode = This->html_mode;
ctx->acc = jsval_undefined();
list_init(&ctx->named_items);
list_init(&ctx->objects);
heap_pool_init(&ctx->tmp_heap);
hres = create_jscaller(ctx);
......
......@@ -137,9 +137,20 @@ typedef struct named_item_t {
struct list entry;
} named_item_t;
struct gc_ctx;
enum gc_traverse_op {
GC_TRAVERSE_UNLINK,
GC_TRAVERSE_SPECULATIVELY,
GC_TRAVERSE
};
HRESULT create_named_item_script_obj(script_ctx_t*,named_item_t*) DECLSPEC_HIDDEN;
named_item_t *lookup_named_item(script_ctx_t*,const WCHAR*,unsigned) DECLSPEC_HIDDEN;
void release_named_item(named_item_t*) DECLSPEC_HIDDEN;
HRESULT gc_run(script_ctx_t*) DECLSPEC_HIDDEN;
HRESULT gc_process_linked_obj(struct gc_ctx*,enum gc_traverse_op,jsdisp_t*,jsdisp_t*,void**) DECLSPEC_HIDDEN;
HRESULT gc_process_linked_val(struct gc_ctx*,enum gc_traverse_op,jsdisp_t*,jsval_t*) DECLSPEC_HIDDEN;
typedef struct {
const WCHAR *name;
......@@ -159,6 +170,7 @@ typedef struct {
unsigned (*idx_length)(jsdisp_t*);
HRESULT (*idx_get)(jsdisp_t*,unsigned,jsval_t*);
HRESULT (*idx_put)(jsdisp_t*,unsigned,jsval_t);
HRESULT (*gc_traverse)(struct gc_ctx*,enum gc_traverse_op,jsdisp_t*);
} builtin_info_t;
struct jsdisp_t {
......@@ -166,15 +178,18 @@ struct jsdisp_t {
LONG ref;
BOOLEAN extensible;
BOOLEAN gc_marked;
DWORD buf_size;
DWORD prop_cnt;
dispex_prop_t *props;
script_ctx_t *ctx;
BOOL extensible;
jsdisp_t *prototype;
const builtin_info_t *builtin_info;
struct list entry;
};
static inline IDispatch *to_disp(jsdisp_t *jsdisp)
......@@ -350,6 +365,7 @@ struct _script_ctx_t {
struct _call_frame_t *call_ctx;
struct list named_items;
struct list objects;
IActiveScriptSite *site;
IInternetHostSecurityManager *secmgr;
DWORD safeopt;
......@@ -362,6 +378,8 @@ struct _script_ctx_t {
heap_pool_t tmp_heap;
BOOL gc_is_unlinking;
jsval_t *stack;
unsigned stack_top;
jsval_t acc;
......
......@@ -548,6 +548,11 @@ static void RegExp_destructor(jsdisp_t *dispex)
free(This);
}
static HRESULT RegExp_gc_traverse(struct gc_ctx *gc_ctx, enum gc_traverse_op op, jsdisp_t *dispex)
{
return gc_process_linked_val(gc_ctx, op, dispex, &regexp_from_jsdisp(dispex)->last_index_val);
}
static const builtin_prop_t RegExp_props[] = {
{L"exec", RegExp_exec, PROPF_METHOD|1},
{L"global", NULL,0, RegExp_get_global},
......@@ -565,7 +570,11 @@ static const builtin_info_t RegExp_info = {
ARRAY_SIZE(RegExp_props),
RegExp_props,
RegExp_destructor,
NULL
NULL,
NULL,
NULL,
NULL,
RegExp_gc_traverse
};
static const builtin_prop_t RegExpInst_props[] = {
......@@ -582,7 +591,11 @@ static const builtin_info_t RegExpInst_info = {
ARRAY_SIZE(RegExpInst_props),
RegExpInst_props,
RegExp_destructor,
NULL
NULL,
NULL,
NULL,
NULL,
RegExp_gc_traverse
};
static HRESULT alloc_regexp(script_ctx_t *ctx, jsstr_t *str, jsdisp_t *object_prototype, RegExpInstance **ret)
......
......@@ -362,6 +362,31 @@ static void Map_destructor(jsdisp_t *dispex)
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},
......@@ -390,7 +415,11 @@ static const builtin_info_t Map_info = {
ARRAY_SIZE(Map_props),
Map_props,
Map_destructor,
NULL
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,
......@@ -545,7 +574,11 @@ static const builtin_info_t Set_info = {
ARRAY_SIZE(Map_props),
Map_props,
Map_destructor,
NULL
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,
......
......@@ -3446,6 +3446,12 @@ static void test_invokeex(void)
static void test_destructors(void)
{
static const WCHAR cyclic_refs[] = L"(function() {\n"
"var a = function() {}, c = { 'a': a, 'ref': Math }, b = { 'a': a, 'c': c };\n"
"Math.ref = { 'obj': testDestrObj, 'ref': Math, 'a': a, 'b': b };\n"
"a.ref = { 'ref': Math, 'a': a }; b.ref = Math.ref;\n"
"a.self = a; b.self = b; c.self = c;\n"
"})(), true";
IActiveScript *script;
VARIANT v;
HRESULT hres;
......@@ -3461,6 +3467,18 @@ static void test_destructors(void)
CHECK_CALLED(testdestrobj);
IActiveScript_Release(script);
V_VT(&v) = VT_EMPTY;
hres = parse_script_expr(cyclic_refs, &v, &script);
ok(hres == S_OK, "parse_script_expr failed: %08lx\n", hres);
ok(V_VT(&v) == VT_BOOL, "V_VT(v) = %d\n", V_VT(&v));
SET_EXPECT(testdestrobj);
hres = IActiveScript_SetScriptState(script, SCRIPTSTATE_UNINITIALIZED);
ok(hres == S_OK, "SetScriptState(SCRIPTSTATE_UNINITIALIZED) failed: %08lx\n", hres);
CHECK_CALLED(testdestrobj);
IActiveScript_Release(script);
}
static void test_eval(void)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment