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

Alan-Jowett / ebpf-verifier / 27778108035

07 Jun 2026 06:51PM UTC coverage: 86.386% (-2.5%) from 88.93%
27778108035

push

github

elazarg
Release v0.2.5

Bump project version to 0.2.5 and add a CHANGELOG entry covering ELF loader hardening, numeric-domain soundness fixes, and the writable helper output initialization documentation update since v0.2.4. Also updates the using_installed_package example version requirement.

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

9125 of 10563 relevant lines covered (86.39%)

6334294.72 hits per line

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

85.34
/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 <gsl/narrow>
13
#include <map>
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
#include <ranges>
22

23
namespace prevail {
24

25
using Index = uint64_t;
26

27
using offset_t = Index;
28

29
// Conceptually, a cell is tuple of an array, offset, size, and
30
// scalar variable such that:
31
//   scalar = array[offset, offset + 1, ..., offset + size - 1]
32
// For simplicity, we don't carry the array inside the cell.
33
struct Cell final {
34
    offset_t offset{};
10,097,000✔
35
    unsigned size{};
10,087,266✔
36

37
    bool operator==(const Cell&) const = default;
307,482✔
38
    auto operator<=>(const Cell&) const = default;
19,655,022✔
39

40
    // Return true if [o, o + sz) definitely overlaps with the cell.
41
    // Offsets are bounded by total_stack_size, so wraparound cannot occur.
42
    [[nodiscard]]
43
    bool overlap(const offset_t o, const unsigned sz) const {
8,831,112✔
44
        assert(sz > 0 && "overlap query with zero width");
8,831,112✔
45
        return offset < o + sz && o < offset + size;
8,595,331✔
46
    }
47
};
48

49
static Interval cell_to_interval(const offset_t o, const unsigned size) {
61,240✔
50
    const Number lb{gsl::narrow<int>(o)};
61,240✔
51
    return {lb, lb + size - 1};
91,860✔
52
}
53

54
// Return true if [symb_lb, symb_ub] may overlap with the cell,
55
// where symb_lb and symb_ub are not constant expressions.
56
static bool symbolic_overlap(const Cell& c, const Interval& range) {
108✔
57
    return !(cell_to_interval(c.offset, c.size) & range).is_bottom();
108✔
58
}
59

60
std::ostream& operator<<(std::ostream& o, const Cell& c) { return o << "cell(" << c.offset << "," << c.size << ")"; }
×
61

62
static Variable cell_var(const DataKind kind, const Cell& c) {
512,588✔
63
    return variable_registry.cell_var(kind, c.offset, c.size);
512,588✔
64
}
65

66
// Map offsets to cells.
67
// std::map/std::set are used deliberately: empirically, the median collection holds ~3 cells,
68
// overlap queries hit <5% of the time, and the entire offset map is <1% of verifier runtime.
69
// At these sizes, specialized data structures (patricia tries, flat sorted vectors) show no
70
// macro-level improvement while adding complexity or external dependencies.
71
class offset_map_t final {
2,131,512✔
72
    friend class ArrayDomain;
73
    friend class StackCellRegistry;
74

75
    using cell_set_t = std::set<Cell>;
76

77
    using map_t = std::map<offset_t, cell_set_t>;
78

79
    map_t _map;
80

81
    void remove_cell(const Cell& c);
82

83
    void insert_cell(const Cell& c);
84

85
    [[nodiscard]]
86
    std::optional<Cell> get_cell(offset_t o, unsigned size);
87

88
    Cell mk_cell(offset_t o, unsigned size);
89

90
  public:
91
    offset_map_t() = default;
30,876✔
92

93
    [[nodiscard]]
94
    std::size_t size() const {
95
        return _map.size();
96
    }
97

98
    void operator-=(const Cell& c) { remove_cell(c); }
83,556✔
99

100
    void operator-=(const std::vector<Cell>& cells) {
49,278✔
101
        for (const auto& c : cells) {
132,834✔
102
            this->operator-=(c);
83,556✔
103
        }
104
    }
49,278✔
105

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

109
    [[nodiscard]]
110
    std::vector<Cell> get_overlap_cells_symbolic_offset(const Interval& range);
111

112
    friend std::ostream& operator<<(std::ostream& o, offset_map_t& m);
113
};
114

115
void offset_map_t::remove_cell(const Cell& c) {
83,556✔
116
    const offset_t key = c.offset;
83,556✔
117
    if (const auto it = _map.find(key); it != _map.end()) {
83,556✔
118
        it->second.erase(c);
83,556✔
119
        if (it->second.empty()) {
83,556✔
120
            _map.erase(it);
61,554✔
121
        }
122
    }
123
}
83,556✔
124

125
[[nodiscard]]
126
std::vector<Cell> offset_map_t::get_overlap_cells_symbolic_offset(const Interval& range) {
184✔
127
    std::vector<Cell> out;
184✔
128
    for (const auto& o_cells : _map | std::views::values) {
292✔
129
        // All cells in o_cells have the same offset. They only differ in the size.
130
        // If the largest cell overlaps with [offset, offset + size)
131
        // then the rest of cells are considered to overlap.
132
        // This is an over-approximation because [offset, offset+size) can overlap
133
        // with the largest cell, but it doesn't necessarily overlap with smaller cells.
134
        // For efficiency, we assume it overlaps with all.
135
        if (!o_cells.empty()) {
108✔
136
            // Cells are sorted by (offset, size); last element has the largest size.
137
            const Cell& largest_cell = *o_cells.rbegin();
108✔
138
            if (symbolic_overlap(largest_cell, range)) {
108✔
139
                for (const auto& c : o_cells) {
126✔
140
                    out.push_back(c);
66✔
141
                }
142
            }
143
        }
144
    }
145
    return out;
184✔
146
}
×
147

148
void offset_map_t::insert_cell(const Cell& c) { _map[c.offset].insert(c); }
4,603,984✔
149

150
std::optional<Cell> offset_map_t::get_cell(const offset_t o, const unsigned size) {
377,796✔
151
    if (const auto it = _map.find(o); it != _map.end()) {
377,796✔
152
        if (const auto cit = it->second.find(Cell(o, size)); cit != it->second.end()) {
238,566✔
153
            return *cit;
232,448✔
154
        }
155
    }
156
    return {};
145,348✔
157
}
158

159
Cell offset_map_t::mk_cell(const offset_t o, const unsigned size) {
204,388✔
160
    // TODO: check array is the array associated to this offset map
161

162
    if (const auto maybe_c = get_cell(o, size)) {
204,388✔
163
        return *maybe_c;
81,552✔
164
    }
165
    // create a new scalar variable for representing the contents
166
    // of bytes array[o,o+1,..., o+size-1]
167
    const Cell c(o, size);
122,836✔
168
    insert_cell(c);
122,836✔
169
    return c;
122,836✔
170
}
171

172
// Return all cells that might overlap with (o, size).
173
std::vector<Cell> offset_map_t::get_overlap_cells(const offset_t o, const unsigned size) {
1,370,906✔
174
    std::vector<Cell> out;
1,370,906✔
175
    const Cell query_cell(o, size);
1,370,906✔
176

177
    // Search backwards: cells at offsets <= o that might extend into [o, o+size).
178
    // We cannot break early: a bucket with small cells may not overlap while an
179
    // earlier bucket with larger cells does (e.g., Cell(48,8) overlaps [54,56)
180
    // but Cell(50,2) does not). The map is tiny (~3 entries) so this is fine.
181
    for (auto it = _map.upper_bound(o); it != _map.begin();) {
9,865,715✔
182
        --it;
7,809,356✔
183
        for (const Cell& x : it->second) {
16,117,464✔
184
            if (x.overlap(o, size) && x != query_cell) {
12,620,770✔
185
                out.push_back(x);
100,612✔
186
            }
187
        }
188
    }
189

190
    // Search forwards: cells at offsets > o that start within [o, o+size).
191
    // Early break is safe here: if no cell at offset k overlaps, then k >= o + size,
192
    // and all subsequent offsets are even larger, so they cannot overlap either.
193
    // No duplicates: backward and forward scans visit disjoint key ranges.
194
    for (auto it = _map.upper_bound(o); it != _map.end(); ++it) {
2,105,561✔
195
        bool any_overlap = false;
498,468✔
196
        for (const Cell& x : it->second) {
1,021,472✔
197
            if (x.overlap(o, size)) {
548,725✔
198
                out.push_back(x);
51,442✔
199
                any_overlap = true;
25,721✔
200
            }
201
        }
202
        if (!any_overlap) {
498,468✔
203
            break;
224,633✔
204
        }
205
    }
206

207
    return out;
2,056,359✔
208
}
×
209

210
// Per-domain registry of the stack cells `ArrayDomain` is currently tracking,
211
// keyed by DataKind. Owned by `ArrayDomain`; copied/merged alongside the domain.
212
class StackCellRegistry final {
149,029✔
213
    std::unordered_map<DataKind, offset_map_t> _maps;
214

215
  public:
216
    offset_map_t& get(const DataKind kind) { return _maps[kind]; }
2,633,697✔
217

218
    void merge_from(const StackCellRegistry& other) {
60,592✔
219
        if (this == &other) {
60,592✔
220
            return;
221
        }
222
        for (const auto& [kind, omap] : other._maps) {
1,360,144✔
223
            offset_map_t& dst = _maps[kind];
866,368✔
224
            for (const auto& [_off, cell_set] : omap._map) {
5,041,302✔
225
                for (const Cell& c : cell_set) {
8,656,082✔
226
                    dst.insert_cell(c);
4,481,148✔
227
                }
228
            }
229
        }
230
    }
231
};
232

233
std::shared_ptr<StackCellRegistry> make_stack_cell_registry() { return std::make_shared<StackCellRegistry>(); }
10,078✔
234

235
void ArrayDomain::initialize_numbers(const int lb, const int width) {
280✔
236
    num_bytes.reset(lb, width);
280✔
237
    cells_.get_mutable().get(DataKind::svalues).mk_cell(offset_t{gsl::narrow_cast<Index>(lb)}, width);
280✔
238
}
280✔
239

240
std::ostream& operator<<(std::ostream& o, offset_map_t& m) {
×
241
    if (m._map.empty()) {
×
242
        o << "empty";
×
243
    } else {
244
        for (const auto& cells : m._map | std::views::values) {
×
245
            o << "{";
×
246
            for (auto cit = cells.begin(), cet = cells.end(); cit != cet;) {
×
247
                o << *cit;
×
248
                ++cit;
×
249
                if (cit != cet) {
×
250
                    o << ",";
×
251
                }
252
            }
253
            o << "}\n";
×
254
        }
255
    }
256
    return o;
×
257
}
258

259
// Create a new cell that is a subset of an existing cell.
260
void ArrayDomain::split_cell(NumAbsDomain& inv, const DataKind kind, const int cell_start_index, const unsigned int len,
2,920✔
261
                             const bool big_endian) {
262
    assert(kind == DataKind::svalues || kind == DataKind::uvalues);
2,920✔
263

264
    // Get the values from the indicated stack range.
265
    const std::optional<LinearExpression> svalue =
1,460✔
266
        load(inv, DataKind::svalues, Interval{cell_start_index}, len, big_endian);
2,920✔
267
    const std::optional<LinearExpression> uvalue =
1,460✔
268
        load(inv, DataKind::uvalues, Interval{cell_start_index}, len, big_endian);
2,920✔
269

270
    // Create a new cell for that range.
271
    offset_map_t& offset_map = cells_.get_mutable().get(kind);
2,920✔
272
    const Cell new_cell = offset_map.mk_cell(offset_t{gsl::narrow_cast<Index>(cell_start_index)}, len);
2,920✔
273
    inv.assign(cell_var(DataKind::svalues, new_cell), svalue);
2,920✔
274
    inv.assign(cell_var(DataKind::uvalues, new_cell), uvalue);
4,380✔
275
}
2,920✔
276

277
// Prepare to havoc bytes in the middle of a cell by potentially splitting the cell if it is numeric,
278
// into the part to the left of the havoced portion, and the part to the right of the havoced portion.
279
void ArrayDomain::split_number_var(NumAbsDomain& inv, const DataKind kind, const Interval& ii,
189,816✔
280
                                   const Interval& elem_size, const bool big_endian) {
281
    assert(kind == DataKind::svalues || kind == DataKind::uvalues);
189,816✔
282
    offset_map_t& offset_map = cells_.get_mutable().get(kind);
189,816✔
283
    const std::optional<Number> n = ii.singleton();
189,816✔
284
    if (!n) {
189,816✔
285
        // We can only split a singleton offset.
286
        return;
40✔
287
    }
288
    const std::optional<Number> n_bytes = elem_size.singleton();
189,776✔
289
    if (!n_bytes) {
189,776✔
290
        // We can only split a singleton size.
291
        return;
292
    }
293
    const auto size = n_bytes->narrow<unsigned int>();
189,776✔
294
    const offset_t o(n->narrow<Index>());
189,776✔
295

296
    const std::vector<Cell> overlaps = offset_map.get_overlap_cells(o, size);
189,776✔
297
    for (const Cell& c : overlaps) {
250,908✔
298
        const auto [cell_start_index, cell_end_index] = cell_to_interval(c.offset, c.size).pair<int>();
61,132✔
299
        if (!this->num_bytes.all_num(cell_start_index, cell_end_index + 1) ||
88,017✔
300
            cell_end_index + 1UL < cell_start_index + sizeof(int64_t)) {
53,770✔
301
            // We can only split numeric cells of size 8 or less.
302
            continue;
58,398✔
303
        }
304

305
        if (!inv.eval_interval(cell_var(kind, c)).is_singleton()) {
14,895✔
306
            // We can only split cells with a singleton value.
307
            continue;
7,196✔
308
        }
309
        if (gsl::narrow_cast<Index>(cell_start_index) < o) {
2,734✔
310
            // Use the bytes to the left of the specified range.
311
            split_cell(inv, kind, cell_start_index, gsl::narrow<unsigned int>(o - cell_start_index), big_endian);
858✔
312
        }
313
        if (o + size < cell_end_index + 1UL) {
2,734✔
314
            // Use the bytes to the right of the specified range.
315
            split_cell(inv, kind, gsl::narrow<int>(o + size),
2,062✔
316
                       gsl::narrow<unsigned int>(cell_end_index - (o + size - 1)), big_endian);
2,062✔
317
        }
318
    }
319
}
189,776✔
320

321
// Find overlapping cells for the given index range and kill (havoc + remove) them.
322
// Returns the exact offset and size if the index and element size are both constant.
323
template <typename HavocFn>
324
static std::optional<std::pair<offset_t, unsigned>> kill_and_find_var(StackCellRegistry& cells,
1,166,498✔
325
                                                                      const HavocFn& havoc_var, const DataKind kind,
326
                                                                      const Interval& ii, const Interval& elem_size) {
327
    std::optional<std::pair<offset_t, unsigned>> res;
1,166,498✔
328

329
    offset_map_t& offset_map = cells.get(kind);
1,166,498✔
330
    std::vector<Cell> overlaps;
1,166,498✔
331
    if (const std::optional<Number> n = ii.singleton()) {
1,166,498✔
332
        if (const auto n_bytes = elem_size.singleton()) {
1,166,314✔
333
            auto size = n_bytes->narrow<unsigned int>();
1,166,314✔
334
            // -- Constant index: kill overlapping cells
335
            offset_t o(n->narrow<Index>());
1,166,314✔
336
            overlaps = offset_map.get_overlap_cells(o, size);
1,749,471✔
337
            res = std::make_pair(o, size);
1,166,314✔
338
        }
339
    }
340
    if (!res) {
1,166,498✔
341
        // -- Non-constant index: kill overlapping cells
342
        overlaps = offset_map.get_overlap_cells_symbolic_offset(ii | (ii + elem_size));
276✔
343
    }
344
    if (!overlaps.empty()) {
1,166,498✔
345
        // Forget the scalars from the relevant domain
346
        for (const auto& c : overlaps) {
132,834✔
347
            havoc_var(cell_var(kind, c));
83,556✔
348

349
            // Forget signed and unsigned values together.
350
            if (kind == DataKind::svalues) {
83,556✔
351
                havoc_var(cell_var(DataKind::uvalues, c));
31,314✔
352
            } else if (kind == DataKind::uvalues) {
52,242✔
353
                havoc_var(cell_var(DataKind::svalues, c));
29,864✔
354
            }
355
        }
356
        // Remove the cells. If needed again they will be re-created.
357
        offset_map -= overlaps;
49,278✔
358
    }
359
    return res;
1,749,747✔
360
}
1,166,498✔
361
static std::optional<std::tuple<int, int>> as_numbytes_range(const Interval& range, const int stack_size) {
18,630✔
362
    const Interval bounded_range = Interval{0, stack_size} & range;
18,630✔
363
    if (bounded_range.is_bottom()) {
18,630✔
364
        return {};
8✔
365
    }
366
    const auto bounds = bounded_range.pair<int>();
18,622✔
367
    const auto [lb, ub] = bounds;
18,622✔
368
    if (lb >= ub) {
18,622✔
369
        return {};
8✔
370
    }
371
    return bounds;
18,614✔
372
}
373

374
static std::optional<std::tuple<int, int>> as_numbytes_range(const Interval& index, const Interval& width,
12,606✔
375
                                                             const int stack_size) {
376
    const Interval range = index | (index + width);
12,606✔
377
    return as_numbytes_range(range, stack_size);
18,909✔
378
}
379

380
bool ArrayDomain::all_num_lb_ub(const Interval& lb, const Interval& ub) const {
6,024✔
381
    const auto range = as_numbytes_range(lb | ub, total_stack_size());
6,024✔
382
    if (!range.has_value()) {
6,024✔
383
        return false;
3✔
384
    }
385
    const auto [min_lb, max_ub] = *range;
6,018✔
386
    assert(min_lb < max_ub);
6,018✔
387
    return this->num_bytes.all_num(min_lb, max_ub);
6,018✔
388
}
389

390
bool ArrayDomain::all_num_width(const Interval& index, const Interval& width) const {
12,596✔
391
    const auto range = as_numbytes_range(index, width, total_stack_size());
12,596✔
392
    if (!range.has_value()) {
12,596✔
393
        return false;
5✔
394
    }
395
    const auto [min_lb, max_ub] = *range;
12,586✔
396
    assert(min_lb < max_ub);
12,586✔
397
    return this->num_bytes.all_num(min_lb, max_ub);
12,586✔
398
}
399

400
// Get the number of bytes, starting at offset, that are known to be numbers.
401
int ArrayDomain::min_all_num_size(const NumAbsDomain& inv, const Variable offset) const {
29,060✔
402
    const auto min_lb = inv.eval_interval(offset).lb().number();
43,590✔
403
    const auto max_ub = inv.eval_interval(offset).ub().number();
43,590✔
404
    if (!min_lb || !max_ub || !min_lb->fits<int32_t>() || !max_ub->fits<int32_t>()) {
29,060✔
405
        return 0;
50✔
406
    }
407
    const auto lb = min_lb->narrow<int>();
28,960✔
408
    const auto ub = max_ub->narrow<int>();
28,960✔
409
    return std::max(0, this->num_bytes.all_num_width(lb) - (ub - lb));
41,496✔
410
}
411

412
// Get one byte of a value.
413
std::optional<uint8_t> get_value_byte(const NumAbsDomain& inv, const offset_t o, const int width,
90,508✔
414
                                      const bool big_endian) {
415
    const Variable v = variable_registry.cell_var(DataKind::svalues, (o / width) * width, width);
90,508✔
416
    const std::optional<Number> t = inv.eval_interval(v).singleton();
90,508✔
417
    if (!t) {
90,508✔
418
        return {};
54,582✔
419
    }
420
    Index n = t->cast_to<Index>();
35,926✔
421

422
    // Convert value to bytes of the appropriate endian-ness.
423
    switch (width) {
35,926✔
424
    case sizeof(uint8_t): break;
130✔
425
    case sizeof(uint16_t):
290✔
426
        if (big_endian) {
290✔
427
            n = boost::endian::native_to_big<uint16_t>(n);
×
428
        } else {
429
            n = boost::endian::native_to_little<uint16_t>(n);
290✔
430
        }
431
        break;
145✔
432
    case sizeof(uint32_t):
1,910✔
433
        if (big_endian) {
1,910✔
434
            n = boost::endian::native_to_big<uint32_t>(n);
×
435
        } else {
436
            n = boost::endian::native_to_little<uint32_t>(n);
1,910✔
437
        }
438
        break;
955✔
439
    case sizeof(Index):
33,466✔
440
        if (big_endian) {
33,466✔
441
            n = boost::endian::native_to_big<Index>(n);
48✔
442
        } else {
443
            n = boost::endian::native_to_little<Index>(n);
16,709✔
444
        }
445
        break;
16,733✔
446
    default: CRAB_ERROR("Unexpected width ", width);
×
447
    }
448
    const auto bytes = reinterpret_cast<uint8_t*>(&n);
35,926✔
449
    return bytes[o % width];
35,926✔
450
}
451

452
std::optional<LinearExpression> ArrayDomain::load(const NumAbsDomain& inv, const DataKind kind, const Interval& i,
157,214✔
453
                                                  const int width, const bool big_endian) {
454
    if (const std::optional<Number> n = i.singleton()) {
157,214✔
455
        offset_map_t& offset_map = cells_.get_mutable().get(kind);
157,178✔
456
        const int64_t k = n->narrow<int64_t>();
157,178✔
457
        const offset_t o(k);
157,178✔
458
        const unsigned size = to_unsigned(width);
157,178✔
459
        if (const auto cell = offset_map.get_cell(o, size)) {
157,178✔
460
            return cell_var(kind, *cell);
202,017✔
461
        }
462
        if (kind == DataKind::svalues || kind == DataKind::uvalues) {
22,500✔
463
            // Copy bytes into result_buffer, taking into account that the
464
            // bytes might be in different stack variables and might be unaligned.
465
            uint8_t result_buffer[8];
466
            bool found = true;
40,463✔
467
            for (unsigned int index = 0; index < size; index++) {
58,426✔
468
                const offset_t byte_offset{o + index};
48,754✔
469
                std::optional<uint8_t> b = get_value_byte(inv, byte_offset, 8, big_endian);
48,754✔
470
                if (!b) {
48,754✔
471
                    b = get_value_byte(inv, byte_offset, 4, big_endian);
15,288✔
472
                    if (!b) {
15,288✔
473
                        b = get_value_byte(inv, byte_offset, 2, big_endian);
13,378✔
474
                        if (!b) {
13,378✔
475
                            b = get_value_byte(inv, byte_offset, 1, big_endian);
13,088✔
476
                        }
477
                    }
478
                }
479
                if (b) {
48,754✔
480
                    result_buffer[index] = *b;
35,926✔
481
                } else {
482
                    found = false;
12,828✔
483
                    break;
12,828✔
484
                }
485
            }
486
            if (found) {
17,664✔
487
                // We have an aligned result in result_buffer so we can now
488
                // convert to an integer.
489
                if (size == 1) {
9,672✔
490
                    return *result_buffer;
8,213✔
491
                }
492
                if (size == 2) {
8,638✔
493
                    uint16_t b = *reinterpret_cast<uint16_t*>(result_buffer);
2,272✔
494
                    if (big_endian) {
2,272✔
495
                        b = boost::endian::native_to_big<uint16_t>(b);
14✔
496
                    } else {
497
                        b = boost::endian::native_to_little<uint16_t>(b);
2,265✔
498
                    }
499
                    return b;
2,272✔
500
                }
501
                if (size == 4) {
6,366✔
502
                    uint32_t b = *reinterpret_cast<uint32_t*>(result_buffer);
4,274✔
503
                    if (big_endian) {
4,274✔
504
                        b = boost::endian::native_to_big<uint32_t>(b);
14✔
505
                    } else {
506
                        b = boost::endian::native_to_little<uint32_t>(b);
2,130✔
507
                    }
508
                    return b;
4,274✔
509
                }
510
                if (size == 8) {
2,092✔
511
                    Index b = *reinterpret_cast<Index*>(result_buffer);
116✔
512
                    if (big_endian) {
116✔
513
                        b = boost::endian::native_to_big<Index>(b);
6✔
514
                    } else {
515
                        b = boost::endian::native_to_little<Index>(b);
55✔
516
                    }
517
                    return kind == DataKind::uvalues ? Number(b) : Number(to_signed(b));
116✔
518
                }
519
            }
520
        }
521

522
        const std::vector<Cell> overlaps = offset_map.get_overlap_cells(o, size);
14,804✔
523
        if (overlaps.empty()) {
14,804✔
524
            const Cell c = offset_map.mk_cell(o, size);
8,520✔
525
            // Here it's ok to do assignment (instead of expand) because c is not a summarized variable.
526
            // Otherwise, it would be unsound.
527
            return cell_var(kind, c);
8,520✔
528
        }
529
        CRAB_WARN("Ignored read from cell ", kind, "[", o, "...", o + size - 1, "]", " because it overlaps with ",
6,284✔
530
                  overlaps.size(), " cells");
531
        /*
532
            TODO: we can apply here "Value Recomposition" a la Mine'06 (https://arxiv.org/pdf/cs/0703074.pdf)
533
                to construct values of some type from a sequence of bytes.
534
                It can be endian-independent but it would more precise if we choose between little- and big-endian.
535
        */
536
    } else {
14,804✔
537
        // TODO: we can be more precise here
538
        CRAB_WARN("array expansion: ignored array load because of non-constant array index ", i);
36✔
539
    }
540
    return {};
6,320✔
541
}
542

543
std::optional<LinearExpression> ArrayDomain::load_type(const Interval& i, const int width) {
46,450✔
544
    if (const std::optional<Number> n = i.singleton()) {
46,450✔
545
        offset_map_t& offset_map = cells_.get_mutable().get(DataKind::types);
46,450✔
546
        const int64_t k = n->narrow<int64_t>();
46,450✔
547
        auto [only_num, only_non_num] = num_bytes.uniformity(k, width);
46,450✔
548
        if (only_num) {
46,450✔
549
            return T_NUM;
30,206✔
550
        }
551
        if (!only_non_num || width != 8) {
16,244✔
552
            return {};
14✔
553
        }
554
        const offset_t o(k);
16,230✔
555
        const unsigned size = to_unsigned(width);
16,230✔
556
        if (const auto cell = offset_map.get_cell(o, size)) {
16,230✔
557
            return cell_var(DataKind::types, *cell);
24,327✔
558
        }
559
        const std::vector<Cell> overlaps = offset_map.get_overlap_cells(o, size);
12✔
560
        if (overlaps.empty()) {
12✔
561
            const Cell c = offset_map.mk_cell(o, size);
12✔
562
            // Here it's ok to do assignment (instead of expand) because c is not a summarized variable.
563
            // Otherwise, it would be unsound.
564
            return cell_var(DataKind::types, c);
12✔
565
        }
566
        CRAB_WARN("Ignored read from cell ", DataKind::types, "[", o, "...", o + size - 1, "]",
×
567
                  " because it overlaps with ", overlaps.size(), " cells");
568
        /*
569
            TODO: we can apply here "Value Recomposition" a la Mine'06 (https://arxiv.org/pdf/cs/0703074.pdf)
570
                to construct values of some type from a sequence of bytes.
571
                It can be endian-independent but it would more precise if we choose between little- and big-endian.
572
        */
573
    } else {
12✔
574
        // Check whether the kind is uniform across the entire interval.
575
        const auto lb = i.lb().number();
×
576
        const auto ub = i.ub().number();
×
577
        if (lb.has_value() && ub.has_value()) {
×
578
            const Number fullwidth = ub.value() - lb.value() + width;
×
579
            if (lb->fits<uint32_t>() && fullwidth.fits<uint32_t>()) {
×
580
                auto [only_num, only_non_num] =
×
581
                    num_bytes.uniformity(lb->narrow<uint32_t>(), fullwidth.narrow<uint32_t>());
×
582
                if (only_num) {
×
583
                    return T_NUM;
×
584
                }
585
            }
586
        }
587
    }
588
    return {};
×
589
}
590

591
// We are about to write to a given range of bytes on the stack.
592
// Any cells covering that range need to be removed, and any cells that only
593
// partially cover that range can be split such that any non-covered portions become new cells.
594
static std::optional<std::pair<offset_t, unsigned>>
595
split_and_find_var(ArrayDomain& array_domain, StackCellRegistry& cells, NumAbsDomain& inv, const DataKind kind,
1,040,460✔
596
                   const Interval& idx, const Interval& elem_size, const bool big_endian) {
597
    if (kind == DataKind::svalues || kind == DataKind::uvalues) {
1,040,460✔
598
        array_domain.split_number_var(inv, kind, idx, elem_size, big_endian);
189,816✔
599
    }
600
    return kill_and_find_var(cells, [&inv](const Variable v) { inv.havoc(v); }, kind, idx, elem_size);
1,163,584✔
601
}
602

603
std::optional<Variable> ArrayDomain::store(NumAbsDomain& inv, const DataKind kind, const Interval& idx,
129,760✔
604
                                           const Interval& elem_size, const bool big_endian) {
605
    if (auto maybe_cell = split_and_find_var(*this, cells_.get_mutable(), inv, kind, idx, elem_size, big_endian)) {
129,760✔
606
        // perform strong update
607
        auto [offset, size] = *maybe_cell;
129,736✔
608
        const Cell c = cells_.get_mutable().get(kind).mk_cell(offset, size);
129,736✔
609
        Variable v = cell_var(kind, c);
129,736✔
610
        return v;
129,736✔
611
    }
612
    return {};
24✔
613
}
614

615
std::optional<Variable> ArrayDomain::store_type(TypeDomain& inv, const Interval& idx, const Interval& width,
62,930✔
616
                                                const bool is_num) {
617
    constexpr auto kind = DataKind::types;
62,930✔
618
    if (auto maybe_cell = kill_and_find_var(
94,395✔
619
            cells_.get_mutable(), [&inv](const Variable v) { inv.havoc_type(v); }, kind, idx, width)) {
125,860✔
620
        // perform strong update
621
        auto [offset, size] = *maybe_cell;
62,920✔
622
        if (is_num) {
62,920✔
623
            num_bytes.reset(offset, size);
60,376✔
624
        } else {
625
            num_bytes.havoc(offset, size);
2,544✔
626
        }
627
        const Cell c = cells_.get_mutable().get(kind).mk_cell(offset, size);
62,920✔
628
        Variable v = cell_var(kind, c);
62,920✔
629
        return v;
62,920✔
630
    } else {
631
        using namespace dsl_syntax;
5✔
632
        // Weak update: cannot perform a strong update because the index is
633
        // not a singleton. Havoc the type cells in the range.
634
        const auto range = as_numbytes_range(idx, width, total_stack_size());
10✔
635
        if (!is_num && range.has_value()) {
10✔
636
            const auto [lb, ub] = *range;
2✔
637
            // A non-numeric value may overwrite previously numeric bytes,
638
            // so conservatively mark the range as non-numeric.
639
            num_bytes.havoc(lb, ub);
2✔
640
        }
641
        // When is_num is true, the value being stored is numeric. Any byte
642
        // that gets written will still be numeric, and bytes not written
643
        // keep their existing status, so num_bytes is left unchanged.
644
    }
645
    return {};
10✔
646
}
647

648
void ArrayDomain::havoc(NumAbsDomain& inv, const DataKind kind, const Interval& idx, const Interval& elem_size,
910,700✔
649
                        const bool big_endian) {
650
    split_and_find_var(*this, cells_.get_mutable(), inv, kind, idx, elem_size, big_endian);
910,700✔
651
}
910,700✔
652

653
void ArrayDomain::havoc_type(TypeDomain& inv, const Interval& idx, const Interval& elem_size) {
63,108✔
654
    constexpr auto kind = DataKind::types;
63,108✔
655
    if (auto maybe_cell = kill_and_find_var(
94,662✔
656
            cells_.get_mutable(), [&inv](const Variable v) { inv.havoc_type(v); }, kind, idx, elem_size)) {
147,826✔
657
        auto [offset, size] = *maybe_cell;
63,098✔
658
        num_bytes.havoc(offset, size);
63,098✔
659
    }
660
}
63,108✔
661

662
void ArrayDomain::store_numbers(const Interval& _idx, const Interval& _width) {
7,442✔
663
    const std::optional<Number> idx_n = _idx.singleton();
7,442✔
664
    if (!idx_n) {
7,442✔
665
        CRAB_WARN("array expansion store range ignored because ", "lower bound is not constant");
×
666
        return;
×
667
    }
668

669
    const std::optional<Number> width = _width.singleton();
7,442✔
670
    if (!width) {
7,442✔
671
        CRAB_WARN("array expansion store range ignored because ", "upper bound is not constant");
×
672
        return;
×
673
    }
674

675
    if (*idx_n + *width > total_stack_size()) {
7,442✔
676
        CRAB_WARN("array expansion store range ignored because ", "the number of elements is larger than limit of ",
×
677
                  total_stack_size());
678
        return;
×
679
    }
680
    num_bytes.reset(idx_n->narrow<int>(), width->narrow<int>());
7,442✔
681
}
682

683
void ArrayDomain::set_to_top() { num_bytes.set_to_top(); }
×
684

685
bool ArrayDomain::is_top() const { return num_bytes.is_top(); }
×
686

687
StringInvariant ArrayDomain::to_set() const { return num_bytes.to_set(); }
1,022✔
688

689
bool ArrayDomain::operator<=(const ArrayDomain& other) const { return num_bytes <= other.num_bytes; }
720✔
690

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

693
void ArrayDomain::operator|=(const ArrayDomain& other) {
58,820✔
694
    num_bytes |= other.num_bytes;
58,820✔
695
    cells_.get_mutable().merge_from(*other.cells_);
58,820✔
696
}
58,820✔
697

698
void ArrayDomain::operator|=(ArrayDomain&& other) {
×
699
    num_bytes |= std::move(other.num_bytes);
×
700
    cells_.get_mutable().merge_from(*other.cells_);
×
701
}
×
702

703
// Lattice combinators build a fresh ArrayDomain whose cells map is the union of
704
// both sides' cells. Cell membership is purely advisory (it enables overlap
705
// detection and dedup of mk_cell calls); the underlying numeric domain's join
706
// determines abstract values, and it operates on globally-interned Variable
707
// names so two domains independently tracking the same cell agree on its name.
708
ArrayDomain ArrayDomain::operator|(const ArrayDomain& other) const {
×
709
    ArrayDomain res{num_bytes | other.num_bytes};
×
710
    res.cells_.get_mutable().merge_from(*cells_);
×
711
    res.cells_.get_mutable().merge_from(*other.cells_);
×
712
    return res;
×
713
}
×
714

715
ArrayDomain ArrayDomain::operator&(const ArrayDomain& other) const {
712✔
716
    ArrayDomain res{num_bytes & other.num_bytes};
712✔
717
    res.cells_.get_mutable().merge_from(*cells_);
712✔
718
    res.cells_.get_mutable().merge_from(*other.cells_);
712✔
719
    return res;
712✔
720
}
×
721

722
ArrayDomain ArrayDomain::widen(const ArrayDomain& other) const {
174✔
723
    ArrayDomain res{num_bytes | other.num_bytes};
174✔
724
    res.cells_.get_mutable().merge_from(*cells_);
174✔
725
    res.cells_.get_mutable().merge_from(*other.cells_);
174✔
726
    return res;
174✔
727
}
×
728

729
ArrayDomain ArrayDomain::narrow(const ArrayDomain& other) const {
×
730
    ArrayDomain res{num_bytes & other.num_bytes};
×
731
    res.cells_.get_mutable().merge_from(*cells_);
×
732
    res.cells_.get_mutable().merge_from(*other.cells_);
×
733
    return res;
×
734
}
×
735

736
std::ostream& operator<<(std::ostream& o, const ArrayDomain& dom) { return o << dom.num_bytes; }
6✔
737
} // 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