Version 1.2 of ais/ai-00329.txt
!standard B.01.00 (01) 04-03-0129 AI95-00329/03
!standard 11.04.01 (04)
!standard 11.04.01 (05)
!class amendment 03-03-04
!status work item 03-03-04
!status received 03-03-04
!priority Medium
!difficulty Medium
!subject pragma No_Return -- procedures that never return
!summary
We propose to standardize a pragma "No_Return" defined in GNAT and probably
other Ada95 compilers that specifies that a procedure never
returns "normally" but rather always propagates an exception,
or results in an abort of the calling task.
Additional option: define two new procedures
in Ada.Exceptions, Always_Raise_Exception and
Always_Reraise_Occurrence, to which this new
pragma would apply.
(Or perhaps Ada.Exceptions.No_Return.Raise_Exception
and Ada.Exceptions.No_Return.Reraise_Occurrence.)
!problem
As Ada compiler and other tools do more static analysis of
source code to identify possible errors, it is important
to know whether it is possible for a call on a given procedure
to return or not. For example, it is relatively common
to have a "Fatal_Error" procedure in an application, which
displays an error message or logs the error somewhow, and
then raises an exception or somehow terminates the program.
It would be useful for the compiler, and the human reader,
to know that any call on such a procedure will never return.
This will allow the compiler to provide better warnings about
functions that end without returning, about possibly uninitialized
variables, about dead code, etc.
!proposal
pragma No_Return(local_name {, local_name});
This pragma is a program unit pragma, and is modeled after the
Inline pragma, and has similar syntax, name resolution, and legality
rules, except that it only applies to procedures (not functions).
If a procedure has such a pragma, then return statements
are illegal in the procedure, and Program_Error is raised
if the procedure completes normally by reaching the end
of its body (just as with a function).
Additional option: Add Always_Raise_Exception
and Always_Reraise_Occurrence to Ada.Exceptions
(or perhaps a child?) to which pragma No_Return would
apply. These would raise Constraint_Error if given
Null_Id or Null_Occurrence, respectively, like several
of the other routines in Ada.Exceptions.
Alternatively, create a "Ada.Exceptions.No_Return" child
and add no-return versions of Raise_Exception and
Reraise_Occurrence there.
!wording
Add new clause:
6.5.1 Pragma No_Return
A pragma No_Return indicates that a procedure never returns normally;
it always propagates an exception.
Syntax
The form of a pragma No_Return, which is a program unit pragma (see 10.1.5)
is as follows:
pragma No_Return(local_name {, local_name});
Legality Rules
The pragma shall apply to one or more procedures or generic procedures.
If a pragma No_Return applies to a procedure or a generic procedure, there
shall be no return_statements within the procedure.
Dynamic Semantics
If a pragma No_Return appies to a procedure, then the exception Program_Error
is raised at the point of the call of the procedure if the procedure body
completes normally rather than propagating an exception.
Add after 11.4.1(4):
pragma No_Return(Raise_Exception);
Modify 11.4.1(14) as follows:
[Raise_Exception and] Reraise_Occurrence [have] {has} no effect in the case
of [Null_Id or] Null_Occurrence. {Raise_Exception,} Exception_Message, ...
!example
procedure Fatal_Error(Msg : String);
pragma No_Return(Fatal_Error);
function A_OK(X : Integer) return Integer is
begin
if X = 0 then
Fatal_Error("Divide by zero");
else
return 2**30 / X;
end if;
end A_OK;
By placing the pragma No_Return on a procedure, calling
it is effectively equivalent to raising an exception from
the point of the compiler, so the above should not produce
a warning that the function A_OK might end without executing a return
statement. Similarly:
procedure Also_A_OK(Phase_Of_The_Moon : Float) is
X, Y : Integer;
begin
if Phase_Of_The_Moon = 1.0 then
Fatal_Error("Full moon");
else
X := 42;
end if;
Y := X;
Put_Line("Y = " & Integer'Image(Y));
end Also_A_OK;
The above should not generate a warning that X might
be uninitialized when assigned to Y.
!discussion
Although the above examples are fairly mundane, there are
more serious cases where knowing that a procedure will
never return is critical for verifying or simply for
understanding an algorithm.
GNAT already supports this pragma, and we have recently added it
to the AdaMagic front end to allow the compiler to do a
better job of producing warnings in the presence of
procedures like "Fatal_Error."
It is not uncommon to have a coding discipline that any
compiler warning should be considered an error, and code
must be changed to eliminate any warnings. However, without
a pragma like "No_Return," this can become difficult and
can obscure the true functioning of the code. Furthermore,
it generally requires the insertion of dead code, which is
anathema to most program certification requirements.
We considered various rules regarding the meaning of No_Return.
One possibility was that it simply meant that the compiler
could presume the procedure wouldn't return from the point
of view of compile-time warnings, but it would still be OK
if the procedure did in fact return. However, that seemed
like a recipe for implementation complexity, since the compiler
optimizer is almost being encouraged to assume something that
might be false, which is a good way to create havoc.
Hence, we have simply adopted the rule for functions, namely
that Program_Error is raised if a No_Return procedure
"successfully" reaches the end of its body. Coupled with making
return statements illegal, we ensure that the caller is
guaranteed that the procedure does not return normally.
(Of course, it would be possible for an "imported" procedure
to violate this, but this clearly should be erroneous.)
We also considered rules that would allow return statements,
but require that they could not be "reached." However,
we rejected basing legality rules on control flow analysis, since that
would seem to be a dangerous precedent to set at this point,
since there are almost certainly current Ada compilers that
at least in some modes don't do any flow analysis.
Originally we considered allowing No_Return on functions,
but that seemed of little benefit, and outlawing return
statements in functions would clearly conflict with the
existing (albeit a bit weird) rule that currently requires
at least one.
Note that the existing GNAT pragma No_Return makes it illegal
to "reach" the end of the procedure body, but as explained above,
we felt it was unwise to use reachability in a legality rule.
The proposed rule is upward compatible with existing users of
No_Return, since they currently must abide by the stricter
rule of unreachable end of body. Also, presumably compilers
that currently warn about possibly reachable end-of-function
can generalize the warning to apply to No_Return procedures
as well, so the safety provided by the GNAT rule can be preserved
presuming GNAT users take warnings seriously. (We guess
they do in that GNAT has a "treat warnings as errors" flag.)
Optional addition:
With this pragma, it would be nice to be able to apply it
to Ada.Exceptions.Raise_Exception and Reraise_Occurrence.
However, these existing procedures have no effect if given
Null_Id or Null_Occurrence, respectively.
It might be desirable to change this, but upward
compatibility argues for defining new procedures.
Something like Always_Raise_Exception or Always_Reraise_Occurrence
(or perhaps Raise_Exception_No_Return) could be added.
These would be guaranteed to raise some exception, even
if given Null_Id/Occurrence. To be consistent with
other subprograms in this package, it would seem best
to raise Constraint_Error (though Program_Error would
be another reasonable choice).
If placed in a child, the No_Return aspect could
be built into the name of the child, thereby allowing
a "use" clause to give direct visibility on no-return
versions with the same simple name as before. (Or of
course local renamings could be used to the same effect.)
Hence an alternative is:
package Ada.Exceptions.No_Return is
procedure Raise_Exception( ... );
procedure Reraise_Occurrence( ... );
end Ada.Exceptions.No_Return;
We have made this pragma apply to Ada.Exceptions.Raise_Exception. This
requires a (slightly) incompatible change, as Raise_Exception is defined
in Ada 95 to have no effect if the argument is Null_Id. But this is an
odd definition - when you call Raise_Exception, you really want it to raise
an exception. Thus, we've changed it to raise Constraint_Error when passed
Null_Id.
!ACATS test
!appendix
From: Tucker Taft
Sent: Tuesday, March 4, 2003 11:13 AM
Here is an amendment AI on pragma No_Return,
a pragma supported by GNAT and which we have recently
added to AdaMagic to eliminate various spurious
warnings.
****************************************************************
From: Robert I. Eachus
Sent: Tuesday, March 4, 2003 2:46 PM
> Here is an amendment AI on pragma No_Return, a pragma supported by
> GNAT and which we have recently
> added to AdaMagic to eliminate various spurious warnings.
Should this be referred to as the MTA amendment? ;-)
For non-Americans, the song that made the Kingston Trio famous over 40
years ago was The MTA. ("But did he ever return? No he never
returned--poor old Charlie--and his fate is still unlearned. He may
ride forever 'neath the streets of Boston. He's the man who never
returned.)
****************************************************************
From: Robert A Duff
Sent: Tuesday, March 4, 2003 3:43 PM
Thanks for that amusing bit of nostalgia.
I'm told the point was that the subway required paying on the way out.
Charlie didn't have the money (or the token?), so he had to ride
forever, 'neath the streets of Boston. His wife handed him sandwiches
on the way past the station, but for some reason never gave him the
money to get out.
The current subway in Boston requires paying on the way in.
- Bob
P.S. For symmetry, we should have a pragma No_Deposit.
****************************************************************
From: Robert I. Eachus
Sent: Tuesday, March 4, 2003 6:41 PM
Touch‚! I guess that would be for functions that didn't put their
return values on the stack. ;-)
Also off topic, yes, the "one more nickel" was to be paid on exit if you
transferred. But the normal fare would be paid on entry. Haven't
gotten off at Park Street Under to switch to a trolley in decades.
****************************************************************
From: Robert Dewar
Sent: Tuesday, March 4, 2003 2:39 PM
> Additional option: Add Always_Raise_Exception
> and Always_Reraise_Occurrence to Ada.Exceptions
> (or perhaps a child?) to which pragma No_Return would
> apply. These would raise Constraint_Error if given
> Null_Id or Null_Occurrence, respectively, like several
> of the other routines in Ada.Exceptions.
>
> Alternatively, create a "Ada.Exceptions.No_Return" child
> and add no-return versions of Raise_Exception and
> Reraise_Occurrence there.
As a side note, what GNAT does is to specialize the warning messages involved,
and never complain about a dubious return if the name of the function is
Raise_Exception. A bit of a kludge .... :-)
****************************************************************
From: Robert A. Duff
Sent: Tuesday, March 4, 2003 3:30 PM
> As a side note, what GNAT does is to specialize the warning messages
> involved, and never complain about a dubious return if the name of the
> function is Raise_Exception. A bit of a kludge .... :-)
Makes perfect sense. A kludge to get around what is arguably a language
design flaw. Indeed, Robert has so-argued, and I think today Tucker
would agree that Raise_Exception ought to raise an exception (always),
and if we need one that ignores null it should have been called
Maybe_Raise_Exception, or some such thing. Too late.
But it's interesting that you chose not to put a pragma No_Return on
Raise_Exception (and suppress whatever error message that causes in the
body).
Pragma No_Return really means no return, provably at compile time, so a
compiler can legitimately remove dead code after such a call -- such as
the implicit "raise Program_Error;" at the end of a function body.
****************************************************************
From: Robert Dewar
Sent: Tuesday, March 4, 2003 4:07 PM
> But it's interesting that you chose not to put a pragma No_Return on
> Raise_Exception (and suppress whatever error message that causes in the
> body).
Because that would cause invalid code to be generated! No_Return does much
more than suppress error messages for us, it cuts branches in the flow graph,
and the optimizer can and does take advantage of this.
****************************************************************
From: Robert A. Duff
Sent: Tuesday, March 4, 2003 4:19 PM
Right, that was exactly my point. Our optimizer does the same
(i.e. believe that pragma No_Return is the truth).
****************************************************************
From: Robert I. Eachus
Sent: Tuesday, March 4, 2003 2:39 PM
Count me definitely in favor.
> With this pragma, it would be nice to be able to apply it to
> Ada.Exceptions.Raise_Exception and Reraise_Occurrence However, these
> existing procedures have no effect if given Null_Id or
> Null_Occurrence, respectively.
> It might be desirable to change this, but upward compatibility argues
> for defining new procedures.
I think in this case that the officially upwardly incompatible change is
by far the best option. I can't imagine any program outside compiler
test suites that depends on this behavion.
****************************************************************
From: Tucker Taft
Sent: Tuesday, March 4, 2003 3:42 PM
Robert I. Eachus wrote:
> Count me definitely in favor.
Glad to hear it.
> I think in this case that the officially upwardly incompatible change is
> by far the best option. I can't imagine any program outside compiler
> test suites that depends on this behavion.
I guess I agree for Raise_Exception. For Reraise_Occurrence,
it seems that one might have an exception handler that if
executed, uses Save_Occurrence to save away the occurrence,
and then the enclosing subprogram ends with a Reraise_Occurrence,
which returns normally if no exception occurrence has been saved away.
Raise_Exception is the important one, in any case, from
a "typical user" perspective.
****************************************************************
From: Robert Dewar
Sent: Tuesday, March 4, 2003 3:52 PM
> I think in this case that the officially upwardly incompatible change is
> by far the best option. I can't imagine any program outside compiler
> test suites that depends on this behavion.
I tend to agree, and if you make the semantics be that Program_Error is raised
if presented with a null occurrence, it is unlikely that this upward
incompatibility will cause any trouble.
P.S. to me this was a small but really nasty design error in the current spec.
****************************************************************
From: Randy Brukardt
Sent: Tuesday, March 4, 2003 4:15 PM
I certainly prefer that to adding "almost the same" routines. Moreover, we
had a similar problem with this package (AI-241), and we opted for the
incompatibility rather than adding new routines. So it seems reasonable to
adopt the same rule here.
****************************************************************
From: Tucker Taft
Sent: Wednesday, March 5, 2003 8:06 AM
I would rather leave Reraise_Occurrence as is, since
I don't think that affects users, and an upward
compatibility issue is at least somewhat easier to
construct for that one.
But Raise_Exception with a Null_Id seems very
obscure. I would (mildly) recommend Constraint_Error
rather than Program_Error, to be consistent with
the other routines.
By just changing one routine, there is a nice
"conservation of Constraint_Error" because we have
proposed that Exception_Id(Null_Occurrence) will now *not*
raise Constraint_Error, while Raise_Exception(Null_Id)
*will* raise Constraint_Error. ;-)
****************************************************************
From: Robert Dewar
Sent: Wednesday, March 5, 2003 2:24 PM
I agree with all this (including the smiley)
****************************************************************
From: Jean-Pierre Rosen
Sent: Wednesday, March 5, 2003 1:43 PM
> I think in this case that the officially upwardly incompatible change is
> by far the best option. I can't imagine any program outside compiler
> test suites that depends on this behavion.
>
Lack of imagination? ;-)
I certainly have programs that depend on it.
The idea is that at a certain point, you detect an exception but you don't want
it to be propagated yet. You catch it and save it. Then later, you do a
Reraise_Occurrence. If there was no exception, nothing happens.
Note you can easily add an "if" to check for Null_ID, but *not* for
Null_Occurrence.
(for this reason and others, it would be nice to add an "Is_Null_Occurrence" to
Ada.exceptions).
****************************************************************
From: Robert Dewar
Sent: Wednesday, March 5, 2003 2:40 PM
Note that JPR's example uses Reraise_Occurrence, confirming Tuck's view that we
should not change this one, only the raise call.
****************************************************************
From: Tucker Taft
Sent: Wednesday, March 5, 2003 2:48 PM
This reply seems to back up the idea of leaving
Reraise_Occurrence as it is now, and only changing
Raise_Exception to be a "No_Return" procedure.
****************************************************************
From: Robert I. Eachus
Sent: Wednesday, March 5, 2003 3:57 PM
This is looking like a consensus. I find the examples somewhat
contrived, but I find it even harder to find an example where a compiler
can generate more efficient code for a Reraise_Occurrence if it is
guarenteed to raise some exception. On the other hand, calling
Raise_Exception to NOT raise an exception is baroque.
****************************************************************
From: Randy Brukardt
Sent: Wednesday, March 5, 2003 4:10 PM
Jean-Pierre said:
> Note you can easily add an "if" to check for Null_ID, but *not* for
> Null_Occurrence.
> (for this reason and others, it would be nice to add an
> "Is_Null_Occurrence" to Ada.exceptions).
That was the point of AI-241 (approved long ago). Exception_Identity does
not raise Constraint_Error, it returns Null_Id. So:
if Ada.Exceptions.Exception_Identity (My_Occurrence) = Null_Id then
works to test for Null_Occurrence. And *that* is the existing
incompatibility that we were discussing, which is why introducing another
one in the same package isn't bad.
Of course, if there is any real use of the construct in question, its best
not to change it. So I tend to agree with the consensus here.
****************************************************************
From: Robert Dewar
Sent: Wednesday, March 5, 2003 4:36 PM
I must say that I find any code that uses Reraise_Occurrence relying on the
null case behavior to be very bad programming style. I would far rather
see an explicit test at the source level
if x /= Null_Occurrence then
Reraise_Occurrence (X);
end if;
I must say that from a design point of view it is very odd to have inconsistent
behavior for the two routines. I vote for "fixing" them both, even if it does
make a minor upwards incompatibility.
In the GNAT version of a-except.ads we have:
procedure Raise_Exception (E : Exception_Id; Message : String := "");
-- Note: it would be really nice to give a pragma No_Return for this
-- procedure, but it would be wrong, since Raise_Exception does return
-- if given the null exception. However we do special case the name in
-- the test in the compiler for issuing a warning for a missing return
-- after this call. Program_Error seems reasonable enough in such a case.
-- See also the routine Raise_Exception_Always in the private part.
function Exception_Message (X : Exception_Occurrence) return String;
procedure Reraise_Occurrence (X : Exception_Occurrence);
-- Note: it would be really nice to give a pragma No_Return for this
-- procedure, but it would be wrong, since Reraise_Occurrence does return
-- if the argument is the null exception occurrence. See also procedure
-- Reraise_Occurrence_Always in the private part of this package.
:-)
****************************************************************
From: Jean-Pierre Rosen
Sent: Thursday, March 6, 2003 2:41 AM
Agreed, BUT an occurrence is a limited type....
I agree with the consensus, provided:
1) either Reraise_Occurrence is left as is
2) or we provide "=" for occurrences.
****************************************************************
From: Robert Dewar
Sent: Thursday, March 6, 2003 7:45 AM
Well in fact didn't we agree to provide a test for null occurrence, so the code I
wrote conceptually as:
> > if x /= Null_Occurrence then
> > Reraise_Occurrence (X);
> > end if;
actually comes out as
if not Is_Null_Occurrence (X) then
Reraise_Occurrence (X);
end if;
****************************************************************
From: Tucker Taft
Sent: Thursday, March 6, 2003 8:10 AM
An AI already approved allows Exception_Id(occurrence)
to be used to check for null occurrences:
if Exceptions.Exception_Id(X) /= Exceptions.Null_Id then
Exceptions.Reraise_Occurrence(X);
end if;
****************************************************************
From: Robert A Duff
Sent: Tuesday, March 18, 2003 6:09 PM
Question about pragma No_Return: What does it mean when a call is
dispatching? What does GNAT do in this case?
Similar question for a call through an access-to-subp. Can pragma
No_Return be applied to an access-to-subp type?
****************************************************************
From: Gary Dismukes
Sent: Tuesday, March 18, 2003 6:55 PM
> Question about pragma No_Return: What does it mean when a call is
> dispatching? What does GNAT do in this case?
GNAT just applies the pragma on a procedure-by-procedure basis.
It doesn't treat dispatching procedures specially. So a dispatching
call must be assumed to return.
> Similar question for a call through an access-to-subp. Can pragma
> No_Return be applied to an access-to-subp type?
GNAT only allows it for procedures.
I guess it would be reasonable to support it for access-to-subp
types, and require that the pragma apply to the prefix of 'Access
if it applies to the expected access type. So calls via an access
value of such a type could be assumed to never return. Not sure
how useful that would be though.
****************************************************************
Questions? Ask the ACAA Technical Agent