| OLD | NEW |
| 1 # Copyright (c) 2003-2007 LOGILAB S.A. (Paris, FRANCE). | 1 # Copyright (c) 2003-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 # This program is free software; you can redistribute it and/or modify it under | 3 # This program is free software; you can redistribute it and/or modify it under |
| 4 # the terms of the GNU General Public License as published by the Free Software | 4 # the terms of the GNU General Public License as published by the Free Software |
| 5 # Foundation; either version 2 of the License, or (at your option) any later | 5 # Foundation; either version 2 of the License, or (at your option) any later |
| 6 # version. | 6 # version. |
| 7 # | 7 # |
| 8 # This program is distributed in the hope that it will be useful, but WITHOUT | 8 # This program is distributed in the hope that it will be useful, but WITHOUT |
| 9 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS | 9 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
| 10 # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. | 10 # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. |
| 11 # | 11 # |
| 12 # You should have received a copy of the GNU General Public License along with | 12 # You should have received a copy of the GNU General Public License along with |
| 13 # this program; if not, write to the Free Software Foundation, Inc., | 13 # this program; if not, write to the Free Software Foundation, Inc., |
| 14 # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. | 14 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
| 15 """exceptions handling (raising, catching, exceptions classes) checker | 15 """exceptions handling (raising, catching, exceptions classes) checker |
| 16 """ | 16 """ |
| 17 import sys | 17 import sys |
| 18 | 18 |
| 19 from logilab.common.compat import builtins | 19 from logilab.common.compat import builtins |
| 20 BUILTINS_NAME = builtins.__name__ | 20 BUILTINS_NAME = builtins.__name__ |
| 21 from logilab import astng | 21 import astroid |
| 22 from logilab.astng import YES, Instance, unpack_infer | 22 from astroid import YES, Instance, unpack_infer |
| 23 | 23 |
| 24 from pylint.checkers import BaseChecker | 24 from pylint.checkers import BaseChecker |
| 25 from pylint.checkers.utils import is_empty, is_raising | 25 from pylint.checkers.utils import is_empty, is_raising, check_messages |
| 26 from pylint.interfaces import IASTNGChecker | 26 from pylint.interfaces import IAstroidChecker |
| 27 | 27 |
| 28 def infer_bases(klass): |
| 29 """ Fully infer the bases of the klass node. |
| 28 | 30 |
| 31 This doesn't use .ancestors(), because we need |
| 32 the non-inferable nodes (YES nodes), |
| 33 which can't be retrieved from .ancestors() |
| 34 """ |
| 35 for base in klass.bases: |
| 36 try: |
| 37 inferit = base.infer().next() |
| 38 except astroid.InferenceError: |
| 39 continue |
| 40 if inferit is YES: |
| 41 yield inferit |
| 42 else: |
| 43 for base in infer_bases(inferit): |
| 44 yield base |
| 45 |
| 46 PY3K = sys.version_info >= (3, 0) |
| 29 OVERGENERAL_EXCEPTIONS = ('Exception',) | 47 OVERGENERAL_EXCEPTIONS = ('Exception',) |
| 30 | 48 |
| 31 MSGS = { | 49 MSGS = { |
| 32 'E0701': ( | 50 'E0701': ('Bad except clauses order (%s)', |
| 33 'Bad except clauses order (%s)', | 51 'bad-except-order', |
| 34 'Used when except clauses are not in the correct order (from the \ | 52 'Used when except clauses are not in the correct order (from the ' |
| 35 more specific to the more generic). If you don\'t fix the order, \ | 53 'more specific to the more generic). If you don\'t fix the order,
' |
| 36 some exceptions may not be catched by the most specific handler.'), | 54 'some exceptions may not be catched by the most specific handler.'
), |
| 37 'E0702': ('Raising %s while only classes, instances or string are allowed', | 55 'E0702': ('Raising %s while only classes, instances or string are allowed', |
| 56 'raising-bad-type', |
| 38 'Used when something which is neither a class, an instance or a \ | 57 'Used when something which is neither a class, an instance or a \ |
| 39 string is raised (i.e. a `TypeError` will be raised).'), | 58 string is raised (i.e. a `TypeError` will be raised).'), |
| 59 'E0703': ('Exception context set to something which is not an ' |
| 60 'exception, nor None', |
| 61 'bad-exception-context', |
| 62 'Used when using the syntax "raise ... from ...", ' |
| 63 'where the exception context is not an exception, ' |
| 64 'nor None.', |
| 65 {'minversion': (3, 0)}), |
| 40 'E0710': ('Raising a new style class which doesn\'t inherit from BaseExcepti
on', | 66 'E0710': ('Raising a new style class which doesn\'t inherit from BaseExcepti
on', |
| 67 'raising-non-exception', |
| 41 'Used when a new style class which doesn\'t inherit from \ | 68 'Used when a new style class which doesn\'t inherit from \ |
| 42 BaseException is raised.'), | 69 BaseException is raised.'), |
| 43 'E0711': ('NotImplemented raised - should raise NotImplementedError', | 70 'E0711': ('NotImplemented raised - should raise NotImplementedError', |
| 71 'notimplemented-raised', |
| 44 'Used when NotImplemented is raised instead of \ | 72 'Used when NotImplemented is raised instead of \ |
| 45 NotImplementedError'), | 73 NotImplementedError'), |
| 46 | 74 'E0712': ('Catching an exception which doesn\'t inherit from BaseException:
%s', |
| 75 'catching-non-exception', |
| 76 'Used when a class which doesn\'t inherit from \ |
| 77 BaseException is used as an exception in an except clause.'), |
| 78 |
| 47 'W0701': ('Raising a string exception', | 79 'W0701': ('Raising a string exception', |
| 80 'raising-string', |
| 48 'Used when a string exception is raised.'), | 81 'Used when a string exception is raised.'), |
| 49 'W0702': ('No exception type(s) specified', | 82 'W0702': ('No exception type(s) specified', |
| 83 'bare-except', |
| 50 'Used when an except clause doesn\'t specify exceptions type to \ | 84 'Used when an except clause doesn\'t specify exceptions type to \ |
| 51 catch.'), | 85 catch.'), |
| 52 'W0703': ('Catching too general exception %s', | 86 'W0703': ('Catching too general exception %s', |
| 87 'broad-except', |
| 53 'Used when an except catches a too general exception, \ | 88 'Used when an except catches a too general exception, \ |
| 54 possibly burying unrelated errors.'), | 89 possibly burying unrelated errors.'), |
| 55 'W0704': ('Except doesn\'t do anything', | 90 'W0704': ('Except doesn\'t do anything', |
| 91 'pointless-except', |
| 56 'Used when an except clause does nothing but "pass" and there is\ | 92 'Used when an except clause does nothing but "pass" and there is\ |
| 57 no "else" clause.'), | 93 no "else" clause.'), |
| 58 'W0710': ('Exception doesn\'t inherit from standard "Exception" class', | 94 'W0710': ('Exception doesn\'t inherit from standard "Exception" class', |
| 95 'nonstandard-exception', |
| 59 'Used when a custom exception class is raised but doesn\'t \ | 96 'Used when a custom exception class is raised but doesn\'t \ |
| 60 inherit from the builtin "Exception" class.'), | 97 inherit from the builtin "Exception" class.', |
| 98 {'maxversion': (3, 0)}), |
| 99 'W0711': ('Exception to catch is the result of a binary "%s" operation', |
| 100 'binary-op-exception', |
| 101 'Used when the exception to catch is of the form \ |
| 102 "except A or B:". If intending to catch multiple, \ |
| 103 rewrite as "except (A, B):"'), |
| 104 'W0712': ('Implicit unpacking of exceptions is not supported in Python 3', |
| 105 'unpacking-in-except', |
| 106 'Python3 will not allow implicit unpacking of exceptions in except
' |
| 107 'clauses. ' |
| 108 'See http://www.python.org/dev/peps/pep-3110/', |
| 109 {'maxversion': (3, 0)}), |
| 110 'W0713': ('Indexing exceptions will not work on Python 3', |
| 111 'indexing-exception', |
| 112 'Indexing exceptions will not work on Python 3. Use ' |
| 113 '`exception.args[index]` instead.', |
| 114 {'maxversion': (3, 0)}), |
| 61 } | 115 } |
| 62 | 116 |
| 63 | 117 |
| 64 if sys.version_info < (3, 0): | 118 if sys.version_info < (3, 0): |
| 65 EXCEPTIONS_MODULE = "exceptions" | 119 EXCEPTIONS_MODULE = "exceptions" |
| 66 else: | 120 else: |
| 67 EXCEPTIONS_MODULE = "builtins" | 121 EXCEPTIONS_MODULE = "builtins" |
| 68 | 122 |
| 69 class ExceptionsChecker(BaseChecker): | 123 class ExceptionsChecker(BaseChecker): |
| 70 """checks for | 124 """checks for |
| 71 * excepts without exception filter | 125 * excepts without exception filter |
| 72 * type of raise argument : string, Exceptions, other values | 126 * type of raise argument : string, Exceptions, other values |
| 73 """ | 127 """ |
| 74 | 128 |
| 75 __implements__ = IASTNGChecker | 129 __implements__ = IAstroidChecker |
| 76 | 130 |
| 77 name = 'exceptions' | 131 name = 'exceptions' |
| 78 msgs = MSGS | 132 msgs = MSGS |
| 79 priority = -4 | 133 priority = -4 |
| 80 options = (('overgeneral-exceptions', | 134 options = (('overgeneral-exceptions', |
| 81 {'default' : OVERGENERAL_EXCEPTIONS, | 135 {'default' : OVERGENERAL_EXCEPTIONS, |
| 82 'type' :'csv', 'metavar' : '<comma-separated class names>', | 136 'type' :'csv', 'metavar' : '<comma-separated class names>', |
| 83 'help' : 'Exceptions that will emit a warning ' | 137 'help' : 'Exceptions that will emit a warning ' |
| 84 'when being caught. Defaults to "%s"' % ( | 138 'when being caught. Defaults to "%s"' % ( |
| 85 ', '.join(OVERGENERAL_EXCEPTIONS),)} | 139 ', '.join(OVERGENERAL_EXCEPTIONS),)} |
| 86 ), | 140 ), |
| 87 ) | 141 ) |
| 88 | 142 |
| 143 @check_messages('raising-string', 'nonstandard-exception', 'raising-bad-type
', |
| 144 'raising-non-exception', 'notimplemented-raised', 'bad-excep
tion-context') |
| 89 def visit_raise(self, node): | 145 def visit_raise(self, node): |
| 90 """visit raise possibly inferring value""" | 146 """visit raise possibly inferring value""" |
| 91 # ignore empty raise | 147 # ignore empty raise |
| 92 if node.exc is None: | 148 if node.exc is None: |
| 93 return | 149 return |
| 150 if PY3K and node.cause: |
| 151 try: |
| 152 cause = node.cause.infer().next() |
| 153 except astroid.InferenceError: |
| 154 pass |
| 155 else: |
| 156 if cause is YES: |
| 157 return |
| 158 if isinstance(cause, astroid.Const): |
| 159 if cause.value is not None: |
| 160 self.add_message('bad-exception-context', |
| 161 node=node) |
| 162 elif (not isinstance(cause, astroid.Class) and |
| 163 not inherit_from_std_ex(cause)): |
| 164 self.add_message('bad-exception-context', |
| 165 node=node) |
| 94 expr = node.exc | 166 expr = node.exc |
| 95 if self._check_raise_value(node, expr): | 167 if self._check_raise_value(node, expr): |
| 96 return | 168 return |
| 97 else: | 169 else: |
| 98 try: | 170 try: |
| 99 value = unpack_infer(expr).next() | 171 value = unpack_infer(expr).next() |
| 100 except astng.InferenceError: | 172 except astroid.InferenceError: |
| 101 return | 173 return |
| 102 self._check_raise_value(node, value) | 174 self._check_raise_value(node, value) |
| 103 | 175 |
| 104 def _check_raise_value(self, node, expr): | 176 def _check_raise_value(self, node, expr): |
| 105 """check for bad values, string exception and class inheritance | 177 """check for bad values, string exception and class inheritance |
| 106 """ | 178 """ |
| 107 value_found = True | 179 value_found = True |
| 108 if isinstance(expr, astng.Const): | 180 if isinstance(expr, astroid.Const): |
| 109 value = expr.value | 181 value = expr.value |
| 110 if isinstance(value, str): | 182 if isinstance(value, str): |
| 111 self.add_message('W0701', node=node) | 183 self.add_message('raising-string', node=node) |
| 112 else: | 184 else: |
| 113 self.add_message('E0702', node=node, | 185 self.add_message('raising-bad-type', node=node, |
| 114 args=value.__class__.__name__) | 186 args=value.__class__.__name__) |
| 115 elif (isinstance(expr, astng.Name) and \ | 187 elif (isinstance(expr, astroid.Name) and \ |
| 116 expr.name in ('None', 'True', 'False')) or \ | 188 expr.name in ('None', 'True', 'False')) or \ |
| 117 isinstance(expr, (astng.List, astng.Dict, astng.Tuple, | 189 isinstance(expr, (astroid.List, astroid.Dict, astroid.Tuple, |
| 118 astng.Module, astng.Function)): | 190 astroid.Module, astroid.Function)): |
| 119 self.add_message('E0702', node=node, args=expr.name) | 191 self.add_message('raising-bad-type', node=node, args=expr.name) |
| 120 elif ( (isinstance(expr, astng.Name) and expr.name == 'NotImplemented') | 192 elif ((isinstance(expr, astroid.Name) and expr.name == 'NotImplemented') |
| 121 or (isinstance(expr, astng.CallFunc) and | 193 or (isinstance(expr, astroid.CallFunc) and |
| 122 isinstance(expr.func, astng.Name) and | 194 isinstance(expr.func, astroid.Name) and |
| 123 expr.func.name == 'NotImplemented') ): | 195 expr.func.name == 'NotImplemented')): |
| 124 self.add_message('E0711', node=node) | 196 self.add_message('notimplemented-raised', node=node) |
| 125 elif isinstance(expr, astng.BinOp) and expr.op == '%': | 197 elif isinstance(expr, astroid.BinOp) and expr.op == '%': |
| 126 self.add_message('W0701', node=node) | 198 self.add_message('raising-string', node=node) |
| 127 elif isinstance(expr, (Instance, astng.Class)): | 199 elif isinstance(expr, (Instance, astroid.Class)): |
| 128 if isinstance(expr, Instance): | 200 if isinstance(expr, Instance): |
| 129 expr = expr._proxied | 201 expr = expr._proxied |
| 130 if (isinstance(expr, astng.Class) and | 202 if (isinstance(expr, astroid.Class) and |
| 131 not inherit_from_std_ex(expr) and | 203 not inherit_from_std_ex(expr) and |
| 132 expr.root().name != BUILTINS_NAME): | 204 expr.root().name != BUILTINS_NAME): |
| 133 if expr.newstyle: | 205 if expr.newstyle: |
| 134 self.add_message('E0710', node=node) | 206 self.add_message('raising-non-exception', node=node) |
| 135 else: | 207 else: |
| 136 self.add_message('W0710', node=node) | 208 self.add_message('nonstandard-exception', node=node) |
| 137 else: | 209 else: |
| 138 value_found = False | 210 value_found = False |
| 139 else: | 211 else: |
| 140 value_found = False | 212 value_found = False |
| 141 return value_found | 213 return value_found |
| 142 | 214 |
| 215 @check_messages('unpacking-in-except') |
| 216 def visit_excepthandler(self, node): |
| 217 """Visit an except handler block and check for exception unpacking.""" |
| 218 if isinstance(node.name, (astroid.Tuple, astroid.List)): |
| 219 self.add_message('unpacking-in-except', node=node) |
| 143 | 220 |
| 221 @check_messages('indexing-exception') |
| 222 def visit_subscript(self, node): |
| 223 """ Look for indexing exceptions. """ |
| 224 try: |
| 225 for infered in node.value.infer(): |
| 226 if not isinstance(infered, astroid.Instance): |
| 227 continue |
| 228 if inherit_from_std_ex(infered): |
| 229 self.add_message('indexing-exception', node=node) |
| 230 except astroid.InferenceError: |
| 231 return |
| 232 |
| 233 @check_messages('bare-except', 'broad-except', 'pointless-except', |
| 234 'binary-op-exception', 'bad-except-order', |
| 235 'catching-non-exception') |
| 144 def visit_tryexcept(self, node): | 236 def visit_tryexcept(self, node): |
| 145 """check for empty except""" | 237 """check for empty except""" |
| 146 exceptions_classes = [] | 238 exceptions_classes = [] |
| 147 nb_handlers = len(node.handlers) | 239 nb_handlers = len(node.handlers) |
| 148 for index, handler in enumerate(node.handlers): | 240 for index, handler in enumerate(node.handlers): |
| 149 # single except doing nothing but "pass" without else clause | 241 # single except doing nothing but "pass" without else clause |
| 150 if nb_handlers == 1 and is_empty(handler.body) and not node.orelse: | 242 if is_empty(handler.body) and not node.orelse: |
| 151 self.add_message('W0704', node=handler.type or handler.body[0]) | 243 self.add_message('pointless-except', node=handler.type or handle
r.body[0]) |
| 152 if handler.type is None: | 244 if handler.type is None: |
| 153 if nb_handlers == 1 and not is_raising(handler.body): | 245 if not is_raising(handler.body): |
| 154 self.add_message('W0702', node=handler) | 246 self.add_message('bare-except', node=handler) |
| 155 # check if a "except:" is followed by some other | 247 # check if a "except:" is followed by some other |
| 156 # except | 248 # except |
| 157 elif index < (nb_handlers - 1): | 249 if index < (nb_handlers - 1): |
| 158 msg = 'empty except clause should always appear last' | 250 msg = 'empty except clause should always appear last' |
| 159 self.add_message('E0701', node=node, args=msg) | 251 self.add_message('bad-except-order', node=node, args=msg) |
| 252 |
| 253 elif isinstance(handler.type, astroid.BoolOp): |
| 254 self.add_message('binary-op-exception', node=handler, args=handl
er.type.op) |
| 160 else: | 255 else: |
| 161 try: | 256 try: |
| 162 excs = list(unpack_infer(handler.type)) | 257 excs = list(unpack_infer(handler.type)) |
| 163 except astng.InferenceError: | 258 except astroid.InferenceError: |
| 164 continue | 259 continue |
| 165 for exc in excs: | 260 for exc in excs: |
| 166 # XXX skip other non class nodes | 261 # XXX skip other non class nodes |
| 167 if exc is YES or not isinstance(exc, astng.Class): | 262 if exc is YES or not isinstance(exc, astroid.Class): |
| 168 continue | 263 continue |
| 169 exc_ancestors = [anc for anc in exc.ancestors() | 264 exc_ancestors = [anc for anc in exc.ancestors() |
| 170 if isinstance(anc, astng.Class)] | 265 if isinstance(anc, astroid.Class)] |
| 171 for previous_exc in exceptions_classes: | 266 for previous_exc in exceptions_classes: |
| 172 if previous_exc in exc_ancestors: | 267 if previous_exc in exc_ancestors: |
| 173 msg = '%s is an ancestor class of %s' % ( | 268 msg = '%s is an ancestor class of %s' % ( |
| 174 previous_exc.name, exc.name) | 269 previous_exc.name, exc.name) |
| 175 self.add_message('E0701', node=handler.type, args=ms
g) | 270 self.add_message('bad-except-order', node=handler.ty
pe, args=msg) |
| 176 if (exc.name in self.config.overgeneral_exceptions | 271 if (exc.name in self.config.overgeneral_exceptions |
| 177 and exc.root().name == EXCEPTIONS_MODULE | 272 and exc.root().name == EXCEPTIONS_MODULE |
| 178 and nb_handlers == 1 and not is_raising(handler.body)): | 273 and not is_raising(handler.body)): |
| 179 self.add_message('W0703', args=exc.name, node=handler.ty
pe) | 274 self.add_message('broad-except', args=exc.name, node=han
dler.type) |
| 275 |
| 276 if (not inherit_from_std_ex(exc) and |
| 277 exc.root().name != BUILTINS_NAME): |
| 278 # try to see if the exception is based on a C based |
| 279 # exception, by infering all the base classes and |
| 280 # looking for inference errors |
| 281 bases = infer_bases(exc) |
| 282 fully_infered = all(inferit is not YES |
| 283 for inferit in bases) |
| 284 if fully_infered: |
| 285 self.add_message('catching-non-exception', |
| 286 node=handler.type, |
| 287 args=(exc.name, )) |
| 288 |
| 180 exceptions_classes += excs | 289 exceptions_classes += excs |
| 181 | 290 |
| 182 | 291 |
| 183 def inherit_from_std_ex(node): | 292 def inherit_from_std_ex(node): |
| 184 """return true if the given class node is subclass of | 293 """return true if the given class node is subclass of |
| 185 exceptions.Exception | 294 exceptions.Exception |
| 186 """ | 295 """ |
| 187 if node.name in ('Exception', 'BaseException') \ | 296 if node.name in ('Exception', 'BaseException') \ |
| 188 and node.root().name == EXCEPTIONS_MODULE: | 297 and node.root().name == EXCEPTIONS_MODULE: |
| 189 return True | 298 return True |
| 190 for parent in node.ancestors(recurs=False): | 299 for parent in node.ancestors(recurs=False): |
| 191 if inherit_from_std_ex(parent): | 300 if inherit_from_std_ex(parent): |
| 192 return True | 301 return True |
| 193 return False | 302 return False |
| 194 | 303 |
| 195 def register(linter): | 304 def register(linter): |
| 196 """required method to auto register this checker""" | 305 """required method to auto register this checker""" |
| 197 linter.register_checker(ExceptionsChecker(linter)) | 306 linter.register_checker(ExceptionsChecker(linter)) |
| OLD | NEW |