• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Alan-Jowett / ebpf-verifier / 19036509529

02 Nov 2025 09:22PM UTC coverage: 86.936% (-0.5%) from 87.448%
19036509529

push

github

elazarg
Bump external/libbtf from `b674148` to `04281ee`

Bumps [external/libbtf](https://github.com/Alan-Jowett/libbtf) from `b674148` to `04281ee`.
- [Release notes](https://github.com/Alan-Jowett/libbtf/releases)
- [Commits](https://github.com/Alan-Jowett/libbtf/compare/b6741487e...<a class=hub.com/Alan-Jowett/ebpf-verifier/commit/04281ee7a91595911807897b4ca2e7483cf97497">04281ee7a)

---
updated-dependencies:
- dependency-name: external/libbtf
  dependency-version: '04281ee7a91595911807897b4ca2e7483cf97497'
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>

9044 of 10403 relevant lines covered (86.94%)

3989157.54 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

86.6
/src/crab/array_domain.cpp
1
// Copyright (c) Prevail Verifier contributors.
2
// SPDX-License-Identifier: Apache-2.0
3

4
#include <algorithm>
5
#include <optional>
6
#include <set>
7
#include <unordered_map>
8
#include <utility>
9
#include <vector>
10

11
#include "boost/endian/conversion.hpp"
12
#include "radix_tree/radix_tree.hpp"
13
#include <gsl/narrow>
14

15
#include "arith/dsl_syntax.hpp"
16
#include "config.hpp"
17
#include "crab/array_domain.hpp"
18
#include "crab/var_registry.hpp"
19
#include "crab_utils/num_safety.hpp"
20

21
namespace prevail {
22

23
using Index = uint64_t;
24

25
class offset_t final {
26
    Index _index{};
27
    int _prefix_length;
28

29
  public:
30
    static constexpr int bitsize = 8 * sizeof(Index);
31
    offset_t() : _prefix_length(bitsize) {}
2,988,502✔
32
    explicit offset_t(const Index index) : _index(index), _prefix_length(bitsize) {}
2,929,302✔
33
    offset_t(const Index index, const int prefix_length) : _index(index), _prefix_length(prefix_length) {}
7,513,738✔
34
    explicit operator int() const { return gsl::narrow<int>(_index); }
2,571,470✔
35
    operator Index() const { return _index; }
729,122,148✔
36
    [[nodiscard]]
37
    int prefix_length() const {
15,446,319✔
38
        return _prefix_length;
15,446,319✔
39
    }
40

41
    Index operator[](const int n) const { return (_index >> (bitsize - 1 - n)) & 1; }
16,446,411✔
42
};
43

44
// NOTE: required by radix_tree
45
// Get the length of a key, which is the size usable with the [] operator.
46
[[maybe_unused]]
47
int radix_length(const offset_t& offset) {
15,446,319✔
48
    return offset.prefix_length();
15,446,319✔
49
}
50

51
// NOTE: required by radix_tree
52
// Get a range of bits out of the middle of a key, starting at [begin] for a given length.
53
[[maybe_unused]]
54
offset_t radix_substr(const offset_t& key, const int begin, const int length) {
7,513,738✔
55
    Index mask;
3,756,869✔
56

57
    if (length == offset_t::bitsize) {
7,457,187✔
58
        mask = 0;
32,157✔
59
    } else {
60
        mask = Index{1} << length;
7,392,873✔
61
    }
62

63
    mask -= 1;
7,513,738✔
64
    mask <<= offset_t::bitsize - length - begin;
7,513,738✔
65

66
    const Index value = (gsl::narrow_cast<Index>(key) & mask) << begin;
3,813,420✔
67
    return offset_t{value, length};
3,813,420✔
68
}
69

70
// NOTE: required by radix_tree
71
// Concatenate two bit patterns.
72
[[maybe_unused]]
73
offset_t radix_join(const offset_t& entry1, const offset_t& entry2) {
×
74
    const Index value = entry1 | (entry2 >> entry1.prefix_length());
×
75
    const int prefix_length = entry1.prefix_length() + entry2.prefix_length();
×
76
    return offset_t{value, prefix_length};
×
77
}
78

79
/***
80
   Conceptually, a cell is tuple of an array, offset, size, and
81
   scalar variable such that:
82

83
_scalar = array[_offset, _offset + 1, ..., _offset + _size - 1]
84

85
    For simplicity, we don't carry the array inside the cell class.
86
    Only, offset_map objects can create cells. They will consider the
87
            array when generating the scalar variable.
88
*/
89
class Cell final {
90
  private:
91
    friend class offset_map_t;
92

93
    offset_t _offset{};
94
    unsigned _size{};
95

96
    // Only offset_map_t can create cells
97
    Cell() = default;
448✔
98

99
    Cell(const offset_t offset, const unsigned size) : _offset(offset), _size(size) {}
853,650✔
100

101
    static Interval to_interval(const offset_t o, const unsigned size) {
2,571,470✔
102
        const Number lb{gsl::narrow<int>(o)};
2,571,470✔
103
        return {lb, lb + size - 1};
7,714,410✔
104
    }
2,571,470✔
105

106
  public:
107
    [[nodiscard]]
108
    Interval to_interval() const {
1,859,928✔
109
        return to_interval(_offset, _size);
1,859,928✔
110
    }
111

112
    [[nodiscard]]
113
    bool is_null() const {
544✔
114
        return _offset == 0 && _size == 0;
496✔
115
    }
116

117
    [[nodiscard]]
118
    offset_t get_offset() const {
682,474✔
119
        return _offset;
682,474✔
120
    }
121

122
    [[nodiscard]]
123
    Variable get_scalar(const DataKind kind) const {
139,736✔
124
        return variable_registry->cell_var(kind, gsl::narrow<Index>(_offset), _size);
209,604✔
125
    }
126

127
    // ignore the scalar variable
128
    bool operator==(const Cell& o) const { return to_interval() == o.to_interval(); }
1,134,204✔
129

130
    // ignore the scalar variable
131
    bool operator<(const Cell& o) const {
1,054,976✔
132
        if (_offset == o._offset) {
1,054,976✔
133
            return _size < o._size;
1,054,976✔
134
        }
135
        return _offset < o._offset;
×
136
    }
137

138
    // Return true if [o, o + size) definitely overlaps with the cell,
139
    // where o is a constant expression.
140
    [[nodiscard]]
141
    bool overlap(const offset_t& o, const unsigned size) const {
711,542✔
142
        const Interval x = to_interval();
711,542✔
143
        const Interval y = to_interval(o, size);
711,542✔
144
        const bool res = (!(x & y).is_bottom());
1,067,313✔
145
        CRAB_LOG("array-expansion-overlap",
711,542✔
146
                 std::cout << "**Checking if " << x << " overlaps with " << y << "=" << res << "\n";);
147
        return res;
711,542✔
148
    }
1,067,313✔
149

150
    // Return true if [symb_lb, symb_ub] may overlap with the cell,
151
    // where symb_lb and symb_ub are not constant expressions.
152
    [[nodiscard]]
153
    bool symbolic_overlap(const Interval& range) const;
154

155
    friend std::ostream& operator<<(std::ostream& o, const Cell& c) { return o << "cell(" << c.to_interval() << ")"; }
×
156
};
157

158
// Return true if [symb_lb, symb_ub] may overlap with the cell,
159
// where symb_lb and symb_ub are not constant expressions.
160
bool Cell::symbolic_overlap(const Interval& range) const { return !(to_interval() & range).is_bottom(); }
240✔
161

162
// Map offsets to cells
163
class offset_map_t final {
4,467✔
164
  private:
165
    friend class ArrayDomain;
166

167
    using cell_set_t = std::set<Cell>;
168

169
    /*
170
      The keys in the patricia tree are processing in big-endian order. This means that the keys are sorted.
171
      Sortedness is very important to efficiently perform operations such as checking for overlap cells.
172
      Since keys are treated as bit patterns, negative offsets can be used, but they are treated as large unsigned
173
      numbers.
174
    */
175
    using PatriciaTree = radix_tree<offset_t, cell_set_t>;
176

177
    PatriciaTree _map;
178

179
    // for algorithm::lower_bound and algorithm::upper_bound
180
    struct CompareBinding {
181
        bool operator()(const PatriciaTree::value_type& kv, const offset_t& o) const { return kv.first < o; }
1,835,896✔
182
        bool operator()(const offset_t& o, const PatriciaTree::value_type& kv) const { return o < kv.first; }
1,819,338✔
183
        bool operator()(const PatriciaTree::value_type& kv1, const PatriciaTree::value_type& kv2) const {
184
            return kv1.first < kv2.first;
185
        }
186
    };
187

188
    void remove_cell(const Cell& c);
189

190
    void insert_cell(const Cell& c);
191

192
    [[nodiscard]]
193
    std::optional<Cell> get_cell(offset_t o, unsigned size);
194

195
    Cell mk_cell(offset_t o, unsigned size);
196

197
  public:
198
    offset_map_t() = default;
8,934✔
199

200
    [[nodiscard]]
201
    bool empty() const {
202
        return _map.empty();
203
    }
204

205
    [[nodiscard]]
206
    std::size_t size() const {
207
        return _map.size();
208
    }
209

210
    void operator-=(const Cell& c) { remove_cell(c); }
19,512✔
211

212
    void operator-=(const std::vector<Cell>& cells) {
13,498✔
213
        for (const auto& c : cells) {
33,010✔
214
            this->operator-=(c);
19,512✔
215
        }
216
    }
13,498✔
217

218
    // Return in out all cells that might overlap with (o, size).
219
    std::vector<Cell> get_overlap_cells(offset_t o, unsigned size);
220

221
    [[nodiscard]]
222
    std::vector<Cell> get_overlap_cells_symbolic_offset(const Interval& range);
223

224
    friend std::ostream& operator<<(std::ostream& o, offset_map_t& m);
225

226
    /* Operations needed if used as value in a separate_domain */
227
    [[nodiscard]]
228
    bool is_top() const {
229
        return empty();
230
    }
231

232
    [[nodiscard]]
233
    // ReSharper disable once CppMemberFunctionMayBeStatic
234
    bool is_bottom() const {
235
        return false;
236
    }
237
    /*
238
       We don't distinguish between bottom and top.
239
       This is fine because separate_domain only calls bottom if operator[] is called over a bottom state.
240
       Thus, we will make sure that we don't call operator[] in that case.
241
    */
242
    static offset_map_t bottom() { return offset_map_t(); }
243
    static offset_map_t top() { return offset_map_t(); }
244
};
245

246
void offset_map_t::remove_cell(const Cell& c) {
328,908✔
247
    const offset_t key = c.get_offset();
328,908✔
248
    _map[key].erase(c);
328,908✔
249
}
328,908✔
250

251
[[nodiscard]]
252
std::vector<Cell> offset_map_t::get_overlap_cells_symbolic_offset(const Interval& range) {
56✔
253
    std::vector<Cell> out;
56✔
254
    for (const auto& [_offset, o_cells] : _map) {
756✔
255
        // All cells in o_cells have the same offset. They only differ in the size.
256
        // If the largest cell overlaps with [offset, offset + size)
257
        // then the rest of cells are considered to overlap.
258
        // This is an over-approximation because [offset, offset+size) can overlap
259
        // with the largest cell, but it doesn't necessarily overlap with smaller cells.
260
        // For efficiency, we assume it overlaps with all.
261
        Cell largest_cell;
448✔
262
        for (const Cell& c : o_cells) {
544✔
263
            if (largest_cell.is_null()) {
96✔
264
                largest_cell = c;
96✔
265
            } else {
266
                assert(c.get_offset() == largest_cell.get_offset());
×
267
                if (largest_cell < c) {
×
268
                    largest_cell = c;
×
269
                }
270
            }
271
        }
272
        if (!largest_cell.is_null()) {
672✔
273
            if (largest_cell.symbolic_overlap(range)) {
96✔
274
                for (const auto& c : o_cells) {
192✔
275
                    out.push_back(c);
96✔
276
                }
277
            }
278
        }
279
    }
280
    return out;
56✔
281
}
×
282

283
void offset_map_t::insert_cell(const Cell& c) { _map[c.get_offset()].insert(c); }
353,566✔
284

285
std::optional<Cell> offset_map_t::get_cell(const offset_t o, const unsigned size) {
500,084✔
286
    cell_set_t& cells = _map[o];
500,084✔
287
    const auto it = cells.find(Cell(o, size));
500,084✔
288
    if (it != cells.end()) {
500,084✔
289
        return *it;
140,528✔
290
    }
291
    return {};
359,556✔
292
}
293

294
Cell offset_map_t::mk_cell(const offset_t o, const unsigned size) {
76,680✔
295
    // TODO: check array is the array associated to this offset map
296

297
    if (const auto maybe_c = get_cell(o, size)) {
76,680✔
298
        return *maybe_c;
32,510✔
299
    }
300
    // create a new scalar variable for representing the contents
301
    // of bytes array[o,o+1,..., o+size-1]
302
    const Cell c(o, size);
44,170✔
303
    insert_cell(c);
44,170✔
304
    return c;
44,170✔
305
}
306

307
// Return all cells that might overlap with (o, size).
308
std::vector<Cell> offset_map_t::get_overlap_cells(const offset_t o, const unsigned size) {
391,164✔
309
    std::vector<Cell> out;
391,164✔
310
    constexpr CompareBinding comp;
195,582✔
311

312
    bool added = false;
391,164✔
313
    auto maybe_c = get_cell(o, size);
391,164✔
314
    if (!maybe_c) {
391,164✔
315
        maybe_c = Cell(o, size);
309,396✔
316
        insert_cell(*maybe_c);
309,396✔
317
        added = true;
154,698✔
318
    }
319

320
    auto lb_it = std::lower_bound(_map.begin(), _map.end(), o, comp);
391,164✔
321
    if (lb_it != _map.end()) {
391,164✔
322
        // Store _map[begin,...,lb_it] into a vector so that we can
323
        // go backwards from lb_it.
324
        //
325
        // TODO: give support for reverse iterator in patricia_tree.
326
        std::vector<cell_set_t> upto_lb;
391,164✔
327
        upto_lb.reserve(std::distance(_map.begin(), lb_it));
782,328✔
328
        for (auto it = _map.begin(), et = lb_it; it != et; ++it) {
7,333,410✔
329
            upto_lb.push_back(it->second);
6,942,246✔
330
        }
331
        upto_lb.push_back(lb_it->second);
391,164✔
332

333
        for (int i = gsl::narrow<int>(upto_lb.size() - 1); i >= 0; --i) {
786,036✔
334
            ///////
335
            // All the cells in upto_lb[i] have the same offset. They
336
            // just differ in the size.
337
            //
338
            // If none of the cells in upto_lb[i] overlap with (o, size)
339
            // we can stop.
340
            ////////
341
            bool continue_outer_loop = false;
719,364✔
342
            for (const Cell& x : upto_lb[i]) {
1,272,098✔
343
                if (x.overlap(o, size)) {
552,734✔
344
                    if (x != *maybe_c) {
415,314✔
345
                        // FIXME: we might have some duplicates. this is a very drastic solution.
346
                        if (std::ranges::find(out, x) == out.end()) {
24,150✔
347
                            out.push_back(x);
24,150✔
348
                        }
349
                    }
350
                    continue_outer_loop = true;
207,657✔
351
                }
352
            }
353
            if (!continue_outer_loop) {
719,364✔
354
                break;
162,246✔
355
            }
356
        }
357
    }
391,164✔
358

359
    // search for overlapping cells > o
360
    auto ub_it = std::upper_bound(_map.begin(), _map.end(), o, comp);
391,164✔
361
    for (; ub_it != _map.end(); ++ub_it) {
402,484✔
362
        bool continue_outer_loop = false;
355,046✔
363
        for (const Cell& x : ub_it->second) {
502,534✔
364
            if (x.overlap(o, size)) {
147,488✔
365
                // FIXME: we might have some duplicates. this is a very drastic solution.
366
                if (std::ranges::find(out, x) == out.end()) {
11,320✔
367
                    out.push_back(x);
11,320✔
368
                }
369
                continue_outer_loop = true;
5,660✔
370
            }
371
        }
372
        if (!continue_outer_loop) {
355,046✔
373
            break;
171,863✔
374
        }
375
    }
376

377
    // do not forget the rest of overlapping cells == o
378
    for (auto it = ++lb_it, et = ub_it; it != et; ++it) {
402,484✔
379
        bool continue_outer_loop = false;
11,320✔
380
        for (const Cell& x : it->second) {
22,640✔
381
            if (x == *maybe_c) { // we don't put it in out
11,320✔
382
                continue;
×
383
            }
384
            if (x.overlap(o, size)) {
11,320✔
385
                if (std::ranges::find(out, x) == out.end()) {
11,320✔
386
                    out.push_back(x);
×
387
                }
388
                continue_outer_loop = true;
5,660✔
389
            }
390
        }
391
        if (!continue_outer_loop) {
11,320✔
392
            break;
393
        }
394
    }
395

396
    if (added) {
391,164✔
397
        remove_cell(*maybe_c);
309,396✔
398
    }
399
    return out;
586,746✔
400
}
×
401

402
// We use a global array map
403
using array_map_t = std::unordered_map<DataKind, offset_map_t>;
404

405
static thread_local LazyAllocator<array_map_t> thread_local_array_map;
269,631✔
406

407
void clear_thread_local_state() { thread_local_array_map.clear(); }
3,948✔
408

409
static offset_map_t& lookup_array_map(const DataKind kind) { return (*thread_local_array_map)[kind]; }
535,314✔
410

411
void ArrayDomain::initialize_numbers(const int lb, const int width) {
262✔
412
    num_bytes.reset(lb, width);
262✔
413
    lookup_array_map(DataKind::svalues).mk_cell(offset_t{gsl::narrow_cast<Index>(lb)}, width);
262✔
414
}
262✔
415

416
std::ostream& operator<<(std::ostream& o, offset_map_t& m) {
×
417
    if (m._map.empty()) {
×
418
        o << "empty";
×
419
    } else {
420
        for (const auto& [_offset, cells] : m._map) {
×
421
            o << "{";
×
422
            for (auto cit = cells.begin(), cet = cells.end(); cit != cet;) {
×
423
                o << *cit;
×
424
                ++cit;
×
425
                if (cit != cet) {
×
426
                    o << ",";
×
427
                }
428
            }
429
            o << "}\n";
×
430
        }
431
    }
432
    return o;
×
433
}
434

435
// Create a new cell that is a subset of an existing cell.
436
void ArrayDomain::split_cell(NumAbsDomain& inv, const DataKind kind, const int cell_start_index,
996✔
437
                             const unsigned int len) const {
438
    assert(kind == DataKind::svalues || kind == DataKind::uvalues);
996✔
439

440
    // Get the values from the indicated stack range.
441
    const std::optional<LinearExpression> svalue = load(inv, DataKind::svalues, Interval{cell_start_index}, len);
996✔
442
    const std::optional<LinearExpression> uvalue = load(inv, DataKind::uvalues, Interval{cell_start_index}, len);
996✔
443

444
    // Create a new cell for that range.
445
    offset_map_t& offset_map = lookup_array_map(kind);
996✔
446
    const Cell new_cell = offset_map.mk_cell(offset_t{gsl::narrow_cast<Index>(cell_start_index)}, len);
996✔
447
    inv.assign(new_cell.get_scalar(DataKind::svalues), svalue);
996✔
448
    inv.assign(new_cell.get_scalar(DataKind::uvalues), uvalue);
1,494✔
449
}
996✔
450

451
// Prepare to havoc bytes in the middle of a cell by potentially splitting the cell if it is numeric,
452
// into the part to the left of the havoced portion, and the part to the right of the havoced portion.
453
void ArrayDomain::split_number_var(NumAbsDomain& inv, DataKind kind, const Interval& ii,
68,000✔
454
                                   const Interval& elem_size) const {
455
    assert(kind == DataKind::svalues || kind == DataKind::uvalues);
68,000✔
456
    offset_map_t& offset_map = lookup_array_map(kind);
68,000✔
457
    const std::optional<Number> n = ii.singleton();
68,000✔
458
    if (!n) {
68,000✔
459
        // We can only split a singleton offset.
460
        return;
8✔
461
    }
462
    const std::optional<Number> n_bytes = elem_size.singleton();
67,984✔
463
    if (!n_bytes) {
67,984✔
464
        // We can only split a singleton size.
465
        return;
×
466
    }
467
    const auto size = n_bytes->narrow<unsigned int>();
67,984✔
468
    const offset_t o(n->narrow<Index>());
67,984✔
469

470
    const std::vector<Cell> cells = offset_map.get_overlap_cells(o, size);
67,984✔
471
    for (const Cell& c : cells) {
82,070✔
472
        const auto [cell_start_index, cell_end_index] = c.to_interval().pair<int>();
21,129✔
473
        if (!this->num_bytes.all_num(cell_start_index, cell_end_index + 1) ||
20,380✔
474
            cell_end_index + 1UL < cell_start_index + sizeof(int64_t)) {
12,588✔
475
            // We can only split numeric cells of size 8 or less.
476
            continue;
13,166✔
477
        }
478

479
        if (!inv.eval_interval(c.get_scalar(kind)).is_singleton()) {
4,820✔
480
            // We can only split cells with a singleton value.
481
            continue;
1,490✔
482
        }
483
        if (gsl::narrow_cast<Index>(cell_start_index) < o) {
920✔
484
            // Use the bytes to the left of the specified range.
485
            split_cell(inv, kind, cell_start_index, gsl::narrow<unsigned int>(o - cell_start_index));
268✔
486
        }
487
        if (o + size < cell_end_index + 1UL) {
920✔
488
            // Use the bytes to the right of the specified range.
489
            split_cell(inv, kind, gsl::narrow<int>(o + size),
728✔
490
                       gsl::narrow<unsigned int>(cell_end_index - (o + size - 1)));
728✔
491
        }
492
    }
493
}
68,000✔
494

495
// we can only treat this as non-member because we use global state
496
static std::optional<std::pair<offset_t, unsigned>> kill_and_find_var(NumAbsDomain& inv, DataKind kind,
319,444✔
497
                                                                      const Interval& ii, const Interval& elem_size) {
498
    std::optional<std::pair<offset_t, unsigned>> res;
319,444✔
499

500
    offset_map_t& offset_map = lookup_array_map(kind);
319,444✔
501
    std::vector<Cell> cells;
319,444✔
502
    if (const std::optional<Number> n = ii.singleton()) {
319,444✔
503
        if (const auto n_bytes = elem_size.singleton()) {
319,388✔
504
            auto size = n_bytes->narrow<unsigned int>();
319,388✔
505
            // -- Constant index: kill overlapping cells
506
            offset_t o(n->narrow<Index>());
319,388✔
507
            cells = offset_map.get_overlap_cells(o, size);
479,082✔
508
            res = std::make_pair(o, size);
319,388✔
509
        }
319,388✔
510
    }
159,722✔
511
    if (!res) {
319,444✔
512
        // -- Non-constant index: kill overlapping cells
513
        cells = offset_map.get_overlap_cells_symbolic_offset(ii | (ii + elem_size));
140✔
514
    }
515
    if (!cells.empty()) {
319,444✔
516
        // Forget the scalars from the numerical domain
517
        for (const auto& c : cells) {
33,010✔
518
            inv.havoc(c.get_scalar(kind));
19,512✔
519

520
            // Forget signed and unsigned values together.
521
            if (kind == DataKind::svalues) {
19,512✔
522
                inv.havoc(c.get_scalar(DataKind::uvalues));
7,272✔
523
            } else if (kind == DataKind::uvalues) {
12,240✔
524
                inv.havoc(c.get_scalar(DataKind::svalues));
6,878✔
525
            }
526
        }
527
        // Remove the cells. If needed again they will be re-created.
528
        offset_map -= cells;
13,498✔
529
    }
530
    return res;
479,166✔
531
}
319,444✔
532
static std::tuple<int, int> as_numbytes_range(const Interval& index, const Interval& width) {
4,132✔
533
    return (index | (index + width)).bound(0, EBPF_TOTAL_STACK_SIZE);
8,264✔
534
}
535

536
bool ArrayDomain::all_num_lb_ub(const Interval& lb, const Interval& ub) const {
3,630✔
537
    const auto [min_lb, max_ub] = (lb | ub).bound(0, EBPF_TOTAL_STACK_SIZE);
5,445✔
538
    if (min_lb > max_ub) {
3,630✔
539
        return false;
540
    }
541
    return this->num_bytes.all_num(min_lb, max_ub);
3,630✔
542
}
543

544
bool ArrayDomain::all_num_width(const Interval& index, const Interval& width) const {
4,128✔
545
    const auto [min_lb, max_ub] = as_numbytes_range(index, width);
4,128✔
546
    assert(min_lb <= max_ub);
4,128✔
547
    return this->num_bytes.all_num(min_lb, max_ub);
6,192✔
548
}
549

550
// Get the number of bytes, starting at offset, that are known to be numbers.
551
int ArrayDomain::min_all_num_size(const NumAbsDomain& inv, const Variable offset) const {
13,182✔
552
    const auto min_lb = inv.eval_interval(offset).lb().number();
32,955✔
553
    const auto max_ub = inv.eval_interval(offset).ub().number();
32,955✔
554
    if (!min_lb || !max_ub || !min_lb->fits<int32_t>() || !max_ub->fits<int32_t>()) {
13,182✔
555
        return 0;
1,935✔
556
    }
557
    const auto lb = min_lb->narrow<int>();
9,312✔
558
    const auto ub = max_ub->narrow<int>();
9,312✔
559
    return std::max(0, this->num_bytes.all_num_width(lb) - (ub - lb));
13,165✔
560
}
13,182✔
561

562
// Get one byte of a value.
563
std::optional<uint8_t> get_value_byte(const NumAbsDomain& inv, const offset_t o, const int width) {
24,964✔
564
    const Variable v = variable_registry->cell_var(DataKind::svalues, (o / width) * width, width);
37,446✔
565
    const std::optional<Number> t = inv.eval_interval(v).singleton();
37,446✔
566
    if (!t) {
24,964✔
567
        return {};
13,750✔
568
    }
569
    Index n = t->cast_to<Index>();
11,214✔
570

571
    // Convert value to bytes of the appropriate endian-ness.
572
    switch (width) {
11,214✔
573
    case sizeof(uint8_t): break;
79✔
574
    case sizeof(uint16_t):
132✔
575
        if (thread_local_options.big_endian) {
132✔
576
            n = boost::endian::native_to_big<uint16_t>(n);
×
577
        } else {
578
            n = boost::endian::native_to_little<uint16_t>(n);
132✔
579
        }
580
        break;
66✔
581
    case sizeof(uint32_t):
564✔
582
        if (thread_local_options.big_endian) {
564✔
583
            n = boost::endian::native_to_big<uint32_t>(n);
×
584
        } else {
585
            n = boost::endian::native_to_little<uint32_t>(n);
564✔
586
        }
587
        break;
282✔
588
    case sizeof(Index):
10,360✔
589
        if (thread_local_options.big_endian) {
10,360✔
590
            n = boost::endian::native_to_big<Index>(n);
48✔
591
        } else {
592
            n = boost::endian::native_to_little<Index>(n);
5,156✔
593
        }
594
        break;
5,180✔
595
    default: CRAB_ERROR("Unexpected width ", width);
×
596
    }
597
    const auto bytes = reinterpret_cast<uint8_t*>(&n);
11,214✔
598
    return bytes[o % width];
11,214✔
599
}
24,964✔
600

601
std::optional<LinearExpression> ArrayDomain::load(const NumAbsDomain& inv, const DataKind kind, const Interval& i,
29,632✔
602
                                                  const int width) const {
603
    if (const std::optional<Number> n = i.singleton()) {
29,632✔
604
        offset_map_t& offset_map = lookup_array_map(kind);
29,616✔
605
        const int64_t k = n->narrow<int64_t>();
29,616✔
606
        const offset_t o(k);
29,616✔
607
        const unsigned size = to_unsigned(width);
29,616✔
608
        if (const auto cell = lookup_array_map(kind).get_cell(o, size)) {
29,616✔
609
            return cell->get_scalar(kind);
35,445✔
610
        }
611
        if (kind == DataKind::svalues || kind == DataKind::uvalues) {
5,986✔
612
            // Copy bytes into result_buffer, taking into account that the
613
            // bytes might be in different stack variables and might be unaligned.
614
            uint8_t result_buffer[8];
615
            bool found = true;
11,585✔
616
            for (unsigned int index = 0; index < size; index++) {
17,192✔
617
                const offset_t byte_offset{o + index};
14,326✔
618
                std::optional<uint8_t> b = get_value_byte(inv, byte_offset, 8);
14,326✔
619
                if (!b) {
14,326✔
620
                    b = get_value_byte(inv, byte_offset, 4);
3,966✔
621
                    if (!b) {
3,966✔
622
                        b = get_value_byte(inv, byte_offset, 2);
3,402✔
623
                        if (!b) {
3,402✔
624
                            b = get_value_byte(inv, byte_offset, 1);
3,270✔
625
                        }
626
                    }
627
                }
628
                if (b) {
14,326✔
629
                    result_buffer[index] = *b;
11,214✔
630
                } else {
631
                    found = false;
3,112✔
632
                    break;
3,112✔
633
                }
634
            }
635
            if (found) {
4,545✔
636
                // We have an aligned result in result_buffer so we can now
637
                // convert to an integer.
638
                if (size == 1) {
2,866✔
639
                    return *result_buffer;
2,381✔
640
                }
641
                if (size == 2) {
2,500✔
642
                    uint16_t b = *reinterpret_cast<uint16_t*>(result_buffer);
444✔
643
                    if (thread_local_options.big_endian) {
444✔
644
                        b = boost::endian::native_to_big<uint16_t>(b);
12✔
645
                    } else {
646
                        b = boost::endian::native_to_little<uint16_t>(b);
438✔
647
                    }
648
                    return b;
444✔
649
                }
650
                if (size == 4) {
2,056✔
651
                    uint32_t b = *reinterpret_cast<uint32_t*>(result_buffer);
1,306✔
652
                    if (thread_local_options.big_endian) {
1,306✔
653
                        b = boost::endian::native_to_big<uint32_t>(b);
12✔
654
                    } else {
655
                        b = boost::endian::native_to_little<uint32_t>(b);
647✔
656
                    }
657
                    return b;
1,306✔
658
                }
659
                if (size == 8) {
750✔
660
                    Index b = *reinterpret_cast<Index*>(result_buffer);
82✔
661
                    if (thread_local_options.big_endian) {
82✔
662
                        b = boost::endian::native_to_big<Index>(b);
4✔
663
                    } else {
664
                        b = boost::endian::native_to_little<Index>(b);
39✔
665
                    }
666
                    return kind == DataKind::uvalues ? Number(b) : Number(to_signed(b));
128✔
667
                }
668
            }
669
        }
670

671
        const std::vector<Cell> cells = offset_map.get_overlap_cells(o, size);
3,788✔
672
        if (cells.empty()) {
3,788✔
673
            const Cell c = offset_map.mk_cell(o, size);
2,236✔
674
            // Here it's ok to do assignment (instead of expand) because c is not a summarized variable.
675
            // Otherwise, it would be unsound.
676
            return c.get_scalar(kind);
2,236✔
677
        }
678
        CRAB_WARN("Ignored read from cell ", kind, "[", o, "...", o + size - 1, "]", " because it overlaps with ",
1,552✔
679
                  cells.size(), " cells");
680
        /*
681
            TODO: we can apply here "Value Recomposition" a la Mine'06 (https://arxiv.org/pdf/cs/0703074.pdf)
682
                to construct values of some type from a sequence of bytes.
683
                It can be endian-independent but it would more precise if we choose between little- and big-endian.
684
        */
685
    } else {
3,788✔
686
        // TODO: we can be more precise here
687
        CRAB_WARN("array expansion: ignored array load because of non-constant array index ", i);
792✔
688
    }
28,848✔
689
    return {};
1,568✔
690
}
691

692
std::optional<LinearExpression> ArrayDomain::load_type(const Interval& i, int width) const {
11,574✔
693
    if (std::optional<Number> n = i.singleton()) {
11,574✔
694
        offset_map_t& offset_map = lookup_array_map(DataKind::types);
11,574✔
695
        int64_t k = n->narrow<int64_t>();
11,574✔
696
        auto [only_num, only_non_num] = num_bytes.uniformity(k, width);
11,574✔
697
        if (only_num) {
11,574✔
698
            return T_NUM;
8,950✔
699
        }
700
        if (!only_non_num || width != 8) {
2,624✔
701
            return {};
×
702
        }
703
        offset_t o(k);
2,624✔
704
        unsigned size = to_unsigned(width);
2,624✔
705
        if (auto cell = lookup_array_map(DataKind::types).get_cell(o, size)) {
2,624✔
706
            return cell->get_scalar(DataKind::types);
3,930✔
707
        }
708
        std::vector<Cell> cells = offset_map.get_overlap_cells(o, size);
4✔
709
        if (cells.empty()) {
4✔
710
            Cell c = offset_map.mk_cell(o, size);
4✔
711
            // Here it's ok to do assignment (instead of expand) because c is not a summarized variable.
712
            // Otherwise, it would be unsound.
713
            return c.get_scalar(DataKind::types);
4✔
714
        }
715
        CRAB_WARN("Ignored read from cell ", DataKind::types, "[", o, "...", o + size - 1, "]",
×
716
                  " because it overlaps with ", cells.size(), " cells");
717
        /*
718
            TODO: we can apply here "Value Recomposition" a la Mine'06 (https://arxiv.org/pdf/cs/0703074.pdf)
719
                to construct values of some type from a sequence of bytes.
720
                It can be endian-independent but it would more precise if we choose between little- and big-endian.
721
        */
722
    } else {
4✔
723
        // Check whether the kind is uniform across the entire interval.
724
        auto lb = i.lb().number();
×
725
        auto ub = i.ub().number();
×
726
        if (lb.has_value() && ub.has_value()) {
×
727
            Number fullwidth = ub.value() - lb.value() + width;
×
728
            if (lb->fits<uint32_t>() && fullwidth.fits<uint32_t>()) {
×
729
                auto [only_num, only_non_num] =
×
730
                    num_bytes.uniformity(lb->narrow<uint32_t>(), fullwidth.narrow<uint32_t>());
×
731
                if (only_num) {
×
732
                    return T_NUM;
×
733
                }
734
            }
735
        }
×
736
    }
11,574✔
737
    return {};
×
738
}
739

740
// We are about to write to a given range of bytes on the stack.
741
// Any cells covering that range need to be removed, and any cells that only
742
// partially cover that range can be split such that any non-covered portions become new cells.
743
static std::optional<std::pair<offset_t, unsigned>> split_and_find_var(const ArrayDomain& array_domain,
319,444✔
744
                                                                       NumAbsDomain& inv, const DataKind kind,
745
                                                                       const Interval& idx, const Interval& elem_size) {
746
    if (kind == DataKind::svalues || kind == DataKind::uvalues) {
295,249✔
747
        array_domain.split_number_var(inv, kind, idx, elem_size);
68,000✔
748
    }
749
    return kill_and_find_var(inv, kind, idx, elem_size);
295,249✔
750
}
751

752
std::optional<Variable> ArrayDomain::store(NumAbsDomain& inv, const DataKind kind, const Interval& idx,
49,010✔
753
                                           const Interval& elem_size) {
754
    if (auto maybe_cell = split_and_find_var(*this, inv, kind, idx, elem_size)) {
49,010✔
755
        // perform strong update
756
        auto [offset, size] = *maybe_cell;
49,002✔
757
        Variable v = lookup_array_map(kind).mk_cell(offset, size).get_scalar(kind);
49,002✔
758
        return v;
49,002✔
759
    }
760
    return {};
8✔
761
}
762

763
std::optional<Variable> ArrayDomain::store_type(TypeDomain& inv, const Interval& idx, const Interval& width,
24,184✔
764
                                                const bool is_num) {
765
    constexpr auto kind = DataKind::types;
24,184✔
766
    if (auto maybe_cell = split_and_find_var(*this, inv.inv, kind, idx, width)) {
24,184✔
767
        // perform strong update
768
        auto [offset, size] = *maybe_cell;
24,180✔
769
        if (is_num) {
24,180✔
770
            num_bytes.reset(offset, size);
23,622✔
771
        } else {
772
            num_bytes.havoc(offset, size);
558✔
773
        }
774
        Variable v = lookup_array_map(kind).mk_cell(offset, size).get_scalar(kind);
24,180✔
775
        return v;
24,180✔
776
    } else {
777
        using namespace dsl_syntax;
2✔
778
        // havoc the entire range
779
        const auto [lb, ub] = as_numbytes_range(idx, width);
4✔
780
        num_bytes.havoc(lb, ub);
4✔
781
    }
782
    return {};
4✔
783
}
784

785
void ArrayDomain::havoc(NumAbsDomain& inv, const DataKind kind, const Interval& idx, const Interval& elem_size) {
222,044✔
786
    split_and_find_var(*this, inv, kind, idx, elem_size);
222,044✔
787
}
222,044✔
788

789
void ArrayDomain::havoc_type(TypeDomain& inv, const Interval& idx, const Interval& elem_size) {
24,206✔
790
    constexpr auto kind = DataKind::types;
24,206✔
791
    auto maybe_cell = split_and_find_var(*this, inv.inv, kind, idx, elem_size);
24,206✔
792
    if (maybe_cell) {
24,206✔
793
        auto [offset, size] = *maybe_cell;
24,202✔
794
        num_bytes.havoc(offset, size);
24,202✔
795
    }
796
}
24,206✔
797

798
void ArrayDomain::store_numbers(const Interval& _idx, const Interval& _width) {
1,088✔
799

800
    if (is_bottom()) {
1,088✔
801
        return;
×
802
    }
803

804
    const std::optional<Number> idx_n = _idx.singleton();
1,088✔
805
    if (!idx_n) {
1,088✔
806
        CRAB_WARN("array expansion store range ignored because ", "lower bound is not constant");
×
807
        return;
×
808
    }
809

810
    const std::optional<Number> width = _width.singleton();
1,088✔
811
    if (!width) {
1,088✔
812
        CRAB_WARN("array expansion store range ignored because ", "upper bound is not constant");
×
813
        return;
×
814
    }
815

816
    if (*idx_n + *width > EBPF_TOTAL_STACK_SIZE) {
1,088✔
817
        CRAB_WARN("array expansion store range ignored because ",
×
818
                  "the number of elements is larger than default limit of ", EBPF_TOTAL_STACK_SIZE);
819
        return;
×
820
    }
821
    num_bytes.reset(idx_n->narrow<int>(), width->narrow<int>());
1,088✔
822
}
1,088✔
823

824
void ArrayDomain::set_to_top() { num_bytes.set_to_top(); }
×
825

826
void ArrayDomain::set_to_bottom() { num_bytes.set_to_bottom(); }
×
827

828
bool ArrayDomain::is_bottom() const { return num_bytes.is_bottom(); }
9,658✔
829

830
bool ArrayDomain::is_top() const { return num_bytes.is_top(); }
×
831

832
StringInvariant ArrayDomain::to_set() const { return num_bytes.to_set(); }
1,384✔
833

834
bool ArrayDomain::operator<=(const ArrayDomain& other) const { return num_bytes <= other.num_bytes; }
502✔
835

836
bool ArrayDomain::operator==(const ArrayDomain& other) const { return num_bytes == other.num_bytes; }
×
837

838
void ArrayDomain::operator|=(const ArrayDomain& other) {
120✔
839
    if (is_bottom()) {
120✔
840
        *this = other;
841
        return;
842
    }
843
    num_bytes |= other.num_bytes;
120✔
844
}
845

846
void ArrayDomain::operator|=(ArrayDomain&& other) {
17,020✔
847
    if (is_bottom()) {
17,020✔
848
        *this = std::move(other);
849
        return;
850
    }
851
    num_bytes |= std::move(other.num_bytes);
17,020✔
852
}
853

854
ArrayDomain ArrayDomain::operator|(const ArrayDomain& other) const { return ArrayDomain(num_bytes | other.num_bytes); }
×
855

856
ArrayDomain ArrayDomain::operator&(const ArrayDomain& other) const { return ArrayDomain(num_bytes & other.num_bytes); }
38✔
857

858
ArrayDomain ArrayDomain::widen(const ArrayDomain& other) const { return ArrayDomain(num_bytes | other.num_bytes); }
84✔
859

860
ArrayDomain ArrayDomain::narrow(const ArrayDomain& other) const { return ArrayDomain(num_bytes & other.num_bytes); }
×
861

862
std::ostream& operator<<(std::ostream& o, const ArrayDomain& dom) { return o << dom.num_bytes; }
×
863
} // namespace prevail
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc