【问题标题】:Is it possible to recover from std::abort?是否可以从 std::abort 中恢复?
【发布时间】:2016-03-16 14:42:25
【问题描述】:

我们的 C 代码库正在使用 assert 来检查是否满足前置条件/​​后置条件。

#include <cstdlib>
#include <cassert>

void Aborting_Function(int n);

int main(){

    Aborting_Function(1); //good
    Aborting_Function(0); //calls std::abort()

    //Is it possible to recover somehow?
    //And continue on...
}

void Aborting_Function(int n){
    assert(n > 0);
    //impl...
}

在单元测试中,我想验证函数是否正确地遵循它们的合同
(当他们应该中止时)。

是否可以从 std::abort 中恢复?

我意识到让单元测试检查断言应该检查的内容似乎有些重复,但这会很有帮助,因为我们可以自动检查不应该工作的特定用例。

【问题讨论】:

  • 不要使用assert(x),而是使用our_assert(x),它有条件地定义为什么都不做(发布)、assert(x)(调试)、做一些花哨的事情(测试)?
  • 我不明白。对于因输入错误而中止的函数的单元测试,您的预期结果将被中止。有什么问题?
  • @DietmarKühl 这可能是我们最好的路线。这将依赖于我们更新大部分代码库,但这可能是唯一的方法。
  • 根据标准abort()程序终止,不执行自动、线程或静态存储持续时间的对象的析构函数,也不调用传递给 atexit() 的函数
  • @SergeyA 我们想要一个测试用例列表,我们可以在其中确认“是的,这 50 个单独的函数调用将在指定输入的情况下中止。”。但是,当第一个测试用例中止时,似乎没有办法恢复。

标签: c++ unit-testing abort


【解决方案1】:

有一种方法可以做到这一点,但它有点不符合要求。

您可以使用 HippoMocks(免责声明:我是作者)来模拟该函数并使其抛出异常,然后您可以使用它来检查您的测试框架。你不能让它返回一个值,因为它被标记为 noreturn 并且编译器不会生成任何代码来处理它的返回。

EXPECT_CALL(&abort).Throw(42);

请注意,这违反了 C 和 C++ 中至少 5 条不同的规则,所以更大的问题是,您应该这样做吗?据我所知,您使用断言来防范显示内部不一致的事情。这些是你不应该测试的东西。无论有没有断言,你的所有程序都应该同样有效。如果您希望从您的代码中得到任何想要测试的行为,那么这绝不应该是一个断言,因为它不是一个意外状态(哎呀,您正在测试该状态,所以它是一个经过良好测试的状态) .

所以你可以,但你不应该想要。

【讨论】:

  • 死亡测试 - 其中包括 exit()- considered useful.
  • 有趣,您介意指出哪 5 条规则吗?
  • 1.它采用 C++ 标准函数的地址,这是不允许的。 2. 它用重定向到另一个函数的 x86 字节码覆盖它,在运行时更改它。这只会更改 PLT 条目,因此它甚至不是那么好,但对于单元测试来说它是可以的。 3. 它用 C++ 生成的函数替换 C 函数,但不能保证有效。 (即调用约定) 4. 它从标记为 noreturn 的 C 函数中抛出异常。 5. 至少有一条指导方针说你不应该抛出整数。好的,这是一个延伸,但它仍然不是很好。
【解决方案2】:

对于单元测试,可以从那里替换 abort() 和 longjmp(),或者在通过 C++ 测试时从那里抛出。

例如(表示为C++):

#include <cassert>
#include <csetjmp>
#include <stdexcept>
#include <iostream>

#ifndef lest_ABORT_SIGNATURE
# if _MSC_VER
#  define lest_NORETURN  __declspec(noreturn)
#  define lest_ABORT_SIGNATURE()  _ACRTIMP lest_NORETURN void __cdecl abort(void)
# else
#  define lest_NORETURN  [[noreturn]]
#  define lest_ABORT_SIGNATURE()  lest_NORETURN void __cdecl abort()
# endif
#else
# ifndef  lest_NORETURN
#  define lest_NORETURN
# endif
#endif

#if USE_LONGJMP
    jmp_buf env;

    lest_ABORT_SIGNATURE()
    {
        std::longjmp( env, 1 );
    }
