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

Alan-Jowett / ebpf-verifier / 22160684311

18 Feb 2026 09:20PM UTC coverage: 88.256% (+1.1%) from 87.142%
22160684311

push

github

elazarg
lint

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

78 of 82 new or added lines in 6 files covered. (95.12%)

402 existing lines in 18 files now uncovered.

10731 of 12159 relevant lines covered (88.26%)

837642.51 hits per line

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

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

6
#include "crab/var_registry.hpp"
7
#include "crab/zone_domain.hpp"
8
#include "string_constraints.hpp"
9

10
using namespace splitdbm;
11

12
namespace prevail {
13

14
ZoneDomain::ZoneDomain() : core_(std::make_unique<SplitDBM>()) { rev_map_.emplace_back(std::nullopt); }
1,761,675✔
15

16
ZoneDomain::~ZoneDomain() = default;
4,346,616✔
17

18
ZoneDomain::ZoneDomain(const ZoneDomain& o)
1,380,374✔
19
    : core_(std::make_unique<SplitDBM>(*o.core_)), vert_map_(o.vert_map_), rev_map_(o.rev_map_) {}
2,760,748✔
20

21
ZoneDomain::ZoneDomain(ZoneDomain&& o) noexcept = default;
2,612,730✔
22

23
ZoneDomain& ZoneDomain::operator=(const ZoneDomain& o) {
38,442✔
24
    if (this != &o) {
38,442✔
25
        core_ = std::make_unique<SplitDBM>(*o.core_);
38,442✔
26
        vert_map_ = o.vert_map_;
38,442✔
27
        rev_map_ = o.rev_map_;
38,442✔
28
    }
29
    return *this;
38,442✔
30
}
31

32
ZoneDomain& ZoneDomain::operator=(ZoneDomain&& o) noexcept = default;
99,688✔
33

34
ZoneDomain::ZoneDomain(VertMap&& vert_map, RevMap&& rev_map, std::unique_ptr<SplitDBM> core)
49,972✔
35
    : core_(std::move(core)), vert_map_(std::move(vert_map)), rev_map_(std::move(rev_map)) {
74,958✔
36
    normalize();
74,958✔
37
}
49,972✔
38

39
void ZoneDomain::set_to_top() {
8✔
40
    core_ = std::make_unique<SplitDBM>();
8✔
41
    vert_map_.clear();
8✔
42
    rev_map_.clear();
8✔
43
    rev_map_.emplace_back(std::nullopt);
8✔
44
}
8✔
45

46
bool ZoneDomain::is_top() const { return core_->is_top(); }
77,555✔
47

UNCOV
48
std::pair<std::size_t, std::size_t> ZoneDomain::size() const { return {core_->graph_size(), core_->num_edges()}; }
×
49

50
std::optional<VertId> ZoneDomain::get_vertid(const Variable x) const {
3,485,382✔
51
    const auto it = vert_map_.find(x);
3,485,382✔
52
    if (it == vert_map_.end()) {
5,228,073✔
53
        return {};
1,365,508✔
54
    }
55
    return it->second;
2,119,874✔
56
}
57

58
Bound ZoneDomain::get_lb(const std::optional<VertId>& v) const {
1,925,700✔
59
    return v ? core_->get_bound(*v, Side::LEFT) : MINUS_INFINITY;
1,925,700✔
60
}
61

62
Bound ZoneDomain::get_ub(const std::optional<VertId>& v) const {
1,559,682✔
63
    return v ? core_->get_bound(*v, Side::RIGHT) : PLUS_INFINITY;
1,559,682✔
64
}
65

66
Bound ZoneDomain::get_lb(const Variable x) const { return get_lb(get_vertid(x)); }
1,925,700✔
67

68
Bound ZoneDomain::get_ub(const Variable x) const {
1,791,508✔
69
    if (variable_registry->is_min_only(x)) {
1,791,508✔
70
        return PLUS_INFINITY;
231,826✔
71
    }
72
    return get_ub(get_vertid(x));
1,559,682✔
73
}
74

75
Interval ZoneDomain::get_interval(const Variable x) const { return {get_lb(x), get_ub(x)}; }
1,611,570✔
76

77
static std::optional<VertId> try_at(const ZoneDomain::VertMap& map, const Variable v) {
4,799,140✔
78
    const auto it = map.find(v);
2,399,570✔
79
    if (it == map.end()) {
4,799,140✔
80
        return std::nullopt;
1,972,626✔
81
    }
82
    return it->second;
2,826,514✔
83
}
84

85
VertId ZoneDomain::get_vert(Variable v) {
901,068✔
86
    if (const auto y = try_at(vert_map_, v)) {
901,068✔
87
        return *y;
586,764✔
88
    }
89

90
    VertId vert = core_->new_vertex();
314,304✔
91
    vert_map_.emplace(v, vert);
314,304✔
92
    // Initialize rev_map
93
    assert(vert <= rev_map_.size());
314,304✔
94
    if (vert < rev_map_.size()) {
314,304✔
95
        rev_map_[vert] = v;
202,884✔
96
    } else {
97
        rev_map_.emplace_back(v);
111,420✔
98
    }
99

100
    assert(vert != 0);
314,304✔
101

102
    return vert;
314,304✔
103
}
104

105
void ZoneDomain::diffcsts_of_assign(const LinearExpression& exp, std::vector<std::pair<Variable, Weight>>& lb,
176,794✔
106
                                    std::vector<std::pair<Variable, Weight>>& ub) const {
107
    diffcsts_of_assign(exp, true, ub);
176,794✔
108
    diffcsts_of_assign(exp, false, lb);
176,794✔
109
}
176,794✔
110

111
void ZoneDomain::diffcsts_of_assign(const LinearExpression& exp,
353,588✔
112
                                    /* if true then process the upper
113
                                       bounds, else the lower bounds */
114
                                    bool extract_upper_bounds,
115
                                    /* foreach {v, k} \in diff_csts we have
116
                                       the difference constraint v - k <= k */
117
                                    std::vector<std::pair<Variable, Weight>>& diff_csts) const {
118

119
    std::optional<Variable> unbounded_var;
353,588✔
120
    std::vector<std::pair<Variable, Weight>> terms;
353,588✔
121

122
    Weight residual = exp.constant_term();
353,588✔
123

124
    for (const auto& [y, n] : exp.variable_terms()) {
712,372✔
125
        const Weight coeff = n;
359,876✔
126

127
        if (coeff < Weight(0)) {
359,876✔
128
            // Can't do anything with negative coefficients.
129
            auto y_val = extract_upper_bounds ? get_lb(y) : get_ub(y);
1,432✔
130

131
            if (y_val.is_infinite()) {
1,432✔
132
                return;
172✔
133
            }
134
            const Weight ymax = *y_val.number();
1,260✔
135
            residual += ymax * coeff;
1,260✔
136
        } else {
137
            auto y_val = extract_upper_bounds ? get_ub(y) : get_lb(y);
358,444✔
138

139
            if (y_val.is_infinite()) {
358,444✔
140
                if (unbounded_var || coeff != Weight(1)) {
142,040✔
141
                    return;
920✔
142
                }
143
                unbounded_var = y;
141,120✔
144
            } else {
145
                const Weight ymax = *y_val.number();
216,404✔
146
                residual += ymax * coeff;
216,404✔
147
                terms.emplace_back(y, ymax);
216,404✔
148
            }
149
        }
150
    }
151

152
    if (unbounded_var) {
352,496✔
153
        // There is exactly one unbounded variable with unit
154
        // coefficient
155
        diff_csts.emplace_back(*unbounded_var, residual);
140,140✔
156
    } else {
157
        for (const auto& [v, n] : terms) {
428,018✔
158
            diff_csts.emplace_back(v, residual - n);
215,662✔
159
        }
160
    }
161
}
353,588✔
162

163
void ZoneDomain::diffcsts_of_lin_leq(const LinearExpression& exp,
242,264✔
164
                                     /* difference contraints */
165
                                     std::vector<diffcst_t>& csts,
166
                                     /* x >= lb for each {x,lb} in lbs */
167
                                     std::vector<std::pair<Variable, Weight>>& lbs,
168
                                     /* x <= ub for each {x,ub} in ubs */
169
                                     std::vector<std::pair<Variable, Weight>>& ubs) const {
170
    Weight exp_ub = -Weight{exp.constant_term()};
242,264✔
171

172
    Weight unbounded_lbcoeff;
242,264✔
173
    Weight unbounded_ubcoeff;
242,264✔
174
    std::optional<Variable> unbounded_lbvar;
242,264✔
175
    std::optional<Variable> unbounded_ubvar;
242,264✔
176

177
    std::vector<std::pair<std::pair<Weight, Variable>, Weight>> pos_terms, neg_terms;
242,264✔
178
    for (const auto& [y, n] : exp.variable_terms()) {
547,984✔
179
        const Weight coeff = n;
305,720✔
180
        if (coeff > Weight(0)) {
305,720✔
181
            auto y_lb = get_lb(y);
134,192✔
182
            if (y_lb.is_infinite()) {
134,192✔
183
                if (unbounded_lbvar) {
22,024✔
UNCOV
184
                    return;
×
185
                }
186
                unbounded_lbvar = y;
22,024✔
187
                unbounded_lbcoeff = coeff;
22,024✔
188
            } else {
189
                const Weight ymin = *y_lb.number();
112,168✔
190
                exp_ub -= ymin * coeff;
112,168✔
191
                pos_terms.push_back({{coeff, y}, ymin});
112,168✔
192
            }
193
        } else {
194
            auto y_ub = get_interval(y).ub();
171,528✔
195
            if (y_ub.is_infinite()) {
171,528✔
196
                if (unbounded_ubvar) {
52,412✔
UNCOV
197
                    return;
×
198
                }
199
                unbounded_ubvar = y;
52,412✔
200
                unbounded_ubcoeff = -coeff;
111,970✔
201
            } else {
202
                const Weight ymax = *y_ub.number();
119,116✔
203
                exp_ub -= ymax * coeff;
119,116✔
204
                neg_terms.push_back({{-coeff, y}, ymax});
119,116✔
205
            }
206
        }
207
    }
208

209
    if (unbounded_lbvar) {
242,264✔
210
        Variable x{*unbounded_lbvar};
22,024✔
211
        if (unbounded_ubvar) {
22,024✔
212
            if (unbounded_lbcoeff == Weight(1) && unbounded_ubcoeff == Weight(1)) {
1,340✔
213
                csts.push_back({{x, *unbounded_ubvar}, exp_ub});
1,340✔
214
            }
215
        } else {
216
            if (unbounded_lbcoeff == Weight(1)) {
20,684✔
217
                for (const auto& [nv, k] : neg_terms) {
21,406✔
218
                    csts.push_back({{x, nv.second}, exp_ub - k});
722✔
219
                }
220
            }
221
            // Add bounds for x
222
            ubs.emplace_back(x, exp_ub / unbounded_lbcoeff);
20,684✔
223
        }
224
    } else {
225
        if (unbounded_ubvar) {
220,240✔
226
            Variable y{*unbounded_ubvar};
51,072✔
227
            if (unbounded_ubcoeff == Weight(1)) {
51,072✔
228
                for (const auto& [nv, k] : pos_terms) {
53,408✔
229
                    csts.push_back({{nv.second, y}, exp_ub + k});
2,336✔
230
                }
231
            }
232
            // Add bounds for y
233
            lbs.emplace_back(y, -exp_ub / unbounded_ubcoeff);
51,072✔
234
        } else {
235
            for (const auto& [neg_nv, neg_k] : neg_terms) {
261,070✔
236
                for (const auto& [pos_nv, pos_k] : pos_terms) {
124,466✔
237
                    csts.push_back({{pos_nv.second, neg_nv.second}, exp_ub - neg_k + pos_k});
32,564✔
238
                }
239
            }
240
            for (const auto& [neg_nv, neg_k] : neg_terms) {
261,070✔
241
                lbs.emplace_back(neg_nv.second, -exp_ub / neg_nv.first + neg_k);
91,902✔
242
            }
243
            for (const auto& [pos_nv, pos_k] : pos_terms) {
279,000✔
244
                ubs.emplace_back(pos_nv.second, exp_ub / pos_nv.first + pos_k);
109,832✔
245
            }
246
        }
247
    }
248
}
242,264✔
249

250
bool ZoneDomain::add_linear_leq(const LinearExpression& exp) {
242,264✔
251
    std::vector<std::pair<Variable, Weight>> lbs, ubs;
242,264✔
252
    std::vector<diffcst_t> csts;
242,264✔
253
    diffcsts_of_lin_leq(exp, csts, lbs, ubs);
242,264✔
254

255
    // Apply lower bounds
256
    for (const auto& [var, n] : lbs) {
384,662✔
257
        const VertId vert = get_vert(var);
142,974✔
258
        if (!core_->update_bound_if_tighter(vert, Side::LEFT, n)) {
142,974✔
259
            return false;
21,999✔
260
        }
261
    }
262

263
    // Apply upper bounds
264
    for (const auto& [var, n] : ubs) {
346,472✔
265
        if (variable_registry->is_min_only(var)) {
130,110✔
266
            continue;
14,124✔
267
        }
268
        const VertId vert = get_vert(var);
115,986✔
269
        if (!core_->update_bound_if_tighter(vert, Side::RIGHT, n)) {
115,986✔
270
            return false;
34,374✔
271
        }
272
    }
273

274
    // Apply difference constraints
275
    for (const auto& [diff, k] : csts) {
234,110✔
276
        const VertId src = get_vert(diff.second);
35,268✔
277
        const VertId dest = get_vert(diff.first);
35,268✔
278
        if (!core_->add_difference_constraint(src, dest, k)) {
35,268✔
279
            return false;
30,471✔
280
        }
281
    }
282

283
    core_->close_after_bound_updates();
198,842✔
284
    normalize();
319,974✔
285
    return true;
99,421✔
286
}
242,264✔
287

288
static Interval trim_interval(const Interval& i, const Number& n) {
522✔
289
    if (i.lb() == n) {
522✔
290
        return Interval{n + 1, i.ub()};
134✔
291
    }
292
    if (i.ub() == n) {
388✔
293
        return Interval{i.lb(), n - 1};
4✔
294
    }
295
    if (i.is_top() && n == 0) {
384✔
UNCOV
296
        return Interval{1, std::numeric_limits<uint64_t>::max()};
×
297
    }
298
    return i;
384✔
299
}
300

301
bool ZoneDomain::add_univar_disequation(Variable x, const Number& n) {
522✔
302
    const Interval i = get_interval(x);
522✔
303
    const Interval new_i = trim_interval(i, n);
522✔
304
    if (new_i.is_bottom()) {
522✔
305
        return false;
53✔
306
    }
307
    if (new_i.is_top() || !(new_i <= i)) {
416✔
308
        return true;
61✔
309
    }
310

311
    const VertId v = get_vert(x);
294✔
312
    if (new_i.lb().is_finite()) {
294✔
313
        if (!core_->strengthen_bound(v, Side::LEFT, Weight{*new_i.lb().number()})) {
294✔
314
            return false;
315
        }
316
    }
317
    if (new_i.ub().is_finite() && !variable_registry->is_min_only(x)) {
294✔
318
        if (!core_->strengthen_bound(v, Side::RIGHT, Weight{*new_i.ub().number()})) {
294✔
319
            return false;
320
        }
321
    }
322
    normalize();
555✔
323
    return true;
147✔
324
}
325

326
bool ZoneDomain::operator<=(const ZoneDomain& o) const {
534✔
327
    // cover all trivial cases to avoid allocating a dbm matrix
328
    if (o.is_top()) {
534✔
329
        return true;
34✔
330
    }
331
    if (is_top()) {
466✔
332
        return false;
2✔
333
    }
334

335
    if (vert_map_.size() < o.vert_map_.size()) {
924✔
336
        return false;
28✔
337
    }
338

339
    // Build permutation mapping from o's vertices to this's vertices
340
    constexpr VertId INVALID_VERT = std::numeric_limits<VertId>::max();
406✔
341
    std::vector perm(o.core_->graph_size(), INVALID_VERT);
406✔
342
    perm[0] = 0;
406✔
343
    for (const auto& [v, n] : o.vert_map_) {
2,234✔
344
        if (!o.core_->vertex_has_edges(n)) {
948✔
345
            continue;
2✔
346
        }
347

348
        // We can't have this <= o if we're missing some vertex.
349
        if (auto y = try_at(vert_map_, v)) {
946✔
350
            perm[n] = *y;
946✔
351
        } else {
UNCOV
352
            return false;
×
353
        }
354
    }
355

356
    return SplitDBM::is_subsumed_by(*core_, *o.core_, perm);
406✔
357
}
406✔
358

359
using RevMap = std::vector<std::optional<Variable>>;
360
using VertMap = boost::container::flat_map<Variable, VertId>;
361

362
[[nodiscard]]
363
std::pair<VertMap, RevMap> result_mappings(const RevMap&& aligned_vars) {
49,972✔
364
    VertMap vmap;
24,986✔
365
    RevMap revmap = aligned_vars;
49,972✔
366

367
    for (size_t i = 1; i < aligned_vars.size(); ++i) {
1,789,660✔
368
        if (aligned_vars[i]) {
1,739,688✔
369
            vmap.emplace(*aligned_vars[i], gsl::narrow<VertId>(i));
3,479,376✔
370
        }
371
    }
372
    return {std::move(vmap), std::move(revmap)};
74,958✔
373
}
49,972✔
374

375
// Build alignment from intersection of variables (for join/widen)
376
std::tuple<AlignedPair, RevMap> ZoneDomain::make_intersection_alignment(const ZoneDomain& left,
49,918✔
377
                                                                        const ZoneDomain& right) {
378
    std::vector<VertId> perm_left = {0};
74,877✔
379
    std::vector<VertId> perm_right = {0};
74,877✔
380
    RevMap aligned_vars = {std::nullopt};
74,877✔
381

382
    for (const auto& [var, left_vert] : left.vert_map_) {
2,761,520✔
383
        if (auto right_vert = try_at(right.vert_map_, var)) {
1,774,456✔
384
            perm_left.push_back(left_vert);
1,736,666✔
385
            perm_right.push_back(*right_vert);
1,736,666✔
386
            aligned_vars.emplace_back(var);
1,736,666✔
387
        }
388
    }
389

390
    return std::make_tuple(AlignedPair{*left.core_, *right.core_, std::move(perm_left), std::move(perm_right)},
74,877✔
391
                           std::move(aligned_vars));
124,795✔
392
}
49,918✔
393

394
// Build alignment from union of variables (for meet)
395
std::tuple<AlignedPair, RevMap> ZoneDomain::make_union_alignment(const ZoneDomain& left, const ZoneDomain& right) {
54✔
396
    constexpr VertId NOT_PRESENT = static_cast<VertId>(-1);
54✔
397

398
    std::vector<VertId> perm_left = {0};
81✔
399
    std::vector<VertId> perm_right = {0};
81✔
400
    RevMap aligned_vars = {std::nullopt};
81✔
401
    std::vector initial_potentials = {Weight(0)};
108✔
402

403
    boost::container::flat_map<Variable, size_t> var_to_index;
27✔
404
    for (const auto& [var, left_vert] : left.vert_map_) {
414✔
405
        var_to_index.emplace(var, perm_left.size());
204✔
406
        perm_left.push_back(left_vert);
204✔
407
        perm_right.push_back(NOT_PRESENT);
204✔
408
        aligned_vars.emplace_back(var);
204✔
409
        initial_potentials.push_back(left.core_->potential_at(left_vert) - left.core_->potential_at_zero());
306✔
410
    }
411

412
    for (const auto& [var, right_vert] : right.vert_map_) {
4,578✔
413
        if (const auto it = var_to_index.find(var); it != var_to_index.end()) {
2,980✔
414
            perm_right[it->second] = right_vert;
243✔
415
        } else {
416
            perm_left.push_back(NOT_PRESENT);
2,818✔
417
            perm_right.push_back(right_vert);
2,818✔
418
            aligned_vars.emplace_back(var);
2,818✔
419
            initial_potentials.push_back(right.core_->potential_at(right_vert) - right.core_->potential_at_zero());
4,227✔
420
        }
421
    }
422

423
    return std::make_tuple(AlignedPair{*left.core_, *right.core_, std::move(perm_left), std::move(perm_right),
135✔
424
                                       std::move(initial_potentials)},
27✔
425
                           std::move(aligned_vars));
135✔
426
}
54✔
427

428
ZoneDomain do_join(const ZoneDomain& left, const ZoneDomain& right) {
49,844✔
429
    if (right.is_top()) {
49,844✔
430
        return right;
22✔
431
    }
432
    if (left.is_top()) {
49,822✔
433
        return left;
12✔
434
    }
435

436
    // 1. Build alignment (intersection of variables)
437
    auto [aligned_pair, aligned_vars] = ZoneDomain::make_intersection_alignment(left, right);
49,810✔
438

439
    // 2. Execute graph join
440
    SplitDBM joined = SplitDBM::join(aligned_pair);
49,810✔
441

442
    // 3. Build result mappings and garbage collect
443
    auto [out_vmap, out_revmap] = result_mappings(std::move(aligned_vars));
49,810✔
444
    for (const VertId v : joined.get_disconnected_vertices()) {
89,232✔
445
        joined.forget(v);
39,422✔
446
        if (out_revmap[v]) {
39,422✔
447
            out_vmap.erase(*out_revmap[v]);
39,422✔
448
            out_revmap[v] = std::nullopt;
59,133✔
449
        }
450
    }
49,810✔
451

452
    return ZoneDomain(std::move(out_vmap), std::move(out_revmap), std::make_unique<SplitDBM>(std::move(joined)));
49,810✔
453
}
74,715✔
454

UNCOV
455
void ZoneDomain::operator|=(const ZoneDomain& right) { *this = do_join(*this, right); }
×
456

457
ZoneDomain ZoneDomain::operator|(const ZoneDomain& right) const { return do_join(*this, right); }
49,844✔
458

459
ZoneDomain ZoneDomain::widen(const ZoneDomain& o) const {
108✔
460
    // 1. Build alignment (intersection of variables)
461
    auto [aligned_pair, aligned_vars] = make_intersection_alignment(*this, o);
108✔
462

463
    // 2. Execute graph widen
464
    auto result_core = std::make_unique<SplitDBM>(SplitDBM::widen(aligned_pair));
108✔
465

466
    // 3. Build result mappings
467
    auto [out_vmap, out_revmap] = result_mappings(std::move(aligned_vars));
108✔
468

469
    return ZoneDomain(std::move(out_vmap), std::move(out_revmap), std::move(result_core));
162✔
470
}
162✔
471

472
std::optional<ZoneDomain> ZoneDomain::meet(const ZoneDomain& o) const {
56✔
473
    if (is_top()) {
56✔
UNCOV
474
        return o;
×
475
    }
476
    if (o.is_top()) {
56✔
477
        return *this;
2✔
478
    }
479

480
    // 1. Build alignment (union of variables, with initial potentials)
481
    auto [aligned_pair, aligned_vars] = make_union_alignment(*this, o);
54✔
482

483
    // 2. Execute graph meet
484
    auto meet_result = SplitDBM::meet(aligned_pair);
54✔
485
    if (!meet_result) {
54✔
UNCOV
486
        return std::nullopt; // Infeasible
×
487
    }
488

489
    // 3. Build result mappings
490
    auto [out_vmap, out_revmap] = result_mappings(std::move(aligned_vars));
54✔
491

492
    return ZoneDomain(std::move(out_vmap), std::move(out_revmap), std::make_unique<SplitDBM>(std::move(*meet_result)));
81✔
493
}
108✔
494

495
void ZoneDomain::havoc(const Variable v) {
1,943,778✔
496
    if (const auto y = try_at(vert_map_, v)) {
1,943,778✔
497
        core_->forget(*y);
372,416✔
498
        rev_map_[*y] = std::nullopt;
372,416✔
499
        vert_map_.erase(v);
372,416✔
500
        normalize();
1,344,305✔
501
    }
502
}
1,943,778✔
503

504
// return false if it becomes bottom
505
bool ZoneDomain::add_constraint(const LinearConstraint& cst) {
221,546✔
506
    if (cst.is_tautology()) {
221,546✔
507
        return true;
89✔
508
    }
509

510
    // g.check_adjs();
511

512
    if (cst.is_contradiction()) {
221,368✔
513
        return false;
515✔
514
    }
515

516
    switch (cst.kind()) {
220,338✔
517
    case ConstraintKind::LESS_THAN_OR_EQUALS_ZERO: {
138,252✔
518
        if (!add_linear_leq(cst.expression())) {
138,252✔
519
            return false;
12,134✔
520
        }
521
        break;
56,992✔
522
    }
523
    case ConstraintKind::LESS_THAN_ZERO: {
58,038✔
524
        // We try to convert a strict to non-strict.
525
        // e < 0 --> e <= -1
526
        const auto nc = LinearConstraint(cst.expression().plus(1), ConstraintKind::LESS_THAN_OR_EQUALS_ZERO);
87,057✔
527
        if (!add_linear_leq(nc.expression())) {
58,038✔
528
            return false;
19,062✔
529
        }
530
        break;
38,976✔
531
    }
58,038✔
532
    case ConstraintKind::EQUALS_ZERO: {
23,006✔
533
        const LinearExpression& exp = cst.expression();
23,006✔
534
        if (!add_linear_leq(exp) || !add_linear_leq(exp.negate())) {
45,974✔
535
            return false;
46✔
536
        }
537
        break;
11,457✔
538
    }
539
    case ConstraintKind::NOT_ZERO: {
1,042✔
540
        // XXX: similar precision as the interval domain
541
        const LinearExpression& e = cst.expression();
1,042✔
542
        for (const auto& [variable, coefficient] : e.variable_terms()) {
2,914✔
543
            Interval i = compute_residual(e, variable) / Interval(coefficient);
1,978✔
544
            if (auto k = i.singleton()) {
1,978✔
545
                if (!add_univar_disequation(variable, *k)) {
522✔
546
                    return false;
106✔
547
                }
548
            }
549
        }
550
    } break;
468✔
551
    }
552
    return true;
88,405✔
553
}
554

555
void ZoneDomain::assign(Variable lhs, const LinearExpression& e) {
238,282✔
556
    const Interval value_interval = eval_interval(e);
238,282✔
557

558
    std::optional<Weight> lb_w, ub_w;
238,282✔
559
    if (value_interval.lb().is_finite()) {
238,282✔
560
        lb_w = Weight{-*value_interval.lb().number()};
254,262✔
561
    }
562
    if (value_interval.ub().is_finite()) {
238,282✔
563
        ub_w = Weight{*value_interval.ub().number()};
165,824✔
564
    }
565

566
    // JN: it seems that we can only do this if
567
    // close_bounds_inline is disabled (which in eBPF is always the case).
568
    // Otherwise, the meet operator misses some non-redundant edges.
569
    if (value_interval.is_singleton()) {
238,282✔
570
        set(lhs, value_interval);
61,488✔
571
        normalize();
61,488✔
572
        return;
92,778✔
573
    }
574

575
    std::vector<std::pair<Variable, Weight>> diffs_lb, diffs_ub;
176,794✔
576
    // Construct difference constraints from the assignment
577
    diffcsts_of_assign(e, diffs_lb, diffs_ub);
176,794✔
578
    if (diffs_lb.empty() && diffs_ub.empty()) {
176,794✔
579
        set(lhs, value_interval);
546✔
580
        normalize();
819✔
581
        return;
546✔
582
    }
583

584
    const Weight e_val = eval_expression(e);
176,248✔
585

586
    std::vector<std::pair<VertId, Weight>> diffs_from, diffs_to;
176,248✔
587
    for (const auto& [var, n] : diffs_lb) {
354,150✔
588
        diffs_from.emplace_back(get_vert(var), -n);
177,902✔
589
    }
590
    for (const auto& [var, n] : diffs_ub) {
354,148✔
591
        diffs_to.emplace_back(get_vert(var), n);
177,900✔
592
    }
593

594
    VertId vert = core_->assign_vertex(core_->potential_at_zero() + e_val, diffs_from, diffs_to, lb_w,
440,620✔
595
                                       (ub_w && !variable_registry->is_min_only(lhs)) ? ub_w : std::nullopt);
176,248✔
596

597
    assert(vert <= rev_map_.size());
176,248✔
598
    if (vert == rev_map_.size()) {
176,248✔
599
        rev_map_.emplace_back(lhs);
65,092✔
600
    } else {
601
        rev_map_[vert] = lhs;
111,156✔
602
    }
603
    // Clear the old x vertex
604
    havoc(lhs);
176,248✔
605
    vert_map_.emplace(lhs, vert);
176,248✔
606

607
    normalize();
264,372✔
608
}
177,067✔
609

UNCOV
610
ZoneDomain ZoneDomain::narrow(const ZoneDomain& o) const {
×
611
    if (is_top()) {
×
612
        return o;
×
613
    }
614
    // FIXME: Implement properly
615
    // Narrowing as a no-op should be sound.
UNCOV
616
    return {*this};
×
617
}
618

619
void ZoneDomain::clear_thread_local_state() { SplitDBM::clear_thread_local_state(); }
1,442✔
620

621
void ZoneDomain::normalize() { core_->normalize(); }
967,544✔
622

623
void ZoneDomain::set(const Variable x, const Interval& intv) {
218,948✔
624
    assert(!intv.is_bottom());
218,948✔
625

626
    havoc(x);
218,948✔
627

628
    if (intv.is_top()) {
218,948✔
629
        return;
1,736✔
630
    }
631

632
    const VertId v = get_vert(x);
215,476✔
633
    if (intv.ub().is_finite() && !variable_registry->is_min_only(x)) {
215,476✔
634
        core_->set_bound(v, Side::RIGHT, Weight{*intv.ub().number()});
299,835✔
635
    }
636
    if (intv.lb().is_finite()) {
215,476✔
637
        core_->set_bound(v, Side::LEFT, Weight{*intv.lb().number()});
215,320✔
638
    }
639
    normalize();
215,476✔
640
}
641

UNCOV
642
void ZoneDomain::apply(const ArithBinOp op, const Variable x, const Variable y, const Variable z) {
×
643
    switch (op) {
×
644
    case ArithBinOp::ADD: assign(x, LinearExpression(y).plus(z)); break;
×
645
    case ArithBinOp::SUB: assign(x, LinearExpression(y).subtract(z)); break;
×
646
    // For the rest of operations, we fall back on intervals.
UNCOV
647
    case ArithBinOp::MUL: set(x, get_interval(y) * get_interval(z)); break;
×
648
    default: CRAB_ERROR("DBM: unreachable");
×
649
    }
UNCOV
650
    normalize();
×
651
}
×
652

UNCOV
653
void ZoneDomain::apply(const ArithBinOp op, const Variable x, const Variable y, const Number& k) {
×
654
    switch (op) {
×
655
    case ArithBinOp::ADD: assign(x, LinearExpression(y).plus(k)); break;
×
656
    case ArithBinOp::SUB: assign(x, LinearExpression(y).subtract(k)); break;
×
657
    case ArithBinOp::MUL: assign(x, LinearExpression(k, y)); break;
×
658
    }
UNCOV
659
    normalize();
×
660
}
×
661

UNCOV
662
void ZoneDomain::forget(const VariableVector& variables) {
×
663
    if (is_top()) {
×
664
        return;
665
    }
666

UNCOV
667
    for (const auto v : variables) {
×
668
        if (vert_map_.contains(v)) {
×
669
            havoc(v);
×
670
        }
671
    }
UNCOV
672
    normalize();
×
673
}
674

675
static std::string to_string(const Variable vd, const Variable vs, const Weight& w, const bool eq) {
224✔
676
    std::stringstream elem;
224✔
677
    if (eq) {
224✔
678
        if (w.operator>(0)) {
14✔
679
            elem << vd << "=" << vs << "+" << w;
14✔
UNCOV
680
        } else if (w.operator<(0)) {
×
681
            elem << vs << "=" << vd << "+" << -w;
×
682
        } else {
NEW
683
            const auto [left, right] = std::minmax(vs, vd, VariableRegistry::printing_order);
×
684
            elem << left << "=" << right;
×
685
        }
686
    } else {
687
        elem << vd << "-" << vs << "<=" << w;
210✔
688
    }
689
    return elem.str();
448✔
690
}
224✔
691

692
StringInvariant ZoneDomain::to_set() const {
1,002✔
693
    if (this->is_top()) {
1,002✔
694
        return StringInvariant::top();
34✔
695
    }
696

697
    const Graph& g = core_->graph();
968✔
698

699
    // Extract all the edges
700
    SubGraph g_excl{g, 0};
968✔
701

702
    std::map<Variable, Variable> equivalence_classes;
968✔
703
    std::set<std::tuple<Variable, Variable, Weight>> diff_csts;
968✔
704
    for (const VertId s : g_excl.verts()) {
4,624✔
705
        const Variable vs = *rev_map_.at(s);
3,656✔
706
        Variable least = vs;
3,656✔
707
        for (const VertId d : g_excl.succs(s)) {
7,401✔
708
            const Variable vd = *rev_map_.at(d);
1,656✔
709
            const Weight w = g_excl.edge_val(s, d);
1,656✔
710
            if (w == 0) {
1,656✔
711
                least = std::min(least, vd, VariableRegistry::printing_order);
2,100✔
712
            } else {
713
                diff_csts.emplace(vd, vs, w);
256✔
714
            }
715
        }
716
        equivalence_classes.insert_or_assign(vs, least);
3,656✔
717
    }
718

719
    std::set<Variable> representatives;
968✔
720
    std::set<std::string> result;
968✔
721
    for (const auto [vs, least] : equivalence_classes) {
4,624✔
722
        if (vs == least) {
3,656✔
723
            representatives.insert(least);
3,100✔
724
        } else {
725
            result.insert(variable_registry->name(vs) + "=" + variable_registry->name(least));
1,112✔
726
        }
727
    }
728

729
    // simplify: x - y <= k && y - x <= -k
730
    //        -> x <= y + k <= x
731
    //        -> x = y + k
732
    for (const auto& [vd, vs, w] : diff_csts) {
1,224✔
733
        if (!representatives.contains(vd) || !representatives.contains(vs)) {
256✔
734
            continue;
144✔
735
        }
736
        auto dual = to_string(vs, vd, -w, false);
112✔
737
        if (result.contains(dual)) {
112✔
738
            assert(w != 0);
14✔
739
            result.erase(dual);
14✔
740
            result.insert(to_string(vd, vs, w, true));
21✔
741
        } else {
742
            result.insert(to_string(vd, vs, w, false));
147✔
743
        }
744
    }
112✔
745

746
    // Intervals
747
    for (VertId v : g_excl.verts()) {
4,624✔
748
        const auto pvar = this->rev_map_[v];
3,656✔
749
        if (!pvar || !representatives.contains(*pvar)) {
3,656✔
750
            continue;
644✔
751
        }
752
        const bool has_lb = g.elem(v, 0);
3,100✔
753
        const bool has_ub = g.elem(0, v) && !variable_registry->is_min_only(*pvar);
3,100✔
754
        if (!has_lb && !has_ub) {
3,100✔
755
            continue;
88✔
756
        }
757
        Interval v_out{has_lb ? -Number(g.edge_val(v, 0)) : MINUS_INFINITY,
3,012✔
758
                       has_ub ? Number(g.edge_val(0, v)) : PLUS_INFINITY};
3,012✔
759
        assert(!v_out.is_bottom());
3,012✔
760

761
        Variable variable = *pvar;
3,012✔
762

763
        std::stringstream elem;
3,012✔
764
        elem << variable;
3,012✔
765
        if (variable_registry->is_min_only(variable)) {
3,012✔
766
            // One-sided variables: display just the lower bound
767
            elem << "=" << v_out.lb();
138✔
768
        } else {
769
            elem << "=";
2,874✔
770
            if (v_out.is_singleton()) {
2,874✔
771
                elem << v_out.lb();
2,144✔
772
            } else {
773
                elem << v_out;
730✔
774
            }
775
        }
776
        result.insert(elem.str());
3,012✔
777
    }
3,012✔
778

779
    return StringInvariant{std::move(result)};
968✔
780
}
968✔
781

782
std::ostream& operator<<(std::ostream& o, const ZoneDomain& dom) { return o << dom.to_set(); }
6✔
783

784
Weight ZoneDomain::eval_expression(const LinearExpression& e) const {
176,248✔
785
    Weight res = e.constant_term();
176,248✔
786
    for (const auto& [variable, coefficient] : e.variable_terms()) {
355,140✔
787
        res += (pot_value(variable) - core_->potential_at_zero()) * Weight{coefficient};
268,338✔
788
    }
789
    return res;
176,248✔
790
}
791

792
Interval ZoneDomain::compute_residual(const LinearExpression& e, const Variable pivot) const {
1,978✔
793
    Interval residual(-e.constant_term());
1,978✔
794
    for (const auto& [variable, coefficient] : e.variable_terms()) {
5,932✔
795
        if (variable != pivot) {
3,954✔
796
            residual = residual - Interval(coefficient) * get_interval(variable);
1,976✔
797
        }
798
    }
799
    return residual;
1,978✔
800
}
801

802
Weight ZoneDomain::pot_value(const Variable v) const {
178,892✔
803
    if (const auto y = try_at(vert_map_, v)) {
178,892✔
804
        return core_->potential_at(*y);
129,722✔
805
    }
806
    return {0};
49,170✔
807
}
808

809
Interval ZoneDomain::eval_interval(const LinearExpression& e) const {
1,128,436✔
810
    using namespace prevail::interval_operators;
564,218✔
811
    Interval r{e.constant_term()};
1,128,436✔
812
    for (const auto& [variable, coefficient] : e.variable_terms()) {
2,260,392✔
813
        r += coefficient * get_interval(variable);
1,131,956✔
814
    }
815
    return r;
1,128,436✔
816
}
817

818
bool ZoneDomain::intersect(const LinearConstraint& cst) const {
53,330✔
819
    if (cst.is_contradiction()) {
53,330✔
820
        return false;
821
    }
822
    if (is_top() || cst.is_tautology()) {
53,330✔
823
        return true;
162✔
824
    }
825
    return intersect_aux(cst);
53,168✔
826
}
827

828
bool ZoneDomain::entail(const LinearConstraint& rhs) const {
118,498✔
829
    if (rhs.is_tautology()) {
118,498✔
830
        return true;
45✔
831
    }
832
    if (rhs.is_contradiction()) {
118,408✔
833
        return false;
12✔
834
    }
835
    const Interval interval = eval_interval(rhs.expression());
118,384✔
836
    switch (rhs.kind()) {
118,384✔
837
    case ConstraintKind::EQUALS_ZERO:
25,416✔
838
        if (interval.singleton() == std::optional(Number(0))) {
25,417✔
839
            return true;
14,712✔
840
        }
841
        break;
5,352✔
842
    case ConstraintKind::LESS_THAN_OR_EQUALS_ZERO:
78,938✔
843
        if (interval.ub() <= Number(0)) {
118,407✔
844
            return true;
33,196✔
845
        }
846
        break;
6,273✔
847
    case ConstraintKind::LESS_THAN_ZERO:
13,966✔
848
        if (interval.ub() < Number(0)) {
13,966✔
849
            return true;
6,973✔
850
        }
851
        break;
10✔
852
    case ConstraintKind::NOT_ZERO:
64✔
853
        if (interval.ub() < Number(0) || interval.lb() > Number(0)) {
64✔
UNCOV
854
            return true;
×
855
        }
856
        break;
32✔
857
    }
858
    // TODO: copy the implementation from crab
859
    //       https://github.com/seahorn/crab/blob/master/include/crab/domains/split_dbm.hpp
860
    if (rhs.kind() == ConstraintKind::EQUALS_ZERO) {
23,334✔
861
        // try to convert the equality into inequalities so when it's
862
        // negated we do not have disequalities.
863
        return entail_aux(LinearConstraint(rhs.expression(), ConstraintKind::LESS_THAN_OR_EQUALS_ZERO)) &&
31,342✔
864
               entail_aux(LinearConstraint(rhs.expression().negate(), ConstraintKind::LESS_THAN_OR_EQUALS_ZERO));
24,450✔
865
    } else {
866
        return entail_aux(rhs);
12,630✔
867
    }
868

869
    // Note: we cannot convert rhs into ZoneDomain and then use the <=
870
    //       operator. The problem is that we cannot know for sure
871
    //       whether ZoneDomain can represent precisely rhs. It is not
872
    //       enough to do something like
873
    //
874
    //       ZoneDomain dom = rhs;
875
    //       if (dom.is_top()) { ... }
876
}
877

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