diff options
Diffstat (limited to 'src/libexpr')
-rw-r--r-- | src/libexpr/eval-cache.cc | 4 | ||||
-rw-r--r-- | src/libexpr/eval.cc | 21 | ||||
-rw-r--r-- | src/libexpr/flake/flake.cc | 23 | ||||
-rw-r--r-- | src/libexpr/get-drvs.cc | 4 | ||||
-rw-r--r-- | src/libexpr/json-to-value.cc | 7 | ||||
-rw-r--r-- | src/libexpr/nixexpr.hh | 1 | ||||
-rw-r--r-- | src/libexpr/primops.cc | 98 | ||||
-rw-r--r-- | src/libexpr/primops/fetchTree.cc | 13 | ||||
-rw-r--r-- | src/libexpr/value-to-json.cc | 2 | ||||
-rw-r--r-- | src/libexpr/value.hh | 10 |
10 files changed, 130 insertions, 53 deletions
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(); |