language: cleanly ban integer overflows
This also bans various sneaking of negative numbers from the language into unsuspecting builtins as was exposed while auditing the consequences of changing the Nix language integer type to a newtype. It's unlikely that this change comprehensively ensures correctness when passing integers out of the Nix language and we should probably add a checked-narrowing function or something similar, but that's out of scope for the immediate change. During the development of this I found a few fun facts about the language: - You could overflow integers by converting from unsigned JSON values. - You could overflow unsigned integers by converting negative numbers into them when going into Nix config, into fetchTree, and into flake inputs. The flake inputs and Nix config cannot actually be tested properly since they both ban thunks, however, we put in checks anyway because it's possible these could somehow be used to do such shenanigans some other way. Note that Lix has banned Nix language integer overflows since the very first public beta, but threw a SIGILL about them because we run with -fsanitize=signed-overflow -fsanitize-undefined-trap-on-error in production builds. Since the Nix language uses signed integers, overflow was simply undefined behaviour, and since we defined that to trap, it did. Trapping on it was a bad UX, but we didn't even entirely notice that we had done this at all until it was reported as a bug a couple of months later (which is, to be fair, that flag working as intended), and it's got enough production time that, aside from code that is IMHO buggy (and which is, in any case, not in nixpkgs) such as https://git.lix.systems/lix-project/lix/issues/445, we don't think anyone doing anything reasonable actually depends on wrapping overflow. Even for weird use cases such as doing funny bit crimes, it doesn't make sense IMO to have wrapping behaviour, since two's complement arithmetic overflow behaviour is so *aggressively* not what you want for *any* kind of mathematics/algorithms. The Nix language exists for package management, a domain where bit crimes are already only dubiously in scope to begin with, and it makes a lot more sense for that domain for the integers to never lose precision, either by throwing errors if they would, or by being arbitrary-precision. This change will be ported to CppNix as well, to maintain language consistency. Fixes: https://git.lix.systems/lix-project/lix/issues/423 Change-Id: I51f253840c4af2ea5422b8a420aa5fafbf8fae75
This commit is contained in:
parent
f9641b9efd
commit
917c9bdee7
29 changed files with 254 additions and 59 deletions
|
@ -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;
|
||||
|
||||
|
|
|
@ -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 {{
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 });
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
};
|
||||
|
|
|
@ -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({
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
error:
|
||||
… while calling the 'fetchTree' builtin
|
||||
at /pwd/lang/eval-fail-fetchTree-negative.nix:1:1:
|
||||
1| builtins.fetchTree {
|
||||
| ^
|
||||
2| type = "file";
|
||||
|
||||
error: negative value given for fetchTree attr owner: -1
|
5
tests/functional/lang/eval-fail-fetchTree-negative.nix
Normal file
5
tests/functional/lang/eval-fail-fetchTree-negative.nix
Normal file
|
@ -0,0 +1,5 @@
|
|||
builtins.fetchTree {
|
||||
type = "file";
|
||||
url = "file://eval-fail-fetchTree-negative.nix";
|
||||
owner = -1;
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
error:
|
||||
… while calling the 'seq' builtin
|
||||
at /pwd/lang/eval-fail-flake-ref-to-string-negative-integer.nix:1:16:
|
||||
1| let n = -1; in builtins.seq n (builtins.flakeRefToString {
|
||||
| ^
|
||||
2| type = "github";
|
||||
|
||||
… while calling the 'flakeRefToString' builtin
|
||||
at /pwd/lang/eval-fail-flake-ref-to-string-negative-integer.nix:1:32:
|
||||
1| let n = -1; in builtins.seq n (builtins.flakeRefToString {
|
||||
| ^
|
||||
2| type = "github";
|
||||
|
||||
error: negative value given for flake ref attr repo: -1
|
|
@ -0,0 +1,7 @@
|
|||
let n = -1; in builtins.seq n (builtins.flakeRefToString {
|
||||
type = "github";
|
||||
owner = "NixOS";
|
||||
repo = n;
|
||||
ref = "23.05";
|
||||
dir = "lib";
|
||||
})
|
|
@ -0,0 +1,8 @@
|
|||
error:
|
||||
… while calling the 'fromJSON' builtin
|
||||
at /pwd/lang/eval-fail-fromJSON-overflowing.nix:1:1:
|
||||
1| builtins.fromJSON ''{"attr": 18446744073709551615}''
|
||||
| ^
|
||||
2|
|
||||
|
||||
error: unsigned json number 18446744073709551615 outside of Nix integer range
|
1
tests/functional/lang/eval-fail-fromJSON-overflowing.nix
Normal file
1
tests/functional/lang/eval-fail-fromJSON-overflowing.nix
Normal file
|
@ -0,0 +1 @@
|
|||
builtins.fromJSON ''{"attr": 18446744073709551615}''
|
6
tests/functional/lang/eval-fail-overflowing-add.err.exp
Normal file
6
tests/functional/lang/eval-fail-overflowing-add.err.exp
Normal file
|
@ -0,0 +1,6 @@
|
|||
error: integer overflow in adding 9223372036854775807 + 1
|
||||
at /pwd/lang/eval-fail-overflowing-add.nix:4:8:
|
||||
3| b = 1;
|
||||
4| in a + b
|
||||
| ^
|
||||
5|
|
4
tests/functional/lang/eval-fail-overflowing-add.nix
Normal file
4
tests/functional/lang/eval-fail-overflowing-add.nix
Normal file
|
@ -0,0 +1,4 @@
|
|||
let
|
||||
a = 9223372036854775807;
|
||||
b = 1;
|
||||
in a + b
|
23
tests/functional/lang/eval-fail-overflowing-div.err.exp
Normal file
23
tests/functional/lang/eval-fail-overflowing-div.err.exp
Normal file
|
@ -0,0 +1,23 @@
|
|||
error:
|
||||
… while calling the 'seq' builtin
|
||||
at /pwd/lang/eval-fail-overflowing-div.nix:7:4:
|
||||
6| b = -1;
|
||||
7| in builtins.seq intMin (builtins.seq b (intMin / b))
|
||||
| ^
|
||||
8|
|
||||
|
||||
… while calling the 'seq' builtin
|
||||
at /pwd/lang/eval-fail-overflowing-div.nix:7:25:
|
||||
6| b = -1;
|
||||
7| in builtins.seq intMin (builtins.seq b (intMin / b))
|
||||
| ^
|
||||
8|
|
||||
|
||||
… while calling the 'div' builtin
|
||||
at /pwd/lang/eval-fail-overflowing-div.nix:7:48:
|
||||
6| b = -1;
|
||||
7| in builtins.seq intMin (builtins.seq b (intMin / b))
|
||||
| ^
|
||||
8|
|
||||
|
||||
error: integer overflow in dividing -9223372036854775808 / -1
|
7
tests/functional/lang/eval-fail-overflowing-div.nix
Normal file
7
tests/functional/lang/eval-fail-overflowing-div.nix
Normal file
|
@ -0,0 +1,7 @@
|
|||
let
|
||||
# lol, this has to be written as an expression like this because negative
|
||||
# numbers use unary negation rather than parsing directly, and 2**63 is out
|
||||
# of range
|
||||
intMin = -9223372036854775807 - 1;
|
||||
b = -1;
|
||||
in builtins.seq intMin (builtins.seq b (intMin / b))
|
16
tests/functional/lang/eval-fail-overflowing-mul.err.exp
Normal file
16
tests/functional/lang/eval-fail-overflowing-mul.err.exp
Normal file
|
@ -0,0 +1,16 @@
|
|||
error:
|
||||
… while calling the 'mul' builtin
|
||||
at /pwd/lang/eval-fail-overflowing-mul.nix:3:10:
|
||||
2| a = 4294967297;
|
||||
3| in a * a * a
|
||||
| ^
|
||||
4|
|
||||
|
||||
… while calling the 'mul' builtin
|
||||
at /pwd/lang/eval-fail-overflowing-mul.nix:3:6:
|
||||
2| a = 4294967297;
|
||||
3| in a * a * a
|
||||
| ^
|
||||
4|
|
||||
|
||||
error: integer overflow in multiplying 4294967297 * 4294967297
|
3
tests/functional/lang/eval-fail-overflowing-mul.nix
Normal file
3
tests/functional/lang/eval-fail-overflowing-mul.nix
Normal file
|
@ -0,0 +1,3 @@
|
|||
let
|
||||
a = 4294967297;
|
||||
in a * a * a
|
9
tests/functional/lang/eval-fail-overflowing-sub.err.exp
Normal file
9
tests/functional/lang/eval-fail-overflowing-sub.err.exp
Normal file
|
@ -0,0 +1,9 @@
|
|||
error:
|
||||
… while calling the 'sub' builtin
|
||||
at /pwd/lang/eval-fail-overflowing-sub.nix:4:6:
|
||||
3| b = 2;
|
||||
4| in a - b
|
||||
| ^
|
||||
5|
|
||||
|
||||
error: integer overflow in subtracting -9223372036854775807 - 2
|
4
tests/functional/lang/eval-fail-overflowing-sub.nix
Normal file
4
tests/functional/lang/eval-fail-overflowing-sub.nix
Normal file
|
@ -0,0 +1,4 @@
|
|||
let
|
||||
a = -9223372036854775807;
|
||||
b = 2;
|
||||
in a - b
|
|
@ -77,7 +77,7 @@ namespace nix {
|
|||
if (arg.type() != nInt) {
|
||||
return false;
|
||||
}
|
||||
return arg.integer == v;
|
||||
return arg.integer.value == v;
|
||||
}
|
||||
|
||||
MATCHER_P(IsFloatEq, v, fmt("The float is equal to \"%1%\"", v)) {
|
||||
|
|
Loading…
Reference in a new issue