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

Alan-Jowett / ebpf-verifier / 22075299923

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

push

github

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

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

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

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

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

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

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

52 existing lines in 4 files now uncovered.

9389 of 10777 relevant lines covered (87.12%)

3125310.38 hits per line

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

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

10
#include "arith/num_big.hpp"
11
#include "arith/variable.hpp"
12
#include "cfg/cfg.hpp"
13
#include "crab/interval.hpp"
14
#include "crab/type_encoding.hpp"
15
#include "crab/var_registry.hpp"
16
#include "ir/syntax.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) {
702✔
28
    if (interval.is_bottom()) {
702✔
29
        o << "_|_";
×
30
    } else {
31
        o << "[" << interval._lb << ", " << interval._ub << "]";
702✔
32
    }
33
    return o;
702✔
34
}
35
static std::string int128_to_string(Int128 n) {
311,304✔
36
    if (n == 0) {
311,304✔
37
        return "0";
1,185✔
38
    }
39
    bool negative = false;
310,514✔
40
    if (n < 0) {
310,514✔
41
        negative = true;
414✔
42
        // Handle kInt128Min: negate via unsigned to avoid overflow.
43
        if (n == kInt128Min) {
414✔
44
            // kInt128Min == -170141183460469231731687303715884105728
45
            // Build the string for the absolute value via unsigned arithmetic.
NEW
46
            auto u = static_cast<UInt128>(n);
×
47
            // Two's complement: UInt128(kInt128Min) is the correct magnitude.
NEW
48
            std::string result;
×
NEW
49
            while (u != 0) {
×
NEW
50
                result += static_cast<char>('0' + static_cast<int>(u % 10));
×
NEW
51
                u /= 10;
×
52
            }
NEW
53
            result += '-';
×
NEW
54
            std::ranges::reverse(result);
×
NEW
55
            return result;
×
NEW
56
        }
×
57
        n = -n;
414✔
58
    }
59
    std::string result;
310,514✔
60
    while (n > 0) {
1,552,326✔
61
        result += static_cast<char>('0' + static_cast<int>(n % 10));
1,241,812✔
62
        n /= 10;
1,241,812✔
63
    }
64
    if (negative) {
310,514✔
65
        result += '-';
414✔
66
    }
67
    std::ranges::reverse(result);
310,514✔
68
    return result;
310,514✔
69
}
310,514✔
70

71
std::ostream& operator<<(std::ostream& o, const Number& z) { return o << z.to_string(); }
311,304✔
72

73
std::string Number::to_string() const { return int128_to_string(_n); }
311,304✔
74

75
std::string Interval::to_string() const {
×
76
    std::ostringstream s;
×
77
    s << *this;
×
78
    return s.str();
×
79
}
×
80

81
std::ostream& operator<<(std::ostream& os, const Label& label) {
1,632✔
82
    if (label == Label::entry) {
1,632✔
83
        return os << "entry";
6✔
84
    }
85
    if (label == Label::exit) {
1,626✔
86
        return os << "exit";
10✔
87
    }
88
    if (!label.stack_frame_prefix.empty()) {
1,616✔
89
        os << label.stack_frame_prefix << STACK_FRAME_DELIMITER;
330✔
90
    }
91
    os << label.from;
1,616✔
92
    if (label.to != -1) {
1,616✔
93
        os << ":" << label.to;
50✔
94
    }
95
    if (!label.special_label.empty()) {
1,616✔
96
        os << " (" << label.special_label << ")";
22✔
97
    }
98
    return os;
808✔
99
}
100

101
string to_string(Label const& label) {
1,270✔
102
    std::stringstream str;
1,270✔
103
    str << label;
1,270✔
104
    return str.str();
2,540✔
105
}
1,270✔
106

107
struct LineInfoPrinter {
108
    std::ostream& os;
109
    std::string previous_source_line;
110

111
    void print_line_info(const Label& label) {
×
112
        if (thread_local_options.verbosity_opts.print_line_info) {
×
113
            const auto& line_info_map = thread_local_program_info.get().line_info;
×
114
            const auto& line_info = line_info_map.find(label.from);
×
115
            // Print line info only once.
116
            if (line_info != line_info_map.end() && line_info->second.source_line != previous_source_line) {
×
117
                os << "\n" << line_info->second << "\n";
×
118
                previous_source_line = line_info->second.source_line;
×
119
            }
120
        }
121
    }
×
122
};
123

