From: Francis De Brabandere <francisdb@gmail.com> Matches native VBScript and prevents the dim-time refetch from clobbering the runtime IDispatch cache when a host returns distinct dispatches per GetItemInfo. Probes funcs/classes/dims in that order (declaration order within each group), plus implicit declarations from top-level assignments. Names are deduplicated per named item across the script lifetime, and the probe IDispatch is reused as the runtime cache when no separate runtime fetch has happened yet. --- dlls/vbscript/interp.c | 19 ++-- dlls/vbscript/tests/vbscript.c | 26 +++--- dlls/vbscript/vbscript.c | 165 +++++++++++++++++++++++++++++++-- dlls/vbscript/vbscript.h | 9 ++ 4 files changed, 191 insertions(+), 28 deletions(-) diff --git a/dlls/vbscript/interp.c b/dlls/vbscript/interp.c index 76ea15a6e2e..fa00bac92b2 100644 --- a/dlls/vbscript/interp.c +++ b/dlls/vbscript/interp.c @@ -208,15 +208,18 @@ static HRESULT lookup_identifier(exec_ctx_t *ctx, BSTR name, vbdisp_invoke_type_ return S_OK; } - if(ctx->func->code_ctx->named_item && ctx->func->code_ctx->named_item->disp && + if(ctx->func->code_ctx->named_item && !(ctx->func->code_ctx->named_item->flags & SCRIPTITEM_CODEONLY)) { - hres = disp_get_id(ctx->func->code_ctx->named_item->disp, name, invoke_type, TRUE, &id); - if(SUCCEEDED(hres)) { - ref->type = REF_DISP; - ref->u.d.disp = ctx->func->code_ctx->named_item->disp; - ref->u.d.id = id; - return S_OK; + ensure_named_item_disp(ctx->script, ctx->func->code_ctx->named_item); + if(ctx->func->code_ctx->named_item->disp) { + hres = disp_get_id(ctx->func->code_ctx->named_item->disp, name, invoke_type, TRUE, &id); + if(SUCCEEDED(hres)) { + ref->type = REF_DISP; + ref->u.d.disp = ctx->func->code_ctx->named_item->disp; + ref->u.d.id = id; + return S_OK; + } } } @@ -1900,6 +1903,8 @@ static HRESULT interp_me(exec_ctx_t *ctx) if(ctx->vbthis) { disp = (IDispatch*)&ctx->vbthis->IDispatchEx_iface; }else if(ctx->code->named_item) { + if(!(ctx->code->named_item->flags & SCRIPTITEM_CODEONLY)) + ensure_named_item_disp(ctx->script, ctx->code->named_item); disp = (ctx->code->named_item->flags & SCRIPTITEM_CODEONLY) ? (IDispatch*)&ctx->code->named_item->script_obj->IDispatchEx_iface : ctx->code->named_item->disp; diff --git a/dlls/vbscript/tests/vbscript.c b/dlls/vbscript/tests/vbscript.c index 1b1cb322e90..061042b0ef0 100644 --- a/dlls/vbscript/tests/vbscript.c +++ b/dlls/vbscript/tests/vbscript.c @@ -2211,8 +2211,8 @@ static void test_named_items(void) hres = IActiveScriptParse_ParseScriptText(parse, L"dim abc\n", L"visibleItem", NULL, NULL, 0, 0, 0, NULL, NULL); ok(hres == S_OK, "ParseScriptText failed: %08lx\n", hres); CHECK_CALLED(OnEnterScript); - todo_wine CHECK_CALLED(GetItemInfo_visible); - todo_wine CHECK_CALLED(GetIDsOfNames_visible); + CHECK_CALLED(GetItemInfo_visible); + CHECK_CALLED(GetIDsOfNames_visible); CHECK_CALLED(OnLeaveScript); SET_EXPECT(OnEnterScript); SET_EXPECT(OnLeaveScript); @@ -2246,7 +2246,7 @@ static void test_named_items(void) hres = IActiveScriptParse_ParseScriptText(parse, L"dim probe_a, probe_b, probe_c\n", L"visibleItem", NULL, NULL, 0, 0, 0, NULL, NULL); ok(hres == S_OK, "ParseScriptText failed: %08lx\n", hres); CHECK_CALLED(OnEnterScript); - todo_wine CHECK_CALLED_MULTI(GetIDsOfNames_visible, 3); + CHECK_CALLED_MULTI(GetIDsOfNames_visible, 3); CHECK_CALLED(OnLeaveScript); /* Probe: Dim with explicit array bounds. */ @@ -2256,7 +2256,7 @@ static void test_named_items(void) hres = IActiveScriptParse_ParseScriptText(parse, L"dim probe_arr(5)\n", L"visibleItem", NULL, NULL, 0, 0, 0, NULL, NULL); ok(hres == S_OK, "ParseScriptText failed: %08lx\n", hres); CHECK_CALLED(OnEnterScript); - todo_wine CHECK_CALLED(GetIDsOfNames_visible); + CHECK_CALLED(GetIDsOfNames_visible); CHECK_CALLED(OnLeaveScript); /* Native probes the host with GetIDsOfNames once per top-level @@ -2269,7 +2269,7 @@ static void test_named_items(void) hres = IActiveScriptParse_ParseScriptText(parse, L"sub probe_sub\ndim probe_local\nend sub\n", L"visibleItem", NULL, NULL, 0, 0, 0, NULL, NULL); ok(hres == S_OK, "ParseScriptText failed: %08lx\n", hres); CHECK_CALLED(OnEnterScript); - todo_wine CHECK_CALLED(GetIDsOfNames_visible); + CHECK_CALLED(GetIDsOfNames_visible); CHECK_CALLED(OnLeaveScript); SET_EXPECT(OnEnterScript); @@ -2278,7 +2278,7 @@ static void test_named_items(void) hres = IActiveScriptParse_ParseScriptText(parse, L"class probe_cls\npublic probe_prop\nend class\n", L"visibleItem", NULL, NULL, 0, 0, 0, NULL, NULL); ok(hres == S_OK, "ParseScriptText failed: %08lx\n", hres); CHECK_CALLED(OnEnterScript); - todo_wine CHECK_CALLED(GetIDsOfNames_visible); + CHECK_CALLED(GetIDsOfNames_visible); CHECK_CALLED(OnLeaveScript); SET_EXPECT(OnEnterScript); @@ -2287,7 +2287,7 @@ static void test_named_items(void) hres = IActiveScriptParse_ParseScriptText(parse, L"sub probe_named_sub\nend sub\n", L"visibleItem", NULL, NULL, 0, 0, 0, NULL, NULL); ok(hres == S_OK, "ParseScriptText failed: %08lx\n", hres); CHECK_CALLED(OnEnterScript); - todo_wine CHECK_CALLED(GetIDsOfNames_visible); + CHECK_CALLED(GetIDsOfNames_visible); CHECK_CALLED(OnLeaveScript); SET_EXPECT(OnEnterScript); @@ -2296,7 +2296,7 @@ static void test_named_items(void) hres = IActiveScriptParse_ParseScriptText(parse, L"function probe_fn\nend function\n", L"visibleItem", NULL, NULL, 0, 0, 0, NULL, NULL); ok(hres == S_OK, "ParseScriptText failed: %08lx\n", hres); CHECK_CALLED(OnEnterScript); - todo_wine CHECK_CALLED(GetIDsOfNames_visible); + CHECK_CALLED(GetIDsOfNames_visible); CHECK_CALLED(OnLeaveScript); SET_EXPECT(OnEnterScript); @@ -2305,7 +2305,7 @@ static void test_named_items(void) hres = IActiveScriptParse_ParseScriptText(parse, L"dim probe_top\nsub probe_s3\nend sub\n", L"visibleItem", NULL, NULL, 0, 0, 0, NULL, NULL); ok(hres == S_OK, "ParseScriptText failed: %08lx\n", hres); CHECK_CALLED(OnEnterScript); - todo_wine CHECK_CALLED_MULTI(GetIDsOfNames_visible, 2); + CHECK_CALLED_MULTI(GetIDsOfNames_visible, 2); CHECK_CALLED(OnLeaveScript); /* Probe: a second top-level Dim parse on the same named item. The @@ -2317,7 +2317,7 @@ static void test_named_items(void) hres = IActiveScriptParse_ParseScriptText(parse, L"dim probe_second\n", L"visibleItem", NULL, NULL, 0, 0, 0, NULL, NULL); ok(hres == S_OK, "ParseScriptText failed: %08lx\n", hres); CHECK_CALLED(OnEnterScript); - todo_wine CHECK_CALLED(GetIDsOfNames_visible); + CHECK_CALLED(GetIDsOfNames_visible); CHECK_CALLED(OnLeaveScript); SET_EXPECT(OnEnterScript); @@ -3004,7 +3004,7 @@ static void test_named_item_dim_first_use_no_double_fetch(void) hres = IActiveScriptParse_ParseScriptText(parse, L"dim foo\n", L"visibleItem", NULL, NULL, 0, 0, 0, NULL, NULL); ok(hres == S_OK, "ParseScriptText failed: %08lx\n", hres); CHECK_CALLED(OnEnterScript); - todo_wine CHECK_CALLED(GetIDsOfNames_visible); + CHECK_CALLED(GetIDsOfNames_visible); CHECK_CALLED(OnLeaveScript); ok(called_GetItemInfo_visible == 1, "GetItemInfo_visible called %d times, expected 1\n", called_GetItemInfo_visible); @@ -3085,8 +3085,8 @@ static void test_named_item_dim_two_slot_rotating(void) hres = IActiveScriptParse_ParseScriptText(parse, L"dim foo\n", L"rotatingItem", NULL, NULL, 0, 0, 0, NULL, NULL); ok(hres == S_OK, "ParseScriptText failed: %08lx\n", hres); CHECK_CALLED(OnEnterScript); - todo_wine CHECK_CALLED(GetItemInfo_rotating); - todo_wine CHECK_CALLED(GetIDsOfNames_rotating); + CHECK_CALLED(GetItemInfo_rotating); + CHECK_CALLED(GetIDsOfNames_rotating); CHECK_CALLED(OnLeaveScript); /* Second runtime access: native uses cached rotating_a (the runtime diff --git a/dlls/vbscript/vbscript.c b/dlls/vbscript/vbscript.c index ff28b3b17ef..0aa687c2c67 100644 --- a/dlls/vbscript/vbscript.c +++ b/dlls/vbscript/vbscript.c @@ -93,6 +93,57 @@ static inline BOOL is_started(VBScript *This) || This->state == SCRIPTSTATE_DISCONNECTED; } +static HRESULT retrieve_named_item_disp(IActiveScriptSite *site, named_item_t *item); +static HRESULT fetch_named_item_disp(IActiveScriptSite *site, const WCHAR *name, IDispatch **out); + +HRESULT ensure_named_item_disp(script_ctx_t *ctx, named_item_t *item) +{ + if(item->disp || (item->flags & SCRIPTITEM_CODEONLY)) + return S_OK; + /* If the dim-probe already fetched a host dispatch for this item, reuse + * it as the runtime cache instead of issuing a second GetItemInfo. + * Native VBScript shares the dispatch when the probe ran first; only + * when runtime use precedes the probe do the two slots diverge (the + * rotating-dispatch case). */ + if(item->dim_probe_disp) { + IDispatch_AddRef(item->dim_probe_disp); + item->disp = item->dim_probe_disp; + return S_OK; + } + return retrieve_named_item_disp(ctx->site, item); +} + +/* Probe `name` against the named-item's probe IDispatch unless we've + * already probed it. Native VBScript only probes a given name once per + * named item across the script lifetime. */ +static void probe_name_once(named_item_t *item, const WCHAR *name) +{ + unsigned i; + BSTR bstr; + DISPID id; + WCHAR **new_arr; + + for(i = 0; i < item->probed_names_cnt; i++) { + if(!vbs_wcsicmp(item->probed_names[i], name)) return; + } + if(item->probed_names_cnt == item->probed_names_size) { + unsigned new_size = item->probed_names_size ? item->probed_names_size * 2 : 8; + new_arr = realloc(item->probed_names, new_size * sizeof(*new_arr)); + if(!new_arr) return; + item->probed_names = new_arr; + item->probed_names_size = new_size; + } + item->probed_names[item->probed_names_cnt] = wcsdup(name); + if(!item->probed_names[item->probed_names_cnt]) return; + item->probed_names_cnt++; + + bstr = SysAllocString(name); + if(bstr) { + disp_get_id(item->dim_probe_disp, bstr, VBDISP_CALLGET, TRUE, &id); + SysFreeString(bstr); + } +} + HRESULT exec_global_code(script_ctx_t *ctx, vbscode_t *code, VARIANT *res, BOOL extern_caller) { ScriptDisp *obj = ctx->script_obj; @@ -138,6 +189,73 @@ HRESULT exec_global_code(script_ctx_t *ctx, vbscode_t *code, VARIANT *res, BOOL obj->global_funcs_size = cnt; } + /* For visible named items, native VBScript fetches a dedicated host + IDispatch via GetItemInfo on the first top-level declaration parse + and probes each top-level name on it. Native order is: + (1) Subs/Functions in declaration order + (2) Classes in declaration order + (3) Explicit Dim variables in declaration order + (4) Implicit declarations from top-level assignments (LHS), in + source order, deduplicated against the names above. + The probe IDispatch is independent of the runtime cache + (named_item->disp) so subsequent qualified access keeps using the + runtime cache. Probe results are never used to skip name creation; + declarations always succeed. */ + if(code->named_item + && (code->named_item->flags & SCRIPTITEM_ISVISIBLE) + && !(code->named_item->flags & (SCRIPTITEM_CODEONLY | SCRIPTITEM_GLOBALMEMBERS))) + { + BOOL has_top_level_assigns = FALSE; + instr_t *ip; + + for (ip = code->instrs + code->main_code.code_off; ip->op != OP_ret; ip++) { + if (ip->op == OP_assign_ident || ip->op == OP_set_ident) { + has_top_level_assigns = TRUE; + break; + } + } + + if(code->main_code.var_cnt || code->funcs || code->classes || has_top_level_assigns) + { + if(!code->named_item->dim_disp_probed) { + fetch_named_item_disp(ctx->site, code->named_item->name, &code->named_item->dim_probe_disp); + code->named_item->dim_disp_probed = TRUE; + } + + if(code->named_item->dim_probe_disp) { + class_desc_t *class_iter; + class_desc_t **classes_ordered = NULL; + unsigned class_n = 0, k; + + /* code->funcs is in declaration order (head-first). */ + for (func_iter = code->funcs; func_iter; func_iter = func_iter->next) + probe_name_once(code->named_item, func_iter->name); + /* code->classes is built by prepend, so iterating head-first + * gives reverse-declaration order. Reverse via local array. */ + for (class_iter = code->classes; class_iter; class_iter = class_iter->next) class_n++; + if (class_n) { + classes_ordered = malloc(class_n * sizeof(*classes_ordered)); + if (classes_ordered) { + k = class_n; + for (class_iter = code->classes; class_iter; class_iter = class_iter->next) + classes_ordered[--k] = class_iter; + for (k = 0; k < class_n; k++) + probe_name_once(code->named_item, classes_ordered[k]->name); + } + } + for (i = 0; i < code->main_code.var_cnt; i++) + probe_name_once(code->named_item, code->main_code.vars[i].name); + /* Implicit declarations from bare top-level assignments, + * in source order. probe_name_once already deduplicates. */ + for (ip = code->instrs + code->main_code.code_off; ip->op != OP_ret; ip++) { + if (ip->op == OP_assign_ident || ip->op == OP_set_ident) + probe_name_once(code->named_item, ip->arg1.bstr); + } + free(classes_ordered); + } + } + } + for (i = 0; i < code->main_code.var_cnt; i++) { if (script_disp_find_var(obj, code->main_code.vars[i].name)) @@ -216,25 +334,32 @@ static void exec_queued_code(script_ctx_t *ctx) } } -static HRESULT retrieve_named_item_disp(IActiveScriptSite *site, named_item_t *item) +static HRESULT fetch_named_item_disp(IActiveScriptSite *site, const WCHAR *name, IDispatch **out) { IUnknown *unk; HRESULT hres; - hres = IActiveScriptSite_GetItemInfo(site, item->name, SCRIPTINFO_IUNKNOWN, &unk, NULL); + *out = NULL; + if(!site) + return E_UNEXPECTED; + + hres = IActiveScriptSite_GetItemInfo(site, name, SCRIPTINFO_IUNKNOWN, &unk, NULL); if(FAILED(hres)) { WARN("GetItemInfo failed: %08lx\n", hres); return hres; } - hres = IUnknown_QueryInterface(unk, &IID_IDispatch, (void**)&item->disp); + hres = IUnknown_QueryInterface(unk, &IID_IDispatch, (void**)out); IUnknown_Release(unk); - if(FAILED(hres)) { + if(FAILED(hres)) WARN("object does not implement IDispatch\n"); - return hres; - } - return S_OK; + return hres; +} + +static HRESULT retrieve_named_item_disp(IActiveScriptSite *site, named_item_t *item) +{ + return fetch_named_item_disp(site, item->name, &item->disp); } named_item_t *lookup_named_item(script_ctx_t *ctx, const WCHAR *name, unsigned flags) @@ -249,7 +374,7 @@ named_item_t *lookup_named_item(script_ctx_t *ctx, const WCHAR *name, unsigned f if(FAILED(hres)) return NULL; } - if(!item->disp && (flags || !(item->flags & SCRIPTITEM_CODEONLY))) { + if(!item->disp && flags) { hres = retrieve_named_item_disp(ctx->site, item); if(FAILED(hres)) continue; } @@ -272,8 +397,12 @@ static void release_named_item_script_obj(named_item_t *item) void release_named_item(named_item_t *item) { + unsigned i; if(--item->ref) return; + for(i = 0; i < item->probed_names_cnt; i++) + free(item->probed_names[i]); + free(item->probed_names); free(item->name); free(item); } @@ -308,6 +437,21 @@ static void release_script(script_ctx_t *ctx) IDispatch_Release(item->disp); item->disp = NULL; } + if(item->dim_probe_disp) + { + IDispatch_Release(item->dim_probe_disp); + item->dim_probe_disp = NULL; + } + item->dim_disp_probed = FALSE; + { + unsigned pi; + for(pi = 0; pi < item->probed_names_cnt; pi++) + free(item->probed_names[pi]); + free(item->probed_names); + item->probed_names = NULL; + item->probed_names_cnt = 0; + item->probed_names_size = 0; + } release_named_item_script_obj(item); if(!(item->flags & SCRIPTITEM_ISPERSISTENT)) { @@ -867,8 +1011,13 @@ static HRESULT WINAPI VBScript_AddNamedItem(IActiveScript *iface, LPCOLESTR pstr item->ref = 1; item->disp = disp; + item->dim_probe_disp = NULL; item->flags = dwFlags; item->script_obj = NULL; + item->dim_disp_probed = FALSE; + item->probed_names = NULL; + item->probed_names_cnt = 0; + item->probed_names_size = 0; item->name = wcsdup(pstrName); if(!item->name) { if(disp) diff --git a/dlls/vbscript/vbscript.h b/dlls/vbscript/vbscript.h index 7b8315748c9..bac30e9dcb2 100644 --- a/dlls/vbscript/vbscript.h +++ b/dlls/vbscript/vbscript.h @@ -160,9 +160,17 @@ typedef struct { typedef struct named_item_t { ScriptDisp *script_obj; IDispatch *disp; + IDispatch *dim_probe_disp; unsigned ref; DWORD flags; LPWSTR name; + BOOL dim_disp_probed; + + /* Names already probed against dim_probe_disp; native VBScript only + * probes a given name once per named item across the script lifetime. */ + WCHAR **probed_names; + unsigned probed_names_cnt; + unsigned probed_names_size; struct list entry; } named_item_t; @@ -426,6 +434,7 @@ BOOL is_exec_local_scope(exec_ctx_t*); HRESULT exec_add_caller_dynamic_var(script_ctx_t*,exec_ctx_t*,const WCHAR*); void release_dynamic_var(dynamic_var_t*); named_item_t *lookup_named_item(script_ctx_t*,const WCHAR*,unsigned); +HRESULT ensure_named_item_disp(script_ctx_t*,named_item_t*); void release_named_item(named_item_t*); void clear_error_loc(script_ctx_t*); void clear_ei(EXCEPINFO*); -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10393