]>
Commit | Line | Data |
---|---|---|
05b3c97b BP |
1 | #! /usr/bin/python |
2 | ||
a44d74d7 | 3 | import os.path |
05b3c97b BP |
4 | import sys |
5 | import re | |
6 | ||
7 | macros = {} | |
8 | ||
9 | anyWarnings = False | |
10 | ||
11 | types = {} | |
12 | types['char'] = {"size": 1, "alignment": 1} | |
13 | types['uint8_t'] = {"size": 1, "alignment": 1} | |
fba47339 BP |
14 | types['ovs_be16'] = {"size": 2, "alignment": 2} |
15 | types['ovs_be32'] = {"size": 4, "alignment": 4} | |
16 | types['ovs_be64'] = {"size": 8, "alignment": 8} | |
c4617b3c | 17 | types['ovs_32aligned_be64'] = {"size": 8, "alignment": 4} |
05b3c97b BP |
18 | |
19 | token = None | |
20 | line = "" | |
21 | idRe = "[a-zA-Z_][a-zA-Z_0-9]*" | |
22 | tokenRe = "#?" + idRe + "|[0-9]+|." | |
1825f2ec | 23 | includeRe = re.compile(r'\s*#include\s+<(openflow/[^#]+)>') |
53931554 | 24 | includePath = '' |
05b3c97b BP |
25 | inComment = False |
26 | inDirective = False | |
53931554 | 27 | inputStack = [] |
05b3c97b BP |
28 | def getToken(): |
29 | global token | |
30 | global line | |
31 | global inComment | |
32 | global inDirective | |
53931554 BP |
33 | global inputFile |
34 | global fileName | |
05b3c97b BP |
35 | while True: |
36 | line = line.lstrip() | |
37 | if line != "": | |
38 | if line.startswith("/*"): | |
39 | inComment = True | |
40 | line = line[2:] | |
41 | elif inComment: | |
42 | commentEnd = line.find("*/") | |
43 | if commentEnd < 0: | |
44 | line = "" | |
45 | else: | |
46 | inComment = False | |
47 | line = line[commentEnd + 2:] | |
48 | else: | |
49 | match = re.match(tokenRe, line) | |
50 | token = match.group(0) | |
51 | line = line[len(token):] | |
52 | if token.startswith('#'): | |
53 | inDirective = True | |
54 | elif token in macros and not inDirective: | |
55 | line = macros[token] + line | |
56 | continue | |
57 | return True | |
58 | elif inDirective: | |
59 | token = "$" | |
60 | inDirective = False | |
61 | return True | |
62 | else: | |
63 | global lineNumber | |
53931554 BP |
64 | while True: |
65 | line = inputFile.readline() | |
05b3c97b | 66 | lineNumber += 1 |
53931554 BP |
67 | while line.endswith("\\\n"): |
68 | line = line[:-2] + inputFile.readline() | |
69 | lineNumber += 1 | |
70 | match = includeRe.match(line) | |
71 | if match: | |
72 | inputStack.append((fileName, inputFile, lineNumber)) | |
73 | inputFile = open(includePath + match.group(1)) | |
74 | lineNumber = 0 | |
75 | continue | |
76 | if line == "": | |
77 | if inputStack: | |
78 | fileName, inputFile, lineNumber = inputStack.pop() | |
79 | continue | |
80 | if token == None: | |
81 | fatal("unexpected end of input") | |
82 | token = None | |
83 | return False | |
84 | break | |
05b3c97b BP |
85 | |
86 | def fatal(msg): | |
87 | sys.stderr.write("%s:%d: error at \"%s\": %s\n" % (fileName, lineNumber, token, msg)) | |
88 | sys.exit(1) | |
89 | ||
90 | def warn(msg): | |
91 | global anyWarnings | |
92 | anyWarnings = True | |
93 | sys.stderr.write("%s:%d: warning: %s\n" % (fileName, lineNumber, msg)) | |
94 | ||
95 | def skipDirective(): | |
96 | getToken() | |
97 | while token != '$': | |
98 | getToken() | |
99 | ||
100 | def isId(s): | |
101 | return re.match(idRe + "$", s) != None | |
102 | ||
103 | def forceId(): | |
104 | if not isId(token): | |
105 | fatal("identifier expected") | |
106 | ||
107 | def forceInteger(): | |
108 | if not re.match('[0-9]+$', token): | |
109 | fatal("integer expected") | |
110 | ||
111 | def match(t): | |
112 | if token == t: | |
113 | getToken() | |
114 | return True | |
115 | else: | |
116 | return False | |
117 | ||
118 | def forceMatch(t): | |
119 | if not match(t): | |
120 | fatal("%s expected" % t) | |
121 | ||
122 | def parseTaggedName(): | |
123 | assert token in ('struct', 'union') | |
124 | name = token | |
125 | getToken() | |
126 | forceId() | |
127 | name = "%s %s" % (name, token) | |
128 | getToken() | |
129 | return name | |
130 | ||
131 | def parseTypeName(): | |
132 | if token in ('struct', 'union'): | |
133 | name = parseTaggedName() | |
134 | elif isId(token): | |
135 | name = token | |
136 | getToken() | |
137 | else: | |
138 | fatal("type name expected") | |
139 | ||
140 | if name in types: | |
141 | return name | |
142 | else: | |
143 | fatal("unknown type \"%s\"" % name) | |
144 | ||
145 | def parseStruct(): | |
146 | isStruct = token == 'struct' | |
147 | structName = parseTaggedName() | |
148 | if token == ";": | |
149 | return | |
150 | ||
151 | ofs = size = 0 | |
152 | alignment = 4 # ARM has minimum 32-bit alignment | |
153 | forceMatch('{') | |
154 | while not match('}'): | |
155 | typeName = parseTypeName() | |
156 | typeSize = types[typeName]['size'] | |
157 | typeAlignment = types[typeName]['alignment'] | |
158 | ||
159 | forceId() | |
160 | memberName = token | |
161 | getToken() | |
162 | ||
163 | if match('['): | |
164 | if token == ']': | |
165 | count = 0 | |
166 | else: | |
167 | forceInteger() | |
168 | count = int(token) | |
169 | getToken() | |
170 | forceMatch(']') | |
171 | else: | |
172 | count = 1 | |
173 | ||
174 | nBytes = typeSize * count | |
175 | if isStruct: | |
176 | if ofs % typeAlignment: | |
177 | shortage = typeAlignment - (ofs % typeAlignment) | |
178 | warn("%s member %s is %d bytes short of %d-byte alignment" | |
179 | % (structName, memberName, shortage, typeAlignment)) | |
180 | size += shortage | |
181 | ofs += shortage | |
182 | size += nBytes | |
183 | ofs += nBytes | |
184 | else: | |
185 | if nBytes > size: | |
186 | size = nBytes | |
187 | if typeAlignment > alignment: | |
188 | alignment = typeAlignment | |
189 | ||
190 | forceMatch(';') | |
191 | if size % alignment: | |
192 | shortage = alignment - (size % alignment) | |
31a9e63f | 193 | if (structName == "struct ofp10_packet_in" and |
05b3c97b BP |
194 | shortage == 2 and |
195 | memberName == 'data' and | |
196 | count == 0): | |
197 | # This is intentional | |
198 | pass | |
199 | else: | |
200 | warn("%s needs %d bytes of tail padding" % (structName, shortage)) | |
201 | size += shortage | |
202 | types[structName] = {"size": size, "alignment": alignment} | |
f937ccc2 | 203 | return structName |
05b3c97b BP |
204 | |
205 | def checkStructs(): | |
206 | if len(sys.argv) < 2: | |
207 | sys.stderr.write("at least one non-option argument required; " | |
208 | "use --help for help") | |
209 | sys.exit(1) | |
210 | ||
211 | if '--help' in sys.argv: | |
a44d74d7 | 212 | argv0 = os.path.basename(sys.argv[0]) |
05b3c97b BP |
213 | print '''\ |
214 | %(argv0)s, for checking struct and struct member alignment | |
53931554 | 215 | usage: %(argv0)s -Ipath HEADER [HEADER]... |
05b3c97b BP |
216 | |
217 | This program reads the header files specified on the command line and | |
218 | verifies that all struct members are aligned on natural boundaries | |
219 | without any need for the compiler to add additional padding. It also | |
220 | verifies that each struct's size is a multiple of 32 bits (because | |
221 | some ABIs for ARM require all structs to be a multiple of 32 bits), or | |
222 | 64 bits if the struct has any 64-bit members, again without the | |
223 | compiler adding additional padding. Finally, it checks struct size | |
224 | assertions using OFP_ASSERT. | |
225 | ||
53931554 BP |
226 | This program is specialized for reading Open vSwitch's OpenFlow header |
227 | files. It will not work on arbitrary header files without extensions.\ | |
228 | ''' % {"argv0": argv0} | |
05b3c97b BP |
229 | sys.exit(0) |
230 | ||
231 | global fileName | |
232 | for fileName in sys.argv[1:]: | |
53931554 BP |
233 | if fileName.startswith('-I'): |
234 | global includePath | |
235 | includePath = fileName[2:] | |
236 | if not includePath.endswith('/'): | |
237 | includePath += '/' | |
238 | continue | |
05b3c97b BP |
239 | global inputFile |
240 | global lineNumber | |
241 | inputFile = open(fileName) | |
242 | lineNumber = 0 | |
f937ccc2 | 243 | lastStruct = None |
05b3c97b BP |
244 | while getToken(): |
245 | if token in ("#ifdef", "#ifndef", "#include", | |
246 | "#endif", "#elif", "#else"): | |
247 | skipDirective() | |
248 | elif token == "#define": | |
249 | getToken() | |
250 | name = token | |
251 | if line.startswith('('): | |
252 | skipDirective() | |
253 | else: | |
254 | definition = "" | |
255 | getToken() | |
256 | while token != '$': | |
257 | definition += token | |
258 | getToken() | |
259 | macros[name] = definition | |
260 | elif token == "enum": | |
261 | while token != ';': | |
262 | getToken() | |
263 | elif token in ('struct', 'union'): | |
f937ccc2 | 264 | lastStruct = parseStruct() |
05b3c97b BP |
265 | elif match('OFP_ASSERT') or match('BOOST_STATIC_ASSERT'): |
266 | forceMatch('(') | |
267 | forceMatch('sizeof') | |
268 | forceMatch('(') | |
269 | typeName = parseTypeName() | |
f937ccc2 BP |
270 | if typeName != lastStruct: |
271 | warn("checking size of %s but %s was most recently defined" | |
272 | % (typeName, lastStruct)) | |
05b3c97b BP |
273 | forceMatch(')') |
274 | forceMatch('=') | |
275 | forceMatch('=') | |
276 | forceInteger() | |
277 | size = int(token) | |
278 | getToken() | |
279 | forceMatch(')') | |
280 | if types[typeName]['size'] != size: | |
281 | warn("%s is %d bytes long but declared as %d" % ( | |
282 | typeName, types[typeName]['size'], size)) | |
283 | else: | |
284 | fatal("parse error") | |
285 | inputFile.close() | |
286 | if anyWarnings: | |
287 | sys.exit(1) | |
288 | ||
289 | if __name__ == '__main__': | |
290 | checkStructs() |