diff options
| -rw-r--r-- | .gitignore | 1 | ||||
| -rw-r--r-- | src/Makefile.am | 2 | ||||
| -rw-r--r-- | src/meson.build | 4 | ||||
| -rw-r--r-- | src/stdplus/util/cexec.hpp | 113 | ||||
| -rw-r--r-- | test/Makefile.am | 5 | ||||
| -rw-r--r-- | test/meson.build | 1 | ||||
| -rw-r--r-- | test/util/cexec.cpp | 230 |
7 files changed, 356 insertions, 0 deletions
@@ -44,3 +44,4 @@ Makefile.in /test/handle/copyable /test/handle/managed /test/signal +/test/util/cexec diff --git a/src/Makefile.am b/src/Makefile.am index d45007e..2c1f835 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -10,3 +10,5 @@ nobase_include_HEADERS += stdplus/handle/managed.hpp nobase_include_HEADERS += stdplus/signal.hpp libstdplus_la_SOURCES += stdplus/signal.cpp + +nobase_include_HEADERS += stdplus/util/cexec.hpp diff --git a/src/meson.build b/src/meson.build index a6ff714..8744d33 100644 --- a/src/meson.build +++ b/src/meson.build @@ -23,3 +23,7 @@ install_headers( 'stdplus/handle/copyable.hpp', 'stdplus/handle/managed.hpp', subdir: 'stdplus/handle') + +install_headers( + 'stdplus/util/cexec.hpp', + subdir: 'stdplus/util') diff --git a/src/stdplus/util/cexec.hpp b/src/stdplus/util/cexec.hpp new file mode 100644 index 0000000..86dfe88 --- /dev/null +++ b/src/stdplus/util/cexec.hpp @@ -0,0 +1,113 @@ +#pragma once +#include <functional> +#include <system_error> +#include <type_traits> +#include <utility> + +namespace stdplus +{ +namespace util +{ + +/** @brief Common pattern used by default for constructing a system exception + * @details Most libc or system calls will want to return a generic + * system_error when detecting an error in a call. This function + * creates that error from the errno and message. + * + * @param[in] error - + * @param[in] msg - + * @return The exception passed to a `throw` call. + */ +inline auto makeSystemError(int error, const char* msg) +{ + return std::system_error(error, std::generic_category(), msg); +} + +/** @brief Wraps common c style error handling for exception throwing + * This requires the callee to set errno on error. + * @details We often have a pattern in our code for checking errors and + * propagating up exceptions: + * + * int c_call(const char* path); + * + * int our_cpp_call(const char* path) + * { + * int r = c_call(path); + * if (r < 0) + * { + * throw std::system_error(errno, std::generic_category(), + * "our msg"); + * } + * return r; + * } + * + * To make that more succinct, we can use callCheckErrno: + * + * int our_cpp_call(const char* path) + * { + * return callCheckErrno("our msg", c_call, path); + * } + * + * @param[in] msg - The error message displayed when errno is set. + * @param[in] func - The wrapped function we invoke + * @param[in] args... - The arguments passed to the function + * @throws std::system_error for an error case. + * @return A successful return value based on the function type + */ +template <auto (*makeError)(int, const char*) = makeSystemError, + typename... Args> +inline auto callCheckErrno(const char* msg, Args&&... args) +{ + using Ret = typename std::invoke_result<Args...>::type; + + if constexpr (std::is_integral_v<Ret> && std::is_signed_v<Ret>) + { + Ret r = std::invoke(std::forward<Args>(args)...); + if (r < 0) + throw makeError(errno, msg); + return r; + } + else if constexpr (std::is_pointer_v<Ret>) + { + Ret r = std::invoke(std::forward<Args>(args)...); + if (r == nullptr) + throw makeError(errno, msg); + return r; + } + else + { + static_assert(std::is_same_v<Ret, int>, "Unimplemented check routine"); + } +} + +/** @brief Wraps common c style error handling for exception throwing + * This requires the callee to provide error information in -r. + * See callCheckErrno() for details. + * + * @param[in] msg - The error message displayed when errno is set. + * @param[in] func - The wrapped function we invoke + * @param[in] args... - The arguments passed to the function + * @throws std::system_error for an error case. + * @return A successful return value based on the function type + */ +template <auto (*makeError)(int, const char*) = makeSystemError, + typename... Args> +inline auto callCheckRet(const char* msg, Args&&... args) +{ + using Ret = typename std::invoke_result<Args...>::type; + + if constexpr (std::is_integral_v<Ret> && std::is_signed_v<Ret>) + { + Ret r = std::invoke(std::forward<Args>(args)...); + if (r < 0) + throw makeError(-r, msg); + return r; + } + else + { + static_assert(std::is_same_v<Ret, int>, "Unimplemented check routine"); + } +} + +} // namespace util +} // namespace stdplus diff --git a/test/Makefile.am b/test/Makefile.am index 76cd56f..7698901 100644 --- a/test/Makefile.am +++ b/test/Makefile.am @@ -21,3 +21,8 @@ check_PROGRAMS += signal signal_SOURCES = signal.cpp signal_CPPFLAGS = $(gtest_cppflags) signal_LDADD = $(gtest_ldadd) + +check_PROGRAMS += util/cexec +util_cexec_SOURCES = util/cexec.cpp +util_cexec_CPPFLAGS = $(gtest_cppflags) +util_cexec_LDADD = $(gtest_ldadd) diff --git a/test/meson.build b/test/meson.build index cc3d12d..f11864e 100644 --- a/test/meson.build +++ b/test/meson.build @@ -5,6 +5,7 @@ tests = [ 'signal', 'handle/copyable', 'handle/managed', + 'util/cexec', ] foreach t : tests diff --git a/test/util/cexec.cpp b/test/util/cexec.cpp new file mode 100644 index 0000000..51fdb8b --- /dev/null +++ b/test/util/cexec.cpp @@ -0,0 +1,230 @@ +#include <gtest/gtest.h> +#include <stdplus/util/cexec.hpp> +#include <string_view> +#include <system_error> + +namespace stdplus +{ +namespace util +{ +namespace +{ + +int sample1() +{ + return 1; +} + +int sample2(int val) +{ + return val; +} + +ssize_t sample3(int val, ssize_t* val2) +{ + return *val2 + val; +} + +const char* ptr(const char* p) +{ + return p; +} + +struct sample +{ + int count = 3; + + int one() + { + return count++; + } + + int two(int val) const + { + return val; + } + + int* ptr() + { + return &count; + } + + int* ptr2() + { + return nullptr; + } + + static int s(int val) + { + return val; + } +}; + +int makeTrivialError(int error, const char* msg) +{ + (void)msg; + return error; +} + +TEST(Cexec, CallCheckErrnoInt) +{ + EXPECT_EQ(1, callCheckErrno("sample1", sample1)); + EXPECT_EQ(2, callCheckErrno("sample2", &sample2, 2)); + EXPECT_EQ(4, callCheckErrno("sample::s", sample::s, 4)); + ssize_t v = 10; + EXPECT_EQ(12, callCheckErrno("sample3", sample3, 2, &v)); + + constexpr auto error = "sample2 error"; + try + { + errno = EBADF; + callCheckErrno(error, sample2, -1); + EXPECT_TRUE(false); + } + catch (const std::system_error& e) + { + EXPECT_EQ(std::string_view(error), + std::string_view(e.what(), strlen(error))); + EXPECT_EQ(EBADF, e.code().value()); + } +} + +TEST(Cexec, CallCheckErrnoIntMem) +{ + sample s; + const sample* sp = &s; + EXPECT_EQ(3, callCheckErrno("sample::one", &sample::one, s)); + EXPECT_EQ(4, callCheckErrno("sample::one", &sample::one, &s)); + EXPECT_EQ(5, callCheckErrno("sample::two", &sample::two, sp, 5)); + + constexpr auto error = "sample error"; + try + { + errno = EBADF; + callCheckErrno(error, &sample::two, sp, -1); + EXPECT_TRUE(false); + } + catch (const std::system_error& e) + { + EXPECT_EQ(std::string_view(error), + std::string_view(e.what(), strlen(error))); + EXPECT_EQ(EBADF, e.code().value()); + } +} + +TEST(Cexec, CallCheckErrnoPtr) +{ + constexpr auto sample = "sample"; + EXPECT_EQ(sample, callCheckErrno("sample1", ptr, sample)); + + constexpr auto error = "sample error"; + try + { + errno = EBADF; + callCheckErrno(error, &ptr, nullptr); + EXPECT_TRUE(false); + } + catch (const std::system_error& e) + { + EXPECT_EQ(std::string_view(error), + std::string_view(e.what(), strlen(error))); + EXPECT_EQ(EBADF, e.code().value()); + } +} + +TEST(Cexec, CallCheckErrnoPtrMem) +{ + sample s; + EXPECT_EQ(&s.count, callCheckErrno("sample1", &sample::ptr, &s)); + + constexpr auto error = "sample error"; + try + { + errno = EBADF; + callCheckErrno(error, &sample::ptr2, s); + EXPECT_TRUE(false); + } + catch (const std::system_error& e) + { + EXPECT_EQ(std::string_view(error), + std::string_view(e.what(), strlen(error))); + EXPECT_EQ(EBADF, e.code().value()); + } +} + +TEST(Cexec, CallCheckErrnoErrorFunc) +{ + errno = EBADF; + try + { + callCheckErrno<makeTrivialError>("sample2", sample2, -1); + EXPECT_TRUE(false); + } + catch (int error) + { + EXPECT_EQ(errno, error); + } +} + +TEST(Cexec, CallCheckRetInt) +{ + EXPECT_EQ(1, callCheckRet("sample1", sample1)); + EXPECT_EQ(2, callCheckRet("sample2", &sample2, 2)); + EXPECT_EQ(4, callCheckRet("sample::s", sample::s, 4)); + ssize_t v = 10; + EXPECT_EQ(12, callCheckRet("sample3", sample3, 2, &v)); + + constexpr auto error = "sample2 error"; + try + { + errno = EBADF; + callCheckRet(error, sample2, -EINTR); + EXPECT_TRUE(false); + } + catch (const std::system_error& e) + { + EXPECT_EQ(std::string_view(error), + std::string_view(e.what(), strlen(error))); + EXPECT_EQ(EINTR, e.code().value()); + } +} + +TEST(Cexec, CallCheckRetIntMem) +{ + sample s; + const sample* sp = &s; + EXPECT_EQ(3, callCheckRet("sample::one", &sample::one, s)); + EXPECT_EQ(4, callCheckRet("sample::one", &sample::one, &s)); + EXPECT_EQ(5, callCheckRet("sample::two", &sample::two, sp, 5)); + + constexpr auto error = "sample error"; + try + { + errno = EBADF; + callCheckRet(error, &sample::two, s, -EINTR); + EXPECT_TRUE(false); + } + catch (const std::system_error& e) + { + EXPECT_EQ(std::string_view(error), + std::string_view(e.what(), strlen(error))); + EXPECT_EQ(EINTR, e.code().value()); + } +} + +TEST(Cexec, CallCheckRetErrorFunc) +{ + try + { + callCheckRet<makeTrivialError>("sample2", sample2, -EBADF); + EXPECT_TRUE(false); + } + catch (int error) + { + EXPECT_EQ(EBADF, error); + } +} + +} // namespace +} // namespace util +} // namespace stdplus |

