Make certificate viewer a tab-modal dialog.
[chromium-blink-merge.git] / ppapi / generators / idl_thunk.py
blob1f498579844883b49150ba9403620f24d8629774
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.
6 """ Generator for C++ style thunks """
8 import glob
9 import os
10 import re
11 import sys
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):
28 self.value = msg
30 def __str__(self):
31 return repr(self.value)
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()
41 def AddApi(self, api):
42 self._apis.add(api)
44 def Apis(self):
45 return self._apis
47 def AddInclude(self, include):
48 self._includes.add(include)
50 def Includes(self):
51 return self._includes
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.
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
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
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
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,
107 meta):
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')]
116 api_name += '_API'
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')
121 else:
122 manually_provided_api = False
124 if arg[0] == 'PP_Instance':
125 if callback is None:
126 arg_string = arg[1]
127 else:
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)
133 else:
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)
139 if callback is None:
140 return '%s enter(%s, %s);' % (enter_type, arg[1],
141 str(handle_errors).lower())
142 else:
143 return '%s enter(%s, %s, %s);' % (enter_type, arg[1],
144 callback,
145 str(handle_errors).lower())
146 else:
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:
155 parts = parts[:-1]
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.
174 values = {
175 'PP_Bool': 'PP_FALSE',
176 'PP_Resource': '0',
177 'struct PP_Var': 'PP_MakeUndefined()',
178 'float': '0.0f',
179 'int32_t': 'enter.retval()',
180 'uint16_t': '0',
181 'uint32_t': '0',
182 'uint64_t': '0',
184 if t in values:
185 return values[t]
186 return None
189 def _MakeCreateMemberBody(interface, member, args):
190 """Returns the body of a Create() function.
192 Args:
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]
206 else:
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')
214 else:
215 create_func = _GetCreateFuncName(interface)
216 body += 'return enter.functions()->%s(%s);' % (create_func,
217 arg_list)
218 return body
221 def _GetOutputParams(member, release):
222 """Returns output parameters (and their types) for a member function.
224 Args:
225 member - IDLNode for the member function
226 release - Release to get output parameters for
227 Returns:
228 A list of name strings for all output parameters of the member
229 function.
231 out_params = []
232 callnode = member.GetOneOf('Callspec')
233 if callnode:
234 cgen = CGen()
235 for param in callnode.GetListOf('Param'):
236 mode = cgen.GetParamMode(param)
237 if mode == 'out':
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)
242 return out_params
245 def _MakeNormalMemberBody(filenode, release, node, member, rtype, args,
246 include_version, meta):
247 """Returns the body of a typical function.
249 Args:
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'
261 if is_callback_func:
262 call_args = args[:-1] + [('', 'enter.callback()', '', '')]
263 meta.AddInclude('ppapi/c/pp_completion_callback.h')
264 else:
265 call_args = args
267 if args[0][0] == 'PP_Instance':
268 call_arglist = ', '.join(a[1] for a in call_args)
269 function_container = 'functions'
270 else:
271 call_arglist = ', '.join(a[1] for a in call_args[1:])
272 function_container = 'object'
274 function_name = member.GetName()
275 if include_version:
276 version = node.GetVersion(release).replace('.', '_')
277 function_name += version
279 invocation = 'enter.%s()->%s(%s)' % (function_container,
280 function_name,
281 call_arglist)
283 handle_errors = not (member.GetProperty('report_errors') == 'False')
284 out_params = _GetOutputParams(member, release)
285 if is_callback_func:
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.
298 else:
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
316 body += '}\n'
317 body += '%s' % success_return
318 meta.AddBuiltinInclude('string.h')
319 else:
320 body += 'if (enter.failed())\n'
321 body += ' %s\n' % failure_return
322 body += '%s' % success_return
323 return body
326 def DefineMember(filenode, node, member, release, include_version, meta):
327 """Returns a definition for a member function of an interface.
329 Args:
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
336 Returns:
337 A string with the member definition.
339 cgen = CGen()
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,
345 None, meta)
346 body += 'return PP_FromBool(enter.succeeded());'
347 elif member.GetName() == 'Create':
348 body += _MakeCreateMemberBody(node, member, args)
349 else:
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
364 versions.
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.
370 Args:
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:
384 return False
385 return True
388 class TGen(GeneratorByFile):
389 def __init__(self):
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))
399 return False
400 do_generate = filenode.GetProperty('generate_thunk')
401 if not do_generate:
402 return False
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'
416 cgen = CGen()
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))
429 else:
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)
435 out.Write('\n')
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():
448 includes.append(i)
449 for include in sorted(includes):
450 out.Write('#include "%s"\n' % include)
451 out.Write('\n')
452 out.Write('namespace ppapi {\n')
453 out.Write('namespace thunk {\n')
454 out.Write('\n')
455 out.Write('namespace {\n')
456 out.Write('\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'
466 out_members = []
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
472 continue
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:
482 continue
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,
487 meta)
488 if not member:
489 continue
490 out_members.append(member)
491 return (out_members, meta)
493 def WriteTail(self, out, filenode, releases, options):
494 __pychecker__ = 'unusednames=options'
495 cgen = CGen()
497 version_list = []
498 out.Write('\n\n')
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_' + \
504 version
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')
526 out.Write('\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,
531 thunk_type)
532 out.Write(thunk_decl)
533 out.Write(' return &%s;\n' % thunk_name)
534 out.Write('}\n')
535 out.Write('\n')
536 out.Write('} // namespace thunk\n')
537 out.Write('} // namespace ppapi\n')
540 tgen = TGen()
543 def Main(args):
544 # Default invocation will verify the golden files are unchanged.
545 failed = 0
546 if not args:
547 args = ['--wnone', '--diff', '--test', '--thunkroot=.']
549 ParseOptions(args)
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."
557 failed = 1
558 else:
559 print "Golden file for M13-M14 passed."
561 return failed
564 if __name__ == '__main__':
565 sys.exit(Main(sys.argv[1:]))