summaryrefslogtreecommitdiff
path: root/pylint/checkers/base
diff options
context:
space:
mode:
authorDani Alcala <112832187+clavedeluna@users.noreply.github.com>2022-11-13 19:35:30 -0300
committerGitHub <noreply@github.com>2022-11-13 23:35:30 +0100
commitbed42badfe6be6317e6dc6406433218287ded3b9 (patch)
tree8d1cdc8e59b8f74c6f74678d561bc799b424bfac /pylint/checkers/base
parent435a5a1d8e542d1240b8a66985feb8950fb6d739 (diff)
downloadpylint-git-bed42badfe6be6317e6dc6406433218287ded3b9.tar.gz
Detect unreachable code after sys.exit, quit, exit, and os._exit (#7520)
Co-authored-by: Pierre Sassoulas <pierre.sassoulas@gmail.com> Co-authored-by: Mark Byrne <31762852+mbyrnepr2@users.noreply.github.com>
Diffstat (limited to 'pylint/checkers/base')
-rw-r--r--pylint/checkers/base/basic_checker.py43
1 files changed, 36 insertions, 7 deletions
diff --git a/pylint/checkers/base/basic_checker.py b/pylint/checkers/base/basic_checker.py
index 6ea7197b3..077fc3751 100644
--- a/pylint/checkers/base/basic_checker.py
+++ b/pylint/checkers/base/basic_checker.py
@@ -17,7 +17,7 @@ from astroid import nodes
from pylint import utils as lint_utils
from pylint.checkers import BaseChecker, utils
-from pylint.interfaces import HIGH, INFERENCE
+from pylint.interfaces import HIGH, INFERENCE, Confidence
from pylint.reporters.ureports import nodes as reporter_nodes
from pylint.utils import LinterStats
@@ -657,13 +657,38 @@ class BasicChecker(_BasicChecker):
):
self.add_message("misplaced-format-function", node=call_node)
+ @staticmethod
+ def _is_terminating_func(node: nodes.Call) -> bool:
+ """Detect call to exit(), quit(), os._exit(), or sys.exit()."""
+ if (
+ not isinstance(node.func, nodes.Attribute)
+ and not (isinstance(node.func, nodes.Name))
+ or isinstance(node.parent, nodes.Lambda)
+ ):
+ return False
+
+ qnames = {"_sitebuiltins.Quitter", "sys.exit", "posix._exit", "nt._exit"}
+
+ try:
+ for inferred in node.func.infer():
+ if hasattr(inferred, "qname") and inferred.qname() in qnames:
+ return True
+ except (StopIteration, astroid.InferenceError):
+ pass
+
+ return False
+
@utils.only_required_for_messages(
- "eval-used", "exec-used", "bad-reversed-sequence", "misplaced-format-function"
+ "eval-used",
+ "exec-used",
+ "bad-reversed-sequence",
+ "misplaced-format-function",
+ "unreachable",
)
def visit_call(self, node: nodes.Call) -> None:
- """Visit a Call node -> check if this is not a disallowed builtin
- call and check for * or ** use.
- """
+ """Visit a Call node."""
+ if self._is_terminating_func(node):
+ self._check_unreachable(node, confidence=INFERENCE)
self._check_misplaced_format_function(node)
if isinstance(node.func, nodes.Name):
name = node.func.name
@@ -731,7 +756,9 @@ class BasicChecker(_BasicChecker):
self._tryfinallys.pop()
def _check_unreachable(
- self, node: nodes.Return | nodes.Continue | nodes.Break | nodes.Raise
+ self,
+ node: nodes.Return | nodes.Continue | nodes.Break | nodes.Raise | nodes.Call,
+ confidence: Confidence = HIGH,
) -> None:
"""Check unreachable code."""
unreachable_statement = node.next_sibling()
@@ -746,7 +773,9 @@ class BasicChecker(_BasicChecker):
unreachable_statement = unreachable_statement.next_sibling()
if unreachable_statement is None:
return
- self.add_message("unreachable", node=unreachable_statement, confidence=HIGH)
+ self.add_message(
+ "unreachable", node=unreachable_statement, confidence=confidence
+ )
def _check_not_in_finally(
self,