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

Alan-Jowett / ebpf-verifier / 22160684311

18 Feb 2026 09:20PM UTC coverage: 88.256% (+1.1%) from 87.142%
22160684311

push

github

elazarg
lint

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

78 of 82 new or added lines in 6 files covered. (95.12%)

402 existing lines in 18 files now uncovered.

10731 of 12159 relevant lines covered (88.26%)

837642.51 hits per line

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

99.62
/src/test/test_join.cpp
1
// Copyright (c) Prevail Verifier contributors.
2
// SPDX-License-Identifier: MIT
3
#include <catch2/catch_all.hpp>
4

5
#include "arith/dsl_syntax.hpp"
6
#include "crab/ebpf_domain.hpp"
7
#include "crab/splitdbm/split_dbm.hpp"
8

9
using namespace prevail;
10

11
using TypeRestrictions = std::vector<std::pair<Variable, TypeSet>>;
12

13
static void require_join(const TypeRestrictions& a_type, const std::vector<LinearConstraint>& a_csts,
84✔
14
                         const TypeRestrictions& b_type, const std::vector<LinearConstraint>& b_csts,
15
                         const TypeRestrictions& over_type, const std::vector<LinearConstraint>& over_csts) {
16
    using namespace dsl_syntax;
42✔
17
    EbpfDomain a = EbpfDomain::from_constraints(a_type, a_csts);
84✔
18
    EbpfDomain b = EbpfDomain::from_constraints(b_type, b_csts);
84✔
19
    EbpfDomain j = a | b;
84✔
20
    REQUIRE(a <= j);
84✔
21
    REQUIRE(b <= j);
84✔
22
    EbpfDomain over_approx = EbpfDomain::from_constraints(over_type, over_csts);
84✔
23
    REQUIRE(j <= over_approx);
126✔
24
}
84✔
25

26
static const RegPack r0 = reg_pack(0);
27
static const Variable r0_type = reg_type(Reg{0});
28
static const RegPack r1 = reg_pack(1);
29
static const Variable r1_type = reg_type(Reg{1});
30
static const RegPack r2 = reg_pack(2);
31
static const Variable r2_type = reg_type(Reg{2});
32
static const RegPack r6 = reg_pack(6);
33
static const Variable r6_type = reg_type(Reg{6});
34
static const RegPack r7 = reg_pack(7);
35
static const Variable r7_type = reg_type(Reg{7});
36
static const RegPack r10 = reg_pack(10);
37
static const Variable r10_type = reg_type(Reg{10});
38

39
TEST_CASE("EbpfDomain basic join", "[join][lattice]") {
2✔
40
    using namespace dsl_syntax;
1✔
41
    require_join({}, {}, {}, {}, {}, {});
2✔
42
}
2✔
43

44
// 1) Type-only joins
45
TEST_CASE("join same precise type", "[join][lattice]") {
2✔
46
    using namespace dsl_syntax;
1✔
47
    require_join({{r0_type, TypeSet{T_NUM}}}, {}, {{r0_type, TypeSet{T_NUM}}}, {}, {{r0_type, TypeSet{T_NUM}}}, {});
8✔
48
}
2✔
49

50
TEST_CASE("join disjoint precise types gives exact union", "[join][lattice]") {
2✔
51
    using namespace dsl_syntax;
1✔
52
    require_join({{r0_type, TypeSet{T_MAP}}}, {}, {{r0_type, TypeSet{T_STACK}}}, {},
8✔
53
                 {{r0_type, TypeSet{T_MAP, T_STACK}}}, {});
4✔
54
}
2✔
55

56
TEST_CASE("join precise with top widens type range", "[join][lattice]") {
2✔
57
    using namespace dsl_syntax;
1✔
58
    require_join({{r0_type, TypeSet{T_MAP}}}, {}, {{r0_type, TypeSet{T_NUM}}}, {}, {{r0_type, TypeSet{T_MAP, T_NUM}}},
8✔
59
                 {});
60
}
2✔
61

