util/cexec: Add lazy evaluated error checking functions
This inhibits things like error string construction until an error
actually occurs to speed up the good path.
Change-Id: I9a9350259466a2284536d50074c2c42b008da33b
Signed-off-by: William A. Kennington III <wak@google.com>
diff --git a/src/stdplus/signal.cpp b/src/stdplus/signal.cpp
index 85b9f1e..525f2c0 100644
--- a/src/stdplus/signal.cpp
+++ b/src/stdplus/signal.cpp
@@ -8,14 +8,12 @@
namespace signal
{
-using util::callCheckErrno;
-
void block(int signum)
{
sigset_t set;
- callCheckErrno("sigprocmask get", sigprocmask, SIG_BLOCK, nullptr, &set);
- callCheckErrno("sigaddset", sigaddset, &set, signum);
- callCheckErrno("sigprocmask set", sigprocmask, SIG_BLOCK, &set, nullptr);
+ CHECK_ERRNO(sigprocmask(SIG_BLOCK, nullptr, &set), "sigprocmask get");
+ CHECK_ERRNO(sigaddset(&set, signum), "sigaddset");
+ CHECK_ERRNO(sigprocmask(SIG_BLOCK, &set, nullptr), "sigprocmask set");
}
} // namespace signal
diff --git a/src/stdplus/util/cexec.hpp b/src/stdplus/util/cexec.hpp
index 822067a..8a1a3f7 100644
--- a/src/stdplus/util/cexec.hpp
+++ b/src/stdplus/util/cexec.hpp
@@ -5,6 +5,80 @@
#include <type_traits>
#include <utility>
+/** @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 CHECK_ERRNO:
+ *
+ * int our_cpp_call(const char* path)
+ * {
+ * return CHECK_ERRNO(c_call(path), "our msg");
+ * }
+ *
+ * @param[in] expr - The expression returning an errno value
+ * @param[in] error_handler - Passed to the doError function
+ * @throws std::system_error (depends on error_handler) for an error case
+ * @return A successful return value based on the function type
+ */
+#define CHECK_ERRNO(expr, error_handler) \
+ [&](auto check_errno_ret) { \
+ if constexpr (::std::is_pointer_v<decltype(check_errno_ret)>) \
+ { \
+ if (check_errno_ret == nullptr) \
+ ::stdplus::util::doError(errno, (error_handler)); \
+ } \
+ else if constexpr (::std::is_signed_v<decltype(check_errno_ret)>) \
+ { \
+ if (check_errno_ret < 0) \
+ ::stdplus::util::doError(errno, (error_handler)); \
+ } \
+ else \
+ { \
+ static_assert(::std::is_same_v<decltype(check_errno_ret), int>, \
+ "Unimplemented check routine"); \
+ } \
+ return check_errno_ret; \
+ }((expr))
+
+/** @brief Wraps common c style error handling for exception throwing
+ * This requires the callee to provide error information in -r.
+ * See CHECK_ERRNO() for details.
+ *
+ * @param[in] expr - The expression returning an negative error value
+ * @param[in] error_handler - Passed to the doError function
+ * @throws std::system_error (depends on error_handler) for an error case
+ * @return A successful return value based on the function type
+ */
+#define CHECK_RET(expr, error_handler) \
+ [&](auto check_ret_ret) { \
+ if constexpr (::std::is_signed_v<decltype(check_ret_ret)>) \
+ { \
+ if (check_ret_ret < 0) \
+ ::stdplus::util::doError(-check_ret_ret, (error_handler)); \
+ } \
+ else \
+ { \
+ static_assert(::std::is_same_v<decltype(check_ret_ret), int>, \
+ "Unimplemented check routine"); \
+ } \
+ return check_ret_ret; \
+ }((expr))
+
namespace stdplus
{
namespace util
@@ -24,6 +98,33 @@
return std::system_error(error, std::generic_category(), msg);
}
+/** @brief Turns errors into exceptions for the given error handler
+ *
+ * @param[in] error - The numeric system error code
+ * @param[in] msg - The string used to describe the error
+ * @throws A system exception with the given error and msg
+ */
+inline void doError(int error, const char* msg)
+{
+ throw makeSystemError(error, msg);
+}
+inline void doError(int error, const std::string& msg)
+{
+ throw makeSystemError(error, msg.c_str());
+}
+
+/** @brief Turns errors into exceptions for the given error handler
+ *
+ * @param[in] error - The numeric system error code
+ * @param[in] fun - the function used to throw the error
+ */
+template <typename Fun>
+inline std::enable_if_t<std::is_invocable_v<Fun, int>, void> doError(int error,
+ Fun&& fun)
+{
+ fun(error);
+}
+
/** @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
diff --git a/test/util/cexec.cpp b/test/util/cexec.cpp
index c41c2d4..f5b7a5e 100644
--- a/test/util/cexec.cpp
+++ b/test/util/cexec.cpp
@@ -68,6 +68,9 @@
TEST(Cexec, CallCheckErrnoInt)
{
+ EXPECT_EQ(1, CHECK_ERRNO(sample1(), [](int) { throw 0; }));
+ EXPECT_EQ(1, CHECK_ERRNO(sample1(), "sample1"));
+ EXPECT_EQ(1, CHECK_ERRNO(sample1(), std::string("sample1")));
EXPECT_EQ(1, callCheckErrno("sample1", sample1));
EXPECT_EQ(2, callCheckErrno(std::string("sample2"), &sample2, 2));
EXPECT_EQ(4, callCheckErrno("sample::s", sample::s, 4));
@@ -87,6 +90,19 @@
std::string_view(e.what(), strlen(error)));
EXPECT_EQ(EBADF, e.code().value());
}
+
+ try
+ {
+ errno = EBADF;
+ CHECK_ERRNO(sample2(-1), error);
+ 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)
@@ -164,11 +180,23 @@
{
EXPECT_EQ(errno, error);
}
+
+ try
+ {
+ errno = EBADF;
+ CHECK_ERRNO(sample2(-1), [](int r) { throw r; });
+ EXPECT_TRUE(false);
+ }
+ catch (int i)
+ {
+ EXPECT_EQ(EBADF, i);
+ }
}
TEST(Cexec, CallCheckRetInt)
{
EXPECT_EQ(1, callCheckRet("sample1", sample1));
+ EXPECT_EQ(1, CHECK_RET(sample1(), "sample1"));
EXPECT_EQ(2, callCheckRet(std::string("sample2"), &sample2, 2));
EXPECT_EQ(4, callCheckRet("sample::s", sample::s, 4));
ssize_t v = 10;
@@ -187,6 +215,19 @@
std::string_view(e.what(), strlen(error)));
EXPECT_EQ(EINTR, e.code().value());
}
+
+ try
+ {
+ errno = EBADF;
+ CHECK_RET(sample2(-EINTR), error);
+ 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)