Why is boost::filesystem aborting instead of throwing an exception?

帅比萌擦擦* 提交于 2019-12-24 00:53:49

问题


I'm migrating some code from VS2010 (using boost 1.55) to VS 2015 (using boost 1.60).

I end up with "Microsoft Visual C++ Runtime Library" reporting that abort() has been called while boost rties to throw an exception. However, I could get it throw other exceptions without any problem (and it used to work with VS2010/boost1.55):

#include <boost/filesystem.hpp>
#include <boost/filesystem/operations.hpp>

#include <iostream>

int main( int argc, char* argv[] )
{
    // Stepping to folder:

    try
    {
        boost::filesystem::current_path("B:/dev/msvc2015/vobs_bci/public/tst/base/cppunit/utlfile");
        std::cout << "Worked" << std::endl; // works OK
    }
    catch (...)
    {

    }

    // test throwing upon copy_directory because dource folder does not exist:

    try
    {
        boost::filesystem::copy_directory("s", "b");
    }
    catch (...)
    {
        std::cout << "Caught" << std::endl; // works OK
    }

    // test throwing upon copy because target file already exists:

    try
    {
        boost::filesystem::copy("./test.h", "./copied.cpp"); // works
        boost::filesystem::copy("./test.h", "./copied.cpp"); // should throw and be caught
    }
    catch (...)
    {
        std::cout << "Caught" << std::endl; // never reached...
    }

    std::cout << "Done" << std::endl;

    return 0;
}

This outputs:

Worked
Caught
-> then aborts!

With the debugger, I see that abort is called when error function below (in filesystem/src/operations.cpp) calls BOOST_FILESYSTEM_THROW:

bool error(err_t error_num, const path& p1, const path& p2, error_code* ec,
    const char* message)
{
    if (!error_num)
    {
      if (ec != 0) ec->clear();
    }
    else  
    { //  error
      if (ec == 0)
        BOOST_FILESYSTEM_THROW(filesystem_error(message,
          p1, p2, error_code(error_num, system_category())));  // << Here!
      else
        ec->assign(error_num, system_category());
    }
    return error_num != 0;
  }

I checked with the debugger, and I reach filesystem_error constructor and can step out of it without any problem, next step (pressed F11 in the debugger, throw should now be called), abort() gets called.

Strange thing is that when copy_directory throws an exception, it also works, and this does call exactly the same error function in filesystem/src/operations.cpp.

Call stack upon abort is:

>   ntdll.dll!KiUserExceptionDispatcher()   Inconnu
    KernelBase.dll!RaiseException() Inconnu
    vcruntime140d.dll!_CxxThrowException(void * pExceptionObject=0x000000000019f670, const _s__ThrowInfo * pThrowInfo=0x000000013fd01870) Ligne 136 C++
    test_3rdparty_inprg_boost.exe!`anonymous namespace'::error(unsigned long error_num=80, const boost::filesystem::path & p1={...}, const boost::filesystem::path & p2={...}, boost::system::error_code * ec=0x0000000000000000, const char * message=0x000000013fcf6fb8) Ligne 321    C++
    test_3rdparty_inprg_boost.exe!boost::filesystem::detail::copy_file(const boost::filesystem::path & from={...}, const boost::filesystem::path & to={...}, boost::filesystem::detail::copy_option option=none, boost::system::error_code * ec=0x0000000000000000) Ligne 919   C++
    test_3rdparty_inprg_boost.exe!boost::filesystem::copy_file(const boost::filesystem::path & from={...}, const boost::filesystem::path & to={...}, boost::filesystem::copy_option option=none, boost::system::error_code & ec) Ligne 550  C++
    test_3rdparty_inprg_boost.exe!boost::filesystem::detail::copy(const boost::filesystem::path & from={...}, const boost::filesystem::path & to={...}, boost::system::error_code * ec=0x0000000000000000) Ligne 894    C++
    test_3rdparty_inprg_boost.exe!boost::filesystem::copy(const boost::filesystem::path & from={...}, const boost::filesystem::path & to={...}) Ligne 524   C++
    test_3rdparty_inprg_boost.exe!main(int argc=1, char * * argv=0x00000000003f3cc0) Ligne 35   C++
    test_3rdparty_inprg_boost.exe!invoke_main() Ligne 75    C++

But I can't see the source code of ntdll.dll!KiUserExceptionDispatcher()nor KernelBase.dll!RaiseException().


回答1:


boost::filesystem::copy is a huge broken mess. The function simply calls boost::filesystem::detail::copy with the third argument defaulted to null:

  BOOST_FILESYSTEM_DECL
  void copy(const path& from, const path& to, system::error_code* ec)
  {
    file_status s(symlink_status(from, *ec));
    if (ec != 0 && *ec) return;

    if(is_symlink(s))
    {
      copy_symlink(from, to, *ec);
    }
    else if(is_directory(s))
    {
      copy_directory(from, to, *ec);
    }
    else if(is_regular_file(s))
    {
      copy_file(from, to, fs::copy_option::fail_if_exists, *ec);
    }
    else
    {
      if (ec == 0)
        BOOST_FILESYSTEM_THROW(filesystem_error("boost::filesystem::copy",
          from, to, error_code(BOOST_ERROR_NOT_SUPPORTED, system_category())));
      ec->assign(BOOST_ERROR_NOT_SUPPORTED, system_category());
    }
  }

This function in turn is full of invalid dereferences of that potentially-null pointer, and also calls the error code variants of the specific functions that are declared noexcept, passing a bogus reference that resulted from dereferencing the null pointer, which the compiler might well forward as such (remember, we're already in UB land here). These functions in turn take the address of the reference (which typically again yields a null pointer) and call their own detail versions again, which use the error function, which throws if the error code pointer is null.

The workaround:

  • Don't use copy(), use the concrete function for the type of thing you want if you know it (e.g. copy_file()), or
  • Use the version of copy() that takes an error_code and examine the code yourself.

I see you've already posted a bug report. This bug report is correct.


Edit by jpo38:

Don't use copy()

Note that this is still the case in boost 1.65.1 recent release. You can prevent developpers from using the function by marking it as deprecated:

Create a file containing:

#ifdef __GNUC__
#define DEPRECATED(func) func __attribute__ ((deprecated))
#elif defined(_MSC_VER)
#define DEPRECATED(func) __declspec(deprecated) func
#else
#pragma message("WARNING: You need to implement DEPRECATED for this compiler")
#define DEPRECATED(func) func
#endif

...

namespace boost
{
namespace filesystem
{
class path;
DEPRECATED( void copy(const path& from, const path& to) );
}
}

And then include it for all cpp file using /FI option. Then you'll get a warning if any code tries to use this messy function.




回答2:


See the boost source code. According to that, BOOST_FILESYSTEM_THROW(EX) is simply throw EX. So there must be a reason, why throw calls abort(). That might be the case, when the exception is thrown while another exception is thrown - e.g. in the exceptions constructor.

For the moment my assumption is a bug in boost::filesystem. You might consider to file a bug report.



来源:https://stackoverflow.com/questions/34793451/why-is-boostfilesystem-aborting-instead-of-throwing-an-exception

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!