#else
    struct Abort{};

    lest_NORETURN void my_abort()
    {
        throw Abort{};
    }

    lest_ABORT_SIGNATURE()
    {
        // throw indirectly and prevent warning in VC14:
        my_abort();
    }
#endif

int main()
{
#if USE_LONGJMP
    if ( ! setjmp( env ) )
    {
        std::cout << "assert(false):\n";
        assert( false );
    }
    else
    {
        std::cout << "Intercepted abort\n";
    }
#else
    try
    {
        std::cout << "assert(false):\n";
        assert( false );
    }
    catch ( Abort const & )
    {
        std::cout << "Caught Abort\n";
    }
    catch ( std::exception const & e )
    {
        std::cout << "Exception: " << e.what() << "\n";
    }
#endif
    std::cout << "End\n";
}

#if 0
cl  -EHsc -DUSE_LONGJMP=1 abort-own.cpp && abort-own.exe
g++ -Wall -DUSE_LONGJMP=1 -std=c++11 -o abort-own.exe abort-own.cpp && abort-own.exe
#endif

使用 VC14 (VS2015) 编译并运行:

cl -EHsc -DUSE_LONGJMP=1 abort-own.cpp && abort-own.exe

产生以下输出:

...
assert(false):
Assertion failed: false, file abort-own.cpp, line 45
Intercepted abort
End

使用 VC14 之前的编译器编译会产生链接错误:

LIBCMT.lib(abort.obj) : error LNK2005: _abort already defined in {file}
{exe} : fatal error LNK1169: one or more multiply defined symbols found

这可以通过以下方式治愈:

使用-std=c++03-std=c++11 通过g++ 编译不会导致多重定义的符号。

我正在为lest test framework 开发的上述代码的更详细版本:

【讨论】:

  • +1 以使用 setjmp/longjmp 进行测试,我们有一个测试表明我们的崩溃处理程序可以处理检查它的段错误。当然,您也可以使用 SIGABRT 处理程序并在那里实现它...
  • @dascandy longjmp 来自 SIGABRT 处理程序 only appears to work,就像从它抛出一样。但是,根据标准,您可以从这样的处理程序中做的很少。另请参阅此页面上的其他答案和here
  • 从技术上讲,一般情况下你不能扔。务实地说,在测试运行环境中,能够预期中止然后继续测试可能很有用。
【解决方案3】:

简短的回答,“不”。

与其颠覆 abort(),不如考虑使用 google 测试框架。

这有 DEATH_TEST(此处的文档:https://github.com/google/googletest/blob/master/googletest/docs/V1_7_AdvancedGuide.md

本质上它的作用是派生一个子进程并检查语句是否导致它退出(如果它中止它会这样做)。

【讨论】:

    【解决方案4】:

    根据 POSIX,

    abort() 函数会导致进程异常终止,除非信号 SIGABRT 被捕获并且信号处理程序没有返回。

    这意味着如果您捕获信号并且信号处理程序返回,它仍然需要终止程序(例如,通过将信号处理程序重置为默认终止行为,然后再次引发信号)。

    所以“恢复”的唯一方法是捕获信号而不是从信号处理程序返回。所以无法到达Aborting_Function(0) 之后的行。此外,您可能不希望您的程序在信号处理程序中度过余生,因为这样所有外部变量都变得无法安全访问(无锁原子除外)。这不是很好。

    在 Windows 上呢?我不知道。

    【讨论】:

    • 您可以使用setjmplongjmp 来退出中止上下文吗?
    • @Barmar 我相信是这样,但这并不能解决在信号处理程序之外访问变量的问题。而且你只能跳到你已经去过的地方。
    • 我在想你会在调用Aborting_Function()之前调用setjmp,然后测试SIGABRT设置的变量。
    【解决方案5】:

    可以从std::abort恢复吗?

    这取决于你所说的恢复是什么意思。来自http://en.cppreference.com/w/cpp/utility/program/abort

    导致程序异常终止,除非SIGABRT 被传递给signal 的信号处理程序捕获并且处理程序不返回。

    如果您希望能够从调用std::abort 的位置继续,那么答案是。如果您希望能够在程序退出之前做某事,那么答案是是的

    我猜您希望能够从调用std::abort 的位置继续。因此,对于您的用例,答案是

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-01-11
      • 1970-01-01
      • 1970-01-01
      • 2013-03-09
      • 2011-07-03
      • 2019-08-01
      相关资源
      最近更新 更多