Home | History | Annotate | Download | only in generators
      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