def _mutate(self): result = dict() for contract in self.fortress.contracts: for function in contract.functions_declared + 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.fortress.source_code[in_file] # Get the string start = node.source_mapping["start"] stop = start + node.source_mapping["length"] old_str = in_file_str[start:stop] # Replace the expression with true new_str = "true" create_patch(result, in_file, start, stop, old_str, new_str) return result
def _explore_structures_declaration(fortress, structures, result, target, convert): for st in structures: # Explore the variable declared within the structure (VariableStructure) _explore_variables_declaration(fortress, st.elems.values(), result, target, convert) # If the structure is the target if st == target: old_str = st.name new_str = convert(old_str, fortress) 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 = fortress.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_contract(fortress, contract, result, target, convert): _explore_variables_declaration(fortress, contract.state_variables, result, target, convert) _explore_structures_declaration(fortress, contract.structures, result, target, convert) _explore_functions(fortress, contract.functions_and_modifiers, result, target, convert) _explore_enums(fortress, 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 = fortress.source_code[filename_source_code].encode( "utf8")[full_txt_start:full_txt_end] old_str = contract.name new_str = convert(old_str, fortress) # 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_functions(fortress, functions, result, target, convert): for function in functions: _explore_variables_declaration(fortress, function.variables, result, target, convert, True) _explore_irs(fortress, 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, fortress) 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 = fortress.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 _patch(fortress, result, in_file, modify_loc_start, modify_loc_end): in_file_str = fortress.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", )
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.fortress.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, )
def _patch(fortress, result, in_file, pragma, modify_loc_start, modify_loc_end): # pylint: disable=too-many-arguments in_file_str = fortress.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, )
def _patch(fortress, result, in_file, solc_version, modify_loc_start, modify_loc_end): in_file_str = fortress.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, )
def _patch(fortress, result, in_file, modify_loc_start): in_file_str = fortress.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, "", )
def _explore_events_declaration(fortress, events, result, target, convert): for event in events: # Explore the parameters _explore_variables_declaration(fortress, 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, fortress) 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 _patch(fortress, result, in_file, modify_loc_start, modify_loc_end): in_file_str = fortress.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!" )
def _explore_irs(fortress, irs, result, target, convert): # pylint: disable=too-many-locals 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 = fortress.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, fortress) 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_enums(fortress, enums, result, target, convert): for enum in enums: if enum == target: old_str = enum.name new_str = convert(old_str, fortress) 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 = fortress.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 _patch( # pylint: disable=too-many-arguments fortress, result, in_file, match_text, replace_text, modify_loc_start, modify_loc_end ): in_file_str = fortress.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?!")
def _explore_variables_declaration( # pylint: disable=too-many-arguments,too-many-locals,too-many-nested-blocks fortress, 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 = fortress.source_code[filename_source_code].encode( "utf8")[full_txt_start:full_txt_end] _explore_type( fortress, 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, fortress) 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 = fortress.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_type( # pylint: disable=too-many-arguments,too-many-locals,too-many-branches fortress, result, target, convert, custom_type, filename_source_code, start, end): if isinstance(custom_type, UserDefinedType): # Patch type based on contract/enum if isinstance(custom_type.type, (Enum, Contract)): if custom_type.type == target: old_str = custom_type.type.name new_str = convert(old_str, fortress) loc_start = start if _is_var_declaration(fortress, 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(custom_type.type, Structure) if custom_type.type == target: old_str = custom_type.type.name new_str = convert(old_str, fortress) loc_start = start if _is_var_declaration(fortress, 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(fortress, custom_type.type.elems.values(), result, target, convert) if isinstance(custom_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(custom_type.type_to, (UserDefinedType, MappingType)) or target in [ custom_type.type_from, custom_type.type_to, ]: full_txt_start = start full_txt_end = end full_txt = fortress.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 custom_type.type_from == target: old_str = custom_type.type_from.name new_str = convert(old_str, fortress) 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 custom_type.type_to == target: old_str = custom_type.type_to.name new_str = convert(old_str, fortress) 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(custom_type.type_to, (UserDefinedType, MappingType)): loc_start = start + re_match.start(2) loc_end = start + re_match.end(2) _explore_type( fortress, result, target, convert, custom_type.type_to, filename_source_code, loc_start, loc_end, )