mirror of
git://source.winehq.org/git/wine.git
synced 2024-10-06 08:39:34 +00:00
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: Gabriel Ivăncescu <gabrielopcode@gmail.com>
This commit is contained in:
parent
b0db79d769
commit
e8ee6ddae8
|
@ -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, ®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)
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in a new issue