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

vbpf / ebpf-verifier / 13955273819

19 Mar 2025 07:38PM UTC coverage: 88.66% (+0.5%) from 88.134%
13955273819

push

github

web-flow
Fix sign extension (#850)

* fix sign extension
* add exhaustive tests for sign extension of <=3 bits
* fix interval::size()
* streamline bit/width operations

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

203 of 214 new or added lines in 9 files covered. (94.86%)

9 existing lines in 5 files now uncovered.

8639 of 9744 relevant lines covered (88.66%)

8977818.22 hits per line

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

95.06
/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,115,868✔
17
    const auto it = map.find(v);
4,557,932✔
18
    if (it == map.end()) {
9,115,868✔
19
        return std::nullopt;
5,414,003✔
20
    }
21
    return it->second;
3,701,865✔
22
}
23

24
SplitDBM::vert_id SplitDBM::get_vert(variable_t v) {
2,473,797✔
25
    if (const auto y = try_at(vert_map, v)) {
2,473,797✔
26
        return *y;
936,735✔
27
    }
28

29
    vert_id vert(g.new_vertex());
1,537,062✔
30
    vert_map.emplace(v, vert);
1,537,062✔
31
    // Initialize
32
    assert(vert <= rev_map.size());
1,537,062✔
33
    if (vert < rev_map.size()) {
1,537,062✔
34
        potential[vert] = Weight(0);
1,193,156✔
35
        rev_map[vert] = v;
1,193,156✔
36
    } else {
37
        potential.emplace_back(0);
343,906✔
38
        rev_map.push_back(v);
343,906✔
39
    }
40
    vert_map.emplace(v, vert);
1,537,062✔
41

42
    assert(vert != 0);
1,537,062✔
43

44
    return vert;
1,537,062✔
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,940,378✔
63
    out = n;
9,982,777✔
64
    return false;
11,940,378✔
65
}
66

67
void SplitDBM::diffcsts_of_assign(variable_t x, const linear_expression_t& exp,
678,600✔
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;
678,600✔
76
    std::vector<std::pair<variable_t, Weight>> terms;
678,600✔
77

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

83
    for (const auto& [y, n] : exp.variable_terms()) {
1,367,224✔
84
        Weight coeff;
689,636✔
85
        if (convert_NtoW_overflow(n, coeff)) {
689,636✔
86
            continue;
87
        }
88

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

93
            if (y_val.is_infinite()) {
1,768✔
94
                return;
256✔
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,768✔
103
            auto y_val = (extract_upper_bounds ? this->operator[](y).ub() : this->operator[](y).lb());
1,719,670✔
104

105
            if (y_val.is_infinite()) {
687,868✔
106
                if (unbounded_var || coeff != Weight(1)) {
324,794✔
107
                    return;
756✔
108
                }
109
                unbounded_var = y;
559,572✔
110
            } else {
111
                Weight ymax;
471,096✔
112
                if (convert_NtoW_overflow(*y_val.number(), ymax)) {
471,096✔
113
                    continue;
114
                }
115
                residual += ymax * coeff;
706,644✔
116
                terms.emplace_back(y, ymax);
471,096✔
117
            }
471,096✔
118
        }
687,868✔
119
    }
689,636✔
120

121
    if (unbounded_var) {
677,588✔
122
        // There is exactly one unbounded variable with unit
123
        // coefficient
124
        diff_csts.emplace_back(*unbounded_var, residual);
215,228✔
125
    } else {
126
        for (const auto& [v, n] : terms) {
932,654✔
127
            diff_csts.emplace_back(v, residual - n);
705,441✔
128
        }
129
    }
130
}
679,106✔
131

