Skip to content

Commit 1a2a7f1

Browse files
r41k0uclaude
andcommitted
Core: Dereference a pointer wherever a number is wanted, not only in operands
return p and p + 1 on a map lookup worked, because they go through get_typed_operand, which loads through the pointer with a null check. r.val = p, x = p into an integer local, and c_int64(p) (so also return c_int64(p)) did not: those three sites call eval_expr directly and handed the pointer to the integer path, failing with i64* vs i64. deref_to_value is get_typed_operand's pointer fallback as a helper, used by it and by the three sites, only when the destination is an integer: a pointer slot still aliases (q = p), and an opaque pointer, which names no pointee, is left alone. The descriptor takes the map's declared value ctype, as the operand path does. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
1 parent dc628ee commit 1a2a7f1

3 files changed

Lines changed: 44 additions & 5 deletions

File tree

‎pythonbpf/assign_pass.py‎

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from inspect import isclass
44

55
from llvmlite import ir
6-
from pythonbpf.expr import eval_expr, convert
6+
from pythonbpf.expr import eval_expr, convert, deref_to_value
77
from pythonbpf.helper import emit_probe_read_kernel_str_call
88
from pythonbpf.type_deducer import ctypes_to_ir
99
from pythonbpf.vmlinux_parser.dependency_node import Field
@@ -40,6 +40,11 @@ def handle_struct_field_assignment(
4040
return
4141

4242
val, val_type = val_result
43+
if isinstance(field_type, ir.IntType):
44+
# An integer field takes the value a pointer points at (r.val = p).
45+
val, val_type = deref_to_value(
46+
func, builder, rval, val, val_type, local_sym_tab
47+
)
4348

4449
# Special case: i8* string to [N x i8] char array
4550
if _is_char_array(field_type) and _is_i8_ptr(val_type):
@@ -147,6 +152,12 @@ def handle_variable_assignment(
147152
return False
148153

149154
val, val_type = val_result
155+
if isinstance(var_type, ir.IntType):
156+
# An integer slot takes the value a pointer points at (x = p); a
157+
# pointer slot keeps the pointer, below.
158+
val, val_type = deref_to_value(
159+
func, builder, rval, val, val_type, local_sym_tab
160+
)
150161
logger.info(
151162
f"Evaluated value for {var_name}: {val} of type {val_type}, expected {var_type}"
152163
)

‎pythonbpf/expr/__init__.py‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from .expr_pass import eval_expr, handle_expr, get_typed_operand
1+
from .expr_pass import eval_expr, handle_expr, get_typed_operand, deref_to_value
22
from .type_normalization import (
33
convert_to_bool,
44
get_base_type_and_depth,
@@ -20,6 +20,7 @@
2020
"canonicalise",
2121
"to_promoted",
2222
"get_typed_operand",
23+
"deref_to_value",
2324
"usual_arithmetic_conversions",
2425
"get_base_type_and_depth",
2526
"deref_to_depth",

‎pythonbpf/expr/expr_pass.py‎

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,28 @@ def _descriptor(val, ty):
222222
return None
223223

224224

225+
def deref_to_value(func, builder, expr, val, val_ty, local_sym_tab):
226+
"""Load through a pointer to an integer (a map-lookup result), with the
227+
null check every dereference gets, so a consumer that wants a number gets
228+
one. Anything else is returned unchanged. The descriptor comes from the
229+
map's declared value ctype when expr names a lookup local (a c_uint64 value
230+
is unsigned), else from the pointee."""
231+
if val is None or not isinstance(val.type, ir.PointerType):
232+
return val, val_ty
233+
if not hasattr(val.type, "pointee"):
234+
return val, val_ty # an opaque pointer says nothing about a pointee
235+
base_type, depth = get_base_type_and_depth(val.type)
236+
if not isinstance(base_type, ir.IntType):
237+
return val, val_ty # a struct or char pointer is not a number
238+
val = deref_to_depth(func, builder, val, depth)
239+
declared = None
240+
if isinstance(expr, ast.Name) and expr.id in local_sym_tab:
241+
meta = local_sym_tab[expr.id].metadata
242+
if isinstance(meta, str) and is_ctypes(meta):
243+
declared = ctypes_to_ir(meta)
244+
return val, _descriptor(val, declared if declared is not None else base_type)
245+
246+
225247
def get_typed_operand(func, compilation_context, operand, builder, local_sym_tab):
226248
"""Evaluate an operand to (value, IntTy). Pointers (map-lookup results) are
227249
dereferenced to the scalar they point at."""
@@ -270,9 +292,8 @@ def get_typed_operand(func, compilation_context, operand, builder, local_sym_tab
270292
raise ValueError(f"Failed to evaluate call expression: {operand}")
271293
val, ty = res
272294
logger.info(f"Evaluated expr to {val} of type {val.type}")
273-
base_type, depth = get_base_type_and_depth(val.type)
274-
if depth > 0:
275-
val = deref_to_depth(func, builder, val, depth)
295+
if isinstance(val.type, ir.PointerType):
296+
return deref_to_value(func, builder, operand, val, ty, local_sym_tab)
276297
return val, _descriptor(val, ty)
277298
raise TypeError(f"Unsupported operand type: {type(operand)}")
278299

@@ -353,6 +374,12 @@ def _handle_ctypes_call(
353374
# Extract the actual IR value and type
354375
# val could be (value, ir_type) or (value, Field)
355376
value, val_type = val
377+
if isinstance(expected_type, ir.IntType) and call_type != "c_void_p":
378+
# c_int64(p) on a map lookup is the value, as p + 0 already is;
379+
# c_void_p(p) keeps the pointer.
380+
value, val_type = deref_to_value(
381+
func, builder, arg, value, val_type, local_sym_tab
382+
)
356383

357384
# If val_type is a Field object (from vmlinux struct), get the actual IR type of the value
358385
if isinstance(val_type, Field):

0 commit comments

Comments
 (0)