:quit in the debugger should quit the whole program

(cherry picked from commit 2a8fe9a93837733e9dd9ed5c078734a35b203e14)
Change-Id: I71dadfef6b24d9272b206e9e2c408040559d8a1c
This commit is contained in:
eldritch horrors 2024-03-08 09:19:15 +01:00
parent 6b11c2cd70
commit 992d99592f
10 changed files with 111 additions and 44 deletions

View file

@ -49,6 +49,27 @@ extern "C" {
namespace nix { namespace nix {
/**
* Returned by `NixRepl::processLine`.
*/
enum class ProcessLineResult {
/**
* The user exited with `:quit`. The REPL should exit. The surrounding
* program or evaluation (e.g., if the REPL was acting as the debugger)
* should also exit.
*/
QuitAll,
/**
* The user exited with `:continue`. The REPL should exit, but the program
* should continue running.
*/
QuitOnce,
/**
* The user did not exit. The REPL should request another line of input.
*/
Continue,
};
struct NixRepl struct NixRepl
: AbstractNixRepl : AbstractNixRepl
#if HAVE_BOEHMGC #if HAVE_BOEHMGC
@ -72,13 +93,13 @@ struct NixRepl
std::function<AnnotatedValues()> getValues); std::function<AnnotatedValues()> getValues);
virtual ~NixRepl(); virtual ~NixRepl();
void mainLoop() override; ReplExitStatus mainLoop() override;
void initEnv() override; void initEnv() override;
StringSet completePrefix(const std::string & prefix); StringSet completePrefix(const std::string & prefix);
bool getLine(std::string & input, const std::string & prompt); bool getLine(std::string & input, const std::string & prompt);
StorePath getDerivationPath(Value & v); StorePath getDerivationPath(Value & v);
bool processLine(std::string line); ProcessLineResult processLine(std::string line);
void loadFile(const Path & path); void loadFile(const Path & path);
void loadFlake(const std::string & flakeRef); void loadFlake(const std::string & flakeRef);
@ -243,7 +264,7 @@ static std::ostream & showDebugTrace(std::ostream & out, const PosTable & positi
static bool isFirstRepl = true; static bool isFirstRepl = true;
void NixRepl::mainLoop() ReplExitStatus NixRepl::mainLoop()
{ {
if (isFirstRepl) { if (isFirstRepl) {
std::string_view debuggerNotice = ""; std::string_view debuggerNotice = "";
@ -284,15 +305,25 @@ void NixRepl::mainLoop()
// When continuing input from previous lines, don't print a prompt, just align to the same // When continuing input from previous lines, don't print a prompt, just align to the same
// number of chars as the prompt. // number of chars as the prompt.
if (!getLine(input, input.empty() ? "nix-repl> " : " ")) { if (!getLine(input, input.empty() ? "nix-repl> " : " ")) {
// ctrl-D should exit the debugger. // Ctrl-D should exit the debugger.
state->debugStop = false; state->debugStop = false;
state->debugQuit = true;
logger->cout(""); logger->cout("");
break; // TODO: Should Ctrl-D exit just the current debugger session or
// the entire program?
return ReplExitStatus::QuitAll;
} }
logger->resume(); logger->resume();
try { try {
if (!removeWhitespace(input).empty() && !processLine(input)) return; switch (processLine(input)) {
case ProcessLineResult::QuitAll:
return ReplExitStatus::QuitAll;
case ProcessLineResult::QuitOnce:
return ReplExitStatus::Continue;
case ProcessLineResult::Continue:
break;
default:
abort();
}
} catch (ParseError & e) { } catch (ParseError & e) {
if (e.msg().find("unexpected end of file") != std::string::npos) { if (e.msg().find("unexpected end of file") != std::string::npos) {
// For parse errors on incomplete input, we continue waiting for the next line of // For parse errors on incomplete input, we continue waiting for the next line of
@ -479,10 +510,11 @@ void NixRepl::loadDebugTraceEnv(DebugTrace & dt)
} }
} }
bool NixRepl::processLine(std::string line) ProcessLineResult NixRepl::processLine(std::string line)
{ {
line = trim(line); line = trim(line);
if (line == "") return true; if (line.empty())
return ProcessLineResult::Continue;
_isInterrupted = false; _isInterrupted = false;
@ -577,13 +609,13 @@ bool NixRepl::processLine(std::string line)
else if (state->debugRepl && (command == ":s" || command == ":step")) { else if (state->debugRepl && (command == ":s" || command == ":step")) {
// set flag to stop at next DebugTrace; exit repl. // set flag to stop at next DebugTrace; exit repl.
state->debugStop = true; state->debugStop = true;
return false; return ProcessLineResult::QuitOnce;
} }
else if (state->debugRepl && (command == ":c" || command == ":continue")) { else if (state->debugRepl && (command == ":c" || command == ":continue")) {
// set flag to run to next breakpoint or end of program; exit repl. // set flag to run to next breakpoint or end of program; exit repl.
state->debugStop = false; state->debugStop = false;
return false; return ProcessLineResult::QuitOnce;
} }
else if (command == ":a" || command == ":add") { else if (command == ":a" || command == ":add") {
@ -726,8 +758,7 @@ bool NixRepl::processLine(std::string line)
else if (command == ":q" || command == ":quit") { else if (command == ":q" || command == ":quit") {
state->debugStop = false; state->debugStop = false;
state->debugQuit = true; return ProcessLineResult::QuitAll;
return false;
} }
else if (command == ":doc") { else if (command == ":doc") {
@ -788,7 +819,7 @@ bool NixRepl::processLine(std::string line)
} }
} }
return true; return ProcessLineResult::Continue;
} }
void NixRepl::loadFile(const Path & path) void NixRepl::loadFile(const Path & path)
@ -919,7 +950,7 @@ std::unique_ptr<AbstractNixRepl> AbstractNixRepl::create(
} }
void AbstractNixRepl::runSimple( ReplExitStatus AbstractNixRepl::runSimple(
ref<EvalState> evalState, ref<EvalState> evalState,
const ValMap & extraEnv) const ValMap & extraEnv)
{ {
@ -941,7 +972,7 @@ void AbstractNixRepl::runSimple(
for (auto & [name, value] : extraEnv) for (auto & [name, value] : extraEnv)
repl->addVarToScope(repl->state->symbols.create(name), *value); repl->addVarToScope(repl->state->symbols.create(name), *value);
repl->mainLoop(); return repl->mainLoop();
} }
} }

View file

@ -28,13 +28,13 @@ struct AbstractNixRepl
const SearchPath & searchPath, nix::ref<Store> store, ref<EvalState> state, const SearchPath & searchPath, nix::ref<Store> store, ref<EvalState> state,
std::function<AnnotatedValues()> getValues); std::function<AnnotatedValues()> getValues);
static void runSimple( static ReplExitStatus runSimple(
ref<EvalState> evalState, ref<EvalState> evalState,
const ValMap & extraEnv); const ValMap & extraEnv);
virtual void initEnv() = 0; virtual void initEnv() = 0;
virtual void mainLoop() = 0; virtual ReplExitStatus mainLoop() = 0;
}; };
} }

