Added new mutant generators

pull/2278/head
Vishnuram Rajkumar 11 months ago
parent 37c23e11ce
commit 36eda1b3cb
  1. 44
      slither/tools/mutator/__main__.py
  2. 47
      slither/tools/mutator/mutators/LOR.py
  3. 65
      slither/tools/mutator/mutators/MIA.py
  4. 48
      slither/tools/mutator/mutators/MVIE.py
  5. 41
      slither/tools/mutator/mutators/MVIV.py
  6. 53
      slither/tools/mutator/mutators/ROR.py
  7. 94
      slither/tools/mutator/mutators/SBR.py
  8. 56
      slither/tools/mutator/mutators/UOI.py
  9. 46
      slither/tools/mutator/mutators/abstract_mutator.py
  10. 7
      slither/tools/mutator/mutators/all_mutators.py
  11. 34
      slither/tools/mutator/utils/file_handling.py
  12. 28
      slither/tools/mutator/utils/generic_patching.py
  13. 58
      slither/tools/mutator/utils/testing_generated_mutant.py

@ -4,6 +4,7 @@ import logging
import sys
from typing import Type, List, Any, Dict, Tuple
import os
import shutil
from crytic_compile import cryticparser
@ -12,12 +13,12 @@ from slither.tools.mutator.mutators import all_mutators
from .mutators.abstract_mutator import AbstractMutator
from .utils.command_line import output_mutators
from .utils.file_handling import transfer_and_delete, backup_source_file, get_sol_file_list
from slither.utils.colors import yellow, magenta
logging.basicConfig()
logger = logging.getLogger("Slither-Mutate")
logger.setLevel(logging.INFO)
###################################################################################
###################################################################################
# region Cli Arguments
@ -52,12 +53,17 @@ def parse_args() -> argparse.Namespace:
help="Directory of tests"
)
# parameter to ignore the interfaces, libraries
# argument to ignore the interfaces, libraries
parser.add_argument(
"--ignore-dirs",
help="Directories to ignore"
)
# to_do: add time out argument
parser.add_argument(
"--timeout",
help="Set timeout for test command"
)
# Initiate all the crytic config cli options
cryticparser.init(parser)
@ -90,7 +96,6 @@ 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
@ -98,19 +103,29 @@ def main() -> None:
# arguments
test_command: str = args.test_cmd
test_directory: str = args.test_dir
paths_to_ignore: List[str] | None = args.ignore_dirs
paths_to_ignore: str | None = args.ignore_dirs
timeout: int = args.timeout
print(magenta(f"Starting Mutation Campaign in '{args.codebase} \n"))
if paths_to_ignore:
paths_to_ignore_list = paths_to_ignore.strip('][').split(',')
print(magenta(f"Ignored paths - {', '.join(paths_to_ignore_list)} \n"))
else:
paths_to_ignore_list = []
# get all the contracts as a list from given codebase
sol_file_list: List[str] = get_sol_file_list(args.codebase, paths_to_ignore)
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 os.path.exists(output_folder):
shutil.rmtree(output_folder)
print("Starting Mutation Campaign in", args.codebase, "\n")
for filename in sol_file_list:
contract_name = os.path.split(filename)[1].split('.sol')[0]
# slither object
sl = Slither(filename, **vars(args))
# folder where backup files and valid mutants created
output_folder = os.getcwd() + "/mutation_campaign"
# create a backup files
files_dict = backup_source_file(sl.source_code, output_folder)
@ -129,9 +144,9 @@ def main() -> None:
# print(i.name)
for M in _get_mutators():
m = M(compilation_unit_of_main_file)
v_count, i_count = m.mutate(test_command, test_directory, contract_name)
if v_count != None and i_count != None:
total_count = total_count + v_count + i_count
count_valid, count_invalid = m.mutate(test_command, test_directory, contract_name)
v_count += count_valid
total_count += count_valid + count_invalid
except Exception as e:
logger.error(e)
@ -143,10 +158,9 @@ def main() -> None:
# transfer and delete the backup files
transfer_and_delete(files_dict)
# output
print(f"Done mutating, '{filename}'. Valid mutant count: '{v_count}' and Total mutant count '{total_count}'.\n")
print(yellow(f"Done mutating, '{filename}'. Valid mutant count: '{v_count}' and Total mutant count '{total_count}'.\n"))
print("Finished Mutation Campaign in", args.codebase, "\n")
print(magenta(f"Finished Mutation Campaign in '{args.codebase}' \n"))
# endregion

@ -0,0 +1,47 @@
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
logical_operators = [
BinaryType.OROR,
BinaryType.ANDAND,
]
class LOR(AbstractMutator): # pylint: disable=too-few-public-methods
NAME = "LOR"
HELP = "Logical operator replacement"
FAULTCLASS = FaultClass.Checking
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 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 = 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])
return result

