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