From aedb21638f797f8d4859e10baecdec51290dfbd2 Mon Sep 17 00:00:00 2001 From: Mark VanderVoord Date: Tue, 30 Jun 2026 14:53:17 -0400 Subject: [PATCH 01/11] Implement broader testing across platforms. --- .github/workflows/main.yml | 41 ++++++++++++++++++++++++++------------ Gemfile | 5 ++++- 2 files changed, 32 insertions(+), 14 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 9c89b144..139a53d7 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -14,37 +14,52 @@ jobs: # Job: Unit test suite unit-tests: name: "Unit Tests" - runs-on: ubuntu-latest + runs-on: ${{ matrix.os }} strategy: + fail-fast: false matrix: + os: [ubuntu-latest, windows-latest, macos-latest] ruby: ['3.0', '3.1', '3.2', '3.3'] + exclude: + # create sparse matrix to avoid pointless duplication + - os: macos-latest + ruby: '3.1' + - os: macos-latest + ruby: '3.2' + - os: windows-latest + ruby: '3.1' + - os: windows-latest + ruby: '3.2' steps: - # Install Multilib + # Install Multilib (Linux only) - name: Install Multilib + if: matrix.os == 'ubuntu-latest' run: | sudo apt-get update -qq sudo apt-get install --assume-yes --quiet gcc-multilib + # Add MinGW GCC to PATH (Windows only — MSYS2 is pre-installed on the runner) + - name: Add GCC to PATH + if: matrix.os == 'windows-latest' + run: echo "C:\msys64\mingw64\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append + # Checks out repository under $GITHUB_WORKSPACE - name: Checkout Latest Repo - uses: actions/checkout@v2 - with: + uses: actions/checkout@v4 + with: submodules: recursive - # Setup Ruby Testing Tools to do tests on multiple ruby version - - name: Setup Ruby Testing Tools + # Setup Ruby + - name: Setup Ruby uses: ruby/setup-ruby@v1 with: ruby-version: ${{ matrix.ruby }} - # Install Ruby Testing Tools - - name: Setup Ruby Testing Tools + # Install Ruby dependencies + - name: Install Ruby Dependencies run: | - sudo gem install rspec - sudo gem install rubocop -v 1.57.2 - sudo gem install bundler - bundle update - bundle install + gem install bundler + bundle install # Run Tests - name: Run All Unit Tests diff --git a/Gemfile b/Gemfile index c6d4f853..899065c4 100644 --- a/Gemfile +++ b/Gemfile @@ -1 +1,4 @@ -source "http://rubygems.org/" +source "https://rubygems.org/" + +gem 'rspec' +gem 'rubocop', '1.57.2' From 3d745ce8f509c87b6bb27bb6a44aea0c04def9c0 Mon Sep 17 00:00:00 2001 From: Mark VanderVoord Date: Tue, 30 Jun 2026 15:20:01 -0400 Subject: [PATCH 02/11] disable color handling when windows testing. --- .github/workflows/main.yml | 2 ++ test/rakefile_helper.rb | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 139a53d7..8b66a498 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -63,5 +63,7 @@ jobs: # Run Tests - name: Run All Unit Tests + env: + NO_COLOR: ${{ matrix.os == 'windows-latest' && '1' || '' }} run: | cd test && rake ci diff --git a/test/rakefile_helper.rb b/test/rakefile_helper.rb index 66030ac9..8c9adbab 100644 --- a/test/rakefile_helper.rb +++ b/test/rakefile_helper.rb @@ -80,7 +80,7 @@ def load_configuration(config_file, cmock_overlay = nil) raise "Cannot find Config File #{config_target}" end - $colour_output = $proj[:project][:colour] + $colour_output = $proj[:project][:colour] && !ENV['NO_COLOR'] end def configure_clean From ff87d4c08a2c96165b7f7ca534c3973ba20ec583 Mon Sep 17 00:00:00 2001 From: Mark VanderVoord Date: Tue, 30 Jun 2026 16:11:43 -0400 Subject: [PATCH 03/11] Fix some issues that windows noticed with volatile handling. Improve some docs. --- docs/CMock_Summary.md | 26 ++++++++++++++++--- lib/cmock_generator_plugin_return_thru_ptr.rb | 6 ++--- ...k_generator_plugin_return_thru_ptr_test.rb | 4 +-- 3 files changed, 27 insertions(+), 9 deletions(-) diff --git a/docs/CMock_Summary.md b/docs/CMock_Summary.md index 6728b936..7e799dbc 100644 --- a/docs/CMock_Summary.md +++ b/docs/CMock_Summary.md @@ -189,7 +189,7 @@ care how many times it was called, right? StopIgnore: ------- -Maybe you want to ignore a particular function for part of a test but dont want to +Maybe you want to ignore a particular function for part of a test but don't want to ignore it later on. In that case, you want to use StopIgnore which will cancel the previously called Ignore or IgnoreAndReturn requiring you to Expect or otherwise handle the call to a function. @@ -199,6 +199,25 @@ handle the call to a function. * `retval func(void)` => `void func_StopIgnore(void)` * `retval func(params)` => `void func_StopIgnore(void)` +It's important to note that the effect of this function is immediate and applies to +this function's stack of expectations. So the following will work as intended: + +``` +Blah_Ignore(); +funcThatMightCallBlahButWeDoNotCare(); +Blah_StopIgnore(); +funcThatWeWantToMakeSureDoesNotCallBlah(); +``` + +But this is NOT going to work, because StopIgnore immediately cancels ignore: + +``` +Blah_Ignore(); +Blah_StopIgnore(); +funcThatMightCallBlahButWeDoNotCare(); +funcThatWeWantToMakeSureDoesNotCallBlah(); +``` + IgnoreStateless: ---------------- @@ -951,8 +970,9 @@ that exposes them as virtual methods and modify your code to inject mocks at run-time... but there is another way! Simply use CMock to mock the static member methods and a C++ mocking framework -to handle the virtual methods. (Yes, you can mix mocks from CMock and a C++ -mocking framework together in the same test!) +to handle the virtual methods. CMock does NOT mock non-static members. For those, +you'll need an actual C++ mocking framework. (Yes, you can mix mocks from CMock +and a C++ mocking framework together in the same test!) Keep in mind that since C++ mocking frameworks often link the real object to the unit test too, we need to resolve multiple definition errors with something like diff --git a/lib/cmock_generator_plugin_return_thru_ptr.rb b/lib/cmock_generator_plugin_return_thru_ptr.rb index 755ddbfa..85eb5f7b 100644 --- a/lib/cmock_generator_plugin_return_thru_ptr.rb +++ b/lib/cmock_generator_plugin_return_thru_ptr.rb @@ -80,12 +80,11 @@ def mock_precheck_return_thru_ptr(function) arg_name = arg[:name] next unless @utils.ptr_or_str?(arg[:type]) && !(arg[:const?]) - dest_cast = arg[:volatile?] ? '(void*)(CMOCK_MEM_PTR_AS_INT)' : '(void*)' 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(#{dest_cast}#{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 @@ -136,11 +135,10 @@ def mock_implementation(function) arg_name = arg[:name] next unless @utils.ptr_or_str?(arg[:type]) && !(arg[:const?]) - dest_cast = arg[:volatile?] ? '(void*)(CMOCK_MEM_PTR_AS_INT)' : '(void*)' 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 << " CMOCK_MEMCPY(#{dest_cast}#{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/test/unit/cmock_generator_plugin_return_thru_ptr_test.rb b/test/unit/cmock_generator_plugin_return_thru_ptr_test.rb index 74055594..61852bc4 100644 --- a/test/unit/cmock_generator_plugin_return_thru_ptr_test.rb +++ b/test/unit/cmock_generator_plugin_return_thru_ptr_test.rb @@ -253,14 +253,14 @@ def volatile_ptr_func_expect assert_equal(expected, returned) end - it "uses (void*)(CMOCK_MEM_PTR_AS_INT) cast in mock_implementation for volatile pointer arg" do + it "uses (void*) cast in mock_implementation for volatile pointer arg" do volatile_ptr_func_expect() expected = " if (cmock_call_instance->ReturnThruPtr_foo_handle_Used)\n" + " {\n" + " UNITY_TEST_ASSERT_NOT_NULL(foo_handle, cmock_line, CMockStringPtrIsNULL);\n" + - " CMOCK_MEMCPY((void*)(CMOCK_MEM_PTR_AS_INT)foo_handle, (const void*)cmock_call_instance->ReturnThruPtr_foo_handle_Val,\n" + + " CMOCK_MEMCPY((void*)foo_handle, (const void*)cmock_call_instance->ReturnThruPtr_foo_handle_Val,\n" + " cmock_call_instance->ReturnThruPtr_foo_handle_Size);\n" + " }\n" From 61f6f2991b9a69bad68bbb594cc62faad77620e9 Mon Sep 17 00:00:00 2001 From: Mark VanderVoord Date: Tue, 30 Jun 2026 16:56:16 -0400 Subject: [PATCH 04/11] Add single instance of a valgrind test to verify memory handling in CMock. --- .github/workflows/main.yml | 48 ++++++++++++++++++++++++++++++++++++++ test/gcc_64_valgrind.yml | 48 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 96 insertions(+) create mode 100644 test/gcc_64_valgrind.yml diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 8b66a498..e543d7ed 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -67,3 +67,51 @@ jobs: NO_COLOR: ${{ matrix.os == 'windows-latest' && '1' || '' }} run: | cd test && rake ci + + # Job: Valgrind memory-leak check (Linux/gcc_64 only, latest Ruby) + valgrind: + name: "Valgrind Memory Check" + runs-on: ubuntu-latest + steps: + - name: Install Dependencies + run: | + sudo apt-get update -qq + sudo apt-get install --assume-yes --quiet gcc-multilib valgrind + + - name: Checkout Latest Repo + uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Setup Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: '3.3' + + - name: Install Ruby Dependencies + run: | + gem install bundler + bundle install + + # Build and run C unit tests, then re-run the executable under valgrind. + # test:system clobbers the build directory, so check TestCMockC before that happens. + - name: Build and Run C Unit Tests + run: cd test && rake config[gcc_64_valgrind] test:c + + - name: Valgrind Check - C Unit Tests + run: | + valgrind --leak-check=full --track-origins=yes --error-exitcode=1 \ + test/system/build/TestCMockC.exe + + # Build and run system tests, then re-run each executable under valgrind. + - name: Build and Run System Tests + run: cd test && rake config[gcc_64_valgrind] test:system + + - name: Valgrind Check - System Tests + run: | + failed=0 + for exe in test/system/build/test_*.exe; do + echo "Checking: $exe" + valgrind --leak-check=full --track-origins=yes --error-exitcode=1 "$exe" || failed=1 + done + exit $failed diff --git a/test/gcc_64_valgrind.yml b/test/gcc_64_valgrind.yml new file mode 100644 index 00000000..a3af73b7 --- /dev/null +++ b/test/gcc_64_valgrind.yml @@ -0,0 +1,48 @@ +# ========================================================================= +# CMock - Automatic Mock Generation for C +# ThrowTheSwitch.org +# Copyright (c) 2007-26 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + +# gcc_64 with debug symbols enabled for meaningful valgrind output. +# Used by the CI valgrind job to check for memory leaks and errors. + +--- +:tools: + :test_compiler: + :name: compiler + :executable: gcc + :arguments: + - "-c" + - "-m64" + - "-g" + - "-Wall" + - "-Wno-address" + - "-std=c99" + - "-pedantic" + - '-I"${5}"' + - "-D${6}" + - "${1}" + - "-o ${2}" + :test_linker: + :name: linker + :executable: gcc + :arguments: + - "${1}" + - "-lm" + - "-m64" + - "-o ${2}" +:extension: + :object: ".o" + :executable: ".exe" +:defines: + :test: + - UNITY_EXCLUDE_STDINT_H + - UNITY_EXCLUDE_LIMITS_H + - UNITY_INCLUDE_DOUBLE + - UNITY_SUPPORT_TEST_CASES + - UNITY_SUPPORT_64 + - UNITY_INT_WIDTH=32 + - UNITY_LONG_WIDTH=64 + - UNITY_POINTER_WIDTH=64 From 895d066157083c01df446b901487a43979039a3d Mon Sep 17 00:00:00 2001 From: Mark VanderVoord Date: Tue, 30 Jun 2026 22:15:29 -0400 Subject: [PATCH 05/11] Change the exit code for valgrind so that we can detect issues. --- .github/workflows/main.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index e543d7ed..8f28ebab 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -112,6 +112,11 @@ jobs: failed=0 for exe in test/system/build/test_*.exe; do echo "Checking: $exe" - valgrind --leak-check=full --track-origins=yes --error-exitcode=1 "$exe" || failed=1 + # Use exit code 42 to distinguish valgrind errors from Unity test failures. + # Some executables intentionally contain tests expected to fail (testing CMock's + # error-handling), so Unity exits non-zero. --error-exitcode=42 only fires when + # valgrind itself detects errors, letting Unity's exit code pass through otherwise. + valgrind --leak-check=full --track-origins=yes --error-exitcode=42 "$exe" + [ $? -eq 42 ] && failed=1 done exit $failed From bf10a5bc8a5bed12b60d50b72e3105cc22b1a684 Mon Sep 17 00:00:00 2001 From: Mark VanderVoord Date: Tue, 30 Jun 2026 22:24:17 -0400 Subject: [PATCH 06/11] Further refine testing process for speed and coverage. --- .github/workflows/main.yml | 58 +++++++++++++++++++++++++++++--------- 1 file changed, 44 insertions(+), 14 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 8f28ebab..22e7c829 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -11,9 +11,10 @@ on: branches: [ master ] jobs: - # Job: Unit test suite - unit-tests: - name: "Unit Tests" + # Job: Ruby unit tests across all supported Ruby versions and OSes. + # These are fast (no C compilation) and verify the generator logic is Ruby-version-portable. + ruby-tests: + name: "Ruby Tests (${{ matrix.os }}, Ruby ${{ matrix.ruby }})" runs-on: ${{ matrix.os }} strategy: fail-fast: false @@ -30,6 +31,37 @@ jobs: ruby: '3.1' - os: windows-latest ruby: '3.2' + steps: + - name: Checkout Latest Repo + uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Setup Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: ${{ matrix.ruby }} + + - name: Install Ruby Dependencies + run: | + gem install bundler + bundle install + + - name: Run Ruby Unit Tests and Style Check + env: + NO_COLOR: ${{ matrix.os == 'windows-latest' && '1' || '' }} + run: | + cd test && rake test:unit style:check + + # Job: C compilation and system tests — only needs to run on one Ruby version per OS, + # since the generated C code and runtime behavior don't vary with the Ruby version. + c-tests: + name: "C Tests (${{ matrix.os }})" + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, windows-latest, macos-latest] steps: # Install Multilib (Linux only) - name: Install Multilib @@ -43,30 +75,26 @@ jobs: if: matrix.os == 'windows-latest' run: echo "C:\msys64\mingw64\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append - # Checks out repository under $GITHUB_WORKSPACE - name: Checkout Latest Repo uses: actions/checkout@v4 with: submodules: recursive - # Setup Ruby - name: Setup Ruby uses: ruby/setup-ruby@v1 with: - ruby-version: ${{ matrix.ruby }} + ruby-version: '3.3' - # Install Ruby dependencies - name: Install Ruby Dependencies run: | gem install bundler bundle install - # Run Tests - - name: Run All Unit Tests + - name: Run C and System Tests env: NO_COLOR: ${{ matrix.os == 'windows-latest' && '1' || '' }} run: | - cd test && rake ci + cd test && rake test:c test:system test:examples # Job: Valgrind memory-leak check (Linux/gcc_64 only, latest Ruby) valgrind: @@ -114,9 +142,11 @@ jobs: echo "Checking: $exe" # Use exit code 42 to distinguish valgrind errors from Unity test failures. # Some executables intentionally contain tests expected to fail (testing CMock's - # error-handling), so Unity exits non-zero. --error-exitcode=42 only fires when - # valgrind itself detects errors, letting Unity's exit code pass through otherwise. - valgrind --leak-check=full --track-origins=yes --error-exitcode=42 "$exe" - [ $? -eq 42 ] && failed=1 + # error-handling), so Unity exits non-zero. The `|| exit_code=$?` prevents bash's + # set -e from aborting the script on Unity's non-zero exit, while still capturing + # valgrind's own error exit code (42) separately. + exit_code=0 + valgrind --leak-check=full --track-origins=yes --error-exitcode=42 "$exe" || exit_code=$? + [ $exit_code -eq 42 ] && failed=1 done exit $failed From 9a412dfccc8e580786a856d48f467ec9bd8ac76c Mon Sep 17 00:00:00 2001 From: Mark VanderVoord Date: Wed, 1 Jul 2026 07:14:45 -0400 Subject: [PATCH 07/11] More tweaking of flow. --- .github/workflows/main.yml | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 22e7c829..df1e2358 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -23,10 +23,14 @@ jobs: ruby: ['3.0', '3.1', '3.2', '3.3'] exclude: # create sparse matrix to avoid pointless duplication + - os: macos-latest + ruby: '3.0' - os: macos-latest ruby: '3.1' - os: macos-latest ruby: '3.2' + - os: windows-latest + ruby: '3.0' - os: windows-latest ruby: '3.1' - os: windows-latest @@ -90,11 +94,15 @@ jobs: gem install bundler bundle install - - name: Run C and System Tests + - name: Run C Unit Tests env: NO_COLOR: ${{ matrix.os == 'windows-latest' && '1' || '' }} - run: | - cd test && rake test:c test:system test:examples + run: cd test && rake test:c + + - name: Run System Tests + env: + NO_COLOR: ${{ matrix.os == 'windows-latest' && '1' || '' }} + run: cd test && rake test:system test:examples # Job: Valgrind memory-leak check (Linux/gcc_64 only, latest Ruby) valgrind: From af43eb87b1fceb701e2427a1cea2dfea59131ecd Mon Sep 17 00:00:00 2001 From: Mark VanderVoord Date: Wed, 1 Jul 2026 07:48:23 -0400 Subject: [PATCH 08/11] streamline function declaration regex. --- lib/cmock_header_parser.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/cmock_header_parser.rb b/lib/cmock_header_parser.rb index c3140031..b2fd69b9 100644 --- a/lib/cmock_header_parser.rb +++ b/lib/cmock_header_parser.rb @@ -15,7 +15,7 @@ def initialize(cfg) @c_calling_conventions = cfg.c_calling_conventions.uniq @treat_as_array = cfg.treat_as_array @treat_as_void = (['void'] + cfg.treat_as_void).uniq - @function_declaration_parse_base_match = '([\w\s\*\(\),\[\]]*?\w[\w\s\*\(\),\[\]]*?)\(([\w\s\*\(\),\.\[\]+\-\/]*)\)' + @function_declaration_parse_base_match = '([^(]*\w)\s*\(([\w\s\*\(\),\.\[\]+\-\/]*)\)' @declaration_parse_matcher = /#{@function_declaration_parse_base_match}$/m @standards = (%w[int short char long unsigned signed] + cfg.treat_as.keys).uniq @array_size_name = cfg.array_size_name From 847e87e71e7056132f4d4e27c6cf60c43384edcf Mon Sep 17 00:00:00 2001 From: Mark VanderVoord Date: Wed, 1 Jul 2026 10:27:01 -0400 Subject: [PATCH 09/11] Fall back to iterative brace removal because I suspect regex version is too slow. Further tidying of ci. --- .github/workflows/main.yml | 17 ++++++++++++++--- lib/cmock_header_parser.rb | 12 ++++-------- 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index df1e2358..dfc65792 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -51,11 +51,17 @@ jobs: gem install bundler bundle install - - name: Run Ruby Unit Tests and Style Check + - name: Run Ruby Unit Tests env: NO_COLOR: ${{ matrix.os == 'windows-latest' && '1' || '' }} run: | - cd test && rake test:unit style:check + cd test && rake test:unit + + - name: Run Style Check + env: + NO_COLOR: ${{ matrix.os == 'windows-latest' && '1' || '' }} + run: | + cd test && rake style:check # Job: C compilation and system tests — only needs to run on one Ruby version per OS, # since the generated C code and runtime behavior don't vary with the Ruby version. @@ -102,7 +108,12 @@ jobs: - name: Run System Tests env: NO_COLOR: ${{ matrix.os == 'windows-latest' && '1' || '' }} - run: cd test && rake test:system test:examples + run: cd test && rake test:system + + - name: Run Examples + env: + NO_COLOR: ${{ matrix.os == 'windows-latest' && '1' || '' }} + run: cd test && rake test:examples # Job: Valgrind memory-leak check (Linux/gcc_64 only, latest Ruby) valgrind: diff --git a/lib/cmock_header_parser.rb b/lib/cmock_header_parser.rb index b2fd69b9..42ed862e 100644 --- a/lib/cmock_header_parser.rb +++ b/lib/cmock_header_parser.rb @@ -118,15 +118,11 @@ def remove_comments_from_source(source) def remove_nested_pairs_of_braces(source) # remove nested pairs of braces because no function declarations will be inside of them (leave outer pair for function definition detection) - if RUBY_VERSION.split('.')[0].to_i > 1 - # we assign a string first because (no joke) if Ruby 1.9.3 sees this line as a regex, it will crash. - r = '\\{([^\\{\\}]*|\\g<0>)*\\}' - source.gsub!(/#{r}/m, '{ }') - else - while source.gsub!(/\{[^{}]*\{[^{}]*\}[^{}]*\}/m, '{ }') - end + # Use iterative approach for all Ruby versions: the recursive regex \{([^\{\}]*|\g<0>)*\} + # is exponential on unbalanced brace inputs (catastrophic backtracking on Ruby < 3.2). + while source.gsub!(/\{[^{}]*\{[^{}]*\}[^{}]*\}/m, '{ }') + # Keep doing it! end - source end From 922ebfa699c848b0f383ff2f6edb056f2afa4b66 Mon Sep 17 00:00:00 2001 From: Mark VanderVoord Date: Wed, 1 Jul 2026 10:41:29 -0400 Subject: [PATCH 10/11] Whoops. Fixed last issue. --- lib/cmock_header_parser.rb | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/lib/cmock_header_parser.rb b/lib/cmock_header_parser.rb index 42ed862e..883f7f2b 100644 --- a/lib/cmock_header_parser.rb +++ b/lib/cmock_header_parser.rb @@ -118,11 +118,13 @@ def remove_comments_from_source(source) def remove_nested_pairs_of_braces(source) # remove nested pairs of braces because no function declarations will be inside of them (leave outer pair for function definition detection) - # Use iterative approach for all Ruby versions: the recursive regex \{([^\{\}]*|\g<0>)*\} - # is exponential on unbalanced brace inputs (catastrophic backtracking on Ruby < 3.2). - while source.gsub!(/\{[^{}]*\{[^{}]*\}[^{}]*\}/m, '{ }') - # Keep doing it! + # Collapse innermost brace pairs first using a brace-free sentinel (\x00), working + # outward until no balanced pairs remain. This avoids the catastrophic backtracking + # of the recursive regex \{([^\{\}]*|\g<0>)*\} on Ruby < 3.2 while preserving + # identical semantics: every balanced brace structure is collapsed to '{ }'. + while source.gsub!(/\{[^{}]*\}/m, "\x00") end + source.gsub!("\x00", '{ }') source end From a737db33eb559394d8a976c75d57b3823584175e Mon Sep 17 00:00:00 2001 From: Mark VanderVoord Date: Wed, 1 Jul 2026 14:09:52 -0400 Subject: [PATCH 11/11] Another tweak to regex handling in parser for performance purposes. --- lib/cmock_header_parser.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/cmock_header_parser.rb b/lib/cmock_header_parser.rb index 883f7f2b..dfc82847 100644 --- a/lib/cmock_header_parser.rb +++ b/lib/cmock_header_parser.rb @@ -309,7 +309,7 @@ def import_source(source, parse_project, cpp = false) source.gsub!(/\b(?:#{@ct_assert_patterns.join('|')})\s*\([^;]*\)/, '') unless @ct_assert_patterns.empty? # strip any remaining WORD(...==...) etc. -- calls containing comparison operators cannot be C function prototypes # must run before default-value removal, which would corrupt "!= 0" into "!" by removing "= 0" - source.gsub!(/\b\w+\s*\((?:[^()!=<>]*(?:\([^()]*\))*)*(?:==|!=|<=|>=)[^;]*\)/, '') + source.gsub!(/\b\w+\s*\((?:[^()!=<>]|\([^()]*\))*(?:==|!=|<=|>=)[^;]*\)/, '') source.gsub!(/\s*=\s*['"a-zA-Z0-9_.]+\s*/, '') # remove default value statements from argument lists