Compare commits

...

1 Commits

Author SHA1 Message Date
Sergei Zimmerman
2ded675e56 treewide: Make exceptions cloneable
This is needed to make it possible to store exceptions in failed values
with each new rethrow getting a fresh copy of the exception object.
2026-02-18 19:45:49 +03:00
25 changed files with 83 additions and 63 deletions

View File

@@ -11,7 +11,7 @@
namespace nix::eval_cache { namespace nix::eval_cache {
CachedEvalError::CachedEvalError(ref<AttrCursor> cursor, Symbol attr) CachedEvalError::CachedEvalError(ref<AttrCursor> cursor, Symbol attr)
: EvalError(cursor->root->state, "cached failure of attribute '%s'", cursor->getAttrPathStr(attr)) : CloneableError(cursor->root->state, "cached failure of attribute '%s'", cursor->getAttrPathStr(attr))
, cursor(cursor) , cursor(cursor)
, attr(attr) , attr(attr)
{ {

View File

@@ -14,7 +14,7 @@ namespace nix::eval_cache {
struct AttrDb; struct AttrDb;
class AttrCursor; class AttrCursor;
struct CachedEvalError : EvalError struct CachedEvalError : CloneableError<CachedEvalError, EvalError>
{ {
const ref<AttrCursor> cursor; const ref<AttrCursor> cursor;
const Symbol attr; const Symbol attr;

View File

@@ -18,7 +18,7 @@ class EvalErrorBuilder;
* *
* Most subclasses should inherit from `EvalError` instead of this class. * Most subclasses should inherit from `EvalError` instead of this class.
*/ */
class EvalBaseError : public Error class EvalBaseError : public CloneableError<EvalBaseError, Error>
{ {
template<class T> template<class T>
friend class EvalErrorBuilder; friend class EvalErrorBuilder;
@@ -26,14 +26,14 @@ public:
EvalState & state; EvalState & state;
EvalBaseError(EvalState & state, ErrorInfo && errorInfo) EvalBaseError(EvalState & state, ErrorInfo && errorInfo)
: Error(errorInfo) : CloneableError(errorInfo)
, state(state) , state(state)
{ {
} }
template<typename... Args> template<typename... Args>
explicit EvalBaseError(EvalState & state, const std::string & formatString, const Args &... formatArgs) explicit EvalBaseError(EvalState & state, const std::string & formatString, const Args &... formatArgs)
: Error(formatString, formatArgs...) : CloneableError(formatString, formatArgs...)
, state(state) , state(state)
{ {
} }
@@ -60,23 +60,23 @@ MakeError(InfiniteRecursionError, EvalError);
* Inherits from EvalBaseError (not EvalError) because resource exhaustion * Inherits from EvalBaseError (not EvalError) because resource exhaustion
* should not be cached. * should not be cached.
*/ */
struct StackOverflowError : public EvalBaseError struct StackOverflowError : public CloneableError<StackOverflowError, EvalBaseError>
{ {
StackOverflowError(EvalState & state) StackOverflowError(EvalState & state)
: EvalBaseError(state, "stack overflow; max-call-depth exceeded") : CloneableError(state, "stack overflow; max-call-depth exceeded")
{ {
} }
}; };
MakeError(IFDError, EvalBaseError); MakeError(IFDError, EvalBaseError);
struct InvalidPathError : public EvalError struct InvalidPathError : public CloneableError<InvalidPathError, EvalError>
{ {
public: public:
Path path; Path path;
InvalidPathError(EvalState & state, const Path & path) InvalidPathError(EvalState & state, const Path & path)
: EvalError(state, "path '%s' is not valid", path) : CloneableError(state, "path '%s' is not valid", path)
{ {
} }
}; };

View File

@@ -9,14 +9,14 @@
namespace nix { namespace nix {
class BadNixStringContextElem : public Error class BadNixStringContextElem final : public CloneableError<BadNixStringContextElem, Error>
{ {
public: public:
std::string_view raw; std::string_view raw;
template<typename... Args> template<typename... Args>
BadNixStringContextElem(std::string_view raw_, const Args &... args) BadNixStringContextElem(std::string_view raw_, const Args &... args)
: Error("") : CloneableError("")
{ {
raw = raw_; raw = raw_;
auto hf = HintFmt(args...); auto hf = HintFmt(args...);

View File

@@ -1312,11 +1312,12 @@ static void prim_warn(EvalState & state, const PosIdx pos, Value ** args, Value
state.forceString(*args[0], pos, "while evaluating the first argument; the message passed to builtins.warn"); state.forceString(*args[0], pos, "while evaluating the first argument; the message passed to builtins.warn");
{ {
BaseError msg(std::string{msgStr}); ErrorInfo info{
msg.atPos(state.positions[pos]); .level = lvlWarn,
auto info = msg.info(); .msg = HintFmt(std::string(msgStr)),
info.level = lvlWarn; .pos = state.positions[pos],
info.isFromExpr = true; .isFromExpr = true,
};
logWarning(info); logWarning(info);
} }

View File

@@ -74,11 +74,11 @@ namespace nix {
struct GitSourceAccessor; struct GitSourceAccessor;
struct GitError : public Error struct GitError final : public CloneableError<GitError, Error>
{ {
template<typename... Ts> template<typename... Ts>
GitError(const git_error & error, Ts &&... args) GitError(const git_error & error, Ts &&... args)
: Error("") : CloneableError("")
{ {
auto hf = HintFmt(std::forward<Ts>(args)...); auto hf = HintFmt(std::forward<Ts>(args)...);
err.msg = HintFmt("%1%: %2% (libgit2 error code = %3%)", Uncolored(hf.str()), error.message, error.klass); err.msg = HintFmt("%1%: %2% (libgit2 error code = %3%)", Uncolored(hf.str()), error.message, error.klass);

View File

@@ -28,7 +28,7 @@
namespace nix { namespace nix {
AwsAuthError::AwsAuthError(int errorCode) AwsAuthError::AwsAuthError(int errorCode)
: Error("AWS authentication error: '%s' (%d)", aws_error_str(errorCode), errorCode) : CloneableError("AWS authentication error: '%s' (%d)", aws_error_str(errorCode), errorCode)
, errorCode(errorCode) , errorCode(errorCode)
{ {
} }

View File

@@ -1,11 +1,11 @@
#include "nix/store/build/goal.hh" #include "nix/store/build/goal.hh"
#include "nix/store/build/worker.hh" #include "nix/store/build/worker.hh"
#include "nix/store/globals.hh" #include "nix/store/worker-settings.hh"
namespace nix { namespace nix {
TimedOut::TimedOut(time_t maxDuration) TimedOut::TimedOut(time_t maxDuration)
: BuildError(BuildResult::Failure::TimedOut, "timed out after %1% seconds", maxDuration) : CloneableError(BuildResult::Failure::TimedOut, "timed out after %1% seconds", maxDuration)
, maxDuration(maxDuration) , maxDuration(maxDuration)
{ {
} }

View File

@@ -60,12 +60,12 @@ namespace {
using curlSList = std::unique_ptr<::curl_slist, decltype([](::curl_slist * list) { ::curl_slist_free_all(list); })>; using curlSList = std::unique_ptr<::curl_slist, decltype([](::curl_slist * list) { ::curl_slist_free_all(list); })>;
using curlMulti = std::unique_ptr<::CURLM, decltype([](::CURLM * multi) { ::curl_multi_cleanup(multi); })>; using curlMulti = std::unique_ptr<::CURLM, decltype([](::CURLM * multi) { ::curl_multi_cleanup(multi); })>;
struct curlMultiError : Error struct curlMultiError final : CloneableError<curlMultiError, Error>
{ {
::CURLMcode code; ::CURLMcode code;
curlMultiError(::CURLMcode code) curlMultiError(::CURLMcode code)
: Error{"unexpected curl multi error: %s", ::curl_multi_strerror(code)} : CloneableError{"unexpected curl multi error: %s", ::curl_multi_strerror(code)}
{ {
assert(code != CURLM_OK); assert(code != CURLM_OK);
} }
@@ -1212,7 +1212,7 @@ void FileTransfer::download(
template<typename... Args> template<typename... Args>
FileTransferError::FileTransferError( FileTransferError::FileTransferError(
FileTransfer::Error error, std::optional<std::string> response, const Args &... args) FileTransfer::Error error, std::optional<std::string> response, const Args &... args)
: Error(args...) : CloneableError(args...)
, error(error) , error(error)
, response(response) , response(response)
{ {

View File

@@ -34,12 +34,13 @@ struct AwsCredentials
} }
}; };
class AwsAuthError : public Error class AwsAuthError final : public CloneableError<AwsAuthError, Error>
{ {
std::optional<int> errorCode; std::optional<int> errorCode;
public: public:
using Error::Error; using CloneableError::CloneableError;
AwsAuthError(int errorCode); AwsAuthError(int errorCode);
std::optional<int> getErrorCode() const std::optional<int> getErrorCode() const

View File

@@ -58,7 +58,7 @@ enum struct BuildResultFailureStatus : uint8_t {
* This is both an exception type (inherits from Error) and serves as * This is both an exception type (inherits from Error) and serves as
* the failure variant in BuildResult::inner. * the failure variant in BuildResult::inner.
*/ */
struct BuildError : public Error struct BuildError : public CloneableError<BuildError, Error>
{ {
using Status = BuildResultFailureStatus; using Status = BuildResultFailureStatus;
using enum Status; using enum Status;
@@ -80,7 +80,7 @@ public:
*/ */
template<typename... Args> template<typename... Args>
BuildError(Status status, const Args &... args) BuildError(Status status, const Args &... args)
: Error(args...) : CloneableError(args...)
, status{status} , status{status}
{ {
} }
@@ -97,7 +97,7 @@ public:
* Also used for deserialization. * Also used for deserialization.
*/ */
BuildError(Args args) BuildError(Args args)
: Error(std::move(args.msg)) : CloneableError(std::move(args.msg))
, status{args.status} , status{args.status}
, isNonDeterministic{args.isNonDeterministic} , isNonDeterministic{args.isNonDeterministic}
@@ -108,7 +108,7 @@ public:
* Default constructor for deserialization. * Default constructor for deserialization.
*/ */
BuildError() BuildError()
: Error("") : CloneableError("")
{ {
} }

View File

@@ -20,14 +20,14 @@ namespace nix {
* Denotes a build failure that stemmed from the builder exiting with a * Denotes a build failure that stemmed from the builder exiting with a
* failing exist status. * failing exist status.
*/ */
struct BuilderFailureError : BuildError struct BuilderFailureError final : CloneableError<BuilderFailureError, BuildError>
{ {
int builderStatus; int builderStatus;
std::string extraMsgAfter; std::string extraMsgAfter;
BuilderFailureError(BuildResult::Failure::Status status, int builderStatus, std::string extraMsgAfter) BuilderFailureError(BuildResult::Failure::Status status, int builderStatus, std::string extraMsgAfter)
: BuildError{ : CloneableError{
status, status,
/* No message for now, because the caller will make for /* No message for now, because the caller will make for
us, with extra context */ us, with extra context */

View File

@@ -10,7 +10,7 @@
namespace nix { namespace nix {
struct TimedOut : BuildError struct TimedOut final : CloneableError<TimedOut, BuildError>
{ {
time_t maxDuration; time_t maxDuration;

View File

@@ -22,7 +22,7 @@ struct Package
} }
}; };
class BuildEnvFileConflictError : public Error class BuildEnvFileConflictError final : public CloneableError<BuildEnvFileConflictError, Error>
{ {
public: public:
const Path fileA; const Path fileA;
@@ -30,7 +30,7 @@ public:
int priority; int priority;
BuildEnvFileConflictError(const Path fileA, const Path fileB, int priority) BuildEnvFileConflictError(const Path fileA, const Path fileB, int priority)
: Error( : CloneableError(
"Unable to build profile. There is a conflict for the following files:\n" "Unable to build profile. There is a conflict for the following files:\n"
"\n" "\n"
" %1%\n" " %1%\n"

View File

@@ -403,7 +403,7 @@ ref<FileTransfer> getFileTransfer();
*/ */
ref<FileTransfer> makeFileTransfer(const FileTransferSettings & settings = fileTransferSettings); ref<FileTransfer> makeFileTransfer(const FileTransferSettings & settings = fileTransferSettings);
class FileTransferError : public Error class FileTransferError final : public CloneableError<FileTransferError, Error>
{ {
public: public:
FileTransfer::Error error; FileTransfer::Error error;

View File

@@ -146,7 +146,7 @@ struct RealisedPath
auto operator<=>(const RealisedPath &) const = default; auto operator<=>(const RealisedPath &) const = default;
}; };
class MissingRealisation : public Error class MissingRealisation final : public CloneableError<MissingRealisation, Error>
{ {
public: public:
MissingRealisation(DrvOutput & outputId) MissingRealisation(DrvOutput & outputId)
@@ -155,7 +155,7 @@ public:
} }
MissingRealisation(std::string_view drv, OutputName outputName) MissingRealisation(std::string_view drv, OutputName outputName)
: Error( : CloneableError(
"cannot operate on output '%s' of the " "cannot operate on output '%s' of the "
"unbuilt derivation '%s'", "unbuilt derivation '%s'",
outputName, outputName,

View File

@@ -166,7 +166,7 @@ struct SQLiteTxn
~SQLiteTxn(); ~SQLiteTxn();
}; };
struct SQLiteError : Error struct SQLiteError : CloneableError<SQLiteError, Error>
{ {
std::string path; std::string path;
std::string errMsg; std::string errMsg;

View File

@@ -17,7 +17,7 @@ namespace nix {
SQLiteError::SQLiteError( SQLiteError::SQLiteError(
const char * path, const char * errMsg, int errNo, int extendedErrNo, int offset, HintFmt && hf) const char * path, const char * errMsg, int errNo, int extendedErrNo, int offset, HintFmt && hf)
: Error("") : CloneableError("")
, path(path) , path(path)
, errMsg(errMsg) , errMsg(errMsg)
, errNo(errNo) , errNo(errNo)

View File

@@ -18,11 +18,11 @@ static std::string parsePublicHostKey(std::string_view host, std::string_view ss
} }
} }
class InvalidSSHAuthority : public Error class InvalidSSHAuthority final : public CloneableError<InvalidSSHAuthority, Error>
{ {
public: public:
InvalidSSHAuthority(const ParsedURL::Authority & authority, std::string_view reason) InvalidSSHAuthority(const ParsedURL::Authority & authority, std::string_view reason)
: Error("invalid SSH authority: '%s': %s", authority.to_string(), reason) : CloneableError("invalid SSH authority: '%s': %s", authority.to_string(), reason)
{ {
} }
}; };

View File

@@ -55,10 +55,10 @@
namespace nix { namespace nix {
struct NotDeterministic : BuildError struct NotDeterministic final : CloneableError<NotDeterministic, BuildError>
{ {
NotDeterministic(auto &&... args) NotDeterministic(auto &&... args)
: BuildError(BuildResult::Failure::NotDeterministic, args...) : CloneableError(BuildResult::Failure::NotDeterministic, args...)
{ {
isNonDeterministic = true; isNonDeterministic = true;
} }

View File

@@ -378,7 +378,7 @@ std::set<ExperimentalFeature> parseFeatures(const StringSet & rawFeatures)
} }
MissingExperimentalFeature::MissingExperimentalFeature(ExperimentalFeature feature, std::string reason) MissingExperimentalFeature::MissingExperimentalFeature(ExperimentalFeature feature, std::string reason)
: Error( : CloneableError(
"experimental Nix feature '%1%' is disabled%2%; add '--extra-experimental-features %1%' to enable it", "experimental Nix feature '%1%' is disabled%2%; add '--extra-experimental-features %1%' to enable it",
showExperimentalFeature(feature), showExperimentalFeature(feature),
Uncolored(optionalBracket(" (", reason, ")"))) Uncolored(optionalBracket(" (", reason, ")")))

View File

@@ -226,13 +226,31 @@ public:
{ {
return err; return err;
}; };
[[noreturn]] virtual void throwClone() const = 0;
}; };
#define MakeError(newClass, superClass) \ template<typename Derived, typename Base>
class newClass : public superClass \ class CloneableError : public Base
{ \ {
public: \ public:
using superClass::superClass; \ using Base::Base;
/**
* Rethrow a copy of this exception. Useful when the exception can get
* modified when appending traces.
*/
[[noreturn]] void throwClone() const override
{
throw Derived(static_cast<const Derived &>(*this));
}
};
#define MakeError(newClass, superClass) \
class newClass : public CloneableError<newClass, superClass> \
{ \
public: \
using CloneableError<newClass, superClass>::CloneableError; \
} }
MakeError(Error, BaseError); MakeError(Error, BaseError);
@@ -244,21 +262,21 @@ MakeError(UnimplementedError, Error);
* std::error_code. Use when you want to catch and check an error condition like * std::error_code. Use when you want to catch and check an error condition like
* no_such_file_or_directory (ENOENT) without ifdefs. * no_such_file_or_directory (ENOENT) without ifdefs.
*/ */
class SystemError : public Error class SystemError : public CloneableError<SystemError, Error>
{ {
std::error_code errorCode; std::error_code errorCode;
public: public:
template<typename... Args> template<typename... Args>
SystemError(std::errc posixErrNo, Args &&... args) SystemError(std::errc posixErrNo, Args &&... args)
: Error(std::forward<Args>(args)...) : CloneableError(std::forward<Args>(args)...)
, errorCode(std::make_error_code(posixErrNo)) , errorCode(std::make_error_code(posixErrNo))
{ {
} }
template<typename... Args> template<typename... Args>
SystemError(std::error_code errorCode, Args &&... args) SystemError(std::error_code errorCode, Args &&... args)
: Error(std::forward<Args>(args)...) : CloneableError(std::forward<Args>(args)...)
, errorCode(errorCode) , errorCode(errorCode)
{ {
} }
@@ -290,7 +308,7 @@ public:
* support is too WIP to justify the code churn, but if it is finished * support is too WIP to justify the code churn, but if it is finished
* then a better identifier becomes moe worth it. * then a better identifier becomes moe worth it.
*/ */
class SysError : public SystemError class SysError final : public CloneableError<SysError, SystemError>
{ {
public: public:
int errNo; int errNo;
@@ -301,7 +319,7 @@ public:
*/ */
template<typename... Args> template<typename... Args>
SysError(int errNo, const Args &... args) SysError(int errNo, const Args &... args)
: SystemError(static_cast<std::errc>(errNo), "") : CloneableError(static_cast<std::errc>(errNo), "")
, errNo(errNo) , errNo(errNo)
{ {
auto hf = HintFmt(args...); auto hf = HintFmt(args...);
@@ -369,7 +387,7 @@ namespace windows {
* Unless you need to catch a specific error number, don't catch this in * Unless you need to catch a specific error number, don't catch this in
* portable code. Catch `SystemError` instead. * portable code. Catch `SystemError` instead.
*/ */
class WinError : public SystemError class WinError : public CloneableError<WinError, SystemError>
{ {
public: public:
DWORD lastError; DWORD lastError;
@@ -381,7 +399,7 @@ public:
*/ */
template<typename... Args> template<typename... Args>
WinError(DWORD lastError, const Args &... args) WinError(DWORD lastError, const Args &... args)
: SystemError(std::error_code(lastError, std::system_category()), "") : CloneableError(std::error_code(lastError, std::system_category()), "")
, lastError(lastError) , lastError(lastError)
{ {
auto hf = HintFmt(args...); auto hf = HintFmt(args...);

View File

@@ -80,7 +80,7 @@ std::set<ExperimentalFeature> parseFeatures(const StringSet &);
* An experimental feature was required for some (experimental) * An experimental feature was required for some (experimental)
* operation, but was not enabled. * operation, but was not enabled.
*/ */
class MissingExperimentalFeature : public Error class MissingExperimentalFeature final : public CloneableError<MissingExperimentalFeature, Error>
{ {
public: public:
/** /**

View File

@@ -133,14 +133,14 @@ std::pair<int, std::string> runProgram(RunOptions && options);
void runProgram2(const RunOptions & options); void runProgram2(const RunOptions & options);
class ExecError : public Error class ExecError final : public CloneableError<ExecError, Error>
{ {
public: public:
int status; int status;
template<typename... Args> template<typename... Args>
ExecError(int status, const Args &... args) ExecError(int status, const Args &... args)
: Error(args...) : CloneableError(args...)
, status(status) , status(status)
{ {
} }

View File

@@ -231,19 +231,19 @@ ref<SourceAccessor> makeEmptySourceAccessor();
*/ */
MakeError(RestrictedPathError, Error); MakeError(RestrictedPathError, Error);
struct SymlinkNotAllowed : public Error struct SymlinkNotAllowed final : public CloneableError<SymlinkNotAllowed, Error>
{ {
CanonPath path; CanonPath path;
SymlinkNotAllowed(CanonPath path) SymlinkNotAllowed(CanonPath path)
: Error("relative path '%s' points to a symlink, which is not allowed", path.rel()) : CloneableError("relative path '%s' points to a symlink, which is not allowed", path.rel())
, path(std::move(path)) , path(std::move(path))
{ {
} }
template<typename... Args> template<typename... Args>
SymlinkNotAllowed(CanonPath path, const std::string & fs, Args &&... args) SymlinkNotAllowed(CanonPath path, const std::string & fs, Args &&... args)
: Error(fs, std::forward<Args>(args)...) : CloneableError(fs, std::forward<Args>(args)...)
, path(std::move(path)) , path(std::move(path))
{ {
} }