Version 1.6 of ais/ai-00132.txt
!standard 13.13.01 (08) 99-10-08 AI95-00132/05
!standard 13.13.02 (35)
!standard A.13 (13)
!class binding interpretation 96-09-05
!status Corrigendum 2000 99-07-28
!status WG9 approved 98-06-12
!status ARG Approved 11-0-1 97-11-14
!status ARG Approved (subject to letter ballot) 10-0-1 96-10-07
!status work item 96-05-07
!status received 96-04-17
!priority High
!difficulty Hard
!qualifier Omission
!subject Exception Raised at End of Stream
!summary
If the default version of T'Read (for some type T) is used to read from
a stream, then if end of stream is encountered, End_Error is raised.
!question
Suppose one gets a stream from the Ada.Text_IO.Streams.Stream function.
(The same question applies to the Wide_Text_IO version, and also to
streams created by Ada.Streams.Stream_IO.)
What happens if the stream's position corresponds to end-of-file, and
one tries to get an item using the default version of T'Read, for some
type T? Is Data_Error or End_Error raised? Can the result be abnormal?
!recommendation
(See summary.)
!wording
(See corrigendum.)
!discussion
13.13.1(8) admits to the notion of "end of stream" for stream types:
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.
The Read operation does not raise any exception at end of stream -- it
just indicates this fact in the value returned in Last. Of course, what
constitutes end of stream is defined by the particular stream type. The
stream returned by Text_Streams.Stream has a notion of end of stream
that corresponds to the end of the text file. A user-defined stream
might have a different notion of end of stream, or might not have any
such notion -- it is quite possible to implement a stream type that
represents an infinitely long sequence.
So the question is, if a given stream type has a notion of end of
stream, then what happens when T'Read hits the end (which it can detect
by looking at the Last parameter returned by Streams.Read)?
A.13(13,17) say:
13 The exception Data_Error can be propagated by the procedure Read (or by
the Read attribute) if the element read cannot be interpreted as a value of
the required subtype. ...
17 If the element read by the procedure Read (or by the Read attribute)
cannot be interpreted as a value of the required subtype, but this is not
detected and Data_Error is not propagated, then the resulting value can be
abnormal, and subsequent references to the value can lead to erroneous
execution, as explained in 13.9.1.
Note that it is somewhat odd that the Read attribute can raise
IO_Exceptions.Data_Error, since streams have nothing directly to do with
I/O, and a given invocation of the Read attribute does not know whether
it is dealing with a file stream or not. Nonetheless, that's what it
says. Raising IO_Exceptions.End_Error is no more or less odd in this
regard.
Possibilities are:
Alternative 1: Either Data_Error is raised, or an abnormal value is
returned. This alternative is supported by the wording of A.13(13,17).
Reading zero bytes, or an insufficient number of bytes, clearly gives a
malformed piece of data. The programmer is forced to encode the number
of elements in the stream somehow, or otherwise encode the end of stream
explicitly, in order to avoid erroneous execution.
Alternative 2: Data_Error is raised. There is really no implementation
reason for allowing abnormal values, since the condition is easy to
detect, and a very minor efficiency hit. However, this alternative
still forces the programmer to encode the end of stream by hand, since
Data_Error does not distinguish between malformed data and end of
stream.
Alternative 3: End_Error is raised. This alternative still forces the
programmer to encode the end of stream by hand, because it does not
distinguish between encountering the end of the stream in between stream
elements, versus in the middle of an element -- the latter being a case
of malformed data.
Alternative 4: End_Error is raised if the programmer calls the T'Read
and the stream is at end of stream. However, Data_Error is raised if
end of stream is encountered in the middle. This allows the programmer
to reliably read a sequence of items from a stream, and notice when the
last item has been read (by detecting End_Error), and distinguish this
situation from malformed data (Data_Error). Thus, the programmer does
not need to add extra data to the stream to explicitly encode end of
stream. However, this alternative is harder to implement, since Read
attributes are highly recursive. For example, suppose T is a record
type with two components. If 'Read raises End_Error on the second
component, T'Read must catch that exception, and turn it into Data_Error
-- the second component wasn't malformed, but the record as a whole is
malformed. On the other hand, an End_Error raised by reading the first
component would simply be propagated by T'Read. In addition, if the
user-defined overriding of the Read attribute would presumably want to
mimic this behavior. (Note: AI83-00307 requires a similar behavior for
Get procedures in Text_IO.)
Alternatives 5,6,7,8: Same as alternatives 1,2,3,4, but define
Data_Error and/or End_Error exceptions in Streams, rather than using the
ones from IO_Exceptions. This might be more elegant, but serves no
practical purpose, and is too big a change to make at this point.
We choose Alternative 3, because it seems the friendliest alternative
that has a reasonable implementation cost.
The programmer can reliably detect end-of-file for file streams as
follows:
if not End_Of_File(An_Input_File) then
T'Read(Stream(An_Input_File), Value);
...
!corrigendum 13.13.2(35)
Insert after the paragraph:
In the default implementation of Read and Input for a composite type,
for each scalar component that is a discriminant or whose
component_declaration includes a default_expression, a check is
made that the value
returned by Read for the component belongs to its subtype. Constraint_Error
is raised if this check fails. For other scalar components, no check is
made. For each component that is of an access type, if the implementation
can detect that the value returned by Read for the component is not a value
of its subtype, Constraint_Error is raised. If the value is not a value of
its subtype and this error is not detected, the component has an abnormal
value, and erroneous execution can result (see 13.9.1).
the new paragraph:
In the default implementation of Read and Input for a type, End_Error
is raised if the end of the stream is reached before the reading of a value of
the type is completed.
!ACATS test
Create a C-Test to test this using Ada.Text_IO.Streams.
!appendix
!section A.13(13)
!subject Exception raised at end of text stream
!reference RM95-A.13(13)
!from Bob Duff
!reference 96-5491.a Robert A Duff 96-4-16>>
!discussion
Robert Dewar requested a (confirmation) AI on this topic:
Suppose I get a stream from the Ada.Text_IO.Streams.Stream function.
(The same question applies to the Wide_Text_IO version, and also to
streams created by Ada.Streams.Stream_IO.)
What happens if the stream's position corresponds to end-of-file, and I
try to get an item using T'Read, for some type T? Is End_Error
raised?
The answer is, no, End_Error is not raised. Data_Error is raised,
or else a junk value is returned.
A.13(13,17) say:
13 The exception Data_Error can be propagated by the procedure Read (or by
the Read attribute) if the element read cannot be interpreted as a value of
the required subtype. ...
17 If the element read by the procedure Read (or by the Read attribute)
cannot be interpreted as a value of the required subtype, but this is not
detected and Data_Error is not propagated, then the resulting value can be
abnormal, and subsequent references to the value can lead to erroneous
execution, as explained in 13.9.1.
The Read procedure in package Streams clearly does not raise any
exception on end-of-stream -- if there are no more bytes available, it
returns a count of zero bytes read. So, clearly, if you're trying to
interpret zero bytes as something-or-other, you'll get Data_Error.
Alternatively, the implementation might take advantage of A.13(17), and
return junk. The Read attribute doesn't know that it's reading from a
file -- it's reading from a stream, which may or may not have a concept
of "end of stream".
Robert points out that it is somewhat strange to raise Data_Error, since
streams having nothing in particular to do with I/O. True; perhaps the
Streams package ought to have defined its own exceptions, but it's too
late for that now. I certainly see no reason to raise End_Error. A
user-defined stream can raise whatever exceptions it likes, but the
language-defined ones should not, and the Read attribute needs to work
in the presence of any stream.
- Bob
****************************************************************
!section A.13(13)
!subject Exception raised at end of text stream
!reference AI95-00132/00
!reference 96-5491.a Robert A Duff 96-4-16
!from Norman Cohen 96-04-29
!reference 96-5527.a Norman H. Cohen 96-4-29>>
!discussion
The !response states:
> The Read attribute doesn't know that it's reading from a file -- it's
> reading from a stream, which may or may not have a concept of "end of
> stream".
This is a red herring. The proposed behavior is to raise Data_Error,
which is every bit as much an I/O exception as End_Error. There is a
long ;-) tradition of stream operations raising I/O exceptions, thus
exposing the fact that a particular stream happens to be a file:
AI95-00001/02 (approved November 1995 subject to letter ballot) says that
Status_Error encountered when reading or writing the underlying file is
passed to the caller of Ada.Streams.Read or Ada.Streams.Write.
(By 13.13.1(1), predefined versions of 'Read call Ada.Streams.Read, and
thus presumably propagate Status_Error.)
The implicit model in AI95-132/00 is that T'Read has, in effect, the
following body:
procedure T'Read
(Stream: access Root_Stream_Type'Class; Item: out T) is
subtype Item_Elements_Subtype is
Stream_Element_Array (1 .. Item'Length_In_Stream_Elements);
Elements: Item_Elements_Subtype;
function Elements_To_T is
new Ada.Unchecked_Conversion (Elements, T);
pragma Wave_Hands (About => "T'Size if T is unconstrained");
begin
Ada.Streams.Read (Stream.all, Elements, Length);
Item := Elements_To_T (Elements);
end T'Read;
('Length_In_Stream_Elements is an implementation-defined attribute and
Wave_Hands is an implementation-defined pragma. ;-) )
At end-of-file, Ada.Streams.Read fills in 0 or more, but not all, of the
components of Elements and sets Length to a value less than
Elements'Length. (According to 13.13.1(8), this is the only circumstance
in which Length can be given a value less than Elements'Length.) The
call on Elements_To_T then does an unchecked conversion of the ENTIRE
array Elements, including the array components that were left undefined,
leaving garbage in Item.
The draft AI asserts that A.13(17) applies:
If the element read by the procedure Read (or by the Read attribute)
cannot be interpreted as a value of the required subtype, but this is
not detected and Data_Error is not propagated, then the resulting
value can be abnormal, and subsequent references to the value can lead
to erroneous execution, as explained in 13.9.1.
In fact, this particular error can always be detected by examining the
value Ada.Streams.Read leaves in Length, so there is no excuse for
inviting erroneous execution by failing to raise the exception. That is,
the body of T'Read should be, in effect:
procedure T'Read
(Stream: access Root_Stream_Type'Class; Item: out T) is
...
begin
Ada.Streams.Read (Stream.all, Elements, Length);
if Length = Elements'Length then
Item := Elements_To_T (Elements);
else
raise Data_Error;
end if;
end T'Read;
But is Data_Error the right exception? In 96-5491.a, Bob Duff writes:
> Robert points out that it is somewhat strange to raise Data_Error,
> since streams having nothing in particular to do with I/O. True;
> perhaps the Streams package ought to have defined its own exceptions,
> but it's too late for that now.
I think we may have no choice. Consider the case in which a
memory-buffer stream is written as a value of one type and read as an
invalid value of some other type. If the error is detected, the same
exception ought to be raised in this case, in which there are no files
anywhere in sight and IO_Exceptions.Data_Error seems even less
justifiable. The longer we hesitate in defining a new exception
Ada.Streams.Data_Error (or Ada.Streams.Exceptions.Data_Error), the more
too late it becomes. ;-)
(P.S.--The AI should also mention T'Input.)
****************************************************************
Questions? Ask the ACAA Technical Agent