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) ...@@ -434,12 +434,40 @@ static void scope_destructor(jsdisp_t *dispex)
free(scope); 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 = { static const builtin_info_t scope_info = {
JSCLASS_NONE, JSCLASS_NONE,
NULL, NULL,
0, 0,
NULL, NULL,
scope_destructor, 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) 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) ...@@ -88,6 +88,11 @@ static void Enumerator_destructor(jsdisp_t *dispex)
free(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, static HRESULT Enumerator_atEnd(script_ctx_t *ctx, jsval_t vthis, WORD flags, unsigned argc, jsval_t *argv,
jsval_t *r) jsval_t *r)
{ {
...@@ -189,7 +194,11 @@ static const builtin_info_t EnumeratorInst_info = { ...@@ -189,7 +194,11 @@ static const builtin_info_t EnumeratorInst_info = {
0, 0,
NULL, NULL,
Enumerator_destructor, Enumerator_destructor,
NULL NULL,
NULL,
NULL,
NULL,
Enumerator_gc_traverse
}; };
static HRESULT alloc_enumerator(script_ctx_t *ctx, jsdisp_t *object_prototype, EnumeratorInstance **ret) static HRESULT alloc_enumerator(script_ctx_t *ctx, jsdisp_t *object_prototype, EnumeratorInstance **ret)
......
...@@ -39,6 +39,7 @@ struct _function_vtbl_t { ...@@ -39,6 +39,7 @@ struct _function_vtbl_t {
HRESULT (*toString)(FunctionInstance*,jsstr_t**); HRESULT (*toString)(FunctionInstance*,jsstr_t**);
function_code_t* (*get_code)(FunctionInstance*); function_code_t* (*get_code)(FunctionInstance*);
void (*destructor)(FunctionInstance*); void (*destructor)(FunctionInstance*);
HRESULT (*gc_traverse)(struct gc_ctx*,enum gc_traverse_op,FunctionInstance*);
}; };
typedef struct { typedef struct {
...@@ -72,6 +73,11 @@ 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 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) static inline FunctionInstance *function_from_jsdisp(jsdisp_t *jsdisp)
{ {
return CONTAINING_RECORD(jsdisp, FunctionInstance, dispex); return CONTAINING_RECORD(jsdisp, FunctionInstance, dispex);
...@@ -108,7 +114,8 @@ static void Arguments_destructor(jsdisp_t *jsdisp) ...@@ -108,7 +114,8 @@ static void Arguments_destructor(jsdisp_t *jsdisp)
free(arguments->buf); free(arguments->buf);
} }
jsdisp_release(&arguments->function->function.dispex); if(arguments->function)
jsdisp_release(&arguments->function->function.dispex);
free(arguments); free(arguments);
} }
...@@ -166,6 +173,23 @@ static HRESULT Arguments_idx_put(jsdisp_t *jsdisp, unsigned idx, jsval_t val) ...@@ -166,6 +173,23 @@ static HRESULT Arguments_idx_put(jsdisp_t *jsdisp, unsigned idx, jsval_t val)
arguments->function->func_code->params[idx], 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 = { static const builtin_info_t Arguments_info = {
JSCLASS_ARGUMENTS, JSCLASS_ARGUMENTS,
Arguments_value, Arguments_value,
...@@ -174,7 +198,8 @@ static const builtin_info_t Arguments_info = { ...@@ -174,7 +198,8 @@ static const builtin_info_t Arguments_info = {
NULL, NULL,
Arguments_idx_length, Arguments_idx_length,
Arguments_idx_get, Arguments_idx_get,
Arguments_idx_put Arguments_idx_put,
Arguments_gc_traverse
}; };
HRESULT setup_arguments_object(script_ctx_t *ctx, call_frame_t *frame) HRESULT setup_arguments_object(script_ctx_t *ctx, call_frame_t *frame)
...@@ -548,6 +573,12 @@ static void Function_destructor(jsdisp_t *dispex) ...@@ -548,6 +573,12 @@ static void Function_destructor(jsdisp_t *dispex)
free(function); 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[] = { static const builtin_prop_t Function_props[] = {
{L"apply", Function_apply, PROPF_METHOD|2}, {L"apply", Function_apply, PROPF_METHOD|2},
{L"arguments", NULL, 0, Function_get_arguments}, {L"arguments", NULL, 0, Function_get_arguments},
...@@ -563,7 +594,11 @@ static const builtin_info_t Function_info = { ...@@ -563,7 +594,11 @@ static const builtin_info_t Function_info = {
ARRAY_SIZE(Function_props), ARRAY_SIZE(Function_props),
Function_props, Function_props,
Function_destructor, Function_destructor,
NULL NULL,
NULL,
NULL,
NULL,
Function_gc_traverse
}; };
static const builtin_prop_t FunctionInst_props[] = { static const builtin_prop_t FunctionInst_props[] = {
...@@ -577,7 +612,11 @@ static const builtin_info_t FunctionInst_info = { ...@@ -577,7 +612,11 @@ static const builtin_info_t FunctionInst_info = {
ARRAY_SIZE(FunctionInst_props), ARRAY_SIZE(FunctionInst_props),
FunctionInst_props, FunctionInst_props,
Function_destructor, 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, 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 = { ...@@ -657,7 +696,8 @@ static const function_vtbl_t NativeFunctionVtbl = {
NativeFunction_call, NativeFunction_call,
NativeFunction_toString, NativeFunction_toString,
NativeFunction_get_code, 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, 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) ...@@ -776,11 +816,22 @@ static void InterpretedFunction_destructor(FunctionInstance *func)
scope_release(function->scope_chain); 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 = { static const function_vtbl_t InterpretedFunctionVtbl = {
InterpretedFunction_call, InterpretedFunction_call,
InterpretedFunction_toString, InterpretedFunction_toString,
InterpretedFunction_get_code, 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, 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) ...@@ -870,15 +921,36 @@ static void BindFunction_destructor(FunctionInstance *func)
for(i = 0; i < function->argc; i++) for(i = 0; i < function->argc; i++)
jsval_release(function->args[i]); jsval_release(function->args[i]);
jsdisp_release(&function->target->dispex); if(function->target)
jsdisp_release(&function->target->dispex);
jsval_release(function->this); 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 = { static const function_vtbl_t BindFunctionVtbl = {
BindFunction_call, BindFunction_call,
BindFunction_toString, BindFunction_toString,
BindFunction_get_code, 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, 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) ...@@ -495,6 +495,8 @@ static void decrease_state(JScript *This, SCRIPTSTATE state)
} }
script_globals_release(This->ctx); script_globals_release(This->ctx);
gc_run(This->ctx);
/* FALLTHROUGH */ /* FALLTHROUGH */
case SCRIPTSTATE_UNINITIALIZED: case SCRIPTSTATE_UNINITIALIZED:
change_state(This, state); change_state(This, state);
...@@ -734,6 +736,7 @@ static HRESULT WINAPI JScript_SetScriptSite(IActiveScript *iface, ...@@ -734,6 +736,7 @@ static HRESULT WINAPI JScript_SetScriptSite(IActiveScript *iface,
ctx->html_mode = This->html_mode; ctx->html_mode = This->html_mode;
ctx->acc = jsval_undefined(); ctx->acc = jsval_undefined();
list_init(&ctx->named_items); list_init(&ctx->named_items);
list_init(&ctx->objects);
heap_pool_init(&ctx->tmp_heap); heap_pool_init(&ctx->tmp_heap);
hres = create_jscaller(ctx); hres = create_jscaller(ctx);
......
...@@ -137,9 +137,20 @@ typedef struct named_item_t { ...@@ -137,9 +137,20 @@ typedef struct named_item_t {
struct list entry; struct list entry;
} named_item_t; } 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; 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; named_item_t *lookup_named_item(script_ctx_t*,const WCHAR*,unsigned) DECLSPEC_HIDDEN;
void release_named_item(named_item_t*) 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 { typedef struct {
const WCHAR *name; const WCHAR *name;
...@@ -159,6 +170,7 @@ typedef struct { ...@@ -159,6 +170,7 @@ typedef struct {
unsigned (*idx_length)(jsdisp_t*); unsigned (*idx_length)(jsdisp_t*);
HRESULT (*idx_get)(jsdisp_t*,unsigned,jsval_t*); HRESULT (*idx_get)(jsdisp_t*,unsigned,jsval_t*);
HRESULT (*idx_put)(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; } builtin_info_t;
struct jsdisp_t { struct jsdisp_t {
...@@ -166,15 +178,18 @@ struct jsdisp_t { ...@@ -166,15 +178,18 @@ struct jsdisp_t {
LONG ref; LONG ref;
BOOLEAN extensible;
BOOLEAN gc_marked;
DWORD buf_size; DWORD buf_size;
DWORD prop_cnt; DWORD prop_cnt;
dispex_prop_t *props; dispex_prop_t *props;
script_ctx_t *ctx; script_ctx_t *ctx;
BOOL extensible;
jsdisp_t *prototype; jsdisp_t *prototype;
const builtin_info_t *builtin_info; const builtin_info_t *builtin_info;
struct list entry;
}; };
static inline IDispatch *to_disp(jsdisp_t *jsdisp) static inline IDispatch *to_disp(jsdisp_t *jsdisp)
...@@ -350,6 +365,7 @@ struct _script_ctx_t { ...@@ -350,6 +365,7 @@ struct _script_ctx_t {
struct _call_frame_t *call_ctx; struct _call_frame_t *call_ctx;
struct list named_items; struct list named_items;
struct list objects;
IActiveScriptSite *site; IActiveScriptSite *site;
IInternetHostSecurityManager *secmgr; IInternetHostSecurityManager *secmgr;
DWORD safeopt; DWORD safeopt;
...@@ -362,6 +378,8 @@ struct _script_ctx_t { ...@@ -362,6 +378,8 @@ struct _script_ctx_t {
heap_pool_t tmp_heap; heap_pool_t tmp_heap;
BOOL gc_is_unlinking;
jsval_t *stack; jsval_t *stack;
unsigned stack_top; unsigned stack_top;
jsval_t acc; jsval_t acc;
......
...@@ -548,6 +548,11 @@ static void RegExp_destructor(jsdisp_t *dispex) ...@@ -548,6 +548,11 @@ static void RegExp_destructor(jsdisp_t *dispex)
free(This); 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[] = { static const builtin_prop_t RegExp_props[] = {
{L"exec", RegExp_exec, PROPF_METHOD|1}, {L"exec", RegExp_exec, PROPF_METHOD|1},
{L"global", NULL,0, RegExp_get_global}, {L"global", NULL,0, RegExp_get_global},
...@@ -565,7 +570,11 @@ static const builtin_info_t RegExp_info = { ...@@ -565,7 +570,11 @@ static const builtin_info_t RegExp_info = {
ARRAY_SIZE(RegExp_props), ARRAY_SIZE(RegExp_props),
RegExp_props, RegExp_props,
RegExp_destructor, RegExp_destructor,
NULL NULL,
NULL,
NULL,
NULL,
RegExp_gc_traverse
}; };
static const builtin_prop_t RegExpInst_props[] = { static const builtin_prop_t RegExpInst_props[] = {
...@@ -582,7 +591,11 @@ static const builtin_info_t RegExpInst_info = { ...@@ -582,7 +591,11 @@ static const builtin_info_t RegExpInst_info = {
ARRAY_SIZE(RegExpInst_props), ARRAY_SIZE(RegExpInst_props),
RegExpInst_props, RegExpInst_props,
RegExp_destructor, 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) 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) ...@@ -362,6 +362,31 @@ static void Map_destructor(jsdisp_t *dispex)
free(map); 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[] = { static const builtin_prop_t Map_prototype_props[] = {
{L"clear", Map_clear, PROPF_METHOD}, {L"clear", Map_clear, PROPF_METHOD},
{L"delete" , Map_delete, PROPF_METHOD|1}, {L"delete" , Map_delete, PROPF_METHOD|1},
...@@ -390,7 +415,11 @@ static const builtin_info_t Map_info = { ...@@ -390,7 +415,11 @@ static const builtin_info_t Map_info = {
ARRAY_SIZE(Map_props), ARRAY_SIZE(Map_props),
Map_props, Map_props,
Map_destructor, 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, 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 = { ...@@ -545,7 +574,11 @@ static const builtin_info_t Set_info = {
ARRAY_SIZE(Map_props), ARRAY_SIZE(Map_props),
Map_props, Map_props,
Map_destructor, 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, 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) ...@@ -3446,6 +3446,12 @@ static void test_invokeex(void)
static void test_destructors(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; IActiveScript *script;
VARIANT v; VARIANT v;
HRESULT hres; HRESULT hres;
...@@ -3461,6 +3467,18 @@ static void test_destructors(void) ...@@ -3461,6 +3467,18 @@ static void test_destructors(void)
CHECK_CALLED(testdestrobj); CHECK_CALLED(testdestrobj);
IActiveScript_Release(script); 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) 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