-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Description
STL shipped with MSVC 14.34 introduced SSO (1ea95df). SSO implementation has changed _STD::_String_val::_Bxty::_Bxty() constructor: previously it zero-initialized _Ptr field (in MSVC 14.33), now it initializes _Buf field (in MSVC 14.34). SSO implementation relies on the fact that _Buf is initialized with zeroes, however this assumption breaks when std::string comes from TU compiled using older STL, where _Ptr field was initialized instead. When std::string in old TU is initialized using a small (>8 and <16) string literal, std::string::c_str() would return pointer to buffer which is not zero terminated properly.
Command-line test case
Compiling TU using MSVC 14.33 without SSO
//projects/msvc-sso-14_33-34_bug/lib_1433
❯ msvc-env x64 14.33
//projects/msvc-sso-14_33-34_bug/lib_1433
❯ type .\lib.cpp
#include <string>
static_assert(_MSC_VER == 1933);
std::string GetStringOld(const char* p) {
return std::string(p);
}
//projects/msvc-sso-14_33-34_bug/lib_1433
❯ cl /c /O2 /EHsc lib.cpp
Microsoft (R) C/C++ Optimizing Compiler Version 19.33.31631 for x64
Copyright (C) Microsoft Corporation. All rights reserved.
lib.cpp
Compiling TU using MSVC 14.34 with SSO
//projects/msvc-sso-14_33-34_bug/exe_1434
❯ msvc-env x64 14.34
//projects/msvc-sso-14_33-34_bug/exe_1434
❯ type exe.cpp
#include <iostream>
#include <string>
static_assert(_MSC_VER == 1934);
std::string GetStringOld(const char* p);
__declspec(noinline) std::string GetStringNew(const char* p) {
return std::string(p);
}
__declspec(noinline) void dirty_stack() {
char trash[32];
memset(trash, '@', sizeof(trash) - 1);
trash[sizeof(trash) - 1] = '\0';
std::cout << trash << '\n';
}
__declspec(noinline) void nobug_new() {
std::cout << GetStringNew("01234567#$").c_str() << '\n';
}
__declspec(noinline) void bug_old() {
std::cout << GetStringOld("01234567#$").c_str() << '\n';
}
int main() {
dirty_stack();
bug_old();
dirty_stack();
nobug_new();
return 0;
}
//projects/msvc-sso-14_33-34_bug/exe_1434
❯ cl /O2 /EHsc .\exe.cpp ..\lib_1433\lib.obj
Microsoft (R) C/C++ Optimizing Compiler Version 19.34.31935 for x64
Copyright (C) Microsoft Corporation. All rights reserved.
exe.cpp
Microsoft (R) Incremental Linker Version 14.34.31935.0
Copyright (C) Microsoft Corporation. All rights reserved.
/out:exe.exe
exe.obj
..\lib_1433\lib.obj
//projects/msvc-sso-14_33-34_bug/exe_1434
❯ .\exe.exe
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
01234567#$@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
01234567#$
Note: it's important to have GetStringNew, which references std::basic_string functions forcing linker to choose new std::string implementation for GetStringOld.
Expected behavior
I expect the following assertion to succeed
assert(!strcmp(GetStringOld("01234567--").c_str(), "01234567--"));Namely GetStringOld should be able to initialize std::string in a way that std::string::c_str() would return a pointer to buffer which is zero-terminated correctly.
Additional context
This problem was discovered when I compiled a project using MSVC 14.34 re-using LLVM compiled using MSVC 14.33. The problematic part was in llvm::Twine::str().
For your convenience I created a repo with reproducible example in Visual Studio solution.