P. 1
Using Assertions to Detect Bugs

Using Assertions to Detect Bugs

|Views: 99|Likes:
Published by Jo Stichbury
Part of the Code Clinic series. First published in 2008.
Part of the Code Clinic series. First published in 2008.

More info:

Published by: Jo Stichbury on Jun 29, 2010
Copyright:Attribution Non-commercial

Availability:

Read on Scribd mobile: iPhone, iPad and Android.
download as PDF, TXT or read online from Scribd
See more
See less

01/06/2013

pdf

text

original

Using Assertions to Detect Bugs

by Jo Stichbury
Here's one example of how to use the __ASSERT_DEBUG macro:
void CExampleClass::TestValue(TInt aValue) { #ifdef _DEBUG // Define panic literal in debug build only // to avoid a compiler warning, since it’s used // in the debug assertion only _LIT(KPanicDescriptor, “TestValue”); #endif __ASSERT_DEBUG((aValue>=0), ... }
User::Panic(KMyPanicDescriptor,KErrArgument));

__ASSERT_DEBUG

A good way to detect bugs in C++ is to use assertion statements. An assertion checks that the state of objects, function parameters or return values, is as expected. Typically, an assertion evaluates a statement and, if it is false, halts execution of the code. The definition of two assertion macros are found in e32def.h. The __ASSERT_ALWAYS macro performs an assertion check in both debug and release builds, while the __ASSERT_DEBUG macro checks code in debug builds only, and has no effect in release builds.
// For debug and release builds #define __ASSERT_ALWAYS(c,p) (void)((c)||(p,0)) ... // For debug builds only #if defined(_DEBUG) #define __ASSERT_DEBUG(c,p) (void)((c)||(p,0)) #else #define __ASSERT_DEBUG(c,p) #endif

