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

Alan-Jowett / ebpf-verifier / 21966617508

12 Feb 2026 10:24PM UTC coverage: 87.348% (+0.6%) from 86.783%
21966617508

Pull #161

github

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

518 of 664 new or added lines in 8 files covered. (78.01%)

21 existing lines in 2 files now uncovered.

10011 of 11461 relevant lines covered (87.35%)

2950172.49 hits per line

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

93.18
/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
// Integration tests using real failing programs
118
TEST_CASE("failure slice for badmapptr.o", "[failure_slice][integration]") {
2✔
119
    const std::string sample = "ebpf-samples/build/badmapptr.o";
2✔
120
    if (!sample_exists(sample)) {
2✔
NEW
121
        SKIP("Sample file not found: " << sample);
×
122
    }
123
    auto slices = get_failure_slices(sample, "test");
2✔
124

125
    REQUIRE(slices.size() == 1);
2✔
126
    REQUIRE(slices[0].relevance.size() > 0);
2✔
127
    REQUIRE(slices[0].relevance.contains(slices[0].failing_label));
2✔
128

129
    // The failure is about r1 type
130
    auto& failing_relevance = slices[0].relevance.at(slices[0].failing_label);
2✔
131
    REQUIRE(failing_relevance.registers.contains(Reg{1}));
3✔
132
}
2✔
133

134
TEST_CASE("failure slice for exposeptr.o", "[failure_slice][integration]") {
2✔
135
    const std::string sample = "ebpf-samples/build/exposeptr.o";
2✔
136
    if (!sample_exists(sample)) {
2✔
NEW
137
        SKIP("Sample file not found: " << sample);
×
138
    }
139
    auto slices = get_failure_slices(sample, ".text");
2✔
140

141
    REQUIRE(slices.size() == 1);
2✔
142
    REQUIRE(slices[0].relevance.size() > 0);
2✔
143

144
    // The failure involves map update with registers r1, r3
145
    auto& failing_relevance = slices[0].relevance.at(slices[0].failing_label);
2✔
146
    // At minimum r3 should be relevant (the value being stored)
147
    REQUIRE(failing_relevance.registers.size() > 0);
2✔
148
}
2✔
149

150
TEST_CASE("failure slice for nullmapref.o", "[failure_slice][integration]") {
2✔
151
    const std::string sample = "ebpf-samples/build/nullmapref.o";
2✔
152
    if (!sample_exists(sample)) {
2✔
NEW
153
        SKIP("Sample file not found: " << sample);
×
154
    }
155
    auto slices = get_failure_slices(sample, "test");
2✔
156

157
    REQUIRE(slices.size() >= 1);
2✔
158
    // Slice should have at least the failing label
159
    REQUIRE(slices[0].relevance.size() >= 1);
2✔
160
}
2✔
161

162
// Test print_failure_slices produces output
163
TEST_CASE("print_failure_slices produces structured output", "[failure_slice][print]") {
2✔
164
    const std::string sample = "ebpf-samples/build/badmapptr.o";
2✔
165
    if (!sample_exists(sample)) {
2✔
NEW
166
        SKIP("Sample file not found: " << sample);
×
167
    }
168
    ebpf_verifier_options_t options{};
2✔
169
    options.verbosity_opts.collect_instruction_deps = true;
2✔
170

171
    auto raw_progs = read_elf(sample, "test", "", options, &g_ebpf_platform_linux);
5✔
172
    REQUIRE(raw_progs.size() == 1);
2✔
173

174
    const RawProgram& raw_prog = raw_progs.back();
2✔
175
    auto prog_or_error = unmarshal(raw_prog, options);
2✔
176
    auto inst_seq = std::get_if<InstructionSeq>(&prog_or_error);
2✔
177
    REQUIRE(inst_seq != nullptr);
2✔
178

179
    const Program prog = Program::from_sequence(*inst_seq, raw_prog.info, options);
2✔
180
    auto result = analyze(prog);
2✔
181
    auto slices = result.compute_failure_slices(prog);
2✔
182

183
    std::stringstream output;
2✔
184
    print_failure_slices(output, prog, false, result, slices);
2✔
185

186
    std::string output_str = output.str();
2✔
187

188
    // Check expected sections are present
189
    REQUIRE(output_str.find("[ERROR]") != std::string::npos);
2✔
190
    REQUIRE(output_str.find("[LOCATION]") != std::string::npos);
2✔
191
    REQUIRE(output_str.find("[RELEVANT REGISTERS]") != std::string::npos);
2✔
192
    REQUIRE(output_str.find("[SLICE SIZE]") != std::string::npos);
2✔
193
    REQUIRE(output_str.find("[CAUSAL TRACE]") != std::string::npos);
3✔
194
}
2✔
195

