1
0
Fork 0
mirror of https://github.com/NixOS/nix synced 2025-07-06 21:41:48 +02:00

Merge pull request #11188 from lf-/jade/kill-int-overflow

Ban integer overflow in the Nix language
This commit is contained in:
Robert Hensing 2024-08-11 04:24:16 +02:00 committed by GitHub
commit 18485d2d53
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
40 changed files with 707 additions and 81 deletions

View file

@ -104,12 +104,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 {{

View file

@ -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

View file

@ -306,7 +306,7 @@ int64_t nix_get_int(nix_c_context * context, const nix_value * value)
try {
auto & v = check_value_in(value);
assert(v.type() == nix::nInt);
return v.integer();
return v.integer().value;
}
NIXC_CATCH_ERRS_RES(0);
}

View file

@ -328,7 +328,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:
@ -471,7 +471,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

View file

@ -1979,7 +1979,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;
@ -2023,17 +2023,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
@ -2158,7 +2163,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%",
@ -2345,7 +2350,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 "";
@ -2728,9 +2733,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;

View file

@ -246,8 +246,8 @@ NixInt PackageInfo::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->c_str()))
return *n;
if (auto n = string2Int<NixInt::Inner>(v->c_str()))
return NixInt{*n};
}
return def;
}

View file

@ -2,6 +2,7 @@
#include "value.hh"
#include "eval.hh"
#include <limits>
#include <variant>
#include <nlohmann/json.hpp>
@ -101,8 +102,12 @@ public:
return true;
}
bool number_unsigned(number_unsigned_t val) override
bool number_unsigned(number_unsigned_t val_) override
{
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;

View file

@ -138,7 +138,7 @@ or { return OR_KW; }
{INT} { errno = 0;
std::optional<int64_t> numMay = string2Int<int64_t>(yytext);
if (numMay.has_value()) {
yylval->n = *numMay;
yylval->n = NixInt{*numMay};
} else {
throw ParseError(ErrorInfo{
.msg = HintFmt("invalid integer '%1%'", yytext),

View file

@ -107,6 +107,7 @@ struct ExprInt : Expr
{
Value v;
ExprInt(NixInt n) { v.mkInt(n); };
ExprInt(NixInt::Inner n) { v.mkInt(n); };
Value * maybeThunk(EvalState & state, Env & env) override;
COMMON_METHODS
};

View file

@ -587,9 +587,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
@ -2762,13 +2762,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);
}
};
@ -3244,7 +3244,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({
@ -3538,10 +3539,12 @@ 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();
if (len_ < 0)
state.error<EvalError>("cannot create list of size %1%", len_).atPos(pos).debugThrow();
size_t len = size_t(len_);
// More strict than striclty (!) necessary, but acceptable
// as evaluating map without accessing any values makes little sense.
@ -3798,9 +3801,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({
@ -3819,9 +3830,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({
@ -3840,9 +3860,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({
@ -3869,10 +3898,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();
}
}
}
@ -3887,8 +3918,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({
@ -3902,8 +3934,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({
@ -3917,8 +3951,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({
@ -3998,13 +4034,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
@ -4047,7 +4089,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({
@ -4531,7 +4573,8 @@ static void prim_compareVersions(EvalState & state, const PosIdx pos, Value * *
{
auto version1 = state.forceStringNoCtx(*args[0], pos, "while evaluating the first argument passed to builtins.compareVersions");
auto version2 = state.forceStringNoCtx(*args[1], pos, "while evaluating the second argument passed to builtins.compareVersions");
v.mkInt(compareVersions(version1, version2));
auto result = compareVersions(version1, version2);
v.mkInt(result < 0 ? -1 : result > 0 ? 1 : 0);
}
static RegisterPrimOp primop_compareVersions({

View file

@ -122,9 +122,15 @@ 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 if (state.symbols[attr.name] == "publicKeys") {
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();
}
attrs.emplace(state.symbols[attr.name], uint64_t(intValue));
} else if (state.symbols[attr.name] == "publicKeys") {
experimentalFeatureSettings.require(Xp::VerifiedFetches);
attrs.emplace(state.symbols[attr.name], printValueAsJSON(state, true, *attr.value, pos, context).dump());
}

View file

@ -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:

View file

@ -8,6 +8,7 @@
#include "value/context.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
@ -304,6 +305,11 @@ public:
return internalType != tUninitialized;
}
inline void mkInt(NixInt::Inner n)
{
mkInt(NixInt{n});
}
inline void mkInt(NixInt n)
{
finishValue(tInt, { .integer = n });

View file

@ -140,9 +140,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();
}
attrs.emplace(state.symbols[attr.name], uint64_t(intValue));
break;
}
default:
if (attr.name == state.symbols.create("publicKeys")) {
experimentalFeatureSettings.require(Xp::VerifiedFetches);
@ -272,7 +279,7 @@ static Flake readFlake(
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],
@ -904,8 +911,13 @@ 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();
}
attrs.emplace(state.symbols[attr.name], uint64_t(intValue));
} else if (t == nBool) {
attrs.emplace(state.symbols[attr.name],
Explicit<bool> { attr.value->boolean() });

View file

@ -1131,7 +1131,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
@ -1221,7 +1224,10 @@ public:
Setting<uint64_t> warnLargePathThreshold{
this,
std::numeric_limits<uint64_t>::max(),
// 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.
std::numeric_limits<int64_t>::max(),
"warn-large-path-threshold",
R"(
Warn when copying a path larger than this number of bytes to the Nix store

View file

@ -94,7 +94,7 @@ static bool componentsLT(const std::string_view c1, const std::string_view c2)
}
int compareVersions(const std::string_view v1, const std::string_view v2)
std::strong_ordering compareVersions(const std::string_view v1, const std::string_view v2)
{
auto p1 = v1.begin();
auto p2 = v2.begin();
@ -102,11 +102,11 @@ int compareVersions(const std::string_view v1, const std::string_view v2)
while (p1 != v1.end() || p2 != v2.end()) {
auto c1 = nextComponent(p1, v1.end());
auto c2 = nextComponent(p2, v2.end());
if (componentsLT(c1, c2)) return -1;
else if (componentsLT(c2, c1)) return 1;
if (componentsLT(c1, c2)) return std::strong_ordering::less;
else if (componentsLT(c2, c1)) return std::strong_ordering::greater;
}
return 0;
return std::strong_ordering::equal;
}

View file

@ -30,7 +30,7 @@ typedef std::list<DrvName> DrvNames;
std::string_view nextComponent(std::string_view::const_iterator & p,
const std::string_view::const_iterator end);
int compareVersions(const std::string_view v1, const std::string_view v2);
std::strong_ordering compareVersions(const std::string_view v1, const std::string_view v2);
DrvNames drvNamesFromArgs(const Strings & opArgs);
}

View file

@ -0,0 +1,182 @@
#pragma once
/**
* @file Checked arithmetic with classes that make it hard to accidentally make something an unchecked operation.
*/
#include <compare>
#include <concepts> // IWYU pragma: keep
#include <exception>
#include <ostream>
#include <limits>
#include <optional>
#include <type_traits>
namespace nix::checked {
class DivideByZero : std::exception
{};
/**
* Numeric value enforcing checked arithmetic. Performing mathematical operations on such values will return a Result
* type which needs to be checked.
*/
template<std::integral T>
struct Checked
{
using Inner = T;
// TODO: this must be a "trivial default constructor", which means it
// cannot set the value to NOT DO UB on uninit.
T value;
Checked() = default;
explicit Checked(T const value)
: value{value}
{
}
Checked(Checked<T> const & other) = default;
Checked(Checked<T> && other) = default;
Checked<T> & operator=(Checked<T> const & other) = default;
std::strong_ordering operator<=>(Checked<T> const & other) const = default;
std::strong_ordering operator<=>(T const & other) const
{
return value <=> other;
}
explicit operator T() const
{
return value;
}
enum class OverflowKind {
NoOverflow,
Overflow,
DivByZero,
};
class Result
{
T value;
OverflowKind overflowed_;
public:
Result(T value, bool overflowed)
: value{value}
, overflowed_{overflowed ? OverflowKind::Overflow : OverflowKind::NoOverflow}
{
}
Result(T value, OverflowKind overflowed)
: value{value}
, overflowed_{overflowed}
{
}
bool operator==(Result other) const
{
return value == other.value && overflowed_ == other.overflowed_;
}
std::optional<T> valueChecked() const
{
if (overflowed_ != OverflowKind::NoOverflow) {
return std::nullopt;
} else {
return value;
}
}
/**
* Returns the result as if the arithmetic were performed as wrapping arithmetic.
*
* \throws DivideByZero if the operation was a divide by zero.
*/
T valueWrapping() const
{
if (overflowed_ == OverflowKind::DivByZero) {
throw DivideByZero{};
}
return value;
}
bool overflowed() const
{
return overflowed_ == OverflowKind::Overflow;
}
bool divideByZero() const
{
return overflowed_ == OverflowKind::DivByZero;
}
};
Result operator+(Checked<T> const other) const
{
return (*this) + other.value;
}
Result operator+(T const other) const
{
T result;
bool overflowed = __builtin_add_overflow(value, other, &result);
return Result{result, overflowed};
}
Result operator-(Checked<T> const other) const
{
return (*this) - other.value;
}
Result operator-(T const other) const
{
T result;
bool overflowed = __builtin_sub_overflow(value, other, &result);
return Result{result, overflowed};
}
Result operator*(Checked<T> const other) const
{
return (*this) * other.value;
}
Result operator*(T const other) const
{
T result;
bool overflowed = __builtin_mul_overflow(value, other, &result);
return Result{result, overflowed};
}
Result operator/(Checked<T> const other) const
{
return (*this) / other.value;
}
/**
* Performs a checked division.
*
* If the right hand side is zero, the result is marked as a DivByZero and
* valueWrapping will throw.
*/
Result operator/(T const other) const
{
constexpr T const minV = std::numeric_limits<T>::min();
// It's only possible to overflow with signed division since doing so
// requires crossing the two's complement limits by MIN / -1 (since
// two's complement has one more in range in the negative direction
// than in the positive one).
if (std::is_signed<T>() && (value == minV && other == -1)) {
return Result{minV, true};
} else if (other == 0) {
return Result{0, OverflowKind::DivByZero};
} else {
T result = value / other;
return Result{result, false};
}
}
};
template<std::integral T>
std::ostream & operator<<(std::ostream & ios, Checked<T> v)
{
ios << v.value;
return ios;
}
}

View file

@ -172,6 +172,7 @@ headers = [config_h] + files(
'args/root.hh',
'callback.hh',
'canon-path.hh',
'checked-arithmetic.hh',
'chunked-vector.hh',
'closure.hh',
'comparator.hh',

View file

@ -204,15 +204,15 @@ static void loadDerivations(EvalState & state, const SourcePath & nixExprPath,
}
static long getPriority(EvalState & state, PackageInfo & drv)
static NixInt getPriority(EvalState & state, PackageInfo & drv)
{
return drv.queryMetaInt("priority", 0);
return drv.queryMetaInt("priority", NixInt(0));
}
static long comparePriorities(EvalState & state, PackageInfo & drv1, PackageInfo & drv2)
static std::strong_ordering comparePriorities(EvalState & state, PackageInfo & drv1, PackageInfo & drv2)
{
return getPriority(state, drv2) - getPriority(state, drv1);
return getPriority(state, drv2) <=> getPriority(state, drv1);
}
@ -280,7 +280,7 @@ std::vector<Match> pickNewestOnly(EvalState & state, std::vector<Match> matches)
auto & oneDrv = match.packageInfo;
const auto drvName = DrvName { oneDrv.queryName() };
long comparison = 1;
std::strong_ordering comparison = std::strong_ordering::greater;
const auto itOther = newest.find(drvName.name);
@ -288,9 +288,9 @@ std::vector<Match> pickNewestOnly(EvalState & state, std::vector<Match> matches)
auto & newestDrv = itOther->second.packageInfo;
comparison =
oneDrv.querySystem() == newestDrv.querySystem() ? 0 :
oneDrv.querySystem() == settings.thisSystem ? 1 :
newestDrv.querySystem() == settings.thisSystem ? -1 : 0;
oneDrv.querySystem() == newestDrv.querySystem() ? std::strong_ordering::equal :
oneDrv.querySystem() == settings.thisSystem ? std::strong_ordering::greater :
newestDrv.querySystem() == settings.thisSystem ? std::strong_ordering::less : std::strong_ordering::equal;
if (comparison == 0)
comparison = comparePriorities(state, oneDrv, newestDrv);
if (comparison == 0)
@ -625,13 +625,13 @@ static void upgradeDerivations(Globals & globals,
continue;
DrvName newName(j->queryName());
if (newName.name == drvName.name) {
int d = compareVersions(drvName.version, newName.version);
std::strong_ordering d = compareVersions(drvName.version, newName.version);
if ((upgradeType == utLt && d < 0) ||
(upgradeType == utLeq && d <= 0) ||
(upgradeType == utEq && d == 0) ||
upgradeType == utAlways)
{
long d2 = -1;
std::strong_ordering d2 = std::strong_ordering::less;
if (bestElem != availElems.end()) {
d2 = comparePriorities(*globals.state, *bestElem, *j);
if (d2 == 0) d2 = compareVersions(bestVersion, newName.version);
@ -902,7 +902,7 @@ static VersionDiff compareVersionAgainstSet(
for (auto & i : elems) {
DrvName name2(i.queryName());
if (name.name == name2.name) {
int d = compareVersions(name.version, name2.version);
std::strong_ordering d = compareVersions(name.version, name2.version);
if (d < 0) {
diff = cvGreater;
version = name2.version;