Esempio n. 1
0
    def create_new_event(self, create_call: ast.Call) -> Event:
        event = Event('')
        event_args = create_call.args

        if len(event_args) < 0:
            self._log_error(
                CompilerError.UnfilledArgument(line=create_call.lineno,
                                               col=create_call.col_offset,
                                               param=list(
                                                   Builtin.NewEvent.args)[0]))
        elif len(event_args) > 0:
            args_type = self.get_type(event_args[0])
            if not Type.list.is_type_of(args_type):
                self._log_error(
                    CompilerError.MismatchedTypes(
                        line=event_args[0].lineno,
                        col=event_args[0].col_offset,
                        expected_type_id=Type.list.identifier,
                        actual_type_id=args_type.identifier))
            else:
                for value in event_args[0].elts:
                    if not isinstance(value, ast.Tuple):
                        CompilerError.MismatchedTypes(
                            line=value.lineno,
                            col=value.col_offset,
                            expected_type_id=Type.tuple.identifier,
                            actual_type_id=self.get_type(value).identifier)
                    elif len(value.elts) < 2:
                        self._log_error(
                            CompilerError.UnfilledArgument(
                                line=value.lineno,
                                col=value.col_offset,
                                param=list(Builtin.NewEvent.args)[0]))
                    elif not (isinstance(value.elts[0], ast.Str) and
                              (isinstance(value.elts[1], ast.Name)
                               and isinstance(
                                   self.get_symbol(value.elts[1].id), IType))):
                        CompilerError.MismatchedTypes(
                            line=value.lineno,
                            col=value.col_offset,
                            expected_type_id=Type.tuple.identifier,
                            actual_type_id=self.get_type(value).identifier)
                    else:
                        arg_name = value.elts[0].s
                        arg_type = self.get_symbol(value.elts[1].id)
                        event.args[arg_name] = Variable(arg_type)

            if len(event_args) > 1:
                if not isinstance(event_args[1], ast.Str):
                    name_type = self.get_type(event_args[1])
                    CompilerError.MismatchedTypes(
                        line=event_args[1].lineno,
                        col=event_args[1].col_offset,
                        expected_type_id=Type.str.identifier,
                        actual_type_id=name_type.identifier)
                else:
                    event.name = event_args[1].s

        return event
Esempio n. 2
0
    def visit_Num(self, num: ast.Num) -> int:
        """
        Verifies if the number is an integer

        :param num: the python ast number node
        :return: returns the value of the number
        """
        if not isinstance(num.n, int):
            # only integer numbers are allowed
            self._log_error(
                CompilerError.InvalidType(num.lineno, num.col_offset, symbol_id=type(num.n).__name__)
            )
        return num.n
Esempio n. 3
0
    def visit_arg(self, arg: ast.arg):
        """
        Verifies if the argument of a function has a type annotation

        :param arg: the python ast arg node
        """
        if arg.annotation is None:
            self._log_error(
                CompilerError.TypeHintMissing(arg.lineno, arg.col_offset, symbol_id=arg.arg)
            )

        # continue to walk through the tree
        self.generic_visit(arg)
Esempio n. 4
0
    def visit_Constant(self, constant: ast.Constant) -> Any:
        """
        Visitor of constant values node

        :param constant: the python ast constant value node
        :return: the value of the constant
        """
        if isinstance(constant, ast.Num) and not isinstance(constant.value, int):
            # only integer numbers are allowed
            self._log_error(
                CompilerError.InvalidType(constant.lineno, constant.col_offset, symbol_id=type(constant.value).__name__)
            )
        return constant.value
