STL String Crashes When HID = 0

Awhile ago, we upgraded our compiler to VC90 (Visual Studio 2008), we found out that Has Iterator Debugging (HID) and Secure SCL in VC9.0 were severely degrading our product’s performance. So we took the time to rebuild everything by disabling those features.

This is done by defining preprocessor macros _SECURE_SCL = 0 and _HAS_ITERATOR_DEBUGGING = 0.

Soon after, we experienced some strange crashes in Debug build that makes no sense to us.

Crashes at std::_Container_base_secure::_Orphan_all()

Here’s one found by my co-worker. [Note: The code was simplified for demonstration purposes]

#define _HAS_ITERATOR_DEBUGGING 0 //turn off Has Iterator Debugging
#include <string>
#include <algorithm>
using namespace std;
int main()
{
	string abc = "abc";

	// Method 1: Crashes upon exit with an access violation
	string dot_abc = "." + abc;

	// Method 2: Works
	//string dot_abc = string(".") + abc;

	string buffer = ".abc";

	// Works without the search call here
	search(buffer.begin(), buffer.end(), dot_abc.begin(), dot_abc.end());

	return 0;
}

If you choose Method 1, you will get an access violation upon the destruction of the string class.

msvcp90d.dll!std::_Container_base_secure::_Orphan_all()  Line 223 + 0x5 bytes    C++
msvcp90d.dll!std::_Container_base_secure::~_Container_base_secure()  Line 115    C++
msvcp90d.dll!std::_String_base::~_String_base()  + 0x11 bytes    C++
msvcp90d.dll!std::_String_val<unsigned short, std::allocator<unsigned short> >::~_String_val<unsigned short,std::allocator<unsigned short> >()  + 0x11 bytes    C++
msvcp90d.dll!std::basic_string<char, std::char_traits<char>,std::allocator<char> >::~basic_string<char,std::char_traits<char>,std::allocator<char> >()  Line 917 + 0xf bytes    C++

However, if you choose Method 2, it will exit gracefully. And both method works under Release build.

The first alarming thing from the call stack is the fact that we are calling into msvcp90d.dll. Strings, unlike other STL containers, is separately compiled into another DLL since VC80.

Remember, to turn off HID and Secure SCL, it is required that all binaries linked by a translation unit to have the same HID and Secure SCL settings. After some online search, it is clear that msvcp90d.dll is built with HID = 1.

Yikes! Since we can’t build msvcp90d.dll, there isn’t much we can do. But Microsoft isn’t stupid, they clearly have worked around some of the problems because Method 2 does work.

Stepping In std::string

In C++, the devil is in the details. Method 1 and Method 2 appears to be functionally equvialent, they are vastly different underneath.

// Method 1
string dot_abc = "." + abc;

At a glance, Method 1 should invoke the operator+ with const char * as the left argument, and std::string as the right argument. After stepping into the function call, it calls into an operator+ in that constructs a basic_string object.

//string L27 operator +(const char *, std::string)
template<class _Elem,
	class _Traits,
	class _Alloc> inline
	basic_string<_Elem, _Traits, _Alloc> __CLRCALL_OR_CDECL operator+(
		const _Elem *_Left,
		const basic_string<_Elem, _Traits, _Alloc>& _Right)
	{	// return NTCS + string
	return (basic_string<_Elem, _Traits, _Alloc>(_Left) += _Right);
	}

It calls a copy constructor that takes in _Left (which is “.”) in this case, and performs operator+= with _Right (which is std::string abc).

// xstring L661 cctor(const char*)
__CLR_OR_THIS_CALL basic_string(const _Elem *_Ptr)
	: _Mybase()
	{	// construct from [_Ptr, <null>)
	_Tidy();
	assign(_Ptr);
	}

In method 2, the operation is different. First, a copy constructor is invoked to create a temp string.

// xstring L798 cctor(const char *)
__CLR_OR_THIS_CALL basic_string(const _Elem *_Ptr, _NO_DEBUG_PLACEHOLDER)
	: _Mybase()
	{	// construct from [_Ptr, <null>)
	this->_Myfirstiter = _IGNORE_MYITERLIST;
	_Tidy();
	assign(_Ptr);
	}

Then it will invoke operator+ with std::string as the left and right argument.

// string L17 operator +(std::string const &, std::string const &)
template<class _Elem,
	class _Traits,
	class _Alloc> inline
	basic_string<_Elem, _Traits, _Alloc> __CLRCALL_OR_CDECL operator+(
		const basic_string<_Elem, _Traits, _Alloc>& _Left,
		const basic_string<_Elem, _Traits, _Alloc>& _Right)
	{	// return string + string
	return (basic_string<_Elem, _Traits, _Alloc>(_Left) += _Right);
	}

Notice anything strange?

For the operation where “.” is copied into a std::string, the copy constructor invoked by Method 1 and Method 2 are different! In method 2, it has a different signature, and there is an extra line in the copy constructor – this->_Myfirstiter = _IGNORE_MYITERLIST.

This is probably one of Visual Studio’s work around to allow programs compiled with HID=0 to safely invoke the std::string library in msvcp90d.dll. Unfortunately, there are loop holes in their patch that fails in Method 1.

Conclusion

If you want to turn off HID and Secure SCL for performance reason, be careful with the string library. There are quite a few bugs in VC9.0 that crashes on perfectly legal C++ code. The example above is one of several scenarios that we’ve found. We have also seen similar crashes on certain usage of stringstream.

On a side note, a co-worker of mine filed this bug in Microsoft Connect. They closed the bug shortly, and told him that it has been fixed in VC10 Beta 2. Basically, they are suggesting that we should upgrade our compiler to a beta version, and pay them more money when it officially comes out. Great customer support, M$.