• 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

94.82
/src/crab/split_dbm.cpp
1
// Copyright (c) Prevail Verifier contributors.
2
// SPDX-License-Identifier: Apache-2.0
3
#include <utility>
4

5
#include <gsl/narrow>
6

7
#include "crab/split_dbm.hpp"
8
#include "crab_utils/debug.hpp"
9
#include "crab_utils/graph_ops.hpp"
10
#include "crab_utils/stats.hpp"
11
#include "string_constraints.hpp"
12
#include "type_encoding.hpp"
13

14
namespace crab::domains {
15

16
static std::optional<SplitDBM::vert_id> try_at(const SplitDBM::vert_map_t& map, const variable_t v) {
9,313,854✔
17
    const auto it = map.find(v);
4,656,925✔
18
    if (it == map.end()) {
9,313,854✔
19
        return std::nullopt;
5,581,543✔
20
    }
21
    return it->second;
3,732,311✔
22
}
23

24
SplitDBM::vert_id SplitDBM::get_vert(variable_t v) {
2,489,287✔
25
    if (const auto y = try_at(vert_map, v)) {
2,489,287✔
26
        return *y;
941,663✔
27
    }
28

29
    vert_id vert(g.new_vertex());
1,547,624✔
30
    vert_map.emplace(v, vert);
1,547,624✔
31
    // Initialize
32
    assert(vert <= rev_map.size());
1,547,624✔
33
    if (vert < rev_map.size()) {
1,547,624✔
34
        potential[vert] = Weight(0);
1,198,944✔
35
        rev_map[vert] = v;
1,198,944✔
36
    } else {
37
        potential.emplace_back(0);
348,680✔
38
        rev_map.push_back(v);
348,680✔
39
    }
40
    vert_map.emplace(v, vert);
1,547,624✔
41

42
    assert(vert != 0);
1,547,624✔
43

44
    return vert;
1,547,624✔
45
}
46

47
/**
48
 * Helper to translate from Number to DBM Weight (graph weights).  Number
49
 * used to be the template parameter of the DBM-based abstract domain to
50
 * represent a number. Number might not fit into Weight type.
51
 **/
52
[[maybe_unused]]
53
static bool convert_NtoW_overflow(const number_t& n, safe_i64& out) {
54
    if (n.fits<int64_t>()) {
55
        out = n;
56
        return false;
57
    }
58
    return true;
59
}
60

61
[[maybe_unused]]
62
static bool convert_NtoW_overflow(const number_t& n, number_t& out) {
11,846,008✔
63
    out = n;
9,884,660✔
64
    return false;
11,846,008✔
65
}
66

67
void SplitDBM::diffcsts_of_assign(variable_t x, const linear_expression_t& exp,
680,664✔
68
                                  /* if true then process the upper
69
                                     bounds, else the lower bounds */
70
                                  bool extract_upper_bounds,
71
                                  /* foreach {v, k} \in diff_csts we have
72
                                     the difference constraint v - k <= k */
73
                                  std::vector<std::pair<variable_t, Weight>>& diff_csts) const {
74

75
    std::optional<variable_t> unbounded_var;
680,664✔
76
    std::vector<std::pair<variable_t, Weight>> terms;
680,664✔
77

78
    Weight residual;
680,664✔
79
    if (convert_NtoW_overflow(exp.constant_term(), residual)) {
680,664✔
80
        return;
81
    }
82

83
    for (const auto& [y, n] : exp.variable_terms()) {
1,371,332✔
84
        Weight coeff;
691,696✔
85
        if (convert_NtoW_overflow(n, coeff)) {
691,696✔
86
            continue;
87
        }
88

89
        if (coeff < Weight(0)) {
691,696✔
90
            // Can't do anything with negative coefficients.
91
            auto y_val = (extract_upper_bounds ? this->operator[](y).lb() : this->operator[](y).ub());
4,460✔
92

93
            if (y_val.is_infinite()) {
1,784✔
94
                return;
272✔
95
            }
96
            Weight ymax;
1,512✔
97
            if (convert_NtoW_overflow(*y_val.number(), ymax)) {
1,512✔
98
                continue;
99
            }
100
            // was before the condition
101
            residual += ymax * coeff;
2,268✔
102
        } else {
1,784✔
103
            auto y_val = (extract_upper_bounds ? this->operator[](y).ub() : this->operator[](y).lb());
1,724,780✔
104

105
            if (y_val.is_infinite()) {
689,912✔
106
                if (unbounded_var || coeff != Weight(1)) {
324,848✔
107
                    return;
756✔
108
                }
109
                unbounded_var = y;
560,630✔
110
            } else {
111
                Weight ymax;
473,104✔
112
                if (convert_NtoW_overflow(*y_val.number(), ymax)) {
473,104✔
113
                    continue;
114
                }
115
                residual += ymax * coeff;
709,656✔
116
                terms.emplace_back(y, ymax);
473,104✔
117
            }
473,104✔
118
        }
689,912✔
119
    }
691,696✔
120

121
    if (unbounded_var) {
679,636✔
122
        // There is exactly one unbounded variable with unit
123
        // coefficient
124
        diff_csts.emplace_back(*unbounded_var, residual);
215,264✔
125
    } else {
126
        for (const auto& [v, n] : terms) {
936,678✔
127
            diff_csts.emplace_back(v, residual - n);
708,459✔
128
        }
129
    }
130
}
681,178✔
131

132
void SplitDBM::diffcsts_of_lin_leq(const linear_expression_t& exp,
1,993,530✔
133
                                   /* difference contraints */
134
                                   std::vector<diffcst_t>& csts,
135
                                   /* x >= lb for each {x,lb} in lbs */
136
                                   std::vector<std::pair<variable_t, Weight>>& lbs,
137
                                   /* x <= ub for each {x,ub} in ubs */
138
                                   std::vector<std::pair<variable_t, Weight>>& ubs) const {
139
    Weight exp_ub;
1,993,530✔
140
    if (convert_NtoW_overflow(exp.constant_term(), exp_ub)) {
1,993,530✔
141
        return;
142
    }
143
    exp_ub = -exp_ub;
2,990,295✔
144

145
    // temporary hack
146
    Weight _tmp;
1,993,530✔
147
    if (convert_NtoW_overflow(exp.constant_term() - 1, _tmp)) {
2,990,295✔
148
        // We don't like MIN either because the code will compute
149
        // minus MIN, and it will silently overflow.
150
        return;
151
    }
152

153
    Weight unbounded_lbcoeff;
1,993,530✔
154
    Weight unbounded_ubcoeff;
1,993,530✔
155
    std::optional<variable_t> unbounded_lbvar;
1,993,530✔
156
    std::optional<variable_t> unbounded_ubvar;
1,993,530✔
157

158
    std::vector<std::pair<std::pair<Weight, variable_t>, Weight>> pos_terms, neg_terms;
1,993,530✔
159
    for (const auto& [y, n] : exp.variable_terms()) {
4,054,068✔
160
        Weight coeff;
2,886,147✔
161
        if (convert_NtoW_overflow(n, coeff)) {
2,886,147✔
162
            continue;
163
        }
164
        if (coeff > Weight(0)) {
2,886,147✔
165
            auto y_lb = this->operator[](y).lb();
1,488,750✔
166
            if (y_lb.is_infinite()) {
992,500✔
167
                if (unbounded_lbvar) {
869,707✔
168
                    return;
×
169
                }
170
                unbounded_lbvar = y;
869,707✔
171
                unbounded_lbcoeff = coeff;
1,365,957✔
172
            } else {
173
                Weight ymin;
122,793✔
174
                if (convert_NtoW_overflow(*y_lb.number(), ymin)) {
122,793✔
175
                    continue;
176
                }
177
                exp_ub -= ymin * coeff;
184,189✔
178
                pos_terms.push_back({{coeff, y}, ymin});
245,585✔
179
            }
122,793✔
180
        } else {
992,500✔
181
            auto y_ub = this->operator[](y).ub();
2,840,471✔
182
            if (y_ub.is_infinite()) {
1,893,647✔
183
                if (unbounded_ubvar) {
1,734,655✔
184
                    return;
825,609✔
185
                }
186
                unbounded_ubvar = y;
909,046✔
187
                unbounded_ubcoeff = -coeff;
1,363,569✔
188
            } else {
189
                Weight ymax;
158,992✔
190
                if (convert_NtoW_overflow(*y_ub.number(), ymax)) {
158,992✔
191
                    continue;
192
                }
193
                exp_ub -= ymax * coeff;
238,488✔
194
                neg_terms.push_back({{-coeff, y}, ymax});
397,480✔
195
            }
158,992✔
196
        }
1,893,647✔
197
    }
2,473,342✔
198

199
    if (unbounded_lbvar) {
1,167,921✔
200
        variable_t x{*unbounded_lbvar};
869,707✔
201
        if (unbounded_ubvar) {
869,707✔
202
            if (unbounded_lbcoeff == Weight(1) && unbounded_ubcoeff == Weight(1)) {
14,466✔
203
                csts.push_back({{x, *unbounded_ubvar}, exp_ub});
14,466✔
204
            }
205
        } else {
206
            if (unbounded_lbcoeff == Weight(1)) {
860,063✔
207
                for (const auto& [nv, k] : neg_terms) {
862,089✔
208
                    csts.push_back({{x, nv.second}, exp_ub - k});
3,039✔
209
                }
210
            }
211
            // Add bounds for x
212
            ubs.emplace_back(x, exp_ub / unbounded_lbcoeff);
1,290,095✔
213
        }
214
    } else {
215
        if (unbounded_ubvar) {
298,214✔
216
            variable_t y{*unbounded_ubvar};
73,793✔
217
            if (unbounded_ubcoeff == Weight(1)) {
73,793✔
218
                for (const auto& [nv, k] : pos_terms) {
76,021✔
219
                    csts.push_back({{nv.second, y}, exp_ub + k});
3,342✔
220
                }
221
            }
222
            // Add bounds for y
223
            lbs.emplace_back(y, -exp_ub / unbounded_ubcoeff);
110,689✔
224
        } else {
225
            for (const auto& [neg_nv, neg_k] : neg_terms) {
349,503✔
226
                for (const auto& [pos_nv, pos_k] : pos_terms) {
138,052✔
227
                    csts.push_back({{pos_nv.second, neg_nv.second}, exp_ub - neg_k + pos_k});
19,455✔
228
                }
229
            }
230
            for (const auto& [neg_nv, neg_k] : neg_terms) {
349,503✔
231
                lbs.emplace_back(neg_nv.second, -exp_ub / neg_nv.first + neg_k);
187,623✔
232
            }
233
            for (const auto& [pos_nv, pos_k] : pos_terms) {
343,782✔
234
                ubs.emplace_back(pos_nv.second, exp_ub / pos_nv.first + pos_k);
179,041✔
235
            }
236
        }
237
    }
238
}
4,057,550✔
239

240
static GraphOps::PotentialFunction index_to_call(const GraphOps::WeightVector& p) {
2,340,456✔
241
    return [&p](GraphOps::vert_id v) -> GraphOps::Weight { return p[v]; };
3,205,509,444✔
242
}
243

244
bool SplitDBM::add_linear_leq(const linear_expression_t& exp) {
1,993,530✔
245
    std::vector<std::pair<variable_t, Weight>> lbs, ubs;
1,993,530✔
246
    std::vector<diffcst_t> csts;
1,993,530✔
247
    diffcsts_of_lin_leq(exp, csts, lbs, ubs);
1,993,530✔
248

249
    for (const auto& [var, n] : lbs) {
2,178,505✔
250
        CRAB_LOG("zones-split", std::cout << var << ">=" << n << "\n");
193,177✔
251
        const vert_id vert = get_vert(var);
193,177✔
252
        if (auto w = g.lookup(vert, 0)) {
193,177✔
253
            if (*w <= -n) {
99,018✔
254
                continue;
70,634✔
255
            }
256
        }
131,906✔
257
        g.set_edge(vert, -n, 0);
122,543✔
258

259
        if (!repair_potential(vert, 0)) {
122,543✔
260
            return false;
24,262✔
261
        }
262
    }
263
    for (const auto& [var, n] : ubs) {
2,932,330✔
264
        CRAB_LOG("zones-split", std::cout << var << "<=" << n << "\n");
979,032✔
265
        const vert_id vert = get_vert(var);
979,032✔
266
        if (auto w = g.lookup(0, vert)) {
979,032✔
267
            if (*w <= n) {
89,124✔
268
                continue;
37,454✔
269
            }
270
        }
508,243✔
271
        g.set_edge(0, n, vert);
941,578✔
272
        if (!repair_potential(0, vert)) {
941,578✔
273
            return false;
36,176✔
274
        }
275
    }
276

277
    for (const auto& [diff, k] : csts) {
1,979,642✔
278
        CRAB_LOG("zones-split", std::cout << diff.first << "-" << diff.second << "<=" << k << "\n");
26,434✔
279

280
        const vert_id src = get_vert(diff.second);
26,434✔
281
        const vert_id dest = get_vert(diff.first);
26,434✔
282
        g.update_edge(src, k, dest);
26,434✔
283
        if (!repair_potential(src, dest)) {
26,434✔
284
            return false;
20,206✔
285
        }
286
        GraphOps::close_over_edge(g, src, dest);
26,344✔
287
    }
288
    GraphOps::apply_delta(g, GraphOps::close_after_assign(g, index_to_call(potential), 0));
1,953,208✔
289
    normalize();
1,953,208✔
290
    return true;
976,604✔
291
}
1,993,530✔
292

293
static interval_t trim_interval(const interval_t& i, const number_t& n) {
4,726✔
294
    if (i.lb() == n) {
9,452✔
295
        return interval_t{n + 1, i.ub()};
8,200✔
296
    }
297
    if (i.ub() == n) {
2,892✔
298
        return interval_t{i.lb(), n - 1};
16✔
299
    }
300
    if (i.is_top() && n == 0) {
1,513✔
301
        return interval_t{1, std::numeric_limits<uint64_t>::max()};
42✔
302
    }
303
    return i;
1,410✔
304
}
305

306
bool SplitDBM::add_univar_disequation(variable_t x, const number_t& n) {
4,726✔
307
    interval_t i = get_interval(x, 0);
7,089✔
308
    interval_t new_i = trim_interval(i, n);
4,726✔
309
    if (new_i.is_bottom()) {
4,726✔
310
        return false;
1,621✔
311
    }
312
    if (new_i.is_top() || !(new_i <= i)) {
1,484✔
313
        return true;
122✔
314
    }
315

316
    vert_id v = get_vert(x);
1,362✔
317
    if (new_i.lb().is_finite()) {
2,043✔
318
        // strengthen lb
319
        Weight lb_val;
1,362✔
320
        if (convert_NtoW_overflow(-*new_i.lb().number(), lb_val)) {
3,405✔
321
            return true;
322
        }
323

324
        if (auto w = g.lookup(v, 0)) {
1,362✔
325
            if (lb_val < *w) {
1,334✔
326
                g.set_edge(v, lb_val, 0);
38✔
327
                if (!repair_potential(v, 0)) {
38✔
328
                    return false;
329
                }
330
                // Update other bounds
331
                for (const auto e : g.e_preds(v)) {
120✔
332
                    if (e.vert == 0) {
82✔
333
                        continue;
38✔
334
                    }
335
                    g.update_edge(e.vert, e.val + lb_val, 0);
44✔
336
                    if (!repair_potential(e.vert, 0)) {
44✔
337
                        return false;
×
338
                    }
339
                }
82✔
340
            }
341
        }
1,362✔
342
    }
1,362✔
343
    if (new_i.ub().is_finite()) {
2,043✔
344
        // strengthen ub
345
        Weight ub_val;
1,362✔
346
        if (convert_NtoW_overflow(*new_i.ub().number(), ub_val)) {
2,724✔
347
            return true;
348
        }
349

350
        if (auto w = g.lookup(0, v)) {
1,362✔
351
            if (ub_val < *w) {
1,334✔
352
                g.set_edge(0, ub_val, v);
8✔
353
                if (!repair_potential(0, v)) {
8✔
354
                    return false;
355
                }
356
                // Update other bounds
357
                for (const auto e : g.e_succs(v)) {
20✔
358
                    if (e.vert == 0) {
12✔
359
                        continue;
8✔
360
                    }
361
                    g.update_edge(0, e.val + ub_val, e.vert);
4✔
362
                    if (!repair_potential(0, e.vert)) {
4✔
363
                        return false;
×
364
                    }
365
                }
12✔
366
            }
367
        }
1,362✔
368
    }
1,362✔
369
    normalize();
1,362✔
370
    return true;
681✔
371
}
7,089✔
372

373
bool SplitDBM::operator<=(const SplitDBM& o) const {
198✔
374
    CrabStats::count("SplitDBM.count.leq");
297✔
375
    ScopedCrabStats __st__("SplitDBM.leq");
198✔
376

377
    // cover all trivial cases to avoid allocating a dbm matrix
378
    if (o.is_top()) {
198✔
379
        return true;
380
    }
381
    if (is_top()) {
198✔
382
        return false;
383
    }
384

385
    if (vert_map.size() < o.vert_map.size()) {
396✔
386
        return false;
23✔
387
    }
388

389
    // Set up a mapping from o to this.
390
    std::vector<unsigned int> vert_renaming(o.g.size(), -1);
152✔
391
    vert_renaming[0] = 0;
152✔
392
    for (const auto& [v, n] : o.vert_map) {
1,192✔
393
        if (o.g.succs(n).size() == 0 && o.g.preds(n).size() == 0) {
592✔
394
            continue;
×
395
        }
396

397
        // We can't have this <= o if we're missing some vertex.
398
        if (auto y = try_at(vert_map, v)) {
592✔
399
            vert_renaming[n] = *y;
592✔
400
        } else {
401
            return false;
×
402
        }
403
    }
404

405
    assert(g.size() > 0);
152✔
406
    for (vert_id ox : o.g.verts()) {
508✔
407
        if (o.g.succs(ox).size() == 0) {
432✔
408
            continue;
28✔
409
        }
410

411
        assert(vert_renaming[ox] != (unsigned)-1);
404✔
412
        vert_id x = vert_renaming[ox];
404✔
413
        for (const auto edge : o.g.e_succs(ox)) {
1,238✔
414
            vert_id oy = edge.vert;
910✔
415
            assert(vert_renaming[oy] != (unsigned)-1);
910✔
416
            vert_id y = vert_renaming[oy];
910✔
417
            Weight ow = edge.val;
910✔
418

419
            if (auto w = g.lookup(x, y)) {
910✔
420
                if (*w <= ow) {
910✔
421
                    continue;
834✔
422
                }
423
            }
872✔
424

425
            if (auto wx = g.lookup(x, 0)) {
76✔
426
                if (auto wy = g.lookup(0, y)) {
×
427
                    if (*wx + *wy <= ow) {
×
428
                        continue;
×
429
                    }
430
                }
×
431
            }
38✔
432
            return false;
76✔
433
        }
1,365✔
434
    }
435
    return true;
76✔
436
}
198✔
437

438
SplitDBM SplitDBM::operator|(const SplitDBM& o) const& {
24,216✔
439
    CrabStats::count("SplitDBM.count.join");
36,324✔
440
    ScopedCrabStats __st__("SplitDBM.join");
24,216✔
441

442
    if (o.is_top()) {
24,216✔
UNCOV
443
        return o;
×
444
    }
445
    if (is_top()) {
24,216✔
446
        return *this;
504✔
447
    }
448
    CRAB_LOG("zones-split", std::cout << "Before join:\n"
23,712✔
449
                                      << "DBM 1\n"
450
                                      << *this << "\n"
451
                                      << "DBM 2\n"
452
                                      << o << "\n");
453
    // Figure out the common renaming, initializing the
454
    // resulting potentials as we go.
455
    std::vector<vert_id> perm_x;
23,712✔
456
    std::vector<vert_id> perm_y;
23,712✔
457
    std::vector<variable_t> perm_inv;
23,712✔
458

459
    std::vector<Weight> pot_rx;
23,712✔
460
    std::vector<Weight> pot_ry;
23,712✔
461
    vert_map_t out_vmap;
11,856✔
462
    rev_map_t out_revmap;
23,712✔
463
    // Add the zero vertex
464
    assert(!potential.empty());
23,712✔
465
    pot_rx.emplace_back(0);
23,712✔
466
    pot_ry.emplace_back(0);
23,712✔
467
    perm_x.push_back(0);
23,712✔
468
    perm_y.push_back(0);
23,712✔
469
    out_revmap.push_back(std::nullopt);
23,712✔
470

471
    for (const auto& [v, n] : vert_map) {
2,533,968✔
472
        if (auto y = try_at(o.vert_map, v)) {
1,657,696✔
473
            // Variable exists in both
474
            out_vmap.emplace(v, gsl::narrow<vert_id>(perm_x.size()));
1,606,056✔
475
            out_revmap.push_back(v);
1,606,056✔
476

477
            pot_rx.push_back(potential[n] - potential[0]);
2,409,084✔
478
            // XXX JNL: check this out
479
            // pot_ry.push_back(o.potential[p.second] - o.potential[0]);
480
            pot_ry.push_back(o.potential[*y] - o.potential[0]);
2,409,084✔
481
            perm_inv.push_back(v);
1,606,056✔
482
            perm_x.push_back(n);
1,606,056✔
483
            perm_y.push_back(*y);
1,606,056✔
484
        }
485
    }
486
    size_t sz = perm_x.size();
23,712✔
487

488
    // Build the permuted view of x and y.
489
    assert(g.size() > 0);
23,712✔
490
    GraphPerm gx(perm_x, g);
23,712✔
491
    assert(o.g.size() > 0);
23,712✔
492
    GraphPerm gy(perm_y, o.g);
23,712✔
493

494
    // Compute the deferred relations
495
    graph_t g_ix_ry;
23,712✔
496
    g_ix_ry.growTo(sz);
23,712✔
497
    SubGraph gy_excl(gy, 0);
23,712✔
498
    for (vert_id s : gy_excl.verts()) {
2,432,799✔
499
        for (vert_id d : gy_excl.succs(s)) {
3,397,940✔
500
            if (auto ws = gx.lookup(s, 0)) {
988,856✔
501
                if (auto wd = gx.lookup(0, d)) {
901,002✔
502
                    g_ix_ry.add_edge(s, *ws + *wd, d);
1,347,990✔
503
                }
901,002✔
504
            }
988,856✔
505
        }
506
    }
507
    // Apply the deferred relations, and re-close.
508
    bool is_closed;
11,856✔
509
    graph_t g_rx(GraphOps::meet(gx, g_ix_ry, is_closed));
23,712✔
510
    if (!is_closed) {
23,712✔
511
        GraphOps::apply_delta(g_rx, GraphOps::close_after_meet(SubGraph(g_rx, 0), index_to_call(pot_rx), gx, g_ix_ry));
23,712✔
512
    }
513

514
    graph_t g_rx_iy;
23,712✔
515
    g_rx_iy.growTo(sz);
23,712✔
516

517
    SubGraph gx_excl(gx, 0);
23,712✔
518
    for (vert_id s : gx_excl.verts()) {
2,432,799✔
519
        for (vert_id d : gx_excl.succs(s)) {
3,629,626✔
520
            // Assumption: gx.mem(s, d) -> gx.edge_val(s, d) <= ranges[var(s)].ub() - ranges[var(d)].lb()
521
            // That is, if the relation exists, it's at least as strong as the bounds.
522
            if (auto ws = gy.lookup(s, 0)) {
1,220,542✔
523
                if (auto wd = gy.lookup(0, d)) {
1,128,280✔
524
                    g_rx_iy.add_edge(s, *ws + *wd, d);
1,686,753✔
525
                }
1,128,280✔
526
            }
1,220,542✔
527
        }
528
    }
529
    // Similarly, should use a SubGraph view.
530
    graph_t g_ry(GraphOps::meet(gy, g_rx_iy, is_closed));
23,712✔
531
    if (!is_closed) {
23,712✔
532
        GraphOps::apply_delta(g_ry, GraphOps::close_after_meet(SubGraph(g_ry, 0), index_to_call(pot_ry), gy, g_rx_iy));
23,712✔
533
    }
534

535
    // We now have the relevant set of relations. Because g_rx and g_ry are closed,
536
    // the result is also closed.
537
    graph_t join_g(GraphOps::join(g_rx, g_ry));
23,712✔
538

539
    // Now reapply the missing independent relations.
540
    // Need to derive vert_ids from lb_up/lb_down, and make sure the vertices exist
541
    std::vector<vert_id> lb_up;
23,712✔
542
    std::vector<vert_id> lb_down;
23,712✔
543
    std::vector<vert_id> ub_up;
23,712✔
544
    std::vector<vert_id> ub_down;
23,712✔
545

546
    for (vert_id v : gx_excl.verts()) {
2,432,799✔
547
        if (auto wx = gx.lookup(0, v)) {
1,606,056✔
548
            if (auto wy = gy.lookup(0, v)) {
1,490,946✔
549
                if (*wx < *wy) {
1,483,342✔
550
                    ub_up.push_back(v);
22,060✔
551
                }
552
                if (*wy < *wx) {
1,483,342✔
553
                    ub_down.push_back(v);
45,970✔
554
                }
555
            }
1,490,946✔
556
        }
803,028✔
557
        if (auto wx = gx.lookup(v, 0)) {
1,606,056✔
558
            if (auto wy = gy.lookup(v, 0)) {
1,493,426✔
559
                if (*wx < *wy) {
1,486,008✔
560
                    lb_down.push_back(v);
18,042✔
561
                }
562
                if (*wy < *wx) {
1,486,008✔
563
                    lb_up.push_back(v);
43,478✔
564
                }
565
            }
1,493,426✔
566
        }
1,606,056✔
567
    }
568

569
    for (vert_id s : lb_up) {
67,190✔
570
        Weight dx_s = gx.edge_val(s, 0);
43,478✔
571
        Weight dy_s = gy.edge_val(s, 0);
43,478✔
572
        for (vert_id d : ub_up) {
103,532✔
573
            if (s == d) {
60,054✔
574
                continue;
13,368✔
575
            }
576

577
            join_g.update_edge(s, std::max(dx_s + gx.edge_val(0, d), dy_s + gy.edge_val(0, d)), d);
70,029✔
578
        }
579
    }
43,478✔
580

581
    for (vert_id s : lb_down) {
41,754✔
582
        Weight dx_s = gx.edge_val(s, 0);
18,042✔
583
        Weight dy_s = gy.edge_val(s, 0);
18,042✔
584
        for (vert_id d : ub_down) {
73,014✔
585
            if (s == d) {
54,972✔
586
                continue;
13,248✔
587
            }
588

589
            join_g.update_edge(s, std::max(dx_s + gx.edge_val(0, d), dy_s + gy.edge_val(0, d)), d);
62,586✔
590
        }
591
    }
18,042✔
592

593
    // Conjecture: join_g remains closed.
594

595
    // Now garbage collect any unused vertices
596
    for (vert_id v : join_g.verts()) {
1,653,480✔
597
        if (v == 0) {
1,629,768✔
598
            continue;
23,712✔
599
        }
600
        if (join_g.succs(v).size() == 0 && join_g.preds(v).size() == 0) {
1,606,056✔
601
            join_g.forget(v);
32,064✔
602
            if (out_revmap[v]) {
32,064✔
603
                out_vmap.erase(*(out_revmap[v]));
32,064✔
604
                out_revmap[v] = std::nullopt;
846,948✔
605
            }
606
        }
607
    }
608

609
    // SplitDBM res(join_range, out_vmap, out_revmap, join_g, join_pot);
610
    SplitDBM res(std::move(out_vmap), std::move(out_revmap), std::move(join_g), std::move(pot_rx), vert_set_t());
23,712✔
611
    // join_g.check_adjs();
612
    CRAB_LOG("zones-split", std::cout << "Result join:\n" << res << "\n");
23,712✔
613

614
    return res;
23,712✔
615
}
47,928✔
616

617
SplitDBM SplitDBM::widen(const SplitDBM& o) const {
84✔
618
    CrabStats::count("SplitDBM.count.widening");
126✔
619
    ScopedCrabStats __st__("SplitDBM.widening");
84✔
620

621
    CRAB_LOG("zones-split", std::cout << "Before widening:\n"
84✔
622
                                      << "DBM 1\n"
623
                                      << *this << "\n"
624
                                      << "DBM 2\n"
625
                                      << o << "\n");
626

627
    // Figure out the common renaming
628
    assert(!potential.empty());
84✔
629
    std::vector<Weight> widen_pot = {0};
126✔
630
    std::vector<vert_id> perm_x = {0};
126✔
631
    std::vector<vert_id> perm_y = {0};
168✔
632
    vert_map_t out_vmap;
42✔
633
    rev_map_t out_revmap = {std::nullopt};
126✔
634
    for (const auto& [v, n] : vert_map) {
4,035✔
635
        if (auto y = try_at(o.vert_map, v)) {
2,578✔
636
            // Variable exists in both
637
            out_vmap.emplace(v, gsl::narrow<vert_id>(perm_x.size()));
346✔
638
            out_revmap.push_back(v);
346✔
639

640
            widen_pot.push_back(potential[n] - potential[0]);
519✔
641
            perm_x.push_back(n);
346✔
642
            perm_y.push_back(*y);
346✔
643
        }
644
    }
645

646
    // Build the permuted view of x and y.
647
    assert(g.size() > 0);
84✔
648
    GraphPerm gx(perm_x, g);
84✔
649
    assert(o.g.size() > 0);
84✔
650
    GraphPerm gy(perm_y, o.g);
84✔
651

652
    // Now perform the widening
653
    vert_set_t widen_unstable(unstable);
84✔
654
    graph_t widen_g(GraphOps::widen(gx, gy, widen_unstable));
84✔
655

656
    SplitDBM res(std::move(out_vmap), std::move(out_revmap), std::move(widen_g), std::move(widen_pot),
84✔
657
                 std::move(widen_unstable));
126✔
658

659
    CRAB_LOG("zones-split", std::cout << "Result widening:\n" << res << "\n");
84✔
660
    return res;
126✔
661
}
210✔
662

663
std::optional<SplitDBM> SplitDBM::meet(const SplitDBM& o) const {
38✔
664
    CrabStats::count("SplitDBM.count.meet");
57✔
665
    ScopedCrabStats __st__("SplitDBM.meet");
38✔
666

667
    if (is_top()) {
38✔
668
        return o;
×
669
    }
670
    if (o.is_top()) {
38✔
671
        return *this;
×
672
    }
673
    CRAB_LOG("zones-split", std::cout << "Before meet:\n"
38✔
674
                                      << "DBM 1\n"
675
                                      << *this << "\n"
676
                                      << "DBM 2\n"
677
                                      << o << "\n");
678

679
    // We map vertices in the left operand onto a contiguous range.
680
    // This will often be the identity map, but there might be gaps.
681
    vert_map_t meet_verts;
19✔
682
    rev_map_t meet_rev;
38✔
683

684
    std::vector<vert_id> perm_x;
38✔
685
    std::vector<vert_id> perm_y;
38✔
686
    std::vector<Weight> meet_pi;
38✔
687
    perm_x.push_back(0);
38✔
688
    perm_y.push_back(0);
38✔
689
    meet_pi.emplace_back(0);
38✔
690
    meet_rev.push_back(std::nullopt);
38✔
691
    for (const auto& [v, n] : vert_map) {
310✔
692
        vert_id vv = gsl::narrow<vert_id>(perm_x.size());
156✔
693
        meet_verts.emplace(v, vv);
78✔
694
        meet_rev.push_back(v);
156✔
695

696
        perm_x.push_back(n);
156✔
697
        perm_y.push_back(-1);
156✔
698
        meet_pi.push_back(potential[n] - potential[0]);
234✔
699
    }
700

701
    // Add missing mappings from the right operand.
702
    for (const auto& [v, n] : o.vert_map) {
3,553✔
703
        auto it = meet_verts.find(v);
1,159✔
704

705
        if (it == meet_verts.end()) {
2,318✔
706
            vert_id vv = gsl::narrow<vert_id>(perm_y.size());
2,216✔
707
            meet_rev.push_back(v);
2,216✔
708

709
            perm_y.push_back(n);
2,216✔
710
            perm_x.push_back(-1);
2,216✔
711
            meet_pi.push_back(o.potential[n] - o.potential[0]);
3,324✔
712
            meet_verts.emplace(v, vv);
2,216✔
713
        } else {
714
            perm_y[it->second] = n;
153✔
715
        }
716
    }
717

718
    // Build the permuted view of x and y.
719
    assert(g.size() > 0);
38✔
720
    GraphPerm gx(perm_x, g);
38✔
721
    assert(o.g.size() > 0);
38✔
722
    GraphPerm gy(perm_y, o.g);
38✔
723

724
    // Compute the syntactic meet of the permuted graphs.
725
    bool is_closed{};
38✔
726
    graph_t meet_g(GraphOps::meet(gx, gy, is_closed));
38✔
727

728
    // Compute updated potentials on the zero-enriched graph
729
    // vector<Weight> meet_pi(meet_g.size());
730
    // We've warm-started pi with the operand potentials
731
    if (!GraphOps::select_potentials(meet_g, meet_pi)) {
38✔
732
        // Potentials cannot be selected -- state is infeasible.
733
        return {};
×
734
    }
735

736
    if (!is_closed) {
38✔
737
        const auto potential_func = index_to_call(meet_pi);
38✔
738
        GraphOps::apply_delta(meet_g, GraphOps::close_after_meet(SubGraph(meet_g, 0), potential_func, gx, gy));
38✔
739

740
        // Recover updated LBs and UBs.<
741

742
        GraphOps::apply_delta(meet_g, GraphOps::close_after_assign(meet_g, potential_func, 0));
38✔
743
    }
38✔
744
    SplitDBM res(std::move(meet_verts), std::move(meet_rev), std::move(meet_g), std::move(meet_pi), vert_set_t());
38✔
745
    CRAB_LOG("zones-split", std::cout << "Result meet:\n" << res << "\n");
38✔
746
    return res;
38✔
747
}
76✔
748

749
void SplitDBM::havoc(const variable_t v) {
4,818,849✔
750
    if (const auto y = try_at(vert_map, v)) {
4,818,849✔
751
        g.forget(*y);
895,576✔
752
        rev_map[*y] = std::nullopt;
895,576✔
753
        vert_map.erase(v);
895,576✔
754
        normalize();
895,576✔
755
    }
756
}
4,818,849✔
757

758
bool SplitDBM::add_constraint(const linear_constraint_t& cst) {
1,971,754✔
759
    CrabStats::count("SplitDBM.count.add_constraints");
2,957,631✔
760
    ScopedCrabStats __st__("SplitDBM.add_constraints");
1,971,754✔
761

762
    if (cst.is_tautology()) {
1,971,754✔
763
        return true;
72✔
764
    }
765

766
    // g.check_adjs();
767

768
    if (cst.is_contradiction()) {
1,971,610✔
769
        return false;
334✔
770
    }
771

772
    switch (cst.kind()) {
1,970,942✔
773
    case constraint_kind_t::LESS_THAN_OR_EQUALS_ZERO: {
1,885,886✔
774
        if (!add_linear_leq(cst.expression())) {
1,885,886✔
775
            return false;
19,928✔
776
        }
777
        //  g.check_adjs();
778
        CRAB_LOG("zones-split", std::cout << "--- " << cst << "\n" << *this << "\n");
1,846,030✔
779
        break;
923,015✔
780
    }
781
    case constraint_kind_t::LESS_THAN_ZERO: {
52,006✔
782
        // We try to convert a strict to non-strict.
783
        // e < 0 --> e <= -1
784
        const auto nc = linear_constraint_t(cst.expression().plus(1), constraint_kind_t::LESS_THAN_OR_EQUALS_ZERO);
78,009✔
785
        if (!add_linear_leq(nc.expression())) {
52,006✔
786
            return false;
376✔
787
        }
788
        CRAB_LOG("zones-split", std::cout << "--- " << cst << "\n" << *this << "\n");
51,630✔
789
        break;
51,630✔
790
    }
52,006✔
791
    case constraint_kind_t::EQUALS_ZERO: {
27,840✔
792
        const linear_expression_t& exp = cst.expression();
27,840✔
793
        if (!add_linear_leq(exp) || !add_linear_leq(exp.negate())) {
55,614✔
794
            CRAB_LOG("zones-split", std::cout << " ~~> _|_" << "\n");
90✔
795
            return false;
90✔
796
        }
797
        // g.check_adjs();
798
        CRAB_LOG("zones-split", std::cout << "--- " << cst << "\n" << *this << "\n");
27,750✔
799
        break;
13,875✔
800
    }
801
    case constraint_kind_t::NOT_ZERO: {
5,210✔
802
        // XXX: similar precision as the interval domain
803
        const linear_expression_t& e = cst.expression();
5,210✔
804
        for (const auto& [variable, coefficient] : e.variable_terms()) {
8,074✔
805
            interval_t i = compute_residual(e, variable) / interval_t(coefficient);
9,159✔
806
            if (auto k = i.singleton()) {
6,106✔
807
                if (!add_univar_disequation(variable, *k)) {
4,726✔
808
                    return false;
3,242✔
809
                }
810
            }
6,106✔
811
        }
6,106✔
812
    } break;
984✔
813
    }
814

815
    CRAB_WARN("Unhandled constraint ", cst, " by split_dbm");
1,927,378✔
816
    CRAB_LOG("zones-split", std::cout << "---" << cst << "\n" << *this << "\n");
1,927,378✔
817
    normalize();
1,927,378✔
818
    return true;
963,689✔
819
}
1,971,754✔
820

821
void SplitDBM::assign(variable_t lhs, const linear_expression_t& e) {
611,311✔
822
    CrabStats::count("SplitDBM.count.assign");
916,967✔
823
    ScopedCrabStats __st__("SplitDBM.assign");
611,311✔
824

825
    CRAB_LOG("zones-split", std::cout << "Before assign: " << *this << "\n");
611,311✔
826
    CRAB_LOG("zones-split", std::cout << lhs << ":=" << e << "\n");
611,311✔
827

828
    interval_t value_interval = eval_interval(e);
611,311✔
829

830
    std::optional<Weight> lb_w, ub_w;
611,311✔
831
    if (value_interval.lb().is_finite()) {
916,966✔
832
        Weight tmp;
503,177✔
833
        if (convert_NtoW_overflow(-*value_interval.lb().number(), tmp)) {
1,257,941✔
834
            havoc(lhs);
835
            CRAB_LOG("zones-split", std::cout << "---" << lhs << ":=" << e << "\n" << *this << "\n");
836
            normalize();
837
            return;
838
        }
839
        lb_w = tmp;
503,177✔
840
    }
503,177✔
841
    if (value_interval.ub().is_finite()) {
916,966✔
842
        Weight tmp;
503,153✔
843
        if (convert_NtoW_overflow(*value_interval.ub().number(), tmp)) {
1,006,305✔
844
            havoc(lhs);
845
            CRAB_LOG("zones-split", std::cout << "---" << lhs << ":=" << e << "\n" << *this << "\n");
846
            normalize();
847
            return;
848
        }
849
        ub_w = tmp;
503,153✔
850
    }
503,153✔
851

852
    // JN: it seems that we can only do this if
853
    // close_bounds_inline is disabled (which in eBPF is always the case).
854
    // Otherwise, the meet operator misses some non-redundant edges.
855
    if (value_interval.is_singleton()) {
611,311✔
856
        set(lhs, value_interval);
270,979✔
857
        normalize();
270,979✔
858
        return;
135,490✔
859
    }
860

861
    std::vector<std::pair<variable_t, Weight>> diffs_lb, diffs_ub;
340,332✔
862
    // Construct difference constraints from the assignment
863
    diffcsts_of_assign(lhs, e, diffs_lb, diffs_ub);
340,332✔
864
    if (diffs_lb.empty() && diffs_ub.empty()) {
340,332✔
865
        set(lhs, value_interval);
592✔
866
        normalize();
592✔
867
        return;
296✔
868
    }
869

870
    Weight e_val;
339,740✔
871
    if (eval_expression_overflow(e, e_val)) {
339,740✔
NEW
872
        havoc(lhs);
×
873
        return;
×
874
    }
875
    // Allocate a new vertex for x
876
    vert_id vert = g.new_vertex();
339,740✔
877
    assert(vert <= rev_map.size());
339,740✔
878
    if (vert == rev_map.size()) {
339,740✔
879
        rev_map.push_back(lhs);
53,314✔
880
        potential.push_back(potential[0] + e_val);
79,971✔
881
    } else {
882
        potential[vert] = potential[0] + e_val;
286,426✔
883
        rev_map[vert] = lhs;
286,426✔
884
    }
885

886
    {
169,870✔
887
        GraphOps::edge_vector delta;
339,740✔
888
        for (const auto& [var, n] : diffs_lb) {
683,526✔
889
            delta.emplace_back(vert, get_vert(var), -n);
343,786✔
890
        }
891

892
        for (const auto& [var, n] : diffs_ub) {
683,524✔
893
            delta.emplace_back(get_vert(var), vert, n);
343,784✔
894
        }
895

896
        // apply_delta should be safe here, as x has no edges in G.
897
        GraphOps::apply_delta(g, delta);
339,740✔
898
    }
339,740✔
899
    GraphOps::apply_delta(g, GraphOps::close_after_assign(SubGraph(g, 0), index_to_call(potential), vert));
339,740✔
900

901
    if (lb_w) {
339,740✔
902
        g.update_edge(vert, *lb_w, 0);
348,180✔
903
    }
904
    if (ub_w) {
339,740✔
905
        g.update_edge(0, *ub_w, vert);
348,144✔
906
    }
907
    // Clear the old x vertex
908
    havoc(lhs);
339,740✔
909
    vert_map.emplace(lhs, vert);
339,740✔
910

911
    normalize();
339,740✔
912
    CRAB_LOG("zones-split", std::cout << "---" << lhs << ":=" << e << "\n" << *this << "\n");
339,740✔
913
}
1,325,212✔
914

915
SplitDBM SplitDBM::narrow(const SplitDBM& o) const {
×
916
    CrabStats::count("SplitDBM.count.narrowing");
×
917
    ScopedCrabStats __st__("SplitDBM.narrowing");
×
918

919
    if (is_top()) {
×
920
        return o;
×
921
    }
922
    // FIXME: Implement properly
923
    // Narrowing as a no-op should be sound.
924
    return {*this};
×
925
}
×
926

927
class vert_set_wrap_t {
928
  public:
929
    explicit vert_set_wrap_t(const SplitDBM::vert_set_t& _vs) : vs(_vs) {}
46✔
930

931
    bool operator[](const SplitDBM::vert_id v) const { return vs.contains(v); }
206✔
932
    const SplitDBM::vert_set_t& vs;
933
};
934

935
bool SplitDBM::repair_potential(vert_id src, vert_id dest) {
1,090,649✔
936
    return GraphOps::repair_potential(g, potential, src, dest);
1,090,649✔
937
}
938

939
void SplitDBM::clear_thread_local_state() { GraphOps::clear_thread_local_state(); }
×
940

941
void SplitDBM::normalize() {
6,197,923✔
942
    CrabStats::count("SplitDBM.count.normalize");
9,296,887✔
943
    ScopedCrabStats __st__("SplitDBM.normalize");
6,197,923✔
944

945
    // dbm_canonical(_dbm);
946
    // Always maintained in normal form, except for widening
947
    if (unstable.empty()) {
6,197,923✔
948
        return;
6,197,877✔
949
    }
950

951
    GraphOps::edge_vector delta;
46✔
952
    // GraphOps::close_after_widen(g, potential, vert_set_wrap_t(unstable), delta);
953
    // GKG: Check
954
    const auto p = index_to_call(potential);
46✔
955
    GraphOps::apply_delta(g, GraphOps::close_after_widen(SubGraph(g, 0), p, vert_set_wrap_t(unstable)));
46✔
956
    // Retrieve variable bounds
957
    GraphOps::apply_delta(g, GraphOps::close_after_assign(g, p, 0));
46✔
958

959
    unstable.clear();
46✔
960
}
6,197,923✔
961

962
void SplitDBM::set(const variable_t x, const interval_t& intv) {
1,120,890✔
963
    CrabStats::count("SplitDBM.count.assign");
1,681,336✔
964
    ScopedCrabStats __st__("SplitDBM.assign");
1,120,890✔
965
    assert(!intv.is_bottom());
1,120,890✔
966

967
    havoc(x);
1,120,890✔
968

969
    if (intv.is_top()) {
1,120,890✔
970
        return;
545,612✔
971
    }
972

973
    const vert_id v = get_vert(x);
575,278✔
974
    if (intv.ub().is_finite()) {
862,916✔
975
        Weight ub;
575,272✔
976
        if (convert_NtoW_overflow(*intv.ub().number(), ub)) {
1,150,542✔
977
            normalize();
978
            return;
979
        }
980
        potential[v] = potential[0] + ub;
575,272✔
981
        g.set_edge(0, ub, v);
862,907✔
982
    }
575,272✔
983
    if (intv.lb().is_finite()) {
862,916✔
984
        Weight lb;
575,122✔
985
        if (convert_NtoW_overflow(*intv.lb().number(), lb)) {
1,150,242✔
986
            normalize();
987
            return;
988
        }
989
        potential[v] = potential[0] + lb;
575,122✔
990
        g.set_edge(v, -lb, 0);
862,682✔
991
    }
575,122✔
992
    normalize();
575,278✔
993
}
1,120,890✔
994

995
void SplitDBM::apply(const arith_binop_t op, const variable_t x, const variable_t y, const variable_t z,
6,058✔
996
                     const int finite_width) {
997
    CrabStats::count("SplitDBM.count.apply");
9,087✔
998
    ScopedCrabStats __st__("SplitDBM.apply");
6,058✔
999

1000
    switch (op) {
6,058✔
1001
    case arith_binop_t::ADD: assign(x, linear_expression_t(y).plus(z)); break;
4,974✔
1002
    case arith_binop_t::SUB: assign(x, linear_expression_t(y).subtract(z)); break;
796✔
1003
    // For the rest of operations, we fall back on intervals.
1004
    case arith_binop_t::MUL: set(x, get_interval(y, finite_width) * get_interval(z, finite_width)); break;
180✔
1005
    case arith_binop_t::SDIV: set(x, get_interval(y, finite_width).sdiv(get_interval(z, finite_width))); break;
80✔
1006
    case arith_binop_t::UDIV: set(x, get_interval(y, finite_width).udiv(get_interval(z, finite_width))); break;
260✔
1007
    case arith_binop_t::SREM: set(x, get_interval(y, finite_width).srem(get_interval(z, finite_width))); break;
115✔
1008
    case arith_binop_t::UREM: set(x, get_interval(y, finite_width).urem(get_interval(z, finite_width))); break;
85✔
1009
    default: CRAB_ERROR("DBM: unreachable");
×
1010
    }
1011
    normalize();
6,058✔
1012
}
6,058✔
1013

1014
// As defined in the BPF ISA specification, the immediate value of an unsigned modulo and division is treated
1015
// differently depending on the width:
1016
// * for 32 bit, as a 32-bit unsigned integer
1017
// * for 64 bit, as a 32-bit (not 64 bit) signed integer
1018
static number_t read_imm_for_udiv_or_umod(const number_t& imm, const int width) {
2,206✔
1019
    assert(width == 32 || width == 64);
2,206✔
1020
    if (width == 32) {
2,206✔
1021
        return number_t{imm.cast_to<uint32_t>()};
2,104✔
1022
    }
1023
    return number_t{imm.cast_to<int32_t>()};
102✔
1024
}
1025

1026
// As defined in the BPF ISA specification, the immediate value of a signed modulo and division is treated
1027
// differently depending on the width:
1028
// * for 32 bit, as a 32-bit signed integer
1029
// * for 64 bit, as a 64-bit signed integer
1030
static number_t read_imm_for_sdiv_or_smod(const number_t& imm, const int width) {
52✔
1031
    assert(width == 32 || width == 64);
52✔
1032
    if (width == 32) {
52✔
1033
        return number_t{imm.cast_to<int32_t>()};
20✔
1034
    }
1035
    return number_t{imm.cast_to<int64_t>()};
32✔
1036
}
1037

1038
void SplitDBM::apply(const arith_binop_t op, const variable_t x, const variable_t y, const number_t& k,
48,936✔
1039
                     const int finite_width) {
1040
    CrabStats::count("SplitDBM.count.apply");
73,404✔
1041
    ScopedCrabStats __st__("SplitDBM.apply");
48,936✔
1042

1043
    switch (op) {
48,936✔
1044
    case arith_binop_t::ADD: assign(x, linear_expression_t(y).plus(k)); break;
45,392✔
1045
    case arith_binop_t::SUB: assign(x, linear_expression_t(y).subtract(k)); break;
1,038✔
1046
    case arith_binop_t::MUL:
248✔
1047
        assign(x, linear_expression_t(k, y));
248✔
1048
        break;
248✔
1049
        // For the rest of operations, we fall back on intervals.
1050
    case arith_binop_t::SDIV:
18✔
1051
        set(x, get_interval(y, finite_width).sdiv(interval_t{read_imm_for_sdiv_or_smod(k, finite_width)}));
36✔
1052
        break;
18✔
1053
    case arith_binop_t::UDIV:
2,182✔
1054
        set(x, get_interval(y, finite_width).udiv(interval_t{read_imm_for_udiv_or_umod(k, finite_width)}));
4,364✔
1055
        break;
2,182✔
1056
    case arith_binop_t::SREM:
34✔
1057
        set(x, get_interval(y, finite_width).srem(interval_t{read_imm_for_sdiv_or_smod(k, finite_width)}));
68✔
1058
        break;
34✔
1059
    case arith_binop_t::UREM:
24✔
1060
        set(x, get_interval(y, finite_width).urem(interval_t{read_imm_for_udiv_or_umod(k, finite_width)}));
48✔
1061
        break;
24✔
1062
    default: CRAB_ERROR("DBM: unreachable");
×
1063
    }
1064
    normalize();
48,936✔
1065
}
48,936✔
1066

1067
void SplitDBM::apply(bitwise_binop_t op, variable_t x, variable_t y, variable_t z, int finite_width) {
7,186✔
1068
    CrabStats::count("SplitDBM.count.apply");
10,779✔
1069
    ScopedCrabStats __st__("SplitDBM.apply");
7,186✔
1070

1071
    // Convert to intervals and perform the operation
1072
    interval_t yi = this->operator[](y);
10,779✔
1073
    interval_t zi = this->operator[](z);
10,779✔
1074
    interval_t xi = interval_t::bottom();
7,186✔
1075
    switch (op) {
7,186✔
1076
    case bitwise_binop_t::AND: xi = yi.bitwise_and(zi); break;
714✔
1077
    case bitwise_binop_t::OR: xi = yi.bitwise_or(zi); break;
8,541✔
1078
    case bitwise_binop_t::XOR: xi = yi.bitwise_xor(zi); break;
711✔
1079
    case bitwise_binop_t::SHL: xi = yi.shl(zi); break;
813✔
1080
    case bitwise_binop_t::LSHR: xi = yi.lshr(zi); break;
×
1081
    case bitwise_binop_t::ASHR: xi = yi.ashr(zi); break;
×
1082
    default: CRAB_ERROR("DBM: unreachable");
×
1083
    }
1084
    set(x, xi);
7,186✔
1085
    normalize();
7,186✔
1086
}
14,372✔
1087

1088
// Apply a bitwise operator to a uvalue.
1089
void SplitDBM::apply(bitwise_binop_t op, variable_t x, variable_t y, const number_t& k, int finite_width) {
73,898✔
1090
    CrabStats::count("SplitDBM.count.apply");
110,847✔
1091
    ScopedCrabStats __st__("SplitDBM.apply");
73,898✔
1092

1093
    // Convert to intervals and perform the operation
1094
    normalize();
73,898✔
1095
    interval_t yi = this->operator[](y);
110,847✔
1096
    interval_t zi(number_t(k.cast_to<uint64_t>()));
73,898✔
1097
    interval_t xi = interval_t::bottom();
73,898✔
1098

1099
    switch (op) {
73,898✔
1100
    case bitwise_binop_t::AND: xi = yi.bitwise_and(zi); break;
103,818✔
1101
    case bitwise_binop_t::OR: xi = yi.bitwise_or(zi); break;
3,975✔
1102
    case bitwise_binop_t::XOR: xi = yi.bitwise_xor(zi); break;
552✔
1103
    case bitwise_binop_t::SHL: xi = yi.shl(zi); break;
2,502✔
1104
    case bitwise_binop_t::LSHR: xi = yi.lshr(zi); break;
×
1105
    case bitwise_binop_t::ASHR: xi = yi.ashr(zi); break;
×
1106
    default: CRAB_ERROR("DBM: unreachable");
×
1107
    }
1108
    set(x, xi);
73,898✔
1109
    normalize();
73,898✔
1110
}
147,796✔
1111

1112
void SplitDBM::forget(const variable_vector_t& variables) {
×
1113
    if (is_top()) {
×
1114
        return;
1115
    }
1116

1117
    for (const auto v : variables) {
×
1118
        if (vert_map.contains(v)) {
×
NEW
1119
            havoc(v);
×
1120
        }
1121
    }
1122
    normalize();
×
1123
}
1124

1125
static std::string to_string(const variable_t vd, const variable_t vs, const SplitDBM::Weight& w, const bool eq) {
268✔
1126
    std::stringstream elem;
268✔
1127
    if (eq) {
268✔
1128
        if (w.operator>(0)) {
8✔
1129
            elem << vd << "=" << vs << "+" << w;
8✔
1130
        } else if (w.operator<(0)) {
×
1131
            elem << vs << "=" << vd << "+" << -w;
×
1132
        } else {
1133
            elem << std::min(vs.name(), vd.name()) << "=" << std::max(vs.name(), vd.name());
×
1134
        }
1135
    } else {
1136
        elem << vd << "-" << vs << "<=" << w;
260✔
1137
    }
1138
    return elem.str();
536✔
1139
}
268✔
1140

1141
string_invariant SplitDBM::to_set() const {
1,188✔
1142
    if (this->is_top()) {
1,188✔
1143
        return string_invariant::top();
40✔
1144
    }
1145
    // Extract all the edges
1146
    SubGraph g_excl{this->g, 0};
1,148✔
1147

1148
    std::map<variable_t, variable_t> equivalence_classes;
1,148✔
1149
    std::set<std::tuple<variable_t, variable_t, Weight>> diff_csts;
1,148✔
1150
    for (const vert_id s : g_excl.verts()) {
7,088✔
1151
        const variable_t vs = *rev_map.at(s);
5,940✔
1152
        variable_t least = vs;
5,940✔
1153
        for (const vert_id d : g_excl.succs(s)) {
10,839✔
1154
            const variable_t vd = *rev_map.at(d);
1,740✔
1155
            const Weight w = g_excl.edge_val(s, d);
1,740✔
1156
            if (w == 0) {
1,740✔
1157
                least = std::min(least, vd, variable_t::printing_order);
2,142✔
1158
            } else {
1159
                diff_csts.emplace(vd, vs, w);
312✔
1160
            }
1161
        }
1,740✔
1162
        equivalence_classes.insert_or_assign(vs, least);
5,940✔
1163
    }
1164

1165
    std::set<variable_t> representatives;
1,148✔
1166
    std::set<std::string> result;
1,148✔
1167
    for (const auto [vs, least] : equivalence_classes) {
7,088✔
1168
        if (vs == least) {
5,940✔
1169
            representatives.insert(least);
5,300✔
1170
        } else {
1171
            result.insert(vs.name() + "=" + least.name());
1,280✔
1172
        }
1173
    }
1174

1175
    // simplify: x - y <= k && y - x <= -k
1176
    //        -> x <= y + k <= x
1177
    //        -> x = y + k
1178
    for (const auto& [vd, vs, w] : diff_csts) {
1,460✔
1179
        if (!representatives.contains(vd) || !representatives.contains(vs)) {
312✔
1180
            continue;
178✔
1181
        }
1182
        auto dual = to_string(vs, vd, -w, false);
134✔
1183
        if (result.contains(dual)) {
134✔
1184
            assert(w != 0);
8✔
1185
            result.erase(dual);
8✔
1186
            result.insert(to_string(vd, vs, w, true));
12✔
1187
        } else {
1188
            result.insert(to_string(vd, vs, w, false));
189✔
1189
        }
1190
    }
134✔
1191

1192
    // Intervals
1193
    for (vert_id v : g_excl.verts()) {
7,088✔
1194
        const auto pvar = this->rev_map[v];
5,940✔
1195
        if (!pvar || !representatives.contains(*pvar)) {
5,940✔
1196
            continue;
866✔
1197
        }
1198
        if (!this->g.elem(0, v) && !this->g.elem(v, 0)) {
5,300✔
1199
            continue;
156✔
1200
        }
1201
        interval_t v_out{this->g.elem(v, 0) ? -number_t(this->g.edge_val(v, 0)) : extended_number::minus_infinity(),
15,428✔
1202
                         this->g.elem(0, v) ? number_t(this->g.edge_val(0, v)) : extended_number::plus_infinity()};
12,828✔
1203
        assert(!v_out.is_bottom());
5,144✔
1204

1205
        variable_t variable = *pvar;
5,144✔
1206

1207
        std::stringstream elem;
5,144✔
1208
        elem << variable;
5,144✔
1209
        if (variable.is_type()) {
5,144✔
1210
            auto [lb, ub] = v_out.bound(T_UNINIT, T_MAX);
1,948✔
1211
            if (lb == ub) {
1,948✔
1212
                if (variable.is_in_stack() && lb == T_NUM) {
1,922✔
1213
                    // no need to show this
1214
                    continue;
70✔
1215
                }
1216
                elem << "=" << lb;
1,852✔
1217
            } else {
1218
                elem << " in " << typeset_to_string(iterate_types(lb, ub));
39✔
1219
            }
1220
        } else {
1221
            elem << "=";
3,196✔
1222
            if (v_out.is_singleton()) {
3,196✔
1223
                elem << v_out.lb();
4,752✔
1224
            } else {
1225
                elem << v_out;
820✔
1226
            }
1227
        }
1228
        result.insert(elem.str());
5,074✔
1229
    }
7,751✔
1230

1231
    return string_invariant{result};
1,148✔
1232
}
1,148✔
1233

UNCOV
1234
std::ostream& operator<<(std::ostream& o, const SplitDBM& dom) { return o << dom.to_set(); }
×
1235

1236
bool SplitDBM::eval_expression_overflow(const linear_expression_t& e, Weight& out) const {
339,740✔
1237
    [[maybe_unused]]
1238
    const bool overflow = convert_NtoW_overflow(e.constant_term(), out);
339,740✔
1239
    assert(!overflow);
339,740✔
1240
    for (const auto& [variable, coefficient] : e.variable_terms()) {
684,592✔
1241
        Weight coef;
344,852✔
1242
        if (convert_NtoW_overflow(coefficient, coef)) {
344,852✔
1243
            out = Weight(0);
1244
            return true;
1245
        }
1246
        out += (pot_value(variable) - potential[0]) * coef;
517,278✔
1247
    }
344,852✔
1248
    return false;
339,740✔
1249
}
1250

1251
interval_t SplitDBM::compute_residual(const linear_expression_t& e, const variable_t pivot) const {
6,106✔
1252
    interval_t residual(-e.constant_term());
6,106✔
1253
    for (const auto& [variable, coefficient] : e.variable_terms()) {
14,068✔
1254
        if (variable != pivot) {
7,962✔
1255
            residual = residual - (interval_t(coefficient) * this->operator[](variable));
5,568✔
1256
        }
1257
    }
1258
    return residual;
6,106✔
1259
}
×
1260

1261
SplitDBM::Weight SplitDBM::pot_value(const variable_t v) const {
344,852✔
1262
    if (const auto y = try_at(vert_map, v)) {
344,852✔
1263
        return potential[*y];
288,078✔
1264
    }
1265
    return {0};
56,774✔
1266
}
1267

1268
interval_t SplitDBM::eval_interval(const linear_expression_t& e) const {
31,422,163✔
1269
    using namespace crab::interval_operators;
15,711,083✔
1270
    interval_t r{e.constant_term()};
31,422,163✔
1271
    for (const auto& [variable, coefficient] : e.variable_terms()) {
61,916,285✔
1272
        r += coefficient * operator[](variable);
60,988,248✔
1273
    }
1274
    return r;
31,422,163✔
1275
}
×
1276

1277
bool SplitDBM::intersect(const linear_constraint_t& cst) const {
1,724,038✔
1278
    if (cst.is_contradiction()) {
1,724,038✔
1279
        return false;
1280
    }
1281
    if (is_top() || cst.is_tautology()) {
1,724,038✔
1282
        return true;
146✔
1283
    }
1284
    return intersect_aux(cst);
1,723,892✔
1285
}
1286

1287
bool SplitDBM::entail(const linear_constraint_t& rhs) const {
205,026✔
1288
    if (rhs.is_tautology()) {
205,026✔
1289
        return true;
78✔
1290
    }
1291
    if (rhs.is_contradiction()) {
204,870✔
1292
        return false;
1293
    }
1294
    const interval_t interval = eval_interval(rhs.expression());
204,870✔
1295
    switch (rhs.kind()) {
204,870✔
1296
    case constraint_kind_t::EQUALS_ZERO:
100,312✔
1297
        if (interval.singleton() == std::optional(number_t(0))) {
100,312✔
1298
            return true;
49,263✔
1299
        }
1300
        break;
893✔
1301
    case constraint_kind_t::LESS_THAN_OR_EQUALS_ZERO:
93,018✔
1302
        if (interval.ub() <= number_t(0)) {
139,527✔
1303
            return true;
40,359✔
1304
        }
1305
        break;
6,150✔
1306
    case constraint_kind_t::LESS_THAN_ZERO:
10,126✔
1307
        if (interval.ub() < number_t(0)) {
15,189✔
1308
            return true;
5,055✔
1309
        }
1310
        break;
8✔
1311
    case constraint_kind_t::NOT_ZERO:
1,414✔
1312
        if (interval.ub() < number_t(0) || interval.lb() > number_t(0)) {
5,617✔
1313
            return true;
633✔
1314
        }
1315
        break;
74✔
1316
    }
1317
    // TODO: copy the implementation from crab
1318
    //       https://github.com/seahorn/crab/blob/master/include/crab/domains/split_dbm.hpp
1319
    if (rhs.kind() == constraint_kind_t::EQUALS_ZERO) {
14,250✔
1320
        // try to convert the equality into inequalities so when it's
1321
        // negated we do not have disequalities.
1322
        return entail_aux(linear_constraint_t(rhs.expression(), constraint_kind_t::LESS_THAN_OR_EQUALS_ZERO)) &&
3,584✔
1323
               entail_aux(linear_constraint_t(rhs.expression().negate(), constraint_kind_t::LESS_THAN_OR_EQUALS_ZERO));
1,798✔
1324
    } else {
1325
        return entail_aux(rhs);
12,464✔
1326
    }
1327

1328
    // Note: we cannot convert rhs into SplitDBM and then use the <=
1329
    //       operator. The problem is that we cannot know for sure
1330
    //       whether SplitDBM can represent precisely rhs. It is not
1331
    //       enough to do something like
1332
    //
1333
    //       SplitDBM dom = rhs;
1334
    //       if (dom.is_top()) { ... }
1335
}
204,870✔
1336

1337
void SplitDBM::diffcsts_of_assign(const variable_t x, const linear_expression_t& exp,
340,332✔
1338
                                  std::vector<std::pair<variable_t, Weight>>& lb,
1339
                                  std::vector<std::pair<variable_t, Weight>>& ub) const {
1340
    diffcsts_of_assign(x, exp, true, ub);
340,332✔
1341
    diffcsts_of_assign(x, exp, false, lb);
340,332✔
1342
}
340,332✔
1343

1344
static interval_t get_interval(const SplitDBM::vert_map_t& m, const SplitDBM::graph_t& r, const variable_t x,
34,169,651✔
1345
                               const int finite_width) {
1346
    const auto it = m.find(x);
17,084,828✔
1347
    if (it == m.end()) {
34,169,651✔
1348
        return interval_t::top();
20,032,083✔
1349
    }
1350
    const SplitDBM::vert_id v = it->second;
14,137,568✔
1351
    extended_number lb = extended_number::minus_infinity();
14,137,568✔
1352
    extended_number ub = extended_number::plus_infinity();
14,137,568✔
1353
    if (r.elem(v, 0)) {
14,137,568✔
1354
        lb = x.is_unsigned() ? (-number_t(r.edge_val(v, 0))).zero_extend(finite_width)
48,110,674✔
1355
                             : (-number_t(r.edge_val(v, 0))).sign_extend(finite_width);
60,948,536✔
1356
    }
1357
    if (r.elem(0, v)) {
14,137,568✔
1358
        ub = x.is_unsigned() ? number_t(r.edge_val(0, v)).zero_extend(finite_width)
34,570,743✔
1359
                             : number_t(r.edge_val(0, v)).sign_extend(finite_width);
41,148,678✔
1360
    }
1361
    return {lb, ub};
14,137,568✔
1362
}
14,137,568✔
1363

1364
interval_t SplitDBM::get_interval(const variable_t x, const int finite_width) const {
7,560✔
1365
    return domains::get_interval(vert_map, g, x, finite_width);
7,416✔
1366
}
1367

1368
interval_t SplitDBM::operator[](const variable_t x) const { return domains::get_interval(vert_map, g, x, 0); }
34,158,498✔
1369

1370
} // 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