|
1 #!/usr/bin/env/python |
|
2 # qsgen.py - Generate XPConnect quick stubs. |
|
3 # |
|
4 # This Source Code Form is subject to the terms of the Mozilla Public |
|
5 # License, v. 2.0. If a copy of the MPL was not distributed with this |
|
6 # file, You can obtain one at http://mozilla.org/MPL/2.0/. |
|
7 |
|
8 # =About quick stubs= |
|
9 # qsgen.py generates "quick stubs", custom SpiderMonkey getters, setters, and |
|
10 # methods for specified XPCOM interface members. These quick stubs serve at |
|
11 # runtime as replacements for the XPConnect functions XPC_WN_GetterSetter and |
|
12 # XPC_WN_CallMethod, which are the extremely generic (and slow) SpiderMonkey |
|
13 # getter/setter/methods otherwise used for all XPCOM member accesses from JS. |
|
14 # |
|
15 # There are two ways quick stubs win: |
|
16 # 1. Pure, transparent optimization by partial evaluation. |
|
17 # 2. Cutting corners. |
|
18 # |
|
19 # == Partial evaluation == |
|
20 # Partial evaluation is when you execute part of a program early (before or at |
|
21 # compile time) so that you don't have to execute it at run time. In this |
|
22 # case, everything that involves interpreting xptcall data (for example, the |
|
23 # big methodInfo loops in XPCWrappedNative::CallMethod and the switch statement |
|
24 # in XPCConert::JSData2Native) might as well happen at build time, since all |
|
25 # the type information for any given member is already known. That's what this |
|
26 # script does. It gets the information from IDL instead of XPT files. Apart |
|
27 # from that, the code in this script is very similar to what you'll find in |
|
28 # XPConnect itself. The advantage is that it runs once, at build time, not in |
|
29 # tight loops at run time. |
|
30 # |
|
31 # == Cutting corners == |
|
32 # The XPConnect versions have to be slow because they do tons of work that's |
|
33 # only necessary in a few cases. The quick stubs skip a lot of that work. So |
|
34 # quick stubs necessarily differ from XPConnect in potentially observable ways. |
|
35 # For many specific interface members, the differences are not observable from |
|
36 # scripts or don't matter enough to worry about; but you do have to be careful |
|
37 # which members you decide to generate quick stubs for. |
|
38 # |
|
39 # The complete list of known differences, as of this writing, after an |
|
40 # assiduous search: |
|
41 # |
|
42 # - Quick stubs affect the handling of naming conflicts--that is, which C++ |
|
43 # method gets called when a script uses an XPCOM feature that is declared in |
|
44 # more than one of the interfaces the object implements. Without quick |
|
45 # stubs, XPConnect just walks the interfaces in the order they're listed by |
|
46 # nsClassInfo. You get the first interface that implements a feature with |
|
47 # that name. With quick stubs, it's the same except that non-quick-stubbed |
|
48 # features are shadowed. |
|
49 # |
|
50 # - Quick stub methods are JSFastNative, which means that when a quick stub |
|
51 # method is called, no JS stack frame is created. This doesn't affect |
|
52 # Mozilla security checks because they look for scripted JSStackFrames, not |
|
53 # native ones. |
|
54 # |
|
55 # It does affect the 'stack' property of JavaScript exceptions, though: the |
|
56 # stubbed member will not appear. (Note that if the stubbed member itself |
|
57 # fails, the member name will appear in the 'message' property.) |
|
58 # |
|
59 # - Many quick stubs don't create an XPCCallContext. In those cases, no entry |
|
60 # is added to the XPCCallContext stack. So native implementations of |
|
61 # quick-stubbed methods must avoid nsXPConnect::GetCurrentNativeCallContext. |
|
62 # |
|
63 # (Even when a quick stub does have an XPCCallContext, it never pushes it all |
|
64 # the way to READY_TO_CALL state, so a lot of its members are garbage. But |
|
65 # this doesn't endanger native implementations of non-quick-stubbed methods |
|
66 # that use GetCurrentNativeCallContext and are called indirectly from |
|
67 # quick-stubbed methods, because only the current top XPCCallContext is |
|
68 # exposed--nsAXPCNativeCallContext does not expose |
|
69 # XPCCallContext::GetPrevCallContext.) |
|
70 # |
|
71 # - Quick stubs never suspend the JS request. So they are only suitable for |
|
72 # main-thread-only interfaces. |
|
73 # |
|
74 # - Quick stubs don't call XPCContext::SetLastResult. This is visible on the |
|
75 # Components object. |
|
76 # |
|
77 # - Quick stubs skip a security check that XPConnect does in |
|
78 # XPCWrappedNative::CallMethod. This means the security manager doesn't have |
|
79 # an opportunity to veto accesses to members for which quick stubs exist. |
|
80 # |
|
81 # - There are many features of IDL that XPConnect supports but qsgen does not, |
|
82 # including dependent types, arrays, and out parameters. |
|
83 |
|
84 |
|
85 import xpidl |
|
86 import header |
|
87 import makeutils |
|
88 import os, re |
|
89 import sys |
|
90 |
|
91 # === Preliminaries |
|
92 |
|
93 def warn(msg): |
|
94 sys.stderr.write(msg + '\n') |
|
95 |
|
96 def unaliasType(t): |
|
97 while t.kind == 'typedef': |
|
98 t = t.realtype |
|
99 assert t is not None |
|
100 return t |
|
101 |
|
102 def isVoidType(type): |
|
103 """ Return True if the given xpidl type is void. """ |
|
104 return type.kind == 'builtin' and type.name == 'void' |
|
105 |
|
106 def isInterfaceType(t): |
|
107 t = unaliasType(t) |
|
108 assert t.kind in ('builtin', 'native', 'interface', 'forward') |
|
109 return t.kind in ('interface', 'forward') |
|
110 |
|
111 def isSpecificInterfaceType(t, name): |
|
112 """ True if `t` is an interface type with the given name, or a forward |
|
113 declaration or typedef aliasing it. |
|
114 |
|
115 `name` must not be the name of a typedef but the actual name of the |
|
116 interface. |
|
117 """ |
|
118 t = unaliasType(t) |
|
119 return t.kind in ('interface', 'forward') and t.name == name |
|
120 |
|
121 def getBuiltinOrNativeTypeName(t): |
|
122 t = unaliasType(t) |
|
123 if t.kind == 'builtin': |
|
124 return t.name |
|
125 elif t.kind == 'native': |
|
126 assert t.specialtype is not None |
|
127 return '[%s]' % t.specialtype |
|
128 else: |
|
129 return None |
|
130 |
|
131 |
|
132 # === Reading the file |
|
133 |
|
134 class UserError(Exception): |
|
135 pass |
|
136 |
|
137 def findIDL(includePath, irregularFilenames, interfaceName): |
|
138 filename = irregularFilenames.get(interfaceName, interfaceName) + '.idl' |
|
139 for d in includePath: |
|
140 # Not os.path.join: we need a forward slash even on Windows because |
|
141 # this filename ends up in makedepend output. |
|
142 path = d + '/' + filename |
|
143 if os.path.exists(path): |
|
144 return path |
|
145 raise UserError("No IDL file found for interface %s " |
|
146 "in include path %r" |
|
147 % (interfaceName, includePath)) |
|
148 |
|
149 def loadIDL(parser, includePath, filename): |
|
150 makeutils.dependencies.append(filename) |
|
151 text = open(filename, 'r').read() |
|
152 idl = parser.parse(text, filename=filename) |
|
153 idl.resolve(includePath, parser) |
|
154 return idl |
|
155 |
|
156 def removeStubMember(memberId, member): |
|
157 if member not in member.iface.stubMembers: |
|
158 raise UserError("Trying to remove member %s from interface %s, but it was never added" |
|
159 % (member.name, member.iface.name)) |
|
160 member.iface.stubMembers.remove(member) |
|
161 |
|
162 def addStubMember(memberId, member): |
|
163 if member.kind == 'method' and not member.implicit_jscontext and not isVariantType(member.realtype): |
|
164 for param in member.params: |
|
165 for attrname, value in vars(param).items(): |
|
166 if value is True: |
|
167 if attrname == 'optional': |
|
168 continue |
|
169 |
|
170 raise UserError("Method %s, parameter %s: " |
|
171 "unrecognized property %r" |
|
172 % (memberId, param.name, attrname)) |
|
173 |
|
174 # Add this member to the list. |
|
175 member.iface.stubMembers.append(member) |
|
176 |
|
177 def checkStubMember(member): |
|
178 memberId = member.iface.name + "." + member.name |
|
179 if member.kind not in ('method', 'attribute'): |
|
180 raise UserError("Member %s is %r, not a method or attribute." |
|
181 % (memberId, member.kind)) |
|
182 if member.noscript: |
|
183 raise UserError("%s %s is noscript." |
|
184 % (member.kind.capitalize(), memberId)) |
|
185 if member.kind == 'method' and member.notxpcom: |
|
186 raise UserError( |
|
187 "%s %s: notxpcom methods are not supported." |
|
188 % (member.kind.capitalize(), memberId)) |
|
189 |
|
190 if (member.kind == 'attribute' |
|
191 and not member.readonly |
|
192 and isSpecificInterfaceType(member.realtype, 'nsIVariant')): |
|
193 raise UserError( |
|
194 "Attribute %s: Non-readonly attributes of type nsIVariant " |
|
195 "are not supported." |
|
196 % memberId) |
|
197 |
|
198 # Check for unknown properties. |
|
199 for attrname, value in vars(member).items(): |
|
200 if value is True and attrname not in ('readonly','optional_argc', |
|
201 'implicit_jscontext', |
|
202 'getter', 'stringifier'): |
|
203 raise UserError("%s %s: unrecognized property %r" |
|
204 % (member.kind.capitalize(), memberId, |
|
205 attrname)) |
|
206 |
|
207 def parseMemberId(memberId): |
|
208 """ Split the geven member id into its parts. """ |
|
209 pieces = memberId.split('.') |
|
210 if len(pieces) < 2: |
|
211 raise UserError("Member %r: Missing dot." % memberId) |
|
212 if len(pieces) > 2: |
|
213 raise UserError("Member %r: Dots out of control." % memberId) |
|
214 return tuple(pieces) |
|
215 |
|
216 class Configuration: |
|
217 def __init__(self, filename, includePath): |
|
218 self.includePath = includePath |
|
219 config = {} |
|
220 execfile(filename, config) |
|
221 # required settings |
|
222 for name in ('name', 'members'): |
|
223 if name not in config: |
|
224 raise UserError(filename + ": `%s` was not defined." % name) |
|
225 setattr(self, name, config[name]) |
|
226 # optional settings |
|
227 self.irregularFilenames = config.get('irregularFilenames', {}) |
|
228 self.customIncludes = config.get('customIncludes', []) |
|
229 self.customMethodCalls = config.get('customMethodCalls', {}) |
|
230 self.newBindingProperties = config.get('newBindingProperties', {}) |
|
231 |
|
232 def readConfigFile(filename, includePath, cachedir): |
|
233 # Read the config file. |
|
234 conf = Configuration(filename, includePath) |
|
235 |
|
236 # Now read IDL files to connect the information in the config file to |
|
237 # actual XPCOM interfaces, methods, and attributes. |
|
238 interfaces = [] |
|
239 interfacesByName = {} |
|
240 parser = xpidl.IDLParser(cachedir) |
|
241 |
|
242 def getInterface(interfaceName, errorLoc): |
|
243 iface = interfacesByName.get(interfaceName) |
|
244 if iface is None: |
|
245 idlFile = findIDL(conf.includePath, conf.irregularFilenames, |
|
246 interfaceName) |
|
247 idl = loadIDL(parser, conf.includePath, idlFile) |
|
248 if not idl.hasName(interfaceName): |
|
249 raise UserError("The interface %s was not found " |
|
250 "in the idl file %r." |
|
251 % (interfaceName, idlFile)) |
|
252 iface = idl.getName(interfaceName, errorLoc) |
|
253 iface.stubMembers = [] |
|
254 iface.newBindingProperties = 'nullptr' |
|
255 interfaces.append(iface) |
|
256 interfacesByName[interfaceName] = iface |
|
257 return iface |
|
258 |
|
259 stubbedInterfaces = [] |
|
260 |
|
261 for memberId in conf.members: |
|
262 add = True |
|
263 interfaceName, memberName = parseMemberId(memberId) |
|
264 |
|
265 # If the interfaceName starts with -, then remove this entry from the list |
|
266 if interfaceName[0] == '-': |
|
267 add = False |
|
268 interfaceName = interfaceName[1:] |
|
269 |
|
270 iface = getInterface(interfaceName, errorLoc='looking for %r' % memberId) |
|
271 |
|
272 if not iface.attributes.scriptable: |
|
273 raise UserError("Interface %s is not scriptable." % interfaceName) |
|
274 |
|
275 if memberName == '*': |
|
276 if not add: |
|
277 raise UserError("Can't use negation in stub list with wildcard, in %s.*" % interfaceName) |
|
278 |
|
279 # Stub all scriptable members of this interface. |
|
280 for member in iface.members: |
|
281 if member.kind in ('method', 'attribute') and not member.noscript: |
|
282 addStubMember(iface.name + '.' + member.name, member) |
|
283 |
|
284 if member.iface not in stubbedInterfaces: |
|
285 stubbedInterfaces.append(member.iface) |
|
286 else: |
|
287 # Look up a member by name. |
|
288 if memberName not in iface.namemap: |
|
289 idlFile = iface.idl.parser.lexer.filename |
|
290 raise UserError("Interface %s has no member %r. " |
|
291 "(See IDL file %r.)" |
|
292 % (interfaceName, memberName, idlFile)) |
|
293 member = iface.namemap.get(memberName, None) |
|
294 if add: |
|
295 if member in iface.stubMembers: |
|
296 raise UserError("Member %s is specified more than once." |
|
297 % memberId) |
|
298 |
|
299 addStubMember(memberId, member) |
|
300 if member.iface not in stubbedInterfaces: |
|
301 stubbedInterfaces.append(member.iface) |
|
302 else: |
|
303 removeStubMember(memberId, member) |
|
304 |
|
305 for (interfaceName, v) in conf.newBindingProperties.iteritems(): |
|
306 iface = getInterface(interfaceName, errorLoc='looking for %r' % interfaceName) |
|
307 iface.newBindingProperties = v |
|
308 if iface not in stubbedInterfaces: |
|
309 stubbedInterfaces.append(iface) |
|
310 |
|
311 # Now go through and check all the interfaces' members |
|
312 for iface in stubbedInterfaces: |
|
313 for member in iface.stubMembers: |
|
314 checkStubMember(member) |
|
315 |
|
316 return conf, interfaces |
|
317 |
|
318 |
|
319 # === Generating the header file |
|
320 |
|
321 def writeHeaderFile(filename, name): |
|
322 print "Creating header file", filename |
|
323 |
|
324 headerMacro = '__gen_%s__' % filename.replace('.', '_') |
|
325 f = open(filename, 'w') |
|
326 try: |
|
327 f.write("/* THIS FILE IS AUTOGENERATED - DO NOT EDIT */\n" |
|
328 "#ifndef " + headerMacro + "\n" |
|
329 "#define " + headerMacro + "\n\n" |
|
330 "bool " + name + "_DefineQuickStubs(" |
|
331 "JSContext *cx, JSObject *proto, unsigned flags, " |
|
332 "uint32_t count, const nsID **iids);\n\n" |
|
333 "void " + name + "_MarkInterfaces();\n\n" |
|
334 "void " + name + "_ClearInterfaces();\n\n" |
|
335 "inline void " + name + "_InitInterfaces()\n" |
|
336 "{\n" |
|
337 " " + name + "_ClearInterfaces();\n" |
|
338 "}\n\n" |
|
339 "#endif\n") |
|
340 finally: |
|
341 f.close() |
|
342 |
|
343 # === Generating the source file |
|
344 |
|
345 class StringTable: |
|
346 def __init__(self): |
|
347 self.current_index = 0; |
|
348 self.table = {} |
|
349 self.reverse_table = {} |
|
350 |
|
351 def c_strlen(self, string): |
|
352 return len(string) + 1 |
|
353 |
|
354 def stringIndex(self, string): |
|
355 if string in self.table: |
|
356 return self.table[string] |
|
357 else: |
|
358 result = self.current_index |
|
359 self.table[string] = result |
|
360 self.current_index += self.c_strlen(string) |
|
361 return result |
|
362 |
|
363 def writeDefinition(self, f, name): |
|
364 entries = self.table.items() |
|
365 entries.sort(key=lambda x:x[1]) |
|
366 # Avoid null-in-string warnings with GCC and potentially |
|
367 # overlong string constants; write everything out the long way. |
|
368 def explodeToCharArray(string): |
|
369 return ", ".join(map(lambda x:"'%s'" % x, string)) |
|
370 f.write("static const char %s[] = {\n" % name) |
|
371 for (string, offset) in entries[:-1]: |
|
372 f.write(" /* %5d */ %s, '\\0',\n" |
|
373 % (offset, explodeToCharArray(string))) |
|
374 f.write(" /* %5d */ %s, '\\0' };\n\n" |
|
375 % (entries[-1][1], explodeToCharArray(entries[-1][0]))) |
|
376 f.write("const char* xpc_qsStringTable = %s;\n\n" % name); |
|
377 |
|
378 def substitute(template, vals): |
|
379 """ Simple replacement for string.Template, which isn't in Python 2.3. """ |
|
380 def replacement(match): |
|
381 return vals[match.group(1)] |
|
382 return re.sub(r'\${(\w+)}', replacement, template) |
|
383 |
|
384 # From JSData2Native. |
|
385 argumentUnboxingTemplates = { |
|
386 'octet': |
|
387 " uint32_t ${name}_u32;\n" |
|
388 " if (!JS::ToUint32(cx, ${argVal}, &${name}_u32))\n" |
|
389 " return false;\n" |
|
390 " uint8_t ${name} = (uint8_t) ${name}_u32;\n", |
|
391 |
|
392 'short': |
|
393 " int32_t ${name}_i32;\n" |
|
394 " if (!JS::ToInt32(cx, ${argVal}, &${name}_i32))\n" |
|
395 " return false;\n" |
|
396 " int16_t ${name} = (int16_t) ${name}_i32;\n", |
|
397 |
|
398 'unsigned short': |
|
399 " uint32_t ${name}_u32;\n" |
|
400 " if (!JS::ToUint32(cx, ${argVal}, &${name}_u32))\n" |
|
401 " return false;\n" |
|
402 " uint16_t ${name} = (uint16_t) ${name}_u32;\n", |
|
403 |
|
404 'long': |
|
405 " int32_t ${name};\n" |
|
406 " if (!JS::ToInt32(cx, ${argVal}, &${name}))\n" |
|
407 " return false;\n", |
|
408 |
|
409 'unsigned long': |
|
410 " uint32_t ${name};\n" |
|
411 " if (!JS::ToUint32(cx, ${argVal}, &${name}))\n" |
|
412 " return false;\n", |
|
413 |
|
414 'long long': |
|
415 " int64_t ${name};\n" |
|
416 " if (!JS::ToInt64(cx, ${argVal}, &${name}))\n" |
|
417 " return false;\n", |
|
418 |
|
419 'unsigned long long': |
|
420 " uint64_t ${name};\n" |
|
421 " if (!JS::ToUint64(cx, ${argVal}, &${name}))\n" |
|
422 " return false;\n", |
|
423 |
|
424 'float': |
|
425 " double ${name}_dbl;\n" |
|
426 " if (!JS::ToNumber(cx, ${argVal}, &${name}_dbl))\n" |
|
427 " return false;\n" |
|
428 " float ${name} = (float) ${name}_dbl;\n", |
|
429 |
|
430 'double': |
|
431 " double ${name};\n" |
|
432 " if (!JS::ToNumber(cx, ${argVal}, &${name}))\n" |
|
433 " return false;\n", |
|
434 |
|
435 'boolean': |
|
436 " bool ${name} = JS::ToBoolean(${argVal});\n", |
|
437 |
|
438 '[astring]': |
|
439 " xpc_qsAString ${name}(cx, ${argVal}, ${argPtr}, ${notPassed});\n" |
|
440 " if (!${name}.IsValid())\n" |
|
441 " return false;\n", |
|
442 |
|
443 '[domstring]': |
|
444 " xpc_qsDOMString ${name}(cx, ${argVal},\n" |
|
445 " ${argPtr}, ${notPassed},\n" |
|
446 " xpc_qsDOMString::e${nullBehavior},\n" |
|
447 " xpc_qsDOMString::e${undefinedBehavior});\n" |
|
448 " if (!${name}.IsValid())\n" |
|
449 " return false;\n", |
|
450 |
|
451 'string': |
|
452 " JSAutoByteString ${name}_bytes;\n" |
|
453 " if (!xpc_qsJsvalToCharStr(cx, ${argVal}, &${name}_bytes))\n" |
|
454 " return false;\n" |
|
455 " char *${name} = ${name}_bytes.ptr();\n", |
|
456 |
|
457 '[cstring]': |
|
458 " xpc_qsACString ${name}(cx, ${argVal}, ${argPtr}, ${notPassed});\n" |
|
459 " if (!${name}.IsValid())\n" |
|
460 " return false;\n", |
|
461 |
|
462 '[utf8string]': |
|
463 " xpc_qsAUTF8String ${name}(cx, ${argVal}, ${argPtr}, ${notPassed});\n" |
|
464 " if (!${name}.IsValid())\n" |
|
465 " return false;\n", |
|
466 |
|
467 '[jsval]': |
|
468 " JS::RootedValue ${name}(cx, ${argVal});\n" |
|
469 } |
|
470 |
|
471 # From JSData2Native. |
|
472 # |
|
473 # Omitted optional arguments are treated as though the caller had passed JS |
|
474 # `null`; this behavior is from XPCWrappedNative::CallMethod. The 'jsval' type, |
|
475 # however, defaults to 'undefined'. |
|
476 # |
|
477 def writeArgumentUnboxing(f, i, name, type, optional, rvdeclared, |
|
478 nullBehavior, undefinedBehavior, |
|
479 propIndex=None): |
|
480 # f - file to write to |
|
481 # i - int or None - Indicates the source jsval. If i is an int, the source |
|
482 # jsval is args[i]; otherwise it is args[0]. But if Python i >= C++ argc, |
|
483 # which can only happen if optional is True, the argument is missing; |
|
484 # use JSVAL_NULL as the source jsval instead. |
|
485 # name - str - name of the native C++ variable to create. |
|
486 # type - xpidl.{Interface,Native,Builtin} - IDL type of argument |
|
487 # optional - bool - True if the parameter is optional. |
|
488 # rvdeclared - bool - False if no |nsresult rv| has been declared earlier. |
|
489 |
|
490 typeName = getBuiltinOrNativeTypeName(type) |
|
491 |
|
492 isSetter = (i is None) |
|
493 |
|
494 notPassed = "false" |
|
495 if isSetter: |
|
496 argPtr = "args[0]" |
|
497 argVal = "args[0]" |
|
498 elif optional: |
|
499 if typeName == "[jsval]": |
|
500 val = "JS::UndefinedHandleValue" |
|
501 else: |
|
502 val = "JS::NullHandleValue" |
|
503 argVal = "(%d < argc ? args[%d] : %s)" % (i, i, val) |
|
504 if typeName == "[jsval]": |
|
505 # This should use the rooted argument, |
|
506 # however we probably won't ever need to support that. |
|
507 argPtr = None |
|
508 notPassed = None |
|
509 else: |
|
510 # Need a MutableHandleValue to pass into eg the xpc_qsAString |
|
511 # constructor, but the corresponding argument may not have been |
|
512 # passed in at all. In that case, point the MutableHandleValue at a |
|
513 # dummy variable, and pass in a boolean saying that the argument |
|
514 # wasn't passed (previously, this used a NULL ptr sentinel value.) |
|
515 f.write(" JS::RootedValue {name}_dummy(cx);\n".format(name=name)) |
|
516 f.write(" JS::MutableHandleValue {name}_mhv({i} < argc ? args[{i}] : &{name}_dummy);\n".format(name=name, i=i)) |
|
517 f.write(" (void) {name}_mhv;\n".format(name=name, i=i)) |
|
518 |
|
519 argPtr = "{name}_mhv".format(name=name) |
|
520 notPassed = "argc < {i}".format(i=i) |
|
521 else: |
|
522 argVal = "args[%d]" % i |
|
523 argPtr = "args[%d]" % i |
|
524 |
|
525 params = { |
|
526 'name': name, |
|
527 'argVal': argVal, |
|
528 'argPtr': argPtr, |
|
529 'notPassed': notPassed, |
|
530 'nullBehavior': nullBehavior or 'DefaultNullBehavior', |
|
531 'undefinedBehavior': undefinedBehavior or 'DefaultUndefinedBehavior' |
|
532 } |
|
533 |
|
534 if typeName is not None: |
|
535 template = argumentUnboxingTemplates.get(typeName) |
|
536 if template is not None: |
|
537 f.write(substitute(template, params)) |
|
538 return rvdeclared |
|
539 # else fall through; the type isn't supported yet. |
|
540 elif isInterfaceType(type): |
|
541 if type.name == 'nsIVariant': |
|
542 # Totally custom. |
|
543 template = ( |
|
544 " nsCOMPtr<nsIVariant> ${name}(already_AddRefed<nsIVariant>(" |
|
545 "XPCVariant::newVariant(cx, ${argVal})));\n" |
|
546 " if (!${name}) {\n" |
|
547 " xpc_qsThrowBadArg(cx, NS_ERROR_INVALID_ARG, vp, %d);\n" |
|
548 " return false;\n" |
|
549 " }") % i |
|
550 f.write(substitute(template, params)) |
|
551 return rvdeclared |
|
552 elif type.name == 'nsIAtom': |
|
553 # Should have special atomizing behavior. Fall through. |
|
554 pass |
|
555 else: |
|
556 if not rvdeclared: |
|
557 f.write(" nsresult rv;\n"); |
|
558 f.write(" %s *%s;\n" % (type.name, name)) |
|
559 f.write(" xpc_qsSelfRef %sref;\n" % name) |
|
560 f.write(" rv = xpc_qsUnwrapArg<%s>(" |
|
561 "cx, %s, &%s, &%sref.ptr, %s);\n" |
|
562 % (type.name, argVal, name, name, argPtr)) |
|
563 f.write(" if (NS_FAILED(rv)) {\n") |
|
564 if isSetter: |
|
565 assert(propIndex is not None) |
|
566 f.write(" xpc_qsThrowBadSetterValue(cx, rv, JSVAL_TO_OBJECT(vp[1]), (uint16_t)%s);\n" % |
|
567 propIndex) |
|
568 else: |
|
569 f.write(" xpc_qsThrowBadArg(cx, rv, vp, %d);\n" % i) |
|
570 f.write(" return false;\n" |
|
571 " }\n") |
|
572 return True |
|
573 |
|
574 warn("Unable to unbox argument of type %s (native type %s)" % (type.name, typeName)) |
|
575 if i is None: |
|
576 src = 'args[0]' |
|
577 else: |
|
578 src = 'args[%d]' % i |
|
579 f.write(" !; // TODO - Unbox argument %s = %s\n" % (name, src)) |
|
580 return rvdeclared |
|
581 |
|
582 def writeResultDecl(f, type, varname): |
|
583 if isVoidType(type): |
|
584 return # nothing to declare |
|
585 |
|
586 t = unaliasType(type) |
|
587 if t.kind == 'builtin': |
|
588 if not t.nativename.endswith('*'): |
|
589 if type.kind == 'typedef': |
|
590 typeName = type.name # use it |
|
591 else: |
|
592 typeName = t.nativename |
|
593 f.write(" %s %s;\n" % (typeName, varname)) |
|
594 return |
|
595 elif t.kind == 'native': |
|
596 name = getBuiltinOrNativeTypeName(t) |
|
597 if name in ('[domstring]', '[astring]'): |
|
598 f.write(" nsString %s;\n" % varname) |
|
599 return |
|
600 elif name == '[jsval]': |
|
601 f.write(" JS::RootedValue %s(cx);\n" % varname) |
|
602 return |
|
603 elif t.kind in ('interface', 'forward'): |
|
604 f.write(" nsCOMPtr<%s> %s;\n" % (type.name, varname)) |
|
605 return |
|
606 |
|
607 warn("Unable to declare result of type %s" % type.name) |
|
608 f.write(" !; // TODO - Declare out parameter `%s`.\n" % varname) |
|
609 |
|
610 def outParamForm(name, type): |
|
611 type = unaliasType(type) |
|
612 if type.kind == 'builtin': |
|
613 return '&' + name |
|
614 elif type.kind == 'native': |
|
615 if getBuiltinOrNativeTypeName(type) == '[jsval]': |
|
616 return '&' + name |
|
617 if type.modifier == 'ref': |
|
618 return name |
|
619 else: |
|
620 return '&' + name |
|
621 else: |
|
622 return 'getter_AddRefs(%s)' % name |
|
623 |
|
624 # From NativeData2JS. |
|
625 resultConvTemplates = { |
|
626 'void': |
|
627 " ${jsvalRef} = JSVAL_VOID;\n" |
|
628 " return true;\n", |
|
629 |
|
630 'octet': |
|
631 " ${jsvalRef} = INT_TO_JSVAL((int32_t) result);\n" |
|
632 " return true;\n", |
|
633 |
|
634 'short': |
|
635 " ${jsvalRef} = INT_TO_JSVAL((int32_t) result);\n" |
|
636 " return true;\n", |
|
637 |
|
638 'long': |
|
639 " ${jsvalRef} = INT_TO_JSVAL(result);\n" |
|
640 " return true;\n", |
|
641 |
|
642 'long long': |
|
643 " return xpc_qsInt64ToJsval(cx, result, ${jsvalPtr});\n", |
|
644 |
|
645 'unsigned short': |
|
646 " ${jsvalRef} = INT_TO_JSVAL((int32_t) result);\n" |
|
647 " return true;\n", |
|
648 |
|
649 'unsigned long': |
|
650 " ${jsvalRef} = UINT_TO_JSVAL(result);\n" |
|
651 " return true;\n", |
|
652 |
|
653 'unsigned long long': |
|
654 " return xpc_qsUint64ToJsval(cx, result, ${jsvalPtr});\n", |
|
655 |
|
656 'float': |
|
657 " ${jsvalRef} = JS_NumberValue(result);\n" |
|
658 " return true;\n", |
|
659 |
|
660 'double': |
|
661 " ${jsvalRef} = JS_NumberValue(result);\n" |
|
662 " return true;\n", |
|
663 |
|
664 'boolean': |
|
665 " ${jsvalRef} = (result ? JSVAL_TRUE : JSVAL_FALSE);\n" |
|
666 " return true;\n", |
|
667 |
|
668 '[astring]': |
|
669 " return xpc::StringToJsval(cx, result, ${jsvalPtr});\n", |
|
670 |
|
671 '[domstring]': |
|
672 " return xpc::StringToJsval(cx, result, ${jsvalPtr});\n", |
|
673 |
|
674 '[jsval]': |
|
675 " ${jsvalPtr}.set(result);\n" |
|
676 " return JS_WrapValue(cx, ${jsvalPtr});\n" |
|
677 } |
|
678 |
|
679 def isVariantType(t): |
|
680 return isSpecificInterfaceType(t, 'nsIVariant') |
|
681 |
|
682 def writeResultConv(f, type, jsvalPtr, jsvalRef): |
|
683 """ Emit code to convert the C++ variable `result` to a jsval. |
|
684 |
|
685 The emitted code contains a return statement; it returns true on |
|
686 success, false on error. |
|
687 """ |
|
688 # From NativeData2JS. |
|
689 typeName = getBuiltinOrNativeTypeName(type) |
|
690 if typeName is not None: |
|
691 template = resultConvTemplates.get(typeName) |
|
692 if template is not None: |
|
693 values = {'jsvalRef': jsvalRef, |
|
694 'jsvalPtr': jsvalPtr} |
|
695 f.write(substitute(template, values)) |
|
696 return |
|
697 # else fall through; this type isn't supported yet |
|
698 elif isInterfaceType(type): |
|
699 if isVariantType(type): |
|
700 f.write(" return xpc_qsVariantToJsval(cx, result, %s);\n" |
|
701 % jsvalPtr) |
|
702 return |
|
703 else: |
|
704 f.write(" if (!result) {\n" |
|
705 " %s.setNull();\n" |
|
706 " return true;\n" |
|
707 " }\n" |
|
708 " nsWrapperCache* cache = xpc_qsGetWrapperCache(result);\n" |
|
709 " if (xpc_FastGetCachedWrapper(cx, cache, %s)) {\n" |
|
710 " return true;\n" |
|
711 " }\n" |
|
712 " // After this point do not use 'result'!\n" |
|
713 " qsObjectHelper helper(result, cache);\n" |
|
714 " return xpc_qsXPCOMObjectToJsval(cx, " |
|
715 "helper, &NS_GET_IID(%s), &interfaces[k_%s], %s);\n" |
|
716 % (jsvalPtr, jsvalPtr, type.name, type.name, jsvalPtr)) |
|
717 return |
|
718 |
|
719 warn("Unable to convert result of type %s" % type.name) |
|
720 f.write(" !; // TODO - Convert `result` to jsval, store in `%s`.\n" |
|
721 % jsvalRef) |
|
722 f.write(" return xpc_qsThrow(cx, NS_ERROR_UNEXPECTED); // FIXME\n") |
|
723 |
|
724 def memberNeedsCallee(member): |
|
725 return isInterfaceType(member.realtype) |
|
726 |
|
727 def validateParam(member, param): |
|
728 def pfail(msg): |
|
729 raise UserError( |
|
730 member.iface.name + '.' + member.name + ": " |
|
731 "parameter " + param.name + ": " + msg) |
|
732 |
|
733 if param.iid_is is not None: |
|
734 pfail("iid_is parameters are not supported.") |
|
735 if param.size_is is not None: |
|
736 pfail("size_is parameters are not supported.") |
|
737 if param.retval: |
|
738 pfail("Unexpected retval parameter!") |
|
739 if param.paramtype in ('out', 'inout'): |
|
740 pfail("Out parameters are not supported.") |
|
741 if param.const or param.array or param.shared: |
|
742 pfail("I am a simple caveman.") |
|
743 |
|
744 def setOptimizationForMSVC(f, b): |
|
745 """ Write a pragma that turns optimizations off (if b is False) or |
|
746 on (if b is True) for MSVC. |
|
747 """ |
|
748 if b: |
|
749 pragmaParam = "on" |
|
750 else: |
|
751 pragmaParam = "off" |
|
752 f.write("#ifdef _MSC_VER\n") |
|
753 f.write('# pragma optimize("", %s)\n'%pragmaParam) |
|
754 f.write("#endif\n") |
|
755 |
|
756 def writeQuickStub(f, customMethodCalls, stringtable, member, stubName, |
|
757 isSetter=False): |
|
758 """ Write a single quick stub (a custom SpiderMonkey getter/setter/method) |
|
759 for the specified XPCOM interface-member. |
|
760 """ |
|
761 # Workaround for suspected compiler bug. |
|
762 # See https://bugzilla.mozilla.org/show_bug.cgi?id=750019 |
|
763 disableOptimizationForMSVC = (stubName == 'nsIDOMHTMLDocument_Write') |
|
764 |
|
765 isAttr = (member.kind == 'attribute') |
|
766 isMethod = (member.kind == 'method') |
|
767 assert isAttr or isMethod |
|
768 isGetter = isAttr and not isSetter |
|
769 |
|
770 signature = ("static bool\n" + |
|
771 "%s(JSContext *cx, unsigned argc,%s jsval *vp)\n") |
|
772 |
|
773 customMethodCall = customMethodCalls.get(stubName, None) |
|
774 |
|
775 if customMethodCall is None: |
|
776 customMethodCall = customMethodCalls.get(member.iface.name + '_', None) |
|
777 if customMethodCall is not None: |
|
778 if isMethod: |
|
779 code = customMethodCall.get('code', None) |
|
780 elif isGetter: |
|
781 code = customMethodCall.get('getter_code', None) |
|
782 else: |
|
783 code = customMethodCall.get('setter_code', None) |
|
784 else: |
|
785 code = None |
|
786 |
|
787 if code is not None: |
|
788 templateName = member.iface.name |
|
789 if isGetter: |
|
790 templateName += '_Get' |
|
791 elif isSetter: |
|
792 templateName += '_Set' |
|
793 |
|
794 # Generate the code for the stub, calling the template function |
|
795 # that's shared between the stubs. The stubs can't have additional |
|
796 # arguments, only the template function can. |
|
797 callTemplate = signature % (stubName, '') |
|
798 callTemplate += "{\n" |
|
799 |
|
800 nativeName = (member.binaryname is not None and member.binaryname |
|
801 or header.firstCap(member.name)) |
|
802 argumentValues = (customMethodCall['additionalArgumentValues'] |
|
803 % nativeName) |
|
804 callTemplate += (" return %s(cx, argc, %s, vp);\n" |
|
805 % (templateName, argumentValues)) |
|
806 callTemplate += "}\n\n" |
|
807 |
|
808 # Fall through and create the template function stub called from the |
|
809 # real stubs, but only generate the stub once. Otherwise, just write |
|
810 # out the call to the template function and return. |
|
811 templateGenerated = templateName + '_generated' |
|
812 if templateGenerated in customMethodCall: |
|
813 f.write(callTemplate) |
|
814 return |
|
815 customMethodCall[templateGenerated] = True |
|
816 |
|
817 stubName = templateName |
|
818 else: |
|
819 callTemplate = "" |
|
820 else: |
|
821 callTemplate = "" |
|
822 code = customMethodCall.get('code', None) |
|
823 |
|
824 unwrapThisFailureFatal = (customMethodCall is None or |
|
825 customMethodCall.get('unwrapThisFailureFatal', True)); |
|
826 if (not unwrapThisFailureFatal and not isAttr): |
|
827 raise UserError(member.iface.name + '.' + member.name + ": " |
|
828 "Unwrapping this failure must be fatal for methods") |
|
829 |
|
830 # Function prolog. |
|
831 |
|
832 # Only template functions can have additional arguments. |
|
833 if customMethodCall is None or not 'additionalArguments' in customMethodCall: |
|
834 additionalArguments = '' |
|
835 else: |
|
836 additionalArguments = " %s," % customMethodCall['additionalArguments'] |
|
837 if disableOptimizationForMSVC: |
|
838 setOptimizationForMSVC(f, False) |
|
839 f.write(signature % (stubName, additionalArguments)) |
|
840 f.write("{\n") |
|
841 f.write(" XPC_QS_ASSERT_CONTEXT_OK(cx);\n") |
|
842 |
|
843 # Compute "args". |
|
844 f.write(" JS::CallArgs args = JS::CallArgsFromVp(argc, vp);\n") |
|
845 f.write(" (void) args;\n") |
|
846 |
|
847 # Compute "this". |
|
848 f.write(" JS::RootedObject obj(cx, JS_THIS_OBJECT(cx, vp));\n" |
|
849 " if (!obj)\n" |
|
850 " return false;\n") |
|
851 |
|
852 # Get the 'self' pointer. |
|
853 if customMethodCall is None or not 'thisType' in customMethodCall: |
|
854 f.write(" %s *self;\n" % member.iface.name) |
|
855 else: |
|
856 f.write(" %s *self;\n" % customMethodCall['thisType']) |
|
857 f.write(" xpc_qsSelfRef selfref;\n") |
|
858 pthisval = 'JS::MutableHandleValue::fromMarkedLocation(&vp[1])' # as above, ok to overwrite vp[1] |
|
859 |
|
860 if unwrapThisFailureFatal: |
|
861 unwrapFatalArg = "true" |
|
862 else: |
|
863 unwrapFatalArg = "false" |
|
864 |
|
865 f.write(" if (!xpc_qsUnwrapThis(cx, obj, &self, " |
|
866 "&selfref.ptr, %s, %s))\n" % (pthisval, unwrapFatalArg)) |
|
867 f.write(" return false;\n") |
|
868 |
|
869 if not unwrapThisFailureFatal: |
|
870 f.write(" if (!self) {\n") |
|
871 if (isGetter): |
|
872 f.write(" args.rval().setNull();\n") |
|
873 f.write(" return true;\n") |
|
874 f.write(" }\n"); |
|
875 |
|
876 if isMethod: |
|
877 # If there are any required arguments, check argc. |
|
878 requiredArgs = len(member.params) |
|
879 while requiredArgs and member.params[requiredArgs-1].optional: |
|
880 requiredArgs -= 1 |
|
881 elif isSetter: |
|
882 requiredArgs = 1 |
|
883 else: |
|
884 requiredArgs = 0 |
|
885 if requiredArgs: |
|
886 f.write(" if (argc < %d)\n" % requiredArgs) |
|
887 f.write(" return xpc_qsThrow(cx, " |
|
888 "NS_ERROR_XPC_NOT_ENOUGH_ARGS);\n") |
|
889 |
|
890 # Convert in-parameters. |
|
891 rvdeclared = False |
|
892 if isMethod: |
|
893 for i, param in enumerate(member.params): |
|
894 argName = 'arg%d' % i |
|
895 argTypeKey = argName + 'Type' |
|
896 if customMethodCall is None or not argTypeKey in customMethodCall: |
|
897 validateParam(member, param) |
|
898 realtype = param.realtype |
|
899 else: |
|
900 realtype = xpidl.Forward(name=customMethodCall[argTypeKey], |
|
901 location='', doccomments='') |
|
902 # Emit code to convert this argument from jsval. |
|
903 rvdeclared = writeArgumentUnboxing( |
|
904 f, i, argName, realtype, |
|
905 optional=param.optional, |
|
906 rvdeclared=rvdeclared, |
|
907 nullBehavior=param.null, |
|
908 undefinedBehavior=param.undefined) |
|
909 elif isSetter: |
|
910 rvdeclared = writeArgumentUnboxing(f, None, 'arg0', member.realtype, |
|
911 optional=False, |
|
912 rvdeclared=rvdeclared, |
|
913 nullBehavior=member.null, |
|
914 undefinedBehavior=member.undefined, |
|
915 propIndex=stringtable.stringIndex(member.name)) |
|
916 |
|
917 canFail = customMethodCall is None or customMethodCall.get('canFail', True) |
|
918 if canFail and not rvdeclared: |
|
919 f.write(" nsresult rv;\n") |
|
920 rvdeclared = True |
|
921 |
|
922 if code is not None: |
|
923 f.write("%s\n" % code) |
|
924 |
|
925 if code is None or (isGetter and callTemplate is ""): |
|
926 debugGetter = code is not None |
|
927 if debugGetter: |
|
928 f.write("#ifdef DEBUG\n") |
|
929 f.write(" nsresult debug_rv;\n") |
|
930 f.write(" nsCOMPtr<%s> debug_self;\n" |
|
931 " CallQueryInterface(self, getter_AddRefs(debug_self));\n" |
|
932 % member.iface.name); |
|
933 prefix = 'debug_' |
|
934 else: |
|
935 prefix = '' |
|
936 |
|
937 resultname = prefix + 'result' |
|
938 selfname = prefix + 'self' |
|
939 nsresultname = prefix + 'rv' |
|
940 |
|
941 # Prepare out-parameter. |
|
942 if isMethod or isGetter: |
|
943 writeResultDecl(f, member.realtype, resultname) |
|
944 |
|
945 # Call the method. |
|
946 if isMethod: |
|
947 comName = header.methodNativeName(member) |
|
948 argv = ['arg' + str(i) for i, p in enumerate(member.params)] |
|
949 if member.implicit_jscontext: |
|
950 argv.append('cx') |
|
951 if member.optional_argc: |
|
952 argv.append('std::min<uint32_t>(argc, %d) - %d' % |
|
953 (len(member.params), requiredArgs)) |
|
954 if not isVoidType(member.realtype): |
|
955 argv.append(outParamForm(resultname, member.realtype)) |
|
956 args = ', '.join(argv) |
|
957 else: |
|
958 comName = header.attributeNativeName(member, isGetter) |
|
959 if isGetter: |
|
960 args = outParamForm(resultname, member.realtype) |
|
961 else: |
|
962 args = "arg0" |
|
963 if member.implicit_jscontext: |
|
964 args = "cx, " + args |
|
965 |
|
966 f.write(" ") |
|
967 if canFail or debugGetter: |
|
968 f.write("%s = " % nsresultname) |
|
969 f.write("%s->%s(%s);\n" % (selfname, comName, args)) |
|
970 |
|
971 if debugGetter: |
|
972 checkSuccess = "NS_SUCCEEDED(debug_rv)" |
|
973 if canFail: |
|
974 checkSuccess += " == NS_SUCCEEDED(rv)" |
|
975 f.write(" MOZ_ASSERT(%s && " |
|
976 "xpc_qsSameResult(debug_result, result),\n" |
|
977 " \"Got the wrong answer from the custom " |
|
978 "method call!\");\n" % checkSuccess) |
|
979 f.write("#endif\n") |
|
980 |
|
981 if canFail: |
|
982 # Check for errors. |
|
983 f.write(" if (NS_FAILED(rv))\n") |
|
984 if isMethod: |
|
985 f.write(" return xpc_qsThrowMethodFailed(" |
|
986 "cx, rv, vp);\n") |
|
987 else: |
|
988 f.write(" return xpc_qsThrowGetterSetterFailed(cx, rv, " + |
|
989 "JSVAL_TO_OBJECT(vp[1]), (uint16_t)%d);\n" % |
|
990 stringtable.stringIndex(member.name)) |
|
991 |
|
992 # Convert the return value. |
|
993 if isMethod or isGetter: |
|
994 writeResultConv(f, member.realtype, 'args.rval()', '*vp') |
|
995 else: |
|
996 f.write(" return true;\n") |
|
997 |
|
998 # Epilog. |
|
999 f.write("}\n") |
|
1000 if disableOptimizationForMSVC: |
|
1001 setOptimizationForMSVC(f, True) |
|
1002 f.write("\n") |
|
1003 |
|
1004 # Now write out the call to the template function. |
|
1005 if customMethodCall is not None: |
|
1006 f.write(callTemplate) |
|
1007 |
|
1008 def writeAttrStubs(f, customMethodCalls, stringtable, attr): |
|
1009 getterName = (attr.iface.name + '_' |
|
1010 + header.attributeNativeName(attr, True)) |
|
1011 writeQuickStub(f, customMethodCalls, stringtable, attr, getterName) |
|
1012 if attr.readonly: |
|
1013 setterName = 'xpc_qsGetterOnlyNativeStub' |
|
1014 else: |
|
1015 setterName = (attr.iface.name + '_' |
|
1016 + header.attributeNativeName(attr, False)) |
|
1017 writeQuickStub(f, customMethodCalls, stringtable, attr, setterName, |
|
1018 isSetter=True) |
|
1019 |
|
1020 ps = ('{%d, %s, %s}' |
|
1021 % (stringtable.stringIndex(attr.name), getterName, setterName)) |
|
1022 return ps |
|
1023 |
|
1024 def writeMethodStub(f, customMethodCalls, stringtable, method): |
|
1025 """ Write a method stub to `f`. Return an xpc_qsFunctionSpec initializer. """ |
|
1026 |
|
1027 stubName = method.iface.name + '_' + header.methodNativeName(method) |
|
1028 writeQuickStub(f, customMethodCalls, stringtable, method, stubName) |
|
1029 fs = '{%d, %d, %s}' % (stringtable.stringIndex(method.name), |
|
1030 len(method.params), stubName) |
|
1031 return fs |
|
1032 |
|
1033 def writeStubsForInterface(f, customMethodCalls, stringtable, iface): |
|
1034 f.write("// === interface %s\n\n" % iface.name) |
|
1035 propspecs = [] |
|
1036 funcspecs = [] |
|
1037 for member in iface.stubMembers: |
|
1038 if member.kind == 'attribute': |
|
1039 ps = writeAttrStubs(f, customMethodCalls, stringtable, member) |
|
1040 propspecs.append(ps) |
|
1041 elif member.kind == 'method': |
|
1042 fs = writeMethodStub(f, customMethodCalls, stringtable, member) |
|
1043 funcspecs.append(fs) |
|
1044 else: |
|
1045 raise TypeError('expected attribute or method, not %r' |
|
1046 % member.__class__.__name__) |
|
1047 |
|
1048 iface.propspecs = propspecs |
|
1049 iface.funcspecs = funcspecs |
|
1050 |
|
1051 def hashIID(iid): |
|
1052 # See nsIDKey::HashCode in nsHashtable.h. |
|
1053 return int(iid[:8], 16) |
|
1054 |
|
1055 uuid_re = re.compile(r'^([0-9a-f]{8})-([0-9a-f]{4})-([0-9a-f]{4})-([0-9a-f]{4})-([0-9a-f]{12})$') |
|
1056 |
|
1057 def writeResultXPCInterfacesArray(f, conf, resulttypes): |
|
1058 f.write("// === XPCNativeInterface cache \n\n") |
|
1059 count = len(resulttypes) |
|
1060 if count > 0: |
|
1061 f.write("static XPCNativeInterface* interfaces[%d];\n\n" % count) |
|
1062 f.write("void %s_MarkInterfaces()\n" |
|
1063 "{\n" % conf.name) |
|
1064 if count > 0: |
|
1065 f.write(" for (uint32_t i = 0; i < %d; ++i)\n" |
|
1066 " if (interfaces[i])\n" |
|
1067 " interfaces[i]->Mark();\n" % count) |
|
1068 f.write("}\n") |
|
1069 f.write("void %s_ClearInterfaces()\n" |
|
1070 "{\n" % conf.name) |
|
1071 if count > 0: |
|
1072 f.write(" memset(interfaces, 0, %d * " |
|
1073 "sizeof(XPCNativeInterface*));\n" % count) |
|
1074 f.write("}\n\n") |
|
1075 i = 0 |
|
1076 for type in resulttypes: |
|
1077 f.write("static const uint32_t k_%s = %d;\n" % (type, i)) |
|
1078 i += 1 |
|
1079 if count > 0: |
|
1080 f.write("\n\n") |
|
1081 |
|
1082 def writeSpecs(f, elementType, varname, spec_type, spec_indices, interfaces): |
|
1083 index = 0 |
|
1084 f.write("static const %s %s[] = {\n" % (elementType, varname)) |
|
1085 for iface in interfaces: |
|
1086 specs = getattr(iface, spec_type) |
|
1087 if specs: |
|
1088 spec_indices[iface.name] = index |
|
1089 f.write(" // %s (index %d)\n" % (iface.name,index)) |
|
1090 for s in specs: |
|
1091 f.write(" %s,\n" % s) |
|
1092 index += len(specs) |
|
1093 f.write("};\n\n") |
|
1094 |
|
1095 def writeDefiner(f, conf, stringtable, interfaces): |
|
1096 f.write("// === Definer\n\n") |
|
1097 |
|
1098 # Write out the properties and functions |
|
1099 propspecs_indices = {} |
|
1100 funcspecs_indices = {} |
|
1101 prop_array_name = "all_properties" |
|
1102 func_array_name = "all_functions" |
|
1103 writeSpecs(f, "xpc_qsPropertySpec", prop_array_name, |
|
1104 "propspecs", propspecs_indices, interfaces) |
|
1105 writeSpecs(f, "xpc_qsFunctionSpec", func_array_name, |
|
1106 "funcspecs", funcspecs_indices, interfaces) |
|
1107 |
|
1108 # generate the static hash table |
|
1109 loadFactor = 0.6 |
|
1110 size = int(len(interfaces) / loadFactor) |
|
1111 buckets = [[] for i in range(size)] |
|
1112 for iface in interfaces: |
|
1113 # This if-statement discards interfaces specified with |
|
1114 # "nsInterfaceName.*" that don't have any stub-able members. |
|
1115 if iface.stubMembers or iface.newBindingProperties: |
|
1116 h = hashIID(iface.attributes.uuid) |
|
1117 buckets[h % size].append(iface) |
|
1118 |
|
1119 # Calculate where each interface's entry will show up in tableData. Where |
|
1120 # there are hash collisions, the extra entries are added at the end of the |
|
1121 # table. |
|
1122 entryIndexes = {} |
|
1123 arraySize = size |
|
1124 for i, bucket in enumerate(buckets): |
|
1125 if bucket: |
|
1126 entryIndexes[bucket[0].attributes.uuid] = i |
|
1127 for iface in bucket[1:]: |
|
1128 entryIndexes[iface.attributes.uuid] = arraySize |
|
1129 arraySize += 1 |
|
1130 |
|
1131 entries = [" {{0, 0, 0, {0, 0, 0, 0, 0, 0, 0, 0}}, " |
|
1132 "0, 0, 0, 0, nullptr, XPC_QS_NULL_INDEX, XPC_QS_NULL_INDEX}" |
|
1133 for i in range(arraySize)] |
|
1134 for i, bucket in enumerate(buckets): |
|
1135 for j, iface in enumerate(bucket): |
|
1136 # iid field |
|
1137 uuid = iface.attributes.uuid.lower() |
|
1138 m = uuid_re.match(uuid) |
|
1139 assert m is not None |
|
1140 m0, m1, m2, m3, m4 = m.groups() |
|
1141 m3arr = ('{0x%s, 0x%s, 0x%s, 0x%s, 0x%s, 0x%s, 0x%s, 0x%s}' |
|
1142 % (m3[0:2], m3[2:4], m4[0:2], m4[2:4], |
|
1143 m4[4:6], m4[6:8], m4[8:10], m4[10:12])) |
|
1144 iid = ('{0x%s, 0x%s, 0x%s, %s}' % (m0, m1, m2, m3arr)) |
|
1145 |
|
1146 # properties fields |
|
1147 prop_index = 0 |
|
1148 prop_n_entries = 0 |
|
1149 if iface.propspecs: |
|
1150 prop_index = propspecs_indices[iface.name] |
|
1151 prop_n_entries = len(iface.propspecs) |
|
1152 |
|
1153 # member fields |
|
1154 func_index = 0 |
|
1155 func_n_entries = 0 |
|
1156 if iface.funcspecs: |
|
1157 func_index = funcspecs_indices[iface.name] |
|
1158 func_n_entries = len(iface.funcspecs) |
|
1159 |
|
1160 # parentInterface field |
|
1161 baseName = iface.base |
|
1162 while baseName is not None: |
|
1163 piface = iface.idl.getName(baseName, None) |
|
1164 k = entryIndexes.get(piface.attributes.uuid) |
|
1165 if k is not None: |
|
1166 parentInterface = str(k) |
|
1167 break |
|
1168 baseName = piface.base |
|
1169 else: |
|
1170 parentInterface = "XPC_QS_NULL_INDEX" |
|
1171 |
|
1172 # chain field |
|
1173 if j == len(bucket) - 1: |
|
1174 chain = "XPC_QS_NULL_INDEX" |
|
1175 else: |
|
1176 k = entryIndexes[bucket[j+1].attributes.uuid] |
|
1177 chain = str(k) |
|
1178 |
|
1179 # add entry |
|
1180 entry = " /* %s */ {%s, %d, %d, %d, %d, %s, %s, %s}" % ( |
|
1181 iface.name, iid, prop_index, prop_n_entries, |
|
1182 func_index, func_n_entries, iface.newBindingProperties, |
|
1183 parentInterface, chain) |
|
1184 entries[entryIndexes[iface.attributes.uuid]] = entry |
|
1185 |
|
1186 f.write("static const xpc_qsHashEntry tableData[] = {\n") |
|
1187 f.write(",\n".join(entries)) |
|
1188 f.write("\n };\n\n") |
|
1189 f.write("// Make sure our table indices aren't overflowed\n" |
|
1190 "PR_STATIC_ASSERT((sizeof(tableData) / sizeof(tableData[0])) < (1 << (8 * sizeof(tableData[0].parentInterface))));\n" |
|
1191 "PR_STATIC_ASSERT((sizeof(tableData) / sizeof(tableData[0])) < (1 << (8 * sizeof(tableData[0].chain))));\n\n") |
|
1192 |
|
1193 # The string table for property and method names. |
|
1194 table_name = "stringtab" |
|
1195 stringtable.writeDefinition(f, table_name) |
|
1196 structNames = [prop_array_name, func_array_name] |
|
1197 for name in structNames: |
|
1198 f.write("PR_STATIC_ASSERT(sizeof(%s) < (1 << (8 * sizeof(%s[0].name_index))));\n" |
|
1199 % (table_name, name)) |
|
1200 f.write("\n") |
|
1201 |
|
1202 # the definer function (entry point to this quick stubs file) |
|
1203 f.write("namespace xpc {\n") |
|
1204 f.write("bool %s_DefineQuickStubs(" % conf.name) |
|
1205 f.write("JSContext *cx, JSObject *proto, unsigned flags, uint32_t count, " |
|
1206 "const nsID **iids)\n" |
|
1207 "{\n") |
|
1208 f.write(" return !!xpc_qsDefineQuickStubs(" |
|
1209 "cx, proto, flags, count, iids, %d, tableData, %s, %s, %s);\n" % ( |
|
1210 size, prop_array_name, func_array_name, table_name)) |
|
1211 f.write("}\n") |
|
1212 f.write("} // namespace xpc\n\n\n") |
|
1213 |
|
1214 |
|
1215 stubTopTemplate = '''\ |
|
1216 /* THIS FILE IS AUTOGENERATED - DO NOT EDIT */ |
|
1217 #include "jsapi.h" |
|
1218 #include "qsWinUndefs.h" |
|
1219 #include "qsObjectHelper.h" |
|
1220 #include "nsID.h" |
|
1221 #include "%s" |
|
1222 #include "nsCOMPtr.h" |
|
1223 #include "xpcprivate.h" // for XPCCallContext |
|
1224 #include "XPCQuickStubs.h" |
|
1225 #include <algorithm> |
|
1226 ''' |
|
1227 |
|
1228 def writeStubFile(filename, headerFilename, conf, interfaces): |
|
1229 print "Creating stub file", filename |
|
1230 makeutils.targets.append(filename) |
|
1231 |
|
1232 f = open(filename, 'w') |
|
1233 filesIncluded = set() |
|
1234 |
|
1235 def includeType(type): |
|
1236 type = unaliasType(type) |
|
1237 if type.kind in ('builtin', 'native'): |
|
1238 return None |
|
1239 file = conf.irregularFilenames.get(type.name, type.name) + '.h' |
|
1240 if file not in filesIncluded: |
|
1241 f.write('#include "%s"\n' % file) |
|
1242 filesIncluded.add(file) |
|
1243 return type |
|
1244 |
|
1245 def writeIncludesForMember(member): |
|
1246 assert member.kind in ('attribute', 'method') |
|
1247 resulttype = includeType(member.realtype) |
|
1248 if member.kind == 'method': |
|
1249 for p in member.params: |
|
1250 includeType(p.realtype) |
|
1251 return resulttype |
|
1252 |
|
1253 def writeIncludesForInterface(iface): |
|
1254 assert iface.kind == 'interface' |
|
1255 resulttypes = [] |
|
1256 for member in iface.stubMembers: |
|
1257 resulttype = writeIncludesForMember(member) |
|
1258 if resulttype is not None and not isVariantType(resulttype): |
|
1259 resulttypes.append(resulttype.name) |
|
1260 |
|
1261 includeType(iface) |
|
1262 |
|
1263 return resulttypes |
|
1264 |
|
1265 try: |
|
1266 f.write(stubTopTemplate % os.path.basename(headerFilename)) |
|
1267 resulttypes = [] |
|
1268 for iface in interfaces: |
|
1269 resulttypes.extend(writeIncludesForInterface(iface)) |
|
1270 for customInclude in conf.customIncludes: |
|
1271 f.write('#include "%s"\n' % customInclude) |
|
1272 f.write("\n\n") |
|
1273 writeResultXPCInterfacesArray(f, conf, frozenset(resulttypes)) |
|
1274 stringtable = StringTable() |
|
1275 for iface in interfaces: |
|
1276 writeStubsForInterface(f, conf.customMethodCalls, stringtable, iface) |
|
1277 writeDefiner(f, conf, stringtable, interfaces) |
|
1278 finally: |
|
1279 f.close() |
|
1280 |
|
1281 def main(): |
|
1282 from optparse import OptionParser |
|
1283 o = OptionParser(usage="usage: %prog [options] configfile") |
|
1284 o.add_option('-o', "--stub-output", |
|
1285 type='string', dest='stub_output', default=None, |
|
1286 help="Quick stub C++ source output file", metavar="FILE") |
|
1287 o.add_option('--header-output', type='string', default=None, |
|
1288 help="Quick stub header output file", metavar="FILE") |
|
1289 o.add_option('--makedepend-output', type='string', default=None, |
|
1290 help="gnumake dependencies output file", metavar="FILE") |
|
1291 o.add_option('--idlpath', type='string', default='.', |
|
1292 help="colon-separated directories to search for idl files", |
|
1293 metavar="PATH") |
|
1294 o.add_option('--cachedir', dest='cachedir', default='', |
|
1295 help="Directory in which to cache lex/parse tables.") |
|
1296 o.add_option("--verbose-errors", action='store_true', default=False, |
|
1297 help="When an error happens, display the Python traceback.") |
|
1298 (options, filenames) = o.parse_args() |
|
1299 |
|
1300 if len(filenames) != 1: |
|
1301 o.error("Exactly one config filename is needed.") |
|
1302 filename = filenames[0] |
|
1303 |
|
1304 if options.stub_output is None: |
|
1305 if filename.endswith('.qsconf') or filename.endswith('.py'): |
|
1306 options.stub_output = filename.rsplit('.', 1)[0] + '.cpp' |
|
1307 else: |
|
1308 options.stub_output = filename + '.cpp' |
|
1309 if options.header_output is None: |
|
1310 options.header_output = re.sub(r'(\.c|\.cpp)?$', '.h', |
|
1311 options.stub_output) |
|
1312 |
|
1313 if options.cachedir != '': |
|
1314 sys.path.append(options.cachedir) |
|
1315 if not os.path.isdir(options.cachedir): |
|
1316 os.makedirs(options.cachedir) |
|
1317 |
|
1318 try: |
|
1319 includePath = options.idlpath.split(':') |
|
1320 conf, interfaces = readConfigFile(filename, |
|
1321 includePath=includePath, |
|
1322 cachedir=options.cachedir) |
|
1323 writeStubFile(options.stub_output, options.header_output, |
|
1324 conf, interfaces) |
|
1325 writeHeaderFile(options.header_output, conf.name) |
|
1326 if options.makedepend_output is not None: |
|
1327 makeutils.writeMakeDependOutput(options.makedepend_output) |
|
1328 except Exception, exc: |
|
1329 if options.verbose_errors: |
|
1330 raise |
|
1331 elif isinstance(exc, (UserError, xpidl.IDLError)): |
|
1332 warn(str(exc)) |
|
1333 elif isinstance(exc, OSError): |
|
1334 warn("%s: %s" % (exc.__class__.__name__, exc)) |
|
1335 else: |
|
1336 raise |
|
1337 sys.exit(1) |
|
1338 |
|
1339 if __name__ == '__main__': |
|
1340 main() |