Added new mutators

pull/2278/head
Vishnuram Rajkumar 11 months ago
parent 36eda1b3cb
commit 67b95dff74
  1. 33
      slither/tools/mutator/__main__.py
  2. 38
      slither/tools/mutator/mutators/AOR.py
  3. 50
      slither/tools/mutator/mutators/ASOR.py
  4. 35
      slither/tools/mutator/mutators/BOR.py
  5. 14
      slither/tools/mutator/mutators/LOR.py
  6. 20
      slither/tools/mutator/mutators/MIA.py
  7. 45
      slither/tools/mutator/mutators/MVIE.py
  8. 45
      slither/tools/mutator/mutators/MVIV.py
  9. 34
      slither/tools/mutator/mutators/MWA.py
  10. 20
      slither/tools/mutator/mutators/ROR.py
  11. 22
      slither/tools/mutator/mutators/SBR.py
  12. 61
      slither/tools/mutator/mutators/UOI.py
  13. 25
      slither/tools/mutator/mutators/abstract_mutator.py
  14. 16
      slither/tools/mutator/mutators/all_mutators.py
  15. 40
      slither/tools/mutator/utils/generic_patching.py
  16. 48
      slither/tools/mutator/utils/replace_conditions.py
  17. 13
      slither/tools/mutator/utils/testing_generated_mutant.py

@ -59,11 +59,18 @@ def parse_args() -> argparse.Namespace:
help="Directories to ignore"
)
# to_do: add time out argument
# time out argument
parser.add_argument(
"--timeout",
help="Set timeout for test command"
help="Set timeout for test command (by deafult 30 seconds)"
)
# output directory argument
parser.add_argument(
"--output-dir",
help="Output Directory (by default it is 'mutation_campaign')"
)
# Initiate all the crytic config cli options
cryticparser.init(parser)
@ -98,13 +105,13 @@ class ListMutators(argparse.Action): # pylint: disable=too-few-public-methods
def main() -> None:
args = parse_args()
# print(os.path.isdir(args.codebase)) # provided file/folder
# arguments
test_command: str = args.test_cmd
test_directory: str = args.test_dir
paths_to_ignore: str | None = args.ignore_dirs
timeout: int = args.timeout
output_dir: str | None = args.output_dir
timeout: int | None = args.timeout
print(magenta(f"Starting Mutation Campaign in '{args.codebase} \n"))
@ -118,33 +125,33 @@ def main() -> None:
sol_file_list: List[str] = get_sol_file_list(args.codebase, paths_to_ignore_list)
# folder where backup files and valid mutants created
output_folder = os.getcwd() + "/mutation_campaign"
if output_dir == None:
output_dir = "/mutation_campaign"
output_folder = os.getcwd() + output_dir
if os.path.exists(output_folder):
shutil.rmtree(output_folder)
# set default timeout
if timeout == None:
timeout = 30
for filename in sol_file_list:
contract_name = os.path.split(filename)[1].split('.sol')[0]
# slither object
sl = Slither(filename, **vars(args))
# create a backup files
files_dict = backup_source_file(sl.source_code, output_folder)
# total count of mutants
total_count = 0
# count of valid mutants
v_count = 0
# mutation
try:
for compilation_unit_of_main_file in sl.compilation_units:
# compilation_unit_of_main_file = sl.compilation_units[-1]
# for i in compilation_unit_of_main_file.contracts:
# print(i.name)
for M in _get_mutators():
m = M(compilation_unit_of_main_file)
count_valid, count_invalid = m.mutate(test_command, test_directory, contract_name)
m = M(compilation_unit_of_main_file, int(timeout), test_command, test_directory, contract_name)
count_valid, count_invalid = m.mutate()
v_count += count_valid
total_count += count_valid + count_invalid
except Exception as e:

@ -0,0 +1,38 @@
from typing import Dict
from slither.slithir.operations import Binary, BinaryType
from slither.formatters.utils.patches import create_patch
from slither.tools.mutator.mutators.abstract_mutator import AbstractMutator, FaultNature, FaultClass
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"
FAULTCLASS = FaultClass.Checking
FAULTNATURE = FaultNature.Missing
def _mutate(self) -> Dict:
result: Dict = {}
for 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 arithmetic_operators:
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
# 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(result, self.in_file, start, stop, old_str, new_str, line_no[0])
return result

@ -0,0 +1,50 @@
from typing import Dict
from slither.slithir.operations import Binary, BinaryType
from slither.formatters.utils.patches import create_patch
from slither.tools.mutator.mutators.abstract_mutator import AbstractMutator, FaultNature, FaultClass
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"
FAULTCLASS = FaultClass.Checking
FAULTNATURE = FaultNature.Missing
def _mutate(self) -> Dict:
result: Dict = {}
for 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:
continue
for op in assignment_operators:
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
# 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(result, self.in_file, start, stop, old_str, new_str, line_no[0])
return result

@ -0,0 +1,35 @@
from typing import Dict
from slither.slithir.operations import Binary, BinaryType
from slither.formatters.utils.patches import create_patch
from slither.tools.mutator.mutators.abstract_mutator import AbstractMutator, FaultNature, FaultClass
bitwise_operators = [
BinaryType.AND,
BinaryType.OR
]
class BOR(AbstractMutator): # pylint: disable=too-few-public-methods
NAME = "BOR"
HELP = "Bitwise Operator Replacement"
FAULTCLASS = FaultClass.Checking
FAULTNATURE = FaultNature.Missing
def _mutate(self) -> Dict:
result: Dict = {}
for 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
# 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(result, self.in_file, start, stop, old_str, new_str, line_no[0])
return result

@ -15,17 +15,9 @@ class LOR(AbstractMutator): # pylint: disable=too-few-public-methods
FAULTNATURE = FaultNature.Missing
def _mutate(self) -> Dict:
result: Dict = {}
contract = self.contract
# 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]
for function in contract.functions_and_modifiers_declared:
for 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:
@ -37,11 +29,11 @@ class LOR(AbstractMutator): # pylint: disable=too-few-public-methods
# Get the string
start = node.source_mapping.start
stop = start + node.source_mapping.length
old_str = in_file_str[start:stop]
old_str = self.in_file_str[start:stop]
line_no = node.source_mapping.lines
# Replace the expression with true
# new_str = f"{ir.variable_left} {op.value} {ir.variable_right}"
new_str = f"{old_str.split(ir.type.value)[0]} {op.value} {old_str.split(ir.type.value)[1]}"
create_patch(result, in_file, start, stop, old_str, new_str, line_no[0])
create_patch(result, self.in_file, start, stop, old_str, new_str, line_no[0])
return result

@ -2,6 +2,7 @@ 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.core.expressions.unary_operation import UnaryOperationType, UnaryOperation
class MIA(AbstractMutator): # pylint: disable=too-few-public-methods
NAME = "MIA"
@ -10,32 +11,31 @@ class MIA(AbstractMutator): # pylint: disable=too-few-public-methods
FAULTNATURE = FaultNature.Missing
def _mutate(self) -> Dict:
result: Dict = {}
# Retrieve the file
in_file = self.contract.source_mapping.filename.absolute
# Retrieve the source code
in_file_str = self.contract.compilation_unit.core.source_code[in_file]
for function in self.contract.functions_declared + list(self.contract.modifiers_declared):
for function in self.contract.functions_and_modifiers_declared:
for node in function.nodes:
if node.type == NodeType.IF:
# Get the string
start = node.source_mapping.start
stop = start + node.source_mapping.length
old_str = in_file_str[start:stop]
old_str = self.in_file_str[start:stop]
line_no = node.source_mapping.lines
# Replace the expression with true and false
for value in ["true", "false"]:
new_str = value
create_patch(result, in_file, start, stop, old_str, new_str, line_no[0])
create_patch(result, self.in_file, start, stop, old_str, new_str, line_no[0])
# print(node.expression)
if not isinstance(node.expression, UnaryOperation):
new_str = str(UnaryOperationType.BANG) + '(' + old_str + ')'
create_patch(result, self.in_file, start, stop, old_str, new_str, line_no[0])
return result
# limitations - won't work if it is tenary operation

