<locale>: Eagerly-allocated facets in non-default std::locale can lead to ODR violation between DLL-exported version and header version
Nobody has claimed this yet.
- Dominant language
- C++
- Stars
- 11.1k
- Forks
- 1.7k
- Avg merge
- 4d 15h
- Merged PRs (30d)
- 22
Description
When facets are eagerly allocated during locale creation, they are constructed from within the MSVCP140 DLL (via MSVCP140!std::locale::_Locimp::_Makeloc). All other times, they are constructed from within the user binary (via std::use_facet).
The following facets are eagerly allocated when creating a new locale:
ctype<char>num_get<char, istreambuf_iterator<char, char_traits<char>>>num_put<char, ostreambuf_iterator<char, char_traits<char>>>numpunct<char>codecvt<char, char, mbstate_t>
Facets also have virtual methods, so the return values of those virtual methods are also allocated inside MSVCP140. In the reported scenario, this is detected in Debug mode when calling std::numpunct::grouping() which returns a std::string with an overridden new/delete. New/delete being overridden will violate ODR for the std::string and causes a new/delete mismatch for the IDL container proxy of the std::string returned from std::numpunct::grouping().
#include <malloc.h>
#include <cstddef>
#include <locale>
#include <stdexcept>
#include <string>
namespace
{
void* new_impl(std::size_t size)
{
void* ptr = _aligned_malloc(size, 16);
if (!ptr)
throw std::bad_alloc();
return ptr;
}
void delete_impl(void* ptr)
{
if (ptr)
_aligned_free(ptr);
}
}
void* operator new(std::size_t size)
{
return new_impl(size);
}
void* operator new[](std::size_t size)
{
return new_impl(size);
}
void* operator new(std::size_t size, const std::nothrow_t&) throw()
{
return new_impl(size);
}
void* operator new[](std::size_t size, const std::nothrow_t&) throw()
{
return new_impl(size);
}
void operator delete(void* ptr)
{
delete_impl(ptr);
}
void operator delete[](void* ptr)
{
delete_impl(ptr);
}
void operator delete(void* ptr, const std::nothrow_t&)
{
delete_impl(ptr);
}
void operator delete[](void* ptr, const std::nothrow_t&)
{
delete_impl(ptr);
}
int main()
{
const std::locale locale("C");
const auto& np = std::use_facet<std::numpunct<char>>(locale);
const std::string grouping = np.grouping();
}
I don't believe this is resolved by current locale and facet plans for vNext.
Also tracked by Developer Community as DevCom-875199 and Microsoft-internal VSO-1049731 / AB#1049731. Also DevCom-329394 and Microsoft-internal VSO-678119 / AB#678119.
vNext note: Resolving this issue will require breaking binary compatibility. We won't be able to accept pull requests for this issue until the vNext branch is available. See #169 for more information.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with the supplied reproducer and compare facet construction through MSVCP140!std::locale::_Locimp::_Makeloc with construction through std::use_facet, focusing on std::numpunct::grouping(). The issue names no source files or tests; resolution is blocked until the vNext branch and requires breaking binary compatibility, as described in #169.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- cpp
- Domain
- compilers
- Issue type
- Bug
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Stale
- Clarity
- Needs clarification
- Newbie friendliness
- 15/100