196
// Test that passing programs produce no slices
197
TEST_CASE("passing program produces no failure slices", "[failure_slice][integration]") {
2✔
198
    const std::string sample = "ebpf-samples/build/stackok.o";
2✔
199
    if (!sample_exists(sample)) {
2✔
NEW
200
        SKIP("Sample file not found: " << sample);
×
201
    }
202
    ebpf_verifier_options_t options{};
2✔
203
    options.verbosity_opts.collect_instruction_deps = true;
2✔
204

205
    auto raw_progs = read_elf(sample, ".text", "", options, &g_ebpf_platform_linux);
5✔
206
    REQUIRE(raw_progs.size() == 1);
2✔
207

208
    const RawProgram& raw_prog = raw_progs.back();
2✔
209
    auto prog_or_error = unmarshal(raw_prog, options);
2✔
210
    auto inst_seq = std::get_if<InstructionSeq>(&prog_or_error);
2✔
211
    REQUIRE(inst_seq != nullptr);
2✔
212

213
    const Program prog = Program::from_sequence(*inst_seq, raw_prog.info, options);
2✔
214
    auto result = analyze(prog);
2✔
215

216
    REQUIRE_FALSE(result.failed);
2✔
217

218
    auto slices = result.compute_failure_slices(prog);
2✔
219
    REQUIRE(slices.empty());
2✔
220
}
2✔
221

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

232
    REQUIRE(slices.size() >= 1);
2✔
233
    const auto& slice = slices[0];
2✔
234
    REQUIRE(slice.relevance.size() > 0);
2✔
235

236
    // Check that at least one Assume label is in the slice
237
    // (the guard condition that determines reachability of the failing label)
238
    ebpf_verifier_options_t options{};
2✔
239
    options.verbosity_opts.collect_instruction_deps = true;
2✔
240
    auto raw_progs = read_elf(sample, "xdp", "", options, &g_ebpf_platform_linux);
5✔
241
    REQUIRE(raw_progs.size() == 1);
2✔
242
    auto prog_or_error = unmarshal(raw_progs.back(), options);
2✔
243
    auto inst_seq = std::get_if<InstructionSeq>(&prog_or_error);
2✔
244
    REQUIRE(inst_seq != nullptr);
2✔
245
    const Program prog = Program::from_sequence(*inst_seq, raw_progs.back().info, options);
2✔
246

247
    bool found_assume_in_slice = false;
2✔
248
    for (const auto& [label, relevance] : slice.relevance) {
10✔
249
        if (std::holds_alternative<Assume>(prog.instruction_at(label))) {
10✔
250
            found_assume_in_slice = true;
2✔
251
            // The Assume's condition registers should be in the relevance set
252
            REQUIRE(relevance.registers.size() > 0);
2✔
253
            break;
2✔
254
        }
255
    }
256
    REQUIRE(found_assume_in_slice);
2✔
257
}
2✔
258
TEST_CASE("empty seed assertion still includes failing label", "[failure_slice][integration]") {
2✔
259
    const std::string sample = "ebpf-samples/build/bounded_loop.o";
2✔
260
    if (!sample_exists(sample)) {
2✔
261
        SKIP("Sample file not found: " << sample);
3✔
262
    }
263
    auto slices = get_failure_slices(sample, "test");
1✔
NEW
264
    if (slices.empty()) {
×
265
        // bounded_loop may pass verification depending on build; skip if so
NEW
266
        SKIP("Program passed verification (no failure slices)");
×
267
    }
268

269
    // The slice should still contain the failing label even with no register deps
NEW
270
    for (const auto& slice : slices) {
×
NEW
271
        auto labels = slice.impacted_labels();
×
NEW
272
        REQUIRE(labels.contains(slice.failing_label));
×
NEW
273
    }
×
274
}
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