def all_declarations_public(source: str) -> str: """ Using a regex it makes public all the variable inside source :param source: :param ast: :return: edited_source: str """ ast = asthelper.compile_from_source(source) nodes = asthelper.find_all_nodes(ast, { 'nodeType': 'VariableDeclaration', 'stateVariable': True }) destination = source declaration_re = re.compile( r"(address|string|bool|bytes?\d*|u?int\d*|mapping.*\))(\[\d*\]|)?\s*(public|private|internal|external|)?\s+(\w+)\s*(;|=)" ) for n in nodes: start, length, _ = n['src'].split(':') start = int(start) length = int(length) line = source[start:start + length + 1] # print("before: ", line) left, right = tuple(destination.split(line, 1)) middle = declaration_re.sub(r"\g<1>\g<2> public \g<4> \g<5>", line) destination = left + middle + right # print("after: ", middle) return destination
def before_test(self, source): ast_json = asthelper.compile_from_source(source) function_name = 'testFunction' function_node = asthelper.find_node(ast_json, { 'nodeType': 'FunctionDefinition', 'name': function_name }) function_body = function_node['body'] function_statements = function_body['statements'] # Enumerate all the stataments with a require require_nodes = [ i for (i, x) in enumerate(function_statements) if asthelper.find_node(x, {'name': 'require'}) ] if not require_nodes: raise Exception("No requires") # stataments_under_inspection = All the stataments of the function until the last require (included) statements_under_inspection = function_statements[:require_nodes[-1] + 1] # Remove event emit statements # Remove banned statements events_definition_nodes = asthelper.find_all_nodes( ast_json, {'nodeType': 'EventDefinition'}) return events_definition_nodes, statements_under_inspection
def inject_getter_functions(source: str): """ Create getters for array's length member :param source: original source code :return: edited source code """ ast = asthelper.compile_from_source(source) state_variables = \ [x for x in asthelper.find_all_nodes(ast, {'nodeType': 'VariableDeclaration', 'stateVariable': True})] state_variables_list = [ x for x in state_variables if 't_array' in x['typeDescriptions']['typeIdentifier'] ] state_variables_names = [x['name'] for x in state_variables_list] edited_source = source for state_variable in state_variables_names: edited_source = _inject_getter_function(edited_source, state_variable) return edited_source
def convert_reassignment(source: str) -> str: regex = r"^[\s|\t]*([^\s]+)\s+(\+|\-|\*)?=\s+([^;]*)" ast = asthelper.compile_from_source(source) assigment_nodes = asthelper.find_all_nodes(ast, {'nodeType': '^Assignment$'}) edited_source = source for node in assigment_nodes: start, length, _ = node['src'].split(':') start = int(start) length = int(length) line = source[start:start + length + 1] line_rgx = r'^[\s|\t]*' + re.escape(line) pieces = re.compile(line_rgx, re.M).split(edited_source, 1) if len(pieces) != 2: # not exact "LINE" match found continue left, right = tuple(pieces) name = 'var nvar_{1}'.format(node['typeDescriptions']['typeString'], node['id']) new_line = line matches = re.search(regex, line, re.M) if matches and matches.group(2) is None: subst = name + ' = \g<3>' new_line = re.sub(regex, subst, new_line, re.M) elif matches and matches.group(2): subst = name + ' = \g<1> \g<2> \g<3>' new_line = re.sub(regex, subst, new_line, re.M) edited_source = left + new_line + right return edited_source
def visit_ast(ast_json: list, function_name: str) -> dict: """ Given the AST of the complete solidity source file and the function name of the function under test, visit_ast checks if the function has a list of valid statements which precede the last require statements (using the sub-function filter_statements) and collect information for later analysis such as the candidate variables for an overflow, variable declarations, accessed state variables and the require expressions. :param ast_json: ast of the file generated using solc :param function_name: str, function name under test :return: inspected_parameters: dict """ function_nodes = asthelper.find_all_nodes(ast_json, {'nodeType': 'FunctionDefinition', 'name': "^" + function_name + "$"}) function_nodes_with_body = [x for x in function_nodes if x['body'] and x['body']['statements']] if not function_nodes_with_body: raise Exception("AstVisitErr", "No function definition") function_node = function_nodes_with_body[0] function_body = function_node['body'] function_statements = function_body['statements'] # ==== # ==== EXTRACT STATEMENTS AND CHECKS VALIDITY # ==== # Enumerate all the stataments with a require require_nodes = [i for (i, x) in enumerate(function_statements) if asthelper.find_node(x, {'name': '^require$'})] statements_under_inspection = function_statements # Remove event emit statements # Remove banned statements events_definition_nodes = asthelper.find_all_nodes(ast_json, {'nodeType': '^EventDefinition$'}) filtered_statements = filter_statements(events_definition_nodes, statements_under_inspection) if not filtered_statements: raise Exception("AstVisitErr", "Not handled statements") # ==== # ==== LOCAL VARIABLE DECLARATION # ==== local_variables = [x for x in filtered_statements if x['nodeType'] == 'VariableDeclarationStatement'] # remove local variable decleration without initial values local_variables = list(filter(lambda a: 'initialValue' in a and a['initialValue'], local_variables)) # ==== # ==== REQUIRES # ==== """ Description: Test the function _add_require_condition_with_overflow Each require contains an expression, this expression, which is then used to evaluate a boolean formula can lead to an overflow. So it is important to extract from the expression which contains the candidate to overflow value an implicit symbolic variable that is then added as a normal overflow candidate. """ expression_candidates = [] expressions_map = [] requires_nodes = [x for x in filtered_statements if asthelper.find_node(x, {'name': '^require$'})] for require_node in requires_nodes: require_exp = expressionhelper.Expression(require_node['expression']['arguments'][0]) candidate_check_results = check_for_overflow_candidate(require_exp) if candidate_check_results: candidates_exp = [x[0] for x in candidate_check_results] expression_map = [x[1] for x in candidate_check_results] expression_candidates += candidates_exp expressions_map += expression_map # ==== # ==== CANDIDATE FOR OVERFLOW # ==== # Check if there is a at least one candidate for overflow local_variables_candidates = [] for lv in local_variables: name = lv['declarations'][0]['name'] lv_exp = expressionhelper.Expression(lv['initialValue']) lv_exp.dic['name'] = name # set a name to the expression candidate_check_results = check_for_overflow_candidate(lv_exp) if candidate_check_results: candidate_exp, expression_map = candidate_check_results[0] local_variables_candidates.append(candidate_exp) variables_candidate_for_overflow = local_variables_candidates + expression_candidates if not variables_candidate_for_overflow: raise Exception("AstVisitErr", "No variable is a good candidate for overflow ") # ==== # ==== ACCESSED STATE VARIABLES # ==== # Enumerate all the state variables state_variables_declaration = \ [x for x in asthelper.find_all_nodes(ast_json, {'nodeType': 'VariableDeclaration', 'stateVariable': True})] # Find the accessed variables # No repetition of variable accessed # multiple times accessed_state_variables = [] for state_variable_declaration in state_variables_declaration: state_id = state_variable_declaration['id'] for statement in filtered_statements: access = asthelper.find_node(statement, {'nodeType': 'Identifier', 'referencedDeclaration': state_id}) if access: accessed_state_variables.append(state_variable_declaration) break # ==== # ==== FORMAL PARAMETERS # ==== # List the input parameters of the function under test formal_parameters = function_node['parameters']['parameters'] map_id_variable_name = {} local_variables_tmp = [x['declarations'][0] for x in local_variables ] for v in local_variables_tmp + formal_parameters + accessed_state_variables: map_id_variable_name[str(v['id'])] = v['name'] # append 'this' identifier this_identifiers = asthelper.find_all_nodes(ast_json, {'nodeType': 'Identifier', 'name': 'this'}) for this_id in this_identifiers: map_id_variable_name[str(this_id['id'])] = this_id['name'] map_id_variable_name[str(this_id['referencedDeclaration'])] = this_id['name'] return { 'formal_parameters': formal_parameters, 'local_variables': local_variables, 'require_nodes': requires_nodes, 'require_expression_map': expressions_map, 'candidate_for_overflow': variables_candidate_for_overflow, 'accessed_state_variables': accessed_state_variables, 'map_id_variable_name': map_id_variable_name }
def ifrevert2require(source: str) -> str: """ Conver the assigment to newly created declarations :param source: :return: """ ast = asthelper.compile_from_source(source) if_nodes = asthelper.find_all_nodes(ast, {'nodeType': '^IfStatement$'}) edited_source = source for node in if_nodes: try: reverts = asthelper.find_node(node['trueBody'], { 'nodeType': '^Identifier$', 'name': '^revert$' }) # Check for return false return_false = False if 'statements' in node['trueBody']: if_statements = node['trueBody']['statements'] if len(if_statements) == 1: return_statement = asthelper.find_node( node['trueBody'], {'nodeType': '^Return$'}) if return_statement and asthelper.find_node( node['trueBody'], { 'nodeType': '^Literal$', 'value': '^false$' }): return_false = True elif 'expression' in node['trueBody']: return_statement = asthelper.find_node( node['trueBody'], {'nodeType': '^Return$'}) if return_statement and asthelper.find_node( node['trueBody'], { 'nodeType': '^Literal$', 'value': '^false$' }): return_false = True if reverts or return_false: start, length, _ = node['src'].split(':') start = int(start) length = int(length) line = source[start:start + length + 1] left, right = tuple(edited_source.split(line, 1)) # other way # new_line = line.replace('if', 'require') # sa = new_line.split(')') # new_line = ')'.join(sa[:-2]) # remove the portion after the last if closed bracket if_condition = asthelper.astnode.AstNode( None, node['condition']).to_sol_string() require_statement = 'require(({}) == false);\n'.format( if_condition) edited_source = left + require_statement + right except Exception as e: # an error while editing some if, maybe contains a function # just skip continue return edited_source