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

Alan-Jowett / ebpf-verifier / 18949625295

28 Oct 2025 10:33AM UTC coverage: 87.448% (-1.0%) from 88.47%
18949625295

push

github

elazarg
Bump CLI11 to v2.6.1

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

9022 of 10317 relevant lines covered (87.45%)

10783407.68 hits per line

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

94.06
/src/crab/ebpf_transformer.cpp
1
// Copyright (c) Prevail Verifier contributors.
2
// SPDX-License-Identifier: MIT
3

4
// This file is eBPF-specific, not derived from CRAB.
5

6
#include <bitset>
7
#include <optional>
8
#include <utility>
9
#include <vector>
10

11
#include "boost/endian/conversion.hpp"
12

13
#include "arith/dsl_syntax.hpp"
14
#include "config.hpp"
15
#include "crab/array_domain.hpp"
16
#include "crab/ebpf_domain.hpp"
17
#include "crab/var_registry.hpp"
18
#include "crab_utils/num_safety.hpp"
19
#include "ir/unmarshal.hpp"
20
#include "platform.hpp"
21
#include "string_constraints.hpp"
22

23
namespace prevail {
24
class EbpfTransformer final {
25
    EbpfDomain& dom;
26
    // shorthands:
27
    ArrayDomain& stack;
28

29
  public:
30
    explicit EbpfTransformer(EbpfDomain& _dom) : dom(_dom), stack(_dom.stack) {}
239,000✔
31

32
    // abstract transformers
33
    void operator()(const Assume&);
34

35
    void operator()(const Atomic&);
36

37
    void operator()(const Bin&);
38

39
    void operator()(const Call&);
40

41
    void operator()(const CallLocal&);
42

43
    void operator()(const Callx&);
44

45
    void operator()(const Exit&);
46

47
    void operator()(const IncrementLoopCounter&);
48

49
    void operator()(const Jmp&) const;
50

51
    void operator()(const LoadMapFd&);
52

53
    void operator()(const LoadMapAddress&);
54

55
    void operator()(const Mem&);
56

57
    void operator()(const Packet&);
58

59
    void operator()(const Un&);
60

61
    void operator()(const Undefined&);
62

63
    void initialize_loop_counter(const Label& label);
64

65
  private:
66
    /// Forget everything about all offset variables for a given register.
67
    void scratch_caller_saved_registers();
68

69
    void save_callee_saved_registers(const std::string& prefix);
70

71
    void restore_callee_saved_registers(const std::string& prefix);
72

73
    void havoc_subprogram_stack(const std::string& prefix);
74

75
    void forget_packet_pointers();
76

77
    void do_load_mapfd(const Reg& dst_reg, int mapfd, bool maybe_null);
78

79
    void do_load_map_address(const Reg& dst_reg, int mapfd, int32_t offset);
80

81
    void assign_valid_ptr(const Reg& dst_reg, bool maybe_null);
82

83
    static void recompute_stack_numeric_size(TypeToNumDomain& rcp, ArrayDomain& stack, const Reg& reg);
84

85
    static void recompute_stack_numeric_size(TypeToNumDomain& rcp, ArrayDomain& stack, Variable type_variable);
86

87
    static void do_load_stack(TypeToNumDomain& rcp, ArrayDomain& stack, const Reg& target_reg,
88
                              const LinearExpression& addr, int width, const Reg& src_reg);
89

90
    void do_load(const Mem& b, const Reg& target_reg);
91

92
    static void do_store_stack(TypeToNumDomain& rcp, ArrayDomain& stack, const LinearExpression& symb_addr,
93
                               int exact_width, const LinearExpression& val_svalue, const LinearExpression& val_uvalue,
94
                               const std::optional<Reg>& opt_val_reg);
95

96
    void do_mem_store(const Mem& b, const LinearExpression& val_svalue, const LinearExpression& val_uvalue,
97
                      const std::optional<Reg>& opt_val_reg);
98

99
    void add(const Reg& dst_reg, int imm, int finite_width);
100

101
    void shl(const Reg& dst_reg, int imm, int finite_width);
102

103
    void lshr(const Reg& dst_reg, int imm, int finite_width);
104

105
    void ashr(const Reg& dst_reg, const LinearExpression& right_svalue, int finite_width);
106
}; // end EbpfDomain
107

108
void ebpf_domain_transform(EbpfDomain& inv, const Instruction& ins) {
386,966✔
109
    if (inv.is_bottom()) {
386,966✔
110
        return;
147,998✔
111
    }
112
    const auto pre = inv;
238,968✔
113
    std::visit(EbpfTransformer{inv}, ins);
238,968✔
114
    if (inv.is_bottom() && !std::holds_alternative<Assume>(ins)) {
238,968✔
115
        // Fail. raise an exception to stop the analysis.
116
        std::stringstream msg;
×
117
        msg << "Bug! pre-invariant:\n"
×
118
            << pre << "\n followed by instruction: " << ins << "\n"
×
119
            << "leads to bottom";
×
120
        throw std::logic_error(msg.str());
×
121
    }
×
122
}
238,968✔
123

124
void EbpfTransformer::scratch_caller_saved_registers() {
10,636✔
125
    for (uint8_t i = R1_ARG; i <= R5_ARG; i++) {
63,816✔
126
        dom.rcp.havoc_register(Reg{i});
53,180✔
127
    }
128
}
10,636✔
129

130
/// Create variables specific to the new call stack frame that store
131
/// copies of the states of r6 through r9.
132
void EbpfTransformer::save_callee_saved_registers(const std::string& prefix) {
56✔
133
    // TODO: define location `stack_frame_var`, and pass to dom.rcp.assign().
134
    //       Similarly in restore_callee_saved_registers
135
    for (uint8_t r = R6; r <= R9; r++) {
280✔
136
        if (dom.rcp.types.is_initialized(Reg{r})) {
224✔
137
            const Variable type_var = variable_registry->type_reg(r);
26✔
138
            dom.rcp.assign_type(variable_registry->stack_frame_var(DataKind::types, r, prefix), type_var);
26✔
139
            for (const TypeEncoding type : dom.rcp.types.iterate_types(Reg{r})) {
52✔
140
                auto kinds = type_to_kinds.at(type);
26✔
141
                kinds.push_back(DataKind::uvalues);
26✔
142
                kinds.push_back(DataKind::svalues);
26✔
143
                for (const DataKind kind : kinds) {
104✔
144
                    const Variable src_var = variable_registry->reg(kind, r);
78✔
145
                    const Variable dst_var = variable_registry->stack_frame_var(kind, r, prefix);
78✔
146
                    if (!dom.rcp.values.eval_interval(src_var).is_top()) {
156✔
147
                        dom.rcp.values.assign(dst_var, src_var);
108✔
148
                    }
149
                }
150
            }
52✔
151
        }
152
    }
153
}
56✔
154

155
void EbpfTransformer::restore_callee_saved_registers(const std::string& prefix) {
54✔
156
    for (uint8_t r = R6; r <= R9; r++) {
270✔
157
        Reg reg{r};
216✔
158
        const Variable type_var = variable_registry->stack_frame_var(DataKind::types, r, prefix);
216✔
159
        if (dom.rcp.types.is_initialized(type_var)) {
216✔
160
            dom.rcp.assign_type(reg, type_var);
26✔
161
            for (const TypeEncoding type : dom.rcp.types.iterate_types(reg)) {
52✔
162
                auto kinds = type_to_kinds.at(type);
26✔
163
                kinds.push_back(DataKind::uvalues);
26✔
164
                kinds.push_back(DataKind::svalues);
26✔
165
                for (const DataKind kind : kinds) {
104✔
166
                    const Variable src_var = variable_registry->stack_frame_var(kind, r, prefix);
78✔
167
                    const Variable dst_var = variable_registry->reg(kind, r);
78✔
168
                    if (!dom.rcp.values.eval_interval(src_var).is_top()) {
156✔
169
                        dom.rcp.values.assign(dst_var, src_var);
108✔
170
                    } else {
171
                        dom.rcp.values.havoc(dst_var);
6✔
172
                    }
173
                    dom.rcp.values.havoc(src_var);
78✔
174
                }
175
            }
52✔
176
        }
177
        dom.rcp.types.havoc_type(type_var);
216✔
178
    }
179
}
54✔
180

181
void EbpfTransformer::havoc_subprogram_stack(const std::string& prefix) {
54✔
182
    const Variable r10_stack_offset = reg_pack(R10_STACK_POINTER).stack_offset;
54✔
183
    const auto intv = dom.rcp.values.eval_interval(r10_stack_offset);
54✔
184
    if (!intv.is_singleton()) {
54✔
185
        return;
32✔
186
    }
187
    const int64_t stack_start = intv.singleton()->cast_to<int64_t>() - EBPF_SUBPROGRAM_STACK_SIZE;
22✔
188
    stack.havoc_type(dom.rcp.types, Interval{stack_start}, Interval{EBPF_SUBPROGRAM_STACK_SIZE});
33✔
189
    for (const DataKind kind : iterate_kinds()) {
242✔
190
        stack.havoc(dom.rcp.values, kind, Interval{stack_start}, Interval{EBPF_SUBPROGRAM_STACK_SIZE});
440✔
191
    }
22✔
192
}
54✔
193

194
void EbpfTransformer::forget_packet_pointers() {
40✔
195
    dom.rcp.havoc_all_locations_having_type(T_PACKET);
40✔
196
    dom.initialize_packet();
40✔
197
}
40✔
198

199
/** Linear constraint for a pointer comparison.
200
 */
201
static LinearConstraint assume_cst_offsets_reg(const Condition::Op op, const Variable dst_offset,
1,876✔
202
                                               const Variable src_offset) {
203
    using namespace dsl_syntax;
938✔
204
    using Op = Condition::Op;
938✔
205
    switch (op) {
1,876✔
206
    case Op::EQ: return eq(dst_offset, src_offset);
6✔
207
    case Op::NE: return neq(dst_offset, src_offset);
10✔
208
    case Op::GE: return dst_offset >= src_offset;
6✔
209
    case Op::SGE: return dst_offset >= src_offset; // pointer comparison is unsigned
×
210
    case Op::LE: return dst_offset <= src_offset;
926✔
211
    case Op::SLE: return dst_offset <= src_offset; // pointer comparison is unsigned
×
212
    case Op::GT: return dst_offset > src_offset;
1,389✔
213
    case Op::SGT: return dst_offset > src_offset; // pointer comparison is unsigned
×
214
    case Op::SLT: return src_offset > dst_offset;
×
215
    // Note: reverse the test as a workaround strange lookup:
216
    case Op::LT: return src_offset > dst_offset; // FIX unsigned
6✔
217
    default: return dst_offset - dst_offset == 0;
×
218
    }
219
}
220

221
void EbpfTransformer::operator()(const Assume& s) {
37,288✔
222
    if (dom.is_bottom()) {
37,288✔
223
        return;
×
224
    }
225
    const Condition cond = s.cond;
37,288✔
226
    const auto dst = reg_pack(cond.left);
37,288✔
227
    if (const auto psrc_reg = std::get_if<Reg>(&cond.right)) {
37,288✔
228
        const auto src_reg = *psrc_reg;
8,838✔
229
        const auto src = reg_pack(src_reg);
8,838✔
230
        // This should have been checked by EbpfChecker
231
        assert(dom.rcp.types.same_type(cond.left, std::get<Reg>(cond.right)));
8,838✔
232
        dom.rcp = dom.rcp.join_over_types(cond.left, [&](TypeToNumDomain& rcp, const TypeEncoding type) {
17,676✔
233
            if (type == T_NUM) {
8,842✔
234
                for (const LinearConstraint& cst :
11,830✔
235
                     rcp.values->assume_cst_reg(cond.op, cond.is64, dst.svalue, dst.uvalue, src.svalue, src.uvalue)) {
20,177✔
236
                    rcp.values.add_constraint(cst);
9,728✔
237
                }
6,966✔
238
            } else {
239
                // Either pointers to a singleton region,
240
                // or an equality comparison on map descriptors/pointers to non-singleton locations
241
                if (const auto dst_offset = get_type_offset_variable(cond.left, type)) {
1,876✔
242
                    if (const auto src_offset = get_type_offset_variable(src_reg, type)) {
1,876✔
243
                        rcp.values.add_constraint(
1,876✔
244
                            assume_cst_offsets_reg(cond.op, dst_offset.value(), src_offset.value()));
3,752✔
245
                    }
246
                }
247
            }
248
        });
17,680✔
249
    } else {
250
        const int64_t imm = gsl::narrow_cast<int64_t>(std::get<Imm>(cond.right).v);
28,450✔
251
        for (const LinearConstraint& cst :
59,541✔
252
             dom.rcp.values->assume_cst_imm(cond.op, cond.is64, dst.svalue, dst.uvalue, imm)) {
104,857✔
253
            dom.rcp.values.add_constraint(cst);
62,182✔
254
        }
28,450✔
255
    }
256
}
257

258
void EbpfTransformer::operator()(const Undefined& a) {}
2,325✔
259

260
// Simple truncation function usable with swap_endianness().
261
template <class T>
262
constexpr T truncate(T x) noexcept {
32✔
263
    return x;
32✔
264
}
265

266
void EbpfTransformer::operator()(const Un& stmt) {
1,004✔
267
    if (dom.is_bottom()) {
1,004✔
268
        return;
×
269
    }
270
    const auto dst = reg_pack(stmt.dst);
1,004✔
271
    auto swap_endianness = [&](const Variable v, auto be_or_le) {
2,190✔
272
        if (dom.rcp.types.type_is_number(stmt.dst)) {
1,688✔
273
            if (const auto n = dom.rcp.values.eval_interval(v).singleton()) {
3,502✔
274
                if (n->fits_cast_to<int64_t>()) {
252✔
275
                    dom.rcp.values.set(v, Interval{be_or_le(n->cast_to<int64_t>())});
252✔
276
                    return;
252✔
277
                }
278
            }
279
        }
280
        dom.rcp.values.havoc(v);
1,436✔
281
        dom.rcp.havoc_offsets(stmt.dst);
1,436✔
282
    };
1,004✔
283
    // Swap bytes if needed.  For 64-bit types we need the weights to fit in a
284
    // signed int64, but for smaller types we don't want sign extension,
285
    // so we use unsigned which still fits in a signed int64.
286
    switch (stmt.op) {
1,004✔
287
    case Un::Op::BE16:
648✔
288
        if (!thread_local_options.big_endian) {
648✔
289
            swap_endianness(dst.svalue, boost::endian::endian_reverse<uint16_t>);
644✔
290
            swap_endianness(dst.uvalue, boost::endian::endian_reverse<uint16_t>);
644✔
291
        } else {
292
            swap_endianness(dst.svalue, truncate<uint16_t>);
4✔
293
            swap_endianness(dst.uvalue, truncate<uint16_t>);
4✔
294
        }
295
        break;
324✔
296
    case Un::Op::BE32:
122✔
297
        if (!thread_local_options.big_endian) {
122✔
298
            swap_endianness(dst.svalue, boost::endian::endian_reverse<uint32_t>);
120✔
299
            swap_endianness(dst.uvalue, boost::endian::endian_reverse<uint32_t>);
120✔
300
        } else {
301
            swap_endianness(dst.svalue, truncate<uint32_t>);
2✔
302
            swap_endianness(dst.uvalue, truncate<uint32_t>);
2✔
303
        }
304
        break;
61✔
305
    case Un::Op::BE64:
28✔
306
        if (!thread_local_options.big_endian) {
28✔
307
            swap_endianness(dst.svalue, boost::endian::endian_reverse<int64_t>);
26✔
308
            swap_endianness(dst.uvalue, boost::endian::endian_reverse<uint64_t>);
26✔
309
        }
310
        break;
14✔
311
    case Un::Op::LE16:
14✔
312
        if (thread_local_options.big_endian) {
14✔
313
            swap_endianness(dst.svalue, boost::endian::endian_reverse<uint16_t>);
2✔
314
            swap_endianness(dst.uvalue, boost::endian::endian_reverse<uint16_t>);
2✔
315
        } else {
316
            swap_endianness(dst.svalue, truncate<uint16_t>);
12✔
317
            swap_endianness(dst.uvalue, truncate<uint16_t>);
12✔
318
        }
319
        break;
7✔
320
    case Un::Op::LE32:
10✔
321
        if (thread_local_options.big_endian) {
10✔
322
            swap_endianness(dst.svalue, boost::endian::endian_reverse<uint32_t>);
2✔
323
            swap_endianness(dst.uvalue, boost::endian::endian_reverse<uint32_t>);
2✔
324
        } else {
325
            swap_endianness(dst.svalue, truncate<uint32_t>);
8✔
326
            swap_endianness(dst.uvalue, truncate<uint32_t>);
8✔
327
        }
328
        break;
5✔
329
    case Un::Op::LE64:
8✔
330
        if (thread_local_options.big_endian) {
8✔
331
            swap_endianness(dst.svalue, boost::endian::endian_reverse<int64_t>);
2✔
332
            swap_endianness(dst.uvalue, boost::endian::endian_reverse<uint64_t>);
2✔
333
        }
334
        break;
4✔
335
    case Un::Op::SWAP16:
10✔
336
        swap_endianness(dst.svalue, boost::endian::endian_reverse<uint16_t>);
10✔
337
        swap_endianness(dst.uvalue, boost::endian::endian_reverse<uint16_t>);
10✔
338
        break;
5✔
339
    case Un::Op::SWAP32:
6✔
340
        swap_endianness(dst.svalue, boost::endian::endian_reverse<uint32_t>);
6✔
341
        swap_endianness(dst.uvalue, boost::endian::endian_reverse<uint32_t>);
6✔
342
        break;
3✔
343
    case Un::Op::SWAP64:
6✔
344
        swap_endianness(dst.svalue, boost::endian::endian_reverse<int64_t>);
6✔
345
        swap_endianness(dst.uvalue, boost::endian::endian_reverse<uint64_t>);
6✔
346
        break;
3✔
347
    case Un::Op::NEG:
152✔
348
        dom.rcp.values->neg(dst.svalue, dst.uvalue, stmt.is64 ? 64 : 32);
175✔
349
        dom.rcp.havoc_offsets(stmt.dst);
152✔
350
        break;
76✔
351
    }
352
}
353

354
void EbpfTransformer::operator()(const Exit& a) {
1,284✔
355
    if (dom.is_bottom()) {
1,284✔
356
        return;
1,230✔
357
    }
358
    // Clean up any state for the current stack frame.
359
    const std::string prefix = a.stack_frame_prefix;
1,284✔
360
    if (prefix.empty()) {
1,284✔
361
        return;
1,230✔
362
    }
363
    havoc_subprogram_stack(prefix);
54✔
364
    restore_callee_saved_registers(prefix);
54✔
365

366
    // Restore r10.
367
    constexpr Reg r10_reg{R10_STACK_POINTER};
54✔
368
    add(r10_reg, EBPF_SUBPROGRAM_STACK_SIZE, 64);
54✔
369
}
1,284✔
370

371
void EbpfTransformer::operator()(const Jmp&) const {
11,100✔
372
    // This is a NOP. It only exists to hold the jump preconditions.
373
}
11,100✔
374

375
void EbpfTransformer::operator()(const Packet& a) {
236✔
376
    if (dom.is_bottom()) {
236✔
377
        return;
×
378
    }
379
    constexpr Reg r0_reg{R0_RETURN_VALUE};
236✔
380
    dom.rcp.havoc_register_except_type(r0_reg);
236✔
381
    dom.rcp.assign_type(r0_reg, T_NUM);
236✔
382
    scratch_caller_saved_registers();
236✔
383
}
384

385
void EbpfTransformer::do_load_stack(TypeToNumDomain& rcp, ArrayDomain& stack, const Reg& target_reg,
12,028✔
386
                                    const LinearExpression& symb_addr, const int width, const Reg& src_reg) {
387
    const Interval addr = rcp.values.eval_interval(symb_addr);
12,028✔
388
    using namespace dsl_syntax;
6,014✔
389
    if (rcp.values.entail(width <= reg_pack(src_reg).stack_numeric_size)) {
24,056✔
390
        rcp.assign_type(target_reg, T_NUM);
454✔
391
    } else {
392
        rcp.assign_type(target_reg, stack.load_type(addr, width));
17,361✔
393
        if (!rcp.types.is_initialized(target_reg)) {
11,574✔
394
            // We don't know what we loaded, so just havoc the destination register.
395
            rcp.havoc_register(target_reg);
4✔
396
            return;
4✔
397
        }
398
    }
399

400
    const RegPack& target = reg_pack(target_reg);
12,024✔
401
    if (width == 1 || width == 2 || width == 4 || width == 8) {
12,024✔
402
        // Use the addr before we havoc the destination register since we might be getting the
403
        // addr from that same register.
404
        const std::optional<LinearExpression> sresult = stack.load(rcp.values, DataKind::svalues, addr, width);
12,024✔
405
        const std::optional<LinearExpression> uresult = stack.load(rcp.values, DataKind::uvalues, addr, width);
12,024✔
406
        rcp.havoc_register_except_type(target_reg);
12,024✔
407
        rcp.values.assign(target.svalue, sresult);
12,024✔
408
        rcp.values.assign(target.uvalue, uresult);
12,024✔
409
        for (const TypeEncoding type : rcp.types.iterate_types(target_reg)) {
24,064✔
410
            for (const auto& kind : type_to_kinds.at(type)) {
15,632✔
411
                const Variable dst_var = variable_registry->reg(kind, target_reg.v);
3,592✔
412
                rcp.values.assign(dst_var, stack.load(rcp.values, kind, addr, width));
5,388✔
413
            }
414
        }
12,024✔
415
    } else {
12,024✔
416
        rcp.havoc_register_except_type(target_reg);
×
417
    }
418
}
12,028✔
419

420
static void do_load_ctx(TypeToNumDomain& rcp, const Reg& target_reg, const LinearExpression& addr_vague,
3,768✔
421
                        const int width) {
422
    using namespace dsl_syntax;
1,884✔
423
    if (rcp.values.is_bottom()) {
3,768✔
424
        return;
1,904✔
425
    }
426

427
    const ebpf_context_descriptor_t* desc = thread_local_program_info->type.context_descriptor;
3,768✔
428

429
    const RegPack& target = reg_pack(target_reg);
3,768✔
430

431
    if (desc->end < 0) {
3,768✔
432
        rcp.havoc_register(target_reg);
290✔
433
        rcp.assign_type(target_reg, T_NUM);
290✔
434
        return;
290✔
435
    }
436

437
    const Interval interval = rcp.values.eval_interval(addr_vague);
3,478✔
438
    const std::optional<Number> maybe_addr = interval.singleton();
3,478✔
439
    rcp.havoc_register(target_reg);
3,478✔
440

441
    const bool may_touch_ptr =
1,739✔
442
        interval.contains(desc->data) || interval.contains(desc->meta) || interval.contains(desc->end);
6,017✔
443

444
    if (!maybe_addr) {
3,478✔
445
        if (may_touch_ptr) {
×
446
            rcp.types.havoc_type(target_reg);
×
447
        } else {
448
            rcp.assign_type(target_reg, T_NUM);
×
449
        }
450
        return;
×
451
    }
452

453
    const Number addr = *maybe_addr;
3,478✔
454

455
    // We use offsets for packet data, data_end, and meta during verification,
456
    // but at runtime they will be 64-bit pointers.  We can use the offset values
457
    // for verification like we use map_fd's as a proxy for maps which
458
    // at runtime are actually 64-bit memory pointers.
459
    const int offset_width = desc->end - desc->data;
3,478✔
460
    if (addr == desc->data) {
3,478✔
461
        if (width == offset_width) {
928✔
462
            rcp.values.assign(target.packet_offset, 0);
1,392✔
463
        }
464
    } else if (addr == desc->end) {
2,550✔
465
        if (width == offset_width) {
914✔
466
            rcp.values.assign(target.packet_offset, variable_registry->packet_size());
1,362✔
467
        }
468
    } else if (addr == desc->meta) {
1,636✔
469
        if (width == offset_width) {
22✔
470
            rcp.values.assign(target.packet_offset, variable_registry->meta_offset());
33✔
471
        }
472
    } else {
473
        if (may_touch_ptr) {
1,614✔
474
            rcp.types.havoc_type(target_reg);
×
475
        } else {
476
            rcp.assign_type(target_reg, T_NUM);
1,614✔
477
        }
478
        return;
1,614✔
479
    }
480
    if (width == offset_width) {
1,864✔
481
        rcp.assign_type(target_reg, T_PACKET);
1,858✔
482
        rcp.values.add_constraint(4098 <= target.svalue);
3,716✔
483
        rcp.values.add_constraint(target.svalue <= PTR_MAX);
2,787✔
484
    }
485
}
7,638✔
486

487
static void do_load_packet_or_shared(TypeToNumDomain& rcp, const Reg& target_reg, const int width) {
11,256✔
488
    if (rcp.values.is_bottom()) {
11,256✔
489
        return;
×
490
    }
491
    const RegPack& target = reg_pack(target_reg);
11,256✔
492

493
    rcp.havoc_register(target_reg);
11,256✔
494
    rcp.assign_type(target_reg, T_NUM);
11,256✔
495

496
    // A 1 or 2 byte copy results in a limited range of values that may be used as array indices.
497
    if (width == 1 || width == 2) {
11,256✔
498
        const Interval full = Interval::unsigned_int(width * 8);
7,096✔
499
        rcp.values.set(target.svalue, full);
7,096✔
500
        rcp.values.set(target.uvalue, full);
7,096✔
501
    }
7,096✔
502
}
503

504
void EbpfTransformer::do_load(const Mem& b, const Reg& target_reg) {
26,880✔
505
    using namespace dsl_syntax;
13,440✔
506

507
    const auto mem_reg = reg_pack(b.access.basereg);
26,880✔
508
    const int width = b.access.width;
26,880✔
509
    const int offset = b.access.offset;
26,880✔
510

511
    if (b.access.basereg.v == R10_STACK_POINTER) {
26,880✔
512
        const LinearExpression addr = mem_reg.stack_offset + offset;
17,262✔
513
        do_load_stack(dom.rcp, stack, target_reg, addr, width, b.access.basereg);
11,508✔
514
        return;
11,508✔
515
    }
11,508✔
516
    dom.rcp = dom.rcp.join_over_types(b.access.basereg, [&](TypeToNumDomain& rcp, TypeEncoding type) {
30,744✔
517
        switch (type) {
15,544✔
518
        case T_UNINIT: return;
519
        case T_MAP: return;
520
        case T_MAP_PROGRAMS: return;
521
        case T_NUM: return;
522
        case T_CTX: {
3,768✔
523
            LinearExpression addr = mem_reg.ctx_offset + offset;
5,652✔
524
            do_load_ctx(rcp, target_reg, addr, width);
3,768✔
525
            break;
3,768✔
526
        }
3,768✔
527
        case T_STACK: {
520✔
528
            LinearExpression addr = mem_reg.stack_offset + offset;
780✔
529
            do_load_stack(rcp, stack, target_reg, addr, width, b.access.basereg);
520✔
530
            break;
520✔
531
        }
520✔
532
        case T_PACKET: {
3,332✔
533
            LinearExpression addr = mem_reg.packet_offset + offset;
4,998✔
534
            do_load_packet_or_shared(rcp, target_reg, width);
3,332✔
535
            break;
3,332✔
536
        }
3,332✔
537
        case T_SHARED: {
7,924✔
538
            LinearExpression addr = mem_reg.shared_offset + offset;
11,886✔
539
            do_load_packet_or_shared(rcp, target_reg, width);
7,924✔
540
            break;
7,924✔
541
        }
7,924✔
542
        }
543
    });
15,372✔
544
}
545

546
void EbpfTransformer::do_store_stack(TypeToNumDomain& rcp, ArrayDomain& stack, const LinearExpression& symb_addr,
24,184✔
547
                                     const int exact_width, const LinearExpression& val_svalue,
548
                                     const LinearExpression& val_uvalue, const std::optional<Reg>& opt_val_reg) {
549
    const Interval addr = rcp.values.eval_interval(symb_addr);
24,184✔
550
    const Interval width{exact_width};
24,184✔
551
    // no aliasing of val - we don't move from stack to stack, so we can just havoc first
552
    stack.havoc_type(rcp.types, addr, width);
24,184✔
553
    for (const DataKind kind : iterate_kinds()) {
266,024✔
554
        if (kind == DataKind::svalues || kind == DataKind::uvalues) {
241,840✔
555
            continue;
48,368✔
556
        }
557
        stack.havoc(rcp.values, kind, addr, width);
193,472✔
558
    }
12,092✔
559
    bool must_be_num = false;
24,184✔
560
    if (opt_val_reg && !rcp.types.is_initialized(*opt_val_reg)) {
24,184✔
561
        stack.havoc(rcp.values, DataKind::svalues, addr, width);
×
562
        stack.havoc(rcp.values, DataKind::uvalues, addr, width);
×
563
    } else {
564
        // opt_val_reg is unset when storing an immediate value.
565
        must_be_num = !opt_val_reg || rcp.types.type_is_number(*opt_val_reg);
24,184✔
566
        const LinearExpression val_type =
12,092✔
567
            must_be_num ? LinearExpression{T_NUM} : variable_registry->type_reg(opt_val_reg->v);
24,184✔
568
        rcp.assign_type(stack.store_type(rcp.types, addr, width, must_be_num), val_type);
24,184✔
569

570
        if (exact_width == 8) {
24,184✔
571
            stack.havoc(rcp.values, DataKind::svalues, addr, width);
8,702✔
572
            stack.havoc(rcp.values, DataKind::uvalues, addr, width);
8,702✔
573
            rcp.values.assign(stack.store(rcp.values, DataKind::svalues, addr, width), val_svalue);
8,702✔
574
            rcp.values.assign(stack.store(rcp.values, DataKind::uvalues, addr, width), val_uvalue);
8,702✔
575

576
            if (!must_be_num) {
8,702✔
577
                for (TypeEncoding type : rcp.types.iterate_types(*opt_val_reg)) {
1,066✔
578
                    for (const DataKind kind : type_to_kinds.at(type)) {
1,240✔
579
                        const Variable src_var = variable_registry->reg(kind, opt_val_reg->v);
702✔
580
                        rcp.values.assign(stack.store(rcp.values, kind, addr, width), src_var);
702✔
581
                    }
582
                }
528✔
583
            }
584
        } else if ((exact_width == 1 || exact_width == 2 || exact_width == 4) && must_be_num) {
15,482✔
585
            // Keep track of numbers on the stack that might be used as array indices.
586
            if (const auto stack_svalue = stack.store(rcp.values, DataKind::svalues, addr, width)) {
15,452✔
587
                rcp.values.assign(stack_svalue, val_svalue);
15,448✔
588
                rcp.values->overflow_bounds(*stack_svalue, exact_width * 8, true);
15,448✔
589
            } else {
590
                stack.havoc(rcp.values, DataKind::svalues, addr, width);
4✔
591
            }
592
            if (const auto stack_uvalue = stack.store(rcp.values, DataKind::uvalues, addr, width)) {
15,452✔
593
                rcp.values.assign(stack_uvalue, val_uvalue);
15,448✔
594
                rcp.values->overflow_bounds(*stack_uvalue, exact_width * 8, false);
15,448✔
595
            } else {
596
                stack.havoc(rcp.values, DataKind::uvalues, addr, width);
4✔
597
            }
598
        } else {
15,452✔
599
            stack.havoc(rcp.values, DataKind::svalues, addr, width);
30✔
600
            stack.havoc(rcp.values, DataKind::uvalues, addr, width);
30✔
601
        }
602
    }
24,184✔
603

604
    // Update stack_numeric_size for any stack type variables.
605
    // stack_numeric_size holds the number of continuous bytes starting from stack_offset that are known to be numeric.
606
    for (const Variable type_variable : variable_registry->get_type_variables()) {
1,282,220✔
607
        if (!rcp.types.is_initialized(type_variable) || !rcp.types.may_have_type(type_variable, T_STACK)) {
1,273,185✔
608
            continue;
1,227,738✔
609
        }
610
        const Variable stack_offset_variable = variable_registry->kind_var(DataKind::stack_offsets, type_variable);
30,298✔
611
        const Variable stack_numeric_size_variable =
15,149✔
612
            variable_registry->kind_var(DataKind::stack_numeric_sizes, type_variable);
30,298✔
613

614
        using namespace dsl_syntax;
15,149✔
615
        // See if the variable's numeric interval overlaps with changed bytes.
616
        if (rcp.values.intersect(
121,192✔
617
                dsl_syntax::operator<=(symb_addr, stack_offset_variable + stack_numeric_size_variable)) &&
90,359✔
618
            rcp.values.intersect(dsl_syntax::operator>=(symb_addr + exact_width, stack_offset_variable))) {
59,526✔
619
            if (!must_be_num) {
5,270✔
620
                rcp.values.havoc(stack_numeric_size_variable);
146✔
621
            }
622
            recompute_stack_numeric_size(rcp, stack, type_variable);
5,270✔
623
        }
624
    }
24,184✔
625
}
36,276✔
626

627
void EbpfTransformer::operator()(const Mem& b) {
56,356✔
628
    if (dom.is_bottom()) {
56,356✔
629
        return;
630
    }
631
    if (const auto preg = std::get_if<Reg>(&b.value)) {
56,356✔
632
        if (b.is_load) {
56,322✔
633
            do_load(b, *preg);
26,880✔
634
        } else {
635
            const auto data_reg = reg_pack(*preg);
29,442✔
636
            do_mem_store(b, data_reg.svalue, data_reg.uvalue, *preg);
29,442✔
637
        }
638
    } else {
639
        const uint64_t imm = std::get<Imm>(b.value).v;
34✔
640
        do_mem_store(b, to_signed(imm), imm, {});
34✔
641
    }
642
}
643

644
void EbpfTransformer::do_mem_store(const Mem& b, const LinearExpression& val_svalue, const LinearExpression& val_uvalue,
29,476✔
645
                                   const std::optional<Reg>& opt_val_reg) {
646
    if (dom.is_bottom()) {
29,476✔
647
        return;
24,030✔
648
    }
649
    const int width = b.access.width;
29,476✔
650
    const Number offset{b.access.offset};
29,476✔
651
    if (b.access.basereg.v == R10_STACK_POINTER) {
29,476✔
652
        const auto r10_stack_offset = reg_pack(b.access.basereg).stack_offset;
24,030✔
653
        const auto r10_interval = dom.rcp.values.eval_interval(r10_stack_offset);
24,030✔
654
        if (r10_interval.is_singleton()) {
24,030✔
655
            const int32_t stack_offset = r10_interval.singleton()->cast_to<int32_t>();
24,030✔
656
            const Number base_addr{stack_offset};
24,030✔
657
            do_store_stack(dom.rcp, stack, base_addr + offset, width, val_svalue, val_uvalue, opt_val_reg);
36,045✔
658
            return;
24,030✔
659
        }
24,030✔
660
    }
24,030✔
661
    dom.rcp = dom.rcp.join_over_types(b.access.basereg, [&](TypeToNumDomain& rcp, const TypeEncoding type) {
10,892✔
662
        if (type == T_STACK) {
5,446✔
663
            const auto base_addr = LinearExpression(reg_pack(b.access.basereg).stack_offset);
154✔
664
            do_store_stack(rcp, stack, dsl_syntax::operator+(base_addr, offset), width, val_svalue, val_uvalue,
231✔
665
                           opt_val_reg);
666
        }
154✔
667
        // do nothing for any other type
668
    });
10,892✔
669
}
29,476✔
670

671
// Construct a Bin operation that does the main operation that a given Atomic operation does atomically.
672
static Bin atomic_to_bin(const Atomic& a) {
84✔
673
    Bin bin{.dst = Reg{R11_ATOMIC_SCRATCH}, .v = a.valreg, .is64 = a.access.width == sizeof(uint64_t), .lddw = false};
84✔
674
    switch (a.op) {
84✔
675
    case Atomic::Op::ADD: bin.op = Bin::Op::ADD; break;
16✔
676
    case Atomic::Op::OR: bin.op = Bin::Op::OR; break;
16✔
677
    case Atomic::Op::AND: bin.op = Bin::Op::AND; break;
16✔
678
    case Atomic::Op::XOR: bin.op = Bin::Op::XOR; break;
16✔
679
    case Atomic::Op::XCHG:
10✔
680
    case Atomic::Op::CMPXCHG: bin.op = Bin::Op::MOV; break;
10✔
681
    default: throw std::exception();
×
682
    }
683
    return bin;
84✔
684
}
685

686
void EbpfTransformer::operator()(const Atomic& a) {
380✔
687
    if (dom.is_bottom()) {
380✔
688
        return;
296✔
689
    }
690
    if (!dom.rcp.types.type_is_pointer(a.access.basereg) || !dom.rcp.types.type_is_number(a.valreg)) {
380✔
691
        return;
×
692
    }
693
    if (dom.rcp.types.type_is_not_stack(a.access.basereg)) {
380✔
694
        // Shared memory regions are volatile so we can just havoc
695
        // any register that will be updated.
696
        if (a.op == Atomic::Op::CMPXCHG) {
296✔
697
            dom.rcp.havoc_register_except_type(Reg{R0_RETURN_VALUE});
4✔
698
        } else if (a.fetch) {
292✔
699
            dom.rcp.havoc_register_except_type(a.valreg);
52✔
700
        }
701
        return;
296✔
702
    }
703

704
    // Fetch the current value into the R11 pseudo-register.
705
    constexpr Reg r11{R11_ATOMIC_SCRATCH};
84✔
706
    (*this)(Mem{.access = a.access, .value = r11, .is_load = true});
84✔
707

708
    // Compute the new value in R11.
709
    (*this)(atomic_to_bin(a));
84✔
710

711
    if (a.op == Atomic::Op::CMPXCHG) {
84✔
712
        // For CMPXCHG, store the original value in r0.
713
        (*this)(Mem{.access = a.access, .value = Reg{R0_RETURN_VALUE}, .is_load = true});
12✔
714

715
        // For the destination, there are 3 possibilities:
716
        // 1) dst.value == r0.value : set R11 to valreg
717
        // 2) dst.value != r0.value : don't modify R11
718
        // 3) dst.value may or may not == r0.value : set R11 to the union of R11 and valreg
719
        // For now we just havoc the value of R11.
720
        dom.rcp.havoc_register_except_type(r11);
12✔
721
    } else if (a.fetch) {
72✔
722
        // For other FETCH operations, store the original value in the src register.
723
        (*this)(Mem{.access = a.access, .value = a.valreg, .is_load = true});
40✔
724
    }
725

726
    // Store the new value back in the original shared memory location.
727
    // Note that do_mem_store() currently doesn't track shared memory values,
728
    // but stack memory values are tracked and are legal here.
729
    (*this)(Mem{.access = a.access, .value = r11, .is_load = false});
84✔
730

731
    // Clear the R11 pseudo-register.
732
    dom.rcp.havoc_register(r11);
84✔
733
}
734

735
void EbpfTransformer::operator()(const Call& call) {
10,400✔
736
    using namespace dsl_syntax;
5,200✔
737
    if (dom.is_bottom()) {
10,400✔
738
        return;
×
739
    }
740
    std::optional<Reg> maybe_fd_reg{};
10,400✔
741
    for (ArgSingle param : call.singles) {
34,746✔
742
        switch (param.kind) {
24,346✔
743
        case ArgSingle::Kind::MAP_FD: maybe_fd_reg = param.reg; break;
16,623✔
744
        case ArgSingle::Kind::ANYTHING:
9,948✔
745
        case ArgSingle::Kind::MAP_FD_PROGRAMS:
746
        case ArgSingle::Kind::PTR_TO_MAP_KEY:
747
        case ArgSingle::Kind::PTR_TO_MAP_VALUE:
748
        case ArgSingle::Kind::PTR_TO_CTX:
749
            // Do nothing. We don't track the content of relevant memory regions
750
            break;
9,948✔
751
        case ArgSingle::Kind::PTR_TO_STACK:
752
            // Do nothing; the stack is passed as context, not to be modified.
753
            break;
754
        }
755
    }
756
    for (ArgPair param : call.pairs) {
14,902✔
757
        switch (param.kind) {
4,502✔
758
        case ArgPair::Kind::PTR_TO_READABLE_MEM:
1,703✔
759
            // Do nothing. No side effect allowed.
760
            break;
1,703✔
761

762
        case ArgPair::Kind::PTR_TO_WRITABLE_MEM: {
1,096✔
763
            bool store_numbers = true;
1,096✔
764
            auto variable = dom.rcp.get_type_offset_variable(param.mem);
1,096✔
765
            if (!variable.has_value()) {
1,096✔
766
                // checked by the checker
767
                break;
768
            }
769
            Interval addr = dom.rcp.values.eval_interval(variable.value());
1,096✔
770
            Interval width = dom.rcp.values.eval_interval(reg_pack(param.size).svalue);
1,096✔
771

772
            dom.rcp = dom.rcp.join_over_types(param.mem, [&](TypeToNumDomain& rcp, const TypeEncoding type) {
2,192✔
773
                if (type == T_STACK) {
1,096✔
774
                    // Pointer to a memory region that the called function may change,
775
                    // so we must havoc.
776
                    for (const DataKind kind : iterate_kinds()) {
11,968✔
777
                        stack.havoc(rcp.values, kind, addr, width);
10,880✔
778
                    }
1,088✔
779
                } else {
780
                    store_numbers = false;
8✔
781
                }
782
            });
2,192✔
783
            if (store_numbers) {
1,096✔
784
                // Functions are not allowed to write sensitive data,
785
                // and initialization is guaranteed
786
                stack.store_numbers(addr, width);
1,088✔
787
            }
788
        }
1,644✔
789
        }
790
    }
791

792
    constexpr Reg r0_reg{R0_RETURN_VALUE};
10,400✔
793
    const auto r0_pack = reg_pack(r0_reg);
10,400✔
794
    dom.rcp.values.havoc(r0_pack.stack_numeric_size);
10,400✔
795
    if (call.is_map_lookup) {
10,400✔
796
        // This is the only way to get a null pointer
797
        if (maybe_fd_reg) {
2,634✔
798
            if (const auto map_type = dom.get_map_type(*maybe_fd_reg)) {
2,634✔
799
                if (thread_local_program_info->platform->get_map_type(*map_type).value_type == EbpfMapValueType::MAP) {
2,632✔
800
                    if (const auto inner_map_fd = dom.get_map_inner_map_fd(*maybe_fd_reg)) {
32✔
801
                        do_load_mapfd(r0_reg, to_signed(*inner_map_fd), true);
32✔
802
                        goto out;
32✔
803
                    }
804
                } else {
805
                    assign_valid_ptr(r0_reg, true);
2,600✔
806
                    dom.rcp.values.assign(r0_pack.shared_offset, 0);
2,600✔
807
                    dom.rcp.values.set(r0_pack.shared_region_size, dom.get_map_value_size(*maybe_fd_reg));
2,600✔
808
                    dom.rcp.assign_type(r0_reg, T_SHARED);
2,600✔
809
                }
810
            }
811
        }
812
        assign_valid_ptr(r0_reg, true);
2,602✔
813
        dom.rcp.values.assign(r0_pack.shared_offset, 0);
2,602✔
814
        dom.rcp.assign_type(r0_reg, T_SHARED);
2,602✔
815
    } else {
816
        dom.rcp.havoc_register_except_type(r0_reg);
7,766✔
817
        dom.rcp.assign_type(r0_reg, T_NUM);
7,766✔
818
        // dom.rcp.values.add_constraint(r0_pack.value < 0); for INTEGER_OR_NO_RETURN_IF_SUCCEED.
819
    }
820
out:
10,400✔
821
    scratch_caller_saved_registers();
10,400✔
822
    if (call.reallocate_packet) {
10,400✔
823
        forget_packet_pointers();
40✔
824
    }
825
}
826

827
void EbpfTransformer::operator()(const CallLocal& call) {
56✔
828
    using namespace dsl_syntax;
28✔
829
    if (dom.is_bottom()) {
56✔
830
        return;
×
831
    }
832
    save_callee_saved_registers(call.stack_frame_prefix);
56✔
833

834
    // Update r10.
835
    constexpr Reg r10_reg{R10_STACK_POINTER};
56✔
836
    add(r10_reg, -EBPF_SUBPROGRAM_STACK_SIZE, 64);
56✔
837
}
838

839
void EbpfTransformer::operator()(const Callx& callx) {
22✔
840
    using namespace dsl_syntax;
11✔
841
    if (dom.is_bottom()) {
22✔
842
        return;
×
843
    }
844

845
    // Look up the helper function id.
846
    const RegPack& reg = reg_pack(callx.func);
22✔
847
    const auto src_interval = dom.rcp.values.eval_interval(reg.svalue);
22✔
848
    if (const auto sn = src_interval.singleton()) {
22✔
849
        if (sn->fits<int32_t>()) {
22✔
850
            // We can now process it as if the id was immediate.
851
            const int32_t imm = sn->cast_to<int32_t>();
22✔
852
            if (!thread_local_program_info->platform->is_helper_usable(imm)) {
22✔
853
                return;
×
854
            }
855
            const Call call = make_call(imm, *thread_local_program_info->platform);
22✔
856
            (*this)(call);
22✔
857
        }
22✔
858
    }
22✔
859
}
22✔
860

861
void EbpfTransformer::do_load_mapfd(const Reg& dst_reg, const int mapfd, const bool maybe_null) {
5,038✔
862
    const EbpfMapDescriptor& desc = thread_local_program_info->platform->get_map_descriptor(mapfd);
5,038✔
863
    const EbpfMapType& type = thread_local_program_info->platform->get_map_type(desc.type);
5,038✔
864
    const RegPack& dst = reg_pack(dst_reg);
5,038✔
865
    if (type.value_type == EbpfMapValueType::PROGRAM) {
5,038✔
866
        dom.rcp.assign_type(dst_reg, T_MAP_PROGRAMS);
546✔
867
        dom.rcp.values.assign(dst.map_fd_programs, mapfd);
819✔
868
    } else {
869
        dom.rcp.assign_type(dst_reg, T_MAP);
4,492✔
870
        dom.rcp.values.assign(dst.map_fd, mapfd);
6,738✔
871
    }
872
    assign_valid_ptr(dst_reg, maybe_null);
5,038✔
873
}
5,038✔
874

875
void EbpfTransformer::operator()(const LoadMapFd& ins) {
5,006✔
876
    if (dom.is_bottom()) {
5,006✔
877
        return;
878
    }
879
    do_load_mapfd(ins.dst, ins.mapfd, false);
5,006✔
880
}
881

882
void EbpfTransformer::do_load_map_address(const Reg& dst_reg, const int mapfd, const int32_t offset) {
288✔
883
    const EbpfMapDescriptor& desc = thread_local_program_info->platform->get_map_descriptor(mapfd);
288✔
884
    const EbpfMapType& type = thread_local_program_info->platform->get_map_type(desc.type);
288✔
885

886
    if (type.value_type == EbpfMapValueType::PROGRAM) {
288✔
887
        throw std::invalid_argument("Cannot load address of program map type - only data maps are supported");
×
888
    }
889

890
    // Set the shared region size and offset for the map.
891
    dom.rcp.assign_type(dst_reg, T_SHARED);
288✔
892
    const RegPack& dst = reg_pack(dst_reg);
288✔
893
    dom.rcp.values.assign(dst.shared_offset, offset);
288✔
894
    dom.rcp.values.assign(dst.shared_region_size, desc.value_size);
288✔
895
    assign_valid_ptr(dst_reg, false);
288✔
896
}
288✔
897

898
void EbpfTransformer::operator()(const LoadMapAddress& ins) {
288✔
899
    if (dom.is_bottom()) {
288✔
900
        return;
901
    }
902
    do_load_map_address(ins.dst, ins.mapfd, ins.offset);
288✔
903
}
904

905
void EbpfTransformer::assign_valid_ptr(const Reg& dst_reg, const bool maybe_null) {
10,528✔
906
    using namespace dsl_syntax;
5,264✔
907
    const RegPack& reg = reg_pack(dst_reg);
10,528✔
908
    dom.rcp.values.havoc(reg.svalue);
10,528✔
909
    dom.rcp.values.havoc(reg.uvalue);
10,528✔
910
    if (maybe_null) {
10,528✔
911
        dom.rcp.values.add_constraint(0 <= reg.svalue);
10,468✔
912
    } else {
913
        dom.rcp.values.add_constraint(0 < reg.svalue);
10,588✔
914
    }
915
    dom.rcp.values.add_constraint(reg.svalue <= PTR_MAX);
15,792✔
916
    dom.rcp.values.assign(reg.uvalue, reg.svalue);
10,528✔
917
}
10,528✔
918

919
// If nothing is known of the stack_numeric_size,
920
// try to recompute the stack_numeric_size.
921
void EbpfTransformer::recompute_stack_numeric_size(TypeToNumDomain& rcp, ArrayDomain& stack,
14,152✔
922
                                                   const Variable type_variable) {
923
    const Variable stack_numeric_size_variable =
7,076✔
924
        variable_registry->kind_var(DataKind::stack_numeric_sizes, type_variable);
14,152✔
925

926
    if (rcp.types.may_have_type(type_variable, T_STACK)) {
14,152✔
927
        const int numeric_size =
6,591✔
928
            stack.min_all_num_size(rcp.values, variable_registry->kind_var(DataKind::stack_offsets, type_variable));
13,182✔
929
        if (numeric_size > 0) {
13,182✔
930
            rcp.values.assign(stack_numeric_size_variable, numeric_size);
11,559✔
931
        }
932
    }
933
}
14,152✔
934

935
void EbpfTransformer::recompute_stack_numeric_size(TypeToNumDomain& rcp, ArrayDomain& stack, const Reg& reg) {
8,882✔
936
    recompute_stack_numeric_size(rcp, stack, reg_type(reg));
8,882✔
937
}
8,882✔
938

939
void EbpfTransformer::add(const Reg& dst_reg, const int imm, const int finite_width) {
11,692✔
940
    const auto dst = reg_pack(dst_reg);
11,692✔
941
    dom.rcp.values->add_overflow(dst.svalue, dst.uvalue, imm, finite_width);
11,692✔
942
    if (const auto offset = dom.rcp.get_type_offset_variable(dst_reg)) {
11,692✔
943
        dom.rcp.values->add(*offset, imm);
8,876✔
944
        if (imm > 0) {
8,876✔
945
            // Since the start offset is increasing but
946
            // the end offset is not, the numeric size decreases.
947
            dom.rcp.values->sub(dst.stack_numeric_size, imm);
1,488✔
948
        } else if (imm < 0) {
7,884✔
949
            dom.rcp.values.havoc(dst.stack_numeric_size);
7,884✔
950
        }
951
        recompute_stack_numeric_size(dom.rcp, stack, dst_reg);
8,876✔
952
    }
953
}
11,692✔
954

955
void EbpfTransformer::shl(const Reg& dst_reg, int imm, const int finite_width) {
5,600✔
956
    dom.rcp.havoc_offsets(dst_reg);
5,600✔
957

958
    // The BPF ISA requires masking the imm.
959
    imm &= finite_width - 1;
5,600✔
960
    const RegPack dst = reg_pack(dst_reg);
5,600✔
961
    if (dom.rcp.types.type_is_number(dst_reg)) {
5,600✔
962
        dom.rcp.values->shl(dst.svalue, dst.uvalue, imm, finite_width);
5,600✔
963
    } else {
964
        dom.rcp.values.havoc(dst.svalue);
×
965
        dom.rcp.values.havoc(dst.uvalue);
×
966
    }
967
}
5,600✔
968

969
void EbpfTransformer::lshr(const Reg& dst_reg, int imm, int finite_width) {
2,604✔
970
    dom.rcp.havoc_offsets(dst_reg);
2,604✔
971

972
    // The BPF ISA requires masking the imm.
973
    imm &= finite_width - 1;
2,604✔
974
    const RegPack dst = reg_pack(dst_reg);
2,604✔
975
    if (dom.rcp.types.type_is_number(dst_reg)) {
2,604✔
976
        dom.rcp.values->lshr(dst.svalue, dst.uvalue, imm, finite_width);
2,604✔
977
    } else {
978
        dom.rcp.values.havoc(dst.svalue);
×
979
        dom.rcp.values.havoc(dst.uvalue);
×
980
    }
981
}
2,604✔
982

983
void EbpfTransformer::ashr(const Reg& dst_reg, const LinearExpression& right_svalue, const int finite_width) {
762✔
984
    dom.rcp.havoc_offsets(dst_reg);
762✔
985

986
    const RegPack dst = reg_pack(dst_reg);
762✔
987
    if (dom.rcp.types.type_is_number(dst_reg)) {
762✔
988
        dom.rcp.values->ashr(dst.svalue, dst.uvalue, right_svalue, finite_width);
762✔
989
    } else {
990
        dom.rcp.values.havoc(dst.svalue);
×
991
        dom.rcp.values.havoc(dst.uvalue);
×
992
    }
993
}
762✔
994

995
static int _movsx_bits(const Bin::Op op) {
2,208✔
996
    switch (op) {
2,208✔
997
    case Bin::Op::MOVSX8: return 8;
8✔
998
    case Bin::Op::MOVSX16: return 16;
8✔
999
    case Bin::Op::MOVSX32: return 32;
1,088✔
1000
    default: throw std::exception();
×
1001
    }
1002
}
1003

1004
void EbpfTransformer::operator()(const Bin& bin) {
99,960✔
1005
    if (dom.is_bottom()) {
99,960✔
1006
        return;
36✔
1007
    }
1008
    using namespace dsl_syntax;
49,980✔
1009

1010
    auto dst = reg_pack(bin.dst);
99,960✔
1011
    int finite_width = bin.is64 ? 64 : 32;
99,960✔
1012

1013
    // TODO: Unusable states and values should be better handled.
1014
    //       Probably by propagating an error state.
1015
    if (!dom.rcp.types.is_initialized(bin.dst) &&
130,622✔
1016
        !std::set{Bin::Op::MOV, Bin::Op::MOVSX8, Bin::Op::MOVSX16, Bin::Op::MOVSX32}.contains(bin.op)) {
176,615✔
1017
        dom.rcp.havoc_register(bin.dst);
×
1018
        return;
×
1019
    }
1020
    if (auto pimm = std::get_if<Imm>(&bin.v)) {
99,960✔
1021
        // dst += K
1022
        int64_t imm;
30,010✔
1023
        if (bin.is64) {
60,020✔
1024
            // Use the full signed value.
1025
            imm = to_signed(pimm->v);
49,910✔
1026
        } else {
1027
            // Use only the low 32 bits of the value.
1028
            imm = gsl::narrow_cast<int32_t>(pimm->v);
10,110✔
1029
            // If this is a 32-bit operation and the destination is not proven a number, forget the register.
1030
            if (dom.rcp.types.type_is_number(bin.dst)) {
10,110✔
1031
                // Safe to zero-extend the low 32 bits; even if it's also bin.src, only the 32-bit value is used.
1032
                dom.rcp.values->bitwise_and(dst.svalue, dst.uvalue, std::numeric_limits<uint32_t>::max());
11,256✔
1033
            } else {
1034
                dom.rcp.havoc_register(bin.dst);
2,606✔
1035
            }
1036
        }
1037
        switch (bin.op) {
60,020✔
1038
        case Bin::Op::MOV:
34,354✔
1039
            dom.rcp.values.assign(dst.svalue, imm);
34,354✔
1040
            dom.rcp.values.assign(dst.uvalue, imm);
34,354✔
1041
            dom.rcp.values->overflow_bounds(dst.uvalue, bin.is64 ? 64 : 32, false);
37,235✔
1042
            dom.rcp.assign_type(bin.dst, T_NUM);
34,354✔
1043
            dom.rcp.havoc_offsets(bin.dst);
34,354✔
1044
            break;
17,177✔
1045
        case Bin::Op::MOVSX8:
×
1046
        case Bin::Op::MOVSX16:
1047
        case Bin::Op::MOVSX32: CRAB_ERROR("Unsupported operation");
×
1048
        case Bin::Op::ADD:
11,560✔
1049
            if (imm == 0) {
11,560✔
1050
                return;
21✔
1051
            }
1052
            add(bin.dst, gsl::narrow<int>(imm), finite_width);
11,556✔
1053
            break;
5,778✔
1054
        case Bin::Op::SUB:
28✔
1055
            if (imm == 0) {
28✔
1056
                return;
1✔
1057
            }
1058
            add(bin.dst, gsl::narrow<int>(-imm), finite_width);
26✔
1059
            break;
13✔
1060
        case Bin::Op::MUL:
100✔
1061
            dom.rcp.values->mul(dst.svalue, dst.uvalue, imm, finite_width);
100✔
1062
            dom.rcp.havoc_offsets(bin.dst);
100✔
1063
            break;
50✔
1064
        case Bin::Op::UDIV:
168✔
1065
            dom.rcp.values->udiv(dst.svalue, dst.uvalue, imm, finite_width);
168✔
1066
            dom.rcp.havoc_offsets(bin.dst);
168✔
1067
            break;
84✔
1068
        case Bin::Op::UMOD:
22✔
1069
            dom.rcp.values->urem(dst.svalue, dst.uvalue, imm, finite_width);
22✔
1070
            dom.rcp.havoc_offsets(bin.dst);
22✔
1071
            break;
11✔
1072
        case Bin::Op::SDIV:
16✔
1073
            dom.rcp.values->sdiv(dst.svalue, dst.uvalue, imm, finite_width);
16✔
1074
            dom.rcp.havoc_offsets(bin.dst);
16✔
1075
            break;
8✔
1076
        case Bin::Op::SMOD:
32✔
1077
            dom.rcp.values->srem(dst.svalue, dst.uvalue, imm, finite_width);
32✔
1078
            dom.rcp.havoc_offsets(bin.dst);
32✔
1079
            break;
16✔
1080
        case Bin::Op::OR:
556✔
1081
            dom.rcp.values->bitwise_or(dst.svalue, dst.uvalue, imm);
556✔
1082
            dom.rcp.havoc_offsets(bin.dst);
556✔
1083
            break;
278✔
1084
        case Bin::Op::AND:
4,150✔
1085
            // FIX: what to do with ptr&-8 as in counter/simple_loop_unrolled?
1086
            dom.rcp.values->bitwise_and(dst.svalue, dst.uvalue, imm);
4,150✔
1087
            if (gsl::narrow<int32_t>(imm) > 0) {
4,150✔
1088
                // AND with immediate is only a 32-bit operation so svalue and uvalue are the same.
1089
                dom.rcp.values.add_constraint(dst.svalue <= imm);
5,370✔
1090
                dom.rcp.values.add_constraint(dst.uvalue <= imm);
5,370✔
1091
                dom.rcp.values.add_constraint(0 <= dst.svalue);
7,160✔
1092
                dom.rcp.values.add_constraint(0 <= dst.uvalue);
7,160✔
1093
            }
1094
            dom.rcp.havoc_offsets(bin.dst);
4,150✔
1095
            break;
2,075✔
1096
        case Bin::Op::LSH: shl(bin.dst, gsl::narrow<int32_t>(imm), finite_width); break;
5,554✔
1097
        case Bin::Op::RSH: lshr(bin.dst, gsl::narrow<int32_t>(imm), finite_width); break;
2,570✔
1098
        case Bin::Op::ARSH: ashr(bin.dst, gsl::narrow<int32_t>(imm), finite_width); break;
730✔
1099
        case Bin::Op::XOR:
180✔
1100
            dom.rcp.values->bitwise_xor(dst.svalue, dst.uvalue, imm);
180✔
1101
            dom.rcp.havoc_offsets(bin.dst);
180✔
1102
            break;
90✔
1103
        }
1104
    } else {
1105
        // dst op= src
1106
        auto src_reg = std::get<Reg>(bin.v);
39,940✔
1107
        auto src = reg_pack(src_reg);
39,940✔
1108
        if (!dom.rcp.types.is_initialized(src_reg)) {
39,940✔
1109
            dom.rcp.havoc_register(bin.dst);
8✔
1110
            return;
34✔
1111
        }
1112
        switch (bin.op) {
39,932✔
1113
        case Bin::Op::ADD: {
2,060✔
1114
            if (dom.rcp.types.same_type(bin.dst, std::get<Reg>(bin.v))) {
2,060✔
1115
                // both must have been checked to be numbers
1116
                dom.rcp.values->add_overflow(dst.svalue, dst.uvalue, src.svalue, finite_width);
1,460✔
1117
            } else {
1118
                // Here we're not sure that lhs and rhs are the same type; they might be.
1119
                // But previous assertions should fail unless we know that exactly one of lhs or rhs is a pointer.
1120
                dom.rcp = dom.rcp.join_over_types(bin.dst, [&](TypeToNumDomain& rcp, const TypeEncoding dst_type) {
1,200✔
1121
                    rcp = rcp.join_over_types(src_reg, [&](TypeToNumDomain& rcp, const TypeEncoding src_type) {
1,204✔
1122
                        if (dst_type == T_NUM && src_type != T_NUM) {
604✔
1123
                            // num += ptr
1124
                            rcp.assign_type(bin.dst, src_type);
8✔
1125
                            if (const auto dst_offset = get_type_offset_variable(bin.dst, src_type)) {
8✔
1126
                                rcp.values->apply(ArithBinOp::ADD, dst_offset.value(), dst.svalue,
12✔
1127
                                                  get_type_offset_variable(src_reg, src_type).value());
16✔
1128
                            }
1129
                            if (src_type == T_SHARED) {
8✔
1130
                                rcp.values.assign(dst.shared_region_size, src.shared_region_size);
×
1131
                            }
1132
                        } else if (dst_type != T_NUM && src_type == T_NUM) {
600✔
1133
                            // ptr += num
1134
                            rcp.assign_type(bin.dst, dst_type);
596✔
1135
                            if (const auto dst_offset = get_type_offset_variable(bin.dst, dst_type)) {
596✔
1136
                                rcp.values->apply(ArithBinOp::ADD, dst_offset.value(), dst_offset.value(), src.svalue);
596✔
1137
                                if (dst_type == T_STACK) {
596✔
1138
                                    // Reduce the numeric size.
1139
                                    using namespace dsl_syntax;
8✔
1140
                                    if (rcp.values.intersect(src.svalue < 0)) {
24✔
1141
                                        rcp.values.havoc(dst.stack_numeric_size);
2✔
1142
                                        recompute_stack_numeric_size(rcp, stack, bin.dst);
2✔
1143
                                    } else {
1144
                                        rcp.values->apply_signed(ArithBinOp::SUB, dst.stack_numeric_size,
28✔
1145
                                                                 dst.stack_numeric_size, dst.stack_numeric_size,
14✔
1146
                                                                 src.svalue, 0);
14✔
1147
                                    }
1148
                                }
1149
                            }
1150
                        } else if (dst_type == T_NUM && src_type == T_NUM) {
596✔
1151
                            // dst and src don't necessarily have the same type, but among the possibilities
1152
                            // enumerated is the case where they are both numbers.
1153
                            rcp.values->apply_signed(ArithBinOp::ADD, dst.svalue, dst.uvalue, dst.svalue, src.svalue,
×
1154
                                                     finite_width);
1155
                        } else {
1156
                            // We ignore the cases here that do not match the assumption described
1157
                            // above.  Joining bottom with another result will leave the other
1158
                            // results unchanged.
1159
                            rcp.values.set_to_bottom();
×
1160
                        }
1161
                    });
1,206✔
1162
                });
1,202✔
1163
                // careful: change dst.value only after dealing with offset
1164
                dom.rcp.values->apply_signed(ArithBinOp::ADD, dst.svalue, dst.uvalue, dst.svalue, src.svalue,
600✔
1165
                                             finite_width);
1166
            }
1167
            break;
1,030✔
1168
        }
1169
        case Bin::Op::SUB: {
718✔
1170
            if (dom.rcp.types.same_type(bin.dst, std::get<Reg>(bin.v))) {
718✔
1171
                // src and dest have the same type.
1172
                TypeDomain tmp_m_type_inv = dom.rcp.types;
714✔
1173
                dom.rcp = dom.rcp.join_over_types(bin.dst, [&](TypeToNumDomain& rcp, const TypeEncoding type) {
1,428✔
1174
                    switch (type) {
718✔
1175
                    case T_NUM:
524✔
1176
                        // This is: sub_overflow(inv, dst.value, src.value, finite_width);
1177
                        rcp.values->apply_signed(ArithBinOp::SUB, dst.svalue, dst.uvalue, dst.svalue, src.svalue,
524✔
1178
                                                 finite_width);
1179
                        rcp.assign_type(bin.dst, T_NUM);
524✔
1180
                        rcp.havoc_offsets(bin.dst);
524✔
1181
                        break;
524✔
1182
                    default:
194✔
1183
                        // ptr -= ptr
1184
                        // Assertions should make sure we only perform this on non-shared pointers.
1185
                        if (const auto dst_offset = get_type_offset_variable(bin.dst, type)) {
194✔
1186
                            rcp.values->apply_signed(ArithBinOp::SUB, dst.svalue, dst.uvalue, dst_offset.value(),
388✔
1187
                                                     get_type_offset_variable(src_reg, type).value(), finite_width);
291✔
1188
                            rcp.values.havoc(dst_offset.value());
194✔
1189
                        }
1190
                        rcp.havoc_offsets(bin.dst);
194✔
1191
                        rcp.assign_type(bin.dst, T_NUM);
194✔
1192
                        break;
194✔
1193
                    }
1194
                });
1,073✔
1195
            } else {
714✔
1196
                // We're not sure that lhs and rhs are the same type.
1197
                // Either they're different, or at least one is not a singleton.
1198
                if (dom.rcp.types.type_is_number(std::get<Reg>(bin.v))) {
4✔
1199
                    dom.rcp.values->sub_overflow(dst.svalue, dst.uvalue, src.svalue, finite_width);
4✔
1200
                    if (auto dst_offset = dom.rcp.get_type_offset_variable(bin.dst)) {
4✔
1201
                        dom.rcp.values->sub(dst_offset.value(), src.svalue);
4✔
1202
                        if (dom.rcp.types.may_have_type(bin.dst, T_STACK)) {
4✔
1203
                            // Reduce the numeric size.
1204
                            using namespace dsl_syntax;
2✔
1205
                            if (dom.rcp.values.intersect(src.svalue > 0)) {
8✔
1206
                                dom.rcp.values.havoc(dst.stack_numeric_size);
4✔
1207
                                recompute_stack_numeric_size(dom.rcp, stack, bin.dst);
4✔
1208
                            } else {
1209
                                dom.rcp.values->apply(ArithBinOp::ADD, dst.stack_numeric_size, dst.stack_numeric_size,
×
1210
                                                      src.svalue);
1211
                            }
1212
                        }
1213
                    }
1214
                } else {
1215
                    dom.rcp.havoc_register(bin.dst);
×
1216
                }
1217
            }
1218
            break;
359✔
1219
        }
1220
        case Bin::Op::MUL:
72✔
1221
            dom.rcp.values->mul(dst.svalue, dst.uvalue, src.svalue, finite_width);
72✔
1222
            dom.rcp.havoc_offsets(bin.dst);
72✔
1223
            break;
36✔
1224
        case Bin::Op::UDIV:
88✔
1225
            dom.rcp.values->udiv(dst.svalue, dst.uvalue, src.uvalue, finite_width);
88✔
1226
            dom.rcp.havoc_offsets(bin.dst);
88✔
1227
            break;
44✔
1228
        case Bin::Op::UMOD:
30✔
1229
            dom.rcp.values->urem(dst.svalue, dst.uvalue, src.uvalue, finite_width);
30✔
1230
            dom.rcp.havoc_offsets(bin.dst);
30✔
1231
            break;
15✔
1232
        case Bin::Op::SDIV:
16✔
1233
            dom.rcp.values->sdiv(dst.svalue, dst.uvalue, src.svalue, finite_width);
16✔
1234
            dom.rcp.havoc_offsets(bin.dst);
16✔
1235
            break;
8✔
1236
        case Bin::Op::SMOD:
30✔
1237
            dom.rcp.values->srem(dst.svalue, dst.uvalue, src.svalue, finite_width);
30✔
1238
            dom.rcp.havoc_offsets(bin.dst);
30✔
1239
            break;
15✔
1240
        case Bin::Op::OR:
5,748✔
1241
            dom.rcp.values->bitwise_or(dst.svalue, dst.uvalue, src.uvalue, finite_width);
5,748✔
1242
            dom.rcp.havoc_offsets(bin.dst);
5,748✔
1243
            break;
2,874✔
1244
        case Bin::Op::AND:
492✔
1245
            dom.rcp.values->bitwise_and(dst.svalue, dst.uvalue, src.uvalue, finite_width);
492✔
1246
            dom.rcp.havoc_offsets(bin.dst);
492✔
1247
            break;
246✔
1248
        case Bin::Op::LSH:
528✔
1249
            if (dom.rcp.types.type_is_number(src_reg)) {
528✔
1250
                auto src_interval = dom.rcp.values.eval_interval(src.uvalue);
528✔
1251
                if (std::optional<Number> sn = src_interval.singleton()) {
528✔
1252
                    // truncate to uint64?
1253
                    uint64_t imm = sn->cast_to<uint64_t>() & (bin.is64 ? 63 : 31);
46✔
1254
                    if (imm <= std::numeric_limits<int32_t>::max()) {
46✔
1255
                        if (!bin.is64) {
46✔
1256
                            // Use only the low 32 bits of the value.
1257
                            dom.rcp.values->bitwise_and(dst.svalue, dst.uvalue, std::numeric_limits<uint32_t>::max());
39✔
1258
                        }
1259
                        shl(bin.dst, gsl::narrow_cast<int32_t>(imm), finite_width);
46✔
1260
                        break;
46✔
1261
                    }
1262
                }
528✔
1263
            }
528✔
1264
            dom.rcp.values->shl_overflow(dst.svalue, dst.uvalue, src.uvalue);
482✔
1265
            dom.rcp.havoc_offsets(bin.dst);
482✔
1266
            break;
241✔
1267
        case Bin::Op::RSH:
78✔
1268
            if (dom.rcp.types.type_is_number(src_reg)) {
78✔
1269
                auto src_interval = dom.rcp.values.eval_interval(src.uvalue);
78✔
1270
                if (std::optional<Number> sn = src_interval.singleton()) {
78✔
1271
                    uint64_t imm = sn->cast_to<uint64_t>() & (bin.is64 ? 63 : 31);
34✔
1272
                    if (imm <= std::numeric_limits<int32_t>::max()) {
34✔
1273
                        if (!bin.is64) {
34✔
1274
                            // Use only the low 32 bits of the value.
1275
                            dom.rcp.values->bitwise_and(dst.svalue, dst.uvalue, std::numeric_limits<uint32_t>::max());
24✔
1276
                        }
1277
                        lshr(bin.dst, gsl::narrow_cast<int32_t>(imm), finite_width);
34✔
1278
                        break;
34✔
1279
                    }
1280
                }
78✔
1281
            }
78✔
1282
            dom.rcp.havoc_register_except_type(bin.dst);
44✔
1283
            break;
22✔
1284
        case Bin::Op::ARSH:
32✔
1285
            if (dom.rcp.types.type_is_number(src_reg)) {
32✔
1286
                ashr(bin.dst, src.svalue, finite_width);
32✔
1287
                break;
32✔
1288
            }
1289
            dom.rcp.havoc_register_except_type(bin.dst);
×
1290
            break;
1291
        case Bin::Op::XOR:
490✔
1292
            dom.rcp.values->bitwise_xor(dst.svalue, dst.uvalue, src.uvalue, finite_width);
490✔
1293
            dom.rcp.havoc_offsets(bin.dst);
490✔
1294
            break;
245✔
1295
        case Bin::Op::MOVSX8:
2,208✔
1296
        case Bin::Op::MOVSX16:
1,104✔
1297
        case Bin::Op::MOVSX32: {
1,104✔
1298
            const int source_width = _movsx_bits(bin.op);
2,208✔
1299
            // Keep relational information if operation is a no-op.
1300
            if (dst.svalue == src.svalue &&
3,292✔
1301
                dom.rcp.values.eval_interval(dst.svalue) <= Interval::signed_int(source_width)) {
5,460✔
1302
                return;
11✔
1303
            }
1304
            if (dom.rcp.types.type_is_number(src_reg)) {
2,186✔
1305
                dom.rcp.havoc_offsets(bin.dst);
2,186✔
1306
                dom.rcp.assign_type(bin.dst, T_NUM);
2,186✔
1307
                dom.rcp.values->sign_extend(dst.svalue, dst.uvalue, src.svalue, finite_width, source_width);
2,186✔
1308
                break;
2,186✔
1309
            }
1310
            dom.rcp.havoc_register(bin.dst);
×
1311
            break;
1312
        }
1313
        case Bin::Op::MOV:
27,342✔
1314
            // Keep relational information if operation is a no-op.
1315
            if (bin.is64 || dom.rcp.types.type_is_number(src_reg)) {
27,342✔
1316
                if (bin.dst != src_reg) {
46,628✔
1317
                    // the 32bit case is handled below
1318
                    dom.rcp.assign(bin.dst, src_reg);
26,004✔
1319
                }
1320
            } else {
1321
                // If src is not a number, we don't know how to truncate a pointer.
1322
                dom.rcp.havoc_register(bin.dst);
×
1323
            }
1324
            break;
13,671✔
1325
        }
1326
    }
1327
    if (!bin.is64) {
99,924✔
1328
        dom.rcp.values->bitwise_and(dst.svalue, dst.uvalue, std::numeric_limits<uint32_t>::max());
25,125✔
1329
    }
1330
}
1331

1332
void EbpfTransformer::initialize_loop_counter(const Label& label) {
32✔
1333
    dom.rcp.values.assign(variable_registry->loop_counter(to_string(label)), 0);
48✔
1334
}
32✔
1335

1336
void EbpfTransformer::operator()(const IncrementLoopCounter& ins) {
164✔
1337
    if (dom.is_bottom()) {
164✔
1338
        return;
×
1339
    }
1340
    const auto counter = variable_registry->loop_counter(to_string(ins.name));
164✔
1341
    dom.rcp.values->add(counter, 1);
246✔
1342
}
1343

1344
void ebpf_domain_initialize_loop_counter(EbpfDomain& dom, const Label& label) {
32✔
1345
    EbpfTransformer{dom}.initialize_loop_counter(label);
32✔
1346
}
32✔
1347
} // 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