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

vbpf / ebpf-verifier / 13958948275

19 Mar 2025 11:53PM UTC coverage: 88.194% (-0.5%) from 88.66%
13958948275

push

github

web-flow
Finite domain (#849)

* Move arithmetic and bit operations functions to finite_domain.cpp
* Remove operator-= (now havoc) and operator+= (now add_constraint)
* Abort early when registers are not usable; clear type variable instead of explicitly assigning T_UNINIT. Update YAML tests accordingly
---------
Signed-off-by: Elazar Gershuni <elazarg@gmail.com>

847 of 898 new or added lines in 11 files covered. (94.32%)

57 existing lines in 8 files now uncovered.

8628 of 9783 relevant lines covered (88.19%)

9034663.84 hits per line

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

88.55
/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 crab::domains {
22

23
using index_t = uint64_t;
24

25
static bool maybe_between(const NumAbsDomain& dom, const extended_number& x, const linear_expression_t& symb_lb,
96✔
26
                          const linear_expression_t& symb_ub) {
27
    using namespace dsl_syntax;
48✔
28
    assert(x.is_finite());
96✔
29
    const linear_expression_t 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_t _index{};
38
    int _prefix_length;
39

40
  public:
41
    static constexpr int bitsize = 8 * sizeof(index_t);
42
    offset_t() : _prefix_length(bitsize) {}
2,999,154✔
43
    explicit offset_t(const index_t index) : _index(index), _prefix_length(bitsize) {}
3,014,503✔
44
    offset_t(const index_t 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_t() 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_t 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_t mask;
3,838,400✔
67

68
    if (length == offset_t::bitsize) {
7,627,445✔
69
        mask = 0;
25,027✔
70
    } else {
71
        mask = index_t{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_t value = (gsl::narrow_cast<index_t>(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_t 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_t 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_t() = default;
320✔
109

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

112
    static interval_t to_interval(const offset_t o, const unsigned size) {
2,634,450✔
113
        const number_t 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_t 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_t get_scalar(const data_kind_t kind) const {
160,328✔
135
        return variable_t::cell_var(kind, gsl::narrow<index_t>(_offset), _size);
240,492✔
136
    }
137

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

141
    // ignore the scalar variable
142
    bool operator<(const cell_t& 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_t x = to_interval();
717,496✔
154
        const interval_t 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 linear_expression_t& symb_lb, const linear_expression_t& symb_ub,
165
                          const NumAbsDomain& dom) const;
166

167
    friend std::ostream& operator<<(std::ostream& o, const cell_t& 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_t::symbolic_overlap(const linear_expression_t& symb_lb, const linear_expression_t& symb_ub,
96✔
173
                              const NumAbsDomain& dom) const {
174
    const interval_t 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 array_domain_t;
182

183
    using cell_set_t = std::set<cell_t>;
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 patricia_tree_t = radix_tree<offset_t, cell_set_t>;
192

193
    patricia_tree_t _map;
194

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

204
    void remove_cell(const cell_t& c);
205

206
    void insert_cell(const cell_t& c);
207

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

211
    cell_t 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_t& c) { remove_cell(c); }
18,730✔
227

228
    void operator-=(const std::vector<cell_t>& 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_t> get_overlap_cells(offset_t o, unsigned size);
236

237
    [[nodiscard]]
238
    std::vector<cell_t> get_overlap_cells_symbolic_offset(const NumAbsDomain& dom, const linear_expression_t& symb_lb,
239
                                                          const linear_expression_t& 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_t& 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_t> offset_map_t::get_overlap_cells_symbolic_offset(const NumAbsDomain& dom,
20✔
270
                                                                    const linear_expression_t& symb_lb,
271
                                                                    const linear_expression_t& symb_ub) {
272
    std::vector<cell_t> 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_t largest_cell;
320✔
281
        for (const cell_t& 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_t& c) { _map[c.get_offset()].insert(c); }
328,860✔
303

304
std::optional<cell_t> 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_t(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_t 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_t 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_t> offset_map_t::get_overlap_cells(const offset_t o, const unsigned size) {
394,346✔
328
    std::vector<cell_t> out;
394,346✔
329
    constexpr compare_binding_t 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_t(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_t& 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_t& 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_t& 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<data_kind_t, offset_map_t>;
423

424
static thread_local lazy_allocator<array_map_t> thread_local_array_map;
282,628✔
425

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

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

430
void array_domain_t::initialize_numbers(const int lb, const int width) {
256✔
431
    num_bytes.reset(lb, width);
256✔
432
    lookup_array_map(data_kind_t::svalues).mk_cell(offset_t{gsl::narrow_cast<index_t>(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 array_domain_t::split_cell(NumAbsDomain& inv, const data_kind_t kind, const int cell_start_index,
976✔
456
                                const unsigned int len) const {
457
    assert(kind == data_kind_t::svalues || kind == data_kind_t::uvalues);
976✔
458

459
    // Get the values from the indicated stack range.
460
    const std::optional<linear_expression_t> svalue = load(inv, data_kind_t::svalues, number_t(cell_start_index), len);
976✔
461
    const std::optional<linear_expression_t> uvalue = load(inv, data_kind_t::uvalues, number_t(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_t new_cell = offset_map.mk_cell(offset_t{gsl::narrow_cast<index_t>(cell_start_index)}, len);
976✔
466
    inv.assign(new_cell.get_scalar(data_kind_t::svalues), svalue);
976✔
467
    inv.assign(new_cell.get_scalar(data_kind_t::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 array_domain_t::split_number_var(NumAbsDomain& inv, data_kind_t kind, const linear_expression_t& i,
65,084✔
473
                                      const linear_expression_t& elem_size) const {
474
    assert(kind == data_kind_t::svalues || kind == data_kind_t::uvalues);
65,084✔
475
    offset_map_t& offset_map = lookup_array_map(kind);
65,084✔
476
    interval_t ii = inv.eval_interval(i);
65,084✔
477
    std::optional<number_t> n = ii.singleton();
65,084✔
478
    if (!n) {
65,084✔
479
        // We can only split a singleton offset.
480
        return;
2✔
481
    }
482
    interval_t i_elem_size = inv.eval_interval(elem_size);
65,080✔
483
    std::optional<number_t> 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_t>());
65,080✔
490

491
    std::vector<cell_t> cells = offset_map.get_overlap_cells(o, size);
65,080✔
492
    for (cell_t const& c : cells) {
77,874✔
493
        interval_t 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_t>(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>> kill_and_find_var(NumAbsDomain& inv, data_kind_t kind,
324,570✔
521
                                                                      const linear_expression_t& i,
522
                                                                      const linear_expression_t& elem_size) {
523
    std::optional<std::pair<offset_t, unsigned>> res;
324,570✔
524

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

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

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

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

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

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

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

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

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

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

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

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

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

796
void array_domain_t::havoc(NumAbsDomain& inv, const data_kind_t kind, const linear_expression_t& idx,
229,210✔
797
                           const linear_expression_t& elem_size) {
798
    auto maybe_cell = split_and_find_var(*this, inv, kind, idx, elem_size);
229,210✔
799
    if (maybe_cell && kind == data_kind_t::types) {
229,210✔
800
        auto [offset, size] = *maybe_cell;
872✔
801
        num_bytes.havoc(offset, size);
872✔
802
    }
803
}
229,210✔
804

805
void array_domain_t::store_numbers(const NumAbsDomain& inv, const variable_t _idx, const variable_t _width) {
850✔
806

807
    // TODO: this should be an user parameter.
808
    const number_t max_num_elems = EBPF_TOTAL_STACK_SIZE;
850✔
809

810
    if (is_bottom()) {
850✔
811
        return;
812
    }
813

814
    const std::optional<number_t> idx_n = inv.eval_interval(_idx).singleton();
1,275✔
815
    if (!idx_n) {
850✔
816
        CRAB_WARN("array expansion store range ignored because ", "lower bound is not constant");
×
817
        return;
×
818
    }
819

820
    const std::optional<number_t> width = inv.eval_interval(_width).singleton();
1,275✔
821
    if (!width) {
850✔
822
        CRAB_WARN("array expansion store range ignored because ", "upper bound is not constant");
×
823
        return;
×
824
    }
825

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

834
void array_domain_t::set_to_top() { num_bytes.set_to_top(); }
×
835

836
void array_domain_t::set_to_bottom() { num_bytes.set_to_bottom(); }
×
837

838
bool array_domain_t::is_bottom() const { return num_bytes.is_bottom(); }
12,966✔
839

840
bool array_domain_t::is_top() const { return num_bytes.is_top(); }
×
841

842
string_invariant array_domain_t::to_set() const { return num_bytes.to_set(); }
1,386✔
843

844
bool array_domain_t::operator<=(const array_domain_t& other) const { return num_bytes <= other.num_bytes; }
76✔
845

846
bool array_domain_t::operator==(const array_domain_t& other) const { return num_bytes == other.num_bytes; }
×
847

848
void array_domain_t::operator|=(const array_domain_t& other) {
24,232✔
849
    if (is_bottom()) {
24,232✔
850
        *this = other;
851
        return;
852
    }
853
    num_bytes |= other.num_bytes;
24,232✔
854
}
855

856
array_domain_t array_domain_t::operator|(const array_domain_t& other) const {
122✔
857
    return array_domain_t(num_bytes | other.num_bytes);
122✔
858
}
859

860
array_domain_t array_domain_t::operator&(const array_domain_t& other) const {
38✔
861
    return array_domain_t(num_bytes & other.num_bytes);
38✔
862
}
863

864
array_domain_t array_domain_t::widen(const array_domain_t& other) const {
×
865
    return array_domain_t(num_bytes | other.num_bytes);
×
866
}
867

868
array_domain_t array_domain_t::widening_thresholds(const array_domain_t& other, const thresholds_t& ts) const {
×
869
    return array_domain_t(num_bytes | other.num_bytes);
×
870
}
871

872
array_domain_t array_domain_t::narrow(const array_domain_t& other) const {
×
873
    return array_domain_t(num_bytes & other.num_bytes);
×
874
}
875

UNCOV
876
std::ostream& operator<<(std::ostream& o, const array_domain_t& dom) { return o << dom.num_bytes; }
×
877
} // namespace crab::domains
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

© 2025 Coveralls, Inc