NO

Author Topic: _snwprintf broken because it adheres to the broken standard  (Read 4779 times)

severach

  • Guest
_snwprintf broken because it adheres to the broken standard
« on: October 11, 2006, 05:28:43 AM »
Code: [Select]
#ifdef UNICODE
#define TLS _T("l")
#else
#define TLS ""
#endif
  WCHAR str[256];
  _snwprintf(str,NELEM(str),L"%s %s %s",L"ABC",L"DEF",L"GHI"); // result is A D G
  _snwprintf(str,NELEM(str),L"%ls %ls %ls",L"ABC",L"DEF",L"GHI"); // results correct
  TCHAR tstr[256];
  _sntprintf(tstr,NELEM(tstr),_T("%") TLS _T("s %") TLS _T("s %") TLS _T("s"),_T("ABC"),_T("DEF"),_T("GHI"));
  // Must I do it this way because Pelles insists on following a bad standard?


Pelles-C help is ambigious but a randomly selected C Standards Document is clear that the above behavior is according to standard. The trouble with the standard is that extraordinary measures are needed to support <tchar.h> ready source code under Windows. %s functions correctly which is against the standard in every other compiler I  have: MSVC, MinGW, OpenWatcom (see help for wprintf), Borland-C (code tested), and DMC (code tested). Every one broke the standard and Pelles-C should too to be a proper Windows development environment.

It's easy to see why the standard is wrong, not just for Windows but for all systems. Why should %s support any other size than the native character size? On linux should the format string be 32 bit UNICODE and each %s be 8 bit chars? Are those 8-bit Ansi or 8-bit UTF-8? Should %d always be 16 bit just because MS-DOS was a popular programming environment? It seems simple to me: when the native character size changes %s changes with it and let the modifers alter the size for special cases. I've got 5 compilers which encompass 4 separate runtime libraries on my side. Can I have 6 & 5 or must I make my format strings even more unreadable by sprinkling TLS all over or link to a _snwprintf that works correctly?

The second standards issue is that the standard guarantee's that a NUL byte will terminate the ouput but no other Windows compiler does this and Pelles-C in the wild gyrations to try and provide this ends up with a bounds error. Here's a program to test it.
Code: [Select]
#include <wchar.h>
#if defined(__DMC__) || defined(__MINGW32__) || defined(_MSC_VER) || !defined(__POCC__) || defined(__WATCOMC__)
#define _wmemset wmemset
#define _wmemchr wmemchr
#endif

EXTERNC int my_snprintf(CHAR *szDest,size_t cchDest,const CHAR *szFormat,...) {
  va_list ap;
  va_start(ap, szFormat);
  int Result=_vsnprintf(szDest,cchDest,szFormat,ap);
  va_end(ap);
  return Result;
}
EXTERNC int my_snwprintf(WCHAR *szDest,size_t cchDest,const WCHAR *szFormat,...) {
  va_list ap;
  va_start(ap, szFormat);
  int Result=_vsnwprintf(szDest,cchDest,szFormat,ap);
  va_end(ap);
  return Result;
}

