[PATCH 0/2] MR10449: Draft: vbscript: Implement the Erase statement.
Erase reinitializes fixed-size arrays (clearing all elements to their default values) and deallocates dynamic arrays (setting them back to an uninitialized state). -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10449
From: Francis De Brabandere <francisdb@gmail.com> Dim arr() followed by ReDim arr(N) crashed due to a NULL SAFEARRAY pointer dereference. --- dlls/vbscript/interp.c | 2 +- dlls/vbscript/tests/lang.vbs | 21 +++++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/dlls/vbscript/interp.c b/dlls/vbscript/interp.c index 00ef7d93eb6..57adced5042 100644 --- a/dlls/vbscript/interp.c +++ b/dlls/vbscript/interp.c @@ -1348,7 +1348,7 @@ static HRESULT interp_redim(exec_ctx_t *ctx) if(V_ISARRAY(v)) { SAFEARRAY *sa = V_ISBYREF(v) ? *V_ARRAYREF(v) : V_ARRAY(v); - if(sa->fFeatures & FADF_FIXEDSIZE) + if(sa && (sa->fFeatures & FADF_FIXEDSIZE)) return MAKE_VBSERROR(VBSE_ARRAY_LOCKED); } diff --git a/dlls/vbscript/tests/lang.vbs b/dlls/vbscript/tests/lang.vbs index ff34ac8059b..ea677454ee2 100644 --- a/dlls/vbscript/tests/lang.vbs +++ b/dlls/vbscript/tests/lang.vbs @@ -1898,6 +1898,27 @@ call TestReDimPreserveByRef(rx) ok ubound(rx) = 7, "ubound(rx) = " & ubound(rx) ok rx(3) = 2, "rx(3) = " & rx(3) +' ReDim on an uninitialized dynamic array (Dim arr() has a NULL SAFEARRAY pointer) +dim dynarr() +redim dynarr(3) +ok ubound(dynarr) = 3, "ubound(dynarr) = " & ubound(dynarr) +dynarr(0) = "a" +dynarr(3) = "b" +ok dynarr(0) = "a", "dynarr(0) = " & dynarr(0) +ok dynarr(3) = "b", "dynarr(3) = " & dynarr(3) +redim dynarr(5) +ok ubound(dynarr) = 5, "ubound(dynarr) = " & ubound(dynarr) +ok dynarr(0) = empty, "dynarr(0) after redim = " & dynarr(0) + +' ReDim Preserve on an uninitialized dynamic array should also work and retain data +dim dynarr2() +redim preserve dynarr2(3) +ok ubound(dynarr2) = 3, "ubound(dynarr2) = " & ubound(dynarr2) +dynarr2(0) = "x" +redim preserve dynarr2(5) +ok ubound(dynarr2) = 5, "ubound(dynarr2) = " & ubound(dynarr2) +ok dynarr2(0) = "x", "dynarr2(0) after redim preserve = " & dynarr2(0) + Class ArrClass Dim classarr(3) Dim classnoarr() -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10449
From: Francis De Brabandere <francisdb@gmail.com> Erase reinitializes fixed-size arrays (clearing all elements to their default values) and deallocates dynamic arrays (setting them back to an uninitialized state). --- dlls/vbscript/compile.c | 24 +++++++++++++++ dlls/vbscript/interp.c | 58 ++++++++++++++++++++++++++++++++++++ dlls/vbscript/lex.c | 1 + dlls/vbscript/parse.h | 11 +++++++ dlls/vbscript/parser.y | 36 +++++++++++++++++++++- dlls/vbscript/tests/lang.vbs | 30 +++++++++++++++++++ dlls/vbscript/vbscript.h | 1 + 7 files changed, 160 insertions(+), 1 deletion(-) diff --git a/dlls/vbscript/compile.c b/dlls/vbscript/compile.c index 345e3b5291b..83711ca933c 100644 --- a/dlls/vbscript/compile.c +++ b/dlls/vbscript/compile.c @@ -1192,6 +1192,27 @@ static HRESULT compile_redim_statement(compile_ctx_t *ctx, redim_statement_t *st return S_OK; } +static HRESULT compile_erase_statement(compile_ctx_t *ctx, erase_statement_t *stat) +{ + erase_decl_t *decl = stat->erase_decls; + HRESULT hres; + + while(1) { + hres = push_instr_bstr(ctx, OP_erase, decl->name); + if(FAILED(hres)) + return hres; + + if(!emit_catch(ctx, 0)) + return E_OUTOFMEMORY; + + if(!decl->next) + break; + decl = decl->next; + } + + return S_OK; +} + static HRESULT compile_const_statement(compile_ctx_t *ctx, const_statement_t *stat) { const_decl_t *decl, *next_decl = stat->decls; @@ -1520,6 +1541,9 @@ static HRESULT compile_statement(compile_ctx_t *ctx, statement_ctx_t *stat_ctx, case STAT_ONERROR: hres = compile_onerror_statement(ctx, (onerror_statement_t*)stat); break; + case STAT_ERASE: + hres = compile_erase_statement(ctx, (erase_statement_t*)stat); + break; case STAT_REDIM: hres = compile_redim_statement(ctx, (redim_statement_t*)stat); break; diff --git a/dlls/vbscript/interp.c b/dlls/vbscript/interp.c index 57adced5042..59b83488f3c 100644 --- a/dlls/vbscript/interp.c +++ b/dlls/vbscript/interp.c @@ -1439,6 +1439,64 @@ static HRESULT interp_redim_preserve(exec_ctx_t *ctx) return hres; } +static HRESULT interp_erase(exec_ctx_t *ctx) +{ + const BSTR identifier = ctx->instr->arg1.bstr; + VARIANT *v; + SAFEARRAY *array; + ref_t ref; + HRESULT hres; + + TRACE("%s\n", debugstr_w(identifier)); + + hres = lookup_identifier(ctx, identifier, VBDISP_LET, &ref); + if(FAILED(hres)) + return hres; + + if(ref.type != REF_VAR) { + FIXME("got ref.type = %d\n", ref.type); + return E_FAIL; + } + + v = ref.u.v; + if(V_VT(v) == (VT_VARIANT|VT_BYREF)) + v = V_VARIANTREF(v); + + if(!(V_VT(v) & VT_ARRAY)) { + WARN("Erase on non-array type %d\n", V_VT(v)); + return MAKE_VBSERROR(VBSE_TYPE_MISMATCH); + } + + array = V_ISBYREF(v) ? *V_ARRAYREF(v) : V_ARRAY(v); + if(!array) + return S_OK; + + if(array->fFeatures & FADF_FIXEDSIZE) { + /* Fixed-size array: reinitialize all elements to default values. */ + unsigned i, element_cnt = 1; + VARIANT *data; + + for(i = 0; i < array->cDims; i++) + element_cnt *= array->rgsabound[i].cElements; + + hres = SafeArrayAccessData(array, (void**)&data); + if(SUCCEEDED(hres)) { + for(i = 0; i < element_cnt; i++) + VariantClear(&data[i]); + SafeArrayUnaccessData(array); + } + }else { + /* Dynamic array: deallocate it. */ + SafeArrayDestroy(array); + if(V_ISBYREF(v)) + *V_ARRAYREF(v) = NULL; + else + V_ARRAY(v) = NULL; + } + + return hres; +} + static HRESULT interp_step(exec_ctx_t *ctx) { const BSTR ident = ctx->instr->arg2.bstr; diff --git a/dlls/vbscript/lex.c b/dlls/vbscript/lex.c index 75838ac586a..69c7d58de3b 100644 --- a/dlls/vbscript/lex.c +++ b/dlls/vbscript/lex.c @@ -48,6 +48,7 @@ static const struct { {L"empty", tEMPTY}, {L"end", tEND}, {L"eqv", tEQV}, + {L"erase", tERASE}, {L"error", tERROR}, {L"exit", tEXIT}, {L"explicit", tEXPLICIT}, diff --git a/dlls/vbscript/parse.h b/dlls/vbscript/parse.h index 64c3dee2a69..22e03c6cbe6 100644 --- a/dlls/vbscript/parse.h +++ b/dlls/vbscript/parse.h @@ -116,6 +116,7 @@ typedef enum { STAT_DIM, STAT_DOUNTIL, STAT_DOWHILE, + STAT_ERASE, STAT_EXITDO, STAT_EXITFOR, STAT_EXITFUNC, @@ -184,6 +185,16 @@ typedef struct { redim_decl_t *redim_decls; } redim_statement_t; +typedef struct _erase_decl_t { + const WCHAR *name; + struct _erase_decl_t *next; +} erase_decl_t; + +typedef struct { + statement_t stat; + erase_decl_t *erase_decls; +} erase_statement_t; + typedef struct _arg_decl_t { const WCHAR *name; BOOL by_ref; diff --git a/dlls/vbscript/parser.y b/dlls/vbscript/parser.y index 4977358b333..eab3c783463 100644 --- a/dlls/vbscript/parser.y +++ b/dlls/vbscript/parser.y @@ -52,6 +52,8 @@ static statement_t *new_assign_statement(parser_ctx_t*,unsigned,expression_t*,ex static statement_t *new_set_statement(parser_ctx_t*,unsigned,expression_t*,expression_t*); static statement_t *new_dim_statement(parser_ctx_t*,unsigned,dim_decl_t*); static statement_t *new_redim_statement(parser_ctx_t*,unsigned,BOOL,redim_decl_t*); +static erase_decl_t *new_erase_decl(parser_ctx_t*,const WCHAR*); +static statement_t *new_erase_statement(parser_ctx_t*,unsigned,erase_decl_t*); static statement_t *new_while_statement(parser_ctx_t*,unsigned,statement_type_t,expression_t*,statement_t*); static statement_t *new_forto_statement(parser_ctx_t*,unsigned,const WCHAR*,expression_t*,expression_t*,expression_t*,statement_t*); static statement_t *new_foreach_statement(parser_ctx_t*,unsigned,const WCHAR*,expression_t*,statement_t*); @@ -102,6 +104,7 @@ static statement_t *link_statements(statement_t*,statement_t*); dim_decl_t *dim_decl; dim_list_t *dim_list; redim_decl_t *redim_decl; + erase_decl_t *erase_decl; function_decl_t *func_decl; arg_decl_t *arg_decl; class_decl_t *class_decl; @@ -121,7 +124,7 @@ static statement_t *link_statements(statement_t*,statement_t*); %token <string> tNOT tAND tOR tXOR tEQV tIMP %token <string> tIS tMOD %token <string> tCALL tSUB tFUNCTION tGET tLET tCONST -%token <string> tDIM tREDIM tPRESERVE +%token <string> tDIM tREDIM tPRESERVE tERASE %token <string> tIF tELSE tELSEIF tEND tTHEN tEXIT %token <string> tWHILE tWEND tDO tLOOP tUNTIL tFOR tTO tEACH tIN %token <string> tSELECT tCASE tWITH @@ -153,6 +156,7 @@ static statement_t *link_statements(statement_t*,statement_t*); %type <dim_decl> DimDeclList DimDecl MemberDeclList MemberDecl %type <dim_list> DimList %type <redim_decl> ReDimDeclList ReDimDecl +%type <erase_decl> EraseDeclList %type <const_decl> ConstDecl ConstDeclList %type <string> Identifier MemberIdentifier %type <case_clausule> CaseClausules @@ -217,6 +221,7 @@ SimpleStatement { $$ = new_assign_statement(ctx, @$, $1, $3); CHECK_ERROR; } | tDIM DimDeclList { $$ = new_dim_statement(ctx, @$, $2); CHECK_ERROR; } | tREDIM Preserve_opt ReDimDeclList { $$ = new_redim_statement(ctx, @$, $2, $3); CHECK_ERROR; } + | tERASE EraseDeclList { $$ = new_erase_statement(ctx, @$, $2); CHECK_ERROR; } | IfStatement { $$ = $1; } | tWHILE Expression StSep StatementsNl_opt tWEND { $$ = new_while_statement(ctx, @$, STAT_WHILE, $2, $4); CHECK_ERROR; } @@ -273,6 +278,10 @@ ReDimDeclList : ReDimDecl { $$ = $1; } | ReDimDecl ',' ReDimDeclList { $1->next = $3; $$ = $1; } +EraseDeclList + : Identifier { $$ = new_erase_decl(ctx, $1); CHECK_ERROR; } + | Identifier ',' EraseDeclList { $$ = new_erase_decl(ctx, $1); CHECK_ERROR; $$->next = $3; } + DimDeclList : DimDecl { $$ = $1; } | DimDecl ',' DimDeclList { $1->next = $3; $$ = $1; } @@ -930,6 +939,31 @@ static statement_t *new_redim_statement(parser_ctx_t *ctx, unsigned loc, BOOL pr return &stat->stat; } +static erase_decl_t *new_erase_decl(parser_ctx_t *ctx, const WCHAR *name) +{ + erase_decl_t *decl; + + decl = parser_alloc(ctx, sizeof(*decl)); + if(!decl) + return NULL; + + decl->name = name; + decl->next = NULL; + return decl; +} + +static statement_t *new_erase_statement(parser_ctx_t *ctx, unsigned loc, erase_decl_t *decls) +{ + erase_statement_t *stat; + + stat = new_statement(ctx, STAT_ERASE, sizeof(*stat), loc); + if(!stat) + return NULL; + + stat->erase_decls = decls; + return &stat->stat; +} + static elseif_decl_t *new_elseif_decl(parser_ctx_t *ctx, unsigned loc, expression_t *expr, statement_t *stat) { elseif_decl_t *decl; diff --git a/dlls/vbscript/tests/lang.vbs b/dlls/vbscript/tests/lang.vbs index ea677454ee2..1d35ee2037c 100644 --- a/dlls/vbscript/tests/lang.vbs +++ b/dlls/vbscript/tests/lang.vbs @@ -1919,6 +1919,36 @@ redim preserve dynarr2(5) ok ubound(dynarr2) = 5, "ubound(dynarr2) = " & ubound(dynarr2) ok dynarr2(0) = "x", "dynarr2(0) after redim preserve = " & dynarr2(0) +' Erase on fixed-size array: clears elements to default values +dim eraseFixed(3) +eraseFixed(0) = "a" +eraseFixed(1) = 2 +eraseFixed(2) = True +Erase eraseFixed +ok eraseFixed(0) = empty, "eraseFixed(0) after Erase = " & eraseFixed(0) +ok eraseFixed(1) = empty, "eraseFixed(1) after Erase = " & eraseFixed(1) +ok eraseFixed(2) = empty, "eraseFixed(2) after Erase = " & eraseFixed(2) +ok ubound(eraseFixed) = 3, "ubound(eraseFixed) after Erase = " & ubound(eraseFixed) + +' Erase on dynamic array: deallocates the array +dim eraseDyn() +redim eraseDyn(3) +eraseDyn(0) = "x" +Erase eraseDyn +on error resume next +y = eraseDyn(0) +e = err.number +on error goto 0 +todo_wine_ok e = 9, "access after Erase dynamic: err.number = " & e + +' Erase with multiple arrays +dim eraseMulti1(2), eraseMulti2(2) +eraseMulti1(0) = "a" +eraseMulti2(0) = "b" +Erase eraseMulti1, eraseMulti2 +ok eraseMulti1(0) = empty, "eraseMulti1(0) after Erase = " & eraseMulti1(0) +ok eraseMulti2(0) = empty, "eraseMulti2(0) after Erase = " & eraseMulti2(0) + Class ArrClass Dim classarr(3) Dim classnoarr() diff --git a/dlls/vbscript/vbscript.h b/dlls/vbscript/vbscript.h index a0c8b9f82e8..fc4b33651c3 100644 --- a/dlls/vbscript/vbscript.h +++ b/dlls/vbscript/vbscript.h @@ -251,6 +251,7 @@ typedef enum { X(hres, 1, ARG_UINT, 0) \ X(errmode, 1, ARG_INT, 0) \ X(eqv, 1, 0, 0) \ + X(erase, 1, ARG_BSTR, 0) \ X(exp, 1, 0, 0) \ X(gt, 1, 0, 0) \ X(gteq, 1, 0, 0) \ -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10449
This is based on top of !10446 Will move out of draft once that is merged. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10449#note_133752
participants (2)
-
Francis De Brabandere -
Francis De Brabandere (@francisdb)