@ -1,9 +1,8 @@
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.formatters.utils.patches import create_patch
class MVIE(AbstractMutator): # pylint: disable=too-few-public-methods
NAME = "MVIE"
@ -12,24 +11,52 @@ class MVIE(AbstractMutator): # pylint: disable=too-few-public-methods
FAULTNATURE = FaultNature.Missing
def _mutate(self) -> Dict:
result: Dict = {}
variable: Variable
contract = self.contract
# Create fault for state variables declaration
for variable in contract.state_variables_declared:
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):
remove_assignement(variable, contract, result)
for function in contract.functions_declared + list(contract.modifiers_declared):
# 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 = [0]
create_patch(
result,
self.in_file,
start,
stop + variable.expression.source_mapping.length,
old_str,
new_str,
line_no
)
for function in self.contract.functions_and_modifiers_declared:
for variable in function.local_variables:
if variable.initialized and not isinstance(variable.expression, Literal):
remove_assignement(variable, contract, result)
# 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 = [0]
create_patch(
result,
self.in_file,
start,
stop + variable.expression.source_mapping.length,
old_str,
new_str,
line_no
)
return result

@ -1,9 +1,9 @@
from typing import Dict, Tuple
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.formatters.utils.patches import create_patch
class MVIV(AbstractMutator): # pylint: disable=too-few-public-methods
NAME = "MVIV"
@ -12,24 +12,51 @@ class MVIV(AbstractMutator): # pylint: disable=too-few-public-methods
FAULTNATURE = FaultNature.Missing
def _mutate(self) -> Dict:
result: Dict = {}
variable: Variable
contract = self.contract
# Create fault for state variables declaration
for variable in contract.state_variables_declared:
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):
remove_assignement(variable, contract, result)
# Get the string
start = variable.source_mapping.start
stop = variable.expression.source_mapping.start
old_str = self.in_file_str[start:stop]
for function in contract.functions_declared + list(contract.modifiers_declared):
new_str = old_str[: old_str.find("=")]
line_no = [0]
create_patch(
result,
self.in_file,
start,
stop + variable.expression.source_mapping.length,
old_str,
new_str,
line_no
)
for function in self.contract.functions_and_modifiers_declared:
for variable in function.local_variables:
if variable.initialized and isinstance(variable.expression, Literal):
remove_assignement(variable, contract, result)
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 = [0]
create_patch(
result,
self.in_file,
start,
stop + variable.expression.source_mapping.length,
old_str,
new_str,
line_no
)
return result

@ -0,0 +1,34 @@
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.core.expressions.unary_operation import UnaryOperationType, UnaryOperation
class MWA(AbstractMutator): # pylint: disable=too-few-public-methods
NAME = "MIA"
HELP = '"while" construct around statement'
FAULTCLASS = FaultClass.Checking
FAULTNATURE = FaultNature.Missing
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 isinstance(node.expression, UnaryOperation):
new_str = str(UnaryOperationType.BANG) + '(' + old_str + ')'
create_patch(result, self.in_file, start, stop, old_str, new_str, line_no[0])
return result
# limitations - won't work if it is tenary operation

@ -1,5 +1,4 @@
from typing import Dict
from collections import defaultdict
from slither.slithir.operations import Binary, BinaryType
from slither.formatters.utils.patches import create_patch
from slither.tools.mutator.mutators.abstract_mutator import AbstractMutator, FaultNature, FaultClass
@ -22,19 +21,11 @@ class ROR(AbstractMutator): # pylint: disable=too-few-public-methods
FAULTNATURE = FaultNature.Missing
def _mutate(self) -> Dict:
result: Dict = {}
# result["patches"] = defaultdict(list)
contract = self.contract
for function in contract.functions_and_modifiers_declared:
for function in self.contract.functions_and_modifiers_declared:
for node in function.nodes:
for ir in node.irs:
# Retrieve the file
in_file = self.contract.source_mapping.filename.absolute
# Retrieve the source code
in_file_str = self.contract.compilation_unit.core.source_code[in_file]
if isinstance(ir, Binary) and ir.type in relational_operators:
alternative_ops = relational_operators[:]
alternative_ops.remove(ir.type)
@ -43,11 +34,12 @@ class ROR(AbstractMutator): # pylint: disable=too-few-public-methods
# Get the string
start = node.source_mapping.start
stop = start + node.source_mapping.length
old_str = in_file_str[start:stop]
old_str = self.in_file_str[start:stop]
line_no = node.source_mapping.lines
# 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(result, in_file, start, stop, old_str, new_str, line_no[0])
return result
create_patch(result, self.in_file, start, stop, old_str, new_str, line_no[0])
return result
# failing in case of condition1 || condition2

