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