Skip to content

Commit 1b9adc7

Browse files
r41k0uclaude
andcommitted
Tests: Pin augmented assignment on struct fields and the shadowing error
A struct-field test with a non-add operator and a sub-64-bit field (the narrow-on-store path), and a strict expected failure for counter += 1 without a global statement, which now fails as loudly as plain assignment does. Ruff flags that line as F823 (referenced before assignment) -- the same UnboundLocalError Python itself would raise, which is exactly the semantics the global statement requirement mirrors. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L1PX8EuP9C3o3veWGA84RF
1 parent dc9aecf commit 1b9adc7

3 files changed

Lines changed: 65 additions & 0 deletions

File tree

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# `counter += 1` without `global counter` must be the same loud error as plain
2+
# assignment. Before direct lowering this slipped past the allocation pass
3+
# (which only walks Assign) and failed later with a misleading message.
4+
from pythonbpf import bpf, section, bpfglobal, compile
5+
from ctypes import c_void_p, c_int64, c_uint64
6+
7+
8+
@bpf
9+
@bpfglobal
10+
def counter() -> c_uint64:
11+
return c_uint64(0)
12+
13+
14+
@bpf
15+
@section("tracepoint/raw_syscalls/sys_enter")
16+
def prog(ctx: c_void_p) -> c_int64:
17+
counter += 1 # noqa: F823, F841 -- missing `global counter` on purpose; this is
18+
# the UnboundLocalError shape, and the compiler must reject it just as loudly
19+
return c_int64(0)
20+
21+
22+
@bpf
23+
@bpfglobal
24+
def LICENSE() -> str:
25+
return "GPL"
26+
27+
28+
compile()
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# Augmented assignment to a struct field: one GEP, load, op, store. Also a
2+
# non-add operator and a sub-64-bit field, which exercises the narrow-on-store
3+
# path of the direct lowering.
4+
from pythonbpf import bpf, section, bpfglobal, compile, struct
5+
from ctypes import c_void_p, c_int64, c_uint64, c_uint32
6+
7+
8+
@bpf
9+
@struct
10+
class data_t:
11+
pid: c_uint64
12+
hits: c_uint32
13+
14+
15+
@bpf
16+
@section("tracepoint/raw_syscalls/sys_enter")
17+
def prog(ctx: c_void_p) -> c_int64:
18+
x = 5
19+
x -= 1
20+
dat = data_t()
21+
dat.pid = 10
22+
dat.pid += x
23+
dat.hits = 1
24+
dat.hits <<= 2
25+
print(f"pid {dat.pid} hits {dat.hits} x {x}")
26+
return c_int64(0)
27+
28+
29+
@bpf
30+
@bpfglobal
31+
def LICENSE() -> str:
32+
return "GPL"
33+
34+
35+
compile()

‎tests/test_config.toml‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,3 +38,5 @@
3838
"failing_tests/globals_undeclared_name.py" = {reason = "`global x` naming something that is not a @bpfglobal is a compile error", level = "ir"}
3939

4040
"failing_tests/globals_bad_type.py" = {reason = "Non-integer-scalar globals are not supported in milestone 1 (NotImplementedError by design)", level = "ir"}
41+
42+
"failing_tests/globals_augassign_shadowing.py" = {reason = "Augmented assignment to a global name without a `global` statement is a deliberate compile error (would shadow the BPF global)", level = "ir"}

0 commit comments

Comments
 (0)