diff options
Diffstat (limited to 'third_party/nix/src/libexpr/eval.cc')
-rw-r--r-- | third_party/nix/src/libexpr/eval.cc | 3065 |
1 files changed, 1461 insertions, 1604 deletions
diff --git a/third_party/nix/src/libexpr/eval.cc b/third_party/nix/src/libexpr/eval.cc index 3426afb6cf6e..14b600b06ec4 100644 --- a/third_party/nix/src/libexpr/eval.cc +++ b/third_party/nix/src/libexpr/eval.cc @@ -1,24 +1,21 @@ #include "eval.hh" -#include "hash.hh" -#include "util.hh" -#include "store-api.hh" -#include "derivations.hh" -#include "globals.hh" -#include "eval-inline.hh" -#include "download.hh" -#include "json.hh" -#include "function-trace.hh" - +#include <sys/resource.h> +#include <sys/time.h> +#include <unistd.h> #include <algorithm> #include <chrono> #include <cstring> -#include <unistd.h> -#include <sys/time.h> -#include <sys/resource.h> -#include <iostream> #include <fstream> - -#include <sys/resource.h> +#include <iostream> +#include "derivations.hh" +#include "download.hh" +#include "eval-inline.hh" +#include "function-trace.hh" +#include "globals.hh" +#include "hash.hh" +#include "json.hh" +#include "store-api.hh" +#include "util.hh" #if HAVE_BOEHMGC @@ -29,1963 +26,1823 @@ namespace nix { - -static char * dupString(const char * s) -{ - char * t; +static char* dupString(const char* s) { + char* t; #if HAVE_BOEHMGC - t = GC_STRDUP(s); + t = GC_STRDUP(s); #else - t = strdup(s); + t = strdup(s); #endif - if (!t) throw std::bad_alloc(); - return t; + if (!t) throw std::bad_alloc(); + return t; } +static void printValue(std::ostream& str, std::set<const Value*>& active, + const Value& v) { + checkInterrupt(); -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); - if (active.find(&v) != active.end()) { - str << "<CYCLE>"; - return; - } - active.insert(&v); - - switch (v.type) { + switch (v.type) { case tInt: - str << v.integer; - break; + str << v.integer; + break; case tBool: - str << (v.boolean ? "true" : "false"); - break; + str << (v.boolean ? "true" : "false"); + break; case tString: - str << "\""; - for (const char * i = v.string.s; *i; 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; + str << "\""; + for (const char* i = v.string.s; *i; 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; + str << v.path; // !!! escaping? + break; case tNull: - str << "null"; - break; + str << "null"; + break; case tAttrs: { - str << "{ "; - for (auto & i : v.attrs->lexicographicOrder()) { - str << i->name << " = "; - printValue(str, active, *i->value); - str << "; "; - } - str << "}"; - break; + str << "{ "; + for (auto& i : v.attrs->lexicographicOrder()) { + str << i->name << " = "; + printValue(str, active, *i->value); + str << "; "; + } + str << "}"; + break; } case tList1: case tList2: case tListN: - str << "[ "; - for (unsigned int n = 0; n < v.listSize(); ++n) { - printValue(str, active, *v.listElems()[n]); - str << " "; - } - str << "]"; - break; + str << "[ "; + for (unsigned int n = 0; n < v.listSize(); ++n) { + printValue(str, active, *v.listElems()[n]); + str << " "; + } + str << "]"; + break; case tThunk: case tApp: - str << "<CODE>"; - break; + str << "<CODE>"; + break; case tLambda: - str << "<LAMBDA>"; - break; + str << "<LAMBDA>"; + break; case tPrimOp: - str << "<PRIMOP>"; - break; + str << "<PRIMOP>"; + break; case tPrimOpApp: - str << "<PRIMOP-APP>"; - break; + str << "<PRIMOP-APP>"; + break; case tExternal: - str << *v.external; - break; + str << *v.external; + break; case tFloat: - str << v.fpoint; - break; + str << v.fpoint; + break; default: - throw Error("invalid value"); - } + throw Error("invalid value"); + } - active.erase(&v); + active.erase(&v); } - -std::ostream & operator << (std::ostream & str, const Value & v) -{ - std::set<const Value *> active; - printValue(str, active, v); - return str; +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; +const Value* getPrimOp(const Value& v) { + const Value* primOp = &v; + while (primOp->type == tPrimOpApp) { + primOp = primOp->primOpApp.left; + } + assert(primOp->type == tPrimOp); + return primOp; } - -string showType(const Value & v) -{ - switch (v.type) { - case tInt: return "an integer"; - case tBool: return "a boolean"; - case tString: return v.string.context ? "a string with context" : "a string"; - case tPath: return "a path"; - case tNull: return "null"; - case tAttrs: return "a set"; - case tList1: case tList2: case tListN: 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'", string(v.primOp->name)); - case tPrimOpApp: - return fmt("the partially applied built-in function '%s'", string(getPrimOp(v)->primOp->name)); - case tExternal: return v.external->showType(); - case tFloat: return "a float"; - } - abort(); +string showType(const Value& v) { + switch (v.type) { + case tInt: + return "an integer"; + case tBool: + return "a boolean"; + case tString: + return v.string.context ? "a string with context" : "a string"; + case tPath: + return "a path"; + case tNull: + return "null"; + case tAttrs: + return "a set"; + case tList1: + case tList2: + case tListN: + 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'", string(v.primOp->name)); + case tPrimOpApp: + return fmt("the partially applied built-in function '%s'", + string(getPrimOp(v)->primOp->name)); + case tExternal: + return v.external->showType(); + case tFloat: + return "a float"; + } + abort(); } - #if HAVE_BOEHMGC /* Called when the Boehm GC runs out of memory. */ -static void * oomHandler(size_t requested) -{ - /* Convert this to a proper C++ exception. */ - throw std::bad_alloc(); +static void* oomHandler(size_t requested) { + /* Convert this to a proper C++ exception. */ + throw std::bad_alloc(); } #endif - -static Symbol getName(const AttrName & name, EvalState & state, Env & env) -{ - if (name.symbol.set()) { - return name.symbol; - } else { - Value nameValue; - name.expr->eval(state, env, nameValue); - state.forceStringNoCtx(nameValue); - return state.symbols.create(nameValue.string.s); - } +static Symbol getName(const AttrName& name, EvalState& state, Env& env) { + if (name.symbol.set()) { + return name.symbol; + } else { + Value nameValue; + name.expr->eval(state, env, nameValue); + state.forceStringNoCtx(nameValue); + return state.symbols.create(nameValue.string.s); + } } - static bool gcInitialised = false; -void initGC() -{ - if (gcInitialised) return; +void initGC() { + if (gcInitialised) return; #if HAVE_BOEHMGC - /* Initialise the Boehm garbage collector. */ - - /* Don't look for interior pointers. This reduces the odds of - misdetection a bit. */ - GC_set_all_interior_pointers(0); - - /* We don't have any roots in data segments, so don't scan from - there. */ - GC_set_no_dls(1); - - GC_INIT(); - - GC_set_oom_fn(oomHandler); - - /* Set the initial heap size to something fairly big (25% of - physical RAM, up to a maximum of 384 MiB) so that in most cases - we don't need to garbage collect at all. (Collection has a - fairly significant overhead.) The heap size can be overridden - through libgc's GC_INITIAL_HEAP_SIZE environment variable. We - should probably also provide a nix.conf setting for this. Note - that GC_expand_hp() causes a lot of virtual, but not physical - (resident) memory to be allocated. This might be a problem on - systems that don't overcommit. */ - if (!getenv("GC_INITIAL_HEAP_SIZE")) { - size_t size = 32 * 1024 * 1024; + /* Initialise the Boehm garbage collector. */ + + /* Don't look for interior pointers. This reduces the odds of + misdetection a bit. */ + GC_set_all_interior_pointers(0); + + /* We don't have any roots in data segments, so don't scan from + there. */ + GC_set_no_dls(1); + + GC_INIT(); + + GC_set_oom_fn(oomHandler); + + /* Set the initial heap size to something fairly big (25% of + physical RAM, up to a maximum of 384 MiB) so that in most cases + we don't need to garbage collect at all. (Collection has a + fairly significant overhead.) The heap size can be overridden + through libgc's GC_INITIAL_HEAP_SIZE environment variable. We + should probably also provide a nix.conf setting for this. Note + that GC_expand_hp() causes a lot of virtual, but not physical + (resident) memory to be allocated. This might be a problem on + systems that don't overcommit. */ + if (!getenv("GC_INITIAL_HEAP_SIZE")) { + size_t size = 32 * 1024 * 1024; #if HAVE_SYSCONF && defined(_SC_PAGESIZE) && defined(_SC_PHYS_PAGES) - size_t maxSize = 384 * 1024 * 1024; - long pageSize = sysconf(_SC_PAGESIZE); - long pages = sysconf(_SC_PHYS_PAGES); - if (pageSize != -1) - size = (pageSize * pages) / 4; // 25% of RAM - if (size > maxSize) size = maxSize; + size_t maxSize = 384 * 1024 * 1024; + long pageSize = sysconf(_SC_PAGESIZE); + long pages = sysconf(_SC_PHYS_PAGES); + if (pageSize != -1) size = (pageSize * pages) / 4; // 25% of RAM + if (size > maxSize) size = maxSize; #endif - debug(format("setting initial heap size to %1% bytes") % size); - GC_expand_hp(size); - } + debug(format("setting initial heap size to %1% bytes") % size); + GC_expand_hp(size); + } #endif - gcInitialised = true; + gcInitialised = true; } - /* 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 string & s) -{ - Strings res; - - auto p = s.begin(); +static Strings parseNixPath(const string& s) { + Strings res; - while (p != s.end()) { - auto start = p; - auto start2 = p; + auto p = s.begin(); - 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; - } + while (p != s.end()) { + auto start = p; + auto start2 = p; - 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; + while (p != s.end() && *p != ':') { + if (*p == '=') start2 = p + 1; + ++p; } - return res; -} - - -EvalState::EvalState(const Strings & _searchPath, 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")) - , sOverrides(symbols.create("__overrides")) - , 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")) - , repair(NoRepair) - , store(store) - , baseEnv(allocEnv(128)) - , staticBaseEnv(false, 0) -{ - countCalls = getEnv("NIX_COUNT_CALLS", "0") != "0"; - - assert(gcInitialised); - - static_assert(sizeof(Env) <= 16, "environment must be <= 16 bytes"); - - /* Initialise the Nix expression search path. */ - if (!evalSettings.pureEval) { - Strings paths = parseNixPath(getEnv("NIX_PATH", "")); - for (auto & i : _searchPath) addToSearchPath(i); - for (auto & i : paths) addToSearchPath(i); + if (p == s.end()) { + if (p != start) res.push_back(std::string(start, p)); + break; } - 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); - } + 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; } - clearValue(vEmptySet); - vEmptySet.type = tAttrs; - vEmptySet.attrs = allocBindings(0); - - createBaseEnv(); -} + ++p; + } + + return res; +} + +EvalState::EvalState(const Strings& _searchPath, 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")), + sOverrides(symbols.create("__overrides")), + 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")), + repair(NoRepair), + store(store), + baseEnv(allocEnv(128)), + staticBaseEnv(false, 0) { + countCalls = getEnv("NIX_COUNT_CALLS", "0") != "0"; + + assert(gcInitialised); + + static_assert(sizeof(Env) <= 16, "environment must be <= 16 bytes"); + + /* Initialise the Nix expression search path. */ + if (!evalSettings.pureEval) { + Strings paths = parseNixPath(getEnv("NIX_PATH", "")); + 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); + } + } + clearValue(vEmptySet); + vEmptySet.type = tAttrs; + vEmptySet.attrs = allocBindings(0); -EvalState::~EvalState() -{ + createBaseEnv(); } +EvalState::~EvalState() {} -Path EvalState::checkSourcePath(const Path & path_) -{ - if (!allowedPaths) return path_; - - auto i = resolvedPaths.find(path_); - if (i != resolvedPaths.end()) - return i->second; - - bool found = false; +Path EvalState::checkSourcePath(const Path& path_) { + if (!allowedPaths) return path_; - /* 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; - } - } + auto i = resolvedPaths.find(path_); + if (i != resolvedPaths.end()) return i->second; - if (!found) - throw RestrictedPathError("access to path '%1%' is forbidden in restricted mode", abspath); + bool found = false; - /* Resolve symlinks. */ - debug(format("checking access to '%s'") % abspath); - Path path = canonPath(abspath, true); + /* 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(path, i)) { - resolvedPaths[path_] = path; - return path; - } + for (auto& i : *allowedPaths) { + if (isDirOrInDir(abspath, i)) { + found = true; + break; } + } - throw RestrictedPathError("access to path '%1%' is forbidden in restricted mode", path); -} - + if (!found) + throw RestrictedPathError( + "access to path '%1%' is forbidden in restricted mode", abspath); -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.size() > 0 - && hasPrefix(uri, prefix) - && (prefix[prefix.size() - 1] == '/' || uri[prefix.size()] == '/'))) - return; - - /* If the URI is a path, then check it against allowedPaths as - well. */ - if (hasPrefix(uri, "/")) { - checkSourcePath(uri); - return; - } + /* Resolve symlinks. */ + debug(format("checking access to '%s'") % abspath); + Path path = canonPath(abspath, true); - if (hasPrefix(uri, "file://")) { - checkSourcePath(std::string(uri, 7)); - return; + for (auto& i : *allowedPaths) { + if (isDirOrInDir(path, i)) { + resolvedPaths[path_] = path; + return path; } - - 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; + } + + 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.size() > 0 && + hasPrefix(uri, prefix) && + (prefix[prefix.size() - 1] == '/' || uri[prefix.size()] == '/'))) + return; + + /* If the URI is a path, then check it against allowedPaths as + well. */ + if (hasPrefix(uri, "/")) { + checkSourcePath(uri); + return; + } + + if (hasPrefix(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 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)); - return v2; +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)); + return v2; } - -Value * EvalState::addPrimOp(const string & name, - size_t arity, PrimOpFun primOp) -{ - if (arity == 0) { - Value v; - primOp(*this, noPos, nullptr, v); - return addConstant(name, v); - } - Value * v = allocValue(); - string name2 = string(name, 0, 2) == "__" ? string(name, 2) : name; - Symbol sym = symbols.create(name2); - v->type = tPrimOp; - v->primOp = new 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::addPrimOp(const string& name, size_t arity, + PrimOpFun primOp) { + if (arity == 0) { + Value v; + primOp(*this, noPos, nullptr, v); + return addConstant(name, v); + } + Value* v = allocValue(); + string name2 = string(name, 0, 2) == "__" ? string(name, 2) : name; + Symbol sym = symbols.create(name2); + v->type = tPrimOp; + v->primOp = new 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 string & name) -{ - return *baseEnv.values[0]->attrs->find(symbols.create(name))->value; +Value& EvalState::getBuiltin(const string& name) { + return *baseEnv.values[0]->attrs->find(symbols.create(name))->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 string & s2)) -{ - throw EvalError(format(s) % s2); +LocalNoInlineNoReturn(void throwEvalError(const char* s, const string& s2)) { + throw EvalError(format(s) % s2); } -LocalNoInlineNoReturn(void throwEvalError(const char * s, const string & s2, const Pos & pos)) -{ - throw EvalError(format(s) % s2 % pos); +LocalNoInlineNoReturn(void throwEvalError(const char* s, const string& s2, + const Pos& pos)) { + throw EvalError(format(s) % s2 % pos); } -LocalNoInlineNoReturn(void throwEvalError(const char * s, const string & s2, const string & s3)) -{ - throw EvalError(format(s) % s2 % s3); +LocalNoInlineNoReturn(void throwEvalError(const char* s, const string& s2, + const string& s3)) { + throw EvalError(format(s) % s2 % s3); } -LocalNoInlineNoReturn(void throwEvalError(const char * s, const string & s2, const string & s3, const Pos & pos)) -{ - throw EvalError(format(s) % s2 % s3 % pos); +LocalNoInlineNoReturn(void throwEvalError(const char* s, const string& s2, + const 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 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 Pos& pos)) { + throw TypeError(format(s) % pos); } -LocalNoInlineNoReturn(void throwTypeError(const char * s, const string & s1)) -{ - throw TypeError(format(s) % s1); +LocalNoInlineNoReturn(void throwTypeError(const char* s, const 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 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 string & s1, const Pos & pos)) -{ - throw AssertionError(format(s) % s1 % pos); +LocalNoInlineNoReturn(void throwAssertionError(const char* s, const string& s1, + const Pos& pos)) { + throw AssertionError(format(s) % s1 % pos); } -LocalNoInlineNoReturn(void throwUndefinedVarError(const char * s, const string & s1, const Pos & pos)) -{ - throw UndefinedVarError(format(s) % s1 % pos); +LocalNoInlineNoReturn(void throwUndefinedVarError(const char* s, + const string& s1, + const Pos& pos)) { + throw UndefinedVarError(format(s) % s1 % pos); } -LocalNoInline(void addErrorPrefix(Error & e, const char * s, const string & s2)) -{ - e.addPrefix(format(s) % s2); +LocalNoInline(void addErrorPrefix(Error& e, const char* s, const 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 ExprLambda& fun, const Pos& pos)) { + e.addPrefix(format(s) % fun.showNamePos() % pos); } -LocalNoInline(void addErrorPrefix(Error & e, const char * s, const string & s2, const Pos & pos)) -{ - e.addPrefix(format(s) % s2 % pos); +LocalNoInline(void addErrorPrefix(Error& e, const char* s, const string& s2, + const Pos& pos)) { + e.addPrefix(format(s) % s2 % pos); } +void mkString(Value& v, const char* s) { mkStringNoCopy(v, dupString(s)); } -void mkString(Value & v, const char * s) -{ - mkStringNoCopy(v, dupString(s)); +Value& mkString(Value& v, const string& s, const PathSet& context) { + mkString(v, s.c_str()); + if (!context.empty()) { + size_t n = 0; + v.string.context = + (const char**)allocBytes((context.size() + 1) * sizeof(char*)); + for (auto& i : context) v.string.context[n++] = dupString(i.c_str()); + v.string.context[n] = 0; + } + return v; } +void mkPath(Value& v, const char* s) { mkPathNoCopy(v, dupString(s)); } -Value & mkString(Value & v, const string & s, const PathSet & context) -{ - mkString(v, s.c_str()); - if (!context.empty()) { - size_t n = 0; - v.string.context = (const char * *) - allocBytes((context.size() + 1) * sizeof(char *)); - for (auto & i : context) - v.string.context[n++] = dupString(i.c_str()); - v.string.context[n] = 0; - } - 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; --l, env = env->up) + ; -inline Value * EvalState::lookupVar(Env * env, const ExprVar & var, bool noEval) -{ - for (size_t l = var.level; l; --l, env = env->up) ; + if (!var.fromWith) return env->values[var.displ]; - if (!var.fromWith) return env->values[var.displ]; - - while (1) { - if (env->type == Env::HasWithExpr) { - if (noEval) return 0; - Value * v = allocValue(); - evalAttrs(*env->up, (Expr *) env->values[0], *v); - env->values[0] = v; - env->type = Env::HasWithAttrs; - } - Bindings::iterator j = env->values[0]->attrs->find(var.name); - if (j != env->values[0]->attrs->end()) { - if (countCalls && j->pos) attrSelects[*j->pos]++; - return j->value; - } - if (!env->prevWith) - throwUndefinedVarError("undefined variable '%1%' at %2%", var.name, var.pos); - for (size_t l = env->prevWith; l; --l, env = env->up) ; + while (1) { + if (env->type == Env::HasWithExpr) { + if (noEval) return 0; + Value* v = allocValue(); + evalAttrs(*env->up, (Expr*)env->values[0], *v); + env->values[0] = v; + env->type = Env::HasWithAttrs; } + Bindings::iterator j = env->values[0]->attrs->find(var.name); + if (j != env->values[0]->attrs->end()) { + if (countCalls && j->pos) attrSelects[*j->pos]++; + return j->value; + } + if (!env->prevWith) + throwUndefinedVarError("undefined variable '%1%' at %2%", var.name, + var.pos); + for (size_t l = env->prevWith; l; --l, env = env->up) + ; + } } - std::atomic<uint64_t> nrValuesFreed{0}; -void finalizeValue(void * obj, void * data) -{ - nrValuesFreed++; -} +void finalizeValue(void* obj, void* data) { nrValuesFreed++; } -Value * EvalState::allocValue() -{ - nrValues++; - auto v = (Value *) allocBytes(sizeof(Value)); - //GC_register_finalizer_no_order(v, finalizeValue, nullptr, nullptr, nullptr); - return v; +Value* EvalState::allocValue() { + nrValues++; + auto v = (Value*)allocBytes(sizeof(Value)); + // GC_register_finalizer_no_order(v, finalizeValue, nullptr, nullptr, + // nullptr); + return v; } +Env& EvalState::allocEnv(size_t size) { + if (size > std::numeric_limits<decltype(Env::size)>::max()) + throw Error("environment size %d is too big", size); -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 = (Env *) allocBytes(sizeof(Env) + size * sizeof(Value *)); - env->size = (decltype(Env::size)) size; - env->type = Env::Plain; + nrEnvs++; + nrValuesInEnvs += size; + Env* env = (Env*)allocBytes(sizeof(Env) + size * sizeof(Value*)); + env->size = (decltype(Env::size))size; + env->type = Env::Plain; - /* We assume that env->values has been cleared by the allocator; maybeThunk() and lookupVar fromWith expect this. */ + /* We assume that env->values has been cleared by the allocator; maybeThunk() + * and lookupVar fromWith expect this. */ - return *env; + return *env; } - -void EvalState::mkList(Value & v, size_t size) -{ - clearValue(v); - if (size == 1) - v.type = tList1; - else if (size == 2) - v.type = tList2; - else { - v.type = tListN; - v.bigList.size = size; - v.bigList.elems = size ? (Value * *) allocBytes(size * sizeof(Value *)) : 0; - } - nrListElems += size; +void EvalState::mkList(Value& v, size_t size) { + clearValue(v); + if (size == 1) + v.type = tList1; + else if (size == 2) + v.type = tList2; + else { + v.type = tListN; + v.bigList.size = size; + v.bigList.elems = size ? (Value**)allocBytes(size * sizeof(Value*)) : 0; + } + nrListElems += 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++; +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::mkThunk_(Value & v, Expr * expr) -{ - mkThunk(v, baseEnv, expr); +void EvalState::mkPos(Value& v, Pos* pos) { + if (pos && pos->file.set()) { + mkAttrs(v, 3); + mkString(*allocAttr(v, sFile), pos->file); + mkInt(*allocAttr(v, sLine), pos->line); + mkInt(*allocAttr(v, sColumn), pos->column); + v.attrs->sort(); + } else + mkNull(v); } - -void EvalState::mkPos(Value & v, Pos * pos) -{ - if (pos && pos->file.set()) { - mkAttrs(v, 3); - mkString(*allocAttr(v, sFile), pos->file); - mkInt(*allocAttr(v, sLine), pos->line); - mkInt(*allocAttr(v, sColumn), pos->column); - v.attrs->sort(); - } 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; +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) { nrAvoided++; return v; } - return Expr::maybeThunk(state, env); -} - - -Value * ExprString::maybeThunk(EvalState & state, Env & env) -{ +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) { nrAvoided++; - return &v; + return v; + } + return Expr::maybeThunk(state, env); } -Value * ExprInt::maybeThunk(EvalState & state, Env & env) -{ - nrAvoided++; - return &v; +Value* ExprString::maybeThunk(EvalState& state, Env& env) { + nrAvoided++; + return &v; } -Value * ExprFloat::maybeThunk(EvalState & state, Env & env) -{ - nrAvoided++; - return &v; +Value* ExprInt::maybeThunk(EvalState& state, Env& env) { + nrAvoided++; + return &v; } -Value * ExprPath::maybeThunk(EvalState & state, Env & env) -{ - nrAvoided++; - return &v; +Value* ExprFloat::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; - } - - printTalkative("evaluating file '%1%'", path2); - Expr * e = nullptr; - - auto j = fileParseCache.find(path2); - if (j != fileParseCache.end()) - e = j->second; - - if (!e) - 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; +Value* ExprPath::maybeThunk(EvalState& state, Env& env) { + nrAvoided++; + return &v; } +void EvalState::evalFile(const Path& path_, Value& v) { + auto path = checkSourcePath(path_); -void EvalState::resetFileCache() -{ - fileEvalCache.clear(); - fileParseCache.clear(); -} + 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; + } -void EvalState::eval(Expr * e, Value & v) -{ - e->eval(*this, baseEnv, v); -} + printTalkative("evaluating file '%1%'", path2); + Expr* e = nullptr; + auto j = fileParseCache.find(path2); + if (j != fileParseCache.end()) e = j->second; -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; -} - + if (!e) e = parseExprFromFile(checkSourcePath(path2)); -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); -} + fileParseCache[path2] = e; + try { + eval(e, v); + } catch (Error& e) { + addErrorPrefix(e, "while evaluating the file '%1%':\n", path2); + throw; + } -void Expr::eval(EvalState & state, Env & env, Value & v) -{ - abort(); + fileEvalCache[path2] = v; + if (path != path2) fileEvalCache[path] = v; } - -void ExprInt::eval(EvalState & state, Env & env, Value & v) -{ - v = this->v; +void EvalState::resetFileCache() { + fileEvalCache.clear(); + fileParseCache.clear(); } +void EvalState::eval(Expr* e, Value& v) { e->eval(*this, baseEnv, v); } -void ExprFloat::eval(EvalState & state, Env & env, Value & v) -{ - v = this->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; } -void ExprString::eval(EvalState & state, Env & env, Value & v) -{ - v = this->v; +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; } - -void ExprPath::eval(EvalState & state, Env & env, Value & v) -{ - v = this->v; +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 ExprAttrs::eval(EvalState & state, Env & env, Value & v) -{ - state.mkAttrs(v, attrs.size() + dynamicAttrs.size()); - Env *dynamicEnv = &env; +void ExprInt::eval(EvalState& state, Env& env, Value& v) { v = this->v; } - if (recursive) { - /* Create a new environment that contains the attributes in - this `rec'. */ - Env & env2(state.allocEnv(attrs.size())); - env2.up = &env; - dynamicEnv = &env2; +void ExprFloat::eval(EvalState& state, Env& env, Value& v) { v = this->v; } - AttrDefs::iterator overrides = attrs.find(state.sOverrides); - bool hasOverrides = overrides != attrs.end(); +void ExprString::eval(EvalState& state, Env& env, Value& v) { v = this->v; } - /* 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) { - Value * vAttr; - if (hasOverrides && !i.second.inherited) { - vAttr = state.allocValue(); - mkThunk(*vAttr, env2, i.second.e); - } else - vAttr = i.second.e->maybeThunk(state, i.second.inherited ? env : env2); - env2.values[displ++] = vAttr; - v.attrs->push_back(Attr(i.first, vAttr, &i.second.pos)); - } +void ExprPath::eval(EvalState& state, Env& env, Value& v) { v = this->v; } - /* If the rec contains an attribute called `__overrides', then - evaluate it, and add the attributes in that set to the rec. - This allows overriding of recursive attributes, which is - otherwise not possible. (You can use the // operator to - replace an attribute, but other attributes in the rec will - still reference the original value, because that value has - been substituted into the bodies of the other attributes. - Hence we need __overrides.) */ - if (hasOverrides) { - Value * vOverrides = (*v.attrs)[overrides->second.displ].value; - state.forceAttrs(*vOverrides); - Bindings * newBnds = state.allocBindings(v.attrs->capacity() + vOverrides->attrs->size()); - for (auto & i : *v.attrs) - newBnds->push_back(i); - for (auto & i : *vOverrides->attrs) { - AttrDefs::iterator j = attrs.find(i.name); - if (j != attrs.end()) { - (*newBnds)[j->second.displ] = i; - env2.values[j->second.displ] = i.value; - } else - newBnds->push_back(i); - } - newBnds->sort(); - v.attrs = newBnds; - } - } +void ExprAttrs::eval(EvalState& state, Env& env, Value& v) { + state.mkAttrs(v, attrs.size() + dynamicAttrs.size()); + Env* dynamicEnv = &env; - else - for (auto & i : attrs) - v.attrs->push_back(Attr(i.first, i.second.e->maybeThunk(state, env), &i.second.pos)); - - /* Dynamic attrs apply *after* rec and __overrides. */ - 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 = v.attrs->find(nameSym); - if (j != v.attrs->end()) - throwEvalError("dynamic attribute '%1%' at %2% already defined at %3%", nameSym, i.pos, *j->pos); - - i.valueExpr->setName(nameSym); - /* Keep sorted order so find can catch duplicates */ - v.attrs->push_back(Attr(nameSym, i.valueExpr->maybeThunk(state, *dynamicEnv), &i.pos)); - v.attrs->sort(); // FIXME: inefficient - } -} - - -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())); + 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 & 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.listElems()[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; -} - + AttrDefs::iterator overrides = attrs.find(state.sOverrides); + bool hasOverrides = overrides != attrs.end(); -static 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; - try { - out << getName(i, state, env); - } catch (Error & e) { - assert(!i.symbol.set()); - out << "\"${" << *i.expr << "}\""; - } + /* 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) { + Value* vAttr; + if (hasOverrides && !i.second.inherited) { + vAttr = state.allocValue(); + mkThunk(*vAttr, env2, i.second.e); + } else + vAttr = i.second.e->maybeThunk(state, i.second.inherited ? env : env2); + env2.values[displ++] = vAttr; + v.attrs->push_back(Attr(i.first, vAttr, &i.second.pos)); } - return out.str(); -} - - -unsigned long nrLookups = 0; - -void ExprSelect::eval(EvalState & state, Env & env, Value & v) -{ - Value vTmp; - Pos * pos2 = 0; - Value * vAttrs = &vTmp; - - e->eval(state, env, vTmp); + /* If the rec contains an attribute called `__overrides', then + evaluate it, and add the attributes in that set to the rec. + This allows overriding of recursive attributes, which is + otherwise not possible. (You can use the // operator to + replace an attribute, but other attributes in the rec will + still reference the original value, because that value has + been substituted into the bodies of the other attributes. + Hence we need __overrides.) */ + if (hasOverrides) { + Value* vOverrides = (*v.attrs)[overrides->second.displ].value; + state.forceAttrs(*vOverrides); + Bindings* newBnds = + state.allocBindings(v.attrs->capacity() + vOverrides->attrs->size()); + for (auto& i : *v.attrs) newBnds->push_back(i); + for (auto& i : *vOverrides->attrs) { + AttrDefs::iterator j = attrs.find(i.name); + if (j != attrs.end()) { + (*newBnds)[j->second.displ] = i; + env2.values[j->second.displ] = i.value; + } else + newBnds->push_back(i); + } + newBnds->sort(); + v.attrs = newBnds; + } + } + + else + for (auto& i : attrs) + v.attrs->push_back( + Attr(i.first, i.second.e->maybeThunk(state, env), &i.second.pos)); + + /* Dynamic attrs apply *after* rec and __overrides. */ + 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 = v.attrs->find(nameSym); + if (j != v.attrs->end()) + throwEvalError("dynamic attribute '%1%' at %2% already defined at %3%", + nameSym, i.pos, *j->pos); + + i.valueExpr->setName(nameSym); + /* Keep sorted order so find can catch duplicates */ + v.attrs->push_back( + Attr(nameSym, i.valueExpr->maybeThunk(state, *dynamicEnv), &i.pos)); + v.attrs->sort(); // FIXME: inefficient + } +} + +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.listElems()[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 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; try { - - for (auto & i : attrPath) { - nrLookups++; - Bindings::iterator j; - Symbol name = getName(i, state, env); - if (def) { - 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->value; - pos2 = j->pos; - if (state.countCalls && pos2) state.attrSelects[*pos2]++; - } - - state.forceValue(*vAttrs, ( pos2 != NULL ? *pos2 : this->pos ) ); - - } catch (Error & e) { - if (pos2 && pos2->file != state.sDerivationNix) - addErrorPrefix(e, "while evaluating the attribute '%1%' at %2%:\n", - showAttrPath(state, env, attrPath), *pos2); - throw; + out << getName(i, state, env); + } catch (Error& e) { + assert(!i.symbol.set()); + out << "\"${" << *i.expr << "}\""; } - - v = *vAttrs; + } + return out.str(); } +unsigned long nrLookups = 0; -void ExprOpHasAttr::eval(EvalState & state, Env & env, Value & v) -{ - Value vTmp; - Value * vAttrs = &vTmp; +void ExprSelect::eval(EvalState& state, Env& env, Value& v) { + Value vTmp; + Pos* pos2 = 0; + Value* vAttrs = &vTmp; - e->eval(state, env, vTmp); + e->eval(state, env, vTmp); - for (auto & i : attrPath) { - state.forceValue(*vAttrs); - Bindings::iterator j; - Symbol name = getName(i, state, env); + try { + for (auto& i : attrPath) { + nrLookups++; + Bindings::iterator j; + Symbol name = getName(i, state, env); + if (def) { + state.forceValue(*vAttrs, pos); if (vAttrs->type != tAttrs || - (j = vAttrs->attrs->find(name)) == vAttrs->attrs->end()) - { - mkBool(v, false); - return; - } else { - vAttrs = j->value; + (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->value; + pos2 = j->pos; + if (state.countCalls && pos2) state.attrSelects[*pos2]++; } - mkBool(v, true); -} + state.forceValue(*vAttrs, (pos2 != NULL ? *pos2 : this->pos)); + } catch (Error& e) { + if (pos2 && pos2->file != state.sDerivationNix) + addErrorPrefix(e, "while evaluating the attribute '%1%' at %2%:\n", + showAttrPath(state, env, attrPath), *pos2); + throw; + } -void ExprLambda::eval(EvalState & state, Env & env, Value & v) -{ - v.type = tLambda; - v.lambda.env = &env; - v.lambda.fun = this; + v = *vAttrs; } +void ExprOpHasAttr::eval(EvalState& state, Env& env, Value& v) { + Value vTmp; + Value* vAttrs = &vTmp; -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); -} - + e->eval(state, env, vTmp); -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); + 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; } else { - Value * fun2 = allocValue(); - *fun2 = fun; - v.type = tPrimOpApp; - v.primOpApp.left = fun2; - v.primOpApp.right = &arg; + vAttrs = j->value; } -} - -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; + } + + 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 (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; + callFunction(*found->value, fun2, v2, pos); + return callFunction(v2, arg, v, pos); } - - 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; - callFunction(*found->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) + 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->value; } } - 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) 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->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.name) == lambda.formals->argNames.end()) - throwTypeError("%1% called with unexpected argument '%2%', at %3%", lambda, i.name, pos); - abort(); // can't happen - } + /* 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("%1% called with unexpected argument '%2%', at %3%", + lambda, i.name, pos); + abort(); // can'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); + } + + 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->value, fun, *v, noPos); - forceValue(*v); - return autoCallFunction(args, *v, res); - } +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->value, fun, *v, noPos); + forceValue(*v); + return autoCallFunction(args, *v, res); } + } - if (fun.type != tLambda || !fun.lambda.fun->matchAttrs) { - res = fun; - return; - } + if (fun.type != tLambda || !fun.lambda.fun->matchAttrs) { + res = fun; + return; + } - Value * actualArgs = allocValue(); - mkAttrs(*actualArgs, fun.lambda.fun->formals->formals.size()); + Value* actualArgs = allocValue(); + mkAttrs(*actualArgs, fun.lambda.fun->formals->formals.size()); - for (auto & i : fun.lambda.fun->formals->formals) { - Bindings::iterator j = args.find(i.name); - if (j != args.end()) - actualArgs->attrs->push_back(*j); - else if (!i.def) - throwTypeError("cannot auto-call a function that has an argument without a default value ('%1%')", i.name); - } + for (auto& i : fun.lambda.fun->formals->formals) { + Bindings::iterator j = args.find(i.name); + if (j != args.end()) + actualArgs->attrs->push_back(*j); + else if (!i.def) + throwTypeError( + "cannot auto-call a function that has an argument without a default " + "value ('%1%')", + i.name); + } - actualArgs->attrs->sort(); + actualArgs->attrs->sort(); - callFunction(fun, *actualArgs, res, noPos); + 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; + env2.values[0] = (Value*)attrs; -void ExprWith::eval(EvalState & state, Env & env, Value & v) -{ - Env & env2(state.allocEnv(1)); - env2.up = &env; - env2.prevWith = prevWith; - env2.type = Env::HasWithExpr; - env2.values[0] = (Value *) attrs; - - body->eval(state, env2, v); + 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 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 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 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 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 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 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 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 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& v) { + Value v1, v2; + state.evalAttrs(env, e1, v1); + state.evalAttrs(env, e2, v2); -void ExprOpUpdate::eval(EvalState & state, Env & env, Value & v) -{ - Value v1, v2; - state.evalAttrs(env, e1, v1); - state.evalAttrs(env, e2, v2); - - state.nrOpUpdates++; + state.nrOpUpdates++; - if (v1.attrs->size() == 0) { v = v2; return; } - if (v2.attrs->size() == 0) { v = v1; return; } + if (v1.attrs->size() == 0) { + v = v2; + return; + } + if (v2.attrs->size() == 0) { + v = v1; + return; + } - state.mkAttrs(v, v1.attrs->size() + v2.attrs->size()); + state.mkAttrs(v, v1.attrs->size() + v2.attrs->size()); - /* Merge the sets, preferring values from the second set. Make - sure to keep the resulting vector in sorted order. */ - Bindings::iterator i = v1.attrs->begin(); - Bindings::iterator j = v2.attrs->begin(); + /* Merge the sets, preferring values from the second set. Make + sure to keep the resulting vector in sorted order. */ + Bindings::iterator i = v1.attrs->begin(); + Bindings::iterator j = v2.attrs->begin(); - while (i != v1.attrs->end() && j != v2.attrs->end()) { - if (i->name == j->name) { - v.attrs->push_back(*j); - ++i; ++j; - } - else if (i->name < j->name) - v.attrs->push_back(*i++); - else - v.attrs->push_back(*j++); - } + while (i != v1.attrs->end() && j != v2.attrs->end()) { + if (i->name == j->name) { + v.attrs->push_back(*j); + ++i; + ++j; + } else if (i->name < j->name) + v.attrs->push_back(*i++); + else + v.attrs->push_back(*j++); + } - while (i != v1.attrs->end()) v.attrs->push_back(*i++); - while (j != v2.attrs->end()) v.attrs->push_back(*j++); + while (i != v1.attrs->end()) v.attrs->push_back(*i++); + while (j != v2.attrs->end()) v.attrs->push_back(*j++); - state.nrOpUpdateValuesCopied += v.attrs->size(); + state.nrOpUpdateValuesCopied += v.attrs->size(); } - -void ExprOpConcatLists::eval(EvalState & state, Env & env, Value & v) -{ - Value v1; e1->eval(state, env, v1); - Value v2; e2->eval(state, env, v2); - Value * lists[2] = { &v1, &v2 }; - state.concatLists(v, 2, lists, pos); +void ExprOpConcatLists::eval(EvalState& state, Env& env, Value& v) { + Value v1; + e1->eval(state, env, v1); + Value v2; + e2->eval(state, env, v2); + Value* lists[2] = {&v1, &v2}; + state.concatLists(v, 2, lists, pos); } +void EvalState::concatLists(Value& v, size_t nrLists, Value** lists, + const Pos& pos) { + nrListConcats++; -void EvalState::concatLists(Value & v, size_t nrLists, Value * * lists, const Pos & pos) -{ - nrListConcats++; - - Value * nonEmpty = 0; - size_t len = 0; - for (size_t n = 0; n < nrLists; ++n) { - forceList(*lists[n], pos); - auto l = lists[n]->listSize(); - len += l; - if (l) nonEmpty = lists[n]; - } + Value* nonEmpty = 0; + size_t len = 0; + for (size_t n = 0; n < nrLists; ++n) { + forceList(*lists[n], pos); + auto l = lists[n]->listSize(); + len += l; + if (l) nonEmpty = lists[n]; + } - if (nonEmpty && len == nonEmpty->listSize()) { - v = *nonEmpty; - return; - } + if (nonEmpty && len == nonEmpty->listSize()) { + v = *nonEmpty; + return; + } - mkList(v, len); - auto out = v.listElems(); - for (size_t n = 0, pos = 0; n < nrLists; ++n) { - auto l = lists[n]->listSize(); - if (l) - memcpy(out + pos, lists[n]->listElems(), l * sizeof(Value *)); - pos += l; - } + mkList(v, len); + auto out = v.listElems(); + for (size_t n = 0, pos = 0; n < nrLists; ++n) { + auto l = lists[n]->listSize(); + if (l) memcpy(out + pos, lists[n]->listElems(), l * sizeof(Value*)); + pos += l; + } } +void ExprConcatStrings::eval(EvalState& state, Env& env, Value& v) { + PathSet context; + std::ostringstream s; + NixInt n = 0; + NixFloat nf = 0; -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; + 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); + 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) - 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()); + 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 - mkString(v, s.str(), context); -} + s << state.coerceToString(pos, vTmp, context, false, + firstType == tString); + } - -void ExprPos::eval(EvalState & state, Env & env, Value & v) -{ - state.mkPos(v, &pos); + 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); +} -void EvalState::forceValueDeep(Value & v) -{ - std::set<const Value *> seen; +void EvalState::forceValueDeep(Value& v) { + std::set<const Value*> seen; - std::function<void(Value & v)> recurse; + std::function<void(Value & v)> recurse; - recurse = [&](Value & v) { - if (seen.find(&v) != seen.end()) return; - seen.insert(&v); + recurse = [&](Value& v) { + if (seen.find(&v) != seen.end()) return; + seen.insert(&v); - forceValue(v); + forceValue(v); - if (v.type == tAttrs) { - for (auto & i : *v.attrs) - try { - recurse(*i.value); - } catch (Error & e) { - addErrorPrefix(e, "while evaluating the attribute '%1%' at %2%:\n", i.name, *i.pos); - throw; - } + if (v.type == tAttrs) { + for (auto& i : *v.attrs) try { + recurse(*i.value); + } catch (Error& e) { + addErrorPrefix(e, "while evaluating the attribute '%1%' at %2%:\n", + i.name, *i.pos); + throw; } + } - else if (v.isList()) { - for (size_t n = 0; n < v.listSize(); ++n) - recurse(*v.listElems()[n]); - } - }; + else if (v.isList()) { + for (size_t n = 0; n < v.listSize(); ++n) recurse(*v.listElems()[n]); + } + }; - recurse(v); + 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; +} -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); +NixFloat EvalState::forceFloat(Value& v, const Pos& pos) { + forceValue(v, pos); + if (v.type == tInt) return v.integer; + else if (v.type != tFloat) + throwTypeError("value is %1% while a float was expected, at %2%", v, pos); + return v.fpoint; } - -NixFloat EvalState::forceFloat(Value & v, const Pos & pos) -{ - forceValue(v, pos); - if (v.type == tInt) - return v.integer; - else 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::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(); } - -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); +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); } - -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 string(v.string.s); +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 string(v.string.s); } - -void copyContext(const Value & v, PathSet & context) -{ - if (v.string.context) - for (const char * * p = v.string.context; *p; ++p) - context.insert(*p); +void copyContext(const Value& v, PathSet& context) { + if (v.string.context) + for (const char** p = v.string.context; *p; ++p) context.insert(*p); } - -string EvalState::forceString(Value & v, PathSet & context, const Pos & pos) -{ - string s = forceString(v, pos); - copyContext(v, context); - return s; +string EvalState::forceString(Value& v, PathSet& context, const Pos& pos) { + string s = forceString(v, pos); + copyContext(v, context); + return s; } - -string EvalState::forceStringNoCtx(Value & v, const Pos & pos) -{ - string s = forceString(v, pos); - if (v.string.context) { - 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; +string EvalState::forceStringNoCtx(Value& v, const Pos& pos) { + string s = forceString(v, pos); + if (v.string.context) { + 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->value); - if (i->value->type != tString) return false; - return strcmp(i->value->string.s, "derivation") == 0; +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->value); + if (i->value->type != tString) return false; + return strcmp(i->value->string.s, "derivation") == 0; } +std::optional<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->value, v, v1, pos); + return coerceToString(pos, v1, context, coerceMore, copyToStore); + } -std::optional<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->value, v, v1, pos); - return coerceToString(pos, v1, context, coerceMore, copyToStore); - } - - return {}; + return {}; } -string EvalState::coerceToString(const Pos & pos, Value & v, PathSet & context, - bool coerceMore, bool copyToStore) -{ - forceValue(v); - - string s; +string EvalState::coerceToString(const Pos& pos, Value& v, PathSet& context, + bool coerceMore, bool copyToStore) { + forceValue(v); - 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; - } + string s; - 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->value, context, coerceMore, copyToStore); + 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; } - - if (v.type == tExternal) - 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 == 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()) { - string result; - for (size_t n = 0; n < v.listSize(); ++n) { - result += coerceToString(pos, *v.listElems()[n], - context, coerceMore, copyToStore); - if (n < v.listSize() - 1 - /* !!! not quite correct */ - && (!v.listElems()[n]->isList() || v.listElems()[n]->listSize() != 0)) - result += " "; - } - return result; - } + 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->value, context, coerceMore, copyToStore); + } + + if (v.type == tExternal) + 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 == 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()) { + string result; + for (size_t n = 0; n < v.listSize(); ++n) { + result += coerceToString(pos, *v.listElems()[n], context, coerceMore, + copyToStore); + if (n < v.listSize() - 1 + /* !!! not quite correct */ + && + (!v.listElems()[n]->isList() || v.listElems()[n]->listSize() != 0)) + result += " "; + } + return result; } + } - throwTypeError("cannot coerce %1% to a string, at %2%", v, pos); + throwTypeError("cannot coerce %1% to a string, at %2%", v, pos); } +string EvalState::copyPathToStore(PathSet& context, const Path& path) { + if (nix::isDerivation(path)) + throwEvalError("file names are not allowed to end in '%1%'", drvExtension); -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] != "") + 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; + printMsg(lvlChatty, + format("copied source '%1%' -> '%2%'") % path % dstPath); + } - Path dstPath; - if (srcToStore[path] != "") - 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; - printMsg(lvlChatty, format("copied source '%1%' -> '%2%'") - % path % dstPath); - } - - context.insert(dstPath); - return dstPath; + context.insert(dstPath); + return dstPath; } - -Path EvalState::coerceToPath(const Pos & pos, Value & v, PathSet & context) -{ - string path = coerceToString(pos, v, context, false, false); - if (path == "" || path[0] != '/') - throwEvalError("string '%1%' doesn't represent an absolute path, at %2%", path, pos); - return path; +Path EvalState::coerceToPath(const Pos& pos, Value& v, PathSet& context) { + string path = coerceToString(pos, v, context, false, false); + if (path == "" || 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); -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; - /* !!! 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; - // 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; - // 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; + switch (v1.type) { + case tInt: + return v1.integer == v2.integer; - case tBool: - return v1.boolean == v2.boolean; + case tBool: + return v1.boolean == v2.boolean; - case tString: - return strcmp(v1.string.s, v2.string.s) == 0; + case tString: + return strcmp(v1.string.s, v2.string.s) == 0; - case tPath: - return strcmp(v1.path, v2.path) == 0; + case tPath: + return strcmp(v1.path, v2.path) == 0; - case tNull: - return true; + case tNull: + return true; - case tList1: - case tList2: - case tListN: - if (v1.listSize() != v2.listSize()) return false; - for (size_t n = 0; n < v1.listSize(); ++n) - if (!eqValues(*v1.listElems()[n], *v2.listElems()[n])) return false; - return true; + case tList1: + case tList2: + case tListN: + if (v1.listSize() != v2.listSize()) return false; + for (size_t n = 0; n < v1.listSize(); ++n) + if (!eqValues(*v1.listElems()[n], *v2.listElems()[n])) return false; + return true; - case tAttrs: { - /* 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->value, *j->value); - } + case tAttrs: { + /* 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->value, *j->value); + } - if (v1.attrs->size() != v2.attrs->size()) return false; + if (v1.attrs->size() != v2.attrs->size()) return false; - /* Otherwise, compare the attributes one by one. */ - Bindings::iterator i, j; - for (i = v1.attrs->begin(), j = v2.attrs->begin(); i != v1.attrs->end(); ++i, ++j) - if (i->name != j->name || !eqValues(*i->value, *j->value)) - return false; + /* Otherwise, compare the attributes one by one. */ + Bindings::iterator i, j; + for (i = v1.attrs->begin(), j = v2.attrs->begin(); i != v1.attrs->end(); + ++i, ++j) + if (i->name != j->name || !eqValues(*i->value, *j->value)) return false; - return true; - } + return true; + } - /* Functions are incomparable. */ - case tLambda: - case tPrimOp: - case tPrimOpApp: - return false; + /* Functions are incomparable. */ + case tLambda: + case tPrimOp: + case tPrimOpApp: + return false; - case tExternal: - return *v1.external == *v2.external; + case tExternal: + return *v1.external == *v2.external; - case tFloat: - return v1.fpoint == v2.fpoint; + case tFloat: + return v1.fpoint == v2.fpoint; - default: - throwEvalError("cannot compare %1% with %2%", showType(v1), showType(v2)); - } + default: + throwEvalError("cannot compare %1% with %2%", showType(v1), showType(v2)); + } } -void EvalState::printStats() -{ - bool showStats = getEnv("NIX_SHOW_STATS", "0") != "0"; +void EvalState::printStats() { + bool showStats = getEnv("NIX_SHOW_STATS", "0") != "0"; - struct rusage buf; - getrusage(RUSAGE_SELF, &buf); - float cpuTime = buf.ru_utime.tv_sec + ((float) buf.ru_utime.tv_usec / 1000000); + struct rusage buf; + getrusage(RUSAGE_SELF, &buf); + float cpuTime = buf.ru_utime.tv_sec + ((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); + 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 HAVE_BOEHMGC - GC_word heapSize, totalBytes; - GC_get_heap_usage_safe(&heapSize, 0, 0, 0, &totalBytes); + GC_word heapSize, totalBytes; + GC_get_heap_usage_safe(&heapSize, 0, 0, 0, &totalBytes); #endif - if (showStats) { - auto outPath = getEnv("NIX_SHOW_STATS_PATH","-"); - 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 (showStats) { + auto outPath = getEnv("NIX_SHOW_STATS_PATH", "-"); + 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 HAVE_BOEHMGC - { - auto gc = topObj.object("gc"); - gc.attr("heapSize", heapSize); - gc.attr("totalBytes", totalBytes); - } + { + auto gc = topObj.object("gc"); + gc.attr("heapSize", heapSize); + gc.attr("totalBytes", totalBytes); + } #endif - 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.set()) - obj.attr("name", (const string &) i.first->name); - else - obj.attr("name", nullptr); - if (i.first->pos) { - obj.attr("file", (const 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 string &) i.first.file); - obj.attr("line", i.first.line); - obj.attr("column", i.first.column); - } - obj.attr("count", i.second); - } - } + 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.set()) + obj.attr("name", (const string&)i.first->name); + else + obj.attr("name", nullptr); + if (i.first->pos) { + obj.attr("file", (const string&)i.first->pos.file); + obj.attr("line", i.first->pos.line); + obj.attr("column", i.first->pos.column); + } + obj.attr("count", i.second); } - - if (getEnv("NIX_SHOW_SYMBOLS", "0") != "0") { - auto list = topObj.list("symbols"); - symbols.dump([&](const std::string & s) { list.elem(s); }); + } + { + auto list = topObj.list("attributes"); + for (auto& i : attrSelects) { + auto obj = list.object(); + if (i.first) { + obj.attr("file", (const string&)i.first.file); + obj.attr("line", i.first.line); + obj.attr("column", i.first.column); + } + obj.attr("count", i.second); } + } + } + + if (getEnv("NIX_SHOW_SYMBOLS", "0") != "0") { + auto list = topObj.list("symbols"); + symbols.dump([&](const std::string& s) { list.elem(s); }); } + } } +size_t valueSize(Value& v) { + std::set<const void*> seen; -size_t valueSize(Value & v) -{ - std::set<const void *> seen; - - auto doString = [&](const char * s) -> size_t { - if (seen.find(s) != seen.end()) return 0; - seen.insert(s); - return strlen(s) + 1; - }; - - std::function<size_t(Value & v)> doValue; - std::function<size_t(Env & v)> doEnv; - - doValue = [&](Value & v) -> size_t { - if (seen.find(&v) != seen.end()) return 0; - seen.insert(&v); - - size_t sz = sizeof(Value); - - switch (v.type) { - case tString: - sz += doString(v.string.s); - if (v.string.context) - for (const char * * p = v.string.context; *p; ++p) - sz += doString(*p); - break; - case tPath: - sz += doString(v.path); - break; - case tAttrs: - if (seen.find(v.attrs) == seen.end()) { - seen.insert(v.attrs); - sz += sizeof(Bindings) + sizeof(Attr) * v.attrs->capacity(); - for (auto & i : *v.attrs) - sz += doValue(*i.value); - } - break; - case tList1: - case tList2: - case tListN: - if (seen.find(v.listElems()) == seen.end()) { - seen.insert(v.listElems()); - sz += v.listSize() * sizeof(Value *); - for (size_t n = 0; n < v.listSize(); ++n) - sz += doValue(*v.listElems()[n]); - } - 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; - case tExternal: - if (seen.find(v.external) != seen.end()) break; - seen.insert(v.external); - sz += v.external->valueSize(seen); - break; - default: - ; - } + auto doString = [&](const char* s) -> size_t { + if (seen.find(s) != seen.end()) return 0; + seen.insert(s); + return strlen(s) + 1; + }; - return sz; - }; + std::function<size_t(Value & v)> doValue; + std::function<size_t(Env & v)> doEnv; - doEnv = [&](Env & env) -> size_t { - if (seen.find(&env) != seen.end()) return 0; - seen.insert(&env); + doValue = [&](Value& v) -> size_t { + if (seen.find(&v) != seen.end()) return 0; + seen.insert(&v); - size_t sz = sizeof(Env) + sizeof(Value *) * env.size; + size_t sz = sizeof(Value); - if (env.type != Env::HasWithExpr) - for (size_t i = 0; i < env.size; ++i) - if (env.values[i]) - sz += doValue(*env.values[i]); + switch (v.type) { + case tString: + sz += doString(v.string.s); + if (v.string.context) + for (const char** p = v.string.context; *p; ++p) sz += doString(*p); + break; + case tPath: + sz += doString(v.path); + break; + case tAttrs: + if (seen.find(v.attrs) == seen.end()) { + seen.insert(v.attrs); + sz += sizeof(Bindings) + sizeof(Attr) * v.attrs->capacity(); + for (auto& i : *v.attrs) sz += doValue(*i.value); + } + break; + case tList1: + case tList2: + case tListN: + if (seen.find(v.listElems()) == seen.end()) { + seen.insert(v.listElems()); + sz += v.listSize() * sizeof(Value*); + for (size_t n = 0; n < v.listSize(); ++n) + sz += doValue(*v.listElems()[n]); + } + 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; + case tExternal: + if (seen.find(v.external) != seen.end()) break; + seen.insert(v.external); + sz += v.external->valueSize(seen); + break; + default:; + } - if (env.up) sz += doEnv(*env.up); + return sz; + }; - return sz; - }; + doEnv = [&](Env& env) -> size_t { + if (seen.find(&env) != seen.end()) return 0; + seen.insert(&env); - return doValue(v); -} + size_t sz = sizeof(Env) + sizeof(Value*) * env.size; + if (env.type != Env::HasWithExpr) + for (size_t i = 0; i < env.size; ++i) + if (env.values[i]) sz += doValue(*env.values[i]); -string ExternalValueBase::coerceToString(const Pos & pos, PathSet & context, bool copyMore, bool copyToStore) const -{ - throw TypeError(format("cannot coerce %1% to a string, at %2%") % - showType() % pos); -} + if (env.up) sz += doEnv(*env.up); + return sz; + }; -bool ExternalValueBase::operator==(const ExternalValueBase & b) const -{ - return false; + return doValue(v); } +string ExternalValueBase::coerceToString(const Pos& pos, PathSet& context, + bool copyMore, + bool copyToStore) const { + throw TypeError(format("cannot coerce %1% to a string, at %2%") % showType() % + pos); +} -std::ostream & operator << (std::ostream & str, const ExternalValueBase & v) { - return v.print(str); +bool ExternalValueBase::operator==(const ExternalValueBase& b) const { + return false; } +std::ostream& operator<<(std::ostream& str, const ExternalValueBase& v) { + return v.print(str); +} EvalSettings evalSettings; static GlobalConfig::Register r1(&evalSettings); - -} +} // namespace nix |