[go: nahoru, domu]

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