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

Alan-Jowett / ebpf-verifier / 27778108035

07 Jun 2026 06:51PM UTC coverage: 86.386% (-2.5%) from 88.93%
27778108035

push

github

elazarg
Release v0.2.5

Bump project version to 0.2.5 and add a CHANGELOG entry covering ELF loader hardening, numeric-domain soundness fixes, and the writable helper output initialization documentation update since v0.2.4. Also updates the using_installed_package example version requirement.

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

9125 of 10563 relevant lines covered (86.39%)

6334294.72 hits per line

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

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

12
#include "cfg/cfg.hpp"
13
#include "cfg/wto.hpp"
14
#include "config.hpp"
15
#include "ir/call_resolver.hpp"
16
#include "ir/program.hpp"
17
#include "ir/syntax.hpp"
18
#include "platform.hpp"
19

20
using std::optional;
21
using std::set;
22
using std::string;
23
using std::to_string;
24
using std::vector;
25

26
namespace prevail {
27
struct CallbackMetadata {
2,067✔
28
    std::set<int32_t> target_labels;
29
    std::set<int32_t> targets_with_exit;
30
};
31

32
struct CfgBuilder final {
8✔
33
    Program prog;
34

35
    // TODO: ins should be inserted elsewhere
36
    void insert_after(const Label& prev_label, const Label& new_label, const Instruction& ins) {
44✔
37
        if (prev_label == new_label) {
44✔
38
            CRAB_ERROR("Cannot insert after the same label ", to_string(new_label));
×
39
        }
40
        std::set<Label> prev_children;
44✔
41
        std::swap(prev_children, prog.m_cfg.get_node(prev_label).children);
44✔
42

43
        for (const Label& next_label : prev_children) {
96✔
44
            prog.m_cfg.get_node(next_label).parents.erase(prev_label);
52✔
45
        }
46

47
        insert(new_label, ins);
44✔
48
        for (const Label& next_label : prev_children) {
96✔
49
            add_child(prev_label, new_label);
52✔
50
            add_child(new_label, next_label);
52✔
51
        }
52
    }
44✔
53

54
    // TODO: ins should be inserted elsewhere
55
    void insert(const Label& _label, const Instruction& ins) {
1,317,932✔
56
        if (const auto it = prog.m_cfg.neighbours.find(_label); it != prog.m_cfg.neighbours.end()) {
1,317,932✔
57
            CRAB_ERROR("Label ", to_string(_label), " already exists");
×
58
        }
59
        prog.m_cfg.neighbours.emplace(_label, Cfg::Adjacent{});
1,317,932✔
60
        prog.m_instructions.emplace(_label, ins);
1,317,932✔
61
    }
1,317,932✔
62

63
    // TODO: ins should be inserted elsewhere
64
    Label insert_jump(const Label& from, const Label& to, const Instruction& ins) {
198,744✔
65
        const Label jump_label = Label::make_jump(from, to);
198,744✔
66
        if (prog.m_cfg.contains(jump_label)) {
198,744✔
67
            CRAB_ERROR("Jump label ", to_string(jump_label), " already exists");
×
68
        }
69
        insert(jump_label, ins);
198,744✔
70
        add_child(from, jump_label);
198,744✔
71
        add_child(jump_label, to);
198,744✔
72
        return jump_label;
198,744✔
73
    }
×
74

75
    void add_child(const Label& a, const Label& b) {
1,425,854✔
76
        assert(b != Label::entry);
1,425,854✔
77
        assert(a != Label::exit);
1,425,854✔
78
        prog.m_cfg.neighbours.at(a).children.insert(b);
1,425,854✔
79
        prog.m_cfg.neighbours.at(b).parents.insert(a);
1,425,854✔
80
    }
1,425,854✔
81

82
    void remove_child(const Label& a, const Label& b) {
710✔
83
        prog.m_cfg.get_node(a).children.erase(b);
710✔
84
        prog.m_cfg.get_node(b).parents.erase(a);
710✔
85
    }
710✔
86

87
    void set_assertions(const Label& label, const std::vector<Assertion>& assertions) {
1,325,946✔
88
        if (!prog.m_cfg.contains(label)) {
1,325,946✔
89
            CRAB_ERROR("Label ", to_string(label), " not found in the CFG: ");
×
90
        }
91
        prog.m_assertions.insert_or_assign(label, assertions);
1,325,946✔
92
    }
1,325,946✔
93

94
    void set_callback_metadata(CallbackMetadata md) {
4,134✔
95
        prog.m_callback_target_labels = std::move(md.target_labels);
4,134✔
96
        prog.m_callback_targets_with_exit = std::move(md.targets_with_exit);
4,134✔
97
    }
4,134✔
98
};
99

100
/// Get the inverse of a given comparison operation.
101
static Condition::Op reverse(const Condition::Op op) {
99,372✔
102
    switch (op) {
99,372✔
103
    case Condition::Op::EQ: return Condition::Op::NE;
22,660✔
104
    case Condition::Op::NE: return Condition::Op::EQ;
13,856✔
105

106
    case Condition::Op::GE: return Condition::Op::LT;
366✔
107
    case Condition::Op::LT: return Condition::Op::GE;
1,315✔
108

109
    case Condition::Op::SGE: return Condition::Op::SLT;
56✔
110
    case Condition::Op::SLT: return Condition::Op::SGE;
3,210✔
111

112
    case Condition::Op::LE: return Condition::Op::GT;
141✔
113
    case Condition::Op::GT: return Condition::Op::LE;
4,696✔
114

115
    case Condition::Op::SLE: return Condition::Op::SGT;
22✔
116
    case Condition::Op::SGT: return Condition::Op::SLE;
3,345✔
117

118
    case Condition::Op::SET: return Condition::Op::NSET;
17✔
119
    case Condition::Op::NSET: return Condition::Op::SET;
2✔
120
    }
121
    std::unreachable();
122
}
123

124
/// Get the inverse of a given comparison condition.
125
static Condition reverse(const Condition& cond) {
99,372✔
126
    return {.op = reverse(cond.op), .left = cond.left, .right = cond.right, .is64 = cond.is64};
99,372✔
127
}
128

129
static bool has_fall(const Instruction& ins) {
971,614✔
130
    if (std::holds_alternative<Exit>(ins)) {
971,614✔
131
        return false;
3,012✔
132
    }
133

134
    if (const auto pins = std::get_if<Jmp>(&ins)) {
965,590✔
135
        if (!pins->cond) {
502✔
136
            return false;
251✔
137
        }
138
    }
139

140
    return true;
482,544✔
141
}
142

143
enum class RejectKind {
144
    NotImplemented,
145
    Capability,
146
};
147

148
struct RejectionReason {
46✔
149
    RejectKind kind{};
150
    std::string detail;
151
};
152

153
static bool supports(const ebpf_platform_t& platform, const bpf_conformance_groups_t group) {
1,656,284✔
154
    return platform.supports_group(group);
1,651,710✔
155
}
156

157
static bool un_requires_base64(const Un& un) {
11,412✔
158
    switch (un.op) {
11,412✔
159
    case Un::Op::BE64:
49✔
160
    case Un::Op::LE64:
161
    case Un::Op::SWAP64: return true;
49✔
162
    default: return false;
7,558✔
163
    }
164
}
165

166
static std::optional<ResolvedCall> resolve_kfunc_call(const CallBtf& call_btf, const ProgramInfo& info,
64✔
167
                                                      std::string* why_not) {
168
    if (!info.platform || !info.platform->resolve_kfunc_call) {
64✔
169
        if (why_not) {
×
170
            *why_not = "kfunc resolution is unavailable on this platform";
×
171
        }
172
        return std::nullopt;
×
173
    }
174
    return info.platform->resolve_kfunc_call(call_btf.btf_id, call_btf.module, info.type, why_not);
64✔
175
}
176

177
// CallBtf in the instruction stream is replaced by a key-only Call{func,
178
// kind=kfunc}; the ResolvedCall is reproduced on demand via resolve(call, info).
179
using ResolvedKfuncCalls = std::map<Label, Call>;
180

181
[[nodiscard]]
182
static std::optional<RejectionReason> check_instruction_feature_support(const Instruction& ins,
1,645,459✔
183
                                                                        const ProgramInfo& info) {
184
    const ebpf_platform_t& platform = *info.platform;
1,645,459✔
185
    auto reject_not_implemented = [](std::string detail) {
550,433✔
186
        return RejectionReason{.kind = RejectKind::NotImplemented, .detail = std::move(detail)};
×
187
    };
188
    auto reject_capability = [](std::string detail) {
550,479✔
189
        return RejectionReason{.kind = RejectKind::Capability, .detail = std::move(detail)};
46✔
190
    };
191

192
    if (const auto p = std::get_if<Call>(&ins)) {
1,645,459✔
193
        const auto resolved = resolve(*p, info);
89,810✔
194
        if (!resolved.is_supported) {
89,810✔
195
            return reject_capability(resolved.unsupported_reason);
9✔
196
        }
197
    }
89,810✔
198
    if (std::holds_alternative<Callx>(ins) && !supports(platform, bpf_conformance_groups_t::callx)) {
1,645,453✔
199
        return reject_capability("requires conformance group callx");
×
200
    }
201
    if ((std::holds_alternative<Call>(ins) || std::holds_alternative<CallLocal>(ins) ||
3,715,020✔
202
         std::holds_alternative<Callx>(ins) || std::holds_alternative<CallBtf>(ins) ||
3,622,965✔
203
         std::holds_alternative<Exit>(ins)) &&
4,294,664✔
204
        !supports(platform, bpf_conformance_groups_t::base32)) {
97,206✔
205
        return reject_capability("requires conformance group base32");
×
206
    }
207
    if (const auto p = std::get_if<Bin>(&ins)) {
1,934,708✔
208
        if (!supports(platform, p->is64 ? bpf_conformance_groups_t::base64 : bpf_conformance_groups_t::base32)) {
955,020✔
209
            return reject_capability(p->is64 ? "requires conformance group base64"
×
210
                                             : "requires conformance group base32");
×
211
        }
212
        if ((p->op == Bin::Op::MUL || p->op == Bin::Op::UDIV || p->op == Bin::Op::UMOD || p->op == Bin::Op::SDIV ||
860,626✔
213
             p->op == Bin::Op::SMOD) &&
1,441,245✔
214
            !supports(platform, p->is64 ? bpf_conformance_groups_t::divmul64 : bpf_conformance_groups_t::divmul32)) {
13,618✔
215
            return reject_capability(p->is64 ? "requires conformance group divmul64"
×
216
                                             : "requires conformance group divmul32");
×
217
        }
218
    }
219
    if (const auto p = std::get_if<Un>(&ins)) {
1,645,452✔
220
        const bool need_base64 = p->is64 || un_requires_base64(*p);
15,624✔
221
        if (!supports(platform, need_base64 ? bpf_conformance_groups_t::base64 : bpf_conformance_groups_t::base32)) {
11,717✔
222
            return reject_capability(need_base64 ? "requires conformance group base64"
6✔
223
                                                 : "requires conformance group base32");
2✔
224
        }
225
    }
226
    if (const auto p = std::get_if<Jmp>(&ins)) {
1,645,451✔
227
        if (!supports(platform,
283,710✔
228
                      p->cond ? (p->cond->is64 ? bpf_conformance_groups_t::base64 : bpf_conformance_groups_t::base32)
283,710✔
229
                              : bpf_conformance_groups_t::base32)) {
230
            return reject_capability((p->cond && p->cond->is64) ? "requires conformance group base64"
×
231
                                                                : "requires conformance group base32");
×
232
        }
233
    }
234
    if (const auto p = std::get_if<LoadPseudo>(&ins)) {
1,645,465✔
235
        if (!supports(platform, bpf_conformance_groups_t::base64)) {
40✔
236
            return reject_capability("requires conformance group base64");
×
237
        }
238
        switch (p->addr.kind) {
40✔
239
        case PseudoAddress::Kind::VARIABLE_ADDR:
26✔
240
        case PseudoAddress::Kind::CODE_ADDR:
241
        case PseudoAddress::Kind::MAP_BY_IDX:
242
        case PseudoAddress::Kind::MAP_VALUE_BY_IDX: break; // Resolved during CFG construction.
26✔
243
        default: return reject_not_implemented("lddw unknown pseudo");
×
244
        }
245
    }
246
    if ((std::holds_alternative<LoadMapFd>(ins) || std::holds_alternative<LoadMapAddress>(ins)) &&
1,686,268✔
247
        !supports(platform, bpf_conformance_groups_t::base64)) {
61,582✔
248
        return reject_capability("requires conformance group base64");
×
249
    }
250
    if (const auto p = std::get_if<Mem>(&ins)) {
1,645,451✔
251
        if (!supports(platform,
423,003✔
252
                      (p->access.width == 8) ? bpf_conformance_groups_t::base64 : bpf_conformance_groups_t::base32)) {
423,003✔
253
            return reject_capability((p->access.width == 8) ? "requires conformance group base64"
×
254
                                                            : "requires conformance group base32");
×
255
        }
256
        if (p->is_signed && !supports(platform, bpf_conformance_groups_t::base64)) {
423,003✔
257
            return reject_capability("requires conformance group base64");
×
258
        }
259
    }
260
    if (std::holds_alternative<Packet>(ins) && !supports(platform, bpf_conformance_groups_t::packet)) {
1,645,451✔
261
        return reject_capability("requires conformance group packet");
76✔
262
    }
263
    if (const auto p = std::get_if<Atomic>(&ins)) {
1,645,413✔
264
        const auto group =
2,612✔
265
            (p->access.width == 8) ? bpf_conformance_groups_t::atomic64 : bpf_conformance_groups_t::atomic32;
1,959✔
266
        if (!supports(platform, group)) {
1,959✔
267
            return reject_capability((group == bpf_conformance_groups_t::atomic64)
×
268
                                         ? "requires conformance group atomic64"
269
                                         : "requires conformance group atomic32");
×
270
        }
271
    }
272
    return {};
1,645,413✔
273
}
274

275
// Pass: ValidateInstructionSupport
276
// Throwing wrapper around the value-returning check_instruction_feature_support.
277
// Single named place that converts a rejection into the throw shape; the
278
// value-returning checker is preserved for sites that want to query without
279
// throwing (see the assert at pass_populate_nodes).
280
[[noreturn]]
281
static void throw_unsupported(const RejectionReason& reason, const Label& label) {
46✔
282
    const std::string prefix = (reason.kind == RejectKind::NotImplemented) ? "not implemented: " : "rejected: ";
69✔
283
    throw InvalidControlFlow{prefix + reason.detail + " (at " + to_string(label) + ")"};
92✔
284
}
46✔
285

286
static void enforce_instruction_feature_support(const Instruction& ins, const Label& label, const ProgramInfo& info) {
1,100,866✔
287
    if (const auto reason = check_instruction_feature_support(ins, info)) {
1,100,866✔
288
        throw_unsupported(*reason, label);
46✔
289
    }
1,100,866✔
290
}
1,100,820✔
291

292
// Reads    : instruction sequence, platform conformance groups.
293
// Writes   : nothing.
294
// Throws   : InvalidControlFlow on any instruction the platform cannot run.
295
// Invariant: must run before CFG construction; pass_populate_nodes assumes
296
//            every instruction has been vetted here.
297
static void pass_validate_instruction_support(const InstructionSeq& insts, const ProgramInfo& info) {
4,226✔
298
    for (const auto& [label, inst, _] : insts) {
1,105,046✔
299
        enforce_instruction_feature_support(inst, label, info);
1,100,866✔
300
    }
301
}
4,180✔
302

303
// Pass: ResolveKfuncCalls
304
// Reads    : instruction sequence, platform kfunc resolver.
305
// Writes   : returns a Label -> Call map for every CallBtf in the sequence.
306
// Throws   : InvalidControlFlow if any CallBtf cannot be resolved for this platform.
307
// Invariant: pass_populate_nodes consults this map to replace CallBtf with the resolved Call.
308
static ResolvedKfuncCalls pass_resolve_kfunc_calls(const InstructionSeq& insts, const ProgramInfo& info) {
4,180✔
309
    ResolvedKfuncCalls resolved;
4,180✔
310
    for (const auto& [label, inst, _] : insts) {
1,093,594✔
311
        const auto* call_btf = std::get_if<CallBtf>(&inst);
1,089,440✔
312
        if (!call_btf) {
1,089,440✔
313
            continue;
1,089,376✔
314
        }
315
        std::string why_not;
64✔
316
        const auto r = resolve_kfunc_call(*call_btf, info, &why_not);
64✔
317
        if (!r) {
64✔
318
            throw InvalidControlFlow{"not implemented: " + why_not + " (at " + to_string(label) + ")"};
52✔
319
        }
320
        // Build the lowered Call key directly from the source CallBtf rather
321
        // than trusting any field the platform returned. The lowered IR's
322
        // identity is the pre-resolution (btf_id, module) pair plus the fixed
323
        // CallKind::kfunc tag; nothing else can soundly change it. Constructing
324
        // the key here makes that invariant audit-visible and removes a class
325
        // of bugs where a misbehaving resolver mis-keys the lowered IR — in
326
        // particular, two kfuncs sharing a BTF id across modules must remain
327
        // distinguishable.
328
        const Call lowered{
38✔
329
            .func = call_btf->btf_id,
38✔
330
            .kind = CallKind::kfunc,
331
            .module = call_btf->module,
38✔
332
        };
38✔
333
        resolved.insert_or_assign(label, lowered);
38✔
334
    }
90✔
335
    return resolved;
4,154✔
336
}
26✔
337

338
/// Update a control-flow graph to inline function macros.
339
static void add_cfg_nodes(CfgBuilder& builder, const Label& caller_label, const Label& entry_label,
718✔
340
                          const int max_call_stack_frames) {
341
    const string caller_label_str = to_string(caller_label);
718✔
342
    const long stack_frame_depth = std::ranges::count(caller_label_str, STACK_FRAME_DELIMITER) + 2;
718✔
343
    if (stack_frame_depth > max_call_stack_frames) {
718✔
344
        throw InvalidControlFlow{"too many call stack frames"};
10✔
345
    }
346

347
    bool first = true;
714✔
348

349
    // Get the label of the node to go to on returning from the macro.
350
    Label exit_to_label = builder.prog.cfg().get_child(caller_label);
714✔
351

352
    // Construct the variable prefix to use for the new stack frame
353
    // and store a copy in the CallLocal instruction since the instruction-specific
354
    // labels may only exist until the CFG is simplified.
355
    const std::string stack_frame_prefix = to_string(caller_label);
714✔
356
    if (const auto pcall = std::get_if<CallLocal>(&builder.prog.instruction_at(caller_label))) {
1,071✔
357
        pcall->stack_frame_prefix = stack_frame_prefix;
714✔
358
    }
359

360
    // Walk the transitive closure of CFG nodes starting at entry_label and ending at
361
    // any exit instruction.
362
    std::set macro_labels{entry_label};
1,802✔
363
    std::set seen_labels{entry_label};
1,802✔
364
    while (!macro_labels.empty()) {
30,622✔
365
        Label macro_label = *macro_labels.begin();
29,912✔
366
        macro_labels.erase(macro_label);
29,912✔
367

368
        if (stack_frame_prefix == macro_label.stack_frame_prefix) {
29,912✔
369
            throw InvalidControlFlow{stack_frame_prefix + ": illegal recursion"};
6✔
370
        }
371

372
        // Clone the macro block into a new block with the new stack frame prefix.
373
        const Label label{macro_label.from, macro_label.to, stack_frame_prefix};
44,864✔
374
        auto inst = builder.prog.instruction_at(macro_label);
29,908✔
375
        if (const auto pexit = std::get_if<Exit>(&inst)) {
30,266✔
376
            pexit->stack_frame_prefix = label.stack_frame_prefix;
716✔
377
        }
378
        builder.insert(label, inst);
29,908✔
379

380
        if (first) {
29,908✔
381
            // Add an edge from the caller to the new block.
382
            first = false;
714✔
383
            builder.add_child(caller_label, label);
714✔
384
        }
385

386
        // Add an edge from any other predecessors.
387
        for (const auto& prev_macro_nodes = builder.prog.cfg().parents_of(macro_label);
44,862✔
388
             const auto& prev_macro_label : prev_macro_nodes) {
77,668✔
389
            const Label prev_label(prev_macro_label.from, prev_macro_label.to, to_string(caller_label));
32,806✔
390
            if (const auto& labels = builder.prog.cfg().labels();
32,806✔
391
                std::ranges::find(labels, prev_label) != labels.end()) {
32,806✔
392
                builder.add_child(prev_label, label);
32,800✔
393
            }
394
        }
32,806✔
395

396
        // Walk all successor nodes.
397
        for (const auto& next_macro_nodes = builder.prog.cfg().children_of(macro_label);
61,621✔
398
             const auto& next_macro_label : next_macro_nodes) {
78,380✔
399
            if (next_macro_label == builder.prog.cfg().exit_label()) {
50,277✔
400
                // This is an exit transition, so add edge to the block to execute
401
                // upon returning from the macro.
402
                builder.add_child(label, exit_to_label);
714✔
403
            } else if (!seen_labels.contains(next_macro_label)) {
32,804✔
404
                // Push any other unprocessed successor label onto the list to be processed.
405
                if (!macro_labels.contains(next_macro_label)) {
29,198✔
406
                    macro_labels.insert(next_macro_label);
29,198✔
407
                }
408
                seen_labels.insert(next_macro_label);
29,198✔
409
            }
410
        }
411
    }
59,820✔
412

413
    // Remove the original edge from the caller node to its successor,
414
    // since processing now goes through the function macro instead.
415
    builder.remove_child(caller_label, exit_to_label);
710✔
416

417
    // Finally, recurse to replace any nested function macros.
418
    for (const auto& macro_label : seen_labels) {
30,546✔
419
        const Label label{macro_label.from, macro_label.to, caller_label_str};
44,816✔
420
        if (const auto pins = std::get_if<CallLocal>(&builder.prog.instruction_at(label))) {
44,784✔
421
            add_cfg_nodes(builder, label, pins->target, max_call_stack_frames);
132✔
422
        }
423
    }
29,866✔
424
}
2,603✔
425

426
struct Imm64Parts {
427
    int32_t lo{};
428
    int32_t hi{};
429
};
430

431
static uint64_t merge_imm32_to_u64(const Imm64Parts parts) {
4✔
432
    return static_cast<uint64_t>(static_cast<uint32_t>(parts.lo)) |
4✔
433
           (static_cast<uint64_t>(static_cast<uint32_t>(parts.hi)) << 32);
4✔
434
}
435

436
/// Lower a single LoadPseudo to a concrete instruction.
437
/// VARIABLE_ADDR is lowered to an immediate scalar MOV; MAP_BY_IDX / MAP_VALUE_BY_IDX are
438
/// rewritten against the current map descriptor table. CODE_ADDR is kept as LoadPseudo by
439
/// pass_lower_pseudo_loads so the abstract transformer can type it as T_FUNC; this helper
440
/// is never called for CODE_ADDR.
441
static Instruction lower_pseudo_load(const LoadPseudo& pseudo, const ProgramInfo& info) {
12✔
442
    if (pseudo.addr.kind == PseudoAddress::Kind::VARIABLE_ADDR) {
12✔
443
        return Bin{
10✔
444
            .op = Bin::Op::MOV,
445
            .dst = pseudo.dst,
446
            .v = Imm{merge_imm32_to_u64({.lo = pseudo.addr.imm, .hi = pseudo.addr.next_imm})},
6✔
447
            .is64 = true,
448
            .lddw = true,
449
        };
4✔
450
    }
451

452
    const auto& descriptors = info.map_descriptors;
8✔
453
    if (pseudo.addr.imm < 0 || static_cast<size_t>(pseudo.addr.imm) >= descriptors.size()) {
8✔
454
        throw InvalidControlFlow{"invalid map index " + std::to_string(pseudo.addr.imm) + " (have " +
10✔
455
                                 std::to_string(descriptors.size()) + " maps)"};
12✔
456
    }
457
    const auto map_idx = static_cast<size_t>(pseudo.addr.imm);
4✔
458
    const int mapfd = descriptors.at(map_idx).original_fd;
4✔
459
    switch (pseudo.addr.kind) {
4✔
460
    case PseudoAddress::Kind::MAP_BY_IDX: return LoadMapFd{.dst = pseudo.dst, .mapfd = mapfd};
2✔
461
    case PseudoAddress::Kind::MAP_VALUE_BY_IDX:
2✔
462
        return LoadMapAddress{.dst = pseudo.dst, .mapfd = mapfd, .offset = pseudo.addr.next_imm};
2✔
463
    default: CRAB_ERROR("Invalid address kind: ", static_cast<int>(pseudo.addr.kind));
×
464
    }
465
}
466

467
using LoweredPseudoLoads = std::map<Label, Instruction>;
468

469
// Pass: LowerPseudoLoads
470
// Reads    : instruction sequence, program info (map_descriptors).
471
// Writes   : returns a Label -> Instruction map with the concrete replacement for every
472
//            LoadPseudo that is lowered. CODE_ADDR LoadPseudo instructions are intentionally
473
//            excluded so they remain observable to the abstract transformer (which types
474
//            them as T_FUNC); every other kind is replaced.
475
// Throws   : InvalidControlFlow if a MAP_BY_IDX / MAP_VALUE_BY_IDX references an out-of-range
476
//            map descriptor.
477
// Invariant: pass_populate_nodes consults this map to substitute LoadPseudo with its lowered
478
//            form while inserting CFG nodes.
479
static LoweredPseudoLoads pass_lower_pseudo_loads(const InstructionSeq& insts, const ProgramInfo& info) {
4,154✔
480
    LoweredPseudoLoads lowered;
4,154✔
481
    for (const auto& [label, inst, _] : insts) {
1,093,340✔
482
        const auto* pseudo = std::get_if<LoadPseudo>(&inst);
1,089,190✔
483
        if (!pseudo || pseudo->addr.kind == PseudoAddress::Kind::CODE_ADDR) {
544,609✔
484
            continue;
1,089,178✔
485
        }
486
        lowered.insert_or_assign(label, lower_pseudo_load(*pseudo, info));
16✔
487
    }
488
    return lowered;
4,150✔
489
}
4✔
490

491
// Pass: BuildInitialCfg -- populate_nodes step.
492
// Reads    : instruction sequence, program info, resolved kfunc map, lowered pseudo-load map.
493
// Writes   : inserts one CFG node per live instruction into builder (labels + instructions).
494
//            CallBtf is replaced with the resolved Call; non-CODE_ADDR LoadPseudo is replaced
495
//            with its lowered form; every other instruction is inserted verbatim.
496
// Throws   : InvalidControlFlow if either substitution map is inconsistent with the sequence
497
//            (internal error; indicates a missing prior pass).
498
// Invariant: pass_validate_instruction_support, pass_resolve_kfunc_calls and
499
//            pass_lower_pseudo_loads have been applied on the same instruction sequence.
500
static void pass_populate_nodes(CfgBuilder& builder, const InstructionSeq& insts, const ProgramInfo& info,
4,150✔
501
                                const ResolvedKfuncCalls& resolved_kfunc_calls,
502
                                const LoweredPseudoLoads& lowered_pseudo_loads) {
503
    for (const auto& [label, inst, _] : insts) {
1,093,336✔
504
        assert(!check_instruction_feature_support(inst, info).has_value() &&
1,089,186✔
505
               "instruction support must be validated before CFG construction");
506
        if (std::holds_alternative<Undefined>(inst)) {
1,089,186✔
507
            continue;
×
508
        }
509
        if (std::holds_alternative<CallBtf>(inst)) {
1,089,186✔
510
            const auto it = resolved_kfunc_calls.find(label);
38✔
511
            if (it == resolved_kfunc_calls.end()) {
38✔
512
                CRAB_ERROR("missing validated kfunc resolution at ", to_string(label));
×
513
            }
514
            builder.insert(label, it->second);
38✔
515
            continue;
38✔
516
        }
38✔
517
        if (const auto it = lowered_pseudo_loads.find(label); it != lowered_pseudo_loads.end()) {
1,089,148✔
518
            builder.insert(label, it->second);
8✔
519
            continue;
8✔
520
        }
521
        builder.insert(label, inst);
1,089,140✔
522
    }
523
}
4,150✔
524

525
// Pass: BuildInitialCfg -- connect_edges step (also performs InsertAssumeEdges).
526
// Reads    : instruction sequence, must_have_exit flag.
527
// Writes   : CFG edges from entry, and for every populated node to its successors.
528
//            Conditional Jmp instructions are materialised as two synthetic Assume
529
//            jump-labels (insert_jump) carrying the positive and negated conditions.
530
// Throws   : InvalidControlFlow on empty sequence, fallthrough past the final instruction,
531
//            or a jump whose target label is not in the CFG.
532
// Invariant: pass_populate_nodes has been applied (all nodes exist before edges are added).
533
static void pass_connect_edges(CfgBuilder& builder, const InstructionSeq& insts, const bool must_have_exit) {
4,150✔
534
    if (insts.empty()) {
4,150✔
535
        throw InvalidControlFlow{"empty instruction sequence"};
5✔
536
    }
537
    // Ordering check: pass_populate_nodes must run first so that every non-Undefined label
538
    // referenced below (the entry's target, jump targets, fallthrough labels) already exists.
539
    assert(std::holds_alternative<Undefined>(std::get<1>(insts[0])) ||
4,148✔
540
           builder.prog.cfg().contains(std::get<0>(insts[0])));
541
    builder.add_child(builder.prog.cfg().entry_label(), std::get<0>(insts[0]));
4,148✔
542

543
    for (size_t i = 0; i < insts.size(); i++) {
1,093,328✔
544
        const auto& [label, inst, _0] = insts[i];
1,089,184✔
545

546
        if (std::holds_alternative<Undefined>(inst)) {
1,089,184✔
547
            continue;
172✔
548
        }
549
        Label fallthrough{builder.prog.cfg().exit_label()};
1,089,184✔
550
        if (i + 1 < insts.size()) {
1,089,184✔
551
            fallthrough = std::get<0>(insts[i + 1]);
1,085,038✔
552
        } else {
553
            if (has_fall(inst) && must_have_exit) {
5,049✔
554
                throw InvalidControlFlow{"fallthrough in last instruction"};
5✔
555
            }
556
        }
557
        if (const auto jmp = std::get_if<Jmp>(&inst)) {
1,089,182✔
558
            if (const auto cond = jmp->cond) {
121,714✔
559
                Label target_label = jmp->target;
99,546✔
560
                if (target_label == fallthrough) {
99,546✔
561
                    builder.add_child(label, fallthrough);
172✔
562
                    continue;
172✔
563
                }
564
                if (!builder.prog.cfg().contains(target_label)) {
99,374✔
565
                    throw InvalidControlFlow{"jump to undefined label " + to_string(target_label)};
3✔
566
                }
567
                builder.insert_jump(label, target_label, Assume{.cond = *cond, .is_implicit = true});
149,058✔
568
                builder.insert_jump(label, fallthrough, Assume{.cond = reverse(*cond), .is_implicit = true});
149,059✔
569
            } else {
99,546✔
570
                builder.add_child(label, jmp->target);
22,168✔
571
            }
572
        } else {
573
            if (has_fall(inst)) {
967,468✔
574
                builder.add_child(label, fallthrough);
963,784✔
575
            }
576
        }
577
        if (std::holds_alternative<Exit>(inst)) {
1,089,008✔
578
            builder.add_child(label, builder.prog.cfg().exit_label());
5,528✔
579
        }
580
    }
1,089,182✔
581
}
4,144✔
582

583
// Pass: InlineLocalCalls
584
// Reads    : instruction sequence, max_call_stack_frames bound.
585
// Writes   : for every CallLocal in the sequence, clones the callee region into the CFG
586
//            under a unique stack-frame prefix. Recurses into nested calls.
587
// Throws   : InvalidControlFlow on illegal recursion or exceeding the call-stack frame bound.
588
// Invariant: pass_connect_edges has been applied -- inlining walks existing parents/children.
589
//            Restricted to callees that are reachable after edge connection, which is why
590
//            this runs as a separate second pass rather than during population.
591
static void pass_inline_local_calls(CfgBuilder& builder, const InstructionSeq& insts, const int max_call_stack_frames) {
4,144✔
592
    // Ordering check: pass_connect_edges must have run. When insts is non-empty, its first
593
    // label has been wired as a child of Label::entry, so entry has at least one successor.
594
    assert(insts.empty() || !builder.prog.cfg().children_of(Label::entry).empty());
4,144✔
595
    for (const auto& [label, inst, _] : insts) {
1,093,268✔
596
        if (const auto pins = std::get_if<CallLocal>(&inst)) {
1,089,421✔
597
            add_cfg_nodes(builder, label, pins->target, max_call_stack_frames);
586✔
598
        }
599
    }
600
}
4,136✔
601

602
static bool is_tail_call_helper(const Call& call, const ebpf_platform_t& platform,
59,824✔
603
                                const EbpfProgramType& program_type) {
604
    if (call.kind != CallKind::helper) {
59,824✔
605
        return false;
1,248✔
606
    }
607
    if (!platform.is_helper_usable(call.func, program_type)) {
57,328✔
608
        return false;
609
    }
610
    return platform.get_helper_prototype(call.func, program_type).return_type ==
57,328✔
611
           EBPF_RETURN_TYPE_INTEGER_OR_NO_RETURN_IF_SUCCEED;
57,328✔
612
}
613

614
static bool is_tail_call_site(const Instruction& ins, const ebpf_platform_t& platform,
1,293,338✔
615
                              const EbpfProgramType& program_type) {
616
    if (const auto* call = std::get_if<Call>(&ins)) {
1,293,338✔
617
        return is_tail_call_helper(*call, platform, program_type);
59,824✔
618
    }
619
    if (std::holds_alternative<Callx>(ins)) {
1,233,514✔
620
        // At CFG-construction time, callx target ids are not available.
621
        // Conservatively treat callx as a potential tail-call site.
622
        return true;
48✔
623
    }
624
    return false;
616,733✔
625
}
626

627
static void collect_wto_labels(const CycleOrLabel& component, std::set<Label>& labels) {
1,293,422✔
628
    if (const auto plabel = std::get_if<Label>(&component)) {
1,293,422✔
629
        labels.insert(*plabel);
1,293,338✔
630
        return;
1,293,338✔
631
    }
632
    for (const auto& nested_component : *std::get<std::shared_ptr<WtoCycle>>(component)) {
800✔
633
        collect_wto_labels(nested_component, labels);
716✔
634
    }
635
}
636

637
// Pass: ValidateTailCallDepth
638
// Reads    : Program (CFG + instructions), Wto, platform, program type.
639
// Writes   : nothing.
640
// Throws   : InvalidControlFlow if the reachable tail-call chain exceeds the fixed limit.
641
// Notes    : Counts tail-call sites along the longest path through the reachable maximal-SCC DAG
642
//            so cycles do not inflate depth. Maximal SCCs are derived from WTO nesting: labels in
643
//            the same outermost WTO cycle are mutually reachable and form one maximal SCC.
644
static void pass_validate_tail_call_depth(const Program& prog, const Wto& wto, const ebpf_platform_t& platform,
4,136✔
645
                                          const EbpfProgramType& program_type) {
646
    constexpr int tail_call_chain_limit = 33;
4,136✔
647

648
    // WTO only covers labels reachable from entry.
649
    std::set<Label> reachable;
4,136✔
650
    for (const auto& component : wto) {
1,296,842✔
651
        collect_wto_labels(component, reachable);
1,292,706✔
652
    }
653

654
    // Partition reachable labels by maximal SCC representative:
655
    // the outermost containing WTO head, or the label itself if not in a cycle.
656
    std::map<Label, Label> maximal_scc_of;
4,136✔
657
    std::set<Label> maximal_scc_ids;
4,136✔
658
    for (const auto& label : reachable) {
1,297,474✔
659
        const Label scc_id = wto.nesting(label).outermost_head().value_or(label);
2,586,677✔
660
        maximal_scc_of.emplace(label, scc_id);
1,293,338✔
661
        maximal_scc_ids.insert(scc_id);
1,293,338✔
662
    }
1,293,338✔
663

664
    std::map<Label, int> tail_sites_per_scc;
4,136✔
665
    std::map<Label, std::optional<Label>> representative_tail_label;
4,136✔
666
    std::map<Label, std::set<Label>> dag_successors;
4,136✔
667
    std::map<Label, int> indegree;
4,136✔
668
    for (const auto& scc_id : maximal_scc_ids) {
1,296,842✔
669
        tail_sites_per_scc.emplace(scc_id, 0);
1,292,706✔
670
        representative_tail_label.emplace(scc_id, std::nullopt);
1,292,706✔
671
        dag_successors.emplace(scc_id, std::set<Label>{});
1,292,706✔
672
        indegree.emplace(scc_id, 0);
1,292,707✔
673
    }
674

675
    for (const auto& label : reachable) {
1,297,474✔
676
        const Label src_scc = maximal_scc_of.at(label);
1,293,338✔
677
        if (is_tail_call_site(prog.instruction_at(label), platform, program_type)) {
1,293,338✔
678
            ++tail_sites_per_scc.at(src_scc);
1,646✔
679
            auto& representative = representative_tail_label.at(src_scc);
1,646✔
680
            if (!representative.has_value()) {
1,646✔
681
                representative = label;
1,646✔
682
            }
683
        }
684
        for (const auto& child : prog.cfg().children_of(label)) {
2,681,992✔
685
            if (!reachable.contains(child)) {
1,388,654✔
686
                continue;
×
687
            }
688
            const Label dst_scc = maximal_scc_of.at(child);
1,388,654✔
689
            if (src_scc != dst_scc && dag_successors[src_scc].insert(dst_scc).second) {
1,388,654✔
690
                ++indegree.at(dst_scc);
1,387,920✔
691
            }
692
        }
1,388,654✔
693
    }
1,293,338✔
694

695
    std::map<Label, int> indegree_for_sources = indegree;
4,136✔
696
    std::vector<Label> topo_order;
4,136✔
697
    topo_order.reserve(maximal_scc_ids.size());
4,136✔
698
    for (const auto& scc_id : maximal_scc_ids) {
1,296,842✔
699
        if (indegree.at(scc_id) == 0) {
1,292,706✔
700
            topo_order.push_back(scc_id);
4,136✔
701
        }
702
    }
703
    for (size_t index = 0; index < topo_order.size(); ++index) {
1,296,842✔
704
        const Label scc_id = topo_order[index];
1,292,706✔
705
        for (const auto& succ : dag_successors.at(scc_id)) {
2,680,626✔
706
            --indegree.at(succ);
1,387,920✔
707
            if (indegree.at(succ) == 0) {
1,387,920✔
708
                topo_order.push_back(succ);
1,288,570✔
709
            }
710
        }
711
    }
1,292,706✔
712
    if (topo_order.size() != maximal_scc_ids.size()) {
4,136✔
713
        CRAB_ERROR("WTO-derived SCC graph must be acyclic");
×
714
    }
715

716
    // Longest path over maximal SCC DAG by tail-call site count.
717
    constexpr int uninitialized_depth = std::numeric_limits<int>::min();
4,136✔
718
    std::map<Label, int> max_tail_depth;
4,136✔
719
    std::map<Label, std::optional<Label>> depth_label;
4,136✔
720
    for (const auto& scc_id : maximal_scc_ids) {
1,296,842✔
721
        max_tail_depth.emplace(scc_id, uninitialized_depth);
1,292,706✔
722
        depth_label.emplace(scc_id, std::nullopt);
1,292,706✔
723
        if (indegree_for_sources.at(scc_id) == 0) {
1,292,706✔
724
            max_tail_depth.at(scc_id) = tail_sites_per_scc.at(scc_id);
4,136✔
725
            depth_label.at(scc_id) = representative_tail_label.at(scc_id);
650,489✔
726
        }
727
    }
728

729
    for (const auto& scc_id : topo_order) {
1,296,836✔
730
        const int current_depth = max_tail_depth.at(scc_id);
1,292,702✔
731
        if (current_depth == uninitialized_depth) {
1,292,702✔
732
            continue;
×
733
        }
734
        if (current_depth > tail_call_chain_limit) {
1,292,702✔
735
            const Label at = depth_label.at(scc_id).value_or(scc_id);
2✔
736
            throw InvalidControlFlow{"tail call chain depth exceeds " + std::to_string(tail_call_chain_limit) +
4✔
737
                                     " (at " + to_string(at) + ")"};
7✔
738
        }
2✔
739
        for (const auto& succ : dag_successors.at(scc_id)) {
2,680,616✔
740
            const int candidate_depth = current_depth + tail_sites_per_scc.at(succ);
1,387,916✔
741
            if (candidate_depth > max_tail_depth.at(succ)) {
1,387,916✔
742
                max_tail_depth.at(succ) = candidate_depth;
1,289,520✔
743
                depth_label.at(succ) = representative_tail_label.at(succ).has_value()
2,579,040✔
744
                                           ? representative_tail_label.at(succ)
1,648✔
745
                                           : depth_label.at(scc_id);
2,628,238✔
746
            }
747
        }
748
    }
749
}
4,154✔
750

751
// Pass: ComputeCallbackMetadata
752
// Reads    : Program (CFG + instructions).
753
// Writes   : builder.prog's callback metadata, via CfgBuilder::set_callback_metadata: the set
754
//            of top-level concrete-instruction labels eligible as PTR_TO_FUNC targets, and the
755
//            subset whose body can reach a top-level Exit.
756
// Notes    : Excludes Label::entry/Label::exit, synthetic jump labels, labels under an inlined
757
//            stack-frame prefix, and Exit instructions themselves.
758
static void pass_compute_callback_metadata(CfgBuilder& builder) {
4,134✔
759
    const Program& prog = builder.prog;
4,134✔
760
    CallbackMetadata md;
4,134✔
761
    for (const Label& label : prog.labels()) {
1,330,036✔
762
        if (label == Label::entry || label == Label::exit || label.isjump() || !label.stack_frame_prefix.empty()) {
1,325,902✔
763
            continue;
236,848✔
764
        }
765
        if (std::holds_alternative<Exit>(prog.instruction_at(label))) {
1,089,054✔
766
            continue;
3,654✔
767
        }
768
        md.target_labels.insert(label.from);
1,085,400✔
769
    }
770

771
    const auto has_reachable_top_level_exit = [&](const Label& start) {
1,087,467✔
772
        std::set<Label> seen;
1,085,400✔
773
        std::vector<Label> worklist{start};
2,713,500✔
774
        while (!worklist.empty()) {
303,231,172✔
775
            Label label = worklist.back();
303,231,158✔
776
            worklist.pop_back();
303,231,158✔
777
            if (seen.contains(label)) {
303,231,158✔
778
                continue;
264✔
779
            }
780
            seen.insert(label);
303,230,894✔
781
            if (label == Label::exit) {
303,230,894✔
782
                return true;
793✔
783
            }
784
            if (label != Label::entry && prog.cfg().contains(label) &&
758,073,270✔
785
                std::holds_alternative<Exit>(prog.instruction_at(label)) && label.stack_frame_prefix.empty()) {
758,073,270✔
786
                return true;
541,900✔
787
            }
788
            for (const Label& child : prog.cfg().children_of(label)) {
624,198,630✔
789
                worklist.push_back(child);
322,053,122✔
790
            }
791
        }
303,231,158✔
792
        return false;
7✔
793
    };
2,170,800✔
794
    for (const int32_t label_num : md.target_labels) {
1,089,534✔
795
        const Label label{gsl::narrow<int>(label_num)};
1,085,400✔
796
        if (has_reachable_top_level_exit(label)) {
1,085,400✔
797
            md.targets_with_exit.insert(label_num);
1,085,386✔
798
        }
799
    }
1,085,400✔
800
    builder.set_callback_metadata(std::move(md));
4,134✔
801
}
4,134✔
802

803
// Pass: InsertTerminationCounters
804
// Reads    : WTO of the current CFG.
805
// Writes   : For each WTO loop head, inserts an IncrementLoopCounter at a synthetic
806
//            increment-counter label placed between the head and its successors (CFG edges and
807
//            instructions are both mutated via CfgBuilder::insert_after).
808
// Notes    : WTO identifies every strongly connected component and its entry point(s), which
809
//            are the natural locations for counters that help verify program termination.
810
static void pass_insert_termination_counters(CfgBuilder& builder, const Wto& wto) {
46✔
811
    wto.for_each_loop_head([&](const Label& label) -> void {
91✔
812
        builder.insert_after(label, Label::make_increment_counter(label), IncrementLoopCounter{label});
88✔
813
    });
44✔
814
}
46✔
815

816
// Pass: ExtractAssertions
817
// Reads    : Program instructions, ProgramInfo, options.
818
// Writes   : Populates builder.prog.m_assertions with the per-label precondition vector
819
//            (memory bounds, type guards, etc.) produced by get_assertions.
820
// Notes    : Runs for every label in the CFG, including synthetic ones (Assume / counters).
821
static void pass_extract_assertions(CfgBuilder& builder, const ProgramInfo& info, const VerifierOptions& options) {
4,134✔
822
    for (const auto& label : builder.prog.labels()) {
1,330,080✔
823
        builder.set_assertions(label, get_assertions(builder.prog.instruction_at(label), info, options.runtime, label));
1,988,919✔
824
    }
825
}
4,134✔
826

827
// from_sequence orchestrates the preparation pipeline. Each pass has a documented
828
// pre/postcondition; this function's job is just to sequence them and hand the
829
// result off as a finalised Program.
830
Program Program::from_sequence(const InstructionSeq& inst_seq, const ProgramInfo& info,
4,226✔
831
                               const VerifierOptions& options) {
832
    // --- Pass: ValidateOptions --------------------------------------------
833
    options.runtime.validate();
4,226✔
834
    // Preserves the platform-non-null invariant for every subsequent pass in this pipeline.
835
    assert(info.platform != nullptr && "info.platform must be set before Program::from_sequence");
4,226✔
836

837
    // --- Pass: ValidateInstructionSupport ---------------------------------
838
    pass_validate_instruction_support(inst_seq, info);
4,226✔
839

840
    // --- Pass: ResolveKfuncCalls ------------------------------------------
841
    const ResolvedKfuncCalls resolved_kfunc_calls = pass_resolve_kfunc_calls(inst_seq, info);
4,180✔
842

843
    // --- Pass: LowerPseudoLoads -------------------------------------------
844
    const LoweredPseudoLoads lowered_pseudo_loads = pass_lower_pseudo_loads(inst_seq, info);
4,154✔
845

846
    // --- Pass: BuildInitialCfg (nodes, then edges with InsertAssumeEdges) -
847
    CfgBuilder builder;
4,150✔
848
    // Populate m_info up front so any pass that reads builder.prog.info() sees the real
849
    // ProgramInfo rather than a default-constructed (null-platform) one.
850
    builder.prog.m_info = info;
4,150✔
851
    pass_populate_nodes(builder, inst_seq, info, resolved_kfunc_calls, lowered_pseudo_loads);
4,150✔
852
    pass_connect_edges(builder, inst_seq, options.must_have_exit);
4,150✔
853

854
    // --- Pass: InlineLocalCalls -------------------------------------------
855
    pass_inline_local_calls(builder, inst_seq, options.runtime.max_call_stack_frames);
4,144✔
856

857
    // --- Pass: ValidateTailCallDepth --------------------------------------
858
    const Wto wto{builder.prog.cfg()};
4,136✔
859
    pass_validate_tail_call_depth(builder.prog, wto, *info.platform, info.type);
4,136✔
860

861
    // --- Pass: ComputeCallbackMetadata ------------------------------------
862
    pass_compute_callback_metadata(builder);
4,134✔
863

864
    // --- Pass: InsertTerminationCounters ----------------------------------
865
    if (options.runtime.check_for_termination) {
4,134✔
866
        pass_insert_termination_counters(builder, wto);
46✔
867
    }
868

869
    // --- Pass: ExtractAssertions ------------------------------------------
870
    pass_extract_assertions(builder, info, options);
4,134✔
871

872
    return std::move(builder.prog);
6,201✔
873
}
4,178✔
874

875
std::set<BasicBlock> BasicBlock::collect_basic_blocks(const Cfg& cfg, const bool simplify) {
2✔
876
    if (!simplify) {
2✔
877
        std::set<BasicBlock> res;
2✔
878
        for (const Label& label : cfg.labels()) {
32✔
879
            if (label != cfg.entry_label() && label != cfg.exit_label()) {
73✔
880
                res.insert(BasicBlock{label});
39✔
881
            }
882
        }
883
        return res;
2✔
884
    }
2✔
885

886
    std::set<BasicBlock> res;
×
887
    std::set<Label> worklist;
×
888
    for (const Label& label : cfg.labels()) {
×
889
        worklist.insert(label);
×
890
    }
891
    std::set<Label> seen;
×
892
    while (!worklist.empty()) {
×
893
        Label label = *worklist.begin();
×
894
        worklist.erase(label);
×
895
        if (seen.contains(label)) {
×
896
            continue;
×
897
        }
898
        seen.insert(label);
×
899

900
        if (cfg.in_degree(label) == 1 && cfg.num_siblings(label) == 1) {
×
901
            continue;
×
902
        }
903
        BasicBlock bb{label};
×
904
        while (cfg.out_degree(label) == 1) {
×
905
            const Label& next_label = cfg.get_child(bb.last_label());
×
906

907
            if (seen.contains(next_label) || next_label == cfg.exit_label() || cfg.in_degree(next_label) != 1) {
×
908
                break;
909
            }
910

911
            if (bb.first_label() == cfg.entry_label()) {
×
912
                // Entry instruction is Undefined. We want to start with 0
913
                bb.m_ts.clear();
×
914
            }
915
            bb.m_ts.push_back(next_label);
×
916

917
            worklist.erase(next_label);
×
918
            seen.insert(next_label);
×
919

920
            label = next_label;
×
921
        }
×
922
        res.emplace(std::move(bb));
×
923
    }
×
924
    return res;
×
925
}
×
926

927
/// Get the type of given Instruction.
928
/// Most of these type names are also statistics header labels.
929
Cfg cfg_from_adjacency_list(const std::map<Label, std::vector<Label>>& AdjList) {
8✔
930
    CfgBuilder builder;
8✔
931
    for (const auto& label : std::views::keys(AdjList)) {
66✔
932
        if (label == Label::entry || label == Label::exit) {
58✔
933
            continue;
8✔
934
        }
935
        builder.insert(label, Undefined{});
75✔
936
    }
937
    for (const auto& [label, children] : AdjList) {
66✔
938
        for (const auto& child : children) {
136✔
939
            builder.add_child(label, child);
78✔
940
        }
941
    }
942
    return builder.prog.cfg();
16✔
943
}
8✔
944
} // 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