Version 1.3 of ais/ai-00227.txt

Unformatted version of ais/ai-00227.txt version 1.3
Other versions for file ais/ai-00227.txt

!standard 13.13.1 (4)          00-03-16 AI95-00227/03
!standard 13.13.1 (5)
!standard 13.13.1 (8)
!class binding interpretation 00-03-15
!status work item 00-03-13
!status received 00-02-09
!priority Medium
!difficulty Easy
!subject Behavior of Ada.Streams.Read when at the end of stream
!summary
Ada.Streams.Read returns Item'First - 1 in Last when no elements are transferred. If Item'First = Stream_Element_Offset'First and no elements are transferred, Constraint_Error is raised.
!question
The declarations in Ada.Streams include:
type Stream_Element_Offset is range <implementation-defined>; subtype Stream_Element_Count is Stream_Element_Offset range 0 .. Stream_Element_Offset'Last; type Stream_Element_Array is array (Stream_Element_Offset range <>) of Stream_Element;
procedure Read (Stream : in out Root_Stream_Type; Item : out Stream_Element_Array; Last : out Stream_Element_Offset) is abstract;
Consider the following case:
Strm : <some type derived from stream>; Item : Stream_Element_Array (Stream_Element_Offset'First ..
Stream_Element_Offset'First + 10);
Last : Stream_Element_Offset;
... Read (Strm, Item, Last);
If Strm is at end of stream, such that no elements are read, what is the value of Last? (Constraint_Error is raised).
!recommendation
(See summary.)
!wording
(See corrigendum.)
!discussion
13.13.1(8) says:
The Read operation transfers Item'Length stream elements from the specified stream to fill the array Item. The index of the last stream element transferred is returned in Last. Last is less than Item'Last only if the end of the stream is reached.
In order to answer the question, we need to know what the value of Last is when no elements are transferred. However, 13.13.1(8) doesn't say. The second sentence says that Last is set to the index of the last element transferred, but it says nothing about the value if no elements are transferred.
We answer the question by appealing by analogy to the similar routine, Ada.Text_IO.Get_Line. A.10.7(20) says "If no characters are read, returns in Last an index value that is one less than Item'First." This result is known by every Ada programmer, and it seems best to define Ada.Streams.Read the same way.
Having determined the value of Last, we can now answer the question. Last will be equal to Item'First - 1. If Item'First = Stream_Element_Offset'First, this value will be out of the range of the base type, and thus will raise Constraint_Error.
The fact that this routine was designed to avoid raising an exception even when nothing was read is irrelevant. Users of Ada.Streams.Read ought to avoid passing in Item arrays whose lower bound is Item'First.
!corrigendum 13.13.01(08)
Replace the paragraph:
The Read operation transfers Item'Length stream elements from the specified stream to fill the array Item. The index of the last stream element transferred is returned in Last. Last is less than Item'Last only if the end of the stream is reached.
by:
The Read operation transfers stream elements from the specified stream to fill the array Item. Elements are transferred until Item'Length items have been transferred, or until the end of the stream is reached. If any elements are transferred, the index of the last stream element transferred is returned in Last. Otherwise, Item'First - 1 is returned in Last. Last is less than Item'Last only if the end of the stream is reached.
!corrigendum 13.13.01(10)
Insert after the paragraph:
See A.12.1, ``The Package Streams.Stream_IO'' for an example of extending type Root_Stream_Type.
the new paragraph:
If the end of stream has been reached, and Item'First is Stream_Element_Offset'First, Read will raise Constraint_Error.
!ACATS test
A C-test should be created (or better, added to an existing test) to test this case.
!appendix

From: Randy Brukardt
Sent: Wednesday, February 09, 2000 3:04 PM
To: 'Ada Comment'
Subject: [Ada-Comment] Behavior of Ada.Streams.Read when Last is not in
the base type.

The declarations in Ada.Streams include:

   type Stream_Element_Offset is range <implementation-defined>;
   subtype Stream_Element_Count is Stream_Element_Offset range 0 ..
Stream_Element_Offset'Last;
   type Stream_Element_Array is array (Stream_Element_Offset range <>) of
Stream_Element;

   procedure Read (Stream : in out Root_Stream_Type;
                            Item : out Stream_Element_Array;
                            Last : out Stream_Element_Offset) is abstract;

Consider the following case:

     Strm : <some type derived from stream>;
     Item : Stream_Element_Array (Stream_Element_Offset'First ..
Stream_Element_Offset'First + 10);
     Last : Stream_Element_Offset;

     ...
     Read (Strm, Item, Last);

If Strm is at end of stream, such that no elements are read, what is the
value of Last? It ought to be Stream_Element_Offset'First - 1, but that
value is out of range of the base type. Thus it appears that such a call
raises Constraint_Error, which appears surprising. (At least it is
preventable by the programmer.) It's surprising because the design of these
routines goes out of their way to avoid raising End_Error or something like
it in this case, yet here we still can get an exception.

I don't see anything about this in the ARM or in any of the existing AIs.

Aside: There doesn't seem to be any reason for Stream_Element_Count in
Ada.Streams. It is never used anywhere so far as I can tell. I wonder if the
intent was to declare the array type as

     type Stream_Element_Array is array (Stream_Element_Count range <>) of
Stream_Element;

which would have avoided the Constraint_Error problem above. I think this
would be too large a change to make now anyway, so that is mainly of
academic interest.

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

From: Christoph Grein
Sent: Wednesday, February 09, 2000 11:53 PM
Subject: Re: [Ada-Comment] Behavior of Ada.Streams.Read when Last is not
in the base type.

> The declarations in Ada.Streams include:
>
>    type Stream_Element_Offset is range <implementation-defined>;
>    subtype Stream_Element_Count is Stream_Element_Offset
>             range 0 .. Stream_Element_Offset'Last;
>    type Stream_Element_Array is array (Stream_Element_Offset range <>)
>             of Stream_Element;
>
>    procedure Read (Stream : in out Root_Stream_Type;
>                             Item : out Stream_Element_Array;
>                             Last : out Stream_Element_Offset) is abstract;
>
... (snip)
> Aside: There doesn't seem to be any reason for Stream_Element_Count in
> Ada.Streams. It is never used anywhere so far as I can tell. I wonder if the
> intent was to declare the array type as
>
>      type Stream_Element_Array is array (Stream_Element_Count range <>) of
> Stream_Element;
>
> which would have avoided the Constraint_Error problem above. I think this
> would be too large a change to make now anyway, so that is mainly of
> academic interest.
>
> 			Randy Brukardt.

OOPS, you're nearly right with your proposal; my 2 Euro-cents:
I gather they meant something like (see Text_IO.Get_Line):


   type    Stream_Element_Count  is range 0 .. <implementation-defined>;
   subtype Stream_Element_Offset is
           Stream_Element_Count     range 1 .. Stream_Element_Count'Last;

   type Stream_Element_Array is array (Stream_Element_Offset range <>)
             of Stream_Element;

   procedure Read (Stream: in out Root_Stream_Type;
                   Item  : out Stream_Element_Array;
                   Last  : out Stream_Element_Count) is abstract;

Then Last can safely be 0 if there is nothing in the stream.

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

From: Robert A Duff
Sent: Thursday, February 10, 2000 10:51 AM
Subject: Re: [Ada-Comment] Behavior of Ada.Streams.Read when Last is not
in the base type.

Randy,

You suggest maybe we meant:

>      type Stream_Element_Array is array (Stream_Element_Count range <>) of
> Stream_Element;

The history, as I recall, is that I wrote it that way first.
Then Offer Pazy argued that it should start at 1 instead of 0.
Tucker settled the argument by saying the programmer can choose,
and they can choose anything else, too, if they like.

I don't see a big problem with the way it is -- the programmer just has
to know not to declare arrays starting at the most negative number.

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

From: Randy Brukardt
Sent: Thursday, February 10, 2000 11:41 AM
Subject: Re: [Ada-Comment] Behavior of Ada.Streams.Read when Last is not
in the base type.

Bob said:

"I don't see a big problem with the way it is -- the programmer just has
to know not to declare arrays starting at the most negative number."

You're right about it not being a big problem, but the latter part of your
statement is what I was commenting on: how do they know not to do that? *I*
didn't know it until I stumbled over it yesterday. There is nothing in the
standard or the AARM mentioning that Constraint_Error can be raised if the
Item'First = Stream_Element_Offset'First. I've started arrays at
Index_Subtype'First without ever checking what that value might be, and I
can imagine others doing so.

There also is an implementation issue: we needed to code an explicit check
of this case (since Janus/Ada runtime packages always have been distributed
with checking suppressed). I wouldn't be surprised to find other compilers
that failed to make this check and do something bad in this case.

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

From: Robert Dewar
Sent: Thursday, February 10, 2000 2:05 PM
Subject: Re: [Ada-Comment] Behavior of Ada.Streams.Read when Last is not
in the base type.

<<"I don't see a big problem with the way it is -- the programmer just has
to know not to declare arrays starting at the most negative number."
>>

Note that this is a standard problem, slices don't work well in this
situation either, and the problem is even more severe with enumeration
types used as subscript indexes.

This simply reflects an annoying flaw in the language design which cannot
be fixed. It's no big deal anyway.

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

From: Christoph Grein
Sent: Thursday, February 10, 2000 11:59 PM
Subject: Re: [Ada-Comment] Behavior of Ada.Streams.Read when Last is not
in the base type.

> <<"I don't see a big problem with the way it is -- the programmer just has
> to know not to declare arrays starting at the most negative number."
> >>
>
> Note that this is a standard problem, slices don't work well in this
> situation either, and the problem is even more severe with enumeration
> types used as subscript indexes.
>
> This simply reflects an annoying flaw in the language design which cannot
> be fixed. It's no big deal anyway.

Robert, please elaborate. I do not see enumerations involved here.
I'm no compiler builder, so I do not see a flaw that cannot be fixed by
declaring Stream_Element_Array like String.

Is there a fundamental need to use negative indices?

If this is really a standard problem, it should be obvious to simple
programmers. To me at least it's not.

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

From: Ted Baker
Sent: Friday, February 11, 2000 6:22 AM
Subject: Re: [Ada-Comment] Behavior of Ada.Streams.Read when Last is not
in the base type.

While we are revisiting Stream_Element array (and I agree with Christoph)
it would not hurt to revisit the issue of needless copying of
buffers that this interface imposes by not requiring the array to
have aliased components.  Since Stream_Elements are used to represent
data of other types, the uses of this type will involve unchecked
conversions of pointers to map sections of a stream_element_array
onto objects of various types.  In my experience, this has required me
to first copy the data to/from the stream_element_array from/to
an array of aliased stream elements (or aliased elements of some other
byte-like type, with unchecked conversion at that point), and then
do the actual I/O operation.  This useless copying could be avoided if
the elements of stream_element_array were aliased.

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

From: Randy Brukardt
Sent: Friday, February 11, 2000 10:27 AM
Subject: Re: [Ada-Comment] Behavior of Ada.Streams.Read when Last is not
in the base type.

The aliased element issue was already addressed by the approved AI-00181,
so let's not revisit that. Indeed, this rule will be in the Corrigendum.

Of course, the ARG having made the components aliased and having compilers
actually implement them that way are two different things. We tried to take
advantage of this change in Claw last year, and found that exactly zero out
of four compilers that we tried supported it. I suspect that will change
only when there is an ACATS test for it.

Of course, the ARG is supposed to be creating tests for AIs, and this one is
no exception. I looked to see who this test was assigned to, and it is
assigned to one Ted Baker! So Ted, if you *REALLY* want to see this happen,
you'll write that test. Otherwise, I doubt that implementors will actually
comply with the AI; probably many of them aren't aware of it.

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

From: Pascal Leroy
Sent: Friday, February 11, 2000 12:21 PM
Subject: Re: [Ada-Comment] Behavior of Ada.Streams.Read when Last is not
in the base type.

> it would not hurt to revisit the issue of needless copying of
> buffers that this interface imposes by not requiring the array to
> have aliased components.

This was fixed years ago.  See AI95-00181.

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

From: Robert Dewar
Sent: Saturday, February 12, 2000 6:51 AM
Subject: Re: [Ada-Comment] Behavior of Ada.Streams.Read when Last is not
in the base type.

<<Robert, please elaborate. I do not see enumerations involved here.
I'm no compiler builder, so I do not see a flaw that cannot be fixed by
declaring Stream_Element_Array like String.
>>

The point with enumerations (happens with modular types too) is that it
is natural to declare arrays subscript types whose lower bound is
type'first (which does not happen with signed types).

The problem is that if you say

  type x is array (character) of integer;

there is no way to reference the null string just before the first character,
so you have to special case this (it particularly comes up in generics).

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

From: Ted Baker
Sent: Monday, February 14, 2000 1:20 PM
Subject: Re: [Ada-Comment] Behavior of Ada.Streams.Read when Last is not
in the base type.

ooops!........  My memory is sometimes stuck in the past.
I should write the test.  --Ted

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

From: Randy Brukardt
Sent: Monday, March 13, 2000 1:31 PM

I was writing up a confirmation AI on the stream elements question of last
month (we have to consider an AI because I stupidly posted the message to
Ada-Comment rather than here). Anyway, I had to fudge it a bit.

13.13.1(8) says in part:

The index of the last stream element transferred is returned in Last. Last
is less than Item'Last only if the end of the stream is reached.

This says nothing about the value of Last if no elements are transferred. I
think everyone has *assumed* that it is Item'First - 1, but the wording
above seems to leave it undefined (although it ought to be less than
Item'Last).

Is this defined somewhere else? If not, perhaps we ought to use the AI to
repair this???

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

From: Tucker Taft
Sent: Tuesday, March 14, 2000 12:54 PM

Why is this necessary to specify?  Isn't it Ok to leave it
implementation-defined (i.e., the proper check is "Last < Item'First"
rather than "Last = Item'First - 1")?

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

From: Randy Brukardt
Sent: Tuesday, March 14, 2000 3:01 PM

I suppose it isn't necessary to specify a particular value (although why we
would want to bring all of the problems of super-null arrays into this case is
beyond me), but it certainly is necessary to specify *something* about the
value. The text does *not* specify that the value of Last must be less than
Item'First; indeed it makes no requirements at all.

Note that it says "Last is less than Item'Last only if the end of stream is
reached." It does *not* say "If the end of stream is reached, Last is less than
Item'Last.". So, returning Last = Item'Last + 1 would meet all of the
requirements of the paragraph in the case where nothing was transferred.

Indeed, if I wanted to split hairs, I could argue that the sentence "The index
of the last stream element transferred is returned in Last." doesn't apply at
all in this case, because nothing was transferred. That would make Last an
uninitialized variable in that case: certainly a result we don't want.

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

From: Tucker Taft
Sent: Wednesday, March 15, 2000 9:26 AM

Randy Brukardt wrote:
> ...
> Indeed, if I wanted to split hairs, I could argue that the sentence "The
> index of the last stream element transferred is returned in Last." doesn't
> apply at all in this case, because nothing was transferred. That would make
> Last an uninitialized variable in that case: certainly a result we don't
> want.

I now agree with you that the wording needs fixing.  I would rewrite
it in the spirit of the wording used for Get_Line -- A.10.7(20).

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

From: Tucker Taft
Sent: Wednesday, March 15, 2000 2:45 PM

Randy Brukardt wrote:
> ...
>
> @drepl
> The Read operation transfers Item'Length stream elements from the
> specified stream to fill the array Item. The index of the last stream
> element transferred is returned in Last. Last is less than Item'Last only if
> the end of the stream is reached.
> @dby
> The Read operation transfers Item'Length stream elements from the
> specified stream to fill the array Item. If elements are transferred, the
> index
> of the last stream element transferred is returned in Last. If no elements
> are
> transferred, Item'First - 1 is returned in Last. Last is less than Item'Last
> only if the end of the stream is reached.

This wording still seems a little screwy.  Why say "the Read
operation transfers Item'Length..." and then immediately thereafter
contradict it?   We should indicate that it transfers Item'Length
stream elements, or all of the stream elements until the end of the stream,
whichever is less.

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

From: Randy Brukardt
Sent: Wednesday, March 15, 2000 7:34 PM

I of course made the minimum change. I have no objection to a more sweeping
change.

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


Questions? Ask the ACAA Technical Agent