aboutsummaryrefslogtreecommitdiff
path: root/src/libexpr/eval.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/libexpr/eval.cc')
-rw-r--r--src/libexpr/eval.cc311
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);
}
}