!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 does not append a nul to the Str 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 programmers 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 a Chars parameter containing nul truncate the Item, and thus passing it a nul-terminated string is bound to cause trouble. The problem here is that 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 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 optional 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 to Update. !corrigendum B.3.1(50) @drepl @indent @dby @indent 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. ****************************************************************