From: Francis De Brabandere <francisdb@gmail.com> VarCmp falls into its "BSTR always greater" branch for BSTR vs VT_UI1 / VT_CY; VBScript instead coerces the numeric side to its CStr form and string-compares. Non-numeric BSTRs return False with no error (unlike VT_I2/I4 which raise type-mismatch), and relational operators use lex order. Also strips VT_BYREF before dispatching so a ByRef BSTR doesn't silently fall through to plain VarCmp. Folds the VT_UI1 / VT_CY divergences out of is_numeric_vt into a new is_string_compared_vt, and routes VT_BOOL through the same branch (the only difference being that bool's BSTR form is hardcoded "True"/"False" rather than what VarBstrFromBool yields). --- dlls/vbscript/interp.c | 57 ++++++++++++++++++++++++------------ dlls/vbscript/tests/lang.vbs | 27 ++++------------- 2 files changed, 45 insertions(+), 39 deletions(-) diff --git a/dlls/vbscript/interp.c b/dlls/vbscript/interp.c index ce4cdd8cf87..823585ccad5 100644 --- a/dlls/vbscript/interp.c +++ b/dlls/vbscript/interp.c @@ -2110,16 +2110,29 @@ static HRESULT interp_imp(exec_ctx_t *ctx) static inline BOOL is_numeric_vt(VARTYPE vt) { return vt == VT_I2 || vt == VT_I4 || vt == VT_R4 || vt == VT_R8 - || vt == VT_CY || vt == VT_DATE || vt == VT_UI1; + || vt == VT_DATE; +} + +/* VBScript treats some numeric types (VT_UI1, VT_CY) by converting them to + * BSTR and string-comparing, rather than coercing the BSTR side to numeric. + * Non-numeric BSTRs return False with no error and relational operators use + * lex order. VT_BOOL behaves the same way but with hardcoded "True"/"False" + * since VarBstrFromBool yields "-1"/"0". */ +static inline BOOL is_string_compared_vt(VARTYPE vt) +{ + return vt == VT_UI1 || vt == VT_CY; } static HRESULT var_cmp(exec_ctx_t *ctx, VARIANT *l, VARIANT *r) { + VARTYPE lvt = V_VT(l) & VT_TYPEMASK; + VARTYPE rvt = V_VT(r) & VT_TYPEMASK; + TRACE("%s %s\n", debugstr_variant(l), debugstr_variant(r)); /* VarCmp would use string comparison; VBScript converts the string to a number. */ - if((V_VT(l) == VT_BSTR && is_numeric_vt(V_VT(r))) || - (V_VT(r) == VT_BSTR && is_numeric_vt(V_VT(l)))) { + if((lvt == VT_BSTR && is_numeric_vt(rvt)) || + (rvt == VT_BSTR && is_numeric_vt(lvt))) { double dl, dr; HRESULT hres; @@ -2136,23 +2149,31 @@ static HRESULT var_cmp(exec_ctx_t *ctx, VARIANT *l, VARIANT *r) return VARCMP_EQ; } - /* VarCmp returns "BSTR always greater" for BSTR vs VT_BOOL; VBScript instead - * coerces the boolean to its CStr form ("True"/"False") and string-compares. - * (VarBstrFromBool yields "-1"/"0" — OLE numeric convention, not what VBScript - * uses.) */ - if((V_VT(l) == VT_BSTR && V_VT(r) == VT_BOOL) || - (V_VT(r) == VT_BSTR && V_VT(l) == VT_BOOL)) { - VARIANT_BOOL b = V_VT(l) == VT_BOOL ? V_BOOL(l) : V_BOOL(r); - VARIANT bool_str; + /* VarCmp returns "BSTR always greater" for BSTR vs VT_BOOL/UI1/CY; VBScript + * instead coerces the numeric side to its CStr form and string-compares. + * (VarBstrFromBool yields "-1"/"0" rather than "True"/"False", so the bool + * strings are hardcoded.) */ + if((lvt == VT_BSTR && (rvt == VT_BOOL || is_string_compared_vt(rvt))) || + (rvt == VT_BSTR && (lvt == VT_BOOL || is_string_compared_vt(lvt)))) { + VARIANT *num = lvt == VT_BSTR ? r : l; + VARIANT *str = lvt == VT_BSTR ? l : r; + VARIANT num_str; HRESULT hres; - V_VT(&bool_str) = VT_BSTR; - V_BSTR(&bool_str) = SysAllocString(b ? L"True" : L"False"); - if(!V_BSTR(&bool_str)) - return E_OUTOFMEMORY; - hres = V_VT(l) == VT_BOOL ? VarCmp(&bool_str, r, 0, 0) - : VarCmp(l, &bool_str, 0, 0); - VariantClear(&bool_str); + VariantInit(&num_str); + if((V_VT(num) & VT_TYPEMASK) == VT_BOOL) { + V_VT(&num_str) = VT_BSTR; + V_BSTR(&num_str) = SysAllocString(V_BOOL(num) ? L"True" : L"False"); + if(!V_BSTR(&num_str)) + return E_OUTOFMEMORY; + }else { + hres = VariantChangeType(&num_str, num, 0, VT_BSTR); + if(FAILED(hres)) + return hres; + } + hres = lvt == VT_BSTR ? VarCmp(str, &num_str, 0, 0) + : VarCmp(&num_str, str, 0, 0); + VariantClear(&num_str); return hres; } diff --git a/dlls/vbscript/tests/lang.vbs b/dlls/vbscript/tests/lang.vbs index 1e10e28e39e..ccee919f238 100644 --- a/dlls/vbscript/tests/lang.vbs +++ b/dlls/vbscript/tests/lang.vbs @@ -222,28 +222,13 @@ Call ok("&H1F" = 31, """&H1F"" = 31 should be true") ' VT_UI1/VT_CY vs BSTR diverge from VT_I2: string-compare against CStr(numeric). ' Non-numeric BSTR returns False with NO error (VT_I2 raises 13 for the same). -on error resume next -err.clear -x = ("abc" = CByte(5)) -saved_err = err.number -err.clear -Call todo_wine_ok(saved_err = 0, """abc"" = CByte(5) should not raise error (err=" & saved_err & ")") -x = ("abc" = CCur(5)) -saved_err = err.number -err.clear -Call todo_wine_ok(saved_err = 0, """abc"" = CCur(5) should not raise error (err=" & saved_err & ")") -x = ("" = CByte(0)) -saved_err = err.number -err.clear -Call todo_wine_ok(saved_err = 0, """"" = CByte(0) should not raise error (err=" & saved_err & ")") -x = ("" = CCur(0)) -saved_err = err.number -err.clear -Call todo_wine_ok(saved_err = 0, """"" = CCur(0) should not raise error (err=" & saved_err & ")") -on error goto 0 +Call ok(not ("abc" = CByte(5)), """abc"" = CByte(5) should be false (no error)") +Call ok(not ("abc" = CCur(5)), """abc"" = CCur(5) should be false (no error)") +Call ok(not ("" = CByte(0)), """"" = CByte(0) should be false (no error)") +Call ok(not ("" = CCur(0)), """"" = CCur(0) should be false (no error)") ' Relational confirms lex compare: 10 > 5 numerically would be true; lex "10" < "5". -Call todo_wine_ok(not ("10" > CByte(5)), """10"" > CByte(5) should be false (lex)") -Call todo_wine_ok(not ("5" < CCur(10)), """5"" < CCur(10) should be false (lex)") +Call ok(not ("10" > CByte(5)), """10"" > CByte(5) should be false (lex)") +Call ok(not ("5" < CCur(10)), """5"" < CCur(10) should be false (lex)") Call ok(getVT(false) = "VT_BOOL", "getVT(false) is not VT_BOOL") Call ok(getVT(true) = "VT_BOOL", "getVT(true) is not VT_BOOL") -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10766