TEAM-ADA Archives

Team Ada: Ada Programming Language Advocacy


Options: Use Forum View

Use Monospaced Font
Show Text Part by Default
Condense Mail Headers

Message: [<< First] [< Prev] [Next >] [Last >>]
Topic: [<< First] [< Prev] [Next >] [Last >>]
Author: [<< First] [< Prev] [Next >] [Last >>]

Print Reply
"Team Ada: Ada Advocacy Issues (83 & 95)" <[log in to unmask]>
"Harbaugh, John S" <[log in to unmask]>
Thu, 30 Aug 2001 16:28:18 -0400
Tucker Taft <[log in to unmask]>
Tucker Taft <[log in to unmask]>
text/plain; charset=us-ascii
AverStar (formerly Intermetrics) Burlington, MA USA
text/plain (128 lines)
"Harbaugh, John S" wrote:
> Fellow Teamers -
> I am working on a large (>1M SLOC) Ada95 project.  We are applying static analysis to help identify and remove sources of operational instability.  Our analysis has revealed numerous use-before-set problems.  Many of the problems are simple programming errors, but there are several pervasive problems which are not easily solved.  The reason is that the problems are related to parameter profiles for generics and access to subprogram types (callbacks), as will be illustrated shortly.  I need to understand which of these problems are near-term, must fix problems, and which can be worked later.  To that end, I would appreciate your insights on the language.  I have to say,  in over an hour of digging in the LRM I was unable to find a definitive statement.  Okay, on to the code.
> > The first problem category is passing uninitialized variables to subprograms with "in out" parameters.  This is a potential problem if, inside the subprogram, the parameter is read before being set.  However, exceptions can still arise even if the parameter is not read.  Consider the following example:
> >
> > procedure Main2 is
> >
> >    type Color is (Red, Green);
> >
> >    procedure Initialize (P : in out Color) is
> >    begin
> >       P := Green;
> >    end;
> >
> >    Var : Color;
> >    Tar : Color range Red..Red;
> >    Car : Color range Green..Green;
> >
> > begin
> >
> >    Initialize(Var);  -- GNAT 3.13p Does not raise exception
> >    Initialize(Car);  -- GNAT 3.13p Raises Constraint_Error
> >    Initialize(Tar);  -- GNAT 3.13p Raises Constraint_Error
> >
> > end;
> >

It is certainly an error to pass an uninitialized variable
to an IN OUT parameter.  Whether or not it is detected
depends on luck, the implementation, etc.  Either the
parameter mode should be changed to OUT, or the variable
should be initialized.

> Testing revealed no exceptions with pass-by-reference parameters. I need to understand when is this simply awful but unexceptional code, and when is it prone to exceptions.

It is true that modes IN OUT and OUT are essentially identical
for pass-by-reference parameters, from an implementation point
of view.  However, from a logical point of view, the same
rule applies, namely that either the mode should be OUT or
the actual parameter should be initialized.  Also, in some cases,
one implementation might pass a parameter by copy while the other
passes by reference.

One thing about pass-by-reference parameters, i.e. arrays and records
(and tasks and protected objects), being "initialized" is somewhat
ill-defined, since an array inside a record might have some components
purposely left uninitialized, even though the record as a whole
is considered "initialized."  E.g. a record like:
    type Text is record
        Length : Integer := 0;
        Buffer : String(1..100);
    end record;

could be considered adequately initialized, even though no components
of the Buffer field have been initialized.

> ... In particular, can the first call  potentially raise exceptions on some other implementation?  RM 3.2.2(12) suggests why a pass-by-copy parameter could raise an exception, but then why don't all three calls raise Constraint_Error?

This is just luck of the draw, since it depends on what "stack junk"
is sitting in the local variables at the time of the call.
The stack junk might be in range, or it might not be.

> ...  Are pass-by-reference parameters always immune to exceptions under these conditions?

In general, the only constraint checks that apply to pass-by-reference
parameters are "structural" constraint checks, that check the "shape"
or the discriminants of the parameter.  These things generally don't
change as a result of initialization, so it is rare that a constraint
check can detect lack of initialization.  In fact, for composite types,
the constraint check performed is independent of whether the parameter
mode is IN OUT, OUT, or IN.  The check is always performed before
the call, and depends on the bounds or discriminant values, not on
the "content" of the object.

> > The second category of problems is failure to assign a value to an "out" parameter.  This is certainly a problem if the caller should try to use the result.  However, exceptions can still arise even if the parameter is not used.  Consider another example:
> >
> > procedure Main is
> >
> >    type Color is (Red, Green);
> >
> >    procedure Is_Broken (P : out Color) is
> >    begin
> >       null;
> >    end;
> >
> >    Var : Color;
> >    Tar : Color range Red..Red;
> >    Car : Color range Green..Green;
> >
> > begin
> >
> >    Is_Broken(Var);  -- Does not raise exception
> >    Is_Broken(Tar);  -- Does not raise exception
> >    Is_Broken(Car);  -- Raises Constraint_Error
> >
> > end;
> >
> > The obvious solution is to always assign a value to the parameter, but this may not always be possible.  For example, we have generic containers with methods that can return a component and a validity flag.  If a component can not be returned, the container assigns nothing to the component parameter, and sets the validity parameter appropriately.  Unfortunately, the containers have no formal nil object in there specification, and therefor cannot return a safe value.  Again, to change a generic spec. can have significant ripple effect on other units.

Certainly if the type of the OUT parameter is (constrained) elementary,
then you should assign it a value, since there will be a constraint
check after the call.  If the type of the OUT parameter is composite
or unconstrained scalar, then you generally don't need to assign
it a value, since there is no constraint check after the call.
Of course in a generic, you may not know whether the parameter type
might or might not be constrained elementary.  In that case, you
should probably not use the validity flag notion, or perhaps bundle
the two OUT parameters (validity flag and result) into a single
record OUT parameter, ensuring that there is no constraint check on

> >
> Thanks in advance for your comments,
>         - John Harbaugh

-Tucker Taft   [log in to unmask]
Chief Technology Officer, AverCom Corporation (A Titan Company)
Bedford, MA  USA (AverCom was formerly the Commercial Division of AverStar: