mirror of https://github.com/crytic/slither
commit
044c6beb1c
@ -0,0 +1,33 @@ |
||||
# Slither-mutate |
||||
|
||||
`slither-mutate` is a mutation testing tool for solidity based smart contracts. |
||||
|
||||
## Usage |
||||
|
||||
`slither-mutate <codebase> --test-cmd <test-command> <options>` |
||||
|
||||
To view the list of mutators available `slither-mutate --list-mutators` |
||||
|
||||
### CLI Interface |
||||
|
||||
```shell |
||||
positional arguments: |
||||
codebase Codebase to analyze (.sol file, project directory, ...) |
||||
|
||||
options: |
||||
-h, --help show this help message and exit |
||||
--list-mutators List available detectors |
||||
--test-cmd TEST_CMD Command to run the tests for your project |
||||
--test-dir TEST_DIR Tests directory |
||||
--ignore-dirs IGNORE_DIRS |
||||
Directories to ignore |
||||
--timeout TIMEOUT Set timeout for test command (by default 30 seconds) |
||||
--output-dir OUTPUT_DIR |
||||
Name of output directory (by default 'mutation_campaign') |
||||
--verbose output all mutants generated |
||||
--mutators-to-run MUTATORS_TO_RUN |
||||
mutant generators to run |
||||
--contract-names CONTRACT_NAMES |
||||
list of contract names you want to mutate |
||||
--quick to stop full mutation if revert mutator passes |
||||
``` |
@ -0,0 +1,54 @@ |
||||
from typing import Dict |
||||
from slither.slithir.operations import Binary, BinaryType |
||||
from slither.tools.mutator.utils.patch import create_patch_with_line |
||||
from slither.tools.mutator.mutators.abstract_mutator import AbstractMutator |
||||
from slither.core.expressions.unary_operation import UnaryOperation |
||||
|
||||
arithmetic_operators = [ |
||||
BinaryType.ADDITION, |
||||
BinaryType.DIVISION, |
||||
BinaryType.MULTIPLICATION, |
||||
BinaryType.SUBTRACTION, |
||||
BinaryType.MODULO, |
||||
] |
||||
|
||||
|
||||
class AOR(AbstractMutator): # pylint: disable=too-few-public-methods |
||||
NAME = "AOR" |
||||
HELP = "Arithmetic operator replacement" |
||||
|
||||
def _mutate(self) -> Dict: |
||||
result: Dict = {} |
||||
for ( # pylint: disable=too-many-nested-blocks |
||||
function |
||||
) in self.contract.functions_and_modifiers_declared: |
||||
for node in function.nodes: |
||||
try: |
||||
ir_expression = node.expression |
||||
except: # pylint: disable=bare-except |
||||
continue |
||||
for ir in node.irs: |
||||
if isinstance(ir, Binary) and ir.type in arithmetic_operators: |
||||
if isinstance(ir_expression, UnaryOperation): |
||||
continue |
||||
alternative_ops = arithmetic_operators[:] |
||||
alternative_ops.remove(ir.type) |
||||
for op in alternative_ops: |
||||
# Get the string |
||||
start = node.source_mapping.start |
||||
stop = start + node.source_mapping.length |
||||
old_str = self.in_file_str[start:stop] |
||||
line_no = node.source_mapping.lines |
||||
if not line_no[0] in self.dont_mutate_line: |
||||
# Replace the expression with true |
||||
new_str = f"{old_str.split(ir.type.value)[0]}{op.value}{old_str.split(ir.type.value)[1]}" |
||||
create_patch_with_line( |
||||
result, |
||||
self.in_file, |
||||
start, |
||||
stop, |
||||
old_str, |
||||
new_str, |
||||
line_no[0], |
||||
) |
||||
return result |
@ -0,0 +1,65 @@ |
||||
from typing import Dict |
||||
from slither.tools.mutator.utils.patch import create_patch_with_line |
||||
from slither.tools.mutator.mutators.abstract_mutator import AbstractMutator |
||||
from slither.core.expressions.assignment_operation import ( |
||||
AssignmentOperationType, |
||||
AssignmentOperation, |
||||
) |
||||
|
||||
assignment_operators = [ |
||||
AssignmentOperationType.ASSIGN_ADDITION, |
||||
AssignmentOperationType.ASSIGN_SUBTRACTION, |
||||
AssignmentOperationType.ASSIGN, |
||||
AssignmentOperationType.ASSIGN_OR, |
||||
AssignmentOperationType.ASSIGN_CARET, |
||||
AssignmentOperationType.ASSIGN_AND, |
||||
AssignmentOperationType.ASSIGN_LEFT_SHIFT, |
||||
AssignmentOperationType.ASSIGN_RIGHT_SHIFT, |
||||
AssignmentOperationType.ASSIGN_MULTIPLICATION, |
||||
AssignmentOperationType.ASSIGN_DIVISION, |
||||
AssignmentOperationType.ASSIGN_MODULO, |
||||
] |
||||
|
||||
|
||||
class ASOR(AbstractMutator): # pylint: disable=too-few-public-methods |
||||
NAME = "ASOR" |
||||
HELP = "Assignment Operator Replacement" |
||||
|
||||
def _mutate(self) -> Dict: |
||||
result: Dict = {} |
||||
|
||||
for ( # pylint: disable=too-many-nested-blocks |
||||
function |
||||
) in self.contract.functions_and_modifiers_declared: |
||||
for node in function.nodes: |
||||
for ir in node.irs: |
||||
if ( |
||||
isinstance(ir.expression, AssignmentOperation) |
||||
and ir.expression.type in assignment_operators |
||||
): |
||||
if ir.expression.type == AssignmentOperationType.ASSIGN: |
||||
continue |
||||
alternative_ops = assignment_operators[:] |
||||
try: |
||||
alternative_ops.remove(ir.expression.type) |
||||
except: # pylint: disable=bare-except |
||||
continue |
||||
for op in alternative_ops: |
||||
if op != ir.expression: |
||||
start = node.source_mapping.start |
||||
stop = start + node.source_mapping.length |
||||
old_str = self.in_file_str[start:stop] |
||||
line_no = node.source_mapping.lines |
||||
if not line_no[0] in self.dont_mutate_line: |
||||
# Replace the expression with true |
||||
new_str = f"{old_str.split(str(ir.expression.type))[0]}{op}{old_str.split(str(ir.expression.type))[1]}" |
||||
create_patch_with_line( |
||||
result, |
||||
self.in_file, |
||||
start, |
||||
stop, |
||||
old_str, |
||||
new_str, |
||||
line_no[0], |
||||
) |
||||
return result |
@ -0,0 +1,48 @@ |
||||
from typing import Dict |
||||
from slither.slithir.operations import Binary, BinaryType |
||||
from slither.tools.mutator.utils.patch import create_patch_with_line |
||||
from slither.tools.mutator.mutators.abstract_mutator import AbstractMutator |
||||
|
||||
bitwise_operators = [ |
||||
BinaryType.AND, |
||||
BinaryType.OR, |
||||
BinaryType.LEFT_SHIFT, |
||||
BinaryType.RIGHT_SHIFT, |
||||
BinaryType.CARET, |
||||
] |
||||
|
||||
|
||||
class BOR(AbstractMutator): # pylint: disable=too-few-public-methods |
||||
NAME = "BOR" |
||||
HELP = "Bitwise Operator Replacement" |
||||
|
||||
def _mutate(self) -> Dict: |
||||
result: Dict = {} |
||||
|
||||
for ( # pylint: disable=too-many-nested-blocks |
||||
function |
||||
) in self.contract.functions_and_modifiers_declared: |
||||
for node in function.nodes: |
||||
for ir in node.irs: |
||||
if isinstance(ir, Binary) and ir.type in bitwise_operators: |
||||
alternative_ops = bitwise_operators[:] |
||||
alternative_ops.remove(ir.type) |
||||
for op in alternative_ops: |
||||
# Get the string |
||||
start = node.source_mapping.start |
||||
stop = start + node.source_mapping.length |
||||
old_str = self.in_file_str[start:stop] |
||||
line_no = node.source_mapping.lines |
||||
if not line_no[0] in self.dont_mutate_line: |
||||
# Replace the expression with true |
||||
new_str = f"{old_str.split(ir.type.value)[0]}{op.value}{old_str.split(ir.type.value)[1]}" |
||||
create_patch_with_line( |
||||
result, |
||||
self.in_file, |
||||
start, |
||||
stop, |
||||
old_str, |
||||
new_str, |
||||
line_no[0], |
||||
) |
||||
return result |
@ -0,0 +1,39 @@ |
||||
from typing import Dict |
||||
from slither.core.cfg.node import NodeType |
||||
from slither.tools.mutator.utils.patch import create_patch_with_line |
||||
from slither.tools.mutator.mutators.abstract_mutator import AbstractMutator |
||||
|
||||
|
||||
class CR(AbstractMutator): # pylint: disable=too-few-public-methods |
||||
NAME = "CR" |
||||
HELP = "Comment Replacement" |
||||
|
||||
def _mutate(self) -> Dict: |
||||
result: Dict = {} |
||||
|
||||
for ( # pylint: disable=too-many-nested-blocks |
||||
function |
||||
) in self.contract.functions_and_modifiers_declared: |
||||
for node in function.nodes: |
||||
if node.type not in ( |
||||
NodeType.ENTRYPOINT, |
||||
NodeType.ENDIF, |
||||
NodeType.ENDLOOP, |
||||
): |
||||
# Get the string |
||||
start = node.source_mapping.start |
||||
stop = start + node.source_mapping.length |
||||
old_str = self.in_file_str[start:stop] |
||||
line_no = node.source_mapping.lines |
||||
if not line_no[0] in self.dont_mutate_line: |
||||
new_str = "//" + old_str |
||||
create_patch_with_line( |
||||
result, |
||||
self.in_file, |
||||
start, |
||||
stop, |
||||
old_str, |
||||
new_str, |
||||
line_no[0], |
||||
) |
||||
return result |
@ -0,0 +1,42 @@ |
||||
from typing import Dict |
||||
import re |
||||
from slither.tools.mutator.utils.patch import create_patch_with_line |
||||
from slither.tools.mutator.mutators.abstract_mutator import AbstractMutator |
||||
|
||||
|
||||
function_header_replacements = [ |
||||
"pure ==> view", |
||||
"view ==> pure", |
||||
"(\\s)(external|public|internal) ==> \\1private", |
||||
"(\\s)(external|public) ==> \\1internal", |
||||
] |
||||
|
||||
|
||||
class FHR(AbstractMutator): # pylint: disable=too-few-public-methods |
||||
NAME = "FHR" |
||||
HELP = "Function Header Replacement" |
||||
|
||||
def _mutate(self) -> Dict: |
||||
result: Dict = {} |
||||
|
||||
for function in self.contract.functions_and_modifiers_declared: |
||||
start = function.source_mapping.start |
||||
stop = start + function.source_mapping.content.find("{") |
||||
old_str = self.in_file_str[start:stop] |
||||
line_no = function.source_mapping.lines |
||||
if not line_no[0] in self.dont_mutate_line: |
||||
for value in function_header_replacements: |
||||
left_value = value.split(" ==> ", maxsplit=1)[0] |
||||
right_value = value.split(" ==> ")[1] |
||||
if re.search(re.compile(left_value), old_str) is not None: |
||||
new_str = re.sub(re.compile(left_value), right_value, old_str) |
||||
create_patch_with_line( |
||||
result, |
||||
self.in_file, |
||||
start, |
||||
stop, |
||||
old_str, |
||||
new_str, |
||||
line_no[0], |
||||
) |
||||
return result |
@ -0,0 +1,86 @@ |
||||
from typing import Dict |
||||
from slither.core.expressions import Literal |
||||
from slither.core.variables.variable import Variable |
||||
from slither.tools.mutator.mutators.abstract_mutator import AbstractMutator |
||||
from slither.tools.mutator.utils.patch import create_patch_with_line |
||||
from slither.core.solidity_types import ElementaryType |
||||
|
||||
literal_replacements = [] |
||||
|
||||
|
||||
class LIR(AbstractMutator): # pylint: disable=too-few-public-methods |
||||
NAME = "LIR" |
||||
HELP = "Literal Interger Replacement" |
||||
|
||||
def _mutate(self) -> Dict: # pylint: disable=too-many-branches |
||||
result: Dict = {} |
||||
variable: Variable |
||||
|
||||
# Create fault for state variables declaration |
||||
for ( # pylint: disable=too-many-nested-blocks |
||||
variable |
||||
) in self.contract.state_variables_declared: |
||||
if variable.initialized: |
||||
# Cannot remove the initialization of constant variables |
||||
if variable.is_constant: |
||||
continue |
||||
|
||||
if isinstance(variable.expression, Literal): |
||||
if isinstance(variable.type, ElementaryType): |
||||
literal_replacements.append(variable.type.min) # append data type min value |
||||
literal_replacements.append(variable.type.max) # append data type max value |
||||
if str(variable.type).startswith("uint"): |
||||
literal_replacements.append("1") |
||||
elif str(variable.type).startswith("uint"): |
||||
literal_replacements.append("-1") |
||||
# Get the string |
||||
start = variable.source_mapping.start |
||||
stop = start + variable.source_mapping.length |
||||
old_str = self.in_file_str[start:stop] |
||||
line_no = variable.node_initialization.source_mapping.lines |
||||
if not line_no[0] in self.dont_mutate_line: |
||||
for value in literal_replacements: |
||||
old_value = old_str[old_str.find("=") + 1 :].strip() |
||||
if old_value != value: |
||||
new_str = f"{old_str.split('=')[0]}= {value}" |
||||
create_patch_with_line( |
||||
result, |
||||
self.in_file, |
||||
start, |
||||
stop, |
||||
old_str, |
||||
new_str, |
||||
line_no[0], |
||||
) |
||||
|
||||
for ( # pylint: disable=too-many-nested-blocks |
||||
function |
||||
) in self.contract.functions_and_modifiers_declared: |
||||
for variable in function.local_variables: |
||||
if variable.initialized and isinstance(variable.expression, Literal): |
||||
if isinstance(variable.type, ElementaryType): |
||||
literal_replacements.append(variable.type.min) |
||||
literal_replacements.append(variable.type.max) |
||||
if str(variable.type).startswith("uint"): |
||||
literal_replacements.append("1") |
||||
elif str(variable.type).startswith("uint"): |
||||
literal_replacements.append("-1") |
||||
start = variable.source_mapping.start |
||||
stop = start + variable.source_mapping.length |
||||
old_str = self.in_file_str[start:stop] |
||||
line_no = variable.source_mapping.lines |
||||
if not line_no[0] in self.dont_mutate_line: |
||||
for new_value in literal_replacements: |
||||
old_value = old_str[old_str.find("=") + 1 :].strip() |
||||
if old_value != new_value: |
||||
new_str = f"{old_str.split('=')[0]}= {new_value}" |
||||
create_patch_with_line( |
||||
result, |
||||
self.in_file, |
||||
start, |
||||
stop, |
||||
old_str, |
||||
new_str, |
||||
line_no[0], |
||||
) |
||||
return result |
@ -0,0 +1,46 @@ |
||||
from typing import Dict |
||||
from slither.slithir.operations import Binary, BinaryType |
||||
from slither.tools.mutator.utils.patch import create_patch_with_line |
||||
from slither.tools.mutator.mutators.abstract_mutator import AbstractMutator |
||||
|
||||
logical_operators = [ |
||||
BinaryType.OROR, |
||||
BinaryType.ANDAND, |
||||
] |
||||
|
||||
|
||||
class LOR(AbstractMutator): # pylint: disable=too-few-public-methods |
||||
NAME = "LOR" |
||||
HELP = "Logical Operator Replacement" |
||||
|
||||
def _mutate(self) -> Dict: |
||||
result: Dict = {} |
||||
|
||||
for ( # pylint: disable=too-many-nested-blocks |
||||
function |
||||
) in self.contract.functions_and_modifiers_declared: |
||||
for node in function.nodes: |
||||
for ir in node.irs: |
||||
if isinstance(ir, Binary) and ir.type in logical_operators: |
||||
alternative_ops = logical_operators[:] |
||||
alternative_ops.remove(ir.type) |
||||
|
||||
for op in alternative_ops: |
||||
# Get the string |
||||
start = node.source_mapping.start |
||||
stop = start + node.source_mapping.length |
||||
old_str = self.in_file_str[start:stop] |
||||
line_no = node.source_mapping.lines |
||||
if not line_no[0] in self.dont_mutate_line: |
||||
# Replace the expression with true |
||||
new_str = f"{old_str.split(ir.type.value)[0]} {op.value} {old_str.split(ir.type.value)[1]}" |
||||
create_patch_with_line( |
||||
result, |
||||
self.in_file, |
||||
start, |
||||
stop, |
||||
old_str, |
||||
new_str, |
||||
line_no[0], |
||||
) |
||||
return result |
@ -1,39 +1,47 @@ |
||||
from typing import Dict |
||||
|
||||
from slither.core.cfg.node import NodeType |
||||
from slither.formatters.utils.patches import create_patch |
||||
from slither.tools.mutator.mutators.abstract_mutator import AbstractMutator, FaultNature, FaultClass |
||||
from slither.tools.mutator.utils.patch import create_patch_with_line |
||||
from slither.tools.mutator.mutators.abstract_mutator import AbstractMutator |
||||
from slither.core.expressions.unary_operation import UnaryOperationType, UnaryOperation |
||||
|
||||
|
||||
class MIA(AbstractMutator): # pylint: disable=too-few-public-methods |
||||
NAME = "MIA" |
||||
HELP = '"if" construct around statement' |
||||
FAULTCLASS = FaultClass.Checking |
||||
FAULTNATURE = FaultNature.Missing |
||||
|
||||
def _mutate(self) -> Dict: |
||||
|
||||
result: Dict = {} |
||||
|
||||
for contract in self.slither.contracts: |
||||
|
||||
for function in contract.functions_declared + list(contract.modifiers_declared): |
||||
|
||||
for node in function.nodes: |
||||
if node.type == NodeType.IF: |
||||
# Retrieve the file |
||||
in_file = contract.source_mapping.filename.absolute |
||||
# Retrieve the source code |
||||
in_file_str = contract.compilation_unit.core.source_code[in_file] |
||||
|
||||
# Get the string |
||||
start = node.source_mapping.start |
||||
stop = start + node.source_mapping.length |
||||
old_str = in_file_str[start:stop] |
||||
|
||||
# Replace the expression with true |
||||
new_str = "true" |
||||
|
||||
create_patch(result, in_file, start, stop, old_str, new_str) |
||||
|
||||
for function in self.contract.functions_and_modifiers_declared: |
||||
for node in function.nodes: |
||||
if node.type == NodeType.IF: |
||||
# Get the string |
||||
start = node.expression.source_mapping.start |
||||
stop = start + node.expression.source_mapping.length |
||||
old_str = self.in_file_str[start:stop] |
||||
line_no = node.source_mapping.lines |
||||
if not line_no[0] in self.dont_mutate_line: |
||||
# Replace the expression with true and false |
||||
for value in ["true", "false"]: |
||||
new_str = value |
||||
create_patch_with_line( |
||||
result, |
||||
self.in_file, |
||||
start, |
||||
stop, |
||||
old_str, |
||||
new_str, |
||||
line_no[0], |
||||
) |
||||
|
||||
if not isinstance(node.expression, UnaryOperation): |
||||
new_str = str(UnaryOperationType.BANG) + "(" + old_str + ")" |
||||
create_patch_with_line( |
||||
result, |
||||
self.in_file, |
||||
start, |
||||
stop, |
||||
old_str, |
||||
new_str, |
||||
line_no[0], |
||||
) |
||||
return result |
||||
|
@ -1,36 +1,60 @@ |
||||
from typing import Dict |
||||
|
||||
from slither.core.expressions import Literal |
||||
from slither.core.variables.variable import Variable |
||||
from slither.tools.mutator.mutators.abstract_mutator import AbstractMutator, FaultNature, FaultClass |
||||
from slither.tools.mutator.utils.generic_patching import remove_assignement |
||||
from slither.tools.mutator.mutators.abstract_mutator import AbstractMutator |
||||
from slither.tools.mutator.utils.patch import create_patch_with_line |
||||
|
||||
|
||||
class MVIE(AbstractMutator): # pylint: disable=too-few-public-methods |
||||
NAME = "MVIE" |
||||
HELP = "variable initialization using an expression" |
||||
FAULTCLASS = FaultClass.Assignement |
||||
FAULTNATURE = FaultNature.Missing |
||||
|
||||
def _mutate(self) -> Dict: |
||||
|
||||
result: Dict = {} |
||||
variable: Variable |
||||
for contract in self.slither.contracts: |
||||
|
||||
# Create fault for state variables declaration |
||||
for variable in contract.state_variables_declared: |
||||
if variable.initialized: |
||||
# Cannot remove the initialization of constant variables |
||||
if variable.is_constant: |
||||
continue |
||||
|
||||
if not isinstance(variable.expression, Literal): |
||||
remove_assignement(variable, contract, result) |
||||
|
||||
for function in contract.functions_declared + list(contract.modifiers_declared): |
||||
for variable in function.local_variables: |
||||
if variable.initialized and not isinstance(variable.expression, Literal): |
||||
remove_assignement(variable, contract, result) |
||||
|
||||
# Create fault for state variables declaration |
||||
for variable in self.contract.state_variables_declared: |
||||
if variable.initialized: |
||||
# Cannot remove the initialization of constant variables |
||||
if variable.is_constant: |
||||
continue |
||||
|
||||
if not isinstance(variable.expression, Literal): |
||||
# Get the string |
||||
start = variable.source_mapping.start |
||||
stop = variable.expression.source_mapping.start |
||||
old_str = self.in_file_str[start:stop] |
||||
new_str = old_str[: old_str.find("=")] |
||||
line_no = variable.node_initialization.source_mapping.lines |
||||
if not line_no[0] in self.dont_mutate_line: |
||||
create_patch_with_line( |
||||
result, |
||||
self.in_file, |
||||
start, |
||||
stop + variable.expression.source_mapping.length, |
||||
old_str, |
||||
new_str, |
||||
line_no[0], |
||||
) |
||||
|
||||
for function in self.contract.functions_and_modifiers_declared: |
||||
for variable in function.local_variables: |
||||
if variable.initialized and not isinstance(variable.expression, Literal): |
||||
# Get the string |
||||
start = variable.source_mapping.start |
||||
stop = variable.expression.source_mapping.start |
||||
old_str = self.in_file_str[start:stop] |
||||
new_str = old_str[: old_str.find("=")] |
||||
line_no = variable.source_mapping.lines |
||||
if not line_no[0] in self.dont_mutate_line: |
||||
create_patch_with_line( |
||||
result, |
||||
self.in_file, |
||||
start, |
||||
stop + variable.expression.source_mapping.length, |
||||
old_str, |
||||
new_str, |
||||
line_no[0], |
||||
) |
||||
return result |
||||
|
@ -1,37 +1,59 @@ |
||||
from typing import Dict |
||||
|
||||
from slither.core.expressions import Literal |
||||
from slither.core.variables.variable import Variable |
||||
from slither.tools.mutator.mutators.abstract_mutator import AbstractMutator, FaultNature, FaultClass |
||||
from slither.tools.mutator.utils.generic_patching import remove_assignement |
||||
from slither.tools.mutator.mutators.abstract_mutator import AbstractMutator |
||||
from slither.tools.mutator.utils.patch import create_patch_with_line |
||||
|
||||
|
||||
class MVIV(AbstractMutator): # pylint: disable=too-few-public-methods |
||||
NAME = "MVIV" |
||||
HELP = "variable initialization using a value" |
||||
FAULTCLASS = FaultClass.Assignement |
||||
FAULTNATURE = FaultNature.Missing |
||||
|
||||
def _mutate(self) -> Dict: |
||||
|
||||
result: Dict = {} |
||||
variable: Variable |
||||
|
||||
for contract in self.slither.contracts: |
||||
|
||||
# Create fault for state variables declaration |
||||
for variable in contract.state_variables_declared: |
||||
if variable.initialized: |
||||
# Cannot remove the initialization of constant variables |
||||
if variable.is_constant: |
||||
continue |
||||
|
||||
if isinstance(variable.expression, Literal): |
||||
remove_assignement(variable, contract, result) |
||||
|
||||
for function in contract.functions_declared + list(contract.modifiers_declared): |
||||
for variable in function.local_variables: |
||||
if variable.initialized and isinstance(variable.expression, Literal): |
||||
remove_assignement(variable, contract, result) |
||||
|
||||
# Create fault for state variables declaration |
||||
for variable in self.contract.state_variables_declared: |
||||
if variable.initialized: |
||||
# Cannot remove the initialization of constant variables |
||||
if variable.is_constant: |
||||
continue |
||||
|
||||
if isinstance(variable.expression, Literal): |
||||
# Get the string |
||||
start = variable.source_mapping.start |
||||
stop = variable.expression.source_mapping.start |
||||
old_str = self.in_file_str[start:stop] |
||||
new_str = old_str[: old_str.find("=")] |
||||
line_no = variable.node_initialization.source_mapping.lines |
||||
if not line_no[0] in self.dont_mutate_line: |
||||
create_patch_with_line( |
||||
result, |
||||
self.in_file, |
||||
start, |
||||
stop + variable.expression.source_mapping.length, |
||||
old_str, |
||||
new_str, |
||||
line_no[0], |
||||
) |
||||
|
||||
for function in self.contract.functions_and_modifiers_declared: |
||||
for variable in function.local_variables: |
||||
if variable.initialized and isinstance(variable.expression, Literal): |
||||
start = variable.source_mapping.start |
||||
stop = variable.expression.source_mapping.start |
||||
old_str = self.in_file_str[start:stop] |
||||
new_str = old_str[: old_str.find("=")] |
||||
line_no = variable.source_mapping.lines |
||||
if not line_no[0] in self.dont_mutate_line: |
||||
create_patch_with_line( |
||||
result, |
||||
self.in_file, |
||||
start, |
||||
stop + variable.expression.source_mapping.length, |
||||
old_str, |
||||
new_str, |
||||
line_no[0], |
||||
) |
||||
return result |
||||
|
@ -0,0 +1,35 @@ |
||||
from typing import Dict |
||||
from slither.core.cfg.node import NodeType |
||||
from slither.tools.mutator.utils.patch import create_patch_with_line |
||||
from slither.tools.mutator.mutators.abstract_mutator import AbstractMutator |
||||
from slither.core.expressions.unary_operation import UnaryOperationType, UnaryOperation |
||||
|
||||
|
||||
class MWA(AbstractMutator): # pylint: disable=too-few-public-methods |
||||
NAME = "MWA" |
||||
HELP = '"while" construct around statement' |
||||
|
||||
def _mutate(self) -> Dict: |
||||
result: Dict = {} |
||||
|
||||
for function in self.contract.functions_and_modifiers_declared: |
||||
for node in function.nodes: |
||||
if node.type == NodeType.IFLOOP: |
||||
# Get the string |
||||
start = node.source_mapping.start |
||||
stop = start + node.source_mapping.length |
||||
old_str = self.in_file_str[start:stop] |
||||
line_no = node.source_mapping.lines |
||||
if not line_no[0] in self.dont_mutate_line: |
||||
if not isinstance(node.expression, UnaryOperation): |
||||
new_str = str(UnaryOperationType.BANG) + "(" + old_str + ")" |
||||
create_patch_with_line( |
||||
result, |
||||
self.in_file, |
||||
start, |
||||
stop, |
||||
old_str, |
||||
new_str, |
||||
line_no[0], |
||||
) |
||||
return result |
@ -0,0 +1,53 @@ |
||||
from typing import Dict |
||||
from slither.slithir.operations import Binary, BinaryType |
||||
from slither.tools.mutator.utils.patch import create_patch_with_line |
||||
from slither.tools.mutator.mutators.abstract_mutator import AbstractMutator |
||||
|
||||
relational_operators = [ |
||||
BinaryType.LESS, |
||||
BinaryType.GREATER, |
||||
BinaryType.LESS_EQUAL, |
||||
BinaryType.GREATER_EQUAL, |
||||
BinaryType.EQUAL, |
||||
BinaryType.NOT_EQUAL, |
||||
] |
||||
|
||||
|
||||
class ROR(AbstractMutator): # pylint: disable=too-few-public-methods |
||||
NAME = "ROR" |
||||
HELP = "Relational Operator Replacement" |
||||
|
||||
def _mutate(self) -> Dict: |
||||
result: Dict = {} |
||||
|
||||
for ( # pylint: disable=too-many-nested-blocks |
||||
function |
||||
) in self.contract.functions_and_modifiers_declared: |
||||
for node in function.nodes: |
||||
for ir in node.irs: |
||||
if isinstance(ir, Binary) and ir.type in relational_operators: |
||||
if ( |
||||
str(ir.variable_left.type) != "address" |
||||
and str(ir.variable_right) != "address" |
||||
): |
||||
alternative_ops = relational_operators[:] |
||||
alternative_ops.remove(ir.type) |
||||
for op in alternative_ops: |
||||
# Get the string |
||||
start = ir.expression.source_mapping.start |
||||
stop = start + ir.expression.source_mapping.length |
||||
old_str = self.in_file_str[start:stop] |
||||
line_no = node.source_mapping.lines |
||||
if not line_no[0] in self.dont_mutate_line: |
||||
# Replace the expression with true |
||||
new_str = f"{old_str.split(ir.type.value)[0]} {op.value} {old_str.split(ir.type.value)[1]}" |
||||
create_patch_with_line( |
||||
result, |
||||
self.in_file, |
||||
start, |
||||
stop, |
||||
old_str, |
||||
new_str, |
||||
line_no[0], |
||||
) |
||||
return result |
@ -0,0 +1,38 @@ |
||||
from typing import Dict |
||||
from slither.core.cfg.node import NodeType |
||||
from slither.tools.mutator.utils.patch import create_patch_with_line |
||||
from slither.tools.mutator.mutators.abstract_mutator import AbstractMutator |
||||
|
||||
|
||||
class RR(AbstractMutator): # pylint: disable=too-few-public-methods |
||||
NAME = "RR" |
||||
HELP = "Revert Replacement" |
||||
|
||||
def _mutate(self) -> Dict: |
||||
result: Dict = {} |
||||
|
||||
for function in self.contract.functions_and_modifiers_declared: |
||||
for node in function.nodes: |
||||
if node.type not in ( |
||||
NodeType.ENTRYPOINT, |
||||
NodeType.ENDIF, |
||||
NodeType.ENDLOOP, |
||||
): |
||||
# Get the string |
||||
start = node.source_mapping.start |
||||
stop = start + node.source_mapping.length |
||||
old_str = self.in_file_str[start:stop] |
||||
line_no = node.source_mapping.lines |
||||
if not line_no[0] in self.dont_mutate_line: |
||||
if old_str != "revert()": |
||||
new_str = "revert()" |
||||
create_patch_with_line( |
||||
result, |
||||
self.in_file, |
||||
start, |
||||
stop, |
||||
old_str, |
||||
new_str, |
||||
line_no[0], |
||||
) |
||||
return result |
@ -0,0 +1,109 @@ |
||||
from typing import Dict |
||||
import re |
||||
from slither.core.cfg.node import NodeType |
||||
from slither.tools.mutator.utils.patch import create_patch_with_line |
||||
from slither.tools.mutator.mutators.abstract_mutator import AbstractMutator |
||||
from slither.core.variables.variable import Variable |
||||
|
||||
solidity_rules = [ |
||||
"abi\\.encode\\( ==> abi.encodePacked(", |
||||
"abi\\.encodePacked\\( ==> abi.encode(", |
||||
"\\.call([({]) ==> .delegatecall\\1", |
||||
"\\.call([({]) ==> .staticcall\\1", |
||||
"\\.delegatecall([({]) ==> .call\\1", |
||||
"\\.delegatecall([({]) ==> .staticcall\\1", |
||||
"\\.staticcall([({]) ==> .delegatecall\\1", |
||||
"\\.staticcall([({]) ==> .call\\1", |
||||
"^now$ ==> 0", |
||||
"block.timestamp ==> 0", |
||||
"msg.value ==> 0", |
||||
"msg.value ==> 1", |
||||
"(\\s)(wei|gwei) ==> \\1ether", |
||||
"(\\s)(ether|gwei) ==> \\1wei", |
||||
"(\\s)(wei|ether) ==> \\1gwei", |
||||
"(\\s)(minutes|days|hours|weeks) ==> \\1seconds", |
||||
"(\\s)(seconds|days|hours|weeks) ==> \\1minutes", |
||||
"(\\s)(seconds|minutes|hours|weeks) ==> \\1days", |
||||
"(\\s)(seconds|minutes|days|weeks) ==> \\1hours", |
||||
"(\\s)(seconds|minutes|days|hours) ==> \\1weeks", |
||||
"(\\s)(memory) ==> \\1storage", |
||||
"(\\s)(storage) ==> \\1memory", |
||||
"(\\s)(constant) ==> \\1immutable", |
||||
"addmod ==> mulmod", |
||||
"mulmod ==> addmod", |
||||
"msg.sender ==> tx.origin", |
||||
"tx.origin ==> msg.sender", |
||||
"([^u])fixed ==> \\1ufixed", |
||||
"ufixed ==> fixed", |
||||
"(u?)int16 ==> \\1int8", |
||||
"(u?)int32 ==> \\1int16", |
||||
"(u?)int64 ==> \\1int32", |
||||
"(u?)int128 ==> \\1int64", |
||||
"(u?)int256 ==> \\1int128", |
||||
"while ==> if", |
||||
] |
||||
|
||||
|
||||
class SBR(AbstractMutator): # pylint: disable=too-few-public-methods |
||||
NAME = "SBR" |
||||
HELP = "Solidity Based Replacement" |
||||
|
||||
def _mutate(self) -> Dict: |
||||
result: Dict = {} |
||||
variable: Variable |
||||
|
||||
for ( # pylint: disable=too-many-nested-blocks |
||||
function |
||||
) in self.contract.functions_and_modifiers_declared: |
||||
for node in function.nodes: |
||||
if node.type not in ( |
||||
NodeType.ENTRYPOINT, |
||||
NodeType.ENDIF, |
||||
NodeType.ENDLOOP, |
||||
): |
||||
# Get the string |
||||
start = node.source_mapping.start |
||||
stop = start + node.source_mapping.length |
||||
old_str = self.in_file_str[start:stop] |
||||
line_no = node.source_mapping.lines |
||||
if not line_no[0] in self.dont_mutate_line: |
||||
for value in solidity_rules: |
||||
left_value = value.split(" ==> ", maxsplit=1)[0] |
||||
right_value = value.split(" ==> ")[1] |
||||
if re.search(re.compile(left_value), old_str) is not None: |
||||
new_str = re.sub(re.compile(left_value), right_value, old_str) |
||||
create_patch_with_line( |
||||
result, |
||||
self.in_file, |
||||
start, |
||||
stop, |
||||
old_str, |
||||
new_str, |
||||
line_no[0], |
||||
) |
||||
|
||||
for ( # pylint: disable=too-many-nested-blocks |
||||
variable |
||||
) in self.contract.state_variables_declared: |
||||
node = variable.node_initialization |
||||
if node: |
||||
start = node.source_mapping.start |
||||
stop = start + node.source_mapping.length |
||||
old_str = self.in_file_str[start:stop] |
||||
line_no = node.source_mapping.lines |
||||
if not line_no[0] in self.dont_mutate_line: |
||||
for value in solidity_rules: |
||||
left_value = value.split(" ==> ", maxsplit=1)[0] |
||||
right_value = value.split(" ==> ")[1] |
||||
if re.search(re.compile(left_value), old_str) is not None: |
||||
new_str = re.sub(re.compile(left_value), right_value, old_str) |
||||
create_patch_with_line( |
||||
result, |
||||
self.in_file, |
||||
start, |
||||
stop, |
||||
old_str, |
||||
new_str, |
||||
line_no[0], |
||||
) |
||||
return result |
@ -0,0 +1,88 @@ |
||||
from typing import Dict |
||||
from slither.core.expressions.unary_operation import UnaryOperationType, UnaryOperation |
||||
from slither.tools.mutator.utils.patch import create_patch_with_line |
||||
from slither.tools.mutator.mutators.abstract_mutator import AbstractMutator |
||||
|
||||
unary_operators = [ |
||||
UnaryOperationType.PLUSPLUS_PRE, |
||||
UnaryOperationType.MINUSMINUS_PRE, |
||||
UnaryOperationType.PLUSPLUS_POST, |
||||
UnaryOperationType.MINUSMINUS_POST, |
||||
UnaryOperationType.MINUS_PRE, |
||||
] |
||||
|
||||
|
||||
class UOR(AbstractMutator): # pylint: disable=too-few-public-methods |
||||
NAME = "UOR" |
||||
HELP = "Unary Operator Replacement" |
||||
|
||||
def _mutate(self) -> Dict: |
||||
result: Dict = {} |
||||
|
||||
for ( # pylint: disable=too-many-nested-blocks |
||||
function |
||||
) in self.contract.functions_and_modifiers_declared: |
||||
for node in function.nodes: |
||||
try: |
||||
ir_expression = node.expression |
||||
except: # pylint: disable=bare-except |
||||
continue |
||||
start = node.source_mapping.start |
||||
stop = start + node.source_mapping.length |
||||
old_str = self.in_file_str[start:stop] |
||||
line_no = node.source_mapping.lines |
||||
if not line_no[0] in self.dont_mutate_line: |
||||
if ( |
||||
isinstance(ir_expression, UnaryOperation) |
||||
and ir_expression.type in unary_operators |
||||
): |
||||
for op in unary_operators: |
||||
if not node.expression.is_prefix: |
||||
if node.expression.type != op: |
||||
variable_read = node.variables_read[0] |
||||
new_str = str(variable_read) + str(op) |
||||
if new_str != old_str and str(op) != "-": |
||||
create_patch_with_line( |
||||
result, |
||||
self.in_file, |
||||
start, |
||||
stop, |
||||
old_str, |
||||
new_str, |
||||
line_no[0], |
||||
) |
||||
new_str = str(op) + str(variable_read) |
||||
create_patch_with_line( |
||||
result, |
||||
self.in_file, |
||||
start, |
||||
stop, |
||||
old_str, |
||||
new_str, |
||||
line_no[0], |
||||
) |
||||
else: |
||||
if node.expression.type != op: |
||||
variable_read = node.variables_read[0] |
||||
new_str = str(op) + str(variable_read) |
||||
if new_str != old_str and str(op) != "-": |
||||
create_patch_with_line( |
||||
result, |
||||
self.in_file, |
||||
start, |
||||
stop, |
||||
old_str, |
||||
new_str, |
||||
line_no[0], |
||||
) |
||||
new_str = str(variable_read) + str(op) |
||||
create_patch_with_line( |
||||
result, |
||||
self.in_file, |
||||
start, |
||||
stop, |
||||
old_str, |
||||
new_str, |
||||
line_no[0], |
||||
) |
||||
return result |
@ -1,4 +1,16 @@ |
||||
# pylint: disable=unused-import |
||||
from slither.tools.mutator.mutators.MVIV import MVIV |
||||
from slither.tools.mutator.mutators.MVIE import MVIE |
||||
from slither.tools.mutator.mutators.MIA import MIA |
||||
from slither.tools.mutator.mutators.MVIV import MVIV # severity low |
||||
from slither.tools.mutator.mutators.MVIE import MVIE # severity low |
||||
from slither.tools.mutator.mutators.LOR import LOR # severity medium |
||||
from slither.tools.mutator.mutators.UOR import UOR # severity medium |
||||
from slither.tools.mutator.mutators.SBR import SBR # severity medium |
||||
from slither.tools.mutator.mutators.AOR import AOR # severity medium |
||||
from slither.tools.mutator.mutators.BOR import BOR # severity medium |
||||
from slither.tools.mutator.mutators.ASOR import ASOR # severity medium |
||||
from slither.tools.mutator.mutators.MWA import MWA # severity medium |
||||
from slither.tools.mutator.mutators.LIR import LIR # severity medium |
||||
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.RR import RR # severity high |
||||
from slither.tools.mutator.mutators.CR import CR # severity high |
||||
|
@ -0,0 +1,130 @@ |
||||
import os |
||||
from typing import Dict, List |
||||
import logging |
||||
|
||||
logger = logging.getLogger("Slither-Mutate") |
||||
|
||||
duplicated_files = {} |
||||
|
||||
|
||||
def backup_source_file(source_code: Dict, output_folder: str) -> Dict: |
||||
""" |
||||
function to backup the source file |
||||
returns: dictionary of duplicated files |
||||
""" |
||||
os.makedirs(output_folder, exist_ok=True) |
||||
|
||||
for file_path, content in source_code.items(): |
||||
directory, filename = os.path.split(file_path) |
||||
new_filename = f"{output_folder}/backup_{filename}" |
||||
new_file_path = os.path.join(directory, new_filename) |
||||
|
||||
with open(new_file_path, "w", encoding="utf8") as new_file: |
||||
new_file.write(content) |
||||
duplicated_files[file_path] = new_file_path |
||||
|
||||
return duplicated_files |
||||
|
||||
|
||||
def transfer_and_delete(files_dict: Dict) -> None: |
||||
"""function to transfer the original content to the sol file after campaign""" |
||||
try: |
||||
files_dict_copy = files_dict.copy() |
||||
for item, value in files_dict_copy.items(): |
||||
with open(value, "r", encoding="utf8") as duplicated_file: |
||||
content = duplicated_file.read() |
||||
|
||||
with open(item, "w", encoding="utf8") as original_file: |
||||
original_file.write(content) |
||||
|
||||
os.remove(value) |
||||
|
||||
# delete elements from the global dict |
||||
del duplicated_files[item] |
||||
|
||||
except Exception as e: # pylint: disable=broad-except |
||||
logger.error(f"Error transferring content: {e}") |
||||
|
||||
|
||||
def create_mutant_file(file: str, count: int, rule: str) -> None: |
||||
"""function to create new mutant file""" |
||||
try: |
||||
_, filename = os.path.split(file) |
||||
# Read content from the duplicated file |
||||
with open(file, "r", encoding="utf8") as source_file: |
||||
content = source_file.read() |
||||
|
||||
# Write content to the original file |
||||
mutant_name = filename.split(".")[0] |
||||
|
||||
# create folder for each contract |
||||
os.makedirs("mutation_campaign/" + mutant_name, exist_ok=True) |
||||
with open( |
||||
"mutation_campaign/" |
||||
+ mutant_name |
||||
+ "/" |
||||
+ mutant_name |
||||
+ "_" |
||||
+ rule |
||||
+ "_" |
||||
+ str(count) |
||||
+ ".sol", |
||||
"w", |
||||
encoding="utf8", |
||||
) as mutant_file: |
||||
mutant_file.write(content) |
||||
|
||||
# reset the file |
||||
with open(duplicated_files[file], "r", encoding="utf8") as duplicated_file: |
||||
duplicate_content = duplicated_file.read() |
||||
|
||||
with open(file, "w", encoding="utf8") as source_file: |
||||
source_file.write(duplicate_content) |
||||
|
||||
except Exception as e: # pylint: disable=broad-except |
||||
logger.error(f"Error creating mutant: {e}") |
||||
|
||||
|
||||
def reset_file(file: str) -> None: |
||||
"""function to reset the file""" |
||||
try: |
||||
# directory, filename = os.path.split(file) |
||||
# reset the file |
||||
with open(duplicated_files[file], "r", encoding="utf8") as duplicated_file: |
||||
duplicate_content = duplicated_file.read() |
||||
|
||||
with open(file, "w", encoding="utf8") as source_file: |
||||
source_file.write(duplicate_content) |
||||
|
||||
except Exception as e: # pylint: disable=broad-except |
||||
logger.error(f"Error resetting file: {e}") |
||||
|
||||
|
||||
def get_sol_file_list(codebase: str, ignore_paths: List[str] | None) -> List[str]: |
||||
""" |
||||
function to get the contracts list |
||||
returns: list of .sol files |
||||
""" |
||||
sol_file_list = [] |
||||
if ignore_paths is None: |
||||
ignore_paths = [] |
||||
|
||||
# if input is contract file |
||||
if os.path.isfile(codebase): |
||||
return [codebase] |
||||
|
||||
# if input is folder |
||||
if os.path.isdir(codebase): |
||||
directory = os.path.abspath(codebase) |
||||
for file in os.listdir(directory): |
||||
filename = os.path.join(directory, file) |
||||
if os.path.isfile(filename): |
||||
sol_file_list.append(filename) |
||||
elif os.path.isdir(filename): |
||||
_, dirname = os.path.split(filename) |
||||
if dirname in ignore_paths: |
||||
continue |
||||
for i in get_sol_file_list(filename, ignore_paths): |
||||
sol_file_list.append(i) |
||||
|
||||
return sol_file_list |
@ -1,36 +0,0 @@ |
||||
from typing import Dict |
||||
|
||||
from slither.core.declarations import Contract |
||||
from slither.core.variables.variable import Variable |
||||
from slither.formatters.utils.patches import create_patch |
||||
|
||||
|
||||
def remove_assignement(variable: Variable, contract: Contract, result: Dict): |
||||
""" |
||||
Remove the variable's initial assignement |
||||
|
||||
:param variable: |
||||
:param contract: |
||||
:param result: |
||||
:return: |
||||
""" |
||||
# Retrieve the file |
||||
in_file = contract.source_mapping.filename.absolute |
||||
# Retrieve the source code |
||||
in_file_str = contract.compilation_unit.core.source_code[in_file] |
||||
|
||||
# Get the string |
||||
start = variable.source_mapping.start |
||||
stop = variable.expression.source_mapping.start |
||||
old_str = in_file_str[start:stop] |
||||
|
||||
new_str = old_str[: old_str.find("=")] |
||||
|
||||
create_patch( |
||||
result, |
||||
in_file, |
||||
start, |
||||
stop + variable.expression.source_mapping.length, |
||||
old_str, |
||||
new_str, |
||||
) |
@ -0,0 +1,29 @@ |
||||
from typing import Dict, Union |
||||
from collections import defaultdict |
||||
|
||||
|
||||
# pylint: disable=too-many-arguments |
||||
def create_patch_with_line( |
||||
result: Dict, |
||||
file: str, |
||||
start: int, |
||||
end: int, |
||||
old_str: Union[str, bytes], |
||||
new_str: Union[str, bytes], |
||||
line_no: int, |
||||
) -> None: |
||||
if isinstance(old_str, bytes): |
||||
old_str = old_str.decode("utf8") |
||||
if isinstance(new_str, bytes): |
||||
new_str = new_str.decode("utf8") |
||||
p = { |
||||
"start": start, |
||||
"end": end, |
||||
"old_string": old_str, |
||||
"new_string": new_str, |
||||
"line_number": line_no, |
||||
} |
||||
if "patches" not in result: |
||||
result["patches"] = defaultdict(list) |
||||
if p not in result["patches"][file]: |
||||
result["patches"][file].append(p) |
@ -0,0 +1,100 @@ |
||||
import subprocess |
||||
import os |
||||
import logging |
||||
import time |
||||
import signal |
||||
from typing import Dict |
||||
import crytic_compile |
||||
from slither.tools.mutator.utils.file_handling import create_mutant_file, reset_file |
||||
from slither.utils.colors import green, red |
||||
|
||||
logger = logging.getLogger("Slither-Mutate") |
||||
|
||||
|
||||
def compile_generated_mutant(file_path: str, mappings: str) -> bool: |
||||
""" |
||||
function to compile the generated mutant |
||||
returns: status of compilation |
||||
""" |
||||
try: |
||||
crytic_compile.CryticCompile(file_path, solc_remaps=mappings) |
||||
return True |
||||
except: # pylint: disable=bare-except |
||||
return False |
||||
|
||||
|
||||
def run_test_cmd(cmd: str, test_dir: str, timeout: int) -> bool: |
||||
""" |
||||
function to run codebase tests |
||||
returns: boolean whether the tests passed or not |
||||
""" |
||||
# future purpose |
||||
_ = test_dir |
||||
# add --fail-fast for foundry tests, to exit after first failure |
||||
if "forge test" in cmd and "--fail-fast" not in cmd: |
||||
cmd += " --fail-fast" |
||||
# add --bail for hardhat and truffle tests, to exit after first failure |
||||
elif "hardhat test" in cmd or "truffle test" in cmd and "--bail" not in cmd: |
||||
cmd += " --bail" |
||||
|
||||
start = time.time() |
||||
|
||||
# starting new process |
||||
with subprocess.Popen([cmd], shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) as P: |
||||
try: |
||||
# checking whether the process is completed or not within 30 seconds(default) |
||||
while P.poll() is None and (time.time() - start) < timeout: |
||||
time.sleep(0.05) |
||||
finally: |
||||
if P.poll() is None: |
||||
logger.error("HAD TO TERMINATE ANALYSIS (TIMEOUT OR EXCEPTION)") |
||||
# sends a SIGTERM signal to process group - bascially killing the process |
||||
os.killpg(os.getpgid(P.pid), signal.SIGTERM) |
||||
# Avoid any weird race conditions from grabbing the return code |
||||
time.sleep(0.05) |
||||
# indicates whether the command executed sucessfully or not |
||||
r = P.returncode |
||||
|
||||
# if r is 0 then it is valid mutant because tests didn't fail |
||||
return r == 0 |
||||
|
||||
|
||||
def test_patch( # pylint: disable=too-many-arguments |
||||
file: str, |
||||
patch: Dict, |
||||
command: str, |
||||
index: int, |
||||
generator_name: str, |
||||
timeout: int, |
||||
mappings: str | None, |
||||
verbose: bool, |
||||
) -> bool: |
||||
""" |
||||
function to verify the validity of each patch |
||||
returns: valid or invalid patch |
||||
""" |
||||
with open(file, "r", encoding="utf-8") as filepath: |
||||
content = filepath.read() |
||||
# Perform the replacement based on the index values |
||||
replaced_content = content[: patch["start"]] + patch["new_string"] + content[patch["end"] :] |
||||
# Write the modified content back to the file |
||||
with open(file, "w", encoding="utf-8") as filepath: |
||||
filepath.write(replaced_content) |
||||
if compile_generated_mutant(file, mappings): |
||||
if run_test_cmd(command, file, timeout): |
||||
create_mutant_file(file, index, generator_name) |
||||
print( |
||||
green( |
||||
f"String '{patch['old_string']}' replaced with '{patch['new_string']}' at line no. '{patch['line_number']}' ---> VALID\n" |
||||
) |
||||
) |
||||
return True |
||||
|
||||
reset_file(file) |
||||
if verbose: |
||||
print( |
||||
red( |
||||
f"String '{patch['old_string']}' replaced with '{patch['new_string']}' at line no. '{patch['line_number']}' ---> INVALID\n" |
||||
) |
||||
) |
||||
return False |
@ -1,2 +1,4 @@ |
||||
C.i_am_a_backdoor2(address) (tests/e2e/detectors/test_data/suicidal/0.7.6/suicidal.sol#8-10) allows anyone to destruct the contract |
||||
|
||||
C.i_am_a_backdoor() (tests/e2e/detectors/test_data/suicidal/0.7.6/suicidal.sol#4-6) allows anyone to destruct the contract |
||||
|
||||
|
Binary file not shown.
Binary file not shown.
@ -0,0 +1,8 @@ |
||||
{ |
||||
"Lib": { |
||||
"f(Hello)": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n}\n" |
||||
}, |
||||
"Hello": { |
||||
"test()": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: EXPRESSION 1\n\"];\n}\n" |
||||
} |
||||
} |
@ -0,0 +1,13 @@ |
||||
library Lib { |
||||
function f(Hello h) external { |
||||
|
||||
} |
||||
} |
||||
contract Hello { |
||||
using Lib for Hello; |
||||
|
||||
function test() external { |
||||
this.f(); |
||||
} |
||||
} |
||||
|
Loading…
Reference in new issue