[comp.lang.c++] Probably bug in Turbo C++ - want verification

ahodgson@athena.mit.edu (Antony Hodgson) (01/31/91)

/*  TEST.CPP:   This code attempts to read all subdirectories listed
                in the current directory and write out a message any
                time it finds one that's not the current directory (".").

                There is a subtle bug in this code, however.  If we try to
                perform the test:

                    if ( (F->IsSubdirectory()) && !(F->Name() == ".") )

                where F is a pointer to a file descriptor, then we often,
                but unfortunately unpredictably, get a null pointer
                assignment message which manifests itself as an overwrite
                of the Turbo C++ message stored at offset 4.  This can
                be seen by monitoring (char*)4 with the debugger.  The
                program may have to be run a couple of times to generate
                the null pointer message.

                The unusual thing is that if we write the above statement
                as:

                    if ( F->IsSubdirectory() )
                    if ( !(F->Name() == ".") )

                then everything works fine.  Since the two forms are
                logically identical (except that the second requires
                an extra matching else statement), they should work the
                same way.  The question is:  why don't they?  I would
                greatly appreciate any help anyone can offer.

                I ran this program in a subdirectory with 6 or 7 regular
                files, one subdirectory, and the ever-present . and ..
                directories.
*/

#include <dir.h>
#include <dos.h>
#include <iostream.h>
#include <process.h>
#include <string.h>

#define TRUE 1
#define FALSE 0
typedef unsigned char boolean;
#define CASE_SENSITIVE 0
#define CASE_INSENSITIVE 1


//  A very basic string class containing constructors, an assignment operator
//  and an equality operator (the == operator can perform either case-
//  sensitive or case-insensitive comparisons).

class String
    {
    protected:
        char *S;
        int ComparisonMode;

    public:
        String();
        String( char *s, int mode = CASE_SENSITIVE );
        String( String &s );
        String  operator  = ( String& s );
        boolean operator == ( String& s );
        operator const char * () { return S; }
        ~String()   {
                        delete S;
                    }

        friend ostream& operator << ( ostream &s, String &x )
            {   return (s << x.S);  }
    };

void AllocationError()
    {
        cerr << "Cannot allocate space for new string.\n";
        exit(1);
    }


//  S is guaranteed to point to something, even if it's a null string ("\x0")

String::String()
    {
        ComparisonMode = CASE_SENSITIVE;
        S = strdup( "" );
    }

String::String( char *s, int mode )
    {
        ComparisonMode = mode;
        if ( s == NULL ) S = strdup( "" );
        else if ( ( S = strdup(s) ) == NULL ) AllocationError();
    }

String::String( String &s )
    {
        ComparisonMode = s.ComparisonMode;
        if ( ( S = strdup( s.S ) ) == NULL ) AllocationError();
    }

String String::operator = ( String& s )
    {
        delete S;
        S = strdup( s.S );
        ComparisonMode = s.ComparisonMode;
        return *this;
    }

boolean String::operator == ( String& s )  // are the strings equal?
    {
        if ( ComparisonMode == CASE_SENSITIVE )
            if ( strcmp( s.S, S ) ) return FALSE;   //  strcmp = 0 if equal
            else                    return TRUE;
        else    //  CASE_INSENSITIVE
          { //  Duplicate the strings so we can convert them to uppercase
            String s1 = *this, s2 = s;
            strupr( s1.S );  strupr( s2.S );
            if ( strcmp( s1.S, s2.S ) ) return FALSE;
            else                        return TRUE;
          }
    }

class File
    {
    public:
        struct ffblk f;

        File( struct ffblk fb );
        String   Name();
        boolean  IsSubdirectory();
    };


File::File( struct ffblk fb )   {   f = fb;             }

String File::Name()
    {
        //  We create an intermediate variable here because Turbo C++
        //  has reportedly had problems returning an instance created
        //  in the return statement.
        String temp  ( f.ff_name, CASE_INSENSITIVE );
        return temp;
    }

boolean File::IsSubdirectory()
    {
        if ( ( f.ff_attrib & FA_DIREC ) == FA_DIREC ) return TRUE;
        else return FALSE;
    }

boolean ReadDir()
    {
        struct ffblk fb;
        File *F;

        //  read subdirectories
        int done = findfirst( "*.*", &fb, FA_DIREC );
        String cd( "." );
        while (!done)
            {
                F = new File( fb );

                if (
                    ( F->IsSubdirectory( ) )

//  The following three lines of code all attempt to perform roughly the
//  same comparison:  check if the name is the current subdirectory.
//  If the first of the three is used, we get null pointer assignments.
//  Either of the latter two work fine.  (Note:  if we use 2 if statements,
//  we must have two matching else statements below).

//  Uncomment one of the following three lines:
                    &&  ( !(F->Name() == cd ) )     //  this one -> NULL ptr
//                ) if (( !(F->Name() == cd ) )             //  this works
//                  &&  ( strcmp( F->f.ff_name, "." ) )     //  this works

                   )

                    cout << "found subdirectory not equal to '.'\n";

//              else delete F;  //  we need two delete statements if we
                                //  use two if statements above in order
                                //  to ensure that unused Fs are deleted.
                else delete F;
                done = findnext( &fb );
            }

        return TRUE;
    }

main( int, char )
    {
        ReadDir();
        cout << "\n";
    }