diff --git a/slither/tools/mutator/mutators/RCN.py b/slither/tools/mutator/mutators/RCN.py new file mode 100644 index 0000000000..7ff70c8aa6 --- /dev/null +++ b/slither/tools/mutator/mutators/RCN.py @@ -0,0 +1,63 @@ +from slither.core.declarations.solidity_variables import SolidityFunction +from slither.core.expressions.call_expression import CallExpression +from slither.core.expressions.identifier import Identifier +from slither.core.expressions.unary_operation import UnaryOperation, UnaryOperationType +from slither.tools.mutator.mutators.abstract_mutator import AbstractMutator +from slither.tools.mutator.utils.patch import create_patch_with_line + + +class RCN(AbstractMutator): + NAME = "RCN" + HELP = "Require Condition Negation" + + def _mutate(self) -> dict: + result: dict = {} + + for function in self.contract.functions_and_modifiers_declared: + if not self.should_mutate_function(function): + continue + + for node in function.nodes: + if not self.should_mutate_node(node): + continue + + try: + expression = node.expression + except AttributeError: + continue + + if not isinstance(expression, CallExpression): + continue + + if not isinstance(expression.called, Identifier): + continue + + called = expression.called.value + if not isinstance(called, SolidityFunction): + continue + + if not called.name.startswith("require("): + continue + + if not expression.arguments: + continue + + condition = expression.arguments[0] + + start = condition.source_mapping.start + stop = start + condition.source_mapping.length + old_str = condition.source_mapping.content + line_no = condition.source_mapping.lines[0] + new_str = f"!({old_str})" + + create_patch_with_line( + result, + self.in_file, + start, + stop, + old_str, + new_str, + line_no, + ) + + return result diff --git a/slither/tools/mutator/mutators/all_mutators.py b/slither/tools/mutator/mutators/all_mutators.py index e2ef20a4d3..b81ea3355c 100644 --- a/slither/tools/mutator/mutators/all_mutators.py +++ b/slither/tools/mutator/mutators/all_mutators.py @@ -11,5 +11,6 @@ from slither.tools.mutator.mutators.FHR import FHR # severity medium from slither.tools.mutator.mutators.MIA import MIA # severity medium from slither.tools.mutator.mutators.ROR import ROR # severity medium +from slither.tools.mutator.mutators.RCN import RCN # severity medium from slither.tools.mutator.mutators.RR import RR # severity high from slither.tools.mutator.mutators.CR import CR # severity high diff --git a/tests/tools/mutator/test_mutator.py b/tests/tools/mutator/test_mutator.py index 6f9af26999..0884983737 100644 --- a/tests/tools/mutator/test_mutator.py +++ b/tests/tools/mutator/test_mutator.py @@ -13,6 +13,7 @@ from slither.tools.mutator.utils.testing_generated_mutant import run_test_cmd from slither.tools.mutator.utils.file_handling import get_sol_file_list, backup_source_file from slither.utils.function import get_function_id +from slither.tools.mutator.mutators.RCN import RCN from slither.tools.mutator.mutators.RR import RR @@ -36,6 +37,10 @@ def test_get_mutators(): mutators = _get_mutators(None) assert mutators + mutators = _get_mutators(["RCN"]) + assert len(mutators) == 1 + assert mutators[0].NAME == "RCN" + mutators = _get_mutators(["ASOR"]) assert len(mutators) == 1 assert mutators[0].NAME == "ASOR" @@ -283,3 +288,35 @@ def test_should_mutate_function_includes_modifier(solc_binary_path): for mod in contract.modifiers: if mod.name == "onlyOwner": assert mutator.should_mutate_function(mod) is True + + +def test_rcn_mutates_require_condition(solc_binary_path): + solc_path = solc_binary_path("0.8.15") + file_path = (TEST_DATA_DIR / "test_source_unit" / "src" / "Counter.sol").as_posix() + sl = Slither(file_path, solc=solc_path, compile_force_framework="solc") + contract = next(c for c in sl.contracts if c.name == "Counter") + + with tempfile.TemporaryDirectory() as tmpdir: + mutator = RCN( + sl.compilation_units[0], + timeout=30, + testing_command="true", + testing_directory=None, + contract_instance=contract, + solc_remappings=None, + verbose=False, + output_folder=Path(tmpdir), + dont_mutate_line=[], + target_selectors=None, + target_modifiers=None, + ) + + patches = mutator._mutate() + assert "patches" in patches + assert file_path in patches["patches"] + + assert any( + patch["old_string"] == "msg.sender == owner" + and patch["new_string"] == "!(msg.sender == owner)" + for patch in patches["patches"][file_path] + )