зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1611933 - Support infallible xpcom methods, and use it for nsIURI.schemeIs. r=nika,xpcom-reviewers
I've wanted to use this recently for a couple things. This uses the same scheme and even templates we use for attributes, so it's mostly moving code around... Inverting the code generation so that the implementation is infallible, and we actually generate the NS_IMETHOD goop inline somehow could be potentially desirable, though that causes an extra virtual call for non-C++ callers I guess, so maybe it's not such a great trade-off. Plus it seems more complicated... Explicitly forbid mixing infallible with notxpcom (as it doesn't make sense), and similarly forbid infallible + returning void (as C++ doesn't allow us to overload a function that differs only on its return type). Differential Revision: https://phabricator.services.mozilla.com/D90044
This commit is contained in:
Родитель
a3b66dd82c
Коммит
b1d66c8b2e
|
@ -183,18 +183,7 @@ interface nsIURI : nsISupports
|
|||
* to GetScheme, thereby saving extra allocating and freeing. Returns true if
|
||||
* the schemes match (case ignored).
|
||||
*/
|
||||
boolean schemeIs(in string scheme);
|
||||
|
||||
/*
|
||||
* Infallible version of SchemeIs for C++ callers.
|
||||
*/
|
||||
%{C++
|
||||
bool SchemeIs(const char* aScheme) {
|
||||
bool ret;
|
||||
mozilla::Unused << SchemeIs(aScheme, &ret);
|
||||
return ret;
|
||||
}
|
||||
%}
|
||||
[infallible] boolean schemeIs(in string scheme);
|
||||
|
||||
/**
|
||||
* This method resolves a relative string into an absolute URI string,
|
||||
|
|
|
@ -33,8 +33,8 @@ def attributeParamName(a):
|
|||
return "a" + firstCap(a.name)
|
||||
|
||||
|
||||
def attributeParamNames(a, getter):
|
||||
if getter and a.notxpcom:
|
||||
def attributeParamNames(a, getter, return_param=True):
|
||||
if getter and (a.notxpcom or not return_param):
|
||||
l = []
|
||||
else:
|
||||
l = [attributeParamName(a)]
|
||||
|
@ -48,6 +48,24 @@ def attributeNativeName(a, getter):
|
|||
return "%s%s" % (getter and 'Get' or 'Set', binaryname)
|
||||
|
||||
|
||||
def attributeAttributes(a, getter):
|
||||
ret = ""
|
||||
|
||||
if a.must_use:
|
||||
ret = "[[nodiscard]] " + ret
|
||||
|
||||
# Ideally, we'd set MOZ_CAN_RUN_SCRIPT in the "scriptable and not
|
||||
# builtinclass" case too, so we'd just have memberCanRunScript() check
|
||||
# explicit_setter_can_run_script/explicit_setter_can_run_script and call it
|
||||
# here. But that would likely require a fair amount of Gecko-side
|
||||
# annotation work. See bug 1534292.
|
||||
if ((a.explicit_getter_can_run_script and getter) or
|
||||
(a.explicit_setter_can_run_script and not getter)):
|
||||
ret = "MOZ_CAN_RUN_SCRIPT " + ret
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
def attributeReturnType(a, getter, macro):
|
||||
"""macro should be NS_IMETHOD or NS_IMETHODIMP"""
|
||||
# Pick the type to be returned from the getter/setter.
|
||||
|
@ -67,22 +85,11 @@ def attributeReturnType(a, getter, macro):
|
|||
else:
|
||||
ret = "%s_(%s)" % (macro, ret)
|
||||
|
||||
if a.must_use:
|
||||
ret = "[[nodiscard]] " + ret
|
||||
|
||||
# Ideally, we'd set MOZ_CAN_RUN_SCRIPT in the "scriptable and not
|
||||
# builtinclass" case too, so we'd just have memberCanRunScript() check
|
||||
# explicit_setter_can_run_script/explicit_setter_can_run_script and call it
|
||||
# here. But that would likely require a fair amount of Gecko-side
|
||||
# annotation work. See bug 1534292.
|
||||
if ((a.explicit_getter_can_run_script and getter) or
|
||||
(a.explicit_setter_can_run_script and not getter)):
|
||||
ret = "MOZ_CAN_RUN_SCRIPT " + ret
|
||||
return ret
|
||||
return attributeAttributes(a, getter) + ret
|
||||
|
||||
|
||||
def attributeParamlist(a, getter):
|
||||
if getter and a.notxpcom:
|
||||
def attributeParamlist(a, getter, return_param=True):
|
||||
if getter and (a.notxpcom or not return_param):
|
||||
l = []
|
||||
else:
|
||||
l = ["%s%s" % (a.realtype.nativeType(getter and 'out' or 'in'),
|
||||
|
@ -104,6 +111,22 @@ def methodNativeName(m):
|
|||
return m.binaryname is not None and m.binaryname or firstCap(m.name)
|
||||
|
||||
|
||||
def methodAttributes(m):
|
||||
ret = ""
|
||||
|
||||
if m.must_use:
|
||||
ret = "[[nodiscard]] " + ret
|
||||
|
||||
# Ideally, we'd set MOZ_CAN_RUN_SCRIPT in the "scriptable and not
|
||||
# builtinclass" case too, so we'd just have memberCanRunScript() check
|
||||
# explicit_can_run_script and call it here. But that would likely require
|
||||
# a fair amount of Gecko-side annotation work. See bug 1534292.
|
||||
if m.explicit_can_run_script:
|
||||
ret = "MOZ_CAN_RUN_SCRIPT " + ret
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
def methodReturnType(m, macro):
|
||||
"""macro should be NS_IMETHOD or NS_IMETHODIMP"""
|
||||
if m.notxpcom:
|
||||
|
@ -122,16 +145,7 @@ def methodReturnType(m, macro):
|
|||
else:
|
||||
ret = "%s_(%s)" % (macro, ret)
|
||||
|
||||
if m.must_use:
|
||||
ret = "[[nodiscard]] " + ret
|
||||
|
||||
# Ideally, we'd set MOZ_CAN_RUN_SCRIPT in the "scriptable and not
|
||||
# builtinclass" case too, so we'd just have memberCanRunScript() check
|
||||
# explicit_can_run_script and call it here. But that would likely require
|
||||
# a fair amount of Gecko-side annotation work. See bug 1534292.
|
||||
if m.explicit_can_run_script:
|
||||
ret = "MOZ_CAN_RUN_SCRIPT " + ret
|
||||
return ret
|
||||
return methodAttributes(m) + ret
|
||||
|
||||
|
||||
def methodAsNative(m, declType='NS_IMETHOD'):
|
||||
|
@ -140,7 +154,7 @@ def methodAsNative(m, declType='NS_IMETHOD'):
|
|||
paramlistAsNative(m))
|
||||
|
||||
|
||||
def paramlistAsNative(m, empty='void'):
|
||||
def paramlistAsNative(m, empty='void', return_param=True):
|
||||
l = [paramAsNative(p) for p in m.params]
|
||||
|
||||
if m.implicit_jscontext:
|
||||
|
@ -149,7 +163,7 @@ def paramlistAsNative(m, empty='void'):
|
|||
if m.optional_argc:
|
||||
l.append('uint8_t _argc')
|
||||
|
||||
if not m.notxpcom and m.realtype.name != 'void':
|
||||
if not m.notxpcom and m.realtype.name != 'void' and return_param:
|
||||
l.append(paramAsNative(xpidl.Param(
|
||||
paramtype='out', type=None, name='_retval', attlist=[],
|
||||
location=None, realtype=m.realtype)))
|
||||
|
@ -191,7 +205,7 @@ def paramAsNative(p):
|
|||
default_spec)
|
||||
|
||||
|
||||
def paramlistNames(m):
|
||||
def paramlistNames(m, return_param=True):
|
||||
names = [p.name for p in m.params]
|
||||
|
||||
if m.implicit_jscontext:
|
||||
|
@ -200,7 +214,7 @@ def paramlistNames(m):
|
|||
if m.optional_argc:
|
||||
names.append('_argc')
|
||||
|
||||
if not m.notxpcom and m.realtype.name != 'void':
|
||||
if not m.notxpcom and m.realtype.name != 'void' and return_param:
|
||||
names.append('_retval')
|
||||
|
||||
if len(names) == 0:
|
||||
|
@ -273,20 +287,19 @@ def print_header(idl, fd, filename, relpath):
|
|||
# Include some extra files if any attributes are infallible.
|
||||
interfaces = [p for p in idl.productions if p.kind == 'interface']
|
||||
wroteRunScriptIncludes = False
|
||||
wroteInfallibleIncludes = False
|
||||
for iface in interfaces:
|
||||
attrs = [m for m in iface.members if isinstance(m, xpidl.Attribute)]
|
||||
for attr in attrs:
|
||||
if attr.infallible:
|
||||
for member in iface.members:
|
||||
if not isinstance(member, xpidl.Attribute) and not isinstance(member, xpidl.Method):
|
||||
continue
|
||||
if not wroteInfallibleIncludes and member.infallible:
|
||||
fd.write(infallible_includes)
|
||||
break
|
||||
|
||||
if not wroteRunScriptIncludes:
|
||||
methods = [m for m in iface.members if isinstance(m, xpidl.Method)]
|
||||
for member in itertools.chain(attrs, methods):
|
||||
if memberCanRunScript(member):
|
||||
fd.write(can_run_script_includes)
|
||||
wroteRunScriptIncludes = True
|
||||
break
|
||||
wroteInfallibleIncludes = True
|
||||
if not wroteRunScriptIncludes and memberCanRunScript(member):
|
||||
fd.write(can_run_script_includes)
|
||||
wroteRunScriptIncludes = True
|
||||
if wroteRunScriptIncludes and wroteInfallibleIncludes:
|
||||
break
|
||||
|
||||
fd.write('\n')
|
||||
fd.write(header_end)
|
||||
|
@ -377,8 +390,8 @@ iface_forward_safe = """
|
|||
/* Use this macro to declare functions that forward the behavior of this interface to another object in a safe way. */
|
||||
#define NS_FORWARD_SAFE_%(macroname)s(_to) """ # NOQA: E501
|
||||
|
||||
attr_builtin_infallible_tmpl = """\
|
||||
inline %(realtype)s%(nativename)s(%(args)s)
|
||||
builtin_infallible_tmpl = """\
|
||||
%(attributes)sinline %(realtype)s %(nativename)s(%(args)s)
|
||||
{
|
||||
%(realtype)sresult;
|
||||
mozilla::DebugOnly<nsresult> rv = %(nativename)s(%(argnames)s&result);
|
||||
|
@ -390,8 +403,8 @@ attr_builtin_infallible_tmpl = """\
|
|||
# NOTE: We don't use RefPtr::forget here because we don't want to need the
|
||||
# definition of %(realtype)s in scope, which we would need for the
|
||||
# AddRef/Release calls.
|
||||
attr_refcnt_infallible_tmpl = """\
|
||||
inline already_AddRefed<%(realtype)s>%(nativename)s(%(args)s)
|
||||
refcnt_infallible_tmpl = """\
|
||||
%(attributes)s inline already_AddRefed<%(realtype)s> %(nativename)s(%(args)s)
|
||||
{
|
||||
%(realtype)s* result = nullptr;
|
||||
mozilla::DebugOnly<nsresult> rv = %(nativename)s(%(argnames)s&result);
|
||||
|
@ -401,6 +414,37 @@ attr_refcnt_infallible_tmpl = """\
|
|||
"""
|
||||
|
||||
|
||||
def infallibleDecl(member):
|
||||
isattr = isinstance(member, xpidl.Attribute)
|
||||
ismethod = isinstance(member, xpidl.Method)
|
||||
assert isattr or ismethod
|
||||
|
||||
realtype = member.realtype.nativeType('in')
|
||||
tmpl = builtin_infallible_tmpl
|
||||
|
||||
if member.realtype.kind != 'builtin' and member.realtype.kind != 'cenum':
|
||||
assert realtype.endswith(' *'), "bad infallible type"
|
||||
tmpl = refcnt_infallible_tmpl
|
||||
realtype = realtype[:-2] # strip trailing pointer
|
||||
|
||||
if isattr:
|
||||
nativename = attributeNativeName(member, getter=True)
|
||||
args = attributeParamlist(member, getter=True, return_param=False)
|
||||
argnames = attributeParamNames(member, getter=True, return_param=False)
|
||||
attributes = attributeAttributes(member, getter=True)
|
||||
else:
|
||||
nativename = methodNativeName(member)
|
||||
args = paramlistAsNative(member, return_param=False)
|
||||
argnames = paramlistNames(member, return_param=False)
|
||||
attributes = methodAttributes(member)
|
||||
|
||||
return tmpl % {'attributes': attributes,
|
||||
'realtype': realtype,
|
||||
'nativename': nativename,
|
||||
'args': args,
|
||||
'argnames': argnames + ', ' if argnames else ''}
|
||||
|
||||
|
||||
def write_interface(iface, fd):
|
||||
if iface.namemap is None:
|
||||
raise Exception("Interface was not resolved.")
|
||||
|
@ -448,6 +492,9 @@ def write_interface(iface, fd):
|
|||
fd.write(" %s%s = 0;\n\n" % (runScriptAnnotation(m),
|
||||
methodAsNative(m)))
|
||||
|
||||
if m.infallible:
|
||||
fd.write(infallibleDecl(m))
|
||||
|
||||
def write_attr_decl(a):
|
||||
printComments(fd, a.doccomments, ' ')
|
||||
|
||||
|
@ -456,18 +503,7 @@ def write_interface(iface, fd):
|
|||
fd.write(" %s%s = 0;\n" % (runScriptAnnotation(a),
|
||||
attributeAsNative(a, True)))
|
||||
if a.infallible:
|
||||
realtype = a.realtype.nativeType('in')
|
||||
tmpl = attr_builtin_infallible_tmpl
|
||||
|
||||
if a.realtype.kind != 'builtin' and a.realtype.kind != 'cenum':
|
||||
assert realtype.endswith(' *'), "bad infallible type"
|
||||
tmpl = attr_refcnt_infallible_tmpl
|
||||
realtype = realtype[:-2] # strip trailing pointer
|
||||
|
||||
fd.write(tmpl % {'realtype': realtype,
|
||||
'nativename': attributeNativeName(a, getter=True),
|
||||
'args': '' if not a.implicit_jscontext else 'JSContext* cx',
|
||||
'argnames': '' if not a.implicit_jscontext else 'cx, '})
|
||||
fd.write(infallibleDecl(a))
|
||||
|
||||
if not a.readonly:
|
||||
fd.write(" %s%s = 0;\n" % (runScriptAnnotation(a),
|
||||
|
|
|
@ -965,6 +965,32 @@ class CEnum(object):
|
|||
return "\tcenum %s : %d { %s };\n" % (self.name, self.width, body)
|
||||
|
||||
|
||||
# Infallible doesn't work for all return types.
|
||||
#
|
||||
# It also must be implemented on a builtinclass (otherwise it'd be unsound as
|
||||
# it could be implemented by JS).
|
||||
def ensureInfallibleIsSound(methodOrAttribute):
|
||||
if not methodOrAttribute.infallible:
|
||||
return
|
||||
if methodOrAttribute.realtype.kind not in ['builtin',
|
||||
'interface',
|
||||
'forward',
|
||||
'webidl',
|
||||
'cenum']:
|
||||
raise IDLError('[infallible] only works on interfaces, domobjects, and builtin types '
|
||||
'(numbers, booleans, cenum, and raw char types)',
|
||||
methodOrAttribute.location)
|
||||
if not methodOrAttribute.iface.attributes.builtinclass:
|
||||
raise IDLError('[infallible] attributes and methods are only allowed on '
|
||||
'[builtinclass] interfaces',
|
||||
methodOrAttribute.location)
|
||||
|
||||
if methodOrAttribute.notxpcom:
|
||||
raise IDLError('[infallible] does not make sense for a [notxpcom] '
|
||||
'method or attribute',
|
||||
methodOrAttribute.location)
|
||||
|
||||
|
||||
# An interface cannot be implemented by JS if it has a notxpcom
|
||||
# method or attribute, so it must be marked as builtinclass.
|
||||
#
|
||||
|
@ -1060,19 +1086,8 @@ class Attribute(object):
|
|||
def resolve(self, iface):
|
||||
self.iface = iface
|
||||
self.realtype = iface.idl.getName(self.type, self.location)
|
||||
if self.infallible and self.realtype.kind not in ['builtin',
|
||||
'interface',
|
||||
'forward',
|
||||
'webidl',
|
||||
'cenum']:
|
||||
raise IDLError('[infallible] only works on interfaces, domobjects, and builtin types '
|
||||
'(numbers, booleans, cenum, and raw char types)',
|
||||
self.location)
|
||||
if self.infallible and not iface.attributes.builtinclass:
|
||||
raise IDLError('[infallible] attributes are only allowed on '
|
||||
'[builtinclass] interfaces',
|
||||
self.location)
|
||||
|
||||
ensureInfallibleIsSound(self)
|
||||
ensureBuiltinClassIfNeeded(self)
|
||||
|
||||
def toIDL(self):
|
||||
|
@ -1106,6 +1121,7 @@ class Method(object):
|
|||
# explicit_can_run_script is true if the method is explicitly annotated
|
||||
# as being able to cause script to run.
|
||||
explicit_can_run_script = False
|
||||
infallible = False
|
||||
|
||||
def __init__(self, type, name, attlist, paramlist, location, doccomments, raises):
|
||||
self.type = type
|
||||
|
@ -1144,6 +1160,8 @@ class Method(object):
|
|||
self.must_use = True
|
||||
elif name == 'can_run_script':
|
||||
self.explicit_can_run_script = True
|
||||
elif name == 'infallible':
|
||||
self.infallible = True
|
||||
else:
|
||||
raise IDLError("Unexpected attribute '%s'" % name, aloc)
|
||||
|
||||
|
@ -1155,6 +1173,7 @@ class Method(object):
|
|||
self.iface = iface
|
||||
self.realtype = self.iface.idl.getName(self.type, self.location)
|
||||
|
||||
ensureInfallibleIsSound(self)
|
||||
ensureBuiltinClassIfNeeded(self)
|
||||
|
||||
for p in self.params:
|
||||
|
|
Загрузка…
Ссылка в новой задаче