Assert, Assert, Assert, and Assert


I hope most of you already know what an assertion is, because it's the most important proactive programming tool in your debugging arsenal. For those who are unfamiliar with the term, here's a brief definition: an assertion declares that a certain condition must be true at a specific point in a program. The assertion is said to fail if the condition is false. You use assertions in addition to normal error checking. Traditionally, assertions are functions or macros that execute only in debug builds and bring up a message box telling you what condition failed. I extend the definition of assertions to include conditionally compiled code that checks conditions and assumptions that are too complex for a general assertion function or macro to handle. Assertions are a key component of proactive programming because they help developers and test engineers determine not just that bugs are present but also why the errors are happening.

Even if you've heard of assertions and drop them in your code occasionally, you might not be familiar enough with them to use them effectively. Development engineers can never be too rich or too thin—or use too many assertions. The rule of thumb I've always followed to judge whether I've used enough assertions is simple: I have enough assertions when my junior coworkers complain that they get multiple message boxes reporting assertion failures whenever they call into my code with invalid information or assumptions.

If used sufficiently, assertions will tell you most of the information you need to diagnose a problem at the first sign of trouble. Without assertions, you'll spend considerable time in the debugger working backward from the crash searching for where things started to go wrong. A good assertion will tell you where and why a condition was invalid. A good assertion will also let you get into the debugger after a condition fails so that you can see the complete state of the program at the point of failure. A bad assertion tells you something's wrong, but not what, why, or where.

A side benefit of using plenty of assertions is that they serve as outstanding additional documentation in your code. What assertions capture is your intent. I'm sure you go well out of your way to keep your design documents perfectly up to date, but I'm just as sure that a few random projects let their design documents slip through the cracks. By having good assertions throughout your code, the maintenance developer can see exactly what value ranges you expected for a parameter or what you anticipated would fail in a normal course of operation versus a major failure condition. Assertions will never replace proper comments, but by using them to capture the elusive "here's what I meant, which is not what the docs say," you can save a great deal of time later in the project.

How and What to Assert

My stock answer when asked what to assert is to assert everything. I would love to say that for every line of code you should have an assertion, but it's an unrealistic albeit admirable goal. You should assert any condition because it might be the one you need to solve a nasty bug in the future. Don't worry that putting in too many assertions will hamper your program's performance—assertions usually are active only in debug builds, and the bug-finding opportunities created more than outweigh the small performance hit.

Assertions should never change any variables or states of a program. Treat all data you check in assertions as read-only. Because assertions are active only in debug builds, if you do change data by using an assertion, you'll have different behavior between debug and release builds, and tracking down the differences will be extremely difficult.

In this section, I want to concentrate on how to use assertions and what to assert. I'll do this by showing code examples. I need to mention that in these examples, Debug.Assert is the .NET assertion from the System.Diagnostic namespace, and ASSERT is the native C++ method, which I'll introduce later in this chapter.

start sidebar
Debugging War Story: A Career-Limiting Move

The Battle

A long, long time ago, I worked at a company whose software product had serious stability problems. As the senior Windows engineer on this behemoth project, I found that many of the issues affecting the project resulted from a lack of understanding about why calls made to others' modules failed. I wrote a memo advising the same practices I promote in this chapter, telling project members why and when they were supposed to use assertions. I had a little bit of power, so I also made it part of the code review criteria to look for proper assertion usage.

After sending out the memo, I answered a few questions people had about assertions and thought everything was fine. Three days later, my boss came into my office and started screaming at me about how I screwed everyone up, and he ordered me to rescind my assertion memo. I was stunned, and we proceeded to get into an extremely heated argument about my assertion recommendations. I couldn't quite understand what my boss was arguing about, but it had something to do with making the product much more unstable. After five minutes of yelling at each other, I finally challenged my boss to prove that people were using assertions incorrectly. He handed me a code printout that looked like the following:

BOOL DoSomeWork ( HMODULE * pModArray , int iCount , LPCTSTR szBuff ) {     ASSERT ( if ( ( pModArray == NULL ) &&                 ( IsBadWritePtr ( pModArray ,                                  ( sizeof ( HMODULE ) * iCount ) ) &&                 ( iCount != 0 ) &&             ( szBuff != NULL ) ) )             {                 return ( FALSE ) ;             }             ) ;      for ( int i = 0 ; i < iCount ; i++ )      {           pModArray[ i ] = m_pDataMods[ i ] ;      }       }

The Outcome

I should also mention here that my boss and I generally didn't get along. He thought I was a young whippersnapper who hadn't paid his dues and didn't know a thing, and I thought he was a completely clueless mouth-breathing moron who couldn't engineer his way out of a wet paper bag. As I read over the code, my eyes popped completely out of my head! The person who had coded this example had completely misunderstood the purpose of assertions and was simply going through and wrapping all the normal error handling in an assertion macro. Since assertions disappear in release builds, the person who wrote the code was removing all error checking in release builds!

By this point, I was livid and screamed at the top of my lungs, "Whoever wrote this needs to be fired! I can't believe we have an engineer on our staff who is this incredibly and completely @#!&*&$ stupid!" My boss got very quiet, grabbed the paper out of my hands, and quietly said, "That's my code." My career-limiting move was to start laughing hysterically as my boss walked away.

The Lesson

I can't stress this enough: use assertions in addition to normal error handling, never as a replacement for it. If you have an assertion, you need to have some sort of error handling near it in the code. As for my boss, when I went into his office a few weeks later to resign because I had accepted a job at a better company, I was treated to a grown person dancing on his desk and singing that it was the best day of his life.

end sidebar

How to Assert

The first rule when using assertions is to check a single item at a time. If you check multiple conditions with just one assertion, you have no way of knowing which condition caused the failure. In the following example, I show the same function with two assertion checks. Although the assertion in the first function will catch a bad parameter, the assertion won't tell you which condition failed or even which of the three parameters is the offending one.

// The wrong way to write an assertion. Which parameter was bad? BOOL GetPathItem ( int i , LPTSTR szItem , int iLen ) {     ASSERT ( ( i > 0                                       ) &&               ( NULL != szItem                              ) &&               ( ( iLen > 0 ) && ( iLen < MAX_PATH )         ) &&               ( FALSE == IsBadStringPtr ( szItem , iLen ) ) ) ;       }     // The proper way. Each parameter is checked individually so that you // can see which one failed.  BOOL GetPathItem ( int i , LPTSTR szItem , int iLen ) {     ASSERT ( i > 0 ) ;      ASSERT ( NULL != szItem ) ;     ASSERT ( ( iLen > 0 ) && ( iLen < MAX_PATH ) ) ;     ASSERT ( FALSE == IsBadStringPtr ( szItem , iLen ) ) ;       }

When you assert a condition, you need to strive to check the condition completely. For example, if your .NET method takes a string as a parameter and you expect the string to have something in it, checking against null checks only part of the error condition.

// An example of checking only a part of the error condition bool LookupCustomerName ( string CustomerName ) {     Debug.Assert ( null != CustomerName , "null != CustomerName" ) ;       }

You can check the full condition by also checking to see whether the string is empty.

// An example of completely checking the error condition bool LookupCustomerName ( string CustomerName ) {     Debug.Assert ( null != CustomerName , "null != CustomerName" ) ;     Debug.Assert ( 0 != CustomerName.Length ,"\"\" != CustomerName.Length" ) ;       

Another step I always take is to ensure that I'm asserting against specific values. The following example shows first how to check for a positive value incorrectly and then how to check for it correctly:

// Example of a poorly written assertion:  nCount should be positive,  // but the assertion doesn't fail when nCount is negative. void UpdateListEntries ( int nCount )  {     ASSERT ( nCount ) ;       }     // A proper assertion that explicitly checks against what the value // is supposed to be void UpdateListEntries ( int nCount )  {     ASSERT ( nCount > 0 ) ;       }

The incorrect sample essentially checks only whether nCount isn't 0, which is just half of the information that needs to be asserted. By explicitly checking the acceptable values, you guarantee that your assertion is self-documenting, and you also ensure that your assertion catches corrupted data.

What to Assert

Now that you're armed with an idea of how to assert, we can turn to exactly what you need to be asserting throughout your code. If you haven't guessed from the examples I've presented so far, let me clarify that the first mandatory items to assert are the parameters coming into the method. Asserting parameters is especially critical with module interfaces and class methods that others on your team call. Because those gateway functions are the entry points into your code, you want to make sure that each parameter and assumption is valid. As I pointed out in the debugging war story earlier in this chapter, "A Career-Limiting Move," assertions never take the place of normal error handling.

As you move inside your module, the parameters of the module's private methods might not require as much checking, depending mainly on where the parameters originated. Much of the decision about which parameters to validate comes down to a judgment call. It doesn't hurt to assert every parameter of every method, but if a parameter comes from outside the module, and if you fully asserted it once, you might not need to again. By asserting each parameter on every function, however, you might catch some errors internal to your module.

I sit right in the middle of the two extremes. Deciding how many parameter assertions are right for you just takes some experience. As you get a feel for where you typically encounter problems in your code, you'll figure out where and when you need to assert parameters internal to your module. One safeguard I've learned to use is to add parameter assertions whenever a bad parameter blows up my code. That way, the mistake won't get repeated because the assertion will catch it.

Another area that's mandatory for assertions is method return values because the return values tell you whether methods succeeded or failed. One of the biggest problems I see in debugging other developers' code is that they simply call methods without ever checking the return value. I have seen so many cases in which I've looked for a bug, only to find out that some method early on in the code failed but no one bothered to check its return value. Of course, by the time you realize the culprit, the bug is manifested, so the program dies or corrupts data some 20 minutes later. By asserting return values appropriately, you at least know about a problem when it happens.

Keep in mind that I'm not advocating asserting on every single possible failure. Some failures are expected in code, and you should handle them appropriately. Having an assertion fire each time a lookup in the database fails will likely drive everyone to disabling assertions in the project. Be smart about it, and assert on return values when it's something serious. Handling good data throughout your program should never cause an assertion to trigger.

Finally, I recommend that you use assertions when you need to check an assumption. For example, if the specifications for a class require 3 MB of disk space, you should check this assumption with a conditional compilation assertion inside that class to ensure the callers are upholding their end of the deal. Here's another example: if your code is supposed to access a database, you should have a check to see whether the required tables actually exist in the database. That way you'll know immediately what's wrong instead of wondering why you're getting weird return values from other methods in the class.

In both of the preceding examples, as with most assumption assertions, you can't check the assumptions in a general assertion method or macro. In these situations, the conditional compilation technique that I indicated in the last paragraph should be part of your assertion toolkit. Because the code executed in the conditional compilation works on live data, you must take extra precautions to ensure that you don't change the state of the program. To avoid the serious problems that can be created by introducing code with side effects, I prefer to implement these types of assertions in separate methods, if possible. By doing so, you avoid changing any local variables inside the original method. Additionally, the conditionally compiled assertion methods can come in handy in the Watch window, as you'll see in Chapter 5 when we talk about the Microsoft Visual Studio .NET debugger. Listing 3-1 shows a conditionally compiled method that checks whether a table exists so that you'll get the assertion before you start any heavy access. Note that this test method assumes that you've already validated the connection string and can fully access the database. AssertTableExists ensures the table exists so that you can validate this assumption instead of looking at an odd failure message from deep inside the bowels of your code.

Listing 3-1: AssertTableExists checks whether a table exists

