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._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
60def _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
72def _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
83def _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
93def _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
105def _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
116def _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
153def _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
162def _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
169def _GetCreateFuncName(interface):
170  """Returns the creation function name for an interface."""
171  return 'Create%s' % _GetShortName(interface, ['Dev'])
172
173
174def _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
195def _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
227def _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
251def _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
348def 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
386def _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
415class 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
566tgen = TGen()
567
568
569def 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
590if __name__ == '__main__':
591  sys.exit(Main(sys.argv[1:]))
592