Version 1.5 of ai12s/ai12-0024-1.txt

Unformatted version of ai12s/ai12-0024-1.txt version 1.5
Other versions for file ai12s/ai12-0024-1.txt

!standard 4.6(30)          12-05-02 AI12-0024-1/01
!class Amendment 12-05-02
!status No Action (7-0-1) 15-10-16
!status hold 12-06-15
!status work item 12-05-02
!status received 12-02-12
!priority Medium
!difficulty Medium
!subject Compile-time detection of range and length errors
!summary
**TBD.
!problem
Ada tries to detect errors at compile-time. However, range and length errors of objects involving static ranges and values are runtime errors. Consider:
package Annoying is S : String(1..3) := "abcde"; -- (1) A : array (1..2, 1..3) of Integer := (1..3 => (1..2 => 0)); -- (2) subtype T1 is Integer range 1 .. 3; X1 : T1 := 5; -- (3) end Annoying;
All three of the marked locations unconditionally raise Constraint_Error; these are clear errors that should be detected at compile-time.
!proposal
[Editor's note: The following is not quite wording; I didn't check every use of every term, in particular the correct terminology for bounds in an aggregate. But it is pretty close; language wording should not be significantly more complicated than this.]
Unless the expression is either itself statically unevaluated, or part of a statement that is statically unevaluated, an expression containing any of the following is illegal:
* If the right operand of an integer divide operator is a static value that is zero; * A subtype conversion of a static value to a static subtype if the value does not satisfy the subtype; * A subtype conversion of an array aggregate or string literal with static bounds to a statically constrained array subtype, if the length of any dimension of the aggregate does not match the length of the corresponding dimension of the array subtype.
A statement is statically unevaluated if it is contained in (directly or indirectly) a sequence_of_statements of an if_statement, and either: * the condition that controls the sequence_of_statements is a static expression with the value False; or * the condition that applies to some previous sequence_of_statements is a static expression with the value True.
[Editor's note: We could try to define a similar rule for case statements. We could also try to define the statements directly following a goto, raise statement, or return statement in the same sequence_of_statements in this category; but those are probably errors in their own right so it probably doesn't pay to do so. We also need to handle zero-trip loops and aggregate expressions with no associated componeents.]
!wording
** TBD.
!discussion
It has been claimed that only a "small subset of errors" can be detected this way. It's certainly true that these errors are only a small subset of the possibilities for runtime errors in Ada, but these particular cases are very common in Ada code as most discrete subtypes are static and most array objects have static bounds. It makes sense to detect these problems even if many other problems have to be detected at runtime.
It also has been claimed that it is too hard to describe the rules in language terms. But that's not true; most of the terminology needed already exists (as should be obvious from the relatively simple description given above). Nor are the rules suggested above hard to implement; most (and probably all) compilers already implement similar warnings; changing those to errors is unlikely to be difficult.
Still, this problem is not as clear-cut as it appears on the surface. There would be a significant problem with false positives with "obvious" version of these rules (where any out-of-range static value is an error).
Consider:
Buffer_Size : constant Natural := 0; -- No buffer needed.
... if Buffer_Size > 0 then Average := Total / Buffer_Size; -- (1) else Average := Total; end if;
The basic rule is that a division by a static value that is zero is illegal. That means that the division at (1) would be illegal. But that code can never be executed when Buffer_Size = 0. Moreover, there is no sane workaround: the only way to get rid of such an error message would be to make Buffer_Size a variable (or introduce a function with the same effect). But we want to encourage making as many objects as possible constant, not discourage that.
Thus, we introduced the "statically unevaluated" exception to the rules; that means that there is no error at (1).
Note that this is not just a problem with the division rule (which could otherwise be dropped). For instance, the Editor has a tool containing the following code:
Command_Len : constant Natural := 8;
type Command_Type is Full_Name : String (1 .. Command_Len); Short_Name : String (1 .. 4); end record;
if Command_Len > 11 then Cmd(1) := (Full_Name => "View_Project" & (13 .. Command_Len => ' '), Short_Name => "#Prj"); --(2) else -- Only define the short name for the command. Cmd(1) := (Full_Name => "#Prj" & (5 .. Command_Len => ' '), Short_Name => "#Prj"); end if;
Without the statically unevaluated rules, the aggregate at (2) would be illegal. Again, the only way to fix that would be to make something in the aggregate non-static -- which would defeat the purpose of having written the code this way in the first place (which was allow processing of these definitions at compile-time).
The statically unevaluated rules mitigates the false positive problem, but does not eliminate it. Specifically, if "Command_Len > 11" is replaced by a function call that returns that result, the aggregate again becomes illegal.
These false positives mean that this proposal is incompatible. It's mostly incompatible for programs that use these sorts of errors to raise exceptions, a lousy technique that we can do without. But there also will be cases where some code which cannot be executed because of complex runtime expressions that raises an exception. Those cases would present more of a problem.
An alternative to making the program illegal would be to define a sort of "soft error" that can be ignored via a pragma. A program containing such an error is illegal unless the pragma applies to the site of the soft error. (One of the options for return statements in functions - AI12-0029-1 has a similar requirement, so one could use the mechanism in a variety of places; it wouldn't make sense to define it just for this problem.)
!ACATS test
** TBD.
!appendix

From: Dan Eilers
Sent: Sunday, February 12, 2012  11:04 PM

[Part of a message on another topic - Editor.]

I am also in favor of fixing the following language bug regarding staticness of
ranges, which is perhaps somewhat related.

package bad is
   s: string(1..3) := "abcde";     -- currently legal but should be illegal
   a: array (1..2, 1..3) of integer := (1..3 => (1..2 => 0));   -- ditto
end bad;

****************************************************************

From: Robert Dewar
Sent: Sunday, February 12, 2012  11:32 PM

[The relevant part of a reply to the previous message - Editor.]

I don't think that's a bug, and I don't think it should be "fixed".
A warning (which any decent compiler will give) is good enough here.

****************************************************************

From: Dan Eilers
Sent: Monday, February 13, 2012  12:44 AM

[The relevant part of a reply to the previous message - Editor.]

Can you explain why a language known for early detection of errors, and used in
safety-critical applications should allow stuffing 5 characters into a
3-character string, or swapping the dimensions of an aggregate? I've seen the
aggregate case happen.

Warnings are a necessary evil when there is a high likelihood of false
positives, or when detecting the condition is beyond the capabilities of some
compilers.  But that isn't the case here.

****************************************************************

From: Bob Duff
Sent: Monday, February 13, 2012  9:11 AM

[The relevant part of a reply to the previous message - Editor.]

This is really a separate issue, so you should have sent it as a separate email,
to ease Randy's job of filing the emails. (Maybe we could automate the filing
process!)