@ -1,63 +1,38 @@
from typing import Dict, Tuple
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.testing_generated_mutant import compile_generated_mutant, run_test_suite
from slither.tools.mutator.utils.replace_conditions import replace_string_in_source_file_specific_line
from slither.tools.mutator.utils.file_handling import create_mutant_file
class MIA(AbstractMutator): # pylint: disable=too-few-public-methods
NAME = "MIA"
HELP = '"if" construct around statement'
FAULTCLASS = FaultClass.Checking
FAULTNATURE = FaultNature.Missing
VALID_MUTANTS_COUNT = 0
INVALID_MUTANTS_COUNT = 0
def _mutate(self, test_cmd: str, test_dir: str, contract_name: str) -> Tuple[(Dict, int, int)]:
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 contract in self.slither.contracts:
# if not contract.is_library:
# if not contract.is_interface:
if contract_name == str(contract.name):
for function in contract.functions_declared + list(contract.modifiers_declared):
for node in function.nodes:
if node.contains_if():
# print(node.expression)
# 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 self.contract.functions_declared + list(self.contract.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]
line_no = node.source_mapping.lines
# Get the string
start = node.source_mapping.start
stop = start + node.source_mapping.length
# old_str = in_file_str[start:stop]
old_str = str(node.expression)
line_no = node.source_mapping.lines
# Replace the expression with true
new_str = "true"
print(line_no[0])
replace_string_in_source_file_specific_line(in_file, old_str, new_str, line_no[0])
# compile and run tests
if compile_generated_mutant(in_file):
if run_test_suite(test_cmd, test_dir):
# generate the mutant and patch
create_mutant_file(in_file, self.VALID_MUTANTS_COUNT, self.NAME)
create_patch(result, in_file, start, stop, old_str, new_str)
self.VALID_MUTANTS_COUNT = self.VALID_MUTANTS_COUNT + 1
else:
self.INVALID_MUTANTS_COUNT = self.INVALID_MUTANTS_COUNT + 1
else:
self.INVALID_MUTANTS_COUNT = self.INVALID_MUTANTS_COUNT + 1
print(self.INVALID_MUTANTS_COUNT)
# 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])
return (result, self.VALID_MUTANTS_COUNT, self.INVALID_MUTANTS_COUNT)
return result

@ -1,43 +1,35 @@
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.tools.mutator.utils.file_handling import create_mutant_file
class MVIE(AbstractMutator): # pylint: disable=too-few-public-methods
NAME = "MVIE"
HELP = "variable initialization using an expression"
FAULTCLASS = FaultClass.Assignement
FAULTNATURE = FaultNature.Missing
VALID_MUTANTS_COUNT = 0
INVALID_MUTANTS_COUNT = 0
def _mutate(self, test_cmd: str, test_dir: str, contract_name: str) -> Tuple[(Dict, int, int)]:
def _mutate(self) -> Dict:
result: Dict = {}
variable: Variable
for contract in self.slither.contracts:
# if not contract.is_library:
# if not contract.is_interface:
if contract_name == str(contract.name):
# 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):
if(remove_assignement(variable, contract, result, test_cmd, test_dir)):
create_mutant_file(contract.source_mapping.filename.absolute, self.VALID_MUTANTS_COUNT, self.NAME)
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):
if(remove_assignement(variable, contract, result, test_cmd, test_dir)):
create_mutant_file(contract.source_mapping.filename.absolute, self.VALID_MUTANTS_COUNT, self.NAME)
return (result, self.VALID_MUTANTS_COUNT, self.INVALID_MUTANTS_COUNT)
contract = self.contract
# 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)
return result

@ -4,41 +4,32 @@ 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.utils.file_handling import create_mutant_file
class MVIV(AbstractMutator): # pylint: disable=too-few-public-methods
NAME = "MVIV"
HELP = "variable initialization using a value"
FAULTCLASS = FaultClass.Assignement
FAULTNATURE = FaultNature.Missing
VALID_MUTANTS_COUNT = 0
INVALID_MUTANTS_COUNT = 0
def _mutate(self, test_cmd: str, test_dir: str, contract_name: str) -> Tuple[(Dict, int, int)]:
def _mutate(self) -> Dict:
result: Dict = {}
variable: Variable
contract = self.contract
# 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
for contract in self.slither.contracts:
# if not contract.is_library:
# if not contract.is_interface:
if contract_name == str(contract.name):
# 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)
if isinstance(variable.expression, Literal):
if(remove_assignement(variable, contract, result, test_cmd, test_dir)):
create_mutant_file(contract.source_mapping.filename.absolute, self.VALID_MUTANTS_COUNT, self.NAME)
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)
for function in contract.functions_declared + list(contract.modifiers_declared):
for variable in function.local_variables:
if variable.initialized and isinstance(variable.expression, Literal):
if(remove_assignement(variable, contract, result, test_cmd, test_dir)):
create_mutant_file(contract.source_mapping.filename.absolute, self.VALID_MUTANTS_COUNT, self.NAME)
return (result, self.VALID_MUTANTS_COUNT, self.INVALID_MUTANTS_COUNT)
return result

