| OLD | NEW |
| 1 # Copyright (c) 2003-2013 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 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, 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 | |
| 20 BUILTINS_NAME = builtins.__name__ | |
| 21 import astroid | 19 import astroid |
| 22 from astroid import YES, Instance, unpack_infer, List, Tuple | 20 from astroid import YES, Instance, unpack_infer, List, Tuple |
| 21 from logilab.common.compat import builtins |
| 23 | 22 |
| 24 from pylint.checkers import BaseChecker | 23 from pylint.checkers import BaseChecker |
| 25 from pylint.checkers.utils import ( | 24 from pylint.checkers.utils import ( |
| 26 is_empty, is_raising, | 25 is_empty, |
| 27 check_messages, inherit_from_std_ex, | 26 is_raising, |
| 28 EXCEPTIONS_MODULE, has_known_bases) | 27 check_messages, |
| 28 inherit_from_std_ex, |
| 29 EXCEPTIONS_MODULE, |
| 30 has_known_bases, |
| 31 safe_infer) |
| 29 from pylint.interfaces import IAstroidChecker, INFERENCE, INFERENCE_FAILURE | 32 from pylint.interfaces import IAstroidChecker, INFERENCE, INFERENCE_FAILURE |
| 30 | 33 |
| 34 |
| 31 def _annotated_unpack_infer(stmt, context=None): | 35 def _annotated_unpack_infer(stmt, context=None): |
| 32 """ | 36 """ |
| 33 Recursively generate nodes inferred by the given statement. | 37 Recursively generate nodes inferred by the given statement. |
| 34 If the inferred value is a list or a tuple, recurse on the elements. | 38 If the inferred value is a list or a tuple, recurse on the elements. |
| 35 Returns an iterator which yields tuples in the format | 39 Returns an iterator which yields tuples in the format |
| 36 ('original node', 'infered node'). | 40 ('original node', 'infered node'). |
| 37 """ | 41 """ |
| 38 # TODO: the same code as unpack_infer, except for the annotated | |
| 39 # return. We need this type of annotation only here and | |
| 40 # there is no point in complicating the API for unpack_infer. | |
| 41 # If the need arises, this behaviour can be promoted to unpack_infer | |
| 42 # as well. | |
| 43 if isinstance(stmt, (List, Tuple)): | 42 if isinstance(stmt, (List, Tuple)): |
| 44 for elt in stmt.elts: | 43 for elt in stmt.elts: |
| 45 for infered_elt in unpack_infer(elt, context): | 44 inferred = safe_infer(elt) |
| 46 yield elt, infered_elt | 45 if inferred and inferred is not YES: |
| 46 yield elt, inferred |
| 47 return | 47 return |
| 48 # if infered is a final node, return it and stop | |
| 49 infered = next(stmt.infer(context)) | |
| 50 if infered is stmt: | |
| 51 yield stmt, infered | |
| 52 return | |
| 53 # else, infer recursivly, except YES object that should be returned as is | |
| 54 for infered in stmt.infer(context): | 48 for infered in stmt.infer(context): |
| 55 if infered is YES: | 49 if infered is YES: |
| 56 yield stmt, infered | 50 continue |
| 57 else: | 51 yield stmt, infered |
| 58 for inf_inf in unpack_infer(infered, context): | |
| 59 yield stmt, inf_inf | |
| 60 | 52 |
| 61 | 53 |
| 62 PY3K = sys.version_info >= (3, 0) | 54 PY3K = sys.version_info >= (3, 0) |
| 63 OVERGENERAL_EXCEPTIONS = ('Exception',) | 55 OVERGENERAL_EXCEPTIONS = ('Exception',) |
| 64 | 56 BUILTINS_NAME = builtins.__name__ |
| 65 MSGS = { | 57 MSGS = { |
| 66 'E0701': ('Bad except clauses order (%s)', | 58 'E0701': ('Bad except clauses order (%s)', |
| 67 'bad-except-order', | 59 'bad-except-order', |
| 68 'Used when except clauses are not in the correct order (from the ' | 60 'Used when except clauses are not in the correct order (from the ' |
| 69 'more specific to the more generic). If you don\'t fix the order,
' | 61 'more specific to the more generic). If you don\'t fix the order,
' |
| 70 'some exceptions may not be catched by the most specific handler.'
), | 62 'some exceptions may not be catched by the most specific handler.'
), |
| 71 'E0702': ('Raising %s while only classes or instances are allowed', | 63 'E0702': ('Raising %s while only classes or instances are allowed', |
| 72 'raising-bad-type', | 64 'raising-bad-type', |
| 73 'Used when something which is neither a class, an instance or a \ | 65 'Used when something which is neither a class, an instance or a \ |
| 74 string is raised (i.e. a `TypeError` will be raised).'), | 66 string is raised (i.e. a `TypeError` will be raised).'), |
| (...skipping 63 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 138 | 130 |
| 139 @check_messages('nonstandard-exception', | 131 @check_messages('nonstandard-exception', |
| 140 'raising-bad-type', 'raising-non-exception', | 132 'raising-bad-type', 'raising-non-exception', |
| 141 'notimplemented-raised', 'bad-exception-context') | 133 'notimplemented-raised', 'bad-exception-context') |
| 142 def visit_raise(self, node): | 134 def visit_raise(self, node): |
| 143 """visit raise possibly inferring value""" | 135 """visit raise possibly inferring value""" |
| 144 # ignore empty raise | 136 # ignore empty raise |
| 145 if node.exc is None: | 137 if node.exc is None: |
| 146 return | 138 return |
| 147 if PY3K and node.cause: | 139 if PY3K and node.cause: |
| 148 try: | 140 self._check_bad_exception_context(node) |
| 149 cause = next(node.cause.infer()) | 141 |
| 150 except astroid.InferenceError: | |
| 151 pass | |
| 152 else: | |
| 153 if cause is YES: | |
| 154 return | |
| 155 if isinstance(cause, astroid.Const): | |
| 156 if cause.value is not None: | |
| 157 self.add_message('bad-exception-context', | |
| 158 node=node) | |
| 159 elif (not isinstance(cause, astroid.Class) and | |
| 160 not inherit_from_std_ex(cause)): | |
| 161 self.add_message('bad-exception-context', | |
| 162 node=node) | |
| 163 expr = node.exc | 142 expr = node.exc |
| 164 if self._check_raise_value(node, expr): | 143 if self._check_raise_value(node, expr): |
| 165 return | 144 return |
| 166 else: | 145 else: |
| 167 try: | 146 try: |
| 168 value = next(unpack_infer(expr)) | 147 value = next(unpack_infer(expr)) |
| 169 except astroid.InferenceError: | 148 except astroid.InferenceError: |
| 170 return | 149 return |
| 171 self._check_raise_value(node, value) | 150 self._check_raise_value(node, value) |
| 172 | 151 |
| 152 def _check_bad_exception_context(self, node): |
| 153 """Verify that the exception context is properly set. |
| 154 |
| 155 An exception context can be only `None` or an exception. |
| 156 """ |
| 157 cause = safe_infer(node.cause) |
| 158 if cause in (YES, None): |
| 159 return |
| 160 if isinstance(cause, astroid.Const): |
| 161 if cause.value is not None: |
| 162 self.add_message('bad-exception-context', |
| 163 node=node) |
| 164 elif (not isinstance(cause, astroid.Class) and |
| 165 not inherit_from_std_ex(cause)): |
| 166 self.add_message('bad-exception-context', |
| 167 node=node) |
| 168 |
| 173 def _check_raise_value(self, node, expr): | 169 def _check_raise_value(self, node, expr): |
| 174 """check for bad values, string exception and class inheritance | 170 """check for bad values, string exception and class inheritance |
| 175 """ | 171 """ |
| 176 value_found = True | 172 value_found = True |
| 177 if isinstance(expr, astroid.Const): | 173 if isinstance(expr, astroid.Const): |
| 178 value = expr.value | 174 value = expr.value |
| 179 if isinstance(value, str): | 175 if not isinstance(value, str): |
| 180 # raising-string will be emitted from python3 porting checker. | 176 # raising-string will be emitted from python3 porting checker. |
| 181 pass | |
| 182 else: | |
| 183 self.add_message('raising-bad-type', node=node, | 177 self.add_message('raising-bad-type', node=node, |
| 184 args=value.__class__.__name__) | 178 args=value.__class__.__name__) |
| 185 elif (isinstance(expr, astroid.Name) and \ | 179 elif ((isinstance(expr, astroid.Name) and |
| 186 expr.name in ('None', 'True', 'False')) or \ | 180 expr.name in ('None', 'True', 'False')) or |
| 187 isinstance(expr, (astroid.List, astroid.Dict, astroid.Tuple, | 181 isinstance(expr, (astroid.List, astroid.Dict, astroid.Tuple, |
| 188 astroid.Module, astroid.Function)): | 182 astroid.Module, astroid.Function))): |
| 189 self.add_message('raising-bad-type', node=node, args=expr.name) | 183 emit = True |
| 184 if not PY3K and isinstance(expr, astroid.Tuple): |
| 185 # On Python 2, using the following is not an error: |
| 186 # raise (ZeroDivisionError, None) |
| 187 # raise (ZeroDivisionError, ) |
| 188 # What's left to do is to check that the first |
| 189 # argument is indeed an exception. |
| 190 # Verifying the other arguments is not |
| 191 # the scope of this check. |
| 192 first = expr.elts[0] |
| 193 inferred = safe_infer(first) |
| 194 if isinstance(inferred, Instance): |
| 195 # pylint: disable=protected-access |
| 196 inferred = inferred._proxied |
| 197 if (inferred is YES or |
| 198 isinstance(inferred, astroid.Class) |
| 199 and inherit_from_std_ex(inferred)): |
| 200 emit = False |
| 201 if emit: |
| 202 self.add_message('raising-bad-type', |
| 203 node=node, |
| 204 args=expr.name) |
| 190 elif ((isinstance(expr, astroid.Name) and expr.name == 'NotImplemented') | 205 elif ((isinstance(expr, astroid.Name) and expr.name == 'NotImplemented') |
| 191 or (isinstance(expr, astroid.CallFunc) and | 206 or (isinstance(expr, astroid.CallFunc) and |
| 192 isinstance(expr.func, astroid.Name) and | 207 isinstance(expr.func, astroid.Name) and |
| 193 expr.func.name == 'NotImplemented')): | 208 expr.func.name == 'NotImplemented')): |
| 194 self.add_message('notimplemented-raised', node=node) | 209 self.add_message('notimplemented-raised', node=node) |
| 195 elif isinstance(expr, (Instance, astroid.Class)): | 210 elif isinstance(expr, (Instance, astroid.Class)): |
| 196 if isinstance(expr, Instance): | 211 if isinstance(expr, Instance): |
| 212 # pylint: disable=protected-access |
| 197 expr = expr._proxied | 213 expr = expr._proxied |
| 198 if (isinstance(expr, astroid.Class) and | 214 if (isinstance(expr, astroid.Class) and |
| 199 not inherit_from_std_ex(expr) and | 215 not inherit_from_std_ex(expr)): |
| 200 expr.root().name != BUILTINS_NAME): | |
| 201 if expr.newstyle: | 216 if expr.newstyle: |
| 202 self.add_message('raising-non-exception', node=node) | 217 self.add_message('raising-non-exception', node=node) |
| 203 else: | 218 else: |
| 219 if has_known_bases(expr): |
| 220 confidence = INFERENCE |
| 221 else: |
| 222 confidence = INFERENCE_FAILURE |
| 204 self.add_message( | 223 self.add_message( |
| 205 'nonstandard-exception', node=node, | 224 'nonstandard-exception', node=node, |
| 206 confidence=INFERENCE if has_known_bases(expr) else INFER
ENCE_FAILURE) | 225 confidence=confidence) |
| 207 else: | 226 else: |
| 208 value_found = False | 227 value_found = False |
| 209 else: | 228 else: |
| 210 value_found = False | 229 value_found = False |
| 211 return value_found | 230 return value_found |
| 212 | 231 |
| 232 def _check_catching_non_exception(self, handler, exc, part): |
| 233 if isinstance(exc, astroid.Tuple): |
| 234 # Check if it is a tuple of exceptions. |
| 235 inferred = [safe_infer(elt) for elt in exc.elts] |
| 236 if any(node is astroid.YES for node in inferred): |
| 237 # Don't emit if we don't know every component. |
| 238 return |
| 239 if all(node and inherit_from_std_ex(node) |
| 240 for node in inferred): |
| 241 return |
| 242 |
| 243 if not isinstance(exc, astroid.Class): |
| 244 # Don't emit the warning if the infered stmt |
| 245 # is None, but the exception handler is something else, |
| 246 # maybe it was redefined. |
| 247 if (isinstance(exc, astroid.Const) and |
| 248 exc.value is None): |
| 249 if ((isinstance(handler.type, astroid.Const) and |
| 250 handler.type.value is None) or |
| 251 handler.type.parent_of(exc)): |
| 252 # If the exception handler catches None or |
| 253 # the exception component, which is None, is |
| 254 # defined by the entire exception handler, then |
| 255 # emit a warning. |
| 256 self.add_message('catching-non-exception', |
| 257 node=handler.type, |
| 258 args=(part.as_string(), )) |
| 259 else: |
| 260 self.add_message('catching-non-exception', |
| 261 node=handler.type, |
| 262 args=(part.as_string(), )) |
| 263 return |
| 264 if (not inherit_from_std_ex(exc) and |
| 265 exc.root().name != BUILTINS_NAME): |
| 266 if has_known_bases(exc): |
| 267 self.add_message('catching-non-exception', |
| 268 node=handler.type, |
| 269 args=(exc.name, )) |
| 270 |
| 213 @check_messages('bare-except', 'broad-except', 'pointless-except', | 271 @check_messages('bare-except', 'broad-except', 'pointless-except', |
| 214 'binary-op-exception', 'bad-except-order', | 272 'binary-op-exception', 'bad-except-order', |
| 215 'catching-non-exception') | 273 'catching-non-exception') |
| 216 def visit_tryexcept(self, node): | 274 def visit_tryexcept(self, node): |
| 217 """check for empty except""" | 275 """check for empty except""" |
| 218 exceptions_classes = [] | 276 exceptions_classes = [] |
| 219 nb_handlers = len(node.handlers) | 277 nb_handlers = len(node.handlers) |
| 220 for index, handler in enumerate(node.handlers): | 278 for index, handler in enumerate(node.handlers): |
| 221 # single except doing nothing but "pass" without else clause | 279 # single except doing nothing but "pass" without else clause |
| 222 if is_empty(handler.body) and not node.orelse: | 280 if is_empty(handler.body) and not node.orelse: |
| (...skipping 12 matching lines...) Expand all Loading... |
| 235 self.add_message('binary-op-exception', | 293 self.add_message('binary-op-exception', |
| 236 node=handler, args=handler.type.op) | 294 node=handler, args=handler.type.op) |
| 237 else: | 295 else: |
| 238 try: | 296 try: |
| 239 excs = list(_annotated_unpack_infer(handler.type)) | 297 excs = list(_annotated_unpack_infer(handler.type)) |
| 240 except astroid.InferenceError: | 298 except astroid.InferenceError: |
| 241 continue | 299 continue |
| 242 for part, exc in excs: | 300 for part, exc in excs: |
| 243 if exc is YES: | 301 if exc is YES: |
| 244 continue | 302 continue |
| 245 if isinstance(exc, astroid.Instance) and inherit_from_std_ex
(exc): | 303 if (isinstance(exc, astroid.Instance) |
| 304 and inherit_from_std_ex(exc)): |
| 305 # pylint: disable=protected-access |
| 246 exc = exc._proxied | 306 exc = exc._proxied |
| 307 |
| 308 self._check_catching_non_exception(handler, exc, part) |
| 309 |
| 247 if not isinstance(exc, astroid.Class): | 310 if not isinstance(exc, astroid.Class): |
| 248 # Don't emit the warning if the infered stmt | |
| 249 # is None, but the exception handler is something else, | |
| 250 # maybe it was redefined. | |
| 251 if (isinstance(exc, astroid.Const) and | |
| 252 exc.value is None): | |
| 253 if ((isinstance(handler.type, astroid.Const) and | |
| 254 handler.type.value is None) or | |
| 255 handler.type.parent_of(exc)): | |
| 256 # If the exception handler catches None or | |
| 257 # the exception component, which is None, is | |
| 258 # defined by the entire exception handler, then | |
| 259 # emit a warning. | |
| 260 self.add_message('catching-non-exception', | |
| 261 node=handler.type, | |
| 262 args=(part.as_string(), )) | |
| 263 else: | |
| 264 self.add_message('catching-non-exception', | |
| 265 node=handler.type, | |
| 266 args=(part.as_string(), )) | |
| 267 continue | 311 continue |
| 268 | 312 |
| 269 exc_ancestors = [anc for anc in exc.ancestors() | 313 exc_ancestors = [anc for anc in exc.ancestors() |
| 270 if isinstance(anc, astroid.Class)] | 314 if isinstance(anc, astroid.Class)] |
| 271 for previous_exc in exceptions_classes: | 315 for previous_exc in exceptions_classes: |
| 272 if previous_exc in exc_ancestors: | 316 if previous_exc in exc_ancestors: |
| 273 msg = '%s is an ancestor class of %s' % ( | 317 msg = '%s is an ancestor class of %s' % ( |
| 274 previous_exc.name, exc.name) | 318 previous_exc.name, exc.name) |
| 275 self.add_message('bad-except-order', | 319 self.add_message('bad-except-order', |
| 276 node=handler.type, args=msg) | 320 node=handler.type, args=msg) |
| 277 if (exc.name in self.config.overgeneral_exceptions | 321 if (exc.name in self.config.overgeneral_exceptions |
| 278 and exc.root().name == EXCEPTIONS_MODULE | 322 and exc.root().name == EXCEPTIONS_MODULE |
| 279 and not is_raising(handler.body)): | 323 and not is_raising(handler.body)): |
| 280 self.add_message('broad-except', | 324 self.add_message('broad-except', |
| 281 args=exc.name, node=handler.type) | 325 args=exc.name, node=handler.type) |
| 282 | 326 |
| 283 if (not inherit_from_std_ex(exc) and | |
| 284 exc.root().name != BUILTINS_NAME): | |
| 285 if has_known_bases(exc): | |
| 286 self.add_message('catching-non-exception', | |
| 287 node=handler.type, | |
| 288 args=(exc.name, )) | |
| 289 | |
| 290 exceptions_classes += [exc for _, exc in excs] | 327 exceptions_classes += [exc for _, exc in excs] |
| 291 | 328 |
| 292 | 329 |
| 293 def register(linter): | 330 def register(linter): |
| 294 """required method to auto register this checker""" | 331 """required method to auto register this checker""" |
| 295 linter.register_checker(ExceptionsChecker(linter)) | 332 linter.register_checker(ExceptionsChecker(linter)) |
| OLD | NEW |