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:
Emilio Cobos Álvarez 2020-09-18 00:24:12 +00:00
Родитель a3b66dd82c
Коммит b1d66c8b2e
3 изменённых файлов: 126 добавлений и 82 удалений

Просмотреть файл

@ -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: