closes https://bugs.winehq.org/show_bug.cgi?id=56464
-- v6: vbscript: implement Join()
From: Francis De Brabandere francisdb@gmail.com
--- dlls/vbscript/global.c | 129 +++++++++++++++++++++++++++++++++++- dlls/vbscript/tests/api.vbs | 95 ++++++++++++++++++++++++++ 2 files changed, 221 insertions(+), 3 deletions(-)
diff --git a/dlls/vbscript/global.c b/dlls/vbscript/global.c index 972d0d2ea0b..10d5709e73c 100644 --- a/dlls/vbscript/global.c +++ b/dlls/vbscript/global.c @@ -2581,10 +2581,133 @@ static HRESULT Global_Filter(BuiltinDisp *This, VARIANT *arg, unsigned args_cnt, return E_NOTIMPL; }
-static HRESULT Global_Join(BuiltinDisp *This, VARIANT *arg, unsigned args_cnt, VARIANT *res) +static HRESULT Global_Join(BuiltinDisp *This, VARIANT *args, unsigned args_cnt, VARIANT *res) { - FIXME("\n"); - return E_NOTIMPL; + BSTR delimiter = NULL, output = NULL, str = NULL; + BOOL free_delimiter = FALSE; + SAFEARRAY *sa; + HRESULT hres; + LONG lbound, ubound; + VARIANT *data; + UINT total_len = 0, delimiter_len = 0, str_len; + WCHAR *output_ptr; + INT i; + + assert(1 <= args_cnt && args_cnt <= 2); + + switch(V_VT(args)) { + case VT_NULL: + return MAKE_VBSERROR(VBSE_ILLEGAL_NULL_USE); + case VT_DISPATCH: + return MAKE_VBSERROR(VBSE_OLE_NO_PROP_OR_METHOD); + case VT_VARIANT|VT_ARRAY: + sa = V_ARRAY(args); + break; + case VT_VARIANT|VT_ARRAY|VT_BYREF: + sa = *V_ARRAYREF(args); + break; + default: + return MAKE_VBSERROR(VBSE_TYPE_MISMATCH); + } + + if (args_cnt == 2) { + if (V_VT(args + 1) == VT_NULL) + return MAKE_VBSERROR(VBSE_ILLEGAL_NULL_USE); + if (V_VT(args + 1) != VT_BSTR) { + hres = to_string(args + 1, &delimiter); + if (FAILED(hres)) + return hres; + } else { + delimiter = V_BSTR(args + 1); + } + } else { + delimiter = SysAllocString(L" "); + if (!delimiter) + return E_OUTOFMEMORY; + free_delimiter = TRUE; + } + + if (SafeArrayGetDim(sa) != 1) { + hres = MAKE_VBSERROR(VBSE_TYPE_MISMATCH); + goto cleanup; + } + + hres = SafeArrayGetLBound(sa, 1, &lbound); + if (FAILED(hres)) + goto cleanup; + + hres = SafeArrayGetUBound(sa, 1, &ubound); + if (FAILED(hres)) + goto cleanup; + + hres = SafeArrayAccessData(sa, (void**)&data); + if (FAILED(hres)) + goto cleanup; + + delimiter_len = SysStringLen(delimiter); + + for (i = lbound; i <= ubound; i++) { + if (V_VT(&data[i]) != VT_BSTR) { + hres = to_string(&data[i], &str); + if (FAILED(hres)) + goto cleanup_data; + } else { + str = V_BSTR(&data[i]); + } + + total_len += SysStringLen(str); + if (i > lbound) + total_len += delimiter_len; + + if (V_VT(&data[i]) != VT_BSTR) + SysFreeString(str); + } + + output = SysAllocStringLen(NULL, total_len); + if (!output) { + hres = E_OUTOFMEMORY; + goto cleanup_data; + } + + output_ptr = output; + + for (i = lbound; i <= ubound; i++) { + if (V_VT(&data[i]) != VT_BSTR) { + hres = to_string(&data[i], &str); + if (FAILED(hres)) + goto cleanup_output; + } else { + str = V_BSTR(&data[i]); + } + + if (i > lbound) { + memcpy(output_ptr, delimiter, delimiter_len * sizeof(WCHAR)); + output_ptr += delimiter_len; + } + + str_len = SysStringLen(str); + memcpy(output_ptr, str, str_len * sizeof(WCHAR)); + output_ptr += str_len; + + if (V_VT(&data[i]) != VT_BSTR) + SysFreeString(str); + } + + *output_ptr = L'\0'; + SafeArrayUnaccessData(sa); + if (free_delimiter) + SysFreeString(delimiter); + + return return_bstr(res, output); + +cleanup_output: + SysFreeString(output); +cleanup_data: + SafeArrayUnaccessData(sa); +cleanup: + if (free_delimiter) + SysFreeString(delimiter); + return hres; }
static HRESULT Global_Split(BuiltinDisp *This, VARIANT *args, unsigned args_cnt, VARIANT *res) diff --git a/dlls/vbscript/tests/api.vbs b/dlls/vbscript/tests/api.vbs index dfa2816bec5..7a27912279a 100644 --- a/dlls/vbscript/tests/api.vbs +++ b/dlls/vbscript/tests/api.vbs @@ -22,6 +22,45 @@ Dim x Class EmptyClass End Class
+' Returns the amount of dimensions of an array. +' Returns 0 when it is not an array +Function GetDimensions(arr) + Dim dimension, upperBound + On error resume next + For dimension = 1 to 255 + upperBound = ubound(arr, dimension) + If err.Number <> 0 Then Exit for + Next + On error goto 0 + GetDimensions = dimension-1 +End Function + +' Helper function to print a variable +Function ToString(x) + If IsEmpty(x) Then + ToString = "Empty" + ElseIf IsNull(x) Then + ToString = "Null" + ElseIf IsObject(x) Then + ToString = "Object" + ElseIf IsArray(x) Then + Dim i, arrStr + arrStr = "Array(" + If GetDimensions(x) = 1 Then + For i = LBound(x) To UBound(x) + arrStr = arrStr & ToString(x(i)) + If i < UBound(x) Then arrStr = arrStr & ", " + Next + Else + arrStr = arrStr & "...multidim..." + End If + arrStr = arrStr & ")" + ToString = arrStr + Else + ToString = CStr(x) + End If +End Function + Call ok(vbSunday = 1, "vbSunday = " & vbSunday) Call ok(getVT(vbSunday) = "VT_I2", "getVT(vbSunday) = " & getVT(vbSunday)) Call ok(vbMonday = 2, "vbMonday = " & vbMonday) @@ -713,6 +752,62 @@ TestLCase 0.123, doubleAsString(0.123) TestLCase Empty, "" Call ok(getVT(LCase(Null)) = "VT_NULL", "getVT(LCase(Null)) = " & getVT(LCase(Null)))
+' Join + +Sub TestJoin(arg, ex) + x = Join(arg) + Call ok(x = ex, "Join(" & ToString(arg) & ") = " & x & " expected " & ex) +End Sub + +Sub TestJoin2(arg1, arg2, ex) + x = Join(arg1, arg2) + Call ok(x = ex, "Join(" & ToString(arg1) & "," & arg2 & ") = " & x & " expected " & ex) +End Sub + +Sub TestJoinError(arg, num) + On Error Resume Next + Call Join(arg) + Dim err_num: err_num = Err.number + Call Err.clear() + On Error GoTo 0 + Call ok(err_num = num, "Join(" & ToString(arg) & ") expected Err.number = " & num & " got " & err_num) +End Sub + +TestJoin Array(), "" +TestJoin Array("a", "b", "c"), "a b c" +TestJoin Array("a", "b", "c", 1, 2, 3), "a b c 1 2 3" +TestJoin Array(1, Empty), "1 " + +TestJoin2 Array(), "", "" +TestJoin2 Array("a"), "-", "a" +TestJoin2 Array("a", "b"), "-", "a-b" +TestJoin2 Array("a", "b", "c"), "", "abc" +TestJoin2 Array(1, "Hello"), "-", "1-Hello" +TestJoin2 Array("a", "b", "c"), Empty, "abc" + +TestJoinError Null , 94 +TestJoinError Empty, 13 +TestJoinError 1, 13 +TestJoinError "test", 13 +TestJoinError 1.2, 13 +TestJoinError New EmptyClass, 438 +TestJoinError Array(1, Null), 13 +TestJoinError Array(Array(1, 2), Array(3, 4)), 13 +Dim multidim(2, 2) +TestJoinError multidim, 13 + +On Error Resume Next +Call Join(Array(), Null) +Call ok(Err.number = 94, "Join(Array(), Null) expected Err.number = 94 got " & Err.number) +Call Err.clear +On Error GoTo 0 + +On Error Resume Next +Call Join(Array(), "a", "b") +Call ok(Err.number = 450, "Join(Array(), ""a"", ""b"") expected Err.number = 450 got " & Err.number) +Call Err.clear +On Error GoTo 0 + x=Split("abc") Call ok(x(0) = "abc", "Split(""abc"")(0)=" & x(0)) x = Split("abc def")
Hi,
It looks like your patch introduced the new failures shown below. Please investigate and fix them before resubmitting your patch. If they are not new, fixing them anyway would help a lot. Otherwise please ask for the known failures list to be updated.
The tests also ran into some preexisting test failures. If you know how to fix them that would be helpful. See the TestBot job for the details:
The full results can be found at: https://testbot.winehq.org/JobDetails.pl?Key=150814
Your paranoid android.
=== debian11b (64 bit WoW report) ===
dnsapi: query.c:142: Test failed: expected record name L"testbot.winehq.org", got L"becky.ns.cloudflare.com"
secur32: schannel.c:1206: Test failed: QueryContextAttributesW(SECPKG_ATTR_ENDPOINT_BINDINGS) failed: 00000057 schannel.c:1284: Test failed: got L"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384" schannel.c:1298: Test failed: got L"ECDSA" schannel.c:1307: Test failed: key_info.SignatureAlgorithm = 2203 schannel.c:1309: Test failed: key_info.sSignatureAlgorithmName = ECDSA
wininet: http.c:6917: Test failed: lpszSubjectInfo = test.winehq.org
On Thu Jan 9 10:15:34 2025 +0000, Francis De Brabandere wrote:
changed this line in [version 6 of the diff](/wine/wine/-/merge_requests/7052/diffs?diff_id=151665&start_sha=10bc052a9aaa4de4c82ebac6a935436d4cb6c3c1#f705ff2124202b08096a71599eedf1d31dff92a3_2631_2630)
I checked on windows and this now returns VBSE_TYPE_MISMATCH
On Thu Jan 9 10:15:34 2025 +0000, Francis De Brabandere wrote:
changed this line in [version 6 of the diff](/wine/wine/-/merge_requests/7052/diffs?diff_id=151665&start_sha=10bc052a9aaa4de4c82ebac6a935436d4cb6c3c1#f705ff2124202b08096a71599eedf1d31dff92a3_2680_2689)
I think I improved this.
Jacek Caban (@jacek) commented about dlls/vbscript/global.c:
return MAKE_VBSERROR(VBSE_OLE_NO_PROP_OR_METHOD);
case VT_VARIANT|VT_ARRAY:
sa = V_ARRAY(args);
break;
case VT_VARIANT|VT_ARRAY|VT_BYREF:
sa = *V_ARRAYREF(args);
break;
default:
return MAKE_VBSERROR(VBSE_TYPE_MISMATCH);
- }
- if (args_cnt == 2) {
if (V_VT(args + 1) == VT_NULL)
return MAKE_VBSERROR(VBSE_ILLEGAL_NULL_USE);
if (V_VT(args + 1) != VT_BSTR) {
hres = to_string(args + 1, &delimiter);
This also allocates `delimiter`, so we should free it on cleanup.
Jacek Caban (@jacek) commented about dlls/vbscript/global.c:
default:
return MAKE_VBSERROR(VBSE_TYPE_MISMATCH);
- }
- if (args_cnt == 2) {
if (V_VT(args + 1) == VT_NULL)
return MAKE_VBSERROR(VBSE_ILLEGAL_NULL_USE);
if (V_VT(args + 1) != VT_BSTR) {
hres = to_string(args + 1, &delimiter);
if (FAILED(hres))
return hres;
} else {
delimiter = V_BSTR(args + 1);
}
- } else {
delimiter = SysAllocString(L" ");
This isn't a major issue, but allocating delimiter dynamically here might be unnecessary. Consider keeping `free_delimiter` as a `BSTR` and `delimiter` as a `const WCHAR *`. In this approach, you could set delimiter to `L" "` here and leave `free_delimiter` as `NULL` for this path (only assigning it in the branch that uses `to_string`).
With this change, you wouldn’t be able to use `SysStringLen` directly on `delimiter`. However, if you move the `delimiter_len` assignment here, you can set it separately for each relevant branch.
Jacek Caban (@jacek) commented about dlls/vbscript/global.c:
- if (FAILED(hres))
goto cleanup;
- hres = SafeArrayGetUBound(sa, 1, &ubound);
- if (FAILED(hres))
goto cleanup;
- hres = SafeArrayAccessData(sa, (void**)&data);
- if (FAILED(hres))
goto cleanup;
- delimiter_len = SysStringLen(delimiter);
- for (i = lbound; i <= ubound; i++) {
if (V_VT(&data[i]) != VT_BSTR) {
hres = to_string(&data[i], &str);
When `to_string` is called on an object, it internally invokes `Invoke(DISPID_VALUE)` on that object. This means that performing the conversion twice (once here and again in the later loop) can cause visible side effects in the script.
To avoid this, we could allocate an array of `BSTR`s, store the results of `to_string` in that array, and reuse them in the subsequent loop.