mirror of https://github.com/crytic/slither
parent
40536d8e88
commit
a0365982ad
@ -1,39 +1,60 @@ |
||||
from typing import Dict |
||||
|
||||
from typing import Dict, Tuple |
||||
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 = 1 |
||||
|
||||
def _mutate(self) -> Dict: |
||||
def _mutate(self, test_cmd: str, test_dir: str) -> Tuple[(Dict, int)]: |
||||
|
||||
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) |
||||
|
||||
return result |
||||
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] |
||||
|
||||
# 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) |
||||
|
||||
|
||||
|
||||
return (result, self.VALID_MUTANTS_COUNT) |
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -1,36 +1,41 @@ |
||||
from typing import Dict |
||||
from typing import Dict, Tuple |
||||
|
||||
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 = 1 |
||||
|
||||
def _mutate(self) -> Dict: |
||||
def _mutate(self, test_cmd: str, test_dir: str) -> Tuple[(Dict, int)]: |
||||
|
||||
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) |
||||
|
||||
return result |
||||
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 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) |
||||
|
@ -1,37 +1,42 @@ |
||||
from typing import Dict |
||||
from typing import Dict, Tuple |
||||
|
||||
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 = 1 |
||||
|
||||
def _mutate(self) -> Dict: |
||||
def _mutate(self, test_cmd: str, test_dir: str) -> Tuple[(Dict, int)]: |
||||
|
||||
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) |
||||
|
||||
return result |
||||
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 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) |
||||
|
@ -0,0 +1,77 @@ |
||||
import os |
||||
from typing import Dict, Tuple, List |
||||
import logging |
||||
|
||||
logger = logging.getLogger("Slither-Mutate") |
||||
|
||||
# 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(): |
||||
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') as new_file: |
||||
new_file.write(content) |
||||
duplicated_files[file_path] = new_file_path |
||||
|
||||
return duplicated_files |
||||
|
||||
# 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(): |
||||
with open(value, 'r') as duplicated_file: |
||||
content = duplicated_file.read() |
||||
|
||||
with open(item, 'w') as original_file: |
||||
original_file.write(content) |
||||
|
||||
os.remove(value) |
||||
except Exception as e: |
||||
logger.error(f"Error transferring content: {e}") |
||||
|
||||
#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 |
||||
with open(file, 'r') as source_file: |
||||
content = source_file.read() |
||||
|
||||
# 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: |
||||
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]: |
||||
sol_file_list = [] |
||||
|
||||
# if input is contract file |
||||
if os.path.isfile(codebase): |
||||
return [codebase] |
||||
|
||||
# if input is folder |
||||
elif 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): |
||||
directory_name, 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 |
||||
# to_do: create a function to delete the commands from the sol file |
||||
# def remove_comments(self) -> None: |
@ -0,0 +1,43 @@ |
||||
import logging |
||||
|
||||
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): |
||||
# 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) |
||||
|
||||
# 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}') |
@ -0,0 +1,31 @@ |
||||
import crytic_compile |
||||
import subprocess |
||||
import os |
||||
import logging |
||||
|
||||
logger = logging.getLogger("Slither-Mutate") |
||||
|
||||
# function to compile the generated mutant |
||||
def compile_generated_mutant(file_path: str) -> bool: |
||||
try: |
||||
crytic_compile.CryticCompile(file_path) |
||||
return True |
||||
except Exception as e: # pylint: disable=broad-except |
||||
logger.error("Error Crytic Compile", e) |
||||
|
||||
# function to run the tests |
||||
def run_test_suite(cmd: str, dir: str) -> bool: |
||||
try: |
||||
# Change to the foundry folder |
||||
# os.chdir(dir) |
||||
|
||||
result = subprocess.run(cmd.split(' '), check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) |
||||
|
||||
if not result.stderr: |
||||
return True |
||||
except subprocess.CalledProcessError as e: |
||||
logger.error(f"Error executing 'forge test': {e}") |
||||
return False |
||||
except Exception as e: |
||||
logger.error(f"An unexpected error occurred: {e}") |
||||
return False |
Loading…
Reference in new issue