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

vbpf / ebpf-verifier / 14231336081

02 Apr 2025 11:08PM UTC coverage: 87.272% (-0.9%) from 88.177%
14231336081

push

github

web-flow
Propogate ebpf_verifier_options_t to thread_local_options (#856)

Signed-off-by: Alan Jowett <alanjo@microsoft.com>

5 of 5 new or added lines in 2 files covered. (100.0%)

58 existing lines in 19 files now uncovered.

8324 of 9538 relevant lines covered (87.27%)

4881701.3 hits per line

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

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

5
#include <gsl/narrow>
6

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

14
namespace crab::domains {
15

16
static std::optional<SplitDBM::vert_id> try_at(const SplitDBM::vert_map_t& map, const variable_t v) {
4,656,925✔
17
    const auto it = map.find(v);
4,656,925✔
18
    if (it == map.end()) {
4,656,925✔
19
        return std::nullopt;
2,790,771✔
20
    }
21
    return it->second;
1,866,154✔
22
}
23

24
SplitDBM::vert_id SplitDBM::get_vert(variable_t v) {
1,244,642✔
25
    if (const auto y = try_at(vert_map, v)) {
1,244,642✔
26
        return *y;
470,831✔
27
    }
28

29
    vert_id vert(g.new_vertex());
773,811✔
30
    vert_map.emplace(v, vert);
773,811✔
31
    // Initialize
32
    assert(vert <= rev_map.size());
773,811✔
33
    if (vert < rev_map.size()) {
773,811✔
34
        potential[vert] = Weight(0);
599,471✔
35
        rev_map[vert] = v;
599,471✔
36
    } else {
37
        potential.emplace_back(0);
174,340✔
38
        rev_map.push_back(v);
174,340✔
39
    }
40
    vert_map.emplace(v, vert);
773,811✔
41

42
    assert(vert != 0);
773,811✔
43

44
    return vert;
773,811✔
45
}
46

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

61
[[maybe_unused]]
62
static bool convert_NtoW_overflow(const number_t& n, number_t& out) {
5,923,001✔
63
    out = n;
3,961,653✔
64
    return false;
5,923,001✔
65
}
66

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

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

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

83
    for (const auto& [y, n] : exp.variable_terms()) {
685,666✔
84
        Weight coeff;
345,848✔
85
        if (convert_NtoW_overflow(n, coeff)) {
345,848✔
86
            continue;
87
        }
88

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

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

105
            if (y_val.is_infinite()) {
344,956✔
106
                if (unbounded_var || coeff != Weight(1)) {
216,444✔
107
                    return;
378✔
108
                }
109
                unbounded_var = y;
452,604✔
110
            } else {
111
                Weight ymax;
236,552✔
112
                if (convert_NtoW_overflow(*y_val.number(), ymax)) {
236,552✔
113
                    continue;
114
                }
115
                residual += ymax * coeff;
473,104✔
116
                terms.emplace_back(y, ymax);
236,552✔
117
            }
236,552✔
118
        }
344,956✔
119
    }
345,848✔
120

121
    if (unbounded_var) {
339,818✔
122
        // There is exactly one unbounded variable with unit
123
        // coefficient
124
        diff_csts.emplace_back(*unbounded_var, residual);
107,632✔
125
    } else {
126
        for (const auto& [v, n] : terms) {
468,339✔
127
            diff_csts.emplace_back(v, residual - n);
472,306✔
128
        }
129
    }
130
}
340,332✔
131

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

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

158
    std::vector<std::pair<std::pair<Weight, variable_t>, Weight>> pos_terms, neg_terms;
996,765✔
159
    for (const auto& [y, n] : exp.variable_terms()) {
2,027,034✔
160
        Weight coeff;
1,443,074✔
161
        if (convert_NtoW_overflow(n, coeff)) {
1,443,074✔
162
            continue;
163
        }
164
        if (coeff > Weight(0)) {
1,443,074✔
165
            auto y_lb = this->operator[](y).lb();
992,500✔
166
            if (y_lb.is_infinite()) {
496,250✔
167
                if (unbounded_lbvar) {
434,854✔
168
                    return;
×
169
                }
170
                unbounded_lbvar = y;
434,854✔
171
                unbounded_lbcoeff = coeff;
931,104✔
172
            } else {
173
                Weight ymin;
61,396✔
174
                if (convert_NtoW_overflow(*y_lb.number(), ymin)) {
61,396✔
175
                    continue;
176
                }
177
                exp_ub -= ymin * coeff;
122,792✔
178
                pos_terms.push_back({{coeff, y}, ymin});
184,188✔
179
            }
61,396✔
180
        } else {
496,250✔
181
            auto y_ub = this->operator[](y).ub();
1,893,648✔
182
            if (y_ub.is_infinite()) {
946,824✔
183
                if (unbounded_ubvar) {
867,328✔
184
                    return;
412,805✔
185
                }
186
                unbounded_ubvar = y;
454,523✔
187
                unbounded_ubcoeff = -coeff;
909,046✔
188
            } else {
189
                Weight ymax;
79,496✔
190
                if (convert_NtoW_overflow(*y_ub.number(), ymax)) {
79,496✔
191
                    continue;
192
                }
193
                exp_ub -= ymax * coeff;
158,992✔
194
                neg_terms.push_back({{-coeff, y}, ymax});
317,984✔
195
            }
79,496✔
196
        }
946,824✔
197
    }
