Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 21 additions & 3 deletions addons/misra.py
Original file line number Diff line number Diff line change
Expand Up @@ -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')

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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 :
Expand Down
13 changes: 13 additions & 0 deletions addons/test/misra/misra-regression-prototypes.c
Original file line number Diff line number Diff line change
@@ -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);
}
15 changes: 15 additions & 0 deletions addons/test/misra_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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")
Expand Down