diff -r 000000000000 -r 6474c204b198 js/xpconnect/src/qsgen.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/js/xpconnect/src/qsgen.py Wed Dec 31 06:09:35 2014 +0100 @@ -0,0 +1,1340 @@ +#!/usr/bin/env/python +# qsgen.py - Generate XPConnect quick stubs. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +# =About quick stubs= +# qsgen.py generates "quick stubs", custom SpiderMonkey getters, setters, and +# methods for specified XPCOM interface members. These quick stubs serve at +# runtime as replacements for the XPConnect functions XPC_WN_GetterSetter and +# XPC_WN_CallMethod, which are the extremely generic (and slow) SpiderMonkey +# getter/setter/methods otherwise used for all XPCOM member accesses from JS. +# +# There are two ways quick stubs win: +# 1. Pure, transparent optimization by partial evaluation. +# 2. Cutting corners. +# +# == Partial evaluation == +# Partial evaluation is when you execute part of a program early (before or at +# compile time) so that you don't have to execute it at run time. In this +# case, everything that involves interpreting xptcall data (for example, the +# big methodInfo loops in XPCWrappedNative::CallMethod and the switch statement +# in XPCConert::JSData2Native) might as well happen at build time, since all +# the type information for any given member is already known. That's what this +# script does. It gets the information from IDL instead of XPT files. Apart +# from that, the code in this script is very similar to what you'll find in +# XPConnect itself. The advantage is that it runs once, at build time, not in +# tight loops at run time. +# +# == Cutting corners == +# The XPConnect versions have to be slow because they do tons of work that's +# only necessary in a few cases. The quick stubs skip a lot of that work. So +# quick stubs necessarily differ from XPConnect in potentially observable ways. +# For many specific interface members, the differences are not observable from +# scripts or don't matter enough to worry about; but you do have to be careful +# which members you decide to generate quick stubs for. +# +# The complete list of known differences, as of this writing, after an +# assiduous search: +# +# - Quick stubs affect the handling of naming conflicts--that is, which C++ +# method gets called when a script uses an XPCOM feature that is declared in +# more than one of the interfaces the object implements. Without quick +# stubs, XPConnect just walks the interfaces in the order they're listed by +# nsClassInfo. You get the first interface that implements a feature with +# that name. With quick stubs, it's the same except that non-quick-stubbed +# features are shadowed. +# +# - Quick stub methods are JSFastNative, which means that when a quick stub +# method is called, no JS stack frame is created. This doesn't affect +# Mozilla security checks because they look for scripted JSStackFrames, not +# native ones. +# +# It does affect the 'stack' property of JavaScript exceptions, though: the +# stubbed member will not appear. (Note that if the stubbed member itself +# fails, the member name will appear in the 'message' property.) +# +# - Many quick stubs don't create an XPCCallContext. In those cases, no entry +# is added to the XPCCallContext stack. So native implementations of +# quick-stubbed methods must avoid nsXPConnect::GetCurrentNativeCallContext. +# +# (Even when a quick stub does have an XPCCallContext, it never pushes it all +# the way to READY_TO_CALL state, so a lot of its members are garbage. But +# this doesn't endanger native implementations of non-quick-stubbed methods +# that use GetCurrentNativeCallContext and are called indirectly from +# quick-stubbed methods, because only the current top XPCCallContext is +# exposed--nsAXPCNativeCallContext does not expose +# XPCCallContext::GetPrevCallContext.) +# +# - Quick stubs never suspend the JS request. So they are only suitable for +# main-thread-only interfaces. +# +# - Quick stubs don't call XPCContext::SetLastResult. This is visible on the +# Components object. +# +# - Quick stubs skip a security check that XPConnect does in +# XPCWrappedNative::CallMethod. This means the security manager doesn't have +# an opportunity to veto accesses to members for which quick stubs exist. +# +# - There are many features of IDL that XPConnect supports but qsgen does not, +# including dependent types, arrays, and out parameters. + + +import xpidl +import header +import makeutils +import os, re +import sys + +# === Preliminaries + +def warn(msg): + sys.stderr.write(msg + '\n') + +def unaliasType(t): + while t.kind == 'typedef': + t = t.realtype + assert t is not None + return t + +def isVoidType(type): + """ Return True if the given xpidl type is void. """ + return type.kind == 'builtin' and type.name == 'void' + +def isInterfaceType(t): + t = unaliasType(t) + assert t.kind in ('builtin', 'native', 'interface', 'forward') + return t.kind in ('interface', 'forward') + +def isSpecificInterfaceType(t, name): + """ True if `t` is an interface type with the given name, or a forward + declaration or typedef aliasing it. + + `name` must not be the name of a typedef but the actual name of the + interface. + """ + t = unaliasType(t) + return t.kind in ('interface', 'forward') and t.name == name + +def getBuiltinOrNativeTypeName(t): + t = unaliasType(t) + if t.kind == 'builtin': + return t.name + elif t.kind == 'native': + assert t.specialtype is not None + return '[%s]' % t.specialtype + else: + return None + + +# === Reading the file + +class UserError(Exception): + pass + +def findIDL(includePath, irregularFilenames, interfaceName): + filename = irregularFilenames.get(interfaceName, interfaceName) + '.idl' + for d in includePath: + # Not os.path.join: we need a forward slash even on Windows because + # this filename ends up in makedepend output. + path = d + '/' + filename + if os.path.exists(path): + return path + raise UserError("No IDL file found for interface %s " + "in include path %r" + % (interfaceName, includePath)) + +def loadIDL(parser, includePath, filename): + makeutils.dependencies.append(filename) + text = open(filename, 'r').read() + idl = parser.parse(text, filename=filename) + idl.resolve(includePath, parser) + return idl + +def removeStubMember(memberId, member): + if member not in member.iface.stubMembers: + raise UserError("Trying to remove member %s from interface %s, but it was never added" + % (member.name, member.iface.name)) + member.iface.stubMembers.remove(member) + +def addStubMember(memberId, member): + if member.kind == 'method' and not member.implicit_jscontext and not isVariantType(member.realtype): + for param in member.params: + for attrname, value in vars(param).items(): + if value is True: + if attrname == 'optional': + continue + + raise UserError("Method %s, parameter %s: " + "unrecognized property %r" + % (memberId, param.name, attrname)) + + # Add this member to the list. + member.iface.stubMembers.append(member) + +def checkStubMember(member): + memberId = member.iface.name + "." + member.name + if member.kind not in ('method', 'attribute'): + raise UserError("Member %s is %r, not a method or attribute." + % (memberId, member.kind)) + if member.noscript: + raise UserError("%s %s is noscript." + % (member.kind.capitalize(), memberId)) + if member.kind == 'method' and member.notxpcom: + raise UserError( + "%s %s: notxpcom methods are not supported." + % (member.kind.capitalize(), memberId)) + + if (member.kind == 'attribute' + and not member.readonly + and isSpecificInterfaceType(member.realtype, 'nsIVariant')): + raise UserError( + "Attribute %s: Non-readonly attributes of type nsIVariant " + "are not supported." + % memberId) + + # Check for unknown properties. + for attrname, value in vars(member).items(): + if value is True and attrname not in ('readonly','optional_argc', + 'implicit_jscontext', + 'getter', 'stringifier'): + raise UserError("%s %s: unrecognized property %r" + % (member.kind.capitalize(), memberId, + attrname)) + +def parseMemberId(memberId): + """ Split the geven member id into its parts. """ + pieces = memberId.split('.') + if len(pieces) < 2: + raise UserError("Member %r: Missing dot." % memberId) + if len(pieces) > 2: + raise UserError("Member %r: Dots out of control." % memberId) + return tuple(pieces) + +class Configuration: + def __init__(self, filename, includePath): + self.includePath = includePath + config = {} + execfile(filename, config) + # required settings + for name in ('name', 'members'): + if name not in config: + raise UserError(filename + ": `%s` was not defined." % name) + setattr(self, name, config[name]) + # optional settings + self.irregularFilenames = config.get('irregularFilenames', {}) + self.customIncludes = config.get('customIncludes', []) + self.customMethodCalls = config.get('customMethodCalls', {}) + self.newBindingProperties = config.get('newBindingProperties', {}) + +def readConfigFile(filename, includePath, cachedir): + # Read the config file. + conf = Configuration(filename, includePath) + + # Now read IDL files to connect the information in the config file to + # actual XPCOM interfaces, methods, and attributes. + interfaces = [] + interfacesByName = {} + parser = xpidl.IDLParser(cachedir) + + def getInterface(interfaceName, errorLoc): + iface = interfacesByName.get(interfaceName) + if iface is None: + idlFile = findIDL(conf.includePath, conf.irregularFilenames, + interfaceName) + idl = loadIDL(parser, conf.includePath, idlFile) + if not idl.hasName(interfaceName): + raise UserError("The interface %s was not found " + "in the idl file %r." + % (interfaceName, idlFile)) + iface = idl.getName(interfaceName, errorLoc) + iface.stubMembers = [] + iface.newBindingProperties = 'nullptr' + interfaces.append(iface) + interfacesByName[interfaceName] = iface + return iface + + stubbedInterfaces = [] + + for memberId in conf.members: + add = True + interfaceName, memberName = parseMemberId(memberId) + + # If the interfaceName starts with -, then remove this entry from the list + if interfaceName[0] == '-': + add = False + interfaceName = interfaceName[1:] + + iface = getInterface(interfaceName, errorLoc='looking for %r' % memberId) + + if not iface.attributes.scriptable: + raise UserError("Interface %s is not scriptable." % interfaceName) + + if memberName == '*': + if not add: + raise UserError("Can't use negation in stub list with wildcard, in %s.*" % interfaceName) + + # Stub all scriptable members of this interface. + for member in iface.members: + if member.kind in ('method', 'attribute') and not member.noscript: + addStubMember(iface.name + '.' + member.name, member) + + if member.iface not in stubbedInterfaces: + stubbedInterfaces.append(member.iface) + else: + # Look up a member by name. + if memberName not in iface.namemap: + idlFile = iface.idl.parser.lexer.filename + raise UserError("Interface %s has no member %r. " + "(See IDL file %r.)" + % (interfaceName, memberName, idlFile)) + member = iface.namemap.get(memberName, None) + if add: + if member in iface.stubMembers: + raise UserError("Member %s is specified more than once." + % memberId) + + addStubMember(memberId, member) + if member.iface not in stubbedInterfaces: + stubbedInterfaces.append(member.iface) + else: + removeStubMember(memberId, member) + + for (interfaceName, v) in conf.newBindingProperties.iteritems(): + iface = getInterface(interfaceName, errorLoc='looking for %r' % interfaceName) + iface.newBindingProperties = v + if iface not in stubbedInterfaces: + stubbedInterfaces.append(iface) + + # Now go through and check all the interfaces' members + for iface in stubbedInterfaces: + for member in iface.stubMembers: + checkStubMember(member) + + return conf, interfaces + + +# === Generating the header file + +def writeHeaderFile(filename, name): + print "Creating header file", filename + + headerMacro = '__gen_%s__' % filename.replace('.', '_') + f = open(filename, 'w') + try: + f.write("/* THIS FILE IS AUTOGENERATED - DO NOT EDIT */\n" + "#ifndef " + headerMacro + "\n" + "#define " + headerMacro + "\n\n" + "bool " + name + "_DefineQuickStubs(" + "JSContext *cx, JSObject *proto, unsigned flags, " + "uint32_t count, const nsID **iids);\n\n" + "void " + name + "_MarkInterfaces();\n\n" + "void " + name + "_ClearInterfaces();\n\n" + "inline void " + name + "_InitInterfaces()\n" + "{\n" + " " + name + "_ClearInterfaces();\n" + "}\n\n" + "#endif\n") + finally: + f.close() + +# === Generating the source file + +class StringTable: + def __init__(self): + self.current_index = 0; + self.table = {} + self.reverse_table = {} + + def c_strlen(self, string): + return len(string) + 1 + + def stringIndex(self, string): + if string in self.table: + return self.table[string] + else: + result = self.current_index + self.table[string] = result + self.current_index += self.c_strlen(string) + return result + + def writeDefinition(self, f, name): + entries = self.table.items() + entries.sort(key=lambda x:x[1]) + # Avoid null-in-string warnings with GCC and potentially + # overlong string constants; write everything out the long way. + def explodeToCharArray(string): + return ", ".join(map(lambda x:"'%s'" % x, string)) + f.write("static const char %s[] = {\n" % name) + for (string, offset) in entries[:-1]: + f.write(" /* %5d */ %s, '\\0',\n" + % (offset, explodeToCharArray(string))) + f.write(" /* %5d */ %s, '\\0' };\n\n" + % (entries[-1][1], explodeToCharArray(entries[-1][0]))) + f.write("const char* xpc_qsStringTable = %s;\n\n" % name); + +def substitute(template, vals): + """ Simple replacement for string.Template, which isn't in Python 2.3. """ + def replacement(match): + return vals[match.group(1)] + return re.sub(r'\${(\w+)}', replacement, template) + +# From JSData2Native. +argumentUnboxingTemplates = { + 'octet': + " uint32_t ${name}_u32;\n" + " if (!JS::ToUint32(cx, ${argVal}, &${name}_u32))\n" + " return false;\n" + " uint8_t ${name} = (uint8_t) ${name}_u32;\n", + + 'short': + " int32_t ${name}_i32;\n" + " if (!JS::ToInt32(cx, ${argVal}, &${name}_i32))\n" + " return false;\n" + " int16_t ${name} = (int16_t) ${name}_i32;\n", + + 'unsigned short': + " uint32_t ${name}_u32;\n" + " if (!JS::ToUint32(cx, ${argVal}, &${name}_u32))\n" + " return false;\n" + " uint16_t ${name} = (uint16_t) ${name}_u32;\n", + + 'long': + " int32_t ${name};\n" + " if (!JS::ToInt32(cx, ${argVal}, &${name}))\n" + " return false;\n", + + 'unsigned long': + " uint32_t ${name};\n" + " if (!JS::ToUint32(cx, ${argVal}, &${name}))\n" + " return false;\n", + + 'long long': + " int64_t ${name};\n" + " if (!JS::ToInt64(cx, ${argVal}, &${name}))\n" + " return false;\n", + + 'unsigned long long': + " uint64_t ${name};\n" + " if (!JS::ToUint64(cx, ${argVal}, &${name}))\n" + " return false;\n", + + 'float': + " double ${name}_dbl;\n" + " if (!JS::ToNumber(cx, ${argVal}, &${name}_dbl))\n" + " return false;\n" + " float ${name} = (float) ${name}_dbl;\n", + + 'double': + " double ${name};\n" + " if (!JS::ToNumber(cx, ${argVal}, &${name}))\n" + " return false;\n", + + 'boolean': + " bool ${name} = JS::ToBoolean(${argVal});\n", + + '[astring]': + " xpc_qsAString ${name}(cx, ${argVal}, ${argPtr}, ${notPassed});\n" + " if (!${name}.IsValid())\n" + " return false;\n", + + '[domstring]': + " xpc_qsDOMString ${name}(cx, ${argVal},\n" + " ${argPtr}, ${notPassed},\n" + " xpc_qsDOMString::e${nullBehavior},\n" + " xpc_qsDOMString::e${undefinedBehavior});\n" + " if (!${name}.IsValid())\n" + " return false;\n", + + 'string': + " JSAutoByteString ${name}_bytes;\n" + " if (!xpc_qsJsvalToCharStr(cx, ${argVal}, &${name}_bytes))\n" + " return false;\n" + " char *${name} = ${name}_bytes.ptr();\n", + + '[cstring]': + " xpc_qsACString ${name}(cx, ${argVal}, ${argPtr}, ${notPassed});\n" + " if (!${name}.IsValid())\n" + " return false;\n", + + '[utf8string]': + " xpc_qsAUTF8String ${name}(cx, ${argVal}, ${argPtr}, ${notPassed});\n" + " if (!${name}.IsValid())\n" + " return false;\n", + + '[jsval]': + " JS::RootedValue ${name}(cx, ${argVal});\n" + } + +# From JSData2Native. +# +# Omitted optional arguments are treated as though the caller had passed JS +# `null`; this behavior is from XPCWrappedNative::CallMethod. The 'jsval' type, +# however, defaults to 'undefined'. +# +def writeArgumentUnboxing(f, i, name, type, optional, rvdeclared, + nullBehavior, undefinedBehavior, + propIndex=None): + # f - file to write to + # i - int or None - Indicates the source jsval. If i is an int, the source + # jsval is args[i]; otherwise it is args[0]. But if Python i >= C++ argc, + # which can only happen if optional is True, the argument is missing; + # use JSVAL_NULL as the source jsval instead. + # name - str - name of the native C++ variable to create. + # type - xpidl.{Interface,Native,Builtin} - IDL type of argument + # optional - bool - True if the parameter is optional. + # rvdeclared - bool - False if no |nsresult rv| has been declared earlier. + + typeName = getBuiltinOrNativeTypeName(type) + + isSetter = (i is None) + + notPassed = "false" + if isSetter: + argPtr = "args[0]" + argVal = "args[0]" + elif optional: + if typeName == "[jsval]": + val = "JS::UndefinedHandleValue" + else: + val = "JS::NullHandleValue" + argVal = "(%d < argc ? args[%d] : %s)" % (i, i, val) + if typeName == "[jsval]": + # This should use the rooted argument, + # however we probably won't ever need to support that. + argPtr = None + notPassed = None + else: + # Need a MutableHandleValue to pass into eg the xpc_qsAString + # constructor, but the corresponding argument may not have been + # passed in at all. In that case, point the MutableHandleValue at a + # dummy variable, and pass in a boolean saying that the argument + # wasn't passed (previously, this used a NULL ptr sentinel value.) + f.write(" JS::RootedValue {name}_dummy(cx);\n".format(name=name)) + f.write(" JS::MutableHandleValue {name}_mhv({i} < argc ? args[{i}] : &{name}_dummy);\n".format(name=name, i=i)) + f.write(" (void) {name}_mhv;\n".format(name=name, i=i)) + + argPtr = "{name}_mhv".format(name=name) + notPassed = "argc < {i}".format(i=i) + else: + argVal = "args[%d]" % i + argPtr = "args[%d]" % i + + params = { + 'name': name, + 'argVal': argVal, + 'argPtr': argPtr, + 'notPassed': notPassed, + 'nullBehavior': nullBehavior or 'DefaultNullBehavior', + 'undefinedBehavior': undefinedBehavior or 'DefaultUndefinedBehavior' + } + + if typeName is not None: + template = argumentUnboxingTemplates.get(typeName) + if template is not None: + f.write(substitute(template, params)) + return rvdeclared + # else fall through; the type isn't supported yet. + elif isInterfaceType(type): + if type.name == 'nsIVariant': + # Totally custom. + template = ( + " nsCOMPtr ${name}(already_AddRefed(" + "XPCVariant::newVariant(cx, ${argVal})));\n" + " if (!${name}) {\n" + " xpc_qsThrowBadArg(cx, NS_ERROR_INVALID_ARG, vp, %d);\n" + " return false;\n" + " }") % i + f.write(substitute(template, params)) + return rvdeclared + elif type.name == 'nsIAtom': + # Should have special atomizing behavior. Fall through. + pass + else: + if not rvdeclared: + f.write(" nsresult rv;\n"); + f.write(" %s *%s;\n" % (type.name, name)) + f.write(" xpc_qsSelfRef %sref;\n" % name) + f.write(" rv = xpc_qsUnwrapArg<%s>(" + "cx, %s, &%s, &%sref.ptr, %s);\n" + % (type.name, argVal, name, name, argPtr)) + f.write(" if (NS_FAILED(rv)) {\n") + if isSetter: + assert(propIndex is not None) + f.write(" xpc_qsThrowBadSetterValue(cx, rv, JSVAL_TO_OBJECT(vp[1]), (uint16_t)%s);\n" % + propIndex) + else: + f.write(" xpc_qsThrowBadArg(cx, rv, vp, %d);\n" % i) + f.write(" return false;\n" + " }\n") + return True + + warn("Unable to unbox argument of type %s (native type %s)" % (type.name, typeName)) + if i is None: + src = 'args[0]' + else: + src = 'args[%d]' % i + f.write(" !; // TODO - Unbox argument %s = %s\n" % (name, src)) + return rvdeclared + +def writeResultDecl(f, type, varname): + if isVoidType(type): + return # nothing to declare + + t = unaliasType(type) + if t.kind == 'builtin': + if not t.nativename.endswith('*'): + if type.kind == 'typedef': + typeName = type.name # use it + else: + typeName = t.nativename + f.write(" %s %s;\n" % (typeName, varname)) + return + elif t.kind == 'native': + name = getBuiltinOrNativeTypeName(t) + if name in ('[domstring]', '[astring]'): + f.write(" nsString %s;\n" % varname) + return + elif name == '[jsval]': + f.write(" JS::RootedValue %s(cx);\n" % varname) + return + elif t.kind in ('interface', 'forward'): + f.write(" nsCOMPtr<%s> %s;\n" % (type.name, varname)) + return + + warn("Unable to declare result of type %s" % type.name) + f.write(" !; // TODO - Declare out parameter `%s`.\n" % varname) + +def outParamForm(name, type): + type = unaliasType(type) + if type.kind == 'builtin': + return '&' + name + elif type.kind == 'native': + if getBuiltinOrNativeTypeName(type) == '[jsval]': + return '&' + name + if type.modifier == 'ref': + return name + else: + return '&' + name + else: + return 'getter_AddRefs(%s)' % name + +# From NativeData2JS. +resultConvTemplates = { + 'void': + " ${jsvalRef} = JSVAL_VOID;\n" + " return true;\n", + + 'octet': + " ${jsvalRef} = INT_TO_JSVAL((int32_t) result);\n" + " return true;\n", + + 'short': + " ${jsvalRef} = INT_TO_JSVAL((int32_t) result);\n" + " return true;\n", + + 'long': + " ${jsvalRef} = INT_TO_JSVAL(result);\n" + " return true;\n", + + 'long long': + " return xpc_qsInt64ToJsval(cx, result, ${jsvalPtr});\n", + + 'unsigned short': + " ${jsvalRef} = INT_TO_JSVAL((int32_t) result);\n" + " return true;\n", + + 'unsigned long': + " ${jsvalRef} = UINT_TO_JSVAL(result);\n" + " return true;\n", + + 'unsigned long long': + " return xpc_qsUint64ToJsval(cx, result, ${jsvalPtr});\n", + + 'float': + " ${jsvalRef} = JS_NumberValue(result);\n" + " return true;\n", + + 'double': + " ${jsvalRef} = JS_NumberValue(result);\n" + " return true;\n", + + 'boolean': + " ${jsvalRef} = (result ? JSVAL_TRUE : JSVAL_FALSE);\n" + " return true;\n", + + '[astring]': + " return xpc::StringToJsval(cx, result, ${jsvalPtr});\n", + + '[domstring]': + " return xpc::StringToJsval(cx, result, ${jsvalPtr});\n", + + '[jsval]': + " ${jsvalPtr}.set(result);\n" + " return JS_WrapValue(cx, ${jsvalPtr});\n" + } + +def isVariantType(t): + return isSpecificInterfaceType(t, 'nsIVariant') + +def writeResultConv(f, type, jsvalPtr, jsvalRef): + """ Emit code to convert the C++ variable `result` to a jsval. + + The emitted code contains a return statement; it returns true on + success, false on error. + """ + # From NativeData2JS. + typeName = getBuiltinOrNativeTypeName(type) + if typeName is not None: + template = resultConvTemplates.get(typeName) + if template is not None: + values = {'jsvalRef': jsvalRef, + 'jsvalPtr': jsvalPtr} + f.write(substitute(template, values)) + return + # else fall through; this type isn't supported yet + elif isInterfaceType(type): + if isVariantType(type): + f.write(" return xpc_qsVariantToJsval(cx, result, %s);\n" + % jsvalPtr) + return + else: + f.write(" if (!result) {\n" + " %s.setNull();\n" + " return true;\n" + " }\n" + " nsWrapperCache* cache = xpc_qsGetWrapperCache(result);\n" + " if (xpc_FastGetCachedWrapper(cx, cache, %s)) {\n" + " return true;\n" + " }\n" + " // After this point do not use 'result'!\n" + " qsObjectHelper helper(result, cache);\n" + " return xpc_qsXPCOMObjectToJsval(cx, " + "helper, &NS_GET_IID(%s), &interfaces[k_%s], %s);\n" + % (jsvalPtr, jsvalPtr, type.name, type.name, jsvalPtr)) + return + + warn("Unable to convert result of type %s" % type.name) + f.write(" !; // TODO - Convert `result` to jsval, store in `%s`.\n" + % jsvalRef) + f.write(" return xpc_qsThrow(cx, NS_ERROR_UNEXPECTED); // FIXME\n") + +def memberNeedsCallee(member): + return isInterfaceType(member.realtype) + +def validateParam(member, param): + def pfail(msg): + raise UserError( + member.iface.name + '.' + member.name + ": " + "parameter " + param.name + ": " + msg) + + if param.iid_is is not None: + pfail("iid_is parameters are not supported.") + if param.size_is is not None: + pfail("size_is parameters are not supported.") + if param.retval: + pfail("Unexpected retval parameter!") + if param.paramtype in ('out', 'inout'): + pfail("Out parameters are not supported.") + if param.const or param.array or param.shared: + pfail("I am a simple caveman.") + +def setOptimizationForMSVC(f, b): + """ Write a pragma that turns optimizations off (if b is False) or + on (if b is True) for MSVC. + """ + if b: + pragmaParam = "on" + else: + pragmaParam = "off" + f.write("#ifdef _MSC_VER\n") + f.write('# pragma optimize("", %s)\n'%pragmaParam) + f.write("#endif\n") + +def writeQuickStub(f, customMethodCalls, stringtable, member, stubName, + isSetter=False): + """ Write a single quick stub (a custom SpiderMonkey getter/setter/method) + for the specified XPCOM interface-member. + """ + # Workaround for suspected compiler bug. + # See https://bugzilla.mozilla.org/show_bug.cgi?id=750019 + disableOptimizationForMSVC = (stubName == 'nsIDOMHTMLDocument_Write') + + isAttr = (member.kind == 'attribute') + isMethod = (member.kind == 'method') + assert isAttr or isMethod + isGetter = isAttr and not isSetter + + signature = ("static bool\n" + + "%s(JSContext *cx, unsigned argc,%s jsval *vp)\n") + + customMethodCall = customMethodCalls.get(stubName, None) + + if customMethodCall is None: + customMethodCall = customMethodCalls.get(member.iface.name + '_', None) + if customMethodCall is not None: + if isMethod: + code = customMethodCall.get('code', None) + elif isGetter: + code = customMethodCall.get('getter_code', None) + else: + code = customMethodCall.get('setter_code', None) + else: + code = None + + if code is not None: + templateName = member.iface.name + if isGetter: + templateName += '_Get' + elif isSetter: + templateName += '_Set' + + # Generate the code for the stub, calling the template function + # that's shared between the stubs. The stubs can't have additional + # arguments, only the template function can. + callTemplate = signature % (stubName, '') + callTemplate += "{\n" + + nativeName = (member.binaryname is not None and member.binaryname + or header.firstCap(member.name)) + argumentValues = (customMethodCall['additionalArgumentValues'] + % nativeName) + callTemplate += (" return %s(cx, argc, %s, vp);\n" + % (templateName, argumentValues)) + callTemplate += "}\n\n" + + # Fall through and create the template function stub called from the + # real stubs, but only generate the stub once. Otherwise, just write + # out the call to the template function and return. + templateGenerated = templateName + '_generated' + if templateGenerated in customMethodCall: + f.write(callTemplate) + return + customMethodCall[templateGenerated] = True + + stubName = templateName + else: + callTemplate = "" + else: + callTemplate = "" + code = customMethodCall.get('code', None) + + unwrapThisFailureFatal = (customMethodCall is None or + customMethodCall.get('unwrapThisFailureFatal', True)); + if (not unwrapThisFailureFatal and not isAttr): + raise UserError(member.iface.name + '.' + member.name + ": " + "Unwrapping this failure must be fatal for methods") + + # Function prolog. + + # Only template functions can have additional arguments. + if customMethodCall is None or not 'additionalArguments' in customMethodCall: + additionalArguments = '' + else: + additionalArguments = " %s," % customMethodCall['additionalArguments'] + if disableOptimizationForMSVC: + setOptimizationForMSVC(f, False) + f.write(signature % (stubName, additionalArguments)) + f.write("{\n") + f.write(" XPC_QS_ASSERT_CONTEXT_OK(cx);\n") + + # Compute "args". + f.write(" JS::CallArgs args = JS::CallArgsFromVp(argc, vp);\n") + f.write(" (void) args;\n") + + # Compute "this". + f.write(" JS::RootedObject obj(cx, JS_THIS_OBJECT(cx, vp));\n" + " if (!obj)\n" + " return false;\n") + + # Get the 'self' pointer. + if customMethodCall is None or not 'thisType' in customMethodCall: + f.write(" %s *self;\n" % member.iface.name) + else: + f.write(" %s *self;\n" % customMethodCall['thisType']) + f.write(" xpc_qsSelfRef selfref;\n") + pthisval = 'JS::MutableHandleValue::fromMarkedLocation(&vp[1])' # as above, ok to overwrite vp[1] + + if unwrapThisFailureFatal: + unwrapFatalArg = "true" + else: + unwrapFatalArg = "false" + + f.write(" if (!xpc_qsUnwrapThis(cx, obj, &self, " + "&selfref.ptr, %s, %s))\n" % (pthisval, unwrapFatalArg)) + f.write(" return false;\n") + + if not unwrapThisFailureFatal: + f.write(" if (!self) {\n") + if (isGetter): + f.write(" args.rval().setNull();\n") + f.write(" return true;\n") + f.write(" }\n"); + + if isMethod: + # If there are any required arguments, check argc. + requiredArgs = len(member.params) + while requiredArgs and member.params[requiredArgs-1].optional: + requiredArgs -= 1 + elif isSetter: + requiredArgs = 1 + else: + requiredArgs = 0 + if requiredArgs: + f.write(" if (argc < %d)\n" % requiredArgs) + f.write(" return xpc_qsThrow(cx, " + "NS_ERROR_XPC_NOT_ENOUGH_ARGS);\n") + + # Convert in-parameters. + rvdeclared = False + if isMethod: + for i, param in enumerate(member.params): + argName = 'arg%d' % i + argTypeKey = argName + 'Type' + if customMethodCall is None or not argTypeKey in customMethodCall: + validateParam(member, param) + realtype = param.realtype + else: + realtype = xpidl.Forward(name=customMethodCall[argTypeKey], + location='', doccomments='') + # Emit code to convert this argument from jsval. + rvdeclared = writeArgumentUnboxing( + f, i, argName, realtype, + optional=param.optional, + rvdeclared=rvdeclared, + nullBehavior=param.null, + undefinedBehavior=param.undefined) + elif isSetter: + rvdeclared = writeArgumentUnboxing(f, None, 'arg0', member.realtype, + optional=False, + rvdeclared=rvdeclared, + nullBehavior=member.null, + undefinedBehavior=member.undefined, + propIndex=stringtable.stringIndex(member.name)) + + canFail = customMethodCall is None or customMethodCall.get('canFail', True) + if canFail and not rvdeclared: + f.write(" nsresult rv;\n") + rvdeclared = True + + if code is not None: + f.write("%s\n" % code) + + if code is None or (isGetter and callTemplate is ""): + debugGetter = code is not None + if debugGetter: + f.write("#ifdef DEBUG\n") + f.write(" nsresult debug_rv;\n") + f.write(" nsCOMPtr<%s> debug_self;\n" + " CallQueryInterface(self, getter_AddRefs(debug_self));\n" + % member.iface.name); + prefix = 'debug_' + else: + prefix = '' + + resultname = prefix + 'result' + selfname = prefix + 'self' + nsresultname = prefix + 'rv' + + # Prepare out-parameter. + if isMethod or isGetter: + writeResultDecl(f, member.realtype, resultname) + + # Call the method. + if isMethod: + comName = header.methodNativeName(member) + argv = ['arg' + str(i) for i, p in enumerate(member.params)] + if member.implicit_jscontext: + argv.append('cx') + if member.optional_argc: + argv.append('std::min(argc, %d) - %d' % + (len(member.params), requiredArgs)) + if not isVoidType(member.realtype): + argv.append(outParamForm(resultname, member.realtype)) + args = ', '.join(argv) + else: + comName = header.attributeNativeName(member, isGetter) + if isGetter: + args = outParamForm(resultname, member.realtype) + else: + args = "arg0" + if member.implicit_jscontext: + args = "cx, " + args + + f.write(" ") + if canFail or debugGetter: + f.write("%s = " % nsresultname) + f.write("%s->%s(%s);\n" % (selfname, comName, args)) + + if debugGetter: + checkSuccess = "NS_SUCCEEDED(debug_rv)" + if canFail: + checkSuccess += " == NS_SUCCEEDED(rv)" + f.write(" MOZ_ASSERT(%s && " + "xpc_qsSameResult(debug_result, result),\n" + " \"Got the wrong answer from the custom " + "method call!\");\n" % checkSuccess) + f.write("#endif\n") + + if canFail: + # Check for errors. + f.write(" if (NS_FAILED(rv))\n") + if isMethod: + f.write(" return xpc_qsThrowMethodFailed(" + "cx, rv, vp);\n") + else: + f.write(" return xpc_qsThrowGetterSetterFailed(cx, rv, " + + "JSVAL_TO_OBJECT(vp[1]), (uint16_t)%d);\n" % + stringtable.stringIndex(member.name)) + + # Convert the return value. + if isMethod or isGetter: + writeResultConv(f, member.realtype, 'args.rval()', '*vp') + else: + f.write(" return true;\n") + + # Epilog. + f.write("}\n") + if disableOptimizationForMSVC: + setOptimizationForMSVC(f, True) + f.write("\n") + + # Now write out the call to the template function. + if customMethodCall is not None: + f.write(callTemplate) + +def writeAttrStubs(f, customMethodCalls, stringtable, attr): + getterName = (attr.iface.name + '_' + + header.attributeNativeName(attr, True)) + writeQuickStub(f, customMethodCalls, stringtable, attr, getterName) + if attr.readonly: + setterName = 'xpc_qsGetterOnlyNativeStub' + else: + setterName = (attr.iface.name + '_' + + header.attributeNativeName(attr, False)) + writeQuickStub(f, customMethodCalls, stringtable, attr, setterName, + isSetter=True) + + ps = ('{%d, %s, %s}' + % (stringtable.stringIndex(attr.name), getterName, setterName)) + return ps + +def writeMethodStub(f, customMethodCalls, stringtable, method): + """ Write a method stub to `f`. Return an xpc_qsFunctionSpec initializer. """ + + stubName = method.iface.name + '_' + header.methodNativeName(method) + writeQuickStub(f, customMethodCalls, stringtable, method, stubName) + fs = '{%d, %d, %s}' % (stringtable.stringIndex(method.name), + len(method.params), stubName) + return fs + +def writeStubsForInterface(f, customMethodCalls, stringtable, iface): + f.write("// === interface %s\n\n" % iface.name) + propspecs = [] + funcspecs = [] + for member in iface.stubMembers: + if member.kind == 'attribute': + ps = writeAttrStubs(f, customMethodCalls, stringtable, member) + propspecs.append(ps) + elif member.kind == 'method': + fs = writeMethodStub(f, customMethodCalls, stringtable, member) + funcspecs.append(fs) + else: + raise TypeError('expected attribute or method, not %r' + % member.__class__.__name__) + + iface.propspecs = propspecs + iface.funcspecs = funcspecs + +def hashIID(iid): + # See nsIDKey::HashCode in nsHashtable.h. + return int(iid[:8], 16) + +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})$') + +def writeResultXPCInterfacesArray(f, conf, resulttypes): + f.write("// === XPCNativeInterface cache \n\n") + count = len(resulttypes) + if count > 0: + f.write("static XPCNativeInterface* interfaces[%d];\n\n" % count) + f.write("void %s_MarkInterfaces()\n" + "{\n" % conf.name) + if count > 0: + f.write(" for (uint32_t i = 0; i < %d; ++i)\n" + " if (interfaces[i])\n" + " interfaces[i]->Mark();\n" % count) + f.write("}\n") + f.write("void %s_ClearInterfaces()\n" + "{\n" % conf.name) + if count > 0: + f.write(" memset(interfaces, 0, %d * " + "sizeof(XPCNativeInterface*));\n" % count) + f.write("}\n\n") + i = 0 + for type in resulttypes: + f.write("static const uint32_t k_%s = %d;\n" % (type, i)) + i += 1 + if count > 0: + f.write("\n\n") + +def writeSpecs(f, elementType, varname, spec_type, spec_indices, interfaces): + index = 0 + f.write("static const %s %s[] = {\n" % (elementType, varname)) + for iface in interfaces: + specs = getattr(iface, spec_type) + if specs: + spec_indices[iface.name] = index + f.write(" // %s (index %d)\n" % (iface.name,index)) + for s in specs: + f.write(" %s,\n" % s) + index += len(specs) + f.write("};\n\n") + +def writeDefiner(f, conf, stringtable, interfaces): + f.write("// === Definer\n\n") + + # Write out the properties and functions + propspecs_indices = {} + funcspecs_indices = {} + prop_array_name = "all_properties" + func_array_name = "all_functions" + writeSpecs(f, "xpc_qsPropertySpec", prop_array_name, + "propspecs", propspecs_indices, interfaces) + writeSpecs(f, "xpc_qsFunctionSpec", func_array_name, + "funcspecs", funcspecs_indices, interfaces) + + # generate the static hash table + loadFactor = 0.6 + size = int(len(interfaces) / loadFactor) + buckets = [[] for i in range(size)] + for iface in interfaces: + # This if-statement discards interfaces specified with + # "nsInterfaceName.*" that don't have any stub-able members. + if iface.stubMembers or iface.newBindingProperties: + h = hashIID(iface.attributes.uuid) + buckets[h % size].append(iface) + + # Calculate where each interface's entry will show up in tableData. Where + # there are hash collisions, the extra entries are added at the end of the + # table. + entryIndexes = {} + arraySize = size + for i, bucket in enumerate(buckets): + if bucket: + entryIndexes[bucket[0].attributes.uuid] = i + for iface in bucket[1:]: + entryIndexes[iface.attributes.uuid] = arraySize + arraySize += 1 + + entries = [" {{0, 0, 0, {0, 0, 0, 0, 0, 0, 0, 0}}, " + "0, 0, 0, 0, nullptr, XPC_QS_NULL_INDEX, XPC_QS_NULL_INDEX}" + for i in range(arraySize)] + for i, bucket in enumerate(buckets): + for j, iface in enumerate(bucket): + # iid field + uuid = iface.attributes.uuid.lower() + m = uuid_re.match(uuid) + assert m is not None + m0, m1, m2, m3, m4 = m.groups() + m3arr = ('{0x%s, 0x%s, 0x%s, 0x%s, 0x%s, 0x%s, 0x%s, 0x%s}' + % (m3[0:2], m3[2:4], m4[0:2], m4[2:4], + m4[4:6], m4[6:8], m4[8:10], m4[10:12])) + iid = ('{0x%s, 0x%s, 0x%s, %s}' % (m0, m1, m2, m3arr)) + + # properties fields + prop_index = 0 + prop_n_entries = 0 + if iface.propspecs: + prop_index = propspecs_indices[iface.name] + prop_n_entries = len(iface.propspecs) + + # member fields + func_index = 0 + func_n_entries = 0 + if iface.funcspecs: + func_index = funcspecs_indices[iface.name] + func_n_entries = len(iface.funcspecs) + + # parentInterface field + baseName = iface.base + while baseName is not None: + piface = iface.idl.getName(baseName, None) + k = entryIndexes.get(piface.attributes.uuid) + if k is not None: + parentInterface = str(k) + break + baseName = piface.base + else: + parentInterface = "XPC_QS_NULL_INDEX" + + # chain field + if j == len(bucket) - 1: + chain = "XPC_QS_NULL_INDEX" + else: + k = entryIndexes[bucket[j+1].attributes.uuid] + chain = str(k) + + # add entry + entry = " /* %s */ {%s, %d, %d, %d, %d, %s, %s, %s}" % ( + iface.name, iid, prop_index, prop_n_entries, + func_index, func_n_entries, iface.newBindingProperties, + parentInterface, chain) + entries[entryIndexes[iface.attributes.uuid]] = entry + + f.write("static const xpc_qsHashEntry tableData[] = {\n") + f.write(",\n".join(entries)) + f.write("\n };\n\n") + f.write("// Make sure our table indices aren't overflowed\n" + "PR_STATIC_ASSERT((sizeof(tableData) / sizeof(tableData[0])) < (1 << (8 * sizeof(tableData[0].parentInterface))));\n" + "PR_STATIC_ASSERT((sizeof(tableData) / sizeof(tableData[0])) < (1 << (8 * sizeof(tableData[0].chain))));\n\n") + + # The string table for property and method names. + table_name = "stringtab" + stringtable.writeDefinition(f, table_name) + structNames = [prop_array_name, func_array_name] + for name in structNames: + f.write("PR_STATIC_ASSERT(sizeof(%s) < (1 << (8 * sizeof(%s[0].name_index))));\n" + % (table_name, name)) + f.write("\n") + + # the definer function (entry point to this quick stubs file) + f.write("namespace xpc {\n") + f.write("bool %s_DefineQuickStubs(" % conf.name) + f.write("JSContext *cx, JSObject *proto, unsigned flags, uint32_t count, " + "const nsID **iids)\n" + "{\n") + f.write(" return !!xpc_qsDefineQuickStubs(" + "cx, proto, flags, count, iids, %d, tableData, %s, %s, %s);\n" % ( + size, prop_array_name, func_array_name, table_name)) + f.write("}\n") + f.write("} // namespace xpc\n\n\n") + + +stubTopTemplate = '''\ +/* THIS FILE IS AUTOGENERATED - DO NOT EDIT */ +#include "jsapi.h" +#include "qsWinUndefs.h" +#include "qsObjectHelper.h" +#include "nsID.h" +#include "%s" +#include "nsCOMPtr.h" +#include "xpcprivate.h" // for XPCCallContext +#include "XPCQuickStubs.h" +#include +''' + +def writeStubFile(filename, headerFilename, conf, interfaces): + print "Creating stub file", filename + makeutils.targets.append(filename) + + f = open(filename, 'w') + filesIncluded = set() + + def includeType(type): + type = unaliasType(type) + if type.kind in ('builtin', 'native'): + return None + file = conf.irregularFilenames.get(type.name, type.name) + '.h' + if file not in filesIncluded: + f.write('#include "%s"\n' % file) + filesIncluded.add(file) + return type + + def writeIncludesForMember(member): + assert member.kind in ('attribute', 'method') + resulttype = includeType(member.realtype) + if member.kind == 'method': + for p in member.params: + includeType(p.realtype) + return resulttype + + def writeIncludesForInterface(iface): + assert iface.kind == 'interface' + resulttypes = [] + for member in iface.stubMembers: + resulttype = writeIncludesForMember(member) + if resulttype is not None and not isVariantType(resulttype): + resulttypes.append(resulttype.name) + + includeType(iface) + + return resulttypes + + try: + f.write(stubTopTemplate % os.path.basename(headerFilename)) + resulttypes = [] + for iface in interfaces: + resulttypes.extend(writeIncludesForInterface(iface)) + for customInclude in conf.customIncludes: + f.write('#include "%s"\n' % customInclude) + f.write("\n\n") + writeResultXPCInterfacesArray(f, conf, frozenset(resulttypes)) + stringtable = StringTable() + for iface in interfaces: + writeStubsForInterface(f, conf.customMethodCalls, stringtable, iface) + writeDefiner(f, conf, stringtable, interfaces) + finally: + f.close() + +def main(): + from optparse import OptionParser + o = OptionParser(usage="usage: %prog [options] configfile") + o.add_option('-o', "--stub-output", + type='string', dest='stub_output', default=None, + help="Quick stub C++ source output file", metavar="FILE") + o.add_option('--header-output', type='string', default=None, + help="Quick stub header output file", metavar="FILE") + o.add_option('--makedepend-output', type='string', default=None, + help="gnumake dependencies output file", metavar="FILE") + o.add_option('--idlpath', type='string', default='.', + help="colon-separated directories to search for idl files", + metavar="PATH") + o.add_option('--cachedir', dest='cachedir', default='', + help="Directory in which to cache lex/parse tables.") + o.add_option("--verbose-errors", action='store_true', default=False, + help="When an error happens, display the Python traceback.") + (options, filenames) = o.parse_args() + + if len(filenames) != 1: + o.error("Exactly one config filename is needed.") + filename = filenames[0] + + if options.stub_output is None: + if filename.endswith('.qsconf') or filename.endswith('.py'): + options.stub_output = filename.rsplit('.', 1)[0] + '.cpp' + else: + options.stub_output = filename + '.cpp' + if options.header_output is None: + options.header_output = re.sub(r'(\.c|\.cpp)?$', '.h', + options.stub_output) + + if options.cachedir != '': + sys.path.append(options.cachedir) + if not os.path.isdir(options.cachedir): + os.makedirs(options.cachedir) + + try: + includePath = options.idlpath.split(':') + conf, interfaces = readConfigFile(filename, + includePath=includePath, + cachedir=options.cachedir) + writeStubFile(options.stub_output, options.header_output, + conf, interfaces) + writeHeaderFile(options.header_output, conf.name) + if options.makedepend_output is not None: + makeutils.writeMakeDependOutput(options.makedepend_output) + except Exception, exc: + if options.verbose_errors: + raise + elif isinstance(exc, (UserError, xpidl.IDLError)): + warn(str(exc)) + elif isinstance(exc, OSError): + warn("%s: %s" % (exc.__class__.__name__, exc)) + else: + raise + sys.exit(1) + +if __name__ == '__main__': + main()