From: Francis De Brabandere <francisdb@gmail.com> When a visible named item's code runs, native probes the host IDispatch (GetItemInfo and GetIDsOfNames) for each top-level declared name (Subs and Functions, Classes, Dims), in that order, and caches the result. Do the same in exec_global_code, and keep the probe dispatch slot separate from the runtime cache so a Dim no longer overwrites it and misroutes later qualified access against a host that returns a different dispatch on a later GetItemInfo. Implicit names from bare assignments are not probed here: native probes those only when the assignment executes, which the runtime identifier lookup already handles, so an assignment that never runs is not probed. --- dlls/vbscript/interp.c | 38 +++++-- dlls/vbscript/tests/vbscript.c | 28 +++--- dlls/vbscript/vbscript.c | 175 +++++++++++++++++++++++++++++++-- dlls/vbscript/vbscript.h | 18 ++++ 4 files changed, 230 insertions(+), 29 deletions(-) diff --git a/dlls/vbscript/interp.c b/dlls/vbscript/interp.c index 8f1688229a1..29526e68edf 100644 --- a/dlls/vbscript/interp.c +++ b/dlls/vbscript/interp.c @@ -208,15 +208,37 @@ 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; + named_item_t *ni = ctx->func->code_ctx->named_item; + BOOL cached = FALSE; + DISPID cached_id = lookup_probed_name(ni, name, &cached); + + if(cached) { + /* Probe ran for this name during parse. Reuse the cached + * dispid (or skip the host route if the host didn't claim + * it) instead of issuing another GetIDsOfNames. */ + if(cached_id != DISPID_UNKNOWN) { + ensure_named_item_disp(ctx->script, ni); + if(ni->disp) { + ref->type = REF_DISP; + ref->u.d.disp = ni->disp; + ref->u.d.id = cached_id; + return S_OK; + } + } + }else { + ensure_named_item_disp(ctx->script, ni); + if(ni->disp) { + hres = disp_get_id(ni->disp, name, invoke_type, TRUE, &id); + if(SUCCEEDED(hres)) { + ref->type = REF_DISP; + ref->u.d.disp = ni->disp; + ref->u.d.id = id; + return S_OK; + } + } } } @@ -1930,6 +1952,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 a9f4ba8d611..850555ec818 100644 --- a/dlls/vbscript/tests/vbscript.c +++ b/dlls/vbscript/tests/vbscript.c @@ -2510,8 +2510,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); @@ -2542,7 +2542,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. */ @@ -2552,7 +2552,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); SET_EXPECT(OnEnterScript); @@ -2561,7 +2561,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); @@ -2570,7 +2570,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); @@ -2579,7 +2579,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); @@ -2588,7 +2588,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); @@ -2597,7 +2597,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); SET_EXPECT(OnEnterScript); @@ -2606,7 +2606,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); @@ -3350,7 +3350,7 @@ static void test_named_item_deferred_probe(void) CHECK_CALLED(OnEnterScript); CHECK_CALLED(OnLeaveScript); CHECK_CALLED(OnStateChange_CONNECTED); - todo_wine ok(deadcode_probe_count == 1, "host not probed when the deferred code ran (count=%d)\n", deadcode_probe_count); + ok(deadcode_probe_count == 1, "host not probed when the deferred code ran (count=%d)\n", deadcode_probe_count); SET_EXPECT(OnStateChange_DISCONNECTED); SET_EXPECT(OnStateChange_INITIALIZED); @@ -3619,7 +3619,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); @@ -3689,8 +3689,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); SET_EXPECT(OnEnterScript); diff --git a/dlls/vbscript/vbscript.c b/dlls/vbscript/vbscript.c index ff28b3b17ef..db5cc774bae 100644 --- a/dlls/vbscript/vbscript.c +++ b/dlls/vbscript/vbscript.c @@ -93,6 +93,83 @@ 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); +} + +static probed_name_t *find_probed_name(named_item_t *item, const WCHAR *name) +{ + unsigned i; + for(i = 0; i < item->probed_names_cnt; i++) + if(!vbs_wcsicmp(item->probed_names[i].name, name)) + return &item->probed_names[i]; + return NULL; +} + +/* 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; the dispid is cached so later + * runtime lookups don't reissue GetIDsOfNames. */ +static void probe_name_once(named_item_t *item, const WCHAR *name) +{ + probed_name_t *new_arr; + BSTR bstr; + DISPID id; + HRESULT hres; + + if(find_probed_name(item, 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].name = wcsdup(name); + if(!item->probed_names[item->probed_names_cnt].name) return; + item->probed_names[item->probed_names_cnt].dispid = DISPID_UNKNOWN; + + bstr = SysAllocString(name); + if(bstr) { + hres = disp_get_id(item->dim_probe_disp, bstr, VBDISP_CALLGET, TRUE, &id); + if(SUCCEEDED(hres)) + item->probed_names[item->probed_names_cnt].dispid = id; + SysFreeString(bstr); + } + item->probed_names_cnt++; +} + +/* Look up `name` in the named-item's probe cache. Returns the cached + * dispid, or DISPID_UNKNOWN if the name was probed and the host did not + * claim it. Returns DISPID_UNKNOWN and sets *cached to FALSE if the name + * has not been probed. */ +DISPID lookup_probed_name(named_item_t *item, const WCHAR *name, BOOL *cached) +{ + probed_name_t *p = find_probed_name(item, name); + if(!p) { + *cached = FALSE; + return DISPID_UNKNOWN; + } + *cached = TRUE; + return p->dispid; +} + HRESULT exec_global_code(script_ctx_t *ctx, vbscode_t *code, VARIANT *res, BOOL extern_caller) { ScriptDisp *obj = ctx->script_obj; @@ -138,6 +215,57 @@ 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))) + { + if(code->main_code.var_cnt || code->funcs || code->classes) + { + 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); + 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 +344,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 +384,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 +407,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].name); + free(item->probed_names); free(item->name); free(item); } @@ -308,6 +447,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].name); + 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 +1021,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 7e902badb0f..c51a0adffc8 100644 --- a/dlls/vbscript/vbscript.h +++ b/dlls/vbscript/vbscript.h @@ -157,12 +157,28 @@ typedef struct { script_ctx_t *ctx; } BuiltinDisp; +typedef struct { + WCHAR *name; + DISPID dispid; /* DISPID_UNKNOWN when the host didn't claim this name */ +} probed_name_t; + 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, with the dispid the + * host returned (or DISPID_UNKNOWN if it didn't claim the name). + * Native VBScript only probes a given name once per named item + * across the script lifetime; later runtime lookups reuse the + * cached result instead of issuing another GetIDsOfNames. */ + probed_name_t *probed_names; + unsigned probed_names_cnt; + unsigned probed_names_size; struct list entry; } named_item_t; @@ -427,6 +443,8 @@ 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*); +DISPID lookup_probed_name(named_item_t*,const WCHAR*,BOOL*); 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