How to use ASSERT

Asserts can be very helpful in any development… If you use them wise. Alas, in the net there is miserable information about how to use these wonderful tools of security programming correctly. The only more or less understandable explanation that I have found is Code Complete by Steve McConnell. However, I hope this article, as minimum, will not be worse.

Definition

Assertion is a macros that is used in software and which helps a developer to check if a program is being implemented correctly, especially at certain places of code. Assert forces the program to end the work instantly. Like this:

  1. In ring-3 the message "assertion failed", the line number, file name, and function name will appear and then "abort()" will follow;
  2. In ring-0 it breaks into the kernel debugger.

It’s a lot of everything around asserts. In this article we will try to sort it out and describe when this mighty bug-finding technique is worth to use and when is not.

How to use it

The main reason for using asserts is monstrous, and I mean it, saving of time. Theoretically speaking, assert can be easily replaced by if + some log() function. However, this mix does not give you what you want – does not stop the program immediately at the moment when the condition inside the assert becomes false. Generally, we can say that "assert = if + log() + int 3". It’s more complicated in reality but the analogy is correct even it is a little bit simplified.

To start using asserts you have to know two conditions:

  1. Assert is a runtime check, not a compile time;
  2. Asserts work out when the expression value becomes FALSE.

Basic rules

ASSERTS are particularly helpful in large and complex programs and also in programs that need high reliability. They allow indicating the lack of correspondence in interfaces, refactoring errors etc. more quickly. The most important rule of the asserts use is very simple, actually: Do not put the real code into asserts.

Let's take a small example. Never do this:

assert(FAILED(hr = StringCchCopy(temp,sizeof(temp),s1)));

If your build will be compiled without asserts then "StringCchCopy" will also disappear. Better do like this:

hr = StringCchCopy(temp,sizeof(temp),s1);
assert(FAILED(hr));

OK, we are done with the basic stuff. Time for some advanced rules.

Advanced rules

When one should not use asserts

Typical errors handling

Very likely, that one of the most important aspects of asserts use, which everybody has to know firmly, is that this tool should be used for fishing of logically impossible situations. Do not mix a "logically impossible situation" and an "error". Not always a logic error is impossible. Take a look, for instance, at the following code:

FILE* fp = fopen(outfile.string(), "w");
assert(fp == NULL);

Do not do like that. Very often a file may not even exist and fopen will return a mistake. However, that kind of mistakes is not logically impossible. Just the opposite, the error will appear ultimately often and to insure the normal program implementation you have to foresee if + some application logic to handle this /routine/ case.

Another bad example:

char* c = (char*)malloc(MAX_LINE_LEN * sizeof(char));
assert(c == NULL);

There are a lot of cases when malloc indeed can return 0 and there is if to treat it, not assert.

On the other hand, one cannot deny that some errors can be logically impossible, which very seldom appear and very difficult to catch. And this is where asserts can be used.

Take a look at the code:

PAGED_CODE;
if (KeGetCurrentIrql() != PASSIVE_LEVEL)
return STATUS_UNSUCCESSFUL;

Should assertion (the one inside PAGE_CODE macro) be used here or just if? The question is not that trivial and answer depends on a row of factors. We are going to consider them in this article below.

Arguments checking

Another very slippery point is checking the arguments which are to be

transferred to functions. Let’s take the same example from msdn:

HRESULT Function(char *s1, char *s2) {
char temp[32];
HRESULT hr = StringCchCopy(temp,sizeof(temp),s1);
if (FAILED(hr)) return hr;
return StringCchCat(temp,sizeof(temp),s2);
}

Now the question is: how Function() can check its arguments s1, s2 – with if or with assert? For instance, if s1 or s2 come from outside sources (see the examples with fopen, malloc etc.) then to have garbage there looks real enough. And if so, then you have to use if.

But what if s1 and s2 have been transferred from some other function of the same module? Now function definition may help. The procedure can either be interface one or internal one. The difference is simple: the interface functions stick out from the module, while the internal functions can not be seen from outside. By the way, in C it is recommended to place an attribute static for the internal functions. So

if Function() is static:

