MIT Information Systems

Macintosh Development

[Home] [About Us] [People] [Information Systems]
[Kerberos for Macintosh] [Applications] [Miscellaneous Documentation]


Assertions In DebuggingLib

What is an assertion?

An assertion, or a signal, is code that verifies whether a condition that should always be true is satisfied, and interrupts the flow of the program if the condition fails. Assertions are a very useful debugging tool, because they reveal situations in which the program is not in a state it should be, thus exposing bugs that might be hard to find otherwise.

Assertions ae different from exceptions (which are, confusingly, also called signals in some texts; then there are UNIX signals too...), because assertions are a debugging aid, and exceptions are an error-handling mechanism.

A typical assertion would look like this: suppose that DeleteObject is a function that takes a non-nil pointer to an Object, and deletes it. Obviously, nil is not a valid input for that function, so we could write:

void
DeleteObject (
	Object*	inObject)
{
	Assert_ (inObject != nil);
	/* delete the object */
 
}

Of course, were DeleteObject designed to take nil pointers, this assertion would be unnecessary.

Common behavior of assertions is to display an error message, usually including some amount of debugging information. Since assertions can be computationally expensive, it is common to remove some assertions from final versions of programs, but leave them in debugging versions.

For additional general information about assertions (and other debugging techniques) you should refer to a good book, such as Steve Maguire's "Writing Solid Code". This section is by no means sufficient to describe all the good and useful things about assertions.


When should I use an assertion?

Whenever you can and it makes sense to do so.

If your code makes an assumption about its parameters, assert it. This is especially important for library developers, since they have no control over the parameters passed into the library, but they frequently need to make an assumption about the parameters passed, or place a restriction on the valid values of parameters. The above example is a good example of this technique.

If you write a piece of code that performs some algorithm which has some preconditions (conditions that are necessary for the algorithm to be valid) and postconditions (conditions that are guaranteed to be true if the algorithm is correct), assert them. For example, let MatrixAdd be a function that takes two matrices of the same size and returns their sum:

