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