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

Alan-Jowett / ebpf-verifier / 22316701280

22 Feb 2026 08:43PM UTC coverage: 88.93% (+0.9%) from 88.002%
22316701280

push

github

web-flow
Human-friendly CLI output for bin/prevail (#1042)

* Replace CSV output with human-friendly PASS/FAIL CLI output

The default output was `{0|1},{seconds},{memory_kb}` — a CSV row for
benchmarking scripts that is unfriendly for humans. Benchmarking is
better done externally (time, /usr/bin/time -v).

New output: `PASS: section/function` or `FAIL: section/function` with
the first error and a hint line pointing to --failure-slice / -v.
Add -q/--quiet (exit code only) and --cfg (replaces --domain cfg).

Remove dead code: --domain option (linux, stats, zoneCrab selectors),
linux_verifier, memsize helpers, collect_stats/stats_headers, fnv1a64,
@headers special filename, bin/check alias, and stale benchmark scripts.

Move src/main/check.cpp → src/main.cpp (simplified, rewritten).

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

* Graduate loop3.o from skip to expected failure

The >4x performance improvement means loop3.o no longer hangs.
It now completes quickly but is rejected due to type precision
loss through the loop join (VerifierTypeTracking).

* Remove redundant install exclude, fix doc path

- Remove `PATTERN "main.cpp" EXCLUDE` from install — the glob only
  matches *.hpp/*.h so main.cpp would never be included anyway.
- Fix `./prevail` → `./bin/prevail` in docs/architecture.md for
  consistency with the actual binary location.

---------

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

1 of 1 new or added line in 1 file covered. (100.0%)

198 existing lines in 11 files now uncovered.

13159 of 14797 relevant lines covered (88.93%)

4658715.43 hits per line

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

94.12
/src/test/test_failure_slice.cpp
1
// Copyright (c) Prevail Verifier contributors.
2
// SPDX-License-Identifier: MIT
3

4
#include <catch2/catch_all.hpp>
5
#include <filesystem>
6
#include <sstream>
7

8
#include "ebpf_verifier.hpp"
9

10
using namespace prevail;
11

12
// Helper to check if a test sample file exists
13
static bool sample_exists(const std::string& filename) { return std::filesystem::exists(filename); }
21✔
14

15
// Helper to load a program, run analysis with deps collection, and compute slices
16
static std::vector<FailureSlice> get_failure_slices(const std::string& filename, const std::string& section) {
10✔
17
    ebpf_verifier_options_t options{};
10✔
18
    options.verbosity_opts.collect_instruction_deps = true;
10✔
19

20
    ElfObject elf{filename, options, &g_ebpf_platform_linux};
10✔
21
    const auto& raw_progs = elf.get_programs(section);
10✔
22
    REQUIRE(raw_progs.size() == 1);
10✔
23

24
    const RawProgram& raw_prog = raw_progs.back();
10✔
25
    auto prog_or_error = unmarshal(raw_prog, options);
10✔
26
    auto inst_seq = std::get_if<InstructionSeq>(&prog_or_error);
10✔
27
    REQUIRE(inst_seq != nullptr);
10✔
28

29
    const Program prog = Program::from_sequence(*inst_seq, raw_prog.info, options);
10✔
30
    auto result = analyze(prog);
10✔
31

32
    return result.compute_failure_slices(prog);
20✔
33
}
10✔
34

35
// Test that extract_instruction_deps correctly identifies register reads/writes
36
TEST_CASE("extract_instruction_deps for Bin instruction", "[failure_slice][deps]") {
2✔
37
    // r1 = r1 + r2 should read r1, r2 and write r1
38
    Bin bin_add{Bin::Op::ADD, Reg{1}, Reg{2}, true, false};
2✔
39
    Instruction ins = bin_add;
2✔
40
    EbpfDomain dom = EbpfDomain::top();
2✔
41

42
    auto deps = extract_instruction_deps(ins, dom);
2✔
43

44
    REQUIRE(deps.regs_written.contains(Reg{1}));
3✔
45
    REQUIRE(deps.regs_read.contains(Reg{1})); // ADD also reads dst
3✔
46
    REQUIRE(deps.regs_read.contains(Reg{2}));
4✔
47
}
3✔
48

49
TEST_CASE("extract_instruction_deps for MOV instruction", "[failure_slice][deps]") {
2✔
50
    // r1 = r2 should read r2 and write r1 (but not read r1)
51
    Bin bin_mov{Bin::Op::MOV, Reg{1}, Reg{2}, true, false};
2✔
52
    Instruction ins = bin_mov;
2✔
53
    EbpfDomain dom = EbpfDomain::top();
2✔
54

55
    auto deps = extract_instruction_deps(ins, dom);
2✔
56

57
    REQUIRE(deps.regs_written.contains(Reg{1}));
3✔
58
    REQUIRE_FALSE(deps.regs_read.contains(Reg{1})); // MOV doesn't read dst
3✔
59
    REQUIRE(deps.regs_read.contains(Reg{2}));
3✔
60
}
3✔
61

62
TEST_CASE("extract_instruction_deps for Mem load", "[failure_slice][deps]") {
2✔
63
    // r1 = *(r10 - 8) should read r10, write r1, and read stack[-8]
64
    Mem mem_load{Deref{8, Reg{10}, -8}, Reg{1}, true};
2✔
65
    Instruction ins = mem_load;
2✔
66
    EbpfDomain dom = EbpfDomain::top();
2✔
67

68
    auto deps = extract_instruction_deps(ins, dom);
2✔
69

70
    REQUIRE(deps.regs_written.contains(Reg{1}));
3✔
71
    REQUIRE(deps.regs_read.contains(Reg{10}));
3✔
72
    REQUIRE(deps.stack_read.contains(-8));
3✔
73
}
3✔
74

75
TEST_CASE("extract_instruction_deps for Mem store", "[failure_slice][deps]") {
2✔
76
    // *(r10 - 8) = r1 should read r10, r1 and write stack[-8]
77
    Mem mem_store{Deref{8, Reg{10}, -8}, Reg{1}, false};
2✔
78
    Instruction ins = mem_store;
2✔
79
    EbpfDomain dom = EbpfDomain::top();
2✔
80

81
    auto deps = extract_instruction_deps(ins, dom);
2✔
82

83
    REQUIRE(deps.regs_read.contains(Reg{10}));
3✔
84
    REQUIRE(deps.regs_read.contains(Reg{1}));
3✔
85
    REQUIRE(deps.stack_written.contains(-8));
3✔
86
}
3✔
87

88
// Test that extract_assertion_registers correctly identifies assertion dependencies
89
TEST_CASE("extract_assertion_registers for ValidAccess", "[failure_slice][deps]") {
2✔
90
    ValidAccess va{0, Reg{1}, 0, Imm{8}, false, AccessType::read};
2✔
91
    const Assertion assertion = va;
2✔
92

93
    const auto regs = extract_assertion_registers(assertion);
2✔
94

95
    REQUIRE(regs.contains(Reg{1}));
4✔
96
}
3✔
97

98
TEST_CASE("extract_assertion_registers for Comparable", "[failure_slice][deps]") {
2✔
99
    Comparable comp{Reg{1}, Reg{2}, false};
2✔
100
    const Assertion assertion = comp;
2✔
101

102
    const auto regs = extract_assertion_registers(assertion);
2✔
103

104
    REQUIRE(regs.contains(Reg{1}));
3✔
105
    REQUIRE(regs.contains(Reg{2}));
4✔
106
}
3✔
107

108
TEST_CASE("extract_assertion_registers for ValidDivisor", "[failure_slice][deps]") {
2✔
109
    ValidDivisor vd{Reg{3}, false};
2✔
110
    const Assertion assertion = vd;
2✔
111

112
    const auto regs = extract_assertion_registers(assertion);
2✔
113

114
    REQUIRE(regs.contains(Reg{3}));
4✔
115
}
3✔
116

117
TEST_CASE("extract_assertion_registers for BoundedLoopCount", "[failure_slice][deps]") {
2✔
118
    BoundedLoopCount blc{Label{0}};
2✔
119
    const Assertion assertion = blc;
2✔
120

121
    const auto regs = extract_assertion_registers(assertion);
2✔
122

123
    REQUIRE(regs.empty());
3✔
124
}
4✔
125

126
// Integration tests using real failing programs
127
TEST_CASE("failure slice for badmapptr.o", "[failure_slice][integration]") {
2✔
128
    const std::string sample = "ebpf-samples/build/badmapptr.o";
2✔
129
    if (!sample_exists(sample)) {
2✔
UNCOV
130
        SKIP("Sample file not found: " << sample);
×
131
    }
132
    auto slices = get_failure_slices(sample, "test");
2✔
133

134
    REQUIRE(slices.size() == 1);
2✔
135
    REQUIRE(slices[0].relevance.size() == 2); // Labels 2 and 4
2✔
136
    REQUIRE(slices[0].relevance.contains(slices[0].failing_label));
2✔
137

138
    // The failure is about r1 type (map_fd is not in {number, ctx, stack, packet, shared})
139
    auto& failing_relevance = slices[0].relevance.at(slices[0].failing_label);
2✔
140
    REQUIRE(failing_relevance.registers.size() == 1);
2✔
141
    REQUIRE(failing_relevance.registers.contains(Reg{1}));
3✔
142
}
2✔
143

144
TEST_CASE("failure slice for exposeptr.o", "[failure_slice][integration]") {
2✔
145
    const std::string sample = "ebpf-samples/build/exposeptr.o";
2✔
146
    if (!sample_exists(sample)) {
2✔
UNCOV
147
        SKIP("Sample file not found: " << sample);
×
148
    }
149
    const auto slices = get_failure_slices(sample, ".text");
2✔
150

151
    REQUIRE(slices.size() == 1);
2✔
152
    REQUIRE(!slices[0].relevance.empty());
2✔
153

154
    // The failure involves map update with registers r1, r3
155
    const auto& failing_relevance = slices[0].relevance.at(slices[0].failing_label);
2✔
156
    // At minimum r3 should be relevant (the value being stored)
157
    REQUIRE(!failing_relevance.registers.empty());
2✔
158
}
2✔
159

160
TEST_CASE("failure slice for nullmapref.o", "[failure_slice][integration]") {
2✔
161
    const std::string sample = "ebpf-samples/build/nullmapref.o";
2✔
162
    if (!sample_exists(sample)) {
2✔
UNCOV
163
        SKIP("Sample file not found: " << sample);
×
164
    }
165
    const auto slices = get_failure_slices(sample, "test");
2✔
166

167
    REQUIRE(!slices.empty());
2✔
168
    // Slice should have at least the failing label
169
    REQUIRE(!slices[0].relevance.empty());
2✔
170
}
2✔
171

172
// Test print_failure_slices produces output
173
TEST_CASE("print_failure_slices produces structured output", "[failure_slice][print]") {
2✔
174
    const std::string sample = "ebpf-samples/build/badmapptr.o";
2✔
175
    if (!sample_exists(sample)) {
2✔
UNCOV
176
        SKIP("Sample file not found: " << sample);
×
177
    }
178
    ebpf_verifier_options_t options{};
2✔
179
    options.verbosity_opts.collect_instruction_deps = true;
2✔
180

181
    ElfObject elf{sample, options, &g_ebpf_platform_linux};
3✔
182
    const auto& raw_progs = elf.get_programs("test");
4✔
183
    REQUIRE(raw_progs.size() == 1);
2✔
184

185
    const RawProgram& raw_prog = raw_progs.back();
2✔
186
    auto prog_or_error = unmarshal(raw_prog, options);
2✔
187
    auto inst_seq = std::get_if<InstructionSeq>(&prog_or_error);
2✔
188
    REQUIRE(inst_seq != nullptr);
2✔
189

190
    const Program prog = Program::from_sequence(*inst_seq, raw_prog.info, options);
2✔
191
    auto result = analyze(prog);
2✔
192
    auto slices = result.compute_failure_slices(prog);
2✔
193

194
    std::stringstream output;
2✔
195
    print_failure_slices(output, prog, false, result, slices);
2✔
196

197
    std::string output_str = output.str();
2✔
198

199
    // Check expected sections are present
200
    REQUIRE(output_str.find("[ERROR]") != std::string::npos);
2✔
201
    REQUIRE(output_str.find("[LOCATION]") != std::string::npos);
2✔
202
    REQUIRE(output_str.find("[RELEVANT REGISTERS]") != std::string::npos);
2✔
203
    REQUIRE(output_str.find("[SLICE SIZE]") != std::string::npos);
2✔
204
    REQUIRE(output_str.find("[CAUSAL TRACE]") != std::string::npos);
3✔
205
}
2✔
206

207
// Test that passing programs produce no slices
208
TEST_CASE("passing program produces no failure slices", "[failure_slice][integration]") {
2✔
209
    const std::string sample = "ebpf-samples/build/stackok.o";
2✔
210
    if (!sample_exists(sample)) {
2✔
UNCOV
211
        SKIP("Sample file not found: " << sample);
×
212
    }
213
    ebpf_verifier_options_t options{};
2✔
214
    options.verbosity_opts.collect_instruction_deps = true;
2✔
215

216
    ElfObject elf{sample, options, &g_ebpf_platform_linux};
3✔
217
    const auto& raw_progs = elf.get_programs(".text");
4✔
218
    REQUIRE(raw_progs.size() == 1);
2✔
219

220
    const RawProgram& raw_prog = raw_progs.back();
2✔
221
    auto prog_or_error = unmarshal(raw_prog, options);
2✔
222
    auto inst_seq = std::get_if<InstructionSeq>(&prog_or_error);
2✔
223
    REQUIRE(inst_seq != nullptr);
2✔
224

225
    const Program prog = Program::from_sequence(*inst_seq, raw_prog.info, options);
2✔
226
    auto result = analyze(prog);
2✔
227

228
    REQUIRE_FALSE(result.failed);
2✔
229

230
    auto slices = result.compute_failure_slices(prog);
2✔
231
    REQUIRE(slices.empty());
2✔
232
}
2✔
233

234
// Test that Assume control-dependency logic includes guard condition registers.
235
// When an Assume is a direct predecessor of the failing label, its condition
236
// registers should appear as relevant in the slice.
237
TEST_CASE("assume guard registers become relevant in slice", "[failure_slice][integration]") {
2✔
238
    const std::string sample = "ebpf-samples/build/dependent_read.o";
2✔
239
    if (!sample_exists(sample)) {
2✔
UNCOV
240
        SKIP("Sample file not found: " << sample);
×
241
    }
242
    auto slices = get_failure_slices(sample, "xdp");
2✔
243

244
    REQUIRE(!slices.empty());
2✔
245
    const auto& slice = slices[0];
2✔
246
    REQUIRE(!slice.relevance.empty());
2✔
247

248
    // Check that at least one Assume label is in the slice
249
    // (the guard condition that determines reachability of the failing label)
250
    ebpf_verifier_options_t options{};
2✔
251
    options.verbosity_opts.collect_instruction_deps = true;
2✔
252
    ElfObject elf{sample, options, &g_ebpf_platform_linux};
3✔
253
    const auto& raw_progs = elf.get_programs("xdp");
4✔
254
    REQUIRE(raw_progs.size() == 1);
2✔
255
    auto prog_or_error = unmarshal(raw_progs.back(), options);
2✔
256
    auto inst_seq = std::get_if<InstructionSeq>(&prog_or_error);
2✔
257
    REQUIRE(inst_seq != nullptr);
2✔
258
    const Program prog = Program::from_sequence(*inst_seq, raw_progs.back().info, options);
2✔
259

260
    bool found_assume_in_slice = false;
2✔
261
    for (const auto& [label, relevance] : slice.relevance) {
10✔
262
        if (std::holds_alternative<Assume>(prog.instruction_at(label))) {
10✔
263
            found_assume_in_slice = true;
2✔
264
            // The Assume's condition registers should be in the relevance set
265
            REQUIRE(!relevance.registers.empty());
2✔
266
            break;
2✔
267
        }
268
    }
269
    REQUIRE(found_assume_in_slice);
2✔
270
}
2✔
271

272
TEST_CASE("empty seed assertion still includes failing label", "[failure_slice][integration]") {
2✔
273
    const std::string sample = "ebpf-samples/build/bounded_loop.o";
2✔
274
    if (!sample_exists(sample)) {
2✔
UNCOV
275
        SKIP("Sample file not found: " << sample);
×
276
    }
277
    const auto slices = get_failure_slices(sample, "test");
3✔
278
    if (slices.empty()) {
2✔
279
        // bounded_loop may pass verification depending on build; skip if so
280
        SKIP("Program passed verification (no failure slices)");
3✔
281
    }
282

283
    // The slice should still contain the failing label even with no register deps
UNCOV
284
    for (const auto& slice : slices) {
×
UNCOV
285
        auto labels = slice.impacted_labels();
×
UNCOV
286
        REQUIRE(labels.contains(slice.failing_label));
×
UNCOV
287
    }
×
288
}
3✔
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