From 2def6c4f21b7df3e42179a86068d2896b60d80eb Mon Sep 17 00:00:00 2001 From: laurens Date: Tue, 12 Nov 2019 12:10:27 +0100 Subject: [PATCH 1/7] Fix compile warnings on inline.h test header --- test/system/test_compilation/inline.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/system/test_compilation/inline.h b/test/system/test_compilation/inline.h index ae026e0..06e33c1 100644 --- a/test/system/test_compilation/inline.h +++ b/test/system/test_compilation/inline.h @@ -1,19 +1,19 @@ -static inline void dummy_func_0(void) { +static inline int dummy_func_0(void) { return 5; } -inline static void dummy_func_1(int a) { +inline static int dummy_func_1(int a) { int a = dummy_func_0(); int b = 10; return a + b; } -void inline static dummy_func_2(int a, char b, float c) { +int inline static dummy_func_2(int a, char b, float c) { c += 3.14; b -= 32; - return a + (int)(b) + c; + return a + (int)(b) + (int)c; } void dummy_normal_func(int a); From 0af0e20d15d7dd2c35724d433354ae70513af060 Mon Sep 17 00:00:00 2001 From: laurens Date: Tue, 12 Nov 2019 15:12:49 +0100 Subject: [PATCH 2/7] Refactor transform_inline_functions - Just looking for static|inline in the gsub is a bit too aggressive. Instead, look for the "static inline" and parse it: - Everything before the match should just be copied, we don't want to touch anything but the inline functions. - Remove the implementation of the inline function (this is enclosed in square brackets) and replace it with ";" to complete the transformation to normal/non-inline function. - Copy everything after the inline function implementation Repeat the above step until we can't find "static inline" anymore - To remove the inline implementation, we count the number of square-bracket 'levels' in the inline function and remove every pair. This ensures that constructs like: inline void func(void) { if (...) { //NOP } else { //NOP } } will not end up leaving the 'else' keyword unremoved: inline void func(void); else If we count the number of levels, we don't end up in this scenario since we remove 3 'pairs', the if-one, the else-one and finally the main body. --- lib/cmock_header_parser.rb | 67 +++++++++++++++++++++------ test/unit/cmock_header_parser_test.rb | 67 ++++++++++++++++++++++++--- 2 files changed, 112 insertions(+), 22 deletions(-) diff --git a/lib/cmock_header_parser.rb b/lib/cmock_header_parser.rb index cc6bcfa..6939c53 100644 --- a/lib/cmock_header_parser.rb +++ b/lib/cmock_header_parser.rb @@ -74,27 +74,64 @@ class CMockHeaderParser end def transform_inline_functions(source) + # Format to look for inline functions. + # This is a combination of "static" and "inline" keywords ("static inline", "inline static", "inline", "static") + # There are several possibilities: + # - sometimes they appear together, sometimes individually, + # - The keywords can appear before or after the return type (this is a compiler warning but people do weird stuff), + # so we check for word boundaries when searching for them + # - We first remove "static inline" combinations and boil down to + inline_function_regex_formats = [ + /(static\s+inline|inline\s+static)\s*/, # Last part (\s*) is just to remove whitespaces (only to prettify the output) + /(\bstatic\b|\binline\b)\s*/, # Last part (\s*) is just to remove whitespaces (only to prettify the output) + ] + # let's clean up the encoding in case they've done anything weird with the characters we might find source = source.force_encoding("ISO-8859-1").encode("utf-8", :replace => nil) - source.gsub!(/(static|inline)+.*\{.*\w*\}/m) do |m| - m.gsub!(/(static|inline)/, '') # remove static and inline keywords - m = remove_nested_pairs_of_braces(m) + # - Just looking for static|inline in the gsub is a bit too aggressive (functions that are named like this, ...), so we try to be a bit smarter + # Instead, look for "static inline" and parse it: + # - Everything before the match should just be copied, we don't want + # to touch anything but the inline functions. + # - Remove the implementation of the inline function (this is enclosed + # in square brackets) and replace it with ";" to complete the + # transformation to normal/non-inline function. + # To ensure proper removal of the function body, we count the number of square-bracket pairs + # and remove the pairs one-by-one. + # - Copy everything after the inline function implementation and start the parsing of the next inline function - # Functions having "{ }" at this point are/were inline functions, - # Disguise them as normal functions with the ";" - m.gsub!(/\s*\{\s\}/, ";") + inline_function_regex_formats.each do |format| + loop do + inline_function_match = source.match(/#{format}/) # Search for inline function declaration + break if nil == inline_function_match - # Cleanup the function declarations - # Not strictly necessary, it will compile just fine, but it can help during debugging - m_lines = m.split(/\s*;\s*/).uniq - m_lines.each do |m_line| - m_line.gsub!(/^\s+/, '') # remove extra white space from beginning of line - m_line.gsub!(/\s+/, ' ') # remove remaining extra white space - m_line.gsub!(/\n/, '') # remove newlines + # Get number of square brackets in function, we already now there is atleast 1 pair (main function body) + curr = 0 + started = false + total = 0 + + inline_function_match.post_match.each_char do |c| + if ("{" == c) + curr += 1 + total +=1 + started = true + elsif ("}" == c) + curr -=1 + end + + break if started && curr == 0 # We reached the end of the inline function body + end + + inline_function_stripped = inline_function_match.post_match + until total == 1 + inline_function_stripped.sub!(/\{[^\{\}]*\}/, "") # Remove code in between square brackets + total -= 1 + end + + inline_function_stripped.sub!(/(\s*\{[^\{\}]*|[^\{\}]*\})+/, ";") # Remove inline implementation + + source = inline_function_match.pre_match + inline_function_stripped # Make new source with the inline function removed and move on to the next end - - m_lines.join(";\n") + ";" # Join the lines and add the last semicolon manually end return source diff --git a/test/unit/cmock_header_parser_test.rb b/test/unit/cmock_header_parser_test.rb index b2cd653..9ef83a2 100644 --- a/test/unit/cmock_header_parser_test.rb +++ b/test/unit/cmock_header_parser_test.rb @@ -1842,18 +1842,59 @@ describe CMockHeaderParser, "Verify CMockHeaderParser Module" do "};\n" + "int my_function(int a);\n" + "int my_better_function(struct my_struct *s);\n" + - "static inline int get_member_a(struct my_struct *s)\n" + + "static inline int staticinlinebar(struct my_struct *s)\n" + # This is a function with a lot of indentation levels, we should be able to handle it + "{\n" + + "\t{\n" + + "\t\t{\n" + + "\t\t\treturn s->a;\n" + + "\t\t}\n" + + "\t}\n" + + "}\n" + + "static inline int staticinlinefunc(struct my_struct *s)\n" + "{\n" + " return s->a;\n" + "}\n" + - "inline static int my_func_0(int a)\n" + - "{\n" + + "int bar(struct my_struct *s);\n" + # A normal function to screw with our parser + "inline static int inlinestaticfunc(int a) {\n" + " return a + 42;\n" + "}\n" + - "inline int my_func_1(struct my_struct *s)\n" + + "inline int StaticInlineFunc(struct my_struct *s)\n" + "{\n" + " return get_member_a(s) + 42;\n" + "}\n" + + "int inline StaticInlineBar(struct my_struct *s)\n" + + "{\n" + + " return get_member_a(s) + 42;\n" + + "}\n" + + "struct staticinlinestruct {\n" + # Add a structure declaration between the inline functions, just to make sure we don't touch it! + "int a;\n" + + "};\n" + + "\n" + + "struct staticinlinestruct fubarstruct(struct my_struct *s);\n" + # Another normal function to screw with our parser + "static inline struct staticinlinestruct inlinefubarfunction(struct my_struct *s)\n" + + "{\n" + + " return (struct staticinlinestruct)*s;\n" + + "}\n" + + "int fubar(struct my_struct *s);\n" + # Another normal function to screw with our parser + "inline int stuff(int num)" + + "{" + + " int reg = 0x12;" + + " if (num > 0)" + + " {" + + " reg |= (0x0Eu);" + + " }" + + " else" + + " {" + + " reg |= (0x07u);" + + " }" + + " return reg;" + + "}" + + "\n" + + "int inline static dummy_func_2(int a, char b, float c) {" + # This is a sneaky one, inline static is placed AFTER the return value + " c += 3.14;" + + " b -= 32;" + + " return a + (int)(b) + (int)c;" + + "}" + "#endif _NOINCLUDES\n" expected = @@ -1883,9 +1924,21 @@ describe CMockHeaderParser, "Verify CMockHeaderParser Module" do "};\n" + "int my_function(int a);\n" + "int my_better_function(struct my_struct *s);\n" + - "int get_member_a(struct my_struct *s);\n" + - "int my_func_0(int a);\n" + - "int my_func_1(struct my_struct *s);\n" + + "int staticinlinebar(struct my_struct *s);\n" + + "int staticinlinefunc(struct my_struct *s);\n" + + "int bar(struct my_struct *s);\n" + + "int inlinestaticfunc(int a);\n" + + "int StaticInlineFunc(struct my_struct *s);\n" + + "int StaticInlineBar(struct my_struct *s);\n" + + "struct staticinlinestruct {\n" + + "int a;\n" + + "};\n" + + "\n" + + "struct staticinlinestruct fubarstruct(struct my_struct *s);\n" + + "struct staticinlinestruct inlinefubarfunction(struct my_struct *s);\n" + + "int fubar(struct my_struct *s);\n" + + "int stuff(int num);\n" + + "int dummy_func_2(int a, char b, float c);" + "#endif _NOINCLUDES\n" assert_equal(expected, @parser.transform_inline_functions(source)) From 8ba3ed99a1c4f856183b56b080078e1f00d2892a Mon Sep 17 00:00:00 2001 From: laurens Date: Tue, 12 Nov 2019 19:58:25 +0100 Subject: [PATCH 3/7] Extract count_number_of_pairs_of_braces_in_function method --- lib/cmock_header_parser.rb | 56 +++++++++++++++++---------- test/unit/cmock_header_parser_test.rb | 49 +++++++++++++++++++++++ 2 files changed, 84 insertions(+), 21 deletions(-) diff --git a/lib/cmock_header_parser.rb b/lib/cmock_header_parser.rb index 6939c53..83d045e 100644 --- a/lib/cmock_header_parser.rb +++ b/lib/cmock_header_parser.rb @@ -73,6 +73,34 @@ class CMockHeaderParser return source end + # Return the number of pairs of braces/square brackets in the function provided by the user + # +source+:: String containing the function to be processed + def count_number_of_pairs_of_braces_in_function(source) + is_function_start_found = false + curr_level = 0 + total_levels = 0 + + source.each_char do |c| + if ("{" == c) + curr_level += 1 + total_levels +=1 + is_function_start_found = true + elsif ("}" == c) + curr_level -=1 + end + + break if is_function_start_found && curr_level == 0 # We reached the end of the inline function body + end + + if 0 != curr_level + total_levels = 0 # Something is fishy about this source, not enough closing braces? + end + + return total_levels + end + + # Transform inline functions to regular functions in the source by the user + # +source+:: String containing the source to be processed def transform_inline_functions(source) # Format to look for inline functions. # This is a combination of "static" and "inline" keywords ("static inline", "inline static", "inline", "static") @@ -85,6 +113,7 @@ class CMockHeaderParser /(static\s+inline|inline\s+static)\s*/, # Last part (\s*) is just to remove whitespaces (only to prettify the output) /(\bstatic\b|\binline\b)\s*/, # Last part (\s*) is just to remove whitespaces (only to prettify the output) ] + square_bracket_pair_regex_format = /\{[^\{\}]*\}/ # Regex to match one whole block enclosed by two square brackets # let's clean up the encoding in case they've done anything weird with the characters we might find source = source.force_encoding("ISO-8859-1").encode("utf-8", :replace => nil) @@ -103,33 +132,18 @@ class CMockHeaderParser inline_function_regex_formats.each do |format| loop do inline_function_match = source.match(/#{format}/) # Search for inline function declaration - break if nil == inline_function_match + break if nil == inline_function_match # No inline functions so nothing to do - # Get number of square brackets in function, we already now there is atleast 1 pair (main function body) - curr = 0 - started = false - total = 0 + total_pairs_to_remove = count_number_of_pairs_of_braces_in_function(inline_function_match.post_match) - inline_function_match.post_match.each_char do |c| - if ("{" == c) - curr += 1 - total +=1 - started = true - elsif ("}" == c) - curr -=1 - end - - break if started && curr == 0 # We reached the end of the inline function body - end + break if 0 == total_pairs_to_remove # Bad source? inline_function_stripped = inline_function_match.post_match - until total == 1 - inline_function_stripped.sub!(/\{[^\{\}]*\}/, "") # Remove code in between square brackets - total -= 1 + until total_pairs_to_remove == 0 + inline_function_stripped.sub!(/\s*#{square_bracket_pair_regex_format}/, ";") # Remove inline implementation + total_pairs_to_remove -= 1 end - inline_function_stripped.sub!(/(\s*\{[^\{\}]*|[^\{\}]*\})+/, ";") # Remove inline implementation - source = inline_function_match.pre_match + inline_function_stripped # Make new source with the inline function removed and move on to the next end end diff --git a/test/unit/cmock_header_parser_test.rb b/test/unit/cmock_header_parser_test.rb index 9ef83a2..a58b087 100644 --- a/test/unit/cmock_header_parser_test.rb +++ b/test/unit/cmock_header_parser_test.rb @@ -1944,4 +1944,53 @@ describe CMockHeaderParser, "Verify CMockHeaderParser Module" do assert_equal(expected, @parser.transform_inline_functions(source)) end + it "Count number of pairs of braces in function succesfully" do + source = + "int foo(struct my_struct *s)\n" + + "{\n" + + " return get_member_a(s) + 42;\n" + + "}\n" + complex_source = + "int bar(struct my_struct *s)\n" + + "{\n" + + "\tint a = 6;\n" + + "\tint res = foo(&(struct my_struct){.nr = a});\n" + + "\t{\n" + + "\t\tint a = 5;\n" + + "\t\tint res = foo(&(struct my_struct){.nr = a});\n" + + "\t\t{\n" + + "\t\t\tstruct my_struct a = {.nr = 1};\n" + + "\t\t}\n" + + "\t}\n" + + "\treturn 42;\n" + + "}\n" + + assert_equal(1, @parser.count_number_of_pairs_of_braces_in_function(source)) + assert_equal(6, @parser.count_number_of_pairs_of_braces_in_function(complex_source)) + end + + it "Count number of pairs of braces returns 0 if bad source is supplied" do + bad_source_0 = + "int foo(struct my_struct *s)\n" + + "{\n" + + " return get_member_a(s) + 42;\n" + + "\n" # Missing closing brace + bad_source_1 = + "int bar(struct my_struct *s)\n" + + "{\n" + + "\tint a = 6;\n" + + "\tint res = foo(&(struct my_struct){.nr = a});\n" + + "\t{\n" + + "\t\tint a = 5;\n" + + "\t\tint res = foo(&(struct my_struct){.nr = a});\n" + + "\t\t{\n" + + "\t\t\n" + # Missing closing brace + "\t}\n" + + "\treturn 42;\n" + + "}\n" + + assert_equal(0, @parser.count_number_of_pairs_of_braces_in_function(bad_source_0)) + assert_equal(0, @parser.count_number_of_pairs_of_braces_in_function(bad_source_1)) + end + end From 60a1829acf56a79f9b24114ba55941b3bb167ee1 Mon Sep 17 00:00:00 2001 From: laurens Date: Tue, 12 Nov 2019 20:03:52 +0100 Subject: [PATCH 4/7] Fix comments --- lib/cmock_header_parser.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/cmock_header_parser.rb b/lib/cmock_header_parser.rb index 83d045e..6a33eb2 100644 --- a/lib/cmock_header_parser.rb +++ b/lib/cmock_header_parser.rb @@ -108,7 +108,7 @@ class CMockHeaderParser # - sometimes they appear together, sometimes individually, # - The keywords can appear before or after the return type (this is a compiler warning but people do weird stuff), # so we check for word boundaries when searching for them - # - We first remove "static inline" combinations and boil down to + # - We first remove "static inline" combinations and boil down to single inline or static statements inline_function_regex_formats = [ /(static\s+inline|inline\s+static)\s*/, # Last part (\s*) is just to remove whitespaces (only to prettify the output) /(\bstatic\b|\binline\b)\s*/, # Last part (\s*) is just to remove whitespaces (only to prettify the output) @@ -140,7 +140,7 @@ class CMockHeaderParser inline_function_stripped = inline_function_match.post_match until total_pairs_to_remove == 0 - inline_function_stripped.sub!(/\s*#{square_bracket_pair_regex_format}/, ";") # Remove inline implementation + inline_function_stripped.sub!(/\s*#{square_bracket_pair_regex_format}/, ";") # Remove inline implementation (+ some whitespace because it's prettier) total_pairs_to_remove -= 1 end From 759d1826c8f17be6503479101868a560d225ed94 Mon Sep 17 00:00:00 2001 From: laurens Date: Tue, 12 Nov 2019 20:28:08 +0100 Subject: [PATCH 5/7] Add test with no braces in source - Should return 0 since no braces in source --- test/unit/cmock_header_parser_test.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/unit/cmock_header_parser_test.rb b/test/unit/cmock_header_parser_test.rb index a58b087..07b77ee 100644 --- a/test/unit/cmock_header_parser_test.rb +++ b/test/unit/cmock_header_parser_test.rb @@ -1988,9 +1988,13 @@ describe CMockHeaderParser, "Verify CMockHeaderParser Module" do "\t}\n" + "\treturn 42;\n" + "}\n" + bad_source_2 = + "int foo(struct my_struct *s)\n" + + "\n" # No braces in source assert_equal(0, @parser.count_number_of_pairs_of_braces_in_function(bad_source_0)) assert_equal(0, @parser.count_number_of_pairs_of_braces_in_function(bad_source_1)) + assert_equal(0, @parser.count_number_of_pairs_of_braces_in_function(bad_source_2)) end end From 811b85e1b1ed7c107e2d6b290d23e5df04b1a929 Mon Sep 17 00:00:00 2001 From: laurens Date: Wed, 13 Nov 2019 00:03:30 +0100 Subject: [PATCH 6/7] Use .times iso until --- lib/cmock_header_parser.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/cmock_header_parser.rb b/lib/cmock_header_parser.rb index 6a33eb2..fdf005f 100644 --- a/lib/cmock_header_parser.rb +++ b/lib/cmock_header_parser.rb @@ -139,9 +139,9 @@ class CMockHeaderParser break if 0 == total_pairs_to_remove # Bad source? inline_function_stripped = inline_function_match.post_match - until total_pairs_to_remove == 0 + + total_pairs_to_remove.times do inline_function_stripped.sub!(/\s*#{square_bracket_pair_regex_format}/, ";") # Remove inline implementation (+ some whitespace because it's prettier) - total_pairs_to_remove -= 1 end source = inline_function_match.pre_match + inline_function_stripped # Make new source with the inline function removed and move on to the next From 0c45d26a285d109741c8a08e4709bd6c84752bca Mon Sep 17 00:00:00 2001 From: laurens Date: Wed, 13 Nov 2019 00:03:52 +0100 Subject: [PATCH 7/7] Rename total_levels to total_pairs - We are counting pairs of braces, not levels of indentation --- lib/cmock_header_parser.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/cmock_header_parser.rb b/lib/cmock_header_parser.rb index fdf005f..d47b539 100644 --- a/lib/cmock_header_parser.rb +++ b/lib/cmock_header_parser.rb @@ -78,12 +78,12 @@ class CMockHeaderParser def count_number_of_pairs_of_braces_in_function(source) is_function_start_found = false curr_level = 0 - total_levels = 0 + total_pairs = 0 source.each_char do |c| if ("{" == c) curr_level += 1 - total_levels +=1 + total_pairs +=1 is_function_start_found = true elsif ("}" == c) curr_level -=1 @@ -93,10 +93,10 @@ class CMockHeaderParser end if 0 != curr_level - total_levels = 0 # Something is fishy about this source, not enough closing braces? + total_pairs = 0 # Something is fishy about this source, not enough closing braces? end - return total_levels + return total_pairs end # Transform inline functions to regular functions in the source by the user