blob: 72b7eb1e7072d534d9a056a2c22d5fa0a543d944 [file] [log] [blame]
[email protected]8c311f02012-11-17 16:01:321#!/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
8import glob
9import os
10import re
11import sys
12
13from idl_log import ErrOut, InfoOut, WarnOut
14from idl_node import IDLAttribute, IDLNode
15from idl_ast import IDLAst
16from idl_option import GetOption, Option, ParseOptions
17from idl_outfile import IDLOutFile
18from idl_parser import ParseFiles
19from idl_c_proto import CGen, GetNodeComments, CommentLines, Comment
20from idl_generator import Generator, GeneratorByFile
21
22Option('thunkroot', 'Base directory of output',
23 default=os.path.join('..', 'thunk'))
24
25
26class TGenError(Exception):
27 def __init__(self, msg):
28 self.value = msg
29
30 def __str__(self):
31 return repr(self.value)
32
33
[email protected]cba2c4302012-11-20 18:18:1334class ThunkBodyMetadata(object):
35 """Metadata about thunk body. Used for selecting which headers to emit."""
36 def __init__(self):
37 self._apis = set()
[email protected]2a7e8c52013-03-30 00:13:2538 self._builtin_includes = set()
[email protected]8f2a08d52012-12-08 00:10:5039 self._includes = set()
[email protected]cba2c4302012-11-20 18:18:1340
41 def AddApi(self, api):
42 self._apis.add(api)
43
44 def Apis(self):
45 return self._apis
46
[email protected]8f2a08d52012-12-08 00:10:5047 def AddInclude(self, include):
48 self._includes.add(include)
49
50 def Includes(self):
51 return self._includes
52
[email protected]2a7e8c52013-03-30 00:13:2553 def AddBuiltinInclude(self, include):
54 self._builtin_includes.add(include)
55
56 def BuiltinIncludes(self):
57 return self._builtin_includes
58
[email protected]cba2c4302012-11-20 18:18:1359
[email protected]8c311f02012-11-17 16:01:3260def _GetBaseFileName(filenode):
61 """Returns the base name for output files, given the filenode.
62
63 Examples:
[email protected]a5b6ace2013-02-08 21:09:5264 'dev/ppb_find_dev.h' -> 'ppb_find_dev'
[email protected]8c311f02012-11-17 16:01:3265 '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]
[email protected]8c311f02012-11-17 16:01:3269 return name
70
71
72def _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
83def _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
[email protected]2a7e8c52013-03-30 00:13:2593def _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 meta.AddApi(api_basename + '_api')
102
103
[email protected]cba2c4302012-11-20 18:18:13104def _MakeEnterLine(filenode, interface, arg, handle_errors, callback, meta):
[email protected]8c311f02012-11-17 16:01:32105 """Returns an EnterInstance/EnterResource string for a function."""
[email protected]2a7e8c52013-03-30 00:13:25106 api_name = interface.GetName()
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 api_name += '_API'
112
[email protected]8c311f02012-11-17 16:01:32113 if arg[0] == 'PP_Instance':
114 if callback is None:
[email protected]2a7e8c52013-03-30 00:13:25115 arg_string = arg[1]
[email protected]8c311f02012-11-17 16:01:32116 else:
[email protected]2a7e8c52013-03-30 00:13:25117 arg_string = '%s, %s' % (arg[1], callback)
118 if interface.GetProperty('singleton_resource'):
119 _AddApiHeader(filenode, meta)
120 return 'EnterInstanceAPI<%s> enter(%s);' % (api_name, arg_string)
121 else:
122 return 'EnterInstance enter(%s);' % arg_string
[email protected]8c311f02012-11-17 16:01:32123 elif arg[0] == 'PP_Resource':
[email protected]8c311f02012-11-17 16:01:32124 enter_type = 'EnterResource<%s>' % api_name
[email protected]2a7e8c52013-03-30 00:13:25125 _AddApiHeader(filenode, meta)
[email protected]8c311f02012-11-17 16:01:32126 if callback is None:
127 return '%s enter(%s, %s);' % (enter_type, arg[1],
128 str(handle_errors).lower())
129 else:
130 return '%s enter(%s, %s, %s);' % (enter_type, arg[1],
131 callback,
132 str(handle_errors).lower())
133 else:
134 raise TGenError("Unknown type for _MakeEnterLine: %s" % arg[0])
135
136
137def _GetShortName(interface, filter_suffixes):
138 """Return a shorter interface name that matches Is* and Create* functions."""
139 parts = interface.GetName().split('_')[1:]
140 tail = parts[len(parts) - 1]
141 if tail in filter_suffixes:
142 parts = parts[:-1]
143 return ''.join(parts)
144
145
146def _IsTypeCheck(interface, node):
147 """Returns true if node represents a type-checking function."""
148 return node.GetName() == 'Is%s' % _GetShortName(interface, ['Dev', 'Private'])
149
150
151def _GetCreateFuncName(interface):
152 """Returns the creation function name for an interface."""
153 return 'Create%s' % _GetShortName(interface, ['Dev'])
154
155
156def _GetDefaultFailureValue(t):
157 """Returns the default failure value for a given type.
158
159 Returns None if no default failure value exists for the type.
160 """
161 values = {
162 'PP_Bool': 'PP_FALSE',
163 'PP_Resource': '0',
164 'struct PP_Var': 'PP_MakeUndefined()',
[email protected]12b6f612013-02-13 18:17:58165 'float': '0.0f',
[email protected]8c311f02012-11-17 16:01:32166 'int32_t': 'enter.retval()',
167 'uint16_t': '0',
168 'uint32_t': '0',
169 'uint64_t': '0',
170 }
171 if t in values:
172 return values[t]
173 return None
174
175
176def _MakeCreateMemberBody(interface, member, args):
177 """Returns the body of a Create() function.
178
179 Args:
180 interface - IDLNode for the interface
181 member - IDLNode for member function
182 args - List of arguments for the Create() function
183 """
184 if args[0][0] == 'PP_Resource':
[email protected]8917a672013-02-25 17:18:04185 body = 'Resource* object =\n'
186 body += ' PpapiGlobals::Get()->GetResourceTracker()->'
[email protected]8c311f02012-11-17 16:01:32187 body += 'GetResource(%s);\n' % args[0][1]
[email protected]8917a672013-02-25 17:18:04188 body += 'if (!object)\n'
189 body += ' return 0;\n'
190 body += 'EnterResourceCreation enter(object->pp_instance());\n'
[email protected]8c311f02012-11-17 16:01:32191 elif args[0][0] == 'PP_Instance':
[email protected]8917a672013-02-25 17:18:04192 body = 'EnterResourceCreation enter(%s);\n' % args[0][1]
[email protected]8c311f02012-11-17 16:01:32193 else:
194 raise TGenError('Unknown arg type for Create(): %s' % args[0][0])
195
[email protected]8917a672013-02-25 17:18:04196 body += 'if (enter.failed())\n'
197 body += ' return 0;\n'
[email protected]8c311f02012-11-17 16:01:32198 arg_list = ', '.join([a[1] for a in args])
199 if member.GetProperty('create_func'):
200 create_func = member.GetProperty('create_func')
201 else:
202 create_func = _GetCreateFuncName(interface)
[email protected]8917a672013-02-25 17:18:04203 body += 'return enter.functions()->%s(%s);' % (create_func,
204 arg_list)
[email protected]8c311f02012-11-17 16:01:32205 return body
206
207
[email protected]8917a672013-02-25 17:18:04208def _MakeNormalMemberBody(filenode, release, node, member, rtype, args,
209 include_version, meta):
[email protected]8c311f02012-11-17 16:01:32210 """Returns the body of a typical function.
211
212 Args:
213 filenode - IDLNode for the file
[email protected]8917a672013-02-25 17:18:04214 release - release to generate body for
[email protected]8c311f02012-11-17 16:01:32215 node - IDLNode for the interface
216 member - IDLNode for the member function
217 rtype - Return type for the member function
218 args - List of 4-tuple arguments for the member function
[email protected]8917a672013-02-25 17:18:04219 include_version - whether to include the version in the invocation
[email protected]cba2c4302012-11-20 18:18:13220 meta - ThunkBodyMetadata for header hints
[email protected]8c311f02012-11-17 16:01:32221 """
222 is_callback_func = args[len(args) - 1][0] == 'struct PP_CompletionCallback'
223
224 if is_callback_func:
225 call_args = args[:-1] + [('', 'enter.callback()', '', '')]
[email protected]8f2a08d52012-12-08 00:10:50226 meta.AddInclude('ppapi/c/pp_completion_callback.h')
[email protected]8c311f02012-11-17 16:01:32227 else:
228 call_args = args
229
230 if args[0][0] == 'PP_Instance':
231 call_arglist = ', '.join(a[1] for a in call_args)
232 function_container = 'functions'
233 else:
234 call_arglist = ', '.join(a[1] for a in call_args[1:])
235 function_container = 'object'
236
[email protected]8917a672013-02-25 17:18:04237 function_name = member.GetName()
238 if include_version:
239 version = node.GetVersion(release).replace('.', '_')
240 function_name += version
241
[email protected]8c311f02012-11-17 16:01:32242 invocation = 'enter.%s()->%s(%s)' % (function_container,
[email protected]8917a672013-02-25 17:18:04243 function_name,
[email protected]8c311f02012-11-17 16:01:32244 call_arglist)
245
246 handle_errors = not (member.GetProperty('report_errors') == 'False')
247 if is_callback_func:
[email protected]8917a672013-02-25 17:18:04248 body = '%s\n' % _MakeEnterLine(filenode, node, args[0], handle_errors,
249 args[len(args) - 1][1], meta)
250 body += 'if (enter.failed())\n'
[email protected]8c311f02012-11-17 16:01:32251 value = member.GetProperty('on_failure')
252 if value is None:
253 value = 'enter.retval()'
[email protected]8917a672013-02-25 17:18:04254 body += ' return %s;\n' % value
255 body += 'return enter.SetResult(%s);' % invocation
[email protected]8c311f02012-11-17 16:01:32256 elif rtype == 'void':
[email protected]2a7e8c52013-03-30 00:13:25257 # On failure, zero out all output parameters.
258 out_params = []
259 callnode = member.GetOneOf('Callspec')
260 if callnode:
261 cgen = CGen()
262 for param in callnode.GetListOf('Param'):
263 mode = cgen.GetParamMode(param)
264 if mode == 'out':
265 # We use the 'store' mode when getting the parameter type, since we
266 # need to call sizeof() for memset().
267 ptype, pname, _, _ = cgen.GetComponents(param, release, 'store')
268 out_params.append((pname, ptype))
269
[email protected]8917a672013-02-25 17:18:04270 body = '%s\n' % _MakeEnterLine(filenode, node, args[0], handle_errors,
271 None, meta)
[email protected]2a7e8c52013-03-30 00:13:25272 if not out_params:
273 body += 'if (enter.succeeded())\n'
274 body += ' %s;' % invocation
275 else:
276 body += 'if (enter.succeeded()) {\n'
277 body += ' %s;\n' % invocation
278 body += ' return;\n'
279 body += '}'
280 for param in out_params:
281 body += '\nmemset(%s, 0, sizeof(%s));' % param
282 meta.AddBuiltinInclude('string.h')
283
[email protected]8c311f02012-11-17 16:01:32284 else:
285 value = member.GetProperty('on_failure')
286 if value is None:
287 value = _GetDefaultFailureValue(rtype)
288 if value is None:
289 raise TGenError('No default value for rtype %s' % rtype)
290
[email protected]8917a672013-02-25 17:18:04291 body = '%s\n' % _MakeEnterLine(filenode, node, args[0], handle_errors,
292 None, meta)
293 body += 'if (enter.failed())\n'
294 body += ' return %s;\n' % value
295 body += 'return %s;' % invocation
[email protected]8c311f02012-11-17 16:01:32296 return body
297
298
[email protected]cba2c4302012-11-20 18:18:13299def DefineMember(filenode, node, member, release, include_version, meta):
[email protected]8c311f02012-11-17 16:01:32300 """Returns a definition for a member function of an interface.
301
302 Args:
303 filenode - IDLNode for the file
304 node - IDLNode for the interface
305 member - IDLNode for the member function
306 release - release to generate
307 include_version - include the version in emitted function name.
[email protected]cba2c4302012-11-20 18:18:13308 meta - ThunkMetadata for header hints
[email protected]8c311f02012-11-17 16:01:32309 Returns:
310 A string with the member definition.
311 """
312 cgen = CGen()
313 rtype, name, arrays, args = cgen.GetComponents(member, release, 'return')
314
315 if _IsTypeCheck(node, member):
[email protected]8917a672013-02-25 17:18:04316 body = '%s\n' % _MakeEnterLine(filenode, node, args[0], False, None, meta)
317 body += 'return PP_FromBool(enter.succeeded());'
[email protected]8c311f02012-11-17 16:01:32318 elif member.GetName() == 'Create':
319 body = _MakeCreateMemberBody(node, member, args)
320 else:
[email protected]8917a672013-02-25 17:18:04321 body = _MakeNormalMemberBody(filenode, release, node, member, rtype, args,
322 include_version, meta)
[email protected]8c311f02012-11-17 16:01:32323
324 signature = cgen.GetSignature(member, release, 'return', func_as_ptr=False,
325 include_version=include_version)
[email protected]8917a672013-02-25 17:18:04326 return '%s\n%s\n}' % (cgen.Indent('%s {' % signature, tabs=0),
327 cgen.Indent(body, tabs=1))
328
329
330def _IsNewestMember(member, members, releases):
331 """Returns true if member is the newest node with its name in members.
332
333 Currently, every node in the AST only has one version. This means that we
334 will have two sibling nodes with the same name to represent different
335 versions.
336 See http://crbug.com/157017 .
337
338 Special handling is required for nodes which share their name with others,
339 but aren't the newest version in the IDL.
340
341 Args:
342 member - The member which is checked if it's newest
343 members - The list of members to inspect
344 releases - The set of releases to check for versions in.
345 """
346 build_list = member.GetUniqueReleases(releases)
[email protected]ba054c02013-02-26 23:27:18347 release = build_list[0] # Pick the oldest release.
[email protected]8917a672013-02-25 17:18:04348 same_name_siblings = filter(
349 lambda n: str(n) == str(member) and n != member, members)
350
351 for s in same_name_siblings:
352 sibling_build_list = s.GetUniqueReleases(releases)
[email protected]ba054c02013-02-26 23:27:18353 sibling_release = sibling_build_list[0]
[email protected]8917a672013-02-25 17:18:04354 if sibling_release > release:
355 return False
356 return True
[email protected]8c311f02012-11-17 16:01:32357
358
359class TGen(GeneratorByFile):
360 def __init__(self):
361 Generator.__init__(self, 'Thunk', 'tgen', 'Generate the C++ thunk.')
362
363 def GenerateFile(self, filenode, releases, options):
364 savename = _GetThunkFileName(filenode, GetOption('thunkroot'))
365 my_min, my_max = filenode.GetMinMax(releases)
366 if my_min > releases[-1] or my_max < releases[0]:
367 if os.path.isfile(savename):
368 print "Removing stale %s for this range." % filenode.GetName()
369 os.remove(os.path.realpath(savename))
370 return False
371 do_generate = filenode.GetProperty('generate_thunk')
372 if not do_generate:
373 return False
374
375 thunk_out = IDLOutFile(savename)
[email protected]cba2c4302012-11-20 18:18:13376 body, meta = self.GenerateBody(thunk_out, filenode, releases, options)
377 self.WriteHead(thunk_out, filenode, releases, options, meta)
378 thunk_out.Write('\n\n'.join(body))
379 self.WriteTail(thunk_out, filenode, releases, options)
[email protected]8c311f02012-11-17 16:01:32380 return thunk_out.Close()
381
[email protected]cba2c4302012-11-20 18:18:13382 def WriteHead(self, out, filenode, releases, options, meta):
[email protected]8c311f02012-11-17 16:01:32383 __pychecker__ = 'unusednames=options'
384 cgen = CGen()
385
386 cright_node = filenode.GetChildren()[0]
387 assert(cright_node.IsA('Copyright'))
388 out.Write('%s\n' % cgen.Copyright(cright_node, cpp_style=True))
389
390 # Wrap the From ... modified ... comment if it would be >80 characters.
391 from_text = 'From %s' % (
392 filenode.GetProperty('NAME').replace(os.sep,'/'))
393 modified_text = 'modified %s.' % (
394 filenode.GetProperty('DATETIME'))
395 if len(from_text) + len(modified_text) < 74:
396 out.Write('// %s %s\n\n' % (from_text, modified_text))
397 else:
398 out.Write('// %s,\n// %s\n\n' % (from_text, modified_text))
399
[email protected]2a7e8c52013-03-30 00:13:25400 if meta.BuiltinIncludes():
401 for include in sorted(meta.BuiltinIncludes()):
402 out.Write('#include <%s>\n' % include)
403 out.Write('\n')
[email protected]8c311f02012-11-17 16:01:32404
405 # TODO(teravest): Don't emit includes we don't need.
406 includes = ['ppapi/c/pp_errors.h',
407 'ppapi/shared_impl/tracked_callback.h',
408 'ppapi/thunk/enter.h',
409 'ppapi/thunk/ppb_instance_api.h',
410 'ppapi/thunk/resource_creation_api.h',
411 'ppapi/thunk/thunk.h']
412 includes.append(_GetHeaderFileName(filenode))
[email protected]cba2c4302012-11-20 18:18:13413 for api in meta.Apis():
414 includes.append('ppapi/thunk/%s.h' % api.lower())
[email protected]8f2a08d52012-12-08 00:10:50415 for i in meta.Includes():
416 includes.append(i)
[email protected]8c311f02012-11-17 16:01:32417 for include in sorted(includes):
418 out.Write('#include "%s"\n' % include)
419 out.Write('\n')
420 out.Write('namespace ppapi {\n')
421 out.Write('namespace thunk {\n')
422 out.Write('\n')
423 out.Write('namespace {\n')
424 out.Write('\n')
425
426 def GenerateBody(self, out, filenode, releases, options):
[email protected]cba2c4302012-11-20 18:18:13427 """Generates a member function lines to be written and metadata.
428
429 Returns a tuple of (body, meta) where:
430 body - a list of lines with member function bodies
431 meta - a ThunkMetadata instance for hinting which headers are needed.
432 """
[email protected]8c311f02012-11-17 16:01:32433 __pychecker__ = 'unusednames=options'
[email protected]8917a672013-02-25 17:18:04434 out_members = []
[email protected]cba2c4302012-11-20 18:18:13435 meta = ThunkBodyMetadata()
[email protected]8c311f02012-11-17 16:01:32436 for node in filenode.GetListOf('Interface'):
437 # Skip if this node is not in this release
438 if not node.InReleases(releases):
439 print "Skipping %s" % node
440 continue
441
442 # Generate Member functions
443 if node.IsA('Interface'):
[email protected]8917a672013-02-25 17:18:04444 members = node.GetListOf('Member')
445 for child in members:
[email protected]8c311f02012-11-17 16:01:32446 build_list = child.GetUniqueReleases(releases)
447 # We have to filter out releases this node isn't in.
448 build_list = filter(lambda r: child.InReleases([r]), build_list)
449 if len(build_list) == 0:
450 continue
[email protected]8917a672013-02-25 17:18:04451 assert(len(build_list) == 1)
452 release = build_list[-1]
453 include_version = not _IsNewestMember(child, members, releases)
454 member = DefineMember(filenode, node, child, release, include_version,
455 meta)
[email protected]8c311f02012-11-17 16:01:32456 if not member:
457 continue
[email protected]8917a672013-02-25 17:18:04458 out_members.append(member)
459 return (out_members, meta)
[email protected]8c311f02012-11-17 16:01:32460
[email protected]cba2c4302012-11-20 18:18:13461 def WriteTail(self, out, filenode, releases, options):
[email protected]8c311f02012-11-17 16:01:32462 __pychecker__ = 'unusednames=options'
463 cgen = CGen()
464
465 version_list = []
466 out.Write('\n\n')
467 for node in filenode.GetListOf('Interface'):
468 build_list = node.GetUniqueReleases(releases)
469 for build in build_list:
470 version = node.GetVersion(build).replace('.', '_')
471 thunk_name = 'g_' + node.GetName().lower() + '_thunk_' + \
472 version
473 thunk_type = '_'.join((node.GetName(), version))
474 version_list.append((thunk_type, thunk_name))
475
[email protected]8c71647a62013-02-26 19:32:50476 declare_line = 'const %s %s = {' % (thunk_type, thunk_name)
477 if len(declare_line) > 80:
478 declare_line = 'const %s\n %s = {' % (thunk_type, thunk_name)
479 out.Write('%s\n' % declare_line)
[email protected]cfc881d2013-01-03 19:07:46480 generated_functions = []
[email protected]8917a672013-02-25 17:18:04481 members = node.GetListOf('Member')
482 for child in members:
[email protected]8c311f02012-11-17 16:01:32483 rtype, name, arrays, args = cgen.GetComponents(
484 child, build, 'return')
[email protected]8917a672013-02-25 17:18:04485 if not _IsNewestMember(child, members, releases):
486 version = node.GetVersion(build).replace('.', '_')
487 name += '_' + version
[email protected]cfc881d2013-01-03 19:07:46488 if child.InReleases([build]):
489 generated_functions.append(name)
490 out.Write(',\n'.join([' &%s' % f for f in generated_functions]))
491 out.Write('\n};\n\n')
[email protected]8c311f02012-11-17 16:01:32492
493 out.Write('} // namespace\n')
494 out.Write('\n')
495 for thunk_type, thunk_name in version_list:
496 thunk_decl = 'const %s* Get%s_Thunk() {\n' % (thunk_type, thunk_type)
497 if len(thunk_decl) > 80:
498 thunk_decl = 'const %s*\n Get%s_Thunk() {\n' % (thunk_type,
499 thunk_type)
500 out.Write(thunk_decl)
501 out.Write(' return &%s;\n' % thunk_name)
502 out.Write('}\n')
503 out.Write('\n')
504 out.Write('} // namespace thunk\n')
505 out.Write('} // namespace ppapi\n')
506
507
508tgen = TGen()
509
510
511def Main(args):
512 # Default invocation will verify the golden files are unchanged.
513 failed = 0
514 if not args:
515 args = ['--wnone', '--diff', '--test', '--thunkroot=.']
516
517 ParseOptions(args)
518
519 idldir = os.path.split(sys.argv[0])[0]
520 idldir = os.path.join(idldir, 'test_thunk', '*.idl')
521 filenames = glob.glob(idldir)
522 ast = ParseFiles(filenames)
523 if tgen.GenerateRange(ast, ['M13', 'M14'], {}):
524 print "Golden file for M13-M14 failed."
525 failed = 1
526 else:
527 print "Golden file for M13-M14 passed."
528
529 return failed
530
531
532if __name__ == '__main__':
533 sys.exit(Main(sys.argv[1:]))