Esempio n. 5
0
    def visit_Module(self, module: ast.Module):
        """
        Visitor of the module node

        Fills module symbol table

        :param module:
        """
        mod: Module = Module()
        self._current_module = mod
        self._scope_stack.append(SymbolScope())

        global_stmts = []
        function_stmts = []
        for stmt in module.body:
            if isinstance(stmt, (ast.FunctionDef, ast.AsyncFunctionDef)):
                function_stmts.append(stmt)
            elif not (isinstance(stmt, ast.Expr) and isinstance(stmt.value, ast.Constant)):
                # don't evaluate constant expression - for example: string for documentation
                if self.visit(stmt) is not Builtin.Event:
                    global_stmts.append(stmt)

        module.body = global_stmts + function_stmts
        for var_id, var in mod.variables.items():
            # all static fields must be initialized
            if not mod.is_variable_assigned(var_id):
                self._log_error(
                    CompilerError.UnresolvedReference(
                        line=var.origin.lineno if var.origin is not None else 0,
                        col=var.origin.col_offset if var.origin is not None else 0,
                        symbol_id=var_id
                    )
                )

        for stmt in function_stmts:
            result = self.visit(stmt)
            # don't evaluate the metadata function in the following analysers
            if result is Builtin.Metadata:
                module.body.remove(stmt)

        # TODO: include the body of the builtin methods to the ast
        # TODO: get module name
        self.modules['main'] = mod
        module_scope = self._scope_stack.pop()
        for symbol_id, symbol in module_scope.symbols.items():
            if symbol_id in self._global_assigned_variables:
                mod.include_symbol(symbol_id, symbol)
                mod.assign_variable(symbol_id)

        self._global_assigned_variables.clear()
        self._current_module = None
Esempio n. 6
0
    def visit_UnaryOp(self, un_op: ast.UnaryOp) -> Optional[IType]:
        """
        Verifies if the type of the operand is valid to the operation

        If the operation is valid, changes de Python operator by the Boa operator in the syntax tree

        :param un_op: the python ast unary operation node
        :return: the type of the result of the operation if the operation is valid. Otherwise, returns None
        :rtype: IType or None
        """
        operator: Operator = self.get_operator(un_op.op)
        operand = self.visit(un_op.operand)

        if not isinstance(operator, Operator):
            # the operator is invalid or it was not implemented yet
            self._log_error(
                CompilerError.UnresolvedReference(un_op.lineno, un_op.col_offset, type(un_op.op).__name__)
            )

        try:
            operation: UnaryOperation = self.get_un_op(operator, operand)
            if operation is None:
                self._log_error(
                    CompilerError.NotSupportedOperation(un_op.lineno, un_op.col_offset, operator)
                )
            elif not operation.is_supported:
                self._log_error(
                    CompilerError.NotSupportedOperation(un_op.lineno, un_op.col_offset, operator)
                )
            else:
                un_op.op = operation
                return operation.result
        except CompilerError.MismatchedTypes as raised_error:
            raised_error.line = un_op.lineno
            raised_error.col = un_op.col_offset
            # raises the exception with the line/col info
            self._log_error(raised_error)
Esempio n. 7
0
    def visit_Return(self, ret: ast.Return):
        """
        Verifies if the return of the function is the same type as the return type annotation

        :param ret: the python ast return node
        """
        ret_value: Any = self.visit(ret.value) if ret.value is not None else None
        if ret.value is not None and self.get_type(ret.value) is not Type.none:
            # multiple returns are not allowed
            if isinstance(ret.value, ast.Tuple):
                self._log_error(
                    CompilerError.TooManyReturns(ret.lineno, ret.col_offset)
                )
                return
            # it is returning something, but there is no type hint for return
            elif self._current_method.return_type is Type.none:
                self._log_error(
                    CompilerError.TypeHintMissing(ret.lineno, ret.col_offset, symbol_id=self._current_method_id)
                )
                return
        self.validate_type_variable_assign(ret, ret_value, self._current_method)

        # continue to walk through the tree
        self.generic_visit(ret)
Esempio n. 8
0
    def visit_Return(self, ret: ast.Return):
        """
        Visitor of the function return node

        If the return is a name, verifies if the symbol is defined

        :param ret: the python ast return node
        """
        if isinstance(ret.value, ast.Name):
            symbol_id = self.visit(ret.value)
            symbol = self.get_symbol(symbol_id)
            if symbol is None:
                # the symbol doesn't exists
                self._log_error(
                    CompilerError.UnresolvedReference(ret.value.lineno, ret.value.col_offset, symbol_id)
                )
Esempio n. 9
0
    def validate_if(self, if_node: ast.AST):
        """
        Verifies if the type of if test is valid

        :param if_node: the python ast if statement node
        :type if_node: ast.If or ast.IfExp
        """
        test = self.visit(if_node.test)
        test_type: IType = self.get_type(test)

        if test_type is not Type.bool:
            self._log_error(
                CompilerError.MismatchedTypes(
                    if_node.lineno, if_node.col_offset,
                    actual_type_id=test_type.identifier,
                    expected_type_id=Type.bool.identifier)
            )
