Development of an internal social media platform with personalised dashboards for students
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

exceptions.py 17KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416
  1. # -*- coding: utf-8 -*-
  2. # Copyright (c) 2006-2011, 2013-2014 LOGILAB S.A. (Paris, FRANCE) <contact@logilab.fr>
  3. # Copyright (c) 2011-2014 Google, Inc.
  4. # Copyright (c) 2012 Tim Hatch <tim@timhatch.com>
  5. # Copyright (c) 2013-2017 Claudiu Popa <pcmanticore@gmail.com>
  6. # Copyright (c) 2014 Brett Cannon <brett@python.org>
  7. # Copyright (c) 2014 Arun Persaud <arun@nubati.net>
  8. # Copyright (c) 2015 Rene Zhang <rz99@cornell.edu>
  9. # Copyright (c) 2015 Florian Bruhin <me@the-compiler.org>
  10. # Copyright (c) 2015 Steven Myint <hg@stevenmyint.com>
  11. # Copyright (c) 2015 Ionel Cristian Maries <contact@ionelmc.ro>
  12. # Copyright (c) 2016 Erik <erik.eriksson@yahoo.com>
  13. # Copyright (c) 2016 Jakub Wilk <jwilk@jwilk.net>
  14. # Copyright (c) 2017 Łukasz Rogalski <rogalski.91@gmail.com>
  15. # Copyright (c) 2017 Martin von Gagern <gagern@google.com>
  16. # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html
  17. # For details: https://github.com/PyCQA/pylint/blob/master/COPYING
  18. """Checks for various exception related errors."""
  19. import inspect
  20. import sys
  21. import six
  22. from six.moves import builtins
  23. import astroid
  24. from pylint import checkers
  25. from pylint.checkers import utils
  26. from pylint import interfaces
  27. def _builtin_exceptions():
  28. def predicate(obj):
  29. return isinstance(obj, type) and issubclass(obj, BaseException)
  30. members = inspect.getmembers(six.moves.builtins, predicate)
  31. return {exc.__name__ for (_, exc) in members}
  32. def _annotated_unpack_infer(stmt, context=None):
  33. """
  34. Recursively generate nodes inferred by the given statement.
  35. If the inferred value is a list or a tuple, recurse on the elements.
  36. Returns an iterator which yields tuples in the format
  37. ('original node', 'infered node').
  38. """
  39. if isinstance(stmt, (astroid.List, astroid.Tuple)):
  40. for elt in stmt.elts:
  41. inferred = utils.safe_infer(elt)
  42. if inferred and inferred is not astroid.YES:
  43. yield elt, inferred
  44. return
  45. for infered in stmt.infer(context):
  46. if infered is astroid.YES:
  47. continue
  48. yield stmt, infered
  49. PY3K = sys.version_info >= (3, 0)
  50. OVERGENERAL_EXCEPTIONS = ('Exception',)
  51. BUILTINS_NAME = builtins.__name__
  52. MSGS = {
  53. 'E0701': ('Bad except clauses order (%s)',
  54. 'bad-except-order',
  55. 'Used when except clauses are not in the correct order (from the '
  56. 'more specific to the more generic). If you don\'t fix the order, '
  57. 'some exceptions may not be caught by the most specific handler.'),
  58. 'E0702': ('Raising %s while only classes or instances are allowed',
  59. 'raising-bad-type',
  60. 'Used when something which is neither a class, an instance or a \
  61. string is raised (i.e. a `TypeError` will be raised).'),
  62. 'E0703': ('Exception context set to something which is not an '
  63. 'exception, nor None',
  64. 'bad-exception-context',
  65. 'Used when using the syntax "raise ... from ...", '
  66. 'where the exception context is not an exception, '
  67. 'nor None.',
  68. {'minversion': (3, 0)}),
  69. 'E0704': ('The raise statement is not inside an except clause',
  70. 'misplaced-bare-raise',
  71. 'Used when a bare raise is not used inside an except clause. '
  72. 'This generates an error, since there are no active exceptions '
  73. 'to be reraised. An exception to this rule is represented by '
  74. 'a bare raise inside a finally clause, which might work, as long '
  75. 'as an exception is raised inside the try block, but it is '
  76. 'nevertheless a code smell that must not be relied upon.'),
  77. 'E0710': ('Raising a new style class which doesn\'t inherit from BaseException',
  78. 'raising-non-exception',
  79. 'Used when a new style class which doesn\'t inherit from \
  80. BaseException is raised.'),
  81. 'E0711': ('NotImplemented raised - should raise NotImplementedError',
  82. 'notimplemented-raised',
  83. 'Used when NotImplemented is raised instead of \
  84. NotImplementedError'),
  85. 'E0712': ('Catching an exception which doesn\'t inherit from Exception: %s',
  86. 'catching-non-exception',
  87. 'Used when a class which doesn\'t inherit from \
  88. Exception is used as an exception in an except clause.'),
  89. 'W0702': ('No exception type(s) specified',
  90. 'bare-except',
  91. 'Used when an except clause doesn\'t specify exceptions type to \
  92. catch.'),
  93. 'W0703': ('Catching too general exception %s',
  94. 'broad-except',
  95. 'Used when an except catches a too general exception, \
  96. possibly burying unrelated errors.'),
  97. 'W0705': ('Catching previously caught exception type %s',
  98. 'duplicate-except',
  99. 'Used when an except catches a type that was already caught by '
  100. 'a previous handler.'),
  101. 'W0710': ('Exception doesn\'t inherit from standard "Exception" class',
  102. 'nonstandard-exception',
  103. 'Used when a custom exception class is raised but doesn\'t \
  104. inherit from the builtin "Exception" class.',
  105. {'maxversion': (3, 0)}),
  106. 'W0711': ('Exception to catch is the result of a binary "%s" operation',
  107. 'binary-op-exception',
  108. 'Used when the exception to catch is of the form \
  109. "except A or B:". If intending to catch multiple, \
  110. rewrite as "except (A, B):"'),
  111. 'W0715': ('Exception arguments suggest string formatting might be intended',
  112. 'raising-format-tuple',
  113. 'Used when passing multiple arguments to an exception \
  114. constructor, the first of them a string literal containing what \
  115. appears to be placeholders intended for formatting'),
  116. }
  117. class BaseVisitor(object):
  118. """Base class for visitors defined in this module."""
  119. def __init__(self, checker, node):
  120. self._checker = checker
  121. self._node = node
  122. def visit(self, node):
  123. name = node.__class__.__name__.lower()
  124. dispatch_meth = getattr(self, 'visit_' + name, None)
  125. if dispatch_meth:
  126. dispatch_meth(node)
  127. else:
  128. self.visit_default(node)
  129. def visit_default(self, node): # pylint: disable=unused-argument
  130. """Default implementation for all the nodes."""
  131. class ExceptionRaiseRefVisitor(BaseVisitor):
  132. """Visit references (anything that is not an AST leaf)."""
  133. def visit_name(self, name):
  134. if name.name == 'NotImplemented':
  135. self._checker.add_message(
  136. 'notimplemented-raised',
  137. node=self._node)
  138. def visit_call(self, call):
  139. if isinstance(call.func, astroid.Name):
  140. self.visit_name(call.func)
  141. if (len(call.args) > 1 and
  142. isinstance(call.args[0], astroid.Const) and
  143. isinstance(call.args[0].value, six.string_types)):
  144. msg = call.args[0].value
  145. if ('%' in msg or
  146. ('{' in msg and '}' in msg)):
  147. self._checker.add_message(
  148. 'raising-format-tuple',
  149. node=self._node)
  150. class ExceptionRaiseLeafVisitor(BaseVisitor):
  151. """Visitor for handling leaf kinds of a raise value."""
  152. def visit_const(self, const):
  153. if not isinstance(const.value, str):
  154. # raising-string will be emitted from python3 porting checker.
  155. self._checker.add_message('raising-bad-type', node=self._node,
  156. args=const.value.__class__.__name__)
  157. def visit_instance(self, instance):
  158. # pylint: disable=protected-access
  159. cls = instance._proxied
  160. self.visit_classdef(cls)
  161. # Exception instances have a particular class type
  162. visit_exceptioninstance = visit_instance
  163. def visit_classdef(self, cls):
  164. if (not utils.inherit_from_std_ex(cls) and
  165. utils.has_known_bases(cls)):
  166. if cls.newstyle:
  167. self._checker.add_message('raising-non-exception', node=self._node)
  168. else:
  169. self._checker.add_message('nonstandard-exception', node=self._node)
  170. def visit_tuple(self, tuple_node):
  171. if PY3K or not tuple_node.elts:
  172. self._checker.add_message('raising-bad-type',
  173. node=self._node,
  174. args='tuple')
  175. return
  176. # On Python 2, using the following is not an error:
  177. # raise (ZeroDivisionError, None)
  178. # raise (ZeroDivisionError, )
  179. # What's left to do is to check that the first
  180. # argument is indeed an exception. Verifying the other arguments
  181. # is not the scope of this check.
  182. first = tuple_node.elts[0]
  183. inferred = utils.safe_infer(first)
  184. if not inferred or inferred is astroid.Uninferable:
  185. return
  186. if (isinstance(inferred, astroid.Instance)
  187. and inferred.__class__.__name__ != 'Instance'):
  188. # TODO: explain why
  189. self.visit_default(tuple_node)
  190. else:
  191. self.visit(inferred)
  192. def visit_default(self, node):
  193. name = getattr(node, 'name', node.__class__.__name__)
  194. self._checker.add_message('raising-bad-type',
  195. node=self._node,
  196. args=name)
  197. class ExceptionsChecker(checkers.BaseChecker):
  198. """Exception related checks."""
  199. __implements__ = interfaces.IAstroidChecker
  200. name = 'exceptions'
  201. msgs = MSGS
  202. priority = -4
  203. options = (('overgeneral-exceptions',
  204. {'default' : OVERGENERAL_EXCEPTIONS,
  205. 'type' : 'csv', 'metavar' : '<comma-separated class names>',
  206. 'help' : 'Exceptions that will emit a warning '
  207. 'when being caught. Defaults to "%s"' % (
  208. ', '.join(OVERGENERAL_EXCEPTIONS),)}
  209. ),
  210. )
  211. def open(self):
  212. self._builtin_exceptions = _builtin_exceptions()
  213. super(ExceptionsChecker, self).open()
  214. @utils.check_messages('nonstandard-exception', 'misplaced-bare-raise',
  215. 'raising-bad-type', 'raising-non-exception',
  216. 'notimplemented-raised', 'bad-exception-context',
  217. 'raising-format-tuple')
  218. def visit_raise(self, node):
  219. if node.exc is None:
  220. self._check_misplaced_bare_raise(node)
  221. return
  222. if PY3K and node.cause:
  223. self._check_bad_exception_context(node)
  224. expr = node.exc
  225. try:
  226. inferred_value = next(expr.infer())
  227. except astroid.InferenceError:
  228. inferred_value = None
  229. ExceptionRaiseRefVisitor(self, node).visit(expr)
  230. if inferred_value:
  231. ExceptionRaiseLeafVisitor(self, node).visit(inferred_value)
  232. def _check_misplaced_bare_raise(self, node):
  233. # Filter out if it's present in __exit__.
  234. scope = node.scope()
  235. if (isinstance(scope, astroid.FunctionDef)
  236. and scope.is_method()
  237. and scope.name == '__exit__'):
  238. return
  239. current = node
  240. # Stop when a new scope is generated or when the raise
  241. # statement is found inside a TryFinally.
  242. ignores = (astroid.ExceptHandler, astroid.FunctionDef, astroid.TryFinally)
  243. while current and not isinstance(current.parent, ignores):
  244. current = current.parent
  245. expected = (astroid.ExceptHandler,)
  246. if not current or not isinstance(current.parent, expected):
  247. self.add_message('misplaced-bare-raise', node=node)
  248. def _check_bad_exception_context(self, node):
  249. """Verify that the exception context is properly set.
  250. An exception context can be only `None` or an exception.
  251. """
  252. cause = utils.safe_infer(node.cause)
  253. if cause in (astroid.YES, None):
  254. return
  255. if isinstance(cause, astroid.Const):
  256. if cause.value is not None:
  257. self.add_message('bad-exception-context',
  258. node=node)
  259. elif (not isinstance(cause, astroid.ClassDef) and
  260. not utils.inherit_from_std_ex(cause)):
  261. self.add_message('bad-exception-context',
  262. node=node)
  263. def _check_catching_non_exception(self, handler, exc, part):
  264. if isinstance(exc, astroid.Tuple):
  265. # Check if it is a tuple of exceptions.
  266. inferred = [utils.safe_infer(elt) for elt in exc.elts]
  267. if any(node is astroid.YES for node in inferred):
  268. # Don't emit if we don't know every component.
  269. return
  270. if all(node and (utils.inherit_from_std_ex(node) or
  271. not utils.has_known_bases(node))
  272. for node in inferred):
  273. return
  274. if not isinstance(exc, astroid.ClassDef):
  275. # Don't emit the warning if the infered stmt
  276. # is None, but the exception handler is something else,
  277. # maybe it was redefined.
  278. if (isinstance(exc, astroid.Const) and
  279. exc.value is None):
  280. if ((isinstance(handler.type, astroid.Const) and
  281. handler.type.value is None) or
  282. handler.type.parent_of(exc)):
  283. # If the exception handler catches None or
  284. # the exception component, which is None, is
  285. # defined by the entire exception handler, then
  286. # emit a warning.
  287. self.add_message('catching-non-exception',
  288. node=handler.type,
  289. args=(part.as_string(), ))
  290. else:
  291. self.add_message('catching-non-exception',
  292. node=handler.type,
  293. args=(part.as_string(), ))
  294. return
  295. if (not utils.inherit_from_std_ex(exc) and
  296. exc.name not in self._builtin_exceptions):
  297. if utils.has_known_bases(exc):
  298. self.add_message('catching-non-exception',
  299. node=handler.type,
  300. args=(exc.name, ))
  301. @utils.check_messages('bare-except', 'broad-except',
  302. 'binary-op-exception', 'bad-except-order',
  303. 'catching-non-exception', 'duplicate-except')
  304. def visit_tryexcept(self, node):
  305. """check for empty except"""
  306. exceptions_classes = []
  307. nb_handlers = len(node.handlers)
  308. for index, handler in enumerate(node.handlers):
  309. if handler.type is None:
  310. if not utils.is_raising(handler.body):
  311. self.add_message('bare-except', node=handler)
  312. # check if a "except:" is followed by some other
  313. # except
  314. if index < (nb_handlers - 1):
  315. msg = 'empty except clause should always appear last'
  316. self.add_message('bad-except-order', node=node, args=msg)
  317. elif isinstance(handler.type, astroid.BoolOp):
  318. self.add_message('binary-op-exception',
  319. node=handler, args=handler.type.op)
  320. else:
  321. try:
  322. excs = list(_annotated_unpack_infer(handler.type))
  323. except astroid.InferenceError:
  324. continue
  325. for part, exc in excs:
  326. if exc is astroid.YES:
  327. continue
  328. if (isinstance(exc, astroid.Instance)
  329. and utils.inherit_from_std_ex(exc)):
  330. # pylint: disable=protected-access
  331. exc = exc._proxied
  332. self._check_catching_non_exception(handler, exc, part)
  333. if not isinstance(exc, astroid.ClassDef):
  334. continue
  335. exc_ancestors = [anc for anc in exc.ancestors()
  336. if isinstance(anc, astroid.ClassDef)]
  337. for previous_exc in exceptions_classes:
  338. if previous_exc in exc_ancestors:
  339. msg = '%s is an ancestor class of %s' % (
  340. previous_exc.name, exc.name)
  341. self.add_message('bad-except-order',
  342. node=handler.type, args=msg)
  343. if (exc.name in self.config.overgeneral_exceptions
  344. and exc.root().name == utils.EXCEPTIONS_MODULE
  345. and not utils.is_raising(handler.body)):
  346. self.add_message('broad-except',
  347. args=exc.name, node=handler.type)
  348. if exc in exceptions_classes:
  349. self.add_message('duplicate-except',
  350. args=exc.name, node=handler.type)
  351. exceptions_classes += [exc for _, exc in excs]
  352. def register(linter):
  353. """required method to auto register this checker"""
  354. linter.register_checker(ExceptionsChecker(linter))