123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426 |
- #!/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.
- """
- from __future__ import print_function
- 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:]))
|