Anyway, there are many places where run-time errors can be caught at compile
time.  The general style of Ada is to call them run-time errors, and hope that
compilers warn when possible. If you propose to fix that in general, it's WAY
too much work. If you propose to fix the two examples you showed, then what's
the point -- it doesn't fix the general problem.  Without an exact
wording-change proposal, I can't tell which you are proposing, or maybe
something in between.

****************************************************************

From: Jean-Pierre Rosen
Sent: Monday, February 13, 2012  11:05 AM

> Anyway, there are many places where run-time errors can be caught at
> compile time.  The general style of Ada is to call them run-time
> errors, and hope that compilers warn when possible.

Let me add that I strongly support that. The general principle is that a rule is
either a compile time rule or a runtime rule. The language avoided (OK, tried to
avoid...) rules that are at runtime except sometimes at runtime (please, refrain
from pronouncing "accessibility rules" ;-) ). That makes it much more
orthogonal, at the cost of sometimes having a warning in place of an error.

****************************************************************

From: Dan Eilers
Sent: Monday, February 13, 2012  11:36 AM

> This is really a separate issue, so you should have sent it as a
> separate email, to ease Randy's job of filing the emails.
> (Maybe we could automate the filing process!)

OK, I changed the subject to range violations.

> If you propose to fix that in general, it's WAY too much work.
> If you propose to fix the two examples you showed, then what's the
> point -- it doesn't fix the general problem.  Without an exact
> wording-change proposal, I can't tell which you are proposing, or
> maybe something in between.

I am proposing that when the bounds of an array or array aggregate are defined
using static expressions or static positional notation, then any run-time
constraint checks currently required by the language should be performed at
compile time.

There are a few other cases where the compiler statically knows an exception
will be raised at runtime, without involving any value-following mechanisms,
such as elaboration checks for instantiating a generic before its body is seen.
I would like to see those fixed too, but consider that a separate issue.

Such a language change reduces the list of Ada vulnerabilities, which ISO is
tracking, improving Ada's suitability and marketability for high-reliability
systems.  It also makes array range violations consistent with range violations
for discrete types.  Compiler warnings are outside the scope of the ISO
standard, and so the ISO standard should not depend on all compilers generating
such warnings and all users always heeding such warnings.

****************************************************************

From: Dan Eilers
Sent: Monday, February 13, 2012  12:08 PM

> It also makes array range violations consistent with range violations
> for discrete types.

Actually, discrete types are sometimes checked at compile time, and sometimes
not, for reasons not clear to me.  For example:

procedure test is

   subtype T1 is integer range 1..3;
   x1: T1 := 5;         -- checked at runtime

begin
   case x1 is
      when 5 => null;   -- checked at compile time
      when others => null;
   end case;
end;

****************************************************************

From: Bob Duff
Sent: Monday, February 13, 2012  11:45 AM

> Such a language change reduces the list of Ada vulnerabilities, which
> ISO is tracking, improving Ada's suitability and marketability for
> high-reliability systems.  It also makes array range violations
> consistent with range violations for discrete types.  Compiler
> warnings are outside the scope of the ISO standard, and so the ISO
> standard should not depend on all compilers generating such warnings
> and all users always heeding such warnings.

I agree with you in principle.  I just think it's too much work (in RM wording,
and in compilers) to fix.

****************************************************************

From: Randy Brukardt
Sent: Monday, February 13, 2012  1:55 PM

...
> I am proposing that when the bounds of an array or array aggregate are
> defined using static expressions or static positional notation, then
> any run-time constraint checks currently required by the language
> should be performed at compile time.

The examples you've shown have *length* checks, not range checks. Not much
difference in practice, but sliding is allowed.

> There are a few other cases where the compiler statically knows an
> exception will be raised at runtime, without involving any
> value-following mechanisms, such as elaboration checks for
> instantiating a generic before its body is seen.
> I would like to see those fixed too, but consider that a separate
> issue.
>
> Such a language change reduces the list of Ada vulnerabilities, which
> ISO is tracking, improving Ada's suitability and marketability for
> high-reliability systems.
> It also makes array range violations consistent with range violations
> for discrete types.

