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

Alan-Jowett / ebpf-verifier / 15194704016

22 May 2025 08:53AM UTC coverage: 88.11% (-0.07%) from 88.177%
15194704016

push

github

elazarg
uniform class names and explicit constructors for adapt_sgraph.hpp

Signed-off-by: Elazar Gershuni <elazarg@gmail.com>

27 of 30 new or added lines in 1 file covered. (90.0%)

481 existing lines in 33 files now uncovered.

8552 of 9706 relevant lines covered (88.11%)

9089054.61 hits per line

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

88.92
/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 "config.hpp"
16
#include "crab/array_domain.hpp"
17
#include "crab/dsl_syntax.hpp"
18
#include "crab_utils/num_safety.hpp"
19
#include "spec_type_descriptors.hpp"
20

21
namespace prevail {
22

23
using Index = uint64_t;
24

25
static bool maybe_between(const NumAbsDomain& dom, const ExtendedNumber& x, const LinearExpression& symb_lb,
96✔
26
                          const LinearExpression& symb_ub) {
27
    using namespace dsl_syntax;
48✔
28
    assert(x.is_finite());
96✔
29
    const LinearExpression num(*x.number());
144✔
30
    NumAbsDomain tmp(dom);
96✔
31
    tmp.add_constraint(num >= symb_lb);
96✔
32
    tmp.add_constraint(num <= symb_ub);
96✔
33
    return !tmp.is_bottom();
192✔
34
}
96✔
35

36
class offset_t final {
37
    Index _index{};
38
    int _prefix_length;
39

40
  public:
41
    static constexpr int bitsize = 8 * sizeof(Index);
42
    offset_t() : _prefix_length(bitsize) {}
2,999,154✔
43
    explicit offset_t(const Index index) : _index(index), _prefix_length(bitsize) {}
3,014,503✔
44
    offset_t(const Index index, const int prefix_length) : _index(index), _prefix_length(prefix_length) {}
7,676,800✔
45
    explicit operator int() const { return gsl::narrow<int>(_index); }
2,634,450✔
46
    operator Index() const { return _index; }
835,628,074✔
47
    [[nodiscard]]
48
    int prefix_length() const {
15,846,684✔
49
        return _prefix_length;
15,846,684✔
50
    }
51

52
    Index operator[](const int n) const { return (_index >> (bitsize - 1 - n)) & 1; }
16,911,744✔
53
};
54

55
// NOTE: required by radix_tree
56
// Get the length of a key, which is the size usable with the [] operator.
57
[[maybe_unused]]
58
int radix_length(const offset_t& offset) {
15,846,684✔
59
    return offset.prefix_length();
15,846,684✔
60
}
61

62
// NOTE: required by radix_tree
63
// Get a range of bits out of the middle of a key, starting at [begin] for a given length.
64
[[maybe_unused]]
65
offset_t radix_substr(const offset_t& key, const int begin, const int length) {
7,676,800✔
66
    Index mask;
3,838,400✔
67

68
    if (length == offset_t::bitsize) {
7,627,445✔
69
        mask = 0;
25,027✔
70
    } else {
71
        mask = Index{1} << length;
7,577,391✔
72
    }
73

74
    mask -= 1;
7,676,800✔
75
    mask <<= offset_t::bitsize - length - begin;
7,676,800✔
76

77
    const Index value = (gsl::narrow_cast<Index>(key) & mask) << begin;
3,887,755✔
78
    return offset_t{value, length};
3,887,755✔
79
}
80

81
// NOTE: required by radix_tree
82
// Concatenate two bit patterns.
83
[[maybe_unused]]
84
offset_t radix_join(const offset_t& entry1, const offset_t& entry2) {
×
85
    const Index value = entry1 | (entry2 >> entry1.prefix_length());
×
86
    const int prefix_length = entry1.prefix_length() + entry2.prefix_length();
×
87
    return offset_t{value, prefix_length};
×
88
}
89

90
/***
91
   Conceptually, a cell is tuple of an array, offset, size, and
92
   scalar variable such that:
93

94
_scalar = array[_offset, _offset + 1, ..., _offset + _size - 1]
95

96
    For simplicity, we don't carry the array inside the cell class.
97
    Only, offset_map objects can create cells. They will consider the
98
            array when generating the scalar variable.
99
*/
100
class Cell final {
101
  private:
102
    friend class offset_map_t;
103

104
    offset_t _offset{};
105
    unsigned _size{};
106

107
    // Only offset_map_t can create cells
108
    Cell() = default;
320✔
109

110
    Cell(const offset_t offset, const unsigned size) : _offset(offset), _size(size) {}
856,060✔
111

112
    static Interval to_interval(const offset_t o, const unsigned size) {
2,634,450✔
113
        const Number lb{gsl::narrow<int>(o)};
2,634,450✔
114
        return {lb, lb + size - 1};
7,903,350✔
115
    }
2,634,450✔
116

117
  public:
118
    [[nodiscard]]
119
    Interval to_interval() const {
1,916,954✔
120
        return to_interval(_offset, _size);
1,916,954✔
121
    }
122

123
    [[nodiscard]]
124
    bool is_null() const {
416✔
125
        return _offset == 0 && _size == 0;
368✔
126
    }
127

128
    [[nodiscard]]
129
    offset_t get_offset() const {
634,256✔
130
        return _offset;
634,256✔
131
    }
132

133
    [[nodiscard]]
134
    Variable get_scalar(const DataKind kind) const {
160,328✔
135
        return Variable::cell_var(kind, gsl::narrow<Index>(_offset), _size);
240,492✔
136
    }
137

138
    // ignore the scalar variable
139
    bool operator==(const Cell& o) const { return to_interval() == o.to_interval(); }
1,186,568✔
140

141
    // ignore the scalar variable
142
    bool operator<(const Cell& o) const {
1,101,436✔
143
        if (_offset == o._offset) {
1,101,436✔
144
            return _size < o._size;
1,101,436✔
145
        }
146
        return _offset < o._offset;
×
147
    }
148

149
    // Return true if [o, o + size) definitely overlaps with the cell,
150
    // where o is a constant expression.
151
    [[nodiscard]]
152
    bool overlap(const offset_t& o, const unsigned size) const {
717,496✔
153
        const Interval x = to_interval();
717,496✔
154
        const Interval y = to_interval(o, size);
717,496✔
155
        const bool res = (!(x & y).is_bottom());
1,076,244✔
156
        CRAB_LOG("array-expansion-overlap",
717,496✔
157
                 std::cout << "**Checking if " << x << " overlaps with " << y << "=" << res << "\n";);
158
        return res;
717,496✔
159
    }
1,076,244✔
160

161
    // Return true if [symb_lb, symb_ub] may overlap with the cell,
162
    // where symb_lb and symb_ub are not constant expressions.
163
    [[nodiscard]]
164
    bool symbolic_overlap(const LinearExpression& symb_lb, const LinearExpression& symb_ub,
165
                          const NumAbsDomain& dom) const;
166

167
    friend std::ostream& operator<<(std::ostream& o, const Cell& c) { return o << "cell(" << c.to_interval() << ")"; }
×
168
};
169

170
// Return true if [symb_lb, symb_ub] may overlap with the cell,
171
// where symb_lb and symb_ub are not constant expressions.
172
bool Cell::symbolic_overlap(const LinearExpression& symb_lb, const LinearExpression& symb_ub,
96✔
173
                            const NumAbsDomain& dom) const {
174
    const Interval x = to_interval();
96✔
175
    return maybe_between(dom, x.lb(), symb_lb, symb_ub) || maybe_between(dom, x.ub(), symb_lb, symb_ub);
192✔
176
}
96✔
177

178
// Map offsets to cells
179
class offset_map_t final {
3,818✔
180
  private:
181
    friend class ArrayDomain;
182

183
    using cell_set_t = std::set<Cell>;
184

185
    /*
186
      The keys in the patricia tree are processing in big-endian order. This means that the keys are sorted.
187
      Sortedness is very important to efficiently perform operations such as checking for overlap cells.
188
      Since keys are treated as bit patterns, negative offsets can be used, but they are treated as large unsigned
189
      numbers.
190
    */
191
    using PatriciaTree = radix_tree<offset_t, cell_set_t>;
192

193
    PatriciaTree _map;
194

195
    // for algorithm::lower_bound and algorithm::upper_bound
196
    struct CompareBinding {
197
        bool operator()(const PatriciaTree::value_type& kv, const offset_t& o) const { return kv.first < o; }
1,975,042✔
198
        bool operator()(const offset_t& o, const PatriciaTree::value_type& kv) const { return o < kv.first; }
1,949,590✔
199
        bool operator()(const PatriciaTree::value_type& kv1, const PatriciaTree::value_type& kv2) const {
200
            return kv1.first < kv2.first;
201
        }
202
    };
203

204
    void remove_cell(const Cell& c);
205

206
    void insert_cell(const Cell& c);
207

208
    [[nodiscard]]
209
    std::optional<Cell> get_cell(offset_t o, unsigned size);
210

211
    Cell mk_cell(offset_t o, unsigned size);
212

213
  public:
214
    offset_map_t() = default;
7,636✔
215

216
    [[nodiscard]]
217
    bool empty() const {
218
        return _map.empty();
219
    }
220

221
    [[nodiscard]]
222
    std::size_t size() const {
223
        return _map.size();
224
    }
225

226
    void operator-=(const Cell& c) { remove_cell(c); }
18,730✔
227

228
    void operator-=(const std::vector<Cell>& cells) {
12,366✔
229
        for (const auto& c : cells) {
31,096✔
230
            this->operator-=(c);
18,730✔
231
        }
232
    }
12,366✔
233

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

237
    [[nodiscard]]
238
    std::vector<Cell> get_overlap_cells_symbolic_offset(const NumAbsDomain& dom, const LinearExpression& symb_lb,
239
                                                        const LinearExpression& symb_ub);
240

241
    friend std::ostream& operator<<(std::ostream& o, offset_map_t& m);
242

243
    /* Operations needed if used as value in a separate_domain */
244
    [[nodiscard]]
245
    bool is_top() const {
246
        return empty();
247
    }
248

249
    [[nodiscard]]
250
    // ReSharper disable once CppMemberFunctionMayBeStatic
251
    bool is_bottom() const {
252
        return false;
253
    }
254
    /*
255
       We don't distinguish between bottom and top.
256
       This is fine because separate_domain only calls bottom if operator[] is called over a bottom state.
257
       Thus, we will make sure that we don't call operator[] in that case.
258
    */
259
    static offset_map_t bottom() { return offset_map_t(); }
260
    static offset_map_t top() { return offset_map_t(); }
261
};
262

263
void offset_map_t::remove_cell(const Cell& c) {
305,396✔
264
    const offset_t key = c.get_offset();
305,396✔
265
    _map[key].erase(c);
305,396✔
266
}
305,396✔
267

268
[[nodiscard]]
269
std::vector<Cell> offset_map_t::get_overlap_cells_symbolic_offset(const NumAbsDomain& dom,
20✔
270
                                                                  const LinearExpression& symb_lb,
271
                                                                  const LinearExpression& symb_ub) {
272
    std::vector<Cell> out;
20✔
273
    for (const auto& [_offset, o_cells] : _map) {
510✔
274
        // All cells in o_cells have the same offset. They only differ in the size.
275
        // If the largest cell overlaps with [offset, offset + size)
276
        // then the rest of cells are considered to overlap.
277
        // This is an over-approximation because [offset, offset+size) can overlap
278
        // with the largest cell, but it doesn't necessarily overlap with smaller cells.
279
        // For efficiency, we assume it overlaps with all.
280
        Cell largest_cell;
320✔
281
        for (const Cell& c : o_cells) {
416✔
282
            if (largest_cell.is_null()) {
96✔
283
                largest_cell = c;
96✔
284
            } else {
285
                assert(c.get_offset() == largest_cell.get_offset());
×
286
                if (largest_cell < c) {
×
287
                    largest_cell = c;
×
288
                }
289
            }
290
        }
291
        if (!largest_cell.is_null()) {
480✔
292
            if (largest_cell.symbolic_overlap(symb_lb, symb_ub, dom)) {
96✔
293
                for (const auto& c : o_cells) {
192✔
294
                    out.push_back(c);
96✔
295
                }
296
            }
297
        }
298
    }
299
    return out;
20✔
300
}
×
301

302
void offset_map_t::insert_cell(const Cell& c) { _map[c.get_offset()].insert(c); }
328,860✔
303

304
std::optional<Cell> offset_map_t::get_cell(const offset_t o, const unsigned size) {
527,200✔
305
    cell_set_t& cells = _map[o];
527,200✔
306
    const auto it = cells.find(Cell(o, size));
527,200✔
307
    if (it != cells.end()) {
527,200✔
308
        return *it;
191,416✔
309
    }
310
    return {};
335,784✔
311
}
312

313
Cell offset_map_t::mk_cell(const offset_t o, const unsigned size) {
99,240✔
314
    // TODO: check array is the array associated to this offset map
315

316
    if (const auto maybe_c = get_cell(o, size)) {
99,240✔
317
        return *maybe_c;
57,046✔
318
    }
319
    // create a new scalar variable for representing the contents
320
    // of bytes array[o,o+1,..., o+size-1]
321
    const Cell c(o, size);
42,194✔
322
    insert_cell(c);
42,194✔
323
    return c;
42,194✔
324
}
325

326
// Return all cells that might overlap with (o, size).
327
std::vector<Cell> offset_map_t::get_overlap_cells(const offset_t o, const unsigned size) {
394,346✔
328
    std::vector<Cell> out;
394,346✔
329
    constexpr CompareBinding comp;
197,173✔
330

331
    bool added = false;
394,346✔
332
    auto maybe_c = get_cell(o, size);
394,346✔
333
    if (!maybe_c) {
394,346✔
334
        maybe_c = Cell(o, size);
286,666✔
335
        insert_cell(*maybe_c);
286,666✔
336
        added = true;
143,333✔
337
    }
338

339
    auto lb_it = std::lower_bound(_map.begin(), _map.end(), o, comp);
394,346✔
340
    if (lb_it != _map.end()) {
394,346✔
341
        // Store _map[begin,...,lb_it] into a vector so that we can
342
        // go backwards from lb_it.
343
        //
344
        // TODO: give support for reverse iterator in patricia_tree.
345
        std::vector<cell_set_t> upto_lb;
394,346✔
346
        upto_lb.reserve(std::distance(_map.begin(), lb_it));
788,692✔
347
        for (auto it = _map.begin(), et = lb_it; it != et; ++it) {
8,455,030✔
348
            upto_lb.push_back(it->second);
8,060,684✔
349
        }
350
        upto_lb.push_back(lb_it->second);
394,346✔
351

352
        for (int i = gsl::narrow<int>(upto_lb.size() - 1); i >= 0; --i) {
792,568✔
353
            ///////
354
            // All the cells in upto_lb[i] have the same offset. They
355
            // just differ in the size.
356
            //
357
            // If none of the cells in upto_lb[i] overlap with (o, size)
358
            // we can stop.
359
            ////////
360
            bool continue_outer_loop = false;
743,866✔
361
            for (const Cell& x : upto_lb[i]) {
1,297,230✔
362
                if (x.overlap(o, size)) {
553,364✔
363
                    if (x != *maybe_c) {
416,916✔
364
                        // FIXME: we might have some duplicates. this is a very drastic solution.
365
                        if (std::ranges::find(out, x) == out.end()) {
22,570✔
366
                            out.push_back(x);
22,570✔
367
                        }
368
                    }
369
                    continue_outer_loop = true;
208,458✔
370
                }
371
            }
372
            if (!continue_outer_loop) {
743,866✔
373
                break;
172,822✔
374
            }
375
        }
376
    }
394,346✔
377

378
    // search for overlapping cells > o
379
    auto ub_it = std::upper_bound(_map.begin(), _map.end(), o, comp);
394,346✔
380
    for (; ub_it != _map.end(); ++ub_it) {
405,622✔
381
        bool continue_outer_loop = false;
370,720✔
382
        for (const Cell& x : ub_it->second) {
523,576✔
383
            if (x.overlap(o, size)) {
152,856✔
384
                // FIXME: we might have some duplicates. this is a very drastic solution.
385
                if (std::ranges::find(out, x) == out.end()) {
11,276✔
386
                    out.push_back(x);
11,276✔
387
                }
388
                continue_outer_loop = true;
5,638✔
389
            }
390
        }
391
        if (!continue_outer_loop) {
370,720✔
392
            break;
179,722✔
393
        }
394
    }
395

396
    // do not forget the rest of overlapping cells == o
397
    for (auto it = ++lb_it, et = ub_it; it != et; ++it) {
405,622✔
398
        bool continue_outer_loop = false;
11,276✔
399
        for (const Cell& x : it->second) {
22,552✔
400
            if (x == *maybe_c) { // we don't put it in out
11,276✔
401
                continue;
×
402
            }
403
            if (x.overlap(o, size)) {
11,276✔
404
                if (std::ranges::find(out, x) == out.end()) {
11,276✔
405
                    out.push_back(x);
×
406
                }
407
                continue_outer_loop = true;
5,638✔
408
            }
409
        }
410
        if (!continue_outer_loop) {
11,276✔
411
            break;
412
        }
413
    }
414

415
    if (added) {
394,346✔
416
        remove_cell(*maybe_c);
286,666✔
417
    }
418
    return out;
591,519✔
419
}
×
420

421
// We use a global array map
422
using array_map_t = std::unordered_map<DataKind, offset_map_t>;
423

424
static thread_local LazyAllocator<array_map_t> thread_local_array_map;
282,633✔
425

426
void clear_thread_local_state() { thread_local_array_map.clear(); }
2,502✔
427

428
static offset_map_t& lookup_array_map(const DataKind kind) { return (*thread_local_array_map)[kind]; }
562,764✔
429

430
void ArrayDomain::initialize_numbers(const int lb, const int width) {
256✔
431
    num_bytes.reset(lb, width);
256✔
432
    lookup_array_map(DataKind::svalues).mk_cell(offset_t{gsl::narrow_cast<Index>(lb)}, width);
256✔
433
}
256✔
434

435
std::ostream& operator<<(std::ostream& o, offset_map_t& m) {
×
436
    if (m._map.empty()) {
×
437
        o << "empty";
×
438
    } else {
439
        for (const auto& [_offset, cells] : m._map) {
×
440
            o << "{";
×
441
            for (auto cit = cells.begin(), cet = cells.end(); cit != cet;) {
×
442
                o << *cit;
×
443
                ++cit;
×
444
                if (cit != cet) {
×
445
                    o << ",";
×
446
                }
447
            }
448
            o << "}\n";
×
449
        }
450
    }
451
    return o;
×
452
}
453

454
// Create a new cell that is a subset of an existing cell.
455
void ArrayDomain::split_cell(NumAbsDomain& inv, const DataKind kind, const int cell_start_index,
976✔
456
                             const unsigned int len) const {
457
    assert(kind == DataKind::svalues || kind == DataKind::uvalues);
976✔
458

459
    // Get the values from the indicated stack range.
460
    const std::optional<LinearExpression> svalue = load(inv, DataKind::svalues, Number(cell_start_index), len);
976✔
461
    const std::optional<LinearExpression> uvalue = load(inv, DataKind::uvalues, Number(cell_start_index), len);
976✔
462

463
    // Create a new cell for that range.
464
    offset_map_t& offset_map = lookup_array_map(kind);
976✔
465
    const Cell new_cell = offset_map.mk_cell(offset_t{gsl::narrow_cast<Index>(cell_start_index)}, len);
976✔
466
    inv.assign(new_cell.get_scalar(DataKind::svalues), svalue);
976✔
467
    inv.assign(new_cell.get_scalar(DataKind::uvalues), uvalue);
1,464✔
468
}
976✔
469

470
// Prepare to havoc bytes in the middle of a cell by potentially splitting the cell if it is numeric,
471
// into the part to the left of the havoced portion, and the part to the right of the havoced portion.
472
void ArrayDomain::split_number_var(NumAbsDomain& inv, DataKind kind, const LinearExpression& i,
65,084✔
473
                                   const LinearExpression& elem_size) const {
474
    assert(kind == DataKind::svalues || kind == DataKind::uvalues);
65,084✔
475
    offset_map_t& offset_map = lookup_array_map(kind);
65,084✔
476
    Interval ii = inv.eval_interval(i);
65,084✔
477
    std::optional<Number> n = ii.singleton();
65,084✔
478
    if (!n) {
65,084✔
479
        // We can only split a singleton offset.
480
        return;
2✔
481
    }
482
    Interval i_elem_size = inv.eval_interval(elem_size);
65,080✔
483
    std::optional<Number> n_bytes = i_elem_size.singleton();
65,080✔
484
    if (!n_bytes) {
65,080✔
485
        // We can only split a singleton size.
486
        return;
×
487
    }
488
    auto size = n_bytes->narrow<unsigned int>();
65,080✔
489
    offset_t o(n->narrow<Index>());
65,080✔
490

491
    std::vector<Cell> cells = offset_map.get_overlap_cells(o, size);
65,080✔
492
    for (Cell const& c : cells) {
77,874✔
493
        Interval intv = c.to_interval();
12,794✔
494
        int cell_start_index = intv.lb().narrow<int>();
19,191✔
495
        int cell_end_index = intv.ub().narrow<int>();
19,191✔
496

497
        if (!this->num_bytes.all_num(cell_start_index, cell_end_index + 1) ||
17,090✔
498
            cell_end_index + 1UL < cell_start_index + sizeof(int64_t)) {
8,592✔
499
            // We can only split numeric cells of size 8 or less.
500
            continue;
10,704✔
501
        }
502

503
        if (!inv.eval_interval(c.get_scalar(kind)).is_singleton()) {
4,180✔
504
            // We can only split cells with a singleton value.
505
            continue;
1,278✔
506
        }
507
        if (gsl::narrow_cast<Index>(cell_start_index) < o) {
812✔
508
            // Use the bytes to the left of the specified range.
509
            split_cell(inv, kind, cell_start_index, gsl::narrow<unsigned int>(o - cell_start_index));
270✔
510
        }
511
        if (o + size < cell_end_index + 1UL) {
812✔
512
            // Use the bytes to the right of the specified range.
513
            split_cell(inv, kind, gsl::narrow<int>(o + size),
706✔
514
                       gsl::narrow<unsigned int>(cell_end_index - (o + size - 1)));
706✔
515
        }
516
    }
12,794✔
517
}
130,168✔
518

519
// we can only treat this as non-member because we use global state
520
static std::optional<std::pair<offset_t, unsigned>>
521
kill_and_find_var(NumAbsDomain& inv, DataKind kind, const LinearExpression& i, const LinearExpression& elem_size) {
324,570✔
522
    std::optional<std::pair<offset_t, unsigned>> res;
324,570✔
523

524
    offset_map_t& offset_map = lookup_array_map(kind);
324,570✔
525
    Interval ii = inv.eval_interval(i);
324,570✔
526
    std::vector<Cell> cells;
324,570✔
527
    if (std::optional<Number> n = ii.singleton()) {
324,570✔
528
        Interval i_elem_size = inv.eval_interval(elem_size);
324,550✔
529
        if (auto n_bytes = i_elem_size.singleton()) {
324,550✔
530
            auto size = n_bytes->narrow<unsigned int>();
324,550✔
531
            // -- Constant index: kill overlapping cells
532
            offset_t o(n->narrow<Index>());
324,550✔
533
            cells = offset_map.get_overlap_cells(o, size);
486,825✔
534
            res = std::make_pair(o, size);
324,550✔
535
        }
324,550✔
536
    }
486,835✔
537
    if (!res) {
324,570✔
538
        // -- Non-constant index: kill overlapping cells
539
        cells = offset_map.get_overlap_cells_symbolic_offset(inv, i, i.plus(elem_size));
30✔
540
    }
541
    if (!cells.empty()) {
324,570✔
542
        // Forget the scalars from the numerical domain
543
        for (const auto& c : cells) {
31,096✔
544
            inv.havoc(c.get_scalar(kind));
18,730✔
545

546
            // Forget signed and unsigned values together.
547
            if (kind == DataKind::svalues) {
18,730✔
548
                inv.havoc(c.get_scalar(DataKind::uvalues));
6,636✔
549
            } else if (kind == DataKind::uvalues) {
12,094✔
550
                inv.havoc(c.get_scalar(DataKind::svalues));
6,222✔
551
            }
552
        }
553
        // Remove the cells. If needed again they will be re-created.
554
        offset_map -= cells;
12,366✔
555
    }
556
    return res;
649,140✔
557
}
486,855✔
558

559
bool ArrayDomain::all_num(const NumAbsDomain& inv, const LinearExpression& lb, const LinearExpression& ub) const {
5,626✔
560
    const auto min_lb = inv.eval_interval(lb).lb().number();
11,252✔
561
    const auto max_ub = inv.eval_interval(ub).ub().number();
11,252✔
562
    if (!min_lb || !max_ub || !min_lb->fits<int32_t>() || !max_ub->fits<int32_t>()) {
5,626✔
563
        return false;
2✔
564
    }
565

566
    // The all_num() call requires a legal range. If we have an illegal range,
567
    // we should have already generated an error about the invalid range so just
568
    // return true now to avoid an extra error about a non-numeric range.
569
    if (*min_lb >= *max_ub) {
5,622✔
570
        return true;
1✔
571
    }
572

573
    return this->num_bytes.all_num(min_lb->narrow<int32_t>(), max_ub->narrow<int32_t>());
5,620✔
574
}
5,626✔
575

576
// Get the number of bytes, starting at offset, that are known to be numbers.
577
int ArrayDomain::min_all_num_size(const NumAbsDomain& inv, const Variable offset) const {
846,155✔
578
    const auto min_lb = inv.eval_interval(offset).lb().number();
2,115,389✔
579
    const auto max_ub = inv.eval_interval(offset).ub().number();
2,115,389✔
580
    if (!min_lb || !max_ub || !min_lb->fits<int32_t>() || !max_ub->fits<int32_t>()) {
846,155✔
581
        return 0;
412,788✔
582
    }
583
    const auto lb = min_lb->narrow<int>();
20,578✔
584
    const auto ub = max_ub->narrow<int>();
20,578✔
585
    return std::max(0, this->num_bytes.all_num_width(lb) - (ub - lb));
30,116✔
586
}
846,155✔
587

588
// Get one byte of a value.
589
std::optional<uint8_t> get_value_byte(const NumAbsDomain& inv, const offset_t o, const int width) {
24,348✔
590
    const Variable v = Variable::cell_var(DataKind::svalues, (o / width) * width, width);
36,522✔
591
    const std::optional<Number> t = inv.eval_interval(v).singleton();
36,522✔
592
    if (!t) {
24,348✔
593
        return {};
13,342✔
594
    }
595
    Index n = t->cast_to<Index>();
11,006✔
596

597
    // Convert value to bytes of the appropriate endian-ness.
598
    switch (width) {
11,006✔
599
    case sizeof(uint8_t): break;
79✔
600
    case sizeof(uint16_t):
140✔
601
        if (thread_local_options.big_endian) {
140✔
UNCOV
602
            n = boost::endian::native_to_big<uint16_t>(n);
×
603
        } else {
604
            n = boost::endian::native_to_little<uint16_t>(n);
140✔
605
        }
606
        break;
70✔
607
    case sizeof(uint32_t):
620✔
608
        if (thread_local_options.big_endian) {
620✔
UNCOV
609
            n = boost::endian::native_to_big<uint32_t>(n);
×
610
        } else {
611
            n = boost::endian::native_to_little<uint32_t>(n);
620✔
612
        }
613
        break;
310✔
614
    case sizeof(Index):
10,088✔
615
        if (thread_local_options.big_endian) {
10,088✔
616
            n = boost::endian::native_to_big<Index>(n);
48✔
617
        } else {
618
            n = boost::endian::native_to_little<Index>(n);
5,020✔
619
        }
620
        break;
5,044✔
UNCOV
621
    default: CRAB_ERROR("Unexpected width ", width);
×
622
    }
623
    const auto bytes = reinterpret_cast<uint8_t*>(&n);
11,006✔
624
    return bytes[o % width];
11,006✔
625
}
24,348✔
626

627
std::optional<LinearExpression> ArrayDomain::load(const NumAbsDomain& inv, DataKind kind, const LinearExpression& i,
47,968✔
628
                                                  int width) const {
629
    Interval ii = inv.eval_interval(i);
47,968✔
630
    if (std::optional<Number> n = ii.singleton()) {
47,968✔
631
        offset_map_t& offset_map = lookup_array_map(kind);
42,910✔
632
        int64_t k = n->narrow<int64_t>();
42,910✔
633
        if (kind == DataKind::types) {
42,910✔
634
            auto [only_num, only_non_num] = num_bytes.uniformity(k, width);
12,008✔
635
            if (only_num) {
12,008✔
636
                return T_NUM;
8,896✔
637
            }
638
            if (!only_non_num || width != 8) {
3,112✔
639
                return {};
400✔
640
            }
641
        }
642
        offset_t o(k);
33,614✔
643
        unsigned size = to_unsigned(width);
33,614✔
644
        if (auto cell = lookup_array_map(kind).get_cell(o, size)) {
33,614✔
645
            return cell->get_scalar(kind);
40,035✔
646
        }
647
        if (kind == DataKind::svalues || kind == DataKind::uvalues) {
6,924✔
648
            // Copy bytes into result_buffer, taking into account that the
649
            // bytes might be in different stack variables and might be unaligned.
650
            uint8_t result_buffer[8];
651
            bool found = true;
11,343✔
652
            for (unsigned int index = 0; index < size; index++) {
16,846✔
653
                offset_t byte_offset{o + index};
13,998✔
654
                std::optional<uint8_t> b = get_value_byte(inv, byte_offset, 8);
13,998✔
655
                if (!b) {
13,998✔
656
                    b = get_value_byte(inv, byte_offset, 4);
3,910✔
657
                    if (!b) {
3,910✔
658
                        b = get_value_byte(inv, byte_offset, 2);
3,290✔
659
                        if (!b) {
3,290✔
660
                            b = get_value_byte(inv, byte_offset, 1);
3,150✔
661
                        }
662
                    }
663
                }
664
                if (b) {
13,998✔
665
                    result_buffer[index] = *b;
11,006✔
666
                } else {
667
                    found = false;
2,992✔
668
                    break;
2,992✔
669
                }
670
            }
671
            if (found) {
4,416✔
672
                // We have an aligned result in result_buffer so we can now
673
                // convert to an integer.
674
                if (size == 1) {
2,848✔
675
                    return *result_buffer;
2,395✔
676
                }
677
                if (size == 2) {
2,474✔
678
                    uint16_t b = *reinterpret_cast<uint16_t*>(result_buffer);
452✔
679
                    if (thread_local_options.big_endian) {
452✔
680
                        b = boost::endian::native_to_big<uint16_t>(b);
12✔
681
                    } else {
682
                        b = boost::endian::native_to_little<uint16_t>(b);
446✔
683
                    }
684
                    return b;
452✔
685
                }
686
                if (size == 4) {
2,022✔
687
                    uint32_t b = *reinterpret_cast<uint32_t*>(result_buffer);
1,306✔
688
                    if (thread_local_options.big_endian) {
1,306✔
689
                        b = boost::endian::native_to_big<uint32_t>(b);
12✔
690
                    } else {
691
                        b = boost::endian::native_to_little<uint32_t>(b);
647✔
692
                    }
693
                    return b;
1,306✔
694
                }
695
                if (size == 8) {
716✔
696
                    Index b = *reinterpret_cast<Index*>(result_buffer);
76✔
697
                    if (thread_local_options.big_endian) {
76✔
698
                        b = boost::endian::native_to_big<Index>(b);
4✔
699
                    } else {
700
                        b = boost::endian::native_to_little<Index>(b);
36✔
701
                    }
702
                    return kind == DataKind::uvalues ? Number(b) : Number(to_signed(b));
114✔
703
                }
704
            }
705
        }
706

707
        std::vector<Cell> cells = offset_map.get_overlap_cells(o, size);
4,716✔
708
        if (cells.empty()) {
4,716✔
709
            Cell c = offset_map.mk_cell(o, size);
2,654✔
710
            // Here it's ok to do assignment (instead of expand) because c is not a summarized variable.
711
            // Otherwise, it would be unsound.
712
            return c.get_scalar(kind);
2,654✔
713
        }
714
        CRAB_WARN("Ignored read from cell ", kind, "[", o, "...", o + size - 1, "]", " because it overlaps with ",
2,062✔
715
                  cells.size(), " cells");
716
        /*
717
            TODO: we can apply here "Value Recomposition" a la Mine'06 (https://arxiv.org/pdf/cs/0703074.pdf)
718
                to construct values of some type from a sequence of bytes.
719
                It can be endian-independent but it would more precise if we choose between little- and big-endian.
720
        */
721
    } else if (kind == DataKind::types) {
9,774✔
722
        // Check whether the kind is uniform across the entire interval.
723
        auto lb = ii.lb().number();
1,020✔
724
        auto ub = ii.ub().number();
1,020✔
725
        if (lb.has_value() && ub.has_value()) {
510✔
726
            Number fullwidth = ub.value() - lb.value() + width;
9✔
727
            if (lb->fits<uint32_t>() && fullwidth.fits<uint32_t>()) {
6✔
728
                auto [only_num, only_non_num] =
9✔
729
                    num_bytes.uniformity(lb->narrow<uint32_t>(), fullwidth.narrow<uint32_t>());
6✔
730
                if (only_num) {
6✔
731
                    return T_NUM;
4✔
732
                }
733
            }
734
        }
6✔
735
    } else {
512✔
736
        // TODO: we can be more precise here
737
        CRAB_WARN("array expansion: ignored array load because of non-constant array index ", i);
5,832✔
738
    }
44,410✔
739
    return {};
7,116✔
740
}
47,968✔
741

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

755
std::optional<Variable> ArrayDomain::store(NumAbsDomain& inv, const DataKind kind, const LinearExpression& idx,
63,690✔
756
                                           const LinearExpression& elem_size, const LinearExpression& val) {
757
    if (auto maybe_cell = split_and_find_var(*this, inv, kind, idx, elem_size)) {
63,690✔
758
        // perform strong update
759
        auto [offset, size] = *maybe_cell;
63,686✔
760
        if (kind == DataKind::types) {
63,686✔
UNCOV
761
            const std::optional<Number> t = inv.eval_interval(val).singleton();
×
UNCOV
762
            if (t == Number{T_NUM}) {
×
UNCOV
763
                num_bytes.reset(offset, size);
×
764
            } else {
765
                num_bytes.havoc(offset, size);
×
766
            }
UNCOV
767
        }
×
768
        Variable v = lookup_array_map(kind).mk_cell(offset, size).get_scalar(kind);
63,686✔
769
        return v;
63,686✔
770
    }
771
    return {};
4✔
772
}
773

774
std::optional<Variable> ArrayDomain::store_type(NumAbsDomain& inv, const LinearExpression& idx,
31,670✔
775
                                                const LinearExpression& elem_size, const LinearExpression& val) {
776
    constexpr auto kind = DataKind::types;
31,670✔
777
    if (auto maybe_cell = split_and_find_var(*this, inv, kind, idx, elem_size)) {
31,670✔
778
        // perform strong update
779
        auto [offset, size] = *maybe_cell;
31,668✔
780
        const std::optional<Number> t = inv.eval_interval(val).singleton();
31,668✔
781
        if (t == Number{T_NUM}) {
31,668✔
782
            num_bytes.reset(offset, size);
30,904✔
783
        } else {
784
            num_bytes.havoc(offset, size);
764✔
785
        }
786
        Variable v = lookup_array_map(kind).mk_cell(offset, size).get_scalar(kind);
31,668✔
787
        return v;
31,668✔
788
    }
31,668✔
789
    return {};
2✔
790
}
791

792
void ArrayDomain::havoc(NumAbsDomain& inv, const DataKind kind, const LinearExpression& idx,
229,210✔
793
                        const LinearExpression& elem_size) {
794
    auto maybe_cell = split_and_find_var(*this, inv, kind, idx, elem_size);
229,210✔
795
    if (maybe_cell && kind == DataKind::types) {
229,210✔
796
        auto [offset, size] = *maybe_cell;
872✔
797
        num_bytes.havoc(offset, size);
872✔
798
    }
799
}
229,210✔
800

801
void ArrayDomain::store_numbers(const NumAbsDomain& inv, const Variable _idx, const Variable _width) {
850✔
802

803
    // TODO: this should be an user parameter.
804
    const Number max_num_elems = EBPF_TOTAL_STACK_SIZE;
850✔
805

806
    if (is_bottom()) {
850✔
807
        return;
808
    }
809

810
    const std::optional<Number> idx_n = inv.eval_interval(_idx).singleton();
1,275✔
811
    if (!idx_n) {
850✔
UNCOV
812
        CRAB_WARN("array expansion store range ignored because ", "lower bound is not constant");
×
UNCOV
813
        return;
×
814
    }
815

816
    const std::optional<Number> width = inv.eval_interval(_width).singleton();
1,275✔
817
    if (!width) {
850✔
UNCOV
818
        CRAB_WARN("array expansion store range ignored because ", "upper bound is not constant");
×
UNCOV
819
        return;
×
820
    }
821

822
    if (*idx_n + *width > max_num_elems) {
850✔
823
        CRAB_WARN("array expansion store range ignored because ",
2✔
824
                  "the number of elements is larger than default limit of ", max_num_elems);
825
        return;
2✔
826
    }
827
    num_bytes.reset(idx_n->narrow<size_t>(), width->narrow<int>());
848✔
828
}
853✔
829

UNCOV
830
void ArrayDomain::set_to_top() { num_bytes.set_to_top(); }
×
831

UNCOV
832
void ArrayDomain::set_to_bottom() { num_bytes.set_to_bottom(); }
×
833

834
bool ArrayDomain::is_bottom() const { return num_bytes.is_bottom(); }
12,966✔
835

836
bool ArrayDomain::is_top() const { return num_bytes.is_top(); }
×
837

838
StringInvariant ArrayDomain::to_set() const { return num_bytes.to_set(); }
1,396✔
839

840
bool ArrayDomain::operator<=(const ArrayDomain& other) const { return num_bytes <= other.num_bytes; }
76✔
841

UNCOV
842
bool ArrayDomain::operator==(const ArrayDomain& other) const { return num_bytes == other.num_bytes; }
×
843

844
void ArrayDomain::operator|=(const ArrayDomain& other) {
24,232✔
845
    if (is_bottom()) {
24,232✔
846
        *this = other;
847
        return;
848
    }
849
    num_bytes |= other.num_bytes;
24,232✔
850
}
851

852
ArrayDomain ArrayDomain::operator|(const ArrayDomain& other) const { return ArrayDomain(num_bytes | other.num_bytes); }
122✔
853

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

UNCOV
856
ArrayDomain ArrayDomain::widen(const ArrayDomain& other) const { return ArrayDomain(num_bytes | other.num_bytes); }
×
857

UNCOV
858
ArrayDomain ArrayDomain::widening_thresholds(const ArrayDomain& other, const Thresholds&) const {
×
UNCOV
859
    return widen(other);
×
860
}
861

UNCOV
862
ArrayDomain ArrayDomain::narrow(const ArrayDomain& other) const { return ArrayDomain(num_bytes & other.num_bytes); }
×
863

864
std::ostream& operator<<(std::ostream& o, const ArrayDomain& dom) { return o << dom.num_bytes; }
×
865
} // 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