View file

@ -3,6 +3,7 @@
#include "hash.hh" #include "hash.hh"
#include "primops.hh" #include "primops.hh"
#include "print-options.hh" #include "print-options.hh"
#include "shared.hh"
#include "types.hh" #include "types.hh"
#include "util.hh" #include "util.hh"
#include "store-api.hh" #include "store-api.hh"
@ -391,7 +392,6 @@ EvalState::EvalState(
, buildStore(buildStore ? buildStore : store) , buildStore(buildStore ? buildStore : store)
, debugRepl(nullptr) , debugRepl(nullptr)
, debugStop(false) , debugStop(false)
, debugQuit(false)
, trylevel(0) , trylevel(0)
, regexCache(makeRegexCache()) , regexCache(makeRegexCache())
#if HAVE_BOEHMGC #if HAVE_BOEHMGC
@ -798,7 +798,17 @@ void EvalState::runDebugRepl(const Error * error, const Env & env, const Expr &
auto se = getStaticEnv(expr); auto se = getStaticEnv(expr);
if (se) { if (se) {
auto vm = mapStaticEnvBindings(symbols, *se.get(), env); auto vm = mapStaticEnvBindings(symbols, *se.get(), env);
(debugRepl)(ref<EvalState>(shared_from_this()), *vm); auto exitStatus = (debugRepl)(ref<EvalState>(shared_from_this()), *vm);
switch (exitStatus) {
case ReplExitStatus::QuitAll:
if (error)
throw *error;
throw Exit(0);
case ReplExitStatus::Continue:
break;
default:
abort();
}
} }
} }

View file

