-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Description
Bug Description
To format a time, std::time_put::do_put will call _Wcsftime in a loop until the call succeeds. _Wcsftime is similar to _wcsftime_l, except instead of receiving a locale_t, it accepts data from the _Gettnames internal UCRT API, which returns an internal data structure that describes all time data associated with a locale.
Unlike other implementations of strftime/wcsftime, the UCRT implementation can fail either because there was not enough space in the buffer (errno == ERANGE) or because of an invalid parameter (errno == EINVAL). In a typical case, if an invalid parameter is passed, then the invalid parameter handler is triggered and the program will stop. However, if the invalid parameter handler is changed to not end execution (this is the case for OS code in Windows), then 0 will be returned and errno will be set to EINVAL. The STL time_put::do_put function does not handle this case. Instead, it considers any error an issue of buffer size and will keep increasing the buffer size and retrying until the program crashes.
Command-line test case
C:\Users\amyw\source\invalid_param>type test.cpp
#include <crtdbg.h>
#include <stdlib.h>
#include <iomanip>
#include <sstream>
#define PM_TEST_PASS 100
#define PM_TEST_FAIL 1
void test_invalid_parameter_handler(
const wchar_t * const expression,
const wchar_t * const function,
const wchar_t * const file,
const unsigned int line,
const uintptr_t reserved
)
{
(void) expression;
(void) reserved;
// Stop test early. Without this,
static int num_called = 0;
if (num_called++ > 10) {
wprintf(L"Test Failed: Invalid parameter handler was called over 10 times by %s in %s:%d\n", function, file, line); // These arguments are only populated in debug mode.
exit(PM_TEST_FAIL);
}
}
int main()
{
_set_invalid_parameter_handler(test_invalid_parameter_handler);
#ifdef _DEBUG
// In debug mode, a message box will pop up even if an invalid parameter is set. Configure reports to print to stderr.
_CrtSetReportMode(_CRT_ASSERT, _CRTDBG_MODE_FILE);
_CrtSetReportFile(_CRT_ASSERT, _CRTDBG_FILE_STDERR);
#endif
time_t nullTime = std::time(nullptr);
#pragma warning(suppress: 4996) // localtime deprecated, use localtime_s
tm currentTime = *std::localtime(&nullTime);
std::wstringstream sample;
sample << std::put_time(¤tTime, L"%Y-%m-%d-%H-%M-%s-");
// Invalid parameter, but user requests it's recoverable. badbit should be set, and no others.
if (sample.rdstate() != std::ios_base::badbit) {
return PM_TEST_FAIL;
}
return PM_TEST_PASS;
}
C:\Users\amyw\source\invalid_param>cl /nologo /W4 /WX /EHsc /MDd test.cpp /Zi
test.cpp
C:\Users\amyw\source\invalid_param>test.exe
minkernel\crts\ucrt\src\appcrt\time\wcsftime.cpp(1163) : Assertion failed: false
minkernel\crts\ucrt\src\appcrt\time\wcsftime.cpp(1163) : Assertion failed: false
minkernel\crts\ucrt\src\appcrt\time\wcsftime.cpp(1163) : Assertion failed: false
minkernel\crts\ucrt\src\appcrt\time\wcsftime.cpp(1163) : Assertion failed: false
minkernel\crts\ucrt\src\appcrt\time\wcsftime.cpp(1163) : Assertion failed: false
minkernel\crts\ucrt\src\appcrt\time\wcsftime.cpp(1163) : Assertion failed: false
minkernel\crts\ucrt\src\appcrt\time\wcsftime.cpp(1163) : Assertion failed: false
minkernel\crts\ucrt\src\appcrt\time\wcsftime.cpp(1163) : Assertion failed: false
minkernel\crts\ucrt\src\appcrt\time\wcsftime.cpp(1163) : Assertion failed: false
minkernel\crts\ucrt\src\appcrt\time\wcsftime.cpp(1163) : Assertion failed: false
minkernel\crts\ucrt\src\appcrt\time\wcsftime.cpp(1163) : Assertion failed: false
minkernel\crts\ucrt\src\appcrt\time\wcsftime.cpp(1163) : Assertion failed: false
Test Failed: Invalid parameter handler was called over 10 times by _Wcsftime_l in minkernel\crts\ucrt\src\appcrt\time\wcsftime.cpp:1163
C:\Users\amyw\source\invalid_param>echo %ERRORLEVEL%
1
Expected behavior
I believe time_put::do_put should instead fail gracefully and set the badbit on the stream. This behavior is different from libc++ and libstdc++, as those implementations rely on an implementation of strftime that allows invalid specifiers and no-ops them instead of erroring.
STL version
Microsoft Visual Studio Community 2019 Version 16.8.0
Additional context
Also tracked by DevCom-1240167 and Microsoft-internal VSO-1241150 / AB#1241150