openflow: Use types and accessors for half-aligned 64-bit fields.
[cascardo/ovs.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 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
191 def checkStructs():
192     if len(sys.argv) < 2:
193         sys.stderr.write("at least one non-option argument required; "
194                          "use --help for help")
195         sys.exit(1)
196
197     if '--help' in sys.argv:
198         argv0 = os.path.basename(sys.argv[0])
199         print '''\
200 %(argv0)s, for checking struct and struct member alignment
201 usage: %(argv0)s HEADER [HEADER]...
202
203 This program reads the header files specified on the command line and
204 verifies that all struct members are aligned on natural boundaries
205 without any need for the compiler to add additional padding.  It also
206 verifies that each struct's size is a multiple of 32 bits (because
207 some ABIs for ARM require all structs to be a multiple of 32 bits), or
208 64 bits if the struct has any 64-bit members, again without the
209 compiler adding additional padding.  Finally, it checks struct size
210 assertions using OFP_ASSERT.
211
212 Header files are read in the order specified.  #include directives are
213 not processed, so specify them in dependency order.
214
215 This program is specialized for reading include/openflow/openflow.h
216 and include/openflow/nicira-ext.h.  It will not work on arbitrary
217 header files without extensions.''' % {"argv0": argv0}
218         sys.exit(0)
219
220     global fileName
221     for fileName in sys.argv[1:]:
222         global inputFile
223         global lineNumber
224         inputFile = open(fileName)
225         lineNumber = 0
226         while getToken():
227             if token in ("#ifdef", "#ifndef", "#include",
228                          "#endif", "#elif", "#else"):
229                 skipDirective()
230             elif token == "#define":
231                 getToken()
232                 name = token
233                 if line.startswith('('):
234                     skipDirective()
235                 else:
236                     definition = ""
237                     getToken()
238                     while token != '$':
239                         definition += token
240                         getToken()
241                     macros[name] = definition
242             elif token == "enum":
243                 while token != ';':
244                     getToken()
245             elif token in ('struct', 'union'):
246                 parseStruct()
247             elif match('OFP_ASSERT') or match('BOOST_STATIC_ASSERT'):
248                 forceMatch('(')
249                 forceMatch('sizeof')
250                 forceMatch('(')
251                 typeName = parseTypeName()
252                 forceMatch(')')
253                 forceMatch('=')
254                 forceMatch('=')
255                 forceInteger()
256                 size = int(token)
257                 getToken()
258                 forceMatch(')')
259                 if types[typeName]['size'] != size:
260                     warn("%s is %d bytes long but declared as %d" % (
261                             typeName, types[typeName]['size'], size))
262             else:
263                 fatal("parse error")
264         inputFile.close()
265     if anyWarnings:
266         sys.exit(1)
267
268 if __name__ == '__main__':
269     checkStructs()