1

I have a test program, based on CPPUNIT library and Qt that runs ~900 unit tests. This program is deployed on Android using QtCreator. It links with ~80 libraries, each one defining some tests.

After a code change, test program started crashing due to std::bad_cast being thrown.

Original code (no bad_cast):

class TimeUnit
{
    friend std::ostream& operator<<(std::ostream &os, const TimeUnit& var);
public:

    std::string TimeUnit::toString() const
    {
        std::stringstream stream;
        static std::string facetStr = "%Y/%m/%d %H:%M";

        stream.imbue(std::locale(std::locale::classic(), new boost::posix_time::time_facet(facetStr.c_str())));
        stream << *m_time;
        return stream.str();
    }
private:
    boost::posix_time::ptime m_time;
};

std::ostream& operator<<(std::ostream &os, const TimeUnit& var)
{
    os << var.toString();
    return os;
}

New code (makes the test program throw bad_cast at some point):

class TimeUnit
{
    friend std::ostream& operator<<(std::ostream &os, const TimeUnit& var);
public:

    std::string TimeUnit::toString() const
    {
        std::stringstream stream;
        stream << *this;
        return stream.str();
    }
private:
    boost::posix_time::ptime m_time;
};

std::ostream& operator<<(std::ostream &os, const TimeUnit& var)
{
    static std::string facetStr = "%Y/%m/%d %H:%M";

    os.imbue(std::locale(std::locale::classic(), new boost::posix_time::time_facet(facetStr.c_str())));
    // this next line ends up throwing std::bad_cast!
    os << *(var.m_time);
    return os;
}

The new code makes std::bad_cast exception be thrown by my operator<< at some point. According to the doc:

An exception of this type is thrown when a dynamic_cast to a reference type fails the run-time check (e.g. because the types are not related by inheritance), and also from std::use_facet if the requested facet does not exist in the locale.

...boost operator<< for boost::posix_time::ptime does a use_facet...

After a lot of investigation, I can conclude that:

  • The crash is Android-specific (Window works just prefectly)
  • It is due to attaching a facet to std::cout (in the new code, if I create a local std::stringstream apply the facet to it and later copy the result with os << str.str(), no more crash)
  • os.imbue(std::locale(std::locale::classic(),...) or os.imbue(std::locale(os.getloc(),...)) both will lead to the same crash
  • Removing os.imbue call makes the crash disappear
  • Calling os.imbue(std::locale::classic()); before returning from my operator<< does not fix the issue

Unfortunately, I tried to isolate the problem in a MCVE but could not reproduce the problem with some more basic code...

So my question is (are):

  • Am I doing something wrong in my operator<<?
  • Why would this code work on Windows and crash on Android? Could this be due to an insane C++ runtime that would delete the facet in a wrong way?

Edit:

After some more investigation, I found out that the bad_cast is only thrown if I redirect std::cout to a file from my main, using:

class ScopedRedirect
{
public:
    ScopedRedirect(std::ostream & inOriginal, std::ostream & inRedirect) :
        mOriginal(inOriginal),
        mOldBuffer(inOriginal.rdbuf(inRedirect.rdbuf()))
    {}
    virtual ~ScopedRedirect()
    {
        mOriginal.rdbuf(mOldBuffer);
    }

    ScopedRedirect(const ScopedRedirect&) = delete;
    ScopedRedirect& operator=(const ScopedRedirect&) = delete;

protected:
    std::ostream & mOriginal;
    std::streambuf * mOldBuffer;
};

int main( int argc, char* argv[] )
{
    std::fstream output( "cout.txt", std::ios_base::out );
    assert( output.is_open() );

    ScopedRedirect redirectCount( std::cout, output );

    ...
}
jpo38
  • 20,821
  • 10
  • 70
  • 151
  • 3
    is `facetStr.c_str()` valid outside the `operator<<` ? [I don't think so ...](https://stackoverflow.com/questions/6456359/what-is-stdstringc-str-lifetime) what if you try to make "%Y/%m/%d %H:%M" a global const ? – Selvin Sep 07 '17 at 14:15
  • @Selvin: That's definitely a good point. I need to test if making the string static fixes the crash. By the way, is this facet mechanism thread-safe? I see boost case does `if (has_facet) use_facet`, what happens if another thread destroys the facet between the check of `has_facet` and `use_facet`...? – jpo38 Sep 07 '17 at 14:21
  • isn't it using a std::shared_ptr for both facet and locale? – Selvin Sep 07 '17 at 14:31
  • @Selvin: This http://en.cppreference.com/w/cpp/locale/locale seams to say it's thread-safe.... – jpo38 Sep 07 '17 at 14:36
  • 1
    @Selvin: My whole code takes hours to build and run, I'll update you after I tested if `static string` fixes the crash, but hopefully it will, and it would explain why passing through a local `std::stringstream` works (as the facet is then destroyed with the stream). I think you should post an answer to get deserved reputation (I hope it will fix my crash, but even if it does not, it could have fixed it...). – jpo38 Sep 07 '17 at 14:54
  • I am probably missing something obvious, but I cannot see `TimeUnit::m_data` in your code snippet. – Alex Cohn Sep 07 '17 at 15:33
  • @AlexCohn: You are not, it's just that I simplified my code whe I posted it (TimeUnit class is much bigger indead) and forgot to modify attribute name. I edited the post. Sorry about that. – jpo38 Sep 07 '17 at 16:37
  • At what /point/ does it crash? (E.g. during shutdown) – sehe Sep 07 '17 at 17:01
  • It's still surprising that going out if scope, the facet string triggers bad_cast out of all possible failures. But the cure is either to make the string eternal (e.g. use static), or to remove the locale from the stream after use. – Alex Cohn Sep 07 '17 at 17:09
  • @sehe: It actually does not crash as I'm running CPPUNIT tests and it catches `std::bad_cast` exception. Apparently, the `bad_cast` is thrown by my `operator<<` at some point. Will update my post. – jpo38 Sep 07 '17 at 19:09
  • 1
    @AlexCohn: Note that I tried to call `os.imbue(std::locale::classic());`, which is suppose, I understood, to remove the locale from the stream, but it will later throw `bad_cast` anyway. – jpo38 Sep 07 '17 at 19:10
  • right, this is weird – Alex Cohn Sep 07 '17 at 20:02
  • @Selvin: Tested with static std::string and string literal. It still crashs. I checked and `boost::posix_time::time_facet` stores it as a `std::string` attribute, so it makes a deep copy. This cannot lead to any code instability. – jpo38 Sep 08 '17 at 15:10

0 Answers0