Ticket #1555: check-miscaptures.darcs.patch

File check-miscaptures.darcs.patch, 6.3 KB (added by davidsarah, at 2011-10-07T02:19:10Z)

Add misc/coding_tools/check-miscaptures.py to detect incorrect captures of variables declared in a for loop, and a 'make check-miscaptures' Makefile target to run it. (It is also run by 'make code-checks'.) refs #1555

Line 
11 patch for repository http://tahoe-lafs.org/source/tahoe/trunk:
2
3Fri Oct  7 03:15:29 BST 2011  david-sarah@jacaranda.org
4  * Add misc/coding_tools/check-miscaptures.py to detect incorrect captures of variables declared in a for loop, and a 'make check-miscaptures' Makefile target to run it. (It is also run by 'make code-checks'.) refs #1555
5
6New patches:
7
8[Add misc/coding_tools/check-miscaptures.py to detect incorrect captures of variables declared in a for loop, and a 'make check-miscaptures' Makefile target to run it. (It is also run by 'make code-checks'.) refs #1555
9david-sarah@jacaranda.org**20111007021529
10 Ignore-this: 212f4f6039af508716e09b8952ae7ab
11] {
12hunk ./Makefile 124
13        false
14 endif
15 
16-code-checks: build version-and-path check-interfaces -find-trailing-spaces -check-umids pyflakes
17+code-checks: build version-and-path check-interfaces check-miscaptures -find-trailing-spaces -check-umids pyflakes
18 
19 version-and-path:
20        $(TAHOE) --version-and-path
21hunk ./Makefile 133
22        $(TAHOE) @misc/coding_tools/check-interfaces.py 2>&1 |tee violations.txt
23        @echo
24 
25+check-miscaptures:
26+       $(PYTHON) misc/coding_tools/check-miscaptures.py $(SOURCES) 2>&1 |tee miscaptures.txt
27+       @echo
28+
29 pyflakes:
30        $(PYTHON) -OOu `which pyflakes` $(SOURCES) |sort |uniq
31        @echo
32addfile ./misc/coding_tools/check-miscaptures.py
33hunk ./misc/coding_tools/check-miscaptures.py 1
34+#! /usr/bin/python
35+
36+import os, sys, compiler
37+from compiler.ast import Node, For, AssName, Name, Lambda, Function
38+
39+def check_file(filename):
40+    results = []
41+    check_ast(compiler.parseFile(filename), results)
42+    return results
43+
44+def check_source(source):
45+    results = []
46+    check_ast(compiler.parse(source), results)
47+    return results
48+
49+def check_ast(ast, results):
50+    if isinstance(ast, For):
51+        check_for(ast, results)
52+    else:
53+        for child in ast.getChildNodes():
54+            if isinstance(ast, Node):
55+                check_ast(child, results)
56+
57+def check_for(ast, results):
58+    declared = set()
59+    captured = set()
60+    collect_declared(ast, declared)
61+    collect_captured(ast, captured, set(), False)
62+    for name in captured & declared:
63+        results.append((name, ast.lineno))
64+
65+def collect_declared(ast, declared):
66+    if isinstance(ast, AssName):
67+        declared.add(ast.name)
68+    else:
69+        for child in ast.getChildNodes():
70+            if isinstance(ast, Node):
71+                collect_declared(child, declared)
72+
73+def collect_captured(ast, captured, excluded, in_func):
74+    if isinstance(ast, Name):
75+        if in_func and ast.name not in excluded:
76+            captured.add(ast.name)
77+    elif isinstance(ast, (Lambda, Function)):
78+        # Formal parameters of the function are not captures
79+        newly_excluded = excluded.copy()
80+        for argname in ast.argnames:
81+            newly_excluded.add(argname)
82+
83+        childnodes = ast.getChildNodes()
84+        # ... and neither are variable uses in default argument
85+        # expressions, provided we weren't already in a function.
86+        if not in_func:
87+            childnodes = childnodes[len(ast.defaults):]
88+
89+        for child in childnodes:
90+            if isinstance(ast, Node):
91+                collect_captured(child, captured, newly_excluded, True)
92+    else:
93+        for child in ast.getChildNodes():
94+            if isinstance(ast, Node):
95+                collect_captured(child, captured, excluded, in_func)
96+
97+def report(path, results, out):
98+    for (name, lineno) in results:
99+        print >>out, "%s:%d %r" % (path, lineno, name)
100+
101+
102+def check(sources, out):
103+    class Counts:
104+        n = 0
105+        processed_files = 0
106+        suspect_files = 0
107+    counts = Counts()
108+
109+    def _process(path):
110+        results = check_file(path)
111+        report(path, results, out)
112+        counts.n += len(results)
113+        counts.processed_files += 1
114+        if len(results) > 0:
115+            counts.suspect_files += 1
116+
117+    for source in sources:
118+        print >>out, "Checking %s..." % (source,)
119+        if os.path.isfile(source):
120+            _process(source)
121+        else:
122+            for (dirpath, dirnames, filenames) in os.walk(source):
123+                for fn in filenames:
124+                    (basename, ext) = os.path.splitext(fn)
125+                    if ext == '.py':
126+                        _process(os.path.join(dirpath, fn))
127+
128+    print >>out, ("%d suspiciously captured variables in %d out of %d files"
129+                  % (counts.n, counts.suspect_files, counts.processed_files))
130+
131+
132+sources = ['src']
133+if len(sys.argv) > 1:
134+    sources = sys.argv[1:]
135+check(sources, sys.stderr)
136+
137+
138+# TODO: self-tests
139}
140
141Context:
142
143[no_network.py: Clean up whitespace around code changed by previous patch.
144david-sarah@jacaranda.org**20111004010407
145 Ignore-this: 647ec8a9346dca1a41212ab250619b72
146]
147[no_network.py: Fix potential bugs in some tests due to capture of slots in for loops.
148david-sarah@jacaranda.org**20111004010231
149 Ignore-this: 9c496877613a3befd54979e5de6e63d2
150]
151[docs: fix the rst formatting of COPYING.TGPPL.rst
152zooko@zooko.com**20111003043333
153 Ignore-this: c5fbc83f4a3db81a0c95b27053c463c5
154 Now it renders correctly both on trac and with rst2html --verbose from docutils v0.8.1.
155]
156[MDMF: remove extension fields from caps, tolerate arbitrary ones. Fixes #1526
157Brian Warner <warner@lothar.com>**20111001233553
158 Ignore-this: 335e1690aef1146a2c0b8d8c18c1cb21
159 
160 The filecaps used to be produced with hints for 'k' and segsize, but they
161 weren't actually used, and doing so had the potential to limit how we change
162 those filecaps in the future. Also the parsing code had some problems dealing
163 with other numbers of extensions. Removing the existing fields and making the
164 parser tolerate (and ignore) extra ones makes MDMF more future-proof.
165]
166[test/test_runner.py: BinTahoe.test_path has rare nondeterministic failures; this patch probably fixes a problem where the actual cause of failure is masked by a string conversion error.
167david-sarah@jacaranda.org**20110927225336
168 Ignore-this: 6f1ad68004194cc9cea55ace3745e4af
169]
170[docs/configuration.rst: add section about the types of node, and clarify when setting web.port enables web-API service. fixes #1444
171zooko@zooko.com**20110926203801
172 Ignore-this: ab94d470c68e720101a7ff3c207a719e
173]
174[TAG allmydata-tahoe-1.9.0a2
175warner@lothar.com**20110925234811
176 Ignore-this: e9649c58f9c9017a7d55008938dba64f
177]
178Patch bundle hash:
17983fda893e4e4585dd65a793d1d45225f7b8177ee