idl_thunk.py revision 2a99a7e74a7f215066514fe81d2bfa6639d9eddd
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""" Generator for C++ style thunks """ 7 8import glob 9import os 10import re 11import sys 12 13from idl_log import ErrOut, InfoOut, WarnOut 14from idl_node import IDLAttribute, IDLNode 15from idl_ast import IDLAst 16from idl_option import GetOption, Option, ParseOptions 17from idl_outfile import IDLOutFile 18from idl_parser import ParseFiles 19from idl_c_proto import CGen, GetNodeComments, CommentLines, Comment 20from idl_generator import Generator, GeneratorByFile 21 22Option('thunkroot', 'Base directory of output', 23 default=os.path.join('..', 'thunk')) 24 25 26class TGenError(Exception): 27 def __init__(self, msg): 28 self.value = msg 29 30 def __str__(self): 31 return repr(self.value) 32 33 34class ThunkBodyMetadata(object): 35 """Metadata about thunk body. Used for selecting which headers to emit.""" 36 def __init__(self): 37 self._apis = set() 38 self._includes = set() 39 40 def AddApi(self, api): 41 self._apis.add(api) 42 43 def Apis(self): 44 return self._apis 45 46 def AddInclude(self, include): 47 self._includes.add(include) 48 49 def Includes(self): 50 return self._includes 51 52 53def _GetBaseFileName(filenode): 54 """Returns the base name for output files, given the filenode. 55 56 Examples: 57 'dev/ppb_find_dev.h' -> 'ppb_find_dev' 58 'trusted/ppb_buffer_trusted.h' -> 'ppb_buffer_trusted' 59 """ 60 path, name = os.path.split(filenode.GetProperty('NAME')) 61 name = os.path.splitext(name)[0] 62 return name 63 64 65def _GetHeaderFileName(filenode): 66 """Returns the name for the header for this file.""" 67 path, name = os.path.split(filenode.GetProperty('NAME')) 68 name = os.path.splitext(name)[0] 69 if path: 70 header = "ppapi/c/%s/%s.h" % (path, name) 71 else: 72 header = "ppapi/c/%s.h" % name 73 return header 74 75 76def _GetThunkFileName(filenode, relpath): 77 """Returns the thunk file name.""" 78 path = os.path.split(filenode.GetProperty('NAME'))[0] 79 name = _GetBaseFileName(filenode) 80 # We don't reattach the path for thunk. 81 if relpath: name = os.path.join(relpath, name) 82 name = '%s%s' % (name, '_thunk.cc') 83 return name 84 85 86def _MakeEnterLine(filenode, interface, arg, handle_errors, callback, meta): 87 """Returns an EnterInstance/EnterResource string for a function.""" 88 if arg[0] == 'PP_Instance': 89 if callback is None: 90 return 'EnterInstance enter(%s);' % arg[1] 91 else: 92 return 'EnterInstance enter(%s, %s);' % (arg[1], callback) 93 elif arg[0] == 'PP_Resource': 94 api_name = interface.GetName() 95 if api_name.endswith('Trusted'): 96 api_name = api_name[:-len('Trusted')] 97 if api_name.endswith('_Dev'): 98 api_name = api_name[:-len('_Dev')] 99 api_name += '_API' 100 101 enter_type = 'EnterResource<%s>' % api_name 102 # The API header matches the file name, not the interface name. 103 api_basename = _GetBaseFileName(filenode) 104 if api_basename.endswith('_dev'): 105 # Clip off _dev suffix. 106 api_basename = api_basename[:-len('_dev')] 107 if api_basename.endswith('_trusted'): 108 # Clip off _trusted suffix. 109 api_basename = api_basename[:-len('_trusted')] 110 meta.AddApi(api_basename + '_api') 111 112 if callback is None: 113 return '%s enter(%s, %s);' % (enter_type, arg[1], 114 str(handle_errors).lower()) 115 else: 116 return '%s enter(%s, %s, %s);' % (enter_type, arg[1], 117 callback, 118 str(handle_errors).lower()) 119 else: 120 raise TGenError("Unknown type for _MakeEnterLine: %s" % arg[0]) 121 122 123def _GetShortName(interface, filter_suffixes): 124 """Return a shorter interface name that matches Is* and Create* functions.""" 125 parts = interface.GetName().split('_')[1:] 126 tail = parts[len(parts) - 1] 127 if tail in filter_suffixes: 128 parts = parts[:-1] 129 return ''.join(parts) 130 131 132def _IsTypeCheck(interface, node): 133 """Returns true if node represents a type-checking function.""" 134 return node.GetName() == 'Is%s' % _GetShortName(interface, ['Dev', 'Private']) 135 136 137def _GetCreateFuncName(interface): 138 """Returns the creation function name for an interface.""" 139 return 'Create%s' % _GetShortName(interface, ['Dev']) 140 141 142def _GetDefaultFailureValue(t): 143 """Returns the default failure value for a given type. 144 145 Returns None if no default failure value exists for the type. 146 """ 147 values = { 148 'PP_Bool': 'PP_FALSE', 149 'PP_Resource': '0', 150 'struct PP_Var': 'PP_MakeUndefined()', 151 'float': '0.0f', 152 'int32_t': 'enter.retval()', 153 'uint16_t': '0', 154 'uint32_t': '0', 155 'uint64_t': '0', 156 } 157 if t in values: 158 return values[t] 159 return None 160 161 162def _MakeCreateMemberBody(interface, member, args): 163 """Returns the body of a Create() function. 164 165 Args: 166 interface - IDLNode for the interface 167 member - IDLNode for member function 168 args - List of arguments for the Create() function 169 """ 170 if args[0][0] == 'PP_Resource': 171 body = 'Resource* object =\n' 172 body += ' PpapiGlobals::Get()->GetResourceTracker()->' 173 body += 'GetResource(%s);\n' % args[0][1] 174 body += 'if (!object)\n' 175 body += ' return 0;\n' 176 body += 'EnterResourceCreation enter(object->pp_instance());\n' 177 elif args[0][0] == 'PP_Instance': 178 body = 'EnterResourceCreation enter(%s);\n' % args[0][1] 179 else: 180 raise TGenError('Unknown arg type for Create(): %s' % args[0][0]) 181 182 body += 'if (enter.failed())\n' 183 body += ' return 0;\n' 184 arg_list = ', '.join([a[1] for a in args]) 185 if member.GetProperty('create_func'): 186 create_func = member.GetProperty('create_func') 187 else: 188 create_func = _GetCreateFuncName(interface) 189 body += 'return enter.functions()->%s(%s);' % (create_func, 190 arg_list) 191 return body 192 193 194def _MakeNormalMemberBody(filenode, release, node, member, rtype, args, 195 include_version, meta): 196 """Returns the body of a typical function. 197 198 Args: 199 filenode - IDLNode for the file 200 release - release to generate body for 201 node - IDLNode for the interface 202 member - IDLNode for the member function 203 rtype - Return type for the member function 204 args - List of 4-tuple arguments for the member function 205 include_version - whether to include the version in the invocation 206 meta - ThunkBodyMetadata for header hints 207 """ 208 is_callback_func = args[len(args) - 1][0] == 'struct PP_CompletionCallback' 209 210 if is_callback_func: 211 call_args = args[:-1] + [('', 'enter.callback()', '', '')] 212 meta.AddInclude('ppapi/c/pp_completion_callback.h') 213 else: 214 call_args = args 215 216 if args[0][0] == 'PP_Instance': 217 call_arglist = ', '.join(a[1] for a in call_args) 218 function_container = 'functions' 219 else: 220 call_arglist = ', '.join(a[1] for a in call_args[1:]) 221 function_container = 'object' 222 223 function_name = member.GetName() 224 if include_version: 225 version = node.GetVersion(release).replace('.', '_') 226 function_name += version 227 228 invocation = 'enter.%s()->%s(%s)' % (function_container, 229 function_name, 230 call_arglist) 231 232 handle_errors = not (member.GetProperty('report_errors') == 'False') 233 if is_callback_func: 234 body = '%s\n' % _MakeEnterLine(filenode, node, args[0], handle_errors, 235 args[len(args) - 1][1], meta) 236 body += 'if (enter.failed())\n' 237 value = member.GetProperty('on_failure') 238 if value is None: 239 value = 'enter.retval()' 240 body += ' return %s;\n' % value 241 body += 'return enter.SetResult(%s);' % invocation 242 elif rtype == 'void': 243 body = '%s\n' % _MakeEnterLine(filenode, node, args[0], handle_errors, 244 None, meta) 245 body += 'if (enter.succeeded())\n' 246 body += ' %s;' % invocation 247 else: 248 value = member.GetProperty('on_failure') 249 if value is None: 250 value = _GetDefaultFailureValue(rtype) 251 if value is None: 252 raise TGenError('No default value for rtype %s' % rtype) 253 254 body = '%s\n' % _MakeEnterLine(filenode, node, args[0], handle_errors, 255 None, meta) 256 body += 'if (enter.failed())\n' 257 body += ' return %s;\n' % value 258 body += 'return %s;' % invocation 259 return body 260 261 262def DefineMember(filenode, node, member, release, include_version, meta): 263 """Returns a definition for a member function of an interface. 264 265 Args: 266 filenode - IDLNode for the file 267 node - IDLNode for the interface 268 member - IDLNode for the member function 269 release - release to generate 270 include_version - include the version in emitted function name. 271 meta - ThunkMetadata for header hints 272 Returns: 273 A string with the member definition. 274 """ 275 cgen = CGen() 276 rtype, name, arrays, args = cgen.GetComponents(member, release, 'return') 277 278 if _IsTypeCheck(node, member): 279 body = '%s\n' % _MakeEnterLine(filenode, node, args[0], False, None, meta) 280 body += 'return PP_FromBool(enter.succeeded());' 281 elif member.GetName() == 'Create': 282 body = _MakeCreateMemberBody(node, member, args) 283 else: 284 body = _MakeNormalMemberBody(filenode, release, node, member, rtype, args, 285 include_version, meta) 286 287 signature = cgen.GetSignature(member, release, 'return', func_as_ptr=False, 288 include_version=include_version) 289 return '%s\n%s\n}' % (cgen.Indent('%s {' % signature, tabs=0), 290 cgen.Indent(body, tabs=1)) 291 292 293def _IsNewestMember(member, members, releases): 294 """Returns true if member is the newest node with its name in members. 295 296 Currently, every node in the AST only has one version. This means that we 297 will have two sibling nodes with the same name to represent different 298 versions. 299 See http://crbug.com/157017 . 300 301 Special handling is required for nodes which share their name with others, 302 but aren't the newest version in the IDL. 303 304 Args: 305 member - The member which is checked if it's newest 306 members - The list of members to inspect 307 releases - The set of releases to check for versions in. 308 """ 309 build_list = member.GetUniqueReleases(releases) 310 release = build_list[0] # Pick the oldest release. 311 same_name_siblings = filter( 312 lambda n: str(n) == str(member) and n != member, members) 313 314 for s in same_name_siblings: 315 sibling_build_list = s.GetUniqueReleases(releases) 316 sibling_release = sibling_build_list[0] 317 if sibling_release > release: 318 return False 319 return True 320 321 322class TGen(GeneratorByFile): 323 def __init__(self): 324 Generator.__init__(self, 'Thunk', 'tgen', 'Generate the C++ thunk.') 325 326 def GenerateFile(self, filenode, releases, options): 327 savename = _GetThunkFileName(filenode, GetOption('thunkroot')) 328 my_min, my_max = filenode.GetMinMax(releases) 329 if my_min > releases[-1] or my_max < releases[0]: 330 if os.path.isfile(savename): 331 print "Removing stale %s for this range." % filenode.GetName() 332 os.remove(os.path.realpath(savename)) 333 return False 334 do_generate = filenode.GetProperty('generate_thunk') 335 if not do_generate: 336 return False 337 338 thunk_out = IDLOutFile(savename) 339 body, meta = self.GenerateBody(thunk_out, filenode, releases, options) 340 self.WriteHead(thunk_out, filenode, releases, options, meta) 341 thunk_out.Write('\n\n'.join(body)) 342 self.WriteTail(thunk_out, filenode, releases, options) 343 return thunk_out.Close() 344 345 def WriteHead(self, out, filenode, releases, options, meta): 346 __pychecker__ = 'unusednames=options' 347 cgen = CGen() 348 349 cright_node = filenode.GetChildren()[0] 350 assert(cright_node.IsA('Copyright')) 351 out.Write('%s\n' % cgen.Copyright(cright_node, cpp_style=True)) 352 353 # Wrap the From ... modified ... comment if it would be >80 characters. 354 from_text = 'From %s' % ( 355 filenode.GetProperty('NAME').replace(os.sep,'/')) 356 modified_text = 'modified %s.' % ( 357 filenode.GetProperty('DATETIME')) 358 if len(from_text) + len(modified_text) < 74: 359 out.Write('// %s %s\n\n' % (from_text, modified_text)) 360 else: 361 out.Write('// %s,\n// %s\n\n' % (from_text, modified_text)) 362 363 364 # TODO(teravest): Don't emit includes we don't need. 365 includes = ['ppapi/c/pp_errors.h', 366 'ppapi/shared_impl/tracked_callback.h', 367 'ppapi/thunk/enter.h', 368 'ppapi/thunk/ppb_instance_api.h', 369 'ppapi/thunk/resource_creation_api.h', 370 'ppapi/thunk/thunk.h'] 371 includes.append(_GetHeaderFileName(filenode)) 372 for api in meta.Apis(): 373 includes.append('ppapi/thunk/%s.h' % api.lower()) 374 for i in meta.Includes(): 375 includes.append(i) 376 for include in sorted(includes): 377 out.Write('#include "%s"\n' % include) 378 out.Write('\n') 379 out.Write('namespace ppapi {\n') 380 out.Write('namespace thunk {\n') 381 out.Write('\n') 382 out.Write('namespace {\n') 383 out.Write('\n') 384 385 def GenerateBody(self, out, filenode, releases, options): 386 """Generates a member function lines to be written and metadata. 387 388 Returns a tuple of (body, meta) where: 389 body - a list of lines with member function bodies 390 meta - a ThunkMetadata instance for hinting which headers are needed. 391 """ 392 __pychecker__ = 'unusednames=options' 393 out_members = [] 394 meta = ThunkBodyMetadata() 395 for node in filenode.GetListOf('Interface'): 396 # Skip if this node is not in this release 397 if not node.InReleases(releases): 398 print "Skipping %s" % node 399 continue 400 401 # Generate Member functions 402 if node.IsA('Interface'): 403 members = node.GetListOf('Member') 404 for child in members: 405 build_list = child.GetUniqueReleases(releases) 406 # We have to filter out releases this node isn't in. 407 build_list = filter(lambda r: child.InReleases([r]), build_list) 408 if len(build_list) == 0: 409 continue 410 assert(len(build_list) == 1) 411 release = build_list[-1] 412 include_version = not _IsNewestMember(child, members, releases) 413 member = DefineMember(filenode, node, child, release, include_version, 414 meta) 415 if not member: 416 continue 417 out_members.append(member) 418 return (out_members, meta) 419 420 def WriteTail(self, out, filenode, releases, options): 421 __pychecker__ = 'unusednames=options' 422 cgen = CGen() 423 424 version_list = [] 425 out.Write('\n\n') 426 for node in filenode.GetListOf('Interface'): 427 build_list = node.GetUniqueReleases(releases) 428 for build in build_list: 429 version = node.GetVersion(build).replace('.', '_') 430 thunk_name = 'g_' + node.GetName().lower() + '_thunk_' + \ 431 version 432 thunk_type = '_'.join((node.GetName(), version)) 433 version_list.append((thunk_type, thunk_name)) 434 435 declare_line = 'const %s %s = {' % (thunk_type, thunk_name) 436 if len(declare_line) > 80: 437 declare_line = 'const %s\n %s = {' % (thunk_type, thunk_name) 438 out.Write('%s\n' % declare_line) 439 generated_functions = [] 440 members = node.GetListOf('Member') 441 for child in members: 442 rtype, name, arrays, args = cgen.GetComponents( 443 child, build, 'return') 444 if not _IsNewestMember(child, members, releases): 445 version = node.GetVersion(build).replace('.', '_') 446 name += '_' + version 447 if child.InReleases([build]): 448 generated_functions.append(name) 449 out.Write(',\n'.join([' &%s' % f for f in generated_functions])) 450 out.Write('\n};\n\n') 451 452 out.Write('} // namespace\n') 453 out.Write('\n') 454 for thunk_type, thunk_name in version_list: 455 thunk_decl = 'const %s* Get%s_Thunk() {\n' % (thunk_type, thunk_type) 456 if len(thunk_decl) > 80: 457 thunk_decl = 'const %s*\n Get%s_Thunk() {\n' % (thunk_type, 458 thunk_type) 459 out.Write(thunk_decl) 460 out.Write(' return &%s;\n' % thunk_name) 461 out.Write('}\n') 462 out.Write('\n') 463 out.Write('} // namespace thunk\n') 464 out.Write('} // namespace ppapi\n') 465 466 467tgen = TGen() 468 469 470def Main(args): 471 # Default invocation will verify the golden files are unchanged. 472 failed = 0 473 if not args: 474 args = ['--wnone', '--diff', '--test', '--thunkroot=.'] 475 476 ParseOptions(args) 477 478 idldir = os.path.split(sys.argv[0])[0] 479 idldir = os.path.join(idldir, 'test_thunk', '*.idl') 480 filenames = glob.glob(idldir) 481 ast = ParseFiles(filenames) 482 if tgen.GenerateRange(ast, ['M13', 'M14'], {}): 483 print "Golden file for M13-M14 failed." 484 failed = 1 485 else: 486 print "Golden file for M13-M14 passed." 487 488 return failed 489 490 491if __name__ == '__main__': 492 sys.exit(Main(sys.argv[1:])) 493