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

Alan-Jowett / ebpf-verifier / 22075299923

16 Feb 2026 04:55PM UTC coverage: 87.121% (+0.8%) from 86.313%
22075299923

push

github

web-flow
Replace Number backend: boost's cpp_int → __int128 (#1007)

Backport the i128 representation from the Rust port. Number is an
intermediate representation for eBPF verification where all inputs
and outputs are ≤64-bit; i128 provides headroom for widening
arithmetic without the overhead of arbitrary-precision integers.

This commit has been verified to produce the same invariants as its parent on the test data.

Key changes to src/arith/num_big.hpp:
- Use __int128 on GCC/Clang, boost::multiprecision::int128_t on MSVC
- All arithmetic operators use checked helpers (__builtin_*_overflow
  on GCC/Clang, manual pre-checks on MSVC) to prevent signed overflow UB
- Simplify sign_extend/zero_extend: direct bit arithmetic replaces
  template dispatch over fixed widths
- fill_ones computed in unsigned space to avoid shift UB
- Custom decimal parser accumulates in UInt128 to handle kInt128Min
- Width assertions tightened to eBPF domain bounds (≤64/65)
 Consequential simplifications:
- Weight = Number (was already the case), so convert_NtoW_overflow
  was a passthrough returning false — inlined at all ~18 call sites
  in zone_domain.cpp, removing dead overflow branches
- eval_expression_overflow → eval_expression, returns Weight directly
- Remove dead SafeI64 overload and num_safeint.hpp include
- Custom int128_to_string in printing.cpp (no std::to_string for __int128)
- Add missing transitive includes (<algorithm>, <cassert>) previously
  provided by boost headers

Also: remove unused stats infrastructure, rename_classes.py, and
miscellaneous cleanup (docs ASCII arrows, splitdbm simplifications).

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

139 of 166 new or added lines in 5 files covered. (83.73%)

52 existing lines in 4 files now uncovered.

9389 of 10777 relevant lines covered (87.12%)

3125310.38 hits per line

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

69.04
/src/ir/cfg_builder.cpp
1
// Copyright (c) Prevail Verifier contributors.
2
// SPDX-License-Identifier: MIT
3
#include <algorithm>
4
#include <cassert>
5
#include <map>
6
#include <optional>
7
#include <string>
8
#include <vector>
9

10
#include "cfg/cfg.hpp"
11
#include "cfg/wto.hpp"
12
#include "config.hpp"
13
#include "ir/program.hpp"
14
#include "ir/syntax.hpp"
15
#include "platform.hpp"
16

17
using std::optional;
18
using std::set;
19
using std::string;
20
using std::to_string;
21
using std::vector;
22

