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

Alan-Jowett / ebpf-verifier / 22235569093

20 Feb 2026 02:12AM UTC coverage: 88.002% (-0.2%) from 88.157%
22235569093

push

github

web-flow
Handle Call builtins: fix handling of Falco tests  (#1025)

* falco: fix raw_tracepoint privilege and group expected failures

Mark raw_tracepoint/raw_tracepoint_writable as privileged program types so Falco raw-tracepoint sections are not treated as unprivileged argument checks.

Update Falco sample matrix to move now-passing sections out of TEST_SECTION_FAIL and group the remaining expected failures by root-cause class (offset lower-bound loss vs size lower-bound loss at correlated joins).

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

* elf/unmarshal: gate builtin relocations via platform call model

Add platform hooks to resolve builtin symbols and provide builtin call contracts, thread relocation-gated builtin call offsets through ProgramInfo, and only treat static helper IDs as builtins at gated call sites.

Also extend platform-table, marshal, and YAML-platform tests to cover builtin resolver wiring and call unmarshal behavior.
* crab: canonicalize unsigned intervals in bitwise_and
When uvalue intervals temporarily carry signed lower bounds (e.g. after joins), Interval::bitwise_and asserted in debug builds. Canonicalize both operands via zero_extend(64) before unsigned bitwise reasoning, preserving soundness and avoiding debug aborts.

Validated by reproducing SIGABRT on reverted code in [falco][verify] and confirming the patched build completes with expected 73 pass / 20 failed-as-expected.

* Fix unsound bitwise_and case for non-singleton all-ones rhs

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

239 of 252 new or added lines in 9 files covered. (94.84%)

602 existing lines in 16 files now uncovered.

11743 of 13344 relevant lines covered (88.0%)

3262592.78 hits per line

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

89.91
/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)); }
489,188✔
29