Huh? Range violations for discrete types are not errors.

   subtype Sml is Integer range 1 .. 10;

   Eleven : Sml := 11; -- Raises C_E, no error here.

I was thinking about this issue (for unrelated reasons) in the shower this
morning. It would appear to be a good thing to detect these at compile-time, but
it turns out that we need to allow these in some such cases:

       Buffer_Size : constant Natural := 0; -- No buffer needed.

       ...
       if Buffer_Size > 0 then
           Average := Total / Buffer_Size; -- !!
       else
	     Average := Total;
       end if;

If we required compile time checks, the divide-by-zero here would be illegal.
But this code can never be executed when Buffer_Size = 0! You'd have to take the
"constant" off of Buffer_Size to make this go away, which is exactly the wrong
thing to encourage. And I can't think of any other rewrite that would work when
Buffer_Size = 0.

We could fix this by defining a notion of "statically unevaluated" for
statements, and saying that it is only an error if the statement is *not*
statically unevaluated. (That's what we do for static expressions.) But that's
pretty complicated.

Note that the rule you are suggesting would run afoul of a similar problem.
Here's some code similar to that which appears in some of RR's tools:

      Command_Len : constant Natural := 8;

      type Command_Type is
          Full_Name : String (1 .. Command_Len);
          Short_Name : String (1 .. 4);
      end record;

      if Command_Len > 11 then
          Cmd(1) := (Full_Name => "View_Project" & (13 .. Command_Len => ' '),
                     Short_Name => "#Prj");
      else -- Only define the short name for the command.
          Cmd(1) := (Full_Name => "#Prj" & (5 .. Command_Len => ' '),
                     Short_Name => "#Prj");
      end if;

With the change you are suggesting, the first aggregate is illegal -- even
though it would never be executed. And the only solution I can think of would be
to introduce a function into the aggregate somewhere so it isn't static -- which
defeats the purpose of this complex construction (which was to keep everything
compile-time).

Your other point about case statements is also instructive: in situations like
these, you have to avoid case statements. Usually, it's easy enough to use if
statements as an alternative. But if we adopted a blanket compile-time check
rule, there would be no alternative (other than wasting time and space in the
finished program by making many things dynamic).

So I think I'm with Bob: I can imagine trying to solve this problem in general,
but it's fairly complex to avoid throwing out the baby with the bathwater.
Definitely too late to do in Ada 2012. Might be worth considering for Ada 2020,
though.

****************************************************************

From: Robert Dewar
Sent: Monday, February 13, 2012  1:38 PM

> I agree with you in principle.  I just think it's too much work (in RM
> wording, and in compilers) to fix.

Well the set of cases that can be detected at compile time is not something that
can be characterized in the standard. So you end up with a small subset being
illegal, and relying on warnings for the rest ahyway. I just don't see enough
gain in identifying this small subset precisely. It sure sounds like a lot of
work for this case.

****************************************************************

From: Randy Brukardt
Sent: Tuesday, April 22, 2014  6:01 PM

The issue of code that always raises exceptions has come up repeatedly. Most Ada
programmers would like for compilers to reject such code rather than accepting
it and having a runtime error that isn't detected until testing (if ever).
[Aside: Bob will want to object to my use of the term "reject", which is Ada
83-speak, but sorry, I find it clearer for informal discussion such as this.
Presume the right words will be used in the official documents.]

However, Ada uses dead but compilable code as a form of conditional compilation.
As such, we cannot make code that never executes illegal, else we would have a
significant compatibility problem. Consider something like:

    Average : Natural := (if Num_Items = 0 then 0 else Count/Num_Items);

If Num_Items is statically 0, then Count/Num_Items will always raise
Constraint_Error -- but it also will never be executed in this expression. So we
certainly would not want to make this expression illegal if Num_Items is
statically 0.

I tried to formally define the idea of statically unevaluated for this purpose
in my preliminary write-up of AI12-0024-1. (Ada 2012 already has such an idea
for the purpose of static expressions, which are illegal if they raise an
exception.) The problem with this is that it gets very complicated, as we want
to eliminate as many false positives (that is, programs rejected because of a
range problem that will never be executed). That means defining rules similar to
the ones for static expressions (which already take up 6 lengthy bullets), along
with a number of cases that can't occur in static expressions, like zero-trip
loops, aggregate choices that have no associated elements (others and null
ranges), code directly following an exception raise or goto [although one might
decide those are errors anyway and not bother], and hardest of all, subprograms
that are never called. This looked like enough of a can of worms that the idea
has not gained much support.

AI12-0024-1 also doesn't go far enough in that it doesn't include other sorts of
checks like elaboration checks, dynamic accessibility failures, and detected
bounded errors (which almost always raise Program_Error).

Today I had the idea of taking a completely different approach to this problem.
Rather than trying to make a lengthy list of examples where we allow rejection,
perhaps we should simply give an implementation permission to reject code that
always raises an unhandled exception and let implementations do what they can in
this area.

So, without further ado, here is some wording expressing the idea (this would go
into 1.1.5 - "Classification of Errors", following paragraph 11, as it would
apply to all Ada programs without further mention).

                Implementation Permissions

If any possible execution of a partition would propagate an exception such that
the partition would be terminated by the exception, the implementation may treat
the compilation unit that raised the exception or the entire partition as
illegal.

