| #!/usr/bin/env python |
| # Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| """This script should be run manually on occasion to make sure all PPAPI types |
| have appropriate size checking. |
| """ |
| |
| import optparse |
| import os |
| import subprocess |
| import sys |
| |
| |
| # The string that the PrintNamesAndSizes plugin uses to indicate a type is |
| # expected to have architecture-dependent size. |
| ARCH_DEPENDENT_STRING = "ArchDependentSize" |
| |
| |
| COPYRIGHT_STRING_C = ( |
| """/* Copyright (c) %s The Chromium Authors. All rights reserved. |
| * Use of this source code is governed by a BSD-style license that can be |
| * found in the LICENSE file. |
| * |
| * This file has compile assertions for the sizes of types that are dependent |
| * on the architecture for which they are compiled (i.e., 32-bit vs 64-bit). |
| */ |
| |
| """) % datetime.date.today().year |
| |
| |
| class SourceLocation(object): |
| """A class representing the source location of a definiton.""" |
| |
| def __init__(self, filename="", start_line=-1, end_line=-1): |
| self.filename = os.path.normpath(filename) |
| self.start_line = start_line |
| self.end_line = end_line |
| |
| |
| class TypeInfo(object): |
| """A class representing information about a C++ type. It contains the |
| following fields: |
| - kind: The Clang TypeClassName (Record, Enum, Typedef, Union, etc) |
| - name: The unmangled string name of the type. |
| - size: The size in bytes of the type. |
| - arch_dependent: True if the type may have architecture dependent size |
| according to PrintNamesAndSizes. False otherwise. Types |
| which are considered architecture-dependent from 32-bit |
| to 64-bit are pointers, longs, unsigned longs, and any |
| type that contains an architecture-dependent type. |
| - source_location: A SourceLocation describing where the type is defined. |
| - target: The target Clang was compiling when it found the type definition. |
| This is used only for diagnostic output. |
| - parsed_line: The line which Clang output which was used to create this |
| TypeInfo (as the info_string parameter to __init__). This is |
| used only for diagnostic output. |
| """ |
| |
| def __init__(self, info_string, target): |
| """Create a TypeInfo from a given info_string. Also store the name of the |
| target for which the TypeInfo was first created just so we can print useful |
| error information. |
| info_string is a comma-delimited string of the following form: |
| kind,name,size,arch_dependent,source_file,start_line,end_line |
| Where: |
| - kind: The Clang TypeClassName (Record, Enum, Typedef, Union, etc) |
| - name: The unmangled string name of the type. |
| - size: The size in bytes of the type. |
| - arch_dependent: 'ArchDependentSize' if the type has architecture-dependent |
| size, NotArchDependentSize otherwise. |
| - source_file: The source file in which the type is defined. |
| - first_line: The first line of the definition (counting from 0). |
| - last_line: The last line of the definition (counting from 0). |
| This should match the output of the PrintNamesAndSizes plugin. |
| """ |
| [self.kind, self.name, self.size, arch_dependent_string, source_file, |
| start_line, end_line] = info_string.split(',') |
| self.target = target |
| self.parsed_line = info_string |
| # Note that Clang counts line numbers from 1, but we want to count from 0. |
| self.source_location = SourceLocation(source_file, |
| int(start_line)-1, |
| int(end_line)-1) |
| self.arch_dependent = (arch_dependent_string == ARCH_DEPENDENT_STRING) |
| |
| |
| class FilePatch(object): |
| """A class representing a set of line-by-line changes to a particular file. |
| None of the changes are applied until Apply is called. All line numbers are |
| counted from 0. |
| """ |
| |
| def __init__(self, filename): |
| self.filename = filename |
| self.linenums_to_delete = set() |
| # A dictionary from line number to an array of strings to be inserted at |
| # that line number. |
| self.lines_to_add = {} |
| |
| def Delete(self, start_line, end_line): |
| """Make the patch delete the lines starting with |start_line| up to but not |
| including |end_line|. |
| """ |
| self.linenums_to_delete |= set(range(start_line, end_line)) |
| |
| def Add(self, text, line_number): |
| """Add the given text before the text on the given line number.""" |
| if line_number in self.lines_to_add: |
| self.lines_to_add[line_number].append(text) |
| else: |
| self.lines_to_add[line_number] = [text] |
| |
| def Apply(self): |
| """Apply the patch by writing it to self.filename.""" |
| # Read the lines of the existing file in to a list. |
| sourcefile = open(self.filename, "r") |
| file_lines = sourcefile.readlines() |
| sourcefile.close() |
| # Now apply the patch. Our strategy is to keep the array at the same size, |
| # and just edit strings in the file_lines list as necessary. When we delete |
| # lines, we just blank the line and keep it in the list. When we add lines, |
| # we just prepend the added source code to the start of the existing line at |
| # that line number. This way, all the line numbers we cached from calls to |
| # Add and Delete remain valid list indices, and we don't have to worry about |
| # maintaining any offsets. Each element of file_lines at the end may |
| # contain any number of lines (0 or more) delimited by carriage returns. |
| for linenum_to_delete in self.linenums_to_delete: |
| file_lines[linenum_to_delete] = ""; |
| for linenum, sourcelines in self.lines_to_add.items(): |
| # Sort the lines we're adding so we get relatively consistent results. |
| sourcelines.sort() |
| # Prepend the new lines. When we output |
| file_lines[linenum] = "".join(sourcelines) + file_lines[linenum] |
| newsource = open(self.filename, "w") |
| for line in file_lines: |
| newsource.write(line) |
| newsource.close() |
| |
| |
| def CheckAndInsert(typeinfo, typeinfo_map): |
| """Check if a TypeInfo exists already in the given map with the same name. If |
| so, make sure the size is consistent. |
| - If the name exists but the sizes do not match, print a message and |
| exit with non-zero exit code. |
| - If the name exists and the sizes match, do nothing. |
| - If the name does not exist, insert the typeinfo in to the map. |
| |
| """ |
| # If the type is unnamed, ignore it. |
| if typeinfo.name == "": |
| return |
| # If the size is 0, ignore it. |
| elif int(typeinfo.size) == 0: |
| return |
| # If the type is not defined under ppapi, ignore it. |
| elif typeinfo.source_location.filename.find("ppapi") == -1: |
| return |
| # If the type is defined under GLES2, ignore it. |
| elif typeinfo.source_location.filename.find("GLES2") > -1: |
| return |
| # If the type is an interface (by convention, starts with PPP_ or PPB_), |
| # ignore it. |
| elif (typeinfo.name[:4] == "PPP_") or (typeinfo.name[:4] == "PPB_"): |
| return |
| elif typeinfo.name in typeinfo_map: |
| if typeinfo.size != typeinfo_map[typeinfo.name].size: |
| print "Error: '" + typeinfo.name + "' is", \ |
| typeinfo_map[typeinfo.name].size, \ |
| "bytes on target '" + typeinfo_map[typeinfo.name].target + \ |
| "', but", typeinfo.size, "on target '" + typeinfo.target + "'" |
| print typeinfo_map[typeinfo.name].parsed_line |
| print typeinfo.parsed_line |
| sys.exit(1) |
| else: |
| # It's already in the map and the sizes match. |
| pass |
| else: |
| typeinfo_map[typeinfo.name] = typeinfo |
| |
| |
| def ProcessTarget(clang_command, target, types): |
| """Run clang using the given clang_command for the given target string. Parse |
| the output to create TypeInfos for each discovered type. Insert each type in |
| to the 'types' dictionary. If the type already exists in the types |
| dictionary, make sure that the size matches what's already in the map. If |
| not, exit with an error message. |
| """ |
| p = subprocess.Popen(clang_command + " -triple " + target, |
| shell=True, |
| stdout=subprocess.PIPE) |
| lines = p.communicate()[0].split() |
| for line in lines: |
| typeinfo = TypeInfo(line, target) |
| CheckAndInsert(typeinfo, types) |
| |
| |
| def ToAssertionCode(typeinfo): |
| """Convert the TypeInfo to an appropriate C compile assertion. |
| If it's a struct (Record in Clang terminology), we want a line like this: |
| PP_COMPILE_ASSERT_STRUCT_SIZE_IN_BYTES(<name>, <size>);\n |
| Enums: |
| PP_COMPILE_ASSERT_ENUM_SIZE_IN_BYTES(<name>, <size>);\n |
| Typedefs: |
| PP_COMPILE_ASSERT_SIZE_IN_BYTES(<name>, <size>);\n |
| |
| """ |
| line = "PP_COMPILE_ASSERT_" |
| if typeinfo.kind == "Enum": |
| line += "ENUM_" |
| elif typeinfo.kind == "Record": |
| line += "STRUCT_" |
| line += "SIZE_IN_BYTES(" |
| line += typeinfo.name |
| line += ", " |
| line += typeinfo.size |
| line += ");\n" |
| return line |
| |
| |
| def IsMacroDefinedName(typename): |
| """Return true iff the given typename came from a PPAPI compile assertion.""" |
| return typename.find("PP_Dummy_Struct_For_") == 0 |
| |
| |
| def WriteArchSpecificCode(types, root, filename): |
| """Write a header file that contains a compile-time assertion for the size of |
| each of the given typeinfos, in to a file named filename rooted at root. |
| """ |
| assertion_lines = [ToAssertionCode(typeinfo) for typeinfo in types] |
| assertion_lines.sort() |
| outfile = open(os.path.join(root, filename), "w") |
| header_guard = "PPAPI_TESTS_" + filename.upper().replace(".", "_") + "_" |
| outfile.write(COPYRIGHT_STRING_C) |
| outfile.write('#ifndef ' + header_guard + '\n') |
| outfile.write('#define ' + header_guard + '\n\n') |
| outfile.write('#include "ppapi/tests/test_struct_sizes.c"\n\n') |
| for line in assertion_lines: |
| outfile.write(line) |
| outfile.write('\n#endif /* ' + header_guard + ' */\n') |
| |
| |
| def main(argv): |
| # See README file for example command-line invocation. This script runs the |
| # PrintNamesAndSizes Clang plugin with 'test_struct_sizes.c' as input, which |
| # should include all C headers and all existing size checks. It runs the |
| # plugin multiple times; once for each of a set of targets, some 32-bit and |
| # some 64-bit. It verifies that wherever possible, types have a consistent |
| # size on both platforms. Types that can't easily have consistent size (e.g. |
| # ones that contain a pointer) are checked to make sure they are consistent |
| # for all 32-bit platforms and consistent on all 64-bit platforms, but the |
| # sizes on 32 vs 64 are allowed to differ. |
| # |
| # Then, if all the types have consistent size as expected, compile assertions |
| # are added to the source code. Types whose size is independent of |
| # architectureacross have their compile assertions placed immediately after |
| # their definition in the C API header. Types whose size differs on 32-bit |
| # vs 64-bit have a compile assertion placed in each of: |
| # ppapi/tests/arch_dependent_sizes_32.h and |
| # ppapi/tests/arch_dependent_sizes_64.h. |
| # |
| # Note that you should always check the results of the tool to make sure |
| # they are sane. |
| parser = optparse.OptionParser() |
| parser.add_option( |
| '-c', '--clang-path', dest='clang_path', |
| default=(''), |
| help='the path to the clang binary (default is to get it from your path)') |
| parser.add_option( |
| '-p', '--plugin', dest='plugin', |
| default='tests/clang/libPrintNamesAndSizes.so', |
| help='The path to the PrintNamesAndSizes plugin library.') |
| parser.add_option( |
| '--targets32', dest='targets32', |
| default='i386-pc-linux,arm-pc-linux,i386-pc-win32', |
| help='Which 32-bit target triples to provide to clang.') |
| parser.add_option( |
| '--targets64', dest='targets64', |
| default='x86_64-pc-linux,x86_64-pc-win', |
| help='Which 32-bit target triples to provide to clang.') |
| parser.add_option( |
| '-r', '--ppapi-root', dest='ppapi_root', |
| default='.', |
| help='The root directory of ppapi.') |
| options, args = parser.parse_args(argv) |
| if args: |
| parser.print_help() |
| print 'ERROR: invalid argument' |
| sys.exit(1) |
| |
| clang_executable = os.path.join(options.clang_path, 'clang') |
| clang_command = clang_executable + " -cc1" \ |
| + " -load " + options.plugin \ |
| + " -plugin PrintNamesAndSizes" \ |
| + " -I" + os.path.join(options.ppapi_root, "..") \ |
| + " " \ |
| + os.path.join(options.ppapi_root, "tests", "test_struct_sizes.c") |
| |
| # Dictionaries mapping type names to TypeInfo objects. |
| # Types that have size dependent on architecture, for 32-bit |
| types32 = {} |
| # Types that have size dependent on architecture, for 64-bit |
| types64 = {} |
| # Note that types32 and types64 should contain the same types, but with |
| # different sizes. |
| |
| # Types whose size should be consistent regardless of architecture. |
| types_independent = {} |
| |
| # Now run clang for each target. Along the way, make sure architecture- |
| # dependent types are consistent sizes on all 32-bit platforms and consistent |
| # on all 64-bit platforms. |
| targets32 = options.targets32.split(','); |
| for target in targets32: |
| # For each 32-bit target, run the PrintNamesAndSizes Clang plugin to get |
| # information about all types in the translation unit, and add a TypeInfo |
| # for each of them to types32. If any size mismatches are found, |
| # ProcessTarget will spit out an error and exit. |
| ProcessTarget(clang_command, target, types32) |
| targets64 = options.targets64.split(','); |
| for target in targets64: |
| # Do the same as above for each 64-bit target; put all types in types64. |
| ProcessTarget(clang_command, target, types64) |
| |
| # Now for each dictionary, find types whose size are consistent regardless of |
| # architecture, and move those in to types_independent. Anywhere sizes |
| # differ, make sure they are expected to be architecture-dependent based on |
| # their structure. If we find types which could easily be consistent but |
| # aren't, spit out an error and exit. |
| types_independent = {} |
| for typename, typeinfo32 in types32.items(): |
| if (typename in types64): |
| typeinfo64 = types64[typename] |
| if (typeinfo64.size == typeinfo32.size): |
| # The types are the same size, so we can treat it as arch-independent. |
| types_independent[typename] = typeinfo32 |
| del types32[typename] |
| del types64[typename] |
| elif (typeinfo32.arch_dependent or typeinfo64.arch_dependent): |
| # The type is defined in such a way that it would be difficult to make |
| # its size consistent. E.g., it has pointers. We'll leave it in the |
| # arch-dependent maps so that we can put arch-dependent size checks in |
| # test code. |
| pass |
| else: |
| # The sizes don't match, but there's no reason they couldn't. It's |
| # probably due to an alignment mismatch between Win32/NaCl vs Linux32/ |
| # Mac32. |
| print "Error: '" + typename + "' is", typeinfo32.size, \ |
| "bytes on target '" + typeinfo32.target + \ |
| "', but", typeinfo64.size, "on target '" + typeinfo64.target + "'" |
| print typeinfo32.parsed_line |
| print typeinfo64.parsed_line |
| sys.exit(1) |
| else: |
| print "WARNING: Type '", typename, "' was defined for target '", |
| print typeinfo32.target, ", but not for any 64-bit targets." |
| |
| # Now we have all the information we need to generate our static assertions. |
| # Types that have consistent size across architectures will have the static |
| # assertion placed immediately after their definition. Types whose size |
| # depends on 32-bit vs 64-bit architecture will have checks placed in |
| # tests/arch_dependent_sizes_32/64.h. |
| |
| # This dictionary maps file names to FilePatch objects. We will add items |
| # to it as needed. Each FilePatch represents a set of changes to make to the |
| # associated file (additions and deletions). |
| file_patches = {} |
| |
| # Find locations of existing macros, and just delete them all. Note that |
| # normally, only things in 'types_independent' need to be deleted, as arch- |
| # dependent checks exist in tests/arch_dependent_sizes_32/64.h, which are |
| # always completely over-ridden. However, it's possible that a type that used |
| # to be arch-independent has changed to now be arch-dependent (e.g., because |
| # a pointer was added), and we want to delete the old check in that case. |
| for name, typeinfo in \ |
| types_independent.items() + types32.items() + types64.items(): |
| if IsMacroDefinedName(name): |
| sourcefile = typeinfo.source_location.filename |
| if sourcefile not in file_patches: |
| file_patches[sourcefile] = FilePatch(sourcefile) |
| file_patches[sourcefile].Delete(typeinfo.source_location.start_line, |
| typeinfo.source_location.end_line+1) |
| |
| # Add a compile-time assertion for each type whose size is independent of |
| # architecture. These assertions go immediately after the class definition. |
| for name, typeinfo in types_independent.items(): |
| # Ignore dummy types that were defined by macros and also ignore types that |
| # are 0 bytes (i.e., typedefs to void). |
| if not IsMacroDefinedName(name) and typeinfo.size > 0: |
| sourcefile = typeinfo.source_location.filename |
| if sourcefile not in file_patches: |
| file_patches[sourcefile] = FilePatch(sourcefile) |
| # Add the assertion code just after the definition of the type. |
| # E.g.: |
| # struct Foo { |
| # int32_t x; |
| # }; |
| # PP_COMPILE_ASSERT_STRUCT_SIZE_IN_BYTES(Foo, 4); <---Add this line |
| file_patches[sourcefile].Add(ToAssertionCode(typeinfo), |
| typeinfo.source_location.end_line+1) |
| |
| # Apply our patches. This actually edits the files containing the definitions |
| # for the types in types_independent. |
| for filename, patch in file_patches.items(): |
| patch.Apply() |
| |
| # Write out a file of checks for 32-bit architectures and a separate file for |
| # 64-bit architectures. These only have checks for types that are |
| # architecture-dependent. |
| c_source_root = os.path.join(options.ppapi_root, "tests") |
| WriteArchSpecificCode(types32.values(), |
| c_source_root, |
| "arch_dependent_sizes_32.h") |
| WriteArchSpecificCode(types64.values(), |
| c_source_root, |
| "arch_dependent_sizes_64.h") |
| |
| return 0 |
| |
| |
| if __name__ == '__main__': |
| sys.exit(main(sys.argv[1:])) |