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
58 changes: 58 additions & 0 deletions slither/tools/mutator/mutators/RNM.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
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
from slither.visitors.expression.expression import ExpressionVisitor


class RemoveNegationVisitor(ExpressionVisitor):
def __init__(self, expression, mutator, result):
self._mutator = mutator
self._result = result

# Traverse the expression AST
super().__init__(expression)

def _post_unary_operation(self, expression: UnaryOperation):
# Mutate only logical negation (!)
if expression.type != UnaryOperationType.BANG:
return

if not expression.source_mapping:
return

operand = expression.expression

# Replace '!expr' with 'expr'
create_patch_with_line(
self._result,
self._mutator.in_file,
expression.source_mapping.start,
expression.source_mapping.start + expression.source_mapping.length,
expression.source_mapping.content,
operand.source_mapping.content,
expression.source_mapping.lines[0],
)


class RNM(AbstractMutator):
NAME = "RNM"
HELP = "Remove Negation"

def _mutate(self) -> dict:
result = {}

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

expression = getattr(node, "expression", None)
if expression is None:
continue

RemoveNegationVisitor(expression, self, result)

return result
1 change: 1 addition & 0 deletions slither/tools/mutator/mutators/all_mutators.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@
from slither.tools.mutator.mutators.ROR import ROR # severity medium
from slither.tools.mutator.mutators.RR import RR # severity high
from slither.tools.mutator.mutators.CR import CR # severity high
from slither.tools.mutator.mutators.RNM import RNM # severity medium
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,8 @@ contract Counter {
function restrictedIncrement() public onlyOwner {
number++;
}

function checkNegation(uint256 newNumber) public pure {
assert(!(newNumber != 7));
}
}
35 changes: 35 additions & 0 deletions tests/tools/mutator/test_mutator.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
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.RR import RR
from slither.tools.mutator.mutators.RNM import RNM


TEST_DATA_DIR = Path(__file__).resolve().parent / "test_data"
Expand Down Expand Up @@ -283,3 +284,37 @@ 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_rnm_mutates_negation(solc_binary_path):
"""Negations used by target function should be mutated"""
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 = RNM(
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"] == "!(newNumber != 7)" and patch["new_string"] == "(newNumber != 7)"
for patch in patches["patches"][file_path]
)
Loading