1,030,269✔
198

199
    if (unbounded_lbvar) {
583,960✔
200
        variable_t x{*unbounded_lbvar};
434,854✔
201
        if (unbounded_ubvar) {
434,854✔
202
            if (unbounded_lbcoeff == Weight(1) && unbounded_ubcoeff == Weight(1)) {
9,644✔
203
                csts.push_back({{x, *unbounded_ubvar}, exp_ub});
9,644✔
204
            }
205
        } else {
206
            if (unbounded_lbcoeff == Weight(1)) {
430,032✔
207
                for (const auto& [nv, k] : neg_terms) {
431,045✔
208
                    csts.push_back({{x, nv.second}, exp_ub - k});
2,026✔
209
                }
210
            }
211
            // Add bounds for x
212
            ubs.emplace_back(x, exp_ub / unbounded_lbcoeff);
860,064✔
213
        }
214
    } else {
215
        if (unbounded_ubvar) {
149,106✔
216
            variable_t y{*unbounded_ubvar};
36,896✔
217
            if (unbounded_ubcoeff == Weight(1)) {
36,896✔
218
                for (const auto& [nv, k] : pos_terms) {
38,010✔
219
                    csts.push_back({{nv.second, y}, exp_ub + k});
2,228✔
220
                }
221
            }
222
            // Add bounds for y
223
            lbs.emplace_back(y, -exp_ub / unbounded_ubcoeff);
73,792✔
224
        } else {
225
            for (const auto& [neg_nv, neg_k] : neg_terms) {
174,751✔
226
                for (const auto& [pos_nv, pos_k] : pos_terms) {
69,026✔
227
                    csts.push_back({{pos_nv.second, neg_nv.second}, exp_ub - neg_k + pos_k});
12,970✔
228
                }
229
            }
230
            for (const auto& [neg_nv, neg_k] : neg_terms) {
174,751✔
231
                lbs.emplace_back(neg_nv.second, -exp_ub / neg_nv.first + neg_k);
125,082✔
232
            }
233
            for (const auto& [pos_nv, pos_k] : pos_terms) {
171,890✔
234
                ubs.emplace_back(pos_nv.second, exp_ub / pos_nv.first + pos_k);
119,360✔
235
            }
236
        }
237
    }
238
}
996,765✔
239

240
static GraphOps::PotentialFunction index_to_call(const GraphOps::WeightVector& p) {
1,170,228✔
241
    return [&p](GraphOps::vert_id v) -> GraphOps::Weight { return p[v]; };
1,602,169,034✔
242
}
243

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

249
    for (const auto& [var, n] : lbs) {
1,089,252✔
250
        CRAB_LOG("zones-split", std::cout << var << ">=" << n << "\n");
96,588✔
251
        const vert_id vert = get_vert(var);
96,588✔
252
        if (auto w = g.lookup(vert, 0)) {
96,588✔
253
            if (*w <= -n) {
49,509✔
254
                continue;
35,317✔
255
            }
256
        }
35,317✔
257
        g.set_edge(vert, -n, 0);
61,271✔
258

259
        if (!repair_potential(vert, 0)) {
61,271✔
260
            return false;
20,161✔
261
        }
262
    }
263
    for (const auto& [var, n] : ubs) {
1,466,165✔
264
        CRAB_LOG("zones-split", std::cout << var << "<=" << n << "\n");
489,516✔
265
        const vert_id vert = get_vert(var);
489,516✔
266
        if (auto w = g.lookup(0, vert)) {
489,516✔
267
            if (*w <= n) {
44,562✔
268
                continue;
18,727✔
269
            }
270
        }
18,727✔
271
        g.set_edge(0, n, vert);
470,789✔
272
        if (!repair_potential(0, vert)) {
470,789✔
273
            return false;
20,161✔
274
        }
275
    }
276