AARM To Be Honest: "Any possible execution" here means any execution that
executes normally; it does not mean to take into account outside forces other
than those defined as external interactions. Examples of such forces include
power interruption, hardware faults, and cosmic ray disruptions. For instance,
if the main subprogram body contained "delay 10.0; raise Program_Error;", this
permission could be invoked even though an execution for which the power switch
was turned off 5 seconds after the program starts would not propagate an
exception.

AARM Implementation Notes: "Any possible execution" means that every execution
taking into account every possible external interaction of the program. For
instance, an exception raised because of some value read from a file would not
trigger this rule (unless some exception was raised for any possible value
read). In particular, this permission never applies to code that is never
executed. For instance, the following never propagates an exception, even if
Num_Items is statically known to be zero:
    Average : Natural := (if Num_Items = 0 then 0 else Count/Num_Items);
and thus this expression does not trigger this permission.

Note that this wording is written so that it can be applied both at compile-time
and at link-time. Compile-time application is limited to exceptions raised by
library-level package elaboration (most likely range checks, length checks, and
elaboration checks), as that is the only code that can be assumed to execute
(the only way that it wouldn't execute is if the unit is never included in a
partition or if some other unit' elaboration previously failed and propagated an
exception, both cases that can be safely ignored for this purpose). Link-time
application depends on the cleverness of the compiler; any subprogram that
unconditionally propagates an exception could trigger the rule if it is
unconditionally called.

---------------

The only downside I can see to this permission is that it would make programs
that use unhandled exception to terminate the program potentially illegal. Such
a program might propagate an exception like "Stop_Program" as Ada doesn't have a
Halt library subprogram. However, the permission can be avoided by simply
handling the exception at the end of the main subprogram (or, if visibility of
the exception is a problem, handling all exceptions there). This is better
anyway, as it makes it clear that the exception is intended and signals to the
reader that the program might be terminated this way.

Otherwise, such a permission might make some test programs illegal (although as
this is a permission, no implementation would have to use it, so a mode for
unhandled exception testing would be possible), but it's hard to imagine correct
programs that could be caught by this permission. Moreover, this is written such
that false positives are not possible (other than the Halt case mentioned above)
-- the implementation has to prove that all executions will fail before it can
invoke the permission. This is in contrast to  rules requiring detection other
than in some dead code cases (as in the AI12-0024-1 proposal) -- these will
always have some level of false positives -- and false positives == significant
incompatibility if one happens in your code (incompatibilities in other people's
code are always less significant :-).

A rule like this would allow GNAT to use some portion of their static
elaboration model in Standard-conforming mode, and would allow the example in
AI12-0024-1 to be declared illegal.

If there is a downside, it's that this rule couldn't be applied that often, as
it would be unlikely to be of use within a subprogram. So it might not be worth
the effort of using.

What do people here think? Would this catch enough low-hanging fruit (i.e.
silly errors) to make the implementation worthwhile? Or is there a better way to
deal with this? Or is this not worth doing at all?

****************************************************************

From: Bob Duff
Sent: Tuesday, April 22, 2014  7:49 PM

> What do people here think? Would this catch enough low-hanging fruit (i.e.
> silly errors) to make the implementation worthwhile? Or is there a
> better way to deal with this?

Yes, there's a better way to deal with this sort of thing.  My idea of requiring
a diagnostic message, but still allowing the program to run, is apt.

>... Or is this not worth doing at all?

Yes, in this case.

In any case, I don't think adding Implementation Permissions is of any benefit.
Implementations can already do that, and many do.

We language designers tend to fall into the trap of thinking that language
standards require something, and I think you're doing that here.  Standards
compliance is entirely optional! (In fact I suspect most Implementation
Permissions fall into this category, including ones I was once in favor of.)

There is nothing wrong with nonstandard modes, especially modes like "treat
warnings as illegalities", which do no harm to portability. Implementations
don't need "permission" for that.

I think an Impl Perm is nonresponsive to the supposed problem of
AI12-0024-1:

    From: Dan Eilers
    Sent: Monday, February 13, 2012  11:36 AM

    I am proposing that when the bounds of an array or array aggregate are defined
    using static expressions or static positional notation, then any run-time
    constraint checks currently required by the language should be performed at
    compile time.

The original example happened to be at library level, but Dan wants this to be a
legality error even when the code in question might not be executed.  And he
doesn't want *permission*, he wants all conforming compilers to do that.

One final point:  There is little value in detecting (at compile time) run-time
errors that will happen every time the program is run. Even the most minimal
amount of testing can catch such errors (just run the program once, on any
input).  Legality errors are useful when they detect errors that might not
happen during testing.

****************************************************************

From: Adam Beneschan
Sent: Wednesday, April 23, 2014  11:32 AM

I had missed this AI (AI12-0024) before, or else I wasn't paying close
attention.  But something occurred to me when I read it just now.

In the example in the AI:

    package Annoying is
       S  : String(1..3) := "abcde";     -- (1)
       A  : array (1..2, 1..3) of Integer := (1..3 => (1..2 => 0));   -- (2)
       subtype T1 is Integer range 1 .. 3;
       X1 : T1 := 5;                     -- (3)
    end Annoying;

In cases (1) and (2), the declarations involved would raise Constraint_Error
always, everywhere, no matter what the context of the declarations would be.  It
seems pretty clear that writing those declarations is an error.

However:

    package Comm_Protocol is
       Command_Field_Length : constant := 3;
    end Comm_Protocol;

    package Command_Set is
       Query_Command : constant String := "QUERY";
    end Command_Set;

    procedure Send_Simple_Command is
       S : String(1..Comm_Protocol.Command_Field_Length) :=
              Command_Set.Query_Command;
       ...

