diff --git a/doc/manual/rl-next/libexpr-fix-ignore-try.md b/doc/manual/rl-next/libexpr-fix-ignore-try.md new file mode 100644 index 000000000..a7935c3a0 --- /dev/null +++ b/doc/manual/rl-next/libexpr-fix-ignore-try.md @@ -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. diff --git a/lix/libexpr/primops.cc b/lix/libexpr/primops.cc index cfbf86ced..17b9e9d58 100644 --- a/lix/libexpr/primops.cc +++ b/lix/libexpr/primops.cc @@ -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> trylevel; - std::unique_ptr 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> 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); } diff --git a/tests/functional/repl_characterization/data/debug_ignore_try.test b/tests/functional/repl_characterization/data/debug_ignore_try.test new file mode 100644 index 000000000..2c326a401 --- /dev/null +++ b/tests/functional/repl_characterization/data/debug_ignore_try.test @@ -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 diff --git a/tests/functional/repl_characterization/repl_characterization.cc b/tests/functional/repl_characterization/repl_characterization.cc index 7e33ecec0..eccaeaf8e 100644 --- a/tests/functional/repl_characterization/repl_characterization.cc +++ b/tests/functional/repl_characterization/repl_characterization.cc @@ -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