277
    for (const auto& [diff, k] : csts) {
989,821✔
278
        CRAB_LOG("zones-split", std::cout << diff.first << "-" << diff.second << "<=" << k << "\n");
13,217✔
279

280
        const vert_id src = get_vert(diff.second);
13,217✔
281
        const vert_id dest = get_vert(diff.first);
13,217✔
282
        g.update_edge(src, k, dest);
13,217✔
283
        if (!repair_potential(src, dest)) {
13,217✔
284
            return false;
20,161✔
285
        }
286
        GraphOps::close_over_edge(g, src, dest);
13,172✔
287
    }
288
    GraphOps::apply_delta(g, GraphOps::close_after_assign(g, index_to_call(potential), 0));
976,604✔
289
    normalize();
976,604✔
290
    return true;
291
}
996,765✔
292

293
static interval_t trim_interval(const interval_t& i, const number_t& n) {
2,363✔
294
    if (i.lb() == n) {
7,089✔
295
        return interval_t{n + 1, i.ub()};
6,560✔
296
    }
297
    if (i.ub() == n) {
2,169✔
298
        return interval_t{i.lb(), n - 1};
12✔
299
    }
300
    if (i.is_top() && n == 0) {
794✔
301
        return interval_t{1, std::numeric_limits<uint64_t>::max()};
28✔
302
    }
303
    return i;
705✔
304
}
305

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

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

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

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

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

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

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

389
    // Set up a mapping from o to this.
390
    std::vector<unsigned int> vert_renaming(o.g.size(), -1);
76✔
391
    vert_renaming[0] = 0;
76✔
392
    for (const auto& [v, n] : o.vert_map) {
372✔
393
        if (o.g.succs(n).size() == 0 && o.g.preds(n).size() == 0) {
296✔
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)) {
296✔
399
            vert_renaming[n] = *y;
296✔
400
        } else {
401
            return false;
×
402
        }
403
    }
404

405
    assert(g.size() > 0);
76✔
406
    for (vert_id ox : o.g.verts()) {
254✔
407
        if (o.g.succs(ox).size() == 0) {
216✔
408
            continue;
14✔
409
        }
410

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

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

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

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

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

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

471
    for (const auto& [v, n] : vert_map) {
840,704✔
472
        if (auto y = try_at(o.vert_map, v)) {
828,848✔
473
            // Variable exists in both
474
            out_vmap.emplace(v, gsl::narrow<vert_id>(perm_x.size()));
803,028✔
475
            out_revmap.push_back(v);
803,028✔
476

477
            pot_rx.push_back(potential[n] - potential[0]);
1,606,056✔
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]);
1,606,056✔
481
            perm_inv.push_back(v);
803,028✔
482
            perm_x.push_back(n);
803,028✔
483
            perm_y.push_back(*y);
803,028✔
484
        }
485
    }
486
    size_t sz = perm_x.size();
11,856✔
487

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

494
    // Compute the deferred relations
495
    graph_t g_ix_ry;
11,856✔
496
    g_ix_ry.growTo(sz);
11,856✔
497
    SubGraph gy_excl(gy, 0);
11,856✔
498
    for (vert_id s : gy_excl.verts()) {
1,617,915✔
499
        for (vert_id d : gy_excl.succs(s)) {
2,100,484✔
500
            if (auto ws = gx.lookup(s, 0)) {
494,428✔
501
                if (auto wd = gx.lookup(0, d)) {
450,501✔
502
                    g_ix_ry.add_edge(s, *ws + *wd, d);
898,660✔
503
                }
450,501✔
504
            }
494,428✔
505
        }
506
    }
507
    // Apply the deferred relations, and re-close.
508
    bool is_closed;
11,856✔
509
    graph_t g_rx(GraphOps::meet(gx, g_ix_ry, is_closed));
11,856✔
510
    if (!is_closed) {
11,856✔
511
        GraphOps::apply_delta(g_rx, GraphOps::close_after_meet(SubGraph(g_rx, 0), index_to_call(pot_rx), gx, g_ix_ry));
11,856✔
512
    }
513

514
    graph_t g_rx_iy;
11,856✔
515
    g_rx_iy.growTo(sz);
11,856✔
516

517
    SubGraph gx_excl(gx, 0);
11,856✔
518
    for (vert_id s : gx_excl.verts()) {
1,617,915✔
519
        for (vert_id d : gx_excl.succs(s)) {
2,216,327✔
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)) {
610,271✔
523
                if (auto wd = gy.lookup(0, d)) {
564,140✔
524
                    g_rx_iy.add_edge(s, *ws + *wd, d);
1,124,502✔
525
                }
564,140✔
526
            }
610,271✔
527
        }
528
    }
529
    // Similarly, should use a SubGraph view.
530
    graph_t g_ry(GraphOps::meet(gy, g_rx_iy, is_closed));
