OLD | NEW |
1 # Copyright (c) 2003-2013 LOGILAB S.A. (Paris, FRANCE). | 1 # Copyright (c) 2003-2007 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 # 59 Temple Place - Suite 330, Boston, MA 02111-1307, 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 import astroid | 21 from logilab import astng |
22 from astroid import YES, Instance, unpack_infer | 22 from logilab.astng 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, check_messages | 25 from pylint.checkers.utils import is_empty, is_raising |
26 from pylint.interfaces import IAstroidChecker | 26 from pylint.interfaces import IASTNGChecker |
27 | 27 |
28 def infer_bases(klass): | |
29 """ Fully infer the bases of the klass node. | |
30 | 28 |
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) | |
47 OVERGENERAL_EXCEPTIONS = ('Exception',) | 29 OVERGENERAL_EXCEPTIONS = ('Exception',) |
48 | 30 |
49 MSGS = { | 31 MSGS = { |
50 'E0701': ('Bad except clauses order (%s)', | 32 'E0701': ( |
51 'bad-except-order', | 33 'Bad except clauses order (%s)', |
52 'Used when except clauses are not in the correct order (from the ' | 34 'Used when except clauses are not in the correct order (from the \ |
53 'more specific to the more generic). If you don\'t fix the order,
' | 35 more specific to the more generic). If you don\'t fix the order, \ |
54 'some exceptions may not be catched by the most specific handler.'
), | 36 some exceptions may not be catched by the most specific handler.'), |
55 'E0702': ('Raising %s while only classes, instances or string are allowed', | 37 'E0702': ('Raising %s while only classes, instances or string are allowed', |
56 'raising-bad-type', | |
57 'Used when something which is neither a class, an instance or a \ | 38 'Used when something which is neither a class, an instance or a \ |
58 string is raised (i.e. a `TypeError` will be raised).'), | 39 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)}), | |
66 'E0710': ('Raising a new style class which doesn\'t inherit from BaseExcepti
on', | 40 'E0710': ('Raising a new style class which doesn\'t inherit from BaseExcepti
on', |
67 'raising-non-exception', | |
68 'Used when a new style class which doesn\'t inherit from \ | 41 'Used when a new style class which doesn\'t inherit from \ |
69 BaseException is raised.'), | 42 BaseException is raised.'), |
70 'E0711': ('NotImplemented raised - should raise NotImplementedError', | 43 'E0711': ('NotImplemented raised - should raise NotImplementedError', |
71 'notimplemented-raised', | |
72 'Used when NotImplemented is raised instead of \ | 44 'Used when NotImplemented is raised instead of \ |
73 NotImplementedError'), | 45 NotImplementedError'), |
74 'E0712': ('Catching an exception which doesn\'t inherit from BaseException:
%s', | 46 |
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 | |
79 'W0701': ('Raising a string exception', | 47 'W0701': ('Raising a string exception', |
80 'raising-string', | |
81 'Used when a string exception is raised.'), | 48 'Used when a string exception is raised.'), |
82 'W0702': ('No exception type(s) specified', | 49 'W0702': ('No exception type(s) specified', |
83 'bare-except', | |
84 'Used when an except clause doesn\'t specify exceptions type to \ | 50 'Used when an except clause doesn\'t specify exceptions type to \ |
85 catch.'), | 51 catch.'), |
86 'W0703': ('Catching too general exception %s', | 52 'W0703': ('Catching too general exception %s', |
87 'broad-except', | |
88 'Used when an except catches a too general exception, \ | 53 'Used when an except catches a too general exception, \ |
89 possibly burying unrelated errors.'), | 54 possibly burying unrelated errors.'), |
90 'W0704': ('Except doesn\'t do anything', | 55 'W0704': ('Except doesn\'t do anything', |
91 'pointless-except', | |
92 'Used when an except clause does nothing but "pass" and there is\ | 56 'Used when an except clause does nothing but "pass" and there is\ |
93 no "else" clause.'), | 57 no "else" clause.'), |
94 'W0710': ('Exception doesn\'t inherit from standard "Exception" class', | 58 'W0710': ('Exception doesn\'t inherit from standard "Exception" class', |
95 'nonstandard-exception', | |
96 'Used when a custom exception class is raised but doesn\'t \ | 59 'Used when a custom exception class is raised but doesn\'t \ |
97 inherit from the builtin "Exception" class.', | 60 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)}), | |
115 } | 61 } |
116 | 62 |
117 | 63 |
118 if sys.version_info < (3, 0): | 64 if sys.version_info < (3, 0): |
119 EXCEPTIONS_MODULE = "exceptions" | 65 EXCEPTIONS_MODULE = "exceptions" |
120 else: | 66 else: |
121 EXCEPTIONS_MODULE = "builtins" | 67 EXCEPTIONS_MODULE = "builtins" |
122 | 68 |
123 class ExceptionsChecker(BaseChecker): | 69 class ExceptionsChecker(BaseChecker): |
124 """checks for | 70 """checks for |
125 * excepts without exception filter | 71 * excepts without exception filter |
126 * type of raise argument : string, Exceptions, other values | 72 * type of raise argument : string, Exceptions, other values |
127 """ | 73 """ |
128 | 74 |
129 __implements__ = IAstroidChecker | 75 __implements__ = IASTNGChecker |
130 | 76 |
131 name = 'exceptions' | 77 name = 'exceptions' |
132 msgs = MSGS | 78 msgs = MSGS |
133 priority = -4 | 79 priority = -4 |
134 options = (('overgeneral-exceptions', | 80 options = (('overgeneral-exceptions', |
135 {'default' : OVERGENERAL_EXCEPTIONS, | 81 {'default' : OVERGENERAL_EXCEPTIONS, |
136 'type' :'csv', 'metavar' : '<comma-separated class names>', | 82 'type' :'csv', 'metavar' : '<comma-separated class names>', |
137 'help' : 'Exceptions that will emit a warning ' | 83 'help' : 'Exceptions that will emit a warning ' |
138 'when being caught. Defaults to "%s"' % ( | 84 'when being caught. Defaults to "%s"' % ( |
139 ', '.join(OVERGENERAL_EXCEPTIONS),)} | 85 ', '.join(OVERGENERAL_EXCEPTIONS),)} |
140 ), | 86 ), |
141 ) | 87 ) |
142 | 88 |
143 @check_messages('raising-string', 'nonstandard-exception', 'raising-bad-type
', | |
144 'raising-non-exception', 'notimplemented-raised', 'bad-excep
tion-context') | |
145 def visit_raise(self, node): | 89 def visit_raise(self, node): |
146 """visit raise possibly inferring value""" | 90 """visit raise possibly inferring value""" |
147 # ignore empty raise | 91 # ignore empty raise |
148 if node.exc is None: | 92 if node.exc is None: |
149 return | 93 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) | |
166 expr = node.exc | 94 expr = node.exc |
167 if self._check_raise_value(node, expr): | 95 if self._check_raise_value(node, expr): |
168 return | 96 return |
169 else: | 97 else: |
170 try: | 98 try: |
171 value = unpack_infer(expr).next() | 99 value = unpack_infer(expr).next() |
172 except astroid.InferenceError: | 100 except astng.InferenceError: |
173 return | 101 return |
174 self._check_raise_value(node, value) | 102 self._check_raise_value(node, value) |
175 | 103 |
176 def _check_raise_value(self, node, expr): | 104 def _check_raise_value(self, node, expr): |
177 """check for bad values, string exception and class inheritance | 105 """check for bad values, string exception and class inheritance |
178 """ | 106 """ |
179 value_found = True | 107 value_found = True |
180 if isinstance(expr, astroid.Const): | 108 if isinstance(expr, astng.Const): |
181 value = expr.value | 109 value = expr.value |
182 if isinstance(value, str): | 110 if isinstance(value, str): |
183 self.add_message('raising-string', node=node) | 111 self.add_message('W0701', node=node) |
184 else: | 112 else: |
185 self.add_message('raising-bad-type', node=node, | 113 self.add_message('E0702', node=node, |
186 args=value.__class__.__name__) | 114 args=value.__class__.__name__) |
187 elif (isinstance(expr, astroid.Name) and \ | 115 elif (isinstance(expr, astng.Name) and \ |
188 expr.name in ('None', 'True', 'False')) or \ | 116 expr.name in ('None', 'True', 'False')) or \ |
189 isinstance(expr, (astroid.List, astroid.Dict, astroid.Tuple, | 117 isinstance(expr, (astng.List, astng.Dict, astng.Tuple, |
190 astroid.Module, astroid.Function)): | 118 astng.Module, astng.Function)): |
191 self.add_message('raising-bad-type', node=node, args=expr.name) | 119 self.add_message('E0702', node=node, args=expr.name) |
192 elif ((isinstance(expr, astroid.Name) and expr.name == 'NotImplemented') | 120 elif ( (isinstance(expr, astng.Name) and expr.name == 'NotImplemented') |
193 or (isinstance(expr, astroid.CallFunc) and | 121 or (isinstance(expr, astng.CallFunc) and |
194 isinstance(expr.func, astroid.Name) and | 122 isinstance(expr.func, astng.Name) and |
195 expr.func.name == 'NotImplemented')): | 123 expr.func.name == 'NotImplemented') ): |
196 self.add_message('notimplemented-raised', node=node) | 124 self.add_message('E0711', node=node) |
197 elif isinstance(expr, astroid.BinOp) and expr.op == '%': | 125 elif isinstance(expr, astng.BinOp) and expr.op == '%': |
198 self.add_message('raising-string', node=node) | 126 self.add_message('W0701', node=node) |
199 elif isinstance(expr, (Instance, astroid.Class)): | 127 elif isinstance(expr, (Instance, astng.Class)): |
200 if isinstance(expr, Instance): | 128 if isinstance(expr, Instance): |
201 expr = expr._proxied | 129 expr = expr._proxied |
202 if (isinstance(expr, astroid.Class) and | 130 if (isinstance(expr, astng.Class) and |
203 not inherit_from_std_ex(expr) and | 131 not inherit_from_std_ex(expr) and |
204 expr.root().name != BUILTINS_NAME): | 132 expr.root().name != BUILTINS_NAME): |
205 if expr.newstyle: | 133 if expr.newstyle: |
206 self.add_message('raising-non-exception', node=node) | 134 self.add_message('E0710', node=node) |
207 else: | 135 else: |
208 self.add_message('nonstandard-exception', node=node) | 136 self.add_message('W0710', node=node) |
209 else: | 137 else: |
210 value_found = False | 138 value_found = False |
211 else: | 139 else: |
212 value_found = False | 140 value_found = False |
213 return value_found | 141 return value_found |
214 | 142 |
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) | |
220 | 143 |
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') | |
236 def visit_tryexcept(self, node): | 144 def visit_tryexcept(self, node): |
237 """check for empty except""" | 145 """check for empty except""" |
238 exceptions_classes = [] | 146 exceptions_classes = [] |
239 nb_handlers = len(node.handlers) | 147 nb_handlers = len(node.handlers) |
240 for index, handler in enumerate(node.handlers): | 148 for index, handler in enumerate(node.handlers): |
241 # single except doing nothing but "pass" without else clause | 149 # single except doing nothing but "pass" without else clause |
242 if is_empty(handler.body) and not node.orelse: | 150 if nb_handlers == 1 and is_empty(handler.body) and not node.orelse: |
243 self.add_message('pointless-except', node=handler.type or handle
r.body[0]) | 151 self.add_message('W0704', node=handler.type or handler.body[0]) |
244 if handler.type is None: | 152 if handler.type is None: |
245 if not is_raising(handler.body): | 153 if nb_handlers == 1 and not is_raising(handler.body): |
246 self.add_message('bare-except', node=handler) | 154 self.add_message('W0702', node=handler) |
247 # check if a "except:" is followed by some other | 155 # check if a "except:" is followed by some other |
248 # except | 156 # except |
249 if index < (nb_handlers - 1): | 157 elif index < (nb_handlers - 1): |
250 msg = 'empty except clause should always appear last' | 158 msg = 'empty except clause should always appear last' |
251 self.add_message('bad-except-order', node=node, args=msg) | 159 self.add_message('E0701', 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) | |
255 else: | 160 else: |
256 try: | 161 try: |
257 excs = list(unpack_infer(handler.type)) | 162 excs = list(unpack_infer(handler.type)) |
258 except astroid.InferenceError: | 163 except astng.InferenceError: |
259 continue | 164 continue |
260 for exc in excs: | 165 for exc in excs: |
261 # XXX skip other non class nodes | 166 # XXX skip other non class nodes |
262 if exc is YES or not isinstance(exc, astroid.Class): | 167 if exc is YES or not isinstance(exc, astng.Class): |
263 continue | 168 continue |
264 exc_ancestors = [anc for anc in exc.ancestors() | 169 exc_ancestors = [anc for anc in exc.ancestors() |
265 if isinstance(anc, astroid.Class)] | 170 if isinstance(anc, astng.Class)] |
266 for previous_exc in exceptions_classes: | 171 for previous_exc in exceptions_classes: |
267 if previous_exc in exc_ancestors: | 172 if previous_exc in exc_ancestors: |
268 msg = '%s is an ancestor class of %s' % ( | 173 msg = '%s is an ancestor class of %s' % ( |
269 previous_exc.name, exc.name) | 174 previous_exc.name, exc.name) |
270 self.add_message('bad-except-order', node=handler.ty
pe, args=msg) | 175 self.add_message('E0701', node=handler.type, args=ms
g) |
271 if (exc.name in self.config.overgeneral_exceptions | 176 if (exc.name in self.config.overgeneral_exceptions |
272 and exc.root().name == EXCEPTIONS_MODULE | 177 and exc.root().name == EXCEPTIONS_MODULE |
273 and not is_raising(handler.body)): | 178 and nb_handlers == 1 and not is_raising(handler.body)): |
274 self.add_message('broad-except', args=exc.name, node=han
dler.type) | 179 self.add_message('W0703', args=exc.name, node=handler.ty
pe) |
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 | |
289 exceptions_classes += excs | 180 exceptions_classes += excs |
290 | 181 |
291 | 182 |
292 def inherit_from_std_ex(node): | 183 def inherit_from_std_ex(node): |
293 """return true if the given class node is subclass of | 184 """return true if the given class node is subclass of |
294 exceptions.Exception | 185 exceptions.Exception |
295 """ | 186 """ |
296 if node.name in ('Exception', 'BaseException') \ | 187 if node.name in ('Exception', 'BaseException') \ |
297 and node.root().name == EXCEPTIONS_MODULE: | 188 and node.root().name == EXCEPTIONS_MODULE: |
298 return True | 189 return True |
299 for parent in node.ancestors(recurs=False): | 190 for parent in node.ancestors(recurs=False): |
300 if inherit_from_std_ex(parent): | 191 if inherit_from_std_ex(parent): |
301 return True | 192 return True |
302 return False | 193 return False |
303 | 194 |
304 def register(linter): | 195 def register(linter): |
305 """required method to auto register this checker""" | 196 """required method to auto register this checker""" |
306 linter.register_checker(ExceptionsChecker(linter)) | 197 linter.register_checker(ExceptionsChecker(linter)) |
OLD | NEW |