diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
commit | 26a029d407be480d791972afb5975cf62c9360a6 (patch) | |
tree | f435a8308119effd964b339f76abb83a57c29483 /xpcom/components/gen_static_components.py | |
parent | Initial commit. (diff) | |
download | firefox-upstream/124.0.1.tar.xz firefox-upstream/124.0.1.zip |
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'xpcom/components/gen_static_components.py')
-rw-r--r-- | xpcom/components/gen_static_components.py | 1216 |
1 files changed, 1216 insertions, 0 deletions
diff --git a/xpcom/components/gen_static_components.py b/xpcom/components/gen_static_components.py new file mode 100644 index 0000000000..f759dc3132 --- /dev/null +++ b/xpcom/components/gen_static_components.py @@ -0,0 +1,1216 @@ +import json +import os +import re +import struct +from collections import defaultdict +from uuid import UUID + +import buildconfig +from mozbuild.util import FileAvoidWrite +from perfecthash import PerfectHash + +NO_CONTRACT_ID = 0xFFFFFFFF + +PHF_SIZE = 512 + +TINY_PHF_SIZE = 16 + +# In tests, we might not have a (complete) buildconfig. +ENDIAN = ( + "<" if buildconfig.substs.get("TARGET_ENDIANNESS", "little") == "little" else ">" +) + + +# Represents a UUID in the format used internally by Gecko, and supports +# serializing it in that format to both C++ source and raw byte arrays. +class UUIDRepr(object): + def __init__(self, uuid): + self.uuid = uuid + + fields = uuid.fields + + self.a = fields[0] + self.b = fields[1] + self.c = fields[2] + + d = list(fields[3:5]) + for i in range(0, 6): + d.append(fields[5] >> (8 * (5 - i)) & 0xFF) + + self.d = tuple(d) + + def __str__(self): + return str(self.uuid) + + @property + def bytes(self): + return struct.pack(ENDIAN + "IHHBBBBBBBB", self.a, self.b, self.c, *self.d) + + def to_cxx(self): + rest = ", ".join("0x%02x" % b for b in self.d) + + return "{ 0x%x, 0x%x, 0x%x, { %s } }" % (self.a, self.b, self.c, rest) + + +# Corresponds to the Module::ProcessSelector enum in Module.h. The actual +# values don't matter, since the code generator emits symbolic constants for +# these values, but we use the same values as the enum constants for clarity. +class ProcessSelector: + ANY_PROCESS = 0 + MAIN_PROCESS_ONLY = 1 << 0 + CONTENT_PROCESS_ONLY = 1 << 1 + ALLOW_IN_GPU_PROCESS = 1 << 2 + ALLOW_IN_VR_PROCESS = 1 << 3 + ALLOW_IN_SOCKET_PROCESS = 1 << 4 + ALLOW_IN_RDD_PROCESS = 1 << 5 + ALLOW_IN_UTILITY_PROCESS = 1 << 6 + ALLOW_IN_GMPLUGIN_PROCESS = 1 << 7 + ALLOW_IN_GPU_AND_MAIN_PROCESS = ALLOW_IN_GPU_PROCESS | MAIN_PROCESS_ONLY + ALLOW_IN_GPU_AND_SOCKET_PROCESS = ALLOW_IN_GPU_PROCESS | ALLOW_IN_SOCKET_PROCESS + ALLOW_IN_GPU_AND_VR_PROCESS = ALLOW_IN_GPU_PROCESS | ALLOW_IN_VR_PROCESS + ALLOW_IN_GPU_VR_AND_SOCKET_PROCESS = ( + ALLOW_IN_GPU_PROCESS | ALLOW_IN_VR_PROCESS | ALLOW_IN_SOCKET_PROCESS + ) + ALLOW_IN_RDD_AND_SOCKET_PROCESS = ALLOW_IN_RDD_PROCESS | ALLOW_IN_SOCKET_PROCESS + ALLOW_IN_GPU_RDD_AND_SOCKET_PROCESS = ( + ALLOW_IN_GPU_PROCESS | ALLOW_IN_RDD_PROCESS | ALLOW_IN_SOCKET_PROCESS + ) + ALLOW_IN_GPU_RDD_SOCKET_AND_UTILITY_PROCESS = ( + ALLOW_IN_GPU_PROCESS + | ALLOW_IN_RDD_PROCESS + | ALLOW_IN_SOCKET_PROCESS + | ALLOW_IN_UTILITY_PROCESS + ) + ALLOW_IN_GPU_RDD_VR_AND_SOCKET_PROCESS = ( + ALLOW_IN_GPU_PROCESS + | ALLOW_IN_RDD_PROCESS + | ALLOW_IN_VR_PROCESS + | ALLOW_IN_SOCKET_PROCESS + ) + ALLOW_IN_GPU_RDD_VR_SOCKET_AND_UTILITY_PROCESS = ( + ALLOW_IN_GPU_PROCESS + | ALLOW_IN_RDD_PROCESS + | ALLOW_IN_VR_PROCESS + | ALLOW_IN_SOCKET_PROCESS + | ALLOW_IN_UTILITY_PROCESS + ) + ALLOW_IN_GPU_RDD_VR_SOCKET_UTILITY_AND_GMPLUGIN_PROCESS = ( + ALLOW_IN_GPU_PROCESS + | ALLOW_IN_RDD_PROCESS + | ALLOW_IN_VR_PROCESS + | ALLOW_IN_SOCKET_PROCESS + | ALLOW_IN_UTILITY_PROCESS + | ALLOW_IN_GMPLUGIN_PROCESS + ) + + +# Maps ProcessSelector constants to the name of the corresponding +# Module::ProcessSelector enum value. +PROCESSES = { + ProcessSelector.ANY_PROCESS: "ANY_PROCESS", + ProcessSelector.MAIN_PROCESS_ONLY: "MAIN_PROCESS_ONLY", + ProcessSelector.CONTENT_PROCESS_ONLY: "CONTENT_PROCESS_ONLY", + ProcessSelector.ALLOW_IN_GPU_PROCESS: "ALLOW_IN_GPU_PROCESS", + ProcessSelector.ALLOW_IN_VR_PROCESS: "ALLOW_IN_VR_PROCESS", + ProcessSelector.ALLOW_IN_SOCKET_PROCESS: "ALLOW_IN_SOCKET_PROCESS", + ProcessSelector.ALLOW_IN_RDD_PROCESS: "ALLOW_IN_RDD_PROCESS", + ProcessSelector.ALLOW_IN_GPU_AND_MAIN_PROCESS: "ALLOW_IN_GPU_AND_MAIN_PROCESS", + ProcessSelector.ALLOW_IN_GPU_AND_SOCKET_PROCESS: "ALLOW_IN_GPU_AND_SOCKET_PROCESS", + ProcessSelector.ALLOW_IN_GPU_AND_VR_PROCESS: "ALLOW_IN_GPU_AND_VR_PROCESS", + ProcessSelector.ALLOW_IN_GPU_VR_AND_SOCKET_PROCESS: "ALLOW_IN_GPU_VR_AND_SOCKET_PROCESS", + ProcessSelector.ALLOW_IN_RDD_AND_SOCKET_PROCESS: "ALLOW_IN_RDD_AND_SOCKET_PROCESS", + ProcessSelector.ALLOW_IN_GPU_RDD_AND_SOCKET_PROCESS: "ALLOW_IN_GPU_RDD_AND_SOCKET_PROCESS", + ProcessSelector.ALLOW_IN_GPU_RDD_SOCKET_AND_UTILITY_PROCESS: "ALLOW_IN_GPU_RDD_SOCKET_AND_UTILITY_PROCESS", # NOQA: E501 + ProcessSelector.ALLOW_IN_GPU_RDD_VR_AND_SOCKET_PROCESS: "ALLOW_IN_GPU_RDD_VR_AND_SOCKET_PROCESS", # NOQA: E501 + ProcessSelector.ALLOW_IN_GPU_RDD_VR_SOCKET_AND_UTILITY_PROCESS: "ALLOW_IN_GPU_RDD_VR_SOCKET_AND_UTILITY_PROCESS", # NOQA: E501 + ProcessSelector.ALLOW_IN_GPU_RDD_VR_SOCKET_UTILITY_AND_GMPLUGIN_PROCESS: "ALLOW_IN_GPU_RDD_VR_SOCKET_UTILITY_AND_GMPLUGIN_PROCESS", # NOQA: E501 +} + + +# Emits the C++ symbolic constant corresponding to a ProcessSelector constant. +def lower_processes(processes): + return "Module::ProcessSelector::%s" % PROCESSES[processes] + + +# Emits the C++ symbolic constant for a ModuleEntry's ModuleID enum entry. +def lower_module_id(module): + return "ModuleID::%s" % module.name + + +# Corresponds to the Module::BackgroundTasksSelector enum in Module.h. The +# actual values don't matter, since the code generator emits symbolic constants +# for these values, but we use the same values as the enum constants for +# clarity. +class BackgroundTasksSelector: + NO_TASKS = 0x0 + ALL_TASKS = 0xFFFF + + +# Maps BackgroundTasksSelector constants to the name of the corresponding +# Module::BackgroundTasksSelector enum value. +BACKGROUNDTASKS = { + BackgroundTasksSelector.ALL_TASKS: "ALL_TASKS", + BackgroundTasksSelector.NO_TASKS: "NO_TASKS", +} + + +# Emits the C++ symbolic constant corresponding to a BackgroundTasks constant. +def lower_backgroundtasks(backgroundtasks): + return "Module::BackgroundTasksSelector::%s" % BACKGROUNDTASKS[backgroundtasks] + + +# Represents a static string table, indexed by offset. This allows us to +# reference strings from static data structures without requiring runtime +# relocations. +class StringTable(object): + def __init__(self): + self.entries = {} + self.entry_list = [] + self.size = 0 + + self._serialized = False + + # Returns the index of the given string in the `entry_list` array. If + # no entry for the string exists, it first creates one. + def get_idx(self, string): + idx = self.entries.get(string, None) + if idx is not None: + return idx + + assert not self._serialized + + assert len(string) == len(string.encode("utf-8")) + + idx = self.size + self.size += len(string) + 1 + + self.entries[string] = idx + self.entry_list.append(string) + return idx + + # Returns the C++ code representing string data of this string table, as a + # single string literal. This must only be called after the last call to + # `get_idx()` or `entry_to_cxx()` for this instance. + def to_cxx(self): + self._serialized = True + + lines = [] + + idx = 0 + for entry in self.entry_list: + str_ = entry.replace("\\", "\\\\").replace('"', r"\"").replace("\n", r"\n") + + lines.append(' /* 0x%x */ "%s\\0"\n' % (idx, str_)) + + idx += len(entry) + 1 + + return "".join(lines) + + # Returns a `StringEntry` struct initializer for the string table entry + # corresponding to the given string. If no matching entry exists, it is + # first created. + def entry_to_cxx(self, string): + idx = self.get_idx(string) + return "{ 0x%x } /* %s */" % (idx, pretty_string(string)) + + +strings = StringTable() + +interfaces = [] + + +# Represents a C++ namespace, containing a set of classes and potentially +# sub-namespaces. This is used to generate pre-declarations for incomplete +# types referenced in XPCOM manifests. +class Namespace(object): + def __init__(self, name=None): + self.name = name + self.classes = set() + self.namespaces = {} + + # Returns a Namespace object for the sub-namespace with the given name. + def sub(self, name): + assert name not in self.classes + + if name not in self.namespaces: + self.namespaces[name] = Namespace(name) + return self.namespaces[name] + + # Generates C++ code to pre-declare all classes in this namespace and all + # of its sub-namespaces. + def to_cxx(self): + res = "" + if self.name: + res += "namespace %s {\n" % self.name + + for clas in sorted(self.classes): + res += "class %s;\n" % clas + + for ns in sorted(self.namespaces.keys()): + res += self.namespaces[ns].to_cxx() + + if self.name: + res += "} // namespace %s\n" % self.name + + return res + + +# Represents a component defined in an XPCOM manifest's `Classes` array. +class ModuleEntry(object): + next_anon_id = 0 + + def __init__(self, data, init_idx): + self.cid = UUIDRepr(UUID(data["cid"])) + self.contract_ids = data.get("contract_ids", []) + self.type = data.get("type", "nsISupports") + self.categories = data.get("categories", {}) + self.processes = data.get("processes", 0) + self.headers = data.get("headers", []) + + self.js_name = data.get("js_name", None) + self.interfaces = data.get("interfaces", []) + + if len(self.interfaces) > 255: + raise Exception( + "JS service %s may not have more than 255 " "interfaces" % self.js_name + ) + + self.interfaces_offset = len(interfaces) + for iface in self.interfaces: + interfaces.append(iface) + + # If the manifest declares Init or Unload functions, this contains its + # index, as understood by the `CallInitFunc()` function. + # + # If it contains any value other than `None`, a corresponding + # `CallInitFunc(init_idx)` call will be genrated before calling this + # module's constructor. + self.init_idx = init_idx + + self.constructor = data.get("constructor", None) + self.legacy_constructor = data.get("legacy_constructor", None) + self.init_method = data.get("init_method", []) + + self.jsm = data.get("jsm", None) + self.esModule = data.get("esModule", None) + + self.external = data.get( + "external", not (self.headers or self.legacy_constructor) + ) + self.singleton = data.get("singleton", False) + self.overridable = data.get("overridable", False) + + self.protocol_config = data.get("protocol_config", None) + + if "name" in data: + self.anonymous = False + self.name = data["name"] + else: + self.anonymous = True + self.name = "Anonymous%03d" % ModuleEntry.next_anon_id + ModuleEntry.next_anon_id += 1 + + def error(str_): + raise Exception( + "Error defining component %s (%s): %s" + % (str(self.cid), ", ".join(map(repr, self.contract_ids)), str_) + ) + + if self.jsm: + if not self.constructor: + error("JavaScript components must specify a constructor") + + for prop in ("init_method", "legacy_constructor", "headers"): + if getattr(self, prop): + error( + "JavaScript components may not specify a '%s' " + "property" % prop + ) + elif self.esModule: + if not self.constructor: + error("JavaScript components must specify a constructor") + + for prop in ("init_method", "legacy_constructor", "headers"): + if getattr(self, prop): + error( + "JavaScript components may not specify a '%s' " + "property" % prop + ) + elif self.external: + if self.constructor or self.legacy_constructor: + error( + "Externally-constructed components may not specify " + "'constructor' or 'legacy_constructor' properties" + ) + if self.init_method: + error( + "Externally-constructed components may not specify " + "'init_method' properties" + ) + if self.type == "nsISupports": + error( + "Externally-constructed components must specify a type " + "other than nsISupports" + ) + + if self.constructor and self.legacy_constructor: + error( + "The 'constructor' and 'legacy_constructor' properties " + "are mutually exclusive" + ) + + if self.overridable and not self.contract_ids: + error("Overridable components must specify at least one contract " "ID") + + @property + def contract_id(self): + return self.contract_ids[0] + + # Generates the C++ code for a StaticModule struct initializer + # representing this component. + def to_cxx(self): + contract_id = ( + strings.entry_to_cxx(self.contract_id) + if self.overridable + else "{ 0x%x }" % NO_CONTRACT_ID + ) + + return """ + /* {name} */ {{ + /* {{{cid_string}}} */ + {cid}, + {contract_id}, + {processes}, + }}""".format( + name=self.name, + cid=self.cid.to_cxx(), + cid_string=str(self.cid), + contract_id=contract_id, + processes=lower_processes(self.processes), + ) + + # Generates the C++ code for a JSServiceEntry representing this module. + def lower_js_service(self): + return """ + {{ + {js_name}, + ModuleID::{name}, + {{ {iface_offset} }}, + {iface_count} + }}""".format( + js_name=strings.entry_to_cxx(self.js_name), + name=self.name, + iface_offset=self.interfaces_offset, + iface_count=len(self.interfaces), + ) + + # Generates the C++ code necessary to construct an instance of this + # component. + # + # This code lives in a function with the following arguments: + # + # - aIID: The `const nsIID&` interface ID that the resulting instance + # will be queried to. + # + # - aResult: The `void**` pointer in which to store the result. + # + # And which returns an `nsresult` indicating success or failure. + def lower_constructor(self): + res = "" + + if self.init_idx is not None: + res += " MOZ_TRY(CallInitFunc(%d));\n" % self.init_idx + + if self.legacy_constructor: + res += ( + " return /* legacy */ %s(aIID, aResult);\n" + % self.legacy_constructor + ) + return res + + if self.jsm: + res += ( + " nsCOMPtr<nsISupports> inst;\n" + " MOZ_TRY(ConstructJSMComponent(nsLiteralCString(%s),\n" + " %s,\n" + " getter_AddRefs(inst)));" + "\n" % (json.dumps(self.jsm), json.dumps(self.constructor)) + ) + elif self.esModule: + res += ( + " nsCOMPtr<nsISupports> inst;\n" + " MOZ_TRY(ConstructESModuleComponent(nsLiteralCString(%s),\n" + " %s,\n" + " getter_AddRefs(inst)));" + "\n" % (json.dumps(self.esModule), json.dumps(self.constructor)) + ) + elif self.external: + res += ( + " nsCOMPtr<nsISupports> inst = " + "mozCreateComponent<%s>();\n" % self.type + ) + # The custom constructor may return null, so check before calling + # any methods. + res += " NS_ENSURE_TRUE(inst, NS_ERROR_FAILURE);\n" + else: + res += " RefPtr<%s> inst = " % self.type + + if not self.constructor: + res += "new %s();\n" % self.type + else: + res += "%s();\n" % self.constructor + # The `new` operator is infallible, so we don't need to worry + # about it returning null, but custom constructors may, so + # check before calling any methods. + res += " NS_ENSURE_TRUE(inst, NS_ERROR_OUT_OF_MEMORY);\n" + + # Check that the constructor function returns an appropriate + # `already_AddRefed` value for our declared type. + res += """ + using T = + RemoveAlreadyAddRefed<decltype(%(constructor)s())>::Type; + static_assert( + std::is_same_v<already_AddRefed<T>, decltype(%(constructor)s())>, + "Singleton constructor must return already_AddRefed"); + static_assert( + std::is_base_of<%(type)s, T>::value, + "Singleton constructor must return correct already_AddRefed"); + +""" % { + "type": self.type, + "constructor": self.constructor, + } + + if self.init_method: + res += " MOZ_TRY(inst->%s());\n" % self.init_method + + res += " return inst->QueryInterface(aIID, aResult);\n" + + return res + + # Generates the C++ code for the `mozilla::components::<name>` entry + # corresponding to this component. This may not be called for modules + # without an explicit `name` (in which cases, `self.anonymous` will be + # true). + def lower_getters(self): + assert not self.anonymous + + substs = { + "name": self.name, + "id": "::mozilla::xpcom::ModuleID::%s" % self.name, + } + + res = ( + """ +namespace %(name)s { +static inline const nsID& CID() { + return ::mozilla::xpcom::Components::GetCID(%(id)s); +} + +static inline ::mozilla::xpcom::GetServiceHelper Service(nsresult* aRv = nullptr) { + return {%(id)s, aRv}; +} +""" + % substs + ) + + if not self.singleton: + res += ( + """ +static inline ::mozilla::xpcom::CreateInstanceHelper Create(nsresult* aRv = nullptr) { + return {%(id)s, aRv}; +} +""" + % substs + ) + + res += ( + """\ +} // namespace %(name)s +""" + % substs + ) + + return res + + # Generates the rust code for the `xpcom::components::<name>` entry + # corresponding to this component. This may not be called for modules + # without an explicit `name` (in which cases, `self.anonymous` will be + # true). + def lower_getters_rust(self): + assert not self.anonymous + + substs = { + "name": self.name, + "id": "super::ModuleID::%s" % self.name, + } + + res = ( + """ +#[allow(non_snake_case)] +pub mod %(name)s { + /// Get the singleton service instance for this component. + pub fn service<T: crate::XpCom>() -> Result<crate::RefPtr<T>, nserror::nsresult> { + let mut ga = crate::GetterAddrefs::<T>::new(); + let rv = unsafe { super::Gecko_GetServiceByModuleID(%(id)s, &T::IID, ga.void_ptr()) }; + if rv.failed() { + return Err(rv); + } + ga.refptr().ok_or(nserror::NS_ERROR_NO_INTERFACE) + } +""" + % substs + ) + + if not self.singleton: + res += ( + """ + /// Create a new instance of this component. + pub fn create<T: crate::XpCom>() -> Result<crate::RefPtr<T>, nserror::nsresult> { + let mut ga = crate::GetterAddrefs::<T>::new(); + let rv = unsafe { super::Gecko_CreateInstanceByModuleID(%(id)s, &T::IID, ga.void_ptr()) }; + if rv.failed() { + return Err(rv); + } + ga.refptr().ok_or(nserror::NS_ERROR_NO_INTERFACE) + } +""" + % substs + ) + + res += """\ +} +""" + + return res + + +# Returns a quoted string literal representing the given raw string, with +# certain special characters replaced so that it can be used in a C++-style +# (/* ... */) comment. +def pretty_string(string): + return json.dumps(string).replace("*/", r"*\/").replace("/*", r"/\*") + + +# Represents a static contract ID entry, corresponding to a C++ ContractEntry +# struct, mapping a contract ID to a static module entry. +class ContractEntry(object): + def __init__(self, contract, module): + self.contract = contract + self.module = module + + def to_cxx(self): + return """ + {{ + {contract}, + {module_id}, + }}""".format( + contract=strings.entry_to_cxx(self.contract), + module_id=lower_module_id(self.module), + ) + + +# Represents a static ProtocolHandler entry, corresponding to a C++ +# ProtocolEntry struct, mapping a scheme to a static module entry and metadata. +class ProtocolHandler(object): + def __init__(self, config, module): + def error(str_): + raise Exception( + "Error defining protocol handler %s (%s): %s" + % (str(module.cid), ", ".join(map(repr, module.contract_ids)), str_) + ) + + self.module = module + self.scheme = config.get("scheme", None) + if self.scheme is None: + error("No scheme defined for protocol component") + self.flags = config.get("flags", None) + if self.flags is None: + error("No flags defined for protocol component") + self.default_port = config.get("default_port", -1) + self.has_dynamic_flags = config.get("has_dynamic_flags", False) + + def to_cxx(self): + return """ + {{ + .mScheme = {scheme}, + .mProtocolFlags = {flags}, + .mDefaultPort = {default_port}, + .mModuleID = {module_id}, + .mHasDynamicFlags = {has_dynamic_flags}, + }} + """.format( + scheme=strings.entry_to_cxx(self.scheme), + module_id=lower_module_id(self.module), + flags=" | ".join("nsIProtocolHandler::%s" % flag for flag in self.flags), + default_port=self.default_port, + has_dynamic_flags="true" if self.has_dynamic_flags else "false", + ) + + +# Generates the C++ code for the StaticCategoryEntry and StaticCategory +# structs for all category entries declared in XPCOM manifests. +def gen_categories(substs, categories): + cats = [] + ents = [] + + count = 0 + for category, entries in sorted(categories.items()): + + def k(entry): + return tuple(entry[0]["name"]) + entry[1:] + + entries.sort(key=k) + + cats.append( + " { %s,\n" + " %d, %d },\n" % (strings.entry_to_cxx(category), count, len(entries)) + ) + count += len(entries) + + ents.append(" /* %s */\n" % pretty_string(category)) + for entry, value, processes in entries: + name = entry["name"] + backgroundtasks = entry.get( + "backgroundtasks", BackgroundTasksSelector.NO_TASKS + ) + + ents.append( + " { %s,\n" + " %s,\n" + " %s,\n" + " %s },\n" + % ( + strings.entry_to_cxx(name), + strings.entry_to_cxx(value), + lower_backgroundtasks(backgroundtasks), + lower_processes(processes), + ) + ) + ents.append("\n") + ents.pop() + + substs["category_count"] = len(cats) + substs["categories"] = "".join(cats) + substs["category_entries"] = "".join(ents) + + +# Generates the C++ code for all Init and Unload functions declared in XPCOM +# manifests. These form the bodies of the `CallInitFunc()` and `CallUnload` +# functions in StaticComponents.cpp. +def gen_module_funcs(substs, funcs): + inits = [] + unloads = [] + + template = """\ + case %d: + %s + break; +""" + + for i, (init, unload) in enumerate(funcs): + init_code = "%s();" % init if init else "/* empty */" + inits.append(template % (i, init_code)) + + if unload: + unloads.append( + """\ + if (CalledInit(%d)) { + %s(); + } +""" + % (i, unload) + ) + + substs["init_funcs"] = "".join(inits) + substs["unload_funcs"] = "".join(unloads) + substs["init_count"] = len(funcs) + + +def gen_interfaces(ifaces): + res = [] + for iface in ifaces: + res.append(" nsXPTInterface::%s,\n" % iface) + return "".join(res) + + +# Generates class pre-declarations for any types referenced in `Classes` array +# entries which do not have corresponding `headers` entries to fully declare +# their types. +def gen_decls(types): + root_ns = Namespace() + + for type_ in sorted(types): + parts = type_.split("::") + + ns = root_ns + for part in parts[:-1]: + ns = ns.sub(part) + ns.classes.add(parts[-1]) + + return root_ns.to_cxx() + + +# Generates the `switch` body for the `CreateInstanceImpl()` function, with a +# `case` for each value in ModuleID to construct an instance of the +# corresponding component. +def gen_constructors(entries): + constructors = [] + for entry in entries: + constructors.append( + """\ + case {id}: {{ +{constructor}\ + }} +""".format( + id=lower_module_id(entry), constructor=entry.lower_constructor() + ) + ) + + return "".join(constructors) + + +# Generates the getter code for each named component entry in the +# `mozilla::components::` namespace. +def gen_getters(entries): + entries = list(entries) + entries.sort(key=lambda e: e.name) + + return "".join(entry.lower_getters() for entry in entries if not entry.anonymous) + + +# Generates the rust getter code for each named component entry in the +# `xpcom::components::` module. +def gen_getters_rust(entries): + entries = list(entries) + entries.sort(key=lambda e: e.name) + + return "".join( + entry.lower_getters_rust() for entry in entries if not entry.anonymous + ) + + +def gen_includes(substs, all_headers): + headers = set() + absolute_headers = set() + + for header in all_headers: + if header.startswith("/"): + absolute_headers.add(header) + else: + headers.add(header) + + includes = ['#include "%s"' % header for header in sorted(headers)] + substs["includes"] = "\n".join(includes) + "\n" + + relative_includes = [ + '#include "../..%s"' % header for header in sorted(absolute_headers) + ] + substs["relative_includes"] = "\n".join(relative_includes) + "\n" + + +def to_category_list(val): + # Entries can be bare strings (like `"m-browser"`), lists of bare strings, + # or dictionaries (like `{"name": "m-browser", "backgroundtasks": + # BackgroundTasksSelector.ALL_TASKS}`), somewhat recursively. + + def ensure_dict(v): + # Turn `v` into `{"name": v}` if it's not already a dict. + if isinstance(v, dict): + return v + return {"name": v} + + if isinstance(val, (list, tuple)): + return tuple(ensure_dict(v) for v in val) + + if isinstance(val, dict): + # Explode `{"name": ["x", "y"], "backgroundtasks": ...}` into + # `[{"name": "x", "backgroundtasks": ...}, {"name": "y", "backgroundtasks": ...}]`. + names = val.pop("name") + + vals = [] + for entry in to_category_list(names): + d = dict(val) + d["name"] = entry["name"] + vals.append(d) + + return tuple(vals) + + return (ensure_dict(val),) + + +def gen_substs(manifests): + module_funcs = [] + + headers = set() + + modules = [] + categories = defaultdict(list) + + for manifest in manifests: + headers |= set(manifest.get("Headers", [])) + + init_idx = None + init = manifest.get("InitFunc") + unload = manifest.get("UnloadFunc") + if init or unload: + init_idx = len(module_funcs) + module_funcs.append((init, unload)) + + for clas in manifest["Classes"]: + modules.append(ModuleEntry(clas, init_idx)) + + for category, entries in manifest.get("Categories", {}).items(): + for key, entry in entries.items(): + if isinstance(entry, tuple): + value, process = entry + else: + value, process = entry, 0 + categories[category].append(({"name": key}, value, process)) + + cids = set() + contracts = [] + contract_map = {} + js_services = {} + protocol_handlers = {} + + jsms = set() + esModules = set() + + types = set() + + for mod in modules: + headers |= set(mod.headers) + + for contract_id in mod.contract_ids: + if contract_id in contract_map: + raise Exception("Duplicate contract ID: %s" % contract_id) + + entry = ContractEntry(contract_id, mod) + contracts.append(entry) + contract_map[contract_id] = entry + + for category, entries in mod.categories.items(): + for entry in to_category_list(entries): + categories[category].append((entry, mod.contract_id, mod.processes)) + + if mod.type and not mod.headers: + types.add(mod.type) + + if mod.jsm: + jsms.add(mod.jsm) + + if mod.esModule: + esModules.add(mod.esModule) + + if mod.js_name: + if mod.js_name in js_services: + raise Exception("Duplicate JS service name: %s" % mod.js_name) + js_services[mod.js_name] = mod + + if mod.protocol_config: + handler = ProtocolHandler(mod.protocol_config, mod) + if handler.scheme in protocol_handlers: + raise Exception("Duplicate protocol handler: %s" % handler.scheme) + protocol_handlers[handler.scheme] = handler + + if str(mod.cid) in cids: + raise Exception("Duplicate cid: %s" % str(mod.cid)) + cids.add(str(mod.cid)) + + cid_phf = PerfectHash(modules, PHF_SIZE, key=lambda module: module.cid.bytes) + + contract_phf = PerfectHash(contracts, PHF_SIZE, key=lambda entry: entry.contract) + + js_services_phf = PerfectHash( + list(js_services.values()), PHF_SIZE, key=lambda entry: entry.js_name + ) + + protocol_handlers_phf = PerfectHash( + list(protocol_handlers.values()), TINY_PHF_SIZE, key=lambda entry: entry.scheme + ) + + js_services_json = {} + for entry in js_services.values(): + for iface in entry.interfaces: + js_services_json[iface] = entry.js_name + + substs = {} + + gen_categories(substs, categories) + + substs["module_ids"] = "".join(" %s,\n" % entry.name for entry in cid_phf.entries) + + substs["module_count"] = len(modules) + substs["contract_count"] = len(contracts) + substs["protocol_handler_count"] = len(protocol_handlers) + + substs["default_protocol_handler_idx"] = protocol_handlers_phf.get_index("default") + + gen_module_funcs(substs, module_funcs) + + gen_includes(substs, headers) + + substs["component_jsms"] = ( + "\n".join(" %s," % strings.entry_to_cxx(jsm) for jsm in sorted(jsms)) + "\n" + ) + substs["component_esmodules"] = ( + "\n".join( + " %s," % strings.entry_to_cxx(esModule) for esModule in sorted(esModules) + ) + + "\n" + ) + + substs["interfaces"] = gen_interfaces(interfaces) + + substs["decls"] = gen_decls(types) + + substs["constructors"] = gen_constructors(cid_phf.entries) + + substs["component_getters"] = gen_getters(cid_phf.entries) + + substs["component_getters_rust"] = gen_getters_rust(cid_phf.entries) + + substs["module_cid_table"] = cid_phf.cxx_codegen( + name="ModuleByCID", + entry_type="StaticModule", + entries_name="gStaticModules", + lower_entry=lambda entry: entry.to_cxx(), + return_type="const StaticModule*", + return_entry=( + "return entry.CID().Equals(aKey) && entry.Active()" " ? &entry : nullptr;" + ), + key_type="const nsID&", + key_bytes="reinterpret_cast<const char*>(&aKey)", + key_length="sizeof(nsID)", + ) + + substs["module_contract_id_table"] = contract_phf.cxx_codegen( + name="LookupContractID", + entry_type="ContractEntry", + entries_name="gContractEntries", + lower_entry=lambda entry: entry.to_cxx(), + return_type="const ContractEntry*", + return_entry="return entry.Matches(aKey) ? &entry : nullptr;", + key_type="const nsACString&", + key_bytes="aKey.BeginReading()", + key_length="aKey.Length()", + ) + + substs["js_services_table"] = js_services_phf.cxx_codegen( + name="LookupJSService", + entry_type="JSServiceEntry", + entries_name="gJSServices", + lower_entry=lambda entry: entry.lower_js_service(), + return_type="const JSServiceEntry*", + return_entry="return entry.Name() == aKey ? &entry : nullptr;", + key_type="const nsACString&", + key_bytes="aKey.BeginReading()", + key_length="aKey.Length()", + ) + + substs["protocol_handlers_table"] = protocol_handlers_phf.cxx_codegen( + name="LookupProtocolHandler", + entry_type="StaticProtocolHandler", + entries_name="gStaticProtocolHandlers", + lower_entry=lambda entry: entry.to_cxx(), + return_type="const StaticProtocolHandler*", + return_entry="return entry.Scheme() == aKey ? &entry : nullptr;", + key_type="const nsACString&", + key_bytes="aKey.BeginReading()", + key_length="aKey.Length()", + ) + + substs["js_services_json"] = json.dumps(js_services_json, sort_keys=True, indent=4) + + # Do this only after everything else has been emitted so we're sure the + # string table is complete. + substs["strings"] = strings.to_cxx() + return substs + + +# Returns true if the given build config substitution is defined and truthy. +def defined(subst): + return bool(buildconfig.substs.get(subst)) + + +def read_manifest(filename): + glbl = { + "buildconfig": buildconfig, + "defined": defined, + "ProcessSelector": ProcessSelector, + "BackgroundTasksSelector": BackgroundTasksSelector, + } + code = compile(open(filename).read(), filename, "exec") + exec(code, glbl) + return glbl + + +def main(fd, conf_file, template_file): + def open_output(filename): + return FileAvoidWrite(os.path.join(os.path.dirname(fd.name), filename)) + + conf = json.load(open(conf_file, "r")) + + deps = set() + + manifests = [] + for filename in conf["manifests"]: + deps.add(filename) + manifest = read_manifest(filename) + manifests.append(manifest) + manifest.setdefault("Priority", 50) + manifest["__filename__"] = filename + + manifests.sort(key=lambda man: (man["Priority"], man["__filename__"])) + + substs = gen_substs(manifests) + + def replacer(match): + return substs[match.group(1)] + + with open_output("StaticComponents.cpp") as fh: + with open(template_file, "r") as tfh: + template = tfh.read() + + fh.write(re.sub(r"//# @([a-zA-Z_]+)@\n", replacer, template)) + + with open_output("StaticComponentData.h") as fh: + fh.write( + """\ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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/. */ + +#ifndef StaticComponentData_h +#define StaticComponentData_h + +#include <stddef.h> + +namespace mozilla { +namespace xpcom { + +static constexpr size_t kStaticModuleCount = %(module_count)d; + +static constexpr size_t kContractCount = %(contract_count)d; + +static constexpr size_t kStaticCategoryCount = %(category_count)d; + +static constexpr size_t kModuleInitCount = %(init_count)d; + +static constexpr size_t kStaticProtocolHandlerCount = %(protocol_handler_count)d; + +static constexpr size_t kDefaultProtocolHandlerIndex = %(default_protocol_handler_idx)d; + +} // namespace xpcom +} // namespace mozilla + +#endif +""" + % substs + ) + + with open_output("components.rs") as fh: + fh.write( + """\ +/// Unique IDs for each statically-registered module. +#[repr(u16)] +pub enum ModuleID { +%(module_ids)s +} + +%(component_getters_rust)s +""" + % substs + ) + + fd.write( + """\ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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/. */ + +#ifndef mozilla_Components_h +#define mozilla_Components_h + +#include "nsCOMPtr.h" + +struct nsID; + +#define NS_IMPL_COMPONENT_FACTORY(iface) \\ + template <> \\ + already_AddRefed<nsISupports> mozCreateComponent<iface>() + +template <typename T> +already_AddRefed<nsISupports> mozCreateComponent(); + +namespace mozilla { +namespace xpcom { + +enum class ModuleID : uint16_t { +%(module_ids)s +}; + +// May be added as a friend function to allow constructing services via +// private constructors and init methods. +nsresult CreateInstanceImpl(ModuleID aID, const nsIID& aIID, void** aResult); + +class MOZ_STACK_CLASS StaticModuleHelper : public nsCOMPtr_helper { + public: + StaticModuleHelper(ModuleID aId, nsresult* aErrorPtr) + : mId(aId), mErrorPtr(aErrorPtr) {} + + protected: + nsresult SetResult(nsresult aRv) const { + if (mErrorPtr) { + *mErrorPtr = aRv; + } + return aRv; + } + + ModuleID mId; + nsresult* mErrorPtr; +}; + +class MOZ_STACK_CLASS GetServiceHelper final : public StaticModuleHelper { + public: + using StaticModuleHelper::StaticModuleHelper; + + nsresult NS_FASTCALL operator()(const nsIID& aIID, + void** aResult) const override; +}; + +class MOZ_STACK_CLASS CreateInstanceHelper final : public StaticModuleHelper { + public: + using StaticModuleHelper::StaticModuleHelper; + + nsresult NS_FASTCALL operator()(const nsIID& aIID, + void** aResult) const override; +}; + +class Components final { + public: + static const nsID& GetCID(ModuleID aID); +}; + +} // namespace xpcom + +namespace components { +%(component_getters)s +} // namespace components + +} // namespace mozilla + +#endif +""" + % substs + ) + + with open_output("services.json") as fh: + fh.write(substs["js_services_json"]) + + return deps |