[PATCH v11 0/1] MR10368: vbscript: Implement Eval, Execute, and ExecuteGlobal.
Eval compiles the string as an expression and returns the result. ExecuteGlobal compiles and executes the string at global scope. Execute compiles and executes the string in the calling scope. When called from within a Sub or Function, Eval and Execute use the calling scope for variable resolution, accessing local variables and arguments. When called from global scope, they fall back to exec_global_code. ExecuteGlobal always executes in global scope. The calling scope is passed through script_ctx_t.caller_exec, which exec_script picks up and stores on exec_ctx_t.caller. The interpreter's lookup_identifier checks the caller's locals, arguments, and dynamic vars when running FUNC_GLOBAL code with a caller context set. Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=49908 Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=53962 -- v11: vbscript: Implement Eval, Execute, and ExecuteGlobal. https://gitlab.winehq.org/wine/wine/-/merge_requests/10368
From: Francis De Brabandere <francisdb@gmail.com> Eval compiles the string as an expression and returns the result. ExecuteGlobal compiles and executes the string at global scope. Execute compiles and executes the string in the calling scope. When called from within a Sub or Function, Eval and Execute use the calling scope for variable resolution, accessing local variables and arguments. When called from global scope, they fall back to exec_global_code. ExecuteGlobal always executes in global scope. The calling scope is passed through script_ctx_t.caller_exec, which exec_script picks up and stores on exec_ctx_t.caller. The interpreter's lookup_identifier checks the caller's locals, arguments, and dynamic vars when running FUNC_GLOBAL code with a caller context set. Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=49908 Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=53962 --- dlls/vbscript/compile.c | 9 +++ dlls/vbscript/global.c | 87 ++++++++++++++++++++-- dlls/vbscript/interp.c | 39 ++++++++++ dlls/vbscript/tests/lang.vbs | 136 ++++++++++++++++++++++++++++++++++ dlls/vbscript/vbscript.c | 8 +- dlls/vbscript/vbscript.h | 7 ++ dlls/vbscript/vbscript.rc | 1 + dlls/vbscript/vbscript_defs.h | 1 + 8 files changed, 278 insertions(+), 10 deletions(-) diff --git a/dlls/vbscript/compile.c b/dlls/vbscript/compile.c index 345e3b5291b..98f89b5b707 100644 --- a/dlls/vbscript/compile.c +++ b/dlls/vbscript/compile.c @@ -63,6 +63,8 @@ typedef struct { function_t *func; function_decl_t *func_decls; + + DWORD flags; } compile_ctx_t; static HRESULT compile_expression(compile_ctx_t*,expression_t*); @@ -402,6 +404,12 @@ static HRESULT compile_error(script_ctx_t *ctx, compile_ctx_t *compiler, HRESULT ctx->ei.scode = error; ctx->ei.bstrSource = get_vbscript_string(VBS_COMPILE_ERROR); map_vbs_exception(&ctx->ei); + + /* For internal calls (Eval/Execute/ExecuteGlobal), don't report to OnScriptError. + * Just return SCRIPT_E_RECORDED to indicate ctx->ei is populated. */ + if(compiler->flags & SCRIPTTEXT_NOERRORREPORT) + return SCRIPT_E_RECORDED; + return report_script_error(ctx, compiler->code, compiler->loc, TRUE); } @@ -2113,6 +2121,7 @@ HRESULT compile_script(script_ctx_t *script, const WCHAR *src, const WCHAR *item } memset(&ctx, 0, sizeof(ctx)); + ctx.flags = flags; code = ctx.code = alloc_vbscode(&ctx, src, cookie, start_line); if(!ctx.code) return E_OUTOFMEMORY; diff --git a/dlls/vbscript/global.c b/dlls/vbscript/global.c index f38ff895ef7..781762f0cc5 100644 --- a/dlls/vbscript/global.c +++ b/dlls/vbscript/global.c @@ -3333,20 +3333,95 @@ static HRESULT Global_Unescape(BuiltinDisp *This, VARIANT *arg, unsigned args_cn static HRESULT Global_Eval(BuiltinDisp *This, VARIANT *arg, unsigned args_cnt, VARIANT *res) { - FIXME("\n"); - return E_NOTIMPL; + vbscode_t *code; + HRESULT hres; + + TRACE("%s\n", debugstr_variant(arg)); + + if(V_VT(arg) != VT_BSTR) { + if(res) + return VariantCopy(res, arg); + return S_OK; + } + + hres = compile_script(This->ctx, V_BSTR(arg), NULL, NULL, 0, 0, + SCRIPTTEXT_ISEXPRESSION | SCRIPTTEXT_NOERRORREPORT, &code); + if(FAILED(hres)) { + /* Compilation failed. If compile_error didn't set up a proper VBS error, + * fall back to VBSE_SYNTAX_ERROR. */ + if(HRESULT_FACILITY(This->ctx->ei.scode) != FACILITY_VBS) { + clear_ei(&This->ctx->ei); + This->ctx->ei.scode = MAKE_VBSERROR(VBSE_SYNTAX_ERROR); + This->ctx->ei.bstrSource = get_vbscript_string(VBS_COMPILE_ERROR); + This->ctx->ei.bstrDescription = get_vbscript_string(VBSE_SYNTAX_ERROR); + } + return SCRIPT_E_RECORDED; + } + + if(is_exec_local_scope(This->ctx->current_exec)) { + This->ctx->caller_exec = This->ctx->current_exec; + return exec_script(This->ctx, FALSE, &code->main_code, NULL, NULL, res); + } + + return exec_global_code(This->ctx, code, res, FALSE); } static HRESULT Global_Execute(BuiltinDisp *This, VARIANT *arg, unsigned args_cnt, VARIANT *res) { - FIXME("\n"); - return E_NOTIMPL; + vbscode_t *code; + HRESULT hres; + + TRACE("%s\n", debugstr_variant(arg)); + + if(V_VT(arg) != VT_BSTR) + return MAKE_VBSERROR(VBSE_TYPE_MISMATCH); + + hres = compile_script(This->ctx, V_BSTR(arg), NULL, NULL, 0, 0, SCRIPTTEXT_NOERRORREPORT, &code); + if(FAILED(hres)) { + /* Compilation failed. If compile_error didn't set up a proper VBS error, + * fall back to VBSE_SYNTAX_ERROR. */ + if(HRESULT_FACILITY(This->ctx->ei.scode) != FACILITY_VBS) { + clear_ei(&This->ctx->ei); + This->ctx->ei.scode = MAKE_VBSERROR(VBSE_SYNTAX_ERROR); + This->ctx->ei.bstrSource = get_vbscript_string(VBS_COMPILE_ERROR); + This->ctx->ei.bstrDescription = get_vbscript_string(VBSE_SYNTAX_ERROR); + } + return SCRIPT_E_RECORDED; + } + + if(is_exec_local_scope(This->ctx->current_exec)) { + This->ctx->caller_exec = This->ctx->current_exec; + return exec_script(This->ctx, FALSE, &code->main_code, NULL, NULL, res); + } + + return exec_global_code(This->ctx, code, res, FALSE); } + static HRESULT Global_ExecuteGlobal(BuiltinDisp *This, VARIANT *arg, unsigned args_cnt, VARIANT *res) { - FIXME("\n"); - return E_NOTIMPL; + vbscode_t *code; + HRESULT hres; + + TRACE("%s\n", debugstr_variant(arg)); + + if(V_VT(arg) != VT_BSTR) + return MAKE_VBSERROR(VBSE_TYPE_MISMATCH); + + hres = compile_script(This->ctx, V_BSTR(arg), NULL, NULL, 0, 0, SCRIPTTEXT_NOERRORREPORT, &code); + if(FAILED(hres)) { + /* Compilation failed. If compile_error didn't set up a proper VBS error, + * fall back to VBSE_SYNTAX_ERROR. */ + if(HRESULT_FACILITY(This->ctx->ei.scode) != FACILITY_VBS) { + clear_ei(&This->ctx->ei); + This->ctx->ei.scode = MAKE_VBSERROR(VBSE_SYNTAX_ERROR); + This->ctx->ei.bstrSource = get_vbscript_string(VBS_COMPILE_ERROR); + This->ctx->ei.bstrDescription = get_vbscript_string(VBSE_SYNTAX_ERROR); + } + return SCRIPT_E_RECORDED; + } + + return exec_global_code(This->ctx, code, res, FALSE); } static HRESULT Global_GetRef(BuiltinDisp *This, VARIANT *arg, unsigned args_cnt, VARIANT *res) diff --git a/dlls/vbscript/interp.c b/dlls/vbscript/interp.c index 00ef7d93eb6..dca4c232a63 100644 --- a/dlls/vbscript/interp.c +++ b/dlls/vbscript/interp.c @@ -47,6 +47,8 @@ typedef struct { VARIANT *stack; VARIANT ret_val; + + void *caller; } exec_ctx_t; typedef HRESULT (*instr_func_t)(exec_ctx_t*); @@ -182,6 +184,29 @@ static HRESULT lookup_identifier(exec_ctx_t *ctx, BSTR name, vbdisp_invoke_type_ } } + if(ctx->func->type == FUNC_GLOBAL && ctx->caller) { + exec_ctx_t *caller = ctx->caller; + + for(i=0; i < caller->func->var_cnt; i++) { + if(!wcsicmp(caller->func->vars[i].name, name)) { + ref->type = REF_VAR; + ref->u.v = caller->vars+i; + return S_OK; + } + } + + for(i=0; i < caller->func->arg_cnt; i++) { + if(!wcsicmp(caller->func->args[i].name, name)) { + ref->type = REF_VAR; + ref->u.v = caller->args+i; + return S_OK; + } + } + + if(lookup_dynamic_vars(caller->dynamic_vars, name, ref)) + return S_OK; + } + if(ctx->code->named_item) { if(lookup_global_vars(ctx->code->named_item->script_obj, name, ref)) return S_OK; @@ -2469,13 +2494,22 @@ static void release_exec(exec_ctx_t *ctx) free(ctx->stack); } +BOOL is_exec_local_scope(void *exec) +{ + exec_ctx_t *ctx = exec; + return ctx && ctx->func->type != FUNC_GLOBAL; +} + HRESULT exec_script(script_ctx_t *ctx, BOOL extern_caller, function_t *func, vbdisp_t *vbthis, DISPPARAMS *dp, VARIANT *res) { exec_ctx_t exec = {func->code_ctx}; + void *prev_exec; vbsop_t op; HRESULT hres = S_OK; exec.code = func->code_ctx; + exec.caller = ctx->caller_exec; + ctx->caller_exec = NULL; if(dp ? func->arg_cnt != arg_cnt(dp) : func->arg_cnt) { FIXME("wrong arg_cnt %d, expected %d\n", dp ? arg_cnt(dp) : 0, func->arg_cnt); @@ -2545,6 +2579,9 @@ HRESULT exec_script(script_ctx_t *ctx, BOOL extern_caller, function_t *func, vbd exec.script = ctx; exec.func = func; + prev_exec = ctx->current_exec; + ctx->current_exec = &exec; + while(exec.instr) { op = exec.instr->op; hres = op_funcs[op](&exec); @@ -2605,6 +2642,8 @@ HRESULT exec_script(script_ctx_t *ctx, BOOL extern_caller, function_t *func, vbd exec.instr += op_move[op]; } + ctx->current_exec = prev_exec; + assert(!exec.top); if(extern_caller) { diff --git a/dlls/vbscript/tests/lang.vbs b/dlls/vbscript/tests/lang.vbs index ff34ac8059b..1cf884410ff 100644 --- a/dlls/vbscript/tests/lang.vbs +++ b/dlls/vbscript/tests/lang.vbs @@ -2408,4 +2408,140 @@ arr (0) = 2 xor -2 Call ok(indexedObj(3) = 6, "indexedObj(3) = " & indexedObj(3)) Call ok(indexedObj(0) = 0, "indexedObj(0) = " & indexedObj(0)) +' Eval tests +Call ok(Eval("1 + 2") = 3, "Eval(""1 + 2"") = " & Eval("1 + 2")) +Call ok(Eval("""test""") = "test", "Eval(""""""test"""""") = " & Eval("""test""")) +Call ok(Eval("true") = true, "Eval(""true"") = " & Eval("true")) + +x = 5 +Call ok(Eval("x + 1") = 6, "Eval(""x + 1"") = " & Eval("x + 1")) +Call ok(Eval("x * x") = 25, "Eval(""x * x"") = " & Eval("x * x")) + +Sub TestEvalLocalScope + Dim a + a = 10 + Call ok(Eval("a") = 10, "Eval(""a"") in local scope = " & Eval("a")) + Call ok(Eval("a + 5") = 15, "Eval(""a + 5"") in local scope = " & Eval("a + 5")) +End Sub +Call TestEvalLocalScope + +Function TestEvalLocalArgs(x) + TestEvalLocalArgs = Eval("x * 2") +End Function +Call ok(TestEvalLocalArgs(7) = 14, "TestEvalLocalArgs(7) = " & TestEvalLocalArgs(7)) + +Dim evalLocal +Sub TestEvalNoLeak + Dim evalLocal + evalLocal = 77 + Call ok(Eval("evalLocal") = 77, "Eval evalLocal = " & Eval("evalLocal")) +End Sub +Call TestEvalNoLeak +Call ok(getVT(evalLocal) = "VT_EMPTY*", "evalLocal leaked from Eval scope: " & getVT(evalLocal)) + +' ExecuteGlobal tests +x = 0 +ExecuteGlobal "x = 42" +Call ok(x = 42, "ExecuteGlobal x = " & x) + +ExecuteGlobal "Function evalTestFunc : evalTestFunc = 7 : End Function" +Call ok(evalTestFunc() = 7, "evalTestFunc() = " & evalTestFunc()) + +' Execute tests +x = 0 +Execute "x = 99" +Call ok(x = 99, "Execute x = " & x) + +Sub TestExecuteLocalScope + Dim a + a = 10 + Execute "a = 20" + Call ok(a = 20, "Execute local assign: a = " & a) +End Sub +Call TestExecuteLocalScope + +Dim executeLocal +Sub TestExecuteNoLeak + Dim executeLocal + executeLocal = 10 + Execute "executeLocal = 55" + Call ok(executeLocal = 55, "executeLocal = " & executeLocal) +End Sub +Call TestExecuteNoLeak +Call ok(getVT(executeLocal) = "VT_EMPTY*", "executeLocal leaked from Execute scope: " & getVT(executeLocal)) + +' Eval/Execute/ExecuteGlobal error handling tests +On Error Resume Next + +' Test Eval with syntax error +Err.Clear +Call Eval("1 + ") +Call ok(Err.Number = 1002, "Eval syntax error: Err.Number = " & Err.Number & " expected 1002") +Call ok(Err.Description = "Syntax error", "Eval syntax error: Err.Description = """ & Err.Description & """ expected ""Syntax error""") +Call ok(Err.Source = "Microsoft VBScript compilation error", "Eval syntax error: Err.Source = """ & Err.Source & """") + +' Test Execute with syntax error +Err.Clear +Execute "x = " +Call ok(Err.Number = 1002, "Execute syntax error: Err.Number = " & Err.Number & " expected 1002") +Call ok(Err.Description = "Syntax error", "Execute syntax error: Err.Description = """ & Err.Description & """ expected ""Syntax error""") +Call ok(Err.Source = "Microsoft VBScript compilation error", "Execute syntax error: Err.Source = """ & Err.Source & """") + +' Test ExecuteGlobal with syntax error +Err.Clear +ExecuteGlobal "y = " +Call ok(Err.Number = 1002, "ExecuteGlobal syntax error: Err.Number = " & Err.Number & " expected 1002") +Call ok(Err.Description = "Syntax error", "ExecuteGlobal syntax error: Err.Description = """ & Err.Description & """ expected ""Syntax error""") +Call ok(Err.Source = "Microsoft VBScript compilation error", "ExecuteGlobal syntax error: Err.Source = """ & Err.Source & """") + +' Eval with non-string arguments returns the value directly +Err.Clear +Call ok(Eval(123) = 123, "Eval(123) = " & Eval(123)) +Call ok(Err.Number = 0, "Eval(123) Err.Number = " & Err.Number) + +Err.Clear +Call ok(Eval(True) = True, "Eval(True) = " & Eval(True)) +Call ok(Err.Number = 0, "Eval(True) Err.Number = " & Err.Number) + +Err.Clear +Call ok(IsNull(Eval(Null)), "Eval(Null) should be Null") +Call ok(Err.Number = 0, "Eval(Null) Err.Number = " & Err.Number) + +Err.Clear +Call ok(IsEmpty(Eval(Empty)), "Eval(Empty) should be Empty") +Call ok(Err.Number = 0, "Eval(Empty) Err.Number = " & Err.Number) + +' Execute with non-string arguments returns type mismatch +Err.Clear +Execute 123 +Call ok(Err.Number = 13, "Execute 123 Err.Number = " & Err.Number) + +Err.Clear +Execute Null +Call ok(Err.Number = 13, "Execute Null Err.Number = " & Err.Number) + +Err.Clear +Execute Empty +Call ok(Err.Number = 13, "Execute Empty Err.Number = " & Err.Number) + +' ExecuteGlobal with non-string arguments returns type mismatch +Err.Clear +ExecuteGlobal 123 +Call ok(Err.Number = 13, "ExecuteGlobal 123 Err.Number = " & Err.Number) + +Err.Clear +ExecuteGlobal Null +Call ok(Err.Number = 13, "ExecuteGlobal Null Err.Number = " & Err.Number) + +Err.Clear +ExecuteGlobal Empty +Call ok(Err.Number = 13, "ExecuteGlobal Empty Err.Number = " & Err.Number) +' Test runtime error in Eval is caught by On Error Resume Next +Err.Clear +Call Eval("CBool(""notabool"")") +Call ok(Err.Number = 13, "Eval type mismatch: Err.Number = " & Err.Number & " expected 13") +Call ok(Err.Source = "Microsoft VBScript runtime error", "Eval type mismatch: Err.Source = """ & Err.Source & """") + +On Error Goto 0 + reportSuccess() diff --git a/dlls/vbscript/vbscript.c b/dlls/vbscript/vbscript.c index bc5a4ea416c..2c891401fff 100644 --- a/dlls/vbscript/vbscript.c +++ b/dlls/vbscript/vbscript.c @@ -93,7 +93,7 @@ static inline BOOL is_started(VBScript *This) || This->state == SCRIPTSTATE_DISCONNECTED; } -static HRESULT exec_global_code(script_ctx_t *ctx, vbscode_t *code, VARIANT *res) +HRESULT exec_global_code(script_ctx_t *ctx, vbscode_t *code, VARIANT *res, BOOL extern_caller) { ScriptDisp *obj = ctx->script_obj; function_t *func_iter, **new_funcs; @@ -191,7 +191,7 @@ static HRESULT exec_global_code(script_ctx_t *ctx, vbscode_t *code, VARIANT *res prev_caller = ctx->vbcaller->caller; ctx->vbcaller->caller = SP_CALLER_UNINITIALIZED; - hres = exec_script(ctx, TRUE, &code->main_code, NULL, NULL, res); + hres = exec_script(ctx, extern_caller, &code->main_code, NULL, NULL, res); ctx->vbcaller->caller = prev_caller; return hres; } @@ -202,7 +202,7 @@ static void exec_queued_code(script_ctx_t *ctx) LIST_FOR_EACH_ENTRY(iter, &ctx->code_list, vbscode_t, entry) { if(iter->pending_exec) - exec_global_code(ctx, iter, NULL); + exec_global_code(ctx, iter, NULL, TRUE); } } @@ -1099,7 +1099,7 @@ static HRESULT WINAPI VBScriptParse_ParseScriptText(IActiveScriptParse *iface, return S_OK; } - return exec_global_code(This->ctx, code, pvarResult); + return exec_global_code(This->ctx, code, pvarResult, TRUE); } static const IActiveScriptParseVtbl VBScriptParseVtbl = { diff --git a/dlls/vbscript/vbscript.h b/dlls/vbscript/vbscript.h index a0c8b9f82e8..7d462a7326d 100644 --- a/dlls/vbscript/vbscript.h +++ b/dlls/vbscript/vbscript.h @@ -205,6 +205,9 @@ struct _script_ctx_t { BuiltinDisp *global_obj; BuiltinDisp *err_obj; + void *current_exec; + void *caller_exec; + EXCEPINFO ei; vbscode_t *error_loc_code; unsigned error_loc_offset; @@ -387,9 +390,13 @@ static inline void grab_vbscode(vbscode_t *code) } void release_vbscode(vbscode_t*); +/* Internal flag to suppress OnScriptError reporting - used by Eval/Execute/ExecuteGlobal */ +#define SCRIPTTEXT_NOERRORREPORT 0x80000000 HRESULT compile_script(script_ctx_t*,const WCHAR*,const WCHAR*,const WCHAR*,DWORD_PTR,unsigned,DWORD,vbscode_t**); HRESULT compile_procedure(script_ctx_t*,const WCHAR*,const WCHAR*,const WCHAR*,DWORD_PTR,unsigned,DWORD,class_desc_t**); HRESULT exec_script(script_ctx_t*,BOOL,function_t*,vbdisp_t*,DISPPARAMS*,VARIANT*); +HRESULT exec_global_code(script_ctx_t*,vbscode_t*,VARIANT*,BOOL); +BOOL is_exec_local_scope(void*); void release_dynamic_var(dynamic_var_t*); named_item_t *lookup_named_item(script_ctx_t*,const WCHAR*,unsigned); void release_named_item(named_item_t*); diff --git a/dlls/vbscript/vbscript.rc b/dlls/vbscript/vbscript.rc index b9a1c3ad5c3..dad78421dbc 100644 --- a/dlls/vbscript/vbscript.rc +++ b/dlls/vbscript/vbscript.rc @@ -59,6 +59,7 @@ STRINGTABLE VBSE_INVALID_TYPELIB_VARIABLE "Variable uses an Automation type not supported in VBScript" VBSE_SERVER_NOT_FOUND "The remote server machine does not exist or is unavailable" VBSE_UNQUALIFIED_REFERENCE "Invalid or unqualified reference" + VBSE_SYNTAX_ERROR "Syntax error" VBS_COMPILE_ERROR "Microsoft VBScript compilation error" VBS_RUNTIME_ERROR "Microsoft VBScript runtime error" diff --git a/dlls/vbscript/vbscript_defs.h b/dlls/vbscript/vbscript_defs.h index e0e9d1cac94..d61db1ee8a2 100644 --- a/dlls/vbscript/vbscript_defs.h +++ b/dlls/vbscript/vbscript_defs.h @@ -270,6 +270,7 @@ #define VBSE_INVALID_TYPELIB_VARIABLE 458 #define VBSE_SERVER_NOT_FOUND 462 #define VBSE_UNQUALIFIED_REFERENCE 505 +#define VBSE_SYNTAX_ERROR 1002 #define VBS_COMPILE_ERROR 4096 #define VBS_RUNTIME_ERROR 4097 -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10368
participants (2)
-
Francis De Brabandere -
Francis De Brabandere (@francisdb)