Of course, this is somewhat awkward, especially if you expect to use a number of assertions to validate your code, so it’s sensible to define a panic utility function for your module:
enum TExampleEnginePanic { ECorrupt, // =0, ENotInitialized, // =1, EInvalidTestValue, // =2 }; static void ExamplePanic(TExampleEnginePanic aCategory) { _LIT(KExamplePanic, “EXAMPLE-ENGINE”); User::Panic(KExamplePanic, aCategory); }

The assertion in TestValue() is now written as follows:
void CExampleClass::TestValue(TInt aValue) { __ASSERT_DEBUG((aValue>=0), ExamplePanic(EInvalidTestValue); ... }

Parameter c is a conditional expression that returns a true or false value. Parameter p is a function, called if c is false, which should halt the flow of execution. In effect, the code behaves as follows:
if (!c) p

You’ll notice that the assertion macros do not panic by default, but allow you to specify the code to run should the assertion fail. My advice is always to use a panic in an assertion statement (or write a custom function that first logs the specific failure to file and then panics). This is because you should immediately terminate the running code and flag up the failure, rather than return an error, leave or do nothing. Assertions help you detect invalid states or bad program logic so you can fix your code as early as possible. It makes sense to stop the code at the point of error, thus forcing you to fix the problem (or remove the assertion statement if your assumption is invalid). If the assertion simply returns an error on failure, not only does it alter the program flow, but it also makes it harder to track down the bug.

The advantage of using an identifiable panic descriptor and enumerated values for different assertion conditions is traceability, for yourself and callers of your code. This is particularly useful for others using your libraries, since they may not have access to your code in its entirety, but merely to the header files. If your panic string is clear and unique, they should be able to locate the appropriate class and use the panic category enumeration to find the associated failure, which you will have named and documented clearly to explain why the assertion failed. There may be cases where there’s nothing more a client programmer can do other than report the bug to you, the author of the code; alternatively, the problem could be down to their misuse of your API, which they’ll be able to correct.

ASSERT()

If you don’t want or need an extensive set of enumerated panic values, and know that external callers will never need to trace a panic, you may consider using a more lightweight assertion macro. A good example of where this may be appropriate is when you want to test the internal state of an object, which could not possibly be modified by an external caller, and thus should always be valid unless you have a bug in your own code. In these cases, you may consider using the ASSERT macro, defined in e32def.h as follows:
#define ASSERT(x) __ASSERT_DEBUG(x, User::Invariant())

the fact that the latter are compiled into release code, because, while you may initially decide the assertion applies in release builds, this may change during the development or maintenance process. You could be storing up a future bug for the sake of avoiding an extra line of code.

__ASSERT_ALWAYS

It goes without saying that your code should be tested thoroughly. A user, or another software developer using your code, expects you to have debugged your code before release; they don’t want to do it for you. The use of assertion statements in debug builds can help you detect programming errors inside your code and fix them before delivery. So why would you need to use assertions in production code? Why does Symbian C++ have an __ASSERT_ALWAYS macro to execute checks in both debug and release builds? For one thing, won’t the impact on the execution speed and code size of adding extra checking to your code be prohibitive? Doesn’t the outcome of a failed assertion make for a poor user experience when it results in an application’s untimely end? Should you ever use __ASSERT_ALWAYS? First of all, consider what __ASSERT_ALWAYS is checking. You’ve used debug assertions to verify your internal code logic, so the only reason for using release build assertions is to check the validity of incoming parameter data. While invalid input is a bug from the perspective of your code, it may be caused by an exceptional condition in the calling code, which that code can handle gracefully if you return it an error or leave instead of firing an assertion and terminating the running code. Let’s consider a simple example. Your code offers a method to open and write to a file; the caller passes in the full file name and path, as well as the data to be written to the file. If the file does not exist, it is generally more appropriate to notify the caller through a returned error code or leave value than to assert in a release build. The calling code can then deal with the error.
TInt CTestClass::WriteToFile(const TDesC& aFilename, const TDesC8& aData) { TInt r = KErrNone; if (KNullDesC8==aData) {// No data to write - invalid! r = KErrArgument; } else { RFile file; __ASSERT_DEBUG(iFs, Panic(EUninitializedValue)); r = file.Open(*iFs, aFilename, EFileWrite); if (KErrNone==r) {// Only executes if file can be opened ... } } return (r); }

In debug builds only, if condition x is false, User::Invariant() is called, which itself panics with category USER and reason 0. The macro can be used as follows:
ASSERT(iPointer>0);

Personally, I like this macro because it doesn’t need you to provide a panic category or descriptor. Some Symbian developers consider it to be infuriating, because it allows you to scatter assertions throughout your code with no easy way to diagnose what went wrong. While it’s OK to do so if you’re sure you’re going to be the only developer that sees them fire, and immediately fix the code accordingly, if they do make it ‘downstream’, they make problems harder to trace. I think it’s better to use them than nothing at all (which may be the case if you have to go back and add supporting code to use __ASSERT_DEBUG()) but I include the following caution here for completeness: When adding an assertion, consider whether anyone else will ever see it. If so, make it traceable. As an alternative to using ASSERT to test the internal state of your object, you may wish to consider using the __TEST_INVARIANT macro.

Avoid ‘side effects’

Don’t put code with side effects into assertion statements that execute in debug builds only. By this, I mean code that is evaluated before a condition can be verified. For example:
__ASSERT_DEBUG(FunctionReturningTrue(), Panic(EUnexpectedReturnValue)); __ASSERT_DEBUG(++index<=KMaxValue, Panic(EInvalidIndex));

The reason for this is clear; the code may well behave as you expect in debug mode, but in release builds the assertion statements are removed by the preprocessor, and with them potentially vital steps in your programming logic. Rather than use the abbreviated cases above, you should perform the evaluations first and then pass the returned values into the assertion. In fact, you should follow this rule for both __ASSERT_ DEBUG and __ASSERT_ALWAYS statements, despite

The code illustrates this approach, by returning an error if the caller passes in an invalid parameter. (You’ll notice that I’ve included an __ASSERT_DEBUG statement to verify internal state in my code and catch any defects - such as attempting to use the file server handle before it has been initialized). If the caller is passing data from a source that it does not directly control, say from the user’s keystrokes or from a communications link, there is always a possibility for invalid input. In the example above, this could perhaps be handled by creating the file or using a default set of data. The calling code is far better placed to make a ‘decision’ about how to handle each scenario, so it’s better for the code above to indicate the invalid incoming data by returning an error or leaving than by asserting and panicking when the input is invalid. When illegal input values can only have arisen through a bug in calling code, and can potentially result in ‘bad things’ occurring, such as data corruption, it is desirable to use __ASSERT_ALWAYS checks. You could still return an error to the caller, but it’s probably clearer to flag up the problem so it can be fixed. A good example of this is in the Symbian C++ array classes (RArray and RPointerArray), which have __ASSERT_ALWAYS guards to prevent a caller passing an invalid index to methods that access the array. The class provides functions to determine the size of the array, so if a caller attempts to write beyond the end of the array, it can only be doing so because of a bug. The Symbian C++ descriptors are similarly protected to prevent writing outside of the descriptor’s data area. __ASSERT_ALWAYS can be used to protect against illegal input that can only ever have arisen through a bug in the caller. The assertion should cause a panic to indicate to the calling code that they have a programming error which needs to be resolved. Once the bug is fixed, the code runs and the failed assertion is never seen by the user. __ASSERT_ALWAYS should not be used to protect against invalid input that occurs because of an error or exception, since an assertion failure will be seen as a panic by the user, resulting in a poor experience.

This article was first published in 2008, on developer.symbian. com, as part of the Code Clinic series. The author would like to thank Mark Shackman and Hamish Willee for reviewing the original article. Jo Stichbury is a technical writer and communications professional. Author of a number of books about Symbian C++, she is also an experienced copywriter, editor and web content creator. You can find a portfolio of Jo’s work on jostichbury.com and find out more about her services at uk.linkedin. com/in/jostichbury.

Further information
• •

Symbian panics explained Symbian C++ miscellany (panics and assertions)

You're Reading a Free Preview

Download
scribd
/*********** DO NOT ALTER ANYTHING BELOW THIS LINE ! ************/ var s_code=s.t();if(s_code)document.write(s_code)//-->