From 61686f8b1f151d33b3ce41242573648ac242036a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Fran=C3=A7ois=20DEVERGE?= Date: Fri, 21 Aug 2026 09:30:19 +0200 Subject: [PATCH] fix: improve misra.py for missing prototype false positives --- addons/misra.py | 24 ++++++++++++++++--- .../test/misra/misra-regression-prototypes.c | 13 ++++++++++ addons/test/misra_test.py | 15 ++++++++++++ 3 files changed, 49 insertions(+), 3 deletions(-) create mode 100644 addons/test/misra/misra-regression-prototypes.c diff --git a/addons/misra.py b/addons/misra.py index 933ef4c2025..7abc99f365a 100755 --- a/addons/misra.py +++ b/addons/misra.py @@ -538,6 +538,20 @@ def is_source_file(file): return file.endswith('.c') +def has_prior_function_prototype(cfg, function): + """Return whether a global prototype precedes the function definition.""" + for token in cfg.tokenlist: + if token == function.tokenDef: + break + if token.str != function.name or token.scope.type != 'Global': + continue + opening = token.next + if opening and opening.str == '(' and opening.link and \ + opening.link.next and opening.link.next.str == ';': + return True + return False + + def is_header(file): return file.endswith('.h') @@ -803,7 +817,8 @@ def get_function_pointer_type(tok): ret += '(' tok = tok.next.next while tok and (tok.str not in '()'): - ret += ' ' + tok.str + if tok.varId is None: + ret += ' ' + tok.str tok = tok.next if (tok is None) or tok.str != ')': return None @@ -2248,6 +2263,8 @@ def misra_8_4(self, cfg): continue if func.token != func.tokenDef: continue + if has_prior_function_prototype(cfg, func): + continue if func.tokenDef.str == 'main': continue self.reportError(func.tokenDef, 8, 4) @@ -3497,8 +3514,9 @@ def misra_17_3(self, cfg): # Additional check for implicit function calls in expressions for token in cfg.tokenlist: - if token.isName and token.function is None and token.valueType is None: - if token.next and token.next.str == "(" and token.next.valueType is None: + if token.isName and token.scope.type != 'Global' and token.function is None and token.valueType is None: + if token.next and token.next.str == "(" and token.next.valueType is None and \ + isFunctionCall(token.next, cfg.standards.c): if token.next.next.str == "*" and \ token.next.next.next.isName and token.next.next.next.valueType is not None and \ token.next.next.next.valueType.pointer > 0 : diff --git a/addons/test/misra/misra-regression-prototypes.c b/addons/test/misra/misra-regression-prototypes.c new file mode 100644 index 00000000000..11ce35c9fa0 --- /dev/null +++ b/addons/test/misra/misra-regression-prototypes.c @@ -0,0 +1,13 @@ +int regression_function(int value); + +int regression_function(int value) +{ + return value; +} + +int main(void) +{ + int local_prototype(int value); + int (*callback)(int) = regression_function; + return callback(0); +} \ No newline at end of file diff --git a/addons/test/misra_test.py b/addons/test/misra_test.py index 55a24cb4b27..dc46d8beef8 100644 --- a/addons/test/misra_test.py +++ b/addons/test/misra_test.py @@ -11,6 +11,7 @@ from addons.misra import C11_STDLIB_IDENTIFIERS, C99_STDLIB_IDENTIFIERS,C90_STDLIB_IDENTIFIERS, isStdLibId, isKeyword TEST_SOURCE_FILES = [os.path.join('addons','test','misra','misra-test.c')] +REGRESSION_SOURCE_FILE = os.path.join('addons', 'test', 'misra', 'misra-regression-prototypes.c') def remove_misra_config(s:str): @@ -93,6 +94,20 @@ def test_json_out(checker, capsys, test_files): assert("Required" in json_output['c2012-21.3'][0]['extra']) assert("Advisory" in json_output['c2012-20.1'][0]['extra']) +def test_function_prototype_and_pointer_call_regressions(checker, capsys): + dump_create(REGRESSION_SOURCE_FILE) + sys.argv.append("--cli") + try: + checker.loadRuleTexts("./addons/test/misra/misra_rules_dummy.txt") + checker.parseDump(REGRESSION_SOURCE_FILE + ".dump") + captured = capsys.readouterr().out + json_output = convert_json_output(captured.splitlines()) + assert "c2012-8.4" not in json_output + assert "c2012-17.3" not in json_output + finally: + sys.argv.remove("--cli") + dump_remove(REGRESSION_SOURCE_FILE) + def test_rules_cppcheck_severity(checker, capsys, test_files): checker.loadRuleTexts("./addons/test/misra/misra_rules_dummy.txt")