23
namespace prevail {
24
struct CfgBuilder final {
25
    Program prog;
26

27
    // TODO: ins should be inserted elsewhere
28
    void insert_after(const Label& prev_label, const Label& new_label, const Instruction& ins) {
40✔
29
        if (prev_label == new_label) {
40✔
30
            CRAB_ERROR("Cannot insert after the same label ", to_string(new_label));
×
31
        }
32
        std::set<Label> prev_children;
40✔
33
        std::swap(prev_children, prog.m_cfg.get_node(prev_label).children);
40✔
34

35
        for (const Label& next_label : prev_children) {
88✔
36
            prog.m_cfg.get_node(next_label).parents.erase(prev_label);
48✔
37
        }
38

39
        insert(new_label, ins);
40✔
40
        for (const Label& next_label : prev_children) {
88✔
41
            add_child(prev_label, new_label);
48✔
42
            add_child(new_label, next_label);
48✔
43
        }
44
    }
40✔
45

46
    // TODO: ins should be inserted elsewhere
47
    void insert(const Label& _label, const Instruction& ins) {
385,030✔
48
        if (const auto it = prog.m_cfg.neighbours.find(_label); it != prog.m_cfg.neighbours.end()) {
385,030✔
49
            CRAB_ERROR("Label ", to_string(_label), " already exists");
×
50
        }
51
        prog.m_cfg.neighbours.emplace(_label, Cfg::Adjacent{});
385,030✔
52
        prog.m_instructions.emplace(_label, ins);
385,030✔
53
    }
385,030✔
54

55
    // TODO: ins should be inserted elsewhere
56
    Label insert_jump(const Label& from, const Label& to, const Instruction& ins) {
60,072✔
57
        const Label jump_label = Label::make_jump(from, to);
60,072✔
58
        if (prog.m_cfg.contains(jump_label)) {
60,072✔
59
            CRAB_ERROR("Jump label ", to_string(jump_label), " already exists");
×
60
        }
61
        insert(jump_label, ins);
60,072✔
62
        add_child(from, jump_label);
60,072✔
63
        add_child(jump_label, to);
60,072✔
64
        return jump_label;
60,072✔
65
    }
×
66

67
    void add_child(const Label& a, const Label& b) {
418,066✔
68
        assert(b != Label::entry);
418,066✔
69
        assert(a != Label::exit);
418,066✔
70
        prog.m_cfg.neighbours.at(a).children.insert(b);
418,066✔
71
        prog.m_cfg.neighbours.at(b).parents.insert(a);
418,066✔
72
    }
418,066✔
73

74
    void remove_child(const Label& a, const Label& b) {
136✔
75
        prog.m_cfg.get_node(a).children.erase(b);
136✔
76
        prog.m_cfg.get_node(b).parents.erase(a);
136✔
77
    }
136✔
78

79
    void set_assertions(const Label& label, const std::vector<Assertion>& assertions) {
390,416✔
80
        if (!prog.m_cfg.contains(label)) {
390,416✔
81
            CRAB_ERROR("Label ", to_string(label), " not found in the CFG: ");
×
82
        }
83
        prog.m_assertions.insert_or_assign(label, assertions);
390,416✔
84
    }
390,416✔
85
};
86

87
/// Get the inverse of a given comparison operation.
88
static Condition::Op reverse(const Condition::Op op) {
30,036✔
89
    switch (op) {
30,036✔
90
    case Condition::Op::EQ: return Condition::Op::NE;
6,301✔
91
    case Condition::Op::NE: return Condition::Op::EQ;
5,036✔
92

93
    case Condition::Op::GE: return Condition::Op::LT;
175✔
94
    case Condition::Op::LT: return Condition::Op::GE;
383✔
95

96
    case Condition::Op::SGE: return Condition::Op::SLT;
21✔
97
    case Condition::Op::SLT: return Condition::Op::SGE;
825✔
98

99
    case Condition::Op::LE: return Condition::Op::GT;
47✔
100
    case Condition::Op::GT: return Condition::Op::LE;
1,187✔
101

102
    case Condition::Op::SLE: return Condition::Op::SGT;
21✔
103
    case Condition::Op::SGT: return Condition::Op::SLE;
1,003✔
104

105
    case Condition::Op::SET: return Condition::Op::NSET;
17✔
106
    case Condition::Op::NSET: return Condition::Op::SET;
2✔
107
    }
108
    assert(false);
109
    return {};
110
}
111

112
/// Get the inverse of a given comparison condition.
113
static Condition reverse(const Condition& cond) {
30,036✔
114
    return {.op = reverse(cond.op), .left = cond.left, .right = cond.right, .is64 = cond.is64};
30,036✔
115
}
116

117
static bool has_fall(const Instruction& ins) {
291,496✔
118
    if (std::holds_alternative<Exit>(ins)) {
291,496✔
119
        return false;
1,601✔
120
    }
121

122
    if (const auto pins = std::get_if<Jmp>(&ins)) {
288,294✔
123
        if (!pins->cond) {
144✔
124
            return false;
72✔
125
        }
126
    }
127

128
    return true;
144,075✔
129
}
130

131
enum class RejectKind {
132
    NotImplemented,
133
    Capability,
134
};
135

136
struct RejectionReason {
46✔
137
    RejectKind kind{};
138
    std::string detail;
139
};
140

141
static bool supports(const ebpf_platform_t& platform, const bpf_conformance_groups_t group) {
494,218✔
142
    return platform.supports_group(group);
492,364✔
143
}
144

145
static bool un_requires_base64(const Un& un) {
4,465✔
146
    switch (un.op) {
4,465✔
147
    case Un::Op::BE64:
35✔
148
    case Un::Op::LE64:
149
    case Un::Op::SWAP64: return true;
35✔
150
    default: return false;
2,941✔
151
    }
152
}
153

154
static std::optional<RejectionReason> check_instruction_feature_support(const Instruction& ins,
490,431✔
155
                                                                        const ebpf_platform_t& platform) {
156
    auto reject_not_implemented = [](std::string detail) {
164,131✔
157
        return RejectionReason{.kind = RejectKind::NotImplemented, .detail = std::move(detail)};
4✔
158
    };
159
    auto reject_capability = [](std::string detail) {
164,169✔
160
        return RejectionReason{.kind = RejectKind::Capability, .detail = std::move(detail)};
42✔
161
    };
162

163
    if (std::holds_alternative<CallBtf>(ins)) {
490,431✔
164
        return reject_not_implemented("call helper by BTF id");
4✔
165
    }
166
    if (const auto p = std::get_if<Call>(&ins)) {
490,429✔
167
        if (!p->is_supported) {
32,385✔
168
            return reject_capability(p->unsupported_reason);
3✔
169
        }
170
    }
171
    if (std::holds_alternative<Callx>(ins) && !supports(platform, bpf_conformance_groups_t::callx)) {
490,427✔
172
        return reject_capability("requires conformance group callx");
×
173
    }
174
    if ((std::holds_alternative<Call>(ins) || std::holds_alternative<CallLocal>(ins) ||
1,099,823✔
175
         std::holds_alternative<Callx>(ins) || std::holds_alternative<Exit>(ins)) &&
1,276,477✔
176
        !supports(platform, bpf_conformance_groups_t::base32)) {
35,312✔
177
        return reject_capability("requires conformance group base32");
×
178
    }
179
    if (const auto p = std::get_if<Bin>(&ins)) {
576,524✔
180
        if (!supports(platform, p->is64 ? bpf_conformance_groups_t::base64 : bpf_conformance_groups_t::base32)) {
282,496✔
181
            return reject_capability(p->is64 ? "requires conformance group base64"
×
182
                                             : "requires conformance group base32");
×
183
        }
184
        if ((p->op == Bin::Op::MUL || p->op == Bin::Op::UDIV || p->op == Bin::Op::UMOD || p->op == Bin::Op::SDIV ||
255,818✔
185
             p->op == Bin::Op::SMOD) &&
428,715✔
186
            !supports(platform, p->is64 ? bpf_conformance_groups_t::divmul64 : bpf_conformance_groups_t::divmul32)) {
5,330✔
187
            return reject_capability(p->is64 ? "requires conformance group divmul64"
×
188
                                             : "requires conformance group divmul32");
×
189
        }
190
    }
191
    if (const auto p = std::get_if<Un>(&ins)) {
490,426✔
192
        const bool need_base64 = p->is64 || un_requires_base64(*p);
6,321✔
193
        if (!supports(platform, need_base64 ? bpf_conformance_groups_t::base64 : bpf_conformance_groups_t::base32)) {
4,740✔
194
            return reject_capability(need_base64 ? "requires conformance group base64"
6✔
195
                                                 : "requires conformance group base32");
2✔
196
        }
197
    }
198
    if (const auto p = std::get_if<Jmp>(&ins)) {
490,425✔
199
        if (!supports(platform,
84,196✔
200
                      p->cond ? (p->cond->is64 ? bpf_conformance_groups_t::base64 : bpf_conformance_groups_t::base32)
84,196✔
201
                              : bpf_conformance_groups_t::base32)) {
202
            return reject_capability((p->cond && p->cond->is64) ? "requires conformance group base64"
×
203
                                                                : "requires conformance group base32");
×
204
        }
205
    }
206
    if (const auto p = std::get_if<LoadPseudo>(&ins)) {
490,428✔
207
        if (!supports(platform, bpf_conformance_groups_t::base64)) {
11✔
208
            return reject_capability("requires conformance group base64");
×
209
        }
210
        switch (p->addr.kind) {
11✔
211
        case PseudoAddress::Kind::VARIABLE_ADDR: return reject_not_implemented("lddw variable_addr pseudo");
5✔
212
        case PseudoAddress::Kind::CODE_ADDR: return reject_not_implemented("lddw code_addr pseudo");
×
213
        case PseudoAddress::Kind::MAP_BY_IDX:
6✔
214
        case PseudoAddress::Kind::MAP_VALUE_BY_IDX: break; // Resolved during CFG construction.
6✔
215
        default: return reject_not_implemented("lddw unknown pseudo");
×
216
        }
217
    }
218
    if ((std::holds_alternative<LoadMapFd>(ins) || std::holds_alternative<LoadMapAddress>(ins)) &&
504,506✔
219
        !supports(platform, bpf_conformance_groups_t::base64)) {
21,138✔
220
        return reject_capability("requires conformance group base64");
×
221
    }
222
    if (const auto p = std::get_if<Mem>(&ins)) {
490,423✔
223
        if (!supports(platform,
116,219✔
224
                      (p->access.width == 8) ? bpf_conformance_groups_t::base64 : bpf_conformance_groups_t::base32)) {
116,219✔
225
            return reject_capability((p->access.width == 8) ? "requires conformance group base64"
×
226
                                                            : "requires conformance group base32");
×
227
        }
228
        if (p->is_signed && !supports(platform, bpf_conformance_groups_t::base64)) {
116,219✔
229
            return reject_capability("requires conformance group base64");
×
230
        }
231
    }
232
    if (std::holds_alternative<Packet>(ins) && !supports(platform, bpf_conformance_groups_t::packet)) {
490,423✔
233
        return reject_capability("requires conformance group packet");
76✔
234
    }
235
    if (const auto p = std::get_if<Atomic>(&ins)) {
490,385✔
236
        const auto group =
1,076✔
237
            (p->access.width == 8) ? bpf_conformance_groups_t::atomic64 : bpf_conformance_groups_t::atomic32;
807✔
238
        if (!supports(platform, group)) {
807✔
239
            return reject_capability((group == bpf_conformance_groups_t::atomic64)
×
240
                                         ? "requires conformance group atomic64"
241
                                         : "requires conformance group atomic32");
×
242
        }
243
    }
244
    return {};
490,385✔
245
}
246

247
// Validate instruction-level feature support before CFG construction.
248
// This is the user-facing rejection point for unsupported or unavailable features.
249
static void validate_instruction_feature_support(const InstructionSeq& insts, const ebpf_platform_t& platform) {
2,830✔
250
    for (const auto& [label, inst, _] : insts) {
331,038✔
251
        if (const auto reason = check_instruction_feature_support(inst, platform)) {
328,254✔
252
            if (reason->kind == RejectKind::NotImplemented) {
46✔
253
                throw InvalidControlFlow{"not implemented: " + reason->detail + " (at " + to_string(label) + ")"};
8✔
254
            }
255
            throw InvalidControlFlow{"rejected: " + reason->detail + " (at " + to_string(label) + ")"};
84✔
256
        }
328,231✔
257
    }
258
}
2,784✔
259

260
/// Update a control-flow graph to inline function macros.
261
static void add_cfg_nodes(CfgBuilder& builder, const Label& caller_label, const Label& entry_label) {
140✔
262
    bool first = true;
140✔
263

264
    // Get the label of the node to go to on returning from the macro.
265
    Label exit_to_label = builder.prog.cfg().get_child(caller_label);
140✔
266

267
    // Construct the variable prefix to use for the new stack frame
268
    // and store a copy in the CallLocal instruction since the instruction-specific
269
    // labels may only exist until the CFG is simplified.
270
    const std::string stack_frame_prefix = to_string(caller_label);
140✔
271
    if (const auto pcall = std::get_if<CallLocal>(&builder.prog.instruction_at(caller_label))) {
210✔
272
        pcall->stack_frame_prefix = stack_frame_prefix;
140✔
273
    }
274

275
    // Walk the transitive closure of CFG nodes starting at entry_label and ending at
276
    // any exit instruction.
277
    std::set macro_labels{entry_label};
367✔
278
    std::set seen_labels{entry_label};
367✔
279
    while (!macro_labels.empty()) {
672✔
280
        Label macro_label = *macro_labels.begin();
536✔
281
        macro_labels.erase(macro_label);
536✔
282

283
        if (stack_frame_prefix == macro_label.stack_frame_prefix) {
536✔
284
            throw InvalidControlFlow{stack_frame_prefix + ": illegal recursion"};
6✔
285
        }
286

287
        // Clone the macro block into a new block with the new stack frame prefix.
288
        const Label label{macro_label.from, macro_label.to, stack_frame_prefix};
800✔
289
        auto inst = builder.prog.instruction_at(macro_label);
532✔
290
        if (const auto pexit = std::get_if<Exit>(&inst)) {
532✔
291
            pexit->stack_frame_prefix = label.stack_frame_prefix;
142✔
292
        } else if (const auto pcall = std::get_if<Call>(&inst)) {
462✔
293
            pcall->stack_frame_prefix = label.stack_frame_prefix;
2✔
294
        }
295
        builder.insert(label, inst);
532✔
296

297
        if (first) {
532✔
298
            // Add an edge from the caller to the new block.
299
            first = false;
140✔
300
            builder.add_child(caller_label, label);
140✔
301
        }
302

303
        // Add an edge from any other predecessors.
304
        for (const auto& prev_macro_nodes = builder.prog.cfg().parents_of(macro_label);
798✔
305
             const auto& prev_macro_label : prev_macro_nodes) {
1,198✔
306
            const Label prev_label(prev_macro_label.from, prev_macro_label.to, to_string(caller_label));
400✔
307
            if (const auto& labels = builder.prog.cfg().labels();
400✔
308
                std::ranges::find(labels, prev_label) != labels.end()) {
400✔
309
                builder.add_child(prev_label, label);
394✔
310
            }
311
        }
400✔
312

313
        // Walk all successor nodes.
314
        for (const auto& next_macro_nodes = builder.prog.cfg().children_of(macro_label);
1,067✔
315
             const auto& next_macro_label : next_macro_nodes) {
1,336✔
316
            if (next_macro_label == builder.prog.cfg().exit_label()) {
807✔
317
                // This is an exit transition, so add edge to the block to execute
318
                // upon returning from the macro.
319
                builder.add_child(label, exit_to_label);
140✔
320
            } else if (!seen_labels.contains(next_macro_label)) {
398✔
321
                // Push any other unprocessed successor label onto the list to be processed.
322
                if (!macro_labels.contains(next_macro_label)) {
396✔
323
                    macro_labels.insert(next_macro_label);
396✔
324
                }
325
                seen_labels.insert(next_macro_label);
396✔
326
            }
327
        }
328
    }
1,068✔
329

330
    // Remove the original edge from the caller node to its successor,
331
    // since processing now goes through the function macro instead.
332
    builder.remove_child(caller_label, exit_to_label);
136✔
333

334
    // Finally, recurse to replace any nested function macros.
335
    string caller_label_str = to_string(caller_label);
136✔
336
    const long stack_frame_depth = std::ranges::count(caller_label_str, STACK_FRAME_DELIMITER) + 2;
136✔
337
    for (const auto& macro_label : seen_labels) {
596✔
338
        const Label label{macro_label.from, macro_label.to, caller_label_str};
750✔
339
        if (const auto pins = std::get_if<CallLocal>(&builder.prog.instruction_at(label))) {
720✔
340
            if (stack_frame_depth >= MAX_CALL_STACK_FRAMES) {
82✔
341
                throw InvalidControlFlow{"too many call stack frames"};
10✔
342
            }
343
            add_cfg_nodes(builder, label, pins->target);
78✔
344
        }
345
    }
490✔
346
}
588✔
347

348
/// Resolve a LoadPseudo with map-by-index addressing to the concrete LoadMapFd or LoadMapAddress instruction.
349
static Instruction resolve_map_by_index(const LoadPseudo& pseudo) {
6✔
350
    const auto& descriptors = thread_local_program_info->map_descriptors;
6✔
351
    if (pseudo.addr.imm < 0 || static_cast<size_t>(pseudo.addr.imm) >= descriptors.size()) {
6✔
352
        throw InvalidControlFlow{"invalid map index " + std::to_string(pseudo.addr.imm) + " (have " +
5✔
353
                                 std::to_string(descriptors.size()) + " maps)"};
6✔
354
    }
355
    const auto map_idx = static_cast<size_t>(pseudo.addr.imm);
4✔
356
    const int mapfd = descriptors.at(map_idx).original_fd;
4✔
357
    switch (pseudo.addr.kind) {
4✔
358
    case PseudoAddress::Kind::MAP_BY_IDX: return LoadMapFd{.dst = pseudo.dst, .mapfd = mapfd};
2✔
359
    case PseudoAddress::Kind::MAP_VALUE_BY_IDX:
2✔
360
        return LoadMapAddress{.dst = pseudo.dst, .mapfd = mapfd, .offset = pseudo.addr.next_imm};
2✔
UNCOV
361
    default: CRAB_ERROR("Invalid address kind: ", static_cast<int>(pseudo.addr.kind));
×
362
    }
363
}
364

365
/// Convert an instruction sequence to a control-flow graph (CFG).
366
static CfgBuilder instruction_seq_to_cfg(const InstructionSeq& insts, const bool must_have_exit) {
2,784✔
367
    CfgBuilder builder;
2,784✔
368
    assert(thread_local_program_info->platform != nullptr && "platform must be set before CFG construction");
2,784✔
369

370
    // First, add all instructions to the CFG without connecting
371
    for (const auto& [label, inst, _] : insts) {
327,136✔
372
        assert(!check_instruction_feature_support(inst, *thread_local_program_info->platform).has_value() &&
324,354✔
373
               "instruction support must be validated before CFG construction");
374
        if (std::holds_alternative<Undefined>(inst)) {
324,354✔
UNCOV
375
            continue;
×
376
        }
377
        if (const auto* pseudo = std::get_if<LoadPseudo>(&inst)) {
324,354✔
378
            builder.insert(label, resolve_map_by_index(*pseudo));
8✔
379
        } else {
380
            builder.insert(label, inst);
324,348✔
381
        }
382
    }
383

384
    if (insts.empty()) {
2,782✔
UNCOV
385
        throw InvalidControlFlow{"empty instruction sequence"};
×
386
    } else {
387
        const auto& [label, inst, _0] = insts[0];
2,782✔
388
        builder.add_child(builder.prog.cfg().entry_label(), label);
2,787✔
389
    }
390

391
    // Do a first pass ignoring all function macro calls.
392
    for (size_t i = 0; i < insts.size(); i++) {
327,134✔
393
        const auto& [label, inst, _0] = insts[i];
324,352✔
394

395
        if (std::holds_alternative<Undefined>(inst)) {
324,352✔
396
            continue;
18✔
397
        }
398
        assert(!std::holds_alternative<CallBtf>(inst) && "CallBtf must be rejected before CFG edge construction");
324,352✔
399

400
        Label fallthrough{builder.prog.cfg().exit_label()};
324,352✔
401
        if (i + 1 < insts.size()) {
324,352✔
402
            fallthrough = std::get<0>(insts[i + 1]);
321,570✔
403
        } else {
404
            if (has_fall(inst) && must_have_exit) {
3,483✔
UNCOV
405
                throw InvalidControlFlow{"fallthrough in last instruction"};
×
406
            }
407
        }
408
        if (const auto jmp = std::get_if<Jmp>(&inst)) {
324,352✔
409
            if (const auto cond = jmp->cond) {
35,638✔
410
                Label target_label = jmp->target;
30,054✔
411
                if (target_label == fallthrough) {
30,054✔
412
                    builder.add_child(label, fallthrough);
18✔
413
                    continue;
18✔
414
                }
415
                if (!builder.prog.cfg().contains(target_label)) {
30,036✔
UNCOV
416
                    throw InvalidControlFlow{"jump to undefined label " + to_string(target_label)};
×
417
                }
418
                builder.insert_jump(label, target_label, Assume{.cond = *cond, .is_implicit = true});
45,054✔
419
                builder.insert_jump(label, fallthrough, Assume{.cond = reverse(*cond), .is_implicit = true});
60,072✔
420
            } else {
30,054✔
421
                builder.add_child(label, jmp->target);
5,584✔
422
            }
423
        } else {
424
            if (has_fall(inst)) {
288,714✔
425
                builder.add_child(label, fallthrough);
286,892✔
426
            }
427
        }
428
        if (std::holds_alternative<Exit>(inst)) {
324,334✔
429
            builder.add_child(label, builder.prog.cfg().exit_label());
2,733✔
430
        }
431
    }
324,352✔
432

433
    // Now replace macros. We have to do this as a second pass so that
434
    // we only add new nodes that are actually reachable, based on the
435
    // results of the first pass.
436
    for (const auto& [label, inst, _] : insts) {
327,078✔
437
        if (const auto pins = std::get_if<CallLocal>(&inst)) {
324,331✔
438
            add_cfg_nodes(builder, label, pins->target);
62✔
439
        }
440
    }
441

442
    return builder;
2,774✔
443
}
10✔
444

445
Program Program::from_sequence(const InstructionSeq& inst_seq, const ProgramInfo& info,
2,830✔
446
                               const ebpf_verifier_options_t& options) {
447
    thread_local_program_info.set(info);
4,245✔
448
    thread_local_options = options;
2,830✔
449
    assert(info.platform != nullptr && "platform must be set before instruction feature validation");
2,830✔
450
    validate_instruction_feature_support(inst_seq, *info.platform);
2,830✔
451

452
    // Convert the instruction sequence to a deterministic control-flow graph.
453
    CfgBuilder builder = instruction_seq_to_cfg(inst_seq, options.cfg_opts.must_have_exit);
2,784✔
454

455
    // Detect loops using Weak Topological Ordering (WTO) and insert counters at loop entry points. WTO provides a
456
    // hierarchical decomposition of the CFG that identifies all strongly connected components (cycles) and their entry
457
    // points. These entry points serve as natural locations for loop counters that help verify program termination.
458
    if (options.cfg_opts.check_for_termination) {
2,774✔
459
        const Wto wto{builder.prog.cfg()};
42✔
460
        wto.for_each_loop_head([&](const Label& label) -> void {
42✔
461
            builder.insert_after(label, Label::make_increment_counter(label), IncrementLoopCounter{label});
80✔
462
        });
40✔
463
    }
42✔
464

465
    // Annotate the CFG by explicitly adding in assertions before every memory instruction.
466
    for (const auto& label : builder.prog.labels()) {
393,190✔
467
        builder.set_assertions(label, get_assertions(builder.prog.instruction_at(label), info, label));
585,624✔
468
    }
469
    return std::move(builder.prog);
4,161✔
470
}
2,774✔
471

472
std::set<BasicBlock> BasicBlock::collect_basic_blocks(const Cfg& cfg, const bool simplify) {
×
UNCOV
473
    if (!simplify) {
×
474
        std::set<BasicBlock> res;
×
UNCOV
475
        for (const Label& label : cfg.labels()) {
×
476
            if (label != cfg.entry_label() && label != cfg.exit_label()) {
×
477
                res.insert(BasicBlock{label});
×
478
            }
479
        }
480
        return res;
×
481
    }
×
482

483
    std::set<BasicBlock> res;
×
UNCOV
484
    std::set<Label> worklist;
×
UNCOV
485
    for (const Label& label : cfg.labels()) {
×
UNCOV
486
        worklist.insert(label);
×
487
    }
UNCOV
488
    std::set<Label> seen;
×
489
    while (!worklist.empty()) {
×
UNCOV
490
        Label label = *worklist.begin();
×
491
        worklist.erase(label);
×
UNCOV
492
        if (seen.contains(label)) {
×
493
            continue;
×
494
        }
UNCOV
495
        seen.insert(label);
×
496

497
        if (cfg.in_degree(label) == 1 && cfg.num_siblings(label) == 1) {
×
498
            continue;
×
499
        }
500
        BasicBlock bb{label};
×
501
        while (cfg.out_degree(label) == 1) {
×
UNCOV
502
            const Label& next_label = cfg.get_child(bb.last_label());
×
503

UNCOV
504
            if (seen.contains(next_label) || next_label == cfg.exit_label() || cfg.in_degree(next_label) != 1) {
×
505
                break;
506
            }
507

508
            if (bb.first_label() == cfg.entry_label()) {
×
509
                // Entry instruction is Undefined. We want to start with 0
510
                bb.m_ts.clear();
×
511
            }
UNCOV
512
            bb.m_ts.push_back(next_label);
×
513

UNCOV
514
            worklist.erase(next_label);
×
UNCOV
515
            seen.insert(next_label);
×
516

UNCOV
517
            label = next_label;
×
518
        }
×
UNCOV
519
        res.emplace(std::move(bb));
×
520
    }
×
521
    return res;
×
522
}
×
523

524
/// Get the type of given Instruction.
525
/// Most of these type names are also statistics header labels.
526
static std::string instype(Instruction ins) {
×
527
    if (const auto pcall = std::get_if<Call>(&ins)) {
×
528
        if (pcall->is_map_lookup) {
×
529
            return "call_1";
×
530
        }
UNCOV
531
        if (pcall->pairs.empty()) {
×
532
            if (std::ranges::all_of(pcall->singles,
×
533
                                    [](const ArgSingle kr) { return kr.kind == ArgSingle::Kind::ANYTHING; })) {
UNCOV
534
                return "call_nomem";
×
535
            }
536
        }
UNCOV
537
        return "call_mem";
×
538
    } else if (std::holds_alternative<Callx>(ins)) {
UNCOV
539
        return "callx";
×
540
    } else if (std::holds_alternative<CallBtf>(ins)) {
UNCOV
541
        return "call_btf";
×
542
    } else if (const auto pimm = std::get_if<Mem>(&ins)) {
×
UNCOV
543
        return pimm->is_load ? "load" : "store";
×
544
    } else if (std::holds_alternative<Atomic>(ins)) {
UNCOV
545
        return "load_store";
×
546
    } else if (std::holds_alternative<Packet>(ins)) {
UNCOV
547
        return "packet_access";
×
UNCOV
548
    } else if (const auto pins = std::get_if<Bin>(&ins)) {
×
UNCOV
549
        switch (pins->op) {
×
550
        case Bin::Op::MOV:
×
551
        case Bin::Op::MOVSX8:
552
        case Bin::Op::MOVSX16:
UNCOV
553
        case Bin::Op::MOVSX32: return "assign";
×
UNCOV
554
        default: return "arith";
×
555
        }
556
    } else if (std::holds_alternative<Un>(ins)) {
UNCOV
557
        return "arith";
×
558
    } else if (std::holds_alternative<LoadMapFd>(ins)) {
559
        return "assign";
×
560
    } else if (std::holds_alternative<LoadMapAddress>(ins)) {
561
        return "assign";
×
562
    } else if (std::holds_alternative<LoadPseudo>(ins)) {
563
        return "assign";
×
564
    } else if (std::holds_alternative<Assume>(ins)) {
565
        return "assume";
×
566
    } else {
567
        return "other";
×
568
    }
569
}
570

571
std::vector<std::string> stats_headers() {
×
572
    return {
573
        "instructions", "joins",      "other",      "jumps",         "assign",  "arith",
574
        "load",         "store",      "load_store", "packet_access", "call_1",  "call_mem",
575
        "call_nomem",   "reallocate", "map_in_map", "arith64",       "arith32",
576
    };
×
577
}
578

579
std::map<std::string, int> collect_stats(const Program& prog) {
×
580
    std::map<std::string, int> res;
×
581
    for (const auto& h : stats_headers()) {
×
UNCOV
582
        res[h] = 0;
×
583
    }
×
584
    for (const auto& label : prog.labels()) {
×
UNCOV
585
        res["instructions"]++;
×
586
        const auto& cmd = prog.instruction_at(label);
×
587
        if (const auto pins = std::get_if<LoadMapFd>(&cmd)) {
×
588
            if (pins->mapfd == -1) {
×
UNCOV
589
                res["map_in_map"] = 1;
×
590
            }
591
        }
UNCOV
592
        if (const auto pins = std::get_if<Call>(&cmd)) {
×
UNCOV
593
            if (pins->reallocate_packet) {
×
UNCOV
594
                res["reallocate"] = 1;
×
595
            }
596
        }
UNCOV
597
        if (const auto pins = std::get_if<Bin>(&cmd)) {
×
UNCOV
598
            res[pins->is64 ? "arith64" : "arith32"]++;
×
599
        }
UNCOV
600
        res[instype(cmd)]++;
×
UNCOV
601
        if (prog.cfg().in_degree(label) > 1) {
×
UNCOV
602
            res["joins"]++;
×
603
        }
UNCOV
604
        if (prog.cfg().out_degree(label) > 1) {
×
UNCOV
605
            res["jumps"]++;
×
606
        }
607
    }
UNCOV
608
    return res;
×
UNCOV
609
}
×
610

611
Cfg cfg_from_adjacency_list(const std::map<Label, std::vector<Label>>& AdjList) {
6✔
612
    CfgBuilder builder;
6✔
613
    for (const auto& label : std::views::keys(AdjList)) {
46✔
614
        if (label == Label::entry || label == Label::exit) {
40✔
615
            continue;
6✔
616
        }
617
        builder.insert(label, Undefined{});
51✔
618
    }
619
    for (const auto& [label, children] : AdjList) {
46✔
620
        for (const auto& child : children) {
94✔
621
            builder.add_child(label, child);
54✔
622
        }
623
    }
624
    return builder.prog.cfg();
12✔
625
}
6✔
626
} // 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