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

Alan-Jowett / ebpf-verifier / 22235569093

20 Feb 2026 02:12AM UTC coverage: 88.002% (-0.2%) from 88.157%
22235569093

push

github

web-flow
Handle Call builtins: fix handling of Falco tests  (#1025)

* falco: fix raw_tracepoint privilege and group expected failures

Mark raw_tracepoint/raw_tracepoint_writable as privileged program types so Falco raw-tracepoint sections are not treated as unprivileged argument checks.

Update Falco sample matrix to move now-passing sections out of TEST_SECTION_FAIL and group the remaining expected failures by root-cause class (offset lower-bound loss vs size lower-bound loss at correlated joins).

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

* elf/unmarshal: gate builtin relocations via platform call model

Add platform hooks to resolve builtin symbols and provide builtin call contracts, thread relocation-gated builtin call offsets through ProgramInfo, and only treat static helper IDs as builtins at gated call sites.

Also extend platform-table, marshal, and YAML-platform tests to cover builtin resolver wiring and call unmarshal behavior.
* crab: canonicalize unsigned intervals in bitwise_and
When uvalue intervals temporarily carry signed lower bounds (e.g. after joins), Interval::bitwise_and asserted in debug builds. Canonicalize both operands via zero_extend(64) before unsigned bitwise reasoning, preserving soundness and avoiding debug aborts.

Validated by reproducing SIGABRT on reverted code in [falco][verify] and confirming the patched build completes with expected 73 pass / 20 failed-as-expected.

* Fix unsound bitwise_and case for non-singleton all-ones rhs

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

239 of 252 new or added lines in 9 files covered. (94.84%)

602 existing lines in 16 files now uncovered.

11743 of 13344 relevant lines covered (88.0%)

3262592.78 hits per line

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

76.19
/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 <span>
12
#include <sstream>
13
#include <stdexcept>
14
#include <variant>
15

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

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

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

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

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

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

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

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

NEW
48
static std::optional<int32_t> ebpf_resolve_builtin_call(const std::string& name) {
×
NEW
49
    if (!g_ebpf_platform_linux.resolve_builtin_call) {
×
NEW
50
        return std::nullopt;
×
51
    }
NEW
52
    return g_ebpf_platform_linux.resolve_builtin_call(name);
×
53
}
54

NEW
55
static std::optional<Call> ebpf_get_builtin_call(const int32_t id) {
×
NEW
56
    if (!g_ebpf_platform_linux.get_builtin_call) {
×
NEW
57
        return std::nullopt;
×
58
    }
NEW
59
    return g_ebpf_platform_linux.get_builtin_call(id);
×
60
}
61

62
static std::optional<Call> ebpf_resolve_kfunc_call(const int32_t btf_id, const ProgramInfo* info,
×
63
                                                   std::string* why_not) {
UNCOV
64
    if (!g_ebpf_platform_linux.resolve_kfunc_call) {
×
UNCOV
65
        return std::nullopt;
×
66
    }
UNCOV
67
    return g_ebpf_platform_linux.resolve_kfunc_call(btf_id, info, why_not);
×
68
}
69

UNCOV
70
static void ebpf_parse_maps_section(vector<EbpfMapDescriptor>&, const char*, size_t, int, const ebpf_platform_t*,
×
UNCOV
71
                                    ebpf_verifier_options_t) {}
×
72

73
static EbpfMapDescriptor test_map_descriptor = {.original_fd = 0,
74
                                                .type = 0,
75
                                                .key_size = sizeof(uint32_t),
76
                                                .value_size = sizeof(uint32_t),
77
                                                .max_entries = 4,
78
                                                .inner_map_fd = 0};
79

80
static EbpfMapDescriptor& ebpf_get_map_descriptor(int) { return test_map_descriptor; }
200✔
81

82
ebpf_platform_t g_platform_test = {.get_program_type = ebpf_get_program_type,
83
                                   .get_helper_prototype = ebpf_get_helper_prototype,
84
                                   .is_helper_usable = ebpf_is_helper_usable,
85
                                   .resolve_builtin_call = ebpf_resolve_builtin_call,
86
                                   .get_builtin_call = ebpf_get_builtin_call,
87
                                   .resolve_kfunc_call = ebpf_resolve_kfunc_call,
88
                                   .map_record_size = 0,
89
                                   .parse_maps_section = ebpf_parse_maps_section,
90
                                   .get_map_descriptor = ebpf_get_map_descriptor,
91
                                   .get_map_type = ebpf_get_map_type,
92
                                   .supported_conformance_groups = bpf_conformance_groups_t::default_groups |
93
                                                                   bpf_conformance_groups_t::packet |
94
                                                                   bpf_conformance_groups_t::callx};
95

96
static EbpfProgramType make_program_type(const string& name, const ebpf_context_descriptor_t* context_descriptor) {
2,068✔
97
    return EbpfProgramType{.name = name,
1,347✔
98
                           .context_descriptor = context_descriptor,
99
                           .platform_specific_data = 0,
100
                           .section_prefixes = {},
101
                           .is_privileged = false};
1,034✔
102
}
103

104
static std::set<string> vector_to_set(const vector<string>& s) {
3,704✔
105
    std::set<string> res;
3,704✔
106
    for (const auto& item : s) {
16,830✔
107
        res.insert(item);
13,126✔
108
    }
109
    return res;
3,704✔
UNCOV
110
}
×
111

112
std::set<string> operator-(const std::set<string>& a, const std::set<string>& b) {
×
UNCOV
113
    std::set<string> res;
×
UNCOV
114
    std::ranges::set_difference(a, b, std::inserter(res, res.begin()));
×
UNCOV
115
    return res;
×
UNCOV
116
}
×
117

118
static StringInvariant read_invariant(const vector<string>& raw_invariant) {
2,958✔
119
    std::set<string> res = vector_to_set(raw_invariant);
2,958✔
120
    if (res == std::set<string>{"_|_"}) {
7,395✔
UNCOV
121
        return StringInvariant{};
×
122
    }
123
    return StringInvariant{std::move(res)};
5,916✔
124
}
10,353✔
125

126
static Label parse_label_scalar(const YAML::Node& node) {
20✔
127
    if (!node.IsDefined() || node.IsNull() || !node.IsScalar()) {
40✔
UNCOV
128
        throw std::runtime_error("Invalid observation label; expected scalar 'entry'/'exit' or instruction index");
×
129
    }
130

131
    const auto s = node.as<string>();
20✔
132
    if (s == "entry") {
20✔
UNCOV
133
        return Label::entry;
×
134
    }
135
    if (s == "exit") {
20✔
136
        return Label::exit;
16✔
137
    }
138

139
    try {
2✔
140
        size_t pos = 0;
4✔
141
        const int index = std::stoi(s, &pos);
4✔
142
        if (pos != s.size()) {
4✔
UNCOV
143
            throw std::runtime_error("Invalid observation label: " + s);
×
144
        }
145
        if (index < 0) {
4✔
146
            throw std::runtime_error("Invalid observation label: " + s);
3✔
147
        }
148
        return Label{index};
2✔
149
    } catch (const std::exception&) {
2✔
150
        throw std::runtime_error("Invalid observation label: " + s);
3✔
151
    }
2✔
152
}
19✔
153

154
static InvariantPoint parse_point(const YAML::Node& node) {
18✔
155
    if (!node.IsDefined() || node.IsNull()) {
25✔
156
        return InvariantPoint::pre;
4✔
157
    }
158
    const auto s = node.as<string>();
14✔
159
    if (s == "pre") {
14✔
160
        return InvariantPoint::pre;
1✔
161
    }
162
    if (s == "post") {
12✔
163
        return InvariantPoint::post;
6✔
164
    }
UNCOV
165
    throw std::runtime_error("Invalid observation point: " + s);
×
166
}
14✔
167

168
static ObservationCheckMode parse_mode(const YAML::Node& node) {
18✔
169
    if (!node.IsDefined() || node.IsNull()) {
25✔
170
        return ObservationCheckMode::consistent;
4✔
171
    }
172
    const auto s = node.as<string>();
14✔
173
    if (s == "consistent") {
14✔
174
        return ObservationCheckMode::consistent;
5✔
175
    }
176
    if (s == "entailed") {
4✔
177
        return ObservationCheckMode::entailed;
2✔
178
    }
UNCOV
179
    throw std::runtime_error("Invalid observation mode: " + s);
×
180
}
14✔
181

182
static std::vector<TestCase::Observation> parse_observations(const YAML::Node& observe_node) {
1,472✔
183
    std::vector<TestCase::Observation> res;
1,472✔
184
    if (!observe_node.IsDefined() || observe_node.IsNull()) {
1,491✔
185
        return res;
724✔
186
    }
187
    if (!observe_node.IsSequence()) {
24✔
188
        throw std::runtime_error("observe must be a sequence");
2✔
189
    }
190
    for (const auto& item : observe_node) {
79✔
191
        if (!item.IsMap()) {
22✔
192
            throw std::runtime_error("observe item must be a map");
2✔
193
        }
194
        const Label label = parse_label_scalar(item["at"]);
21✔
195
        const InvariantPoint point = parse_point(item["point"]);
18✔
196
        const ObservationCheckMode mode = parse_mode(item["mode"]);
20✔
197
        const YAML::Node constraints_node = item["constraints"];
18✔
198
        if (!constraints_node.IsDefined() || constraints_node.IsNull()) {
26✔
199
            throw std::runtime_error("observe item missing required 'constraints' field");
2✔
200
        }
201
        if (!constraints_node.IsSequence()) {
16✔
202
            throw std::runtime_error("observe.constraints must be a sequence of strings");
2✔
203
        }
204
        const auto constraints_vec = constraints_node.as<vector<string>>();
14✔
205
        TestCase::Observation obs;
14✔
206
        obs.label = label;
14✔
207
        obs.point = point;
14✔
208
        obs.mode = mode;
14✔
209
        obs.constraints = read_invariant(constraints_vec);
21✔
210
        res.push_back(std::move(obs));
14✔
211
    }
65✔
212
    return res;
14✔
213
}
10✔
214

215
struct RawTestCase {
216
    string test_case;
217
    std::optional<string> expected_exception;
218
    std::set<string> options;
219
    vector<string> pre;
220
    vector<std::tuple<string, vector<string>>> raw_blocks;
221
    vector<string> post;
222
    std::set<string> messages;
223
    YAML::Node observe;
224
};
225

226
static vector<string> parse_block(const YAML::Node& block_node) {
1,768✔
227
    vector<string> block;
1,768✔
228
    std::istringstream is{block_node.as<string>()};
1,768✔
229
    string line;
1,768✔
230
    while (std::getline(is, line)) {
4,154✔
231
        block.emplace_back(line);
2,386✔
232
    }
233
    return block;
3,536✔
234
}
1,768✔
235

236
static auto parse_code(const YAML::Node& code_node) {
1,472✔
237
    vector<std::tuple<string, vector<string>>> res;
1,472✔
238
    for (const auto& item : code_node) {
7,364✔
239
        res.emplace_back(item.first.as<string>(), parse_block(item.second));
2,652✔
240
    }
3,240✔
241
    return res;
1,472✔
UNCOV
242
}
×
243

244
static std::set<string> as_set_empty_default(const YAML::Node& optional_node) {
2,944✔
245
    if (!optional_node.IsDefined() || optional_node.IsNull()) {
3,317✔
246
        return {};
2,198✔
247
    }
248
    return vector_to_set(optional_node.as<vector<string>>());
746✔
249
}
250

251
static std::optional<string> as_optional_string(const YAML::Node& optional_node) {
1,472✔
252
    if (!optional_node.IsDefined() || optional_node.IsNull()) {
1,477✔
253
        return std::nullopt;
1,462✔
254
    }
255
    return optional_node.as<string>();
15✔
256
}
257

258
static RawTestCase parse_case(const YAML::Node& case_node) {
1,472✔
259
    return RawTestCase{
2,208✔
260
        .test_case = case_node["test-case"].as<string>(),
261
        .expected_exception = as_optional_string(case_node["expected-exception"]),
262
        .options = as_set_empty_default(case_node["options"]),
263
        .pre = case_node["pre"].as<vector<string>>(),
264
        .raw_blocks = parse_code(case_node["code"]),
265
        .post = case_node["post"].as<vector<string>>(),
266
        .messages = as_set_empty_default(case_node["messages"]),
267
        .observe = case_node["observe"],
268
    };
2,208✔
269
}
270

271
static InstructionSeq raw_cfg_to_instruction_seq(const vector<std::tuple<string, vector<string>>>& raw_blocks) {
1,472✔
272
    std::map<string, Label> label_name_to_label;
1,472✔
273

274
    int label_index = 0;
1,472✔
275
    for (const auto& [label_name, raw_block] : raw_blocks) {
3,240✔
276
        label_name_to_label.emplace(label_name, label_index);
1,768✔
277
        // don't count large instructions as 2
278
        label_index += gsl::narrow<int>(raw_block.size());
1,768✔
279
    }
280

281
    InstructionSeq res;
1,472✔
282
    label_index = 0;
1,472✔
283
    for (const auto& [label_name, raw_block] : raw_blocks) {
3,240✔
284
        for (const string& line : raw_block) {
4,154✔
285
            try {
1,193✔
286
                const Instruction& ins = parse_instruction(line, label_name_to_label);
2,386✔
287
                if (std::holds_alternative<Undefined>(ins)) {
2,386✔
UNCOV
288
                    std::cout << "text:" << line << "; ins: " << ins << "\n";
×
289
                }
290
                res.emplace_back(label_index, ins, std::optional<btf_line_info_t>());
3,579✔
291
            } catch (const std::exception& e) {
2,386✔
UNCOV
292
                std::cout << "text:" << line << "; error: " << e.what() << "\n";
×
UNCOV
293
                res.emplace_back(label_index, Undefined{0}, std::optional<btf_line_info_t>());
×
UNCOV
294
            }
×
295
            label_index++;
2,386✔
296
        }
297
    }
298
    return res;
2,208✔
299
}
1,472✔
300

301
static ebpf_verifier_options_t raw_options_to_options(const std::set<string>& raw_options) {
1,472✔
302
    ebpf_verifier_options_t options{};
1,472✔
303

304
    // Use ~simplify for YAML tests unless otherwise specified.
305
    options.verbosity_opts.simplify = false;
1,472✔
306

307
    // All YAML tests use !setup_constraints.
308
    options.setup_constraints = false;
1,472✔
309

310
    // Default to the machine's native endianness.
311
    options.big_endian = std::endian::native == std::endian::big;
1,472✔
312

313
    // Permit test cases to not have an exit instruction.
314
    options.cfg_opts.must_have_exit = false;
1,472✔
315

316
    for (const string& name : raw_options) {
1,656✔
317
        if (name == "!allow_division_by_zero") {
184✔
318
            options.allow_division_by_zero = false;
32✔
319
        } else if (name == "termination") {
120✔
320
            options.cfg_opts.check_for_termination = true;
21✔
321
        } else if (name == "strict") {
78✔
322
            options.strict = true;
323
        } else if (name == "simplify") {
78✔
324
            options.verbosity_opts.simplify = true;
1✔
325
        } else if (name == "big_endian") {
76✔
326
            options.big_endian = true;
17✔
327
        } else if (name == "!big_endian") {
42✔
328
            options.big_endian = false;
21✔
329
        } else {
UNCOV
330
            throw std::runtime_error("Unknown option: " + name);
×
331
        }
332
    }
333
    return options;
1,472✔
334
}
335

336
static TestCase read_case(const RawTestCase& raw_case) {
1,472✔
337
    return TestCase{.name = raw_case.test_case,
1,527✔
338
                    .options = raw_options_to_options(raw_case.options),
1,472✔
339
                    .assumed_pre_invariant = read_invariant(raw_case.pre),
1,472✔
340
                    .instruction_seq = raw_cfg_to_instruction_seq(raw_case.raw_blocks),
1,472✔
341
                    .expected_post_invariant = read_invariant(raw_case.post),
1,472✔
342
                    .expected_messages = raw_case.messages,
1,472✔
343
                    .observations = parse_observations(raw_case.observe)};
1,472✔
344
}
345

346
static vector<TestCase> read_suite(const string& path) {
54✔
347
    std::ifstream f{path};
54✔
348
    vector<TestCase> res;
54✔
349
    for (const YAML::Node& config : YAML::LoadAll(f)) {
1,526✔
350
        const RawTestCase raw_case = parse_case(config);
1,472✔
351
        if (raw_case.expected_exception.has_value()) {
1,472✔
352
            TestCase tc;
10✔
353
            tc.name = raw_case.test_case;
10✔
354
            tc.expected_exception = raw_case.expected_exception;
10✔
355

356
            try {
5✔
357
                (void)read_case(raw_case);
10✔
UNCOV
358
                tc.actual_exception = std::nullopt;
×
359
            } catch (const std::exception& ex) {
10✔
360
                tc.actual_exception = ex.what();
10✔
361
            }
10✔
362

363
            res.push_back(std::move(tc));
10✔
364
        } else {
10✔
365
            res.push_back(read_case(raw_case));
2,193✔
366
        }
367
    }
1,526✔
368
    return res;
81✔
369
}
54✔
370

371
template <typename T>
UNCOV
372
static Diff<T> make_diff(const T& actual, const T& expected) {
×
373
    return Diff<T>{
374
        .unexpected = actual - expected,
375
        .unseen = expected - actual,
UNCOV
376
    };
×
UNCOV
377
}
×
378

379
std::optional<Failure> run_yaml_test_case(TestCase test_case, bool debug) {
1,452✔
380
    if (test_case.expected_exception.has_value()) {
1,452✔
381
        const std::set<string> expected_messages{"Exception: " + *test_case.expected_exception};
25✔
382
        std::set<string> actual_messages;
10✔
383
        if (test_case.actual_exception.has_value()) {
10✔
384
            actual_messages.insert("Exception: " + *test_case.actual_exception);
15✔
385
        }
386
        if (actual_messages == expected_messages) {
10✔
387
            return {};
10✔
388
        }
UNCOV
389
        return Failure{
×
390
            .invariant = make_diff(StringInvariant::top(), StringInvariant::top()),
×
391
            .messages = make_diff(actual_messages, expected_messages),
UNCOV
392
        };
×
393
    }
10✔
394

395
    thread_local_options = {};
1,442✔
396
    ThreadLocalGuard clear_thread_local_state;
721✔
397
    test_case.options.verbosity_opts.print_failures = true;
1,442✔
398
    if (debug) {
1,442✔
UNCOV
399
        test_case.options.verbosity_opts.print_invariants = true;
×
400
    }
401

402
    ebpf_context_descriptor_t context_descriptor{64, 0, 4, -1};
1,442✔
403
    EbpfProgramType program_type = make_program_type(test_case.name, &context_descriptor);
1,442✔
404

405
    ProgramInfo info{&g_platform_test, {test_map_descriptor}, program_type};
2,163✔
406
    thread_local_options = test_case.options;
1,442✔
407
    try {
721✔
408
        const Program prog = Program::from_sequence(test_case.instruction_seq, info, test_case.options);
1,442✔
409
        const AnalysisResult result = analyze(prog, test_case.assumed_pre_invariant);
1,432✔
410
        const StringInvariant actual_last_invariant = result.invariant_at(Label::exit);
1,432✔
411
        std::set<string> actual_messages;
1,432✔
412
        if (auto error = result.find_first_error()) {
1,432✔
413
            actual_messages.insert(to_string(*error));
471✔
414
        }
716✔
415
        for (const auto& [label, msgs] : result.find_unreachable(prog)) {
1,614✔
416
            for (const auto& msg : msgs) {
364✔
417
                actual_messages.insert(msg);
182✔
418
            }
419
        }
716✔
420

421
        // Evaluate optional observation checks.
422
        for (const auto& obs : test_case.observations) {
1,446✔
423
            const ObservationCheckResult check =
7✔
424
                result.check_observation_at_label(obs.label, obs.point, obs.constraints, obs.mode);
14✔
425
            if (!check.ok) {
14✔
426
                const std::string point_s = (obs.point == InvariantPoint::pre) ? "pre" : "post";
8✔
427
                const std::string mode_s = (obs.mode == ObservationCheckMode::consistent) ? "consistent" : "entailed";
5✔
428
                // Keep the message stable for YAML expectations; details are available via debug logging.
429
                actual_messages.insert(to_string(obs.label) + ": observation " + mode_s + " failed at " + point_s);
12✔
430
                if (debug && !check.message.empty()) {
4✔
UNCOV
431
                    std::cout << "Observation check failed at " << obs.label << " (" << point_s << ", " << mode_s
×
432
                              << "): " << check.message << "\n";
×
433
                }
434
            }
4✔
435
        }
14✔
436

437
        if (actual_last_invariant == test_case.expected_post_invariant &&
2,864✔
438
            actual_messages == test_case.expected_messages) {
1,432✔
439
            return {};
1,432✔
440
        }
UNCOV
441
        return Failure{
×
442
            .invariant = make_diff(actual_last_invariant, test_case.expected_post_invariant),
×
443
            .messages = make_diff(actual_messages, test_case.expected_messages),
×
444
        };
×
445
    } catch (InvalidControlFlow& ex) {
2,158✔
446
        const std::set<string> actual_messages{ex.what()};
25✔
447
        if (test_case.expected_post_invariant == StringInvariant::top() &&
30✔
448
            actual_messages == test_case.expected_messages) {
10✔
449
            return {};
10✔
450
        }
451
        return Failure{
×
452
            .invariant = make_diff(StringInvariant::top(), test_case.expected_post_invariant),
×
UNCOV
453
            .messages = make_diff(actual_messages, test_case.expected_messages),
×
UNCOV
454
        };
×
455
    } catch (const std::exception& ex) {
10✔
UNCOV
456
        const std::set<string> actual_messages{std::string{"Exception: "} + ex.what()};
×
UNCOV
457
        return Failure{
×
UNCOV
458
            .invariant = make_diff(StringInvariant::top(), test_case.expected_post_invariant),
×
UNCOV
459
            .messages = make_diff(actual_messages, test_case.expected_messages),
×
UNCOV
460
        };
×
UNCOV
461
    }
×
462
}
2,198✔
463

464
template <typename T>
465
    requires std::is_trivially_copyable_v<T>
466
static vector<T> vector_of(const std::vector<std::byte>& bytes) {
467
    auto data = bytes.data();
468
    const auto size = bytes.size();
469
    if (size % sizeof(T) != 0 || size > std::numeric_limits<uint32_t>::max() || !data) {
470
        throw std::runtime_error("Invalid argument to vector_of");
471
    }
472
    return {reinterpret_cast<const T*>(data), reinterpret_cast<const T*>(data + size)};
473
}
474

475
template <std::signed_integral TS>
476
void add_stack_variable(std::set<std::string>& more, int& offset, const std::vector<std::byte>& memory_bytes) {
138✔
477
    using TU = std::make_unsigned_t<TS>;
478
    constexpr size_t size = sizeof(TS);
138✔
479
    static_assert(sizeof(TU) == size);
480
    const auto src = memory_bytes.data() + offset + memory_bytes.size() - EBPF_TOTAL_STACK_SIZE;
138✔
481
    TS svalue;
482
    std::memcpy(&svalue, src, size);
138✔
483
    TU uvalue;
484
    std::memcpy(&uvalue, src, size);
138✔
485
    const auto range = "s[" + std::to_string(offset) + "..." + std::to_string(offset + size - 1) + "]";
345✔
486
    more.insert(range + ".svalue=" + std::to_string(svalue));
207✔
487
    more.insert(range + ".uvalue=" + std::to_string(uvalue));
207✔
488
    offset += size;
138✔
489
}
138✔
490

491
StringInvariant stack_contents_invariant(const std::vector<std::byte>& memory_bytes) {
80✔
492
    std::set<std::string> more = {"r1.type=stack",
40✔
493
                                  "r1.stack_offset=" + std::to_string(EBPF_TOTAL_STACK_SIZE - memory_bytes.size()),
120✔
494
                                  "r1.stack_numeric_size=" + std::to_string(memory_bytes.size()),
160✔
495
                                  "r10.type=stack",
496
                                  "r10.stack_offset=" + std::to_string(EBPF_TOTAL_STACK_SIZE),
120✔
497
                                  "s[" + std::to_string(EBPF_TOTAL_STACK_SIZE - memory_bytes.size()) + "..." +
240✔
498
                                      std::to_string(EBPF_TOTAL_STACK_SIZE - 1) + "].type=number"};
760✔
499

500
    int offset = EBPF_TOTAL_STACK_SIZE - gsl::narrow<int>(memory_bytes.size());
120✔
501
    if (offset % 2 != 0) {
80✔
502
        add_stack_variable<int8_t>(more, offset, memory_bytes);
8✔
503
    }
504
    if (offset % 4 != 0) {
80✔
505
        add_stack_variable<int16_t>(more, offset, memory_bytes);
20✔
506
    }
507
    if (offset % 8 != 0) {
80✔
508
        add_stack_variable<int32_t>(more, offset, memory_bytes);
26✔
509
    }
510
    while (offset < EBPF_TOTAL_STACK_SIZE) {
164✔
511
        add_stack_variable<int64_t>(more, offset, memory_bytes);
84✔
512
    }
513

514
    return StringInvariant(std::move(more));
200✔
515
}
560✔
516

517
// Helper to convert uint8_t memory to stack invariant
518
static StringInvariant stack_contents_invariant(const std::vector<uint8_t>& memory_bytes) {
80✔
519
    std::vector<std::byte> bytes(memory_bytes.size());
80✔
520
    std::transform(memory_bytes.begin(), memory_bytes.end(), bytes.begin(),
80✔
521
                   [](uint8_t b) { return static_cast<std::byte>(b); });
412✔
522
    return stack_contents_invariant(bytes);
120✔
523
}
80✔
524

525
ConformanceTestResult run_conformance_test_case(const std::vector<uint8_t>& memory_bytes,
626✔
526
                                                std::span<const EbpfInst> instructions, bool debug) {
527
    ebpf_context_descriptor_t context_descriptor{64, -1, -1, -1};
626✔
528
    EbpfProgramType program_type = make_program_type("conformance_check", &context_descriptor);
939✔
529

530
    ProgramInfo info{&g_platform_test, {}, program_type};
626✔
531

532
    // Copy instructions into a local vector for RawProgram.
533
    std::vector<EbpfInst> insts(instructions.begin(), instructions.end());
626✔
534

535
    StringInvariant pre_invariant = StringInvariant::top();
626✔
536

537
    if (!memory_bytes.empty()) {
626✔
538
        if (memory_bytes.size() > EBPF_TOTAL_STACK_SIZE) {
80✔
UNCOV
539
            std::cerr << "memory size overflow\n";
×
UNCOV
540
            return {};
×
541
        }
542
        pre_invariant = pre_invariant + stack_contents_invariant(memory_bytes);
200✔
543
    }
544
    RawProgram raw_prog{.prog = insts};
626✔
545
    ebpf_platform_t platform = g_ebpf_platform_linux;
626✔
546
    platform.supported_conformance_groups |= bpf_conformance_groups_t::callx;
626✔
547
    raw_prog.info.platform = &platform;
626✔
548

549
    // Convert the raw program section to a set of instructions.
550
    std::variant<InstructionSeq, std::string> prog_or_error = unmarshal(raw_prog, {});
626✔
551
    if (auto prog = std::get_if<std::string>(&prog_or_error)) {
626✔
552
        std::cerr << "unmarshaling error at " << *prog << "\n";
×
553
        return ConformanceTestResult{.success = false, .r0_value = Interval::top(), .error_reason = *prog};
313✔
554
    }
555

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

558
    ebpf_verifier_options_t options{};
626✔
559
    if (debug) {
626✔
UNCOV
560
        print(inst_seq, std::cout, {});
×
UNCOV
561
        options.verbosity_opts.print_failures = true;
×
UNCOV
562
        options.verbosity_opts.print_invariants = true;
×
563
        options.verbosity_opts.simplify = false;
×
564
    }
565

566
    try {
313✔
567
        const Program prog = Program::from_sequence(inst_seq, info, options);
626✔
568
        const AnalysisResult result = analyze(prog, pre_invariant);
626✔
569
        return ConformanceTestResult{.success = !result.failed, .r0_value = result.exit_value};
626✔
570
    } catch (const std::exception& ex) {
626✔
571
        // Catch exceptions thrown in ebpf_domain.cpp.
572
        return ConformanceTestResult{.success = false, .r0_value = Interval::top(), .error_reason = ex.what()};
×
UNCOV
573
    }
×
574
}
1,252✔
575

UNCOV
576
void print_failure(const Failure& failure, std::ostream& os) {
×
577
    constexpr auto INDENT = "  ";
×
UNCOV
578
    if (!failure.invariant.unexpected.empty()) {
×
UNCOV
579
        os << "Unexpected properties:\n" << INDENT << failure.invariant.unexpected << "\n";
×
580
    } else {
581
        os << "Unexpected properties: None\n";
×
582
    }
583
    if (!failure.invariant.unseen.empty()) {
×
UNCOV
584
        os << "Unseen properties:\n" << INDENT << failure.invariant.unseen << "\n";
×
585
    } else {
586
        os << "Unseen properties: None\n";
×
587
    }
588

589
    if (!failure.messages.unexpected.empty()) {
×
590
        os << "Unexpected messages:\n";
×
591
        for (const auto& item : failure.messages.unexpected) {
×
592
            os << INDENT << item << "\n";
×
593
        }
594
    } else {
595
        os << "Unexpected messages: None\n";
×
596
    }
597

UNCOV
598
    if (!failure.messages.unseen.empty()) {
×
UNCOV
599
        os << "Unseen messages:\n";
×
UNCOV
600
        for (const auto& item : failure.messages.unseen) {
×
UNCOV
601
            os << INDENT << item << "\n";
×
602
        }
603
    } else {
UNCOV
604
        os << "Unseen messages: None\n";
×
605
    }
606
}
×
607

608
void foreach_suite(const string& path, const std::function<void(const TestCase&)>& f) {
1,452✔
609
    static std::map<string, vector<TestCase>> cache;
1,452✔
610
    auto [it, inserted] = cache.try_emplace(path);
1,452✔
611
    if (inserted) {
1,452✔
612
        try {
27✔
613
            it->second = read_suite(path);
54✔
UNCOV
614
        } catch (...) {
×
UNCOV
615
            cache.erase(it);
×
UNCOV
616
            throw;
×
UNCOV
617
        }
×
618
    }
619
    for (const TestCase& test_case : it->second) {
86,726✔
620
        f(test_case);
127,911✔
621
    }
622
}
1,452✔
623
} // 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