@ -3,6 +3,7 @@ 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
import re
from slither.core.variables.variable import Variable
solidity_rules = [
"abi\.encode\( ==> abi.encodePacked(",
@ -38,7 +39,8 @@ solidity_rules = [
"(u?)int32 ==> \\1int16",
"(u?)int64 ==> \\1int32",
"(u?)int128 ==> \\1int64",
"(u?)int256 ==> \\1int128"
"(u?)int256 ==> \\1int128"
"while ==> if",
]
@ -51,40 +53,36 @@ class SBR(AbstractMutator): # pylint: disable=too-few-public-methods
def _mutate(self) -> Dict:
result: Dict = {}
contract = self.contract
# 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]
variable: Variable
for function in contract.functions_and_modifiers_declared:
for function in self.contract.functions_and_modifiers_declared:
for node in function.nodes:
if node.type != NodeType.ENTRYPOINT:
# Get the string
start = node.source_mapping.start
stop = start + node.source_mapping.length
old_str = in_file_str[start:stop]
old_str = self.in_file_str[start:stop]
line_no = node.source_mapping.lines
for value in solidity_rules:
left_value = value.split(" ==> ")[0]
right_value = value.split(" ==> ")[1]
if re.search(re.compile(left_value), old_str) != None:
new_str = re.sub(re.compile(left_value), right_value, old_str)
create_patch(result, in_file, start, stop, old_str, new_str, line_no[0])
create_patch(result, self.in_file, start, stop, old_str, new_str, line_no[0])
for variable in contract.state_variables_declared:
for 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 = in_file_str[start:stop]
old_str = self.in_file_str[start:stop]
line_no = node.source_mapping.lines
for value in solidity_rules:
left_value = value.split(" ==> ")[0]
right_value = value.split(" ==> ")[1]
if re.search(re.compile(left_value), old_str) != None:
new_str = re.sub(re.compile(left_value), right_value, old_str)
create_patch(result, in_file, start, stop, old_str, new_str, line_no[0])
create_patch(result, self.in_file, start, stop, old_str, new_str, line_no[0])
return result

@ -1,23 +1,19 @@
from typing import Dict
import re
from slither.core.expressions.unary_operation import UnaryOperationType
from slither.core.expressions.unary_operation import UnaryOperationType, UnaryOperation
from slither.core.expressions.expression import Expression
from slither.slithir.variables import Constant
from slither.core.variables.local_variable import LocalVariable
from slither.core.expressions.expression import Expression
from slither.formatters.utils.patches import create_patch
from slither.tools.mutator.mutators.abstract_mutator import AbstractMutator, FaultNature, FaultClass
from slither.core.cfg.node import NodeType
unary_operators = [
UnaryOperationType.PLUSPLUS_PRE,
UnaryOperationType.MINUSMINUS_PRE,
UnaryOperationType.PLUSPLUS_POST,
UnaryOperationType.MINUSMINUS_POST,
UnaryOperationType.MINUS_PRE,
UnaryOperationType.MINUS_PRE
]
class UOI(AbstractMutator): # pylint: disable=too-few-public-methods
NAME = "UOI"
HELP = "Unary operator insertion"
@ -25,32 +21,35 @@ class UOI(AbstractMutator): # pylint: disable=too-few-public-methods
FAULTNATURE = FaultNature.Missing
def _mutate(self) -> Dict:
result: Dict = {}
contract = self.contract
# 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]
for function in contract.functions_and_modifiers_declared:
for function in self.contract.functions_and_modifiers_declared:
for node in function.nodes:
if (node.type == NodeType.EXPRESSION):
try:
ir_expression = node.expression
except Exception as e:
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 isinstance(ir_expression, UnaryOperation) and ir_expression.type in unary_operators:
for op in unary_operators:
if str(op) in str(node.expression):
for i in node.variables_written:
print(i)
# Get the string
start = node.source_mapping.start
stop = start + node.source_mapping.length
old_str = in_file_str[start:stop]
# print(old_str)
# Replace the expression with true
# new_str = old_str.replace(str(operand), f"{str(op)}{operand}")
# new_str = re.sub(r'(\w+)\+\+', r'++\1', text)
# print(new_str)
# create_patch(result, in_file, start, stop, old_str, new_str)
print(result)
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:
create_patch(result, self.in_file, start, stop, old_str, new_str, line_no[0])
new_str = str(op) + str(variable_read)
create_patch(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:
create_patch(result, self.in_file, start, stop, old_str, new_str, line_no[0])
new_str = str(variable_read) + str(op)
create_patch(result, self.in_file, start, stop, old_str, new_str, line_no[0])
return result

@ -38,12 +38,15 @@ class AbstractMutator(metaclass=abc.ABCMeta): # pylint: disable=too-few-public-
INVALID_MUTANTS_COUNT = 0
def __init__(
self, compilation_unit: SlitherCompilationUnit, rate: int = 10, seed: Optional[int] = None
self, compilation_unit: SlitherCompilationUnit, timeout: int, testing_command: str, testing_directory: str, contract_name: str, rate: int = 10, seed: Optional[int] = None
):
self.compilation_unit = compilation_unit
self.slither = compilation_unit.core
self.seed = seed
self.rate = rate
self.test_command = testing_command
self.test_directory = testing_directory
self.timeout = timeout
if not self.NAME:
raise IncorrectMutatorInitialization(
@ -69,18 +72,23 @@ class AbstractMutator(metaclass=abc.ABCMeta): # pylint: disable=too-few-public-
raise IncorrectMutatorInitialization(
f"rate must be between 0 and 100 {self.__class__.__name__}"
)
# identify the main contract, ignore the imports
for contract in self.slither.contracts:
if contract_name == str(contract.name): # limitation: what if the contract name is not same as file name
# contract
self.contract = contract
# Retrieve the file
self.in_file = self.contract.source_mapping.filename.absolute
# Retrieve the source code
self.in_file_str = self.contract.compilation_unit.core.source_code[self.in_file]
@abc.abstractmethod
def _mutate(self, test_cmd: str, test_dir: str) -> Dict:
"""TODO Documentation"""
return {}
def mutate(self, testing_command: str, testing_directory: str, contract_name: str) -> Tuple[int, int]:
# identify the main contract, ignore the imports
for contract in self.slither.contracts:
if contract_name == str(contract.name):
self.contract = contract
def mutate(self) -> Tuple[int, int]:
# call _mutate function from different mutators
(all_patches) = self._mutate()
@ -98,9 +106,8 @@ class AbstractMutator(metaclass=abc.ABCMeta): # pylint: disable=too-few-public-
# logger.error(f"Impossible to generate patch; patches collisions: {patches}")
# continue
for patch in patches:
# print(patch)
# test the patch
flag = test_patch(file, patch, testing_command, self.VALID_MUTANTS_COUNT, self.NAME)
flag = test_patch(file, patch, self.test_command, self.VALID_MUTANTS_COUNT, self.NAME, self.timeout)
# count the valid and invalid mutants
if not flag:
self.INVALID_MUTANTS_COUNT += 1

@ -1,9 +1,13 @@
# 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
from slither.tools.mutator.mutators.MVIE import MVIE
from slither.tools.mutator.mutators.MIA import MIA
from slither.tools.mutator.mutators.ROR import ROR
# from slither.tools.mutator.mutators.LOR import LOR
# from slither.tools.mutator.mutators.UOI import UOI
# from slither.tools.mutator.mutators.SBR import SBR
from slither.tools.mutator.mutators.LOR import LOR
from slither.tools.mutator.mutators.UOI import UOI
from slither.tools.mutator.mutators.SBR import SBR
from slither.tools.mutator.mutators.AOR import AOR
from slither.tools.mutator.mutators.BOR import BOR
from slither.tools.mutator.mutators.ASOR import ASOR
from slither.tools.mutator.mutators.MWA import MWA

@ -1,40 +0,0 @@
from typing import Dict
import os
from slither.core.declarations import Contract
from slither.core.variables.variable import Variable
from slither.formatters.utils.patches import create_patch
from slither.tools.mutator.utils.testing_generated_mutant import compile_generated_mutant, run_test_suite
from slither.tools.mutator.utils.replace_conditions import replace_string_in_source_file
from slither.tools.mutator.utils.file_handling import create_mutant_file
def remove_assignement(variable: Variable, contract: Contract, result: Dict) -> bool:
"""
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("=")]
line_no = [0]
create_patch(
result,
in_file,
start,
stop + variable.expression.source_mapping.length,
old_str,
new_str,
line_no
)

@ -1,48 +0,0 @@
import logging
import re
logger = logging.getLogger("Slither-Mutate")
# function to replace the string
def replace_string_in_source_file(file_path: str, old_string: str, new_string: str) -> None:
try:
# Read the content of the Solidity file
with open(file_path, 'r') as file:
content = file.read()
# Perform the string replacement
modified_content = content.replace(old_string, new_string)
# Write the modified content back to the file
with open(file_path, 'w') as file:
file.write(modified_content)
logger.info(f"String '{old_string}' replaced with '{new_string}' in '{file_path}'.")
except Exception as e:
logger.error(f"Error replacing string: {e}")
# function to replace the string in a specific line
def replace_string_in_source_file_specific_line(file_path: str, old_string: str, new_string: str, line_number : int) -> None:
try:
# Read the content of the Solidity file
with open(file_path, 'r') as file:
lines = file.readlines()
if 1 <= line_number <= len(lines):
# remove the spaces in the string
line = lines[line_number - 1].replace(" ", "")
old_string = old_string.replace(" ", "")
# Replace the old string with the new string on the specified line
lines[line_number - 1] = line.replace(old_string.strip(), new_string)
# Write the modified content back to the file
with open(file_path, 'w') as file:
file.writelines(lines)
logger.info(f"String '{old_string}' replaced with '{new_string}' in '{file_path}'.' at '{line_number}")
else:
logger.error(f'Error: Line number {line_number} is out of range')
except Exception as e:
logger.erro(f'Error: {e}')

@ -38,14 +38,12 @@ def run_test_suite(cmd: str, dir: str) -> bool:
logger.error(f"An unexpected error occurred: {e}")
return False
def run_test_cmd(cmd: str, dir: str) -> bool:
def run_test_cmd(cmd: str, dir: str, timeout: int) -> bool:
start = time.time()
# starting new process
P = subprocess.Popen([cmd], shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, preexec_fn=os.setsid)
try:
# checking whether the process is completed or not for 30 seconds
# 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:
@ -58,21 +56,20 @@ def run_test_cmd(cmd: str, dir: str) -> bool:
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 True if r == 0 else False
def test_patch(file: str, patch: Dict, command: str, index: int, generator_name: str) -> bool:
def test_patch(file: str, patch: Dict, command: str, index: int, generator_name: str, timeout: int) -> bool:
with open(file, 'r') 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') as filepath:
filepath.write(replaced_content)
if(compile_generated_mutant(file)):
if(run_test_cmd(command, file)):
if(run_test_cmd(command, file, timeout)):
create_mutant_file(file, index, generator_name)
logger.info(green(f"String '{patch['old_string']}' replaced with '{patch['new_string']}' at line no. '{patch['line_number']}' in '{file}' ---> VALID\n"))
return True

Loading…
Cancel
Save