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

Merge remote-tracking branch 'origin/2.29-maintenance' into detsys-main

This commit is contained in:
Eelco Dolstra 2025-05-16 12:48:44 +02:00
commit c20642ac7b
354 changed files with 6768 additions and 3808 deletions

View file

@ -74,7 +74,7 @@ std::pair<Value *, PosIdx> findAlongAttrPath(EvalState & state, const std::strin
auto a = v->attrs()->get(state.symbols.create(attr));
if (!a) {
std::set<std::string> attrNames;
StringSet attrNames;
for (auto & attr : *v->attrs())
attrNames.insert(std::string(state.symbols[attr.name]));

View file

@ -492,7 +492,7 @@ Value & AttrCursor::forceValue()
Suggestions AttrCursor::getSuggestionsForAttr(Symbol name)
{
auto attrNames = getAttrs();
std::set<std::string> strAttrNames;
StringSet strAttrNames;
for (auto & name : attrNames)
strAttrNames.insert(std::string(root->state.symbols[name]));

View file

@ -110,5 +110,6 @@ template class EvalErrorBuilder<UndefinedVarError>;
template class EvalErrorBuilder<MissingArgumentError>;
template class EvalErrorBuilder<InfiniteRecursionError>;
template class EvalErrorBuilder<InvalidPathError>;
template class EvalErrorBuilder<IFDError>;
}

View file

@ -252,7 +252,25 @@ EvalState::EvalState(
makeMountedSourceAccessor(
{
{CanonPath::root, makeEmptySourceAccessor()},
{CanonPath(store->storeDir), makeFSSourceAccessor(dirOf(store->toRealPath(StorePath::dummy)))}
/* In the pure eval case, we can simply require
valid paths. However, in the *impure* eval
case this gets in the way of the union
mechanism, because an invalid access in the
upper layer will *not* be caught by the union
source accessor, but instead abort the entire
lookup.
This happens when the store dir in the
ambient file system has a path (e.g. because
another Nix store there), but the relocated
store does not.
TODO make the various source accessors doing
access control all throw the same type of
exception, and make union source accessor
catch it, so we don't need to do this hack.
*/
{CanonPath(store->storeDir), store->getFSAccessor(settings.pureEval)},
}))
, rootFS(
({
@ -1435,7 +1453,7 @@ void ExprSelect::eval(EvalState & state, Env & env, Value & v)
} else {
state.forceAttrs(*vAttrs, pos, "while selecting an attribute");
if (!(j = vAttrs->attrs()->get(name))) {
std::set<std::string> allAttrNames;
StringSet allAttrNames;
for (auto & attr : *vAttrs->attrs())
allAttrNames.insert(std::string(state.symbols[attr.name]));
auto suggestions = Suggestions::bestMatches(allAttrNames, state.symbols[name]);
@ -1592,7 +1610,7 @@ void EvalState::callFunction(Value & fun, std::span<Value *> args, Value & vRes,
user. */
for (auto & i : *args[0]->attrs())
if (!lambda.formals->has(i.name)) {
std::set<std::string> formalNames;
StringSet formalNames;
for (auto & formal : lambda.formals->formals)
formalNames.insert(std::string(symbols[formal.name]));
auto suggestions = Suggestions::bestMatches(formalNames, symbols[i.name]);

View file

@ -54,6 +54,7 @@ MakeError(TypeError, EvalError);
MakeError(UndefinedVarError, EvalError);
MakeError(MissingArgumentError, EvalError);
MakeError(InfiniteRecursionError, EvalError);
MakeError(IFDError, EvalBaseError);
struct InvalidPathError : public EvalError
{

View file

@ -27,7 +27,12 @@ constexpr size_t conservativeStackReservation = 16;
struct RegisterPrimOp
{
typedef std::vector<PrimOp> PrimOps;
static PrimOps * primOps;
static PrimOps & primOps()
{
static PrimOps primOps;
return primOps;
}
/**
* You can register a constant by passing an arity of 0. fun

View file

@ -81,26 +81,29 @@ public:
class SymbolTable
{
private:
std::unordered_map<std::string_view, std::pair<const std::string *, uint32_t>> symbols;
/**
* Map from string view (backed by ChunkedVector) -> offset into the store.
* ChunkedVector references are never invalidated.
*/
std::unordered_map<std::string_view, uint32_t> symbols;
ChunkedVector<std::string, 8192> store{16};
public:
/**
* converts a string into a symbol.
* Converts a string into a symbol.
*/
Symbol create(std::string_view s)
{
// Most symbols are looked up more than once, so we trade off insertion performance
// for lookup performance.
// TODO: could probably be done more efficiently with transparent Hash and Equals
// on the original implementation using unordered_set
// FIXME: make this thread-safe.
auto it = symbols.find(s);
if (it != symbols.end()) return Symbol(it->second.second + 1);
if (it != symbols.end())
return Symbol(it->second + 1);
const auto & [rawSym, idx] = store.add(std::string(s));
symbols.emplace(rawSym, std::make_pair(&rawSym, idx));
const auto & [rawSym, idx] = store.add(s);
symbols.emplace(rawSym, idx);
return Symbol(idx + 1);
}

View file

@ -1,3 +1,4 @@
%option 8bit
%option reentrant bison-bridge bison-locations
%option align
%option noyywrap

View file

@ -112,6 +112,7 @@ lexer_tab = custom_target(
],
command : [
'flex',
'-Cf', # Use full scanner tables
'--outfile',
'@OUTPUT0@',
'--header-file=' + '@OUTPUT1@',

View file

@ -179,7 +179,12 @@ static Expr * makeCall(PosIdx pos, Expr * fn, Expr * arg) {
%%
start: expr { state->result = $1; };
start: expr {
state->result = $1;
// This parser does not use yynerrs; suppress the warning.
(void) yynerrs;
};
expr: expr_function;

View file

@ -98,7 +98,7 @@ StringMap EvalState::realiseContext(const NixStringContext & context, StorePathS
if (drvs.empty()) return {};
if (isIFD && !settings.enableImportFromDerivation)
error<EvalBaseError>(
error<IFDError>(
"cannot build '%1%' during evaluation because the option 'allow-import-from-derivation' is disabled",
drvs.begin()->to_string(*store)
).debugThrow();
@ -895,18 +895,40 @@ static void prim_ceil(EvalState & state, const PosIdx pos, Value * * args, Value
{
auto value = state.forceFloat(*args[0], args[0]->determinePos(pos),
"while evaluating the first argument passed to builtins.ceil");
v.mkInt(ceil(value));
auto ceilValue = ceil(value);
bool isInt = args[0]->type() == nInt;
constexpr NixFloat int_min = std::numeric_limits<NixInt::Inner>::min(); // power of 2, so that no rounding occurs
if (ceilValue >= int_min && ceilValue < -int_min) {
v.mkInt(ceilValue);
} else if (isInt) {
// a NixInt, e.g. INT64_MAX, can be rounded to -int_min due to the cast to NixFloat
state.error<EvalError>("Due to a bug (see https://github.com/NixOS/nix/issues/12899) the NixInt argument %1% caused undefined behavior in previous Nix versions.\n\tFuture Nix versions might implement the correct behavior.", args[0]->integer().value).atPos(pos).debugThrow();
} else {
state.error<EvalError>("NixFloat argument %1% is not in the range of NixInt", args[0]->fpoint()).atPos(pos).debugThrow();
}
// `forceFloat` casts NixInt to NixFloat, but instead NixInt args shall be returned unmodified
if (isInt) {
auto arg = args[0]->integer();
auto res = v.integer();
if (arg != res) {
state.error<EvalError>("Due to a bug (see https://github.com/NixOS/nix/issues/12899) a loss of precision occured in previous Nix versions because the NixInt argument %1% was rounded to %2%.\n\tFuture Nix versions might implement the correct behavior.", arg, res).atPos(pos).debugThrow();
}
}
}
static RegisterPrimOp primop_ceil({
.name = "__ceil",
.args = {"double"},
.args = {"number"},
.doc = R"(
Converts an IEEE-754 double-precision floating-point number (*double*) to
the next higher integer.
Rounds and converts *number* to the next higher NixInt value if possible, i.e. `ceil *number* >= *number*` and
`ceil *number* - *number* < 1`.
If the datatype is neither an integer nor a "float", an evaluation error will be
thrown.
An evaluation error is thrown, if there exists no such NixInt value `ceil *number*`.
Due to bugs in previous Nix versions an evaluation error might be thrown, if the datatype of *number* is
a NixInt and if `*number* < -9007199254740992` or `*number* > 9007199254740992`.
If the datatype of *number* is neither a NixInt (signed 64-bit integer) nor a NixFloat
(IEEE-754 double-precision floating-point number), an evaluation error will be thrown.
)",
.fun = prim_ceil,
});
@ -914,18 +936,40 @@ static RegisterPrimOp primop_ceil({
static void prim_floor(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
auto value = state.forceFloat(*args[0], args[0]->determinePos(pos), "while evaluating the first argument passed to builtins.floor");
v.mkInt(floor(value));
auto floorValue = floor(value);
bool isInt = args[0]->type() == nInt;
constexpr NixFloat int_min = std::numeric_limits<NixInt::Inner>::min(); // power of 2, so that no rounding occurs
if (floorValue >= int_min && floorValue < -int_min) {
v.mkInt(floorValue);
} else if (isInt) {
// a NixInt, e.g. INT64_MAX, can be rounded to -int_min due to the cast to NixFloat
state.error<EvalError>("Due to a bug (see https://github.com/NixOS/nix/issues/12899) the NixInt argument %1% caused undefined behavior in previous Nix versions.\n\tFuture Nix versions might implement the correct behavior.", args[0]->integer().value).atPos(pos).debugThrow();
} else {
state.error<EvalError>("NixFloat argument %1% is not in the range of NixInt", args[0]->fpoint()).atPos(pos).debugThrow();
}
// `forceFloat` casts NixInt to NixFloat, but instead NixInt args shall be returned unmodified
if (isInt) {
auto arg = args[0]->integer();
auto res = v.integer();
if (arg != res) {
state.error<EvalError>("Due to a bug (see https://github.com/NixOS/nix/issues/12899) a loss of precision occured in previous Nix versions because the NixInt argument %1% was rounded to %2%.\n\tFuture Nix versions might implement the correct behavior.", arg, res).atPos(pos).debugThrow();
}
}
}
static RegisterPrimOp primop_floor({
.name = "__floor",
.args = {"double"},
.args = {"number"},
.doc = R"(
Converts an IEEE-754 double-precision floating-point number (*double*) to
the next lower integer.
Rounds and converts *number* to the next lower NixInt value if possible, i.e. `floor *number* <= *number*` and
`*number* - floor *number* < 1`.
If the datatype is neither an integer nor a "float", an evaluation error will be
thrown.
An evaluation error is thrown, if there exists no such NixInt value `floor *number*`.
Due to bugs in previous Nix versions an evaluation error might be thrown, if the datatype of *number* is
a NixInt and if `*number* < -9007199254740992` or `*number* > 9007199254740992`.
If the datatype of *number* is neither a NixInt (signed 64-bit integer) nor a NixFloat
(IEEE-754 double-precision floating-point number), an evaluation error will be thrown.
)",
.fun = prim_floor,
});
@ -2813,7 +2857,13 @@ static void prim_unsafeGetAttrPos(EvalState & state, const PosIdx pos, Value * *
static RegisterPrimOp primop_unsafeGetAttrPos(PrimOp {
.name = "__unsafeGetAttrPos",
.args = {"s", "set"},
.arity = 2,
.doc = R"(
`unsafeGetAttrPos` returns the position of the attribute named *s*
from *set*. This is used by Nixpkgs to provide location information
in error messages.
)",
.fun = prim_unsafeGetAttrPos,
});
@ -4299,9 +4349,7 @@ struct RegexCache
{
struct State
{
// TODO use C++20 transparent comparison when available
std::unordered_map<std::string_view, std::regex> cache;
std::list<std::string> keys;
std::unordered_map<std::string, std::regex, StringViewHash, std::equal_to<>> cache;
};
Sync<State> state_;
@ -4312,8 +4360,14 @@ struct RegexCache
auto it = state->cache.find(re);
if (it != state->cache.end())
return it->second;
state->keys.emplace_back(re);
return state->cache.emplace(state->keys.back(), std::regex(state->keys.back(), std::regex::extended)).first->second;
/* No std::regex constructor overload from std::string_view, but can be constructed
from a pointer + size or an iterator range. */
return state->cache
.emplace(
std::piecewise_construct,
std::forward_as_tuple(re),
std::forward_as_tuple(/*s=*/re.data(), /*count=*/re.size(), std::regex::extended))
.first->second;
}
};
@ -4697,13 +4751,9 @@ static RegisterPrimOp primop_splitVersion({
*************************************************************/
RegisterPrimOp::PrimOps * RegisterPrimOp::primOps;
RegisterPrimOp::RegisterPrimOp(PrimOp && primOp)
{
if (!primOps) primOps = new PrimOps;
primOps->push_back(std::move(primOp));
primOps().push_back(std::move(primOp));
}
@ -4957,14 +5007,18 @@ void EvalState::createBaseEnv(const EvalSettings & evalSettings)
)",
});
if (RegisterPrimOp::primOps)
for (auto & primOp : *RegisterPrimOp::primOps)
if (experimentalFeatureSettings.isEnabled(primOp.experimentalFeature))
{
auto primOpAdjusted = primOp;
primOpAdjusted.arity = std::max(primOp.args.size(), primOp.arity);
addPrimOp(std::move(primOpAdjusted));
}
for (auto & primOp : RegisterPrimOp::primOps())
if (experimentalFeatureSettings.isEnabled(primOp.experimentalFeature)) {
auto primOpAdjusted = primOp;
primOpAdjusted.arity = std::max(primOp.args.size(), primOp.arity);
addPrimOp(std::move(primOpAdjusted));
}
for (auto & primOp : evalSettings.extraPrimOps) {
auto primOpAdjusted = primOp;
primOpAdjusted.arity = std::max(primOp.args.size(), primOp.arity);
addPrimOp(std::move(primOpAdjusted));
}
for (auto & primOp : evalSettings.extraPrimOps) {
auto primOpAdjusted = primOp;

View file

@ -1,5 +1,5 @@
#include "nix/expr/primops.hh"
#include "nix/store/store-api.hh"
#include "nix/store/store-open.hh"
#include "nix/store/realisation.hh"
#include "nix/store/make-content-addressed.hh"
#include "nix/util/url.hh"

View file

@ -11,6 +11,7 @@ namespace nix {
using json = nlohmann::json;
// TODO: rename. It doesn't print.
json printValueAsJSON(EvalState & state, bool strict,
Value & v, const PosIdx pos, NixStringContext & context, bool copyToStore)
{