gvsig-scripting / org.gvsig.scripting / trunk / org.gvsig.scripting / org.gvsig.scripting.app / org.gvsig.scripting.app.mainplugin / src / main / resources-plugin / scripting / lib / pylint / checkers / exceptions.py @ 745
History | View | Annotate | Download (16 KB)
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 inspect |
18 |
import sys |
19 |
|
20 |
import six |
21 |
from six.moves import builtins |
22 |
|
23 |
import astroid |
24 |
from pylint.checkers import BaseChecker |
25 |
from pylint.checkers.utils import ( |
26 |
is_raising, |
27 |
check_messages, |
28 |
inherit_from_std_ex, |
29 |
EXCEPTIONS_MODULE, |
30 |
safe_infer, |
31 |
has_known_bases) |
32 |
from pylint.interfaces import IAstroidChecker |
33 |
|
34 |
|
35 |
def _builtin_exceptions(): |
36 |
def predicate(obj): |
37 |
return isinstance(obj, type) and issubclass(obj, BaseException) |
38 |
|
39 |
members = inspect.getmembers(six.moves.builtins, predicate) |
40 |
return {exc.__name__ for (_, exc) in members} |
41 |
|
42 |
|
43 |
def _annotated_unpack_infer(stmt, context=None): |
44 |
"""
|
45 |
Recursively generate nodes inferred by the given statement.
|
46 |
If the inferred value is a list or a tuple, recurse on the elements.
|
47 |
Returns an iterator which yields tuples in the format
|
48 |
('original node', 'infered node').
|
49 |
"""
|
50 |
if isinstance(stmt, (astroid.List, astroid.Tuple)): |
51 |
for elt in stmt.elts: |
52 |
inferred = safe_infer(elt) |
53 |
if inferred and inferred is not astroid.YES: |
54 |
yield elt, inferred
|
55 |
return
|
56 |
for infered in stmt.infer(context): |
57 |
if infered is astroid.YES: |
58 |
continue
|
59 |
yield stmt, infered
|
60 |
|
61 |
|
62 |
PY3K = sys.version_info >= (3, 0) |
63 |
OVERGENERAL_EXCEPTIONS = ('Exception',)
|
64 |
BUILTINS_NAME = builtins.__name__ |
65 |
|
66 |
MSGS = { |
67 |
'E0701': ('Bad except clauses order (%s)', |
68 |
'bad-except-order',
|
69 |
'Used when except clauses are not in the correct order (from the '
|
70 |
'more specific to the more generic). If you don\'t fix the order, '
|
71 |
'some exceptions may not be catched by the most specific handler.'),
|
72 |
'E0702': ('Raising %s while only classes or instances are allowed', |
73 |
'raising-bad-type',
|
74 |
'Used when something which is neither a class, an instance or a \
|
75 |
string is raised (i.e. a `TypeError` will be raised).'),
|
76 |
'E0703': ('Exception context set to something which is not an ' |
77 |
'exception, nor None',
|
78 |
'bad-exception-context',
|
79 |
'Used when using the syntax "raise ... from ...", '
|
80 |
'where the exception context is not an exception, '
|
81 |
'nor None.',
|
82 |
{'minversion': (3, 0)}), |
83 |
'E0704': ('The raise statement is not inside an except clause', |
84 |
'misplaced-bare-raise',
|
85 |
'Used when a bare raise is not used inside an except clause. '
|
86 |
'This generates an error, since there are no active exceptions '
|
87 |
'to be reraised. An exception to this rule is represented by '
|
88 |
'a bare raise inside a finally clause, which might work, as long '
|
89 |
'as an exception is raised inside the try block, but it is '
|
90 |
'nevertheless a code smell that must not be relied upon.'),
|
91 |
'E0710': ('Raising a new style class which doesn\'t inherit from BaseException', |
92 |
'raising-non-exception',
|
93 |
'Used when a new style class which doesn\'t inherit from \
|
94 |
BaseException is raised.'),
|
95 |
'E0711': ('NotImplemented raised - should raise NotImplementedError', |
96 |
'notimplemented-raised',
|
97 |
'Used when NotImplemented is raised instead of \
|
98 |
NotImplementedError'),
|
99 |
'E0712': ('Catching an exception which doesn\'t inherit from BaseException: %s', |
100 |
'catching-non-exception',
|
101 |
'Used when a class which doesn\'t inherit from \
|
102 |
BaseException is used as an exception in an except clause.'),
|
103 |
'W0702': ('No exception type(s) specified', |
104 |
'bare-except',
|
105 |
'Used when an except clause doesn\'t specify exceptions type to \
|
106 |
catch.'),
|
107 |
'W0703': ('Catching too general exception %s', |
108 |
'broad-except',
|
109 |
'Used when an except catches a too general exception, \
|
110 |
possibly burying unrelated errors.'),
|
111 |
'W0705': ('Catching previously caught exception type %s', |
112 |
'duplicate-except',
|
113 |
'Used when an except catches a type that was already caught by '
|
114 |
'a previous handler.'),
|
115 |
'W0710': ('Exception doesn\'t inherit from standard "Exception" class', |
116 |
'nonstandard-exception',
|
117 |
'Used when a custom exception class is raised but doesn\'t \
|
118 |
inherit from the builtin "Exception" class.',
|
119 |
{'maxversion': (3, 0)}), |
120 |
'W0711': ('Exception to catch is the result of a binary "%s" operation', |
121 |
'binary-op-exception',
|
122 |
'Used when the exception to catch is of the form \
|
123 |
"except A or B:". If intending to catch multiple, \
|
124 |
rewrite as "except (A, B):"'),
|
125 |
} |
126 |
|
127 |
|
128 |
class ExceptionsChecker(BaseChecker): |
129 |
"""checks for
|
130 |
* excepts without exception filter
|
131 |
* type of raise argument : string, Exceptions, other values
|
132 |
"""
|
133 |
|
134 |
__implements__ = IAstroidChecker |
135 |
|
136 |
name = 'exceptions'
|
137 |
msgs = MSGS |
138 |
priority = -4
|
139 |
options = (('overgeneral-exceptions',
|
140 |
{'default' : OVERGENERAL_EXCEPTIONS,
|
141 |
'type' :'csv', 'metavar' : '<comma-separated class names>', |
142 |
'help' : 'Exceptions that will emit a warning ' |
143 |
'when being caught. Defaults to "%s"' % (
|
144 |
', '.join(OVERGENERAL_EXCEPTIONS),)}
|
145 |
), |
146 |
) |
147 |
|
148 |
def open(self): |
149 |
self.builtin_exceptions = _builtin_exceptions()
|
150 |
super(ExceptionsChecker, self).open() |
151 |
|
152 |
@check_messages('nonstandard-exception', 'misplaced-bare-raise', |
153 |
'raising-bad-type', 'raising-non-exception', |
154 |
'notimplemented-raised', 'bad-exception-context') |
155 |
def visit_raise(self, node): |
156 |
"""visit raise possibly inferring value"""
|
157 |
if node.exc is None: |
158 |
self._check_misplaced_bare_raise(node)
|
159 |
return
|
160 |
if PY3K and node.cause: |
161 |
self._check_bad_exception_context(node)
|
162 |
|
163 |
expr = node.exc |
164 |
if self._check_raise_value(node, expr): |
165 |
return
|
166 |
else:
|
167 |
try:
|
168 |
value = next(astroid.unpack_infer(expr))
|
169 |
except astroid.InferenceError:
|
170 |
return
|
171 |
self._check_raise_value(node, value)
|
172 |
|
173 |
def _check_misplaced_bare_raise(self, node): |
174 |
# Filter out if it's present in __exit__.
|
175 |
scope = node.scope() |
176 |
if (isinstance(scope, astroid.FunctionDef) |
177 |
and scope.is_method()
|
178 |
and scope.name == '__exit__'): |
179 |
return
|
180 |
|
181 |
current = node |
182 |
# Stop when a new scope is generated or when the raise
|
183 |
# statement is found inside a TryFinally.
|
184 |
ignores = (astroid.ExceptHandler, astroid.FunctionDef, astroid.TryFinally) |
185 |
while current and not isinstance(current.parent, ignores): |
186 |
current = current.parent |
187 |
|
188 |
expected = (astroid.ExceptHandler,) |
189 |
if (not current |
190 |
or not isinstance(current.parent, expected)): |
191 |
self.add_message('misplaced-bare-raise', node=node) |
192 |
|
193 |
def _check_bad_exception_context(self, node): |
194 |
"""Verify that the exception context is properly set.
|
195 |
|
196 |
An exception context can be only `None` or an exception.
|
197 |
"""
|
198 |
cause = safe_infer(node.cause) |
199 |
if cause in (astroid.YES, None): |
200 |
return
|
201 |
if isinstance(cause, astroid.Const): |
202 |
if cause.value is not None: |
203 |
self.add_message('bad-exception-context', |
204 |
node=node) |
205 |
elif (not isinstance(cause, astroid.ClassDef) and |
206 |
not inherit_from_std_ex(cause)):
|
207 |
self.add_message('bad-exception-context', |
208 |
node=node) |
209 |
|
210 |
def _check_raise_value(self, node, expr): |
211 |
"""check for bad values, string exception and class inheritance
|
212 |
"""
|
213 |
value_found = True
|
214 |
if isinstance(expr, astroid.Const): |
215 |
value = expr.value |
216 |
if not isinstance(value, str): |
217 |
# raising-string will be emitted from python3 porting checker.
|
218 |
self.add_message('raising-bad-type', node=node, |
219 |
args=value.__class__.__name__) |
220 |
elif ((isinstance(expr, astroid.Name) and |
221 |
expr.name in ('None', 'True', 'False')) or |
222 |
isinstance(expr, (astroid.List, astroid.Dict, astroid.Tuple,
|
223 |
astroid.Module, astroid.FunctionDef))): |
224 |
emit = True
|
225 |
if not PY3K and isinstance(expr, astroid.Tuple) and expr.elts: |
226 |
# On Python 2, using the following is not an error:
|
227 |
# raise (ZeroDivisionError, None)
|
228 |
# raise (ZeroDivisionError, )
|
229 |
# What's left to do is to check that the first
|
230 |
# argument is indeed an exception.
|
231 |
# Verifying the other arguments is not
|
232 |
# the scope of this check.
|
233 |
first = expr.elts[0]
|
234 |
inferred = safe_infer(first) |
235 |
if isinstance(inferred, astroid.Instance): |
236 |
# pylint: disable=protected-access
|
237 |
inferred = inferred._proxied |
238 |
if (inferred is astroid.YES or |
239 |
isinstance(inferred, astroid.ClassDef)
|
240 |
and inherit_from_std_ex(inferred)):
|
241 |
emit = False
|
242 |
if emit:
|
243 |
self.add_message('raising-bad-type', |
244 |
node=node, |
245 |
args=expr.name) |
246 |
elif ((isinstance(expr, astroid.Name) and expr.name == 'NotImplemented') |
247 |
or (isinstance(expr, astroid.Call) and |
248 |
isinstance(expr.func, astroid.Name) and |
249 |
expr.func.name == 'NotImplemented')):
|
250 |
self.add_message('notimplemented-raised', node=node) |
251 |
elif isinstance(expr, (astroid.Instance, astroid.ClassDef)): |
252 |
if isinstance(expr, astroid.Instance): |
253 |
# pylint: disable=protected-access
|
254 |
expr = expr._proxied |
255 |
if (isinstance(expr, astroid.ClassDef) and |
256 |
not inherit_from_std_ex(expr) and |
257 |
has_known_bases(expr)): |
258 |
if expr.newstyle:
|
259 |
self.add_message('raising-non-exception', node=node) |
260 |
else:
|
261 |
self.add_message('nonstandard-exception', node=node) |
262 |
else:
|
263 |
value_found = False
|
264 |
else:
|
265 |
value_found = False
|
266 |
return value_found
|
267 |
|
268 |
def _check_catching_non_exception(self, handler, exc, part): |
269 |
if isinstance(exc, astroid.Tuple): |
270 |
# Check if it is a tuple of exceptions.
|
271 |
inferred = [safe_infer(elt) for elt in exc.elts] |
272 |
if any(node is astroid.YES for node in inferred): |
273 |
# Don't emit if we don't know every component.
|
274 |
return
|
275 |
if all(node and inherit_from_std_ex(node) |
276 |
for node in inferred): |
277 |
return
|
278 |
|
279 |
if not isinstance(exc, astroid.ClassDef): |
280 |
# Don't emit the warning if the infered stmt
|
281 |
# is None, but the exception handler is something else,
|
282 |
# maybe it was redefined.
|
283 |
if (isinstance(exc, astroid.Const) and |
284 |
exc.value is None): |
285 |
if ((isinstance(handler.type, astroid.Const) and |
286 |
handler.type.value is None) or |
287 |
handler.type.parent_of(exc)): |
288 |
# If the exception handler catches None or
|
289 |
# the exception component, which is None, is
|
290 |
# defined by the entire exception handler, then
|
291 |
# emit a warning.
|
292 |
self.add_message('catching-non-exception', |
293 |
node=handler.type, |
294 |
args=(part.as_string(), )) |
295 |
else:
|
296 |
self.add_message('catching-non-exception', |
297 |
node=handler.type, |
298 |
args=(part.as_string(), )) |
299 |
return
|
300 |
|
301 |
if (not inherit_from_std_ex(exc) and |
302 |
exc.name not in self.builtin_exceptions): |
303 |
if has_known_bases(exc):
|
304 |
self.add_message('catching-non-exception', |
305 |
node=handler.type, |
306 |
args=(exc.name, )) |
307 |
|
308 |
@check_messages('bare-except', 'broad-except', |
309 |
'binary-op-exception', 'bad-except-order', |
310 |
'catching-non-exception', 'duplicate-except') |
311 |
def visit_tryexcept(self, node): |
312 |
"""check for empty except"""
|
313 |
exceptions_classes = [] |
314 |
nb_handlers = len(node.handlers)
|
315 |
for index, handler in enumerate(node.handlers): |
316 |
if handler.type is None: |
317 |
if not is_raising(handler.body): |
318 |
self.add_message('bare-except', node=handler) |
319 |
# check if a "except:" is followed by some other
|
320 |
# except
|
321 |
if index < (nb_handlers - 1): |
322 |
msg = 'empty except clause should always appear last'
|
323 |
self.add_message('bad-except-order', node=node, args=msg) |
324 |
|
325 |
elif isinstance(handler.type, astroid.BoolOp): |
326 |
self.add_message('binary-op-exception', |
327 |
node=handler, args=handler.type.op) |
328 |
else:
|
329 |
try:
|
330 |
excs = list(_annotated_unpack_infer(handler.type))
|
331 |
except astroid.InferenceError:
|
332 |
continue
|
333 |
for part, exc in excs: |
334 |
if exc is astroid.YES: |
335 |
continue
|
336 |
if (isinstance(exc, astroid.Instance) |
337 |
and inherit_from_std_ex(exc)):
|
338 |
# pylint: disable=protected-access
|
339 |
exc = exc._proxied |
340 |
|
341 |
self._check_catching_non_exception(handler, exc, part)
|
342 |
|
343 |
if not isinstance(exc, astroid.ClassDef): |
344 |
continue
|
345 |
|
346 |
exc_ancestors = [anc for anc in exc.ancestors() |
347 |
if isinstance(anc, astroid.ClassDef)] |
348 |
for previous_exc in exceptions_classes: |
349 |
if previous_exc in exc_ancestors: |
350 |
msg = '%s is an ancestor class of %s' % (
|
351 |
previous_exc.name, exc.name) |
352 |
self.add_message('bad-except-order', |
353 |
node=handler.type, args=msg) |
354 |
if (exc.name in self.config.overgeneral_exceptions |
355 |
and exc.root().name == EXCEPTIONS_MODULE
|
356 |
and not is_raising(handler.body)): |
357 |
self.add_message('broad-except', |
358 |
args=exc.name, node=handler.type) |
359 |
|
360 |
if exc in exceptions_classes: |
361 |
self.add_message('duplicate-except', |
362 |
args=exc.name, node=handler.type) |
363 |
|
364 |
exceptions_classes += [exc for _, exc in excs] |
365 |
|
366 |
|
367 |
def register(linter): |
368 |
"""required method to auto register this checker"""
|
369 |
linter.register_checker(ExceptionsChecker(linter)) |