62
TEST_CASE("EbpfDomain disjoint value join", "[join][lattice]") {
2✔
63
    using namespace dsl_syntax;
1✔
64
    require_join({{r0_type, TypeSet{T_NUM}}}, {r0.svalue == 0}, {{r0_type, TypeSet{T_NUM}}}, {r0.svalue == 5},
26✔
65
                 {{r0_type, TypeSet{T_NUM}}}, {r0.svalue >= 0, r0.svalue <= 5});
4✔
66
}
13✔
67

68
TEST_CASE("join respects partial order", "[join][lattice]") {
2✔
69
    using namespace dsl_syntax;
1✔
70
    require_join({{r0_type, TypeSet{T_NUM}}}, {r0.svalue == 1}, {{r0_type, TypeSet{T_NUM}}}, {r0.svalue == 2},
26✔
71
                 {{r0_type, TypeSet{T_NUM}}}, {r0.svalue >= 1, r0.svalue <= 2});
4✔
72
}
13✔
73

74
// 2) Value constraints with same type
75
TEST_CASE("join keeps equal constants", "[join][lattice]") {
2✔
76
    using namespace dsl_syntax;
1✔
77
    require_join({{r0_type, TypeSet{T_NUM}}}, {r0.svalue == 7}, {{r0_type, TypeSet{T_NUM}}}, {r0.svalue == 7},
24✔
78
                 {{r0_type, TypeSet{T_NUM}}}, {r0.svalue == 7});
4✔
79
}
12✔
80

81
TEST_CASE("join convex hull of disjoint constants", "[join][lattice]") {
2✔
82
    using namespace dsl_syntax;
1✔
83
    require_join({{r0_type, TypeSet{T_NUM}}}, {r0.svalue == 1}, {{r0_type, TypeSet{T_NUM}}}, {r0.svalue == 3},
26✔
84
                 {{r0_type, TypeSet{T_NUM}}}, {r0.svalue >= 1, r0.svalue <= 3});
4✔
85
}
13✔
86

87
TEST_CASE("join overlapping intervals", "[join][lattice]") {
2✔
88
    using namespace dsl_syntax;
1✔
89
    require_join({{r0_type, TypeSet{T_NUM}}}, {r0.svalue >= 0, r0.svalue <= 5}, {{r0_type, TypeSet{T_NUM}}},
30✔
90
                 {r0.svalue >= 3, r0.svalue <= 10}, {{r0_type, TypeSet{T_NUM}}}, {r0.svalue >= 0, r0.svalue <= 10});
4✔
91
}
15✔
92

93
TEST_CASE("join with one top one constrained becomes top for value", "[join][lattice]") {
2✔
94
    using namespace dsl_syntax;
1✔
95
    require_join({{r0_type, TypeSet{T_NUM}}}, {r0.svalue >= -5, r0.svalue <= 5}, {{r0_type, TypeSet{T_NUM}}}, {},
16✔
96
                 {{r0_type, TypeSet{T_NUM}}}, {});
4✔
97
}
7✔
98

99
// 3) Same memory type, offsets
100
TEST_CASE("join same pointer type same offset", "[join][lattice]") {
2✔
101
    using namespace dsl_syntax;
1✔
102
    require_join({{r0_type, TypeSet{T_PACKET}}}, {r0.svalue == 123, r0.packet_offset == 4},
31✔
103
                 {{r0_type, TypeSet{T_PACKET}}}, {r0.svalue == 123, r0.packet_offset == 4},
4✔
104
                 {{r0_type, TypeSet{T_PACKET}}}, {r0.svalue == 123, r0.packet_offset == 4});
4✔
105
}
12✔
106

107
TEST_CASE("join ranges offsets for same pointer type", "[join][lattice]") {
2✔
108
    using namespace dsl_syntax;
1✔
109
    require_join({{r0_type, TypeSet{T_PACKET}}}, {r0.svalue == 1, r0.packet_offset == 0},
35✔
110
                 {{r0_type, TypeSet{T_PACKET}}}, {r0.svalue == 3, r0.packet_offset == 4},
4✔
111
                 {{r0_type, TypeSet{T_PACKET}}},
4✔
112
                 {r0.svalue >= 1, r0.svalue <= 3, r0.packet_offset >= 0, r0.packet_offset <= 4});
113
}
14✔
114

