1
1
Fork 0
mirror of https://github.com/NixOS/nix.git synced 2025-11-11 13:06:01 +01:00

Merge remote-tracking branch 'origin/master' into relative-flakes

This commit is contained in:
Eelco Dolstra 2024-11-22 14:44:32 +01:00
commit 0b00bf7c09
869 changed files with 5358 additions and 7769 deletions

View file

@ -10,6 +10,9 @@ lockFileStr:
# unlocked trees.
overrides:
# This is `prim_fetchFinalTree`.
fetchTreeFinal:
let
lockFile = builtins.fromJSON lockFileStr;
@ -51,7 +54,8 @@ let
}
else
# FIXME: remove obsolete node.info.
fetchTree (node.info or {} // removeAttrs node.locked ["dir"]);
# Note: lock file entries are always final.
fetchTreeFinal (node.info or {} // removeAttrs node.locked ["dir"]);
subdir = overrides.${key}.dir or node.locked.dir or "";

View file

@ -101,7 +101,7 @@ struct AttrDb
state->txn->commit();
state->txn.reset();
} catch (...) {
ignoreException();
ignoreExceptionInDestructor();
}
}
@ -112,7 +112,7 @@ struct AttrDb
try {
return fun();
} catch (SQLiteError &) {
ignoreException();
ignoreExceptionExceptInterrupt();
failed = true;
return 0;
}
@ -351,7 +351,7 @@ static std::shared_ptr<AttrDb> makeAttrDb(
try {
return std::make_shared<AttrDb>(cfg, fingerprint, symbols);
} catch (SQLiteError &) {
ignoreException();
ignoreExceptionExceptInterrupt();
return nullptr;
}
}

View file

@ -13,6 +13,8 @@
#else
# include <memory>
/* Some dummy aliases for Boehm GC definitions to reduce the number of
#ifdefs. */
@ -23,7 +25,6 @@ template<typename T>
using gc_allocator = std::allocator<T>;
# define GC_MALLOC_ATOMIC std::malloc
# define GC_STRDUP strdup
struct gc
{};

View file