EXTERNC void __stdcall sntest(void) {
  FILE *fo=fopen("C:\\sntest.txt","wt");
  if (fo) {
    size_t i;
    for(i=2; i<=6; i++) {
      size_t j;
      {WCHAR szStrW[8];
      _wmemset(szStrW,L'#',NELEM(szStrW)); szStrW[NELEM(szStrW)-1]=L'\0';
      int cchResultW=_snwprintf(szStrW,i,L"ABCD");
      int cchActualW=(WCHAR *)_wmemchr(szStrW,L'#',NELEM(szStrW))-szStrW;
      int cchZeroW=(WCHAR *)_wmemchr(szStrW,0,NELEM(szStrW))-szStrW; if (cchZeroW==NELEM(szStrW)-1) cchZeroW=-1;
      fprintf(fo,"cchResult:%2d= _snwprintf(szStrW,cch=%d,\"ABCD\"); // ZeroIdx=%2d cchActual=%d ",cchResultW,i,cchZeroW,cchActualW);
      for(j=0; j<NELEM(szStrW)-1; j++) fprintf(fo,szStrW[j]<32?"0":"%c",szStrW[j]);
      if (cchActualW>i) fputs(" Bounds Error",fo);
      fputs("\n",fo);
      _wmemset(szStrW,L'#',NELEM(szStrW)); szStrW[NELEM(szStrW)-1]=L'\0';
          cchResultW=my_snwprintf(szStrW,i,L"ABCD");
          cchActualW=(WCHAR *)_wmemchr(szStrW,L'#',NELEM(szStrW))-szStrW;
          cchZeroW=(WCHAR *)_wmemchr(szStrW,0,NELEM(szStrW))-szStrW; if (cchZeroW==NELEM(szStrW)-1) cchZeroW=-1;
      fprintf(fo,"cchResult:%2d=_vsnwprintf(szStrW,cch=%d,\"ABCD\"); // ZeroIdx=%2d cchActual=%d ",cchResultW,i,cchZeroW,cchActualW);
      for(j=0; j<NELEM(szStrW)-1; j++) fprintf(fo,szStrW[j]<32?"0":"%c",szStrW[j]);
      if (cchActualW>i) fputs(" Bounds Error",fo);
      fputs("\n",fo);}
      {CHAR szStrA[8];
      memset(szStrA,'#',NELEM(szStrA)); szStrA[NELEM(szStrA)-1]='\0';
      int cchResultA= _snprintf(szStrA,i,"ABCD");
      int cchActualA=( CHAR *)memchr(szStrA,'#',NELEM(szStrA))-szStrA;
      int cchZeroA=( CHAR *)memchr(szStrA,0,NELEM(szStrA))-szStrA; if (cchZeroA==NELEM(szStrA)-1) cchZeroA=-1;
      fprintf(fo,"cchResult:%2d=  _snprintf(szStrA,cch=%d,\"ABCD\"); // ZeroIdx=%2d cchActual=%d ",cchResultA,i,cchZeroA,cchActualA);
      for(j=0; j<NELEM(szStrA)-1; j++) fprintf(fo,szStrA[j]<32?"0":"%c",szStrA[j]);
      if (cchActualA>i) fputs(" Bounds Error",fo);
      fputs("\n",fo);
      memset(szStrA,'#',NELEM(szStrA)); szStrA[NELEM(szStrA)-1]='\0';
          cchResultA=my_snprintf(szStrA,i,"ABCD");
          cchActualA=( CHAR *)memchr(szStrA,'#',NELEM(szStrA))-szStrA;
          cchZeroA=( CHAR *)memchr(szStrA,0,NELEM(szStrA))-szStrA; if (cchZeroA==NELEM(szStrA)-1) cchZeroA=-1;
      fprintf(fo,"cchResult:%2d= _vsnprintf(szStrA,cch=%d,\"ABCD\"); // ZeroIdx=%2d cchActual=%d ",cchResultA,i,cchZeroA,cchActualA);
      for(j=0; j<NELEM(szStrA)-1; j++) fprintf(fo,szStrA[j]<32?"0":"%c",szStrA[j]);
      if (cchActualA>i) fputs(" Bounds Error",fo);
      fputs("\n",fo);}
      fputs("\n",fo);
    }
    fclose(fo);
  }
}


I don't think I need to describe the multitude of broken things in Pelles-C output:
Code: [Select]

cchResult:-1= _snwprintf(szStrW,cch=2,"ABCD"); // ZeroIdx=-1 cchActual=2 AB#####
cchResult: 4=_vsnwprintf(szStrW,cch=2,"ABCD"); // ZeroIdx= 1 cchActual=2 A0#####
cchResult: 4=  _snprintf(szStrA,cch=2,"ABCD"); // ZeroIdx= 1 cchActual=2 A0#####
cchResult: 4= _vsnprintf(szStrA,cch=2,"ABCD"); // ZeroIdx= 1 cchActual=2 A0#####

cchResult:-1= _snwprintf(szStrW,cch=3,"ABCD"); // ZeroIdx=-1 cchActual=3 ABC####
cchResult: 4=_vsnwprintf(szStrW,cch=3,"ABCD"); // ZeroIdx= 2 cchActual=3 AB0####
cchResult: 4=  _snprintf(szStrA,cch=3,"ABCD"); // ZeroIdx= 2 cchActual=3 AB0####
cchResult: 4= _vsnprintf(szStrA,cch=3,"ABCD"); // ZeroIdx= 2 cchActual=3 AB0####

