From 2193b9e887b109a7134d3318406be665a45471af Mon Sep 17 00:00:00 2001 From: Andrew Nguyen Date: Wed, 28 Apr 2010 04:20:32 -0500 Subject: [PATCH] kernel32: Correct output buffer behavior with empty input strings for FormatMessageA/W. --- dlls/kernel32/format_msg.c | 48 ++++++++++++++------- dlls/kernel32/tests/format_msg.c | 71 +++++++++++++++++++++++++++++++- 2 files changed, 101 insertions(+), 18 deletions(-) diff --git a/dlls/kernel32/format_msg.c b/dlls/kernel32/format_msg.c index feed8a1974..185c459390 100644 --- a/dlls/kernel32/format_msg.c +++ b/dlls/kernel32/format_msg.c @@ -446,22 +446,30 @@ DWORD WINAPI FormatMessageA( goto failure; TRACE("-- %s\n", debugstr_w(target)); - destlength = WideCharToMultiByte(CP_ACP, 0, target, -1, NULL, 0, NULL, NULL); - if (dwFlags & FORMAT_MESSAGE_ALLOCATE_BUFFER) { - LPSTR buf = LocalAlloc(LMEM_ZEROINIT, max(nSize, destlength)); - WideCharToMultiByte(CP_ACP, 0, target, -1, buf, destlength, NULL, NULL); - *((LPSTR*)lpBuffer) = buf; - } else { - if (nSize < destlength) + + /* Only try writing to an output buffer if there are processed characters + * in the temporary output buffer. */ + if (*target) + { + destlength = WideCharToMultiByte(CP_ACP, 0, target, -1, NULL, 0, NULL, NULL); + if (dwFlags & FORMAT_MESSAGE_ALLOCATE_BUFFER) { - SetLastError(ERROR_INSUFFICIENT_BUFFER); - goto failure; + LPSTR buf = LocalAlloc(LMEM_ZEROINIT, max(nSize, destlength)); + WideCharToMultiByte(CP_ACP, 0, target, -1, buf, destlength, NULL, NULL); + *((LPSTR*)lpBuffer) = buf; } - - WideCharToMultiByte(CP_ACP, 0, target, -1, lpBuffer, destlength, NULL, NULL); + else + { + if (nSize < destlength) + { + SetLastError(ERROR_INSUFFICIENT_BUFFER); + goto failure; + } + WideCharToMultiByte(CP_ACP, 0, target, -1, lpBuffer, destlength, NULL, NULL); + } + ret = destlength - 1; /* null terminator */ } - ret = destlength - 1; /* null terminator */ failure: HeapFree(GetProcessHeap(),0,target); HeapFree(GetProcessHeap(),0,from); @@ -542,10 +550,18 @@ DWORD WINAPI FormatMessageW( talloced = strlenW(target)+1; TRACE("-- %s\n",debugstr_w(target)); - if (dwFlags & FORMAT_MESSAGE_ALLOCATE_BUFFER) { - /* nSize is the MINIMUM size */ - *((LPVOID*)lpBuffer) = LocalAlloc(LMEM_ZEROINIT, max(nSize, talloced)*sizeof(WCHAR)); - strcpyW(*(LPWSTR*)lpBuffer, target); + + /* Only allocate a buffer if there are processed characters in the + * temporary output buffer. If a caller supplies the buffer, then + * a null terminator will be written to it. */ + if (dwFlags & FORMAT_MESSAGE_ALLOCATE_BUFFER) + { + if (*target) + { + /* nSize is the MINIMUM size */ + *((LPVOID*)lpBuffer) = LocalAlloc(LMEM_ZEROINIT, max(nSize, talloced)*sizeof(WCHAR)); + strcpyW(*(LPWSTR*)lpBuffer, target); + } } else { diff --git a/dlls/kernel32/tests/format_msg.c b/dlls/kernel32/tests/format_msg.c index e1f5b3ef0b..9021c09c67 100644 --- a/dlls/kernel32/tests/format_msg.c +++ b/dlls/kernel32/tests/format_msg.c @@ -53,6 +53,7 @@ static DWORD __cdecl doitW(DWORD flags, LPCVOID src, DWORD msg_id, DWORD lang_id static void test_message_from_string_wide(void) { static const WCHAR test[] = {'t','e','s','t',0}; + static const WCHAR empty[] = {0}; static const WCHAR te[] = {'t','e',0}; static const WCHAR st[] = {'s','t',0}; static const WCHAR t[] = {'t',0}; @@ -144,6 +145,25 @@ static void test_message_from_string_wide(void) ok(!lstrcmpW(test, out), "failed out=%s\n", wine_dbgstr_w(out)); ok(r==4, "failed: r=%d\n", r); + /* null string, crashes on Windows */ + if (0) + { + SetLastError(0xdeadbeef); + memcpy(out, init_buf, sizeof(init_buf)); + r = FormatMessageW(FORMAT_MESSAGE_FROM_STRING, NULL, 0, + 0, out, sizeof(out)/sizeof(WCHAR), NULL); + } + + /* empty string */ + SetLastError(0xdeadbeef); + memcpy(out, init_buf, sizeof(init_buf)); + r = FormatMessageW(FORMAT_MESSAGE_FROM_STRING, empty, 0, + 0, out, sizeof(out)/sizeof(WCHAR), NULL); + error = GetLastError(); + ok(!lstrcmpW(empty, out), "failed out=%s\n", wine_dbgstr_w(out)); + ok(r==0, "succeeded: r=%d\n", r); + ok(error==0xdeadbeef, "last error %u\n", error); + /* format placeholder with no specifier */ SetLastError(0xdeadbeef); memcpy(out, init_buf, sizeof(init_buf)); @@ -420,6 +440,27 @@ static void test_message_from_string(void) ok(!strcmp("test", out),"failed out=[%s]\n",out); ok(r==4,"failed: r=%d\n",r); + /* null string, crashes on Windows */ + if (0) + { + SetLastError(0xdeadbeef); + memcpy(out, init_buf, sizeof(init_buf)); + r = FormatMessageA(FORMAT_MESSAGE_FROM_STRING, NULL, 0, + 0, out, sizeof(out)/sizeof(CHAR), NULL); + } + + /* empty string */ + SetLastError(0xdeadbeef); + memcpy(out, init_buf, sizeof(init_buf)); + r = FormatMessageA(FORMAT_MESSAGE_FROM_STRING, "", 0, + 0, out, sizeof(out)/sizeof(CHAR), NULL); + ok(!memcmp(out, init_buf, sizeof(init_buf)) || + broken(!strcmp("", out)), /* Win9x */ + "Expected the buffer to be untouched\n"); + ok(r==0, "succeeded: r=%d\n", r); + ok(GetLastError()==0xdeadbeef, + "last error %u\n", GetLastError()); + /* format placeholder with no specifier */ SetLastError(0xdeadbeef); memcpy(out, init_buf, sizeof(init_buf)); @@ -898,6 +939,15 @@ static void test_message_allocate_buffer(void) * in any case be safe for FormatMessageA to allocate in the manner that * MSDN suggests. */ + SetLastError(0xdeadbeef); + buf = (char *)0xdeadbeef; + ret = FormatMessageA(FORMAT_MESSAGE_FROM_STRING | FORMAT_MESSAGE_ALLOCATE_BUFFER, + "", 0, 0, (char *)&buf, 0, NULL); + ok(ret == 0, "Expected FormatMessageA to return 0, got %u\n", ret); + ok(buf == NULL, "Expected output buffer pointer to be NULL\n"); + ok(GetLastError() == 0xdeadbeef, + "Expected last error to be untouched, got %u\n", GetLastError()); + buf = (char *)0xdeadbeef; ret = FormatMessageA(FORMAT_MESSAGE_FROM_STRING | FORMAT_MESSAGE_ALLOCATE_BUFFER, "test", 0, 0, (char *)&buf, 0, NULL); @@ -966,6 +1016,7 @@ static void test_message_allocate_buffer(void) static void test_message_allocate_buffer_wide(void) { + static const WCHAR empty[] = {0}; static const WCHAR test[] = {'t','e','s','t',0}; DWORD ret; @@ -979,13 +1030,29 @@ static void test_message_allocate_buffer_wide(void) return; } - /* While MSDN suggests that FormatMessageA allocates a buffer whose size is + /* While MSDN suggests that FormatMessageW allocates a buffer whose size is * the larger of the output string and the requested buffer size, the tests * will not try to determine the actual size of the buffer allocated, as * the return value of LocalSize cannot be trusted for the purpose, and it should - * in any case be safe for FormatMessageA to allocate in the manner that + * in any case be safe for FormatMessageW to allocate in the manner that * MSDN suggests. */ + if (0) /* crashes on Windows */ + { + buf = (WCHAR *)0xdeadbeef; + ret = FormatMessageW(FORMAT_MESSAGE_FROM_STRING | FORMAT_MESSAGE_ALLOCATE_BUFFER, + NULL, 0, 0, (WCHAR *)&buf, 0, NULL); + } + + SetLastError(0xdeadbeef); + buf = (WCHAR *)0xdeadbeef; + ret = FormatMessageW(FORMAT_MESSAGE_FROM_STRING | FORMAT_MESSAGE_ALLOCATE_BUFFER, + empty, 0, 0, (WCHAR *)&buf, 0, NULL); + ok(ret == 0, "Expected FormatMessageW to return 0, got %u\n", ret); + ok(buf == NULL, "Expected output buffer pointer to be NULL\n"); + ok(GetLastError() == 0xdeadbeef, + "Expected last error to be untouched, got %u\n", GetLastError()); + buf = (WCHAR *)0xdeadbeef; ret = FormatMessageW(FORMAT_MESSAGE_FROM_STRING | FORMAT_MESSAGE_ALLOCATE_BUFFER, test, 0, 0, (WCHAR *)&buf, 0, NULL); -- 2.32.0.93.g670b81a890