11 Exceptions


Overview

Exceptions are described in the Ada Language Reference Manual as errors or other exceptional conditions that arise during normal program execution. Exception handling is the process of catching these errors at run time and executing appropriate code to resolve the error, either by correcting the cause of the problem or by taking some remedial action.


Predefined exceptions

There are five exceptions predefined in the language. They are described below.

The exception CONSTRAINT_ERROR is raised whenever an attempt is made to violate a range constraint.

If the user enters a number outside the range 1..20, then x's range constraint will be violated, and a constraint_exception will occur. The exception is said to have been 'raised'. Because we have not included any code to handle this exception, the program will abort, and the Ada run time environment will report the error back to the user. The line 'put("thank you");' will not be executed either - once an exception occurs the remainder of the currently executing block is abandoned.

This error also occurs when an array index constraint is violated.

In this example the constraint exception will be raised when we try to access a non existant index in the array.

The exception NUMERIC_ERROR is raised when a numeric operation cannot deliver a correct result (e.g. for arithmetic overflow, division by zero, inability to deliver the required accuracy for a float operation). NUMERIC_ERROR has been redefined in Ada95 to be the same as CONSTRAINT_ERROR

The exception PROGRAM_ERROR is raised whenever the end of a function is reached (this means that no return statement was encountered). As well it can be raised when an elaboration check fails.

The exception STORAGE_ERROR is raised whenever space is exhausted, whether during a call to create a dynamic object or when calling a subprocedure (and stack space is exhausted).

The exception TASKING_ERROR is raised when exceptions arise during intertask communication; an example is task that attempts to rendezvous with a task that has aborted.


Handling exceptions

So far the code seen has not processed the exceptions. In these cases the programs are aborted by the run time code. To make exceptions useful we have to be able to write code that is run whenever an exception occurs.

The exception handler is placed at the end of a block statement, the body of a subprogram, package, task unit or generic unit.

To handle a constraint error consider the following code.

If the user enters a number between 1 and 20 then no error occurs and the message "thank you " appears. Otherwise the message "that number ..." appears and the block terminates.

If we want the user to continue to enter in numbers until there is no constraint error then we can write the following:

This highlights the point the instruction executed after an exception is that following the block in which the exception was handled.

Exceptions can be raised by the programmer simply by using the raise statement.

The exception raised is indistinguishable from a genuine numeric_error.


Exception propagation

If an exception is not handled in the subprocedure in which it was raised, the exception is propagated to the subprocedure that called it. A handler for the exception is searced for in the calling subprocedure. If no handler is found there then the exception is propagated again. This continues until either an exception handler is found, or the highest level of the current task is reached, in which case the task is aborted. If there is only one task running (typical for student projects) then the Ada runtime environment handles the exception and the program is aborted.

If this program was run the only output would be "exception caught in level_1". The exception is handled here and does not propagate any further. If we want to we can place a raise statement in the exception handler - this has the effect of propagating the exception up to the calling subprogram. In this way an exception can be viewed by each subprocedure in the call hierachy, with each performing whatever action it deems necessary.

The raise statement is very useful when used in the others section of an exception handler. In this case the appropriate exception is still raised and propagated.


User defined exceptions

Ada gives the user the ability to define their own exceptions. These are placed in the declarative part of the code. They can be placed whereever a normal declaration is placed (e.g. even in package specifications). The format for the declaration of an exception is

The use of exceptions is the subject of much debate about whether it is a lazy way of programming, without thinking too much about the problem and likely error conditions, or whether it is a valid form of control structure that can be used to some effect.


Problems with scope

Handling user exceptions is identical to that of the predefined exceptions except for the problem of scoping. Consider the following example.

This example is illegal. The problem is that the scope of an_exception is limited to procedure exception_raiser. It's name is not defined outside of this procedure and thus it cannot be explicity handled in procedure demo.

The solution is to use the others clause in the outer procedure's exception handler.

Another problem arises when one procedure's exception hides another exception due to scoping rules.

The output of this procedure is "just handled a fred exception". The exception handled in p2 is simply a local exception. This is similar to the handling of scope with normal variables.


Procedure p2 could be rewritten as


Suppression of exception checks

Exceptions are generated because extra code that is inserted inline detects some erroneous condition, or through some hardware checking mechanisms such as software interrupts or traps.

Accordingly it is possible to suppress the insertion of these checks into the code. The method that Ada uses is the pragma SUPPRESS. However it should be noted that through use of program analysis by the compiler, a good number of checks can automatically be removed at compile time.

The pragma can be placed in the code where the suppression is required. The suppression extends to the end of the current block (using normal scope rules).

It has a large range of options to enable suppression of various checks on either a type basis, an object basis or a functional basis. The versatility of the pragma is dependent upon the implementation. As well various implementations are free to implement (or ignore) any pragma suppress feature.

The exception CONSTRAINT_ERROR can be raised by failing several suppressable checks

The exception PROGRAM_ERROR has only one suppressable check

The exception STORAGE_ERROR has only one suppressable check


Applying suppression of checks

We can suppress the checking of exceptions on individual objects.

It can also relate to a single type.

The use of a pragma would be as follows. In this case the scope of the pragma is till the end of the block.

This code would cause no constraint error to be generated.


to the index...