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

Alan-Jowett / ebpf-verifier / 21995767800

13 Feb 2026 05:11PM UTC coverage: 86.86% (+0.5%) from 86.313%
21995767800

Pull #161

github

web-flow
Merge 65fb5a1f3 into 429d09d0b
Pull Request #161: Failure slice

533 of 684 new or added lines in 8 files covered. (77.92%)

1 existing line in 1 file now uncovered.

10193 of 11735 relevant lines covered (86.86%)

2883793.74 hits per line

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

58.52
/src/printing.cpp
1
// Copyright (c) Prevail Verifier contributors.
2
// SPDX-License-Identifier: MIT
3
#include <fstream>
4
#include <iomanip>
5
#include <iostream>
6
#include <variant>
7
#include <vector>
8

9
#include "arith/num_big.hpp"
10
#include "arith/variable.hpp"
11
#include "cfg/cfg.hpp"
12
#include "crab/interval.hpp"
13
#include "crab/type_encoding.hpp"
14
#include "crab/var_registry.hpp"
15
#include "ir/syntax.hpp"
16
#include "linux/gpl/spec_type_descriptors.hpp"
17
#include "platform.hpp"
18
#include "spec/function_prototypes.hpp"
19
#include "verifier.hpp"
20

21
using std::optional;
22
using std::string;
23
using std::vector;
24

