!standard 13.13.01 (08) 00-07-12 AI95-00132/06 !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: 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. ... 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); ... end if; !corrigendum 13.13.2(35) @dinsa In the default implementation of Read and Input for a composite type, for each scalar component that is a discriminant or whose @fa includes a @fa, 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). @dinst 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 The test CDD2001 was constructed to check this rule. (Test, 7-0-1, ARG Letter Ballot, February 2001). !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.) ****************************************************************