Detecting invalidated iterators in Visual Studio

Introduction

When working with the STL you certainly are using iterators at one point or another. One of the common mishaps when working with iterators is to run into undefined behavior if the iterator is or becomes invalid. This can not only happen due to obvious reasons (like missing to initialize an iterator) but also when performing certain container operations.

Undefined behavior due to iterator invalidation

Take a look at the following code sample:

We define a simple class which stores an id and provide a Print()-function which outputs the id to the console.

We then construct 4 elements of these classes and add them (as pointers) to the vector.

Finally we loop over the elements, output the current item, and add two more elements in there.

If you run the code in release configuration, it might produce the output you more or less expected/intended, produce some garbled output, or might crash.
Doing a couple of successive test runs using VS 2015 Update 3, the following output was produced:

In another run the same application triggered this output however:

The issue in the code is most likely quite obvious to everybody. Inside the for-loop we push additional elements onto the vector. Vector iterators are invalidated however, if a reallocation occurs. [1]

So the result due to adding the vector::push_back() calls inside the for-loop is us causing undefined behavior. Bare in mind that while it’s an obvious issue in this tiny code sample, it can be a really hard to trace down these kind of issues in real world applications especially if these are hugely complex and/or lacking a proper system design.

One of the more nasty results these bugs can cause are memory corruptions which can be a headache to resolve especially if the conditions are strongly impacted by the exact runtime conditions between multiple threads which can make them close to impossible to trigger reliably (and even if they trigger, it’s not guaranteed that they cause a memory corruption in all runs as presented above already – that’s the nature of the behavior being undefined).

_ITERATOR_DEBUG_LEVEL comes to help

Fortunately, the developer is not left alone to trace down these issues. Visual Studio provides built-in checks in its STL implementation which detect such issues and trigger assertions/runtime exceptions if an issue is detected.

When you run the sample code above in debug configuration, you’ll get the following assertion:

The debugger will directly point you to the fact that using the ++-operator on the iterator triggered the assertion as the iterator became invalid after the previous push_back() call.

These detailed checks are part of Visual Studio’s Debug Iterator Support [2] which contain quite a bunch of checks to ensure proper/valid usage of STL iterators.

These checks are obviously not free and degrade performance and increase the memory footprint of an application. Therefore, these checks are disabled in the release configuration by default and only enabled in debug configuration.

Different levels for _ITERATOR_DEBUG_LEVEL

The checks come in two flavors: level 1 (aka: Checked Iterators) and level 2  (aka: Debug Iterator Support) checks.
While level 1 checks perform certain cheap checks on iterators which ensures that out of bound access is detected [3] (as it is the case in the sample code above), level 2 checks perform additional more costly checks and therefore can detect other type of programming mishaps.

The level can be set using the _ITERATOR_DEBUG_LEVEL macro. However, level 2 checks require the use of the debug versions of the Microsoft Visual Studio runtime (i.e. /MDd or /MTd) and therefore are practically limited to the debug configuration.

Level 1 checks on the other side are also supported in release builds and if we add

as the first line in the sample code above and run the program in release configuration we get the invalid parameter handler exception:

This makes it straight forward to detect such issues and prevents situations where you are spending days or even weeks tracing down memory corruption which are caused by such bugs.
Enabling iterator debug level can therefore be a real time safer.

Caveats of the _ITERATOR_DEBUG_LEVEL

The challenge to use the iterator checks is to enable the feature. While it’s enabled by default in debug configuration and therefore will basically work out of the box, debug configurations are not always something you can use in larger projects (f.e. in games). It also won’t cover situations where the conditions triggering iterator invalidation only occur in the release configuration (f.e. since the conditions are related to certain optimizations which change the runtime behavior of the program or when certain debug checks set up in the debug configurations are skipped).

You therefore might require to enable it in release builds – but this can imply a certain work overhead when 3rd-party libraries have to be taken into account.

The _ITERATOR_DEBUG_LEVEL setting must be set to the same value throughout all libraries linked with the program. Since the default setting for this is to have it disabled in release configurations this means that one has to (re-)build all 3rd-party libraries by himself and enable the setting for all libraries.

Fortunately, it’s mostly a thing of the past not being able to get access to 3rd-party source code when licensing  libraries/frameworks/SDKs. So technically this should be doable. Depending on the project, this however can still be quite an undertaking.

If you happen to work on such a project where using the debug configuration for your daily work is unfeasible due to performance limitations, you might wanna consider introducing a third project configuration which corresponds to your release configuration PLUS the addition of enabled iterator checks. There might be other settings worth changing for such a configuration too (f.e. disabling the whole program optimization to safe time in building your project for the daily work but still keep it close to the released version) and a separate “developer release” configuration will also enable you to add code only used for development while leaving it out of the shipped versions.

A historical side note

Checked iterators is nothing which was recently introduced and has been around in Visual Studio for over a decade. In versions prior to Visual Studio 2010 it was however using two other macros:

  • _SECURE_SCL
  • _HAS_ITERATOR_DEBUGGING

