From: Gabriel Ivăncescu gabrielopcode@gmail.com
Implement a basic GC based on the mark-and-sweep algorithm, without requiring manually specifying "roots", which vastly simplifies the code.
Signed-off-by: Gabriel Ivăncescu gabrielopcode@gmail.com --- dlls/jscript/dispex.c | 324 ++++++++++++++++++++++++++++++++++++++ dlls/jscript/engine.c | 28 ++++ dlls/jscript/enumerator.c | 11 +- dlls/jscript/function.c | 88 ++++++++++- dlls/jscript/jscript.c | 3 + dlls/jscript/jscript.h | 20 ++- dlls/jscript/jsregexp.c | 17 +- dlls/jscript/set.c | 37 ++++- dlls/jscript/tests/run.c | 18 +++ 9 files changed, 532 insertions(+), 14 deletions(-)
diff --git a/dlls/jscript/dispex.c b/dlls/jscript/dispex.c index 1a7a75875df..bc6c18bd7ec 100644 --- a/dlls/jscript/dispex.c +++ b/dlls/jscript/dispex.c @@ -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++) { diff --git a/dlls/jscript/engine.c b/dlls/jscript/engine.c index bb43df3d4f2..28ada7e53bd 100644 --- a/dlls/jscript/engine.c +++ b/dlls/jscript/engine.c @@ -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) diff --git a/dlls/jscript/enumerator.c b/dlls/jscript/enumerator.c index cce2f2d8ec0..d724d0685c9 100644 --- a/dlls/jscript/enumerator.c +++ b/dlls/jscript/enumerator.c @@ -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) diff --git a/dlls/jscript/function.c b/dlls/jscript/function.c index 8d2b3ed9f4f..8aa20b44876 100644 --- a/dlls/jscript/function.c +++ b/dlls/jscript/function.c @@ -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, diff --git a/dlls/jscript/jscript.c b/dlls/jscript/jscript.c index b020f24beb3..f5331a13b96 100644 --- a/dlls/jscript/jscript.c +++ b/dlls/jscript/jscript.c @@ -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); diff --git a/dlls/jscript/jscript.h b/dlls/jscript/jscript.h index e1a3da04097..66a4258306b 100644 --- a/dlls/jscript/jscript.h +++ b/dlls/jscript/jscript.h @@ -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; diff --git a/dlls/jscript/jsregexp.c b/dlls/jscript/jsregexp.c index 1cbc828ce09..3775df823c3 100644 --- a/dlls/jscript/jsregexp.c +++ b/dlls/jscript/jsregexp.c @@ -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, ®exp_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) diff --git a/dlls/jscript/set.c b/dlls/jscript/set.c index 535ecdc49a4..eca26a890f7 100644 --- a/dlls/jscript/set.c +++ b/dlls/jscript/set.c @@ -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, diff --git a/dlls/jscript/tests/run.c b/dlls/jscript/tests/run.c index f40ffb3a84e..7955f345281 100644 --- a/dlls/jscript/tests/run.c +++ b/dlls/jscript/tests/run.c @@ -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)