#!/usr/bin/env python
# header.py - Generate C++ header files from IDL.
#
# 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/.
"""Print a C++ header file for the IDL files specified on the command line"""
import itertools
import os.path
import re
from xpidl
import xpidl
printdoccomments =
False
if printdoccomments:
def printComments(fd, clist, indent):
for c
in clist:
fd.write(f
"{indent}{c}\n")
else:
def printComments(fd, clist, indent):
pass
def firstCap(str):
return str[
0].upper() + str[
1:]
def attributeParamName(a):
return "a" + firstCap(a.name)
def attributeParamNames(a, getter, return_param=
True):
if getter
and (a.notxpcom
or not return_param):
l = []
else:
l = [attributeParamName(a)]
if a.implicit_jscontext:
l.insert(
0,
"cx")
return ", ".join(l)
def attributeNativeName(a, getter):
binaryname = a.binaryname
is not None and a.binaryname
or firstCap(a.name)
prefix = getter
and "Get" or "Set"
return f
"{prefix}{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.
if a.notxpcom:
ret = a.realtype.nativeType(
"in").strip()
if getter
else "void"
else:
ret =
"nsresult"
# Set calling convention and virtual-ness
if a.nostdcall:
if macro ==
"NS_IMETHOD":
# This is the declaration.
ret = f
"virtual {ret}"
elif ret ==
"nsresult":
ret = macro
else:
ret = f
"{macro}_({ret})"
return attributeAttributes(a, getter) + ret
def attributeParamlist(a, getter, return_param=
True):
if getter
and (a.notxpcom
or not return_param):
l = []
else:
prefix = a.realtype.nativeType(getter
and "out" or "in")
l = [f
"{prefix}{attributeParamName(a)}"]
if a.implicit_jscontext:
l.insert(
0,
"JSContext* cx")
return ", ".join(l)
def attributeAsNative(a, getter, declType=
"NS_IMETHOD"):
returntype = attributeReturnType(a, getter, declType)
binaryname = attributeNativeName(a, getter)
paramlist = attributeParamlist(a, getter)
return f
"{returntype} {binaryname}({paramlist})"
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:
ret = m.realtype.nativeType(
"in").strip()
else:
ret =
"nsresult"
# Set calling convention and virtual-ness
if m.nostdcall:
if macro ==
"NS_IMETHOD":
# This is the declaration
ret = f
"virtual {ret}"
elif ret ==
"nsresult":
ret = macro
else:
ret = f
"{macro}_({ret})"
return methodAttributes(m) + ret
def methodAsNative(m, declType=
"NS_IMETHOD"):
returntype = methodReturnType(m, declType)
binaryname = methodNativeName(m)
paramlist = paramlistAsNative(m)
return f
"{returntype} {binaryname}({paramlist})"
def paramlistAsNative(m, empty=
"void", return_param=
True):
l = [paramAsNative(p)
for p
in m.params]
if m.implicit_jscontext:
l.append(
"JSContext* cx")
if m.optional_argc:
l.append(
"uint8_t _argc")
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,
)
)
)
# Set any optional out params to default to nullptr. Skip if we just added
# extra non-optional args to l.
if len(l) == len(m.params):
paramIter = len(m.params) -
1
while (
paramIter >=
0
and m.params[paramIter].optional
and "out" in m.params[paramIter].paramtype
):
t = m.params[paramIter].type
# Strings can't be optional, so this shouldn't happen, but let's make sure:
if t
in {
"AString",
"ACString",
"AUTF8String"}:
break
l[paramIter] +=
" = nullptr"
paramIter -=
1
if len(l) ==
0:
return empty
return ", ".join(l)
def memberCanRunScript(member):
# This can only happen if the member is scriptable and its interface is not builtinclass.
return member.isScriptable()
and not member.iface.attributes.builtinclass
def runScriptAnnotation(member):
return "JS_HAZ_CAN_RUN_SCRIPT " if memberCanRunScript(member)
else ""
def paramAsNative(p):
default_spec =
""
if p.default_value:
default_spec =
" = " + p.default_value
return f
"{p.nativeType()}{p.name}{default_spec}"
def paramlistNames(m, return_param=
True):
names = [p.name
for p
in m.params]
if m.implicit_jscontext:
names.append(
"cx")
if m.optional_argc:
names.append(
"_argc")
if not m.notxpcom
and m.realtype.name !=
"void" and return_param:
names.append(
"_retval")
if len(names) ==
0:
return ""
return ", ".join(names)
header =
"""/*
* DO
NOT EDIT. THIS FILE
IS GENERATED
FROM $SRCDIR/{relpath}
*/
#ifndef __gen_{basename}_h__
#define __gen_{basename}_h__
"""
include =
"""
#include "{basename}.h"
"""
jsvalue_include =
"""
#include "js/Value.h"
"""
infallible_includes =
"""
#include "mozilla/AlreadyAddRefed.h"
#include "mozilla/Assertions.h"
#include "mozilla/DebugOnly.h"
"""
can_run_script_includes =
"""
#include "js/GCAnnotations.h"
"""
header_end =
"""/* For IDL files that don't want to include root IDL files. */
#ifndef NS_NO_VTABLE
#define NS_NO_VTABLE
#endif
"""
footer =
"""
#endif /* __gen_{basename}_h__ */
"""
forward_decl =
"""class {name}; /* forward declaration */
"""
def idl_basename(f):
"""returns the base name of a file with the last extension stripped"""
return os.path.basename(f).rpartition(
".")[
0]
def print_header(idl, fd, filename, relpath):
fd.write(header.format(relpath=relpath, basename=idl_basename(filename)))
foundinc =
False
for inc
in idl.includes():
if not foundinc:
foundinc =
True
fd.write(
"\n")
fd.write(include.format(basename=idl_basename(inc.filename)))
if idl.needsJSTypes():
fd.write(jsvalue_include)
# 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:
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)
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)
for p
in idl.productions:
if p.kind ==
"include":
continue
if p.kind ==
"cdata":
fd.write(p.data_with_comment())
continue
if p.kind ==
"webidl":
write_webidl(p, fd)
continue
if p.kind ==
"forward":
fd.write(forward_decl.format(name=p.name))
continue
if p.kind ==
"interface":
write_interface(p, fd)
continue
if p.kind ==
"typedef":
printComments(fd, p.doccomments,
"")
nativetype = p.realtype.nativeType(
"in")
fd.write(f
"typedef {nativetype} {p.name};\n\n")
fd.write(footer.format(basename=idl_basename(filename)))
def write_webidl(p, fd):
path = p.native.split(
"::")
for seg
in path[:-
1]:
fd.write(f
"namespace {seg} {{\n")
fd.write(f
"class {path[-1]}; /* webidl {p.name} */\n")
for seg
in reversed(path[:-
1]):
fd.write(f
"}} // namespace {seg}\n")
fd.write(
"\n")
iface_header = r
"""
/* starting interface: {name} */
#define {defname}_IID_STR "{iid}"
#define {defname}_IID \
{{
0x{m0},
0x{m1},
0x{m2}, \
{{ {m3joined} }}}}
"""
uuid_decoder = re.compile(
r
"""(?P<m0>[a-f0-9]{8})-
(?P<m1>[a-f0-
9]{
4})-
(?P<m2>[a-f0-
9]{
4})-
(?P<m3>[a-f0-
9]{
4})-
(?P<m4>[a-f0-
9]{
12})$
""",
re.X,
)
iface_prolog =
""" {{
public:
NS_INLINE_DECL_STATIC_IID({defname}_IID)
"""
iface_scriptable =
"""\
/* Used by ToJSValue to check which scriptable interface
is implemented. */
using ScriptableInterfaceType = {name};
"""
iface_epilog =
"""}};
"""
iface_decl =
"""
/* Use this macro when declaring classes that implement this interface. */
#define NS_DECL_{macroname} """
iface_nonvirtual =
"""
/* Use this macro when declaring the members of this interface when the
class doesn
't implement the interface. This is useful for forwarding. */
#define NS_DECL_NON_VIRTUAL_{macroname} """
iface_forward =
"""
/* Use this macro to declare functions that forward the behavior of this interface to another ob
ject. */
#define NS_FORWARD_{macroname}(_to) """ # NOQA: E501
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}(_to) """ # NOQA: E501
builtin_infallible_tmpl = """\
{attributes}inline {realtype} {nativename}({args})
{{
{realtype}result;
mozilla::DebugOnly<nsresult> rv = {nativename}({argnames}&result);
MOZ_ASSERT(NS_SUCCEEDED(rv));
return result;
}}
"""
# NOTE: We don't use RefPtr::forget here because we don't want to need the
# definition of {realtype} in scope, which we would need for the
# AddRef/Release calls.
refcnt_infallible_tmpl = """\
{attributes} inline already_AddRefed<{realtype}> {nativename}({args})
{{
{realtype}* result = nullptr;
mozilla::DebugOnly<nsresult> rv = {nativename}({argnames}&result);
MOZ_ASSERT(NS_SUCCEEDED(rv));
return already_AddRefed<{realtype}>(result);
}}
"""
iface_threadsafe_tmpl = """\
namespace mozilla::detail {{
template <>
class InterfaceNeedsThreadSafeRefCnt<{name}> : public std::true_type {{}};
}}
"""
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 not in {"builtin", "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.format(
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.")
# Confirm that no names of methods will overload in this interface
names = set()
def record_name(name):
if name in names:
raise Exception(
f"Unexpected overloaded virtual method {name} in interface {iface.name}"
)
names.add(name)
for m in iface.members:
if type(m) is xpidl.Attribute:
record_name(attributeNativeName(m, getter=True))
if not m.readonly:
record_name(attributeNativeName(m, getter=False))
elif type(m) is xpidl.Method:
record_name(methodNativeName(m))
def write_const_decls(g):
fd.write(" enum {\n")
enums = []
for c in g:
printComments(fd, c.doccomments, " ")
basetype = c.basetype
value = c.getValue()
signed = (not basetype.signed) and "U" or ""
enums.append(f" {c.name} = {value}{signed}")
fd.write(",\n".join(enums))
fd.write("\n };\n\n")
def write_cenum_decl(b):
fd.write(f" enum {b.basename} : uint{b.width}_t {{\n")
for var in b.variants:
fd.write(f" {var.name} = {var.value},\n")
fd.write(" };\n\n")
def write_method_decl(m):
printComments(fd, m.doccomments, " ")
fd.write(f" /* {m.toIDL()} */\n")
fd.write(f" {runScriptAnnotation(m)}{methodAsNative(m)} = 0;\n\n")
if m.infallible:
fd.write(infallibleDecl(m))
def write_attr_decl(a):
printComments(fd, a.doccomments, " ")
fd.write(f" /* {a.toIDL()} */\n")
fd.write(f" {runScriptAnnotation(a)}{attributeAsNative(a, True)} = 0;\n")
if a.infallible:
fd.write(infallibleDecl(a))
if not a.readonly:
fd.write(f" {runScriptAnnotation(a)}{attributeAsNative(a, False)} = 0;\n")
fd.write("\n")
defname = iface.name.upper()
if iface.name[0:2] == "ns":
defname = "NS_" + defname[2:]
names = uuid_decoder.match(iface.attributes.uuid).groupdict()
m3str = names["m3"] + names["m4"]
names["m3joined"] = ", ".join([f"0x{m3str[i : i + 2]}" for i in range(0, 16, 2)])
if iface.name[2] == "I":
implclass = iface.name[:2] + iface.name[3:]
else:
implclass = "_MYCLASS_"
names.update({
"defname": defname,
"macroname": iface.name.upper(),
"name": iface.name,
"iid": iface.attributes.uuid,
"implclass": implclass,
})
fd.write(iface_header.format(**names))
printComments(fd, iface.doccomments, "")
fd.write("class ")
foundcdata = False
for m in iface.members:
if isinstance(m, xpidl.CDATA):
foundcdata = True
if not foundcdata:
fd.write("NS_NO_VTABLE ")
fd.write(iface.name)
if iface.base:
fd.write(f" : public {iface.base}")
fd.write(iface_prolog.format(**names))
if iface.attributes.scriptable:
fd.write(iface_scriptable.format(**names))
for key, group in itertools.groupby(iface.members, key=type):
if key == xpidl.ConstMember:
write_const_decls(group) # iterator of all the consts
else:
for member in group:
if key == xpidl.Attribute:
write_attr_decl(member)
elif key == xpidl.Method:
write_method_decl(member)
elif key == xpidl.CDATA:
fd.write(member.data_with_comment())
elif key == xpidl.CEnum:
write_cenum_decl(member)
else:
raise Exception(f"Unexpected interface member: {member}")
fd.write(iface_epilog.format(**names))
if iface.attributes.rust_sync:
fd.write(iface_threadsafe_tmpl.format(**names))
fd.write(iface_decl.format(**names))
def writeDeclaration(fd, iface, virtual):
declType = "NS_IMETHOD" if virtual else "nsresult"
suffix = " override" if virtual else ""
for member in iface.members:
if isinstance(member, xpidl.Attribute):
if member.infallible:
fd.write(
f"\\\n using {iface.name}::{attributeNativeName(member, True)}; "
)
fd.write(f"\\\n {attributeAsNative(member, True, declType)}{suffix}; ")
if not member.readonly:
fd.write(
f"\\\n {attributeAsNative(member, False, declType)}{suffix}; "
)
elif isinstance(member, xpidl.Method):
fd.write(f"\\\n {methodAsNative(member, declType)}{suffix}; ")
if len(iface.members) == 0:
fd.write("\\\n /* no methods! */")
elif member.kind not in ("attribute", "method"):
fd.write("\\")
writeDeclaration(fd, iface, True)
fd.write(iface_nonvirtual.format(**names))
writeDeclaration(fd, iface, False)
fd.write(iface_forward.format(**names))
def emitTemplate(forward_infallible, tmpl, tmpl_notxpcom=None):
if tmpl_notxpcom is None:
tmpl_notxpcom = tmpl
for member in iface.members:
if isinstance(member, xpidl.Attribute):
if forward_infallible and member.infallible:
fd.write(
f"\\\n using {iface.name}::{attributeNativeName(member, True)}; "
)
attr_tmpl = tmpl_notxpcom if member.notxpcom else tmpl
fd.write(
attr_tmpl.format(
asNative=attributeAsNative(member, True),
nativeName=attributeNativeName(member, True),
paramList=attributeParamNames(member, True),
)
)
if not member.readonly:
fd.write(
attr_tmpl.format(
asNative=attributeAsNative(member, False),
nativeName=attributeNativeName(member, False),
paramList=attributeParamNames(member, False),
)
)
elif isinstance(member, xpidl.Method):
if member.notxpcom:
fd.write(
tmpl_notxpcom.format(
asNative=methodAsNative(member),
nativeName=methodNativeName(member),
paramList=paramlistNames(member),
)
)
else:
fd.write(
tmpl.format(
asNative=methodAsNative(member),
nativeName=methodNativeName(member),
paramList=paramlistNames(member),
)
)
if len(iface.members) == 0:
fd.write("\\\n /* no methods! */")
elif member.kind not in ("attribute", "method"):
fd.write("\\")
emitTemplate(
True,
"\\\n {asNative} override {{ return _to {nativeName}({paramList}); }} ",
)
fd.write(iface_forward_safe.format(**names))
# Don't try to safely forward notxpcom functions, because we have no
# sensible default error return. Instead, the caller will have to
# implement them.
emitTemplate(
False,
"\\\n {asNative} override {{ return !_to ? NS_ERROR_NULL_POINTER : _to->{nativeName}({paramList}); }} ", # NOQA: E501
"\\\n {asNative} override; ",
)
fd.write("\n\n")
def main(outputfile):
xpidl.IDLParser()
if __name__ == "__main__":
main(None)