Esempio n. 10
0
    def visit_Assign(self, assign: ast.Assign):
        """
        Verifies if it is a multiple assignments statement

        :param assign: the python ast variable assignment node
        """
        # multiple assignments
        if isinstance(assign.targets[0], ast.Tuple):
            self._log_error(
                CompilerError.NotSupportedOperation(assign.lineno, assign.col_offset, 'Multiple variable assignments')
            )
        else:
            for target in assign.targets:
                self.validate_type_variable_assign(target, assign.value)

        # continue to walk through the tree
        self.generic_visit(assign)
Esempio n. 11
0
    def visit_Raise(self, raise_node: ast.Raise):
        """
        Visitor of the raise node

        Verifies if the raised object is a exception

        :param raise_node: the python ast raise node
        """
        raised = self.visit(raise_node.exc)
        raised_type: IType = self.get_type(raised)
        if raised_type is not Type.exception:
            self._log_error(
                CompilerError.MismatchedTypes(
                    raise_node.lineno, raise_node.col_offset,
                    actual_type_id=raised_type.identifier,
                    expected_type_id=Type.exception.identifier
                ))
    def _validate_IsInstanceMethod(self, method: IsInstanceMethod,
                                   args_types: List[IType]):
        """
        Validates the arguments for `isinstance` method

        :param method: instance of the builtin method
        :param args_types: types of the arguments
        """
        last_arg = self.call.args[-1]
        if isinstance(last_arg, ast.Tuple) and all(
                isinstance(tpe, (ast.Attribute, ast.Name))
                for tpe in last_arg.elts):
            if len(last_arg.elts) == 1:
                # if the types tuple has only one type, remove it from inside the tuple
                last_arg = self.call.args[-1] = last_arg.elts[-1]
                args_types[-1] = args_types[-1].value_type
            elif len(last_arg.elts) > 1:
                # if there are more than one type, updates information in the instance of the method
                types: List[IType] = [
                    self.get_symbol_from_node(name) for name in last_arg.elts
                ]
                method.set_instance_type(types)
                self.call.args[-1] = last_arg.elts[-1]
                return

        from boa3.model.type.annotation.metatype import MetaType
        from boa3.model.type.type import Type
        is_ast_valid = (isinstance(last_arg, ast.Name)
                        or (isinstance(last_arg, ast.NameConstant)
                            and args_types[-1] is Type.none))

        is_id_valid = (hasattr(last_arg, 'id')
                       and last_arg.id != args_types[-1].identifier
                       and last_arg.id != args_types[-1].raw_identifier
                       and isinstance(
                           self.get_type(last_arg, use_metadata=True),
                           MetaType))

        if not is_ast_valid or is_id_valid:
            # if the value is not the identifier of a type
            self._log_error(
                CompilerError.MismatchedTypes(
                    last_arg.lineno,
                    last_arg.col_offset,
                    expected_type_id=type.__name__,
                    actual_type_id=args_types[-1].identifier))
Esempio n. 13
0
    def visit_Expr(self, expr: ast.Expr):
        """
        Visitor of the atom expression node

        :param expr:
        """
        value = self.visit(expr.value)
        if isinstance(expr.value, ast.Name):
            if (
                    # it is not a symbol of the method scope
                    (self._current_method is not None and value not in self._current_method.symbols)
                    # nor it is a symbol of the module scope
                    or (self._current_module is not None and value not in self._current_module.symbols)
                    # nor it is a symbol of the global scope
                    or value not in self.symbols
            ):
                self._log_error(
                    CompilerError.UnresolvedReference(expr.value.lineno, expr.value.col_offset, value)
                )
Esempio n. 14
0
    def validate_passed_arguments(self, call: ast.Call, args_types: List[IType], callable_id: str, callable: Callable):
        if isinstance(callable, IBuiltinMethod):
            builtin_analyser = BuiltinFunctionCallAnalyser(self, call, callable_id, callable)
            if builtin_analyser.validate():
                self.errors.extend(builtin_analyser.errors)
                self.warnings.extend(builtin_analyser.warnings)
                return

        for index, (arg_id, arg_value) in enumerate(callable.args.items()):
            param = call.args[index]
            param_type = self.get_type(param)
            args_types.append(param_type)
            if not arg_value.type.is_type_of(param_type):
                self._log_error(
                    CompilerError.MismatchedTypes(
                        param.lineno, param.col_offset,
                        arg_value.type.identifier,
                        param_type.identifier
                    ))
