# ========================================================================= # CMock - Automatic Mock Generation for C # ThrowTheSwitch.org # Copyright (c) 2007-26 Mike Karlesky, Mark VanderVoord, & Greg Williams # SPDX-License-Identifier: MIT # ========================================================================= class CMockGeneratorUtils attr_accessor :config, :helpers, :ordered, :ptr_handling, :arrays, :cexception def initialize(config, helpers = {}) @config = config @ptr_handling = @config.when_ptr @ordered = @config.enforce_strict_ordering @arrays = @config.plugins.include? :array @cexception = @config.plugins.include? :cexception @expect_any = @config.plugins.include? :expect_any_args @return_thru_ptr = @config.plugins.include? :return_thru_ptr @ignore_arg = @config.plugins.include? :ignore_arg @ignore = @config.plugins.include? :ignore @ignore_stateless = @config.plugins.include? :ignore_stateless @treat_as = @config.treat_as @treat_as_void = (['void'] + (@config.respond_to?(:treat_as_void) ? @config.treat_as_void : [])).uniq @helpers = helpers end def self.arg_type_with_const(arg) # Restore any "const" that was removed in header parsing if arg[:type].include?('*') arg[:const_ptr?] ? "#{arg[:type]} const" : arg[:type] else arg[:const?] ? "const #{arg[:type]}" : arg[:type] end end def arg_type_with_const(arg) self.class.arg_type_with_const(arg) end def self.arg_declaration(arg) if arg[:ptr_to_array?] && arg[:array_dims] base_type = arg_type_with_const(arg).sub(/\*$/, '').strip dims_str = arg[:array_dims].map { |d| "[#{d}]" }.join "#{base_type} (*#{arg[:name]})#{dims_str}" elsif arg[:array_dims] base_type = arg_type_with_const(arg).sub(/\*$/, '').strip dims_str = arg[:array_dims].map { |d| "[#{d}]" }.join "#{base_type} #{arg[:name]}#{dims_str}" else "#{arg_type_with_const(arg)} #{arg[:name]}" end end def arg_declaration(arg) self.class.arg_declaration(arg) end def code_verify_an_arg_expectation(function, arg) if @arrays case @ptr_handling when :smart then code_verify_an_arg_expectation_with_smart_arrays(function, arg) when :compare_data then code_verify_an_arg_expectation_with_normal_arrays(function, arg) when :compare_ptr then raise "ERROR: the array plugin doesn't enjoy working with :compare_ptr only. Disable one option." end else code_verify_an_arg_expectation_with_no_arrays(function, arg) end end def code_add_base_expectation(func_name, global_ordering_supported = true) lines = " CMOCK_MEM_INDEX_TYPE cmock_guts_index = CMock_Guts_MemNew(sizeof(CMOCK_#{func_name}_CALL_INSTANCE));\n" lines << " CMOCK_#{func_name}_CALL_INSTANCE* cmock_call_instance = (CMOCK_#{func_name}_CALL_INSTANCE*)CMock_Guts_GetAddressFor(cmock_guts_index);\n" lines << " UNITY_TEST_ASSERT_NOT_NULL(cmock_call_instance, cmock_line, CMockStringOutOfMemory);\n" lines << " memset(cmock_call_instance, 0, sizeof(*cmock_call_instance));\n" lines << " Mock.#{func_name}_CallInstance = CMock_Guts_MemChain(Mock.#{func_name}_CallInstance, cmock_guts_index);\n" lines << " Mock.#{func_name}_IgnoreBool = (char)0;\n" if @ignore || @ignore_stateless lines << " cmock_call_instance->LineNumber = cmock_line;\n" lines << " cmock_call_instance->CallOrder = ++GlobalExpectCount;\n" if @ordered && global_ordering_supported lines << " cmock_call_instance->ExceptionToThrow = CEXCEPTION_NONE;\n" if @cexception lines << " cmock_call_instance->ExpectAnyArgsBool = (char)0;\n" if @expect_any lines end def code_add_an_arg_expectation(arg, depth = 1) lines = code_assign_argument_quickly("cmock_call_instance->Expected_#{arg[:name]}", arg) lines << " cmock_call_instance->Expected_#{arg[:name]}_Depth = #{depth};\n" if @arrays && depth.instance_of?(String) lines << " cmock_call_instance->IgnoreArg_#{arg[:name]} = 0;\n" if @ignore_arg lines << " cmock_call_instance->ReturnThruPtr_#{arg[:name]}_Used = 0;\n" if @return_thru_ptr && ptr_or_str?(arg[:type]) && !(arg[:const?]) lines end def code_assign_argument_quickly(dest, arg) if arg[:ptr?] || @treat_as.include?(arg[:type]) cast = arg[:array_dims] ? "(#{arg[:type]})" : '' " #{dest} = #{cast}#{arg[:name]};\n" else assert_expr = "sizeof(#{arg[:name]}) == sizeof(#{arg[:type]}) ? 1 : -1" comment = "/* add #{arg[:type]} to :treat_as_array if this causes an error */" " memcpy((void*)(&#{dest}), (const void*)(&#{arg[:name]}),\n" \ " sizeof(#{arg[:type]}[#{assert_expr}])); #{comment}\n" end end def code_add_argument_loader(function) if function[:args_string] != 'void' if @arrays args_string = function[:args].map do |m| if (m[:ptr?] || m[:string?]) && m[:array_size_order] == :before arg_declaration(m) elsif m[:ptr?] || m[:string?] "#{arg_declaration(m)}, int #{m[:name]}_Depth" else arg_declaration(m) end end.join(', ') body = function[:args].inject('') do |all, arg| depth = if (arg[:ptr?] || arg[:string?]) && arg[:array_size_order] == :before arg[:array_size_name] elsif arg[:ptr?] || arg[:string?] "#{arg[:name]}_Depth" else 1 end all + code_add_an_arg_expectation(arg, depth) end "void CMockExpectParameters_#{function[:name]}(CMOCK_#{function[:name]}_CALL_INSTANCE* cmock_call_instance, #{args_string});\n" \ "void CMockExpectParameters_#{function[:name]}(CMOCK_#{function[:name]}_CALL_INSTANCE* cmock_call_instance, #{args_string})\n{\n" \ "#{body}" \ "}\n\n" else "void CMockExpectParameters_#{function[:name]}(CMOCK_#{function[:name]}_CALL_INSTANCE* cmock_call_instance, #{function[:args_string]});\n" \ "void CMockExpectParameters_#{function[:name]}(CMOCK_#{function[:name]}_CALL_INSTANCE* cmock_call_instance, #{function[:args_string]})\n{\n" \ "#{function[:args].inject('') { |all, arg| all + code_add_an_arg_expectation(arg) }}" \ "}\n\n" end else '' end end def code_call_argument_loader(function) if function[:args_string] != 'void' args = function[:args].map do |m| if @arrays && (m[:ptr?] || m[:string?]) && m[:array_size_order] == :after "#{m[:name]}, #{m[:array_size_name]}" elsif @arrays && (m[:ptr?] || m[:string?]) && m[:array_size_order] == :before m[:name] elsif @arrays && m[:ptr?] "#{m[:name]}, 1" elsif @arrays && m[:string?] "#{m[:name]}, 0" elsif @arrays && m[:array_size?] m[:name] else m[:name] end end " CMockExpectParameters_#{function[:name]}(cmock_call_instance, #{args.join(', ')});\n" else '' end end def ptr_or_str?(arg_type) (arg_type.include?('*') || @treat_as.fetch(arg_type, '').include?('*')) end # private ###################### def lookup_expect_type(_function, arg) c_type = arg[:type] arg_name = arg[:name] expected = "cmock_call_instance->Expected_#{arg_name}" ignore = "cmock_call_instance->IgnoreArg_#{arg_name}" # Pointer-to-array args (e.g., char (*buf)[N]) always compare by memory contents return [c_type, arg_name, expected, ignore, 'UNITY_TEST_ASSERT_EQUAL_MEMORY_ARRAY', ''] if arg[:ptr_to_array?] unity_func = if (arg[:ptr?]) && ((c_type =~ /\*\*/) || (@ptr_handling == :compare_ptr)) ['UNITY_TEST_ASSERT_EQUAL_PTR', ''] elsif arg[:ptr?] && !@arrays && void_pointer_type?(c_type) # void* cannot be safely dereferenced; without the array plugin there's no way # to specify depth, so fall back to comparing the pointer value itself ['UNITY_TEST_ASSERT_EQUAL_PTR', ''] elsif arg[:ptr?] && @arrays && void_pointer_type?(c_type) # With the array plugin, treat_as_void aliases not in treat_as would fall back to # MEMORY_ARRAY with sizeof(void), which is illegal. Use HEX8_ARRAY (byte comparison) # to match what literal void* gets from the default treat_as entry. ['UNITY_TEST_ASSERT_EQUAL_HEX8_ARRAY', ''] else @helpers.nil? || @helpers[:unity_helper].nil? ? ['UNITY_TEST_ASSERT_EQUAL', ''] : @helpers[:unity_helper].get_helper(c_type) end [c_type, arg_name, expected, ignore, unity_func[0], unity_func[1]] end def void_pointer_type?(c_type) base = c_type.gsub(/\bconst\b/, '').gsub(/\s+/, '') return false unless base.end_with?('*') @treat_as_void.include?(base.chomp('*')) end def ptr_to_array_elem_size(arg, c_type) # For pointer-to-array args (e.g., char (*buf)[10]), the element size is sizeof(base_type[dims]) if arg[:ptr_to_array?] && arg[:array_dims] base_type = c_type.gsub(/\*+$/, '').strip "sizeof(#{base_type}#{arg[:array_dims].map { |d| "[#{d}]" }.join})" else "sizeof(#{c_type.sub('*', '')})" end end def code_verify_an_arg_expectation_with_no_arrays(function, arg) c_type, arg_name, expected, ignore, unity_func, pre = lookup_expect_type(function, arg) elem_size = ptr_to_array_elem_size(arg, c_type) lines = '' lines << " if (!#{ignore})\n" if @ignore_arg lines << " {\n" lines << " UNITY_SET_DETAILS(CMockString_#{function[:name]},CMockString_#{arg_name});\n" case unity_func when 'UNITY_TEST_ASSERT_EQUAL_MEMORY' c_type_local = c_type.gsub(/\*$/, '') lines << " UNITY_TEST_ASSERT_EQUAL_MEMORY(#{pre}#{expected}, #{pre}#{arg_name}, sizeof(#{c_type_local}), cmock_line, CMockStringMismatch);\n" when 'UNITY_TEST_ASSERT_EQUAL_MEMORY_ARRAY' if pre == '&' lines << " UNITY_TEST_ASSERT_EQUAL_MEMORY(#{pre}#{expected}, #{pre}#{arg_name}, #{elem_size}, cmock_line, CMockStringMismatch);\n" else lines << " if (#{pre}#{expected} == NULL)\n" lines << " { UNITY_TEST_ASSERT_NULL(#{pre}#{arg_name}, cmock_line, CMockStringExpNULL); }\n" lines << " else\n" lines << " { UNITY_TEST_ASSERT_EQUAL_MEMORY(#{pre}#{expected}, #{pre}#{arg_name}, #{elem_size}, cmock_line, CMockStringMismatch); }\n" end when /_ARRAY/ if pre == '&' lines << " #{unity_func}(#{pre}#{expected}, #{pre}#{arg_name}, 1, cmock_line, CMockStringMismatch);\n" else lines << " if (#{pre}#{expected} == NULL)\n" lines << " { UNITY_TEST_ASSERT_NULL(#{pre}#{arg_name}, cmock_line, CMockStringExpNULL); }\n" lines << " else\n" lines << " { #{unity_func}(#{pre}#{expected}, #{pre}#{arg_name}, 1, cmock_line, CMockStringMismatch); }\n" end else lines << " #{unity_func}(#{pre}#{expected}, #{pre}#{arg_name}, cmock_line, CMockStringMismatch);\n" end lines << " }\n" lines end def code_verify_an_arg_expectation_with_normal_arrays(function, arg) c_type, arg_name, expected, ignore, unity_func, pre = lookup_expect_type(function, arg) depth_name = arg[:ptr?] || arg[:string?] ? "cmock_call_instance->Expected_#{arg_name}_Depth" : 1 elem_size = ptr_to_array_elem_size(arg, c_type) lines = '' lines << " if (!#{ignore})\n" if @ignore_arg lines << " {\n" lines << " UNITY_SET_DETAILS(CMockString_#{function[:name]},CMockString_#{arg_name});\n" case unity_func when 'UNITY_TEST_ASSERT_EQUAL_MEMORY' c_type_local = c_type.gsub(/\*$/, '') lines << " UNITY_TEST_ASSERT_EQUAL_MEMORY(#{pre}#{expected}, #{pre}#{arg_name}, sizeof(#{c_type_local}), cmock_line, CMockStringMismatch);\n" when 'UNITY_TEST_ASSERT_EQUAL_MEMORY_ARRAY' if pre == '&' lines << " UNITY_TEST_ASSERT_EQUAL_MEMORY(#{pre}#{expected}, #{pre}#{arg_name}, #{elem_size}, cmock_line, CMockStringMismatch);\n" else lines << " if (#{pre}#{expected} == NULL)\n" lines << " { UNITY_TEST_ASSERT_NULL(#{pre}#{arg_name}, cmock_line, CMockStringExpNULL); }\n" lines << " else\n" lines << " { UNITY_TEST_ASSERT_EQUAL_MEMORY_ARRAY(#{pre}#{expected}, #{pre}#{arg_name}, #{elem_size}, #{depth_name}, cmock_line, CMockStringMismatch); }\n" end when /_ARRAY/ if pre == '&' lines << " #{unity_func}(#{pre}#{expected}, #{pre}#{arg_name}, #{depth_name}, cmock_line, CMockStringMismatch);\n" else lines << " if (#{pre}#{expected} == NULL)\n" lines << " { UNITY_TEST_ASSERT_NULL(#{pre}#{arg_name}, cmock_line, CMockStringExpNULL); }\n" lines << " else\n" lines << " { #{unity_func}(#{pre}#{expected}, #{pre}#{arg_name}, #{depth_name}, cmock_line, CMockStringMismatch); }\n" end else if arg[:string?] lines << " if (#{depth_name} == 0)\n" lines << " { #{unity_func}(#{pre}#{expected}, #{pre}#{arg_name}, cmock_line, CMockStringMismatch); }\n" lines << " else\n" lines << " { UNITY_TEST_ASSERT_EQUAL_MEMORY((const void*)(#{pre}#{expected}), (const void*)(#{pre}#{arg_name}), (UNITY_UINT32)(#{depth_name}), cmock_line, CMockStringMismatch); }\n" else lines << " #{unity_func}(#{pre}#{expected}, #{pre}#{arg_name}, cmock_line, CMockStringMismatch);\n" end end lines << " }\n" lines end def code_verify_an_arg_expectation_with_smart_arrays(function, arg) c_type, arg_name, expected, ignore, unity_func, pre = lookup_expect_type(function, arg) depth_name = arg[:ptr?] || arg[:string?] ? "cmock_call_instance->Expected_#{arg_name}_Depth" : 1 elem_size = ptr_to_array_elem_size(arg, c_type) lines = '' lines << " if (!#{ignore})\n" if @ignore_arg lines << " {\n" lines << " UNITY_SET_DETAILS(CMockString_#{function[:name]},CMockString_#{arg_name});\n" case unity_func when 'UNITY_TEST_ASSERT_EQUAL_MEMORY' c_type_local = c_type.gsub(/\*$/, '') lines << " UNITY_TEST_ASSERT_EQUAL_MEMORY(#{pre}#{expected}, #{pre}#{arg_name}, sizeof(#{c_type_local}), cmock_line, CMockStringMismatch);\n" when 'UNITY_TEST_ASSERT_EQUAL_MEMORY_ARRAY' if pre == '&' lines << " UNITY_TEST_ASSERT_EQUAL_MEMORY_ARRAY(#{pre}#{expected}, #{pre}#{arg_name}, #{elem_size}, #{depth_name}, cmock_line, CMockStringMismatch);\n" else lines << " if (#{pre}#{expected} == NULL)\n" lines << " { UNITY_TEST_ASSERT_NULL(#{arg_name}, cmock_line, CMockStringExpNULL); }\n" lines << (depth_name != 1 ? " else if (#{depth_name} == 0)\n { UNITY_TEST_ASSERT_EQUAL_PTR(#{pre}#{expected}, #{pre}#{arg_name}, cmock_line, CMockStringMismatch); }\n" : '') lines << " else\n" lines << " { UNITY_TEST_ASSERT_EQUAL_MEMORY_ARRAY(#{pre}#{expected}, #{pre}#{arg_name}, #{elem_size}, #{depth_name}, cmock_line, CMockStringMismatch); }\n" end when /_ARRAY/ if pre == '&' lines << " #{unity_func}(#{pre}#{expected}, #{pre}#{arg_name}, #{depth_name}, cmock_line, CMockStringMismatch);\n" else lines << " if (#{pre}#{expected} == NULL)\n" lines << " { UNITY_TEST_ASSERT_NULL(#{pre}#{arg_name}, cmock_line, CMockStringExpNULL); }\n" lines << (depth_name != 1 ? " else if (#{depth_name} == 0)\n { UNITY_TEST_ASSERT_EQUAL_PTR(#{pre}#{expected}, #{pre}#{arg_name}, cmock_line, CMockStringMismatch); }\n" : '') lines << " else\n" lines << " { #{unity_func}(#{pre}#{expected}, #{pre}#{arg_name}, #{depth_name}, cmock_line, CMockStringMismatch); }\n" end else if arg[:string?] lines << " if (#{depth_name} == 0)\n" lines << " { #{unity_func}(#{pre}#{expected}, #{pre}#{arg_name}, cmock_line, CMockStringMismatch); }\n" lines << " else\n" lines << " { UNITY_TEST_ASSERT_EQUAL_MEMORY((const void*)(#{pre}#{expected}), (const void*)(#{pre}#{arg_name}), (UNITY_UINT32)(#{depth_name}), cmock_line, CMockStringMismatch); }\n" else lines << " #{unity_func}(#{pre}#{expected}, #{pre}#{arg_name}, cmock_line, CMockStringMismatch);\n" end end lines << " }\n" lines end end