From 789c5852b597ac0f6c72806a1aefb76a21ae3a87 Mon Sep 17 00:00:00 2001 From: John Lindgren Date: Wed, 29 Aug 2018 16:15:27 -0400 Subject: [PATCH 1/2] Add treat_as_array configuration option. --- docs/CMock_Summary.md | 12 ++++++++++++ lib/cmock_config.rb | 1 + lib/cmock_header_parser.rb | 10 ++++++++++ test/unit/cmock_header_parser_test.rb | 27 +++++++++++++++++++++++++++ 4 files changed, 50 insertions(+) diff --git a/docs/CMock_Summary.md b/docs/CMock_Summary.md index 87f9c00..4e4ec55 100644 --- a/docs/CMock_Summary.md +++ b/docs/CMock_Summary.md @@ -479,6 +479,18 @@ from the defaults. We've tried to specify what the defaults are below. * 'float': 'FLOAT' * 'double': 'FLOAT' +* `:treat_as_array`: + A specialized sort of `:treat_as` to be used when you've created a + typedef of an array type, such as `typedef int TenIntegers[10];`. This + is a hash of typedef name to element type. For example: + + { "TenIntegers" => "int", + "ArrayOfFloat" => "float" } + + Telling CMock about these typedefs allows it to be more intelligent + about parameters of such types, so that you can use features like + ExpectWithArray and ReturnArrayThruPtr with them. + * `:treat_as_void`: We've seen "fun" legacy systems typedef 'void' with a custom type, like MY_VOID. Add any instances of those to this list to help CMock diff --git a/lib/cmock_config.rb b/lib/cmock_config.rb index 398c582..de12a56 100644 --- a/lib/cmock_config.rb +++ b/lib/cmock_config.rb @@ -22,6 +22,7 @@ class CMockConfig :fail_on_unexpected_calls => true, :unity_helper_path => false, :treat_as => {}, + :treat_as_array => {}, :treat_as_void => [], :memcmp_if_unknown => true, :when_no_prototypes => :warn, #the options being :ignore, :warn, or :error diff --git a/lib/cmock_header_parser.rb b/lib/cmock_header_parser.rb index ee0fe6a..eded60a 100644 --- a/lib/cmock_header_parser.rb +++ b/lib/cmock_header_parser.rb @@ -14,6 +14,7 @@ class CMockHeaderParser @c_attr_noconst = cfg.attributes.uniq - ['const'] @c_attributes = ['const'] + c_attr_noconst @c_calling_conventions = cfg.c_calling_conventions.uniq + @treat_as_array = cfg.treat_as_array @treat_as_void = (['void'] + cfg.treat_as_void).uniq @declaration_parse_matcher = /([\d\w\s\*\(\),\[\]]+??)\(([\d\w\s\*\(\),\.\[\]+-]*)\)$/m @standards = (['int','short','char','long','unsigned','signed'] + cfg.treat_as.keys).uniq @@ -184,6 +185,15 @@ class CMockHeaderParser arg_info = parse_type_and_name(arg) arg_info.delete(:modifier) # don't care about this arg_info.delete(:c_calling_convention) # don't care about this + + # in C, array arguments implicitly degrade to pointers + # make the translation explicit here to simplify later logic + if @treat_as_array[arg_info[:type]] and not arg_info[:ptr?] then + arg_info[:type] = "#{@treat_as_array[arg_info[:type]]}*" + arg_info[:type] = "const #{arg_info[:type]}" if arg_info[:const?] + arg_info[:ptr?] = true + end + args << arg_info end return args diff --git a/test/unit/cmock_header_parser_test.rb b/test/unit/cmock_header_parser_test.rb index add920f..805780d 100644 --- a/test/unit/cmock_header_parser_test.rb +++ b/test/unit/cmock_header_parser_test.rb @@ -19,6 +19,7 @@ describe CMockHeaderParser, "Verify CMockHeaderParser Module" do @config.expect :c_calling_conventions, ['__stdcall'] @config.expect :treat_as_void, ['MY_FUNKY_VOID'] @config.expect :treat_as, { "BANJOS" => "INT", "TUBAS" => "HEX16"} + @config.expect :treat_as_array, {"IntArray" => "int", "Book" => "Page"} @config.expect :when_no_prototypes, :error @config.expect :verbosity, 1 @config.expect :treat_externs, :exclude @@ -949,6 +950,32 @@ describe CMockHeaderParser, "Verify CMockHeaderParser Module" do assert_equal(expected, @parser.parse("module", source)[:functions]) end + it "converts typedef'd array arguments to pointers" do + + source = "Book AddToBook(Book book, const IntArray values);\n" + + expected = [{ :name => "AddToBook", + :modifier=>"", + :return => { :type => "Book", + :name => "cmock_to_return", + :str => "Book cmock_to_return", + :void? => false, + :ptr? => false, + :const? => false, + :const_ptr? => false + }, + :var_arg => nil, + :args => [{ :type => "Page*", :name => "book", :ptr? => true, :const? => false, :const_ptr? => false }, + { :type => "const int*", :name => "values", :ptr? => true, :const? => true, :const_ptr? => false }], + :args_string => "Book book, const IntArray values", + :args_call => "book, values", + :contains_ptr? => true + }] + + assert_equal(expected, @parser.parse("module", source)[:functions]) + + end + it "properly detect typedef'd variants of void and use those" do source = "typedef (void) FUNKY_VOID_T;\n" + From cfa46d6440d43105f1b1d35dabc9994529b565b4 Mon Sep 17 00:00:00 2001 From: John Lindgren Date: Tue, 4 Dec 2018 18:17:17 -0500 Subject: [PATCH 2/2] Prevent undefined behavior due to typedef array usage. --- lib/cmock_generator_utils.rb | 5 ++++- test/unit/cmock_generator_utils_test.rb | 12 ++++++++---- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/lib/cmock_generator_utils.rb b/lib/cmock_generator_utils.rb index eb12e23..cdaaf03 100644 --- a/lib/cmock_generator_utils.rb +++ b/lib/cmock_generator_utils.rb @@ -73,7 +73,10 @@ class CMockGeneratorUtils if (arg[:ptr?] or @treat_as.include?(arg[:type])) " #{dest} = #{arg[:name]};\n" else - " memcpy(&#{dest}, &#{arg[:name]}, sizeof(#{arg[:type]}));\n" + assert_expr = "sizeof(#{arg[:name]}) == sizeof(#{arg[:type]}) ? 1 : -1" + comment = "/* add #{arg[:type]} to :treat_as_array if this causes an error */" + " memcpy(&#{dest}, &#{arg[:name]},\n" + + " sizeof(#{arg[:type]}[#{assert_expr}])); #{comment}\n" end end diff --git a/test/unit/cmock_generator_utils_test.rb b/test/unit/cmock_generator_utils_test.rb index bd59749..80ec47f 100644 --- a/test/unit/cmock_generator_utils_test.rb +++ b/test/unit/cmock_generator_utils_test.rb @@ -106,7 +106,8 @@ describe CMockGeneratorUtils, "Verify CMockGeneratorUtils Module" do expected3 = " cmock_call_instance->Expected_Kiwi = Kiwi;\n" arg4 = { :name => "Lime", :const? => false, :type => 'LIME_T', :ptr? => false } - expected4 = " memcpy(&cmock_call_instance->Expected_Lime, &Lime, sizeof(LIME_T));\n" + expected4 = " memcpy(&cmock_call_instance->Expected_Lime, &Lime,\n" + + " sizeof(LIME_T[sizeof(Lime) == sizeof(LIME_T) ? 1 : -1])); /* add LIME_T to :treat_as_array if this causes an error */\n" assert_equal(expected1, @cmock_generator_utils_simple.code_add_an_arg_expectation(arg1)) assert_equal(expected2, @cmock_generator_utils_simple.code_add_an_arg_expectation(arg2)) @@ -131,7 +132,8 @@ describe CMockGeneratorUtils, "Verify CMockGeneratorUtils Module" do " cmock_call_instance->ReturnThruPtr_Kiwi_Used = 0;\n" arg4 = { :name => "Lime", :const? => false, :type => 'LIME_T', :ptr? => false } - expected4 = " memcpy(&cmock_call_instance->Expected_Lime, &Lime, sizeof(LIME_T));\n" + + expected4 = " memcpy(&cmock_call_instance->Expected_Lime, &Lime,\n" + + " sizeof(LIME_T[sizeof(Lime) == sizeof(LIME_T) ? 1 : -1])); /* add LIME_T to :treat_as_array if this causes an error */\n" + " cmock_call_instance->IgnoreArg_Lime = 0;\n" assert_equal(expected1, @cmock_generator_utils_complex.code_add_an_arg_expectation(arg1)) @@ -153,7 +155,8 @@ describe CMockGeneratorUtils, "Verify CMockGeneratorUtils Module" do } expected = "void CMockExpectParameters_Melon(CMOCK_Melon_CALL_INSTANCE* cmock_call_instance, stuff)\n{\n" + " cmock_call_instance->Expected_MyIntPtr = MyIntPtr;\n" + - " memcpy(&cmock_call_instance->Expected_MyMyType, &MyMyType, sizeof(MY_TYPE));\n" + + " memcpy(&cmock_call_instance->Expected_MyMyType, &MyMyType,\n" + + " sizeof(MY_TYPE[sizeof(MyMyType) == sizeof(MY_TYPE) ? 1 : -1])); /* add MY_TYPE to :treat_as_array if this causes an error */\n" + " cmock_call_instance->Expected_MyStr = MyStr;\n" + "}\n\n" assert_equal(expected, @cmock_generator_utils_simple.code_add_argument_loader(function)) @@ -169,7 +172,8 @@ describe CMockGeneratorUtils, "Verify CMockGeneratorUtils Module" do " cmock_call_instance->Expected_MyIntPtr_Depth = MyIntPtr_Depth;\n" + " cmock_call_instance->IgnoreArg_MyIntPtr = 0;\n" + " cmock_call_instance->ReturnThruPtr_MyIntPtr_Used = 0;\n" + - " memcpy(&cmock_call_instance->Expected_MyMyType, &MyMyType, sizeof(MY_TYPE));\n" + + " memcpy(&cmock_call_instance->Expected_MyMyType, &MyMyType,\n" + + " sizeof(MY_TYPE[sizeof(MyMyType) == sizeof(MY_TYPE) ? 1 : -1])); /* add MY_TYPE to :treat_as_array if this causes an error */\n" + " cmock_call_instance->IgnoreArg_MyMyType = 0;\n" + " cmock_call_instance->Expected_MyStr = MyStr;\n" + " cmock_call_instance->IgnoreArg_MyStr = 0;\n" +