OLD | NEW |
1 #!/usr/bin/python | 1 #!/usr/bin/python |
2 | 2 |
3 # Copyright (c) 2010 The Chromium Authors. All rights reserved. | 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 | 4 # Use of this source code is governed by a BSD-style license that can be |
5 # found in the LICENSE file. | 5 # found in the LICENSE file. |
6 | 6 |
7 """This script should be run manually on occasion to make sure all PPAPI types | 7 """This script should be run manually on occasion to make sure all PPAPI types |
8 have appropriate size checking. | 8 have appropriate size checking. |
9 | 9 |
10 """ | 10 """ |
11 | 11 |
12 import optparse | 12 import optparse |
13 import os | 13 import os |
14 import subprocess | 14 import subprocess |
15 import sys | 15 import sys |
16 | 16 |
17 | 17 |
18 # The string that the PrintNamesAndSizes plugin uses to indicate a type is or | 18 # The string that the PrintNamesAndSizes plugin uses to indicate a type is |
19 # contains a pointer. | 19 # expected to have architecture-dependent size. |
20 HAS_POINTER_STRING = "HasPointer" | 20 ARCH_DEPENDENT_STRING = "ArchDependentSize" |
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. | |
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 | 21 |
33 | 22 |
34 | 23 |
35 class SourceLocation: | 24 class SourceLocation: |
36 | 25 |
37 """A class representing the source location of a definiton.""" | 26 """A class representing the source location of a definiton.""" |
38 | 27 |
39 def __init__(self, filename="", start_line=-1, end_line=-1): | 28 def __init__(self, filename="", start_line=-1, end_line=-1): |
40 self.filename = os.path.normpath(filename) | 29 self.filename = os.path.normpath(filename) |
41 self.start_line = start_line | 30 self.start_line = start_line |
42 self.end_line = end_line | 31 self.end_line = end_line |
43 | 32 |
44 | 33 |
45 | 34 |
46 class TypeInfo: | 35 class TypeInfo: |
47 | 36 |
48 """A class representing information about a C++ type.""" | 37 """A class representing information about a C++ type. It contains the |
| 38 following fields: |
| 39 - kind: The Clang TypeClassName (Record, Enum, Typedef, Union, etc) |
| 40 - name: The unmangled string name of the type. |
| 41 - size: The size in bytes of the type. |
| 42 - arch_dependent: True if the type may have architecture dependent size |
| 43 according to PrintNamesAndSizes. False otherwise. Types |
| 44 which are considered architecture-dependent from 32-bit |
| 45 to 64-bit are pointers, longs, unsigned longs, and any |
| 46 type that contains an architecture-dependent type. |
| 47 - source_location: A SourceLocation describing where the type is defined. |
| 48 - target: The target Clang was compiling when it found the type definition. |
| 49 This is used only for diagnostic output. |
| 50 - parsed_line: The line which Clang output which was used to create this |
| 51 TypeInfo (as the info_string parameter to __init__). This is |
| 52 used only for diagnostic output. |
| 53 """ |
49 | 54 |
50 def __init__(self, info_string, target): | 55 def __init__(self, info_string, target): |
51 [self.kind, self.name, self.size, has_pointer_string, source_file, | 56 """Create a TypeInfo from a given info_string. Also store the name of the |
| 57 target for which the TypeInfo was first created just so we can print useful |
| 58 error information. |
| 59 info_string is a comma-delimited string of the following form: |
| 60 kind,name,size,arch_dependent,source_file,start_line,end_line |
| 61 Where: |
| 62 - kind: The Clang TypeClassName (Record, Enum, Typedef, Union, etc) |
| 63 - name: The unmangled string name of the type. |
| 64 - size: The size in bytes of the type. |
| 65 - arch_dependent: 'ArchDependentSize' if the type has architecture-dependent |
| 66 size, NotArchDependentSize otherwise. |
| 67 - source_file: The source file in which the type is defined. |
| 68 - first_line: The first line of the definition (counting from 0). |
| 69 - last_line: The last line of the definition (counting from 0). |
| 70 This should match the output of the PrintNamesAndSizes plugin. |
| 71 """ |
| 72 [self.kind, self.name, self.size, arch_dependent_string, source_file, |
52 start_line, end_line] = info_string.split(',') | 73 start_line, end_line] = info_string.split(',') |
53 self.target = target | 74 self.target = target |
54 self.parsed_line = info_string | 75 self.parsed_line = info_string |
55 # Note that Clang counts line numbers from 1, but we want to count from 0. | 76 # Note that Clang counts line numbers from 1, but we want to count from 0. |
56 self.source_location = SourceLocation(source_file, | 77 self.source_location = SourceLocation(source_file, |
57 int(start_line)-1, | 78 int(start_line)-1, |
58 int(end_line)-1) | 79 int(end_line)-1) |
59 # If the type is one of our known special cases, mark it as architecture- | 80 self.arch_dependent = (arch_dependent_string == ARCH_DEPENDENT_STRING) |
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 | 81 |
71 | 82 |
72 class FilePatch: | 83 class FilePatch: |
73 | 84 |
74 """A class representing a set of line-by-line changes to a particular file. | 85 """A class representing a set of line-by-line changes to a particular file. |
75 None | 86 None of the changes are applied until Apply is called. All line numbers are |
76 of the changes are applied until Apply is called. All line numbers are | |
77 counted from 0. | 87 counted from 0. |
78 """ | 88 """ |
79 | 89 |
80 def __init__(self, filename): | 90 def __init__(self, filename): |
81 self.filename = filename | 91 self.filename = filename |
82 self.linenums_to_delete = set() | 92 self.linenums_to_delete = set() |
83 # A dictionary from line number to an array of strings to be inserted at | 93 # A dictionary from line number to an array of strings to be inserted at |
84 # that line number. | 94 # that line number. |
85 self.lines_to_add = {} | 95 self.lines_to_add = {} |
86 | 96 |
87 def Delete(self, start_line, end_line): | 97 def Delete(self, start_line, end_line): |
88 """Make the patch delete the lines [start_line, end_line).""" | 98 """Make the patch delete the lines starting with |start_line| up to but not |
| 99 including |end_line|. |
| 100 """ |
89 self.linenums_to_delete |= set(range(start_line, end_line)) | 101 self.linenums_to_delete |= set(range(start_line, end_line)) |
90 | 102 |
91 def Add(self, text, line_number): | 103 def Add(self, text, line_number): |
92 """Add the given text before the text on the given line number.""" | 104 """Add the given text before the text on the given line number.""" |
93 if line_number in self.lines_to_add: | 105 if line_number in self.lines_to_add: |
94 self.lines_to_add[line_number].append(text) | 106 self.lines_to_add[line_number].append(text) |
95 else: | 107 else: |
96 self.lines_to_add[line_number] = [text] | 108 self.lines_to_add[line_number] = [text] |
97 | 109 |
98 def Apply(self): | 110 def Apply(self): |
99 """Apply the patch by writing it to self.filename""" | 111 """Apply the patch by writing it to self.filename.""" |
100 # Read the lines of the existing file in to a list. | 112 # Read the lines of the existing file in to a list. |
101 sourcefile = open(self.filename, "r") | 113 sourcefile = open(self.filename, "r") |
102 file_lines = sourcefile.readlines() | 114 file_lines = sourcefile.readlines() |
103 sourcefile.close() | 115 sourcefile.close() |
104 # Now apply the patch. Our strategy is to keep the array at the same size, | 116 # 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 | 117 # 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, | 118 # 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 | 119 # 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 | 120 # 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 | 121 # Add and Delete remain valid list indices, and we don't have to worry about |
(...skipping 46 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
156 print typeinfo_map[typeinfo.name].parsed_line | 168 print typeinfo_map[typeinfo.name].parsed_line |
157 print typeinfo.parsed_line | 169 print typeinfo.parsed_line |
158 sys.exit(1) | 170 sys.exit(1) |
159 else: | 171 else: |
160 # It's already in the map and the sizes match. | 172 # It's already in the map and the sizes match. |
161 pass | 173 pass |
162 else: | 174 else: |
163 typeinfo_map[typeinfo.name] = typeinfo | 175 typeinfo_map[typeinfo.name] = typeinfo |
164 | 176 |
165 | 177 |
166 def ProcessTarget(clang_command, target, arch_types, ind_types): | 178 def ProcessTarget(clang_command, target, types): |
167 """Run clang using the given clang_command for the given target string. Parse | 179 """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 | 180 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 | 181 to the 'types' dictionary. If the type already exists in the types |
170 size, insert it in to arch_types. Types with independent size go in to | 182 dictionary, make sure that the size matches what's already in the map. If |
171 ind_types. If the type already exists in the appropriate map, make sure that | 183 not, exit with an error message. |
172 the size matches what's already in the map. If not, the script terminates | |
173 with an error message. | |
174 """ | 184 """ |
175 p = subprocess.Popen(clang_command + " -triple " + target, | 185 p = subprocess.Popen(clang_command + " -triple " + target, |
176 shell=True, | 186 shell=True, |
177 stdout=subprocess.PIPE) | 187 stdout=subprocess.PIPE) |
178 lines = p.communicate()[0].split() | 188 lines = p.communicate()[0].split() |
179 for line in lines: | 189 for line in lines: |
180 typeinfo = TypeInfo(line, target) | 190 typeinfo = TypeInfo(line, target) |
181 # Put types which have architecture-specific size in to arch_types. All | 191 CheckAndInsert(typeinfo, types) |
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 | 192 |
189 | 193 |
190 def ToAssertionCode(typeinfo): | 194 def ToAssertionCode(typeinfo): |
191 """Convert the TypeInfo to an appropriate C compile assertion. | 195 """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: | 196 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 | 197 PP_COMPILE_ASSERT_STRUCT_SIZE_IN_BYTES(<name>, <size>);\n |
194 Enums: | 198 Enums: |
195 PP_COMPILE_ASSERT_ENUM_SIZE_IN_BYTES(<name>, <size>);\n | 199 PP_COMPILE_ASSERT_ENUM_SIZE_IN_BYTES(<name>, <size>);\n |
196 Typedefs: | 200 Typedefs: |
197 PP_COMPILE_ASSERT_SIZE_IN_BYTES(<name>, <size>);\n | 201 PP_COMPILE_ASSERT_SIZE_IN_BYTES(<name>, <size>);\n |
(...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
238 outfile.write(COPYRIGHT_STRING_C) | 242 outfile.write(COPYRIGHT_STRING_C) |
239 outfile.write('#ifndef ' + header_guard + '\n') | 243 outfile.write('#ifndef ' + header_guard + '\n') |
240 outfile.write('#define ' + header_guard + '\n\n') | 244 outfile.write('#define ' + header_guard + '\n\n') |
241 outfile.write('#include "ppapi/tests/test_struct_sizes.c"\n\n') | 245 outfile.write('#include "ppapi/tests/test_struct_sizes.c"\n\n') |
242 for line in assertion_lines: | 246 for line in assertion_lines: |
243 outfile.write(line) | 247 outfile.write(line) |
244 outfile.write('\n#endif /* ' + header_guard + ' */\n') | 248 outfile.write('\n#endif /* ' + header_guard + ' */\n') |
245 | 249 |
246 | 250 |
247 def main(argv): | 251 def main(argv): |
| 252 # See README file for example command-line invocation. This script runs the |
| 253 # PrintNamesAndSizes Clang plugin with 'test_struct_sizes.c' as input, which |
| 254 # should include all C headers and all existing size checks. It runs the |
| 255 # plugin multiple times; once for each of a set of targets, some 32-bit and |
| 256 # some 64-bit. It verifies that wherever possible, types have a consistent |
| 257 # size on both platforms. Types that can't easily have consistent size (e.g. |
| 258 # ones that contain a pointer) are checked to make sure they are consistent |
| 259 # for all 32-bit platforms and consistent on all 64-bit platforms, but the |
| 260 # sizes on 32 vs 64 are allowed to differ. |
| 261 # |
| 262 # Then, if all the types have consistent size as expected, compile assertions |
| 263 # are added to the source code. Types whose size is independent of |
| 264 # architectureacross have their compile assertions placed immediately after |
| 265 # their definition in the C API header. Types whose size differs on 32-bit |
| 266 # vs 64-bit have a compile assertion placed in each of: |
| 267 # ppapi/tests/arch_dependent_sizes_32.h and |
| 268 # ppapi/tests/arch_dependent_sizes_64.h. |
| 269 # |
| 270 # Note that you should always check the results of the tool to make sure |
| 271 # they are sane. |
248 parser = optparse.OptionParser() | 272 parser = optparse.OptionParser() |
249 parser.add_option( | 273 parser.add_option( |
250 '-c', '--clang-path', dest='clang_path', | 274 '-c', '--clang-path', dest='clang_path', |
251 default=(''), | 275 default=(''), |
252 help='the path to the clang binary (default is to get it from your path)') | 276 help='the path to the clang binary (default is to get it from your path)') |
253 parser.add_option( | 277 parser.add_option( |
254 '-p', '--plugin', dest='plugin', | 278 '-p', '--plugin', dest='plugin', |
255 default='tests/clang/libPrintNamesAndSizes.so', | 279 default='tests/clang/libPrintNamesAndSizes.so', |
256 help='The path to the PrintNamesAndSizes plugin library.') | 280 help='The path to the PrintNamesAndSizes plugin library.') |
257 parser.add_option( | 281 parser.add_option( |
(...skipping 28 matching lines...) Expand all Loading... |
286 # Types that have size dependent on architecture, for 64-bit | 310 # Types that have size dependent on architecture, for 64-bit |
287 types64 = {} | 311 types64 = {} |
288 # Note that types32 and types64 should contain the same types, but with | 312 # Note that types32 and types64 should contain the same types, but with |
289 # different sizes. | 313 # different sizes. |
290 | 314 |
291 # Types whose size should be consistent regardless of architecture. | 315 # Types whose size should be consistent regardless of architecture. |
292 types_independent = {} | 316 types_independent = {} |
293 | 317 |
294 # Now run clang for each target. Along the way, make sure architecture- | 318 # 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 | 319 # 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 | 320 # on all 64-bit platforms. |
297 # all targets to make sure their size is consistent across all of them. | |
298 targets32 = options.targets32.split(','); | 321 targets32 = options.targets32.split(','); |
299 for target in targets32: | 322 for target in targets32: |
300 ProcessTarget(clang_command, target, types32, types_independent) | 323 # For each 32-bit target, run the PrintNamesAndSizes Clang plugin to get |
| 324 # information about all types in the translation unit, and add a TypeInfo |
| 325 # for each of them to types32. If any size mismatches are found, |
| 326 # ProcessTarget will spit out an error and exit. |
| 327 ProcessTarget(clang_command, target, types32) |
301 targets64 = options.targets64.split(','); | 328 targets64 = options.targets64.split(','); |
302 for target in targets64: | 329 for target in targets64: |
303 ProcessTarget(clang_command, target, types64, types_independent) | 330 # Do the same as above for each 64-bit target; put all types in types64. |
| 331 ProcessTarget(clang_command, target, types64) |
304 | 332 |
305 # This dictionary maps file names to FilePatch objects. | 333 # Now for each dictionary, find types whose size are consistent regardless of |
| 334 # architecture, and move those in to types_independent. Anywhere sizes |
| 335 # differ, make sure they are expected to be architecture-dependent based on |
| 336 # their structure. If we find types which could easily be consistent but |
| 337 # aren't, spit out an error and exit. |
| 338 types_independent = {} |
| 339 for typename, typeinfo32 in types32.items(): |
| 340 if (typename in types64): |
| 341 typeinfo64 = types64[typename] |
| 342 if (typeinfo64.size == typeinfo32.size): |
| 343 # The types are the same size, so we can treat it as arch-independent. |
| 344 types_independent[typename] = typeinfo32 |
| 345 del types32[typename] |
| 346 del types64[typename] |
| 347 elif (typeinfo32.arch_dependent or typeinfo64.arch_dependent): |
| 348 # The type is defined in such a way that it would be difficult to make |
| 349 # its size consistent. E.g., it has pointers. We'll leave it in the |
| 350 # arch-dependent maps so that we can put arch-dependent size checks in |
| 351 # test code. |
| 352 pass |
| 353 else: |
| 354 # The sizes don't match, but there's no reason they couldn't. It's |
| 355 # probably due to an alignment mismatch between Win32/NaCl vs Linux32/ |
| 356 # Mac32. |
| 357 print "Error: '" + typename + "' is", typeinfo32.size, \ |
| 358 "bytes on target '" + typeinfo32.target + \ |
| 359 "', but", typeinfo64.size, "on target '" + typeinfo64.target + "'" |
| 360 print typeinfo32.parsed_line |
| 361 print typeinfo64.parsed_line |
| 362 sys.exit(1) |
| 363 else: |
| 364 print "WARNING: Type '", typename, "' was defined for target '", |
| 365 print typeinfo32.target, ", but not for any 64-bit targets." |
| 366 |
| 367 # Now we have all the information we need to generate our static assertions. |
| 368 # Types that have consistent size across architectures will have the static |
| 369 # assertion placed immediately after their definition. Types whose size |
| 370 # depends on 32-bit vs 64-bit architecture will have checks placed in |
| 371 # tests/arch_dependent_sizes_32/64.h. |
| 372 |
| 373 # This dictionary maps file names to FilePatch objects. We will add items |
| 374 # to it as needed. Each FilePatch represents a set of changes to make to the |
| 375 # associated file (additions and deletions). |
306 file_patches = {} | 376 file_patches = {} |
307 | 377 |
308 # Find locations of existing macros, and just delete them all. | 378 # Find locations of existing macros, and just delete them all. Note that |
| 379 # normally, only things in 'types_independent' need to be deleted, as arch- |
| 380 # dependent checks exist in tests/arch_dependent_sizes_32/64.h, which are |
| 381 # always completely over-ridden. However, it's possible that a type that used |
| 382 # to be arch-independent has changed to now be arch-dependent (e.g., because |
| 383 # a pointer was added), and we want to delete the old check in that case. |
309 for name, typeinfo in \ | 384 for name, typeinfo in \ |
310 types_independent.items() + types32.items() + types64.items(): | 385 types_independent.items() + types32.items() + types64.items(): |
311 if IsMacroDefinedName(name): | 386 if IsMacroDefinedName(name): |
312 sourcefile = typeinfo.source_location.filename | 387 sourcefile = typeinfo.source_location.filename |
313 if sourcefile not in file_patches: | 388 if sourcefile not in file_patches: |
314 file_patches[sourcefile] = FilePatch(sourcefile) | 389 file_patches[sourcefile] = FilePatch(sourcefile) |
315 file_patches[sourcefile].Delete(typeinfo.source_location.start_line, | 390 file_patches[sourcefile].Delete(typeinfo.source_location.start_line, |
316 typeinfo.source_location.end_line+1) | 391 typeinfo.source_location.end_line+1) |
317 | 392 |
318 # Add a compile-time assertion for each type whose size is independent of | 393 # Add a compile-time assertion for each type whose size is independent of |
319 # architecture. These assertions go immediately after the class definition. | 394 # architecture. These assertions go immediately after the class definition. |
320 for name, typeinfo in types_independent.items(): | 395 for name, typeinfo in types_independent.items(): |
321 # Ignore macros and types that are 0 bytes (i.e., typedefs to void) | 396 # Ignore dummy types that were defined by macros and also ignore types that |
| 397 # are 0 bytes (i.e., typedefs to void). |
322 if not IsMacroDefinedName(name) and typeinfo.size > 0: | 398 if not IsMacroDefinedName(name) and typeinfo.size > 0: |
323 sourcefile = typeinfo.source_location.filename | 399 sourcefile = typeinfo.source_location.filename |
324 if sourcefile not in file_patches: | 400 if sourcefile not in file_patches: |
325 file_patches[sourcefile] = FilePatch(sourcefile) | 401 file_patches[sourcefile] = FilePatch(sourcefile) |
326 # Add the assertion code just after the definition of the type. | 402 # Add the assertion code just after the definition of the type. |
| 403 # E.g.: |
| 404 # struct Foo { |
| 405 # int32_t x; |
| 406 # }; |
| 407 # PP_COMPILE_ASSERT_STRUCT_SIZE_IN_BYTES(Foo, 4); <---Add this line |
327 file_patches[sourcefile].Add(ToAssertionCode(typeinfo), | 408 file_patches[sourcefile].Add(ToAssertionCode(typeinfo), |
328 typeinfo.source_location.end_line+1) | 409 typeinfo.source_location.end_line+1) |
329 | 410 |
| 411 # Apply our patches. This actually edits the files containing the definitions |
| 412 # for the types in types_independent. |
330 for filename, patch in file_patches.items(): | 413 for filename, patch in file_patches.items(): |
331 patch.Apply() | 414 patch.Apply() |
332 | 415 |
| 416 # Write out a file of checks for 32-bit architectures and a separate file for |
| 417 # 64-bit architectures. These only have checks for types that are |
| 418 # architecture-dependent. |
333 c_source_root = os.path.join(options.ppapi_root, "tests") | 419 c_source_root = os.path.join(options.ppapi_root, "tests") |
334 WriteArchSpecificCode(types32.values(), | 420 WriteArchSpecificCode(types32.values(), |
335 c_source_root, | 421 c_source_root, |
336 "arch_dependent_sizes_32.h") | 422 "arch_dependent_sizes_32.h") |
337 WriteArchSpecificCode(types64.values(), | 423 WriteArchSpecificCode(types64.values(), |
338 c_source_root, | 424 c_source_root, |
339 "arch_dependent_sizes_64.h") | 425 "arch_dependent_sizes_64.h") |
340 | 426 |
341 return 0 | 427 return 0 |
342 | 428 |
343 | 429 |
344 if __name__ == '__main__': | 430 if __name__ == '__main__': |
345 sys.exit(main(sys.argv[1:])) | 431 sys.exit(main(sys.argv[1:])) |
346 | 432 |
OLD | NEW |