Files
cup_edit/ios/include/utils/Panic.h
2022-02-06 13:28:28 +08:00

562 lines
22 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)
#else
# define PANIC_FILE(F) ""
#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(__PRETTY_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(__PRETTY_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(__PRETTY_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(__PRETTY_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