11,856✔
531
    if (!is_closed) {
11,856✔
532
        GraphOps::apply_delta(g_ry, GraphOps::close_after_meet(SubGraph(g_ry, 0), index_to_call(pot_ry), gy, g_rx_iy));
11,856✔
533
    }
534

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

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

546
    for (vert_id v : gx_excl.verts()) {
1,617,915✔
547
        if (auto wx = gx.lookup(0, v)) {
803,028✔
548
            if (auto wy = gy.lookup(0, v)) {
745,473✔
549
                if (*wx < *wy) {
741,671✔
550
                    ub_up.push_back(v);
11,030✔
551
                }
552
                if (*wy < *wx) {
741,671✔
553
                    ub_down.push_back(v);
22,985✔
554
                }
555
            }
745,473✔
UNCOV
556
        }
×
557
        if (auto wx = gx.lookup(v, 0)) {
803,028✔
558
            if (auto wy = gy.lookup(v, 0)) {
746,713✔
559
                if (*wx < *wy) {
743,004✔
560
                    lb_down.push_back(v);
9,021✔
561
                }
562
                if (*wy < *wx) {
743,004✔
563
                    lb_up.push_back(v);
21,739✔
564
                }
565
            }
746,713✔
566
        }
803,028✔
567
    }
568

569
    for (vert_id s : lb_up) {
33,595✔
570
        Weight dx_s = gx.edge_val(s, 0);
21,739✔
571
        Weight dy_s = gy.edge_val(s, 0);
21,739✔
572
        for (vert_id d : ub_up) {
51,766✔
573
            if (s == d) {
30,027✔
574
                continue;
6,684✔
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);
46,686✔
578
        }
579
    }
21,739✔
580

581
    for (vert_id s : lb_down) {
20,877✔
582
        Weight dx_s = gx.edge_val(s, 0);
9,021✔
583
        Weight dy_s = gy.edge_val(s, 0);
9,021✔
584
        for (vert_id d : ub_down) {
36,507✔
585
            if (s == d) {
27,486✔
586
                continue;
6,624✔
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);
41,724✔
590
        }
591
    }
9,021✔
592

593
    // Conjecture: join_g remains closed.
594

595
    // Now garbage collect any unused vertices
596
    for (vert_id v : join_g.verts()) {
826,740✔
597
        if (v == 0) {
814,884✔
598
            continue;
11,856✔
599
        }
600
        if (join_g.succs(v).size() == 0 && join_g.preds(v).size() == 0) {
803,028✔
601
            join_g.forget(v);
16,032✔
602
            if (out_revmap[v]) {
16,032✔
603
                out_vmap.erase(*(out_revmap[v]));
16,032✔
604
                out_revmap[v] = std::nullopt;
830,916✔
605
            }
606
        }
607
    }
608

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

614
    return res;
11,856✔
615
}
35,820✔
616

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

621
    CRAB_LOG("zones-split", std::cout << "Before widening:\n"
42✔
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());
42✔
629
    std::vector<Weight> widen_pot = {0};
42✔
630
    std::vector<vert_id> perm_x = {0};
42✔
631
    std::vector<vert_id> perm_y = {0};
42✔
632
    vert_map_t out_vmap;
42✔
633
    rev_map_t out_revmap = {std::nullopt};
42✔
634
    for (const auto& [v, n] : vert_map) {
1,331✔
635
        if (auto y = try_at(o.vert_map, v)) {
1,289✔
636
            // Variable exists in both
637
            out_vmap.emplace(v, gsl::narrow<vert_id>(perm_x.size()));
173✔
638
            out_revmap.push_back(v);
173✔
639

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

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

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

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

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

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

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

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

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

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

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

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

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

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

724
    // Compute the syntactic meet of the permuted graphs.
725
    bool is_closed{};
19✔
726
    graph_t meet_g(GraphOps::meet(gx, gy, is_closed));
19✔
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)) {
19✔
732
        // Potentials cannot be selected -- state is infeasible.
733
        return {};
×
734
    }
735

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

740
        // Recover updated LBs and UBs.<
741

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

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

