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

Alan-Jowett / ebpf-verifier / 15194704016

22 May 2025 08:53AM UTC coverage: 88.11% (-0.07%) from 88.177%
15194704016

push

github

elazarg
uniform class names and explicit constructors for adapt_sgraph.hpp

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

27 of 30 new or added lines in 1 file covered. (90.0%)

481 existing lines in 33 files now uncovered.

8552 of 9706 relevant lines covered (88.11%)

9089054.61 hits per line

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

94.85
/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 prevail {
15

16
static std::optional<SplitDBM::VertId> try_at(const SplitDBM::VertMap& map, const Variable v) {
9,314,232✔
17
    const auto it = map.find(v);
4,657,114✔
18
    if (it == map.end()) {
9,314,232✔
19
        return std::nullopt;
5,581,771✔
20
    }
21
    return it->second;
3,732,461✔
22
}
23

24
SplitDBM::VertId SplitDBM::get_vert(Variable v) {
2,489,513✔
25
    if (const auto y = try_at(vert_map, v)) {
2,489,513✔
26
        return *y;
941,801✔
27
    }
28

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

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

44
    return vert;
1,547,712✔
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& n, SafeI64& 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& n, Number& out) {
11,846,696✔
63
    out = n;
9,885,255✔
64
    return false;
11,846,696✔
65
}
66

67
void SplitDBM::diffcsts_of_assign(Variable x, const LinearExpression& exp,
680,672✔
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, Weight>>& diff_csts) const {
74

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

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

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

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

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

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

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

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

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

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

158
    std::vector<std::pair<std::pair<Weight, Variable>, Weight>> pos_terms, neg_terms;
1,993,676✔
159
    for (const auto& [y, n] : exp.variable_terms()) {
4,054,382✔
160
        Weight coeff;
2,886,315✔
161
        if (convert_NtoW_overflow(n, coeff)) {
2,886,315✔
162
            continue;
163
        }
164
        if (coeff > Weight(0)) {
2,886,315✔
165
            auto y_lb = this->operator[](y).lb();
1,488,876✔
166
            if (y_lb.is_infinite()) {
992,584✔
167
                if (unbounded_lbvar) {
869,747✔
168
                    return;
×
169
                }
170
                unbounded_lbvar = y;
869,747✔
171
                unbounded_lbcoeff = coeff;
1,366,039✔
172
            } else {
173
                Weight ymin;
122,837✔
174
                if (convert_NtoW_overflow(*y_lb.number(), ymin)) {
122,837✔
175
                    continue;
176
                }
177
                exp_ub -= ymin * coeff;
184,255✔
178
                pos_terms.push_back({{coeff, y}, ymin});
245,673✔
179
            }
122,837✔
180
        } else {
992,584✔
181
            auto y_ub = this->operator[](y).ub();
2,840,597✔
182
            if (y_ub.is_infinite()) {
1,893,731✔
183
                if (unbounded_ubvar) {
1,734,681✔
184
                    return;
825,609✔
185
                }
186
                unbounded_ubvar = y;
909,072✔
187
                unbounded_ubcoeff = -coeff;
1,363,608✔
188
            } else {
189
                Weight ymax;
159,050✔
190
                if (convert_NtoW_overflow(*y_ub.number(), ymax)) {
159,050✔
191
                    continue;
192
                }
193
                exp_ub -= ymax * coeff;
238,575✔
194
                neg_terms.push_back({{-coeff, y}, ymax});
397,625✔
195
            }
159,050✔
196
        }
1,893,731✔
197
    }
2,473,510✔
198

199
    if (unbounded_lbvar) {
1,168,067✔
200
        Variable x{*unbounded_lbvar};
869,747✔
201
        if (unbounded_ubvar) {
869,747✔
202
            if (unbounded_lbcoeff == Weight(1) && unbounded_ubcoeff == Weight(1)) {
14,466✔
203
                csts.push_back({{x, *unbounded_ubvar}, exp_ub});
14,466✔
204
            }
205
        } else {
206
            if (unbounded_lbcoeff == Weight(1)) {
860,103✔
207
                for (const auto& [nv, k] : neg_terms) {
862,131✔
208
                    csts.push_back({{x, nv.second}, exp_ub - k});
3,042✔
209
                }
210
            }
211
            // Add bounds for x
212
            ubs.emplace_back(x, exp_ub / unbounded_lbcoeff);
1,290,155✔
213
        }
214
    } else {
215
        if (unbounded_ubvar) {
298,320✔
216
            Variable y{*unbounded_ubvar};
73,819✔
217
            if (unbounded_ubcoeff == Weight(1)) {
73,819✔
218
                for (const auto& [nv, k] : pos_terms) {
76,051✔
219
                    csts.push_back({{nv.second, y}, exp_ub + k});
3,348✔
220
                }
221
            }
222
            // Add bounds for y
223
            lbs.emplace_back(y, -exp_ub / unbounded_ubcoeff);
110,728✔
224
        } else {
225
            for (const auto& [neg_nv, neg_k] : neg_terms) {
349,639✔
226
                for (const auto& [pos_nv, pos_k] : pos_terms) {
138,124✔
227
                    csts.push_back({{pos_nv.second, neg_nv.second}, exp_ub - neg_k + pos_k});
19,479✔
228
                }
229
            }
230
            for (const auto& [neg_nv, neg_k] : neg_terms) {
349,639✔
231
                lbs.emplace_back(neg_nv.second, -exp_ub / neg_nv.first + neg_k);
187,707✔
232
            }
233
            for (const auto& [pos_nv, pos_k] : pos_terms) {
343,902✔
234
                ubs.emplace_back(pos_nv.second, exp_ub / pos_nv.first + pos_k);
179,101✔
235
            }
236
        }
237
    }
238
}
4,057,696✔
239

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

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

249
    for (const auto& [var, n] : lbs) {
2,178,729✔
250
        CRAB_LOG("zones-split", std::cout << var << ">=" << n << "\n");
193,259✔
251
        const VertId vert = get_vert(var);
193,259✔
252
        if (auto w = g.lookup(vert, 0)) {
193,259✔
253
            if (*w <= -n) {
99,036✔
254
                continue;
70,642✔
255
            }
256
        }
257
        g.set_edge(vert, -n, 0);
122,617✔
258

259
        if (!repair_potential(vert, 0)) {
122,617✔
260
            return false;
24,268✔
261
        }
262
    }
263
    for (const auto& [var, n] : ubs) {
2,932,548✔
264
        CRAB_LOG("zones-split", std::cout << var << "<=" << n << "\n");
979,108✔
265
        const VertId vert = get_vert(var);
979,108✔
266
        if (auto w = g.lookup(0, vert)) {
979,108✔
267
            if (*w <= n) {
89,136✔
268
                continue;
37,462✔
269
            }
270
        }
271
        g.set_edge(0, n, vert);
941,646✔
272
        if (!repair_potential(0, vert)) {
941,646✔
273
            return false;
36,180✔
274
        }
275
    }
276

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

280
        const VertId src = get_vert(diff.second);
26,452✔
281
        const VertId dest = get_vert(diff.first);
26,452✔
282
        g.update_edge(src, k, dest);
26,452✔
283
        if (!repair_potential(src, dest)) {
26,452✔
284
            return false;
20,212✔
285
        }
286
        GraphOps::close_over_edge(g, src, dest);
26,358✔
287
    }