132
void SplitDBM::diffcsts_of_lin_leq(const linear_expression_t& exp,
1,990,158✔
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,990,158✔
140
    if (convert_NtoW_overflow(exp.constant_term(), exp_ub)) {
1,990,158✔
141
        return;
142
    }
143
    exp_ub = -exp_ub;
2,985,237✔
144

145
    // temporary hack
146
    Weight _tmp;
1,990,158✔
147
    if (convert_NtoW_overflow(exp.constant_term() - 1, _tmp)) {
2,985,237✔
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,990,158✔
154
    Weight unbounded_ubcoeff;
1,990,158✔
155
    std::optional<variable_t> unbounded_lbvar;
1,990,158✔
156
    std::optional<variable_t> unbounded_ubvar;
1,990,158✔
157

158
    std::vector<std::pair<std::pair<Weight, variable_t>, Weight>> pos_terms, neg_terms;
1,990,158✔
159
    for (const auto& [y, n] : exp.variable_terms()) {
4,046,284✔
160
        Weight coeff;
2,881,735✔
161
        if (convert_NtoW_overflow(n, coeff)) {
2,881,735✔
162
            continue;
163
        }
164
        if (coeff > Weight(0)) {
2,881,735✔
165
            auto y_lb = this->operator[](y).lb();
1,485,441✔
166
            if (y_lb.is_infinite()) {
990,294✔
167
                if (unbounded_lbvar) {
868,521✔
168
                    return;
×
169
                }
170
                unbounded_lbvar = y;
868,521✔
171
                unbounded_lbcoeff = coeff;
1,363,668✔
172
            } else {
173
                Weight ymin;
121,773✔
174
                if (convert_NtoW_overflow(*y_lb.number(), ymin)) {
121,773✔
175
                    continue;
176
                }
177
                exp_ub -= ymin * coeff;
182,659✔
178
                pos_terms.push_back({{coeff, y}, ymin});
243,545✔
179
            }
121,773✔
180
        } else {
990,294✔
181
            auto y_ub = this->operator[](y).ub();
2,837,162✔
182
            if (y_ub.is_infinite()) {
1,891,441✔
183
                if (unbounded_ubvar) {
1,733,639✔
184
                    return;
825,609✔
185
                }
186
                unbounded_ubvar = y;
908,030✔
187
                unbounded_ubcoeff = -coeff;
1,362,045✔
188
            } else {
189
                Weight ymax;
157,802✔
190
                if (convert_NtoW_overflow(*y_ub.number(), ymax)) {
157,802✔
191
                    continue;
192
                }
193
                exp_ub -= ymax * coeff;
236,703✔
194
                neg_terms.push_back({{-coeff, y}, ymax});
394,505✔
195
            }
157,802✔
196
        }
1,891,441✔
197
    }
2,468,930✔
198

199
    if (unbounded_lbvar) {
1,164,549✔
200
        variable_t x{*unbounded_lbvar};
868,521✔
201
        if (unbounded_ubvar) {
868,521✔
202
            if (unbounded_lbcoeff == Weight(1) && unbounded_ubcoeff == Weight(1)) {
13,470✔
203
                csts.push_back({{x, *unbounded_ubvar}, exp_ub});
13,470✔
204
            }
205
        } else {
206
            if (unbounded_lbcoeff == Weight(1)) {
859,541✔
207
                for (const auto& [nv, k] : neg_terms) {
861,551✔
208
                    csts.push_back({{x, nv.second}, exp_ub - k});
3,015✔
209
                }
210
            }
211
            // Add bounds for x
212
            ubs.emplace_back(x, exp_ub / unbounded_lbcoeff);
1,289,312✔
213
        }
214
    } else {
215
        if (unbounded_ubvar) {
296,028✔
216
            variable_t y{*unbounded_ubvar};
73,441✔
217
            if (unbounded_ubcoeff == Weight(1)) {
73,441✔
218
                for (const auto& [nv, k] : pos_terms) {
75,581✔
219
                    csts.push_back({{nv.second, y}, exp_ub + k});
3,210✔
220
                }
221
            }
222
            // Add bounds for y
223
            lbs.emplace_back(y, -exp_ub / unbounded_ubcoeff);
110,161✔
224
        } else {
225
            for (const auto& [neg_nv, neg_k] : neg_terms) {
346,495✔
226
                for (const auto& [pos_nv, pos_k] : pos_terms) {
136,606✔
227
                    csts.push_back({{pos_nv.second, neg_nv.second}, exp_ub - neg_k + pos_k});
19,047✔
228
                }
229
            }
230
            for (const auto& [neg_nv, neg_k] : neg_terms) {
346,495✔
231
                lbs.emplace_back(neg_nv.second, -exp_ub / neg_nv.first + neg_k);
185,862✔
232
            }
233
            for (const auto& [pos_nv, pos_k] : pos_terms) {
341,016✔
234
                ubs.emplace_back(pos_nv.second, exp_ub / pos_nv.first + pos_k);
177,643✔
235
            }
236
        }
237
    }
238
}
4,054,178✔
239

240
static GraphOps::PotentialFunction index_to_call(const GraphOps::WeightVector& p) {
2,334,520✔
241
    return [&p](GraphOps::vert_id v) -> GraphOps::Weight { return p[v]; };
3,205,858,300✔
242
}
243

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

249
    for (const auto& [var, n] : lbs) {
2,173,643✔
250
        CRAB_LOG("zones-split", std::cout << var << ">=" << n << "\n");
191,651✔
251
        const vert_id vert = get_vert(var);
191,651✔
252
        if (auto w = g.lookup(vert, 0)) {
191,651✔
253
            if (*w <= -n) {
97,790✔
254
                continue;
69,602✔
255
            }
256
        }
130,627✔
257
        g.set_edge(vert, -n, 0);
122,049✔
258

259
        if (!repair_potential(vert, 0)) {
122,049✔
260
            return false;
24,226✔
261
        }
262
    }
263
    for (const auto& [var, n] : ubs) {
2,927,540✔
264
        CRAB_LOG("zones-split", std::cout << var << "<=" << n << "\n");
977,578✔
265
        const vert_id vert = get_vert(var);
977,578✔
266
        if (auto w = g.lookup(0, vert)) {
977,578✔
267
            if (*w <= n) {
88,440✔
268
                continue;
37,050✔
269
            }
270
        }
507,314✔
271
        g.set_edge(0, n, vert);
940,528✔
272
        if (!repair_potential(0, vert)) {
940,528✔
273
            return false;
36,158✔
274
        }
275
    }
276

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

280
        const vert_id src = get_vert(diff.second);
25,394✔
281
        const vert_id dest = get_vert(diff.first);
25,394✔
282
        g.update_edge(src, k, dest);
25,394✔
283
        if (!repair_potential(src, dest)) {
25,394✔
284
            return false;
20,188✔
285
        }
286
        GraphOps::close_over_edge(g, src, dest);
25,304✔
287
    }
