/* * File: dbg.cpp * Author: Pete Goodliffe * Version: 1.08 * Date: 7 June 2001 * * Purpose: C++ debugging support library * * Copyright (c) Pete Goodliffe 2001 (pete.goodliffe@pace.co.uk) * * This file is modifiable/redistributable under the terms of the GNU * Lesser General Public License. * * You should have recieved a copy of the GNU General Public License along * with this program; see the file COPYING. If not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 0211-1307, USA. */ #ifndef DBG_ENABLED #define DBG_ENABLED #endif #include "dbg.h" #include #include #include #include #include #include /********************************************************************** * Implementation notes ********************************************************************** * Tested and found to work ok under * - gcc 2.96 * - gcc 3.0 * - bcc32 5.5.1 * - MSVC 6.0 * * MSVC v6.0 * - This platform makes me cry. * - The header doesn't put all the definitions into the std * namespace. * - This means that we have to sacrifice our good namespace-based code * for something more disgusting and primitve. * - Where this has happened, and where in the future I'd really like to * put the "std" namespace back in, I have instead used a STDCLK macro. * See the implementation comment about this below for more grief. * - A documented hack has been made in the dbg.h header file, of slightly * less ghastly proportions. See dbgclock_t there. * - Additionally, the dbg::array_size template utility could be (and was) * more elegantly be written: * template * inline unsigned int array_size(T (&array)[size]) * { * return size; * } * Of course, MSVC doesn't like that. Sigh. The version in dbg.h also * works, its just not quite so nice. * * Other thoughts: * - Break out to debugger facility? * - Only works for ostreams, not all basic_ostreams * - Post-conditions are a bit limited, this is more of a C++ * language limitation, really. *********************************************************************/ /****************************************************************************** * Tedious compiler-specific issues *****************************************************************************/ // Work around MSVC 6.0 #ifdef _MSC_VER #define STDCLK #pragma warning(disable:4786) #else // In an ideal world, the following line would be // namespace STDCLK = std; // However, gcc 2.96 doesn't seem to cope well with namespace aliases. // Sigh. #define STDCLK std #endif // Queiten tedius build warnings on Borland C++ compiler #ifdef __BCPLUSPLUS__ #pragma warn -8066 #pragma warn -8071 #pragma warn -8070 #endif /****************************************************************************** * General dbg library private implementation *****************************************************************************/ namespace { const char *LEVEL_NAMES[] = { "info", "warning", "error", "fatal", "tracing", "debug", "none", "all" }; const char *BEHAVIOUR_NAMES[] = { "assertions_abort", "assertions_throw", "assertions_continue" }; enum constraint_type { why_assertion, why_sentinel, why_unimplemented, why_check_ptr }; const char *TRACE_IN = "->"; const char *TRACE_OUT = "<-"; const char *INDENT = " "; const char *PREFIX = "*** "; const char *TRUE_STRING = "true"; const char *FALSE_STRING = "false"; const unsigned int ALL_SOURCES_MASK = 0xff; struct period_data { size_t no_triggers; STDCLK::clock_t triggered_at; period_data(); }; struct lt_sp { bool operator()(const dbg::source_pos &a, const dbg::source_pos &b) const { if (a.file == b.file) { if (a.func == b.func) { return a.line < b.line; } else { return a.func < b.func; } } else { return a.file < b.file; } } }; typedef std::map source_map_type; typedef std::map period_map_type; dbg::assertion_behaviour behaviour[dbg::all+1] = { dbg::assertions_abort, dbg::assertions_abort, dbg::assertions_abort, dbg::assertions_abort, dbg::assertions_abort, dbg::assertions_abort, dbg::assertions_abort, dbg::assertions_abort }; unsigned int indent_depth = 0; std::string indent_prefix = PREFIX; bool level_prefix = false; bool time_prefix = false; STDCLK::clock_t period = 0; source_map_type sources; period_map_type period_map; period_data::period_data() : no_triggers(0), triggered_at(STDCLK::clock() - period*2) {} /** * Prints a source_pos to the given ostream. */ void print_pos(std::ostream &out, const dbg::source_pos &where) { if (where.file) { if (where.func) { out << "function: " << where.func << ", "; } out << "line: " << where.line << ", file: " << where.file; } } /** * Prints a source_pos to the given ostream in short format * (suitable for trace). */ void print_pos_short(std::ostream &out, const dbg::source_pos &where) { if (where.file) { if (where.func) { out << where.func << " (" << where.line << " in " << where.file << ")"; } else { out << "function at (" << where.line << " in " << where.file << ")"; } } } void print_period_info(std::ostream &out, const dbg::source_pos &where) { if (period) { size_t no_triggers = period_map[where].no_triggers; out << " (triggered " << no_triggers << " time"; if (no_triggers > 1) out << "s)"; else out << ")"; } } /** * Does whatever the assertion_behaviour is set to. If an assertion * is triggered, then this will be called. */ void do_assertion_behaviour(dbg::level lvl, constraint_type why, const dbg::source_pos &pos) { switch (lvl != dbg::fatal ? behaviour[lvl] : dbg::assertions_abort) { case dbg::assertions_abort: { abort(); break; } case dbg::assertions_throw: { switch (why) { default: case why_assertion: { throw dbg::assertion_exception(pos); break; } case why_sentinel: { throw dbg::sentinel_exception(pos); break; } case why_unimplemented: { throw dbg::unimplemented_exception(pos); break; } case why_check_ptr: { throw dbg::check_ptr_exception(pos); break; } } break; } case dbg::assertions_continue: default: { break; } } } /** * Produces a level prefix for the specified level to the * given ostream. */ void do_prefix(dbg::level lvl, std::ostream &s) { if (time_prefix) { STDCLK::time_t t = STDCLK::time(0); if (t != -1) { s << std::string(STDCLK::ctime(&t), 24) << ": "; } } if (level_prefix) { switch (lvl) { case dbg::info: { s << " info: "; break; } case dbg::warning: { s << "warning: "; break; } case dbg::error: { s << " error: "; break; } case dbg::fatal: { s << " fatal: "; break; } case dbg::tracing: { s << " trace: "; break; } case dbg::debug: { s << " debug: "; break; } case dbg::none: { break; } case dbg::all: { s << " all: "; break; } } } } /** * Creates a unsigned int mask that is used as the second value of the * sources map. */ unsigned int dbg_source_mask(dbg::level lvl) { return (lvl != dbg::all) ? 1 << lvl : ALL_SOURCES_MASK; } /** * Returns whether or not a dbg_source has been enabled at the * specified level. */ bool dbg_source_enabled(dbg::level lvl, dbg::dbg_source src) { if (src) { if (sources.find(src) != sources.end()) { return (sources[src] & dbg_source_mask(lvl)) != 0; } else { sources[src] = 0; return false; } } else { return true; } } /** * Used by period_allows below. */ bool period_allows_impl(const dbg::source_pos &where) { period_data &data = period_map[where]; data.no_triggers++; if (data.triggered_at < STDCLK::clock() - period) { data.triggered_at = STDCLK::clock(); return true; } else { return false; } } /** * Returns whether the period allows the constraint at the specified * @ref dbg::source_pos to trigger. This presumes that the assertion * at this position has shown to be broken already. * * This is a small inline function to make code that uses the period * implementation easier to read. */ inline bool period_allows(const dbg::source_pos &where) { return !period || period_allows_impl(where); } } /****************************************************************************** * Logging *****************************************************************************/ namespace { /** * A handy streambuf that sends its input to multiple output streams. */ class dbg_streambuf : public std::streambuf { public: dbg_streambuf(std::vector &ostreams, int bsize = 0); ~dbg_streambuf(); protected: int overflow(int); int sync(); private: void put_buffer(void); void put_char(int); std::vector &ostreams; }; dbg_streambuf::dbg_streambuf(std::vector &o, int bsize) : ostreams(o) { if (bsize) { char *ptr = new char[bsize]; setp(ptr, ptr + bsize); } else { setp(0, 0); } setg(0, 0, 0); } dbg_streambuf::~dbg_streambuf() { sync(); delete[] pbase(); } int dbg_streambuf::overflow(int c) { put_buffer(); if (c != EOF) { if (pbase() == epptr()) { put_char(c); } else { sputc(c); } } return 0; } int dbg_streambuf::sync() { put_buffer(); return 0; } void dbg_streambuf::put_buffer(void) { if (pbase() != pptr()) { std::vector::iterator i = ostreams.begin(); while (i != ostreams.end()) { (*i)->write(pbase(), pptr() - pbase()); ++i; } setp(pbase(), epptr()); } } void dbg_streambuf::put_char(int c) { std::vector::iterator i = ostreams.begin(); while (i != ostreams.end()) { (**i) << static_cast(c); i++; } } /** * A handy streambuf that swallows its output and doesn't burp. */ class null_streambuf : public std::streambuf { public: null_streambuf() {} ~null_streambuf() {} protected: int overflow(int) { return 0; } int sync() { return 0; } }; /** * Adds a std::ostream to the specified vector, preventing multiple * instertions. */ void add_ostream_to(std::vector &vec, std::ostream *o) { if (std::find(vec.begin(), vec.end(), o) == vec.end()) { vec.push_back(o); } } /** * Removes a std::ostream from the specified vector, if it is in there * at all. */ void remove_ostream_from(std::vector &vec, std::ostream *o) { std::vector::iterator i = std::find(vec.begin(), vec.end(), o); if (i != vec.end()) { vec.erase(i); } } /* * ostream vectors */ std::vector info_ostreams; std::vector warning_ostreams; std::vector error_ostreams; std::vector fatal_ostreams; std::vector tracing_ostreams; std::vector debug_ostreams; /* * ostreams */ std::ostream info_ostream(new dbg_streambuf(info_ostreams)); std::ostream warning_ostream(new dbg_streambuf(warning_ostreams)); std::ostream error_ostream(new dbg_streambuf(error_ostreams)); std::ostream fatal_ostream(new dbg_streambuf(fatal_ostreams)); std::ostream tracing_ostream(new dbg_streambuf(tracing_ostreams)); std::ostream debug_ostream(new dbg_streambuf(debug_ostreams)); std::ostream null_ostream(new null_streambuf()); /* * enables */ bool info_enabled = false; bool warning_enabled = false; bool error_enabled = false; bool fatal_enabled = false; bool tracing_enabled = false; bool debug_enabled = false; } void dbg::enable(dbg::level lvl, bool enabled) { debug_ostream << prefix(debug) << "dbg::enable(" << LEVEL_NAMES[lvl] << "," << (enabled ? TRUE_STRING : FALSE_STRING) << ")\n"; static bool initialised = false; if (!initialised) { add_ostream_to(error_ostreams, &std::cerr); add_ostream_to(fatal_ostreams, &std::cerr); initialised = true; } if (lvl == dbg::info || lvl == dbg::all) { info_enabled = enabled; } if (lvl == dbg::warning || lvl == dbg::all) { warning_enabled = enabled; } if (lvl == dbg::error || lvl == dbg::all) { error_enabled = enabled; } if (lvl == dbg::fatal || lvl == dbg::all) { fatal_enabled = enabled; } if (lvl == dbg::debug || lvl == dbg::all) { debug_enabled = enabled; } if (lvl == dbg::tracing || lvl == dbg::all) { tracing_enabled = enabled; } } void dbg::enable(dbg::level lvl, dbg::dbg_source src, bool enabled) { debug_ostream << prefix(debug) << "dbg::enable(" << LEVEL_NAMES[lvl] << ",\"" << src << "\"," << (enabled ? TRUE_STRING : FALSE_STRING) << ")\n"; if (dbg_source_enabled(lvl, src)) { if (!enabled) sources[src] &= ~dbg_source_mask(lvl); } else { if (enabled) sources[src] |= dbg_source_mask(lvl); } } void dbg::enable_all(dbg::level lvl, bool enabled) { debug_ostream << prefix(debug) << "dbg::enable_all(" << LEVEL_NAMES[lvl] << "," << (enabled ? TRUE_STRING : FALSE_STRING) << ")\n"; source_map_type::iterator i = sources.begin(); while (i != sources.end()) { i->second &= ~dbg_source_mask(lvl); if (enabled) { i->second |= dbg_source_mask(lvl); } i++; } } std::ostream &dbg::out(dbg::level lvl, dbg::dbg_source src) { if (!dbg_source_enabled(lvl, src)) { lvl = none; } switch (lvl) { case info: { if (info_enabled) { return info_ostream; } else { return null_ostream; } break; } case warning: { if (warning_enabled) { return warning_ostream; } else { return null_ostream; } break; } case error: { if (error_enabled) { return error_ostream; } else { return null_ostream; } break; } case fatal: { if (fatal_enabled) { return fatal_ostream; } else { return null_ostream; } break; } case tracing: { if (tracing_enabled) { return tracing_ostream; } else { return null_ostream; } break; } case debug: case none: default: { return null_ostream; break; } } } void dbg::attach_ostream(dbg::level lvl, std::ostream &o) { debug_ostream << prefix(debug) << "dbg::attach_ostream(" << LEVEL_NAMES[lvl] << ",ostream)\n"; if (lvl == dbg::info || lvl == dbg::all) { add_ostream_to(info_ostreams, &o); } if (lvl == dbg::warning || lvl == dbg::all) { add_ostream_to(warning_ostreams, &o); } if (lvl == dbg::error || lvl == dbg::all) { add_ostream_to(error_ostreams, &o); } if (lvl == dbg::fatal || lvl == dbg::all) { add_ostream_to(fatal_ostreams, &o); } if (lvl == dbg::tracing || lvl == dbg::all) { add_ostream_to(tracing_ostreams, &o); } if (lvl == dbg::debug || lvl == dbg::all) { add_ostream_to(debug_ostreams, &o); } } void dbg::detach_ostream(dbg::level lvl, std::ostream &o) { if (lvl == dbg::info || lvl == dbg::all) { remove_ostream_from(info_ostreams, &o); } if (lvl == dbg::warning || lvl == dbg::all) { remove_ostream_from(warning_ostreams, &o); } if (lvl == dbg::error || lvl == dbg::all) { remove_ostream_from(error_ostreams, &o); } if (lvl == dbg::fatal || lvl == dbg::all) { remove_ostream_from(fatal_ostreams, &o); } if (lvl == dbg::tracing || lvl == dbg::all) { remove_ostream_from(tracing_ostreams, &o); } if (lvl == dbg::debug || lvl == dbg::all) { remove_ostream_from(debug_ostreams, &o); } } void dbg::detach_all_ostreams(dbg::level lvl) { debug_ostream << prefix(debug) << "dbg::detach_all_ostreams(" << LEVEL_NAMES[lvl] << ")\n"; if (lvl == dbg::info || lvl == dbg::all) { info_ostreams.clear(); } if (lvl == dbg::warning || lvl == dbg::all) { warning_ostreams.clear(); } if (lvl == dbg::error || lvl == dbg::all) { error_ostreams.clear(); } if (lvl == dbg::fatal || lvl == dbg::all) { fatal_ostreams.clear(); } if (lvl == dbg::tracing || lvl == dbg::all) { tracing_ostreams.clear(); } if (lvl == dbg::debug || lvl == dbg::all) { debug_ostreams.clear(); } } /****************************************************************************** * Output formatting *****************************************************************************/ void dbg::set_prefix(const char *pfx) { debug_ostream << prefix(debug) << "dbg::set_prefix(" << pfx << ")\n"; indent_prefix = pfx; } void dbg::enable_level_prefix(bool enabled) { debug_ostream << prefix(debug) << "dbg::enable_level_prefix(" << (enabled ? TRUE_STRING : FALSE_STRING) << ")\n"; level_prefix = enabled; } void dbg::enable_time_prefix(bool enabled) { debug_ostream << prefix(debug) << "dbg::enable_time_prefix(" << (enabled ? TRUE_STRING : FALSE_STRING) << ")\n"; time_prefix = enabled; } std::ostream &dbg::operator<<(std::ostream &s, const prefix &p) { s << indent_prefix.c_str(); do_prefix(p.l, s); return s; } std::ostream &dbg::operator<<(std::ostream &s, const indent &i) { s << indent_prefix.c_str(); do_prefix(i.l, s); for (unsigned int n = 0; n < indent_depth; n++) s << INDENT; return s; } std::ostream &dbg::operator<<(std::ostream &s, const source_pos &pos) { print_pos(s, pos); return s; } /****************************************************************************** * Behaviour *****************************************************************************/ void dbg::set_assertion_behaviour(level lvl, dbg::assertion_behaviour b) { debug_ostream << prefix(debug) << "dbg::set_assertion_behaviour(" << LEVEL_NAMES[lvl] << "," << BEHAVIOUR_NAMES[b] << ")\n"; if (lvl < dbg::all) { behaviour[lvl] = b; } else { for (int n = 0; n < dbg::all; n++) { behaviour[n] = b; } } } void dbg::set_assertion_period(dbgclock_t p) { debug_ostream << prefix(debug) << "dbg::set_assertion_period(" << p << ")\n"; if (!p && period) { period_map.clear(); } period = p; if (p && STDCLK::clock() == -1) { period = p; debug_ostream << prefix(debug) << "*** WARNING ***\n" << "Platform does not support std::clock, so this\n" << "feature is not supported.\n"; } } /****************************************************************************** * Assertion *****************************************************************************/ void dbg::assertion(dbg::level lvl, dbg::dbg_source src, const assert_info &info) { if (dbg_source_enabled(lvl, src) && !info.asserted && period_allows(info)) { out(lvl) << indent(lvl) << "assertion \"" << info.text << "\" failed "; if (src) { out(lvl) << "for \"" << src << "\" "; } out(lvl) << "at "; print_pos(out(lvl), info); print_period_info(out(lvl), info); out(lvl) << "\n"; do_assertion_behaviour(lvl, why_assertion, info); } } /****************************************************************************** * Sentinel *****************************************************************************/ void dbg::sentinel(dbg::level lvl, dbg::dbg_source src, const source_pos &here) { if (dbg_source_enabled(lvl, src) && period_allows(here)) { out(lvl) << indent(lvl) << "sentinel reached at "; print_pos(out(lvl), here); print_period_info(out(lvl), here); out(lvl) << "\n"; do_assertion_behaviour(lvl, why_sentinel, here); } } /****************************************************************************** * Unimplemented *****************************************************************************/ void dbg::unimplemented(dbg::level lvl, dbg::dbg_source src, const source_pos &here) { if (dbg_source_enabled(lvl, src) && period_allows(here)) { out(lvl) << indent(lvl) << "behaviour not yet implemented at "; print_pos(out(lvl), here); print_period_info(out(lvl), here); out(lvl) << "\n"; do_assertion_behaviour(lvl, why_unimplemented, here); } } /****************************************************************************** * Pointer checking *****************************************************************************/ void dbg::check_ptr(dbg::level lvl, dbg::dbg_source src, void *p, const source_pos &here) { if (dbg_source_enabled(lvl, src) && p == 0 && period_allows(here)) { out(lvl) << indent(lvl) << "pointer is zero at "; print_pos(out(lvl), here); print_period_info(out(lvl), here); out(lvl) << "\n"; do_assertion_behaviour(lvl, why_check_ptr, here); } } /****************************************************************************** * Bounds checking *****************************************************************************/ void dbg::check_bounds(dbg::level lvl, dbg::dbg_source src, int index, int bound, const source_pos &here) { if (dbg_source_enabled(lvl, src) && index >= 0 && index >= bound && period_allows(here)) { out(lvl) << indent(lvl) << "index " << index << " is out of bounds (" << bound << ") at "; print_pos(out(lvl), here); print_period_info(out(lvl), here); out(lvl) << "\n"; do_assertion_behaviour(lvl, why_check_ptr, here); } } /****************************************************************************** * Tracing *****************************************************************************/ dbg::trace::trace(func_name_t name) : m_src(0), m_name(name), m_pos(DBG_HERE), m_triggered(false) { if (dbg_source_enabled(tracing, m_src)) { trace_begin(); } } dbg::trace::trace(dbg_source src, func_name_t name) : m_src(src), m_name(name), m_pos(DBG_HERE), m_triggered(false) { if (dbg_source_enabled(tracing, m_src)) { trace_begin(); } } dbg::trace::trace(const source_pos &where) : m_src(0), m_name(0), m_pos(where), m_triggered(false) { if (dbg_source_enabled(tracing, m_src)) { trace_begin(); } } dbg::trace::trace(dbg_source src, const source_pos &where) : m_src(src), m_name(0), m_pos(where), m_triggered(false) { if (dbg_source_enabled(tracing, m_src)) { trace_begin(); } } dbg::trace::~trace() { if (m_triggered) { trace_end(); } } void dbg::trace::trace_begin() { out(tracing) << indent(tracing); indent_depth++; out(tracing) << TRACE_IN; if (m_name) { out(tracing) << m_name; } else { print_pos_short(out(tracing), m_pos); } if (m_src) { out(tracing) << " (for \"" << m_src << "\")"; } out(tracing) << std::endl; m_triggered = true; } void dbg::trace::trace_end() { indent_depth--; out(tracing) << indent(tracing); out(tracing) << TRACE_OUT; if (m_name) { out(tracing) << m_name; } else { print_pos_short(out(tracing), m_pos); } if (m_src) { out(tracing) << " (for \"" << m_src << "\")"; } if (!dbg_source_enabled(tracing, m_src)) { out(tracing) << " (note: this tracing level is disabled)"; } out(tracing) << std::endl; }