Updated replace string logic

pull/2278/head
Vishnuram Rajkumar 11 months ago
parent a0365982ad
commit 37c23e11ce
  1. 31
      slither/tools/mutator/__main__.py
  2. 72
      slither/tools/mutator/mutators/MIA.py
  3. 44
      slither/tools/mutator/mutators/MVIE.py
  4. 44
      slither/tools/mutator/mutators/MVIV.py
  5. 8
      slither/tools/mutator/mutators/abstract_mutator.py
  6. 4
      slither/tools/mutator/mutators/all_mutators.py
  7. 10
      slither/tools/mutator/utils/file_handling.py
  8. 9
      slither/tools/mutator/utils/replace_conditions.py
  9. 7
      slither/tools/mutator/utils/testing_generated_mutant.py

@ -40,11 +40,13 @@ def parse_args() -> argparse.Namespace:
default=False,
)
# argument to add the test command
parser.add_argument(
"--test-cmd",
help="Command line needed to run the tests for your project"
)
# argument to add the test directory - containing all the tests
parser.add_argument(
"--test-dir",
help="Directory of tests"
@ -96,13 +98,14 @@ def main() -> None:
# arguments
test_command: str = args.test_cmd
test_directory: str = args.test_dir
paths_to_ignore: List[str] = args.ignore_dirs
paths_to_ignore: List[str] | None = args.ignore_dirs
# get all the contracts as a list from given codebase
sol_file_list: List[str] = get_sol_file_list(args.codebase, paths_to_ignore)
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))
@ -112,9 +115,12 @@ def main() -> None:
# create a backup files
files_dict = backup_source_file(sl.source_code, output_folder)
# total count of valid mutants
# 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:
@ -123,19 +129,24 @@ def main() -> None:
# print(i.name)
for M in _get_mutators():
m = M(compilation_unit_of_main_file)
count = m.mutate(test_command, test_directory)
if count != None:
total_count = total_count + count
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
except Exception as e:
logger.error(e)
except KeyboardInterrupt:
# transfer and delete the backup files if interrupted
logger.error("\nExecution interrupted by user (Ctrl + C). Cleaning up...")
transfer_and_delete(files_dict)
# transfer and delete the backup files
transfer_and_delete(files_dict)
# output
print(f"Done mutating, '{filename}'")
print(f"Valid mutant count: '{total_count}'\n")
# output
print(f"Done mutating, '{filename}'. Valid mutant count: '{v_count}' and Total mutant count '{total_count}'.\n")
print("Finished Mutation Campaign in", args.codebase, "\n")
# endregion

@ -11,47 +11,53 @@ class MIA(AbstractMutator): # pylint: disable=too-few-public-methods
HELP = '"if" construct around statement'
FAULTCLASS = FaultClass.Checking
FAULTNATURE = FaultNature.Missing
VALID_MUTANTS_COUNT = 1
VALID_MUTANTS_COUNT = 0
INVALID_MUTANTS_COUNT = 0
def _mutate(self, test_cmd: str, test_dir: str) -> Tuple[(Dict, int)]:
def _mutate(self, test_cmd: str, test_dir: str, contract_name: str) -> Tuple[(Dict, int, int)]:
result: Dict = {}
for contract in self.slither.contracts:
if not contract.is_library:
if not contract.is_interface:
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]
# 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]
# 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
print(line_no)
# Replace the expression with true
new_str = "true"
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):
# print(True)
# 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)
# 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)
return (result, self.VALID_MUTANTS_COUNT)
return (result, self.VALID_MUTANTS_COUNT, self.INVALID_MUTANTS_COUNT)

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

@ -11,32 +11,34 @@ class MVIV(AbstractMutator): # pylint: disable=too-few-public-methods
HELP = "variable initialization using a value"
FAULTCLASS = FaultClass.Assignement
FAULTNATURE = FaultNature.Missing
VALID_MUTANTS_COUNT = 1
VALID_MUTANTS_COUNT = 0
INVALID_MUTANTS_COUNT = 0
def _mutate(self, test_cmd: str, test_dir: str) -> Tuple[(Dict, int)]:
def _mutate(self, test_cmd: str, test_dir: str, contract_name: str) -> Tuple[(Dict, int, int)]:
result: Dict = {}
variable: Variable
for contract in self.slither.contracts:
if not contract.is_library:
if not contract.is_interface:
# 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 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):
if(remove_assignement(variable, contract, result, test_cmd, test_dir)):
create_mutant_file(contract.source_mapping.filename.absolute, self.VALID_MUTANTS_COUNT, self.NAME)
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):
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)
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)

@ -1,7 +1,7 @@
import abc
import logging
from enum import Enum
from typing import Optional, Dict
from typing import Optional, Dict, Tuple
from slither.core.compilation_unit import SlitherCompilationUnit
from slither.tools.doctor.utils import snip_section
@ -73,9 +73,9 @@ class AbstractMutator(metaclass=abc.ABCMeta): # pylint: disable=too-few-public-
"""TODO Documentation"""
return {}
def mutate(self, testing_command: str, testing_directory: str) -> int:
def mutate(self, testing_command: str, testing_directory: str, contract_name: str) -> Tuple[(int, int)]:
# call _mutate function from different mutators
(all_patches, valid_mutant_count) = self._mutate(testing_command, testing_directory)
(all_patches, valid_mutant_count, invalid_mutant_count) = self._mutate(testing_command, testing_directory, contract_name)
if "patches" not in all_patches:
logger.debug(f"No patches found by {self.NAME}")
@ -98,7 +98,7 @@ class AbstractMutator(metaclass=abc.ABCMeta): # pylint: disable=too-few-public-
# print the differences
print(diff)
return valid_mutant_count
return (valid_mutant_count, invalid_mutant_count)

@ -1,4 +1,4 @@
# 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.MVIV import MVIV
# from slither.tools.mutator.mutators.MVIE import MVIE
from slither.tools.mutator.mutators.MIA import MIA

@ -36,6 +36,7 @@ def transfer_and_delete(files_dict: Dict) -> None:
#function to create new mutant file
def create_mutant_file(file: str, count: int, rule: str) -> None:
try:
directory, filename = os.path.split(file)
# Read content from the duplicated file
@ -44,16 +45,19 @@ def create_mutant_file(file: str, count: int, rule: str) -> None:
# Write content to the original file
mutant_name = filename.split('.')[0]
with open("mutation_campaign/" + mutant_name + '_' + rule + '_' + str(count) + '.sol', 'w') as mutant_file:
# 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:
mutant_file.write(content)
except Exception as e:
logger.error(f"Error creating mutant: {e}")
# function to get the contracts list
def get_sol_file_list(codebase: str, ignore_paths: List[str]) -> List[str]:
def get_sol_file_list(codebase: str, ignore_paths: List[str] | None) -> List[str]:
sol_file_list = []
if ignore_paths == None:
ignore_paths = []
# if input is contract file
if os.path.isfile(codebase):
return [codebase]

@ -1,4 +1,5 @@
import logging
import re
logger = logging.getLogger("Slither-Mutate")
@ -8,7 +9,7 @@ def replace_string_in_source_file(file_path: str, old_string: str, new_string: s
# 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)
@ -28,8 +29,12 @@ def replace_string_in_source_file_specific_line(file_path: str, old_string: str,
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] = lines[line_number - 1].replace(old_string, new_string)
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:

@ -20,11 +20,14 @@ def run_test_suite(cmd: str, dir: str) -> bool:
# os.chdir(dir)
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:
logger.error(f"Error executing 'forge test': {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}")

Loading…
Cancel
Save