// Copyright(C) 1999-2020, 2022 National Technology & Engineering Solutions // of Sandia, LLC (NTESS). Under the terms of Contract DE-NA0003525 with // NTESS, the U.S. Government retains certain rights in this software. // // See packages/seacas/LICENSE for details #if !defined(SMART_ASSERT_H) #define SMART_ASSERT_H #if _MSC_VER > 1000 #pragma once #endif // _MSC_VER > 1000 #if _MSC_VER > 1000 // note: // moving this after pragma push will render it useless (VC6) // // identifier truncated to 255 chars in debug information #pragma warning(disable : 4786) #pragma warning(push) // *this used in base-member initialization; it's ok #pragma warning(disable : 4355) #endif #include #include #include #include #include #include enum { // default behavior - just loges this assert // (a message is shown to the user to the console) lvl_warn = 100, // default behavior - asks the user what to do: // Ignore/ Retry/ etc. lvl_debug = 200, // default behavior - throws a smart_assert_error lvl_error = 300, // default behavior - dumps all assert context to console, // and aborts lvl_fatal = 1000 }; /* contains details about a failed assertion */ class assert_context { using string = std::string; public: assert_context() {} // where the assertion failed: file & line void set_file_line(const char *file, int line) { file_ = file; line_ = line; } const string &get_context_file() const { return file_; } int get_context_line() const { return line_; } // get/ set expression void set_expr(const string &str) { expr_ = str; } const string &get_expr() const { return expr_; } typedef std::pair val_and_str; using vals_array = std::vector; // return values array as a vector of pairs: // [Value, corresponding string] const vals_array &get_vals_array() const { return vals_; } // adds one value and its corresponding string void add_val(const string &val, const string &str) { vals_.push_back(val_and_str(val, str)); } // get/set level of assertion void set_level(int nLevel) { level_ = nLevel; } int get_level() const { return level_; } // get/set (user-friendly) message void set_level_msg(const char *strMsg) { if (strMsg != nullptr) { msg_ = strMsg; } else { msg_.erase(); } } const string &get_level_msg() const { return msg_; } private: // where the assertion occurred string file_{}; int line_{0}; // expression and values string expr_{}; vals_array vals_{}; // level and message int level_{lvl_debug}; string msg_{}; }; namespace smart_assert { using assert_func = void (*)(const assert_context &); // helpers std::string get_typeof_level(int nLevel); void dump_context_summary(const assert_context &context, std::ostream &out); void dump_context_detail(const assert_context &context, std::ostream &out); // defaults void default_warn_handler(const assert_context &context); void default_debug_handler(const assert_context &context); void default_error_handler(const assert_context &context); void default_fatal_handler(const assert_context &context); void default_logger(const assert_context &context); } // namespace smart_assert namespace Private { void init_assert(); void set_default_log_stream(std::ostream &out); void set_default_log_name(const char *str); // allows finding if a value is of type 'const char *' // and is null; if so, we cannot print it to an ostream // directly!!! template struct is_null_finder { bool is(const T & /*unused*/) const { return false; } }; template <> struct is_null_finder { bool is(char *const &val) { return val == nullptr; } }; template <> struct is_null_finder { bool is(const char *const &val) { return val == nullptr; } }; } // namespace Private struct Assert { using assert_func = smart_assert::assert_func; // helpers, in order to be able to compile the code Assert &SMART_ASSERT_A; Assert &SMART_ASSERT_B; explicit Assert(const char *expr) : SMART_ASSERT_A(*this), SMART_ASSERT_B(*this), needs_handling_(true) { context_.set_expr(expr); if ((logger() == nullptr) || handlers().size() < 4) { // used before main! Private::init_assert(); } } Assert(const Assert &other) : SMART_ASSERT_A(*this), SMART_ASSERT_B(*this), context_(other.context_), needs_handling_(true) { other.needs_handling_ = false; } ~Assert() { if (needs_handling_) { handle_assert(); } } template Assert &print_current_val(const type &val, const char *my_msg) { std::ostringstream out; Private::is_null_finder f; bool bIsNull = f.is(val); if (!bIsNull) { out << val; } else { // null string out << "null"; } context_.add_val(out.str(), my_msg); return *this; } Assert &print_context(const char *file, int line) { context_.set_file_line(file, line); return *this; } Assert &msg(const char *strMsg) { context_.set_level_msg(strMsg); return *this; } Assert &level(int nLevel, const char *strMsg = nullptr) { context_.set_level(nLevel); context_.set_level_msg(strMsg); return *this; } Assert &warn(const char *strMsg = nullptr) { return level(lvl_warn, strMsg); } Assert &debug(const char *strMsg = nullptr) { return level(lvl_debug, strMsg); } Assert &error(const char *strMsg = nullptr) { return level(lvl_error, strMsg); } Assert &fatal(const char *strMsg = nullptr) { return level(lvl_fatal, strMsg); } // in this case, we set the default logger, and make it // write everything to this file static void set_log(const char *strFileName) { Private::set_default_log_name(strFileName); logger() = &smart_assert::default_logger; } // in this case, we set the default logger, and make it // write everything to this log static void set_log(std::ostream &out) { Private::set_default_log_stream(out); logger() = &smart_assert::default_logger; } static void set_log(assert_func log) { logger() = log; } static void set_handler(int nLevel, assert_func handler) { handlers()[nLevel] = handler; } private: // handles the current assertion. void handle_assert() { logger()(context_); get_handler(context_.get_level())(context_); } /* IMPORTANT NOTE: The only reason logger & handlers are functions, are because you might use SMART_ASSERT before main(). In this case, since they're statics, they might not be initialized. However, making them functions will make it work. */ // the log static assert_func &logger() { static assert_func inst; return inst; } // the handler typedef std::map handlers_collection; static handlers_collection &handlers() { static handlers_collection inst; return inst; } static assert_func get_handler(int nLevel) { handlers_collection::const_iterator found = handlers().find(nLevel); if (found != handlers().end()) { return (*found).second; } // we always assume the debug handler has been set return (*handlers().find(lvl_debug)).second; } private: assert_context context_{}; mutable bool needs_handling_{true}; }; namespace smart_assert { inline Assert make_assert(const char *expr) { return Assert(expr); } } // namespace smart_assert //////////////////////////////////////////////////////// // macro trickery // note: NEVER define SMART_ASSERT_DEBUG directly // (it will be overridden); // // #define SMART_ASSERT_DEBUG_MODE instead #ifdef SMART_ASSERT_DEBUG_MODE #if SMART_ASSERT_DEBUG_MODE == 1 #define SMART_ASSERT_DEBUG #else #undef SMART_ASSERT_DEBUG #endif #else // defaults #ifndef NDEBUG #define SMART_ASSERT_DEBUG #else #undef SMART_ASSERT_DEBUG #endif #endif #ifdef SMART_ASSERT_DEBUG // "debug" mode #define SMART_ASSERT(expr) \ if ((expr)) \ ; \ else \ (void)::smart_assert::make_assert(#expr).print_context(__FILE__, __LINE__).SMART_ASSERT_A /**/ #else // "release" mode #define SMART_ASSERT(expr) \ if (true) \ ; \ else \ (void)::smart_assert::make_assert("").SMART_ASSERT_A /**/ #endif // ifdef SMART_ASSERT_DEBUG #define SMART_VERIFY(expr) \ if ((expr)) \ ; \ else \ (void)::smart_assert::make_assert(#expr) \ .error() \ .print_context(__FILE__, __LINE__) \ .SMART_ASSERT_A /**/ #define SMART_ASSERT_A(x) SMART_ASSERT_OP(x, B) #define SMART_ASSERT_B(x) SMART_ASSERT_OP(x, A) #define SMART_ASSERT_OP(x, next) SMART_ASSERT_A.print_current_val((x), #x).SMART_ASSERT_##next /**/ #if _MSC_VER > 1000 #pragma warning(pop) #endif #endif