[libc] Add a new test matcher for tests raising floating point exceptions.

This new matcher does not use death tests to check if SIGFPE is raised.
Instead, that a SIGFPE was raised is checked using a SIGFPE signal handler.

Reviewed By: mcgrathr

Differential Revision: https://reviews.llvm.org/D106086
This commit is contained in:
Siva Chandra Reddy 2021-07-15 16:57:36 +00:00
parent d7314b3c09
commit ec14ab9624
7 changed files with 121 additions and 50 deletions

View File

@ -1,3 +1,30 @@
function(add_fp_unittest name)
cmake_parse_arguments(
"MATH_UNITTEST"
"NEED_MPFR" # Optional arguments
"" # Single value arguments
"" # Multi-value arguments
${ARGN}
)
if(MATH_UNITTEST_NEED_MPFR)
if(NOT LIBC_TESTS_CAN_USE_MPFR)
message("WARNING: Math test ${name} will be skipped as MPFR library is not available.")
return()
endif()
endif()
add_libc_unittest(${name} ${MATH_UNITTEST_UNPARSED_ARGUMENTS})
get_fq_target_name(${name} fq_target_name)
if (NOT TARGET ${fq_target_name})
return()
endif()
if(MATH_UNITTEST_NEED_MPFR)
target_link_libraries(${fq_target_name} PRIVATE libcMPFRWrapper -lmpfr -lgmp)
endif()
target_link_libraries(${fq_target_name} PRIVATE LibcFPTestHelpers)
endfunction(add_fp_unittest)
add_subdirectory(__support)
add_subdirectory(ctype)
add_subdirectory(errno)

View File

