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

Alan-Jowett / ebpf-verifier / 18658728485

18 Oct 2025 05:56PM UTC coverage: 88.47% (+0.4%) from 88.11%
18658728485

push

github

elazarg
Bump external/bpf_conformance from `8f3c2fe` to `6fa6a20`

Bumps [external/bpf_conformance](https://github.com/Alan-Jowett/bpf_conformance) from `8f3c2fe` to `6fa6a20`.
- [Release notes](https://github.com/Alan-Jowett/bpf_conformance/releases)
- [Commits](https://github.com/Alan-Jowett/bpf_conformance/compare/8f3c2fe88...<a class=hub.com/Alan-Jowett/ebpf-verifier/commit/6fa6a20ac6fd3612ea9338312a67408687b9f06b">6fa6a20ac)

---
updated-dependencies:
- dependency-name: external/bpf_conformance
  dependency-version: 6fa6a20ac6fd3612ea9338312a67408687b9f06b
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>

8954 of 10121 relevant lines covered (88.47%)

18293099.16 hits per line

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

95.6
/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/var_registry.hpp"
9
#include "crab_utils/debug.hpp"
10
#include "crab_utils/graph_ops.hpp"
11
#include "crab_utils/stats.hpp"
12
#include "string_constraints.hpp"
13
#include "type_encoding.hpp"
14

15
namespace prevail {
16

17
static std::optional<SplitDBM::VertId> try_at(const SplitDBM::VertMap& map, const Variable v) {
39,700,618✔
18
    const auto it = map.find(v);
19,850,309✔
19
    if (it == map.end()) {
39,700,618✔
20
        return std::nullopt;
4,311,592✔
21
    }
22
    return it->second;
35,389,026✔
23
}
24

25
SplitDBM::VertId SplitDBM::get_vert(Variable v) {
3,455,406✔
26
    if (const auto y = try_at(vert_map, v)) {
3,455,406✔
27
        return *y;
1,855,886✔
28
    }
29

30
    VertId vert(g.new_vertex());
1,599,520✔
31
    vert_map.emplace(v, vert);
1,599,520✔
32
    // Initialize
33
    assert(vert <= rev_map.size());
1,599,520✔
34
    if (vert < rev_map.size()) {
1,599,520✔
35
        potential[vert] = Weight(0);
396,036✔
36
        rev_map[vert] = v;
396,036✔
37
    } else {
38
        potential.emplace_back(0);
1,203,484✔
39
        rev_map.push_back(v);
1,203,484✔
40
    }
41
    vert_map.emplace(v, vert);
1,599,520✔
42

43
    assert(vert != 0);
1,599,520✔
44

45
    return vert;
1,599,520✔
46
}
47

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

62
[[maybe_unused]]
63
static bool convert_NtoW_overflow(const Number& n, Number& out) {
12,509,064✔
64
    out = n;
10,863,409✔
65
    return false;
12,509,064✔
66
}
67

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

76
    std::optional<Variable> unbounded_var;
661,816✔
77
    std::vector<std::pair<Variable, Weight>> terms;
661,816✔
78

79
    Weight residual;
661,816✔
80
    if (convert_NtoW_overflow(exp.constant_term(), residual)) {
661,816✔
81
        return;
82
    }
83

84
    for (const auto& [y, n] : exp.variable_terms()) {
1,333,664✔
85
        Weight coeff;
672,892✔
86
        if (convert_NtoW_overflow(n, coeff)) {
672,892✔
87
            continue;
88
        }
89

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

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

106
            if (y_val.is_infinite()) {
671,104✔
107
                if (unbounded_var || coeff != Weight(1)) {
319,902✔
108
                    return;
772✔
109
                }
110
                unbounded_var = y;
547,910✔
111
            } else {
112
                Weight ymax;
457,588✔
113
                if (convert_NtoW_overflow(*y_val.number(), ymax)) {
457,588✔
114
                    continue;
115
                }
116
                residual += ymax * coeff;
686,382✔
117
                terms.emplace_back(y, ymax);
457,588✔
118
            }
457,588✔
119
        }
671,104✔
120
    }
672,892✔
121

122
    if (unbounded_var) {
660,772✔
123
        // There is exactly one unbounded variable with unit
124
        // coefficient
125
        diff_csts.emplace_back(*unbounded_var, residual);
211,940✔
126
    } else {
127
        for (const auto& [v, n] : terms) {
905,622✔
128
            diff_csts.emplace_back(v, residual - n);
685,185✔
129
        }
130
    }
131
}
662,338✔
132

133
void SplitDBM::diffcsts_of_lin_leq(const LinearExpression& exp,
2,207,618✔
134
                                   /* difference contraints */
135
                                   std::vector<diffcst_t>& csts,
136
                                   /* x >= lb for each {x,lb} in lbs */
137
                                   std::vector<std::pair<Variable, Weight>>& lbs,
138
                                   /* x <= ub for each {x,ub} in ubs */
139
                                   std::vector<std::pair<Variable, Weight>>& ubs) const {
140
    Weight exp_ub;
2,207,618✔
141
    if (convert_NtoW_overflow(exp.constant_term(), exp_ub)) {
2,207,618✔
142
        return;
143
    }
144
    exp_ub = -exp_ub;
3,311,427✔
145

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

154
    Weight unbounded_lbcoeff;
2,207,618✔
155
    Weight unbounded_ubcoeff;
2,207,618✔
156
    std::optional<Variable> unbounded_lbvar;
2,207,618✔
157
    std::optional<Variable> unbounded_ubvar;
2,207,618✔
158

159
    std::vector<std::pair<std::pair<Weight, Variable>, Weight>> pos_terms, neg_terms;
2,207,618✔
160
    for (const auto& [y, n] : exp.variable_terms()) {
4,480,236✔
161
        Weight coeff;
2,282,984✔
162
        if (convert_NtoW_overflow(n, coeff)) {
2,282,984✔
163
            continue;
164
        }
165
        if (coeff > Weight(0)) {
2,282,984✔
166
            auto y_lb = this->operator[](y).lb();
1,641,843✔
167
            if (y_lb.is_infinite()) {
1,094,562✔
168
                if (unbounded_lbvar) {
899,344✔
169
                    return;
×
170
                }
171
                unbounded_lbvar = y;
899,344✔
172
                unbounded_lbcoeff = coeff;
1,446,625✔
173
            } else {
174
                Weight ymin;
195,218✔
175
                if (convert_NtoW_overflow(*y_lb.number(), ymin)) {
195,218✔
176
                    continue;
177
                }
178
                exp_ub -= ymin * coeff;
292,827✔
179
                pos_terms.push_back({{coeff, y}, ymin});
390,436✔
180
            }
195,218✔
181
        } else {
1,094,562✔
182
            auto y_ub = this->operator[](y).ub();
1,782,633✔
183
            if (y_ub.is_infinite()) {
1,188,422✔
184
                if (unbounded_ubvar) {
111,144✔
185
                    return;
10,366✔
186
                }
187
                unbounded_ubvar = y;
100,778✔
188
                unbounded_ubcoeff = -coeff;
151,167✔
189
            } else {
190
                Weight ymax;
1,077,278✔
191
                if (convert_NtoW_overflow(*y_ub.number(), ymax)) {
1,077,278✔
192
                    continue;
193
                }
194
                exp_ub -= ymax * coeff;
1,615,917✔
195
                neg_terms.push_back({{-coeff, y}, ymax});
2,693,195✔
196
            }
1,077,278✔
197
        }
1,188,422✔
198
    }
2,277,801✔
199

200
    if (unbounded_lbvar) {
2,197,252✔
201
        Variable x{*unbounded_lbvar};
899,344✔
202
        if (unbounded_ubvar) {
899,344✔
203
            if (unbounded_lbcoeff == Weight(1) && unbounded_ubcoeff == Weight(1)) {
15,018✔
204
                csts.push_back({{x, *unbounded_ubvar}, exp_ub});
15,018✔
205
            }
206
        } else {
207
            if (unbounded_lbcoeff == Weight(1)) {
889,332✔
208
                for (const auto& [nv, k] : neg_terms) {
890,118✔
209
                    csts.push_back({{x, nv.second}, exp_ub - k});
1,179✔
210
                }
211
            }
212
            // Add bounds for x
213
            ubs.emplace_back(x, exp_ub / unbounded_lbcoeff);
1,333,998✔
214
        }
215
    } else {
216
        if (unbounded_ubvar) {
1,297,908✔
217
            Variable y{*unbounded_ubvar};
80,400✔
218
            if (unbounded_ubcoeff == Weight(1)) {
80,400✔
219
                for (const auto& [nv, k] : pos_terms) {
82,672✔
220
                    csts.push_back({{nv.second, y}, exp_ub + k});
3,408✔
221
                }
222
            }
223
            // Add bounds for y
224
            lbs.emplace_back(y, -exp_ub / unbounded_ubcoeff);
120,600✔
225
        } else {
226
            for (const auto& [neg_nv, neg_k] : neg_terms) {
2,262,126✔
227
                for (const auto& [pos_nv, pos_k] : pos_terms) {
1,057,620✔
228
                    csts.push_back({{pos_nv.second, neg_nv.second}, exp_ub - neg_k + pos_k});
19,503✔
229
                }
230
            }
231
            for (const auto& [neg_nv, neg_k] : neg_terms) {
2,262,126✔
232
                lbs.emplace_back(neg_nv.second, -exp_ub / neg_nv.first + neg_k);
1,566,927✔
233
            }
234
            for (const auto& [pos_nv, pos_k] : pos_terms) {
1,410,454✔
235
                ubs.emplace_back(pos_nv.second, exp_ub / pos_nv.first + pos_k);
289,419✔
236
            }
237
        }
238
    }
239
}
2,233,533✔
240

241
static GraphOps::PotentialFunction index_to_call(const GraphOps::WeightVector& p) {
4,221,644✔
242
    return [&p](GraphOps::VertId v) -> GraphOps::Weight { return p[v]; };
1,256,633,616✔
243
}
244

245
bool SplitDBM::add_linear_leq(const LinearExpression& exp) {
2,207,618✔
246
    std::vector<std::pair<Variable, Weight>> lbs, ubs;
2,207,618✔
247
    std::vector<diffcst_t> csts;
2,207,618✔
248
    diffcsts_of_lin_leq(exp, csts, lbs, ubs);
2,207,618✔
249

250
    for (const auto& [var, n] : lbs) {
3,318,466✔
251
        CRAB_LOG("zones-split", std::cout << var << ">=" << n << "\n");
1,119,324✔
252
        const VertId vert = get_vert(var);
1,119,324✔
253
        if (auto w = g.lookup(vert, 0)) {
1,119,324✔
254
            if (*w <= -n) {
173,974✔
255
                continue;
144,624✔
256
            }
257
        }
258
        g.set_edge(vert, -n, 0);
974,700✔
259

260
        if (!repair_potential(vert, 0)) {
974,700✔
261
            return false;
24,539✔
262
        }
263
    }
264
    for (const auto& [var, n] : ubs) {
3,248,988✔
265
        CRAB_LOG("zones-split", std::cout << var << "<=" << n << "\n");
1,081,874✔
266
        const VertId vert = get_vert(var);
1,081,874✔
267
        if (auto w = g.lookup(0, vert)) {
1,081,874✔
268
            if (*w <= n) {
162,498✔
269
                continue;
110,440✔
270
            }
271
        }
272
        g.set_edge(0, n, vert);
971,434✔
273
        if (!repair_potential(0, vert)) {
971,434✔
274
            return false;
36,315✔
275
        }
276
    }
277

278
    for (const auto& [diff, k] : csts) {
2,192,642✔
279
        CRAB_LOG("zones-split", std::cout << diff.first << "-" << diff.second << "<=" << k << "\n");
25,626✔
280

281
        const VertId src = get_vert(diff.second);
25,626✔
282
        const VertId dest = get_vert(diff.first);
25,626✔
283
        g.update_edge(src, k, dest);
25,626✔
284
        if (!repair_potential(src, dest)) {
25,626✔
285
            return false;
20,350✔
286
        }
287
        GraphOps::close_over_edge(g, src, dest);
25,528✔
288
    }
289
    GraphOps::apply_delta(g, GraphOps::close_after_assign(g, index_to_call(potential), 0));
2,167,016✔
290
    normalize();
2,167,016✔
291
    return true;
1,083,508✔
292
}
2,207,618✔
293

294
static Interval trim_interval(const Interval& i, const Number& n) {
4,726✔
295
    if (i.lb() == n) {
9,452✔
296
        return Interval{n + 1, i.ub()};
8,195✔
297
    }
298
    if (i.ub() == n) {
2,896✔
299
        return Interval{i.lb(), n - 1};
16✔
300
    }
301
    if (i.is_top() && n == 0) {
1,515✔
302
        return Interval{1, std::numeric_limits<uint64_t>::max()};
42✔
303
    }
304
    return i;
1,412✔
305
}
306

307
bool SplitDBM::add_univar_disequation(Variable x, const Number& n) {
4,726✔
308
    Interval i = get_interval(x, 0);
7,089✔
309
    Interval new_i = trim_interval(i, n);
4,726✔
310
    if (new_i.is_bottom()) {
4,726✔
311
        return false;
1,620✔
312
    }
313
    if (new_i.is_top() || !(new_i <= i)) {
1,486✔
314
        return true;
122✔
315
    }
316

317
    VertId v = get_vert(x);
1,364✔
318
    if (new_i.lb().is_finite()) {
2,046✔
319
        // strengthen lb
320
        Weight lb_val;
1,364✔
321
        if (convert_NtoW_overflow(-*new_i.lb().number(), lb_val)) {
3,410✔
322
            return true;
323
        }
324

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

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

374
bool SplitDBM::operator<=(const SplitDBM& o) const {
974✔
375
    CrabStats::count("SplitDBM.count.leq");
1,461✔
376
    ScopedCrabStats __st__("SplitDBM.leq");
974✔
377

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

386
    if (vert_map.size() < o.vert_map.size()) {
1,664✔
387
        return false;
21✔
388
    }
389
    constexpr VertId INVALID_VERT = std::numeric_limits<VertId>::max();
790✔
390
    // Set up a mapping from o to this.
391
    std::vector vert_renaming(o.g.size(), INVALID_VERT);
790✔
392
    vert_renaming[0] = 0;
790✔
393
    for (const auto& [v, n] : o.vert_map) {
3,548✔
394
        if (o.g.succs(n).size() == 0 && o.g.preds(n).size() == 0) {
1,312✔
395
            continue;
×
396
        }
397

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

406
    assert(g.size() > 0);
790✔
407
    for (VertId ox : o.g.verts()) {
2,558✔
408
        if (o.g.succs(ox).size() == 0) {
1,866✔
409
            continue;
48✔
410
        }
411

412
        assert(vert_renaming[ox] != INVALID_VERT);
1,818✔
413
        VertId x = vert_renaming[ox];
1,818✔
414
        for (const auto edge : o.g.e_succs(ox)) {
4,210✔
415
            VertId oy = edge.vert;
2,490✔
416
            assert(vert_renaming[oy] != INVALID_VERT);
2,490✔
417
            VertId y = vert_renaming[oy];
2,490✔
418
            Weight ow = edge.val;
2,490✔
419

420
            if (auto w = g.lookup(x, y)) {
2,490✔
421
                if (*w <= ow) {
2,452✔
422
                    continue;
2,356✔
423
                }
424
            }
425

426
            if (auto wx = g.lookup(x, 0)) {
134✔
427
                if (auto wy = g.lookup(0, y)) {
44✔
428
                    if (*wx + *wy <= ow) {
36✔
429
                        continue;
36✔
430
                    }
431
                }
432
            }
433
            return false;
98✔
434
        }
3,735✔
435
    }
436
    return true;
692✔
437
}
974✔
438

439
SplitDBM SplitDBM::operator|(const SplitDBM& o) const& {
867,508✔
440
    CrabStats::count("SplitDBM.count.join");
1,301,262✔
441
    ScopedCrabStats __st__("SplitDBM.join");
867,508✔
442

443
    if (o.is_top()) {
867,508✔
444
        return o;
4,830✔
445
    }
446
    if (is_top()) {
862,678✔
447
        return *this;
560✔
448
    }
449
    CRAB_LOG("zones-split", std::cout << "Before join:\n"
862,118✔
450
                                      << "DBM 1\n"
451
                                      << *this << "\n"
452
                                      << "DBM 2\n"
453
                                      << o << "\n");
454
    // Figure out the common renaming, initializing the
455
    // resulting potentials as we go.
456
    std::vector<VertId> perm_x;
862,118✔
457
    std::vector<VertId> perm_y;
862,118✔
458
    std::vector<Variable> perm_inv;
862,118✔
459

460
    std::vector<Weight> pot_rx;
862,118✔
461
    std::vector<Weight> pot_ry;
862,118✔
462
    VertMap out_vmap;
431,059✔
463
    RevMap out_revmap;
862,118✔
464
    // Add the zero vertex
465
    assert(!potential.empty());
862,118✔
466
    pot_rx.emplace_back(0);
862,118✔
467
    pot_ry.emplace_back(0);
862,118✔
468
    perm_x.push_back(0);
862,118✔
469
    perm_y.push_back(0);
862,118✔
470
    out_revmap.push_back(std::nullopt);
862,118✔
471

472
    for (const auto& [v, n] : vert_map) {
50,505,727✔
473
        if (auto y = try_at(o.vert_map, v)) {
32,520,994✔
474
            // Variable exists in both
475
            out_vmap.emplace(v, gsl::narrow<VertId>(perm_x.size()));
32,468,104✔
476
            out_revmap.push_back(v);
32,468,104✔
477

478
            pot_rx.push_back(potential[n] - potential[0]);
48,702,156✔
479
            // XXX JNL: check this out
480
            // pot_ry.push_back(o.potential[p.second] - o.potential[0]);
481
            pot_ry.push_back(o.potential[*y] - o.potential[0]);
48,702,156✔
482
            perm_inv.push_back(v);
32,468,104✔
483
            perm_x.push_back(n);
32,468,104✔
484
            perm_y.push_back(*y);
32,468,104✔
485
        }
486
    }
487
    size_t sz = perm_x.size();
862,118✔
488

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

495
    // Compute the deferred relations
496
    Graph g_ix_ry;
862,118✔
497
    g_ix_ry.growTo(sz);
862,118✔
498
    SubGraph gy_excl(gy, 0);
862,118✔
499
    for (VertId s : gy_excl.verts()) {
49,564,281✔
500
        for (VertId d : gy_excl.succs(s)) {
51,786,156✔
501
            if (auto ws = gx.lookup(s, 0)) {
19,318,052✔
502
                if (auto wd = gx.lookup(0, d)) {
17,324,474✔
503
                    g_ix_ry.add_edge(s, *ws + *wd, d);
25,960,743✔
504
                }
505
            }
506
        }
507
    }
508
    // Apply the deferred relations, and re-close.
509
    bool is_closed;
431,059✔
510
    Graph g_rx(GraphOps::meet(gx, g_ix_ry, is_closed));
862,118✔
511
    if (!is_closed) {
862,118✔
512
        GraphOps::apply_delta(g_rx, GraphOps::close_after_meet(SubGraph(g_rx, 0), index_to_call(pot_rx), gx, g_ix_ry));
862,118✔
513
    }
514

515
    Graph g_rx_iy;
862,118✔
516
    g_rx_iy.growTo(sz);
862,118✔
517

518
    SubGraph gx_excl(gx, 0);
862,118✔
519
    for (VertId s : gx_excl.verts()) {
49,564,281✔
520
        for (VertId d : gx_excl.succs(s)) {
51,949,858✔
521
            // Assumption: gx.mem(s, d) -> gx.edge_val(s, d) <= ranges[var(s)].ub() - ranges[var(d)].lb()
522
            // That is, if the relation exists, it's at least as strong as the bounds.
523
            if (auto ws = gy.lookup(s, 0)) {
19,481,754✔
524
                if (auto wd = gy.lookup(0, d)) {
17,488,902✔
525
                    g_rx_iy.add_edge(s, *ws + *wd, d);
26,206,320✔
526
                }
527
            }
528
        }
529
    }
530
    // Similarly, should use a SubGraph view.
531
    Graph g_ry(GraphOps::meet(gy, g_rx_iy, is_closed));
862,118✔
532
    if (!is_closed) {
862,118✔
533
        GraphOps::apply_delta(g_ry, GraphOps::close_after_meet(SubGraph(g_ry, 0), index_to_call(pot_ry), gy, g_rx_iy));
862,118✔
534
    }
535

536
    // We now have the relevant set of relations. Because g_rx and g_ry are closed,
537
    // the result is also closed.
538
    Graph join_g(GraphOps::join(g_rx, g_ry));
862,118✔
539

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

547
    for (VertId v : gx_excl.verts()) {
49,564,281✔
548
        if (auto wx = gx.lookup(0, v)) {
32,468,104✔
549
            if (auto wy = gy.lookup(0, v)) {
30,425,646✔
550
                if (*wx < *wy) {
30,422,832✔
551
                    ub_up.push_back(v);
22,576✔
552
                }
553
                if (*wy < *wx) {
30,422,832✔
554
                    ub_down.push_back(v);
46,040✔
555
                }
556
            }
557
        }
558
        if (auto wx = gx.lookup(v, 0)) {
32,468,104✔
559
            if (auto wy = gy.lookup(v, 0)) {
30,471,786✔
560
                if (*wx < *wy) {
30,469,180✔
561
                    lb_down.push_back(v);
18,034✔
562
                }
563
                if (*wy < *wx) {
30,469,180✔
564
                    lb_up.push_back(v);
43,572✔
565
                }
566
            }
567
        }
568
    }
569

570
    for (VertId s : lb_up) {
905,690✔
571
        Weight dx_s = gx.edge_val(s, 0);
43,572✔
572
        Weight dy_s = gy.edge_val(s, 0);
43,572✔
573
        for (VertId d : ub_up) {
97,058✔
574
            if (s == d) {
53,486✔
575
                continue;
13,664✔
576
            }
577

578
            join_g.update_edge(s, std::max(dx_s + gx.edge_val(0, d), dy_s + gy.edge_val(0, d)), d);
59,733✔
579
        }
580
    }
43,572✔
581

582
    for (VertId s : lb_down) {
880,152✔
583
        Weight dx_s = gx.edge_val(s, 0);
18,034✔
584
        Weight dy_s = gy.edge_val(s, 0);
18,034✔
585
        for (VertId d : ub_down) {
63,006✔
586
            if (s == d) {
44,972✔
587
                continue;
13,252✔
588
            }
589

590
            join_g.update_edge(s, std::max(dx_s + gx.edge_val(0, d), dy_s + gy.edge_val(0, d)), d);
47,580✔
591
        }
592
    }
18,034✔
593

594
    // Conjecture: join_g remains closed.
595

596
    // Now garbage collect any unused vertices
597
    for (VertId v : join_g.verts()) {
34,192,340✔
598
        if (v == 0) {
33,330,222✔
599
            continue;
862,118✔
600
        }
601
        if (join_g.succs(v).size() == 0 && join_g.preds(v).size() == 0) {
32,468,104✔
602
            join_g.forget(v);
87,304✔
603
            if (out_revmap[v]) {
87,304✔
604
                out_vmap.erase(*(out_revmap[v]));
87,304✔
605
                out_revmap[v] = std::nullopt;
16,752,415✔
606
            }
607
        }
608
    }
609

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

615
    return res;
862,118✔
616
}
1,729,626✔
617

618
SplitDBM SplitDBM::widen(const SplitDBM& o) const {
168✔
619
    CrabStats::count("SplitDBM.count.widening");
252✔
620
    ScopedCrabStats __st__("SplitDBM.widening");
168✔
621

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

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

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

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

653
    // Now perform the widening
654
    VertSet widen_unstable(unstable);
168✔
655
    Graph widen_g(GraphOps::widen(gx, gy, widen_unstable));
168✔
656

657
    SplitDBM res(std::move(out_vmap), std::move(out_revmap), std::move(widen_g), std::move(widen_pot),
168✔
658
                 std::move(widen_unstable));
252✔
659

660
    CRAB_LOG("zones-split", std::cout << "Result widening:\n" << res << "\n");
168✔
661
    return res;
252✔
662
}
420✔
663

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

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

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

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

697
        perm_x.push_back(n);
112✔
698
        perm_y.push_back(-1);
112✔
699
        meet_pi.push_back(potential[n] - potential[0]);
168✔
700
    }
701

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

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

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

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

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

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

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

741
        // Recover updated LBs and UBs.<
742

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

750
void SplitDBM::havoc(const Variable v) {
3,384,864✔
751
    if (const auto y = try_at(vert_map, v)) {
3,384,864✔
752
        g.forget(*y);
822,642✔
753
        rev_map[*y] = std::nullopt;
822,642✔
754
        vert_map.erase(v);
822,642✔
755
        normalize();
822,642✔
756
    }
757
}
3,384,864✔
758

759
// return false if becomes bottom
760
bool SplitDBM::add_constraint(const LinearConstraint& cst) {
1,268,268✔
761
    CrabStats::count("SplitDBM.count.add_constraints");
1,902,402✔
762
    ScopedCrabStats __st__("SplitDBM.add_constraints");
1,268,268✔
763

764
    if (cst.is_tautology()) {
1,268,268✔
765
        return true;
72✔
766
    }
767

768
    // g.check_adjs();
769

770
    if (cst.is_contradiction()) {
1,268,124✔
771
        return false;
330✔
772
    }
773

774
    switch (cst.kind()) {
1,267,464✔
775
    case ConstraintKind::LESS_THAN_OR_EQUALS_ZERO: {
256,608✔
776
        if (!add_linear_leq(cst.expression())) {
256,608✔
777
            return false;
19,926✔
778
        }
779
        //  g.check_adjs();
780
        CRAB_LOG("zones-split", std::cout << "--- " << cst << "\n" << *this << "\n");
216,756✔
781
        break;
108,378✔
782
    }
783
    case ConstraintKind::LESS_THAN_ZERO: {
60,240✔
784
        // We try to convert a strict to non-strict.
785
        // e < 0 --> e <= -1
786
        const auto nc = LinearConstraint(cst.expression().plus(1), ConstraintKind::LESS_THAN_OR_EQUALS_ZERO);
90,360✔
787
        if (!add_linear_leq(nc.expression())) {
60,240✔
788
            return false;
660✔
789
        }
790
        CRAB_LOG("zones-split", std::cout << "--- " << cst << "\n" << *this << "\n");
59,580✔
791
        break;
59,580✔
792
    }
60,240✔
793
    case ConstraintKind::EQUALS_ZERO: {
945,406✔
794
        const LinearExpression& exp = cst.expression();
945,406✔
795
        if (!add_linear_leq(exp) || !add_linear_leq(exp.negate())) {
1,890,746✔
796
            CRAB_LOG("zones-split", std::cout << " ~~> _|_" << "\n");
90✔
797
            return false;
90✔
798
        }
799
        // g.check_adjs();
800
        CRAB_LOG("zones-split", std::cout << "--- " << cst << "\n" << *this << "\n");
945,316✔
801
        break;
472,658✔
802
    }
803
    case ConstraintKind::NOT_ZERO: {
5,210✔
804
        // XXX: similar precision as the interval domain
805
        const LinearExpression& e = cst.expression();
5,210✔
806
        for (const auto& [variable, coefficient] : e.variable_terms()) {
8,076✔
807
            Interval i = compute_residual(e, variable) / Interval(coefficient);
9,159✔
808
            if (auto k = i.singleton()) {
6,106✔
809
                if (!add_univar_disequation(variable, *k)) {
4,726✔
810
                    return false;
3,240✔
811
                }
812
            }
6,106✔
813
        }
6,106✔
814
    } break;
985✔
815
    }
816

817
    CRAB_WARN("Unhandled constraint ", cst, " by split_dbm");
1,223,622✔
818
    CRAB_LOG("zones-split", std::cout << "---" << cst << "\n" << *this << "\n");
1,223,622✔
819
    normalize();
1,223,622✔
820
    return true;
611,811✔
821
}
1,268,268✔
822

823
void SplitDBM::assign(Variable lhs, const LinearExpression& e) {
611,744✔
824
    CrabStats::count("SplitDBM.count.assign");
917,616✔
825
    ScopedCrabStats __st__("SplitDBM.assign");
611,744✔
826

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

830
    Interval value_interval = eval_interval(e);
611,744✔
831

832
    std::optional<Weight> lb_w, ub_w;
611,744✔
833
    if (value_interval.lb().is_finite()) {
917,616✔
834
        Weight tmp;
505,264✔
835
        if (convert_NtoW_overflow(-*value_interval.lb().number(), tmp)) {
1,263,160✔
836
            havoc(lhs);
837
            CRAB_LOG("zones-split", std::cout << "---" << lhs << ":=" << e << "\n" << *this << "\n");
838
            normalize();
839
            return;
840
        }
841
        lb_w = tmp;
505,264✔
842
    }
505,264✔
843
    if (value_interval.ub().is_finite()) {
917,616✔
844
        Weight tmp;
505,240✔
845
        if (convert_NtoW_overflow(*value_interval.ub().number(), tmp)) {
1,010,480✔
846
            havoc(lhs);
847
            CRAB_LOG("zones-split", std::cout << "---" << lhs << ":=" << e << "\n" << *this << "\n");
848
            normalize();
849
            return;
850
        }
851
        ub_w = tmp;
505,240✔
852
    }
505,240✔
853

854
    // JN: it seems that we can only do this if
855
    // close_bounds_inline is disabled (which in eBPF is always the case).
856
    // Otherwise, the meet operator misses some non-redundant edges.
857
    if (value_interval.is_singleton()) {
611,744✔
858
        set(lhs, value_interval);
280,836✔
859
        normalize();
280,836✔
860
        return;
140,418✔
861
    }
862

863
    std::vector<std::pair<Variable, Weight>> diffs_lb, diffs_ub;
330,908✔
864
    // Construct difference constraints from the assignment
865
    diffcsts_of_assign(lhs, e, diffs_lb, diffs_ub);
330,908✔
866
    if (diffs_lb.empty() && diffs_ub.empty()) {
330,908✔
867
        set(lhs, value_interval);
600✔
868
        normalize();
600✔
869
        return;
300✔
870
    }
871

872
    Weight e_val;
330,308✔
873
    if (eval_expression_overflow(e, e_val)) {
330,308✔
874
        havoc(lhs);
×
875
        return;
×
876
    }
877
    // Allocate a new vertex for x
878
    VertId vert = g.new_vertex();
330,308✔
879
    assert(vert <= rev_map.size());
330,308✔
880
    if (vert == rev_map.size()) {
330,308✔
881
        rev_map.push_back(lhs);
181,998✔
882
        potential.push_back(potential[0] + e_val);
272,997✔
883
    } else {
884
        potential[vert] = potential[0] + e_val;
148,310✔
885
        rev_map[vert] = lhs;
148,310✔
886
    }
887

888
    {
165,154✔
889
        GraphOps::EdgeVector delta;
330,308✔
890
        for (const auto& [var, n] : diffs_lb) {
664,674✔
891
            delta.emplace_back(vert, get_vert(var), -n);
334,366✔
892
        }
893

894
        for (const auto& [var, n] : diffs_ub) {
664,672✔
895
            delta.emplace_back(get_vert(var), vert, n);
334,364✔
896
        }
897

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

903
    if (lb_w) {
330,308✔
904
        g.update_edge(vert, *lb_w, 0);
336,525✔
905
    }
906
    if (ub_w) {
330,308✔
907
        g.update_edge(0, *ub_w, vert);
336,489✔
908
    }
909
    // Clear the old x vertex
910
    havoc(lhs);
330,308✔
911
    vert_map.emplace(lhs, vert);
330,308✔
912

913
    normalize();
330,308✔
914
    CRAB_LOG("zones-split", std::cout << "---" << lhs << ":=" << e << "\n" << *this << "\n");
330,308✔
915
}
1,340,670✔
916

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

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

929
class VertSetWrap {
930
  public:
931
    explicit VertSetWrap(const SplitDBM::VertSet& _vs) : vs(_vs) {}
46✔
932

933
    bool operator[](const SplitDBM::VertId v) const { return vs.contains(v); }
148✔
934
    const SplitDBM::VertSet& vs;
935
};
936

937
bool SplitDBM::repair_potential(VertId src, VertId dest) { return GraphOps::repair_potential(g, potential, src, dest); }
1,971,854✔
938

939
void SplitDBM::clear_thread_local_state() { GraphOps::clear_thread_local_state(); }
1,392✔
940

941
void SplitDBM::normalize() {
6,418,160✔
942
    CrabStats::count("SplitDBM.count.normalize");
9,627,240✔
943
    ScopedCrabStats __st__("SplitDBM.normalize");
6,418,160✔
944

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

951
    GraphOps::EdgeVector delta;
46✔
952
    // GraphOps::close_after_widen(g, potential, VertSetWrap(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, VertSetWrap(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,418,160✔
961

962
void SplitDBM::set(const Variable x, const Interval& intv) {
536,762✔
963
    CrabStats::count("SplitDBM.count.assign");
805,143✔
964
    ScopedCrabStats __st__("SplitDBM.assign");
536,762✔
965
    assert(!intv.is_bottom());
536,762✔
966

967
    havoc(x);
536,762✔
968

969
    if (intv.is_top()) {
536,762✔
970
        return;
3,900✔
971
    }
972

973
    const VertId v = get_vert(x);
532,862✔
974
    if (intv.ub().is_finite()) {
799,293✔
975
        Weight ub;
532,856✔
976
        if (convert_NtoW_overflow(*intv.ub().number(), ub)) {
1,065,712✔
977
            normalize();
978
            return;
979
        }
980
        potential[v] = potential[0] + ub;
532,856✔
981
        g.set_edge(0, ub, v);
799,284✔
982
    }
532,856✔
983
    if (intv.lb().is_finite()) {
799,293✔
984
        Weight lb;
532,706✔
985
        if (convert_NtoW_overflow(*intv.lb().number(), lb)) {
1,065,412✔
986
            normalize();
987
            return;
988
        }
989
        potential[v] = potential[0] + lb;
532,706✔
990
        g.set_edge(v, -lb, 0);
799,059✔
991
    }
532,706✔
992
    normalize();
532,862✔
993
}
536,762✔
994

995
void SplitDBM::apply(const ArithBinOp op, const Variable x, const Variable y, const Variable z,
6,080✔
996
                     const int finite_width) {
997
    CrabStats::count("SplitDBM.count.apply");
9,120✔
998
    ScopedCrabStats __st__("SplitDBM.apply");
6,080✔
999

1000
    switch (op) {
6,080✔
1001
    case ArithBinOp::ADD: assign(x, LinearExpression(y).plus(z)); break;
4,994✔
1002
    case ArithBinOp::SUB: assign(x, LinearExpression(y).subtract(z)); break;
798✔
1003
    // For the rest of operations, we fall back on intervals.
1004
    case ArithBinOp::MUL: set(x, get_interval(y, finite_width) * get_interval(z, finite_width)); break;
180✔
1005
    case ArithBinOp::SDIV: set(x, get_interval(y, finite_width).sdiv(get_interval(z, finite_width))); break;
80✔
1006
    case ArithBinOp::UDIV: set(x, get_interval(y, finite_width).udiv(get_interval(z, finite_width))); break;
260✔
1007
    case ArithBinOp::SREM: set(x, get_interval(y, finite_width).srem(get_interval(z, finite_width))); break;
115✔
1008
    case ArithBinOp::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,080✔
1012
}
6,080✔
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 read_imm_for_udiv_or_umod(const Number& imm, const int width) {
2,206✔
1019
    assert(width == 32 || width == 64);
2,206✔
1020
    if (width == 32) {
2,206✔
1021
        return Number{imm.cast_to<uint32_t>()};
2,104✔
1022
    }
1023
    return Number{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 read_imm_for_sdiv_or_smod(const Number& imm, const int width) {
52✔
1031
    assert(width == 32 || width == 64);
52✔
1032
    if (width == 32) {
52✔
1033
        return Number{imm.cast_to<int32_t>()};
20✔
1034
    }
1035
    return Number{imm.cast_to<int64_t>()};
32✔
1036
}
1037

1038
void SplitDBM::apply(const ArithBinOp op, const Variable x, const Variable y, const Number& k, const int finite_width) {
49,136✔
1039
    CrabStats::count("SplitDBM.count.apply");
73,704✔
1040
    ScopedCrabStats __st__("SplitDBM.apply");
49,136✔
1041

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

1066
void SplitDBM::apply(BitwiseBinOp op, Variable x, Variable y, Variable z, int finite_width) {
7,194✔
1067
    CrabStats::count("SplitDBM.count.apply");
10,791✔
1068
    ScopedCrabStats __st__("SplitDBM.apply");
7,194✔
1069

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

1087
// Apply a bitwise operator to a uvalue.
1088
void SplitDBM::apply(BitwiseBinOp op, Variable x, Variable y, const Number& k, int finite_width) {
67,088✔
1089
    CrabStats::count("SplitDBM.count.apply");
100,632✔
1090
    ScopedCrabStats __st__("SplitDBM.apply");
67,088✔
1091

1092
    // Convert to intervals and perform the operation
1093
    normalize();
67,088✔
1094
    Interval yi = this->operator[](y);
100,632✔
1095
    Interval zi(Number(k.cast_to<uint64_t>()));
67,088✔
1096
    Interval xi = Interval::bottom();
67,088✔
1097

1098
    switch (op) {
67,088✔
1099
    case BitwiseBinOp::AND: xi = yi.bitwise_and(zi); break;
93,567✔
1100
    case BitwiseBinOp::OR: xi = yi.bitwise_or(zi); break;
3,975✔
1101
    case BitwiseBinOp::XOR: xi = yi.bitwise_xor(zi); break;
588✔
1102
    case BitwiseBinOp::SHL: xi = yi.shl(zi); break;
2,502✔
1103
    case BitwiseBinOp::LSHR: xi = yi.lshr(zi); break;
×
1104
    case BitwiseBinOp::ASHR: xi = yi.ashr(zi); break;
×
1105
    default: CRAB_ERROR("DBM: unreachable");
×
1106
    }
1107
    set(x, xi);
67,088✔
1108
    normalize();
67,088✔
1109
}
134,176✔
1110

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

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

1124
static std::string to_string(const Variable vd, const Variable vs, const SplitDBM::Weight& w, const bool eq) {
252✔
1125
    std::stringstream elem;
252✔
1126
    if (eq) {
252✔
1127
        if (w.operator>(0)) {
8✔
1128
            elem << vd << "=" << vs << "+" << w;
8✔
1129
        } else if (w.operator<(0)) {
×
1130
            elem << vs << "=" << vd << "+" << -w;
×
1131
        } else {
1132
            const auto [left, right] = std::minmax(vs, vd, variable_registry->printing_order);
×
1133
            elem << left << "=" << right;
×
1134
        }
1135
    } else {
1136
        elem << vd << "-" << vs << "<=" << w;
244✔
1137
    }
1138
    return elem.str();
504✔
1139
}
252✔
1140

1141
StringInvariant SplitDBM::to_set() const {
2,392✔
1142
    if (this->is_top()) {
2,392✔
1143
        return StringInvariant::top();
154✔
1144
    }
1145
    // Extract all the edges
1146
    SubGraph g_excl{this->g, 0};
2,238✔
1147

1148
    std::map<Variable, Variable> equivalence_classes;
2,238✔
1149
    std::set<std::tuple<Variable, Variable, Weight>> diff_csts;
2,238✔
1150
    for (const VertId s : g_excl.verts()) {
8,156✔
1151
        const Variable vs = *rev_map.at(s);
5,918✔
1152
        Variable least = vs;
5,918✔
1153
        for (const VertId d : g_excl.succs(s)) {
10,659✔
1154
            const Variable vd = *rev_map.at(d);
1,596✔
1155
            const Weight w = g_excl.edge_val(s, d);
1,596✔
1156
            if (w == 0) {
1,596✔
1157
                least = std::min(least, vd, variable_registry->printing_order);
2,016✔
1158
            } else {
1159
                diff_csts.emplace(vd, vs, w);
252✔
1160
            }
1161
        }
1,596✔
1162
        equivalence_classes.insert_or_assign(vs, least);
5,918✔
1163
    }
1164

1165
    std::set<Variable> representatives;
2,238✔
1166
    std::set<std::string> result;
2,238✔
1167
    for (const auto [vs, least] : equivalence_classes) {
8,156✔
1168
        if (vs == least) {
5,918✔
1169
            representatives.insert(least);
5,326✔
1170
        } else {
1171
            result.insert(variable_registry->name(vs) + "=" + variable_registry->name(least));
1,184✔
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) {
2,490✔
1179
        if (!representatives.contains(vd) || !representatives.contains(vs)) {
252✔
1180
            continue;
126✔
1181
        }
1182
        auto dual = to_string(vs, vd, -w, false);
126✔
1183
        if (result.contains(dual)) {
126✔
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));
177✔
1189
        }
1190
    }
126✔
1191

1192
    // Intervals
1193
    for (VertId v : g_excl.verts()) {
8,156✔
1194
        const auto pvar = this->rev_map[v];
5,918✔
1195
        if (!pvar || !representatives.contains(*pvar)) {
5,918✔
1196
            continue;
726✔
1197
        }
1198
        if (!this->g.elem(0, v) && !this->g.elem(v, 0)) {
5,326✔
1199
            continue;
60✔
1200
        }
1201
        Interval v_out{this->g.elem(v, 0) ? -Number(this->g.edge_val(v, 0)) : ExtendedNumber::minus_infinity(),
15,796✔
1202
                       this->g.elem(0, v) ? Number(this->g.edge_val(0, v)) : ExtendedNumber::plus_infinity()};
13,133✔
1203
        assert(!v_out.is_bottom());
5,266✔
1204

1205
        Variable variable = *pvar;
5,266✔
1206

1207
        std::stringstream elem;
5,266✔
1208
        elem << variable;
5,266✔
1209
        if (variable_registry->is_type(variable)) {
5,266✔
1210
            auto [lb, ub] = v_out.bound(T_UNINIT, T_MAX);
1,992✔
1211
            if (lb == ub) {
1,992✔
1212
                if (variable_registry->is_in_stack(variable) && lb == T_NUM) {
1,962✔
1213
                    // no need to show this
1214
                    continue;
74✔
1215
                }
1216
                elem << "=" << lb;
1,888✔
1217
            } else {
1218
                elem << " in " << typeset_to_string(iterate_types(lb, ub));
45✔
1219
            }
1220
        } else {
1221
            elem << "=";
3,274✔
1222
            if (v_out.is_singleton()) {
3,274✔
1223
                elem << v_out.lb();
4,840✔
1224
            } else {
1225
                elem << v_out;
854✔
1226
            }
1227
        }
1228
        result.insert(elem.str());
5,192✔
1229
    }
7,936✔
1230

1231
    return StringInvariant{result};
2,238✔
1232
}
2,238✔
1233

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

1236
bool SplitDBM::eval_expression_overflow(const LinearExpression& e, Weight& out) const {
330,308✔
1237
    [[maybe_unused]]
1238
    const bool overflow = convert_NtoW_overflow(e.constant_term(), out);
330,308✔
1239
    assert(!overflow);
330,308✔
1240
    for (const auto& [variable, coefficient] : e.variable_terms()) {
665,742✔
1241
        Weight coef;
335,434✔
1242
        if (convert_NtoW_overflow(coefficient, coef)) {
335,434✔
1243
            out = Weight(0);
1244
            return true;
1245
        }
1246
        out += (pot_value(variable) - potential[0]) * coef;
503,151✔
1247
    }
335,434✔
1248
    return false;
330,308✔
1249
}
1250

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

1261
SplitDBM::Weight SplitDBM::pot_value(const Variable v) const {
335,434✔
1262
    if (const auto y = try_at(vert_map, v)) {
335,434✔
1263
        return potential[*y];
240,736✔
1264
    }
1265
    return {0};
94,698✔
1266
}
1267

1268
Interval SplitDBM::eval_interval(const LinearExpression& e) const {
287,092,807✔
1269
    using namespace prevail::interval_operators;
143,543,179✔
1270
    Interval r{e.constant_term()};
287,092,807✔
1271
    for (const auto& [variable, coefficient] : e.variable_terms()) {
574,023,594✔
1272
        r += coefficient * operator[](variable);
573,855,125✔
1273
    }
1274
    return r;
287,092,807✔
1275
}
×
1276

1277
bool SplitDBM::intersect(const LinearConstraint& cst) const {
94,354✔
1278
    if (cst.is_contradiction()) {
94,354✔
1279
        return false;
140✔
1280
    }
1281
    if (is_top() || cst.is_tautology()) {
94,074✔
1282
        return true;
390✔
1283
    }
1284
    return intersect_aux(cst);
93,684✔
1285
}
1286

1287
bool SplitDBM::entail(const LinearConstraint& rhs) const {
2,407,894✔
1288
    if (rhs.is_tautology()) {
2,407,894✔
1289
        return true;
101✔
1290
    }
1291
    if (rhs.is_contradiction()) {
2,407,692✔
1292
        return false;
1293
    }
1294
    const Interval interval = eval_interval(rhs.expression());
2,407,692✔
1295
    switch (rhs.kind()) {
2,407,692✔
1296
    case ConstraintKind::EQUALS_ZERO:
166,610✔
1297
        if (interval.singleton() == std::optional(Number(0))) {
166,610✔
1298
            return true;
78,512✔
1299
        }
1300
        break;
4,793✔
1301
    case ConstraintKind::LESS_THAN_OR_EQUALS_ZERO:
93,210✔
1302
        if (interval.ub() <= Number(0)) {
139,815✔
1303
            return true;
40,378✔
1304
        }
1305
        break;
6,227✔
1306
    case ConstraintKind::LESS_THAN_ZERO:
10,118✔
1307
        if (interval.ub() < Number(0)) {
15,177✔
1308
            return true;
5,051✔
1309
        }
1310
        break;
8✔
1311
    case ConstraintKind::NOT_ZERO:
2,137,754✔
1312
        if (interval.ub() < Number(0) || interval.lb() > Number(0)) {
9,197,237✔
1313
            return true;
646,893✔
1314
        }
1315
        break;
421,984✔
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() == ConstraintKind::EQUALS_ZERO) {
866,024✔
1320
        // try to convert the equality into inequalities so when it's
1321
        // negated we do not have disequalities.
1322
        return entail_aux(LinearConstraint(rhs.expression(), ConstraintKind::LESS_THAN_OR_EQUALS_ZERO)) &&
19,321✔
1323
               entail_aux(LinearConstraint(rhs.expression().negate(), ConstraintKind::LESS_THAN_OR_EQUALS_ZERO));
9,735✔
1324
    } else {
1325
        return entail_aux(rhs);
856,438✔
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
}
2,407,692✔
1336

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

1344
static Interval get_interval(const SplitDBM::VertMap& m, const SplitDBM::Graph& r, const Variable x,
289,977,553✔
1345
                             const int finite_width) {
1346
    const auto it = m.find(x);
144,985,552✔
1347
    if (it == m.end()) {
289,977,553✔
1348
        return Interval::top();
133,924,179✔
1349
    }
1350
    const SplitDBM::VertId v = it->second;
156,053,374✔
1351
    ExtendedNumber lb = ExtendedNumber::minus_infinity();
156,053,374✔
1352
    ExtendedNumber ub = ExtendedNumber::plus_infinity();
156,053,374✔
1353
    if (r.elem(v, 0)) {
156,053,374✔
1354
        lb = variable_registry->is_unsigned(x) ? (-Number(r.edge_val(v, 0))).zero_extend(finite_width)
542,515,360✔
1355
                                               : (-Number(r.edge_val(v, 0))).sign_extend(finite_width);
696,653,259✔
1356
    }
1357
    if (r.elem(0, v)) {
156,053,374✔
1358
        ub = variable_registry->is_unsigned(x) ? Number(r.edge_val(0, v)).zero_extend(finite_width)
389,816,835✔
1359
                                               : Number(r.edge_val(0, v)).sign_extend(finite_width);
467,459,268✔
1360
    }
1361
    return {lb, ub};
156,053,374✔
1362
}
156,053,374✔
1363

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

1368
Interval SplitDBM::operator[](const Variable x) const { return prevail::get_interval(vert_map, g, x, 0); }
289,966,396✔
1369

1370
} // namespace prevail
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc