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