758
bool SplitDBM::add_constraint(const linear_constraint_t& cst) {
985,877✔
759
    CrabStats::count("SplitDBM.count.add_constraints");
985,877✔
760
    ScopedCrabStats __st__("SplitDBM.add_constraints");
985,877✔
761

762
    if (cst.is_tautology()) {
985,877✔
763
        return true;
764
    }
765

766
    // g.check_adjs();
767

768
    if (cst.is_contradiction()) {
985,805✔
769
        return false;
770
    }
771

772
    switch (cst.kind()) {
985,471✔
773
    case constraint_kind_t::LESS_THAN_OR_EQUALS_ZERO: {
942,943✔
774
        if (!add_linear_leq(cst.expression())) {
942,943✔
775
            return false;
776
        }
777
        //  g.check_adjs();
778
        CRAB_LOG("zones-split", std::cout << "--- " << cst << "\n" << *this << "\n");
923,015✔
779
        break;
780
    }
781
    case constraint_kind_t::LESS_THAN_ZERO: {
26,003✔
782
        // We try to convert a strict to non-strict.
783
        // e < 0 --> e <= -1
784
        const auto nc = linear_constraint_t(cst.expression().plus(1), constraint_kind_t::LESS_THAN_OR_EQUALS_ZERO);
52,006✔
785
        if (!add_linear_leq(nc.expression())) {
26,003✔
786
            return false;
188✔
787
        }
788
        CRAB_LOG("zones-split", std::cout << "--- " << cst << "\n" << *this << "\n");
25,815✔
789
        break;
25,815✔
790
    }
26,003✔
791
    case constraint_kind_t::EQUALS_ZERO: {
13,920✔
792
        const linear_expression_t& exp = cst.expression();
13,920✔
793
        if (!add_linear_leq(exp) || !add_linear_leq(exp.negate())) {
41,694✔
794
            CRAB_LOG("zones-split", std::cout << " ~~> _|_" << "\n");
45✔
795
            return false;
45✔
796
        }
797
        // g.check_adjs();
798
        CRAB_LOG("zones-split", std::cout << "--- " << cst << "\n" << *this << "\n");
13,875✔
799
        break;
800
    }
801
    case constraint_kind_t::NOT_ZERO: {
2,605✔
802
        // XXX: similar precision as the interval domain
803
        const linear_expression_t& e = cst.expression();
2,605✔
804
        for (const auto& [variable, coefficient] : e.variable_terms()) {
4,037✔
805
            interval_t i = compute_residual(e, variable) / interval_t(coefficient);
6,106✔
806
            if (auto k = i.singleton()) {
3,053✔
807
                if (!add_univar_disequation(variable, *k)) {
2,363✔
808
                    return false;
1,621✔
809
                }
810
            }
3,053✔
811
        }
3,053✔
812
    } break;
813
    }
814

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

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

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

828
    interval_t value_interval = eval_interval(e);
305,655✔
829

830
    std::optional<Weight> lb_w, ub_w;
305,655✔
831
    if (value_interval.lb().is_finite()) {
611,310✔
832
        Weight tmp;
251,588✔
833
        if (convert_NtoW_overflow(-*value_interval.lb().number(), tmp)) {
1,006,352✔
834
            havoc(lhs);
835
            CRAB_LOG("zones-split", std::cout << "---" << lhs << ":=" << e << "\n" << *this << "\n");
836
            normalize();
837
            return;
838
        }
839
        lb_w = tmp;
251,588✔
840
    }
251,588✔
841
    if (value_interval.ub().is_finite()) {
611,310✔
842
        Weight tmp;
251,576✔
843
        if (convert_NtoW_overflow(*value_interval.ub().number(), tmp)) {
754,728✔
844
            havoc(lhs);
845
            CRAB_LOG("zones-split", std::cout << "---" << lhs << ":=" << e << "\n" << *this << "\n");
846
            normalize();
847
            return;
848
        }
849
        ub_w = tmp;
251,576✔
850
    }
251,576✔
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()) {
305,655✔
856
        set(lhs, value_interval);
135,489✔
857
        normalize();
135,489✔
858
        return;
859
    }
860

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

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

886
    {
169,870✔
887
        GraphOps::edge_vector delta;
169,870✔
888
        for (const auto& [var, n] : diffs_lb) {
341,763✔
889
            delta.emplace_back(vert, get_vert(var), -n);
171,893✔
890
        }
891

892
        for (const auto& [var, n] : diffs_ub) {
341,762✔
893
            delta.emplace_back(get_vert(var), vert, n);
171,892✔
894
        }
895

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

901
    if (lb_w) {
169,870✔
902
        g.update_edge(vert, *lb_w, 0);
232,120✔
903
    }
904
    if (ub_w) {
169,870✔
905
        g.update_edge(0, *ub_w, vert);
232,096✔
906
    }
907
    // Clear the old x vertex
908
    havoc(lhs);
169,870✔
909
    vert_map.emplace(lhs, vert);
169,870✔
910

911
    normalize();
169,870✔
912
    CRAB_LOG("zones-split", std::cout << "---" << lhs << ":=" << e << "\n" << *this << "\n");
169,870✔
913
}
611,606✔
914

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

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

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

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

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

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

941
void SplitDBM::normalize() {
3,098,959✔
942
    CrabStats::count("SplitDBM.count.normalize");
3,098,959✔
943
    ScopedCrabStats __st__("SplitDBM.normalize");
3,098,959✔
944

945
    // dbm_canonical(_dbm);
946
    // Always maintained in normal form, except for widening
947
    if (unstable.empty()) {
3,098,959✔
948
        return;
3,098,936✔
949
    }
950

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

959
    unstable.clear();
23✔
960
}
3,098,959✔
961

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

