Version 1.3 of ais/ai-00242.txt
!standard B.3.1 (50) 01-10-18 AI95-00242/02
!class binding interpretation 00-10-04
!status ARG Approved 4-0-2 02-02-11
!status work item 00-10-04
!status received 00-10-04
!qualifier Clarification
!priority Medium
!difficulty Medium
!subject Surprise behavior of Update
!summary
Interfaces.C.Strings.Update should support a nul-terminated Chars parameter.
!question
The required functionality of the Interfaces.C.Update procedures can
result in behavior that seems contrary to a programmer's reasonable
expectations. Consider the following example:
with Interfaces.C.Strings;
use Interfaces.C.Strings;
procedure Cstr_Update_Test is
Cs : Chars_Ptr;
S1 : String := "ABCD";
S2 : String := "WXYZ";
begin
Cs := New_String(S1);
Update(Cs, 0, S2);
end Cstr_Update_Test;
Most would expect the result of executing this procedure to be that
the Cs variable would indicate a char* string now containing the
string "WXYZ" with an appended nul.
This is not the case however. The standard requires that the Update procedure
raise an Update_Error in this situation. Is this intended? (No.)
Even when Update does not raise Update_Error, it does not work as expected.
Consider:
procedure Cstr_Update_Test2 is
Cs : Chars_Ptr;
S1 : String := "An not";
S2 : String := "m";
begin
Cs := New_String(S1);
Update(Cs, 1, S2);
end Cstr_Update_Test2;
Update will copy the nul into the Cs string, effectively truncating it. So
Cs will equal "Am" after this code, not "Am not" as expected. Is this
intended? (No.)
!recommendation
(See summary.)
!wording
(See corrigendum.)
!discussion
The Update procedure with a string parameter is defined in terms of the
Update with a Char_Array parameter. The definition uses To_C to create
a nul-terminated string, which lengthens the string by one character.
The Update with a Char_Array parameter does not support the input of a
nul-terminated string that is the same size as the output. Moreover, the
intent (noted in AARM B.3.1(48.a)) is that an Chars parameter containing
nul truncate the Item, and thus passing it a nul-terminated string is bound
to cause trouble.
The problem here is the equivalence adds a nul to the input string. If no
nul is added, the behavior of the Update with a String parameter operates
as expected.
The rather bizarre shortening semantics of the Update with a Char_Array
parameter is clearly intended, as it is noted in the AARM.
The rather bizarre and error-prone shortening semantics of the Update with a
Char_Array parameter is inconsistent with the rest of the standard, as the
other subprograms in Interfaces.C.Strings that take a Char_Array allow an
optionally nul-termination parameter as do the similar subprograms in
Interfaces.C. However, since this semantics is clearly intended (as evidenced
by the AARM note), there is insufficient reason to add a Truncate parameter.
!corrigendum B.3.1(50)
Replace the paragraph:
@indent<Equivalent to Update(Item, Offset, To_C(Str), Check).>
by:
@indent<Equivalent to Update(Item, Offset, To_C(Str, Append_Nul => False),
Check).>
!ACATS test
These cases should be added to an existing ACATS test.
!appendix
From: Criley, Marc A
Sent: Thursday, September 07, 2000 12:49 PM
!topic Misleading behavior of Interfaces.C.Update
!reference RM95-B.3.1(43-50)
!from Marc A. Criley 00-09-07
!keywords Interfaces.C
!discussion
The required functionality of the Interfaces.C.Update procedures can
result in behavior that seems contrary to a programmer's reasonable
expectations. Consider the following example:
with Interfaces.C.Strings;
use Interfaces.C.Strings;
procedure Cstr_Update_Test is
Cs : Chars_Ptr;
S1 : String := "ABCD";
S2 : String := "WXYZ";
begin
Cs := New_String(S1);
Update(Cs, 0, S2);
end Cstr_Update_Test;
Most would expect the result of executing this procedure to be that
the Cs variable would indicate a char* string now containing the
string "WXYZ" with an appended nul.
This is not the case however. RM95 requires that the Update procedure
raise an Update_Error in this situation.
Paragraphs 43, 49, and 50 are provided here for reference:
procedure Update(Item : in chars_ptr; (43)
Offset : in size_t;
Chars : in char_array;
Check : Boolean := True);
procedure Update(Item : in chars_ptr; (49)
Offset : in size_t;
Str : in String;
Check : Boolean := True);
Equivalent to Update(Item, Offset, To_C(Str), Check). (50)
The second definition of Update, utilizing an update string of type
String, requires that it first be converted to a char_array via the
To_C function. The effect of To_C is to place the contents of Str
into a char_array and append a nul. In the example this results in a
5-element char_array ("WXYZ" & nul).
The behavior specified by RM95 then requires that if the Check
parameter is True, then:
If Offset + Chars'Length > Strlen(Item), propagate Update_Error. (46)
In the example, Offset is 0, Chars'Length is 5 ("WXYZ" & Nul), and
StrLen(Item) is 4.
It is at first puzzling to discover that a chars_ptr string with 4
characters cannot be updated/overridden with another string also
containing 4 characters unless the "overflow" Check is disabled.
Possible modifications to RM95 to restore the expected behavior
include:
- Defining the effect of Update (with String) in its own terms, rather
than as an invocation of the prior Update procedure with a converted
parameter.
- Modifying the check to take into account whether Chars is, or is
not, nul-terminated. If it is, then use Strlen(Chars), rather than
Chars'Length, in the calculation and subsequent update.
Marc A. Criley
Software Architect
Lockheed Martin M&DS
(610) 531-7850
****************************************************************
From: Randy Brukardt
Sent: Thursday, September 07, 2000 1:57 PM
> The required functionality of the Interfaces.C.Update procedures can
> result in behavior that seems contrary to a programmer's reasonable
> expectations. ...
Yes, I agree. The basic Update does not support nul-terminated string that
are the same size as the output as input (that's weird by itself), and thus
passing it a nul-terminated string is bound to cause trouble sooner or
later.
The easiest fix is to pass the original Update what it wants:
Replace paragraph 50:
Equivalent to Update(Item, Offset, To_C(Str), Check).
by:
Equivalent to Update(Item, Offset, To_C(Str, Append_Nul => False),
Check).
However, the fact that the original Update does not allow a nul-terminated
"Chars" argument seems weird and inconsistent with the rest of the package.
(All of the other functions in Interfaces.C.Strings take optionally
nul-terminated Char_Arrays).
To modify Marc's example:
with Interfaces.C.Strings;
use Interfaces.C, Interfaces.C.Strings;
procedure Cstr_Update_Test2 is
Cs : Chars_Ptr;
C2 : Char_Array := To_C("WXYZ");
begin
Cs := New_String("ABCD");
Update(Cs, 0, C2);
end Cstr_Update_Test2;
Update also raises Update_Error in this case, even though the "real" parts
of the strings are the same length.
So I think a better solution is to fix the original Update:
Replace 45, 46, 48:
* Let N=Strlen(Item). If Check is True, then:
* If Offset+Chars'Length>N, propagate Update_Error.
* ...
* If Check is False, then processing is as above, but with no check
that Offset+Chars'Length>N.
By:
* Let N=Strlen(Item). Let M=Chars'Length if Chars does not contain nul,
or
the smallest size_t value I such that Chars(I)=nul minus Chars'First.
If
Check is True, then:
* If Offset+M>N, propagate Update_Error.
* ...
* If Check is False, then processing is as above, but with no check
that Offset+M>N.
The clunky wording for para 45 is a parallel with para 28. It's unfortunate
that Interfaces.C doesn't contain a Strlen for Chars_Arrays; that would make
the above a lot easier to write. It does have Is_Nul_Terminated, so it is
odd that Strlen isn't there.
Anyway, it does seem that a fix is in order.
****************************************************************
Questions? Ask the ACAA Technical Agent