From dde720f46e678671b5e134656376c6d7244132f2 Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Fri, 9 Oct 2026 03:13:47 +0530 Subject: [PATCH 1/5] Core: Pass helpers the pointer a local holds, not its slot's address A local bound to a map lookup holds a pointer, and get_or_create_ptr_from_arg handed a helper the address of that local's stack slot instead. So count.update(k, prev) stored the kernel address of the map value, not the value, and after prev = prev + 1 it stored the address of the temporary. The verifier accepts both when loading as root, so nothing failed: the failing_tests/vmlinux/named_arg.py example has been passing all along while storing the wrong thing. The slot is now loaded when it holds a pointer. named_arg.py moves to passing_tests, and both it and a new map_update_from_lookup.py are pinned by IR assertions. Co-Authored-By: Claude Opus 5.5 (1M context) --- pythonbpf/helper/helper_utils.py | 5 +++ .../helpers/map_update_from_lookup.py | 31 +++++++++++++++++++ .../vmlinux/named_arg.py | 11 +++---- tests/test_signedness_ir.py | 11 +++++++ 4 files changed, 52 insertions(+), 6 deletions(-) create mode 100644 tests/passing_tests/helpers/map_update_from_lookup.py rename tests/{failing_tests => passing_tests}/vmlinux/named_arg.py (61%) diff --git a/pythonbpf/helper/helper_utils.py b/pythonbpf/helper/helper_utils.py index 28b60b34..a621fefe 100644 --- a/pythonbpf/helper/helper_utils.py +++ b/pythonbpf/helper/helper_utils.py @@ -67,6 +67,11 @@ def get_or_create_ptr_from_arg( else: # Stack space is already allocated ptr = get_var_ptr_from_name(arg.id, local_sym_tab) + if symbol is not None and isinstance(symbol.ir_type, ir.PointerType): + # The slot holds a pointer (a map lookup result, or a local + # rebound to point at a temporary): the helper wants that + # pointer, not the address of the slot it is kept in. + ptr = builder.load(ptr) elif isinstance(arg, ast.Constant) and isinstance(arg.value, int): int_width = 64 # Default to i64 if expected_type and isinstance(expected_type, ir.IntType): diff --git a/tests/passing_tests/helpers/map_update_from_lookup.py b/tests/passing_tests/helpers/map_update_from_lookup.py new file mode 100644 index 00000000..010c6d6f --- /dev/null +++ b/tests/passing_tests/helpers/map_update_from_lookup.py @@ -0,0 +1,31 @@ +# A map lookup result passed straight to map.update(): the helper gets the +# pointer the local holds (the map value), as bpf_map_update_elem(&m, &k, prev, 0) +# would in C, not the address of the local's slot. + +from pythonbpf import bpf, map, section, bpfglobal, compile +from pythonbpf.maps import HashMap +from ctypes import c_void_p, c_int64 + + +@bpf +@map +def count() -> HashMap: + return HashMap(key=c_int64, value=c_int64, max_entries=2) + + +@bpf +@section("tracepoint/syscalls/sys_enter_getpid") +def copy(ctx: c_void_p) -> c_int64: + prev = count.lookup(0) + if prev: + count.update(1, prev) + return c_int64(0) + + +@bpf +@bpfglobal +def LICENSE() -> str: + return "GPL" + + +compile() diff --git a/tests/failing_tests/vmlinux/named_arg.py b/tests/passing_tests/vmlinux/named_arg.py similarity index 61% rename from tests/failing_tests/vmlinux/named_arg.py rename to tests/passing_tests/vmlinux/named_arg.py index bea5e22f..05a553a2 100644 --- a/tests/failing_tests/vmlinux/named_arg.py +++ b/tests/passing_tests/vmlinux/named_arg.py @@ -4,12 +4,11 @@ from ctypes import c_void_p, c_int64 -# NOTE: This example exposes the problems with our typing system. -# We can't do steps on line 25 and 27. -# prev is of type i64**. For prev + 1, we deref it down to i64 -# To assign it back to prev, we need to go back to i64**. -# We cannot allocate space for the intermediate type now. -# We probably need to track the ref/deref chain for each variable. +# `prev` starts as a map lookup result (a pointer) and is rebound to +# `prev + 1`, which lives in a temporary that `prev` then points at. The update +# must store the incremented value: the helper gets the pointer the local +# holds, not the address of the local's own slot (that used to store a kernel +# stack address into the map). @bpf diff --git a/tests/test_signedness_ir.py b/tests/test_signedness_ir.py index 96b003e4..ce1067e8 100644 --- a/tests/test_signedness_ir.py +++ b/tests/test_signedness_ir.py @@ -112,6 +112,17 @@ [r"deref_0_not_null", r"ret i64 %"], [r"ret i64\*"], ), + # A local holding a pointer is passed to a helper as that pointer, loaded + # from its slot; the slot's own address (an i64**) would make the map store + # a kernel address instead of the value. + "helpers/map_update_from_lookup.py": ( + [r'load i64\*, i64\*\* %"prev"'], + [r'i64\*\* %"prev", i64 0\)'], + ), + "vmlinux/named_arg.py": ( + [r'load i64\*, i64\*\* %"prev"'], + [r'i64\*\* %"prev", i64 0\)'], + ), } From 4c57a4e834895f99d3562188ce6bc19dfa3af7c8 Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Fri, 9 Oct 2026 03:13:49 +0530 Subject: [PATCH 2/5] Core: Reject a struct value as a condition with a clear error `if dat:` on a local struct built Constant(struct_type, 0) in convert_to_bool, and llvmlite failed later, while printing the module, with "'int' object is not iterable". A struct has no truth value (C rejects `if (s)`; Python would call any object true), so it is now a TypeError that says to test a field. A pointer to a struct is unaffected. The xfail was named struct_ptr but tests a struct value; it is renamed to struct_value.py and becomes a negative test. Co-Authored-By: Claude Opus 5.5 (1M context) --- pythonbpf/expr/type_normalization.py | 8 ++++++++ .../conditionals/{struct_ptr.py => struct_value.py} | 8 ++++---- tests/test_config.toml | 2 +- 3 files changed, 13 insertions(+), 5 deletions(-) rename tests/failing_tests/conditionals/{struct_ptr.py => struct_value.py} (60%) diff --git a/pythonbpf/expr/type_normalization.py b/pythonbpf/expr/type_normalization.py index e89815f8..049bae13 100644 --- a/pythonbpf/expr/type_normalization.py +++ b/pythonbpf/expr/type_normalization.py @@ -113,6 +113,14 @@ def convert_to_bool(builder, val): """Convert a value to boolean.""" if val.type == ir.IntType(1): return val + if not isinstance(val.type, (ir.IntType, ir.PointerType)): + # C rejects `if (s)` on a struct, and Python would call any object + # true; either way it is never what was meant. + kind = "struct" if isinstance(val.type, ir.BaseStructType) else "value" + raise TypeError( + f"a {kind} of type {val.type} has no truth value: test one of its " + "fields instead" + ) if isinstance(val.type, ir.PointerType): zero = ir.Constant(val.type, None) else: diff --git a/tests/failing_tests/conditionals/struct_ptr.py b/tests/failing_tests/conditionals/struct_value.py similarity index 60% rename from tests/failing_tests/conditionals/struct_ptr.py rename to tests/failing_tests/conditionals/struct_value.py index 7085f819..9c29071e 100644 --- a/tests/failing_tests/conditionals/struct_ptr.py +++ b/tests/failing_tests/conditionals/struct_value.py @@ -1,10 +1,10 @@ from pythonbpf import bpf, struct, section, bpfglobal, compile from ctypes import c_void_p, c_int64, c_uint64 -# NOTE: Decided against fixing this -# as one workaround is to just check any field of the struct -# in the if statement. Ugly but works. -# Might fix in future. +# Negative test: `dat` is a struct value, and a struct has no truth value +# (C rejects `if (s)`; Python would call any object true). It is a compile +# error that says to test one of the struct's fields instead. A *pointer* to +# a struct, such as a map lookup result, is a valid condition (non-null). @bpf diff --git a/tests/test_config.toml b/tests/test_config.toml index 453650f2..10ab94b4 100644 --- a/tests/test_config.toml +++ b/tests/test_config.toml @@ -15,7 +15,7 @@ [xfail] -"failing_tests/conditionals/struct_ptr.py" = {reason = "Struct pointer used directly as boolean condition not supported", level = "ir"} +"failing_tests/conditionals/struct_value.py" = {reason = "A struct value has no truth value (C rejects `if (s)`): a compile error naming the struct", level = "ir"} "failing_tests/return_struct.py" = {reason = "Returning a struct value from an integer function is a type error (nothing to dereference to an integer)", level = "ir"} From 28536c2c072639ed3da519dcefc51b582a33c5fd Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Fri, 9 Oct 2026 03:13:50 +0530 Subject: [PATCH 3/5] Core: Refuse an expression statement whose calls would be dropped handle_expr lowered a statement only when it was a call, and dropped any other expression with an INFO log. So `print(a) if c else print(b)` compiled to nothing at all, and failing_tests/conditionals/oneline.py passed. An expression statement that contains a call is now a compile error; one without (a docstring, a bare name) is still dropped. oneline.py becomes an IR-level xfail. Co-Authored-By: Claude Opus 5.5 (1M context) --- pythonbpf/expr/expr_pass.py | 10 +++++++++- tests/test_config.toml | 2 ++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/pythonbpf/expr/expr_pass.py b/pythonbpf/expr/expr_pass.py index b9984b83..a4a746f2 100644 --- a/pythonbpf/expr/expr_pass.py +++ b/pythonbpf/expr/expr_pass.py @@ -842,5 +842,13 @@ def handle_expr( call, local_sym_tab, ) + elif any(isinstance(node, ast.Call) for node in ast.walk(call)): + # A docstring or a bare name has no effect and can be dropped; an + # expression with a call in it (`f() if c else g()`, `a() or b()`) + # does, and dropping it would silently lose the call. + raise NotImplementedError( + f"Unsupported expression statement on line {expr.lineno}: " + f"{type(call).__name__}; write it as an if statement or assignment" + ) else: - logger.info("Unsupported expression type") + logger.debug(f"Dropping expression statement without effect: {ast.dump(call)}") diff --git a/tests/test_config.toml b/tests/test_config.toml index 10ab94b4..c9353121 100644 --- a/tests/test_config.toml +++ b/tests/test_config.toml @@ -17,6 +17,8 @@ "failing_tests/conditionals/struct_value.py" = {reason = "A struct value has no truth value (C rejects `if (s)`): a compile error naming the struct", level = "ir"} +"failing_tests/conditionals/oneline.py" = {reason = "A conditional expression used as a statement (`f() if c else g()`) is not lowered: a compile error rather than silently dropping both calls; write it as an if statement", level = "ir"} + "failing_tests/return_struct.py" = {reason = "Returning a struct value from an integer function is a type error (nothing to dereference to an integer)", level = "ir"} "failing_tests/license.py" = {reason = "Missing LICENSE global produces IR that llc rejects — should be caught earlier with a clear error message", level = "llc"} From f5eb809f4b7053f7041b57c67c7ff86a191c5f6a Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Fri, 9 Oct 2026 03:13:51 +0530 Subject: [PATCH 4/5] Tests: Move tests that pass out of failing_tests retype, helper_cond, direct_assign and globals had no xfail entry and have been passing at every level; their NOTE headers describe limits that have since been lifted, so they are rewritten. xdp_pass was an xfail only because it used the old count().lookup() spelling of a map call; in the current syntax it compiles. Co-Authored-By: Claude Opus 5.5 (1M context) --- tests/failing_tests/vmlinux/direct_assign.py | 45 ---------------- tests/failing_tests/vmlinux/xdp_pass.py | 54 ------------------- .../assign/retype.py | 10 ++-- .../conditionals/helper_cond.py | 6 +-- .../globals/several_globals.py} | 0 tests/passing_tests/vmlinux/direct_assign.py | 37 +++++++++++++ tests/passing_tests/vmlinux/xdp_pass.py | 41 ++++++++++++++ tests/test_config.toml | 1 - 8 files changed, 83 insertions(+), 111 deletions(-) delete mode 100644 tests/failing_tests/vmlinux/direct_assign.py delete mode 100644 tests/failing_tests/vmlinux/xdp_pass.py rename tests/{failing_tests => passing_tests}/assign/retype.py (53%) rename tests/{failing_tests => passing_tests}/conditionals/helper_cond.py (75%) rename tests/{failing_tests/globals.py => passing_tests/globals/several_globals.py} (100%) create mode 100644 tests/passing_tests/vmlinux/direct_assign.py create mode 100644 tests/passing_tests/vmlinux/xdp_pass.py diff --git a/tests/failing_tests/vmlinux/direct_assign.py b/tests/failing_tests/vmlinux/direct_assign.py deleted file mode 100644 index f3362b40..00000000 --- a/tests/failing_tests/vmlinux/direct_assign.py +++ /dev/null @@ -1,45 +0,0 @@ -from pythonbpf import bpf, map, section, bpfglobal, compile -from vmlinux import XDP_PASS -from pythonbpf.maps import HashMap - -from ctypes import c_void_p, c_int64 - -# NOTE: I have decided to not fix this example for now. -# The issue is in line 31, where we are passing an expression. -# The update helper expects a pointer type. But the problem is -# that we must allocate the space for said pointer in the first -# basic block. As that usage is in a different basic block, we -# are unable to cast the expression to a pointer type. (as we never -# allocated space for it). -# Shall we change our space allocation logic? That allows users to -# spam the same helper with the same args, and still run out of -# stack space. So we consider this usage invalid for now. -# Might fix it later. - - -@bpf -@map -def count() -> HashMap: - return HashMap(key=c_int64, value=c_int64, max_entries=1) - - -@bpf -@section("xdp") -def hello_world(ctx: c_void_p) -> c_int64: - prev = count.lookup(0) - if prev: - count.update(0, prev + 1) - return XDP_PASS - else: - count.update(0, 1) - - return XDP_PASS - - -@bpf -@bpfglobal -def LICENSE() -> str: - return "GPL" - - -compile() diff --git a/tests/failing_tests/vmlinux/xdp_pass.py b/tests/failing_tests/vmlinux/xdp_pass.py deleted file mode 100644 index b37a9b82..00000000 --- a/tests/failing_tests/vmlinux/xdp_pass.py +++ /dev/null @@ -1,54 +0,0 @@ -from pythonbpf import bpf, map, section, bpfglobal, compile_to_ir -from pythonbpf.maps import HashMap -from vmlinux import XDP_PASS -from vmlinux import TASK_COMM_LEN # noqa: F401 - -from vmlinux import struct_qspinlock # noqa: F401 - -# from vmlinux import struct_trace_event_raw_sys_enter # noqa: F401 -# from vmlinux import struct_posix_cputimers # noqa: F401 -from vmlinux import struct_xdp_md - -# from vmlinux import struct_trace_event_raw_sys_enter # noqa: F401 -# from vmlinux import struct_ring_buffer_per_cpu # noqa: F401 -# from vmlinux import struct_request # noqa: F401 -from ctypes import c_int64 - -# Instructions to how to run this program -# 1. Install PythonBPF: pip install pythonbpf -# 2. Run the program: python examples/xdp_pass.py -# 3. Run the program with sudo: sudo tools/check.sh run examples/xdp_pass.o -# 4. Attach object file to any network device with something like ./check.sh xdp examples/xdp_pass.o tailscale0 -# 5. send traffic through the device and observe effects - - -@bpf -@map -def count() -> HashMap: - return HashMap(key=c_int64, value=c_int64, max_entries=1) - - -@bpf -@section("xdp") -def hello_world(ctx: struct_xdp_md) -> c_int64: - key = 0 - one = 1 - prev = count().lookup(key) - if prev: - prevval = prev + 1 - print(f"count: {prevval}") - count().update(key, prevval) - return XDP_PASS - else: - count().update(key, one) - - return XDP_PASS - - -@bpf -@bpfglobal -def LICENSE() -> str: - return "GPL" - - -compile_to_ir("xdp_pass.py", "xdp_pass.ll") diff --git a/tests/failing_tests/assign/retype.py b/tests/passing_tests/assign/retype.py similarity index 53% rename from tests/failing_tests/assign/retype.py rename to tests/passing_tests/assign/retype.py index 0cd99fb3..f4466198 100644 --- a/tests/failing_tests/assign/retype.py +++ b/tests/passing_tests/assign/retype.py @@ -3,13 +3,9 @@ from pythonbpf.maps import HashMap -# NOTE: This example tries to reinterpret the variable `x` to a different type. -# We do not allow this for now, as stack allocations are typed and have to be -# done in the first basic block. Allowing re-interpretation would require -# re-allocation of stack space (possibly in a new basic block), which is not -# supported in eBPF yet. -# We can allow bitcasts in cases where the width of the types is the same in -# the future. But for now, we do not allow any re-interpretation of variables. +# `x` is first bound to a map lookup (a pointer) and then rebound to an integer, +# as Python allows; the comparison sees the integer. This was once refused +# because stack slots are typed, and now works. @bpf diff --git a/tests/failing_tests/conditionals/helper_cond.py b/tests/passing_tests/conditionals/helper_cond.py similarity index 75% rename from tests/failing_tests/conditionals/helper_cond.py rename to tests/passing_tests/conditionals/helper_cond.py index 8cf5bdb9..5d3aa4be 100644 --- a/tests/failing_tests/conditionals/helper_cond.py +++ b/tests/passing_tests/conditionals/helper_cond.py @@ -2,10 +2,8 @@ from ctypes import c_void_p, c_int64, c_uint64 from pythonbpf.maps import HashMap -# NOTE: Decided against fixing this -# as a workaround is assigning the result of lookup to a variable -# and then using that variable in the if statement. -# Might fix in future. +# A map lookup used directly in a comparison: the value is null-checked and +# dereferenced, and a missing key takes the else branch. @bpf diff --git a/tests/failing_tests/globals.py b/tests/passing_tests/globals/several_globals.py similarity index 100% rename from tests/failing_tests/globals.py rename to tests/passing_tests/globals/several_globals.py diff --git a/tests/passing_tests/vmlinux/direct_assign.py b/tests/passing_tests/vmlinux/direct_assign.py new file mode 100644 index 00000000..5c7fc9b3 --- /dev/null +++ b/tests/passing_tests/vmlinux/direct_assign.py @@ -0,0 +1,37 @@ +from pythonbpf import bpf, map, section, bpfglobal, compile +from vmlinux import XDP_PASS +from pythonbpf.maps import HashMap + +from ctypes import c_void_p, c_int64 + +# An expression (`prev + 1`) passed straight to map.update(): it is evaluated +# into a scratch slot and the helper gets that slot's address. This was once +# refused for want of a slot allocated up front, and now works. + + +@bpf +@map +def count() -> HashMap: + return HashMap(key=c_int64, value=c_int64, max_entries=1) + + +@bpf +@section("xdp") +def hello_world(ctx: c_void_p) -> c_int64: + prev = count.lookup(0) + if prev: + count.update(0, prev + 1) + return XDP_PASS + else: + count.update(0, 1) + + return XDP_PASS + + +@bpf +@bpfglobal +def LICENSE() -> str: + return "GPL" + + +compile() diff --git a/tests/passing_tests/vmlinux/xdp_pass.py b/tests/passing_tests/vmlinux/xdp_pass.py new file mode 100644 index 00000000..ccd4dcd8 --- /dev/null +++ b/tests/passing_tests/vmlinux/xdp_pass.py @@ -0,0 +1,41 @@ +# Counts packets in a one-entry map and lets every packet through. It used to +# be an xfail, but the only thing failing was the old `count().lookup(...)` +# spelling of a map call; `count.lookup(...)` is the current one. + +from pythonbpf import bpf, map, section, bpfglobal, compile +from pythonbpf.maps import HashMap +from vmlinux import XDP_PASS +from vmlinux import struct_xdp_md +from ctypes import c_int64 + + +@bpf +@map +def count() -> HashMap: + return HashMap(key=c_int64, value=c_int64, max_entries=1) + + +@bpf +@section("xdp") +def hello_world(ctx: struct_xdp_md) -> c_int64: + key = 0 + one = 1 + prev = count.lookup(key) + if prev: + prevval = prev + 1 + print(f"count: {prevval}") + count.update(key, prevval) + return XDP_PASS + else: + count.update(key, one) + + return XDP_PASS + + +@bpf +@bpfglobal +def LICENSE() -> str: + return "GPL" + + +compile() diff --git a/tests/test_config.toml b/tests/test_config.toml index c9353121..cc24a3a9 100644 --- a/tests/test_config.toml +++ b/tests/test_config.toml @@ -28,7 +28,6 @@ "failing_tests/vmlinux/args_test.py" = {reason = "struct_trace_event_raw_sys_enter args field access not supported", level = "ir"} -"failing_tests/vmlinux/xdp_pass.py" = {reason = "XDP program using vmlinux structs (struct_xdp_md) and complex map/struct interaction not yet supported", level = "ir"} "failing_tests/globals_read_before_shadow.py" = {reason = "Reading a name above the assignment that makes it a local shadowing a global is UnboundLocalError in Python, and a compile error here", level = "ir"} From 37fe5d7a9a435f6af44c8cf614a75fe23eaab386 Mon Sep 17 00:00:00 2001 From: Pragyansh Chaturvedi Date: Fri, 9 Oct 2026 05:31:38 +0530 Subject: [PATCH 5/5] Tests: Derive the map-value pointer-depth check from a clang reference tests/c-form/map_update_from_lookup.bpf.c mirrors the two programs the check covers. At -O0 clang passes bpf_map_update_elem the result of `load ptr, ptr `, in both copy() and rebind(). The old expected pattern only asked for some load of prev, and `if prev` already does one, so it matched the buggy IR too. The check now requires the value loaded from prev's slot to be the helper's value argument. It fails on the code before dde720f and passes on this branch. Co-Authored-By: Claude Opus 5.5 (1M context) --- tests/c-form/map_update_from_lookup.bpf.c | 60 +++++++++++++++++++++++ tests/test_signedness_ir.py | 16 ++++-- 2 files changed, 73 insertions(+), 3 deletions(-) create mode 100644 tests/c-form/map_update_from_lookup.bpf.c diff --git a/tests/c-form/map_update_from_lookup.bpf.c b/tests/c-form/map_update_from_lookup.bpf.c new file mode 100644 index 00000000..fbfd44ba --- /dev/null +++ b/tests/c-form/map_update_from_lookup.bpf.c @@ -0,0 +1,60 @@ +/* Reference for passing a map lookup result to a helper. `prev` is a local + * holding the pointer bpf_map_lookup_elem returned; bpf_map_update_elem gets + * that pointer (the map value), loaded from prev's slot. It never gets &prev, + * the slot's own address, which would make the map store a stack address. + * The clang -O0 IR for this file is the specification for the pointer depth + * that tests/test_signedness_ir.py checks: + * copy() mirrors passing_tests/helpers/map_update_from_lookup.py + * rebind() mirrors passing_tests/vmlinux/named_arg.py, where `prev = prev + 1` + * rebinds prev to a stack temporary holding the sum; the helper + * still gets the pointer loaded from prev's slot. */ +#define SEC(name) __attribute__((section(name), used)) +#define __uint(name, val) int (*name)[val] +#define __type(name, val) typeof(val) *name +typedef long long __s64; + +#define BPF_MAP_TYPE_HASH 1 +#define BPF_ANY 0 +#define XDP_PASS 2 + +static void *(*bpf_map_lookup_elem)(void *map, const void *key) = (void *)1; +static long (*bpf_map_update_elem)(void *map, const void *key, + const void *value, __s64 flags) = (void *)2; + +struct { + __uint(type, BPF_MAP_TYPE_HASH); + __uint(max_entries, 2); + __type(key, __s64); + __type(value, __s64); +} count SEC(".maps"); + +SEC("tracepoint/syscalls/sys_enter_getpid") +__s64 copy(void *ctx) +{ + __s64 key = 0; + __s64 *prev = bpf_map_lookup_elem(&count, &key); + if (prev) { + key = 1; + bpf_map_update_elem(&count, &key, prev, BPF_ANY); + } + return 0; +} + +SEC("xdp") +__s64 rebind(void *ctx) +{ + __s64 key = 0; + __s64 tmp; + __s64 *prev = bpf_map_lookup_elem(&count, &key); + if (prev) { + tmp = *prev + 1; + prev = &tmp; + bpf_map_update_elem(&count, &key, prev, BPF_ANY); + return XDP_PASS; + } + tmp = 1; + bpf_map_update_elem(&count, &key, &tmp, BPF_ANY); + return XDP_PASS; +} + +char LICENSE[] SEC("license") = "GPL"; diff --git a/tests/test_signedness_ir.py b/tests/test_signedness_ir.py index ce1067e8..041ac8b6 100644 --- a/tests/test_signedness_ir.py +++ b/tests/test_signedness_ir.py @@ -19,6 +19,13 @@ PASSING_DIR = Path(__file__).parent / "passing_tests" HAVE_VMLINUX = importlib.util.find_spec("vmlinux") is not None +# The value argument of a map update is the very SSA value loaded from prev's +# slot, not merely some load of prev elsewhere in the function. +UPDATE_WITH_LOADED_PREV = ( + r'(%"[^"]+") = load i64\*, i64\*\* %"prev"\n(?:.*\n)*?' + r'.*call i64 %"[^"]+"\(\{[^}]*\}\* @"count", i64\* %"[^"]+", i64\* \1, i64 0\)' +) + # path under passing_tests -> (patterns that must appear, patterns that must not) CASES = { "signedness/widen_unsigned.py": ( @@ -114,13 +121,16 @@ ), # A local holding a pointer is passed to a helper as that pointer, loaded # from its slot; the slot's own address (an i64**) would make the map store - # a kernel address instead of the value. + # a kernel address instead of the value. The reference is + # tests/c-form/map_update_from_lookup.bpf.c: at -O0 clang passes + # bpf_map_update_elem the result of `load ptr, ptr ` in both + # copy() and rebind(). "helpers/map_update_from_lookup.py": ( - [r'load i64\*, i64\*\* %"prev"'], + [UPDATE_WITH_LOADED_PREV], [r'i64\*\* %"prev", i64 0\)'], ), "vmlinux/named_arg.py": ( - [r'load i64\*, i64\*\* %"prev"'], + [UPDATE_WITH_LOADED_PREV], [r'i64\*\* %"prev", i64 0\)'], ), }