cchResult: 4= _snwprintf(szStrW,cch=4,"ABCD"); // ZeroIdx= 4 cchActual=5 ABCD0## Bounds Error
cchResult: 4=_vsnwprintf(szStrW,cch=4,"ABCD"); // ZeroIdx= 3 cchActual=4 ABC0###
cchResult: 4=  _snprintf(szStrA,cch=4,"ABCD"); // ZeroIdx= 3 cchActual=4 ABC0###
cchResult: 4= _vsnprintf(szStrA,cch=4,"ABCD"); // ZeroIdx= 3 cchActual=4 ABC0###

cchResult: 4= _snwprintf(szStrW,cch=5,"ABCD"); // ZeroIdx= 4 cchActual=5 ABCD0##
cchResult: 4=_vsnwprintf(szStrW,cch=5,"ABCD"); // ZeroIdx= 4 cchActual=5 ABCD0##
cchResult: 4=  _snprintf(szStrA,cch=5,"ABCD"); // ZeroIdx= 4 cchActual=5 ABCD0##
cchResult: 4= _vsnprintf(szStrA,cch=5,"ABCD"); // ZeroIdx= 4 cchActual=5 ABCD0##

cchResult: 4= _snwprintf(szStrW,cch=6,"ABCD"); // ZeroIdx= 4 cchActual=5 ABCD0##
cchResult: 4=_vsnwprintf(szStrW,cch=6,"ABCD"); // ZeroIdx= 4 cchActual=5 ABCD0##
cchResult: 4=  _snprintf(szStrA,cch=6,"ABCD"); // ZeroIdx= 4 cchActual=5 ABCD0##
cchResult: 4= _vsnprintf(szStrA,cch=6,"ABCD"); // ZeroIdx= 4 cchActual=5 ABCD0##


Correct output from Borland, MinGW, MSVC, DMC, and WC:
Code: [Select]
cchResult:-1= _snwprintf(szStrW,cch=2,"ABCD"); // ZeroIdx=-1 cchActual=2 AB#####
cchResult:-1=_vsnwprintf(szStrW,cch=2,"ABCD"); // ZeroIdx=-1 cchActual=2 AB#####
cchResult:-1=  _snprintf(szStrA,cch=2,"ABCD"); // ZeroIdx=-1 cchActual=2 AB#####
cchResult:-1= _vsnprintf(szStrA,cch=2,"ABCD"); // ZeroIdx=-1 cchActual=2 AB#####

cchResult:-1= _snwprintf(szStrW,cch=3,"ABCD"); // ZeroIdx=-1 cchActual=3 ABC####
cchResult:-1=_vsnwprintf(szStrW,cch=3,"ABCD"); // ZeroIdx=-1 cchActual=3 ABC####
cchResult:-1=  _snprintf(szStrA,cch=3,"ABCD"); // ZeroIdx=-1 cchActual=3 ABC####
cchResult:-1= _vsnprintf(szStrA,cch=3,"ABCD"); // ZeroIdx=-1 cchActual=3 ABC####

cchResult: 4= _snwprintf(szStrW,cch=4,"ABCD"); // ZeroIdx=-1 cchActual=4 ABCD###
cchResult: 4=_vsnwprintf(szStrW,cch=4,"ABCD"); // ZeroIdx=-1 cchActual=4 ABCD###
cchResult: 4=  _snprintf(szStrA,cch=4,"ABCD"); // ZeroIdx=-1 cchActual=4 ABCD###
cchResult: 4= _vsnprintf(szStrA,cch=4,"ABCD"); // ZeroIdx=-1 cchActual=4 ABCD###

cchResult: 4= _snwprintf(szStrW,cch=5,"ABCD"); // ZeroIdx= 4 cchActual=5 ABCD0##
cchResult: 4=_vsnwprintf(szStrW,cch=5,"ABCD"); // ZeroIdx= 4 cchActual=5 ABCD0##
cchResult: 4=  _snprintf(szStrA,cch=5,"ABCD"); // ZeroIdx= 4 cchActual=5 ABCD0##
cchResult: 4= _vsnprintf(szStrA,cch=5,"ABCD"); // ZeroIdx= 4 cchActual=5 ABCD0##

