util/cexec: Implement exception helpers
See src/stdplus/util/cexec.hpp callCheckErrno for the motivation behind
this change.
Change-Id: I0225b87398b632624f2ef8ccd6c00b5dd6b7e056
Signed-off-by: William A. Kennington III <wak@google.com>
diff --git a/.gitignore b/.gitignore
index f58f9f5..b7f492b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -44,3 +44,4 @@
/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/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 @@
'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 @@
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 @@
'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