def validate_functions(vy_module: vy_ast.Module) -> None: """Analyzes a vyper ast and validates the function-level namespaces.""" err_list = ExceptionList() namespace = get_namespace() for node in vy_module.get_children(vy_ast.FunctionDef): with namespace.enter_scope(): try: FunctionNodeVisitor(vy_module, node, namespace) except VyperException as e: err_list.append(e) err_list.raise_if_not_empty()
def __init__( self, module_node: vy_ast.Module, interface_codes: InterfaceDict, namespace: dict ) -> None: self.ast = module_node self.interface_codes = interface_codes or {} self.namespace = namespace module_nodes = module_node.body.copy() while module_nodes: count = len(module_nodes) err_list = ExceptionList() for node in list(module_nodes): try: self.visit(node) module_nodes.remove(node) except (InvalidLiteral, InvalidType, VariableDeclarationException): # these exceptions cannot be caused by another statement not yet being # parsed, so we raise them immediately raise except VyperException as e: err_list.append(e) # Only raise if no nodes were successfully processed. This allows module # level logic to parse regardless of the ordering of code elements. if count == len(module_nodes): err_list.raise_if_not_empty() # check for collisions between 4byte function selectors # internal functions are intentionally included in this check, to prevent breaking # changes in in case of a future change to their calling convention self_members = namespace["self"].members functions = [i for i in self_members.values() if isinstance(i, ContractFunction)] validate_unique_method_ids(functions) # generate an `InterfacePrimitive` from the top-level node - used for building the ABI interface = namespace["interface"].build_primitive_from_node(module_node) module_node._metadata["type"] = interface # get list of internal function calls made by each function function_defs = self.ast.get_children(vy_ast.FunctionDef) function_names = set(node.name for node in function_defs) for node in function_defs: calls_to_self = set( i.func.attr for i in node.get_descendants(vy_ast.Call, {"func.value.id": "self"}) ) # anything that is not a function call will get semantically checked later calls_to_self = calls_to_self.intersection(function_names) self_members[node.name].internal_calls = calls_to_self if node.name in self_members[node.name].internal_calls: self_node = node.get_descendants( vy_ast.Attribute, {"value.id": "self", "attr": node.name} )[0] raise CallViolation(f"Function '{node.name}' calls into itself", self_node) for fn_name in sorted(function_names): if fn_name not in self_members: # the referenced function does not exist - this is an issue, but we'll report # it later when parsing the function so we can give more meaningful output continue # check for circular function calls sequence = _find_cyclic_call([fn_name], self_members) if sequence is not None: nodes = [] for i in range(len(sequence) - 1): fn_node = self.ast.get_children(vy_ast.FunctionDef, {"name": sequence[i]})[0] call_node = fn_node.get_descendants( vy_ast.Attribute, {"value.id": "self", "attr": sequence[i + 1]} )[0] nodes.append(call_node) raise CallViolation("Contract contains cyclic function call", *nodes) # get complete list of functions that are reachable from this function function_set = set(i for i in self_members[fn_name].internal_calls if i in self_members) while True: expanded = set(x for i in function_set for x in self_members[i].internal_calls) expanded |= function_set if expanded == function_set: break function_set = expanded self_members[fn_name].recursive_calls = function_set
def __init__( self, module_node: vy_ast.Module, interface_codes: InterfaceDict, namespace: dict, ) -> None: self.ast = module_node self.interface_codes = interface_codes or {} self.namespace = namespace module_nodes = module_node.body.copy() while module_nodes: count = len(module_nodes) err_list = ExceptionList() for node in list(module_nodes): try: self.visit(node) module_nodes.remove(node) except VyperException as e: err_list.append(e) # Only raise if no nodes were successfully processed. This allows module # level logic to parse regardless of the ordering of code elements. if count == len(module_nodes): err_list.raise_if_not_empty() # get list of internal function calls made by each function call_function_names = set() self_members = namespace["self"].members for node in self.ast.get_children(vy_ast.FunctionDef): call_function_names.add(node.name) self_members[node.name].internal_calls = set( i.func.attr for i in node.get_descendants( vy_ast.Call, {"func.value.id": "self"})) if node.name in self_members[node.name].internal_calls: self_node = node.get_descendants(vy_ast.Attribute, { "value.id": "self", "attr": node.name })[0] raise CallViolation( f"Function '{node.name}' calls into itself", self_node) for fn_name in sorted(call_function_names): if fn_name not in self_members: # the referenced function does not exist - this is an issue, but we'll report # it later when parsing the function so we can give more meaningful output continue # check for circular function calls sequence = _find_cyclic_call([fn_name], self_members) if sequence is not None: nodes = [] for i in range(len(sequence) - 1): fn_node = self.ast.get_children(vy_ast.FunctionDef, {"name": sequence[i]})[0] call_node = fn_node.get_descendants( vy_ast.Attribute, { "value.id": "self", "attr": sequence[i + 1] })[0] nodes.append(call_node) raise CallViolation("Contract contains cyclic function call", *nodes) # get complete list of functions that are reachable from this function function_set = set(i for i in self_members[fn_name].internal_calls if i in self_members) while True: expanded = set(x for i in function_set for x in self_members[i].internal_calls) expanded |= function_set if expanded == function_set: break function_set = expanded self_members[fn_name].recursive_calls = function_set