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 _AddApiHeader(filenode
, meta
):
94 """Adds an API header for the given file to the ThunkBodyMetadata."""
95 # The API header matches the file name, not the interface name.
96 api_basename
= _GetBaseFileName(filenode
)
97 if api_basename
.endswith('_dev'):
98 api_basename
= api_basename
[:-len('_dev')]
99 if api_basename
.endswith('_trusted'):
100 api_basename
= api_basename
[:-len('_trusted')]
101 if api_basename
.endswith('_private'):
102 api_basename
= api_basename
[:-len('_private')]
103 meta
.AddApi(api_basename
+ '_api')
106 def _MakeEnterLine(filenode
, interface
, member
, arg
, handle_errors
, callback
,
108 """Returns an EnterInstance/EnterResource string for a function."""
109 api_name
= interface
.GetName()
110 if api_name
.endswith('Trusted'):
111 api_name
= api_name
[:-len('Trusted')]
112 if api_name
.endswith('_Dev'):
113 api_name
= api_name
[:-len('_Dev')]
114 if api_name
.endswith('_Private'):
115 api_name
= api_name
[:-len('_Private')]
117 if member
.GetProperty('api'): # Override API name.
118 manually_provided_api
= True
119 # TODO(teravest): Automatically guess the API header file.
120 api_name
= member
.GetProperty('api')
122 manually_provided_api
= False
124 if arg
[0] == 'PP_Instance':
128 arg_string
= '%s, %s' % (arg
[1], callback
)
129 if interface
.GetProperty('singleton') or member
.GetProperty('singleton'):
130 if not manually_provided_api
:
131 _AddApiHeader(filenode
, meta
)
132 return 'EnterInstanceAPI<%s> enter(%s);' % (api_name
, arg_string
)
134 return 'EnterInstance enter(%s);' % arg_string
135 elif arg
[0] == 'PP_Resource':
136 enter_type
= 'EnterResource<%s>' % api_name
137 if not manually_provided_api
:
138 _AddApiHeader(filenode
, meta
)
140 return '%s enter(%s, %s);' % (enter_type
, arg
[1],
141 str(handle_errors
).lower())
143 return '%s enter(%s, %s, %s);' % (enter_type
, arg
[1],
145 str(handle_errors
).lower())
147 raise TGenError("Unknown type for _MakeEnterLine: %s" % arg
[0])
150 def _GetShortName(interface
, filter_suffixes
):
151 """Return a shorter interface name that matches Is* and Create* functions."""
152 parts
= interface
.GetName().split('_')[1:]
153 tail
= parts
[len(parts
) - 1]
154 if tail
in filter_suffixes
:
156 return ''.join(parts
)
159 def _IsTypeCheck(interface
, node
):
160 """Returns true if node represents a type-checking function."""
161 return node
.GetName() == 'Is%s' % _GetShortName(interface
, ['Dev', 'Private'])
164 def _GetCreateFuncName(interface
):
165 """Returns the creation function name for an interface."""
166 return 'Create%s' % _GetShortName(interface
, ['Dev'])
169 def _GetDefaultFailureValue(t
):
170 """Returns the default failure value for a given type.
172 Returns None if no default failure value exists for the type.
175 'PP_Bool': 'PP_FALSE',
177 'struct PP_Var': 'PP_MakeUndefined()',
179 'int32_t': 'enter.retval()',
189 def _MakeCreateMemberBody(interface
, member
, args
):
190 """Returns the body of a Create() function.
193 interface - IDLNode for the interface
194 member - IDLNode for member function
195 args - List of arguments for the Create() function
197 if args
[0][0] == 'PP_Resource':
198 body
= 'Resource* object =\n'
199 body
+= ' PpapiGlobals::Get()->GetResourceTracker()->'
200 body
+= 'GetResource(%s);\n' % args
[0][1]
201 body
+= 'if (!object)\n'
202 body
+= ' return 0;\n'
203 body
+= 'EnterResourceCreation enter(object->pp_instance());\n'
204 elif args
[0][0] == 'PP_Instance':
205 body
= 'EnterResourceCreation enter(%s);\n' % args
[0][1]
207 raise TGenError('Unknown arg type for Create(): %s' % args
[0][0])
209 body
+= 'if (enter.failed())\n'
210 body
+= ' return 0;\n'
211 arg_list
= ', '.join([a
[1] for a
in args
])
212 if member
.GetProperty('create_func'):
213 create_func
= member
.GetProperty('create_func')
215 create_func
= _GetCreateFuncName(interface
)
216 body
+= 'return enter.functions()->%s(%s);' % (create_func
,
221 def _GetOutputParams(member
, release
):
222 """Returns output parameters (and their types) for a member function.
225 member - IDLNode for the member function
226 release - Release to get output parameters for
228 A list of name strings for all output parameters of the member
232 callnode
= member
.GetOneOf('Callspec')
235 for param
in callnode
.GetListOf('Param'):
236 mode
= cgen
.GetParamMode(param
)
238 # We use the 'store' mode when getting the parameter type, since we
239 # need to call sizeof() for memset().
240 _
, pname
, _
, _
= cgen
.GetComponents(param
, release
, 'store')
241 out_params
.append(pname
)
245 def _MakeNormalMemberBody(filenode
, release
, node
, member
, rtype
, args
,
246 include_version
, meta
):
247 """Returns the body of a typical function.
250 filenode - IDLNode for the file
251 release - release to generate body for
252 node - IDLNode for the interface
253 member - IDLNode for the member function
254 rtype - Return type for the member function
255 args - List of 4-tuple arguments for the member function
256 include_version - whether to include the version in the invocation
257 meta - ThunkBodyMetadata for header hints
259 is_callback_func
= args
[len(args
) - 1][0] == 'struct PP_CompletionCallback'
262 call_args
= args
[:-1] + [('', 'enter.callback()', '', '')]
263 meta
.AddInclude('ppapi/c/pp_completion_callback.h')
267 if args
[0][0] == 'PP_Instance':
268 call_arglist
= ', '.join(a
[1] for a
in call_args
)
269 function_container
= 'functions'
271 call_arglist
= ', '.join(a
[1] for a
in call_args
[1:])
272 function_container
= 'object'
274 function_name
= member
.GetName()
276 version
= node
.GetVersion(release
).replace('.', '_')
277 function_name
+= version
279 invocation
= 'enter.%s()->%s(%s)' % (function_container
,
283 handle_errors
= not (member
.GetProperty('report_errors') == 'False')
284 out_params
= _GetOutputParams(member
, release
)
286 body
= '%s\n' % _MakeEnterLine(filenode
, node
, member
, args
[0],
287 handle_errors
, args
[len(args
) - 1][1], meta
)
288 failure_value
= member
.GetProperty('on_failure')
289 if failure_value
is None:
290 failure_value
= 'enter.retval()'
291 failure_return
= 'return %s;' % failure_value
292 success_return
= 'return enter.SetResult(%s);' % invocation
293 elif rtype
== 'void':
294 body
= '%s\n' % _MakeEnterLine(filenode
, node
, member
, args
[0],
295 handle_errors
, None, meta
)
296 failure_return
= 'return;'
297 success_return
= '%s;' % invocation
# We don't return anything for void.
299 body
= '%s\n' % _MakeEnterLine(filenode
, node
, member
, args
[0],
300 handle_errors
, None, meta
)
301 failure_value
= member
.GetProperty('on_failure')
302 if failure_value
is None:
303 failure_value
= _GetDefaultFailureValue(rtype
)
304 if failure_value
is None:
305 raise TGenError('There is no default value for rtype %s. '
306 'Maybe you should provide an on_failure attribute '
307 'in the IDL file.' % rtype
)
308 failure_return
= 'return %s;' % failure_value
309 success_return
= 'return %s;' % invocation
311 if member
.GetProperty('always_set_output_parameters'):
312 body
+= 'if (enter.failed()) {\n'
313 for param
in out_params
:
314 body
+= ' memset(%s, 0, sizeof(*%s));\n' % (param
, param
)
315 body
+= ' %s\n' % failure_return
317 body
+= '%s' % success_return
318 meta
.AddBuiltinInclude('string.h')
320 body
+= 'if (enter.failed())\n'
321 body
+= ' %s\n' % failure_return
322 body
+= '%s' % success_return
326 def DefineMember(filenode
, node
, member
, release
, include_version
, meta
):
327 """Returns a definition for a member function of an interface.
330 filenode - IDLNode for the file
331 node - IDLNode for the interface
332 member - IDLNode for the member function
333 release - release to generate
334 include_version - include the version in emitted function name.
335 meta - ThunkMetadata for header hints
337 A string with the member definition.
340 rtype
, name
, arrays
, args
= cgen
.GetComponents(member
, release
, 'return')
341 body
= 'VLOG(4) << \"%s::%s()\";\n' % (node
.GetName(), member
.GetName())
343 if _IsTypeCheck(node
, member
):
344 body
+= '%s\n' % _MakeEnterLine(filenode
, node
, member
, args
[0], False,
346 body
+= 'return PP_FromBool(enter.succeeded());'
347 elif member
.GetName() == 'Create':
348 body
+= _MakeCreateMemberBody(node
, member
, args
)
350 body
+= _MakeNormalMemberBody(filenode
, release
, node
, member
, rtype
, args
,
351 include_version
, meta
)
353 signature
= cgen
.GetSignature(member
, release
, 'return', func_as_ptr
=False,
354 include_version
=include_version
)
355 return '%s\n%s\n}' % (cgen
.Indent('%s {' % signature
, tabs
=0),
356 cgen
.Indent(body
, tabs
=1))
359 def _IsNewestMember(member
, members
, releases
):
360 """Returns true if member is the newest node with its name in members.
362 Currently, every node in the AST only has one version. This means that we
363 will have two sibling nodes with the same name to represent different
365 See http://crbug.com/157017 .
367 Special handling is required for nodes which share their name with others,
368 but aren't the newest version in the IDL.
371 member - The member which is checked if it's newest
372 members - The list of members to inspect
373 releases - The set of releases to check for versions in.
375 build_list
= member
.GetUniqueReleases(releases
)
376 release
= build_list
[0] # Pick the oldest release.
377 same_name_siblings
= filter(
378 lambda n
: str(n
) == str(member
) and n
!= member
, members
)
380 for s
in same_name_siblings
:
381 sibling_build_list
= s
.GetUniqueReleases(releases
)
382 sibling_release
= sibling_build_list
[0]
383 if sibling_release
> release
:
388 class TGen(GeneratorByFile
):
390 Generator
.__init
__(self
, 'Thunk', 'tgen', 'Generate the C++ thunk.')
392 def GenerateFile(self
, filenode
, releases
, options
):
393 savename
= _GetThunkFileName(filenode
, GetOption('thunkroot'))
394 my_min
, my_max
= filenode
.GetMinMax(releases
)
395 if my_min
> releases
[-1] or my_max
< releases
[0]:
396 if os
.path
.isfile(savename
):
397 print "Removing stale %s for this range." % filenode
.GetName()
398 os
.remove(os
.path
.realpath(savename
))
400 do_generate
= filenode
.GetProperty('generate_thunk')
404 thunk_out
= IDLOutFile(savename
)
405 body
, meta
= self
.GenerateBody(thunk_out
, filenode
, releases
, options
)
406 # TODO(teravest): How do we handle repeated values?
407 if filenode
.GetProperty('thunk_include'):
408 meta
.AddInclude(filenode
.GetProperty('thunk_include'))
409 self
.WriteHead(thunk_out
, filenode
, releases
, options
, meta
)
410 thunk_out
.Write('\n\n'.join(body
))
411 self
.WriteTail(thunk_out
, filenode
, releases
, options
)
412 return thunk_out
.Close()
414 def WriteHead(self
, out
, filenode
, releases
, options
, meta
):
415 __pychecker__
= 'unusednames=options'
418 cright_node
= filenode
.GetChildren()[0]
419 assert(cright_node
.IsA('Copyright'))
420 out
.Write('%s\n' % cgen
.Copyright(cright_node
, cpp_style
=True))
422 # Wrap the From ... modified ... comment if it would be >80 characters.
423 from_text
= 'From %s' % (
424 filenode
.GetProperty('NAME').replace(os
.sep
,'/'))
425 modified_text
= 'modified %s.' % (
426 filenode
.GetProperty('DATETIME'))
427 if len(from_text
) + len(modified_text
) < 74:
428 out
.Write('// %s %s\n\n' % (from_text
, modified_text
))
430 out
.Write('// %s,\n// %s\n\n' % (from_text
, modified_text
))
432 if meta
.BuiltinIncludes():
433 for include
in sorted(meta
.BuiltinIncludes()):
434 out
.Write('#include <%s>\n' % include
)
437 # TODO(teravest): Don't emit includes we don't need.
438 includes
= ['ppapi/c/pp_errors.h',
439 'ppapi/shared_impl/tracked_callback.h',
440 'ppapi/thunk/enter.h',
441 'ppapi/thunk/ppb_instance_api.h',
442 'ppapi/thunk/resource_creation_api.h',
443 'ppapi/thunk/thunk.h']
444 includes
.append(_GetHeaderFileName(filenode
))
445 for api
in meta
.Apis():
446 includes
.append('ppapi/thunk/%s.h' % api
.lower())
447 for i
in meta
.Includes():
449 for include
in sorted(includes
):
450 out
.Write('#include "%s"\n' % include
)
452 out
.Write('namespace ppapi {\n')
453 out
.Write('namespace thunk {\n')
455 out
.Write('namespace {\n')
458 def GenerateBody(self
, out
, filenode
, releases
, options
):
459 """Generates a member function lines to be written and metadata.
461 Returns a tuple of (body, meta) where:
462 body - a list of lines with member function bodies
463 meta - a ThunkMetadata instance for hinting which headers are needed.
465 __pychecker__
= 'unusednames=options'
467 meta
= ThunkBodyMetadata()
468 for node
in filenode
.GetListOf('Interface'):
469 # Skip if this node is not in this release
470 if not node
.InReleases(releases
):
471 print "Skipping %s" % node
474 # Generate Member functions
475 if node
.IsA('Interface'):
476 members
= node
.GetListOf('Member')
477 for child
in members
:
478 build_list
= child
.GetUniqueReleases(releases
)
479 # We have to filter out releases this node isn't in.
480 build_list
= filter(lambda r
: child
.InReleases([r
]), build_list
)
481 if len(build_list
) == 0:
483 assert(len(build_list
) == 1)
484 release
= build_list
[-1]
485 include_version
= not _IsNewestMember(child
, members
, releases
)
486 member
= DefineMember(filenode
, node
, child
, release
, include_version
,
490 out_members
.append(member
)
491 return (out_members
, meta
)
493 def WriteTail(self
, out
, filenode
, releases
, options
):
494 __pychecker__
= 'unusednames=options'
499 for node
in filenode
.GetListOf('Interface'):
500 build_list
= node
.GetUniqueReleases(releases
)
501 for build
in build_list
:
502 version
= node
.GetVersion(build
).replace('.', '_')
503 thunk_name
= 'g_' + node
.GetName().lower() + '_thunk_' + \
505 thunk_type
= '_'.join((node
.GetName(), version
))
506 version_list
.append((thunk_type
, thunk_name
))
508 declare_line
= 'const %s %s = {' % (thunk_type
, thunk_name
)
509 if len(declare_line
) > 80:
510 declare_line
= 'const %s\n %s = {' % (thunk_type
, thunk_name
)
511 out
.Write('%s\n' % declare_line
)
512 generated_functions
= []
513 members
= node
.GetListOf('Member')
514 for child
in members
:
515 rtype
, name
, arrays
, args
= cgen
.GetComponents(
516 child
, build
, 'return')
517 if not _IsNewestMember(child
, members
, releases
):
518 version
= node
.GetVersion(build
).replace('.', '_')
519 name
+= '_' + version
520 if child
.InReleases([build
]):
521 generated_functions
.append(name
)
522 out
.Write(',\n'.join([' &%s' % f
for f
in generated_functions
]))
523 out
.Write('\n};\n\n')
525 out
.Write('} // namespace\n')
527 for thunk_type
, thunk_name
in version_list
:
528 thunk_decl
= 'const %s* Get%s_Thunk() {\n' % (thunk_type
, thunk_type
)
529 if len(thunk_decl
) > 80:
530 thunk_decl
= 'const %s*\n Get%s_Thunk() {\n' % (thunk_type
,
532 out
.Write(thunk_decl
)
533 out
.Write(' return &%s;\n' % thunk_name
)
536 out
.Write('} // namespace thunk\n')
537 out
.Write('} // namespace ppapi\n')
544 # Default invocation will verify the golden files are unchanged.
547 args
= ['--wnone', '--diff', '--test', '--thunkroot=.']
551 idldir
= os
.path
.split(sys
.argv
[0])[0]
552 idldir
= os
.path
.join(idldir
, 'test_thunk', '*.idl')
553 filenames
= glob
.glob(idldir
)
554 ast
= ParseFiles(filenames
)
555 if tgen
.GenerateRange(ast
, ['M13', 'M14'], {}):
556 print "Golden file for M13-M14 failed."
559 print "Golden file for M13-M14 passed."
564 if __name__
== '__main__':
565 sys
.exit(Main(sys
.argv
[1:]))