diff --git a/tests/plugins/test_variable_redefinition_in_foreach.py b/tests/plugins/test_variable_redefinition_in_foreach.py new file mode 100644 index 00000000..3159b7bb --- /dev/null +++ b/tests/plugins/test_variable_redefinition_in_foreach.py @@ -0,0 +1,55 @@ +# SPDX-License-Identifier: GPL-3.0-or-later +# SPDX-FileCopyrightText: 2024 Greenbone AG + +from pathlib import Path + +from tests.plugins import PluginTestCase +from troubadix.plugin import LinterWarning +from troubadix.plugins.variable_redefinition_in_foreach import ( + CheckVariableRedefinitionInForeach, +) + + +class CheckVariableRedefinitionInForeachTestCase(PluginTestCase): + def test_ok(self): + nasl_file = Path(__file__).parent / "test.nasl" + content = ( + "urls = ['foo', 'bar']\n" + "foreach url ( urls ) {}\n" + "url1 = 'foo'\n" + "foreach url(make_list(url1,'bar'))" + ) + fake_context = self.create_file_plugin_context( + nasl_file=nasl_file, file_content=content + ) + plugin = CheckVariableRedefinitionInForeach(fake_context) + results = list(plugin.run()) + + self.assertEqual(len(results), 0) + + def test_fail(self): + nasl_file = Path(__file__).parent / "test.nasl" + content = ( + "url1 = 'foo'\n" + "foreach url ( url ) {}\n" + "foreach url(make_list(url1,url)) {}\n" + ) + fake_context = self.create_file_plugin_context( + nasl_file=nasl_file, file_content=content + ) + plugin = CheckVariableRedefinitionInForeach(fake_context) + results = list(plugin.run()) + self.assertEqual(len(results), 2) + self.assertIsInstance(results[0], LinterWarning) + self.assertEqual( + "The variable 'url' is redefined " + "by being the identifier\nand the iterator in the" + " same foreach loop 'foreach url ( url )'", + results[0].message, + ) + self.assertEqual( + "The variable 'url' is used as identifier and\n" + "as part of the iterator in the" + " same foreach loop\n'foreach url(make_list(url1,url))'", + results[1].message, + ) diff --git a/troubadix/plugins/__init__.py b/troubadix/plugins/__init__.py index 91bea8cb..43bf9942 100644 --- a/troubadix/plugins/__init__.py +++ b/troubadix/plugins/__init__.py @@ -20,6 +20,9 @@ from troubadix.plugin import FilePlugin, FilesPlugin, Plugin from troubadix.plugins.multiple_re_parameters import CheckMultipleReParameters from troubadix.plugins.spaces_in_filename import CheckSpacesInFilename +from troubadix.plugins.variable_redefinition_in_foreach import ( + CheckVariableRedefinitionInForeach, +) from .badwords import CheckBadwords from .copyright_text import CheckCopyrightText @@ -138,6 +141,7 @@ CheckOverlongDescriptionLines, CheckSpacesInFilename, CheckMultipleReParameters, + CheckVariableRedefinitionInForeach, ] # plugins checking all files diff --git a/troubadix/plugins/variable_redefinition_in_foreach.py b/troubadix/plugins/variable_redefinition_in_foreach.py new file mode 100644 index 00000000..72aae448 --- /dev/null +++ b/troubadix/plugins/variable_redefinition_in_foreach.py @@ -0,0 +1,53 @@ +# SPDX-License-Identifier: GPL-3.0-or-later +# SPDX-FileCopyrightText: 2024 Greenbone AG + +import re +from pathlib import Path +from typing import Iterator + +from troubadix.plugin import FileContentPlugin, LinterResult, LinterWarning + +foreach_pattern = re.compile(r"foreach\s+(\w+)\s*\((.+)\)") +make_list_pattern = re.compile(r"^(?:make_list|make_list_unique)\((.+)\)$") + + +class CheckVariableRedefinitionInForeach(FileContentPlugin): + name = "check_variable_redefinition_in_foreach" + + def check_content( + self, nasl_file: Path, file_content: str + ) -> Iterator[LinterResult]: + """This plugin checks for a redefinition of the variable + that is passed to the foreach loop. + This can be caused by using same variable name + for both the list and the element being iterated over. + Incorrect uses of foreach loops that are covered by this plugin are: + foreach foo(foo){} + foreach foo(make_list(bar,foo)){} + foreach foo(make_list_unique(bar,foo)){} + """ + + for foreach_match in foreach_pattern.finditer(file_content): + identifier = foreach_match.group(1) + iterator = foreach_match.group(2).replace(" ", "") + + if make_list_match := make_list_pattern.fullmatch(iterator): + make_list_params = make_list_match.group(1).split(",") + if identifier in make_list_params: + yield LinterWarning( + f"The variable '{foreach_match.group(1)}' " + f"is used as identifier and\n" + f"as part of the iterator in the" + f" same foreach loop\n'{foreach_match.group()}'", + plugin=self.name, + file=nasl_file, + ) + else: + if identifier == iterator: + yield LinterWarning( + f"The variable '{foreach_match.group(1)}' is redefined " + f"by being the identifier\nand the iterator in the" + f" same foreach loop '{foreach_match.group()}'", + plugin=self.name, + file=nasl_file, + )