diff options
Diffstat (limited to 'src/libexpr/eval.cc')
-rw-r--r-- | src/libexpr/eval.cc | 311 |
1 files changed, 257 insertions, 54 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); } } |