967
    havoc(x);
560,444✔
968

969
    if (intv.is_top()) {
560,444✔
970
        return;
272,806✔
971
    }
972

973
    const vert_id v = get_vert(x);
287,638✔
974
    if (intv.ub().is_finite()) {
575,276✔
975
        Weight ub;
287,635✔
976
        if (convert_NtoW_overflow(*intv.ub().number(), ub)) {
862,905✔
977
            normalize();
978
            return;
979
        }
980
        potential[v] = potential[0] + ub;
287,635✔
981
        g.set_edge(0, ub, v);
575,270✔
982
    }
287,635✔
983
    if (intv.lb().is_finite()) {
575,276✔
984
        Weight lb;
287,560✔
985
        if (convert_NtoW_overflow(*intv.lb().number(), lb)) {
862,680✔
986
            normalize();
987
            return;
988
        }
989
        potential[v] = potential[0] + lb;
287,560✔
990
        g.set_edge(v, -lb, 0);
575,120✔
991
    }
287,560✔
992
    normalize();
287,638✔
993
}
560,444✔
994

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

1000
    switch (op) {
3,029✔
1001
    case arith_binop_t::ADD: assign(x, linear_expression_t(y).plus(z)); break;
2,487✔
1002
    case arith_binop_t::SUB: assign(x, linear_expression_t(y).subtract(z)); break;
398✔
1003
    // For the rest of operations, we fall back on intervals.
1004
    case arith_binop_t::MUL: set(x, get_interval(y, finite_width) * get_interval(z, finite_width)); break;
144✔
1005
    case arith_binop_t::SDIV: set(x, get_interval(y, finite_width).sdiv(get_interval(z, finite_width))); break;
64✔
1006
    case arith_binop_t::UDIV: set(x, get_interval(y, finite_width).udiv(get_interval(z, finite_width))); break;
208✔
1007
    case arith_binop_t::SREM: set(x, get_interval(y, finite_width).srem(get_interval(z, finite_width))); break;
92✔
1008
    case arith_binop_t::UREM: set(x, get_interval(y, finite_width).urem(get_interval(z, finite_width))); break;
68✔
1009
    default: CRAB_ERROR("DBM: unreachable");
×
1010
    }
1011
    normalize();
3,029✔
1012
}
3,029✔
1013

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

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

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

1043
    switch (op) {
24,468✔
1044
    case arith_binop_t::ADD: assign(x, linear_expression_t(y).plus(k)); break;
22,696✔
1045
    case arith_binop_t::SUB: assign(x, linear_expression_t(y).subtract(k)); break;
519✔
1046
    case arith_binop_t::MUL:
124✔
1047
        assign(x, linear_expression_t(k, y));
124✔
1048
        break;
124✔
1049
        // For the rest of operations, we fall back on intervals.
1050
    case arith_binop_t::SDIV:
9✔
1051
        set(x, get_interval(y, finite_width).sdiv(interval_t{read_imm_for_sdiv_or_smod(k, finite_width)}));
27✔
1052
        break;
9✔
1053
    case arith_binop_t::UDIV:
1,091✔
1054
        set(x, get_interval(y, finite_width).udiv(interval_t{read_imm_for_udiv_or_umod(k, finite_width)}));
3,273✔
1055
        break;
1,091✔
1056
    case arith_binop_t::SREM:
17✔
1057
        set(x, get_interval(y, finite_width).srem(interval_t{read_imm_for_sdiv_or_smod(k, finite_width)}));
51✔
1058
        break;
17✔
1059
    case arith_binop_t::UREM:
12✔
1060
        set(x, get_interval(y, finite_width).urem(interval_t{read_imm_for_udiv_or_umod(k, finite_width)}));
36✔
1061
        break;
12✔
1062
    default: CRAB_ERROR("DBM: unreachable");
×
1063
    }
1064
    normalize();
24,468✔
1065
}
24,468✔
1066

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

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

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

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

1099
    switch (op) {
36,949✔
1100
    case bitwise_binop_t::AND: xi = yi.bitwise_and(zi); break;
69,212✔
1101
    case bitwise_binop_t::OR: xi = yi.bitwise_or(zi); break;
2,650✔
1102
    case bitwise_binop_t::XOR: xi = yi.bitwise_xor(zi); break;
368✔
1103
    case bitwise_binop_t::SHL: xi = yi.shl(zi); break;
1,668✔
1104
    case bitwise_binop_t::LSHR: xi = yi.lshr(zi); break;
×
1105
    case bitwise_binop_t::ASHR: xi = yi.ashr(zi); break;
×
1106
    default: CRAB_ERROR("DBM: unreachable");
×
1107
    }