288
    GraphOps::apply_delta(g, GraphOps::close_after_assign(g, index_to_call(potential), 0));
1,949,872✔
289
    normalize();
1,949,872✔
290
    return true;
974,936✔
291
}
1,990,158✔
292

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

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

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

324
        if (auto w = g.lookup(v, 0)) {
1,322✔
325
            if (lb_val < *w) {
1,294✔
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,322✔
342
    }
1,322✔
343
    if (new_i.ub().is_finite()) {
1,983✔
344
        // strengthen ub
345
        Weight ub_val;
1,322✔
346
        if (convert_NtoW_overflow(*new_i.ub().number(), ub_val)) {
2,644✔
347
            return true;
348
        }
349

350
        if (auto w = g.lookup(0, v)) {
1,322✔
351
            if (ub_val < *w) {
1,294✔
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,322✔
368
    }
1,322✔
369
    normalize();
1,322✔
370
    return true;
661✔
371
}
7,017✔
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& {
22,970✔
439
    CrabStats::count("SplitDBM.count.join");
34,455✔
440
    ScopedCrabStats __st__("SplitDBM.join");
22,970✔
441

442
    if (o.is_top()) {
22,970✔
443
        return o;
26✔
444
    }
445
    if (is_top()) {
22,944✔
446
        return *this;
20✔
447
    }
448
    CRAB_LOG("zones-split", std::cout << "Before join:\n"
22,924✔
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;
22,924✔
456
    std::vector<vert_id> perm_y;
22,924✔
457
    std::vector<variable_t> perm_inv;
22,924✔
458

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

471
    for (const auto& [v, n] : vert_map) {
2,502,011✔
472
        if (auto y = try_at(o.vert_map, v)) {
1,637,442✔
473
            // Variable exists in both
474
            out_vmap.emplace(v, gsl::narrow<vert_id>(perm_x.size()));
1,587,550✔
475
            out_revmap.push_back(v);
1,587,550✔
476

477
            pot_rx.push_back(potential[n] - potential[0]);
2,381,325✔
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,381,325✔
481
            perm_inv.push_back(v);
1,587,550✔
482
            perm_x.push_back(n);
1,587,550✔
483
            perm_y.push_back(*y);
1,587,550✔
484
        }
485
    }
486
    size_t sz = perm_x.size();
22,924✔
487

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

494
    // Compute the deferred relations
495
    graph_t g_ix_ry;
22,924✔
496
    g_ix_ry.growTo(sz);
22,924✔
497
    SubGraph gy_excl(gy, 0);
22,924✔
498
    for (vert_id s : gy_excl.verts()) {
2,404,249✔
499
        for (vert_id d : gy_excl.succs(s)) {
3,356,269✔
500
            if (auto ws = gx.lookup(s, 0)) {
974,944✔
501
                if (auto wd = gx.lookup(0, d)) {
887,670✔
502
                    g_ix_ry.add_edge(s, *ws + *wd, d);
1,328,028✔
503
                }
887,670✔
504
            }
974,944✔
505
        }
506
    }
507
    // Apply the deferred relations, and re-close.
508
    bool is_closed;
11,462✔
509
    graph_t g_rx(GraphOps::meet(gx, g_ix_ry, is_closed));
22,924✔
510
    if (!is_closed) {
22,924✔
511
        GraphOps::apply_delta(g_rx, GraphOps::close_after_meet(SubGraph(g_rx, 0), index_to_call(pot_rx), gx, g_ix_ry));
22,924✔
512
    }
513

514
    graph_t g_rx_iy;
22,924✔
515
    g_rx_iy.growTo(sz);
22,924✔
516

517
    SubGraph gx_excl(gx, 0);
22,924✔
518
    for (vert_id s : gx_excl.verts()) {
2,404,249✔
519
        for (vert_id d : gx_excl.succs(s)) {
3,581,947✔
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,200,622✔
523
                if (auto wd = gy.lookup(0, d)) {
1,108,876✔
524
                    g_rx_iy.add_edge(s, *ws + *wd, d);
1,657,743✔
525
                }
1,108,876✔
526
            }
1,200,622✔
527
        }
528
    }
529
    // Similarly, should use a SubGraph view.
530
    graph_t g_ry(GraphOps::meet(gy, g_rx_iy, is_closed));
22,924✔
531
    if (!is_closed) {
22,924✔
532
        GraphOps::apply_delta(g_ry, GraphOps::close_after_meet(SubGraph(g_ry, 0), index_to_call(pot_ry), gy, g_rx_iy));
22,924✔
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));
22,924✔
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;
22,924✔
542
    std::vector<vert_id> lb_down;
22,924✔
543
    std::vector<vert_id> ub_up;
22,924✔
544
    std::vector<vert_id> ub_down;
22,924✔
545

546
    for (vert_id v : gx_excl.verts()) {
2,404,249✔
547
        if (auto wx = gx.lookup(0, v)) {
1,587,550✔
548
            if (auto wy = gy.lookup(0, v)) {
1,473,152✔
549
                if (*wx < *wy) {
1,465,616✔
550
                    ub_up.push_back(v);
21,688✔
551
                }
552
                if (*wy < *wx) {
1,465,616✔
553
                    ub_down.push_back(v);
44,482✔
554
                }
555
            }
1,473,152✔
556
        }
793,775✔
557
        if (auto wx = gx.lookup(v, 0)) {
1,587,550✔
558
            if (auto wy = gy.lookup(v, 0)) {
1,475,632✔
559
                if (*wx < *wy) {
1,468,282✔
560
                    lb_down.push_back(v);
17,690✔
561
                }
562
                if (*wy < *wx) {
1,468,282✔
563
                    lb_up.push_back(v);
41,950✔
564
                }
565
            }
1,475,632✔
566
        }
1,587,550✔
567
    }
568

569
    for (vert_id s : lb_up) {
64,874✔
570
        Weight dx_s = gx.edge_val(s, 0);
41,950✔
571
        Weight dy_s = gy.edge_val(s, 0);
41,950✔
572
        for (vert_id d : ub_up) {
100,952✔
573
            if (s == d) {
59,002✔
574
                continue;
12,972✔
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);
69,045✔
578
        }
579
    }
41,950✔
580

581
    for (vert_id s : lb_down) {
40,614✔
582
        Weight dx_s = gx.edge_val(s, 0);
17,690✔
583
        Weight dy_s = gy.edge_val(s, 0);
17,690✔
584
        for (vert_id d : ub_down) {
71,498✔
585
            if (s == d) {
53,808✔
586
                continue;
12,900✔
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);
61,362✔
590
        }
591
    }
17,690✔
592

593
    // Conjecture: join_g remains closed.
594

595
    // Now garbage collect any unused vertices
596
    for (vert_id v : join_g.verts()) {
1,633,398✔
597
        if (v == 0) {
1,610,474✔
598
            continue;
22,924✔
599
        }
600
        if (join_g.succs(v).size() == 0 && join_g.preds(v).size() == 0) {
1,587,550✔
601
            join_g.forget(v);
31,924✔
602
            if (out_revmap[v]) {
31,924✔
603
                out_vmap.erase(*(out_revmap[v]));
31,924✔
604
                out_revmap[v] = std::nullopt;
837,161✔
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());
22,924✔
611
    // join_g.check_adjs();
612
    CRAB_LOG("zones-split", std::cout << "Result join:\n" << res << "\n");
22,924✔
613

614
    return res;
22,924✔
615
}
45,894✔
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::operator-=(const variable_t v) {
4,657,629✔
750
    if (const auto y = try_at(vert_map, v)) {
4,657,629✔
751
        g.forget(*y);
889,484✔
752
        rev_map[*y] = std::nullopt;
889,484✔
753
        vert_map.erase(v);
889,484✔
754
        normalize();
889,484✔
755
    }
756
}
4,657,629✔
757

758
bool SplitDBM::add_constraint(const linear_constraint_t& cst) {
1,968,480✔
759
    CrabStats::count("SplitDBM.count.add_constraints");
2,952,720✔
760
    ScopedCrabStats __st__("SplitDBM.add_constraints");
1,968,480✔
761

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

766
    // g.check_adjs();
767

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

772
    switch (cst.kind()) {
1,967,668✔
773
    case constraint_kind_t::LESS_THAN_OR_EQUALS_ZERO: {
1,884,386✔
774
        if (!add_linear_leq(cst.expression())) {
1,884,386✔
775
            return false;
19,912✔
776
        }
777
        //  g.check_adjs();
778
        CRAB_LOG("zones-split", std::cout << "--- " << cst << "\n" << *this << "\n");
1,844,562✔
779
        break;
922,281✔
780
    }
781
    case constraint_kind_t::LESS_THAN_ZERO: {
50,442✔
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);
75,663✔
785
        if (!add_linear_leq(nc.expression())) {
50,442✔
786
            return false;
372✔
787
        }
788
        CRAB_LOG("zones-split", std::cout << "--- " << cst << "\n" << *this << "\n");
50,070✔
789
        break;
50,070✔
790
    }
50,442✔
791
    case constraint_kind_t::EQUALS_ZERO: {
27,686✔
792
        const linear_expression_t& exp = cst.expression();
27,686✔
793
        if (!add_linear_leq(exp) || !add_linear_leq(exp.negate())) {
55,306✔
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,596✔
799
        break;
13,798✔
800
    }
801
    case constraint_kind_t::NOT_ZERO: {
5,154✔
802
        // XXX: similar precision as the interval domain
803
        const linear_expression_t& e = cst.expression();
5,154✔
804
        for (const auto& [variable, coefficient] : e.variable_terms()) {
7,962✔
805
            interval_t i = compute_residual(e, variable) / interval_t(coefficient);
9,063✔
806
            if (auto k = i.singleton()) {
6,042✔
807
                if (!add_univar_disequation(variable, *k)) {
4,678✔
808
                    return false;
3,234✔
809
                }
810
            }
6,042✔
811
        }
6,042✔
812
    } break;
960✔
813
    }
814

815
    CRAB_WARN("Unhandled constraint ", cst, " by split_dbm");
1,924,148✔
816
    CRAB_LOG("zones-split", std::cout << "---" << cst << "\n" << *this << "\n");
1,924,148✔
817
    normalize();
1,924,148✔
818
    return true;
962,074✔
819
}
1,968,480✔
820

821
void SplitDBM::assign(variable_t lhs, const linear_expression_t& e) {
677,624✔
822
    CrabStats::count("SplitDBM.count.assign");
1,016,437✔
823
    ScopedCrabStats __st__("SplitDBM.assign");
677,624✔
824

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

828
    interval_t value_interval = eval_interval(e);
677,624✔
829

830
    std::optional<Weight> lb_w, ub_w;
677,624✔
831
    if (value_interval.lb().is_finite()) {
1,016,435✔
832
        Weight tmp;
569,516✔
833
        if (convert_NtoW_overflow(-*value_interval.lb().number(), tmp)) {
1,423,787✔
834
            operator-=(lhs);
835
            CRAB_LOG("zones-split", std::cout << "---" << lhs << ":=" << e << "\n" << *this << "\n");
836
            normalize();
837
            return;
838
        }
839
        lb_w = tmp;
569,516✔
840
    }
569,516✔
841
    if (value_interval.ub().is_finite()) {
1,016,435✔
842
        Weight tmp;
569,492✔
843
        if (convert_NtoW_overflow(*value_interval.ub().number(), tmp)) {
1,138,982✔
844
            operator-=(lhs);
845
            CRAB_LOG("zones-split", std::cout << "---" << lhs << ":=" << e << "\n" << *this << "\n");
846
            normalize();
847
            return;
848
        }
849
        ub_w = tmp;
569,492✔
850
    }
569,492✔
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()) {
677,624✔
856
        set(lhs, value_interval);
338,324✔
857
        normalize();
338,324✔
858
        return;
169,163✔
859
    }
860

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

870
    Weight e_val;
338,716✔
871
    if (eval_expression_overflow(e, e_val)) {
338,716✔
872
        operator-=(lhs);
×
873
        return;
×
874
    }
875
    // Allocate a new vertex for x
876
    vert_id vert = g.new_vertex();
338,716✔
877
    assert(vert <= rev_map.size());
338,716✔
878
    if (vert == rev_map.size()) {
338,716✔
879
        rev_map.push_back(lhs);
52,518✔
880
        potential.push_back(potential[0] + e_val);
78,777✔
881
    } else {
882
        potential[vert] = potential[0] + e_val;
286,198✔
883
        rev_map[vert] = lhs;
286,198✔
884
    }
885

886
    {
169,358✔
887
        GraphOps::edge_vector delta;
338,716✔
888
        for (const auto& [var, n] : diffs_lb) {
681,478✔
889
            delta.emplace_back(vert, get_vert(var), -n);
342,762✔
890
        }
891

892
        for (const auto& [var, n] : diffs_ub) {
681,476✔
893
            delta.emplace_back(get_vert(var), vert, n);
342,760✔
894
        }
895

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

901
    if (lb_w) {
338,716✔
902
        g.update_edge(vert, *lb_w, 0);
346,671✔
903
    }
904
    if (ub_w) {
338,716✔
905
        g.update_edge(0, *ub_w, vert);
346,635✔
906
    }
907
    // Clear the old x vertex
908
    operator-=(lhs);
338,716✔
909
    vert_map.emplace(lhs, vert);
338,716✔
910

911
    normalize();
338,716✔
912
    CRAB_LOG("zones-split", std::cout << "---" << lhs << ":=" << e << "\n" << *this << "\n");
338,716✔
913
}
1,525,676✔
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,088,065✔
936
    return GraphOps::repair_potential(g, potential, src, dest);
1,088,065✔
937
}
938

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

941
void SplitDBM::normalize() {
6,238,984✔
942
    CrabStats::count("SplitDBM.count.normalize");
9,358,479✔
943
    ScopedCrabStats __st__("SplitDBM.normalize");
6,238,984✔
944

945
    // dbm_canonical(_dbm);
946
    // Always maintained in normal form, except for widening
947
    if (unstable.empty()) {
6,238,984✔
948
        return;
6,238,938✔
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,238,984✔
961

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

967
    this->operator-=(x);
1,079,308✔
968

969
    if (intv.is_top()) {
1,079,308✔
970
        return;
512,372✔
971
    }
972

973
    const vert_id v = get_vert(x);
566,936✔
974
    if (intv.ub().is_finite()) {
850,403✔
975
        Weight ub;
566,930✔
976
        if (convert_NtoW_overflow(*intv.ub().number(), ub)) {
1,133,858✔
977
            normalize();
978
            return;
979
        }
980
        potential[v] = potential[0] + ub;
566,930✔
981
        g.set_edge(0, ub, v);
850,394✔
982
    }
566,930✔
983
    if (intv.lb().is_finite()) {
850,403✔
984
        Weight lb;
566,780✔
985
        if (convert_NtoW_overflow(*intv.lb().number(), lb)) {
1,133,558✔
986
            normalize();
987
            return;
988
        }
989
        potential[v] = potential[0] + lb;
566,780✔
990
        g.set_edge(v, -lb, 0);
850,169✔
991
    }
566,780✔
992
    normalize();
566,936✔
993
}
1,079,308✔
994

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

1000
    switch (op) {
6,074✔
1001
    case arith_binop_t::ADD: assign(x, linear_expression_t(y).plus(z)); break;
4,976✔
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;
185✔
1005
    case arith_binop_t::SDIV: set(x, get_interval(y, finite_width).sdiv(get_interval(z, finite_width))); break;
90✔
1006
    case arith_binop_t::UDIV: set(x, get_interval(y, finite_width).udiv(get_interval(z, finite_width))); break;
270✔
1007
    case arith_binop_t::SREM: set(x, get_interval(y, finite_width).srem(get_interval(z, finite_width))); break;
120✔
1008
    case arith_binop_t::UREM: set(x, get_interval(y, finite_width).urem(get_interval(z, finite_width))); break;
90✔
UNCOV
1009
    default: CRAB_ERROR("DBM: unreachable");
×
1010
    }
1011
    normalize();
6,074✔
1012
}
6,074✔
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,960✔
1039
                     const int finite_width) {
1040
    CrabStats::count("SplitDBM.count.apply");
73,440✔
1041
    ScopedCrabStats __st__("SplitDBM.apply");
48,960✔
1042

1043
    switch (op) {
48,960✔
1044
    case arith_binop_t::ADD: assign(x, linear_expression_t(y).plus(k)); break;
45,424✔
1045
    case arith_binop_t::SUB: assign(x, linear_expression_t(y).subtract(k)); break;
1,038✔
1046
    case arith_binop_t::MUL:
240✔
1047
        assign(x, linear_expression_t(k, y));
240✔
1048
        break;
240✔
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,960✔
1065
}
48,960✔
1066

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

1071
    // Convert to intervals and perform the operation
1072
    interval_t yi = this->operator[](y);
10,713✔
1073
    interval_t zi = this->operator[](z);
10,713✔
1074
    interval_t xi = interval_t::bottom();
7,142✔
1075
    switch (op) {
7,142✔
1076
    case bitwise_binop_t::AND: xi = yi.bitwise_and(zi); break;
720✔
1077
    case bitwise_binop_t::OR: xi = yi.bitwise_or(zi); break;
8,460✔
1078
    case bitwise_binop_t::XOR: xi = yi.bitwise_xor(zi); break;
714✔
1079
    case bitwise_binop_t::SHL: xi = yi.shl(zi); break;
819✔
NEW
1080
    case bitwise_binop_t::LSHR: xi = yi.lshr(zi); break;
×
NEW
1081
    case bitwise_binop_t::ASHR: xi = yi.ashr(zi); break;
×
UNCOV
1082
    default: CRAB_ERROR("DBM: unreachable");
×
1083
    }
1084
    set(x, xi);
7,142✔
1085
    normalize();
7,142✔
1086
}
14,284✔
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) {
72,188✔
1090
    CrabStats::count("SplitDBM.count.apply");
108,282✔
1091
    ScopedCrabStats __st__("SplitDBM.apply");
72,188✔
1092

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

1099
    switch (op) {
72,188✔
1100
    case bitwise_binop_t::AND: xi = yi.bitwise_and(zi); break;
101,358✔
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;
528✔
1103
    case bitwise_binop_t::SHL: xi = yi.shl(zi); break;
2,421✔
NEW
1104
    case bitwise_binop_t::LSHR: xi = yi.lshr(zi); break;
×
NEW
1105
    case bitwise_binop_t::ASHR: xi = yi.ashr(zi); break;
×
UNCOV
1106
    default: CRAB_ERROR("DBM: unreachable");
×
1107
    }
1108
    set(x, xi);
72,188✔
1109
    normalize();
72,188✔
1110
}
144,376✔
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)) {
×
1119
            operator-=(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,204✔
1142
    if (this->is_top()) {
1,204✔
1143
        return string_invariant::top();
8✔
1144
    }
1145
    // Extract all the edges
1146
    SubGraph g_excl{this->g, 0};
1,196✔
1147

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

1165
    std::set<variable_t> representatives;
1,196✔
1166
    std::set<std::string> result;
1,196✔
1167
    for (const auto [vs, least] : equivalence_classes) {
7,294✔
1168
        if (vs == least) {
6,098✔
1169
            representatives.insert(least);
5,416✔
1170
        } else {
1171
            result.insert(vs.name() + "=" + least.name());
1,364✔
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,508✔
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,294✔
1194
        const auto pvar = this->rev_map[v];
6,098✔
1195
        if (!pvar || !representatives.contains(*pvar)) {
6,098✔
1196
            continue;
946✔
1197
        }
1198
        if (!this->g.elem(0, v) && !this->g.elem(v, 0)) {
5,416✔
1199
            continue;
194✔
1200
        }
1201
        interval_t v_out{this->g.elem(v, 0) ? -number_t(this->g.edge_val(v, 0)) : extended_number::minus_infinity(),
15,662✔
1202
                         this->g.elem(0, v) ? number_t(this->g.edge_val(0, v)) : extended_number::plus_infinity()};
13,023✔
1203
        assert(!v_out.is_bottom());
5,222✔
1204

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

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

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

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

1236
bool SplitDBM::eval_expression_overflow(const linear_expression_t& e, Weight& out) const {
338,716✔
1237
    [[maybe_unused]]
1238
    const bool overflow = convert_NtoW_overflow(e.constant_term(), out);
338,716✔
1239
    assert(!overflow);
338,716✔
1240
    for (const auto& [variable, coefficient] : e.variable_terms()) {
682,546✔
1241
        Weight coef;
343,830✔
1242
        if (convert_NtoW_overflow(coefficient, coef)) {
343,830✔
1243
            out = Weight(0);
1244
            return true;
1245
        }
1246
        out += (pot_value(variable) - potential[0]) * coef;
515,745✔
1247
    }
343,830✔
1248
    return false;
338,716✔
1249
}
1250

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

1261
SplitDBM::Weight SplitDBM::pot_value(const variable_t v) const {
343,830✔
1262
    if (const auto y = try_at(vert_map, v)) {
343,830✔
1263
        return potential[*y];
287,158✔
1264
    }
1265
    return {0};
56,672✔
1266
}
1267

1268
interval_t SplitDBM::eval_interval(const linear_expression_t& e) const {
28,997,480✔
1269
    using namespace crab::interval_operators;
14,498,741✔
1270
    interval_t r{e.constant_term()};
28,997,480✔
1271
    for (const auto& [variable, coefficient] : e.variable_terms()) {
56,998,568✔
1272
        r += coefficient * operator[](variable);
56,002,180✔
1273
    }
1274
    return r;
28,997,480✔
1275
}
×
1276

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

1287
bool SplitDBM::entail(const linear_constraint_t& rhs) const {
200,766✔
1288
    if (rhs.is_tautology()) {
200,766✔
1289
        return true;
78✔
1290
    }
1291
    if (rhs.is_contradiction()) {
200,610✔
1292
        return false;
1293
    }
1294
    const interval_t interval = eval_interval(rhs.expression());
200,610✔
1295
    switch (rhs.kind()) {
200,610✔
1296
    case constraint_kind_t::EQUALS_ZERO:
98,500✔
1297
        if (interval.singleton() == std::optional(number_t(0))) {
98,500✔
1298
            return true;
48,697✔
1299
        }
1300
        break;
553✔
1301
    case constraint_kind_t::LESS_THAN_OR_EQUALS_ZERO:
90,578✔
1302
        if (interval.ub() <= number_t(0)) {
135,867✔
1303
            return true;
39,391✔
1304
        }
1305
        break;
5,898✔
1306
    case constraint_kind_t::LESS_THAN_ZERO:
10,118✔
1307
        if (interval.ub() < number_t(0)) {
15,177✔
1308
            return true;
5,051✔
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) {
13,066✔
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)) &&
2,224✔
1323
               entail_aux(linear_constraint_t(rhs.expression().negate(), constraint_kind_t::LESS_THAN_OR_EQUALS_ZERO));
1,118✔
1324
    } else {
1325
        return entail_aux(rhs);
11,960✔
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
}
200,610✔
1336

1337
void SplitDBM::diffcsts_of_assign(const variable_t x, const linear_expression_t& exp,
339,300✔
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);
339,300✔
1341
    diffcsts_of_assign(x, exp, false, lb);
339,300✔
1342
}
339,300✔
1343

1344
static interval_t get_interval(const SplitDBM::vert_map_t& m, const SplitDBM::graph_t& r, const variable_t x,
32,033,155✔
1345
                               const int finite_width) {
1346
    const auto it = m.find(x);
16,016,580✔
1347
    if (it == m.end()) {
32,033,155✔
1348
        return interval_t::top();
18,220,439✔
1349
    }
1350
    const SplitDBM::vert_id v = it->second;
13,812,716✔
1351
    extended_number lb = extended_number::minus_infinity();
13,812,716✔
1352
    extended_number ub = extended_number::plus_infinity();
13,812,716✔
1353
    if (r.elem(v, 0)) {
13,812,716✔
1354
        lb = x.is_unsigned() ? (-number_t(r.edge_val(v, 0))).zero_extend(finite_width)
46,975,132✔
1355
                             : (-number_t(r.edge_val(v, 0))).sign_extend(finite_width);
59,499,568✔
1356
    }
1357
    if (r.elem(0, v)) {
13,812,716✔
1358
        ub = x.is_unsigned() ? number_t(r.edge_val(0, v)).zero_extend(finite_width)
33,757,578✔
1359
                             : number_t(r.edge_val(0, v)).sign_extend(finite_width);
40,176,906✔
1360
    }
1361
    return {lb, ub};
13,812,716✔
1362
}
13,812,716✔
1363

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

1368
interval_t SplitDBM::operator[](const variable_t x) const { return domains::get_interval(vert_map, g, x, 0); }
32,022,044✔
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