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

Alan-Jowett / ebpf-verifier / 21993000823

13 Feb 2026 01:02PM UTC coverage: 86.313% (-0.5%) from 86.783%
21993000823

push

github

web-flow
ISA feature support matrix and precise rejection semantics (#999)

* ISA feature support matrix and precise rejection semantics

Signed-off-by: Elazar Gershuni <elazarg@gmail.com>

282 of 380 new or added lines in 14 files covered. (74.21%)

3 existing lines in 3 files now uncovered.

9535 of 11047 relevant lines covered (86.31%)

3060772.25 hits per line

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

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

4
#include <algorithm>
5
#include <bit>
6
#include <fstream>
7
#include <iostream>
8
#include <map>
9
#include <ranges>
10
#include <set>
11
#include <sstream>
12
#include <stdexcept>
13
#include <variant>
14

15
#include <yaml-cpp/yaml.h>
16

17
#include "ebpf_verifier.hpp"
18
#include "ir/parse.hpp"
19
#include "ir/syntax.hpp"
20
#include "string_constraints.hpp"
21
#include "test/ebpf_yaml.hpp"
22
#include "verifier.hpp"
23

24
using std::string;
25
using std::vector;
26

27
namespace prevail {
28
// The YAML tests for Call depend on Linux prototypes.
29
// parse_instruction() in asm_parse.cpp explicitly uses
30
// g_ebpf_platform_linux when parsing Call instructions
31
// so we do the same here.
32

33
static EbpfProgramType ebpf_get_program_type(const string& section, const string& path) {
×
34
    return g_ebpf_platform_linux.get_program_type(section, path);
×
35
}
36

37
static EbpfMapType ebpf_get_map_type(const uint32_t platform_specific_type) {
16✔
38
    return g_ebpf_platform_linux.get_map_type(platform_specific_type);
16✔
39
}
40

41
static EbpfHelperPrototype ebpf_get_helper_prototype(const int32_t n) {
68✔
42
    return g_ebpf_platform_linux.get_helper_prototype(n);
68✔
43
}
44

45
static bool ebpf_is_helper_usable(const int32_t n) { return g_ebpf_platform_linux.is_helper_usable(n); }
68✔
46

47
static void ebpf_parse_maps_section(vector<EbpfMapDescriptor>&, const char*, size_t, int, const ebpf_platform_t*,
×
48
                                    ebpf_verifier_options_t) {}
×
49

50
static EbpfMapDescriptor test_map_descriptor = {.original_fd = 0,
51
                                                .type = 0,
52
                                                .key_size = sizeof(uint32_t),
53
                                                .value_size = sizeof(uint32_t),
54
                                                .max_entries = 4,
55
                                                .inner_map_fd = 0};
56

57
static EbpfMapDescriptor& ebpf_get_map_descriptor(int) { return test_map_descriptor; }
196✔
58

59
ebpf_platform_t g_platform_test = {.get_program_type = ebpf_get_program_type,
60
                                   .get_helper_prototype = ebpf_get_helper_prototype,
61
                                   .is_helper_usable = ebpf_is_helper_usable,
62
                                   .map_record_size = 0,
63
                                   .parse_maps_section = ebpf_parse_maps_section,
64
                                   .get_map_descriptor = ebpf_get_map_descriptor,
65
                                   .get_map_type = ebpf_get_map_type,
66
                                   .supported_conformance_groups = bpf_conformance_groups_t::default_groups |
67
                                                                   bpf_conformance_groups_t::packet |
68
                                                                   bpf_conformance_groups_t::callx};
69

70
static EbpfProgramType make_program_type(const string& name, const ebpf_context_descriptor_t* context_descriptor) {
2,034✔
71
    return EbpfProgramType{.name = name,
1,330✔
72
                           .context_descriptor = context_descriptor,
73
                           .platform_specific_data = 0,
74
                           .section_prefixes = {},
75
                           .is_privileged = false};
1,017✔
76
}
77

78
static std::set<string> vector_to_set(const vector<string>& s) {
207,394✔
79
    std::set<string> res;
207,394✔
80
    for (const auto& item : s) {
894,786✔
81
        res.insert(item);
687,392✔
82
    }
83
    return res;
207,394✔
84
}
×
85

86
std::set<string> operator-(const std::set<string>& a, const std::set<string>& b) {
×
87
    std::set<string> res;
×
88
    std::ranges::set_difference(a, b, std::inserter(res, res.begin()));
×
89
    return res;
×
90
}
×
91

92
static StringInvariant read_invariant(const vector<string>& raw_invariant) {
169,228✔
93
    std::set<string> res = vector_to_set(raw_invariant);
169,228✔
94
    if (res == std::set<string>{"_|_"}) {
423,070✔
95
        return StringInvariant{};
×
96
    }
97
    return StringInvariant{std::move(res)};
338,456✔
98
}
592,298✔
99

100
static Label parse_label_scalar(const YAML::Node& node) {
198✔
101
    if (!node.IsDefined() || node.IsNull() || !node.IsScalar()) {
396✔
102
        throw std::runtime_error("Invalid observation label; expected scalar 'entry'/'exit' or instruction index");
×
103
    }
104

105
    const auto s = node.as<string>();
198✔
106
    if (s == "entry") {
198✔
107
        return Label::entry;
×
108
    }
109
    if (s == "exit") {
198✔
110
        return Label::exit;
154✔
111
    }
112

113
    try {
22✔
114
        size_t pos = 0;
44✔
115
        const int index = std::stoi(s, &pos);
44✔
116
        if (pos != s.size()) {
44✔
117
            throw std::runtime_error("Invalid observation label: " + s);
×
118
        }
119
        if (index < 0) {
44✔
120
            throw std::runtime_error("Invalid observation label: " + s);
33✔
121
        }
122
        return Label{index};
22✔
123
    } catch (const std::exception&) {
22✔
124
        throw std::runtime_error("Invalid observation label: " + s);
33✔
125
    }
22✔
126
}
187✔
127

128
static InvariantPoint parse_point(const YAML::Node& node) {
176✔
129
    if (!node.IsDefined() || node.IsNull()) {
242✔
130
        return InvariantPoint::pre;
44✔
131
    }
132
    const auto s = node.as<string>();
132✔
133
    if (s == "pre") {
132✔
134
        return InvariantPoint::pre;
11✔
135
    }
136
    if (s == "post") {
110✔
137
        return InvariantPoint::post;
55✔
138
    }
139
    throw std::runtime_error("Invalid observation point: " + s);
×
140
}
132✔
141

142
static ObservationCheckMode parse_mode(const YAML::Node& node) {
176✔
143
    if (!node.IsDefined() || node.IsNull()) {
242✔
144
        return ObservationCheckMode::consistent;
44✔
145
    }
146
    const auto s = node.as<string>();
132✔
147
    if (s == "consistent") {
132✔
148
        return ObservationCheckMode::consistent;
44✔
149
    }
150
    if (s == "entailed") {
44✔
151
        return ObservationCheckMode::entailed;
22✔
152
    }
153
    throw std::runtime_error("Invalid observation mode: " + s);
×
154
}
132✔
155

156
static std::vector<TestCase::Observation> parse_observations(const YAML::Node& observe_node) {
84,548✔
157
    std::vector<TestCase::Observation> res;
84,548✔
158
    if (!observe_node.IsDefined() || observe_node.IsNull()) {
84,735✔
159
        return res;
42,153✔
160
    }
161
    if (!observe_node.IsSequence()) {
242✔
162
        throw std::runtime_error("observe must be a sequence");
22✔
163
    }
164
    for (const auto& item : observe_node) {
770✔
165
        if (!item.IsMap()) {
220✔
166
            throw std::runtime_error("observe item must be a map");
22✔
167
        }
168
        const Label label = parse_label_scalar(item["at"]);
209✔
169
        const InvariantPoint point = parse_point(item["point"]);
176✔
170
        const ObservationCheckMode mode = parse_mode(item["mode"]);
198✔
171
        const YAML::Node constraints_node = item["constraints"];
176✔
172
        if (!constraints_node.IsDefined() || constraints_node.IsNull()) {
253✔
173
            throw std::runtime_error("observe item missing required 'constraints' field");
22✔
174
        }
175
        if (!constraints_node.IsSequence()) {
154✔
176
            throw std::runtime_error("observe.constraints must be a sequence of strings");
22✔
177
        }
178
        const auto constraints_vec = constraints_node.as<vector<string>>();
132✔
179
        TestCase::Observation obs;
132✔
180
        obs.label = label;
132✔
181
        obs.point = point;
132✔
182
        obs.mode = mode;
132✔
183
        obs.constraints = read_invariant(constraints_vec);
198✔
184
        res.push_back(std::move(obs));
132✔
185
    }
660✔
186
    return res;
132✔
187
}
110✔
188

189
struct RawTestCase {
190
    string test_case;
191
    std::optional<string> expected_exception;
192
    std::set<string> options;
193
    vector<string> pre;
194
    vector<std::tuple<string, vector<string>>> raw_blocks;
195
    vector<string> post;
196
    std::set<string> messages;
197
    YAML::Node observe;
198
};
199

200
static vector<string> parse_block(const YAML::Node& block_node) {
92,898✔
201
    vector<string> block;
92,898✔
202
    std::istringstream is{block_node.as<string>()};
92,898✔
203
    string line;
92,898✔
204
    while (std::getline(is, line)) {
208,260✔
205
        block.emplace_back(line);
115,362✔
206
    }
207
    return block;
185,796✔
208
}
92,898✔
209

210
static auto parse_code(const YAML::Node& code_node) {
84,548✔
211
    vector<std::tuple<string, vector<string>>> res;
84,548✔
212
    for (const auto& item : code_node) {
401,341✔
213
        res.emplace_back(item.first.as<string>(), parse_block(item.second));
139,347✔
214
    }
177,446✔
215
    return res;
84,548✔
216
}
×
217

218
static std::set<string> as_set_empty_default(const YAML::Node& optional_node) {
169,096✔
219
    if (!optional_node.IsDefined() || optional_node.IsNull()) {
188,179✔
220
        return {};
130,930✔
221
    }
222
    return vector_to_set(optional_node.as<vector<string>>());
38,166✔
223
}
224

225
static std::optional<string> as_optional_string(const YAML::Node& optional_node) {
84,548✔
226
    if (!optional_node.IsDefined() || optional_node.IsNull()) {
84,603✔
227
        return std::nullopt;
84,438✔
228
    }
229
    return optional_node.as<string>();
165✔
230
}
231

232
static RawTestCase parse_case(const YAML::Node& case_node) {
84,548✔
233
    return RawTestCase{
126,822✔
234
        .test_case = case_node["test-case"].as<string>(),
235
        .expected_exception = as_optional_string(case_node["expected-exception"]),
236
        .options = as_set_empty_default(case_node["options"]),
237
        .pre = case_node["pre"].as<vector<string>>(),
238
        .raw_blocks = parse_code(case_node["code"]),
239
        .post = case_node["post"].as<vector<string>>(),
240
        .messages = as_set_empty_default(case_node["messages"]),
241
        .observe = case_node["observe"],
242
    };
126,822✔
243
}
244

245
static InstructionSeq raw_cfg_to_instruction_seq(const vector<std::tuple<string, vector<string>>>& raw_blocks) {
84,548✔
246
    std::map<string, Label> label_name_to_label;
84,548✔
247

248
    int label_index = 0;
84,548✔
249
    for (const auto& [label_name, raw_block] : raw_blocks) {
177,446✔
250
        label_name_to_label.emplace(label_name, label_index);
92,898✔
251
        // don't count large instructions as 2
252
        label_index += gsl::narrow<int>(raw_block.size());
92,898✔
253
    }
254

255
    InstructionSeq res;
84,548✔
256
    label_index = 0;
84,548✔
257
    for (const auto& [label_name, raw_block] : raw_blocks) {
177,446✔
258
        for (const string& line : raw_block) {
208,260✔
259
            try {
57,681✔
260
                const Instruction& ins = parse_instruction(line, label_name_to_label);
115,362✔
261
                if (std::holds_alternative<Undefined>(ins)) {
115,362✔
262
                    std::cout << "text:" << line << "; ins: " << ins << "\n";
×
263
                }
264
                res.emplace_back(label_index, ins, std::optional<btf_line_info_t>());
173,043✔
265
            } catch (const std::exception& e) {
115,362✔
266
                std::cout << "text:" << line << "; error: " << e.what() << "\n";
×
267
                res.emplace_back(label_index, Undefined{0}, std::optional<btf_line_info_t>());
×
268
            }
×
269
            label_index++;
115,362✔
270
        }
271
    }
272
    return res;
126,822✔
273
}
84,548✔
274

275
static ebpf_verifier_options_t raw_options_to_options(const std::set<string>& raw_options) {
84,548✔
276
    ebpf_verifier_options_t options{};
84,548✔
277

278
    // Use ~simplify for YAML tests unless otherwise specified.
279
    options.verbosity_opts.simplify = false;
84,548✔
280

281
    // All YAML tests use !setup_constraints.
282
    options.setup_constraints = false;
84,548✔
283

284
    // Default to the machine's native endianness.
285
    options.big_endian = std::endian::native == std::endian::big;
84,548✔
286

287
    // Permit test cases to not have an exit instruction.
288
    options.cfg_opts.must_have_exit = false;
84,548✔
289

290
    for (const string& name : raw_options) {
89,454✔
291
        if (name == "!allow_division_by_zero") {
4,906✔
292
            options.allow_division_by_zero = false;
1,024✔
293
        } else if (name == "termination") {
2,858✔
294
            options.cfg_opts.check_for_termination = true;
323✔
295
        } else if (name == "strict") {
2,212✔
296
            options.strict = true;
297
        } else if (name == "simplify") {
2,212✔
298
            options.verbosity_opts.simplify = true;
15✔
299
        } else if (name == "big_endian") {
2,182✔
300
            options.big_endian = true;
512✔
301
        } else if (name == "!big_endian") {
1,158✔
302
            options.big_endian = false;
579✔
303
        } else {
304
            throw std::runtime_error("Unknown option: " + name);
×
305
        }
306
    }
307
    return options;
84,548✔
308
}
309

310
static TestCase read_case(const RawTestCase& raw_case) {
84,548✔
311
    return TestCase{.name = raw_case.test_case,
85,153✔
312
                    .options = raw_options_to_options(raw_case.options),
84,548✔
313
                    .assumed_pre_invariant = read_invariant(raw_case.pre),
84,548✔
314
                    .instruction_seq = raw_cfg_to_instruction_seq(raw_case.raw_blocks),
84,548✔
315
                    .expected_post_invariant = read_invariant(raw_case.post),
84,548✔
316
                    .expected_messages = raw_case.messages,
84,548✔
317
                    .observations = parse_observations(raw_case.observe)};
84,548✔
318
}
319

320
static vector<TestCase> read_suite(const string& path) {
1,418✔
321
    std::ifstream f{path};
1,418✔
322
    vector<TestCase> res;
1,418✔
323
    for (const YAML::Node& config : YAML::LoadAll(f)) {
85,966✔
324
        const RawTestCase raw_case = parse_case(config);
84,548✔
325
        if (raw_case.expected_exception.has_value()) {
84,548✔
326
            TestCase tc;
110✔
327
            tc.name = raw_case.test_case;
110✔
328
            tc.expected_exception = raw_case.expected_exception;
110✔
329

330
            try {
55✔
331
                (void)read_case(raw_case);
110✔
332
                tc.actual_exception = std::nullopt;
×
333
            } catch (const std::exception& ex) {
110✔
334
                tc.actual_exception = ex.what();
110✔
335
            }
110✔
336

337
            res.push_back(std::move(tc));
110✔
338
        } else {
110✔
339
            res.push_back(read_case(raw_case));
126,657✔
340
        }
341
    }
85,966✔
342
    return res;
2,127✔
343
}
1,418✔
344

345
template <typename T>
346
static Diff<T> make_diff(const T& actual, const T& expected) {
×
347
    return Diff<T>{
348
        .unexpected = actual - expected,
349
        .unseen = expected - actual,
350
    };
×
351
}
×
352

353
std::optional<Failure> run_yaml_test_case(TestCase test_case, bool debug) {
1,418✔
354
    if (test_case.expected_exception.has_value()) {
1,418✔
355
        const std::set<string> expected_messages{"Exception: " + *test_case.expected_exception};
25✔
356
        std::set<string> actual_messages;
10✔
357
        if (test_case.actual_exception.has_value()) {
10✔
358
            actual_messages.insert("Exception: " + *test_case.actual_exception);
15✔
359
        }
360
        if (actual_messages == expected_messages) {
10✔
361
            return {};
10✔
362
        }
363
        return Failure{
×
364
            .invariant = make_diff(StringInvariant::top(), StringInvariant::top()),
×
365
            .messages = make_diff(actual_messages, expected_messages),
366
        };
×
367
    }
10✔
368

369
    thread_local_options = {};
1,408✔
370
    ThreadLocalGuard clear_thread_local_state;
704✔
371
    test_case.options.verbosity_opts.print_failures = true;
1,408✔
372
    if (debug) {
1,408✔
373
        test_case.options.verbosity_opts.print_invariants = true;
×
374
    }
375

376
    ebpf_context_descriptor_t context_descriptor{64, 0, 4, -1};
1,408✔
377
    EbpfProgramType program_type = make_program_type(test_case.name, &context_descriptor);
1,408✔
378

379
    ProgramInfo info{&g_platform_test, {}, program_type};
1,408✔
380
    thread_local_options = test_case.options;
1,408✔
381
    try {
704✔
382
        const Program prog = Program::from_sequence(test_case.instruction_seq, info, test_case.options);
1,408✔
383
        const AnalysisResult result = analyze(prog, test_case.assumed_pre_invariant);
1,400✔
384
        const StringInvariant actual_last_invariant = result.invariant_at(Label::exit);
1,400✔
385
        std::set<string> actual_messages;
1,400✔
386
        if (auto error = result.find_first_error()) {
1,400✔
387
            actual_messages.insert(to_string(*error));
471✔
388
        }
700✔
389
        for (const auto& [label, msgs] : result.find_unreachable(prog)) {
1,582✔
390
            for (const auto& msg : msgs) {
364✔
391
                actual_messages.insert(msg);
182✔
392
            }
393
        }
700✔
394

395
        // Evaluate optional observation checks.
396
        for (const auto& obs : test_case.observations) {
1,412✔
397
            const ObservationCheckResult check =
6✔
398
                result.check_observation_at_label(obs.label, obs.point, obs.constraints, obs.mode);
12✔
399
            if (!check.ok) {
12✔
400
                const std::string point_s = (obs.point == InvariantPoint::pre) ? "pre" : "post";
8✔
401
                const std::string mode_s = (obs.mode == ObservationCheckMode::consistent) ? "consistent" : "entailed";
5✔
402
                // Keep the message stable for YAML expectations; details are available via debug logging.
403
                actual_messages.insert(to_string(obs.label) + ": observation " + mode_s + " failed at " + point_s);
12✔
404
                if (debug && !check.message.empty()) {
4✔
405
                    std::cout << "Observation check failed at " << obs.label << " (" << point_s << ", " << mode_s
×
406
                              << "): " << check.message << "\n";
×
407
                }
408
            }
4✔
409
        }
12✔
410

411
        if (actual_last_invariant == test_case.expected_post_invariant &&
2,800✔
412
            actual_messages == test_case.expected_messages) {
1,400✔
413
            return {};
1,400✔
414
        }
415
        return Failure{
×
416
            .invariant = make_diff(actual_last_invariant, test_case.expected_post_invariant),
×
417
            .messages = make_diff(actual_messages, test_case.expected_messages),
×
418
        };
×
419
    } catch (InvalidControlFlow& ex) {
2,108✔
420
        const std::set<string> actual_messages{ex.what()};
20✔
421
        if (test_case.expected_post_invariant == StringInvariant::top() &&
24✔
422
            actual_messages == test_case.expected_messages) {
8✔
423
            return {};
8✔
424
        }
425
        return Failure{
×
426
            .invariant = make_diff(StringInvariant::top(), test_case.expected_post_invariant),
×
427
            .messages = make_diff(actual_messages, test_case.expected_messages),
×
428
        };
×
429
    } catch (const std::exception& ex) {
8✔
430
        const std::set<string> actual_messages{std::string{"Exception: "} + ex.what()};
×
431
        return Failure{
×
432
            .invariant = make_diff(StringInvariant::top(), test_case.expected_post_invariant),
×
433
            .messages = make_diff(actual_messages, test_case.expected_messages),
×
434
        };
×
435
    }
×
436
}
2,142✔
437

438
template <typename T>
439
    requires std::is_trivially_copyable_v<T>
440
static vector<T> vector_of(const std::vector<std::byte>& bytes) {
626✔
441
    auto data = bytes.data();
626✔
442
    const auto size = bytes.size();
626✔
443
    if (size % sizeof(T) != 0 || size > std::numeric_limits<uint32_t>::max() || !data) {
626✔
444
        throw std::runtime_error("Invalid argument to vector_of");
×
445
    }
446
    return {reinterpret_cast<const T*>(data), reinterpret_cast<const T*>(data + size)};
1,252✔
447
}
448

449
template <std::signed_integral TS>
450
void add_stack_variable(std::set<std::string>& more, int& offset, const std::vector<std::byte>& memory_bytes) {
138✔
451
    using TU = std::make_unsigned_t<TS>;
452
    constexpr size_t size = sizeof(TS);
138✔
453
    static_assert(sizeof(TU) == size);
454
    const auto src = memory_bytes.data() + offset + memory_bytes.size() - EBPF_TOTAL_STACK_SIZE;
138✔
455
    TS svalue;
456
    std::memcpy(&svalue, src, size);
138✔
457
    TU uvalue;
458
    std::memcpy(&uvalue, src, size);
138✔
459
    const auto range = "s[" + std::to_string(offset) + "..." + std::to_string(offset + size - 1) + "]";
345✔
460
    more.insert(range + ".svalue=" + std::to_string(svalue));
207✔
461
    more.insert(range + ".uvalue=" + std::to_string(uvalue));
207✔
462
    offset += size;
138✔
463
}
138✔
464

465
StringInvariant stack_contents_invariant(const std::vector<std::byte>& memory_bytes) {
80✔
466
    std::set<std::string> more = {"r1.type=stack",
40✔
467
                                  "r1.stack_offset=" + std::to_string(EBPF_TOTAL_STACK_SIZE - memory_bytes.size()),
120✔
468
                                  "r1.stack_numeric_size=" + std::to_string(memory_bytes.size()),
160✔
469
                                  "r10.type=stack",
470
                                  "r10.stack_offset=" + std::to_string(EBPF_TOTAL_STACK_SIZE),
120✔
471
                                  "s[" + std::to_string(EBPF_TOTAL_STACK_SIZE - memory_bytes.size()) + "..." +
240✔
472
                                      std::to_string(EBPF_TOTAL_STACK_SIZE - 1) + "].type=number"};
760✔
473

474
    int offset = EBPF_TOTAL_STACK_SIZE - gsl::narrow<int>(memory_bytes.size());
120✔
475
    if (offset % 2 != 0) {
80✔
476
        add_stack_variable<int8_t>(more, offset, memory_bytes);
8✔
477
    }
478
    if (offset % 4 != 0) {
80✔
479
        add_stack_variable<int16_t>(more, offset, memory_bytes);
20✔
480
    }
481
    if (offset % 8 != 0) {
80✔
482
        add_stack_variable<int32_t>(more, offset, memory_bytes);
26✔
483
    }
484
    while (offset < EBPF_TOTAL_STACK_SIZE) {
164✔
485
        add_stack_variable<int64_t>(more, offset, memory_bytes);
84✔
486
    }
487

488
    return StringInvariant(std::move(more));
200✔
489
}
560✔
490

491
ConformanceTestResult run_conformance_test_case(const std::vector<std::byte>& memory_bytes,
626✔
492
                                                const std::vector<std::byte>& program_bytes, bool debug) {
493
    ebpf_context_descriptor_t context_descriptor{64, -1, -1, -1};
626✔
494
    EbpfProgramType program_type = make_program_type("conformance_check", &context_descriptor);
939✔
495

496
    ProgramInfo info{&g_platform_test, {}, program_type};
626✔
497

498
    auto insts = vector_of<EbpfInst>(program_bytes);
626✔
499
    StringInvariant pre_invariant = StringInvariant::top();
626✔
500

501
    if (!memory_bytes.empty()) {
626✔
502
        if (memory_bytes.size() > EBPF_TOTAL_STACK_SIZE) {
80✔
503
            std::cerr << "memory size overflow\n";
×
504
            return {};
×
505
        }
506
        pre_invariant = pre_invariant + stack_contents_invariant(memory_bytes);
200✔
507
    }
508
    RawProgram raw_prog{.prog = insts};
626✔
509
    ebpf_platform_t platform = g_ebpf_platform_linux;
626✔
510
    platform.supported_conformance_groups |= bpf_conformance_groups_t::callx;
626✔
511
    raw_prog.info.platform = &platform;
626✔
512

513
    // Convert the raw program section to a set of instructions.
514
    std::variant<InstructionSeq, std::string> prog_or_error = unmarshal(raw_prog, {});
626✔
515
    if (auto prog = std::get_if<std::string>(&prog_or_error)) {
626✔
UNCOV
516
        std::cerr << "unmarshaling error at " << *prog << "\n";
×
517
        return ConformanceTestResult{.success = false, .r0_value = Interval::top(), .error_reason = *prog};
313✔
518
    }
519

520
    const InstructionSeq& inst_seq = std::get<InstructionSeq>(prog_or_error);
626✔
521

522
    ebpf_verifier_options_t options{};
626✔
523
    if (debug) {
626✔
524
        print(inst_seq, std::cout, {});
×
525
        options.verbosity_opts.print_failures = true;
×
526
        options.verbosity_opts.print_invariants = true;
×
527
        options.verbosity_opts.simplify = false;
×
528
    }
529

530
    try {
313✔
531
        const Program prog = Program::from_sequence(inst_seq, info, options);
626✔
532
        const AnalysisResult result = analyze(prog, pre_invariant);
626✔
533
        return ConformanceTestResult{.success = !result.failed, .r0_value = result.exit_value};
626✔
534
    } catch (const std::exception& ex) {
626✔
535
        // Catch exceptions thrown in ebpf_domain.cpp.
NEW
536
        return ConformanceTestResult{.success = false, .r0_value = Interval::top(), .error_reason = ex.what()};
×
537
    }
×
538
}
1,252✔
539

540
void print_failure(const Failure& failure, std::ostream& os) {
×
541
    constexpr auto INDENT = "  ";
×
542
    if (!failure.invariant.unexpected.empty()) {
×
543
        os << "Unexpected properties:\n" << INDENT << failure.invariant.unexpected << "\n";
×
544
    } else {
545
        os << "Unexpected properties: None\n";
×
546
    }
547
    if (!failure.invariant.unseen.empty()) {
×
548
        os << "Unseen properties:\n" << INDENT << failure.invariant.unseen << "\n";
×
549
    } else {
550
        os << "Unseen properties: None\n";
×
551
    }
552

553
    if (!failure.messages.unexpected.empty()) {
×
554
        os << "Unexpected messages:\n";
×
555
        for (const auto& item : failure.messages.unexpected) {
×
556
            os << INDENT << item << "\n";
×
557
        }
558
    } else {
559
        os << "Unexpected messages: None\n";
×
560
    }
561

562
    if (!failure.messages.unseen.empty()) {
×
563
        os << "Unseen messages:\n";
×
564
        for (const auto& item : failure.messages.unseen) {
×
565
            os << INDENT << item << "\n";
×
566
        }
567
    } else {
568
        os << "Unseen messages: None\n";
×
569
    }
570
}
×
571

572
void foreach_suite(const string& path, const std::function<void(const TestCase&)>& f) {
1,418✔
573
    for (const TestCase& test_case : read_suite(path)) {
85,966✔
574
        f(test_case);
126,822✔
575
    }
1,418✔
576
}
1,418✔
577
} // 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