From: Francis De Brabandere <francisdb@gmail.com> Matches native VBScript: probe each top-level Sub/Function/Class/Dim and bare-assignment LHS at parse time, cache the result, and reuse it during runtime name resolution. The probe IDispatch is independent of the runtime cache so a host returning distinct dispatches per GetItemInfo isn't routed to the wrong slot on later qualified access. 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. lookup_identifier consults the probe cache so already-probed names don't reissue GetIDsOfNames at runtime. --- dlls/vbscript/interp.c | 38 +++++-- dlls/vbscript/tests/vbscript.c | 26 ++--- dlls/vbscript/vbscript.c | 191 +++++++++++++++++++++++++++++++-- dlls/vbscript/vbscript.h | 18 ++++ 4 files changed, 245 insertions(+), 28 deletions(-) diff --git a/dlls/vbscript/interp.c b/dlls/vbscript/interp.c index 76ea15a6e2e..8f941fa4623 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; + } + } } } @@ -1900,6 +1922,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..350df3c8529 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,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 +360,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 +400,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 +423,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 +463,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 +1037,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..9524d407d9a 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; @@ -426,6 +442,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