start example
  [Conditional("DEBUG")] static public void AssertTableExists ( string ConnStr ,                                         string TableName ) {     SqlConnection Conn = new SqlConnection ( ConnStr ) ;         StringBuilder sBuildCmd = new StringBuilder ( ) ;         sBuildCmd.Append ( "select * from dbo.sysobjects where " ) ;     sBuildCmd.Append ( "id = object_id('" ) ;     sBuildCmd.Append ( TableName ) ;     sBuildCmd.Append ( "')" ) ;         // Make the command.     SqlCommand Cmd = new SqlCommand ( sBuildCmd.ToString ( ) , Conn ) ;         try     {         // Open the database.         Conn.Open ( ) ;             // Create a dataset to fill.         DataSet TableSet = new DataSet ( ) ;             // Create the data adapter.         SqlDataAdapter TableDataAdapter = new SqlDataAdapter ( ) ;             // Set the command to do the select.         TableDataAdapter.SelectCommand = Cmd ;             // Fill the dataset from the adapter.         TableDataAdapter.Fill ( TableSet ) ;             // If anything showed up, the table exists.         if ( 0 == TableSet.Tables[0].Rows.Count )         {             String sMsg = "Table : '" + TableName +                             "' does not exist!\r\n" ;             Debug.Assert ( false , sMsg ) ;         }     }     catch ( Exception e )     {         Debug.Assert ( false , e.Message ) ;     }     finally      {         Conn.Close ( ) ;     } }
end example

Before I describe issues unique to the various assertions for .NET and native code, I want to show an example of how I handle assertions. Listing 3-2 shows the StartDebugging function from the native code debugger in Chapter 4. This code is an entry point from one module to another, so it shows all the appropriate assertions I covered in this section. I chose a C++ method because so many more problems can surface in native C++ and thus there are more conditions to assert. I'll go over some of the issues you'll see in this example later in the section "Assertions in Native C++ Applications."

Listing 3-2: Full assertion example

start example
 HANDLE DEBUGINTERFACE_DLLINTERFACE __stdcall     StartDebugging ( LPCTSTR          szDebuggee        ,                      LPCTSTR          szCmdLine         ,                      LPDWORD          lpPID             ,                      CDebugBaseUser * pUserClass        ,                      LPHANDLE         lpDebugSyncEvents  ) {     // Assert the parameters.     ASSERT ( FALSE == IsBadStringPtr ( szDebuggee , MAX_PATH ) ) ;     ASSERT ( FALSE == IsBadStringPtr ( szCmdLine , MAX_PATH ) ) ;     ASSERT ( FALSE == IsBadWritePtr ( lpPID , sizeof ( DWORD ) ) ) ;     ASSERT ( FALSE == IsBadReadPtr ( pUserClass ,                                      sizeof ( CDebugBaseUser * ) ) ) ;     ASSERT ( FALSE == IsBadWritePtr ( lpDebugSyncEvents ,                                       sizeof ( HANDLE ) *                                             NUM_DEBUGEVENTS ) ) ;     // Check them all for real.     if ( ( TRUE == IsBadStringPtr ( szDebuggee , MAX_PATH )     )  ||          ( TRUE == IsBadStringPtr ( szCmdLine , MAX_PATH )      )  ||          ( TRUE == IsBadWritePtr ( lpPID , sizeof ( DWORD )   ) )  ||          ( TRUE == IsBadReadPtr ( pUserClass ,                                   sizeof ( CDebugBaseUser * ) ) )  ||          ( TRUE == IsBadWritePtr ( lpDebugSyncEvents ,                                    sizeof ( HANDLE ) *                                         NUM_DEBUGEVENTS )     )       )     {         SetLastError ( ERROR_INVALID_PARAMETER ) ;         return ( INVALID_HANDLE_VALUE ) ;     }         // The string used for the startup acknowledgment event     TCHAR szStartAck [ MAX_PATH ] = _T ( "\0" ) ;         // Load up the string for startup acknowledgment.     if ( 0 == LoadString ( GetDllHandle ( )      ,                            IDS_DBGEVENTINIT      ,                            szStartAck            ,                            MAX_PATH               ) )     {         ASSERT ( !"LoadString IDS_DBGEVENTINIT failed!" ) ;         return ( INVALID_HANDLE_VALUE ) ;     }         // The handle of the startup acknowledgment that this function     // will wait on until the debug thread gets started     HANDLE hStartAck = NULL ;           // Create the startup acknowledgment event.     hStartAck = CreateEvent ( NULL   ,     // Default security                               TRUE   ,     // Manual-reset event                               FALSE  ,     // Initial state=Not signaled                               szStartAck ) ; // Event name     ASSERT ( NULL != hStartAck ) ;     if ( NULL == hStartAck )     {         return ( INVALID_HANDLE_VALUE ) ;     }         // Bundle up the parameters.     THREADPARAMS stParams ;     stParams.lpPID = lpPID ;     stParams.pUserClass = pUserClass ;     stParams.szDebuggee = szDebuggee ;     stParams.szCmdLine  = szCmdLine  ;         // The handle to the debug thread     HANDLE hDbgThread = INVALID_HANDLE_VALUE ;         // Try to create the thread.     UINT dwTID = 0 ;     hDbgThread = (HANDLE)_beginthreadex ( NULL        ,                                           0           ,                                           DebugThread ,                                           &stParams   ,                                           0           ,                                           &dwTID       ) ;     ASSERT ( INVALID_HANDLE_VALUE != hDbgThread ) ;     if (INVALID_HANDLE_VALUE == hDbgThread )     {         VERIFY ( CloseHandle ( hStartAck ) ) ;         return ( INVALID_HANDLE_VALUE ) ;     }         // Wait until the debug thread gets good and cranking.     DWORD dwRet = ::WaitForSingleObject ( hStartAck , INFINITE ) ;     ASSERT (WAIT_OBJECT_0 == dwRet ) ;     if (WAIT_OBJECT_0 != dwRet )     {         VERIFY ( CloseHandle ( hStartAck ) ) ;         VERIFY ( CloseHandle ( hDbgThread ) ) ;         return ( INVALID_HANDLE_VALUE ) ;     }         // Get rid of the acknowledgment handle.     VERIFY ( CloseHandle ( hStartAck ) ) ;         // Check that the debug thread is still running. If it isn't,     // the debuggee probably couldn't get started.     DWORD dwExitCode = ~STILL_ACTIVE ;     if ( FALSE == GetExitCodeThread ( hDbgThread , &dwExitCode ) )     {         ASSERT ( !"GetExitCodeThread failed!" ) ;         VERIFY ( CloseHandle ( hDbgThread ) ) ;         return ( INVALID_HANDLE_VALUE ) ;     }      ASSERT ( STILL_ACTIVE == dwExitCode ) ;     if ( STILL_ACTIVE != dwExitCode )     {         VERIFY ( CloseHandle ( hDbgThread ) ) ;         return ( INVALID_HANDLE_VALUE ) ;     }         // Create the synchronization events so that the main thread can     // tell the debug loop what to do.     BOOL bCreateDbgSyncEvts =                   CreateDebugSyncEvents ( lpDebugSyncEvents , *lpPID ) ;     ASSERT ( TRUE == bCreateDbgSyncEvts ) ;     if ( FALSE == bCreateDbgSyncEvts )     {         // This is a serious problem. I got the debug thread going, but         // I was unable to create the synchronization events that the         // user interface thread needs to control the debug thread. My         // only option here is to punt. I'll kill the         // debug thread and just return. I can't do much else.         TRACE ( "StartDebugging : CreateDebugSyncEvents failed\n" ) ;         VERIFY ( TerminateThread ( hDbgThread , (DWORD)-1 ) ) ;         VERIFY ( CloseHandle ( hDbgThread ) ) ;         return ( INVALID_HANDLE_VALUE ) ;     }          // Just in case someone modifies the function and fails to properly     // initialize the returned value.     ASSERT ( INVALID_HANDLE_VALUE != hDbgThread ) ;          // Life is good!     return ( hDbgThread ) ; }
end example

Assertions in .NET Windows Forms or Console Applications

Before I get into the gritty details of .NET assertions, I want to mention one key mistake I've seen in almost all .NET code written, especially in many of the samples from which developers are lifting code to build their applications. Everyone forgets that it's entirely possible to have an object parameter passed as null. Even when developers are using assertions, the code looks like the following:

void DoSomeWork ( string TheName ) {      Debug.Assert ( TheName.Length > 0 ) ;

Instead of triggering the assertion, if TheName is null, calling the Length property causes a System.NullReferenceException exception in your application, effectively crashing it. This is the horrible case where the assertion is causing a nasty side effect, thus breaking the cardinal rule of assertions. Of course, it logically follows that if developers aren't checking for null objects in their assertions, they aren't checking for them in their normal parameter checking. Do yourself a huge favor and start checking objects for null.

The fact that .NET applications don't have to worry about pointers and memory blocks means that at least 60 percent of the assertions we were used to handling in the C++ days just went away. On the assertion front, the .NET team added as part of the System.Diagnostic namespace two objects, Debug and Trace, which are active only if you defined DEBUG or TRACE, respectively, when compiling your application. Both of these defines can be specified as part of the project Property Pages dialog box. As you've seen, the Assert method is the method handling assertions in .NET. Interestingly enough, both Debug and Trace have identical methods, including an Assert method. I find it a little confusing to have two possible assertions that are conditionally compiled differently. Consequently, since assertions should be active only in debug builds, I use only Debug.Assert for assertions. Doing so avoids surprises from end users calling me up and asking about a weird dialog box or message telling them something went bad. I strongly suggest you do the same so that you contribute to some consistency in the world of assertions.

There are three overloaded Assert methods. All three take a Boolean value as their first or only parameter, and if the value is false, the assertion is triggered. As shown in the preceding examples in which I used Debug.Assert, one of the methods takes a second parameter of type string, which is shown as a message in the output. The final overloaded Assert method takes a third parameter of type string, which provides even more information when the assertion triggers. In my experience, the two-parameter approach is the easiest to use because I simply copy the condition checked in the first parameter and paste it in as a string. Of course, now that the assertion requiring the conditional expression is in quotes, make it part of your code reviews to verify that the string value always matches the real condition. The following code shows all three Assert methods in action:

Debug.Assert ( i > 3 )  Debug.Assert ( i > 3 , "i > 3" )  Debug.Assert ( i > 3 , "i > 3" , "This means I got a bad parameter")

The .NET Debug object is intriguing because you can see the output in multiple ways. The output for the Debug object—and the Trace object for that matter—goes through another object, named a TraceListener. Classes derived from TraceListener are added to the Debug object's Listener collection property. The beauty of the TraceListener approach is that each time an assertion fails, the Debug object runs through the Listener collection and calls each TraceListener object in turn. This convenient functionality means that even when new and improved ways of reporting assertions surface, you won't have to make major code changes to benefit from them. Even better, in the next section, I'll show you how you can add new TraceListener objects without changing your code at all, which makes for ultimate extensibility!

The default TraceListener object, appropriately named DefaultTraceListener, sends the output to two different places, the most visible of which is the assertion message box shown in Figure 3-1. As you can see in the figure, the bulk of the message box is taken up with the stack walk and parameter types as well as the source and line for each item. The top lines of the message box report the string values you passed to Debug.Assert. In the case of Figure 3-1, I just passed "Debug.Assert assertion" as the second parameter to Debug.Assert.

click to expand
Figure 3-1: The DefaultTraceListener message box

The result of pressing each button is described in the title bar for the message box. The only interesting button is Retry. If you're running under a debugger, you simply drop into the debugger at the line directly after the assertion. If you're not running under a debugger, clicking Retry triggers a special exception and then launches the Just In Time debugger selector to allow you to pick which registered debugger you'd like to use to debug the assertion.

In addition to the message box output, Debug.Assert also sends all the output through OutputDebugString so the attached debugger will get the output. The output has a nearly identical format, shown in the following code. Since the DefaultTraceListener does the OutputDebugString output, you can always use Mark Russinovich's excellent DebugView (www.sysinternals.com) to view the output even when you're not running under a debugger. I'll discuss this in more detail later in the chapter.

---- DEBUG ASSERTION FAILED ---- ---- Assert Short Message ---- Debug.Assert assertion ---- Assert Long Message ----         at HappyAppy.Fum()  d:\asserterexample\asserter.cs(15)     at HappyAppy.Fo(StringBuilder sb)  d:\asserterexample\asserter.cs(20)     at HappyAppy.Fi(IntPtr p)  d:\asserterexample\asserter.cs(24)     at HappyAppy.Fee(String Blah)  d:\asserterexample\asserter.cs(29)     at HappyAppy.Baz(Double d)  d:\asserterexample\asserter.cs(34)     at HappyAppy.Bar(Object o)  d:\asserterexample\asserter.cs(39)     at HappyAppy.Foo(Int32 i)  d:\asserterexample\asserter.cs(46)     at HappyAppy.Main()  d:\\asserterexample\asserter.cs(76)

Armed with the information supplied by Debug.Assert, you should never again have to wonder how you got into the assertion condition! The .NET Framework also supplies two other TraceListener objects. To write the output to a text file, use the TextWriterTraceListener class. To write the output to the event log, use the EventLogTraceListener class. Unfortunately, the TextWriterTraceListener and EventLogTraceListener classes are essentially worthless because they log only the message fields to your assertions and not the stack trace at all. The good news is that implementing your own TraceListener objects is fairly trivial, so as part of BugslayerUtil.NET.DLL, I went ahead and wrote the correct versions for TextWriterTraceListener and EventLogTraceListener for you: BugslayerTextWriterTraceListener and BugslayerEventLogTraceListener, respectively.

Neither BugslayerTextWriterTraceListener nor BugslayerEventLogTraceListener are very exciting classes. BugslayerTextWriterTraceListener is derived directly from TextWriterTraceListener, so all it does is override the Fail method, which is what Debug.Assert calls to do the output. Keep in mind that when using BugslayerTextWriterTraceListener or TextWriterTraceListener, the associated text file for the output isn't flushed unless you set the trace element autoflush attribute to true in the application configuration file, explicitly call Close on the stream or file, or set Debug.AutoFlush to true so that each write causes a flush to disk. For some bizarre reason, the EventLogTraceListener class is sealed, so I couldn't derive directly from it and had to derive from the abstract TraceListener class directly. However, I did retrieve the stack trace in an interesting way. The default StackTrace class provided by .NET makes it easy to get the current stack trace at any point, as the following snippet shows:

StackTrace StkTrc = new StackTrace ( ) ;

Compared with the gyrations you have to go through with native code to get a stack trace, the .NET way is a fine example of how .NET can make your life easier. StackTrace returns the collection of StackFrame objects that comprise the stack. Looking through the documentation for StackFrame, you'll see that it has all sorts of interesting methods for getting the source line and number. The StackTrace object has a ToString method that I thought for sure would have some sort of option for adding the source and line to the resulting stack trace. Alas, I was wrong. Therefore, I had to spend 30 minutes coding up and testing a class that BugslayerStackTrace derived from StackTrace, which overrides ToString, to add the source and line information beside each method. The two methods from BugslayerStackTrace that do the work are shown in Listing 3-3.

Listing 3-3: BugslayerStackTrace building a full stack trace with source and line information

start example
 /// <summary> /// Builds a readable representation of the stack trace /// </summary> /// <returns> /// A readable representation of the stack trace /// </returns> public override string ToString ( ) {     // New up the StringBuilder to hold all the stuff.     StringBuilder StrBld = new StringBuilder ( ) ;         // First thing on is a line feed.     StrBld.Append ( DefaultLineEnd ) ;     // Loop'em and do'em!  You can't use foreach here as StackTrace     // is not derived from IEnumerable.     for ( int i = 0 ; i < FrameCount ; i++ )     {         StackFrame StkFrame = GetFrame ( i ) ;         if ( null != StkFrame )         {             BuildFrameInfo ( StrBld , StkFrame ) ;         }     }         return ( StrBld.ToString ( ) ) ; }     /*///////////////////////////////////////////////////////////////// // Private methods. /////////////////////////////////////////////////////////////////*/     /// <summary> /// Takes care of the scut work to convert a frame into a string /// and to plop it into a string builder /// </summary> /// <param name="StrBld"> /// The StringBuilder to append the results to /// </param> /// <param name="StkFrame"> /// The stack frame to convert /// </param> private void BuildFrameInfo ( StringBuilder StrBld   ,                                StackFrame    StkFrame  ) {     // Get the method from all the cool reflection stuff.     MethodBase Meth = StkFrame.GetMethod ( ) ;         // If nothing is returned, get out now.      if ( null == Meth )     {         return ;     }           // Grab the method.     String StrMethName = Meth.ReflectedType.Name ;          // Slap in the function indent if one is there.     if ( null != FunctionIndent )     {         StrBld.Append ( FunctionIndent ) ;     }          // Get the class type and name on there.     StrBld.Append ( StrMethName ) ;     StrBld.Append ( "." ) ;     StrBld.Append ( Meth.Name ) ;     StrBld.Append ( "(" ) ;          // Slap the parameters on, including all param names.     ParameterInfo[] Params = Meth.GetParameters ( ) ;     for ( int i = 0 ; i < Params.Length ; i++ )     {         ParameterInfo CurrParam = Params[ i ] ;         StrBld.Append ( CurrParam.ParameterType.Name ) ;         StrBld.Append ( " " ) ;         StrBld.Append ( CurrParam.Name ) ;         if ( i != ( Params.Length - 1 ) )         {             StrBld.Append ( ", " ) ;         }     }         // Close the param list.     StrBld.Append ( ")" ) ;          // Get the source and line on only if there is one.     if ( null != StkFrame.GetFileName ( ) )     {         // Am I supposed to indent the source?  If so, I need to put         // a line break on the end followed by the indent.         if ( null != SourceIndentString )         {             StrBld.Append ( LineEnd ) ;             StrBld.Append ( SourceIndentString ) ;         }         else         {             // Just add a space.             StrBld.Append ( ' ' ) ;         }              // Get the file and line of the problem on here.         StrBld.Append ( StkFrame.GetFileName ( ) ) ;         StrBld.Append ( "(" ) ;         StrBld.Append ( StkFrame.GetFileLineNumber().ToString());         StrBld.Append ( ")" ) ;     }             // Always stick a line feed on.     StrBld.Append ( LineEnd ) ; }
end example

Now that you have other TraceListener classes that are worth adding to the Listeners collection, we can add and remove TraceListener objects in code. As with any .NET collection, call the Add method to add an object and the Remove method to get rid of one. Note that the default trace listener is named "Default." The following code example shows adding BugslayerTextWriterTraceListener and removing DefaultTraceListener:

Stream AssertFile = File.Create ( "BSUNBTWTLTest.txt" ) ;     BugslayerTextWriterTraceListener tListener =              new BugslayerTextWriterTraceListener ( AssertFile ) ;     Debug.Listeners.Add ( tListener ) ;     Debug.Listeners.Remove ( "Default" ) ;

Controlling the TraceListener Object with Configuration Files

If you develop console and Windows Forms applications, for the most part, DefaultTraceListener should serve most of your needs. However, having a message box that pops up every once in a while can wreak havoc on any automated test scripts you might have. Alternatively, you use a third-party component in a Win32 service and the debug build of that component is properly using Debug.Assert. In both of these cases, you want to be able to shut off the message box generated by DefaultTraceListener. You could add code to remove the DefaultTraceListener object, but you can also remove it without touching the code.

Any .NET binary can have an external XML configuration file associated with it. This file resides in the same directory as the binary and is the name of the binary with .CONFIG appended to the end. For example, the configuration file for FOO.EXE is FOO.EXE.CONFIG. You can easily add a configuration file to your project by adding a new XML file named APP.CONFIG. That file will automatically be copied to the output directory and named to match the binary.

In the XML configuration file, the assert element under system.diagnostics has two attributes. If you set the first attribute, assertuienabled, to false, .NET doesn't display message boxes and the output still goes through OutputDebugString. The second attribute, logfilename, allows you to specify a file you want any assertion output written to. Interestingly, when you specify a file in the logfilename attribute, any trace statements, which I'll discuss later in the chapter, will also appear in the file. A minimal configuration file is shown in the next code snippet, and you can see how easy it is to shut off the assertion message boxes. Don't forget that the master configuration file MACHINE.CONFIG has the same settings as the EXE configuration file, so you can optionally turn off message boxes on the whole machine using the same settings.

<?xml version="1.0" encoding="UTF-8" ?> <configuration>     <system.diagnostics>         <assert assertuienabled="false"                 logfilename="tracelog.txt" />     </system.diagnostics> </configuration> 

As I mentioned earlier, you can add and remove listeners without touching the code, and as you probably guessed, the configuration file has something to do with it. This file looks straightforward in the documentation, but the documentation at the time I am writing this book is not correct. After a little experimentation, I figured out all the gyrations necessary to control your listeners correctly without changing the code.

All the action happens under the trace element of the configuration. The trace element happens to have one very important optional attribute you should always set to true in your configuration files: autoflush. By setting autoflush to true, you force the output buffer to be flushed each time a write operation occurs. If you don't set autoflush, you'll have to add calls to your code to get the output.

Underneath trace is the listener element, where TraceListener objects are added or removed. Removing a TraceListener object is very simple. Specify the remove element, and set the name attribute to the string name of the desired TraceListener object. Below is the complete configuration file necessary to remove DefaultTraceListener.

<?xml version="1.0" encoding="UTF-8" ?> <configuration>     <system.diagnostics>       <trace autoflush="true" indentsize="0">          <listeners>            <remove name="Default" />          </listeners>       </trace>     </system.diagnostics> </configuration>

The add element has two required attributes. The name attribute is a string that specifies the name of the TraceListener object as it is placed into the TraceListener.Name property. The second attribute, type, is the one that's confusing, and I'll explain why. The documentation shows only adding a type that is in the global assembly cache (GAC) and hints that adding your own trace listeners is much harder than it needs to be. The one optional attribute, initializeData, is the string passed to the constructor of the TraceListener object.

To add a TraceListener object that's in the GAC, the type element specifies only the complete class of the TraceListener object. The documentation indicates that to add a TraceListener object that's not in the GAC, you'll have to plug in a whole bunch of stuff like culture and public key tokens. Fortunately, all you need to do is simply specify the complete class, a comma, and the name of the assembly. That's what causes the System.Configuration. ConfigurationException to be thrown, so don't include the comma and class name. The following shows the proper way of adding the global TextWriterTraceListener class:

<?xml version="1.0" encoding="UTF-8" ?> <configuration>     <system.diagnostics>       <trace autoflush="true" indentsize="0">          <listeners>             <add name="CorrectWay"                  type="System.Diagnostics.TextWriterTraceListener"                   initializeData="TextLog.log"/>          </listeners>       </trace>     </system.diagnostics> </configuration>

To add those TraceListener objects that don't reside in the GAC, the assembly containing the TraceListener derived class must reside in the same directory as the binary. I tried every possible path combination and configuration setting option, and I found that there's no way to force the configuration file to include an assembly from a different directory. When adding the derived TraceListener object, you do add the comma followed by the name of the assembly. The following shows how to add BugslayerTextWriterTraceListener from BugslayerUtil.NET.DLL:

<?xml version="1.0" encoding="UTF-8" ?> <configuration>     <system.diagnostics>       <trace autoflush="true" indentsize="0">          <listeners>             <add name="AGoodListener"                  type= "Wintellect.BugslayerTextWriterTraceListener,BugslayerUtil.NET"                   initializeData="BSUTWTL.log"/>          </listeners>       </trace>     </system.diagnostics> </configuration>

Assertions in ASP.NET Applications and XML Web Services

I'm really glad to see a development platform that has ideas for handling assertions built in. We have a whole namespace in System.Diagnostics that contains all these helpful classes, culminating in the Debug object. Like most of you, I started learning .NET by creating console and Windows Forms applications because they were a lot easier to fit in my head at the time. When I turned to ASP.NET, I was already using Debug.Assert, and I figured that Microsoft had done the right thing by getting rid of the message box automatically. Surely they realized that when running under ASP.NET, I'd be able to break into the debugger when encountering an assertion. Imagine my surprise when I triggered an assertion and nothing stopped! I did see the normal assertion output written to the debugger Output window, but I didn't see any OutputDebugString calls showing the assertion. Because XML Web services in .NET are essentially ASP.NET applications without a user interface, I tried the same experiments with an XML Web service and had the same results. (For the rest of this section, I'll use the term ASP.NET to include ASP.NET and XML Web services.) Amazingly, this meant that no real assertions existed in ASP.NET! Without assertions you might as well not program! The only good news is that DefaultTraceListener doesn't pop up the normal message box in ASP.NET applications.

Without assertions, I felt like I was programming naked, and I knew I had to do something about it. After thinking about whether to introduce a new assertion object, I decided the best solution was to stick with Debug.Assert as the one and only way of handling assertions. Doing so enabled me to deal with several key issues. The first was having one consistent way of doing assertions across all of .NET—I didn't ever want to wonder whether code would run in Windows Forms or ASP.NET and possibly use the wrong assertion. The second issue concerned using a third-party library that does use Debug.Assert and ensuring that those assertions appeared in the same place as all the other assertions.

A third issue was to make using the assertion library as painless as possible. After writing lots of utility code, I realized the importance of integrating the assertion library easily into an application. The final issue I wanted to deal with was having a server-side control that enabled you to see the assertions easily on the page. All the code is in BugslayerUtil.NET.DLL, so you might want to open that project as well as the test harness application, BSUNAssertTest, which is located in the Test directory below BugslayerUtil.NET. Make sure you create a virtual directory in Microsoft Internet Information Services (IIS) that points to the BSUNAssertTest directory before you open the project.

The issues I wanted to address made it blindingly obvious that I was looking at creating a special class derived from TraceListener. I'll talk about that code in a moment, but no matter how cool I made TraceListener, I had to find a way to hook up my TraceListener object as well as remove DefaultTraceListener. No matter what, that was going to require a code change on your part because I had to get some code executed. To make using assertions simple and to ensure the assertion library would be called as early as possible, I used a class derived from System.Web.HttpApplication because the constructor and Init method are the very first things called in an ASP.NET application. The first step to assertion nirvana is to derive your Global class in Global.ASAX.cs (or Global.ASAX.vb) by using my AssertHttpApplication class. That will get my ASPTraceListener properly hooked up as well as put a reference to it in the application state bag under "ASPTraceListener" so that you can change output options on the fly. If all you want in your application is the option to stop when an assertion triggers, this is all you have to do.

To see assertions on the page, I wrote a very simple control named, appropriately enough, AssertControl. You can add AssertControl to your Toolbox by right-clicking on the Web Forms tab and selecting Add/Remove Items from the shortcut menu. In the Customize Toolbox dialog box, select the .NET tab, click the Browse button, and browse over to BugslayerUtil.NET.DLL in the File Open dialog box. Now you can simply drag AssertControl to any page for which you need assertions. You don't need to touch the control in your code because the ASPTraceListener class will hunt it down on the page and produce the appropriate output. Even if AssertControl is nested in another control, it will still be found. If no assertions occur when processing the page on the server, AssertControl produces no output at all. If you do have an assertion, the same assertion messages and stack trace displayed in a Windows-based or console application are displayed by AssertControl. Since multiple assertions can appear on the page, AssertControl shows all of them. Figure 3-2 shows the BSUNAssertTest page after an assertion is triggered. The text at the bottom of the page is the AssertControl output.

click to expand
Figure 3-2: An ASP.NET application displaying an assertion using AssertControl

All the real work takes place in the ASPTraceListener class, the bulk of which is shown in Listing 3-4. To be your one-stop-shop TraceListener, ASPTraceListener has several properties that allow you to redirect as well as change output on the fly. Table 3-1 describes those properties and lists their default values.

Table 3-1: ASPTraceListener Output and Control Properties

Property

Default Value

Description

ShowDebugLog

true

Shows output in any attached debugger

ShowOutputDebugString

false

Shows output through OutputDebugString

EventSource

null/Nothing

The name of the event source for writing output to the event log. No permissions or security checking for access to the event log is done inside BugslayerUtil.NET.DLL. You'll need to request permissions before setting the EventSource.

Writer

null/Nothing

The TextWriter object for writing output to a file

LaunchDebuggerOnAssert

true

If a debugger is attached, the debugger stops immediately when an assertion triggers.

All the main work for doing the assertion output, which includes finding the assertion controls on the page, is done by the ASPTraceListener.HandleOutput method, shown in Listing 3-4. My first attempt at writing the HandleOutput method was much more involved. Although I could get the current IHttpHandler for the current HTTP request from the static HttpContext.Current.Handler property, I couldn't find a way to determine whether the handler was an actual System.Web.UI.Page. If I could figure out that it was a page, I could easily grind through and find any assertion controls on the page. My original attempt was to write quite a bit of code by using the very cool reflection interfaces so that I could walk the derivation chains myself. As I was getting close to finishing around 500 lines of code, Jeff Prosise innocently asked if I had seen the is operator, which determines whether a run-time type of an object is compatible with a given type. Developing my own is operator functionality was an interesting exercise, but it wasn't something I needed to do.

Once I had the Page object, I started looking for an AssertControl on the page. I knew it could be embedded inside another control, so I used a little recursion to walk through everything. Of course, when doing recursion, I needed to ensure I had a degenerative case or I could have easily ended up in an infinite recursion case. For ASPTraceListener.FindAssertControl, I chose to take advantage of the very interesting out keyword, which allows you to pass a method parameter by reference but doesn't require you to initialize it. It's more logical to treat the condition not found as null, and the out keyword lets me do that.

The final work I do with an assertion in the ASPTraceListener.HandleOutput method is determine whether I'm supposed to pop into the debugger when an assertion is triggered. The wonderful System.Diagnostics.Debugger object allows you to communicate with a debugger from inside your code. If a debugger is currently debugging the code, the Debugger.IsAttached property will be true and you can simply call Debugger.Break to force a breakpoint stop in the debugger. This solution assumes, of course, that you're actively debugging that particular Web site. I still need to handle the case of getting a debugger cranked up when you're not debugging.

If you look at the Debugger class, you'll see it has a very cool method named Launch that allows you to start a debugger to attach to your process. However, if the user account the process is running under isn't in the Debugger Users group, Debugger.Launch doesn't work. If you want to attach the debugger from the assertion code when you're not running under a debugger, you're going to have to get the account ASP.NET runs under in the Debugger Users group. Before I go on, I need to say that by allowing ASP.NET to spawn the debugger, you are potentially opening security holes, so you'll want to enable this only on development machines that aren't directly connected to the Internet.

On Windows 2000 and Windows XP, ASP.NET runs under the ASPNET account, so that's what you'll add to the Debugger Users group. Once you've added the account, you'll need to restart IIS so that Debugger.Launch will bring up the Just-In-Time (JIT) Debugging dialog. For Windows Server 2003, ASP.NET runs under the NETWORK SERVICE account. After you add NETWORK SERVICE to Debugger Users, you'll need to restart the machine.

Once I got Debugger.Launch working by getting the security correct, I had to ensure that I wasn't going to be calling Debugger.Launch only under the right conditions. If I called Debugger.Launch when no one was logged in on the server, I was going to cause lots of problems because the JIT debugger could be waiting for a key press on a window station no one could get to! In the ASPTraceListener class, I need to make sure the HTTP request is from the local machine because that indicates someone is logged in and there to debug the assertion. The ASPTraceListener.IsRequestFromLocalMachine method checks to see whether either the host address is 127.0.0.1 or the LOCAL_ADDR server variable is equal to the user's host address.

One final comment I have to make about bringing up the debugger involves Terminal Services. If you have a Remote Desktop Connection window open to a server, the Web address for any requests to the server will resolve as an IP address on the server, as you would expect. My assertion code's default property, if the requesting address comes from the same machine as the server, is to call Debugger.Launch. As I was testing an ASP.NET application by using Remote Desktop and running the browser on the server, I was in for a rude shock when an assertion triggered. (Keep in mind that I was not debugging the process on any machine.)

While I expected to see either the security warning message box or the JIT Debugger dialog box, all I saw was a hung browser. I was quite perplexed until I walked over to the server and moved the mouse. There on the server's logon screen was my security message box! It dawned on me that although it looked like a bug, it was explainable. As it is the ASPNET/NETWORK SERVICE account that brings up the message box or JIT Debugger dialog box, ASP.NET has no knowledge that it was a Terminal Services session that had the connection. There's no way for those accounts to keep track of exactly which session called Debugger.Launch. Consequently, the output goes to the only real window station on the machine.

The good news is that if you have a debugger attached, either inside the Remote Desktop Connection window or on another machine, the call to Debugger.Launch works exactly how you'd expect and stops in the debugger. Additionally, if you make the call into the server from a browser on another machine, the call to Debugger.Launch will not stop. The moral of the story is that if you're going to use Remote Desktop Connection to connect to the server and if you are going to run a browser inside that Remote Desktop Connection window (for example, on the server), you need to have a debugger attached to that server's ASP.NET process.

Although it's inexcusable that Microsoft made no provisions for assertions inside ASP.NET, at least armed with AssertControl, you can start programming. If you're looking for a control to learn how to extend, AssertControl is pretty bare bones. An interesting extension to AssertControl would be to use JavaScript in the code to bring up a better UI, like a Web dialog box, to tell the user there was a problem.

Listing 3-4: Important methods of ASPTraceListener

start example
public class ASPTraceListener : TraceListener {  /* CODE REMOVED FOR CLARITY * /         // The method that's called when an assertion failed.     public override void Fail ( String Message       ,                                 String DetailMessage  )     {         // For reasons beyond me, it's nearly impossible to         // consistently be able to get the number of items on the         // stack up to the Debug.Assert. Sometimes it's 4 other         // times it's 5. Unfortunately, the only way I can see         // to handle this is to manually figure it out. Bummer.         StackTrace StkSheez = new StackTrace ( ) ;         int i = 0 ;         for ( ; i < StkSheez.FrameCount ; i++ )         {             MethodBase Meth = StkSheez.GetFrame(i).GetMethod ( ) ;                 // If nothing is returned, get out now.             if ( null != Meth )             {                 if ( "Debug" == Meth.ReflectedType.Name )                 {                     i++ ;                     break ;                 }             }         }         BugslayerStackTrace Stk = new BugslayerStackTrace ( i ) ;         HandleOutput ( Message , DetailMessage , Stk ) ;     }      /* CODE REMOVED FOR CLARITY * /         /// <summary>     /// Private assertion title message.     /// </summary>     private const String AssertionMsg = "ASSERTION FAILURE!\r\n" ;     /// <summary>     /// Private hard coded carriage return line feed string.     /// </summary>     private const String CrLf = "\r\n" ;     /// <summary>     /// The private assertion string boarder.     /// </summary>     private const String Border =         "----------------------------------------\r\n" ;         /// <summary>     /// Output the assertion or trace message.     /// </summary>     /// <remarks>     /// Takes care of all the output for the trace or assertion.     /// </remarks>     /// <param name="Message">     /// The message to display.     /// </param>     /// <param name="DetailMessage">     /// The detailed message to display.     /// </param>     /// <param name="Stk">     /// The  value     /// containing stack walk information for the assertion. If this is     /// not null, this function is called from an assertion. Trace     /// output sets this to null.     /// </param>     protected void HandleOutput ( String              Message       ,                                   String              DetailMessage ,                                   BugslayerStackTrace Stk            )     {         // Create the StringBuilder to help me build the text         // string for the output here.         StringBuilder StrOut = new StringBuilder ( ) ;             // If the StackArray is not null, it's an assertion.         if ( null != Stk )         {             StrOut.Append ( Border ) ;             StrOut.Append ( AssertionMsg ) ;             StrOut.Append ( Border ) ;         }             // Pop on the message.         StrOut.Append ( Message ) ;         StrOut.Append ( CrLf ) ;             // Poke on the detail message if it's there.         if ( null != DetailMessage )         {             StrOut.Append ( DetailMessage ) ;             StrOut.Append ( CrLf ) ;         }             // If an assertion, show the stack below a border.         if ( null != Stk )         {             StrOut.Append ( Border ) ;         }             // Go through and poke on all the stack information         // if it's present.         if ( null != Stk )         {             Stk.SourceIndentString = "      " ;             Stk.FunctionIndent = "   " ;             StrOut.Append ( Stk.ToString ( ) ) ;         }             // Since I use the string multiple places, get it once here.         String FinalString = StrOut.ToString ( ) ;             if ( ( true == m_ShowDebugLog         ) &&              ( true == Debugger.IsLogging ( ) )    )         {             Debugger.Log ( 0 , null , FinalString ) ;         }         if ( true == m_ShowOutputDebugString )         {             OutputDebugStringA ( FinalString ) ;         }         if ( null != m_EvtLog )         {             m_EvtLog.WriteEntry ( FinalString ,                 System.Diagnostics.EventLogEntryType.Error ) ;         }         if ( null != m_Writer )         {             m_Writer.WriteLine ( FinalString ) ;             // Add a CRLF just in case.             m_Writer.WriteLine ( "" ) ;             m_Writer.Flush ( ) ;         }             // Always do the page level output!         if ( null != Stk )         {             // Do the warning output to the current TraceContext.             HttpContext.Current.Trace.Warn ( FinalString ) ;                          // Hunt down the AssertionControl on the page.                          // First, make sure the handler is a page!             if ( HttpContext.Current.Handler is System.Web.UI.Page )             {                 System.Web.UI.Page CurrPage =                     (System.Web.UI.Page)HttpContext.Current.Handler ;                                      // Take the easy way out if there are no                 // controls (which I doubt!)                 if ( true == CurrPage.HasControls( ) )                 {                     // Hunt down the control.                     AssertControl AssertCtl = null ;                     FindAssertControl ( CurrPage.Controls ,                                         out AssertCtl      ) ;                                                              // If there was one, add the happy assertion!                     if ( null != AssertCtl )                     {                         AssertCtl.AddAssertion ( Message       ,                                                  DetailMessage ,                                                  Stk            ) ;                     }                 }             }                 // Finally, launch the debugger if I'm supposed to.             if ( true == m_LaunchDebuggerOnAssert )             {                 // If a debugger is already attached, I can just use                 // Debugger.Break on it. It doesn't matter where the                 // debugger is running, as long as it's running on this                 // process.                 if ( true == Debugger.IsAttached )                 {                     Debugger.Break ( ) ;                 }                 else                 {                     // With the changes to the security model for the                     // RTM release of .NET, the ASPNET account that                     // ASPNET_WP.EXE uses is set to User instead of                     // running as the System account. In order to                     // allow Debugger.Launch to work, you need to add                     // ASPNET to the Debugger Users group. While this                     // is safe for development systems, you may want                     // to be careful on production systems.                     bool bRet = IsRequestFromLocalMachine ( ) ;                     if ( true == bRet )                     {                         Debugger.Launch ( ) ;                     }                 }             }         }         else         {             // The TraceContext is accessible right off the             // HttpContext.             HttpContext.Current.Trace.Write ( FinalString ) ;         }     }         /// <summary>     /// Determines if the request came from a local machine.     /// </summary>     /// <remarks>     /// Checks if the IP address is 127.0.0.1 or the server variable     /// LOCAL_ADDR matches the current machine.     /// </remarks>     /// <returns>     /// Returns true if the request came from the local machine,     /// false otherwise.     /// </returns>     private bool IsRequestFromLocalMachine ( )     {         // Get the request object.         HttpRequest Req = HttpContext.Current.Request ;             // Is the user sitting on the loopback node?         bool bRet = Req.UserHostAddress.Equals ( "127.0.0.1" ) ;         if ( false == bRet )         {             // Get the local IP address out of the server             // variables.             String LocalStr =                 Req.ServerVariables.Get ( "LOCAL_ADDR" ) ;             // Compare the local IP with the IP address that             // accompanied the request.             bRet = Req.UserHostAddress.Equals ( LocalStr ) ;         }         return ( bRet ) ;         }          /// <summary>     /// Finds any assertion controls on the page.     /// </summary>     /// <remarks>     /// All assertion controls have the name "AssertControl" so this     /// method simply loops through the page's control collection     /// looking for them. It also looks through children of children     /// recursively.     /// </remarks>     /// <param name="CtlCol">     /// The collection control to look through.     /// </param>     /// <param name="AssertCtrl">     /// The output parameter that contains the assertion control found.     /// </param>     private void FindAssertControl ( ControlCollection CtlCol    ,                                      out AssertControl AssertCtrl )         {         // Loop through all the controls in the control array.         foreach ( Control Ctl in CtlCol )         {             // Is this one the assertion control?             if ( "AssertControl" == Ctl.GetType().Name )             {                 // Yep!  Stop now.                 AssertCtrl = (AssertControl)Ctl ;                 return ;             }             else             {                 // If this control has children do them too.                 if ( true == Ctl.HasControls ( ) )                 {                     FindAssertControl ( Ctl.Controls ,                                         out AssertCtrl ) ;                     // If one of the children had the assertion,                     // I can stop now.                     if ( null != AssertCtrl )                     {                         return ;                     }                 }             }         }         // Didn't find it in this chain.         AssertCtrl = null ;         return  ;     } }
end example

Assertions in Native C++ Applications

For years an old computer joke describing all the different computer languages as different cars has always described C++ as a Formula One racer, fast but dangerous to drive. Another joke says that C++ gives you a gun to shoot yourself in the foot and has the trigger nearly pulled when you get past "Hello World!" I think it's safe to say native C++ is a Formula One car that has two shotguns so that you can shoot yourself in the foot at the same time you crash. Even with the smallest mistake capable of crashing your application, using assertions heavily with C++ is the only way you stand a chance of debugging your native applications.

C and C++ also have all sorts of functions that can help make your assertions as descriptive as possible. Table 3-2 shows the helper functions you can use to check which condition you need.

Table 3-2: Helper Functions for Descriptive C and C++ Assertions

Function

Description

GetObjectType

A graphics device interface (GDI) subsystem function that returns the type for a GDI handle

IsBadCodePtr

Checks that the memory pointer can be executed

IsBadReadPtr

Checks that the memory pointer is readable for the specified number of bytes

IsBadStringPtr

Checks that the string pointer is readable up to the string's NULL terminator or the maximum number of characters specified

IsBadWritePtr

Checks that the memory pointer is writable for the specified number of bytes

IsWindow

Checks whether the HWND parameter is a valid window

The IsBad* functions are not thread-safe. Whereas one thread calls IsBadWritePtr to check the access permissions on a piece of memory, another thread could be changing the memory the pointer points to. What these functions give you is a snapshot of a moment in time. Some readers of this book's first edition argued that since the IsBad* functions aren't multithread-safe, you should never use them because they can lead you into a false sense of security. I couldn't disagree more. There's no practical way of guaranteeing truly thread-safe memory checks unless you wrap every byte access inside structured exception handling (SEH). Doing this is possible, but the code would be so slow that you couldn't use the machine. The one problem, which a few people have blown well out of proportion, is that the IsBad* functions can eat EXCEPTION_GUARD_PAGE exceptions in very rare cases. In all my years of Windows development, I've never run into this problem. I'm more than willing to live with these two limitations of the IsBad* functions for all the wonderful benefits of knowing that a pointer is bad.

The following code shows one of the mistakes I used to make with my C++ assertions:

// Poor assertion usage BOOL CheckDriveFreeSpace ( LPCTSTR szDrive ) {     ULARGE_INTEGER ulgAvail ;     ULARGE_INTEGER ulgNumBytes ;     ULARGE_INTEGER ulgFree ;     if ( FALSE == GetDiskFreeSpaceEx ( szDrive      ,                                        &ulgAvail    ,                                        &ulgNumBytes ,                                        &ulgFree      ) )      {         ASSERT ( FALSE ) ;         return ( FALSE ) ;     }       }

Although I was using ASSERT, which is good, I wasn't showing the condition that failed. The assertion message box showed only the expression "FALSE," which isn't that helpful. When using an assertion, you want to try to get as much information about the assertion failure in the message box as possible.

My friend Dave Angel pointed out to me that in C and C++, you can just use the logical NOT operator (!) and use a string as its operand. This combination gives you a much better expression in the assertion message box so that you at least have an idea of what failed without looking at the source code. The following example shows the proper way to assert a false condition:

// Proper assertion usage BOOL CheckDriveFreeSpace ( LPCTSTR szDrive ) {     ULARGE_INTEGER ulgAvail ;     ULARGE_INTEGER ulgNumBytes ;     ULARGE_INTEGER ulgFree ;     if ( FALSE == GetDiskFreeSpaceEx ( szDrive      ,                                        &ulgAvail    ,                                        &ulgNumBytes ,                                        &ulgFree      ) )      {         ASSERT ( !"GetDiskFreeSpaceEx failed!" ) ;         return ( FALSE ) ;     }       }

You can also extend Dave's assertion trick by using the logical AND conditional operator (&&) to perform a normal assertion and still get the message text. The following example shows how. Note that when using the logical AND trick, you do not use the "!" in front of the string.

BOOL AddToDataTree ( PTREENODE pNode ) {     ASSERT ( ( FALSE == IsBadReadPtr ( pNode , sizeof ( TREENODE) ) ) &&              "Invalid parameter!"                  ) ;       } 

The VERIFY Macro

Before we get into the various assertion macros and functions you'll encounter in Windows development as well as some of the problems with them, I want to talk about the VERIFY macro that's used quite a bit in Microsoft Foundation Class (MFC) library development. In a debug build, the VERIFY macro behaves the same way as a normal assertion because it's defined to be ASSERT. If the condition evaluates to 0, the VERIFY macro triggers the normal assertion message box to warn you. In a release build, the VERIFY macro does not display a message box, however, the parameter to the VERIFY macro stays in the source code and is evaluated as a normal part of processing.

In essence, the VERIFY macro allows you to have normal assertions with side effects, and those side effects stay in release builds. Ideally, you should never use conditions for any type of assertion that causes side effects. However, in one situation the VERIFY macro is useful—when you have a function that returns an error value that you wouldn't check otherwise. For example, when you call ResetEvent to clear a signaled event handle and the call fails, there's not much you can do other than terminate the application, which is why most engineers call ResetEvent and never check the return value in either debug or release builds. If you wrap the call with the VERIFY macro, at least you'll be notified in your debug builds that something went wrong. Of course, I could achieve the same results by using ASSERT, but VERIFY saves me the trouble of creating a new variable just to store and verify the return value of the ResetEvent call—a variable that would probably be used only in debug builds anyway.

I think most MFC programmers use the VERIFY macro for convenience, but you should try to break yourself of the habit. In most cases, when programmers use the VERIFY macro, they should be checking the return value instead. A good example of where everyone seems to use VERIFY is around the CString::LoadString member function, which loads resource strings. Using VERIFY this way is fine in a debug build because if LoadString fails, the VERIFY macro warns you. In a release build, however, if LoadString fails, you end up using an uninitialized variable. If you're lucky, you'll just have a blank string, but most of the time, you'll crash in your release build. The moral of this story is to check your return values. If you're about to use a VERIFY macro, you need to ask whether ignoring the return value will cause you any problems in release builds.

start sidebar
Debugging War Story: Disappearing Files and Threads

The Battle

While working on a version of NuMega's BoundsChecker, we had incredible difficulty with random crashes that were almost impossible to duplicate. The only clues we had were that file handles and thread handles occasionally became invalid, which meant that files were randomly closing and thread synchronization was sometimes breaking. The user interface developers were also experiencing occasional crashes, but only when running under the debugger. These problems plagued us throughout development, finally escalating to the point where all developers on the team stopped what they were doing and started trying to solve these bugs.

The Outcome

The team nearly tarred and feathered me because the problem turned out to be my fault. I was responsible for the debug loop in BoundsChecker. In the debug loop, you use the Windows debugging API to start and control another process, the debuggee, and to respond to debug events the debugger generates. Being a conscientious programmer, I saw that the WaitForDebugEvent function was returning handle values for some of the debugging event notifications. For example, when a process started under a debugger, the debugger would get a structure that contained a handle to the process and the initial thread for that process.

Because I'm so careful, I knew that if an API gave you a handle to some object and you no longer needed the object, you called CloseHandle to free the underlying memory for that object. Therefore, whenever the debugging API gave me a handle, I closed that handle as soon as I finished using it. That seemed like the reasonable thing to do.

However, much to my chagrin, I hadn't read the fine print in the debugging API documentation, which says that the debugging API itself closes any process and thread handles it generates. What was happening was that I was holding some of the handles returned by the debugging API until I needed them, but I was closing those same handles after I finished using them—after the debugging API had already closed them.

To understand how this situation led to our problem, you need to know that when you close a handle, the operating system marks that handle value as available. Microsoft Windows NT 4, the operating system we were using at the time, is particularly aggressive about recycling handle values. (Microsoft Windows 2000 and Microsoft Windows XP exhibit the same aggressive behavior toward handle values.) Our UI portions, which were heavily multithreaded and opened many files, were creating and using new handles all the time. Because the debugging API was closing my handles and the operating system was recycling them, sometimes the UI portions would get one of the handles that I was saving. As I closed my copies of the handles later, I was actually closing the UI's threads and file handles!

I was barely able to avoid the tar and feathers because I showed that this bug was also in the debug loop of previous versions of BoundsChecker. We'd just gotten lucky before. What had changed was that the version we were working on had a new and improved UI that was doing much more with files and threads, so the conditions were ripe for my bug to do more damage.

The Lesson

I could have avoided this problem if I'd read the fine print in the debugging API documentation. Additionally—and this is the big lesson—I learned that you always check the return values to CloseHandle. Although you can't do much when you close an invalid handle, the operating system does tell you when you're doing something wrong, and you should pay attention.

As a side note, I want to mention that if you attempt to double-close a handle or pass a bad value to CloseHandle and you're running under a debugger, Windows operating systems will report an "Invalid Handle" exception (0xC0000008). When you see that exception value, you can stop and explore why it occurred.

I also learned that it really helps to be able to out-sprint your coworkers when they're chasing you with a pot of tar and bags of feathers.

end sidebar

Different Types of Visual C++ Assertions

Even though I define all my C++ assertion macros and functions to just plain ASSERT, which I'll talk about in a moment, I want to quickly go over the different types of assertions available in Visual C++ and provide a little information about their implementation. That way, if you see one of them in someone else's code, you can recognize it. I also want to alert you to the problems with some of the implementations.

assert, _ASSERT, and _ASSERTE

The first type of assertion is from the C run-time library, the ANSI C standard assert macro. This version is portable across all C compilers and platforms and is defined by including ASSERT.H. In the Windows world, if you're working with a console application and it fails an assertion, assert will send the output to stderr. If your application is a Windows graphical user interface (GUI) application, assert will show the assertion failure as a message box.

The second type of assertion in the C run-time library is specific to Windows. These assertions are _ASSERT and _ASSERTE, which are defined in CRTDBG.H. The only difference between the two is that the _ASSERTE version also prints the expression passed as its parameter. Because the expression is so important to have, especially when your test engineers are testing, if you're using the C run-time library, you should always use _ASSERTE. Both macros are part of the extremely useful debug run-time library code, and the assertions are only one of its many features.

Although assert, _ASSERT, and _ASSERTE are convenient to use and free of charge, they do have a few drawbacks. The assert macro has two problems that can cause you some grief. The first problem is that the filename display truncates to 60 characters, so sometimes you don't have any idea which file triggered the assertion. The second problem with assert occurs if you're working on a project that doesn't have a UI, such as a Windows service or a COM out-of-process server. Because assert sends its output to stderr or to a message box, you can miss the assertion. In the case of the message box, your application will hang because you can't dismiss the message box when you can't display your UI.

The C run-time implementation macros, on the other hand, address the issue with defaulting to a message box by allowing you to redirect the assertion to a file or to the OutputDebugString API function by calling the _CrtSetReportMode function. All the Microsoft-supplied assertions suffer from one fatal flaw, however: they change the state of the system, which is the cardinal rule assertions can't break. Having your assertion calls suffer from side effects is almost worse than not using assertions at all. The following code shows an example of how the supplied assertions can change your state between debug and release builds. Can you spot the problem?

// Send the message over to the window. If it times out, the other  // thread is hung, so I need to abort the thread. As a reminder, the  // only way to check whether SendMessageTimeout failed is to check  // GetLastError. If the function returned 0 and the last error is // 0, SendMessageTimeout timed out. _ASSERTE ( NULL != pDataPacket ) ; if ( NULL == pDataPacket ) {     return ( ERR_INVALID_DATA ) ; } LRESULT lRes = SendMessageTimeout ( hUIWnd                  ,                                     WM_USER_NEEDNEXTPACKET  ,                                     0                       ,                                     (LPARAM)pDataPacket     ,                                     SMTO_BLOCK              ,                                     10000                   ,                                     &pdwRes                  ) ; _ASSERTE ( FALSE != lRes ) ; if ( FALSE == lRes ) {     // Get the last error value.     DWORD dwLastErr = GetLastError ( ) ;     if ( 0 == dwLastErr )     {         // The UI is hung or not processing data fast enough.         return ( ERR_UI_IS_HUNG ) ;     }     // If the error is anything else, there was a problem      // with the data sent as a parameter.     return ( ERR_INVALID_DATA ) ; } return ( ERR_SUCCESS ) ;      

The problem, which is insidious, is that the supplied assertions destroy the last error value. In the preceding case, the "_ASSERTE ( FALSE != lRes )" executes, shows the message box, and changes the last error value to 0. Thus in debug builds, the UI thread always appears to hang, whereas in the release build, you see the cases in which the parameters passed to SendMessageTimeout are bad.

The fact that the last error value is destroyed with the system-supplied assertions might never be an issue in the code you write, but my own experience has been different—two bugs that took a great deal of time to track down turned out to be related to this problem. Fortunately, if you use the assertion presented later in this chapter in the section "SUPERASSERT," I'll take care of this problem for you as well as give you some information that the system-supplied version doesn't.

ASSERT_KINDOF and ASSERT_VALID

If you're programming with MFC, you'll run into two additional assertion macros that are specific to MFC and are fantastic examples of proactive debugging. If you've declared your classes with DECLARE_DYNAMIC or DECLARE_SERIAL, you can use the ASSERT_KINDOF macro to check whether a pointer to a CObject-derived class is a specific class or is derived from a specific class. The ASSERT_KINDOF assertion is just a wrapper around the CObject::IsKindOf method. The following code snippet first checks the parameter in the ASSERT_KINDOF assertion and then does the real parameter error checking.

BOOL DoSomeMFCStuffToAFrame ( CWnd * pWnd ) {     ASSERT ( NULL != pWnd ) ;     ASSERT_KINDOF ( CFrameWnd , pWnd ) ;     if ( ( NULL  == pWnd ) ||          ( FALSE == pWnd->IsKindOf ( RUNTIME_CLASS ( CFrameWnd ) ) ) )     {         return ( FALSE ) ;     }           // Do some MFC stuff; pWnd is guaranteed to be a CFrameWnd or     // to be derived from a CFrameWnd.       }

The second MFC-specific assertion macro is ASSERT_VALID. This assertion resolves down to AfxAssertValidObject, which completely validates that the pointer is a proper pointer to a CObject-derived class. After validating the pointer, ASSERT_VALID calls the object's AssertValid method. AssertValid is a method that you can override in your derived classes so that you can check each of the internal data structures in your class. This method is a great way to do a deep validation on your classes. You should override AssertValid for all your key classes.

SUPERASSERT

Having told you what the problems are with the supplied assertions, now I want to show you how I was able to fix and extend the assertions to really make them tell you how and why you had a problem, plus much more. Figures 3-3 and 3-4 show examples of a SUPERASSERT error dialog box. In the first edition of this book, the SUPERASSERT output was a message box that showed the location of the failed assertion, the last error value translated into text, and the call stack. As you can see in Figures 3-3 and 3-4, SUPERASSERT has certainly grown up! (However, I have resisted calling it SUPERDUPERASSERT!)

click to expand
Figure 3-3: Example of a folded SUPERASSERT dialog box

click to expand
Figure 3-4: Example of an unfolded SUPERASSERT dialog box

The most amazing part about writing books and articles is the incredible conversations I've had with readers through e-mail and in person. I feel so lucky to learn from such amazingly smart folks! Soon after the first edition came out, Scott Bilas and I had a great e-mail exchange about his theories on what assertion messages should do and how they should be used. I originally used a message box because I wanted to be as lightweight as possible. However, after swapping lots of interesting thoughts with Scott, I was convinced that assertion messages should offer more features, such as assertion suppression. As we chatted, Scott even provided some code to accomplish dialog box folding as well as his ASSERT macros for keeping track of ignore counts and such. After being inspired by Scott's ideas, I worked up the new version of SUPERASSERT. I did this right after the first version came out and have been using the new code in all my development since, so it's been sufficiently thrashed.

Figure 3-3 shows the parts of the dialog box that are always visible. The failure edit control contains the reason for the failure, either assertion or verify, the expression that failed, the location of the failure, the decoded last error value, and how many times this particular assertion has failed. If the assertion is running on Windows XP, Windows Server 2003 or higher, it will also display the total kernel handle count in the process. In SUPERASSERT, I translate the last error values into their textual representations. Seeing the error messages written out as text is extremely helpful when an API function fails: you can see why it failed and can start debugging faster. For example, if GetModuleFileName fails because the input buffer isn't large enough, SUPERASSERT will set the last error value to 122, which is ERROR_INSUFFICIENT_BUFFER from WINERROR.H. By immediately seeing the text "The data area passed to a system call is too small," you know exactly what the problem is and how to fix it. Figure 3-3 shows a standard Windows error message, but you can add your own message resource to the SUPERASSERT last error message translation. For more information about using your own message resources, look up the "Message Compiler" topic in MSDN. An added incentive for using message resources is that they make internationalizing your application much easier.

The Ignore Once button, located below the failure edit control, simply continues execution. It is the default button, so you can press Enter or the spacebar to immediately move past an assertion after analyzing its cause. Abort Program calls ExitProcess to attempt to do a clean shutdown of the application. The Break Into Debugger button causes a DebugBreak call so that you can start debugging the failure by either popping into the debugger or starting the JIT debugger. The Copy To Clipboard button on the second row copies to the clipboard all the text from the failure edit control as well as the information from all threads you have done stack walks for. The last button, More>> or Less<<, toggles the dialog box folding.

The Create Mini Dump and Email Assertion buttons need a little explanation. If the version of DBGHELP.DLL loaded in the process space has the minidump functions exported, the Create Mini Dump button is enabled. If the minidump functions are not accessible, the button is disabled. To best preserve the state of the application, SUPERASSERT suspends all other threads in the application. This means that SUPERASSERT can't use the common file dialog because that dialog cranks up some background threads that stick around after it goes away. When SUPERASSERT suspends all the threads, the common file dialog code hangs because it's waiting on a suspended thread. Consequently, I can't use the common file dialog. The dialog that pops up after clicking Create Mini Dump, then, is a simple prompt dialog box with an edit box that asks you for the full path and name of the minidump.

The Email Assertion button is active only if you've put a special define in your source file that indicates the e-mail address you want the assertion information mailed to. This is a fantastic feature for testers to use to send the appropriate assertion to the correct developer. All the information, including all stack walks, is part of the e-mail message, so developers should be able to get exactly why the assertion triggered. At the top of each source file, you'll want to include code like the following to automatically get the e-mail capabilities. It's not required to use SUPERASSERT to define SUPERASSERT_EMAIL, but I strongly suggest you do.

#ifdef SUPERASSERT_EMAIL #undef SUPERASSERT_EMAIL // Please put your own email address in! #define SUPERASSERT_EMAIL "john@wintellect.com" #endif

The unfolded SUPERASSERT dialog box in Figure 3-4 has all sorts of helpful options in it. The Ignore section allows you to control how you want to ignore the assertion. The first button, Ignore Assertion Always, marks the particular assertion as ignored for the life of the program. You can also specify a specific ignore count by typing it into the edit box. To set the ignore specific count to the desired assertion, click This Assertion Only. To ignore subsequent assertions no matter where they come from, click All Assertions. Originally, I didn't think you should ever ignore an assertion, but after having had the option to skip an assertion that's triggered in every iteration through a loop, I don't know how I lived without it.

The final part of the dialog box is dedicated to the call stack. Even though I had the call stack in the first version of SUPERASSERT, if you look closely at the call stack in the edit box, you'll see all the local variables and their current values under each function! The SymbolEngine library, which is part of BugslayerUtil.DLL, can decode all basic types, structures, classes, and arrays. It's also smart enough to decode important values such as character pointers and character arrays. In Figure 3-4 you can see that it's showing both ANSI and Unicode strings as well as decoding a RECT structure. Building the proper symbol decoding was one of the toughest pieces of code I've written! If you're interested in the dirty details, you can read more about the SymbolEngine library in Chapter 4. The good news for you is that I did all the hard work, so you don't even have to think about it if you don't want to!

The first button in the Stack section, Walk Stack, allows you to walk the stack. Walk Stack is disabled in Figure 3-4 because the stack has already been walked. The Thread ID drop-down list allows you to pick the thread you want to take a look at. If the application has only a single thread, the Thread ID drop-down list doesn't appear in the dialog box. The Locals Depth drop-down list allows you choose how deep local variables will be expanded. This drop-down list is akin to the plus arrows next to expandable items in the debugger watch window. The higher the number, the more will be displayed about appropriate local variables. For example, if you had a local of type int**, you'd have to set the locals depth to 3 to see the value of the integer pointed to by that variable. The Expand Arrays check box tells SUPERASSERT to expand all array types encountered. Calculating deeply nested types or pointers as well as large arrays is quite expensive, so you probably don't want to expand arrays unless you need to. Of course, SUPERASSERT does the right thing by reevaluating all the local variables on the fly when you change the locals depth or request, so you can see the information you need when you need it.

SUPERASSERT has some global options you can change on the fly as well. The Global SUPERASSERT Options dialog box is shown in Figure 3-5 and can be opened by selecting Options from the system menu.


Figure 3-5: The Global SUPERASSERT Options dialog box

The Stack Walking section determines how much stack walking will be done when the SUPERASSERT dialog box appears. The default is to walk only the thread that had the assertion, but if you want to have the fastest popup possible, you might want to set it to only walk stacks manually. The Additional Mini Dump Information section specifies how much information you want written out to any minidumps. (For more information, see the MINIDUMP_TYPE enumeration documentation.) Checking Play Sounds On Assertions plays the default message beep when the SUPERASSERT dialog box pops up. Checking Force Assertion To Top sets the SUPERASSERT dialog box as the topmost window. If the process is being debugged, the topmost window setting is not applied because SUPERASSERT can block the debugger. All SUPERASSERT global settings are stored in the registry under HKCU\Software\Bugslayer\SUPERASSERT. Also stored in the registry are the last location and fold state so that SUPERASSERT returns to the same position you expect every time.

I want to mention a few other details about SUPERASSERT. The first is that, as you see in Figures 3-3 and 3-4, SUPERASSERT has a grippy area in the lower right corner so that you can resize the dialog box. SUPERASSERT is also multiple-monitor-aware, so on the system menu is an option allowing you to center the dialog box on the current monitor so that you can get SUPERASSERT back to a desired location. What you can't see from the figures is that you don't have to display SUPERASSERT at all. At first, you might think that option is counterproductive, but I assure you it isn't! If you followed my recommendations in Chapter 2 and started testing your debug builds with a regression-testing tool, you know that handling random assertion messages is almost impossible. Because of the problems in handling assertion messages, your test engineers are much less likely to use the debug build. With my assertion code, you can specify that you want the output to go to OutputDebugString, a file handle, the event log, or any combination of the three. This flexibility allows you to run the code and get all the great assertion information but still automate your debug builds. Finally, SUPERASSERT is super smart about when it pops up. It always checks whether an interactive user is logged into the process windows station. If no one is interactively logged into that window station, SUPERASSERT won't pop up and hang your application.

Because of all the information SUPERASSERT gives me, I'm using the debugger less than ever before, which is a huge win for debugging speed. When I hit an assertion, I position the SUPERASSERT dialog box to pop up on my second monitor. I look through the local variable information and start reading source code on my primary monitor. I've found that I'm able to solve about 20 percent more bugs without starting the debugger. Although the first edition was very helpful, the second edition really rocks!

Using SUPERASSERT

Integrating SUPERASSERT into your applications is quite easy. You simply need to include BUGSLAYERUTIL.H, which is probably best included in your precompiled header, and link against BUGSLAYERUTIL.LIB so that you bring BUGSLAYERUTIL.DLL into the address space. That gives you the ASSERT macro and automatically redirects any existing ASSERT and assert calls to my functions. My code does not redirect the _ASSERT and _ASSERTE macros because you might be doing some advanced work or specific output with the debug run-time library and I don't want to break your existing solutions. My code leaves ASSERT_KINDOF and ASSERT_VALID alone as well.

If you'd like to change where output goes, such as to the event log or a text file, use the SETDIAGASSERTOPTIONS macro, which takes several self-explanatory bit field macros that determine the location of the output. These bit field macros are all defined in DIAGASSERT.H.

A Word About Ignoring Assertions

It's always a bad moment when another developer or tester drags you over to his machine to blame your code for a crash. It's even worse when you start diagnosing the problem by asking him if he clicked the Ignore button on an assertion that popped up. Many times he'll swear to you that he didn't, but you know that there's no way that crash could have occurred without a particular assertion trigger. When you finally pin him down and force him to admit that he did click that Ignore button, you're on the verge of ripping his head off. If he had reported that assertion, you could have easily solved the problem!

The Ignore button, if you haven't already guessed, is potentially a very dangerous option because people are so tempted to press it! Although it might have been a little draconian, I seriously considered not putting an Ignore button on SUPERASSERT to force you to deal with the assertion and its underlying cause. In some companies, the developers add an easy way to check whether any assertions have been ignored in the current run. This allows them to check whether the Ignore button has been clicked before they waste their time looking at the crash.

If you're using SUPERASSERT, and you want to see how many assertions have been triggered overall, you can look at the global variable, g_iTotalAssertions, in SUPERASSERT.CPP. Of course, this presumes that either you have a debugger attached to the crashed program or you have a memory dump of the crashed program. If you'd like to get the total assertion counts programmatically, call GetSuperAssertionCount, which is exported from BUGSLAYERUTIL.DLL.

What you might want to consider adding to SUPERASSERT is complete logging of all assertions that are triggered. That way you'd automatically have a running total of the number of Ignore values clicked by users, allowing you to validate what user actions led to the crash. Some companies automatically log assertions to a central database so that they can keep track of assertion frequencies and determine whether developers and testers are improperly using the Ignore button.

Since I've talked about protecting yourself against the user's knee-jerk reaction of pressing the Ignore button, it's only fair that I mention that you might be doing it too. Assertions should never pop up in normal operation—only when something is amiss. Here's a perfect example of an improperly used assertion that I encountered while helping debug an application. When I choose an item on the most recently used menu that didn't have a target item, an assertion fired before the normal error handling. In such a case, the normal error handling was more than sufficient. If you're getting complaints that assertions are firing too much, you need to carefully analyze whether those assertions really need to be there.

SUPERASSERT Implementation Highlights

If you start looking at the code for SUPERASSERT, you might think it looks a little convoluted because it's spread across two separate sets of assertion code, SUPERASSERT.CPP and DIAGASSERT.CPP. The DIAGASSERT.CPP portion is actually the first version of SUPERASSERT. I left that code in place because quite a bit of UI code was involved with building the new SUPERASSERT, and since I couldn't use SUPERASSERT on itself, I needed a second set of assertions to make the SUPERASSERT development cleaner and more robust. Lastly, because SUPERASSERT requires more supporting code, the old assertion code is what you'll need to use in the one case in which SUPERASSERT can't be used, that is, when doing assertions in RawDllMain before anything in your module has been initialized.

The first interesting part of SUPERASSERT is the macro that eventually resolves down to the call to the SuperAssertion function, as shown in Listing 3-5. Since SUPERASSERT needs to keep track of local assertion ignore counts, the macro creates a new scope each time it's used. Inside the scope, it declares the two static integers to keep track of the number of times the particular assertion failed as well as the number of times the user wants to ignore the assertion. After checking the result of the expression, the rest of the macro is getting the current stack pointer and the frame pointer, and it is calling the actual SuperAssertion function.

Listing 3-5: The main ASSERT macro

start example
#ifdef _M_IX86 #define NEWASSERT_REALMACRO( exp , type )                              \ {                                                                      \     /* The local instance of the ignore count and the total hits. */   \     static int sIgnoreCount = 0 ;                                      \     static int sFailCount   = 0 ;                                      \     /* The local stack and frame at the assertion's location. */       \     DWORD dwStack ;                                                    \     DWORD dwStackFrame ;                                               \     /* Check the expression. */                                        \     if ( ! ( exp ) )                                                   \     {                                                                  \         /* Houston, we have a problem. */                              \         _asm { MOV dwStack , ESP }                                     \         _asm { MOV dwStackFrame , EBP }                                \         if ( TRUE == SuperAssertion ( TEXT ( type )         ,          \                                       TEXT ( #exp )         ,          \                                       TEXT ( __FUNCTION__ ) ,          \                                       TEXT ( __FILE__ )     ,          \                                       __LINE__              ,          \                                       SUPERASSERT_EMAIL     ,          \                                       (DWORD64)dwStack      ,          \                                       (DWORD64)dwStackFrame ,          \                                       &sFailCount           ,          \                                       &sIgnoreCount          ) )       \         {                                                              \             __asm INT 3                                                \         }                                                              \     }                                                                  \ } #endif  // _M_IX86 
end example

Most processing of the assertion is handled in SUPERASSERT.CPP, shown in Listing 3-6. The majority of the work is done in two functions, RealSuperAssertion and PopTheFancyAssertion. RealSuperAssertion determines whether the assertion is ignored, builds the actual assertion message, and figures out where the output needs to go. PopTheFancyAssertion has more interesting functionality. To minimize the impact on the application, I suspend all the other threads in the application when the SUPERASSERT dialog box is up. That way I can cleanly get the call stacks as well. To suspend all the threads and stop everything, I boost the asserting thread to time-critical priority. I found that some tricky issues come up when you suspend all other threads in an application! The biggest problem was not being able to allocate memory right before I was going to suspend the threads. It's entirely possible for another thread to be holding onto the C run-time heap critical section, and if I suspend it and then need memory, I can't acquire the critical section. Although I can run through and count the threads before boosting the priority, that's even more time the other threads are running while the asserting thread is grinding through lots of stuff. Consequently, I decided that it was best to just set a fixed array of the maximum number of threads I'm willing to handle. If you have more than 100 threads in your application, you'll need to update the k_MAXTHREADS value at the top of SUPERASSERT.CPP.

Listing 3-6: SUPERASSERT.CPP

start example
 /*---------------------------------------------------------------------- Debugging Applications for Microsoft .NET and Microsoft Windows  Copyright (c) 1997-2003 John Robbins -- All rights reserved. ----------------------------------------------------------------------*/ #include "PCH.h" #include "BugslayerUtil.h" #include "SuperAssert.h" #include "AssertDlg.h" #include "CriticalSection.h" #include "resource.h" #include "Internal.h"     /*////////////////////////////////////////////////////////////////////// // File Scope Typedefs, Constants, & Defines //////////////////////////////////////////////////////////////////////*/ // The maximum number of threads I can handle at once. const int k_MAXTHREADS = 100 ;     // The GetProcessHandleCount typedef. typedef BOOL (__stdcall *GETPROCESSHANDLECOUNT)(HANDLE , PDWORD) ; /*////////////////////////////////////////////////////////////////////// // File Scope Prototypes //////////////////////////////////////////////////////////////////////*/ // Does the actual work to pop the assertion dialog. static INT_PTR PopTheFancyAssertion ( TCHAR * szBuffer      ,                                       LPCSTR  szEmail       ,                                       DWORD64 dwStack       ,                                       DWORD64 dwStackFrame  ,                                       DWORD64 dwIP          ,                                       int *   piIgnoreCount  ) ;     // Tries to get the module causing the assertion. static SIZE_T GetModuleWithAssert ( DWORD64 dwIP   ,                                     TCHAR * szMod  ,                                     DWORD   dwSize  ) ;                                        // Yes, this is the compiler intrinsic, but you have to prototype it in // order to use it. extern "C" void * _ReturnAddress ( void ) ; #pragma intrinsic ( _ReturnAddress )     // A function to hide the machinations to get the open handles in the // process. static BOOL SafelyGetProcessHandleCount ( PDWORD pdwHandleCount ) ;     /*////////////////////////////////////////////////////////////////////// // File Scope Globals //////////////////////////////////////////////////////////////////////*/ // The number of assertions to ignore on a global basis. int g_iGlobalIgnoreCount = 0 ; // The total number of assertions. static int g_iTotalAssertions = 0 ; // The critical section that protects everything. static CCriticalSection g_cCS ; // The pointer to the GetProcessHandleCount function. static GETPROCESSHANDLECOUNT g_pfnGPH = NULL ;     /*////////////////////////////////////////////////////////////////////// // Implementation! //////////////////////////////////////////////////////////////////////*/ // Turn off "unreachable code" error from this function calling // ExitProcess. #pragma warning ( disable : 4702 ) BOOL RealSuperAssertion ( LPCWSTR  szType        ,                           LPCWSTR  szExpression  ,                           LPCWSTR  szFunction    ,                           LPCWSTR  szFile        ,                           int      iLine         ,                           LPCSTR   szEmail       ,                           DWORD64  dwStack       ,                           DWORD64  dwStackFrame  ,                           DWORD64  dwIP          ,                           int *    piFailCount   ,                           int *    piIgnoreCount  ) {     // Always start by bumping up the total number of assertions seen     // so far.     g_iTotalAssertions++ ;         // Bump up this particular instance failure count.     if ( NULL != piFailCount )     {         *piFailCount = *piFailCount + 1 ;     }         // See if there is any way to short circuit doing the whole dialog.     // A "-1" means ignore everything.     if ( ( g_iGlobalIgnoreCount < 0                        ) ||          ( ( NULL != piIgnoreCount ) && *piIgnoreCount < 0 )   )     {         return ( FALSE ) ;     }         // If I am in the middle of ignoring all assertions for a bit, I can     // skip out early!     if ( g_iGlobalIgnoreCount > 0 )     {         g_iGlobalIgnoreCount-- ;         return ( FALSE ) ;     }         // Am I supposed to skip this local assertion?     if ( ( NULL != piIgnoreCount ) && ( *piIgnoreCount > 0 ) )     {         *piIgnoreCount = *piIgnoreCount - 1 ;         return ( FALSE ) ;     }         // Holds the return value of the string (STRSAFE) manipulation     // functions.     HRESULT hr = S_OK ;         // Save off the last error value so I don't whack it doing the     // assertion dialog.     DWORD dwLastError = GetLastError ( ) ;         TCHAR szFmtMsg[ MAX_PATH ] ;     DWORD dwMsgRes = ConvertErrorToMessage ( dwLastError ,                                              szFmtMsg    ,                                              sizeof ( szFmtMsg ) /                                                     sizeof ( TCHAR ) ) ;     if ( 0 == dwMsgRes )     {         hr = StringCchCopy ( szFmtMsg                               ,                              sizeof ( szFmtMsg ) / sizeof ( TCHAR ) ,                   _T ( "Last error message text not available\r\n" ) ) ;         ASSERT ( SUCCEEDED ( hr ) ) ;     }         // Get the module information.     TCHAR szModuleName[ MAX_PATH ] ;     if ( 0 == GetModuleWithAssert ( dwIP , szModuleName , MAX_PATH ))     {         hr = StringCchCopy ( szModuleName                             ,                              sizeof ( szModuleName ) / sizeof (TCHAR) ,                              _T ( "<unknown application>" )           );         ASSERT ( SUCCEEDED ( hr ) ) ;     }         // Grab the synchronization object to block other threads from     // getting to this point.     EnterCriticalSection ( &g_cCS.m_CritSec ) ;         // The buffer to hold the expression message.     TCHAR szBuffer[ 2048 ] ; #define BUFF_CHAR_SIZE ( sizeof ( szBuffer ) / sizeof ( TCHAR ) )         if ( ( NULL != szFile ) && ( NULL != szFunction ) )     {         // Split out the base name from the whole filename.         TCHAR szTempName[ MAX_PATH ] ;         LPTSTR szFileName ;         LPTSTR szDir = szTempName ;             hr = StringCchCopy ( szDir                                    ,                              sizeof ( szTempName ) / sizeof ( TCHAR ) ,                              szFile                                   );         ASSERT ( SUCCEEDED ( hr ) ) ;         szFileName = _tcsrchr ( szDir , _T ( '\\' ) ) ;         if ( NULL == szFileName )         {             szFileName = szTempName ;             szDir = _T ( "" ) ;         }         else         {             *szFileName = _T ( '\0' ) ;             szFileName++ ;         }         DWORD dwHandleCount = 0 ;         if ( TRUE == SafelyGetProcessHandleCount ( &dwHandleCount ) )         {             // Use the new STRSAFE functions to ensure I don't run off             // the end of the buffer.             hr = StringCchPrintf (                        szBuffer                                      ,                        BUFF_CHAR_SIZE                                ,                       _T ( "Type         : %s\r\n"                   )\                       _T ( "Expression   : %s\r\n"                   )\                       _T ( "Module       : %s\r\n"                   )\                       _T ( "Location     : %s, Line %d in %s (%s)\r\n")\                       _T ( "LastError    : 0x%08X (%d)\r\n"          )\                       _T ( "               %s"                       )\                       _T ( "Fail count   : %d\r\n"                   )\                       _T ( "Handle count : %d"                       ),                        szType                                         ,                        szExpression                                   ,                        szModuleName                                   ,                        szFunction                                     ,                        iLine                                          ,                        szFileName                                     ,                        szDir                                          ,                        dwLastError                                    ,                        dwLastError                                    ,                        szFmtMsg                                       ,                        *piFailCount                                   ,                        dwHandleCount                                  );             ASSERT ( SUCCEEDED ( hr ) ) ;         }         else         {             hr = StringCchPrintf (                        szBuffer                                      ,                        BUFF_CHAR_SIZE                                ,                        _T ( "Type       : %s\r\n"                   ) \                        _T ( "Expression : %s\r\n"                   ) \                        _T ( "Module     : %s\r\n"                   ) \                        _T ( "Location   : %s, Line %d in %s (%s)\r\n")\                        _T ( "LastError  : 0x%08X (%d)\r\n"          ) \                        _T ( "             %s"                       ) \                        _T ( "Fail count : %d\r\n"                   ) ,                        szType                                         ,                        szExpression                                   ,                        szModuleName                                   ,                        szFunction                                     ,                        iLine                                          ,                        szFileName                                     ,                        szDir                                          ,                        dwLastError                                    ,                        dwLastError                                    ,                        szFmtMsg                                       ,                        *piFailCount                                   );             ASSERT ( SUCCEEDED ( hr ) ) ;         }     }     else     {         if ( NULL == szFunction )         {             szFunction = _T ( "Unknown function" ) ;         }         hr = StringCchPrintf ( szBuffer                        ,                           BUFF_CHAR_SIZE                       ,                           _T ( "Type       : %s\r\n"           ) \                           _T ( "Expression : %s\r\n"           ) \                           _T ( "Function   : %s\r\n"           ) \                           _T ( "Module     : %s\r\n"           ) \                           _T ( "LastError  : 0x%08X (%d)\r\n"  )                           _T ( "             %s"               ) ,                           szType                                 ,                           szExpression                           ,                           szFunction                             ,                           szModuleName                           ,                           dwLastError                            ,                           dwLastError                            ,                           szFmtMsg                                ) ;         ASSERT ( SUCCEEDED ( hr ) ) ;     }         if ( DA_SHOWODS == ( DA_SHOWODS & GetDiagAssertOptions ( ) ) )     {         OutputDebugString ( szBuffer ) ;         OutputDebugString ( _T ( "\n" ) ) ;     }         if ( DA_SHOWEVENTLOG ==                         ( DA_SHOWEVENTLOG & GetDiagAssertOptions ( ) ) )     {         // Only write to the event log if everything is really kosher.         static BOOL bEventSuccessful = TRUE ;         if ( TRUE == bEventSuccessful )         {             bEventSuccessful = OutputToEventLog ( szBuffer ) ;         }     }         if ( INVALID_HANDLE_VALUE != GetDiagAssertFile ( ) )     {         static BOOL bWriteSuccessful = TRUE ;                  if ( TRUE == bWriteSuccessful )         {             DWORD dwWritten ;             int    iLen = lstrlen ( szBuffer ) ;             char * pToWrite = NULL ;     #ifdef UNICODE             pToWrite = (char*)_alloca ( iLen + 1 ) ;                 BSUWide2Ansi ( szBuffer , pToWrite , iLen + 1 ) ; #else             pToWrite = szBuffer ; #endif             bWriteSuccessful = WriteFile ( GetDiagAssertFile ( )   ,                                            pToWrite                ,                                            iLen                    ,                                            &dwWritten              ,                                            NULL                     ) ;             if ( FALSE == bWriteSuccessful )             {                 OutputDebugString (                   _T ( "\n\nWriting assertion to file failed.\n\n" ) ) ;             }         }     }         // By default, treat the return as an IGNORE. This works best in     // the case the user does not want the MessageBox.     INT_PTR iRet = IDIGNORE ;         // Only show the dialog if the process is running interactively and     // the user wants to see it.     if ( ( DA_SHOWMSGBOX == ( DA_SHOWMSGBOX & GetDiagAssertOptions()))&&          ( TRUE == BSUIsInteractiveUser ( )                          )  )     {         iRet = PopTheFancyAssertion ( szBuffer      ,                                       szEmail       ,                                       dwStack       ,                                       dwStackFrame  ,                                       dwIP          ,                                       piIgnoreCount  ) ;     }         // I'm done with the critical section!     LeaveCriticalSection ( &g_cCS.m_CritSec ) ;         SetLastError ( dwLastError ) ;         // Does the user want to break into the debugger?     if ( IDRETRY == iRet )     {         return ( TRUE ) ;     }         // Does the user want to abort the program?     if ( IDABORT == iRet )     {         ExitProcess ( (UINT)-1 ) ;         return ( TRUE ) ;     }         // The only option left is to ignore the assertion.     return ( FALSE ) ; }     // Takes care of the grunge to get the assertion dialog shown. static INT_PTR PopTheFancyAssertion ( TCHAR * szBuffer      ,                                       LPCSTR  szEmail       ,                                       DWORD64 dwStack       ,                                       DWORD64 dwStackFrame  ,                                       DWORD64 dwIP          ,                                       int *   piIgnoreCount  ) {         // I don't do any memory allocation in this routine because I can     // get into some weird problems. I am about to boost this threads     // priority pretty high, in an attempt to starve the other     // threads so I can suspend them. If I try to allocate memory at     // that point, I can end up in a situation where a lower priority     // thread has the CRT or OS heap synch object and this thread needs     // it. Consequently, you are looking at one fat, happy deadlock.     // (Yes, I originally did this to myself, that's how I know about     // it!)     THREADINFO aThreadInfo [ k_MAXTHREADS ] ;     DWORD aThreadIds [ k_MAXTHREADS ] ;         // The first thread in the thread info array is ALWAYS the current     // thread. It's a zero based array, so the dialog code can treat     // all threads as equals. However, for this function, the array     // is treated as a one-based array so I don't suspend the current     // thread and such.     UINT uiThreadHandleCount = 1 ;         aThreadInfo[ 0 ].dwTID = GetCurrentThreadId ( ) ;     aThreadInfo[ 0 ].hThread = GetCurrentThread ( ) ;     aThreadInfo[ 0 ].szStackWalk = NULL ;         // The first thing is to blast the priority for this thread up to     // real time. I don't want to have a thread created while I'm     // preparing to suspend them.     int iOldPriority = GetThreadPriority ( GetCurrentThread ( ) ) ;     VERIFY ( SetThreadPriority ( GetCurrentThread ( )          ,                                  THREAD_PRIORITY_TIME_CRITICAL  ) ) ;         DWORD dwPID = GetCurrentProcessId ( ) ;         DWORD dwIDCount = 0 ;     if ( TRUE == GetProcessThreadIds ( dwPID                ,                                        k_MAXTHREADS         ,                                        (LPDWORD)&aThreadIds ,                                         &dwIDCount            ) )     {         // There has to be at least one thread!!         ASSERT ( 0 != dwIDCount ) ;         ASSERT ( dwIDCount < k_MAXTHREADS ) ;             // Calculate the number of handles.         uiThreadHandleCount = dwIDCount ;         // If the number of handles is 1, it's a single threaded app,         // and I don't need to do anything!         if ( ( uiThreadHandleCount > 1            ) &&              ( uiThreadHandleCount < k_MAXTHREADS )   )         {             // Open each handle, suspend it, and store the             // handle so I can resume them later.             int iCurrHandle = 1 ;             for ( DWORD i = 0 ; i < dwIDCount ; i++ )             {                 // Of course, don't suspend this thread!!                 if ( GetCurrentThreadId ( ) != aThreadIds[ i ] )                 {                     HANDLE hThread =                             OpenThread ( THREAD_ALL_ACCESS ,                                          FALSE             ,                                          aThreadIds [ i ]   ) ;                     if ( ( NULL != hThread                 ) &&                          ( INVALID_HANDLE_VALUE != hThread )   )                     {                         // If SuspendThread returns -1, there no point                         // and keeping that thread value around.                         if ( (DWORD)-1 != SuspendThread ( hThread ) )                         {                             aThreadInfo[iCurrHandle].hThread = hThread ;                             aThreadInfo[iCurrHandle].dwTID =                                                        aThreadIds[ i ] ;                             aThreadInfo[iCurrHandle].szStackWalk = NULL;                             iCurrHandle++ ;                         }                         else                         {                             VERIFY ( CloseHandle ( hThread ) ) ;                             uiThreadHandleCount-- ;                         }                     }                     else                     {                         // Either this thread has some security set on                         // it or it happened to end right after I                         // collected the threads.  Consequently, I need                         // to decrement the total thread handles or I                         // will be one off.                         TRACE( "Can't open thread: %08X\n" ,                                 aThreadIds [ i ]            ) ;                         uiThreadHandleCount-- ;                     }                 }             }         }     }         // Drop the thread priority back down!     SetThreadPriority ( GetCurrentThread ( ) , iOldPriority ) ;         // Ensure the application resources are set up.     JfxGetApp()->m_hInstResources = GetBSUInstanceHandle ( ) ;         // The assertion dialog its self.     JAssertionDlg cAssertDlg ( szBuffer                     ,                                szEmail                      ,                                dwStack                      ,                                dwStackFrame                 ,                                dwIP                         ,                                piIgnoreCount                ,                                (LPTHREADINFO)&aThreadInfo   ,                                uiThreadHandleCount           ) ;         INT_PTR iRet = cAssertDlg.DoModal ( ) ;         if ( ( 1 != uiThreadHandleCount           ) &&          ( uiThreadHandleCount < k_MAXTHREADS )    )     {         // Crank up the thread priority again!         int iOldPriority = GetThreadPriority ( GetCurrentThread ( ) ) ;         VERIFY ( SetThreadPriority ( GetCurrentThread ( )          ,                                      THREAD_PRIORITY_TIME_CRITICAL  ) );             // If I've suspended the other threads in the process, I need to          // resume them, close the handles and delete the array.         for ( UINT i = 1 ; i < uiThreadHandleCount ; i++ )         {             VERIFY ( (DWORD)-1 !=                         ResumeThread ( aThreadInfo[ i ].hThread ) ) ;             VERIFY ( CloseHandle ( aThreadInfo[ i ].hThread ) ) ;         }         // Drop the thread priority back to what it was.         VERIFY ( SetThreadPriority ( GetCurrentThread ( ) ,                                      iOldPriority           ) ) ;     }     return ( iRet ) ; }     BOOL BUGSUTIL_DLLINTERFACE     SuperAssertionA ( LPCSTR  szType        ,                       LPCSTR  szExpression  ,                       LPCSTR  szFunction    ,                       LPCSTR  szFile        ,                       int     iLine         ,                       LPCSTR  szEmail       ,                       DWORD64 dwStack       ,                       DWORD64 dwStackFrame  ,                       int *   piFailCount   ,                       int *   piIgnoreCount  ) {     int iLenType = lstrlenA ( szType ) ;     int iLenExp = lstrlenA ( szExpression ) ;     int iLenFile = lstrlenA ( szFile ) ;     int iLenFunc = lstrlenA ( szFunction ) ;         wchar_t * pWideType = (wchar_t*)                           HeapAlloc ( GetProcessHeap ( )       ,                                       HEAP_GENERATE_EXCEPTIONS ,                                       ( iLenType + 1 ) *                                         sizeof ( wchar_t )     ) ;     wchar_t * pWideExp = (wchar_t*)                          HeapAlloc ( GetProcessHeap ( )       ,                                      HEAP_GENERATE_EXCEPTIONS ,                                      ( iLenExp + 1 ) *                                        sizeof ( wchar_t )      ) ;     wchar_t * pWideFile = (wchar_t*)                           HeapAlloc ( GetProcessHeap ( )      ,                                       HEAP_GENERATE_EXCEPTIONS ,                                       ( iLenFile + 1 ) *                                            sizeof ( wchar_t )   );     wchar_t * pWideFunc = (wchar_t*)                           HeapAlloc ( GetProcessHeap ( )       ,                                       HEAP_GENERATE_EXCEPTIONS ,                                       ( iLenFunc + 1 ) *                                            sizeof ( wchar_t )   ) ;         BSUAnsi2Wide ( szType , pWideType , iLenType + 1 ) ;     BSUAnsi2Wide ( szExpression , pWideExp , iLenExp + 1 ) ;     BSUAnsi2Wide ( szFile , pWideFile , iLenFile + 1 ) ;     BSUAnsi2Wide ( szFunction , pWideFunc , iLenFunc + 1 ) ;         BOOL bRet ;     bRet = RealSuperAssertion ( pWideType                     ,                                 pWideExp                      ,                                 pWideFunc                     ,                                 pWideFile                     ,                                 iLine                         ,                                 szEmail                       ,                                 dwStack                       ,                                 dwStackFrame                  ,                                 (DWORD64)_ReturnAddress ( )   ,                                 piFailCount                   ,                                 piIgnoreCount                  ) ;         VERIFY ( HeapFree ( GetProcessHeap ( ) , 0 , pWideType ) ) ;     VERIFY ( HeapFree ( GetProcessHeap ( ) , 0 , pWideExp ) ) ;     VERIFY ( HeapFree ( GetProcessHeap ( ) , 0 , pWideFile ) ) ;         return ( bRet ) ; }     BOOL BUGSUTIL_DLLINTERFACE     SuperAssertionW ( LPCWSTR szType        ,                       LPCWSTR szExpression  ,                       LPCWSTR szFunction    ,                       LPCWSTR szFile        ,                       int     iLine         ,                       LPCSTR  szEmail       ,                       DWORD64 dwStack       ,                       DWORD64 dwStackFrame  ,                       int *   piFailCount   ,                       int *   piIgnoreCount  ) {     return ( RealSuperAssertion ( szType                      ,                                   szExpression                ,                                   szFunction                  ,                                   szFile                      ,                                   iLine                       ,                                   szEmail                     ,                                   dwStack                     ,                                   dwStackFrame                ,                                   (DWORD64)_ReturnAddress ( ) ,                                   piFailCount                 ,                                   piIgnoreCount                ) ) ; }     // Returns the number of times an assertion has been triggered in an // application. This number takes into account any way the assertion // was ignored. int BUGSUTIL_DLLINTERFACE GetSuperAssertionCount ( void ) {     return ( g_iTotalAssertions ) ; }     static BOOL SafelyGetProcessHandleCount ( PDWORD pdwHandleCount ) {     static BOOL bAlreadyLooked = FALSE ;     if ( FALSE == bAlreadyLooked )     {         HMODULE hKernel32 = ::LoadLibrary ( _T ( "kernel32.dll" ) ) ;         g_pfnGPH = (GETPROCESSHANDLECOUNT)                    ::GetProcAddress ( hKernel32               ,                                       "GetProcessHandleCount"  ) ;         FreeLibrary ( hKernel32 ) ;         bAlreadyLooked = TRUE ;     }     if ( NULL != g_pfnGPH )     {         return ( g_pfnGPH ( GetCurrentProcess ( ) , pdwHandleCount ) );     }     else     {         return ( FALSE ) ;     } }     static SIZE_T GetModuleWithAssert ( DWORD64 dwIP   ,                                     TCHAR * szMod  ,                                     DWORD   dwSize  ) {     // Attempt to get the memory base address for the value on the     // stack. From the base address, I'll try to get the module.     MEMORY_BASIC_INFORMATION stMBI ;     ZeroMemory ( &stMBI , sizeof ( MEMORY_BASIC_INFORMATION ) ) ;     SIZE_T dwRet = VirtualQuery ( (LPCVOID)dwIP                      ,                                   &stMBI                             ,                                   sizeof ( MEMORY_BASIC_INFORMATION ) );     if ( 0 != dwRet )     {         dwRet = GetModuleFileName ( (HMODULE)stMBI.AllocationBase ,                                     szMod                         ,                                     dwSize                         ) ;         if ( 0 == dwRet )         {             // Punt and simply return the EXE.             dwRet = GetModuleFileName ( NULL , szMod , dwSize ) ;         }     }     return ( dwRet ) ; } 
end example

The actual dialog code in ASSERTDLG.CPP is pretty uneventful, so it's not worth printing in the book. When Scott Bilas and I discussed what the dialog box should be written in, we realized it needed to be written in a lightweight language that didn't require any extra binaries other than the DLL containing the dialog box—pretty much ruling out MFC. At the time I wrote the dialog box, the Windows Template Library (WTL) hadn't been released. But I probably wouldn't have chosen to use it anyway, because I find templates problematic. Very few developers actually understand the ramifications of templates, and most of the bugs my company is involved in fixing are a direct result of templates, so I am hesitant to use them. Several years ago, Jeffrey Richter and I were involved in a project that needed an extremely lightweight UI, and we developed a straightforward UI class library named JFX. Jeffrey will tell you JFX stands for "Jeffrey's Framework," but it really stands for "John's Framework" no matter what he says. Regardless of the name, I used JFX to handle the UI. The complete source code is included with this book's sample files. There are a couple of test programs under the JFX directory that show you how to use JFX as well as the SUPERASSERT dialog code. The good news is that JFX is extremely small and compact—the release version of BugslayerUtil.DLL, which does a whole lot more than just SUPERASSERT, is less than 70 KB.

start sidebar
Common Debugging Question: Why do you always put the constants on the left-hand side of conditional statements?

As you look through my code, you'll notice that I always use statements such as "if (INVALID_HANDLE_VALUE == hFile)" instead of "if (hFile == INVALID_HANDLE_VALUE)." The reason I use this style is to avoid bugs. You can easily forget one of the equal signs, and using the former version will yield a compiler error if you do forget. The latter version might not issue a warning (whether or not it does depends on the warning level), and you'll change the variable's value. In compilers, trying to assign a value to a constant will produce a compiler error. If you've ever had to track down a bug involving an accidental assignment, you know just how difficult this type of bug is to find.

If you pay close attention, you'll notice that I also use constant variables on the left side of the equalities. As with the constant values case, the compilers will report errors when you try to assign a value to a constant variable. I've found that it's a lot easier to fix compiler errors than to fix bugs in the debugger.

Some developers have complained, sometimes vociferously, that the way I write conditional statements makes the code more confusing to read. I don't agree. My conditional statements take only a second longer to read and translate. I'm willing to give up that second to avoid wasting huge amounts of time later.

end sidebar




Debugging Applications for Microsoft. NET and Microsoft Windows
Debugging Applications for MicrosoftВ® .NET and Microsoft WindowsВ® (Pro-Developer)
ISBN: 0735615365
EAN: 2147483647
Year: 2003
Pages: 177
Authors: John Robbins

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net