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