Skip to content

Commit ef6f438

Browse files
jdufresnemyint
authored andcommitted
Add check for unused exception binding in except: block (#293)
Fixes #301
1 parent da2dd4a commit ef6f438

File tree

5 files changed

+80
-31
lines changed

5 files changed

+80
-31
lines changed

NEWS.txt

+3
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
UNRELEASED
2+
- Check for unused exception binding in `except:` block
3+
14
1.6.0 (2017-08-03)
25
- Process function scope variable annotations for used names
36
- Find Python files without extensions by their shebang

pyflakes/checker.py

+37-19
Original file line numberDiff line numberDiff line change
@@ -1322,34 +1322,52 @@ def EXCEPTHANDLER(self, node):
13221322
self.handleChildren(node)
13231323
return
13241324

1325-
# 3.x: the name of the exception, which is not a Name node, but
1326-
# a simple string, creates a local that is only bound within the scope
1327-
# of the except: block.
1325+
# If the name already exists in the scope, modify state of existing
1326+
# binding.
1327+
if node.name in self.scope:
1328+
self.handleNodeStore(node)
1329+
1330+
# 3.x: the name of the exception, which is not a Name node, but a
1331+
# simple string, creates a local that is only bound within the scope of
1332+
# the except: block. As such, temporarily remove the existing binding
1333+
# to more accurately determine if the name is used in the except:
1334+
# block.
13281335

13291336
for scope in self.scopeStack[::-1]:
1330-
if node.name in scope:
1331-
is_name_previously_defined = True
1337+
try:
1338+
binding = scope.pop(node.name)
1339+
except KeyError:
1340+
pass
1341+
else:
1342+
prev_definition = scope, binding
13321343
break
13331344
else:
1334-
is_name_previously_defined = False
1345+
prev_definition = None
13351346

13361347
self.handleNodeStore(node)
13371348
self.handleChildren(node)
1338-
if not is_name_previously_defined:
1339-
# See discussion on https://github.com/PyCQA/pyflakes/pull/59
13401349

1341-
# We're removing the local name since it's being unbound
1342-
# after leaving the except: block and it's always unbound
1343-
# if the except: block is never entered. This will cause an
1344-
# "undefined name" error raised if the checked code tries to
1345-
# use the name afterwards.
1346-
#
1347-
# Unless it's been removed already. Then do nothing.
1350+
# See discussion on https://github.com/PyCQA/pyflakes/pull/59
13481351

1349-
try:
1350-
del self.scope[node.name]
1351-
except KeyError:
1352-
pass
1352+
# We're removing the local name since it's being unbound after leaving
1353+
# the except: block and it's always unbound if the except: block is
1354+
# never entered. This will cause an "undefined name" error raised if
1355+
# the checked code tries to use the name afterwards.
1356+
#
1357+
# Unless it's been removed already. Then do nothing.
1358+
1359+
try:
1360+
binding = self.scope.pop(node.name)
1361+
except KeyError:
1362+
pass
1363+
else:
1364+
if not binding.used:
1365+
self.report(messages.UnusedVariable, node, node.name)
1366+
1367+
# Restore.
1368+
if prev_definition:
1369+
scope, binding = prev_definition
1370+
scope[node.name] = binding
13531371

13541372
def ANNASSIGN(self, node):
13551373
if node.value:

pyflakes/test/test_imports.py

+5-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
21
from sys import version_info
32

43
from pyflakes import messages as m
@@ -571,11 +570,15 @@ def test_usedInExcept(self):
571570

572571
def test_redefinedByExcept(self):
573572
as_exc = ', ' if version_info < (2, 6) else ' as '
573+
expected = [m.RedefinedWhileUnused]
574+
if version_info >= (3,):
575+
# The exc variable is unused inside the exception handler.
576+
expected.append(m.UnusedVariable)
574577
self.flakes('''
575578
import fu
576579
try: pass
577580
except Exception%sfu: pass
578-
''' % as_exc, m.RedefinedWhileUnused)
581+
''' % as_exc, *expected)
579582

580583
def test_usedInRaise(self):
581584
self.flakes('''

pyflakes/test/test_other.py

+16
Original file line numberDiff line numberDiff line change
@@ -1597,6 +1597,22 @@ def download_review():
15971597
except Exception%se: e
15981598
''' % as_exc)
15991599

1600+
@skipIf(version_info < (3,),
1601+
"In Python 2 exception names stay bound after the exception handler")
1602+
def test_exceptionUnusedInExcept(self):
1603+
self.flakes('''
1604+
try: pass
1605+
except Exception as e: pass
1606+
''', m.UnusedVariable)
1607+
1608+
def test_exceptionUnusedInExceptInFunction(self):
1609+
as_exc = ', ' if version_info < (2, 6) else ' as '
1610+
self.flakes('''
1611+
def download_review():
1612+
try: pass
1613+
except Exception%se: pass
1614+
''' % as_exc, m.UnusedVariable)
1615+
16001616
def test_exceptWithoutNameInFunction(self):
16011617
"""
16021618
Don't issue false warning when an unnamed exception is used.

pyflakes/test/test_undefined_names.py

+19-10
Original file line numberDiff line numberDiff line change
@@ -25,15 +25,16 @@ def test_undefinedInListComp(self):
2525
@skipIf(version_info < (3,),
2626
'in Python 2 exception names stay bound after the except: block')
2727
def test_undefinedExceptionName(self):
28-
"""Exception names can't be used after the except: block."""
28+
"""Exception names can't be used after the except: block.
29+
30+
The exc variable is unused inside the exception handler."""
2931
self.flakes('''
3032
try:
3133
raise ValueError('ve')
3234
except ValueError as exc:
3335
pass
3436
exc
35-
''',
36-
m.UndefinedName)
37+
''', m.UndefinedName, m.UnusedVariable)
3738