1108
    set(x, xi);
36,949✔
1109
    normalize();
36,949✔
1110
}
110,847✔
1111

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

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

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

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

1148
    std::map<variable_t, variable_t> equivalence_classes;
574✔
1149
    std::set<std::tuple<variable_t, variable_t, Weight>> diff_csts;
574✔
1150
    for (const vert_id s : g_excl.verts()) {
3,544✔
1151
        const variable_t vs = *rev_map.at(s);
2,970✔
1152
        variable_t least = vs;
2,970✔
1153
        for (const vert_id d : g_excl.succs(s)) {
6,999✔
1154
            const variable_t vd = *rev_map.at(d);
870✔
1155
            const Weight w = g_excl.edge_val(s, d);
870✔
1156
            if (w == 0) {
870✔
1157
                least = std::min(least, vd, variable_t::printing_order);
1,428✔
1158
            } else {
1159
                diff_csts.emplace(vd, vs, w);
156✔
1160
            }
1161
        }
870✔
1162
        equivalence_classes.insert_or_assign(vs, least);
2,970✔
1163
    }
1164

1165
    std::set<variable_t> representatives;
574✔
1166
    std::set<std::string> result;
574✔
1167
    for (const auto [vs, least] : equivalence_classes) {
3,544✔
1168
        if (vs == least) {
2,970✔
1169
            representatives.insert(least);
2,650✔
1170
        } else {
1171
            result.insert(vs.name() + "=" + least.name());
960✔
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) {
730✔
1179
        if (!representatives.contains(vd) || !representatives.contains(vs)) {
156✔
1180
            continue;
89✔
1181
        }
1182
        auto dual = to_string(vs, vd, -w, false);
67✔
1183
        if (result.contains(dual)) {
67✔
1184
            assert(w != 0);
4✔
1185
            result.erase(dual);
4✔
1186
            result.insert(to_string(vd, vs, w, true));
8✔
1187
        } else {
1188
            result.insert(to_string(vd, vs, w, false));
126✔
1189
        }
1190
    }
67✔
1191

1192
    // Intervals
1193
    for (vert_id v : g_excl.verts()) {
3,544✔
1194
        const auto pvar = this->rev_map[v];
2,970✔
1195
        if (!pvar || !representatives.contains(*pvar)) {
2,970✔
1196
            continue;
433✔
1197
        }
1198
        if (!this->g.elem(0, v) && !this->g.elem(v, 0)) {
2,650✔
1199
            continue;
78✔
1200
        }
1201
        interval_t v_out{this->g.elem(v, 0) ? -number_t(this->g.edge_val(v, 0)) : extended_number::minus_infinity(),
10,284✔
1202
                         this->g.elem(0, v) ? number_t(this->g.edge_val(0, v)) : extended_number::plus_infinity()};
10,256✔
1203
        assert(!v_out.is_bottom());
2,572✔
1204

1205
        variable_t variable = *pvar;
2,572✔
1206

1207
        std::stringstream elem;
2,572✔
1208
        elem << variable;
2,572✔
1209
        if (variable.is_type()) {
2,572✔
1210
            auto [lb, ub] = v_out.bound(T_UNINIT, T_MAX);
974✔
1211
            if (lb == ub) {
974✔
1212
                if (variable.is_in_stack() && lb == T_NUM) {
961✔
1213
                    // no need to show this
1214
                    continue;
35✔
1215
                }
1216
                elem << "=" << lb;
926✔
1217
            } else {
1218
                elem << " in " << typeset_to_string(iterate_types(lb, ub));
26✔
1219
            }
1220
        } else {
1221
            elem << "=";
1,598✔
1222
            if (v_out.is_singleton()) {
1,598✔
1223
                elem << v_out.lb();
3,564✔
1224
            } else {
1225
                elem << v_out;
410✔
1226
            }
1227
        }
1228
        result.insert(elem.str());
2,537✔
1229
    }
5,144✔
1230

1231
    return string_invariant{result};
574✔
1232
}
574✔
1233

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

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

1251
interval_t SplitDBM::compute_residual(const linear_expression_t& e, const variable_t pivot) const {
3,053✔
1252
    interval_t residual(-e.constant_term());
3,053✔
1253
    for (const auto& [variable, coefficient] : e.variable_terms()) {
7,034✔
1254
        if (variable != pivot) {
3,981✔
1255
            residual = residual - (interval_t(coefficient) * this->operator[](variable));
4,640✔
1256
        }
1257
    }
1258
    return residual;
3,053✔
1259
}
×
1260

