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

Alan-Jowett / ebpf-verifier / 15194704016

22 May 2025 08:53AM UTC coverage: 88.11% (-0.07%) from 88.177%
15194704016

push

github

elazarg
uniform class names and explicit constructors for adapt_sgraph.hpp

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

27 of 30 new or added lines in 1 file covered. (90.0%)

481 existing lines in 33 files now uncovered.

8552 of 9706 relevant lines covered (88.11%)

9089054.61 hits per line

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

64.79
/src/asm_cfg.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 "asm_syntax.hpp"
11
#include "cfg/cfg.hpp"
12
#include "cfg/wto.hpp"
13
#include "config.hpp"
14
#include "program.hpp"
15

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

22
namespace prevail {
23
struct CfgBuilder final {
1,251✔
24
    Program prog;
25

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

34
        for (const Label& next_label : prev_children) {
72✔
35
            prog.m_cfg.get_node(next_label).parents.erase(prev_label);
40✔
36
        }
37

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

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

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

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

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

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

86
/// Get the inverse of a given comparison operation.
87
static Condition::Op reverse(const Condition::Op op) {
25,224✔
88
    switch (op) {
25,224✔
89
    case Condition::Op::EQ: return Condition::Op::NE;
5,153✔
90
    case Condition::Op::NE: return Condition::Op::EQ;
4,394✔
91

92
    case Condition::Op::GE: return Condition::Op::LT;
150✔
93
    case Condition::Op::LT: return Condition::Op::GE;
243✔
94

95
    case Condition::Op::SGE: return Condition::Op::SLT;
17✔
96
    case Condition::Op::SLT: return Condition::Op::SGE;
616✔
97

98
    case Condition::Op::LE: return Condition::Op::GT;
17✔
99
    case Condition::Op::GT: return Condition::Op::LE;
1,055✔
100

101
    case Condition::Op::SLE: return Condition::Op::SGT;
17✔
102
    case Condition::Op::SGT: return Condition::Op::SLE;
935✔
103

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

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

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

121
    if (const auto pins = std::get_if<Jmp>(&ins)) {
255,562✔
122
        if (!pins->cond) {
114✔
123
            return false;
57✔
124
        }
125
    }
126

127
    return true;
127,724✔
128
}
129

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

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

137
    // Construct the variable prefix to use for the new stack frame
138
    // and store a copy in the CallLocal instruction since the instruction-specific
139
    // labels may only exist until the CFG is simplified.
140
    const std::string stack_frame_prefix = to_string(caller_label);
140✔
141
    if (const auto pcall = std::get_if<CallLocal>(&builder.prog.instruction_at(caller_label))) {
210✔
142
        pcall->stack_frame_prefix = stack_frame_prefix;
140✔
143
    }
144

145
    // Walk the transitive closure of CFG nodes starting at entry_label and ending at
146
    // any exit instruction.
147
    std::set macro_labels{entry_label};
367✔
148
    std::set seen_labels{entry_label};
367✔
149
    while (!macro_labels.empty()) {
662✔
150
        Label macro_label = *macro_labels.begin();
526✔
151
        macro_labels.erase(macro_label);
526✔
152

153
        if (stack_frame_prefix == macro_label.stack_frame_prefix) {
526✔
154
            throw InvalidControlFlow{stack_frame_prefix + ": illegal recursion"};
6✔
155
        }
156

157
        // Clone the macro block into a new block with the new stack frame prefix.
158
        const Label label{macro_label.from, macro_label.to, stack_frame_prefix};
785✔
159
        auto inst = builder.prog.instruction_at(macro_label);
522✔
160
        if (const auto pexit = std::get_if<Exit>(&inst)) {
522✔
161
            pexit->stack_frame_prefix = label.stack_frame_prefix;
142✔
162
        } else if (const auto pcall = std::get_if<Call>(&inst)) {
452✔
163
            pcall->stack_frame_prefix = label.stack_frame_prefix;
2✔
164
        }
165
        builder.insert(label, inst);
522✔
166

167
        if (first) {
522✔
168
            // Add an edge from the caller to the new block.
169
            first = false;
140✔
170
            builder.add_child(caller_label, label);
140✔
171
        }
172

173
        // Add an edge from any other predecessors.
174
        for (const auto& prev_macro_nodes = builder.prog.cfg().parents_of(macro_label);
783✔
175
             const auto& prev_macro_label : prev_macro_nodes) {
1,173✔
176
            const Label prev_label(prev_macro_label.from, prev_macro_label.to, to_string(caller_label));
390✔
177
            if (const auto& labels = builder.prog.cfg().labels();
390✔
178
                std::ranges::find(labels, prev_label) != labels.end()) {
390✔
179
                builder.add_child(prev_label, label);
384✔
180
            }
181
        }
390✔
182

183
        // Walk all successor nodes.
184
        for (const auto& next_macro_nodes = builder.prog.cfg().children_of(macro_label);
1,047✔
185
             const auto& next_macro_label : next_macro_nodes) {
1,311✔
186
            if (next_macro_label == builder.prog.cfg().exit_label()) {
792✔
187
                // This is an exit transition, so add edge to the block to execute
188
                // upon returning from the macro.
189
                builder.add_child(label, exit_to_label);
140✔
190
            } else if (!seen_labels.contains(next_macro_label)) {
388✔
191
                // Push any other unprocessed successor label onto the list to be processed.
192
                if (!macro_labels.contains(next_macro_label)) {
388✔
193
                    macro_labels.insert(next_macro_label);
386✔
194
                }
195
                seen_labels.insert(macro_label);
388✔
196
            }
197
        }
198
    }
1,048✔
199

200
    // Remove the original edge from the caller node to its successor,
201
    // since processing now goes through the function macro instead.
202
    builder.remove_child(caller_label, exit_to_label);
136✔
203

204
    // Finally, recurse to replace any nested function macros.
205
    string caller_label_str = to_string(caller_label);
136✔
206
    const long stack_frame_depth = std::ranges::count(caller_label_str, STACK_FRAME_DELIMITER) + 2;
136✔
207
    for (const auto& macro_label : seen_labels) {
480✔
208
        const Label label{macro_label.from, macro_label.to, caller_label_str};
576✔
209
        if (const auto pins = std::get_if<CallLocal>(&builder.prog.instruction_at(label))) {
546✔
210
            if (stack_frame_depth >= MAX_CALL_STACK_FRAMES) {
82✔
211
                throw InvalidControlFlow{"too many call stack frames"};
10✔
212
            }
213
            add_cfg_nodes(builder, label, pins->target);
78✔
214
        }
215
    }
374✔
216
}
588✔
217

218
/// Convert an instruction sequence to a control-flow graph (CFG).
219
static CfgBuilder instruction_seq_to_cfg(const InstructionSeq& insts, const bool must_have_exit) {
2,510✔
220
    CfgBuilder builder;
2,510✔
221

222
    // First, add all instructions to the CFG without connecting
223
    for (const auto& [label, inst, _] : insts) {
287,850✔
224
        if (std::holds_alternative<Undefined>(inst)) {
285,340✔
UNCOV
225
            continue;
×
226
        }
227
        builder.insert(label, inst);
285,340✔
228
    }
229

230
    if (insts.size() == 0) {
2,510✔
UNCOV
231
        throw InvalidControlFlow{"empty instruction sequence"};
×
232
    } else {
233
        const auto& [label, inst, _0] = insts[0];
2,510✔
234
        builder.add_child(builder.prog.cfg().entry_label(), label);
2,514✔
235
    }
236

237
    // Do a first pass ignoring all function macro calls.
238
    for (size_t i = 0; i < insts.size(); i++) {
287,850✔
239
        const auto& [label, inst, _0] = insts[i];
285,340✔
240

241
        if (std::holds_alternative<Undefined>(inst)) {
285,340✔
242
            continue;
6✔
243
        }
244

245
        Label fallthrough{builder.prog.cfg().exit_label()};
285,340✔
246
        if (i + 1 < insts.size()) {
285,340✔
247
            fallthrough = std::get<0>(insts[i + 1]);
282,830✔
248
        } else {
249
            if (has_fall(inst) && must_have_exit) {
3,195✔
UNCOV
250
                throw InvalidControlFlow{"fallthrough in last instruction"};
×
251
            }
252
        }
253
        if (const auto jmp = std::get_if<Jmp>(&inst)) {
285,340✔
254
            if (const auto cond = jmp->cond) {
29,734✔
255
                Label target_label = jmp->target;
25,230✔
256
                if (target_label == fallthrough) {
25,230✔
257
                    builder.add_child(label, fallthrough);
6✔
258
                    continue;
6✔
259
                }
260
                if (!builder.prog.cfg().contains(target_label)) {
25,224✔
UNCOV
261
                    throw InvalidControlFlow{"jump to undefined label " + to_string(target_label)};
×
262
                }
263
                builder.insert_jump(label, target_label, Assume{.cond = *cond, .is_implicit = true});
37,836✔
264
                builder.insert_jump(label, fallthrough, Assume{.cond = reverse(*cond), .is_implicit = true});
50,448✔
265
            } else {
25,230✔
266
                builder.add_child(label, jmp->target);
4,504✔
267
            }
268
        } else {
269
            if (has_fall(inst)) {
255,606✔
270
                builder.add_child(label, fallthrough);
254,192✔
271
            }
272
        }
273
        if (std::holds_alternative<Exit>(inst)) {
285,334✔
274
            builder.add_child(label, builder.prog.cfg().exit_label());
2,121✔
275
        }
276
    }
285,340✔
277

278
    // Now replace macros. We have to do this as a second pass so that
279
    // we only add new nodes that are actually reachable, based on the
280
    // results of the first pass.
281
    for (const auto& [label, inst, _] : insts) {
287,794✔
282
        if (const auto pins = std::get_if<CallLocal>(&inst)) {
285,319✔
283
            add_cfg_nodes(builder, label, pins->target);
62✔
284
        }
285
    }
286

287
    return builder;
2,502✔
288
}
8✔
289

290
Program Program::from_sequence(const InstructionSeq& inst_seq, const ProgramInfo& info,
2,510✔
291
                               const ebpf_verifier_options_t& options) {
292
    thread_local_program_info.set(info);
3,765✔
293
    thread_local_options = options;
2,510✔
294

295
    // Convert the instruction sequence to a deterministic control-flow graph.
296
    CfgBuilder builder = instruction_seq_to_cfg(inst_seq, options.cfg_opts.must_have_exit);
2,510✔
297

298
    // Detect loops using Weak Topological Ordering (WTO) and insert counters at loop entry points. WTO provides a
299
    // hierarchical decomposition of the CFG that identifies all strongly connected components (cycles) and their entry
300
    // points. These entry points serve as natural locations for loop counters that help verify program termination.
301
    if (options.cfg_opts.check_for_termination) {
2,502✔
302
        const Wto wto{builder.prog.cfg()};
34✔
303
        wto.for_each_loop_head([&](const Label& label) -> void {
34✔
304
            builder.insert_after(label, Label::make_increment_counter(label), IncrementLoopCounter{label});
64✔
305
        });
32✔
306
    }
34✔
307

308
    // Annotate the CFG by explicitly adding in assertions before every memory instruction.
309
    for (const auto& label : builder.prog.labels()) {
343,720✔
310
        builder.set_assertions(label, get_assertions(builder.prog.instruction_at(label), info, label));
682,436✔
311
    }
312
    return builder.prog;
3,753✔
313
}
2,502✔
314

UNCOV
315
std::set<BasicBlock> BasicBlock::collect_basic_blocks(const Cfg& cfg, const bool simplify) {
×
UNCOV
316
    if (!simplify) {
×
317
        std::set<BasicBlock> res;
×
318
        for (const Label& label : cfg.labels()) {
×
319
            if (label != cfg.entry_label() && label != cfg.exit_label()) {
×
320
                res.insert(BasicBlock{label});
×
321
            }
322
        }
UNCOV
323
        return res;
×
UNCOV
324
    }
×
325

326
    std::set<BasicBlock> res;
×
UNCOV
327
    std::set<Label> worklist;
×
328
    for (const Label& label : cfg.labels()) {
×
329
        worklist.insert(label);
×
330
    }
331
    std::set<Label> seen;
×
UNCOV
332
    while (!worklist.empty()) {
×
333
        Label label = *worklist.begin();
×
334
        worklist.erase(label);
×
335
        if (seen.contains(label)) {
×
336
            continue;
×
337
        }
338
        seen.insert(label);
×
339

340
        if (cfg.in_degree(label) == 1 && cfg.num_siblings(label) == 1) {
×
UNCOV
341
            continue;
×
342
        }
343
        BasicBlock bb{label};
×
UNCOV
344
        while (cfg.out_degree(label) == 1) {
×
345
            const Label& next_label = cfg.get_child(bb.last_label());
×
346

347
            if (seen.contains(next_label) || next_label == cfg.exit_label() || cfg.in_degree(next_label) != 1) {
×
348
                break;
349
            }
350

UNCOV
351
            if (bb.first_label() == cfg.entry_label()) {
×
352
                // Entry instruction is Undefined. We want to start with 0
353
                bb.m_ts.clear();
×
354
            }
355
            bb.m_ts.push_back(next_label);
×
356

357
            worklist.erase(next_label);
×
UNCOV
358
            seen.insert(next_label);
×
359

360
            label = next_label;
×
UNCOV
361
        }
×
362
        res.emplace(std::move(bb));
×
363
    }
×
364
    return res;
×
365
}
×
366

367
/// Get the type of given Instruction.
368
/// Most of these type names are also statistics header labels.
UNCOV
369
static std::string instype(Instruction ins) {
×
UNCOV
370
    if (const auto pcall = std::get_if<Call>(&ins)) {
×
371
        if (pcall->is_map_lookup) {
×
372
            return "call_1";
×
373
        }
374
        if (pcall->pairs.empty()) {
×
UNCOV
375
            if (std::ranges::all_of(pcall->singles,
×
376
                                    [](const ArgSingle kr) { return kr.kind == ArgSingle::Kind::ANYTHING; })) {
377
                return "call_nomem";
×
378
            }
379
        }
UNCOV
380
        return "call_mem";
×
381
    } else if (std::holds_alternative<Callx>(ins)) {
382
        return "callx";
×
UNCOV
383
    } else if (const auto pimm = std::get_if<Mem>(&ins)) {
×
384
        return pimm->is_load ? "load" : "store";
×
385
    } else if (std::holds_alternative<Atomic>(ins)) {
386
        return "load_store";
×
387
    } else if (std::holds_alternative<Packet>(ins)) {
388
        return "packet_access";
×
UNCOV
389
    } else if (const auto pins = std::get_if<Bin>(&ins)) {
×
390
        switch (pins->op) {
×
391
        case Bin::Op::MOV:
×
392
        case Bin::Op::MOVSX8:
393
        case Bin::Op::MOVSX16:
UNCOV
394
        case Bin::Op::MOVSX32: return "assign";
×
UNCOV
395
        default: return "arith";
×
396
        }
397
    } else if (std::holds_alternative<Un>(ins)) {
UNCOV
398
        return "arith";
×
399
    } else if (std::holds_alternative<LoadMapFd>(ins)) {
400
        return "assign";
×
401
    } else if (std::holds_alternative<LoadMapAddress>(ins)) {
402
        return "assign";
×
403
    } else if (std::holds_alternative<Assume>(ins)) {
404
        return "assume";
×
405
    } else {
406
        return "other";
×
407
    }
408
}
409

UNCOV
410
std::vector<std::string> stats_headers() {
×
411
    return {
412
        "instructions", "joins",      "other",      "jumps",         "assign",  "arith",
413
        "load",         "store",      "load_store", "packet_access", "call_1",  "call_mem",
414
        "call_nomem",   "reallocate", "map_in_map", "arith64",       "arith32",
UNCOV
415
    };
×
416
}
417

UNCOV
418
std::map<std::string, int> collect_stats(const Program& prog) {
×
UNCOV
419
    std::map<std::string, int> res;
×
420
    for (const auto& h : stats_headers()) {
×
421
        res[h] = 0;
×
422
    }
×
423
    for (const auto& label : prog.labels()) {
×
424
        res["instructions"]++;
×
425
        const auto cmd = prog.instruction_at(label);
×
426
        if (const auto pins = std::get_if<LoadMapFd>(&cmd)) {
×
427
            if (pins->mapfd == -1) {
×
428
                res["map_in_map"] = 1;
×
429
            }
430
        }
UNCOV
431
        if (const auto pins = std::get_if<Call>(&cmd)) {
×
UNCOV
432
            if (pins->reallocate_packet) {
×
433
                res["reallocate"] = 1;
×
434
            }
435
        }
UNCOV
436
        if (const auto pins = std::get_if<Bin>(&cmd)) {
×
UNCOV
437
            res[pins->is64 ? "arith64" : "arith32"]++;
×
438
        }
439
        res[instype(cmd)]++;
×
UNCOV
440
        if (prog.cfg().in_degree(label) > 1) {
×
441
            res["joins"]++;
×
442
        }
443
        if (prog.cfg().out_degree(label) > 1) {
×
UNCOV
444
            res["jumps"]++;
×
445
        }
446
    }
×
UNCOV
447
    return res;
×
448
}
×
449

450
Cfg cfg_from_adjacency_list(const std::map<Label, std::vector<Label>>& AdjList) {
6✔
451
    CfgBuilder builder;
6✔
452
    for (const auto& label : std::views::keys(AdjList)) {
46✔
453
        if (label == Label::entry || label == Label::exit) {
40✔
454
            continue;
6✔
455
        }
456
        builder.insert(label, Undefined{});
51✔
457
    }
458
    for (const auto& [label, children] : AdjList) {
46✔
459
        for (const auto& child : children) {
94✔
460
            builder.add_child(label, child);
54✔
461
        }
462
    }
463
    return builder.prog.cfg();
12✔
464
}
6✔
465
} // 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