【问题标题】:Why is boost::filesystem aborting instead of throwing an exception?为什么 boost::filesystem 中止而不是抛出异常?
【发布时间】:2016-01-14 15:33:40
【问题描述】:

我正在将一些代码从 VS2010(使用 boost 1.55)迁移到 VS 2015(使用 boost 1.60)。

我最终得到“Microsoft Visual C++ 运行时库”报告 abort() has been called 而 boost rties 抛出异常。但是,我可以让它毫无问题地抛出其他异常(它曾经与 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;
}

这个输出:

Worked
Caught
-> then aborts!

使用调试器,我看到当下面的错误函数(在 filesystem/src/operations.cpp 中)调用 BOOST_FILESYSTEM_THROW 时调用了 abort:

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;
  }

我检查了调试器,我到达了filesystem_error 构造函数,并且可以毫无问题地退出它,下一步(在调试器中按 F11,现在应该调用 throw),调用 abort()

奇怪的是,当copy_directory 抛出异常时,它也可以工作,并且这确实调用了与filesystem/src/operations.cpp 完全相同的error 函数

中止时的调用堆栈是:

>   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++

但是我看不到ntdll.dll!KiUserExceptionDispatcher()也看不到KernelBase.dll!RaiseException()的源代码。

【问题讨论】:

  • abort() 引发一个导致异常终止的信号 (SIGABRT),而不是异常。 catch 语句包含异常,而不是信号。这意味着遇到了一个错误,并且在异常启动之前调用了abort
  • @mikedu95 我看到的问题不是为什么abort() 不能被抓到,而是为什么(也许在哪里)abort() 被调用。
  • @mikedu95 但是当我在“Microsft Visual C++ 运行时库”弹出窗口上单击“重试”时,它会将我带到BOOST_FILESYSTEM_THROW 行。为什么这会中止而不是抛出呢?
  • @hvd 你说得对,我已经更新了我的评论
  • 除了编译器错误之外,唯一想到的是非工作调用堆栈中的某些函数无意中声明为 noexcept,或者更不可能使用不兼容的异常规范声明。

标签: c++ exception-handling visual-studio-2015 boost-filesystem


【解决方案1】:

boost::filesystem::copy 是一个巨大的破烂烂摊子。该函数只是调用boost::filesystem::detail::copy,第三个参数默认为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());
    }
  }

这个函数又充满了对该潜在空指针的无效解引用,并且还调用了声明为 noexcept 的特定函数的错误代码变体,传递了一个由解引用空指针产生的虚假引用,编译器可能会这样(请记住,我们已经在 UB 土地上)。这些函数依次获取引用的地址(通常再次产生一个空指针)并再次调用它们自己的详细版本,这些版本使用错误函数,如果错误代码指针为空则抛出。

解决方法:

  • 不要使用copy(),如果您知道,请使用具体函数来表示您想要的事物类型(例如copy_file()),或者
  • 使用带有error_codecopy() 版本并自行检查代码。

我看到您已经发布了错误报告。这个错误报告是正确的。


由 jpo38 编辑:

不要使用copy()

请注意,在最近的 boost 1.65.1 版本中仍然如此。您可以通过将其标记为已弃用来阻止开发人员使用该功能:

创建一个包含以下内容的文件:

#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) );
}
}

然后使用 /FI 选项将其包含在所有 cpp 文件中。然后,如果任何代码尝试使用这个乱七八糟的功能,您会收到警告。

【讨论】:

  • 其他解决方法:从 copy_file 中删除 BOOST_NOEXCEPT 并重新编译 boost?我做到了,它彻底解决了问题!
  • @jpo38 那仍然让你留在 UB 的土地上。举个例子,编译器可以判断函数的第一个if中的ec不可能为null,从而无条件调用error_codebool转换为null this-pointer,从而真正崩溃. Visual Studio 优化器似乎过于保守,无法做到这一点,但 GCC 和 Clang 会。当 GCC 开始这样做时,一个著名的 Linux 内核错误被暴露出来,导致 GCC 添加了一个命令行开关来禁用此行为。
  • “腰带和大括号”我都会做(重新编译+删除对copy的调用);-)
  • 使用copy_file 为我做到了。带有 VC 2017 (15.3) 的 Boost 1.64.0 中显然仍然存在错误。非常感谢! :)
  • 这个bug在1.67.0中依然存在。在它被引入 4 年后,在 cmets 中针对另一个答案的错误报告打开 3 年后,并且补丁在 3 个月前提交。
【解决方案2】:

the boost source code。据此,BOOST_FILESYSTEM_THROW(EX) 就是throw EX。所以肯定有原因,为什么要抛出电话abort()。当抛出异常而抛出另一个异常时,可能就是这种情况 - 例如在异常构造函数中。

目前我的假设是boost::filesystem 中的一个错误。您可以考虑提交错误报告。

【讨论】:

  • 在 boost 中发现这样一个微不足道的 bug 我真的很惊讶……但这是有可能的
  • 填写了一张提升票:svn.boost.org/trac/boost/ticket/11914。如果他们说这确实是他们代码中的一个错误,我会接受你的回答......
  • 该错误似乎已被“接受”...但尚未修复?