libexpr: fix --debugger --ignore-try

a65e9e5828 did not inform `tryEval` that
(as far as it's concerned) `state.debug` moved to `state.errors.debug`
and changed types. this resulted in the REPL erroneously coming up, that
REPL having a non-debug state, and segfaulting after that REPL exited.

it's probably good that `state.debug` isn't mutated by `--ignore-try`
anymore.

Change-Id: I1918e93edacd626452aa423fc2eb825080738835
Fixes: a65e9e5828 ("libexpr: extract eval error creation into new type")
Signed-off-by: Dusk Banks <me@bb010g.com>
(cherry picked from commit 6a583136b7)
This commit is contained in:
Dusk Banks
2025-01-31 12:36:14 +00:00
committed by eldritch horrors
parent d2b1af70ee
commit 5fa27057b2
4 changed files with 43 additions and 20 deletions
@@ -0,0 +1,9 @@
---
synopsis: "Fix `--debugger --ignore-try`"
issues: []
cls: [2440]
category: "Fixes"
credits: ["bb010g"]
---
When in debug mode (e.g. from using the `--debugger` flag), enabling [`ignore-try`](@docroot@/command-ref/conf-file.md#conf-ignore-try) once again properly disables debug REPLs within [`builtins.tryEval`](@docroot@/language/builtins.md#builtins-tryEval) calls. Previously, a debug REPL would be started as if `ignore-try` was disabled, but that REPL wouldn't actually be in debug mode, and upon exiting the REPL the evaluating process would segfault.
+25 -20
View File
@@ -10,6 +10,7 @@
#include "lix/libexpr/json-to-value.hh"
#include "lix/libstore/names.hh"
#include "lix/libstore/path-references.hh"
#include "lix/libutil/finally.hh"
#include "lix/libutil/processes.hh"
#include "lix/libstore/store-api.hh"
#include "lix/libexpr/value-to-json.hh"
@@ -640,29 +641,33 @@ static void prim_tryEval(EvalState & state, const PosIdx pos, Value * * args, Va
{
auto attrs = state.ctx.buildBindings(2);
std::optional<MaintainCount<int>> trylevel;
std::unique_ptr<DebugState> savedDebug;
if (state.ctx.debug) {
trylevel.emplace(state.ctx.debug->trylevel);
if (evalSettings.ignoreExceptionsDuringTry) {
/* to prevent starting the repl from exceptions withing a tryEval, null it. */
savedDebug = std::move(state.ctx.debug);
{
std::optional<MaintainCount<int>> trylevel;
DebugState * savedDebug = nullptr;
Finally resetDebug([&] {
if (savedDebug) {
state.ctx.errors.debug = savedDebug;
}
});
if (state.ctx.errors.debug != nullptr) {
trylevel.emplace(state.ctx.errors.debug->trylevel);
if (evalSettings.ignoreExceptionsDuringTry) {
/* to prevent starting the repl from exceptions within a tryEval, null it. */
savedDebug = state.ctx.errors.debug;
state.ctx.errors.debug = nullptr;
}
}
try {
state.forceValue(*args[0], pos);
attrs.insert(state.ctx.s.value, args[0]);
attrs.alloc("success").mkBool(true);
} catch (AssertionError & e) {
attrs.alloc(state.ctx.s.value).mkBool(false);
attrs.alloc("success").mkBool(false);
}
}
try {
state.forceValue(*args[0], pos);
attrs.insert(state.ctx.s.value, args[0]);
attrs.alloc("success").mkBool(true);
} catch (AssertionError & e) {
attrs.alloc(state.ctx.s.value).mkBool(false);
attrs.alloc("success").mkBool(false);
}
// restore the debugRepl pointer if we saved it earlier.
if (savedDebug)
state.ctx.debug = std::move(savedDebug);
v.mkAttrs(attrs);
}
@@ -0,0 +1,8 @@
@args --debugger --ignore-try
we don't enter a debug repl through tryEval
nix-repl> (builtins.tryEval ((x: throw "foo") 1)).success
false
no segfault either
nix-repl> :quit
@@ -187,5 +187,6 @@ REPL_TEST(stack_vars);
REPL_TEST(errors);
REPL_TEST(idempotent);
REPL_TEST(debug_frames);
REPL_TEST(debug_ignore_try);
}; // namespace nix