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