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.
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.
procedure constraint_demo is x :integer range 1..20; y :integer; begin put("enter a number "); get(y); x:=y; put("thank you"); end constraint_demo;
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.
procedure constraint_demo2 is
x :array (1..5) of integer:=(1,2,3,4,5);
y :integer :=6;
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
procedure numeric_demo is x :integer; y :integer; begin x:=integer'last; y:=x+x; -- causes a numeric error end numeric_demo;
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.
procedure program_demo is
function y(x :integer) return integer is
if x < 10 then
elsif x < 20 then
end y; -- if we get here, no return has been
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.
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.
declare x:integer range 1..20; begin put("please enter a number "); get(x); put("thank you"); exception when constraint_error => put("that number should be between 1 and 20"); when others => put("some other error occurred"); end;
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:
loop declare ... begin ... get(x); exit; exception when constraint_error => put("that number ... end; end loop;
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.
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.
procedure exception_demo is --------------------------------- procedure level_2 is -- no excpetion handler here begin raise constraint_error; end level_2; --------------------------------- procedure level_1 is begin level_2; exception when constraint_error => put("exception caught in level_1"); end level_1; begin level_1; exception when constraint_error => put("exception caught in exception_demo"); end exception_demo;
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.
.... exception when constraint_error => package_disabled:=true; raise; -- re raise the current exception, -- allow other procedures to have a go -- at processing the exception. end;
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.
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
my_very_own_exception :exception; another_exception :exception;
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.
Handling user exceptions is identical to that of the predefined exceptions except for the problem of scoping. Consider the following example.
with text_io; use text_io; procedure demo is procedure problem_in_scope is cant_be_seen :exception; begin raise cant__be_seen; end problem_in_scope; begin problem_in_scope; exception when cant_be_seen => put("just handled an_exception"); end demo;
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.
with text_io; use text_io; procedure demo is procedure problem_in_scope is cant_be_seen :exception; begin raise cant_be_seen; end problem_in_scope; begin problem_in_scope; exception when others => put("just handled some exception"); end demo;
Another problem arises when one procedure's exception hides another exception
due to scoping rules.
with text_io; use text_io;
procedure demo is
procedure p1 is
procedure p2 is
fred :exception; -- a local exception
when fred =>
put("wow, a fred exception");
when fred =>
put("just handled a fred exception");
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
------------------------------------ procedure p2 is fred :exception; begin p1; exception when fred => -- the local exception put("wow, an_exception"); when demo.fred => -- the more 'global' exception put("handeled demo.fred exception"); end p2;
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
pragma suppress (access_check);
pragma suppress (discriminant_check);
pragma suppress (index_check);
pragma suppress (length_check);
pragma suppress (range_check); pragma suppress (division_check); pragma suppress (overflow_check);
The exception PROGRAM_ERROR has only one suppressable check
pragma suppress (elaboration_check);
The exception STORAGE_ERROR has only one suppressable check
pragma suppress (storage_check);
We can suppress the checking of exceptions on individual objects.
pragma suppress (index_check, on => table);
It can also relate to a single type.
type employee_id is new integer; pragma suppress (range_check, employee_id);
The use of a pragma would be as follows. In this case the scope of the pragma is till the end of the block.
subtype small_integer is integer range 1..10;
This code would cause no constraint error to be generated.