[PATCH v6 0/2] MR10818: vbscript: Cover non-literal numeric vs whitespace-only BSTR.
Native VBScript treats a whitespace-only BSTR (e.g. Space(N)) as larger than any numeric in BSTR-vs-numeric comparison, regardless of binary lex order. Wine's CStr-coerce path goes through VarCmp's binary order and gets the opposite result for single-digit numerics, breaking the practical Left(str, n) guard pattern Len(str) > Space(n). continuation of !10775 -- v6: vbscript: Treat all-whitespace and control-char BSTR as greater than numeric. vbscript/tests: Cover non-literal numeric vs whitespace-only and control-char BSTR. https://gitlab.winehq.org/wine/wine/-/merge_requests/10818
From: Francis De Brabandere <francisdb@gmail.com> Native VBScript treats a BSTR as larger than any numeric in BSTR-vs-numeric comparison, regardless of binary lex order, when the BSTR is non-empty and either all-whitespace (e.g. Space(N)) or contains any C0 control character (Chr(0)..Chr(31)). Wine's CStr- coerce path goes through VarCmp's binary order and gets the opposite result for single-digit numerics, breaking the practical Left(str, n) guard pattern Len(str) > Space(n). --- dlls/vbscript/tests/lang.vbs | 41 ++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/dlls/vbscript/tests/lang.vbs b/dlls/vbscript/tests/lang.vbs index cc33d6951af..156e2f46103 100644 --- a/dlls/vbscript/tests/lang.vbs +++ b/dlls/vbscript/tests/lang.vbs @@ -317,6 +317,47 @@ Call ok(not ("10" > CDbl(5)), """10"" > CDbl(5) should be false (lex)") Call ok(not ("10" > CSng(5)), """10"" > CSng(5) should be false (lex)") Call ok(not ("9" < CDbl(10)), """9"" < CDbl(10) should be false (lex)") +Dim ws_space : ws_space = Space(3) +Dim ws_tab : ws_tab = Chr(9) +Dim ws_lf : ws_lf = Chr(10) +Dim ws_cr : ws_cr = Chr(13) +Dim ws_nbsp : ws_nbsp = Chr(160) +Call todo_wine_ok(not (Len("ab") > ws_space), "Len(""ab"") > Space(3) should be false") +Call todo_wine_ok((Len("ab") < ws_space), "Len(""ab"") < Space(3) should be true") +Call ok(not (Len("ab") = ws_space), "Len(""ab"") = Space(3) should be false") +Call todo_wine_ok(not (Len("ab") > ws_tab), "Len(""ab"") > Chr(9) should be false") +Call todo_wine_ok((Len("ab") < ws_tab), "Len(""ab"") < Chr(9) should be true") +Call todo_wine_ok(not (Len("ab") > ws_lf), "Len(""ab"") > Chr(10) should be false") +Call todo_wine_ok((Len("ab") < ws_lf), "Len(""ab"") < Chr(10) should be true") +Call todo_wine_ok(not (Len("ab") > ws_cr), "Len(""ab"") > Chr(13) should be false") +Call todo_wine_ok((Len("ab") < ws_cr), "Len(""ab"") < Chr(13) should be true") +Call ok(not (Len("ab") > ws_nbsp), "Len(""ab"") > Chr(160) should be false") +Call ok((Len("ab") < ws_nbsp), "Len(""ab"") < Chr(160) should be true") + +Dim ctl_nul : ctl_nul = Chr(0) +Dim ctl_soh : ctl_soh = Chr(1) +Dim ctl_us : ctl_us = Chr(31) +Call todo_wine_ok((Len("ab") < ctl_nul), "Len(""ab"") < Chr(0) should be true") +Call todo_wine_ok((Len("ab") < ctl_soh), "Len(""ab"") < Chr(1) should be true") +Call todo_wine_ok((Len("ab") < ctl_us), "Len(""ab"") < Chr(31) should be true") +Call todo_wine_ok((Len("ab") < (ctl_nul & "5")), "Len(""ab"") < Chr(0)&""5"" should be true") +Call todo_wine_ok((Len("ab") < (ctl_nul & " ")), "Len(""ab"") < Chr(0)&"" "" should be true") +Call todo_wine_ok((Len("ab") < (" " & ctl_nul)), "Len(""ab"") < "" ""&Chr(0) should be true") + +Call ok((Len("ab") > ""), "Len(""ab"") > """" should be true") + +Dim guard_str : guard_str = "ab" +Dim guard_err, guard_r +On Error Resume Next +Err.Clear +If Len(guard_str) > Space(3) Then + guard_r = Left(guard_str, Space(3)) +End If +guard_err = Err.number +Err.Clear +On Error Goto 0 +Call todo_wine_ok(guard_err = 0, "Len(""ab"") > Space(3) guard should not raise (got " & guard_err & ")") + ' --- VT_DATE from CDate is non-literal: string compare, no error. --- Dim cdt : cdt = CDate("2024-01-15") Call ok(not ("abc" = cdt), "CDate: ""abc"" = CDate(...) should be false") -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10818
From: Francis De Brabandere <francisdb@gmail.com> Native VBScript sorts a non-empty BSTR higher than any numeric or boolean operand, regardless of the binary lex order their CStr forms would produce, when the BSTR is either all-whitespace (Space, tab, LF, CR, NBSP, ...) or contains any C0 control character (Chr(0).. Chr(31)). Add an early-out in var_cmp before the CStr-coerce path. BSTRs are length-prefixed and may contain embedded NUL, so the scan walks SysStringLen characters rather than stopping at NUL. --- dlls/vbscript/interp.c | 22 ++++++++++++++++++++++ dlls/vbscript/tests/lang.vbs | 30 +++++++++++++++--------------- 2 files changed, 37 insertions(+), 15 deletions(-) diff --git a/dlls/vbscript/interp.c b/dlls/vbscript/interp.c index 7ff3baecce8..2ed97da1cce 100644 --- a/dlls/vbscript/interp.c +++ b/dlls/vbscript/interp.c @@ -2168,9 +2168,31 @@ static HRESULT var_cmp(exec_ctx_t *ctx, VARIANT *l, VARIANT *r, unsigned flags) (rvt == VT_BSTR && (is_numeric_vt(lvt) || lvt == VT_BOOL))) { VARIANT *num = lvt == VT_BSTR ? r : l; VARIANT *str = lvt == VT_BSTR ? l : r; + BSTR str_bstr = V_BSTR(str); VARIANT num_str; HRESULT hres; + /* Native treats a BSTR as greater than any numeric or boolean, + * regardless of binary lex order, when it is non-empty and either + * all-whitespace or contains any C0 control character (Chr(0).. + * Chr(31)). BSTRs are length-prefixed and may contain embedded + * NUL, so use SysStringLen rather than NUL-terminated scan. */ + if(str_bstr) { + UINT len = SysStringLen(str_bstr); + BOOL has_ctrl = FALSE, all_space = TRUE; + UINT i; + for(i = 0; i < len; i++) { + if(str_bstr[i] < 0x20) { + has_ctrl = TRUE; + break; + } + if(!iswspace(str_bstr[i])) + all_space = FALSE; + } + if(len && (has_ctrl || all_space)) + return lvt == VT_BSTR ? VARCMP_GT : VARCMP_LT; + } + VariantInit(&num_str); if((V_VT(num) & VT_TYPEMASK) == VT_BOOL) { V_VT(&num_str) = VT_BSTR; diff --git a/dlls/vbscript/tests/lang.vbs b/dlls/vbscript/tests/lang.vbs index 156e2f46103..02e9781b01b 100644 --- a/dlls/vbscript/tests/lang.vbs +++ b/dlls/vbscript/tests/lang.vbs @@ -322,27 +322,27 @@ Dim ws_tab : ws_tab = Chr(9) Dim ws_lf : ws_lf = Chr(10) Dim ws_cr : ws_cr = Chr(13) Dim ws_nbsp : ws_nbsp = Chr(160) -Call todo_wine_ok(not (Len("ab") > ws_space), "Len(""ab"") > Space(3) should be false") -Call todo_wine_ok((Len("ab") < ws_space), "Len(""ab"") < Space(3) should be true") +Call ok(not (Len("ab") > ws_space), "Len(""ab"") > Space(3) should be false") +Call ok((Len("ab") < ws_space), "Len(""ab"") < Space(3) should be true") Call ok(not (Len("ab") = ws_space), "Len(""ab"") = Space(3) should be false") -Call todo_wine_ok(not (Len("ab") > ws_tab), "Len(""ab"") > Chr(9) should be false") -Call todo_wine_ok((Len("ab") < ws_tab), "Len(""ab"") < Chr(9) should be true") -Call todo_wine_ok(not (Len("ab") > ws_lf), "Len(""ab"") > Chr(10) should be false") -Call todo_wine_ok((Len("ab") < ws_lf), "Len(""ab"") < Chr(10) should be true") -Call todo_wine_ok(not (Len("ab") > ws_cr), "Len(""ab"") > Chr(13) should be false") -Call todo_wine_ok((Len("ab") < ws_cr), "Len(""ab"") < Chr(13) should be true") +Call ok(not (Len("ab") > ws_tab), "Len(""ab"") > Chr(9) should be false") +Call ok((Len("ab") < ws_tab), "Len(""ab"") < Chr(9) should be true") +Call ok(not (Len("ab") > ws_lf), "Len(""ab"") > Chr(10) should be false") +Call ok((Len("ab") < ws_lf), "Len(""ab"") < Chr(10) should be true") +Call ok(not (Len("ab") > ws_cr), "Len(""ab"") > Chr(13) should be false") +Call ok((Len("ab") < ws_cr), "Len(""ab"") < Chr(13) should be true") Call ok(not (Len("ab") > ws_nbsp), "Len(""ab"") > Chr(160) should be false") Call ok((Len("ab") < ws_nbsp), "Len(""ab"") < Chr(160) should be true") Dim ctl_nul : ctl_nul = Chr(0) Dim ctl_soh : ctl_soh = Chr(1) Dim ctl_us : ctl_us = Chr(31) -Call todo_wine_ok((Len("ab") < ctl_nul), "Len(""ab"") < Chr(0) should be true") -Call todo_wine_ok((Len("ab") < ctl_soh), "Len(""ab"") < Chr(1) should be true") -Call todo_wine_ok((Len("ab") < ctl_us), "Len(""ab"") < Chr(31) should be true") -Call todo_wine_ok((Len("ab") < (ctl_nul & "5")), "Len(""ab"") < Chr(0)&""5"" should be true") -Call todo_wine_ok((Len("ab") < (ctl_nul & " ")), "Len(""ab"") < Chr(0)&"" "" should be true") -Call todo_wine_ok((Len("ab") < (" " & ctl_nul)), "Len(""ab"") < "" ""&Chr(0) should be true") +Call ok((Len("ab") < ctl_nul), "Len(""ab"") < Chr(0) should be true") +Call ok((Len("ab") < ctl_soh), "Len(""ab"") < Chr(1) should be true") +Call ok((Len("ab") < ctl_us), "Len(""ab"") < Chr(31) should be true") +Call ok((Len("ab") < (ctl_nul & "5")), "Len(""ab"") < Chr(0)&""5"" should be true") +Call ok((Len("ab") < (ctl_nul & " ")), "Len(""ab"") < Chr(0)&"" "" should be true") +Call ok((Len("ab") < (" " & ctl_nul)), "Len(""ab"") < "" ""&Chr(0) should be true") Call ok((Len("ab") > ""), "Len(""ab"") > """" should be true") @@ -356,7 +356,7 @@ End If guard_err = Err.number Err.Clear On Error Goto 0 -Call todo_wine_ok(guard_err = 0, "Len(""ab"") > Space(3) guard should not raise (got " & guard_err & ")") +Call ok(guard_err = 0, "Len(""ab"") > Space(3) guard should not raise (got " & guard_err & ")") ' --- VT_DATE from CDate is non-literal: string compare, no error. --- Dim cdt : cdt = CDate("2024-01-15") -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10818
This merge request was approved by Jacek Caban. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10818
participants (3)
-
Francis De Brabandere -
Francis De Brabandere (@francisdb) -
Jacek Caban (@jacek)