def _check_reversed(self, node: nodes.Call) -> None: """Check that the argument to `reversed` is a sequence.""" try: argument = utils.safe_infer( utils.get_argument_from_call(node, position=0)) except utils.NoSuchArgumentError: pass else: if argument is astroid.Uninferable: return if argument is None: # Nothing was inferred. # Try to see if we have iter(). if isinstance(node.args[0], nodes.Call): try: func = next(node.args[0].func.infer()) except astroid.InferenceError: return if getattr( func, "name", None) == "iter" and utils.is_builtin_object(func): self.add_message("bad-reversed-sequence", node=node) return if isinstance(argument, (nodes.List, nodes.Tuple)): return # dicts are reversible, but only from Python 3.8 onward. Prior to # that, any class based on dict must explicitly provide a # __reversed__ method if not self._py38_plus and isinstance(argument, astroid.Instance): if any(ancestor.name == "dict" and utils.is_builtin_object(ancestor) for ancestor in itertools.chain(( argument._proxied, ), argument._proxied.ancestors())): try: argument.locals[REVERSED_PROTOCOL_METHOD] except KeyError: self.add_message("bad-reversed-sequence", node=node) return if hasattr(argument, "getattr"): # everything else is not a proper sequence for reversed() for methods in REVERSED_METHODS: for meth in methods: try: argument.getattr(meth) except astroid.NotFoundError: break else: break else: self.add_message("bad-reversed-sequence", node=node) else: self.add_message("bad-reversed-sequence", node=node)
def _check_reversed(self, node): """ check that the argument to `reversed` is a sequence """ try: argument = safe_infer(get_argument_from_call(node, position=0)) except NoSuchArgumentError: self.add_message('missing-reversed-argument', node=node) else: if argument is astroid.YES: return if argument is None: # nothing was infered # try to see if we have iter() if isinstance(node.args[0], astroid.CallFunc): try: func = node.args[0].func.infer().next() except InferenceError: return if (getattr(func, 'name', None) == 'iter' and is_builtin_object(func)): self.add_message('bad-reversed-sequence', node=node) return if isinstance(argument, astroid.Instance): if (argument._proxied.name == 'dict' and is_builtin_object(argument._proxied)): self.add_message('bad-reversed-sequence', node=node) return elif any( ancestor.name == 'dict' and is_builtin_object(ancestor) for ancestor in argument._proxied.ancestors()): # mappings aren't accepted by reversed() self.add_message('bad-reversed-sequence', node=node) return for methods in REVERSED_METHODS: for meth in methods: try: argument.getattr(meth) except astroid.NotFoundError: break else: break else: # check if it is a .deque. It doesn't seem that # we can retrieve special methods # from C implemented constructs if argument._proxied.qname().endswith(".deque"): return self.add_message('bad-reversed-sequence', node=node) elif not isinstance(argument, (astroid.List, astroid.Tuple)): # everything else is not a proper sequence for reversed() self.add_message('bad-reversed-sequence', node=node)
def _check_reversed(self, node): """ check that the argument to `reversed` is a sequence """ try: argument = safe_infer(get_argument_from_call(node, position=0)) except NoSuchArgumentError: self.add_message('missing-reversed-argument', node=node) else: if argument is astroid.YES: return if argument is None: # nothing was infered # try to see if we have iter() if isinstance(node.args[0], astroid.CallFunc): try: func = node.args[0].func.infer().next() except InferenceError: return if (getattr(func, 'name', None) == 'iter' and is_builtin_object(func)): self.add_message('bad-reversed-sequence', node=node) return if isinstance(argument, astroid.Instance): if (argument._proxied.name == 'dict' and is_builtin_object(argument._proxied)): self.add_message('bad-reversed-sequence', node=node) return elif any(ancestor.name == 'dict' and is_builtin_object(ancestor) for ancestor in argument._proxied.ancestors()): # mappings aren't accepted by reversed() self.add_message('bad-reversed-sequence', node=node) return for methods in REVERSED_METHODS: for meth in methods: try: argument.getattr(meth) except astroid.NotFoundError: break else: break else: # check if it is a .deque. It doesn't seem that # we can retrieve special methods # from C implemented constructs if argument._proxied.qname().endswith(".deque"): return self.add_message('bad-reversed-sequence', node=node) elif not isinstance(argument, (astroid.List, astroid.Tuple)): # everything else is not a proper sequence for reversed() self.add_message('bad-reversed-sequence', node=node)
def visit_name(self, node): """Detect when a "bad" built-in is referenced.""" node_infer = utils.safe_infer(node) if not utils.is_builtin_object(node_infer): # Skip not builtin objects return if node_infer.name == 'eval': self.add_message('eval-referenced', node=node)
def _check_protected_attribute_access(self, node): '''Given an attribute access node (set or get), check if attribute access is legitimate. Call _check_first_attr with node before calling this method. Valid cases are: * self._attr in a method or cls._attr in a classmethod. Checked by _check_first_attr. * Klass._attr inside "Klass" class. * Klass2._attr inside "Klass" class when Klass2 is a base class of Klass. ''' attrname = node.attrname if (is_attr_protected(attrname) and attrname not in self.config.exclude_protected): klass = node_frame_class(node) # XXX infer to be more safe and less dirty ?? # in classes, check we are not getting a parent method # through the class object or through super callee = node.expr.as_string() # We are not in a class, no remaining valid case if klass is None: self.add_message('protected-access', node=node, args=attrname) return # If the expression begins with a call to super, that's ok. if isinstance(node.expr, astroid.CallFunc) and \ isinstance(node.expr.func, astroid.Name) and \ node.expr.func.name == 'super': return # We are in a class, one remaining valid cases, Klass._attr inside # Klass if not (callee == klass.name or callee in klass.basenames): # Detect property assignments in the body of the class. # This is acceptable: # # class A: # b = property(lambda: self._b) stmt = node.parent.statement() try: if (isinstance(stmt, astroid.Assign) and (stmt in klass.body or klass.parent_of(stmt)) and isinstance(stmt.value, astroid.CallFunc) and isinstance(stmt.value.func, astroid.Name) and stmt.value.func.name == 'property' and is_builtin_object( next(stmt.value.func.infer(), None))): return except astroid.InferenceError: pass self.add_message('protected-access', node=node, args=attrname)
def _is_invalid_metaclass(metaclass): try: mro = metaclass.mro() except NotImplementedError: # Cannot have a metaclass which is not a newstyle class. return True else: if not any(is_builtin_object(cls) and cls.name == 'type' for cls in mro): return True return False
def _check_init(self, node): """check that the __init__ method call super or ancestors'__init__ method """ if (not self.linter.is_message_enabled('super-init-not-called') and not self.linter.is_message_enabled('non-parent-init-called')): return klass_node = node.parent.frame() to_call = _ancestors_to_call(klass_node) not_called_yet = dict(to_call) for stmt in node.nodes_of_class(astroid.Call): expr = stmt.func if not isinstance(expr, astroid.Attribute) \ or expr.attrname != '__init__': continue # skip the test if using super if isinstance(expr.expr, astroid.Call) and \ isinstance(expr.expr.func, astroid.Name) and \ expr.expr.func.name == 'super': return try: for klass in expr.expr.infer(): if klass is astroid.YES: continue # The infered klass can be super(), which was # assigned to a variable and the `__init__` # was called later. # # base = super() # base.__init__(...) if (isinstance(klass, astroid.Instance) and isinstance(klass._proxied, astroid.ClassDef) and is_builtin_object(klass._proxied) and klass._proxied.name == 'super'): return elif isinstance(klass, objects.Super): return try: del not_called_yet[klass] except KeyError: if klass not in to_call: self.add_message('non-parent-init-called', node=expr, args=klass.name) except astroid.InferenceError: continue for klass, method in six.iteritems(not_called_yet): cls = node_frame_class(method) if klass.name == 'object' or (cls and cls.name == 'object'): continue self.add_message('super-init-not-called', args=klass.name, node=node)
def visit_callfunc(self, node): """visit a CallFunc node, check if map or filter are called with a lambda """ if not node.args: return if not isinstance(node.args[0], astroid.Lambda): return infered = safe_infer(node.func) if (is_builtin_object(infered) and infered.name in ['map', 'filter']): self.add_message('deprecated-lambda', node=node)
def _check_protected_attribute_access(self, node): '''Given an attribute access node (set or get), check if attribute access is legitimate. Call _check_first_attr with node before calling this method. Valid cases are: * self._attr in a method or cls._attr in a classmethod. Checked by _check_first_attr. * Klass._attr inside "Klass" class. * Klass2._attr inside "Klass" class when Klass2 is a base class of Klass. ''' attrname = node.attrname if (is_attr_protected(attrname) and attrname not in self.config.exclude_protected): klass = node_frame_class(node) # XXX infer to be more safe and less dirty ?? # in classes, check we are not getting a parent method # through the class object or through super callee = node.expr.as_string() # We are not in a class, no remaining valid case if klass is None: self.add_message('protected-access', node=node, args=attrname) return # If the expression begins with a call to super, that's ok. if isinstance(node.expr, astroid.CallFunc) and \ isinstance(node.expr.func, astroid.Name) and \ node.expr.func.name == 'super': return # We are in a class, one remaining valid cases, Klass._attr inside # Klass if not (callee == klass.name or callee in klass.basenames): # Detect property assignments in the body of the class. # This is acceptable: # # class A: # b = property(lambda: self._b) stmt = node.parent.statement() try: if (isinstance(stmt, astroid.Assign) and (stmt in klass.body or klass.parent_of(stmt)) and isinstance(stmt.value, astroid.CallFunc) and isinstance(stmt.value.func, astroid.Name) and stmt.value.func.name == 'property' and is_builtin_object(next(stmt.value.func.infer(), None))): return except astroid.InferenceError: pass self.add_message('protected-access', node=node, args=attrname)
def visit_call(self, node): node_infer = utils.safe_infer(node.func) if utils.is_builtin_object(node_infer) and \ self.get_func_name(node.func) == 'format': self.add_message('prefer-other-formatting', node=node) if 'fields' == self.get_func_lib(node.func) and \ isinstance(node.parent, astroid.Assign) and \ isinstance(node.parent.parent, astroid.ClassDef): has_help = False args = misc.join_node_args_kwargs(node) for argument in args: argument_aux = argument if isinstance(argument, astroid.Keyword): argument_aux = argument.value if argument.arg in ['compute', 'search', 'inverse'] and \ isinstance(argument.value, astroid.Const) and \ argument_aux.value and \ not argument_aux.value.startswith( '_' + argument.arg + '_'): self.add_message('method-' + argument.arg, node=argument_aux) elif argument.arg == 'help': has_help = True elif argument.arg == 'selection_add': # The argument "selection_add" is for overwrite field. # Then don't need help. has_help = None if isinstance(argument_aux, astroid.CallFunc) and \ isinstance(argument_aux.func, astroid.Name) and \ argument_aux.func.name == '_': self.add_message('translation-field', node=argument_aux) if has_help is False: self.add_message('consider-add-field-help', node=node) # Check cr.commit() if isinstance(node, astroid.CallFunc) and \ isinstance(node.func, astroid.Getattr) and \ node.func.attrname == 'commit' and \ self.get_cursor_name(node.func) in self.config.cursor_expr: self.add_message('invalid-commit', node=node) # SQL Injection if isinstance(node, astroid.CallFunc) and node.args and \ isinstance(node.func, astroid.Getattr) and \ node.func.attrname == 'execute' and \ self.get_cursor_name(node.func) in self.config.cursor_expr: first_arg = node.args[0] is_bin_op = isinstance(first_arg, astroid.BinOp) and \ first_arg.op == '%' is_format = isinstance(first_arg, astroid.CallFunc) and \ self.get_func_name(first_arg.func) == 'format' if is_bin_op or is_format: self.add_message('sql-injection', node=node)
def _duplicated_isinstance_types(node): """Get the duplicated types from the underlying isinstance calls. :param astroid.BoolOp node: Node which should contain a bunch of isinstance calls. :returns: Dictionary of the comparison objects from the isinstance calls, to duplicate values from consecutive calls. :rtype: dict """ duplicated_objects = set() all_types = collections.defaultdict(set) for call in node.values: if not isinstance(call, astroid.Call) or len(call.args) != 2: continue inferred = utils.safe_infer(call.func) if not inferred or not utils.is_builtin_object(inferred): continue if inferred.name != 'isinstance': continue isinstance_object = call.args[0].as_string() isinstance_types = call.args[1] if isinstance_object in all_types: duplicated_objects.add(isinstance_object) if isinstance(isinstance_types, astroid.Tuple): elems = [ class_type.as_string() for class_type in isinstance_types.itered() ] else: elems = [isinstance_types.as_string()] all_types[isinstance_object].update(elems) # Remove all keys which not duplicated return { key: value for key, value in all_types.items() if key in duplicated_objects }
def _duplicated_isinstance_types(node): """Get the duplicated types from the underlying isinstance calls. :param astroid.BoolOp node: Node which should contain a bunch of isinstance calls. :returns: Dictionary of the comparison objects from the isinstance calls, to duplicate values from consecutive calls. :rtype: dict """ duplicated_objects = set() all_types = collections.defaultdict(set) for call in node.values: if not isinstance(call, astroid.Call) or len(call.args) != 2: continue inferred = utils.safe_infer(call.func) if not inferred or not utils.is_builtin_object(inferred): continue if inferred.name != 'isinstance': continue isinstance_object = call.args[0].as_string() isinstance_types = call.args[1] if isinstance_object in all_types: duplicated_objects.add(isinstance_object) if isinstance(isinstance_types, astroid.Tuple): elems = [class_type.as_string() for class_type in isinstance_types.itered()] else: elems = [isinstance_types.as_string()] all_types[isinstance_object].update(elems) # Remove all keys which not duplicated return {key: value for key, value in all_types.items() if key in duplicated_objects}
def visit_function(self, node): """check method arguments, overriding""" # ignore actual functions if not node.is_method(): return method_name = node.name if method_name not in self.METHOD_NAMES: return klass_node = node.parent.frame() to_call = _ancestors_to_call(klass_node, method_name) not_called_yet = dict(to_call) for stmt in node.nodes_of_class(astroid.CallFunc): expr = stmt.func if not isinstance(expr, astroid.Getattr): continue if expr.attrname != method_name: continue # Skip the test if using super if (isinstance(expr.expr, astroid.CallFunc) and isinstance(expr.expr.func, astroid.Name) and expr.expr.func.name == 'super'): return try: klass = next(expr.expr.infer()) if klass is astroid.YES: continue # The infered klass can be super(), which was # assigned to a variable and the `__init__` was called later. # # base = super() # base.__init__(...) if (isinstance(klass, astroid.Instance) and isinstance(klass._proxied, astroid.Class) and utils.is_builtin_object(klass._proxied) and klass._proxied.name == 'super'): return try: del not_called_yet[klass] except KeyError: if klass not in to_call: self.add_message( self.NON_PARENT_MESSAGE_ID, node=expr, args=(method_name, klass.name), ) except astroid.InferenceError: continue for klass, method in six.iteritems(not_called_yet): if klass.name == 'object' or method.parent.name == 'object': continue self.add_message( self.NOT_CALLED_MESSAGE_ID, args=(method_name, klass.name), node=node, )
def visit_call(self, node): node_infer = utils.safe_infer(node.func) if utils.is_builtin_object(node_infer) and \ self.get_func_name(node.func) == 'format': self.add_message('prefer-other-formatting', node=node) if ('fields' == self.get_func_lib(node.func) and isinstance(node.parent, astroid.Assign) and isinstance(node.parent.parent, astroid.ClassDef)): has_help = False args = misc.join_node_args_kwargs(node) index = 0 field_name = '' if (isinstance(node.parent, astroid.Assign) and node.parent.targets and isinstance(node.parent.targets[0], astroid.AssignName)): field_name = (node.parent.targets[0].name .replace('_', ' ')) for argument in args: argument_aux = argument # Check this 'name = fields.Char("name")' if (isinstance(argument, astroid.Const) and (index == FIELDS_METHOD.get(argument.parent.func.attrname, 0)) and (argument.value in [field_name.capitalize(), field_name.title()])): self.add_message('attribute-string-redundant', node=node) if isinstance(argument, astroid.Keyword): argument_aux = argument.value deprecated = self.config.deprecated_field_parameters if argument.arg in ['compute', 'search', 'inverse'] and \ isinstance(argument_aux, astroid.Const) and \ isinstance(argument_aux.value, string_types) and \ not argument_aux.value.startswith( '_' + argument.arg + '_'): self.add_message('method-' + argument.arg, node=argument_aux) # Check if the param string is equal to the name # of variable elif argument.arg == 'string' and \ (isinstance(argument_aux, astroid.Const) and argument_aux.value in [field_name.capitalize(), field_name.title()]): self.add_message( 'attribute-string-redundant', node=node) elif (argument.arg in deprecated): self.add_message( 'renamed-field-parameter', node=node, args=(argument.arg, deprecated[argument.arg]) ) elif argument.arg == 'help': has_help = True elif argument.arg == 'selection_add': # The argument "selection_add" is for overwrite field. # Then don't need help. has_help = None if isinstance(argument_aux, astroid.CallFunc) and \ isinstance(argument_aux.func, astroid.Name) and \ argument_aux.func.name == '_': self.add_message('translation-field', node=argument_aux) index += 1 if has_help is False: self.add_message('consider-add-field-help', node=node) # Check cr.commit() if isinstance(node, astroid.CallFunc) and \ isinstance(node.func, astroid.Getattr) and \ node.func.attrname == 'commit' and \ self.get_cursor_name(node.func) in self.config.cursor_expr: self.add_message('invalid-commit', node=node) # SQL Injection if isinstance(node, astroid.CallFunc) and node.args and \ isinstance(node.func, astroid.Getattr) and \ node.func.attrname in ('execute', 'executemany') and \ self.get_cursor_name(node.func) in self.config.cursor_expr: first_arg = node.args[0] risky = self._check_node_for_sqli_risk(first_arg) if (not risky and isinstance(first_arg, (astroid.Name, astroid.Subscript))): # 1) look for parent method / controller current = node while (current and not isinstance(current.parent, astroid.FunctionDef)): current = current.parent parent = current.parent # 2) check how was the variable built for node in parent.nodes_of_class(astroid.Assign): if node.targets[0].as_string() == first_arg.as_string(): risky = self._check_node_for_sqli_risk(node.value) if risky: break if risky: self.add_message('sql-injection', node=node)
def _is_builtin(node, function): inferred = utils.safe_infer(node) if not inferred: return False return utils.is_builtin_object(inferred) and inferred.name == function
def _is_invalid_base_class(cls): return cls.name in INVALID_BASE_CLASSES and is_builtin_object(cls)
def visit_call(self, node): infer_node = utils.safe_infer(node.func) if utils.is_builtin_object(infer_node) and infer_node.name == 'print': self.add_message('print-used', node=node) if ('fields' == self.get_func_lib(node.func) and isinstance(node.parent, astroid.Assign) and isinstance(node.parent.parent, astroid.ClassDef)): args = misc.join_node_args_kwargs(node) index = 0 field_name = '' if (isinstance(node.parent, astroid.Assign) and node.parent.targets and isinstance( node.parent.targets[0], astroid.AssignName)): field_name = (node.parent.targets[0].name.replace('_', ' ')) is_related = bool( [1 for kw in node.keywords or [] if kw.arg == 'related']) for argument in args: argument_aux = argument # Check this 'name = fields.Char("name")' if (not is_related and isinstance(argument, astroid.Const) and (index == FIELDS_METHOD.get( argument.parent.func.attrname, 0)) and (argument.value in [field_name.capitalize(), field_name.title()])): self.add_message('attribute-string-redundant', node=node) if isinstance(argument, astroid.Keyword): argument_aux = argument.value deprecated = self.config.deprecated_field_parameters if argument.arg in ['compute', 'search', 'inverse'] and \ isinstance(argument_aux, astroid.Const) and \ isinstance(argument_aux.value, string_types) and \ not argument_aux.value.startswith( '_' + argument.arg + '_'): self.add_message('method-' + argument.arg, node=argument_aux) # Check if the param string is equal to the name # of variable elif not is_related and argument.arg == 'string' and \ (isinstance(argument_aux, astroid.Const) and argument_aux.value in [field_name.capitalize(), field_name.title()]): self.add_message('attribute-string-redundant', node=node) elif (argument.arg in deprecated): self.add_message('renamed-field-parameter', node=node, args=(argument.arg, deprecated[argument.arg])) if isinstance(argument_aux, astroid.Call) and \ isinstance(argument_aux.func, astroid.Name) and \ argument_aux.func.name == '_': self.add_message('translation-field', node=argument_aux) index += 1 # Check cr.commit() if isinstance(node, astroid.Call) and \ isinstance(node.func, astroid.Attribute) and \ node.func.attrname == 'commit' and \ self.get_cursor_name(node.func) in self.config.cursor_expr: self.add_message('invalid-commit', node=node) if (isinstance(node, astroid.Call) and isinstance(node.func, astroid.Attribute) and node.func.attrname == 'with_context' and not node.keywords and node.args): # with_context(**ctx) is considered a keywords # So, if only one args is received it is overridden self.add_message('context-overridden', node=node, args=(node.args[0].as_string(), )) # Call the message_post() base_dirname = os.path.basename( os.path.normpath(os.path.dirname(self.linter.current_file))) if (base_dirname != 'tests' and isinstance(node, astroid.Call) and isinstance(node.func, astroid.Attribute) and node.func.attrname == 'message_post'): for arg in itertools.chain(node.args, node.keywords or []): if isinstance(arg, astroid.Keyword): keyword = arg.arg value = arg.value else: keyword = '' value = arg if keyword and keyword not in ('subject', 'body'): continue as_string = '' # case: message_post(body='String') if isinstance(value, astroid.Const): as_string = value.as_string() # case: message_post(body='String %s' % (...)) elif (isinstance(value, astroid.BinOp) and value.op == '%' and isinstance(value.left, astroid.Const) # The right part is translatable only if it's a # function or a list of functions and not (isinstance(value.right, ( astroid.Call, astroid.Tuple, astroid.List)) and all([ isinstance(child, astroid.Call) for child in getattr(value.right, 'elts', []) ]))): as_string = value.left.as_string() # case: message_post(body='String {...}'.format(...)) elif (isinstance(value, astroid.Call) and isinstance(value.func, astroid.Attribute) and isinstance(value.func.expr, astroid.Const) and value.func.attrname == 'format'): as_string = value.func.expr.as_string() if as_string: keyword = keyword and '%s=' % keyword self.add_message('translation-required', node=node, args=('message_post', keyword, as_string)) # Call _(...) with variables into the term to be translated if (isinstance(node.func, astroid.Name) and node.func.name == '_' and node.args): wrong = '' right = '' arg = node.args[0] # case: _('...' % (variables)) if isinstance(arg, astroid.BinOp) and arg.op == '%': wrong = '%s %% %s' % (arg.left.as_string(), arg.right.as_string()) right = '_(%s) %% %s' % (arg.left.as_string(), arg.right.as_string()) # Case: _('...'.format(variables)) elif (isinstance(arg, astroid.Call) and isinstance(arg.func, astroid.Attribute) and isinstance(arg.func.expr, astroid.Const) and arg.func.attrname == 'format'): self.add_message('str-format-used', node=node) wrong = arg.as_string() params_as_string = ', '.join([ x.as_string() for x in itertools.chain(arg.args, arg.keywords or []) ]) right = '_(%s).format(%s)' % (arg.func.expr.as_string(), params_as_string) if wrong and right: self.add_message('translation-contains-variable', node=node, args=(wrong, right)) # translation-positional-used: Check "string to translate" # to check "%s %s..." used where the position can't be changed str2translate = arg.as_string() printf_args = (misc.WrapperModuleChecker. _get_printf_str_args_kwargs(str2translate)) if isinstance(printf_args, tuple) and len(printf_args) >= 2: # Return tuple for %s and dict for %(varname)s # Check just the following cases "%s %s..." self.add_message('translation-positional-used', node=node, args=(str2translate, )) # SQL Injection if self._check_sql_injection_risky(node): self.add_message('sql-injection', node=node) # external-request-timeout lib_alias = self.get_func_lib(node.func) # Use dict "self._from_imports" to know the source library of the method lib_original = self._from_imports.get(lib_alias) or lib_alias func_name = self.get_func_name(node.func) lib_original_func_name = ( # If it using "requests.request()" "%s.%s" % (lib_original, func_name) if lib_original # If it using "from requests import request;request()" else self._from_imports.get(func_name)) if lib_original_func_name in self.config.external_request_timeout_methods: for argument in misc.join_node_args_kwargs(node): if not isinstance(argument, astroid.Keyword): continue if argument.arg == 'timeout': break else: self.add_message('external-request-timeout', node=node, args=(lib_original_func_name, ))
def visit_call(self, node): infer_node = utils.safe_infer(node.func) if utils.is_builtin_object(infer_node) and infer_node.name == 'print': self.add_message('print-used', node=node) if ('fields' == self.get_func_lib(node.func) and isinstance(node.parent, astroid.Assign) and isinstance(node.parent.parent, astroid.ClassDef)): args = misc.join_node_args_kwargs(node) index = 0 field_name = '' if (isinstance(node.parent, astroid.Assign) and node.parent.targets and isinstance(node.parent.targets[0], astroid.AssignName)): field_name = (node.parent.targets[0].name .replace('_', ' ')) for argument in args: argument_aux = argument # Check this 'name = fields.Char("name")' if (isinstance(argument, astroid.Const) and (index == FIELDS_METHOD.get(argument.parent.func.attrname, 0)) and (argument.value in [field_name.capitalize(), field_name.title()])): self.add_message('attribute-string-redundant', node=node) if isinstance(argument, astroid.Keyword): argument_aux = argument.value deprecated = self.config.deprecated_field_parameters if argument.arg in ['compute', 'search', 'inverse'] and \ isinstance(argument_aux, astroid.Const) and \ isinstance(argument_aux.value, string_types) and \ not argument_aux.value.startswith( '_' + argument.arg + '_'): self.add_message('method-' + argument.arg, node=argument_aux) # Check if the param string is equal to the name # of variable elif argument.arg == 'string' and \ (isinstance(argument_aux, astroid.Const) and argument_aux.value in [field_name.capitalize(), field_name.title()]): self.add_message( 'attribute-string-redundant', node=node) elif (argument.arg in deprecated): self.add_message( 'renamed-field-parameter', node=node, args=(argument.arg, deprecated[argument.arg]) ) if isinstance(argument_aux, astroid.Call) and \ isinstance(argument_aux.func, astroid.Name) and \ argument_aux.func.name == '_': self.add_message('translation-field', node=argument_aux) index += 1 # Check cr.commit() if isinstance(node, astroid.Call) and \ isinstance(node.func, astroid.Attribute) and \ node.func.attrname == 'commit' and \ self.get_cursor_name(node.func) in self.config.cursor_expr: self.add_message('invalid-commit', node=node) # Call the message_post() if (isinstance(node, astroid.Call) and isinstance(node.func, astroid.Attribute) and node.func.attrname == 'message_post'): for arg in itertools.chain(node.args, node.keywords or []): if isinstance(arg, astroid.Keyword): keyword = arg.arg value = arg.value else: keyword = '' value = arg if keyword and keyword not in ('subject', 'body'): continue as_string = '' # case: message_post(body='String') if isinstance(value, astroid.Const): as_string = value.as_string() # case: message_post(body='String %s' % (...)) elif (isinstance(value, astroid.BinOp) and value.op == '%' and isinstance(value.left, astroid.Const) # The right part is translatable only if it's a # function or a list of functions and not ( isinstance(value.right, ( astroid.Call, astroid.Tuple, astroid.List)) and all([ isinstance(child, astroid.Call) for child in getattr(value.right, 'elts', []) ]))): as_string = value.left.as_string() # case: message_post(body='String {...}'.format(...)) elif (isinstance(value, astroid.Call) and isinstance(value.func, astroid.Attribute) and isinstance(value.func.expr, astroid.Const) and value.func.attrname == 'format'): as_string = value.func.expr.as_string() if as_string: keyword = keyword and '%s=' % keyword self.add_message( 'translation-required', node=node, args=('message_post', keyword, as_string)) # Call _(...) with variables into the term to be translated if (isinstance(node.func, astroid.Name) and node.func.name == '_' and node.args): wrong = '' right = '' arg = node.args[0] # case: _('...' % (variables)) if isinstance(arg, astroid.BinOp) and arg.op == '%': wrong = '%s %% %s' % ( arg.left.as_string(), arg.right.as_string()) right = '_(%s) %% %s' % ( arg.left.as_string(), arg.right.as_string()) # Case: _('...'.format(variables)) elif (isinstance(arg, astroid.Call) and isinstance(arg.func, astroid.Attribute) and isinstance(arg.func.expr, astroid.Const) and arg.func.attrname == 'format'): self.add_message('str-format-used', node=node) wrong = arg.as_string() params_as_string = ', '.join([ x.as_string() for x in itertools.chain(arg.args, arg.keywords or [])]) right = '_(%s).format(%s)' % ( arg.func.expr.as_string(), params_as_string) if wrong and right: self.add_message( 'translation-contains-variable', node=node, args=(wrong, right)) # translation-positional-used: Check "string to translate" # to check "%s %s..." used where the position can't be changed str2translate = arg.as_string() printf_args = ( misc.WrapperModuleChecker. _get_printf_str_args_kwargs(str2translate)) if isinstance(printf_args, tuple) and len(printf_args) >= 2: # Return tuple for %s and dict for %(varname)s # Check just the following cases "%s %s..." self.add_message('translation-positional-used', node=node, args=(str2translate,)) # SQL Injection if isinstance(node, astroid.Call) and node.args and \ isinstance(node.func, astroid.Attribute) and \ node.func.attrname in ('execute', 'executemany') and \ self.get_cursor_name(node.func) in self.config.cursor_expr: first_arg = node.args[0] risky = self._check_node_for_sqli_risk(first_arg) if not risky: for node_assignation in self._get_assignation_nodes(first_arg): risky = self._check_node_for_sqli_risk(node_assignation) if risky: break if risky: self.add_message('sql-injection', node=node)
def visit_function(self, node): """Called for every function definition in the source code.""" # ignore actual functions if not node.is_method(): return method_name = node.name if method_name not in self.METHOD_NAMES: return klass_node = node.parent.frame() to_call = _ancestors_to_call(klass_node, method_name) not_called_yet = dict(to_call) for stmt in node.nodes_of_class(astroid.CallFunc): expr = stmt.func if not isinstance(expr, astroid.Getattr): continue if expr.attrname != method_name: continue # Skip the test if using super if (isinstance(expr.expr, astroid.CallFunc) and isinstance(expr.expr.func, astroid.Name) and expr.expr.func.name == 'super'): return try: klass = next(expr.expr.infer()) if klass is astroid.YES: continue # The infered klass can be super(), which was # assigned to a variable and the `__init__` was called later. # # base = super() # base.__init__(...) # pylint: disable=protected-access if (isinstance(klass, astroid.Instance) and isinstance(klass._proxied, astroid.Class) and utils.is_builtin_object(klass._proxied) and klass._proxied.name == 'super'): return try: del not_called_yet[klass] except KeyError: if klass not in to_call: self.add_message( self.NON_PARENT_MESSAGE_ID, node=expr, args=(method_name, klass.name), ) except astroid.InferenceError: continue for klass, method in six.iteritems(not_called_yet): if klass.name == 'object' or method.parent.name == 'object': continue self.add_message( self.NOT_CALLED_MESSAGE_ID, args=(method_name, klass.name), node=node, )
def visit_functiondef(self, node): """Called for every function definition in the source code.""" # ignore actual functions if not node.is_method(): return method_name = node.name if method_name not in self.METHOD_NAMES: return klass_node = node.parent.frame() to_call = _ancestors_to_call(klass_node, method_name) not_called_yet = dict(to_call) for stmt in node.nodes_of_class(astroid.Call): expr = stmt.func if not isinstance(expr, astroid.Attribute): continue if expr.attrname != method_name: continue # Skip the test if using super if ( isinstance(expr.expr, astroid.Call) and isinstance(expr.expr.func, astroid.Name) and expr.expr.func.name == "super" ): return try: klass = next(expr.expr.infer()) if klass is astroid.Uninferable: continue # The infered klass can be super(), which was # assigned to a variable and the `__init__` was called later. # # base = super() # base.__init__(...) # pylint: disable=protected-access if ( isinstance(klass, astroid.Instance) and isinstance(klass._proxied, astroid.ClassDef) and utils.is_builtin_object(klass._proxied) and klass._proxied.name == "super" ): return if isinstance(klass, astroid.objects.Super): return try: del not_called_yet[klass] except KeyError: if klass not in to_call: self.add_message( self.NON_PARENT_MESSAGE_ID, node=expr, args=(method_name, usable_class_name(klass)) ) except astroid.InferenceError: continue for klass, method in not_called_yet.items(): if klass.name == "object" or method.parent.name == "object": continue self.add_message(self.NOT_CALLED_MESSAGE_ID, args=(method_name, usable_class_name(klass)), node=node)