Esempio n. 15
0
    def get_un_op(self, operator: Operator, operand: Any) -> UnaryOperation:
        """
        Returns the binary operation specified by the operator and the types of the operands

        :param operator: the operator
        :param operand: the operand

        :return: Returns the corresponding :class:`UnaryOperation` if the types are valid.
        :raise MismatchedTypes: raised if the types aren't valid for the operator
        """
        op_type: IType = self.get_type(operand)

        actual_type: str = op_type.identifier
        operation: UnaryOperation = UnaryOp.validate_type(operator, op_type)

        if operation is not None:
            return operation
        else:
            expected_op: UnaryOperation = UnaryOp.get_operation_by_operator(operator)
            expected_type: str = expected_op.operand_type.identifier
            raise CompilerError.MismatchedTypes(0, 0, expected_type, actual_type)
Esempio n. 16
0
    def visit_Call(self, call: ast.Call) -> Optional[IType]:
        """
        Visitor of a function call node

        :param call: the python ast function call node
        :return: the result type of the called function. None if the function is not found
        """
        func_id = self.visit(call.func)
        func_symbol = self.get_symbol(func_id)

        # if func_symbol is None, the called function may be a function written after in the code
        # that's why it shouldn't log a compiler error here
        if func_symbol is None:
            return None

        if not isinstance(func_symbol, Callable):
            # verifiy if it is a builtin method with its name shadowed
            func = Builtin.get_symbol(func_id)
            func_symbol = func if func is not None else func_symbol

            if func_symbol is Type.exception:
                func_symbol = Builtin.Exception
            elif isinstance(func_symbol, ClassType):
                func_symbol = func_symbol.constructor_method()

        if not isinstance(func_symbol, Callable):
            # the symbol doesn't exists
            self._log_error(
                CompilerError.UnresolvedReference(call.func.lineno,
                                                  call.func.col_offset,
                                                  func_id))
        elif isinstance(func_symbol, IBuiltinMethod):
            if func_symbol.body is not None:
                self._builtin_functions_to_visit[func_id] = func_symbol
            elif func_symbol is Builtin.NewEvent:
                new_event = self.create_new_event(call)
                self.__include_callable(new_event.identifier, new_event)
                self._current_event = new_event

        return self.get_type(call.func)
Esempio n. 17
0
    def visit_While(self, while_node: ast.While):
        """
        Verifies if the type of while test is valid

        :param while_node: the python ast while statement node
        """
        test = self.visit(while_node.test)
        test_type: IType = self.get_type(test)

        if test_type is not Type.bool:
            self._log_error(
                CompilerError.MismatchedTypes(
                    while_node.lineno, while_node.col_offset,
                    actual_type_id=test_type.identifier,
                    expected_type_id=Type.bool.identifier)
            )

        # continue to walk through the tree
        for stmt in while_node.body:
            self.visit(stmt)
        for stmt in while_node.orelse:
            self.visit(stmt)
Esempio n. 18
0
    def update_callable_after_validation(self, call: ast.Call, callable_id: str, callable_target: Callable):
        # if the arguments are not generic, include the specified method in the symbol table
        if (isinstance(callable_target, IBuiltinMethod)
                and callable_target.identifier != callable_id
                and callable_target.raw_identifier == callable_id
                and callable_target.identifier not in self.symbols):
            self.symbols[callable_target.identifier] = callable_target
            call.func = ast.Name(lineno=call.func.lineno, col_offset=call.func.col_offset,
                                 ctx=ast.Load(), id=callable_target.identifier)

        # validates if metadata matches callable's requirements
        if hasattr(callable_target, 'requires_storage') and callable_target.requires_storage:
            if not self._metadata.has_storage:
                self._log_error(
                    CompilerError.MetadataInformationMissing(
                        line=call.func.lineno, col=call.func.col_offset,
                        symbol_id=callable_target.identifier,
                        metadata_attr_id='has_storage'
                    )
                )
            else:
                self._current_method.set_storage()