1261
SplitDBM::Weight SplitDBM::pot_value(const variable_t v) const {
172,426✔
1262
    if (const auto y = try_at(vert_map, v)) {
172,426✔
1263
        return potential[*y];
144,039✔
1264
    }
1265
    return {0};
28,387✔
1266
}
1267

1268
interval_t SplitDBM::eval_interval(const linear_expression_t& e) const {
15,711,083✔
1269
    using namespace crab::interval_operators;
15,711,083✔
1270
    interval_t r{e.constant_term()};
15,711,083✔
1271
    for (const auto& [variable, coefficient] : e.variable_terms()) {
30,958,146✔
1272
        r += coefficient * operator[](variable);
45,741,189✔
1273
    }
1274
    return r;
15,711,083✔
1275
}
×
1276

1277
bool SplitDBM::intersect(const linear_constraint_t& cst) const {
862,020✔
1278
    if (cst.is_contradiction()) {
862,020✔
1279
        return false;
1280
    }
1281
    if (is_top() || cst.is_tautology()) {
862,020✔
1282
        return true;
73✔
1283
    }
1284
    return intersect_aux(cst);
861,947✔
1285
}
1286

1287
bool SplitDBM::entail(const linear_constraint_t& rhs) const {
102,513✔
1288
    if (rhs.is_tautology()) {
102,513✔
1289
        return true;
1290
    }
1291
    if (rhs.is_contradiction()) {
102,435✔
1292
        return false;
1293
    }
1294
    const interval_t interval = eval_interval(rhs.expression());
102,435✔
1295
    switch (rhs.kind()) {
102,435✔
1296
    case constraint_kind_t::EQUALS_ZERO:
50,156✔
1297
        if (interval.singleton() == std::optional(number_t(0))) {
50,156✔
1298
            return true;
1299
        }
1300
        break;
1301
    case constraint_kind_t::LESS_THAN_OR_EQUALS_ZERO:
46,509✔
1302
        if (interval.ub() <= number_t(0)) {
93,018✔
1303
            return true;
1304
        }
1305
        break;
1306
    case constraint_kind_t::LESS_THAN_ZERO:
5,063✔
1307
        if (interval.ub() < number_t(0)) {
10,126✔
1308
            return true;
1309
        }
1310
        break;
1311
    case constraint_kind_t::NOT_ZERO:
707✔
1312
        if (interval.ub() < number_t(0) || interval.lb() > number_t(0)) {
4,910✔
1313
            return true;
1314
        }
1315
        break;
1316
    }
1317
    // TODO: copy the implementation from crab
1318
    //       https://github.com/seahorn/crab/blob/master/include/crab/domains/split_dbm.hpp
1319
    if (rhs.kind() == constraint_kind_t::EQUALS_ZERO) {
7,125✔
1320
        // try to convert the equality into inequalities so when it's
1321
        // negated we do not have disequalities.
1322
        return entail_aux(linear_constraint_t(rhs.expression(), constraint_kind_t::LESS_THAN_OR_EQUALS_ZERO)) &&
1,786✔
1323
               entail_aux(linear_constraint_t(rhs.expression().negate(), constraint_kind_t::LESS_THAN_OR_EQUALS_ZERO));
893✔
1324
    } else {
1325
        return entail_aux(rhs);
6,232✔
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
}
102,435✔
1336

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

1344
static interval_t get_interval(const SplitDBM::vert_map_t& m, const SplitDBM::graph_t& r, const variable_t x,
17,084,828✔
1345
                               const int finite_width) {
1346
    const auto it = m.find(x);
17,084,828✔
1347
    if (it == m.end()) {
17,084,828✔
1348
        return interval_t::top();
10,016,045✔
1349
    }
1350
    const SplitDBM::vert_id v = it->second;
7,068,783✔
1351
    extended_number lb = extended_number::minus_infinity();
7,068,783✔
1352
    extended_number ub = extended_number::plus_infinity();
7,068,783✔
1353
    if (r.elem(v, 0)) {
7,068,783✔
1354
        lb = x.is_unsigned() ? (-number_t(r.edge_val(v, 0))).zero_extend(finite_width)
20,762,232✔
1355
                             : (-number_t(r.edge_val(v, 0))).sign_extend(finite_width);
33,600,094✔
1356
    }
1357
    if (r.elem(0, v)) {
7,068,783✔
1358
        ub = x.is_unsigned() ? number_t(r.edge_val(0, v)).zero_extend(finite_width)
13,828,296✔
1359
                             : number_t(r.edge_val(0, v)).sign_extend(finite_width);
20,406,231✔
1360
    }
1361
    return {lb, ub};
7,068,783✔
1362
}
7,068,783✔
1363

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

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

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

© 2025 Coveralls, Inc