summaryrefslogtreecommitdiff
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
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>
-rw-r--r--doc/whatsnew/fragments/519.false_negative3
-rw-r--r--pylint/checkers/base/basic_checker.py43
-rw-r--r--tests/functional/u/unreachable.py49
-rw-r--r--tests/functional/u/unreachable.txt15
4 files changed, 97 insertions, 13 deletions
diff --git a/doc/whatsnew/fragments/519.false_negative b/doc/whatsnew/fragments/519.false_negative
new file mode 100644
index 000000000..7c8a0f45c
--- /dev/null
+++ b/doc/whatsnew/fragments/519.false_negative
@@ -0,0 +1,3 @@
+Code following a call to ``quit``, ``exit``, ``sys.exit`` or ``os._exit`` will be marked as `unreachable`.
+
+Refs #519
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,
diff --git a/tests/functional/u/unreachable.py b/tests/functional/u/unreachable.py
index 0bb35b20d..0211a6136 100644
--- a/tests/functional/u/unreachable.py
+++ b/tests/functional/u/unreachable.py
@@ -1,5 +1,9 @@
-# pylint: disable=missing-docstring, broad-exception-raised
+# pylint: disable=missing-docstring, broad-exception-raised, too-few-public-methods, redefined-outer-name
+# pylint: disable=consider-using-sys-exit, protected-access
+import os
+import signal
+import sys
def func1():
return 1
@@ -32,3 +36,46 @@ def func6():
return
yield
print("unreachable") # [unreachable]
+
+def func7():
+ sys.exit(1)
+ var = 2 + 2 # [unreachable]
+ print(var)
+
+def func8():
+ signal.signal(signal.SIGTERM, lambda *args: sys.exit(0))
+ try:
+ print(1)
+ except KeyboardInterrupt:
+ pass
+
+class FalseExit:
+ def exit(self, number):
+ print(f"False positive this is not sys.exit({number})")
+
+def func_false_exit():
+ sys = FalseExit()
+ sys.exit(1)
+ var = 2 + 2
+ print(var)
+
+def func9():
+ os._exit()
+ var = 2 + 2 # [unreachable]
+ print(var)
+
+def func10():
+ exit()
+ var = 2 + 2 # [unreachable]
+ print(var)
+
+def func11():
+ quit()
+ var = 2 + 2 # [unreachable]
+ print(var)
+
+incognito_function = sys.exit
+def func12():
+ incognito_function()
+ var = 2 + 2 # [unreachable]
+ print(var)
diff --git a/tests/functional/u/unreachable.txt b/tests/functional/u/unreachable.txt
index 491912d99..82f9797aa 100644
--- a/tests/functional/u/unreachable.txt
+++ b/tests/functional/u/unreachable.txt
@@ -1,5 +1,10 @@
-unreachable:6:4:6:24:func1:Unreachable code:HIGH
-unreachable:11:8:11:28:func2:Unreachable code:HIGH
-unreachable:17:8:17:28:func3:Unreachable code:HIGH
-unreachable:21:4:21:16:func4:Unreachable code:HIGH
-unreachable:34:4:34:24:func6:Unreachable code:HIGH
+unreachable:10:4:10:24:func1:Unreachable code:HIGH
+unreachable:15:8:15:28:func2:Unreachable code:HIGH
+unreachable:21:8:21:28:func3:Unreachable code:HIGH
+unreachable:25:4:25:16:func4:Unreachable code:HIGH
+unreachable:38:4:38:24:func6:Unreachable code:HIGH
+unreachable:42:4:42:15:func7:Unreachable code:INFERENCE
+unreachable:64:4:64:15:func9:Unreachable code:INFERENCE
+unreachable:69:4:69:15:func10:Unreachable code:INFERENCE
+unreachable:74:4:74:15:func11:Unreachable code:INFERENCE
+unreachable:80:4:80:15:func12:Unreachable code:INFERENCE