Statistics
| Revision:

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))