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

Alan-Jowett / ebpf-verifier / 22120002931

17 Feb 2026 11:32PM UTC coverage: 87.654% (+0.5%) from 87.142%
22120002931

Pull #161

github

web-flow
Merge 2680038c5 into 3745b88ec
Pull Request #161: Failure slice

531 of 682 new or added lines in 8 files covered. (77.86%)

1 existing line in 1 file now uncovered.

9975 of 11380 relevant lines covered (87.65%)

2867868.35 hits per line

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

93.99
/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) {
10✔
18
    ebpf_verifier_options_t options{};
10✔
19
    options.verbosity_opts.collect_instruction_deps = true;
10✔
20

21
    auto raw_progs = read_elf(filename, section, "", options, &g_ebpf_platform_linux);
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
    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() == 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✔
NEW
147
        SKIP("Sample file not found: " << sample);
×
148
    }
149
    auto slices = get_failure_slices(sample, ".text");
2✔
150

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

154
    // The failure involves map update with registers r1, r3
155
    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.size() > 0);
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✔
NEW
163
        SKIP("Sample file not found: " << sample);
×
164
    }
165
    auto slices = get_failure_slices(sample, "test");
2✔
166

167
    REQUIRE(slices.size() >= 1);
2✔
168
    // Slice should have at least the failing label
169
    REQUIRE(slices[0].relevance.size() >= 1);
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✔
NEW
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
    auto raw_progs = read_elf(sample, "test", "", options, &g_ebpf_platform_linux);
5✔
182
    REQUIRE(raw_progs.size() == 1);
2✔
183

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

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

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

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

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

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

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

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

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

226
    REQUIRE_FALSE(result.failed);
2✔
227

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

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

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

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

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

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

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