libexpr: move debug repl state into own struct

just to keep debug-related state closer together. eventually this will
also allow us to not allocate debugger state altogether unless needed.

Change-Id: Id83fea75d96d0ecbe21683cb1b57dd5b11b13535
This commit is contained in:
eldritch horrors 2024-11-27 02:09:08 +01:00
parent e635db77e3
commit 003883306d
7 changed files with 116 additions and 113 deletions

View file

@ -118,7 +118,7 @@ ref<EvalState> EvalCommand::getEvalState()
evalState->repair = repair;
if (startReplOnEvalErrors) {
evalState->debugRepl = &AbstractNixRepl::runSimple;
evalState->debug.repl = &AbstractNixRepl::runSimple;
};
}
return ref<EvalState>(evalState);

View file

@ -285,7 +285,7 @@ ReplExitStatus NixRepl::mainLoop()
{
if (isFirstRepl) {
std::string_view debuggerNotice = "";
if (state.debugRepl) {
if (state.debug.repl) {
debuggerNotice = " debugger";
}
notice("Lix %1%%2%\nType :? for help.", nixVersion, debuggerNotice);
@ -310,7 +310,7 @@ ReplExitStatus NixRepl::mainLoop()
// number of chars as the prompt.
if (!interacter->getLine(input, input.empty() ? ReplPromptType::ReplPrompt : ReplPromptType::ContinuationPrompt)) {
// Ctrl-D should exit the debugger.
state.debugStop = false;
state.debug.stop = false;
logger->cout("");
// TODO: Should Ctrl-D exit just the current debugger session or
// the entire program?
@ -366,7 +366,7 @@ StringSet NixRepl::completePrefix(const std::string & prefix)
}
}
if (state.debugRepl) {
if (state.debug.repl) {
for (auto const & colonCmd : this->DEBUG_COMMANDS) {
if (colonCmd.starts_with(prefix)) {
completions.insert(std::string(colonCmd));
@ -416,9 +416,9 @@ StringSet NixRepl::completePrefix(const std::string & prefix)
}
} else {
/* Temporarily disable the debugger, to avoid re-entering readline. */
auto debug_repl = state.debugRepl;
state.debugRepl = nullptr;
Finally restoreDebug([&]() { state.debugRepl = debug_repl; });
auto debug_repl = state.debug.repl;
state.debug.repl = nullptr;
Finally restoreDebug([&]() { state.debug.repl = debug_repl; });
try {
/* This is an expression that should evaluate to an
attribute set. Evaluate it to get the names of the
@ -485,7 +485,7 @@ void NixRepl::loadDebugTraceEnv(DebugTrace & dt)
{
initEnv();
auto se = state.getStaticEnv(dt.expr);
auto se = state.debug.staticEnvFor(dt.expr);
if (se) {
auto vm = mapStaticEnvBindings(state.symbols, *se.get(), dt.env);
@ -541,7 +541,7 @@ ProcessLineResult NixRepl::processLine(std::string line)
<< " errors\n"
<< " :?, :help Brings up this help menu\n"
;
if (state.debugRepl) {
if (state.debug.repl) {
std::cout
<< "\n"
<< " Debug mode commands\n"
@ -556,15 +556,15 @@ ProcessLineResult NixRepl::processLine(std::string line)
}
else if (state.debugRepl && (command == ":bt" || command == ":backtrace")) {
for (const auto & [idx, i] : enumerate(state.debugTraces)) {
else if (state.debug.repl && (command == ":bt" || command == ":backtrace")) {
for (const auto & [idx, i] : enumerate(state.debug.traces)) {
std::cout << "\n" << ANSI_BLUE << idx << ANSI_NORMAL << ": ";
showDebugTrace(std::cout, state.positions, i);
}
}
else if (state.debugRepl && (command == ":env")) {
for (const auto & [idx, i] : enumerate(state.debugTraces)) {
else if (state.debug.repl && (command == ":env")) {
for (const auto & [idx, i] : enumerate(state.debug.traces)) {
if (idx == debugTraceIndex) {
printEnvBindings(state, i.expr, i.env);
break;
@ -572,13 +572,13 @@ ProcessLineResult NixRepl::processLine(std::string line)
}
}
else if (state.debugRepl && (command == ":st")) {
else if (state.debug.repl && (command == ":st")) {
try {
// change the DebugTrace index.
debugTraceIndex = stoi(arg);
} catch (...) { }
for (const auto & [idx, i] : enumerate(state.debugTraces)) {
for (const auto & [idx, i] : enumerate(state.debug.traces)) {
if (idx == debugTraceIndex) {
std::cout << "\n" << ANSI_BLUE << idx << ANSI_NORMAL << ": ";
showDebugTrace(std::cout, state.positions, i);
@ -590,15 +590,15 @@ ProcessLineResult NixRepl::processLine(std::string line)
}
}
else if (state.debugRepl && (command == ":s" || command == ":step")) {
else if (state.debug.repl && (command == ":s" || command == ":step")) {
// set flag to stop at next DebugTrace; exit repl.
state.debugStop = true;
state.debug.stop = true;
return ProcessLineResult::Continue;
}
else if (state.debugRepl && (command == ":c" || command == ":continue")) {
else if (state.debug.repl && (command == ":c" || command == ":continue")) {
// set flag to run to next breakpoint or end of program; exit repl.
state.debugStop = false;
state.debug.stop = false;
return ProcessLineResult::Continue;
}
@ -771,7 +771,7 @@ ProcessLineResult NixRepl::processLine(std::string line)
}
else if (command == ":q" || command == ":quit") {
state.debugStop = false;
state.debug.stop = false;
return ProcessLineResult::Quit;
}

View file

@ -45,7 +45,7 @@ EvalErrorBuilder<T> & EvalErrorBuilder<T>::withFrame(const Env & env, const Expr
// NOTE: This is abusing side-effects.
// TODO: check compatibility with nested debugger calls.
// TODO: What side-effects??
error.state.debugTraces.push_front(DebugTrace{
error.state.debug.traces.push_front(DebugTrace{
.pos = error.state.positions[expr.getPos()],
.expr = expr,
.env = env,
@ -74,11 +74,11 @@ EvalErrorBuilder<T>::addTrace(PosIdx pos, std::string_view formatString, const A
template<class T>
void EvalErrorBuilder<T>::debugThrow()
{
if (error.state.debugRepl && !error.state.debugTraces.empty()) {
const DebugTrace & last = error.state.debugTraces.front();
if (error.state.debug.repl && !error.state.debug.traces.empty()) {
const DebugTrace & last = error.state.debug.traces.front();
const Env * env = &last.env;
const Expr * expr = &last.expr;
error.state.runDebugRepl(&error, *env, *expr);
error.state.debug.runDebugRepl(error.state, &error, *env, *expr);
}
// `EvalState` is the only class that can construct an `EvalErrorBuilder`,

View file

@ -262,9 +262,6 @@ EvalState::EvalState(
, derivationInternal(rootPath(CanonPath("/builtin/derivation.nix")))
, store(store)
, buildStore(buildStore ? buildStore : store)
, debugRepl(nullptr)
, debugStop(false)
, trylevel(0)
, regexCache(makeRegexCache())
#if HAVE_BOEHMGC
, valueAllocCache(std::allocate_shared<void *>(traceable_allocator<void *>(), nullptr))
@ -590,7 +587,7 @@ void printEnvBindings(const SymbolTable & st, const StaticEnv & se, const Env &
void printEnvBindings(const EvalState &es, const Expr & expr, const Env & env)
{
// just print the names for now
auto se = es.getStaticEnv(expr);
auto se = es.debug.staticEnvFor(expr);
if (se)
printEnvBindings(es.symbols, *se, env, 0);
}
@ -639,18 +636,20 @@ public:
}
};
void EvalState::runDebugRepl(const EvalError * error, const Env & env, const Expr & expr)
void DebugState::runDebugRepl(
EvalState & evalState, const EvalError * error, const Env & env, const Expr & expr
)
{
// Make sure we have a debugger to run and we're not already in a debugger.
if (!debugRepl || inDebugger)
if (!repl || inDebugger)
return;
auto dts =
error && expr.getPos()
? std::make_unique<DebugTraceStacker>(
*this,
evalState,
DebugTrace {
.pos = error->info().pos ? error->info().pos : positions[expr.getPos()],
.pos = error->info().pos ? error->info().pos : evalState.positions[expr.getPos()],
.expr = expr,
.env = env,
.hint = error->info().msg,
@ -666,11 +665,11 @@ void EvalState::runDebugRepl(const EvalError * error, const Env & env, const Exp
printError("This exception occurred in a 'tryEval' call. Use " ANSI_GREEN "--ignore-try" ANSI_NORMAL " to skip these.\n");
}
auto se = getStaticEnv(expr);
auto se = staticEnvFor(expr);
if (se) {
auto vm = mapStaticEnvBindings(symbols, *se.get(), env);
auto vm = mapStaticEnvBindings(evalState.symbols, *se.get(), env);
DebuggerGuard _guard(inDebugger);
auto exitStatus = (debugRepl)(*this, *vm);
auto exitStatus = repl(evalState, *vm);
switch (exitStatus) {
case ReplExitStatus::QuitAll:
if (error)
@ -718,9 +717,9 @@ DebugTraceStacker::DebugTraceStacker(EvalState & evalState, DebugTrace t)
: evalState(evalState)
, trace(std::move(t))
{
evalState.debugTraces.push_front(trace);
if (evalState.debugStop && evalState.debugRepl)
evalState.runDebugRepl(nullptr, trace.env, trace.expr);
evalState.debug.traces.push_front(trace);
if (evalState.debug.stop && evalState.debug.repl)
evalState.debug.runDebugRepl(evalState, nullptr, trace.env, trace.expr);
}
inline Value * EvalState::lookupVar(Env * env, const ExprVar & var, bool noEval)
@ -955,7 +954,7 @@ void EvalState::cacheFile(
fileParseCache[resolvedPath] = e;
try {
auto dts = debugRepl
auto dts = debug.repl
? makeDebugTraceStacker(
*this,
*e,
@ -1173,7 +1172,7 @@ void ExprLet::eval(EvalState & state, Env & env, Value & v)
*i.second.chooseByKind(&env2, &env, inheritEnv));
}
auto dts = state.debugRepl
auto dts = state.debug.repl
? makeDebugTraceStacker(
state,
*this,
@ -1258,7 +1257,7 @@ void ExprSelect::eval(EvalState & state, Env & env, Value & v)
}
try {
auto dts = state.debugRepl
auto dts = state.debug.repl
? makeDebugTraceStacker(
state,
*this,
@ -1576,7 +1575,7 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value &
/* Evaluate the body. */
try {
auto dts = debugRepl
auto dts = debug.repl
? makeDebugTraceStacker(
*this, *lambda.body, env2, positions[lambda.pos],
"while calling %s",
@ -1715,7 +1714,7 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value &
void ExprCall::eval(EvalState & state, Env & env, Value & v)
{
auto dts = state.debugRepl
auto dts = state.debug.repl
? makeDebugTraceStacker(
state,
*this,
@ -2088,7 +2087,7 @@ void EvalState::forceValueDeep(Value & v)
for (auto & i : *v.attrs)
try {
// If the value is a thunk, we're evaling. Otherwise no trace necessary.
auto dts = debugRepl && i.value->isThunk()
auto dts = debug.repl && i.value->isThunk()
? makeDebugTraceStacker(*this, *i.value->thunk.expr, *i.value->thunk.env, positions[i.pos],
"while evaluating the attribute '%1%'", symbols[i.name])
: nullptr;

View file

@ -152,6 +152,28 @@ struct DebugTrace {
bool isError;
};
struct DebugState
{
std::function<ReplExitStatus(EvalState & es, ValMap const & extraEnv)> repl;
bool stop = false;
bool inDebugger = false;
std::list<DebugTrace> traces;
std::map<const Expr *, const std::shared_ptr<const StaticEnv>> exprEnvs;
int trylevel = 0;
void runDebugRepl(
EvalState & evalState, const EvalError * error, const Env & env, const Expr & expr
);
const std::shared_ptr<const StaticEnv> staticEnvFor(const Expr & expr) const
{
if (auto i = exprEnvs.find(&expr); i != exprEnvs.end()) {
return i->second;
}
return nullptr;
}
};
struct StaticSymbols
{
@ -202,25 +224,7 @@ public:
RootValue vCallFlake = nullptr;
RootValue vImportedDrvToDerivation = nullptr;
/**
* Debugger
*/
std::function<ReplExitStatus(EvalState & es, ValMap const & extraEnv)> debugRepl;
bool debugStop;
bool inDebugger = false;
int trylevel;
std::list<DebugTrace> debugTraces;
std::map<const Expr*, const std::shared_ptr<const StaticEnv>> exprEnvs;
const std::shared_ptr<const StaticEnv> getStaticEnv(const Expr & expr) const
{
auto i = exprEnvs.find(&expr);
if (i != exprEnvs.end())
return i->second;
else
return std::shared_ptr<const StaticEnv>();;
}
void runDebugRepl(const EvalError * error, const Env & env, const Expr & expr);
DebugState debug;
template<class T, typename... Args>
[[nodiscard, gnu::noinline]]
@ -760,7 +764,7 @@ struct DebugTraceStacker {
DebugTraceStacker(EvalState & evalState, DebugTrace t);
~DebugTraceStacker()
{
evalState.debugTraces.pop_front();
evalState.debug.traces.pop_front();
}
EvalState & evalState;
DebugTrace trace;

View file

@ -302,32 +302,32 @@ void Expr::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env
void ExprInt::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env)
{
if (es.debugRepl)
es.exprEnvs.insert(std::make_pair(this, env));
if (es.debug.repl)
es.debug.exprEnvs.insert(std::make_pair(this, env));
}
void ExprFloat::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env)
{
if (es.debugRepl)
es.exprEnvs.insert(std::make_pair(this, env));
if (es.debug.repl)
es.debug.exprEnvs.insert(std::make_pair(this, env));
}
void ExprString::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env)
{
if (es.debugRepl)
es.exprEnvs.insert(std::make_pair(this, env));
if (es.debug.repl)
es.debug.exprEnvs.insert(std::make_pair(this, env));
}
void ExprPath::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env)
{
if (es.debugRepl)
es.exprEnvs.insert(std::make_pair(this, env));
if (es.debug.repl)
es.debug.exprEnvs.insert(std::make_pair(this, env));
}
void ExprVar::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env)
{
if (es.debugRepl)
es.exprEnvs.insert(std::make_pair(this, env));
if (es.debug.repl)
es.debug.exprEnvs.insert(std::make_pair(this, env));
fromWith = nullptr;
@ -364,14 +364,14 @@ void ExprVar::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> &
void ExprInheritFrom::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env)
{
if (es.debugRepl)
es.exprEnvs.insert(std::make_pair(this, env));
if (es.debug.repl)
es.debug.exprEnvs.insert(std::make_pair(this, env));
}
void ExprSelect::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env)
{
if (es.debugRepl)
es.exprEnvs.insert(std::make_pair(this, env));
if (es.debug.repl)
es.debug.exprEnvs.insert(std::make_pair(this, env));
e->bindVars(es, env);
if (def) def->bindVars(es, env);
@ -382,8 +382,8 @@ void ExprSelect::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv>
void ExprOpHasAttr::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env)
{
if (es.debugRepl)
es.exprEnvs.insert(std::make_pair(this, env));
if (es.debug.repl)
es.debug.exprEnvs.insert(std::make_pair(this, env));
e->bindVars(es, env);
for (auto & i : attrPath)
@ -414,8 +414,8 @@ std::shared_ptr<const StaticEnv> ExprAttrs::bindInheritSources(
void ExprAttrs::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env)
{
if (es.debugRepl)
es.exprEnvs.insert(std::make_pair(this, env));
if (es.debug.repl)
es.debug.exprEnvs.insert(std::make_pair(this, env));
if (recursive) {
auto newEnv = [&] () -> std::shared_ptr<const StaticEnv> {
@ -453,8 +453,8 @@ void ExprAttrs::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv>
void ExprList::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env)
{
if (es.debugRepl)
es.exprEnvs.insert(std::make_pair(this, env));
if (es.debug.repl)
es.debug.exprEnvs.insert(std::make_pair(this, env));
for (auto & i : elems)
i->bindVars(es, env);
@ -462,8 +462,8 @@ void ExprList::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> &
void ExprLambda::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env)
{
if (es.debugRepl)
es.exprEnvs.insert(std::make_pair(this, env));
if (es.debug.repl)
es.debug.exprEnvs.insert(std::make_pair(this, env));
auto newEnv = std::make_shared<StaticEnv>(
nullptr, env.get(),
@ -489,8 +489,8 @@ void ExprLambda::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv>
void ExprCall::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env)
{
if (es.debugRepl)
es.exprEnvs.insert(std::make_pair(this, env));
if (es.debug.repl)
es.debug.exprEnvs.insert(std::make_pair(this, env));
fun->bindVars(es, env);
for (auto & e : args)
@ -514,16 +514,16 @@ void ExprLet::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> &
for (auto & i : attrs->attrs)
i.second.e->bindVars(es, i.second.chooseByKind(newEnv, env, inheritFromEnv));
if (es.debugRepl)
es.exprEnvs.insert(std::make_pair(this, env));
if (es.debug.repl)
es.debug.exprEnvs.insert(std::make_pair(this, env));
body->bindVars(es, newEnv);
}
void ExprWith::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env)
{
if (es.debugRepl)
es.exprEnvs.insert(std::make_pair(this, env));
if (es.debug.repl)
es.debug.exprEnvs.insert(std::make_pair(this, env));
parentWith = nullptr;
for (auto * e = env.get(); e && !parentWith; e = e->up)
@ -548,8 +548,8 @@ void ExprWith::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> &
void ExprIf::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env)
{
if (es.debugRepl)
es.exprEnvs.insert(std::make_pair(this, env));
if (es.debug.repl)
es.debug.exprEnvs.insert(std::make_pair(this, env));
cond->bindVars(es, env);
then->bindVars(es, env);
@ -558,8 +558,8 @@ void ExprIf::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & e
void ExprAssert::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env)
{
if (es.debugRepl)
es.exprEnvs.insert(std::make_pair(this, env));
if (es.debug.repl)
es.debug.exprEnvs.insert(std::make_pair(this, env));
cond->bindVars(es, env);
body->bindVars(es, env);
@ -567,16 +567,16 @@ void ExprAssert::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv>
void ExprOpNot::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env)
{
if (es.debugRepl)
es.exprEnvs.insert(std::make_pair(this, env));
if (es.debug.repl)
es.debug.exprEnvs.insert(std::make_pair(this, env));
e->bindVars(es, env);
}
void ExprConcatStrings::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env)
{
if (es.debugRepl)
es.exprEnvs.insert(std::make_pair(this, env));
if (es.debug.repl)
es.debug.exprEnvs.insert(std::make_pair(this, env));
for (auto & i : this->es)
i.second->bindVars(es, env);
@ -584,8 +584,8 @@ void ExprConcatStrings::bindVars(EvalState & es, const std::shared_ptr<const Sta
void ExprPos::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env)
{
if (es.debugRepl)
es.exprEnvs.insert(std::make_pair(this, env));
if (es.debug.repl)
es.debug.exprEnvs.insert(std::make_pair(this, env));
}

View file

@ -566,15 +566,15 @@ static void prim_genericClosure(EvalState & state, const PosIdx pos, Value * * a
static void prim_break(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
if (state.debugRepl && !state.debugTraces.empty()) {
if (state.debug.repl && !state.debug.traces.empty()) {
auto error = EvalError(state, ErrorInfo {
.level = lvlInfo,
.msg = HintFmt("breakpoint reached"),
.pos = state.positions[pos],
});
auto & dt = state.debugTraces.front();
state.runDebugRepl(&error, dt.env, dt.expr);
auto & dt = state.debug.traces.front();
state.debug.runDebugRepl(state, &error, dt.env, dt.expr);
}
// Return the value we were passed.
@ -638,14 +638,14 @@ static void prim_tryEval(EvalState & state, const PosIdx pos, Value * * args, Va
auto attrs = state.buildBindings(2);
/* increment state.trylevel, and decrement it when this function returns. */
MaintainCount trylevel(state.trylevel);
MaintainCount trylevel(state.debug.trylevel);
std::function<ReplExitStatus(EvalState & es, ValMap const & extraEnv)> savedDebugRepl;
if (state.debugRepl && evalSettings.ignoreExceptionsDuringTry)
if (state.debug.repl && evalSettings.ignoreExceptionsDuringTry)
{
/* to prevent starting the repl from exceptions withing a tryEval, null it. */
savedDebugRepl = state.debugRepl;
state.debugRepl = nullptr;
savedDebugRepl = state.debug.repl;
state.debug.repl = nullptr;
}
try {
@ -659,7 +659,7 @@ static void prim_tryEval(EvalState & state, const PosIdx pos, Value * * args, Va
// restore the debugRepl pointer if we saved it earlier.
if (savedDebugRepl)
state.debugRepl = savedDebugRepl;
state.debug.repl = savedDebugRepl;
v.mkAttrs(attrs);
}
@ -697,9 +697,9 @@ static void prim_trace(EvalState & state, const PosIdx pos, Value * * args, Valu
printError("trace: %1%", args[0]->string.s);
else
printError("trace: %1%", ValuePrinter(state, *args[0]));
if (evalSettings.builtinsTraceDebugger && state.debugRepl && !state.debugTraces.empty()) {
const DebugTrace & last = state.debugTraces.front();
state.runDebugRepl(nullptr, last.env, last.expr);
if (evalSettings.builtinsTraceDebugger && state.debug.repl && !state.debug.traces.empty()) {
const DebugTrace & last = state.debug.traces.front();
state.debug.runDebugRepl(state, nullptr, last.env, last.expr);
}
state.forceValue(*args[1], pos);
v = *args[1];