From 693c65878028ab9e1ed66d354885e717a33cddec Mon Sep 17 00:00:00 2001 From: John Lindgren Date: Thu, 31 Aug 2017 16:25:11 -0400 Subject: [PATCH 1/3] Fix const determination for pointer-to-pointer types. For example, consider: void function(const int **ret_ptr). This function stores a (const int *) at the address passed in. We'd want to test it with function_ReturnThruPtr_ret_ptr, but CMock was incorrectly considering the (const int **) a constant argument, and thus did not generate the ReturnThruPtr function. Add a test to ensure that all permutations of constants and pointers work as expected. --- lib/cmock_header_parser.rb | 28 +++++++--------------- test/unit/cmock_header_parser_test.rb | 34 +++++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 19 deletions(-) diff --git a/lib/cmock_header_parser.rb b/lib/cmock_header_parser.rb index 53377b9..a44cc20 100644 --- a/lib/cmock_header_parser.rb +++ b/lib/cmock_header_parser.rb @@ -160,30 +160,20 @@ class CMockHeaderParser end def divine_const(arg) - return false if !(/(?:^|\s|\*)const(?:\*|\s|$)/ =~ arg) # check for const as part of a larger word - return true if (/const(?:\w|\s)*\*/ =~ arg) # check const comes before * indicating const data - return false if (/\*\s*const/ =~ arg) # check const comes after * indicating const ptr - return true + # a non-pointer arg containing "const" is a constant + # an arg containing "const" before the last * is a pointer to a constant + return ( arg.include?('*') ? (/(^|\s|\*)const(\s(\w|\s)*)?\*(?!.*\*)/ =~ arg) + : (/(^|\s)const(\s|$)/ =~ arg) ) ? true : false end def divine_ptr_and_const(arg) - divination = { :ptr? => false, :const? => false, :const_ptr? => false } + divination = {} - #first check if there is a pointer present and that it's not part of a C string or function definition - #divination[:ptr?] = (arg.split[0..-2].join.include?('*') && !arg.gsub(/(const|char|\*|\s)+/,'').empty?) - divination[:ptr?] = (arg.include?('*') && !arg.gsub(/(const|char|\*|\s)+/,'').empty?) + divination[:ptr?] = divine_ptr(arg) + divination[:const?] = divine_const(arg) - #if there isn't a const that isn't part of a larger word, we're done - return divination if !(/(?:^|\s|\*)const(?:\*|\s|$)/ =~ arg) - divination[:const?] = true - - # check const comes after * indicating const ptr - if (/\*\s*const/ =~ arg) - divination[:const_ptr?] = true - - #check const comes before * indicating also const data - divination[:const?] = (/const(?:\w|\s)*\*/ =~ arg) ? true : false - end + # an arg containing "const" after the last * is a constant pointer + divination[:const_ptr?] = (/\*(?!.*\*)\s*const(\s|$)/ =~ arg) ? true : false return divination end diff --git a/test/unit/cmock_header_parser_test.rb b/test/unit/cmock_header_parser_test.rb index a3d4a45..591cb57 100644 --- a/test/unit/cmock_header_parser_test.rb +++ b/test/unit/cmock_header_parser_test.rb @@ -1510,4 +1510,38 @@ describe CMockHeaderParser, "Verify CMockHeaderParser Module" do assert_equal(expected, @parser.parse("module", source)[:functions]) end + it "divines all permutations of ptr, const, and const_ptr correctly" do + truth_table = [ + # argument ptr const const_ptr + [ "constNOTconst constNOTconst", false, false, false ], + [ "const constNOTconst constNOTconst", false, true, false ], + [ "constNOTconst const constNOTconst", false, true, false ], + [ "constNOTconst *constNOTconst", true, false, false ], + [ "const constNOTconst *constNOTconst", true, true, false ], + [ "constNOTconst const *constNOTconst", true, true, false ], + [ "constNOTconst *const constNOTconst", true, false, true ], + [ "const constNOTconst *const constNOTconst", true, true, true ], + [ "constNOTconst const *const constNOTconst", true, true, true ], + [ "constNOTconst **constNOTconst", true, false, false ], + [ "const constNOTconst **constNOTconst", true, false, false ], + [ "constNOTconst const **constNOTconst", true, false, false ], + [ "constNOTconst *const *constNOTconst", true, true, false ], + [ "const constNOTconst *const *constNOTconst", true, true, false ], + [ "constNOTconst const *const *constNOTconst", true, true, false ], + [ "constNOTconst **const constNOTconst", true, false, true ], + [ "const constNOTconst **const constNOTconst", true, false, true ], + [ "constNOTconst const **const constNOTconst", true, false, true ], + [ "constNOTconst *const *const constNOTconst", true, true, true ], + [ "const constNOTconst *const *const constNOTconst", true, true, true ], + [ "constNOTconst const *const *const constNOTconst", true, true, true ] + ] + + truth_table.each do |entry| + assert_equal(@parser.divine_ptr(entry[0]), entry[1]) + assert_equal(@parser.divine_const(entry[0]), entry[2]) + assert_equal(@parser.divine_ptr_and_const(entry[0]), + { ptr?: entry[1], const?: entry[2], const_ptr?: entry[3] }) + end + end + end From 42dab4836d6bce770e2ec5c65538ea32f7d7e785 Mon Sep 17 00:00:00 2001 From: John Lindgren Date: Thu, 31 Aug 2017 17:44:08 -0400 Subject: [PATCH 2/3] Fix handling of string arguments. Since 2f0a44d7ce1b, string arguments were being tested with UNITY_TEST_ASSERT_EQUAL_PTR, not UNITY_TEST_ASSERT_EQUAL_STRING. Before that change, we would call divine_ptr(arg_type) where arg_type had the argument name stripped, i.e. "const char *". Now we are passing it the name as well, i.e. "const char *str" and it was not prepared to handle that. --- lib/cmock_header_parser.rb | 9 ++++--- test/unit/cmock_header_parser_test.rb | 37 ++++++++++++++++++++++++--- 2 files changed, 39 insertions(+), 7 deletions(-) diff --git a/lib/cmock_header_parser.rb b/lib/cmock_header_parser.rb index a44cc20..d0e5a65 100644 --- a/lib/cmock_header_parser.rb +++ b/lib/cmock_header_parser.rb @@ -146,16 +146,17 @@ class CMockHeaderParser return args if (arg =~ /^\s*((\.\.\.)|(void))\s*$/) # we're done if we reach void by itself or ... arg_array = arg.split arg_elements = arg_array - @c_attributes # split up words and remove known attributes - args << { :type => (arg_type = arg_elements[0..-2].join(' ')), + args << { :type => arg_elements[0..-2].join(' '), :name => arg_elements[-1] }.merge(divine_ptr_and_const(arg)) end return args end - def divine_ptr(arg_type) - return false unless arg_type.include? '*' - return false if arg_type.gsub(/(const|char|\*|\s)+/,'').empty? + def divine_ptr(arg) + return false unless arg.include? '*' + # treat "const char *" and similar as a string, not a pointer + return false if /(^|\s)(const\s+)?char(\s+const)?\s*\*(?!.*\*)/ =~ arg return true end diff --git a/test/unit/cmock_header_parser_test.rb b/test/unit/cmock_header_parser_test.rb index 591cb57..1d13caa 100644 --- a/test/unit/cmock_header_parser_test.rb +++ b/test/unit/cmock_header_parser_test.rb @@ -1052,7 +1052,7 @@ describe CMockHeaderParser, "Verify CMockHeaderParser Module" do assert_equal(expected, result[:functions]) end - it "handle arrays and treat them as pointers" do + it "handle arrays and treat them as pointers or strings" do source = "void KeyOperated(CUSTOM_TYPE thing1[], int thing2 [ ], char thing3 [][2 ][ 3], int* thing4[4])" expected = [{:var_arg=>nil, :return=>{ :type => "void", @@ -1068,7 +1068,7 @@ describe CMockHeaderParser, "Verify CMockHeaderParser Module" do :contains_ptr? => true, :args=>[ {:type=>"CUSTOM_TYPE*", :name=>"thing1", :ptr? => true, :const? => false, :const_ptr? => false}, {:type=>"int*", :name=>"thing2", :ptr? => true, :const? => false, :const_ptr? => false}, - {:type=>"char*", :name=>"thing3", :ptr? => true, :const? => false, :const_ptr? => false}, #THIS one will likely change in the future when we improve multidimensional array support + {:type=>"char*", :name=>"thing3", :ptr? => false, :const? => false, :const_ptr? => false}, #THIS one will likely change in the future when we improve multidimensional array support {:type=>"int**", :name=>"thing4", :ptr? => true, :const? => false, :const_ptr? => false} #THIS one will likely change in the future when we improve multidimensional array support ], :args_string=>"CUSTOM_TYPE* thing1, int* thing2, char* thing3, int** thing4", @@ -1407,7 +1407,7 @@ describe CMockHeaderParser, "Verify CMockHeaderParser Module" do :contains_ptr? => true, :args=>[ {:type=>"sqlite3_stmt*", :name=>"cmock_arg2", :ptr? => true, :const? => false, :const_ptr? => false}, {:type=>"int", :name=>"cmock_arg3", :ptr? => false, :const? => false, :const_ptr? => false}, - {:type=>"char*", :name=>"cmock_arg4", :ptr? => true, :const? => true, :const_ptr? => false}, + {:type=>"char*", :name=>"cmock_arg4", :ptr? => false, :const? => true, :const_ptr? => false}, {:type=>"int", :name=>"n", :ptr? => false, :const? => false, :const_ptr? => false}, {:type=>"cmock_module_func_ptr1", :name=>"cmock_arg1", :ptr? => false, :const? => false, :const_ptr? => false} ], @@ -1544,4 +1544,35 @@ describe CMockHeaderParser, "Verify CMockHeaderParser Module" do end end + it "divines ptr correctly for string types" do + truth_table = [ + # argument ptr + [ "char s", false ], + [ "const char s", false ], + [ "char const s", false ], + [ "char *s", false ], + [ "const char *s", false ], + [ "char const *s", false ], + [ "char *const s", false ], + [ "const char *const s", false ], + [ "char const *const s", false ], + [ "char **s", true ], + [ "const char **s", true ], + [ "char const **s", true ], + [ "char *const *s", true ], + [ "const char *const *s", true ], + [ "char const *const *s", true ], + [ "char **const s", true ], + [ "const char **const s", true ], + [ "char const **const s", true ], + [ "char *const *const s", true ], + [ "const char *const *const s", true ], + [ "char const *const *const s", true ] + ] + + truth_table.each do |entry| + assert_equal(@parser.divine_ptr(entry[0]), entry[1]) + end + end + end From 55462aef40899193c1a36736342dea8027e04141 Mon Sep 17 00:00:00 2001 From: John Lindgren Date: Fri, 1 Sep 2017 12:30:11 -0400 Subject: [PATCH 3/3] Fix parsing of declarations like "int const* doStuff(void)". The "const" in this case was not correctly stripped from the return type, leading to a double const in generated typedefs, i.e.: typedef const int const* (* doStuff_CALLBACK)(int cmock_num_calls); Add a test for these cases. --- lib/cmock_header_parser.rb | 6 ++--- test/unit/cmock_header_parser_test.rb | 33 +++++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 3 deletions(-) diff --git a/lib/cmock_header_parser.rb b/lib/cmock_header_parser.rb index d0e5a65..4ade8cc 100644 --- a/lib/cmock_header_parser.rb +++ b/lib/cmock_header_parser.rb @@ -228,8 +228,8 @@ class CMockHeaderParser #process function attributes, return type, and name descriptors = regex_match[1] - descriptors.gsub!(/\s+\*/,'*') #remove space to place asterisks with return type (where they belong) - descriptors.gsub!(/\*(\w)/,'* \1') #pull asterisks away from function name to place asterisks with return type (where they belong) + descriptors.gsub!(/(\w)\*/,'\1 *') #pull asterisks away from preceding word + descriptors.gsub!(/\*(\w)/,'* \1') #pull asterisks away from following word descriptors = descriptors.split #array of all descriptor strings #grab name @@ -249,7 +249,7 @@ class CMockHeaderParser end end decl[:modifier] = decl[:modifier].join(' ') - rettype = rettype.join(' ') + rettype = rettype.join(' ').gsub(/\s+\*/,'*') #remove space before asterisks rettype = 'void' if (@local_as_void.include?(rettype.strip)) decl[:return] = { :type => rettype, :name => 'cmock_to_return', diff --git a/test/unit/cmock_header_parser_test.rb b/test/unit/cmock_header_parser_test.rb index 1d13caa..7375c50 100644 --- a/test/unit/cmock_header_parser_test.rb +++ b/test/unit/cmock_header_parser_test.rb @@ -805,6 +805,39 @@ describe CMockHeaderParser, "Verify CMockHeaderParser Module" do assert_equal(expected, @parser.parse("module", source)[:functions]) end + it "should properly handle const before or after return type" do + + sources = [ + "const int * PorkRoast(void);\n", + "int const * PorkRoast(void);\n", + "const int* PorkRoast(void);\n", + "int const* PorkRoast(void);\n", + "const int *PorkRoast(void);\n", + "int const *PorkRoast(void);\n" + ] + + expected = [{ :var_arg=>nil, + :name=>"PorkRoast", + :return=> { :type => "int*", + :name => 'cmock_to_return', + :ptr? => true, + :const? => true, + :const_ptr? => false, + :str => "int* 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 "should properly handle const applied after asterisk in return type (not legal C, but sometimes used)" do source = "int * const PorkRoast(void);\n"