OLD | NEW |
(Empty) | |
| 1 # Copyright (c) 2003-2013 LOGILAB S.A. (Paris, FRANCE). |
| 2 # http://www.logilab.fr/ -- mailto:contact@logilab.fr |
| 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 |
| 5 # Foundation; either version 2 of the License, or (at your option) any later |
| 6 # version. |
| 7 # |
| 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 |
| 10 # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. |
| 11 # |
| 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., |
| 14 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
| 15 """exceptions handling (raising, catching, exceptions classes) checker |
| 16 """ |
| 17 import sys |
| 18 |
| 19 import astroid |
| 20 from astroid import YES, Instance, unpack_infer, List, Tuple |
| 21 from logilab.common.compat import builtins |
| 22 |
| 23 from pylint.checkers import BaseChecker |
| 24 from pylint.checkers.utils import ( |
| 25 is_empty, |
| 26 is_raising, |
| 27 check_messages, |
| 28 inherit_from_std_ex, |
| 29 EXCEPTIONS_MODULE, |
| 30 has_known_bases, |
| 31 safe_infer) |
| 32 from pylint.interfaces import IAstroidChecker, INFERENCE, INFERENCE_FAILURE |
| 33 |
| 34 |
| 35 def _annotated_unpack_infer(stmt, context=None): |
| 36 """ |
| 37 Recursively generate nodes inferred by the given statement. |
| 38 If the inferred value is a list or a tuple, recurse on the elements. |
| 39 Returns an iterator which yields tuples in the format |
| 40 ('original node', 'infered node'). |
| 41 """ |
| 42 if isinstance(stmt, (List, Tuple)): |
| 43 for elt in stmt.elts: |
| 44 inferred = safe_infer(elt) |
| 45 if inferred and inferred is not YES: |
| 46 yield elt, inferred |
| 47 return |
| 48 for infered in stmt.infer(context): |
| 49 if infered is YES: |
| 50 continue |
| 51 yield stmt, infered |
| 52 |
| 53 |
| 54 PY3K = sys.version_info >= (3, 0) |
| 55 OVERGENERAL_EXCEPTIONS = ('Exception',) |
| 56 BUILTINS_NAME = builtins.__name__ |
| 57 MSGS = { |
| 58 'E0701': ('Bad except clauses order (%s)', |
| 59 'bad-except-order', |
| 60 'Used when except clauses are not in the correct order (from the ' |
| 61 'more specific to the more generic). If you don\'t fix the order,
' |
| 62 'some exceptions may not be catched by the most specific handler.'
), |
| 63 'E0702': ('Raising %s while only classes or instances are allowed', |
| 64 'raising-bad-type', |
| 65 'Used when something which is neither a class, an instance or a \ |
| 66 string is raised (i.e. a `TypeError` will be raised).'), |
| 67 'E0703': ('Exception context set to something which is not an ' |
| 68 'exception, nor None', |
| 69 'bad-exception-context', |
| 70 'Used when using the syntax "raise ... from ...", ' |
| 71 'where the exception context is not an exception, ' |
| 72 'nor None.', |
| 73 {'minversion': (3, 0)}), |
| 74 'E0710': ('Raising a new style class which doesn\'t inherit from BaseExcepti
on', |
| 75 'raising-non-exception', |
| 76 'Used when a new style class which doesn\'t inherit from \ |
| 77 BaseException is raised.'), |
| 78 'E0711': ('NotImplemented raised - should raise NotImplementedError', |
| 79 'notimplemented-raised', |
| 80 'Used when NotImplemented is raised instead of \ |
| 81 NotImplementedError'), |
| 82 'E0712': ('Catching an exception which doesn\'t inherit from BaseException:
%s', |
| 83 'catching-non-exception', |
| 84 'Used when a class which doesn\'t inherit from \ |
| 85 BaseException is used as an exception in an except clause.'), |
| 86 'W0702': ('No exception type(s) specified', |
| 87 'bare-except', |
| 88 'Used when an except clause doesn\'t specify exceptions type to \ |
| 89 catch.'), |
| 90 'W0703': ('Catching too general exception %s', |
| 91 'broad-except', |
| 92 'Used when an except catches a too general exception, \ |
| 93 possibly burying unrelated errors.'), |
| 94 'W0704': ('Except doesn\'t do anything', |
| 95 'pointless-except', |
| 96 'Used when an except clause does nothing but "pass" and there is\ |
| 97 no "else" clause.'), |
| 98 'W0710': ('Exception doesn\'t inherit from standard "Exception" class', |
| 99 'nonstandard-exception', |
| 100 'Used when a custom exception class is raised but doesn\'t \ |
| 101 inherit from the builtin "Exception" class.', |
| 102 {'maxversion': (3, 0)}), |
| 103 'W0711': ('Exception to catch is the result of a binary "%s" operation', |
| 104 'binary-op-exception', |
| 105 'Used when the exception to catch is of the form \ |
| 106 "except A or B:". If intending to catch multiple, \ |
| 107 rewrite as "except (A, B):"'), |
| 108 } |
| 109 |
| 110 |
| 111 class ExceptionsChecker(BaseChecker): |
| 112 """checks for |
| 113 * excepts without exception filter |
| 114 * type of raise argument : string, Exceptions, other values |
| 115 """ |
| 116 |
| 117 __implements__ = IAstroidChecker |
| 118 |
| 119 name = 'exceptions' |
| 120 msgs = MSGS |
| 121 priority = -4 |
| 122 options = (('overgeneral-exceptions', |
| 123 {'default' : OVERGENERAL_EXCEPTIONS, |
| 124 'type' :'csv', 'metavar' : '<comma-separated class names>', |
| 125 'help' : 'Exceptions that will emit a warning ' |
| 126 'when being caught. Defaults to "%s"' % ( |
| 127 ', '.join(OVERGENERAL_EXCEPTIONS),)} |
| 128 ), |
| 129 ) |
| 130 |
| 131 @check_messages('nonstandard-exception', |
| 132 'raising-bad-type', 'raising-non-exception', |
| 133 'notimplemented-raised', 'bad-exception-context') |
| 134 def visit_raise(self, node): |
| 135 """visit raise possibly inferring value""" |
| 136 # ignore empty raise |
| 137 if node.exc is None: |
| 138 return |
| 139 if PY3K and node.cause: |
| 140 self._check_bad_exception_context(node) |
| 141 |
| 142 expr = node.exc |
| 143 if self._check_raise_value(node, expr): |
| 144 return |
| 145 else: |
| 146 try: |
| 147 value = next(unpack_infer(expr)) |
| 148 except astroid.InferenceError: |
| 149 return |
| 150 self._check_raise_value(node, value) |
| 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 |
| 169 def _check_raise_value(self, node, expr): |
| 170 """check for bad values, string exception and class inheritance |
| 171 """ |
| 172 value_found = True |
| 173 if isinstance(expr, astroid.Const): |
| 174 value = expr.value |
| 175 if not isinstance(value, str): |
| 176 # raising-string will be emitted from python3 porting checker. |
| 177 self.add_message('raising-bad-type', node=node, |
| 178 args=value.__class__.__name__) |
| 179 elif ((isinstance(expr, astroid.Name) and |
| 180 expr.name in ('None', 'True', 'False')) or |
| 181 isinstance(expr, (astroid.List, astroid.Dict, astroid.Tuple, |
| 182 astroid.Module, astroid.Function))): |
| 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) |
| 205 elif ((isinstance(expr, astroid.Name) and expr.name == 'NotImplemented') |
| 206 or (isinstance(expr, astroid.CallFunc) and |
| 207 isinstance(expr.func, astroid.Name) and |
| 208 expr.func.name == 'NotImplemented')): |
| 209 self.add_message('notimplemented-raised', node=node) |
| 210 elif isinstance(expr, (Instance, astroid.Class)): |
| 211 if isinstance(expr, Instance): |
| 212 # pylint: disable=protected-access |
| 213 expr = expr._proxied |
| 214 if (isinstance(expr, astroid.Class) and |
| 215 not inherit_from_std_ex(expr)): |
| 216 if expr.newstyle: |
| 217 self.add_message('raising-non-exception', node=node) |
| 218 else: |
| 219 if has_known_bases(expr): |
| 220 confidence = INFERENCE |
| 221 else: |
| 222 confidence = INFERENCE_FAILURE |
| 223 self.add_message( |
| 224 'nonstandard-exception', node=node, |
| 225 confidence=confidence) |
| 226 else: |
| 227 value_found = False |
| 228 else: |
| 229 value_found = False |
| 230 return value_found |
| 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 |
| 271 @check_messages('bare-except', 'broad-except', 'pointless-except', |
| 272 'binary-op-exception', 'bad-except-order', |
| 273 'catching-non-exception') |
| 274 def visit_tryexcept(self, node): |
| 275 """check for empty except""" |
| 276 exceptions_classes = [] |
| 277 nb_handlers = len(node.handlers) |
| 278 for index, handler in enumerate(node.handlers): |
| 279 # single except doing nothing but "pass" without else clause |
| 280 if is_empty(handler.body) and not node.orelse: |
| 281 self.add_message('pointless-except', |
| 282 node=handler.type or handler.body[0]) |
| 283 if handler.type is None: |
| 284 if not is_raising(handler.body): |
| 285 self.add_message('bare-except', node=handler) |
| 286 # check if a "except:" is followed by some other |
| 287 # except |
| 288 if index < (nb_handlers - 1): |
| 289 msg = 'empty except clause should always appear last' |
| 290 self.add_message('bad-except-order', node=node, args=msg) |
| 291 |
| 292 elif isinstance(handler.type, astroid.BoolOp): |
| 293 self.add_message('binary-op-exception', |
| 294 node=handler, args=handler.type.op) |
| 295 else: |
| 296 try: |
| 297 excs = list(_annotated_unpack_infer(handler.type)) |
| 298 except astroid.InferenceError: |
| 299 continue |
| 300 for part, exc in excs: |
| 301 if exc is YES: |
| 302 continue |
| 303 if (isinstance(exc, astroid.Instance) |
| 304 and inherit_from_std_ex(exc)): |
| 305 # pylint: disable=protected-access |
| 306 exc = exc._proxied |
| 307 |
| 308 self._check_catching_non_exception(handler, exc, part) |
| 309 |
| 310 if not isinstance(exc, astroid.Class): |
| 311 continue |
| 312 |
| 313 exc_ancestors = [anc for anc in exc.ancestors() |
| 314 if isinstance(anc, astroid.Class)] |
| 315 for previous_exc in exceptions_classes: |
| 316 if previous_exc in exc_ancestors: |
| 317 msg = '%s is an ancestor class of %s' % ( |
| 318 previous_exc.name, exc.name) |
| 319 self.add_message('bad-except-order', |
| 320 node=handler.type, args=msg) |
| 321 if (exc.name in self.config.overgeneral_exceptions |
| 322 and exc.root().name == EXCEPTIONS_MODULE |
| 323 and not is_raising(handler.body)): |
| 324 self.add_message('broad-except', |
| 325 args=exc.name, node=handler.type) |
| 326 |
| 327 exceptions_classes += [exc for _, exc in excs] |
| 328 |
| 329 |
| 330 def register(linter): |
| 331 """required method to auto register this checker""" |
| 332 linter.register_checker(ExceptionsChecker(linter)) |
OLD | NEW |