• 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

90.53
/src/crab/type_domain.cpp
1
// Copyright (c) Prevail Verifier contributors.
2
// SPDX-License-Identifier: MIT
3

4
// This file is eBPF-specific, not derived from CRAB.
5
#include <algorithm>
6
#include <cassert>
7
#include <map>
8
#include <optional>
9
#include <set>
10
#include <sstream>
11

12
#include "arith/variable.hpp"
13
#include "crab/dsu.hpp"
14
#include "crab/type_domain.hpp"
15
#include "crab/type_encoding.hpp"
16
#include "crab/var_id_map.hpp"
17
#include "crab/var_registry.hpp"
18
#include "crab_utils/debug.hpp"
19

20
#include <ranges>
21

22
namespace prevail {
23

24
// ============================================================================
25
// DataKind utilities
26
// ============================================================================
27

28
static void operator++(DataKind& t) { t = static_cast<DataKind>(1 + static_cast<std::underlying_type_t<DataKind>>(t)); }
255,140✔
29

30
std::vector<DataKind> iterate_kinds(const DataKind lb, const DataKind ub) {
25,514✔
31
    if (lb > ub) {
25,514✔
32
        CRAB_ERROR("lower bound ", lb, " is greater than upper bound ", ub);
×
33
    }
34
    if (lb < KIND_MIN || ub > KIND_MAX) {
25,514✔
UNCOV
35
        CRAB_ERROR("bounds ", lb, " and ", ub, " are out of range");
×
36
    }
37
    std::vector<DataKind> res;
25,514✔
38
    for (DataKind i = lb; i <= ub; ++i) {
280,654✔
39
        res.push_back(i);
255,140✔
40
    }
41
    return res;
25,514✔
UNCOV
42
}
×
43

44
// ============================================================================
45
// TypeSet constants
46
// ============================================================================
47

48
const TypeSet TS_NUM{T_NUM};
49
const TypeSet TS_MAP{T_MAP};
50
const TypeSet TS_POINTER{T_CTX, T_PACKET, T_STACK, T_SHARED};
51
const TypeSet TS_SINGLETON_PTR{T_CTX, T_PACKET, T_STACK};
52
const TypeSet TS_MEM{T_PACKET, T_STACK, T_SHARED};
53

54
// ============================================================================
55
// TypeEncoding utilities
56
// ============================================================================
57

58
static constexpr auto S_UNINIT = "uninit";
59
static constexpr auto S_STACK = "stack";
60
static constexpr auto S_PACKET = "packet";
61
static constexpr auto S_CTX = "ctx";
62
static constexpr auto S_MAP_PROGRAMS = "map_fd_programs";
63
static constexpr auto S_MAP = "map_fd";
64
static constexpr auto S_NUM = "number";
65
static constexpr auto S_SHARED = "shared";
66

67
std::string name_of(const DataKind kind) {
10,144,078✔
68
    switch (kind) {
10,144,078✔
69
    case DataKind::ctx_offsets: return "ctx_offset";
1,833,080✔
70
    case DataKind::map_fds: return "map_fd";
1,793,460✔
71
    case DataKind::map_fd_programs: return "map_fd_programs";
1,792,032✔
72
    case DataKind::packet_offsets: return "packet_offset";
1,802,524✔
73
    case DataKind::shared_offsets: return "shared_offset";
1,803,728✔
74
    case DataKind::shared_region_sizes: return "shared_region_size";
1,803,728✔
75
    case DataKind::stack_numeric_sizes: return "stack_numeric_size";
1,920,784✔
76
    case DataKind::stack_offsets: return "stack_offset";
1,919,576✔
77
    case DataKind::svalues: return "svalue";
1,806,908✔
78
    case DataKind::types: return "type";
2,066,532✔
79
    case DataKind::uvalues: return "uvalue";
1,754,892✔
80
    }
UNCOV
81
    return {};
×
82
}
83

84
DataKind regkind(const std::string& s) {
4,674✔
85
    static const std::map<std::string, DataKind> string_to_kind{
2,337✔
86
        {"type", DataKind::types},
1✔
87
        {"ctx_offset", DataKind::ctx_offsets},
1✔
88
        {"map_fd", DataKind::map_fds},
1✔
89
        {"map_fd_programs", DataKind::map_fd_programs},
1✔
90
        {"packet_offset", DataKind::packet_offsets},
1✔
91
        {"shared_offset", DataKind::shared_offsets},
1✔
92
        {"stack_offset", DataKind::stack_offsets},
1✔
93
        {"shared_region_size", DataKind::shared_region_sizes},
1✔
94
        {"stack_numeric_size", DataKind::stack_numeric_sizes},
1✔
95
        {"svalue", DataKind::svalues},
1✔
96
        {"uvalue", DataKind::uvalues},
1✔
97
    };
4,699✔
98
    if (string_to_kind.contains(s)) {
4,674✔
99
        return string_to_kind.at(s);
4,674✔
100
    }
UNCOV
101
    throw std::runtime_error(std::string() + "Bad kind: " + s);
×
102
}
2✔
103

104
std::ostream& operator<<(std::ostream& os, const TypeEncoding s) {
1,826✔
105
    switch (s) {
1,826✔
106
    case T_SHARED: return os << S_SHARED;
78✔
107
    case T_STACK: return os << S_STACK;
264✔
108
    case T_PACKET: return os << S_PACKET;
86✔
109
    case T_CTX: return os << S_CTX;
82✔
110
    case T_NUM: return os << S_NUM;
1,280✔
111
    case T_MAP: return os << S_MAP;
32✔
112
    case T_MAP_PROGRAMS: return os << S_MAP_PROGRAMS;
4✔
UNCOV
113
    case T_UNINIT: return os << S_UNINIT;
×
UNCOV
114
    default: CRAB_ERROR("Unsupported type encoding", s);
×
115
    }
116
}
117

118
TypeEncoding string_to_type_encoding(const std::string& s) {
2,678✔
119
    static std::map<std::string, TypeEncoding> string_to_type{
1,339✔
120
        {S_UNINIT, T_UNINIT}, {S_MAP_PROGRAMS, T_MAP_PROGRAMS},
1✔
121
        {S_MAP, T_MAP},       {S_NUM, T_NUM},
2✔
122
        {S_CTX, T_CTX},       {S_STACK, T_STACK},
2✔
123
        {S_PACKET, T_PACKET}, {S_SHARED, T_SHARED},
2✔
124
    };
2,697✔
125
    if (string_to_type.contains(s)) {
2,678✔
126
        return string_to_type[s];
2,678✔
127
    }
UNCOV
128
    throw std::runtime_error(std::string("Unsupported type name: ") + s);
×
129
}
2✔
130

131
std::optional<TypeEncoding> int_to_type_encoding(const int v) {
32,942✔
132
    if (v >= 0 && v < static_cast<int>(NUM_TYPE_ENCODINGS)) {
16,471✔
133
        return static_cast<TypeEncoding>(v);
16,471✔
134
    }
UNCOV
135
    return std::nullopt;
×
136
}
137

138
std::string typeset_to_string(const std::vector<TypeEncoding>& items) {
50✔
139
    std::stringstream ss;
50✔
140
    ss << "{";
50✔
141
    for (auto it = items.begin(); it != items.end(); ++it) {
168✔
142
        ss << *it;
118✔
143
        if (std::next(it) != items.end()) {
177✔
144
            ss << ", ";
68✔
145
        }
146
    }
147
    ss << "}";
50✔
148
    return ss.str();
100✔
149
}
50✔
150

151
// ============================================================================
152
// TypeSet methods
153
// ============================================================================
154

155
std::optional<TypeEncoding> TypeSet::as_singleton() const {
1,238,648✔
156
    if (!is_singleton()) {
1,238,648✔
157
        return std::nullopt;
858,468✔
158
    }
159
    for (unsigned i = 0; i < NUM_TYPE_ENCODINGS; i++) {
2,023,204✔
160
        if (contains(static_cast<TypeEncoding>(i))) {
2,023,204✔
161
            return static_cast<TypeEncoding>(i);
380,180✔
162
        }
163
    }
UNCOV
164
    return std::nullopt; // unreachable
×
165
}
166

167
std::vector<TypeEncoding> TypeSet::to_vector() const {
1,465,470✔
168
    std::vector<TypeEncoding> result;
1,465,470✔
169
    for (unsigned i = 0; i < NUM_TYPE_ENCODINGS; i++) {
13,189,230✔
170
        if (contains(static_cast<TypeEncoding>(i))) {
11,723,760✔
171
            result.push_back(static_cast<TypeEncoding>(i));
10,618,788✔
172
        }
173
    }
174
    return result;
1,465,470✔
UNCOV
175
}
×
176

177
std::string TypeSet::to_string() const {
1,742✔
178
    const auto items = to_vector();
1,742✔
179
    if (items.size() == 1) {
1,742✔
180
        std::stringstream ss;
1,708✔
181
        ss << items[0];
1,708✔
182
        return ss.str();
1,708✔
183
    }
1,708✔
184
    return typeset_to_string(items);
34✔
185
}
1,742✔
186

187
// ============================================================================
188
// TypeGroup utilities
189
// ============================================================================
190

191
TypeSet to_typeset(const TypeGroup group) {
127,586✔
192
    switch (group) {
127,586✔
193
    case TypeGroup::number: return TypeSet{T_NUM};
66,596✔
194
    case TypeGroup::map_fd: return TypeSet{T_MAP};
4,524✔
195
    case TypeGroup::ctx: return TypeSet{T_CTX};
5,804✔
UNCOV
196
    case TypeGroup::packet: return TypeSet{T_PACKET};
×
UNCOV
197
    case TypeGroup::stack: return TypeSet{T_STACK};
×
UNCOV
198
    case TypeGroup::shared: return TypeSet{T_SHARED};
×
199
    case TypeGroup::map_fd_programs: return TypeSet{T_MAP_PROGRAMS};
474✔
200
    case TypeGroup::ctx_or_num: return TypeSet{T_NUM, T_CTX};
48✔
UNCOV
201
    case TypeGroup::stack_or_num: return TypeSet{T_NUM, T_STACK};
×
202
    case TypeGroup::mem: return TypeSet{T_PACKET, T_STACK, T_SHARED};
7,778✔
203
    case TypeGroup::mem_or_num: return TypeSet{T_NUM, T_PACKET, T_STACK, T_SHARED};
972✔
204
    case TypeGroup::pointer: return TypeSet{T_CTX, T_PACKET, T_STACK, T_SHARED};
21,504✔
205
    case TypeGroup::ptr_or_num: return TypeSet{T_NUM, T_CTX, T_PACKET, T_STACK, T_SHARED};
19,886✔
UNCOV
206
    case TypeGroup::stack_or_packet: return TypeSet{T_STACK, T_PACKET};
×
UNCOV
207
    case TypeGroup::singleton_ptr: return TypeSet{T_CTX, T_PACKET, T_STACK};
×
208
    default: CRAB_ERROR("Unsupported type group", group);
×
209
    }
210
}
211

212
bool is_singleton_type(const TypeGroup t) {
224✔
213
    switch (t) {
224✔
214
    case TypeGroup::number:
91✔
215
    case TypeGroup::map_fd:
216
    case TypeGroup::map_fd_programs:
217
    case TypeGroup::ctx:
218
    case TypeGroup::packet:
219
    case TypeGroup::stack:
220
    case TypeGroup::shared: return true;
91✔
221
    default: return false;
42✔
222
    }
223
}
224

225
std::ostream& operator<<(std::ostream& os, const TypeGroup ts) {
234✔
226
    using namespace prevail;
117✔
227
    static const std::map<TypeGroup, std::string> string_to_type{
117✔
228
        {TypeGroup::number, S_NUM},
1✔
229
        {TypeGroup::map_fd, S_MAP},
1✔
230
        {TypeGroup::map_fd_programs, S_MAP_PROGRAMS},
1✔
231
        {TypeGroup::ctx, S_CTX},
1✔
232
        {TypeGroup::ctx_or_num, typeset_to_string({T_NUM, T_CTX})},
4✔
233
        {TypeGroup::packet, S_PACKET},
2✔
234
        {TypeGroup::stack, S_STACK},
1✔
235
        {TypeGroup::stack_or_num, typeset_to_string({T_NUM, T_STACK})},
4✔
236
        {TypeGroup::shared, S_SHARED},
2✔
237
        {TypeGroup::mem, typeset_to_string({T_STACK, T_PACKET, T_SHARED})},
4✔
238
        {TypeGroup::pointer, typeset_to_string({T_CTX, T_STACK, T_PACKET, T_SHARED})},
4✔
239
        {TypeGroup::ptr_or_num, typeset_to_string({T_NUM, T_CTX, T_STACK, T_PACKET, T_SHARED})},
4✔
240
        {TypeGroup::stack_or_packet, typeset_to_string({T_STACK, T_PACKET})},
4✔
241
        {TypeGroup::singleton_ptr, typeset_to_string({T_CTX, T_STACK, T_PACKET})},
4✔
242
        {TypeGroup::mem_or_num, typeset_to_string({T_NUM, T_STACK, T_PACKET, T_SHARED})},
4✔
243
    };
269✔
244
    if (string_to_type.contains(ts)) {
234✔
245
        return os << string_to_type.at(ts);
234✔
246
    }
UNCOV
247
    CRAB_ERROR("Unsupported type group", ts);
×
248
}
18✔
249

250
// ============================================================================
251
// TypeDomain -- DSU-based implementation
252
// ============================================================================
253

254
Variable reg_type(const Reg& lhs) { return variable_registry->type_reg(lhs.v); }
992,812✔
255

256
// -- State definition --------------------------------------------------------
257

258
struct TypeDomain::State {
259
    DisjointSetUnion dsu;
260
    VarIdMap var_ids;
261
    std::vector<TypeSet> class_types;
262

263
    State();
264

265
    // Internal helpers
266
    void merge_if_singleton(size_t id);
267
    size_t ensure_var(Variable v);
268
    void detach(Variable v);
269

270
    // Queries
271
    [[nodiscard]]
272
    TypeSet get_typeset(Variable v) const;
273
    [[nodiscard]]
274
    bool same_type(Variable a, Variable b) const;
275
    [[nodiscard]]
276
    StringInvariant to_set() const;
277

278
    // Mutations returning false on empty TypeSet (caller sets bottom)
279
    bool restrict_var(Variable v, TypeSet mask);
280
    bool assume_eq(Variable v1, Variable v2);
281
    bool remove_type(Variable v, TypeEncoding te);
282
    bool assign_from_expr(Variable lhs, const LinearExpression& expr);
283

284
    // Mutations that never produce bottom
285
    void assign_copy(Variable lhs, Variable rhs);
286
    void assign_encoding(Variable v, TypeEncoding te);
287

288
    // Lattice
289
    [[nodiscard]]
290
    static State join(const State& a, const State& b);
291
    [[nodiscard]]
292
    std::optional<State> meet(const State& other) const;
293
    [[nodiscard]]
294
    bool is_subsumed_by(const State& other) const;
295
};
296

297
TypeDomain::State::State() {
1,307,548✔
298
    dsu = DisjointSetUnion{NUM_TYPE_ENCODINGS};
1,307,548✔
299
    var_ids = VarIdMap{};
1,307,548✔
300
    class_types.resize(NUM_TYPE_ENCODINGS);
1,307,548✔
301
    for (const TypeEncoding te : TypeSet::all().to_vector()) {
11,767,932✔
302
        class_types[type_to_bit(te)] = TypeSet{te};
10,460,384✔
303
    }
1,307,548✔
304
}
1,307,548✔
305

306
// -- Special member functions ------------------------------------------------
307

308
TypeDomain::TypeDomain() : state_(std::make_unique<State>()) {}
1,232,561✔
309

310
TypeDomain::~TypeDomain() = default;
3,619,514✔
311

312
TypeDomain::TypeDomain(const TypeDomain& other)
707,696✔
313
    : state_(other.state_ ? std::make_unique<State>(*other.state_) : nullptr) {}
707,695✔
314

315
TypeDomain::TypeDomain(TypeDomain&& other) noexcept = default;
839,613✔
316

317
TypeDomain& TypeDomain::operator=(const TypeDomain& other) {
1,092,888✔
318
    if (this != &other) {
1,092,888✔
319
        state_ = other.state_ ? std::make_unique<State>(*other.state_) : nullptr;
1,092,888✔
320
    }
321
    return *this;
1,092,888✔
322
}
323

324
TypeDomain& TypeDomain::operator=(TypeDomain&& other) noexcept = default;
49,844✔
325

326
// -- State: internal helpers -------------------------------------------------
327

328
void TypeDomain::State::merge_if_singleton(const size_t id) {
1,224,398✔
329
    const size_t rep = dsu.find(id);
1,224,398✔
330
    const TypeSet ts = class_types[rep];
1,224,398✔
331
    if (const auto te = ts.as_singleton()) {
1,224,398✔
332
        const size_t sentinel = type_to_bit(*te);
366,034✔
333
        const size_t sentinel_rep = dsu.find(sentinel);
366,034✔
334
        if (rep != sentinel_rep) {
366,034✔
335
            const size_t new_rep = dsu.unite(rep, sentinel);
301,180✔
336
            class_types[new_rep] = ts;
301,180✔
337
        }
338
    }
339
}
1,224,398✔
340

341
size_t TypeDomain::State::ensure_var(const Variable v) {
2,033,220✔
342
    if (const auto existing = var_ids.find_id(v)) {
2,033,220✔
343
        return *existing;
73,364✔
344
    }
345
    const size_t id = dsu.push();
1,959,856✔
346
    var_ids.insert(v, id);
1,959,856✔
347
    class_types.push_back(TypeSet::all());
1,959,856✔
348
    assert(class_types.size() == dsu.size());
1,959,856✔
349
    assert(var_ids.id_capacity() == dsu.size());
1,959,856✔
350
    return id;
1,959,856✔
351
}
352

353
void TypeDomain::State::detach(const Variable v) {
214,978✔
354
    var_ids.orphan_var(v);
214,978✔
355
    const size_t new_id = dsu.push();
214,978✔
356
    var_ids.insert(v, new_id);
214,978✔
357
    class_types.push_back(TypeSet::all());
214,978✔
358
    assert(class_types.size() == dsu.size());
214,978✔
359
    assert(var_ids.id_capacity() == dsu.size());
214,978✔
360
}
214,978✔
361

362
// -- State: queries ----------------------------------------------------------
363

364
TypeSet TypeDomain::State::get_typeset(const Variable v) const {
33,273,778✔
365
    if (const auto id = var_ids.find_id(v)) {
33,273,778✔
366
        const size_t rep = dsu.find_const(*id);
29,584,744✔
367
        return class_types[rep];
29,584,744✔
368
    }
369
    return TypeSet::all(); // unknown variable = top
3,689,034✔
370
}
371

372
bool TypeDomain::State::same_type(const Variable a, const Variable b) const {
12,738✔
373
    const auto id_a = var_ids.find_id(a);
12,738✔
374
    const auto id_b = var_ids.find_id(b);
12,738✔
375
    if (id_a && id_b) {
12,738✔
376
        return dsu.find_const(*id_a) == dsu.find_const(*id_b);
20,928✔
377
    }
378
    return false;
1✔
379
}
380

381
StringInvariant TypeDomain::State::to_set() const {
1,010✔
382
    // Group variables by DSU representative.
383
    std::map<size_t, std::vector<Variable>> classes;
1,010✔
384
    for (const auto& [v, id] : var_ids.vars()) {
3,340✔
385
        const size_t rep = dsu.find_const(id);
2,330✔
386
        classes[rep].push_back(v);
2,330✔
387
    }
388

389
    std::set<std::string> result;
1,010✔
390

391
    for (const auto& members : classes | std::views::values) {
2,772✔
392
        const TypeSet ts = get_typeset(members[0]);
1,762✔
393
        if (ts == TypeSet::all()) {
1,762✔
394
            continue; // top = no constraint
476✔
395
        }
396

397
        // Sort members for deterministic output
398
        std::vector<Variable> sorted = members;
1,286✔
399
        std::ranges::sort(sorted, VariableRegistry::printing_order);
1,286✔
400

401
        if (const auto te = ts.as_singleton()) {
1,286✔
402
            for (const Variable& m : sorted) {
3,040✔
403
                // Stack type variables with type=number are implicit (not printed)
404
                if (*te == T_NUM && variable_registry->is_in_stack(m)) {
1,788✔
405
                    continue;
80✔
406
                }
407
                result.insert(variable_registry->name(m) + "=" + ts.to_string());
3,416✔
408
            }
409
        } else {
410
            const std::string first_name = variable_registry->name(sorted[0]);
34✔
411
            result.insert(first_name + " in " + ts.to_string());
51✔
412

413
            for (size_t i = 1; i < sorted.size(); i++) {
66✔
414
                result.insert(variable_registry->name(sorted[i]) + "=" + first_name);
64✔
415
            }
416
        }
34✔
417
    }
1,286✔
418

419
    return StringInvariant{std::move(result)};
1,515✔
420
}
1,010✔
421

422
// -- State: mutations (return false → bottom) --------------------------------
423

424
bool TypeDomain::State::restrict_var(const Variable v, const TypeSet mask) {
40,398✔
425
    const size_t id = ensure_var(v);
40,398✔
426
    const size_t rep = dsu.find(id);
40,398✔
427
    const TypeSet result = class_types[rep] & mask;
40,398✔
428
    class_types[rep] = result;
40,398✔
429
    if (result.is_empty()) {
40,398✔
430
        return false;
1,898✔
431
    }
432
    merge_if_singleton(id);
36,602✔
433
    return true;
18,301✔
434
}
435

436
bool TypeDomain::State::assume_eq(const Variable v1, const Variable v2) {
16✔
437
    const size_t id1 = ensure_var(v1);
16✔
438
    const size_t id2 = ensure_var(v2);
16✔
439
    const size_t rep1 = dsu.find(id1);
16✔
440
    const size_t rep2 = dsu.find(id2);
16✔
441
    const TypeSet ts = class_types[rep1] & class_types[rep2];
16✔
442
    const size_t new_rep = dsu.unite(id1, id2);
16✔
443
    class_types[new_rep] = ts;
16✔
444
    if (ts.is_empty()) {
16✔
445
        return false;
1✔
446
    }
447
    merge_if_singleton(id1);
14✔
448
    return true;
7✔
449
}
450

451
bool TypeDomain::State::remove_type(const Variable v, const TypeEncoding te) {
6,268✔
452
    const size_t id = ensure_var(v);
6,268✔
453
    const size_t rep = dsu.find(id);
6,268✔
454
    const TypeSet result = class_types[rep].remove(te);
6,268✔
455
    class_types[rep] = result;
6,268✔
456
    if (result.is_empty()) {
6,268✔
457
        return false;
2,110✔
458
    }
459
    merge_if_singleton(id);
2,048✔
460
    return true;
1,024✔
461
}
462

463
bool TypeDomain::State::assign_from_expr(const Variable lhs, const LinearExpression& expr) {
36,178✔
464
    if (const auto& terms = expr.variable_terms(); terms.empty()) {
36,178✔
465
        const int val = expr.constant_term().narrow<int>();
32,942✔
466
        detach(lhs);
32,942✔
467
        const size_t id = *var_ids.find_id(lhs);
32,942✔
468
        if (const auto te = int_to_type_encoding(val)) {
32,942✔
469
            class_types[id] = TypeSet{*te};
32,942✔
470
            merge_if_singleton(id);
32,942✔
471
            return true;
32,942✔
472
        }
UNCOV
473
        return false;
×
474
    } else if (terms.size() == 1) {
3,236✔
475
        const auto& [var, coeff] = *terms.begin();
3,236✔
476
        if (coeff == 1 && expr.constant_term() == 0) {
3,236✔
477
            assign_copy(lhs, var);
3,236✔
478
        } else {
UNCOV
479
            detach(lhs);
×
480
        }
481
    } else {
UNCOV
482
        detach(lhs);
×
483
    }
484
    return true;
1,618✔
485
}
486

487
// -- State: mutations (never produce bottom) ---------------------------------
488

489
void TypeDomain::State::assign_copy(const Variable lhs, const Variable rhs) {
29,402✔
490
    detach(lhs);
29,402✔
491
    const size_t rhs_id = ensure_var(rhs);
29,402✔
492
    const size_t lhs_id = *var_ids.find_id(lhs);
29,402✔
493
    const TypeSet rhs_ts = class_types[dsu.find(rhs_id)];
29,402✔
494
    const size_t new_rep = dsu.unite(lhs_id, rhs_id);
29,402✔
495
    class_types[new_rep] = rhs_ts;
29,402✔
496
    merge_if_singleton(lhs_id);
29,402✔
497
}
29,402✔
498

499
void TypeDomain::State::assign_encoding(const Variable v, const TypeEncoding te) {
74,928✔
500
    detach(v);
74,928✔
501
    const size_t id = *var_ids.find_id(v);
74,928✔
502
    class_types[id] = TypeSet{te};
74,928✔
503
    merge_if_singleton(id);
74,928✔
504
}
74,928✔
505

506
// -- State: lattice ----------------------------------------------------------
507

508
TypeDomain::State TypeDomain::State::join(const State& a, const State& b) {
49,966✔
509
    // With the singleton-merging invariant, all variables with singleton {te}
510
    // share the same DSU rep (the sentinel) in each operand. So raw DSU reps
511
    // partition correctly without a special singleton key.
512

513
    // Collect all variables from both operands.
514
    std::set<Variable> all_vars_seen;
49,966✔
515
    for (const auto& v : a.var_ids.vars() | std::views::keys) {
1,987,672✔
516
        all_vars_seen.insert(v);
1,937,706✔
517
    }
518
    for (const auto& v : b.var_ids.vars() | std::views::keys) {
1,988,168✔
519
        all_vars_seen.insert(v);
1,938,202✔
520
    }
521

522
    // Compute (rep_left, rep_right) for each variable, group by key pair.
523
    // Variables absent from one side get unique keys (not nullopt) to avoid
524
    // falsely unifying unrelated variables that happen to share a rep on the
525
    // other side.
526
    size_t next_unique_a = a.dsu.size();
49,966✔
527
    size_t next_unique_b = b.dsu.size();
49,966✔
528
    std::map<std::pair<size_t, size_t>, std::vector<Variable>> key_groups;
49,966✔
529
    for (const auto& v : all_vars_seen) {
2,007,070✔
530
        size_t key_a;
978,552✔
531
        if (const auto id = a.var_ids.find_id(v)) {
1,957,104✔
532
            key_a = a.dsu.find_const(*id);
1,937,706✔
533
        } else {
534
            key_a = next_unique_a++;
19,398✔
535
        }
536
        size_t key_b;
978,552✔
537
        if (const auto id = b.var_ids.find_id(v)) {
1,957,104✔
538
            key_b = b.dsu.find_const(*id);
1,938,202✔
539
        } else {
540
            key_b = next_unique_b++;
18,902✔
541
        }
542
        key_groups[{key_a, key_b}].push_back(v);
1,957,104✔
543
    }
544

545
    // Build result
546
    State result;
49,966✔
547
    for (const auto& members : key_groups | std::views::values) {
1,098,428✔
548
        // TypeSet = union of per-variable TypeSets from both operands
549
        TypeSet ts{};
1,048,462✔
550
        for (const Variable& v : members) {
3,005,566✔
551
            ts |= a.get_typeset(v);
1,957,104✔
552
            ts |= b.get_typeset(v);
1,957,104✔
553
        }
554

555
        // Register all members in the result, unified
556
        std::optional<size_t> first_id;
1,048,462✔
557
        for (const Variable& v : members) {
3,005,566✔
558
            const size_t id = result.ensure_var(v);
1,957,104✔
559
            if (first_id) {
1,957,104✔
560
                result.dsu.unite(*first_id, id);
908,642✔
561
            } else {
562
                first_id = id;
1,502,783✔
563
            }
564
        }
565
        // Set the TypeSet on the representative (after all unifications).
566
        if (first_id) {
1,048,462✔
567
            result.class_types[result.dsu.find(*first_id)] = ts;
1,048,462✔
568
            result.merge_if_singleton(*first_id);
1,048,462✔
569
        }
570
    }
571

572
    return result;
74,949✔
573
}
49,966✔
574

575
std::optional<TypeDomain::State> TypeDomain::State::meet(const State& other) const {
62✔
576
    State result = *this;
62✔
577

578
    // Ensure all other variables exist in result
579
    for (const auto& v : other.var_ids.vars() | std::views::keys) {
78✔
580
        result.ensure_var(v);
16✔
581
    }
582

583
    // Merge equalities from other
584
    std::map<size_t, std::vector<Variable>> other_classes;
62✔
585
    for (const auto& [v, id] : other.var_ids.vars()) {
78✔
586
        const size_t rep = other.dsu.find_const(id);
16✔
587
        other_classes[rep].push_back(v);
16✔
588
    }
589
    for (const auto& members : other_classes | std::views::values) {
74✔
590
        for (size_t i = 1; i < members.size(); i++) {
16✔
591
            if (!result.assume_eq(members[0], members[i])) {
4✔
UNCOV
592
                return std::nullopt;
×
593
            }
594
        }
595
    }
596

597
    // Intersect TypeSets from other
598
    for (const auto& v : other.var_ids.vars() | std::views::keys) {
74✔
599
        if (!result.restrict_var(v, other.get_typeset(v))) {
14✔
600
            return std::nullopt;
2✔
601
        }
602
    }
603

604
    return result;
60✔
605
}
62✔
606

607
bool TypeDomain::State::is_subsumed_by(const State& other) const {
572✔
608
    // Check TypeSet refinement: S[v] in self must be subset of S[v] in other.
609
    for (const auto& [v, id] : var_ids.vars()) {
1,464✔
610
        const size_t rep = dsu.find_const(id);
910✔
611
        const TypeSet ts_self = class_types[rep];
910✔
612
        const TypeSet ts_other = other.get_typeset(v);
910✔
613
        if (!ts_self.is_subset_of(ts_other)) {
910✔
614
            return false;
18✔
615
        }
616
    }
617
    for (const auto& [v, id] : other.var_ids.vars()) {
1,412✔
618
        if (!var_ids.contains(v)) {
864✔
619
            const size_t rep = other.dsu.find_const(id);
6✔
620
            if (other.class_types[rep] != TypeSet::all()) {
6✔
621
                return false;
15✔
622
            }
623
        }
624
    }
625

626
    // Check equality preservation: other's equalities must hold in self.
627
    std::map<size_t, std::vector<Variable>> other_classes;
548✔
628
    for (const auto& [v, id] : other.var_ids.vars()) {
1,406✔
629
        const size_t rep = other.dsu.find_const(id);
858✔
630
        other_classes[rep].push_back(v);
858✔
631
    }
632
    for (const auto& members : other_classes | std::views::values) {
1,260✔
633
        if (members.size() <= 1) {
712✔
634
            continue;
608✔
635
        }
636
        const auto id0 = var_ids.find_id(members[0]);
104✔
637
        if (!id0) {
104✔
UNCOV
638
            for (size_t i = 1; i < members.size(); i++) {
×
UNCOV
639
                if (var_ids.contains(members[i])) {
×
UNCOV
640
                    return false;
×
641
                }
642
            }
UNCOV
643
            continue;
×
UNCOV
644
        }
×
645
        const size_t rep0 = dsu.find_const(*id0);
104✔
646
        for (size_t i = 1; i < members.size(); i++) {
250✔
647
            const auto id_i = var_ids.find_id(members[i]);
146✔
648
            if (!id_i) {
146✔
UNCOV
649
                return false;
×
650
            }
651
            if (dsu.find_const(*id_i) != rep0) {
219✔
652
                return false;
653
            }
654
        }
655
    }
656

657
    return true;
274✔
658
}
548✔
659

660
// ============================================================================
661
// TypeDomain — thin WithBottom wrappers
662
// ============================================================================
663

664
void TypeDomain::set_to_top() { state_ = std::make_unique<State>(); }
8✔
665
void TypeDomain::set_to_bottom() { state_.reset(); }
4,220✔
666

667
// -- Lattice -----------------------------------------------------------------
668

669
TypeDomain TypeDomain::join(const TypeDomain& other) const {
49,968✔
670
    if (!state_) {
49,968✔
UNCOV
671
        return other;
×
672
    }
673
    if (!other.state_) {
49,968✔
674
        return *this;
24,985✔
675
    }
676
    TypeDomain result;
49,966✔
677
    result.state_ = std::make_unique<State>(State::join(*state_, *other.state_));
74,949✔
678
    return result;
49,966✔
679
}
49,966✔
680

681
void TypeDomain::operator|=(const TypeDomain& other) {
49,844✔
682
    if (!other.state_) {
49,844✔
683
        return;
684
    }
685
    if (!state_) {
49,844✔
UNCOV
686
        *this = other;
×
UNCOV
687
        return;
×
688
    }
689
    *this = join(other);
74,766✔
690
}
691

692
TypeDomain TypeDomain::operator|(const TypeDomain& other) const { return join(other); }
124✔
693

694
std::optional<TypeDomain> TypeDomain::meet(const TypeDomain& other) const {
62✔
695
    if (!state_ || !other.state_) {
62✔
UNCOV
696
        return std::nullopt;
×
697
    }
698
    if (auto result = state_->meet(*other.state_)) {
62✔
699
        TypeDomain td;
60✔
700
        td.state_ = std::make_unique<State>(std::move(*result));
90✔
701
        return td;
60✔
702
    }
91✔
703
    return std::nullopt;
2✔
704
}
705

706
bool TypeDomain::operator<=(const TypeDomain& other) const {
574✔
707
    if (!state_) {
574✔
708
        return true;
1✔
709
    }
710
    if (!other.state_) {
572✔
711
        return false;
712
    }
713
    return state_->is_subsumed_by(*other.state_);
572✔
714
}
715

UNCOV
716
TypeDomain TypeDomain::narrow(const TypeDomain& other) const {
×
UNCOV
717
    if (auto res = meet(other)) {
×
UNCOV
718
        return std::move(*res);
×
UNCOV
719
    }
×
UNCOV
720
    TypeDomain res;
×
UNCOV
721
    res.set_to_bottom();
×
UNCOV
722
    return res;
×
UNCOV
723
}
×
724

725
// -- Assignment --------------------------------------------------------------
726

727
void TypeDomain::assign_type(const Reg& lhs, const Reg& rhs) {
26,166✔
728
    if (auto* s = state_.get()) {
26,166✔
729
        s->assign_copy(reg_type(lhs), reg_type(rhs));
26,166✔
730
    }
731
}
26,166✔
732

733
void TypeDomain::assign_type(const Reg& lhs, const std::optional<LinearExpression>& rhs) {
11,754✔
734
    if (!state_) {
11,754✔
735
        return;
736
    }
737
    if (!rhs) {
11,754✔
UNCOV
738
        havoc_type(reg_type(lhs));
×
UNCOV
739
        return;
×
740
    }
741
    if (!state_->assign_from_expr(reg_type(lhs), *rhs)) {
11,754✔
UNCOV
742
        set_to_bottom();
×
743
    }
744
}
745

746
void TypeDomain::assign_type(const std::optional<Variable> lhs, const LinearExpression& t) {
24,428✔
747
    if (!state_ || !lhs) {
24,428✔
748
        return;
2✔
749
    }
750
    if (!state_->assign_from_expr(*lhs, t)) {
24,424✔
UNCOV
751
        set_to_bottom();
×
752
    }
753
}
754

755
void TypeDomain::assign_type(const Reg& lhs, const TypeEncoding type) {
74,928✔
756
    if (auto* s = state_.get()) {
74,928✔
757
        s->assign_encoding(reg_type(lhs), type);
74,928✔
758
    }
759
}
74,928✔
760

761
// -- Constraint handling -----------------------------------------------------
762

763
void TypeDomain::restrict_to(const Variable v, const TypeSet mask) {
35,980✔
764
    if (auto* s = state_.get()) {
35,980✔
765
        if (!s->restrict_var(v, mask)) {
35,980✔
766
            set_to_bottom();
8✔
767
        }
768
    }
769
}
35,980✔
770

771
void TypeDomain::assume_eq(const Variable v1, const Variable v2) {
12✔
772
    if (auto* s = state_.get()) {
12✔
773
        if (!s->assume_eq(v1, v2)) {
12✔
774
            set_to_bottom();
2✔
775
        }
776
    }
777
}
12✔
778

779
void TypeDomain::remove_type(const Variable v, const TypeEncoding te) {
5,352✔
780
    if (auto* s = state_.get()) {
5,352✔
781
        if (!s->remove_type(v, te)) {
5,352✔
782
            set_to_bottom();
4,210✔
783
        }
784
    }
785
}
5,352✔
786

787
// -- Havoc -------------------------------------------------------------------
788

789
void TypeDomain::havoc_type(const Reg& r) {
71,198✔
790
    if (auto* s = state_.get()) {
71,198✔
791
        s->detach(reg_type(r));
71,198✔
792
    }
793
}
71,198✔
794

795
void TypeDomain::havoc_type(const Variable& v) {
6,508✔
796
    if (auto* s = state_.get()) {
6,508✔
797
        s->detach(v);
6,508✔
798
    }
799
}
6,508✔
800

801
// -- Query methods -----------------------------------------------------------
802

803
TypeSet TypeDomain::get_typeset(const Variable v) const {
29,359,570✔
804
    if (const auto* s = state_.get()) {
29,359,570✔
805
        return s->get_typeset(v);
29,355,360✔
806
    }
807
    return TypeSet{}; // bottom
4,210✔
808
}
809

810
std::vector<TypeEncoding> TypeDomain::iterate_types(const Reg& reg) const {
156,180✔
811
    if (!state_) {
156,180✔
UNCOV
812
        return {};
×
813
    }
814
    const TypeSet ts = get_typeset(reg_type(reg));
156,180✔
815
    if (ts.contains(T_UNINIT)) {
156,180✔
UNCOV
816
        return {T_UNINIT};
×
817
    }
818
    return ts.remove(T_UNINIT).to_vector();
156,180✔
819
}
820

821
std::optional<TypeEncoding> TypeDomain::get_type(const Reg& r) const { return get_typeset(reg_type(r)).as_singleton(); }
12,964✔
822

823
bool TypeDomain::implies_superset(const Reg& premise_reg, const TypeSet premise_set, const Reg& conclusion_reg,
4,404✔
824
                                  const TypeSet conclusion_set) const {
825
    if (!state_) {
4,404✔
826
        return true;
827
    }
828
    State restricted = *state_;
4,404✔
829
    if (!restricted.restrict_var(reg_type(premise_reg), premise_set)) {
4,404✔
830
        return true;
1,893✔
831
    }
832
    return restricted.get_typeset(reg_type(conclusion_reg)).is_subset_of(conclusion_set);
618✔
833
}
4,404✔
834

835
bool TypeDomain::implies_not_type(const Reg& premise_reg, const TypeEncoding excluded_type, const Reg& conclusion_reg,
916✔
836
                                  const TypeSet conclusion_set) const {
837
    if (!state_) {
916✔
838
        return true;
839
    }
840
    State restricted = *state_;
916✔
841
    if (!restricted.remove_type(reg_type(premise_reg), excluded_type)) {
916✔
842
        return true;
5✔
843
    }
844
    return restricted.get_typeset(reg_type(conclusion_reg)).is_subset_of(conclusion_set);
906✔
845
}
916✔
846

847
bool TypeDomain::entail_type(const Variable v, const TypeEncoding te) const {
14✔
848
    if (!state_) {
14✔
849
        return true; // bottom entails everything
850
    }
851
    return get_typeset(v) == TypeSet{te};
14✔
852
}
853

854
bool TypeDomain::may_have_type(const Reg& r, const TypeEncoding type) const {
442✔
855
    return get_typeset(reg_type(r)).contains(type);
442✔
856
}
857

858
bool TypeDomain::may_have_type(const Variable v, const TypeEncoding type) const {
27,473,056✔
859
    return get_typeset(v).contains(type);
27,473,056✔
860
}
861

862
bool TypeDomain::is_initialized(const Reg& r) const { return !get_typeset(reg_type(r)).contains(T_UNINIT); }
268,846✔
863

864
bool TypeDomain::is_initialized(const Variable v) const { return !get_typeset(v).contains(T_UNINIT); }
1,261,948✔
865

866
bool TypeDomain::same_type(const Reg& a, const Reg& b) const {
12,738✔
867
    if (const auto* s = state_.get()) {
12,738✔
868
        return s->same_type(reg_type(a), reg_type(b));
12,738✔
869
    }
870
    return true; // bottom entails everything
871
}
872

873
bool TypeDomain::is_in_group(const Reg& r, const TypeSet types) const {
186,120✔
874
    return get_typeset(reg_type(r)).is_subset_of(types);
186,120✔
875
}
876

877
// -- Serialization -----------------------------------------------------------
878

879
StringInvariant TypeDomain::to_set() const {
1,010✔
880
    if (!state_) {
1,010✔
UNCOV
881
        return StringInvariant::bottom();
×
882
    }
883
    return state_->to_set();
1,010✔
884
}
885

886
std::ostream& operator<<(std::ostream& o, const TypeDomain& dom) { return o << dom.to_set(); }
6✔
887

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