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