From 0324ad68eed762ebf00ed4fb5a7ea2f2e3bd77b6 Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Tue, 22 Nov 2016 12:08:21 +0200 Subject: Add diagnostics facility --- butl/buildfile | 3 +- butl/diagnostics | 198 +++++++++++++++++++++++++++++++++++++++++++++++++++ butl/diagnostics.cxx | 41 +++++++++++ butl/utility | 47 +++++++++++- butl/utility.cxx | 10 +++ 5 files changed, 295 insertions(+), 4 deletions(-) create mode 100644 butl/diagnostics create mode 100644 butl/diagnostics.cxx create mode 100644 butl/utility.cxx (limited to 'butl') diff --git a/butl/buildfile b/butl/buildfile index fcb5f86..1607e7d 100644 --- a/butl/buildfile +++ b/butl/buildfile @@ -5,6 +5,7 @@ lib{butl}: \ {hxx cxx}{ base64 } \ {hxx cxx}{ char-scanner } \ +{hxx cxx}{ diagnostics } \ {hxx }{ export } \ {hxx ixx cxx}{ fdstream } \ {hxx ixx cxx}{ filesystem } \ @@ -23,7 +24,7 @@ lib{butl}: \ {hxx txx }{ string-table } \ {hxx cxx}{ timestamp } \ {hxx cxx}{ triplet } \ -{hxx ixx }{ utility } \ +{hxx ixx cxx}{ utility } \ {hxx }{ vector-view } \ {hxx }{ version } diff --git a/butl/diagnostics b/butl/diagnostics new file mode 100644 index 0000000..77f3f9c --- /dev/null +++ b/butl/diagnostics @@ -0,0 +1,198 @@ +// file : butl/diagnostics -*- C++ -*- +// copyright : Copyright (c) 2014-2016 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUTL_DIAGNOSTICS +#define BUTL_DIAGNOSTICS + +#include +#include +#include +#include // move(), forward() +#include // uncaught_exception() + +#include + +namespace butl +{ + // Diagnostic facility base infrastructure. + // + extern std::ostream* diag_stream; // std::cerr by default. + + struct diag_record; + template struct diag_prologue; + template struct diag_mark; + + using diag_epilogue = void (const diag_record&); + + struct diag_record + { + template + friend const diag_record& + operator<< (const diag_record& r, const T& x) + { + r.os << x; + return r; + } + + diag_record (): empty_ (true), epilogue_ (nullptr) {} + + template + explicit + diag_record (const diag_prologue& p) + : empty_ (true), epilogue_ (nullptr) { *this << p;} + + template + explicit + diag_record (const diag_mark& m) + : empty_ (true), epilogue_ (nullptr) { *this << m;} + + ~diag_record () noexcept (false); + + bool + empty () const {return empty_;} + + bool + full () const {return !empty_;} + + void + flush () const; + + void + append (diag_epilogue* e) const + { + if (e != nullptr) + { + assert (epilogue_ == nullptr); // No multiple epilogues support. + epilogue_ = e; + } + + if (empty_) + empty_ = false; + else + os << "\n "; + } + + // Move constructible-only type. + // + // Older versions of libstdc++ don't have the ostringstream move support + // and accuratly detecting its version is non-trivial. So we always use + // the pessimized implementation with libstdc++. Luckily, GCC doesn't seem + // to be needing move due to copy/move elision. + // +#ifdef __GLIBCXX__ + diag_record (diag_record&&); +#else + diag_record (diag_record&& r) +#ifndef __GLIBCXX__ + : os (std::move (r.os)) +#endif + { + empty_ = r.empty_; + epilogue_ = r.epilogue_; + + if (!empty_) + { +#ifdef __GLIBCXX__ + // os << r.os.str (); + assert (false); // No way to copy extra stream data? +#endif + r.empty_ = true; + r.epilogue_ = nullptr; + } + } +#endif + + diag_record& operator= (diag_record&&) = delete; + + diag_record (const diag_record&) = delete; + diag_record& operator= (const diag_record&) = delete; + + public: + mutable std::ostringstream os; + + protected: + mutable bool empty_; + mutable diag_epilogue* epilogue_; + }; + + template + struct diag_prologue: B + { + diag_prologue (diag_epilogue* e = nullptr): B (), epilogue_ (e) {} + + template + diag_prologue (A&&... a) + : B (std::forward (a)...), epilogue_ (nullptr) {} + + template + diag_prologue (diag_epilogue* e, A&&... a) + : B (std::forward (a)...), epilogue_ (e) {} + + template + diag_record + operator<< (const T& x) const + { + diag_record r; + r.append (epilogue_); + B::operator() (r); + r << x; + return r; + } + + friend const diag_record& + operator<< (const diag_record& r, const diag_prologue& p) + { + r.append (p.epilogue_); + p (r); + return r; + } + + private: + diag_epilogue* epilogue_; + }; + + template + struct diag_mark: B + { + diag_mark (): B () {} + + template + diag_mark (A&&... a): B (std::forward (a)...) {} + + template + diag_record + operator<< (const T& x) const + { + return B::operator() () << x; + } + + friend const diag_record& + operator<< (const diag_record& r, const diag_mark& m) + { + return r << m (); + } + }; + + template + struct diag_noreturn_end: B + { + diag_noreturn_end (): B () {} + + template + diag_noreturn_end (A&&... a): B (std::forward (a)...) {} + + [[noreturn]] friend void + operator<< (const diag_record& r, const diag_noreturn_end& e) + { + // We said that we never return which means this end mark cannot be used + // to "maybe not return". And not returning without any diagnostics is + // probably a mistake. + // + assert (r.full ()); + e.B::operator() (r); + } + }; +} + +#endif // BUTL_DIAGNOSTICS diff --git a/butl/diagnostics.cxx b/butl/diagnostics.cxx new file mode 100644 index 0000000..e4c22dc --- /dev/null +++ b/butl/diagnostics.cxx @@ -0,0 +1,41 @@ +// file : butl/diagnostics.cxx -*- C++ -*- +// copyright : Copyright (c) 2014-2016 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#include + +#include // cerr + +using namespace std; + +namespace butl +{ + ostream* diag_stream = &cerr; + + void diag_record:: + flush () const + { + if (!empty_) + { + *diag_stream << os.str () << endl; + empty_ = true; + + if (epilogue_ != nullptr) + epilogue_ (*this); // Can throw. + } + } + + diag_record:: + ~diag_record () noexcept (false) + { + // Don't flush the record if this destructor was called as part of + // the stack unwinding. Right now this means we cannot use this + // mechanism in destructors, which is not a big deal, except for + // one place: exception_guard. So for now we are going to have + // this ugly special check which we will be able to get rid of + // once C++17 uncaught_exceptions() becomes available. + // + if (!std::uncaught_exception () || exception_unwinding_dtor) + flush (); + } +} diff --git a/butl/utility b/butl/utility index a513325..6155207 100644 --- a/butl/utility +++ b/butl/utility @@ -6,9 +6,10 @@ #define BUTL_UTILITY #include -#include // size_t -#include // forward() -#include // strcmp(), strlen() +#include // size_t +#include // move(), forward() +#include // strcmp(), strlen() +#include // uncaught_exception() //#include // hash namespace butl @@ -146,6 +147,46 @@ namespace butl template inline reverse_range reverse_iterate (T&& x) {return reverse_range (std::forward (x));} + + // Call a function if there is an exception. + // + + // True means we are in the body of a destructor that is being called as + // part of the exception stack unwindining. Used to compensate for the + // deficiencies of uncaught_exception() until C++17 uncaught_exceptions() + // becomes available. + // + // @@ MT: will have to be TLS. + // + extern bool exception_unwinding_dtor; + + template + struct exception_guard; + + template + inline exception_guard + make_exception_guard (F f) + { + return exception_guard (std::move (f)); + } + + template + struct exception_guard + { + exception_guard (F f): f_ (std::move (f)) {} + ~exception_guard () + { + if (std::uncaught_exception ()) + { + exception_unwinding_dtor = true; + f_ (); + exception_unwinding_dtor = false; + } + } + + private: + F f_; + }; } #include diff --git a/butl/utility.cxx b/butl/utility.cxx new file mode 100644 index 0000000..d6f418c --- /dev/null +++ b/butl/utility.cxx @@ -0,0 +1,10 @@ +// file : butl/utility.cxx -*- C++ -*- +// copyright : Copyright (c) 2014-2016 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#include + +namespace butl +{ + bool exception_unwinding_dtor = false; +} -- cgit v1.1