diff options
Diffstat (limited to 'src/libexpr')
-rw-r--r-- | src/libexpr/eval.cc | 311 | ||||
-rw-r--r-- | src/libexpr/eval.hh | 35 | ||||
-rw-r--r-- | src/libexpr/nixexpr.cc | 140 | ||||
-rw-r--r-- | src/libexpr/nixexpr.hh | 40 | ||||
-rw-r--r-- | src/libexpr/parser.y | 7 | ||||
-rw-r--r-- | src/libexpr/primops.cc | 6 |
6 files changed, 424 insertions, 115 deletions
diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc index a95726f5f..851058b3e 100644 --- a/src/libexpr/eval.cc +++ b/src/libexpr/eval.cc @@ -17,6 +17,7 @@ #include <sys/resource.h> #include <iostream> #include <fstream> +#include <functional> #include <sys/resource.h> @@ -35,6 +36,7 @@ namespace nix { +std::function<void(const Error & error, const Env & env, const Expr & expr)> debuggerHook; static char * dupString(const char * s) { @@ -417,7 +419,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"; @@ -594,7 +596,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)); @@ -619,7 +621,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; @@ -645,7 +647,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; @@ -674,85 +676,213 @@ std::optional<EvalState::Doc> EvalState::getDoc(Value & v) return {}; } +void printStaticEnvBindings(const StaticEnv &se, int lvl) +{ + std::cout << "Env level " << lvl << std::endl; + + for (auto i = se.vars.begin(); i != se.vars.end(); ++i) + { + std::cout << i->first << " "; + } + std::cout << std::endl; + std::cout << 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. + // 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({ + 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)) @@ -765,13 +895,25 @@ 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, std::optional<ErrPos> pos, const char * s, const string & s2)) +{ + return std::unique_ptr<DebugTraceStacker>( + new DebugTraceStacker( + state, + DebugTrace + {.pos = pos, + .expr = expr, + .hint = hintfmt(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())); @@ -812,8 +954,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) ; } } @@ -844,6 +987,7 @@ Value * EvalState::allocValue() Env & EvalState::allocEnv(size_t size) { + nrEnvs++; nrValuesInEnvs += size; Env * env = (Env *) allocBytes(sizeof(Env) + size * sizeof(Value *)); @@ -854,6 +998,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) { @@ -986,6 +1139,15 @@ void EvalState::cacheFile( fileParseCache[resolvedPath] = e; try { + std::unique_ptr<DebugTraceStacker> dts = + debuggerHook ? + makeDebugTraceStacker( + *this, + *e, + (e->getPos() ? std::optional(ErrPos(*e->getPos())) : std::nullopt), + "while evaluating the file '%1%':", resolvedPath) + : std::unique_ptr<DebugTraceStacker>(); + // Enforce that 'flake.nix' is a direct attrset, not a // computation. if (mustBeTrivial && @@ -1137,7 +1299,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 */ @@ -1209,6 +1372,15 @@ void ExprSelect::eval(EvalState & state, Env & env, Value & v) e->eval(state, env, vTmp); try { + std::unique_ptr<DebugTraceStacker> dts = + debuggerHook ? + makeDebugTraceStacker( + state, + *this, + *pos2, + "while evaluating the attribute '%1%'", + showAttrPath(state, env, attrPath)) + : std::unique_ptr<DebugTraceStacker>(); for (auto & i : attrPath) { state.nrLookups++; @@ -1225,7 +1397,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; @@ -1312,7 +1484,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); @@ -1327,7 +1498,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++; @@ -1342,7 +1513,8 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value & user. */ for (auto & i : *args[0]->attrs) if (lambda.formals->argNames.find(i.name) == lambda.formals->argNames.end()) - 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 } } @@ -1352,6 +1524,15 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value & /* Evaluate the body. */ try { + std::unique_ptr<DebugTraceStacker> dts = + debuggerHook ? + makeDebugTraceStacker(*this, *lambda.body, lambda.pos, + "while evaluating %s", + (lambda.name.set() + ? "'" + (string) lambda.name + "'" + : "anonymous lambda")) + : std::unique_ptr<DebugTraceStacker>(); + lambda.body->eval(*this, env2, vCur); } catch (Error & e) { if (loggerSettings.showTrace.get()) { @@ -1506,8 +1687,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); } } } @@ -1541,7 +1723,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); } @@ -1691,15 +1873,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 /* skip canonization of first path, which would only be not canonized in the first place if it's coming from a ./${foo} type @@ -1743,6 +1926,16 @@ void EvalState::forceValueDeep(Value & v) if (v.type() == nAttrs) { for (auto & i : *v.attrs) try { + + std::unique_ptr<DebugTraceStacker> dts = + debuggerHook ? + // if the value is a thunk, we're evaling. otherwise no trace necessary. + (i.value->isThunk() ? + makeDebugTraceStacker(*this, *v.thunk.expr, *i.pos, + "while evaluating the attribute '%1%'", i.name) + : std::unique_ptr<DebugTraceStacker>()) + : std::unique_ptr<DebugTraceStacker>(); + recurse(*i.value); } catch (Error & e) { addErrorTrace(e, *i.pos, "while evaluating the attribute '%1%'", i.name); @@ -1764,7 +1957,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; } @@ -1775,7 +1969,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; } @@ -1784,7 +1979,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; } @@ -1799,7 +1995,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); } @@ -1807,10 +2004,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); } @@ -1861,10 +2056,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; } @@ -1917,7 +2112,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); } @@ -1925,7 +2122,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"; @@ -1948,14 +2144,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); @@ -1980,7 +2179,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; } @@ -2059,7 +2259,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 d7ef7b88a..5dbb9b5e5 100644 --- a/src/libexpr/eval.hh +++ b/src/libexpr/eval.hh @@ -24,6 +24,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 { @@ -34,6 +37,7 @@ struct PrimOp const char * doc = nullptr; }; +typedef std::map<std::string, Value *> valmap; struct Env { @@ -43,6 +47,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()); @@ -69,6 +74,11 @@ struct RegexCache; std::shared_ptr<RegexCache> makeRegexCache(); +struct DebugTrace { + std::optional<ErrPos> pos; + const Expr &expr; + hintformat hint; +}; class EvalState { @@ -104,6 +114,8 @@ public: RootValue vCallFlake = nullptr; RootValue vImportedDrvToDerivation = nullptr; + std::list<DebugTrace> debugTraces; + private: SrcToStore srcToStore; @@ -178,10 +190,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(); @@ -270,7 +282,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: @@ -311,7 +323,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: @@ -399,6 +411,21 @@ private: friend void prim_match(EvalState & state, const Pos & pos, Value * * args, Value & v); }; +class DebugTraceStacker { + public: + DebugTraceStacker(EvalState &evalState, DebugTrace t) + :evalState(evalState), trace(t) + { + evalState.debugTraces.push_front(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 a75357871..f7541d32c 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 b328b3941..c4c459f0b 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) { mkInt(v, 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) { mkFloat(v, 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 Symbol s; Value v; ExprString(const Symbol & s) : s(s) { mkString(v, s); }; - COMMON_METHODS Value * maybeThunk(EvalState & state, Env & env); + Pos* getPos() { return 0; } + COMMON_METHODS }; /* Temporary class used during parsing of indented strings. */ @@ -122,6 +129,7 @@ struct ExprIndStr : Expr { string s; ExprIndStr(const string & s) : s(s) { }; + Pos* getPos() { return 0; } }; struct ExprPath : Expr @@ -129,8 +137,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; @@ -156,8 +165,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 @@ -167,6 +177,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 }; @@ -175,6 +186,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 }; @@ -203,6 +215,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 }; @@ -210,6 +223,7 @@ struct ExprList : Expr { std::vector<Expr *> elems; ExprList() { }; + Pos* getPos() { return 0; } COMMON_METHODS }; @@ -248,6 +262,7 @@ struct ExprLambda : Expr void setName(Symbol & name); string showNamePos() const; inline bool hasFormals() const { return formals != nullptr; } + Pos* getPos() { return &pos; } COMMON_METHODS }; @@ -259,6 +274,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 }; @@ -267,6 +283,7 @@ struct ExprLet : Expr ExprAttrs * attrs; Expr * body; ExprLet(ExprAttrs * attrs, Expr * body) : attrs(attrs), body(body) { }; + Pos* getPos() { return 0; } COMMON_METHODS }; @@ -276,6 +293,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 }; @@ -284,6 +302,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 }; @@ -292,6 +311,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 }; @@ -299,6 +319,7 @@ struct ExprOpNot : Expr { Expr * e; ExprOpNot(Expr * e) : e(e) { }; + Pos* getPos() { return 0; } COMMON_METHODS }; @@ -313,11 +334,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, "==") @@ -335,6 +357,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 }; @@ -342,6 +365,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 f8aaea582..db2d4e204 100644 --- a/src/libexpr/parser.y +++ b/src/libexpr/parser.y @@ -21,6 +21,7 @@ #include "nixexpr.hh" #include "eval.hh" #include "globals.hh" +#include <iostream> namespace nix { @@ -590,7 +591,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 +654,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 7c964bd0d..66265f917 100644 --- a/src/libexpr/primops.cc +++ b/src/libexpr/primops.cc @@ -217,11 +217,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; } @@ -3827,7 +3827,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'. */ |