@ -87,11 +87,15 @@ void EvalState::forceValue(Value & v, const PosIdx pos)
{
if (v.isThunk()) {
Env * env = v.payload.thunk.env;
assert(env || v.isBlackhole());
Expr * expr = v.payload.thunk.expr;
try {
v.mkBlackhole();
//checkInterrupt();
expr->eval(*this, *env, v);
if (env) [[likely]]
expr->eval(*this, *env, v);
else
ExprBlackHole::throwInfiniteRecursionError(*this, v);
} catch (...) {
v.mkThunk(env, expr);
tryFixupBlackHolePos(v, pos);

View file

@ -3,10 +3,11 @@
#include "config.hh"
#include "ref.hh"
#include "source-path.hh"
namespace nix {
class Store;
class EvalState;
struct EvalSettings : Config
{
@ -18,11 +19,8 @@ struct EvalSettings : Config
*
* The return value is (a) whether the entry was valid, and, if so,
* what does it map to.
*
* @todo Return (`std::optional` of) `SourceAccssor` or something
* more structured instead of mere `std::string`?
*/
using LookupPathHook = std::optional<std::string>(ref<Store> store, std::string_view);
using LookupPathHook = std::optional<SourcePath>(EvalState & state, std::string_view);
/**
* Map from "scheme" to a `LookupPathHook`.

View file

@ -53,15 +53,6 @@ static char * allocString(size_t size)
}
static char * dupString(const char * s)
{
char * t;
t = GC_STRDUP(s);
if (!t) throw std::bad_alloc();
return t;
}
// When there's no need to write to the string, we can optimize away empty
// string allocations.
// This function handles makeImmutableString(std::string_view()) by returning
@ -457,7 +448,7 @@ void EvalState::addConstant(const std::string & name, Value * v, Constant info)
/* Install value the base environment. */
staticBaseEnv->vars.emplace_back(symbols.create(name), baseEnvDispl);
baseEnv.values[baseEnvDispl++] = v;
baseEnv.values[0]->payload.attrs->push_back(Attr(symbols.create(name2), v));
getBuiltins().payload.attrs->push_back(Attr(symbols.create(name2), v));
}
}
@ -519,16 +510,32 @@ Value * EvalState::addPrimOp(PrimOp && primOp)
Value * v = allocValue();
v->mkPrimOp(new PrimOp(primOp));
staticBaseEnv->vars.emplace_back(envName, baseEnvDispl);
baseEnv.values[baseEnvDispl++] = v;
baseEnv.values[0]->payload.attrs->push_back(Attr(symbols.create(primOp.name), v));
if (primOp.internal)
internalPrimOps.emplace(primOp.name, v);
else {
staticBaseEnv->vars.emplace_back(envName, baseEnvDispl);
baseEnv.values[baseEnvDispl++] = v;
getBuiltins().payload.attrs->push_back(Attr(symbols.create(primOp.name), v));
}
return v;
}
Value & EvalState::getBuiltins()
{
return *baseEnv.values[0];
}
Value & EvalState::getBuiltin(const std::string & name)
{
return *baseEnv.values[0]->attrs()->find(symbols.create(name))->value;
auto it = getBuiltins().attrs()->get(symbols.create(name));
if (it)
return *it->value;
else
error<EvalError>("builtin '%1%' not found", name).debugThrow();
}
@ -548,7 +555,7 @@ std::optional<EvalState::Doc> EvalState::getDoc(Value & v)
if (v.isLambda()) {
auto exprLambda = v.payload.lambda.fun;
std::stringstream s(std::ios_base::out);
std::ostringstream s;
std::string name;
auto pos = positions[exprLambda->getPos()];
std::string docStr;
@ -580,30 +587,25 @@ std::optional<EvalState::Doc> EvalState::getDoc(Value & v)
s << docStr;
s << '\0'; // for making a c string below
std::string ss = s.str();
return Doc {
.pos = pos,
.name = name,
.arity = 0, // FIXME: figure out how deep by syntax only? It's not semantically useful though...
.args = {},
.doc =
// FIXME: this leaks; make the field std::string?
strdup(ss.data()),
.doc = makeImmutableString(toView(s)), // NOTE: memory leak when compiled without GC
};
}
if (isFunctor(v)) {
try {
Value & functor = *v.attrs()->find(sFunctor)->value;
Value * vp = &v;
Value * vp[] = {&v};
Value partiallyApplied;
// The first paramater is not user-provided, and may be
// handled by code that is opaque to the user, like lib.const = x: y: y;
// So preferably we show docs that are relevant to the
// "partially applied" function returned by e.g. `const`.
// We apply the first argument:
callFunction(functor, 1, &vp, partiallyApplied, noPos);
callFunction(functor, vp, partiallyApplied, noPos);
auto _level = addCallDepth(noPos);
return getDoc(partiallyApplied);
}
@ -832,9 +834,10 @@ static const char * * encodeContext(const NixStringContext & context)
size_t n = 0;
auto ctx = (const char * *)
allocBytes((context.size() + 1) * sizeof(char *));
for (auto & i : context)
ctx[n++] = dupString(i.to_string().c_str());
ctx[n] = 0;
for (auto & i : context) {
ctx[n++] = makeImmutableString({i.to_string()});
}
ctx[n] = nullptr;
return ctx;
} else
return nullptr;
@ -1467,7 +1470,7 @@ void ExprLambda::eval(EvalState & state, Env & env, Value & v)
v.mkLambda(&env, this);
}
void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value & vRes, const PosIdx pos)
void EvalState::callFunction(Value & fun, std::span<Value *> args, Value & vRes, const PosIdx pos)
{
auto _level = addCallDepth(pos);
@ -1482,16 +1485,16 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value &
auto makeAppChain = [&]()
{
vRes = vCur;
for (size_t i = 0; i < nrArgs; ++i) {
for (auto arg : args) {
auto fun2 = allocValue();
*fun2 = vRes;
vRes.mkPrimOpApp(fun2, args[i]);
vRes.mkPrimOpApp(fun2, arg);
}
};
const Attr * functor;
while (nrArgs > 0) {
while (args.size() > 0) {
if (vCur.isLambda()) {
@ -1594,15 +1597,14 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value &
throw;
}
nrArgs--;
args += 1;
args = args.subspan(1);
}
else if (vCur.isPrimOp()) {
size_t argsLeft = vCur.primOp()->arity;
if (nrArgs < argsLeft) {
if (args.size() < argsLeft) {
/* We don't have enough arguments, so create a tPrimOpApp chain. */
makeAppChain();
return;
@ -1614,15 +1616,14 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value &
if (countCalls) primOpCalls[fn->name]++;
try {
fn->fun(*this, vCur.determinePos(noPos), args, vCur);
fn->fun(*this, vCur.determinePos(noPos), args.data(), vCur);
} catch (Error & e) {
if (fn->addTrace)
addErrorTrace(e, pos, "while calling the '%1%' builtin", fn->name);
throw;
}
nrArgs -= argsLeft;
args += argsLeft;
args = args.subspan(argsLeft);
}
}
@ -1638,7 +1639,7 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value &
auto arity = primOp->primOp()->arity;
auto argsLeft = arity - argsDone;
if (nrArgs < argsLeft) {
if (args.size() < argsLeft) {
/* We still don't have enough arguments, so extend the tPrimOpApp chain. */
makeAppChain();
return;
@ -1670,8 +1671,7 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value &
throw;
}
nrArgs -= argsLeft;
args += argsLeft;
args = args.subspan(argsLeft);
}
}
@ -1682,13 +1682,12 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value &
Value * args2[] = {allocValue(), args[0]};
*args2[0] = vCur;
try {
callFunction(*functor->value, 2, args2, vCur, functor->pos);
callFunction(*functor->value, args2, vCur, functor->pos);
} catch (Error & e) {
e.addTrace(positions[pos], "while calling a functor (an attribute set with a '__functor' attribute)");
throw;
}
nrArgs--;
args++;
args = args.subspan(1);
}
else
@ -1731,7 +1730,7 @@ void ExprCall::eval(EvalState & state, Env & env, Value & v)
for (size_t i = 0; i < args.size(); ++i)
vArgs[i] = args[i]->maybeThunk(state, env);
state.callFunction(vFun, args.size(), vArgs.data(), v, pos);
state.callFunction(vFun, vArgs, v, pos);
}
@ -1743,7 +1742,7 @@ void EvalState::incrFunctionCall(ExprLambda * fun)
}
void EvalState::autoCallFunction(Bindings & args, Value & fun, Value & res)
void EvalState::autoCallFunction(const Bindings & args, Value & fun, Value & res)
{
auto pos = fun.determinePos(noPos);
@ -1813,11 +1812,9 @@ void ExprIf::eval(EvalState & state, Env & env, Value & v)
void ExprAssert::eval(EvalState & state, Env & env, Value & v)
{
if (!state.evalBool(env, cond, pos, "in the condition of the assert statement")) {
auto exprStr = ({
std::ostringstream out;
cond->show(state.symbols, out);
out.str();
});
std::ostringstream out;
cond->show(state.symbols, out);
auto exprStr = toView(out);
if (auto eq = dynamic_cast<ExprOpEq *>(cond)) {
try {
@ -2055,9 +2052,12 @@ void ExprPos::eval(EvalState & state, Env & env, Value & v)
state.mkPos(v, pos);
}
void ExprBlackHole::eval(EvalState & state, Env & env, Value & v)
void ExprBlackHole::eval(EvalState & state, [[maybe_unused]] Env & env, Value & v)
{
throwInfiniteRecursionError(state, v);
}
[[gnu::noinline]] [[noreturn]] void ExprBlackHole::throwInfiniteRecursionError(EvalState & state, Value &v) {
state.error<InfiniteRecursionError>("infinite recursion encountered")
.atPos(v.determinePos(noPos))
.debugThrow();
@ -2849,7 +2849,9 @@ void EvalState::printStatistics()
#endif
#if HAVE_BOEHMGC
{GC_is_incremental_mode() ? "gcNonIncremental" : "gc", gcFullOnlyTime},
#ifndef _WIN32 // TODO implement
{GC_is_incremental_mode() ? "gcNonIncrementalFraction" : "gcFraction", gcFullOnlyTime / cpuTime},
#endif
#endif
};
topObj["envs"] = {
@ -3036,8 +3038,8 @@ SourcePath EvalState::findFile(const LookupPath & lookupPath, const std::string_
if (!rOpt) continue;
auto r = *rOpt;
Path res = suffix == "" ? r : concatStrings(r, "/", suffix);
if (pathExists(res)) return rootPath(CanonPath(canonPath(res)));
auto res = (r / CanonPath(suffix)).resolveSymlinks();
if (res.pathExists()) return res;
}
if (hasPrefix(path, "nix/"))
@ -3052,13 +3054,13 @@ SourcePath EvalState::findFile(const LookupPath & lookupPath, const std::string_
}
std::optional<std::string> EvalState::resolveLookupPathPath(const LookupPath::Path & value0, bool initAccessControl)
std::optional<SourcePath> EvalState::resolveLookupPathPath(const LookupPath::Path & value0, bool initAccessControl)
{
auto & value = value0.s;
auto i = lookupPathResolved.find(value);
if (i != lookupPathResolved.end()) return i->second;
auto finish = [&](std::string res) {
auto finish = [&](SourcePath res) {
debug("resolved search path element '%s' to '%s'", value, res);
lookupPathResolved.emplace(value, res);
return res;
@ -3071,7 +3073,7 @@ std::optional<std::string> EvalState::resolveLookupPathPath(const LookupPath::Pa
fetchSettings,
EvalSettings::resolvePseudoUrl(value));
auto storePath = fetchToStore(*store, SourcePath(accessor), FetchMode::Copy);
return finish(store->toRealPath(storePath));
return finish(rootPath(store->toRealPath(storePath)));
} catch (Error & e) {
logWarning({
.msg = HintFmt("Nix search path entry '%1%' cannot be downloaded, ignoring", value)
@ -3083,29 +3085,29 @@ std::optional<std::string> EvalState::resolveLookupPathPath(const LookupPath::Pa
auto scheme = value.substr(0, colPos);
auto rest = value.substr(colPos + 1);
if (auto * hook = get(settings.lookupPathHooks, scheme)) {
auto res = (*hook)(store, rest);
auto res = (*hook)(*this, rest);
if (res)
return finish(std::move(*res));
}
}
{
auto path = absPath(value);
auto path = rootPath(value);
/* Allow access to paths in the search path. */
if (initAccessControl) {
allowPath(path);
if (store->isInStore(path)) {
allowPath(path.path.abs());
if (store->isInStore(path.path.abs())) {
try {
StorePathSet closure;
store->computeFSClosure(store->toStorePath(path).first, closure);
store->computeFSClosure(store->toStorePath(path.path.abs()).first, closure);
for (auto & p : closure)
allowPath(p);
} catch (InvalidPath &) { }
}
}
if (pathExists(path))
if (path.pathExists())
return finish(std::move(path));
else {
logWarning({
@ -3116,7 +3118,6 @@ std::optional<std::string> EvalState::resolveLookupPathPath(const LookupPath::Pa
debug("failed to resolve search path element '%s'", value);
return std::nullopt;
}

View file

@ -91,7 +91,7 @@ struct PrimOp
const char * doc = nullptr;
/**
* Add a trace item, `while calling the '<name>' builtin`
* Add a trace item, while calling the `<name>` builtin.
*
* This is used to remove the redundant item for `builtins.addErrorContext`.
*/
@ -107,6 +107,11 @@ struct PrimOp
*/
std::optional<ExperimentalFeature> experimentalFeature;
/**
* If true, this primop is not exposed to the user.
*/
bool internal = false;
/**
* Validity check to be performed by functions that introduce primops,
* such as RegisterPrimOp() and Value::mkPrimOp().
@ -342,7 +347,7 @@ private:
LookupPath lookupPath;
std::map<std::string, std::optional<std::string>> lookupPathResolved;
std::map<std::string, std::optional<SourcePath>> lookupPathResolved;
/**
* Cache used by prim_match().
@ -447,9 +452,9 @@ public:
*
* If the specified search path element is a URI, download it.
*
* If it is not found, return `std::nullopt`
* If it is not found, return `std::nullopt`.
*/
std::optional<std::string> resolveLookupPathPath(
std::optional<SourcePath> resolveLookupPathPath(
const LookupPath::Path & elem,
bool initAccessControl = false);
@ -591,6 +596,11 @@ public:
*/
std::shared_ptr<StaticEnv> staticBaseEnv; // !!! should be private
/**
* Internal primops not exposed to the user.
*/
std::unordered_map<std::string, Value *, std::hash<std::string>, std::equal_to<std::string>, traceable_allocator<std::pair<const std::string, Value *>>> internalPrimOps;
/**
* Name and documentation about every constant.
*
@ -613,8 +623,19 @@ private:
public:
/**
* Retrieve a specific builtin, equivalent to evaluating `builtins.${name}`.
* @param name The attribute name of the builtin to retrieve.
* @throws EvalError if the builtin does not exist.
*/
Value & getBuiltin(const std::string & name);
/**
* Retrieve the `builtins` attrset, equivalent to evaluating the reference `builtins`.
* Always returns an attribute set value.
*/
Value & getBuiltins();
struct Doc
{
Pos pos;
@ -680,20 +701,19 @@ public:
bool isFunctor(Value & fun);
// FIXME: use std::span
void callFunction(Value & fun, size_t nrArgs, Value * * args, Value & vRes, const PosIdx pos);
void callFunction(Value & fun, std::span<Value *> args, Value & vRes, const PosIdx pos);
void callFunction(Value & fun, Value & arg, Value & vRes, const PosIdx pos)
{
Value * args[] = {&arg};
callFunction(fun, 1, args, vRes, pos);
callFunction(fun, args, vRes, 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);
void autoCallFunction(const Bindings & args, Value & fun, Value & res);
/**
* Allocation primitives.
@ -799,7 +819,6 @@ public:
bool callPathFilter(
Value * filterFun,
const SourcePath & path,
std::string_view pathArg,
PosIdx pos);
DocComment getDocCommentForPos(PosIdx pos);

View file

@ -374,11 +374,12 @@ static void getDerivations(EvalState & state, Value & vIn,
bound to the attribute with the "lower" name should take
precedence). */
for (auto & i : v.attrs()->lexicographicOrder(state.symbols)) {
std::string_view symbol{state.symbols[i->name]};
try {
debug("evaluating attribute '%1%'", state.symbols[i->name]);
if (!std::regex_match(std::string(state.symbols[i->name]), attrRegex))
debug("evaluating attribute '%1%'", symbol);
if (!std::regex_match(symbol.begin(), symbol.end(), attrRegex))
continue;
std::string pathPrefix2 = addToPath(pathPrefix, state.symbols[i->name]);
std::string pathPrefix2 = addToPath(pathPrefix, symbol);
if (combineChannels)
getDerivations(state, *i->value, pathPrefix2, autoArgs, drvs, done, ignoreAssertionFailures);
else if (getDerivation(state, *i->value, pathPrefix2, drvs, done, ignoreAssertionFailures)) {
@ -392,7 +393,7 @@ static void getDerivations(EvalState & state, Value & vIn,
}
}
} catch (Error & e) {
e.addTrace(state.positions[i->pos], "while evaluating the attribute '%s'", state.symbols[i->name]);
e.addTrace(state.positions[i->pos], "while evaluating the attribute '%s'", symbol);
throw;
}
}

View file

@ -1,50 +0,0 @@
libraries += libexpr
libexpr_NAME = libnixexpr
libexpr_DIR := $(d)
libexpr_SOURCES := \
$(wildcard $(d)/*.cc) \
$(wildcard $(d)/value/*.cc) \
$(wildcard $(d)/primops/*.cc) \
$(d)/lexer-tab.cc \
$(d)/parser-tab.cc
# Not just for this library itself, but also for downstream libraries using this library
INCLUDE_libexpr := -I $(d)
libexpr_CXXFLAGS += \
$(INCLUDE_libutil) $(INCLUDE_libstore) $(INCLUDE_libfetchers) $(INCLUDE_libexpr) \
-DGC_THREADS
libexpr_LIBS = libutil libstore libfetchers
libexpr_LDFLAGS += -lboost_context $(THREAD_LDFLAGS)
ifdef HOST_LINUX
libexpr_LDFLAGS += -ldl
endif
# The dependency on libgc must be propagated (i.e. meaning that
# programs/libraries that use libexpr must explicitly pass -lgc),
# because inline functions in libexpr's header files call libgc.
libexpr_LDFLAGS_PROPAGATED = $(BDW_GC_LIBS)
libexpr_ORDER_AFTER := $(d)/parser-tab.cc $(d)/parser-tab.hh $(d)/lexer-tab.cc $(d)/lexer-tab.hh
$(d)/parser-tab.cc $(d)/parser-tab.hh: $(d)/parser.y
$(trace-gen) bison -v -o $(libexpr_DIR)/parser-tab.cc $< -d
$(d)/lexer-tab.cc $(d)/lexer-tab.hh: $(d)/lexer.l
$(trace-gen) flex --outfile $(libexpr_DIR)/lexer-tab.cc --header-file=$(libexpr_DIR)/lexer-tab.hh $<
clean-files += $(d)/parser-tab.cc $(d)/parser-tab.hh $(d)/lexer-tab.cc $(d)/lexer-tab.hh
$(eval $(call install-file-in, $(buildprefix)$(d)/nix-expr.pc, $(libdir)/pkgconfig, 0644))
$(foreach i, $(wildcard src/libexpr/value/*.hh), \
$(eval $(call install-file-in, $(i), $(includedir)/nix/value, 0644)))
$(d)/primops.cc: $(d)/imported-drv-to-derivation.nix.gen.hh
$(d)/eval.cc: $(d)/primops/derivation.nix.gen.hh $(d)/fetchurl.nix.gen.hh $(d)/call-flake.nix.gen.hh

View file

@ -27,8 +27,6 @@ deps_public_maybe_subproject = [
]
subdir('build-utils-meson/subprojects')
subdir('build-utils-meson/threads')
boost = dependency(
'boost',
modules : ['container', 'context'],
@ -79,7 +77,7 @@ add_project_arguments(
language : 'cpp',
)
subdir('build-utils-meson/diagnostics')
subdir('build-utils-meson/common')
parser_tab = custom_target(
input : 'parser.y',

View file

@ -1,10 +0,0 @@
prefix=@prefix@
libdir=@libdir@
includedir=@includedir@
Name: Nix
Description: Nix Package Manager
Version: @PACKAGE_VERSION@
Requires: nix-store bdw-gc
Libs: -L${libdir} -lnixexpr
Cflags: -I${includedir}/nix -std=c++2a

View file

@ -663,4 +663,32 @@ std::string DocComment::getInnerText(const PosTable & positions) const {
return docStr;
}
/* Cursed or handling.
*
* In parser.y, every use of expr_select in a production must call one of the
* two below functions.
*
* To be removed by https://github.com/NixOS/nix/pull/11121
*/
void ExprCall::resetCursedOr()
{
cursedOrEndPos.reset();
}
void ExprCall::warnIfCursedOr(const SymbolTable & symbols, const PosTable & positions)
{
if (cursedOrEndPos.has_value()) {
std::ostringstream out;
out << "at " << positions[pos] << ": "
"This expression uses `or` as an identifier in a way that will change in a future Nix release.\n"
"Wrap this entire expression in parentheses to preserve its current meaning:\n"
" (" << positions[pos].getSnippetUpTo(positions[*cursedOrEndPos]).value_or("could not read expression") << ")\n"
"Give feedback at https://github.com/NixOS/nix/pull/11121";
warn(out.str());
}
}
}

View file

@ -96,6 +96,10 @@ struct Expr
virtual void setName(Symbol name);
virtual void setDocComment(DocComment docComment) { };
virtual PosIdx getPos() const { return noPos; }
// These are temporary methods to be used only in parser.y
virtual void resetCursedOr() { };
virtual void warnIfCursedOr(const SymbolTable & symbols, const PosTable & positions) { };
};
#define COMMON_METHODS \
@ -202,7 +206,7 @@ struct ExprSelect : Expr
/**
* Evaluate the `a.b.c` part of `a.b.c.d`. This exists mostly for the purpose of :doc in the repl.
*
* @param[out] v The attribute set that should contain the last attribute name (if it exists).
* @param[out] attrs The attribute set that should contain the last attribute name (if it exists).
* @return The last attribute name in `attrPath`
*
* @note This does *not* evaluate the final attribute, and does not fail if that's the only attribute that does not exist.
@ -354,10 +358,16 @@ struct ExprCall : Expr
Expr * fun;
std::vector<Expr *> args;
PosIdx pos;
std::optional<PosIdx> cursedOrEndPos; // used during parsing to warn about https://github.com/NixOS/nix/issues/11118
ExprCall(const PosIdx & pos, Expr * fun, std::vector<Expr *> && args)
: fun(fun), args(args), pos(pos)
: fun(fun), args(args), pos(pos), cursedOrEndPos({})
{ }
ExprCall(const PosIdx & pos, Expr * fun, std::vector<Expr *> && args, PosIdx && cursedOrEndPos)
: fun(fun), args(args), pos(pos), cursedOrEndPos(cursedOrEndPos)
{ }
PosIdx getPos() const override { return pos; }
virtual void resetCursedOr() override;
virtual void warnIfCursedOr(const SymbolTable & symbols, const PosTable & positions) override;
COMMON_METHODS
};
@ -458,6 +468,7 @@ struct ExprBlackHole : Expr
void show(const SymbolTable & symbols, std::ostream & str) const override {}
void eval(EvalState & state, Env & env, Value & v) override;
void bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env) override {}
[[noreturn]] static void throwInfiniteRecursionError(EvalState & state, Value & v);
};
extern ExprBlackHole eBlackHole;

View file

@ -1,11 +1,7 @@
{ lib
, stdenv
, mkMesonDerivation
, releaseTools
, mkMesonLibrary
, meson
, ninja
, pkg-config
, bison
, flex
, cmake # for resolving toml11 dep
@ -38,7 +34,7 @@ let
inherit (lib) fileset;
in
mkMesonDerivation (finalAttrs: {
mkMesonLibrary (finalAttrs: {
pname = "nix-expr";
inherit version;
@ -55,15 +51,13 @@ mkMesonDerivation (finalAttrs: {
(fileset.fileFilter (file: file.hasExt "hh") ./.)
./lexer.l
./parser.y
(fileset.fileFilter (file: file.hasExt "nix") ./.)
(fileset.difference
(fileset.fileFilter (file: file.hasExt "nix") ./.)
./package.nix
)
];
outputs = [ "out" "dev" ];
nativeBuildInputs = [
meson
ninja
pkg-config
bison
flex
cmake
@ -77,6 +71,10 @@ mkMesonDerivation (finalAttrs: {
nix-util
nix-store
nix-fetchers
] ++ finalAttrs.passthru.externalPropagatedBuildInputs;
# Hack for sake of the dev shell
passthru.externalPropagatedBuildInputs = [
boost
nlohmann_json
] ++ lib.optional enableGC boehmgc;
@ -102,10 +100,6 @@ mkMesonDerivation (finalAttrs: {
LDFLAGS = "-fuse-ld=gold";
};
separateDebugInfo = !stdenv.hostPlatform.isStatic;
hardeningDisable = lib.optional stdenv.hostPlatform.isStatic "pie";
meta = {
platforms = lib.platforms.unix ++ lib.platforms.windows;
};

View file

@ -264,19 +264,28 @@ expr_op
;
expr_app
: expr_app expr_select { $$ = makeCall(CUR_POS, $1, $2); }
| expr_select
: expr_app expr_select { $$ = makeCall(CUR_POS, $1, $2); $2->warnIfCursedOr(state->symbols, state->positions); }
| /* Once a cursed or reaches this nonterminal, it is no longer cursed,
because the uncursed parse would also produce an expr_app. But we need
to remove the cursed status in order to prevent valid things like
`f (g or)` from triggering the warning. */
expr_select { $$ = $1; $$->resetCursedOr(); }
;
expr_select
: expr_simple '.' attrpath
{ $$ = new ExprSelect(CUR_POS, $1, std::move(*$3), nullptr); delete $3; }
| expr_simple '.' attrpath OR_KW expr_select
{ $$ = new ExprSelect(CUR_POS, $1, std::move(*$3), $5); delete $3; }
| /* Backwards compatibility: because Nixpkgs has a rarely used
function named or, allow stuff like map or [...]. */
{ $$ = new ExprSelect(CUR_POS, $1, std::move(*$3), $5); delete $3; $5->warnIfCursedOr(state->symbols, state->positions); }
| /* Backwards compatibility: because Nixpkgs has a function named or,
allow stuff like map or [...]. This production is problematic (see
https://github.com/NixOS/nix/issues/11118) and will be refactored in the
future by treating `or` as a regular identifier. The refactor will (in
very rare cases, we think) change the meaning of expressions, so we mark
the ExprCall with data (establishing that it is a cursed or) that can
be used to emit a warning when an affected expression is parsed. */
expr_simple OR_KW
{ $$ = new ExprCall(CUR_POS, $1, {new ExprVar(CUR_POS, state->s.or_)}); }
{ $$ = new ExprCall(CUR_POS, $1, {new ExprVar(CUR_POS, state->s.or_)}, state->positions.add(state->origin, @$.endOffset)); }
| expr_simple
;
@ -472,7 +481,7 @@ string_attr
;
expr_list
: expr_list expr_select { $$ = $1; $1->elems.push_back($2); /* !!! dangerous */ }
: expr_list expr_select { $$ = $1; $1->elems.push_back($2); /* !!! dangerous */; $2->warnIfCursedOr(state->symbols, state->positions); }
| { $$ = new ExprList; }
;

View file

@ -40,6 +40,13 @@ namespace nix {
* Miscellaneous
*************************************************************/
static inline Value * mkString(EvalState & state, const std::csub_match & match)
{
Value * v = state.allocValue();
v->mkString({match.first, match.second});
return v;
}
StringMap EvalState::realiseContext(const NixStringContext & context, StorePathSet * maybePathsOut, bool isIFD)
{
std::vector<DerivedPath::Built> drvs;
@ -84,6 +91,7 @@ StringMap EvalState::realiseContext(const NixStringContext & context, StorePathS
/* Build/substitute the context. */
std::vector<DerivedPath> buildReqs;
buildReqs.reserve(drvs.size());
for (auto & d : drvs) buildReqs.emplace_back(DerivedPath { d });
buildStore->buildPaths(buildReqs, bmNormal, store);
@ -171,6 +179,78 @@ static void mkOutputString(
o.second.path(*state.store, Derivation::nameFromPath(drvPath), o.first));
}
/**
* `import` will parse a derivation when it imports a `.drv` file from the store.
*
* @param state The evaluation state.
* @param pos The position of the `import` call.
* @param path The path to the `.drv` to import.
* @param storePath The path to the `.drv` to import.
* @param v Return value
*/
void derivationToValue(EvalState & state, const PosIdx pos, const SourcePath & path, const StorePath & storePath, Value & v) {
auto path2 = path.path.abs();
Derivation drv = state.store->readDerivation(storePath);
auto attrs = state.buildBindings(3 + drv.outputs.size());
attrs.alloc(state.sDrvPath).mkString(path2, {
NixStringContextElem::DrvDeep { .drvPath = storePath },
});
attrs.alloc(state.sName).mkString(drv.env["name"]);
auto list = state.buildList(drv.outputs.size());
for (const auto & [i, o] : enumerate(drv.outputs)) {
mkOutputString(state, attrs, storePath, o);
(list[i] = state.allocValue())->mkString(o.first);
}
attrs.alloc(state.sOutputs).mkList(list);
auto w = state.allocValue();
w->mkAttrs(attrs);
if (!state.vImportedDrvToDerivation) {
state.vImportedDrvToDerivation = allocRootValue(state.allocValue());
state.eval(state.parseExprFromString(
#include "imported-drv-to-derivation.nix.gen.hh"
, state.rootPath(CanonPath::root)), **state.vImportedDrvToDerivation);
}
state.forceFunction(**state.vImportedDrvToDerivation, pos, "while evaluating imported-drv-to-derivation.nix.gen.hh");
v.mkApp(*state.vImportedDrvToDerivation, w);
state.forceAttrs(v, pos, "while calling imported-drv-to-derivation.nix.gen.hh");
}
/**
* Import a Nix file with an alternate base scope, as `builtins.scopedImport` does.
*
* @param state The evaluation state.
* @param pos The position of the import call.
* @param path The path to the file to import.
* @param vScope The base scope to use for the import.
* @param v Return value
*/
static void scopedImport(EvalState & state, const PosIdx pos, SourcePath & path, Value * vScope, Value & v) {
state.forceAttrs(*vScope, pos, "while evaluating the first argument passed to builtins.scopedImport");
Env * env = &state.allocEnv(vScope->attrs()->size());
env->up = &state.baseEnv;
auto staticEnv = std::make_shared<StaticEnv>(nullptr, state.staticBaseEnv.get(), vScope->attrs()->size());
unsigned int displ = 0;
for (auto & attr : *vScope->attrs()) {
staticEnv->vars.emplace_back(attr.name, displ);
env->values[displ++] = attr.value;
}
// No need to call staticEnv.sort(), because
// args[0]->attrs is already sorted.
printTalkative("evaluating file '%1%'", path);
Expr * e = state.parseExprFromFile(resolveExprPath(path), staticEnv);
e->eval(state, *env, v);
}
/* Load and evaluate an expression from path specified by the
argument. */
static void import(EvalState & state, const PosIdx pos, Value & vPath, Value * vScope, Value & v)
@ -189,60 +269,13 @@ static void import(EvalState & state, const PosIdx pos, Value & vPath, Value * v
};
if (auto storePath = isValidDerivationInStore()) {
Derivation drv = state.store->readDerivation(*storePath);
auto attrs = state.buildBindings(3 + drv.outputs.size());
attrs.alloc(state.sDrvPath).mkString(path2, {
NixStringContextElem::DrvDeep { .drvPath = *storePath },
});
attrs.alloc(state.sName).mkString(drv.env["name"]);
auto list = state.buildList(drv.outputs.size());
for (const auto & [i, o] : enumerate(drv.outputs)) {
mkOutputString(state, attrs, *storePath, o);
(list[i] = state.allocValue())->mkString(o.first);
}
attrs.alloc(state.sOutputs).mkList(list);
auto w = state.allocValue();
w->mkAttrs(attrs);
if (!state.vImportedDrvToDerivation) {
state.vImportedDrvToDerivation = allocRootValue(state.allocValue());
state.eval(state.parseExprFromString(
#include "imported-drv-to-derivation.nix.gen.hh"
, state.rootPath(CanonPath::root)), **state.vImportedDrvToDerivation);
}
state.forceFunction(**state.vImportedDrvToDerivation, pos, "while evaluating imported-drv-to-derivation.nix.gen.hh");
v.mkApp(*state.vImportedDrvToDerivation, w);
state.forceAttrs(v, pos, "while calling imported-drv-to-derivation.nix.gen.hh");
derivationToValue(state, pos, path, *storePath, v);
}
else if (vScope) {
scopedImport(state, pos, path, vScope, v);
}
else {
if (!vScope)
state.evalFile(path, v);
else {
state.forceAttrs(*vScope, pos, "while evaluating the first argument passed to builtins.scopedImport");
Env * env = &state.allocEnv(vScope->attrs()->size());
env->up = &state.baseEnv;
auto staticEnv = std::make_shared<StaticEnv>(nullptr, state.staticBaseEnv.get(), vScope->attrs()->size());
unsigned int displ = 0;
for (auto & attr : *vScope->attrs()) {
staticEnv->vars.emplace_back(attr.name, displ);
env->values[displ++] = attr.value;
}
// No need to call staticEnv.sort(), because
// args[0]->attrs is already sorted.
printTalkative("evaluating file '%1%'", path);
Expr * e = state.parseExprFromFile(resolveExprPath(path), staticEnv);
e->eval(state, *env, v);
}
state.evalFile(path, v);
}
}
@ -691,7 +724,7 @@ static void prim_genericClosure(EvalState & state, const PosIdx pos, Value * * a
/* Call the `operator' function with `e' as argument. */
Value newElements;
state.callFunction(*op->value, 1, &e, newElements, noPos);
state.callFunction(*op->value, {&e, 1}, newElements, noPos);
state.forceList(newElements, noPos, "while evaluating the return value of the `operator` passed to builtins.genericClosure");
/* Add the values returned by the operator to the work set. */
@ -937,6 +970,9 @@ static RegisterPrimOp primop_tryEval({
`let e = { x = throw ""; }; in
(builtins.tryEval (builtins.deepSeq e e)).success` will be
`false`.
`tryEval` intentionally does not return the error message, because that risks bringing non-determinism into the evaluation result, and it would become very difficult to improve error reporting without breaking existing expressions.
Instead, use [`builtins.addErrorContext`](@docroot@/language/builtins.md#builtins-addErrorContext) to add context to the error message, and use a Nix unit testing tool for testing.
)",
.fun = prim_tryEval,
});
@ -1567,7 +1603,8 @@ static RegisterPrimOp primop_placeholder({
*************************************************************/
/* Convert the argument to a path. !!! obsolete? */
/* Convert the argument to a path and then to a string (confusing,
eh?). !!! obsolete? */
static void prim_toPath(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
NixStringContext context;
@ -2129,7 +2166,7 @@ static void prim_toXML(EvalState & state, const PosIdx pos, Value * * args, Valu
std::ostringstream out;
NixStringContext context;
printValueAsXML(state, true, false, *args[0], out, context, pos);
v.mkString(out.str(), context);
v.mkString(toView(out), context);
}
static RegisterPrimOp primop_toXML({
@ -2237,7 +2274,7 @@ static void prim_toJSON(EvalState & state, const PosIdx pos, Value * * args, Val
std::ostringstream out;
NixStringContext context;
printValueAsJSON(state, true, *args[0], pos, out, context);
v.mkString(out.str(), context);
v.mkString(toView(out), context);
}
static RegisterPrimOp primop_toJSON({
@ -2401,7 +2438,6 @@ static RegisterPrimOp primop_toFile({
bool EvalState::callPathFilter(
Value * filterFun,
const SourcePath & path,
std::string_view pathArg,
PosIdx pos)
{
auto st = path.lstat();
@ -2409,12 +2445,12 @@ bool EvalState::callPathFilter(
/* Call the filter function. The first argument is the path, the
second is a string indicating the type of the file. */
Value arg1;
arg1.mkString(pathArg);
arg1.mkString(path.path.abs());
// assert that type is not "unknown"
Value * args []{&arg1, fileTypeToString(*this, st.type)};
Value res;
callFunction(*filterFun, 2, args, res, pos);
callFunction(*filterFun, args, res, pos);
return forceBool(res, pos, "while evaluating the return value of the path filter function");
}
@ -2452,7 +2488,7 @@ static void addPath(
if (filterFun)
filter = std::make_unique<PathFilter>([&](const Path & p) {
auto p2 = CanonPath(p);
return state.callPathFilter(filterFun, {path.accessor, p2}, p2.abs(), pos);
return state.callPathFilter(filterFun, {path.accessor, p2}, pos);
});
std::optional<StorePath> expectedStorePath;
@ -2578,13 +2614,13 @@ static void prim_path(EvalState & state, const PosIdx pos, Value * * args, Value
expectedHash = newHashAllowEmpty(state.forceStringNoCtx(*attr.value, attr.pos, "while evaluating the `sha256` attribute passed to builtins.path"), HashAlgorithm::SHA256);
else
state.error<EvalError>(
"unsupported argument '%1%' to 'addPath'",
"unsupported argument '%1%' to 'builtins.path'",
state.symbols[attr.name]
).atPos(attr.pos).debugThrow();
}
if (!path)
state.error<EvalError>(
"missing required 'path' attribute in the first argument to builtins.path"
"missing required 'path' attribute in the first argument to 'builtins.path'"
).atPos(pos).debugThrow();
if (name.empty())
name = path->baseName();
@ -3451,7 +3487,7 @@ static void prim_foldlStrict(EvalState & state, const PosIdx pos, Value * * args
for (auto [n, elem] : enumerate(args[2]->listItems())) {
Value * vs []{vCur, elem};
vCur = n == args[2]->listSize() - 1 ? &v : state.allocValue();
state.callFunction(*args[0], 2, vs, *vCur, pos);
state.callFunction(*args[0], vs, *vCur, pos);
}
state.forceValue(v, pos);
} else {
@ -3601,7 +3637,7 @@ static void prim_sort(EvalState & state, const PosIdx pos, Value * * args, Value
Value * vs[] = {a, b};
Value vBool;
state.callFunction(*args[0], 2, vs, vBool, noPos);
state.callFunction(*args[0], vs, vBool, noPos);
return state.forceBool(vBool, pos, "while evaluating the return value of the sorting function passed to builtins.sort");
};
@ -4268,7 +4304,7 @@ void prim_match(EvalState & state, const PosIdx pos, Value * * args, Value & v)
if (!match[i + 1].matched)
v2 = &state.vNull;
else
(v2 = state.allocValue())->mkString(match[i + 1].str());
v2 = mkString(state, match[i + 1]);
v.mkList(list);
} catch (std::regex_error & e) {
@ -4352,7 +4388,7 @@ void prim_split(EvalState & state, const PosIdx pos, Value * * args, Value & v)
auto match = *i;
// Add a string for non-matched characters.
(list[idx++] = state.allocValue())->mkString(match.prefix().str());
list[idx++] = mkString(state, match.prefix());
// Add a list for matched substrings.
const size_t slen = match.size() - 1;
@ -4363,14 +4399,14 @@ void prim_split(EvalState & state, const PosIdx pos, Value * * args, Value & v)
if (!match[si + 1].matched)
v2 = &state.vNull;
else
(v2 = state.allocValue())->mkString(match[si + 1].str());
v2 = mkString(state, match[si + 1]);
}
(list[idx++] = state.allocValue())->mkList(list2);
// Add a string for non-matched suffix characters.
if (idx == 2 * len)
(list[idx++] = state.allocValue())->mkString(match.suffix().str());
list[idx++] = mkString(state, match.suffix());
}
assert(idx == 2 * len + 1);
@ -4901,7 +4937,7 @@ void EvalState::createBaseEnv()
/* Now that we've added all primops, sort the `builtins' set,
because attribute lookups expect it to be sorted. */
baseEnv.values[0]->payload.attrs->sort();
getBuiltins().payload.attrs->sort();
staticBaseEnv->sort();

View file

@ -86,7 +86,7 @@ static RegisterPrimOp primop_unsafeDiscardOutputDependency({
This is the opposite of [`builtins.addDrvOutputDependencies`](#builtins-addDrvOutputDependencies).
This is unsafe because it allows us to "forget" store objects we would have otherwise refered to with the string context,
This is unsafe because it allows us to "forget" store objects we would have otherwise referred to with the string context,
whereas Nix normally tracks all dependencies consistently.
Safe operations "grow" but never "shrink" string contexts.
[`builtins.addDrvOutputDependencies`] in contrast is safe because "derivation deep" string context element always refers to the underlying derivation (among many more things).

View file

@ -11,6 +11,8 @@
#include "value-to-json.hh"
#include "fetch-to-store.hh"
#include <nlohmann/json.hpp>
#include <ctime>
#include <iomanip>
#include <regex>
@ -75,6 +77,7 @@ struct FetchTreeParams {
bool emptyRevFallback = false;
bool allowNameArgument = false;
bool isFetchGit = false;
bool isFinal = false;
};
static void fetchTree(
@ -192,6 +195,13 @@ static void fetchTree(
state.checkURI(input.toURLString());
if (params.isFinal) {
input.attrs.insert_or_assign("__final", Explicit<bool>(true));
} else {
if (input.isFinal())
throw Error("input '%s' is not allowed to use the '__final' attribute", input.to_string());
}
auto [storePath, input2] = input.fetchToStore(state.store);
state.allowPath(storePath);
@ -245,7 +255,7 @@ static RegisterPrimOp primop_fetchTree({
The following source types and associated input attributes are supported.
<!-- TODO: It would be soooo much more predictable to work with (and
document) if `fetchTree` was a curried call with the first paramter for
document) if `fetchTree` was a curried call with the first parameter for
`type` or an attribute like `builtins.fetchTree.git`! -->
- `"file"`
@ -428,6 +438,18 @@ static RegisterPrimOp primop_fetchTree({
.experimentalFeature = Xp::FetchTree,
});
void prim_fetchFinalTree(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
fetchTree(state, pos, args, v, {.isFinal = true});
}
static RegisterPrimOp primop_fetchFinalTree({
.name = "fetchFinalTree",
.args = {"input"},
.fun = prim_fetchFinalTree,
.internal = true,
});
static void fetch(EvalState & state, const PosIdx pos, Value * * args, Value & v,
const std::string & who, bool unpack, std::string name)
{

View file

@ -66,7 +66,7 @@ static void prim_fromTOML(EvalState & state, const PosIdx pos, Value * * args, V
attrs.alloc("_type").mkString("timestamp");
std::ostringstream s;
s << t;
attrs.alloc("value").mkString(s.str());
attrs.alloc("value").mkString(toView(s));
v.mkAttrs(attrs);
} else {
throw std::runtime_error("Dates and times are not supported");

View file

@ -460,7 +460,7 @@ private:
std::ostringstream s;
s << state.positions[v.payload.lambda.fun->pos];
output << " @ " << filterANSIEscapes(s.str());
output << " @ " << filterANSIEscapes(toView(s));
}
} else if (v.isPrimOp()) {
if (v.primOp())

View file

@ -141,7 +141,9 @@ public:
Value * * elems;
ListBuilder(EvalState & state, size_t size);
ListBuilder(ListBuilder && x)
// NOTE: Can be noexcept because we are just copying integral values and
// raw pointers.
ListBuilder(ListBuilder && x) noexcept
: size(x.size)
, inlineElems{x.inlineElems[0], x.inlineElems[1]}
, elems(size <= 2 ? inlineElems : x.elems)

View file

@ -28,7 +28,7 @@ struct NixStringContextElem {
/**
* Plain opaque path to some store object.
*
* Encoded as just the path: <path>.
* Encoded as just the path: `<path>`.
*/
using Opaque = SingleDerivedPath::Opaque;
@ -39,7 +39,7 @@ struct NixStringContextElem {
* also all outputs of all derivations in that closure (including the
* root derivation).
*
* Encoded in the form =<drvPath>.
* Encoded in the form `=<drvPath>`.
*/
struct DrvDeep {
StorePath drvPath;
@ -50,7 +50,7 @@ struct NixStringContextElem {
/**
* Derivation output.
*
* Encoded in the form !<output>!<drvPath>.
* Encoded in the form `!<output>!<drvPath>`.
*/
using Built = SingleDerivedPath::Built;
@ -68,9 +68,9 @@ struct NixStringContextElem {
/**
* Decode a context string, one of:
* - <path>
* - =<path>
* - !<name>!<path>
* - `<path>`
* - `=<path>`
* - `!<name>!<path>`
*
* @param xpSettings Stop-gap to avoid globals during unit tests.
*/