diff --git a/libs/utils/include/utils/Panic.h b/libs/utils/include/utils/Panic.h index b4ec032c81..c658da4b14 100644 --- a/libs/utils/include/utils/Panic.h +++ b/libs/utils/include/utils/Panic.h @@ -250,42 +250,71 @@ namespace utils { */ class UTILS_PUBLIC Panic { public: + + using PanicHandlerCallback = void(*)(void* user, Panic const& panic); + + /** + * Sets a user-defined handler for the Panic. If exceptions are enabled, the concrete Panic + * object will be thrown upon return; moreover it is acceptable to throw from the provided + * callback, but it is unsafe to throw the Panic object itself, since it's just an interface. + * It is also acceptable to abort from the callback. If exceptions are not enabled, std::abort() + * will be automatically called upon return. + * + * The PanicHandlerCallback can be called from any thread. + * + * Caveat: this API can misbehave if is used as a static library in multiple translation units, + * some of these translation units might not see the callback. + * + * @param handler pointer to the user defined handler for the Panic + * @param user user pointer given back to the callback + */ + static void setPanicHandler(PanicHandlerCallback handler, void* user) noexcept; + + virtual ~Panic() noexcept; /** - * @return a detailed description of the error + * @return a formatted and detailed description of the error including all available + * information. * @see std::exception */ virtual const char* what() const noexcept = 0; /** - * Get the function name where the panic was detected + * Get the reason string for the panic + * @return a C string containing the reason for the panic + */ + virtual const char* getReason() const noexcept = 0; + + /** + * Get the function name where the panic was detected. On debug build the fully qualified + * function name is returned; on release builds only the function name is. * @return a C string containing the function name where the panic was detected */ virtual const char* getFunction() const noexcept = 0; /** - * Get the file name where the panic was detected + * Get the file name where the panic was detected. Only available on debug builds. * @return a C string containing the file name where the panic was detected */ virtual const char* getFile() const noexcept = 0; /** - * Get the line number in the file where the panic was detected + * Get the line number in the file where the panic was detected. Only available on debug builds. * @return an integer containing the line number in the file where the panic was detected */ virtual int getLine() const noexcept = 0; /** - * Logs this exception to the system-log - */ - virtual void log() const noexcept = 0; - - /** - * Get the CallStack when the panic was detected + * Get the CallStack when the panic was detected if available. * @return the CallStack when the panic was detected */ virtual const CallStack& getCallStack() const noexcept = 0; + + /** + * Logs this exception to the system-log + */ + virtual void log() const noexcept = 0; }; // ----------------------------------------------------------------------------------------------- @@ -305,6 +334,7 @@ public: const char* what() const noexcept override; // Panic interface + const char* getReason() const noexcept override; const char* getFunction() const noexcept override; const char* getFile() const noexcept override; int getLine() const noexcept override; diff --git a/libs/utils/src/Panic.cpp b/libs/utils/src/Panic.cpp index 023a79b247..addec296c1 100644 --- a/libs/utils/src/Panic.cpp +++ b/libs/utils/src/Panic.cpp @@ -14,17 +14,64 @@ * limitations under the License. */ +#include #include +#include +#include -#include -#include #include #include +#include +#include +#include +#include -#include +#include +#include +#include +#include namespace utils { +// ------------------------------------------------------------------------------------------------ + +class UserPanicHandler { + struct CallBack { + Panic::PanicHandlerCallback handler = nullptr; + void* user = nullptr; + void call(Panic const& panic) const noexcept { + if (UTILS_UNLIKELY(handler)) { + handler(user, panic); + } + } + }; + + mutable std::mutex mLock{}; + CallBack mCallBack{}; + + CallBack getCallback() const noexcept { + std::lock_guard const lock(mLock); + return mCallBack; + } + +public: + static UserPanicHandler& get() noexcept { + static UserPanicHandler data; + return data; + } + + void call(Panic const& panic) const noexcept { + getCallback().call(panic); + } + + void set(Panic::PanicHandlerCallback handler, void* user) noexcept { + std::lock_guard const lock(mLock); + mCallBack = { handler, user }; + } +}; + +// ------------------------------------------------------------------------------------------------ + static std::string formatString(const char* format, va_list args) noexcept { std::string reason; @@ -35,10 +82,12 @@ static std::string formatString(const char* format, va_list args) noexcept { if (n >= 0) { ++n; // for the nul-terminating char - char* buf = new char[n]; - vsnprintf(buf, size_t(n), format, args); - reason.assign(buf); - delete [] buf; + char* const buf = new(std::nothrow) char[n]; + if (buf) { + vsnprintf(buf, size_t(n), format, args); + reason.assign(buf); + delete [] buf; + } } return reason; } @@ -63,8 +112,16 @@ static std::string panicString( #endif } +// ------------------------------------------------------------------------------------------------ + Panic::~Panic() noexcept = default; +void Panic::setPanicHandler(PanicHandlerCallback handler, void* user) noexcept { + UserPanicHandler::get().set(handler, user); +} + +// ------------------------------------------------------------------------------------------------ + template TPanic::TPanic(std::string reason) : m_reason(std::move(reason)) { @@ -88,6 +145,11 @@ const char* TPanic::what() const noexcept { return m_msg.c_str(); } +template +const char* TPanic::getReason() const noexcept { + return m_reason.c_str(); +} + template const char* TPanic::getFunction() const noexcept { return m_function; @@ -135,13 +197,22 @@ template void TPanic::panic(char const* function, char const* file, int line, const char* format, ...) { va_list args; va_start(args, format); - std::string reason(formatString(format, args)); + std::string const reason(formatString(format, args)); va_end(args); T e(function, formatFile(file), line, reason); + + // always log the Panic at the point it is detected e.log(); + + // Call the user provided handler + UserPanicHandler::get().call(e); + + // if exceptions are enabled, throw now. #ifdef __EXCEPTIONS - throw e; + throw e; #endif + + // and finally abort if we somehow get here std::abort(); } @@ -152,7 +223,7 @@ namespace details { void panicLog(char const* function, char const* file, int line, const char* format, ...) noexcept { va_list args; va_start(args, format); - std::string reason(formatString(format, args)); + std::string const reason(formatString(format, args)); va_end(args); const std::string msg = panicString("" /* no extra message */,