[PATCH v4 0/3] MR10980: vbscript: Propagate errors from a script-invoked function reference.
FuncRef_Invoke always ran the referenced function as an external caller, so an error raised through a GetRef reference was reported to the host via IActiveScriptSite::OnScriptError even when the reference was called from within running script. A direct call propagates the error as an HRESULT to the caller's On Error Resume Next instead. Treat the reference as an external caller only when the engine is not already executing script. -- v4: vbscript/tests: Note the error HRESULT seen by an external caller. vbscript: Run a script-invoked function reference as an internal call. vbscript/tests: Add tests for error reporting from a GetRef reference. https://gitlab.winehq.org/wine/wine/-/merge_requests/10980
From: Francis De Brabandere <francisdb@gmail.com> An error raised through a GetRef reference invoked from script under On Error Resume Next must propagate to the caller; the same reference invoked by an external object must instead be reported to the host. --- dlls/vbscript/tests/run.c | 69 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/dlls/vbscript/tests/run.c b/dlls/vbscript/tests/run.c index decde599d0c..8a1a731fb3b 100644 --- a/dlls/vbscript/tests/run.c +++ b/dlls/vbscript/tests/run.c @@ -148,6 +148,7 @@ DEFINE_EXPECT(OnLeaveScript); #define DISPID_GLOBAL_THROWEXCEPTION 1027 #define DISPID_GLOBAL_ISARRAYFIXED 1028 #define DISPID_GLOBAL_MAXCHARSIZE 1029 +#define DISPID_GLOBAL_INVOKEDISP 1030 #define DISPID_TESTOBJ_PROPGET 2000 #define DISPID_TESTOBJ_PROPPUT 2001 @@ -1237,6 +1238,7 @@ static HRESULT WINAPI Global_GetDispID(IDispatchEx *iface, BSTR bstrName, DWORD { L"MaxCharSize", DISPID_GLOBAL_MAXCHARSIZE }, { L"firstDayOfWeek", DISPID_GLOBAL_WEEKSTARTDAY }, { L"globalCallback", DISPID_GLOBAL_GLOBALCALLBACK }, + { L"invokeDisp", DISPID_GLOBAL_INVOKEDISP }, { L"testObj", DISPID_GLOBAL_TESTOBJ }, { L"collectionObj" , DISPID_GLOBAL_COLLOBJ }, { L"vbvar", DISPID_GLOBAL_VBVAR, REF_EXPECT(global_vbvar_d) }, @@ -1362,6 +1364,29 @@ static HRESULT WINAPI Global_InvokeEx(IDispatchEx *iface, DISPID id, LCID lcid, V_I4(pvarRes) = MaxCharSize; return S_OK; + case DISPID_GLOBAL_INVOKEDISP: { + DISPPARAMS params = { NULL, NULL, 0, 0 }; + VARIANT *arg = pdp->rgvarg; + EXCEPINFO ei; + HRESULT hr; + + ok(wFlags == INVOKE_FUNC || wFlags == (INVOKE_FUNC|INVOKE_PROPERTYGET), "wFlags = %x\n", wFlags); + ok(pdp != NULL, "pdp == NULL\n"); + ok(pdp->cArgs == 1, "cArgs = %d\n", pdp->cArgs); + if(V_VT(arg) == (VT_VARIANT|VT_BYREF)) + arg = V_VARIANTREF(arg); + ok(V_VT(arg) == VT_DISPATCH, "V_VT(arg) = %d\n", V_VT(arg)); + + /* Mimic an external object invoking a function reference back into the + * engine while a script frame is still on the stack. The result is + * dropped so the test observes only how the engine reports the error. */ + memset(&ei, 0, sizeof(ei)); + hr = IDispatch_Invoke(V_DISPATCH(arg), DISPID_VALUE, &IID_NULL, 0, + DISPATCH_METHOD, ¶ms, NULL, &ei, NULL); + trace("invokeDisp: inner Invoke returned %08lx\n", hr); + return S_OK; + } + case DISPID_GLOBAL_WEEKSTARTDAY: V_VT(pvarRes) = VT_I4; V_I4(pvarRes) = first_day_of_week; @@ -3540,6 +3565,48 @@ static void test_redefine_scope(void) } } +static void test_getref_error_reporting(void) +{ + /* An error raised inside a function reference obtained from GetRef, when + * the reference is called from script under the caller's On Error Resume + * Next, must propagate to that caller rather than be reported to the host. */ + static const WCHAR *src = + L"Dim cb : Set cb = GetRef(\"RaisesError\")\n" + L"Sub CallIndirect\n" + L" On Error Resume Next\n" + L" cb\n" + L" On Error Goto 0\n" + L"End Sub\n" + L"Sub RaisesError\n" + L" Err.Raise 5\n" + L"End Sub\n" + L"CallIndirect\n"; + HRESULT hres; + + SET_EXPECT(OnScriptError); + hres = parse_script_wr(src); + ok(hres == S_OK, "parse_script_wr returned %08lx\n", hres); + ok(!called_OnScriptError, + "error in a GetRef reference under the caller's On Error Resume Next was reported to the host\n"); + CLEAR_CALLED(OnScriptError); +} + +static void test_getref_external_caller_error(void) +{ + static const WCHAR *src = + L"Dim cb : Set cb = GetRef(\"RaisesError\")\n" + L"Sub RaisesError\n" + L" Err.Raise 5\n" + L"End Sub\n" + L"Call invokeDisp(cb)\n"; + HRESULT hres; + + SET_EXPECT(OnScriptError); + hres = parse_script_wr(src); + ok(hres == S_OK, "parse_script_wr returned %08lx\n", hres); + CHECK_CALLED(OnScriptError); +} + static void test_msgbox(void) { HRESULT hres; @@ -4237,6 +4304,8 @@ static void run_tests(void) test_option_explicit_errors(); test_parse_errors(); test_redefine_scope(); + test_getref_error_reporting(); + test_getref_external_caller_error(); test_parse_context(); test_callbacks(); test_multiple_parse(); -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10980
From: Francis De Brabandere <francisdb@gmail.com> FuncRef_Invoke always ran the reference as an external caller, reporting errors to the host even when called from running script. Route the engine's own references through disp_call as internal calls, like class instances, so a reference reaching FuncRef_Invoke is always an external caller. --- dlls/vbscript/vbdisp.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/dlls/vbscript/vbdisp.c b/dlls/vbscript/vbdisp.c index 554d410b57e..38e4ed140ab 100644 --- a/dlls/vbscript/vbdisp.c +++ b/dlls/vbscript/vbdisp.c @@ -857,6 +857,10 @@ static HRESULT WINAPI FuncRef_Invoke(IDispatch *iface, DISPID dispIdMember, REFI if(dispIdMember != DISPID_VALUE) return DISP_E_MEMBERNOTFOUND; + /* The engine runs its own references through disp_call as internal calls; + * reaching this vtable entry means the caller is outside the engine, so the + * reference is entered as an external caller and errors are reported to the + * host rather than propagated as an HRESULT. */ return exec_script(This->ctx, TRUE, This->func, NULL, pDispParams, pVarResult); } @@ -870,6 +874,11 @@ static const IDispatchVtbl FuncRefVtbl = { FuncRef_Invoke }; +static inline FuncRef *unsafe_funcref_from_IDispatch(IDispatch *iface) +{ + return iface->lpVtbl == &FuncRefVtbl ? FuncRef_from_IDispatch(iface) : NULL; +} + HRESULT create_func_ref(script_ctx_t *ctx, function_t *func, IDispatch **ret) { FuncRef *ref; @@ -2009,6 +2018,7 @@ HRESULT disp_call(script_ctx_t *ctx, IDispatch *disp, DISPID id, BOOL is_call, D const WORD flags = DISPATCH_METHOD|(retv ? DISPATCH_PROPERTYGET : 0); IDispatchEx *dispex; vbdisp_t *vbdisp; + FuncRef *funcref; EXCEPINFO ei; HRESULT hres; @@ -2020,6 +2030,10 @@ HRESULT disp_call(script_ctx_t *ctx, IDispatch *disp, DISPID id, BOOL is_call, D if(vbdisp && vbdisp->desc && vbdisp->desc->ctx == ctx) return invoke_vbdisp(vbdisp, id, flags, FALSE, is_call, dp, retv); + funcref = unsafe_funcref_from_IDispatch(disp); + if(funcref && funcref->ctx == ctx && id == DISPID_VALUE) + return exec_script(ctx, FALSE, funcref->func, NULL, dp, retv); + hres = IDispatch_QueryInterface(disp, &IID_IDispatchEx, (void**)&dispex); if(SUCCEEDED(hres)) { hres = IDispatchEx_InvokeEx(dispex, id, ctx->lcid, flags, dp, retv, &ei, &ctx->vbcaller->IServiceProvider_iface); -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10980
From: Francis De Brabandere <francisdb@gmail.com> When an external object invokes a script function reference or class method while a script frame is on the stack and the host leaves the error unhandled, native returns SCRIPT_E_RECORDED and DISP_E_EXCEPTION respectively to the caller. Wine returns the raw error code instead; mark these with todo_wine. --- dlls/vbscript/tests/run.c | 60 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 57 insertions(+), 3 deletions(-) diff --git a/dlls/vbscript/tests/run.c b/dlls/vbscript/tests/run.c index 8a1a731fb3b..f54efd099e2 100644 --- a/dlls/vbscript/tests/run.c +++ b/dlls/vbscript/tests/run.c @@ -149,6 +149,7 @@ DEFINE_EXPECT(OnLeaveScript); #define DISPID_GLOBAL_ISARRAYFIXED 1028 #define DISPID_GLOBAL_MAXCHARSIZE 1029 #define DISPID_GLOBAL_INVOKEDISP 1030 +#define DISPID_GLOBAL_INVOKEMETHOD 1031 #define DISPID_TESTOBJ_PROPGET 2000 #define DISPID_TESTOBJ_PROPPUT 2001 @@ -1239,6 +1240,7 @@ static HRESULT WINAPI Global_GetDispID(IDispatchEx *iface, BSTR bstrName, DWORD { L"firstDayOfWeek", DISPID_GLOBAL_WEEKSTARTDAY }, { L"globalCallback", DISPID_GLOBAL_GLOBALCALLBACK }, { L"invokeDisp", DISPID_GLOBAL_INVOKEDISP }, + { L"invokeMethod", DISPID_GLOBAL_INVOKEMETHOD }, { L"testObj", DISPID_GLOBAL_TESTOBJ }, { L"collectionObj" , DISPID_GLOBAL_COLLOBJ }, { L"vbvar", DISPID_GLOBAL_VBVAR, REF_EXPECT(global_vbvar_d) }, @@ -1378,12 +1380,46 @@ static HRESULT WINAPI Global_InvokeEx(IDispatchEx *iface, DISPID id, LCID lcid, ok(V_VT(arg) == VT_DISPATCH, "V_VT(arg) = %d\n", V_VT(arg)); /* Mimic an external object invoking a function reference back into the - * engine while a script frame is still on the stack. The result is - * dropped so the test observes only how the engine reports the error. */ + * engine while a script frame is on the stack. With the host leaving + * the error unhandled, native returns SCRIPT_E_RECORDED to the caller; + * Wine returns the raw error code instead. */ memset(&ei, 0, sizeof(ei)); hr = IDispatch_Invoke(V_DISPATCH(arg), DISPID_VALUE, &IID_NULL, 0, DISPATCH_METHOD, ¶ms, NULL, &ei, NULL); - trace("invokeDisp: inner Invoke returned %08lx\n", hr); + todo_wine ok(hr == SCRIPT_E_RECORDED, "inner Invoke returned %08lx\n", hr); + return S_OK; + } + + case DISPID_GLOBAL_INVOKEMETHOD: { + DISPPARAMS params = { NULL, NULL, 0, 0 }; + VARIANT *arg = pdp->rgvarg; + IDispatchEx *dispex; + EXCEPINFO ei; + VARIANT v; + DISPID did; + BSTR str; + HRESULT hr; + + ok(pdp->cArgs == 1, "cArgs = %d\n", pdp->cArgs); + if(V_VT(arg) == (VT_VARIANT|VT_BYREF)) + arg = V_VARIANTREF(arg); + ok(V_VT(arg) == VT_DISPATCH, "V_VT(arg) = %d\n", V_VT(arg)); + + hr = IDispatch_QueryInterface(V_DISPATCH(arg), &IID_IDispatchEx, (void**)&dispex); + ok(hr == S_OK, "QueryInterface(IDispatchEx) returned %08lx\n", hr); + str = SysAllocString(L"raiser"); + hr = IDispatchEx_GetDispID(dispex, str, fdexNameCaseInsensitive, &did); + ok(hr == S_OK, "GetDispID(raiser) returned %08lx\n", hr); + SysFreeString(str); + + /* As above, but for a class method invoked by an external object. With + * the host leaving the error unhandled, native returns DISP_E_EXCEPTION + * to the caller; Wine returns the raw error code instead. */ + memset(&ei, 0, sizeof(ei)); + V_VT(&v) = VT_EMPTY; + hr = IDispatchEx_InvokeEx(dispex, did, 0, DISPATCH_METHOD, ¶ms, &v, &ei, NULL); + todo_wine ok(hr == DISP_E_EXCEPTION, "inner InvokeEx returned %08lx\n", hr); + IDispatchEx_Release(dispex); return S_OK; } @@ -3607,6 +3643,23 @@ static void test_getref_external_caller_error(void) CHECK_CALLED(OnScriptError); } +static void test_external_caller_method_error(void) +{ + static const WCHAR *src = + L"Class C\n" + L" Public Sub raiser\n" + L" Err.Raise 5\n" + L" End Sub\n" + L"End Class\n" + L"Call invokeMethod(new C)\n"; + HRESULT hres; + + SET_EXPECT(OnScriptError); + hres = parse_script_wr(src); + ok(hres == S_OK, "parse_script_wr returned %08lx\n", hres); + CHECK_CALLED(OnScriptError); +} + static void test_msgbox(void) { HRESULT hres; @@ -4306,6 +4359,7 @@ static void run_tests(void) test_redefine_scope(); test_getref_error_reporting(); test_getref_external_caller_error(); + test_external_caller_method_error(); test_parse_context(); test_callbacks(); test_multiple_parse(); -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10980
On Tue May 26 16:13:10 2026 +0000, Francis De Brabandere wrote:
There was no need to introduce a flag since this is structured a bit different than jscript Also added another todo_wine test commit for 2 extra things I found. Do you think this requires fixing @jacek ?
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/10980#note_141347
This merge request was approved by Jacek Caban. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10980
On Tue May 26 16:13:32 2026 +0000, Francis De Brabandere wrote:
Also added another todo_wine test commit for 2 extra things I found. Do you think this requires fixing @jacek ? Looks fine to me. And yeah, jscript mainly needs the flag because it executes calls by changing interpreter state instead of adding another interpreter loop on the C stack. We don't have that in vbscript yet.
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/10980#note_141350
participants (3)
-
Francis De Brabandere -
Francis De Brabandere (@francisdb) -
Jacek Caban (@jacek)