564 lines
23 KiB
C++
564 lines
23 KiB
C++
/*
|
|
* Copyright (C) 2015 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
#ifndef TNT_UTILS_PANIC_H
|
|
#define TNT_UTILS_PANIC_H
|
|
|
|
#include <string>
|
|
|
|
#include <utils/CallStack.h>
|
|
#include <utils/compiler.h>
|
|
|
|
#ifdef __EXCEPTIONS
|
|
# define UTILS_EXCEPTIONS 1
|
|
#else
|
|
#endif
|
|
|
|
/**
|
|
* @defgroup errors Handling Catastrophic Failures (Panics)
|
|
*
|
|
* @brief Failure detection and reporting facilities
|
|
*
|
|
* ## What's a Panic? ##
|
|
*
|
|
* In the context of this document, a _panic_ is a type of error due to a _contract violation_,
|
|
* it shouldn't be confused with a _result_ or _status_ code. The POSIX API for instance,
|
|
* unfortunately often conflates the two.
|
|
* @see <http://en.wikipedia.org/wiki/Design_by_contract>
|
|
*
|
|
*
|
|
* Here we give the following definition of a _panic_:
|
|
*
|
|
* 1. Failures to meet a function's own **postconditions**\n
|
|
* The function cannot establish one of its own postconditions, such as (but not limited to)
|
|
* producing a valid return value object.
|
|
*
|
|
* Often these failures are only detectable at runtime, for instance they can be caused by
|
|
* arithmetic errors, as it was the case for the Ariane 5 rocket. Ariane 5 crashed because it
|
|
* reused an inertial module from Ariane 4, which didn't account for the greater horizontal
|
|
* acceleration of Ariane 5 and caused an overflow in the computations. Ariane 4's module
|
|
* wasn't per-say buggy, but was improperly used and failed to meet, obviously, certain
|
|
* postconditions.
|
|
* @see <http://en.wikipedia.org/wiki/Cluster_(spacecraft)>
|
|
*
|
|
* 2. Failures to meet the **preconditions** of any of a function's callees\n
|
|
* The function cannot meet a precondition of another function it must call, such as a
|
|
* restriction on a parameter.
|
|
*
|
|
* Not to be confused with the case where the preconditions of a function are already
|
|
* violated upon entry, which indicates a programming error from the caller.
|
|
*
|
|
* Typically these failures can be avoided and arise because of programming errors.
|
|
*
|
|
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
* ## Failure reporting vs. handling ##
|
|
*
|
|
* Very often when a panic, as defined above, is detected, the program has little other choice
|
|
* but to terminate.\n
|
|
* Typically these situations can be handled by _assert()_. However, _assert()_ also conflates two
|
|
* very different concepts: detecting and handling failures.\n
|
|
* The place where a failure is detected is rarely the place where there is enough
|
|
* context to decide what to do. _assert()_ terminates the program which, may or may not be
|
|
* appropriate. At the very least the failure must be logged (which _assert()_ does in a crude way),
|
|
* but some other actions might need to happen, such as:\n
|
|
*
|
|
* - logging the failure in the system-wide logger
|
|
* - providing enough information in development builds to analyze/debug the problem
|
|
* - cleanly releasing some resources, such as communication channels with other processes\n
|
|
* e.g.: to avoid their pre- or postconditions from being violated as well.
|
|
*
|
|
* In some _rare_ cases, the failure might even be ignored altogether because it doesn't matter in
|
|
* the context where it happened. This decision clearly doesn't always lie at the failure-site.
|
|
*
|
|
* It is therefore important to separate failure detection from handling.
|
|
*
|
|
*
|
|
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
* ## Failure detection and handling facilities ##
|
|
*
|
|
* Clearly, catastrophic failures should be **rare**; in fact they should
|
|
* never happen, except possibly for "failures to meet a function's own postconditions", which
|
|
* may depend on external factors and should still be very rare. Yet, when a failure happens, it
|
|
* must be detected and handled appropriately.\n
|
|
* Since panics are rare, it is desirable that the handling mechanism be as unobtrusive
|
|
* as possible, without allowing such failures to go unnoticed or swallowed by mistake. Ideally, the
|
|
* programmer using an API should have nothing special to do to handle that API's failure
|
|
* conditions.\n\n
|
|
*
|
|
* An important feature of the Panic Handling facilities here is that **panics are not part of
|
|
* the API of a function or method**\n\n
|
|
*
|
|
*
|
|
* The panic handling facility has the following benefits:
|
|
* - provides an easy way to detect and report failure
|
|
* - separates failure detection from handling
|
|
* - makes it hard for detected failures to be ignored (i.e.: not handled)
|
|
* - doesn't add burden on the API design
|
|
* - doesn't add overhead (visual or otherwise) at call sites
|
|
* - has very little performance overhead for failure detection
|
|
* - has little to no performance impact for failure handling in the common (success) case
|
|
* - is flexible and extensible
|
|
*
|
|
* Since we have established that failures are **rare**, **exceptional** situations, it would be
|
|
* appropriate to handle them with an _assert_ mechanism and that's what the API below
|
|
* provides. However, under-the-hood it uses C++ exceptions as a means to separate
|
|
* _reporting_ from _handling_.
|
|
*
|
|
* \note On devices where exceptions are not supported or appropriate, these APIs can be turned
|
|
* into a regular _std::terminate()_.
|
|
*
|
|
*
|
|
* ASSERT_PRECONDITION(condition, format, ...)
|
|
* ASSERT_POSTCONDITION(condition, format, ...)
|
|
* ASSERT_ARITHMETIC(condition, format, ...)
|
|
* ASSERT_DESTRUCTOR(condition, format, ...)
|
|
*
|
|
*
|
|
* @see ASSERT_PRECONDITION, ASSERT_POSTCONDITION, ASSERT_ARITHMETIC
|
|
* @see ASSERT_DESTRUCTOR
|
|
*
|
|
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
* ## Writing code that can assert ##
|
|
*
|
|
* Because we've separated failure reporting from failure handling, there are some considerations
|
|
* that need to be thought about when writing code that calls the macros above (i.e.: the program
|
|
* won't terminate at the point where the failure is detected).\n\n
|
|
*
|
|
*
|
|
* ### Panic guarantees ###
|
|
*
|
|
* After the failure condition is reported by a function, additional guarantees may be provided
|
|
* with regards to the state of the program. The following four levels of guarantee are
|
|
* generally recognized, each of which is a strict superset of its successors:
|
|
*
|
|
* 1. Nothrow exception guarantee\n
|
|
* The function never asserts. e.g.: This should always be the case with destructors.\n\n
|
|
*
|
|
* 2. Strong exception guarantee\n
|
|
* If the function asserts, the state of the program is rolled back to the state just before
|
|
* the function call.\n\n
|
|
*
|
|
* 3. Basic exception guarantee\n
|
|
* If the function asserts, the program is in a valid state. It may require cleanup,
|
|
* but all invariants are intact.\n\n
|
|
*
|
|
* 4. No exception guarantee\n
|
|
* If the function asserts, the program may not be in a valid state: resource leaks, memory
|
|
* corruption, or other invariant-destroying failures may have occurred.
|
|
*
|
|
* In each function, give the **strongest** safety guarantee that won't penalize callers who
|
|
* don't need it, but **always give at least the basic guarantee**. The RAII (Resource
|
|
* Acquisition Is Initialization) pattern can help with achieving these guarantees.
|
|
*
|
|
* @see [RAII](http://en.wikipedia.org/wiki/Resource_Acquisition_Is_Initialization)
|
|
*
|
|
* ### Special considerations for Constructors ###
|
|
*
|
|
* Constructors are a bit special because if a failure occurs during their execution, the
|
|
* destructor won't be called (how could it? since the object wasn't constructed!). This can lead
|
|
* to leaked resources allocated in the constructor prior to the failure. Thankfully there is
|
|
* a nice C++ syntax to handle this case:
|
|
*
|
|
* @code
|
|
* Foo::Foo(size_t s) try : m_size(s), m_buf(new uint32_t[s]) {
|
|
* ASSERT_POSTCONDITION(s&0xF==0,
|
|
* "object size is %u, but must be multiple of 16", s);
|
|
* } catch (...) {
|
|
* delete [] m_buf;
|
|
* // the exception will be automatically re-thrown
|
|
* }
|
|
* @endcode
|
|
*
|
|
* Unfortunately, this usage leaks the underlying, exception-based, implementation of the
|
|
* panic handling macros. For this reason, it is best to keep constructors simple and guarantee
|
|
* they can't fail. An _init()_ function with a factory can be used for actual initialization.
|
|
*
|
|
*
|
|
* ### Special considerations for Destructors ###
|
|
*
|
|
* In C++ destructors cannot throw exceptions and since the above macros internally use exceptions
|
|
* they cannot be used in destructors. Doing so will result in immediate termination of the
|
|
* program by _std::terminate()_.\n
|
|
* It is therefore best to always guarantee that destructors won't fail. In case of such a
|
|
* failure in a destructor the ASSERT_DESTRUCTOR() macro can be used instead, it
|
|
* will log the failure but won't terminate the program, instead it'll proceed as if nothing
|
|
* happened. Generally this will result in some resource leak which, eventually, will cause
|
|
* another failure (typically a postcondition violation).\n\n
|
|
*
|
|
* Rationale for this behavior: There are fundamentally no way to report a failure from a
|
|
* destructor in C++, violently terminating the process is inadequate because it again conflates
|
|
* failure reporting and failure handling; for instance a failure in glDeleteTextures() shouldn't
|
|
* be necessarily fatal (certainly not without saving the user's data first). The alternative
|
|
* would be for the caller to swallow the failure entirely, but that's not great either because the
|
|
* failure would go unnoticed. The solution retained here is a compromise.
|
|
*
|
|
* @see ASSERT_DESTRUCTOR
|
|
*
|
|
* ### Testing Code that Uses Panics ###
|
|
*
|
|
* Since panics use exceptions for their underlying implementation, you can test code that uses
|
|
* panics with EXPECT_THROW by doing the following things:
|
|
* \li Set panic mode to THROW (default is TERMINATE)
|
|
* \li Pass Panic to EXPECT_THROW as the exception type
|
|
*
|
|
* Example code for your test file:
|
|
*
|
|
* @code
|
|
* #include <MyClass.hpp> // since your code uses panics, this should include utils/Panic.hpp
|
|
*
|
|
* using utils::Panic;
|
|
*
|
|
* TEST(MyClassTest, value_that_causes_panic) {
|
|
* EXPECT_THROW(MyClass::function(value_that_causes_panic), Panic);
|
|
* }
|
|
*
|
|
* // ... other tests ...
|
|
*
|
|
* int main(int argc, char** argv) {
|
|
* ::testing::InitGoogleTest(&argc, argv);
|
|
* Panic::setMode(Panic::Mode::THROW);
|
|
* return RUN_ALL_TESTS();
|
|
* }
|
|
* @endcode
|
|
*
|
|
*/
|
|
|
|
namespace utils {
|
|
|
|
// -----------------------------------------------------------------------------------------------
|
|
|
|
/**
|
|
* @ingroup errors
|
|
*
|
|
* \brief Base class of all exceptions thrown by all the ASSERT macros
|
|
*
|
|
* The Panic class provides the std::exception protocol, it is the base exception object
|
|
* used for all thrown exceptions.
|
|
*/
|
|
class UTILS_PUBLIC Panic {
|
|
public:
|
|
virtual ~Panic() noexcept;
|
|
|
|
/**
|
|
* @return a detailed description of the error
|
|
* @see std::exception
|
|
*/
|
|
virtual const char* what() const noexcept = 0;
|
|
|
|
/**
|
|
* Get the function name where the panic was detected
|
|
* @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
|
|
* @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
|
|
* @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
|
|
* @return the CallStack when the panic was detected
|
|
*/
|
|
virtual const CallStack& getCallStack() const noexcept = 0;
|
|
};
|
|
|
|
// -----------------------------------------------------------------------------------------------
|
|
|
|
/**
|
|
* @ingroup errors
|
|
*
|
|
* \brief Concrete implementation of the Panic interface.
|
|
*
|
|
* The TPanic<> class implements the std::exception protocol as well as the Panic
|
|
* interface common to all exceptions thrown by the framework.
|
|
*/
|
|
template <typename T>
|
|
class UTILS_PUBLIC TPanic : public Panic {
|
|
public:
|
|
// std::exception protocol
|
|
const char* what() const noexcept override;
|
|
|
|
// Panic interface
|
|
const char* getFunction() const noexcept override;
|
|
const char* getFile() const noexcept override;
|
|
int getLine() const noexcept override;
|
|
const CallStack& getCallStack() const noexcept override;
|
|
void log() const noexcept override;
|
|
|
|
/**
|
|
* Depending on the mode set, either throws an exception of type T with the given reason plus
|
|
* extra information about the error-site, or logs the error and calls std::terminate().
|
|
* This function never returns.
|
|
* @param function the name of the function where the error was detected
|
|
* @param file the file where the above function in implemented
|
|
* @param line the line in the above file where the error was detected
|
|
* @param format printf style string describing the error
|
|
* @see ASSERT_PRECONDITION, ASSERT_POSTCONDITION, ASSERT_ARITHMETIC
|
|
* @see PANIC_PRECONDITION, PANIC_POSTCONDITION, PANIC_ARITHMETIC
|
|
* @see setMode()
|
|
*/
|
|
static void panic(char const* function, char const* file, int line, const char* format, ...)
|
|
UTILS_NORETURN;
|
|
|
|
/**
|
|
* Depending on the mode set, either throws an exception of type T with the given reason plus
|
|
* extra information about the error-site, or logs the error and calls std::terminate().
|
|
* This function never returns.
|
|
* @param function the name of the function where the error was detected
|
|
* @param file the file where the above function in implemented
|
|
* @param line the line in the above file where the error was detected
|
|
* @param s std::string describing the error
|
|
* @see ASSERT_PRECONDITION, ASSERT_POSTCONDITION, ASSERT_ARITHMETIC
|
|
* @see PANIC_PRECONDITION, PANIC_POSTCONDITION, PANIC_ARITHMETIC
|
|
* @see setMode()
|
|
*/
|
|
static inline void panic(char const* function, char const* file, int line, const std::string& s)
|
|
UTILS_NORETURN {
|
|
panic(function, file, line, s.c_str());
|
|
}
|
|
|
|
protected:
|
|
/**
|
|
* Creates a Panic.
|
|
* @param reason a description of the cause of the error
|
|
*/
|
|
explicit TPanic(std::string reason);
|
|
|
|
/**
|
|
* Creates a Panic with extra information about the error-site.
|
|
* @param function the name of the function where the error was detected
|
|
* @param file the file where the above function in implemented
|
|
* @param line the line in the above file where the error was detected
|
|
* @param reason a description of the cause of the error
|
|
*/
|
|
TPanic(char const* function, char const* file, int line, std::string reason);
|
|
|
|
~TPanic() override;
|
|
|
|
private:
|
|
void buildMessage();
|
|
|
|
CallStack m_callstack;
|
|
std::string m_reason;
|
|
char const* const m_function = nullptr;
|
|
char const* const m_file = nullptr;
|
|
const int m_line = -1;
|
|
mutable std::string m_msg;
|
|
};
|
|
|
|
namespace details {
|
|
// these are private, don't use
|
|
void panicLog(
|
|
char const* function, char const* file, int line, const char* format, ...) noexcept;
|
|
} // namespace details
|
|
|
|
// -----------------------------------------------------------------------------------------------
|
|
|
|
/**
|
|
* @ingroup errors
|
|
*
|
|
* ASSERT_PRECONDITION uses this Panic to report a precondition failure.
|
|
* @see ASSERT_PRECONDITION
|
|
*/
|
|
class UTILS_PUBLIC PreconditionPanic : public TPanic<PreconditionPanic> {
|
|
// Programming error, can be avoided
|
|
// e.g.: invalid arguments
|
|
using TPanic<PreconditionPanic>::TPanic;
|
|
friend class TPanic<PreconditionPanic>;
|
|
};
|
|
|
|
/**
|
|
* @ingroup errors
|
|
*
|
|
* ASSERT_POSTCONDITION uses this Panic to report a postcondition failure.
|
|
* @see ASSERT_POSTCONDITION
|
|
*/
|
|
class UTILS_PUBLIC PostconditionPanic : public TPanic<PostconditionPanic> {
|
|
// Usually only detectable at runtime
|
|
// e.g.: dead-lock would occur, arithmetic errors
|
|
using TPanic<PostconditionPanic>::TPanic;
|
|
friend class TPanic<PostconditionPanic>;
|
|
};
|
|
|
|
/**
|
|
* @ingroup errors
|
|
*
|
|
* ASSERT_ARITHMETIC uses this Panic to report an arithmetic (postcondition) failure.
|
|
* @see ASSERT_ARITHMETIC
|
|
*/
|
|
class UTILS_PUBLIC ArithmeticPanic : public TPanic<ArithmeticPanic> {
|
|
// A common case of post-condition error
|
|
// e.g.: underflow, overflow, internal computations errors
|
|
using TPanic<ArithmeticPanic>::TPanic;
|
|
friend class TPanic<ArithmeticPanic>;
|
|
};
|
|
|
|
// -----------------------------------------------------------------------------------------------
|
|
} // namespace utils
|
|
|
|
#ifndef NDEBUG
|
|
# define PANIC_FILE(F) (F)
|
|
# define PANIC_FUNCTION __PRETTY_FUNCTION__
|
|
#else
|
|
# define PANIC_FILE(F) ""
|
|
# define PANIC_FUNCTION __func__
|
|
#endif
|
|
|
|
/**
|
|
* PANIC_PRECONDITION is a macro that reports a PreconditionPanic
|
|
* @param format printf-style string describing the error in more details
|
|
*/
|
|
#define PANIC_PRECONDITION(format, ...) \
|
|
::utils::PreconditionPanic::panic(PANIC_FUNCTION, \
|
|
PANIC_FILE(__FILE__), __LINE__, format, ##__VA_ARGS__)
|
|
|
|
/**
|
|
* PANIC_POSTCONDITION is a macro that reports a PostconditionPanic
|
|
* @param format printf-style string describing the error in more details
|
|
*/
|
|
#define PANIC_POSTCONDITION(format, ...) \
|
|
::utils::PostconditionPanic::panic(PANIC_FUNCTION, \
|
|
PANIC_FILE(__FILE__), __LINE__, format, ##__VA_ARGS__)
|
|
|
|
/**
|
|
* PANIC_ARITHMETIC is a macro that reports a ArithmeticPanic
|
|
* @param format printf-style string describing the error in more details
|
|
*/
|
|
#define PANIC_ARITHMETIC(format, ...) \
|
|
::utils::ArithmeticPanic::panic(PANIC_FUNCTION, \
|
|
PANIC_FILE(__FILE__), __LINE__, format, ##__VA_ARGS__)
|
|
|
|
/**
|
|
* PANIC_LOG is a macro that logs a Panic, and continues as usual.
|
|
* @param format printf-style string describing the error in more details
|
|
*/
|
|
#define PANIC_LOG(format, ...) \
|
|
::utils::details::panicLog(PANIC_FUNCTION, \
|
|
PANIC_FILE(__FILE__), __LINE__, format, ##__VA_ARGS__)
|
|
|
|
/**
|
|
* @ingroup errors
|
|
*
|
|
* ASSERT_PRECONDITION is a macro that checks the given condition and reports a PreconditionPanic
|
|
* if it evaluates to false.
|
|
* @param cond a boolean expression
|
|
* @param format printf-style string describing the error in more details
|
|
*/
|
|
#define ASSERT_PRECONDITION(cond, format, ...) \
|
|
(!UTILS_LIKELY(cond) ? PANIC_PRECONDITION(format, ##__VA_ARGS__) : (void)0)
|
|
|
|
#if defined(UTILS_EXCEPTIONS) || !defined(NDEBUG)
|
|
#define ASSERT_PRECONDITION_NON_FATAL(cond, format, ...) \
|
|
(!UTILS_LIKELY(cond) ? PANIC_PRECONDITION(format, ##__VA_ARGS__), false : true)
|
|
#else
|
|
#define ASSERT_PRECONDITION_NON_FATAL(cond, format, ...) \
|
|
(!UTILS_LIKELY(cond) ? PANIC_LOG(format, ##__VA_ARGS__), false : true)
|
|
#endif
|
|
|
|
|
|
/**
|
|
* @ingroup errors
|
|
*
|
|
* ASSERT_POSTCONDITION is a macro that checks the given condition and reports a PostconditionPanic
|
|
* if it evaluates to false.
|
|
* @param cond a boolean expression
|
|
* @param format printf-style string describing the error in more details
|
|
*
|
|
* Example:
|
|
* @code
|
|
* int& Foo::operator[](size_t index) {
|
|
* ASSERT_POSTCONDITION(index<m_size, "cannot produce a valid return value");
|
|
* return m_array[index];
|
|
* }
|
|
* @endcode
|
|
*/
|
|
#define ASSERT_POSTCONDITION(cond, format, ...) \
|
|
(!UTILS_LIKELY(cond) ? PANIC_POSTCONDITION(format, ##__VA_ARGS__) : (void)0)
|
|
|
|
#if defined(UTILS_EXCEPTIONS) || !defined(NDEBUG)
|
|
#define ASSERT_POSTCONDITION_NON_FATAL(cond, format, ...) \
|
|
(!UTILS_LIKELY(cond) ? PANIC_POSTCONDITION(format, ##__VA_ARGS__), false : true)
|
|
#else
|
|
#define ASSERT_POSTCONDITION_NON_FATAL(cond, format, ...) \
|
|
(!UTILS_LIKELY(cond) ? PANIC_LOG(format, ##__VA_ARGS__), false : true)
|
|
#endif
|
|
|
|
/**
|
|
* @ingroup errors
|
|
*
|
|
* ASSERT_ARITHMETIC is a macro that checks the given condition and reports a ArithmeticPanic
|
|
* if it evaluates to false.
|
|
* @param cond a boolean expression
|
|
* @param format printf-style string describing the error in more details
|
|
*
|
|
* Example:
|
|
* @code
|
|
* unt32_t floatToUInt1616(float v) {
|
|
* v *= 65536;
|
|
* ASSERT_ARITHMETIC(v>=0 && v<65536, "overflow occurred");
|
|
* return uint32_t(v);
|
|
* }
|
|
* @endcode
|
|
*/
|
|
#define ASSERT_ARITHMETIC(cond, format, ...) \
|
|
(!(cond) ? PANIC_ARITHMETIC(format, ##__VA_ARGS__) : (void)0)
|
|
|
|
#if defined(UTILS_EXCEPTIONS) || !defined(NDEBUG)
|
|
#define ASSERT_ARITHMETIC_NON_FATAL(cond, format, ...) \
|
|
(!UTILS_LIKELY(cond) ? PANIC_ARITHMETIC(format, ##__VA_ARGS__), false : true)
|
|
#else
|
|
#define ASSERT_ARITHMETIC_NON_FATAL(cond, format, ...) \
|
|
(!UTILS_LIKELY(cond) ? PANIC_LOG(format, ##__VA_ARGS__), false : true)
|
|
#endif
|
|
|
|
/**
|
|
* @ingroup errors
|
|
*
|
|
* ASSERT_DESTRUCTOR is a macro that checks the given condition and logs an error
|
|
* if it evaluates to false.
|
|
* @param cond a boolean expression
|
|
* @param format printf-style string describing the error in more details
|
|
*
|
|
* @warning Use this macro if a destructor can fail, which should be avoided at all costs.
|
|
* Unlike the other ASSERT macros, this will never result in the process termination. Instead,
|
|
* the error will be logged and the program will continue as if nothing happened.
|
|
*
|
|
* Example:
|
|
* @code
|
|
* Foo::~Foo() {
|
|
* glDeleteTextures(1, &m_texture);
|
|
* GLint err = glGetError();
|
|
* ASSERT_DESTRUCTOR(err == GL_NO_ERROR, "cannot free GL resource!");
|
|
* }
|
|
* @endcode
|
|
*/
|
|
#define ASSERT_DESTRUCTOR(cond, format, ...) (!(cond) ? PANIC_LOG(format, ##__VA_ARGS__) : (void)0)
|
|
|
|
#endif // TNT_UTILS_PANIC_H
|