c++builderc++builder-2010

C++ Builder 2010 Strange Access Violations


I've got a program that is to become part of an already existing, larger product which is built using C++ Builder 2010.

The smaller program does not (yet) depend on C++ Builder. It works fine in MS Visual Studio, but with C++ Builder it produces strange access violations.

Please let me explain this.

Depending on the code and on compiler settings, access violations happen or do not happen. The access violations are reproducible: When the program is built then the access violation does never occur or it does always occur at the same place. If the program is rebuilt with the same settings, it will show the same behavior. (I'm really glad about that).

The access violation happens at places where the delete operator is called. This can happen (depending on compiler settings and exact code) inside certain destructors, including destructors of own classes and inside the destructor of std::string.

The following things make the access violation less likely:

The program makes use of several C++ features, including exceptions, STL, move constructors etc. and it of course uses the heap.

I already tried some tools, none of them reported problems:

Use of precompiled headers and incremental linking (which both seem to me are prone to errors) are disabled.

Neither the C++ Builder compiler ("enable all warnings") nor the one of Visual Studio (/W4) produces a warning that might be related to this issue.

I do not have access to another version of C++ Builder.

As the program will become part of a larger product, it is not an option to switch to a different compiler, and it is not an option to tune the compiler settings until the access violation does no longer happen. (I fear if this really should a compiler bug, the bug might show up again.)

Putting this together, I'm guessing this might result from heap corruption that is related to some compiler bug. However, I was not able to find a bug on qc.embarcadero.com. I'm guessing further this is related to cleanup code that is executed upon stack rewinding when an exception has been thrown. But, well, maybe it's only a stupid code bug.

Currently, I do not have any idea how to proceed. Any help appreciated. Thank you in advance!


Solution

  • tl;dr I believe the bug is that code is generated to delete the std::string from both branches of the ternary operator during stack unwinding, however only one of them was actually created of course.


    Here is a simpler MCVE, which shows the problem via outputs in XE5:

    #include <vcl.h>
    #include <tchar.h>
    #include <stdio.h>
    using namespace std;
    
    struct S
    {
        S() { printf("Create: %p\n", this); }
        S(S const &) { printf("Copy: %p\n", this); }
        void operator=(S const &) { printf("Assign: %p\n", this); }
        ~S() { printf("Destroy: %p\n", this); }
    
        char const *c_str() { return "xx"; }
    };
    
    S rX() { return S(); }
    int foo() { return 2; }
    
    #pragma argsused
    int _tmain(int argc, _TCHAR* argv[])
    {
       try
       {
          throw Exception( (foo() ? rX() : rX()).c_str() );
       }
       catch (const Exception& e)
       {
       }
    
       getchar();
       return 0;
    }
    

    This version shows the problem via output strings on the console. Check the edit history for this post to see a version that uses std::string and causes the segfault instead.

    My output is:

     Create: 0018FF38
    Destroy: 0018FF2C
    Destroy: 0018FF38
    

    In the original code, the segfault comes from the bogus Destroy ending up calling delete on the bogus value it obtains by trying to retrieve the internal data pointer for a std::string which was actually never created at that location.

    My conjecture is that the code generation for stack unwinding is bugged and tries to delete the temporary string from both branches of the ternary operator. The presence of the temporary UnicodeString does have something to do with it; as the bug did not occur in any variations where I tried to avoid that temporary.

    In the debugger you can see the call stack and it is during global stack unwinding that this happens.