Matrix
MatrixAdd (
	Matrix	inMatrix1,
	Matrix	inMatrix2)
{
	Assert_ (	(NumberOfRows (inMatrix1) == NumberOfRows (inMatrix2)) &&
			(NumberOfColumns (inMatrix1) == NumberOfColumns (inMatrix2));
	/* add the matrices */
}

Do not use assertions for error handling. If a combination of input parameters to your function is valid, but generates an error, don't assert; return an error code instead.


How should I prepare my project for assertions?

First, make sure you've prepared your targets and prefix files as described in How do I use DebuggingLib?

Then, add the following to your project prefix file:

#include <Debug.h>

Debug.h and some other header files require specific #include order to work; in particular, ansi_prefix.mac.h (if you are using it) must come before Debug.h in your prefix file.

If you are using PowerPlant, you can add Debug.h to your precompiler headers, but it has to come after UDebugging.h.

In your source files, you'll need:

#include <Debug.Assert.h>

You also need to add DebuggingLib.[68K|PPC][.debug] to your project.

If you are building a PowerPlant application, you should replace UDebugging.cp with DebuggingLib.[68K|PPC][.debug]. PowerPlant code will then automatically use the Debugging library (including getting the "Debug" button in assertion alerts), and you can use assertions in your code as described in the rest of this document.

Note: only versions 1.9.2 and 1.9.3 of PowerPlant are supported right now (these are versions of PowerPlant in CW Pro 4).


How should I use assertions?

Glad you asked. For your basic assertion needs, use one of the following:

SignalIf_ (aCondition);
SignalIfNot_ (aCondition);
Assert_ (aCondition);
 

The meaning is self-explanatory: SignalIf_ will alert you to an unexpected condition if aCondition is true; SignalIfNot_ and Assert_ will alert you if aCondition is false.

The default behavior of assertions is to display an alert, both in debug and in final version. The alert is slightly different between two versions: in the debug version it has a button which lets you debug the application by dropping into Metrowerks debugger or MacsBug, and the alert in the final version has more user-friendly text.


Advanced Topics

There are several ways you can do more interesting things with assertions. In most cases, you won't have to use them, so this section is entirely optional.

Asserting with customized strings

In some cases you might want to assert with a specific message, which may not correspond to some useful boolean condition. You can do that using:

SignalPStr_ (inPascalString);
SignalCStr_ (inCString);

These two alert you immediately, but the message displayed in the alert/debugger message contains the specified string, instead of a quoted boolean condition.

The argument to SignalPStr_ can be either a string literal (text between quotes) or a pascal string variable. The argument to SignalCStr_ must be string literal, and cannot be a char* (this is because the macro uses preprocessor concatenation to convert the string to a Pascal string). For example:

if (lateInTheEvening) {
	Sleep ();
} else 
	SignalPStr_ ("\pIt's too early to sleep. Tool some more.");

Changing runtime assertion behavior

If you need to specify the behavior an assertion will have, you can use the following two:

SetSignalAction_ (inSignalAction);
RestoreSignalAction_ ();

Because of the way these are defined, you must put the call to SetSignalAction_ within, or immediatelly before or after, the declarations section of the block for which you want to have an alternate behavior:

{
	SetSignalAction_ (signalAction_SourceDebugger);
	long	i;
	long j = 0;
	
	/* Do something */
	
	RestoreSignalAction_ ();
}
 

You absolutely must (and I mean it) restore the changes you made, except when you are writing an application and the call to SetSignalAction_ is at the beginning of your main().

The valid values of inSignalAction, and their meanings, are:

Value

Behavior in debug version

Behavior in final version

signalAction_Nothing

Nothing happens. The assertion/signal is ignored. You shouldn't use this setting unless you are absolutely sure you want to ignore signals in a section of code.

Nothing happens. The assertion/signal is ignored. You shouldn't use this setting unless you are absolutely sure you want to ignore signals in a section of code.

signalAction_Alert

An alert is displayed with the message consisting of the text of the signal condition, and file and line number where the assertion ocurred. The alert will have Quit, Ignore, and Debug buttons. Quit button kills the application, Ignore button ignores the assert, and Debug button drops into Metrowerks debugger. If you don't have Metrowerks debugger running, you may crash if you click Debug.

Same as signalAction_NiceAlert

signalAction_SourceDebugger

Control is transferred to Metrowerks debugger. If you don't have Metrowerks debugger running, you may crash.

Nothing happens. The assertion is ignored.

signalAction_LowLevelDebugger

Control is transferred to MacsBug. If you don't have MacsBug installed and loaded, you may crash.

Nothing happens. The assertion is ignored.

signalAction_NiceAlert

Similar to signalAction_Alert, except that the message is more user-friendly, and there is no Debug button.

Similar to signalAction_Alert, except that the message is more user-friendly, and there is no Debug button.

When should I change the assertion behavior?

Almost never. There are two important situations when you might want to:

Altering the behavior while debugging

If you find yourself debugging some code, and you want to change the assertion behavior from that point on, you must change the value of the global variable gSignalAction. (Of course, all standard warnings about changing variables in your program apply.) The debugging library uses this value to determine the runtime behavior of assertions, and you can use the debugger to set the value of this variable to any of the values given in the table above. This is, in fact, what the SetSignalAction_ macro does, but you shouldn't rely on that in your code.

Altering the behavior programatically

There may be some cases when you cannot allow arbitrary behavior inside a code segment (for example, you should not allow alerts from an interrupt handler or CFM initialization function). This is not an issue in most cases, but for the rare cases when this must be done, you can use the macros described above.

Changing assertion compiler options

Since assertions can be computationally expensive, it is frequently desirable to strip some of them from final versions. On the other hand, it's better for an application to display a nice alert than to lock up or otherwise irritate users. Hence, assertions have three levels of compile-time support:

SignalMode_All_

All assertions/signals are compiled into the code. Runtime behavior corresponds to the debug column of the previous table.

SignalMode_Inexpensive_

Only assertions/signals not marked as computationally expensive are compiled into code. Runtime behavior corresponds to the final column of the previous table.

SignalMode_None_

No assertions/signals are compiled into code.

You can change this option by using one of:

#define SignalMode_ SignalMode_All_
#define SignalMode_ SignalMode_Inexpensive_
#define SignalMode_ SignalMode_None_

From that point on, your asserts will be compiled according to the option you specify.

Shielding computationally expensive assertions

Given the above, you are probably wondering how you should mark your expensive assertions, so that they can be removed from your final version. You can use

#if SignalMode_ == SignalMode_All_
#endif /* SignalMode_ */

to achieve that. For example:

#if SignalMode_ == SignalMode_All_
	Assert_ (CalculateSomethingHorriblyComplicated () == noErr);
#endif /* SignalMode_ */

This assertion will be removed from your final version, and so your final version won't carry the computational overhead.

Asserting complex expressions

In some cases, you might need to assert based on the result of some complex expression; in fact, the expression might be so complex that you can't write it in a single conditional. In this case, you should bracket that part of your code inside

#if SignalMode_ == SignalMode_All_
#endif /* SignalMode_ */

or

#if SignalMode_ != SignalMode_None_
#endif /* SignalMode_ */

depending on whether you want the statements that comprise the assertion to be in the final version or not.

I also recommend you put those blocks of code inside {} braces; that way you will reduce the chance of accidentally introducing dependencies between your debug and final code (see more about this in Caveats). For example:

#if SignalMode_ == SignalMode_All_
/* The sum must be non-zero */
{
	int i;
	int theSumOfEyes = 0;
	for (i = 0; i < 1000; i++) {
		theSumOfEyes += DoSomething (i);
	}
	Assert_ (theSumOfEyes != 0);
}
#endif /* SignalMode_ */
 

Caveats

Assertions can be tricky. Assertions are based on preprocessor macros. This implementation of assertions also uses a global variable. Therefore, there are some things you can do to shoot yourself in the foot. Assuming you are not very keen to do that (maybe because someday you'll miss and shoot everybody else, at which point we'll hunt you down and make you understand the layout of the macathena locker), here are some ways to avoid when using assertions:

Don't assert on expressions with side effects

This is a bad way to assert:

Assert_ ((myHandle = NewHandle (inSize)) == nil);

When this code is compiled with assertions turned off, myHandle will never be allocated. Boom.

Don't add functionality to your code with assertions

The assertions in your code should almost always rely on normal flow of your code, by checking the values your code normally produces, or paths your code normally takes. In rare cases, you might want to use an alternate algorithm to double-check the result your code normally produces. In no case should your assertions (and debugging code in general) perform anything that your final code doesn't. (This is really just a generalization of the first caveat.) Consequences of failing to follow this rule can range from minor annoyance to horrible mess, if, for example, you don't test your final version well enough and you later get reports that the functionality is not as you expected it to be. Take only pictures, leave only fotprints.

Always restore the assertion behavior

If you change the assertion behavior, always restore it to its original value. Be especially careful with continue, break, return, and goto statements, as they break the linear flow of your program, and you could accidentally skip over a RestoreSignalAction_ with them. Failure to comply is hazard for your (and other people's) health.


Questions or comments? Send mail to macdev@mit.edu
Last updated on $Date: 2003/11/19 20:49:47 $
Last modified by $Author: smcguire $