diff --git a/docs/CMock_Summary.md b/docs/CMock_Summary.md index 28e2a48..b78327d 100644 --- a/docs/CMock_Summary.md +++ b/docs/CMock_Summary.md @@ -881,6 +881,29 @@ based on other settings, particularly Unity's settings. This needs to be something big enough to point anywhere in Cmock's memory space... usually it's a size_t. +* `CMOCK_MEMCPY` + The memory-copy function used by CMock's internals and the generated mocks. + It defaults to `memcpy` from ``. Override alongside `CMOCK_MEMSET` + to supply a custom implementation on targets where the standard library is + unavailable or undesirable: + + ```c + #define CMOCK_MEMCPY(dst, src, size) my_memcpy(dst, src, size) + ``` + +* `CMOCK_MEMSET` + The memory-set function used by CMock's internals to zero-initialize + allocated blocks. It defaults to `memset` from ``. Override + alongside `CMOCK_MEMCPY` to keep CMock fully independent of the standard + library: + + ```c + #define CMOCK_MEMSET(dst, val, size) my_memset(dst, val, size) + ``` + + If both `CMOCK_MEMCPY` and `CMOCK_MEMSET` are defined before including + `cmock.h`, CMock will not pull in `` at all. + Other Tips ========== diff --git a/lib/cmock_generator.rb b/lib/cmock_generator.rb index 516599a..eb6a3ca 100644 --- a/lib/cmock_generator.rb +++ b/lib/cmock_generator.rb @@ -346,6 +346,7 @@ class CMockGenerator file << " UNITY_SET_DETAIL(CMockString_#{function[:name]});\n" file << " cmock_call_instance = (CMOCK_#{function[:name]}_CALL_INSTANCE*)CMock_Guts_GetAddressFor(Mock.#{function[:name]}_CallInstance);\n" file << " Mock.#{function[:name]}_CallInstance = CMock_Guts_MemNext(Mock.#{function[:name]}_CallInstance);\n" + file << @plugins.run(:mock_precheck_return_thru_ptr, function) file << @plugins.run(:mock_implementation_precheck, function) file << " UNITY_TEST_ASSERT_NOT_NULL(cmock_call_instance, cmock_line, CMockStringCalledMore);\n" file << " cmock_line = cmock_call_instance->LineNumber;\n" diff --git a/lib/cmock_generator_plugin_return_thru_ptr.rb b/lib/cmock_generator_plugin_return_thru_ptr.rb index e6f1bdb..a821826 100644 --- a/lib/cmock_generator_plugin_return_thru_ptr.rb +++ b/lib/cmock_generator_plugin_return_thru_ptr.rb @@ -13,6 +13,8 @@ class CMockGeneratorPluginReturnThruPtr @utils = utils @priority = 9 @config = config + plugins = @config.plugins + @ignore_used = plugins.include?(:ignore) || plugins.include?(:ignore_stateless) end def ptr_to_const(arg_type) @@ -69,6 +71,25 @@ class CMockGeneratorPluginReturnThruPtr lines end + def mock_precheck_return_thru_ptr(function) + return '' unless @ignore_used + + lines = [] + function[:args].each do |arg| + arg_name = arg[:name] + next unless @utils.ptr_or_str?(arg[:type]) && !(arg[:const?]) + + lines << " if (Mock.#{function[:name]}_IgnoreBool && cmock_call_instance != NULL &&\n" + lines << " cmock_call_instance->ReturnThruPtr_#{arg_name}_Used)\n" + lines << " {\n" + lines << " UNITY_TEST_ASSERT_NOT_NULL(#{arg_name}, cmock_line, CMockStringPtrIsNULL);\n" + lines << " CMOCK_MEMCPY((void*)#{arg_name}, (const void*)cmock_call_instance->ReturnThruPtr_#{arg_name}_Val,\n" + lines << " cmock_call_instance->ReturnThruPtr_#{arg_name}_Size);\n" + lines << " }\n" + end + lines + end + def mock_interfaces(function) lines = [] func_name = function[:name] @@ -80,6 +101,23 @@ class CMockGeneratorPluginReturnThruPtr lines << "{\n" lines << " CMOCK_#{func_name}_CALL_INSTANCE* cmock_call_instance = " \ "(CMOCK_#{func_name}_CALL_INSTANCE*)CMock_Guts_GetAddressFor(CMock_Guts_MemEndOfChain(Mock.#{func_name}_CallInstance));\n" + if @ignore_used + lines << " if (Mock.#{func_name}_IgnoreBool &&\n" + lines << " (cmock_call_instance == NULL || cmock_call_instance->ReturnThruPtr_#{arg_name}_Used))\n" + lines << " {\n" + lines << " CMOCK_MEM_INDEX_TYPE cmock_guts_index = CMock_Guts_MemNew(sizeof(CMOCK_#{func_name}_CALL_INSTANCE));\n" + lines << " CMOCK_#{func_name}_CALL_INSTANCE* new_instance = (CMOCK_#{func_name}_CALL_INSTANCE*)CMock_Guts_GetAddressFor(cmock_guts_index);\n" + lines << " UNITY_TEST_ASSERT_NOT_NULL(new_instance, cmock_line, CMockStringOutOfMemory);\n" + lines << " memset(new_instance, 0, sizeof(*new_instance));\n" + lines << " new_instance->LineNumber = cmock_line;\n" + unless function[:return][:void?] + lines << " if (cmock_call_instance != NULL)\n" + lines << " new_instance->ReturnVal = cmock_call_instance->ReturnVal;\n" + end + lines << " Mock.#{func_name}_CallInstance = CMock_Guts_MemChain(Mock.#{func_name}_CallInstance, cmock_guts_index);\n" + lines << " cmock_call_instance = new_instance;\n" + lines << " }\n" + end lines << " UNITY_TEST_ASSERT_NOT_NULL(cmock_call_instance, cmock_line, CMockStringPtrPreExp);\n" lines << " cmock_call_instance->ReturnThruPtr_#{arg_name}_Used = 1;\n" lines << " cmock_call_instance->ReturnThruPtr_#{arg_name}_Val = #{arg_name};\n" @@ -98,7 +136,7 @@ class CMockGeneratorPluginReturnThruPtr lines << " if (cmock_call_instance->ReturnThruPtr_#{arg_name}_Used)\n" lines << " {\n" lines << " UNITY_TEST_ASSERT_NOT_NULL(#{arg_name}, cmock_line, CMockStringPtrIsNULL);\n" - lines << " memcpy((void*)#{arg_name}, (const void*)cmock_call_instance->ReturnThruPtr_#{arg_name}_Val,\n" + lines << " CMOCK_MEMCPY((void*)#{arg_name}, (const void*)cmock_call_instance->ReturnThruPtr_#{arg_name}_Val,\n" lines << " cmock_call_instance->ReturnThruPtr_#{arg_name}_Size);\n" lines << " }\n" end diff --git a/lib/cmock_header_parser.rb b/lib/cmock_header_parser.rb index 36b7779..ef789f8 100644 --- a/lib/cmock_header_parser.rb +++ b/lib/cmock_header_parser.rb @@ -251,6 +251,8 @@ class CMockHeaderParser source.gsub!(/^\s*#.*/, '') # enums, unions, structs, and typedefs can all contain things (e.g. function pointers) that parse like function prototypes, so yank them + # pre-collapse nested brace pairs so that structs containing nested structs/unions are removed as a unit below + source = remove_nested_pairs_of_braces(source) unless cpp # forward declared structs are removed before struct definitions so they don't mess up real thing later. we leave structs keywords in function prototypes source.gsub!(/^[\w\s]*struct[^;{}()]+;/m, '') # remove forward declared structs source.gsub!(/^[\w\s]*(enum|union|struct|typedef)[\w\s()]*\{[^}]+\}[\w\s*,]*;/m, '') # remove struct, union, and enum definitions and typedefs with braces @@ -565,6 +567,10 @@ class CMockHeaderParser # pull asterisks away from arg to place asterisks with type (where they belong) arg_list.gsub!(/\*(\w)/, '* \1') + # normalize parenthesized pointer arguments like int (* numb) -> int * numb + # negative lookahead prevents matching function pointers (*name)(args) and pointer-to-arrays (*name)[dims] + arg_list.gsub!(/\(\s*\*\s*((?:const\s+)?\w+)\s*\)(?!\s*[(\[])/, '* \1') + # scan argument list for function pointers and replace them with custom types arg_list.gsub!(/([\w\s*]+)\(+([\w\s]*)\*[*\s]*([\w\s]*)\s*\)+\s*\(((?:[\w\s*]*,?)*)\s*\)*/) do |_m| functype = "cmock_#{parse_project[:module_name]}_func_ptr#{parse_project[:typedefs].size + 1}" @@ -592,11 +598,24 @@ class CMockHeaderParser funcname = Regexp.last_match(2).strip funcargs = Regexp.last_match(3).strip funconst = '' + funcdecl = '' if funcname.include? 'const' funcname.gsub!('const', '').strip! funconst = 'const ' end - parse_project[:typedefs] << "typedef #{funcret}(*#{functype})(#{funcargs});" + # Extract any calling convention from the return type (it belongs in the function pointer declaration) + @c_calling_conventions.each do |cc| + next unless funcret.include?(cc) + + funcret = funcret.gsub(cc, '').strip + funcdecl = cc + break + end + parse_project[:typedefs] << if funcdecl.empty? + "typedef #{funcret}(*#{functype})(#{funcargs});" + else + "typedef #{funcret}(#{funcdecl} *#{functype})(#{funcargs});" + end funcname = "cmock_arg#{c += 1}" if funcname.empty? "#{functype} #{funconst}#{funcname}" end diff --git a/src/cmock.c b/src/cmock.c index 28f276f..1cc1725 100644 --- a/src/cmock.c +++ b/src/cmock.c @@ -68,7 +68,7 @@ CMOCK_MEM_INDEX_TYPE CMock_Guts_MemNew(CMOCK_MEM_INDEX_TYPE size) /* determine where we're putting this new block, and init its pointer to be the end of the line */ index = CMock_Guts_FreePtr + CMOCK_MEM_INDEX_SIZE; - *(CMOCK_MEM_INDEX_TYPE*)(&CMock_Guts_Buffer[CMock_Guts_FreePtr]) = CMOCK_GUTS_NONE; + CMOCK_MEMSET(&CMock_Guts_Buffer[CMock_Guts_FreePtr], 0, CMOCK_MEM_INDEX_SIZE); CMock_Guts_FreePtr += size; return index; @@ -108,7 +108,7 @@ CMOCK_MEM_INDEX_TYPE CMock_Guts_MemChain(CMOCK_MEM_INDEX_TYPE root_index, CMOCK_ next = root; do { - index = *(CMOCK_MEM_INDEX_TYPE*)((CMOCK_MEM_PTR_AS_INT)next - CMOCK_MEM_INDEX_SIZE); + CMOCK_MEMCPY(&index, (unsigned char*)next - CMOCK_MEM_INDEX_SIZE, sizeof(index)); if (index >= CMock_Guts_FreePtr) { return CMOCK_GUTS_NONE; @@ -119,7 +119,10 @@ CMOCK_MEM_INDEX_TYPE CMock_Guts_MemChain(CMOCK_MEM_INDEX_TYPE root_index, CMOCK_ } } while (index > 0); - *(CMOCK_MEM_INDEX_TYPE*)((CMOCK_MEM_PTR_AS_INT)next - CMOCK_MEM_INDEX_SIZE) = (CMOCK_MEM_INDEX_TYPE)((CMOCK_MEM_PTR_AS_INT)obj - (CMOCK_MEM_PTR_AS_INT)CMock_Guts_Buffer); + { + CMOCK_MEM_INDEX_TYPE tmp = (CMOCK_MEM_INDEX_TYPE)((unsigned char*)obj - CMock_Guts_Buffer); + CMOCK_MEMCPY((unsigned char*)next - CMOCK_MEM_INDEX_SIZE, &tmp, sizeof(tmp)); + } return root_index; } } @@ -141,7 +144,7 @@ CMOCK_MEM_INDEX_TYPE CMock_Guts_MemNext(CMOCK_MEM_INDEX_TYPE previous_item_index /* if the pointer is good, then use it to look up the next index * (we know the first element always goes in zero, so NEXT must always be > 1) */ - index = *(CMOCK_MEM_INDEX_TYPE*)((CMOCK_MEM_PTR_AS_INT)previous_item - CMOCK_MEM_INDEX_SIZE); + CMOCK_MEMCPY(&index, (unsigned char*)previous_item - CMOCK_MEM_INDEX_SIZE, sizeof(index)); if ((index > 1) && (index < CMock_Guts_FreePtr)) { return index; diff --git a/src/cmock_internals.h b/src/cmock_internals.h index 3877296..b3d23f5 100644 --- a/src/cmock_internals.h +++ b/src/cmock_internals.h @@ -83,6 +83,18 @@ extern const char* CMockStringMismatch; #define CMOCK_MEM_SIZE (32768) #endif +/* memory copy/set functions used by CMock internals and generated mocks. + * Override to use custom implementations on targets without standard libc. */ +#if !defined(CMOCK_MEMCPY) || !defined(CMOCK_MEMSET) + #include + #ifndef CMOCK_MEMCPY + #define CMOCK_MEMCPY(a, b, c) memcpy(a, b, c) + #endif + #ifndef CMOCK_MEMSET + #define CMOCK_MEMSET(a, b, c) memset(a, b, c) + #endif +#endif + /* automatically calculated defs for easier reading */ #define CMOCK_MEM_ALIGN_SIZE (CMOCK_MEM_INDEX_TYPE)(1u << CMOCK_MEM_ALIGN) #define CMOCK_MEM_ALIGN_MASK (CMOCK_MEM_INDEX_TYPE)(CMOCK_MEM_ALIGN_SIZE - 1) diff --git a/test/system/test_interactions/const_custom_type.yml b/test/system/test_interactions/const_custom_type.yml new file mode 100644 index 0000000..c7c1890 --- /dev/null +++ b/test/system/test_interactions/const_custom_type.yml @@ -0,0 +1,68 @@ +# ========================================================================= +# CMock - Automatic Mock Generation for C +# ThrowTheSwitch.org +# Copyright (c) 2007-26 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + +--- +:cmock: + :plugins: + - # none + +:systest: + :types: | + typedef enum { + MyTypeA, + MyTypeB, + MyTypeC, + } MyType_t; + + :mockable: | + int myFunc(const MyType_t t_MyType); + + :source: + :header: | + int exercise(const MyType_t t_MyType); + + :code: | + int exercise(const MyType_t t_MyType) + { + return myFunc(t_MyType); + } + + :tests: + :common: | + void setUp(void) {} + void tearDown(void) {} + + :units: + - :pass: TRUE + :should: 'compile and pass when the correct enum value is passed and return value matches' + :code: | + test() + { + myFunc_ExpectAndReturn(MyTypeB, 42); + TEST_ASSERT_EQUAL(42, exercise(MyTypeB)); + } + + - :pass: FALSE + :should: 'fail when the wrong enum value is passed' + :code: | + test() + { + myFunc_ExpectAndReturn(MyTypeB, 42); + exercise(MyTypeC); + } + + - :pass: TRUE + :should: 'pass when called with a const MyType_t variable' + :code: | + test() + { + const MyType_t val = MyTypeA; + myFunc_ExpectAndReturn(MyTypeA, 0); + TEST_ASSERT_EQUAL(0, exercise(val)); + } + +... diff --git a/test/system/test_interactions/const_ptr_return_and_arg.yml b/test/system/test_interactions/const_ptr_return_and_arg.yml new file mode 100644 index 0000000..bc43b70 --- /dev/null +++ b/test/system/test_interactions/const_ptr_return_and_arg.yml @@ -0,0 +1,80 @@ +# ========================================================================= +# CMock - Automatic Mock Generation for C +# ThrowTheSwitch.org +# Copyright (c) 2007-26 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + +--- +:cmock: + :plugins: + - # none + +:systest: + :types: | + + :mockable: | + int *const foo(float const *const self); + + :source: + :header: | + int *const exercise(float const *const self); + + :code: | + int *const exercise(float const *const self) + { + return foo(self); + } + + :tests: + :common: | + static float f1 = 1.0f; + static float f2 = 2.0f; + static int i1 = 10; + static int i2 = 20; + static float const *const self_a = &f1; + static float const *const self_b = &f2; + static int *const ret_a = &i1; + static int *const ret_b = &i2; + + void setUp(void) {} + void tearDown(void) {} + + :units: + - :pass: TRUE + :should: 'compile and pass when the correct argument is passed and return value matches' + :code: | + test() + { + foo_ExpectAndReturn(self_a, ret_a); + TEST_ASSERT_EQUAL_PTR(ret_a, exercise(self_a)); + } + + - :pass: FALSE + :should: 'fail when the wrong argument pointer is passed' + :code: | + test() + { + foo_ExpectAndReturn(self_a, ret_a); + exercise(self_b); + } + + - :pass: TRUE + :should: 'return the exact pointer provided to ExpectAndReturn' + :code: | + test() + { + foo_ExpectAndReturn(self_a, ret_b); + TEST_ASSERT_EQUAL_PTR(ret_b, exercise(self_a)); + } + + - :pass: FALSE + :should: 'fail when the returned pointer does not match what the test expects' + :code: | + test() + { + foo_ExpectAndReturn(self_a, ret_b); + TEST_ASSERT_EQUAL_PTR(ret_a, exercise(self_a)); + } + +... diff --git a/test/system/test_interactions/parsing_challenges_with_func_pointers.yml b/test/system/test_interactions/parsing_challenges_with_func_pointers.yml new file mode 100644 index 0000000..21e2615 --- /dev/null +++ b/test/system/test_interactions/parsing_challenges_with_func_pointers.yml @@ -0,0 +1,88 @@ +# ========================================================================= +# CMock - Automatic Mock Generation for C +# ThrowTheSwitch.org +# Copyright (c) 2007-26 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + +--- +#The purpose of this test is to verify that structs containing function pointer +#members are not mistakenly mocked by CMock. Only actual function prototypes +#at file scope should be mocked. This is especially tricky when the struct +#contains nested anonymous structs or unions, because the struct removal regex +#can be confused by the inner closing brace and leave function pointer members +#in the source for the parser to encounter. +:cmock: + :plugins: + - # none + :includes: + - "" + +:systest: + :types: | + #include + + /* Forward-declare the type so the source header can use SaladBowl* before + the full definition (which lives in the mockable header) is visible. */ + typedef struct SaladBowlStruct SaladBowl; + + :mockable: | + #include + + /* Full struct definition with a nested anonymous struct and function pointer + members. CMock must ignore all of these and only mock saladBowlInit. */ + struct SaladBowlStruct { + struct { + uint16_t remainingCapacity; + uint16_t ingredientCount; + } stats; + + void* (*toss)(struct SaladBowlStruct *self, uint16_t itemSize); + int32_t (*empty)(struct SaladBowlStruct *self); + void* (*grab)(struct SaladBowlStruct *self, uint16_t itemIndex); + void* (*add)(struct SaladBowlStruct *self, uint16_t itemIndex, uint16_t itemSize); + int32_t (*pluck)(struct SaladBowlStruct *self, uint16_t itemIndex); + }; + + int32_t saladBowlInit(SaladBowl *bowl, uint16_t sizeInBytes, uint16_t headerSizeInBytes); + void saladBowlBase(void); + void saladBowlTop(void); + + /* inlines containing function calls look like function prototypes too */ + inline void inlineSaladKit(void) + { + { + saladBowlBase(); + } + { + saladBowlTop(); + } + } + + :source: + :header: | + #include + void exercise_salad_bowl(SaladBowl *bowl); + + :code: | + void exercise_salad_bowl(SaladBowl *bowl) + { + saladBowlInit(bowl, 256, 16); + } + + :tests: + :common: | + SaladBowl g_bowl; + void setUp(void) {} + void tearDown(void) {} + :units: + - :pass: TRUE + :should: 'mock only saladBowlInit and ignore function pointer members of the struct' + :code: | + test() + { + saladBowlInit_ExpectAndReturn(&g_bowl, 256, 16, 0); + exercise_salad_bowl(&g_bowl); + } + +... diff --git a/test/system/test_interactions/return_thru_ptr_ignore.yml b/test/system/test_interactions/return_thru_ptr_ignore.yml new file mode 100644 index 0000000..ecfe3a9 --- /dev/null +++ b/test/system/test_interactions/return_thru_ptr_ignore.yml @@ -0,0 +1,142 @@ +# ========================================================================= +# CMock - Automatic Mock Generation for C +# ThrowTheSwitch.org +# Copyright (c) 2007-26 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + +--- +:cmock: + :mock_path: test/mocks + :mock_prefix: mock_ + :when_ptr: :smart + :plugins: + - :ignore + - :return_thru_ptr + +:systest: + :types: "" + :mockable: | + void ptr_ret_int(int *r); + int ptr_ret_int_rtn(int *r); + + :source: + :header: | + #include + #define lengthof(x) (sizeof(x)/sizeof((x)[0])) + + :code: | + + :tests: + :common: | + void setUp(void) {} + void tearDown(void) {} + + :units: + - :pass: TRUE + :should: "handle a single int* argument" + :code: | + test() + { + int r = 1; + int res = 4; + + ptr_ret_int_Expect(&r); + ptr_ret_int_ReturnThruPtr_r(&res); + + ptr_ret_int(&r); + TEST_ASSERT_EQUAL(4, r); + } + + - :pass: TRUE + :should: "ignore a call but still return arguments" + :code: | + test() + { + int r = 1; + int res = 4; + + ptr_ret_int_Ignore(); + ptr_ret_int_ReturnThruPtr_r(&res); + + ptr_ret_int(&r); + TEST_ASSERT_EQUAL(4, r); + } + + - :pass: TRUE + :should: "queue multiple return values in ignored calls" + :code: | + test() + { + int r = 1; + int res1 = 4; + int res2 = 8; + int res3 = 16; + + ptr_ret_int_Ignore(); + ptr_ret_int_ReturnThruPtr_r(&res1); + ptr_ret_int_ReturnThruPtr_r(&res2); + ptr_ret_int_ReturnThruPtr_r(&res3); + + ptr_ret_int(&r); + TEST_ASSERT_EQUAL(4, r); + ptr_ret_int(&r); + TEST_ASSERT_EQUAL(8, r); + ptr_ret_int(&r); + TEST_ASSERT_EQUAL(16, r); + + } + + - :pass: TRUE + :should: "return func and handle a single int* argument" + :code: | + test() + { + int r = 1; + int res = 4; + + ptr_ret_int_rtn_ExpectAndReturn(&r,1); + ptr_ret_int_rtn_ReturnThruPtr_r(&res); + + TEST_ASSERT_EQUAL_INT(1,ptr_ret_int_rtn(&r)); + TEST_ASSERT_EQUAL(4, r); + } + + - :pass: TRUE + :should: "ignore and return a call but still return arguments" + :code: | + test() + { + int r = 1; + int res = 4; + + ptr_ret_int_rtn_IgnoreAndReturn(1); + ptr_ret_int_rtn_ReturnThruPtr_r(&res); + + TEST_ASSERT_EQUAL_INT(1,ptr_ret_int_rtn(&r)); + TEST_ASSERT_EQUAL(4, r); + } + + - :pass: TRUE + :should: "queue multiple return values in ignore and return calls" + :code: | + test() + { + int r = 1; + int res1 = 4; + int res2 = 8; + int res3 = 16; + + ptr_ret_int_rtn_IgnoreAndReturn(1); + ptr_ret_int_rtn_ReturnThruPtr_r(&res1); + ptr_ret_int_rtn_ReturnThruPtr_r(&res2); + ptr_ret_int_rtn_ReturnThruPtr_r(&res3); + + TEST_ASSERT_EQUAL_INT(1,ptr_ret_int_rtn(&r)); + TEST_ASSERT_EQUAL(4, r); + TEST_ASSERT_EQUAL_INT(1,ptr_ret_int_rtn(&r)); + TEST_ASSERT_EQUAL(8, r); + TEST_ASSERT_EQUAL_INT(1,ptr_ret_int_rtn(&r)); + TEST_ASSERT_EQUAL(16, r); + + } diff --git a/test/system/test_interactions/teardown_mock_calls.yml b/test/system/test_interactions/teardown_mock_calls.yml new file mode 100644 index 0000000..81e32e6 --- /dev/null +++ b/test/system/test_interactions/teardown_mock_calls.yml @@ -0,0 +1,57 @@ +# ========================================================================= +# CMock - Automatic Mock Generation for C +# ThrowTheSwitch.org +# Copyright (c) 2007-26 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + +# Test for issue #67: mock segfault on incorrect expectations in tearDown. +# Verifies that calling a mock function in tearDown without a matching +# expectation produces a clean failure message rather than a crash. + +--- +:cmock: + :plugins: + - # none + +:systest: + :types: | + + :mockable: | + void init(void); + void deinit(void); + + :source: + :header: | + /* no source functions needed for this test */ + + :tests: + :common: | + void setUp(void) {} + void tearDown(void) + { + /* Simulate issue #67: accidentally call a mock with no expectation. + This should produce "Called more times than expected", not a crash. */ + deinit(); + } + + :units: + - :pass: FALSE + :should: 'fail gracefully (not segfault) when a mock is called in tearDown without any expectation' + :code: | + test() + { + /* Empty test body; tearDown calls deinit() with no expectation */ + } + + - :pass: FALSE + :should: 'fail gracefully when mock is called in tearDown even when test body passed' + :code: | + test() + { + /* Test body passes fine; the tearDown call is what causes failure */ + init_Expect(); + init(); + } + +... diff --git a/test/system/test_interactions/teardown_mock_expectations.yml b/test/system/test_interactions/teardown_mock_expectations.yml new file mode 100644 index 0000000..205cea4 --- /dev/null +++ b/test/system/test_interactions/teardown_mock_expectations.yml @@ -0,0 +1,59 @@ +# ========================================================================= +# CMock - Automatic Mock Generation for C +# ThrowTheSwitch.org +# Copyright (c) 2007-26 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + +# Test for issue #67: mock segfault on incorrect expectations in tearDown. +# Verifies that setting mock expectations in tearDown for functions that are +# never called produces a clean failure message rather than a crash. + +--- +:cmock: + :plugins: + - # none + +:systest: + :types: | + + :mockable: | + void init(void); + void deinit(void); + + :source: + :header: | + /* no source functions needed for this test */ + + :tests: + :common: | + void setUp(void) {} + void tearDown(void) + { + /* Simulate issue #67: user meant to call deinit_Expect() but accidentally + called init_Expect() instead. The init function is never actually called, + so CMock should report "Called too few times", not segfault. */ + init_Expect(); + } + + :units: + - :pass: FALSE + :should: 'fail gracefully (not segfault) when wrong expectations are set in tearDown' + :code: | + test() + { + /* Empty test body. tearDown sets up init_Expect() but init is never + called, so CMock_Verify should report an unmet expectation. */ + } + + - :pass: FALSE + :should: 'fail gracefully when wrong expectations are set in tearDown even if test body passed' + :code: | + test() + { + /* Test body is correct; tearDown's wrong expectation causes the failure */ + deinit_Expect(); + deinit(); + } + +... diff --git a/test/unit/cmock_generator_main_test.rb b/test/unit/cmock_generator_main_test.rb index aeecded..b802054 100644 --- a/test/unit/cmock_generator_main_test.rb +++ b/test/unit/cmock_generator_main_test.rb @@ -539,6 +539,7 @@ describe CMockGenerator, "Verify CMockGenerator Module" do " return cmock_call_instance->ReturnVal;\n", "}\n\n" ] + @plugins.expect :run, "", [:mock_precheck_return_thru_ptr, function] @plugins.expect :run, [" uno"], [:mock_implementation_precheck, function] @plugins.expect :run, [" dos"," tres"], [:mock_implementation, function] @@ -577,6 +578,7 @@ describe CMockGenerator, "Verify CMockGenerator Module" do " return cmock_call_instance->ReturnVal;\n", "}\n\n" ] + @plugins.expect :run, "", [:mock_precheck_return_thru_ptr, function] @plugins.expect :run, [" uno"], [:mock_implementation_precheck, function] @plugins.expect :run, [" dos"," tres"], [:mock_implementation, function] @@ -618,6 +620,7 @@ describe CMockGenerator, "Verify CMockGenerator Module" do "}\n", "}\n\n", ] + @plugins.expect :run, "", [:mock_precheck_return_thru_ptr, function] @plugins.expect :run, [" uno"], [:mock_implementation_precheck, function] @plugins.expect :run, [" dos"," tres"], [:mock_implementation, function] @@ -656,6 +659,7 @@ describe CMockGenerator, "Verify CMockGenerator Module" do " return cmock_call_instance->ReturnVal;\n", "}\n\n" ] + @plugins.expect :run, "", [:mock_precheck_return_thru_ptr, function] @plugins.expect :run, [" uno"], [:mock_implementation_precheck, function] @plugins.expect :run, [" dos"," tres"], [:mock_implementation, function] diff --git a/test/unit/cmock_generator_plugin_expect_a_test.rb b/test/unit/cmock_generator_plugin_expect_a_test.rb index eede693..f649ba1 100644 --- a/test/unit/cmock_generator_plugin_expect_a_test.rb +++ b/test/unit/cmock_generator_plugin_expect_a_test.rb @@ -187,4 +187,118 @@ describe CMockGeneratorPluginExpect, "Verify CMockGeneratorPluginExpect Module W returned = @cmock_generator_plugin_expect.mock_verify(function) assert_equal(expected, returned) end + + it "preserve const-pointer ordering in typedef struct fields for arguments" do + function = { + :name => "Willow", + :args => [ + { :name => "ptr_to_const", :type => "const int*", :ptr? => true, :const? => true, :const_ptr? => false }, + { :name => "const_ptr", :type => "int*", :ptr? => true, :const? => false, :const_ptr? => true }, + { :name => "both_const", :type => "const int*", :ptr? => true, :const? => true, :const_ptr? => true }, + { :name => "plain_ptr", :type => "int*", :ptr? => true, :const? => false, :const_ptr? => false }, + ], + :return => test_return[:void] + } + # Struct fields use arg[:type] directly (no reconstruction via arg_type_with_const): + # - "const int*" preserved as "const int*" (pointer to const data) + # - "int*" (from int* const) stored as "int*" — the const_ptr? is intentionally omitted + # because a const struct field can never be written to, making the mock unworkable + # - "const int*" (from const int* const) similarly stored without the trailing const + expected = " const int* Expected_ptr_to_const;\n" + + " int* Expected_const_ptr;\n" + + " const int* Expected_both_const;\n" + + " int* Expected_plain_ptr;\n" + returned = @cmock_generator_plugin_expect.instance_typedefs(function) + assert_equal(expected, returned) + end + + it "preserve const-before-pointer in return typedef struct field" do + const_int_ptr_return = { :type => "const int*", :name => "cmock_to_return", :ptr? => true, + :const? => true, :const_ptr? => false, :void? => false, + :str => "const int* cmock_to_return" } + function = { :name => "Elm", :args => [], :return => const_int_ptr_return } + # ReturnVal uses return[:type] = "const int*" + expected = " const int* ReturnVal;\n" + returned = @cmock_generator_plugin_expect.instance_typedefs(function) + assert_equal(expected, returned) + end + + it "preserve const on non-pointer custom type in mock function declaration but drop it from struct field" do + function = { + :name => "myFunc", + :args => [{ :name => "t_MyType", :type => "MyType_t", :ptr? => false, :const? => true, :const_ptr? => false }], + :args_string => "const MyType_t t_MyType", + :args_call => "t_MyType", + :return => test_return[:int] + } + # struct field uses arg[:type] directly — no const, so the field stays writable + expected_typedef = " int ReturnVal;\n" \ + " MyType_t Expected_t_MyType;\n" + assert_equal(expected_typedef, @cmock_generator_plugin_expect.instance_typedefs(function)) + + # function declaration uses args_string — const MyType_t must appear in the C signature + expected_decl = "#define myFunc_Expect(t_MyType) TEST_FAIL_MESSAGE(\"myFunc requires _ExpectAndReturn\");\n" \ + "#define myFunc_ExpectAndReturn(t_MyType, cmock_retval) myFunc_CMockExpectAndReturn(__LINE__, t_MyType, cmock_retval)\n" \ + "void myFunc_CMockExpectAndReturn(UNITY_LINE_TYPE cmock_line, const MyType_t t_MyType, int cmock_to_return);\n" + assert_equal(expected_decl, @cmock_generator_plugin_expect.mock_function_declarations(function)) + end + + it "store const-pointer return value without trailing const in typedef struct field (for writability)" do + int_ptr_const_return = { :type => "int*", :name => "cmock_to_return", :ptr? => true, + :const? => false, :const_ptr? => true, :void? => false, + :str => "int* const cmock_to_return" } + function = { :name => "Elm", :args => [], :return => int_ptr_const_return } + # ReturnVal uses return[:type] = "int*"; the trailing const is intentionally dropped + # so that the struct field remains assignable + expected = " int* ReturnVal;\n" + returned = @cmock_generator_plugin_expect.instance_typedefs(function) + assert_equal(expected, returned) + end + + it "preserve const and pointer order in mock function declaration" do + int_ptr_const_return = { :type => "int*", :name => "cmock_to_return", :ptr? => true, + :const? => false, :const_ptr? => true, :void? => false, + :str => "int* const cmock_to_return" } + function = { + :name => "Cedar", + :args => [ + { :name => "p", :type => "const int*", :ptr? => true, :const? => true, :const_ptr? => false }, + { :name => "q", :type => "int*", :ptr? => true, :const? => false, :const_ptr? => true } + ], + :args_string => "const int* p, int* const q", + :args_call => "p, q", + :return => int_ptr_const_return + } + expected = "#define Cedar_Expect(p, q) TEST_FAIL_MESSAGE(\"Cedar requires _ExpectAndReturn\");\n" + + "#define Cedar_ExpectAndReturn(p, q, cmock_retval) Cedar_CMockExpectAndReturn(__LINE__, p, q, cmock_retval)\n" + + "void Cedar_CMockExpectAndReturn(UNITY_LINE_TYPE cmock_line, const int* p, int* const q, int* const cmock_to_return);\n" + returned = @cmock_generator_plugin_expect.mock_function_declarations(function) + assert_equal(expected, returned) + end + + it "preserve const-before-pointer return type in mock function declaration for void-arg functions" do + const_int_ptr_return = { :type => "const int*", :name => "cmock_to_return", :ptr? => true, + :const? => true, :const_ptr? => false, :void? => false, + :str => "const int* cmock_to_return" } + function = { :name => "Oak", :args => [], :args_string => "void", :args_call => "", + :return => const_int_ptr_return } + expected = "#define Oak_Expect() TEST_FAIL_MESSAGE(\"Oak requires _ExpectAndReturn\");\n" + + "#define Oak_ExpectAndReturn(cmock_retval) Oak_CMockExpectAndReturn(__LINE__, cmock_retval)\n" + + "void Oak_CMockExpectAndReturn(UNITY_LINE_TYPE cmock_line, const int* cmock_to_return);\n" + returned = @cmock_generator_plugin_expect.mock_function_declarations(function) + assert_equal(expected, returned) + end + + it "preserve const-after-pointer return type in mock function declaration for void-arg functions" do + int_ptr_const_return = { :type => "int*", :name => "cmock_to_return", :ptr? => true, + :const? => false, :const_ptr? => true, :void? => false, + :str => "int* const cmock_to_return" } + function = { :name => "Oak", :args => [], :args_string => "void", :args_call => "", + :return => int_ptr_const_return } + expected = "#define Oak_Expect() TEST_FAIL_MESSAGE(\"Oak requires _ExpectAndReturn\");\n" + + "#define Oak_ExpectAndReturn(cmock_retval) Oak_CMockExpectAndReturn(__LINE__, cmock_retval)\n" + + "void Oak_CMockExpectAndReturn(UNITY_LINE_TYPE cmock_line, int* const cmock_to_return);\n" + returned = @cmock_generator_plugin_expect.mock_function_declarations(function) + assert_equal(expected, returned) + end end diff --git a/test/unit/cmock_generator_plugin_return_thru_ptr_test.rb b/test/unit/cmock_generator_plugin_return_thru_ptr_test.rb index 65a309d..d38cde2 100644 --- a/test/unit/cmock_generator_plugin_return_thru_ptr_test.rb +++ b/test/unit/cmock_generator_plugin_return_thru_ptr_test.rb @@ -57,6 +57,7 @@ describe CMockGeneratorPluginReturnThruPtr, "Verify CMockGeneratorPluginReturnTh :contains_ptr? => true } #no strict ordering + @config.expect :plugins, [] @cmock_generator_plugin_return_thru_ptr = CMockGeneratorPluginReturnThruPtr.new(@config, @utils) end @@ -192,13 +193,13 @@ describe CMockGeneratorPluginReturnThruPtr, "Verify CMockGeneratorPluginReturnTh " if (cmock_call_instance->ReturnThruPtr_tofu_Used)\n" + " {\n" + " UNITY_TEST_ASSERT_NOT_NULL(tofu, cmock_line, CMockStringPtrIsNULL);\n" + - " memcpy((void*)tofu, (const void*)cmock_call_instance->ReturnThruPtr_tofu_Val,\n" + + " CMOCK_MEMCPY((void*)tofu, (const void*)cmock_call_instance->ReturnThruPtr_tofu_Val,\n" + " cmock_call_instance->ReturnThruPtr_tofu_Size);\n" + " }\n" + " if (cmock_call_instance->ReturnThruPtr_bean_buffer_Used)\n" + " {\n" + " UNITY_TEST_ASSERT_NOT_NULL(bean_buffer, cmock_line, CMockStringPtrIsNULL);\n" + - " memcpy((void*)bean_buffer, (const void*)cmock_call_instance->ReturnThruPtr_bean_buffer_Val,\n" + + " CMOCK_MEMCPY((void*)bean_buffer, (const void*)cmock_call_instance->ReturnThruPtr_bean_buffer_Val,\n" + " cmock_call_instance->ReturnThruPtr_bean_buffer_Size);\n" + " }\n" @@ -206,4 +207,74 @@ describe CMockGeneratorPluginReturnThruPtr, "Verify CMockGeneratorPluginReturnTh assert_equal(expected, returned) end + it "converts single pointer type to pointer-to-const via ptr_to_const" do + plugin = @cmock_generator_plugin_return_thru_ptr + assert_equal("int const*", plugin.ptr_to_const("int*")) + assert_equal("char const*", plugin.ptr_to_const("char*")) + assert_equal("uint8_t const*", plugin.ptr_to_const("uint8_t*")) + assert_equal("void const*", plugin.ptr_to_const("void*")) + assert_equal("MY_TYPE const*", plugin.ptr_to_const("MY_TYPE*")) + end + + it "converts double pointer type by making inner pointer const via ptr_to_const" do + plugin = @cmock_generator_plugin_return_thru_ptr + assert_equal("char* const*", plugin.ptr_to_const("char**")) + assert_equal("int* const*", plugin.ptr_to_const("int**")) + end + + it "includes int* const args (const pointer, mutable data) in typedef but excludes const int* args" do + # int* const: const_ptr?=true, const?=false → data is mutable, pointer is const + # The condition `!(arg[:const?])` checks whether the POINTED-TO data is const. + # const? is about the data, not the pointer itself, so int* const IS included. + const_ptr_func = { + :name => "Birch", + :args => [ + { :type => "int*", :name => "mutable_ptr", :ptr? => true, :const? => false, :const_ptr? => false }, + { :type => "int*", :name => "const_ptr", :ptr? => true, :const? => false, :const_ptr? => true }, + { :type => "const int*", :name => "ptr_to_const", :ptr? => true, :const? => true, :const_ptr? => false }, + ], + :return => test_return[:void] + } + + @utils.expect :ptr_or_str?, true, ["int*"] + @utils.expect :ptr_or_str?, true, ["int*"] + @utils.expect :ptr_or_str?, true, ["const int*"] + + # mutable_ptr and const_ptr are included; ptr_to_const is excluded (const?=true) + expected = " char ReturnThruPtr_mutable_ptr_Used;\n" + + " int const* ReturnThruPtr_mutable_ptr_Val;\n" + + " size_t ReturnThruPtr_mutable_ptr_Size;\n" + + " char ReturnThruPtr_const_ptr_Used;\n" + + " int const* ReturnThruPtr_const_ptr_Val;\n" + + " size_t ReturnThruPtr_const_ptr_Size;\n" + + returned = @cmock_generator_plugin_return_thru_ptr.instance_typedefs(const_ptr_func) + assert_equal(expected, returned) + end + + it "generates correct function signature for int* const args in mock interface" do + const_ptr_func = { + :name => "Birch", + :args => [ + { :type => "int*", :name => "const_ptr", :ptr? => true, :const? => false, :const_ptr? => true }, + ], + :return => test_return[:void] + } + + @utils.expect :ptr_or_str?, true, ["int*"] + + # ptr_to_const("int*") = "int const*", so the helper function takes int const* const_ptr + expected = + "#define Birch_ReturnThruPtr_const_ptr(const_ptr)" + + " Birch_CMockReturnMemThruPtr_const_ptr(__LINE__, const_ptr, sizeof(int))\n" + + "#define Birch_ReturnArrayThruPtr_const_ptr(const_ptr, cmock_len)" + + " Birch_CMockReturnMemThruPtr_const_ptr(__LINE__, const_ptr, (cmock_len * sizeof(*const_ptr)))\n" + + "#define Birch_ReturnMemThruPtr_const_ptr(const_ptr, cmock_size)" + + " Birch_CMockReturnMemThruPtr_const_ptr(__LINE__, const_ptr, (cmock_size))\n" + + "void Birch_CMockReturnMemThruPtr_const_ptr(UNITY_LINE_TYPE cmock_line, int const* const_ptr, size_t cmock_size);\n" + + returned = @cmock_generator_plugin_return_thru_ptr.mock_function_declarations(const_ptr_func) + assert_equal(expected, returned) + end + end diff --git a/test/unit/cmock_generator_utils_test.rb b/test/unit/cmock_generator_utils_test.rb index 36b8888..cec492f 100644 --- a/test/unit/cmock_generator_utils_test.rb +++ b/test/unit/cmock_generator_utils_test.rb @@ -508,4 +508,65 @@ describe CMockGeneratorUtils, "Verify CMockGeneratorUtils Module" do " }\n" assert_equal(expected, utils.code_verify_an_arg_expectation(function, arg)) end + + it 'correctly reconstruct type strings preserving const and pointer order' do + # non-pointer: no const + assert_equal("int", CMockGeneratorUtils.arg_type_with_const({:type => "int", :ptr? => false, :const? => false, :const_ptr? => false})) + # non-pointer: const (prepended) + assert_equal("const int", CMockGeneratorUtils.arg_type_with_const({:type => "int", :ptr? => false, :const? => true, :const_ptr? => false})) + + # pointer to mutable: no const + assert_equal("int*", CMockGeneratorUtils.arg_type_with_const({:type => "int*", :ptr? => true, :const? => false, :const_ptr? => false})) + + # pointer to const (const int*): const already in :type, no trailing const + assert_equal("const int*", CMockGeneratorUtils.arg_type_with_const({:type => "const int*", :ptr? => true, :const? => true, :const_ptr? => false})) + + # const pointer (int* const): :type has no const, const_ptr? appends " const" + assert_equal("int* const", CMockGeneratorUtils.arg_type_with_const({:type => "int*", :ptr? => true, :const? => false, :const_ptr? => true})) + + # const pointer to const (const int* const) + assert_equal("const int* const", CMockGeneratorUtils.arg_type_with_const({:type => "const int*", :ptr? => true, :const? => true, :const_ptr? => true})) + + # trailing-const form: int const* (same semantics as const int* but different spelling) + assert_equal("int const*", CMockGeneratorUtils.arg_type_with_const({:type => "int const*", :ptr? => true, :const? => true, :const_ptr? => false})) + + # trailing-const pointer to const: int const* const + assert_equal("int const* const", CMockGeneratorUtils.arg_type_with_const({:type => "int const*", :ptr? => true, :const? => true, :const_ptr? => true})) + + # custom type pointer + assert_equal("MY_TYPE*", CMockGeneratorUtils.arg_type_with_const({:type => "MY_TYPE*", :ptr? => true, :const? => false, :const_ptr? => false})) + assert_equal("const MY_TYPE", CMockGeneratorUtils.arg_type_with_const({:type => "MY_TYPE", :ptr? => false, :const? => true, :const_ptr? => false})) + + # double pointer: no const_ptr, so :type used as-is + assert_equal("int**", CMockGeneratorUtils.arg_type_with_const({:type => "int**", :ptr? => true, :const? => false, :const_ptr? => false})) + assert_equal("const int**", CMockGeneratorUtils.arg_type_with_const({:type => "const int**", :ptr? => true, :const? => true, :const_ptr? => false})) + # double pointer with const_ptr: appends " const" after last * + assert_equal("int** const", CMockGeneratorUtils.arg_type_with_const({:type => "int**", :ptr? => true, :const? => false, :const_ptr? => true})) + end + + it 'produce correct C declarations preserving const and pointer order' do + # const pointer to mutable int: int* const p + arg = {:type => "int*", :name => "p", :ptr? => true, :const? => false, :const_ptr? => true} + assert_equal("int* const p", CMockGeneratorUtils.arg_declaration(arg)) + + # pointer to const int: const int* p + arg = {:type => "const int*", :name => "p", :ptr? => true, :const? => true, :const_ptr? => false} + assert_equal("const int* p", CMockGeneratorUtils.arg_declaration(arg)) + + # const pointer to const int: const int* const p + arg = {:type => "const int*", :name => "p", :ptr? => true, :const? => true, :const_ptr? => true} + assert_equal("const int* const p", CMockGeneratorUtils.arg_declaration(arg)) + + # trailing-const form: int const* const p + arg = {:type => "int const*", :name => "p", :ptr? => true, :const? => true, :const_ptr? => true} + assert_equal("int const* const p", CMockGeneratorUtils.arg_declaration(arg)) + + # plain pointer: int* p + arg = {:type => "int*", :name => "p", :ptr? => true, :const? => false, :const_ptr? => false} + assert_equal("int* p", CMockGeneratorUtils.arg_declaration(arg)) + + # non-pointer const: const MY_TYPE v + arg = {:type => "MY_TYPE", :name => "v", :ptr? => false, :const? => true, :const_ptr? => false} + assert_equal("const MY_TYPE v", CMockGeneratorUtils.arg_declaration(arg)) + end end diff --git a/test/unit/cmock_header_parser_test.rb b/test/unit/cmock_header_parser_test.rb index b20247b..56f5667 100644 --- a/test/unit/cmock_header_parser_test.rb +++ b/test/unit/cmock_header_parser_test.rb @@ -1515,6 +1515,33 @@ describe CMockHeaderParser, "Verify CMockHeaderParser Module" do assert_equal(typedefs, result[:typedefs]) end + it "extract functions using a function pointer with shorthand notation and a calling convention" do + source = "void FunkyTurkey(void __stdcall * func_ptr(int arg0))" + expected = [{ :var_arg=>nil, + :return=>{ :type => "void", + :name => 'cmock_to_return', + :ptr? => false, + :const? => false, + :const_ptr? => false, + :str => "void cmock_to_return", + :void? => true + }, + :name=>"FunkyTurkey", + :unscoped_name=>"FunkyTurkey", + :namespace=>[], + :class=>nil, + :modifier=>"", + :contains_ptr? => false, + :args=>[ {:type=>"cmock_module_func_ptr1", :name=>"func_ptr", :ptr? => false, :string? => false, :const? => false, :const_ptr? => false} + ], + :args_string=>"cmock_module_func_ptr1 func_ptr", + :args_call=>"func_ptr" }] + typedefs = ["typedef void *(__stdcall *cmock_module_func_ptr1)(int arg0);"] + result = @parser.parse("module", source) + assert_equal(expected, result[:functions]) + assert_equal(typedefs, result[:typedefs]) + end + it "extract functions containing a function pointer with a void" do source = "void FunkyTurkey(void (*func_ptr)(void))" expected = [{ :var_arg=>nil, @@ -1845,6 +1872,59 @@ describe CMockHeaderParser, "Verify CMockHeaderParser Module" do assert_equal(typedefs, result[:typedefs]) end + it "extract functions containing a parenthesized pointer argument" do + source = "void func(char *str, int (* numb))" + expected = [{ :var_arg=>nil, + :return=>{ :type => "void", + :name => 'cmock_to_return', + :ptr? => false, + :const? => false, + :const_ptr? => false, + :str => "void cmock_to_return", + :void? => true + }, + :name=>"func", + :unscoped_name=>"func", + :namespace=>[], + :class=>nil, + :modifier=>"", + :contains_ptr? => true, + :args=>[ {:type=>"char*", :name=>"str", :ptr? => false, :string? => true, :const? => false, :const_ptr? => false}, + {:type=>"int*", :name=>"numb", :ptr? => true, :string? => false, :const? => false, :const_ptr? => false} + ], + :args_string=>"char* str, int* numb", + :args_call=>"str, numb" }] + result = @parser.parse("module", source) + assert_equal(expected, result[:functions]) + assert_equal([], result[:typedefs]) + end + + it "extract functions containing a parenthesized const pointer argument" do + source = "void func(int (* const numb))" + expected = [{ :var_arg=>nil, + :return=>{ :type => "void", + :name => 'cmock_to_return', + :ptr? => false, + :const? => false, + :const_ptr? => false, + :str => "void cmock_to_return", + :void? => true + }, + :name=>"func", + :unscoped_name=>"func", + :namespace=>[], + :class=>nil, + :modifier=>"", + :contains_ptr? => true, + :args=>[ {:type=>"int*", :name=>"numb", :ptr? => true, :string? => false, :const? => false, :const_ptr? => true} + ], + :args_string=>"int* const numb", + :args_call=>"numb" }] + result = @parser.parse("module", source) + assert_equal(expected, result[:functions]) + assert_equal([], result[:typedefs]) + end + it "extract functions with varargs" do source = "int XFiles(int Scully, int Mulder, ...);\n" expected = [{ :var_arg=>"...", @@ -3003,4 +3083,169 @@ describe CMockHeaderParser, "Verify CMockHeaderParser Module" do assert_equal(true, src_len_arg[:array_size?], "src_len should be marked as array_size?") end + it "correctly parse const int* const return type (both const qualifiers)" do + sources = [ + "const int* const DoubleConst(void);\n", + "const int *const DoubleConst(void);\n", + ] + + # The trailing 'const' (const_ptr?) also lands in :modifier, matching the existing + # behavior seen for 'int* const' return types (see the test above for PorkRoast). + # The generated mock correctly uses return[:str] and function_return_type() which + # reconstruct the full "const int* const" from :type + const_ptr? without needing :modifier. + expected = [{ :var_arg => nil, + :name => "DoubleConst", + :unscoped_name => "DoubleConst", + :namespace => [], + :class => nil, + :return => { :type => "const int*", + :name => 'cmock_to_return', + :ptr? => true, + :const? => true, + :const_ptr? => true, + :str => "const int* const cmock_to_return", + :void? => false + }, + :modifier => "const", + :contains_ptr? => false, + :args => [], + :args_string => "void", + :args_call => "" + }] + + sources.each do |source| + assert_equal(expected, @parser.parse("module", source)[:functions]) + end + end + + it "correctly parse int const* const return type (trailing-const form, both qualifiers)" do + sources = [ + "int const* const DoubleConst(void);\n", + "int const *const DoubleConst(void);\n", + ] + + expected = [{ :var_arg => nil, + :name => "DoubleConst", + :unscoped_name => "DoubleConst", + :namespace => [], + :class => nil, + :return => { :type => "int const*", + :name => 'cmock_to_return', + :ptr? => true, + :const? => true, + :const_ptr? => true, + :str => "int const* const cmock_to_return", + :void? => false + }, + :modifier => "const", + :contains_ptr? => false, + :args => [], + :args_string => "void", + :args_call => "" + }] + + sources.each do |source| + assert_equal(expected, @parser.parse("module", source)[:functions]) + end + end + + it "correctly parse double-pointer argument types preserving const and pointer ordering" do + # Tests the full parse pipeline for double-pointer args (not just divine_ptr_and_const). + # const int** p → const? false (const is not before the final *), type preserves "const int**" + # int** const q → const_ptr? true, type is "int**" + # int* const* p → const? true (const* before the last *), type is "int* const*" + source = "void TriplePlay(const int** a, int** const b, int* const* c);\n" + + expected = [{ :var_arg => nil, + :name => "TriplePlay", + :unscoped_name => "TriplePlay", + :namespace => [], + :class => nil, + :return => { :type => "void", + :name => 'cmock_to_return', + :ptr? => false, + :const? => false, + :const_ptr? => false, + :str => "void cmock_to_return", + :void? => true + }, + :modifier => "", + :contains_ptr? => true, + :args => [ + { :type => "const int**", :name => "a", :ptr? => true, :string? => false, + :const? => false, :const_ptr? => false }, + { :type => "int**", :name => "b", :ptr? => true, :string? => false, + :const? => false, :const_ptr? => true }, + { :type => "int* const*", :name => "c", :ptr? => true, :string? => false, + :const? => true, :const_ptr? => false }, + ], + :args_string => "const int** a, int** const b, int* const* c", + :args_call => "a, b, c" + }] + + assert_equal(expected, @parser.parse("module", source)[:functions]) + end + + it "correctly parse double-pointer argument types with all consts" do + # const int** const p → both const forms: type "const int**", const?=false, const_ptr?=true + # int* const* const q → both const forms: type "int* const*", const?=true, const_ptr?=true + source = "void AllConst(const int** const a, int* const* const b);\n" + + expected = [{ :var_arg => nil, + :name => "AllConst", + :unscoped_name => "AllConst", + :namespace => [], + :class => nil, + :return => { :type => "void", + :name => 'cmock_to_return', + :ptr? => false, + :const? => false, + :const_ptr? => false, + :str => "void cmock_to_return", + :void? => true + }, + :modifier => "", + :contains_ptr? => true, + :args => [ + { :type => "const int**", :name => "a", :ptr? => true, :string? => false, + :const? => false, :const_ptr? => true }, + { :type => "int* const*", :name => "b", :ptr? => true, :string? => false, + :const? => true, :const_ptr? => true }, + ], + :args_string => "const int** const a, int* const* const b", + :args_call => "a, b" + }] + + assert_equal(expected, @parser.parse("module", source)[:functions]) + end + + it "preserve const on non-pointer custom type arguments (e.g. const MyType_t)" do + source = "int myFunc(const MyType_t t_MyType);\n" + + expected = [{ :var_arg => nil, + :name => "myFunc", + :unscoped_name => "myFunc", + :namespace => [], + :class => nil, + :return => { :type => "int", + :name => 'cmock_to_return', + :ptr? => false, + :const? => false, + :const_ptr? => false, + :str => "int cmock_to_return", + :void? => false + }, + :modifier => "", + :contains_ptr? => false, + :args => [ + { :type => "MyType_t", :name => "t_MyType", :ptr? => false, :string? => false, + :const? => true, :const_ptr? => false } + ], + :args_string => "const MyType_t t_MyType", + :args_call => "t_MyType" + }] + + assert_equal(expected, @parser.parse("module", source)[:functions]) + end + end