Skip to content

Commit

Permalink
Workaround crash on exit for MinGW 32bit
Browse files Browse the repository at this point in the history
There is a bug in GCC for 32bit MinGW until version 11.
This causes a use-after free for destruction of `thread_local` variables
that crash the application when the destructor accesses any member.
In the tests it shows up as exit code/status `-1073741819` i.e. `0xC0000005`.
Workaround this by not destructing the `stackstring` instance used to
hold the value of the last `getenv` result.
In the case where any call to `getenv` of a thread yielded a large
value heap memory will be allocated and not freed due to this missing
destructor call causing a memory leak, possibly for each thread.
However values up to some length are stored on stack memory and hence
the missing destructor call does not cause a memory leak as the type is
essentially trivial in this state.

See https://gcc.gnu.org/bugzilla/show_bug.cgi?id=83562
Fixed by https://gcc.gnu.org/git/?p=gcc.git;a=commit;h=7fc0f78c3f43af1967cb7b1ee8f4947f3b890aa2
  • Loading branch information
Flamefire committed Dec 20, 2024
1 parent a2d660f commit b9ff85d
Showing 1 changed file with 37 additions and 2 deletions.
39 changes: 37 additions & 2 deletions src/cstdlib.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,46 @@ namespace nowide {
#include <vector>
#include <windows.h>

namespace {
// thread_local was broken on MinGW for all 32bit compiler releases prior to 11.x, see
// https://sourceforge.net/p/mingw-w64/bugs/527/
// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=83562
// Using a non-trivial destructor causes program termination on thread exit.
#if defined(__MINGW32__) && !defined(__MINGW64__) && !defined(__clang__) && (__GNUC__ < 11)
class stackstring_for_thread
{
union
{
boost::nowide::stackstring s_;
};

public:
stackstring_for_thread() : s_(){};
// Empty destructor so the union member (using a non-trivial destructor) does not get destroyed.
// This will leak memory if any is allocated by the stackstring for each terminated thread
// but as most values fit into the stack buffer this is rare and still better than a crash.
~stackstring_for_thread(){};
void convert(const wchar_t* begin, const wchar_t* end)
{
s_.convert(begin, end);
}

char* get()
{
return s_.get();
}
};
#else
using stackstring_for_thread = boost::nowide::stackstring;
#endif

} // namespace

namespace boost {
namespace nowide {
char* getenv(const char* key)
{
thread_local stackstring value;
thread_local stackstring_for_thread value;

const wshort_stackstring name(key);

Expand All @@ -72,7 +107,7 @@ namespace nowide {
return 0;
ptr = &tmp[0];
}
value.convert(ptr);
value.convert(ptr, ptr + n);
return value.get();
}

Expand Down

0 comments on commit b9ff85d

Please sign in to comment.