Thanks to visit codestin.com
Credit goes to github.com

Skip to content

SSO breaks backwards compatibility of ABI #3273

@RIscRIpt

Description

@RIscRIpt

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions