0% found this document useful (0 votes)
170 views

Using Assertions To Detect Bugs

The document discusses using assertions in C++ code to detect bugs. It describes different assertion macros like __ASSERT_DEBUG and __ASSERT_ALWAYS and recommends always using a panic statement with assertions. It also provides an example of defining a custom panic function for assertions in a module.

Uploaded by

Jo Stichbury
Copyright
© Attribution Non-Commercial (BY-NC)
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
170 views

Using Assertions To Detect Bugs

The document discusses using assertions in C++ code to detect bugs. It describes different assertion macros like __ASSERT_DEBUG and __ASSERT_ALWAYS and recommends always using a panic statement with assertions. It also provides an example of defining a custom panic function for assertions in a module.

Uploaded by

Jo Stichbury
Copyright
© Attribution Non-Commercial (BY-NC)
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 3

Using Assertions to Detect Bugs

by Jo Stichbury

__ASSERT_DEBUG
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));
...
}
A good way to detect bugs in C++ is to use assertion
statements. An assertion checks that the state of objects, Of course, this is somewhat awkward, especially if you
function parameters or return values, is as expected. expect to use a number of assertions to validate your
Typically, an assertion evaluates a statement and, if it is code, so it’s sensible to define a panic utility function for
false, halts execution of the code. your module:

The definition of two assertion macros are found in enum TExampleEnginePanic


e32def.h. The __ASSERT_ALWAYS macro performs an {
assertion check in both debug and release builds, while ECorrupt, // =0,
the __ASSERT_DEBUG macro checks code in debug ENotInitialized, // =1,
builds only, and has no effect in release builds. EInvalidTestValue, // =2
};
// For debug and release builds
static void ExamplePanic(TExampleEnginePanic
#define __ASSERT_ALWAYS(c,p) (void)((c)||(p,0))
aCategory)
...
{
// For debug builds only
_LIT(KExamplePanic, “EXAMPLE-ENGINE”);
#if defined(_DEBUG)
User::Panic(KExamplePanic, aCategory);
#define __ASSERT_DEBUG(c,p) (void)((c)||(p,0))
}
#else
#define __ASSERT_DEBUG(c,p)
#endif The assertion in TestValue() is now written as follows:

void CExampleClass::TestValue(TInt aValue)


Parameter c is a conditional expression that returns a
{
true or false value. Parameter p is a function, called if c __ASSERT_DEBUG((aValue>=0),
is false, which should halt the flow of execution. In effect, ExamplePanic(EInvalidTestValue);
the code behaves as follows: ...
}
if (!c)
p The advantage of using an identifiable panic descriptor
and enumerated values for different assertion conditions
You’ll notice that the assertion macros do not panic by is traceability, for yourself and callers of your code. This
default, but allow you to specify the code to run should is particularly useful for others using your libraries, since
the assertion fail. My advice is always to use a panic in they may not have access to your code in its entirety,
an assertion statement (or write a custom function that but merely to the header files. If your panic string is clear
first logs the specific failure to file and then panics). This and unique, they should be able to locate the appropriate
is because you should immediately terminate the running class and use the panic category enumeration to find the
code and flag up the failure, rather than return an error, associated failure, which you will have named and docu-
leave or do nothing. Assertions help you detect invalid mented clearly to explain why the assertion failed.
states or bad program logic so you can fix your code as
early as possible. It makes sense to stop the code at There may be cases where there’s nothing more a client
the point of error, thus forcing you to fix the problem (or programmer can do other than report the bug to you, the
remove the assertion statement if your assumption is author of the code; alternatively, the problem could be
invalid). If the assertion simply returns an error on failure, down to their misuse of your API, which they’ll be able to
not only does it alter the program flow, but it also makes it correct.
harder to track down the bug.
ASSERT() the fact that the latter are compiled into release code, be-
If you don’t want or need an extensive set of enumerated cause, while you may initially decide the assertion applies
panic values, and know that external callers will never in release builds, this may change during the develop-
need to trace a panic, you may consider using a more ment or maintenance process. You could be storing up a
lightweight assertion macro. A good example of where future bug for the sake of avoiding an extra line of code.
this may be appropriate is when you want to test the
internal state of an object, which could not possibly be __ASSERT_ALWAYS
modified by an external caller, and thus should always be It goes without saying that your code should be tested
valid unless you have a bug in your own code. In these thoroughly. A user, or another software developer using
cases, you may consider using the ASSERT macro, de- your code, expects you to have debugged your code
fined in e32def.h as follows: before release; they don’t want to do it for you. The use of
assertion statements in debug builds can help you detect
#define ASSERT(x) __ASSERT_DEBUG(x, programming errors inside your code and fix them before
User::Invariant()) delivery.
In debug builds only, if condition x is false, So why would you need to use assertions in production
User::Invariant() is called, which itself panics with code? Why does Symbian C++ have an __ASSERT_AL-
category USER and reason 0. The macro can be used as WAYS macro to execute checks in both debug and release
follows: builds? For one thing, won’t the impact on the execution
speed and code size of adding extra checking to your
ASSERT(iPointer>0);
code be prohibitive? Doesn’t the outcome of a failed as-
sertion make for a poor user experience when it results
Personally, I like this macro because it doesn’t need you
in an application’s untimely end? Should you ever use
to provide a panic category or descriptor. Some Sym-
__ASSERT_ALWAYS?
bian developers consider it to be infuriating, because it
allows you to scatter assertions throughout your code
First of all, consider what __ASSERT_ALWAYS is check-
with no easy way to diagnose what went wrong. While
ing. You’ve used debug assertions to verify your internal
it’s OK to do so if you’re sure you’re going to be the only
code logic, so the only reason for using release build
developer that sees them fire, and immediately fix the
assertions is to check the validity of incoming parameter
code accordingly, if they do make it ‘downstream’, they
data. While invalid input is a bug from the perspective of
make problems harder to trace. I think it’s better to use
your code, it may be caused by an exceptional condition
them than nothing at all (which may be the case if you
in the calling code, which that code can handle gracefully
have to go back and add supporting code to use __AS-
if you return it an error or leave instead of firing an asser-
SERT_DEBUG()) but I include the following caution here
tion and terminating the running code.
for completeness:
Let’s consider a simple example. Your code offers a
When adding an assertion, consider whether anyone else
method to open and write to a file; the caller passes in the
will ever see it. If so, make it traceable.
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
As an alternative to using ASSERT to test the internal
appropriate to notify the caller through a returned error
state of your object, you may wish to consider using the
code or leave value than to assert in a release build. The
__TEST_INVARIANT macro.
calling code can then deal with the error.

