Skip to content

Commit 238001d

Browse files
Zac-HDcooperlees
authored andcommitted
Fix error on attributes-of-attributes in except (...): clauses (#109)
* Fix #108, error on nested Attributes * Add fuzz-tests to check for crashes * Require attrs>=19.2.0 (this is only for hypothesmith - so could be moved to .travis.yml)
1 parent 3731237 commit 238001d

File tree

5 files changed

+44
-10
lines changed

5 files changed

+44
-10
lines changed

.travis.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ matrix:
1414
- python: nightly
1515

1616
install:
17-
- pip install --upgrade pip setuptools coverage black
17+
- pip install --upgrade pip setuptools coverage black hypothesmith
1818
- pip install -e .
1919

2020
script:

bugbear.py

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,15 @@ def should_warn(self, code):
110110
return False
111111

112112

113+
def _to_name_str(node):
114+
# Turn Name and Attribute nodes to strings, e.g "ValueError" or
115+
# "pkg.mod.error", handling any depth of attribute accesses.
116+
if isinstance(node, ast.Name):
117+
return node.id
118+
assert isinstance(node, ast.Attribute)
119+
return _to_name_str(node.value) + "." + node.attr
120+
121+
113122
@attr.s
114123
class BugBearVisitor(ast.NodeVisitor):
115124
filename = attr.ib()
@@ -141,13 +150,7 @@ def visit_ExceptHandler(self, node):
141150
B001(node.lineno, node.col_offset, vars=("bare `except:`",))
142151
)
143152
elif isinstance(node.type, ast.Tuple):
144-
names = []
145-
for e in node.type.elts:
146-
if isinstance(e, ast.Name):
147-
names.append(e.id)
148-
else:
149-
assert isinstance(e, ast.Attribute)
150-
names.append("{}.{}".format(e.value.id, e.attr))
153+
names = [_to_name_str(e) for e in node.type.elts]
151154
as_ = " as " + node.name if node.name is not None else ""
152155
if len(names) == 0:
153156
vs = ("`except (){}:`".format(as_),)

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838
py_modules=["bugbear"],
3939
zip_safe=False,
4040
python_requires=">=3.6",
41-
install_requires=["flake8 >= 3.0.0", "attrs"],
41+
install_requires=["flake8 >= 3.0.0", "attrs>=19.2.0"],
4242
test_suite="tests.test_bugbear",
4343
classifiers=[
4444
"Development Status :: 5 - Production/Stable",

tests/b013.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,9 @@
2828
except (re.error,):
2929
# pointless use of tuple with dotted attribute
3030
pass
31+
32+
try:
33+
pass
34+
except (a.b.c.d, b.c.d):
35+
# attribute of attribute, etc.
36+
pass

tests/test_bugbear.py

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,14 @@
1+
import ast
2+
import os
13
from pathlib import Path
4+
import site
25
import subprocess
36
import unittest
47

5-
from bugbear import BugBearChecker
8+
from hypothesis import given
9+
from hypothesmith import from_grammar
10+
11+
from bugbear import BugBearChecker, BugBearVisitor
612
from bugbear import (
713
B001,
814
B002,
@@ -262,5 +268,24 @@ def test_selfclean_test_bugbear(self):
262268
self.assertEqual(proc.stderr, b"")
263269

264270

271+
class TestFuzz(unittest.TestCase):
272+
@given(from_grammar().map(ast.parse))
273+
def test_does_not_crash_on_any_valid_code(self, syntax_tree):
274+
# Given any syntatically-valid source code, flake8-bugbear should
275+
# not crash. This tests doesn't check that we do the *right* thing,
276+
# just that we don't crash on valid-if-poorly-styled code!
277+
BugBearVisitor(filename="<string>", lines=[]).visit(syntax_tree)
278+
279+
def test_does_not_crash_on_site_code(self):
280+
# Because the generator isn't perfect, we'll also test on all the code
281+
# we can easily find in our current Python environment - this includes
282+
# the standard library, and all installed packages.
283+
for base in sorted(set(site.PREFIXES)):
284+
for dirname, _, files in os.walk(base):
285+
for f in files:
286+
if f.endswith(".py"):
287+
BugBearChecker(filename=str(Path(dirname) / f))
288+
289+
265290
if __name__ == "__main__":
266291
unittest.main()

0 commit comments

Comments
 (0)