Esempio n. 19
0
    def visit_FunctionDef(self, function: ast.FunctionDef):
        """
        Visitor of the function node

        Performs the type checking in the body of the function

        :param function: the python ast function definition node
        """
        self.visit(function.args)
        method = self.symbols[function.name]
        from boa3.model.event import Event
        if isinstance(method, Method):
            self._current_method = method

            for stmt in function.body:
                self.visit(stmt)

            self._validate_return(function)

            if (len(function.body) > 0
                    and not isinstance(function.body[-1], ast.Return)
                    and method.return_type is Type.none):
                # include return None in void functions
                default_value: str = str(method.return_type.default_value)
                node: ast.AST = ast.parse(default_value).body[0].value
                function.body.append(
                    ast.Return(lineno=function.lineno, col_offset=function.col_offset, value=node)
                )
            self._current_method = None
        elif (isinstance(method, Event)  # events don't have return
              and function.returns is not None):
            return_type = self.get_type(function.returns)
            if return_type is not Type.none:
                self._log_error(
                    CompilerError.MismatchedTypes(line=function.lineno, col=function.col_offset,
                                                  expected_type_id=Type.none.identifier,
                                                  actual_type_id=return_type.identifier)
                )
Esempio n. 20
0
    def _read_metadata_object(self, function: ast.FunctionDef):
        """
        Gets the metadata object defined in this function

        :param function:
        """
        if self._metadata is not None:
            # metadata function has been defined already
            self._log_warning(
                CompilerWarning.RedeclaredSymbol(
                    line=function.lineno,
                    col=function.col_offset,
                    symbol_id=Builtin.Metadata.identifier))
        # this function must have a return and no arguments
        elif len(function.args.args) != 0:
            self._log_error(
                CompilerError.UnexpectedArgument(line=function.lineno,
                                                 col=function.col_offset))
        elif not any(isinstance(stmt, ast.Return) for stmt in function.body):
            self._log_error(
                CompilerError.MissingReturnStatement(line=function.lineno,
                                                     col=function.col_offset,
                                                     symbol_id=function.name))
        else:
            function.returns = None
            function.decorator_list = []
            module: ast.Module = ast.parse('')
            module.body = [
                node for node in self._tree.body
                if isinstance(node, (ast.ImportFrom, ast.Import))
            ]
            module.body.append(function)
            ast.copy_location(module, function)

            # executes the function
            code = compile(module, filename='<boa3>', mode='exec')
            namespace = {}
            exec(code, namespace)
            obj: Any = namespace[function.name]()

            node: ast.AST = function.body[-1] if len(
                function.body) > 0 else function
            # return must be a NeoMetadata object
            if not isinstance(obj, NeoMetadata):
                obj_type = self.get_type(obj).identifier if self.get_type(
                    obj) is not Type.any else type(obj).__name__
                self._log_error(
                    CompilerError.MismatchedTypes(
                        line=node.lineno,
                        col=node.col_offset,
                        expected_type_id=NeoMetadata.__name__,
                        actual_type_id=obj_type))
                return

            # validates the metadata attributes types
            attributes: Dict[str, Any] = {
                attr: value
                for attr, value in dict(obj.__dict__).items()
                if attr in Builtin.metadata_fields
            }
            if any(not isinstance(value, Builtin.metadata_fields[attr])
                   for attr, value in attributes.items()):
                for expected, actual in [
                    (Builtin.metadata_fields[attr], type(v_type))
                        for attr, v_type in attributes.items() if
                        not isinstance(v_type, Builtin.metadata_fields[attr])
                ]:
                    if isinstance(expected, Iterable):
                        expected_id = 'Union[{0}]'.format(', '.join(
                            [tpe.__name__ for tpe in expected]))
                    elif hasattr(expected, '__name__'):
                        expected_id = expected.__name__
                    else:
                        expected_id = str(expected)

                    self._log_error(
                        CompilerError.MismatchedTypes(
                            line=node.lineno,
                            col=node.col_offset,
                            expected_type_id=expected_id,
                            actual_type_id=actual.__name__))
            else:
                # if the function was defined correctly, sets the metadata object of the smart contract
                self._metadata = obj
                self._metadata_node = function  # for error messages only
