Skip to content

Further optimized error printing #12

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 19 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
a020752
Optimize and add ability to configure error printing function
rayhamel May 31, 2021
6b3371d
Optimize logging by concatenating strings before printing
rayhamel May 31, 2021
ba09c4b
Make parameters to print functions const, for consistency
rayhamel May 31, 2021
732627c
Use operator<< instead of ostream::write for string_view
rayhamel May 31, 2021
d490d97
Merge branch 'ConfigurableErrorPrinting' into FurtherOptimizedErrorPr…
rayhamel May 31, 2021
c369831
'invert' and eliminate repeated code in the stdio and iostream print_…
rayhamel May 31, 2021
e006adb
Switch back to ostream::write
rayhamel May 31, 2021
0d28933
Change error printing customization point to a variable (function poi…
rayhamel May 31, 2021
92914b2
Add tests for iostream output
rayhamel May 31, 2021
4264c17
Make clang-tidy happy with namespace closing comment
rayhamel Jun 1, 2021
09d4baf
Make print_error an inline variable to silence clang-tidy warning
rayhamel Jun 1, 2021
43b4f9a
Define a stub for cjdb::print_error even when CJDB_SKIP_STDIO is defi…
rayhamel Jun 1, 2021
6977cc1
Update documentation
rayhamel Jun 1, 2021
d553310
Minor formatting change
rayhamel Jun 1, 2021
b8623f5
Another formatting change
rayhamel Jun 1, 2021
a838ca6
Merge branch 'ConfigurableErrorPrinting' into FurtherOptimizedErrorPr…
rayhamel Jun 1, 2021
e2bc28d
Another formatting change
rayhamel Jun 1, 2021
dd5e855
Merge branch 'ConfigurableErrorPrinting' into FurtherOptimizedErrorPr…
rayhamel Jun 1, 2021
994e8c0
Don't need to copy the whole suffix
rayhamel Jun 1, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,8 @@ contracts are always checked at compile-time. If the contract's predicate isn't
program won't compile. This is a huge advantage over using `<cassert>` or the GSL's `Expects` and
`Ensures` macros.

When optimisations are diabled and `NDEBUG` is not defined as a macro, the contract will check your
predicate at run-time. If the predicate fails, then a diagnostic will be emit, and the program will
When optimisations are disabled and `NDEBUG` is not defined as a macro, the contract will check your
predicate at run-time. If the predicate fails, then a diagnostic will be emitted, and the program will
crash.

When optimisations are enabled, and `NDEBUG` remains undefined, the program will emit a diagnostic
Expand Down Expand Up @@ -196,7 +196,11 @@ generation than when the contract isn't used. [See for yourself][__builtin_unrea
</table>

*Rudimentary testing has identified that neither GCC nor Clang perform optimisations <em>before</em>
the contract.
the contract.*

### Configuring diagnostics

By default, diagnostic messages are printed to `stderr` by `std::fwrite`. If `CJDB_USE_IOSTREAM` is defined as a macro, messages are printed with `std::cerr.write` instead. If `CJDB_SKIP_STDIO` is defined as a macro, there is no dependency on either `cstdio` or `iostream` and printing diagnostic messages is a no-op. If you would like to customize how diagnostics are printed, you may set the function pointer `cjdb::print_error` to any function or lambda with the signature `void(std::string_view)`.

### Assertions

Expand Down
54 changes: 46 additions & 8 deletions include/cjdb/contracts.hpp
Original file line number Diff line number Diff line change
@@ -1,13 +1,22 @@

// Copyright (c) Christopher Di Bella.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
#ifndef CJDB_CONTRACTS_HPP
#define CJDB_CONTRACTS_HPP

#include <cstdio>
#include <cstring>
#include <string_view>
#include <type_traits>

#ifdef CJDB_USE_IOSTREAM
#include <iostream>
#elif !defined(CJDB_SKIP_STDIO)
#include <cerrno>
#include <cstdio>
#include <system_error>
#endif // CJDB_USE_IOSTREAM

// clang-tidy doesn't yet support this
//
// #ifndef __cpp_lib_is_constant_evaluated
Expand All @@ -24,22 +33,50 @@
#define CJDB_PRETTY_FUNCTION __PRETTY_FUNCTION__
#endif // _MSC_VER

namespace cjdb::contracts_detail {
namespace cjdb {
using print_error_fn = void(std::string_view);
inline print_error_fn* print_error = [](std::string_view message) {
#ifdef CJDB_USE_IOSTREAM
std::cerr.write(message.data(), static_cast<std::streamsize>(message.size()));
#elif !defined(CJDB_SKIP_STDIO)
if (auto const len = message.size();
std::fwrite(message.data(), sizeof(char), len, stderr) < len) [[unlikely]]
{
throw std::system_error{errno, std::system_category()};
}
#endif // CJDB_USE_IOSTREAM
};

namespace contracts_detail {
#ifdef NDEBUG
inline constexpr auto is_debug = false;
#else
inline constexpr auto is_debug = true;
#endif // NDEBUG

struct contract_impl_fn {
template<std::size_t N1, std::size_t N2>
constexpr void operator()(bool const result,
std::string_view const message,
std::string_view const function) const noexcept
char const(&message)[N1], // NOLINT(modernize-avoid-c-arrays)
char const(&function)[N2]) const noexcept // NOLINT(modernize-avoid-c-arrays)
{
if (not result) {
if (not std::is_constant_evaluated()) {
if constexpr (is_debug) {
std::fprintf(stderr, "%s in `%s`\n", message.data(), function.data());
if constexpr (is_debug) { // NOLINT
#ifdef _WIN32
constexpr auto& suffix = "`\r\n";
#else
constexpr auto& suffix = "`\n";
#endif // _WIN32
constexpr auto message_size = N1 - 1;
constexpr auto function_size = N2 - 1;
// NOLINTNEXTLINE(modernize-avoid-c-arrays)
char full_message[message_size + function_size + sizeof suffix]{};
auto p = full_message;
std::memcpy(p, message, message_size);
std::memcpy(p += message_size, function, function_size);
std::memcpy(p += function_size, suffix, sizeof suffix - 1);
::cjdb::print_error(full_message);
}
}
#ifdef _MSC_VER
Expand All @@ -65,11 +102,12 @@ namespace cjdb::contracts_detail {
}
};
inline constexpr auto matches_bool = matches_bool_fn{};
} // namespace cjdb::contracts_detail
} // namespace contracts_detail
} // namespace cjdb

#define CJDB_CONTRACT_IMPL(CJDB_KIND, ...) \
::cjdb::contracts_detail::contract_impl(::cjdb::contracts_detail::matches_bool(__VA_ARGS__), \
__FILE__ ":" CJDB_TO_STRING(__LINE__) ": " CJDB_KIND " `" #__VA_ARGS__ "` failed", \
__FILE__ ":" CJDB_TO_STRING(__LINE__) ": " CJDB_KIND " `" #__VA_ARGS__ "` failed in `", \
CJDB_PRETTY_FUNCTION)


Expand Down
11 changes: 11 additions & 0 deletions test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,22 @@
#
function(build_contract filename)
set(target "${filename}")
set(target_ios "${filename}_ios")
add_executable("${target}" "${filename}.cpp")
add_executable("${target_ios}" "${filename}.cpp")
if(MSVC)
target_compile_options("${target}" PRIVATE "/permissive-")
target_compile_options("${target_ios}" PRIVATE "/permissive-" "/DCJDB_USE_IOSTREAM")
else()
target_compile_options("${target_ios}" PRIVATE "-DCJDB_USE_IOSTREAM")
endif()
target_include_directories("${target}" PRIVATE "${CMAKE_SOURCE_DIR}/include")
target_include_directories("${target_ios}" PRIVATE "${CMAKE_SOURCE_DIR}/include")
endfunction()

build_contract(pass)
add_test(test.pass pass)
add_test(test.pass_ios pass_ios)

function(test_contract target expected_output)
set(args "${CMAKE_SOURCE_DIR}/test/check-failure.py"
Expand All @@ -23,6 +30,10 @@ function(test_contract target expected_output)
add_test(NAME "test.${target}"
COMMAND python3 ${args}
WORKING_DIRECTORY "${CMAKE_BINARY_DIR}")
list(TRANSFORM args APPEND "_ios" AT 1)
add_test(NAME "test.${target}_ios"
COMMAND python3 ${args}
WORKING_DIRECTORY "${CMAKE_BINARY_DIR}")
endfunction()

function(test_quiet_contract target expected_output)
Expand Down