#pragma once /** * @file * * @brief This file defines two main structs/classes used in nix error handling. * * ErrorInfo provides a standard payload of error information, with conversion to string * happening in the logger rather than at the call site. * * BaseError is the ancestor of nix specific exceptions (and Interrupted), and contains * an ErrorInfo. * * ErrorInfo structs are sent to the logger as part of an exception, or directly with the * logError or logWarning macros. * See libutil/tests/logging.cc for usage examples. */ #include "suggestions.hh" #include "ref.hh" #include "types.hh" #include "fmt.hh" #include #include #include #include #include #include #include #include /* Before 4.7, gcc's std::exception uses empty throw() specifiers for * its (virtual) destructor and what() in c++11 mode, in violation of spec */ #ifdef __GNUC__ #if __GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 7) #define EXCEPTION_NEEDS_THROW_SPEC #endif #endif namespace nix { typedef enum { lvlError = 0, lvlWarn, lvlNotice, lvlInfo, lvlTalkative, lvlChatty, lvlDebug, lvlVomit } Verbosity; /** * The lines of code surrounding an error. */ struct LinesOfCode { std::optional prevLineOfCode; std::optional errLineOfCode; std::optional nextLineOfCode; }; /** * An abstract type that represents a location in a source file. */ struct AbstractPos { uint32_t line = 0; uint32_t column = 0; /** * Return the contents of the source file. */ virtual std::optional getSource() const { return std::nullopt; }; virtual void print(std::ostream & out) const = 0; std::optional getCodeLines() const; virtual ~AbstractPos() = default; }; std::ostream & operator << (std::ostream & str, const AbstractPos & pos); void printCodeLines(std::ostream & out, const std::string & prefix, const AbstractPos & errPos, const LinesOfCode & loc); struct Trace { std::shared_ptr pos; hintformat hint; bool frame; }; struct ErrorInfo { Verbosity level; hintformat msg; std::shared_ptr errPos; std::list traces; Suggestions suggestions; static std::optional programName; }; std::ostream & showErrorInfo(std::ostream & out, const ErrorInfo & einfo, bool showTrace); /** * BaseError should generally not be caught, as it has Interrupted as * a subclass. Catch Error instead. */ class BaseError : public std::exception { protected: mutable ErrorInfo err; mutable std::optional what_; const std::string & calcWhat() const; public: unsigned int status = 1; // exit status BaseError(const BaseError &) = default; template BaseError(unsigned int status, const Args & ... args) : err { .level = lvlError, .msg = hintfmt(args...) } , status(status) { } template explicit BaseError(const std::string & fs, const Args & ... args) : err { .level = lvlError, .msg = hintfmt(fs, args...) } { } template BaseError(const Suggestions & sug, const Args & ... args) : err { .level = lvlError, .msg = hintfmt(args...), .suggestions = sug } { } BaseError(hintformat hint) : err { .level = lvlError, .msg = hint } { } BaseError(ErrorInfo && e) : err(std::move(e)) { } BaseError(const ErrorInfo & e) : err(e) { } #ifdef EXCEPTION_NEEDS_THROW_SPEC ~BaseError() throw () { }; const char * what() const throw () { return calcWhat().c_str(); } #else const char * what() const noexcept override { return calcWhat().c_str(); } #endif const std::string & msg() const { return calcWhat(); } const ErrorInfo & info() const { calcWhat(); return err; } void pushTrace(Trace trace) { err.traces.push_front(trace); } template void addTrace(std::shared_ptr && e, std::string_view fs, const Args & ... args) { addTrace(std::move(e), hintfmt(std::string(fs), args...)); } void addTrace(std::shared_ptr && e, hintformat hint, bool frame = false); bool hasTrace() const { return !err.traces.empty(); } const ErrorInfo & info() { return err; }; }; #define MakeError(newClass, superClass) \ class newClass : public superClass \ { \ public: \ using superClass::superClass; \ } MakeError(Error, BaseError); MakeError(UsageError, Error); MakeError(UnimplementedError, Error); class SysError : public Error { public: int errNo; template SysError(int errNo_, const Args & ... args) : Error("") { errNo = errNo_; auto hf = hintfmt(args...); err.msg = hintfmt("%1%: %2%", normaltxt(hf.str()), strerror(errNo)); } template SysError(const Args & ... args) : SysError(errno, args ...) { } }; }