Skip to content

Commit

Permalink
Configuration file, minimal handler support (#17)
Browse files Browse the repository at this point in the history
* Documentation updates

* implement configuration file inclusion and inline handler,

* Fix emscripten; use minimal trap on all GCCs
  • Loading branch information
dabrahams authored Aug 20, 2024
1 parent c0b7221 commit 13575d2
Show file tree
Hide file tree
Showing 8 changed files with 122 additions and 60 deletions.
15 changes: 10 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -390,11 +390,16 @@ ctest --output-on-failure --test-dir ../build # test

## Reference

- `ADOBE_MINIMAL_TRAP()`: Invoke this macro to inject minimal code
that stops your program; it's usually just one machine instruction
and more efficient than calling `terminate()`. You'd typically use
this macro from a minimal contract violation handler (see the
example project).
- `ADOBE_MINIMAL_TRAP()`: Invoke this macro, followed by a semicolon,
to inject minimal code that stops your program; it's usually just
one machine instruction and more efficient than calling
`terminate()`. You'd typically use this macro from a minimal
contract violation handler (see the example project).

- `ADOBE_MINIMAL_INLINE_CONTRACT_VIOLATION_HANDLER()`: Invoke this
macro from your
[`ADOBE_CONTRACT_CHECKS_CONFIGURATION`](#configuration) file to
inject a handler that only invokes `ADOBE_MINIMAL_TRAP`.

### Configuration

Expand Down
52 changes: 31 additions & 21 deletions include/adobe/contract_checks.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,37 @@
#include <exception>
#include <stdexcept>

// ADOBE_MINIMAL_TRAP(): followed with a semicolon, expands to a
// minimal statement that stops the program.
#if !defined(__EMSCRIPTEN__) && (defined(__clang__) || defined(__GNUC__))
#define ADOBE_MINIMAL_TRAP() __builtin_trap()
#elif defined(_MSC_VER)
#define ADOBE_MINIMAL_TRAP() __debugbreak()
#else
#define ADOBE_MINIMAL_TRAP() std::abort()
#endif

// Injects an inline definition of ::adobe::contract_violated that
// stops the program in the most efficient known way, without any
// diagnostic output. This macro is only useful in your
// ADOBE_CONTRACT_CHECKS_CONFIGURATION file.
#define ADOBE_MINIMAL_INLINE_CONTRACT_VIOLATION_HANDLER() \
namespace adobe { \
[[noreturn]] inline void contract_violated(const char *const, \
int, \
const char *const, \
std::uint32_t const, \
const char *const) noexcept \
{ \
ADOBE_MINIMAL_TRAP(); \
} \
}

// Do not move this #include up; the included file needs to be able to use the above macros.
#ifdef ADOBE_CONTRACT_CHECKS_CONFIGURATION
#include ADOBE_CONTRACT_CHECKS_CONFIGURATION
#endif

#ifdef ADOBE_CONTRACT_VIOLATED_THROWS
#define INTERNAL_ADOBE_CONTRACT_VIOLATED_NOEXCEPT
#else
Expand Down Expand Up @@ -134,31 +165,10 @@ class contract_violation final : public ::std::logic_error

#include <cstdlib>

#if defined(__clang__) || defined(__GNUC__) && __GNUC__ < 10
#define INTERNAL_ADOBE_BUILTIN_TRAP() __builtin_trap()
#elif defined(_MSC_VER)
#define INTERNAL_ADOBE_BUILTIN_TRAP() __debugbreak()
#else
#define INTERNAL_ADOBE_BUILTIN_TRAP() std::abort()
#endif

// Part of a workaround for an MSVC preprocessor bug. See
// https://stackoverflow.com/a/5134656.
#define INTERNAL_ADOBE_MSVC_EXPAND(x) x

// Injects a definition of ::adobe::contract_violated that stops the
// program in the most efficient known way, without any diagnostic
// output.
#define ADOBE_MINIMAL_CONTRACT_VIOLATION_HANDLER() \
[[noreturn]] void ::adobe::contract_violated(const char *const, \
::adobe::contract_violation::kind_t, \
const char *const, \
std::uint32_t const, \
const char *const) noexcept \
{ \
INTERNAL_ADOBE_BUILTIN_TRAP(); \
}

// Contract checking macros take a condition and an optional second argument.
//
// Information on how to simulate optional arguments is here:
Expand Down
13 changes: 10 additions & 3 deletions test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -27,16 +27,18 @@ include(GoogleTest)

# Adds a test called verify_<base_name>, built from <base_name>.cpp
# with test executable target named <base_name>, that passes iff it
# calls abort() and outputs a string matching the regular expression
# <expected_output_re>.
# exits as expected and outputs a string matching the regular
# expression <expected_output_re>. Additional arguments are
# interpreted as source files.
function(adobe_contract_checking_add_test base_name)
add_executable("${base_name}" "${base_name}.cpp" win32_abort_detection.cpp)
add_executable("${base_name}" "${base_name}.cpp" win32_abort_detection.cpp ${ARGN})
adobe_contract_checking_apply_standard_options("${base_name}")
target_link_libraries(
"${base_name}"
PRIVATE
adobe-contract-checking
GTest::gtest_main)
target_include_directories("${base_name}" PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}")
gtest_discover_tests("${base_name}")
if(EMSCRIPTEN)
handle_emscripten_death_tests("${base_name}")
Expand Down Expand Up @@ -73,4 +75,9 @@ endfunction()
adobe_contract_checking_add_test(default_handler_tests)
adobe_contract_checking_add_test(throwing_tests)

adobe_contract_checking_add_test(minimal_configuration_tests
inline_handler_assurance.cpp)
target_compile_definitions(minimal_configuration_tests PRIVATE
"ADOBE_CONTRACT_CHECKS_CONFIGURATION=\"minimal_configuration.hpp\"")

# adobe_contract_checking_add_test(wrapper_header_tests)
32 changes: 1 addition & 31 deletions test/default_handler_tests.cpp
Original file line number Diff line number Diff line change
@@ -1,39 +1,9 @@
#include "adobe/contract_checks.hpp"
#include "portable_death_tests.hpp"
#include <gtest/gtest.h>
#if !defined(GTEST_OS_WINDOWS) && !defined(__EMSCRIPTEN__)
#include <csignal>
#endif

ADOBE_DEFAULT_CONTRACT_VIOLATION_HANDLER()

#if defined(__EMSCRIPTEN__)

// GoogleTest doesn't support death tests under emscripten, so instead
// we handle death by setting the test's WILL_FAIL property in CMake.
// Therefore the test simply executes the code that aborts with no
// wrapper. There's currently no facility for checking test output.

// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define EXPECT_ABORT(code, expected_output_regex) code

#elif defined(GTEST_OS_WINDOWS)
// GoogleTest doesn't support checking for the abort signal on
// Windows, so we use an auxilliary file, win32_abort_detection.cpp,
// to ensure that an unusual string is printed, which we can check for
// with the EXPECT_DEATH macro.

// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define EXPECT_ABORT(code, expected_output_regex) \
EXPECT_DEATH(code, expected_output_regex ".*\n*##ABORTED##");

#else

// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define EXPECT_ABORT(code, expected_output_regex) \
EXPECT_EXIT(code, testing::KilledBySignal(SIGABRT), expected_output_regex);

#endif

TEST(DefaultHandler, ContractNonViolationsDoNotCauseAbort)
{
ADOBE_PRECONDITION(true);
Expand Down
4 changes: 4 additions & 0 deletions test/inline_handler_assurance.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// This file merely ensures that the handler was defined inline by including the file that defines
// it in a second translation unit. The program should fail to link if the handler was defined
// out-of-line.
#include "adobe/contract_checks.hpp"// NOLINT
1 change: 1 addition & 0 deletions test/minimal_configuration.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ADOBE_MINIMAL_INLINE_CONTRACT_VIOLATION_HANDLER()
18 changes: 18 additions & 0 deletions test/minimal_configuration_tests.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#include "adobe/contract_checks.hpp"
#include "portable_death_tests.hpp"
#include <gtest/gtest.h>

TEST(MinimalConfigurationDeathTests, FailedChecksDie)
{
EXPECT_PORTABLE_DEATH(ADOBE_PRECONDITION(false), "");
EXPECT_PORTABLE_DEATH(ADOBE_POSTCONDITION(false), "");
EXPECT_PORTABLE_DEATH(ADOBE_INVARIANT(false), "");
}

// ****** This should be the last test in the file. *********
//
// For unknown reasons if the last test is a death test, under
// emscripten, the test fails even if the executable aborts.
#if defined(__EMSCRIPTEN__)
TEST(MinimalConfiguration, EmscriptenDummy) {}
#endif
47 changes: 47 additions & 0 deletions test/portable_death_tests.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
#include <gtest/gtest.h>
#if !defined(GTEST_OS_WINDOWS) && !defined(__EMSCRIPTEN__)
#include <csignal>
#endif

// gtest-style test macros that, given the use of handle_emscripten_death_tests in CMakeLists.txt,
// portably detect various kinds of abnormal exits.
//
// EXPECT_ABORT: succeeds when the test case aborts.
// EXPECT_PORTABLE_DEATH: succeeds when the test case exits abnormally.

#if defined(__EMSCRIPTEN__)

// GoogleTest doesn't support death tests under emscripten, so instead
// we handle death by setting the test's WILL_FAIL property in CMake.
// Therefore the test simply executes the code that aborts with no
// wrapper. There's currently no facility for checking test output.

// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define EXPECT_ABORT(code, expected_output_regex) code

// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define EXPECT_PORTABLE_DEATH(code, expected_output_regex) code

#elif defined(GTEST_OS_WINDOWS)
// GoogleTest doesn't support checking for the abort signal on
// Windows, so we use an auxilliary file, win32_abort_detection.cpp,
// to ensure that an unusual string is printed, which we can check for
// with the EXPECT_DEATH macro.

// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define EXPECT_ABORT(code, expected_output_regex) \
EXPECT_DEATH(code, expected_output_regex ".*\n*##ABORTED##");

// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define EXPECT_PORTABLE_DEATH(code, expected_output_regex) EXPECT_DEATH(code, expected_output_regex)

#else

// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define EXPECT_ABORT(code, expected_output_regex) \
EXPECT_EXIT(code, testing::KilledBySignal(SIGABRT), expected_output_regex);

// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define EXPECT_PORTABLE_DEATH(code, expected_output_regex) EXPECT_DEATH(code, expected_output_regex)

#endif

0 comments on commit 13575d2

Please sign in to comment.