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
7have appropriate size checking.
8"""
9
10import optparse
11import os
12import subprocess
13import sys
14
15
16# The string that the PrintNamesAndSizes plugin uses to indicate a type is
17# expected to have architecture-dependent size.
18ARCH_DEPENDENT_STRING = "ArchDependentSize"
19
20
21COPYRIGHT_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
33class 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
42class 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
89class 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
142def 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
183def 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
199def 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
222def 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
227def 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
244def 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
423if __name__ == '__main__':
424    sys.exit(main(sys.argv[1:]))
425