288
    GraphOps::apply_delta(g, GraphOps::close_after_assign(g, index_to_call(potential), 0));
1,953,346✔
289
    normalize();
1,953,346✔
290
    return true;
976,673✔
291
}
1,993,676✔
292

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

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

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

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

350
        if (auto w = g.lookup(0, v)) {
1,364✔
351
            if (ub_val < *w) {
1,336✔
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
        }
368
    }
1,364✔
369
    normalize();
1,364✔
370
    return true;
682✔
371
}
7,095✔
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
    constexpr VertId INVALID_VERT = std::numeric_limits<VertId>::max();
152✔
389
    // Set up a mapping from o to this.
390
    std::vector vert_renaming(o.g.size(), INVALID_VERT);
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 (VertId ox : o.g.verts()) {
508✔
407
        if (o.g.succs(ox).size() == 0) {
432✔
408
            continue;
28✔
409
        }
410

411
        assert(vert_renaming[ox] != INVALID_VERT);
404✔
412
        VertId x = vert_renaming[ox];
404✔
413
        for (const auto edge : o.g.e_succs(ox)) {
1,238✔
414
            VertId oy = edge.vert;
910✔
415
            assert(vert_renaming[oy] != INVALID_VERT);
910✔
416
            VertId 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
            }
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
            }