Avoid ‘side effects’ TInt CTestClass::WriteToFile(const TDesC&


Don’t put code with side effects into assertion statements aFilename, const TDesC8& aData)
that execute in debug builds only. By this, I mean code {
that is evaluated before a condition can be verified. For TInt r = KErrNone;
example: if (KNullDesC8==aData)
{// No data to write - invalid!
__ASSERT_DEBUG(FunctionReturningTrue(), r = KErrArgument;
Panic(EUnexpectedReturnValue)); }
__ASSERT_DEBUG(++index<=KMaxValue, else
Panic(EInvalidIndex)); {
RFile file;
__ASSERT_DEBUG(iFs,
The reason for this is clear; the code may well behave Panic(EUninitializedValue));
as you expect in debug mode, but in release builds the r = file.Open(*iFs, aFilename, EFileWrite);
assertion statements are removed by the preprocessor, if (KErrNone==r)
and with them potentially vital steps in your programming {// Only executes if file can be opened
logic. Rather than use the abbreviated cases above, you ...
should perform the evaluations first and then pass the }
returned values into the assertion. }
return (r);
}
In fact, you should follow this rule for both __ASSERT_
DEBUG and __ASSERT_ALWAYS statements, despite
The code illustrates this approach, by returning an This article was first published in
error if the caller passes in an invalid parameter. 2008, on developer.symbian.
(You’ll notice that I’ve included an __ASSERT_DE- com, as part of the Code Clinic
BUG statement to verify internal state in my code series.
and catch any defects - such as attempting to use
the file server handle before it has been initialized). The author would like to thank
Mark Shackman and Hamish
If the caller is passing data from a source that it Willee for reviewing the original
does not directly control, say from the user’s key- article.
strokes or from a communications link, there is al-
ways a possibility for invalid input. In the example Jo Stichbury is a technical writer and communications
above, this could perhaps be handled by creating professional. Author of a number of books about Sym-
the file or using a default set of data. The calling bian C++, she is also an experienced copywriter, editor
code is far better placed to make a ‘decision’ about and web content creator.
how to handle each scenario, so it’s better for the
code above to indicate the invalid incoming data You can find a portfolio of Jo’s work on jostichbury.com
by returning an error or leaving than by asserting and find out more about her services at uk.linkedin.
and panicking when the input is invalid. com/in/jostichbury.

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 cor-
ruption, 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 RPoint-
erArray), 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 ar-
ray, 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.

Further information
• Symbian panics explained
• Symbian C++ miscellany (panics and asser-
tions)

You might also like