#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 "fmt.hh" #include #include #include #include #include #include #include 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; }; struct Pos; void printCodeLines(std::ostream & out, const std::string & prefix, const Pos & errPos, const LinesOfCode & loc); /** * When a stack frame is printed. */ enum struct TracePrint { /** * The default behavior; always printed when `--show-trace` is set. */ Default, /** Always printed. Produced by `builtins.addErrorContext`. */ Always, }; struct Trace { std::shared_ptr pos; HintFmt hint; TracePrint print = TracePrint::Default; }; inline std::strong_ordering operator<=>(const Trace& lhs, const Trace& rhs); struct ErrorInfo { Verbosity level; HintFmt msg; std::shared_ptr pos; std::list traces; /** * Some messages are generated directly by expressions; notably `builtins.warn`, `abort`, `throw`. * These may be rendered differently, so that users can distinguish them. */ bool isFromExpr = false; /** * Exit status. */ unsigned int status = 1; 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; /** * Cached formatted contents of `err.msg`. */ mutable std::optional what_; /** * Format `err.msg` and set `what_` to the resulting value. */ const std::string & calcWhat() const; public: BaseError(const BaseError &) = default; BaseError& operator=(const BaseError &) = default; BaseError& operator=(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(HintFmt hint) : err { .level = lvlError, .msg = hint } { } BaseError(ErrorInfo && e) : err(std::move(e)) { } BaseError(const ErrorInfo & e) : err(e) { } /** The error message without "error: " prefixed to it. */ std::string message() { return err.msg.str(); } const char * what() const noexcept override { return calcWhat().c_str(); } const std::string & msg() const { return calcWhat(); } const ErrorInfo & info() const { calcWhat(); return err; } void withExitStatus(unsigned int status) { err.status = status; } void atPos(std::shared_ptr pos) { err.pos = pos; } 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, HintFmt hint, TracePrint print = TracePrint::Default); 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); /** * To use in catch-blocks. */ MakeError(SystemError, Error); /** * POSIX system error, created using `errno`, `strerror` friends. * * Throw this, but prefer not to catch this, and catch `SystemError` * instead. This allows implementations to freely switch between this * and `windows::WinError` without breaking catch blocks. * * However, it is permissible to catch this and rethrow so long as * certain conditions are not met (e.g. to catch only if `errNo = * EFooBar`). In that case, try to also catch the equivalent `windows::WinError` * code. * * @todo Rename this to `PosixError` or similar. At this point Windows * support is too WIP to justify the code churn, but if it is finished * then a better identifier becomes moe worth it. */ class SysError : public SystemError { public: int errNo; /** * Construct using the explicitly-provided error number. `strerror` * will be used to try to add additional information to the message. */ template SysError(int errNo, const Args & ... args) : SystemError(""), errNo(errNo) { auto hf = HintFmt(args...); err.msg = HintFmt("%1%: %2%", Uncolored(hf.str()), strerror(errNo)); } /** * Construct using the ambient `errno`. * * Be sure to not perform another `errno`-modifying operation before * calling this constructor! */ template SysError(const Args & ... args) : SysError(errno, args ...) { } }; #ifdef _WIN32 namespace windows { class WinError; } #endif /** * Convenience alias for when we use a `errno`-based error handling * function on Unix, and `GetLastError()`-based error handling on on * Windows. */ using NativeSysError = #ifdef _WIN32 windows::WinError #else SysError #endif ; /** * Throw an exception for the purpose of checking that exception * handling works; see 'initLibUtil()'. */ void throwExceptionSelfCheck(); /** * Print a message and abort(). */ [[noreturn]] void panic(std::string_view msg); /** * Print a basic error message with source position and abort(). * Use the unreachable() macro to call this. */ [[noreturn]] void panic(const char * file, int line, const char * func); /** * Print a basic error message with source position and abort(). * * @note: This assumes that the logger is operational */ #define unreachable() (::nix::panic(__FILE__, __LINE__, __func__)) }