summaryrefslogtreecommitdiff
path: root/venv/Lib/site-packages/pylint/checkers/exceptions.py
diff options
context:
space:
mode:
Diffstat (limited to 'venv/Lib/site-packages/pylint/checkers/exceptions.py')
-rw-r--r--venv/Lib/site-packages/pylint/checkers/exceptions.py546
1 files changed, 546 insertions, 0 deletions
diff --git a/venv/Lib/site-packages/pylint/checkers/exceptions.py b/venv/Lib/site-packages/pylint/checkers/exceptions.py
new file mode 100644
index 0000000..360e1d1
--- /dev/null
+++ b/venv/Lib/site-packages/pylint/checkers/exceptions.py
@@ -0,0 +1,546 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2006-2011, 2013-2014 LOGILAB S.A. (Paris, FRANCE) <contact@logilab.fr>
+# Copyright (c) 2011-2014 Google, Inc.
+# Copyright (c) 2012 Tim Hatch <tim@timhatch.com>
+# Copyright (c) 2013-2018 Claudiu Popa <pcmanticore@gmail.com>
+# Copyright (c) 2014 Brett Cannon <brett@python.org>
+# Copyright (c) 2014 Arun Persaud <arun@nubati.net>
+# Copyright (c) 2015 Rene Zhang <rz99@cornell.edu>
+# Copyright (c) 2015 Florian Bruhin <me@the-compiler.org>
+# Copyright (c) 2015 Steven Myint <hg@stevenmyint.com>
+# Copyright (c) 2015 Ionel Cristian Maries <contact@ionelmc.ro>
+# Copyright (c) 2016 Erik <erik.eriksson@yahoo.com>
+# Copyright (c) 2016 Jakub Wilk <jwilk@jwilk.net>
+# Copyright (c) 2017 Łukasz Rogalski <rogalski.91@gmail.com>
+# Copyright (c) 2017 Martin von Gagern <gagern@google.com>
+# Copyright (c) 2018 Mike Frysinger <vapier@gmail.com>
+# Copyright (c) 2018 ssolanki <sushobhitsolanki@gmail.com>
+# Copyright (c) 2018 Alexander Todorov <atodorov@otb.bg>
+# Copyright (c) 2018 Ville Skyttä <ville.skytta@upcloud.com>
+
+# Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html
+# For details: https://github.com/PyCQA/pylint/blob/master/COPYING
+
+"""Checks for various exception related errors."""
+import builtins
+import inspect
+import typing
+
+import astroid
+from astroid.node_classes import NodeNG
+
+from pylint import checkers, interfaces
+from pylint.checkers import utils
+
+
+def _builtin_exceptions():
+ def predicate(obj):
+ return isinstance(obj, type) and issubclass(obj, BaseException)
+
+ members = inspect.getmembers(builtins, predicate)
+ return {exc.__name__ for (_, exc) in members}
+
+
+def _annotated_unpack_infer(stmt, context=None):
+ """
+ Recursively generate nodes inferred by the given statement.
+ If the inferred value is a list or a tuple, recurse on the elements.
+ Returns an iterator which yields tuples in the format
+ ('original node', 'inferred node').
+ """
+ if isinstance(stmt, (astroid.List, astroid.Tuple)):
+ for elt in stmt.elts:
+ inferred = utils.safe_infer(elt)
+ if inferred and inferred is not astroid.Uninferable:
+ yield elt, inferred
+ return
+ for inferred in stmt.infer(context):
+ if inferred is astroid.Uninferable:
+ continue
+ yield stmt, inferred
+
+
+def _is_raising(body: typing.List) -> bool:
+ """Return true if the given statement node raise an exception"""
+ for node in body:
+ if isinstance(node, astroid.Raise):
+ return True
+ return False
+
+
+OVERGENERAL_EXCEPTIONS = ("BaseException", "Exception")
+BUILTINS_NAME = builtins.__name__
+
+MSGS = {
+ "E0701": (
+ "Bad except clauses order (%s)",
+ "bad-except-order",
+ "Used when except clauses are not in the correct order (from the "
+ "more specific to the more generic). If you don't fix the order, "
+ "some exceptions may not be caught by the most specific handler.",
+ ),
+ "E0702": (
+ "Raising %s while only classes or instances are allowed",
+ "raising-bad-type",
+ "Used when something which is neither a class, an instance or a "
+ "string is raised (i.e. a `TypeError` will be raised).",
+ ),
+ "E0703": (
+ "Exception context set to something which is not an exception, nor None",
+ "bad-exception-context",
+ 'Used when using the syntax "raise ... from ...", '
+ "where the exception context is not an exception, "
+ "nor None.",
+ ),
+ "E0704": (
+ "The raise statement is not inside an except clause",
+ "misplaced-bare-raise",
+ "Used when a bare raise is not used inside an except clause. "
+ "This generates an error, since there are no active exceptions "
+ "to be reraised. An exception to this rule is represented by "
+ "a bare raise inside a finally clause, which might work, as long "
+ "as an exception is raised inside the try block, but it is "
+ "nevertheless a code smell that must not be relied upon.",
+ ),
+ "E0710": (
+ "Raising a new style class which doesn't inherit from BaseException",
+ "raising-non-exception",
+ "Used when a new style class which doesn't inherit from "
+ "BaseException is raised.",
+ ),
+ "E0711": (
+ "NotImplemented raised - should raise NotImplementedError",
+ "notimplemented-raised",
+ "Used when NotImplemented is raised instead of NotImplementedError",
+ ),
+ "E0712": (
+ "Catching an exception which doesn't inherit from Exception: %s",
+ "catching-non-exception",
+ "Used when a class which doesn't inherit from "
+ "Exception is used as an exception in an except clause.",
+ ),
+ "W0702": (
+ "No exception type(s) specified",
+ "bare-except",
+ "Used when an except clause doesn't specify exceptions type to catch.",
+ ),
+ "W0703": (
+ "Catching too general exception %s",
+ "broad-except",
+ "Used when an except catches a too general exception, "
+ "possibly burying unrelated errors.",
+ ),
+ "W0705": (
+ "Catching previously caught exception type %s",
+ "duplicate-except",
+ "Used when an except catches a type that was already caught by "
+ "a previous handler.",
+ ),
+ "W0706": (
+ "The except handler raises immediately",
+ "try-except-raise",
+ "Used when an except handler uses raise as its first or only "
+ "operator. This is useless because it raises back the exception "
+ "immediately. Remove the raise operator or the entire "
+ "try-except-raise block!",
+ ),
+ "W0711": (
+ 'Exception to catch is the result of a binary "%s" operation',
+ "binary-op-exception",
+ "Used when the exception to catch is of the form "
+ '"except A or B:". If intending to catch multiple, '
+ 'rewrite as "except (A, B):"',
+ ),
+ "W0715": (
+ "Exception arguments suggest string formatting might be intended",
+ "raising-format-tuple",
+ "Used when passing multiple arguments to an exception "
+ "constructor, the first of them a string literal containing what "
+ "appears to be placeholders intended for formatting",
+ ),
+ "W0716": (
+ "Invalid exception operation. %s",
+ "wrong-exception-operation",
+ "Used when an operation is done against an exception, but the operation "
+ "is not valid for the exception in question. Usually emitted when having "
+ "binary operations between exceptions in except handlers.",
+ ),
+}
+
+
+class BaseVisitor:
+ """Base class for visitors defined in this module."""
+
+ def __init__(self, checker, node):
+ self._checker = checker
+ self._node = node
+
+ def visit(self, node):
+ name = node.__class__.__name__.lower()
+ dispatch_meth = getattr(self, "visit_" + name, None)
+ if dispatch_meth:
+ dispatch_meth(node)
+ else:
+ self.visit_default(node)
+
+ def visit_default(self, node): # pylint: disable=unused-argument
+ """Default implementation for all the nodes."""
+
+
+class ExceptionRaiseRefVisitor(BaseVisitor):
+ """Visit references (anything that is not an AST leaf)."""
+
+ def visit_name(self, name):
+ if name.name == "NotImplemented":
+ self._checker.add_message("notimplemented-raised", node=self._node)
+
+ def visit_call(self, call):
+ if isinstance(call.func, astroid.Name):
+ self.visit_name(call.func)
+ if (
+ len(call.args) > 1
+ and isinstance(call.args[0], astroid.Const)
+ and isinstance(call.args[0].value, str)
+ ):
+ msg = call.args[0].value
+ if "%" in msg or ("{" in msg and "}" in msg):
+ self._checker.add_message("raising-format-tuple", node=self._node)
+
+
+class ExceptionRaiseLeafVisitor(BaseVisitor):
+ """Visitor for handling leaf kinds of a raise value."""
+
+ def visit_const(self, const):
+ if not isinstance(const.value, str):
+ # raising-string will be emitted from python3 porting checker.
+ self._checker.add_message(
+ "raising-bad-type", node=self._node, args=const.value.__class__.__name__
+ )
+
+ def visit_instance(self, instance):
+ # pylint: disable=protected-access
+ cls = instance._proxied
+ self.visit_classdef(cls)
+
+ # Exception instances have a particular class type
+ visit_exceptioninstance = visit_instance
+
+ def visit_classdef(self, cls):
+ if not utils.inherit_from_std_ex(cls) and utils.has_known_bases(cls):
+ if cls.newstyle:
+ self._checker.add_message("raising-non-exception", node=self._node)
+
+ def visit_tuple(self, _):
+ self._checker.add_message("raising-bad-type", node=self._node, args="tuple")
+
+ def visit_default(self, node):
+ name = getattr(node, "name", node.__class__.__name__)
+ self._checker.add_message("raising-bad-type", node=self._node, args=name)
+
+
+class ExceptionsChecker(checkers.BaseChecker):
+ """Exception related checks."""
+
+ __implements__ = interfaces.IAstroidChecker
+
+ name = "exceptions"
+ msgs = MSGS
+ priority = -4
+ options = (
+ (
+ "overgeneral-exceptions",
+ {
+ "default": OVERGENERAL_EXCEPTIONS,
+ "type": "csv",
+ "metavar": "<comma-separated class names>",
+ "help": "Exceptions that will emit a warning "
+ 'when being caught. Defaults to "%s".'
+ % (", ".join(OVERGENERAL_EXCEPTIONS),),
+ },
+ ),
+ )
+
+ def open(self):
+ self._builtin_exceptions = _builtin_exceptions()
+ super(ExceptionsChecker, self).open()
+
+ @utils.check_messages(
+ "misplaced-bare-raise",
+ "raising-bad-type",
+ "raising-non-exception",
+ "notimplemented-raised",
+ "bad-exception-context",
+ "raising-format-tuple",
+ )
+ def visit_raise(self, node):
+ if node.exc is None:
+ self._check_misplaced_bare_raise(node)
+ return
+
+ if node.cause:
+ self._check_bad_exception_context(node)
+
+ expr = node.exc
+ ExceptionRaiseRefVisitor(self, node).visit(expr)
+
+ try:
+ inferred_value = expr.inferred()[-1]
+ except astroid.InferenceError:
+ pass
+ else:
+ if inferred_value:
+ ExceptionRaiseLeafVisitor(self, node).visit(inferred_value)
+
+ def _check_misplaced_bare_raise(self, node):
+ # Filter out if it's present in __exit__.
+ scope = node.scope()
+ if (
+ isinstance(scope, astroid.FunctionDef)
+ and scope.is_method()
+ and scope.name == "__exit__"
+ ):
+ return
+
+ current = node
+ # Stop when a new scope is generated or when the raise
+ # statement is found inside a TryFinally.
+ ignores = (astroid.ExceptHandler, astroid.FunctionDef)
+ while current and not isinstance(current.parent, ignores):
+ current = current.parent
+
+ expected = (astroid.ExceptHandler,)
+ if not current or not isinstance(current.parent, expected):
+ self.add_message("misplaced-bare-raise", node=node)
+
+ def _check_bad_exception_context(self, node):
+ """Verify that the exception context is properly set.
+
+ An exception context can be only `None` or an exception.
+ """
+ cause = utils.safe_infer(node.cause)
+ if cause in (astroid.Uninferable, None):
+ return
+
+ if isinstance(cause, astroid.Const):
+ if cause.value is not None:
+ self.add_message("bad-exception-context", node=node)
+ elif not isinstance(cause, astroid.ClassDef) and not utils.inherit_from_std_ex(
+ cause
+ ):
+ self.add_message("bad-exception-context", node=node)
+
+ def _check_catching_non_exception(self, handler, exc, part):
+ if isinstance(exc, astroid.Tuple):
+ # Check if it is a tuple of exceptions.
+ inferred = [utils.safe_infer(elt) for elt in exc.elts]
+ if any(node is astroid.Uninferable for node in inferred):
+ # Don't emit if we don't know every component.
+ return
+ if all(
+ node
+ and (utils.inherit_from_std_ex(node) or not utils.has_known_bases(node))
+ for node in inferred
+ ):
+ return
+
+ if not isinstance(exc, astroid.ClassDef):
+ # Don't emit the warning if the inferred stmt
+ # is None, but the exception handler is something else,
+ # maybe it was redefined.
+ if isinstance(exc, astroid.Const) and exc.value is None:
+ if (
+ isinstance(handler.type, astroid.Const)
+ and handler.type.value is None
+ ) or handler.type.parent_of(exc):
+ # If the exception handler catches None or
+ # the exception component, which is None, is
+ # defined by the entire exception handler, then
+ # emit a warning.
+ self.add_message(
+ "catching-non-exception",
+ node=handler.type,
+ args=(part.as_string(),),
+ )
+ else:
+ self.add_message(
+ "catching-non-exception",
+ node=handler.type,
+ args=(part.as_string(),),
+ )
+ return
+
+ if (
+ not utils.inherit_from_std_ex(exc)
+ and exc.name not in self._builtin_exceptions
+ ):
+ if utils.has_known_bases(exc):
+ self.add_message(
+ "catching-non-exception", node=handler.type, args=(exc.name,)
+ )
+
+ def _check_try_except_raise(self, node):
+ def gather_exceptions_from_handler(
+ handler
+ ) -> typing.Optional[typing.List[NodeNG]]:
+ exceptions = [] # type: typing.List[NodeNG]
+ if handler.type:
+ exceptions_in_handler = utils.safe_infer(handler.type)
+ if isinstance(exceptions_in_handler, astroid.Tuple):
+ exceptions = list(
+ {
+ exception
+ for exception in exceptions_in_handler.elts
+ if isinstance(exception, astroid.Name)
+ }
+ )
+ elif exceptions_in_handler:
+ exceptions = [exceptions_in_handler]
+ else:
+ # Break when we cannot infer anything reliably.
+ return None
+ return exceptions
+
+ bare_raise = False
+ handler_having_bare_raise = None
+ excs_in_bare_handler = []
+ for handler in node.handlers:
+ if bare_raise:
+ # check that subsequent handler is not parent of handler which had bare raise.
+ # since utils.safe_infer can fail for bare except, check it before.
+ # also break early if bare except is followed by bare except.
+
+ excs_in_current_handler = gather_exceptions_from_handler(handler)
+
+ if not excs_in_current_handler:
+ bare_raise = False
+ break
+ if excs_in_bare_handler is None:
+ # It can be `None` when the inference failed
+ break
+
+ for exc_in_current_handler in excs_in_current_handler:
+ inferred_current = utils.safe_infer(exc_in_current_handler)
+ if any(
+ utils.is_subclass_of(
+ utils.safe_infer(exc_in_bare_handler), inferred_current
+ )
+ for exc_in_bare_handler in excs_in_bare_handler
+ ):
+ bare_raise = False
+ break
+
+ # `raise` as the first operator inside the except handler
+ if _is_raising([handler.body[0]]):
+ # flags when there is a bare raise
+ if handler.body[0].exc is None:
+ bare_raise = True
+ handler_having_bare_raise = handler
+ excs_in_bare_handler = gather_exceptions_from_handler(handler)
+ else:
+ if bare_raise:
+ self.add_message("try-except-raise", node=handler_having_bare_raise)
+
+ @utils.check_messages("wrong-exception-operation")
+ def visit_binop(self, node):
+ if isinstance(node.parent, astroid.ExceptHandler):
+ # except (V | A)
+ suggestion = "Did you mean '(%s, %s)' instead?" % (
+ node.left.as_string(),
+ node.right.as_string(),
+ )
+ self.add_message("wrong-exception-operation", node=node, args=(suggestion,))
+
+ @utils.check_messages("wrong-exception-operation")
+ def visit_compare(self, node):
+ if isinstance(node.parent, astroid.ExceptHandler):
+ # except (V < A)
+ suggestion = "Did you mean '(%s, %s)' instead?" % (
+ node.left.as_string(),
+ ", ".join(operand.as_string() for _, operand in node.ops),
+ )
+ self.add_message("wrong-exception-operation", node=node, args=(suggestion,))
+
+ @utils.check_messages(
+ "bare-except",
+ "broad-except",
+ "try-except-raise",
+ "binary-op-exception",
+ "bad-except-order",
+ "catching-non-exception",
+ "duplicate-except",
+ )
+ def visit_tryexcept(self, node):
+ """check for empty except"""
+ self._check_try_except_raise(node)
+ exceptions_classes = []
+ nb_handlers = len(node.handlers)
+ for index, handler in enumerate(node.handlers):
+ if handler.type is None:
+ if not _is_raising(handler.body):
+ self.add_message("bare-except", node=handler)
+
+ # check if an "except:" is followed by some other
+ # except
+ if index < (nb_handlers - 1):
+ msg = "empty except clause should always appear last"
+ self.add_message("bad-except-order", node=node, args=msg)
+
+ elif isinstance(handler.type, astroid.BoolOp):
+ self.add_message(
+ "binary-op-exception", node=handler, args=handler.type.op
+ )
+ else:
+ try:
+ excs = list(_annotated_unpack_infer(handler.type))
+ except astroid.InferenceError:
+ continue
+
+ for part, exc in excs:
+ if exc is astroid.Uninferable:
+ continue
+ if isinstance(exc, astroid.Instance) and utils.inherit_from_std_ex(
+ exc
+ ):
+ # pylint: disable=protected-access
+ exc = exc._proxied
+
+ self._check_catching_non_exception(handler, exc, part)
+
+ if not isinstance(exc, astroid.ClassDef):
+ continue
+
+ exc_ancestors = [
+ anc
+ for anc in exc.ancestors()
+ if isinstance(anc, astroid.ClassDef)
+ ]
+
+ for previous_exc in exceptions_classes:
+ if previous_exc in exc_ancestors:
+ msg = "%s is an ancestor class of %s" % (
+ previous_exc.name,
+ exc.name,
+ )
+ self.add_message(
+ "bad-except-order", node=handler.type, args=msg
+ )
+ if (
+ exc.name in self.config.overgeneral_exceptions
+ and exc.root().name == utils.EXCEPTIONS_MODULE
+ and not _is_raising(handler.body)
+ ):
+ self.add_message(
+ "broad-except", args=exc.name, node=handler.type
+ )
+
+ if exc in exceptions_classes:
+ self.add_message(
+ "duplicate-except", args=exc.name, node=handler.type
+ )
+
+ exceptions_classes += [exc for _, exc in excs]
+
+
+def register(linter):
+ """required method to auto register this checker"""
+ linter.register_checker(ExceptionsChecker(linter))