115
// 4) Different memory types keep per-type offsets
116
TEST_CASE("join keeps offsets across disjoint types", "[join][lattice]") {
2✔
117
    using namespace dsl_syntax;
1✔
118
    require_join({{r1_type, TypeSet{T_STACK}}}, {r1.svalue == 123, r1.stack_offset == 100},
33✔
119
                 {{r1_type, TypeSet{T_PACKET}}}, {r1.svalue == 123, r1.packet_offset == 4},
4✔
120
                 {{r1_type, TypeSet{T_PACKET, T_STACK}}},
4✔
121
                 {r1.svalue == 123, r1.stack_offset == 100, r1.packet_offset == 4});
122
}
12✔
123

124
// 5) One branch pointer, other numeric
125
TEST_CASE("join widens type and preserves offset from one side", "[join][lattice]") {
2✔
126
    using namespace dsl_syntax;
1✔
127
    require_join({{r0_type, TypeSet{T_PACKET}}}, {r0.packet_offset == 8}, {{r0_type, TypeSet{T_NUM}}}, {},
19✔
128
                 {{r0_type, TypeSet{T_NUM, T_PACKET}}}, {r0.packet_offset == 8});
4✔
129
}
8✔
130

131
TEST_CASE("join with matching map types", "[join][lattice]") {
2✔
132
    using namespace dsl_syntax;
1✔
133
    require_join({{r0_type, TypeSet{T_MAP}}}, {r0.svalue == 1}, {{r0_type, TypeSet{T_MAP}}}, {r0.svalue == 3},
26✔
134
                 {{r0_type, TypeSet{T_MAP}}}, {r0.svalue >= 1, r0.svalue <= 3});
4✔
135
}
13✔
136

137
TEST_CASE("join with matching memory types but different offsets", "[join][lattice]") {
2✔
138
    using namespace dsl_syntax;
1✔
139
    require_join({{r0_type, TypeSet{T_PACKET}}}, {r0.svalue == 1, r0.packet_offset == 0},
35✔
140
                 {{r0_type, TypeSet{T_PACKET}}}, {r0.svalue == 3, r0.packet_offset == 4},
4✔
141
                 {{r0_type, TypeSet{T_PACKET}}},
4✔
142
                 {r0.svalue >= 1, r0.svalue <= 3, r0.packet_offset >= 0, r0.packet_offset <= 4});
143
}
14✔
144

145
TEST_CASE("join with different types and unknown offsets", "[join][lattice]") {
2✔
146
    using namespace dsl_syntax;
1✔
147
    require_join({{r0_type, TypeSet{T_MAP}}}, {r0.svalue == 1}, {{r0_type, TypeSet{T_STACK}}}, {r0.svalue == 2},
26✔
148
                 {{r0_type, TypeSet{T_MAP, T_STACK}}}, {r0.svalue >= 1, r0.svalue <= 2});
4✔
149
}
13✔
150

151
// 6) Shared memory offsets and sizes
152
TEST_CASE("join shared offsets and sizes", "[join][lattice]") {
2✔
153
    using namespace dsl_syntax;
1✔
154
    require_join({{r0_type, TypeSet{T_SHARED}}}, {r0.shared_offset == 16, r0.shared_region_size == 64},
33✔
155
                 {{r0_type, TypeSet{T_SHARED}}}, {r0.shared_offset == 32, r0.shared_region_size == 64},
4✔
156
                 {{r0_type, TypeSet{T_SHARED}}},
4✔
157
                 {r0.shared_offset >= 16, r0.shared_offset <= 32, r0.shared_region_size == 64});
158
}
13✔
159

