"""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
ARCH_DEPENDENT_STRING = "ArchDependentSize"
COPYRIGHT_STRING_C = (
"""/* Copyright %s The Chromium Authors
* 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
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()
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."""
sourcefile = open(self.filename, "r")
file_lines = sourcefile.readlines()
sourcefile.close()
for linenum_to_delete in self.linenums_to_delete:
file_lines[linenum_to_delete] = "";
for linenum, sourcelines in self.lines_to_add.items():
sourcelines.sort()
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 typeinfo.name == "":
return
elif int(typeinfo.size) == 0:
return
elif typeinfo.source_location.filename.find("ppapi") == -1:
return
elif typeinfo.source_location.filename.find("GLES2") > -1:
return
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:
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):
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")
types32 = {}
types64 = {}
types_independent = {}
targets32 = options.targets32.split(',');
for target in targets32:
ProcessTarget(clang_command, target, types32)
targets64 = options.targets64.split(',');
for target in targets64:
ProcessTarget(clang_command, target, types64)
types_independent = {}
for typename, typeinfo32 in types32.items():
if (typename in types64):
typeinfo64 = types64[typename]
if (typeinfo64.size == typeinfo32.size):
types_independent[typename] = typeinfo32
del types32[typename]
del types64[typename]
elif (typeinfo32.arch_dependent or typeinfo64.arch_dependent):
pass
else:
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.")
file_patches = {}
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)
for name, typeinfo in types_independent.items():
if not IsMacroDefinedName(name) and typeinfo.size > 0:
sourcefile = typeinfo.source_location.filename
if sourcefile not in file_patches:
file_patches[sourcefile] = FilePatch(sourcefile)
file_patches[sourcefile].Add(ToAssertionCode(typeinfo),
typeinfo.source_location.end_line+1)
for filename, patch in file_patches.items():
patch.Apply()
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:]))