30
std::vector<DataKind> iterate_kinds(const DataKind lb, const DataKind ub) {
34,942✔
31
    if (lb > ub) {
34,942✔
32
        CRAB_ERROR("lower bound ", lb, " is greater than upper bound ", ub);
×
33
    }
34
    if (lb < KIND_MIN || ub > KIND_MAX) {
34,942✔
35
        CRAB_ERROR("bounds ", lb, " and ", ub, " are out of range");
×
36
    }
37
    std::vector<DataKind> res;
34,942✔
38
    for (DataKind i = lb; i <= ub; ++i) {
524,130✔
39
        res.push_back(i);
489,188✔
40
    }
41
    return res;
34,942✔
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
const TypeSet TS_SOCKET{T_SOCKET};
54
const TypeSet TS_BTF_ID{T_BTF_ID};
55
const TypeSet TS_ALLOC_MEM{T_ALLOC_MEM};
56

57
// ============================================================================
58
// TypeEncoding utilities
59
// ============================================================================
60

61
static constexpr auto S_UNINIT = "uninit";
62
static constexpr auto S_STACK = "stack";
63
static constexpr auto S_PACKET = "packet";
64
static constexpr auto S_CTX = "ctx";
65
static constexpr auto S_MAP_PROGRAMS = "map_fd_programs";
66
static constexpr auto S_MAP = "map_fd";
67
static constexpr auto S_NUM = "number";
68
static constexpr auto S_SHARED = "shared";
69
static constexpr auto S_SOCKET = "socket";
70
static constexpr auto S_BTF_ID = "btf_id";
71
static constexpr auto S_ALLOC_MEM = "alloc_mem";
72
static constexpr auto S_FUNC = "func";
73

74
std::string name_of(const DataKind kind) {
27,920,858✔
75
    switch (kind) {
27,920,858✔
76
    case DataKind::ctx_offsets: return "ctx_offset";
3,701,724✔
77
    case DataKind::map_fds: return "map_fd";
3,645,196✔
78
    case DataKind::map_fd_programs: return "map_fd_programs";
3,643,768✔
79
    case DataKind::packet_offsets: return "packet_offset";
3,654,260✔
80
    case DataKind::shared_offsets: return "shared_offset";
3,689,920✔
81
    case DataKind::shared_region_sizes: return "shared_region_size";
3,689,920✔
82
    case DataKind::stack_numeric_sizes: return "stack_numeric_size";
3,846,940✔
83
    case DataKind::stack_offsets: return "stack_offset";
3,845,196✔
84
    case DataKind::socket_offsets: return "socket_offset";
3,642,968✔
85
    case DataKind::btf_id_offsets: return "btf_id_offset";
3,642,968✔
86
    case DataKind::alloc_mem_offsets: return "alloc_mem_offset";
3,642,968✔
87
    case DataKind::alloc_mem_sizes: return "alloc_mem_size";
3,642,968✔
88
    case DataKind::svalues: return "svalue";
4,568,175✔
89
    case DataKind::types: return "type";
4,339,608✔
90
    case DataKind::uvalues: return "uvalue";
3,572,796✔
91
    }
UNCOV
92
    return {};
×
93
}
94

95
DataKind regkind(const std::string& s) {
4,674✔
96
    static const std::map<std::string, DataKind> string_to_kind{
2,337✔
97
        {"type", DataKind::types},
1✔
98
        {"ctx_offset", DataKind::ctx_offsets},
1✔
99
        {"map_fd", DataKind::map_fds},
1✔
100
        {"map_fd_programs", DataKind::map_fd_programs},
1✔
101
        {"packet_offset", DataKind::packet_offsets},
1✔
102
        {"shared_offset", DataKind::shared_offsets},
1✔
103
        {"stack_offset", DataKind::stack_offsets},
1✔
104
        {"shared_region_size", DataKind::shared_region_sizes},
1✔
105
        {"stack_numeric_size", DataKind::stack_numeric_sizes},
1✔
106
        {"socket_offset", DataKind::socket_offsets},
1✔
107
        {"btf_id_offset", DataKind::btf_id_offsets},
1✔
108
        {"alloc_mem_offset", DataKind::alloc_mem_offsets},
1✔
109
        {"alloc_mem_size", DataKind::alloc_mem_sizes},
1✔
110
        {"svalue", DataKind::svalues},
1✔
111
        {"uvalue", DataKind::uvalues},
1✔
112
    };
4,707✔
113
    if (string_to_kind.contains(s)) {
4,674✔
114
        return string_to_kind.at(s);
4,674✔
115
    }
UNCOV
116
    throw std::runtime_error(std::string() + "Bad kind: " + s);
×
117
}
2✔
118

119
std::ostream& operator<<(std::ostream& os, const TypeEncoding s) {
1,828✔
120
    switch (s) {
1,828✔
121
    case T_SHARED: return os << S_SHARED;
78✔
122
    case T_STACK: return os << S_STACK;
264✔
123
    case T_PACKET: return os << S_PACKET;
86✔
124
    case T_CTX: return os << S_CTX;
82✔
125
    case T_NUM: return os << S_NUM;
1,282✔
126
    case T_MAP: return os << S_MAP;
32✔
127
    case T_MAP_PROGRAMS: return os << S_MAP_PROGRAMS;
4✔
128
    case T_UNINIT: return os << S_UNINIT;
×
UNCOV
129
    case T_SOCKET: return os << S_SOCKET;
×
UNCOV
130
    case T_BTF_ID: return os << S_BTF_ID;
×
UNCOV
131
    case T_ALLOC_MEM: return os << S_ALLOC_MEM;
×
UNCOV
132
    case T_FUNC: return os << S_FUNC;
×
133
    }
UNCOV
134
    CRAB_ERROR("Unsupported type encoding");
×
135
}
136

137
TypeEncoding string_to_type_encoding(const std::string& s) {
2,678✔
138
    static std::map<std::string, TypeEncoding> string_to_type{
1,339✔
139
        {S_UNINIT, T_UNINIT},
1✔
140
        {S_MAP_PROGRAMS, T_MAP_PROGRAMS},
1✔
141
        {S_MAP, T_MAP},
1✔
142
        {S_NUM, T_NUM},
1✔
143
        {S_CTX, T_CTX},
1✔
144
        {S_STACK, T_STACK},
1✔
145
        {S_PACKET, T_PACKET},
1✔
146
        {S_SHARED, T_SHARED},
1✔
147
        {S_SOCKET, T_SOCKET},
1✔
148
        {S_BTF_ID, T_BTF_ID},
1✔
149
        {S_ALLOC_MEM, T_ALLOC_MEM},
1✔
150
        {S_FUNC, T_FUNC},
1✔
151
    };
2,705✔
152
    if (string_to_type.contains(s)) {
2,678✔
153
        return string_to_type[s];
2,678✔
154
    }
UNCOV
155
    throw std::runtime_error(std::string("Unsupported type name: ") + s);
×
156
}
2✔
157

158
std::optional<TypeEncoding> int_to_type_encoding(const int v) {
44,262✔
159
    if (v >= 0 && v < static_cast<int>(NUM_TYPE_ENCODINGS)) {
22,131✔
160
        return static_cast<TypeEncoding>(v);
22,131✔
161
    }
UNCOV
162
    return std::nullopt;
×
163
}
164

165
std::string typeset_to_string(const std::vector<TypeEncoding>& items) {
50✔
166
    std::stringstream ss;
50✔
167
    ss << "{";
50✔
168
    for (auto it = items.begin(); it != items.end(); ++it) {
168✔
169
        ss << *it;
118✔
170
        if (std::next(it) != items.end()) {
177✔
171
            ss << ", ";
68✔
172
        }
173
    }
174
    ss << "}";
50✔
175
    return ss.str();
100✔
176
}
50✔
177

178
// ============================================================================
179
// TypeSet methods
180
// ============================================================================
181

182
std::optional<TypeEncoding> TypeSet::as_singleton() const {
2,008,072✔
183
    if (!is_singleton()) {
2,008,072✔
184
        return std::nullopt;
1,089,776✔
185
    }
186
    for (unsigned i = 0; i < NUM_TYPE_ENCODINGS; i++) {
5,220,794✔
187
        if (contains(static_cast<TypeEncoding>(i))) {
5,220,794✔
188
            return static_cast<TypeEncoding>(i);
918,296✔
189
        }
190
    }
UNCOV
191
    return std::nullopt; // unreachable
×
192
}
193

194
std::vector<TypeEncoding> TypeSet::to_vector() const {
2,729,294✔
195
    std::vector<TypeEncoding> result;
2,729,294✔
196
    for (unsigned i = 0; i < NUM_TYPE_ENCODINGS; i++) {
35,480,822✔
197
        if (contains(static_cast<TypeEncoding>(i))) {
32,751,528✔
198
            result.push_back(static_cast<TypeEncoding>(i));
28,784,706✔
199
        }
200
    }
201
    return result;
2,729,294✔
UNCOV
202
}
×
203

204
std::string TypeSet::to_string() const {
1,744✔
205
    const auto items = to_vector();
1,744✔
206
    if (items.size() == 1) {
1,744✔
207
        std::stringstream ss;
1,710✔
208
        ss << items[0];
1,710✔
209
        return ss.str();
1,710✔
210
    }
1,710✔
211
    return typeset_to_string(items);
34✔
212
}
1,744✔
213

214
// ============================================================================
215
// TypeGroup utilities
216
// ============================================================================
217

218
TypeSet to_typeset(const TypeGroup group) {
294,764✔
219
    switch (group) {
294,764✔
220
    case TypeGroup::number: return TypeSet{T_NUM};
154,866✔
221
    case TypeGroup::map_fd: return TypeSet{T_MAP};
5,772✔
222
    case TypeGroup::ctx: return TypeSet{T_CTX};
6,122✔
UNCOV
223
    case TypeGroup::packet: return TypeSet{T_PACKET};
×
UNCOV
224
    case TypeGroup::stack: return TypeSet{T_STACK};
×
UNCOV
225
    case TypeGroup::shared: return TypeSet{T_SHARED};
×
226
    case TypeGroup::map_fd_programs: return TypeSet{T_MAP_PROGRAMS};
644✔
UNCOV
227
    case TypeGroup::socket: return TypeSet{T_SOCKET};
×
UNCOV
228
    case TypeGroup::btf_id: return TypeSet{T_BTF_ID};
×
UNCOV
229
    case TypeGroup::alloc_mem: return TypeSet{T_ALLOC_MEM};
×
230
    case TypeGroup::func: return TypeSet{T_FUNC};
10✔
231
    case TypeGroup::ctx_or_num: return TypeSet{T_NUM, T_CTX};
48✔
232
    case TypeGroup::stack_or_num: return TypeSet{T_NUM, T_STACK};
4✔
233
    case TypeGroup::mem: return TS_MEM;
14,284✔
234
    case TypeGroup::mem_or_num: return TS_MEM | TS_NUM;
976✔
235
    case TypeGroup::pointer: return TS_POINTER;
73,496✔
236
    case TypeGroup::ptr_or_num: return TS_POINTER | TS_NUM;
38,542✔
UNCOV
237
    case TypeGroup::stack_or_packet: return TypeSet{T_STACK, T_PACKET};
×
UNCOV
238
    case TypeGroup::singleton_ptr: return TS_SINGLETON_PTR;
×
239
    }
UNCOV
240
    CRAB_ERROR("Unsupported type group");
×
241
}
242

243
/// Whether a TypeGroup maps to a single TypeEncoding value (singleton TypeSet),
244
/// NOT whether there is a single memory region of that type.
245
/// For example, shared is singleton here ({T_SHARED}) even though multiple
246
/// shared regions can exist. In contrast, TypeGroup::mem is not singleton
247
/// because it maps to {T_PACKET, T_STACK, T_SHARED}.
248
bool is_singleton_type(const TypeGroup t) {
232✔
249
    switch (t) {
232✔
250
    case TypeGroup::number:
94✔
251
    case TypeGroup::map_fd:
252
    case TypeGroup::map_fd_programs:
253
    case TypeGroup::ctx:
254
    case TypeGroup::packet:
255
    case TypeGroup::stack:
256
    case TypeGroup::shared:
257
    case TypeGroup::socket:
258
    case TypeGroup::btf_id:
259
    case TypeGroup::alloc_mem:
260
    case TypeGroup::func: return true;
94✔
261
    default: return false;
44✔
262
    }
263
}
264

265
std::ostream& operator<<(std::ostream& os, const TypeGroup ts) {
242✔
266
    using namespace prevail;
121✔
267
    static const std::map<TypeGroup, std::string> string_to_type{
121✔
268
        {TypeGroup::number, S_NUM},
1✔
269
        {TypeGroup::map_fd, S_MAP},
1✔
270
        {TypeGroup::map_fd_programs, S_MAP_PROGRAMS},
1✔
271
        {TypeGroup::ctx, S_CTX},
1✔
272
        {TypeGroup::ctx_or_num, typeset_to_string({T_NUM, T_CTX})},
4✔
273
        {TypeGroup::packet, S_PACKET},
2✔
274
        {TypeGroup::stack, S_STACK},
1✔
275
        {TypeGroup::stack_or_num, typeset_to_string({T_NUM, T_STACK})},
4✔
276
        {TypeGroup::shared, S_SHARED},
2✔
277
        {TypeGroup::socket, S_SOCKET},
1✔
278
        {TypeGroup::btf_id, S_BTF_ID},
1✔
279
        {TypeGroup::alloc_mem, S_ALLOC_MEM},
1✔
280
        {TypeGroup::func, S_FUNC},
1✔
281
        {TypeGroup::mem, typeset_to_string({T_STACK, T_PACKET, T_SHARED})},
4✔
282
        {TypeGroup::pointer, typeset_to_string({T_CTX, T_STACK, T_PACKET, T_SHARED})},
4✔
283
        {TypeGroup::ptr_or_num, typeset_to_string({T_NUM, T_CTX, T_STACK, T_PACKET, T_SHARED})},
4✔
284
        {TypeGroup::stack_or_packet, typeset_to_string({T_STACK, T_PACKET})},
4✔
285
        {TypeGroup::singleton_ptr, typeset_to_string({T_CTX, T_STACK, T_PACKET})},
4✔
286
        {TypeGroup::mem_or_num, typeset_to_string({T_NUM, T_STACK, T_PACKET, T_SHARED})},
4✔
287
    };
285✔
288
    if (string_to_type.contains(ts)) {
242✔
289
        return os << string_to_type.at(ts);
242✔
290
    }
UNCOV
291
    CRAB_ERROR("Unsupported type group");
×
292
}
18✔
293

294
// ============================================================================
295
// TypeDomain -- DSU-based implementation
296
// ============================================================================
297

298
Variable reg_type(const Reg& lhs) { return variable_registry->type_reg(lhs.v); }
2,112,174✔
299

300
// -- State definition --------------------------------------------------------
301

302
struct TypeDomain::State {
303
    DisjointSetUnion dsu;
304
    VarIdMap var_ids;
305
    std::vector<TypeSet> class_types;
306

307
    State();
308

309
    // Internal helpers
310
    void merge_if_singleton(size_t id);
311
    size_t ensure_var(Variable v);
312
    void detach(Variable v);
313

314
    // Queries
315
    [[nodiscard]]
316
    TypeSet get_typeset(Variable v) const;
317
    [[nodiscard]]
318
    bool same_type(Variable a, Variable b) const;
319
    [[nodiscard]]
320
    StringInvariant to_set() const;
321

322
    // Mutations returning false on empty TypeSet (caller sets bottom)
323
    bool restrict_var(Variable v, TypeSet mask);
324
    bool assume_eq(Variable v1, Variable v2);
325
    bool remove_type(Variable v, TypeEncoding te);
326
    bool assign_from_expr(Variable lhs, const LinearExpression& expr);
327

328
    // Mutations that never produce bottom
329
    void assign_copy(Variable lhs, Variable rhs);
330
    void assign_encoding(Variable v, TypeEncoding te);
331

332
    // Lattice
333
    [[nodiscard]]
334
    static State join(const State& a, const State& b);
335
    [[nodiscard]]
336
    std::optional<State> meet(const State& other) const;
337
    [[nodiscard]]
338
    bool is_subsumed_by(const State& other) const;
339
};
340

341
TypeDomain::State::State() {
2,368,630✔
342
    dsu = DisjointSetUnion{NUM_TYPE_ENCODINGS};
2,368,630✔
343
    var_ids = VarIdMap{};
2,368,630✔
344
    class_types.resize(NUM_TYPE_ENCODINGS);
2,368,630✔
345
    for (const TypeEncoding te : TypeSet::all().to_vector()) {
30,792,190✔
346
        class_types[type_to_bit(te)] = TypeSet{te};
28,423,560✔
347
    }
2,368,630✔
348
}
2,368,630✔
349

350
// -- Special member functions ------------------------------------------------
351

352
TypeDomain::TypeDomain() : state_(std::make_unique<State>()) {}
2,165,888✔
353

354
TypeDomain::~TypeDomain() = default;
6,497,007✔
355

356
TypeDomain::TypeDomain(const TypeDomain& other)
1,332,896✔
357
    : state_(other.state_ ? std::make_unique<State>(*other.state_) : nullptr) {}
1,332,895✔
358

359
TypeDomain::TypeDomain(TypeDomain&& other) noexcept = default;
1,499,096✔
360

361
TypeDomain& TypeDomain::operator=(const TypeDomain& other) {
2,042,778✔
362
    if (this != &other) {
2,042,778✔
363
        state_ = other.state_ ? std::make_unique<State>(*other.state_) : nullptr;
2,042,778✔
364
    }
365
    return *this;
2,042,778✔
366
}
367

368
TypeDomain& TypeDomain::operator=(TypeDomain&& other) noexcept = default;
135,014✔
369

370
// -- State: internal helpers -------------------------------------------------
371

372
void TypeDomain::State::merge_if_singleton(const size_t id) {
1,980,726✔
373
    const size_t rep = dsu.find(id);
1,980,726✔
374
    const TypeSet ts = class_types[rep];
1,980,726✔
375
    if (const auto te = ts.as_singleton()) {
1,980,726✔
376
        const size_t sentinel = type_to_bit(*te);
891,056✔
377
        const size_t sentinel_rep = dsu.find(sentinel);
891,056✔
378
        if (rep != sentinel_rep) {
891,056✔
379
            const size_t new_rep = dsu.unite(rep, sentinel);
714,646✔
380
            class_types[new_rep] = ts;
714,646✔
381
        }
382
    }
383
}
1,980,726✔
384

385
size_t TypeDomain::State::ensure_var(const Variable v) {
3,829,394✔
386
    if (const auto existing = var_ids.find_id(v)) {
3,829,394✔
387
        return *existing;
192,042✔
388
    }
389
    const size_t id = dsu.push();
3,637,352✔
390
    var_ids.insert(v, id);
3,637,352✔
391
    class_types.push_back(TypeSet::all());
3,637,352✔
392
    assert(class_types.size() == dsu.size());
3,637,352✔
393
    assert(var_ids.id_capacity() == dsu.size());
3,637,352✔
394
    return id;
3,637,352✔
395
}
396

397
void TypeDomain::State::detach(const Variable v) {
389,864✔
398
    var_ids.orphan_var(v);
389,864✔
399
    const size_t new_id = dsu.push();
389,864✔
400
    var_ids.insert(v, new_id);
389,864✔
401
    class_types.push_back(TypeSet::all());
389,864✔
402
    assert(class_types.size() == dsu.size());
389,864✔
403
    assert(var_ids.id_capacity() == dsu.size());
389,864✔
404
}
389,864✔
405

406
// -- State: queries ----------------------------------------------------------
407

408
TypeSet TypeDomain::State::get_typeset(const Variable v) const {
81,395,804✔
409
    if (const auto id = var_ids.find_id(v)) {
81,395,804✔
410
        const size_t rep = dsu.find_const(*id);
75,669,882✔
411
        return class_types[rep];
75,669,882✔
412
    }
413
    return TypeSet::all(); // unknown variable = top
5,725,922✔
414
}
415

416
bool TypeDomain::State::same_type(const Variable a, const Variable b) const {
20,936✔
417
    const auto id_a = var_ids.find_id(a);
20,936✔
418
    const auto id_b = var_ids.find_id(b);
20,936✔
419
    if (id_a && id_b) {
20,936✔
420
        return dsu.find_const(*id_a) == dsu.find_const(*id_b);
34,856✔
421
    }
422
    return false;
1✔
423
}
424

425
StringInvariant TypeDomain::State::to_set() const {
1,012✔
426
    // Group variables by DSU representative.
427
    std::map<size_t, std::vector<Variable>> classes;
1,012✔
428
    for (const auto& [v, id] : var_ids.vars()) {
3,362✔
429
        const size_t rep = dsu.find_const(id);
2,350✔
430
        classes[rep].push_back(v);
2,350✔
431
    }
432

433
    std::set<std::string> result;
1,012✔
434

435
    for (const auto& members : classes | std::views::values) {
2,794✔
436
        const TypeSet ts = get_typeset(members[0]);
1,782✔
437
        if (ts == TypeSet::all()) {
1,782✔
438
            continue; // top = no constraint
494✔
439
        }
440

441
        // Sort members for deterministic output
442
        std::vector<Variable> sorted = members;
1,288✔
443
        std::ranges::sort(sorted, VariableRegistry::printing_order);
1,288✔
444

445
        if (const auto te = ts.as_singleton()) {
1,288✔
446
            for (const Variable& m : sorted) {
3,044✔
447
                // Stack type variables with type=number are implicit (not printed)
448
                if (*te == T_NUM && variable_registry->is_in_stack(m)) {
1,790✔
449
                    continue;
80✔
450
                }
451
                result.insert(variable_registry->name(m) + "=" + ts.to_string());
3,420✔
452
            }
453
        } else {
454
            const std::string first_name = variable_registry->name(sorted[0]);
34✔
455
            result.insert(first_name + " in " + ts.to_string());
51✔
456

457
            for (size_t i = 1; i < sorted.size(); i++) {
66✔
458
                result.insert(variable_registry->name(sorted[i]) + "=" + first_name);
64✔
459
            }
460
        }
34✔
461
    }
1,288✔
462

463
    return StringInvariant{std::move(result)};
1,518✔
464
}
1,012✔
465

466
// -- State: mutations (return false → bottom) --------------------------------
467

468
bool TypeDomain::State::restrict_var(const Variable v, const TypeSet mask) {
120,428✔
469
    const size_t id = ensure_var(v);
120,428✔
470
    const size_t rep = dsu.find(id);
120,428✔
471
    const TypeSet result = class_types[rep] & mask;
120,428✔
472
    class_types[rep] = result;
120,428✔
473
    if (result.is_empty()) {
120,428✔
474
        return false;
5,349✔
475
    }
476
    merge_if_singleton(id);
109,730✔
477
    return true;
54,865✔
478
}
479

480
bool TypeDomain::State::assume_eq(const Variable v1, const Variable v2) {
16✔
481
    const size_t id1 = ensure_var(v1);
16✔
482
    const size_t id2 = ensure_var(v2);
16✔
483
    const size_t rep1 = dsu.find(id1);
16✔
484
    const size_t rep2 = dsu.find(id2);
16✔
485
    const TypeSet ts = class_types[rep1] & class_types[rep2];
16✔
486
    const size_t new_rep = dsu.unite(id1, id2);
16✔
487
    class_types[new_rep] = ts;
16✔
488
    if (ts.is_empty()) {
16✔
489
        return false;
1✔
490
    }
491
    merge_if_singleton(id1);
14✔
492
    return true;
7✔
493
}
494

495
bool TypeDomain::State::remove_type(const Variable v, const TypeEncoding te) {
6,484✔
496
    const size_t id = ensure_var(v);
6,484✔
497
    const size_t rep = dsu.find(id);
6,484✔
498
    const TypeSet result = class_types[rep].remove(te);
6,484✔
499
    class_types[rep] = result;
6,484✔
500
    if (result.is_empty()) {
6,484✔
501
        return false;
2,220✔
502
    }
503
    merge_if_singleton(id);
2,044✔
504
    return true;
1,022✔
505
}
506

507
bool TypeDomain::State::assign_from_expr(const Variable lhs, const LinearExpression& expr) {
57,000✔
508
    if (const auto& terms = expr.variable_terms(); terms.empty()) {
57,000✔
509
        const int val = expr.constant_term().narrow<int>();
44,262✔
510
        detach(lhs);
44,262✔
511
        const size_t id = *var_ids.find_id(lhs);
44,262✔
512
        if (const auto te = int_to_type_encoding(val)) {
44,262✔
513
            class_types[id] = TypeSet{*te};
44,262✔
514
            merge_if_singleton(id);
44,262✔
515
            return true;
44,262✔
516
        }
UNCOV
517
        return false;
×
518
    } else if (terms.size() == 1) {
12,738✔
519
        const auto& [var, coeff] = *terms.begin();
12,738✔
520
        if (coeff == 1 && expr.constant_term() == 0) {
12,738✔
521
            assign_copy(lhs, var);
12,738✔
522
        } else {
UNCOV
523
            detach(lhs);
×
524
        }
525
    } else {
UNCOV
526
        detach(lhs);
×
527
    }
528
    return true;
6,369✔
529
}
530

531
// -- State: mutations (never produce bottom) ---------------------------------
532

533
void TypeDomain::State::assign_copy(const Variable lhs, const Variable rhs) {
67,834✔
534
    detach(lhs);
67,834✔
535
    const size_t rhs_id = ensure_var(rhs);
67,834✔
536
    const size_t lhs_id = *var_ids.find_id(lhs);
67,834✔
537
    const TypeSet rhs_ts = class_types[dsu.find(rhs_id)];
67,834✔
538
    const size_t new_rep = dsu.unite(lhs_id, rhs_id);
67,834✔
539
    class_types[new_rep] = rhs_ts;
67,834✔
540
    merge_if_singleton(lhs_id);
67,834✔
541
}
67,834✔
542

543
void TypeDomain::State::assign_encoding(const Variable v, const TypeEncoding te) {
136,646✔
544
    detach(v);
136,646✔
545
    const size_t id = *var_ids.find_id(v);
136,646✔
546
    class_types[id] = TypeSet{te};
136,646✔
547
    merge_if_singleton(id);
136,646✔
548
}
136,646✔
549

550
// -- State: lattice ----------------------------------------------------------
551

552
TypeDomain::State TypeDomain::State::join(const State& a, const State& b) {
135,136✔
553
    // With the singleton-merging invariant, all variables with singleton {te}
554
    // share the same DSU rep (the sentinel) in each operand. So raw DSU reps
555
    // partition correctly without a special singleton key.
556

557
    // Collect all variables from both operands.
558
    std::set<Variable> all_vars_seen;
135,136✔
559
    for (const auto& v : a.var_ids.vars() | std::views::keys) {
3,746,872✔
560
        all_vars_seen.insert(v);
3,611,736✔
561
    }
562
    for (const auto& v : b.var_ids.vars() | std::views::keys) {
3,750,486✔
563
        all_vars_seen.insert(v);
3,615,350✔
564
    }
565

566
    // Compute (rep_left, rep_right) for each variable, group by key pair.
567
    // Variables absent from one side get unique keys (not nullopt) to avoid
568
    // falsely unifying unrelated variables that happen to share a rep on the
569
    // other side.
570
    size_t next_unique_a = a.dsu.size();
135,136✔
571
    size_t next_unique_b = b.dsu.size();
135,136✔
572
    std::map<std::pair<size_t, size_t>, std::vector<Variable>> key_groups;
135,136✔
573
    for (const auto& v : all_vars_seen) {
3,769,736✔
574
        size_t key_a;
1,817,300✔
575
        if (const auto id = a.var_ids.find_id(v)) {
3,634,600✔
576
            key_a = a.dsu.find_const(*id);
3,611,736✔
577
        } else {
578
            key_a = next_unique_a++;
22,864✔
579
        }
580
        size_t key_b;
1,817,300✔
581
        if (const auto id = b.var_ids.find_id(v)) {
3,634,600✔
582
            key_b = b.dsu.find_const(*id);
3,615,350✔
583
        } else {
584
            key_b = next_unique_b++;
19,250✔
585
        }
586
        key_groups[{key_a, key_b}].push_back(v);
3,634,600✔
587
    }
588

589
    // Build result
590
    State result;
135,136✔
591
    for (const auto& members : key_groups | std::views::values) {
1,755,332✔
592
        // TypeSet = union of per-variable TypeSets from both operands
593
        TypeSet ts{};
1,620,196✔
594
        for (const Variable& v : members) {
5,254,796✔
595
            ts |= a.get_typeset(v);
3,634,600✔
596
            ts |= b.get_typeset(v);
3,634,600✔
597
        }
598

599
        // Register all members in the result, unified
600
        std::optional<size_t> first_id;
1,620,196✔
601
        for (const Variable& v : members) {
5,254,796✔
602
            const size_t id = result.ensure_var(v);
3,634,600✔
603
            if (first_id) {
3,634,600✔
604
                result.dsu.unite(*first_id, id);
2,014,404✔
605
            } else {
606
                first_id = id;
2,627,398✔
607
            }
608
        }
609
        // Set the TypeSet on the representative (after all unifications).
610
        if (first_id) {
1,620,196✔
611
            result.class_types[result.dsu.find(*first_id)] = ts;
1,620,196✔
612
            result.merge_if_singleton(*first_id);
1,620,196✔
613
        }
614
    }
615

616
    return result;
202,704✔
617
}
135,136✔
618

619
std::optional<TypeDomain::State> TypeDomain::State::meet(const State& other) const {
62✔
620
    State result = *this;
62✔
621

622
    // Ensure all other variables exist in result
623
    for (const auto& v : other.var_ids.vars() | std::views::keys) {
78✔
624
        result.ensure_var(v);
16✔
625
    }
626

627
    // Merge equalities from other
628
    std::map<size_t, std::vector<Variable>> other_classes;
62✔
629
    for (const auto& [v, id] : other.var_ids.vars()) {
78✔
630
        const size_t rep = other.dsu.find_const(id);
16✔
631
        other_classes[rep].push_back(v);
16✔
632
    }
633
    for (const auto& members : other_classes | std::views::values) {
74✔
634
        for (size_t i = 1; i < members.size(); i++) {
16✔
635
            if (!result.assume_eq(members[0], members[i])) {
4✔
UNCOV
636
                return std::nullopt;
×
637
            }
638
        }
639
    }
640

641
    // Intersect TypeSets from other
642
    for (const auto& v : other.var_ids.vars() | std::views::keys) {
74✔
643
        if (!result.restrict_var(v, other.get_typeset(v))) {
14✔
644
            return std::nullopt;
2✔
645
        }
646
    }
647

648
    return result;
60✔
649
}
62✔
650

651
bool TypeDomain::State::is_subsumed_by(const State& other) const {
572✔
652
    // Check TypeSet refinement: S[v] in self must be subset of S[v] in other.
653
    for (const auto& [v, id] : var_ids.vars()) {
1,464✔
654
        const size_t rep = dsu.find_const(id);
910✔
655
        const TypeSet ts_self = class_types[rep];
910✔
656
        const TypeSet ts_other = other.get_typeset(v);
910✔
657
        if (!ts_self.is_subset_of(ts_other)) {
910✔
658
            return false;
18✔
659
        }
660
    }
661
    for (const auto& [v, id] : other.var_ids.vars()) {
1,412✔
662
        if (!var_ids.contains(v)) {
864✔
663
            const size_t rep = other.dsu.find_const(id);
6✔
664
            if (other.class_types[rep] != TypeSet::all()) {
6✔
665
                return false;
15✔
666
            }
667
        }
668
    }
669

670
    // Check equality preservation: other's equalities must hold in self.
671
    std::map<size_t, std::vector<Variable>> other_classes;
548✔
672
    for (const auto& [v, id] : other.var_ids.vars()) {
1,406✔
673
        const size_t rep = other.dsu.find_const(id);
858✔
674
        other_classes[rep].push_back(v);
858✔
675
    }
676
    for (const auto& members : other_classes | std::views::values) {
1,260✔
677
        if (members.size() <= 1) {
712✔
678
            continue;
608✔
679
        }
680
        const auto id0 = var_ids.find_id(members[0]);
104✔
681
        if (!id0) {
104✔
UNCOV
682
            for (size_t i = 1; i < members.size(); i++) {
×
UNCOV
683
                if (var_ids.contains(members[i])) {
×
UNCOV
684
                    return false;
×
685
                }
686
            }
687
            continue;
×
UNCOV
688
        }
×
689
        const size_t rep0 = dsu.find_const(*id0);
104✔
690
        for (size_t i = 1; i < members.size(); i++) {
250✔
691
            const auto id_i = var_ids.find_id(members[i]);
146✔
692
            if (!id_i) {
146✔
UNCOV
693
                return false;
×
694
            }
695
            if (dsu.find_const(*id_i) != rep0) {
219✔
696
                return false;
697
            }
698
        }
699
    }
700

701
    return true;
274✔
702
}
548✔
703

704
// ============================================================================
705
// TypeDomain — thin WithBottom wrappers
706
// ============================================================================
707

708
void TypeDomain::set_to_top() { state_ = std::make_unique<State>(); }
8✔
709
void TypeDomain::set_to_bottom() { state_.reset(); }
4,440✔
710

711
// -- Lattice -----------------------------------------------------------------
712

713
TypeDomain TypeDomain::join(const TypeDomain& other) const {
135,138✔
714
    if (!state_) {
135,138✔
UNCOV
715
        return other;
×
716
    }
717
    if (!other.state_) {
135,138✔
718
        return *this;
67,570✔
719
    }
720
    TypeDomain result;
135,136✔
721
    result.state_ = std::make_unique<State>(State::join(*state_, *other.state_));
202,704✔
722
    return result;
135,136✔
723
}
135,136✔
724

725
void TypeDomain::operator|=(const TypeDomain& other) {
135,014✔
726
    if (!other.state_) {
135,014✔
727
        return;
728
    }
729
    if (!state_) {
135,014✔
UNCOV
730
        *this = other;
×
UNCOV
731
        return;
×
732
    }
733
    *this = join(other);
202,521✔
734
}
735

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

738
std::optional<TypeDomain> TypeDomain::meet(const TypeDomain& other) const {
62✔
739
    if (!state_ || !other.state_) {
62✔
UNCOV
740
        return std::nullopt;
×
741
    }
742
    if (auto result = state_->meet(*other.state_)) {
62✔
743
        TypeDomain td;
60✔
744
        td.state_ = std::make_unique<State>(std::move(*result));
90✔
745
        return td;
60✔
746
    }
91✔
747
    return std::nullopt;
2✔
748
}
749

750
bool TypeDomain::operator<=(const TypeDomain& other) const {
574✔
751
    if (!state_) {
574✔
752
        return true;
1✔
753
    }
754
    if (!other.state_) {
572✔
755
        return false;
756
    }
757
    return state_->is_subsumed_by(*other.state_);
572✔
758
}
759

UNCOV
760
TypeDomain TypeDomain::narrow(const TypeDomain& other) const {
×
UNCOV
761
    if (auto res = meet(other)) {
×
UNCOV
762
        return std::move(*res);
×
UNCOV
763
    }
×
UNCOV
764
    TypeDomain res;
×
UNCOV
765
    res.set_to_bottom();
×
UNCOV
766
    return res;
×
UNCOV
767
}
×
768

769
// -- Assignment --------------------------------------------------------------
770

771
void TypeDomain::assign_type(const Reg& lhs, const Reg& rhs) {
55,096✔
772
    if (auto* s = state_.get()) {
55,096✔
773
        s->assign_copy(reg_type(lhs), reg_type(rhs));
55,096✔
774
    }
775
}
55,096✔
776

777
void TypeDomain::assign_type(const Reg& lhs, const std::optional<LinearExpression>& rhs) {
27,494✔
778
    if (!state_) {
27,494✔
779
        return;
780
    }
781
    if (!rhs) {
27,494✔
UNCOV
782
        havoc_type(reg_type(lhs));
×
UNCOV
783
        return;
×
784
    }
785
    if (!state_->assign_from_expr(reg_type(lhs), *rhs)) {
27,494✔
UNCOV
786
        set_to_bottom();
×
787
    }
788
}
789

790
void TypeDomain::assign_type(const std::optional<Variable> lhs, const LinearExpression& t) {
29,510✔
791
    if (!state_ || !lhs) {
29,510✔
792
        return;
2✔
793
    }
794
    if (!state_->assign_from_expr(*lhs, t)) {
29,506✔
UNCOV
795
        set_to_bottom();
×
796
    }
797
}
798

799
void TypeDomain::assign_type(const Reg& lhs, const TypeEncoding type) {
136,646✔
800
    if (auto* s = state_.get()) {
136,646✔
801
        s->assign_encoding(reg_type(lhs), type);
136,646✔
802
    }
803
}
136,646✔
804

805
// -- Constraint handling -----------------------------------------------------
806

807
void TypeDomain::restrict_to(const Variable v, const TypeSet mask) {
105,512✔
808
    if (auto* s = state_.get()) {
105,512✔
809
        if (!s->restrict_var(v, mask)) {
105,512✔
810
            set_to_bottom();
8✔
811
        }
812
    }
813
}
105,512✔
814

815
void TypeDomain::assume_eq(const Variable v1, const Variable v2) {
12✔
816
    if (auto* s = state_.get()) {
12✔
817
        if (!s->assume_eq(v1, v2)) {
12✔
818
            set_to_bottom();
2✔
819
        }
820
    }
821
}
12✔
822

823
void TypeDomain::remove_type(const Variable v, const TypeEncoding te) {
5,572✔
824
    if (auto* s = state_.get()) {
5,572✔
825
        if (!s->remove_type(v, te)) {
5,572✔
826
            set_to_bottom();
4,430✔
827
        }
828
    }
829
}
5,572✔
830

831
// -- Havoc -------------------------------------------------------------------
832

833
void TypeDomain::havoc_type(const Reg& r) {
134,498✔
834
    if (auto* s = state_.get()) {
134,498✔
835
        s->detach(reg_type(r));
134,498✔
836
    }
837
}
134,498✔
838

839
void TypeDomain::havoc_type(const Variable& v) {
6,624✔
840
    if (auto* s = state_.get()) {
6,624✔
841
        s->detach(v);
6,624✔
842
    }
843
}
6,624✔
844

845
// -- Query methods -----------------------------------------------------------
846

847
TypeSet TypeDomain::get_typeset(const Variable v) const {
74,123,212✔
848
    if (const auto* s = state_.get()) {
74,123,212✔
849
        return s->get_typeset(v);
74,118,782✔
850
    }
851
    return TypeSet{}; // bottom
4,430✔
852
}
853

854
std::vector<TypeEncoding> TypeDomain::iterate_types(const Reg& reg) const {
358,920✔
855
    if (!state_) {
358,920✔
UNCOV
856
        return {};
×
857
    }
858
    const TypeSet ts = get_typeset(reg_type(reg));
358,920✔
859
    if (ts.contains(T_UNINIT)) {
358,920✔
UNCOV
860
        return {T_UNINIT};
×
861
    }
862
    return ts.remove(T_UNINIT).to_vector();
358,920✔
863
}
864

865
std::optional<TypeEncoding> TypeDomain::get_type(const Reg& r) const { return get_typeset(reg_type(r)).as_singleton(); }
26,058✔
866

867
bool TypeDomain::implies_superset(const Reg& premise_reg, const TypeSet premise_set, const Reg& conclusion_reg,
14,902✔
868
                                  const TypeSet conclusion_set) const {
869
    if (!state_) {
14,902✔
870
        return true;
871
    }
872
    State restricted = *state_;
14,902✔
873
    if (!restricted.restrict_var(reg_type(premise_reg), premise_set)) {
14,902✔
874
        return true;
5,344✔
875
    }
876
    return restricted.get_typeset(reg_type(conclusion_reg)).is_subset_of(conclusion_set);
4,214✔
877
}
14,902✔
878

879
bool TypeDomain::implies_not_type(const Reg& premise_reg, const TypeEncoding excluded_type, const Reg& conclusion_reg,
912✔
880
                                  const TypeSet conclusion_set) const {
881
    if (!state_) {
912✔
882
        return true;
883
    }
884
    State restricted = *state_;
912✔
885
    if (!restricted.remove_type(reg_type(premise_reg), excluded_type)) {
912✔
886
        return true;
5✔
887
    }
888
    return restricted.get_typeset(reg_type(conclusion_reg)).is_subset_of(conclusion_set);
902✔
889
}
912✔
890

891
bool TypeDomain::entail_type(const Variable v, const TypeEncoding te) const {
14✔
892
    if (!state_) {
14✔
893
        return true; // bottom entails everything
894
    }
895
    return get_typeset(v) == TypeSet{te};
14✔
896
}
897

898
bool TypeDomain::may_have_type(const Reg& r, const TypeEncoding type) const {
442✔
899
    return get_typeset(reg_type(r)).contains(type);
442✔
900
}
901

902
bool TypeDomain::may_have_type(const Variable v, const TypeEncoding type) const {
71,380,094✔
903
    return get_typeset(v).contains(type);
71,380,094✔
904
}
905

906
bool TypeDomain::is_initialized(const Reg& r) const { return !get_typeset(reg_type(r)).contains(T_UNINIT); }
600,298✔
907

908
bool TypeDomain::is_initialized(const Variable v) const { return !get_typeset(v).contains(T_UNINIT); }
1,359,782✔
909

910
bool TypeDomain::same_type(const Reg& a, const Reg& b) const {
20,936✔
911
    if (const auto* s = state_.get()) {
20,936✔
912
        return s->same_type(reg_type(a), reg_type(b));
20,936✔
913
    }
914
    return true; // bottom entails everything
915
}
916

917
bool TypeDomain::is_in_group(const Reg& r, const TypeSet types) const {
397,604✔
918
    return get_typeset(reg_type(r)).is_subset_of(types);
397,604✔
919
}
920

921
// -- Serialization -----------------------------------------------------------
922

923
StringInvariant TypeDomain::to_set() const {
1,012✔
924
    if (!state_) {
1,012✔
UNCOV
925
        return StringInvariant::bottom();
×
926
    }
927
    return state_->to_set();
1,012✔
928
}
929

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

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