Skip to content

Commit 166d0b5

Browse files
author
mount.sarah
committed
Added static checker to determine whether a process correctly uses its
documented readsets and writesets.
1 parent 6927bbd commit 166d0b5

15 files changed

+475
-488
lines changed

ChangeLog

+4
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
8 May 2010
2+
Introduced documented readsets and writesets. Added lint-like
3+
static checker to determine whether readsets and writesets are
4+
correctly documented.
15
2 May 2010
26
Introduced CSPServer processes and a @forever decorator to easily
37
create them.

MANIFEST.in

+2-4
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
include README ChangeLog LICENSE
22
include scripts/python-csp
3+
include scripts/cspdb
34

4-
recursive-include applications/ *.py
5-
recursive-include jythonsetup/ *.py
65
recursive-include csp/ *.py
7-
recursive-include bsp/ *.py
8-
recursive-include reactive/ *.py
6+
recursive-include exstatic/ *.py

csp/lint/channels.py

+167-45
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#!/usr/bin/env python
22

33
"""
4+
Check that every process in a file has correct readsets and writesets.
45
56
Copyright (C) Sarah Mount, 2010.
67
@@ -22,93 +23,214 @@
2223
import compiler.ast as ast
2324
import compiler.visitor as visitor
2425

25-
import csp.tracer.icode as icode
26+
import exstatic.cspwarnings
2627

27-
from csp.tracer.stack import Stack
2828

2929
__author__ = 'Sarah Mount <s.mount@wlv.ac.uk>'
3030
__date__ = 'April 2010'
3131

3232

3333
class ChannelChecker(visitor.ASTVisitor):
34+
"""Check that documented readsets and writesets are correct
35+
w.r.t. code.
36+
"""
3437

35-
def __init__(self):
38+
def __init__(self, filename):
3639
visitor.ASTVisitor.__init__(self)
37-
self.model = '\n'
38-
self.processes = Stack()
40+
self.filename = filename
41+
self.current_process = ''
42+
self.current_process_lineno = 0
43+
self.writeset = {}
44+
self.readset = {}
45+
self.readset_lineno = 0
46+
self.writeset_lineno = 0
3947
return
4048

4149
def extract_sets(self, doc):
50+
"""Extract the readset and writeset from function
51+
documentation.
52+
"""
4253
readset = []
4354
writeset = []
44-
if doc is None:
45-
return readset, writeset
46-
for line in doc.split('\n'):
47-
words = line.strip().split('=')
48-
if words is not None:
49-
if words[0].strip() == 'readset':
50-
chans = words[1].strip().split(',')
51-
readset = filter(lambda y: y is not '',
52-
map(lambda x: x.strip(), chans))
53-
elif words[0].strip() == 'writeset':
54-
chans = words[1].strip().split(',')
55-
writeset = filter(lambda y: y is not '',
56-
map(lambda x: x.strip(), chans))
57-
else:
58-
continue
59-
return readset, writeset
55+
has_readset = False
56+
has_writeset = False
57+
lineno = 0
58+
if doc is not None:
59+
for line in doc.split('\n'):
60+
lineno += 1
61+
words = line.strip().split('=')
62+
if words is not None:
63+
if words[0].strip() == 'readset':
64+
has_readset = True
65+
self.readset_lineno += lineno
66+
chans = words[1].strip().split(',')
67+
readset = filter(lambda y: y is not '',
68+
map(lambda x: x.strip(), chans))
69+
elif words[0].strip() == 'writeset':
70+
has_writeset = True
71+
self.writeset_lineno += lineno
72+
chans = words[1].strip().split(',')
73+
writeset = filter(lambda y: y is not '',
74+
map(lambda x: x.strip(), chans))
75+
76+
# 'W002':'No readset given in documentation.'
77+
if not has_readset:
78+
exstatic.cspwarnings.create_error(self.filename,
79+
self.readset_lineno,
80+
self.current_process,
81+
'W002')
82+
83+
# 'W003':'No writeset given in documentation.'
84+
if not has_writeset:
85+
exstatic.cspwarnings.create_error(self.filename,
86+
self.writeset_lineno,
87+
self.current_process,
88+
'W003')
89+
90+
return set(readset), set(writeset)
6091

6192
def is_process(self, decorators):
93+
"""Determine whether or not the current function is a CSP
94+
process.
95+
"""
6296
for decorator in decorators:
63-
if (decorator.name == 'process' or
64-
decorator.name == 'forever'):
97+
if (decorator.name == 'process' or decorator.name == 'forever'):
6598
return True
6699
return False
67100

101+
def check_sets(self, readset, writeset):
102+
"""Check that the documented readset and writeset of the
103+
current function match the code inside the function
104+
definition.
105+
106+
@param readset the documented readset of the current process
107+
@param writeset the documented writeset of the current process
108+
"""
109+
# 'W001':'Channel in both readset and writeset.'
110+
if len(readset.intersection(writeset)) > 0:
111+
exstatic.cspwarnings.create_error(self.filename,
112+
self.readset_lineno,
113+
self.current_process,
114+
'W001')
115+
116+
# 'E004':'Channel appears in documented readset but not read
117+
# from in function body.'
118+
diff = set(self.readset.values()).difference(readset)
119+
for channel in diff:
120+
exstatic.cspwarnings.create_error(self.filename,
121+
self.readset_lineno,
122+
self.current_process,
123+
'E004')
124+
125+
# 'E005':'Channel is read from in function body but does not
126+
# appear in documented readset'
127+
diff = set(readset).difference(self.readset.values())
128+
for channel in diff:
129+
for key in self.readset:
130+
exstatic.cspwarnings.create_error(self.filename,
131+
key,
132+
self.current_process,
133+
'E005')
134+
135+
# 'E006':'Channel appears in documented writeset but not
136+
# written to in function body.'
137+
diff = set(self.writeset.values()).difference(writeset)
138+
for channel in diff:
139+
exstatic.cspwarnings.create_error(self.filename,
140+
self.writeset_lineno,
141+
self.current_process,
142+
'E006')
143+
144+
# 'E007':'Channel is written to in function body but does not
145+
# appear in documented writeset'
146+
diff = set(writeset).difference(self.writeset.values())
147+
for channel in diff:
148+
for key in self.writeset:
149+
exstatic.cspwarnings.create_error(self.filename,
150+
key,
151+
self.current_process,
152+
'E007')
153+
154+
return
155+
68156
def visitFunction(self, node):
69-
# print "VISIT FUNCTION!", dir(node)
70-
# print node.code.__class__
157+
"""Visit function definition.
158+
"""
159+
160+
# If this function definition is not a CSP process, ignore it.
71161
if (node.decorators is None or
72-
self.is_process(node.decorators) is None):
162+
self.is_process(node.decorators) is None):
73163
return
74-
print 'Function %s is a CSP process' % node.name
164+
165+
# Store useful information about this process.
166+
self.current_process = node.name
167+
self.current_process_lineno = node.lineno
168+
self.readset_lineno, self.writeset_lineno = node.lineno, node.lineno
75169
readset, writeset = self.extract_sets(node.doc)
76-
print node.name, 'has readset:', readset, len(readset)
77-
print node.name, 'has writeset:', writeset, len(writeset)
170+
171+
# 'I001':'Function is a CSP process or server process',
172+
exstatic.cspwarnings.create_error(self.filename,
173+
self.current_process_lineno,
174+
self.current_process,
175+
'I001')
176+
177+
# 'E002':'Channel in readset is not a formal parameter to this
178+
# process.',
78179
for channel in readset:
79180
if not channel in node.argnames:
80-
print 'ERROR line %i: Channel %s in readset is not a formal parameter to this process' % (node.lineno, channel)
181+
exstatic.cspwarnings.create_error(self.filename,
182+
self.readset_lineno,
183+
node.name,
184+
'E002')
185+
186+
# 'E003':'Channel in writeset is not a formal parameter to
187+
# this process.',
81188
for channel in writeset:
82189
if not channel in node.argnames:
83-
print 'ERROR line %i: Channel %s in writeset is not a formal parameter to this process' % (node.lineno, channel)
190+
exstatic.cspwarnings.create_error(self.filename,
191+
self.writeset_lineno,
192+
node.name,
193+
'E003')
84194

85-
# Ensure that we visit every line of code in this function.
195+
# Ensure that we visit every statement inside this fuction.
86196
for stmt in node.code:
87197
self.visit(stmt)
88198

199+
# Check the documented readset and writeset against actual
200+
# method calls within the function.
201+
self.check_sets(readset, writeset)
202+
203+
# Remove information held about this function.
204+
self.current_process = ''
205+
self.current_process_lineno = 0
206+
self.writeset = {}
207+
self.readset = {}
208+
return
209+
89210
def visitCallFunc(self, node):
211+
"""Visit function call.
212+
213+
TODO: Deal with Alt and Barrier types.
214+
"""
90215
callee = node.node
91216
if isinstance(callee, ast.Getattr):
92217
if not isinstance(callee.expr, ast.Getattr):
93-
print 'Visiting anonymous function call %s' % callee.expr.name
94-
# print node
95-
# print dir(node)
96-
print callee.expr.name + '.' + callee.attrname
97-
98-
# def visitAssign(self, node):
99-
# print 'VISIT ASSIGN'
100-
# if isinstance(node.expr, ast.CallFunc):
101-
# print 'Expr details:', node.expr.node.__dict__
102-
# if node.expr.node.name == 'Channel':
103-
# self.channels.push(cspmodel.Channel(node.nodes[0].name))
218+
# Catch all calls to channel read().
219+
if callee.attrname == 'read':
220+
self.readset[callee.lineno] = callee.expr.name
221+
# Catch all calls to channel write()
222+
elif callee.attrname == 'write':
223+
self.writeset[callee.lineno] = callee.expr.name
224+
return
104225

105226

106227
if __name__ == '__main__':
107228
import sys
108-
109-
lint = ChannelChecker()
229+
230+
lint = ChannelChecker(sys.argv[1])
110231
compiler.walk(compiler.parseFile(sys.argv[1]),
111232
lint,
112233
walker=lint,
113234
verbose=5)
114235

236+
exstatic.cspwarnings.print_errors(excluded=[])

csp/lint/lint.py

+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
#!/usr/bin/env python
2+
3+
"""
4+
Combined linting for python-csp.
5+
6+
Copyright (C) Sarah Mount, 2010.
7+
8+
This program is free software; you can redistribute it and/or
9+
modify it under the terms of the GNU General Public License
10+
as published by the Free Software Foundation; either version 2
11+
of the License, or (at your option) any later version.
12+
13+
This program is distributed in the hope that it will be useful,
14+
but WITHOUT ANY WARRANTY; without even the implied warranty of
15+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16+
GNU General Public License for more details.
17+
18+
You should have rceeived a copy of the GNU General Public License
19+
along with this program. If not, see <http://www.gnu.org/licenses/>.
20+
"""
21+
22+
import compiler
23+
import csp.lint.channels
24+
25+
import exstatic.cspwarnings
26+
27+
checkers = [csp.lint.channels.ChannelChecker]
28+
29+
def run(filename, excluded=[]):
30+
exstatic.cspwarnings.reset_errors()
31+
for checker in checkers:
32+
lint = checker(filename)
33+
compiler.walk(compiler.parseFile(filename),
34+
lint,
35+
walker=lint,
36+
verbose=5)
37+
exstatic.cspwarnings.print_errors(excluded=excluded)
38+
return
39+
40+
if __name__ == '__main__':
41+
import sys
42+
if sys.argv > 1:
43+
run(sys.argv[1])
44+
sys.exit()

csp/tracer/tracer.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@
4747
import csp.cspprocess
4848

4949
from distutils import sysconfig
50-
#from stack import Stack
50+
#from exstatic.stack import Stack
5151

5252
from contextlib import contextmanager
5353

exstatic/__init__.py

Whitespace-only changes.

0 commit comments

Comments
 (0)