home *** CD-ROM | disk | FTP | other *** search
/ NetNews Usenet Archive 1993 #3 / NN_1993_3.iso / spool / comp / os / mswindo / programm / tools / 2227 < prev    next >
Encoding:
Text File  |  1993-01-25  |  10.1 KB  |  296 lines

  1. Xref: sparky comp.os.ms-windows.programmer.tools:2227 comp.lang.c++:19879
  2. Path: sparky!uunet!mcsun!news.funet.fi!fuug!kiae!demos!newsserv
  3. From: vb@re.mipt.su (Vassili Bykov)
  4. Newsgroups: comp.os.ms-windows.programmer.tools,comp.windows.ms.programmer,comp.lang.c++
  5. Subject: Bugs in BC++ 3.1 (OWL, BIDS, Startup code)
  6. Date: Mon, 25 Jan 93 14:52:21 +0300
  7. Distribution: world
  8. Organization: Department of Radio Engineering of MIPT
  9. Message-ID: <ABrHzOhGH8@re.mipt.su>
  10. Sender: news-service@newcom.kiae.su
  11. Reply-To: vb@re.mipt.su
  12. Lines: 282
  13.  
  14. Everybody knows nothing is perfect, including programs. I guess
  15. anyone working with BC++ can tell you a story about exotic creatures
  16. he or she have seen in seams and crevices of this wonderful environment.
  17. Here I describe two quite serious bugs -- one in OWL streamable
  18. classes, the other in BIDS library, and one not serious, but quite a
  19. funny one -- in Windows startup code. Those bugs present in BC++ 
  20. version 3.1 and previous.
  21.  
  22. I'm not sure if I'm right posting this report to comp.windows.ms.programmer
  23. and comp.os.ms-windows.programmer.tools, but I haven't found any group
  24. which is more appropriate. Please re-post if you know one. Maybe I should
  25. send bug report to Borland, but I don't know how to reach them by email,
  26. while email is the only way I can use for communications.
  27.  
  28. I would be glad to get any response, suggestions or just a chit-chat from
  29. anybody. Send it to Internet: vb@re.mipt.su.
  30.  
  31. ==========================================
  32. OWL Streamable classes -- both 3.0 and 3.1
  33. ==========================================
  34.  
  35. Using OWL streamable classes I had a problem and tracking it down had lead
  36. to a strange implementation feature of classes ipstream and opstream,
  37. looking much like a bug...
  38.  
  39. ipstream::freadBytes( void far *data, size_t sz ) is implemented in the
  40. following way:
  41.  
  42. // --- quote from OBJSTRM.CPP --- //
  43.  
  44. void ipstream::freadBytes( void far *data, size_t sz )
  45. {
  46.     if (sz > 0)
  47.     {
  48.         char *buf = new char[sz+1];
  49.  
  50.         bp->sgetn( (char *)buf, sz );
  51.         _fstrncpy((char far *)data, buf, sz);
  52.  
  53.         delete buf;
  54.     }
  55. }
  56.  
  57. // --- end of quote --- //
  58.  
  59. According to docs, it "reads the number of bytes specified by sz into the
  60. supplied far buffer (data)".
  61.  
  62. As you can see, the bytes are first read into a temporary local buffer and
  63. then copied into the supplied far buffer. But instead of _fmemcpy, as it
  64. would be natural, the copying is done by _fstrncpy. It means that ONLY
  65. null-terminated strings are handled by this function as it is stated.  Raw
  66. binary data, in a general case, will be transferred only partially, until
  67. the first of '\0's is encountered. However, according to docs (and to the
  68. meaning of words "read bytes") it is unlikely that this function is
  69. intended for reading only null-terminated strings.
  70.  
  71. The bug has a mate (even more dangerous) in opstream::fwriteBytes. Look:
  72.  
  73. // --- quote from OBJSTRM.CPP --- //
  74.  
  75. void opstream::fwriteBytes( const void far *data, size_t sz )
  76. {
  77.     if (sz > 0)
  78.     {
  79.         char *buf = new char[sz+1];
  80.  
  81.         _fstrcpy(buf, (char far *)data);
  82.         bp->sputn( (char *)buf, sz );
  83.  
  84.         delete buf;
  85.     }
  86. }
  87.  
  88. // --- quote end --- //
  89.  
  90. Again, according to the docs, it "writes the specified number of bytes
  91. (sz) from the supplied far buffer (data) to the stream". But the bytes are
  92. first copied into a temporary local buffer with a plain _fstrcpy!  It
  93. doesn't only mean that if there's a '\0' byte somewhere in the middle,
  94. we'll copy only the preceding bytes. Just imagine if we have no '\0' bytes
  95. at all! _fstrcpy will wander through the memory, writing PAST THE END of
  96. the allocated buffer until (the best case) it leaves a segment and causes
  97. 13th exception or (the worst case) it stops on some '\0', possibly having
  98. quietly corrupted some data residing past the allocated buffer. If the
  99. corruption causes a problem later, imagine how you track this bug down to
  100. its origin...
  101.  
  102. So, as you can see, both of these member functions are "partially
  103. functional". They work fine with null-terminated strings, but fail to
  104. work with a raw binary data. To make them do what they should, calls to
  105. _fstrncpy and _fstrcpy should be replaced with _fmemcpy.
  106.  
  107. Their non-far siblings, ipstream::readBytes and opstream::writeBytes work
  108. fine, really reading and writing raw binary data as well as
  109. null-terminated strings.
  110.  
  111. Streamable classes themselves use these functions only in
  112. ipstream::freadString and opstream::fwriteString. Since the data those
  113. functions operate with ARE null-terminated strings, they have no problems.
  114.  
  115. I have discovered this problem in BC++ 3.0, but it is present in 3.1 as
  116. well.
  117.  
  118. ============
  119. BIDS library
  120. ============
  121.  
  122. Namely, there's a problem with member functions forEach, firstThat and
  123. lastThat in classes BI_DequeAsVector, BI_QueueAsVector and their indirect
  124. (BI_I...) counterparts. (Later here only direct versions are discussed,
  125. the problems with indirect ones are similar.)
  126.  
  127. The hierarchy of these classes is as follows:
  128.  
  129. BI_QueueAsVector<T>
  130.     |
  131. BI_DequeAsVector<T>
  132.     |
  133. BI_DequeAsVectorImp<BI_VectorImp<T>,T>
  134.  
  135. Queue and deque are derived from class BI_DequeAsVectorImp<class Vect,
  136. class T>, which implements the behavior of queues/deques as vector.  The
  137. first of its parameters, 'Vect', specifies a class used as a vector to
  138. store objects of type specified by the second parameter, 'T'.
  139.  
  140. In our case BI_DequeAsVectorImp uses member 'data' of type BI_VectorImp<T>
  141. to store a vector of objects of class T. It also has two data members,
  142. 'left' and 'right'. These members are the indices of the two ends of a
  143. deque. To store an object at the left end, BI_DequeAsVectorImp decrements
  144. 'left', wraps it if needed and then stores the object in data[left]. To
  145. store an object at the right end BI_DequeAsVectorImp stores the object in
  146. data[right], then increments 'right', wrapping if needed. Initially left =
  147. 0 and right = 0.
  148.  
  149. If we need to access the contained objects (as we do in member functions
  150. forEach, firstThat and lastThat, implemented in BI_DequeAsVector) we
  151. should take into account the two possibilities:
  152.  
  153. left < right    The contained objects reside between left and right-1:
  154.         left <= obj && obj < right
  155.  
  156. left > right    The contained objects reside at the vector positions
  157.         excluding the range between right-1 and left:
  158.         (0 <= obj && obj < right) || (left <= obj && obj < top)
  159.  
  160. Any of the above possibilities can take place. Nevertheless, all 
  161. BI_DequeAsVector does is:
  162.  
  163. // --- quote from BI_DequeAsVector<T> (DEQUES.H) --- //
  164. // ...
  165. void forEach( void (_FAR *f)(T _FAR &, void _FAR *), void _FAR *args )
  166.     { data.forEach( f, args, left, right ); }
  167.  
  168. T _FAR *firstThat( int (_FAR *f)(const T _FAR &, void _FAR *),
  169.     void _FAR *args ) const
  170.     { return data.firstThat( f, args, left, right ); }
  171.  
  172. T _FAR *lastThat( int (_FAR *f)(const T _FAR &, void _FAR *),
  173.     void _FAR *args ) const
  174.     { return data.lastThat( f, args, left, right ); }
  175. // ...
  176. // --- end of quote --- //
  177.  
  178. Thus, only the first possibility is taken into account. In the second case,
  179. when left > right, we won't iterate at all. The following example 
  180. illustrates the problem:
  181.  
  182. // --- example --- //
  183.  
  184. #include <iostream.h>
  185. #include <queues.h>
  186.  
  187. void PrintInt( int & theInt, void* )
  188.   { cout << theInt << '\n'; }
  189.  
  190. int main()
  191.   {
  192.   BI_QueueAsVector<int> intQueue;    // Queue of ints of default size.
  193.   intQueue.put( 1 );            // Insert a couple
  194.   intQueue.put( 2 );            // of integers.
  195.   intQueue.forEach( PrintInt, NULL );    // Now try to print the contents.
  196.   return 0;
  197.   }
  198.  
  199. // --- end of example --- //
  200.  
  201. We could expect the above program to print
  202. 1
  203. 2
  204.     or
  205. 2
  206. 1
  207. but actually it prints nothing. It happens because initially
  208. BI_QueueAsVector have left == 0 and right == 0. The objects are inserted
  209. at the 'left' side, so 'left' pointer is wrapped to the vector's top and
  210. we have left > right. When we call forEach, it calls
  211. BI_VectorImp<int>::forEach, specifying 'left' as a starting index and
  212. 'right' as ending one. BI_VectorImp<int>::forEach sees that starting index
  213. is greater than the ending and doesn't iterate at all. So PrintInt() never
  214. gets called.
  215.  
  216. To solve the problem, forEach and firstThat, for example, should be
  217. implemented in class BI_DequeAsVector<T> as follows. (Or you can derive a
  218. class from BI_QueueAsVector or BI_DequeAsVector and overload these member
  219. functions with the correct implementations.)
  220.  
  221. // --- correct implementation --- //
  222.  
  223. template <class T>
  224. void BI_DequeAsVector<T>::forEach(void (*f)(T _FAR &, void*), void* args)
  225.   {
  226.   if( left < right )
  227.     data.forEach( f, args, left, right );
  228.   else
  229.     {
  230.     data.forEach( f, args, 0, right );
  231.     data.forEach( f, args, left, data.limit() );
  232.     }
  233.   }
  234.  
  235. template <class T>
  236. T _FAR * BI_DequeAsVector<T>::firstThat(int (*f)(T _FAR &, void*), void* args)
  237.   {
  238.   if( left <= right )
  239.     return data.firstThat( f, args, left, right );
  240.   else
  241.     {
  242.     T _FAR * ret = data.firstThat( f, args, left, data.limit() );
  243.     if( !ret )
  244.       ret = data.firstThat( f, args, 0, right );
  245.     return ret;
  246.     }
  247.   }
  248.  
  249. // --- end of correct implementation --- //
  250.  
  251. ====================
  252. Windows startup code
  253. ====================
  254.  
  255. There's a little bug in startup code for Windows -- both application's and
  256. DLL's. (Files C0W.OBJ and C0D.OBJ, their sources are C0W.ASM and C0D.ASM).
  257.  
  258. The bug is not dangerous and not very important, but it is funny how long
  259. such "childish" bugs can live: I discovered it in Borland C++ 2.0, then
  260. saw it in BC++ 3.0, and later we have met as old good friends in BC++ 3.1
  261.  
  262. The sense is: if you test global variable _8087 to determine if the math
  263. coprocessor is present on the machine your program is running on, you'll
  264. get 0 -- not present -- always, even if the chip is here. The variable is
  265. set by the startup code as follows:
  266.  
  267. ; --- quote from C0W.ASM --- ;
  268.  
  269.                 call    GETWINFLAGS
  270.  
  271. ; ... testing for protected mode, omitted here ...
  272.  
  273. ;Test for 8087 presence
  274.  
  275.                 test    dx,04h          ; WF_8087 = 0x0400
  276.                 jz      @@no8087
  277.                 mov     __8087, 1
  278. @@no8087:
  279.  
  280. ; --- end of quote --- ;
  281.  
  282. GetWinFlags() returns DWORD (i.e. loword in AX, hiword in DX), and WF_8087
  283. is #defined as 0x0400, so we need to
  284.  
  285.     test    ah, 04h
  286. or
  287.     test    ax, 0400h
  288.  
  289. Thus, the code actually tests the returned value against 0x40000. This bit
  290. is currently not used (or, at least, is not documented) and always clear,
  291. so _8087 is always set to 0.
  292.  
  293. (Though I seem there are few people who could be concerned.)
  294.  
  295.  
  296.