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