160
TEST_CASE("join preserves shared offset across type widening", "[join][lattice]") {
2✔
161
    using namespace dsl_syntax;
1✔
162
    require_join({{r0_type, TypeSet{T_SHARED}}}, {r0.shared_offset == 0}, {{r0_type, TypeSet{T_NUM}}}, {},
19✔
163
                 {{r0_type, TypeSet{T_NUM, T_SHARED}}}, {r0.shared_offset == 0});
4✔
164
}
8✔
165

166
// 7) Stack numeric size interactions
167
TEST_CASE("join stack offsets and numeric size", "[join][lattice]") {
2✔
168
    using namespace dsl_syntax;
1✔
169
    require_join({{r0_type, TypeSet{T_STACK}}}, {r0.stack_offset == 64, r0.stack_numeric_size == 8},
33✔
170
                 {{r0_type, TypeSet{T_STACK}}}, {r0.stack_offset == 96, r0.stack_numeric_size == 8},
4✔
171
                 {{r0_type, TypeSet{T_STACK}}},
4✔
172
                 {r0.stack_offset >= 64, r0.stack_offset <= 96, r0.stack_numeric_size == 8});
173
}
13✔
174

175
TEST_CASE("join preserves stack facts across type mismatch", "[join][lattice]") {
2✔
176
    using namespace dsl_syntax;
1✔
177
    require_join({{r0_type, TypeSet{T_STACK}}}, {r0.stack_offset == 64, r0.stack_numeric_size == 8},
24✔
178
                 {{r0_type, TypeSet{T_NUM}}}, {}, {{r0_type, TypeSet{T_NUM, T_STACK}}},
8✔
179
                 {r0.stack_offset == 64, r0.stack_numeric_size == 8});
180
}
8✔
181

182
// 8) Context and map-related variables
183
TEST_CASE("join context offsets", "[join][lattice]") {
2✔
184
    using namespace dsl_syntax;
1✔
185
    require_join({{r0_type, TypeSet{T_CTX}}}, {r0.ctx_offset == 0}, {{r0_type, TypeSet{T_CTX}}}, {r0.ctx_offset == 16},
26✔
186
                 {{r0_type, TypeSet{T_CTX}}}, {r0.ctx_offset >= 0, r0.ctx_offset <= 16});
4✔
187
}
13✔
188

189
TEST_CASE("join map fd constants", "[join][lattice]") {
2✔
190
    using namespace dsl_syntax;
1✔
191
    require_join({{r0_type, TypeSet{T_MAP}}}, {r0.map_fd == 10}, {{r0_type, TypeSet{T_MAP}}}, {r0.map_fd == 10},
24✔
192
                 {{r0_type, TypeSet{T_MAP}}}, {r0.map_fd == 10});
4✔
193
}
12✔
194

195
TEST_CASE("join map vs non-map keeps map fd", "[join][lattice]") {
2✔
196
    using namespace dsl_syntax;
1✔
197
    require_join({{r0_type, TypeSet{T_MAP}}}, {r0.map_fd == 7}, {{r0_type, TypeSet{T_NUM}}}, {},
19✔
198
                 {{r0_type, TypeSet{T_MAP, T_NUM}}}, {r0.map_fd == 7});
4✔
199
}
8✔
200

201
// 9) Top/Bottom edge behavior
202
TEST_CASE("join with bottom on one side returns other", "[join][lattice]") {
2✔
203
    using namespace dsl_syntax;
1✔
204
    require_join({}, {r2.svalue == 0, r2.svalue != 0}, {{r0_type, TypeSet{T_NUM}}}, {r2.svalue == 5},
24✔
205
                 {{r0_type, TypeSet{T_NUM}}}, {r2.svalue == 5});
4✔
206
}
12✔
207

208
// 10) Type range with value ranges
209
TEST_CASE("join of type ranges with value ranges", "[join][lattice]") {
2✔
210
    using namespace dsl_syntax;
1✔
211
    require_join({{r0_type, TypeSet{T_PACKET, T_STACK, T_SHARED}}}, {r0.svalue >= 0, r0.svalue <= 100},
31✔
212
                 {{r0_type, TypeSet{T_NUM, T_CTX, T_PACKET, T_STACK}}}, {r0.svalue >= 50, r0.svalue <= 200},
4✔
213
                 {{r0_type, TypeSet{T_NUM, T_CTX, T_PACKET, T_STACK, T_SHARED}}}, {r0.svalue >= 0, r0.svalue <= 200});
4✔
214
}
15✔
215