3839
def test_namesDeclaredInExceptBlocks(self):
3940
"""Locals declared in except: blocks can be used after the block.
@@ -71,22 +72,24 @@ def test_undefinedExceptionNameObscuringLocalVariable2(self):
7172
"""Exception names are unbound after the `except:` block.
7273
7374
Last line will raise UnboundLocalError on Python 3 but would print out
74-
've' on Python 2."""
75+
've' on Python 2. The exc variable is unused inside the exception
76+
handler."""
7577
self.flakes('''
7678
try:
7779
raise ValueError('ve')
7880
except ValueError as exc:
7981
pass
8082
print(exc)
8183
exc = 'Original value'
82-
''',
83-
m.UndefinedName)
84+
''', m.UndefinedName, m.UnusedVariable)
8485

8586
def test_undefinedExceptionNameObscuringLocalVariableFalsePositive1(self):
8687
"""Exception names obscure locals, can't be used after. Unless.
8788
8889
Last line will never raise UnboundLocalError because it's only
8990
entered if no exception was raised."""
91+
# The exc variable is unused inside the exception handler.
92+
expected = [] if version_info < (3,) else [m.UnusedVariable]
9093
self.flakes('''
9194
exc = 'Original value'
9295
try:
@@ -95,7 +98,7 @@ def test_undefinedExceptionNameObscuringLocalVariableFalsePositive1(self):
9598
print('exception logged')
9699
raise
97100
exc
98-
''')
101+
''', *expected)
99102

100103
def test_delExceptionInExcept(self):
101104
"""The exception name can be deleted in the except: block."""
@@ -111,6 +114,8 @@ def test_undefinedExceptionNameObscuringLocalVariableFalsePositive2(self):
111114
112115
Last line will never raise UnboundLocalError because `error` is
113116
only falsy if the `except:` block has not been entered."""
117+
# The exc variable is unused inside the exception handler.
118+
expected = [] if version_info < (3,) else [m.UnusedVariable]
114119
self.flakes('''
115120
exc = 'Original value'
116121
error = None
@@ -122,7 +127,7 @@ def test_undefinedExceptionNameObscuringLocalVariableFalsePositive2(self):
122127
print(error)
123128
else:
124129
exc
125-
''')
130+
''', *expected)
126131

127132
@skip('error reporting disabled due to false positives below')
128133
def test_undefinedExceptionNameObscuringGlobalVariable(self):
@@ -168,6 +173,8 @@ def test_undefinedExceptionNameObscuringGlobalVariableFalsePositive1(self):
168173
169174
Last line will never raise NameError because it's only entered
170175
if no exception was raised."""
176+
# The exc variable is unused inside the exception handler.
177+
expected = [] if version_info < (3,) else [m.UnusedVariable]
171178
self.flakes('''
172179
exc = 'Original value'
173180
def func():
@@ -178,13 +185,15 @@ def func():
178185
print('exception logged')
179186
raise
180187
exc
181-
''')
188+
''', *expected)
182189

183190
def test_undefinedExceptionNameObscuringGlobalVariableFalsePositive2(self):
184191
"""Exception names obscure globals, can't be used after. Unless.
185192
186193
Last line will never raise NameError because `error` is only
187194
falsy if the `except:` block has not been entered."""
195+
# The exc variable is unused inside the exception handler.
196+
expected = [] if version_info < (3,) else [m.UnusedVariable]
188197
self.flakes('''
189198
exc = 'Original value'
190199
def func():
@@ -198,7 +207,7 @@ def func():
198207
print(error)
199208
else:
200209
exc
201-
''')
210+
''', *expected)
202211

203212
def test_functionsNeedGlobalScope(self):
204213
self.flakes('''

0 commit comments

Comments
 (0)