From: Francis De Brabandere <francisdb@gmail.com> Extend compile-time identifier binding to global-scope code. Previously, bind_local() unconditionally skipped binding for FUNC_GLOBAL functions, forcing every global variable access through the string-based lookup_identifier() path at runtime. Now, for code compiled via ParseScriptText (skip_collisions=FALSE), global Dim variables are bound to indexed slots at compile time, emitting OP_local/OP_assign_local instead of OP_ident/OP_assign_ident. At execution time, exec_script() builds a var_refs[] indirection table that maps compile-time indices to their ScriptDisp->global_vars[] storage. This is done once per script block (O(vars * global_vars)), after exec_global_code() has already registered all Dim variables. Execute/ExecuteGlobal code (skip_collisions=TRUE) is not affected: it still skips binding in bind_local() because the destination of its Dim variables (ScriptDisp vs caller dynamic_vars) is not known at compile time. --- dlls/vbscript/compile.c | 14 ++++++++- dlls/vbscript/interp.c | 66 ++++++++++++++++++++++++++++++++++------- 2 files changed, 69 insertions(+), 11 deletions(-) diff --git a/dlls/vbscript/compile.c b/dlls/vbscript/compile.c index c969b9a0058..93a869e6af9 100644 --- a/dlls/vbscript/compile.c +++ b/dlls/vbscript/compile.c @@ -64,6 +64,13 @@ typedef struct { function_t *func; function_decl_t *func_decls; dim_decl_t *class_props; + + /* Set for Execute/ExecuteGlobal: (1) suppresses compile-time binding of + * global Dim variables in bind_local(), since they may end up in the + * caller's dynamic_vars rather than ScriptDisp->global_vars; (2) makes + * check_script_collisions() non-fatal so re-declaring an existing name + * is silently tolerated instead of raising VBSE_NAME_REDEFINED. */ + BOOL skip_collisions; } compile_ctx_t; static HRESULT compile_expression(compile_ctx_t*,expression_t*); @@ -491,7 +498,11 @@ static BOOL bind_local(compile_ctx_t *ctx, const WCHAR *name, int *ret) dim_decl_t *dim_decl; unsigned i; - if(ctx->func->type == FUNC_GLOBAL) + /* Execute/Eval code may run from either global or local scope. + * When run from local scope, Dim vars are placed in the caller's + * dynamic_vars, not in ScriptDisp->global_vars. We cannot bind + * them at compile time because we don't know which case it will be. */ + if(ctx->func->type == FUNC_GLOBAL && ctx->skip_collisions) return FALSE; for(dim_decl = ctx->dim_decls, i = 0; dim_decl; dim_decl = dim_decl->next, i++) { @@ -2325,6 +2336,7 @@ HRESULT compile_script(script_ctx_t *script, const WCHAR *src, const WCHAR *item } memset(&ctx, 0, sizeof(ctx)); + ctx.skip_collisions = skip_collisions; code = ctx.code = alloc_vbscode(&ctx, src, cookie, start_line); if(!ctx.code) return E_OUTOFMEMORY; diff --git a/dlls/vbscript/interp.c b/dlls/vbscript/interp.c index 2ac619cb4ff..e033330c2cb 100644 --- a/dlls/vbscript/interp.c +++ b/dlls/vbscript/interp.c @@ -35,6 +35,9 @@ typedef struct _exec_ctx_t { VARIANT *args; VARIANT *vars; + /* For global-scope code: maps compile-time var indices to their + * ScriptDisp->global_vars[] VARIANT storage. NULL for local scope. */ + VARIANT **var_refs; SAFEARRAY **arrays; dynamic_var_t *dynamic_vars; @@ -873,16 +876,20 @@ static HRESULT interp_mcallv(exec_ctx_t *ctx) return do_mcall(ctx, NULL); } +static inline VARIANT *get_local_var(exec_ctx_t *ctx, int ref) +{ + if(ref < 0) + return ctx->args - ref - 1; + return ctx->var_refs ? ctx->var_refs[ref] : ctx->vars + ref; +} + static HRESULT interp_local(exec_ctx_t *ctx) { const int arg = ctx->instr->arg1.lng; VARIANT *v; VARIANT r; - if(arg < 0) - v = ctx->args - arg - 1; - else - v = ctx->vars + arg; + v = get_local_var(ctx, arg); TRACE("%s\n", debugstr_variant(v)); @@ -1137,7 +1144,7 @@ static HRESULT interp_assign_local(exec_ctx_t *ctx) DISPPARAMS dp; HRESULT hres; - v = ref < 0 ? ctx->args - ref - 1 : ctx->vars + ref; + v = get_local_var(ctx, ref); TRACE("%d\n", ref); @@ -1158,7 +1165,7 @@ static HRESULT interp_set_local(exec_ctx_t *ctx) DISPPARAMS dp; HRESULT hres; - v = ref < 0 ? ctx->args - ref - 1 : ctx->vars + ref; + v = get_local_var(ctx, ref); TRACE("%d %u\n", ref, arg_cnt); @@ -1811,7 +1818,7 @@ static HRESULT interp_step_local(exec_ctx_t *ctx) TRACE("%d\n", ref); - v = ref < 0 ? ctx->args - ref - 1 : ctx->vars + ref; + v = get_local_var(ctx, ref); V_VT(&zero) = VT_I2; V_I2(&zero) = 0; @@ -2770,7 +2777,7 @@ static HRESULT interp_incc_local(exec_ctx_t *ctx) TRACE("%d\n", ref); - lvar = ref < 0 ? ctx->args - ref - 1 : ctx->vars + ref; + lvar = get_local_var(ctx, ref); hres = VarAdd(stack_top(ctx, 0), lvar, &v); if(FAILED(hres)) @@ -2839,8 +2846,16 @@ static void release_exec(exec_ctx_t *ctx) } if(ctx->vars) { - for(i=0; i < ctx->func->var_cnt; i++) - VariantClear(ctx->vars+i); + if(ctx->var_refs) { + /* Only clear fallback slots (vars not found in ScriptDisp) */ + for(i=0; i < ctx->func->var_cnt; i++) { + if(ctx->var_refs[i] == ctx->vars + i) + VariantClear(ctx->vars+i); + } + }else { + for(i=0; i < ctx->func->var_cnt; i++) + VariantClear(ctx->vars+i); + } } if(ctx->arrays) { @@ -2854,6 +2869,7 @@ static void release_exec(exec_ctx_t *ctx) heap_pool_free(&ctx->heap); free(ctx->args); free(ctx->vars); + free(ctx->var_refs); free(ctx->stack); } @@ -2967,6 +2983,36 @@ HRESULT exec_script(script_ctx_t *ctx, BOOL extern_caller, function_t *func, vbd exec.vars = NULL; } + /* For global-scope code (ParseScriptText), resolve compile-time variable + * indices to pointers into ScriptDisp->global_vars[]. This allows OP_local + * and OP_assign_local to access global Dim variables by index instead of + * doing string-based lookup_identifier() at runtime. exec_global_code() + * has already registered all Dim variables into global_vars before this. */ + if(func->type == FUNC_GLOBAL && !exec.caller && func->var_cnt) { + ScriptDisp *obj = func->code_ctx->named_item + ? func->code_ctx->named_item->script_obj : ctx->script_obj; + unsigned i, j; + + exec.var_refs = malloc(func->var_cnt * sizeof(VARIANT*)); + if(!exec.var_refs) { + release_exec(&exec); + return E_OUTOFMEMORY; + } + + for(i = 0; i < func->var_cnt; i++) { + exec.var_refs[i] = NULL; + for(j = 0; j < obj->global_vars_cnt; j++) { + if(!vbs_wcsicmp(func->vars[i].name, obj->global_vars[j]->name)) { + exec.var_refs[i] = &obj->global_vars[j]->v; + break; + } + } + /* If not found (e.g. shadowed by host), fall back to local storage */ + if(!exec.var_refs[i]) + exec.var_refs[i] = exec.vars + i; + } + } + exec.stack_size = 16; exec.top = 0; exec.stack = malloc(exec.stack_size * sizeof(VARIANT)); -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10515