216
TEST_CASE("join regression from 74+103 to 104", "[join][lattice]") {
2✔
217
    using namespace dsl_syntax;
1✔
218
    // distillation of running:
219
    // ./check --no-simplify ebpf-samples/linux/test_map_in_map_kern.o kprobe/sys_connect - v
220

221
    require_join(
60✔
222
        {{r0_type, TypeSet{T_MAP}}, {r6_type, TypeSet{T_MAP}}, {r7_type, TypeSet{T_NUM}}, {r10_type, TypeSet{T_STACK}}},
7✔
223
        {
224
            r0.svalue == 0,
225
            r6.svalue == 0,
226
            r7.svalue == 0,
227
            r1.svalue == 153,
228
            r10.svalue >= 4096,
229
            r10.svalue <= 2147418112,
230
        },
231
        {{r0_type, TypeSet{T_NUM}}, {r6_type, TypeSet{T_NUM}}, {r7_type, TypeSet{T_NUM}}, {r10_type, TypeSet{T_STACK}}},
7✔
232
        {
233
            r7.svalue >= 1,
234
            r1.svalue == 146,
235
            r10.svalue >= 4096,
236
            r10.svalue <= 2147418112,
237
        },
238
        // Conservative overapproximation of correct join
239
        {{r0_type, TypeSet{T_MAP, T_NUM}},
2✔
240
         {r6_type, TypeSet{T_MAP, T_NUM}},
2✔
241
         {r7_type, TypeSet{T_NUM}},
2✔
242
         {r10_type, TypeSet{T_STACK}}},
4✔
243
        {
244
            r7.svalue >= 0,
245
            r1.svalue >= 146,
246
            r1.svalue <= 153,
247
            r10.svalue >= 4096,
248
            r10.svalue <= 2147418112,
249
        });
250
}
18✔
251

252
// 11) Relational domain properties
253
TEST_CASE("join preserves equality relation across branches", "[join][lattice]") {
2✔
254
    using namespace dsl_syntax;
1✔
255
    // Both branches assert r0.svalue == r1.svalue with different constants; after join, equality should be preserved
256
    require_join({{r0_type, TypeSet{T_NUM}}, {r1_type, TypeSet{T_NUM}}},
45✔
257
                 {eq(r0.svalue, r1.svalue), r0.svalue == 5, eq(r0.uvalue, r1.uvalue)},
258
                 {{r0_type, TypeSet{T_NUM}}, {r1_type, TypeSet{T_NUM}}},
5✔
259
                 {eq(r0.svalue, r1.svalue), r0.svalue == 7, eq(r0.uvalue, r1.uvalue)},
260
                 {{r0_type, TypeSet{T_NUM}}, {r1_type, TypeSet{T_NUM}}},
5✔
261
                 {r0.svalue >= 5, r0.svalue <= 7, r1.svalue >= 5, r1.svalue <= 7, eq(r0.svalue, r1.svalue),
262
                  eq(r0.uvalue, r1.uvalue)});
263
}
14✔
264

265
TEST_CASE("join drops opposing order relations but keeps per-var hulls", "[join][lattice]") {
2✔
266
    using namespace dsl_syntax;
1✔
267
    // One branch has r0.svalue <= r1.svalue, the other r1.svalue <= r0.svalue, on the same intervals.
268
    require_join({{r0_type, TypeSet{T_NUM}}, {r1_type, TypeSet{T_NUM}}},
49✔
269
                 {r0.svalue >= 0, r0.svalue <= 10, r1.svalue >= 5, r1.svalue <= 15, r0.svalue <= r1.svalue},
270
                 {{r0_type, TypeSet{T_NUM}}, {r1_type, TypeSet{T_NUM}}},
5✔
271
                 {r0.svalue >= 0, r0.svalue <= 10, r1.svalue >= 5, r1.svalue <= 15, r1.svalue <= r0.svalue},
272
                 {{r0_type, TypeSet{T_NUM}}, {r1_type, TypeSet{T_NUM}}},
5✔
273
                 {r0.svalue >= 0, r0.svalue <= 10, r1.svalue >= 5, r1.svalue <= 15});
274
}
18✔
275

