stp: port_no counter is off by one
[sliver-openvswitch.git] / build-aux / check-structs
1 #! /usr/bin/python
2
3 import os.path
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}
14 types['ovs_be16'] = {"size": 2, "alignment": 2}
15 types['ovs_be32'] = {"size": 4, "alignment": 4}
16 types['ovs_be64'] = {"size": 8, "alignment": 8}
17 types['ovs_32aligned_be64'] = {"size": 8, "alignment": 4}
18
19 token = None
20 line = ""
21 idRe = "[a-zA-Z_][a-zA-Z_0-9]*"
22 tokenRe = "#?" + idRe + "|[0-9]+|."
23 includeRe = re.compile(r'\s*#include\s+"(openflow/[^#]+)"')
24 includePath = ''
25 inComment = False
26 inDirective = False
27 inputStack = []
28 def getToken():
29     global token
30     global line
31     global inComment
32     global inDirective
33     global inputFile
34     global fileName
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
64             while True:
65                 line = inputFile.readline()
66                 lineNumber += 1
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
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)
193         if (structName == "struct ofp_packet_in" and
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}
203     return structName
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:
212         argv0 = os.path.basename(sys.argv[0])
213         print '''\
214 %(argv0)s, for checking struct and struct member alignment
215 usage: %(argv0)s -Ipath HEADER [HEADER]...
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
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}
229         sys.exit(0)
230
231     global fileName
232     for fileName in sys.argv[1:]:
233         if fileName.startswith('-I'):
234             global includePath
235             includePath = fileName[2:]
236             if not includePath.endswith('/'):
237                 includePath += '/'
238             continue
239         global inputFile
240         global lineNumber
241         inputFile = open(fileName)
242         lineNumber = 0
243         lastStruct = None
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'):
264                 lastStruct = parseStruct()
265             elif match('OFP_ASSERT') or match('BOOST_STATIC_ASSERT'):
266                 forceMatch('(')
267                 forceMatch('sizeof')
268                 forceMatch('(')
269                 typeName = parseTypeName()
270                 if typeName != lastStruct:
271                     warn("checking size of %s but %s was most recently defined"
272                          % (typeName, lastStruct))
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()