@ -74,7 +74,7 @@ add_libc_unittest(
if (NOT LLVM_USE_SANITIZER)
# Sanitizers don't like SIGFPE. So, we will run the
# tests which raise SIGFPE only in non-sanitizer builds.
add_libc_unittest(
add_fp_unittest(
enabled_exceptions_test
SUITE
libc_fenv_unittests
@ -88,14 +88,14 @@ if (NOT LLVM_USE_SANITIZER)
libc.utils.FPUtil.fputil
)
add_libc_unittest(
add_fp_unittest(
feholdexcept_test
SUITE
libc_fenv_unittests
SRCS
feholdexcept_test.cpp
DEPENDS
libc.include.signal
libc.include.fenv
libc.src.fenv.feholdexcept
libc.utils.FPUtil.fputil
)

View File

@ -11,6 +11,7 @@
#include "src/fenv/fetestexcept.h"
#include "utils/FPUtil/FEnv.h"
#include "utils/FPUtil/TestHelpers.h"
#include "utils/UnitTest/Test.h"
#include <fenv.h>
@ -34,23 +35,16 @@ TEST(LlvmLibcExceptionStatusTest, RaiseAndCrash) {
FE_DIVBYZERO | FE_INVALID | FE_INEXACT | FE_OVERFLOW | FE_UNDERFLOW;
for (int e : excepts) {
ASSERT_DEATH(
[=] {
__llvm_libc::fputil::disableExcept(FE_ALL_EXCEPT);
__llvm_libc::fputil::enableExcept(e);
ASSERT_EQ(__llvm_libc::feclearexcept(FE_ALL_EXCEPT), 0);
// Raising all exceptions except |e| should not call the
// SIGFPE handler. They should set the exception flag though,
// so we verify that.
int others = allExcepts & ~e;
ASSERT_EQ(__llvm_libc::feraiseexcept(others), 0);
ASSERT_EQ(__llvm_libc::fetestexcept(others), others);
__llvm_libc::fputil::disableExcept(FE_ALL_EXCEPT);
__llvm_libc::fputil::enableExcept(e);
ASSERT_EQ(__llvm_libc::feclearexcept(FE_ALL_EXCEPT), 0);
// Raising all exceptions except |e| should not call the
// SIGFPE handler. They should set the exception flag though,
// so we verify that.
int others = allExcepts & ~e;
ASSERT_EQ(__llvm_libc::feraiseexcept(others), 0);
ASSERT_EQ(__llvm_libc::fetestexcept(others), others);
// We don't check the return value when raising |e| as
// feraiseexcept will not return when it raises an enabled
// exception.
__llvm_libc::feraiseexcept(e);
},
WITH_SIGNAL(SIGFPE));
ASSERT_RAISES_FP_EXCEPT([=] { __llvm_libc::feraiseexcept(e); });
}
}

View File

@ -9,10 +9,10 @@
#include "src/fenv/feholdexcept.h"
#include "utils/FPUtil/FEnv.h"
#include "utils/FPUtil/TestHelpers.h"
#include "utils/UnitTest/Test.h"
#include <fenv.h>
#include <signal.h>
TEST(LlvmLibcFEnvTest, RaiseAndCrash) {
int excepts[] = {FE_DIVBYZERO, FE_INVALID, FE_INEXACT, FE_OVERFLOW,
@ -31,7 +31,6 @@ TEST(LlvmLibcFEnvTest, RaiseAndCrash) {
// When we put back the saved env which has the exception enabled, it
// should crash with SIGFPE.
__llvm_libc::fputil::setEnv(&env);
ASSERT_DEATH([=] { __llvm_libc::fputil::raiseExcept(e); },
WITH_SIGNAL(SIGFPE));
ASSERT_RAISES_FP_EXCEPT([=] { __llvm_libc::fputil::raiseExcept(e); });
}
}

View File

@ -1,32 +1,5 @@
add_libc_testsuite(libc_math_unittests)
function(add_fp_unittest name)
cmake_parse_arguments(
"MATH_UNITTEST"
"NEED_MPFR" # Optional arguments
"" # Single value arguments
"" # Multi-value arguments
${ARGN}
)
if(MATH_UNITTEST_NEED_MPFR)
if(NOT LIBC_TESTS_CAN_USE_MPFR)
message("WARNING: Math test ${name} will be skipped as MPFR library is not available.")
return()
endif()
endif()
add_libc_unittest(${name} ${MATH_UNITTEST_UNPARSED_ARGUMENTS})
get_fq_target_name(${name} fq_target_name)
if (NOT TARGET ${fq_target_name})
return()
endif()
if(MATH_UNITTEST_NEED_MPFR)
target_link_libraries(${fq_target_name} PRIVATE libcMPFRWrapper -lmpfr -lgmp)
endif()
target_link_libraries(${fq_target_name} PRIVATE LibcFPTestHelpers)
endfunction(add_fp_unittest)
add_fp_unittest(
cosf_test
NEED_MPFR

View File

@ -8,8 +8,12 @@
#include "TestHelpers.h"
#include "FEnv.h"
#include "FPBits.h"
#include <memory>
#include <setjmp.h>
#include <signal.h>
#include <string>
namespace __llvm_libc {
@ -70,6 +74,36 @@ template void describeValue<double>(const char *, double,
template void describeValue<long double>(const char *, long double,
testutils::StreamWrapper &);
#if defined(__WIN32) || defined(_WIN64)
#define sigjmp_buf jmp_buf
#define sigsetjmp(buf, save) setjmp(buf)
#define siglongjmp(buf) longjmp(buf)
#endif
static thread_local sigjmp_buf jumpBuffer;
static thread_local bool caughtExcept;
static void sigfpeHandler(int sig) {
caughtExcept = true;
siglongjmp(jumpBuffer, -1);
}
FPExceptMatcher::FPExceptMatcher(FunctionCaller *func) {
auto oldSIGFPEHandler = signal(SIGFPE, &sigfpeHandler);
std::unique_ptr<FunctionCaller> funcUP(func);
caughtExcept = false;
fenv_t oldEnv;
fegetenv(&oldEnv);
if (sigsetjmp(jumpBuffer, 1) == 0)
funcUP->call();
// We restore the previous floating point environment after
// the call to the function which can potentially raise SIGFPE.
fesetenv(&oldEnv);
signal(SIGFPE, oldSIGFPEHandler);
exceptionRaised = caughtExcept;
}
} // namespace testing
} // namespace fputil
} // namespace __llvm_libc

View File

@ -61,6 +61,39 @@ FPMatcher<T, C> getMatcher(T expectedValue) {
return FPMatcher<T, C>(expectedValue);
}
// TODO: Make the matcher match specific exceptions instead of just identifying
// that an exception was raised.
class FPExceptMatcher : public __llvm_libc::testing::Matcher<bool> {
bool exceptionRaised;
public:
class FunctionCaller {
public:
virtual ~FunctionCaller(){};
virtual void call() = 0;
};
template <typename Func> static FunctionCaller *getFunctionCaller(Func func) {
struct Callable : public FunctionCaller {
Func f;
explicit Callable(Func theFunc) : f(theFunc) {}
void call() override { f(); }
};
return new Callable(func);
}
// Takes ownership of func.
explicit FPExceptMatcher(FunctionCaller *func);
bool match(bool unused) { return exceptionRaised; }
void explainError(testutils::StreamWrapper &stream) override {
stream << "A floating point exception should have been raised but it "
<< "wasn't\n";
}
};
} // namespace testing
} // namespace fputil
} // namespace __llvm_libc
@ -98,4 +131,15 @@ FPMatcher<T, C> getMatcher(T expectedValue) {
__llvm_libc::fputil::testing::getMatcher<__llvm_libc::testing::Cond_NE>( \
expected))
#ifdef LLVM_LIBC_TEST_USE_FUCHSIA
#define ASSERT_RAISES_FP_EXCEPT(func) ASSERT_DEATH(func, WITH_SIGNAL(SIGFPE))
#else
#define ASSERT_RAISES_FP_EXCEPT(func) \
ASSERT_THAT( \
true, \
__llvm_libc::fputil::testing::FPExceptMatcher( \
__llvm_libc::fputil::testing::FPExceptMatcher::getFunctionCaller( \
func)))
#endif // LLVM_LIBC_TEST_USE_FUCHSIA
#endif // LLVM_LIBC_UTILS_FPUTIL_TEST_HELPERS_H