124
struct DetailedPrinter : LineInfoPrinter {
125
    const Program& prog;
126

127
    DetailedPrinter(std::ostream& os, const Program& prog) : LineInfoPrinter{os}, prog(prog) {}
×
128

129
    void print_labels(const std::string& direction, const std::set<Label>& labels) {
×
130
        auto [it, et] = std::pair{labels.begin(), labels.end()};
×
131
        if (it != et) {
×
132
            os << "  " << direction << " ";
×
133
            while (it != et) {
×
134
                os << *it;
×
135
                ++it;
×
136
                if (it == et) {
×
137
                    os << ";";
×
138
                } else {
139
                    os << ",";
×
140
                }
141
            }
142
        }
143
        os << "\n";
×
144
    }
×
145

146
    void print_jump(const std::string& direction, const Label& label) {
×
147
        print_labels(direction, direction == "from" ? prog.cfg().parents_of(label) : prog.cfg().children_of(label));
×
148
    }
×
149

150
    void print_instruction(const Program& prog, const Label& label) {
×
151
        for (const auto& pre : prog.assertions_at(label)) {
×
152
            os << "  " << "assert " << pre << ";\n";
×
153
        }
×
154
        os << "  " << prog.instruction_at(label) << ";\n";
×
155
    }
×
156
};
157

158
void print_program(const Program& prog, std::ostream& os, const bool simplify) {
×
159
    DetailedPrinter printer{os, prog};
×
160
    for (const BasicBlock& bb : BasicBlock::collect_basic_blocks(prog.cfg(), simplify)) {
×
161
        printer.print_jump("from", bb.first_label());
×
162
        os << bb.first_label() << ":\n";
×
163
        for (const Label& label : bb) {
×
164
            printer.print_line_info(label);
×
165
            printer.print_instruction(prog, label);
×
166
        }
167
        printer.print_jump("goto", bb.last_label());
×
168
    }
×
169
    os << "\n";
×
170
}
×
171

172
void print_invariants(std::ostream& os, const Program& prog, const bool simplify, const AnalysisResult& result) {
×
173
    DetailedPrinter printer{os, prog};
×
174
    for (const BasicBlock& bb : BasicBlock::collect_basic_blocks(prog.cfg(), simplify)) {
×
175
        if (result.invariants.at(bb.first_label()).pre.is_bottom()) {
×
176
            continue;
×
177
        }
178
        os << "\nPre-invariant : " << result.invariants.at(bb.first_label()).pre << "\n";
×
179
        printer.print_jump("from", bb.first_label());
×
180
        os << bb.first_label() << ":\n";
×
181
        Label last_label = bb.first_label();
×
182
        for (const Label& label : bb) {
×
183
            printer.print_line_info(label);
×
184
            printer.print_instruction(prog, label);
×
185
            last_label = label;
×
186

187
            const auto& current = result.invariants.at(last_label);
×
188
            if (current.error) {
×
189
                os << "\nVerification error:\n";
×
190
                if (label != bb.last_label()) {
×
191
                    os << "After " << current.pre << "\n";
×
192
                }
193
                print_error(os, *current.error);
×
194
                os << "\n";
×
195
                return;
×
196
            }
197
        }
198
        const auto& current = result.invariants.at(last_label);
×
199
        if (!current.post.is_bottom()) {
×
200
            printer.print_jump("goto", last_label);
×
201
            os << "\nPost-invariant : " << current.post << "\n";
×
202
        }
203
    }
×
204
    os << "\n";
×
205
}
×
206

207
void print_dot(const Program& prog, std::ostream& out) {
×
208
    out << "digraph program {\n";
×
209
    out << "    node [shape = rectangle];\n";
×
210
    for (const auto& label : prog.labels()) {
×
211
        out << "    \"" << label << "\"[xlabel=\"" << label << "\",label=\"";
×
212

213
        for (const auto& pre : prog.assertions_at(label)) {
×
214
            out << "assert " << pre << "\\l";
×
215
        }
×
216
        out << prog.instruction_at(label) << "\\l";
×
217

218
        out << "\"];\n";
×
219
        for (const Label& next : prog.cfg().children_of(label)) {
×
220
            out << "    \"" << label << "\" -> \"" << next << "\";\n";
×
221
        }
222
        out << "\n";
×
223
    }
224
    out << "}\n";
×
225
}
×
226

