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.
6 """ Generator for C++ style thunks """
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
22 Option('thunkroot', 'Base directory of output',
23 default
=os
.path
.join('..', 'thunk'))
26 class TGenError(Exception):
27 def __init__(self
, msg
):
31 return repr(self
.value
)
34 class ThunkBodyMetadata(object):
35 """Metadata about thunk body. Used for selecting which headers to emit."""
38 self
._builtin
_includes
= set()
39 self
._includes
= set()
41 def AddApi(self
, api
):
47 def AddInclude(self
, include
):
48 self
._includes
.add(include
)
53 def AddBuiltinInclude(self
, include
):
54 self
._builtin
_includes
.add(include
)
56 def BuiltinIncludes(self
):
57 return self
._builtin
_includes
60 def _GetBaseFileName(filenode
):
61 """Returns the base name for output files, given the filenode.
64 'dev/ppb_find_dev.h' -> 'ppb_find_dev'
65 'trusted/ppb_buffer_trusted.h' -> 'ppb_buffer_trusted'
67 path
, name
= os
.path
.split(filenode
.GetProperty('NAME'))
68 name
= os
.path
.splitext(name
)[0]
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]
77 header
= "ppapi/c/%s/%s.h" % (path
, name
)
79 header
= "ppapi/c/%s.h" % name
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')
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')]
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')]
116 def _MakeEnterLine(filenode
, interface
, member
, arg
, handle_errors
, callback
,
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')
125 manually_provided_api
= False
127 if arg
[0] == 'PP_Instance':
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
)
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
))
143 return '%s enter(%s, %s);' % (enter_type
, arg
[1],
144 str(handle_errors
).lower())
146 return '%s enter(%s, %s, %s);' % (enter_type
, arg
[1],
148 str(handle_errors
).lower())
150 raise TGenError("Unknown type for _MakeEnterLine: %s" % arg
[0])
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
:
159 return ''.join(parts
)
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':
166 return node
.GetName() == 'Is%s' % _GetShortName(interface
, ['Dev', 'Private'])
169 def _GetCreateFuncName(interface
):
170 """Returns the creation function name for an interface."""
171 return 'Create%s' % _GetShortName(interface
, ['Dev'])
174 def _GetDefaultFailureValue(t
):
175 """Returns the default failure value for a given type.
177 Returns None if no default failure value exists for the type.
180 'PP_Bool': 'PP_FALSE',
182 'struct PP_Var': 'PP_MakeUndefined()',
184 'int32_t': 'enter.retval()',
195 def _MakeCreateMemberBody(interface
, member
, args
):
196 """Returns the body of a Create() function.
199 interface - IDLNode for the interface
200 member - IDLNode for member function
201 args - List of arguments for the Create() function
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]
213 raise TGenError('Unknown arg type for Create(): %s' % args
[0][0])
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')
221 create_func
= _GetCreateFuncName(interface
)
222 body
+= 'return enter.functions()->%s(%s);' % (create_func
,
227 def _GetOutputParams(member
, release
):
228 """Returns output parameters (and their types) for a member function.
231 member - IDLNode for the member function
232 release - Release to get output parameters for
234 A list of name strings for all output parameters of the member
238 callnode
= member
.GetOneOf('Callspec')
241 for param
in callnode
.GetListOf('Param'):
242 mode
= cgen
.GetParamMode(param
)
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
)
251 def _MakeNormalMemberBody(filenode
, release
, node
, member
, rtype
, args
,
252 include_version
, meta
):
253 """Returns the body of a typical function.
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
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
269 meta
.AddApi('ppapi/shared_impl/%s_shared.h' % _StripFileName(filenode
))
270 return 'return %s::%s();' % (_StripApiName(node
.GetName()) + '_Shared',
273 is_callback_func
= args
[len(args
) - 1][0] == 'struct PP_CompletionCallback'
276 call_args
= args
[:-1] + [('', 'enter.callback()', '', '')]
277 meta
.AddInclude('ppapi/c/pp_completion_callback.h')
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'
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',
294 ', '.join(a
[1] for a
in args
))
296 function_name
= member
.GetName()
298 version
= node
.GetVersion(release
).replace('.', '_')
299 function_name
+= version
301 invocation
= 'enter.%s()->%s(%s)' % (function_container
,
305 handle_errors
= not (member
.GetProperty('report_errors') == 'False')
306 out_params
= _GetOutputParams(member
, release
)
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.
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
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
339 body
+= '%s' % success_return
340 meta
.AddBuiltinInclude('string.h')
342 body
+= 'if (enter.failed())\n'
343 body
+= ' %s\n' % failure_return
344 body
+= '%s' % success_return
348 def DefineMember(filenode
, node
, member
, release
, include_version
, meta
):
349 """Returns a definition for a member function of an interface.
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
359 A string with the member definition.
362 rtype
, name
, arrays
, args
= cgen
.GetComponents(member
, release
, 'return')
363 body
= 'VLOG(4) << \"%s::%s()\";\n' % (node
.GetName(), member
.GetName())
365 if _IsTypeCheck(node
, member
, args
):
366 body
+= '%s\n' % _MakeEnterLine(filenode
, node
, member
, args
[0], False,
368 body
+= 'return PP_FromBool(enter.succeeded());'
369 elif member
.GetName() == 'Create' or member
.GetName() == 'CreateTrusted':
370 body
+= _MakeCreateMemberBody(node
, member
, args
)
372 body
+= _MakeNormalMemberBody(filenode
, release
, node
, member
, rtype
, args
,
373 include_version
, meta
)
375 signature
= cgen
.GetSignature(member
, release
, 'return', func_as_ptr
=False,
376 include_version
=include_version
)
377 return '%s\n%s\n}' % (cgen
.Indent('%s {' % signature
, tabs
=0),
378 cgen
.Indent(body
, tabs
=1))
381 def _IsNewestMember(member
, members
, releases
):
382 """Returns true if member is the newest node with its name in members.
384 Currently, every node in the AST only has one version. This means that we
385 will have two sibling nodes with the same name to represent different
387 See http://crbug.com/157017 .
389 Special handling is required for nodes which share their name with others,
390 but aren't the newest version in the IDL.
393 member - The member which is checked if it's newest
394 members - The list of members to inspect
395 releases - The set of releases to check for versions in.
397 build_list
= member
.GetUniqueReleases(releases
)
398 release
= build_list
[0] # Pick the oldest release.
399 same_name_siblings
= filter(
400 lambda n
: str(n
) == str(member
) and n
!= member
, members
)
402 for s
in same_name_siblings
:
403 sibling_build_list
= s
.GetUniqueReleases(releases
)
404 sibling_release
= sibling_build_list
[0]
405 if sibling_release
> release
:
410 class TGen(GeneratorByFile
):
412 Generator
.__init
__(self
, 'Thunk', 'tgen', 'Generate the C++ thunk.')
414 def GenerateFile(self
, filenode
, releases
, options
):
415 savename
= _GetThunkFileName(filenode
, GetOption('thunkroot'))
416 my_min
, my_max
= filenode
.GetMinMax(releases
)
417 if my_min
> releases
[-1] or my_max
< releases
[0]:
418 if os
.path
.isfile(savename
):
419 print "Removing stale %s for this range." % filenode
.GetName()
420 os
.remove(os
.path
.realpath(savename
))
422 do_generate
= filenode
.GetProperty('generate_thunk')
426 thunk_out
= IDLOutFile(savename
)
427 body
, meta
= self
.GenerateBody(thunk_out
, filenode
, releases
, options
)
428 # TODO(teravest): How do we handle repeated values?
429 if filenode
.GetProperty('thunk_include'):
430 meta
.AddInclude(filenode
.GetProperty('thunk_include'))
431 self
.WriteHead(thunk_out
, filenode
, releases
, options
, meta
)
432 thunk_out
.Write('\n\n'.join(body
))
433 self
.WriteTail(thunk_out
, filenode
, releases
, options
)
434 return thunk_out
.Close()
436 def WriteHead(self
, out
, filenode
, releases
, options
, meta
):
437 __pychecker__
= 'unusednames=options'
440 cright_node
= filenode
.GetChildren()[0]
441 assert(cright_node
.IsA('Copyright'))
442 out
.Write('%s\n' % cgen
.Copyright(cright_node
, cpp_style
=True))
444 # Wrap the From ... modified ... comment if it would be >80 characters.
445 from_text
= 'From %s' % (
446 filenode
.GetProperty('NAME').replace(os
.sep
,'/'))
447 modified_text
= 'modified %s.' % (
448 filenode
.GetProperty('DATETIME'))
449 if len(from_text
) + len(modified_text
) < 74:
450 out
.Write('// %s %s\n\n' % (from_text
, modified_text
))
452 out
.Write('// %s,\n// %s\n\n' % (from_text
, modified_text
))
454 if meta
.BuiltinIncludes():
455 for include
in sorted(meta
.BuiltinIncludes()):
456 out
.Write('#include <%s>\n' % include
)
459 # TODO(teravest): Don't emit includes we don't need.
460 includes
= ['ppapi/c/pp_errors.h',
461 'ppapi/shared_impl/tracked_callback.h',
462 'ppapi/thunk/enter.h',
463 'ppapi/thunk/ppapi_thunk_export.h']
464 includes
.append(_GetHeaderFileName(filenode
))
465 for api
in meta
.Apis():
466 includes
.append('%s' % api
.lower())
467 for i
in meta
.Includes():
469 for include
in sorted(includes
):
470 out
.Write('#include "%s"\n' % include
)
472 out
.Write('namespace ppapi {\n')
473 out
.Write('namespace thunk {\n')
475 out
.Write('namespace {\n')
478 def GenerateBody(self
, out
, filenode
, releases
, options
):
479 """Generates a member function lines to be written and metadata.
481 Returns a tuple of (body, meta) where:
482 body - a list of lines with member function bodies
483 meta - a ThunkMetadata instance for hinting which headers are needed.
485 __pychecker__
= 'unusednames=options'
487 meta
= ThunkBodyMetadata()
488 for node
in filenode
.GetListOf('Interface'):
489 # Skip if this node is not in this release
490 if not node
.InReleases(releases
):
491 print "Skipping %s" % node
494 # Generate Member functions
495 if node
.IsA('Interface'):
496 members
= node
.GetListOf('Member')
497 for child
in members
:
498 build_list
= child
.GetUniqueReleases(releases
)
499 # We have to filter out releases this node isn't in.
500 build_list
= filter(lambda r
: child
.InReleases([r
]), build_list
)
501 if len(build_list
) == 0:
503 release
= build_list
[-1]
504 include_version
= not _IsNewestMember(child
, members
, releases
)
505 member
= DefineMember(filenode
, node
, child
, release
, include_version
,
509 out_members
.append(member
)
510 return (out_members
, meta
)
512 def WriteTail(self
, out
, filenode
, releases
, options
):
513 __pychecker__
= 'unusednames=options'
518 for node
in filenode
.GetListOf('Interface'):
519 build_list
= node
.GetUniqueReleases(releases
)
520 for build
in build_list
:
521 version
= node
.GetVersion(build
).replace('.', '_')
522 thunk_name
= 'g_' + node
.GetName().lower() + '_thunk_' + \
524 thunk_type
= '_'.join((node
.GetName(), version
))
525 version_list
.append((thunk_type
, thunk_name
))
527 declare_line
= 'const %s %s = {' % (thunk_type
, thunk_name
)
528 if len(declare_line
) > 80:
529 declare_line
= 'const %s\n %s = {' % (thunk_type
, thunk_name
)
530 out
.Write('%s\n' % declare_line
)
531 generated_functions
= []
532 members
= node
.GetListOf('Member')
533 for child
in members
:
534 rtype
, name
, arrays
, args
= cgen
.GetComponents(
535 child
, build
, 'return')
536 if child
.InReleases([build
]):
537 if not _IsNewestMember(child
, members
, releases
):
538 version
= child
.GetVersion(
539 child
.first_release
[build
]).replace('.', '_')
540 name
+= '_' + version
541 generated_functions
.append(name
)
542 out
.Write(',\n'.join([' &%s' % f
for f
in generated_functions
]))
543 out
.Write('\n};\n\n')
545 out
.Write('} // namespace\n')
547 for thunk_type
, thunk_name
in version_list
:
548 thunk_decl
= ('PPAPI_THUNK_EXPORT const %s* Get%s_Thunk() {\n' %
549 (thunk_type
, thunk_type
))
550 if len(thunk_decl
) > 80:
551 thunk_decl
= ('PPAPI_THUNK_EXPORT const %s*\n Get%s_Thunk() {\n' %
552 (thunk_type
, thunk_type
))
553 out
.Write(thunk_decl
)
554 out
.Write(' return &%s;\n' % thunk_name
)
557 out
.Write('} // namespace thunk\n')
558 out
.Write('} // namespace ppapi\n')
565 # Default invocation will verify the golden files are unchanged.
568 args
= ['--wnone', '--diff', '--test', '--thunkroot=.']
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."
580 print "Golden file for M13-M15 passed."
585 if __name__
== '__main__':
586 sys
.exit(Main(sys
.argv
[1:]))