432
            return false;
76✔
433
        }
1,365✔
434
    }
435
    return true;
76✔
436
}
198✔
437

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

593
    // Conjecture: join_g remains closed.
594

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

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

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

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

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

627
    // Figure out the common renaming
628
    assert(!potential.empty());
84✔
629
    std::vector<Weight> widen_pot = {0};
126✔
630
    std::vector<VertId> perm_x = {0};
126✔
631
    std::vector<VertId> perm_y = {0};
168✔
632
    VertMap out_vmap;
42✔
633
    RevMap 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<VertId>(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
    VertSet widen_unstable(unstable);
84✔
654
    Graph 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
    VertMap meet_verts;
19✔
682
    RevMap meet_rev;
38✔
683

684
    std::vector<VertId> perm_x;
38✔
685
    std::vector<VertId> 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
        VertId vv = gsl::narrow<VertId>(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
            VertId vv = gsl::narrow<VertId>(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 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), VertSet());
38✔
745
    CRAB_LOG("zones-split", std::cout << "Result meet:\n" << res << "\n");
38✔
746
    return res;
38✔
747
}
76✔
748

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

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

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

766
    // g.check_adjs();
767

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

772
    switch (cst.kind()) {
1,971,052✔
773
    case ConstraintKind::LESS_THAN_OR_EQUALS_ZERO: {
1,885,942✔
774
        if (!add_linear_leq(cst.expression())) {
1,885,942✔
775
            return false;
19,930✔
776
        }
777
        //  g.check_adjs();
778
        CRAB_LOG("zones-split", std::cout << "--- " << cst << "\n" << *this << "\n");
1,846,082✔
779
        break;
923,041✔
780
    }
781
    case ConstraintKind::LESS_THAN_ZERO: {
52,016✔
782
        // We try to convert a strict to non-strict.
783
        // e < 0 --> e <= -1
784
        const auto nc = LinearConstraint(cst.expression().plus(1), ConstraintKind::LESS_THAN_OR_EQUALS_ZERO);
78,024✔
785
        if (!add_linear_leq(nc.expression())) {
52,016✔
786
            return false;
380✔
787
        }
788
        CRAB_LOG("zones-split", std::cout << "--- " << cst << "\n" << *this << "\n");
51,636✔
789
        break;
51,636✔
790
    }
52,016✔
791
    case ConstraintKind::EQUALS_ZERO: {
27,880✔
792
        const LinearExpression& exp = cst.expression();
27,880✔
793
        if (!add_linear_leq(exp) || !add_linear_leq(exp.negate())) {
55,694✔
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,790✔
799
        break;
13,895✔
800
    }
801
    case ConstraintKind::NOT_ZERO: {
5,214✔
802
        // XXX: similar precision as the interval domain
803
        const LinearExpression& e = cst.expression();
5,214✔
804
        for (const auto& [variable, coefficient] : e.variable_terms()) {
8,080✔
805
            Interval i = compute_residual(e, variable) / Interval(coefficient);
9,165✔
806
            if (auto k = i.singleton()) {
6,110✔
807
                if (!add_univar_disequation(variable, *k)) {
4,730✔
808
                    return false;
3,244✔
809
                }
810
            }
6,110✔
811
        }
6,110✔
812
    } break;
985✔
813
    }
814

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

821
void SplitDBM::assign(Variable lhs, const LinearExpression& e) {
611,329✔
822
    CrabStats::count("SplitDBM.count.assign");
916,994✔
823
    ScopedCrabStats __st__("SplitDBM.assign");
611,329✔
824

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

828
    Interval value_interval = eval_interval(e);
611,329✔
829

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

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

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

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

886
    {
169,872✔
887
        GraphOps::EdgeVector delta;
339,744✔
888
        for (const auto& [var, n] : diffs_lb) {
683,534✔
889
            delta.emplace_back(vert, get_vert(var), -n);
343,790✔
890
        }
891

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

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

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

911
    normalize();
339,744✔
912
    CRAB_LOG("zones-split", std::cout << "---" << lhs << ":=" << e << "\n" << *this << "\n");
339,744✔
913
}
1,325,260✔
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 VertSetWrap {
928
  public:
929
    explicit VertSetWrap(const SplitDBM::VertSet& _vs) : vs(_vs) {}
46✔
930

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

935
bool SplitDBM::repair_potential(VertId src, VertId dest) { return GraphOps::repair_potential(g, potential, src, dest); }
1,090,809✔
936

UNCOV
937
void SplitDBM::clear_thread_local_state() { GraphOps::clear_thread_local_state(); }
×
938

939
void SplitDBM::normalize() {
6,198,211✔
940
    CrabStats::count("SplitDBM.count.normalize");
9,297,319✔
941
    ScopedCrabStats __st__("SplitDBM.normalize");
6,198,211✔
942

943
    // dbm_canonical(_dbm);
944
    // Always maintained in normal form, except for widening
945
    if (unstable.empty()) {
6,198,211✔
946
        return;
6,198,165✔
947
    }
948

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

957
    unstable.clear();
46✔
958
}
6,198,211✔
959

960
void SplitDBM::set(const Variable x, const Interval& intv) {
1,120,912✔
961
    CrabStats::count("SplitDBM.count.assign");
1,681,369✔
962
    ScopedCrabStats __st__("SplitDBM.assign");
1,120,912✔
963
    assert(!intv.is_bottom());
1,120,912✔
964

965
    havoc(x);
1,120,912✔
966

967
    if (intv.is_top()) {
1,120,912✔
968
        return;
545,612✔
969
    }
970

971
    const VertId v = get_vert(x);
575,300✔
972
    if (intv.ub().is_finite()) {
862,949✔
973
        Weight ub;
575,294✔
974
        if (convert_NtoW_overflow(*intv.ub().number(), ub)) {
1,150,586✔
975
            normalize();
976
            return;
977
        }
978
        potential[v] = potential[0] + ub;
575,294✔
979
        g.set_edge(0, ub, v);
862,940✔
980
    }
575,294✔
981
    if (intv.lb().is_finite()) {
862,949✔
982
        Weight lb;
575,144✔
983
        if (convert_NtoW_overflow(*intv.lb().number(), lb)) {
1,150,286✔
984
            normalize();
985
            return;
986
        }
987
        potential[v] = potential[0] + lb;
575,144✔
988
        g.set_edge(v, -lb, 0);
862,715✔
989
    }
575,144✔
990
    normalize();
575,300✔
991
}
1,120,912✔
992

993
void SplitDBM::apply(const ArithBinOp op, const Variable x, const Variable y, const Variable z,
6,060✔
994
                     const int finite_width) {
995
    CrabStats::count("SplitDBM.count.apply");
9,090✔
996
    ScopedCrabStats __st__("SplitDBM.apply");
6,060✔
997

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

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

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

1036
void SplitDBM::apply(const ArithBinOp op, const Variable x, const Variable y, const Number& k, const int finite_width) {
48,936✔
1037
    CrabStats::count("SplitDBM.count.apply");
73,404✔
1038
    ScopedCrabStats __st__("SplitDBM.apply");
48,936✔
1039

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

1064
void SplitDBM::apply(BitwiseBinOp op, Variable x, Variable y, Variable z, int finite_width) {
7,186✔
1065
    CrabStats::count("SplitDBM.count.apply");
10,779✔
1066
    ScopedCrabStats __st__("SplitDBM.apply");
7,186✔
1067

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

1085
// Apply a bitwise operator to a uvalue.
1086
void SplitDBM::apply(BitwiseBinOp op, Variable x, Variable y, const Number& k, int finite_width) {
73,898✔
1087
    CrabStats::count("SplitDBM.count.apply");
110,847✔
1088
    ScopedCrabStats __st__("SplitDBM.apply");
73,898✔
1089

1090
    // Convert to intervals and perform the operation
1091
    normalize();
73,898✔
1092
    Interval yi = this->operator[](y);
110,847✔
1093
    Interval zi(Number(k.cast_to<uint64_t>()));
73,898✔
1094
    Interval xi = Interval::bottom();
73,898✔
1095

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

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

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

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

1138
StringInvariant SplitDBM::to_set() const {
1,198✔
1139
    if (this->is_top()) {
1,198✔
1140
        return StringInvariant::top();
40✔
1141
    }
1142
    // Extract all the edges
1143
    SubGraph g_excl{this->g, 0};
1,158✔
1144

1145
    std::map<Variable, Variable> equivalence_classes;
1,158✔
1146
    std::set<std::tuple<Variable, Variable, Weight>> diff_csts;
1,158✔
1147
    for (const VertId s : g_excl.verts()) {
7,180✔
1148
        const Variable vs = *rev_map.at(s);
6,022✔
1149
        Variable least = vs;
6,022✔
1150
        for (const VertId d : g_excl.succs(s)) {
10,976✔
1151
            const Variable vd = *rev_map.at(d);
1,752✔
1152
            const Weight w = g_excl.edge_val(s, d);
1,752✔
1153
            if (w == 0) {
1,752✔
1154
                least = std::min(least, vd, Variable::printing_order);
2,160✔
1155
            } else {
1156
                diff_csts.emplace(vd, vs, w);
312✔
1157
            }
1158
        }
1,752✔
1159
        equivalence_classes.insert_or_assign(vs, least);
6,022✔
1160
    }
1161

1162
    std::set<Variable> representatives;
1,158✔
1163
    std::set<std::string> result;
1,158✔
1164
    for (const auto [vs, least] : equivalence_classes) {
7,180✔
1165
        if (vs == least) {
6,022✔
1166
            representatives.insert(least);
5,378✔
1167
        } else {
1168
            result.insert(vs.name() + "=" + least.name());
1,288✔
1169
        }
1170
    }
1171

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

1189
    // Intervals
1190
    for (VertId v : g_excl.verts()) {
7,180✔
1191
        const auto pvar = this->rev_map[v];
6,022✔
1192
        if (!pvar || !representatives.contains(*pvar)) {
6,022✔
1193
            continue;
870✔
1194
        }
1195
        if (!this->g.elem(0, v) && !this->g.elem(v, 0)) {
5,378✔
1196
            continue;
156✔
1197
        }
1198
        Interval v_out{this->g.elem(v, 0) ? -Number(this->g.edge_val(v, 0)) : ExtendedNumber::minus_infinity(),
15,662✔
1199
                       this->g.elem(0, v) ? Number(this->g.edge_val(0, v)) : ExtendedNumber::plus_infinity()};
13,023✔
1200
        assert(!v_out.is_bottom());
5,222✔
1201

1202
        Variable variable = *pvar;
5,222✔
1203

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

1228
    return StringInvariant{result};
1,158✔
1229
}
1,158✔
1230

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

1233
bool SplitDBM::eval_expression_overflow(const LinearExpression& e, Weight& out) const {
339,744✔
1234
    [[maybe_unused]]
1235
    const bool overflow = convert_NtoW_overflow(e.constant_term(), out);
339,744✔
1236
    assert(!overflow);
339,744✔
1237
    for (const auto& [variable, coefficient] : e.variable_terms()) {
684,602✔
1238
        Weight coef;
344,858✔
1239
        if (convert_NtoW_overflow(coefficient, coef)) {
344,858✔
1240
            out = Weight(0);
1241
            return true;
1242
        }
1243
        out += (pot_value(variable) - potential[0]) * coef;
517,287✔
1244
    }
344,858✔
1245
    return false;
339,744✔
1246
}
1247

1248
Interval SplitDBM::compute_residual(const LinearExpression& e, const Variable pivot) const {
6,110✔
1249
    Interval residual(-e.constant_term());
6,110✔
1250
    for (const auto& [variable, coefficient] : e.variable_terms()) {
14,076✔
1251
        if (variable != pivot) {
7,966✔
1252
            residual = residual - (Interval(coefficient) * this->operator[](variable));
5,568✔
1253
        }
1254
    }
1255
    return residual;
6,110✔
UNCOV
1256
}
×
1257

1258
SplitDBM::Weight SplitDBM::pot_value(const Variable v) const {
344,858✔
1259
    if (const auto y = try_at(vert_map, v)) {
344,858✔
1260
        return potential[*y];
288,084✔
1261
    }
1262
    return {0};
56,774✔
1263
}
1264

1265
Interval SplitDBM::eval_interval(const LinearExpression& e) const {
31,422,283✔
1266
    using namespace prevail::interval_operators;
15,711,143✔
1267
    Interval r{e.constant_term()};
31,422,283✔
1268
    for (const auto& [variable, coefficient] : e.variable_terms()) {
61,916,545✔
1269
        r += coefficient * operator[](variable);
60,988,528✔
1270
    }
1271
    return r;
31,422,283✔
UNCOV
1272
}
×
1273

1274
bool SplitDBM::intersect(const LinearConstraint& cst) const {
1,724,044✔
1275
    if (cst.is_contradiction()) {
1,724,044✔
1276
        return false;
1277
    }
1278
    if (is_top() || cst.is_tautology()) {
1,724,044✔
1279
        return true;
146✔
1280
    }
1281
    return intersect_aux(cst);
1,723,898✔
1282
}
1283

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

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

1334
void SplitDBM::diffcsts_of_assign(const Variable x, const LinearExpression& exp,
340,336✔
1335
                                  std::vector<std::pair<Variable, Weight>>& lb,
1336
                                  std::vector<std::pair<Variable, Weight>>& ub) const {
1337
    diffcsts_of_assign(x, exp, true, ub);
340,336✔
1338
    diffcsts_of_assign(x, exp, false, lb);
340,336✔
1339
}
340,336✔
1340

1341
static Interval get_interval(const SplitDBM::VertMap& m, const SplitDBM::Graph& r, const Variable x,
34,169,975✔
1342
                             const int finite_width) {
1343
    const auto it = m.find(x);
17,084,990✔
1344
    if (it == m.end()) {
34,169,975✔
1345
        return Interval::top();
20,032,153✔
1346
    }
1347
    const SplitDBM::VertId v = it->second;
14,137,822✔
1348
    ExtendedNumber lb = ExtendedNumber::minus_infinity();
14,137,822✔
1349
    ExtendedNumber ub = ExtendedNumber::plus_infinity();
14,137,822✔
1350
    if (r.elem(v, 0)) {
14,137,822✔
1351
        lb = x.is_unsigned() ? (-Number(r.edge_val(v, 0))).zero_extend(finite_width)
48,111,424✔
1352
                             : (-Number(r.edge_val(v, 0))).sign_extend(finite_width);
60,949,484✔
1353
    }
1354
    if (r.elem(0, v)) {
14,137,822✔
1355
        ub = x.is_unsigned() ? Number(r.edge_val(0, v)).zero_extend(finite_width)
34,571,318✔
1356
                             : Number(r.edge_val(0, v)).sign_extend(finite_width);
41,149,366✔
1357
    }
1358
    return {lb, ub};
14,137,822✔
1359
}
14,137,822✔
1360

1361
Interval SplitDBM::get_interval(const Variable x, const int finite_width) const {
7,564✔
1362
    return prevail::get_interval(vert_map, g, x, finite_width);
7,420✔
1363
}
1364

1365
Interval SplitDBM::operator[](const Variable x) const { return prevail::get_interval(vert_map, g, x, 0); }
34,158,818✔
1366

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