libexpr: don't debugThrow from async code

running a debugger is never-async since it holds an EvalState reference.

fixes #761

Change-Id: Ie1b4df6f81bb5614f11ea183eb9d1058fde73121
This commit is contained in:
eldritch horrors
2025-03-25 21:23:05 +01:00
parent d2f4b4042d
commit 8a90195cdb
14 changed files with 77 additions and 39 deletions
+1 -1
View File
@@ -255,7 +255,7 @@ static void main_nix_build(AsyncIoRoot & aio, std::string programName, Strings a
evaluator->paths.resolveExprPath(aio.blockOn(lookupFileArg(
*evaluator,
inShebang && !packages ? absPath(i, absPath(dirOf(script))) : i
)))
)).unwrap())
));
}
}
+1 -1
View File
@@ -1558,7 +1558,7 @@ static int main_nix_env(AsyncIoRoot & aio, std::string programName, Strings argv
globals.instSource.nixExprPath = std::make_shared<SourcePath>(
file != ""
? aio.blockOn(lookupFileArg(*globals.state, file))
? aio.blockOn(lookupFileArg(*globals.state, file)).unwrap()
: CanonPath(nixExprPath));
globals.instSource.autoArgs = myArgs.getAutoArgs(*globals.state);
+5 -6
View File
@@ -167,7 +167,7 @@ static int main_nix_instantiate(AsyncIoRoot & aio, std::string programName, Stri
if (findFile) {
for (auto & i : files) {
auto p = aio.blockOn(evaluator->paths.findFile(i));
auto p = aio.blockOn(evaluator->paths.findFile(i)).unwrap();
std::cout << p.canonical().abs() << std::endl;
}
return 0;
@@ -181,11 +181,10 @@ static int main_nix_instantiate(AsyncIoRoot & aio, std::string programName, Stri
files.push_back("./default.nix");
for (auto & i : files) {
Expr & e = fromArgs
? evaluator->parseExprFromString(i, CanonPath::fromCwd())
: evaluator->parseExprFromFile(
evaluator->paths.resolveExprPath(aio.blockOn(lookupFileArg(*evaluator, i)))
);
Expr & e = fromArgs ? evaluator->parseExprFromString(i, CanonPath::fromCwd())
: evaluator->parseExprFromFile(evaluator->paths.resolveExprPath(
aio.blockOn(lookupFileArg(*evaluator, i)).unwrap()
));
processExpr(*state, attrPaths, parseOnly, strict, autoArgs,
evalOnly, outputKind, xmlOutputSourceLocation, e);
}
+7 -5
View File
@@ -188,7 +188,8 @@ Bindings * MixEvalArgs::getAutoArgs(Evaluator & state)
return res.finish();
}
kj::Promise<Result<SourcePath>> lookupFileArg(Evaluator & state, std::string_view fileArg)
kj::Promise<Result<EvalPaths::PathResult<SourcePath, ThrownError>>>
lookupFileArg(Evaluator & state, std::string_view fileArg)
try {
if (EvalSettings::isPseudoUrl(fileArg)) {
auto const url = EvalSettings::resolvePseudoUrl(fileArg);
@@ -199,19 +200,20 @@ try {
/* locked */ false
));
StorePath const storePath = downloaded.tree.storePath;
co_return CanonPath(state.store->toRealPath(storePath));
co_return SourcePath(CanonPath(state.store->toRealPath(storePath)));
} else if (fileArg.starts_with("flake:")) {
experimentalFeatureSettings.require(Xp::Flakes);
static constexpr size_t FLAKE_LEN = std::string_view("flake:").size();
auto flakeRef = parseFlakeRef(std::string(fileArg.substr(FLAKE_LEN)), {}, true, false);
auto storePath = TRY_AWAIT(TRY_AWAIT(flakeRef.resolve(state.store)).fetchTree(state.store))
.first.storePath;
co_return CanonPath(state.store->toRealPath(storePath));
} else if (fileArg.size() > 2 && fileArg.at(0) == '<' && fileArg.at(fileArg.size() - 1) == '>') {
co_return SourcePath(CanonPath(state.store->toRealPath(storePath)));
} else if (fileArg.size() > 2 && fileArg.at(0) == '<' && fileArg.at(fileArg.size() - 1) == '>')
{
Path p(fileArg.substr(1, fileArg.size() - 2));
co_return TRY_AWAIT(state.paths.findFile(p));
} else {
co_return CanonPath::fromCwd(fileArg);
co_return SourcePath(CanonPath::fromCwd(fileArg));
}
} catch (...) {
co_return result::current_exception();
+3 -1
View File
@@ -1,6 +1,7 @@
#pragma once
///@file
#include "lix/libexpr/eval-error.hh"
#include "lix/libexpr/eval.hh"
#include "lix/libutil/args.hh"
#include "lix/libmain/common-args.hh"
@@ -49,6 +50,7 @@ private:
*
* @exception nix::ThrownError for failed search path lookup. Probably others.
*/
kj::Promise<Result<SourcePath>> lookupFileArg(Evaluator & state, std::string_view fileArg);
kj::Promise<Result<EvalPaths::PathResult<SourcePath, ThrownError>>>
lookupFileArg(Evaluator & state, std::string_view fileArg);
}
+4 -4
View File
@@ -215,9 +215,9 @@ void SourceExprCommand::completeInstallable(EvalState & state, AddCompletions &
auto evaluator = getEvaluator();
Expr & e = evaluator->parseExprFromFile(
state.ctx.paths.resolveExprPath(state.aio.blockOn(lookupFileArg(*evaluator, *file)))
);
Expr & e = evaluator->parseExprFromFile(state.ctx.paths.resolveExprPath(
state.aio.blockOn(lookupFileArg(*evaluator, *file)).unwrap()
));
Value root;
state.eval(e, root);
@@ -457,7 +457,7 @@ Installables SourceExprCommand::parseInstallables(
state.eval(e, *vFile);
}
else if (file)
state.evalFile(state.aio.blockOn(lookupFileArg(*evaluator, *file)), *vFile);
state.evalFile(state.aio.blockOn(lookupFileArg(*evaluator, *file)).unwrap(), *vFile);
else {
auto & e = evaluator->parseExprFromString(*expr, CanonPath::fromCwd());
state.eval(e, *vFile);
+2 -1
View File
@@ -34,6 +34,7 @@
#include "lix/libutil/signals.hh"
#include "lix/libexpr/print.hh"
#include "lix/libexpr/gc-small-vector.hh"
#include "lix/libutil/types.hh"
#include "lix/libutil/users.hh"
#if HAVE_BOEHMGC
@@ -899,7 +900,7 @@ void NixRepl::loadFile(const Path & path)
loadedFiles.remove(path);
loadedFiles.push_back(path);
Value v, v2;
state.evalFile(state.aio.blockOn(lookupFileArg(evaluator, path)), v);
state.evalFile(state.aio.blockOn(lookupFileArg(evaluator, path)).unwrap(always_progresses), v);
state.autoCallFunction(*autoArgs, v, v2);
addAttrsToScope(v2);
}
+11 -9
View File
@@ -2322,7 +2322,7 @@ BackedStringView EvalState::coerceToString(
v._path
: copyToStore
? ctx.store->printStorePath(
aio.blockOn(ctx.paths.copyPathToStore(context, v.path(), ctx.repair)))
aio.blockOn(ctx.paths.copyPathToStore(context, v.path(), ctx.repair)).unwrap())
: v.path().to_string();
}
@@ -2391,10 +2391,11 @@ BackedStringView EvalState::coerceToString(
}
kj::Promise<Result<StorePath>> EvalPaths::copyPathToStore(NixStringContext & context, const SourcePath & path, RepairFlag repair)
kj::Promise<Result<EvalPaths::PathResult<StorePath, EvalError>>>
EvalPaths::copyPathToStore(NixStringContext & context, const SourcePath & path, RepairFlag repair)
try {
if (nix::isDerivation(path.canonical().abs()))
errors.make<EvalError>("file names are not allowed to end in '%1%'", drvExtension).debugThrow();
co_return errors.make<EvalError>("file names are not allowed to end in '%1%'", drvExtension);
auto i = srcToStore.find(path);
@@ -2792,13 +2793,14 @@ Expr & Evaluator::parseStdin()
}
kj::Promise<Result<SourcePath>> EvalPaths::findFile(const std::string_view path)
kj::Promise<Result<EvalPaths::PathResult<SourcePath, ThrownError>>>
EvalPaths::findFile(const std::string_view path)
{
return findFile(searchPath_, path);
}
kj::Promise<Result<SourcePath>>
kj::Promise<Result<EvalPaths::PathResult<SourcePath, ThrownError>>>
EvalPaths::findFile(const SearchPath & searchPath, const std::string_view path, const PosIdx pos)
try {
for (auto & i : searchPath.elements) {
@@ -2812,18 +2814,18 @@ try {
auto r = *rOpt;
Path res = suffix == "" ? r : concatStrings(r, "/", suffix);
if (pathExists(res)) co_return CanonPath(canonPath(res));
if (pathExists(res)) co_return SourcePath(CanonPath(canonPath(res)));
}
if (path.starts_with("nix/"))
co_return CanonPath(concatStrings(corepkgsPrefix, path.substr(4)));
co_return SourcePath(CanonPath(concatStrings(corepkgsPrefix, path.substr(4))));
errors.make<ThrownError>(
co_return errors.make<ThrownError>(
evalSettings.pureEval
? "cannot look up '<%s>' in pure evaluation mode (use '--impure' to override)"
: "file '%s' was not found in the Nix search path (add it using $NIX_PATH or -I)",
path
).atPos(pos).debugThrow();
).atPos(pos);
} catch (...) {
co_return result::current_exception();
}
+29 -3
View File
@@ -451,11 +451,37 @@ public:
*/
Path toRealPath(const Path & path, const NixStringContext & context);
/**
* findFile wants to throw a debuggable error when the requested file
* is not found, but it can't invoke the debugger itself because it's
* async code. This wraps the result-or-error to allow it regardless.
* This happens for copyPathToStore as well, with another error type.
*/
template<typename T, typename E>
struct PathResult : private std::variant<T, EvalErrorBuilder<E>>
{
PathResult(T p) : std::variant<T, EvalErrorBuilder<E>>(std::move(p)) {}
PathResult(EvalErrorBuilder<E> e) : std::variant<T, EvalErrorBuilder<E>>(std::move(e)) {}
T unwrap(NeverAsync = {}) &&
{
return std::visit(
overloaded{
[](T & p) -> T { return std::move(p); },
[](EvalErrorBuilder<E> & e) -> T {
std::move(e).debugThrow();
}
},
static_cast<std::variant<T, EvalErrorBuilder<E>> &>(*this)
);
}
};
/**
* Look up a file in the search path.
*/
kj::Promise<Result<SourcePath>> findFile(const std::string_view path);
kj::Promise<Result<SourcePath>>
kj::Promise<Result<PathResult<SourcePath, ThrownError>>> findFile(const std::string_view path);
kj::Promise<Result<PathResult<SourcePath, ThrownError>>>
findFile(const SearchPath & searchPath, const std::string_view path, const PosIdx pos = noPos);
/**
@@ -468,7 +494,7 @@ public:
kj::Promise<Result<std::optional<std::string>>>
resolveSearchPathPath(const SearchPath::Path & path);
kj::Promise<Result<StorePath>> copyPathToStore(
kj::Promise<Result<PathResult<StorePath, EvalError>>> copyPathToStore(
NixStringContext & context, const SourcePath & path, RepairFlag repair = NoRepair
);
+1 -1
View File
@@ -1339,7 +1339,7 @@ static void prim_findFile(EvalState & state, const PosIdx pos, Value * * args, V
auto path = state.forceStringNoCtx(*args[1], pos, "while evaluating the second argument passed to builtins.findFile");
v.mkPath(state.ctx.paths.checkSourcePath(
state.aio.blockOn(state.ctx.paths.findFile(searchPath, path, pos))
state.aio.blockOn(state.ctx.paths.findFile(searchPath, path, pos)).unwrap()
));
}
+1 -1
View File
@@ -37,7 +37,7 @@ JSON printValueAsJSON(EvalState & state, bool strict,
if (copyToStore)
out = state.ctx.store->printStorePath(state.aio.blockOn(
state.ctx.paths.copyPathToStore(context, v.path(), state.ctx.repair)
));
).unwrap());
else {
out = v.path().to_string();
}
+1 -1
View File
@@ -204,7 +204,7 @@ static int main_nix_prefetch_url(AsyncIoRoot & aio, std::string programName, Str
Value vRoot;
state->evalFile(
evaluator->paths.resolveExprPath(
aio.blockOn(lookupFileArg(*evaluator, args.empty() ? "." : args[0]))),
aio.blockOn(lookupFileArg(*evaluator, args.empty() ? "." : args[0])).unwrap()),
vRoot);
Value & v(*findAlongAttrPath(*state, attrPath, autoArgs, vRoot).first);
state->forceAttrs(v, noPos, "while evaluating the source attribute to prefetch");
+4 -1
View File
@@ -51,7 +51,10 @@ static nix::Value *releaseExprTopLevelValue(nix::EvalState &state,
nix::CanonPath::fromCwd());
state.eval(e, vTop);
} else {
state.evalFile(state.aio.blockOn(nix::lookupFileArg(state.ctx, args.releaseExpr)), vTop);
state.evalFile(
state.aio.blockOn(nix::lookupFileArg(state.ctx, args.releaseExpr))
.unwrap(),
vTop);
}
auto vRoot = state.ctx.mem.allocValue();
+7 -4
View File
@@ -35,14 +35,16 @@ TEST(Arguments, lookupFileArg) {
auto store = aio.blockOn(openStore("dummy://"));
auto state = std::make_shared<Evaluator>(aio, searchPath, store, store);
SourcePath const foundUnitData = aio.blockOn(lookupFileArg(*state, "<example>"));
SourcePath const foundUnitData =
aio.blockOn(lookupFileArg(*state, "<example>")).unwrap(always_progresses);
EXPECT_EQ(foundUnitData.canonical(), canonDataPath);
// lookupFileArg should not resolve <search paths> if anything else is before or after it.
SourcePath const yepEvenSpaces = aio.blockOn(lookupFileArg(*state, " <example>"));
SourcePath const yepEvenSpaces =
aio.blockOn(lookupFileArg(*state, " <example>")).unwrap(always_progresses);
EXPECT_EQ(yepEvenSpaces.canonical(), CanonPath::fromCwd(" <example>"));
EXPECT_EQ(
aio.blockOn(lookupFileArg(*state, "<example>/nixos")).canonical(),
aio.blockOn(lookupFileArg(*state, "<example>/nixos")).unwrap(always_progresses).canonical(),
CanonPath::fromCwd("<example>/nixos")
);
@@ -53,7 +55,8 @@ TEST(Arguments, lookupFileArg) {
EXPECT_NE(msg.find(CHANNEL_URL), msg.npos);
}
SourcePath const normalFile = aio.blockOn(lookupFileArg(*state, unitDataPath));
SourcePath const normalFile =
aio.blockOn(lookupFileArg(*state, unitDataPath)).unwrap(always_progresses);
EXPECT_EQ(normalFile.canonical(), canonDataPath);
}