| OLD | NEW |
| (Empty) | |
| 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._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 |
| 53 def _GetBaseFileName(filenode): |
| 54 """Returns the base name for output files, given the filenode. |
| 55 |
| 56 Examples: |
| 57 'dev/ppb_find_dev.h' -> 'ppb_find' |
| 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 if name.endswith('_dev'): |
| 63 # Clip off _dev suffix. |
| 64 name = name[:-len('_dev')] |
| 65 return name |
| 66 |
| 67 |
| 68 def _GetHeaderFileName(filenode): |
| 69 """Returns the name for the header for this file.""" |
| 70 path, name = os.path.split(filenode.GetProperty('NAME')) |
| 71 name = os.path.splitext(name)[0] |
| 72 if path: |
| 73 header = "ppapi/c/%s/%s.h" % (path, name) |
| 74 else: |
| 75 header = "ppapi/c/%s.h" % name |
| 76 return header |
| 77 |
| 78 |
| 79 def _GetThunkFileName(filenode, relpath): |
| 80 """Returns the thunk file name.""" |
| 81 path = os.path.split(filenode.GetProperty('NAME'))[0] |
| 82 name = _GetBaseFileName(filenode) |
| 83 # We don't reattach the path for thunk. |
| 84 if relpath: name = os.path.join(relpath, name) |
| 85 name = '%s%s' % (name, '_thunk.cc') |
| 86 return name |
| 87 |
| 88 |
| 89 def _MakeEnterLine(filenode, interface, arg, handle_errors, callback, meta): |
| 90 """Returns an EnterInstance/EnterResource string for a function.""" |
| 91 if arg[0] == 'PP_Instance': |
| 92 if callback is None: |
| 93 return 'EnterInstance enter(%s);' % arg[1] |
| 94 else: |
| 95 return 'EnterInstance enter(%s, %s);' % (arg[1], callback) |
| 96 elif arg[0] == 'PP_Resource': |
| 97 api_name = interface.GetName() |
| 98 if api_name.endswith('_Dev'): |
| 99 api_name = api_name[:-len('_Dev')] |
| 100 api_name += '_API' |
| 101 |
| 102 enter_type = 'EnterResource<%s>' % api_name |
| 103 # The API header matches the file name, not the interface name. |
| 104 meta.AddApi(_GetBaseFileName(filenode) + '_api') |
| 105 |
| 106 if callback is None: |
| 107 return '%s enter(%s, %s);' % (enter_type, arg[1], |
| 108 str(handle_errors).lower()) |
| 109 else: |
| 110 return '%s enter(%s, %s, %s);' % (enter_type, arg[1], |
| 111 callback, |
| 112 str(handle_errors).lower()) |
| 113 else: |
| 114 raise TGenError("Unknown type for _MakeEnterLine: %s" % arg[0]) |
| 115 |
| 116 |
| 117 def _GetShortName(interface, filter_suffixes): |
| 118 """Return a shorter interface name that matches Is* and Create* functions.""" |
| 119 parts = interface.GetName().split('_')[1:] |
| 120 tail = parts[len(parts) - 1] |
| 121 if tail in filter_suffixes: |
| 122 parts = parts[:-1] |
| 123 return ''.join(parts) |
| 124 |
| 125 |
| 126 def _IsTypeCheck(interface, node): |
| 127 """Returns true if node represents a type-checking function.""" |
| 128 return node.GetName() == 'Is%s' % _GetShortName(interface, ['Dev', 'Private']) |
| 129 |
| 130 |
| 131 def _GetCreateFuncName(interface): |
| 132 """Returns the creation function name for an interface.""" |
| 133 return 'Create%s' % _GetShortName(interface, ['Dev']) |
| 134 |
| 135 |
| 136 def _GetDefaultFailureValue(t): |
| 137 """Returns the default failure value for a given type. |
| 138 |
| 139 Returns None if no default failure value exists for the type. |
| 140 """ |
| 141 values = { |
| 142 'PP_Bool': 'PP_FALSE', |
| 143 'PP_Resource': '0', |
| 144 'struct PP_Var': 'PP_MakeUndefined()', |
| 145 'int32_t': 'enter.retval()', |
| 146 'uint16_t': '0', |
| 147 'uint32_t': '0', |
| 148 'uint64_t': '0', |
| 149 } |
| 150 if t in values: |
| 151 return values[t] |
| 152 return None |
| 153 |
| 154 |
| 155 def _MakeCreateMemberBody(interface, member, args): |
| 156 """Returns the body of a Create() function. |
| 157 |
| 158 Args: |
| 159 interface - IDLNode for the interface |
| 160 member - IDLNode for member function |
| 161 args - List of arguments for the Create() function |
| 162 """ |
| 163 if args[0][0] == 'PP_Resource': |
| 164 body = ' Resource* object =\n' |
| 165 body += ' PpapiGlobals::Get()->GetResourceTracker()->' |
| 166 body += 'GetResource(%s);\n' % args[0][1] |
| 167 body += ' if (!object)\n' |
| 168 body += ' return 0;\n' |
| 169 body += ' EnterResourceCreation enter(object->pp_instance());\n' |
| 170 elif args[0][0] == 'PP_Instance': |
| 171 body = ' EnterResourceCreation enter(%s);\n' % args[0][1] |
| 172 else: |
| 173 raise TGenError('Unknown arg type for Create(): %s' % args[0][0]) |
| 174 |
| 175 body += ' if (enter.failed())\n' |
| 176 body += ' return 0;\n' |
| 177 arg_list = ', '.join([a[1] for a in args]) |
| 178 if member.GetProperty('create_func'): |
| 179 create_func = member.GetProperty('create_func') |
| 180 else: |
| 181 create_func = _GetCreateFuncName(interface) |
| 182 body += ' return enter.functions()->%s(%s);' % (create_func, |
| 183 arg_list) |
| 184 return body |
| 185 |
| 186 |
| 187 def _MakeNormalMemberBody(filenode, node, member, rtype, args, meta): |
| 188 """Returns the body of a typical function. |
| 189 |
| 190 Args: |
| 191 filenode - IDLNode for the file |
| 192 node - IDLNode for the interface |
| 193 member - IDLNode for the member function |
| 194 rtype - Return type for the member function |
| 195 args - List of 4-tuple arguments for the member function |
| 196 meta - ThunkBodyMetadata for header hints |
| 197 """ |
| 198 is_callback_func = args[len(args) - 1][0] == 'struct PP_CompletionCallback' |
| 199 |
| 200 if is_callback_func: |
| 201 call_args = args[:-1] + [('', 'enter.callback()', '', '')] |
| 202 meta.AddInclude('ppapi/c/pp_completion_callback.h') |
| 203 else: |
| 204 call_args = args |
| 205 |
| 206 if args[0][0] == 'PP_Instance': |
| 207 call_arglist = ', '.join(a[1] for a in call_args) |
| 208 function_container = 'functions' |
| 209 else: |
| 210 call_arglist = ', '.join(a[1] for a in call_args[1:]) |
| 211 function_container = 'object' |
| 212 |
| 213 invocation = 'enter.%s()->%s(%s)' % (function_container, |
| 214 member.GetName(), |
| 215 call_arglist) |
| 216 |
| 217 handle_errors = not (member.GetProperty('report_errors') == 'False') |
| 218 if is_callback_func: |
| 219 body = ' %s\n' % _MakeEnterLine(filenode, node, args[0], handle_errors, |
| 220 args[len(args) - 1][1], meta) |
| 221 body += ' if (enter.failed())\n' |
| 222 value = member.GetProperty('on_failure') |
| 223 if value is None: |
| 224 value = 'enter.retval()' |
| 225 body += ' return %s;\n' % value |
| 226 body += ' return enter.SetResult(%s);' % invocation |
| 227 elif rtype == 'void': |
| 228 body = ' %s\n' % _MakeEnterLine(filenode, node, args[0], handle_errors, |
| 229 None, meta) |
| 230 body += ' if (enter.succeeded())\n' |
| 231 body += ' %s;' % invocation |
| 232 else: |
| 233 value = member.GetProperty('on_failure') |
| 234 if value is None: |
| 235 value = _GetDefaultFailureValue(rtype) |
| 236 if value is None: |
| 237 raise TGenError('No default value for rtype %s' % rtype) |
| 238 |
| 239 body = ' %s\n' % _MakeEnterLine(filenode, node, args[0], handle_errors, |
| 240 None, meta) |
| 241 body += ' if (enter.failed())\n' |
| 242 body += ' return %s;\n' % value |
| 243 body += ' return %s;' % invocation |
| 244 return body |
| 245 |
| 246 |
| 247 def DefineMember(filenode, node, member, release, include_version, meta): |
| 248 """Returns a definition for a member function of an interface. |
| 249 |
| 250 Args: |
| 251 filenode - IDLNode for the file |
| 252 node - IDLNode for the interface |
| 253 member - IDLNode for the member function |
| 254 release - release to generate |
| 255 include_version - include the version in emitted function name. |
| 256 meta - ThunkMetadata for header hints |
| 257 Returns: |
| 258 A string with the member definition. |
| 259 """ |
| 260 cgen = CGen() |
| 261 rtype, name, arrays, args = cgen.GetComponents(member, release, 'return') |
| 262 |
| 263 if _IsTypeCheck(node, member): |
| 264 body = ' %s\n' % _MakeEnterLine(filenode, node, args[0], False, None, meta) |
| 265 body += ' return PP_FromBool(enter.succeeded());' |
| 266 elif member.GetName() == 'Create': |
| 267 body = _MakeCreateMemberBody(node, member, args) |
| 268 else: |
| 269 body = _MakeNormalMemberBody(filenode, node, member, rtype, args, meta) |
| 270 |
| 271 signature = cgen.GetSignature(member, release, 'return', func_as_ptr=False, |
| 272 include_version=include_version) |
| 273 member_code = '%s {\n%s\n}' % (signature, body) |
| 274 return cgen.Indent(member_code, tabs=0) |
| 275 |
| 276 |
| 277 class TGen(GeneratorByFile): |
| 278 def __init__(self): |
| 279 Generator.__init__(self, 'Thunk', 'tgen', 'Generate the C++ thunk.') |
| 280 |
| 281 def GenerateFile(self, filenode, releases, options): |
| 282 savename = _GetThunkFileName(filenode, GetOption('thunkroot')) |
| 283 my_min, my_max = filenode.GetMinMax(releases) |
| 284 if my_min > releases[-1] or my_max < releases[0]: |
| 285 if os.path.isfile(savename): |
| 286 print "Removing stale %s for this range." % filenode.GetName() |
| 287 os.remove(os.path.realpath(savename)) |
| 288 return False |
| 289 do_generate = filenode.GetProperty('generate_thunk') |
| 290 if not do_generate: |
| 291 return False |
| 292 |
| 293 thunk_out = IDLOutFile(savename) |
| 294 body, meta = self.GenerateBody(thunk_out, filenode, releases, options) |
| 295 self.WriteHead(thunk_out, filenode, releases, options, meta) |
| 296 thunk_out.Write('\n\n'.join(body)) |
| 297 self.WriteTail(thunk_out, filenode, releases, options) |
| 298 return thunk_out.Close() |
| 299 |
| 300 def WriteHead(self, out, filenode, releases, options, meta): |
| 301 __pychecker__ = 'unusednames=options' |
| 302 cgen = CGen() |
| 303 |
| 304 cright_node = filenode.GetChildren()[0] |
| 305 assert(cright_node.IsA('Copyright')) |
| 306 out.Write('%s\n' % cgen.Copyright(cright_node, cpp_style=True)) |
| 307 |
| 308 # Wrap the From ... modified ... comment if it would be >80 characters. |
| 309 from_text = 'From %s' % ( |
| 310 filenode.GetProperty('NAME').replace(os.sep,'/')) |
| 311 modified_text = 'modified %s.' % ( |
| 312 filenode.GetProperty('DATETIME')) |
| 313 if len(from_text) + len(modified_text) < 74: |
| 314 out.Write('// %s %s\n\n' % (from_text, modified_text)) |
| 315 else: |
| 316 out.Write('// %s,\n// %s\n\n' % (from_text, modified_text)) |
| 317 |
| 318 |
| 319 # TODO(teravest): Don't emit includes we don't need. |
| 320 includes = ['ppapi/c/pp_errors.h', |
| 321 'ppapi/shared_impl/tracked_callback.h', |
| 322 'ppapi/thunk/enter.h', |
| 323 'ppapi/thunk/ppb_instance_api.h', |
| 324 'ppapi/thunk/resource_creation_api.h', |
| 325 'ppapi/thunk/thunk.h'] |
| 326 includes.append(_GetHeaderFileName(filenode)) |
| 327 for api in meta.Apis(): |
| 328 includes.append('ppapi/thunk/%s.h' % api.lower()) |
| 329 for i in meta.Includes(): |
| 330 includes.append(i) |
| 331 for include in sorted(includes): |
| 332 out.Write('#include "%s"\n' % include) |
| 333 out.Write('\n') |
| 334 out.Write('namespace ppapi {\n') |
| 335 out.Write('namespace thunk {\n') |
| 336 out.Write('\n') |
| 337 out.Write('namespace {\n') |
| 338 out.Write('\n') |
| 339 |
| 340 def GenerateBody(self, out, filenode, releases, options): |
| 341 """Generates a member function lines to be written and metadata. |
| 342 |
| 343 Returns a tuple of (body, meta) where: |
| 344 body - a list of lines with member function bodies |
| 345 meta - a ThunkMetadata instance for hinting which headers are needed. |
| 346 """ |
| 347 __pychecker__ = 'unusednames=options' |
| 348 members = [] |
| 349 meta = ThunkBodyMetadata() |
| 350 for node in filenode.GetListOf('Interface'): |
| 351 # Skip if this node is not in this release |
| 352 if not node.InReleases(releases): |
| 353 print "Skipping %s" % node |
| 354 continue |
| 355 |
| 356 # Generate Member functions |
| 357 if node.IsA('Interface'): |
| 358 for child in node.GetListOf('Member'): |
| 359 build_list = child.GetUniqueReleases(releases) |
| 360 # We have to filter out releases this node isn't in. |
| 361 build_list = filter(lambda r: child.InReleases([r]), build_list) |
| 362 if len(build_list) == 0: |
| 363 continue |
| 364 release = build_list[-1] # Pick the newest release. |
| 365 member = DefineMember(filenode, node, child, release, False, meta) |
| 366 if not member: |
| 367 continue |
| 368 members.append(member) |
| 369 for build in build_list[:-1]: |
| 370 member = DefineMember(filenode, node, child, build, True, meta) |
| 371 if not member: |
| 372 continue |
| 373 members.append(member) |
| 374 return (members, meta) |
| 375 |
| 376 def WriteTail(self, out, filenode, releases, options): |
| 377 __pychecker__ = 'unusednames=options' |
| 378 cgen = CGen() |
| 379 |
| 380 version_list = [] |
| 381 out.Write('\n\n') |
| 382 for node in filenode.GetListOf('Interface'): |
| 383 build_list = node.GetUniqueReleases(releases) |
| 384 for build in build_list: |
| 385 version = node.GetVersion(build).replace('.', '_') |
| 386 thunk_name = 'g_' + node.GetName().lower() + '_thunk_' + \ |
| 387 version |
| 388 thunk_type = '_'.join((node.GetName(), version)) |
| 389 version_list.append((thunk_type, thunk_name)) |
| 390 |
| 391 out.Write('const %s %s = {\n' % (thunk_type, thunk_name)) |
| 392 generated_functions = [] |
| 393 for child in node.GetListOf('Member'): |
| 394 rtype, name, arrays, args = cgen.GetComponents( |
| 395 child, build, 'return') |
| 396 if child.InReleases([build]): |
| 397 generated_functions.append(name) |
| 398 out.Write(',\n'.join([' &%s' % f for f in generated_functions])) |
| 399 out.Write('\n};\n\n') |
| 400 |
| 401 out.Write('} // namespace\n') |
| 402 out.Write('\n') |
| 403 for thunk_type, thunk_name in version_list: |
| 404 thunk_decl = 'const %s* Get%s_Thunk() {\n' % (thunk_type, thunk_type) |
| 405 if len(thunk_decl) > 80: |
| 406 thunk_decl = 'const %s*\n Get%s_Thunk() {\n' % (thunk_type, |
| 407 thunk_type) |
| 408 out.Write(thunk_decl) |
| 409 out.Write(' return &%s;\n' % thunk_name) |
| 410 out.Write('}\n') |
| 411 out.Write('\n') |
| 412 out.Write('} // namespace thunk\n') |
| 413 out.Write('} // namespace ppapi\n') |
| 414 |
| 415 |
| 416 tgen = TGen() |
| 417 |
| 418 |
| 419 def Main(args): |
| 420 # Default invocation will verify the golden files are unchanged. |
| 421 failed = 0 |
| 422 if not args: |
| 423 args = ['--wnone', '--diff', '--test', '--thunkroot=.'] |
| 424 |
| 425 ParseOptions(args) |
| 426 |
| 427 idldir = os.path.split(sys.argv[0])[0] |
| 428 idldir = os.path.join(idldir, 'test_thunk', '*.idl') |
| 429 filenames = glob.glob(idldir) |
| 430 ast = ParseFiles(filenames) |
| 431 if tgen.GenerateRange(ast, ['M13', 'M14'], {}): |
| 432 print "Golden file for M13-M14 failed." |
| 433 failed = 1 |
| 434 else: |
| 435 print "Golden file for M13-M14 passed." |
| 436 |
| 437 return failed |
| 438 |
| 439 |
| 440 if __name__ == '__main__': |
| 441 sys.exit(Main(sys.argv[1:])) |
| OLD | NEW |