From 6f8103ad4c29d1942a9b097d117d4eb017e21e56 Mon Sep 17 00:00:00 2001 From: Michael Tautschnig Date: Sat, 29 Nov 2025 18:17:16 +0100 Subject: [PATCH] Improve function pointer optimization for constant pointers - Enhanced constant function pointer detection and resolution - Added support for constant pointers to function pointers - Added support for constant structs containing function pointers - Added regression tests for new functionality Fixes: #476 --- .../const-ptr-to-function-ptr/main.c | 62 ++++++++++++++ .../const-ptr-to-function-ptr/test.desc | 10 +++ .../const-struct-with-function-ptr/main.c | 68 +++++++++++++++ .../const-struct-with-function-ptr/test.desc | 10 +++ .../nested-const-function-ptr/main.c | 85 +++++++++++++++++++ .../nested-const-function-ptr/test.desc | 10 +++ .../remove_const_function_pointers.cpp | 67 ++++++++++++++- 7 files changed, 309 insertions(+), 3 deletions(-) create mode 100644 regression/goto-instrument/const-ptr-to-function-ptr/main.c create mode 100644 regression/goto-instrument/const-ptr-to-function-ptr/test.desc create mode 100644 regression/goto-instrument/const-struct-with-function-ptr/main.c create mode 100644 regression/goto-instrument/const-struct-with-function-ptr/test.desc create mode 100644 regression/goto-instrument/nested-const-function-ptr/main.c create mode 100644 regression/goto-instrument/nested-const-function-ptr/test.desc diff --git a/regression/goto-instrument/const-ptr-to-function-ptr/main.c b/regression/goto-instrument/const-ptr-to-function-ptr/main.c new file mode 100644 index 00000000000..2fb57c8efbb --- /dev/null +++ b/regression/goto-instrument/const-ptr-to-function-ptr/main.c @@ -0,0 +1,62 @@ +#include + +void f1(void) +{ + printf("f1\n"); +} +void f2(void) +{ + printf("f2\n"); +} +void f3(void) +{ + printf("f3\n"); +} + +typedef void (*func_ptr)(void); + +// Test 1: constant pointer to function pointer +void test_const_ptr_to_fp(void) +{ + func_ptr fp = f1; + func_ptr *const ptr_to_fp = &fp; + (**ptr_to_fp)(); // Should resolve to f1 +} + +// Test 2: constant pointer to function pointer in array +void test_const_ptr_to_fp_array(void) +{ + func_ptr fps[] = {f1, f2, f3}; + func_ptr *const ptr_to_fp = &fps[1]; + (**ptr_to_fp)(); // Should resolve to f2 +} + +// Test 3: struct with constant function pointer member +struct func_struct +{ + func_ptr const fp; + int value; +}; + +void test_const_struct_member(void) +{ + const struct func_struct fs = {f3, 42}; + fs.fp(); // Should resolve to f3 +} + +// Test 4: constant pointer to struct containing function pointer +void test_const_ptr_to_struct(void) +{ + struct func_struct fs = {f2, 10}; + const struct func_struct *const ptr_fs = &fs; + ptr_fs->fp(); // Should resolve to f2 +} + +int main(void) +{ + test_const_ptr_to_fp(); + test_const_ptr_to_fp_array(); + test_const_struct_member(); + test_const_ptr_to_struct(); + return 0; +} diff --git a/regression/goto-instrument/const-ptr-to-function-ptr/test.desc b/regression/goto-instrument/const-ptr-to-function-ptr/test.desc new file mode 100644 index 00000000000..600f5241011 --- /dev/null +++ b/regression/goto-instrument/const-ptr-to-function-ptr/test.desc @@ -0,0 +1,10 @@ +CORE +main.c +--remove-const-function-pointers --show-goto-functions +^\s*CALL f1\(\) +^\s*CALL f2\(\) +^\s*CALL f3\(\) +^EXIT=0$ +^SIGNAL=0$ +-- +^warning: ignoring diff --git a/regression/goto-instrument/const-struct-with-function-ptr/main.c b/regression/goto-instrument/const-struct-with-function-ptr/main.c new file mode 100644 index 00000000000..c9a91c6cd26 --- /dev/null +++ b/regression/goto-instrument/const-struct-with-function-ptr/main.c @@ -0,0 +1,68 @@ +#include + +void handler_a(void) +{ + printf("A\n"); +} +void handler_b(void) +{ + printf("B\n"); +} +void handler_c(void) +{ + printf("C\n"); +} + +typedef void (*handler_func)(void); + +// Test case 1: struct with const function pointer member +struct handler_config +{ + handler_func const handler; + int id; +}; + +void test_const_member(void) +{ + struct handler_config config = {handler_a, 1}; + config.handler(); // Should resolve to handler_a +} + +// Test case 2: const struct with function pointer member +struct handler_ops +{ + handler_func on_event; + int priority; +}; + +void test_const_struct(void) +{ + const struct handler_ops ops = {handler_b, 10}; + ops.on_event(); // Should resolve to handler_b +} + +// Test case 3: const pointer to struct with function pointer +void test_const_ptr_to_struct(void) +{ + struct handler_ops ops = {handler_c, 5}; + const struct handler_ops *const ptr = &ops; + ptr->on_event(); // Should resolve to handler_c +} + +// Test case 4: Array of const structs with function pointers +void test_const_struct_array(void) +{ + const struct handler_config configs[] = { + {handler_a, 1}, {handler_b, 2}, {handler_c, 3}}; + + configs[1].handler(); // Should resolve to handler_b +} + +int main(void) +{ + test_const_member(); + test_const_struct(); + test_const_ptr_to_struct(); + test_const_struct_array(); + return 0; +} diff --git a/regression/goto-instrument/const-struct-with-function-ptr/test.desc b/regression/goto-instrument/const-struct-with-function-ptr/test.desc new file mode 100644 index 00000000000..d847d5eeb6f --- /dev/null +++ b/regression/goto-instrument/const-struct-with-function-ptr/test.desc @@ -0,0 +1,10 @@ +CORE +main.c +--remove-const-function-pointers --show-goto-functions +^\s*CALL handler_a\(\) +^\s*CALL handler_b\(\) +^\s*CALL handler_c\(\) +^EXIT=0$ +^SIGNAL=0$ +-- +^warning: ignoring diff --git a/regression/goto-instrument/nested-const-function-ptr/main.c b/regression/goto-instrument/nested-const-function-ptr/main.c new file mode 100644 index 00000000000..b63d5fccf0f --- /dev/null +++ b/regression/goto-instrument/nested-const-function-ptr/main.c @@ -0,0 +1,85 @@ +#include + +int result = 0; + +void func_1(void) +{ + result = 1; +} +void func_2(void) +{ + result = 2; +} +void func_3(void) +{ + result = 3; +} + +typedef void (*func_ptr_t)(void); + +// Test 1: Pointer to function pointer (double indirection) +void test_ptr_to_fp(void) +{ + func_ptr_t fp = func_1; + func_ptr_t *ptr_to_fp = &fp; + (**ptr_to_fp)(); + assert(result == 1); +} + +// Test 2: Const pointer to function pointer +void test_const_ptr_to_fp(void) +{ + func_ptr_t fp = func_2; + func_ptr_t *const const_ptr_to_fp = &fp; + (**const_ptr_to_fp)(); + assert(result == 2); +} + +// Test 3: Nested struct with function pointers +struct inner +{ + func_ptr_t handler; +}; + +struct outer +{ + struct inner in; + int id; +}; + +void test_nested_struct(void) +{ + const struct outer obj = {{func_3}, 42}; + obj.in.handler(); + assert(result == 3); +} + +// Test 4: Array of function pointer pointers +void test_fp_ptr_array(void) +{ + func_ptr_t f1 = func_1; + func_ptr_t f2 = func_2; + func_ptr_t f3 = func_3; + + func_ptr_t *const fp_array[] = {&f1, &f2, &f3}; + (**fp_array[1])(); + assert(result == 2); +} + +// Test 5: Const array of function pointers +void test_const_fp_array(void) +{ + const func_ptr_t fp_array[] = {func_1, func_2, func_3}; + fp_array[2](); + assert(result == 3); +} + +int main(void) +{ + test_ptr_to_fp(); + test_const_ptr_to_fp(); + test_nested_struct(); + test_fp_ptr_array(); + test_const_fp_array(); + return 0; +} diff --git a/regression/goto-instrument/nested-const-function-ptr/test.desc b/regression/goto-instrument/nested-const-function-ptr/test.desc new file mode 100644 index 00000000000..4cba9f35403 --- /dev/null +++ b/regression/goto-instrument/nested-const-function-ptr/test.desc @@ -0,0 +1,10 @@ +CORE +main.c +--remove-const-function-pointers --show-goto-functions +^\s*CALL func_1\(\) +^\s*CALL func_2\(\) +^\s*CALL func_3\(\) +^EXIT=0$ +^SIGNAL=0$ +-- +^warning: ignoring diff --git a/src/goto-programs/remove_const_function_pointers.cpp b/src/goto-programs/remove_const_function_pointers.cpp index 349855e122c..4037dc6eef2 100644 --- a/src/goto-programs/remove_const_function_pointers.cpp +++ b/src/goto-programs/remove_const_function_pointers.cpp @@ -344,6 +344,38 @@ bool remove_const_function_pointerst::try_resolve_dereference_function_call( return false; } + // Check if the dereferenced values are themselves pointers to + // function pointers that need further dereferencing + // (e.g., **fp where fp is func_ptr *const) + // This check helps ensure we have valid types before attempting resolution + bool all_are_valid_types = true; + for(const exprt &deref_val : potential_deref_values) + { + // Accept either function types (ID_code) or pointers to functions + if(deref_val.type().id() == ID_pointer) + { + const pointer_typet &ptr_type = to_pointer_type(deref_val.type()); + if(ptr_type.base_type().id() != ID_code) + { + all_are_valid_types = false; + break; + } + } + else if(deref_val.type().id() != ID_code) + { + all_are_valid_types = false; + break; + } + } + + if(!all_are_valid_types) + { + LOG( + "Dereferenced value has incompatible type for function resolution", + deref_expr); + return false; + } + return try_resolve_function_calls(potential_deref_values, out_functions); } @@ -606,7 +638,8 @@ bool remove_const_function_pointerst::try_resolve_index_of( /// \param out_is_const: Is the squashed expression constant /// \return Returns true if it was able to squash the member expression If this /// is the case, out_expressions will contain the possible values this member -/// could return The out_is_const will return whether the struct is const. +/// could return The out_is_const will return whether the struct is const or +/// the specific member is const. bool remove_const_function_pointerst::try_resolve_member( const member_exprt &member_expr, expressionst &out_expressions, @@ -621,6 +654,9 @@ bool remove_const_function_pointerst::try_resolve_member( member_expr.compound(), potential_structs, is_struct_const); if(resolved_struct) { + // Also check if the specific member being accessed is const + bool member_is_const = is_const_type(member_expr.type()); + for(const exprt &potential_struct : potential_structs) { if(potential_struct.id()==ID_struct) @@ -654,7 +690,9 @@ bool remove_const_function_pointerst::try_resolve_member( return false; } } - out_is_const=is_struct_const; + // A member access is const if either the struct is const + // OR the member itself is const + out_is_const = is_struct_const || member_is_const; return true; } else @@ -711,8 +749,15 @@ bool remove_const_function_pointerst::try_resolve_dereference( else { LOG("Failed to resolve value of a dereference", address_expr); + return false; } } + else if(pointer_val.is_constant() && pointer_val.is_zero()) + { + // Null pointer - we can't dereference it but it's a valid constant + // Skip it but don't fail + continue; + } else { LOG( @@ -783,13 +828,29 @@ bool remove_const_function_pointerst::is_const_expression( /// To evaluate the const-ness of the type. /// \param type: The type to check /// \return Returns true if the type has ID_C_constant or is an array since -/// arrays are implicitly const in C. +/// arrays are implicitly const in C. For pointers, checks if the pointer +/// itself is const. For structs, checks if the struct is const. bool remove_const_function_pointerst::is_const_type(const typet &type) const { if(type.id() == ID_array) + { return to_array_type(type).element_type().get_bool(ID_C_constant); + } + else if(type.id() == ID_pointer) + { + // For pointers, we check if the pointer itself is const + // (not what it points to) + return type.get_bool(ID_C_constant); + } + else if(type.id() == ID_struct_tag || type.id() == ID_struct) + { + // For structs, check if the struct type is const + return type.get_bool(ID_C_constant); + } else + { return type.get_bool(ID_C_constant); + } } /// To extract the value of the specific component within a struct