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

Alan-Jowett / ebpf-verifier / 21968098219

12 Feb 2026 11:19PM UTC coverage: 87.328% (+0.5%) from 86.783%
21968098219

Pull #161

github

web-flow
Merge fcfbad30c into f1f1d42d7
Pull Request #161: Failure slice

527 of 677 new or added lines in 8 files covered. (77.84%)

20 existing lines in 2 files now uncovered.

10020 of 11474 relevant lines covered (87.33%)

2947086.45 hits per line

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

93.92
/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
#include "linux/gpl/spec_type_descriptors.hpp"
10

11
using namespace prevail;
12

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

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

21
    auto raw_progs = read_elf(filename, section, "", options, &g_ebpf_platform_linux);
8✔
22
    REQUIRE(raw_progs.size() == 1);
8✔
23

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

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

32
    return result.compute_failure_slices(prog);
16✔
33
}
8✔
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
    Assertion assertion = va;
2✔
92

93
    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
    Assertion assertion = comp;
2✔
101

102
    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
    Assertion assertion = vd;
2✔
111

112
    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
    Assertion assertion = blc;
2✔
120

121
    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✔
NEW
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() > 0);
2✔
136
    REQUIRE(slices[0].relevance.contains(slices[0].failing_label));
2✔
137

138
    // The failure is about r1 type
139
    auto& failing_relevance = slices[0].relevance.at(slices[0].failing_label);
2✔
140
    REQUIRE(failing_relevance.registers.contains(Reg{1}));
3✔
141
}
2✔
142

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

150
    REQUIRE(slices.size() == 1);
2✔
151
    REQUIRE(slices[0].relevance.size() > 0);
2✔
152

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

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

166
    REQUIRE(slices.size() >= 1);
2✔
167
    // Slice should have at least the failing label
168
    REQUIRE(slices[0].relevance.size() >= 1);
2✔
169
}
2✔
170

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

180
    auto raw_progs = read_elf(sample, "test", "", options, &g_ebpf_platform_linux);
5✔
181
    REQUIRE(raw_progs.size() == 1);
2✔
182

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

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

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

195
    std::string output_str = output.str();
2✔
196

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

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

214
    auto raw_progs = read_elf(sample, ".text", "", options, &g_ebpf_platform_linux);
5✔
215
    REQUIRE(raw_progs.size() == 1);
2✔
216

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

222
    const Program prog = Program::from_sequence(*inst_seq, raw_prog.info, options);
2✔
223
    auto result = analyze(prog);
2✔
224

225
    REQUIRE_FALSE(result.failed);
2✔
226

227
    auto slices = result.compute_failure_slices(prog);
2✔
228
    REQUIRE(slices.empty());
2✔
229
}
2✔
230

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

241
    REQUIRE(slices.size() >= 1);
2✔
242
    const auto& slice = slices[0];
2✔
243
    REQUIRE(slice.relevance.size() > 0);
2✔
244

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

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

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

279
    // The slice should still contain the failing label even with no register deps
NEW
280
    for (const auto& slice : slices) {
×
NEW
281
        auto labels = slice.impacted_labels();
×
NEW
282
        REQUIRE(labels.contains(slice.failing_label));
×
283
    }
284
}
1✔
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