@ -11,6 +11,7 @@
#include "experimental-features.hh" #include "experimental-features.hh"
#include "input-accessor.hh" #include "input-accessor.hh"
#include "search-path.hh" #include "search-path.hh"
#include "repl-exit-status.hh"
#include <map> #include <map>
#include <optional> #include <optional>
@ -207,9 +208,8 @@ public:
/** /**
* Debugger * Debugger
*/ */
void (* debugRepl)(ref<EvalState> es, const ValMap & extraEnv); ReplExitStatus (* debugRepl)(ref<EvalState> es, const ValMap & extraEnv);
bool debugStop; bool debugStop;
bool debugQuit;
int trylevel; int trylevel;
std::list<DebugTrace> debugTraces; std::list<DebugTrace> debugTraces;
std::map<const Expr*, const std::shared_ptr<const StaticEnv>> exprEnvs; std::map<const Expr*, const std::shared_ptr<const StaticEnv>> exprEnvs;
@ -753,7 +753,6 @@ struct DebugTraceStacker {
DebugTraceStacker(EvalState & evalState, DebugTrace t); DebugTraceStacker(EvalState & evalState, DebugTrace t);
~DebugTraceStacker() ~DebugTraceStacker()
{ {
// assert(evalState.debugTraces.front() == trace);
evalState.debugTraces.pop_front(); evalState.debugTraces.pop_front();
} }
EvalState & evalState; EvalState & evalState;

View file

@ -754,15 +754,6 @@ static RegisterPrimOp primop_break({
auto & dt = state.debugTraces.front(); auto & dt = state.debugTraces.front();
state.runDebugRepl(&error, dt.env, dt.expr); state.runDebugRepl(&error, dt.env, dt.expr);
if (state.debugQuit) {
// If the user elects to quit the repl, throw an exception.
throw Error(ErrorInfo{
.level = lvlInfo,
.msg = HintFmt("quit the debugger"),
.pos = nullptr,
});
}
} }
// Return the value we were passed. // Return the value we were passed.
@ -873,7 +864,7 @@ static void prim_tryEval(EvalState & state, const PosIdx pos, Value * * args, Va
/* increment state.trylevel, and decrement it when this function returns. */ /* increment state.trylevel, and decrement it when this function returns. */
MaintainCount trylevel(state.trylevel); MaintainCount trylevel(state.trylevel);
void (* savedDebugRepl)(ref<EvalState> es, const ValMap & extraEnv) = nullptr; ReplExitStatus (* savedDebugRepl)(ref<EvalState> es, const ValMap & extraEnv) = nullptr;
if (state.debugRepl && evalSettings.ignoreExceptionsDuringTry) if (state.debugRepl && evalSettings.ignoreExceptionsDuringTry)
{ {
/* to prevent starting the repl from exceptions withing a tryEval, null it. */ /* to prevent starting the repl from exceptions withing a tryEval, null it. */

View file

@ -0,0 +1,20 @@
#pragma once
namespace nix {
/**
* Exit status returned from the REPL.
*/
enum class ReplExitStatus {
/**
* The user exited with `:quit`. The program (e.g., if the REPL was acting
* as the debugger) should exit.
*/
QuitAll,
/**
* The user exited with `:continue`. The program should continue running.
*/
Continue,
};
}

View file

@ -407,6 +407,4 @@ PrintFreed::~PrintFreed()
showBytes(results.bytesFreed)); showBytes(results.bytesFreed));
} }
Exit::~Exit() { }
} }

View file

@ -6,6 +6,7 @@
#include "common-args.hh" #include "common-args.hh"
#include "path.hh" #include "path.hh"
#include "derived-path.hh" #include "derived-path.hh"
#include "exit.hh"
#include <signal.h> #include <signal.h>
@ -14,15 +15,6 @@
namespace nix { namespace nix {
class Exit : public std::exception
{
public:
int status;
Exit() : status(0) { }
Exit(int status) : status(status) { }
virtual ~Exit();
};
int handleExceptions(const std::string & programName, std::function<void()> fun); int handleExceptions(const std::string & programName, std::function<void()> fun);
/** /**

7
src/libutil/exit.cc Normal file
View file

@ -0,0 +1,7 @@
#include "exit.hh"
namespace nix {
Exit::~Exit() {}
}

19
src/libutil/exit.hh Normal file
View file

@ -0,0 +1,19 @@
#pragma once
#include <exception>
namespace nix {
/**
* Exit the program with a given exit code.
*/
class Exit : public std::exception
{
public:
int status;
Exit() : status(0) { }
explicit Exit(int status) : status(status) { }
virtual ~Exit();
};
}