Now it's not clear that this is a programming error.  For all we know, the rest
of the program's logic could have arranged things so that Send_Simple_Command is
called *only* when Command_Field_Length is known to match Query_Command'Length,
and some other "send" procedure is called in other cases.

The language's definition of "static expression" doesn't distinguish these
cases, but I'm wondering if it would be useful to define a "literally static
expression" that is a static expression that doesn't involve any user-defined
names, and doesn't involve implementation-dependent attributes such as
Integer'Last.  Then it might be possible to treat cases differently depending on
whether they involve only "literally static expressions" or not; exceptions such
as those in cases (1) or (2) could cause a program to be rejected, but those
that involve named constants (like my second example) or other user-defined or
implementation-dependent entities wouldn't.  (Case (3) above implicitly involves
T1'Last, which is an attribute of a user-defined type, so I'd put it in the
"don't reject" category since it's conceivable that the subtype may be in
another package and the declaration of X1 could be in a subprogram that the
program would know not to execute if 5 isn't in the subtype range.)  Maybe the
definition of "literally static" could be accomplished by copying 4.8, or parts
of it, and making some simple tweaks.

****************************************************************

From: Tucker Taft
Sent: Wednesday, April 23, 2014  12:10 PM

I believe putting more into the Ada standard on this is unwise.  Speaking from
the experience of developing a sophisticated static analyzer for Ada (CodePeer),
there is a huge amount of "gray" area here, and trying to identify what is and
is not a bug is just too hard.  Even something as simple as complaining when a
statically known value is outside the statically-known base range of a type, has
created controversy and some difficulties for certain Ada coding styles.  Going
beyond that is just opening a huge can of worms.

Compilers can (and probably should) integrate sophisticated static analyzers to
help find bugs as early as possible, but I believe it is beyond the state of the
art, and in some cases actively harmful, for the language standard for a
language as complex as Ada to try to start legislating exactly what is and is
not a bug.  It would be fine when first designing a language to build in more
restrictive legality rules that depend on things like control and data flow
analysis, but it is too late to do that for Ada, I suspect. And based on the
experience of trying to define such rules for Java relating to the
initialization of local variables, writing such legality rules is a big effort!

****************************************************************

From: Bob Duff
Sent: Wednesday, April 23, 2014  4:02 PM

> I believe putting more into the Ada standard on this is unwise.

I agree 100%.

None of the actions being discussed here are viable, IMHO:

    - Giving an Implementation Permission is pointless --
      implementations can already do what they like in this area,
      and in practice do things much more useful than the I.P.
      discussed.

    - Requiring implementations to "reject" some subset of programs
      that will ALWAYS fail, no matter what the input, is also
      pointless.  That will prevent approximately zero bugs from
      escaping into the wild, because those bugs WILL be found by
      testing.  And the cost is rather large language and implementation
      complexity.

    - Requiring implementations to "reject" some programs that MIGHT
      fail, or WILL PROBABLY fail is either pointless, or is an
      unacceptable incompatibility, or both, depending on the exact
      rules.  Complexity costs as above.

****************************************************************

From: Randy Brukardt
Sent: Wednesday, April 23, 2014  4:43 PM

> > What do people here think? Would this catch enough low-hanging fruit (i.e.
> > silly errors) to make the implementation worthwhile? Or is there a
> > better way to deal with this?
>
> Yes, there's a better way to deal with this sort of thing.
> My idea of requiring a diagnostic message, but still allowing the
> program to run, is apt.

I disagree, see below for why.

> >... Or is this not worth doing at all?
>
> Yes, in this case.
>
> In any case, I don't think adding Implementation Permissions is of any
> benefit.  Implementations can already do that, and many do.

No they can't. At least they can't vis-a-vis the ACATS, which is where this
issue arises. There have been a number of instances where for new ACATS tests,
compilers are rejecting dubious code that the language requires accepting and
unconditionally raising an exception. (The GNAT static elaboration model is one
instance of this sort of thing.) I hate that such ACATS tests require
implementers to do a lot of work (raising a runtime exception is takes
considerably more effort in compilers than rejecting code, as one has to figure
out how to generate proper code in the former case) without any benefit to
users. But I have to create such tests, as without them the possibility exists
of the problem not being detected at all. The language does not give me any
leeway to accept compile-time rejection of such cases as passing behavior -- I
think it needs to do that, in some limited circumstances.

The point here is that Implementation Permissions serve to reduce the disconnect
between the ACATS (which is enforcing strict adherence to the Standard) and
actual practical use of Ada (where detecting a problem, perhaps by the "wrong"
means, is far more important than how the problem is reported -- the important
issue is that the problem is detected, not how).

> We language designers tend to fall into the trap of thinking that
> language standards require something, and I think you're doing that
> here.  Standards compliance is entirely optional!
> (In fact I suspect most Implementation Permissions fall into this
> category, including ones I was once in favor of.)

Standards compliance is not optional if you plan to pass the ACATS. I agree that
if you don't plan to pass the ACATS, what the Standard says about anything is
essentially irrelevant - but I don't see any reason that the Standard ought to
care about such people. I *do* care about implementers that want to pass the
ACATS -- I see no reason for them to do work that is of little benefit to their
users simply because the Standard doesn't allow them to do the better thing.

