diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/libcmd/command.cc | 31 | ||||
-rw-r--r-- | src/libcmd/command.hh | 6 | ||||
-rw-r--r-- | src/libcmd/local.mk | 5 | ||||
-rw-r--r-- | src/libcmd/repl.cc (renamed from src/nix/repl.cc) | 64 | ||||
-rw-r--r-- | src/libexpr/eval.cc | 254 | ||||
-rw-r--r-- | src/libexpr/eval.hh | 12 | ||||
-rw-r--r-- | src/libexpr/nixexpr.cc | 126 | ||||
-rw-r--r-- | src/libexpr/nixexpr.hh | 9 | ||||
-rw-r--r-- | src/libexpr/parser.y | 6 | ||||
-rw-r--r-- | src/libexpr/primops.cc | 2 | ||||
-rw-r--r-- | src/libutil/error.hh | 6 |
11 files changed, 400 insertions, 121 deletions
diff --git a/src/libcmd/command.cc b/src/libcmd/command.cc index fd3edfc46..4c5d985aa 100644 --- a/src/libcmd/command.cc +++ b/src/libcmd/command.cc @@ -56,6 +56,37 @@ void StoreCommand::run() 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; + +ref<EvalState> EvalCommand::getEvalState() +{ + if (!evalState) { + evalState = std::make_shared<EvalState>(searchPath, getStore()); + if (startReplOnEvalErrors) + debuggerHook = [evalState{ref<EvalState>(evalState)}](const Error & error, const Env & env, const Expr & expr) { + printError("%s\n\n" ANSI_BOLD "Starting REPL to allow you to inspect the current state of the evaluator.\n" ANSI_NORMAL, error.what()); + + printStaticEnvBindings(expr); + + std::cout << "expr: " << std::endl; + expr.show(std::cout); + std::cout << std::endl; + + if (expr.staticenv) + { + auto vm = mapStaticEnvBindings(*expr.staticenv.get(), env); + runRepl(evalState, *vm); + } + }; + } + return ref<EvalState>(evalState); } EvalCommand::~EvalCommand() diff --git a/src/libcmd/command.hh b/src/libcmd/command.hh index 07f398468..0d847d255 100644 --- a/src/libcmd/command.hh +++ b/src/libcmd/command.hh @@ -45,6 +45,8 @@ private: struct EvalCommand : virtual StoreCommand, MixEvalArgs { + bool startReplOnEvalErrors = false; + EvalCommand(); ~EvalCommand(); @@ -310,4 +312,8 @@ void printClosureDiff( const StorePath & afterPath, std::string_view indent); +void runRepl( + ref<EvalState> evalState, + const std::map<std::string, Value *> & extraEnv); + } diff --git a/src/libcmd/local.mk b/src/libcmd/local.mk index 8b0662753..1ec258a54 100644 --- a/src/libcmd/local.mk +++ b/src/libcmd/local.mk @@ -6,10 +6,11 @@ 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 = $(EDITLINE_LIBS) -llowdown libcmd_LDFLAGS += -llowdown -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 fd86174f2..6faa9f9fa 100644 --- a/src/nix/repl.cc +++ b/src/libcmd/repl.cc @@ -47,20 +47,20 @@ struct NixRepl #endif { string curDir; - std::unique_ptr<EvalState> state; + ref<EvalState> state; Bindings * autoArgs; 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); @@ -72,13 +72,13 @@ struct NixRepl void initEnv(); void reloadFiles(); void addAttrsToScope(Value & attrs); - void addVarToScope(const Symbol & name, Value & v); + void addVarToScope(const Symbol & name, Value * v); Expr * parseString(string s); 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); }; @@ -91,9 +91,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("."); @@ -202,8 +202,9 @@ 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(); if (!loadedFiles.empty()) notice(""); @@ -560,8 +561,8 @@ bool NixRepl::processLine(string line) isVarName(name = removeWhitespace(string(line, 0, p)))) { Expr * e = parseString(string(line, p + 1)); - Value & v(*state->allocValue()); - v.mkThunk(env, e); + Value *v = new Value(*state->allocValue()); + v->mkThunk(env, e); addVarToScope(state->symbols.create(name), v); } else { Value v; @@ -609,10 +610,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); } @@ -643,12 +644,12 @@ void NixRepl::addAttrsToScope(Value & attrs) } -void NixRepl::addVarToScope(const Symbol & name, Value & v) +void NixRepl::addVarToScope(const Symbol & name, Value * v) { if (displ >= envSize) throw Error("environment full; cannot add more variables"); - staticEnv.vars.emplace_back(name, displ); - staticEnv.sort(); + staticEnv->vars.emplace_back(name, displ); + staticEnv->sort(); env->values[displ++] = &v; varNames.insert((string) name); } @@ -813,6 +814,28 @@ std::ostream & NixRepl::printValue(std::ostream & str, Value & v, unsigned int m return str; } +void runRepl( + ref<EvalState> evalState, + const std::map<std::string, Value *> & extraEnv) +{ + auto repl = std::make_unique<NixRepl>(evalState); + + repl->initEnv(); + + 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()); + // printError("The following extra variables are in scope: %s\n", concatStringsSep(", ", names)); + + repl->mainLoop({}); +} + struct CmdRepl : StoreCommand, MixEvalArgs { std::vector<std::string> files; @@ -841,7 +864,10 @@ 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->mainLoop(files); } diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc index 97fc04711..a20123f34 100644 --- a/src/libexpr/eval.cc +++ b/src/libexpr/eval.cc @@ -35,6 +35,7 @@ namespace nix { +std::function<void(const Error & error, const Env & env, const Expr & expr)> debuggerHook; static char * dupString(const char * s) { @@ -417,7 +418,7 @@ EvalState::EvalState( , buildStore(buildStore ? buildStore : store) , regexCache(makeRegexCache()) , baseEnv(allocEnv(128)) - , staticBaseEnv(false, 0) + , staticBaseEnv(new StaticEnv(false, 0)) { countCalls = getEnv("NIX_COUNT_CALLS").value_or("0") != "0"; @@ -615,7 +616,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; @@ -641,7 +642,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; @@ -671,84 +672,210 @@ std::optional<EvalState::Doc> EvalState::getDoc(Value & v) } + +void printStaticEnvBindings(const StaticEnv &se, int lvl) +{ + for (auto i = se.vars.begin(); i != se.vars.end(); ++i) + { + std::cout << lvl << i->first << std::endl; + } + + if (se.up) { + printStaticEnvBindings(*se.up, ++lvl); + } + +} + +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. + 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({ + auto error = TypeError({ + .msg = hintfmt(s, v), + .errPos = pos + }); + + if (debuggerHook && expr) + debuggerHook(error, env, *expr); + + throw error; +} + +LocalNoInlineNoReturn(void throwTypeError(const Pos & pos, const char * s, const string &s2, Env & env, Expr *expr)) +{ + 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 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)) @@ -761,13 +888,11 @@ LocalNoInline(void addErrorTrace(Error & e, const Pos & pos, const char * s, con e.addTrace(pos, s, s2); } - void mkString(Value & v, const char * s) { v.mkString(dupString(s)); } - Value & mkString(Value & v, std::string_view s, const PathSet & context) { v.mkString(dupStringWithLen(s.data(), s.size())); @@ -808,8 +933,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, 0); + } for (size_t l = env->prevWith; l; --l, env = env->up) ; } } @@ -825,6 +951,7 @@ Value * EvalState::allocValue() Env & EvalState::allocEnv(size_t size) { + nrEnvs++; nrValuesInEnvs += size; Env * env = (Env *) allocBytes(sizeof(Env) + size * sizeof(Value *)); @@ -835,6 +962,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) { @@ -1118,7 +1254,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 */ @@ -1206,7 +1343,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; @@ -1293,7 +1430,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); @@ -1308,7 +1444,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++; @@ -1487,8 +1623,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); } } } @@ -1522,7 +1659,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); } @@ -1672,15 +1809,16 @@ void ExprConcatStrings::eval(EvalState & state, Env & env, Value & v) firstType = nFloat; nf = n; nf += vTmp.fpoint; - } else - throwEvalError(pos, "cannot add %1% to an integer", showType(vTmp)); + } else { + throwEvalError(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(pos, "cannot add %1% to a float", showType(vTmp)); + throwEvalError(pos, "cannot add %1% to a float", showType(vTmp), env, this); } else /* skip canonization of first path, which would only be not canonized in the first place if it's coming from a ./${foo} type @@ -1745,7 +1883,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; } @@ -1756,7 +1895,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; } @@ -1765,7 +1905,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; } @@ -1780,7 +1921,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); } @@ -1788,10 +1930,8 @@ string 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 string(v.string.s); } @@ -1842,10 +1982,10 @@ string 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; } @@ -1898,7 +2038,9 @@ string EvalState::coerceToString(const Pos & pos, Value & v, PathSet & context, return *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); } @@ -1906,7 +2048,6 @@ string EvalState::coerceToString(const Pos & pos, Value & v, PathSet & context, 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"; @@ -1929,14 +2070,17 @@ string EvalState::coerceToString(const Pos & pos, Value & v, PathSet & context, } } - 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); @@ -1961,7 +2105,8 @@ Path EvalState::coerceToPath(const Pos & pos, Value & v, PathSet & context) { string path = coerceToString(pos, v, context, false, false); 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; } @@ -2040,7 +2185,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 1aab8e166..485c2df83 100644 --- a/src/libexpr/eval.hh +++ b/src/libexpr/eval.hh @@ -24,6 +24,8 @@ 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); struct PrimOp { @@ -34,6 +36,7 @@ struct PrimOp const char * doc = nullptr; }; +typedef std::map<std::string, Value *> valmap; struct Env { @@ -43,6 +46,7 @@ struct Env Value * values[0]; }; +valmap * mapStaticEnvBindings(const StaticEnv &se, const Env &env); Value & mkString(Value & v, std::string_view s, const PathSet & context = PathSet()); @@ -175,10 +179,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_view s, const Path & basePath, StaticEnv & staticEnv); + Expr * parseExprFromString(std::string_view s, const Path & basePath, std::shared_ptr<StaticEnv> & staticEnv); Expr * parseExprFromString(std::string_view s, const Path & basePath); Expr * parseStdin(); @@ -267,7 +271,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: @@ -308,7 +312,7 @@ private: friend struct ExprLet; Expr * parse(const char * text, FileOrigin origin, const Path & path, - const Path & basePath, StaticEnv & staticEnv); + const Path & basePath, std::shared_ptr<StaticEnv> & staticEnv); public: diff --git a/src/libexpr/nixexpr.cc b/src/libexpr/nixexpr.cc index 57c2f6e44..696b149e3 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,21 +325,24 @@ 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) @@ -334,30 +352,42 @@ void ExprAttrs::bindVars(const StaticEnv & env) 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; @@ -378,6 +408,9 @@ void ExprLambda::bindVars(const StaticEnv & env) void ExprCall::bindVars(const StaticEnv & env) { + if (debuggerHook) + staticenv = env; + fun->bindVars(env); for (auto e : args) e->bindVars(env); @@ -385,7 +418,10 @@ void ExprCall::bindVars(const StaticEnv & env) void ExprLet::bindVars(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) @@ -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->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 13256272c..825933fa1 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,12 @@ 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; }; std::ostream & operator << (std::ostream & str, const Expr & e); @@ -88,7 +91,7 @@ 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 { @@ -313,7 +316,7 @@ 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); \ } \ diff --git a/src/libexpr/parser.y b/src/libexpr/parser.y index c1f4e72e0..58af0df7d 100644 --- a/src/libexpr/parser.y +++ b/src/libexpr/parser.y @@ -590,7 +590,7 @@ namespace nix { Expr * EvalState::parse(const char * text, FileOrigin origin, - const Path & path, const Path & basePath, StaticEnv & staticEnv) + const Path & path, const Path & basePath, std::shared_ptr<StaticEnv> & staticEnv) { yyscan_t scanner; ParseData data(*this); @@ -653,13 +653,13 @@ 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) { return parse(readFile(path).c_str(), foFile, path, dirOf(path), staticEnv); } -Expr * EvalState::parseExprFromString(std::string_view s, const Path & basePath, StaticEnv & staticEnv) +Expr * EvalState::parseExprFromString(std::string_view s, const Path & basePath, std::shared_ptr<StaticEnv> & staticEnv) { return parse(s.data(), foString, "", basePath, staticEnv); } diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc index 8cbeaa520..a9ee96bfa 100644 --- a/src/libexpr/primops.cc +++ b/src/libexpr/primops.cc @@ -184,7 +184,7 @@ 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) { diff --git a/src/libutil/error.hh b/src/libutil/error.hh index ff58d3e00..b6670c8b2 100644 --- a/src/libutil/error.hh +++ b/src/libutil/error.hh @@ -100,6 +100,12 @@ struct ErrPos { } }; +std::optional<LinesOfCode> getCodeLines(const ErrPos & errPos); +void printCodeLines(std::ostream & out, + const string & prefix, + const ErrPos & errPos, + const LinesOfCode & loc); + struct Trace { std::optional<ErrPos> pos; hintformat hint; |