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 |