diff --git a/slither/detectors/all_detectors.py b/slither/detectors/all_detectors.py index 1cab317ad..9722b8793 100644 --- a/slither/detectors/all_detectors.py +++ b/slither/detectors/all_detectors.py @@ -88,3 +88,4 @@ from .statements.delegatecall_in_loop import DelegatecallInLoop from .functions.protected_variable import ProtectedVariables from .functions.permit_domain_signature_collision import DomainSeparatorCollision from .functions.codex import Codex +from .functions.cyclomatic_complexity import CyclomaticComplexity diff --git a/slither/detectors/functions/cyclomatic_complexity.py b/slither/detectors/functions/cyclomatic_complexity.py new file mode 100644 index 000000000..5d1da1823 --- /dev/null +++ b/slither/detectors/functions/cyclomatic_complexity.py @@ -0,0 +1,46 @@ +from slither.core.declarations import Function +from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification +from slither.utils.code_complexity import compute_cyclomatic_complexity + + +class CyclomaticComplexity(AbstractDetector): + """ + Detects functions with high (> 11) cyclomatic complexity. + """ + + ARGUMENT = "cyclomatic-complexity" + HELP = "Detects functions with high (> 11) cyclomatic complexity" + IMPACT = DetectorClassification.INFORMATIONAL + CONFIDENCE = DetectorClassification.HIGH + + WIKI = 'https://github.com/crytic/slither/wiki/Detector-Documentation#cyclomatic-complexity"' + + WIKI_TITLE = "Cyclomatic complexity detector" + WIKI_DESCRIPTION = "Detects functions with high (> 11) cyclomatic complexity." + WIKI_EXPLOIT_SCENARIO = "" + WIKI_RECOMMENDATION = ( + "Reduce cyclomatic complexity by splitting the function into several smaller subroutines." + ) + + @staticmethod + def _check_for_high_cc(high_cc_functions: list, f: Function): + cc = compute_cyclomatic_complexity(f) + if cc > 11: + high_cc_functions.append((f, cc)) + + def _detect(self): + results = [] + high_cc_functions = [] + + for c in self.compilation_unit.contracts: + for f in c.functions_declared: + CyclomaticComplexity._check_for_high_cc(high_cc_functions, f) + + for f in self.compilation_unit.functions_top_level: + CyclomaticComplexity._check_for_high_cc(high_cc_functions, f) + + for f, cc in high_cc_functions: + info = [f, f" has a high cyclomatic complexity ({cc}).\n"] + res = self.generate_result(info) + results.append(res) + return results diff --git a/tests/detectors/cyclomatic-complexity/0.8.16/HighCyclomaticComplexity.sol b/tests/detectors/cyclomatic-complexity/0.8.16/HighCyclomaticComplexity.sol new file mode 100644 index 000000000..cd62827dc --- /dev/null +++ b/tests/detectors/cyclomatic-complexity/0.8.16/HighCyclomaticComplexity.sol @@ -0,0 +1,32 @@ +pragma solidity 0.8.16; + +contract HighCyclomaticComplexity +{ + bool bool1; + bool bool2; + bool bool3; + bool bool4; + bool bool5; + bool bool6; + bool bool7; + bool bool8; + bool bool9; + bool bool10; + bool bool11; + + function highCC() internal view + { + if (bool1) + if (bool2) + if (bool3) + if (bool4) + if (bool5) + if (bool6) + if (bool7) + if (bool8) + if (bool9) + if (bool10) + if (bool11) + revert(); + } +} \ No newline at end of file diff --git a/tests/detectors/cyclomatic-complexity/0.8.16/HighCyclomaticComplexity.sol.0.8.16.CyclomaticComplexity.json b/tests/detectors/cyclomatic-complexity/0.8.16/HighCyclomaticComplexity.sol.0.8.16.CyclomaticComplexity.json new file mode 100644 index 000000000..e6e370ac3 --- /dev/null +++ b/tests/detectors/cyclomatic-complexity/0.8.16/HighCyclomaticComplexity.sol.0.8.16.CyclomaticComplexity.json @@ -0,0 +1,96 @@ +[ + [ + { + "elements": [ + { + "type": "function", + "name": "highCC", + "source_mapping": { + "start": 244, + "length": 536, + "filename_relative": "tests/detectors/cyclomatic-complexity/0.8.16/HighCyclomaticComplexity.sol", + "filename_absolute": "/GENERIC_PATH", + "filename_short": "tests/detectors/cyclomatic-complexity/0.8.16/HighCyclomaticComplexity.sol", + "is_dependency": false, + "lines": [ + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24, + 25, + 26, + 27, + 28, + 29, + 30, + 31 + ], + "starting_column": 5, + "ending_column": 6 + }, + "type_specific_fields": { + "parent": { + "type": "contract", + "name": "HighCyclomaticComplexity", + "source_mapping": { + "start": 25, + "length": 757, + "filename_relative": "tests/detectors/cyclomatic-complexity/0.8.16/HighCyclomaticComplexity.sol", + "filename_absolute": "/GENERIC_PATH", + "filename_short": "tests/detectors/cyclomatic-complexity/0.8.16/HighCyclomaticComplexity.sol", + "is_dependency": false, + "lines": [ + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24, + 25, + 26, + 27, + 28, + 29, + 30, + 31, + 32, + 33 + ], + "starting_column": 1, + "ending_column": 0 + } + }, + "signature": "highCC()" + } + } + ], + "description": "HighCyclomaticComplexity.highCC() (tests/detectors/cyclomatic-complexity/0.8.16/HighCyclomaticComplexity.sol#17-31) has a high cyclomatic complexity (12).\n", + "markdown": "[HighCyclomaticComplexity.highCC()](tests/detectors/cyclomatic-complexity/0.8.16/HighCyclomaticComplexity.sol#L17-L31) has a high cyclomatic complexity (12).\n", + "first_markdown_element": "tests/detectors/cyclomatic-complexity/0.8.16/HighCyclomaticComplexity.sol#L17-L31", + "id": "405b9e7f5697539c75171d728f0a10b6ebb7fe08441c445b0e63c33982c98e2d", + "check": "cyclomatic-complexity", + "impact": "Informational", + "confidence": "High" + } + ] +] \ No newline at end of file diff --git a/tests/detectors/cyclomatic-complexity/0.8.16/LowCyclomaticComplexity.sol b/tests/detectors/cyclomatic-complexity/0.8.16/LowCyclomaticComplexity.sol new file mode 100644 index 000000000..78d7f3414 --- /dev/null +++ b/tests/detectors/cyclomatic-complexity/0.8.16/LowCyclomaticComplexity.sol @@ -0,0 +1,15 @@ +pragma solidity 0.8.16; + +contract LowCyclomaticComplexity +{ + function lowCC() public pure + { + for (uint i = 0; i < 10; i++) + { + for (uint j = 0; j < i; j++) + { + uint a = i + 1; + } + } + } +} \ No newline at end of file diff --git a/tests/detectors/cyclomatic-complexity/0.8.16/LowCyclomaticComplexity.sol.0.8.16.CyclomaticComplexity.json b/tests/detectors/cyclomatic-complexity/0.8.16/LowCyclomaticComplexity.sol.0.8.16.CyclomaticComplexity.json new file mode 100644 index 000000000..5825bcacc --- /dev/null +++ b/tests/detectors/cyclomatic-complexity/0.8.16/LowCyclomaticComplexity.sol.0.8.16.CyclomaticComplexity.json @@ -0,0 +1,3 @@ +[ + [] +] \ No newline at end of file diff --git a/tests/test_detectors.py b/tests/test_detectors.py index 860b281b3..3d3cc7474 100644 --- a/tests/test_detectors.py +++ b/tests/test_detectors.py @@ -1623,6 +1623,16 @@ ALL_TEST_OBJECTS = [ "var_read_using_this.sol", "0.8.15", ), + Test( + all_detectors.CyclomaticComplexity, + "HighCyclomaticComplexity.sol", + "0.8.16", + ), + Test( + all_detectors.CyclomaticComplexity, + "LowCyclomaticComplexity.sol", + "0.8.16", + ), ]