|
OLD | NEW |
---|---|
(Empty) | |
1 #!/usr/bin/python | |
2 | |
3 # Copyright (c) 2010 The Chromium Authors. All rights reserved. | |
4 # Use of this source code is governed by a BSD-style license that can be | |
5 # found in the LICENSE file. | |
6 | |
7 """This script should be run manually on occasion to make sure all PPAPI types | |
8 have appropriate size checking. | |
David Springer
2010/12/13 21:23:08
I might be good to add this to a wiki somewhere (P
| |
9 | |
10 """ | |
11 | |
12 import optparse | |
13 import os | |
14 import subprocess | |
15 import sys | |
16 | |
17 | |
18 # The string that the PrintNamesAndSizes plugin uses to indicate a type is or | |
David Springer
2010/12/13 21:23:08
"a type is or contains" -- ?
| |
19 # contains a pointer. | |
20 HAS_POINTER_STRING = "HasPointer" | |
21 # These are types that don't include a pointer but nonetheless have | |
22 # architecture-dependent size. They all are ultimately typedefs to 'long' or | |
23 # 'unsigned long'. If there get to be too many types that use 'long' or | |
24 # 'unsigned long', we can add detection of them to the plugin to automate this. | |
David Springer
2010/12/13 21:23:08
Uh-oh - this looks like a potential for human omis
| |
25 ARCH_DEPENDENT_TYPES = set(["khronos_intptr_t", | |
26 "khronos_uintptr_t", | |
27 "khronos_ssize_t", | |
28 "khronos_usize_t", | |
29 "GLintptr", | |
30 "GLsizeiptr" | |
31 ]) | |
32 | |
33 | |
34 | |
35 class SourceLocation: | |
36 | |
37 """A class representing the source location of a definiton.""" | |
38 | |
39 def __init__(self, filename="", start_line=-1, end_line=-1): | |
40 self.filename = os.path.normpath(filename) | |
41 self.start_line = start_line | |
42 self.end_line = end_line | |
43 | |
44 | |
45 | |
46 class TypeInfo: | |
47 | |
48 """A class representing information about a C++ type.""" | |
David Springer
2010/12/13 21:23:08
Maybe add: "|info_string| has the following format
| |
49 | |
50 def __init__(self, info_string, target): | |
51 [self.kind, self.name, self.size, has_pointer_string, source_file, | |
52 start_line, end_line] = info_string.split(',') | |
53 self.target = target | |
54 self.parsed_line = info_string | |
55 # Note that Clang counts line numbers from 1, but we want to count from 0. | |
56 self.source_location = SourceLocation(source_file, | |
57 int(start_line)-1, | |
58 int(end_line)-1) | |
59 # If the type is one of our known special cases, mark it as architecture- | |
60 # dependent. | |
61 if self.name in ARCH_DEPENDENT_TYPES: | |
62 self.arch_dependent = True | |
63 # Otherwise, its size can be architecture dependent if it contains one or | |
64 # more pointers (or is a pointer). | |
65 else: | |
66 self.arch_dependent = (has_pointer_string == HAS_POINTER_STRING) | |
67 self.target = target | |
68 self.parsed_line = info_string | |
69 | |
70 | |
71 | |
72 class FilePatch: | |
73 | |
74 """A class representing a set of line-by-line changes to a particular file. | |
75 None | |
76 of the changes are applied until Apply is called. All line numbers are | |
David Springer
2010/12/13 21:23:08
Nit: spurious line-break.
| |
77 counted from 0. | |
78 """ | |
79 | |
80 def __init__(self, filename): | |
81 self.filename = filename | |
82 self.linenums_to_delete = set() | |
83 # A dictionary from line number to an array of strings to be inserted at | |
84 # that line number. | |
85 self.lines_to_add = {} | |
86 | |
87 def Delete(self, start_line, end_line): | |
88 """Make the patch delete the lines [start_line, end_line).""" | |
David Springer
2010/12/13 21:23:08
Maybe state explicitly "delete lines starting with
| |
89 self.linenums_to_delete |= set(range(start_line, end_line)) | |
90 | |
91 def Add(self, text, line_number): | |
92 """Add the given text before the text on the given line number.""" | |
93 if line_number in self.lines_to_add: | |
94 self.lines_to_add[line_number].append(text) | |
95 else: | |
96 self.lines_to_add[line_number] = [text] | |
97 | |
98 def Apply(self): | |
99 """Apply the patch by writing it to self.filename""" | |
100 # Read the lines of the existing file in to a list. | |
101 sourcefile = open(self.filename, "r") | |
102 file_lines = sourcefile.readlines() | |
103 sourcefile.close() | |
104 # Now apply the patch. Our strategy is to keep the array at the same size, | |
105 # and just edit strings in the file_lines list as necessary. When we delete | |
106 # lines, we just blank the line and keep it in the list. When we add lines, | |
107 # we just prepend the added source code to the start of the existing line at | |
108 # that line number. This way, all the line numbers we cached from calls to | |
109 # Add and Delete remain valid list indices, and we don't have to worry about | |
110 # maintaining any offsets. Each element of file_lines at the end may | |
111 # contain any number of lines (0 or more) delimited by carriage returns. | |
112 for linenum_to_delete in self.linenums_to_delete: | |
113 file_lines[linenum_to_delete] = ""; | |
114 for linenum, sourcelines in self.lines_to_add.items(): | |
115 # Sort the lines we're adding so we get relatively consistent results. | |
116 sourcelines.sort() | |
117 # Prepend the new lines. When we output | |
118 file_lines[linenum] = "".join(sourcelines) + file_lines[linenum] | |
119 newsource = open(self.filename, "w") | |
120 for line in file_lines: | |
121 newsource.write(line) | |
122 newsource.close() | |
123 | |
124 | |
125 def CheckAndInsert(typeinfo, typeinfo_map): | |
126 """Check if a TypeInfo exists already in the given map with the same name. If | |
127 so, make sure the size is consistent. | |
128 - If the name exists but the sizes do not match, print a message and | |
129 exit with non-zero exit code. | |
130 - If the name exists and the sizes match, do nothing. | |
131 - If the name does not exist, insert the typeinfo in to the map. | |
132 | |
133 """ | |
134 # If the type is unnamed, ignore it. | |
135 if typeinfo.name == "": | |
136 return | |
137 # If the size is 0, ignore it. | |
138 elif int(typeinfo.size) == 0: | |
139 return | |
140 # If the type is not defined under ppapi, ignore it. | |
141 elif typeinfo.source_location.filename.find("ppapi") == -1: | |
142 return | |
143 # If the type is defined under GLES2, ignore it. | |
144 elif typeinfo.source_location.filename.find("GLES2") > -1: | |
145 return | |
146 # If the type is an interface (by convention, starts with PPP_ or PPB_), | |
147 # ignore it. | |
148 elif (typeinfo.name[:4] == "PPP_") or (typeinfo.name[:4] == "PPB_"): | |
149 return | |
150 elif typeinfo.name in typeinfo_map: | |
151 if typeinfo.size != typeinfo_map[typeinfo.name].size: | |
152 print "Error: '" + typeinfo.name + "' is", \ | |
153 typeinfo_map[typeinfo.name].size, \ | |
154 "bytes on target '" + typeinfo_map[typeinfo.name].target + \ | |
155 "', but", typeinfo.size, "on target '" + typeinfo.target + "'" | |
156 print typeinfo_map[typeinfo.name].parsed_line | |
157 print typeinfo.parsed_line | |
158 sys.exit(1) | |
159 else: | |
160 # It's already in the map and the sizes match. | |
161 pass | |
162 else: | |
163 typeinfo_map[typeinfo.name] = typeinfo | |
164 | |
165 | |
166 def ProcessTarget(clang_command, target, arch_types, ind_types): | |
167 """Run clang using the given clang_command for the given target string. Parse | |
168 the output to create TypeInfos for each discovered type. Insert each type in | |
169 to the appropriate dictionary. For each type that has architecture-dependent | |
170 size, insert it in to arch_types. Types with independent size go in to | |
171 ind_types. If the type already exists in the appropriate map, make sure that | |
172 the size matches what's already in the map. If not, the script terminates | |
173 with an error message. | |
174 """ | |
175 p = subprocess.Popen(clang_command + " -triple " + target, | |
176 shell=True, | |
177 stdout=subprocess.PIPE) | |
178 lines = p.communicate()[0].split() | |
179 for line in lines: | |
180 typeinfo = TypeInfo(line, target) | |
181 # Put types which have architecture-specific size in to arch_types. All | |
182 # other types are 'architecture-independent' and get put in ind_types. | |
183 # in the appropraite dictionary. | |
184 if typeinfo.arch_dependent: | |
185 CheckAndInsert(typeinfo, arch_types) | |
186 else: | |
187 CheckAndInsert(typeinfo, ind_types) | |
188 | |
189 | |
190 def ToAssertionCode(typeinfo): | |
191 """Convert the TypeInfo to an appropriate C compile assertion. | |
192 If it's a struct (Record in Clang terminology), we want a line like this: | |
193 PP_COMPILE_ASSERT_STRUCT_SIZE_IN_BYTES(<name>, <size>);\n | |
194 Enums: | |
195 PP_COMPILE_ASSERT_ENUM_SIZE_IN_BYTES(<name>, <size>);\n | |
196 Typedefs: | |
197 PP_COMPILE_ASSERT_SIZE_IN_BYTES(<name>, <size>);\n | |
198 | |
199 """ | |
200 line = "PP_COMPILE_ASSERT_" | |
201 if typeinfo.kind == "Enum": | |
202 line += "ENUM_" | |
203 elif typeinfo.kind == "Record": | |
204 line += "STRUCT_" | |
205 line += "SIZE_IN_BYTES(" | |
206 line += typeinfo.name | |
207 line += ", " | |
208 line += typeinfo.size | |
209 line += ");\n" | |
210 return line | |
211 | |
212 | |
213 def IsMacroDefinedName(typename): | |
214 """Return true iff the given typename came from a PPAPI compile assertion.""" | |
215 return typename.find("PP_Dummy_Struct_For_") == 0 | |
216 | |
217 | |
218 COPYRIGHT_STRING_C = \ | |
219 """/* Copyright (c) 2010 The Chromium Authors. All rights reserved. | |
220 * Use of this source code is governed by a BSD-style license that can be | |
221 * found in the LICENSE file. | |
222 * | |
223 * This file has compile assertions for the sizes of types that are dependent | |
224 * on the architecture for which they are compiled (i.e., 32-bit vs 64-bit). | |
225 */ | |
226 | |
227 """ | |
228 | |
229 | |
230 def WriteArchSpecificCode(types, root, filename): | |
231 """Write a header file that contains a compile-time assertion for the size of | |
232 each of the given typeinfos, in to a file named filename rooted at root. | |
233 """ | |
234 assertion_lines = [ToAssertionCode(typeinfo) for typeinfo in types] | |
235 assertion_lines.sort() | |
236 outfile = open(os.path.join(root, filename), "w") | |
237 header_guard = "PPAPI_TESTS_" + filename.upper().replace(".", "_") + "_" | |
238 outfile.write(COPYRIGHT_STRING_C) | |
239 outfile.write('#ifndef ' + header_guard + '\n') | |
240 outfile.write('#define ' + header_guard + '\n\n') | |
241 outfile.write('#include "ppapi/tests/test_struct_sizes.c"\n\n') | |
242 for line in assertion_lines: | |
243 outfile.write(line) | |
244 outfile.write('\n#endif /* ' + header_guard + ' */\n') | |
245 | |
246 | |
247 def main(argv): | |
David Springer
2010/12/13 21:23:08
Suggest comment:
# See README file for example com
| |
248 parser = optparse.OptionParser() | |
249 parser.add_option( | |
250 '-c', '--clang-path', dest='clang_path', | |
251 default=(''), | |
252 help='the path to the clang binary (default is to get it from your path)') | |
253 parser.add_option( | |
254 '-p', '--plugin', dest='plugin', | |
255 default='tests/clang/libPrintNamesAndSizes.so', | |
256 help='The path to the PrintNamesAndSizes plugin library.') | |
257 parser.add_option( | |
258 '--targets32', dest='targets32', | |
259 default='i386-pc-linux,arm-pc-linux,i386-pc-win32', | |
260 help='Which 32-bit target triples to provide to clang.') | |
261 parser.add_option( | |
262 '--targets64', dest='targets64', | |
263 default='x86_64-pc-linux,x86_64-pc-win', | |
264 help='Which 32-bit target triples to provide to clang.') | |
265 parser.add_option( | |
266 '-r', '--ppapi-root', dest='ppapi_root', | |
267 default='.', | |
268 help='The root directory of ppapi.') | |
269 options, args = parser.parse_args(argv) | |
270 if args: | |
271 parser.print_help() | |
272 print 'ERROR: invalid argument' | |
273 sys.exit(1) | |
274 | |
275 clang_executable = os.path.join(options.clang_path, 'clang') | |
276 clang_command = clang_executable + " -cc1" \ | |
277 + " -load " + options.plugin \ | |
278 + " -plugin PrintNamesAndSizes" \ | |
279 + " -I" + os.path.join(options.ppapi_root, "..") \ | |
280 + " " \ | |
281 + os.path.join(options.ppapi_root, "tests", "test_struct_sizes.c") | |
282 | |
283 # Dictionaries mapping type names to TypeInfo objects. | |
284 # Types that have size dependent on architecture, for 32-bit | |
285 types32 = {} | |
286 # Types that have size dependent on architecture, for 64-bit | |
287 types64 = {} | |
288 # Note that types32 and types64 should contain the same types, but with | |
289 # different sizes. | |
290 | |
291 # Types whose size should be consistent regardless of architecture. | |
292 types_independent = {} | |
293 | |
294 # Now run clang for each target. Along the way, make sure architecture- | |
295 # dependent types are consistent sizes on all 32-bit platforms and consistent | |
296 # on all 64-bit platforms. Any types in 'types_independent' are checked for | |
297 # all targets to make sure their size is consistent across all of them. | |
298 targets32 = options.targets32.split(','); | |
299 for target in targets32: | |
300 ProcessTarget(clang_command, target, types32, types_independent) | |
301 targets64 = options.targets64.split(','); | |
302 for target in targets64: | |
303 ProcessTarget(clang_command, target, types64, types_independent) | |
304 | |
305 # This dictionary maps file names to FilePatch objects. | |
306 file_patches = {} | |
307 | |
308 # Find locations of existing macros, and just delete them all. | |
309 for name, typeinfo in \ | |
310 types_independent.items() + types32.items() + types64.items(): | |
311 if IsMacroDefinedName(name): | |
312 sourcefile = typeinfo.source_location.filename | |
313 if sourcefile not in file_patches: | |
314 file_patches[sourcefile] = FilePatch(sourcefile) | |
315 file_patches[sourcefile].Delete(typeinfo.source_location.start_line, | |
316 typeinfo.source_location.end_line+1) | |
317 | |
318 # Add a compile-time assertion for each type whose size is independent of | |
319 # architecture. These assertions go immediately after the class definition. | |
David Springer
2010/12/13 21:23:08
It would be helpful for readers like me to see som
| |
320 for name, typeinfo in types_independent.items(): | |
321 # Ignore macros and types that are 0 bytes (i.e., typedefs to void) | |
322 if not IsMacroDefinedName(name) and typeinfo.size > 0: | |
323 sourcefile = typeinfo.source_location.filename | |
324 if sourcefile not in file_patches: | |
325 file_patches[sourcefile] = FilePatch(sourcefile) | |
326 # Add the assertion code just after the definition of the type. | |
327 file_patches[sourcefile].Add(ToAssertionCode(typeinfo), | |
328 typeinfo.source_location.end_line+1) | |
329 | |
330 for filename, patch in file_patches.items(): | |
331 patch.Apply() | |
332 | |
333 c_source_root = os.path.join(options.ppapi_root, "tests") | |
David Springer
2010/12/13 21:23:08
I am not sure what this is doing - it could use a
David Springer
2010/12/13 21:23:08
A comment might be helpful here: I am not certain
| |
334 WriteArchSpecificCode(types32.values(), | |
335 c_source_root, | |
336 "arch_dependent_sizes_32.h") | |
337 WriteArchSpecificCode(types64.values(), | |
338 c_source_root, | |
339 "arch_dependent_sizes_64.h") | |
340 | |
341 return 0 | |
342 | |
343 | |
344 if __name__ == '__main__': | |
345 sys.exit(main(sys.argv[1:])) | |
346 | |
OLD | NEW |