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

Alan-Jowett / ebpf-verifier / 21765175576

06 Feb 2026 11:02AM UTC coverage: 86.699% (-0.09%) from 86.792%
21765175576

push

github

web-flow
SplitDBM as one-sided numerical domain (#985)


Signed-off-by: Elazar Gershuni <elazarg@gmail.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

1316 of 1401 new or added lines in 15 files covered. (93.93%)

136 existing lines in 4 files now uncovered.

9334 of 10766 relevant lines covered (86.7%)

3133567.38 hits per line

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

91.3
/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 "crab_utils/debug.hpp"
9
#include "crab_utils/stats.hpp"
10
#include "string_constraints.hpp"
11
#include "type_encoding.hpp"
12

13
using namespace splitdbm;
14

15
namespace prevail {
16
// =============================================================================
17
// ZoneDomain constructors and assignment operators
18
// =============================================================================
19

20
ZoneDomain::ZoneDomain() : core_(std::make_unique<splitdbm::SplitDBM>()) { rev_map_.emplace_back(std::nullopt); }
3,566,061✔
21

22
ZoneDomain::~ZoneDomain() = default;
9,780,652✔
23

24
ZoneDomain::ZoneDomain(const ZoneDomain& o)
2,684,420✔
25
    : core_(std::make_unique<splitdbm::SplitDBM>(*o.core_)), vert_map_(o.vert_map_), rev_map_(o.rev_map_) {}
5,368,840✔
26

27
ZoneDomain::ZoneDomain(ZoneDomain&& o) noexcept = default;
6,929,184✔
28

29
ZoneDomain& ZoneDomain::operator=(const ZoneDomain& o) {
1,127,090✔
30
    if (this != &o) {
1,127,090✔
31
        core_ = std::make_unique<splitdbm::SplitDBM>(*o.core_);
1,127,090✔
32
        vert_map_ = o.vert_map_;
1,127,090✔
33
        rev_map_ = o.rev_map_;
1,127,090✔
34
    }
35
    return *this;
1,127,090✔
36
}
37

38
ZoneDomain& ZoneDomain::operator=(ZoneDomain&& o) noexcept = default;
198,592✔
39

40
ZoneDomain::ZoneDomain(VertMap&& vert_map, RevMap&& rev_map, std::unique_ptr<splitdbm::SplitDBM> core)
99,402✔
41
    : core_(std::move(core)), vert_map_(std::move(vert_map)), rev_map_(std::move(rev_map)) {
149,103✔
42
    normalize();
149,103✔
43
}
99,402✔
44

NEW
45
void ZoneDomain::set_to_top() {
×
NEW
46
    core_ = std::make_unique<splitdbm::SplitDBM>();
×
NEW
47
    vert_map_.clear();
×
NEW
48
    rev_map_.clear();
×
NEW
49
    rev_map_.emplace_back(std::nullopt);
×
NEW
50
}
×
51

52
bool ZoneDomain::is_top() const { return core_->is_top(); }
131,670✔
53

NEW
54
std::pair<std::size_t, std::size_t> ZoneDomain::size() const { return {core_->graph_size(), core_->num_edges()}; }
×
55

56
// =============================================================================
57
// End of splitdbm::SplitDBM and prevail::ZoneDomain basics
58
// =============================================================================
59

60
std::optional<VertId> ZoneDomain::get_vertid(const Variable x) const {
64,165,130✔
61
    const auto it = vert_map_.find(x);
64,165,130✔
62
    if (it == vert_map_.end()) {
96,257,082✔
63
        return {};
29,079,954✔
64
    }
65
    return it->second;
35,085,176✔
66
}
67

68
Bound ZoneDomain::get_lb(const std::optional<VertId>& v) const {
32,581,391✔
69
    return v ? core_->get_bound(*v, Side::LEFT) : MINUS_INFINITY;
32,581,391✔
70
}
71

72
Bound ZoneDomain::get_ub(const std::optional<VertId>& v) const {
31,583,739✔
73
    return v ? core_->get_bound(*v, Side::RIGHT) : PLUS_INFINITY;
31,583,739✔
74
}
75

76
Bound ZoneDomain::get_lb(const Variable x) const { return get_lb(get_vertid(x)); }
32,581,391✔
77

78
Bound ZoneDomain::get_ub(const Variable x) const {
31,822,191✔
79
    if (variable_registry->is_min_only(x)) {
31,822,191✔
80
        return PLUS_INFINITY;
238,452✔
81
    }
82
    return get_ub(get_vertid(x));
31,583,739✔
83
}
84

85
Interval ZoneDomain::get_interval(const Variable x) const { return {get_lb(x), get_ub(x)}; }
47,445,220✔
86

87
static std::optional<VertId> try_at(const ZoneDomain::VertMap& map, const Variable v) {
7,578,594✔
88
    const auto it = map.find(v);
3,789,293✔
89
    if (it == map.end()) {
7,578,594✔
90
        return std::nullopt;
2,849,898✔
91
    }
92
    return it->second;
4,728,696✔
93
}
94

95
VertId ZoneDomain::get_vert(Variable v) {
2,299,640✔
96
    if (const auto y = try_at(vert_map_, v)) {
2,299,640✔
97
        return *y;
1,210,562✔
98
    }
99

100
    VertId vert = core_->new_vertex();
1,089,078✔
101
    vert_map_.emplace(v, vert);
1,089,078✔
102
    // Initialize rev_map
103
    assert(vert <= rev_map_.size());
1,089,078✔
104
    if (vert < rev_map_.size()) {
1,089,078✔
105
        rev_map_[vert] = v;
482,498✔
106
    } else {
107
        rev_map_.push_back(v);
606,580✔
108
    }
109

110
    assert(vert != 0);
1,089,078✔
111

112
    return vert;
1,089,078✔
113
}
114

115
/**
116
 * Helper to translate from Number to DBM Weight (graph weights).  Number
117
 * used to be the template parameter of the DBM-based abstract domain to
118
 * represent a number. Number might not fit into Weight type.
119
 **/
120
[[maybe_unused]]
121
static bool convert_NtoW_overflow(const Number& n, SafeI64& out) {
122
    if (n.fits<int64_t>()) {
123
        out = n;
124
        return false;
125
    }
126
    return true;
127
}
128

129
[[maybe_unused]]
130
static bool convert_NtoW_overflow(const Number& n, Number& out) {
8,220,822✔
131
    out = n;
7,154,510✔
132
    return false;
8,220,822✔
133
}
134

135
void ZoneDomain::diffcsts_of_assign(const LinearExpression& exp, std::vector<std::pair<Variable, Weight>>& lb,
185,778✔
136
                                    std::vector<std::pair<Variable, Weight>>& ub) const {
137
    diffcsts_of_assign(exp, true, ub);
185,778✔
138
    diffcsts_of_assign(exp, false, lb);
185,778✔
139
}
185,778✔
140

141
void ZoneDomain::diffcsts_of_assign(const LinearExpression& exp,
371,556✔
142
                                    /* if true then process the upper
143
                                       bounds, else the lower bounds */
144
                                    bool extract_upper_bounds,
145
                                    /* foreach {v, k} \in diff_csts we have
146
                                       the difference constraint v - k <= k */
147
                                    std::vector<std::pair<Variable, Weight>>& diff_csts) const {
148

149
    std::optional<Variable> unbounded_var;
371,556✔
150
    std::vector<std::pair<Variable, Weight>> terms;
371,556✔
151

152
    Weight residual;
371,556✔
153
    if (convert_NtoW_overflow(exp.constant_term(), residual)) {
371,556✔
154
        return;
155
    }
156

157
    for (const auto& [y, n] : exp.variable_terms()) {
748,300✔
158
        Weight coeff;
377,832✔
159
        if (convert_NtoW_overflow(n, coeff)) {
377,832✔
160
            continue;
161
        }
162

163
        if (coeff < Weight(0)) {
377,832✔
164
            // Can't do anything with negative coefficients.
165
            auto y_val = extract_upper_bounds ? get_lb(y) : get_ub(y);
1,424✔
166

167
            if (y_val.is_infinite()) {
1,424✔
168
                return;
168✔
169
            }
170
            Weight ymax;
1,256✔
171
            if (convert_NtoW_overflow(*y_val.number(), ymax)) {
1,256✔
172
                continue;
173
            }
174
            // was before the condition
175
            residual += ymax * coeff;
1,884✔
176
        } else {
1,424✔
177
            auto y_val = extract_upper_bounds ? get_ub(y) : get_lb(y);
376,408✔
178

179
            if (y_val.is_infinite()) {
376,408✔
180
                if (unbounded_var || coeff != Weight(1)) {
212,882✔
181
                    return;
920✔
182
                }
183
                unbounded_var = y;
329,052✔
184
            } else {
185
                Weight ymax;
234,180✔
186
                if (convert_NtoW_overflow(*y_val.number(), ymax)) {
234,180✔
187
                    continue;
188
                }
189
                residual += ymax * coeff;
351,270✔
190
                terms.emplace_back(y, ymax);
234,180✔
191
            }
234,180✔
192
        }
376,408✔
193
    }
377,832✔
194

195
    if (unbounded_var) {
370,468✔
196
        // There is exactly one unbounded variable with unit
197
        // coefficient
198
        diff_csts.emplace_back(*unbounded_var, residual);
140,328✔
199
    } else {
200
        for (const auto& [v, n] : terms) {
463,578✔
201
            diff_csts.emplace_back(v, residual - n);
350,157✔
202
        }
203
    }
204
}
372,100✔
205

206
void ZoneDomain::diffcsts_of_lin_leq(const LinearExpression& exp,
1,519,368✔
207
                                     /* difference contraints */
208
                                     std::vector<diffcst_t>& csts,
209
                                     /* x >= lb for each {x,lb} in lbs */
210
                                     std::vector<std::pair<Variable, Weight>>& lbs,
211
                                     /* x <= ub for each {x,ub} in ubs */
212
                                     std::vector<std::pair<Variable, Weight>>& ubs) const {
213
    Weight exp_ub;
1,519,368✔
214
    if (convert_NtoW_overflow(exp.constant_term(), exp_ub)) {
1,519,368✔
215
        return;
216
    }
217
    exp_ub = -exp_ub;
2,279,050✔
218

219
    // temporary hack
220
    Weight _tmp;
1,519,368✔
221
    if (convert_NtoW_overflow(exp.constant_term() - 1, _tmp)) {
2,279,050✔
222
        // We don't like MIN either because the code will compute
223
        // minus MIN, and it will silently overflow.
224
        return;
225
    }
226

227
    Weight unbounded_lbcoeff;
1,519,368✔
228
    Weight unbounded_ubcoeff;
1,519,368✔
229
    std::optional<Variable> unbounded_lbvar;
1,519,368✔
230
    std::optional<Variable> unbounded_ubvar;
1,519,368✔
231

232
    std::vector<std::pair<std::pair<Weight, Variable>, Weight>> pos_terms, neg_terms;
1,519,368✔
233
    for (const auto& [y, n] : exp.variable_terms()) {
3,082,426✔
234
        Weight coeff;
1,566,928✔
235
        if (convert_NtoW_overflow(n, coeff)) {
1,566,928✔
236
            continue;
237
        }
238
        if (coeff > Weight(0)) {
1,566,928✔
239
            auto y_lb = get_lb(y);
759,200✔
240
            if (y_lb.is_infinite()) {
759,200✔
241
                if (unbounded_lbvar) {
633,422✔
NEW
242
                    return;
×
243
                }
244
                unbounded_lbvar = y;
633,422✔
245
                unbounded_lbcoeff = coeff;
1,013,020✔
246
            } else {
247
                Weight ymin;
125,778✔
248
                if (convert_NtoW_overflow(*y_lb.number(), ymin)) {
125,778✔
249
                    continue;
250
                }
251
                exp_ub -= ymin * coeff;
188,665✔
252
                pos_terms.push_back({{coeff, y}, ymin});
251,552✔
253
            }
125,778✔
254
        } else {
759,200✔
255
            auto y_ub = get_interval(y).ub();
1,211,590✔
256
            if (y_ub.is_infinite()) {
807,728✔
257
                if (unbounded_ubvar) {
61,738✔
258
                    return;
3,870✔
259
                }
260
                unbounded_ubvar = y;
57,868✔
261
                unbounded_ubcoeff = -coeff;
86,802✔
262
            } else {
263
                Weight ymax;
745,990✔
264
                if (convert_NtoW_overflow(*y_ub.number(), ymax)) {
745,990✔
265
                    continue;
266
                }
267
                exp_ub -= ymax * coeff;
1,118,983✔
268
                neg_terms.push_back({{-coeff, y}, ymax});
1,864,969✔
269
            }
745,990✔
270
        }
807,728✔
271
    }
1,564,993✔
272

273
    if (unbounded_lbvar) {
1,515,498✔
274
        Variable x{*unbounded_lbvar};
633,422✔
275
        if (unbounded_ubvar) {
633,422✔
276
            if (unbounded_lbcoeff == Weight(1) && unbounded_ubcoeff == Weight(1)) {
1,710✔
277
                csts.push_back({{x, *unbounded_ubvar}, exp_ub});
1,710✔
278
            }
279
        } else {
280
            if (unbounded_lbcoeff == Weight(1)) {
632,282✔
281
                for (const auto& [nv, k] : neg_terms) {
632,976✔
282
                    csts.push_back({{x, nv.second}, exp_ub - k});
1,041✔
283
                }
284
            }
285
            // Add bounds for x
286
            ubs.emplace_back(x, exp_ub / unbounded_lbcoeff);
948,423✔
287
        }
288
    } else {
289
        if (unbounded_ubvar) {
882,076✔
290
            Variable y{*unbounded_ubvar};
52,858✔
291
            if (unbounded_ubcoeff == Weight(1)) {
52,858✔
292
                for (const auto& [nv, k] : pos_terms) {
55,196✔
293
                    csts.push_back({{nv.second, y}, exp_ub + k});
3,507✔
294
                }
295
            }
296
            // Add bounds for y
297
            lbs.emplace_back(y, -exp_ub / unbounded_ubcoeff);
79,287✔
298
        } else {
299
            for (const auto& [neg_nv, neg_k] : neg_terms) {
1,548,122✔
300
                for (const auto& [pos_nv, pos_k] : pos_terms) {
732,028✔
301
                    csts.push_back({{pos_nv.second, neg_nv.second}, exp_ub - neg_k + pos_k});
19,684✔
302
                }
303
            }
304
            for (const auto& [neg_nv, neg_k] : neg_terms) {
1,548,122✔
305
                lbs.emplace_back(neg_nv.second, -exp_ub / neg_nv.first + neg_k);
1,078,354✔
306
            }
307
            for (const auto& [pos_nv, pos_k] : pos_terms) {
952,658✔
308
                ubs.emplace_back(pos_nv.second, exp_ub / pos_nv.first + pos_k);
185,158✔
309
            }
310
        }
311
    }
312
}
1,529,043✔
313

314
bool ZoneDomain::add_linear_leq(const LinearExpression& exp) {
1,519,368✔
315
    std::vector<std::pair<Variable, Weight>> lbs, ubs;
1,519,368✔
316
    std::vector<diffcst_t> csts;
1,519,368✔
317
    diffcsts_of_lin_leq(exp, csts, lbs, ubs);
1,519,368✔
318

319
    // Apply lower bounds
320
    for (const auto& [var, n] : lbs) {
2,286,618✔
321
        CRAB_LOG("zones-split", std::cout << var << ">=" << n << "\n");
771,762✔
322
        const VertId vert = get_vert(var);
771,762✔
323
        if (!core_->update_bound_if_tighter(vert, Side::LEFT, n)) {
771,762✔
324
            return false;
16,744✔
325
        }
326
    }
327

328
    // Apply upper bounds
329
    for (const auto& [var, n] : ubs) {
2,246,178✔
330
        if (variable_registry->is_min_only(var)) {
755,310✔
331
            continue;
14,084✔
332
        }
333
        CRAB_LOG("zones-split", std::cout << var << "<=" << n << "\n");
741,226✔
334
        const VertId vert = get_vert(var);
741,226✔
335
        if (!core_->update_bound_if_tighter(vert, Side::RIGHT, n)) {
741,226✔
336
            return false;
26,484✔
337
        }
338
    }
339

340
    // Apply difference constraints
341
    for (const auto& [diff, k] : csts) {
1,507,248✔
342
        CRAB_LOG("zones-split", std::cout << diff.first << "-" << diff.second << "<=" << k << "\n");
16,860✔
343
        const VertId src = get_vert(diff.second);
16,860✔
344
        const VertId dest = get_vert(diff.first);
16,860✔
345
        if (!core_->add_difference_constraint(src, dest, k)) {
16,860✔
346
            return false;
14,728✔
347
        }
348
    }
349

350
    core_->close_after_bound_updates();
1,490,388✔
351
    normalize();
2,250,070✔
352
    return true;
745,194✔
353
}
1,519,368✔
354

355
static Interval trim_interval(const Interval& i, const Number& n) {
6,740✔
356
    if (i.lb() == n) {
13,480✔
357
        return Interval{n + 1, i.ub()};
10,785✔
358
    }
359
    if (i.ub() == n) {
4,852✔
360
        return Interval{i.lb(), n - 1};
8✔
361
    }
362
    if (i.is_top() && n == 0) {
2,483✔
NEW
363
        return Interval{1, std::numeric_limits<uint64_t>::max()};
×
364
    }
365
    return i;
2,422✔
366
}
367

368
bool ZoneDomain::add_univar_disequation(Variable x, const Number& n) {
6,740✔
369
    Interval i = get_interval(x);
6,740✔
370
    Interval new_i = trim_interval(i, n);
6,740✔
371
    if (new_i.is_bottom()) {
6,740✔
372
        return false;
2,142✔
373
    }
374
    if (new_i.is_top() || !(new_i <= i)) {
2,456✔
375
        return true;
122✔
376
    }
377

378
    VertId v = get_vert(x);
2,334✔
379
    if (new_i.lb().is_finite()) {
3,501✔
380
        Weight lb_val;
2,334✔
381
        if (convert_NtoW_overflow(*new_i.lb().number(), lb_val)) {
4,668✔
382
            return true;
383
        }
384
        if (!core_->strengthen_bound(v, Side::LEFT, lb_val)) {
2,334✔
NEW
385
            return false;
×
386
        }
387
    }
2,334✔
388
    if (new_i.ub().is_finite() && !variable_registry->is_min_only(x)) {
4,668✔
389
        Weight ub_val;
2,334✔
390
        if (convert_NtoW_overflow(*new_i.ub().number(), ub_val)) {
4,668✔
391
            return true;
392
        }
393
        if (!core_->strengthen_bound(v, Side::RIGHT, ub_val)) {
2,334✔
NEW
394
            return false;
×
395
        }
396
    }
2,334✔
397
    normalize();
4,537✔
398
    return true;
1,167✔
399
}
10,110✔
400

401
bool ZoneDomain::operator<=(const ZoneDomain& o) const {
980✔
402
    // cover all trivial cases to avoid allocating a dbm matrix
403
    if (o.is_top()) {
980✔
404
        return true;
68✔
405
    }
406
    if (is_top()) {
844✔
407
        return false;
4✔
408
    }
409

410
    if (vert_map_.size() < o.vert_map_.size()) {
1,672✔
411
        return false;
21✔
412
    }
413

414
    // Build permutation mapping from o's vertices to this's vertices
415
    constexpr VertId INVALID_VERT = std::numeric_limits<VertId>::max();
794✔
416
    std::vector<VertId> perm(o.core_->graph_size(), INVALID_VERT);
794✔
417
    perm[0] = 0;
794✔
418
    for (const auto& [v, n] : o.vert_map_) {
3,565✔
419
        if (!o.core_->vertex_has_edges(n)) {
1,318✔
NEW
420
            continue;
×
421
        }
422

423
        // We can't have this <= o if we're missing some vertex.
424
        if (auto y = try_at(vert_map_, v)) {
1,318✔
425
            perm[n] = *y;
1,318✔
426
        } else {
NEW
427
            return false;
×
428
        }
429
    }
430

431
    return splitdbm::SplitDBM::is_subsumed_by(*core_, *o.core_, perm);
794✔
432
}
794✔
433

434
using RevMap = std::vector<std::optional<Variable>>;
435
using VertMap = boost::container::flat_map<Variable, VertId>;
436

437
[[nodiscard]]
438
std::pair<VertMap, RevMap> result_mappings(const RevMap& aligned_vars) {
99,402✔
439
    VertMap vmap;
49,701✔
440
    RevMap revmap = aligned_vars;
99,402✔
441

442
    for (size_t i = 1; i < aligned_vars.size(); ++i) {
2,959,852✔
443
        if (aligned_vars[i]) {
2,860,450✔
444
            vmap.emplace(*aligned_vars[i], gsl::narrow<VertId>(i));
5,720,900✔
445
        }
446
    }
447
    return {std::move(vmap), std::move(revmap)};
149,103✔
448
}
99,402✔
449

450
// =============================================================================
451
// Helpers for building AlignedPair from ZoneDomain operands
452
// =============================================================================
453

454
// Build alignment from intersection of variables (for join/widen)
455
std::tuple<AlignedPair, RevMap> ZoneDomain::make_intersection_alignment(const ZoneDomain& left,
99,356✔
456
                                                                        const ZoneDomain& right) {
457
    std::vector<VertId> perm_left = {0};
149,034✔
458
    std::vector<VertId> perm_right = {0};
149,034✔
459
    RevMap aligned_vars = {std::nullopt};
149,034✔
460

461
    for (const auto& [var, left_vert] : left.vert_map_) {
4,552,486✔
462
        if (auto right_vert = try_at(right.vert_map_, var)) {
2,902,516✔
463
            perm_left.push_back(left_vert);
2,858,060✔
464
            perm_right.push_back(*right_vert);
2,858,060✔
465
            aligned_vars.push_back(var);
2,858,060✔
466
        }
467
    }
468

469
    return std::make_tuple(AlignedPair{*left.core_, *right.core_, std::move(perm_left), std::move(perm_right)},
149,034✔
470
                           std::move(aligned_vars));
248,390✔
471
}
99,356✔
472

473
// Build alignment from union of variables (for meet)
474
std::tuple<AlignedPair, RevMap> ZoneDomain::make_union_alignment(const ZoneDomain& left, const ZoneDomain& right) {
46✔
475
    constexpr VertId NOT_PRESENT = static_cast<VertId>(-1);
46✔
476

477
    std::vector<VertId> perm_left = {0};
69✔
478
    std::vector<VertId> perm_right = {0};
69✔
479
    RevMap aligned_vars = {std::nullopt};
69✔
480
    std::vector initial_potentials = {Weight(0)};
138✔
481

482
    boost::container::flat_map<Variable, size_t> var_to_index;
23✔
483
    for (const auto& [var, left_vert] : left.vert_map_) {
308✔
484
        var_to_index.emplace(var, perm_left.size());
144✔
485
        perm_left.push_back(left_vert);
144✔
486
        perm_right.push_back(NOT_PRESENT);
144✔
487
        aligned_vars.push_back(var);
144✔
488
        initial_potentials.push_back(left.core_->potential_at(left_vert) - left.core_->potential_at_zero());
216✔
489
    }
490

491
    for (const auto& [var, right_vert] : right.vert_map_) {
3,641✔
492
        if (const auto it = var_to_index.find(var); it != var_to_index.end()) {
2,366✔
493
            perm_right[it->second] = right_vert;
180✔
494
        } else {
495
            perm_left.push_back(NOT_PRESENT);
2,246✔
496
            perm_right.push_back(right_vert);
2,246✔
497
            aligned_vars.push_back(var);
2,246✔
498
            initial_potentials.push_back(right.core_->potential_at(right_vert) - right.core_->potential_at_zero());
3,369✔
499
        }
500
    }
501

502
    return std::make_tuple(AlignedPair{*left.core_, *right.core_, std::move(perm_left), std::move(perm_right),
115✔
503
                                       std::move(initial_potentials)},
23✔
504
                           std::move(aligned_vars));
115✔
505
}
46✔
506

507
// =============================================================================
508
// ZoneDomain join/widen/meet operations using AlignedPair
509
// =============================================================================
510

511
ZoneDomain do_join(const ZoneDomain& left, const ZoneDomain& right) {
99,296✔
512
    if (right.is_top()) {
99,296✔
513
        return right;
36✔
514
    }
515
    if (left.is_top()) {
99,260✔
516
        return left;
72✔
517
    }
518

519
    // 1. Build alignment (intersection of variables)
520
    auto [aligned_pair, aligned_vars] = ZoneDomain::make_intersection_alignment(left, right);
99,188✔
521

522
    // 2. Execute graph join
523
    splitdbm::SplitDBM joined = splitdbm::SplitDBM::join(aligned_pair);
99,188✔
524

525
    // 3. Build result mappings and garbage collect
526
    auto [out_vmap, out_revmap] = result_mappings(std::move(aligned_vars));
99,188✔
527
    for (const VertId v : joined.get_disconnected_vertices()) {
138,734✔
528
        joined.forget(v);
39,546✔
529
        if (out_revmap[v]) {
39,546✔
530
            out_vmap.erase(*out_revmap[v]);
39,546✔
531
            out_revmap[v] = std::nullopt;
59,319✔
532
        }
533
    }
99,188✔
534

535
    return ZoneDomain(std::move(out_vmap), std::move(out_revmap),
99,188✔
536
                      std::make_unique<splitdbm::SplitDBM>(std::move(joined)));
148,782✔
537
}
148,782✔
538

NEW
539
void ZoneDomain::operator|=(const ZoneDomain& right) { *this = do_join(*this, right); }
×
540

541
ZoneDomain ZoneDomain::operator|(const ZoneDomain& right) const { return do_join(*this, right); }
99,296✔
542

543
ZoneDomain ZoneDomain::widen(const ZoneDomain& o) const {
168✔
544
    // 1. Build alignment (intersection of variables)
545
    auto [aligned_pair, aligned_vars] = make_intersection_alignment(*this, o);
168✔
546

547
    // 2. Execute graph widen
548
    auto result_core = std::make_unique<splitdbm::SplitDBM>(splitdbm::SplitDBM::widen(aligned_pair));
168✔
549

550
    // 3. Build result mappings
551
    auto [out_vmap, out_revmap] = result_mappings(aligned_vars);
168✔
552

553
    return ZoneDomain(std::move(out_vmap), std::move(out_revmap), std::move(result_core));
252✔
554
}
252✔
555

556
std::optional<ZoneDomain> ZoneDomain::meet(const ZoneDomain& o) const {
88✔
557
    if (is_top()) {
88✔
558
        return o;
12✔
559
    }
560
    if (o.is_top()) {
76✔
561
        return *this;
30✔
562
    }
563

564
    // 1. Build alignment (union of variables, with initial potentials)
565
    auto [aligned_pair, aligned_vars] = make_union_alignment(*this, o);
46✔
566

567
    // 2. Execute graph meet
568
    auto meet_result = splitdbm::SplitDBM::meet(aligned_pair);
46✔
569
    if (!meet_result) {
46✔
NEW
570
        return std::nullopt; // Infeasible
×
571
    }
572

573
    // 3. Build result mappings
574
    auto [out_vmap, out_revmap] = result_mappings(aligned_vars);
46✔
575

576
    ZoneDomain res(std::move(out_vmap), std::move(out_revmap),
46✔
577
                   std::make_unique<splitdbm::SplitDBM>(std::move(*meet_result)));
69✔
578
    CRAB_LOG("zones-split", std::cout << "Result meet:\n" << res << "\n");
46✔
579
    return res;
46✔
580
}
115✔
581

582
void ZoneDomain::havoc(const Variable v) {
2,187,248✔
583
    if (const auto y = try_at(vert_map_, v)) {
2,187,248✔
584
        core_->forget(*y);
520,194✔
585
        rev_map_[*y] = std::nullopt;
520,194✔
586
        vert_map_.erase(v);
520,194✔
587
        normalize();
1,613,818✔
588
    }
589
}
2,187,248✔
590

591
// return false if becomes bottom
592
bool ZoneDomain::add_constraint(const LinearConstraint& cst) {
863,592✔
593
    CrabStats::count("ZoneDomain.count.add_constraints");
1,295,390✔
594
    ScopedCrabStats __st__("ZoneDomain.add_constraints");
863,592✔
595

596
    if (cst.is_tautology()) {
863,592✔
597
        return true;
89✔
598
    }
599

600
    // g.check_adjs();
601

602
    if (cst.is_contradiction()) {
863,414✔
603
        return false;
515✔
604
    }
605

606
    switch (cst.kind()) {
862,384✔
607
    case ConstraintKind::LESS_THAN_OR_EQUALS_ZERO: {
148,514✔
608
        if (!add_linear_leq(cst.expression())) {
148,514✔
609
            return false;
13,990✔
610
        }
611
        //  g.check_adjs();
612
        CRAB_LOG("zones-split", std::cout << "--- " << cst << "\n" << *this << "\n");
120,534✔
613
        break;
60,267✔
614
    }
615
    case ConstraintKind::LESS_THAN_ZERO: {
42,328✔
616
        // We try to convert a strict to non-strict.
617
        // e < 0 --> e <= -1
618
        const auto nc = LinearConstraint(cst.expression().plus(1), ConstraintKind::LESS_THAN_OR_EQUALS_ZERO);
63,490✔
619
        if (!add_linear_leq(nc.expression())) {
42,328✔
620
            return false;
908✔
621
        }
622
        CRAB_LOG("zones-split", std::cout << "--- " << cst << "\n" << *this << "\n");
41,420✔
623
        break;
41,420✔
624
    }
42,328✔
625
    case ConstraintKind::EQUALS_ZERO: {
664,282✔
626
        const LinearExpression& exp = cst.expression();
664,282✔
627
        if (!add_linear_leq(exp) || !add_linear_leq(exp.negate())) {
1,328,499✔
628
            CRAB_LOG("zones-split", std::cout << " ~~> _|_" << "\n");
92✔
629
            return false;
92✔
630
        }
631
        // g.check_adjs();
632
        CRAB_LOG("zones-split", std::cout << "--- " << cst << "\n" << *this << "\n");
664,190✔
633
        break;
332,095✔
634
    }
635
    case ConstraintKind::NOT_ZERO: {
7,260✔
636
        // XXX: similar precision as the interval domain
637
        const LinearExpression& e = cst.expression();
7,260✔
638
        for (const auto& [variable, coefficient] : e.variable_terms()) {
11,172✔
639
            Interval i = compute_residual(e, variable) / Interval(coefficient);
12,294✔
640
            if (auto k = i.singleton()) {
8,196✔
641
                if (!add_univar_disequation(variable, *k)) {
6,740✔
642
                    return false;
4,284✔
643
                }
644
            }
8,196✔
645
        }
8,196✔
646
    } break;
1,488✔
647
    }
648
    return true;
414,560✔
649
}
863,592✔
650

651
void ZoneDomain::assign(Variable lhs, const LinearExpression& e) {
392,110✔
652
    CrabStats::count("ZoneDomain.count.assign");
588,165✔
653
    ScopedCrabStats __st__("ZoneDomain.assign");
392,110✔
654

655
    CRAB_LOG("zones-split", std::cout << "Before assign: " << *this << "\n");
392,110✔
656
    CRAB_LOG("zones-split", std::cout << lhs << ":=" << e << "\n");
392,110✔
657

658
    Interval value_interval = eval_interval(e);
392,110✔
659

660
    std::optional<Weight> lb_w, ub_w;
392,110✔
661
    if (value_interval.lb().is_finite()) {
588,165✔
662
        Weight tmp;
323,226✔
663
        if (convert_NtoW_overflow(-*value_interval.lb().number(), tmp)) {
808,065✔
664
            havoc(lhs);
665
            CRAB_LOG("zones-split", std::cout << "---" << lhs << ":=" << e << "\n" << *this << "\n");
666
            normalize();
667
            return;
668
        }
669
        lb_w = tmp;
323,226✔
670
    }
323,226✔
671
    if (value_interval.ub().is_finite()) {
588,165✔
672
        Weight tmp;
319,578✔
673
        if (convert_NtoW_overflow(*value_interval.ub().number(), tmp)) {
639,156✔
674
            havoc(lhs);
675
            CRAB_LOG("zones-split", std::cout << "---" << lhs << ":=" << e << "\n" << *this << "\n");
676
            normalize();
677
            return;
678
        }
679
        ub_w = tmp;
319,578✔
680
    }
319,578✔
681

682
    // JN: it seems that we can only do this if
683
    // close_bounds_inline is disabled (which in eBPF is always the case).
684
    // Otherwise, the meet operator misses some non-redundant edges.
685
    if (value_interval.is_singleton()) {
392,110✔
686
        set(lhs, value_interval);
206,332✔
687
        normalize();
206,604✔
688
        return;
103,166✔
689
    }
690

691
    std::vector<std::pair<Variable, Weight>> diffs_lb, diffs_ub;
185,778✔
692
    // Construct difference constraints from the assignment
693
    diffcsts_of_assign(e, diffs_lb, diffs_ub);
185,778✔
694
    if (diffs_lb.empty() && diffs_ub.empty()) {
185,778✔
695
        set(lhs, value_interval);
544✔
696
        normalize();
816✔
697
        return;
272✔
698
    }
699

700
    Weight e_val;
185,234✔
701
    if (eval_expression_overflow(e, e_val)) {
185,234✔
NEW
702
        havoc(lhs);
×
NEW
703
        return;
×
704
    }
705

706
    std::vector<std::pair<VertId, Weight>> diffs_from, diffs_to;
185,234✔
707
    for (const auto& [var, n] : diffs_lb) {
372,118✔
708
        diffs_from.emplace_back(get_vert(var), -n);
186,884✔
709
    }
710
    for (const auto& [var, n] : diffs_ub) {
372,116✔
711
        diffs_to.emplace_back(get_vert(var), n);
186,882✔
712
    }
713

714
    VertId vert = core_->assign_vertex(core_->potential_at_zero() + e_val, diffs_from, diffs_to, lb_w,
555,702✔
715
                                       (ub_w && !variable_registry->is_min_only(lhs)) ? ub_w : std::nullopt);
519,708✔
716

717
    assert(vert <= rev_map_.size());
185,234✔
718
    if (vert == rev_map_.size()) {
185,234✔
719
        rev_map_.push_back(lhs);
65,172✔
720
    } else {
721
        rev_map_[vert] = lhs;
120,062✔
722
    }
723
    // Clear the old x vertex
724
    havoc(lhs);
185,234✔
725
    vert_map_.emplace(lhs, vert);
185,234✔
726

727
    normalize();
277,851✔
728
    CRAB_LOG("zones-split", std::cout << "---" << lhs << ":=" << e << "\n" << *this << "\n");
185,234✔
729
}
899,295✔
730

NEW
731
ZoneDomain ZoneDomain::narrow(const ZoneDomain& o) const {
×
NEW
732
    CrabStats::count("ZoneDomain.count.narrowing");
×
NEW
733
    ScopedCrabStats __st__("ZoneDomain.narrowing");
×
734

NEW
735
    if (is_top()) {
×
NEW
736
        return o;
×
737
    }
738
    // FIXME: Implement properly
739
    // Narrowing as a no-op should be sound.
NEW
740
    return {*this};
×
NEW
741
}
×
742

743
void ZoneDomain::clear_thread_local_state() { splitdbm::SplitDBM::clear_thread_local_state(); }
1,408✔
744

745
void ZoneDomain::normalize() { core_->normalize(); }
2,692,844✔
746

747
void ZoneDomain::set(const Variable x, const Interval& intv) {
380,300✔
748
    CrabStats::count("ZoneDomain.count.assign");
570,450✔
749
    ScopedCrabStats __st__("ZoneDomain.assign");
380,300✔
750
    assert(!intv.is_bottom());
380,300✔
751

752
    havoc(x);
380,300✔
753

754
    if (intv.is_top()) {
380,300✔
755
        return;
3,468✔
756
    }
757

758
    const VertId v = get_vert(x);
376,832✔
759
    if (intv.ub().is_finite() && !variable_registry->is_min_only(x)) {
753,664✔
760
        Weight ub;
361,312✔
761
        if (convert_NtoW_overflow(*intv.ub().number(), ub)) {
722,624✔
762
            normalize();
763
            return;
764
        }
765
        core_->set_bound(v, Side::RIGHT, ub);
361,312✔
766
    }
361,312✔
767
    if (intv.lb().is_finite()) {
565,248✔
768
        Weight lb;
376,676✔
769
        if (convert_NtoW_overflow(*intv.lb().number(), lb)) {
753,352✔
770
            normalize();
771
            return;
772
        }
773
        core_->set_bound(v, Side::LEFT, lb);
376,676✔
774
    }
376,676✔
775
    normalize();
376,832✔
776
}
380,300✔
777

NEW
778
void ZoneDomain::apply(const ArithBinOp op, const Variable x, const Variable y, const Variable z) {
×
NEW
779
    switch (op) {
×
NEW
780
    case ArithBinOp::ADD: assign(x, LinearExpression(y).plus(z)); break;
×
NEW
781
    case ArithBinOp::SUB: assign(x, LinearExpression(y).subtract(z)); break;
×
782
    // For the rest of operations, we fall back on intervals.
NEW
783
    case ArithBinOp::MUL: set(x, get_interval(y) * get_interval(z)); break;
×
NEW
784
    default: CRAB_ERROR("DBM: unreachable");
×
785
    }
NEW
786
    normalize();
×
NEW
787
}
×
788

NEW
789
void ZoneDomain::apply(const ArithBinOp op, const Variable x, const Variable y, const Number& k) {
×
NEW
790
    switch (op) {
×
NEW
791
    case ArithBinOp::ADD: assign(x, LinearExpression(y).plus(k)); break;
×
NEW
792
    case ArithBinOp::SUB: assign(x, LinearExpression(y).subtract(k)); break;
×
NEW
793
    case ArithBinOp::MUL: assign(x, LinearExpression(k, y)); break;
×
794
    }
NEW
795
    normalize();
×
NEW
796
}
×
797

NEW
798
void ZoneDomain::forget(const VariableVector& variables) {
×
NEW
799
    if (is_top()) {
×
800
        return;
801
    }
802

NEW
803
    for (const auto v : variables) {
×
NEW
804
        if (vert_map_.contains(v)) {
×
NEW
805
            havoc(v);
×
806
        }
807
    }
NEW
808
    normalize();
×
809
}
810

811
static std::string to_string(const Variable vd, const Variable vs, const Weight& w, const bool eq) {
200✔
812
    std::stringstream elem;
200✔
813
    if (eq) {
200✔
814
        if (w.operator>(0)) {
8✔
815
            elem << vd << "=" << vs << "+" << w;
8✔
NEW
816
        } else if (w.operator<(0)) {
×
NEW
817
            elem << vs << "=" << vd << "+" << -w;
×
818
        } else {
NEW
819
            const auto [left, right] = std::minmax(vs, vd, variable_registry->printing_order);
×
NEW
820
            elem << left << "=" << right;
×
821
        }
822
    } else {
823
        elem << vd << "-" << vs << "<=" << w;
192✔
824
    }
825
    return elem.str();
400✔
826
}
200✔
827

828
StringInvariant ZoneDomain::to_set() const {
1,928✔
829
    if (this->is_top()) {
1,928✔
830
        return StringInvariant::top();
36✔
831
    }
832

833
    const Graph& g = core_->graph();
1,892✔
834

835
    // Extract all the edges
836
    SubGraph g_excl{g, 0};
1,892✔
837

838
    std::map<Variable, Variable> equivalence_classes;
1,892✔
839
    std::set<std::tuple<Variable, Variable, Weight>> diff_csts;
1,892✔
840
    for (const VertId s : g_excl.verts()) {
6,968✔
841
        const Variable vs = *rev_map_.at(s);
5,076✔
842
        Variable least = vs;
5,076✔
843
        for (const VertId d : g_excl.succs(s)) {
9,059✔
844
            const Variable vd = *rev_map_.at(d);
1,308✔
845
            const Weight w = g_excl.edge_val(s, d);
1,308✔
846
            if (w == 0) {
1,308✔
847
                least = std::min(least, vd, variable_registry->printing_order);
1,686✔
848
            } else {
849
                diff_csts.emplace(vd, vs, w);
184✔
850
            }
851
        }
1,308✔
852
        equivalence_classes.insert_or_assign(vs, least);
5,076✔
853
    }
854

855
    std::set<Variable> representatives;
1,892✔
856
    std::set<std::string> result;
1,892✔
857
    for (const auto [vs, least] : equivalence_classes) {
6,968✔
858
        if (vs == least) {
5,076✔
859
            representatives.insert(least);
4,586✔
860
        } else {
861
            result.insert(variable_registry->name(vs) + "=" + variable_registry->name(least));
980✔
862
        }
863
    }
864

865
    // simplify: x - y <= k && y - x <= -k
866
    //        -> x <= y + k <= x
867
    //        -> x = y + k
868
    for (const auto& [vd, vs, w] : diff_csts) {
2,076✔
869
        if (!representatives.contains(vd) || !representatives.contains(vs)) {
184✔
870
            continue;
84✔
871
        }
872
        auto dual = to_string(vs, vd, -w, false);
100✔
873
        if (result.contains(dual)) {
100✔
874
            assert(w != 0);
8✔
875
            result.erase(dual);
8✔
876
            result.insert(to_string(vd, vs, w, true));
12✔
877
        } else {
878
            result.insert(to_string(vd, vs, w, false));
138✔
879
        }
880
    }
100✔
881

882
    // Intervals
883
    for (VertId v : g_excl.verts()) {
6,968✔
884
        const auto pvar = this->rev_map_[v];
5,076✔
885
        if (!pvar || !representatives.contains(*pvar)) {
5,076✔
886
            continue;
638✔
887
        }
888
        const bool has_lb = g.elem(v, 0);
4,586✔
889
        const bool has_ub = g.elem(0, v) && !variable_registry->is_min_only(*pvar);
4,586✔
890
        if (!has_lb && !has_ub) {
4,586✔
891
            continue;
76✔
892
        }
893
        Interval v_out{has_lb ? -Number(g.edge_val(v, 0)) : MINUS_INFINITY,
13,528✔
894
                       has_ub ? Number(g.edge_val(0, v)) : PLUS_INFINITY};
11,199✔
895
        assert(!v_out.is_bottom());
4,510✔
896

897
        Variable variable = *pvar;
4,510✔
898

899
        std::stringstream elem;
4,510✔
900
        elem << variable;
4,510✔
901
        if (variable_registry->is_type(variable)) {
4,510✔
902
            auto [lb, ub] = v_out.bound(T_UNINIT, T_MAX);
1,690✔
903
            if (lb == ub) {
1,690✔
904
                if (variable_registry->is_in_stack(variable) && lb == T_NUM) {
1,670✔
905
                    // no need to show this
906
                    continue;
72✔
907
                }
908
                elem << "=" << lb;
1,598✔
909
            } else {
910
                elem << " in " << typeset_to_string(iterate_types(lb, ub));
30✔
911
            }
912
        } else if (variable_registry->is_min_only(variable)) {
2,820✔
913
            // One-sided variables: display just the lower bound
914
            elem << "=" << v_out.lb();
244✔
915
        } else {
916
            elem << "=";
2,698✔
917
            if (v_out.is_singleton()) {
2,698✔
918
                elem << v_out.lb();
4,056✔
919
            } else {
920
                elem << v_out;
670✔
921
            }
922
        }
923
        result.insert(elem.str());
4,438✔
924
    }
6,801✔
925

926
    return StringInvariant{std::move(result)};
1,892✔
927
}
1,892✔
928

NEW
929
std::ostream& operator<<(std::ostream& o, const ZoneDomain& dom) { return o << dom.to_set(); }
×
930

931
bool ZoneDomain::eval_expression_overflow(const LinearExpression& e, Weight& out) const {
185,234✔
932
    [[maybe_unused]]
933
    const bool overflow = convert_NtoW_overflow(e.constant_term(), out);
185,234✔
934
    assert(!overflow);
185,234✔
935
    for (const auto& [variable, coefficient] : e.variable_terms()) {
373,106✔
936
        Weight coef;
187,872✔
937
        if (convert_NtoW_overflow(coefficient, coef)) {
187,872✔
938
            out = Weight(0);
939
            return true;
940
        }
941
        out += (pot_value(variable) - core_->potential_at_zero()) * coef;
281,808✔
942
    }
187,872✔
943
    return false;
185,234✔
944
}
945

946
Interval ZoneDomain::compute_residual(const LinearExpression& e, const Variable pivot) const {
8,196✔
947
    Interval residual(-e.constant_term());
8,196✔
948
    for (const auto& [variable, coefficient] : e.variable_terms()) {
18,368✔
949
        if (variable != pivot) {
10,172✔
950
            residual = residual - Interval(coefficient) * get_interval(variable);
5,928✔
951
        }
952
    }
953
    return residual;
8,196✔
NEW
954
}
×
955

956
Weight ZoneDomain::pot_value(const Variable v) const {
187,872✔
957
    if (const auto y = try_at(vert_map_, v)) {
187,872✔
958
        return core_->potential_at(*y);
138,562✔
959
    }
960
    return {0};
49,310✔
961
}
962

963
Interval ZoneDomain::eval_interval(const LinearExpression& e) const {
30,622,932✔
964
    using namespace prevail::interval_operators;
15,309,011✔
965
    Interval r{e.constant_term()};
30,622,932✔
966
    for (const auto& [variable, coefficient] : e.variable_terms()) {
61,135,257✔
967
        r += coefficient * get_interval(variable);
61,015,269✔
968
    }
969
    return r;
30,622,932✔
NEW
970
}
×
971

972
bool ZoneDomain::intersect(const LinearConstraint& cst) const {
60,868✔
973
    if (cst.is_contradiction()) {
60,868✔
974
        return false;
975
    }
976
    if (is_top() || cst.is_tautology()) {
60,868✔
977
        return true;
162✔
978
    }
979
    return intersect_aux(cst);
60,706✔
980
}
981

982
bool ZoneDomain::entail(const LinearConstraint& rhs) const {
1,824,643✔
983
    if (rhs.is_tautology()) {
1,824,643✔
984
        return true;
123✔
985
    }
986
    if (rhs.is_contradiction()) {
1,824,443✔
987
        return false;
10✔
988
    }
989
    const Interval interval = eval_interval(rhs.expression());
1,824,423✔
990
    switch (rhs.kind()) {
1,824,423✔
991
    case ConstraintKind::EQUALS_ZERO:
150,007✔
992
        if (interval.singleton() == std::optional(Number(0))) {
150,007✔
993
            return true;
75,163✔
994
        }
995
        break;
2,076✔
996
    case ConstraintKind::LESS_THAN_OR_EQUALS_ZERO:
131,396✔
997
        if (interval.ub() <= Number(0)) {
197,094✔
998
            return true;
59,415✔
999
        }
1000
        break;
6,283✔
1001
    case ConstraintKind::LESS_THAN_ZERO:
13,934✔
1002
        if (interval.ub() < Number(0)) {
20,901✔
1003
            return true;
6,959✔
1004
        }
1005
        break;
8✔
1006
    case ConstraintKind::NOT_ZERO:
1,529,086✔
1007
        if (interval.ub() < Number(0) || interval.lb() > Number(0)) {
6,577,317✔
1008
            return true;
461,657✔
1009
        }
1010
        break;
302,886✔
1011
    }
1012
    // TODO: copy the implementation from crab
1013
    //       https://github.com/seahorn/crab/blob/master/include/crab/domains/split_dbm.hpp
1014
    if (rhs.kind() == ConstraintKind::EQUALS_ZERO) {
622,504✔
1015
        // try to convert the equality into inequalities so when it's
1016
        // negated we do not have disequalities.
1017
        return entail_aux(LinearConstraint(rhs.expression(), ConstraintKind::LESS_THAN_OR_EQUALS_ZERO)) &&
8,383✔
1018
               entail_aux(LinearConstraint(rhs.expression().negate(), ConstraintKind::LESS_THAN_OR_EQUALS_ZERO));
4,233✔
1019
    } else {
1020
        return entail_aux(rhs);
618,354✔
1021
    }
1022

1023
    // Note: we cannot convert rhs into ZoneDomain and then use the <=
1024
    //       operator. The problem is that we cannot know for sure
1025
    //       whether ZoneDomain can represent precisely rhs. It is not
1026
    //       enough to do something like
1027
    //
1028
    //       ZoneDomain dom = rhs;
1029
    //       if (dom.is_top()) { ... }
1030
}
1,824,423✔
1031

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