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

Alan-Jowett / ebpf-verifier / 19036509529

02 Nov 2025 09:22PM UTC coverage: 86.936% (-0.5%) from 87.448%
19036509529

push

github

elazarg
Bump external/libbtf from `b674148` to `04281ee`

Bumps [external/libbtf](https://github.com/Alan-Jowett/libbtf) from `b674148` to `04281ee`.
- [Release notes](https://github.com/Alan-Jowett/libbtf/releases)
- [Commits](https://github.com/Alan-Jowett/libbtf/compare/b6741487e...<a class=hub.com/Alan-Jowett/ebpf-verifier/commit/04281ee7a91595911807897b4ca2e7483cf97497">04281ee7a)

---
updated-dependencies:
- dependency-name: external/libbtf
  dependency-version: '04281ee7a91595911807897b4ca2e7483cf97497'
  dependency-type: direct:production
...

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

9044 of 10403 relevant lines covered (86.94%)

3989157.54 hits per line

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

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

6
#include <gsl/narrow>
7

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

16
namespace prevail {
17

18
std::optional<SplitDBM::VertId> SplitDBM::get_vertid(const Variable x) const {
32,782,091✔
19
    const auto it = vert_map.find(x);
32,782,091✔
20
    if (it == vert_map.end()) {
49,180,616✔
21
        return {};
15,025,027✔
22
    }
23
    return it->second;
17,757,064✔
24
}
25

26
Bound SplitDBM::get_lb(const std::optional<VertId>& v) const {
32,595,915✔
27
    return v && g.elem(*v, 0) ? -Number(g.edge_val(*v, 0)) : MINUS_INFINITY;
65,191,830✔
28
}
29

30
Bound SplitDBM::get_ub(const std::optional<VertId>& v) const {
31,822,275✔
31
    return v && g.elem(0, *v) ? Number(g.edge_val(0, *v)) : PLUS_INFINITY;
47,740,890✔
32
}
33

34
Bound SplitDBM::get_lb(const Variable x) const { return get_lb(get_vertid(x)); }
959,816✔
35

36
Bound SplitDBM::get_ub(const Variable x) const { return get_ub(get_vertid(x)); }
186,176✔
37

38
Interval SplitDBM::get_interval(const Variable x) const {
31,636,099✔
39
    const auto& v = get_vertid(x);
31,636,099✔
40
    return {get_lb(v), get_ub(v)};
47,446,671✔
41
}
42

43
static std::optional<SplitDBM::VertId> try_at(const SplitDBM::VertMap& map, const Variable v) {
7,601,656✔
44
    const auto it = map.find(v);
3,800,824✔
45
    if (it == map.end()) {
7,601,656✔
46
        return std::nullopt;
2,864,496✔
47
    }
48
    return it->second;
4,737,160✔
49
}
50

51
SplitDBM::VertId SplitDBM::get_vert(Variable v) {
2,344,308✔
52
    if (const auto y = try_at(vert_map, v)) {
2,344,308✔
53
        return *y;
1,226,900✔
54
    }
55

56
    VertId vert(g.new_vertex());
1,117,408✔
57
    vert_map.emplace(v, vert);
1,117,408✔
58
    // Initialize
59
    assert(vert <= rev_map.size());
1,117,408✔
60
    if (vert < rev_map.size()) {
1,117,408✔
61
        potential[vert] = Weight(0);
476,194✔
62
        rev_map[vert] = v;
476,194✔
63
    } else {
64
        potential.emplace_back(0);
641,214✔
65
        rev_map.push_back(v);
641,214✔
66
    }
67
    vert_map.emplace(v, vert);
1,117,408✔
68

69
    assert(vert != 0);
1,117,408✔
70

71
    return vert;
1,117,408✔
72
}
73

74
/**
75
 * Helper to translate from Number to DBM Weight (graph weights).  Number
76
 * used to be the template parameter of the DBM-based abstract domain to
77
 * represent a number. Number might not fit into Weight type.
78
 **/
79
[[maybe_unused]]
80
static bool convert_NtoW_overflow(const Number& n, SafeI64& out) {
81
    if (n.fits<int64_t>()) {
82
        out = n;
83
        return false;
84
    }
85
    return true;
86
}
87

88
[[maybe_unused]]
89
static bool convert_NtoW_overflow(const Number& n, Number& out) {
8,319,898✔
90
    out = n;
7,238,288✔
91
    return false;
8,319,898✔
92
}
93

94
void SplitDBM::diffcsts_of_assign(const LinearExpression& exp, std::vector<std::pair<Variable, Weight>>& lb,
183,042✔
95
                                  std::vector<std::pair<Variable, Weight>>& ub) const {
96
    diffcsts_of_assign(exp, true, ub);
183,042✔
97
    diffcsts_of_assign(exp, false, lb);
183,042✔
98
}
183,042✔
99

100
void SplitDBM::diffcsts_of_assign(const LinearExpression& exp,
366,084✔
101
                                  /* if true then process the upper
102
                                     bounds, else the lower bounds */
103
                                  bool extract_upper_bounds,
104
                                  /* foreach {v, k} \in diff_csts we have
105
                                     the difference constraint v - k <= k */
106
                                  std::vector<std::pair<Variable, Weight>>& diff_csts) const {
107

108
    std::optional<Variable> unbounded_var;
366,084✔
109
    std::vector<std::pair<Variable, Weight>> terms;
366,084✔
110

111
    Weight residual;
366,084✔
112
    if (convert_NtoW_overflow(exp.constant_term(), residual)) {
366,084✔
113
        return;
114
    }
115

116
    for (const auto& [y, n] : exp.variable_terms()) {
737,348✔
117
        Weight coeff;
372,352✔
118
        if (convert_NtoW_overflow(n, coeff)) {
372,352✔
119
            continue;
120
        }
121

122
        if (coeff < Weight(0)) {
372,352✔
123
            // Can't do anything with negative coefficients.
124
            auto y_val = extract_upper_bounds ? get_lb(y) : get_ub(y);
1,416✔
125

126
            if (y_val.is_infinite()) {
1,416✔
127
                return;
168✔
128
            }
129
            Weight ymax;
1,248✔
130
            if (convert_NtoW_overflow(*y_val.number(), ymax)) {
1,248✔
131
                continue;
132
            }
133
            // was before the condition
134
            residual += ymax * coeff;
1,872✔
135
        } else {
1,416✔
136
            auto y_val = extract_upper_bounds ? get_ub(y) : get_lb(y);
370,936✔
137

138
            if (y_val.is_infinite()) {
370,936✔
139
                if (unbounded_var || coeff != Weight(1)) {
207,404✔
140
                    return;
920✔
141
                }
142
                unbounded_var = y;
322,664✔
143
            } else {
144
                Weight ymax;
232,360✔
145
                if (convert_NtoW_overflow(*y_val.number(), ymax)) {
232,360✔
146
                    continue;
147
                }
148
                residual += ymax * coeff;
348,540✔
149
                terms.emplace_back(y, ymax);
232,360✔
150
            }
232,360✔
151
        }
370,936✔
152
    }
372,352✔
153

154
    if (unbounded_var) {
364,996✔
155
        // There is exactly one unbounded variable with unit
156
        // coefficient
157
        diff_csts.emplace_back(*unbounded_var, residual);
136,676✔
158
    } else {
159
        for (const auto& [v, n] : terms) {
459,938✔
160
            diff_csts.emplace_back(v, residual - n);
347,427✔
161
        }
162
    }
163
}
366,628✔
164

165
void SplitDBM::diffcsts_of_lin_leq(const LinearExpression& exp,
1,550,706✔
166
                                   /* difference contraints */
167
                                   std::vector<diffcst_t>& csts,
168
                                   /* x >= lb for each {x,lb} in lbs */
169
                                   std::vector<std::pair<Variable, Weight>>& lbs,
170
                                   /* x <= ub for each {x,ub} in ubs */
171
                                   std::vector<std::pair<Variable, Weight>>& ubs) const {
172
    Weight exp_ub;
1,550,706✔
173
    if (convert_NtoW_overflow(exp.constant_term(), exp_ub)) {
1,550,706✔
174
        return;
175
    }
176
    exp_ub = -exp_ub;
2,326,057✔
177

178
    // temporary hack
179
    Weight _tmp;
1,550,706✔
180
    if (convert_NtoW_overflow(exp.constant_term() - 1, _tmp)) {
2,326,057✔
181
        // We don't like MIN either because the code will compute
182
        // minus MIN, and it will silently overflow.
183
        return;
184
    }
185

186
    Weight unbounded_lbcoeff;
1,550,706✔
187
    Weight unbounded_ubcoeff;
1,550,706✔
188
    std::optional<Variable> unbounded_lbvar;
1,550,706✔
189
    std::optional<Variable> unbounded_ubvar;
1,550,706✔
190

191
    std::vector<std::pair<std::pair<Weight, Variable>, Weight>> pos_terms, neg_terms;
1,550,706✔
192
    for (const auto& [y, n] : exp.variable_terms()) {
3,152,580✔
193
        Weight coeff;
1,605,744✔
194
        if (convert_NtoW_overflow(n, coeff)) {
1,605,744✔
195
            continue;
196
        }
197
        if (coeff > Weight(0)) {
1,605,744✔
198
            auto y_lb = get_lb(y);
773,640✔
199
            if (y_lb.is_infinite()) {
773,640✔
200
                if (unbounded_lbvar) {
651,856✔
201
                    return;
×
202
                }
203
                unbounded_lbvar = y;
651,856✔
204
                unbounded_lbcoeff = coeff;
1,038,674✔
205
            } else {
206
                Weight ymin;
121,784✔
207
                if (convert_NtoW_overflow(*y_lb.number(), ymin)) {
121,784✔
208
                    continue;
209
                }
210
                exp_ub -= ymin * coeff;
182,674✔
211
                pos_terms.push_back({{coeff, y}, ymin});
243,564✔
212
            }
121,784✔
213
        } else {
773,640✔
214
            auto y_ub = get_interval(y).ub();
1,248,154✔
215
            if (y_ub.is_infinite()) {
832,104✔
216
                if (unbounded_ubvar) {
75,152✔
217
                    return;
3,870✔
218
                }
219
                unbounded_ubvar = y;
71,282✔
220
                unbounded_ubcoeff = -coeff;
106,923✔
221
            } else {
222
                Weight ymax;
756,952✔
223
                if (convert_NtoW_overflow(*y_ub.number(), ymax)) {
756,952✔
224
                    continue;
225
                }
226
                exp_ub -= ymax * coeff;
1,135,426✔
227
                neg_terms.push_back({{-coeff, y}, ymax});
1,892,374✔
228
            }
756,952✔
229
        }
832,104✔
230
    }
1,603,809✔
231

232
    if (unbounded_lbvar) {
1,546,836✔
233
        Variable x{*unbounded_lbvar};
651,856✔
234
        if (unbounded_ubvar) {
651,856✔
235
            if (unbounded_lbcoeff == Weight(1) && unbounded_ubcoeff == Weight(1)) {
13,950✔
236
                csts.push_back({{x, *unbounded_ubvar}, exp_ub});
13,950✔
237
            }
238
        } else {
239
            if (unbounded_lbcoeff == Weight(1)) {
642,556✔
240
                for (const auto& [nv, k] : neg_terms) {
643,236✔
241
                    csts.push_back({{x, nv.second}, exp_ub - k});
1,020✔
242
                }
243
            }
244
            // Add bounds for x
245
            ubs.emplace_back(x, exp_ub / unbounded_lbcoeff);
963,834✔
246
        }
247
    } else {
248
        if (unbounded_ubvar) {
894,980✔
249
            Variable y{*unbounded_ubvar};
58,112✔
250
            if (unbounded_ubcoeff == Weight(1)) {
58,112✔
251
                for (const auto& [nv, k] : pos_terms) {
60,350✔
252
                    csts.push_back({{nv.second, y}, exp_ub + k});
3,357✔
253
                }
254
            }
255
            // Add bounds for y
256
            lbs.emplace_back(y, -exp_ub / unbounded_ubcoeff);
87,168✔
257
        } else {
258
            for (const auto& [neg_nv, neg_k] : neg_terms) {
1,568,792✔
259
                for (const auto& [pos_nv, pos_k] : pos_terms) {
744,684✔
260
                    csts.push_back({{pos_nv.second, neg_nv.second}, exp_ub - neg_k + pos_k});
19,138✔
261
                }
262
            }
263
            for (const auto& [neg_nv, neg_k] : neg_terms) {
1,568,792✔
264
                lbs.emplace_back(neg_nv.second, -exp_ub / neg_nv.first + neg_k);
1,097,884✔
265
            }
266
            for (const auto& [pos_nv, pos_k] : pos_terms) {
956,414✔
267
                ubs.emplace_back(pos_nv.second, exp_ub / pos_nv.first + pos_k);
179,317✔
268
            }
269
        }
270
    }
271
}
1,560,381✔
272

273
static GraphOps::PotentialFunction index_to_call(const GraphOps::WeightVector& p) {
1,901,182✔
274
    return [&p](GraphOps::VertId v) -> GraphOps::Weight { return p[v]; };
706,685,231✔
275
}
276

277
bool SplitDBM::add_linear_leq(const LinearExpression& exp) {
1,550,706✔
278
    std::vector<std::pair<Variable, Weight>> lbs, ubs;
1,550,706✔
279
    std::vector<diffcst_t> csts;
1,550,706✔
280
    diffcsts_of_lin_leq(exp, csts, lbs, ubs);
1,550,706✔
281

282
    for (const auto& [var, n] : lbs) {
2,334,176✔
283
        CRAB_LOG("zones-split", std::cout << var << ">=" << n << "\n");
788,966✔
284
        const VertId vert = get_vert(var);
788,966✔
285
        if (const auto w = g.lookup(vert, 0)) {
788,966✔
286
            if (*w <= -n) {
106,054✔
287
                continue;
83,996✔
288
            }
289
        }
290
        g.set_edge(vert, -n, 0);
704,970✔
291

292
        if (!repair_potential(vert, 0)) {
704,970✔
293
            return false;
17,723✔
294
        }
295
    }
296
    for (const auto& [var, n] : ubs) {
2,282,918✔
297
        CRAB_LOG("zones-split", std::cout << var << "<=" << n << "\n");
761,704✔
298
        const VertId vert = get_vert(var);
761,704✔
299
        if (const auto w = g.lookup(0, vert)) {
761,704✔
300
            if (*w <= n) {
99,644✔
301
                continue;
59,672✔
302
            }
303
        }
304
        g.set_edge(0, n, vert);
702,032✔
305
        if (!repair_potential(0, vert)) {
702,032✔
306
            return false;
26,975✔
307
        }
308
    }
309

310
    for (const auto& [diff, k] : csts) {
1,545,290✔
311
        CRAB_LOG("zones-split", std::cout << diff.first << "-" << diff.second << "<=" << k << "\n");
24,538✔
312

313
        const VertId src = get_vert(diff.second);
24,538✔
314
        const VertId dest = get_vert(diff.first);
24,538✔
315
        g.update_edge(src, k, dest);
24,538✔
316
        if (!repair_potential(src, dest)) {
24,538✔
317
            return false;
15,206✔
318
        }
319
        GraphOps::close_over_edge(g, src, dest);
24,076✔
320
    }
321
    GraphOps::apply_delta(g, GraphOps::close_after_assign(g, index_to_call(potential), 0));
1,520,752✔
322
    normalize();
1,520,752✔
323
    return true;
760,376✔
324
}
1,550,706✔
325

326
static Interval trim_interval(const Interval& i, const Number& n) {
6,580✔
327
    if (i.lb() == n) {
13,160✔
328
        return Interval{n + 1, i.ub()};
10,415✔
329
    }
330
    if (i.ub() == n) {
4,828✔
331
        return Interval{i.lb(), n - 1};
8✔
332
    }
333
    if (i.is_top() && n == 0) {
2,467✔
334
        return Interval{1, std::numeric_limits<uint64_t>::max()};
×
335
    }
336
    return i;
2,410✔
337
}
338

339
bool SplitDBM::add_univar_disequation(Variable x, const Number& n) {
6,580✔
340
    Interval i = get_interval(x);
6,580✔
341
    Interval new_i = trim_interval(i, n);
6,580✔
342
    if (new_i.is_bottom()) {
6,580✔
343
        return false;
2,068✔
344
    }
345
    if (new_i.is_top() || !(new_i <= i)) {
2,444✔
346
        return true;
114✔
347
    }
348

349
    VertId v = get_vert(x);
2,330✔
350
    if (new_i.lb().is_finite()) {
3,495✔
351
        // strengthen lb
352
        Weight lb_val;
2,330✔
353
        if (convert_NtoW_overflow(-*new_i.lb().number(), lb_val)) {
5,825✔
354
            return true;
355
        }
356

357
        if (auto w = g.lookup(v, 0)) {
2,330✔
358
            if (lb_val < *w) {
2,330✔
359
                g.set_edge(v, lb_val, 0);
30✔
360
                if (!repair_potential(v, 0)) {
30✔
361
                    return false;
362
                }
363
                // Update other bounds
364
                for (const auto e : g.e_preds(v)) {
96✔
365
                    if (e.vert == 0) {
66✔
366
                        continue;
30✔
367
                    }
368
                    g.update_edge(e.vert, e.val + lb_val, 0);
36✔
369
                    if (!repair_potential(e.vert, 0)) {
36✔
370
                        return false;
×
371
                    }
372
                }
66✔
373
            }
374
        }
375
    }
2,330✔
376
    if (new_i.ub().is_finite()) {
3,495✔
377
        // strengthen ub
378
        Weight ub_val;
2,330✔
379
        if (convert_NtoW_overflow(*new_i.ub().number(), ub_val)) {
4,660✔
380
            return true;
381
        }
382

383
        if (auto w = g.lookup(0, v)) {
2,330✔
384
            if (ub_val < *w) {
2,330✔
385
                g.set_edge(0, ub_val, v);
4✔
386
                if (!repair_potential(0, v)) {
4✔
387
                    return false;
388
                }
389
                // Update other bounds
390
                for (const auto e : g.e_succs(v)) {
12✔
391
                    if (e.vert == 0) {
8✔
392
                        continue;
4✔
393
                    }
394
                    g.update_edge(0, e.val + ub_val, e.vert);
4✔
395
                    if (!repair_potential(0, e.vert)) {
4✔
396
                        return false;
×
397
                    }
398
                }
8✔
399
            }
400
        }
401
    }
2,330✔
402
    normalize();
2,330✔
403
    return true;
1,165✔
404
}
9,870✔
405

406
bool SplitDBM::operator<=(const SplitDBM& o) const {
974✔
407
    // cover all trivial cases to avoid allocating a dbm matrix
408
    if (o.is_top()) {
974✔
409
        return true;
68✔
410
    }
411
    if (is_top()) {
838✔
412
        return false;
3✔
413
    }
414

415
    if (vert_map.size() < o.vert_map.size()) {
1,664✔
416
        return false;
21✔
417
    }
418
    constexpr VertId INVALID_VERT = std::numeric_limits<VertId>::max();
790✔
419
    // Set up a mapping from o to this.
420
    std::vector vert_renaming(o.g.size(), INVALID_VERT);
790✔
421
    vert_renaming[0] = 0;
790✔
422
    for (const auto& [v, n] : o.vert_map) {
3,548✔
423
        if (o.g.succs(n).size() == 0 && o.g.preds(n).size() == 0) {
1,312✔
424
            continue;
×
425
        }
426

427
        // We can't have this <= o if we're missing some vertex.
428
        if (auto y = try_at(vert_map, v)) {
1,312✔
429
            vert_renaming[n] = *y;
1,312✔
430
        } else {
431
            return false;
×
432
        }
433
    }
434

435
    assert(g.size() > 0);
790✔
436
    for (const VertId ox : o.g.verts()) {
2,558✔
437
        if (o.g.succs(ox).size() == 0) {
1,866✔
438
            continue;
48✔
439
        }
440

441
        assert(vert_renaming[ox] != INVALID_VERT);
1,818✔
442
        const VertId x = vert_renaming[ox];
1,818✔
443
        for (const auto edge : o.g.e_succs(ox)) {
4,218✔
444
            const VertId oy = edge.vert;
2,498✔
445
            assert(vert_renaming[oy] != INVALID_VERT);
2,498✔
446
            const VertId y = vert_renaming[oy];
2,498✔
447
            Weight ow = edge.val;
2,498✔
448

449
            if (const auto w = g.lookup(x, y)) {
2,498✔
450
                if (*w <= ow) {
2,460✔
451
                    continue;
2,364✔
452
                }
453
            }
454

455
            if (const auto wx = g.lookup(x, 0)) {
134✔
456
                if (const auto wy = g.lookup(0, y)) {
44✔
457
                    if (*wx + *wy <= ow) {
36✔
458
                        continue;
36✔
459
                    }
460
                }
461
            }
462
            return false;
98✔
463
        }
3,747✔
464
    }
465
    return true;
692✔
466
}
790✔
467

468
class SplitDBMJoiner {
469
    friend class SplitDBM;
470

471
    static SplitDBM join(const SplitDBM& left, const SplitDBM& right) {
99,032✔
472
        using Graph = SplitDBM::Graph;
49,516✔
473
        using VertId = SplitDBM::VertId;
49,516✔
474
        using VertMap = SplitDBM::VertMap;
49,516✔
475
        using Weight = SplitDBM::Weight;
49,516✔
476
        using RevMap = SplitDBM::RevMap;
49,516✔
477
        if (right.is_top()) {
99,032✔
478
            return right;
36✔
479
        }
480
        if (left.is_top()) {
98,996✔
481
            return left;
72✔
482
        }
483
        // Figure out the common renaming, initializing the
484
        // resulting potentials as we go.
485
        std::vector<VertId> perm_x;
98,924✔
486
        std::vector<VertId> perm_y;
98,924✔
487
        std::vector<Variable> perm_inv;
98,924✔
488

489
        std::vector<Weight> pot_rx;
98,924✔
490
        std::vector<Weight> pot_ry;
98,924✔
491
        VertMap out_vmap;
49,462✔
492
        RevMap out_revmap;
98,924✔
493
        // Add the zero vertex
494
        assert(!left.potential.empty());
98,924✔
495
        pot_rx.emplace_back(0);
98,924✔
496
        pot_ry.emplace_back(0);
98,924✔
497
        perm_x.push_back(0);
98,924✔
498
        perm_y.push_back(0);
98,924✔
499
        out_revmap.push_back(std::nullopt);
98,924✔
500

501
        for (const auto& [v, n] : left.vert_map) {
4,544,146✔
502
            if (auto y = try_at(right.vert_map, v)) {
2,897,532✔
503
                // Variable exists in both
504
                out_vmap.emplace(v, gsl::narrow<VertId>(perm_x.size()));
2,855,372✔
505
                out_revmap.push_back(v);
2,855,372✔
506

507
                pot_rx.push_back(left.potential[n] - left.potential[0]);
4,283,058✔
508
                // XXX JNL: check this out
509
                // pot_ry.push_back(o.potential[p.second] - o.potential[0]);
510
                pot_ry.push_back(right.potential[*y] - right.potential[0]);
4,283,058✔
511
                perm_inv.push_back(v);
2,855,372✔
512
                perm_x.push_back(n);
2,855,372✔
513
                perm_y.push_back(*y);
2,855,372✔
514
            }
515
        }
516
        size_t sz = perm_x.size();
98,924✔
517

518
        // Build the permuted view of x and y.
519
        assert(left.g.size() > 0);
98,924✔
520
        GraphPerm gx(perm_x, left.g);
98,924✔
521
        assert(right.g.size() > 0);
98,924✔
522
        GraphPerm gy(perm_y, right.g);
98,924✔
523

524
        // Compute the deferred relations
525
        Graph g_ix_ry;
98,924✔
526
        g_ix_ry.growTo(sz);
98,924✔
527
        SubGraph gy_excl(gy, 0);
98,924✔
528
        for (VertId s : gy_excl.verts()) {
4,381,985✔
529
            for (VertId d : gy_excl.succs(s)) {
4,403,726✔
530
                if (auto ws = gx.lookup(s, 0)) {
1,548,354✔
531
                    if (auto wd = gx.lookup(0, d)) {
1,439,772✔
532
                        g_ix_ry.add_edge(s, *ws + *wd, d);
2,155,617✔
533
                    }
534
                }
535
            }
536
        }
537
        // Apply the deferred relations, and re-close.
538
        bool is_closed;
49,462✔
539
        Graph g_rx(GraphOps::meet(gx, g_ix_ry, is_closed));
98,924✔
540
        if (!is_closed) {
98,924✔
541
            GraphOps::apply_delta(g_rx,
98,924✔
542
                                  GraphOps::close_after_meet(SubGraph(g_rx, 0), index_to_call(pot_rx), gx, g_ix_ry));
197,848✔
543
        }
544

545
        Graph g_rx_iy;
98,924✔
546
        g_rx_iy.growTo(sz);
98,924✔
547

548
        SubGraph gx_excl(gx, 0);
98,924✔
549
        for (VertId s : gx_excl.verts()) {
4,381,985✔
550
            for (VertId d : gx_excl.succs(s)) {
4,532,462✔
551
                // Assumption: gx.mem(s, d) -> gx.edge_val(s, d) <= ranges[var(s)].ub() - ranges[var(d)].lb()
552
                // That is, if the relation exists, it's at least as strong as the bounds.
553
                if (auto ws = gy.lookup(s, 0)) {
1,677,090✔
554
                    if (auto wd = gy.lookup(0, d)) {
1,568,922✔
555
                        g_rx_iy.add_edge(s, *ws + *wd, d);
2,347,863✔
556
                    }
557
                }
558
            }
559
        }
560
        // Similarly, should use a SubGraph view.
561
        Graph g_ry(GraphOps::meet(gy, g_rx_iy, is_closed));
98,924✔
562
        if (!is_closed) {
98,924✔
563
            GraphOps::apply_delta(g_ry,
98,924✔
564
                                  GraphOps::close_after_meet(SubGraph(g_ry, 0), index_to_call(pot_ry), gy, g_rx_iy));
197,848✔
565
        }
566

567
        // We now have the relevant set of relations. Because g_rx and g_ry are closed,
568
        // the result is also closed.
569
        Graph join_g(GraphOps::join(g_rx, g_ry));
98,924✔
570

571
        // Now reapply the missing independent relations.
572
        // Need to derive vert_ids from lb_up/lb_down, and make sure the vertices exist
573
        std::vector<VertId> lb_up;
98,924✔
574
        std::vector<VertId> lb_down;
98,924✔
575
        std::vector<VertId> ub_up;
98,924✔
576
        std::vector<VertId> ub_down;
98,924✔
577

578
        for (VertId v : gx_excl.verts()) {
4,381,985✔
579
            if (auto wx = gx.lookup(0, v)) {
2,855,372✔
580
                if (auto wy = gy.lookup(0, v)) {
2,709,544✔
581
                    if (*wx < *wy) {
2,706,152✔
582
                        ub_up.push_back(v);
20,032✔
583
                    }
584
                    if (*wy < *wx) {
2,706,152✔
585
                        ub_down.push_back(v);
35,008✔
586
                    }
587
                }
588
            }
589
            if (auto wx = gx.lookup(v, 0)) {
2,855,372✔
590
                if (auto wy = gy.lookup(v, 0)) {
2,716,112✔
591
                    if (*wx < *wy) {
2,712,886✔
592
                        lb_down.push_back(v);
15,178✔
593
                    }
594
                    if (*wy < *wx) {
2,712,886✔
595
                        lb_up.push_back(v);
34,380✔
596
                    }
597
                }
598
            }
599
        }
600

601
        for (VertId s : lb_up) {
133,304✔
602
            Weight dx_s = gx.edge_val(s, 0);
34,380✔
603
            Weight dy_s = gy.edge_val(s, 0);
34,380✔
604
            for (VertId d : ub_up) {
83,014✔
605
                if (s == d) {
48,634✔
606
                    continue;
12,628✔
607
                }
608

609
                join_g.update_edge(s, std::max(dx_s + gx.edge_val(0, d), dy_s + gy.edge_val(0, d)), d);
54,009✔
610
            }
611
        }
34,380✔
612

613
        for (VertId s : lb_down) {
114,102✔
614
            Weight dx_s = gx.edge_val(s, 0);
15,178✔
615
            Weight dy_s = gy.edge_val(s, 0);
15,178✔
616
            for (VertId d : ub_down) {
55,682✔
617
                if (s == d) {
40,504✔
618
                    continue;
10,622✔
619
                }
620

621
                join_g.update_edge(s, std::max(dx_s + gx.edge_val(0, d), dy_s + gy.edge_val(0, d)), d);
44,823✔
622
            }
623
        }
15,178✔
624

625
        // Conjecture: join_g remains closed.
626

627
        // Now garbage collect any unused vertices
628
        for (VertId v : join_g.verts()) {
3,053,220✔
629
            if (v == 0) {
2,954,296✔
630
                continue;
98,924✔
631
            }
632
            if (join_g.succs(v).size() == 0 && join_g.preds(v).size() == 0) {
2,855,372✔
633
                join_g.forget(v);
39,530✔
634
                if (out_revmap[v]) {
39,530✔
635
                    out_vmap.erase(*(out_revmap[v]));
39,530✔
636
                    out_revmap[v] = std::nullopt;
1,516,678✔
637
                }
638
            }
639
        }
640
        return SplitDBM(std::move(out_vmap), std::move(out_revmap), std::move(join_g), std::move(pot_rx), {});
98,924✔
641
    }
197,848✔
642
};
643

644
void SplitDBM::operator|=(const SplitDBM& right) { *this = SplitDBMJoiner::join(*this, right); }
×
645

646
SplitDBM SplitDBM::operator|(const SplitDBM& right) const { return SplitDBMJoiner::join(*this, right); }
99,032✔
647

648
SplitDBM SplitDBM::widen(const SplitDBM& o) const {
168✔
649
    // Figure out the common renaming
650
    assert(!potential.empty());
168✔
651
    std::vector<Weight> widen_pot = {0};
504✔
652
    std::vector<VertId> perm_x = {0};
252✔
653
    std::vector<VertId> perm_y = {0};
336✔
654
    VertMap out_vmap;
84✔
655
    RevMap out_revmap = {std::nullopt};
252✔
656
    for (const auto& [v, n] : vert_map) {
4,248✔
657
        if (auto y = try_at(o.vert_map, v)) {
2,608✔
658
            // Variable exists in both
659
            out_vmap.emplace(v, gsl::narrow<VertId>(perm_x.size()));
346✔
660
            out_revmap.push_back(v);
346✔
661

662
            widen_pot.push_back(potential[n] - potential[0]);
519✔
663
            perm_x.push_back(n);
346✔
664
            perm_y.push_back(*y);
346✔
665
        }
666
    }
667

668
    // Build the permuted view of x and y.
669
    assert(g.size() > 0);
168✔
670
    GraphPerm gx(perm_x, g);
168✔
671
    assert(o.g.size() > 0);
168✔
672
    GraphPerm gy(perm_y, o.g);
168✔
673

674
    // Now perform the widening
675
    VertSet widen_unstable(unstable);
168✔
676
    Graph widen_g(GraphOps::widen(gx, gy, widen_unstable));
168✔
677

678
    SplitDBM res(std::move(out_vmap), std::move(out_revmap), std::move(widen_g), std::move(widen_pot),
168✔
679
                 std::move(widen_unstable));
252✔
680
    return res;
252✔
681
}
420✔
682

683
std::optional<SplitDBM> SplitDBM::meet(const SplitDBM& o) const {
76✔
684
    CrabStats::count("SplitDBM.count.meet");
114✔
685
    ScopedCrabStats __st__("SplitDBM.meet");
76✔
686

687
    if (is_top()) {
76✔
688
        return o;
12✔
689
    }
690
    if (o.is_top()) {
64✔
691
        return *this;
26✔
692
    }
693
    CRAB_LOG("zones-split", std::cout << "Before meet:\n"
38✔
694
                                      << "DBM 1\n"
695
                                      << *this << "\n"
696
                                      << "DBM 2\n"
697
                                      << o << "\n");
698

699
    // We map vertices in the left operand onto a contiguous range.
700
    // This will often be the identity map, but there might be gaps.
701
    VertMap meet_verts;
19✔
702
    RevMap meet_rev;
38✔
703

704
    std::vector<VertId> perm_x;
38✔
705
    std::vector<VertId> perm_y;
38✔
706
    std::vector<Weight> meet_pi;
38✔
707
    perm_x.push_back(0);
38✔
708
    perm_y.push_back(0);
38✔
709
    meet_pi.emplace_back(0);
38✔
710
    meet_rev.push_back(std::nullopt);
38✔
711
    for (const auto& [v, n] : vert_map) {
244✔
712
        VertId vv = gsl::narrow<VertId>(perm_x.size());
112✔
713
        meet_verts.emplace(v, vv);
56✔
714
        meet_rev.push_back(v);
112✔
715

716
        perm_x.push_back(n);
112✔
717
        perm_y.push_back(-1);
112✔
718
        meet_pi.push_back(potential[n] - potential[0]);
168✔
719
    }
720

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

725
        if (it == meet_verts.end()) {
2,318✔
726
            VertId vv = gsl::narrow<VertId>(perm_y.size());
2,246✔
727
            meet_rev.push_back(v);
2,246✔
728

729
            perm_y.push_back(n);
2,246✔
730
            perm_x.push_back(-1);
2,246✔
731
            meet_pi.push_back(o.potential[n] - o.potential[0]);
3,369✔
732
            meet_verts.emplace(v, vv);
2,246✔
733
        } else {
734
            perm_y[it->second] = n;
108✔
735
        }
736
    }
737

738
    // Build the permuted view of x and y.
739
    assert(g.size() > 0);
38✔
740
    GraphPerm gx(perm_x, g);
38✔
741
    assert(o.g.size() > 0);
38✔
742
    GraphPerm gy(perm_y, o.g);
38✔
743

744
    // Compute the syntactic meet of the permuted graphs.
745
    bool is_closed{};
38✔
746
    Graph meet_g(GraphOps::meet(gx, gy, is_closed));
38✔
747

748
    // Compute updated potentials on the zero-enriched graph
749
    // vector<Weight> meet_pi(meet_g.size());
750
    // We've warm-started pi with the operand potentials
751
    if (!GraphOps::select_potentials(meet_g, meet_pi)) {
38✔
752
        // Potentials cannot be selected -- state is infeasible.
753
        return {};
×
754
    }
755

756
    if (!is_closed) {
38✔
757
        const auto potential_func = index_to_call(meet_pi);
38✔
758
        GraphOps::apply_delta(meet_g, GraphOps::close_after_meet(SubGraph(meet_g, 0), potential_func, gx, gy));
38✔
759

760
        // Recover updated LBs and UBs.<
761

762
        GraphOps::apply_delta(meet_g, GraphOps::close_after_assign(meet_g, potential_func, 0));
38✔
763
    }
38✔
764
    SplitDBM res(std::move(meet_verts), std::move(meet_rev), std::move(meet_g), std::move(meet_pi), VertSet());
38✔
765
    CRAB_LOG("zones-split", std::cout << "Result meet:\n" << res << "\n");
38✔
766
    return res;
38✔
767
}
114✔
768

769
void SplitDBM::havoc(const Variable v) {
2,170,764✔
770
    if (const auto y = try_at(vert_map, v)) {
2,170,764✔
771
        g.forget(*y);
517,382✔
772
        rev_map[*y] = std::nullopt;
517,382✔
773
        vert_map.erase(v);
517,382✔
774
        normalize();
517,382✔
775
    }
776
}
2,170,764✔
777

778
// return false if becomes bottom
779
bool SplitDBM::add_constraint(const LinearConstraint& cst) {
896,860✔
780
    CrabStats::count("SplitDBM.count.add_constraints");
1,345,292✔
781
    ScopedCrabStats __st__("SplitDBM.add_constraints");
896,860✔
782

783
    if (cst.is_tautology()) {
896,860✔
784
        return true;
73✔
785
    }
786

787
    // g.check_adjs();
788

789
    if (cst.is_contradiction()) {
896,714✔
790
        return false;
373✔
791
    }
792

793
    switch (cst.kind()) {
895,968✔
794
    case ConstraintKind::LESS_THAN_OR_EQUALS_ZERO: {
186,052✔
795
        if (!add_linear_leq(cst.expression())) {
186,052✔
796
            return false;
14,483✔
797
        }
798
        //  g.check_adjs();
799
        CRAB_LOG("zones-split", std::cout << "--- " << cst << "\n" << *this << "\n");
157,086✔
800
        break;
78,543✔
801
    }
802
    case ConstraintKind::LESS_THAN_ZERO: {
40,936✔
803
        // We try to convert a strict to non-strict.
804
        // e < 0 --> e <= -1
805
        const auto nc = LinearConstraint(cst.expression().plus(1), ConstraintKind::LESS_THAN_OR_EQUALS_ZERO);
61,402✔
806
        if (!add_linear_leq(nc.expression())) {
40,936✔
807
            return false;
902✔
808
        }
809
        CRAB_LOG("zones-split", std::cout << "--- " << cst << "\n" << *this << "\n");
40,034✔
810
        break;
40,034✔
811
    }
40,936✔
812
    case ConstraintKind::EQUALS_ZERO: {
661,878✔
813
        const LinearExpression& exp = cst.expression();
661,878✔
814
        if (!add_linear_leq(exp) || !add_linear_leq(exp.negate())) {
1,323,694✔
815
            CRAB_LOG("zones-split", std::cout << " ~~> _|_" << "\n");
86✔
816
            return false;
86✔
817
        }
818
        // g.check_adjs();
819
        CRAB_LOG("zones-split", std::cout << "--- " << cst << "\n" << *this << "\n");
661,792✔
820
        break;
330,896✔
821
    }
822
    case ConstraintKind::NOT_ZERO: {
7,102✔
823
        // XXX: similar precision as the interval domain
824
        const LinearExpression& e = cst.expression();
7,102✔
825
        for (const auto& [variable, coefficient] : e.variable_terms()) {
10,994✔
826
            Interval i = compute_residual(e, variable) / Interval(coefficient);
12,042✔
827
            if (auto k = i.singleton()) {
8,028✔
828
                if (!add_univar_disequation(variable, *k)) {
6,580✔
829
                    return false;
4,136✔
830
                }
831
            }
8,028✔
832
        }
8,028✔
833
    } break;
1,483✔
834
    }
835

836
    CRAB_WARN("Unhandled constraint ", cst, " by split_dbm");
861,878✔
837
    CRAB_LOG("zones-split", std::cout << "---" << cst << "\n" << *this << "\n");
861,878✔
838
    normalize();
861,878✔
839
    return true;
430,939✔
840
}
896,860✔
841

842
void SplitDBM::assign(Variable lhs, const LinearExpression& e) {
389,860✔
843
    CrabStats::count("SplitDBM.count.assign");
584,790✔
844
    ScopedCrabStats __st__("SplitDBM.assign");
389,860✔
845

846
    CRAB_LOG("zones-split", std::cout << "Before assign: " << *this << "\n");
389,860✔
847
    CRAB_LOG("zones-split", std::cout << lhs << ":=" << e << "\n");
389,860✔
848

849
    Interval value_interval = eval_interval(e);
389,860✔
850

851
    std::optional<Weight> lb_w, ub_w;
389,860✔
852
    if (value_interval.lb().is_finite()) {
584,790✔
853
        Weight tmp;
321,002✔
854
        if (convert_NtoW_overflow(-*value_interval.lb().number(), tmp)) {
802,505✔
855
            havoc(lhs);
856
            CRAB_LOG("zones-split", std::cout << "---" << lhs << ":=" << e << "\n" << *this << "\n");
857
            normalize();
858
            return;
859
        }
860
        lb_w = tmp;
321,002✔
861
    }
321,002✔
862
    if (value_interval.ub().is_finite()) {
584,790✔
863
        Weight tmp;
320,954✔
864
        if (convert_NtoW_overflow(*value_interval.ub().number(), tmp)) {
641,908✔
865
            havoc(lhs);
866
            CRAB_LOG("zones-split", std::cout << "---" << lhs << ":=" << e << "\n" << *this << "\n");
867
            normalize();
868
            return;
869
        }
870
        ub_w = tmp;
320,954✔
871
    }
320,954✔
872

873
    // JN: it seems that we can only do this if
874
    // close_bounds_inline is disabled (which in eBPF is always the case).
875
    // Otherwise, the meet operator misses some non-redundant edges.
876
    if (value_interval.is_singleton()) {
389,860✔
877
        set(lhs, value_interval);
206,818✔
878
        normalize();
206,818✔
879
        return;
103,409✔
880
    }
881

882
    std::vector<std::pair<Variable, Weight>> diffs_lb, diffs_ub;
183,042✔
883
    // Construct difference constraints from the assignment
884
    diffcsts_of_assign(e, diffs_lb, diffs_ub);
183,042✔
885
    if (diffs_lb.empty() && diffs_ub.empty()) {
183,042✔
886
        set(lhs, value_interval);
544✔
887
        normalize();
544✔
888
        return;
272✔
889
    }
890

891
    Weight e_val;
182,498✔
892
    if (eval_expression_overflow(e, e_val)) {
182,498✔
893
        havoc(lhs);
×
894
        return;
×
895
    }
896
    // Allocate a new vertex for x
897
    VertId vert = g.new_vertex();
182,498✔
898
    assert(vert <= rev_map.size());
182,498✔
899
    if (vert == rev_map.size()) {
182,498✔
900
        rev_map.push_back(lhs);
54,314✔
901
        potential.push_back(potential[0] + e_val);
81,471✔
902
    } else {
903
        potential[vert] = potential[0] + e_val;
128,184✔
904
        rev_map[vert] = lhs;
128,184✔
905
    }
906

907
    {
91,249✔
908
        GraphOps::EdgeVector delta;
182,498✔
909
        for (const auto& [var, n] : diffs_lb) {
366,646✔
910
            delta.emplace_back(vert, get_vert(var), -n);
184,148✔
911
        }
912

913
        for (const auto& [var, n] : diffs_ub) {
366,644✔
914
            delta.emplace_back(get_vert(var), vert, n);
184,146✔
915
        }
916

917
        // apply_delta should be safe here, as x has no edges in G.
918
        GraphOps::apply_delta(g, delta);
182,498✔
919
    }
182,498✔
920
    GraphOps::apply_delta(g, GraphOps::close_after_assign(SubGraph(g, 0), index_to_call(potential), vert));
182,498✔
921

922
    if (lb_w) {
182,498✔
923
        g.update_edge(vert, *lb_w, 0);
171,276✔
924
    }
925
    if (ub_w) {
182,498✔
926
        g.update_edge(0, *ub_w, vert);
171,204✔
927
    }
928
    // Clear the old x vertex
929
    havoc(lhs);
182,498✔
930
    vert_map.emplace(lhs, vert);
182,498✔
931

932
    normalize();
182,498✔
933
    CRAB_LOG("zones-split", std::cout << "---" << lhs << ":=" << e << "\n" << *this << "\n");
182,498✔
934
}
896,649✔
935

936
SplitDBM SplitDBM::narrow(const SplitDBM& o) const {
×
937
    CrabStats::count("SplitDBM.count.narrowing");
×
938
    ScopedCrabStats __st__("SplitDBM.narrowing");
×
939

940
    if (is_top()) {
×
941
        return o;
×
942
    }
943
    // FIXME: Implement properly
944
    // Narrowing as a no-op should be sound.
945
    return {*this};
×
946
}
×
947

948
class VertSetWrap {
949
  public:
950
    explicit VertSetWrap(const SplitDBM::VertSet& _vs) : vs(_vs) {}
46✔
951

952
    bool operator[](const SplitDBM::VertId v) const { return vs.contains(v); }
148✔
953
    const SplitDBM::VertSet& vs;
954
};
955

956
bool SplitDBM::repair_potential(const VertId src, const VertId dest) {
1,431,614✔
957
    return GraphOps::repair_potential(g, potential, src, dest);
1,431,614✔
958
}
959

960
void SplitDBM::clear_thread_local_state() { GraphOps::clear_thread_local_state(); }
1,392✔
961

962
void SplitDBM::normalize() {
3,765,270✔
963
    CrabStats::count("SplitDBM.count.normalize");
5,647,905✔
964
    ScopedCrabStats __st__("SplitDBM.normalize");
3,765,270✔
965

966
    // dbm_canonical(_dbm);
967
    // Always maintained in normal form, except for widening
968
    if (unstable.empty()) {
3,765,270✔
969
        return;
3,765,224✔
970
    }
971

972
    GraphOps::EdgeVector delta;
46✔
973
    // GraphOps::close_after_widen(g, potential, VertSetWrap(unstable), delta);
974
    // GKG: Check
975
    const auto p = index_to_call(potential);
46✔
976
    GraphOps::apply_delta(g, GraphOps::close_after_widen(SubGraph(g, 0), p, VertSetWrap(unstable)));
46✔
977
    // Retrieve variable bounds
978
    GraphOps::apply_delta(g, GraphOps::close_after_assign(g, p, 0));
46✔
979

980
    unstable.clear();
46✔
981
}
3,765,270✔
982

983
void SplitDBM::set(const Variable x, const Interval& intv) {
377,406✔
984
    CrabStats::count("SplitDBM.count.assign");
566,109✔
985
    ScopedCrabStats __st__("SplitDBM.assign");
377,406✔
986
    assert(!intv.is_bottom());
377,406✔
987

988
    havoc(x);
377,406✔
989

990
    if (intv.is_top()) {
377,406✔
991
        return;
3,468✔
992
    }
993

994
    const VertId v = get_vert(x);
373,938✔
995
    if (intv.ub().is_finite()) {
560,907✔
996
        Weight ub;
373,934✔
997
        if (convert_NtoW_overflow(*intv.ub().number(), ub)) {
747,868✔
998
            normalize();
999
            return;
1000
        }
1001
        potential[v] = potential[0] + ub;
373,934✔
1002
        g.set_edge(0, ub, v);
560,901✔
1003
    }
373,934✔
1004
    if (intv.lb().is_finite()) {
560,907✔
1005
        Weight lb;
373,782✔
1006
        if (convert_NtoW_overflow(*intv.lb().number(), lb)) {
747,564✔
1007
            normalize();
1008
            return;
1009
        }
1010
        potential[v] = potential[0] + lb;
373,782✔
1011
        g.set_edge(v, -lb, 0);
560,673✔
1012
    }
373,782✔
1013
    normalize();
373,938✔
1014
}
377,406✔
1015

1016
void SplitDBM::apply(const ArithBinOp op, const Variable x, const Variable y, const Variable z) {
×
1017
    switch (op) {
×
1018
    case ArithBinOp::ADD: assign(x, LinearExpression(y).plus(z)); break;
×
1019
    case ArithBinOp::SUB: assign(x, LinearExpression(y).subtract(z)); break;
×
1020
    // For the rest of operations, we fall back on intervals.
1021
    case ArithBinOp::MUL: set(x, get_interval(y) * get_interval(z)); break;
×
1022
    default: CRAB_ERROR("DBM: unreachable");
×
1023
    }
1024
    normalize();
×
1025
}
×
1026

1027
void SplitDBM::apply(const ArithBinOp op, const Variable x, const Variable y, const Number& k) {
×
1028
    switch (op) {
×
1029
    case ArithBinOp::ADD: assign(x, LinearExpression(y).plus(k)); break;
×
1030
    case ArithBinOp::SUB: assign(x, LinearExpression(y).subtract(k)); break;
×
1031
    case ArithBinOp::MUL: assign(x, LinearExpression(k, y)); break;
×
1032
    }
1033
    normalize();
×
1034
}
×
1035

1036
void SplitDBM::forget(const VariableVector& variables) {
×
1037
    if (is_top()) {
×
1038
        return;
1039
    }
1040

1041
    for (const auto v : variables) {
×
1042
        if (vert_map.contains(v)) {
×
1043
            havoc(v);
×
1044
        }
1045
    }
1046
    normalize();
×
1047
}
1048

1049
static std::string to_string(const Variable vd, const Variable vs, const SplitDBM::Weight& w, const bool eq) {
200✔
1050
    std::stringstream elem;
200✔
1051
    if (eq) {
200✔
1052
        if (w.operator>(0)) {
8✔
1053
            elem << vd << "=" << vs << "+" << w;
8✔
1054
        } else if (w.operator<(0)) {
×
1055
            elem << vs << "=" << vd << "+" << -w;
×
1056
        } else {
1057
            const auto [left, right] = std::minmax(vs, vd, variable_registry->printing_order);
×
1058
            elem << left << "=" << right;
×
1059
        }
1060
    } else {
1061
        elem << vd << "-" << vs << "<=" << w;
192✔
1062
    }
1063
    return elem.str();
400✔
1064
}
200✔
1065

1066
StringInvariant SplitDBM::to_set() const {
1,900✔
1067
    if (this->is_top()) {
1,900✔
1068
        return StringInvariant::top();
36✔
1069
    }
1070
    // Extract all the edges
1071
    SubGraph g_excl{this->g, 0};
1,864✔
1072

1073
    std::map<Variable, Variable> equivalence_classes;
1,864✔
1074
    std::set<std::tuple<Variable, Variable, Weight>> diff_csts;
1,864✔
1075
    for (const VertId s : g_excl.verts()) {
6,862✔
1076
        const Variable vs = *rev_map.at(s);
4,998✔
1077
        Variable least = vs;
4,998✔
1078
        for (const VertId d : g_excl.succs(s)) {
8,926✔
1079
            const Variable vd = *rev_map.at(d);
1,292✔
1080
            const Weight w = g_excl.edge_val(s, d);
1,292✔
1081
            if (w == 0) {
1,292✔
1082
                least = std::min(least, vd, variable_registry->printing_order);
1,662✔
1083
            } else {
1084
                diff_csts.emplace(vd, vs, w);
184✔
1085
            }
1086
        }
1,292✔
1087
        equivalence_classes.insert_or_assign(vs, least);
4,998✔
1088
    }
1089

1090
    std::set<Variable> representatives;
1,864✔
1091
    std::set<std::string> result;
1,864✔
1092
    for (const auto [vs, least] : equivalence_classes) {
6,862✔
1093
        if (vs == least) {
4,998✔
1094
            representatives.insert(least);
4,516✔
1095
        } else {
1096
            result.insert(variable_registry->name(vs) + "=" + variable_registry->name(least));
964✔
1097
        }
1098
    }
1099

1100
    // simplify: x - y <= k && y - x <= -k
1101
    //        -> x <= y + k <= x
1102
    //        -> x = y + k
1103
    for (const auto& [vd, vs, w] : diff_csts) {
2,048✔
1104
        if (!representatives.contains(vd) || !representatives.contains(vs)) {
184✔
1105
            continue;
84✔
1106
        }
1107
        auto dual = to_string(vs, vd, -w, false);
100✔
1108
        if (result.contains(dual)) {
100✔
1109
            assert(w != 0);
8✔
1110
            result.erase(dual);
8✔
1111
            result.insert(to_string(vd, vs, w, true));
12✔
1112
        } else {
1113
            result.insert(to_string(vd, vs, w, false));
138✔
1114
        }
1115
    }
100✔
1116

1117
    // Intervals
1118
    for (VertId v : g_excl.verts()) {
6,862✔
1119
        const auto pvar = this->rev_map[v];
4,998✔
1120
        if (!pvar || !representatives.contains(*pvar)) {
4,998✔
1121
            continue;
630✔
1122
        }
1123
        if (!this->g.elem(0, v) && !this->g.elem(v, 0)) {
4,516✔
1124
            continue;
76✔
1125
        }
1126
        Interval v_out{this->g.elem(v, 0) ? -Number(this->g.edge_val(v, 0)) : MINUS_INFINITY,
13,318✔
1127
                       this->g.elem(0, v) ? Number(this->g.edge_val(0, v)) : PLUS_INFINITY};
11,086✔
1128
        assert(!v_out.is_bottom());
4,440✔
1129

1130
        Variable variable = *pvar;
4,440✔
1131

1132
        std::stringstream elem;
4,440✔
1133
        elem << variable;
4,440✔
1134
        if (variable_registry->is_type(variable)) {
4,440✔
1135
            auto [lb, ub] = v_out.bound(T_UNINIT, T_MAX);
1,666✔
1136
            if (lb == ub) {
1,666✔
1137
                if (variable_registry->is_in_stack(variable) && lb == T_NUM) {
1,646✔
1138
                    // no need to show this
1139
                    continue;
72✔
1140
                }
1141
                elem << "=" << lb;
1,574✔
1142
            } else {
1143
                elem << " in " << typeset_to_string(iterate_types(lb, ub));
30✔
1144
            }
1145
        } else {
1146
            elem << "=";
2,774✔
1147
            if (v_out.is_singleton()) {
2,774✔
1148
                elem << v_out.lb();
4,180✔
1149
            } else {
1150
                elem << v_out;
684✔
1151
            }
1152
        }
1153
        result.insert(elem.str());
4,368✔
1154
    }
6,696✔
1155

1156
    return StringInvariant{std::move(result)};
1,864✔
1157
}
1,864✔
1158

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

1161
bool SplitDBM::eval_expression_overflow(const LinearExpression& e, Weight& out) const {
182,498✔
1162
    [[maybe_unused]]
1163
    const bool overflow = convert_NtoW_overflow(e.constant_term(), out);
182,498✔
1164
    assert(!overflow);
182,498✔
1165
    for (const auto& [variable, coefficient] : e.variable_terms()) {
367,630✔
1166
        Weight coef;
185,132✔
1167
        if (convert_NtoW_overflow(coefficient, coef)) {
185,132✔
1168
            out = Weight(0);
1169
            return true;
1170
        }
1171
        out += (pot_value(variable) - potential[0]) * coef;
277,698✔
1172
    }
185,132✔
1173
    return false;
182,498✔
1174
}
1175

1176
Interval SplitDBM::compute_residual(const LinearExpression& e, const Variable pivot) const {
8,028✔
1177
    Interval residual(-e.constant_term());
8,028✔
1178
    for (const auto& [variable, coefficient] : e.variable_terms()) {
17,970✔
1179
        if (variable != pivot) {
9,942✔
1180
            residual = residual - Interval(coefficient) * get_interval(variable);
5,742✔
1181
        }
1182
    }
1183
    return residual;
8,028✔
1184
}
×
1185

1186
SplitDBM::Weight SplitDBM::pot_value(const Variable v) const {
185,132✔
1187
    if (const auto y = try_at(vert_map, v)) {
185,132✔
1188
        return potential[*y];
135,848✔
1189
    }
1190
    return {0};
49,284✔
1191
}
1192

1193
Interval SplitDBM::eval_interval(const LinearExpression& e) const {
30,603,074✔
1194
    using namespace prevail::interval_operators;
15,296,248✔
1195
    Interval r{e.constant_term()};
30,603,074✔
1196
    for (const auto& [variable, coefficient] : e.variable_terms()) {
61,097,147✔
1197
        r += coefficient * get_interval(variable);
60,973,195✔
1198
    }
1199
    return r;
30,603,074✔
1200
}
×
1201

1202
bool SplitDBM::intersect(const LinearConstraint& cst) const {
59,546✔
1203
    if (cst.is_contradiction()) {
59,546✔
1204
        return false;
1205
    }
1206
    if (is_top() || cst.is_tautology()) {
59,546✔
1207
        return true;
154✔
1208
    }
1209
    return intersect_aux(cst);
59,392✔
1210
}
1211

1212
bool SplitDBM::entail(const LinearConstraint& rhs) const {
1,819,347✔
1213
    if (rhs.is_tautology()) {
1,819,347✔
1214
        return true;
101✔
1215
    }
1216
    if (rhs.is_contradiction()) {
1,819,191✔
1217
        return false;
10✔
1218
    }
1219
    const Interval interval = eval_interval(rhs.expression());
1,819,171✔
1220
    switch (rhs.kind()) {
1,819,171✔
1221
    case ConstraintKind::EQUALS_ZERO:
148,671✔
1222
        if (interval.singleton() == std::optional(Number(0))) {
148,671✔
1223
            return true;
74,518✔
1224
        }
1225
        break;
2,004✔
1226
    case ConstraintKind::LESS_THAN_OR_EQUALS_ZERO:
131,004✔
1227
        if (interval.ub() <= Number(0)) {
196,506✔
1228
            return true;
59,291✔
1229
        }
1230
        break;
6,211✔
1231
    case ConstraintKind::LESS_THAN_ZERO:
13,934✔
1232
        if (interval.ub() < Number(0)) {
20,901✔
1233
            return true;
6,959✔
1234
        }
1235
        break;
8✔
1236
    case ConstraintKind::NOT_ZERO:
1,525,562✔
1237
        if (interval.ub() < Number(0) || interval.lb() > Number(0)) {
6,562,311✔
1238
            return true;
460,747✔
1239
        }
1240
        break;
302,034✔
1241
    }
1242
    // TODO: copy the implementation from crab
1243
    //       https://github.com/seahorn/crab/blob/master/include/crab/domains/split_dbm.hpp
1244
    if (rhs.kind() == ConstraintKind::EQUALS_ZERO) {
620,512✔
1245
        // try to convert the equality into inequalities so when it's
1246
        // negated we do not have disequalities.
1247
        return entail_aux(LinearConstraint(rhs.expression(), ConstraintKind::LESS_THAN_OR_EQUALS_ZERO)) &&
8,095✔
1248
               entail_aux(LinearConstraint(rhs.expression().negate(), ConstraintKind::LESS_THAN_OR_EQUALS_ZERO));
4,089✔
1249
    } else {
1250
        return entail_aux(rhs);
616,506✔
1251
    }
1252

1253
    // Note: we cannot convert rhs into SplitDBM and then use the <=
1254
    //       operator. The problem is that we cannot know for sure
1255
    //       whether SplitDBM can represent precisely rhs. It is not
1256
    //       enough to do something like
1257
    //
1258
    //       SplitDBM dom = rhs;
1259
    //       if (dom.is_top()) { ... }
1260
}
1,819,171✔
1261

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