A very good example is elaboration checking in GNAT. GNAT's static elaboration
model is clearly superior for most uses of Ada, so understandably most of the
effort goes toward that. However, the ACATS cannot allow that model because its
not in the Standard. Thus, any tests that are constructed that test elaboration
checks force GNAT to either (A) spend effort on dynamic checks where their
static checks are more valuable to their customers or (B) abandon standards
compliance. That's a terrible choice. I could of course avoid the problem by not
issuing any tests for dynamic elaboration checks, but then we are leaving a hole
in the testing coverage, one that could cause other implementers to miss
required checks altogether.

...
> I think an Impl Perm is nonresponsive to the supposed problem of
> AI12-0024-1:
>
>     From: Dan Eilers
>     Sent: Monday, February 13, 2012  11:36 AM
>
>     I am proposing that when the bounds of an array or array aggregate are Defined
>     using static expressions or static positional notation, then any run-time
>     constraint checks currently required by the language should be performed at
>     compile time.
>
> The original example happened to be at library level, but Dan wants
> this to be a legality error even when the code in question might not
> be executed.  And he doesn't want *permission*, he wants all
> conforming compilers to do that.

Dan's request is best solved with exception contracts (if a subprogram might
propagate Constraint_Error but does not have a contract allowing that, then
there is an error). And even then, there will be problems with unreachable code.
A global setting makes absolutely no sense to me - it would have to be turned
off in every system I've ever been associated with, meaning it could do no good
whatsoever.

Errors should be used to reject clearly incorrect or very dubious code. But
there is nothing dubious about code that might raise a exception so long as that
code is dead.

A suppressible error, as you suggest, is absolutely wrong in this context.
Suppressing a suppressible error says that I want to ignore a likely bug. This
is an mechanism that ought to be strongly restricted in Ada style guides; it
should be managed like Gotos to be used only very prescribed circumstances. (In
this case, primarily where modifying the code is not a good idea (such as other
people's code, or code that is frozen for some reason.) In all other cases, the
code should be modified to eliminate the suppressible error, not to add some
suppression mechanism.

That's absolutely not the case here. First, there is no way to avoid these
errors when they happen; there is no sensible code modification that would
eliminate a divide-by-zero if it exists in the code. Secondly, these sorts of
things were anticipated and encouraged by the language design -- Ada had a
strong aversion to traditional conditional compilation mechanisms and sought to
make  the language itself serve that purpose as much as possible.

[Aside: The legality rule that static expressions are illegal if they raise an
exception has periodically bit me. That can be worked around by making the
expression somehow non-static, but it's a major annoyance in parameterized code.
It is already a bad idea, and I would be quite opposed to anything that expands
the effect of values on Legality Rules other than in very limited
circumstances.]

This proposed check would be 10 times more likely to occur than the static case;
literally hundreds of times in some of my packages. For many of my packages, I
have to turn off Janus/Ada's warnings because there are so many of them.

We could do better of course by excepting some of the dead code cases, but I
think that any rules in that direction are doomed to having far too many false
positives to be useful. And defining the rules would be a huge mess.

> One final point:  There is little value in detecting (at compile time)
> run-time errors that will happen every time the program is run.
> Even the most minimal amount of testing can catch such errors (just
> run the program once, on any input).  Legality errors are useful when
> they detect errors that might not happen during testing.

Perhaps my view is colored very much by Claw and the ACATS, but I disagree.
The instance that I recalled that led to this thread was a problem that happened
in a little-used Claw package. It was a long time before the anyone constructed
a test for that package, and it had already been fielded by the time that the
elaboration bug was detected. (I'm not sure it was ever tested, as there is no
practical way to automate GUI tests without corrupting the behavior of the
underlying library, and hand-testing is very time-consuming.)

As far as the ACATS goes, requiring expensive runtime checks when cheap
compile-time checks would do is a inversion of what we want Ada to be. I don't
think it's a good idea to require compilers to make compile-time checks here (my
first idea was an implementation requirement to reject any units that
unconditionally raise an exception during library-level elaboration -- but
that's a lot of work), but we certainly should allow them to do so (since there
is no possible useful program involved).

****************************************************************

From: Randy Brukardt
Sent: Wednesday, April 23, 2014  4:49 PM

...
> The language's definition of "static expression" doesn't distinguish
> these cases, but I'm wondering if it would be useful to define a
> "literally static expression" that is a static expression that doesn't
> involve any user-defined names, and doesn't involve
> implementation-dependent attributes such as Integer'Last.  Then it
> might be possible to treat cases differently depending on whether they
> involve only "literally static expressions" or not ...

I don't see the point. The number of expressions involving "literally static
expressions" should be close to zero if the standard advice about avoiding magic
numbers in your code is followed. As such, it could only help extreme novices
and (as any proposal in this area would do) would make ACATS testing harder.
There are probably better ways to help extreme novices, and even if not, I'm
mostly interested in helping me. ;-) Better make that "Ada programmers of all
experience levels" - I want new rules to have the potential to help in my code,
not just novices.

****************************************************************

From: Randy Brukardt
Sent: Wednesday, April 23, 2014  4:53 PM


> I believe putting more into the Ada standard on this is unwise.
> Speaking from the experience of developing a sophisticated static
> analyzer for Ada (CodePeer), there is a huge amount of "gray" area
> here, and trying to identify what is and is not a bug is just too
> hard.  Even something as simple as complaining when a statically known
> value is outside the statically-known base range of a type, has
> created controversy and some difficulties for certain Ada coding
> styles.  Going beyond that is just opening a huge can of worms.
>
> Compilers can (and probably should) integrate sophisticated static
> analyzers to help find bugs as early as possible, but I believe it is
> beyond the state of the art, and in some cases actively harmful, for
> the language standard for a language as complex as Ada to try to start
> legislating exactly what is and is not a bug.  It would be fine when
> first designing a language to build in more restrictive legality rules
> that depend on things like control and data flow analysis, but it is
> too late to do that for Ada, I suspect.
> And based on the experience of trying to define such rules for Java
> relating to the initialization of local variables, writing such
> legality rules is a big effort!