276
// 12) svalue/uvalue interactions and implicit holes
277
TEST_CASE("join preserves svalue/uvalue-implied hole when both branches imply it", "[join][lattice]") {
2✔
278
    using namespace dsl_syntax;
1✔
279
    // Negative signed implies large unsigned (>= 2^63). Both branches keep this fact, join should too.
280
    const Number two63 = Number::max_int(64) + Number(1); // 2^63
2✔
281
    require_join({{r0_type, TypeSet{T_NUM}}}, {r0.svalue <= -1, r0.uvalue >= two63}, {{r0_type, TypeSet{T_NUM}}},
29✔
282
                 {r0.svalue <= -10, r0.uvalue >= two63}, {{r0_type, TypeSet{T_NUM}}},
4✔
283
                 {r0.svalue <= -1, r0.uvalue >= two63});
284
}
15✔
285

286
TEST_CASE("join may lose hole if other branch allows small non-negative values", "[join][lattice]") {
2✔
287
    using namespace dsl_syntax;
1✔
288
    const Number two63 = Number::max_int(64) + Number(1); // 2^63
2✔
289
    // Branch A implies a hole around 0 (negative signed => u >= 2^63).
290
    // Branch B allows only small non-negative values.
291
    // The joined over-approx (convex hull on each dimension) can lose the hole.
292
    require_join({{r0_type, TypeSet{T_NUM}}}, {r0.svalue <= -1, r0.uvalue >= two63}, {{r0_type, TypeSet{T_NUM}}},
27✔
293
                 {r0.svalue >= 0, r0.svalue <= 100, r0.uvalue >= 0, r0.uvalue <= 100}, {{r0_type, TypeSet{T_NUM}}}, {});
4✔
294
}
13✔
295

296
// 13) Algebraic laws: commutativity and idempotence
297
TEST_CASE("join is commutative and idempotent (domain-wide)", "[join][lattice]") {
2✔
298
    using namespace dsl_syntax;
1✔
299
    const TypeRestrictions TA{{r0_type, TypeSet{T_PACKET}}};
3✔
300
    const std::vector A{r0.svalue >= 1, r0.svalue <= 3, r0.packet_offset == 4};
9✔
301

302
    // Idempotence
303
    require_join(TA, A, TA, A, TA, A);
2✔
304

305
    // Commutativity: A | B = B | A (same over-approx)
306
    const TypeRestrictions TB{{r0_type, TypeSet{T_NUM}}};
3✔
307
    const std::vector B{r0.svalue >= 2, r0.svalue <= 5};
7✔
308
    const TypeRestrictions Tover{{r0_type, TypeSet{T_NUM, T_PACKET}}};
3✔
309
    const std::vector over{r0.svalue >= 1, r0.svalue <= 5, r0.packet_offset == 4};
9✔
310
    require_join(TA, A, TB, B, Tover, over);
2✔
311
    require_join(TB, B, TA, A, Tover, over);
2✔
312
}
17✔
313

314
// 14) Numeric value across pointer/num join (value facts should not leak to pointer case)
315
TEST_CASE("join does not conflate value facts across pointer/num", "[join][lattice]") {
2✔
316
    using namespace dsl_syntax;
1✔
317
    require_join({{r0_type, TypeSet{T_NUM}}}, {r0.svalue == 42}, {{r0_type, TypeSet{T_PACKET}}},
24✔
318
                 {r0.packet_offset == 8}, {{r0_type, TypeSet{T_NUM, T_PACKET}}}, {r0.packet_offset == 8});
4✔
319
}
12✔
320