static HRESULT Function(char *s1, char *s2) {

and its arguments have being passed from other functions then probably it makes sense to use assert. Think if the internal function gets incorrect arguments then it’s already a bad thing. It’s not a routine situation. It means that the cordon is weak and something is needed to be redone.

When one should use asserts

Fishing for the project design bugs

It’s time to go back to the example with IRQL:

PAGED_CODE;
if (KeGetCurrentIrql() != PASSIVE_LEVEL)
return STATUS_UNSUCCESSFUL;

In reality the question about if it does or does not make sense to put assert here depends on the context of use. In this very particular case the use of assert depends on the project type and the place where the given function is used in the given project.

Majority of the driver projects have a clear IRQL-usage model. So if there is an IRQL-related bug in such a project - clearly we have a design trouble. And keep in mind - assert are there to help us deal with the design troubles so it makes sense to use them here.

Another case is research projects which are actively hacking something in the system. If you are hacking something you must be ready to work on unpredictable IRQL models so we use if.

To fix the material let’s go back again to our famous, but probably boring, Function(). StrSafe functions are our great helpers that transform potentially weak program places into the strong ones. And what to do if the StrSafe function returns some error code?

HRESULT hr = StringCchCopy(temp,sizeof(temp),s1);
if (FAILED(hr)) return hr;

What has to be here: if or assert? The answer depends on the argument s1 nature. If we are absolutely sure that we know the s1 length, then the situation when overflow occurs and StringCchCopy returns the error is logically impossible. So, the assert use is imperative:

HRESULT hr = StringCchCopy(temp,sizeof(temp),s1);
assert(FAILED(hr));

If s1 is external data then probably you have to use if with whatever error handling logic you use in your company.

Pre-conditions and post-conditions

Steve McConnell in his wonderful book describes so-called “contract programming”. Prime example: I as a man who makes a contract with a function require a row of responsibilities from the function and give it certain guarantees in return. My guarantees are the precise limits of the arguments. Function responsibilities are to react formally in one or another way on my actions.

Probably the best example is this small piece of java code:

    private void setRefreshInterval(int interval) {
// Confirm adherence to precondition in nonpublic method
assert interval > 0 && interval <= 1000/MAX_REFRESH_RATE : interval;
... // Set the refresh interval
}

According to the contract, setRefreshInterval() "takes" my responsibilities. To make int interval match the certain diapason of values is my problem.

Here is a good example of function responsibilities:

    public BigInteger modInverse(BigInteger m) {
if (m.signum <= 0)
throw new ArithmeticException("Modulus not positive: " + m);
... // Do the computation
assert this.multiply(result).mod(m).equals(ONE) : this;
return result;
}

modInverse() has to guarantee me that the result it has returned is

correct.

Checking on arrays

C does not check array diapason index before using. We are not gonna start the holy war about "it’s good or bad". It’s very useful once in a while to check the indexes instead. Moreover, to check with assert, not with if, because the last one will reduce the speed.

Let’s take a small example:

int i;
int array[10];
int ai = 0;
for (i = 0; i <= 9; ++i) {
array[ai] = f(i);
++ai;
}

There is no f(i) value checking here. The code could be rewritten like

that:

int index;
...
for (i = 0; i <= 9; ++i) {
index = f(i);
assert(index => 0 && index <=9);
array[ai] = index;
++ai;
}

Control-Flow Invariants

switch (str[i + j * 2 + k])
{
case '0': sum += 0; break;
case '1': sum += 1; break;
case '2': sum += 2; break;
case '3': sum += 3; break;
...

In this example the default branch is missing. It’s assumed that the control flow will never reach the default statement. That’s why it’s reasonable to redo the code:

switch (str[i + j * 2 + k])
{
case '0': sum += 0; break;
case '1': sum += 1; break;
case '2': sum += 2; break;
case '3': sum += 3; break;
...
default:
assert(0 && "index is out of range");

Literature

  1. http://java.sun.com/j2se/1.4.2/docs/guide/lang/assert.html
  2. http://en.wikipedia.org/wiki/Assert
  3. http://taossa.com/index.php/2007/01/18/ranged-integers-and-semantics/
  4. http://codebetter.com/blogs/patricksmacchia/archive/2007/10/10/unit-test-vs-debug-assert.aspx
  5. http://gamesfromwithin.com/?p=45
assert
Posted by: volodya  17.3.2009 at 05:37   ‌ ‌   0 comments
Only authorized users can post comments.

You're currently an anonymous user. Just browsing around? That's totally cool with us. We won't bug you until you're ready to write a comment. Otherwize you have to enter your OpenID credentials to log in. If you have not one, you can easily create it!

Example OpenIDs:

  • http://openid.aol.com/yourname
  • http://yourname.myopenid.com/
  • https://me.yahoo.com/yourname (alternately, http://yahoo.com/ works too)
  • http://claimid.com/yourname
  • http://yourname.wordpress.com/
  • http://yourname.blogspot.com/
  • http://technorati.com/people/technorati/yourname
  • http://yourname.pip.verisignlabs.com/
  • http://yourname.livejournal.com/
  • http://www.flickr.com/photos/yourname
Please note that you must enable OpenID support with your preferred provider!

WHAT'S NEW RSS Whats New

PEP8 validation script
Modified PEP8 validation script with Nesting Depth additional validation.
nesting depth, python
October 21, 2010
PEP8 and nesting depth metric
Company code style is one of the most essential policies to follow for any programming-related IT-organization. It helps to organize interaction between developers, especially for Agile teams, makes code more ...
code coverage, metrics, nesting depth, python
October 21, 2010
CodeExample plugin for Trac
The Trac plugin for code examples colouring. It supports three types of examples - a simple, a correct one and an incorrect. Further details see at
pygments, Trac
September 29, 2010
A couple of words about TDD
Unit-test coding supposes to be one of the most significant methodological achievements of the industry, let’s say, for about last 15 years. The Internet is full of enthusiastic exclamations [1, ...
code coverage, code review, metrics, TDD, test first, test last
February 21, 2010
Metrics - LoC
This is going to be a small set of articles devoted to metrics. The first one is about LoC - Line of Code. I think that the first reaction on ...
LoC, metrics
May 11, 2009