Esempio n. 21
0
 def _log_unresolved_import(self, origin_node: ast.AST, import_id: str):
     if self._log:
         self._log_error(
             CompilerError.UnresolvedReference(line=origin_node.lineno,
                                               col=origin_node.col_offset,
                                               symbol_id=import_id))
Esempio n. 22
0
 def visit_ClassDef(self, node: ast.ClassDef):
     # TODO: refactor when classes defined by the user are implemented
     self._log_error(
         CompilerError.NotSupportedOperation(node.lineno,
                                             node.col_offset,
                                             symbol_id='class'))
Esempio n. 23
0
    def _validate_standards(self):
        for standard in self.standards:
            if standard in supportedstandard.neo_standards:
                current_standard = supportedstandard.neo_standards[standard]

                # validate standard's methods
                for standard_method in current_standard.methods:
                    method_id = standard_method.external_name
                    is_implemented = False

                    found_methods = self.get_methods_by_display_name(method_id)
                    for method in found_methods:
                        if isinstance(
                                method,
                                Method) and current_standard.match_definition(
                                    standard_method, method):
                            is_implemented = True
                            break

                    if not is_implemented:
                        self._log_error(
                            CompilerError.MissingStandardDefinition(
                                standard, method_id, standard_method))

                # validate standard's events
                events = [
                    symbol for symbol in self.symbols.values()
                    if isinstance(symbol, Event)
                ]
                # imported events should be included in the validation
                for imported in self._get_all_imports():
                    events.extend([
                        event for event in imported.all_symbols.values()
                        if isinstance(event, Event)
                    ])

                for standard_event in current_standard.events:
                    is_implemented = False
                    for event in events:
                        if (event.name == standard_event.name
                                and current_standard.match_definition(
                                    standard_event, event)):
                            is_implemented = True
                            break

                    if not is_implemented:
                        self._log_error(
                            CompilerError.MissingStandardDefinition(
                                standard, standard_event.name, standard_event))

                # validate optional methods
                for optional_method in current_standard.optionals:
                    method_id = optional_method.external_name
                    is_implemented = False

                    found_methods = self.get_methods_by_display_name(method_id)
                    for method in found_methods:
                        if isinstance(
                                method,
                                Method) and current_standard.match_definition(
                                    optional_method, method):
                            is_implemented = True
                            break

                    if found_methods and not is_implemented:
                        self._log_error(
                            CompilerError.MissingStandardDefinition(
                                standard, method_id, optional_method))
Esempio n. 24
0
    def validate_values(self, *params: Any) -> List[Any]:
        values = []
        if len(params) != 2:
            return values

        origin, visitor = params
        values.append(self.external_name)
        from boa3.analyser.astanalyser import IAstAnalyser
        if not isinstance(visitor, IAstAnalyser):
            return values

        from boa3.exception import CompilerError
        if not isinstance(origin, ast.Call):
            visitor._log_error(
                CompilerError.UnfilledArgument(origin.lineno,
                                               origin.col_offset,
                                               list(self.args.keys())[0]))
            return values

        args_names = list(self.args.keys())
        if len(origin.args) > len(args_names):
            visitor._log_error(
                CompilerError.UnexpectedArgument(origin.lineno,
                                                 origin.col_offset))
            return values

        # read the called arguments
        args_len = min(len(origin.args), len(args_names))
        values.clear()

        for x in range(args_len):
            values.append(visitor.visit(origin.args[x]))

        if len(values) < 1:
            values.append(self.external_name)

        # read the called keyword arguments
        for kwarg in origin.keywords:
            if kwarg.arg in args_names:
                x = args_names.index(kwarg.arg)
                value = visitor.visit(kwarg.value)
                values[x] = value
            else:
                visitor._log_error(
                    CompilerError.UnexpectedArgument(kwarg.lineno,
                                                     kwarg.col_offset))

        if not isinstance(values[0], str):
            visitor._log_error(
                CompilerError.UnfilledArgument(origin.lineno,
                                               origin.col_offset,
                                               list(self.args.keys())[0]))
            return values

        if visitor.get_symbol(values[0]) is not None:
            visitor._log_error(
                CompilerError.InvalidUsage(
                    origin.lineno, origin.col_offset,
                    "Only literal values are accepted for 'name' argument"))
            values[0] = self.external_name

        return values