Heftiger Bug in RAD Studio 2007 mit auto_ptr und Delphi-Klassen?



  • DocShoe schrieb:

    Das Problem liegt höchstwahrscheinlich im Transfer of Ownership des gekapselten Pointers.

    Entschuldige bitte meine Ausdrucksweise, ich bin arbeitstechnisch gerade eh schon leicht gereizt - aber ich bin nicht blöd. Ich weiß, dass man keine Ownership transferiert und weiß auch sonst, wie ein auto_ptr zu benutzen ist. Es ist eine einzige lokale Variable ohne Übertragung von Eigentümerschaft.

    Ein minimalstes Beispiel, was außer Klammern und Kommentaren mein Originalbeispiel ist.

    Eigenes Datamodule erzeugen, in einem anderen Formular (oder sonstwas) in z.B. einen Button->Click folgendes einfügen:

    {
      std::auto_ptr<TMyDatamodule> p( new TMyDatamodule (0 /*Owner*/)); // TMyDatamodule kann in bestimmten, sinnvollen Konstellationen eine Exception im Konstruktor werfen
      p->test(); // wird - sogar mit dem CPU-Monitor geprüft, nicht aufgerufen
      // hier steht absolut garnichts weiter
    } // Scope-Ende, hier scheppert es
    

    CodeGuard hat im übrigen nichts zu meckern.



  • 7H3 N4C3R schrieb:

    Ich weiß auch, dass der auto_ptr nicht erzeugt werden sollte - aber wie im Originalpost geschrieben wird sein Destruktor trotzdem aufgerufen, obwohl die Exception, mit dem Debugger überprüft, im Konstruktor des konkreten Objekts auftritt. Sein Scope ist (bis auf einen Methodenaufruf, der nicht aufgerufen wird), danach explizit durch geschweifte Klammern zuende.

    Wenn das so wäre, würde der BCB2007 wirklich einen Heftigen Bug haben.

    Würdest du mit bitte sagen was folgendes Programm (hoffe ohne Fehler aus dem Gedächtnis geschrieben) für Ausgaben erzeugt?

    #include <iostream>
    #include <memory>
    
    class A
    {
      public:
        A() { std::cout << "A::A() << std::endl; throw(1); }
        A(A const &) { std::cout << "A::A(A const &) << std::endl; }
        A& operator=(A const &) { std::cout << "A& A::operator=(A const &) << std::endl; return *this; }
        ~A() { std::cout << "A::~A() << std::endl; }
    };
    
    class B
    {
      public:
        auto_ptr<A> a;
    
        B()
        : a(new A())
        { std::cout << "B::B() << std::endl; }
    
        ~B()
        { std::cout << "B::~B() << std::endl; }
    };
    
    int main()
    {
      try
      {
        B b;
      }
      catch(...)
      {}
    }
    

    cu André



  • Das Programm erzeugt, wie es auch sollte nur:

    A::A()

    als Ausgabe.

    Mein Verdacht liegt eher bei den VCL/Delphi-Klassen, da deren Konstruktion durch virtuelle Konstruktoren etwas anders funktioniert, als man es in C++ gewohnt ist.

    Das "lustige" an diesem Fehler ist, dass er nur sehr schwer zu reproduzieren ist und, dass CodeGuard mit vollen Optionen nichts zu meckern hat. Eigentlich würde mich das sofort auf zerschossenen Speicher bringen, allerdings konnte ich in der Hinsicht weder selbst, noch Tool-gestützt etwas finden. Das gute, alte Purify kann allerdings mit den Debug-Infos vom bcc32 nix anfangen und macht wie's aussieht die Einsprungspunkte ins Speichermanagement kaputt, wodurch sich die Anwendung garnicht erst starten lässt. 😞



  • Ich habe das gerade auch mal getestet. Dabei habe ich A von TComponent abgeleitet, so dass es eine richtige VCL-Klasse ist. Auch hier wird nur der Konstruktor aufgerufen.



  • Das funktioniert bei mir auch. 😞 Ist zum Verzweifeln. Nur genau deswegen will ich diesen Fehler nicht einfach durch Code-umstellen entfernen - sofern er denn ganz weg ist und dann nicht nur seltener auftritt. Ich will andere Möglichkeiten auch nicht ausschließen, momentan sieht es allerdings nach auto_ptr-zusammenhängend aus. Wenn jemand noch einen gutes Tool á la Purify kennt das auch mit dem bcc32 funktioniert, ich wäre auch sehr interessiert.



  • Wie asc schon sagte könntest du es mit boost::shared_ptr versuchen. Das ist ja eine Header-only-Klasse so dass du keine Libs erzeugen musst. Zusammenarbeit mit den BCBs ist auch kein Problem. Evtl. ja nur mal zum Test.



  • Naja, das wäre aber wiederum die Art zu Debuggen, wie ich sie eigentlich vermeiden will. Also so lange am Code ändern, bis der Fehler nicht mehr auftaucht, ohne tatsächlich verstanden zu haben woran es liegt. Vielleicht geht es dann und bei der nächsten Änderung, die meinetwegen den Stackframe etwas anders aussehen lässt, tritt der gleiche Fehler wieder zutage.
    Immerhin scheint die Verwendung von CodeGuard den Fehler etwas öfter zu provozieren, was doch wieder von auto_ptr wegdeutet. Ich liebe solche Fehler 😞



  • Hallo,

    7H3 N4C3R schrieb:

    Ein minimalstes Beispiel, was außer Klammern und Kommentaren mein Originalbeispiel ist.

    Eigenes Datamodule erzeugen, in einem anderen Formular (oder sonstwas) in z.B. einen Button->Click folgendes einfügen:

    {
      std::auto_ptr<TMyDatamodule> p( new TMyDatamodule (0 /*Owner*/)); // TMyDatamodule kann in bestimmten, sinnvollen Konstellationen eine Exception im Konstruktor werfen
      p->test(); // wird - sogar mit dem CPU-Monitor geprüft, nicht aufgerufen
      // hier steht absolut garnichts weiter
    } // Scope-Ende, hier scheppert es
    

    Wenn ich das Minimalbeispiel in C++Builder 6 oder 2006 zu reproduzieren versuche, funktioniert alles einwandfrei.

    Mein Datenmodul wirft im Konstruktor eine Exception, wenn der übergebene Parameter 0 ist. Dabei habe ich beide Exception-Typen (C++-Exception, Delphi-Exception) getestet, und in beiden Fällen wird der Destruktor von std::auto_ptr nicht aufgerufen, das Objekt vielmehr gar nicht konstruiert.

    Könntest du evtl. dein Minimalbeispiel als vollständiges Projekt irgendwo hochladen (Rapidshare o.ä.)?



  • Auf ein Minimalbeispiel reduziert funktioniert es bei mir auch wunderbar. Breakpoints auf auto_ptr halten nicht an, selbst ein Trace im Disassembler läuft niemals den auto_ptr an. Kann eigentlich nur heißen, dass es entweder richtig funktioniert und irgendwas anderes bösartig zersägt ist oder das Problem nur unter ganz bestimmten Umständen zu Tage tritt.
    Dass das sonst anscheinend noch niemandem weiter passiert ist, spricht wohl gegen einen Bug im RAD Studio.

    Also nochmal meine Bitte, wenn jemand ein Tool wie Purify kennt, was mit den Debug Infos vom Builder zurecht kommt und nicht die Einsprungadressen in den Memorymanager umlegt, wäre mir das sehr willkommen. Der CodeGuard ist zumindest selbst nicht imstande irgendeinen Fehler zu finden. Der BoundsChecker ist ja anscheinend nicht mehr für den C++ Builder/RAD Studio zu haben. Sonst habe ich kein weiteres Tool gefunden.



  • Du kannst es aber, wenn ich dich richtig verstehe, in einem etwas komplexeren Kontext reproduzieren?

    Dann wäre es möglicherweise hilfreich, wenn du mal den für diese Stelle generierten Assembler-Code posten könntest.



  • Argh so, keine Zeit gehabt wieder reinzuschauen.

    Auch im komplexen Kontext ist es nur sporadisch / nicht zuverlässig reproduzierbar. Seit dem ich ein paar Umbauarbeiten gemacht habe, lässt es sich bis dato auch nicht mehr reproduzieren (weder Debug noch Release, weder mit noch ohne CodeGuard). Das spricht eigentlich dafür, dass irgendwo an anderer Stelle der Speicher zersägt wird und vermutlich ein Stackframe oder so zerschossen wird. Das funktionierende Minimalbeispiel spricht ja auch dafür.

    Da ich keine Ahnung habe woran es liegt und auch toolgestützt nix finden konnte, bleibt mir wohl erstmal nix übrig als das Problem ad akta zu legen 😞



  • So, ich glaub ich hab ihn an den Eiern...

    Problem ist anscheinend so ein Konstrukt:

    #include <memory>
    
    class AImpl;
    
    class A
    {
    public:
      A();
      A( const A&);
      A& operator=( const A&);
    private:
      std::auto_ptr<AImpl> impl_;  
    }
    

    Okay, ich könnte mich selbst dafür schlagen 😃 aber der Compiler warnte erst im Release-Build nach Aktivierung aller Warnungen (im Debug-Build mit allen Warnungen hielt er es anscheinend nicht für nötig). Die zutreffende war natürlich per Default deaktiviert. Muss mal den Standard durchwühlen, ob das nicht sogar ein Compile-Fehler sein sollte bzw. ob ein Diagnostic erforderlich ist.

    Der Code war natürlich an völlig anderer Stelle (wurde allerdings benutzt). Ist auch nicht so verwunderlich, dass der KotGuard nix gefunden hat.

    Ich lass den exakten Grund mal gerade noch offen, falls noch jemand drüber grübeln will 😉

    (Hm - 5.3.5/5 sagt nur undefined behaviour)



  • Gerade bei sowas wäre eben boost::shared_ptr angebracht, wenn man denn wirklich kopieren will. 🙂



  • Ja nee, das hat mit dem Urpsrungsproblem nichts mehr zu tun hier 🤡 - es wirkt sich nur darauf aus 😃 - und - boost::shared_ptr wäre genauso auf die Nase gefallen (und außerdem nicht verwendbar in diesem konkreten Beispiel, außer ich schreibe die großen 3 auch alle selbst), bzw. der mistige Compiler hält es nicht für nötig zu warnen.



  • Obwohl der Thread schon ein wenig im Sande versandet ist, mal ein Update für euch.

    Es hat nix mit auto_ptr zu tun, es hat auch nix mit Delphi-Klassen zu tun. (vermute ich zumindest...)

    Das Problem ist sowas hier:

    const B A::f()
    {
      throw 0xdeadbeef;
    }
    

    B ist ein Wert-Objekt. Hier wird nun der Destruktor von B aufgerufen.

    Ja. Im Ernst. Wirklich.

    Bisher schaffe ich es nicht, ein funktionierendes Minimalbeispiel zu basteln, das klappt aber hoffentlich auch noch. Es sind allerdings auch schon mindestens 2 Compilerbugs bei Kotgier gemeldet, wo fälschlich ein Destruktor ausgeführt wird.

    Das Ding ist doch wirklich der totale Bockmist... 😡



  • 7H3 N4C3R schrieb:

    Bisher schaffe ich es nicht, ein funktionierendes Minimalbeispiel zu basteln, das klappt aber hoffentlich auch noch.

    Da bin ich sehr gespannt drauf.

    7H3 N4C3R schrieb:

    Es sind allerdings auch schon mindestens 2 Compilerbugs bei Kotgier gemeldet, wo fälschlich ein Destruktor ausgeführt wird.

    Hast du QC-Nummern dafür?



  • audacia|off schrieb:

    Da bin ich sehr gespannt drauf.

    Still trying... 😞

    audacia|off schrieb:

    Hast du QC-Nummern dafür?

    Die beiden hier:
    http://qc.codegear.com/wc/qcmain.aspx?d=61487
    http://qc.codegear.com/wc/qcmain.aspx?d=61962

    Hier auch ein Fall, wo der Destruktor garnicht aufgerufen wird:
    http://qc.codegear.com/wc/qcmain.aspx?d=61519

    Das "lustige" ist ja auch noch, dass 61487 und 61519 auf fixed stehen, aber kein Resolved in Version angegeben ist und mein RAD-Studio das December Update + den April Hotfix enthält und die Fehler allesamt reproduzierbar sind. Ich find's erschreckend, wie man sich damit an die Öffentlichkeit trauen kann.

    Mein derzeitiger Workaround sieht übrigens so aus (die in der QC angegebenen Workarounds sind bei mir nicht anwendbar, da ich diese Konstellationen nicht habe):

    A::A()
    : impl_( new Impl)
    , pattern_(0xdeadbeef) // analog in cctor
    {
    }
    
    A::~A()
    {
      if( pattern == 0xdeadbeef) {
        delete impl_;
        pattern ^= 0xFFFFFFFF;
      }
    }
    

    Ist zwar auch nicht hieb- und stichfest, sollte aber für hoffentlich 99,9% meiner Fälle ausreichen. (dem anderen 0,1% möchte ich nicht begegnen, sonst muss ich ausrasten :))



  • Am sichersten wäre es sogar, wenn du im Konstruktor pattern den this-Zeiger zuordnest und im Destruktor prüfst, ob pattern == this ist.

    Das Problem hatte ich übrigens auch, als ich eine eigenschaft definiert hab, der ich als index- Element ein Klassenobjekt übergebe. Im klassenobjekt wurde Speicher dynamisch allokiert.
    Codeguard meinte immer, das im Destruktor des Klassenobjekts auf bereits freigegebenen Speicher zugegriffen wird. Nach genauen Verfolgen über den Debugger hab ich rausbekommen, das eine Kopie des Klassenobjektes in der Eigenschaft angelegt wird, dies erfolgt aber nicht über einen Kopierkonstruktor (obwohl er definiert wurde), sondern einfach durch simples Kopieren des Klassenmembers. Als dann die Kopie zerstört wurde, wurde demzufolge auch der vom Originalobjekt angelegte Speicher mit zerstört (weil nämlich der Destruktor der Kopie aufgerufen wurde), so dass das Originalobjekt also dann auf bereits freigegeben Speicher zugriff.
    Mit Hilfe eines solchen "Kennzeichens" wie dein Pattern habe ich das dann so gelöst:

    Vorher:

    [cpp]
    __fastcall TMyObjekt::TMyObjekt(void *data,int len)
    {
        m_data = new BYTE[len];
        memmove(m_data,data,len);
    }
    __fastcall TMyObjekt::~TMyObjekt()
    {
        delete []m_data;
    }
    [/cpp]
    

    Nach Änderung:

    [cpp]
    __fastcall TMyObjekt::TMyObjekt(void *data,int len) : m_assigned(this)
    {
        m_data = new BYTE[len];
        memmove(m_data,data,len);
    }
    __fastcall TMyObjekt::~TMyObjekt()
    {
        if (m_assigned == this)
        {
            delete []m_data;
        }
    }
    [/cpp]
    


  • 7H3 N4C3R schrieb:

    Das "lustige" ist ja auch noch, dass 61487 und 61519 auf fixed stehen, aber kein Resolved in Version angegeben ist und mein RAD-Studio das December Update + den April Hotfix enthält und die Fehler allesamt reproduzierbar sind. Ich find's erschreckend, wie man sich damit an die Öffentlichkeit trauen kann.

    Wenn die Build-Nummer nicht da steht, bedeutet das i.d.R., daß das Problem für die nächste, noch nicht öffentliche Version behoben wurde; es ist somit nur für die Öffentlichkeit nicht sichtbar. Dementsprechend sollte das Problem in C++Builder 2009 behoben sein.



  • Äähm... soll das heißen, dass für den C++ Builder 2007 kein Fix zu erwarten ist?


Anmelden zum Antworten