227
void print_dot(const Program& prog, const std::string& outfile) {
×
228
    std::ofstream out{outfile};
×
229
    if (out.fail()) {
×
230
        throw std::runtime_error(std::string("Could not open file ") + outfile);
×
231
    }
232
    print_dot(prog, out);
×
233
}
×
234

235
void print_unreachable(std::ostream& os, const Program& prog, const AnalysisResult& result) {
×
236
    for (const auto& [label, notes] : result.find_unreachable(prog)) {
×
237
        for (const auto& msg : notes) {
×
238
            os << label << ": " << msg << "\n";
×
239
        }
240
    }
×
241
    os << "\n";
×
242
}
×
243

244
std::string to_string(const VerificationError& error) {
316✔
245
    std::stringstream ss;
316✔
246
    if (const auto& label = error.where) {
316✔
247
        ss << *label << ": ";
316✔
248
    }
249
    ss << error.what();
316✔
250
    return ss.str();
632✔
251
}
316✔
252

253
void print_error(std::ostream& os, const VerificationError& error) {
×
254
    LineInfoPrinter printer{os};
×
255
    if (const auto& label = error.where) {
×
256
        printer.print_line_info(*label);
×
257
        os << *label << ": ";
×
258
    }
259
    os << error.what() << "\n";
×
260
    os << "\n";
×
261
}
×
262

263
std::ostream& operator<<(std::ostream& os, const ArgSingle::Kind kind) {
74✔
264
    switch (kind) {
74✔
265
    case ArgSingle::Kind::ANYTHING: return os << "uint64_t";
14✔
266
    case ArgSingle::Kind::PTR_TO_CTX: return os << "ctx";
4✔
267
    case ArgSingle::Kind::PTR_TO_STACK: return os << "stack";
×
268
    case ArgSingle::Kind::MAP_FD: return os << "map_fd";
24✔
269
    case ArgSingle::Kind::MAP_FD_PROGRAMS: return os << "map_fd_programs";
×
270
    case ArgSingle::Kind::PTR_TO_MAP_KEY: return os << "map_key";
24✔
271
    case ArgSingle::Kind::PTR_TO_MAP_VALUE: return os << "map_value";
8✔
272
    }
273
    assert(false);
274
    return os;
275
}
276

277
std::ostream& operator<<(std::ostream& os, const ArgPair::Kind kind) {
×
278
    switch (kind) {
×
279
    case ArgPair::Kind::PTR_TO_READABLE_MEM: return os << "mem";
×
280
    case ArgPair::Kind::PTR_TO_WRITABLE_MEM: return os << "out";
×
281
    }
282
    assert(false);
283
    return os;
284
}
285

286
std::ostream& operator<<(std::ostream& os, const ArgSingle arg) {
74✔
287
    os << arg.kind;
74✔
288
    if (arg.or_null) {
74✔
289
        os << "?";
×
290
    }
291
    os << " " << arg.reg;
74✔
292
    return os;
74✔
293
}
294

295
std::ostream& operator<<(std::ostream& os, const ArgPair arg) {
×
296
    os << arg.kind;
×
297
    if (arg.or_null) {
×
298
        os << "?";
×
299
    }
300
    os << " " << arg.mem << "[" << arg.size;
×
301
    if (arg.can_be_zero) {
×
302
        os << "?";
×
303
    }
304
    os << "], uint64_t " << arg.size;
×
305
    return os;
×
306
}
307

308
std::ostream& operator<<(std::ostream& os, const Bin::Op op) {
278✔
309
    using Op = Bin::Op;
139✔
310
    switch (op) {
278✔
311
    case Op::MOV: return os;
81✔
312
    case Op::MOVSX8: return os << "s8";
×
313
    case Op::MOVSX16: return os << "s16";
×
314
    case Op::MOVSX32: return os << "s32";
×
315
    case Op::ADD: return os << "+";
70✔
316
    case Op::SUB: return os << "-";
×
317
    case Op::MUL: return os << "*";
×
318
    case Op::UDIV: return os << "/";
×
319
    case Op::SDIV: return os << "s/";
×
320
    case Op::UMOD: return os << "%";
×
321
    case Op::SMOD: return os << "s%";
×
322
    case Op::OR: return os << "|";
×
323
    case Op::AND: return os << "&";
30✔
324
    case Op::LSH: return os << "<<";
12✔
325
    case Op::RSH: return os << ">>";
×
326
    case Op::ARSH: return os << ">>>";
2✔
327
    case Op::XOR: return os << "^";
2✔
328
    }
329
    assert(false);
330
    return os;
331
}
332

