mirror of https://github.com/crytic/slither
commit
2658a6b58d
@ -0,0 +1,12 @@ |
|||||||
|
|
||||||
|
class ChildExpression: |
||||||
|
def __init__(self): |
||||||
|
super(ChildExpression, self).__init__() |
||||||
|
self._expression = None |
||||||
|
|
||||||
|
def set_expression(self, expression): |
||||||
|
self._expression = expression |
||||||
|
|
||||||
|
@property |
||||||
|
def expression(self): |
||||||
|
return self._expression |
@ -0,0 +1,36 @@ |
|||||||
|
import re |
||||||
|
from slither.formatters.exceptions import FormatError |
||||||
|
from slither.formatters.utils.patches import create_patch |
||||||
|
|
||||||
|
def format(slither, result): |
||||||
|
elements = result['elements'] |
||||||
|
for element in elements: |
||||||
|
if element['type'] != "function": |
||||||
|
# Skip variable elements |
||||||
|
continue |
||||||
|
target_contract = slither.get_contract_from_name(element['type_specific_fields']['parent']['name']) |
||||||
|
if target_contract: |
||||||
|
function = target_contract.get_function_from_signature(element['type_specific_fields']['signature']) |
||||||
|
if function: |
||||||
|
_patch(slither, |
||||||
|
result, |
||||||
|
element['source_mapping']['filename_absolute'], |
||||||
|
int(function.parameters_src.source_mapping['start'] + |
||||||
|
function.parameters_src.source_mapping['length']), |
||||||
|
int(function.returns_src.source_mapping['start'])) |
||||||
|
|
||||||
|
|
||||||
|
def _patch(slither, result, in_file, modify_loc_start, modify_loc_end): |
||||||
|
in_file_str = slither.source_code[in_file].encode('utf8') |
||||||
|
old_str_of_interest = in_file_str[modify_loc_start:modify_loc_end] |
||||||
|
# Find the keywords view|pure|constant and remove them |
||||||
|
m = re.search("(view|pure|constant)", old_str_of_interest.decode('utf-8')) |
||||||
|
if m: |
||||||
|
create_patch(result, |
||||||
|
in_file, |
||||||
|
modify_loc_start + m.span()[0], |
||||||
|
modify_loc_start + m.span()[1], |
||||||
|
m.groups(0)[0], # this is view|pure|constant |
||||||
|
"") |
||||||
|
else: |
||||||
|
raise FormatError("No view/pure/constant specifier exists. Regex failed to remove specifier!") |
@ -0,0 +1,69 @@ |
|||||||
|
import re |
||||||
|
from slither.formatters.exceptions import FormatImpossible |
||||||
|
from slither.formatters.utils.patches import create_patch |
||||||
|
|
||||||
|
# Indicates the recommended versions for replacement |
||||||
|
REPLACEMENT_VERSIONS = ["^0.4.25", "^0.5.3"] |
||||||
|
|
||||||
|
# group: |
||||||
|
# 0: ^ > >= < <= (optional) |
||||||
|
# 1: ' ' (optional) |
||||||
|
# 2: version number |
||||||
|
# 3: version number |
||||||
|
# 4: version number |
||||||
|
PATTERN = re.compile('(\^|>|>=|<|<=)?([ ]+)?(\d+)\.(\d+)\.(\d+)') |
||||||
|
|
||||||
|
|
||||||
|
def format(slither, result): |
||||||
|
elements = result['elements'] |
||||||
|
versions_used = [] |
||||||
|
for element in elements: |
||||||
|
versions_used.append(''.join(element['type_specific_fields']['directive'][1:])) |
||||||
|
solc_version_replace = _analyse_versions(versions_used) |
||||||
|
for element in elements: |
||||||
|
_patch(slither, result, element['source_mapping']['filename_absolute'], solc_version_replace, |
||||||
|
element['source_mapping']['start'], |
||||||
|
element['source_mapping']['start'] + element['source_mapping']['length']) |
||||||
|
|
||||||
|
|
||||||
|
def _analyse_versions(used_solc_versions): |
||||||
|
replace_solc_versions = list() |
||||||
|
for version in used_solc_versions: |
||||||
|
replace_solc_versions.append(_determine_solc_version_replacement(version)) |
||||||
|
if not all(version == replace_solc_versions[0] for version in replace_solc_versions): |
||||||
|
raise FormatImpossible("Multiple incompatible versions!") |
||||||
|
else: |
||||||
|
return replace_solc_versions[0] |
||||||
|
|
||||||
|
|
||||||
|
def _determine_solc_version_replacement(used_solc_version): |
||||||
|
versions = PATTERN.findall(used_solc_version) |
||||||
|
if len(versions) == 1: |
||||||
|
version = versions[0] |
||||||
|
minor_version = '.'.join(version[2:])[2] |
||||||
|
if minor_version == '4': |
||||||
|
return "pragma solidity " + REPLACEMENT_VERSIONS[0] + ';' |
||||||
|
elif minor_version == '5': |
||||||
|
return "pragma solidity " + REPLACEMENT_VERSIONS[1] + ';' |
||||||
|
else: |
||||||
|
raise FormatImpossible("Unknown version!") |
||||||
|
elif len(versions) == 2: |
||||||
|
version_right = versions[1] |
||||||
|
minor_version_right = '.'.join(version_right[2:])[2] |
||||||
|
if minor_version_right == '4': |
||||||
|
# Replace with 0.4.25 |
||||||
|
return "pragma solidity " + REPLACEMENT_VERSIONS[0] + ';' |
||||||
|
elif minor_version_right in ['5', '6']: |
||||||
|
# Replace with 0.5.3 |
||||||
|
return "pragma solidity " + REPLACEMENT_VERSIONS[1] + ';' |
||||||
|
|
||||||
|
|
||||||
|
def _patch(slither, result, in_file, pragma, modify_loc_start, modify_loc_end): |
||||||
|
in_file_str = slither.source_code[in_file].encode('utf8') |
||||||
|
old_str_of_interest = in_file_str[modify_loc_start:modify_loc_end] |
||||||
|
create_patch(result, |
||||||
|
in_file, |
||||||
|
int(modify_loc_start), |
||||||
|
int(modify_loc_end), |
||||||
|
old_str_of_interest, |
||||||
|
pragma) |
@ -0,0 +1,59 @@ |
|||||||
|
import re |
||||||
|
from slither.formatters.exceptions import FormatImpossible |
||||||
|
from slither.formatters.utils.patches import create_patch |
||||||
|
|
||||||
|
|
||||||
|
# Indicates the recommended versions for replacement |
||||||
|
REPLACEMENT_VERSIONS = ["^0.4.25", "^0.5.3"] |
||||||
|
|
||||||
|
# group: |
||||||
|
# 0: ^ > >= < <= (optional) |
||||||
|
# 1: ' ' (optional) |
||||||
|
# 2: version number |
||||||
|
# 3: version number |
||||||
|
# 4: version number |
||||||
|
PATTERN = re.compile('(\^|>|>=|<|<=)?([ ]+)?(\d+)\.(\d+)\.(\d+)') |
||||||
|
|
||||||
|
def format(slither, result): |
||||||
|
elements = result['elements'] |
||||||
|
for element in elements: |
||||||
|
solc_version_replace = _determine_solc_version_replacement( |
||||||
|
''.join(element['type_specific_fields']['directive'][1:])) |
||||||
|
|
||||||
|
_patch(slither, result, element['source_mapping']['filename_absolute'], solc_version_replace, |
||||||
|
element['source_mapping']['start'], element['source_mapping']['start'] + |
||||||
|
element['source_mapping']['length']) |
||||||
|
|
||||||
|
def _determine_solc_version_replacement(used_solc_version): |
||||||
|
versions = PATTERN.findall(used_solc_version) |
||||||
|
if len(versions) == 1: |
||||||
|
version = versions[0] |
||||||
|
minor_version = '.'.join(version[2:])[2] |
||||||
|
if minor_version == '4': |
||||||
|
# Replace with 0.4.25 |
||||||
|
return "pragma solidity " + REPLACEMENT_VERSIONS[0] + ';' |
||||||
|
elif minor_version == '5': |
||||||
|
# Replace with 0.5.3 |
||||||
|
return "pragma solidity " + REPLACEMENT_VERSIONS[1] + ';' |
||||||
|
else: |
||||||
|
raise FormatImpossible(f"Unknown version {versions}") |
||||||
|
elif len(versions) == 2: |
||||||
|
version_right = versions[1] |
||||||
|
minor_version_right = '.'.join(version_right[2:])[2] |
||||||
|
if minor_version_right == '4': |
||||||
|
# Replace with 0.4.25 |
||||||
|
return "pragma solidity " + REPLACEMENT_VERSIONS[0] + ';' |
||||||
|
elif minor_version_right in ['5','6']: |
||||||
|
# Replace with 0.5.3 |
||||||
|
return "pragma solidity " + REPLACEMENT_VERSIONS[1] + ';' |
||||||
|
|
||||||
|
|
||||||
|
def _patch(slither, result, in_file, solc_version, modify_loc_start, modify_loc_end): |
||||||
|
in_file_str = slither.source_code[in_file].encode('utf8') |
||||||
|
old_str_of_interest = in_file_str[modify_loc_start:modify_loc_end] |
||||||
|
create_patch(result, |
||||||
|
in_file, |
||||||
|
int(modify_loc_start), |
||||||
|
int(modify_loc_end), |
||||||
|
old_str_of_interest, |
||||||
|
solc_version) |
@ -0,0 +1,5 @@ |
|||||||
|
from slither.exceptions import SlitherException |
||||||
|
|
||||||
|
class FormatImpossible(SlitherException): pass |
||||||
|
|
||||||
|
class FormatError(SlitherException): pass |
@ -0,0 +1,42 @@ |
|||||||
|
import re |
||||||
|
from slither.formatters.utils.patches import create_patch |
||||||
|
|
||||||
|
def format(slither, result): |
||||||
|
elements = result['elements'] |
||||||
|
for element in elements: |
||||||
|
target_contract = slither.get_contract_from_name(element['type_specific_fields']['parent']['name']) |
||||||
|
if target_contract: |
||||||
|
function = target_contract.get_function_from_signature(element['type_specific_fields']['signature']) |
||||||
|
if function: |
||||||
|
_patch(slither, |
||||||
|
result, |
||||||
|
element['source_mapping']['filename_absolute'], |
||||||
|
int(function.parameters_src.source_mapping['start']), |
||||||
|
int(function.returns_src.source_mapping['start'])) |
||||||
|
|
||||||
|
|
||||||
|
def _patch(slither, result, in_file, modify_loc_start, modify_loc_end): |
||||||
|
in_file_str = slither.source_code[in_file].encode('utf8') |
||||||
|
old_str_of_interest = in_file_str[modify_loc_start:modify_loc_end] |
||||||
|
# Search for 'public' keyword which is in-between the function name and modifier name (if present) |
||||||
|
# regex: 'public' could have spaces around or be at the end of the line |
||||||
|
m = re.search(r'((\spublic)\s+)|(\spublic)$|(\)public)$', old_str_of_interest.decode('utf-8')) |
||||||
|
if m is None: |
||||||
|
# No visibility specifier exists; public by default. |
||||||
|
create_patch(result, |
||||||
|
in_file, |
||||||
|
# start after the function definition's closing paranthesis |
||||||
|
modify_loc_start + len(old_str_of_interest.decode('utf-8').split(')')[0]) + 1, |
||||||
|
# end is same as start because we insert the keyword `external` at that location |
||||||
|
modify_loc_start + len(old_str_of_interest.decode('utf-8').split(')')[0]) + 1, |
||||||
|
"", |
||||||
|
" external") # replace_text is `external` |
||||||
|
else: |
||||||
|
create_patch(result, |
||||||
|
in_file, |
||||||
|
# start at the keyword `public` |
||||||
|
modify_loc_start + m.span()[0] + 1, |
||||||
|
# end after the keyword `public` = start + len('public'') |
||||||
|
modify_loc_start + m.span()[0] + 1 + len('public'), |
||||||
|
"public", |
||||||
|
"external") |
@ -0,0 +1,609 @@ |
|||||||
|
import re |
||||||
|
import logging |
||||||
|
from slither.slithir.operations import Send, Transfer, OperationWithLValue, HighLevelCall, LowLevelCall, \ |
||||||
|
InternalCall, InternalDynamicCall |
||||||
|
from slither.core.declarations import Modifier |
||||||
|
from slither.core.solidity_types import UserDefinedType, MappingType |
||||||
|
from slither.core.declarations import Enum, Contract, Structure, Function |
||||||
|
from slither.core.solidity_types.elementary_type import ElementaryTypeName |
||||||
|
from slither.core.variables.local_variable import LocalVariable |
||||||
|
from slither.formatters.exceptions import FormatError, FormatImpossible |
||||||
|
from slither.formatters.utils.patches import create_patch |
||||||
|
|
||||||
|
logging.basicConfig(level=logging.INFO) |
||||||
|
logger = logging.getLogger('Slither.Format') |
||||||
|
|
||||||
|
def format(slither, result): |
||||||
|
elements = result['elements'] |
||||||
|
for element in elements: |
||||||
|
target = element['additional_fields']['target'] |
||||||
|
|
||||||
|
convention = element['additional_fields']['convention'] |
||||||
|
|
||||||
|
if convention == "l_O_I_should_not_be_used": |
||||||
|
# l_O_I_should_not_be_used cannot be automatically patched |
||||||
|
logger.info(f'The following naming convention cannot be patched: \n{result["description"]}') |
||||||
|
continue |
||||||
|
|
||||||
|
_patch(slither, result, element, target) |
||||||
|
|
||||||
|
# endregion |
||||||
|
################################################################################### |
||||||
|
################################################################################### |
||||||
|
# region Conventions |
||||||
|
################################################################################### |
||||||
|
################################################################################### |
||||||
|
|
||||||
|
KEY = 'ALL_NAMES_USED' |
||||||
|
|
||||||
|
# https://solidity.readthedocs.io/en/v0.5.11/miscellaneous.html#reserved-keywords |
||||||
|
SOLIDITY_KEYWORDS = ['abstract', 'after', 'alias', 'apply', 'auto', 'case', 'catch', 'copyof', 'default', 'define', |
||||||
|
'final', 'immutable', 'implements', 'in', 'inline', 'let', 'macro', 'match', 'mutable', 'null', |
||||||
|
'of', 'override', 'partial', 'promise', 'reference', 'relocatable', 'sealed', 'sizeof', 'static', |
||||||
|
'supports', 'switch', 'try', 'typedef', 'typeof', 'unchecked'] |
||||||
|
|
||||||
|
# https://solidity.readthedocs.io/en/v0.5.11/miscellaneous.html#language-grammar |
||||||
|
SOLIDITY_KEYWORDS += ['pragma', 'import', 'contract', 'library', 'contract', 'function', 'using', 'struct', 'enum', |
||||||
|
'public', 'private', 'internal', 'external', 'calldata', 'memory', 'modifier', 'view', 'pure', |
||||||
|
'constant', 'storage', 'for', 'if', 'while', 'break', 'return', 'throw', 'else', 'type'] |
||||||
|
|
||||||
|
SOLIDITY_KEYWORDS += ElementaryTypeName |
||||||
|
|
||||||
|
def _name_already_use(slither, name): |
||||||
|
# Do not convert to a name used somewhere else |
||||||
|
if not KEY in slither.context: |
||||||
|
all_names = set() |
||||||
|
for contract in slither.contracts_derived: |
||||||
|
all_names = all_names.union(set([st.name for st in contract.structures])) |
||||||
|
all_names = all_names.union(set([f.name for f in contract.functions_and_modifiers])) |
||||||
|
all_names = all_names.union(set([e.name for e in contract.enums])) |
||||||
|
all_names = all_names.union(set([s.name for s in contract.state_variables])) |
||||||
|
|
||||||
|
for function in contract.functions: |
||||||
|
all_names = all_names.union(set([v.name for v in function.variables])) |
||||||
|
|
||||||
|
slither.context[KEY] = all_names |
||||||
|
return name in slither.context[KEY] |
||||||
|
|
||||||
|
def _convert_CapWords(original_name, slither): |
||||||
|
name = original_name.capitalize() |
||||||
|
|
||||||
|
while '_' in name: |
||||||
|
offset = name.find('_') |
||||||
|
if len(name) > offset: |
||||||
|
name = name[0:offset] + name[offset+1].upper() + name[offset+1:] |
||||||
|
|
||||||
|
if _name_already_use(slither, name): |
||||||
|
raise FormatImpossible(f'{original_name} cannot be converted to {name} (already used)') |
||||||
|
|
||||||
|
if name in SOLIDITY_KEYWORDS: |
||||||
|
raise FormatImpossible(f'{original_name} cannot be converted to {name} (Solidity keyword)') |
||||||
|
return name |
||||||
|
|
||||||
|
def _convert_mixedCase(original_name, slither): |
||||||
|
name = original_name |
||||||
|
if isinstance(name, bytes): |
||||||
|
name = name.decode('utf8') |
||||||
|
|
||||||
|
while '_' in name: |
||||||
|
offset = name.find('_') |
||||||
|
if len(name) > offset: |
||||||
|
name = name[0:offset] + name[offset + 1].upper() + name[offset + 2:] |
||||||
|
|
||||||
|
name = name[0].lower() + name[1:] |
||||||
|
if _name_already_use(slither, name): |
||||||
|
raise FormatImpossible(f'{original_name} cannot be converted to {name} (already used)') |
||||||
|
if name in SOLIDITY_KEYWORDS: |
||||||
|
raise FormatImpossible(f'{original_name} cannot be converted to {name} (Solidity keyword)') |
||||||
|
return name |
||||||
|
|
||||||
|
def _convert_UPPER_CASE_WITH_UNDERSCORES(name, slither): |
||||||
|
if _name_already_use(slither, name.upper()): |
||||||
|
raise FormatImpossible(f'{name} cannot be converted to {name.upper()} (already used)') |
||||||
|
if name.upper() in SOLIDITY_KEYWORDS: |
||||||
|
raise FormatImpossible(f'{name} cannot be converted to {name.upper()} (Solidity keyword)') |
||||||
|
return name.upper() |
||||||
|
|
||||||
|
conventions ={ |
||||||
|
"CapWords":_convert_CapWords, |
||||||
|
"mixedCase":_convert_mixedCase, |
||||||
|
"UPPER_CASE_WITH_UNDERSCORES":_convert_UPPER_CASE_WITH_UNDERSCORES |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
# endregion |
||||||
|
################################################################################### |
||||||
|
################################################################################### |
||||||
|
# region Helpers |
||||||
|
################################################################################### |
||||||
|
################################################################################### |
||||||
|
|
||||||
|
def _get_from_contract(slither, element, name, getter): |
||||||
|
contract_name = element['type_specific_fields']['parent']['name'] |
||||||
|
contract = slither.get_contract_from_name(contract_name) |
||||||
|
return getattr(contract, getter)(name) |
||||||
|
|
||||||
|
# endregion |
||||||
|
################################################################################### |
||||||
|
################################################################################### |
||||||
|
# region Patch dispatcher |
||||||
|
################################################################################### |
||||||
|
################################################################################### |
||||||
|
|
||||||
|
def _patch(slither, result, element, _target): |
||||||
|
|
||||||
|
if _target == "contract": |
||||||
|
target = slither.get_contract_from_name(element['name']) |
||||||
|
|
||||||
|
elif _target == "structure": |
||||||
|
target = _get_from_contract(slither, element, element['name'], 'get_structure_from_name') |
||||||
|
|
||||||
|
elif _target == "event": |
||||||
|
target = _get_from_contract(slither, element, element['name'], 'get_event_from_name') |
||||||
|
|
||||||
|
elif _target == "function": |
||||||
|
# Avoid constructor (FP?) |
||||||
|
if element['name'] != element['type_specific_fields']['parent']['name']: |
||||||
|
function_sig = element['type_specific_fields']['signature'] |
||||||
|
target = _get_from_contract(slither, element, function_sig, 'get_function_from_signature') |
||||||
|
|
||||||
|
elif _target == "modifier": |
||||||
|
modifier_sig = element['type_specific_fields']['signature'] |
||||||
|
target = _get_from_contract(slither, element, modifier_sig, 'get_modifier_from_signature') |
||||||
|
|
||||||
|
elif _target == "parameter": |
||||||
|
contract_name = element['type_specific_fields']['parent']['type_specific_fields']['parent']['name'] |
||||||
|
function_sig = element['type_specific_fields']['parent']['type_specific_fields']['signature'] |
||||||
|
param_name = element['name'] |
||||||
|
contract = slither.get_contract_from_name(contract_name) |
||||||
|
function = contract.get_function_from_signature(function_sig) |
||||||
|
target = function.get_local_variable_from_name(param_name) |
||||||
|
|
||||||
|
elif _target in ["variable", "variable_constant"]: |
||||||
|
# Local variable |
||||||
|
if element['type_specific_fields']['parent'] == 'function': |
||||||
|
contract_name = element['type_specific_fields']['parent']['type_specific_fields']['parent']['name'] |
||||||
|
function_sig = element['type_specific_fields']['parent']['type_specific_fields']['signature'] |
||||||
|
var_name = element['name'] |
||||||
|
contract = slither.get_contract_from_name(contract_name) |
||||||
|
function = contract.get_function_from_signature(function_sig) |
||||||
|
target = function.get_local_variable_from_name(var_name) |
||||||
|
# State variable |
||||||
|
else: |
||||||
|
target = _get_from_contract(slither, element, element['name'], 'get_state_variable_from_name') |
||||||
|
|
||||||
|
elif _target == "enum": |
||||||
|
target = _get_from_contract(slither, element, element['name'], 'get_enum_from_canonical_name') |
||||||
|
|
||||||
|
else: |
||||||
|
raise FormatError("Unknown naming convention! " + _target) |
||||||
|
|
||||||
|
_explore(slither, |
||||||
|
result, |
||||||
|
target, |
||||||
|
conventions[element['additional_fields']['convention']]) |
||||||
|
|
||||||
|
|
||||||
|
# endregion |
||||||
|
################################################################################### |
||||||
|
################################################################################### |
||||||
|
# region Explore functions |
||||||
|
################################################################################### |
||||||
|
################################################################################### |
||||||
|
|
||||||
|
# group 1: beginning of the from type |
||||||
|
# group 2: beginning of the to type |
||||||
|
# nested mapping are within the group 1 |
||||||
|
#RE_MAPPING = '[ ]*mapping[ ]*\([ ]*([\=\>\(\) a-zA-Z0-9\._\[\]]*)[ ]*=>[ ]*([a-zA-Z0-9\._\[\]]*)\)' |
||||||
|
RE_MAPPING_FROM = b'([a-zA-Z0-9\._\[\]]*)' |
||||||
|
RE_MAPPING_TO = b'([\=\>\(\) a-zA-Z0-9\._\[\]\ ]*)' |
||||||
|
RE_MAPPING = b'[ ]*mapping[ ]*\([ ]*' + RE_MAPPING_FROM + b'[ ]*' + b'=>' + b'[ ]*'+ RE_MAPPING_TO + b'\)' |
||||||
|
|
||||||
|
|
||||||
|
def _is_var_declaration(slither, filename, start): |
||||||
|
''' |
||||||
|
Detect usage of 'var ' for Solidity < 0.5 |
||||||
|
:param slither: |
||||||
|
:param filename: |
||||||
|
:param start: |
||||||
|
:return: |
||||||
|
''' |
||||||
|
v = 'var ' |
||||||
|
return slither.source_code[filename][start:start + len(v)] == v |
||||||
|
|
||||||
|
|
||||||
|
def _explore_type(slither, result, target, convert, type, filename_source_code, start, end): |
||||||
|
if isinstance(type, UserDefinedType): |
||||||
|
# Patch type based on contract/enum |
||||||
|
if isinstance(type.type, (Enum, Contract)): |
||||||
|
if type.type == target: |
||||||
|
old_str = type.type.name |
||||||
|
new_str = convert(old_str, slither) |
||||||
|
|
||||||
|
loc_start = start |
||||||
|
if _is_var_declaration(slither, filename_source_code, start): |
||||||
|
loc_end = loc_start + len('var') |
||||||
|
else: |
||||||
|
loc_end = loc_start + len(old_str) |
||||||
|
|
||||||
|
create_patch(result, |
||||||
|
filename_source_code, |
||||||
|
loc_start, |
||||||
|
loc_end, |
||||||
|
old_str, |
||||||
|
new_str) |
||||||
|
|
||||||
|
|
||||||
|
else: |
||||||
|
# Patch type based on structure |
||||||
|
assert isinstance(type.type, Structure) |
||||||
|
if type.type == target: |
||||||
|
old_str = type.type.name |
||||||
|
new_str = convert(old_str, slither) |
||||||
|
|
||||||
|
loc_start = start |
||||||
|
if _is_var_declaration(slither, filename_source_code, start): |
||||||
|
loc_end = loc_start + len('var') |
||||||
|
else: |
||||||
|
loc_end = loc_start + len(old_str) |
||||||
|
|
||||||
|
create_patch(result, |
||||||
|
filename_source_code, |
||||||
|
loc_start, |
||||||
|
loc_end, |
||||||
|
old_str, |
||||||
|
new_str) |
||||||
|
|
||||||
|
# Structure contain a list of elements, that might need patching |
||||||
|
# .elems return a list of VariableStructure |
||||||
|
_explore_variables_declaration(slither, |
||||||
|
type.type.elems.values(), |
||||||
|
result, |
||||||
|
target, |
||||||
|
convert) |
||||||
|
|
||||||
|
if isinstance(type, MappingType): |
||||||
|
# Mapping has three steps: |
||||||
|
# Convert the "from" type |
||||||
|
# Convert the "to" type |
||||||
|
# Convert nested type in the "to" |
||||||
|
# Ex: mapping (mapping (badName => uint) => uint) |
||||||
|
|
||||||
|
# Do the comparison twice, so we can factor together the re matching |
||||||
|
# mapping can only have elementary type in type_from |
||||||
|
if isinstance(type.type_to, (UserDefinedType, MappingType)) or target in [type.type_from, type.type_to]: |
||||||
|
|
||||||
|
full_txt_start = start |
||||||
|
full_txt_end = end |
||||||
|
full_txt = slither.source_code[filename_source_code].encode('utf8')[full_txt_start:full_txt_end] |
||||||
|
re_match = re.match(RE_MAPPING, full_txt) |
||||||
|
assert re_match |
||||||
|
|
||||||
|
if type.type_from == target: |
||||||
|
old_str = type.type_from.name |
||||||
|
new_str = convert(old_str, slither) |
||||||
|
|
||||||
|
loc_start = start + re_match.start(1) |
||||||
|
loc_end = loc_start + len(old_str) |
||||||
|
|
||||||
|
create_patch(result, |
||||||
|
filename_source_code, |
||||||
|
loc_start, |
||||||
|
loc_end, |
||||||
|
old_str, |
||||||
|
new_str) |
||||||
|
|
||||||
|
if type.type_to == target: |
||||||
|
|
||||||
|
old_str = type.type_to.name |
||||||
|
new_str = convert(old_str, slither) |
||||||
|
|
||||||
|
loc_start = start + re_match.start(2) |
||||||
|
loc_end = loc_start + len(old_str) |
||||||
|
|
||||||
|
create_patch(result, |
||||||
|
filename_source_code, |
||||||
|
loc_start, |
||||||
|
loc_end, |
||||||
|
old_str, |
||||||
|
new_str) |
||||||
|
|
||||||
|
if isinstance(type.type_to, (UserDefinedType, MappingType)): |
||||||
|
loc_start = start + re_match.start(2) |
||||||
|
loc_end = start + re_match.end(2) |
||||||
|
_explore_type(slither, |
||||||
|
result, |
||||||
|
target, |
||||||
|
convert, |
||||||
|
type.type_to, |
||||||
|
filename_source_code, |
||||||
|
loc_start, |
||||||
|
loc_end) |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def _explore_variables_declaration(slither, variables, result, target, convert, patch_comment=False): |
||||||
|
for variable in variables: |
||||||
|
# First explore the type of the variable |
||||||
|
filename_source_code = variable.source_mapping['filename_absolute'] |
||||||
|
full_txt_start = variable.source_mapping['start'] |
||||||
|
full_txt_end = full_txt_start + variable.source_mapping['length'] |
||||||
|
full_txt = slither.source_code[filename_source_code].encode('utf8')[full_txt_start:full_txt_end] |
||||||
|
|
||||||
|
_explore_type(slither, |
||||||
|
result, |
||||||
|
target, |
||||||
|
convert, |
||||||
|
variable.type, |
||||||
|
filename_source_code, |
||||||
|
full_txt_start, |
||||||
|
variable.source_mapping['start'] + variable.source_mapping['length']) |
||||||
|
|
||||||
|
# If the variable is the target |
||||||
|
if variable == target: |
||||||
|
old_str = variable.name |
||||||
|
new_str = convert(old_str, slither) |
||||||
|
|
||||||
|
loc_start = full_txt_start + full_txt.find(old_str.encode('utf8')) |
||||||
|
loc_end = loc_start + len(old_str) |
||||||
|
|
||||||
|
create_patch(result, |
||||||
|
filename_source_code, |
||||||
|
loc_start, |
||||||
|
loc_end, |
||||||
|
old_str, |
||||||
|
new_str) |
||||||
|
|
||||||
|
# Patch comment only makes sense for local variable declaration in the parameter list |
||||||
|
if patch_comment and isinstance(variable, LocalVariable): |
||||||
|
if 'lines' in variable.source_mapping and variable.source_mapping['lines']: |
||||||
|
func = variable.function |
||||||
|
end_line = func.source_mapping['lines'][0] |
||||||
|
if variable in func.parameters: |
||||||
|
idx = len(func.parameters) - func.parameters.index(variable) + 1 |
||||||
|
first_line = end_line - idx - 2 |
||||||
|
|
||||||
|
potential_comments = slither.source_code[filename_source_code].encode('utf8') |
||||||
|
potential_comments = potential_comments.splitlines(keepends=True)[first_line:end_line-1] |
||||||
|
|
||||||
|
idx_beginning = func.source_mapping['start'] |
||||||
|
idx_beginning += - func.source_mapping['starting_column'] + 1 |
||||||
|
idx_beginning += - sum([len(c) for c in potential_comments]) |
||||||
|
|
||||||
|
old_comment = f'@param {old_str}'.encode('utf8') |
||||||
|
|
||||||
|
for line in potential_comments: |
||||||
|
idx = line.find(old_comment) |
||||||
|
if idx >=0: |
||||||
|
loc_start = idx + idx_beginning |
||||||
|
loc_end = loc_start + len(old_comment) |
||||||
|
new_comment = f'@param {new_str}'.encode('utf8') |
||||||
|
|
||||||
|
create_patch(result, |
||||||
|
filename_source_code, |
||||||
|
loc_start, |
||||||
|
loc_end, |
||||||
|
old_comment, |
||||||
|
new_comment) |
||||||
|
|
||||||
|
break |
||||||
|
idx_beginning += len(line) |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def _explore_modifiers_calls(slither, function, result, target, convert): |
||||||
|
for modifier in function.modifiers_statements: |
||||||
|
for node in modifier.nodes: |
||||||
|
if node.irs: |
||||||
|
_explore_irs(slither, node.irs, result, target, convert) |
||||||
|
for modifier in function.explicit_base_constructor_calls_statements: |
||||||
|
for node in modifier.nodes: |
||||||
|
if node.irs: |
||||||
|
_explore_irs(slither, node.irs, result, target, convert) |
||||||
|
|
||||||
|
def _explore_structures_declaration(slither, structures, result, target, convert): |
||||||
|
for st in structures: |
||||||
|
# Explore the variable declared within the structure (VariableStructure) |
||||||
|
_explore_variables_declaration(slither, st.elems.values(), result, target, convert) |
||||||
|
|
||||||
|
# If the structure is the target |
||||||
|
if st == target: |
||||||
|
old_str = st.name |
||||||
|
new_str = convert(old_str, slither) |
||||||
|
|
||||||
|
filename_source_code = st.source_mapping['filename_absolute'] |
||||||
|
full_txt_start = st.source_mapping['start'] |
||||||
|
full_txt_end = full_txt_start + st.source_mapping['length'] |
||||||
|
full_txt = slither.source_code[filename_source_code].encode('utf8')[full_txt_start:full_txt_end] |
||||||
|
|
||||||
|
# The name is after the space |
||||||
|
matches = re.finditer(b'struct[ ]*', full_txt) |
||||||
|
# Look for the end offset of the largest list of ' ' |
||||||
|
loc_start = full_txt_start + max(matches, key=lambda x: len(x.group())).end() |
||||||
|
loc_end = loc_start + len(old_str) |
||||||
|
|
||||||
|
create_patch(result, |
||||||
|
filename_source_code, |
||||||
|
loc_start, |
||||||
|
loc_end, |
||||||
|
old_str, |
||||||
|
new_str) |
||||||
|
|
||||||
|
|
||||||
|
def _explore_events_declaration(slither, events, result, target, convert): |
||||||
|
for event in events: |
||||||
|
# Explore the parameters |
||||||
|
_explore_variables_declaration(slither, event.elems, result, target, convert) |
||||||
|
|
||||||
|
# If the event is the target |
||||||
|
if event == target: |
||||||
|
filename_source_code = event.source_mapping['filename_absolute'] |
||||||
|
|
||||||
|
old_str = event.name |
||||||
|
new_str = convert(old_str, slither) |
||||||
|
|
||||||
|
loc_start = event.source_mapping['start'] |
||||||
|
loc_end = loc_start + len(old_str) |
||||||
|
|
||||||
|
create_patch(result, |
||||||
|
filename_source_code, |
||||||
|
loc_start, |
||||||
|
loc_end, |
||||||
|
old_str, |
||||||
|
new_str) |
||||||
|
|
||||||
|
def get_ir_variables(ir): |
||||||
|
vars = ir.read |
||||||
|
|
||||||
|
if isinstance(ir, (InternalCall, InternalDynamicCall, HighLevelCall)): |
||||||
|
vars += [ir.function] |
||||||
|
|
||||||
|
if isinstance(ir, (HighLevelCall, Send, LowLevelCall, Transfer)): |
||||||
|
vars += [ir.call_value] |
||||||
|
|
||||||
|
if isinstance(ir, (HighLevelCall, LowLevelCall)): |
||||||
|
vars += [ir.call_gas] |
||||||
|
|
||||||
|
if isinstance(ir, OperationWithLValue): |
||||||
|
vars += [ir.lvalue] |
||||||
|
|
||||||
|
return [v for v in vars if v] |
||||||
|
|
||||||
|
def _explore_irs(slither, irs, result, target, convert): |
||||||
|
if irs is None: |
||||||
|
return |
||||||
|
for ir in irs: |
||||||
|
for v in get_ir_variables(ir): |
||||||
|
if target == v or ( |
||||||
|
isinstance(target, Function) and isinstance(v, Function) and |
||||||
|
v.canonical_name == target.canonical_name): |
||||||
|
source_mapping = ir.expression.source_mapping |
||||||
|
filename_source_code = source_mapping['filename_absolute'] |
||||||
|
full_txt_start = source_mapping['start'] |
||||||
|
full_txt_end = full_txt_start + source_mapping['length'] |
||||||
|
full_txt = slither.source_code[filename_source_code].encode('utf8')[full_txt_start:full_txt_end] |
||||||
|
|
||||||
|
if not target.name.encode('utf8') in full_txt: |
||||||
|
raise FormatError(f'{target} not found in {full_txt} ({source_mapping}') |
||||||
|
|
||||||
|
old_str = target.name.encode('utf8') |
||||||
|
new_str = convert(old_str, slither) |
||||||
|
|
||||||
|
counter = 0 |
||||||
|
# Can be found multiple time on the same IR |
||||||
|
# We patch one by one |
||||||
|
while old_str in full_txt: |
||||||
|
|
||||||
|
target_found_at = full_txt.find((old_str)) |
||||||
|
|
||||||
|
full_txt = full_txt[target_found_at+1:] |
||||||
|
counter += target_found_at |
||||||
|
|
||||||
|
loc_start = full_txt_start + counter |
||||||
|
loc_end = loc_start + len(old_str) |
||||||
|
|
||||||
|
create_patch(result, |
||||||
|
filename_source_code, |
||||||
|
loc_start, |
||||||
|
loc_end, |
||||||
|
old_str, |
||||||
|
new_str) |
||||||
|
|
||||||
|
|
||||||
|
def _explore_functions(slither, functions, result, target, convert): |
||||||
|
for function in functions: |
||||||
|
_explore_variables_declaration(slither, function.variables, result, target, convert, True) |
||||||
|
_explore_modifiers_calls(slither, function, result, target, convert) |
||||||
|
_explore_irs(slither, function.all_slithir_operations(), result, target, convert) |
||||||
|
|
||||||
|
if isinstance(target, Function) and function.canonical_name == target.canonical_name: |
||||||
|
old_str = function.name |
||||||
|
new_str = convert(old_str, slither) |
||||||
|
|
||||||
|
filename_source_code = function.source_mapping['filename_absolute'] |
||||||
|
full_txt_start = function.source_mapping['start'] |
||||||
|
full_txt_end = full_txt_start + function.source_mapping['length'] |
||||||
|
full_txt = slither.source_code[filename_source_code].encode('utf8')[full_txt_start:full_txt_end] |
||||||
|
|
||||||
|
# The name is after the space |
||||||
|
if isinstance(target, Modifier): |
||||||
|
matches = re.finditer(b'modifier([ ]*)', full_txt) |
||||||
|
else: |
||||||
|
matches = re.finditer(b'function([ ]*)', full_txt) |
||||||
|
# Look for the end offset of the largest list of ' ' |
||||||
|
loc_start = full_txt_start + max(matches, key=lambda x: len(x.group())).end() |
||||||
|
loc_end = loc_start + len(old_str) |
||||||
|
|
||||||
|
create_patch(result, |
||||||
|
filename_source_code, |
||||||
|
loc_start, |
||||||
|
loc_end, |
||||||
|
old_str, |
||||||
|
new_str) |
||||||
|
|
||||||
|
def _explore_enums(slither, enums, result, target, convert): |
||||||
|
for enum in enums: |
||||||
|
if enum == target: |
||||||
|
old_str = enum.name |
||||||
|
new_str = convert(old_str, slither) |
||||||
|
|
||||||
|
filename_source_code = enum.source_mapping['filename_absolute'] |
||||||
|
full_txt_start = enum.source_mapping['start'] |
||||||
|
full_txt_end = full_txt_start + enum.source_mapping['length'] |
||||||
|
full_txt = slither.source_code[filename_source_code].encode('utf8')[full_txt_start:full_txt_end] |
||||||
|
|
||||||
|
# The name is after the space |
||||||
|
matches = re.finditer(b'enum([ ]*)', full_txt) |
||||||
|
# Look for the end offset of the largest list of ' ' |
||||||
|
loc_start = full_txt_start + max(matches, key=lambda x: len(x.group())).end() |
||||||
|
loc_end = loc_start + len(old_str) |
||||||
|
|
||||||
|
create_patch(result, |
||||||
|
filename_source_code, |
||||||
|
loc_start, |
||||||
|
loc_end, |
||||||
|
old_str, |
||||||
|
new_str) |
||||||
|
|
||||||
|
|
||||||
|
def _explore_contract(slither, contract, result, target, convert): |
||||||
|
_explore_variables_declaration(slither, contract.state_variables, result, target, convert) |
||||||
|
_explore_structures_declaration(slither, contract.structures, result, target, convert) |
||||||
|
_explore_functions(slither, contract.functions_and_modifiers, result, target, convert) |
||||||
|
_explore_enums(slither, contract.enums, result, target, convert) |
||||||
|
|
||||||
|
if contract == target: |
||||||
|
filename_source_code = contract.source_mapping['filename_absolute'] |
||||||
|
full_txt_start = contract.source_mapping['start'] |
||||||
|
full_txt_end = full_txt_start + contract.source_mapping['length'] |
||||||
|
full_txt = slither.source_code[filename_source_code].encode('utf8')[full_txt_start:full_txt_end] |
||||||
|
|
||||||
|
old_str = contract.name |
||||||
|
new_str = convert(old_str, slither) |
||||||
|
|
||||||
|
# The name is after the space |
||||||
|
matches = re.finditer(b'contract[ ]*', full_txt) |
||||||
|
# Look for the end offset of the largest list of ' ' |
||||||
|
loc_start = full_txt_start + max(matches, key=lambda x: len(x.group())).end() |
||||||
|
|
||||||
|
loc_end = loc_start + len(old_str) |
||||||
|
|
||||||
|
create_patch(result, |
||||||
|
filename_source_code, |
||||||
|
loc_start, |
||||||
|
loc_end, |
||||||
|
old_str, |
||||||
|
new_str) |
||||||
|
|
||||||
|
|
||||||
|
def _explore(slither, result, target, convert): |
||||||
|
for contract in slither.contracts_derived: |
||||||
|
_explore_contract(slither, contract, result, target, convert) |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# endregion |
||||||
|
|
||||||
|
|
@ -0,0 +1,43 @@ |
|||||||
|
import os |
||||||
|
import difflib |
||||||
|
from collections import defaultdict |
||||||
|
|
||||||
|
def create_patch(result, file, start, end, old_str, new_str): |
||||||
|
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 |
||||||
|
} |
||||||
|
if 'patches' not in result: |
||||||
|
result['patches'] = defaultdict(list) |
||||||
|
if p not in result['patches'][file]: |
||||||
|
result['patches'][file].append(p) |
||||||
|
|
||||||
|
|
||||||
|
def apply_patch(original_txt, patch, offset): |
||||||
|
patched_txt = original_txt[:int(patch['start'] + offset)] |
||||||
|
patched_txt += patch['new_string'].encode('utf8') |
||||||
|
patched_txt += original_txt[int(patch['end'] + offset):] |
||||||
|
|
||||||
|
# Keep the diff of text added or sub, in case of multiple patches |
||||||
|
patch_length_diff = len(patch['new_string']) - (patch['end'] - patch['start']) |
||||||
|
return patched_txt, patch_length_diff + offset |
||||||
|
|
||||||
|
|
||||||
|
def create_diff(slither, original_txt, patched_txt, filename): |
||||||
|
if slither.crytic_compile: |
||||||
|
relative_path = slither.crytic_compile.filename_lookup(filename).relative |
||||||
|
relative_path = os.path.join('.', relative_path) |
||||||
|
else: |
||||||
|
relative_path = filename |
||||||
|
diff = difflib.unified_diff(original_txt.decode('utf8').splitlines(False), |
||||||
|
patched_txt.decode('utf8').splitlines(False), |
||||||
|
fromfile=relative_path, |
||||||
|
tofile=relative_path, |
||||||
|
lineterm='') |
||||||
|
|
||||||
|
return '\n'.join(list(diff)) + '\n' |
@ -0,0 +1,38 @@ |
|||||||
|
import re |
||||||
|
from slither.formatters.exceptions import FormatError, FormatImpossible |
||||||
|
from slither.formatters.utils.patches import create_patch |
||||||
|
|
||||||
|
def format(slither, result): |
||||||
|
elements = result['elements'] |
||||||
|
for element in elements: |
||||||
|
|
||||||
|
# TODO: decide if this should be changed in the constant detector |
||||||
|
contract_name = element['type_specific_fields']['parent']['name'] |
||||||
|
contract = slither.get_contract_from_name(contract_name) |
||||||
|
var = contract.get_state_variable_from_name(element['name']) |
||||||
|
if not var.expression: |
||||||
|
raise FormatImpossible(f'{var.name} is uninitialized and cannot become constant.') |
||||||
|
|
||||||
|
_patch(slither, result, element['source_mapping']['filename_absolute'], |
||||||
|
element['name'], |
||||||
|
"constant " + element['name'], |
||||||
|
element['source_mapping']['start'], |
||||||
|
element['source_mapping']['start'] + element['source_mapping']['length']) |
||||||
|
|
||||||
|
|
||||||
|
def _patch(slither, result, in_file, match_text, replace_text, modify_loc_start, modify_loc_end): |
||||||
|
in_file_str = slither.source_code[in_file].encode('utf8') |
||||||
|
old_str_of_interest = in_file_str[modify_loc_start:modify_loc_end] |
||||||
|
# Add keyword `constant` before the variable name |
||||||
|
(new_str_of_interest, num_repl) = re.subn(match_text, replace_text, old_str_of_interest.decode('utf-8'), 1) |
||||||
|
if num_repl != 0: |
||||||
|
create_patch(result, |
||||||
|
in_file, |
||||||
|
modify_loc_start, |
||||||
|
modify_loc_end, |
||||||
|
old_str_of_interest, |
||||||
|
new_str_of_interest) |
||||||
|
|
||||||
|
else: |
||||||
|
raise FormatError("State variable not found?!") |
||||||
|
|
@ -0,0 +1,28 @@ |
|||||||
|
from slither.formatters.utils.patches import create_patch |
||||||
|
|
||||||
|
|
||||||
|
def format(slither, result): |
||||||
|
elements = result['elements'] |
||||||
|
for element in elements: |
||||||
|
if element['type'] == "variable": |
||||||
|
_patch(slither, |
||||||
|
result, |
||||||
|
element['source_mapping']['filename_absolute'], |
||||||
|
element['source_mapping']['start']) |
||||||
|
|
||||||
|
|
||||||
|
def _patch(slither, result, in_file, modify_loc_start): |
||||||
|
in_file_str = slither.source_code[in_file].encode('utf8') |
||||||
|
old_str_of_interest = in_file_str[modify_loc_start:] |
||||||
|
old_str = old_str_of_interest.decode('utf-8').partition(';')[0]\ |
||||||
|
+ old_str_of_interest.decode('utf-8').partition(';')[1] |
||||||
|
|
||||||
|
create_patch(result, |
||||||
|
in_file, |
||||||
|
int(modify_loc_start), |
||||||
|
# Remove the entire declaration until the semicolon |
||||||
|
int(modify_loc_start + len(old_str_of_interest.decode('utf-8').partition(';')[0]) + 1), |
||||||
|
old_str, |
||||||
|
"") |
||||||
|
|
||||||
|
|
@ -0,0 +1,14 @@ |
|||||||
|
# .format files are the output files produced by slither-format |
||||||
|
# .patch files are the output files produced by slither-format |
||||||
|
*.format |
||||||
|
*.patch |
||||||
|
|
||||||
|
# Temporary files (Emacs backup files ending in tilde and others) |
||||||
|
*~ |
||||||
|
*.err |
||||||
|
*.out |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -0,0 +1,89 @@ |
|||||||
|
import sys |
||||||
|
import argparse |
||||||
|
from slither import Slither |
||||||
|
from slither.utils.command_line import read_config_file |
||||||
|
import logging |
||||||
|
from .slither_format import slither_format |
||||||
|
from crytic_compile import cryticparser |
||||||
|
|
||||||
|
logging.basicConfig() |
||||||
|
logger = logging.getLogger("Slither").setLevel(logging.INFO) |
||||||
|
|
||||||
|
# Slither detectors for which slither-format currently works |
||||||
|
available_detectors = ["unused-state", |
||||||
|
"solc-version", |
||||||
|
"pragma", |
||||||
|
"naming-convention", |
||||||
|
"external-function", |
||||||
|
"constable-states", |
||||||
|
"constant-function"] |
||||||
|
|
||||||
|
detectors_to_run = [] |
||||||
|
|
||||||
|
def parse_args(): |
||||||
|
""" |
||||||
|
Parse the underlying arguments for the program. |
||||||
|
:return: Returns the arguments for the program. |
||||||
|
""" |
||||||
|
parser = argparse.ArgumentParser(description='slither_format', |
||||||
|
usage='slither_format filename') |
||||||
|
|
||||||
|
parser.add_argument('filename', help='The filename of the contract or truffle directory to analyze.') |
||||||
|
parser.add_argument('--verbose-test', '-v', help='verbose mode output for testing',action='store_true',default=False) |
||||||
|
parser.add_argument('--verbose-json', '-j', help='verbose json output',action='store_true',default=False) |
||||||
|
parser.add_argument('--version', |
||||||
|
help='displays the current version', |
||||||
|
version='0.1.0', |
||||||
|
action='version') |
||||||
|
|
||||||
|
parser.add_argument('--config-file', |
||||||
|
help='Provide a config file (default: slither.config.json)', |
||||||
|
action='store', |
||||||
|
dest='config_file', |
||||||
|
default='slither.config.json') |
||||||
|
|
||||||
|
|
||||||
|
group_detector = parser.add_argument_group('Detectors') |
||||||
|
group_detector.add_argument('--detect', |
||||||
|
help='Comma-separated list of detectors, defaults to all, ' |
||||||
|
'available detectors: {}'.format( |
||||||
|
', '.join(d for d in available_detectors)), |
||||||
|
action='store', |
||||||
|
dest='detectors_to_run', |
||||||
|
default='all') |
||||||
|
|
||||||
|
group_detector.add_argument('--exclude', |
||||||
|
help='Comma-separated list of detectors to exclude,' |
||||||
|
'available detectors: {}'.format( |
||||||
|
', '.join(d for d in available_detectors)), |
||||||
|
action='store', |
||||||
|
dest='detectors_to_exclude', |
||||||
|
default='all') |
||||||
|
|
||||||
|
cryticparser.init(parser) |
||||||
|
|
||||||
|
if len(sys.argv) == 1: |
||||||
|
parser.print_help(sys.stderr) |
||||||
|
sys.exit(1) |
||||||
|
|
||||||
|
return parser.parse_args() |
||||||
|
|
||||||
|
|
||||||
|
def main(): |
||||||
|
# ------------------------------ |
||||||
|
# Usage: python3 -m slither_format filename |
||||||
|
# Example: python3 -m slither_format contract.sol |
||||||
|
# ------------------------------ |
||||||
|
# Parse all arguments |
||||||
|
args = parse_args() |
||||||
|
|
||||||
|
read_config_file(args) |
||||||
|
|
||||||
|
|
||||||
|
# Perform slither analysis on the given filename |
||||||
|
slither = Slither(args.filename, **vars(args)) |
||||||
|
|
||||||
|
# Format the input files based on slither analysis |
||||||
|
slither_format(slither, **vars(args)) |
||||||
|
if __name__ == '__main__': |
||||||
|
main() |
@ -0,0 +1,151 @@ |
|||||||
|
import logging |
||||||
|
from pathlib import Path |
||||||
|
from slither.detectors.variables.unused_state_variables import UnusedStateVars |
||||||
|
from slither.detectors.attributes.incorrect_solc import IncorrectSolc |
||||||
|
from slither.detectors.attributes.constant_pragma import ConstantPragma |
||||||
|
from slither.detectors.naming_convention.naming_convention import NamingConvention |
||||||
|
from slither.detectors.functions.external_function import ExternalFunction |
||||||
|
from slither.detectors.variables.possible_const_state_variables import ConstCandidateStateVars |
||||||
|
from slither.detectors.attributes.const_functions import ConstantFunctions |
||||||
|
from slither.utils.colors import yellow |
||||||
|
|
||||||
|
logging.basicConfig(level=logging.INFO) |
||||||
|
logger = logging.getLogger('Slither.Format') |
||||||
|
|
||||||
|
all_detectors = { |
||||||
|
'unused-state': UnusedStateVars, |
||||||
|
'solc-version': IncorrectSolc, |
||||||
|
'pragma': ConstantPragma, |
||||||
|
'naming-convention': NamingConvention, |
||||||
|
'external-function': ExternalFunction, |
||||||
|
'constable-states' : ConstCandidateStateVars, |
||||||
|
'constant-function': ConstantFunctions |
||||||
|
} |
||||||
|
|
||||||
|
def slither_format(slither, **kwargs): |
||||||
|
'''' |
||||||
|
Keyword Args: |
||||||
|
detectors_to_run (str): Comma-separated list of detectors, defaults to all |
||||||
|
''' |
||||||
|
|
||||||
|
detectors_to_run = choose_detectors(kwargs.get('detectors_to_run', 'all'), |
||||||
|
kwargs.get('detectors_to_exclude', '')) |
||||||
|
|
||||||
|
for detector in detectors_to_run: |
||||||
|
slither.register_detector(detector) |
||||||
|
|
||||||
|
slither.generate_patches = True |
||||||
|
|
||||||
|
detector_results = slither.run_detectors() |
||||||
|
detector_results = [x for x in detector_results if x] # remove empty results |
||||||
|
detector_results = [item for sublist in detector_results for item in sublist] # flatten |
||||||
|
|
||||||
|
export = Path('crytic-export', 'patches') |
||||||
|
|
||||||
|
export.mkdir(parents=True, exist_ok=True) |
||||||
|
|
||||||
|
counter_result = 0 |
||||||
|
|
||||||
|
logger.info(yellow('slither-format is in beta, carefully review each patch before merging it.')) |
||||||
|
|
||||||
|
for result in detector_results: |
||||||
|
if not 'patches' in result: |
||||||
|
continue |
||||||
|
one_line_description = result["description"].split("\n")[0] |
||||||
|
|
||||||
|
export_result = Path(export, f'{counter_result}') |
||||||
|
export_result.mkdir(parents=True, exist_ok=True) |
||||||
|
counter_result += 1 |
||||||
|
counter = 0 |
||||||
|
|
||||||
|
logger.info(f'Issue: {one_line_description}') |
||||||
|
logger.info(f'Generated: ({export_result})') |
||||||
|
|
||||||
|
for file, diff, in result['patches_diff'].items(): |
||||||
|
filename = f'fix_{counter}.patch' |
||||||
|
path = Path(export_result, filename) |
||||||
|
logger.info(f'\t- {filename}') |
||||||
|
with open(path, 'w') as f: |
||||||
|
f.write(diff) |
||||||
|
counter += 1 |
||||||
|
|
||||||
|
|
||||||
|
# endregion |
||||||
|
################################################################################### |
||||||
|
################################################################################### |
||||||
|
# region Detectors |
||||||
|
################################################################################### |
||||||
|
################################################################################### |
||||||
|
|
||||||
|
def choose_detectors(detectors_to_run, detectors_to_exclude): |
||||||
|
# If detectors are specified, run only these ones |
||||||
|
cls_detectors_to_run = [] |
||||||
|
exclude = detectors_to_exclude.split(',') |
||||||
|
if detectors_to_run == 'all': |
||||||
|
for d in all_detectors: |
||||||
|
if d in exclude: |
||||||
|
continue |
||||||
|
cls_detectors_to_run.append(all_detectors[d]) |
||||||
|
else: |
||||||
|
exclude = detectors_to_exclude.split(',') |
||||||
|
for d in detectors_to_run.split(','): |
||||||
|
if d in all_detectors: |
||||||
|
if d in exclude: |
||||||
|
continue |
||||||
|
cls_detectors_to_run.append(all_detectors[d]) |
||||||
|
else: |
||||||
|
raise Exception('Error: {} is not a detector'.format(d)) |
||||||
|
return cls_detectors_to_run |
||||||
|
|
||||||
|
# endregion |
||||||
|
################################################################################### |
||||||
|
################################################################################### |
||||||
|
# region Debug functions |
||||||
|
################################################################################### |
||||||
|
################################################################################### |
||||||
|
|
||||||
|
def print_patches(number_of_slither_results, patches): |
||||||
|
logger.info("Number of Slither results: " + str(number_of_slither_results)) |
||||||
|
number_of_patches = 0 |
||||||
|
for file in patches: |
||||||
|
number_of_patches += len(patches[file]) |
||||||
|
logger.info("Number of patches: " + str(number_of_patches)) |
||||||
|
for file in patches: |
||||||
|
logger.info("Patch file: " + file) |
||||||
|
for patch in patches[file]: |
||||||
|
logger.info("Detector: " + patch['detector']) |
||||||
|
logger.info("Old string: " + patch['old_string'].replace("\n","")) |
||||||
|
logger.info("New string: " + patch['new_string'].replace("\n","")) |
||||||
|
logger.info("Location start: " + str(patch['start'])) |
||||||
|
logger.info("Location end: " + str(patch['end'])) |
||||||
|
|
||||||
|
def print_patches_json(number_of_slither_results, patches): |
||||||
|
print('{',end='') |
||||||
|
print("\"Number of Slither results\":" + '"' + str(number_of_slither_results) + '",') |
||||||
|
print("\"Number of patchlets\":" + "\"" + str(len(patches)) + "\"", ',') |
||||||
|
print("\"Patchlets\":" + '[') |
||||||
|
for index, file in enumerate(patches): |
||||||
|
if index > 0: |
||||||
|
print(',') |
||||||
|
print('{',end='') |
||||||
|
print("\"Patch file\":" + '"' + file + '",') |
||||||
|
print("\"Number of patches\":" + "\"" + str(len(patches[file])) + "\"", ',') |
||||||
|
print("\"Patches\":" + '[') |
||||||
|
for index, patch in enumerate(patches[file]): |
||||||
|
if index > 0: |
||||||
|
print(',') |
||||||
|
print('{',end='') |
||||||
|
print("\"Detector\":" + '"' + patch['detector'] + '",') |
||||||
|
print("\"Old string\":" + '"' + patch['old_string'].replace("\n","") + '",') |
||||||
|
print("\"New string\":" + '"' + patch['new_string'].replace("\n","") + '",') |
||||||
|
print("\"Location start\":" + '"' + str(patch['start']) + '",') |
||||||
|
print("\"Location end\":" + '"' + str(patch['end']) + '"') |
||||||
|
if 'overlaps' in patch: |
||||||
|
print("\"Overlaps\":" + "Yes") |
||||||
|
print('}',end='') |
||||||
|
print(']',end='') |
||||||
|
print('}',end='') |
||||||
|
print(']',end='') |
||||||
|
print('}') |
||||||
|
|
||||||
|
|
Loading…
Reference in new issue