Wednesday, December 7, 2011

Visual C++ 2010 std::istringstream crash in destructor

tl;dr: You have a /vd2 switch in your project settings that you don't know about. Check your property sheets, gtkmm in particular has it in the additional options. [edit 11-dec-2011: but read the update at the bottom, it's not that simple]

I have a Visual C++ 2008 project that uses gtkmm, sqlite and curl that I wanted to port 2010. Most of the times, upgrading a project to a newer version of Visual C++ works quite well, but I got a crash immediately after the first build on a destructor for a std::istringstream. std::ostringstream also crashes in the destructor. The error message is
Unhandled exception at 0xwherever (ntdll.dll) in program.exe: 0xC0000005:
Access violation reading location 0x00000004.
right in
virtual ~basic_istringstream()
  {  // destroy the object
 with this stack trace (edited for sanity)
ntdll.dll!_NtRaiseException@12() + 0x12 bytes
ntdll.dll!_NtRaiseException@12() + 0x12 bytes
msvcr100d.dll!_free() + 0x10 bytes
program.exe!std::istringstream::~istringstream() Line 469 + 0x20
program.exe!std::istringstream::`vbase destructor'() + 0x2e bytes
I also saw
ntdll.dll!_NtRaiseException@12() + 0x12 bytes
ntdll.dll!_NtRaiseException@12() + 0x12 bytes
msvcr100d.dll!__unlock() + 0x16 bytes
ntdll.dll!_NtRaiseException@12()  + 0x12 bytes
ntdll.dll!_NtRaiseException@12()  + 0x12 bytes
msvcr100d.dll!__free_dbg()  + 0x68 bytes
msvcp100d.dll!std::_DebugHeapDelete<_RTL_CRITICAL_SECTION>()  + 0x15 bytes
The first thing I found was a bug report on Microsoft Connect that was about the /vd2 switch, which I'd never heard of before and therefore do not use in my projects. I dismissed it quickly and moved on to not finding anything else.

After multiple rebuilds, checking dll dependencies and generally not having much of a clue, I started looking at the exact command line for my project in the property pages, but still could not find anything useful.

I then started to look at the individual property sheets, starting with gtkmm, where I obviously found this little /vd2 switch in the additional options of the command line. Funnily enough, it looks like these additional options are not shown on the "all options" text box of the project itself. Removing the switch fixed the crash.

Now, fixing a crash is always nice, but finding the reason for the crash is better, so it can be avoided in the future.

This gtkmm bug report dated from November 2004 states that Visual Studio 2003 had a problem with using dynamic_cast in constructors. The fix on Visual Studio 2005 was to use the /vd2 flag. Moving on to 2010, /vd2 broke for unknown reasons, but this is not fixable without breaking binary compatibility, which is not happening until the next major release. I haven't tested whether the /vd2 flag is still necessary (two bug reports seem to indicate it still is, as no resolution has been posted), but preliminary tests seem to be okay.

Therefore, it looks like the /vd flag was extended as a bugfix for 2005, but 2010 introduced a bug on the bugfix. Hopefully, the 2005 bug is gone or we'll need a bugfix for the bugfix. Who said programming wasn't fun?

There's not much internets on this, although there are two threads on gnome's mailing list and a bug report on googletest (where a guy closed it quite unceremoniously "as this is a compiler bug") that appear when searching specifically for "gtkmm" and "vd2".

[edit 11-dec-2011:

I just got linked in a stackoverflow question. I was wondering where that traffic came from.

My "preliminary tests" was successfully running a somewhat complex gtkmm application without /vd2. It was obviously not enough. This (adapted from the OP's code):
#include <cassert>
#include <sstream>

struct object
  virtual ~object() {}

struct base : virtual object
    object* b = static_cast<object*>(this);
    base* d = dynamic_cast<base*>(b);
    assert(this == d);

struct derived : base
  int i;

int main()
  derived d;

fails with Visual C++ 2010, but not with g++ 4.5.3. Adding /vd2 to the compiler settings fixes the problem, but then changing main() to

int main()
  derived d;
  std::istringstream iss;

again fails in istringstream::~istringstream().

What happens in base::base() is legal as per 12.7/5 in C++03 and 12.7/6 in C++11:
When a dynamic_cast is used in a constructor [..] if the operand of the dynamic_cast refers to the object under construction or destruction, this object is considered to be a most derived object that has the type of the constructor or destructor’s class.
Therefore, as long as T refers to a direct or indirect base type of U, U::U() can dynamic_cast this to T and back.

As far as I can tell, there is no way to fix this so that both dynamic_cast in a constructor and stringstreams can work. I don't even know the extent of the problem with /vd2 in Visual C++ 2010 (why only stringstreams?). Documentation on the /vd2 switch for 2010 says that
/vd supports incorrect behavior in an early version of Visual C++, and is no longer needed.
 but that is not what I and others see.]

[edit 28-jun-2012:

Although the dynamic_cast still fails miserably without /vd2, stringstreams now behave correctly on Visual C++ 2012 RC]

1 comment:

Mark Eggleston said...

Thanks for this, it has saved me a lot of time. Unsurprising this also occurs with std::stringstream.

I encountered this when I used std::stringstream to get around a problem with

Glib::ustring::format(std::setprecision(std::numeric_limits::digits10, value);

which throws an exception saying that conversion from WCHAR_T to UTF-8 is not supported. When using MinGW, MSYS and NetBeans to build my project the Glib::ustring::format conversion of the long double value 16.0L to Glib::ustring produced "-0" so I've now removed all occurrences of Glib::ustring::format from my project.