25
namespace prevail {
26

27
std::ostream& operator<<(std::ostream& o, const Interval& interval) {
688✔
28
    if (interval.is_bottom()) {
688✔
29
        o << "_|_";
×
30
    } else {
31
        o << "[" << interval._lb << ", " << interval._ub << "]";
688✔
32
    }
33
    return o;
688✔
34
}
35
std::ostream& operator<<(std::ostream& o, const Number& z) { return o << z._n.str(); }
311,814✔
36

37
std::string Number::to_string() const { return _n.str(); }
×
38

39
std::string Interval::to_string() const {
×
40
    std::ostringstream s;
×
41
    s << *this;
×
42
    return s.str();
×
43
}
×
44

45
std::ostream& operator<<(std::ostream& os, const Label& label) {
1,588✔
46
    if (label == Label::entry) {
1,588✔
47
        return os << "entry";
6✔
48
    }
49
    if (label == Label::exit) {
1,582✔
50
        return os << "exit";
10✔
51
    }
52
    if (!label.stack_frame_prefix.empty()) {
1,572✔
53
        os << label.stack_frame_prefix << STACK_FRAME_DELIMITER;
330✔
54
    }
55
    os << label.from;
1,572✔
56
    if (label.to != -1) {
1,572✔
57
        os << ":" << label.to;
50✔
58
    }
59
    if (!label.special_label.empty()) {
1,572✔
60
        os << " (" << label.special_label << ")";
20✔
61
    }
62
    return os;
786✔
63
}
64

65
string to_string(Label const& label) {
1,210✔
66
    std::stringstream str;
1,210✔
67
    str << label;
1,210✔
68
    return str.str();
2,420✔
69
}
1,210✔
70

71
struct LineInfoPrinter {
1✔
72
    std::ostream& os;
73
    std::string previous_source_line;
74

75
    void print_line_info(const Label& label) {
6✔
76
        if (thread_local_options.verbosity_opts.print_line_info) {
6✔
77
            const auto& line_info_map = thread_local_program_info.get().line_info;
×
78
            const auto& line_info = line_info_map.find(label.from);
×
79
            // Print line info only once.
80
            if (line_info != line_info_map.end() && line_info->second.source_line != previous_source_line) {
×
81
                os << "\n" << line_info->second << "\n";
×
82
                previous_source_line = line_info->second.source_line;
×
83
            }
84
        }
85
    }
6✔
86
};
87

88
struct DetailedPrinter : LineInfoPrinter {
89
    const Program& prog;
90

91
    DetailedPrinter(std::ostream& os, const Program& prog) : LineInfoPrinter{os}, prog(prog) {}
2✔
92

93
    void print_labels(const std::string& direction, const std::set<Label>& labels) {
6✔
94
        auto [it, et] = std::pair{labels.begin(), labels.end()};
6✔
95
        if (it != et) {
6✔
96
            os << "  " << direction << " ";
6✔
97
            while (it != et) {
12✔
98
                os << *it;
6✔
99
                ++it;
6✔
100
                if (it == et) {
6✔
101
                    os << ";";
6✔
102
                } else {
103
                    os << ",";
×
104
                }
105
            }
106
        }
107
        os << "\n";
6✔
108
    }
6✔
109

110
    void print_jump(const std::string& direction, const Label& label) {
6✔
111
        print_labels(direction, direction == "from" ? prog.cfg().parents_of(label) : prog.cfg().children_of(label));
6✔
112
    }
6✔
113

114
    void print_instruction(const Program& prog, const Label& label) {
×
115
        for (const auto& pre : prog.assertions_at(label)) {
×
116
            os << "  " << "assert " << pre << ";\n";
×
117
        }
×
118
        os << "  " << prog.instruction_at(label) << ";\n";
×
119
    }
×
120
};
121

122
void print_program(const Program& prog, std::ostream& os, const bool simplify) {
×
123
    DetailedPrinter printer{os, prog};
×
124
    for (const BasicBlock& bb : BasicBlock::collect_basic_blocks(prog.cfg(), simplify)) {
×
125
        printer.print_jump("from", bb.first_label());
×
126
        os << bb.first_label() << ":\n";
×
127
        for (const Label& label : bb) {
×
128
            printer.print_line_info(label);
×
129
            printer.print_instruction(prog, label);
×
130
        }
131
        printer.print_jump("goto", bb.last_label());
×
132
    }
×
133
    os << "\n";
×
134
}
×
135

136
void print_invariants(std::ostream& os, const Program& prog, const bool simplify, const AnalysisResult& result) {
×
137
    DetailedPrinter printer{os, prog};
×
138
    for (const BasicBlock& bb : BasicBlock::collect_basic_blocks(prog.cfg(), simplify)) {
×
139
        if (result.invariants.at(bb.first_label()).pre.is_bottom()) {
×
140
            continue;
×
141
        }
142
        os << "\nPre-invariant : " << result.invariants.at(bb.first_label()).pre << "\n";
×
143
        printer.print_jump("from", bb.first_label());
×
144
        os << bb.first_label() << ":\n";
×
145
        Label last_label = bb.first_label();
×
146
        for (const Label& label : bb) {
×
147
            printer.print_line_info(label);
×
148
            printer.print_instruction(prog, label);
×
149
            last_label = label;
×
150

151
            const auto& current = result.invariants.at(last_label);
×
152
            if (current.error) {
×
153
                os << "\nVerification error:\n";
×
154
                if (label != bb.last_label()) {
×
155
                    os << "After " << current.pre << "\n";
×
156
                }
157
                print_error(os, *current.error);
×
158
                os << "\n";
×
159
                return;
×
160
            }
161
        }
162
        const auto& current = result.invariants.at(last_label);
×
163
        if (!current.post.is_bottom()) {
×
164
            printer.print_jump("goto", last_label);
×
165
            os << "\nPost-invariant : " << current.post << "\n";
×
166
        }
167
    }
×
168
    os << "\n";
×
169
}
×
170

171
void print_dot(const Program& prog, std::ostream& out) {
×
172
    out << "digraph program {\n";
×
173
    out << "    node [shape = rectangle];\n";
×
174
    for (const auto& label : prog.labels()) {
×
175
        out << "    \"" << label << "\"[xlabel=\"" << label << "\",label=\"";
×
176

177
        for (const auto& pre : prog.assertions_at(label)) {
×
178
            out << "assert " << pre << "\\l";
×
179
        }
×
180
        out << prog.instruction_at(label) << "\\l";
×
181

182
        out << "\"];\n";
×
183
        for (const Label& next : prog.cfg().children_of(label)) {
×
184
            out << "    \"" << label << "\" -> \"" << next << "\";\n";
×
185
        }
186
        out << "\n";
×
187
    }
188
    out << "}\n";
×
189
}
×
190

191
void print_dot(const Program& prog, const std::string& outfile) {
×
192
    std::ofstream out{outfile};
×
193
    if (out.fail()) {
×
194
        throw std::runtime_error(std::string("Could not open file ") + outfile);
×
195
    }
196
    print_dot(prog, out);
×
197
}
×
198

199
void print_unreachable(std::ostream& os, const Program& prog, const AnalysisResult& result) {
×
200
    for (const auto& [label, notes] : result.find_unreachable(prog)) {
×
201
        for (const auto& msg : notes) {
×
202
            os << label << ": " << msg << "\n";
×
203
        }
204
    }
×
205
    os << "\n";
×
206
}
×
207

208
std::string to_string(const VerificationError& error) {
314✔
209
    std::stringstream ss;
314✔
210
    if (const auto& label = error.where) {
314✔
211
        ss << *label << ": ";
314✔
212
    }
213
    ss << error.what();
314✔
214
    return ss.str();
628✔
215
}
314✔
216

217
void print_error(std::ostream& os, const VerificationError& error) {
2✔
218
    LineInfoPrinter printer{os};
2✔
219
    if (const auto& label = error.where) {
2✔
220
        printer.print_line_info(*label);
2✔
221
        os << *label << ": ";
2✔
222
    }
223
    os << error.what() << "\n";
2✔
224
    os << "\n";
2✔
225
}
2✔
226

227
std::ostream& operator<<(std::ostream& os, const ArgSingle::Kind kind) {
74✔
228
    switch (kind) {
74✔
229
    case ArgSingle::Kind::ANYTHING: return os << "uint64_t";
14✔
230
    case ArgSingle::Kind::PTR_TO_CTX: return os << "ctx";
4✔
231
    case ArgSingle::Kind::PTR_TO_STACK: return os << "stack";
×
232
    case ArgSingle::Kind::MAP_FD: return os << "map_fd";
24✔
233
    case ArgSingle::Kind::MAP_FD_PROGRAMS: return os << "map_fd_programs";
×
234
    case ArgSingle::Kind::PTR_TO_MAP_KEY: return os << "map_key";
24✔
235
    case ArgSingle::Kind::PTR_TO_MAP_VALUE: return os << "map_value";
8✔
236
    }
237
    assert(false);
238
    return os;
239
}
240

241
std::ostream& operator<<(std::ostream& os, const ArgPair::Kind kind) {
×
242
    switch (kind) {
×
243
    case ArgPair::Kind::PTR_TO_READABLE_MEM: return os << "mem";
×
244
    case ArgPair::Kind::PTR_TO_WRITABLE_MEM: return os << "out";
×
245
    }
246
    assert(false);
247
    return os;
248
}
249

250
std::ostream& operator<<(std::ostream& os, const ArgSingle arg) {
74✔
251
    os << arg.kind;
74✔
252
    if (arg.or_null) {
74✔
253
        os << "?";
×
254
    }
255
    os << " " << arg.reg;
74✔
256
    return os;
74✔
257
}
258

259
std::ostream& operator<<(std::ostream& os, const ArgPair arg) {
×
260
    os << arg.kind;
×
261
    if (arg.or_null) {
×
262
        os << "?";
×
263
    }
264
    os << " " << arg.mem << "[" << arg.size;
×
265
    if (arg.can_be_zero) {
×
266
        os << "?";
×
267
    }
268
    os << "], uint64_t " << arg.size;
×
269
    return os;
×
270
}
271

272
std::ostream& operator<<(std::ostream& os, const Bin::Op op) {
280✔
273
    using Op = Bin::Op;
140✔
274
    switch (op) {
280✔
275
    case Op::MOV: return os;
81✔
276
    case Op::MOVSX8: return os << "s8";
×
277
    case Op::MOVSX16: return os << "s16";
×
278
    case Op::MOVSX32: return os << "s32";
×
279
    case Op::ADD: return os << "+";
72✔
280
    case Op::SUB: return os << "-";
×
281
    case Op::MUL: return os << "*";
×
282
    case Op::UDIV: return os << "/";
×
283
    case Op::SDIV: return os << "s/";
×
284
    case Op::UMOD: return os << "%";
×
285
    case Op::SMOD: return os << "s%";
×
286
    case Op::OR: return os << "|";
×
287
    case Op::AND: return os << "&";
30✔
288
    case Op::LSH: return os << "<<";
12✔
289
    case Op::RSH: return os << ">>";
×
290
    case Op::ARSH: return os << ">>>";
2✔
291
    case Op::XOR: return os << "^";
2✔
292
    }
293
    assert(false);
294
    return os;
295
}
296

297
std::ostream& operator<<(std::ostream& os, const Condition::Op op) {
222✔
298
    using Op = Condition::Op;
111✔
299
    switch (op) {
222✔
300
    case Op::EQ: return os << "==";
38✔
301
    case Op::NE: return os << "!=";
10✔
302
    case Op::SET: return os << "&==";
×
303
    case Op::NSET: return os << "&!="; // not in ebpf
×
304
    case Op::LT: return os << "<";     // TODO: os << "u<";
124✔
305
    case Op::LE: return os << "<=";    // TODO: os << "u<=";
12✔
306
    case Op::GT: return os << ">";     // TODO: os << "u>";
22✔
307
    case Op::GE: return os << ">=";    // TODO: os << "u>=";
6✔
308
    case Op::SLT: return os << "s<";
4✔
309
    case Op::SLE: return os << "s<=";
×
310
    case Op::SGT: return os << "s>";
4✔
311
    case Op::SGE: return os << "s>=";
2✔
312
    }
313
    assert(false);
314
    return os;
315
}
316

317
static string size(const int w, const bool is_signed = false) {
184✔
318
    return string(is_signed ? "s" : "u") + std::to_string(w * 8);
552✔
319
}
320

321
// ReSharper disable CppMemberFunctionMayBeConst
322
struct AssertionPrinterVisitor {
323
    std::ostream& _os;
324

325
    void operator()(ValidStore const& a) {
2✔
326
        _os << a.mem << ".type != stack -> " << TypeConstraint{a.val, TypeGroup::number};
2✔
327
    }
2✔
328

329
    void operator()(ValidAccess const& a) {
318✔
330
        if (a.or_null) {
318✔
331
            _os << "(" << TypeConstraint{a.reg, TypeGroup::number} << " and " << a.reg << ".value == 0) or ";
4✔
332
        }
333
        _os << "valid_access(" << a.reg << ".offset";
318✔
334
        if (a.offset > 0) {
318✔
335
            _os << "+" << a.offset;
46✔
336
        } else if (a.offset < 0) {
272✔
337
            _os << a.offset;
6✔
338
        }
339

340
        if (a.width == Value{Imm{0}}) {
318✔
341
            // a.width == 0, meaning we only care it's an in-bound pointer,
342
            // so it can be compared with another pointer to the same region.
343
            _os << ") for comparison/subtraction";
4✔
344
        } else {
345
            _os << ", width=" << a.width << ") for ";
314✔
346
            if (a.access_type == AccessType::read) {
314✔
347
                _os << "read";
278✔
348
            } else {
349
                _os << "write";
36✔
350
            }
351
        }
352
    }
318✔
353

354
    void operator()(const BoundedLoopCount& a) {
20✔
355
        _os << variable_registry->loop_counter(to_string(a.name)) << " < " << a.limit;
20✔
356
    }
20✔
357

358
    void operator()(ValidSize const& a) {
2✔
359
        const auto op = a.can_be_zero ? " >= " : " > ";
2✔
360
        _os << a.reg << ".value" << op << 0;
2✔
361
    }
2✔
362

363
    void operator()(ValidCall const& a) {
2✔
364
        const EbpfHelperPrototype proto = thread_local_program_info->platform->get_helper_prototype(a.func);
2✔
365
        _os << "valid call(" << proto.name << ")";
2✔
366
    }
2✔
367

368
    void operator()(ValidMapKeyValue const& a) {
42✔
369
        _os << "within(" << a.access_reg << ":" << (a.key ? "key_size" : "value_size") << "(" << a.map_fd_reg << "))";
51✔
370
    }
42✔
371

372
    void operator()(ZeroCtxOffset const& a) {
2✔
373
        _os << variable_registry->reg(DataKind::ctx_offsets, a.reg.v) << " == 0";
2✔
374
    }
2✔
375

376
    void operator()(Comparable const& a) {
10✔
377
        if (a.or_r2_is_number) {
10✔
378
            _os << TypeConstraint{a.r2, TypeGroup::number} << " or ";
15✔
379
        }
380
        _os << variable_registry->type_reg(a.r1.v) << " == " << variable_registry->type_reg(a.r2.v) << " in "
10✔
381
            << TypeGroup::singleton_ptr;
10✔
382
    }
10✔
383

384
    void operator()(Addable const& a) {
2✔
385
        _os << TypeConstraint{a.ptr, TypeGroup::pointer} << " -> " << TypeConstraint{a.num, TypeGroup::number};
4✔
386
    }
2✔
387

388
    void operator()(ValidDivisor const& a) { _os << a.reg << " != 0"; }
76✔
389

390
    void operator()(TypeConstraint const& tc) {
220✔
391
        const string cmp_op = is_singleton_type(tc.types) ? "==" : "in";
239✔
392
        _os << variable_registry->type_reg(tc.reg.v) << " " << cmp_op << " " << tc.types;
220✔
393
    }
220✔
394

395
    void operator()(FuncConstraint const& fc) { _os << variable_registry->type_reg(fc.reg.v) << " is helper"; }
6✔
396
};
397

398
// ReSharper disable CppMemberFunctionMayBeConst
399
struct CommandPrinterVisitor {
400
    std::ostream& os_;
401

402
    void visit(const auto& item) { std::visit(*this, item); }
403

404
    void operator()(Undefined const& a) { os_ << "Undefined{" << a.opcode << "}"; }
×
405

406
    void operator()(LoadMapFd const& b) { os_ << b.dst << " = map_fd " << b.mapfd; }
26✔
407

408
    void operator()(LoadMapAddress const& b) { os_ << b.dst << " = map_val(" << b.mapfd << ") + " << b.offset; }
×
409

410
    void operator()(LoadPseudo const& b) {
×
411
        os_ << b.dst << " = ";
×
412
        switch (b.addr.kind) {
×
413
        case PseudoAddress::Kind::VARIABLE_ADDR: os_ << "variable_addr(" << b.addr.imm << ")"; break;
×
414
        case PseudoAddress::Kind::CODE_ADDR: os_ << "code_addr(" << b.addr.imm << ")"; break;
×
415
        case PseudoAddress::Kind::MAP_BY_IDX: os_ << "map_by_idx(" << b.addr.imm << ")"; break;
×
416
        case PseudoAddress::Kind::MAP_VALUE_BY_IDX:
×
417
            os_ << "mva(map_by_idx(" << b.addr.imm << ")) + " << b.addr.next_imm;
×
418
            break;
×
419
        }
420
    }
×
421

422
    // llvm-objdump uses "w<number>" for 32-bit operations and "r<number>" for 64-bit operations.
423
    // We use the same convention here for consistency.
424
    static std::string reg_name(Reg const& a, const bool is64) { return ((is64) ? "r" : "w") + std::to_string(a.v); }
420✔
425

426
    void operator()(Bin const& b) {
280✔
427
        os_ << reg_name(b.dst, b.is64) << " " << b.op << "= " << b.v;
420✔
428
        if (b.lddw) {
280✔
429
            os_ << " ll";
2✔
430
        }
431
    }
280✔
432

433
    void operator()(Un const& b) {
12✔
434
        os_ << b.dst << " = ";
12✔
435
        switch (b.op) {
12✔
436
        case Un::Op::BE16: os_ << "be16 "; break;
2✔
437
        case Un::Op::BE32: os_ << "be32 "; break;
2✔
438
        case Un::Op::BE64: os_ << "be64 "; break;
2✔
439
        case Un::Op::LE16: os_ << "le16 "; break;
2✔
440
        case Un::Op::LE32: os_ << "le32 "; break;
2✔
441
        case Un::Op::LE64: os_ << "le64 "; break;
2✔
442
        case Un::Op::SWAP16: os_ << "swap16 "; break;
×
443
        case Un::Op::SWAP32: os_ << "swap32 "; break;
×
444
        case Un::Op::SWAP64: os_ << "swap64 "; break;
×
445
        case Un::Op::NEG: os_ << "-"; break;
×
446
        }
447
        os_ << b.dst;
12✔
448
    }
12✔
449

450
    void operator()(Call const& call) {
70✔
451
        os_ << "r0 = " << call.name << ":" << call.func << "(";
70✔
452
        for (uint8_t r = 1; r <= 5; r++) {
144✔
453
            // Look for a singleton.
454
            auto single = std::ranges::find_if(call.singles, [r](const ArgSingle arg) { return arg.reg.v == r; });
372✔
455
            if (single != call.singles.end()) {
144✔
456
                if (r > 1) {
74✔
457
                    os_ << ", ";
48✔
458
                }
459
                os_ << *single;
74✔
460
                continue;
74✔
461
            }
462

463
            // Look for the start of a pair.
464
            auto pair = std::ranges::find_if(call.pairs, [r](const ArgPair arg) { return arg.mem.v == r; });
70✔
465
            if (pair != call.pairs.end()) {
70✔
466
                if (r > 1) {
×
467
                    os_ << ", ";
×
468
                }
469
                os_ << *pair;
×
470
                r++;
×
471
                continue;
×
472
            }
473

474
            // Not found.
475
            break;
70✔
476
        }
477
        os_ << ")";
70✔
478
    }
70✔
479

480
    void operator()(CallLocal const& call) { os_ << "call <" << to_string(call.target) << ">"; }
×
481

482
    void operator()(Callx const& callx) { os_ << "callx " << callx.func; }
×
483

484
    void operator()(CallBtf const& call) { os_ << "call_btf " << call.btf_id; }
×
485

486
    void operator()(Exit const& b) { os_ << "exit"; }
50✔
487

488
    void operator()(Jmp const& b) {
×
489
        // A "standalone" jump Instruction.
490
        // Print the label without offset calculations.
491
        if (b.cond) {
×
492
            os_ << "if ";
×
493
            print(*b.cond);
×
494
            os_ << " ";
×
495
        }
496
        os_ << "goto label <" << to_string(b.target) << ">";
×
497
    }
×
498

499
    void operator()(Jmp const& b, const int offset) {
54✔
500
        const string sign = offset > 0 ? "+" : "";
54✔
501
        const string target = sign + std::to_string(offset) + " <" + to_string(b.target) + ">";
108✔
502

503
        if (b.cond) {
54✔
504
            os_ << "if ";
40✔
505
            print(*b.cond);
40✔
506
            os_ << " ";
40✔
507
        }
508
        os_ << "goto " << target;
54✔
509
    }
54✔
510

511
    void operator()(Packet const& b) {
×
512
        /* Direct packet access, R0 = *(uint *) (skb->data + imm32) */
513
        /* Indirect packet access, R0 = *(uint *) (skb->data + src_reg + imm32) */
514
        const string s = size(b.width);
×
515
        os_ << "r0 = ";
×
516
        os_ << "*(" << s << " *)skb[";
×
517
        if (b.regoffset) {
×
518
            os_ << *b.regoffset;
×
519
        }
520
        if (b.offset != 0) {
×
521
            if (b.regoffset) {
×
522
                os_ << " + ";
×
523
            }
524
            os_ << b.offset;
×
525
        }
526
        os_ << "]";
×
527
    }
×
528

529
    void print(Deref const& access) {
184✔
530
        const string sign = access.offset < 0 ? " - " : " + ";
212✔
531
        const int offset = std::abs(access.offset); // what about INT_MIN?
184✔
532
        os_ << "*(" << size(access.width) << " *)";
276✔
533
        os_ << "(" << access.basereg << sign << offset << ")";
184✔
534
    }
184✔
535

536
    void print(Condition const& cond) {
222✔
537
        os_ << cond.left << " " << ((!cond.is64) ? "w" : "") << cond.op << " " << cond.right;
261✔
538
    }
222✔
539

540
    void operator()(Mem const& b) {
184✔
541
        if (b.is_load) {
184✔
542
            os_ << b.value << " = ";
44✔
543
        }
544
        if (b.is_load && b.is_signed) {
184✔
545
            const string sign = b.access.offset < 0 ? " - " : " + ";
×
546
            const int offset = std::abs(b.access.offset);
×
547
            os_ << "*(" << size(b.access.width, true) << " *)";
×
548
            os_ << "(" << b.access.basereg << sign << offset << ")";
×
549
        } else {
×
550
            print(b.access);
184✔
551
        }
552
        if (!b.is_load) {
184✔
553
            os_ << " = " << b.value;
140✔
554
        }
555
    }
184✔
556

557
    void operator()(Atomic const& b) {
×
558
        os_ << "lock ";
×
559
        print(b.access);
×
560
        os_ << " ";
×
561
        bool showfetch = true;
×
562
        switch (b.op) {
×
563
        case Atomic::Op::ADD: os_ << "+"; break;
×
564
        case Atomic::Op::OR: os_ << "|"; break;
×
565
        case Atomic::Op::AND: os_ << "&"; break;
×
566
        case Atomic::Op::XOR: os_ << "^"; break;
×
567
        case Atomic::Op::XCHG:
×
568
            os_ << "x";
×
569
            showfetch = false;
×
570
            break;
×
571
        case Atomic::Op::CMPXCHG:
×
572
            os_ << "cx";
×
573
            showfetch = false;
×
574
            break;
×
575
        }
576
        os_ << "= " << b.valreg;
×
577

578
        if (showfetch && b.fetch) {
×
579
            os_ << " fetch";
×
580
        }
581
    }
×
582

583
    void operator()(Assume const& b) {
182✔
584
        os_ << "assume ";
182✔
585
        print(b.cond);
182✔
586
    }
182✔
587

588
    void operator()(IncrementLoopCounter const& a) {
×
589
        os_ << variable_registry->loop_counter(to_string(a.name)) << "++";
×
590
    }
×
591
};
592
// ReSharper restore CppMemberFunctionMayBeConst
593

594
std::ostream& operator<<(std::ostream& os, Instruction const& ins) {
188✔
595
    std::visit(CommandPrinterVisitor{os}, ins);
94✔
596
    return os;
94✔
597
}
598

599
string to_string(Instruction const& ins) {
184✔
600
    std::stringstream str;
184✔
601
    str << ins;
184✔
602
    return str.str();
368✔
603
}
184✔
604

605
std::ostream& operator<<(std::ostream& os, const Assertion& a) {
702✔
606
    std::visit(AssertionPrinterVisitor{os}, a);
360✔
607
    return os;
360✔
608
}
609

610
string to_string(Assertion const& constraint) {
682✔
611
    std::stringstream str;
682✔
612
    str << constraint;
682✔
613
    return str.str();
1,364✔
614
}
682✔
615

616
auto get_labels(const InstructionSeq& insts) {
38✔
617
    Pc pc = 0;
38✔
618
    std::map<Label, Pc> pc_of_label;
38✔
619
    for (const auto& [label, inst, _] : insts) {
708✔
620
        pc_of_label[label] = pc;
670✔
621
        pc += size(inst);
670✔
622
    }
623
    return pc_of_label;
38✔
624
}
×
625

626
void print(const InstructionSeq& insts, std::ostream& out, const std::optional<const Label>& label_to_print,
38✔
627
           const bool print_line_info) {
628
    const auto pc_of_label = get_labels(insts);
38✔
629
    Pc pc = 0;
38✔
630
    std::string previous_source;
38✔
631
    CommandPrinterVisitor visitor{out};
38✔
632
    for (const LabeledInstruction& labeled_inst : insts) {
708✔
633
        const auto& [label, ins, line_info] = labeled_inst;
670✔
634
        if (!label_to_print.has_value() || label == label_to_print) {
670✔
635
            if (line_info.has_value() && print_line_info) {
670✔
636
                auto& [file, source, line, column] = line_info.value();
×
637
                // Only decorate the first instruction associated with a source line.
638
                if (source != previous_source) {
×
639
                    out << line_info.value();
×
640
                    previous_source = source;
×
641
                }
642
            }
643
            if (label.isjump()) {
670✔
644
                out << "\n";
×
645
                out << label << ":\n";
×
646
            }
647
            if (label_to_print.has_value()) {
670✔
648
                out << pc << ": ";
×
649
            } else {
650
                out << std::setw(8) << pc << ":\t";
670✔
651
            }
652
            if (const auto jmp = std::get_if<Jmp>(&ins)) {
670✔
653
                if (!pc_of_label.contains(jmp->target)) {
54✔
654
                    throw std::runtime_error(string("Cannot find label ") + to_string(jmp->target));
×
655
                }
656
                const Pc target_pc = pc_of_label.at(jmp->target);
54✔
657
                visitor(*jmp, target_pc - static_cast<int>(pc) - 1);
54✔
658
            } else {
659
                std::visit(visitor, ins);
616✔
660
            }
661
            out << "\n";
670✔
662
        }
663
        pc += size(ins);
670✔
664
    }
665
}
38✔
666

667
std::ostream& operator<<(std::ostream& o, const EbpfMapDescriptor& desc) {
28✔
668
    return o << "(" << "original_fd = " << desc.original_fd << ", " << "inner_map_fd = " << desc.inner_map_fd << ", "
28✔
669
             << "type = " << desc.type << ", " << "max_entries = " << desc.max_entries << ", "
28✔
670
             << "value_size = " << desc.value_size << ", " << "key_size = " << desc.key_size << ")";
28✔
671
}
672

673
void print_map_descriptors(const std::vector<EbpfMapDescriptor>& descriptors, std::ostream& o) {
38✔
674
    int i = 0;
38✔
675
    for (const auto& desc : descriptors) {
66✔
676
        o << "map " << i << ":" << desc << "\n";
28✔
677
        i++;
28✔
678
    }
679
}
38✔
680

681
std::ostream& operator<<(std::ostream& os, const btf_line_info_t& line_info) {
×
682
    os << "; " << line_info.file_name << ":" << line_info.line_number << "\n";
×
683
    os << "; " << line_info.source_line << "\n";
×
684
    return os;
×
685
}
686

687
void print_invariants_filtered(std::ostream& os, const Program& prog, const bool simplify, const AnalysisResult& result,
2✔
688
                               const std::set<Label>& filter, const bool compact,
689
                               const std::map<Label, RelevantState>* relevance) {
690
    DetailedPrinter printer{os, prog};
2✔
691
    const auto basic_blocks = BasicBlock::collect_basic_blocks(prog.cfg(), simplify);
2✔
692

693
    // Build a mapping from each label in a basic block to the block's first label.
694
    // Needed to look up post-invariants for mid-block predecessor labels at join points.
695
    std::map<Label, Label> label_to_block_leader;
2✔
696
    for (const BasicBlock& bb : basic_blocks) {
28✔
697
        for (const Label& label : bb) {
52✔
698
            label_to_block_leader.insert({label, bb.first_label()});
52✔
699
        }
700
    }
701

702
    // Helper to look up the post-invariant for a predecessor label.
703
    // Mid-block labels don't have direct invariant entries, so we map
704
    // through the block leader to find the containing block's post-state.
705
    // Note: when simplify=true, the block leader's post represents the
706
    // entire collapsed block, which is correct for the last instruction
707
    // but approximate for mid-block predecessors. Failure slicing defaults
708
    // to simplify=false, so this approximation is rarely triggered.
709
    auto get_parent_post_invariant = [&](const Label& parent) -> const EbpfDomain* {
1✔
NEW
710
        const auto leader_it = label_to_block_leader.find(parent);
×
NEW
711
        const Label& lookup_label = (leader_it != label_to_block_leader.end()) ? leader_it->second : parent;
×
NEW
712
        const auto inv_it = result.invariants.find(lookup_label);
×
NEW
713
        if (inv_it != result.invariants.end() && !inv_it->second.post.is_bottom()) {
×
714
            return &inv_it->second.post;
715
        }
716
        return nullptr;
717
    };
2✔
718

719
    for (const BasicBlock& bb : basic_blocks) {
28✔
720
        // Check if any label in this basic block is in the filter
721
        bool bb_has_filtered_label = false;
26✔
722
        for (const Label& label : bb) {
48✔
723
            if (filter.contains(label)) {
26✔
724
                bb_has_filtered_label = true;
2✔
725
                break;
2✔
726
            }
727
        }
728
        if (!bb_has_filtered_label) {
26✔
729
            continue;
22✔
730
        }
731

732
        // Find the first filtered label in this block to use as the block header
733
        Label first_filtered_label = bb.first_label();
4✔
734
        for (const Label& label : bb) {
4✔
735
            if (filter.contains(label)) {
4✔
736
                first_filtered_label = label;
4✔
737
                break;
2✔
738
            }
739
        }
740

741
        // Use bb.first_label() for reachability check: if the block's entry is unreachable,
742
        // skip the entire block. The filtered label's pre-invariant is printed below.
743
        if (result.invariants.at(bb.first_label()).pre.is_bottom()) {
6✔
NEW
744
            continue;
×
745
        }
746

747
        // Print pre-invariant for first filtered label in block (unless compact)
748
        if (!compact) {
4✔
749
            // Set invariant filter if we have relevance info for this label
750
            const auto* label_relevance =
2✔
751
                relevance ? (relevance->contains(first_filtered_label) ? &relevance->at(first_filtered_label) : nullptr)
4✔
752
                          : nullptr;
4✔
753
            os << invariant_filter(label_relevance);
4✔
754
            os << "\nPre-invariant : " << result.invariants.at(first_filtered_label).pre << "\n";
4✔
755
            os << invariant_filter(nullptr); // Clear filter
4✔
756
        }
757

758
        // Print the jump and block header anchored to the basic block entry label
759
        // for correct CFG structure representation.
760
        printer.print_jump("from", bb.first_label());
10✔
761
        os << bb.first_label() << ":\n";
4✔
762

763
        // R3: Show per-predecessor invariants at join points.
764
        // When multiple predecessors exist and at least 2 are in the slice,
765
        // show what each incoming edge contributed to help diagnose lost correlations.
766
        if (!compact && relevance) {
4✔
767
            const auto parents = prog.cfg().parents_of(bb.first_label());
4✔
768
            std::vector<Label> in_slice_parents;
4✔
769
            for (const auto& parent : parents) {
8✔
770
                if (filter.contains(parent)) {
4✔
771
                    in_slice_parents.push_back(parent);
2✔
772
                }
773
            }
774
            if (in_slice_parents.size() >= 2) {
4✔
775
                // Build the union of relevant registers from this label and all in-slice parents
NEW
776
                RelevantState join_relevance;
×
NEW
777
                if (relevance->contains(first_filtered_label)) {
×
NEW
778
                    const auto& fl = relevance->at(first_filtered_label);
×
NEW
779
                    join_relevance.registers.insert(fl.registers.begin(), fl.registers.end());
×
NEW
780
                    join_relevance.stack_offsets.insert(fl.stack_offsets.begin(), fl.stack_offsets.end());
×
781
                }
NEW
782
                for (const auto& parent : in_slice_parents) {
×
NEW
783
                    if (relevance->contains(parent)) {
×
NEW
784
                        const auto& pr = relevance->at(parent);
×
NEW
785
                        join_relevance.registers.insert(pr.registers.begin(), pr.registers.end());
×
NEW
786
                        join_relevance.stack_offsets.insert(pr.stack_offsets.begin(), pr.stack_offsets.end());
×
787
                    }
788
                }
789

NEW
790
                os << "  --- join point: per-predecessor state ---\n";
×
NEW
791
                for (const auto& parent : in_slice_parents) {
×
NEW
792
                    const auto* post = get_parent_post_invariant(parent);
×
NEW
793
                    if (post) {
×
NEW
794
                        os << invariant_filter(&join_relevance);
×
NEW
795
                        os << "  from " << parent << ": " << *post << "\n";
×
NEW
796
                        os << invariant_filter(nullptr);
×
797
                    }
798
                }
NEW
799
                os << "  --- end join point ---\n";
×
NEW
800
            }
×
801
        }
4✔
802

803
        if (first_filtered_label != bb.first_label()) {
6✔
804
            // Indicate that some labels/instructions were skipped due to filtering.
NEW
805
            os << "  ... skipped ...\n";
×
806
        }
807

808
        Label last_label = bb.first_label();
4✔
809
        Label prev_filtered_label = bb.first_label();
4✔
810
        bool has_prev_filtered = false;
4✔
811
        for (const Label& label : bb) {
8✔
812
            if (!filter.contains(label)) {
4✔
NEW
813
                continue;
×
814
            }
815

816
            // If there was a gap since the previous filtered label in this block,
817
            // close the previous label's output and show a skip indicator.
818
            if (has_prev_filtered && prev_filtered_label != label) {
4✔
819
                // Print post-invariant and goto for the previous filtered label
NEW
820
                if (!compact) {
×
NEW
821
                    const auto& prev_current = result.invariants.at(prev_filtered_label);
×
NEW
822
                    if (!prev_current.post.is_bottom()) {
×
823
                        const auto* prev_label_relevance =
NEW
824
                            relevance ? (relevance->contains(prev_filtered_label) ? &relevance->at(prev_filtered_label)
×
825
                                                                                  : nullptr)
NEW
826
                                      : nullptr;
×
NEW
827
                        os << invariant_filter(prev_label_relevance);
×
NEW
828
                        printer.print_jump("goto", prev_filtered_label);
×
NEW
829
                        os << "\nPost-invariant : " << prev_current.post << "\n";
×
NEW
830
                        os << invariant_filter(nullptr);
×
831
                    }
832
                }
833
                // Check if there are skipped labels between prev and current
NEW
834
                bool has_gap = false;
×
NEW
835
                for (const Label& mid : bb) {
×
NEW
836
                    if (mid <= prev_filtered_label) {
×
NEW
837
                        continue;
×
838
                    }
NEW
839
                    if (mid >= label) {
×
840
                        break;
841
                    }
NEW
842
                    has_gap = true;
×
NEW
843
                    break;
×
844
                }
NEW
845
                if (has_gap) {
×
NEW
846
                    os << "  ... skipped ...\n";
×
847
                }
848
                // Print pre-invariant for this label
NEW
849
                if (!compact) {
×
850
                    const auto* label_rel =
NEW
851
                        relevance ? (relevance->contains(label) ? &relevance->at(label) : nullptr) : nullptr;
×
NEW
852
                    os << invariant_filter(label_rel);
×
NEW
853
                    os << "\nPre-invariant : " << result.invariants.at(label).pre << "\n";
×
NEW
854
                    os << invariant_filter(nullptr);
×
NEW
855
                    printer.print_jump("from", label);
×
856
                }
857
            }
858

859
            printer.print_line_info(label);
4✔
860

861
            // Print assertions, filtered by relevance if provided
862
            const auto* label_relevance =
2✔
863
                relevance ? (relevance->contains(label) ? &relevance->at(label) : nullptr) : nullptr;
4✔
864
            for (const auto& assertion : prog.assertions_at(label)) {
6✔
865
                // If we have relevance info, only print assertions involving relevant registers.
866
                // Assertions with no register deps (e.g., ValidCall, BoundedLoopCount) are always
867
                // printed to avoid hiding the failing assertion from the slice output.
868
                if (label_relevance) {
2✔
869
                    auto assertion_regs = extract_assertion_registers(assertion);
2✔
870
                    if (!assertion_regs.empty()) {
2✔
871
                        bool is_relevant = false;
2✔
872
                        for (const auto& reg : assertion_regs) {
2✔
873
                            if (label_relevance->registers.contains(reg)) {
2✔
874
                                is_relevant = true;
1✔
875
                                break;
1✔
876
                            }
877
                        }
878
                        if (!is_relevant) {
2✔
NEW
879
                            continue; // Skip this assertion
×
880
                        }
881
                    }
882
                }
2✔
883
                os << "  assert " << assertion << ";\n";
2✔
884
            }
4✔
885
            os << "  " << prog.instruction_at(label) << ";\n";
4✔
886

887
            last_label = label;
4✔
888
            prev_filtered_label = label;
4✔
889
            has_prev_filtered = true;
4✔
890

891
            const auto& current = result.invariants.at(label);
4✔
892
            if (current.error) {
4✔
893
                os << "\nVerification error:\n";
2✔
894
                print_error(os, *current.error);
2✔
895
                os << "\n";
2✔
896
            }
897
        }
898

899
        // Print post-invariant (unless compact)
900
        if (!compact) {
4✔
901
            const auto& current = result.invariants.at(last_label);
4✔
902
            if (!current.post.is_bottom()) {
4✔
903
                // Set invariant filter for post-invariant
904
                const auto* label_relevance =
1✔
905
                    relevance ? (relevance->contains(last_label) ? &relevance->at(last_label) : nullptr) : nullptr;
2✔
906
                os << invariant_filter(label_relevance);
2✔
907
                printer.print_jump("goto", last_label);
2✔
908
                os << "\nPost-invariant : " << current.post << "\n";
2✔
909
                os << invariant_filter(nullptr); // Clear filter
2✔
910
            }
911
        }
912
    }
8✔
913
    os << "\n";
2✔
914
}
2✔
915

916
void print_failure_slices(std::ostream& os, const Program& prog, const bool simplify, const AnalysisResult& result,
2✔
917
                          const std::vector<FailureSlice>& slices, const bool compact) {
918
    if (slices.empty()) {
2✔
NEW
919
        os << "No verification failures found.\n";
×
NEW
920
        return;
×
921
    }
922

923
    for (size_t i = 0; i < slices.size(); ++i) {
4✔
924
        const auto& slice = slices[i];
2✔
925

926
        os << "=== Failure Slice " << (i + 1) << " of " << slices.size() << " ===\n\n";
2✔
927

928
        // Print error summary
929
        os << "[ERROR] " << slice.error.what() << "\n";
2✔
930
        os << "[LOCATION] " << slice.failing_label << "\n";
2✔
931

932
        // Print relevant registers at failure point
933
        const auto it = slice.relevance.find(slice.failing_label);
2✔
934
        if (it != slice.relevance.end()) {
2✔
935
            os << "[RELEVANT REGISTERS] ";
2✔
936
            bool first = true;
2✔
937
            for (const auto& reg : it->second.registers) {
4✔
938
                if (!first) {
2✔
NEW
939
                    os << ", ";
×
940
                }
941
                os << "r" << static_cast<int>(reg.v);
2✔
942
                first = false;
2✔
943
            }
944
            if (!it->second.stack_offsets.empty()) {
2✔
NEW
945
                for (const auto& offset : it->second.stack_offsets) {
×
NEW
946
                    if (!first) {
×
NEW
947
                        os << ", ";
×
948
                    }
NEW
949
                    os << "stack[" << offset << "]";
×
NEW
950
                    first = false;
×
951
                }
952
            }
953
            os << "\n";
2✔
954
        }
955

956
        os << "[SLICE SIZE] " << slice.relevance.size() << " program points\n\n";
2✔
957

958
        // Print a compact control-flow summary showing the branch-path skeleton
959
        // through the slice. Lists labels in order with Assume/Jmp annotations.
960
        // At join points (labels with ≥2 in-slice predecessors), the converging
961
        // predecessors are grouped as {pred1 | pred2} → join_label.
962
        {
1✔
963
            os << "[CONTROL FLOW] ";
2✔
964
            // Collect and sort impacted labels
965
            auto labels = slice.impacted_labels();
2✔
966

967
            // Build a map: join_label → set of in-slice predecessors
968
            // Also collect which labels are convergence predecessors (to skip them in linear output)
969
            std::map<Label, std::vector<Label>> join_predecessors;
2✔
970
            for (const auto& lbl : labels) {
6✔
971
                const auto& parents = prog.cfg().parents_of(lbl);
4✔
972
                std::vector<Label> in_slice_parents;
4✔
973
                for (const auto& p : parents) {
8✔
974
                    if (labels.contains(p)) {
4✔
975
                        in_slice_parents.push_back(p);
2✔
976
                    }
977
                }
978
                if (in_slice_parents.size() >= 2) {
4✔
NEW
979
                    join_predecessors[lbl] = in_slice_parents;
×
980
                }
981
            }
4✔
982
            // Labels consumed by a {..|..} group are skipped in linear output,
983
            // unless they are themselves join points (nested joins).
984
            std::set<Label> convergence_members;
2✔
985
            for (const auto& [join_lbl, preds] : join_predecessors) {
2✔
NEW
986
                for (const auto& p : preds) {
×
NEW
987
                    if (!join_predecessors.contains(p)) {
×
NEW
988
                        convergence_members.insert(p);
×
989
                    }
990
                }
991
            }
992

993
            // Helper to annotate a label with its instruction type
994
            auto annotate_label = [&](const Label& lbl) {
5✔
995
                os << lbl;
4✔
996
                const auto& ins = prog.instruction_at(lbl);
4✔
997
                if (const auto* assume = std::get_if<Assume>(&ins)) {
4✔
NEW
998
                    os << " (assume " << assume->cond.left << " " << assume->cond.op << " " << assume->cond.right
×
NEW
999
                       << ")";
×
1000
                } else if (const auto* jmp = std::get_if<Jmp>(&ins)) {
4✔
NEW
1001
                    if (jmp->cond) {
×
NEW
1002
                        os << " (if " << jmp->cond->left << " " << jmp->cond->op << " " << jmp->cond->right << ")";
×
1003
                    }
1004
                }
1005
            };
6✔
1006

1007
            bool first_cf = true;
2✔
1008
            for (const auto& lbl : labels) {
6✔
1009
                // Skip labels that are part of a convergence group (printed with their join)
1010
                if (convergence_members.contains(lbl)) {
4✔
NEW
1011
                    continue;
×
1012
                }
1013

1014
                if (!first_cf) {
4✔
1015
                    os << ", ";
2✔
1016
                }
1017
                first_cf = false;
4✔
1018

1019
                // If this label is a join point, print {pred1 | pred2} → lbl
1020
                if (join_predecessors.contains(lbl)) {
4✔
NEW
1021
                    os << "{";
×
NEW
1022
                    bool first_pred = true;
×
NEW
1023
                    for (const auto& pred : join_predecessors.at(lbl)) {
×
NEW
1024
                        if (!first_pred) {
×
NEW
1025
                            os << " | ";
×
1026
                        }
NEW
1027
                        first_pred = false;
×
NEW
1028
                        annotate_label(pred);
×
1029
                    }
NEW
1030
                    os << "} -> ";
×
1031
                }
1032

1033
                annotate_label(lbl);
4✔
1034
            }
1035
            if (labels.contains(slice.failing_label)) {
2✔
1036
                os << " FAIL";
2✔
1037
            }
1038
            os << "\n\n";
2✔
1039
        }
2✔
1040

1041
        // Print the filtered CFG with assertion filtering based on relevance
1042
        os << "[CAUSAL TRACE]\n";
2✔
1043
        print_invariants_filtered(os, prog, simplify, result, slice.impacted_labels(), compact, &slice.relevance);
2✔
1044

1045
        if (i + 1 < slices.size()) {
2✔
NEW
1046
            os << "\n";
×
1047
        }
1048
    }
1049
}
1050

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