I agree, that's precisely why I suggested an Implementation Permission. I
believe that we've pretty much reached the point of what we can mandate checks
for in Ada compilers; almost anything of value is going to be far too complex to
describe in the Standard.

But I want compilers to be able to use these more advanced techniques to push
more things to be errors. And I want to be able to do that in Standard mode; in
particular, I do not want vendors to have to maintain a separate "pedantic" mode
for the purposes of passing the ACATS. That just requires implementers to do
extra work and decreases the quality of the testing available to the "usual"
mode of operation.

A goal like that requires some checks to be implementation-defined as to whether
they are done at compile-time or at run-time. I see that for exception
contracts, and I see something similar in this case (which is essentially the
cases were no contracts are possible -- library-level elaboration). Perhaps this
vision is not where Ada ought to go, but I really don't see any other way to get
to where we ought to be going.

****************************************************************

From: Randy Brukardt
Sent: Wednesday, April 23, 2014  5:14 PM

> None of the actions being discussed here are viable, IMHO:
>
>     - Giving an Implementation Permission is pointless --
>       implementations can already do what they like in this area,
>       and in practice do things much more useful than the I.P.
>       discussed.

I disagree, for reasons discussed earlier. In particular, we need to allow more
in the Standard mode so that implementations aren't maintaining two parallel
versions of their systems -- one for the ACATS, and one for real use. That helps
no one.

And directly to the point, if a compiler can detect an error that will always
happen during library-level elaboration, it should be allowed to reject the
program -- it shouldn't be required to implement an unconditional runtime check
which is necessarily harder to do and of absolutely no value to anyone.

>     - Requiring implementations to "reject" some subset of programs
>       that will ALWAYS fail, no matter what the input, is also
>       pointless.  That will prevent approximately zero bugs from
>       escaping into the wild, because those bugs WILL be found by
>       testing.  And the cost is rather large language and implementation
>       complexity.

I don't see the large language complexity, it's one sentence in the Standard.
("Any program which unconditionally fails a language-defined check during
library-level elaboration is illegal." as an Implementation Requirement in
1.1.5). If the language was newly defined, I would be strongly in favor of such
a rule.

As far as "WILL be found by testing", sure, but who tests fixes to running
systems? I certainly don't; I don't have the resources (time or machines) to do
so. I just field the updated web server or mail filter, keeping the previous
version around for rollback in emergencies. And I've had multiple failures of
checks at elaboration time -- which occur so early that no logging of errors is
possible, so the only effect is that the server doesn't ever start - there is no
way to find out anything about what happened at elaboration time. Finding those
mistakes is a major time sink (I have to guess what I could have done wrong to
cause an error), and having compile-time rejection would have saved a lot of
time creating a version to install.

Even when you do test systems, such bugs cause extra
compile-bind-link-package-copy-to-testbed-test-fix cycles, and those take
significant time, especially as these are bugs in specifications that typically
require rebuilding the entire system. This is a faster cycle than it used to be,
but that doesn't make it free!

The only reason that I wouldn't try to require such rejection is simply that
there is a cost of implementing this in existing compilers (which have already
implemented the suboptimal and painful runtime checks), and I want to let
customer demand, rather than the Standard and the ACATS, dictate what effort is
spent on. New implementations should always do compile-time checks here, if the
Standard allowed it.

>     - Requiring implementations to "reject" some programs that MIGHT
>       fail, or WILL PROBABLY fail is either pointless, or is an
>       unacceptable incompatibility, or both, depending on the exact
>       rules.  Complexity costs as above.

Certainly both. I agree here, that's why I'm looking for alternatives.

****************************************************************

From: Tucker Taft
Sent: Wednesday, April 23, 2014  5:32 PM

>> ...
>> writing such legality rules is a big effort!
>
> I agree, that's precisely why I suggested an Implementation
> Permission. I believe that we've pretty much reached the point of what
> we can mandate checks for in Ada compilers; almost anything of value
> is going to be far too complex to describe in the Standard.
>
> But I want compilers to be able to use these more advanced techniques
> to push more things to be errors. And I want to be able to do that in
> Standard mode; in particular, I do not want vendors to have to
> maintain a separate "pedantic" mode for the purposes of passing the
> ACATS. That just requires implementers to do extra work and decreases
> the quality of the testing available to the "usual" mode of operation. ...

In the limited case of library elaboration checking, I can see some advantage of
providing an implementation permission to report these at compile time and not
produce an executable.  But in fact GNAT has to maintain the ability to support
run-time elaboration checks because the code being compiled might have been
developed without following the somewhat stricter rules enforced by GNAT's
static checking.  And sometimes a small change suddenly breaks the GNAT static
checking rules, and you end up having to fall back to dynamic elaboration
checking even in a system that was developed most of the way using static
checking.  So I am not sure GNAT would actually benefit from this permission.
And I don't think the ACATS tests should be altered to try to match GNAT's more
restrictive checks.