321
// 15) Extreme bounds (overflow safety of convex hull)
322
TEST_CASE("join convex hull with extreme 64-bit bounds", "[join][lattice]") {
2✔
323
    using namespace dsl_syntax;
1✔
324
    const Number min64 = Number::min_int(64);
2✔
325
    const Number max64 = Number::max_int(64);
2✔
326
    require_join({{r0_type, TypeSet{T_NUM}}}, {r0.svalue >= min64, r0.svalue <= -1}, {{r0_type, TypeSet{T_NUM}}},
30✔
327
                 {r0.svalue >= 1, r0.svalue <= max64}, {{r0_type, TypeSet{T_NUM}}},
4✔
328
                 {r0.svalue >= min64, r0.svalue <= max64});
329
}
15✔
330

331
// 16) Disjoint intervals with a gap
332
TEST_CASE("join hull of disjoint intervals with gap", "[join][lattice]") {
2✔
333
    using namespace dsl_syntax;
1✔
334
    require_join({{r0_type, TypeSet{T_NUM}}}, {r0.svalue >= -10, r0.svalue <= -1}, {{r0_type, TypeSet{T_NUM}}},
30✔
335
                 {r0.svalue >= 1, r0.svalue <= 10}, {{r0_type, TypeSet{T_NUM}}}, {r0.svalue >= -10, r0.svalue <= 10});
4✔
336
}
15✔
337

338
// 17) Mixed per-type fields preserved under widening
339
TEST_CASE("join preserves multiple per-type fields across type widening", "[join][lattice]") {
2✔
340
    using namespace dsl_syntax;
1✔
341
    require_join({{r0_type, TypeSet{T_PACKET}}}, {r0.packet_offset == 8}, {{r0_type, TypeSet{T_STACK}}},
26✔
342
                 {r0.stack_offset == 96}, {{r0_type, TypeSet{T_PACKET, T_STACK}}},
4✔
343
                 {r0.packet_offset == 8, r0.stack_offset == 96});
344
}
12✔
345

346
// 18) Map FD differing constants
347
TEST_CASE("join map fd differing constants forms hull", "[join][lattice]") {
2✔
348
    using namespace dsl_syntax;
1✔
349
    require_join({{r0_type, TypeSet{T_MAP}}}, {r0.map_fd == 7}, {{r0_type, TypeSet{T_MAP}}}, {r0.map_fd == 9},
26✔
350
                 {{r0_type, TypeSet{T_MAP}}}, {r0.map_fd >= 7, r0.map_fd <= 9});
4✔
351
}
13✔
352

353
// 19) Shared region size differing
354
TEST_CASE("join shared region size differing values", "[join][lattice]") {
2✔
355
    using namespace dsl_syntax;
1✔
356
    require_join(
37✔
357
        {{r0_type, TypeSet{T_SHARED}}}, {r0.shared_offset == 0, r0.shared_region_size == 64},
4✔
358
        {{r0_type, TypeSet{T_SHARED}}}, {r0.shared_offset == 32, r0.shared_region_size == 128},
4✔
359
        {{r0_type, TypeSet{T_SHARED}}},
4✔
360
        {r0.shared_offset >= 0, r0.shared_offset <= 32, r0.shared_region_size >= 64, r0.shared_region_size <= 128});
361
}
14✔
362

363
// 20) Context fields vanish when resulting type excludes T_CTX
364
TEST_CASE("join does not retain ctx_offset if resulting type excludes T_CTX", "[join][lattice]") {
2✔
365
    using namespace dsl_syntax;
1✔
366
    require_join({{r0_type, TypeSet{T_CTX}}}, {r0.ctx_offset == 16},
15✔
367
                 {{r0_type, TypeSet{T_NUM, T_CTX, T_PACKET, T_STACK}}}, {},
4✔
368
                 {{r0_type, TypeSet{T_NUM, T_CTX, T_PACKET, T_STACK}}}, {});
4✔
369
}
6✔
370

