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