@ -0,0 +1,53 @@
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
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"
FAULTCLASS = FaultClass.Checking
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 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)
for op in alternative_ops:
# Get the string
start = node.source_mapping.start
stop = start + node.source_mapping.length
old_str = 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

@ -0,0 +1,94 @@
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
import re
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"
]
class SBR(AbstractMutator): # pylint: disable=too-few-public-methods
NAME = "SBR"
HELP = 'Solidity Based Replacements'
FAULTCLASS = FaultClass.Checking
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 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]
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])
for variable in 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]
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])
return result

@ -0,0 +1,56 @@
from typing import Dict
import re
from slither.core.expressions.unary_operation import UnaryOperationType
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,
]
class UOI(AbstractMutator): # pylint: disable=too-few-public-methods
NAME = "UOI"
HELP = "Unary operator insertion"
FAULTCLASS = FaultClass.Checking
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 node in function.nodes:
if (node.type == NodeType.EXPRESSION):
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)
return result

@ -1,12 +1,12 @@
import abc
import logging
from enum import Enum
from typing import Optional, Dict, Tuple
from typing import Optional, Dict, Tuple, List
from slither.core.compilation_unit import SlitherCompilationUnit
from slither.tools.doctor.utils import snip_section
from slither.formatters.utils.patches import apply_patch, create_diff
from slither.tools.mutator.utils.testing_generated_mutant import test_patch
logger = logging.getLogger("Slither-Mutate")
@ -34,6 +34,8 @@ class AbstractMutator(metaclass=abc.ABCMeta): # pylint: disable=too-few-public-
HELP = ""
FAULTCLASS = FaultClass.Undefined
FAULTNATURE = FaultNature.Undefined
VALID_MUTANTS_COUNT = 0
INVALID_MUTANTS_COUNT = 0
def __init__(
self, compilation_unit: SlitherCompilationUnit, rate: int = 10, seed: Optional[int] = None
@ -73,13 +75,18 @@ class AbstractMutator(metaclass=abc.ABCMeta): # pylint: disable=too-few-public-
"""TODO Documentation"""
return {}
def mutate(self, testing_command: str, testing_directory: str, contract_name: str) -> Tuple[(int, int)]:
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
# call _mutate function from different mutators
(all_patches, valid_mutant_count, invalid_mutant_count) = self._mutate(testing_command, testing_directory, contract_name)
(all_patches) = self._mutate()
if "patches" not in all_patches:
logger.debug(f"No patches found by {self.NAME}")
return
return (0,0)
for file in all_patches["patches"]:
original_txt = self.slither.source_code[file].encode("utf8")
@ -87,18 +94,27 @@ class AbstractMutator(metaclass=abc.ABCMeta): # pylint: disable=too-few-public-
offset = 0
patches = all_patches["patches"][file]
patches.sort(key=lambda x: x["start"])
if not all(patches[i]["end"] <= patches[i + 1]["end"] for i in range(len(patches) - 1)):
logger.info(f"Impossible to generate patch; patches collisions: {patches}")
continue
# if not all(patches[i]["end"] <= patches[i + 1]["end"] for i in range(len(patches) - 1)):
# logger.error(f"Impossible to generate patch; patches collisions: {patches}")
# continue
for patch in patches:
patched_txt, offset = apply_patch(patched_txt, patch, offset)
diff = create_diff(self.compilation_unit, original_txt, patched_txt, file)
if not diff:
logger.info(f"Impossible to generate patch; empty {patches}")
# print(patch)
# test the patch
flag = test_patch(file, patch, testing_command, self.VALID_MUTANTS_COUNT, self.NAME)
# count the valid and invalid mutants
if not flag:
self.INVALID_MUTANTS_COUNT += 1
continue
self.VALID_MUTANTS_COUNT += 1
# patched_txt, offset = apply_patch(patched_txt, patch, offset)
# diff = create_diff(self.compilation_unit, original_txt, patched_txt, file)
# if not diff:
# logger.info(f"Impossible to generate patch; empty {patches}")
# print the differences
print(diff)
# print(diff)
return (valid_mutant_count, invalid_mutant_count)
return (self.VALID_MUTANTS_COUNT, self.INVALID_MUTANTS_COUNT)

@ -1,4 +1,9 @@
# 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.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

@ -4,9 +4,10 @@ import logging
logger = logging.getLogger("Slither-Mutate")
duplicated_files = {}
# function to backup the source file
def backup_source_file(source_code: Dict, output_folder: str) -> Dict:
duplicated_files = {}
os.makedirs(output_folder, exist_ok=True)
for file_path, content in source_code.items():
@ -23,7 +24,8 @@ def backup_source_file(source_code: Dict, output_folder: str) -> Dict:
# function to transfer the original content to the sol file after campaign
def transfer_and_delete(files_dict: Dict) -> None:
try:
for item, value in files_dict.items():
files_dict_copy = files_dict.copy()
for item, value in files_dict_copy.items():
with open(value, 'r') as duplicated_file:
content = duplicated_file.read()
@ -31,6 +33,10 @@ def transfer_and_delete(files_dict: Dict) -> None:
original_file.write(content)
os.remove(value)
# delete elements from the global dict
del duplicated_files[item]
except Exception as e:
logger.error(f"Error transferring content: {e}")
@ -45,14 +51,36 @@ def create_mutant_file(file: str, count: int, rule: str) -> None:
# 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 + '/' + rule + '_' + str(count) + '.sol', 'w') as mutant_file:
with open("mutation_campaign/" + mutant_name + '/' + mutant_name + '_' + rule + '_' + str(count) + '.sol', 'w') as mutant_file:
mutant_file.write(content)
# reset the file
with open(duplicated_files[file], 'r') as duplicated_file:
duplicate_content = duplicated_file.read()
with open(file, 'w') as source_file:
source_file.write(duplicate_content)
except Exception as e:
logger.error(f"Error creating mutant: {e}")
# function to reset the file
def reset_file(file: str) -> None:
try:
# directory, filename = os.path.split(file)
# reset the file
with open(duplicated_files[file], 'r') as duplicated_file:
duplicate_content = duplicated_file.read()
with open(file, 'w') as source_file:
source_file.write(duplicate_content)
except Exception as e:
logger.error(f"Error resetting file: {e}")
# function to get the contracts list
def get_sol_file_list(codebase: str, ignore_paths: List[str] | None) -> List[str]:
sol_file_list = []

@ -8,7 +8,7 @@ from slither.tools.mutator.utils.testing_generated_mutant import compile_generat
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, test_cmd: str, test_dir: str) -> bool:
def remove_assignement(variable: Variable, contract: Contract, result: Dict) -> bool:
"""
Remove the variable's initial assignement
@ -28,19 +28,13 @@ def remove_assignement(variable: Variable, contract: Contract, result: Dict, tes
old_str = in_file_str[start:stop]
new_str = old_str[: old_str.find("=")]
replace_string_in_source_file(in_file, in_file_str[variable.source_mapping.start + old_str.find("="):variable.source_mapping.end], '')
# compile and run tests before the mutant generated before patching
if compile_generated_mutant(in_file):
if run_test_suite(test_cmd, test_dir):
# create_mutant_file(in_file, )
create_patch(
result,
in_file,
start,
stop + variable.expression.source_mapping.length,
old_str,
new_str,
)
return True
line_no = [0]
create_patch(
result,
in_file,
start,
stop + variable.expression.source_mapping.length,
old_str,
new_str,
line_no
)

@ -2,8 +2,13 @@ import crytic_compile
import subprocess
import os
import logging
import time
import signal
from typing import List, Dict
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")
timeout = 30 # we can get it as parameter
# function to compile the generated mutant
def compile_generated_mutant(file_path: str) -> bool:
@ -11,7 +16,9 @@ def compile_generated_mutant(file_path: str) -> bool:
crytic_compile.CryticCompile(file_path)
return True
except Exception as e: # pylint: disable=broad-except
logger.error("Error Crytic Compile", e)
print(True)
# logger.error("Error Crytic Compile")
return False
# function to run the tests
def run_test_suite(cmd: str, dir: str) -> bool:
@ -21,14 +28,55 @@ def run_test_suite(cmd: str, dir: str) -> bool:
result = subprocess.run(cmd.split(' '), check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
# result = subprocess.run(cmd.split(' '), check=True)
print(result.stdout)
if not result.stderr:
return True
except subprocess.CalledProcessError as e:
print(e.output)
logger.error(f"Error executing '{cmd}': {e}")
return False
except Exception as e:
logger.error(f"An unexpected error occurred: {e}")
return False
return False
def run_test_cmd(cmd: str, dir: str) -> 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
while P.poll() is None and (time.time() - start) < timeout:
time.sleep(0.05)
finally:
if P.poll() is None:
print()
print("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 True if r == 0 else False
def test_patch(file: str, patch: Dict, command: str, index: int, generator_name: str) -> 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)):
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
reset_file(file)
logger.info(red(f"String '{patch['old_string']}' replaced with '{patch['new_string']}' at line no. '{patch['line_number']}' in '{file}' ---> INVALID\n"))
return False
Loading…
Cancel
Save