From: Gabriel Ivăncescu <gabrielopcode@gmail.com> This makes the jscript GC aware of external object edges by using the CC participant callback APIs, and then figure out cycles with the jscript objects. Note that our GC will never unlink external objects, this is purely for knowing which jscript objects are garbage and need to be cycle collected, and for that it needs an accurate representation of all the edges in the graph (including those from external objects). To do this, we are using the CC participant API of Gecko just like its own Cycle Collector does. We assign a map of all the pure/raw external objects we find and count their speculative refcounts there; same logic as used in the normal GC including the speculative traversal. This is to know which of the objects need to be unmarked from garbage collection (if their refcount is not zero after doing the entire speculative traversal) by flooding all the paths reachable from them. For jscript "host" objects (those that have both a host/external object and a jsdisp), we do traverse the host object's edges on top of the jsdisp edges as normal, but use their own refcount there (from the jsdisp) and do NOT assign them to the map. This is done in the "canonicalize" API so we get the jsdisp itself. For this to work the gc stack chunk is now able to store either a jsdisp pushed on it, or an external object; for the latter, we actually store a pointer to the entry in the map, since it needs both the raw object pointer and the participant (and it wouldn't fit on the stack's chunk). When popping, we determine whether it's a jsdisp or an external object and traverse it appropriately, but other than that, it's mostly the same GC logic as before, just with the ability to traverse external objects. Unlinking in principle is not changed because we only unlink jscript objects, but we do extend the unlink itself so it actually unlinks the whole jscript object (including the external host object). Signed-off-by: Gabriel Ivăncescu <gabrielopcode@gmail.com> --- dlls/jscript/dispex.c | 355 +++++++++++++++++++++++++++++------ dlls/jscript/function.c | 9 +- dlls/jscript/jscript.c | 7 + dlls/jscript/jscript.h | 2 + dlls/jscript/jscript_main.c | 11 ++ dlls/jscript/jsdisp.idl | 29 +++ dlls/mshtml/dispex.c | 141 ++++++++++++++ dlls/mshtml/htmlwindow.c | 31 +++ dlls/mshtml/mshtml_private.h | 8 + dlls/mshtml/nsiface.idl | 42 +++++ dlls/mshtml/script.c | 36 ++-- 11 files changed, 599 insertions(+), 72 deletions(-) diff --git a/dlls/jscript/dispex.c b/dlls/jscript/dispex.c index 75bbb2378d9..67bde2f78fe 100644 --- a/dlls/jscript/dispex.c +++ b/dlls/jscript/dispex.c @@ -21,6 +21,7 @@ #include "jscript.h" #include "engine.h" +#include "wine/rbtree.h" #include "wine/debug.h" WINE_DEFAULT_DEBUG_CHANNEL(jscript); @@ -861,17 +862,37 @@ static void unlink_jsdisp(jsdisp_t *jsdisp) * */ struct gc_stack_chunk { - jsdisp_t *objects[1020]; + void *objects[1020]; struct gc_stack_chunk *prev; }; +struct cc_map_entry { + struct rb_entry entry; + ULONG ref; + struct cc_native_obj obj; +}; + struct gc_ctx { + const struct cc_participant_api *cc_participant_api; struct gc_stack_chunk *chunk; struct gc_stack_chunk *next; unsigned idx; + struct cc_traverse_callback cc_cb; + struct cc_map_entry *cc_map_entry; + struct rb_tree cc_map; }; -static HRESULT gc_stack_push(struct gc_ctx *gc_ctx, jsdisp_t *obj) +static inline struct cc_map_entry *cc_map_entry_from_stack_obj(void *obj) +{ + return CONTAINING_RECORD(obj, struct cc_map_entry, obj.obj); +} + +static inline void *cc_map_entry_to_stack_obj(struct cc_map_entry *entry) +{ + return &entry->obj.obj; +} + +static HRESULT gc_stack_push_impl(struct gc_ctx *gc_ctx, void *obj) { if(!gc_ctx->idx) { if(gc_ctx->next) @@ -891,9 +912,24 @@ static HRESULT gc_stack_push(struct gc_ctx *gc_ctx, jsdisp_t *obj) return S_OK; } -static jsdisp_t *gc_stack_pop(struct gc_ctx *gc_ctx) +static inline HRESULT gc_stack_init(struct gc_ctx *gc_ctx) { - jsdisp_t *obj = gc_ctx->chunk->objects[gc_ctx->idx]; + return gc_stack_push_impl(gc_ctx, NULL); +} + +static inline HRESULT gc_stack_push(struct gc_ctx *gc_ctx, jsdisp_t *obj) +{ + return gc_stack_push_impl(gc_ctx, &obj->IWineJSDispatch_iface); +} + +static inline HRESULT gc_stack_push_cc_obj(struct gc_ctx *gc_ctx, struct cc_map_entry *entry) +{ + return gc_stack_push_impl(gc_ctx, cc_map_entry_to_stack_obj(entry)); +} + +static void *gc_stack_pop(struct gc_ctx *gc_ctx) +{ + void *obj = gc_ctx->chunk->objects[gc_ctx->idx]; if(++gc_ctx->idx == ARRAY_SIZE(gc_ctx->chunk->objects)) { free(gc_ctx->next); @@ -904,6 +940,138 @@ static jsdisp_t *gc_stack_pop(struct gc_ctx *gc_ctx) return obj; } +static inline struct gc_ctx *impl_from_cc_traverse_callback(struct cc_traverse_callback *cb) +{ + return CONTAINING_RECORD(cb, struct gc_ctx, cc_cb); +} + +static HRESULT WINAPI cc_speculative_cb_AdviseRefCount(struct cc_traverse_callback *This, ULONG refcount) +{ + struct gc_ctx *gc_ctx = impl_from_cc_traverse_callback(This); + + /* Add to the existing accumulated (negative) refcount of edges we've already processed */ + if(gc_ctx->cc_map_entry) { + gc_ctx->cc_map_entry->ref += refcount; + gc_ctx->cc_map_entry = NULL; + } + return S_OK; +} + +static HRESULT WINAPI cc_speculative_cb_NoteRawEdge(struct cc_traverse_callback *This, struct cc_native_obj obj) +{ + struct gc_ctx *gc_ctx = impl_from_cc_traverse_callback(This); + struct rb_entry *rb_entry = rb_get(&gc_ctx->cc_map, obj.obj); + struct cc_map_entry *entry; + + if(rb_entry) { + /* Skip traversal if we already visited or will visit it, but do process the edge's ref */ + WINE_RB_ENTRY_VALUE(rb_entry, struct cc_map_entry, entry)->ref--; + return S_OK; + } + + if(!(entry = malloc(sizeof(*entry)))) + return E_OUTOFMEMORY; + entry->obj = obj; + entry->ref = -1; /* account for the edge's ref */ + rb_put(&gc_ctx->cc_map, obj.obj, &entry->entry); + return gc_stack_push_cc_obj(gc_ctx, entry); +} + +static HRESULT WINAPI cc_speculative_cb_NoteEdge(struct cc_traverse_callback *This, IUnknown *obj) +{ + struct gc_ctx *gc_ctx = impl_from_cc_traverse_callback(This); + struct cc_native_obj cc_obj = gc_ctx->cc_participant_api->canonicalize(obj); + jsdisp_t *jsdisp = to_jsdisp(cc_obj.obj); + + /* If it's one of our own jscript objects, process the ref and don't traverse it further */ + if(jsdisp) { + jsdisp->ref--; + return S_OK; + } + + return cc_obj.participant ? cc_speculative_cb_NoteRawEdge(&gc_ctx->cc_cb, cc_obj) : S_OK; +} + +static const struct cc_traverse_callback_vtbl cc_speculative_cb_vtbl = { + cc_speculative_cb_AdviseRefCount, + cc_speculative_cb_NoteEdge, + cc_speculative_cb_NoteRawEdge, +}; + +static HRESULT WINAPI cc_unmark_cb_AdviseRefCount(struct cc_traverse_callback *This, ULONG refcount) +{ + return S_OK; +} + +static HRESULT WINAPI cc_unmark_cb_NoteRawEdge(struct cc_traverse_callback *This, struct cc_native_obj obj) +{ + struct gc_ctx *gc_ctx = impl_from_cc_traverse_callback(This); + struct rb_entry *rb_entry = rb_get(&gc_ctx->cc_map, obj.obj); + struct cc_map_entry *entry; + + assert(rb_entry != NULL); + entry = WINE_RB_ENTRY_VALUE(rb_entry, struct cc_map_entry, entry); + + /* Check participant in the map entry itself, as we use it to mark those we've already visited. */ + return entry->obj.participant ? gc_stack_push_cc_obj(gc_ctx, WINE_RB_ENTRY_VALUE(rb_entry, struct cc_map_entry, entry)) : S_OK; +} + +static HRESULT WINAPI cc_unmark_cb_NoteEdge(struct cc_traverse_callback *This, IUnknown *obj) +{ + struct gc_ctx *gc_ctx = impl_from_cc_traverse_callback(This); + struct cc_native_obj cc_obj = gc_ctx->cc_participant_api->canonicalize(obj); + jsdisp_t *jsdisp = to_jsdisp(cc_obj.obj); + + if(jsdisp) { + /* The temporary refcount itself doesn't matter by this point, just make sure it's not 0, + * as this will unmark it and all accessible objects from it later, since they're alive. */ + jsdisp->ref = 1; + return S_OK; + } + + return cc_obj.participant ? cc_unmark_cb_NoteRawEdge(&gc_ctx->cc_cb, cc_obj) : S_OK; +} + +static const struct cc_traverse_callback_vtbl cc_unmark_cb_vtbl = { + cc_unmark_cb_AdviseRefCount, + cc_unmark_cb_NoteEdge, + cc_unmark_cb_NoteRawEdge, +}; + +static HRESULT WINAPI cc_unmark2_cb_NoteEdge(struct cc_traverse_callback *This, IUnknown *obj) +{ + struct gc_ctx *gc_ctx = impl_from_cc_traverse_callback(This); + struct cc_native_obj cc_obj = gc_ctx->cc_participant_api->canonicalize(obj); + jsdisp_t *jsdisp = to_jsdisp(cc_obj.obj); + + if(jsdisp) + return jsdisp->gc_marked ? gc_stack_push(gc_ctx, jsdisp) : S_OK; + + return cc_obj.participant ? cc_unmark_cb_NoteRawEdge(&gc_ctx->cc_cb, cc_obj) : S_OK; +} + +static const struct cc_traverse_callback_vtbl cc_unmark2_cb_vtbl = { + cc_unmark_cb_AdviseRefCount, + cc_unmark2_cb_NoteEdge, + cc_unmark_cb_NoteRawEdge, +}; + +static inline HRESULT process_edge_speculatively(struct gc_ctx *gc_ctx, IDispatch *edge) +{ + return cc_speculative_cb_NoteEdge(&gc_ctx->cc_cb, (IUnknown*)edge); +} + +static inline HRESULT unmark_edge(struct gc_ctx *gc_ctx, IDispatch *edge) +{ + return cc_unmark2_cb_NoteEdge(&gc_ctx->cc_cb, (IUnknown*)edge); +} + +static int cc_map_compare(const void *k, const struct rb_entry *e) +{ + ULONG_PTR a = (ULONG_PTR)k, b = (ULONG_PTR)RB_ENTRY_VALUE(e, struct cc_map_entry, entry)->obj.obj; + return (a > b) - (a < b); +} + HRESULT gc_run(script_ctx_t *ctx) { /* Save original refcounts in a linked list of chunks */ @@ -913,16 +1081,25 @@ HRESULT gc_run(script_ctx_t *ctx) LONG ref[1020]; } *head, *chunk; struct thread_data *thread_data = ctx->thread_data; - jsdisp_t *obj, *obj2, *link, *link2; + struct cc_map_entry *cc_map_iter, *cc_map_iter2; dispex_prop_t *prop, *props_end; - struct gc_ctx gc_ctx = { 0 }; unsigned chunk_idx = 0; + jsdisp_t *obj, *obj2; + struct gc_ctx gc_ctx; HRESULT hres = S_OK; struct list *iter; + void *stack_obj; /* Prevent recursive calls from side-effects during unlinking (e.g. CollectGarbage from host object's Release) */ if(thread_data->gc_is_unlinking) return S_OK; + gc_ctx.next = NULL; + gc_ctx.idx = 0; + + /* We also scan for non-jscript (host) objects reachable from any jscript object, and store a map of them with their refs */ + gc_ctx.cc_participant_api = thread_data->cc_participant_api; + gc_ctx.cc_cb.vtbl = &cc_speculative_cb_vtbl; + rb_init(&gc_ctx.cc_map, cc_map_compare); if(!(head = malloc(sizeof(*head)))) return E_OUTOFMEMORY; @@ -944,18 +1121,24 @@ HRESULT gc_run(script_ctx_t *ctx) chunk->next = NULL; } chunk->ref[chunk_idx++] = obj->ref; + + /* Obtain actual refcount from host objects with external reference counter */ + if(obj->builtin_info->get_host_disp) + obj->ref = IWineJSDispatchHost_GetRefCount(obj->builtin_info->get_host_disp(obj)); } LIST_FOR_EACH_ENTRY(obj, &thread_data->objects, jsdisp_t, entry) { - /* Skip objects with external reference counter */ - if(obj->builtin_info->get_host_disp) { - obj->gc_marked = FALSE; - continue; - } + hres = gc_stack_init(&gc_ctx); + if(FAILED(hres)) + goto fail_init; + 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->ref--; + if(!is_object_instance(prop->u.val)) + break; + hres = process_edge_speculatively(&gc_ctx, get_object(prop->u.val)); + if(FAILED(hres)) + goto fail; break; case PROP_ACCESSOR: if(prop->u.accessor.getter) @@ -972,21 +1155,72 @@ HRESULT gc_run(script_ctx_t *ctx) obj->prototype->ref--; if(obj->builtin_info->gc_traverse) obj->builtin_info->gc_traverse(&gc_ctx, GC_TRAVERSE_SPECULATIVELY, obj); + + for(;;) { + stack_obj = gc_stack_pop(&gc_ctx); + if(!stack_obj) + break; + gc_ctx.cc_map_entry = cc_map_entry_from_stack_obj(stack_obj); + hres = gc_ctx.cc_participant_api->traverse(gc_ctx.cc_map_entry->obj, &gc_ctx.cc_cb); + if(FAILED(hres)) + goto fail; + } + obj->gc_marked = TRUE; } /* 2. Clear mark on objects with non-zero "external refcount" and all objects accessible from them */ + gc_ctx.cc_cb.vtbl = &cc_unmark_cb_vtbl; + RB_FOR_EACH_ENTRY(cc_map_iter, &gc_ctx.cc_map, struct cc_map_entry, entry) { + if(!cc_map_iter->ref || !cc_map_iter->obj.participant) + continue; + + hres = gc_stack_init(&gc_ctx); + if(FAILED(hres)) + goto fail_init; + + /* Traverse and flood all accessible objects from the native objects in the cc map, + * marking already-visited native objects in the map using a NULL participant. */ + stack_obj = cc_map_entry_to_stack_obj(cc_map_iter); + do { + struct cc_native_obj cc_obj = cc_map_entry_from_stack_obj(stack_obj)->obj; + + if(cc_obj.participant) { + cc_map_entry_from_stack_obj(stack_obj)->obj.participant = NULL; + hres = gc_ctx.cc_participant_api->traverse(cc_obj, &gc_ctx.cc_cb); + if(FAILED(hres)) + goto fail; + } + stack_obj = gc_stack_pop(&gc_ctx); + } while(stack_obj); + } + + gc_ctx.cc_cb.vtbl = &cc_unmark2_cb_vtbl; LIST_FOR_EACH_ENTRY(obj, &thread_data->objects, jsdisp_t, entry) { if(!obj->ref || !obj->gc_marked) continue; - hres = gc_stack_push(&gc_ctx, NULL); + hres = gc_stack_init(&gc_ctx); if(FAILED(hres)) - break; + goto fail_init; - obj2 = obj; + stack_obj = &obj->IWineJSDispatch_iface; do { + if(!(obj2 = to_jsdisp(stack_obj))) { + struct cc_native_obj cc_obj = cc_map_entry_from_stack_obj(stack_obj)->obj; + + if(cc_obj.participant) { + cc_map_entry_from_stack_obj(stack_obj)->obj.participant = NULL; + hres = gc_ctx.cc_participant_api->traverse(cc_obj, &gc_ctx.cc_cb); + if(FAILED(hres)) + goto fail; + } + continue; + } + + if(!obj2->gc_marked) + continue; obj2->gc_marked = FALSE; for(prop = obj2->props, props_end = prop + obj2->prop_cnt; prop < props_end; prop++) { @@ -994,41 +1228,37 @@ HRESULT gc_run(script_ctx_t *ctx) case PROP_JSVAL: if(!is_object_instance(prop->u.val)) continue; - link = to_jsdisp(get_object(prop->u.val)); - link2 = NULL; + hres = unmark_edge(&gc_ctx, get_object(prop->u.val)); + if(FAILED(hres)) + goto fail; break; case PROP_ACCESSOR: - link = prop->u.accessor.getter; - link2 = prop->u.accessor.setter; + if(prop->u.accessor.getter && prop->u.accessor.getter->gc_marked) { + hres = gc_stack_push(&gc_ctx, prop->u.accessor.getter); + if(FAILED(hres)) + goto fail; + } + if(prop->u.accessor.setter && prop->u.accessor.setter->gc_marked) { + hres = gc_stack_push(&gc_ctx, prop->u.accessor.setter); + if(FAILED(hres)) + goto fail; + } break; default: continue; } - if(link && link->gc_marked) { - hres = gc_stack_push(&gc_ctx, link); - if(FAILED(hres)) - break; - } - if(link2 && link2->gc_marked) { - hres = gc_stack_push(&gc_ctx, link2); - if(FAILED(hres)) - break; - } } - if(FAILED(hres)) - break; - if(obj2->prototype && obj2->prototype->gc_marked) { hres = gc_stack_push(&gc_ctx, obj2->prototype); if(FAILED(hres)) - break; + goto fail; } if(obj2->builtin_info->gc_traverse) { hres = obj2->builtin_info->gc_traverse(&gc_ctx, GC_TRAVERSE, obj2); if(FAILED(hres)) - break; + goto fail; } /* For weak refs, traverse paths accessible from it via the WeakMaps, if the WeakMaps are alive at this point. @@ -1038,27 +1268,25 @@ HRESULT gc_run(script_ctx_t *ctx) struct weakmap_entry *entry; LIST_FOR_EACH_ENTRY(entry, list, struct weakmap_entry, weak_refs_entry) { - if(!entry->weakmap->gc_marked && is_object_instance(entry->value) && (link = to_jsdisp(get_object(entry->value)))) { - hres = gc_stack_push(&gc_ctx, link); + if(!entry->weakmap->gc_marked && is_object_instance(entry->value)) { + hres = unmark_edge(&gc_ctx, get_object(entry->value)); if(FAILED(hres)) - break; + goto fail; } } - - 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; - } + } while((stack_obj = gc_stack_pop(&gc_ctx))); } + +fail: + if(FAILED(hres)) + do stack_obj = gc_stack_pop(&gc_ctx); while(stack_obj); free(gc_ctx.next); +fail_init: + RB_FOR_EACH_ENTRY_DESTRUCTOR(cc_map_iter, cc_map_iter2, &gc_ctx.cc_map, struct cc_map_entry, entry) + free(cc_map_iter); + /* Restore */ chunk = head; chunk_idx = 0; LIST_FOR_EACH_ENTRY(obj, &thread_data->objects, jsdisp_t, entry) { @@ -1117,8 +1345,6 @@ HRESULT gc_process_linked_obj(struct gc_ctx *gc_ctx, enum gc_traverse_op op, jsd HRESULT gc_process_linked_val(struct gc_ctx *gc_ctx, enum gc_traverse_op op, jsval_t *link) { - jsdisp_t *jsdisp; - if(op == GC_TRAVERSE_UNLINK) { jsval_t val = *link; *link = jsval_undefined(); @@ -1126,13 +1352,22 @@ HRESULT gc_process_linked_val(struct gc_ctx *gc_ctx, enum gc_traverse_op op, jsv return S_OK; } - if(!is_object_instance(*link) || !(jsdisp = to_jsdisp(get_object(*link)))) + if(!is_object_instance(*link)) 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; + return process_edge_speculatively(gc_ctx, get_object(*link)); + return unmark_edge(gc_ctx, get_object(*link)); +} + +HRESULT host_dispatch_gc_traverse(struct gc_ctx *gc_ctx, enum gc_traverse_op op, IWineJSDispatchHost *host_disp) +{ + if(op == GC_TRAVERSE_UNLINK) { + IWineJSDispatchHost_Unlink(host_disp); + return S_OK; + } + + gc_ctx->cc_map_entry = NULL; /* traverse edges but not the refcount on itself as we didn't arrive here from an edge during speculative traversal */ + return IWineJSDispatchHost_Traverse(host_disp, &gc_ctx->cc_cb); } @@ -3616,6 +3851,13 @@ static HRESULT HostObject_to_string(jsdisp_t *jsdisp, jsstr_t **ret) return *ret ? S_OK : E_OUTOFMEMORY; } +static HRESULT HostObject_gc_traverse(struct gc_ctx *gc_ctx, enum gc_traverse_op op, jsdisp_t *jsdisp) +{ + HostObject *This = HostObject_from_jsdisp(jsdisp); + + return host_dispatch_gc_traverse(gc_ctx, op, This->host_iface); +} + static const builtin_info_t HostObject_info = { .class = JSCLASS_HOST, .get_host_disp = HostObject_get_host_disp, @@ -3626,6 +3868,7 @@ static const builtin_info_t HostObject_info = { .prop_config = HostObject_prop_config, .fill_props = HostObject_fill_props, .to_string = HostObject_to_string, + .gc_traverse = HostObject_gc_traverse }; HRESULT init_host_object(script_ctx_t *ctx, IWineJSDispatchHost *host_iface, IWineJSDispatch *prototype_iface, diff --git a/dlls/jscript/function.c b/dlls/jscript/function.c index c6aa0b2f99a..7363670d6b8 100644 --- a/dlls/jscript/function.c +++ b/dlls/jscript/function.c @@ -1189,12 +1189,19 @@ static void HostConstructor_destructor(FunctionInstance *func) { } +static HRESULT HostConstructor_gc_traverse(struct gc_ctx *gc_ctx, enum gc_traverse_op op, FunctionInstance *func) +{ + HostConstructor *constr = (HostConstructor*)func; + + return host_dispatch_gc_traverse(gc_ctx, op, constr->host_iface); +} + static const function_vtbl_t HostConstructorVtbl = { HostConstructor_call, HostConstructor_toString, HostConstructor_get_code, HostConstructor_destructor, - no_gc_traverse, + HostConstructor_gc_traverse, }; HRESULT init_host_constructor(script_ctx_t *ctx, IWineJSDispatchHost *host_constr, const WCHAR *method_name, IWineJSDispatch **ret) diff --git a/dlls/jscript/jscript.c b/dlls/jscript/jscript.c index 02b157749ce..ee881e015e1 100644 --- a/dlls/jscript/jscript.c +++ b/dlls/jscript/jscript.c @@ -1489,6 +1489,12 @@ static HRESULT WINAPI WineJScript_FillGlobals(IWineJScript *iface, IWineJSDispat return fill_globals(This->ctx, script_global); } +static void WINAPI WineJScript_InitCCParticipantAPI(IWineJScript *iface, const struct cc_participant_api *api) +{ + JScript *This = impl_from_IWineJScript(iface); + This->ctx->thread_data->cc_participant_api = api; +} + static const IWineJScriptVtbl WineJScriptVtbl = { WineJScript_QueryInterface, WineJScript_AddRef, @@ -1498,6 +1504,7 @@ static const IWineJScriptVtbl WineJScriptVtbl = { WineJScript_CreateObject, WineJScript_CreateArrayBuffer, WineJScript_FillGlobals, + WineJScript_InitCCParticipantAPI, }; HRESULT create_jscript_object(BOOL is_encode, REFIID riid, void **ppv) diff --git a/dlls/jscript/jscript.h b/dlls/jscript/jscript.h index 4e464e9d458..4a61c33be9f 100644 --- a/dlls/jscript/jscript.h +++ b/dlls/jscript/jscript.h @@ -134,6 +134,7 @@ struct thread_data { BOOL gc_is_unlinking; DWORD gc_last_tick; + const struct cc_participant_api *cc_participant_api; struct list objects; struct rb_tree weak_refs; @@ -166,6 +167,7 @@ void release_named_item(named_item_t*); HRESULT gc_run(script_ctx_t*); HRESULT gc_process_linked_obj(struct gc_ctx*,enum gc_traverse_op,jsdisp_t*,void**); HRESULT gc_process_linked_val(struct gc_ctx*,enum gc_traverse_op,jsval_t*); +HRESULT host_dispatch_gc_traverse(struct gc_ctx*, enum gc_traverse_op, IWineJSDispatchHost*); typedef struct { const WCHAR *name; diff --git a/dlls/jscript/jscript_main.c b/dlls/jscript/jscript_main.c index 6ccab362051..ea6a1579687 100644 --- a/dlls/jscript/jscript_main.c +++ b/dlls/jscript/jscript_main.c @@ -41,6 +41,16 @@ HINSTANCE jscript_hinstance; static DWORD jscript_tls; static ITypeInfo *dispatch_typeinfo; +static struct cc_native_obj WINAPI cc_participant_api_stub_canonicalize(IUnknown *obj) +{ + struct cc_native_obj cc_obj = { .obj = obj, .participant = NULL }; + return cc_obj; +} + +static const struct cc_participant_api cc_participant_api_stub = { + .canonicalize = cc_participant_api_stub_canonicalize, +}; + static int weak_refs_compare(const void *key, const struct rb_entry *entry) { const struct weak_refs_entry *weak_refs_entry = RB_ENTRY_VALUE(entry, const struct weak_refs_entry, entry); @@ -57,6 +67,7 @@ struct thread_data *get_thread_data(void) if(!thread_data) return NULL; thread_data->thread_id = GetCurrentThreadId(); + thread_data->cc_participant_api = &cc_participant_api_stub; list_init(&thread_data->objects); rb_init(&thread_data->weak_refs, weak_refs_compare); TlsSetValue(jscript_tls, thread_data); diff --git a/dlls/jscript/jsdisp.idl b/dlls/jscript/jsdisp.idl index 4783edbfef5..9384be762d4 100644 --- a/dlls/jscript/jsdisp.idl +++ b/dlls/jscript/jsdisp.idl @@ -43,6 +43,31 @@ const unsigned int HOSTOBJ_CONSTRUCTOR = 0x0001; const unsigned int HOSTOBJ_VOLATILE_FILL = 0x0002; const unsigned int HOSTOBJ_VOLATILE_PROPS = 0x0004; +struct cc_native_obj +{ + void *obj; + void *participant; +}; + +struct cc_traverse_callback; +struct cc_traverse_callback_vtbl +{ + HRESULT (__stdcall *AdviseRefCount)(struct cc_traverse_callback *This, ULONG refcount); + HRESULT (__stdcall *NoteEdge)(struct cc_traverse_callback *This, IUnknown *obj); + HRESULT (__stdcall *NoteRawEdge)(struct cc_traverse_callback *This, struct cc_native_obj obj); +}; + +struct cc_traverse_callback +{ + const struct cc_traverse_callback_vtbl *vtbl; +}; + +struct cc_participant_api +{ + struct cc_native_obj (__stdcall *canonicalize)(IUnknown *obj); + HRESULT (__stdcall *traverse)(struct cc_native_obj obj, struct cc_traverse_callback *callback); +}; + interface IWineJSDispatchHost; [ @@ -67,6 +92,7 @@ interface IWineJSDispatch : IDispatchEx ] interface IWineJSDispatchHost : IDispatchEx { + ULONG GetRefCount(); HRESULT GetJSDispatch(IWineJSDispatch **ret); HRESULT LookupProperty(const WCHAR *name, DWORD flags, struct property_info *desc); HRESULT GetProperty(DISPID id, LCID lcid, VARIANT *r, EXCEPINFO *ei, IServiceProvider *caller); @@ -78,6 +104,8 @@ interface IWineJSDispatchHost : IDispatchEx HRESULT FillProperties(); HRESULT GetOuterDispatch(IWineJSDispatchHost **ret); HRESULT ToString(BSTR *str); + HRESULT Traverse(struct cc_traverse_callback *cb); + void Unlink(); } const unsigned int SCRIPTLANGUAGEVERSION_HTML = 0x400; @@ -97,4 +125,5 @@ interface IWineJScript : IUnknown HRESULT CreateObject(IWineJSDispatch **ret); HRESULT CreateArrayBuffer(DWORD size, IWineJSDispatch **ret, void **data); HRESULT FillGlobals(IWineJSDispatchHost *script_global); + void InitCCParticipantAPI(const struct cc_participant_api *api); } diff --git a/dlls/mshtml/dispex.c b/dlls/mshtml/dispex.c index a6b8afb0543..c58aac4b122 100644 --- a/dlls/mshtml/dispex.c +++ b/dlls/mshtml/dispex.c @@ -138,6 +138,8 @@ PRIVATE_TID_LIST #undef XDIID }; +static nsresult NSAPI dispex_unlink(void*); + static HRESULT load_typelib(void) { WCHAR module_path[MAX_PATH + 3]; @@ -2656,6 +2658,13 @@ static HRESULT WINAPI DispatchEx_GetNameSpaceParent(IWineJSDispatchHost *iface, return E_NOTIMPL; } +static ULONG WINAPI JSDispatchHost_GetRefCount(IWineJSDispatchHost *iface) +{ + DispatchEx *This = impl_from_IWineJSDispatchHost(iface); + + return NS_REFCOUNT_VALUE(This->ccref); +} + static HRESULT WINAPI JSDispatchHost_GetJSDispatch(IWineJSDispatchHost *iface, IWineJSDispatch **ret) { DispatchEx *This = impl_from_IWineJSDispatchHost(iface); @@ -2873,6 +2882,21 @@ static HRESULT WINAPI JSDispatchHost_ToString(IWineJSDispatchHost *iface, BSTR * return dispex_to_string(This, str); } +static HRESULT WINAPI JSDispatchHost_Traverse(IWineJSDispatchHost *iface, struct cc_traverse_callback *cb) +{ + DispatchEx *This = impl_from_IWineJSDispatchHost(iface); + struct cc_native_obj obj = { .obj = &This->IWineJSDispatchHost_iface, .participant = &dispex_ccp }; + + return cc_participant_api.traverse(obj, cb); +} + +static void WINAPI JSDispatchHost_Unlink(IWineJSDispatchHost *iface) +{ + DispatchEx *This = impl_from_IWineJSDispatchHost(iface); + + dispex_unlink(&This->IWineJSDispatchHost_iface); +} + static IWineJSDispatchHostVtbl JSDispatchHostVtbl = { DispatchEx_QueryInterface, DispatchEx_AddRef, @@ -2889,6 +2913,7 @@ static IWineJSDispatchHostVtbl JSDispatchHostVtbl = { DispatchEx_GetMemberName, DispatchEx_GetNextDispID, DispatchEx_GetNameSpaceParent, + JSDispatchHost_GetRefCount, JSDispatchHost_GetJSDispatch, JSDispatchHost_LookupProperty, JSDispatchHost_GetProperty, @@ -2900,6 +2925,122 @@ static IWineJSDispatchHostVtbl JSDispatchHostVtbl = { JSDispatchHost_FillProperties, JSDispatchHost_GetOuterDispatch, JSDispatchHost_ToString, + JSDispatchHost_Traverse, + JSDispatchHost_Unlink +}; + +static DispatchEx *unsafe_impl_from_IWineJSDispatchHost(IWineJSDispatchHost *iface) +{ + return iface->lpVtbl == &JSDispatchHostVtbl ? impl_from_IWineJSDispatchHost(iface) : NULL; +} + +static struct cc_native_obj WINAPI cc_participant_api_canonicalize(IUnknown *obj) +{ + struct cc_native_obj cc_obj = { .obj = obj, .participant = NULL }; + DispatchEx *dispex = NULL; + IUnknown *unk; + + /* These QI do not AddRef, so they are scan-safe */ + if(IUnknown_QueryInterface(obj, &IID_nsCycleCollectionISupports, (void**)&unk) != S_OK) + return cc_obj; + + /* We can't QI here because we can't touch the refcount and that would add a ref, so inspect vtbl directly */ + if(!(dispex = unsafe_impl_from_IWineJSDispatchHost((IWineJSDispatchHost*)unk))) { + HTMLOuterWindow *outer_window = unsafe_HTMLOuterWindow_from_IHTMLWindow2((IHTMLWindow2*)unk); + + if(outer_window && outer_window->base.inner_window) + dispex = &outer_window->base.inner_window->event_target.dispex; + } + + if(dispex && dispex->jsdisp) { + cc_obj.obj = dispex->jsdisp; + return cc_obj; + } + + cc_obj.obj = unk; + IUnknown_QueryInterface(unk, &IID_nsXPCOMCycleCollectionParticipant, (void**)&cc_obj.participant); + return cc_obj; +} + +struct cc_callback_ctx { + struct cc_traverse_callback *callback; + nsCycleCollectionTraversalCallback cb; + HRESULT hres; +}; + +static inline struct cc_callback_ctx *impl_from_nsCycleCollectionTraversalCallback(nsCycleCollectionTraversalCallback *cb) +{ + return CONTAINING_RECORD(cb, struct cc_callback_ctx, cb); +} + +static void NSAPI cc_callback_DescribeRefCountedNode(nsCycleCollectionTraversalCallback *This, nsrefcnt refcount, const char *objName) +{ + struct cc_callback_ctx *ctx = impl_from_nsCycleCollectionTraversalCallback(This); + if(SUCCEEDED(ctx->hres)) + ctx->hres = ctx->callback->vtbl->AdviseRefCount(ctx->callback, refcount); +} + +static void NSAPI cc_callback_DescribeGCedNode(nsCycleCollectionTraversalCallback *This, unsigned char isMarked, const char *objName, UINT64 compartmentAddress) +{ +} + +static void NSAPI cc_callback_NoteXPCOMChild(nsCycleCollectionTraversalCallback *This, nsISupports *child) +{ + struct cc_callback_ctx *ctx = impl_from_nsCycleCollectionTraversalCallback(This); + if(ctx->hres == S_OK && child) + ctx->hres = ctx->callback->vtbl->NoteEdge(ctx->callback, (IUnknown*)child); +} + +static void NSAPI cc_callback_NoteJSObject(nsCycleCollectionTraversalCallback *This, void *child) +{ + /* FIXME: somehow obtain the participant and traverse JS objects */ +} + +static void NSAPI cc_callback_NoteJSScript(nsCycleCollectionTraversalCallback *This, void *child) +{ +} + +static void NSAPI cc_callback_NoteNativeChild(nsCycleCollectionTraversalCallback *This, void *child, nsCycleCollectionParticipant *participant) +{ + struct cc_callback_ctx *ctx = impl_from_nsCycleCollectionTraversalCallback(This); + if(ctx->hres == S_OK && child) { + struct cc_native_obj cc_obj = { .obj = child, .participant = participant }; + ctx->hres = ctx->callback->vtbl->NoteRawEdge(ctx->callback, cc_obj); + } +} + +static void NSAPI cc_callback_NoteNextEdgeName(nsCycleCollectionTraversalCallback *This, const char *name) +{ +} + +static const nsCycleCollectionTraversalCallbackVtbl cc_callback_vtbl = { + cc_callback_DescribeRefCountedNode, + cc_callback_DescribeGCedNode, + cc_callback_NoteXPCOMChild, + cc_callback_NoteJSObject, + cc_callback_NoteJSScript, + cc_callback_NoteNativeChild, + cc_callback_NoteNextEdgeName +}; + +static HRESULT WINAPI cc_participant_api_traverse(struct cc_native_obj obj, struct cc_traverse_callback *callback) +{ + nsCycleCollectionParticipant *participant = obj.participant; + struct cc_callback_ctx ctx; + nsresult nsres; + + ctx.cb.lpVtbl = &cc_callback_vtbl; + ctx.cb.flags = nsCycleCollectionTraversalCallback_WANT_ALL_TRACES; + ctx.callback = callback; + ctx.hres = S_FALSE; + + nsres = participant->lpVtbl->Traverse(participant, obj.obj, &ctx.cb); + return NS_FAILED(nsres) ? map_nsresult(nsres) : ctx.hres; +} + +const struct cc_participant_api cc_participant_api = { + cc_participant_api_canonicalize, + cc_participant_api_traverse, }; struct EnumVARIANT { diff --git a/dlls/mshtml/htmlwindow.c b/dlls/mshtml/htmlwindow.c index 1977c6137ba..c84834b2847 100644 --- a/dlls/mshtml/htmlwindow.c +++ b/dlls/mshtml/htmlwindow.c @@ -47,6 +47,7 @@ WINE_DEFAULT_DEBUG_CHANNEL(mshtml); static ExternalCycleCollectionParticipant outer_window_ccp; +static nsresult NSAPI outer_window_unlink(void*); static int window_map_compare(const void *key, const struct wine_rb_entry *entry) { @@ -1585,6 +1586,11 @@ static const IHTMLWindow2Vtbl outer_window_HTMLWindow2Vtbl = { HTMLWindow2_get_external }; +HTMLOuterWindow *unsafe_HTMLOuterWindow_from_IHTMLWindow2(IHTMLWindow2 *iface) +{ + return iface->lpVtbl == &outer_window_HTMLWindow2Vtbl ? HTMLOuterWindow_from_IHTMLWindow2(iface) : NULL; +} + static inline HTMLWindow *impl_from_IHTMLWindow3(IHTMLWindow3 *iface) { return CONTAINING_RECORD(iface, HTMLWindow, IHTMLWindow3_iface); @@ -3462,6 +3468,13 @@ static HRESULT WINAPI WindowDispEx_GetNameSpaceParent(IWineJSDispatchHost *iface return S_OK; } +static ULONG WINAPI WindowDispEx_GetRefCount(IWineJSDispatchHost *iface) +{ + HTMLOuterWindow *This = impl_from_IWineJSDispatchHost(iface); + + return NS_REFCOUNT_VALUE(This->ccref); +} + static HRESULT WINAPI WindowDispEx_GetJSDispatch(IWineJSDispatchHost *iface, IWineJSDispatch **ret) { HTMLOuterWindow *This = impl_from_IWineJSDispatchHost(iface); @@ -3551,6 +3564,21 @@ static HRESULT WINAPI WindowDispEx_ToString(IWineJSDispatchHost *iface, BSTR *st return IWineJSDispatchHost_ToString(&This->base.inner_window->event_target.dispex.IWineJSDispatchHost_iface, str); } +static HRESULT WINAPI WindowDispEx_Traverse(IWineJSDispatchHost *iface, struct cc_traverse_callback *cb) +{ + HTMLOuterWindow *This = impl_from_IWineJSDispatchHost(iface); + struct cc_native_obj obj = { .obj = &This->base.IHTMLWindow2_iface, .participant = &outer_window_ccp }; + + return cc_participant_api.traverse(obj, cb); +} + +static void WINAPI WindowDispEx_Unlink(IWineJSDispatchHost *iface) +{ + HTMLOuterWindow *This = impl_from_IWineJSDispatchHost(iface); + + outer_window_unlink(&This->base.IHTMLWindow2_iface); +} + static const IWineJSDispatchHostVtbl WindowDispExVtbl = { WindowDispEx_QueryInterface, WindowDispEx_AddRef, @@ -3567,6 +3595,7 @@ static const IWineJSDispatchHostVtbl WindowDispExVtbl = { WindowDispEx_GetMemberName, WindowDispEx_GetNextDispID, WindowDispEx_GetNameSpaceParent, + WindowDispEx_GetRefCount, WindowDispEx_GetJSDispatch, WindowDispEx_LookupProperty, WindowDispEx_GetProperty, @@ -3578,6 +3607,8 @@ static const IWineJSDispatchHostVtbl WindowDispExVtbl = { WindowDispEx_FillProperties, WindowDispEx_GetOuterDispatch, WindowDispEx_ToString, + WindowDispEx_Traverse, + WindowDispEx_Unlink }; static inline HTMLOuterWindow *impl_from_IEventTarget(IEventTarget *iface) diff --git a/dlls/mshtml/mshtml_private.h b/dlls/mshtml/mshtml_private.h index e5236ed5f03..0bac63e90aa 100644 --- a/dlls/mshtml/mshtml_private.h +++ b/dlls/mshtml/mshtml_private.h @@ -377,6 +377,11 @@ typedef struct { UINT_PTR x; } nsCycleCollectingAutoRefCnt; +static inline UINT_PTR NS_REFCOUNT_VALUE(nsCycleCollectingAutoRefCnt ref) +{ + return ref.x >> 2; +} + /* dispex is our base IDispatchEx implementation for all mshtml objects, and the vtbl allows customizing the behavior depending on the object. Objects have basically 3 types of props: @@ -646,6 +651,7 @@ extern void (__cdecl *ccref_init)(nsCycleCollectingAutoRefCnt*,nsrefcnt); extern void (__cdecl *ccp_init)(ExternalCycleCollectionParticipant*,const CCObjCallback*); extern void (__cdecl *describe_cc_node)(nsCycleCollectingAutoRefCnt*,const char*,nsCycleCollectionTraversalCallback*); extern void (__cdecl *note_cc_edge)(nsISupports*,const char*,nsCycleCollectionTraversalCallback*); +extern const struct cc_participant_api cc_participant_api; void init_dispatch(DispatchEx*,dispex_static_data_t*,HTMLInnerWindow*,compat_mode_t); void init_dispatch_from_desc(DispatchEx*,dispex_data_t*,HTMLInnerWindow*,DispatchEx*); @@ -795,6 +801,8 @@ struct HTMLOuterWindow { struct wine_rb_entry entry; }; +HTMLOuterWindow *unsafe_HTMLOuterWindow_from_IHTMLWindow2(IHTMLWindow2*); + struct HTMLInnerWindow { HTMLWindow base; EventTarget event_target; diff --git a/dlls/mshtml/nsiface.idl b/dlls/mshtml/nsiface.idl index 8bba7d9f83b..586e4919ae6 100644 --- a/dlls/mshtml/nsiface.idl +++ b/dlls/mshtml/nsiface.idl @@ -208,6 +208,48 @@ typedef void *JSContext; typedef void *JSObject; typedef uint64_t jsval; +typedef struct nsCycleCollectionTraversalCallback nsCycleCollectionTraversalCallback; +typedef struct nsCycleCollectionParticipant nsCycleCollectionParticipant; + +typedef struct nsCycleCollectionParticipantVtbl +{ + nsresult (__stdcall *Traverse)(nsCycleCollectionParticipant *This, void *p, nsCycleCollectionTraversalCallback *cb); + void (__stdcall *Root)(nsCycleCollectionParticipant *This, void *p); + void (__stdcall *Unlink)(nsCycleCollectionParticipant *This, void *p); + void (__stdcall *Unroot)(nsCycleCollectionParticipant *This, void *p); + void (__stdcall *Trace)(nsCycleCollectionParticipant *This, void *p, const void /*TraceCallbacks*/ *cb, void *closure); + void (__stdcall *DeleteCycleCollectable)(nsCycleCollectionParticipant *This, void *p); + bool (__stdcall *CanSkipReal)(nsCycleCollectionParticipant *This, bool removingAllowed); + bool (__stdcall *CanSkipInCCReal)(nsCycleCollectionParticipant *This, void *p); + bool (__stdcall *CanSkipThisReal)(nsCycleCollectionParticipant *This, void *p); +} nsCycleCollectionParticipantVtbl; + +struct nsCycleCollectionParticipant +{ + const nsCycleCollectionParticipantVtbl *lpVtbl; + bool mightSkip; +}; + +typedef struct nsCycleCollectionTraversalCallbackVtbl +{ + void (__stdcall *DescribeRefCountedNode)(nsCycleCollectionTraversalCallback *This, nsrefcnt refcount, const char *objName); + void (__stdcall *DescribeGCedNode)(nsCycleCollectionTraversalCallback *This, bool isMarked, const char *objName, uint64_t compartmentAddress); + void (__stdcall *NoteXPCOMChild)(nsCycleCollectionTraversalCallback *This, nsISupports *child); + void (__stdcall *NoteJSObject)(nsCycleCollectionTraversalCallback *This, void /*JSObject*/ *child); + void (__stdcall *NoteJSScript)(nsCycleCollectionTraversalCallback *This, void /*JSScript*/ *child); + void (__stdcall *NoteNativeChild)(nsCycleCollectionTraversalCallback *This, void *child, nsCycleCollectionParticipant *participant); + void (__stdcall *NoteNextEdgeName)(nsCycleCollectionTraversalCallback *This, const char *name); +} nsCycleCollectionTraversalCallbackVtbl; + +struct nsCycleCollectionTraversalCallback +{ + const nsCycleCollectionTraversalCallbackVtbl *lpVtbl; + uint32_t flags; +}; + +const unsigned int nsCycleCollectionTraversalCallback_WANT_DEBUG_INFO = 1; +const unsigned int nsCycleCollectionTraversalCallback_WANT_ALL_TRACES = 2; + [ object, uuid(8bb35ed9-e332-462d-9155-4a002ab5c958), diff --git a/dlls/mshtml/script.c b/dlls/mshtml/script.c index 786c49990d3..85dd6b7ca49 100644 --- a/dlls/mshtml/script.c +++ b/dlls/mshtml/script.c @@ -204,27 +204,33 @@ static BOOL init_script_engine(ScriptHost *script_host, IActiveScript *script) return FALSE; } - if(compat_mode >= COMPAT_MODE_IE9 && IsEqualGUID(&CLSID_JScript, &script_host->guid)) { + if(IsEqualGUID(&CLSID_JScript, &script_host->guid)) { IWineJScript *jscript; hres = IActiveScript_QueryInterface(script, &IID_IWineJScript, (void **)&jscript); if(SUCCEEDED(hres)) { - DispatchEx *prototype; + IWineJScript_InitCCParticipantAPI(jscript, &cc_participant_api); - assert(!script_host->window->jscript); - assert(!script_host->window->event_target.dispex.jsdisp); - script_host->window->jscript = jscript; + if(compat_mode < COMPAT_MODE_IE9) + IWineJScript_Release(jscript); + else { + DispatchEx *prototype; - hres = get_prototype(script_host->window, OBJID_Window, &prototype); - if(SUCCEEDED(hres)) - hres = IWineJScript_InitHostObject(jscript, - &script_host->window->event_target.dispex.IWineJSDispatchHost_iface, - prototype->jsdisp, object_descriptors[OBJID_Window]->js_flags, - &script_host->window->event_target.dispex.jsdisp); - if(FAILED(hres)) - ERR("Could not initialize script global: %08lx\n", hres); + assert(!script_host->window->jscript); + assert(!script_host->window->event_target.dispex.jsdisp); + script_host->window->jscript = jscript; - /* make sure that script global is fully initialized */ - dispex_compat_mode(&script_host->window->event_target.dispex); + hres = get_prototype(script_host->window, OBJID_Window, &prototype); + if(SUCCEEDED(hres)) + hres = IWineJScript_InitHostObject(jscript, + &script_host->window->event_target.dispex.IWineJSDispatchHost_iface, + prototype->jsdisp, object_descriptors[OBJID_Window]->js_flags, + &script_host->window->event_target.dispex.jsdisp); + if(FAILED(hres)) + ERR("Could not initialize script global: %08lx\n", hres); + + /* make sure that script global is fully initialized */ + dispex_compat_mode(&script_host->window->event_target.dispex); + } }else { ERR("Could not get IWineJScript, don't use native jscript.dll\n"); } -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10045