aboutsummaryrefslogtreecommitdiff
path: root/src/libutil/checked-arithmetic.hh
blob: c0c63feff54e5b25c472c7cbfd13ea6caf29c51f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
#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;
}

}