diff options
Diffstat (limited to 'third_party/nix/src/libexpr/eval.cc')
-rw-r--r-- | third_party/nix/src/libexpr/eval.cc | 1878 |
1 files changed, 0 insertions, 1878 deletions
diff --git a/third_party/nix/src/libexpr/eval.cc b/third_party/nix/src/libexpr/eval.cc deleted file mode 100644 index 682ea6483213..000000000000 --- a/third_party/nix/src/libexpr/eval.cc +++ /dev/null @@ -1,1878 +0,0 @@ -#include "libexpr/eval.hh" - -#include <algorithm> -#include <chrono> -#include <cstdint> -#include <cstring> -#include <fstream> -#include <iostream> -#include <memory> -#include <new> -#include <optional> -#include <variant> - -#include <absl/base/call_once.h> -#include <absl/container/flat_hash_set.h> -#include <absl/strings/match.h> -#include <glog/logging.h> -#include <sys/resource.h> -#include <sys/time.h> -#include <unistd.h> - -#include "libexpr/eval-inline.hh" -#include "libexpr/function-trace.hh" -#include "libexpr/value.hh" -#include "libstore/derivations.hh" -#include "libstore/download.hh" -#include "libstore/globals.hh" -#include "libstore/store-api.hh" -#include "libutil/hash.hh" -#include "libutil/json.hh" -#include "libutil/util.hh" -#include "libutil/visitor.hh" - -namespace nix { -namespace { - -void ConfigureGc() { /* This function intentionally left blank. */ -} - -} // namespace - -namespace expr { - -absl::once_flag gc_flag; - -void InitGC() { absl::call_once(gc_flag, &ConfigureGc); } - -} // namespace expr - -static char* dupString(const char* s) { - char* t; - t = strdup(s); - if (t == nullptr) { - throw std::bad_alloc(); - } - return t; -} - -std::shared_ptr<Value*> allocRootValue(Value* v) { - return std::make_shared<Value*>(v); -} - -static void printValue(std::ostream& str, std::set<const Value*>& active, - const Value& v) { - checkInterrupt(); - - if (active.find(&v) != active.end()) { - str << "<CYCLE>"; - return; - } - active.insert(&v); - - switch (v.type) { - case tInt: - str << v.integer; - break; - case tBool: - str << (v.boolean ? "true" : "false"); - break; - case tString: - str << "\""; - for (const char* i = v.string.s; *i != 0; i++) { - if (*i == '\"' || *i == '\\') { - str << "\\" << *i; - } else if (*i == '\n') { - str << "\\n"; - } else if (*i == '\r') { - str << "\\r"; - } else if (*i == '\t') { - str << "\\t"; - } else { - str << *i; - } - } - str << "\""; - break; - case tPath: - str << v.path; // !!! escaping? - break; - case tNull: - str << "null"; - break; - case tAttrs: { - str << "{ "; - for (const auto& [key, value] : *v.attrs) { - str << key << " = "; - printValue(str, active, *value.value); - str << "; "; - } - str << "}"; - break; - } - case tList: - str << "[ "; - for (unsigned int n = 0; n < v.listSize(); ++n) { - printValue(str, active, *(*v.list)[n]); - str << " "; - } - str << "]"; - break; - case tThunk: - case tApp: - str << "<CODE>"; - break; - case tLambda: - str << "<LAMBDA>"; - break; - case tPrimOp: - str << "<PRIMOP>"; - break; - case tPrimOpApp: - str << "<PRIMOP-APP>"; - break; - case tFloat: - str << v.fpoint; - break; - default: - throw Error( - absl::StrCat("invalid value of type ", static_cast<int>(v.type))); - } - - active.erase(&v); -} - -std::ostream& operator<<(std::ostream& str, const Value& v) { - std::set<const Value*> active; - printValue(str, active, v); - return str; -} - -const Value* getPrimOp(const Value& v) { - const Value* primOp = &v; - while (primOp->type == tPrimOpApp) { - primOp = primOp->primOpApp.left; - } - assert(primOp->type == tPrimOp); - return primOp; -} - -std::string showType(const Value& v) { - switch (v.type) { - case tInt: - return "an integer"; - case tBool: - return "a boolean"; - case tString: - return v.string.context != nullptr ? "a string with context" : "a string"; - case tPath: - return "a path"; - case tNull: - return "null"; - case tAttrs: - return "a set"; - case tList: - return "a list"; - case tThunk: - return "a thunk"; - case tApp: - return "a function application"; - case tLambda: - return "a function"; - case tBlackhole: - return "a black hole"; - case tPrimOp: - return fmt("the built-in function '%s'", std::string(v.primOp->name)); - case tPrimOpApp: - return fmt("the partially applied built-in function '%s'", - std::string(getPrimOp(v)->primOp->name)); - case _reserved1: - LOG(FATAL) << "attempted to show the type string of the deprecated " - "tExternal value"; - break; - case tFloat: - return "a float"; - } - LOG(FATAL) - << "attempted to determine the type string of an unknown type number (" - << static_cast<int>(v.type) << ")"; - abort(); -} - -static Symbol getName(const AttrName& name, EvalState& state, Env& env) { - return std::visit( - util::overloaded{[&](const Symbol& name) -> Symbol { return name; }, - [&](Expr* expr) -> Symbol { - Value nameValue; - expr->eval(state, env, nameValue); - state.forceStringNoCtx(nameValue); - return state.symbols.Create(nameValue.string.s); - }}, - name); -} - -/* Very hacky way to parse $NIX_PATH, which is colon-separated, but - can contain URLs (e.g. "nixpkgs=https://bla...:foo=https://"). */ -static Strings parseNixPath(const std::string& s) { - Strings res; - - auto p = s.begin(); - - while (p != s.end()) { - auto start = p; - auto start2 = p; - - while (p != s.end() && *p != ':') { - if (*p == '=') { - start2 = p + 1; - } - ++p; - } - - if (p == s.end()) { - if (p != start) { - res.push_back(std::string(start, p)); - } - break; - } - - if (*p == ':') { - if (isUri(std::string(start2, s.end()))) { - ++p; - while (p != s.end() && *p != ':') { - ++p; - } - } - res.push_back(std::string(start, p)); - if (p == s.end()) { - break; - } - } - - ++p; - } - - return res; -} - -EvalState::EvalState(const Strings& _searchPath, const ref<Store>& store) - : sWith(symbols.Create("<with>")), - sOutPath(symbols.Create("outPath")), - sDrvPath(symbols.Create("drvPath")), - sType(symbols.Create("type")), - sMeta(symbols.Create("meta")), - sName(symbols.Create("name")), - sValue(symbols.Create("value")), - sSystem(symbols.Create("system")), - sOutputs(symbols.Create("outputs")), - sOutputName(symbols.Create("outputName")), - sIgnoreNulls(symbols.Create("__ignoreNulls")), - sFile(symbols.Create("file")), - sLine(symbols.Create("line")), - sColumn(symbols.Create("column")), - sFunctor(symbols.Create("__functor")), - sToString(symbols.Create("__toString")), - sRight(symbols.Create("right")), - sWrong(symbols.Create("wrong")), - sStructuredAttrs(symbols.Create("__structuredAttrs")), - sBuilder(symbols.Create("builder")), - sArgs(symbols.Create("args")), - sOutputHash(symbols.Create("outputHash")), - sOutputHashAlgo(symbols.Create("outputHashAlgo")), - sOutputHashMode(symbols.Create("outputHashMode")), - sDerivationNix(std::nullopt), - repair(NoRepair), - store(store), - baseEnv(allocEnv(128)), - staticBaseEnv(false, nullptr) { - expr::InitGC(); - - countCalls = getEnv("NIX_COUNT_CALLS").value_or("0") != "0"; - - /* Initialise the Nix expression search path. */ - if (!evalSettings.pureEval) { - Strings paths = parseNixPath(getEnv("NIX_PATH").value_or("")); - for (auto& i : _searchPath) { - addToSearchPath(i); - } - for (auto& i : paths) { - addToSearchPath(i); - } - } - addToSearchPath("nix=" + - canonPath(settings.nixDataDir + "/nix/corepkgs", true)); - - if (evalSettings.restrictEval || evalSettings.pureEval) { - allowedPaths = PathSet(); - - for (auto& i : searchPath) { - auto r = resolveSearchPathElem(i); - if (!r.first) { - continue; - } - - auto path = r.second; - - if (store->isInStore(r.second)) { - PathSet closure; - store->computeFSClosure(store->toStorePath(r.second), closure); - for (auto& path : closure) { - allowedPaths->insert(path); - } - } else { - allowedPaths->insert(r.second); - } - } - } - - createBaseEnv(); -} - -EvalState::~EvalState() = default; - -Path EvalState::checkSourcePath(const Path& path_) { - TraceFileAccess(path_); - if (!allowedPaths) { - return path_; - } - - auto i = resolvedPaths.find(path_); - if (i != resolvedPaths.end()) { - return i->second; - } - - bool found = false; - - /* First canonicalize the path without symlinks, so we make sure an - * attacker can't append ../../... to a path that would be in allowedPaths - * and thus leak symlink targets. - */ - Path abspath = canonPath(path_); - - for (auto& i : *allowedPaths) { - if (isDirOrInDir(abspath, i)) { - found = true; - break; - } - } - - if (!found) { - throw RestrictedPathError( - "access to path '%1%' is forbidden in restricted mode", abspath); - } - - /* Resolve symlinks. */ - DLOG(INFO) << "checking access to '" << abspath << "'"; - Path path = canonPath(abspath, true); - - for (auto& i : *allowedPaths) { - if (isDirOrInDir(path, i)) { - resolvedPaths[path_] = path; - return path; - } - } - - throw RestrictedPathError( - "access to path '%1%' is forbidden in restricted mode", path); -} - -void EvalState::checkURI(const std::string& uri) { - if (!evalSettings.restrictEval) { - return; - } - - /* 'uri' should be equal to a prefix, or in a subdirectory of a - prefix. Thus, the prefix https://github.co does not permit - access to https://github.com. Note: this allows 'http://' and - 'https://' as prefixes for any http/https URI. */ - for (auto& prefix : evalSettings.allowedUris.get()) { - if (uri == prefix || - (uri.size() > prefix.size() && !prefix.empty() && - absl::StartsWith(uri, prefix) && - (prefix[prefix.size() - 1] == '/' || uri[prefix.size()] == '/'))) { - return; - } - } - - /* If the URI is a path, then check it against allowedPaths as - well. */ - if (absl::StartsWith(uri, "/")) { - checkSourcePath(uri); - return; - } - - if (absl::StartsWith(uri, "file://")) { - checkSourcePath(std::string(uri, 7)); - return; - } - - throw RestrictedPathError( - "access to URI '%s' is forbidden in restricted mode", uri); -} - -Path EvalState::toRealPath(const Path& path, const PathSet& context) { - // FIXME: check whether 'path' is in 'context'. - return !context.empty() && store->isInStore(path) ? store->toRealPath(path) - : path; -}; - -Value* EvalState::addConstant(const std::string& name, Value& v) { - Value* v2 = allocValue(); - *v2 = v; - staticBaseEnv.vars[symbols.Create(name)] = baseEnvDispl; - baseEnv.values[baseEnvDispl++] = v2; - std::string name2 = - std::string(name, 0, 2) == "__" ? std::string(name, 2) : name; - baseEnv.values[0]->attrs->push_back(Attr(symbols.Create(name2), v2)); - return v2; -} - -Value* EvalState::addPrimOp(const std::string& name, size_t arity, - PrimOpFun primOp) { - if (arity == 0) { - Value v; - primOp(*this, noPos, nullptr, v); - return addConstant(name, v); - } - std::string name2 = - std::string(name, 0, 2) == "__" ? std::string(name, 2) : name; - Symbol sym = symbols.Create(name2); - Value* v = allocValue(); - v->type = tPrimOp; - v->primOp = std::make_shared<PrimOp>(primOp, arity, sym); - staticBaseEnv.vars[symbols.Create(name)] = baseEnvDispl; - baseEnv.values[baseEnvDispl++] = v; - baseEnv.values[0]->attrs->push_back(Attr(sym, v)); - return v; -} - -Value& EvalState::getBuiltin(const std::string& name) { - return *baseEnv.values[0]->attrs->find(symbols.Create(name))->second.value; -} - -/* 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 std::string& s2)) { - throw EvalError(format(s) % s2); -} - -LocalNoInlineNoReturn(void throwEvalError(const char* s, const std::string& s2, - const Pos& pos)) { - throw EvalError(format(s) % s2 % pos); -} - -LocalNoInlineNoReturn(void throwEvalError(const char* s, const std::string& s2, - const std::string& s3)) { - throw EvalError(format(s) % s2 % s3); -} - -LocalNoInlineNoReturn(void throwEvalError(const char* s, const std::string& s2, - const std::string& s3, - const Pos& pos)) { - throw EvalError(format(s) % s2 % s3 % pos); -} - -LocalNoInlineNoReturn(void throwEvalError(const char* s, const Symbol& sym, - const Pos& p1, const Pos& p2)) { - throw EvalError(format(s) % sym % p1 % p2); -} - -LocalNoInlineNoReturn(void throwTypeError(const char* s, const Pos& pos)) { - throw TypeError(format(s) % pos); -} - -LocalNoInlineNoReturn(void throwTypeError(const char* s, - const std::string& s1)) { - throw TypeError(format(s) % s1); -} - -LocalNoInlineNoReturn(void throwTypeError(const char* s, const ExprLambda& fun, - const Symbol& s2, const Pos& pos)) { - throw TypeError(format(s) % fun.showNamePos() % s2 % pos); -} - -LocalNoInlineNoReturn(void throwAssertionError(const char* s, - const std::string& s1, - const Pos& pos)) { - throw AssertionError(format(s) % s1 % pos); -} - -LocalNoInlineNoReturn(void throwUndefinedVarError(const char* s, - const std::string& s1, - const Pos& pos)) { - throw UndefinedVarError(format(s) % s1 % pos); -} - -LocalNoInline(void addErrorPrefix(Error& e, const char* s, - const std::string& s2)) { - e.addPrefix(format(s) % s2); -} - -LocalNoInline(void addErrorPrefix(Error& e, const char* s, - const ExprLambda& fun, const Pos& pos)) { - e.addPrefix(format(s) % fun.showNamePos() % pos); -} - -LocalNoInline(void addErrorPrefix(Error& e, const char* s, - const std::string& s2, const Pos& pos)) { - e.addPrefix(format(s) % s2 % pos); -} - -void mkString(Value& v, const char* s) { mkStringNoCopy(v, dupString(s)); } - -Value& mkString(Value& v, const std::string& s, const PathSet& context) { - mkString(v, s.c_str()); - if (!context.empty()) { - size_t n = 0; - v.string.context = static_cast<const char**>( - allocBytes((context.size() + 1) * sizeof(char*))); - for (auto& i : context) { - v.string.context[n++] = dupString(i.c_str()); - } - v.string.context[n] = nullptr; - } - return v; -} - -void mkPath(Value& v, const char* s) { mkPathNoCopy(v, dupString(s)); } - -inline Value* EvalState::lookupVar(Env* env, const ExprVar& var, bool noEval) { - for (size_t l = var.level; l != 0u; --l, env = env->up) { - ; - } - - if (!var.fromWith) { - return env->values[var.displ]; - } - - while (true) { - if (env->type == Env::HasWithExpr) { - if (noEval) { - return nullptr; - } - if (!env->withAttrsExpr) { - CHECK(false) << "HasWithExpr evaluated twice"; - } - Value* v = allocValue(); - evalAttrs(*env->up, env->withAttrsExpr, *v); - env->values[0] = v; - env->withAttrsExpr = nullptr; - env->type = Env::HasWithAttrs; - } - Bindings::iterator j = env->values[0]->attrs->find(var.name); - if (j != env->values[0]->attrs->end()) { - if (countCalls && (j->second.pos != nullptr)) { - attrSelects[*j->second.pos]++; - } - return j->second.value; - } - if (env->prevWith == 0u) { - throwUndefinedVarError("undefined variable '%1%' at %2%", var.name, - var.pos); - } - for (size_t l = env->prevWith; l != 0u; --l, env = env->up) { - } - } -} - -Value* EvalState::allocValue() { - nrValues++; - return new Value; -} - -Env& EvalState::allocEnv(size_t size) { - if (size > std::numeric_limits<decltype(Env::size)>::max()) { - throw Error("environment size %d is too big", size); - } - - nrEnvs++; - nrValuesInEnvs += size; - Env* env = new Env(size); - env->type = Env::Plain; - - return *env; -} - -void EvalState::mkList(Value& v, std::shared_ptr<NixList> list) { - nrListElems += list->size(); - clearValue(v); - v.type = tList; - v.list = list; -} - -void EvalState::mkList(Value& v, size_t size) { - EvalState::mkList(v, std::make_shared<NixList>(size)); -} - -unsigned long nrThunks = 0; - -static inline void mkThunk(Value& v, Env& env, Expr* expr) { - v.type = tThunk; - v.thunk.env = &env; - v.thunk.expr = expr; - nrThunks++; -} - -void EvalState::mkThunk_(Value& v, Expr* expr) { mkThunk(v, baseEnv, expr); } - -void EvalState::mkPos(Value& v, Pos* pos) { - if ((pos != nullptr) && pos->file.has_value() && pos->file.value().set()) { - mkAttrs(v, 3); - mkString(*allocAttr(v, sFile), pos->file.value()); - mkInt(*allocAttr(v, sLine), pos->line); - mkInt(*allocAttr(v, sColumn), pos->column); - } else { - mkNull(v); - } -} - -/* Create a thunk for the delayed computation of the given expression - in the given environment. But if the expression is a variable, - then look it up right away. This significantly reduces the number - of thunks allocated. */ -Value* Expr::maybeThunk(EvalState& state, Env& env) { - Value* v = state.allocValue(); - mkThunk(*v, env, this); - return v; -} - -unsigned long nrAvoided = 0; - -Value* ExprVar::maybeThunk(EvalState& state, Env& env) { - Value* v = state.lookupVar(&env, *this, true); - /* The value might not be initialised in the environment yet. - In that case, ignore it. */ - if (v != nullptr) { - nrAvoided++; - return v; - } - return Expr::maybeThunk(state, env); -} - -Value* ExprString::maybeThunk(EvalState& state, Env& env) { - nrAvoided++; - return &v; -} - -Value* ExprInt::maybeThunk(EvalState& state, Env& env) { - nrAvoided++; - return &v; -} - -Value* ExprFloat::maybeThunk(EvalState& state, Env& env) { - nrAvoided++; - return &v; -} - -Value* ExprPath::maybeThunk(EvalState& state, Env& env) { - nrAvoided++; - return &v; -} - -void EvalState::evalFile(const Path& path_, Value& v) { - auto path = checkSourcePath(path_); - - FileEvalCache::iterator i; - if ((i = fileEvalCache.find(path)) != fileEvalCache.end()) { - v = i->second; - return; - } - - Path path2 = resolveExprPath(path); - if ((i = fileEvalCache.find(path2)) != fileEvalCache.end()) { - v = i->second; - return; - } - - VLOG(2) << "evaluating file '" << path2 << "'"; - Expr* e = nullptr; - - auto j = fileParseCache.find(path2); - if (j != fileParseCache.end()) { - e = j->second; - } - - if (e == nullptr) { - e = parseExprFromFile(checkSourcePath(path2)); - } - - fileParseCache[path2] = e; - - try { - eval(e, v); - } catch (Error& e) { - addErrorPrefix(e, "while evaluating the file '%1%':\n", path2); - throw; - } - - fileEvalCache[path2] = v; - if (path != path2) { - fileEvalCache[path] = v; - } -} - -void EvalState::resetFileCache() { - fileEvalCache.clear(); - fileParseCache.clear(); -} - -void EvalState::eval(Expr* e, Value& v) { e->eval(*this, baseEnv, v); } - -inline bool EvalState::evalBool(Env& env, Expr* e) { - Value v; - e->eval(*this, env, v); - if (v.type != tBool) { - throwTypeError("value is %1% while a Boolean was expected", v); - } - return v.boolean; -} - -inline bool EvalState::evalBool(Env& env, Expr* e, const Pos& pos) { - Value v; - e->eval(*this, env, v); - if (v.type != tBool) { - throwTypeError("value is %1% while a Boolean was expected, at %2%", v, pos); - } - return v.boolean; -} - -inline void EvalState::evalAttrs(Env& env, Expr* e, Value& v) { - e->eval(*this, env, v); - if (v.type != tAttrs) { - throwTypeError("value is %1% while a set was expected", v); - } -} - -void Expr::eval(EvalState& state, Env& env, Value& v) { abort(); } - -void ExprInt::eval(EvalState& state, Env& env, Value& v) { v = this->v; } - -void ExprFloat::eval(EvalState& state, Env& env, Value& v) { v = this->v; } - -void ExprString::eval(EvalState& state, Env& env, Value& v) { v = this->v; } - -void ExprPath::eval(EvalState& state, Env& env, Value& v) { v = this->v; } - -void ExprAttrs::eval(EvalState& state, Env& env, Value& value) { - state.mkAttrs(value, attrs.size() + dynamicAttrs.size()); - Env* dynamicEnv = &env; - - if (recursive) { - /* Create a new environment that contains the attributes in - this `rec'. */ - Env& env2(state.allocEnv(attrs.size())); - env2.up = &env; - dynamicEnv = &env2; - - /* The recursive attributes are evaluated in the new - environment, while the inherited attributes are evaluated - in the original environment. */ - size_t displ = 0; - for (auto& attr : attrs) { - Value* vAttr; - vAttr = - attr.second.e->maybeThunk(state, attr.second.inherited ? env : env2); - env2.values[displ++] = vAttr; - value.attrs->push_back(Attr(attr.first, vAttr, &attr.second.pos)); - } - } else { - // TODO(tazjin): insert range - for (auto& i : attrs) { - value.attrs->push_back( - Attr(i.first, i.second.e->maybeThunk(state, env), &i.second.pos)); - } - } - - /* Dynamic attrs apply *after* rec. */ - for (auto& i : dynamicAttrs) { - Value nameVal; - i.nameExpr->eval(state, *dynamicEnv, nameVal); - state.forceValue(nameVal, i.pos); - if (nameVal.type == tNull) { - continue; - } - state.forceStringNoCtx(nameVal); - Symbol nameSym = state.symbols.Create(nameVal.string.s); - Bindings::iterator j = value.attrs->find(nameSym); - if (j != value.attrs->end()) { - throwEvalError("dynamic attribute '%1%' at %2% already defined at %3%", - nameSym, i.pos, *j->second.pos); - } - - value.attrs->push_back( - Attr(nameSym, i.valueExpr->maybeThunk(state, *dynamicEnv), &i.pos)); - } -} - -void ExprLet::eval(EvalState& state, Env& env, Value& v) { - /* Create a new environment that contains the attributes in this - `let'. */ - Env& env2(state.allocEnv(attrs->attrs.size())); - env2.up = &env; - - /* The recursive attributes are evaluated in the new environment, - while the inherited attributes are evaluated in the original - environment. */ - size_t displ = 0; - for (auto& i : attrs->attrs) { - env2.values[displ++] = - i.second.e->maybeThunk(state, i.second.inherited ? env : env2); - } - - body->eval(state, env2, 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.list)[n] = elems[n]->maybeThunk(state, env); - } -} - -void ExprVar::eval(EvalState& state, Env& env, Value& v) { - Value* v2 = state.lookupVar(&env, *this, false); - state.forceValue(*v2, pos); - v = *v2; -} - -static std::string showAttrPath(EvalState& state, Env& env, - const AttrPath& attrPath) { - std::ostringstream out; - bool first = true; - for (auto& i : attrPath) { - if (!first) { - out << '.'; - } else { - first = false; - } - out << getName(i, state, env); - } - return out.str(); -} - -uint64_t nrLookups = 0; - -void ExprSelect::eval(EvalState& state, Env& env, Value& v) { - Value vTmp; - Pos* pos2 = nullptr; - Value* vAttrs = &vTmp; - - e->eval(state, env, vTmp); - - try { - for (auto& i : attrPath) { - nrLookups++; - Bindings::iterator j; - Symbol name = getName(i, state, env); - if (def != nullptr) { - state.forceValue(*vAttrs, pos); - if (vAttrs->type != tAttrs || - (j = vAttrs->attrs->find(name)) == vAttrs->attrs->end()) { - def->eval(state, env, v); - return; - } - } else { - state.forceAttrs(*vAttrs, pos); - if ((j = vAttrs->attrs->find(name)) == vAttrs->attrs->end()) { - throwEvalError("attribute '%1%' missing, at %2%", name, pos); - } - } - vAttrs = j->second.value; - pos2 = j->second.pos; - if (state.countCalls && (pos2 != nullptr)) { - state.attrSelects[*pos2]++; - } - } - - state.forceValue(*vAttrs, (pos2 != nullptr ? *pos2 : this->pos)); - - } catch (Error& e) { - // This code relies on 'sDerivationNix' being correcty mutated at - // some prior point (it would previously otherwise have been a - // nullptr). - // - // We haven't seen this fail, so for now the contained value is - // just accessed at the risk of potentially crashing. - if ((pos2 != nullptr) && pos2->file != state.sDerivationNix.value()) { - addErrorPrefix(e, "while evaluating the attribute '%1%' at %2%:\n", - showAttrPath(state, env, attrPath), *pos2); - } - throw; - } - - v = *vAttrs; -} - -void ExprOpHasAttr::eval(EvalState& state, Env& env, Value& v) { - Value vTmp; - Value* vAttrs = &vTmp; - - e->eval(state, env, vTmp); - - for (auto& i : attrPath) { - state.forceValue(*vAttrs); - Bindings::iterator j; - Symbol name = getName(i, state, env); - if (vAttrs->type != tAttrs || - (j = vAttrs->attrs->find(name)) == vAttrs->attrs->end()) { - mkBool(v, false); - return; - } - vAttrs = j->second.value; - } - - mkBool(v, true); -} - -void ExprLambda::eval(EvalState& state, Env& env, Value& v) { - v.type = tLambda; - v.lambda.env = &env; - v.lambda.fun = this; -} - -void ExprApp::eval(EvalState& state, Env& env, Value& v) { - /* 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); -} - -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->type == tPrimOpApp) { - argsDone++; - primOp = primOp->primOpApp.left; - } - assert(primOp->type == tPrimOp); - 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->type == tPrimOpApp; - 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.type = tPrimOpApp; - v.primOpApp.left = fun2; - v.primOpApp.right = &arg; - } -} - -void EvalState::callFunction(Value& fun, Value& arg, Value& v, const Pos& pos) { - auto trace = evalSettings.traceFunctionCalls - ? std::make_unique<FunctionCallTrace>(pos) - : nullptr; - - forceValue(fun, pos); - - if (fun.type == tPrimOp || fun.type == tPrimOpApp) { - callPrimOp(fun, arg, v, pos); - return; - } - - // If the value to be called is an attribute set, check whether it - // contains an appropriate function in the '__functor' element and - // use that. - if (fun.type == tAttrs) { - 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; - // functors are called with the element itself as the first - // parameter, which is partially applied here - callFunction(*found->second.value, fun2, v2, pos); - return callFunction(v2, arg, v, pos); - } - } - - if (fun.type != tLambda) { - throwTypeError( - "attempt to call something which is not a function but %1%, at %2%", - fun, pos); - } - - ExprLambda& lambda(*fun.lambda.fun); - - auto size = (lambda.arg.empty() ? 0 : 1) + - (lambda.matchAttrs ? lambda.formals->formals.size() : 0); - Env& env2(allocEnv(size)); - env2.up = fun.lambda.env; - - size_t displ = 0; - - if (!lambda.matchAttrs) { - env2.values[displ++] = &arg; - - } 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 == nullptr) { - throwTypeError("%1% called without required argument '%2%', at %3%", - lambda, i.name, pos); - } - env2.values[displ++] = i.def->maybeThunk(*this, env2); - } else { - attrsUsed++; - env2.values[displ++] = j->second.value; - } - } - - /* 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.second.name) == - lambda.formals->argNames.end()) { - throwTypeError("%1% called with unexpected argument '%2%', at %3%", - lambda, i.second.name, pos); - } - } - abort(); // shouldn't happen - } - } - - nrFunctionCalls++; - if (countCalls) { - incrFunctionCall(&lambda); - } - - /* Evaluate the body. This is conditional on showTrace, because - catching exceptions makes this function not tail-recursive. */ - if (settings.showTrace) { - try { - lambda.body->eval(*this, env2, v); - } catch (Error& e) { - addErrorPrefix(e, "while evaluating %1%, called from %2%:\n", lambda, - pos); - throw; - } - } else { - fun.lambda.fun->body->eval(*this, env2, v); - } -} - -// Lifted out of callFunction() because it creates a temporary that -// prevents tail-call optimisation. -void EvalState::incrFunctionCall(ExprLambda* fun) { functionCalls[fun]++; } - -void EvalState::autoCallFunction(Bindings* args, Value& fun, Value& res) { - forceValue(fun); - - if (fun.type == tAttrs) { - auto found = fun.attrs->find(sFunctor); - if (found != fun.attrs->end()) { - Value* v = allocValue(); - callFunction(*found->second.value, fun, *v, noPos); - forceValue(*v); - return autoCallFunction(args, *v, res); - } - } - - if (fun.type != tLambda || !fun.lambda.fun->matchAttrs) { - res = fun; - return; - } - - Value* actualArgs = allocValue(); - mkAttrs(*actualArgs, fun.lambda.fun->formals->formals.size()); - - if (fun.lambda.fun->formals->ellipsis) { - // If the formals have an ellipsis (eg the function accepts extra args) pass - // all available automatic arguments (which includes arguments specified on - // the command line via --arg/--argstr) - for (auto& [_, v] : *args) { - actualArgs->attrs->push_back(v); - } - } else { - // Otherwise, only pass the arguments that the function accepts - for (auto& i : fun.lambda.fun->formals->formals) { - Bindings::iterator j = args->find(i.name); - if (j != args->end()) { - actualArgs->attrs->push_back(j->second); - } else if (i.def == nullptr) { - throwTypeError( - "cannot auto-call a function that has an argument without a " - "default " - "value ('%1%')", - i.name); - } - } - } - - callFunction(fun, *actualArgs, res, noPos); -} - -void ExprWith::eval(EvalState& state, Env& env, Value& v) { - Env& env2(state.allocEnv(1)); - env2.up = &env; - env2.prevWith = prevWith; - env2.type = Env::HasWithExpr; - /* placeholder for result of attrs */ - env2.values[0] = nullptr; - env2.withAttrsExpr = this->attrs; - - body->eval(state, env2, v); -} - -void ExprIf::eval(EvalState& state, Env& env, Value& v) { - (state.evalBool(env, cond) ? then : else_)->eval(state, env, v); -} - -void ExprAssert::eval(EvalState& state, Env& env, Value& v) { - if (!state.evalBool(env, cond, pos)) { - std::ostringstream out; - cond->show(out); - throwAssertionError("assertion %1% failed at %2%", out.str(), pos); - } - body->eval(state, env, v); -} - -void ExprOpNot::eval(EvalState& state, Env& env, Value& v) { - mkBool(v, !state.evalBool(env, e)); -} - -void ExprOpEq::eval(EvalState& state, Env& env, Value& v) { - Value v1; - e1->eval(state, env, v1); - Value v2; - e2->eval(state, env, v2); - mkBool(v, state.eqValues(v1, v2)); -} - -void ExprOpNEq::eval(EvalState& state, Env& env, Value& v) { - Value v1; - e1->eval(state, env, v1); - Value v2; - e2->eval(state, env, v2); - mkBool(v, !state.eqValues(v1, v2)); -} - -void ExprOpAnd::eval(EvalState& state, Env& env, Value& v) { - mkBool(v, state.evalBool(env, e1, pos) && state.evalBool(env, e2, pos)); -} - -void ExprOpOr::eval(EvalState& state, Env& env, Value& v) { - mkBool(v, state.evalBool(env, e1, pos) || state.evalBool(env, e2, pos)); -} - -void ExprOpImpl::eval(EvalState& state, Env& env, Value& v) { - mkBool(v, !state.evalBool(env, e1, pos) || state.evalBool(env, e2, pos)); -} - -void ExprOpUpdate::eval(EvalState& state, Env& env, Value& dest) { - Value v1; - Value v2; - state.evalAttrs(env, e1, v1); - state.evalAttrs(env, e2, v2); - - state.nrOpUpdates++; - - clearValue(dest); - dest.type = tAttrs; - dest.attrs = Bindings::Merge(*v1.attrs, *v2.attrs); -} - -void ExprOpConcatLists::eval(EvalState& state, Env& env, Value& v) { - Value v1; - e1->eval(state, env, v1); - Value v2; - e2->eval(state, env, v2); - state.concatLists(v, {&v1, &v2}, pos); -} - -void EvalState::concatLists(Value& v, const NixList& lists, const Pos& pos) { - nrListConcats++; - - auto outlist = std::make_shared<NixList>(); - - for (Value* list : lists) { - forceList(*list, pos); - outlist->insert(outlist->end(), list->list->begin(), list->list->end()); - } - - mkList(v, outlist); -} - -void ExprConcatStrings::eval(EvalState& state, Env& env, Value& v) { - PathSet context; - std::ostringstream s; - NixInt n = 0; - NixFloat nf = 0; - - bool first = !forceString; - ValueType firstType = tString; - - for (auto& i : *es) { - Value vTmp; - i->eval(state, env, vTmp); - - /* If the first element is a path, then the result will also - be a path, we don't copy anything (yet - that's done later, - since paths are copied when they are used in a derivation), - and none of the strings are allowed to have contexts. */ - if (first) { - firstType = vTmp.type; - first = false; - } - - if (firstType == tInt) { - if (vTmp.type == tInt) { - n += vTmp.integer; - } else if (vTmp.type == tFloat) { - // Upgrade the type from int to float; - firstType = tFloat; - nf = n; - nf += vTmp.fpoint; - } else { - throwEvalError("cannot add %1% to an integer, at %2%", showType(vTmp), - pos); - } - } else if (firstType == tFloat) { - if (vTmp.type == tInt) { - nf += vTmp.integer; - } else if (vTmp.type == tFloat) { - nf += vTmp.fpoint; - } else { - throwEvalError("cannot add %1% to a float, at %2%", showType(vTmp), - pos); - } - } else { - s << state.coerceToString(pos, vTmp, context, false, - firstType == tString); - } - } - - if (firstType == tInt) { - mkInt(v, n); - } else if (firstType == tFloat) { - mkFloat(v, nf); - } else if (firstType == tPath) { - if (!context.empty()) { - throwEvalError( - "a string that refers to a store path cannot be appended to a path, " - "at %1%", - pos); - } - auto path = canonPath(s.str()); - mkPath(v, path.c_str()); - } else { - mkString(v, s.str(), context); - } -} - -void ExprPos::eval(EvalState& state, Env& env, Value& v) { - state.mkPos(v, &pos); -} - -template <typename T> -using traceable_flat_hash_set = absl::flat_hash_set<T>; - -void EvalState::forceValueDeep(Value& v) { - traceable_flat_hash_set<const Value*> seen; - - std::function<void(Value & v)> recurse; - - recurse = [&](Value& v) { - if (seen.find(&v) != seen.end()) { - return; - } - seen.insert(&v); - - forceValue(v); - - if (v.type == tAttrs) { - for (auto& i : *v.attrs) { - try { - recurse(*i.second.value); - } catch (Error& e) { - addErrorPrefix(e, "while evaluating the attribute '%1%' at %2%:\n", - i.second.name, *i.second.pos); - throw; - } - } - } else if (v.isList()) { - for (size_t n = 0; n < v.listSize(); ++n) { - recurse(*(*v.list)[n]); - } - } - }; - - recurse(v); -} - -NixInt EvalState::forceInt(Value& v, const Pos& pos) { - forceValue(v, pos); - if (v.type != tInt) { - throwTypeError("value is %1% while an integer was expected, at %2%", v, - pos); - } - return v.integer; -} - -NixFloat EvalState::forceFloat(Value& v, const Pos& pos) { - forceValue(v, pos); - if (v.type == tInt) { - return static_cast<NixFloat>(v.integer); - } - if (v.type != tFloat) { - throwTypeError("value is %1% while a float was expected, at %2%", v, pos); - } - return v.fpoint; -} - -bool EvalState::forceBool(Value& v, const Pos& pos) { - forceValue(v); - if (v.type != tBool) { - throwTypeError("value is %1% while a Boolean was expected, at %2%", v, pos); - } - return v.boolean; -} - -bool EvalState::isFunctor(Value& fun) { - return fun.type == tAttrs && fun.attrs->find(sFunctor) != fun.attrs->end(); -} - -void EvalState::forceFunction(Value& v, const Pos& pos) { - forceValue(v); - if (v.type != tLambda && v.type != tPrimOp && v.type != tPrimOpApp && - !isFunctor(v)) { - throwTypeError("value is %1% while a function was expected, at %2%", v, - pos); - } -} - -std::string EvalState::forceString(Value& v, const Pos& pos) { - forceValue(v, pos); - if (v.type != tString) { - if (pos) { - throwTypeError("value is %1% while a string was expected, at %2%", v, - pos); - } else { - throwTypeError("value is %1% while a string was expected", v); - } - } - return std::string(v.string.s); -} - -void copyContext(const Value& v, PathSet& context) { - if (v.string.context != nullptr) { - for (const char** p = v.string.context; *p != nullptr; ++p) { - context.insert(*p); - } - } -} - -std::string EvalState::forceString(Value& v, PathSet& context, const Pos& pos) { - std::string s = forceString(v, pos); - copyContext(v, context); - return s; -} - -std::string EvalState::forceStringNoCtx(Value& v, const Pos& pos) { - std::string s = forceString(v, pos); - if (v.string.context != nullptr) { - if (pos) { - throwEvalError( - "the string '%1%' is not allowed to refer to a store path (such as " - "'%2%'), at %3%", - v.string.s, v.string.context[0], pos); - } else { - throwEvalError( - "the string '%1%' is not allowed to refer to a store path (such as " - "'%2%')", - v.string.s, v.string.context[0]); - } - } - return s; -} - -bool EvalState::isDerivation(Value& v) { - if (v.type != tAttrs) { - return false; - } - Bindings::iterator i = v.attrs->find(sType); - if (i == v.attrs->end()) { - return false; - } - forceValue(*i->second.value); - if (i->second.value->type != tString) { - return false; - } - return strcmp(i->second.value->string.s, "derivation") == 0; -} - -std::optional<std::string> EvalState::tryAttrsToString(const Pos& pos, Value& v, - PathSet& context, - bool coerceMore, - bool copyToStore) { - auto i = v.attrs->find(sToString); - if (i != v.attrs->end()) { - Value v1; - callFunction(*i->second.value, v, v1, pos); - return coerceToString(pos, v1, context, coerceMore, copyToStore); - } - - return {}; -} - -std::string EvalState::coerceToString(const Pos& pos, Value& v, - PathSet& context, bool coerceMore, - bool copyToStore) { - forceValue(v); - - std::string s; - - if (v.type == tString) { - copyContext(v, context); - return v.string.s; - } - - if (v.type == tPath) { - Path path(canonPath(v.path)); - return copyToStore ? copyPathToStore(context, path) : path; - } - - if (v.type == tAttrs) { - auto maybeString = - tryAttrsToString(pos, v, context, coerceMore, copyToStore); - if (maybeString) { - return *maybeString; - } - auto i = v.attrs->find(sOutPath); - if (i == v.attrs->end()) { - throwTypeError("cannot coerce a set to a string, at %1%", pos); - } - return coerceToString(pos, *i->second.value, context, coerceMore, - copyToStore); - } - - if (coerceMore) { - /* Note that `false' is represented as an empty string for - shell scripting convenience, just like `null'. */ - if (v.type == tBool && v.boolean) { - return "1"; - } - if (v.type == tBool && !v.boolean) { - return ""; - } - if (v.type == tInt) { - return std::to_string(v.integer); - } - if (v.type == tFloat) { - return std::to_string(v.fpoint); - } - if (v.type == tNull) { - return ""; - } - - if (v.isList()) { - std::string result; - for (size_t n = 0; n < v.listSize(); ++n) { - result += coerceToString(pos, *(*v.list)[n], context, coerceMore, - copyToStore); - if (n < v.listSize() - 1 - /* !!! not quite correct */ - && (!(*v.list)[n]->isList() || (*v.list)[n]->listSize() != 0)) { - result += " "; - } - } - return result; - } - } - - throwTypeError("cannot coerce %1% to a string, at %2%", v, pos); -} - -std::string EvalState::copyPathToStore(PathSet& context, const Path& path) { - if (nix::isDerivation(path)) { - throwEvalError("file names are not allowed to end in '%1%'", drvExtension); - } - - Path dstPath; - if (!srcToStore[path].empty()) { - dstPath = srcToStore[path]; - } else { - dstPath = - settings.readOnlyMode - ? store - ->computeStorePathForPath(baseNameOf(path), - checkSourcePath(path)) - .first - : store->addToStore(baseNameOf(path), checkSourcePath(path), true, - htSHA256, defaultPathFilter, repair); - srcToStore[path] = dstPath; - VLOG(2) << "copied source '" << path << "' -> '" << dstPath << "'"; - } - - context.insert(dstPath); - return dstPath; -} - -Path EvalState::coerceToPath(const Pos& pos, Value& v, PathSet& context) { - std::string path = coerceToString(pos, v, context, false, false); - if (path.empty() || path[0] != '/') { - throwEvalError("string '%1%' doesn't represent an absolute path, at %2%", - path, pos); - } - return path; -} - -bool EvalState::eqValues(Value& v1, Value& v2) { - forceValue(v1); - forceValue(v2); - - /* !!! Hack to support some old broken code that relies on pointer - equality tests between sets. (Specifically, builderDefs calls - uniqList on a list of sets.) Will remove this eventually. */ - if (&v1 == &v2) { - return true; - } - - // Special case type-compatibility between float and int - if (v1.type == tInt && v2.type == tFloat) { - return v1.integer == v2.fpoint; - } - if (v1.type == tFloat && v2.type == tInt) { - return v1.fpoint == v2.integer; - } - - // All other types are not compatible with each other. - if (v1.type != v2.type) { - return false; - } - - switch (v1.type) { - case tInt: - return v1.integer == v2.integer; - - case tBool: - return v1.boolean == v2.boolean; - - case tString: - return strcmp(v1.string.s, v2.string.s) == 0; - - case tPath: - return strcmp(v1.path, v2.path) == 0; - - case tNull: - return true; - - case tList: - if (v1.listSize() != v2.listSize()) { - return false; - } - for (size_t n = 0; n < v1.listSize(); ++n) { - if (!eqValues(*(*v1.list)[n], *(*v2.list)[n])) { - return false; - } - } - return true; - - case tAttrs: { - // As an optimisation if both values are pointing towards the - // same attribute set, we can skip all this extra work. - if (v1.attrs == v2.attrs) { - return true; - } - - /* If both sets denote a derivation (type = "derivation"), - then compare their outPaths. */ - if (isDerivation(v1) && isDerivation(v2)) { - Bindings::iterator i = v1.attrs->find(sOutPath); - Bindings::iterator j = v2.attrs->find(sOutPath); - if (i != v1.attrs->end() && j != v2.attrs->end()) { - return eqValues(*i->second.value, *j->second.value); - } - } - - return v1.attrs->Equal(v2.attrs.get(), *this); - } - - /* Functions are incomparable. */ - case tLambda: - case tPrimOp: - case tPrimOpApp: - return false; - - case tFloat: - return v1.fpoint == v2.fpoint; - - default: - throwEvalError("cannot compare %1% with %2%", showType(v1), showType(v2)); - } -} - -void EvalState::printStats() { - bool showStats = getEnv("NIX_SHOW_STATS").value_or("0") != "0"; - - struct rusage buf; - getrusage(RUSAGE_SELF, &buf); - float cpuTime = buf.ru_utime.tv_sec + - (static_cast<float>(buf.ru_utime.tv_usec) / 1000000); - - uint64_t bEnvs = nrEnvs * sizeof(Env) + nrValuesInEnvs * sizeof(Value*); - uint64_t bLists = nrListElems * sizeof(Value*); - uint64_t bValues = nrValues * sizeof(Value); - uint64_t bAttrsets = - nrAttrsets * sizeof(Bindings) + nrAttrsInAttrsets * sizeof(Attr); - - if (showStats) { - auto outPath = getEnv("NIX_SHOW_STATS_PATH").value_or("-"); - std::fstream fs; - if (outPath != "-") { - fs.open(outPath, std::fstream::out); - } - JSONObject topObj(outPath == "-" ? std::cerr : fs, true); - topObj.attr("cpuTime", cpuTime); - { - auto envs = topObj.object("envs"); - envs.attr("number", nrEnvs); - envs.attr("elements", nrValuesInEnvs); - envs.attr("bytes", bEnvs); - } - { - auto lists = topObj.object("list"); - lists.attr("elements", nrListElems); - lists.attr("bytes", bLists); - lists.attr("concats", nrListConcats); - } - { - auto values = topObj.object("values"); - values.attr("number", nrValues); - values.attr("bytes", bValues); - } - { - auto syms = topObj.object("symbols"); - syms.attr("number", symbols.Size()); - syms.attr("bytes", symbols.TotalSize()); - } - { - auto sets = topObj.object("sets"); - sets.attr("number", nrAttrsets); - sets.attr("bytes", bAttrsets); - sets.attr("elements", nrAttrsInAttrsets); - } - { - auto sizes = topObj.object("sizes"); - sizes.attr("Env", sizeof(Env)); - sizes.attr("Value", sizeof(Value)); - sizes.attr("Bindings", sizeof(Bindings)); - sizes.attr("Attr", sizeof(Attr)); - } - topObj.attr("nrOpUpdates", nrOpUpdates); - topObj.attr("nrOpUpdateValuesCopied", nrOpUpdateValuesCopied); - topObj.attr("nrThunks", nrThunks); - topObj.attr("nrAvoided", nrAvoided); - topObj.attr("nrLookups", nrLookups); - topObj.attr("nrPrimOpCalls", nrPrimOpCalls); - topObj.attr("nrFunctionCalls", nrFunctionCalls); - - if (countCalls) { - { - auto obj = topObj.object("primops"); - for (auto& i : primOpCalls) { - obj.attr(i.first, i.second); - } - } - { - auto list = topObj.list("functions"); - for (auto& i : functionCalls) { - auto obj = list.object(); - if (i.first->name.has_value()) { - obj.attr("name", (const std::string&)i.first->name.value()); - } else { - obj.attr("name", nullptr); - } - if (i.first->pos) { - obj.attr("file", (const std::string&)i.first->pos.file); - obj.attr("line", i.first->pos.line); - obj.attr("column", i.first->pos.column); - } - obj.attr("count", i.second); - } - } - { - auto list = topObj.list("attributes"); - for (auto& i : attrSelects) { - auto obj = list.object(); - if (i.first) { - obj.attr("file", (const std::string&)i.first.file); - obj.attr("line", i.first.line); - obj.attr("column", i.first.column); - } - obj.attr("count", i.second); - } - } - } - - // TODO(tazjin): what is this? commented out because .dump() is gone. - // if (getEnv("NIX_SHOW_SYMBOLS", "0") != "0") { - // auto list = topObj.list("symbols"); - // symbols.dump([&](const std::string& s) { list.elem(s); }); - // } - } -} - -void EvalState::TraceFileAccess(const Path& realPath) { - if (file_access_trace_fn) { - if (last_traced_file != realPath) { - file_access_trace_fn(realPath); - // Basic deduplication. - last_traced_file = std::string(realPath); - } - } -} - -void EvalState::EnableFileAccessTracing(std::function<void(const Path&)> fn) { - file_access_trace_fn = fn; -} - -size_t valueSize(const Value& v) { - traceable_flat_hash_set<const Bindings*> seenBindings; - traceable_flat_hash_set<const Env*> seenEnvs; - traceable_flat_hash_set<const NixList*> seenLists; - traceable_flat_hash_set<const char*> seenStrings; - traceable_flat_hash_set<const Value*> seenValues; - - auto doString = [&](const char* s) -> size_t { - if (seenStrings.find(s) != seenStrings.end()) { - return 0; - } - seenStrings.insert(s); - return strlen(s) + 1; - }; - - std::function<size_t(const Value& v)> doValue; - std::function<size_t(const Env& v)> doEnv; - - doValue = [&](const Value& v) -> size_t { - if (seenValues.find(&v) != seenValues.end()) { - return 0; - } - seenValues.insert(&v); - - size_t sz = sizeof(Value); - - switch (v.type) { - case tString: - sz += doString(v.string.s); - if (v.string.context != nullptr) { - for (const char** p = v.string.context; *p != nullptr; ++p) { - sz += doString(*p); - } - } - break; - case tPath: - sz += doString(v.path); - break; - case tAttrs: - if (seenBindings.find(v.attrs.get()) == seenBindings.end()) { - seenBindings.insert(v.attrs.get()); - sz += sizeof(Bindings); - for (const auto& i : *v.attrs) { - sz += doValue(*i.second.value); - } - } - break; - case tList: - if (seenLists.find(v.list.get()) == seenLists.end()) { - seenLists.insert(v.list.get()); - sz += v.listSize() * sizeof(Value*); - for (const Value* v : *v.list) { - sz += doValue(*v); - } - } - break; - case tThunk: - sz += doEnv(*v.thunk.env); - break; - case tApp: - sz += doValue(*v.app.left); - sz += doValue(*v.app.right); - break; - case tLambda: - sz += doEnv(*v.lambda.env); - break; - case tPrimOpApp: - sz += doValue(*v.primOpApp.left); - sz += doValue(*v.primOpApp.right); - break; - default:; - } - - return sz; - }; - - doEnv = [&](const Env& env) -> size_t { - if (seenEnvs.find(&env) != seenEnvs.end()) { - return 0; - } - seenEnvs.insert(&env); - - size_t sz = sizeof(Env) + sizeof(Value*) * env.size; - - if (env.type != Env::HasWithExpr) { - for (const Value* v : env.values) { - if (v != nullptr) { - sz += doValue(*v); - } - } - } else { - // TODO(kanepyork): trace ExprWith? how important is this accounting? - } - - if (env.up != nullptr) { - sz += doEnv(*env.up); - } - - return sz; - }; - - return doValue(v); -} - -EvalSettings evalSettings; - -static GlobalConfig::Register r1(&evalSettings); - -} // namespace nix |