Any Ada compiler will still need the ability to insert an unconditional raise of
an exception on occasion, since these permissions will never completely
eliminate such cases. I doubt we would actually be helping any implementor in
any significant way with such a permission, and we would certainly make the
ACATS testing process more complex.

****************************************************************

From: Randy Brukardt
Sent: Sunday, April 27, 2014  8:44 PM

...
> In the limited case of library elaboration checking, I can see some
> advantage of providing an implementation permission to report these at
> compile time and not produce an executable.
...
>   I doubt we would actually be helping any implementor in any
> significant way with such a permission, and we would certainly make
> the ACATS testing process more complex.

Obviously the permission idea is not going to get any traction. Perhaps a better
approach is to suggest a targeted change to the language to require compile-time
rejection of some failed elaboration checks.

The original issue is something I saw in one of the new ACATS tests. One of the
tests has (simplified):

   package P is
      type Priv is private;
      function F (P : Priv) return Natural;
      C : constant Priv;
   private
      type Priv is record A : Natural; ...
      Obj : constant Priv := ...;
      function F (P : Priv) return Natural is
         (if P = C then 0 else P.A);
      C : constant Priv := (A => F(Obj), ...);
   end P;

This is illegal because of freezing; C is frozen when it is used in F before it
is complete.

However, if we swap the two declarations (perhaps in an attempt to fix the first
error):

   package P2 is
      type Priv is private;
      function F (P : Priv) return Natural;
      C : constant Priv;
   private
      type Priv is record A : Natural; ...
      Obj : constant Priv := ...;
      C : constant Priv := (A => F(Obj), ...);
      function F (P : Priv) return Natural is
         (if P = C then 0 else P.A);
   end P2;

this is legal but always raises Program_Error when evaluating F (because the
completion hasn't been seen yet). I don't think this is helpful (especially if
warnings are suppressed as they always are in ACATS testing), and the effort to
implement the check is wasted.

It would be better if we had a rule making "obvious" cases of premature calls
illegal. (And yes, these have happened to me many times over the years.)
Something like:

If the prefix of a subprogram call statically denotes a declaration declared in
the same declarative region as the call, the call is illegal if the declaration
requires a completion and the completion follows the call in the declarative
region. For the purposes of this rule, any body stub that the language requires
to contain the completion of the subprogram is assumed to contain that
completion.

Ramification: The program will be illegal if the stub doesn't include the
completion, so we don't need to consider it further.

This would make a number of "obvious" cases of premature call illegal. The
incompatibility would only matter if it occurred in a subprogram that was never
called (as any call would cause the problem), and that's hard to get worried
about.

Thoughts?

****************************************************************

From: Tucker Taft
Sent: Monday, April 28, 2014  9:14 AM

I'm not convinced the benefit outweighs the standardization and implementation
efforts. Compilers can introduce "serious warning" categories if they want, and
at least GNAT has a mode where you can specify that all warnings are treated
like errors.  That seems adequate.  I don't see the need for additional
standardization here, as it isn't a portability issue.  It is already the case
that this code isn't going to execute successfully on any conforming compiler.

****************************************************************

From: Cyrille Comar
Sent: Monday, April 28, 2014  7:59 AM

I don't see any benefit in this suggestion, neither for the compiler writer nor
for the general user. The compiler writer has to be able to emit checks in
elaboration code anyway and for the user, it will only catch some of the cases
and mostly the ones that will be immediately obvious at runtime. I don't see how
it can be considered worth adding a new rule for that.

by the way, if you C definition becomes:

  C : constant Priv := (A => if some_condition then F(Obj) else C, ...);

then your last statement doesn't seem correct: the incompatibility can
occur outside of a subprogram that is never called. Here, it is just
that "some_condition" was not true.

****************************************************************

From: Jeff Cousins
Sent: Tuesday, April 29, 2014  5:51 AM

Although it would be useful, I think this should be left to the vendors.
Quality and control of error and warning messages is something vendors could
compete on. GNAT gives a lot of control of turning individual warnings on or
off, ideally there would be a rules file assigning a priority to each individual
possible warning, one of which could be treat as error.

****************************************************************

From: Randy Brukardt
Sent: Wednesday, May 7, 2014  11:08 PM

...
> I'm not convinced the benefit outweighs the standardization and
> implementation efforts.
> Compilers can introduce "serious warning" categories if they want, and
> at least GNAT has a mode where you can specify that all warnings are
> treated like errors.  That seems adequate.
> I don't see the need for additional standardization here, as it isn't
> a portability issue.  It is already the case that this code isn't
> going to execute successfully on any conforming compiler.

This "argument" makes no sense to me. It's essentially saying that runtime
checks are good enough, because SOME compiler might be able to help. (And my
experience with warnings on compilers is uniformly bad, at least with "portable"
code. GNAT might be better than most, but it still spews bogus warnings in most
of my existing programs -- and the only fix for that would be to insert
GNAT-specific pragmas, which would make *other* compilers spew bogus warnings
about the pragmas. This is NOT a solution to anything! At least Bob's
"suppressible errors" would be part of the language [including the suppression
mechanism] and could be used everywhere. I could not use "GNAT's warnings are
errors mode" on any of my code without destroying any portability.)

I recall the Ada 95 team making a very strong push that compile-time checks are
better than run-time checks, which are better than no checks. I have to wonder
what has changed.

Anyway, I can see that this is getting all of the traction of my car's rather
worn tires in an ice storm, so I'll give up now.

****************************************************************


Questions? Ask the ACAA Technical Agent