diff options
author | Vincent Ambo <tazjin@google.com> | 2020-05-17T15·31+0100 |
---|---|---|
committer | Vincent Ambo <tazjin@google.com> | 2020-05-17T15·31+0100 |
commit | 0f2cf531f705d370321843e5ba9135b2ebdb5d19 (patch) | |
tree | 256feb13963a849ed96e89228fa05454c2a22363 /third_party/nix/src/libexpr | |
parent | 65a1aae98ce5a237c9643e639e550c8b0c0be7f1 (diff) |
style(3p/nix): Reformat project in Google C++ style r/740
Reformatted with: fd . -e hh -e cc | xargs clang-format -i
Diffstat (limited to 'third_party/nix/src/libexpr')
31 files changed, 5888 insertions, 6304 deletions
diff --git a/third_party/nix/src/libexpr/attr-path.cc b/third_party/nix/src/libexpr/attr-path.cc index b0f80db32a88..4eb44ec3357d 100644 --- a/third_party/nix/src/libexpr/attr-path.cc +++ b/third_party/nix/src/libexpr/attr-path.cc @@ -2,95 +2,92 @@ #include "eval-inline.hh" #include "util.hh" - namespace nix { - -static Strings parseAttrPath(const string & s) -{ - Strings res; - string cur; - string::const_iterator i = s.begin(); - while (i != s.end()) { - if (*i == '.') { - res.push_back(cur); - cur.clear(); - } else if (*i == '"') { - ++i; - while (1) { - if (i == s.end()) - throw Error(format("missing closing quote in selection path '%1%'") % s); - if (*i == '"') break; - cur.push_back(*i++); - } - } else - cur.push_back(*i); - ++i; - } - if (!cur.empty()) res.push_back(cur); - return res; +static Strings parseAttrPath(const string& s) { + Strings res; + string cur; + string::const_iterator i = s.begin(); + while (i != s.end()) { + if (*i == '.') { + res.push_back(cur); + cur.clear(); + } else if (*i == '"') { + ++i; + while (1) { + if (i == s.end()) + throw Error(format("missing closing quote in selection path '%1%'") % + s); + if (*i == '"') break; + cur.push_back(*i++); + } + } else + cur.push_back(*i); + ++i; + } + if (!cur.empty()) res.push_back(cur); + return res; } +Value* findAlongAttrPath(EvalState& state, const string& attrPath, + Bindings& autoArgs, Value& vIn) { + Strings tokens = parseAttrPath(attrPath); + + Error attrError = + Error(format("attribute selection path '%1%' does not match expression") % + attrPath); + + Value* v = &vIn; + + for (auto& attr : tokens) { + /* Is i an index (integer) or a normal attribute name? */ + enum { apAttr, apIndex } apType = apAttr; + unsigned int attrIndex; + if (string2Int(attr, attrIndex)) apType = apIndex; + + /* Evaluate the expression. */ + Value* vNew = state.allocValue(); + state.autoCallFunction(autoArgs, *v, *vNew); + v = vNew; + state.forceValue(*v); + + /* It should evaluate to either a set or an expression, + according to what is specified in the attrPath. */ + + if (apType == apAttr) { + if (v->type != tAttrs) + throw TypeError(format("the expression selected by the selection path " + "'%1%' should be a set but is %2%") % + attrPath % showType(*v)); + + if (attr.empty()) + throw Error(format("empty attribute name in selection path '%1%'") % + attrPath); + + Bindings::iterator a = v->attrs->find(state.symbols.create(attr)); + if (a == v->attrs->end()) + throw Error( + format("attribute '%1%' in selection path '%2%' not found") % attr % + attrPath); + v = &*a->value; + } -Value * findAlongAttrPath(EvalState & state, const string & attrPath, - Bindings & autoArgs, Value & vIn) -{ - Strings tokens = parseAttrPath(attrPath); - - Error attrError = - Error(format("attribute selection path '%1%' does not match expression") % attrPath); - - Value * v = &vIn; - - for (auto & attr : tokens) { - - /* Is i an index (integer) or a normal attribute name? */ - enum { apAttr, apIndex } apType = apAttr; - unsigned int attrIndex; - if (string2Int(attr, attrIndex)) apType = apIndex; - - /* Evaluate the expression. */ - Value * vNew = state.allocValue(); - state.autoCallFunction(autoArgs, *v, *vNew); - v = vNew; - state.forceValue(*v); - - /* It should evaluate to either a set or an expression, - according to what is specified in the attrPath. */ - - if (apType == apAttr) { - - if (v->type != tAttrs) - throw TypeError( - format("the expression selected by the selection path '%1%' should be a set but is %2%") - % attrPath % showType(*v)); - - if (attr.empty()) - throw Error(format("empty attribute name in selection path '%1%'") % attrPath); - - Bindings::iterator a = v->attrs->find(state.symbols.create(attr)); - if (a == v->attrs->end()) - throw Error(format("attribute '%1%' in selection path '%2%' not found") % attr % attrPath); - v = &*a->value; - } - - else if (apType == apIndex) { - - if (!v->isList()) - throw TypeError( - format("the expression selected by the selection path '%1%' should be a list but is %2%") - % attrPath % showType(*v)); - - if (attrIndex >= v->listSize()) - throw Error(format("list index %1% in selection path '%2%' is out of range") % attrIndex % attrPath); + else if (apType == apIndex) { + if (!v->isList()) + throw TypeError(format("the expression selected by the selection path " + "'%1%' should be a list but is %2%") % + attrPath % showType(*v)); - v = v->listElems()[attrIndex]; - } + if (attrIndex >= v->listSize()) + throw Error( + format("list index %1% in selection path '%2%' is out of range") % + attrIndex % attrPath); + v = v->listElems()[attrIndex]; } + } - return v; + return v; } - -} +} // namespace nix diff --git a/third_party/nix/src/libexpr/attr-path.hh b/third_party/nix/src/libexpr/attr-path.hh index 46a341950939..9c38cdde049f 100644 --- a/third_party/nix/src/libexpr/attr-path.hh +++ b/third_party/nix/src/libexpr/attr-path.hh @@ -1,13 +1,12 @@ #pragma once -#include "eval.hh" - -#include <string> #include <map> +#include <string> +#include "eval.hh" namespace nix { -Value * findAlongAttrPath(EvalState & state, const string & attrPath, - Bindings & autoArgs, Value & vIn); +Value* findAlongAttrPath(EvalState& state, const string& attrPath, + Bindings& autoArgs, Value& vIn); } diff --git a/third_party/nix/src/libexpr/attr-set.cc b/third_party/nix/src/libexpr/attr-set.cc index 0785897d2513..cdca3953e653 100644 --- a/third_party/nix/src/libexpr/attr-set.cc +++ b/third_party/nix/src/libexpr/attr-set.cc @@ -1,52 +1,40 @@ #include "attr-set.hh" -#include "eval-inline.hh" - #include <algorithm> - +#include "eval-inline.hh" namespace nix { - /* Allocate a new array of attributes for an attribute set with a specific capacity. The space is implicitly reserved after the Bindings structure. */ -Bindings * EvalState::allocBindings(size_t capacity) -{ - if (capacity > std::numeric_limits<Bindings::size_t>::max()) - throw Error("attribute set of size %d is too big", capacity); - return new (allocBytes(sizeof(Bindings) + sizeof(Attr) * capacity)) Bindings((Bindings::size_t) capacity); +Bindings* EvalState::allocBindings(size_t capacity) { + if (capacity > std::numeric_limits<Bindings::size_t>::max()) + throw Error("attribute set of size %d is too big", capacity); + return new (allocBytes(sizeof(Bindings) + sizeof(Attr) * capacity)) + Bindings((Bindings::size_t)capacity); } - -void EvalState::mkAttrs(Value & v, size_t capacity) -{ - if (capacity == 0) { - v = vEmptySet; - return; - } - clearValue(v); - v.type = tAttrs; - v.attrs = allocBindings(capacity); - nrAttrsets++; - nrAttrsInAttrsets += capacity; +void EvalState::mkAttrs(Value& v, size_t capacity) { + if (capacity == 0) { + v = vEmptySet; + return; + } + clearValue(v); + v.type = tAttrs; + v.attrs = allocBindings(capacity); + nrAttrsets++; + nrAttrsInAttrsets += capacity; } - /* Create a new attribute named 'name' on an existing attribute set stored in 'vAttrs' and return the newly allocated Value which is associated with this attribute. */ -Value * EvalState::allocAttr(Value & vAttrs, const Symbol & name) -{ - Value * v = allocValue(); - vAttrs.attrs->push_back(Attr(name, v)); - return v; +Value* EvalState::allocAttr(Value& vAttrs, const Symbol& name) { + Value* v = allocValue(); + vAttrs.attrs->push_back(Attr(name, v)); + return v; } +void Bindings::sort() { std::sort(begin(), end()); } -void Bindings::sort() -{ - std::sort(begin(), end()); -} - - -} +} // namespace nix diff --git a/third_party/nix/src/libexpr/attr-set.hh b/third_party/nix/src/libexpr/attr-set.hh index 3119a1848af2..f56261ad6a5a 100644 --- a/third_party/nix/src/libexpr/attr-set.hh +++ b/third_party/nix/src/libexpr/attr-set.hh @@ -1,95 +1,80 @@ #pragma once +#include <algorithm> #include "nixexpr.hh" #include "symbol-table.hh" -#include <algorithm> - namespace nix { - class EvalState; struct Value; /* Map one attribute name to its value. */ -struct Attr -{ - Symbol name; - Value * value; - Pos * pos; - Attr(Symbol name, Value * value, Pos * pos = &noPos) - : name(name), value(value), pos(pos) { }; - Attr() : pos(&noPos) { }; - bool operator < (const Attr & a) const - { - return name < a.name; - } +struct Attr { + Symbol name; + Value* value; + Pos* pos; + Attr(Symbol name, Value* value, Pos* pos = &noPos) + : name(name), value(value), pos(pos){}; + Attr() : pos(&noPos){}; + bool operator<(const Attr& a) const { return name < a.name; } }; /* Bindings contains all the attributes of an attribute set. It is defined by its size and its capacity, the capacity being the number of Attr elements allocated after this structure, while the size corresponds to the number of elements already inserted in this structure. */ -class Bindings -{ -public: - typedef uint32_t size_t; - -private: - size_t size_, capacity_; - Attr attrs[0]; - - Bindings(size_t capacity) : size_(0), capacity_(capacity) { } - Bindings(const Bindings & bindings) = delete; - -public: - size_t size() const { return size_; } - - bool empty() const { return !size_; } - - typedef Attr * iterator; - - void push_back(const Attr & attr) - { - assert(size_ < capacity_); - attrs[size_++] = attr; - } - - iterator find(const Symbol & name) - { - Attr key(name, 0); - iterator i = std::lower_bound(begin(), end(), key); - if (i != end() && i->name == name) return i; - return end(); - } - - iterator begin() { return &attrs[0]; } - iterator end() { return &attrs[size_]; } - - Attr & operator[](size_t pos) - { - return attrs[pos]; - } - - void sort(); - - size_t capacity() { return capacity_; } - - /* Returns the attributes in lexicographically sorted order. */ - std::vector<const Attr *> lexicographicOrder() const - { - std::vector<const Attr *> res; - res.reserve(size_); - for (size_t n = 0; n < size_; n++) - res.emplace_back(&attrs[n]); - std::sort(res.begin(), res.end(), [](const Attr * a, const Attr * b) { - return (const string &) a->name < (const string &) b->name; - }); - return res; - } - - friend class EvalState; -}; +class Bindings { + public: + typedef uint32_t size_t; + + private: + size_t size_, capacity_; + Attr attrs[0]; + + Bindings(size_t capacity) : size_(0), capacity_(capacity) {} + Bindings(const Bindings& bindings) = delete; + + public: + size_t size() const { return size_; } + + bool empty() const { return !size_; } + typedef Attr* iterator; + + void push_back(const Attr& attr) { + assert(size_ < capacity_); + attrs[size_++] = attr; + } + + iterator find(const Symbol& name) { + Attr key(name, 0); + iterator i = std::lower_bound(begin(), end(), key); + if (i != end() && i->name == name) return i; + return end(); + } + + iterator begin() { return &attrs[0]; } + iterator end() { return &attrs[size_]; } + + Attr& operator[](size_t pos) { return attrs[pos]; } + + void sort(); + + size_t capacity() { return capacity_; } + + /* Returns the attributes in lexicographically sorted order. */ + std::vector<const Attr*> lexicographicOrder() const { + std::vector<const Attr*> res; + res.reserve(size_); + for (size_t n = 0; n < size_; n++) res.emplace_back(&attrs[n]); + std::sort(res.begin(), res.end(), [](const Attr* a, const Attr* b) { + return (const string&)a->name < (const string&)b->name; + }); + return res; + } + + friend class EvalState; +}; -} +} // namespace nix diff --git a/third_party/nix/src/libexpr/common-eval-args.cc b/third_party/nix/src/libexpr/common-eval-args.cc index 13950ab8d169..1e2fe891f544 100644 --- a/third_party/nix/src/libexpr/common-eval-args.cc +++ b/third_party/nix/src/libexpr/common-eval-args.cc @@ -1,59 +1,61 @@ #include "common-eval-args.hh" -#include "shared.hh" #include "download.hh" -#include "util.hh" #include "eval.hh" +#include "shared.hh" +#include "util.hh" namespace nix { -MixEvalArgs::MixEvalArgs() -{ - mkFlag() - .longName("arg") - .description("argument to be passed to Nix functions") - .labels({"name", "expr"}) - .handler([&](std::vector<std::string> ss) { autoArgs[ss[0]] = 'E' + ss[1]; }); +MixEvalArgs::MixEvalArgs() { + mkFlag() + .longName("arg") + .description("argument to be passed to Nix functions") + .labels({"name", "expr"}) + .handler( + [&](std::vector<std::string> ss) { autoArgs[ss[0]] = 'E' + ss[1]; }); - mkFlag() - .longName("argstr") - .description("string-valued argument to be passed to Nix functions") - .labels({"name", "string"}) - .handler([&](std::vector<std::string> ss) { autoArgs[ss[0]] = 'S' + ss[1]; }); + mkFlag() + .longName("argstr") + .description("string-valued argument to be passed to Nix functions") + .labels({"name", "string"}) + .handler( + [&](std::vector<std::string> ss) { autoArgs[ss[0]] = 'S' + ss[1]; }); - mkFlag() - .shortName('I') - .longName("include") - .description("add a path to the list of locations used to look up <...> file names") - .label("path") - .handler([&](std::string s) { searchPath.push_back(s); }); + mkFlag() + .shortName('I') + .longName("include") + .description( + "add a path to the list of locations used to look up <...> file " + "names") + .label("path") + .handler([&](std::string s) { searchPath.push_back(s); }); } -Bindings * MixEvalArgs::getAutoArgs(EvalState & state) -{ - Bindings * res = state.allocBindings(autoArgs.size()); - for (auto & i : autoArgs) { - Value * v = state.allocValue(); - if (i.second[0] == 'E') - state.mkThunk_(*v, state.parseExprFromString(string(i.second, 1), absPath("."))); - else - mkString(*v, string(i.second, 1)); - res->push_back(Attr(state.symbols.create(i.first), v)); - } - res->sort(); - return res; +Bindings* MixEvalArgs::getAutoArgs(EvalState& state) { + Bindings* res = state.allocBindings(autoArgs.size()); + for (auto& i : autoArgs) { + Value* v = state.allocValue(); + if (i.second[0] == 'E') + state.mkThunk_( + *v, state.parseExprFromString(string(i.second, 1), absPath("."))); + else + mkString(*v, string(i.second, 1)); + res->push_back(Attr(state.symbols.create(i.first), v)); + } + res->sort(); + return res; } -Path lookupFileArg(EvalState & state, string s) -{ - if (isUri(s)) { - CachedDownloadRequest request(s); - request.unpack = true; - return getDownloader()->downloadCached(state.store, request).path; - } else if (s.size() > 2 && s.at(0) == '<' && s.at(s.size() - 1) == '>') { - Path p = s.substr(1, s.size() - 2); - return state.findFile(p); - } else - return absPath(s); +Path lookupFileArg(EvalState& state, string s) { + if (isUri(s)) { + CachedDownloadRequest request(s); + request.unpack = true; + return getDownloader()->downloadCached(state.store, request).path; + } else if (s.size() > 2 && s.at(0) == '<' && s.at(s.size() - 1) == '>') { + Path p = s.substr(1, s.size() - 2); + return state.findFile(p); + } else + return absPath(s); } -} +} // namespace nix diff --git a/third_party/nix/src/libexpr/common-eval-args.hh b/third_party/nix/src/libexpr/common-eval-args.hh index be7fda783783..9663d40c148c 100644 --- a/third_party/nix/src/libexpr/common-eval-args.hh +++ b/third_party/nix/src/libexpr/common-eval-args.hh @@ -8,19 +8,17 @@ class Store; class EvalState; class Bindings; -struct MixEvalArgs : virtual Args -{ - MixEvalArgs(); +struct MixEvalArgs : virtual Args { + MixEvalArgs(); - Bindings * getAutoArgs(EvalState & state); + Bindings* getAutoArgs(EvalState& state); - Strings searchPath; + Strings searchPath; -private: - - std::map<std::string, std::string> autoArgs; + private: + std::map<std::string, std::string> autoArgs; }; -Path lookupFileArg(EvalState & state, string s); +Path lookupFileArg(EvalState& state, string s); -} +} // namespace nix diff --git a/third_party/nix/src/libexpr/eval-inline.hh b/third_party/nix/src/libexpr/eval-inline.hh index c27116e3b448..37105e7e398d 100644 --- a/third_party/nix/src/libexpr/eval-inline.hh +++ b/third_party/nix/src/libexpr/eval-inline.hh @@ -2,94 +2,81 @@ #include "eval.hh" -#define LocalNoInline(f) static f __attribute__((noinline)); f -#define LocalNoInlineNoReturn(f) static f __attribute__((noinline, noreturn)); f +#define LocalNoInline(f) \ + static f __attribute__((noinline)); \ + f +#define LocalNoInlineNoReturn(f) \ + static f __attribute__((noinline, noreturn)); \ + f namespace nix { -LocalNoInlineNoReturn(void throwEvalError(const char * s, const Pos & pos)) -{ - throw EvalError(format(s) % pos); +LocalNoInlineNoReturn(void throwEvalError(const char* s, const Pos& pos)) { + throw EvalError(format(s) % pos); } -LocalNoInlineNoReturn(void throwTypeError(const char * s, const Value & v)) -{ - throw TypeError(format(s) % showType(v)); +LocalNoInlineNoReturn(void throwTypeError(const char* s, const Value& v)) { + throw TypeError(format(s) % showType(v)); } - -LocalNoInlineNoReturn(void throwTypeError(const char * s, const Value & v, const Pos & pos)) -{ - throw TypeError(format(s) % showType(v) % pos); +LocalNoInlineNoReturn(void throwTypeError(const char* s, const Value& v, + const Pos& pos)) { + throw TypeError(format(s) % showType(v) % pos); } - -void EvalState::forceValue(Value & v, const Pos & pos) -{ - if (v.type == tThunk) { - Env * env = v.thunk.env; - Expr * expr = v.thunk.expr; - try { - v.type = tBlackhole; - //checkInterrupt(); - expr->eval(*this, *env, v); - } catch (...) { - v.type = tThunk; - v.thunk.env = env; - v.thunk.expr = expr; - throw; - } +void EvalState::forceValue(Value& v, const Pos& pos) { + if (v.type == tThunk) { + Env* env = v.thunk.env; + Expr* expr = v.thunk.expr; + try { + v.type = tBlackhole; + // checkInterrupt(); + expr->eval(*this, *env, v); + } catch (...) { + v.type = tThunk; + v.thunk.env = env; + v.thunk.expr = expr; + throw; } - else if (v.type == tApp) - callFunction(*v.app.left, *v.app.right, v, noPos); - else if (v.type == tBlackhole) - throwEvalError("infinite recursion encountered, at %1%", pos); + } else if (v.type == tApp) + callFunction(*v.app.left, *v.app.right, v, noPos); + else if (v.type == tBlackhole) + throwEvalError("infinite recursion encountered, at %1%", pos); } - -inline void EvalState::forceAttrs(Value & v) -{ - forceValue(v); - if (v.type != tAttrs) - throwTypeError("value is %1% while a set was expected", v); +inline void EvalState::forceAttrs(Value& v) { + forceValue(v); + if (v.type != tAttrs) + throwTypeError("value is %1% while a set was expected", v); } - -inline void EvalState::forceAttrs(Value & v, const Pos & pos) -{ - forceValue(v); - if (v.type != tAttrs) - throwTypeError("value is %1% while a set was expected, at %2%", v, pos); +inline void EvalState::forceAttrs(Value& v, const Pos& pos) { + forceValue(v); + if (v.type != tAttrs) + throwTypeError("value is %1% while a set was expected, at %2%", v, pos); } - -inline void EvalState::forceList(Value & v) -{ - forceValue(v); - if (!v.isList()) - throwTypeError("value is %1% while a list was expected", v); +inline void EvalState::forceList(Value& v) { + forceValue(v); + if (!v.isList()) throwTypeError("value is %1% while a list was expected", v); } - -inline void EvalState::forceList(Value & v, const Pos & pos) -{ - forceValue(v); - if (!v.isList()) - throwTypeError("value is %1% while a list was expected, at %2%", v, pos); +inline void EvalState::forceList(Value& v, const Pos& pos) { + forceValue(v); + if (!v.isList()) + throwTypeError("value is %1% while a list was expected, at %2%", v, pos); } /* Note: Various places expect the allocated memory to be zeroed. */ -inline void * allocBytes(size_t n) -{ - void * p; +inline void* allocBytes(size_t n) { + void* p; #if HAVE_BOEHMGC - p = GC_MALLOC(n); + p = GC_MALLOC(n); #else - p = calloc(n, 1); + p = calloc(n, 1); #endif - if (!p) throw std::bad_alloc(); - return p; + if (!p) throw std::bad_alloc(); + return p; } - -} +} // namespace nix 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 diff --git a/third_party/nix/src/libexpr/eval.hh b/third_party/nix/src/libexpr/eval.hh index 8126e4ea50ff..74777e48fa95 100644 --- a/third_party/nix/src/libexpr/eval.hh +++ b/third_party/nix/src/libexpr/eval.hh @@ -1,363 +1,357 @@ #pragma once -#include "attr-set.hh" -#include "value.hh" -#include "nixexpr.hh" -#include "symbol-table.hh" -#include "hash.hh" -#include "config.hh" - #include <map> #include <optional> #include <unordered_map> - +#include "attr-set.hh" +#include "config.hh" +#include "hash.hh" +#include "nixexpr.hh" +#include "symbol-table.hh" +#include "value.hh" namespace nix { - class Store; class EvalState; enum RepairFlag : bool; +typedef void (*PrimOpFun)(EvalState& state, const Pos& pos, Value** args, + Value& v); -typedef void (* PrimOpFun) (EvalState & state, const Pos & pos, Value * * args, Value & v); - - -struct PrimOp -{ - PrimOpFun fun; - size_t arity; - Symbol name; - PrimOp(PrimOpFun fun, size_t arity, Symbol name) - : fun(fun), arity(arity), name(name) { } +struct PrimOp { + PrimOpFun fun; + size_t arity; + Symbol name; + PrimOp(PrimOpFun fun, size_t arity, Symbol name) + : fun(fun), arity(arity), name(name) {} }; - -struct Env -{ - Env * up; - unsigned short size; // used by ‘valueSize’ - unsigned short prevWith:14; // nr of levels up to next `with' environment - enum { Plain = 0, HasWithExpr, HasWithAttrs } type:2; - Value * values[0]; +struct Env { + Env* up; + unsigned short size; // used by ‘valueSize’ + unsigned short prevWith : 14; // nr of levels up to next `with' environment + enum { Plain = 0, HasWithExpr, HasWithAttrs } type : 2; + Value* values[0]; }; +Value& mkString(Value& v, const string& s, const PathSet& context = PathSet()); -Value & mkString(Value & v, const string & s, const PathSet & context = PathSet()); - -void copyContext(const Value & v, PathSet & context); - +void copyContext(const Value& v, PathSet& context); /* Cache for calls to addToStore(); maps source paths to the store paths. */ typedef std::map<Path, Path> SrcToStore; - -std::ostream & operator << (std::ostream & str, const Value & v); - +std::ostream& operator<<(std::ostream& str, const Value& v); typedef std::pair<std::string, std::string> SearchPathElem; typedef std::list<SearchPathElem> SearchPath; - /* Initialise the Boehm GC, if applicable. */ void initGC(); +class EvalState { + public: + SymbolTable symbols; -class EvalState -{ -public: - SymbolTable symbols; - - const Symbol sWith, sOutPath, sDrvPath, sType, sMeta, sName, sValue, - sSystem, sOverrides, sOutputs, sOutputName, sIgnoreNulls, - sFile, sLine, sColumn, sFunctor, sToString, - sRight, sWrong, sStructuredAttrs, sBuilder, sArgs, - sOutputHash, sOutputHashAlgo, sOutputHashMode; - Symbol sDerivationNix; + const Symbol sWith, sOutPath, sDrvPath, sType, sMeta, sName, sValue, sSystem, + sOverrides, sOutputs, sOutputName, sIgnoreNulls, sFile, sLine, sColumn, + sFunctor, sToString, sRight, sWrong, sStructuredAttrs, sBuilder, sArgs, + sOutputHash, sOutputHashAlgo, sOutputHashMode; + Symbol sDerivationNix; - /* If set, force copying files to the Nix store even if they - already exist there. */ - RepairFlag repair; + /* If set, force copying files to the Nix store even if they + already exist there. */ + RepairFlag repair; - /* The allowed filesystem paths in restricted or pure evaluation - mode. */ - std::optional<PathSet> allowedPaths; + /* The allowed filesystem paths in restricted or pure evaluation + mode. */ + std::optional<PathSet> allowedPaths; - Value vEmptySet; + Value vEmptySet; - const ref<Store> store; + const ref<Store> store; -private: - SrcToStore srcToStore; + private: + SrcToStore srcToStore; - /* A cache from path names to parse trees. */ + /* A cache from path names to parse trees. */ #if HAVE_BOEHMGC - typedef std::map<Path, Expr *, std::less<Path>, traceable_allocator<std::pair<const Path, Expr *> > > FileParseCache; + typedef std::map<Path, Expr*, std::less<Path>, + traceable_allocator<std::pair<const Path, Expr*>>> + FileParseCache; #else - typedef std::map<Path, Expr *> FileParseCache; + typedef std::map<Path, Expr*> FileParseCache; #endif - FileParseCache fileParseCache; + FileParseCache fileParseCache; - /* A cache from path names to values. */ + /* A cache from path names to values. */ #if HAVE_BOEHMGC - typedef std::map<Path, Value, std::less<Path>, traceable_allocator<std::pair<const Path, Value> > > FileEvalCache; + typedef std::map<Path, Value, std::less<Path>, + traceable_allocator<std::pair<const Path, Value>>> + FileEvalCache; #else - typedef std::map<Path, Value> FileEvalCache; + typedef std::map<Path, Value> FileEvalCache; #endif - FileEvalCache fileEvalCache; + FileEvalCache fileEvalCache; + + SearchPath searchPath; + + std::map<std::string, std::pair<bool, std::string>> searchPathResolved; + + /* Cache used by checkSourcePath(). */ + std::unordered_map<Path, Path> resolvedPaths; + + public: + EvalState(const Strings& _searchPath, ref<Store> store); + ~EvalState(); + + void addToSearchPath(const string& s); + + SearchPath getSearchPath() { return searchPath; } + + Path checkSourcePath(const Path& path); + + void checkURI(const std::string& uri); + + /* When using a diverted store and 'path' is in the Nix store, map + 'path' to the diverted location (e.g. /nix/store/foo is mapped + to /home/alice/my-nix/nix/store/foo). However, this is only + done if the context is not empty, since otherwise we're + probably trying to read from the actual /nix/store. This is + intended to distinguish between import-from-derivation and + sources stored in the actual /nix/store. */ + Path toRealPath(const Path& path, const PathSet& context); - SearchPath searchPath; + /* Parse a Nix expression from the specified file. */ + Expr* parseExprFromFile(const Path& path); + Expr* parseExprFromFile(const Path& path, StaticEnv& staticEnv); - std::map<std::string, std::pair<bool, std::string>> searchPathResolved; + /* Parse a Nix expression from the specified string. */ + Expr* parseExprFromString(const string& s, const Path& basePath, + StaticEnv& staticEnv); + Expr* parseExprFromString(const string& s, const Path& basePath); - /* Cache used by checkSourcePath(). */ - std::unordered_map<Path, Path> resolvedPaths; + Expr* parseStdin(); -public: + /* Evaluate an expression read from the given file to normal + form. */ + void evalFile(const Path& path, Value& v); - EvalState(const Strings & _searchPath, ref<Store> store); - ~EvalState(); + void resetFileCache(); - void addToSearchPath(const string & s); + /* Look up a file in the search path. */ + Path findFile(const string& path); + Path findFile(SearchPath& searchPath, const string& path, + const Pos& pos = noPos); - SearchPath getSearchPath() { return searchPath; } + /* If the specified search path element is a URI, download it. */ + std::pair<bool, std::string> resolveSearchPathElem( + const SearchPathElem& elem); - Path checkSourcePath(const Path & path); + /* Evaluate an expression to normal form, storing the result in + value `v'. */ + void eval(Expr* e, Value& v); - void checkURI(const std::string & uri); + /* Evaluation the expression, then verify that it has the expected + type. */ + inline bool evalBool(Env& env, Expr* e); + inline bool evalBool(Env& env, Expr* e, const Pos& pos); + inline void evalAttrs(Env& env, Expr* e, Value& v); - /* When using a diverted store and 'path' is in the Nix store, map - 'path' to the diverted location (e.g. /nix/store/foo is mapped - to /home/alice/my-nix/nix/store/foo). However, this is only - done if the context is not empty, since otherwise we're - probably trying to read from the actual /nix/store. This is - intended to distinguish between import-from-derivation and - sources stored in the actual /nix/store. */ - Path toRealPath(const Path & path, const PathSet & context); + /* If `v' is a thunk, enter it and overwrite `v' with the result + of the evaluation of the thunk. If `v' is a delayed function + application, call the function and overwrite `v' with the + result. Otherwise, this is a no-op. */ + inline void forceValue(Value& v, const Pos& pos = noPos); - /* Parse a Nix expression from the specified file. */ - Expr * parseExprFromFile(const Path & path); - Expr * parseExprFromFile(const Path & path, StaticEnv & staticEnv); + /* Force a value, then recursively force list elements and + attributes. */ + void forceValueDeep(Value& v); - /* Parse a Nix expression from the specified string. */ - Expr * parseExprFromString(const string & s, const Path & basePath, StaticEnv & staticEnv); - Expr * parseExprFromString(const string & s, const Path & basePath); + /* Force `v', and then verify that it has the expected type. */ + NixInt forceInt(Value& v, const Pos& pos); + NixFloat forceFloat(Value& v, const Pos& pos); + bool forceBool(Value& v, const Pos& pos); + inline void forceAttrs(Value& v); + inline void forceAttrs(Value& v, const Pos& pos); + inline void forceList(Value& v); + inline void forceList(Value& v, const Pos& pos); + void forceFunction(Value& v, const Pos& pos); // either lambda or primop + string forceString(Value& v, const Pos& pos = noPos); + string forceString(Value& v, PathSet& context, const Pos& pos = noPos); + string forceStringNoCtx(Value& v, const Pos& pos = noPos); - Expr * parseStdin(); + /* Return true iff the value `v' denotes a derivation (i.e. a + set with attribute `type = "derivation"'). */ + bool isDerivation(Value& v); - /* Evaluate an expression read from the given file to normal - form. */ - void evalFile(const Path & path, Value & v); + std::optional<string> tryAttrsToString(const Pos& pos, Value& v, + PathSet& context, + bool coerceMore = false, + bool copyToStore = true); - void resetFileCache(); + /* String coercion. Converts strings, paths and derivations to a + string. If `coerceMore' is set, also converts nulls, integers, + booleans and lists to a string. If `copyToStore' is set, + referenced paths are copied to the Nix store as a side effect. */ + string coerceToString(const Pos& pos, Value& v, PathSet& context, + bool coerceMore = false, bool copyToStore = true); - /* Look up a file in the search path. */ - Path findFile(const string & path); - Path findFile(SearchPath & searchPath, const string & path, const Pos & pos = noPos); + string copyPathToStore(PathSet& context, const Path& path); - /* If the specified search path element is a URI, download it. */ - std::pair<bool, std::string> resolveSearchPathElem(const SearchPathElem & elem); + /* Path coercion. Converts strings, paths and derivations to a + path. The result is guaranteed to be a canonicalised, absolute + path. Nothing is copied to the store. */ + Path coerceToPath(const Pos& pos, Value& v, PathSet& context); - /* Evaluate an expression to normal form, storing the result in - value `v'. */ - void eval(Expr * e, Value & v); + public: + /* The base environment, containing the builtin functions and + values. */ + Env& baseEnv; - /* Evaluation the expression, then verify that it has the expected - type. */ - inline bool evalBool(Env & env, Expr * e); - inline bool evalBool(Env & env, Expr * e, const Pos & pos); - inline void evalAttrs(Env & env, Expr * e, Value & v); + /* The same, but used during parsing to resolve variables. */ + StaticEnv staticBaseEnv; // !!! should be private + + private: + unsigned int baseEnvDispl = 0; + + void createBaseEnv(); - /* If `v' is a thunk, enter it and overwrite `v' with the result - of the evaluation of the thunk. If `v' is a delayed function - application, call the function and overwrite `v' with the - result. Otherwise, this is a no-op. */ - inline void forceValue(Value & v, const Pos & pos = noPos); + Value* addConstant(const string& name, Value& v); - /* Force a value, then recursively force list elements and - attributes. */ - void forceValueDeep(Value & v); + Value* addPrimOp(const string& name, size_t arity, PrimOpFun primOp); - /* Force `v', and then verify that it has the expected type. */ - NixInt forceInt(Value & v, const Pos & pos); - NixFloat forceFloat(Value & v, const Pos & pos); - bool forceBool(Value & v, const Pos & pos); - inline void forceAttrs(Value & v); - inline void forceAttrs(Value & v, const Pos & pos); - inline void forceList(Value & v); - inline void forceList(Value & v, const Pos & pos); - void forceFunction(Value & v, const Pos & pos); // either lambda or primop - string forceString(Value & v, const Pos & pos = noPos); - string forceString(Value & v, PathSet & context, const Pos & pos = noPos); - string forceStringNoCtx(Value & v, const Pos & pos = noPos); + public: + Value& getBuiltin(const string& name); - /* Return true iff the value `v' denotes a derivation (i.e. a - set with attribute `type = "derivation"'). */ - bool isDerivation(Value & v); + private: + inline Value* lookupVar(Env* env, const ExprVar& var, bool noEval); - std::optional<string> tryAttrsToString(const Pos & pos, Value & v, - PathSet & context, bool coerceMore = false, bool copyToStore = true); + friend struct ExprVar; + friend struct ExprAttrs; + friend struct ExprLet; - /* String coercion. Converts strings, paths and derivations to a - string. If `coerceMore' is set, also converts nulls, integers, - booleans and lists to a string. If `copyToStore' is set, - referenced paths are copied to the Nix store as a side effect. */ - string coerceToString(const Pos & pos, Value & v, PathSet & context, - bool coerceMore = false, bool copyToStore = true); + Expr* parse(const char* text, const Path& path, const Path& basePath, + StaticEnv& staticEnv); - string copyPathToStore(PathSet & context, const Path & path); + public: + /* Do a deep equality test between two values. That is, list + elements and attributes are compared recursively. */ + bool eqValues(Value& v1, Value& v2); - /* Path coercion. Converts strings, paths and derivations to a - path. The result is guaranteed to be a canonicalised, absolute - path. Nothing is copied to the store. */ - Path coerceToPath(const Pos & pos, Value & v, PathSet & context); + bool isFunctor(Value& fun); -public: + void callFunction(Value& fun, Value& arg, Value& v, const Pos& pos); + void callPrimOp(Value& fun, Value& arg, Value& v, const Pos& pos); - /* The base environment, containing the builtin functions and - values. */ - Env & baseEnv; + /* Automatically call a function for which each argument has a + default value or has a binding in the `args' map. */ + void autoCallFunction(Bindings& args, Value& fun, Value& res); - /* The same, but used during parsing to resolve variables. */ - StaticEnv staticBaseEnv; // !!! should be private + /* Allocation primitives. */ + Value* allocValue(); + Env& allocEnv(size_t size); -private: + Value* allocAttr(Value& vAttrs, const Symbol& name); - unsigned int baseEnvDispl = 0; + Bindings* allocBindings(size_t capacity); - void createBaseEnv(); + void mkList(Value& v, size_t length); + void mkAttrs(Value& v, size_t capacity); + void mkThunk_(Value& v, Expr* expr); + void mkPos(Value& v, Pos* pos); - Value * addConstant(const string & name, Value & v); + void concatLists(Value& v, size_t nrLists, Value** lists, const Pos& pos); - Value * addPrimOp(const string & name, - size_t arity, PrimOpFun primOp); + /* Print statistics. */ + void printStats(); -public: + void realiseContext(const PathSet& context); - Value & getBuiltin(const string & name); + private: + unsigned long nrEnvs = 0; + unsigned long nrValuesInEnvs = 0; + unsigned long nrValues = 0; + unsigned long nrListElems = 0; + unsigned long nrAttrsets = 0; + unsigned long nrAttrsInAttrsets = 0; + unsigned long nrOpUpdates = 0; + unsigned long nrOpUpdateValuesCopied = 0; + unsigned long nrListConcats = 0; + unsigned long nrPrimOpCalls = 0; + unsigned long nrFunctionCalls = 0; -private: + bool countCalls; - inline Value * lookupVar(Env * env, const ExprVar & var, bool noEval); + typedef std::map<Symbol, size_t> PrimOpCalls; + PrimOpCalls primOpCalls; - friend struct ExprVar; - friend struct ExprAttrs; - friend struct ExprLet; + typedef std::map<ExprLambda*, size_t> FunctionCalls; + FunctionCalls functionCalls; - Expr * parse(const char * text, const Path & path, - const Path & basePath, StaticEnv & staticEnv); + void incrFunctionCall(ExprLambda* fun); -public: + typedef std::map<Pos, size_t> AttrSelects; + AttrSelects attrSelects; - /* Do a deep equality test between two values. That is, list - elements and attributes are compared recursively. */ - bool eqValues(Value & v1, Value & v2); - - bool isFunctor(Value & fun); - - void callFunction(Value & fun, Value & arg, Value & v, const Pos & pos); - void callPrimOp(Value & fun, Value & arg, Value & v, const Pos & pos); - - /* Automatically call a function for which each argument has a - default value or has a binding in the `args' map. */ - void autoCallFunction(Bindings & args, Value & fun, Value & res); - - /* Allocation primitives. */ - Value * allocValue(); - Env & allocEnv(size_t size); - - Value * allocAttr(Value & vAttrs, const Symbol & name); - - Bindings * allocBindings(size_t capacity); - - void mkList(Value & v, size_t length); - void mkAttrs(Value & v, size_t capacity); - void mkThunk_(Value & v, Expr * expr); - void mkPos(Value & v, Pos * pos); - - void concatLists(Value & v, size_t nrLists, Value * * lists, const Pos & pos); - - /* Print statistics. */ - void printStats(); - - void realiseContext(const PathSet & context); - -private: - - unsigned long nrEnvs = 0; - unsigned long nrValuesInEnvs = 0; - unsigned long nrValues = 0; - unsigned long nrListElems = 0; - unsigned long nrAttrsets = 0; - unsigned long nrAttrsInAttrsets = 0; - unsigned long nrOpUpdates = 0; - unsigned long nrOpUpdateValuesCopied = 0; - unsigned long nrListConcats = 0; - unsigned long nrPrimOpCalls = 0; - unsigned long nrFunctionCalls = 0; - - bool countCalls; - - typedef std::map<Symbol, size_t> PrimOpCalls; - PrimOpCalls primOpCalls; - - typedef std::map<ExprLambda *, size_t> FunctionCalls; - FunctionCalls functionCalls; - - void incrFunctionCall(ExprLambda * fun); - - typedef std::map<Pos, size_t> AttrSelects; - AttrSelects attrSelects; - - friend struct ExprOpUpdate; - friend struct ExprOpConcatLists; - friend struct ExprSelect; - friend void prim_getAttr(EvalState & state, const Pos & pos, Value * * args, Value & v); + friend struct ExprOpUpdate; + friend struct ExprOpConcatLists; + friend struct ExprSelect; + friend void prim_getAttr(EvalState& state, const Pos& pos, Value** args, + Value& v); }; - /* Return a string representing the type of the value `v'. */ -string showType(const Value & v); +string showType(const Value& v); /* Decode a context string ‘!<name>!<path>’ into a pair <path, name>. */ -std::pair<string, string> decodeContext(const string & s); +std::pair<string, string> decodeContext(const string& s); /* If `path' refers to a directory, then append "/default.nix". */ Path resolveExprPath(Path path); -struct InvalidPathError : EvalError -{ - Path path; - InvalidPathError(const Path & path); +struct InvalidPathError : EvalError { + Path path; + InvalidPathError(const Path& path); #ifdef EXCEPTION_NEEDS_THROW_SPEC - ~InvalidPathError() throw () { }; + ~InvalidPathError() throw(){}; #endif }; -struct EvalSettings : Config -{ - Setting<bool> enableNativeCode{this, false, "allow-unsafe-native-code-during-evaluation", - "Whether builtin functions that allow executing native code should be enabled."}; - - Setting<bool> restrictEval{this, false, "restrict-eval", - "Whether to restrict file system access to paths in $NIX_PATH, " - "and network access to the URI prefixes listed in 'allowed-uris'."}; - - Setting<bool> pureEval{this, false, "pure-eval", - "Whether to restrict file system and network access to files specified by cryptographic hash."}; - - Setting<bool> enableImportFromDerivation{this, true, "allow-import-from-derivation", - "Whether the evaluator allows importing the result of a derivation."}; - - Setting<Strings> allowedUris{this, {}, "allowed-uris", - "Prefixes of URIs that builtin functions such as fetchurl and fetchGit are allowed to fetch."}; - - Setting<bool> traceFunctionCalls{this, false, "trace-function-calls", - "Emit log messages for each function entry and exit at the 'vomit' log level (-vvvv)"}; +struct EvalSettings : Config { + Setting<bool> enableNativeCode{this, false, + "allow-unsafe-native-code-during-evaluation", + "Whether builtin functions that allow " + "executing native code should be enabled."}; + + Setting<bool> restrictEval{ + this, false, "restrict-eval", + "Whether to restrict file system access to paths in $NIX_PATH, " + "and network access to the URI prefixes listed in 'allowed-uris'."}; + + Setting<bool> pureEval{this, false, "pure-eval", + "Whether to restrict file system and network access " + "to files specified by cryptographic hash."}; + + Setting<bool> enableImportFromDerivation{ + this, true, "allow-import-from-derivation", + "Whether the evaluator allows importing the result of a derivation."}; + + Setting<Strings> allowedUris{ + this, + {}, + "allowed-uris", + "Prefixes of URIs that builtin functions such as fetchurl and fetchGit " + "are allowed to fetch."}; + + Setting<bool> traceFunctionCalls{this, false, "trace-function-calls", + "Emit log messages for each function entry " + "and exit at the 'vomit' log level (-vvvv)"}; }; extern EvalSettings evalSettings; -} +} // namespace nix diff --git a/third_party/nix/src/libexpr/function-trace.cc b/third_party/nix/src/libexpr/function-trace.cc index af1486f78973..2bc0d943f339 100644 --- a/third_party/nix/src/libexpr/function-trace.cc +++ b/third_party/nix/src/libexpr/function-trace.cc @@ -2,16 +2,16 @@ namespace nix { -FunctionCallTrace::FunctionCallTrace(const Pos & pos) : pos(pos) { - auto duration = std::chrono::high_resolution_clock::now().time_since_epoch(); - auto ns = std::chrono::duration_cast<std::chrono::nanoseconds>(duration); - printMsg(lvlInfo, "function-trace entered %1% at %2%", pos, ns.count()); +FunctionCallTrace::FunctionCallTrace(const Pos& pos) : pos(pos) { + auto duration = std::chrono::high_resolution_clock::now().time_since_epoch(); + auto ns = std::chrono::duration_cast<std::chrono::nanoseconds>(duration); + printMsg(lvlInfo, "function-trace entered %1% at %2%", pos, ns.count()); } FunctionCallTrace::~FunctionCallTrace() { - auto duration = std::chrono::high_resolution_clock::now().time_since_epoch(); - auto ns = std::chrono::duration_cast<std::chrono::nanoseconds>(duration); - printMsg(lvlInfo, "function-trace exited %1% at %2%", pos, ns.count()); + auto duration = std::chrono::high_resolution_clock::now().time_since_epoch(); + auto ns = std::chrono::duration_cast<std::chrono::nanoseconds>(duration); + printMsg(lvlInfo, "function-trace exited %1% at %2%", pos, ns.count()); } -} +} // namespace nix diff --git a/third_party/nix/src/libexpr/function-trace.hh b/third_party/nix/src/libexpr/function-trace.hh index 472f2045ed65..bd27b8aebc7c 100644 --- a/third_party/nix/src/libexpr/function-trace.hh +++ b/third_party/nix/src/libexpr/function-trace.hh @@ -1,15 +1,13 @@ #pragma once -#include "eval.hh" - #include <chrono> +#include "eval.hh" namespace nix { -struct FunctionCallTrace -{ - const Pos & pos; - FunctionCallTrace(const Pos & pos); - ~FunctionCallTrace(); +struct FunctionCallTrace { + const Pos& pos; + FunctionCallTrace(const Pos& pos); + ~FunctionCallTrace(); }; -} +} // namespace nix diff --git a/third_party/nix/src/libexpr/get-drvs.cc b/third_party/nix/src/libexpr/get-drvs.cc index 21a4d7917fce..cdbcb7d76275 100644 --- a/third_party/nix/src/libexpr/get-drvs.cc +++ b/third_party/nix/src/libexpr/get-drvs.cc @@ -1,380 +1,351 @@ #include "get-drvs.hh" -#include "util.hh" -#include "eval-inline.hh" -#include "derivations.hh" - #include <cstring> #include <regex> - +#include "derivations.hh" +#include "eval-inline.hh" +#include "util.hh" namespace nix { +DrvInfo::DrvInfo(EvalState& state, const string& attrPath, Bindings* attrs) + : state(&state), attrs(attrs), attrPath(attrPath) {} -DrvInfo::DrvInfo(EvalState & state, const string & attrPath, Bindings * attrs) - : state(&state), attrs(attrs), attrPath(attrPath) -{ -} - - -DrvInfo::DrvInfo(EvalState & state, ref<Store> store, const std::string & drvPathWithOutputs) - : state(&state), attrs(nullptr), attrPath("") -{ - auto spec = parseDrvPathWithOutputs(drvPathWithOutputs); +DrvInfo::DrvInfo(EvalState& state, ref<Store> store, + const std::string& drvPathWithOutputs) + : state(&state), attrs(nullptr), attrPath("") { + auto spec = parseDrvPathWithOutputs(drvPathWithOutputs); - drvPath = spec.first; + drvPath = spec.first; - auto drv = store->derivationFromPath(drvPath); + auto drv = store->derivationFromPath(drvPath); - name = storePathToName(drvPath); + name = storePathToName(drvPath); - if (spec.second.size() > 1) - throw Error("building more than one derivation output is not supported, in '%s'", drvPathWithOutputs); + if (spec.second.size() > 1) + throw Error( + "building more than one derivation output is not supported, in '%s'", + drvPathWithOutputs); - outputName = - spec.second.empty() - ? get(drv.env, "outputName", "out") - : *spec.second.begin(); + outputName = spec.second.empty() ? get(drv.env, "outputName", "out") + : *spec.second.begin(); - auto i = drv.outputs.find(outputName); - if (i == drv.outputs.end()) - throw Error("derivation '%s' does not have output '%s'", drvPath, outputName); + auto i = drv.outputs.find(outputName); + if (i == drv.outputs.end()) + throw Error("derivation '%s' does not have output '%s'", drvPath, + outputName); - outPath = i->second.path; + outPath = i->second.path; } - -string DrvInfo::queryName() const -{ - if (name == "" && attrs) { - auto i = attrs->find(state->sName); - if (i == attrs->end()) throw TypeError("derivation name missing"); - name = state->forceStringNoCtx(*i->value); - } - return name; +string DrvInfo::queryName() const { + if (name == "" && attrs) { + auto i = attrs->find(state->sName); + if (i == attrs->end()) throw TypeError("derivation name missing"); + name = state->forceStringNoCtx(*i->value); + } + return name; } - -string DrvInfo::querySystem() const -{ - if (system == "" && attrs) { - auto i = attrs->find(state->sSystem); - system = i == attrs->end() ? "unknown" : state->forceStringNoCtx(*i->value, *i->pos); - } - return system; +string DrvInfo::querySystem() const { + if (system == "" && attrs) { + auto i = attrs->find(state->sSystem); + system = i == attrs->end() ? "unknown" + : state->forceStringNoCtx(*i->value, *i->pos); + } + return system; } - -string DrvInfo::queryDrvPath() const -{ - if (drvPath == "" && attrs) { - Bindings::iterator i = attrs->find(state->sDrvPath); - PathSet context; - drvPath = i != attrs->end() ? state->coerceToPath(*i->pos, *i->value, context) : ""; - } - return drvPath; +string DrvInfo::queryDrvPath() const { + if (drvPath == "" && attrs) { + Bindings::iterator i = attrs->find(state->sDrvPath); + PathSet context; + drvPath = i != attrs->end() + ? state->coerceToPath(*i->pos, *i->value, context) + : ""; + } + return drvPath; } - -string DrvInfo::queryOutPath() const -{ - if (outPath == "" && attrs) { - Bindings::iterator i = attrs->find(state->sOutPath); - PathSet context; - outPath = i != attrs->end() ? state->coerceToPath(*i->pos, *i->value, context) : ""; - } - return outPath; +string DrvInfo::queryOutPath() const { + if (outPath == "" && attrs) { + Bindings::iterator i = attrs->find(state->sOutPath); + PathSet context; + outPath = i != attrs->end() + ? state->coerceToPath(*i->pos, *i->value, context) + : ""; + } + return outPath; } - -DrvInfo::Outputs DrvInfo::queryOutputs(bool onlyOutputsToInstall) -{ - if (outputs.empty()) { - /* Get the ‘outputs’ list. */ - Bindings::iterator i; - if (attrs && (i = attrs->find(state->sOutputs)) != attrs->end()) { - state->forceList(*i->value, *i->pos); - - /* For each output... */ - for (unsigned int j = 0; j < i->value->listSize(); ++j) { - /* Evaluate the corresponding set. */ - string name = state->forceStringNoCtx(*i->value->listElems()[j], *i->pos); - Bindings::iterator out = attrs->find(state->symbols.create(name)); - if (out == attrs->end()) continue; // FIXME: throw error? - state->forceAttrs(*out->value); - - /* And evaluate its ‘outPath’ attribute. */ - Bindings::iterator outPath = out->value->attrs->find(state->sOutPath); - if (outPath == out->value->attrs->end()) continue; // FIXME: throw error? - PathSet context; - outputs[name] = state->coerceToPath(*outPath->pos, *outPath->value, context); - } - } else - outputs["out"] = queryOutPath(); - } - if (!onlyOutputsToInstall || !attrs) - return outputs; - - /* Check for `meta.outputsToInstall` and return `outputs` reduced to that. */ - const Value * outTI = queryMeta("outputsToInstall"); - if (!outTI) return outputs; - const auto errMsg = Error("this derivation has bad 'meta.outputsToInstall'"); - /* ^ this shows during `nix-env -i` right under the bad derivation */ - if (!outTI->isList()) throw errMsg; - Outputs result; - for (auto i = outTI->listElems(); i != outTI->listElems() + outTI->listSize(); ++i) { - if ((*i)->type != tString) throw errMsg; - auto out = outputs.find((*i)->string.s); - if (out == outputs.end()) throw errMsg; - result.insert(*out); - } - return result; +DrvInfo::Outputs DrvInfo::queryOutputs(bool onlyOutputsToInstall) { + if (outputs.empty()) { + /* Get the ‘outputs’ list. */ + Bindings::iterator i; + if (attrs && (i = attrs->find(state->sOutputs)) != attrs->end()) { + state->forceList(*i->value, *i->pos); + + /* For each output... */ + for (unsigned int j = 0; j < i->value->listSize(); ++j) { + /* Evaluate the corresponding set. */ + string name = + state->forceStringNoCtx(*i->value->listElems()[j], *i->pos); + Bindings::iterator out = attrs->find(state->symbols.create(name)); + if (out == attrs->end()) continue; // FIXME: throw error? + state->forceAttrs(*out->value); + + /* And evaluate its ‘outPath’ attribute. */ + Bindings::iterator outPath = out->value->attrs->find(state->sOutPath); + if (outPath == out->value->attrs->end()) + continue; // FIXME: throw error? + PathSet context; + outputs[name] = + state->coerceToPath(*outPath->pos, *outPath->value, context); + } + } else + outputs["out"] = queryOutPath(); + } + if (!onlyOutputsToInstall || !attrs) return outputs; + + /* Check for `meta.outputsToInstall` and return `outputs` reduced to that. */ + const Value* outTI = queryMeta("outputsToInstall"); + if (!outTI) return outputs; + const auto errMsg = Error("this derivation has bad 'meta.outputsToInstall'"); + /* ^ this shows during `nix-env -i` right under the bad derivation */ + if (!outTI->isList()) throw errMsg; + Outputs result; + for (auto i = outTI->listElems(); i != outTI->listElems() + outTI->listSize(); + ++i) { + if ((*i)->type != tString) throw errMsg; + auto out = outputs.find((*i)->string.s); + if (out == outputs.end()) throw errMsg; + result.insert(*out); + } + return result; } - -string DrvInfo::queryOutputName() const -{ - if (outputName == "" && attrs) { - Bindings::iterator i = attrs->find(state->sOutputName); - outputName = i != attrs->end() ? state->forceStringNoCtx(*i->value) : ""; - } - return outputName; +string DrvInfo::queryOutputName() const { + if (outputName == "" && attrs) { + Bindings::iterator i = attrs->find(state->sOutputName); + outputName = i != attrs->end() ? state->forceStringNoCtx(*i->value) : ""; + } + return outputName; } - -Bindings * DrvInfo::getMeta() -{ - if (meta) return meta; - if (!attrs) return 0; - Bindings::iterator a = attrs->find(state->sMeta); - if (a == attrs->end()) return 0; - state->forceAttrs(*a->value, *a->pos); - meta = a->value->attrs; - return meta; +Bindings* DrvInfo::getMeta() { + if (meta) return meta; + if (!attrs) return 0; + Bindings::iterator a = attrs->find(state->sMeta); + if (a == attrs->end()) return 0; + state->forceAttrs(*a->value, *a->pos); + meta = a->value->attrs; + return meta; } - -StringSet DrvInfo::queryMetaNames() -{ - StringSet res; - if (!getMeta()) return res; - for (auto & i : *meta) - res.insert(i.name); - return res; +StringSet DrvInfo::queryMetaNames() { + StringSet res; + if (!getMeta()) return res; + for (auto& i : *meta) res.insert(i.name); + return res; } - -bool DrvInfo::checkMeta(Value & v) -{ - state->forceValue(v); - if (v.isList()) { - for (unsigned int n = 0; n < v.listSize(); ++n) - if (!checkMeta(*v.listElems()[n])) return false; - return true; - } - else if (v.type == tAttrs) { - Bindings::iterator i = v.attrs->find(state->sOutPath); - if (i != v.attrs->end()) return false; - for (auto & i : *v.attrs) - if (!checkMeta(*i.value)) return false; - return true; - } - else return v.type == tInt || v.type == tBool || v.type == tString || - v.type == tFloat; +bool DrvInfo::checkMeta(Value& v) { + state->forceValue(v); + if (v.isList()) { + for (unsigned int n = 0; n < v.listSize(); ++n) + if (!checkMeta(*v.listElems()[n])) return false; + return true; + } else if (v.type == tAttrs) { + Bindings::iterator i = v.attrs->find(state->sOutPath); + if (i != v.attrs->end()) return false; + for (auto& i : *v.attrs) + if (!checkMeta(*i.value)) return false; + return true; + } else + return v.type == tInt || v.type == tBool || v.type == tString || + v.type == tFloat; } - -Value * DrvInfo::queryMeta(const string & name) -{ - if (!getMeta()) return 0; - Bindings::iterator a = meta->find(state->symbols.create(name)); - if (a == meta->end() || !checkMeta(*a->value)) return 0; - return a->value; +Value* DrvInfo::queryMeta(const string& name) { + if (!getMeta()) return 0; + Bindings::iterator a = meta->find(state->symbols.create(name)); + if (a == meta->end() || !checkMeta(*a->value)) return 0; + return a->value; } - -string DrvInfo::queryMetaString(const string & name) -{ - Value * v = queryMeta(name); - if (!v || v->type != tString) return ""; - return v->string.s; +string DrvInfo::queryMetaString(const string& name) { + Value* v = queryMeta(name); + if (!v || v->type != tString) return ""; + return v->string.s; } - -NixInt DrvInfo::queryMetaInt(const string & name, NixInt def) -{ - Value * v = queryMeta(name); - if (!v) return def; - if (v->type == tInt) return v->integer; - if (v->type == tString) { - /* Backwards compatibility with before we had support for - integer meta fields. */ - NixInt n; - if (string2Int(v->string.s, n)) return n; - } - return def; +NixInt DrvInfo::queryMetaInt(const string& name, NixInt def) { + Value* v = queryMeta(name); + if (!v) return def; + if (v->type == tInt) return v->integer; + if (v->type == tString) { + /* Backwards compatibility with before we had support for + integer meta fields. */ + NixInt n; + if (string2Int(v->string.s, n)) return n; + } + return def; } -NixFloat DrvInfo::queryMetaFloat(const string & name, NixFloat def) -{ - Value * v = queryMeta(name); - if (!v) return def; - if (v->type == tFloat) return v->fpoint; - if (v->type == tString) { - /* Backwards compatibility with before we had support for - float meta fields. */ - NixFloat n; - if (string2Float(v->string.s, n)) return n; - } - return def; +NixFloat DrvInfo::queryMetaFloat(const string& name, NixFloat def) { + Value* v = queryMeta(name); + if (!v) return def; + if (v->type == tFloat) return v->fpoint; + if (v->type == tString) { + /* Backwards compatibility with before we had support for + float meta fields. */ + NixFloat n; + if (string2Float(v->string.s, n)) return n; + } + return def; } - -bool DrvInfo::queryMetaBool(const string & name, bool def) -{ - Value * v = queryMeta(name); - if (!v) return def; - if (v->type == tBool) return v->boolean; - if (v->type == tString) { - /* Backwards compatibility with before we had support for - Boolean meta fields. */ - if (strcmp(v->string.s, "true") == 0) return true; - if (strcmp(v->string.s, "false") == 0) return false; - } - return def; +bool DrvInfo::queryMetaBool(const string& name, bool def) { + Value* v = queryMeta(name); + if (!v) return def; + if (v->type == tBool) return v->boolean; + if (v->type == tString) { + /* Backwards compatibility with before we had support for + Boolean meta fields. */ + if (strcmp(v->string.s, "true") == 0) return true; + if (strcmp(v->string.s, "false") == 0) return false; + } + return def; } - -void DrvInfo::setMeta(const string & name, Value * v) -{ - getMeta(); - Bindings * old = meta; - meta = state->allocBindings(1 + (old ? old->size() : 0)); - Symbol sym = state->symbols.create(name); - if (old) - for (auto i : *old) - if (i.name != sym) - meta->push_back(i); - if (v) meta->push_back(Attr(sym, v)); - meta->sort(); +void DrvInfo::setMeta(const string& name, Value* v) { + getMeta(); + Bindings* old = meta; + meta = state->allocBindings(1 + (old ? old->size() : 0)); + Symbol sym = state->symbols.create(name); + if (old) + for (auto i : *old) + if (i.name != sym) meta->push_back(i); + if (v) meta->push_back(Attr(sym, v)); + meta->sort(); } - /* Cache for already considered attrsets. */ -typedef set<Bindings *> Done; - +typedef set<Bindings*> Done; /* Evaluate value `v'. If it evaluates to a set of type `derivation', then put information about it in `drvs' (unless it's already in `done'). The result boolean indicates whether it makes sense for the caller to recursively search for derivations in `v'. */ -static bool getDerivation(EvalState & state, Value & v, - const string & attrPath, DrvInfos & drvs, Done & done, - bool ignoreAssertionFailures) -{ - try { - state.forceValue(v); - if (!state.isDerivation(v)) return true; +static bool getDerivation(EvalState& state, Value& v, const string& attrPath, + DrvInfos& drvs, Done& done, + bool ignoreAssertionFailures) { + try { + state.forceValue(v); + if (!state.isDerivation(v)) return true; - /* Remove spurious duplicates (e.g., a set like `rec { x = - derivation {...}; y = x;}'. */ - if (done.find(v.attrs) != done.end()) return false; - done.insert(v.attrs); + /* Remove spurious duplicates (e.g., a set like `rec { x = + derivation {...}; y = x;}'. */ + if (done.find(v.attrs) != done.end()) return false; + done.insert(v.attrs); - DrvInfo drv(state, attrPath, v.attrs); + DrvInfo drv(state, attrPath, v.attrs); - drv.queryName(); + drv.queryName(); - drvs.push_back(drv); + drvs.push_back(drv); - return false; + return false; - } catch (AssertionError & e) { - if (ignoreAssertionFailures) return false; - throw; - } + } catch (AssertionError& e) { + if (ignoreAssertionFailures) return false; + throw; + } } - -std::optional<DrvInfo> getDerivation(EvalState & state, Value & v, - bool ignoreAssertionFailures) -{ - Done done; - DrvInfos drvs; - getDerivation(state, v, "", drvs, done, ignoreAssertionFailures); - if (drvs.size() != 1) return {}; - return std::move(drvs.front()); +std::optional<DrvInfo> getDerivation(EvalState& state, Value& v, + bool ignoreAssertionFailures) { + Done done; + DrvInfos drvs; + getDerivation(state, v, "", drvs, done, ignoreAssertionFailures); + if (drvs.size() != 1) return {}; + return std::move(drvs.front()); } - -static string addToPath(const string & s1, const string & s2) -{ - return s1.empty() ? s2 : s1 + "." + s2; +static string addToPath(const string& s1, const string& s2) { + return s1.empty() ? s2 : s1 + "." + s2; } - static std::regex attrRegex("[A-Za-z_][A-Za-z0-9-_+]*"); - -static void getDerivations(EvalState & state, Value & vIn, - const string & pathPrefix, Bindings & autoArgs, - DrvInfos & drvs, Done & done, - bool ignoreAssertionFailures) -{ - Value v; - state.autoCallFunction(autoArgs, vIn, v); - - /* Process the expression. */ - if (!getDerivation(state, v, pathPrefix, drvs, done, ignoreAssertionFailures)) ; - - else if (v.type == tAttrs) { - - /* !!! undocumented hackery to support combining channels in - nix-env.cc. */ - bool combineChannels = v.attrs->find(state.symbols.create("_combineChannels")) != v.attrs->end(); - - /* Consider the attributes in sorted order to get more - deterministic behaviour in nix-env operations (e.g. when - there are names clashes between derivations, the derivation - bound to the attribute with the "lower" name should take - precedence). */ - for (auto & i : v.attrs->lexicographicOrder()) { - debug("evaluating attribute '%1%'", i->name); - if (!std::regex_match(std::string(i->name), attrRegex)) - continue; - string pathPrefix2 = addToPath(pathPrefix, i->name); - if (combineChannels) - getDerivations(state, *i->value, pathPrefix2, autoArgs, drvs, done, ignoreAssertionFailures); - else if (getDerivation(state, *i->value, pathPrefix2, drvs, done, ignoreAssertionFailures)) { - /* If the value of this attribute is itself a set, - should we recurse into it? => Only if it has a - `recurseForDerivations = true' attribute. */ - if (i->value->type == tAttrs) { - Bindings::iterator j = i->value->attrs->find(state.symbols.create("recurseForDerivations")); - if (j != i->value->attrs->end() && state.forceBool(*j->value, *j->pos)) - getDerivations(state, *i->value, pathPrefix2, autoArgs, drvs, done, ignoreAssertionFailures); - } - } +static void getDerivations(EvalState& state, Value& vIn, + const string& pathPrefix, Bindings& autoArgs, + DrvInfos& drvs, Done& done, + bool ignoreAssertionFailures) { + Value v; + state.autoCallFunction(autoArgs, vIn, v); + + /* Process the expression. */ + if (!getDerivation(state, v, pathPrefix, drvs, done, ignoreAssertionFailures)) + ; + + else if (v.type == tAttrs) { + /* !!! undocumented hackery to support combining channels in + nix-env.cc. */ + bool combineChannels = + v.attrs->find(state.symbols.create("_combineChannels")) != + v.attrs->end(); + + /* Consider the attributes in sorted order to get more + deterministic behaviour in nix-env operations (e.g. when + there are names clashes between derivations, the derivation + bound to the attribute with the "lower" name should take + precedence). */ + for (auto& i : v.attrs->lexicographicOrder()) { + debug("evaluating attribute '%1%'", i->name); + if (!std::regex_match(std::string(i->name), attrRegex)) continue; + string pathPrefix2 = addToPath(pathPrefix, i->name); + if (combineChannels) + getDerivations(state, *i->value, pathPrefix2, autoArgs, drvs, done, + ignoreAssertionFailures); + else if (getDerivation(state, *i->value, pathPrefix2, drvs, done, + ignoreAssertionFailures)) { + /* If the value of this attribute is itself a set, + should we recurse into it? => Only if it has a + `recurseForDerivations = true' attribute. */ + if (i->value->type == tAttrs) { + Bindings::iterator j = i->value->attrs->find( + state.symbols.create("recurseForDerivations")); + if (j != i->value->attrs->end() && + state.forceBool(*j->value, *j->pos)) + getDerivations(state, *i->value, pathPrefix2, autoArgs, drvs, done, + ignoreAssertionFailures); } + } } - - else if (v.isList()) { - for (unsigned int n = 0; n < v.listSize(); ++n) { - string pathPrefix2 = addToPath(pathPrefix, (format("%1%") % n).str()); - if (getDerivation(state, *v.listElems()[n], pathPrefix2, drvs, done, ignoreAssertionFailures)) - getDerivations(state, *v.listElems()[n], pathPrefix2, autoArgs, drvs, done, ignoreAssertionFailures); - } + } + + else if (v.isList()) { + for (unsigned int n = 0; n < v.listSize(); ++n) { + string pathPrefix2 = addToPath(pathPrefix, (format("%1%") % n).str()); + if (getDerivation(state, *v.listElems()[n], pathPrefix2, drvs, done, + ignoreAssertionFailures)) + getDerivations(state, *v.listElems()[n], pathPrefix2, autoArgs, drvs, + done, ignoreAssertionFailures); } + } - else throw TypeError("expression does not evaluate to a derivation (or a set or list of those)"); + else + throw TypeError( + "expression does not evaluate to a derivation (or a set or list of " + "those)"); } - -void getDerivations(EvalState & state, Value & v, const string & pathPrefix, - Bindings & autoArgs, DrvInfos & drvs, bool ignoreAssertionFailures) -{ - Done done; - getDerivations(state, v, pathPrefix, autoArgs, drvs, done, ignoreAssertionFailures); +void getDerivations(EvalState& state, Value& v, const string& pathPrefix, + Bindings& autoArgs, DrvInfos& drvs, + bool ignoreAssertionFailures) { + Done done; + getDerivations(state, v, pathPrefix, autoArgs, drvs, done, + ignoreAssertionFailures); } - -} +} // namespace nix diff --git a/third_party/nix/src/libexpr/get-drvs.hh b/third_party/nix/src/libexpr/get-drvs.hh index d7860fc6a4bc..0dca12cafc4e 100644 --- a/third_party/nix/src/libexpr/get-drvs.hh +++ b/third_party/nix/src/libexpr/get-drvs.hh @@ -1,89 +1,84 @@ #pragma once -#include "eval.hh" - -#include <string> #include <map> - +#include <string> +#include "eval.hh" namespace nix { +struct DrvInfo { + public: + typedef std::map<string, Path> Outputs; -struct DrvInfo -{ -public: - typedef std::map<string, Path> Outputs; - -private: - EvalState * state; + private: + EvalState* state; - mutable string name; - mutable string system; - mutable string drvPath; - mutable string outPath; - mutable string outputName; - Outputs outputs; + mutable string name; + mutable string system; + mutable string drvPath; + mutable string outPath; + mutable string outputName; + Outputs outputs; - bool failed = false; // set if we get an AssertionError + bool failed = false; // set if we get an AssertionError - Bindings * attrs = nullptr, * meta = nullptr; + Bindings *attrs = nullptr, *meta = nullptr; - Bindings * getMeta(); + Bindings* getMeta(); - bool checkMeta(Value & v); + bool checkMeta(Value& v); -public: - string attrPath; /* path towards the derivation */ + public: + string attrPath; /* path towards the derivation */ - DrvInfo(EvalState & state) : state(&state) { }; - DrvInfo(EvalState & state, const string & attrPath, Bindings * attrs); - DrvInfo(EvalState & state, ref<Store> store, const std::string & drvPathWithOutputs); + DrvInfo(EvalState& state) : state(&state){}; + DrvInfo(EvalState& state, const string& attrPath, Bindings* attrs); + DrvInfo(EvalState& state, ref<Store> store, + const std::string& drvPathWithOutputs); - string queryName() const; - string querySystem() const; - string queryDrvPath() const; - string queryOutPath() const; - string queryOutputName() const; - /** Return the list of outputs. The "outputs to install" are determined by `meta.outputsToInstall`. */ - Outputs queryOutputs(bool onlyOutputsToInstall = false); + string queryName() const; + string querySystem() const; + string queryDrvPath() const; + string queryOutPath() const; + string queryOutputName() const; + /** Return the list of outputs. The "outputs to install" are determined by + * `meta.outputsToInstall`. */ + Outputs queryOutputs(bool onlyOutputsToInstall = false); - StringSet queryMetaNames(); - Value * queryMeta(const string & name); - string queryMetaString(const string & name); - NixInt queryMetaInt(const string & name, NixInt def); - NixFloat queryMetaFloat(const string & name, NixFloat def); - bool queryMetaBool(const string & name, bool def); - void setMeta(const string & name, Value * v); + StringSet queryMetaNames(); + Value* queryMeta(const string& name); + string queryMetaString(const string& name); + NixInt queryMetaInt(const string& name, NixInt def); + NixFloat queryMetaFloat(const string& name, NixFloat def); + bool queryMetaBool(const string& name, bool def); + void setMeta(const string& name, Value* v); - /* - MetaInfo queryMetaInfo(EvalState & state) const; - MetaValue queryMetaInfo(EvalState & state, const string & name) const; - */ + /* + MetaInfo queryMetaInfo(EvalState & state) const; + MetaValue queryMetaInfo(EvalState & state, const string & name) const; + */ - void setName(const string & s) { name = s; } - void setDrvPath(const string & s) { drvPath = s; } - void setOutPath(const string & s) { outPath = s; } + void setName(const string& s) { name = s; } + void setDrvPath(const string& s) { drvPath = s; } + void setOutPath(const string& s) { outPath = s; } - void setFailed() { failed = true; }; - bool hasFailed() { return failed; }; + void setFailed() { failed = true; }; + bool hasFailed() { return failed; }; }; - #if HAVE_BOEHMGC typedef list<DrvInfo, traceable_allocator<DrvInfo> > DrvInfos; #else typedef list<DrvInfo> DrvInfos; #endif - /* If value `v' denotes a derivation, return a DrvInfo object describing it. Otherwise return nothing. */ -std::optional<DrvInfo> getDerivation(EvalState & state, - Value & v, bool ignoreAssertionFailures); - -void getDerivations(EvalState & state, Value & v, const string & pathPrefix, - Bindings & autoArgs, DrvInfos & drvs, - bool ignoreAssertionFailures); +std::optional<DrvInfo> getDerivation(EvalState& state, Value& v, + bool ignoreAssertionFailures); +void getDerivations(EvalState& state, Value& v, const string& pathPrefix, + Bindings& autoArgs, DrvInfos& drvs, + bool ignoreAssertionFailures); -} +} // namespace nix diff --git a/third_party/nix/src/libexpr/json-to-value.cc b/third_party/nix/src/libexpr/json-to-value.cc index 8bae986f97fc..8615d9797fa8 100644 --- a/third_party/nix/src/libexpr/json-to-value.cc +++ b/third_party/nix/src/libexpr/json-to-value.cc @@ -1,149 +1,153 @@ #include "json-to-value.hh" - #include <cstring> namespace nix { - -static void skipWhitespace(const char * & s) -{ - while (*s == ' ' || *s == '\t' || *s == '\n' || *s == '\r') s++; +static void skipWhitespace(const char*& s) { + while (*s == ' ' || *s == '\t' || *s == '\n' || *s == '\r') s++; } - -static string parseJSONString(const char * & s) -{ - string res; - if (*s++ != '"') throw JSONParseError("expected JSON string"); - while (*s != '"') { - if (!*s) throw JSONParseError("got end-of-string in JSON string"); - if (*s == '\\') { - s++; - if (*s == '"') res += '"'; - else if (*s == '\\') res += '\\'; - else if (*s == '/') res += '/'; - else if (*s == '/') res += '/'; - else if (*s == 'b') res += '\b'; - else if (*s == 'f') res += '\f'; - else if (*s == 'n') res += '\n'; - else if (*s == 'r') res += '\r'; - else if (*s == 't') res += '\t'; - else if (*s == 'u') throw JSONParseError("\\u characters in JSON strings are currently not supported"); - else throw JSONParseError("invalid escaped character in JSON string"); - s++; - } else - res += *s++; - } - s++; - return res; +static string parseJSONString(const char*& s) { + string res; + if (*s++ != '"') throw JSONParseError("expected JSON string"); + while (*s != '"') { + if (!*s) throw JSONParseError("got end-of-string in JSON string"); + if (*s == '\\') { + s++; + if (*s == '"') + res += '"'; + else if (*s == '\\') + res += '\\'; + else if (*s == '/') + res += '/'; + else if (*s == '/') + res += '/'; + else if (*s == 'b') + res += '\b'; + else if (*s == 'f') + res += '\f'; + else if (*s == 'n') + res += '\n'; + else if (*s == 'r') + res += '\r'; + else if (*s == 't') + res += '\t'; + else if (*s == 'u') + throw JSONParseError( + "\\u characters in JSON strings are currently not supported"); + else + throw JSONParseError("invalid escaped character in JSON string"); + s++; + } else + res += *s++; + } + s++; + return res; } +static void parseJSON(EvalState& state, const char*& s, Value& v) { + skipWhitespace(s); -static void parseJSON(EvalState & state, const char * & s, Value & v) -{ - skipWhitespace(s); + if (!*s) throw JSONParseError("expected JSON value"); - if (!*s) throw JSONParseError("expected JSON value"); - - if (*s == '[') { - s++; - ValueVector values; - values.reserve(128); - skipWhitespace(s); - while (1) { - if (values.empty() && *s == ']') break; - Value * v2 = state.allocValue(); - parseJSON(state, s, *v2); - values.push_back(v2); - skipWhitespace(s); - if (*s == ']') break; - if (*s != ',') throw JSONParseError("expected ',' or ']' after JSON array element"); - s++; - } - s++; - state.mkList(v, values.size()); - for (size_t n = 0; n < values.size(); ++n) - v.listElems()[n] = values[n]; + if (*s == '[') { + s++; + ValueVector values; + values.reserve(128); + skipWhitespace(s); + while (1) { + if (values.empty() && *s == ']') break; + Value* v2 = state.allocValue(); + parseJSON(state, s, *v2); + values.push_back(v2); + skipWhitespace(s); + if (*s == ']') break; + if (*s != ',') + throw JSONParseError("expected ',' or ']' after JSON array element"); + s++; } + s++; + state.mkList(v, values.size()); + for (size_t n = 0; n < values.size(); ++n) v.listElems()[n] = values[n]; + } - else if (*s == '{') { - s++; - ValueMap attrs; - while (1) { - skipWhitespace(s); - if (attrs.empty() && *s == '}') break; - string name = parseJSONString(s); - skipWhitespace(s); - if (*s != ':') throw JSONParseError("expected ':' in JSON object"); - s++; - Value * v2 = state.allocValue(); - parseJSON(state, s, *v2); - attrs[state.symbols.create(name)] = v2; - skipWhitespace(s); - if (*s == '}') break; - if (*s != ',') throw JSONParseError("expected ',' or '}' after JSON member"); - s++; - } - state.mkAttrs(v, attrs.size()); - for (auto & i : attrs) - v.attrs->push_back(Attr(i.first, i.second)); - v.attrs->sort(); - s++; + else if (*s == '{') { + s++; + ValueMap attrs; + while (1) { + skipWhitespace(s); + if (attrs.empty() && *s == '}') break; + string name = parseJSONString(s); + skipWhitespace(s); + if (*s != ':') throw JSONParseError("expected ':' in JSON object"); + s++; + Value* v2 = state.allocValue(); + parseJSON(state, s, *v2); + attrs[state.symbols.create(name)] = v2; + skipWhitespace(s); + if (*s == '}') break; + if (*s != ',') + throw JSONParseError("expected ',' or '}' after JSON member"); + s++; } + state.mkAttrs(v, attrs.size()); + for (auto& i : attrs) v.attrs->push_back(Attr(i.first, i.second)); + v.attrs->sort(); + s++; + } - else if (*s == '"') { - mkString(v, parseJSONString(s)); - } + else if (*s == '"') { + mkString(v, parseJSONString(s)); + } - else if (isdigit(*s) || *s == '-' || *s == '.' ) { - // Buffer into a string first, then use built-in C++ conversions - std::string tmp_number; - ValueType number_type = tInt; - - while (isdigit(*s) || *s == '-' || *s == '.' || *s == 'e' || *s == 'E') { - if (*s == '.' || *s == 'e' || *s == 'E') - number_type = tFloat; - tmp_number += *s++; - } - - try { - if (number_type == tFloat) - mkFloat(v, stod(tmp_number)); - else - mkInt(v, stol(tmp_number)); - } catch (std::invalid_argument & e) { - throw JSONParseError("invalid JSON number"); - } catch (std::out_of_range & e) { - throw JSONParseError("out-of-range JSON number"); - } - } + else if (isdigit(*s) || *s == '-' || *s == '.') { + // Buffer into a string first, then use built-in C++ conversions + std::string tmp_number; + ValueType number_type = tInt; - else if (strncmp(s, "true", 4) == 0) { - s += 4; - mkBool(v, true); + while (isdigit(*s) || *s == '-' || *s == '.' || *s == 'e' || *s == 'E') { + if (*s == '.' || *s == 'e' || *s == 'E') number_type = tFloat; + tmp_number += *s++; } - else if (strncmp(s, "false", 5) == 0) { - s += 5; - mkBool(v, false); + try { + if (number_type == tFloat) + mkFloat(v, stod(tmp_number)); + else + mkInt(v, stol(tmp_number)); + } catch (std::invalid_argument& e) { + throw JSONParseError("invalid JSON number"); + } catch (std::out_of_range& e) { + throw JSONParseError("out-of-range JSON number"); } + } - else if (strncmp(s, "null", 4) == 0) { - s += 4; - mkNull(v); - } + else if (strncmp(s, "true", 4) == 0) { + s += 4; + mkBool(v, true); + } - else throw JSONParseError("unrecognised JSON value"); -} + else if (strncmp(s, "false", 5) == 0) { + s += 5; + mkBool(v, false); + } + else if (strncmp(s, "null", 4) == 0) { + s += 4; + mkNull(v); + } -void parseJSON(EvalState & state, const string & s_, Value & v) -{ - const char * s = s_.c_str(); - parseJSON(state, s, v); - skipWhitespace(s); - if (*s) throw JSONParseError(format("expected end-of-string while parsing JSON value: %1%") % s); + else + throw JSONParseError("unrecognised JSON value"); } - +void parseJSON(EvalState& state, const string& s_, Value& v) { + const char* s = s_.c_str(); + parseJSON(state, s, v); + skipWhitespace(s); + if (*s) + throw JSONParseError( + format("expected end-of-string while parsing JSON value: %1%") % s); } + +} // namespace nix diff --git a/third_party/nix/src/libexpr/json-to-value.hh b/third_party/nix/src/libexpr/json-to-value.hh index 33f35b16ce89..eb555c643fab 100644 --- a/third_party/nix/src/libexpr/json-to-value.hh +++ b/third_party/nix/src/libexpr/json-to-value.hh @@ -1,13 +1,12 @@ #pragma once -#include "eval.hh" - #include <string> +#include "eval.hh" namespace nix { MakeError(JSONParseError, EvalError) -void parseJSON(EvalState & state, const string & s, Value & v); + void parseJSON(EvalState& state, const string& s, Value& v); } diff --git a/third_party/nix/src/libexpr/names.cc b/third_party/nix/src/libexpr/names.cc index 382088c78872..c3dd2a9bd1ed 100644 --- a/third_party/nix/src/libexpr/names.cc +++ b/third_party/nix/src/libexpr/names.cc @@ -1,107 +1,98 @@ #include "names.hh" #include "util.hh" - namespace nix { - -DrvName::DrvName() -{ - name = ""; -} - +DrvName::DrvName() { name = ""; } /* Parse a derivation name. The `name' part of a derivation name is everything up to but not including the first dash *not* followed by a letter. The `version' part is the rest (excluding the separating dash). E.g., `apache-httpd-2.0.48' is parsed to (`apache-httpd', '2.0.48'). */ -DrvName::DrvName(const string & s) : hits(0) -{ - name = fullName = s; - for (unsigned int i = 0; i < s.size(); ++i) { - /* !!! isalpha/isdigit are affected by the locale. */ - if (s[i] == '-' && i + 1 < s.size() && !isalpha(s[i + 1])) { - name = string(s, 0, i); - version = string(s, i + 1); - break; - } +DrvName::DrvName(const string& s) : hits(0) { + name = fullName = s; + for (unsigned int i = 0; i < s.size(); ++i) { + /* !!! isalpha/isdigit are affected by the locale. */ + if (s[i] == '-' && i + 1 < s.size() && !isalpha(s[i + 1])) { + name = string(s, 0, i); + version = string(s, i + 1); + break; } + } } - -bool DrvName::matches(DrvName & n) -{ - if (name != "*") { - if (!regex) regex = std::unique_ptr<std::regex>(new std::regex(name, std::regex::extended)); - if (!std::regex_match(n.name, *regex)) return false; - } - if (version != "" && version != n.version) return false; - return true; +bool DrvName::matches(DrvName& n) { + if (name != "*") { + if (!regex) + regex = std::unique_ptr<std::regex>( + new std::regex(name, std::regex::extended)); + if (!std::regex_match(n.name, *regex)) return false; + } + if (version != "" && version != n.version) return false; + return true; } +string nextComponent(string::const_iterator& p, + const string::const_iterator end) { + /* Skip any dots and dashes (component separators). */ + while (p != end && (*p == '.' || *p == '-')) ++p; -string nextComponent(string::const_iterator & p, - const string::const_iterator end) -{ - /* Skip any dots and dashes (component separators). */ - while (p != end && (*p == '.' || *p == '-')) ++p; + if (p == end) return ""; - if (p == end) return ""; + /* If the first character is a digit, consume the longest sequence + of digits. Otherwise, consume the longest sequence of + non-digit, non-separator characters. */ + string s; + if (isdigit(*p)) + while (p != end && isdigit(*p)) s += *p++; + else + while (p != end && (!isdigit(*p) && *p != '.' && *p != '-')) s += *p++; - /* If the first character is a digit, consume the longest sequence - of digits. Otherwise, consume the longest sequence of - non-digit, non-separator characters. */ - string s; - if (isdigit(*p)) - while (p != end && isdigit(*p)) s += *p++; - else - while (p != end && (!isdigit(*p) && *p != '.' && *p != '-')) - s += *p++; - - return s; + return s; } +static bool componentsLT(const string& c1, const string& c2) { + int n1, n2; + bool c1Num = string2Int(c1, n1), c2Num = string2Int(c2, n2); -static bool componentsLT(const string & c1, const string & c2) -{ - int n1, n2; - bool c1Num = string2Int(c1, n1), c2Num = string2Int(c2, n2); - - if (c1Num && c2Num) return n1 < n2; - else if (c1 == "" && c2Num) return true; - else if (c1 == "pre" && c2 != "pre") return true; - else if (c2 == "pre") return false; - /* Assume that `2.3a' < `2.3.1'. */ - else if (c2Num) return true; - else if (c1Num) return false; - else return c1 < c2; + if (c1Num && c2Num) + return n1 < n2; + else if (c1 == "" && c2Num) + return true; + else if (c1 == "pre" && c2 != "pre") + return true; + else if (c2 == "pre") + return false; + /* Assume that `2.3a' < `2.3.1'. */ + else if (c2Num) + return true; + else if (c1Num) + return false; + else + return c1 < c2; } +int compareVersions(const string& v1, const string& v2) { + string::const_iterator p1 = v1.begin(); + string::const_iterator p2 = v2.begin(); -int compareVersions(const string & v1, const string & v2) -{ - string::const_iterator p1 = v1.begin(); - string::const_iterator p2 = v2.begin(); - - while (p1 != v1.end() || p2 != v2.end()) { - string c1 = nextComponent(p1, v1.end()); - string c2 = nextComponent(p2, v2.end()); - if (componentsLT(c1, c2)) return -1; - else if (componentsLT(c2, c1)) return 1; - } + while (p1 != v1.end() || p2 != v2.end()) { + string c1 = nextComponent(p1, v1.end()); + string c2 = nextComponent(p2, v2.end()); + if (componentsLT(c1, c2)) + return -1; + else if (componentsLT(c2, c1)) + return 1; + } - return 0; + return 0; } - -DrvNames drvNamesFromArgs(const Strings & opArgs) -{ - DrvNames result; - for (auto & i : opArgs) - result.push_back(DrvName(i)); - return result; +DrvNames drvNamesFromArgs(const Strings& opArgs) { + DrvNames result; + for (auto& i : opArgs) result.push_back(DrvName(i)); + return result; } - -} +} // namespace nix diff --git a/third_party/nix/src/libexpr/names.hh b/third_party/nix/src/libexpr/names.hh index 13c3093e77b0..219a595d15e2 100644 --- a/third_party/nix/src/libexpr/names.hh +++ b/third_party/nix/src/libexpr/names.hh @@ -1,32 +1,30 @@ #pragma once #include <memory> - -#include "types.hh" #include <regex> +#include "types.hh" namespace nix { -struct DrvName -{ - string fullName; - string name; - string version; - unsigned int hits; +struct DrvName { + string fullName; + string name; + string version; + unsigned int hits; - DrvName(); - DrvName(const string & s); - bool matches(DrvName & n); + DrvName(); + DrvName(const string& s); + bool matches(DrvName& n); -private: - std::unique_ptr<std::regex> regex; + private: + std::unique_ptr<std::regex> regex; }; typedef list<DrvName> DrvNames; -string nextComponent(string::const_iterator & p, - const string::const_iterator end); -int compareVersions(const string & v1, const string & v2); -DrvNames drvNamesFromArgs(const Strings & opArgs); +string nextComponent(string::const_iterator& p, + const string::const_iterator end); +int compareVersions(const string& v1, const string& v2); +DrvNames drvNamesFromArgs(const Strings& opArgs); -} +} // namespace nix diff --git a/third_party/nix/src/libexpr/nixexpr.cc b/third_party/nix/src/libexpr/nixexpr.cc index 63cbef1ddf84..4147cbbf532b 100644 --- a/third_party/nix/src/libexpr/nixexpr.cc +++ b/third_party/nix/src/libexpr/nixexpr.cc @@ -1,438 +1,361 @@ #include "nixexpr.hh" +#include <cstdlib> #include "derivations.hh" #include "util.hh" -#include <cstdlib> - - namespace nix { - /* Displaying abstract syntax trees. */ -std::ostream & operator << (std::ostream & str, const Expr & e) -{ - e.show(str); - return str; -} - -static void showString(std::ostream & str, const string & s) -{ - str << '"'; - for (auto c : (string) s) - if (c == '"' || c == '\\' || c == '$') str << "\\" << c; - else if (c == '\n') str << "\\n"; - else if (c == '\r') str << "\\r"; - else if (c == '\t') str << "\\t"; - else str << c; - str << '"'; -} - -static void showId(std::ostream & str, const string & s) -{ - if (s.empty()) - str << "\"\""; - else if (s == "if") // FIXME: handle other keywords - str << '"' << s << '"'; - else { - char c = s[0]; - if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_')) { - showString(str, s); - return; - } - for (auto c : s) - if (!((c >= 'a' && c <= 'z') || - (c >= 'A' && c <= 'Z') || - (c >= '0' && c <= '9') || - c == '_' || c == '\'' || c == '-')) { - showString(str, s); - return; - } - str << s; +std::ostream& operator<<(std::ostream& str, const Expr& e) { + e.show(str); + return str; +} + +static void showString(std::ostream& str, const string& s) { + str << '"'; + for (auto c : (string)s) + if (c == '"' || c == '\\' || c == '$') + str << "\\" << c; + else if (c == '\n') + str << "\\n"; + else if (c == '\r') + str << "\\r"; + else if (c == '\t') + str << "\\t"; + else + str << c; + str << '"'; +} + +static void showId(std::ostream& str, const string& s) { + if (s.empty()) + str << "\"\""; + else if (s == "if") // FIXME: handle other keywords + str << '"' << s << '"'; + else { + char c = s[0]; + if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_')) { + showString(str, s); + return; } + for (auto c : s) + if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || + (c >= '0' && c <= '9') || c == '_' || c == '\'' || c == '-')) { + showString(str, s); + return; + } + str << s; + } } -std::ostream & operator << (std::ostream & str, const Symbol & sym) -{ - showId(str, *sym.s); - return str; +std::ostream& operator<<(std::ostream& str, const Symbol& sym) { + showId(str, *sym.s); + return str; } -void Expr::show(std::ostream & str) const -{ - abort(); -} +void Expr::show(std::ostream& str) const { abort(); } -void ExprInt::show(std::ostream & str) const -{ - str << n; -} +void ExprInt::show(std::ostream& str) const { str << n; } -void ExprFloat::show(std::ostream & str) const -{ - str << nf; -} +void ExprFloat::show(std::ostream& str) const { str << nf; } -void ExprString::show(std::ostream & str) const -{ - showString(str, s); -} +void ExprString::show(std::ostream& str) const { showString(str, s); } -void ExprPath::show(std::ostream & str) const -{ - str << s; -} +void ExprPath::show(std::ostream& str) const { str << s; } -void ExprVar::show(std::ostream & str) const -{ - str << name; -} +void ExprVar::show(std::ostream& str) const { str << name; } -void ExprSelect::show(std::ostream & str) const -{ - str << "(" << *e << ")." << showAttrPath(attrPath); - if (def) str << " or (" << *def << ")"; +void ExprSelect::show(std::ostream& str) const { + str << "(" << *e << ")." << showAttrPath(attrPath); + if (def) str << " or (" << *def << ")"; } -void ExprOpHasAttr::show(std::ostream & str) const -{ - str << "((" << *e << ") ? " << showAttrPath(attrPath) << ")"; +void ExprOpHasAttr::show(std::ostream& str) const { + str << "((" << *e << ") ? " << showAttrPath(attrPath) << ")"; } -void ExprAttrs::show(std::ostream & str) const -{ - if (recursive) str << "rec "; - str << "{ "; - for (auto & i : attrs) - if (i.second.inherited) - str << "inherit " << i.first << " " << "; "; - else - str << i.first << " = " << *i.second.e << "; "; - for (auto & i : dynamicAttrs) - str << "\"${" << *i.nameExpr << "}\" = " << *i.valueExpr << "; "; - str << "}"; +void ExprAttrs::show(std::ostream& str) const { + if (recursive) str << "rec "; + str << "{ "; + for (auto& i : attrs) + if (i.second.inherited) + str << "inherit " << i.first << " " + << "; "; + else + str << i.first << " = " << *i.second.e << "; "; + for (auto& i : dynamicAttrs) + str << "\"${" << *i.nameExpr << "}\" = " << *i.valueExpr << "; "; + str << "}"; } -void ExprList::show(std::ostream & str) const -{ - str << "[ "; - for (auto & i : elems) - str << "(" << *i << ") "; - str << "]"; +void ExprList::show(std::ostream& str) const { + str << "[ "; + for (auto& i : elems) str << "(" << *i << ") "; + str << "]"; } -void ExprLambda::show(std::ostream & str) const -{ - str << "("; - if (matchAttrs) { - str << "{ "; - bool first = true; - for (auto & i : formals->formals) { - if (first) first = false; else str << ", "; - str << i.name; - if (i.def) str << " ? " << *i.def; - } - if (formals->ellipsis) { - if (!first) str << ", "; - str << "..."; - } - str << " }"; - if (!arg.empty()) str << " @ "; +void ExprLambda::show(std::ostream& str) const { + str << "("; + if (matchAttrs) { + str << "{ "; + bool first = true; + for (auto& i : formals->formals) { + if (first) + first = false; + else + str << ", "; + str << i.name; + if (i.def) str << " ? " << *i.def; } - if (!arg.empty()) str << arg; - str << ": " << *body << ")"; -} - -void ExprLet::show(std::ostream & str) const -{ - str << "(let "; - for (auto & i : attrs->attrs) - if (i.second.inherited) { - str << "inherit " << i.first << "; "; - } - else - str << i.first << " = " << *i.second.e << "; "; - str << "in " << *body << ")"; -} - -void ExprWith::show(std::ostream & str) const -{ - str << "(with " << *attrs << "; " << *body << ")"; -} - -void ExprIf::show(std::ostream & str) const -{ - str << "(if " << *cond << " then " << *then << " else " << *else_ << ")"; + if (formals->ellipsis) { + if (!first) str << ", "; + str << "..."; + } + str << " }"; + if (!arg.empty()) str << " @ "; + } + if (!arg.empty()) str << arg; + str << ": " << *body << ")"; } -void ExprAssert::show(std::ostream & str) const -{ - str << "assert " << *cond << "; " << *body; +void ExprLet::show(std::ostream& str) const { + str << "(let "; + for (auto& i : attrs->attrs) + if (i.second.inherited) { + str << "inherit " << i.first << "; "; + } else + str << i.first << " = " << *i.second.e << "; "; + str << "in " << *body << ")"; } -void ExprOpNot::show(std::ostream & str) const -{ - str << "(! " << *e << ")"; +void ExprWith::show(std::ostream& str) const { + str << "(with " << *attrs << "; " << *body << ")"; } -void ExprConcatStrings::show(std::ostream & str) const -{ - bool first = true; - str << "("; - for (auto & i : *es) { - if (first) first = false; else str << " + "; - str << *i; - } - str << ")"; +void ExprIf::show(std::ostream& str) const { + str << "(if " << *cond << " then " << *then << " else " << *else_ << ")"; } -void ExprPos::show(std::ostream & str) const -{ - str << "__curPos"; +void ExprAssert::show(std::ostream& str) const { + str << "assert " << *cond << "; " << *body; } +void ExprOpNot::show(std::ostream& str) const { str << "(! " << *e << ")"; } -std::ostream & operator << (std::ostream & str, const Pos & pos) -{ - if (!pos) - str << "undefined position"; +void ExprConcatStrings::show(std::ostream& str) const { + bool first = true; + str << "("; + for (auto& i : *es) { + if (first) + first = false; else - str << (format(ANSI_BOLD "%1%" ANSI_NORMAL ":%2%:%3%") % (string) pos.file % pos.line % pos.column).str(); - return str; -} - - -string showAttrPath(const AttrPath & attrPath) -{ - std::ostringstream out; - bool first = true; - for (auto & i : attrPath) { - if (!first) out << '.'; else first = false; - if (i.symbol.set()) - out << i.symbol; - else - out << "\"${" << *i.expr << "}\""; - } - return out.str(); + str << " + "; + str << *i; + } + str << ")"; +} + +void ExprPos::show(std::ostream& str) const { str << "__curPos"; } + +std::ostream& operator<<(std::ostream& str, const Pos& pos) { + if (!pos) + str << "undefined position"; + else + str << (format(ANSI_BOLD "%1%" ANSI_NORMAL ":%2%:%3%") % (string)pos.file % + pos.line % pos.column) + .str(); + return str; +} + +string showAttrPath(const AttrPath& attrPath) { + std::ostringstream out; + bool first = true; + for (auto& i : attrPath) { + if (!first) + out << '.'; + else + first = false; + if (i.symbol.set()) + out << i.symbol; + else + out << "\"${" << *i.expr << "}\""; + } + return out.str(); } - Pos noPos; - /* Computing levels/displacements for variables. */ -void Expr::bindVars(const StaticEnv & env) -{ - abort(); -} - -void ExprInt::bindVars(const StaticEnv & env) -{ -} - -void ExprFloat::bindVars(const StaticEnv & env) -{ -} - -void ExprString::bindVars(const StaticEnv & env) -{ -} - -void ExprPath::bindVars(const StaticEnv & env) -{ -} - -void ExprVar::bindVars(const StaticEnv & env) -{ - /* Check whether the variable appears in the environment. If so, - set its level and displacement. */ - const StaticEnv * curEnv; - unsigned int level; - int withLevel = -1; - for (curEnv = &env, level = 0; curEnv; curEnv = curEnv->up, level++) { - if (curEnv->isWith) { - if (withLevel == -1) withLevel = level; - } else { - StaticEnv::Vars::const_iterator i = curEnv->vars.find(name); - if (i != curEnv->vars.end()) { - fromWith = false; - this->level = level; - displ = i->second; - return; - } - } +void Expr::bindVars(const StaticEnv& env) { abort(); } + +void ExprInt::bindVars(const StaticEnv& env) {} + +void ExprFloat::bindVars(const StaticEnv& env) {} + +void ExprString::bindVars(const StaticEnv& env) {} + +void ExprPath::bindVars(const StaticEnv& env) {} + +void ExprVar::bindVars(const StaticEnv& env) { + /* Check whether the variable appears in the environment. If so, + set its level and displacement. */ + const StaticEnv* curEnv; + unsigned int level; + int withLevel = -1; + for (curEnv = &env, level = 0; curEnv; curEnv = curEnv->up, level++) { + if (curEnv->isWith) { + if (withLevel == -1) withLevel = level; + } else { + StaticEnv::Vars::const_iterator i = curEnv->vars.find(name); + if (i != curEnv->vars.end()) { + fromWith = false; + this->level = level; + displ = i->second; + return; + } } + } - /* Otherwise, the variable must be obtained from the nearest - enclosing `with'. If there is no `with', then we can issue an - "undefined variable" error now. */ - if (withLevel == -1) throw UndefinedVarError(format("undefined variable '%1%' at %2%") % name % pos); + /* Otherwise, the variable must be obtained from the nearest + enclosing `with'. If there is no `with', then we can issue an + "undefined variable" error now. */ + if (withLevel == -1) + throw UndefinedVarError(format("undefined variable '%1%' at %2%") % name % + pos); - fromWith = true; - this->level = withLevel; + fromWith = true; + this->level = withLevel; } -void ExprSelect::bindVars(const StaticEnv & env) -{ - e->bindVars(env); - if (def) def->bindVars(env); - for (auto & i : attrPath) - if (!i.symbol.set()) - i.expr->bindVars(env); +void ExprSelect::bindVars(const StaticEnv& env) { + e->bindVars(env); + if (def) def->bindVars(env); + for (auto& i : attrPath) + if (!i.symbol.set()) i.expr->bindVars(env); } -void ExprOpHasAttr::bindVars(const StaticEnv & env) -{ - e->bindVars(env); - for (auto & i : attrPath) - if (!i.symbol.set()) - i.expr->bindVars(env); +void ExprOpHasAttr::bindVars(const StaticEnv& env) { + e->bindVars(env); + for (auto& i : attrPath) + if (!i.symbol.set()) i.expr->bindVars(env); } -void ExprAttrs::bindVars(const StaticEnv & env) -{ - const StaticEnv * dynamicEnv = &env; - StaticEnv newEnv(false, &env); +void ExprAttrs::bindVars(const StaticEnv& env) { + const StaticEnv* dynamicEnv = &env; + StaticEnv newEnv(false, &env); - if (recursive) { - dynamicEnv = &newEnv; + if (recursive) { + dynamicEnv = &newEnv; - unsigned int displ = 0; - for (auto & i : attrs) - newEnv.vars[i.first] = i.second.displ = displ++; + unsigned int displ = 0; + for (auto& i : attrs) newEnv.vars[i.first] = i.second.displ = displ++; - for (auto & i : attrs) - i.second.e->bindVars(i.second.inherited ? env : newEnv); - } + for (auto& i : attrs) + i.second.e->bindVars(i.second.inherited ? env : newEnv); + } - else - for (auto & i : attrs) - i.second.e->bindVars(env); + else + for (auto& i : attrs) i.second.e->bindVars(env); - for (auto & i : dynamicAttrs) { - i.nameExpr->bindVars(*dynamicEnv); - i.valueExpr->bindVars(*dynamicEnv); - } + for (auto& i : dynamicAttrs) { + i.nameExpr->bindVars(*dynamicEnv); + i.valueExpr->bindVars(*dynamicEnv); + } } -void ExprList::bindVars(const StaticEnv & env) -{ - for (auto & i : elems) - i->bindVars(env); +void ExprList::bindVars(const StaticEnv& env) { + for (auto& i : elems) i->bindVars(env); } -void ExprLambda::bindVars(const StaticEnv & env) -{ - StaticEnv newEnv(false, &env); +void ExprLambda::bindVars(const StaticEnv& env) { + StaticEnv newEnv(false, &env); - unsigned int displ = 0; + unsigned int displ = 0; - if (!arg.empty()) newEnv.vars[arg] = displ++; + if (!arg.empty()) newEnv.vars[arg] = displ++; - if (matchAttrs) { - for (auto & i : formals->formals) - newEnv.vars[i.name] = displ++; + if (matchAttrs) { + for (auto& i : formals->formals) newEnv.vars[i.name] = displ++; - for (auto & i : formals->formals) - if (i.def) i.def->bindVars(newEnv); - } + for (auto& i : formals->formals) + if (i.def) i.def->bindVars(newEnv); + } - body->bindVars(newEnv); + body->bindVars(newEnv); } -void ExprLet::bindVars(const StaticEnv & env) -{ - StaticEnv newEnv(false, &env); +void ExprLet::bindVars(const StaticEnv& env) { + StaticEnv newEnv(false, &env); - unsigned int displ = 0; - for (auto & i : attrs->attrs) - newEnv.vars[i.first] = i.second.displ = displ++; + unsigned int displ = 0; + for (auto& i : attrs->attrs) newEnv.vars[i.first] = i.second.displ = displ++; - for (auto & i : attrs->attrs) - i.second.e->bindVars(i.second.inherited ? env : newEnv); + for (auto& i : attrs->attrs) + i.second.e->bindVars(i.second.inherited ? env : newEnv); - body->bindVars(newEnv); + body->bindVars(newEnv); } -void ExprWith::bindVars(const StaticEnv & env) -{ - /* Does this `with' have an enclosing `with'? If so, record its - level so that `lookupVar' can look up variables in the previous - `with' if this one doesn't contain the desired attribute. */ - const StaticEnv * curEnv; - unsigned int level; - prevWith = 0; - for (curEnv = &env, level = 1; curEnv; curEnv = curEnv->up, level++) - if (curEnv->isWith) { - prevWith = level; - break; - } - - attrs->bindVars(env); - StaticEnv newEnv(true, &env); - body->bindVars(newEnv); -} +void ExprWith::bindVars(const StaticEnv& env) { + /* Does this `with' have an enclosing `with'? If so, record its + level so that `lookupVar' can look up variables in the previous + `with' if this one doesn't contain the desired attribute. */ + const StaticEnv* curEnv; + unsigned int level; + prevWith = 0; + for (curEnv = &env, level = 1; curEnv; curEnv = curEnv->up, level++) + if (curEnv->isWith) { + prevWith = level; + break; + } -void ExprIf::bindVars(const StaticEnv & env) -{ - cond->bindVars(env); - then->bindVars(env); - else_->bindVars(env); + attrs->bindVars(env); + StaticEnv newEnv(true, &env); + body->bindVars(newEnv); } -void ExprAssert::bindVars(const StaticEnv & env) -{ - cond->bindVars(env); - body->bindVars(env); +void ExprIf::bindVars(const StaticEnv& env) { + cond->bindVars(env); + then->bindVars(env); + else_->bindVars(env); } -void ExprOpNot::bindVars(const StaticEnv & env) -{ - e->bindVars(env); +void ExprAssert::bindVars(const StaticEnv& env) { + cond->bindVars(env); + body->bindVars(env); } -void ExprConcatStrings::bindVars(const StaticEnv & env) -{ - for (auto & i : *es) - i->bindVars(env); -} +void ExprOpNot::bindVars(const StaticEnv& env) { e->bindVars(env); } -void ExprPos::bindVars(const StaticEnv & env) -{ +void ExprConcatStrings::bindVars(const StaticEnv& env) { + for (auto& i : *es) i->bindVars(env); } +void ExprPos::bindVars(const StaticEnv& env) {} /* Storing function names. */ -void Expr::setName(Symbol & name) -{ -} - +void Expr::setName(Symbol& name) {} -void ExprLambda::setName(Symbol & name) -{ - this->name = name; - body->setName(name); +void ExprLambda::setName(Symbol& name) { + this->name = name; + body->setName(name); } - -string ExprLambda::showNamePos() const -{ - return (format("%1% at %2%") % (name.set() ? "'" + (string) name + "'" : "anonymous function") % pos).str(); +string ExprLambda::showNamePos() const { + return (format("%1% at %2%") % + (name.set() ? "'" + (string)name + "'" : "anonymous function") % pos) + .str(); } - - /* Symbol table. */ -size_t SymbolTable::totalSize() const -{ - size_t n = 0; - for (auto & i : symbols) - n += i.size(); - return n; +size_t SymbolTable::totalSize() const { + size_t n = 0; + for (auto& i : symbols) n += i.size(); + return n; } - -} +} // namespace nix diff --git a/third_party/nix/src/libexpr/nixexpr.hh b/third_party/nix/src/libexpr/nixexpr.hh index 665a42987dc1..efc4398c2371 100644 --- a/third_party/nix/src/libexpr/nixexpr.hh +++ b/third_party/nix/src/libexpr/nixexpr.hh @@ -1,342 +1,309 @@ #pragma once -#include "value.hh" -#include "symbol-table.hh" - #include <map> - +#include "symbol-table.hh" +#include "value.hh" namespace nix { - -MakeError(EvalError, Error) -MakeError(ParseError, Error) -MakeError(AssertionError, EvalError) -MakeError(ThrownError, AssertionError) -MakeError(Abort, EvalError) -MakeError(TypeError, EvalError) -MakeError(UndefinedVarError, Error) -MakeError(RestrictedPathError, Error) - - -/* Position objects. */ - -struct Pos -{ - Symbol file; - unsigned int line, column; - Pos() : line(0), column(0) { }; - Pos(const Symbol & file, unsigned int line, unsigned int column) - : file(file), line(line), column(column) { }; - operator bool() const - { - return line != 0; - } - bool operator < (const Pos & p2) const - { - if (!line) return p2.line; - if (!p2.line) return false; - int d = ((string) file).compare((string) p2.file); - if (d < 0) return true; - if (d > 0) return false; - if (line < p2.line) return true; - if (line > p2.line) return false; - return column < p2.column; - } +MakeError(EvalError, Error) MakeError(ParseError, Error) + MakeError(AssertionError, EvalError) MakeError(ThrownError, AssertionError) + MakeError(Abort, EvalError) MakeError(TypeError, EvalError) + MakeError(UndefinedVarError, Error) + MakeError(RestrictedPathError, Error) + + /* Position objects. */ + + struct Pos { + Symbol file; + unsigned int line, column; + Pos() : line(0), column(0){}; + Pos(const Symbol& file, unsigned int line, unsigned int column) + : file(file), line(line), column(column){}; + operator bool() const { return line != 0; } + bool operator<(const Pos& p2) const { + if (!line) return p2.line; + if (!p2.line) return false; + int d = ((string)file).compare((string)p2.file); + if (d < 0) return true; + if (d > 0) return false; + if (line < p2.line) return true; + if (line > p2.line) return false; + return column < p2.column; + } }; extern Pos noPos; -std::ostream & operator << (std::ostream & str, const Pos & pos); - +std::ostream& operator<<(std::ostream& str, const Pos& pos); struct Env; struct Value; class EvalState; struct StaticEnv; - /* An attribute path is a sequence of attribute names. */ -struct AttrName -{ - Symbol symbol; - Expr * expr; - AttrName(const Symbol & s) : symbol(s) {}; - AttrName(Expr * e) : expr(e) {}; +struct AttrName { + Symbol symbol; + Expr* expr; + AttrName(const Symbol& s) : symbol(s){}; + AttrName(Expr* e) : expr(e){}; }; typedef std::vector<AttrName> AttrPath; -string showAttrPath(const AttrPath & attrPath); - +string showAttrPath(const AttrPath& attrPath); /* Abstract syntax of Nix expressions. */ -struct Expr -{ - virtual ~Expr() { }; - virtual void show(std::ostream & str) const; - virtual void bindVars(const StaticEnv & env); - virtual void eval(EvalState & state, Env & env, Value & v); - virtual Value * maybeThunk(EvalState & state, Env & env); - virtual void setName(Symbol & name); +struct Expr { + virtual ~Expr(){}; + virtual void show(std::ostream& str) const; + virtual void bindVars(const StaticEnv& env); + virtual void eval(EvalState& state, Env& env, Value& v); + virtual Value* maybeThunk(EvalState& state, Env& env); + virtual void setName(Symbol& name); }; -std::ostream & operator << (std::ostream & str, const Expr & e); +std::ostream& operator<<(std::ostream& str, const Expr& e); -#define COMMON_METHODS \ - void show(std::ostream & str) const; \ - void eval(EvalState & state, Env & env, Value & v); \ - void bindVars(const StaticEnv & env); +#define COMMON_METHODS \ + void show(std::ostream& str) const; \ + void eval(EvalState& state, Env& env, Value& v); \ + void bindVars(const StaticEnv& env); -struct ExprInt : Expr -{ - NixInt n; - Value v; - ExprInt(NixInt n) : n(n) { mkInt(v, n); }; - COMMON_METHODS - Value * maybeThunk(EvalState & state, Env & env); +struct ExprInt : Expr { + NixInt n; + Value v; + ExprInt(NixInt n) : n(n) { mkInt(v, n); }; + COMMON_METHODS + Value* maybeThunk(EvalState& state, Env& env); }; -struct ExprFloat : Expr -{ - NixFloat nf; - Value v; - ExprFloat(NixFloat nf) : nf(nf) { mkFloat(v, nf); }; - COMMON_METHODS - Value * maybeThunk(EvalState & state, Env & env); +struct ExprFloat : Expr { + NixFloat nf; + Value v; + ExprFloat(NixFloat nf) : nf(nf) { mkFloat(v, nf); }; + COMMON_METHODS + Value* maybeThunk(EvalState& state, Env& env); }; -struct ExprString : Expr -{ - Symbol s; - Value v; - ExprString(const Symbol & s) : s(s) { mkString(v, s); }; - COMMON_METHODS - Value * maybeThunk(EvalState & state, Env & env); +struct ExprString : Expr { + Symbol s; + Value v; + ExprString(const Symbol& s) : s(s) { mkString(v, s); }; + COMMON_METHODS + Value* maybeThunk(EvalState& state, Env& env); }; /* Temporary class used during parsing of indented strings. */ -struct ExprIndStr : Expr -{ - string s; - ExprIndStr(const string & s) : s(s) { }; +struct ExprIndStr : Expr { + string s; + ExprIndStr(const string& s) : s(s){}; }; -struct ExprPath : Expr -{ - string s; - Value v; - ExprPath(const string & s) : s(s) { mkPathNoCopy(v, this->s.c_str()); }; - COMMON_METHODS - Value * maybeThunk(EvalState & state, Env & env); +struct ExprPath : Expr { + string s; + Value v; + ExprPath(const string& s) : s(s) { mkPathNoCopy(v, this->s.c_str()); }; + COMMON_METHODS + Value* maybeThunk(EvalState& state, Env& env); }; -struct ExprVar : Expr -{ - Pos pos; - Symbol name; - - /* Whether the variable comes from an environment (e.g. a rec, let - or function argument) or from a "with". */ - bool fromWith; - - /* In the former case, the value is obtained by going `level' - levels up from the current environment and getting the - `displ'th value in that environment. In the latter case, the - value is obtained by getting the attribute named `name' from - the set stored in the environment that is `level' levels up - from the current one.*/ - unsigned int level; - unsigned int displ; - - ExprVar(const Symbol & name) : name(name) { }; - ExprVar(const Pos & pos, const Symbol & name) : pos(pos), name(name) { }; - COMMON_METHODS - Value * maybeThunk(EvalState & state, Env & env); +struct ExprVar : Expr { + Pos pos; + Symbol name; + + /* Whether the variable comes from an environment (e.g. a rec, let + or function argument) or from a "with". */ + bool fromWith; + + /* In the former case, the value is obtained by going `level' + levels up from the current environment and getting the + `displ'th value in that environment. In the latter case, the + value is obtained by getting the attribute named `name' from + the set stored in the environment that is `level' levels up + from the current one.*/ + unsigned int level; + unsigned int displ; + + ExprVar(const Symbol& name) : name(name){}; + ExprVar(const Pos& pos, const Symbol& name) : pos(pos), name(name){}; + COMMON_METHODS + Value* maybeThunk(EvalState& state, Env& env); }; -struct ExprSelect : Expr -{ - Pos pos; - Expr * e, * def; - AttrPath attrPath; - ExprSelect(const Pos & pos, Expr * e, const AttrPath & attrPath, Expr * def) : pos(pos), e(e), def(def), attrPath(attrPath) { }; - ExprSelect(const Pos & pos, Expr * e, const Symbol & name) : pos(pos), e(e), def(0) { attrPath.push_back(AttrName(name)); }; - COMMON_METHODS +struct ExprSelect : Expr { + Pos pos; + Expr *e, *def; + AttrPath attrPath; + ExprSelect(const Pos& pos, Expr* e, const AttrPath& attrPath, Expr* def) + : pos(pos), e(e), def(def), attrPath(attrPath){}; + ExprSelect(const Pos& pos, Expr* e, const Symbol& name) + : pos(pos), e(e), def(0) { + attrPath.push_back(AttrName(name)); + }; + COMMON_METHODS }; -struct ExprOpHasAttr : Expr -{ - Expr * e; - AttrPath attrPath; - ExprOpHasAttr(Expr * e, const AttrPath & attrPath) : e(e), attrPath(attrPath) { }; - COMMON_METHODS +struct ExprOpHasAttr : Expr { + Expr* e; + AttrPath attrPath; + ExprOpHasAttr(Expr* e, const AttrPath& attrPath) : e(e), attrPath(attrPath){}; + COMMON_METHODS }; -struct ExprAttrs : Expr -{ - bool recursive; - struct AttrDef { - bool inherited; - Expr * e; - Pos pos; - unsigned int displ; // displacement - AttrDef(Expr * e, const Pos & pos, bool inherited=false) - : inherited(inherited), e(e), pos(pos) { }; - AttrDef() { }; - }; - typedef std::map<Symbol, AttrDef> AttrDefs; - AttrDefs attrs; - struct DynamicAttrDef { - Expr * nameExpr, * valueExpr; - Pos pos; - DynamicAttrDef(Expr * nameExpr, Expr * valueExpr, const Pos & pos) - : nameExpr(nameExpr), valueExpr(valueExpr), pos(pos) { }; - }; - typedef std::vector<DynamicAttrDef> DynamicAttrDefs; - DynamicAttrDefs dynamicAttrs; - ExprAttrs() : recursive(false) { }; - COMMON_METHODS +struct ExprAttrs : Expr { + bool recursive; + struct AttrDef { + bool inherited; + Expr* e; + Pos pos; + unsigned int displ; // displacement + AttrDef(Expr* e, const Pos& pos, bool inherited = false) + : inherited(inherited), e(e), pos(pos){}; + AttrDef(){}; + }; + typedef std::map<Symbol, AttrDef> AttrDefs; + AttrDefs attrs; + struct DynamicAttrDef { + Expr *nameExpr, *valueExpr; + Pos pos; + DynamicAttrDef(Expr* nameExpr, Expr* valueExpr, const Pos& pos) + : nameExpr(nameExpr), valueExpr(valueExpr), pos(pos){}; + }; + typedef std::vector<DynamicAttrDef> DynamicAttrDefs; + DynamicAttrDefs dynamicAttrs; + ExprAttrs() : recursive(false){}; + COMMON_METHODS }; -struct ExprList : Expr -{ - std::vector<Expr *> elems; - ExprList() { }; - COMMON_METHODS +struct ExprList : Expr { + std::vector<Expr*> elems; + ExprList(){}; + COMMON_METHODS }; -struct Formal -{ - Symbol name; - Expr * def; - Formal(const Symbol & name, Expr * def) : name(name), def(def) { }; +struct Formal { + Symbol name; + Expr* def; + Formal(const Symbol& name, Expr* def) : name(name), def(def){}; }; -struct Formals -{ - typedef std::list<Formal> Formals_; - Formals_ formals; - std::set<Symbol> argNames; // used during parsing - bool ellipsis; +struct Formals { + typedef std::list<Formal> Formals_; + Formals_ formals; + std::set<Symbol> argNames; // used during parsing + bool ellipsis; }; -struct ExprLambda : Expr -{ - Pos pos; - Symbol name; - Symbol arg; - bool matchAttrs; - Formals * formals; - Expr * body; - ExprLambda(const Pos & pos, const Symbol & arg, bool matchAttrs, Formals * formals, Expr * body) - : pos(pos), arg(arg), matchAttrs(matchAttrs), formals(formals), body(body) - { - if (!arg.empty() && formals && formals->argNames.find(arg) != formals->argNames.end()) - throw ParseError(format("duplicate formal function argument '%1%' at %2%") - % arg % pos); - }; - void setName(Symbol & name); - string showNamePos() const; - COMMON_METHODS +struct ExprLambda : Expr { + Pos pos; + Symbol name; + Symbol arg; + bool matchAttrs; + Formals* formals; + Expr* body; + ExprLambda(const Pos& pos, const Symbol& arg, bool matchAttrs, + Formals* formals, Expr* body) + : pos(pos), + arg(arg), + matchAttrs(matchAttrs), + formals(formals), + body(body) { + if (!arg.empty() && formals && + formals->argNames.find(arg) != formals->argNames.end()) + throw ParseError( + format("duplicate formal function argument '%1%' at %2%") % arg % + pos); + }; + void setName(Symbol& name); + string showNamePos() const; + COMMON_METHODS }; -struct ExprLet : Expr -{ - ExprAttrs * attrs; - Expr * body; - ExprLet(ExprAttrs * attrs, Expr * body) : attrs(attrs), body(body) { }; - COMMON_METHODS +struct ExprLet : Expr { + ExprAttrs* attrs; + Expr* body; + ExprLet(ExprAttrs* attrs, Expr* body) : attrs(attrs), body(body){}; + COMMON_METHODS }; -struct ExprWith : Expr -{ - Pos pos; - Expr * attrs, * body; - size_t prevWith; - ExprWith(const Pos & pos, Expr * attrs, Expr * body) : pos(pos), attrs(attrs), body(body) { }; - COMMON_METHODS +struct ExprWith : Expr { + Pos pos; + Expr *attrs, *body; + size_t prevWith; + ExprWith(const Pos& pos, Expr* attrs, Expr* body) + : pos(pos), attrs(attrs), body(body){}; + COMMON_METHODS }; -struct ExprIf : Expr -{ - Expr * cond, * then, * else_; - ExprIf(Expr * cond, Expr * then, Expr * else_) : cond(cond), then(then), else_(else_) { }; - COMMON_METHODS +struct ExprIf : Expr { + Expr *cond, *then, *else_; + ExprIf(Expr* cond, Expr* then, Expr* else_) + : cond(cond), then(then), else_(else_){}; + COMMON_METHODS }; -struct ExprAssert : Expr -{ - Pos pos; - Expr * cond, * body; - ExprAssert(const Pos & pos, Expr * cond, Expr * body) : pos(pos), cond(cond), body(body) { }; - COMMON_METHODS +struct ExprAssert : Expr { + Pos pos; + Expr *cond, *body; + ExprAssert(const Pos& pos, Expr* cond, Expr* body) + : pos(pos), cond(cond), body(body){}; + COMMON_METHODS }; -struct ExprOpNot : Expr -{ - Expr * e; - ExprOpNot(Expr * e) : e(e) { }; - COMMON_METHODS +struct ExprOpNot : Expr { + Expr* e; + ExprOpNot(Expr* e) : e(e){}; + COMMON_METHODS }; -#define MakeBinOp(name, s) \ - struct name : Expr \ - { \ - Pos pos; \ - Expr * e1, * e2; \ - name(Expr * e1, Expr * e2) : e1(e1), e2(e2) { }; \ - name(const Pos & pos, Expr * e1, Expr * e2) : pos(pos), e1(e1), e2(e2) { }; \ - void show(std::ostream & str) const \ - { \ - str << "(" << *e1 << " " s " " << *e2 << ")"; \ - } \ - void bindVars(const StaticEnv & env) \ - { \ - e1->bindVars(env); e2->bindVars(env); \ - } \ - void eval(EvalState & state, Env & env, Value & v); \ - }; - -MakeBinOp(ExprApp, "") -MakeBinOp(ExprOpEq, "==") -MakeBinOp(ExprOpNEq, "!=") -MakeBinOp(ExprOpAnd, "&&") -MakeBinOp(ExprOpOr, "||") -MakeBinOp(ExprOpImpl, "->") -MakeBinOp(ExprOpUpdate, "//") -MakeBinOp(ExprOpConcatLists, "++") - -struct ExprConcatStrings : Expr -{ - Pos pos; - bool forceString; - vector<Expr *> * es; - ExprConcatStrings(const Pos & pos, bool forceString, vector<Expr *> * es) - : pos(pos), forceString(forceString), es(es) { }; - COMMON_METHODS +#define MakeBinOp(name, s) \ + struct name : Expr { \ + Pos pos; \ + Expr *e1, *e2; \ + name(Expr* e1, Expr* e2) : e1(e1), e2(e2){}; \ + name(const Pos& pos, Expr* e1, Expr* e2) : pos(pos), e1(e1), e2(e2){}; \ + void show(std::ostream& str) const { \ + str << "(" << *e1 << " " s " " << *e2 << ")"; \ + } \ + void bindVars(const StaticEnv& env) { \ + e1->bindVars(env); \ + e2->bindVars(env); \ + } \ + void eval(EvalState& state, Env& env, Value& v); \ + }; + +MakeBinOp(ExprApp, "") MakeBinOp(ExprOpEq, "==") MakeBinOp(ExprOpNEq, "!=") + MakeBinOp(ExprOpAnd, "&&") MakeBinOp(ExprOpOr, "||") + MakeBinOp(ExprOpImpl, "->") MakeBinOp(ExprOpUpdate, "//") + MakeBinOp(ExprOpConcatLists, "++") + + struct ExprConcatStrings : Expr { + Pos pos; + bool forceString; + vector<Expr*>* es; + ExprConcatStrings(const Pos& pos, bool forceString, vector<Expr*>* es) + : pos(pos), forceString(forceString), es(es){}; + COMMON_METHODS }; -struct ExprPos : Expr -{ - Pos pos; - ExprPos(const Pos & pos) : pos(pos) { }; - COMMON_METHODS +struct ExprPos : Expr { + Pos pos; + ExprPos(const Pos& pos) : pos(pos){}; + COMMON_METHODS }; - /* Static environments are used to map variable names onto (level, displacement) pairs used to obtain the value of the variable at runtime. */ -struct StaticEnv -{ - bool isWith; - const StaticEnv * up; - typedef std::map<Symbol, unsigned int> Vars; - Vars vars; - StaticEnv(bool isWith, const StaticEnv * up) : isWith(isWith), up(up) { }; +struct StaticEnv { + bool isWith; + const StaticEnv* up; + typedef std::map<Symbol, unsigned int> Vars; + Vars vars; + StaticEnv(bool isWith, const StaticEnv* up) : isWith(isWith), up(up){}; }; - -} +} // namespace nix diff --git a/third_party/nix/src/libexpr/primops.cc b/third_party/nix/src/libexpr/primops.cc index d4c60f870ed2..9d6d4eaba5ee 100644 --- a/third_party/nix/src/libexpr/primops.cc +++ b/third_party/nix/src/libexpr/primops.cc @@ -1,3 +1,11 @@ +#include "primops.hh" +#include <dlfcn.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> +#include <algorithm> +#include <cstring> +#include <regex> #include "archive.hh" #include "derivations.hh" #include "download.hh" @@ -5,520 +13,514 @@ #include "eval.hh" #include "globals.hh" #include "json-to-value.hh" +#include "json.hh" #include "names.hh" #include "store-api.hh" #include "util.hh" -#include "json.hh" #include "value-to-json.hh" #include "value-to-xml.hh" -#include "primops.hh" - -#include <sys/types.h> -#include <sys/stat.h> -#include <unistd.h> - -#include <algorithm> -#include <cstring> -#include <regex> -#include <dlfcn.h> - namespace nix { - /************************************************************* * Miscellaneous *************************************************************/ - /* Decode a context string ‘!<name>!<path>’ into a pair <path, name>. */ -std::pair<string, string> decodeContext(const string & s) -{ - if (s.at(0) == '!') { - size_t index = s.find("!", 1); - return std::pair<string, string>(string(s, index + 1), string(s, 1, index - 1)); - } else - return std::pair<string, string>(s.at(0) == '/' ? s : string(s, 1), ""); -} - - -InvalidPathError::InvalidPathError(const Path & path) : - EvalError(format("path '%1%' is not valid") % path), path(path) {} - -void EvalState::realiseContext(const PathSet & context) -{ - PathSet drvs; - - for (auto & i : context) { - std::pair<string, string> decoded = decodeContext(i); - Path ctx = decoded.first; - assert(store->isStorePath(ctx)); - if (!store->isValidPath(ctx)) - throw InvalidPathError(ctx); - if (!decoded.second.empty() && nix::isDerivation(ctx)) { - drvs.insert(decoded.first + "!" + decoded.second); - - /* Add the output of this derivation to the allowed - paths. */ - if (allowedPaths) { - auto drv = store->derivationFromPath(decoded.first); - DerivationOutputs::iterator i = drv.outputs.find(decoded.second); - if (i == drv.outputs.end()) - throw Error("derivation '%s' does not have an output named '%s'", decoded.first, decoded.second); - allowedPaths->insert(i->second.path); - } - } +std::pair<string, string> decodeContext(const string& s) { + if (s.at(0) == '!') { + size_t index = s.find("!", 1); + return std::pair<string, string>(string(s, index + 1), + string(s, 1, index - 1)); + } else + return std::pair<string, string>(s.at(0) == '/' ? s : string(s, 1), ""); +} + +InvalidPathError::InvalidPathError(const Path& path) + : EvalError(format("path '%1%' is not valid") % path), path(path) {} + +void EvalState::realiseContext(const PathSet& context) { + PathSet drvs; + + for (auto& i : context) { + std::pair<string, string> decoded = decodeContext(i); + Path ctx = decoded.first; + assert(store->isStorePath(ctx)); + if (!store->isValidPath(ctx)) throw InvalidPathError(ctx); + if (!decoded.second.empty() && nix::isDerivation(ctx)) { + drvs.insert(decoded.first + "!" + decoded.second); + + /* Add the output of this derivation to the allowed + paths. */ + if (allowedPaths) { + auto drv = store->derivationFromPath(decoded.first); + DerivationOutputs::iterator i = drv.outputs.find(decoded.second); + if (i == drv.outputs.end()) + throw Error("derivation '%s' does not have an output named '%s'", + decoded.first, decoded.second); + allowedPaths->insert(i->second.path); + } } + } - if (drvs.empty()) return; + if (drvs.empty()) return; - if (!evalSettings.enableImportFromDerivation) - throw EvalError(format("attempted to realize '%1%' during evaluation but 'allow-import-from-derivation' is false") % *(drvs.begin())); + if (!evalSettings.enableImportFromDerivation) + throw EvalError(format("attempted to realize '%1%' during evaluation but " + "'allow-import-from-derivation' is false") % + *(drvs.begin())); - /* For performance, prefetch all substitute info. */ - PathSet willBuild, willSubstitute, unknown; - unsigned long long downloadSize, narSize; - store->queryMissing(drvs, willBuild, willSubstitute, unknown, downloadSize, narSize); - store->buildPaths(drvs); + /* For performance, prefetch all substitute info. */ + PathSet willBuild, willSubstitute, unknown; + unsigned long long downloadSize, narSize; + store->queryMissing(drvs, willBuild, willSubstitute, unknown, downloadSize, + narSize); + store->buildPaths(drvs); } - /* Load and evaluate an expression from path specified by the argument. */ -static void prim_scopedImport(EvalState & state, const Pos & pos, Value * * args, Value & v) -{ - PathSet context; - Path path = state.coerceToPath(pos, *args[1], context); - - try { - state.realiseContext(context); - } catch (InvalidPathError & e) { - throw EvalError(format("cannot import '%1%', since path '%2%' is not valid, at %3%") - % path % e.path % pos); +static void prim_scopedImport(EvalState& state, const Pos& pos, Value** args, + Value& v) { + PathSet context; + Path path = state.coerceToPath(pos, *args[1], context); + + try { + state.realiseContext(context); + } catch (InvalidPathError& e) { + throw EvalError( + format("cannot import '%1%', since path '%2%' is not valid, at %3%") % + path % e.path % pos); + } + + Path realPath = state.checkSourcePath(state.toRealPath(path, context)); + + if (state.store->isStorePath(path) && state.store->isValidPath(path) && + isDerivation(path)) { + Derivation drv = readDerivation(realPath); + Value& w = *state.allocValue(); + state.mkAttrs(w, 3 + drv.outputs.size()); + Value* v2 = state.allocAttr(w, state.sDrvPath); + mkString(*v2, path, {"=" + path}); + v2 = state.allocAttr(w, state.sName); + mkString(*v2, drv.env["name"]); + Value* outputsVal = state.allocAttr(w, state.symbols.create("outputs")); + state.mkList(*outputsVal, drv.outputs.size()); + unsigned int outputs_index = 0; + + for (const auto& o : drv.outputs) { + v2 = state.allocAttr(w, state.symbols.create(o.first)); + mkString(*v2, o.second.path, {"!" + o.first + "!" + path}); + outputsVal->listElems()[outputs_index] = state.allocValue(); + mkString(*(outputsVal->listElems()[outputs_index++]), o.first); } + w.attrs->sort(); + Value fun; + state.evalFile( + settings.nixDataDir + "/nix/corepkgs/imported-drv-to-derivation.nix", + fun); + state.forceFunction(fun, pos); + mkApp(v, fun, w); + state.forceAttrs(v, pos); + } else { + state.forceAttrs(*args[0]); + if (args[0]->attrs->empty()) + state.evalFile(realPath, v); + else { + Env* env = &state.allocEnv(args[0]->attrs->size()); + env->up = &state.baseEnv; - Path realPath = state.checkSourcePath(state.toRealPath(path, context)); - - if (state.store->isStorePath(path) && state.store->isValidPath(path) && isDerivation(path)) { - Derivation drv = readDerivation(realPath); - Value & w = *state.allocValue(); - state.mkAttrs(w, 3 + drv.outputs.size()); - Value * v2 = state.allocAttr(w, state.sDrvPath); - mkString(*v2, path, {"=" + path}); - v2 = state.allocAttr(w, state.sName); - mkString(*v2, drv.env["name"]); - Value * outputsVal = - state.allocAttr(w, state.symbols.create("outputs")); - state.mkList(*outputsVal, drv.outputs.size()); - unsigned int outputs_index = 0; - - for (const auto & o : drv.outputs) { - v2 = state.allocAttr(w, state.symbols.create(o.first)); - mkString(*v2, o.second.path, {"!" + o.first + "!" + path}); - outputsVal->listElems()[outputs_index] = state.allocValue(); - mkString(*(outputsVal->listElems()[outputs_index++]), o.first); - } - w.attrs->sort(); - Value fun; - state.evalFile(settings.nixDataDir + "/nix/corepkgs/imported-drv-to-derivation.nix", fun); - state.forceFunction(fun, pos); - mkApp(v, fun, w); - state.forceAttrs(v, pos); - } else { - state.forceAttrs(*args[0]); - if (args[0]->attrs->empty()) - state.evalFile(realPath, v); - else { - Env * env = &state.allocEnv(args[0]->attrs->size()); - env->up = &state.baseEnv; - - StaticEnv staticEnv(false, &state.staticBaseEnv); + StaticEnv staticEnv(false, &state.staticBaseEnv); - unsigned int displ = 0; - for (auto & attr : *args[0]->attrs) { - staticEnv.vars[attr.name] = displ; - env->values[displ++] = attr.value; - } + unsigned int displ = 0; + for (auto& attr : *args[0]->attrs) { + staticEnv.vars[attr.name] = displ; + env->values[displ++] = attr.value; + } - printTalkative("evaluating file '%1%'", realPath); - Expr * e = state.parseExprFromFile(resolveExprPath(realPath), staticEnv); + printTalkative("evaluating file '%1%'", realPath); + Expr* e = state.parseExprFromFile(resolveExprPath(realPath), staticEnv); - e->eval(state, *env, v); - } + e->eval(state, *env, v); } + } } - /* Want reasonable symbol names, so extern C */ /* !!! Should we pass the Pos or the file name too? */ -extern "C" typedef void (*ValueInitializer)(EvalState & state, Value & v); +extern "C" typedef void (*ValueInitializer)(EvalState& state, Value& v); /* Load a ValueInitializer from a DSO and return whatever it initializes */ -void prim_importNative(EvalState & state, const Pos & pos, Value * * args, Value & v) -{ - PathSet context; - Path path = state.coerceToPath(pos, *args[0], context); - - try { - state.realiseContext(context); - } catch (InvalidPathError & e) { - throw EvalError(format("cannot import '%1%', since path '%2%' is not valid, at %3%") - % path % e.path % pos); - } - - path = state.checkSourcePath(path); - - string sym = state.forceStringNoCtx(*args[1], pos); - - void *handle = dlopen(path.c_str(), RTLD_LAZY | RTLD_LOCAL); - if (!handle) - throw EvalError(format("could not open '%1%': %2%") % path % dlerror()); - - dlerror(); - ValueInitializer func = (ValueInitializer) dlsym(handle, sym.c_str()); - if(!func) { - char *message = dlerror(); - if (message) - throw EvalError(format("could not load symbol '%1%' from '%2%': %3%") % sym % path % message); - else - throw EvalError(format("symbol '%1%' from '%2%' resolved to NULL when a function pointer was expected") - % sym % path); - } +void prim_importNative(EvalState& state, const Pos& pos, Value** args, + Value& v) { + PathSet context; + Path path = state.coerceToPath(pos, *args[0], context); + + try { + state.realiseContext(context); + } catch (InvalidPathError& e) { + throw EvalError( + format("cannot import '%1%', since path '%2%' is not valid, at %3%") % + path % e.path % pos); + } + + path = state.checkSourcePath(path); + + string sym = state.forceStringNoCtx(*args[1], pos); + + void* handle = dlopen(path.c_str(), RTLD_LAZY | RTLD_LOCAL); + if (!handle) + throw EvalError(format("could not open '%1%': %2%") % path % dlerror()); + + dlerror(); + ValueInitializer func = (ValueInitializer)dlsym(handle, sym.c_str()); + if (!func) { + char* message = dlerror(); + if (message) + throw EvalError(format("could not load symbol '%1%' from '%2%': %3%") % + sym % path % message); + else + throw EvalError(format("symbol '%1%' from '%2%' resolved to NULL when a " + "function pointer was expected") % + sym % path); + } - (func)(state, v); + (func)(state, v); - /* We don't dlclose because v may be a primop referencing a function in the shared object file */ + /* We don't dlclose because v may be a primop referencing a function in the + * shared object file */ } - /* Execute a program and parse its output */ -void prim_exec(EvalState & state, const Pos & pos, Value * * args, Value & v) -{ - state.forceList(*args[0], pos); - auto elems = args[0]->listElems(); - auto count = args[0]->listSize(); - if (count == 0) { - throw EvalError(format("at least one argument to 'exec' required, at %1%") % pos); - } - PathSet context; - auto program = state.coerceToString(pos, *elems[0], context, false, false); - Strings commandArgs; - for (unsigned int i = 1; i < args[0]->listSize(); ++i) { - commandArgs.emplace_back(state.coerceToString(pos, *elems[i], context, false, false)); - } - try { - state.realiseContext(context); - } catch (InvalidPathError & e) { - throw EvalError(format("cannot execute '%1%', since path '%2%' is not valid, at %3%") - % program % e.path % pos); - } - - auto output = runProgram(program, true, commandArgs); - Expr * parsed; - try { - parsed = state.parseExprFromString(output, pos.file); - } catch (Error & e) { - e.addPrefix(format("While parsing the output from '%1%', at %2%\n") % program % pos); - throw; - } - try { - state.eval(parsed, v); - } catch (Error & e) { - e.addPrefix(format("While evaluating the output from '%1%', at %2%\n") % program % pos); - throw; - } +void prim_exec(EvalState& state, const Pos& pos, Value** args, Value& v) { + state.forceList(*args[0], pos); + auto elems = args[0]->listElems(); + auto count = args[0]->listSize(); + if (count == 0) { + throw EvalError(format("at least one argument to 'exec' required, at %1%") % + pos); + } + PathSet context; + auto program = state.coerceToString(pos, *elems[0], context, false, false); + Strings commandArgs; + for (unsigned int i = 1; i < args[0]->listSize(); ++i) { + commandArgs.emplace_back( + state.coerceToString(pos, *elems[i], context, false, false)); + } + try { + state.realiseContext(context); + } catch (InvalidPathError& e) { + throw EvalError( + format("cannot execute '%1%', since path '%2%' is not valid, at %3%") % + program % e.path % pos); + } + + auto output = runProgram(program, true, commandArgs); + Expr* parsed; + try { + parsed = state.parseExprFromString(output, pos.file); + } catch (Error& e) { + e.addPrefix(format("While parsing the output from '%1%', at %2%\n") % + program % pos); + throw; + } + try { + state.eval(parsed, v); + } catch (Error& e) { + e.addPrefix(format("While evaluating the output from '%1%', at %2%\n") % + program % pos); + throw; + } } - /* Return a string representing the type of the expression. */ -static void prim_typeOf(EvalState & state, const Pos & pos, Value * * args, Value & v) -{ - state.forceValue(*args[0]); - string t; - switch (args[0]->type) { - case tInt: t = "int"; break; - case tBool: t = "bool"; break; - case tString: t = "string"; break; - case tPath: t = "path"; break; - case tNull: t = "null"; break; - case tAttrs: t = "set"; break; - case tList1: case tList2: case tListN: t = "list"; break; - case tLambda: - case tPrimOp: - case tPrimOpApp: - t = "lambda"; - break; - case tExternal: - t = args[0]->external->typeOf(); - break; - case tFloat: t = "float"; break; - default: abort(); - } - mkString(v, state.symbols.create(t)); +static void prim_typeOf(EvalState& state, const Pos& pos, Value** args, + Value& v) { + state.forceValue(*args[0]); + string t; + switch (args[0]->type) { + case tInt: + t = "int"; + break; + case tBool: + t = "bool"; + break; + case tString: + t = "string"; + break; + case tPath: + t = "path"; + break; + case tNull: + t = "null"; + break; + case tAttrs: + t = "set"; + break; + case tList1: + case tList2: + case tListN: + t = "list"; + break; + case tLambda: + case tPrimOp: + case tPrimOpApp: + t = "lambda"; + break; + case tExternal: + t = args[0]->external->typeOf(); + break; + case tFloat: + t = "float"; + break; + default: + abort(); + } + mkString(v, state.symbols.create(t)); } - /* Determine whether the argument is the null value. */ -static void prim_isNull(EvalState & state, const Pos & pos, Value * * args, Value & v) -{ - state.forceValue(*args[0]); - mkBool(v, args[0]->type == tNull); +static void prim_isNull(EvalState& state, const Pos& pos, Value** args, + Value& v) { + state.forceValue(*args[0]); + mkBool(v, args[0]->type == tNull); } - /* Determine whether the argument is a function. */ -static void prim_isFunction(EvalState & state, const Pos & pos, Value * * args, Value & v) -{ - state.forceValue(*args[0]); - bool res; - switch (args[0]->type) { - case tLambda: - case tPrimOp: - case tPrimOpApp: - res = true; - break; - default: - res = false; - break; - } - mkBool(v, res); +static void prim_isFunction(EvalState& state, const Pos& pos, Value** args, + Value& v) { + state.forceValue(*args[0]); + bool res; + switch (args[0]->type) { + case tLambda: + case tPrimOp: + case tPrimOpApp: + res = true; + break; + default: + res = false; + break; + } + mkBool(v, res); } - /* Determine whether the argument is an integer. */ -static void prim_isInt(EvalState & state, const Pos & pos, Value * * args, Value & v) -{ - state.forceValue(*args[0]); - mkBool(v, args[0]->type == tInt); +static void prim_isInt(EvalState& state, const Pos& pos, Value** args, + Value& v) { + state.forceValue(*args[0]); + mkBool(v, args[0]->type == tInt); } /* Determine whether the argument is a float. */ -static void prim_isFloat(EvalState & state, const Pos & pos, Value * * args, Value & v) -{ - state.forceValue(*args[0]); - mkBool(v, args[0]->type == tFloat); +static void prim_isFloat(EvalState& state, const Pos& pos, Value** args, + Value& v) { + state.forceValue(*args[0]); + mkBool(v, args[0]->type == tFloat); } /* Determine whether the argument is a string. */ -static void prim_isString(EvalState & state, const Pos & pos, Value * * args, Value & v) -{ - state.forceValue(*args[0]); - mkBool(v, args[0]->type == tString); +static void prim_isString(EvalState& state, const Pos& pos, Value** args, + Value& v) { + state.forceValue(*args[0]); + mkBool(v, args[0]->type == tString); } - /* Determine whether the argument is a Boolean. */ -static void prim_isBool(EvalState & state, const Pos & pos, Value * * args, Value & v) -{ - state.forceValue(*args[0]); - mkBool(v, args[0]->type == tBool); +static void prim_isBool(EvalState& state, const Pos& pos, Value** args, + Value& v) { + state.forceValue(*args[0]); + mkBool(v, args[0]->type == tBool); } /* Determine whether the argument is a path. */ -static void prim_isPath(EvalState & state, const Pos & pos, Value * * args, Value & v) -{ - state.forceValue(*args[0]); - mkBool(v, args[0]->type == tPath); -} - -struct CompareValues -{ - bool operator () (const Value * v1, const Value * v2) const - { - if (v1->type == tFloat && v2->type == tInt) - return v1->fpoint < v2->integer; - if (v1->type == tInt && v2->type == tFloat) - return v1->integer < v2->fpoint; - if (v1->type != v2->type) - throw EvalError(format("cannot compare %1% with %2%") % showType(*v1) % showType(*v2)); - switch (v1->type) { - case tInt: - return v1->integer < v2->integer; - case tFloat: - return v1->fpoint < v2->fpoint; - case tString: - return strcmp(v1->string.s, v2->string.s) < 0; - case tPath: - return strcmp(v1->path, v2->path) < 0; - default: - throw EvalError(format("cannot compare %1% with %2%") % showType(*v1) % showType(*v2)); - } +static void prim_isPath(EvalState& state, const Pos& pos, Value** args, + Value& v) { + state.forceValue(*args[0]); + mkBool(v, args[0]->type == tPath); +} + +struct CompareValues { + bool operator()(const Value* v1, const Value* v2) const { + if (v1->type == tFloat && v2->type == tInt) return v1->fpoint < v2->integer; + if (v1->type == tInt && v2->type == tFloat) return v1->integer < v2->fpoint; + if (v1->type != v2->type) + throw EvalError(format("cannot compare %1% with %2%") % showType(*v1) % + showType(*v2)); + switch (v1->type) { + case tInt: + return v1->integer < v2->integer; + case tFloat: + return v1->fpoint < v2->fpoint; + case tString: + return strcmp(v1->string.s, v2->string.s) < 0; + case tPath: + return strcmp(v1->path, v2->path) < 0; + default: + throw EvalError(format("cannot compare %1% with %2%") % showType(*v1) % + showType(*v2)); } + } }; - #if HAVE_BOEHMGC -typedef list<Value *, gc_allocator<Value *> > ValueList; +typedef list<Value*, gc_allocator<Value*>> ValueList; #else -typedef list<Value *> ValueList; +typedef list<Value*> ValueList; #endif - -static void prim_genericClosure(EvalState & state, const Pos & pos, Value * * args, Value & v) -{ - state.forceAttrs(*args[0], pos); - - /* Get the start set. */ - Bindings::iterator startSet = - args[0]->attrs->find(state.symbols.create("startSet")); - if (startSet == args[0]->attrs->end()) - throw EvalError(format("attribute 'startSet' required, at %1%") % pos); - state.forceList(*startSet->value, pos); - - ValueList workSet; - for (unsigned int n = 0; n < startSet->value->listSize(); ++n) - workSet.push_back(startSet->value->listElems()[n]); - - /* Get the operator. */ - Bindings::iterator op = - args[0]->attrs->find(state.symbols.create("operator")); - if (op == args[0]->attrs->end()) - throw EvalError(format("attribute 'operator' required, at %1%") % pos); - state.forceValue(*op->value); - - /* Construct the closure by applying the operator to element of - `workSet', adding the result to `workSet', continuing until - no new elements are found. */ - ValueList res; - // `doneKeys' doesn't need to be a GC root, because its values are - // reachable from res. - set<Value *, CompareValues> doneKeys; - while (!workSet.empty()) { - Value * e = *(workSet.begin()); - workSet.pop_front(); - - state.forceAttrs(*e, pos); - - Bindings::iterator key = - e->attrs->find(state.symbols.create("key")); - if (key == e->attrs->end()) - throw EvalError(format("attribute 'key' required, at %1%") % pos); - state.forceValue(*key->value); - - if (doneKeys.find(key->value) != doneKeys.end()) continue; - doneKeys.insert(key->value); - res.push_back(e); - - /* Call the `operator' function with `e' as argument. */ - Value call; - mkApp(call, *op->value, *e); - state.forceList(call, pos); - - /* Add the values returned by the operator to the work set. */ - for (unsigned int n = 0; n < call.listSize(); ++n) { - state.forceValue(*call.listElems()[n]); - workSet.push_back(call.listElems()[n]); - } +static void prim_genericClosure(EvalState& state, const Pos& pos, Value** args, + Value& v) { + state.forceAttrs(*args[0], pos); + + /* Get the start set. */ + Bindings::iterator startSet = + args[0]->attrs->find(state.symbols.create("startSet")); + if (startSet == args[0]->attrs->end()) + throw EvalError(format("attribute 'startSet' required, at %1%") % pos); + state.forceList(*startSet->value, pos); + + ValueList workSet; + for (unsigned int n = 0; n < startSet->value->listSize(); ++n) + workSet.push_back(startSet->value->listElems()[n]); + + /* Get the operator. */ + Bindings::iterator op = + args[0]->attrs->find(state.symbols.create("operator")); + if (op == args[0]->attrs->end()) + throw EvalError(format("attribute 'operator' required, at %1%") % pos); + state.forceValue(*op->value); + + /* Construct the closure by applying the operator to element of + `workSet', adding the result to `workSet', continuing until + no new elements are found. */ + ValueList res; + // `doneKeys' doesn't need to be a GC root, because its values are + // reachable from res. + set<Value*, CompareValues> doneKeys; + while (!workSet.empty()) { + Value* e = *(workSet.begin()); + workSet.pop_front(); + + state.forceAttrs(*e, pos); + + Bindings::iterator key = e->attrs->find(state.symbols.create("key")); + if (key == e->attrs->end()) + throw EvalError(format("attribute 'key' required, at %1%") % pos); + state.forceValue(*key->value); + + if (doneKeys.find(key->value) != doneKeys.end()) continue; + doneKeys.insert(key->value); + res.push_back(e); + + /* Call the `operator' function with `e' as argument. */ + Value call; + mkApp(call, *op->value, *e); + state.forceList(call, pos); + + /* Add the values returned by the operator to the work set. */ + for (unsigned int n = 0; n < call.listSize(); ++n) { + state.forceValue(*call.listElems()[n]); + workSet.push_back(call.listElems()[n]); } + } - /* Create the result list. */ - state.mkList(v, res.size()); - unsigned int n = 0; - for (auto & i : res) - v.listElems()[n++] = i; + /* Create the result list. */ + state.mkList(v, res.size()); + unsigned int n = 0; + for (auto& i : res) v.listElems()[n++] = i; } - -static void prim_abort(EvalState & state, const Pos & pos, Value * * args, Value & v) -{ - PathSet context; - string s = state.coerceToString(pos, *args[0], context); - throw Abort(format("evaluation aborted with the following error message: '%1%'") % s); +static void prim_abort(EvalState& state, const Pos& pos, Value** args, + Value& v) { + PathSet context; + string s = state.coerceToString(pos, *args[0], context); + throw Abort( + format("evaluation aborted with the following error message: '%1%'") % s); } - -static void prim_throw(EvalState & state, const Pos & pos, Value * * args, Value & v) -{ - PathSet context; - string s = state.coerceToString(pos, *args[0], context); - throw ThrownError(s); +static void prim_throw(EvalState& state, const Pos& pos, Value** args, + Value& v) { + PathSet context; + string s = state.coerceToString(pos, *args[0], context); + throw ThrownError(s); } - -static void prim_addErrorContext(EvalState & state, const Pos & pos, Value * * args, Value & v) -{ - try { - state.forceValue(*args[1]); - v = *args[1]; - } catch (Error & e) { - PathSet context; - e.addPrefix(format("%1%\n") % state.coerceToString(pos, *args[0], context)); - throw; - } +static void prim_addErrorContext(EvalState& state, const Pos& pos, Value** args, + Value& v) { + try { + state.forceValue(*args[1]); + v = *args[1]; + } catch (Error& e) { + PathSet context; + e.addPrefix(format("%1%\n") % state.coerceToString(pos, *args[0], context)); + throw; + } } - /* Try evaluating the argument. Success => {success=true; value=something;}, * else => {success=false; value=false;} */ -static void prim_tryEval(EvalState & state, const Pos & pos, Value * * args, Value & v) -{ - state.mkAttrs(v, 2); - try { - state.forceValue(*args[0]); - v.attrs->push_back(Attr(state.sValue, args[0])); - mkBool(*state.allocAttr(v, state.symbols.create("success")), true); - } catch (AssertionError & e) { - mkBool(*state.allocAttr(v, state.sValue), false); - mkBool(*state.allocAttr(v, state.symbols.create("success")), false); - } - v.attrs->sort(); +static void prim_tryEval(EvalState& state, const Pos& pos, Value** args, + Value& v) { + state.mkAttrs(v, 2); + try { + state.forceValue(*args[0]); + v.attrs->push_back(Attr(state.sValue, args[0])); + mkBool(*state.allocAttr(v, state.symbols.create("success")), true); + } catch (AssertionError& e) { + mkBool(*state.allocAttr(v, state.sValue), false); + mkBool(*state.allocAttr(v, state.symbols.create("success")), false); + } + v.attrs->sort(); } - /* Return an environment variable. Use with care. */ -static void prim_getEnv(EvalState & state, const Pos & pos, Value * * args, Value & v) -{ - string name = state.forceStringNoCtx(*args[0], pos); - mkString(v, evalSettings.restrictEval || evalSettings.pureEval ? "" : getEnv(name)); +static void prim_getEnv(EvalState& state, const Pos& pos, Value** args, + Value& v) { + string name = state.forceStringNoCtx(*args[0], pos); + mkString(v, evalSettings.restrictEval || evalSettings.pureEval + ? "" + : getEnv(name)); } - /* Evaluate the first argument, then return the second argument. */ -static void prim_seq(EvalState & state, const Pos & pos, Value * * args, Value & v) -{ - state.forceValue(*args[0]); - state.forceValue(*args[1]); - v = *args[1]; +static void prim_seq(EvalState& state, const Pos& pos, Value** args, Value& v) { + state.forceValue(*args[0]); + state.forceValue(*args[1]); + v = *args[1]; } - /* Evaluate the first argument deeply (i.e. recursing into lists and attrsets), then return the second argument. */ -static void prim_deepSeq(EvalState & state, const Pos & pos, Value * * args, Value & v) -{ - state.forceValueDeep(*args[0]); - state.forceValue(*args[1]); - v = *args[1]; +static void prim_deepSeq(EvalState& state, const Pos& pos, Value** args, + Value& v) { + state.forceValueDeep(*args[0]); + state.forceValue(*args[1]); + v = *args[1]; } - /* Evaluate the first expression and print it on standard error. Then return the second expression. Useful for debugging. */ -static void prim_trace(EvalState & state, const Pos & pos, Value * * args, Value & v) -{ - state.forceValue(*args[0]); - if (args[0]->type == tString) - printError(format("trace: %1%") % args[0]->string.s); - else - printError(format("trace: %1%") % *args[0]); - state.forceValue(*args[1]); - v = *args[1]; +static void prim_trace(EvalState& state, const Pos& pos, Value** args, + Value& v) { + state.forceValue(*args[0]); + if (args[0]->type == tString) + printError(format("trace: %1%") % args[0]->string.s); + else + printError(format("trace: %1%") % *args[0]); + state.forceValue(*args[1]); + v = *args[1]; } - -void prim_valueSize(EvalState & state, const Pos & pos, Value * * args, Value & v) -{ - /* We're not forcing the argument on purpose. */ - mkInt(v, valueSize(*args[0])); +void prim_valueSize(EvalState& state, const Pos& pos, Value** args, Value& v) { + /* We're not forcing the argument on purpose. */ + mkInt(v, valueSize(*args[0])); } - /************************************************************* * Derivations *************************************************************/ - /* Construct (as a unobservable side effect) a Nix derivation expression that performs the derivation described by the argument set. Returns the original set extended with the following @@ -526,255 +528,276 @@ void prim_valueSize(EvalState & state, const Pos & pos, Value * * args, Value & derivation; `drvPath' containing the path of the Nix expression; and `type' set to `derivation' to indicate that this is a derivation. */ -static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * * args, Value & v) -{ - state.forceAttrs(*args[0], pos); - - /* Figure out the name first (for stack backtraces). */ - Bindings::iterator attr = args[0]->attrs->find(state.sName); - if (attr == args[0]->attrs->end()) - throw EvalError(format("required attribute 'name' missing, at %1%") % pos); - string drvName; - Pos & posDrvName(*attr->pos); - try { - drvName = state.forceStringNoCtx(*attr->value, pos); - } catch (Error & e) { - e.addPrefix(format("while evaluating the derivation attribute 'name' at %1%:\n") % posDrvName); - throw; - } - - /* Check whether attributes should be passed as a JSON file. */ - std::ostringstream jsonBuf; - std::unique_ptr<JSONObject> jsonObject; - attr = args[0]->attrs->find(state.sStructuredAttrs); - if (attr != args[0]->attrs->end() && state.forceBool(*attr->value, pos)) - jsonObject = std::make_unique<JSONObject>(jsonBuf); - - /* Check whether null attributes should be ignored. */ - bool ignoreNulls = false; - attr = args[0]->attrs->find(state.sIgnoreNulls); - if (attr != args[0]->attrs->end()) - ignoreNulls = state.forceBool(*attr->value, pos); - - /* Build the derivation expression by processing the attributes. */ - Derivation drv; - - PathSet context; - - std::optional<std::string> outputHash; - std::string outputHashAlgo; - bool outputHashRecursive = false; - - StringSet outputs; - outputs.insert("out"); - - for (auto & i : args[0]->attrs->lexicographicOrder()) { - if (i->name == state.sIgnoreNulls) continue; - const string & key = i->name; - vomit("processing attribute '%1%'", key); - - auto handleHashMode = [&](const std::string & s) { - if (s == "recursive") outputHashRecursive = true; - else if (s == "flat") outputHashRecursive = false; - else throw EvalError("invalid value '%s' for 'outputHashMode' attribute, at %s", s, posDrvName); - }; - - auto handleOutputs = [&](const Strings & ss) { - outputs.clear(); - for (auto & j : ss) { - if (outputs.find(j) != outputs.end()) - throw EvalError(format("duplicate derivation output '%1%', at %2%") % j % posDrvName); - /* !!! Check whether j is a valid attribute - name. */ - /* Derivations cannot be named ‘drv’, because - then we'd have an attribute ‘drvPath’ in - the resulting set. */ - if (j == "drv") - throw EvalError(format("invalid derivation output name 'drv', at %1%") % posDrvName); - outputs.insert(j); - } - if (outputs.empty()) - throw EvalError(format("derivation cannot have an empty set of outputs, at %1%") % posDrvName); - }; - - try { - - if (ignoreNulls) { - state.forceValue(*i->value); - if (i->value->type == tNull) continue; - } - - /* The `args' attribute is special: it supplies the - command-line arguments to the builder. */ - if (i->name == state.sArgs) { - state.forceList(*i->value, pos); - for (unsigned int n = 0; n < i->value->listSize(); ++n) { - string s = state.coerceToString(posDrvName, *i->value->listElems()[n], context, true); - drv.args.push_back(s); - } - } - - /* All other attributes are passed to the builder through - the environment. */ - else { - - if (jsonObject) { - - if (i->name == state.sStructuredAttrs) continue; - - auto placeholder(jsonObject->placeholder(key)); - printValueAsJSON(state, true, *i->value, placeholder, context); - - if (i->name == state.sBuilder) - drv.builder = state.forceString(*i->value, context, posDrvName); - else if (i->name == state.sSystem) - drv.platform = state.forceStringNoCtx(*i->value, posDrvName); - else if (i->name == state.sOutputHash) - outputHash = state.forceStringNoCtx(*i->value, posDrvName); - else if (i->name == state.sOutputHashAlgo) - outputHashAlgo = state.forceStringNoCtx(*i->value, posDrvName); - else if (i->name == state.sOutputHashMode) - handleHashMode(state.forceStringNoCtx(*i->value, posDrvName)); - else if (i->name == state.sOutputs) { - /* Require ‘outputs’ to be a list of strings. */ - state.forceList(*i->value, posDrvName); - Strings ss; - for (unsigned int n = 0; n < i->value->listSize(); ++n) - ss.emplace_back(state.forceStringNoCtx(*i->value->listElems()[n], posDrvName)); - handleOutputs(ss); - } - - } else { - auto s = state.coerceToString(posDrvName, *i->value, context, true); - drv.env.emplace(key, s); - if (i->name == state.sBuilder) drv.builder = s; - else if (i->name == state.sSystem) drv.platform = s; - else if (i->name == state.sOutputHash) outputHash = s; - else if (i->name == state.sOutputHashAlgo) outputHashAlgo = s; - else if (i->name == state.sOutputHashMode) handleHashMode(s); - else if (i->name == state.sOutputs) - handleOutputs(tokenizeString<Strings>(s)); - } - - } - - } catch (Error & e) { - e.addPrefix(format("while evaluating the attribute '%1%' of the derivation '%2%' at %3%:\n") - % key % drvName % posDrvName); - throw; - } - } +static void prim_derivationStrict(EvalState& state, const Pos& pos, + Value** args, Value& v) { + state.forceAttrs(*args[0], pos); + + /* Figure out the name first (for stack backtraces). */ + Bindings::iterator attr = args[0]->attrs->find(state.sName); + if (attr == args[0]->attrs->end()) + throw EvalError(format("required attribute 'name' missing, at %1%") % pos); + string drvName; + Pos& posDrvName(*attr->pos); + try { + drvName = state.forceStringNoCtx(*attr->value, pos); + } catch (Error& e) { + e.addPrefix( + format("while evaluating the derivation attribute 'name' at %1%:\n") % + posDrvName); + throw; + } + + /* Check whether attributes should be passed as a JSON file. */ + std::ostringstream jsonBuf; + std::unique_ptr<JSONObject> jsonObject; + attr = args[0]->attrs->find(state.sStructuredAttrs); + if (attr != args[0]->attrs->end() && state.forceBool(*attr->value, pos)) + jsonObject = std::make_unique<JSONObject>(jsonBuf); + + /* Check whether null attributes should be ignored. */ + bool ignoreNulls = false; + attr = args[0]->attrs->find(state.sIgnoreNulls); + if (attr != args[0]->attrs->end()) + ignoreNulls = state.forceBool(*attr->value, pos); + + /* Build the derivation expression by processing the attributes. */ + Derivation drv; + + PathSet context; + + std::optional<std::string> outputHash; + std::string outputHashAlgo; + bool outputHashRecursive = false; + + StringSet outputs; + outputs.insert("out"); + + for (auto& i : args[0]->attrs->lexicographicOrder()) { + if (i->name == state.sIgnoreNulls) continue; + const string& key = i->name; + vomit("processing attribute '%1%'", key); + + auto handleHashMode = [&](const std::string& s) { + if (s == "recursive") + outputHashRecursive = true; + else if (s == "flat") + outputHashRecursive = false; + else + throw EvalError( + "invalid value '%s' for 'outputHashMode' attribute, at %s", s, + posDrvName); + }; - if (jsonObject) { - jsonObject.reset(); - drv.env.emplace("__json", jsonBuf.str()); - } + auto handleOutputs = [&](const Strings& ss) { + outputs.clear(); + for (auto& j : ss) { + if (outputs.find(j) != outputs.end()) + throw EvalError(format("duplicate derivation output '%1%', at %2%") % + j % posDrvName); + /* !!! Check whether j is a valid attribute + name. */ + /* Derivations cannot be named ‘drv’, because + then we'd have an attribute ‘drvPath’ in + the resulting set. */ + if (j == "drv") + throw EvalError( + format("invalid derivation output name 'drv', at %1%") % + posDrvName); + outputs.insert(j); + } + if (outputs.empty()) + throw EvalError( + format("derivation cannot have an empty set of outputs, at %1%") % + posDrvName); + }; - /* Everything in the context of the strings in the derivation - attributes should be added as dependencies of the resulting - derivation. */ - for (auto & path : context) { - - /* Paths marked with `=' denote that the path of a derivation - is explicitly passed to the builder. Since that allows the - builder to gain access to every path in the dependency - graph of the derivation (including all outputs), all paths - in the graph must be added to this derivation's list of - inputs to ensure that they are available when the builder - runs. */ - if (path.at(0) == '=') { - /* !!! This doesn't work if readOnlyMode is set. */ - PathSet refs; - state.store->computeFSClosure(string(path, 1), refs); - for (auto & j : refs) { - drv.inputSrcs.insert(j); - if (isDerivation(j)) - drv.inputDrvs[j] = state.store->queryDerivationOutputNames(j); - } + try { + if (ignoreNulls) { + state.forceValue(*i->value); + if (i->value->type == tNull) continue; + } + + /* The `args' attribute is special: it supplies the + command-line arguments to the builder. */ + if (i->name == state.sArgs) { + state.forceList(*i->value, pos); + for (unsigned int n = 0; n < i->value->listSize(); ++n) { + string s = state.coerceToString(posDrvName, *i->value->listElems()[n], + context, true); + drv.args.push_back(s); } + } + + /* All other attributes are passed to the builder through + the environment. */ + else { + if (jsonObject) { + if (i->name == state.sStructuredAttrs) continue; + + auto placeholder(jsonObject->placeholder(key)); + printValueAsJSON(state, true, *i->value, placeholder, context); + + if (i->name == state.sBuilder) + drv.builder = state.forceString(*i->value, context, posDrvName); + else if (i->name == state.sSystem) + drv.platform = state.forceStringNoCtx(*i->value, posDrvName); + else if (i->name == state.sOutputHash) + outputHash = state.forceStringNoCtx(*i->value, posDrvName); + else if (i->name == state.sOutputHashAlgo) + outputHashAlgo = state.forceStringNoCtx(*i->value, posDrvName); + else if (i->name == state.sOutputHashMode) + handleHashMode(state.forceStringNoCtx(*i->value, posDrvName)); + else if (i->name == state.sOutputs) { + /* Require ‘outputs’ to be a list of strings. */ + state.forceList(*i->value, posDrvName); + Strings ss; + for (unsigned int n = 0; n < i->value->listSize(); ++n) + ss.emplace_back(state.forceStringNoCtx(*i->value->listElems()[n], + posDrvName)); + handleOutputs(ss); + } - /* Handle derivation outputs of the form ‘!<name>!<path>’. */ - else if (path.at(0) == '!') { - std::pair<string, string> ctx = decodeContext(path); - drv.inputDrvs[ctx.first].insert(ctx.second); + } else { + auto s = state.coerceToString(posDrvName, *i->value, context, true); + drv.env.emplace(key, s); + if (i->name == state.sBuilder) + drv.builder = s; + else if (i->name == state.sSystem) + drv.platform = s; + else if (i->name == state.sOutputHash) + outputHash = s; + else if (i->name == state.sOutputHashAlgo) + outputHashAlgo = s; + else if (i->name == state.sOutputHashMode) + handleHashMode(s); + else if (i->name == state.sOutputs) + handleOutputs(tokenizeString<Strings>(s)); } + } - /* Otherwise it's a source file. */ - else - drv.inputSrcs.insert(path); + } catch (Error& e) { + e.addPrefix(format("while evaluating the attribute '%1%' of the " + "derivation '%2%' at %3%:\n") % + key % drvName % posDrvName); + throw; + } + } + + if (jsonObject) { + jsonObject.reset(); + drv.env.emplace("__json", jsonBuf.str()); + } + + /* Everything in the context of the strings in the derivation + attributes should be added as dependencies of the resulting + derivation. */ + for (auto& path : context) { + /* Paths marked with `=' denote that the path of a derivation + is explicitly passed to the builder. Since that allows the + builder to gain access to every path in the dependency + graph of the derivation (including all outputs), all paths + in the graph must be added to this derivation's list of + inputs to ensure that they are available when the builder + runs. */ + if (path.at(0) == '=') { + /* !!! This doesn't work if readOnlyMode is set. */ + PathSet refs; + state.store->computeFSClosure(string(path, 1), refs); + for (auto& j : refs) { + drv.inputSrcs.insert(j); + if (isDerivation(j)) + drv.inputDrvs[j] = state.store->queryDerivationOutputNames(j); + } } - /* Do we have all required attributes? */ - if (drv.builder == "") - throw EvalError(format("required attribute 'builder' missing, at %1%") % posDrvName); - if (drv.platform == "") - throw EvalError(format("required attribute 'system' missing, at %1%") % posDrvName); - - /* Check whether the derivation name is valid. */ - checkStoreName(drvName); - if (isDerivation(drvName)) - throw EvalError(format("derivation names are not allowed to end in '%1%', at %2%") - % drvExtension % posDrvName); - - if (outputHash) { - /* Handle fixed-output derivations. */ - if (outputs.size() != 1 || *(outputs.begin()) != "out") - throw Error(format("multiple outputs are not supported in fixed-output derivations, at %1%") % posDrvName); - - HashType ht = outputHashAlgo.empty() ? htUnknown : parseHashType(outputHashAlgo); - Hash h(*outputHash, ht); - - Path outPath = state.store->makeFixedOutputPath(outputHashRecursive, h, drvName); - if (!jsonObject) drv.env["out"] = outPath; - drv.outputs["out"] = DerivationOutput(outPath, - (outputHashRecursive ? "r:" : "") + printHashType(h.type), - h.to_string(Base16, false)); + /* Handle derivation outputs of the form ‘!<name>!<path>’. */ + else if (path.at(0) == '!') { + std::pair<string, string> ctx = decodeContext(path); + drv.inputDrvs[ctx.first].insert(ctx.second); } - else { - /* Construct the "masked" store derivation, which is the final - one except that in the list of outputs, the output paths - are empty, and the corresponding environment variables have - an empty value. This ensures that changes in the set of - output names do get reflected in the hash. */ - for (auto & i : outputs) { - if (!jsonObject) drv.env[i] = ""; - drv.outputs[i] = DerivationOutput("", "", ""); - } + /* Otherwise it's a source file. */ + else + drv.inputSrcs.insert(path); + } + + /* Do we have all required attributes? */ + if (drv.builder == "") + throw EvalError(format("required attribute 'builder' missing, at %1%") % + posDrvName); + if (drv.platform == "") + throw EvalError(format("required attribute 'system' missing, at %1%") % + posDrvName); + + /* Check whether the derivation name is valid. */ + checkStoreName(drvName); + if (isDerivation(drvName)) + throw EvalError( + format("derivation names are not allowed to end in '%1%', at %2%") % + drvExtension % posDrvName); + + if (outputHash) { + /* Handle fixed-output derivations. */ + if (outputs.size() != 1 || *(outputs.begin()) != "out") + throw Error(format("multiple outputs are not supported in fixed-output " + "derivations, at %1%") % + posDrvName); + + HashType ht = + outputHashAlgo.empty() ? htUnknown : parseHashType(outputHashAlgo); + Hash h(*outputHash, ht); + + Path outPath = + state.store->makeFixedOutputPath(outputHashRecursive, h, drvName); + if (!jsonObject) drv.env["out"] = outPath; + drv.outputs["out"] = DerivationOutput( + outPath, (outputHashRecursive ? "r:" : "") + printHashType(h.type), + h.to_string(Base16, false)); + } + + else { + /* Construct the "masked" store derivation, which is the final + one except that in the list of outputs, the output paths + are empty, and the corresponding environment variables have + an empty value. This ensures that changes in the set of + output names do get reflected in the hash. */ + for (auto& i : outputs) { + if (!jsonObject) drv.env[i] = ""; + drv.outputs[i] = DerivationOutput("", "", ""); + } - /* Use the masked derivation expression to compute the output - path. */ - Hash h = hashDerivationModulo(*state.store, drv); + /* Use the masked derivation expression to compute the output + path. */ + Hash h = hashDerivationModulo(*state.store, drv); - for (auto & i : drv.outputs) - if (i.second.path == "") { - Path outPath = state.store->makeOutputPath(i.first, h, drvName); - if (!jsonObject) drv.env[i.first] = outPath; - i.second.path = outPath; - } - } + for (auto& i : drv.outputs) + if (i.second.path == "") { + Path outPath = state.store->makeOutputPath(i.first, h, drvName); + if (!jsonObject) drv.env[i.first] = outPath; + i.second.path = outPath; + } + } - /* Write the resulting term into the Nix store directory. */ - Path drvPath = writeDerivation(state.store, drv, drvName, state.repair); + /* Write the resulting term into the Nix store directory. */ + Path drvPath = writeDerivation(state.store, drv, drvName, state.repair); - printMsg(lvlChatty, format("instantiated '%1%' -> '%2%'") - % drvName % drvPath); + printMsg(lvlChatty, + format("instantiated '%1%' -> '%2%'") % drvName % drvPath); - /* Optimisation, but required in read-only mode! because in that - case we don't actually write store derivations, so we can't - read them later. */ - drvHashes[drvPath] = hashDerivationModulo(*state.store, drv); + /* Optimisation, but required in read-only mode! because in that + case we don't actually write store derivations, so we can't + read them later. */ + drvHashes[drvPath] = hashDerivationModulo(*state.store, drv); - state.mkAttrs(v, 1 + drv.outputs.size()); - mkString(*state.allocAttr(v, state.sDrvPath), drvPath, {"=" + drvPath}); - for (auto & i : drv.outputs) { - mkString(*state.allocAttr(v, state.symbols.create(i.first)), - i.second.path, {"!" + i.first + "!" + drvPath}); - } - v.attrs->sort(); + state.mkAttrs(v, 1 + drv.outputs.size()); + mkString(*state.allocAttr(v, state.sDrvPath), drvPath, {"=" + drvPath}); + for (auto& i : drv.outputs) { + mkString(*state.allocAttr(v, state.symbols.create(i.first)), i.second.path, + {"!" + i.first + "!" + drvPath}); + } + v.attrs->sort(); } - /* Return a placeholder string for the specified output that will be substituted by the corresponding output path at build time. For example, 'placeholder "out"' returns the string @@ -782,26 +805,23 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * * time, any occurence of this string in an derivation attribute will be replaced with the concrete path in the Nix store of the output ‘out’. */ -static void prim_placeholder(EvalState & state, const Pos & pos, Value * * args, Value & v) -{ - mkString(v, hashPlaceholder(state.forceStringNoCtx(*args[0], pos))); +static void prim_placeholder(EvalState& state, const Pos& pos, Value** args, + Value& v) { + mkString(v, hashPlaceholder(state.forceStringNoCtx(*args[0], pos))); } - /************************************************************* * Paths *************************************************************/ - /* Convert the argument to a path. !!! obsolete? */ -static void prim_toPath(EvalState & state, const Pos & pos, Value * * args, Value & v) -{ - PathSet context; - Path path = state.coerceToPath(pos, *args[0], context); - mkString(v, canonPath(path), context); +static void prim_toPath(EvalState& state, const Pos& pos, Value** args, + Value& v) { + PathSet context; + Path path = state.coerceToPath(pos, *args[0], context); + mkString(v, canonPath(path), context); } - /* Allow a valid store path to be used in an expression. This is useful in some generated expressions such as in nix-push, which generates a call to a function with an already existing store path @@ -810,501 +830,512 @@ static void prim_toPath(EvalState & state, const Pos & pos, Value * * args, Valu /nix/store/newhash-oldhash-oldname. In the past, `toPath' had special case behaviour for store paths, but that created weird corner cases. */ -static void prim_storePath(EvalState & state, const Pos & pos, Value * * args, Value & v) -{ - PathSet context; - Path path = state.checkSourcePath(state.coerceToPath(pos, *args[0], context)); - /* Resolve symlinks in ‘path’, unless ‘path’ itself is a symlink - directly in the store. The latter condition is necessary so - e.g. nix-push does the right thing. */ - if (!state.store->isStorePath(path)) path = canonPath(path, true); - if (!state.store->isInStore(path)) - throw EvalError(format("path '%1%' is not in the Nix store, at %2%") % path % pos); - Path path2 = state.store->toStorePath(path); - if (!settings.readOnlyMode) - state.store->ensurePath(path2); - context.insert(path2); - mkString(v, path, context); -} - - -static void prim_pathExists(EvalState & state, const Pos & pos, Value * * args, Value & v) -{ - PathSet context; - Path path = state.coerceToPath(pos, *args[0], context); - try { - state.realiseContext(context); - } catch (InvalidPathError & e) { - throw EvalError(format( - "cannot check the existence of '%1%', since path '%2%' is not valid, at %3%") - % path % e.path % pos); - } - - try { - mkBool(v, pathExists(state.checkSourcePath(path))); - } catch (SysError & e) { - /* Don't give away info from errors while canonicalising - ‘path’ in restricted mode. */ - mkBool(v, false); - } catch (RestrictedPathError & e) { - mkBool(v, false); - } +static void prim_storePath(EvalState& state, const Pos& pos, Value** args, + Value& v) { + PathSet context; + Path path = state.checkSourcePath(state.coerceToPath(pos, *args[0], context)); + /* Resolve symlinks in ‘path’, unless ‘path’ itself is a symlink + directly in the store. The latter condition is necessary so + e.g. nix-push does the right thing. */ + if (!state.store->isStorePath(path)) path = canonPath(path, true); + if (!state.store->isInStore(path)) + throw EvalError(format("path '%1%' is not in the Nix store, at %2%") % + path % pos); + Path path2 = state.store->toStorePath(path); + if (!settings.readOnlyMode) state.store->ensurePath(path2); + context.insert(path2); + mkString(v, path, context); +} + +static void prim_pathExists(EvalState& state, const Pos& pos, Value** args, + Value& v) { + PathSet context; + Path path = state.coerceToPath(pos, *args[0], context); + try { + state.realiseContext(context); + } catch (InvalidPathError& e) { + throw EvalError(format("cannot check the existence of '%1%', since path " + "'%2%' is not valid, at %3%") % + path % e.path % pos); + } + + try { + mkBool(v, pathExists(state.checkSourcePath(path))); + } catch (SysError& e) { + /* Don't give away info from errors while canonicalising + ‘path’ in restricted mode. */ + mkBool(v, false); + } catch (RestrictedPathError& e) { + mkBool(v, false); + } } - /* Return the base name of the given string, i.e., everything following the last slash. */ -static void prim_baseNameOf(EvalState & state, const Pos & pos, Value * * args, Value & v) -{ - PathSet context; - mkString(v, baseNameOf(state.coerceToString(pos, *args[0], context, false, false)), context); +static void prim_baseNameOf(EvalState& state, const Pos& pos, Value** args, + Value& v) { + PathSet context; + mkString( + v, baseNameOf(state.coerceToString(pos, *args[0], context, false, false)), + context); } - /* Return the directory of the given path, i.e., everything before the last slash. Return either a path or a string depending on the type of the argument. */ -static void prim_dirOf(EvalState & state, const Pos & pos, Value * * args, Value & v) -{ - PathSet context; - Path dir = dirOf(state.coerceToString(pos, *args[0], context, false, false)); - if (args[0]->type == tPath) mkPath(v, dir.c_str()); else mkString(v, dir, context); +static void prim_dirOf(EvalState& state, const Pos& pos, Value** args, + Value& v) { + PathSet context; + Path dir = dirOf(state.coerceToString(pos, *args[0], context, false, false)); + if (args[0]->type == tPath) + mkPath(v, dir.c_str()); + else + mkString(v, dir, context); } - /* Return the contents of a file as a string. */ -static void prim_readFile(EvalState & state, const Pos & pos, Value * * args, Value & v) -{ - PathSet context; - Path path = state.coerceToPath(pos, *args[0], context); - try { - state.realiseContext(context); - } catch (InvalidPathError & e) { - throw EvalError(format("cannot read '%1%', since path '%2%' is not valid, at %3%") - % path % e.path % pos); - } - string s = readFile(state.checkSourcePath(state.toRealPath(path, context))); - if (s.find((char) 0) != string::npos) - throw Error(format("the contents of the file '%1%' cannot be represented as a Nix string") % path); - mkString(v, s.c_str()); +static void prim_readFile(EvalState& state, const Pos& pos, Value** args, + Value& v) { + PathSet context; + Path path = state.coerceToPath(pos, *args[0], context); + try { + state.realiseContext(context); + } catch (InvalidPathError& e) { + throw EvalError( + format("cannot read '%1%', since path '%2%' is not valid, at %3%") % + path % e.path % pos); + } + string s = readFile(state.checkSourcePath(state.toRealPath(path, context))); + if (s.find((char)0) != string::npos) + throw Error(format("the contents of the file '%1%' cannot be represented " + "as a Nix string") % + path); + mkString(v, s.c_str()); } - /* Find a file in the Nix search path. Used to implement <x> paths, which are desugared to 'findFile __nixPath "x"'. */ -static void prim_findFile(EvalState & state, const Pos & pos, Value * * args, Value & v) -{ - state.forceList(*args[0], pos); - - SearchPath searchPath; +static void prim_findFile(EvalState& state, const Pos& pos, Value** args, + Value& v) { + state.forceList(*args[0], pos); - for (unsigned int n = 0; n < args[0]->listSize(); ++n) { - Value & v2(*args[0]->listElems()[n]); - state.forceAttrs(v2, pos); + SearchPath searchPath; - string prefix; - Bindings::iterator i = v2.attrs->find(state.symbols.create("prefix")); - if (i != v2.attrs->end()) - prefix = state.forceStringNoCtx(*i->value, pos); + for (unsigned int n = 0; n < args[0]->listSize(); ++n) { + Value& v2(*args[0]->listElems()[n]); + state.forceAttrs(v2, pos); - i = v2.attrs->find(state.symbols.create("path")); - if (i == v2.attrs->end()) - throw EvalError(format("attribute 'path' missing, at %1%") % pos); + string prefix; + Bindings::iterator i = v2.attrs->find(state.symbols.create("prefix")); + if (i != v2.attrs->end()) prefix = state.forceStringNoCtx(*i->value, pos); - PathSet context; - string path = state.coerceToString(pos, *i->value, context, false, false); + i = v2.attrs->find(state.symbols.create("path")); + if (i == v2.attrs->end()) + throw EvalError(format("attribute 'path' missing, at %1%") % pos); - try { - state.realiseContext(context); - } catch (InvalidPathError & e) { - throw EvalError(format("cannot find '%1%', since path '%2%' is not valid, at %3%") - % path % e.path % pos); - } + PathSet context; + string path = state.coerceToString(pos, *i->value, context, false, false); - searchPath.emplace_back(prefix, path); + try { + state.realiseContext(context); + } catch (InvalidPathError& e) { + throw EvalError( + format("cannot find '%1%', since path '%2%' is not valid, at %3%") % + path % e.path % pos); } - string path = state.forceStringNoCtx(*args[1], pos); + searchPath.emplace_back(prefix, path); + } + + string path = state.forceStringNoCtx(*args[1], pos); - mkPath(v, state.checkSourcePath(state.findFile(searchPath, path, pos)).c_str()); + mkPath(v, + state.checkSourcePath(state.findFile(searchPath, path, pos)).c_str()); } /* Return the cryptographic hash of a file in base-16. */ -static void prim_hashFile(EvalState & state, const Pos & pos, Value * * args, Value & v) -{ - string type = state.forceStringNoCtx(*args[0], pos); - HashType ht = parseHashType(type); - if (ht == htUnknown) - throw Error(format("unknown hash type '%1%', at %2%") % type % pos); +static void prim_hashFile(EvalState& state, const Pos& pos, Value** args, + Value& v) { + string type = state.forceStringNoCtx(*args[0], pos); + HashType ht = parseHashType(type); + if (ht == htUnknown) + throw Error(format("unknown hash type '%1%', at %2%") % type % pos); - PathSet context; // discarded - Path p = state.coerceToPath(pos, *args[1], context); + PathSet context; // discarded + Path p = state.coerceToPath(pos, *args[1], context); - mkString(v, hashFile(ht, state.checkSourcePath(p)).to_string(Base16, false), context); + mkString(v, hashFile(ht, state.checkSourcePath(p)).to_string(Base16, false), + context); } /* Read a directory (without . or ..) */ -static void prim_readDir(EvalState & state, const Pos & pos, Value * * args, Value & v) -{ - PathSet ctx; - Path path = state.coerceToPath(pos, *args[0], ctx); - try { - state.realiseContext(ctx); - } catch (InvalidPathError & e) { - throw EvalError(format("cannot read '%1%', since path '%2%' is not valid, at %3%") - % path % e.path % pos); - } - - DirEntries entries = readDirectory(state.checkSourcePath(path)); - state.mkAttrs(v, entries.size()); - - for (auto & ent : entries) { - Value * ent_val = state.allocAttr(v, state.symbols.create(ent.name)); - if (ent.type == DT_UNKNOWN) - ent.type = getFileType(path + "/" + ent.name); - mkStringNoCopy(*ent_val, - ent.type == DT_REG ? "regular" : - ent.type == DT_DIR ? "directory" : - ent.type == DT_LNK ? "symlink" : - "unknown"); - } - - v.attrs->sort(); +static void prim_readDir(EvalState& state, const Pos& pos, Value** args, + Value& v) { + PathSet ctx; + Path path = state.coerceToPath(pos, *args[0], ctx); + try { + state.realiseContext(ctx); + } catch (InvalidPathError& e) { + throw EvalError( + format("cannot read '%1%', since path '%2%' is not valid, at %3%") % + path % e.path % pos); + } + + DirEntries entries = readDirectory(state.checkSourcePath(path)); + state.mkAttrs(v, entries.size()); + + for (auto& ent : entries) { + Value* ent_val = state.allocAttr(v, state.symbols.create(ent.name)); + if (ent.type == DT_UNKNOWN) ent.type = getFileType(path + "/" + ent.name); + mkStringNoCopy(*ent_val, + ent.type == DT_REG + ? "regular" + : ent.type == DT_DIR + ? "directory" + : ent.type == DT_LNK ? "symlink" : "unknown"); + } + + v.attrs->sort(); } - /************************************************************* * Creating files *************************************************************/ - /* Convert the argument (which can be any Nix expression) to an XML representation returned in a string. Not all Nix expressions can be sensibly or completely represented (e.g., functions). */ -static void prim_toXML(EvalState & state, const Pos & pos, Value * * args, Value & v) -{ - std::ostringstream out; - PathSet context; - printValueAsXML(state, true, false, *args[0], out, context); - mkString(v, out.str(), context); +static void prim_toXML(EvalState& state, const Pos& pos, Value** args, + Value& v) { + std::ostringstream out; + PathSet context; + printValueAsXML(state, true, false, *args[0], out, context); + mkString(v, out.str(), context); } - /* Convert the argument (which can be any Nix expression) to a JSON string. Not all Nix expressions can be sensibly or completely represented (e.g., functions). */ -static void prim_toJSON(EvalState & state, const Pos & pos, Value * * args, Value & v) -{ - std::ostringstream out; - PathSet context; - printValueAsJSON(state, true, *args[0], out, context); - mkString(v, out.str(), context); +static void prim_toJSON(EvalState& state, const Pos& pos, Value** args, + Value& v) { + std::ostringstream out; + PathSet context; + printValueAsJSON(state, true, *args[0], out, context); + mkString(v, out.str(), context); } - /* Parse a JSON string to a value. */ -static void prim_fromJSON(EvalState & state, const Pos & pos, Value * * args, Value & v) -{ - string s = state.forceStringNoCtx(*args[0], pos); - parseJSON(state, s, v); +static void prim_fromJSON(EvalState& state, const Pos& pos, Value** args, + Value& v) { + string s = state.forceStringNoCtx(*args[0], pos); + parseJSON(state, s, v); } - /* Store a string in the Nix store as a source file that can be used as an input by derivations. */ -static void prim_toFile(EvalState & state, const Pos & pos, Value * * args, Value & v) -{ - PathSet context; - string name = state.forceStringNoCtx(*args[0], pos); - string contents = state.forceString(*args[1], context, pos); - - PathSet refs; - - for (auto path : context) { - if (path.at(0) != '/') - throw EvalError(format("in 'toFile': the file '%1%' cannot refer to derivation outputs, at %2%") % name % pos); - refs.insert(path); - } - - Path storePath = settings.readOnlyMode - ? state.store->computeStorePathForText(name, contents, refs) - : state.store->addTextToStore(name, contents, refs, state.repair); - - /* Note: we don't need to add `context' to the context of the - result, since `storePath' itself has references to the paths - used in args[1]. */ - - mkString(v, storePath, {storePath}); -} - - -static void addPath(EvalState & state, const Pos & pos, const string & name, const Path & path_, - Value * filterFun, bool recursive, const Hash & expectedHash, Value & v) -{ - const auto path = evalSettings.pureEval && expectedHash ? - path_ : - state.checkSourcePath(path_); - PathFilter filter = filterFun ? ([&](const Path & path) { - auto st = lstat(path); - - /* Call the filter function. The first argument is the path, - the second is a string indicating the type of the file. */ - Value arg1; - mkString(arg1, path); - - Value fun2; - state.callFunction(*filterFun, arg1, fun2, noPos); - - Value arg2; - mkString(arg2, - S_ISREG(st.st_mode) ? "regular" : - S_ISDIR(st.st_mode) ? "directory" : - S_ISLNK(st.st_mode) ? "symlink" : - "unknown" /* not supported, will fail! */); - - Value res; - state.callFunction(fun2, arg2, res, noPos); - - return state.forceBool(res, pos); - }) : defaultPathFilter; - - Path expectedStorePath; - if (expectedHash) { - expectedStorePath = - state.store->makeFixedOutputPath(recursive, expectedHash, name); - } - Path dstPath; - if (!expectedHash || !state.store->isValidPath(expectedStorePath)) { - dstPath = settings.readOnlyMode - ? state.store->computeStorePathForPath(name, path, recursive, htSHA256, filter).first - : state.store->addToStore(name, path, recursive, htSHA256, filter, state.repair); - if (expectedHash && expectedStorePath != dstPath) { - throw Error(format("store path mismatch in (possibly filtered) path added from '%1%'") % path); - } - } else - dstPath = expectedStorePath; - - mkString(v, dstPath, {dstPath}); -} - - -static void prim_filterSource(EvalState & state, const Pos & pos, Value * * args, Value & v) -{ - PathSet context; - Path path = state.coerceToPath(pos, *args[1], context); - if (!context.empty()) - throw EvalError(format("string '%1%' cannot refer to other paths, at %2%") % path % pos); - - state.forceValue(*args[0]); - if (args[0]->type != tLambda) - throw TypeError(format("first argument in call to 'filterSource' is not a function but %1%, at %2%") % showType(*args[0]) % pos); - - addPath(state, pos, baseNameOf(path), path, args[0], true, Hash(), v); -} - -static void prim_path(EvalState & state, const Pos & pos, Value * * args, Value & v) -{ - state.forceAttrs(*args[0], pos); - Path path; - string name; - Value * filterFun = nullptr; - auto recursive = true; - Hash expectedHash; - - for (auto & attr : *args[0]->attrs) { - const string & n(attr.name); - if (n == "path") { - PathSet context; - path = state.coerceToPath(*attr.pos, *attr.value, context); - if (!context.empty()) - throw EvalError(format("string '%1%' cannot refer to other paths, at %2%") % path % *attr.pos); - } else if (attr.name == state.sName) - name = state.forceStringNoCtx(*attr.value, *attr.pos); - else if (n == "filter") { - state.forceValue(*attr.value); - filterFun = attr.value; - } else if (n == "recursive") - recursive = state.forceBool(*attr.value, *attr.pos); - else if (n == "sha256") - expectedHash = Hash(state.forceStringNoCtx(*attr.value, *attr.pos), htSHA256); - else - throw EvalError(format("unsupported argument '%1%' to 'addPath', at %2%") % attr.name % *attr.pos); +static void prim_toFile(EvalState& state, const Pos& pos, Value** args, + Value& v) { + PathSet context; + string name = state.forceStringNoCtx(*args[0], pos); + string contents = state.forceString(*args[1], context, pos); + + PathSet refs; + + for (auto path : context) { + if (path.at(0) != '/') + throw EvalError(format("in 'toFile': the file '%1%' cannot refer to " + "derivation outputs, at %2%") % + name % pos); + refs.insert(path); + } + + Path storePath = + settings.readOnlyMode + ? state.store->computeStorePathForText(name, contents, refs) + : state.store->addTextToStore(name, contents, refs, state.repair); + + /* Note: we don't need to add `context' to the context of the + result, since `storePath' itself has references to the paths + used in args[1]. */ + + mkString(v, storePath, {storePath}); +} + +static void addPath(EvalState& state, const Pos& pos, const string& name, + const Path& path_, Value* filterFun, bool recursive, + const Hash& expectedHash, Value& v) { + const auto path = evalSettings.pureEval && expectedHash + ? path_ + : state.checkSourcePath(path_); + PathFilter filter = filterFun ? ([&](const Path& path) { + auto st = lstat(path); + + /* Call the filter function. The first argument is the path, + the second is a string indicating the type of the file. */ + Value arg1; + mkString(arg1, path); + + Value fun2; + state.callFunction(*filterFun, arg1, fun2, noPos); + + Value arg2; + mkString(arg2, S_ISREG(st.st_mode) + ? "regular" + : S_ISDIR(st.st_mode) + ? "directory" + : S_ISLNK(st.st_mode) + ? "symlink" + : "unknown" /* not supported, will fail! */); + + Value res; + state.callFunction(fun2, arg2, res, noPos); + + return state.forceBool(res, pos); + }) + : defaultPathFilter; + + Path expectedStorePath; + if (expectedHash) { + expectedStorePath = + state.store->makeFixedOutputPath(recursive, expectedHash, name); + } + Path dstPath; + if (!expectedHash || !state.store->isValidPath(expectedStorePath)) { + dstPath = settings.readOnlyMode + ? state.store + ->computeStorePathForPath(name, path, recursive, + htSHA256, filter) + .first + : state.store->addToStore(name, path, recursive, htSHA256, + filter, state.repair); + if (expectedHash && expectedStorePath != dstPath) { + throw Error(format("store path mismatch in (possibly filtered) path " + "added from '%1%'") % + path); } - if (path.empty()) - throw EvalError(format("'path' required, at %1%") % pos); - if (name.empty()) - name = baseNameOf(path); + } else + dstPath = expectedStorePath; + + mkString(v, dstPath, {dstPath}); +} + +static void prim_filterSource(EvalState& state, const Pos& pos, Value** args, + Value& v) { + PathSet context; + Path path = state.coerceToPath(pos, *args[1], context); + if (!context.empty()) + throw EvalError(format("string '%1%' cannot refer to other paths, at %2%") % + path % pos); + + state.forceValue(*args[0]); + if (args[0]->type != tLambda) + throw TypeError(format("first argument in call to 'filterSource' is not a " + "function but %1%, at %2%") % + showType(*args[0]) % pos); + + addPath(state, pos, baseNameOf(path), path, args[0], true, Hash(), v); +} + +static void prim_path(EvalState& state, const Pos& pos, Value** args, + Value& v) { + state.forceAttrs(*args[0], pos); + Path path; + string name; + Value* filterFun = nullptr; + auto recursive = true; + Hash expectedHash; + + for (auto& attr : *args[0]->attrs) { + const string& n(attr.name); + if (n == "path") { + PathSet context; + path = state.coerceToPath(*attr.pos, *attr.value, context); + if (!context.empty()) + throw EvalError( + format("string '%1%' cannot refer to other paths, at %2%") % path % + *attr.pos); + } else if (attr.name == state.sName) + name = state.forceStringNoCtx(*attr.value, *attr.pos); + else if (n == "filter") { + state.forceValue(*attr.value); + filterFun = attr.value; + } else if (n == "recursive") + recursive = state.forceBool(*attr.value, *attr.pos); + else if (n == "sha256") + expectedHash = + Hash(state.forceStringNoCtx(*attr.value, *attr.pos), htSHA256); + else + throw EvalError( + format("unsupported argument '%1%' to 'addPath', at %2%") % + attr.name % *attr.pos); + } + if (path.empty()) throw EvalError(format("'path' required, at %1%") % pos); + if (name.empty()) name = baseNameOf(path); - addPath(state, pos, name, path, filterFun, recursive, expectedHash, v); + addPath(state, pos, name, path, filterFun, recursive, expectedHash, v); } - /************************************************************* * Sets *************************************************************/ - /* Return the names of the attributes in a set as a sorted list of strings. */ -static void prim_attrNames(EvalState & state, const Pos & pos, Value * * args, Value & v) -{ - state.forceAttrs(*args[0], pos); +static void prim_attrNames(EvalState& state, const Pos& pos, Value** args, + Value& v) { + state.forceAttrs(*args[0], pos); - state.mkList(v, args[0]->attrs->size()); + state.mkList(v, args[0]->attrs->size()); - size_t n = 0; - for (auto & i : *args[0]->attrs) - mkString(*(v.listElems()[n++] = state.allocValue()), i.name); + size_t n = 0; + for (auto& i : *args[0]->attrs) + mkString(*(v.listElems()[n++] = state.allocValue()), i.name); - std::sort(v.listElems(), v.listElems() + n, - [](Value * v1, Value * v2) { return strcmp(v1->string.s, v2->string.s) < 0; }); + std::sort(v.listElems(), v.listElems() + n, [](Value* v1, Value* v2) { + return strcmp(v1->string.s, v2->string.s) < 0; + }); } - /* Return the values of the attributes in a set as a list, in the same order as attrNames. */ -static void prim_attrValues(EvalState & state, const Pos & pos, Value * * args, Value & v) -{ - state.forceAttrs(*args[0], pos); +static void prim_attrValues(EvalState& state, const Pos& pos, Value** args, + Value& v) { + state.forceAttrs(*args[0], pos); - state.mkList(v, args[0]->attrs->size()); + state.mkList(v, args[0]->attrs->size()); - unsigned int n = 0; - for (auto & i : *args[0]->attrs) - v.listElems()[n++] = (Value *) &i; + unsigned int n = 0; + for (auto& i : *args[0]->attrs) v.listElems()[n++] = (Value*)&i; - std::sort(v.listElems(), v.listElems() + n, - [](Value * v1, Value * v2) { return (string) ((Attr *) v1)->name < (string) ((Attr *) v2)->name; }); + std::sort(v.listElems(), v.listElems() + n, [](Value* v1, Value* v2) { + return (string)((Attr*)v1)->name < (string)((Attr*)v2)->name; + }); - for (unsigned int i = 0; i < n; ++i) - v.listElems()[i] = ((Attr *) v.listElems()[i])->value; + for (unsigned int i = 0; i < n; ++i) + v.listElems()[i] = ((Attr*)v.listElems()[i])->value; } - /* Dynamic version of the `.' operator. */ -void prim_getAttr(EvalState & state, const Pos & pos, Value * * args, Value & v) -{ - string attr = state.forceStringNoCtx(*args[0], pos); - state.forceAttrs(*args[1], pos); - // !!! Should we create a symbol here or just do a lookup? - Bindings::iterator i = args[1]->attrs->find(state.symbols.create(attr)); - if (i == args[1]->attrs->end()) - throw EvalError(format("attribute '%1%' missing, at %2%") % attr % pos); - // !!! add to stack trace? - if (state.countCalls && i->pos) state.attrSelects[*i->pos]++; - state.forceValue(*i->value); - v = *i->value; +void prim_getAttr(EvalState& state, const Pos& pos, Value** args, Value& v) { + string attr = state.forceStringNoCtx(*args[0], pos); + state.forceAttrs(*args[1], pos); + // !!! Should we create a symbol here or just do a lookup? + Bindings::iterator i = args[1]->attrs->find(state.symbols.create(attr)); + if (i == args[1]->attrs->end()) + throw EvalError(format("attribute '%1%' missing, at %2%") % attr % pos); + // !!! add to stack trace? + if (state.countCalls && i->pos) state.attrSelects[*i->pos]++; + state.forceValue(*i->value); + v = *i->value; } - /* Return position information of the specified attribute. */ -void prim_unsafeGetAttrPos(EvalState & state, const Pos & pos, Value * * args, Value & v) -{ - string attr = state.forceStringNoCtx(*args[0], pos); - state.forceAttrs(*args[1], pos); - Bindings::iterator i = args[1]->attrs->find(state.symbols.create(attr)); - if (i == args[1]->attrs->end()) - mkNull(v); - else - state.mkPos(v, i->pos); +void prim_unsafeGetAttrPos(EvalState& state, const Pos& pos, Value** args, + Value& v) { + string attr = state.forceStringNoCtx(*args[0], pos); + state.forceAttrs(*args[1], pos); + Bindings::iterator i = args[1]->attrs->find(state.symbols.create(attr)); + if (i == args[1]->attrs->end()) + mkNull(v); + else + state.mkPos(v, i->pos); } - /* Dynamic version of the `?' operator. */ -static void prim_hasAttr(EvalState & state, const Pos & pos, Value * * args, Value & v) -{ - string attr = state.forceStringNoCtx(*args[0], pos); - state.forceAttrs(*args[1], pos); - mkBool(v, args[1]->attrs->find(state.symbols.create(attr)) != args[1]->attrs->end()); +static void prim_hasAttr(EvalState& state, const Pos& pos, Value** args, + Value& v) { + string attr = state.forceStringNoCtx(*args[0], pos); + state.forceAttrs(*args[1], pos); + mkBool(v, args[1]->attrs->find(state.symbols.create(attr)) != + args[1]->attrs->end()); } - /* Determine whether the argument is a set. */ -static void prim_isAttrs(EvalState & state, const Pos & pos, Value * * args, Value & v) -{ - state.forceValue(*args[0]); - mkBool(v, args[0]->type == tAttrs); +static void prim_isAttrs(EvalState& state, const Pos& pos, Value** args, + Value& v) { + state.forceValue(*args[0]); + mkBool(v, args[0]->type == tAttrs); } +static void prim_removeAttrs(EvalState& state, const Pos& pos, Value** args, + Value& v) { + state.forceAttrs(*args[0], pos); + state.forceList(*args[1], pos); -static void prim_removeAttrs(EvalState & state, const Pos & pos, Value * * args, Value & v) -{ - state.forceAttrs(*args[0], pos); - state.forceList(*args[1], pos); - - /* Get the attribute names to be removed. */ - std::set<Symbol> names; - for (unsigned int i = 0; i < args[1]->listSize(); ++i) { - state.forceStringNoCtx(*args[1]->listElems()[i], pos); - names.insert(state.symbols.create(args[1]->listElems()[i]->string.s)); - } + /* Get the attribute names to be removed. */ + std::set<Symbol> names; + for (unsigned int i = 0; i < args[1]->listSize(); ++i) { + state.forceStringNoCtx(*args[1]->listElems()[i], pos); + names.insert(state.symbols.create(args[1]->listElems()[i]->string.s)); + } - /* Copy all attributes not in that set. Note that we don't need - to sort v.attrs because it's a subset of an already sorted - vector. */ - state.mkAttrs(v, args[0]->attrs->size()); - for (auto & i : *args[0]->attrs) { - if (names.find(i.name) == names.end()) - v.attrs->push_back(i); - } + /* Copy all attributes not in that set. Note that we don't need + to sort v.attrs because it's a subset of an already sorted + vector. */ + state.mkAttrs(v, args[0]->attrs->size()); + for (auto& i : *args[0]->attrs) { + if (names.find(i.name) == names.end()) v.attrs->push_back(i); + } } - /* Builds a set from a list specifying (name, value) pairs. To be precise, a list [{name = "name1"; value = value1;} ... {name = "nameN"; value = valueN;}] is transformed to {name1 = value1; ... nameN = valueN;}. In case of duplicate occurences of the same name, the first takes precedence. */ -static void prim_listToAttrs(EvalState & state, const Pos & pos, Value * * args, Value & v) -{ - state.forceList(*args[0], pos); - - state.mkAttrs(v, args[0]->listSize()); - - std::set<Symbol> seen; - - for (unsigned int i = 0; i < args[0]->listSize(); ++i) { - Value & v2(*args[0]->listElems()[i]); - state.forceAttrs(v2, pos); - - Bindings::iterator j = v2.attrs->find(state.sName); - if (j == v2.attrs->end()) - throw TypeError(format("'name' attribute missing in a call to 'listToAttrs', at %1%") % pos); - string name = state.forceStringNoCtx(*j->value, pos); - - Symbol sym = state.symbols.create(name); - if (seen.find(sym) == seen.end()) { - Bindings::iterator j2 = v2.attrs->find(state.symbols.create(state.sValue)); - if (j2 == v2.attrs->end()) - throw TypeError(format("'value' attribute missing in a call to 'listToAttrs', at %1%") % pos); - - v.attrs->push_back(Attr(sym, j2->value, j2->pos)); - seen.insert(sym); - } +static void prim_listToAttrs(EvalState& state, const Pos& pos, Value** args, + Value& v) { + state.forceList(*args[0], pos); + + state.mkAttrs(v, args[0]->listSize()); + + std::set<Symbol> seen; + + for (unsigned int i = 0; i < args[0]->listSize(); ++i) { + Value& v2(*args[0]->listElems()[i]); + state.forceAttrs(v2, pos); + + Bindings::iterator j = v2.attrs->find(state.sName); + if (j == v2.attrs->end()) + throw TypeError( + format( + "'name' attribute missing in a call to 'listToAttrs', at %1%") % + pos); + string name = state.forceStringNoCtx(*j->value, pos); + + Symbol sym = state.symbols.create(name); + if (seen.find(sym) == seen.end()) { + Bindings::iterator j2 = + v2.attrs->find(state.symbols.create(state.sValue)); + if (j2 == v2.attrs->end()) + throw TypeError(format("'value' attribute missing in a call to " + "'listToAttrs', at %1%") % + pos); + + v.attrs->push_back(Attr(sym, j2->value, j2->pos)); + seen.insert(sym); } + } - v.attrs->sort(); + v.attrs->sort(); } - /* Return the right-biased intersection of two sets as1 and as2, i.e. a set that contains every attribute from as2 that is also a member of as1. */ -static void prim_intersectAttrs(EvalState & state, const Pos & pos, Value * * args, Value & v) -{ - state.forceAttrs(*args[0], pos); - state.forceAttrs(*args[1], pos); +static void prim_intersectAttrs(EvalState& state, const Pos& pos, Value** args, + Value& v) { + state.forceAttrs(*args[0], pos); + state.forceAttrs(*args[1], pos); - state.mkAttrs(v, std::min(args[0]->attrs->size(), args[1]->attrs->size())); + state.mkAttrs(v, std::min(args[0]->attrs->size(), args[1]->attrs->size())); - for (auto & i : *args[0]->attrs) { - Bindings::iterator j = args[1]->attrs->find(i.name); - if (j != args[1]->attrs->end()) - v.attrs->push_back(*j); - } + for (auto& i : *args[0]->attrs) { + Bindings::iterator j = args[1]->attrs->find(i.name); + if (j != args[1]->attrs->end()) v.attrs->push_back(*j); + } } - /* Collect each attribute named `attr' from a list of attribute sets. Sets that don't contain the named attribute are ignored. @@ -1312,31 +1343,29 @@ static void prim_intersectAttrs(EvalState & state, const Pos & pos, Value * * ar catAttrs "a" [{a = 1;} {b = 0;} {a = 2;}] => [1 2] */ -static void prim_catAttrs(EvalState & state, const Pos & pos, Value * * args, Value & v) -{ - Symbol attrName = state.symbols.create(state.forceStringNoCtx(*args[0], pos)); - state.forceList(*args[1], pos); - - Value * res[args[1]->listSize()]; - unsigned int found = 0; - - for (unsigned int n = 0; n < args[1]->listSize(); ++n) { - Value & v2(*args[1]->listElems()[n]); - state.forceAttrs(v2, pos); - Bindings::iterator i = v2.attrs->find(attrName); - if (i != v2.attrs->end()) - res[found++] = i->value; - } +static void prim_catAttrs(EvalState& state, const Pos& pos, Value** args, + Value& v) { + Symbol attrName = state.symbols.create(state.forceStringNoCtx(*args[0], pos)); + state.forceList(*args[1], pos); - state.mkList(v, found); - for (unsigned int n = 0; n < found; ++n) - v.listElems()[n] = res[n]; -} + Value* res[args[1]->listSize()]; + unsigned int found = 0; + + for (unsigned int n = 0; n < args[1]->listSize(); ++n) { + Value& v2(*args[1]->listElems()[n]); + state.forceAttrs(v2, pos); + Bindings::iterator i = v2.attrs->find(attrName); + if (i != v2.attrs->end()) res[found++] = i->value; + } + state.mkList(v, found); + for (unsigned int n = 0; n < found; ++n) v.listElems()[n] = res[n]; +} /* Return a set containing the names of the formal arguments expected by the function `f'. The value of each attribute is a Boolean - denoting whether the corresponding argument has a default value. For instance, + denoting whether the corresponding argument has a default value. For + instance, functionArgs ({ x, y ? 123}: ...) => { x = false; y = true; } @@ -1347,983 +1376,936 @@ static void prim_catAttrs(EvalState & state, const Pos & pos, Value * * args, Va functionArgs (x: ...) => { } */ -static void prim_functionArgs(EvalState & state, const Pos & pos, Value * * args, Value & v) -{ - state.forceValue(*args[0]); - if (args[0]->type != tLambda) - throw TypeError(format("'functionArgs' requires a function, at %1%") % pos); +static void prim_functionArgs(EvalState& state, const Pos& pos, Value** args, + Value& v) { + state.forceValue(*args[0]); + if (args[0]->type != tLambda) + throw TypeError(format("'functionArgs' requires a function, at %1%") % pos); - if (!args[0]->lambda.fun->matchAttrs) { - state.mkAttrs(v, 0); - return; - } + if (!args[0]->lambda.fun->matchAttrs) { + state.mkAttrs(v, 0); + return; + } - state.mkAttrs(v, args[0]->lambda.fun->formals->formals.size()); - for (auto & i : args[0]->lambda.fun->formals->formals) - // !!! should optimise booleans (allocate only once) - mkBool(*state.allocAttr(v, i.name), i.def); - v.attrs->sort(); + state.mkAttrs(v, args[0]->lambda.fun->formals->formals.size()); + for (auto& i : args[0]->lambda.fun->formals->formals) + // !!! should optimise booleans (allocate only once) + mkBool(*state.allocAttr(v, i.name), i.def); + v.attrs->sort(); } - /* Apply a function to every element of an attribute set. */ -static void prim_mapAttrs(EvalState & state, const Pos & pos, Value * * args, Value & v) -{ - state.forceAttrs(*args[1], pos); - - state.mkAttrs(v, args[1]->attrs->size()); - - for (auto & i : *args[1]->attrs) { - Value * vName = state.allocValue(); - Value * vFun2 = state.allocValue(); - mkString(*vName, i.name); - mkApp(*vFun2, *args[0], *vName); - mkApp(*state.allocAttr(v, i.name), *vFun2, *i.value); - } -} +static void prim_mapAttrs(EvalState& state, const Pos& pos, Value** args, + Value& v) { + state.forceAttrs(*args[1], pos); + state.mkAttrs(v, args[1]->attrs->size()); + for (auto& i : *args[1]->attrs) { + Value* vName = state.allocValue(); + Value* vFun2 = state.allocValue(); + mkString(*vName, i.name); + mkApp(*vFun2, *args[0], *vName); + mkApp(*state.allocAttr(v, i.name), *vFun2, *i.value); + } +} /************************************************************* * Lists *************************************************************/ - /* Determine whether the argument is a list. */ -static void prim_isList(EvalState & state, const Pos & pos, Value * * args, Value & v) -{ - state.forceValue(*args[0]); - mkBool(v, args[0]->isList()); +static void prim_isList(EvalState& state, const Pos& pos, Value** args, + Value& v) { + state.forceValue(*args[0]); + mkBool(v, args[0]->isList()); } - -static void elemAt(EvalState & state, const Pos & pos, Value & list, int n, Value & v) -{ - state.forceList(list, pos); - if (n < 0 || (unsigned int) n >= list.listSize()) - throw Error(format("list index %1% is out of bounds, at %2%") % n % pos); - state.forceValue(*list.listElems()[n]); - v = *list.listElems()[n]; +static void elemAt(EvalState& state, const Pos& pos, Value& list, int n, + Value& v) { + state.forceList(list, pos); + if (n < 0 || (unsigned int)n >= list.listSize()) + throw Error(format("list index %1% is out of bounds, at %2%") % n % pos); + state.forceValue(*list.listElems()[n]); + v = *list.listElems()[n]; } - /* Return the n-1'th element of a list. */ -static void prim_elemAt(EvalState & state, const Pos & pos, Value * * args, Value & v) -{ - elemAt(state, pos, *args[0], state.forceInt(*args[1], pos), v); +static void prim_elemAt(EvalState& state, const Pos& pos, Value** args, + Value& v) { + elemAt(state, pos, *args[0], state.forceInt(*args[1], pos), v); } - /* Return the first element of a list. */ -static void prim_head(EvalState & state, const Pos & pos, Value * * args, Value & v) -{ - elemAt(state, pos, *args[0], 0, v); +static void prim_head(EvalState& state, const Pos& pos, Value** args, + Value& v) { + elemAt(state, pos, *args[0], 0, v); } - /* Return a list consisting of everything but the first element of a list. Warning: this function takes O(n) time, so you probably don't want to use it! */ -static void prim_tail(EvalState & state, const Pos & pos, Value * * args, Value & v) -{ - state.forceList(*args[0], pos); - if (args[0]->listSize() == 0) - throw Error(format("'tail' called on an empty list, at %1%") % pos); - state.mkList(v, args[0]->listSize() - 1); - for (unsigned int n = 0; n < v.listSize(); ++n) - v.listElems()[n] = args[0]->listElems()[n + 1]; +static void prim_tail(EvalState& state, const Pos& pos, Value** args, + Value& v) { + state.forceList(*args[0], pos); + if (args[0]->listSize() == 0) + throw Error(format("'tail' called on an empty list, at %1%") % pos); + state.mkList(v, args[0]->listSize() - 1); + for (unsigned int n = 0; n < v.listSize(); ++n) + v.listElems()[n] = args[0]->listElems()[n + 1]; } - /* Apply a function to every element of a list. */ -static void prim_map(EvalState & state, const Pos & pos, Value * * args, Value & v) -{ - state.forceList(*args[1], pos); +static void prim_map(EvalState& state, const Pos& pos, Value** args, Value& v) { + state.forceList(*args[1], pos); - state.mkList(v, args[1]->listSize()); + state.mkList(v, args[1]->listSize()); - for (unsigned int n = 0; n < v.listSize(); ++n) - mkApp(*(v.listElems()[n] = state.allocValue()), - *args[0], *args[1]->listElems()[n]); + for (unsigned int n = 0; n < v.listSize(); ++n) + mkApp(*(v.listElems()[n] = state.allocValue()), *args[0], + *args[1]->listElems()[n]); } - /* Filter a list using a predicate; that is, return a list containing every element from the list for which the predicate function returns true. */ -static void prim_filter(EvalState & state, const Pos & pos, Value * * args, Value & v) -{ - state.forceFunction(*args[0], pos); - state.forceList(*args[1], pos); - - // FIXME: putting this on the stack is risky. - Value * vs[args[1]->listSize()]; - unsigned int k = 0; - - bool same = true; - for (unsigned int n = 0; n < args[1]->listSize(); ++n) { - Value res; - state.callFunction(*args[0], *args[1]->listElems()[n], res, noPos); - if (state.forceBool(res, pos)) - vs[k++] = args[1]->listElems()[n]; - else - same = false; - } +static void prim_filter(EvalState& state, const Pos& pos, Value** args, + Value& v) { + state.forceFunction(*args[0], pos); + state.forceList(*args[1], pos); + + // FIXME: putting this on the stack is risky. + Value* vs[args[1]->listSize()]; + unsigned int k = 0; + + bool same = true; + for (unsigned int n = 0; n < args[1]->listSize(); ++n) { + Value res; + state.callFunction(*args[0], *args[1]->listElems()[n], res, noPos); + if (state.forceBool(res, pos)) + vs[k++] = args[1]->listElems()[n]; + else + same = false; + } - if (same) - v = *args[1]; - else { - state.mkList(v, k); - for (unsigned int n = 0; n < k; ++n) v.listElems()[n] = vs[n]; - } + if (same) + v = *args[1]; + else { + state.mkList(v, k); + for (unsigned int n = 0; n < k; ++n) v.listElems()[n] = vs[n]; + } } - /* Return true if a list contains a given element. */ -static void prim_elem(EvalState & state, const Pos & pos, Value * * args, Value & v) -{ - bool res = false; - state.forceList(*args[1], pos); - for (unsigned int n = 0; n < args[1]->listSize(); ++n) - if (state.eqValues(*args[0], *args[1]->listElems()[n])) { - res = true; - break; - } - mkBool(v, res); +static void prim_elem(EvalState& state, const Pos& pos, Value** args, + Value& v) { + bool res = false; + state.forceList(*args[1], pos); + for (unsigned int n = 0; n < args[1]->listSize(); ++n) + if (state.eqValues(*args[0], *args[1]->listElems()[n])) { + res = true; + break; + } + mkBool(v, res); } - /* Concatenate a list of lists. */ -static void prim_concatLists(EvalState & state, const Pos & pos, Value * * args, Value & v) -{ - state.forceList(*args[0], pos); - state.concatLists(v, args[0]->listSize(), args[0]->listElems(), pos); +static void prim_concatLists(EvalState& state, const Pos& pos, Value** args, + Value& v) { + state.forceList(*args[0], pos); + state.concatLists(v, args[0]->listSize(), args[0]->listElems(), pos); } - /* Return the length of a list. This is an O(1) time operation. */ -static void prim_length(EvalState & state, const Pos & pos, Value * * args, Value & v) -{ - state.forceList(*args[0], pos); - mkInt(v, args[0]->listSize()); +static void prim_length(EvalState& state, const Pos& pos, Value** args, + Value& v) { + state.forceList(*args[0], pos); + mkInt(v, args[0]->listSize()); } - /* Reduce a list by applying a binary operator, from left to right. The operator is applied strictly. */ -static void prim_foldlStrict(EvalState & state, const Pos & pos, Value * * args, Value & v) -{ - state.forceFunction(*args[0], pos); - state.forceList(*args[2], pos); - - if (args[2]->listSize()) { - Value * vCur = args[1]; - - for (unsigned int n = 0; n < args[2]->listSize(); ++n) { - Value vTmp; - state.callFunction(*args[0], *vCur, vTmp, pos); - vCur = n == args[2]->listSize() - 1 ? &v : state.allocValue(); - state.callFunction(vTmp, *args[2]->listElems()[n], *vCur, pos); - } - state.forceValue(v); - } else { - state.forceValue(*args[1]); - v = *args[1]; +static void prim_foldlStrict(EvalState& state, const Pos& pos, Value** args, + Value& v) { + state.forceFunction(*args[0], pos); + state.forceList(*args[2], pos); + + if (args[2]->listSize()) { + Value* vCur = args[1]; + + for (unsigned int n = 0; n < args[2]->listSize(); ++n) { + Value vTmp; + state.callFunction(*args[0], *vCur, vTmp, pos); + vCur = n == args[2]->listSize() - 1 ? &v : state.allocValue(); + state.callFunction(vTmp, *args[2]->listElems()[n], *vCur, pos); } + state.forceValue(v); + } else { + state.forceValue(*args[1]); + v = *args[1]; + } } +static void anyOrAll(bool any, EvalState& state, const Pos& pos, Value** args, + Value& v) { + state.forceFunction(*args[0], pos); + state.forceList(*args[1], pos); -static void anyOrAll(bool any, EvalState & state, const Pos & pos, Value * * args, Value & v) -{ - state.forceFunction(*args[0], pos); - state.forceList(*args[1], pos); - - Value vTmp; - for (unsigned int n = 0; n < args[1]->listSize(); ++n) { - state.callFunction(*args[0], *args[1]->listElems()[n], vTmp, pos); - bool res = state.forceBool(vTmp, pos); - if (res == any) { - mkBool(v, any); - return; - } + Value vTmp; + for (unsigned int n = 0; n < args[1]->listSize(); ++n) { + state.callFunction(*args[0], *args[1]->listElems()[n], vTmp, pos); + bool res = state.forceBool(vTmp, pos); + if (res == any) { + mkBool(v, any); + return; } + } - mkBool(v, !any); + mkBool(v, !any); } - -static void prim_any(EvalState & state, const Pos & pos, Value * * args, Value & v) -{ - anyOrAll(true, state, pos, args, v); +static void prim_any(EvalState& state, const Pos& pos, Value** args, Value& v) { + anyOrAll(true, state, pos, args, v); } - -static void prim_all(EvalState & state, const Pos & pos, Value * * args, Value & v) -{ - anyOrAll(false, state, pos, args, v); +static void prim_all(EvalState& state, const Pos& pos, Value** args, Value& v) { + anyOrAll(false, state, pos, args, v); } +static void prim_genList(EvalState& state, const Pos& pos, Value** args, + Value& v) { + auto len = state.forceInt(*args[1], pos); -static void prim_genList(EvalState & state, const Pos & pos, Value * * args, Value & v) -{ - auto len = state.forceInt(*args[1], pos); + if (len < 0) + throw EvalError(format("cannot create list of size %1%, at %2%") % len % + pos); - if (len < 0) - throw EvalError(format("cannot create list of size %1%, at %2%") % len % pos); + state.mkList(v, len); - state.mkList(v, len); - - for (unsigned int n = 0; n < (unsigned int) len; ++n) { - Value * arg = state.allocValue(); - mkInt(*arg, n); - mkApp(*(v.listElems()[n] = state.allocValue()), *args[0], *arg); - } + for (unsigned int n = 0; n < (unsigned int)len; ++n) { + Value* arg = state.allocValue(); + mkInt(*arg, n); + mkApp(*(v.listElems()[n] = state.allocValue()), *args[0], *arg); + } } +static void prim_lessThan(EvalState& state, const Pos& pos, Value** args, + Value& v); -static void prim_lessThan(EvalState & state, const Pos & pos, Value * * args, Value & v); - - -static void prim_sort(EvalState & state, const Pos & pos, Value * * args, Value & v) -{ - state.forceFunction(*args[0], pos); - state.forceList(*args[1], pos); - - auto len = args[1]->listSize(); - state.mkList(v, len); - for (unsigned int n = 0; n < len; ++n) { - state.forceValue(*args[1]->listElems()[n]); - v.listElems()[n] = args[1]->listElems()[n]; - } +static void prim_sort(EvalState& state, const Pos& pos, Value** args, + Value& v) { + state.forceFunction(*args[0], pos); + state.forceList(*args[1], pos); + auto len = args[1]->listSize(); + state.mkList(v, len); + for (unsigned int n = 0; n < len; ++n) { + state.forceValue(*args[1]->listElems()[n]); + v.listElems()[n] = args[1]->listElems()[n]; + } - auto comparator = [&](Value * a, Value * b) { - /* Optimization: if the comparator is lessThan, bypass - callFunction. */ - if (args[0]->type == tPrimOp && args[0]->primOp->fun == prim_lessThan) - return CompareValues()(a, b); + auto comparator = [&](Value* a, Value* b) { + /* Optimization: if the comparator is lessThan, bypass + callFunction. */ + if (args[0]->type == tPrimOp && args[0]->primOp->fun == prim_lessThan) + return CompareValues()(a, b); - Value vTmp1, vTmp2; - state.callFunction(*args[0], *a, vTmp1, pos); - state.callFunction(vTmp1, *b, vTmp2, pos); - return state.forceBool(vTmp2, pos); - }; + Value vTmp1, vTmp2; + state.callFunction(*args[0], *a, vTmp1, pos); + state.callFunction(vTmp1, *b, vTmp2, pos); + return state.forceBool(vTmp2, pos); + }; - /* FIXME: std::sort can segfault if the comparator is not a strict - weak ordering. What to do? std::stable_sort() seems more - resilient, but no guarantees... */ - std::stable_sort(v.listElems(), v.listElems() + len, comparator); + /* FIXME: std::sort can segfault if the comparator is not a strict + weak ordering. What to do? std::stable_sort() seems more + resilient, but no guarantees... */ + std::stable_sort(v.listElems(), v.listElems() + len, comparator); } +static void prim_partition(EvalState& state, const Pos& pos, Value** args, + Value& v) { + state.forceFunction(*args[0], pos); + state.forceList(*args[1], pos); -static void prim_partition(EvalState & state, const Pos & pos, Value * * args, Value & v) -{ - state.forceFunction(*args[0], pos); - state.forceList(*args[1], pos); + auto len = args[1]->listSize(); - auto len = args[1]->listSize(); + ValueVector right, wrong; - ValueVector right, wrong; - - for (unsigned int n = 0; n < len; ++n) { - auto vElem = args[1]->listElems()[n]; - state.forceValue(*vElem); - Value res; - state.callFunction(*args[0], *vElem, res, pos); - if (state.forceBool(res, pos)) - right.push_back(vElem); - else - wrong.push_back(vElem); - } + for (unsigned int n = 0; n < len; ++n) { + auto vElem = args[1]->listElems()[n]; + state.forceValue(*vElem); + Value res; + state.callFunction(*args[0], *vElem, res, pos); + if (state.forceBool(res, pos)) + right.push_back(vElem); + else + wrong.push_back(vElem); + } - state.mkAttrs(v, 2); + state.mkAttrs(v, 2); - Value * vRight = state.allocAttr(v, state.sRight); - auto rsize = right.size(); - state.mkList(*vRight, rsize); - if (rsize) - memcpy(vRight->listElems(), right.data(), sizeof(Value *) * rsize); + Value* vRight = state.allocAttr(v, state.sRight); + auto rsize = right.size(); + state.mkList(*vRight, rsize); + if (rsize) memcpy(vRight->listElems(), right.data(), sizeof(Value*) * rsize); - Value * vWrong = state.allocAttr(v, state.sWrong); - auto wsize = wrong.size(); - state.mkList(*vWrong, wsize); - if (wsize) - memcpy(vWrong->listElems(), wrong.data(), sizeof(Value *) * wsize); + Value* vWrong = state.allocAttr(v, state.sWrong); + auto wsize = wrong.size(); + state.mkList(*vWrong, wsize); + if (wsize) memcpy(vWrong->listElems(), wrong.data(), sizeof(Value*) * wsize); - v.attrs->sort(); + v.attrs->sort(); } - /* concatMap = f: list: concatLists (map f list); */ /* C++-version is to avoid allocating `mkApp', call `f' eagerly */ -static void prim_concatMap(EvalState & state, const Pos & pos, Value * * args, Value & v) -{ - state.forceFunction(*args[0], pos); - state.forceList(*args[1], pos); - auto nrLists = args[1]->listSize(); - - Value lists[nrLists]; - size_t len = 0; - - for (unsigned int n = 0; n < nrLists; ++n) { - Value * vElem = args[1]->listElems()[n]; - state.callFunction(*args[0], *vElem, lists[n], pos); - state.forceList(lists[n], pos); - len += lists[n].listSize(); - } - - state.mkList(v, len); - auto out = v.listElems(); - for (unsigned int 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; - } +static void prim_concatMap(EvalState& state, const Pos& pos, Value** args, + Value& v) { + state.forceFunction(*args[0], pos); + state.forceList(*args[1], pos); + auto nrLists = args[1]->listSize(); + + Value lists[nrLists]; + size_t len = 0; + + for (unsigned int n = 0; n < nrLists; ++n) { + Value* vElem = args[1]->listElems()[n]; + state.callFunction(*args[0], *vElem, lists[n], pos); + state.forceList(lists[n], pos); + len += lists[n].listSize(); + } + + state.mkList(v, len); + auto out = v.listElems(); + for (unsigned int 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; + } } - /************************************************************* * Integer arithmetic *************************************************************/ - -static void prim_add(EvalState & state, const Pos & pos, Value * * args, Value & v) -{ - state.forceValue(*args[0], pos); - state.forceValue(*args[1], pos); - if (args[0]->type == tFloat || args[1]->type == tFloat) - mkFloat(v, state.forceFloat(*args[0], pos) + state.forceFloat(*args[1], pos)); - else - mkInt(v, state.forceInt(*args[0], pos) + state.forceInt(*args[1], pos)); +static void prim_add(EvalState& state, const Pos& pos, Value** args, Value& v) { + state.forceValue(*args[0], pos); + state.forceValue(*args[1], pos); + if (args[0]->type == tFloat || args[1]->type == tFloat) + mkFloat(v, + state.forceFloat(*args[0], pos) + state.forceFloat(*args[1], pos)); + else + mkInt(v, state.forceInt(*args[0], pos) + state.forceInt(*args[1], pos)); } - -static void prim_sub(EvalState & state, const Pos & pos, Value * * args, Value & v) -{ - state.forceValue(*args[0], pos); - state.forceValue(*args[1], pos); - if (args[0]->type == tFloat || args[1]->type == tFloat) - mkFloat(v, state.forceFloat(*args[0], pos) - state.forceFloat(*args[1], pos)); - else - mkInt(v, state.forceInt(*args[0], pos) - state.forceInt(*args[1], pos)); +static void prim_sub(EvalState& state, const Pos& pos, Value** args, Value& v) { + state.forceValue(*args[0], pos); + state.forceValue(*args[1], pos); + if (args[0]->type == tFloat || args[1]->type == tFloat) + mkFloat(v, + state.forceFloat(*args[0], pos) - state.forceFloat(*args[1], pos)); + else + mkInt(v, state.forceInt(*args[0], pos) - state.forceInt(*args[1], pos)); } - -static void prim_mul(EvalState & state, const Pos & pos, Value * * args, Value & v) -{ - state.forceValue(*args[0], pos); - state.forceValue(*args[1], pos); - if (args[0]->type == tFloat || args[1]->type == tFloat) - mkFloat(v, state.forceFloat(*args[0], pos) * state.forceFloat(*args[1], pos)); - else - mkInt(v, state.forceInt(*args[0], pos) * state.forceInt(*args[1], pos)); +static void prim_mul(EvalState& state, const Pos& pos, Value** args, Value& v) { + state.forceValue(*args[0], pos); + state.forceValue(*args[1], pos); + if (args[0]->type == tFloat || args[1]->type == tFloat) + mkFloat(v, + state.forceFloat(*args[0], pos) * state.forceFloat(*args[1], pos)); + else + mkInt(v, state.forceInt(*args[0], pos) * state.forceInt(*args[1], pos)); } +static void prim_div(EvalState& state, const Pos& pos, Value** args, Value& v) { + state.forceValue(*args[0], pos); + state.forceValue(*args[1], pos); -static void prim_div(EvalState & state, const Pos & pos, Value * * args, Value & v) -{ - state.forceValue(*args[0], pos); - state.forceValue(*args[1], pos); - - NixFloat f2 = state.forceFloat(*args[1], pos); - if (f2 == 0) throw EvalError(format("division by zero, at %1%") % pos); + NixFloat f2 = state.forceFloat(*args[1], pos); + if (f2 == 0) throw EvalError(format("division by zero, at %1%") % pos); - if (args[0]->type == tFloat || args[1]->type == tFloat) { - mkFloat(v, state.forceFloat(*args[0], pos) / state.forceFloat(*args[1], pos)); - } else { - NixInt i1 = state.forceInt(*args[0], pos); - NixInt i2 = state.forceInt(*args[1], pos); - /* Avoid division overflow as it might raise SIGFPE. */ - if (i1 == std::numeric_limits<NixInt>::min() && i2 == -1) - throw EvalError(format("overflow in integer division, at %1%") % pos); - mkInt(v, i1 / i2); - } + if (args[0]->type == tFloat || args[1]->type == tFloat) { + mkFloat(v, + state.forceFloat(*args[0], pos) / state.forceFloat(*args[1], pos)); + } else { + NixInt i1 = state.forceInt(*args[0], pos); + NixInt i2 = state.forceInt(*args[1], pos); + /* Avoid division overflow as it might raise SIGFPE. */ + if (i1 == std::numeric_limits<NixInt>::min() && i2 == -1) + throw EvalError(format("overflow in integer division, at %1%") % pos); + mkInt(v, i1 / i2); + } } -static void prim_bitAnd(EvalState & state, const Pos & pos, Value * * args, Value & v) -{ - mkInt(v, state.forceInt(*args[0], pos) & state.forceInt(*args[1], pos)); +static void prim_bitAnd(EvalState& state, const Pos& pos, Value** args, + Value& v) { + mkInt(v, state.forceInt(*args[0], pos) & state.forceInt(*args[1], pos)); } -static void prim_bitOr(EvalState & state, const Pos & pos, Value * * args, Value & v) -{ - mkInt(v, state.forceInt(*args[0], pos) | state.forceInt(*args[1], pos)); +static void prim_bitOr(EvalState& state, const Pos& pos, Value** args, + Value& v) { + mkInt(v, state.forceInt(*args[0], pos) | state.forceInt(*args[1], pos)); } -static void prim_bitXor(EvalState & state, const Pos & pos, Value * * args, Value & v) -{ - mkInt(v, state.forceInt(*args[0], pos) ^ state.forceInt(*args[1], pos)); +static void prim_bitXor(EvalState& state, const Pos& pos, Value** args, + Value& v) { + mkInt(v, state.forceInt(*args[0], pos) ^ state.forceInt(*args[1], pos)); } -static void prim_lessThan(EvalState & state, const Pos & pos, Value * * args, Value & v) -{ - state.forceValue(*args[0]); - state.forceValue(*args[1]); - CompareValues comp; - mkBool(v, comp(args[0], args[1])); +static void prim_lessThan(EvalState& state, const Pos& pos, Value** args, + Value& v) { + state.forceValue(*args[0]); + state.forceValue(*args[1]); + CompareValues comp; + mkBool(v, comp(args[0], args[1])); } - /************************************************************* * String manipulation *************************************************************/ - /* Convert the argument to a string. Paths are *not* copied to the store, so `toString /foo/bar' yields `"/foo/bar"', not `"/nix/store/whatever..."'. */ -static void prim_toString(EvalState & state, const Pos & pos, Value * * args, Value & v) -{ - PathSet context; - string s = state.coerceToString(pos, *args[0], context, true, false); - mkString(v, s, context); +static void prim_toString(EvalState& state, const Pos& pos, Value** args, + Value& v) { + PathSet context; + string s = state.coerceToString(pos, *args[0], context, true, false); + mkString(v, s, context); } - /* `substring start len str' returns the substring of `str' starting at character position `min(start, stringLength str)' inclusive and ending at `min(start + len, stringLength str)'. `start' must be non-negative. */ -static void prim_substring(EvalState & state, const Pos & pos, Value * * args, Value & v) -{ - int start = state.forceInt(*args[0], pos); - int len = state.forceInt(*args[1], pos); - PathSet context; - string s = state.coerceToString(pos, *args[2], context); +static void prim_substring(EvalState& state, const Pos& pos, Value** args, + Value& v) { + int start = state.forceInt(*args[0], pos); + int len = state.forceInt(*args[1], pos); + PathSet context; + string s = state.coerceToString(pos, *args[2], context); - if (start < 0) throw EvalError(format("negative start position in 'substring', at %1%") % pos); + if (start < 0) + throw EvalError(format("negative start position in 'substring', at %1%") % + pos); - mkString(v, (unsigned int) start >= s.size() ? "" : string(s, start, len), context); + mkString(v, (unsigned int)start >= s.size() ? "" : string(s, start, len), + context); } - -static void prim_stringLength(EvalState & state, const Pos & pos, Value * * args, Value & v) -{ - PathSet context; - string s = state.coerceToString(pos, *args[0], context); - mkInt(v, s.size()); +static void prim_stringLength(EvalState& state, const Pos& pos, Value** args, + Value& v) { + PathSet context; + string s = state.coerceToString(pos, *args[0], context); + mkInt(v, s.size()); } - /* Return the cryptographic hash of a string in base-16. */ -static void prim_hashString(EvalState & state, const Pos & pos, Value * * args, Value & v) -{ - string type = state.forceStringNoCtx(*args[0], pos); - HashType ht = parseHashType(type); - if (ht == htUnknown) - throw Error(format("unknown hash type '%1%', at %2%") % type % pos); +static void prim_hashString(EvalState& state, const Pos& pos, Value** args, + Value& v) { + string type = state.forceStringNoCtx(*args[0], pos); + HashType ht = parseHashType(type); + if (ht == htUnknown) + throw Error(format("unknown hash type '%1%', at %2%") % type % pos); - PathSet context; // discarded - string s = state.forceString(*args[1], context, pos); + PathSet context; // discarded + string s = state.forceString(*args[1], context, pos); - mkString(v, hashString(ht, s).to_string(Base16, false), context); + mkString(v, hashString(ht, s).to_string(Base16, false), context); } - /* Match a regular expression against a string and return either ‘null’ or a list containing substring matches. */ -static void prim_match(EvalState & state, const Pos & pos, Value * * args, Value & v) -{ - auto re = state.forceStringNoCtx(*args[0], pos); - - try { +static void prim_match(EvalState& state, const Pos& pos, Value** args, + Value& v) { + auto re = state.forceStringNoCtx(*args[0], pos); - std::regex regex(re, std::regex::extended); + try { + std::regex regex(re, std::regex::extended); - PathSet context; - const std::string str = state.forceString(*args[1], context, pos); + PathSet context; + const std::string str = state.forceString(*args[1], context, pos); - std::smatch match; - if (!std::regex_match(str, match, regex)) { - mkNull(v); - return; - } + std::smatch match; + if (!std::regex_match(str, match, regex)) { + mkNull(v); + return; + } - // the first match is the whole string - const size_t len = match.size() - 1; - state.mkList(v, len); - for (size_t i = 0; i < len; ++i) { - if (!match[i+1].matched) - mkNull(*(v.listElems()[i] = state.allocValue())); - else - mkString(*(v.listElems()[i] = state.allocValue()), match[i + 1].str().c_str()); - } + // the first match is the whole string + const size_t len = match.size() - 1; + state.mkList(v, len); + for (size_t i = 0; i < len; ++i) { + if (!match[i + 1].matched) + mkNull(*(v.listElems()[i] = state.allocValue())); + else + mkString(*(v.listElems()[i] = state.allocValue()), + match[i + 1].str().c_str()); + } - } catch (std::regex_error &e) { - if (e.code() == std::regex_constants::error_space) { - // limit is _GLIBCXX_REGEX_STATE_LIMIT for libstdc++ - throw EvalError("memory limit exceeded by regular expression '%s', at %s", re, pos); - } else { - throw EvalError("invalid regular expression '%s', at %s", re, pos); - } + } catch (std::regex_error& e) { + if (e.code() == std::regex_constants::error_space) { + // limit is _GLIBCXX_REGEX_STATE_LIMIT for libstdc++ + throw EvalError("memory limit exceeded by regular expression '%s', at %s", + re, pos); + } else { + throw EvalError("invalid regular expression '%s', at %s", re, pos); } + } } - /* Split a string with a regular expression, and return a list of the non-matching parts interleaved by the lists of the matching groups. */ -static void prim_split(EvalState & state, const Pos & pos, Value * * args, Value & v) -{ - auto re = state.forceStringNoCtx(*args[0], pos); - - try { - - std::regex regex(re, std::regex::extended); - - PathSet context; - const std::string str = state.forceString(*args[1], context, pos); +static void prim_split(EvalState& state, const Pos& pos, Value** args, + Value& v) { + auto re = state.forceStringNoCtx(*args[0], pos); - auto begin = std::sregex_iterator(str.begin(), str.end(), regex); - auto end = std::sregex_iterator(); + try { + std::regex regex(re, std::regex::extended); - // Any matches results are surrounded by non-matching results. - const size_t len = std::distance(begin, end); - state.mkList(v, 2 * len + 1); - size_t idx = 0; - Value * elem; + PathSet context; + const std::string str = state.forceString(*args[1], context, pos); - if (len == 0) { - v.listElems()[idx++] = args[1]; - return; - } + auto begin = std::sregex_iterator(str.begin(), str.end(), regex); + auto end = std::sregex_iterator(); - for (std::sregex_iterator i = begin; i != end; ++i) { - assert(idx <= 2 * len + 1 - 3); - std::smatch match = *i; - - // Add a string for non-matched characters. - elem = v.listElems()[idx++] = state.allocValue(); - mkString(*elem, match.prefix().str().c_str()); - - // Add a list for matched substrings. - const size_t slen = match.size() - 1; - elem = v.listElems()[idx++] = state.allocValue(); - - // Start at 1, beacause the first match is the whole string. - state.mkList(*elem, slen); - for (size_t si = 0; si < slen; ++si) { - if (!match[si + 1].matched) - mkNull(*(elem->listElems()[si] = state.allocValue())); - else - mkString(*(elem->listElems()[si] = state.allocValue()), match[si + 1].str().c_str()); - } - - // Add a string for non-matched suffix characters. - if (idx == 2 * len) { - elem = v.listElems()[idx++] = state.allocValue(); - mkString(*elem, match.suffix().str().c_str()); - } - } - assert(idx == 2 * len + 1); + // Any matches results are surrounded by non-matching results. + const size_t len = std::distance(begin, end); + state.mkList(v, 2 * len + 1); + size_t idx = 0; + Value* elem; - } catch (std::regex_error &e) { - if (e.code() == std::regex_constants::error_space) { - // limit is _GLIBCXX_REGEX_STATE_LIMIT for libstdc++ - throw EvalError("memory limit exceeded by regular expression '%s', at %s", re, pos); - } else { - throw EvalError("invalid regular expression '%s', at %s", re, pos); - } + if (len == 0) { + v.listElems()[idx++] = args[1]; + return; } -} + for (std::sregex_iterator i = begin; i != end; ++i) { + assert(idx <= 2 * len + 1 - 3); + std::smatch match = *i; -static void prim_concatStringSep(EvalState & state, const Pos & pos, Value * * args, Value & v) -{ - PathSet context; - - auto sep = state.forceString(*args[0], context, pos); - state.forceList(*args[1], pos); + // Add a string for non-matched characters. + elem = v.listElems()[idx++] = state.allocValue(); + mkString(*elem, match.prefix().str().c_str()); - string res; - res.reserve((args[1]->listSize() + 32) * sep.size()); - bool first = true; + // Add a list for matched substrings. + const size_t slen = match.size() - 1; + elem = v.listElems()[idx++] = state.allocValue(); - for (unsigned int n = 0; n < args[1]->listSize(); ++n) { - if (first) first = false; else res += sep; - res += state.coerceToString(pos, *args[1]->listElems()[n], context); + // Start at 1, beacause the first match is the whole string. + state.mkList(*elem, slen); + for (size_t si = 0; si < slen; ++si) { + if (!match[si + 1].matched) + mkNull(*(elem->listElems()[si] = state.allocValue())); + else + mkString(*(elem->listElems()[si] = state.allocValue()), + match[si + 1].str().c_str()); + } + + // Add a string for non-matched suffix characters. + if (idx == 2 * len) { + elem = v.listElems()[idx++] = state.allocValue(); + mkString(*elem, match.suffix().str().c_str()); + } } + assert(idx == 2 * len + 1); - mkString(v, res, context); + } catch (std::regex_error& e) { + if (e.code() == std::regex_constants::error_space) { + // limit is _GLIBCXX_REGEX_STATE_LIMIT for libstdc++ + throw EvalError("memory limit exceeded by regular expression '%s', at %s", + re, pos); + } else { + throw EvalError("invalid regular expression '%s', at %s", re, pos); + } + } } +static void prim_concatStringSep(EvalState& state, const Pos& pos, Value** args, + Value& v) { + PathSet context; -static void prim_replaceStrings(EvalState & state, const Pos & pos, Value * * args, Value & v) -{ - state.forceList(*args[0], pos); - state.forceList(*args[1], pos); - if (args[0]->listSize() != args[1]->listSize()) - throw EvalError(format("'from' and 'to' arguments to 'replaceStrings' have different lengths, at %1%") % pos); - - vector<string> from; - from.reserve(args[0]->listSize()); - for (unsigned int n = 0; n < args[0]->listSize(); ++n) - from.push_back(state.forceString(*args[0]->listElems()[n], pos)); + auto sep = state.forceString(*args[0], context, pos); + state.forceList(*args[1], pos); - vector<std::pair<string, PathSet>> to; - to.reserve(args[1]->listSize()); - for (unsigned int n = 0; n < args[1]->listSize(); ++n) { - PathSet ctx; - auto s = state.forceString(*args[1]->listElems()[n], ctx, pos); - to.push_back(std::make_pair(std::move(s), std::move(ctx))); - } + string res; + res.reserve((args[1]->listSize() + 32) * sep.size()); + bool first = true; - PathSet context; - auto s = state.forceString(*args[2], context, pos); - - string res; - // Loops one past last character to handle the case where 'from' contains an empty string. - for (size_t p = 0; p <= s.size(); ) { - bool found = false; - auto i = from.begin(); - auto j = to.begin(); - for (; i != from.end(); ++i, ++j) - if (s.compare(p, i->size(), *i) == 0) { - found = true; - res += j->first; - if (i->empty()) { - if (p < s.size()) - res += s[p]; - p++; - } else { - p += i->size(); - } - for (auto& path : j->second) - context.insert(path); - j->second.clear(); - break; - } - if (!found) { - if (p < s.size()) - res += s[p]; - p++; + for (unsigned int n = 0; n < args[1]->listSize(); ++n) { + if (first) + first = false; + else + res += sep; + res += state.coerceToString(pos, *args[1]->listElems()[n], context); + } + + mkString(v, res, context); +} + +static void prim_replaceStrings(EvalState& state, const Pos& pos, Value** args, + Value& v) { + state.forceList(*args[0], pos); + state.forceList(*args[1], pos); + if (args[0]->listSize() != args[1]->listSize()) + throw EvalError(format("'from' and 'to' arguments to 'replaceStrings' have " + "different lengths, at %1%") % + pos); + + vector<string> from; + from.reserve(args[0]->listSize()); + for (unsigned int n = 0; n < args[0]->listSize(); ++n) + from.push_back(state.forceString(*args[0]->listElems()[n], pos)); + + vector<std::pair<string, PathSet>> to; + to.reserve(args[1]->listSize()); + for (unsigned int n = 0; n < args[1]->listSize(); ++n) { + PathSet ctx; + auto s = state.forceString(*args[1]->listElems()[n], ctx, pos); + to.push_back(std::make_pair(std::move(s), std::move(ctx))); + } + + PathSet context; + auto s = state.forceString(*args[2], context, pos); + + string res; + // Loops one past last character to handle the case where 'from' contains an + // empty string. + for (size_t p = 0; p <= s.size();) { + bool found = false; + auto i = from.begin(); + auto j = to.begin(); + for (; i != from.end(); ++i, ++j) + if (s.compare(p, i->size(), *i) == 0) { + found = true; + res += j->first; + if (i->empty()) { + if (p < s.size()) res += s[p]; + p++; + } else { + p += i->size(); } + for (auto& path : j->second) context.insert(path); + j->second.clear(); + break; + } + if (!found) { + if (p < s.size()) res += s[p]; + p++; } + } - mkString(v, res, context); + mkString(v, res, context); } - /************************************************************* * Versions *************************************************************/ - -static void prim_parseDrvName(EvalState & state, const Pos & pos, Value * * args, Value & v) -{ - string name = state.forceStringNoCtx(*args[0], pos); - DrvName parsed(name); - state.mkAttrs(v, 2); - mkString(*state.allocAttr(v, state.sName), parsed.name); - mkString(*state.allocAttr(v, state.symbols.create("version")), parsed.version); - v.attrs->sort(); -} - - -static void prim_compareVersions(EvalState & state, const Pos & pos, Value * * args, Value & v) -{ - string version1 = state.forceStringNoCtx(*args[0], pos); - string version2 = state.forceStringNoCtx(*args[1], pos); - mkInt(v, compareVersions(version1, version2)); +static void prim_parseDrvName(EvalState& state, const Pos& pos, Value** args, + Value& v) { + string name = state.forceStringNoCtx(*args[0], pos); + DrvName parsed(name); + state.mkAttrs(v, 2); + mkString(*state.allocAttr(v, state.sName), parsed.name); + mkString(*state.allocAttr(v, state.symbols.create("version")), + parsed.version); + v.attrs->sort(); +} + +static void prim_compareVersions(EvalState& state, const Pos& pos, Value** args, + Value& v) { + string version1 = state.forceStringNoCtx(*args[0], pos); + string version2 = state.forceStringNoCtx(*args[1], pos); + mkInt(v, compareVersions(version1, version2)); +} + +static void prim_splitVersion(EvalState& state, const Pos& pos, Value** args, + Value& v) { + string version = state.forceStringNoCtx(*args[0], pos); + auto iter = version.cbegin(); + Strings components; + while (iter != version.cend()) { + auto component = nextComponent(iter, version.cend()); + if (component.empty()) break; + components.emplace_back(std::move(component)); + } + state.mkList(v, components.size()); + unsigned int n = 0; + for (auto& component : components) { + auto listElem = v.listElems()[n++] = state.allocValue(); + mkString(*listElem, std::move(component)); + } } - -static void prim_splitVersion(EvalState & state, const Pos & pos, Value * * args, Value & v) -{ - string version = state.forceStringNoCtx(*args[0], pos); - auto iter = version.cbegin(); - Strings components; - while (iter != version.cend()) { - auto component = nextComponent(iter, version.cend()); - if (component.empty()) - break; - components.emplace_back(std::move(component)); - } - state.mkList(v, components.size()); - unsigned int n = 0; - for (auto & component : components) { - auto listElem = v.listElems()[n++] = state.allocValue(); - mkString(*listElem, std::move(component)); - } -} - - /************************************************************* * Networking *************************************************************/ +void fetch(EvalState& state, const Pos& pos, Value** args, Value& v, + const string& who, bool unpack, const std::string& defaultName) { + CachedDownloadRequest request(""); + request.unpack = unpack; + request.name = defaultName; -void fetch(EvalState & state, const Pos & pos, Value * * args, Value & v, - const string & who, bool unpack, const std::string & defaultName) -{ - CachedDownloadRequest request(""); - request.unpack = unpack; - request.name = defaultName; - - state.forceValue(*args[0]); - - if (args[0]->type == tAttrs) { + state.forceValue(*args[0]); - state.forceAttrs(*args[0], pos); + if (args[0]->type == tAttrs) { + state.forceAttrs(*args[0], pos); - for (auto & attr : *args[0]->attrs) { - string n(attr.name); - if (n == "url") - request.uri = state.forceStringNoCtx(*attr.value, *attr.pos); - else if (n == "sha256") - request.expectedHash = Hash(state.forceStringNoCtx(*attr.value, *attr.pos), htSHA256); - else if (n == "name") - request.name = state.forceStringNoCtx(*attr.value, *attr.pos); - else - throw EvalError(format("unsupported argument '%1%' to '%2%', at %3%") % attr.name % who % attr.pos); - } + for (auto& attr : *args[0]->attrs) { + string n(attr.name); + if (n == "url") + request.uri = state.forceStringNoCtx(*attr.value, *attr.pos); + else if (n == "sha256") + request.expectedHash = + Hash(state.forceStringNoCtx(*attr.value, *attr.pos), htSHA256); + else if (n == "name") + request.name = state.forceStringNoCtx(*attr.value, *attr.pos); + else + throw EvalError(format("unsupported argument '%1%' to '%2%', at %3%") % + attr.name % who % attr.pos); + } - if (request.uri.empty()) - throw EvalError(format("'url' argument required, at %1%") % pos); + if (request.uri.empty()) + throw EvalError(format("'url' argument required, at %1%") % pos); - } else - request.uri = state.forceStringNoCtx(*args[0], pos); + } else + request.uri = state.forceStringNoCtx(*args[0], pos); - state.checkURI(request.uri); + state.checkURI(request.uri); - if (evalSettings.pureEval && !request.expectedHash) - throw Error("in pure evaluation mode, '%s' requires a 'sha256' argument", who); + if (evalSettings.pureEval && !request.expectedHash) + throw Error("in pure evaluation mode, '%s' requires a 'sha256' argument", + who); - auto res = getDownloader()->downloadCached(state.store, request); + auto res = getDownloader()->downloadCached(state.store, request); - if (state.allowedPaths) - state.allowedPaths->insert(res.path); + if (state.allowedPaths) state.allowedPaths->insert(res.path); - mkString(v, res.storePath, PathSet({res.storePath})); + mkString(v, res.storePath, PathSet({res.storePath})); } - -static void prim_fetchurl(EvalState & state, const Pos & pos, Value * * args, Value & v) -{ - fetch(state, pos, args, v, "fetchurl", false, ""); +static void prim_fetchurl(EvalState& state, const Pos& pos, Value** args, + Value& v) { + fetch(state, pos, args, v, "fetchurl", false, ""); } - -static void prim_fetchTarball(EvalState & state, const Pos & pos, Value * * args, Value & v) -{ - fetch(state, pos, args, v, "fetchTarball", true, "source"); +static void prim_fetchTarball(EvalState& state, const Pos& pos, Value** args, + Value& v) { + fetch(state, pos, args, v, "fetchTarball", true, "source"); } - /************************************************************* * Primop registration *************************************************************/ - -RegisterPrimOp::PrimOps * RegisterPrimOp::primOps; - - -RegisterPrimOp::RegisterPrimOp(std::string name, size_t arity, PrimOpFun fun) -{ - if (!primOps) primOps = new PrimOps; - primOps->emplace_back(name, arity, fun); -} - - -void EvalState::createBaseEnv() -{ - baseEnv.up = 0; - - /* Add global constants such as `true' to the base environment. */ - Value v; - - /* `builtins' must be first! */ - mkAttrs(v, 128); - addConstant("builtins", v); - - mkBool(v, true); - addConstant("true", v); - - mkBool(v, false); - addConstant("false", v); - - mkNull(v); - addConstant("null", v); - - auto vThrow = addPrimOp("throw", 1, prim_throw); - - auto addPurityError = [&](const std::string & name) { - Value * v2 = allocValue(); - mkString(*v2, fmt("'%s' is not allowed in pure evaluation mode", name)); - mkApp(v, *vThrow, *v2); - addConstant(name, v); - }; - - if (!evalSettings.pureEval) { - mkInt(v, time(0)); - addConstant("__currentTime", v); - } - - if (!evalSettings.pureEval) { - mkString(v, settings.thisSystem); - addConstant("__currentSystem", v); - } - - mkString(v, nixVersion); - addConstant("__nixVersion", v); - - mkString(v, store->storeDir); - addConstant("__storeDir", v); - - /* Language version. This should be increased every time a new - language feature gets added. It's not necessary to increase it - when primops get added, because you can just use `builtins ? - primOp' to check. */ - mkInt(v, 5); - addConstant("__langVersion", v); - - // Miscellaneous - auto vScopedImport = addPrimOp("scopedImport", 2, prim_scopedImport); - Value * v2 = allocValue(); - mkAttrs(*v2, 0); - mkApp(v, *vScopedImport, *v2); - forceValue(v); - addConstant("import", v); - if (evalSettings.enableNativeCode) { - addPrimOp("__importNative", 2, prim_importNative); - addPrimOp("__exec", 1, prim_exec); - } - addPrimOp("__typeOf", 1, prim_typeOf); - addPrimOp("isNull", 1, prim_isNull); - addPrimOp("__isFunction", 1, prim_isFunction); - addPrimOp("__isString", 1, prim_isString); - addPrimOp("__isInt", 1, prim_isInt); - addPrimOp("__isFloat", 1, prim_isFloat); - addPrimOp("__isBool", 1, prim_isBool); - addPrimOp("__isPath", 1, prim_isPath); - addPrimOp("__genericClosure", 1, prim_genericClosure); - addPrimOp("abort", 1, prim_abort); - addPrimOp("__addErrorContext", 2, prim_addErrorContext); - addPrimOp("__tryEval", 1, prim_tryEval); - addPrimOp("__getEnv", 1, prim_getEnv); - - // Strictness - addPrimOp("__seq", 2, prim_seq); - addPrimOp("__deepSeq", 2, prim_deepSeq); - - // Debugging - addPrimOp("__trace", 2, prim_trace); - addPrimOp("__valueSize", 1, prim_valueSize); - - // Paths - addPrimOp("__toPath", 1, prim_toPath); - if (evalSettings.pureEval) - addPurityError("__storePath"); - else - addPrimOp("__storePath", 1, prim_storePath); - addPrimOp("__pathExists", 1, prim_pathExists); - addPrimOp("baseNameOf", 1, prim_baseNameOf); - addPrimOp("dirOf", 1, prim_dirOf); - addPrimOp("__readFile", 1, prim_readFile); - addPrimOp("__readDir", 1, prim_readDir); - addPrimOp("__findFile", 2, prim_findFile); - addPrimOp("__hashFile", 2, prim_hashFile); - - // Creating files - addPrimOp("__toXML", 1, prim_toXML); - addPrimOp("__toJSON", 1, prim_toJSON); - addPrimOp("__fromJSON", 1, prim_fromJSON); - addPrimOp("__toFile", 2, prim_toFile); - addPrimOp("__filterSource", 2, prim_filterSource); - addPrimOp("__path", 1, prim_path); - - // Sets - addPrimOp("__attrNames", 1, prim_attrNames); - addPrimOp("__attrValues", 1, prim_attrValues); - addPrimOp("__getAttr", 2, prim_getAttr); - addPrimOp("__unsafeGetAttrPos", 2, prim_unsafeGetAttrPos); - addPrimOp("__hasAttr", 2, prim_hasAttr); - addPrimOp("__isAttrs", 1, prim_isAttrs); - addPrimOp("removeAttrs", 2, prim_removeAttrs); - addPrimOp("__listToAttrs", 1, prim_listToAttrs); - addPrimOp("__intersectAttrs", 2, prim_intersectAttrs); - addPrimOp("__catAttrs", 2, prim_catAttrs); - addPrimOp("__functionArgs", 1, prim_functionArgs); - addPrimOp("__mapAttrs", 2, prim_mapAttrs); - - // Lists - addPrimOp("__isList", 1, prim_isList); - addPrimOp("__elemAt", 2, prim_elemAt); - addPrimOp("__head", 1, prim_head); - addPrimOp("__tail", 1, prim_tail); - addPrimOp("map", 2, prim_map); - addPrimOp("__filter", 2, prim_filter); - addPrimOp("__elem", 2, prim_elem); - addPrimOp("__concatLists", 1, prim_concatLists); - addPrimOp("__length", 1, prim_length); - addPrimOp("__foldl'", 3, prim_foldlStrict); - addPrimOp("__any", 2, prim_any); - addPrimOp("__all", 2, prim_all); - addPrimOp("__genList", 2, prim_genList); - addPrimOp("__sort", 2, prim_sort); - addPrimOp("__partition", 2, prim_partition); - addPrimOp("__concatMap", 2, prim_concatMap); - - // Integer arithmetic - addPrimOp("__add", 2, prim_add); - addPrimOp("__sub", 2, prim_sub); - addPrimOp("__mul", 2, prim_mul); - addPrimOp("__div", 2, prim_div); - addPrimOp("__bitAnd", 2, prim_bitAnd); - addPrimOp("__bitOr", 2, prim_bitOr); - addPrimOp("__bitXor", 2, prim_bitXor); - addPrimOp("__lessThan", 2, prim_lessThan); - - // String manipulation - addPrimOp("toString", 1, prim_toString); - addPrimOp("__substring", 3, prim_substring); - addPrimOp("__stringLength", 1, prim_stringLength); - addPrimOp("__hashString", 2, prim_hashString); - addPrimOp("__match", 2, prim_match); - addPrimOp("__split", 2, prim_split); - addPrimOp("__concatStringsSep", 2, prim_concatStringSep); - addPrimOp("__replaceStrings", 3, prim_replaceStrings); - - // Versions - addPrimOp("__parseDrvName", 1, prim_parseDrvName); - addPrimOp("__compareVersions", 2, prim_compareVersions); - addPrimOp("__splitVersion", 1, prim_splitVersion); - - // Derivations - addPrimOp("derivationStrict", 1, prim_derivationStrict); - addPrimOp("placeholder", 1, prim_placeholder); - - // Networking - addPrimOp("__fetchurl", 1, prim_fetchurl); - addPrimOp("fetchTarball", 1, prim_fetchTarball); - - /* Add a wrapper around the derivation primop that computes the - `drvPath' and `outPath' attributes lazily. */ - string path = canonPath(settings.nixDataDir + "/nix/corepkgs/derivation.nix", true); - sDerivationNix = symbols.create(path); - evalFile(path, v); - addConstant("derivation", v); - - /* Add a value containing the current Nix expression search path. */ - mkList(v, searchPath.size()); - int n = 0; - for (auto & i : searchPath) { - v2 = v.listElems()[n++] = allocValue(); - mkAttrs(*v2, 2); - mkString(*allocAttr(*v2, symbols.create("path")), i.second); - mkString(*allocAttr(*v2, symbols.create("prefix")), i.first); - v2->attrs->sort(); - } - addConstant("__nixPath", v); - - if (RegisterPrimOp::primOps) - for (auto & primOp : *RegisterPrimOp::primOps) - addPrimOp(std::get<0>(primOp), std::get<1>(primOp), std::get<2>(primOp)); - - /* Now that we've added all primops, sort the `builtins' set, - because attribute lookups expect it to be sorted. */ - baseEnv.values[0]->attrs->sort(); -} - - -} +RegisterPrimOp::PrimOps* RegisterPrimOp::primOps; + +RegisterPrimOp::RegisterPrimOp(std::string name, size_t arity, PrimOpFun fun) { + if (!primOps) primOps = new PrimOps; + primOps->emplace_back(name, arity, fun); +} + +void EvalState::createBaseEnv() { + baseEnv.up = 0; + + /* Add global constants such as `true' to the base environment. */ + Value v; + + /* `builtins' must be first! */ + mkAttrs(v, 128); + addConstant("builtins", v); + + mkBool(v, true); + addConstant("true", v); + + mkBool(v, false); + addConstant("false", v); + + mkNull(v); + addConstant("null", v); + + auto vThrow = addPrimOp("throw", 1, prim_throw); + + auto addPurityError = [&](const std::string& name) { + Value* v2 = allocValue(); + mkString(*v2, fmt("'%s' is not allowed in pure evaluation mode", name)); + mkApp(v, *vThrow, *v2); + addConstant(name, v); + }; + + if (!evalSettings.pureEval) { + mkInt(v, time(0)); + addConstant("__currentTime", v); + } + + if (!evalSettings.pureEval) { + mkString(v, settings.thisSystem); + addConstant("__currentSystem", v); + } + + mkString(v, nixVersion); + addConstant("__nixVersion", v); + + mkString(v, store->storeDir); + addConstant("__storeDir", v); + + /* Language version. This should be increased every time a new + language feature gets added. It's not necessary to increase it + when primops get added, because you can just use `builtins ? + primOp' to check. */ + mkInt(v, 5); + addConstant("__langVersion", v); + + // Miscellaneous + auto vScopedImport = addPrimOp("scopedImport", 2, prim_scopedImport); + Value* v2 = allocValue(); + mkAttrs(*v2, 0); + mkApp(v, *vScopedImport, *v2); + forceValue(v); + addConstant("import", v); + if (evalSettings.enableNativeCode) { + addPrimOp("__importNative", 2, prim_importNative); + addPrimOp("__exec", 1, prim_exec); + } + addPrimOp("__typeOf", 1, prim_typeOf); + addPrimOp("isNull", 1, prim_isNull); + addPrimOp("__isFunction", 1, prim_isFunction); + addPrimOp("__isString", 1, prim_isString); + addPrimOp("__isInt", 1, prim_isInt); + addPrimOp("__isFloat", 1, prim_isFloat); + addPrimOp("__isBool", 1, prim_isBool); + addPrimOp("__isPath", 1, prim_isPath); + addPrimOp("__genericClosure", 1, prim_genericClosure); + addPrimOp("abort", 1, prim_abort); + addPrimOp("__addErrorContext", 2, prim_addErrorContext); + addPrimOp("__tryEval", 1, prim_tryEval); + addPrimOp("__getEnv", 1, prim_getEnv); + + // Strictness + addPrimOp("__seq", 2, prim_seq); + addPrimOp("__deepSeq", 2, prim_deepSeq); + + // Debugging + addPrimOp("__trace", 2, prim_trace); + addPrimOp("__valueSize", 1, prim_valueSize); + + // Paths + addPrimOp("__toPath", 1, prim_toPath); + if (evalSettings.pureEval) + addPurityError("__storePath"); + else + addPrimOp("__storePath", 1, prim_storePath); + addPrimOp("__pathExists", 1, prim_pathExists); + addPrimOp("baseNameOf", 1, prim_baseNameOf); + addPrimOp("dirOf", 1, prim_dirOf); + addPrimOp("__readFile", 1, prim_readFile); + addPrimOp("__readDir", 1, prim_readDir); + addPrimOp("__findFile", 2, prim_findFile); + addPrimOp("__hashFile", 2, prim_hashFile); + + // Creating files + addPrimOp("__toXML", 1, prim_toXML); + addPrimOp("__toJSON", 1, prim_toJSON); + addPrimOp("__fromJSON", 1, prim_fromJSON); + addPrimOp("__toFile", 2, prim_toFile); + addPrimOp("__filterSource", 2, prim_filterSource); + addPrimOp("__path", 1, prim_path); + + // Sets + addPrimOp("__attrNames", 1, prim_attrNames); + addPrimOp("__attrValues", 1, prim_attrValues); + addPrimOp("__getAttr", 2, prim_getAttr); + addPrimOp("__unsafeGetAttrPos", 2, prim_unsafeGetAttrPos); + addPrimOp("__hasAttr", 2, prim_hasAttr); + addPrimOp("__isAttrs", 1, prim_isAttrs); + addPrimOp("removeAttrs", 2, prim_removeAttrs); + addPrimOp("__listToAttrs", 1, prim_listToAttrs); + addPrimOp("__intersectAttrs", 2, prim_intersectAttrs); + addPrimOp("__catAttrs", 2, prim_catAttrs); + addPrimOp("__functionArgs", 1, prim_functionArgs); + addPrimOp("__mapAttrs", 2, prim_mapAttrs); + + // Lists + addPrimOp("__isList", 1, prim_isList); + addPrimOp("__elemAt", 2, prim_elemAt); + addPrimOp("__head", 1, prim_head); + addPrimOp("__tail", 1, prim_tail); + addPrimOp("map", 2, prim_map); + addPrimOp("__filter", 2, prim_filter); + addPrimOp("__elem", 2, prim_elem); + addPrimOp("__concatLists", 1, prim_concatLists); + addPrimOp("__length", 1, prim_length); + addPrimOp("__foldl'", 3, prim_foldlStrict); + addPrimOp("__any", 2, prim_any); + addPrimOp("__all", 2, prim_all); + addPrimOp("__genList", 2, prim_genList); + addPrimOp("__sort", 2, prim_sort); + addPrimOp("__partition", 2, prim_partition); + addPrimOp("__concatMap", 2, prim_concatMap); + + // Integer arithmetic + addPrimOp("__add", 2, prim_add); + addPrimOp("__sub", 2, prim_sub); + addPrimOp("__mul", 2, prim_mul); + addPrimOp("__div", 2, prim_div); + addPrimOp("__bitAnd", 2, prim_bitAnd); + addPrimOp("__bitOr", 2, prim_bitOr); + addPrimOp("__bitXor", 2, prim_bitXor); + addPrimOp("__lessThan", 2, prim_lessThan); + + // String manipulation + addPrimOp("toString", 1, prim_toString); + addPrimOp("__substring", 3, prim_substring); + addPrimOp("__stringLength", 1, prim_stringLength); + addPrimOp("__hashString", 2, prim_hashString); + addPrimOp("__match", 2, prim_match); + addPrimOp("__split", 2, prim_split); + addPrimOp("__concatStringsSep", 2, prim_concatStringSep); + addPrimOp("__replaceStrings", 3, prim_replaceStrings); + + // Versions + addPrimOp("__parseDrvName", 1, prim_parseDrvName); + addPrimOp("__compareVersions", 2, prim_compareVersions); + addPrimOp("__splitVersion", 1, prim_splitVersion); + + // Derivations + addPrimOp("derivationStrict", 1, prim_derivationStrict); + addPrimOp("placeholder", 1, prim_placeholder); + + // Networking + addPrimOp("__fetchurl", 1, prim_fetchurl); + addPrimOp("fetchTarball", 1, prim_fetchTarball); + + /* Add a wrapper around the derivation primop that computes the + `drvPath' and `outPath' attributes lazily. */ + string path = + canonPath(settings.nixDataDir + "/nix/corepkgs/derivation.nix", true); + sDerivationNix = symbols.create(path); + evalFile(path, v); + addConstant("derivation", v); + + /* Add a value containing the current Nix expression search path. */ + mkList(v, searchPath.size()); + int n = 0; + for (auto& i : searchPath) { + v2 = v.listElems()[n++] = allocValue(); + mkAttrs(*v2, 2); + mkString(*allocAttr(*v2, symbols.create("path")), i.second); + mkString(*allocAttr(*v2, symbols.create("prefix")), i.first); + v2->attrs->sort(); + } + addConstant("__nixPath", v); + + if (RegisterPrimOp::primOps) + for (auto& primOp : *RegisterPrimOp::primOps) + addPrimOp(std::get<0>(primOp), std::get<1>(primOp), std::get<2>(primOp)); + + /* Now that we've added all primops, sort the `builtins' set, + because attribute lookups expect it to be sorted. */ + baseEnv.values[0]->attrs->sort(); +} + +} // namespace nix diff --git a/third_party/nix/src/libexpr/primops.hh b/third_party/nix/src/libexpr/primops.hh index c790b30f6d0b..60326323b2e0 100644 --- a/third_party/nix/src/libexpr/primops.hh +++ b/third_party/nix/src/libexpr/primops.hh @@ -1,26 +1,25 @@ -#include "eval.hh" - #include <tuple> #include <vector> +#include "eval.hh" namespace nix { -struct RegisterPrimOp -{ - typedef std::vector<std::tuple<std::string, size_t, PrimOpFun>> PrimOps; - static PrimOps * primOps; - /* You can register a constant by passing an arity of 0. fun - will get called during EvalState initialization, so there - may be primops not yet added and builtins is not yet sorted. */ - RegisterPrimOp(std::string name, size_t arity, PrimOpFun fun); +struct RegisterPrimOp { + typedef std::vector<std::tuple<std::string, size_t, PrimOpFun>> PrimOps; + static PrimOps* primOps; + /* You can register a constant by passing an arity of 0. fun + will get called during EvalState initialization, so there + may be primops not yet added and builtins is not yet sorted. */ + RegisterPrimOp(std::string name, size_t arity, PrimOpFun fun); }; /* These primops are disabled without enableNativeCode, but plugins may wish to use them in limited contexts without globally enabling them. */ /* Load a ValueInitializer from a DSO and return whatever it initializes */ -void prim_importNative(EvalState & state, const Pos & pos, Value * * args, Value & v); +void prim_importNative(EvalState& state, const Pos& pos, Value** args, + Value& v); /* Execute a program and parse its output */ -void prim_exec(EvalState & state, const Pos & pos, Value * * args, Value & v); +void prim_exec(EvalState& state, const Pos& pos, Value** args, Value& v); -} +} // namespace nix diff --git a/third_party/nix/src/libexpr/primops/context.cc b/third_party/nix/src/libexpr/primops/context.cc index 2d79739ea047..13faeef8ce9c 100644 --- a/third_party/nix/src/libexpr/primops/context.cc +++ b/third_party/nix/src/libexpr/primops/context.cc @@ -1,49 +1,47 @@ -#include "primops.hh" -#include "eval-inline.hh" #include "derivations.hh" +#include "eval-inline.hh" +#include "primops.hh" namespace nix { -static void prim_unsafeDiscardStringContext(EvalState & state, const Pos & pos, Value * * args, Value & v) -{ - PathSet context; - string s = state.coerceToString(pos, *args[0], context); - mkString(v, s, PathSet()); +static void prim_unsafeDiscardStringContext(EvalState& state, const Pos& pos, + Value** args, Value& v) { + PathSet context; + string s = state.coerceToString(pos, *args[0], context); + mkString(v, s, PathSet()); } -static RegisterPrimOp r1("__unsafeDiscardStringContext", 1, prim_unsafeDiscardStringContext); - +static RegisterPrimOp r1("__unsafeDiscardStringContext", 1, + prim_unsafeDiscardStringContext); -static void prim_hasContext(EvalState & state, const Pos & pos, Value * * args, Value & v) -{ - PathSet context; - state.forceString(*args[0], context, pos); - mkBool(v, !context.empty()); +static void prim_hasContext(EvalState& state, const Pos& pos, Value** args, + Value& v) { + PathSet context; + state.forceString(*args[0], context, pos); + mkBool(v, !context.empty()); } static RegisterPrimOp r2("__hasContext", 1, prim_hasContext); - /* Sometimes we want to pass a derivation path (i.e. pkg.drvPath) to a builder without causing the derivation to be built (for instance, in the derivation that builds NARs in nix-push, when doing source-only deployment). This primop marks the string context so that builtins.derivation adds the path to drv.inputSrcs rather than drv.inputDrvs. */ -static void prim_unsafeDiscardOutputDependency(EvalState & state, const Pos & pos, Value * * args, Value & v) -{ - PathSet context; - string s = state.coerceToString(pos, *args[0], context); +static void prim_unsafeDiscardOutputDependency(EvalState& state, const Pos& pos, + Value** args, Value& v) { + PathSet context; + string s = state.coerceToString(pos, *args[0], context); - PathSet context2; - for (auto & p : context) - context2.insert(p.at(0) == '=' ? string(p, 1) : p); + PathSet context2; + for (auto& p : context) context2.insert(p.at(0) == '=' ? string(p, 1) : p); - mkString(v, s, context2); + mkString(v, s, context2); } -static RegisterPrimOp r3("__unsafeDiscardOutputDependency", 1, prim_unsafeDiscardOutputDependency); - +static RegisterPrimOp r3("__unsafeDiscardOutputDependency", 1, + prim_unsafeDiscardOutputDependency); /* Extract the context of a string as a structured Nix value. @@ -64,124 +62,131 @@ static RegisterPrimOp r3("__unsafeDiscardOutputDependency", 1, prim_unsafeDiscar Note that for a given path any combination of the above attributes may be present. */ -static void prim_getContext(EvalState & state, const Pos & pos, Value * * args, Value & v) -{ - struct ContextInfo { - bool path = false; - bool allOutputs = false; - Strings outputs; - }; - PathSet context; - state.forceString(*args[0], context, pos); - auto contextInfos = std::map<Path, ContextInfo>(); - for (const auto & p : context) { - Path drv; - string output; - const Path * path = &p; - if (p.at(0) == '=') { - drv = string(p, 1); - path = &drv; - } else if (p.at(0) == '!') { - std::pair<string, string> ctx = decodeContext(p); - drv = ctx.first; - output = ctx.second; - path = &drv; - } - auto isPath = drv.empty(); - auto isAllOutputs = (!drv.empty()) && output.empty(); - - auto iter = contextInfos.find(*path); - if (iter == contextInfos.end()) { - contextInfos.emplace(*path, ContextInfo{isPath, isAllOutputs, output.empty() ? Strings{} : Strings{std::move(output)}}); - } else { - if (isPath) - iter->second.path = true; - else if (isAllOutputs) - iter->second.allOutputs = true; - else - iter->second.outputs.emplace_back(std::move(output)); - } +static void prim_getContext(EvalState& state, const Pos& pos, Value** args, + Value& v) { + struct ContextInfo { + bool path = false; + bool allOutputs = false; + Strings outputs; + }; + PathSet context; + state.forceString(*args[0], context, pos); + auto contextInfos = std::map<Path, ContextInfo>(); + for (const auto& p : context) { + Path drv; + string output; + const Path* path = &p; + if (p.at(0) == '=') { + drv = string(p, 1); + path = &drv; + } else if (p.at(0) == '!') { + std::pair<string, string> ctx = decodeContext(p); + drv = ctx.first; + output = ctx.second; + path = &drv; } - - state.mkAttrs(v, contextInfos.size()); - - auto sPath = state.symbols.create("path"); - auto sAllOutputs = state.symbols.create("allOutputs"); - for (const auto & info : contextInfos) { - auto & infoVal = *state.allocAttr(v, state.symbols.create(info.first)); - state.mkAttrs(infoVal, 3); - if (info.second.path) - mkBool(*state.allocAttr(infoVal, sPath), true); - if (info.second.allOutputs) - mkBool(*state.allocAttr(infoVal, sAllOutputs), true); - if (!info.second.outputs.empty()) { - auto & outputsVal = *state.allocAttr(infoVal, state.sOutputs); - state.mkList(outputsVal, info.second.outputs.size()); - size_t i = 0; - for (const auto & output : info.second.outputs) { - mkString(*(outputsVal.listElems()[i++] = state.allocValue()), output); - } - } - infoVal.attrs->sort(); + auto isPath = drv.empty(); + auto isAllOutputs = (!drv.empty()) && output.empty(); + + auto iter = contextInfos.find(*path); + if (iter == contextInfos.end()) { + contextInfos.emplace( + *path, + ContextInfo{isPath, isAllOutputs, + output.empty() ? Strings{} : Strings{std::move(output)}}); + } else { + if (isPath) + iter->second.path = true; + else if (isAllOutputs) + iter->second.allOutputs = true; + else + iter->second.outputs.emplace_back(std::move(output)); + } + } + + state.mkAttrs(v, contextInfos.size()); + + auto sPath = state.symbols.create("path"); + auto sAllOutputs = state.symbols.create("allOutputs"); + for (const auto& info : contextInfos) { + auto& infoVal = *state.allocAttr(v, state.symbols.create(info.first)); + state.mkAttrs(infoVal, 3); + if (info.second.path) mkBool(*state.allocAttr(infoVal, sPath), true); + if (info.second.allOutputs) + mkBool(*state.allocAttr(infoVal, sAllOutputs), true); + if (!info.second.outputs.empty()) { + auto& outputsVal = *state.allocAttr(infoVal, state.sOutputs); + state.mkList(outputsVal, info.second.outputs.size()); + size_t i = 0; + for (const auto& output : info.second.outputs) { + mkString(*(outputsVal.listElems()[i++] = state.allocValue()), output); + } } - v.attrs->sort(); + infoVal.attrs->sort(); + } + v.attrs->sort(); } static RegisterPrimOp r4("__getContext", 1, prim_getContext); - /* Append the given context to a given string. See the commentary above unsafeGetContext for details of the context representation. */ -static void prim_appendContext(EvalState & state, const Pos & pos, Value * * args, Value & v) -{ - PathSet context; - auto orig = state.forceString(*args[0], context, pos); - - state.forceAttrs(*args[1], pos); - - auto sPath = state.symbols.create("path"); - auto sAllOutputs = state.symbols.create("allOutputs"); - for (auto & i : *args[1]->attrs) { - if (!state.store->isStorePath(i.name)) - throw EvalError("Context key '%s' is not a store path, at %s", i.name, i.pos); - if (!settings.readOnlyMode) - state.store->ensurePath(i.name); - state.forceAttrs(*i.value, *i.pos); - auto iter = i.value->attrs->find(sPath); - if (iter != i.value->attrs->end()) { - if (state.forceBool(*iter->value, *iter->pos)) - context.insert(i.name); - } +static void prim_appendContext(EvalState& state, const Pos& pos, Value** args, + Value& v) { + PathSet context; + auto orig = state.forceString(*args[0], context, pos); + + state.forceAttrs(*args[1], pos); + + auto sPath = state.symbols.create("path"); + auto sAllOutputs = state.symbols.create("allOutputs"); + for (auto& i : *args[1]->attrs) { + if (!state.store->isStorePath(i.name)) + throw EvalError("Context key '%s' is not a store path, at %s", i.name, + i.pos); + if (!settings.readOnlyMode) state.store->ensurePath(i.name); + state.forceAttrs(*i.value, *i.pos); + auto iter = i.value->attrs->find(sPath); + if (iter != i.value->attrs->end()) { + if (state.forceBool(*iter->value, *iter->pos)) context.insert(i.name); + } - iter = i.value->attrs->find(sAllOutputs); - if (iter != i.value->attrs->end()) { - if (state.forceBool(*iter->value, *iter->pos)) { - if (!isDerivation(i.name)) { - throw EvalError("Tried to add all-outputs context of %s, which is not a derivation, to a string, at %s", i.name, i.pos); - } - context.insert("=" + string(i.name)); - } + iter = i.value->attrs->find(sAllOutputs); + if (iter != i.value->attrs->end()) { + if (state.forceBool(*iter->value, *iter->pos)) { + if (!isDerivation(i.name)) { + throw EvalError( + "Tried to add all-outputs context of %s, which is not a " + "derivation, to a string, at %s", + i.name, i.pos); } + context.insert("=" + string(i.name)); + } + } - iter = i.value->attrs->find(state.sOutputs); - if (iter != i.value->attrs->end()) { - state.forceList(*iter->value, *iter->pos); - if (iter->value->listSize() && !isDerivation(i.name)) { - throw EvalError("Tried to add derivation output context of %s, which is not a derivation, to a string, at %s", i.name, i.pos); - } - for (unsigned int n = 0; n < iter->value->listSize(); ++n) { - auto name = state.forceStringNoCtx(*iter->value->listElems()[n], *iter->pos); - context.insert("!" + name + "!" + string(i.name)); - } - } + iter = i.value->attrs->find(state.sOutputs); + if (iter != i.value->attrs->end()) { + state.forceList(*iter->value, *iter->pos); + if (iter->value->listSize() && !isDerivation(i.name)) { + throw EvalError( + "Tried to add derivation output context of %s, which is not a " + "derivation, to a string, at %s", + i.name, i.pos); + } + for (unsigned int n = 0; n < iter->value->listSize(); ++n) { + auto name = + state.forceStringNoCtx(*iter->value->listElems()[n], *iter->pos); + context.insert("!" + name + "!" + string(i.name)); + } } + } - mkString(v, orig, context); + mkString(v, orig, context); } static RegisterPrimOp r5("__appendContext", 2, prim_appendContext); -} +} // namespace nix diff --git a/third_party/nix/src/libexpr/primops/fetchGit.cc b/third_party/nix/src/libexpr/primops/fetchGit.cc index 90f600284b22..e1edf9a3e58e 100644 --- a/third_party/nix/src/libexpr/primops/fetchGit.cc +++ b/third_party/nix/src/libexpr/primops/fetchGit.cc @@ -1,247 +1,253 @@ -#include "primops.hh" -#include "eval-inline.hh" -#include "download.hh" -#include "store-api.hh" -#include "pathlocks.hh" -#include "hash.hh" - #include <sys/time.h> - -#include <regex> - #include <nlohmann/json.hpp> +#include <regex> +#include "download.hh" +#include "eval-inline.hh" +#include "hash.hh" +#include "pathlocks.hh" +#include "primops.hh" +#include "store-api.hh" using namespace std::string_literals; namespace nix { -struct GitInfo -{ - Path storePath; - std::string rev; - std::string shortRev; - uint64_t revCount = 0; +struct GitInfo { + Path storePath; + std::string rev; + std::string shortRev; + uint64_t revCount = 0; }; std::regex revRegex("^[0-9a-fA-F]{40}$"); -GitInfo exportGit(ref<Store> store, const std::string & uri, - std::optional<std::string> ref, std::string rev, - const std::string & name) -{ - if (evalSettings.pureEval && rev == "") - throw Error("in pure evaluation mode, 'fetchGit' requires a Git revision"); - - if (!ref && rev == "" && hasPrefix(uri, "/") && pathExists(uri + "/.git")) { - - bool clean = true; - - try { - runProgram("git", true, { "-C", uri, "diff-index", "--quiet", "HEAD", "--" }); - } catch (ExecError & e) { - if (!WIFEXITED(e.status) || WEXITSTATUS(e.status) != 1) throw; - clean = false; - } - - if (!clean) { - - /* This is an unclean working tree. So copy all tracked - files. */ - - GitInfo gitInfo; - gitInfo.rev = "0000000000000000000000000000000000000000"; - gitInfo.shortRev = std::string(gitInfo.rev, 0, 7); - - auto files = tokenizeString<std::set<std::string>>( - runProgram("git", true, { "-C", uri, "ls-files", "-z" }), "\0"s); - - PathFilter filter = [&](const Path & p) -> bool { - assert(hasPrefix(p, uri)); - std::string file(p, uri.size() + 1); - - auto st = lstat(p); +GitInfo exportGit(ref<Store> store, const std::string& uri, + std::optional<std::string> ref, std::string rev, + const std::string& name) { + if (evalSettings.pureEval && rev == "") + throw Error("in pure evaluation mode, 'fetchGit' requires a Git revision"); - if (S_ISDIR(st.st_mode)) { - auto prefix = file + "/"; - auto i = files.lower_bound(prefix); - return i != files.end() && hasPrefix(*i, prefix); - } + if (!ref && rev == "" && hasPrefix(uri, "/") && pathExists(uri + "/.git")) { + bool clean = true; - return files.count(file); - }; - - gitInfo.storePath = store->addToStore("source", uri, true, htSHA256, filter); - - return gitInfo; - } - - // clean working tree, but no ref or rev specified. Use 'HEAD'. - rev = chomp(runProgram("git", true, { "-C", uri, "rev-parse", "HEAD" })); - ref = "HEAD"s; + try { + runProgram("git", true, + {"-C", uri, "diff-index", "--quiet", "HEAD", "--"}); + } catch (ExecError& e) { + if (!WIFEXITED(e.status) || WEXITSTATUS(e.status) != 1) throw; + clean = false; } - if (!ref) ref = "HEAD"s; + if (!clean) { + /* This is an unclean working tree. So copy all tracked + files. */ - if (rev != "" && !std::regex_match(rev, revRegex)) - throw Error("invalid Git revision '%s'", rev); + GitInfo gitInfo; + gitInfo.rev = "0000000000000000000000000000000000000000"; + gitInfo.shortRev = std::string(gitInfo.rev, 0, 7); - deletePath(getCacheDir() + "/nix/git"); + auto files = tokenizeString<std::set<std::string>>( + runProgram("git", true, {"-C", uri, "ls-files", "-z"}), "\0"s); - Path cacheDir = getCacheDir() + "/nix/gitv2/" + hashString(htSHA256, uri).to_string(Base32, false); + PathFilter filter = [&](const Path& p) -> bool { + assert(hasPrefix(p, uri)); + std::string file(p, uri.size() + 1); - if (!pathExists(cacheDir)) { - createDirs(dirOf(cacheDir)); - runProgram("git", true, { "init", "--bare", cacheDir }); - } + auto st = lstat(p); - Path localRefFile; - if (ref->compare(0, 5, "refs/") == 0) - localRefFile = cacheDir + "/" + *ref; - else - localRefFile = cacheDir + "/refs/heads/" + *ref; - - bool doFetch; - time_t now = time(0); - /* If a rev was specified, we need to fetch if it's not in the - repo. */ - if (rev != "") { - try { - runProgram("git", true, { "-C", cacheDir, "cat-file", "-e", rev }); - doFetch = false; - } catch (ExecError & e) { - if (WIFEXITED(e.status)) { - doFetch = true; - } else { - throw; - } + if (S_ISDIR(st.st_mode)) { + auto prefix = file + "/"; + auto i = files.lower_bound(prefix); + return i != files.end() && hasPrefix(*i, prefix); } - } else { - /* If the local ref is older than ‘tarball-ttl’ seconds, do a - git fetch to update the local ref to the remote ref. */ - struct stat st; - doFetch = stat(localRefFile.c_str(), &st) != 0 || - (uint64_t) st.st_mtime + settings.tarballTtl <= (uint64_t) now; - } - if (doFetch) - { - Activity act(*logger, lvlTalkative, actUnknown, fmt("fetching Git repository '%s'", uri)); - // FIXME: git stderr messes up our progress indicator, so - // we're using --quiet for now. Should process its stderr. - runProgram("git", true, { "-C", cacheDir, "fetch", "--quiet", "--force", "--", uri, fmt("%s:%s", *ref, *ref) }); + return files.count(file); + }; - struct timeval times[2]; - times[0].tv_sec = now; - times[0].tv_usec = 0; - times[1].tv_sec = now; - times[1].tv_usec = 0; + gitInfo.storePath = + store->addToStore("source", uri, true, htSHA256, filter); - utimes(localRefFile.c_str(), times); + return gitInfo; } - // FIXME: check whether rev is an ancestor of ref. - GitInfo gitInfo; - gitInfo.rev = rev != "" ? rev : chomp(readFile(localRefFile)); - gitInfo.shortRev = std::string(gitInfo.rev, 0, 7); + // clean working tree, but no ref or rev specified. Use 'HEAD'. + rev = chomp(runProgram("git", true, {"-C", uri, "rev-parse", "HEAD"})); + ref = "HEAD"s; + } - printTalkative("using revision %s of repo '%s'", gitInfo.rev, uri); + if (!ref) ref = "HEAD"s; - std::string storeLinkName = hashString(htSHA512, name + std::string("\0"s) + gitInfo.rev).to_string(Base32, false); - Path storeLink = cacheDir + "/" + storeLinkName + ".link"; - PathLocks storeLinkLock({storeLink}, fmt("waiting for lock on '%1%'...", storeLink)); // FIXME: broken + if (rev != "" && !std::regex_match(rev, revRegex)) + throw Error("invalid Git revision '%s'", rev); - try { - auto json = nlohmann::json::parse(readFile(storeLink)); + deletePath(getCacheDir() + "/nix/git"); - assert(json["name"] == name && json["rev"] == gitInfo.rev); + Path cacheDir = getCacheDir() + "/nix/gitv2/" + + hashString(htSHA256, uri).to_string(Base32, false); - gitInfo.storePath = json["storePath"]; + if (!pathExists(cacheDir)) { + createDirs(dirOf(cacheDir)); + runProgram("git", true, {"init", "--bare", cacheDir}); + } - if (store->isValidPath(gitInfo.storePath)) { - gitInfo.revCount = json["revCount"]; - return gitInfo; - } + Path localRefFile; + if (ref->compare(0, 5, "refs/") == 0) + localRefFile = cacheDir + "/" + *ref; + else + localRefFile = cacheDir + "/refs/heads/" + *ref; - } catch (SysError & e) { - if (e.errNo != ENOENT) throw; + bool doFetch; + time_t now = time(0); + /* If a rev was specified, we need to fetch if it's not in the + repo. */ + if (rev != "") { + try { + runProgram("git", true, {"-C", cacheDir, "cat-file", "-e", rev}); + doFetch = false; + } catch (ExecError& e) { + if (WIFEXITED(e.status)) { + doFetch = true; + } else { + throw; + } } + } else { + /* If the local ref is older than ‘tarball-ttl’ seconds, do a + git fetch to update the local ref to the remote ref. */ + struct stat st; + doFetch = stat(localRefFile.c_str(), &st) != 0 || + (uint64_t)st.st_mtime + settings.tarballTtl <= (uint64_t)now; + } + if (doFetch) { + Activity act(*logger, lvlTalkative, actUnknown, + fmt("fetching Git repository '%s'", uri)); + + // FIXME: git stderr messes up our progress indicator, so + // we're using --quiet for now. Should process its stderr. + runProgram("git", true, + {"-C", cacheDir, "fetch", "--quiet", "--force", "--", uri, + fmt("%s:%s", *ref, *ref)}); + + struct timeval times[2]; + times[0].tv_sec = now; + times[0].tv_usec = 0; + times[1].tv_sec = now; + times[1].tv_usec = 0; + + utimes(localRefFile.c_str(), times); + } + + // FIXME: check whether rev is an ancestor of ref. + GitInfo gitInfo; + gitInfo.rev = rev != "" ? rev : chomp(readFile(localRefFile)); + gitInfo.shortRev = std::string(gitInfo.rev, 0, 7); + + printTalkative("using revision %s of repo '%s'", gitInfo.rev, uri); + + std::string storeLinkName = + hashString(htSHA512, name + std::string("\0"s) + gitInfo.rev) + .to_string(Base32, false); + Path storeLink = cacheDir + "/" + storeLinkName + ".link"; + PathLocks storeLinkLock({storeLink}, fmt("waiting for lock on '%1%'...", + storeLink)); // FIXME: broken + + try { + auto json = nlohmann::json::parse(readFile(storeLink)); + + assert(json["name"] == name && json["rev"] == gitInfo.rev); + + gitInfo.storePath = json["storePath"]; + + if (store->isValidPath(gitInfo.storePath)) { + gitInfo.revCount = json["revCount"]; + return gitInfo; + } + + } catch (SysError& e) { + if (e.errNo != ENOENT) throw; + } - // FIXME: should pipe this, or find some better way to extract a - // revision. - auto tar = runProgram("git", true, { "-C", cacheDir, "archive", gitInfo.rev }); + // FIXME: should pipe this, or find some better way to extract a + // revision. + auto tar = runProgram("git", true, {"-C", cacheDir, "archive", gitInfo.rev}); - Path tmpDir = createTempDir(); - AutoDelete delTmpDir(tmpDir, true); + Path tmpDir = createTempDir(); + AutoDelete delTmpDir(tmpDir, true); - runProgram("tar", true, { "x", "-C", tmpDir }, tar); + runProgram("tar", true, {"x", "-C", tmpDir}, tar); - gitInfo.storePath = store->addToStore(name, tmpDir); + gitInfo.storePath = store->addToStore(name, tmpDir); - gitInfo.revCount = std::stoull(runProgram("git", true, { "-C", cacheDir, "rev-list", "--count", gitInfo.rev })); + gitInfo.revCount = std::stoull(runProgram( + "git", true, {"-C", cacheDir, "rev-list", "--count", gitInfo.rev})); - nlohmann::json json; - json["storePath"] = gitInfo.storePath; - json["uri"] = uri; - json["name"] = name; - json["rev"] = gitInfo.rev; - json["revCount"] = gitInfo.revCount; + nlohmann::json json; + json["storePath"] = gitInfo.storePath; + json["uri"] = uri; + json["name"] = name; + json["rev"] = gitInfo.rev; + json["revCount"] = gitInfo.revCount; - writeFile(storeLink, json.dump()); + writeFile(storeLink, json.dump()); - return gitInfo; + return gitInfo; } -static void prim_fetchGit(EvalState & state, const Pos & pos, Value * * args, Value & v) -{ - std::string url; - std::optional<std::string> ref; - std::string rev; - std::string name = "source"; - PathSet context; - - state.forceValue(*args[0]); - - if (args[0]->type == tAttrs) { - - state.forceAttrs(*args[0], pos); - - for (auto & attr : *args[0]->attrs) { - string n(attr.name); - if (n == "url") - url = state.coerceToString(*attr.pos, *attr.value, context, false, false); - else if (n == "ref") - ref = state.forceStringNoCtx(*attr.value, *attr.pos); - else if (n == "rev") - rev = state.forceStringNoCtx(*attr.value, *attr.pos); - else if (n == "name") - name = state.forceStringNoCtx(*attr.value, *attr.pos); - else - throw EvalError("unsupported argument '%s' to 'fetchGit', at %s", attr.name, *attr.pos); - } +static void prim_fetchGit(EvalState& state, const Pos& pos, Value** args, + Value& v) { + std::string url; + std::optional<std::string> ref; + std::string rev; + std::string name = "source"; + PathSet context; + + state.forceValue(*args[0]); + + if (args[0]->type == tAttrs) { + state.forceAttrs(*args[0], pos); + + for (auto& attr : *args[0]->attrs) { + string n(attr.name); + if (n == "url") + url = + state.coerceToString(*attr.pos, *attr.value, context, false, false); + else if (n == "ref") + ref = state.forceStringNoCtx(*attr.value, *attr.pos); + else if (n == "rev") + rev = state.forceStringNoCtx(*attr.value, *attr.pos); + else if (n == "name") + name = state.forceStringNoCtx(*attr.value, *attr.pos); + else + throw EvalError("unsupported argument '%s' to 'fetchGit', at %s", + attr.name, *attr.pos); + } - if (url.empty()) - throw EvalError(format("'url' argument required, at %1%") % pos); + if (url.empty()) + throw EvalError(format("'url' argument required, at %1%") % pos); - } else - url = state.coerceToString(pos, *args[0], context, false, false); + } else + url = state.coerceToString(pos, *args[0], context, false, false); - // FIXME: git externals probably can be used to bypass the URI - // whitelist. Ah well. - state.checkURI(url); + // FIXME: git externals probably can be used to bypass the URI + // whitelist. Ah well. + state.checkURI(url); - auto gitInfo = exportGit(state.store, url, ref, rev, name); + auto gitInfo = exportGit(state.store, url, ref, rev, name); - state.mkAttrs(v, 8); - mkString(*state.allocAttr(v, state.sOutPath), gitInfo.storePath, PathSet({gitInfo.storePath})); - mkString(*state.allocAttr(v, state.symbols.create("rev")), gitInfo.rev); - mkString(*state.allocAttr(v, state.symbols.create("shortRev")), gitInfo.shortRev); - mkInt(*state.allocAttr(v, state.symbols.create("revCount")), gitInfo.revCount); - v.attrs->sort(); + state.mkAttrs(v, 8); + mkString(*state.allocAttr(v, state.sOutPath), gitInfo.storePath, + PathSet({gitInfo.storePath})); + mkString(*state.allocAttr(v, state.symbols.create("rev")), gitInfo.rev); + mkString(*state.allocAttr(v, state.symbols.create("shortRev")), + gitInfo.shortRev); + mkInt(*state.allocAttr(v, state.symbols.create("revCount")), + gitInfo.revCount); + v.attrs->sort(); - if (state.allowedPaths) - state.allowedPaths->insert(state.store->toRealPath(gitInfo.storePath)); + if (state.allowedPaths) + state.allowedPaths->insert(state.store->toRealPath(gitInfo.storePath)); } static RegisterPrimOp r("fetchGit", 1, prim_fetchGit); -} +} // namespace nix diff --git a/third_party/nix/src/libexpr/primops/fetchMercurial.cc b/third_party/nix/src/libexpr/primops/fetchMercurial.cc index a907d0e1cd82..1ee12542ef32 100644 --- a/third_party/nix/src/libexpr/primops/fetchMercurial.cc +++ b/third_party/nix/src/libexpr/primops/fetchMercurial.cc @@ -1,219 +1,229 @@ -#include "primops.hh" -#include "eval-inline.hh" -#include "download.hh" -#include "store-api.hh" -#include "pathlocks.hh" - #include <sys/time.h> - -#include <regex> - #include <nlohmann/json.hpp> +#include <regex> +#include "download.hh" +#include "eval-inline.hh" +#include "pathlocks.hh" +#include "primops.hh" +#include "store-api.hh" using namespace std::string_literals; namespace nix { -struct HgInfo -{ - Path storePath; - std::string branch; - std::string rev; - uint64_t revCount = 0; +struct HgInfo { + Path storePath; + std::string branch; + std::string rev; + uint64_t revCount = 0; }; std::regex commitHashRegex("^[0-9a-fA-F]{40}$"); -HgInfo exportMercurial(ref<Store> store, const std::string & uri, - std::string rev, const std::string & name) -{ - if (evalSettings.pureEval && rev == "") - throw Error("in pure evaluation mode, 'fetchMercurial' requires a Mercurial revision"); - - if (rev == "" && hasPrefix(uri, "/") && pathExists(uri + "/.hg")) { - - bool clean = runProgram("hg", true, { "status", "-R", uri, "--modified", "--added", "--removed" }) == ""; +HgInfo exportMercurial(ref<Store> store, const std::string& uri, + std::string rev, const std::string& name) { + if (evalSettings.pureEval && rev == "") + throw Error( + "in pure evaluation mode, 'fetchMercurial' requires a Mercurial " + "revision"); - if (!clean) { + if (rev == "" && hasPrefix(uri, "/") && pathExists(uri + "/.hg")) { + bool clean = runProgram("hg", true, + {"status", "-R", uri, "--modified", "--added", + "--removed"}) == ""; - /* This is an unclean working tree. So copy all tracked - files. */ + if (!clean) { + /* This is an unclean working tree. So copy all tracked + files. */ - printTalkative("copying unclean Mercurial working tree '%s'", uri); + printTalkative("copying unclean Mercurial working tree '%s'", uri); - HgInfo hgInfo; - hgInfo.rev = "0000000000000000000000000000000000000000"; - hgInfo.branch = chomp(runProgram("hg", true, { "branch", "-R", uri })); + HgInfo hgInfo; + hgInfo.rev = "0000000000000000000000000000000000000000"; + hgInfo.branch = chomp(runProgram("hg", true, {"branch", "-R", uri})); - auto files = tokenizeString<std::set<std::string>>( - runProgram("hg", true, { "status", "-R", uri, "--clean", "--modified", "--added", "--no-status", "--print0" }), "\0"s); + auto files = tokenizeString<std::set<std::string>>( + runProgram("hg", true, + {"status", "-R", uri, "--clean", "--modified", "--added", + "--no-status", "--print0"}), + "\0"s); - PathFilter filter = [&](const Path & p) -> bool { - assert(hasPrefix(p, uri)); - std::string file(p, uri.size() + 1); + PathFilter filter = [&](const Path& p) -> bool { + assert(hasPrefix(p, uri)); + std::string file(p, uri.size() + 1); - auto st = lstat(p); + auto st = lstat(p); - if (S_ISDIR(st.st_mode)) { - auto prefix = file + "/"; - auto i = files.lower_bound(prefix); - return i != files.end() && hasPrefix(*i, prefix); - } + if (S_ISDIR(st.st_mode)) { + auto prefix = file + "/"; + auto i = files.lower_bound(prefix); + return i != files.end() && hasPrefix(*i, prefix); + } - return files.count(file); - }; + return files.count(file); + }; - hgInfo.storePath = store->addToStore("source", uri, true, htSHA256, filter); + hgInfo.storePath = + store->addToStore("source", uri, true, htSHA256, filter); - return hgInfo; - } + return hgInfo; } - - if (rev == "") rev = "default"; - - Path cacheDir = fmt("%s/nix/hg/%s", getCacheDir(), hashString(htSHA256, uri).to_string(Base32, false)); - - Path stampFile = fmt("%s/.hg/%s.stamp", cacheDir, hashString(htSHA512, rev).to_string(Base32, false)); - - /* If we haven't pulled this repo less than ‘tarball-ttl’ seconds, - do so now. */ - time_t now = time(0); - struct stat st; - if (stat(stampFile.c_str(), &st) != 0 || - (uint64_t) st.st_mtime + settings.tarballTtl <= (uint64_t) now) - { - /* Except that if this is a commit hash that we already have, - we don't have to pull again. */ - if (!(std::regex_match(rev, commitHashRegex) - && pathExists(cacheDir) - && runProgram( - RunOptions("hg", { "log", "-R", cacheDir, "-r", rev, "--template", "1" }) - .killStderr(true)).second == "1")) - { - Activity act(*logger, lvlTalkative, actUnknown, fmt("fetching Mercurial repository '%s'", uri)); - - if (pathExists(cacheDir)) { - try { - runProgram("hg", true, { "pull", "-R", cacheDir, "--", uri }); - } - catch (ExecError & e) { - string transJournal = cacheDir + "/.hg/store/journal"; - /* hg throws "abandoned transaction" error only if this file exists */ - if (pathExists(transJournal)) { - runProgram("hg", true, { "recover", "-R", cacheDir }); - runProgram("hg", true, { "pull", "-R", cacheDir, "--", uri }); - } else { - throw ExecError(e.status, fmt("'hg pull' %s", statusToString(e.status))); - } - } - } else { - createDirs(dirOf(cacheDir)); - runProgram("hg", true, { "clone", "--noupdate", "--", uri, cacheDir }); - } + } + + if (rev == "") rev = "default"; + + Path cacheDir = fmt("%s/nix/hg/%s", getCacheDir(), + hashString(htSHA256, uri).to_string(Base32, false)); + + Path stampFile = fmt("%s/.hg/%s.stamp", cacheDir, + hashString(htSHA512, rev).to_string(Base32, false)); + + /* If we haven't pulled this repo less than ‘tarball-ttl’ seconds, + do so now. */ + time_t now = time(0); + struct stat st; + if (stat(stampFile.c_str(), &st) != 0 || + (uint64_t)st.st_mtime + settings.tarballTtl <= (uint64_t)now) { + /* Except that if this is a commit hash that we already have, + we don't have to pull again. */ + if (!(std::regex_match(rev, commitHashRegex) && pathExists(cacheDir) && + runProgram(RunOptions("hg", {"log", "-R", cacheDir, "-r", rev, + "--template", "1"}) + .killStderr(true)) + .second == "1")) { + Activity act(*logger, lvlTalkative, actUnknown, + fmt("fetching Mercurial repository '%s'", uri)); + + if (pathExists(cacheDir)) { + try { + runProgram("hg", true, {"pull", "-R", cacheDir, "--", uri}); + } catch (ExecError& e) { + string transJournal = cacheDir + "/.hg/store/journal"; + /* hg throws "abandoned transaction" error only if this file exists */ + if (pathExists(transJournal)) { + runProgram("hg", true, {"recover", "-R", cacheDir}); + runProgram("hg", true, {"pull", "-R", cacheDir, "--", uri}); + } else { + throw ExecError(e.status, + fmt("'hg pull' %s", statusToString(e.status))); + } } - - writeFile(stampFile, ""); + } else { + createDirs(dirOf(cacheDir)); + runProgram("hg", true, {"clone", "--noupdate", "--", uri, cacheDir}); + } } - auto tokens = tokenizeString<std::vector<std::string>>( - runProgram("hg", true, { "log", "-R", cacheDir, "-r", rev, "--template", "{node} {rev} {branch}" })); - assert(tokens.size() == 3); + writeFile(stampFile, ""); + } - HgInfo hgInfo; - hgInfo.rev = tokens[0]; - hgInfo.revCount = std::stoull(tokens[1]); - hgInfo.branch = tokens[2]; + auto tokens = tokenizeString<std::vector<std::string>>( + runProgram("hg", true, + {"log", "-R", cacheDir, "-r", rev, "--template", + "{node} {rev} {branch}"})); + assert(tokens.size() == 3); - std::string storeLinkName = hashString(htSHA512, name + std::string("\0"s) + hgInfo.rev).to_string(Base32, false); - Path storeLink = fmt("%s/.hg/%s.link", cacheDir, storeLinkName); + HgInfo hgInfo; + hgInfo.rev = tokens[0]; + hgInfo.revCount = std::stoull(tokens[1]); + hgInfo.branch = tokens[2]; - try { - auto json = nlohmann::json::parse(readFile(storeLink)); + std::string storeLinkName = + hashString(htSHA512, name + std::string("\0"s) + hgInfo.rev) + .to_string(Base32, false); + Path storeLink = fmt("%s/.hg/%s.link", cacheDir, storeLinkName); - assert(json["name"] == name && json["rev"] == hgInfo.rev); + try { + auto json = nlohmann::json::parse(readFile(storeLink)); - hgInfo.storePath = json["storePath"]; + assert(json["name"] == name && json["rev"] == hgInfo.rev); - if (store->isValidPath(hgInfo.storePath)) { - printTalkative("using cached Mercurial store path '%s'", hgInfo.storePath); - return hgInfo; - } + hgInfo.storePath = json["storePath"]; - } catch (SysError & e) { - if (e.errNo != ENOENT) throw; + if (store->isValidPath(hgInfo.storePath)) { + printTalkative("using cached Mercurial store path '%s'", + hgInfo.storePath); + return hgInfo; } - Path tmpDir = createTempDir(); - AutoDelete delTmpDir(tmpDir, true); + } catch (SysError& e) { + if (e.errNo != ENOENT) throw; + } + + Path tmpDir = createTempDir(); + AutoDelete delTmpDir(tmpDir, true); - runProgram("hg", true, { "archive", "-R", cacheDir, "-r", rev, tmpDir }); + runProgram("hg", true, {"archive", "-R", cacheDir, "-r", rev, tmpDir}); - deletePath(tmpDir + "/.hg_archival.txt"); + deletePath(tmpDir + "/.hg_archival.txt"); - hgInfo.storePath = store->addToStore(name, tmpDir); + hgInfo.storePath = store->addToStore(name, tmpDir); - nlohmann::json json; - json["storePath"] = hgInfo.storePath; - json["uri"] = uri; - json["name"] = name; - json["branch"] = hgInfo.branch; - json["rev"] = hgInfo.rev; - json["revCount"] = hgInfo.revCount; + nlohmann::json json; + json["storePath"] = hgInfo.storePath; + json["uri"] = uri; + json["name"] = name; + json["branch"] = hgInfo.branch; + json["rev"] = hgInfo.rev; + json["revCount"] = hgInfo.revCount; - writeFile(storeLink, json.dump()); + writeFile(storeLink, json.dump()); - return hgInfo; + return hgInfo; } -static void prim_fetchMercurial(EvalState & state, const Pos & pos, Value * * args, Value & v) -{ - std::string url; - std::string rev; - std::string name = "source"; - PathSet context; - - state.forceValue(*args[0]); - - if (args[0]->type == tAttrs) { - - state.forceAttrs(*args[0], pos); - - for (auto & attr : *args[0]->attrs) { - string n(attr.name); - if (n == "url") - url = state.coerceToString(*attr.pos, *attr.value, context, false, false); - else if (n == "rev") - rev = state.forceStringNoCtx(*attr.value, *attr.pos); - else if (n == "name") - name = state.forceStringNoCtx(*attr.value, *attr.pos); - else - throw EvalError("unsupported argument '%s' to 'fetchMercurial', at %s", attr.name, *attr.pos); - } +static void prim_fetchMercurial(EvalState& state, const Pos& pos, Value** args, + Value& v) { + std::string url; + std::string rev; + std::string name = "source"; + PathSet context; + + state.forceValue(*args[0]); + + if (args[0]->type == tAttrs) { + state.forceAttrs(*args[0], pos); + + for (auto& attr : *args[0]->attrs) { + string n(attr.name); + if (n == "url") + url = + state.coerceToString(*attr.pos, *attr.value, context, false, false); + else if (n == "rev") + rev = state.forceStringNoCtx(*attr.value, *attr.pos); + else if (n == "name") + name = state.forceStringNoCtx(*attr.value, *attr.pos); + else + throw EvalError("unsupported argument '%s' to 'fetchMercurial', at %s", + attr.name, *attr.pos); + } - if (url.empty()) - throw EvalError(format("'url' argument required, at %1%") % pos); + if (url.empty()) + throw EvalError(format("'url' argument required, at %1%") % pos); - } else - url = state.coerceToString(pos, *args[0], context, false, false); + } else + url = state.coerceToString(pos, *args[0], context, false, false); - // FIXME: git externals probably can be used to bypass the URI - // whitelist. Ah well. - state.checkURI(url); + // FIXME: git externals probably can be used to bypass the URI + // whitelist. Ah well. + state.checkURI(url); - auto hgInfo = exportMercurial(state.store, url, rev, name); + auto hgInfo = exportMercurial(state.store, url, rev, name); - state.mkAttrs(v, 8); - mkString(*state.allocAttr(v, state.sOutPath), hgInfo.storePath, PathSet({hgInfo.storePath})); - mkString(*state.allocAttr(v, state.symbols.create("branch")), hgInfo.branch); - mkString(*state.allocAttr(v, state.symbols.create("rev")), hgInfo.rev); - mkString(*state.allocAttr(v, state.symbols.create("shortRev")), std::string(hgInfo.rev, 0, 12)); - mkInt(*state.allocAttr(v, state.symbols.create("revCount")), hgInfo.revCount); - v.attrs->sort(); + state.mkAttrs(v, 8); + mkString(*state.allocAttr(v, state.sOutPath), hgInfo.storePath, + PathSet({hgInfo.storePath})); + mkString(*state.allocAttr(v, state.symbols.create("branch")), hgInfo.branch); + mkString(*state.allocAttr(v, state.symbols.create("rev")), hgInfo.rev); + mkString(*state.allocAttr(v, state.symbols.create("shortRev")), + std::string(hgInfo.rev, 0, 12)); + mkInt(*state.allocAttr(v, state.symbols.create("revCount")), hgInfo.revCount); + v.attrs->sort(); - if (state.allowedPaths) - state.allowedPaths->insert(state.store->toRealPath(hgInfo.storePath)); + if (state.allowedPaths) + state.allowedPaths->insert(state.store->toRealPath(hgInfo.storePath)); } static RegisterPrimOp r("fetchMercurial", 1, prim_fetchMercurial); -} +} // namespace nix diff --git a/third_party/nix/src/libexpr/primops/fromTOML.cc b/third_party/nix/src/libexpr/primops/fromTOML.cc index a84e569e944d..4b652b379af7 100644 --- a/third_party/nix/src/libexpr/primops/fromTOML.cc +++ b/third_party/nix/src/libexpr/primops/fromTOML.cc @@ -1,90 +1,90 @@ -#include "primops.hh" -#include "eval-inline.hh" - #include "cpptoml/cpptoml.h" +#include "eval-inline.hh" +#include "primops.hh" namespace nix { -static void prim_fromTOML(EvalState & state, const Pos & pos, Value * * args, Value & v) -{ - using namespace cpptoml; - - auto toml = state.forceStringNoCtx(*args[0], pos); - - std::istringstream tomlStream(toml); +static void prim_fromTOML(EvalState& state, const Pos& pos, Value** args, + Value& v) { + using namespace cpptoml; - std::function<void(Value &, std::shared_ptr<base>)> visit; + auto toml = state.forceStringNoCtx(*args[0], pos); - visit = [&](Value & v, std::shared_ptr<base> t) { + std::istringstream tomlStream(toml); - if (auto t2 = t->as_table()) { + std::function<void(Value&, std::shared_ptr<base>)> visit; - size_t size = 0; - for (auto & i : *t2) { (void) i; size++; } + visit = [&](Value& v, std::shared_ptr<base> t) { + if (auto t2 = t->as_table()) { + size_t size = 0; + for (auto& i : *t2) { + (void)i; + size++; + } - state.mkAttrs(v, size); + state.mkAttrs(v, size); - for (auto & i : *t2) { - auto & v2 = *state.allocAttr(v, state.symbols.create(i.first)); + for (auto& i : *t2) { + auto& v2 = *state.allocAttr(v, state.symbols.create(i.first)); - if (auto i2 = i.second->as_table_array()) { - size_t size2 = i2->get().size(); - state.mkList(v2, size2); - for (size_t j = 0; j < size2; ++j) - visit(*(v2.listElems()[j] = state.allocValue()), i2->get()[j]); - } - else - visit(v2, i.second); - } + if (auto i2 = i.second->as_table_array()) { + size_t size2 = i2->get().size(); + state.mkList(v2, size2); + for (size_t j = 0; j < size2; ++j) + visit(*(v2.listElems()[j] = state.allocValue()), i2->get()[j]); + } else + visit(v2, i.second); + } - v.attrs->sort(); - } + v.attrs->sort(); + } - else if (auto t2 = t->as_array()) { - size_t size = t2->get().size(); + else if (auto t2 = t->as_array()) { + size_t size = t2->get().size(); - state.mkList(v, size); + state.mkList(v, size); - for (size_t i = 0; i < size; ++i) - visit(*(v.listElems()[i] = state.allocValue()), t2->get()[i]); - } + for (size_t i = 0; i < size; ++i) + visit(*(v.listElems()[i] = state.allocValue()), t2->get()[i]); + } - // Handle cases like 'a = [[{ a = true }]]', which IMHO should be - // parsed as a array containing an array containing a table, - // but instead are parsed as an array containing a table array - // containing a table. - else if (auto t2 = t->as_table_array()) { - size_t size = t2->get().size(); + // Handle cases like 'a = [[{ a = true }]]', which IMHO should be + // parsed as a array containing an array containing a table, + // but instead are parsed as an array containing a table array + // containing a table. + else if (auto t2 = t->as_table_array()) { + size_t size = t2->get().size(); - state.mkList(v, size); + state.mkList(v, size); - for (size_t j = 0; j < size; ++j) - visit(*(v.listElems()[j] = state.allocValue()), t2->get()[j]); - } + for (size_t j = 0; j < size; ++j) + visit(*(v.listElems()[j] = state.allocValue()), t2->get()[j]); + } - else if (t->is_value()) { - if (auto val = t->as<int64_t>()) - mkInt(v, val->get()); - else if (auto val = t->as<NixFloat>()) - mkFloat(v, val->get()); - else if (auto val = t->as<bool>()) - mkBool(v, val->get()); - else if (auto val = t->as<std::string>()) - mkString(v, val->get()); - else - throw EvalError("unsupported value type in TOML"); - } + else if (t->is_value()) { + if (auto val = t->as<int64_t>()) + mkInt(v, val->get()); + else if (auto val = t->as<NixFloat>()) + mkFloat(v, val->get()); + else if (auto val = t->as<bool>()) + mkBool(v, val->get()); + else if (auto val = t->as<std::string>()) + mkString(v, val->get()); + else + throw EvalError("unsupported value type in TOML"); + } - else abort(); - }; + else + abort(); + }; - try { - visit(v, parser(tomlStream).parse()); - } catch (std::runtime_error & e) { - throw EvalError("while parsing a TOML string at %s: %s", pos, e.what()); - } + try { + visit(v, parser(tomlStream).parse()); + } catch (std::runtime_error& e) { + throw EvalError("while parsing a TOML string at %s: %s", pos, e.what()); + } } static RegisterPrimOp r("fromTOML", 1, prim_fromTOML); -} +} // namespace nix diff --git a/third_party/nix/src/libexpr/symbol-table.hh b/third_party/nix/src/libexpr/symbol-table.hh index 91faea122ce1..932bff5b46fb 100644 --- a/third_party/nix/src/libexpr/symbol-table.hh +++ b/third_party/nix/src/libexpr/symbol-table.hh @@ -2,7 +2,6 @@ #include <map> #include <unordered_set> - #include "types.hh" namespace nix { @@ -13,75 +12,49 @@ namespace nix { they can be compared efficiently (using a pointer equality test), because the symbol table stores only one copy of each string. */ -class Symbol -{ -private: - const string * s; // pointer into SymbolTable - Symbol(const string * s) : s(s) { }; - friend class SymbolTable; - -public: - Symbol() : s(0) { }; - - bool operator == (const Symbol & s2) const - { - return s == s2.s; - } - - bool operator != (const Symbol & s2) const - { - return s != s2.s; - } - - bool operator < (const Symbol & s2) const - { - return s < s2.s; - } - - operator const string & () const - { - return *s; - } - - bool set() const - { - return s; - } - - bool empty() const - { - return s->empty(); - } - - friend std::ostream & operator << (std::ostream & str, const Symbol & sym); +class Symbol { + private: + const string* s; // pointer into SymbolTable + Symbol(const string* s) : s(s){}; + friend class SymbolTable; + + public: + Symbol() : s(0){}; + + bool operator==(const Symbol& s2) const { return s == s2.s; } + + bool operator!=(const Symbol& s2) const { return s != s2.s; } + + bool operator<(const Symbol& s2) const { return s < s2.s; } + + operator const string&() const { return *s; } + + bool set() const { return s; } + + bool empty() const { return s->empty(); } + + friend std::ostream& operator<<(std::ostream& str, const Symbol& sym); }; -class SymbolTable -{ -private: - typedef std::unordered_set<string> Symbols; - Symbols symbols; - -public: - Symbol create(const string & s) - { - std::pair<Symbols::iterator, bool> res = symbols.insert(s); - return Symbol(&*res.first); - } - - size_t size() const - { - return symbols.size(); - } - - size_t totalSize() const; - - template<typename T> - void dump(T callback) - { - for (auto & s : symbols) - callback(s); - } +class SymbolTable { + private: + typedef std::unordered_set<string> Symbols; + Symbols symbols; + + public: + Symbol create(const string& s) { + std::pair<Symbols::iterator, bool> res = symbols.insert(s); + return Symbol(&*res.first); + } + + size_t size() const { return symbols.size(); } + + size_t totalSize() const; + + template <typename T> + void dump(T callback) { + for (auto& s : symbols) callback(s); + } }; -} +} // namespace nix diff --git a/third_party/nix/src/libexpr/value-to-json.cc b/third_party/nix/src/libexpr/value-to-json.cc index 5fe8570adeb4..d96575e734dc 100644 --- a/third_party/nix/src/libexpr/value-to-json.cc +++ b/third_party/nix/src/libexpr/value-to-json.cc @@ -1,100 +1,97 @@ #include "value-to-json.hh" -#include "json.hh" -#include "eval-inline.hh" -#include "util.hh" - #include <cstdlib> #include <iomanip> - +#include "eval-inline.hh" +#include "json.hh" +#include "util.hh" namespace nix { -void printValueAsJSON(EvalState & state, bool strict, - Value & v, JSONPlaceholder & out, PathSet & context) -{ - checkInterrupt(); - - if (strict) state.forceValue(v); - - switch (v.type) { - - case tInt: - out.write(v.integer); - break; - - case tBool: - out.write(v.boolean); - break; - - case tString: - copyContext(v, context); - out.write(v.string.s); - break; - - case tPath: - out.write(state.copyPathToStore(context, v.path)); - break; - - case tNull: - out.write(nullptr); - break; - - case tAttrs: { - auto maybeString = state.tryAttrsToString(noPos, v, context, false, false); - if (maybeString) { - out.write(*maybeString); - break; - } - auto i = v.attrs->find(state.sOutPath); - if (i == v.attrs->end()) { - auto obj(out.object()); - StringSet names; - for (auto & j : *v.attrs) - names.insert(j.name); - for (auto & j : names) { - Attr & a(*v.attrs->find(state.symbols.create(j))); - auto placeholder(obj.placeholder(j)); - printValueAsJSON(state, strict, *a.value, placeholder, context); - } - } else - printValueAsJSON(state, strict, *i->value, out, context); - break; +void printValueAsJSON(EvalState& state, bool strict, Value& v, + JSONPlaceholder& out, PathSet& context) { + checkInterrupt(); + + if (strict) state.forceValue(v); + + switch (v.type) { + case tInt: + out.write(v.integer); + break; + + case tBool: + out.write(v.boolean); + break; + + case tString: + copyContext(v, context); + out.write(v.string.s); + break; + + case tPath: + out.write(state.copyPathToStore(context, v.path)); + break; + + case tNull: + out.write(nullptr); + break; + + case tAttrs: { + auto maybeString = + state.tryAttrsToString(noPos, v, context, false, false); + if (maybeString) { + out.write(*maybeString); + break; + } + auto i = v.attrs->find(state.sOutPath); + if (i == v.attrs->end()) { + auto obj(out.object()); + StringSet names; + for (auto& j : *v.attrs) names.insert(j.name); + for (auto& j : names) { + Attr& a(*v.attrs->find(state.symbols.create(j))); + auto placeholder(obj.placeholder(j)); + printValueAsJSON(state, strict, *a.value, placeholder, context); } + } else + printValueAsJSON(state, strict, *i->value, out, context); + break; + } - case tList1: case tList2: case tListN: { - auto list(out.list()); - for (unsigned int n = 0; n < v.listSize(); ++n) { - auto placeholder(list.placeholder()); - printValueAsJSON(state, strict, *v.listElems()[n], placeholder, context); - } - break; - } + case tList1: + case tList2: + case tListN: { + auto list(out.list()); + for (unsigned int n = 0; n < v.listSize(); ++n) { + auto placeholder(list.placeholder()); + printValueAsJSON(state, strict, *v.listElems()[n], placeholder, + context); + } + break; + } - case tExternal: - v.external->printValueAsJSON(state, strict, out, context); - break; + case tExternal: + v.external->printValueAsJSON(state, strict, out, context); + break; - case tFloat: - out.write(v.fpoint); - break; + case tFloat: + out.write(v.fpoint); + break; - default: - throw TypeError(format("cannot convert %1% to JSON") % showType(v)); - } + default: + throw TypeError(format("cannot convert %1% to JSON") % showType(v)); + } } -void printValueAsJSON(EvalState & state, bool strict, - Value & v, std::ostream & str, PathSet & context) -{ - JSONPlaceholder out(str); - printValueAsJSON(state, strict, v, out, context); +void printValueAsJSON(EvalState& state, bool strict, Value& v, + std::ostream& str, PathSet& context) { + JSONPlaceholder out(str); + printValueAsJSON(state, strict, v, out, context); } -void ExternalValueBase::printValueAsJSON(EvalState & state, bool strict, - JSONPlaceholder & out, PathSet & context) const -{ - throw TypeError(format("cannot convert %1% to JSON") % showType()); +void ExternalValueBase::printValueAsJSON(EvalState& state, bool strict, + JSONPlaceholder& out, + PathSet& context) const { + throw TypeError(format("cannot convert %1% to JSON") % showType()); } - -} +} // namespace nix diff --git a/third_party/nix/src/libexpr/value-to-json.hh b/third_party/nix/src/libexpr/value-to-json.hh index 67fed6487dd9..62708e4751dd 100644 --- a/third_party/nix/src/libexpr/value-to-json.hh +++ b/third_party/nix/src/libexpr/value-to-json.hh @@ -1,19 +1,18 @@ #pragma once -#include "nixexpr.hh" -#include "eval.hh" - -#include <string> #include <map> +#include <string> +#include "eval.hh" +#include "nixexpr.hh" namespace nix { class JSONPlaceholder; -void printValueAsJSON(EvalState & state, bool strict, - Value & v, JSONPlaceholder & out, PathSet & context); +void printValueAsJSON(EvalState& state, bool strict, Value& v, + JSONPlaceholder& out, PathSet& context); -void printValueAsJSON(EvalState & state, bool strict, - Value & v, std::ostream & str, PathSet & context); +void printValueAsJSON(EvalState& state, bool strict, Value& v, + std::ostream& str, PathSet& context); -} +} // namespace nix diff --git a/third_party/nix/src/libexpr/value-to-xml.cc b/third_party/nix/src/libexpr/value-to-xml.cc index 00b1918a82aa..d9fddaafbbec 100644 --- a/third_party/nix/src/libexpr/value-to-xml.cc +++ b/third_party/nix/src/libexpr/value-to-xml.cc @@ -1,178 +1,173 @@ #include "value-to-xml.hh" -#include "xml-writer.hh" +#include <cstdlib> #include "eval-inline.hh" #include "util.hh" - -#include <cstdlib> - +#include "xml-writer.hh" namespace nix { - -static XMLAttrs singletonAttrs(const string & name, const string & value) -{ - XMLAttrs attrs; - attrs[name] = value; - return attrs; +static XMLAttrs singletonAttrs(const string& name, const string& value) { + XMLAttrs attrs; + attrs[name] = value; + return attrs; } +static void printValueAsXML(EvalState& state, bool strict, bool location, + Value& v, XMLWriter& doc, PathSet& context, + PathSet& drvsSeen); -static void printValueAsXML(EvalState & state, bool strict, bool location, - Value & v, XMLWriter & doc, PathSet & context, PathSet & drvsSeen); - - -static void posToXML(XMLAttrs & xmlAttrs, const Pos & pos) -{ - xmlAttrs["path"] = pos.file; - xmlAttrs["line"] = (format("%1%") % pos.line).str(); - xmlAttrs["column"] = (format("%1%") % pos.column).str(); +static void posToXML(XMLAttrs& xmlAttrs, const Pos& pos) { + xmlAttrs["path"] = pos.file; + xmlAttrs["line"] = (format("%1%") % pos.line).str(); + xmlAttrs["column"] = (format("%1%") % pos.column).str(); } +static void showAttrs(EvalState& state, bool strict, bool location, + Bindings& attrs, XMLWriter& doc, PathSet& context, + PathSet& drvsSeen) { + StringSet names; -static void showAttrs(EvalState & state, bool strict, bool location, - Bindings & attrs, XMLWriter & doc, PathSet & context, PathSet & drvsSeen) -{ - StringSet names; + for (auto& i : attrs) names.insert(i.name); - for (auto & i : attrs) - names.insert(i.name); + for (auto& i : names) { + Attr& a(*attrs.find(state.symbols.create(i))); - for (auto & i : names) { - Attr & a(*attrs.find(state.symbols.create(i))); - - XMLAttrs xmlAttrs; - xmlAttrs["name"] = i; - if (location && a.pos != &noPos) posToXML(xmlAttrs, *a.pos); + XMLAttrs xmlAttrs; + xmlAttrs["name"] = i; + if (location && a.pos != &noPos) posToXML(xmlAttrs, *a.pos); - XMLOpenElement _(doc, "attr", xmlAttrs); - printValueAsXML(state, strict, location, - *a.value, doc, context, drvsSeen); - } + XMLOpenElement _(doc, "attr", xmlAttrs); + printValueAsXML(state, strict, location, *a.value, doc, context, drvsSeen); + } } +static void printValueAsXML(EvalState& state, bool strict, bool location, + Value& v, XMLWriter& doc, PathSet& context, + PathSet& drvsSeen) { + checkInterrupt(); -static void printValueAsXML(EvalState & state, bool strict, bool location, - Value & v, XMLWriter & doc, PathSet & context, PathSet & drvsSeen) -{ - checkInterrupt(); - - if (strict) state.forceValue(v); - - switch (v.type) { + if (strict) state.forceValue(v); - case tInt: - doc.writeEmptyElement("int", singletonAttrs("value", (format("%1%") % v.integer).str())); - break; + switch (v.type) { + case tInt: + doc.writeEmptyElement( + "int", singletonAttrs("value", (format("%1%") % v.integer).str())); + break; - case tBool: - doc.writeEmptyElement("bool", singletonAttrs("value", v.boolean ? "true" : "false")); - break; + case tBool: + doc.writeEmptyElement( + "bool", singletonAttrs("value", v.boolean ? "true" : "false")); + break; - case tString: - /* !!! show the context? */ - copyContext(v, context); - doc.writeEmptyElement("string", singletonAttrs("value", v.string.s)); - break; + case tString: + /* !!! show the context? */ + copyContext(v, context); + doc.writeEmptyElement("string", singletonAttrs("value", v.string.s)); + break; - case tPath: - doc.writeEmptyElement("path", singletonAttrs("value", v.path)); - break; + case tPath: + doc.writeEmptyElement("path", singletonAttrs("value", v.path)); + break; - case tNull: - doc.writeEmptyElement("null"); - break; + case tNull: + doc.writeEmptyElement("null"); + break; - case tAttrs: - if (state.isDerivation(v)) { - XMLAttrs xmlAttrs; - - Bindings::iterator a = v.attrs->find(state.symbols.create("derivation")); - - Path drvPath; - a = v.attrs->find(state.sDrvPath); - if (a != v.attrs->end()) { - if (strict) state.forceValue(*a->value); - if (a->value->type == tString) - xmlAttrs["drvPath"] = drvPath = a->value->string.s; - } - - a = v.attrs->find(state.sOutPath); - if (a != v.attrs->end()) { - if (strict) state.forceValue(*a->value); - if (a->value->type == tString) - xmlAttrs["outPath"] = a->value->string.s; - } - - XMLOpenElement _(doc, "derivation", xmlAttrs); - - if (drvPath != "" && drvsSeen.find(drvPath) == drvsSeen.end()) { - drvsSeen.insert(drvPath); - showAttrs(state, strict, location, *v.attrs, doc, context, drvsSeen); - } else - doc.writeEmptyElement("repeated"); - } - - else { - XMLOpenElement _(doc, "attrs"); - showAttrs(state, strict, location, *v.attrs, doc, context, drvsSeen); - } + case tAttrs: + if (state.isDerivation(v)) { + XMLAttrs xmlAttrs; - break; + Bindings::iterator a = + v.attrs->find(state.symbols.create("derivation")); - case tList1: case tList2: case tListN: { - XMLOpenElement _(doc, "list"); - for (unsigned int n = 0; n < v.listSize(); ++n) - printValueAsXML(state, strict, location, *v.listElems()[n], doc, context, drvsSeen); - break; + Path drvPath; + a = v.attrs->find(state.sDrvPath); + if (a != v.attrs->end()) { + if (strict) state.forceValue(*a->value); + if (a->value->type == tString) + xmlAttrs["drvPath"] = drvPath = a->value->string.s; } - case tLambda: { - XMLAttrs xmlAttrs; - if (location) posToXML(xmlAttrs, v.lambda.fun->pos); - XMLOpenElement _(doc, "function", xmlAttrs); - - if (v.lambda.fun->matchAttrs) { - XMLAttrs attrs; - if (!v.lambda.fun->arg.empty()) attrs["name"] = v.lambda.fun->arg; - if (v.lambda.fun->formals->ellipsis) attrs["ellipsis"] = "1"; - XMLOpenElement _(doc, "attrspat", attrs); - for (auto & i : v.lambda.fun->formals->formals) - doc.writeEmptyElement("attr", singletonAttrs("name", i.name)); - } else - doc.writeEmptyElement("varpat", singletonAttrs("name", v.lambda.fun->arg)); - - break; + a = v.attrs->find(state.sOutPath); + if (a != v.attrs->end()) { + if (strict) state.forceValue(*a->value); + if (a->value->type == tString) + xmlAttrs["outPath"] = a->value->string.s; } - case tExternal: - v.external->printValueAsXML(state, strict, location, doc, context, drvsSeen); - break; - - case tFloat: - doc.writeEmptyElement("float", singletonAttrs("value", (format("%1%") % v.fpoint).str())); - break; + XMLOpenElement _(doc, "derivation", xmlAttrs); + + if (drvPath != "" && drvsSeen.find(drvPath) == drvsSeen.end()) { + drvsSeen.insert(drvPath); + showAttrs(state, strict, location, *v.attrs, doc, context, drvsSeen); + } else + doc.writeEmptyElement("repeated"); + } + + else { + XMLOpenElement _(doc, "attrs"); + showAttrs(state, strict, location, *v.attrs, doc, context, drvsSeen); + } + + break; + + case tList1: + case tList2: + case tListN: { + XMLOpenElement _(doc, "list"); + for (unsigned int n = 0; n < v.listSize(); ++n) + printValueAsXML(state, strict, location, *v.listElems()[n], doc, + context, drvsSeen); + break; + } - default: - doc.writeEmptyElement("unevaluated"); + case tLambda: { + XMLAttrs xmlAttrs; + if (location) posToXML(xmlAttrs, v.lambda.fun->pos); + XMLOpenElement _(doc, "function", xmlAttrs); + + if (v.lambda.fun->matchAttrs) { + XMLAttrs attrs; + if (!v.lambda.fun->arg.empty()) attrs["name"] = v.lambda.fun->arg; + if (v.lambda.fun->formals->ellipsis) attrs["ellipsis"] = "1"; + XMLOpenElement _(doc, "attrspat", attrs); + for (auto& i : v.lambda.fun->formals->formals) + doc.writeEmptyElement("attr", singletonAttrs("name", i.name)); + } else + doc.writeEmptyElement("varpat", + singletonAttrs("name", v.lambda.fun->arg)); + + break; } -} + case tExternal: + v.external->printValueAsXML(state, strict, location, doc, context, + drvsSeen); + break; -void ExternalValueBase::printValueAsXML(EvalState & state, bool strict, - bool location, XMLWriter & doc, PathSet & context, PathSet & drvsSeen) const -{ - doc.writeEmptyElement("unevaluated"); -} + case tFloat: + doc.writeEmptyElement( + "float", singletonAttrs("value", (format("%1%") % v.fpoint).str())); + break; - -void printValueAsXML(EvalState & state, bool strict, bool location, - Value & v, std::ostream & out, PathSet & context) -{ - XMLWriter doc(true, out); - XMLOpenElement root(doc, "expr"); - PathSet drvsSeen; - printValueAsXML(state, strict, location, v, doc, context, drvsSeen); + default: + doc.writeEmptyElement("unevaluated"); + } } +void ExternalValueBase::printValueAsXML(EvalState& state, bool strict, + bool location, XMLWriter& doc, + PathSet& context, + PathSet& drvsSeen) const { + doc.writeEmptyElement("unevaluated"); +} +void printValueAsXML(EvalState& state, bool strict, bool location, Value& v, + std::ostream& out, PathSet& context) { + XMLWriter doc(true, out); + XMLOpenElement root(doc, "expr"); + PathSet drvsSeen; + printValueAsXML(state, strict, location, v, doc, context, drvsSeen); } + +} // namespace nix diff --git a/third_party/nix/src/libexpr/value-to-xml.hh b/third_party/nix/src/libexpr/value-to-xml.hh index 97657327edba..e456b4921b24 100644 --- a/third_party/nix/src/libexpr/value-to-xml.hh +++ b/third_party/nix/src/libexpr/value-to-xml.hh @@ -1,14 +1,13 @@ #pragma once -#include "nixexpr.hh" -#include "eval.hh" - -#include <string> #include <map> +#include <string> +#include "eval.hh" +#include "nixexpr.hh" namespace nix { -void printValueAsXML(EvalState & state, bool strict, bool location, - Value & v, std::ostream & out, PathSet & context); - +void printValueAsXML(EvalState& state, bool strict, bool location, Value& v, + std::ostream& out, PathSet& context); + } diff --git a/third_party/nix/src/libexpr/value.hh b/third_party/nix/src/libexpr/value.hh index e1ec87d3b84c..211b1669f8d4 100644 --- a/third_party/nix/src/libexpr/value.hh +++ b/third_party/nix/src/libexpr/value.hh @@ -8,28 +8,26 @@ namespace nix { - typedef enum { - tInt = 1, - tBool, - tString, - tPath, - tNull, - tAttrs, - tList1, - tList2, - tListN, - tThunk, - tApp, - tLambda, - tBlackhole, - tPrimOp, - tPrimOpApp, - tExternal, - tFloat + tInt = 1, + tBool, + tString, + tPath, + tNull, + tAttrs, + tList1, + tList2, + tListN, + tThunk, + tApp, + tLambda, + tBlackhole, + tPrimOp, + tPrimOpApp, + tExternal, + tFloat } ValueType; - class Bindings; struct Env; struct Expr; @@ -42,233 +40,201 @@ class EvalState; class XMLWriter; class JSONPlaceholder; - typedef int64_t NixInt; typedef double NixFloat; /* External values must descend from ExternalValueBase, so that * type-agnostic nix functions (e.g. showType) can be implemented */ -class ExternalValueBase -{ - friend std::ostream & operator << (std::ostream & str, const ExternalValueBase & v); - protected: - /* Print out the value */ - virtual std::ostream & print(std::ostream & str) const = 0; - - public: - /* Return a simple string describing the type */ - virtual string showType() const = 0; - - /* Return a string to be used in builtins.typeOf */ - virtual string typeOf() const = 0; - - /* How much space does this value take up */ - virtual size_t valueSize(std::set<const void *> & seen) const = 0; - - /* Coerce the value to a string. Defaults to uncoercable, i.e. throws an - * error - */ - virtual string coerceToString(const Pos & pos, PathSet & context, bool copyMore, bool copyToStore) const; - - /* Compare to another value of the same type. Defaults to uncomparable, - * i.e. always false. - */ - virtual bool operator==(const ExternalValueBase & b) const; - - /* Print the value as JSON. Defaults to unconvertable, i.e. throws an error */ - virtual void printValueAsJSON(EvalState & state, bool strict, - JSONPlaceholder & out, PathSet & context) const; - - /* Print the value as XML. Defaults to unevaluated */ - virtual void printValueAsXML(EvalState & state, bool strict, bool location, - XMLWriter & doc, PathSet & context, PathSet & drvsSeen) const; - - virtual ~ExternalValueBase() - { - }; -}; - -std::ostream & operator << (std::ostream & str, const ExternalValueBase & v); - - -struct Value -{ - ValueType type; - union - { - NixInt integer; - bool boolean; - - /* Strings in the evaluator carry a so-called `context' which - is a list of strings representing store paths. This is to - allow users to write things like - - "--with-freetype2-library=" + freetype + "/lib" - - where `freetype' is a derivation (or a source to be copied - to the store). If we just concatenated the strings without - keeping track of the referenced store paths, then if the - string is used as a derivation attribute, the derivation - will not have the correct dependencies in its inputDrvs and - inputSrcs. - - The semantics of the context is as follows: when a string - with context C is used as a derivation attribute, then the - derivations in C will be added to the inputDrvs of the - derivation, and the other store paths in C will be added to - the inputSrcs of the derivations. - - For canonicity, the store paths should be in sorted order. */ - struct { - const char * s; - const char * * context; // must be in sorted order - } string; - - const char * path; - Bindings * attrs; - struct { - size_t size; - Value * * elems; - } bigList; - Value * smallList[2]; - struct { - Env * env; - Expr * expr; - } thunk; - struct { - Value * left, * right; - } app; - struct { - Env * env; - ExprLambda * fun; - } lambda; - PrimOp * primOp; - struct { - Value * left, * right; - } primOpApp; - ExternalValueBase * external; - NixFloat fpoint; - }; - - bool isList() const - { - return type == tList1 || type == tList2 || type == tListN; - } - - Value * * listElems() - { - return type == tList1 || type == tList2 ? smallList : bigList.elems; - } - - const Value * const * listElems() const - { - return type == tList1 || type == tList2 ? smallList : bigList.elems; - } - - size_t listSize() const - { - return type == tList1 ? 1 : type == tList2 ? 2 : bigList.size; - } -}; +class ExternalValueBase { + friend std::ostream& operator<<(std::ostream& str, + const ExternalValueBase& v); + protected: + /* Print out the value */ + virtual std::ostream& print(std::ostream& str) const = 0; -/* After overwriting an app node, be sure to clear pointers in the - Value to ensure that the target isn't kept alive unnecessarily. */ -static inline void clearValue(Value & v) -{ - v.app.left = v.app.right = 0; -} + public: + /* Return a simple string describing the type */ + virtual string showType() const = 0; + /* Return a string to be used in builtins.typeOf */ + virtual string typeOf() const = 0; -static inline void mkInt(Value & v, NixInt n) -{ - clearValue(v); - v.type = tInt; - v.integer = n; -} + /* How much space does this value take up */ + virtual size_t valueSize(std::set<const void*>& seen) const = 0; + /* Coerce the value to a string. Defaults to uncoercable, i.e. throws an + * error + */ + virtual string coerceToString(const Pos& pos, PathSet& context, bool copyMore, + bool copyToStore) const; -static inline void mkFloat(Value & v, NixFloat n) -{ - clearValue(v); - v.type = tFloat; - v.fpoint = n; -} + /* Compare to another value of the same type. Defaults to uncomparable, + * i.e. always false. + */ + virtual bool operator==(const ExternalValueBase& b) const; + /* Print the value as JSON. Defaults to unconvertable, i.e. throws an error */ + virtual void printValueAsJSON(EvalState& state, bool strict, + JSONPlaceholder& out, PathSet& context) const; -static inline void mkBool(Value & v, bool b) -{ - clearValue(v); - v.type = tBool; - v.boolean = b; -} + /* Print the value as XML. Defaults to unevaluated */ + virtual void printValueAsXML(EvalState& state, bool strict, bool location, + XMLWriter& doc, PathSet& context, + PathSet& drvsSeen) const; + virtual ~ExternalValueBase(){}; +}; -static inline void mkNull(Value & v) -{ - clearValue(v); - v.type = tNull; -} +std::ostream& operator<<(std::ostream& str, const ExternalValueBase& v); + +struct Value { + ValueType type; + union { + NixInt integer; + bool boolean; + + /* Strings in the evaluator carry a so-called `context' which + is a list of strings representing store paths. This is to + allow users to write things like + + "--with-freetype2-library=" + freetype + "/lib" + + where `freetype' is a derivation (or a source to be copied + to the store). If we just concatenated the strings without + keeping track of the referenced store paths, then if the + string is used as a derivation attribute, the derivation + will not have the correct dependencies in its inputDrvs and + inputSrcs. + + The semantics of the context is as follows: when a string + with context C is used as a derivation attribute, then the + derivations in C will be added to the inputDrvs of the + derivation, and the other store paths in C will be added to + the inputSrcs of the derivations. + + For canonicity, the store paths should be in sorted order. */ + struct { + const char* s; + const char** context; // must be in sorted order + } string; + + const char* path; + Bindings* attrs; + struct { + size_t size; + Value** elems; + } bigList; + Value* smallList[2]; + struct { + Env* env; + Expr* expr; + } thunk; + struct { + Value *left, *right; + } app; + struct { + Env* env; + ExprLambda* fun; + } lambda; + PrimOp* primOp; + struct { + Value *left, *right; + } primOpApp; + ExternalValueBase* external; + NixFloat fpoint; + }; + + bool isList() const { + return type == tList1 || type == tList2 || type == tListN; + } + + Value** listElems() { + return type == tList1 || type == tList2 ? smallList : bigList.elems; + } + + const Value* const* listElems() const { + return type == tList1 || type == tList2 ? smallList : bigList.elems; + } + + size_t listSize() const { + return type == tList1 ? 1 : type == tList2 ? 2 : bigList.size; + } +}; +/* After overwriting an app node, be sure to clear pointers in the + Value to ensure that the target isn't kept alive unnecessarily. */ +static inline void clearValue(Value& v) { v.app.left = v.app.right = 0; } -static inline void mkApp(Value & v, Value & left, Value & right) -{ - v.type = tApp; - v.app.left = &left; - v.app.right = &right; +static inline void mkInt(Value& v, NixInt n) { + clearValue(v); + v.type = tInt; + v.integer = n; } - -static inline void mkPrimOpApp(Value & v, Value & left, Value & right) -{ - v.type = tPrimOpApp; - v.app.left = &left; - v.app.right = &right; +static inline void mkFloat(Value& v, NixFloat n) { + clearValue(v); + v.type = tFloat; + v.fpoint = n; } - -static inline void mkStringNoCopy(Value & v, const char * s) -{ - v.type = tString; - v.string.s = s; - v.string.context = 0; +static inline void mkBool(Value& v, bool b) { + clearValue(v); + v.type = tBool; + v.boolean = b; } - -static inline void mkString(Value & v, const Symbol & s) -{ - mkStringNoCopy(v, ((const string &) s).c_str()); +static inline void mkNull(Value& v) { + clearValue(v); + v.type = tNull; } +static inline void mkApp(Value& v, Value& left, Value& right) { + v.type = tApp; + v.app.left = &left; + v.app.right = &right; +} -void mkString(Value & v, const char * s); +static inline void mkPrimOpApp(Value& v, Value& left, Value& right) { + v.type = tPrimOpApp; + v.app.left = &left; + v.app.right = &right; +} +static inline void mkStringNoCopy(Value& v, const char* s) { + v.type = tString; + v.string.s = s; + v.string.context = 0; +} -static inline void mkPathNoCopy(Value & v, const char * s) -{ - clearValue(v); - v.type = tPath; - v.path = s; +static inline void mkString(Value& v, const Symbol& s) { + mkStringNoCopy(v, ((const string&)s).c_str()); } +void mkString(Value& v, const char* s); -void mkPath(Value & v, const char * s); +static inline void mkPathNoCopy(Value& v, const char* s) { + clearValue(v); + v.type = tPath; + v.path = s; +} +void mkPath(Value& v, const char* s); /* Compute the size in bytes of the given value, including all values and environments reachable from it. Static expressions (Exprs) are not included. */ -size_t valueSize(Value & v); - +size_t valueSize(Value& v); #if HAVE_BOEHMGC -typedef std::vector<Value *, gc_allocator<Value *> > ValueVector; -typedef std::map<Symbol, Value *, std::less<Symbol>, gc_allocator<std::pair<const Symbol, Value *> > > ValueMap; +typedef std::vector<Value*, gc_allocator<Value*> > ValueVector; +typedef std::map<Symbol, Value*, std::less<Symbol>, + gc_allocator<std::pair<const Symbol, Value*> > > + ValueMap; #else -typedef std::vector<Value *> ValueVector; -typedef std::map<Symbol, Value *> ValueMap; +typedef std::vector<Value*> ValueVector; +typedef std::map<Symbol, Value*> ValueMap; #endif - -} +} // namespace nix |