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