These have been deprecated in favor of the new _ITERATOR_DEBUG_LEVEL macro and should be replaced with that one.

Also if you previously tried out the iterator debugging functionality you might have deemed it unsuitable for your needs. Earlier versions of Visual Studio suffered certain bugs triggering false positive assertions and especially at the beginning the implementation wasn’t too performant. Microsoft has worked on these downsides over the past [4] and especially the level 1 setting has a very low performance overhead in the experience of the author so you might wanna give it another try.

Conclusion

Iterator checks can be a real time safer for the daily work. Especially in larger projects with multi threading and different developers of varying experience level its advantages can easily rectify the required effort of enabling it in the project.

Bare in mind that even if you are lucky and never do the mistake of writing code causing invalid iterator use, having enabled iterator checks can still safe you time, because when investigating certain issues (like memory corruptions) you can be quite certain that the cause of these sometimes really hard to trace down bugs are unlikely iterators which became invalid. Therefore, you can rather focus your effort on other potential root causes and should be able to trace down the issue much faster.

References

[1] = C++11 standard / Working Draft N3242 – 23.3.6.5.1 “Remarks: Causes reallocation if the new size is greater than the old capacity […]” / 23.3.6.3.5 “Remarks: Reallocation invalidates all the references, pointers, and iterators referring to the elements in the sequence. […]”
[2] = https://docs.microsoft.com/en-us/cpp/standard-library/debug-iterator-support?view=vs-2017
[3] = https://docs.microsoft.com/en-us/cpp/standard-library/checked-iterators?view=vs-2017
[4] = https://devblogs.microsoft.com/cppblog/c1114-stl-features-fixes-and-breaking-changes-in-vs-2013/

Tracing down application freezes.

Introduction

A not too uncommon issue with larger projects (especially when utilizing  multiple threads / for example: games) are freezes (or alternatively called: application hangs). From a user point of view these look like the app/game is not responding at all and just hangs.

As of Windows Vista a new functionality was added. The so called: “Window Ghosting”. [1] Without going into too much detail, this feature basically detects if an application responds within 5 seconds to the Windows message queue and if it doesn’t, marks the application as “not responding” and eventually provides a popup to the user allowing him to terminate the application. This “potential” freeze will then be uploaded to the Windows Error Reporting facility, where registered developers can access the details about the freeze conditions.

The caveat here is that your company might not want to invest the time/cost associated to get access to this data or might have intentionally disabled the Window Ghosting functionality, because it would cause issues with your particular application.

Retrieving user provided dumps

An alternative to the Windows Error Reporting facility is to ask users to provide you with two successive dumps at the time they experience the freeze/hang.

The idea is that you can then simply review the callstacks in the two dumps and directly see if a particular thread hangs inside a loop or might have run into a deadlock condition.

Retrieving two dumps is important, since a single dump just represents the current state of the app but doesn’t necessary proof that the dump shows a freeze/hang condition (i.e. the application might just be utterly slow in what it’s doing atm and you might misinterpret the current dump-state as a freeze). A second dump however provides you with the means to compare the two application states which usually should point you directly to a freeze or alternatively proofs that the app did not run into a freeze at all (in which case you might be looking at a performance bottleneck of the app which would have to be tackled to resolve the issue at hand).

Luckily starting with Windows Vista it became much easier for users to provide dump files for developers. [2] While before Windows Vista, users had to install additional tools (like ProcDump) which for a non-developer are not too intuitive to use, ever since Windows Vista the functionality to create dump files got directly built in into the task manager.

Creating dump files using the task manager

To create a dump file on Windows Vista or later, open the Task Manager (f.e. by right clicking the task bar and selecting: “Task Manager”), locate the unresponsive application in the list, right click the entry and select: “Create dump file”.

A popup will appear stating that the dump is being generated. Especially for larger applications like games, this can take up to several minutes. Eventually the dump will have been generated and another popup appears stating the location the dump was generated at:

As the user you should then wait a couple of seconds and repeat that step. Afterwards you can provide the developer/support with the two dump files so they can work out what caused the application/game to freeze/hang.

Be aware however, that these files are rather large in size. It’s not uncommon for these to be several GB large. Fortunately they can be compressed quite good (compression factors of 10-100 are not uncommon to achieve).

A word on privacy

Please be aware that these dump files are basically full dumps of the application state including the entire memory section the application uses (that’s the main reason for why the dump file is so large), all the modules currently loaded on your machine, and additional system details which might contain personalized information.

Since the dump contains the entire memory footprint the application has access to, it will also contain any personal data (even potentially unencrypted passwords) the application might have stored. Even if the application doesn’t store passwords/personal data itself, the dump might still contain personal information and passwords which other applications missed to properly cleanup before freeing the memory section (i.e. if another application is suffering a security issue). So be careful whom and how you send these dumps to.

References

[1] = https://blogs.technet.microsoft.com/askperf/2010/09/10/de-ghosting-your-windows/
[2] = https://blogs.msdn.microsoft.com/debugger/2009/12/30/what-is-a-dump-and-how-do-i-create-one/