From 80396c78a6ea263328ba237a43010de15f639fd4 Mon Sep 17 00:00:00 2001 From: varun-r-mallya Date: Sun, 12 Oct 2025 20:59:18 +0530 Subject: [PATCH] recursive parsing fix without ctypes in recursed type --- pythonbpf/vmlinux_parser/class_handler.py | 191 +++++++------------- pythonbpf/vmlinux_parser/dependency_node.py | 3 +- tests/failing_tests/xdp_pass.py | 7 +- 3 files changed, 73 insertions(+), 128 deletions(-) diff --git a/pythonbpf/vmlinux_parser/class_handler.py b/pythonbpf/vmlinux_parser/class_handler.py index b41cd72..238eefa 100644 --- a/pythonbpf/vmlinux_parser/class_handler.py +++ b/pythonbpf/vmlinux_parser/class_handler.py @@ -4,6 +4,7 @@ import importlib from .dependency_handler import DependencyHandler from .dependency_node import DependencyNode import ctypes +from typing import Optional, Any logger = logging.getLogger(__name__) @@ -13,6 +14,7 @@ def get_module_symbols(module_name: str): imported_module = importlib.import_module(module_name) return [name for name in dir(imported_module)], imported_module + def process_vmlinux_class(node, llvm_module, handler: DependencyHandler): symbols_in_module, imported_module = get_module_symbols("vmlinux") if node.name in symbols_in_module: @@ -21,37 +23,30 @@ def process_vmlinux_class(node, llvm_module, handler: DependencyHandler): else: raise ImportError(f"{node.name} not in vmlinux") -# Recursive function that gets all the dependent classes and adds them to handler -def process_vmlinux_post_ast(node, llvm_module, handler: DependencyHandler, processing_stack=None): - """ - Recursively process vmlinux classes and their dependencies. - Args: - node: The class/type to process - llvm_module: The LLVM module context - handler: DependencyHandler to track all nodes - processing_stack: Set of currently processing nodes to detect cycles - """ +def process_vmlinux_post_ast( + elem_type_class, llvm_handler, handler: DependencyHandler, processing_stack=None +): # Initialize processing stack on first call if processing_stack is None: processing_stack = set() - symbols_in_module, imported_module = get_module_symbols("vmlinux") - # Handle both node objects and type objects - if hasattr(node, "name"): - current_symbol_name = node.name - elif hasattr(node, "__name__"): - current_symbol_name = node.__name__ - else: - current_symbol_name = str(node) + current_symbol_name = elem_type_class.__name__ + field_table = {} + is_complex_type = False + containing_type: Optional[Any] = None + ctype_complex_type: Optional[Any] = None + type_length: Optional[int] = None + module_name = getattr(elem_type_class, "__module__", None) - if current_symbol_name not in symbols_in_module: - raise ImportError(f"{current_symbol_name} not present in module vmlinux") + if hasattr(elem_type_class, "_length_") and is_complex_type: + type_length = elem_type_class._length_ - # Check if we're already processing this node (circular dependency) if current_symbol_name in processing_stack: - logger.debug(f"Circular dependency detected for {current_symbol_name}, skipping") + logger.debug( + f"Circular dependency detected for {current_symbol_name}, skipping" + ) return True # Check if already processed @@ -62,116 +57,64 @@ def process_vmlinux_post_ast(node, llvm_module, handler: DependencyHandler, proc logger.info(f"Node {current_symbol_name} already processed and ready") return True - logger.info(f"Resolving vmlinux class {current_symbol_name}") - logger.debug( - f"Current handler state: {handler.is_ready} readiness and {handler.get_all_nodes()} all nodes" - ) - - # Add to processing stack to detect cycles processing_stack.add(current_symbol_name) - try: - field_table = {} # should contain the field and it's type. + if module_name == "vmlinux": + if hasattr(elem_type_class, "_type_"): + is_complex_type = True + containing_type = elem_type_class._type_ + if containing_type.__module__ == "vmlinux": + print("Very weird type ig for containing type", containing_type) + elif containing_type.__module__ == ctypes.__name__: + if isinstance(elem_type_class, type): + if issubclass(elem_type_class, ctypes.Array): + ctype_complex_type = ctypes.Array + elif issubclass(elem_type_class, ctypes._Pointer): + ctype_complex_type = ctypes._Pointer + else: + raise TypeError("Unsupported ctypes subclass") + # handle ctype complex type - # Get the class object from the module - class_obj = getattr(imported_module, current_symbol_name) - - # Inspect the class fields - if hasattr(class_obj, "_fields_"): - for field_name, field_type in class_obj._fields_: - field_table[field_name] = field_type - elif hasattr(class_obj, "__annotations__"): - for field_name, field_type in class_obj.__annotations__.items(): - field_table[field_name] = field_type - else: - raise TypeError("Could not get required class and definition") - - logger.debug(f"Extracted fields for {current_symbol_name}: {field_table}") - - # Create or get the node - if handler.has_node(current_symbol_name): - new_dep_node = handler.get_node(current_symbol_name) + else: + raise ImportError(f"Unsupported module of {containing_type}") else: new_dep_node = DependencyNode(name=current_symbol_name) handler.add_node(new_dep_node) - - # Process each field - for elem_name, elem_type in field_table.items(): - module_name = getattr(elem_type, "__module__", None) - - if module_name == ctypes.__name__: - # Simple ctypes - mark as ready immediately - new_dep_node.add_field(elem_name, elem_type, ready=True) - - elif module_name == "vmlinux": - # Complex vmlinux type - needs recursive processing - new_dep_node.add_field(elem_name, elem_type, ready=False) - logger.debug(f"Processing vmlinux field: {elem_name}, type: {elem_type}") - - identify_ctypes_type(elem_name, elem_type, new_dep_node) - - # Determine the actual symbol to process - symbol_name = ( - elem_type.__name__ - if hasattr(elem_type, "__name__") - else str(elem_type) - ) - vmlinux_symbol = None - - # Handle pointers/arrays to other types - if hasattr(elem_type, "_type_"): - containing_module_name = getattr( - (elem_type._type_), "__module__", None - ) - if containing_module_name == ctypes.__name__: - # Pointer/Array to ctypes - mark as ready - new_dep_node.set_field_ready(elem_name, True) - continue - elif containing_module_name == "vmlinux": - # Pointer/Array to vmlinux type - symbol_name = ( - (elem_type._type_).__name__ - if hasattr((elem_type._type_), "__name__") - else str(elem_type._type_) - ) - - # Self-referential check - if symbol_name == current_symbol_name: - logger.debug(f"Self-referential field {elem_name} in {current_symbol_name}") - # For pointers to self, we can mark as ready since the type is being defined - new_dep_node.set_field_ready(elem_name, True) - continue - - vmlinux_symbol = getattr(imported_module, symbol_name) - else: - # Direct vmlinux type (not pointer/array) - vmlinux_symbol = getattr(imported_module, symbol_name) - - # Recursively process the dependency - if vmlinux_symbol is not None: - if process_vmlinux_post_ast(vmlinux_symbol, llvm_module, handler, processing_stack): - new_dep_node.set_field_ready(elem_name, True) + class_obj = getattr(imported_module, current_symbol_name) + # Inspect the class fields + if hasattr(class_obj, "_fields_"): + for field_name, field_type in class_obj._fields_: + field_table[field_name] = field_type + elif hasattr(class_obj, "__annotations__"): + for field_name, field_type in class_obj.__annotations__.items(): + field_table[field_name] = field_type else: - raise ValueError( - f"{elem_name} with type {elem_type} not supported in recursive resolver" - ) + raise TypeError("Could not get required class and definition") - logger.info(f"Successfully processed node: {current_symbol_name}") - return True + logger.info(f"Extracted fields for {current_symbol_name}: {field_table}") - finally: - # Remove from processing stack when done - processing_stack.discard(current_symbol_name) + for elem_name, elem_type in field_table.items(): + local_module_name = getattr(elem_type, "__module__", None) + if local_module_name == ctypes.__name__: + new_dep_node.add_field(elem_name, elem_type, ready=True) + logger.info(f"Field {elem_name} is direct ctypes type: {elem_type}") + elif local_module_name == "vmlinux": + new_dep_node.add_field(elem_name, elem_type, ready=False) + logger.debug( + f"Processing vmlinux field: {elem_name}, type: {elem_type}" + ) + if process_vmlinux_post_ast( + elem_type, llvm_handler, handler, processing_stack + ): + new_dep_node.set_field_ready(elem_name, True) + else: + raise ValueError( + f"{elem_name} with type {elem_type} from module {module_name} not supported in recursive resolver" + ) + print("") - -def identify_ctypes_type(elem_name, elem_type, new_dep_node: DependencyNode): - if isinstance(elem_type, type): - if issubclass(elem_type, ctypes.Array): - new_dep_node.set_field_type(elem_name, ctypes.Array) - new_dep_node.set_field_containing_type(elem_name, elem_type._type_) - new_dep_node.set_field_type_size(elem_name, elem_type._length_) - elif issubclass(elem_type, ctypes._Pointer): - new_dep_node.set_field_type(elem_name, ctypes._Pointer) - new_dep_node.set_field_containing_type(elem_name, elem_type._type_) else: - raise TypeError("Instance sent instead of Class") + raise ImportError("UNSUPPORTED Module") + + print(current_symbol_name, "DONE") + print(f"handler readiness {handler.is_ready}") diff --git a/pythonbpf/vmlinux_parser/dependency_node.py b/pythonbpf/vmlinux_parser/dependency_node.py index 6f816c5..4efea06 100644 --- a/pythonbpf/vmlinux_parser/dependency_node.py +++ b/pythonbpf/vmlinux_parser/dependency_node.py @@ -1,7 +1,8 @@ from dataclasses import dataclass, field from typing import Dict, Any, Optional -#TODO: FIX THE FUCKING TYPE NAME CONVENTION. + +# TODO: FIX THE FUCKING TYPE NAME CONVENTION. @dataclass class Field: """Represents a field in a dependency node with its type and readiness state.""" diff --git a/tests/failing_tests/xdp_pass.py b/tests/failing_tests/xdp_pass.py index 14663d3..7698ed9 100644 --- a/tests/failing_tests/xdp_pass.py +++ b/tests/failing_tests/xdp_pass.py @@ -1,9 +1,11 @@ from pythonbpf import bpf, map, section, bpfglobal, compile, compile_to_ir from pythonbpf.maps import HashMap from pythonbpf.helper import XDP_PASS -from vmlinux import struct_ring_buffer_per_cpu # noqa: F401 -from vmlinux import struct_xdp_buff # noqa: F401 from vmlinux import struct_xdp_md +from vmlinux import struct_ring_buffer_per_cpu # noqa: F401 + +# from vmlinux import struct_xdp_buff # noqa: F401 +# from vmlinux import struct_xdp_md from ctypes import c_int64 # Instructions to how to run this program @@ -44,4 +46,3 @@ def LICENSE() -> str: compile_to_ir("xdp_pass.py", "xdp_pass.ll") -compile()