333
std::ostream& operator<<(std::ostream& os, const Condition::Op op) {
222✔
334
    using Op = Condition::Op;
111✔
335
    switch (op) {
222✔
336
    case Op::EQ: return os << "==";
38✔
337
    case Op::NE: return os << "!=";
10✔
338
    case Op::SET: return os << "&==";
×
339
    case Op::NSET: return os << "&!="; // not in ebpf
×
340
    case Op::LT: return os << "<";     // TODO: os << "u<";
124✔
341
    case Op::LE: return os << "<=";    // TODO: os << "u<=";
12✔
342
    case Op::GT: return os << ">";     // TODO: os << "u>";
22✔
343
    case Op::GE: return os << ">=";    // TODO: os << "u>=";
6✔
344
    case Op::SLT: return os << "s<";
4✔
345
    case Op::SLE: return os << "s<=";
×
346
    case Op::SGT: return os << "s>";
4✔
347
    case Op::SGE: return os << "s>=";
2✔
348
    }
349
    assert(false);
350
    return os;
351
}
352

353
static string size(const int w, const bool is_signed = false) {
184✔
354
    return string(is_signed ? "s" : "u") + std::to_string(w * 8);
552✔
355
}
356

357
// ReSharper disable CppMemberFunctionMayBeConst
358
struct AssertionPrinterVisitor {
359
    std::ostream& _os;
360

361
    void operator()(ValidStore const& a) {
2✔
362
        _os << a.mem << ".type != stack -> " << TypeConstraint{a.val, TypeGroup::number};
2✔
363
    }
2✔
364

365
    void operator()(ValidAccess const& a) {
310✔
366
        if (a.or_null) {
310✔
367
            _os << "(" << TypeConstraint{a.reg, TypeGroup::number} << " and " << a.reg << ".value == 0) or ";
4✔
368
        }
369
        _os << "valid_access(" << a.reg << ".offset";
310✔
370
        if (a.offset > 0) {
310✔
371
            _os << "+" << a.offset;
46✔
372
        } else if (a.offset < 0) {
264✔
373
            _os << a.offset;
6✔
374
        }
375

376
        if (a.width == Value{Imm{0}}) {
310✔
377
            // a.width == 0, meaning we only care it's an in-bound pointer,
378
            // so it can be compared with another pointer to the same region.
379
            _os << ") for comparison/subtraction";
4✔
380
        } else {
381
            _os << ", width=" << a.width << ") for ";
306✔
382
            if (a.access_type == AccessType::read) {
306✔
383
                _os << "read";
274✔
384
            } else {
385
                _os << "write";
32✔
386
            }
387
        }
388
    }
310✔
389

390
    void operator()(const BoundedLoopCount& a) {
22✔
391
        _os << variable_registry->loop_counter(to_string(a.name)) << " < " << BoundedLoopCount::limit;
22✔
392
    }
22✔
393

394
    void operator()(ValidSize const& a) {
2✔
395
        const auto op = a.can_be_zero ? " >= " : " > ";
2✔
396
        _os << a.reg << ".value" << op << 0;
2✔
397
    }
2✔
398

399
    void operator()(ValidCall const& a) {
2✔
400
        const EbpfHelperPrototype proto = thread_local_program_info->platform->get_helper_prototype(a.func);
2✔
401
        _os << "valid call(" << proto.name << ")";
2✔
402
    }
2✔
403

404
    void operator()(ValidMapKeyValue const& a) {
38✔
405
        _os << "within(" << a.access_reg << ":" << (a.key ? "key_size" : "value_size") << "(" << a.map_fd_reg << "))";
45✔
406
    }
38✔
407

408
    void operator()(ZeroCtxOffset const& a) {
2✔
409
        _os << variable_registry->reg(DataKind::ctx_offsets, a.reg.v) << " == 0";
2✔
410
    }
2✔
411

412
    void operator()(Comparable const& a) {
10✔
413
        if (a.or_r2_is_number) {
10✔
414
            _os << TypeConstraint{a.r2, TypeGroup::number} << " or ";
15✔
415
        }
416
        _os << variable_registry->type_reg(a.r1.v) << " == " << variable_registry->type_reg(a.r2.v) << " in "
10✔
417
            << TypeGroup::singleton_ptr;
10✔
418
    }
10✔
419

420
    void operator()(Addable const& a) {
2✔
421
        _os << TypeConstraint{a.ptr, TypeGroup::pointer} << " -> " << TypeConstraint{a.num, TypeGroup::number};
4✔
422
    }
2✔
423

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

426
    void operator()(TypeConstraint const& tc) {
210✔
427
        const string cmp_op = is_singleton_type(tc.types) ? "==" : "in";
224✔
428
        _os << variable_registry->type_reg(tc.reg.v) << " " << cmp_op << " " << tc.types;
210✔
429
    }
210✔
430

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

434
// ReSharper disable CppMemberFunctionMayBeConst
435
struct CommandPrinterVisitor {
436
    std::ostream& os_;
437

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

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

442
    void operator()(LoadMapFd const& b) { os_ << b.dst << " = map_fd " << b.mapfd; }
24✔
443

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

446
    void operator()(LoadPseudo const& b) {
×
447
        os_ << b.dst << " = ";
×
448
        switch (b.addr.kind) {
×
449
        case PseudoAddress::Kind::VARIABLE_ADDR: os_ << "variable_addr(" << b.addr.imm << ")"; break;
×
450
        case PseudoAddress::Kind::CODE_ADDR: os_ << "code_addr(" << b.addr.imm << ")"; break;
×
451
        case PseudoAddress::Kind::MAP_BY_IDX: os_ << "map_by_idx(" << b.addr.imm << ")"; break;
×
452
        case PseudoAddress::Kind::MAP_VALUE_BY_IDX:
×
453
            os_ << "mva(map_by_idx(" << b.addr.imm << ")) + " << b.addr.next_imm;
×
454
            break;
×
455
        }
456
    }
×
457

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

462
    void operator()(Bin const& b) {
278✔
463
        os_ << reg_name(b.dst, b.is64) << " " << b.op << "= " << b.v;
417✔
464
        if (b.lddw) {
278✔
465
            os_ << " ll";
2✔
466
        }
467
    }
278✔
468

469
    void operator()(Un const& b) {
12✔
470
        os_ << b.dst << " = ";
12✔
471
        switch (b.op) {
12✔
472
        case Un::Op::BE16: os_ << "be16 "; break;
2✔
473
        case Un::Op::BE32: os_ << "be32 "; break;
2✔
474
        case Un::Op::BE64: os_ << "be64 "; break;
2✔
475
        case Un::Op::LE16: os_ << "le16 "; break;
2✔
476
        case Un::Op::LE32: os_ << "le32 "; break;
2✔
477
        case Un::Op::LE64: os_ << "le64 "; break;
2✔
478
        case Un::Op::SWAP16: os_ << "swap16 "; break;
×
479
        case Un::Op::SWAP32: os_ << "swap32 "; break;
×
480
        case Un::Op::SWAP64: os_ << "swap64 "; break;
×
481
        case Un::Op::NEG: os_ << "-"; break;
×
482
        }
483
        os_ << b.dst;
12✔
484
    }
12✔
485

486
    void operator()(Call const& call) {
70✔
487
        os_ << "r0 = " << call.name << ":" << call.func << "(";
70✔
488
        for (uint8_t r = 1; r <= 5; r++) {
144✔
489
            // Look for a singleton.
490
            auto single = std::ranges::find_if(call.singles, [r](const ArgSingle arg) { return arg.reg.v == r; });
372✔
491
            if (single != call.singles.end()) {
144✔
492
                if (r > 1) {
74✔
493
                    os_ << ", ";
48✔
494
                }
495
                os_ << *single;
74✔
496
                continue;
74✔
497
            }
498

499
            // Look for the start of a pair.
500
            auto pair = std::ranges::find_if(call.pairs, [r](const ArgPair arg) { return arg.mem.v == r; });
70✔
501
            if (pair != call.pairs.end()) {
70✔
502
                if (r > 1) {
×
503
                    os_ << ", ";
×
504
                }
505
                os_ << *pair;
×
506
                r++;
×
507
                continue;
×
508
            }
509

510
            // Not found.
511
            break;
70✔
512
        }
513
        os_ << ")";
70✔
514
    }
70✔
515

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

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

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

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

524
    void operator()(Jmp const& b) {
×
525
        // A "standalone" jump Instruction.
526
        // Print the label without offset calculations.
527
        if (b.cond) {
×
528
            os_ << "if ";
×
529
            print(*b.cond);
×
530
            os_ << " ";
×
531
        }
532
        os_ << "goto label <" << to_string(b.target) << ">";
×
533
    }
×
534

535
    void operator()(Jmp const& b, const int offset) {
54✔
536
        const string sign = offset > 0 ? "+" : "";
54✔
537
        const string target = sign + std::to_string(offset) + " <" + to_string(b.target) + ">";
108✔
538

539
        if (b.cond) {
54✔
540
            os_ << "if ";
40✔
541
            print(*b.cond);
40✔
542
            os_ << " ";
40✔
543
        }
544
        os_ << "goto " << target;
54✔
545
    }
54✔
546

547
    void operator()(Packet const& b) {
×
548
        /* Direct packet access, R0 = *(uint *) (skb->data + imm32) */
549
        /* Indirect packet access, R0 = *(uint *) (skb->data + src_reg + imm32) */
550
        const string s = size(b.width);
×
551
        os_ << "r0 = ";
×
552
        os_ << "*(" << s << " *)skb[";
×
553
        if (b.regoffset) {
×
554
            os_ << *b.regoffset;
×
555
        }
556
        if (b.offset != 0) {
×
557
            if (b.regoffset) {
×
558
                os_ << " + ";
×
559
            }
560
            os_ << b.offset;
×
561
        }
562
        os_ << "]";
×
563
    }
×
564

565
    void print(Deref const& access) {
184✔
566
        const string sign = access.offset < 0 ? " - " : " + ";
212✔
567
        const int offset = std::abs(access.offset); // what about INT_MIN?
184✔
568
        os_ << "*(" << size(access.width) << " *)";
276✔
569
        os_ << "(" << access.basereg << sign << offset << ")";
184✔
570
    }
184✔
571

572
    void print(Condition const& cond) {
222✔
573
        os_ << cond.left << " " << ((!cond.is64) ? "w" : "") << cond.op << " " << cond.right;
261✔
574
    }
222✔
575

576
    void operator()(Mem const& b) {
184✔
577
        if (b.is_load) {
184✔
578
            os_ << b.value << " = ";
44✔
579
        }
580
        if (b.is_load && b.is_signed) {
184✔
581
            const string sign = b.access.offset < 0 ? " - " : " + ";
×
582
            const int offset = std::abs(b.access.offset);
×
583
            os_ << "*(" << size(b.access.width, true) << " *)";
×
584
            os_ << "(" << b.access.basereg << sign << offset << ")";
×
585
        } else {
×
586
            print(b.access);
184✔
587
        }
588
        if (!b.is_load) {
184✔
589
            os_ << " = " << b.value;
140✔
590
        }
591
    }
184✔
592

593
    void operator()(Atomic const& b) {
×
594
        os_ << "lock ";
×
595
        print(b.access);
×
596
        os_ << " ";
×
597
        bool showfetch = true;
×
598
        switch (b.op) {
×
599
        case Atomic::Op::ADD: os_ << "+"; break;
×
600
        case Atomic::Op::OR: os_ << "|"; break;
×
601
        case Atomic::Op::AND: os_ << "&"; break;
×
602
        case Atomic::Op::XOR: os_ << "^"; break;
×
603
        case Atomic::Op::XCHG:
×
604
            os_ << "x";
×
605
            showfetch = false;
×
606
            break;
×
607
        case Atomic::Op::CMPXCHG:
×
608
            os_ << "cx";
×
609
            showfetch = false;
×
610
            break;
×
611
        }
612
        os_ << "= " << b.valreg;
×
613

614
        if (showfetch && b.fetch) {
×
615
            os_ << " fetch";
×
616
        }
617
    }
×
618

619
    void operator()(Assume const& b) {
182✔
620
        os_ << "assume ";
182✔
621
        print(b.cond);
182✔
622
    }
182✔
623

624
    void operator()(IncrementLoopCounter const& a) {
×
625
        os_ << variable_registry->loop_counter(to_string(a.name)) << "++";
×
626
    }
×
627
};
628
// ReSharper restore CppMemberFunctionMayBeConst
629

630
std::ostream& operator<<(std::ostream& os, Instruction const& ins) {
184✔
631
    std::visit(CommandPrinterVisitor{os}, ins);
92✔
632
    return os;
92✔
633
}
634

635
string to_string(Instruction const& ins) {
184✔
636
    std::stringstream str;
184✔
637
    str << ins;
184✔
638
    return str.str();
368✔
639
}
184✔
640

641
std::ostream& operator<<(std::ostream& os, const Assertion& a) {
682✔
642
    std::visit(AssertionPrinterVisitor{os}, a);
350✔
643
    return os;
350✔
644
}
645

646
string to_string(Assertion const& constraint) {
664✔
647
    std::stringstream str;
664✔
648
    str << constraint;
664✔
649
    return str.str();
1,328✔
650
}
664✔
651

652
auto get_labels(const InstructionSeq& insts) {
38✔
653
    Pc pc = 0;
38✔
654
    std::map<Label, Pc> pc_of_label;
38✔
655
    for (const auto& [label, inst, _] : insts) {
708✔
656
        pc_of_label[label] = pc;
670✔
657
        pc += size(inst);
670✔
658
    }
659
    return pc_of_label;
38✔
660
}
×
661

662
void print(const InstructionSeq& insts, std::ostream& out, const std::optional<const Label>& label_to_print,
38✔
663
           const bool print_line_info) {
664
    const auto pc_of_label = get_labels(insts);
38✔
665
    Pc pc = 0;
38✔
666
    std::string previous_source;
38✔
667
    CommandPrinterVisitor visitor{out};
38✔
668
    for (const LabeledInstruction& labeled_inst : insts) {
708✔
669
        const auto& [label, ins, line_info] = labeled_inst;
670✔
670
        if (!label_to_print.has_value() || label == label_to_print) {
670✔
671
            if (line_info.has_value() && print_line_info) {
670✔
672
                auto& [file, source, line, column] = line_info.value();
×
673
                // Only decorate the first instruction associated with a source line.
674
                if (source != previous_source) {
×
675
                    out << line_info.value();
×
676
                    previous_source = source;
×
677
                }
678
            }
679
            if (label.isjump()) {
670✔
680
                out << "\n";
×
681
                out << label << ":\n";
×
682
            }
683
            if (label_to_print.has_value()) {
670✔
684
                out << pc << ": ";
×
685
            } else {
686
                out << std::setw(8) << pc << ":\t";
670✔
687
            }
688
            if (const auto jmp = std::get_if<Jmp>(&ins)) {
670✔
689
                if (!pc_of_label.contains(jmp->target)) {
54✔
690
                    throw std::runtime_error(string("Cannot find label ") + to_string(jmp->target));
×
691
                }
692
                const Pc target_pc = pc_of_label.at(jmp->target);
54✔
693
                visitor(*jmp, gsl::narrow<int>(target_pc) - static_cast<int>(pc) - 1);
54✔
694
            } else {
695
                std::visit(visitor, ins);
616✔
696
            }
697
            out << "\n";
670✔
698
        }
699
        pc += size(ins);
670✔
700
    }
701
}
38✔
702

703
std::ostream& operator<<(std::ostream& o, const EbpfMapDescriptor& desc) {
28✔
704
    return o << "(" << "original_fd = " << desc.original_fd << ", " << "inner_map_fd = " << desc.inner_map_fd << ", "
28✔
705
             << "type = " << desc.type << ", " << "max_entries = " << desc.max_entries << ", "
28✔
706
             << "value_size = " << desc.value_size << ", " << "key_size = " << desc.key_size << ")";
28✔
707
}
708

709
void print_map_descriptors(const std::vector<EbpfMapDescriptor>& descriptors, std::ostream& o) {
38✔
710
    int i = 0;
38✔
711
    for (const auto& desc : descriptors) {
66✔
712
        o << "map " << i << ":" << desc << "\n";
28✔
713
        i++;
28✔
714
    }
715
}
38✔
716

717
std::ostream& operator<<(std::ostream& os, const btf_line_info_t& line_info) {
×
718
    os << "; " << line_info.file_name << ":" << line_info.line_number << "\n";
×
719
    os << "; " << line_info.source_line << "\n";
×
720
    return os;
×
721
}
722
} // 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