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