generate_ppapi_size_checks.py 18 KB

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