diff options
-rw-r--r-- | Makefile | 3 | ||||
-rw-r--r-- | doc/manual/manual.is-valid | 0 | ||||
-rw-r--r-- | src/libcmd/command.cc | 46 | ||||
-rw-r--r-- | src/libcmd/command.hh | 8 | ||||
-rw-r--r-- | src/libcmd/local.mk | 6 | ||||
-rw-r--r-- | src/libcmd/repl.cc (renamed from src/nix/repl.cc) | 154 | ||||
-rw-r--r-- | src/libexpr/eval.cc | 346 | ||||
-rw-r--r-- | src/libexpr/eval.hh | 35 | ||||
-rw-r--r-- | src/libexpr/nixexpr.cc | 140 | ||||
-rw-r--r-- | src/libexpr/nixexpr.hh | 39 | ||||
-rw-r--r-- | src/libexpr/parser.y | 7 | ||||
-rw-r--r-- | src/libexpr/primops.cc | 32 | ||||
-rw-r--r-- | src/libutil/error.hh | 9 |
13 files changed, 679 insertions, 146 deletions
@@ -18,7 +18,7 @@ makefiles = \ misc/systemd/local.mk \ misc/launchd/local.mk \ misc/upstart/local.mk \ - doc/manual/local.mk \ + # doc/manual/local.mk \ tests/local.mk \ tests/plugins/local.mk @@ -34,4 +34,5 @@ endif include mk/lib.mk +# GLOBAL_CXXFLAGS += -g -Wall -include config.h -std=c++17 -fstack-usage GLOBAL_CXXFLAGS += -g -Wall -include config.h -std=c++17 -I src diff --git a/doc/manual/manual.is-valid b/doc/manual/manual.is-valid new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/doc/manual/manual.is-valid diff --git a/src/libcmd/command.cc b/src/libcmd/command.cc index 6d183dfad..56288665a 100644 --- a/src/libcmd/command.cc +++ b/src/libcmd/command.cc @@ -86,8 +86,17 @@ ref<Store> CopyCommand::getDstStore() EvalCommand::EvalCommand() { + addFlag({ + .longName = "debugger", + .description = "start an interactive environment if evaluation fails", + .handler = {&startReplOnEvalErrors, true}, + }); } +extern std::function<void(const Error * error, const Env & env, const Expr & expr)> debuggerHook; + + + EvalCommand::~EvalCommand() { if (evalState) @@ -103,7 +112,7 @@ ref<Store> EvalCommand::getEvalStore() ref<EvalState> EvalCommand::getEvalState() { - if (!evalState) + if (!evalState) { evalState = #if HAVE_BOEHMGC std::allocate_shared<EvalState>(traceable_allocator<EvalState>(), @@ -113,6 +122,41 @@ ref<EvalState> EvalCommand::getEvalState() searchPath, getEvalStore(), getStore()) #endif ; + if (startReplOnEvalErrors) + debuggerHook = [evalState{ref<EvalState>(evalState)}](const Error * error, const Env & env, const Expr & expr) { + // clear the screen. + // std::cout << "\033[2J\033[1;1H"; + + if (error) + printError("%s\n\n" ANSI_BOLD "Starting REPL to allow you to inspect the current state of the evaluator.\n" ANSI_NORMAL, error->what()); + else + { + auto iter = evalState->debugTraces.begin(); + if (iter != evalState->debugTraces.end()) { + std::cout << "\n" << "… " << iter->hint.str() << "\n"; + + if (iter->pos.has_value() && (*iter->pos)) { + auto pos = iter->pos.value(); + std::cout << "\n"; + printAtPos(pos, std::cout); + + auto loc = getCodeLines(pos); + if (loc.has_value()) { + std::cout << "\n"; + printCodeLines(std::cout, "", pos, *loc); + std::cout << "\n"; + } + } + } + } + + if (expr.staticenv) + { + auto vm = mapStaticEnvBindings(*expr.staticenv.get(), env); + runRepl(evalState, error, expr, *vm); + } + }; + } return ref<EvalState>(evalState); } diff --git a/src/libcmd/command.hh b/src/libcmd/command.hh index bd2a0a7ee..146a08ed6 100644 --- a/src/libcmd/command.hh +++ b/src/libcmd/command.hh @@ -58,6 +58,8 @@ struct CopyCommand : virtual StoreCommand struct EvalCommand : virtual StoreCommand, MixEvalArgs { + bool startReplOnEvalErrors = false; + EvalCommand(); ~EvalCommand(); @@ -323,4 +325,10 @@ void printClosureDiff( const StorePath & afterPath, std::string_view indent); + +void runRepl( + ref<EvalState> evalState, + const Error *debugError, + const Expr &expr, + const std::map<std::string, Value *> & extraEnv); } diff --git a/src/libcmd/local.mk b/src/libcmd/local.mk index 7a2f83cc7..713c1bf11 100644 --- a/src/libcmd/local.mk +++ b/src/libcmd/local.mk @@ -6,10 +6,12 @@ libcmd_DIR := $(d) libcmd_SOURCES := $(wildcard $(d)/*.cc) -libcmd_CXXFLAGS += -I src/libutil -I src/libstore -I src/libexpr -I src/libmain -I src/libfetchers +libcmd_CXXFLAGS += -I src/libutil -I src/libstore -I src/libexpr -I src/libmain -I src/libfetchers -I src/nix +# libcmd_LDFLAGS += -llowdown -pthread +# libcmd_LDFLAGS = $(EDITLINE_LIBS) -llowdown -pthread libcmd_LDFLAGS += $(LOWDOWN_LIBS) -pthread -libcmd_LIBS = libstore libutil libexpr libmain libfetchers +libcmd_LIBS = libstore libutil libexpr libmain libfetchers libnix $(eval $(call install-file-in, $(d)/nix-cmd.pc, $(libdir)/pkgconfig, 0644)) diff --git a/src/nix/repl.cc b/src/libcmd/repl.cc index e9bebff17..51bbbbc57 100644 --- a/src/nix/repl.cc +++ b/src/libcmd/repl.cc @@ -46,20 +46,22 @@ struct NixRepl #endif { string curDir; - std::unique_ptr<EvalState> state; + ref<EvalState> state; Bindings * autoArgs; + const Error *debugError; + Strings loadedFiles; const static int envSize = 32768; - StaticEnv staticEnv; + std::shared_ptr<StaticEnv> staticEnv; Env * env; int displ; StringSet varNames; const Path historyFile; - NixRepl(const Strings & searchPath, nix::ref<Store> store); + NixRepl(ref<EvalState> state); ~NixRepl(); void mainLoop(const std::vector<std::string> & files); StringSet completePrefix(string prefix); @@ -69,6 +71,7 @@ struct NixRepl void loadFile(const Path & path); void loadFlake(const std::string & flakeRef); void initEnv(); + void loadFiles(); void reloadFiles(); void addAttrsToScope(Value & attrs); void addVarToScope(const Symbol & name, Value & v); @@ -76,8 +79,8 @@ struct NixRepl void evalString(string s, Value & v); typedef set<Value *> ValuesSeen; - std::ostream & printValue(std::ostream & str, Value & v, unsigned int maxDepth); - std::ostream & printValue(std::ostream & str, Value & v, unsigned int maxDepth, ValuesSeen & seen); + std::ostream & printValue(std::ostream & str, Value & v, unsigned int maxDepth); + std::ostream & printValue(std::ostream & str, Value & v, unsigned int maxDepth, ValuesSeen & seen); }; @@ -90,9 +93,9 @@ string removeWhitespace(string s) } -NixRepl::NixRepl(const Strings & searchPath, nix::ref<Store> store) - : state(std::make_unique<EvalState>(searchPath, store)) - , staticEnv(false, &state->staticBaseEnv) +NixRepl::NixRepl(ref<EvalState> state) + : state(state) + , staticEnv(new StaticEnv(false, state->staticBaseEnv.get())) , historyFile(getDataDir() + "/nix/repl-history") { curDir = absPath("."); @@ -201,10 +204,12 @@ void NixRepl::mainLoop(const std::vector<std::string> & files) string error = ANSI_RED "error:" ANSI_NORMAL " "; notice("Welcome to Nix " + nixVersion + ". Type :? for help.\n"); - for (auto & i : files) - loadedFiles.push_back(i); + if (!files.empty()) { + for (auto & i : files) + loadedFiles.push_back(i); + } - reloadFiles(); + loadFiles(); if (!loadedFiles.empty()) notice(""); // Allow nix-repl specific settings in .inputrc @@ -393,7 +398,6 @@ StorePath NixRepl::getDerivationPath(Value & v) { return drvPath; } - bool NixRepl::processLine(string line) { if (line == "") return true; @@ -431,7 +435,61 @@ bool NixRepl::processLine(string line) << " :u <expr> Build derivation, then start nix-shell\n" << " :doc <expr> Show documentation of a builtin function\n" << " :log <expr> Show logs for a derivation\n" - << " :st [bool] Enable, disable or toggle showing traces for errors\n"; + << " :st [bool] Enable, disable or toggle showing traces for errors\n" + << " :d <cmd> Debug mode commands\n" + << " :d stack Show call stack\n" + << " :d env Show env stack\n" + << " :d error Show current error\n" + << " :d go Go until end of program, exception, or builtins.break().\n" + << " :d step Go one step\n" + ; + + } + + else if (command == ":d" || command == ":debug") { + if (arg == "stack") { + for (auto iter = this->state->debugTraces.begin(); + iter != this->state->debugTraces.end(); ++iter) { + std::cout << "\n" << "… " << iter->hint.str() << "\n"; + + if (iter->pos.has_value() && (*iter->pos)) { + auto pos = iter->pos.value(); + std::cout << "\n"; + printAtPos(pos, std::cout); + + auto loc = getCodeLines(pos); + if (loc.has_value()) { + std::cout << "\n"; + printCodeLines(std::cout, "", pos, *loc); + std::cout << "\n"; + } + } + } + } else if (arg == "env") { + auto iter = this->state->debugTraces.begin(); + if (iter != this->state->debugTraces.end()) { + printStaticEnvBindings(iter->expr); + } + } + else if (arg == "error") { + if (this->debugError) { + showErrorInfo(std::cout, debugError->info(), true); + } + else + { + notice("error information not available"); + } + } + else if (arg == "step") { + // set flag and exit repl. + state->debugStop = true; + return false; + } + else if (arg == "go") { + // set flag and exit repl. + state->debugStop = false; + return false; + } } else if (command == ":a" || command == ":add") { @@ -646,10 +704,10 @@ void NixRepl::initEnv() env = &state->allocEnv(envSize); env->up = &state->baseEnv; displ = 0; - staticEnv.vars.clear(); + staticEnv->vars.clear(); varNames.clear(); - for (auto & i : state->staticBaseEnv.vars) + for (auto & i : state->staticBaseEnv->vars) varNames.insert(i.first); } @@ -658,6 +716,12 @@ void NixRepl::reloadFiles() { initEnv(); + loadFiles(); +} + + +void NixRepl::loadFiles() +{ Strings old = loadedFiles; loadedFiles.clear(); @@ -678,12 +742,12 @@ void NixRepl::addAttrsToScope(Value & attrs) throw Error("environment full; cannot add more variables"); for (auto & i : *attrs.attrs) { - staticEnv.vars.emplace_back(i.name, displ); + staticEnv->vars.emplace_back(i.name, displ); env->values[displ++] = i.value; varNames.insert((string) i.name); } - staticEnv.sort(); - staticEnv.deduplicate(); + staticEnv->sort(); + staticEnv->deduplicate(); notice("Added %1% variables.", attrs.attrs->size()); } @@ -692,15 +756,14 @@ void NixRepl::addVarToScope(const Symbol & name, Value & v) { if (displ >= envSize) throw Error("environment full; cannot add more variables"); - if (auto oldVar = staticEnv.find(name); oldVar != staticEnv.vars.end()) - staticEnv.vars.erase(oldVar); - staticEnv.vars.emplace_back(name, displ); - staticEnv.sort(); + if (auto oldVar = staticEnv->find(name); oldVar != staticEnv->vars.end()) + staticEnv->vars.erase(oldVar); + staticEnv->vars.emplace_back(name, displ); + staticEnv->sort(); env->values[displ++] = &v; varNames.insert((string) name); } - Expr * NixRepl::parseString(string s) { Expr * e = state->parseExprFromString(std::move(s), curDir, staticEnv); @@ -860,6 +923,45 @@ std::ostream & NixRepl::printValue(std::ostream & str, Value & v, unsigned int m return str; } +void runRepl( + ref<EvalState> evalState, + const Error *debugError, + const Expr &expr, + const std::map<std::string, Value *> & extraEnv) +{ + auto repl = std::make_unique<NixRepl>(evalState); + + // repl->debugError = debugError; + + repl->initEnv(); + + // auto dts = debugError ? + // std::unique_ptr<DebugTraceStacker>( + // // tack on a final DebugTrace for the error position. + // new DebugTraceStacker( + // *evalState, + // DebugTrace + // {.pos = debugError->info().errPos, + // .expr = expr, + // .env = *repl->env, + // .hint = debugError->info().msg + // }) + // ) + // : nullptr; + + // add 'extra' vars. + std::set<std::string> names; + for (auto & [name, value] : extraEnv) { + // names.insert(ANSI_BOLD + name + ANSI_NORMAL); + names.insert(name); + repl->addVarToScope(repl->state->symbols.create(name), *value); + } + + printError(hintfmt("The following extra variables are in scope: %s\n", concatStringsSep(", ", names)).str()); + + repl->mainLoop({}); +} + struct CmdRepl : StoreCommand, MixEvalArgs { std::vector<std::string> files; @@ -888,8 +990,12 @@ struct CmdRepl : StoreCommand, MixEvalArgs void run(ref<Store> store) override { evalSettings.pureEval = false; - auto repl = std::make_unique<NixRepl>(searchPath, openStore()); + + auto evalState = make_ref<EvalState>(searchPath, store); + + auto repl = std::make_unique<NixRepl>(evalState); repl->autoArgs = getAutoArgs(*repl->state); + repl->initEnv(); repl->mainLoop(files); } }; diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc index 2d4eb57fc..790b00ace 100644 --- a/src/libexpr/eval.cc +++ b/src/libexpr/eval.cc @@ -18,6 +18,7 @@ #include <sys/resource.h> #include <iostream> #include <fstream> +#include <functional> #include <sys/resource.h> @@ -36,6 +37,7 @@ namespace nix { +std::function<void(const Error * error, const Env & env, const Expr & expr)> debuggerHook; static char * allocString(size_t size) { @@ -435,6 +437,7 @@ EvalState::EvalState( , emptyBindings(0) , store(store) , buildStore(buildStore ? buildStore : store) + , debugStop(true) , regexCache(makeRegexCache()) #if HAVE_BOEHMGC , valueAllocCache(std::allocate_shared<void *>(traceable_allocator<void *>(), nullptr)) @@ -442,7 +445,7 @@ EvalState::EvalState( , valueAllocCache(std::make_shared<void *>(nullptr)) #endif , baseEnv(allocEnv(128)) - , staticBaseEnv(false, 0) + , staticBaseEnv(new StaticEnv(false, 0)) { countCalls = getEnv("NIX_COUNT_CALLS").value_or("0") != "0"; @@ -617,7 +620,7 @@ Value * EvalState::addConstant(const string & name, Value & v) void EvalState::addConstant(const string & name, Value * v) { - staticBaseEnv.vars.emplace_back(symbols.create(name), baseEnvDispl); + staticBaseEnv->vars.emplace_back(symbols.create(name), baseEnvDispl); baseEnv.values[baseEnvDispl++] = v; string name2 = string(name, 0, 2) == "__" ? string(name, 2) : name; baseEnv.values[0]->attrs->push_back(Attr(symbols.create(name2), v)); @@ -642,7 +645,7 @@ Value * EvalState::addPrimOp(const string & name, Value * v = allocValue(); v->mkPrimOp(new PrimOp { .fun = primOp, .arity = arity, .name = sym }); - staticBaseEnv.vars.emplace_back(symbols.create(name), baseEnvDispl); + staticBaseEnv->vars.emplace_back(symbols.create(name), baseEnvDispl); baseEnv.values[baseEnvDispl++] = v; baseEnv.values[0]->attrs->push_back(Attr(sym, v)); return v; @@ -668,7 +671,7 @@ Value * EvalState::addPrimOp(PrimOp && primOp) Value * v = allocValue(); v->mkPrimOp(new PrimOp(std::move(primOp))); - staticBaseEnv.vars.emplace_back(envName, baseEnvDispl); + staticBaseEnv->vars.emplace_back(envName, baseEnvDispl); baseEnv.values[baseEnvDispl++] = v; baseEnv.values[0]->attrs->push_back(Attr(primOp.name, v)); return v; @@ -697,90 +700,237 @@ std::optional<EvalState::Doc> EvalState::getDoc(Value & v) return {}; } +void printStaticEnvBindings(const StaticEnv &se, int lvl) +{ + std::cout << "Env level " << lvl << std::endl; + + if (se.up) { + std::cout << ANSI_MAGENTA; + for (auto i = se.vars.begin(); i != se.vars.end(); ++i) + { + std::cout << i->first << " "; + } + std::cout << ANSI_NORMAL; + + std::cout << std::endl; + std::cout << std::endl; + + printStaticEnvBindings(*se.up, ++lvl); + } + else + { + std::cout << ANSI_MAGENTA; + // for the top level, don't print the double underscore ones; they are in builtins. + for (auto i = se.vars.begin(); i != se.vars.end(); ++i) + { + if (((string)i->first).substr(0,2) != "__") + std::cout << i->first << " "; + } + std::cout << ANSI_NORMAL; + std::cout << std::endl; + std::cout << std::endl; + + } + +} + +void printStaticEnvBindings(const Expr &expr) +{ + // just print the names for now + if (expr.staticenv) + { + printStaticEnvBindings(*expr.staticenv.get(), 0); + } +} + +void mapStaticEnvBindings(const StaticEnv &se, const Env &env, valmap & vm) +{ + // add bindings for the next level up first, so that the bindings for this level + // override the higher levels. + // The top level bindings (builtins) are skipped since they are added for us by initEnv() + if (env.up && se.up) { + mapStaticEnvBindings(*se.up, *env.up,vm); + + // iterate through staticenv bindings and add them. + auto map = valmap(); + for (auto iter = se.vars.begin(); iter != se.vars.end(); ++iter) + { + map[iter->first] = env.values[iter->second]; + } + + vm.merge(map); + } +} + +valmap * mapStaticEnvBindings(const StaticEnv &se, const Env &env) +{ + auto vm = new valmap(); + mapStaticEnvBindings(se, env, *vm); + return vm; +} + /* Every "format" object (even temporary) takes up a few hundred bytes of stack space, which is a real killer in the recursive evaluator. So here are some helper functions for throwing exceptions. */ -LocalNoInlineNoReturn(void throwEvalError(const char * s, const string & s2)) +LocalNoInlineNoReturn(void throwEvalError(const char * s, const string & s2, Env & env, Expr *expr)) { - throw EvalError(s, s2); + auto error = EvalError(s, s2); + + if (debuggerHook && expr) + debuggerHook(&error, env, *expr); + throw error; } -LocalNoInlineNoReturn(void throwEvalError(const Pos & pos, const char * s, const string & s2)) +LocalNoInlineNoReturn(void throwEvalError(const Pos & pos, const char * s, const string & s2, Env & env, Expr *expr)) { - throw EvalError({ + auto error = EvalError({ .msg = hintfmt(s, s2), .errPos = pos }); + + if (debuggerHook && expr) + debuggerHook(&error, env, *expr); + + throw error; } -LocalNoInlineNoReturn(void throwEvalError(const char * s, const string & s2, const string & s3)) +LocalNoInlineNoReturn(void throwEvalError(const char * s, const string & s2, const string & s3, Env & env, Expr *expr)) { - throw EvalError(s, s2, s3); + auto error = EvalError(s, s2, s3); + + if (debuggerHook && expr) + debuggerHook(&error, env, *expr); + + throw error; } -LocalNoInlineNoReturn(void throwEvalError(const Pos & pos, const char * s, const string & s2, const string & s3)) +LocalNoInlineNoReturn(void throwEvalError(const Pos & pos, const char * s, const string & s2, const string & s3, Env & env, Expr *expr)) { - throw EvalError({ + auto error = EvalError({ .msg = hintfmt(s, s2, s3), .errPos = pos }); + + if (debuggerHook && expr) + debuggerHook(&error, env, *expr); + + throw error; } -LocalNoInlineNoReturn(void throwEvalError(const Pos & p1, const char * s, const Symbol & sym, const Pos & p2)) +LocalNoInlineNoReturn(void throwEvalError(const Pos & p1, const char * s, const Symbol & sym, const Pos & p2, Env & env, Expr *expr)) { // p1 is where the error occurred; p2 is a position mentioned in the message. - throw EvalError({ + auto error = EvalError({ .msg = hintfmt(s, sym, p2), .errPos = p1 }); + + if (debuggerHook && expr) + debuggerHook(&error, env, *expr); + + throw error; } -LocalNoInlineNoReturn(void throwTypeError(const Pos & pos, const char * s)) +LocalNoInlineNoReturn(void throwTypeError(const Pos & pos, const char * s, Env & env, Expr *expr)) { - throw TypeError({ + auto error = TypeError({ .msg = hintfmt(s), .errPos = pos }); + + if (debuggerHook && expr) + debuggerHook(&error, env, *expr); + + throw error; } -LocalNoInlineNoReturn(void throwTypeError(const Pos & pos, const char * s, const ExprLambda & fun, const Symbol & s2)) +LocalNoInlineNoReturn(void throwTypeError(const Pos & pos, const char * s, const Value & v, Env & env, Expr *expr)) { - throw TypeError({ - .msg = hintfmt(s, fun.showNamePos(), s2), + auto error = TypeError({ + .msg = hintfmt(s, v), .errPos = pos }); + + if (debuggerHook && expr) + debuggerHook(&error, env, *expr); + + throw error; } -LocalNoInlineNoReturn(void throwTypeError(const char * s, const Value & v)) +LocalNoInlineNoReturn(void throwTypeError(const Pos & pos, const char * s, const string &s2, Env & env, Expr *expr)) { - throw TypeError(s, showType(v)); + auto error = TypeError({ + .msg = hintfmt(s, s2), + .errPos = pos + }); + + if (debuggerHook && expr) + debuggerHook(&error, env, *expr); + + throw error; +} + +LocalNoInlineNoReturn(void throwTypeError(const Pos & pos, const char * s, const ExprLambda & fun, const Symbol & s2, Env & env, Expr *expr)) +{ + auto error = TypeError({ + .msg = hintfmt(s, fun.showNamePos(), s2), + .errPos = pos + }); + + if (debuggerHook && expr) + debuggerHook(&error, env, *expr); + + throw error; } -LocalNoInlineNoReturn(void throwAssertionError(const Pos & pos, const char * s, const string & s1)) +// LocalNoInlineNoReturn(void throwTypeError(const char * s, const Value & v, Env & env, Expr *expr)) +// { +// auto error = TypeError({ +// .msg = hintfmt(s, showType(v)) +// .errPos = e ; +// } + +LocalNoInlineNoReturn(void throwAssertionError(const Pos & pos, const char * s, const string & s1, Env & env, Expr *expr)) { - throw AssertionError({ + auto error = AssertionError({ .msg = hintfmt(s, s1), .errPos = pos }); + + if (debuggerHook && expr) + debuggerHook(&error, env, *expr); + + throw error; } -LocalNoInlineNoReturn(void throwUndefinedVarError(const Pos & pos, const char * s, const string & s1)) +LocalNoInlineNoReturn(void throwUndefinedVarError(const Pos & pos, const char * s, const string & s1, Env & env, Expr *expr)) { - throw UndefinedVarError({ + auto error = UndefinedVarError({ .msg = hintfmt(s, s1), .errPos = pos }); + + if (debuggerHook && expr) { + debuggerHook(&error, env, *expr); + } + + throw error; } -LocalNoInlineNoReturn(void throwMissingArgumentError(const Pos & pos, const char * s, const string & s1)) +LocalNoInlineNoReturn(void throwMissingArgumentError(const Pos & pos, const char * s, const string & s1, Env & env, Expr *expr)) { - throw MissingArgumentError({ + auto error = MissingArgumentError({ .msg = hintfmt(s, s1), .errPos = pos }); + + if (debuggerHook && expr) + debuggerHook(&error, env, *expr); + + throw error; } LocalNoInline(void addErrorTrace(Error & e, const char * s, const string & s2)) @@ -793,6 +943,27 @@ LocalNoInline(void addErrorTrace(Error & e, const Pos & pos, const char * s, con e.addTrace(pos, s, s2); } +LocalNoInline(std::unique_ptr<DebugTraceStacker> + makeDebugTraceStacker(EvalState &state, Expr &expr, Env &env, std::optional<ErrPos> pos, const char * s, const string & s2)) +{ + return std::unique_ptr<DebugTraceStacker>( + new DebugTraceStacker( + state, + DebugTrace + {.pos = pos, + .expr = expr, + .env = env, + .hint = hintfmt(s, s2) + })); +} + +DebugTraceStacker::DebugTraceStacker(EvalState &evalState, DebugTrace t) +:evalState(evalState), trace(t) +{ + evalState.debugTraces.push_front(t); + if (evalState.debugStop && debuggerHook) + debuggerHook(0, t.env, t.expr); +} void Value::mkString(std::string_view s) { @@ -850,8 +1021,9 @@ inline Value * EvalState::lookupVar(Env * env, const ExprVar & var, bool noEval) if (countCalls) attrSelects[*j->pos]++; return j->value; } - if (!env->prevWith) - throwUndefinedVarError(var.pos, "undefined variable '%1%'", var.name); + if (!env->prevWith) { + throwUndefinedVarError(var.pos, "undefined variable '%1%'", var.name, *env, (Expr*)&var); + } for (size_t l = env->prevWith; l; --l, env = env->up) ; } } @@ -882,6 +1054,7 @@ Value * EvalState::allocValue() Env & EvalState::allocEnv(size_t size) { + nrEnvs++; nrValuesInEnvs += size; Env * env = (Env *) allocBytes(sizeof(Env) + size * sizeof(Value *)); @@ -892,6 +1065,15 @@ Env & EvalState::allocEnv(size_t size) return *env; } +Env & fakeEnv(size_t size) +{ + // making a fake Env so we'll have one to pass to exception ftns. + // a placeholder until we can pass real envs everywhere they're needed. + Env * env = (Env *) allocBytes(sizeof(Env) + size * sizeof(Value *)); + env->type = Env::Plain; + + return *env; +} void EvalState::mkList(Value & v, size_t size) { @@ -1024,6 +1206,16 @@ void EvalState::cacheFile( fileParseCache[resolvedPath] = e; try { + auto dts = + debuggerHook ? + makeDebugTraceStacker( + *this, + *e, + this->baseEnv, + (e->getPos() ? std::optional(ErrPos(*e->getPos())) : std::nullopt), + "while evaluating the file '%1%':", resolvedPath) + : nullptr; + // Enforce that 'flake.nix' is a direct attrset, not a // computation. if (mustBeTrivial && @@ -1175,7 +1367,8 @@ void ExprAttrs::eval(EvalState & state, Env & env, Value & v) Symbol nameSym = state.symbols.create(nameVal.string.s); Bindings::iterator j = v.attrs->find(nameSym); if (j != v.attrs->end()) - throwEvalError(i.pos, "dynamic attribute '%1%' already defined at %2%", nameSym, *j->pos); + throwEvalError(i.pos, "dynamic attribute '%1%' already defined at %2%", nameSym, *j->pos, + env, this); i.valueExpr->setName(nameSym); /* Keep sorted order so find can catch duplicates */ @@ -1247,6 +1440,16 @@ void ExprSelect::eval(EvalState & state, Env & env, Value & v) e->eval(state, env, vTmp); try { + auto dts = + debuggerHook ? + makeDebugTraceStacker( + state, + *this, + env, + *pos2, + "while evaluating the attribute '%1%'", + showAttrPath(state, env, attrPath)) + : nullptr; for (auto & i : attrPath) { state.nrLookups++; @@ -1263,7 +1466,7 @@ void ExprSelect::eval(EvalState & state, Env & env, Value & v) } else { state.forceAttrs(*vAttrs, pos); if ((j = vAttrs->attrs->find(name)) == vAttrs->attrs->end()) - throwEvalError(pos, "attribute '%1%' missing", name); + throwEvalError(pos, "attribute '%1%' missing", name, env, this); } vAttrs = j->value; pos2 = j->pos; @@ -1350,7 +1553,6 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value & if (!lambda.hasFormals()) env2.values[displ++] = args[0]; - else { forceAttrs(*args[0], pos); @@ -1365,7 +1567,7 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value & auto j = args[0]->attrs->get(i.name); if (!j) { if (!i.def) throwTypeError(pos, "%1% called without required argument '%2%'", - lambda, i.name); + lambda, i.name, *fun.lambda.env, &lambda); env2.values[displ++] = i.def->maybeThunk(*this, env2); } else { attrsUsed++; @@ -1380,7 +1582,8 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value & user. */ for (auto & i : *args[0]->attrs) if (!lambda.formals->has(i.name)) - throwTypeError(pos, "%1% called with unexpected argument '%2%'", lambda, i.name); + throwTypeError(pos, "%1% called with unexpected argument '%2%'", + lambda, i.name, *fun.lambda.env, &lambda); abort(); // can't happen } } @@ -1390,6 +1593,15 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value & /* Evaluate the body. */ try { + auto dts = + debuggerHook ? + makeDebugTraceStacker(*this, *lambda.body, env2, lambda.pos, + "while evaluating %s", + (lambda.name.set() + ? "'" + (string) lambda.name + "'" + : "anonymous lambda")) + : nullptr; + lambda.body->eval(*this, env2, vCur); } catch (Error & e) { if (loggerSettings.showTrace.get()) { @@ -1544,8 +1756,9 @@ void EvalState::autoCallFunction(Bindings & args, Value & fun, Value & res) Nix attempted to evaluate a function as a top level expression; in this case it must have its arguments supplied either by default values, or passed explicitly with '--arg' or '--argstr'. See -https://nixos.org/manual/nix/stable/#ss-functions.)", i.name); - +https://nixos.org/manual/nix/stable/#ss-functions.)", + i.name, + *fun.lambda.env, fun.lambda.fun); } } } @@ -1577,7 +1790,7 @@ void ExprAssert::eval(EvalState & state, Env & env, Value & v) if (!state.evalBool(env, cond, pos)) { std::ostringstream out; cond->show(out); - throwAssertionError(pos, "assertion '%1%' failed", out.str()); + throwAssertionError(pos, "assertion '%1%' failed", out.str(), env, this); } body->eval(state, env, v); } @@ -1753,15 +1966,16 @@ void ExprConcatStrings::eval(EvalState & state, Env & env, Value & v) firstType = nFloat; nf = n; nf += vTmp.fpoint; - } else - throwEvalError(i_pos, "cannot add %1% to an integer", showType(vTmp)); + } else { + throwEvalError(i_pos, "cannot add %1% to an integer", showType(vTmp), env, this); + } } else if (firstType == nFloat) { if (vTmp.type() == nInt) { nf += vTmp.integer; } else if (vTmp.type() == nFloat) { nf += vTmp.fpoint; } else - throwEvalError(i_pos, "cannot add %1% to a float", showType(vTmp)); + throwEvalError(i_pos, "cannot add %1% to a float", showType(vTmp), env, this); } else { if (s.empty()) s.reserve(es->size()); /* skip canonization of first path, which would only be not @@ -1808,6 +2022,16 @@ void EvalState::forceValueDeep(Value & v) if (v.type() == nAttrs) { for (auto & i : *v.attrs) try { + + auto dts = + debuggerHook ? + // if the value is a thunk, we're evaling. otherwise no trace necessary. + (i.value->isThunk() ? + makeDebugTraceStacker(*this, *v.thunk.expr, *v.thunk.env, *i.pos, + "while evaluating the attribute '%1%'", i.name) + : nullptr) + : nullptr; + recurse(*i.value); } catch (Error & e) { addErrorTrace(e, *i.pos, "while evaluating the attribute '%1%'", i.name); @@ -1829,7 +2053,8 @@ NixInt EvalState::forceInt(Value & v, const Pos & pos) { forceValue(v, pos); if (v.type() != nInt) - throwTypeError(pos, "value is %1% while an integer was expected", v); + throwTypeError(pos, "value is %1% while an integer was expected", v, + fakeEnv(1), 0); return v.integer; } @@ -1840,7 +2065,8 @@ NixFloat EvalState::forceFloat(Value & v, const Pos & pos) if (v.type() == nInt) return v.integer; else if (v.type() != nFloat) - throwTypeError(pos, "value is %1% while a float was expected", v); + throwTypeError(pos, "value is %1% while a float was expected", v, + fakeEnv(1), 0); return v.fpoint; } @@ -1849,7 +2075,8 @@ bool EvalState::forceBool(Value & v, const Pos & pos) { forceValue(v, pos); if (v.type() != nBool) - throwTypeError(pos, "value is %1% while a Boolean was expected", v); + throwTypeError(pos, "value is %1% while a Boolean was expected", v, + fakeEnv(1), 0); return v.boolean; } @@ -1864,7 +2091,8 @@ void EvalState::forceFunction(Value & v, const Pos & pos) { forceValue(v, pos); if (v.type() != nFunction && !isFunctor(v)) - throwTypeError(pos, "value is %1% while a function was expected", v); + throwTypeError(pos, "value is %1% while a function was expected", v, + fakeEnv(1), 0); } @@ -1872,10 +2100,8 @@ std::string_view EvalState::forceString(Value & v, const Pos & pos) { forceValue(v, pos); if (v.type() != nString) { - if (pos) - throwTypeError(pos, "value is %1% while a string was expected", v); - else - throwTypeError("value is %1% while a string was expected", v); + throwTypeError(pos, "value is %1% while a string was expected", v, + fakeEnv(1), 0); } return v.string.s; } @@ -1926,10 +2152,10 @@ std::string_view EvalState::forceStringNoCtx(Value & v, const Pos & pos) if (v.string.context) { if (pos) throwEvalError(pos, "the string '%1%' is not allowed to refer to a store path (such as '%2%')", - v.string.s, v.string.context[0]); + v.string.s, v.string.context[0], fakeEnv(1), 0); else throwEvalError("the string '%1%' is not allowed to refer to a store path (such as '%2%')", - v.string.s, v.string.context[0]); + v.string.s, v.string.context[0], fakeEnv(1), 0); } return s; } @@ -1983,7 +2209,9 @@ BackedStringView EvalState::coerceToString(const Pos & pos, Value & v, PathSet & if (maybeString) return std::move(*maybeString); auto i = v.attrs->find(sOutPath); - if (i == v.attrs->end()) throwTypeError(pos, "cannot coerce a set to a string"); + if (i == v.attrs->end()) + throwTypeError(pos, "cannot coerce a set to a string", + fakeEnv(1), 0); return coerceToString(pos, *i->value, context, coerceMore, copyToStore); } @@ -1991,7 +2219,6 @@ BackedStringView EvalState::coerceToString(const Pos & pos, Value & v, PathSet & return v.external->coerceToString(pos, context, coerceMore, copyToStore); if (coerceMore) { - /* Note that `false' is represented as an empty string for shell scripting convenience, just like `null'. */ if (v.type() == nBool && v.boolean) return "1"; @@ -2013,14 +2240,17 @@ BackedStringView EvalState::coerceToString(const Pos & pos, Value & v, PathSet & } } - throwTypeError(pos, "cannot coerce %1% to a string", v); + throwTypeError(pos, "cannot coerce %1% to a string", v, + fakeEnv(1), 0); } string EvalState::copyPathToStore(PathSet & context, const Path & path) { if (nix::isDerivation(path)) - throwEvalError("file names are not allowed to end in '%1%'", drvExtension); + throwEvalError("file names are not allowed to end in '%1%'", + drvExtension, + fakeEnv(1), 0); Path dstPath; auto i = srcToStore.find(path); @@ -2045,7 +2275,8 @@ Path EvalState::coerceToPath(const Pos & pos, Value & v, PathSet & context) { string path = coerceToString(pos, v, context, false, false).toOwned(); if (path == "" || path[0] != '/') - throwEvalError(pos, "string '%1%' doesn't represent an absolute path", path); + throwEvalError(pos, "string '%1%' doesn't represent an absolute path", path, + fakeEnv(1), 0); return path; } @@ -2124,7 +2355,10 @@ bool EvalState::eqValues(Value & v1, Value & v2) return v1.fpoint == v2.fpoint; default: - throwEvalError("cannot compare %1% with %2%", showType(v1), showType(v2)); + throwEvalError("cannot compare %1% with %2%", + showType(v1), + showType(v2), + fakeEnv(1), 0); } } diff --git a/src/libexpr/eval.hh b/src/libexpr/eval.hh index ce5a16e11..8c4430bc8 100644 --- a/src/libexpr/eval.hh +++ b/src/libexpr/eval.hh @@ -25,6 +25,9 @@ enum RepairFlag : bool; typedef void (* PrimOpFun) (EvalState & state, const Pos & pos, Value * * args, Value & v); +extern std::function<void(const Error * error, const Env & env, const Expr & expr)> debuggerHook; +void printStaticEnvBindings(const Expr &expr); +void printStaticEnvBindings(const StaticEnv &se, int lvl = 0); struct PrimOp { @@ -35,6 +38,7 @@ struct PrimOp const char * doc = nullptr; }; +typedef std::map<std::string, Value *> valmap; struct Env { @@ -44,6 +48,7 @@ struct Env Value * values[0]; }; +valmap * mapStaticEnvBindings(const StaticEnv &se, const Env &env); void copyContext(const Value & v, PathSet & context); @@ -68,6 +73,12 @@ struct RegexCache; std::shared_ptr<RegexCache> makeRegexCache(); +struct DebugTrace { + std::optional<ErrPos> pos; + const Expr &expr; + const Env &env; + hintformat hint; +}; class EvalState { @@ -104,6 +115,9 @@ public: RootValue vCallFlake = nullptr; RootValue vImportedDrvToDerivation = nullptr; + bool debugStop; + std::list<DebugTrace> debugTraces; + private: SrcToStore srcToStore; @@ -178,10 +192,10 @@ public: /* Parse a Nix expression from the specified file. */ Expr * parseExprFromFile(const Path & path); - Expr * parseExprFromFile(const Path & path, StaticEnv & staticEnv); + Expr * parseExprFromFile(const Path & path, std::shared_ptr<StaticEnv> & staticEnv); /* Parse a Nix expression from the specified string. */ - Expr * parseExprFromString(std::string s, const Path & basePath, StaticEnv & staticEnv); + Expr * parseExprFromString(std::string s, const Path & basePath, , std::shared_ptr<StaticEnv> & staticEnv); Expr * parseExprFromString(std::string s, const Path & basePath); Expr * parseStdin(); @@ -191,7 +205,7 @@ public: trivial (i.e. doesn't require arbitrary computation). */ void evalFile(const Path & path, Value & v, bool mustBeTrivial = false); - /* Like `cacheFile`, but with an already parsed expression. */ + /* Like `evalFile`, but with an already parsed expression. */ void cacheFile( const Path & path, const Path & resolvedPath, @@ -276,7 +290,7 @@ public: Env & baseEnv; /* The same, but used during parsing to resolve variables. */ - StaticEnv staticBaseEnv; // !!! should be private + std::shared_ptr<StaticEnv> staticBaseEnv; // !!! should be private private: @@ -317,7 +331,7 @@ private: friend struct ExprLet; Expr * parse(char * text, size_t length, FileOrigin origin, const PathView path, - const PathView basePath, StaticEnv & staticEnv); + const Path & basePath, std::shared_ptr<StaticEnv> & staticEnv); public: @@ -412,6 +426,17 @@ private: friend struct Value; }; +class DebugTraceStacker { + public: + DebugTraceStacker(EvalState &evalState, DebugTrace t); + ~DebugTraceStacker() + { + // assert(evalState.debugTraces.front() == trace); + evalState.debugTraces.pop_front(); + } + EvalState &evalState; + DebugTrace trace; +}; /* Return a string representing the type of the value `v'. */ string showType(ValueType type); diff --git a/src/libexpr/nixexpr.cc b/src/libexpr/nixexpr.cc index 2d2cd96cd..41ee92d27 100644 --- a/src/libexpr/nixexpr.cc +++ b/src/libexpr/nixexpr.cc @@ -4,7 +4,6 @@ #include <cstdlib> - namespace nix { @@ -247,35 +246,46 @@ Pos noPos; /* Computing levels/displacements for variables. */ -void Expr::bindVars(const StaticEnv & env) +void Expr::bindVars(const std::shared_ptr<const StaticEnv> &env) { abort(); } -void ExprInt::bindVars(const StaticEnv & env) +void ExprInt::bindVars(const std::shared_ptr<const StaticEnv> &env) { + if (debuggerHook) + staticenv = env; } -void ExprFloat::bindVars(const StaticEnv & env) +void ExprFloat::bindVars(const std::shared_ptr<const StaticEnv> &env) { + if (debuggerHook) + staticenv = env; } -void ExprString::bindVars(const StaticEnv & env) +void ExprString::bindVars(const std::shared_ptr<const StaticEnv> &env) { + if (debuggerHook) + staticenv = env; } -void ExprPath::bindVars(const StaticEnv & env) +void ExprPath::bindVars(const std::shared_ptr<const StaticEnv> &env) { + if (debuggerHook) + staticenv = env; } -void ExprVar::bindVars(const StaticEnv & env) +void ExprVar::bindVars(const std::shared_ptr<const StaticEnv> &env) { + if (debuggerHook) + staticenv = env; + /* Check whether the variable appears in the environment. If so, set its level and displacement. */ const StaticEnv * curEnv; Level level; int withLevel = -1; - for (curEnv = &env, level = 0; curEnv; curEnv = curEnv->up, level++) { + for (curEnv = env.get(), level = 0; curEnv; curEnv = curEnv->up, level++) { if (curEnv->isWith) { if (withLevel == -1) withLevel = level; } else { @@ -292,17 +302,22 @@ void ExprVar::bindVars(const StaticEnv & env) /* Otherwise, the variable must be obtained from the nearest enclosing `with'. If there is no `with', then we can issue an "undefined variable" error now. */ - if (withLevel == -1) + if (withLevel == -1) + { throw UndefinedVarError({ - .msg = hintfmt("undefined variable '%1%'", name), + .msg = hintfmt("undefined variable (ExprVar bindvars) '%1%'", name), .errPos = pos }); + } fromWith = true; this->level = withLevel; } -void ExprSelect::bindVars(const StaticEnv & env) +void ExprSelect::bindVars(const std::shared_ptr<const StaticEnv> &env) { + if (debuggerHook) + staticenv = env; + e->bindVars(env); if (def) def->bindVars(env); for (auto & i : attrPath) @@ -310,64 +325,79 @@ void ExprSelect::bindVars(const StaticEnv & env) i.expr->bindVars(env); } -void ExprOpHasAttr::bindVars(const StaticEnv & env) +void ExprOpHasAttr::bindVars(const std::shared_ptr<const StaticEnv> &env) { + if (debuggerHook) + staticenv = env; + e->bindVars(env); for (auto & i : attrPath) if (!i.symbol.set()) i.expr->bindVars(env); } -void ExprAttrs::bindVars(const StaticEnv & env) +void ExprAttrs::bindVars(const std::shared_ptr<const StaticEnv> &env) { - const StaticEnv * dynamicEnv = &env; - StaticEnv newEnv(false, &env, recursive ? attrs.size() : 0); + if (debuggerHook) + staticenv = env; if (recursive) { - dynamicEnv = &newEnv; + auto newEnv = std::shared_ptr<StaticEnv>(new StaticEnv(false, env.get(), recursive ? attrs.size() : 0)); Displacement displ = 0; for (auto & i : attrs) - newEnv.vars.emplace_back(i.first, i.second.displ = displ++); + newEnv->vars.emplace_back(i.first, i.second.displ = displ++); // No need to sort newEnv since attrs is in sorted order. for (auto & i : attrs) i.second.e->bindVars(i.second.inherited ? env : newEnv); - } - else + for (auto & i : dynamicAttrs) { + i.nameExpr->bindVars(newEnv); + i.valueExpr->bindVars(newEnv); + } + } + else { for (auto & i : attrs) i.second.e->bindVars(env); - for (auto & i : dynamicAttrs) { - i.nameExpr->bindVars(*dynamicEnv); - i.valueExpr->bindVars(*dynamicEnv); + for (auto & i : dynamicAttrs) { + i.nameExpr->bindVars(env); + i.valueExpr->bindVars(env); + } } } -void ExprList::bindVars(const StaticEnv & env) +void ExprList::bindVars(const std::shared_ptr<const StaticEnv> &env) { + if (debuggerHook) + staticenv = env; + for (auto & i : elems) i->bindVars(env); } -void ExprLambda::bindVars(const StaticEnv & env) +void ExprLambda::bindVars(const std::shared_ptr<const StaticEnv> &env) { - StaticEnv newEnv( - false, &env, - (hasFormals() ? formals->formals.size() : 0) + - (arg.empty() ? 0 : 1)); + if (debuggerHook) + staticenv = env; + + auto newEnv = std::shared_ptr<StaticEnv>( + new StaticEnv( + false, env.get(), + (hasFormals() ? formals->formals.size() : 0) + + (arg.empty() ? 0 : 1))); Displacement displ = 0; - if (!arg.empty()) newEnv.vars.emplace_back(arg, displ++); + if (!arg.empty()) newEnv->vars.emplace_back(arg, displ++); if (hasFormals()) { for (auto & i : formals->formals) - newEnv.vars.emplace_back(i.name, displ++); + newEnv->vars.emplace_back(i.name, displ++); - newEnv.sort(); + newEnv->sort(); for (auto & i : formals->formals) if (i.def) i.def->bindVars(newEnv); @@ -376,20 +406,26 @@ void ExprLambda::bindVars(const StaticEnv & env) body->bindVars(newEnv); } -void ExprCall::bindVars(const StaticEnv & env) +void ExprCall::bindVars(const std::shared_ptr<const StaticEnv> &env) { + if (debuggerHook) + staticenv = env; + fun->bindVars(env); for (auto e : args) e->bindVars(env); } -void ExprLet::bindVars(const StaticEnv & env) +void ExprLet::bindVars(const std::shared_ptr<const StaticEnv> &env) { - StaticEnv newEnv(false, &env, attrs->attrs.size()); + if (debuggerHook) + staticenv = env; + + auto newEnv = std::shared_ptr<StaticEnv>(new StaticEnv(false, env.get(), attrs->attrs.size())); Displacement displ = 0; for (auto & i : attrs->attrs) - newEnv.vars.emplace_back(i.first, i.second.displ = displ++); + newEnv->vars.emplace_back(i.first, i.second.displ = displ++); // No need to sort newEnv since attrs->attrs is in sorted order. @@ -399,51 +435,69 @@ void ExprLet::bindVars(const StaticEnv & env) body->bindVars(newEnv); } -void ExprWith::bindVars(const StaticEnv & env) +void ExprWith::bindVars(const std::shared_ptr<const StaticEnv> &env) { + if (debuggerHook) + staticenv = env; + /* Does this `with' have an enclosing `with'? If so, record its level so that `lookupVar' can look up variables in the previous `with' if this one doesn't contain the desired attribute. */ const StaticEnv * curEnv; Level level; prevWith = 0; - for (curEnv = &env, level = 1; curEnv; curEnv = curEnv->up, level++) + for (curEnv = env.get(), level = 1; curEnv; curEnv = curEnv->up, level++) if (curEnv->isWith) { prevWith = level; break; } attrs->bindVars(env); - StaticEnv newEnv(true, &env); + auto newEnv = std::shared_ptr<StaticEnv>(new StaticEnv(true, env.get())); body->bindVars(newEnv); } -void ExprIf::bindVars(const StaticEnv & env) +void ExprIf::bindVars(const std::shared_ptr<const StaticEnv> &env) { + if (debuggerHook) + staticenv = env; + cond->bindVars(env); then->bindVars(env); else_->bindVars(env); } -void ExprAssert::bindVars(const StaticEnv & env) +void ExprAssert::bindVars(const std::shared_ptr<const StaticEnv> &env) { + if (debuggerHook) + staticenv = env; + cond->bindVars(env); body->bindVars(env); } -void ExprOpNot::bindVars(const StaticEnv & env) +void ExprOpNot::bindVars(const std::shared_ptr<const StaticEnv> &env) { + if (debuggerHook) + staticenv = env; + e->bindVars(env); } -void ExprConcatStrings::bindVars(const StaticEnv & env) +void ExprConcatStrings::bindVars(const std::shared_ptr<const StaticEnv> &env) { + if (debuggerHook) + staticenv = env; + for (auto & i : *es) i.second->bindVars(env); } -void ExprPos::bindVars(const StaticEnv & env) +void ExprPos::bindVars(const std::shared_ptr<const StaticEnv> &env) { + if (debuggerHook) + staticenv = env; + } diff --git a/src/libexpr/nixexpr.hh b/src/libexpr/nixexpr.hh index 4e923ac89..64375b5ab 100644 --- a/src/libexpr/nixexpr.hh +++ b/src/libexpr/nixexpr.hh @@ -18,6 +18,7 @@ MakeError(UndefinedVarError, Error); MakeError(MissingArgumentError, EvalError); MakeError(RestrictedPathError, Error); +extern std::function<void(const Error * error, const Env & env, const Expr & expr)> debuggerHook; /* Position objects. */ @@ -77,10 +78,13 @@ struct Expr { virtual ~Expr() { }; virtual void show(std::ostream & str) const; - virtual void bindVars(const StaticEnv & env); + virtual void bindVars(const std::shared_ptr<const StaticEnv> & env); virtual void eval(EvalState & state, Env & env, Value & v); virtual Value * maybeThunk(EvalState & state, Env & env); virtual void setName(Symbol & name); + + std::shared_ptr<const StaticEnv> staticenv; + virtual Pos* getPos() = 0; }; std::ostream & operator << (std::ostream & str, const Expr & e); @@ -88,15 +92,16 @@ std::ostream & operator << (std::ostream & str, const Expr & e); #define COMMON_METHODS \ void show(std::ostream & str) const; \ void eval(EvalState & state, Env & env, Value & v); \ - void bindVars(const StaticEnv & env); + void bindVars(const std::shared_ptr<const StaticEnv> & env); struct ExprInt : Expr { NixInt n; Value v; ExprInt(NixInt n) : n(n) { v.mkInt(n); }; - COMMON_METHODS Value * maybeThunk(EvalState & state, Env & env); + Pos* getPos() { return 0; } + COMMON_METHODS }; struct ExprFloat : Expr @@ -104,8 +109,9 @@ struct ExprFloat : Expr NixFloat nf; Value v; ExprFloat(NixFloat nf) : nf(nf) { v.mkFloat(nf); }; - COMMON_METHODS Value * maybeThunk(EvalState & state, Env & env); + Pos* getPos() { return 0; } + COMMON_METHODS }; struct ExprString : Expr @@ -113,8 +119,9 @@ struct ExprString : Expr string s; Value v; ExprString(std::string s) : s(std::move(s)) { v.mkString(this->s.data()); }; - COMMON_METHODS Value * maybeThunk(EvalState & state, Env & env); + Pos* getPos() { return 0; } + COMMON_METHODS }; struct ExprPath : Expr @@ -122,8 +129,9 @@ struct ExprPath : Expr string s; Value v; ExprPath(const string & s) : s(s) { v.mkPath(this->s.c_str()); }; - COMMON_METHODS Value * maybeThunk(EvalState & state, Env & env); + Pos* getPos() { return 0; } + COMMON_METHODS }; typedef uint32_t Level; @@ -149,8 +157,9 @@ struct ExprVar : Expr ExprVar(const Symbol & name) : name(name) { }; ExprVar(const Pos & pos, const Symbol & name) : pos(pos), name(name) { }; - COMMON_METHODS Value * maybeThunk(EvalState & state, Env & env); + Pos* getPos() { return &pos; } + COMMON_METHODS }; struct ExprSelect : Expr @@ -160,6 +169,7 @@ struct ExprSelect : Expr AttrPath attrPath; ExprSelect(const Pos & pos, Expr * e, const AttrPath & attrPath, Expr * def) : pos(pos), e(e), def(def), attrPath(attrPath) { }; ExprSelect(const Pos & pos, Expr * e, const Symbol & name) : pos(pos), e(e), def(0) { attrPath.push_back(AttrName(name)); }; + Pos* getPos() { return &pos; } COMMON_METHODS }; @@ -168,6 +178,7 @@ struct ExprOpHasAttr : Expr Expr * e; AttrPath attrPath; ExprOpHasAttr(Expr * e, const AttrPath & attrPath) : e(e), attrPath(attrPath) { }; + Pos* getPos() { return e->getPos(); } COMMON_METHODS }; @@ -196,6 +207,7 @@ struct ExprAttrs : Expr DynamicAttrDefs dynamicAttrs; ExprAttrs(const Pos &pos) : recursive(false), pos(pos) { }; ExprAttrs() : recursive(false), pos(noPos) { }; + Pos* getPos() { return &pos; } COMMON_METHODS }; @@ -203,6 +215,7 @@ struct ExprList : Expr { std::vector<Expr *> elems; ExprList() { }; + Pos* getPos() { return 0; } COMMON_METHODS }; @@ -251,6 +264,7 @@ struct ExprLambda : Expr void setName(Symbol & name); string showNamePos() const; inline bool hasFormals() const { return formals != nullptr; } + Pos* getPos() { return &pos; } COMMON_METHODS }; @@ -262,6 +276,7 @@ struct ExprCall : Expr ExprCall(const Pos & pos, Expr * fun, std::vector<Expr *> && args) : fun(fun), args(args), pos(pos) { } + Pos* getPos() { return &pos; } COMMON_METHODS }; @@ -270,6 +285,7 @@ struct ExprLet : Expr ExprAttrs * attrs; Expr * body; ExprLet(ExprAttrs * attrs, Expr * body) : attrs(attrs), body(body) { }; + Pos* getPos() { return 0; } COMMON_METHODS }; @@ -279,6 +295,7 @@ struct ExprWith : Expr Expr * attrs, * body; size_t prevWith; ExprWith(const Pos & pos, Expr * attrs, Expr * body) : pos(pos), attrs(attrs), body(body) { }; + Pos* getPos() { return &pos; } COMMON_METHODS }; @@ -287,6 +304,7 @@ struct ExprIf : Expr Pos pos; Expr * cond, * then, * else_; ExprIf(const Pos & pos, Expr * cond, Expr * then, Expr * else_) : pos(pos), cond(cond), then(then), else_(else_) { }; + Pos* getPos() { return &pos; } COMMON_METHODS }; @@ -295,6 +313,7 @@ struct ExprAssert : Expr Pos pos; Expr * cond, * body; ExprAssert(const Pos & pos, Expr * cond, Expr * body) : pos(pos), cond(cond), body(body) { }; + Pos* getPos() { return &pos; } COMMON_METHODS }; @@ -302,6 +321,7 @@ struct ExprOpNot : Expr { Expr * e; ExprOpNot(Expr * e) : e(e) { }; + Pos* getPos() { return 0; } COMMON_METHODS }; @@ -316,11 +336,12 @@ struct ExprOpNot : Expr { \ str << "(" << *e1 << " " s " " << *e2 << ")"; \ } \ - void bindVars(const StaticEnv & env) \ + void bindVars(const std::shared_ptr<const StaticEnv> & env) \ { \ e1->bindVars(env); e2->bindVars(env); \ } \ void eval(EvalState & state, Env & env, Value & v); \ + Pos* getPos() { return &pos; } \ }; MakeBinOp(ExprOpEq, "==") @@ -338,6 +359,7 @@ struct ExprConcatStrings : Expr vector<std::pair<Pos, Expr *> > * es; ExprConcatStrings(const Pos & pos, bool forceString, vector<std::pair<Pos, Expr *> > * es) : pos(pos), forceString(forceString), es(es) { }; + Pos* getPos() { return &pos; } COMMON_METHODS }; @@ -345,6 +367,7 @@ struct ExprPos : Expr { Pos pos; ExprPos(const Pos & pos) : pos(pos) { }; + Pos* getPos() { return &pos; } COMMON_METHODS }; diff --git a/src/libexpr/parser.y b/src/libexpr/parser.y index f0c80ebd5..d9291e7a2 100644 --- a/src/libexpr/parser.y +++ b/src/libexpr/parser.y @@ -23,6 +23,7 @@ #include "nixexpr.hh" #include "eval.hh" #include "globals.hh" +#include <iostream> namespace nix { @@ -643,7 +644,7 @@ namespace nix { Expr * EvalState::parse(char * text, size_t length, FileOrigin origin, - const PathView path, const PathView basePath, StaticEnv & staticEnv) + const PathView path, const PathView basePath, std::shared_ptr<StaticEnv> & staticEnv) { yyscan_t scanner; ParseData data(*this); @@ -706,7 +707,7 @@ Expr * EvalState::parseExprFromFile(const Path & path) } -Expr * EvalState::parseExprFromFile(const Path & path, StaticEnv & staticEnv) +Expr * EvalState::parseExprFromFile(const Path & path, std::shared_ptr<StaticEnv> & staticEnv) { auto buffer = readFile(path); // readFile should have left some extra space for terminators @@ -715,7 +716,7 @@ Expr * EvalState::parseExprFromFile(const Path & path, StaticEnv & staticEnv) } -Expr * EvalState::parseExprFromString(std::string s, const Path & basePath, StaticEnv & staticEnv) +Expr * EvalState::parseExprFromString(std::string s, const Path & basePath, std::shared_ptr<StaticEnv> & staticEnv) { s.append("\0\0", 2); return parse(s.data(), s.size(), foString, "", basePath, staticEnv); diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc index defb861e6..3b429f328 100644 --- a/src/libexpr/primops.cc +++ b/src/libexpr/primops.cc @@ -215,11 +215,11 @@ static void import(EvalState & state, const Pos & pos, Value & vPath, Value * vS Env * env = &state.allocEnv(vScope->attrs->size()); env->up = &state.baseEnv; - StaticEnv staticEnv(false, &state.staticBaseEnv, vScope->attrs->size()); + auto staticEnv = std::shared_ptr<StaticEnv>(new StaticEnv(false, state.staticBaseEnv.get(), vScope->attrs->size())); unsigned int displ = 0; for (auto & attr : *vScope->attrs) { - staticEnv.vars.emplace_back(attr.name, displ); + staticEnv->vars.emplace_back(attr.name, displ); env->values[displ++] = attr.value; } @@ -698,6 +698,32 @@ static RegisterPrimOp primop_genericClosure(RegisterPrimOp::Info { .fun = prim_genericClosure, }); +static RegisterPrimOp primop_break({ + .name = "break", + .args = {"v"}, + .doc = R"( + In debug mode, pause Nix expression evaluation and enter the repl. + )", + .fun = [](EvalState & state, const Pos & pos, Value * * args, Value & v) + { + std::cout << "primop_break, value: " << *args[0] << std::endl; + // PathSet context; + // string s = state.coerceToString(pos, *args[0], context); + if (debuggerHook && !state.debugTraces.empty()) + { + auto &dt = state.debugTraces.front(); + // std::optional<ErrPos> pos; + // const Expr &expr; + // const Env &env; + // hintformat hint; + debuggerHook(nullptr, dt.env, dt.expr); + + // returning the value we were passed. + v = *args[0]; + } + } +}); + static RegisterPrimOp primop_abort({ .name = "abort", .args = {"s"}, @@ -3896,7 +3922,7 @@ void EvalState::createBaseEnv() because attribute lookups expect it to be sorted. */ baseEnv.values[0]->attrs->sort(); - staticBaseEnv.sort(); + staticBaseEnv->sort(); /* Note: we have to initialize the 'derivation' constant *after* building baseEnv/staticBaseEnv because it uses 'builtins'. */ diff --git a/src/libutil/error.hh b/src/libutil/error.hh index 6fe5e4857..0c86f090e 100644 --- a/src/libutil/error.hh +++ b/src/libutil/error.hh @@ -100,6 +100,15 @@ struct ErrPos { } }; +std::optional<LinesOfCode> getCodeLines(const ErrPos & errPos); +void printCodeLines(std::ostream & out, + const string & prefix, + const ErrPos & errPos, + const LinesOfCode & loc); + +void printAtPos(const ErrPos & pos, std::ostream & out); + + struct Trace { std::optional<ErrPos> pos; hintformat hint; |