mirror of https://github.com/crytic/slither
commit
9f9c572b2d
@ -0,0 +1,25 @@ |
||||
from typing import TYPE_CHECKING |
||||
|
||||
from slither.core.declarations.contract_level import ContractLevel |
||||
from slither.core.declarations import Event |
||||
|
||||
if TYPE_CHECKING: |
||||
from slither.core.declarations import Contract |
||||
|
||||
|
||||
class EventContract(Event, ContractLevel): |
||||
def is_declared_by(self, contract: "Contract") -> bool: |
||||
""" |
||||
Check if the element is declared by the contract |
||||
:param contract: |
||||
:return: |
||||
""" |
||||
return self.contract == contract |
||||
|
||||
@property |
||||
def canonical_name(self) -> str: |
||||
"""Return the function signature as a str |
||||
Returns: |
||||
str: contract.func_name(type1,type2) |
||||
""" |
||||
return self.contract.name + "." + self.full_name |
@ -0,0 +1,13 @@ |
||||
from typing import TYPE_CHECKING |
||||
|
||||
from slither.core.declarations import Event |
||||
from slither.core.declarations.top_level import TopLevel |
||||
|
||||
if TYPE_CHECKING: |
||||
from slither.core.scope.scope import FileScope |
||||
|
||||
|
||||
class EventTopLevel(Event, TopLevel): |
||||
def __init__(self, scope: "FileScope") -> None: |
||||
super().__init__() |
||||
self.file_scope: "FileScope" = scope |
@ -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,2 @@ |
||||
C.f() (tests/e2e/detectors/test_data/incorrect-shift/0.6.11/shift_parameter_mixup.sol#3-7) contains an incorrect shift operation: a = 8 >> a (tests/e2e/detectors/test_data/incorrect-shift/0.6.11/shift_parameter_mixup.sol#5) |
||||
C.f() (tests/e2e/detectors/test_data/incorrect-shift/0.6.11/shift_parameter_mixup.sol#3-10) contains an incorrect shift operation: a = 8 >> a (tests/e2e/detectors/test_data/incorrect-shift/0.6.11/shift_parameter_mixup.sol#5) |
||||
|
||||
|
@ -1,2 +1,2 @@ |
||||
C.f() (tests/e2e/detectors/test_data/incorrect-shift/0.7.6/shift_parameter_mixup.sol#3-8) contains an incorrect shift operation: a = 8 >> a (tests/e2e/detectors/test_data/incorrect-shift/0.7.6/shift_parameter_mixup.sol#5) |
||||
C.f() (tests/e2e/detectors/test_data/incorrect-shift/0.7.6/shift_parameter_mixup.sol#3-10) contains an incorrect shift operation: a = 8 >> a (tests/e2e/detectors/test_data/incorrect-shift/0.7.6/shift_parameter_mixup.sol#5) |
||||
|
||||
|
@ -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 |
||||
|
||||
|
@ -1,8 +1,11 @@ |
||||
contract C { |
||||
|
||||
function f() internal returns (uint a) { |
||||
function f() internal returns (uint a, uint b) { |
||||
assembly { |
||||
a := shr(a, 8) |
||||
b := shl(248, 0xff) |
||||
} |
||||
uint y = 1; |
||||
uint g = 0xff << y; |
||||
} |
||||
} |
Binary file not shown.
@ -1,8 +1,11 @@ |
||||
contract C { |
||||
|
||||
function f() internal returns (uint a) { |
||||
function f() internal returns (uint a, uint b) { |
||||
assembly { |
||||
a := shr(a, 8) |
||||
b := shl(248, 0xff) |
||||
} |
||||
uint y = 1; |
||||
uint g = 0xff << y; |
||||
} |
||||
} |
Binary file not shown.
@ -1,8 +1,11 @@ |
||||
contract C { |
||||
|
||||
function f() internal returns (uint a) { |
||||
function f() internal returns (uint a, uint b) { |
||||
assembly { |
||||
a := shr(a, 8) |
||||
b := shl(248, 0xff) |
||||
} |
||||
uint y = 1; |
||||
uint g = 0xff << y; |
||||
} |
||||
} |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue