TurboVision Bug Listing 10/29/92 The following is an *informal* bug list for Borland C++ / TurboVision. The list is maintained strictly as a service to other forum members; I do not receive any compensation for this. All information is supplied on an "as-is" basis -- I make no guarantees that the problems or solutions are accurate. Wherever possible, I have checked whether or not the bugs still exist in Borland C++ 3.1 (I never remember the A/F version numbers). When I refer a bug being demonstrated or confirmed by someone, I mean simply that I read their forum message to that effect. I apologize in advance if I have misread or misquoted anyone. The list contains bugs that I have found while perusing the forum, and is somewhat slanted to TurboVision and C++ (I don't use OWL or Templates yet, so I haven't been paying attention to them as much). If you send me bugs in these areas, I will add them to the list. If you have any corrections or additions to this list, please send them to me and I will add them to the list. Hope this list helps. -- Ken Vogel 74007,564 -- ------------------------------------------------------------------ Id: KV-1 Location: TurboVision Problem: Unfreed memory when TStreamable descendent object pointers are written to an ofpstream. Version: Exists in BC 3.0 Appears to be fixed in 3.1 (although I have not tested memory counters to verify that there is no leak, as Borland made other changes to this file as well) Proposed Solution: The objs member of ofpstream contains pointers to TPWObj elements, which are created while the stream is written. However, when the stream is deleted, the TV code sets the shouldDelete member of objs to False, preventing the TPWObj members from being properly deleted. **** TOBJSTRM.CPP Line 99 ==== OLD ==== TStreamableTypes::~TStreamableTypes() { shouldDelete = False; } ===== NEW ==== TStreamableTypes::~TStreamableTypes() { } **** TOBJSTRM.CPP Line 512 ==== OLD ==== opstream::~opstream() { objs->shouldDelete = False; objs->shutDown(); delete objs; } ===== NEW ==== opstream::~opstream() { objs->destroy (objs); } *** END *** ------------------------------------------------------------------ Id: KV-2 Location: TurboVision Problem: TFileDialog does not correctly read subdirectories. Version: Exists in BC 3.0, fixed in 3.1 Proposed Solution: A call to fnmerge incorrectly uses *.* as the extension when building the directory mask. In BC 3.0, the fnmerge no longer accepts this. **** TFILLIST.CPP Line 170 ==== OLD ==== fnmerge( path, drive, dir, file, "*.*" ); ===== NEW ==== fnmerge( path, drive, dir, "*", ".*" ); ------------------------------------------------------------------ Id: KV-3 Location: TurboVision Problem: TGroup::valid is not re-entrant. If you have multiple windows on your desktop, and wish to display a message during your valid method in one of the windows, other windows will receive an incorrect command parameter to their valid. Version: Exists in BC 3.0, and in 3.1 Proposed Solution: TGROUP uses a global static variable to pass the current command to the iterator function isInvalid. Pass this parameter via a pointer instead. **** TGROUP.CPP Line 503 ==== OLD ==== static ushort cmd; Boolean isInvalid( TView *p, void * ) { return Boolean( !p->valid( cmd ) ); } Boolean TGroup::valid( ushort command ) { cmd = command; return Boolean( firstThat( isInvalid, 0 ) == 0 ); } ===== NEW ==== Boolean isInvalid( TView *p, void *commandP ) { return Boolean( !p->valid( *(ushort *)commandP ) ); } Boolean TGroup::valid( ushort command ) { return Boolean( firstThat( isInvalid, &command ) == 0 ); } ------------------------------------------------------------------ Id: KV-4 Location: TurboVision Problem: Writing duplicate pointers to descendants of TStreamable streams may writes out a new copy of the pointed to objects each time. Occurs only if pointers to two or more different classes are written to a stream. Version: Exists in BC 3.0, fixed in 3.1 Proposed Solution: The list of pointers which have been written to the stream is maintained in a descendent of TNSSortedCollection by ofpstream. This descendent incorrectly performs a pointer comparison when searching to see if a pointer has already been written to the stream. In some conditions, this causes the search to terminate before the pointer is found, thus causing the same object to be written twice. **** TOBJSTRM.CPP Line 123 ==== OLD ==== int TPWrittenObjects::compare( void *o1, void *o2 ) { if( o1 == o2 ) return 0; else if( o1 < o2 ) return -1; else return 1; } ===== NEW ==== int TPWrittenObjects::compare( void *o1, void *o2 ) { if( o1 == o2 ) return 0; else if( (long)o1 < (long)o2 ) return -1; else return 1; } ------------------------------------------------------------------ Id: KV-5 Location: TurboVision Problem: Directories with names less than two characters in length are not displayed in a TFileList (or TFileDialog) Version: Exists in BC 3.0, and in 3.1 Proposed Solution: For some reason, TFileList elimates directories whose length is less than or equal to four. **** TFILLIST.CPP Line 187 ==== OLD ==== if( strlen( dir ) > 4 ) { ===== NEW ==== if( strlen( dir ) > 1 ) { ------------------------------------------------------------------ Id: KV-6 Location: Compiler optimization Problem: (Sorry, I don't have the name of the person who originated this message. Thanks, whoever you are. --KV--) Version: Exists in BC 3.0. Seems to be fixed in 3.1 This code demonstrates the problem (with loop optimizer ON) int main() { for( int i = 10; i>=0; i--) cout << (10-i) << '\n'; return 0; } The program output should be "0,1,2,...,10". Instead, the output is "0,-1,-2,...,-10". The bug is in the loop optimizer. The compiler introduces an induction variable to replace the expression (10-i), which it stores in register DI. The compiler correctly initializes DI to 0, but in the iteration step it *decrements* DI when it should *increment* it. Looks like you tripped over the "double negative." Proposed Solution: Don't make this code construct, or turn off loop optimization for those sections of the code which use constructs similar to this one. ------------------------------------------------------------------ Id: KV-7 Location: Compiler Optimization Version: Exists in BC 3.0. I haven't tested 3.1 Problem: The -Ob (dead code) optimization and multiple floating point assignments causes valid store to be eliminated. Example: a = b = 3.0; If a was not used, the store to b would also be eliminated Relayed in a message from Jeff Stock, originally posted by Mel Corey. Proposed Solution: Disable -Ob for dead assignments, eliminate unused variables, or store unused variables separately. ------------------------------------------------------------------ Id: KV-8 Location: Compiler Version: Exists in BC 3.0. I haven't tested 3.1 Problem: A for loop with a constant test expression that evaluates to 0 has its body executed once. (I'm not sure if this is related to any optimizations). Example: for( ;0; ) { /*this executes once*/ } I took this from a message by Mark Sidell. Proposed Solution: Eliminate the loop, or use a variable in the condition. ------------------------------------------------------------------ Id: KV-9 Location: C++ Compiler Version: Exists in BC 3.0. Fixed in 3.1 Problem: Temporary destructor is not called in certain situations. Thanks to a forum message by Mark Sidell for this example (confirmed by Eric Nagler). The following program demonstrates what appears to be a bug in BCC 3.0. The Der ctor creates a temporary Item and passes its address to the base ctor. But, the temporary Item is never destroyed! #include struct Item { Item() { cout << "Item ctor\n"; } ~Item() { cout << "Item dtor\n"; } }; struct Base { Base( Item *) {} }; struct Der : Base { // Yikes! The temporary Item is never destroyed. Der() : Base( &Item()) {} }; int main() { Der oDer; return 0; } ------------------------------------------------------------------ Id: KV-10 Location: VROOMing TurboVision programs (and potentially others) Version: Exists in BC 3.0 and in 3.1 Problem: The linker warning: No stub for fixup at _DATA:xxxx in module yyyy.CPP Is reported for certain VROOMed modules. These modules do not execute correctly. Proposed Solution: The problem seems to be related to incorrect code produced for certain virtual tables. Only modules which inherit certain classes or constructors develop the problem. The solution (taken from the TurboVision make file) is to compile these modules with the -B and -Vs options (compile via assembly and local virtual tables). Note that this requires more memory, and may not be possible in the IDE, thus forcing you to a make file. Personally, I don't VROOM these modules until I'm ready for a release, when I do it with a make file. However, if you have enough modules, you will notice that you still get the 'No stub' error even though you compiled it -Vs -B. First, try it *without* the -Vs -B (still overlaid). Oddly enough, some modules work this way. Unfortunately, some modules don't work in either compilation option. Your only option (as far as I know) is to leave those ones non-overlaid. If you find a way to overlay all modules, **please send me mail**. It would really be a big help to me. ------------------------------------------------------------------ Id: KV-11 Location: Compiler Version: Exists in BC 3.0. Fixed in 3.1 Problem: The compiler generates bad code for 1, 2 and 4 byte array initializers. Thanks to TechMate, Inc. for the following simple example (I took it from one of their forum msgs). Jeff Stock of Borland confirmed that it was a problem. #pragma inline void main() { char x[4] = "bug"; }; When compiled with the following command line: bcc -ml x.cpp It generates assembly code with this error: Assembling file: x.ASM **Error** x.ASM(45) Too few operands to instruction Error messages: 1 Warning messages: None Passes: 1 Remaining memory: 378k Proposed Solution: Don't use 1, 2 or 4 byte arrays (use other sizes)? ------------------------------------------------------------------ Id: KV-12 Location: Compiler Version: Exists in BC 3.0. Fixed in 3.1 Problem: Certain shifting operations do not perform correctly with jump optimization. Example: #include long Fct (void) { int a, b; a = 1; b = 0; // try this with and without jump optimization return (((long)a) | (((long)b)<<16)); } main () { long l, Ref; Ref = Fct (); l = ((long)1)<<16; printf ("Long: %lX\n", l); printf ("Return: %lX\n", Ref); return 0; } This was confirmed by Jeff Stock at Borland. Proposed Solution: Turn off jump optimization for those modules that use shifting similar to that shown above. ------------------------------------------------------------------ Id: KV-13 Location: TV Version: Exists in BC 3.0 and in 3.1 (although TOBJSTRM has changed in 3.1, so my original line numbers don't quite work). Problem: The TurboVision stream manager (contained in TOBJSTRM) is very unstable if the file you read is corrupted for any reason. There are several calls to assert(), which does not uninstall the mouse interrupt when exiting, as well as some possible null pointer writing if there are problems. Also, If you try to read an object whose class is not registered with the stream manager (or a corrupt file), this crashes your application. Proposed Solution: I have modified TOBJSTRM to set the stream error flag when an error occurs. The first step is to replace all occurances of assert() with the following two macros: ** START ** #define fassert(Condition) if (!(Condition)) \ { setstate(ios::badbit); errno = EINVDAT; } #define foassert(Condition,Obj) if (!(Condition)) \ { (Obj).setstate(ios::badbit); errno = EINVDAT; } ** END ** Next, you should add some additional tests. In particular, the two calls to ipstream::readPrefix, which returns a pointer, do not check if this pointer is NULL (object not found). Change these lines to read something like: **** FROM **** const TStreamableClass *pc = ps.readPrefix(); ps.readData( pc, &t ); ps.readSuffix(); ** END ** ***** TO ****** const TStreamableClass *pc = ps.readPrefix(); foassert (pc != 0, ps); if (pc != 0) { ps.readData( pc, &t ); ps.readSuffix(); } ** END ** Also, add the following to the start of BOTH versions of ipstream::readString: ** START ** if (!good()) return 0; ** END ** Add the following to the start of ipstream& operator >> for TStreamable&: ** START ** if (!ps.good()) return ps; ** END ** Add the following to the start of ipstream& operator >> for void *&: ** START ** if (!ps.good()) { t = 0; return ps; } ** END ** I'm sure there are more areas, but these have served me well. ------------------------------------------------------------------ Id: KV-14 Location: TV Version: Exists in BC 3.0. Appears fixed 3.1 Problem: Your application will crash with an abnormal program termination if you try to do a file dialog in a directory with too many files. Proposed Solution: Interestingly enough, TFILLIST has code to handle too many files. The loop to read a file diligently checks if the pointer (allocated via new) is NULL, and will terminate the loop. However, NEW.CPP provided with TurboVision will *terminate* when no more memory is available (after dipping into the safety pool). TFILLIST should check the Safety Pool (via lowMemory) while it is looping, NOT the pointer itself. In TFileList::readDirectory, change lines 167 and 179 as follows: **** FROM ***** while( p != 0 && res == 0) ***** TO ***** while( p != 0 && res == 0 && !lowMemory()) ***** END ***** Also, change line 214: **** FROM ***** if( p == 0 ) messageBox( tooManyFiles, mfOKButton | mfWarning ); ***** TO ***** if( p == 0 || lowMemory()) messageBox( tooManyFiles, mfOKButton | mfWarning ); ***** END ***** ------------------------------------------------------------------ Id: KV-15 Location: TV Version: Exists in BC 3.0. Appears to still exist in 3.1 (although they fixed some other problems there) Problem: A descendent of TNSCollection which sets the shouldDelete member to false will leave one unfreed pointer if it is destroyed when items are contained within it. The TOBJSTRM module creates just such a collection during a read of a stream containing TStreamable *. Proposed Solution: The problem appears to be as follows. The shutDown function of TNSCollection calls the member function setLimit(0) to free all object pointers. setLimit *never* lets limit fall below count. Thus, if you destroy a TNSCollection with count > 0, the items member of TNSCollection will never be freed. (Note that I'm not talking about the contained pointers, which are obviously the responsibility of the caller in a shouldDelete = False situation). To fix the problem, change line 57 of TCOLLECT.CPP: **** FROM ***** void TNSCollection::shutDown() { if( shouldDelete ) freeAll(); setLimit(0); TObject::shutDown(); } ***** TO ***** void TNSCollection::shutDown() { if( shouldDelete ) freeAll(); else count = 0; setLimit(0); TObject::shutDown(); } ***** END ***** ------------------------------------------------------------------ Id: KV-16 Location: TV Version: Exists in BC 3.0. Appears to still exist in 3.1 (although they fixed some other problems there) Problem: Typing too many characters into the input line of a file dialog will crash the program. Proposed Solution: The problem arises when you call getFileName with a string of size MAXPATH. TFILDLG has a line that can cause the resultant path to be approximately 2 * MAXPATH. To avoid the problem, you can either pass a string of size MAXPATH * 2 into getFileName, OR make the following 2 changes to TFILDLG: **** FROM (Circa line 178) ***** static void trim( char *dest, const char *src ) { while( *src != EOS && isspace( *src ) ) src++; while( *src != EOS && !isspace( *src ) ) *dest++ = *src++; *dest = EOS; } ***** TO ***** static void trim( char *dest, const char *src, int size ) { while( *src != EOS && isspace( *src ) ) src++; while( *src != EOS && !isspace( *src ) && --size > 0) *dest++ = *src++; *dest = EOS; } **** FROM (Circa line 196) ***** trim( s, fileName->data ); if( relativePath( s ) == True ) { strcpy( s, directory ); trim( s + strlen(s), fileName->data ); } ***** TO ***** trim( s, fileName->data, MAXPATH - 1 ); if( relativePath( s ) == True ) { strcpy( s, directory ); trim( s + strlen(s), fileName->data, MAXPATH - 1 - strlen (s) ); } ***** END **** ------------------------------------------------------------------ Id: KV-17 Location: TV (with VROOM) Version: Exists in BC 3.0. Appears to still exist in 3.1 Problem: A VROOM'ed TurboVision application has a very subtle problem which can cause very rare program crashes. Proposed Solution: The problem occurs TEVENT.CPP is compiled with overlays off and standard stack frame off. TEventQueue::suspend (a very short function) compiles into single call to THWMouse::suspend (a VROOMed module, by default) without the standard stack frame. If the overlay manager is invoked when THWMouse is called, it may lose the correct return address to the caller of TEventQueue::suspend (TProgram::suspend). This causes a program crash. The solution is to compile TEVENT.CPP with standard stack frame (-k) Change MAKEFILE in the TVISION\SOURCE directory as follows: **** FROM (Line 218) ***** tevent.obj : tevent.cpp $(BCC) -Y- $&.cpp ***** TO ***** tevent.obj : tevent.cpp $(BCC) -Y- -k $&.cpp ***** END **** ------------------------------------------------------------------ Id: KV-18 Location: TV (with VROOM) Version: Exists in BC 3.0. Appears to still exist in 3.1 Problem: A VROOM'ed TurboVision application crashes when you get a critical error. Proposed Solution: The basic problem is that you must make sure that the overlay manager CANNOT be called in a critical error handler (it uses those hi-numbered DOS function calls). The powers that be at Borland correctly remove SYSERR.CPP and SYSINT.CPP from the overlay list. However, SYSERR.CPP calls the following modules, which must also be removed from the list: tscreen drivers swapst tevent The solution is to compile these modules separately in the TV makefile, and include them NON-overlayed in your program. I create a library with the six non-overlayed files. Creating the library also avoids BC 3.0 nasty habit of relinking every time you run because it can't find the source for the .OBJ files. ------------------------------------------------------------------ Id: KV-19 Location: TV Version: Exists in BC 3.0. Appears to still exist in 3.1 Problem: The critical error handler crashes if it receives errors which are greater than 14 (i.e., it happens on a network, etc.). Proposed Solution: The problem lies in SYSERR.CPP, which blindly assumes that all errors are less than 14 when it indexes its error name array. Simply put a check for the index in the code. To fix the problem, change line 122 of TCOLLECT.CPP: **** FROM ***** sprintf( s, errorString[ errorCode ], drive + 'a' ); ***** TO ***** if (errorCode < sizeof (errorString) / sizeof (*errorString)) sprintf( s, errorString[ errorCode ], drive + 'a' ); else sprintf( s, "Critical error %d", errorCode); ***** END *****