371
// 21) Multi-register per-type field preservation under mixed joins
372
TEST_CASE("join preserves multi-register per-type field", "[join][lattice]") {
2✔
373
    using namespace dsl_syntax;
1✔
374
    require_join({{r6_type, TypeSet{T_PACKET}}, {r7_type, TypeSet{T_NUM}}}, {r6.packet_offset == 4},
29✔
375
                 {{r6_type, TypeSet{T_NUM}}, {r7_type, TypeSet{T_STACK}}}, {r7.stack_offset == 128},
5✔
376
                 {{r6_type, TypeSet{T_NUM, T_PACKET}}, {r7_type, TypeSet{T_NUM, T_STACK}}},
5✔
377
                 {r6.packet_offset == 4, r7.stack_offset == 128});
378
}
12✔
379

380
// 22) SplitDBM widen closure
381
TEST_CASE("close_after_widen recovers transitive edge through unstable vertex", "[widen][splitdbm]") {
2✔
382
    using namespace splitdbm;
1✔
383

384
    // Vertices: 0 (special zero vertex), 1, 2, 3.
385
    //
386
    // Left (closed) graph:
387
    //   Bounds: 0->v: 100, v->0: 0 for v in {1,2,3}
388
    //   Relations: 1->2: 5, 2->3: 5, 1->3: 10 (closure of 1->2->3)
389
    //
390
    // Right graph (same, except 1->3 is LOOSER: 12 instead of 10):
391
    //   Relations: 1->2: 5, 2->3: 5, 1->3: 12
392
    //
393
    // Widening keeps edges where right <= left, using left's weight:
394
    //   1->2: 5 (kept), 2->3: 5 (kept), 1->3: DROPPED (12 > 10)
395
    //
396
    // Vertex 1 becomes unstable (lost outgoing edge 1->3).
397
    //
398
    // close_after_widen should run Dijkstra recovery from unstable vertex 1
399
    // and discover the transitive path 1->2->3 = 5+5 = 10, adding edge 1->3: 10.
400

401
    auto make_graph = [](Weight w13) {
5✔
402
        Graph g;
4✔
403
        g.growTo(4);
4✔
404
        g.add_edge(0, Weight(100), 1);
4✔
405
        g.add_edge(1, Weight(0), 0);
4✔
406
        g.add_edge(0, Weight(100), 2);
4✔
407
        g.add_edge(2, Weight(0), 0);
4✔
408
        g.add_edge(0, Weight(100), 3);
4✔
409
        g.add_edge(3, Weight(0), 0);
4✔
410
        g.add_edge(1, Weight(5), 2);
4✔
411
        g.add_edge(2, Weight(5), 3);
4✔
412
        g.add_edge(1, w13, 3);
4✔
413
        return g;
4✔
UNCOV
414
    };
×
415

416
    std::vector<Weight> pot = {Weight(0), Weight(0), Weight(5), Weight(10)};
3✔
417

418
    SplitDBM left(make_graph(Weight(10)), std::vector<Weight>(pot), VertSet{});
2✔
419
    SplitDBM right(make_graph(Weight(12)), std::vector<Weight>(pot), VertSet{});
2✔
420

421
    AlignedPair aligned{
1✔
422
        .left = left,
423
        .right = right,
424
        .left_perm = {0, 1, 2, 3},
425
        .right_perm = {0, 1, 2, 3},
426
        .initial_potentials = std::vector<Weight>(pot),
427
    };
4✔
428

429
    SplitDBM result = SplitDBM::widen(aligned);
2✔
430
    const Graph& rg = result.graph();
2✔
431

432
    REQUIRE(rg.elem(1, 2));
2✔
433
    CHECK(rg.edge_val(1, 2) == Weight(5));
2✔
434

435
    REQUIRE(rg.elem(2, 3));
2✔
436
    CHECK(rg.edge_val(2, 3) == Weight(5));
2✔
437

438
    // The transitive edge 1->3 should be recovered via path 1->2->3 = 5+5 = 10.
439
    REQUIRE(rg.elem(1, 3));
2✔
440
    CHECK(rg.edge_val(1, 3) == Weight(10));
2✔
441
}
2✔
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