aboutsummaryrefslogtreecommitdiff
path: root/src/libexpr
diff options
context:
space:
mode:
Diffstat (limited to 'src/libexpr')
-rw-r--r--src/libexpr/eval.cc311
-rw-r--r--src/libexpr/eval.hh35
-rw-r--r--src/libexpr/nixexpr.cc140
-rw-r--r--src/libexpr/nixexpr.hh40
-rw-r--r--src/libexpr/parser.y7
-rw-r--r--src/libexpr/primops.cc6
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'. */