1 | 1 patch for repository http://tahoe-lafs.org/source/tahoe/trunk: |
---|
2 | |
---|
3 | Fri Oct 7 08:41:21 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'.) This is a rewritten version that reports much fewer false positives, by determining captured variables more accurately. fixes #1555 |
---|
5 | |
---|
6 | New 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'.) This is a rewritten version that reports much fewer false positives, by determining captured variables more accurately. fixes #1555 |
---|
9 | david-sarah@jacaranda.org**20111007074121 |
---|
10 | Ignore-this: 51318e9678d132c374ea557ab955e79e |
---|
11 | ] { |
---|
12 | hunk ./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 |
---|
21 | hunk ./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 |
---|
32 | addfile ./misc/coding_tools/check-miscaptures.py |
---|
33 | hunk ./misc/coding_tools/check-miscaptures.py 1 |
---|
34 | +#! /usr/bin/python |
---|
35 | + |
---|
36 | +import os, sys, compiler, traceback |
---|
37 | +from compiler.ast import Node, For, AssName, Name, Lambda, Function |
---|
38 | + |
---|
39 | + |
---|
40 | +def check_source(source): |
---|
41 | + return check_thing(compiler.parse, source) |
---|
42 | + |
---|
43 | +def check_file(path): |
---|
44 | + return check_thing(compiler.parseFile, path) |
---|
45 | + |
---|
46 | +def check_thing(parser, thing): |
---|
47 | + try: |
---|
48 | + ast = parser(thing) |
---|
49 | + except SyntaxError, e: |
---|
50 | + return [e] |
---|
51 | + else: |
---|
52 | + results = [] |
---|
53 | + check_ast(ast, results) |
---|
54 | + return results |
---|
55 | + |
---|
56 | +def check_ast(ast, results): |
---|
57 | + """Check a node outside a 'for' loop.""" |
---|
58 | + if isinstance(ast, For): |
---|
59 | + check_for(ast, results) |
---|
60 | + else: |
---|
61 | + for child in ast.getChildNodes(): |
---|
62 | + if isinstance(ast, Node): |
---|
63 | + check_ast(child, results) |
---|
64 | + |
---|
65 | +def check_for(ast, results): |
---|
66 | + """Check a particular outer 'for' loop.""" |
---|
67 | + |
---|
68 | + declared = {} # maps name to lineno of declaration |
---|
69 | + nested = set() |
---|
70 | + collect_declared_and_nested(ast, declared, nested) |
---|
71 | + |
---|
72 | + # For each nested function... |
---|
73 | + for funcnode in nested: |
---|
74 | + # Check for captured variables in this function. |
---|
75 | + captured = set() |
---|
76 | + collect_captured(funcnode, declared, captured) |
---|
77 | + for name in captured: |
---|
78 | + # We want to report the outermost capturing function |
---|
79 | + # (since that is where the workaround will need to be |
---|
80 | + # added), and the variable declaration. Just one report |
---|
81 | + # per capturing function per variable will do. |
---|
82 | + results.append(make_result(funcnode, name, declared[name])) |
---|
83 | + |
---|
84 | + # Check each node in the function body in case it |
---|
85 | + # contains another 'for' loop. |
---|
86 | + childnodes = funcnode.getChildNodes()[len(funcnode.defaults):] |
---|
87 | + for child in childnodes: |
---|
88 | + check_ast(funcnode, results) |
---|
89 | + |
---|
90 | +def collect_declared_and_nested(ast, declared, nested): |
---|
91 | + """ |
---|
92 | + Collect the names declared in this 'for' loop, not including |
---|
93 | + names declared in nested functions. Also collect the nodes of |
---|
94 | + functions that are nested one level deep. |
---|
95 | + """ |
---|
96 | + if isinstance(ast, AssName): |
---|
97 | + declared[ast.name] = ast.lineno |
---|
98 | + else: |
---|
99 | + childnodes = ast.getChildNodes() |
---|
100 | + if isinstance(ast, (Lambda, Function)): |
---|
101 | + nested.add(ast) |
---|
102 | + |
---|
103 | + # The default argument expressions are "outside" the |
---|
104 | + # function, even though they are children of the |
---|
105 | + # Lambda or Function node. |
---|
106 | + childnodes = childnodes[:len(ast.defaults)] |
---|
107 | + |
---|
108 | + for child in childnodes: |
---|
109 | + if isinstance(ast, Node): |
---|
110 | + collect_declared_and_nested(child, declared, nested) |
---|
111 | + |
---|
112 | +def collect_captured(ast, declared, captured): |
---|
113 | + """Collect any captured variables that are also in declared.""" |
---|
114 | + if isinstance(ast, Name): |
---|
115 | + if ast.name in declared: |
---|
116 | + captured.add(ast.name) |
---|
117 | + else: |
---|
118 | + childnodes = ast.getChildNodes() |
---|
119 | + |
---|
120 | + if isinstance(ast, (Lambda, Function)): |
---|
121 | + # Formal parameters of the function are excluded from |
---|
122 | + # captures we care about in subnodes of the function body. |
---|
123 | + declared = declared.copy() |
---|
124 | + for argname in ast.argnames: |
---|
125 | + if argname in declared: |
---|
126 | + del declared[argname] |
---|
127 | + |
---|
128 | + for child in childnodes[len(ast.defaults):]: |
---|
129 | + collect_captured(child, declared, captured) |
---|
130 | + |
---|
131 | + # The default argument expressions are "outside" the |
---|
132 | + # function, even though they are children of the |
---|
133 | + # Lambda or Function node. |
---|
134 | + childnodes = childnodes[:len(ast.defaults)] |
---|
135 | + |
---|
136 | + for child in childnodes: |
---|
137 | + if isinstance(ast, Node): |
---|
138 | + collect_captured(child, declared, captured) |
---|
139 | + |
---|
140 | + |
---|
141 | +def make_result(funcnode, var_name, var_lineno): |
---|
142 | + if hasattr(funcnode, 'name'): |
---|
143 | + func_name = 'function %r' % (funcnode.name,) |
---|
144 | + else: |
---|
145 | + func_name = '<lambda>' |
---|
146 | + return (funcnode.lineno, func_name, var_name, var_lineno) |
---|
147 | + |
---|
148 | +def report(out, path, results): |
---|
149 | + for r in results: |
---|
150 | + if isinstance(r, SyntaxError): |
---|
151 | + print >>out, path + (" NOT ANALYSED due to syntax error: %s" % r) |
---|
152 | + else: |
---|
153 | + print >>out, path + (":%r %s captures %r declared at line %d" % r) |
---|
154 | + |
---|
155 | +def check(sources, out): |
---|
156 | + class Counts: |
---|
157 | + n = 0 |
---|
158 | + processed_files = 0 |
---|
159 | + suspect_files = 0 |
---|
160 | + counts = Counts() |
---|
161 | + |
---|
162 | + def _process(path): |
---|
163 | + results = check_file(path) |
---|
164 | + report(out, path, results) |
---|
165 | + counts.n += len(results) |
---|
166 | + counts.processed_files += 1 |
---|
167 | + if len(results) > 0: |
---|
168 | + counts.suspect_files += 1 |
---|
169 | + |
---|
170 | + for source in sources: |
---|
171 | + print >>out, "Checking %s..." % (source,) |
---|
172 | + if os.path.isfile(source): |
---|
173 | + _process(source) |
---|
174 | + else: |
---|
175 | + for (dirpath, dirnames, filenames) in os.walk(source): |
---|
176 | + for fn in filenames: |
---|
177 | + (basename, ext) = os.path.splitext(fn) |
---|
178 | + if ext == '.py': |
---|
179 | + _process(os.path.join(dirpath, fn)) |
---|
180 | + |
---|
181 | + print >>out, ("%d suspiciously captured variables in %d out of %d files" |
---|
182 | + % (counts.n, counts.suspect_files, counts.processed_files)) |
---|
183 | + return counts.n |
---|
184 | + |
---|
185 | + |
---|
186 | +sources = ['src'] |
---|
187 | +if len(sys.argv) > 1: |
---|
188 | + sources = sys.argv[1:] |
---|
189 | +if check(sources, sys.stderr) > 0: |
---|
190 | + sys.exit(1) |
---|
191 | + |
---|
192 | + |
---|
193 | +# TODO: self-tests |
---|
194 | } |
---|
195 | |
---|
196 | Context: |
---|
197 | |
---|
198 | [no_network.py: Clean up whitespace around code changed by previous patch. |
---|
199 | david-sarah@jacaranda.org**20111004010407 |
---|
200 | Ignore-this: 647ec8a9346dca1a41212ab250619b72 |
---|
201 | ] |
---|
202 | [no_network.py: Fix potential bugs in some tests due to capture of slots in for loops. |
---|
203 | david-sarah@jacaranda.org**20111004010231 |
---|
204 | Ignore-this: 9c496877613a3befd54979e5de6e63d2 |
---|
205 | ] |
---|
206 | [docs: fix the rst formatting of COPYING.TGPPL.rst |
---|
207 | zooko@zooko.com**20111003043333 |
---|
208 | Ignore-this: c5fbc83f4a3db81a0c95b27053c463c5 |
---|
209 | Now it renders correctly both on trac and with rst2html --verbose from docutils v0.8.1. |
---|
210 | ] |
---|
211 | [MDMF: remove extension fields from caps, tolerate arbitrary ones. Fixes #1526 |
---|
212 | Brian Warner <warner@lothar.com>**20111001233553 |
---|
213 | Ignore-this: 335e1690aef1146a2c0b8d8c18c1cb21 |
---|
214 | |
---|
215 | The filecaps used to be produced with hints for 'k' and segsize, but they |
---|
216 | weren't actually used, and doing so had the potential to limit how we change |
---|
217 | those filecaps in the future. Also the parsing code had some problems dealing |
---|
218 | with other numbers of extensions. Removing the existing fields and making the |
---|
219 | parser tolerate (and ignore) extra ones makes MDMF more future-proof. |
---|
220 | ] |
---|
221 | [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. |
---|
222 | david-sarah@jacaranda.org**20110927225336 |
---|
223 | Ignore-this: 6f1ad68004194cc9cea55ace3745e4af |
---|
224 | ] |
---|
225 | [docs/configuration.rst: add section about the types of node, and clarify when setting web.port enables web-API service. fixes #1444 |
---|
226 | zooko@zooko.com**20110926203801 |
---|
227 | Ignore-this: ab94d470c68e720101a7ff3c207a719e |
---|
228 | ] |
---|
229 | [TAG allmydata-tahoe-1.9.0a2 |
---|
230 | warner@lothar.com**20110925234811 |
---|
231 | Ignore-this: e9649c58f9c9017a7d55008938dba64f |
---|
232 | ] |
---|
233 | Patch bundle hash: |
---|
234 | 98a38d22095e3489ff7773a45cc873e21893eeb5 |
---|