Update To 11.40.268.0
[platform/framework/web/crosswalk.git] / src / ppapi / generators / idl_thunk.py
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     thunk_out.ClangFormat()
440     return thunk_out.Close()
441
442   def WriteHead(self, out, filenode, releases, options, meta):
443     __pychecker__ = 'unusednames=options'
444     cgen = CGen()
445
446     cright_node = filenode.GetChildren()[0]
447     assert(cright_node.IsA('Copyright'))
448     out.Write('%s\n' % cgen.Copyright(cright_node, cpp_style=True))
449
450     from_text = 'From %s' % (
451         filenode.GetProperty('NAME').replace(os.sep,'/'))
452     modified_text = 'modified %s.' % (
453         filenode.GetProperty('DATETIME'))
454     out.Write('// %s %s\n\n' % (from_text, modified_text))
455
456     if meta.BuiltinIncludes():
457       for include in sorted(meta.BuiltinIncludes()):
458         out.Write('#include <%s>\n' % include)
459       out.Write('\n')
460
461     # TODO(teravest): Don't emit includes we don't need.
462     includes = ['ppapi/c/pp_errors.h',
463                 'ppapi/shared_impl/tracked_callback.h',
464                 'ppapi/thunk/enter.h',
465                 'ppapi/thunk/ppapi_thunk_export.h']
466     includes.append(_GetHeaderFileName(filenode))
467     for api in meta.Apis():
468       includes.append('%s' % api.lower())
469     for i in meta.Includes():
470       includes.append(i)
471     for include in sorted(includes):
472       out.Write('#include "%s"\n' % include)
473     out.Write('\n')
474     out.Write('namespace ppapi {\n')
475     out.Write('namespace thunk {\n')
476     out.Write('\n')
477     out.Write('namespace {\n')
478     out.Write('\n')
479
480   def GenerateBody(self, out, filenode, releases, options):
481     """Generates a member function lines to be written and metadata.
482
483     Returns a tuple of (body, meta) where:
484       body - a list of lines with member function bodies
485       meta - a ThunkMetadata instance for hinting which headers are needed.
486     """
487     __pychecker__ = 'unusednames=options'
488     out_members = []
489     meta = ThunkBodyMetadata()
490     for node in filenode.GetListOf('Interface'):
491       # Skip if this node is not in this release
492       if not node.InReleases(releases):
493         print "Skipping %s" % node
494         continue
495
496       # Generate Member functions
497       if node.IsA('Interface'):
498         members = node.GetListOf('Member')
499         for child in members:
500           build_list = child.GetUniqueReleases(releases)
501           # We have to filter out releases this node isn't in.
502           build_list = filter(lambda r: child.InReleases([r]), build_list)
503           if len(build_list) == 0:
504             continue
505           release = build_list[-1]
506           include_version = not _IsNewestMember(child, members, releases)
507           member = DefineMember(filenode, node, child, release, include_version,
508                                 meta)
509           if not member:
510             continue
511           out_members.append(member)
512     return (out_members, meta)
513
514   def WriteTail(self, out, filenode, releases, options):
515     __pychecker__ = 'unusednames=options'
516     cgen = CGen()
517
518     version_list = []
519     out.Write('\n\n')
520     for node in filenode.GetListOf('Interface'):
521       build_list = node.GetUniqueReleases(releases)
522       for build in build_list:
523         version = node.GetVersion(build).replace('.', '_')
524         thunk_name = 'g_' + node.GetName().lower() + '_thunk_' + \
525                       version
526         thunk_type = '_'.join((node.GetName(), version))
527         version_list.append((thunk_type, thunk_name))
528
529         out.Write('const %s %s = {\n' % (thunk_type, thunk_name))
530         generated_functions = []
531         members = node.GetListOf('Member')
532         for child in members:
533           rtype, name, arrays, args = cgen.GetComponents(
534               child, build, 'return')
535           if child.InReleases([build]):
536             if not _IsNewestMember(child, members, releases):
537               version = child.GetVersion(
538                   child.first_release[build]).replace('.', '_')
539               name += '_' + version
540             generated_functions.append(name)
541         out.Write(',\n'.join(['  &%s' % f for f in generated_functions]))
542         out.Write('\n};\n\n')
543
544     out.Write('}  // namespace\n')
545     out.Write('\n')
546     for thunk_type, thunk_name in version_list:
547       out.Write('PPAPI_THUNK_EXPORT const %s* Get%s_Thunk() {\n' %
548                     (thunk_type, thunk_type))
549       out.Write('  return &%s;\n' % thunk_name)
550       out.Write('}\n')
551       out.Write('\n')
552     out.Write('}  // namespace thunk\n')
553     out.Write('}  // namespace ppapi\n')
554
555
556 tgen = TGen()
557
558
559 def Main(args):
560   # Default invocation will verify the golden files are unchanged.
561   failed = 0
562   if not args:
563     args = ['--wnone', '--diff', '--test', '--thunkroot=.']
564
565   ParseOptions(args)
566
567   idldir = os.path.split(sys.argv[0])[0]
568   idldir = os.path.join(idldir, 'test_thunk', '*.idl')
569   filenames = glob.glob(idldir)
570   ast = ParseFiles(filenames)
571   if tgen.GenerateRange(ast, ['M13', 'M14', 'M15'], {}):
572     print "Golden file for M13-M15 failed."
573     failed = 1
574   else:
575     print "Golden file for M13-M15 passed."
576
577   return failed
578
579
580 if __name__ == '__main__':
581   sys.exit(Main(sys.argv[1:]))