May 92 - Exception Handling in MacApp 3
Exception Handling in MacApp 3
Lonnie Millett
One of the reasons MacApp is successful with developers is that applications built with
MacApp can recover and continue operating under conditions that cause other
applications to crash. What's the key to creating applications that never crash? It's
called exception handling, and understanding how it works is the key to making your
own applications fail-safe.
An exception can be defined as any condition which your code cannot be expected to cope
with. One common type of exception occurs when trying to allocate memory when there
is no memory available. Exception handling provides a way of transferring control
and information to an unspecified caller that has expressed willingness to handle
exceptions of a given type."[1] An exception handler that has expressed willingness to
handle a memory exception may be responsible for cleaning up allocations that were
made previous to the allocation that failed.
This first article will focus on the mechanics of exception handling in MacApp. By
mechanics I am referring to the different usage styles, differences between C++ and
Pascal, how it works internally, why and when variables need to be declared
VOLATILE, etc. With the mechanics established, a second article will focus on the "how
to" of exception handling: when it is needed, when it is not needed, where it goes, and
how to introduce it into existing code.
Styles of Exception Handling in MacApp
In MacApp there are two styles of exception handling. The original style of exception
handling available since MacApp 1.0, I will refer to as Pascal Style exception handling.
It was designed to be used with Object-Pascal and relies on local or global procedures
to handle an exception. With MacApp 3.0 a new style of exception handling has been
introduced which I will refer to as C++ Style exception handling. Rather than relying
on local procedures, it handles an exception within the else clause of an if statement.
Neither style of exception handling is exclusive to the language it was designed for.
Instances of Pascal style exception handling can be found in the C++ version of
MacApp, and C++ style exception handling can be used from Pascal.
Pascal Style Exception Handling
Pascal style exception handling relies on the ability of Pascal to define local
procedures as handlers as a way to have access to local variables, procedure
parameters, or instance variables. A typical exception handler in Pascal would look
like:
PROCEDURE TPicture.IPicture();
VAR fi: FailInfo;
PROCEDURE HandleFailure(error: OSErr; message:LONGINT);
BEGIN SELF.Free; END;
BEGIN
CatchFailures(fi, HandleFailure);
fDataHandle := GetPicture(fRsrcID);
FailResError;
Success(fi);
END;
We start by declaring an instance of a FailInfo record to contain the information needed
by the exception handling services. Among the information stored in the FailInfo
record are the current state of all the registers and the address of the procedure that
will handle the exception. Later on we will look at the definition of FailInfo in detail.
Next we define the local procedure that will be called should an exception occur.
Because this is an IMethod, we will want to free ourselves if we can not completely
initialize ourselves. In this case the body of the local procedure simply calls
SELF.Free. With these two pieces defined we are ready to execute the code that could
fail.
In Pascal you bracket any code that could fail with a calls to CatchFailures() and
Success(). CatchFailures() requires two parameters: the FailInfo record to store the
information necessary to handle the exception and the name of the local procedure to
call should an exception occur. CatchFailures() is responsible for storing in the
FailInfo record the information needed to pass control to an exception handler. It also
installs or links the exception handling information to a list of exception handlers. In
the above example, if the call to GetPicture should fail with a resource error, our
local procedure HandleFailure() will be called, allowing us to clean up after
ourselves. If we successfully retrieved the picture, then we finish our bracketing of
the code that could fail with a call to Success(), passing it the same FailInfo record
that we passed to CatchFailures(). Success() is responsible for removing the
exception handling information from the list of installed exception handlers.
The Exception Handling Chain
MacApp maintains a chain of exception handlers in a linked list, with the current, or
last installed, exception handler pointed to by the MacApp global variable gTopHandler.
This means that there may be more than one active exception handler installed at any
point in time, each scoped to perform certain tasks should an exception occur. This is
important. It allows you to define exception handlers that can handle an exception at
the local level and not worry about how the exception should be handled at an outer
level.
So how does it work? After control is passed to the current exception handler, we
usually want the exception to be passed to the other exception handlers in the chain so
we can continue to handle the exception in the appropriate manner. With Pascal style
exception handling, i.e. when the exception handler is installed with CatchFailures(),
this happens automatically. When the global routine Failure() is called, gTopHandler
is made to point to the next exception handler in the chain:
pascal void Failure(short error, long message)
FailInfoPtr pf = gTopHandler;
if (pf)
// pop the stack first, because calling the handler
// is likely to result in a call to another call to
// Failure
gTopHandler = pf->nextInfo;
pf->error = error;
pf->message = message;
DoFailure(pf); // Go execute the failure handler
The call to DoFailure() is responsible for passing control to the currently installed
exception handler. For Pascal style exception handling, as in our example,
DoFailure() would call the local routine HandleFailure(). Following the call to
HandleFailure(), DoFailure() calls Failure() once again with the same error and
message:
MoveA.L (A0)+,A0 ; get address of failure handler
Jsr (A0) ; call failure handler
; (HandleFailure in our example)
MoveM.L (SP)+,D0/D1 ; get error & message back
Move.W D0,-(SP)
Move.L D1,-(SP) ; parameters to Failure
Jsr FAILURE ; Pass the exception to next
; handler
; should Not return
gTopHandler already points to the next exception handler, so another call to Failure()
will pass the exception to the next exception handler in the chain.
If you build debug versions of your MacApp applications you have probably seen
something similar to this at one time or another:
Failure caught by: TAPPLICATION.OPENOLD
Failure caught by: CArrayIterator::IArrayIterator()
Failure caught by: TODOCCOMMAND.DOIT
Failure caught by: TCOMMANDHANDLER.PERFORMCOMMAND
Failure caught by: TAPPLICATION.POLLEVENT
When an exception occurs, part of the debugging information sent to your favorite
source code debugger is a description of the exception stack or chain. Each entry in the
list represents a currently installed exception handler. In the example above,
TApplication::OpenOld was the last exception handler installed.
The last exception handler in the chain for a running application is usually installed in
TApplication::PollEvent. When this exception handler is reached, MacApp assumes that
all efforts to handle the exception have already occurred. The only task left is to
display the appropriate error alert. After the alert has been displayed, the exception
is fully handled, so MacApp continues by processing the event.
What if we don't want to pass the exception to any other installed exception handlers?
This might be the case if we can completely handle an exception locally. Since Pascal
style exception handling automatically propagates the exception, we have to break the
propagation with a Pascal GOTO:
PROCEDURE TPicture.IPicture();
LABEL 1;
VAR fi: FailInfo;
PROCEDURE HandleFailure(
error: OSErr; message:LONGINT);
BEGIN
SELF.Free;
GOTO 1;
END;
BEGIN
CatchFailures(fi, HandleFailure);
fDataHandle := GetPicture(fRsrcID);
FailResError;
Success(fi);
1:
END;
As we saw earlier, DoFailure() will call our exception handler, HandleFailure() in
this example, if some resource error should occur. Rather than returning to
DoFailure() and calling Failure() to propagate the exception, we instead jump to a
label we have defined and continue executing from that point without ever returning.
C++ Style Exception Handling
With MacApp 3.0 a new style of exception handling was introduced. Unlike Pascal style
exception handling, C++ exception handling is encapsulated in an if statement rather
than a local procedure:
pascal void TPicture::IPicture()
FailInfo fi;
if (fi.Try())
this->SetPictureRsrcID(itsRsrcID, kDontRedraw);
fi.Success();
}
else // Recover
{
this->Free();
fi.ReSignal();
}
}
We start as we do in Pascal by declaring an instance of FailInfo. In C++ the FailInfo
record of Pascal has been redefined as a C++ class. This class is not derived from
TObject and is allocated locally on the stack. We are now ready to bracket the code that
could potentially fail. The Try() method of FailInfo has the same responsibilities as
CatchFailures() in Pascal. Try() stores information in the fields of the FailInfo
instance to pass control back to the If statement should an exception occur. It also links
itself into the chain of exception handlers. Finally, Try() returns a boolean TRUE,
indicating that it is ready to catch an exception and the If clause of the statement is
then executed.
In the above example if the call to SetPictureRsrcID() fails we will automatically
jump to the portion of the If statement that tests the return value of Try(). However,
this time the value returned is FALSE, so we jump to the ELSE clause of the statement
and begin executing the exception handling code. If we were successful in retrieving
the picture, we call the Success() method of the FailInfo instance.
Unlike Pascal, C++ style exception handling does not automatically propagate the
exception to the next exception handler in the chain. When an exception is bracketed
with a Try() instead of CatchFailures(), a JMP rather than a JSR is used to arrive at
the exception handler, so we never return to MacApp's exception handling code where
it can continue by calling Failure() automatically. If we wish to propagate the
exception, we must do so explicitly. This is done at the end of the exception handling
code by calling the ReSignal() method of the FailInfo instance. What if we don't want to
propagate the exception? C++ style exception handling makes this easy. If we don't
include the call to ReSignal(), we continue execution with the first statement
following the IF statement.
The Need for VOLATILE
We learned before that when CatchFailures() or Try() is called the current state of
all registers is saved. If an exception occurs, the registers are restored to their saved
state. But what happens if we modified a variable in the bracketed code that was cached
by the compiler in a register? Unfortunately we lose the modified value. This becomes
a serious problem if we must then reference the variable in the exception handling
part of the code. If, for example, the variable was a newly allocated handle that should
be freed on exception, we would lose our reference and would not be able to dispose of
it. What is needed is a way to ensure that a variable or parameter is not cached in a
register.
The concept of a value being volatile was introduced to do just that. Volatile is a hint to
the compiler that the variable in question could change and should not be cached in a
register. Volatile is a keyword in both ANSI C and C++. However, Apple's C compiler
does not have an implementation of volatile that is sufficient for C++. Therefore,
CFront ignores the volatile keyword and does not pass it on to the C compiler. To solve
the problem, MacApp 3.0 defines a macro for C++ users called VOLATILE (all upper
case). The macro is quite simple:
#define VOLATILE(a) ((void) &a)
By taking the address of a variable or parameter we prevent the compiler from
caching it in a register. While this approach currently works for CFront and C, a
smarter compiler might optimize the statement away and then go ahead and cache the