Home Dashboard Directory Help
Search

Unhomed std::locale::facet and std::locale::_Locimp destructors cause crashes by CornedBee


Status: 

Closed
 as Fixed Help for as Fixed


6
0
Sign in
to vote
Type: Bug
ID: 650567
Opened: 3/10/2011 2:16:18 AM
Access Restriction: Public
2
Workaround(s)
view
5
User(s) can reproduce this bug

Description

The destructors and vtables of std::locale::facet and std::locale::_Locimp are not homed. This can cause issues when dynamically loading and unloading DLLs, in ways that are very hard for most users to understand. Specifically, vtables and destructors are duplicated in every module they are used in, and which one is actually used in a given object depends on where the constructor is used. When DLLs are unloaded, this can cause a vtable and destructor that are still referenced to vanish.

Here's a simple example where this could cause crashes:

=== main module ===
#include <fstream>
#include <Windows.h>
typedef void (*Callback)(int);
typedef void (*DllFunc)(Callback);

std::ofstream *g_log;

void log(const char* msg)
{
if (!g_log) g_log = new std::ofstream("log.txt");
(*g_log) << msg << std::endl;
}

void helper(int)
{
log("helper called");
}

int main() {
HINSTANCE lib = LoadLibrary("thelibrary.dll");
DllFunc dllFunc = (DllFunc)GetProcAddress(lib, "DllFunc");

dllFunc(&helper);

// Cleanup
FreeLibrary(lib);
delete g_log; // CRASH!
}

=== dll implementation ===
#include <locale>

typedef void (*Callback)(int);

void DllFunc(Callback cb)
{
// enforce C for numeric facet of global locale
std::locale oldGlob;
std::locale::global(
    std::locale(std::locale(), "C", std::locale::numeric));

cb(0);

std::locale::global(oldGlob);
}

===================

The above code isn't exactly perfect: DllFunc() may well document that it changes the global locale for its duration, but that means that helper() shouldn't create a stream because that will be influenced. But how many programmers actually know that streams capture the current global locale? In my team of 7, I was the only one.

In the example, the following happens:
First, when DllFunc() creates its own locale, a new _Locimp object is allocated on the heap. Because _Locimp is not homed, this _Locimp object points to a vtable that resides in thelibrary.dll. The locale has a refcount of 1.
The DLL then changes the global locale.
The callback function helper() is called.
helper() calls log(), which creates the log output stream. In its constructor, the ofstream object grabs the current global locale and stores a reference (increasing the refcount to 2).
DllFunc() then resets the global locale (dropping the refcount of its own locale to 1) and returns.
main() unloads thelibrary.dll.
Then, main() deletes the log stream. The ofstream destructor, in turn, releases its captured locale object. The refcount drops to 0 and the object gets freed. But the vtable sits in the already unloaded thelibrary.dll! As a consequence, the program crashes deep in the implementation details of the C++RT.

Fundamentally the issue is that locale's internal refcounting can keep objects alive after the library that created them has been unloaded. Because the internal classes aren't homed, these objects reference code from the unloaded library.
Details
Sign in to post a comment.
Posted by Microsoft on 1/18/2012 at 8:27 PM
Hi,

Thanks for reporting this bug. We've fixed it, and the fix will be available in VC11.

If you have any further questions, feel free to E-mail me at stl@microsoft.com .

Stephan T. Lavavej
Visual C++ Libraries Developer
Posted by Prophetable on 9/13/2011 at 3:48 AM
I am seeing this issue as well, Is there any news on a workaround or (hopefully) a fix?
Posted by Microsoft on 3/11/2011 at 12:07 AM
Thanks for your feedback.

We are rerouting this issue to the appropriate group within the Visual Studio Product Team for triage and resolution. These specialized experts will follow-up with your issue.
Posted by Microsoft on 3/10/2011 at 3:12 AM
Thank you for your feedback, we are currently reviewing the issue you have submitted. If this issue is urgent, please contact support directly(http://support.microsoft.com)
Sign in to post a workaround.
Posted by dj_alek on 12/22/2011 at 9:01 AM
Make an additional little dll that holds std::locale code only and tie it to process lifetime via GetModuleHandleEx+GET_MODULE_HANDLE_EX_FLAG_PIN. Be careful: crt version, debug/release must be the same for both dlls.
Posted by Prophetable on 10/18/2011 at 5:46 AM
This is a (fairly unpleasant) workaround that works in my situation, but, it does entail leaking some memory. So, it will not be an acceptable solution for everyone. Rewrite the DllFunc like this:

void DllFunc(Callback cb)
{
// enforce C for numeric facet of global locale
std::locale* kludge = new std::locale(std::locale(), "C", std::locale::numeric);
std::locale oldGlob = std::locale::global(*kludge);

cb(0);

std::locale::global(oldGlob);
}