OLD | NEW |
1 # Copyright (c) 2006-2013 LOGILAB S.A. (Paris, FRANCE). | 1 # Copyright (c) 2006-2013 LOGILAB S.A. (Paris, FRANCE). |
2 # http://www.logilab.fr/ -- mailto:contact@logilab.fr | 2 # http://www.logilab.fr/ -- mailto:contact@logilab.fr |
3 # | 3 # |
4 # This program is free software; you can redistribute it and/or modify it under | 4 # This program is free software; you can redistribute it and/or modify it under |
5 # the terms of the GNU General Public License as published by the Free Software | 5 # the terms of the GNU General Public License as published by the Free Software |
6 # Foundation; either version 2 of the License, or (at your option) any later | 6 # Foundation; either version 2 of the License, or (at your option) any later |
7 # version. | 7 # version. |
8 # | 8 # |
9 # This program is distributed in the hope that it will be useful, but WITHOUT | 9 # This program is distributed in the hope that it will be useful, but WITHOUT |
10 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS | 10 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
11 # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. | 11 # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. |
12 # | 12 # |
13 # You should have received a copy of the GNU General Public License along with | 13 # You should have received a copy of the GNU General Public License along with |
14 # this program; if not, write to the Free Software Foundation, Inc., | 14 # this program; if not, write to the Free Software Foundation, Inc., |
15 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | 15 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
16 """try to find more bugs in the code using astroid inference capabilities | 16 """try to find more bugs in the code using astroid inference capabilities |
17 """ | 17 """ |
18 | 18 |
19 import re | 19 import re |
20 import shlex | 20 import shlex |
21 | 21 |
22 import astroid | 22 import astroid |
23 from astroid import InferenceError, NotFoundError, YES, Instance | 23 from astroid import InferenceError, NotFoundError, YES, Instance |
24 from astroid.bases import BUILTINS | 24 from astroid.bases import BUILTINS |
25 | 25 |
26 from pylint.interfaces import IAstroidChecker | 26 from pylint.interfaces import IAstroidChecker, INFERENCE, INFERENCE_FAILURE |
27 from pylint.checkers import BaseChecker | 27 from pylint.checkers import BaseChecker |
28 from pylint.checkers.utils import safe_infer, is_super, check_messages | 28 from pylint.checkers.utils import ( |
| 29 safe_infer, is_super, |
| 30 check_messages, decorated_with_property) |
29 | 31 |
30 MSGS = { | 32 MSGS = { |
31 'E1101': ('%s %r has no %r member', | 33 'E1101': ('%s %r has no %r member', |
32 'no-member', | 34 'no-member', |
33 'Used when a variable is accessed for an unexistent member.'), | 35 'Used when a variable is accessed for an unexistent member.', |
| 36 {'old_names': [('E1103', 'maybe-no-member')]}), |
34 'E1102': ('%s is not callable', | 37 'E1102': ('%s is not callable', |
35 'not-callable', | 38 'not-callable', |
36 'Used when an object being called has been inferred to a non \ | 39 'Used when an object being called has been inferred to a non \ |
37 callable object'), | 40 callable object'), |
38 'E1103': ('%s %r has no %r member (but some types could not be inferred)', | |
39 'maybe-no-member', | |
40 'Used when a variable is accessed for an unexistent member, but \ | |
41 astroid was not able to interpret all possible types of this \ | |
42 variable.'), | |
43 'E1111': ('Assigning to function call which doesn\'t return', | 41 'E1111': ('Assigning to function call which doesn\'t return', |
44 'assignment-from-no-return', | 42 'assignment-from-no-return', |
45 'Used when an assignment is done on a function call but the \ | 43 'Used when an assignment is done on a function call but the \ |
46 inferred function doesn\'t return anything.'), | 44 inferred function doesn\'t return anything.'), |
47 'W1111': ('Assigning to function call which only returns None', | 45 'W1111': ('Assigning to function call which only returns None', |
48 'assignment-from-none', | 46 'assignment-from-none', |
49 'Used when an assignment is done on a function call but the \ | 47 'Used when an assignment is done on a function call but the \ |
50 inferred function returns nothing but None.'), | 48 inferred function returns nothing but None.'), |
51 | 49 |
52 'E1120': ('No value for argument %s in %s call', | 50 'E1120': ('No value for argument %s in %s call', |
53 'no-value-for-parameter', | 51 'no-value-for-parameter', |
54 'Used when a function call passes too few arguments.'), | 52 'Used when a function call passes too few arguments.'), |
55 'E1121': ('Too many positional arguments for %s call', | 53 'E1121': ('Too many positional arguments for %s call', |
56 'too-many-function-args', | 54 'too-many-function-args', |
57 'Used when a function call passes too many positional \ | 55 'Used when a function call passes too many positional \ |
58 arguments.'), | 56 arguments.'), |
59 'E1122': ('Duplicate keyword argument %r in %s call', | |
60 'duplicate-keyword-arg', | |
61 'Used when a function call passes the same keyword argument \ | |
62 multiple times.', | |
63 {'maxversion': (2, 6)}), | |
64 'E1123': ('Unexpected keyword argument %r in %s call', | 57 'E1123': ('Unexpected keyword argument %r in %s call', |
65 'unexpected-keyword-arg', | 58 'unexpected-keyword-arg', |
66 'Used when a function call passes a keyword argument that \ | 59 'Used when a function call passes a keyword argument that \ |
67 doesn\'t correspond to one of the function\'s parameter names.'), | 60 doesn\'t correspond to one of the function\'s parameter names.'), |
68 'E1124': ('Argument %r passed by position and keyword in %s call', | 61 'E1124': ('Argument %r passed by position and keyword in %s call', |
69 'redundant-keyword-arg', | 62 'redundant-keyword-arg', |
70 'Used when a function call would result in assigning multiple \ | 63 'Used when a function call would result in assigning multiple \ |
71 values to a function parameter, one value from a positional \ | 64 values to a function parameter, one value from a positional \ |
72 argument and one from a keyword argument.'), | 65 argument and one from a keyword argument.'), |
73 'E1125': ('Missing mandatory keyword argument %r in %s call', | 66 'E1125': ('Missing mandatory keyword argument %r in %s call', |
(...skipping 111 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
185 if self.config.zope: | 178 if self.config.zope: |
186 self.generated_members.extend(('REQUEST', 'acl_users', 'aq_parent')) | 179 self.generated_members.extend(('REQUEST', 'acl_users', 'aq_parent')) |
187 | 180 |
188 def visit_assattr(self, node): | 181 def visit_assattr(self, node): |
189 if isinstance(node.ass_type(), astroid.AugAssign): | 182 if isinstance(node.ass_type(), astroid.AugAssign): |
190 self.visit_getattr(node) | 183 self.visit_getattr(node) |
191 | 184 |
192 def visit_delattr(self, node): | 185 def visit_delattr(self, node): |
193 self.visit_getattr(node) | 186 self.visit_getattr(node) |
194 | 187 |
195 @check_messages('no-member', 'maybe-no-member') | 188 @check_messages('no-member') |
196 def visit_getattr(self, node): | 189 def visit_getattr(self, node): |
197 """check that the accessed attribute exists | 190 """check that the accessed attribute exists |
198 | 191 |
199 to avoid to much false positives for now, we'll consider the code as | 192 to avoid to much false positives for now, we'll consider the code as |
200 correct if a single of the inferred nodes has the accessed attribute. | 193 correct if a single of the inferred nodes has the accessed attribute. |
201 | 194 |
202 function/method, super call and metaclasses are ignored | 195 function/method, super call and metaclasses are ignored |
203 """ | 196 """ |
204 # generated_members may containt regular expressions | 197 # generated_members may containt regular expressions |
205 # (surrounded by quote `"` and followed by a comma `,`) | 198 # (surrounded by quote `"` and followed by a comma `,`) |
(...skipping 41 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
247 # XXX method / function | 240 # XXX method / function |
248 continue | 241 continue |
249 except NotFoundError: | 242 except NotFoundError: |
250 if isinstance(owner, astroid.Function) and owner.decorators: | 243 if isinstance(owner, astroid.Function) and owner.decorators: |
251 continue | 244 continue |
252 if isinstance(owner, Instance) and owner.has_dynamic_getattr(): | 245 if isinstance(owner, Instance) and owner.has_dynamic_getattr(): |
253 continue | 246 continue |
254 # explicit skipping of module member access | 247 # explicit skipping of module member access |
255 if owner.root().name in self.config.ignored_modules: | 248 if owner.root().name in self.config.ignored_modules: |
256 continue | 249 continue |
| 250 if isinstance(owner, astroid.Class): |
| 251 # Look up in the metaclass only if the owner is itself |
| 252 # a class. |
| 253 # TODO: getattr doesn't return by default members |
| 254 # from the metaclass, because handling various cases |
| 255 # of methods accessible from the metaclass itself |
| 256 # and/or subclasses only is too complicated for little to |
| 257 # no benefit. |
| 258 metaclass = owner.metaclass() |
| 259 try: |
| 260 if metaclass and metaclass.getattr(node.attrname): |
| 261 continue |
| 262 except NotFoundError: |
| 263 pass |
257 missingattr.add((owner, name)) | 264 missingattr.add((owner, name)) |
258 continue | 265 continue |
259 # stop on the first found | 266 # stop on the first found |
260 break | 267 break |
261 else: | 268 else: |
262 # we have not found any node with the attributes, display the | 269 # we have not found any node with the attributes, display the |
263 # message for infered nodes | 270 # message for infered nodes |
264 done = set() | 271 done = set() |
265 for owner, name in missingattr: | 272 for owner, name in missingattr: |
266 if isinstance(owner, Instance): | 273 if isinstance(owner, Instance): |
267 actual = owner._proxied | 274 actual = owner._proxied |
268 else: | 275 else: |
269 actual = owner | 276 actual = owner |
270 if actual in done: | 277 if actual in done: |
271 continue | 278 continue |
272 done.add(actual) | 279 done.add(actual) |
273 if inference_failure: | 280 confidence = INFERENCE if not inference_failure else INFERENCE_F
AILURE |
274 msgid = 'maybe-no-member' | 281 self.add_message('no-member', node=node, |
275 else: | |
276 msgid = 'no-member' | |
277 self.add_message(msgid, node=node, | |
278 args=(owner.display_type(), name, | 282 args=(owner.display_type(), name, |
279 node.attrname)) | 283 node.attrname), |
| 284 confidence=confidence) |
280 | 285 |
281 @check_messages('assignment-from-no-return', 'assignment-from-none') | 286 @check_messages('assignment-from-no-return', 'assignment-from-none') |
282 def visit_assign(self, node): | 287 def visit_assign(self, node): |
283 """check that if assigning to a function call, the function is | 288 """check that if assigning to a function call, the function is |
284 possibly returning something valuable | 289 possibly returning something valuable |
285 """ | 290 """ |
286 if not isinstance(node.value, astroid.CallFunc): | 291 if not isinstance(node.value, astroid.CallFunc): |
287 return | 292 return |
288 function_node = safe_infer(node.value.func) | 293 function_node = safe_infer(node.value.func) |
289 # skip class, generator and incomplete function definition | 294 # skip class, generator and incomplete function definition |
(...skipping 36 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
326 klass = safe_infer(expr) | 331 klass = safe_infer(expr) |
327 if (klass is None or klass is astroid.YES or | 332 if (klass is None or klass is astroid.YES or |
328 not isinstance(klass, astroid.Instance)): | 333 not isinstance(klass, astroid.Instance)): |
329 return | 334 return |
330 | 335 |
331 try: | 336 try: |
332 attrs = klass._proxied.getattr(node.func.attrname) | 337 attrs = klass._proxied.getattr(node.func.attrname) |
333 except astroid.NotFoundError: | 338 except astroid.NotFoundError: |
334 return | 339 return |
335 | 340 |
336 stop_checking = False | |
337 for attr in attrs: | 341 for attr in attrs: |
338 if attr is astroid.YES: | 342 if attr is astroid.YES: |
339 continue | 343 continue |
340 if stop_checking: | |
341 break | |
342 if not isinstance(attr, astroid.Function): | 344 if not isinstance(attr, astroid.Function): |
343 continue | 345 continue |
344 | 346 |
345 # Decorated, see if it is decorated with a property | 347 # Decorated, see if it is decorated with a property |
346 if not attr.decorators: | 348 if decorated_with_property(attr): |
347 continue | 349 self.add_message('not-callable', node=node, |
348 for decorator in attr.decorators.nodes: | 350 args=node.func.as_string()) |
349 if not isinstance(decorator, astroid.Name): | 351 break |
350 continue | |
351 try: | |
352 for infered in decorator.infer(): | |
353 property_like = False | |
354 if isinstance(infered, astroid.Class): | |
355 if (infered.root().name == BUILTINS and | |
356 infered.name == 'property'): | |
357 property_like = True | |
358 else: | |
359 for ancestor in infered.ancestors(): | |
360 if (ancestor.name == 'property' and | |
361 ancestor.root().name == BUILTINS): | |
362 property_like = True | |
363 break | |
364 if property_like: | |
365 self.add_message('not-callable', node=node, | |
366 args=node.func.as_string()) | |
367 stop_checking = True | |
368 break | |
369 except InferenceError: | |
370 pass | |
371 if stop_checking: | |
372 break | |
373 | 352 |
374 @check_messages(*(list(MSGS.keys()))) | 353 @check_messages(*(list(MSGS.keys()))) |
375 def visit_callfunc(self, node): | 354 def visit_callfunc(self, node): |
376 """check that called functions/methods are inferred to callable objects, | 355 """check that called functions/methods are inferred to callable objects, |
377 and that the arguments passed to the function match the parameters in | 356 and that the arguments passed to the function match the parameters in |
378 the inferred function's definition | 357 the inferred function's definition |
379 """ | 358 """ |
380 # Build the set of keyword arguments, checking for duplicate keywords, | 359 # Build the set of keyword arguments, checking for duplicate keywords, |
381 # and count the positional arguments. | 360 # and count the positional arguments. |
382 keyword_args = set() | 361 keyword_args = set() |
383 num_positional_args = 0 | 362 num_positional_args = 0 |
384 for arg in node.args: | 363 for arg in node.args: |
385 if isinstance(arg, astroid.Keyword): | 364 if isinstance(arg, astroid.Keyword): |
386 keyword = arg.arg | 365 keyword_args.add(arg.arg) |
387 if keyword in keyword_args: | |
388 self.add_message('duplicate-keyword-arg', node=node, | |
389 args=(keyword, 'function')) | |
390 keyword_args.add(keyword) | |
391 else: | 366 else: |
392 num_positional_args += 1 | 367 num_positional_args += 1 |
393 | 368 |
394 called = safe_infer(node.func) | 369 called = safe_infer(node.func) |
395 # only function, generator and object defining __call__ are allowed | 370 # only function, generator and object defining __call__ are allowed |
396 if called is not None and not called.callable(): | 371 if called is not None and not called.callable(): |
397 self.add_message('not-callable', node=node, | 372 self.add_message('not-callable', node=node, |
398 args=node.func.as_string()) | 373 args=node.func.as_string()) |
399 | 374 |
400 self._check_uninferable_callfunc(node) | 375 self._check_uninferable_callfunc(node) |
(...skipping 141 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
542 @check_messages('invalid-sequence-index') | 517 @check_messages('invalid-sequence-index') |
543 def visit_index(self, node): | 518 def visit_index(self, node): |
544 if not node.parent or not hasattr(node.parent, "value"): | 519 if not node.parent or not hasattr(node.parent, "value"): |
545 return | 520 return |
546 | 521 |
547 # Look for index operations where the parent is a sequence type. | 522 # Look for index operations where the parent is a sequence type. |
548 # If the types can be determined, only allow indices to be int, | 523 # If the types can be determined, only allow indices to be int, |
549 # slice or instances with __index__. | 524 # slice or instances with __index__. |
550 | 525 |
551 parent_type = safe_infer(node.parent.value) | 526 parent_type = safe_infer(node.parent.value) |
552 | |
553 if not isinstance(parent_type, (astroid.Class, astroid.Instance)): | 527 if not isinstance(parent_type, (astroid.Class, astroid.Instance)): |
554 return | 528 return |
555 | 529 |
556 # Determine what method on the parent this index will use | 530 # Determine what method on the parent this index will use |
557 # The parent of this node will be a Subscript, and the parent of that | 531 # The parent of this node will be a Subscript, and the parent of that |
558 # node determines if the Subscript is a get, set, or delete operation. | 532 # node determines if the Subscript is a get, set, or delete operation. |
559 operation = node.parent.parent | 533 operation = node.parent.parent |
560 if isinstance(operation, astroid.Assign): | 534 if isinstance(operation, astroid.Assign): |
561 methodname = '__setitem__' | 535 methodname = '__setitem__' |
562 elif isinstance(operation, astroid.Delete): | 536 elif isinstance(operation, astroid.Delete): |
563 methodname = '__delitem__' | 537 methodname = '__delitem__' |
564 else: | 538 else: |
565 methodname = '__getitem__' | 539 methodname = '__getitem__' |
566 | 540 |
567 # Check if this instance's __getitem__, __setitem__, or __delitem__, as | 541 # Check if this instance's __getitem__, __setitem__, or __delitem__, as |
568 # appropriate to the statement, is implemented in a builtin sequence | 542 # appropriate to the statement, is implemented in a builtin sequence |
569 # type. This way we catch subclasses of sequence types but skip classes | 543 # type. This way we catch subclasses of sequence types but skip classes |
570 # that override __getitem__ and which may allow non-integer indices. | 544 # that override __getitem__ and which may allow non-integer indices. |
571 try: | 545 try: |
572 methods = parent_type.getattr(methodname) | 546 methods = parent_type.getattr(methodname) |
573 if methods is astroid.YES: | 547 if methods is astroid.YES: |
574 return | 548 return |
575 itemmethod = methods[0] | 549 itemmethod = methods[0] |
576 except (astroid.NotFoundError, IndexError): | 550 except (astroid.NotFoundError, IndexError): |
577 return | 551 return |
578 | 552 |
579 if not isinstance(itemmethod, astroid.Function): | 553 if not isinstance(itemmethod, astroid.Function): |
580 return | 554 return |
581 | |
582 if itemmethod.root().name != BUILTINS: | 555 if itemmethod.root().name != BUILTINS: |
583 return | 556 return |
584 | |
585 if not itemmethod.parent: | 557 if not itemmethod.parent: |
586 return | 558 return |
587 | |
588 if itemmethod.parent.name not in SEQUENCE_TYPES: | 559 if itemmethod.parent.name not in SEQUENCE_TYPES: |
589 return | 560 return |
590 | 561 |
591 # For ExtSlice objects coming from visit_extslice, no further | 562 # For ExtSlice objects coming from visit_extslice, no further |
592 # inference is necessary, since if we got this far the ExtSlice | 563 # inference is necessary, since if we got this far the ExtSlice |
593 # is an error. | 564 # is an error. |
594 if isinstance(node, astroid.ExtSlice): | 565 if isinstance(node, astroid.ExtSlice): |
595 index_type = node | 566 index_type = node |
596 else: | 567 else: |
597 index_type = safe_infer(node) | 568 index_type = safe_infer(node) |
598 | |
599 if index_type is None or index_type is astroid.YES: | 569 if index_type is None or index_type is astroid.YES: |
600 return | 570 return |
601 | 571 |
602 # Constants must be of type int | 572 # Constants must be of type int |
603 if isinstance(index_type, astroid.Const): | 573 if isinstance(index_type, astroid.Const): |
604 if isinstance(index_type.value, int): | 574 if isinstance(index_type.value, int): |
605 return | 575 return |
606 # Instance values must be int, slice, or have an __index__ method | 576 # Instance values must be int, slice, or have an __index__ method |
607 elif isinstance(index_type, astroid.Instance): | 577 elif isinstance(index_type, astroid.Instance): |
608 if index_type.pytype() in (BUILTINS + '.int', BUILTINS + '.slice'): | 578 if index_type.pytype() in (BUILTINS + '.int', BUILTINS + '.slice'): |
609 return | 579 return |
610 | |
611 try: | 580 try: |
612 index_type.getattr('__index__') | 581 index_type.getattr('__index__') |
613 return | 582 return |
614 except astroid.NotFoundError: | 583 except astroid.NotFoundError: |
615 pass | 584 pass |
616 | 585 |
617 # Anything else is an error | 586 # Anything else is an error |
618 self.add_message('invalid-sequence-index', node=node) | 587 self.add_message('invalid-sequence-index', node=node) |
619 | 588 |
620 @check_messages('invalid-slice-index') | 589 @check_messages('invalid-slice-index') |
621 def visit_slice(self, node): | 590 def visit_slice(self, node): |
622 # Check the type of each part of the slice | 591 # Check the type of each part of the slice |
623 for index in (node.lower, node.upper, node.step): | 592 for index in (node.lower, node.upper, node.step): |
624 if index is None: | 593 if index is None: |
625 continue | 594 continue |
626 | 595 |
627 index_type = safe_infer(index) | 596 index_type = safe_infer(index) |
628 | |
629 if index_type is None or index_type is astroid.YES: | 597 if index_type is None or index_type is astroid.YES: |
630 continue | 598 continue |
631 | 599 |
632 # Constants must of type int or None | 600 # Constants must of type int or None |
633 if isinstance(index_type, astroid.Const): | 601 if isinstance(index_type, astroid.Const): |
634 if isinstance(index_type.value, (int, type(None))): | 602 if isinstance(index_type.value, (int, type(None))): |
635 continue | 603 continue |
636 # Instance values must be of type int, None or an object | 604 # Instance values must be of type int, None or an object |
637 # with __index__ | 605 # with __index__ |
638 elif isinstance(index_type, astroid.Instance): | 606 elif isinstance(index_type, astroid.Instance): |
639 if index_type.pytype() in (BUILTINS + '.int', | 607 if index_type.pytype() in (BUILTINS + '.int', |
640 BUILTINS + '.NoneType'): | 608 BUILTINS + '.NoneType'): |
641 continue | 609 continue |
642 | 610 |
643 try: | 611 try: |
644 index_type.getattr('__index__') | 612 index_type.getattr('__index__') |
645 return | 613 return |
646 except astroid.NotFoundError: | 614 except astroid.NotFoundError: |
647 pass | 615 pass |
648 | 616 |
649 # Anything else is an error | 617 # Anything else is an error |
650 self.add_message('invalid-slice-index', node=node) | 618 self.add_message('invalid-slice-index', node=node) |
651 | 619 |
652 def register(linter): | 620 def register(linter): |
653 """required method to auto register this checker """ | 621 """required method to auto register this checker """ |
654 linter.register_checker(TypeChecker(linter)) | 622 linter.register_checker(TypeChecker(linter)) |
OLD | NEW |