cchResult: 4= _snwprintf(szStrW,cch=6,"ABCD"); // ZeroIdx= 4 cchActual=5 ABCD0##
cchResult: 4=_vsnwprintf(szStrW,cch=6,"ABCD"); // ZeroIdx= 4 cchActual=5 ABCD0##
cchResult: 4=  _snprintf(szStrA,cch=6,"ABCD"); // ZeroIdx= 4 cchActual=5 ABCD0##
cchResult: 4= _vsnprintf(szStrA,cch=6,"ABCD"); // ZeroIdx= 4 cchActual=5 ABCD0##

severach

  • Guest
_snwprintf broken because it adheres to the broken standard
« Reply #1 on: October 24, 2006, 06:59:24 AM »
http://bugzilla.openwatcom.org/show_bug.cgi?id=662

A long discussion at Watcom and this problem is figured out. The standard only specifies %s==8 bit char for snprintf. snwprintf is not included in the standard so there is no requirement that %s==8 bit. This means that %s should be changed to 16 bit for Win32 because that is what matches the standard for the platform.

The standard isn't broken. Pelles is conforming to something that is neither a C99 standard nor a Win32 standard and is using the wrong function names to do it.

snprintf() and snwprintf() are C99 functions that handle the output and provide return values as described in the help. They are incorrectly described and implemented as _snprintf and _snwprintf. The %s handling for the wide function is incorrect.

_snprintf() and _snwprintf() are vendor implementations. The functions in the library that carry these names attempt to conform to C99 and not to the Windows implementation they should.

_snprintf() and _snwprintf() need to be renamed to snprintf() and snwprintf. The proper Win32 _snprintf() and _snwprintf() need to be implemented. All wide printf functions must treat %s as wchar_t. The buffer overruns must be fixed.

JohnF

  • Guest
_snwprintf broken because it adheres to the broken standard
« Reply #2 on: October 24, 2006, 05:27:02 PM »
If you are asking Pelle to introduce a non-standard feature with respect to %s and %ls I think you will not be satisfied.

It is customary to comply with to the standard even if one does not agree with a particular aspect.

John

severach

  • Guest
_snwprintf broken because it adheres to the broken standard
« Reply #3 on: October 24, 2006, 09:54:28 PM »
Apparently I wasn't clear enough and you haven't read the other thread or the relevant portions of the supplied standards document.

The v functions fail to conform to their non v counterparts. _snprintf() fails to comply and _snwprintf() fails to conform to the C99 standard because the names are incorrect and because the Win32 standard for those names is different, they fail to conform to the Win32 standards for their names. If _snprintf() were renamed to snprintf() it would conform to the C99 standard. If _snwprintf() were renamed to snwprintf() and %s with no modifiers expected wchar_t it would conform to the Win32 standard that most closely resembles the C99 standard for its related function snprintf(). The actual functions must be renamed. Macros cannot be used because _snprintf and _snwprintf are required for the Win32 standard.

When that's done the proper _snprintf() and _snwprintf() need to be implemented that conform to the Win32 standards for result string and return values. This is all quite easy since one pair of internal s..printf functions can supply results that the standard functions can modify to be standards compliant.

>If you are asking Pelle to introduce a non-standard feature

Pelles thinks he's complying with standards but he's not. I'm asking Pelle to comply with the C99 standard and conform to the Win32 standard and I'm demonstrating what must be changed to comply and conform. He is in complete non compliance at this time and the buffer overrun makes the functions too dangerous to even use.

>It is customary to comply with to the standard even if one does not agree with a particular aspect.

In Borland, all 4 s..printf functions comply and conform though the tchar macros are incorrect which I can handle because it only requires a few #define's to fix. In the other compilers at least one pair of compliant functions is available. In Pelles, no s..printf function complies with or conforms to any current standard. I disagreed at first but only because there's so much confusion resulting from broken implemenations but now that I have figured it all out, I find the C99 style functions without underscores are the most useful. Unfortunately they are only available in Borland. With a little pressure I hope to get complete conformance to C99 and Win32 in Watcom, Pelles, and later I'll go after DMC.

In short all ..s..printf functions are broken in Pelles and need to be fixed to conform to any standard.