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
......@@ -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