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.cc364
1 files changed, 222 insertions, 142 deletions
diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc
index 3f18ad1ed..8cf4d9549 100644
--- a/src/libexpr/eval.cc
+++ b/src/libexpr/eval.cc
@@ -119,8 +119,8 @@ void printValue(std::ostream & str, std::set<const Value *> & active, const Valu
case tList2:
case tListN:
str << "[ ";
- for (unsigned int n = 0; n < v.listSize(); ++n) {
- printValue(str, active, *v.listElems()[n]);
+ for (auto v2 : v.listItems()) {
+ printValue(str, active, *v2);
str << " ";
}
str << "]";
@@ -445,12 +445,12 @@ EvalState::EvalState(
StorePathSet closure;
store->computeFSClosure(store->toStorePath(r.second).first, closure);
for (auto & path : closure)
- allowedPaths->insert(store->printStorePath(path));
+ allowPath(path);
} catch (InvalidPath &) {
- allowedPaths->insert(r.second);
+ allowPath(r.second);
}
} else
- allowedPaths->insert(r.second);
+ allowPath(r.second);
}
}
@@ -465,6 +465,35 @@ EvalState::~EvalState()
}
+void EvalState::requireExperimentalFeatureOnEvaluation(
+ const ExperimentalFeature & feature,
+ const std::string_view fName,
+ const Pos & pos)
+{
+ if (!settings.isExperimentalFeatureEnabled(feature)) {
+ throw EvalError({
+ .msg = hintfmt(
+ "Cannot call '%2%' because experimental Nix feature '%1%' is disabled. You can enable it via '--extra-experimental-features %1%'.",
+ feature,
+ fName
+ ),
+ .errPos = pos
+ });
+ }
+}
+
+void EvalState::allowPath(const Path & path)
+{
+ if (allowedPaths)
+ allowedPaths->insert(path);
+}
+
+void EvalState::allowPath(const StorePath & storePath)
+{
+ if (allowedPaths)
+ allowedPaths->insert(store->toRealPath(storePath));
+}
+
Path EvalState::checkSourcePath(const Path & path_)
{
if (!allowedPaths) return path_;
@@ -490,8 +519,12 @@ Path EvalState::checkSourcePath(const Path & path_)
}
}
- if (!found)
- throw RestrictedPathError("access to path '%1%' is forbidden in restricted mode", abspath);
+ if (!found) {
+ auto modeInformation = evalSettings.pureEval
+ ? "in pure eval mode (use '--impure' to override)"
+ : "in restricted mode";
+ throw RestrictedPathError("access to absolute path '%1%' is forbidden %2%", abspath, modeInformation);
+ }
/* Resolve symlinks. */
debug(format("checking access to '%s'") % abspath);
@@ -504,7 +537,7 @@ Path EvalState::checkSourcePath(const Path & path_)
}
}
- throw RestrictedPathError("access to path '%1%' is forbidden in restricted mode", path);
+ throw RestrictedPathError("access to canonical path '%1%' is forbidden in restricted mode", path);
}
@@ -554,14 +587,20 @@ Value * EvalState::addConstant(const string & name, Value & v)
{
Value * v2 = allocValue();
*v2 = v;
- staticBaseEnv.vars[symbols.create(name)] = baseEnvDispl;
- baseEnv.values[baseEnvDispl++] = v2;
- string name2 = string(name, 0, 2) == "__" ? string(name, 2) : name;
- baseEnv.values[0]->attrs->push_back(Attr(symbols.create(name2), v2));
+ addConstant(name, v2);
return v2;
}
+void EvalState::addConstant(const string & name, Value * v)
+{
+ 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));
+}
+
+
Value * EvalState::addPrimOp(const string & name,
size_t arity, PrimOpFun primOp)
{
@@ -580,7 +619,7 @@ Value * EvalState::addPrimOp(const string & name,
Value * v = allocValue();
v->mkPrimOp(new PrimOp { .fun = primOp, .arity = arity, .name = sym });
- staticBaseEnv.vars[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;
@@ -606,7 +645,7 @@ Value * EvalState::addPrimOp(PrimOp && primOp)
Value * v = allocValue();
v->mkPrimOp(new PrimOp(std::move(primOp)));
- staticBaseEnv.vars[envName] = baseEnvDispl;
+ staticBaseEnv.vars.emplace_back(envName, baseEnvDispl);
baseEnv.values[baseEnvDispl++] = v;
baseEnv.values[0]->attrs->push_back(Attr(primOp.name, v));
return v;
@@ -756,7 +795,7 @@ void mkPath(Value & v, const char * s)
inline Value * EvalState::lookupVar(Env * env, const ExprVar & var, bool noEval)
{
- for (size_t l = var.level; l; --l, env = env->up) ;
+ for (auto l = var.level; l; --l, env = env->up) ;
if (!var.fromWith) return env->values[var.displ];
@@ -1029,7 +1068,7 @@ void ExprAttrs::eval(EvalState & state, Env & env, Value & v)
/* The recursive attributes are evaluated in the new
environment, while the inherited attributes are evaluated
in the original environment. */
- size_t displ = 0;
+ Displacement displ = 0;
for (auto & i : attrs) {
Value * vAttr;
if (hasOverrides && !i.second.inherited) {
@@ -1105,7 +1144,7 @@ void ExprLet::eval(EvalState & state, Env & env, Value & v)
/* The recursive attributes are evaluated in the new environment,
while the inherited attributes are evaluated in the original
environment. */
- size_t displ = 0;
+ Displacement displ = 0;
for (auto & i : attrs->attrs)
env2.values[displ++] = i.second.e->maybeThunk(state, i.second.inherited ? env : env2);
@@ -1116,8 +1155,8 @@ void ExprLet::eval(EvalState & state, Env & env, Value & v)
void ExprList::eval(EvalState & state, Env & env, Value & v)
{
state.mkList(v, elems.size());
- for (size_t n = 0; n < elems.size(); ++n)
- v.listElems()[n] = elems[n]->maybeThunk(state, env);
+ for (auto [n, v2] : enumerate(v.listItems()))
+ const_cast<Value * &>(v2) = elems[n]->maybeThunk(state, env);
}
@@ -1222,144 +1261,184 @@ void ExprLambda::eval(EvalState & state, Env & env, Value & v)
}
-void ExprApp::eval(EvalState & state, Env & env, Value & v)
+void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value & vRes, const Pos & pos)
{
- /* FIXME: vFun prevents GCC from doing tail call optimisation. */
- Value vFun;
- e1->eval(state, env, vFun);
- state.callFunction(vFun, *(e2->maybeThunk(state, env)), v, pos);
-}
+ auto trace = evalSettings.traceFunctionCalls ? std::make_unique<FunctionCallTrace>(pos) : nullptr;
+ forceValue(fun, pos);
-void EvalState::callPrimOp(Value & fun, Value & arg, Value & v, const Pos & pos)
-{
- /* Figure out the number of arguments still needed. */
- size_t argsDone = 0;
- Value * primOp = &fun;
- while (primOp->isPrimOpApp()) {
- argsDone++;
- primOp = primOp->primOpApp.left;
- }
- assert(primOp->isPrimOp());
- auto arity = primOp->primOp->arity;
- auto argsLeft = arity - argsDone;
-
- if (argsLeft == 1) {
- /* We have all the arguments, so call the primop. */
-
- /* Put all the arguments in an array. */
- Value * vArgs[arity];
- auto n = arity - 1;
- vArgs[n--] = &arg;
- for (Value * arg = &fun; arg->isPrimOpApp(); arg = arg->primOpApp.left)
- vArgs[n--] = arg->primOpApp.right;
-
- /* And call the primop. */
- nrPrimOpCalls++;
- if (countCalls) primOpCalls[primOp->primOp->name]++;
- primOp->primOp->fun(*this, pos, vArgs, v);
- } else {
- Value * fun2 = allocValue();
- *fun2 = fun;
- v.mkPrimOpApp(fun2, &arg);
- }
-}
+ Value vCur(fun);
-void EvalState::callFunction(Value & fun, Value & arg, Value & v, const Pos & pos)
-{
- auto trace = evalSettings.traceFunctionCalls ? std::make_unique<FunctionCallTrace>(pos) : nullptr;
+ auto makeAppChain = [&]()
+ {
+ vRes = vCur;
+ for (size_t i = 0; i < nrArgs; ++i) {
+ auto fun2 = allocValue();
+ *fun2 = vRes;
+ vRes.mkPrimOpApp(fun2, args[i]);
+ }
+ };
- forceValue(fun, pos);
+ Attr * functor;
- if (fun.isPrimOp() || fun.isPrimOpApp()) {
- callPrimOp(fun, arg, v, pos);
- return;
- }
+ while (nrArgs > 0) {
- if (fun.type() == nAttrs) {
- auto found = fun.attrs->find(sFunctor);
- if (found != fun.attrs->end()) {
- /* fun may be allocated on the stack of the calling function,
- * but for functors we may keep a reference, so heap-allocate
- * a copy and use that instead.
- */
- auto & fun2 = *allocValue();
- fun2 = fun;
- /* !!! Should we use the attr pos here? */
- Value v2;
- callFunction(*found->value, fun2, v2, pos);
- return callFunction(v2, arg, v, pos);
- }
- }
+ if (vCur.isLambda()) {
- if (!fun.isLambda())
- throwTypeError(pos, "attempt to call something which is not a function but %1%", fun);
+ ExprLambda & lambda(*vCur.lambda.fun);
- ExprLambda & lambda(*fun.lambda.fun);
+ auto size =
+ (lambda.arg.empty() ? 0 : 1) +
+ (lambda.hasFormals() ? lambda.formals->formals.size() : 0);
+ Env & env2(allocEnv(size));
+ env2.up = vCur.lambda.env;
- auto size =
- (lambda.arg.empty() ? 0 : 1) +
- (lambda.matchAttrs ? lambda.formals->formals.size() : 0);
- Env & env2(allocEnv(size));
- env2.up = fun.lambda.env;
+ Displacement displ = 0;
- size_t displ = 0;
+ if (!lambda.hasFormals())
+ env2.values[displ++] = args[0];
- if (!lambda.matchAttrs)
- env2.values[displ++] = &arg;
+ else {
+ forceAttrs(*args[0], pos);
- else {
- forceAttrs(arg, pos);
-
- if (!lambda.arg.empty())
- env2.values[displ++] = &arg;
-
- /* For each formal argument, get the actual argument. If
- there is no matching actual argument but the formal
- argument has a default, use the default. */
- size_t attrsUsed = 0;
- for (auto & i : lambda.formals->formals) {
- Bindings::iterator j = arg.attrs->find(i.name);
- if (j == arg.attrs->end()) {
- if (!i.def) throwTypeError(pos, "%1% called without required argument '%2%'",
- lambda, i.name);
- env2.values[displ++] = i.def->maybeThunk(*this, env2);
+ if (!lambda.arg.empty())
+ env2.values[displ++] = args[0];
+
+ /* For each formal argument, get the actual argument. If
+ there is no matching actual argument but the formal
+ argument has a default, use the default. */
+ size_t attrsUsed = 0;
+ for (auto & i : lambda.formals->formals) {
+ auto j = args[0]->attrs->get(i.name);
+ if (!j) {
+ if (!i.def) throwTypeError(pos, "%1% called without required argument '%2%'",
+ lambda, i.name);
+ env2.values[displ++] = i.def->maybeThunk(*this, env2);
+ } else {
+ attrsUsed++;
+ env2.values[displ++] = j->value;
+ }
+ }
+
+ /* Check that each actual argument is listed as a formal
+ argument (unless the attribute match specifies a `...'). */
+ if (!lambda.formals->ellipsis && attrsUsed != args[0]->attrs->size()) {
+ /* Nope, so show the first unexpected argument to the
+ 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);
+ abort(); // can't happen
+ }
+ }
+
+ nrFunctionCalls++;
+ if (countCalls) incrFunctionCall(&lambda);
+
+ /* Evaluate the body. */
+ try {
+ lambda.body->eval(*this, env2, vCur);
+ } catch (Error & e) {
+ if (loggerSettings.showTrace.get()) {
+ addErrorTrace(e, lambda.pos, "while evaluating %s",
+ (lambda.name.set()
+ ? "'" + (string) lambda.name + "'"
+ : "anonymous lambda"));
+ addErrorTrace(e, pos, "from call site%s", "");
+ }
+ throw;
+ }
+
+ nrArgs--;
+ args += 1;
+ }
+
+ else if (vCur.isPrimOp()) {
+
+ size_t argsLeft = vCur.primOp->arity;
+
+ if (nrArgs < argsLeft) {
+ /* We don't have enough arguments, so create a tPrimOpApp chain. */
+ makeAppChain();
+ return;
+ } else {
+ /* We have all the arguments, so call the primop. */
+ nrPrimOpCalls++;
+ if (countCalls) primOpCalls[vCur.primOp->name]++;
+ vCur.primOp->fun(*this, pos, args, vCur);
+
+ nrArgs -= argsLeft;
+ args += argsLeft;
+ }
+ }
+
+ else if (vCur.isPrimOpApp()) {
+ /* Figure out the number of arguments still needed. */
+ size_t argsDone = 0;
+ Value * primOp = &vCur;
+ while (primOp->isPrimOpApp()) {
+ argsDone++;
+ primOp = primOp->primOpApp.left;
+ }
+ assert(primOp->isPrimOp());
+ auto arity = primOp->primOp->arity;
+ auto argsLeft = arity - argsDone;
+
+ if (nrArgs < argsLeft) {
+ /* We still don't have enough arguments, so extend the tPrimOpApp chain. */
+ makeAppChain();
+ return;
} else {
- attrsUsed++;
- env2.values[displ++] = j->value;
+ /* We have all the arguments, so call the primop with
+ the previous and new arguments. */
+
+ Value * vArgs[arity];
+ auto n = argsDone;
+ for (Value * arg = &vCur; arg->isPrimOpApp(); arg = arg->primOpApp.left)
+ vArgs[--n] = arg->primOpApp.right;
+
+ for (size_t i = 0; i < argsLeft; ++i)
+ vArgs[argsDone + i] = args[i];
+
+ nrPrimOpCalls++;
+ if (countCalls) primOpCalls[primOp->primOp->name]++;
+ primOp->primOp->fun(*this, pos, vArgs, vCur);
+
+ nrArgs -= argsLeft;
+ args += argsLeft;
}
}
- /* Check that each actual argument is listed as a formal
- argument (unless the attribute match specifies a `...'). */
- if (!lambda.formals->ellipsis && attrsUsed != arg.attrs->size()) {
- /* Nope, so show the first unexpected argument to the
- user. */
- for (auto & i : *arg.attrs)
- if (lambda.formals->argNames.find(i.name) == lambda.formals->argNames.end())
- throwTypeError(pos, "%1% called with unexpected argument '%2%'", lambda, i.name);
- abort(); // can't happen
+ else if (vCur.type() == nAttrs && (functor = vCur.attrs->get(sFunctor))) {
+ /* 'vCur' may be allocated on the stack of the calling
+ function, but for functors we may keep a reference, so
+ heap-allocate a copy and use that instead. */
+ Value * args2[] = {allocValue(), args[0]};
+ *args2[0] = vCur;
+ /* !!! Should we use the attr pos here? */
+ callFunction(*functor->value, 2, args2, vCur, pos);
+ nrArgs--;
+ args++;
}
+
+ else
+ throwTypeError(pos, "attempt to call something which is not a function but %1%", vCur);
}
- nrFunctionCalls++;
- if (countCalls) incrFunctionCall(&lambda);
+ vRes = vCur;
+}
- /* Evaluate the body. This is conditional on showTrace, because
- catching exceptions makes this function not tail-recursive. */
- if (loggerSettings.showTrace.get())
- try {
- lambda.body->eval(*this, env2, v);
- } catch (Error & e) {
- addErrorTrace(e, lambda.pos, "while evaluating %s",
- (lambda.name.set()
- ? "'" + (string) lambda.name + "'"
- : "anonymous lambda"));
- addErrorTrace(e, pos, "from call site%s", "");
- throw;
- }
- else
- fun.lambda.fun->body->eval(*this, env2, v);
+
+void ExprCall::eval(EvalState & state, Env & env, Value & v)
+{
+ Value vFun;
+ fun->eval(state, env, vFun);
+
+ Value * vArgs[args.size()];
+ for (size_t i = 0; i < args.size(); ++i)
+ vArgs[i] = args[i]->maybeThunk(state, env);
+
+ state.callFunction(vFun, args.size(), vArgs, v, pos);
}
@@ -1385,7 +1464,7 @@ void EvalState::autoCallFunction(Bindings & args, Value & fun, Value & res)
}
}
- if (!fun.isLambda() || !fun.lambda.fun->matchAttrs) {
+ if (!fun.isLambda() || !fun.lambda.fun->hasFormals()) {
res = fun;
return;
}
@@ -1657,8 +1736,8 @@ void EvalState::forceValueDeep(Value & v)
}
else if (v.isList()) {
- for (size_t n = 0; n < v.listSize(); ++n)
- recurse(*v.listElems()[n]);
+ for (auto v2 : v.listItems())
+ recurse(*v2);
}
};
@@ -1842,12 +1921,12 @@ string EvalState::coerceToString(const Pos & pos, Value & v, PathSet & context,
if (v.isList()) {
string result;
- for (size_t n = 0; n < v.listSize(); ++n) {
- result += coerceToString(pos, *v.listElems()[n],
+ for (auto [n, v2] : enumerate(v.listItems())) {
+ result += coerceToString(pos, *v2,
context, coerceMore, copyToStore);
if (n < v.listSize() - 1
/* !!! not quite correct */
- && (!v.listElems()[n]->isList() || v.listElems()[n]->listSize() != 0))
+ && (!v2->isList() || v2->listSize() != 0))
result += " ";
}
return result;
@@ -1872,6 +1951,7 @@ string EvalState::copyPathToStore(PathSet & context, const Path & path)
? store->computeStorePathForPath(std::string(baseNameOf(path)), checkSourcePath(path)).first
: store->addToStore(std::string(baseNameOf(path)), checkSourcePath(path), FileIngestionMethod::Recursive, htSHA256, defaultPathFilter, repair);
dstPath = store->printStorePath(p);
+ allowPath(p);
srcToStore.insert_or_assign(path, std::move(p));
printMsg(lvlChatty, "copied source '%1%' -> '%2%'", path, dstPath);
}