aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/libcmd/cmd-profiles.hh2
-rw-r--r--src/libcmd/installable-flake.cc4
-rw-r--r--src/libcmd/installable-value.hh2
-rw-r--r--src/libexpr/eval-cache.cc4
-rw-r--r--src/libexpr/eval.cc21
-rw-r--r--src/libexpr/flake/flake.cc23
-rw-r--r--src/libexpr/get-drvs.cc4
-rw-r--r--src/libexpr/json-to-value.cc7
-rw-r--r--src/libexpr/nixexpr.hh1
-rw-r--r--src/libexpr/primops.cc98
-rw-r--r--src/libexpr/primops/fetchTree.cc13
-rw-r--r--src/libexpr/value-to-json.cc2
-rw-r--r--src/libexpr/value.hh10
-rw-r--r--src/libstore/globals.hh5
14 files changed, 138 insertions, 58 deletions
diff --git a/src/libcmd/cmd-profiles.hh b/src/libcmd/cmd-profiles.hh
index 2185daa34..766a1d82b 100644
--- a/src/libcmd/cmd-profiles.hh
+++ b/src/libcmd/cmd-profiles.hh
@@ -37,7 +37,7 @@ struct ProfileElement
StorePathSet storePaths;
std::optional<ProfileElementSource> source;
bool active = true;
- int priority = DEFAULT_PRIORITY;
+ NixInt::Inner priority = DEFAULT_PRIORITY;
std::string identifier() const;
diff --git a/src/libcmd/installable-flake.cc b/src/libcmd/installable-flake.cc
index b1ebd339e..120bc666f 100644
--- a/src/libcmd/installable-flake.cc
+++ b/src/libcmd/installable-flake.cc
@@ -116,12 +116,12 @@ DerivedPathsWithInfo InstallableFlake::toDerivedPaths()
auto drvPath = attr->forceDerivation();
- std::optional<NixInt> priority;
+ std::optional<NixInt::Inner> priority;
if (attr->maybeGetAttr(state->sOutputSpecified)) {
} else if (auto aMeta = attr->maybeGetAttr(state->sMeta)) {
if (auto aPriority = aMeta->maybeGetAttr("priority"))
- priority = aPriority->getInt();
+ priority = aPriority->getInt().value;
}
return {{
diff --git a/src/libcmd/installable-value.hh b/src/libcmd/installable-value.hh
index 3138ce8ec..b9b54fa2e 100644
--- a/src/libcmd/installable-value.hh
+++ b/src/libcmd/installable-value.hh
@@ -40,7 +40,7 @@ struct ExtraPathInfoValue : ExtraPathInfo
/**
* An optional priority for use with "build envs". See Package
*/
- std::optional<NixInt> priority;
+ std::optional<NixInt::Inner> priority;
/**
* The attribute path associated with this value. The idea is
diff --git a/src/libexpr/eval-cache.cc b/src/libexpr/eval-cache.cc
index 90fbfa308..9d35bab81 100644
--- a/src/libexpr/eval-cache.cc
+++ b/src/libexpr/eval-cache.cc
@@ -306,7 +306,7 @@ struct AttrDb
case AttrType::Bool:
return {{rowId, queryAttribute.getInt(2) != 0}};
case AttrType::Int:
- return {{rowId, int_t{queryAttribute.getInt(2)}}};
+ return {{rowId, int_t{NixInt{queryAttribute.getInt(2)}}}};
case AttrType::ListOfStrings:
return {{rowId, tokenizeString<std::vector<std::string>>(queryAttribute.getStr(2), "\t")}};
case AttrType::Missing:
@@ -449,7 +449,7 @@ Value & AttrCursor::forceValue()
else if (v.type() == nBool)
cachedValue = {root->db->setBool(getKey(), v.boolean), v.boolean};
else if (v.type() == nInt)
- cachedValue = {root->db->setInt(getKey(), v.integer), int_t{v.integer}};
+ cachedValue = {root->db->setInt(getKey(), v.integer.value), int_t{v.integer}};
else if (v.type() == nAttrs)
; // FIXME: do something?
else
diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc
index d9bdb0d2c..2ef3abc52 100644
--- a/src/libexpr/eval.cc
+++ b/src/libexpr/eval.cc
@@ -2141,7 +2141,7 @@ void ExprConcatStrings::eval(EvalState & state, Env & env, Value & v)
NixStringContext context;
std::vector<BackedStringView> s;
size_t sSize = 0;
- NixInt n = 0;
+ NixInt n{0};
NixFloat nf = 0;
bool first = !forceString;
@@ -2185,17 +2185,22 @@ void ExprConcatStrings::eval(EvalState & state, Env & env, Value & v)
if (firstType == nInt) {
if (vTmp.type() == nInt) {
- n += vTmp.integer;
+ auto newN = n + vTmp.integer;
+ if (auto checked = newN.valueChecked(); checked.has_value()) {
+ n = NixInt(*checked);
+ } else {
+ state.error<EvalError>("integer overflow in adding %1% + %2%", n, vTmp.integer).atPos(i_pos).debugThrow();
+ }
} else if (vTmp.type() == nFloat) {
// Upgrade the type from int to float;
firstType = nFloat;
- nf = n;
+ nf = n.value;
nf += vTmp.fpoint;
} else
state.error<EvalError>("cannot add %1% to an integer", showType(vTmp)).atPos(i_pos).withFrame(env, *this).debugThrow();
} else if (firstType == nFloat) {
if (vTmp.type() == nInt) {
- nf += vTmp.integer;
+ nf += vTmp.integer.value;
} else if (vTmp.type() == nFloat) {
nf += vTmp.fpoint;
} else
@@ -2320,7 +2325,7 @@ NixFloat EvalState::forceFloat(Value & v, const PosIdx pos, std::string_view err
try {
forceValue(v, pos);
if (v.type() == nInt)
- return v.integer;
+ return v.integer.value;
else if (v.type() != nFloat)
error<TypeError>(
"expected a float but found %1%: %2%",
@@ -2507,7 +2512,7 @@ BackedStringView EvalState::coerceToString(
shell scripting convenience, just like `null'. */
if (v.type() == nBool && v.boolean) return "1";
if (v.type() == nBool && !v.boolean) return "";
- if (v.type() == nInt) return std::to_string(v.integer);
+ if (v.type() == nInt) return std::to_string(v.integer.value);
if (v.type() == nFloat) return std::to_string(v.fpoint);
if (v.type() == nNull) return "";
@@ -2651,9 +2656,9 @@ bool EvalState::eqValues(Value & v1, Value & v2, const PosIdx pos, std::string_v
// Special case type-compatibility between float and int
if (v1.type() == nInt && v2.type() == nFloat)
- return v1.integer == v2.fpoint;
+ return v1.integer.value == v2.fpoint;
if (v1.type() == nFloat && v2.type() == nInt)
- return v1.fpoint == v2.integer;
+ return v1.fpoint == v2.integer.value;
// All other types are not compatible with each other.
if (v1.type() != v2.type()) return false;
diff --git a/src/libexpr/flake/flake.cc b/src/libexpr/flake/flake.cc
index ac50351ad..7379fc8fb 100644
--- a/src/libexpr/flake/flake.cc
+++ b/src/libexpr/flake/flake.cc
@@ -137,9 +137,16 @@ static FlakeInput parseFlakeInput(EvalState & state,
case nBool:
attrs.emplace(state.symbols[attr.name], Explicit<bool> { attr.value->boolean });
break;
- case nInt:
- attrs.emplace(state.symbols[attr.name], (long unsigned int)attr.value->integer);
+ case nInt: {
+ auto intValue = attr.value->integer.value;
+
+ if (intValue < 0) {
+ state.error<EvalError>("negative value given for flake input attribute %1%: %2%", state.symbols[attr.name], intValue).debugThrow();
+ }
+ uint64_t asUnsigned = intValue;
+ attrs.emplace(state.symbols[attr.name], asUnsigned);
break;
+ }
default:
state.error<TypeError>("flake input attribute '%s' is %s while a string, Boolean, or integer is expected",
state.symbols[attr.name], showType(*attr.value)).debugThrow();
@@ -284,7 +291,7 @@ static Flake getFlake(
else if (setting.value->type() == nInt)
flake.config.settings.emplace(
state.symbols[setting.name],
- state.forceInt(*setting.value, setting.pos, ""));
+ state.forceInt(*setting.value, setting.pos, "").value);
else if (setting.value->type() == nBool)
flake.config.settings.emplace(
state.symbols[setting.name],
@@ -873,8 +880,14 @@ static void prim_flakeRefToString(
for (const auto & attr : *args[0]->attrs) {
auto t = attr.value->type();
if (t == nInt) {
- attrs.emplace(state.symbols[attr.name],
- (uint64_t) attr.value->integer);
+ auto intValue = attr.value->integer.value;
+
+ if (intValue < 0) {
+ state.error<EvalError>("negative value given for flake ref attr %1%: %2%", state.symbols[attr.name], intValue).atPos(pos).debugThrow();
+ }
+ uint64_t asUnsigned = intValue;
+
+ attrs.emplace(state.symbols[attr.name], asUnsigned);
} else if (t == nBool) {
attrs.emplace(state.symbols[attr.name],
Explicit<bool> { attr.value->boolean });
diff --git a/src/libexpr/get-drvs.cc b/src/libexpr/get-drvs.cc
index b199cd09e..8e3969aac 100644
--- a/src/libexpr/get-drvs.cc
+++ b/src/libexpr/get-drvs.cc
@@ -241,8 +241,8 @@ NixInt DrvInfo::queryMetaInt(const std::string & name, NixInt def)
if (v->type() == nString) {
/* Backwards compatibility with before we had support for
integer meta fields. */
- if (auto n = string2Int<NixInt>(v->string.s))
- return *n;
+ if (auto n = string2Int<NixInt::Inner>(v->string.s))
+ return NixInt{*n};
}
return def;
}
diff --git a/src/libexpr/json-to-value.cc b/src/libexpr/json-to-value.cc
index 2d12c47c5..b69216a48 100644
--- a/src/libexpr/json-to-value.cc
+++ b/src/libexpr/json-to-value.cc
@@ -2,6 +2,7 @@
#include "value.hh"
#include "eval.hh"
+#include <limits>
#include <variant>
#include <nlohmann/json.hpp>
@@ -102,8 +103,12 @@ public:
return true;
}
- bool number_unsigned(number_unsigned_t val)
+ bool number_unsigned(number_unsigned_t val_)
{
+ if (val_ > std::numeric_limits<NixInt::Inner>::max()) {
+ throw Error("unsigned json number %1% outside of Nix integer range", val_);
+ }
+ NixInt::Inner val = val_;
rs->value(state).mkInt(val);
rs->add();
return true;
diff --git a/src/libexpr/nixexpr.hh b/src/libexpr/nixexpr.hh
index 25ba94595..49e2e4147 100644
--- a/src/libexpr/nixexpr.hh
+++ b/src/libexpr/nixexpr.hh
@@ -75,6 +75,7 @@ struct ExprInt : Expr
NixInt n;
Value v;
ExprInt(NixInt n) : n(n) { v.mkInt(n); };
+ ExprInt(NixInt::Inner n) : n(n) { v.mkInt(n); };
Value * maybeThunk(EvalState & state, Env & env) override;
COMMON_METHODS
};
diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc
index 80c64232f..471ddc4f3 100644
--- a/src/libexpr/primops.cc
+++ b/src/libexpr/primops.cc
@@ -582,9 +582,9 @@ struct CompareValues
{
try {
if (v1->type() == nFloat && v2->type() == nInt)
- return v1->fpoint < v2->integer;
+ return v1->fpoint < v2->integer.value;
if (v1->type() == nInt && v2->type() == nFloat)
- return v1->integer < v2->fpoint;
+ return v1->integer.value < v2->fpoint;
if (v1->type() != v2->type())
state.error<EvalError>("cannot compare %s with %s", showType(*v1), showType(*v2)).debugThrow();
// Allow selecting a subset of enum values
@@ -2514,13 +2514,13 @@ static struct LazyPosAcessors {
PrimOp primop_lineOfPos{
.arity = 1,
.fun = [] (EvalState & state, PosIdx pos, Value * * args, Value & v) {
- v.mkInt(state.positions[PosIdx(args[0]->integer)].line);
+ v.mkInt(state.positions[PosIdx(args[0]->integer.value)].line);
}
};
PrimOp primop_columnOfPos{
.arity = 1,
.fun = [] (EvalState & state, PosIdx pos, Value * * args, Value & v) {
- v.mkInt(state.positions[PosIdx(args[0]->integer)].column);
+ v.mkInt(state.positions[PosIdx(args[0]->integer.value)].column);
}
};
@@ -2990,7 +2990,8 @@ static void elemAt(EvalState & state, const PosIdx pos, Value & list, int n, Val
/* Return the n-1'th element of a list. */
static void prim_elemAt(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
- elemAt(state, pos, *args[0], state.forceInt(*args[1], pos, "while evaluating the second argument passed to builtins.elemAt"), v);
+ NixInt::Inner elem = state.forceInt(*args[1], pos, "while evaluating the second argument passed to builtins.elemAt").value;
+ elemAt(state, pos, *args[0], elem, v);
}
static RegisterPrimOp primop_elemAt({
@@ -3274,7 +3275,7 @@ static RegisterPrimOp primop_all({
static void prim_genList(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
- auto len = state.forceInt(*args[1], pos, "while evaluating the second argument passed to builtins.genList");
+ auto len = state.forceInt(*args[1], pos, "while evaluating the second argument passed to builtins.genList").value;
if (len < 0)
state.error<EvalError>("cannot create list of size %1%", len).atPos(pos).debugThrow();
@@ -3284,7 +3285,7 @@ static void prim_genList(EvalState & state, const PosIdx pos, Value * * args, Va
state.forceFunction(*args[0], noPos, "while evaluating the first argument passed to builtins.genList");
state.mkList(v, len);
- for (unsigned int n = 0; n < (unsigned int) len; ++n) {
+ for (size_t n = 0; n < len; ++n) {
auto arg = state.allocValue();
arg->mkInt(n);
(v.listElems()[n] = state.allocValue())->mkApp(args[0], arg);
@@ -3534,9 +3535,17 @@ static void prim_add(EvalState & state, const PosIdx pos, Value * * args, Value
if (args[0]->type() == nFloat || args[1]->type() == nFloat)
v.mkFloat(state.forceFloat(*args[0], pos, "while evaluating the first argument of the addition")
+ state.forceFloat(*args[1], pos, "while evaluating the second argument of the addition"));
- else
- v.mkInt( state.forceInt(*args[0], pos, "while evaluating the first argument of the addition")
- + state.forceInt(*args[1], pos, "while evaluating the second argument of the addition"));
+ else {
+ auto i1 = state.forceInt(*args[0], pos, "while evaluating the first argument of the addition");
+ auto i2 = state.forceInt(*args[1], pos, "while evaluating the second argument of the addition");
+
+ auto result_ = i1 + i2;
+ if (auto result = result_.valueChecked(); result.has_value()) {
+ v.mkInt(*result);
+ } else {
+ state.error<EvalError>("integer overflow in adding %1% + %2%", i1, i2).atPos(pos).debugThrow();
+ }
+ }
}
static RegisterPrimOp primop_add({
@@ -3555,9 +3564,18 @@ static void prim_sub(EvalState & state, const PosIdx pos, Value * * args, Value
if (args[0]->type() == nFloat || args[1]->type() == nFloat)
v.mkFloat(state.forceFloat(*args[0], pos, "while evaluating the first argument of the subtraction")
- state.forceFloat(*args[1], pos, "while evaluating the second argument of the subtraction"));
- else
- v.mkInt( state.forceInt(*args[0], pos, "while evaluating the first argument of the subtraction")
- - state.forceInt(*args[1], pos, "while evaluating the second argument of the subtraction"));
+ else {
+ auto i1 = state.forceInt(*args[0], pos, "while evaluating the first argument of the subtraction");
+ auto i2 = state.forceInt(*args[1], pos, "while evaluating the second argument of the subtraction");
+
+ auto result_ = i1 - i2;
+
+ if (auto result = result_.valueChecked(); result.has_value()) {
+ v.mkInt(*result);
+ } else {
+ state.error<EvalError>("integer overflow in subtracting %1% - %2%", i1, i2).atPos(pos).debugThrow();
+ }
+ }
}
static RegisterPrimOp primop_sub({
@@ -3576,9 +3594,18 @@ static void prim_mul(EvalState & state, const PosIdx pos, Value * * args, Value
if (args[0]->type() == nFloat || args[1]->type() == nFloat)
v.mkFloat(state.forceFloat(*args[0], pos, "while evaluating the first of the multiplication")
* state.forceFloat(*args[1], pos, "while evaluating the second argument of the multiplication"));
- else
- v.mkInt( state.forceInt(*args[0], pos, "while evaluating the first argument of the multiplication")
- * state.forceInt(*args[1], pos, "while evaluating the second argument of the multiplication"));
+ else {
+ auto i1 = state.forceInt(*args[0], pos, "while evaluating the first argument of the multiplication");
+ auto i2 = state.forceInt(*args[1], pos, "while evaluating the second argument of the multiplication");
+
+ auto result_ = i1 * i2;
+
+ if (auto result = result_.valueChecked(); result.has_value()) {
+ v.mkInt(*result);
+ } else {
+ state.error<EvalError>("integer overflow in multiplying %1% * %2%", i1, i2).atPos(pos).debugThrow();
+ }
+ }
}
static RegisterPrimOp primop_mul({
@@ -3605,10 +3632,12 @@ static void prim_div(EvalState & state, const PosIdx pos, Value * * args, Value
NixInt i1 = state.forceInt(*args[0], pos, "while evaluating the first operand of the division");
NixInt i2 = state.forceInt(*args[1], pos, "while evaluating the second operand of the division");
/* Avoid division overflow as it might raise SIGFPE. */
- if (i1 == std::numeric_limits<NixInt>::min() && i2 == -1)
- state.error<EvalError>("overflow in integer division").atPos(pos).debugThrow();
-
- v.mkInt(i1 / i2);
+ auto result_ = i1 / i2;
+ if (auto result = result_.valueChecked(); result.has_value()) {
+ v.mkInt(*result);
+ } else {
+ state.error<EvalError>("integer overflow in dividing %1% / %2%", i1, i2).atPos(pos).debugThrow();
+ }
}
}
@@ -3623,8 +3652,9 @@ static RegisterPrimOp primop_div({
static void prim_bitAnd(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
- v.mkInt(state.forceInt(*args[0], pos, "while evaluating the first argument passed to builtins.bitAnd")
- & state.forceInt(*args[1], pos, "while evaluating the second argument passed to builtins.bitAnd"));
+ auto i1 = state.forceInt(*args[0], pos, "while evaluating the first argument passed to builtins.bitAnd");
+ auto i2 = state.forceInt(*args[1], pos, "while evaluating the second argument passed to builtins.bitAnd");
+ v.mkInt(i1.value & i2.value);
}
static RegisterPrimOp primop_bitAnd({
@@ -3638,8 +3668,10 @@ static RegisterPrimOp primop_bitAnd({
static void prim_bitOr(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
- v.mkInt(state.forceInt(*args[0], pos, "while evaluating the first argument passed to builtins.bitOr")
- | state.forceInt(*args[1], pos, "while evaluating the second argument passed to builtins.bitOr"));
+ auto i1 = state.forceInt(*args[0], pos, "while evaluating the first argument passed to builtins.bitOr");
+ auto i2 = state.forceInt(*args[1], pos, "while evaluating the second argument passed to builtins.bitOr");
+
+ v.mkInt(i1.value | i2.value);
}
static RegisterPrimOp primop_bitOr({
@@ -3653,8 +3685,10 @@ static RegisterPrimOp primop_bitOr({
static void prim_bitXor(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
- v.mkInt(state.forceInt(*args[0], pos, "while evaluating the first argument passed to builtins.bitXor")
- ^ state.forceInt(*args[1], pos, "while evaluating the second argument passed to builtins.bitXor"));
+ auto i1 = state.forceInt(*args[0], pos, "while evaluating the first argument passed to builtins.bitXor");
+ auto i2 = state.forceInt(*args[1], pos, "while evaluating the second argument passed to builtins.bitXor");
+
+ v.mkInt(i1.value ^ i2.value);
}
static RegisterPrimOp primop_bitXor({
@@ -3734,13 +3768,19 @@ static RegisterPrimOp primop_toString({
non-negative. */
static void prim_substring(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
- int start = state.forceInt(*args[0], pos, "while evaluating the first argument (the start offset) passed to builtins.substring");
+ NixInt::Inner start = state.forceInt(*args[0], pos, "while evaluating the first argument (the start offset) passed to builtins.substring").value;
if (start < 0)
state.error<EvalError>("negative start position in 'substring'").atPos(pos).debugThrow();
- int len = state.forceInt(*args[1], pos, "while evaluating the second argument (the substring length) passed to builtins.substring");
+ NixInt::Inner len = state.forceInt(*args[1], pos, "while evaluating the second argument (the substring length) passed to builtins.substring").value;
+
+ // Negative length may be idiomatically passed to builtins.substring to get
+ // the tail of the string.
+ if (len < 0) {
+ len = std::numeric_limits<NixInt::Inner>::max();
+ }
// Special-case on empty substring to avoid O(n) strlen
// This allows for the use of empty substrings to efficently capture string context
@@ -3782,7 +3822,7 @@ static void prim_stringLength(EvalState & state, const PosIdx pos, Value * * arg
{
NixStringContext context;
auto s = state.coerceToString(pos, *args[0], context, "while evaluating the argument passed to builtins.stringLength");
- v.mkInt(s->size());
+ v.mkInt(NixInt::Inner(s->size()));
}
static RegisterPrimOp primop_stringLength({
diff --git a/src/libexpr/primops/fetchTree.cc b/src/libexpr/primops/fetchTree.cc
index 2e58be45d..e289fe9ca 100644
--- a/src/libexpr/primops/fetchTree.cc
+++ b/src/libexpr/primops/fetchTree.cc
@@ -148,9 +148,16 @@ static void fetchTree(
}
else if (attr.value->type() == nBool)
attrs.emplace(state.symbols[attr.name], Explicit<bool>{attr.value->boolean});
- else if (attr.value->type() == nInt)
- attrs.emplace(state.symbols[attr.name], uint64_t(attr.value->integer));
- else
+ else if (attr.value->type() == nInt) {
+ auto intValue = attr.value->integer.value;
+
+ if (intValue < 0) {
+ state.error<EvalError>("negative value given for fetchTree attr %1%: %2%", state.symbols[attr.name], intValue).atPos(pos).debugThrow();
+ }
+ unsigned long asUnsigned = intValue;
+
+ attrs.emplace(state.symbols[attr.name], asUnsigned);
+ } else
state.error<TypeError>("fetchTree argument '%s' is %s while a string, Boolean or integer is expected",
state.symbols[attr.name], showType(*attr.value)).debugThrow();
}
diff --git a/src/libexpr/value-to-json.cc b/src/libexpr/value-to-json.cc
index 5743d9057..74e729a82 100644
--- a/src/libexpr/value-to-json.cc
+++ b/src/libexpr/value-to-json.cc
@@ -22,7 +22,7 @@ json printValueAsJSON(EvalState & state, bool strict,
switch (v.type()) {
case nInt:
- out = v.integer;
+ out = v.integer.value;
break;
case nBool:
diff --git a/src/libexpr/value.hh b/src/libexpr/value.hh
index dcef82e40..5c57257be 100644
--- a/src/libexpr/value.hh
+++ b/src/libexpr/value.hh
@@ -9,6 +9,7 @@
#include "input-accessor.hh"
#include "source-path.hh"
#include "print-options.hh"
+#include "checked-arithmetic.hh"
#if HAVE_BOEHMGC
#include <gc/gc_allocator.h>
@@ -73,8 +74,8 @@ class EvalState;
class XMLWriter;
class Printer;
-typedef int64_t NixInt;
-typedef double NixFloat;
+using NixInt = checked::Checked<int64_t>;
+using NixFloat = double;
/**
* External values must descend from ExternalValueBase, so that
@@ -254,6 +255,11 @@ public:
app.left = app.right = 0;
}
+ inline void mkInt(NixInt::Inner n)
+ {
+ mkInt(NixInt{n});
+ }
+
inline void mkInt(NixInt n)
{
clearValue();
diff --git a/src/libstore/globals.hh b/src/libstore/globals.hh
index d360e5c5e..1aeb462d4 100644
--- a/src/libstore/globals.hh
+++ b/src/libstore/globals.hh
@@ -981,7 +981,10 @@ public:
)"};
Setting<uint64_t> maxFree{
- this, std::numeric_limits<uint64_t>::max(), "max-free",
+ // n.b. this is deliberately int64 max rather than uint64 max because
+ // this goes through the Nix language JSON parser and thus needs to be
+ // representable in Nix language integers.
+ this, std::numeric_limits<int64_t>::max(), "max-free",
R"(
When a garbage collection is triggered by the `min-free` option, it
stops as soon as `max-free` bytes are available. The default is