import json import os import re import struct from collections import defaultdict from uuid import UUID from mozbuild.util import FileAvoidWrite from perfecthash import PerfectHash import buildconfig NO_CONTRACT_ID = 0xffffffff PHF_SIZE = 512 ENDIAN = '<' if buildconfig.substs['TARGET_ENDIANNESS'] == '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 = 0x0 MAIN_PROCESS_ONLY = 0x1 CONTENT_PROCESS_ONLY = 0x2 ALLOW_IN_GPU_PROCESS = 0x4 ALLOW_IN_VR_PROCESS = 0x8 ALLOW_IN_SOCKET_PROCESS = 0x10 ALLOW_IN_RDD_PROCESS = 0x20 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_VR_AND_SOCKET_PROCESS = (ALLOW_IN_GPU_PROCESS | ALLOW_IN_RDD_PROCESS | ALLOW_IN_VR_PROCESS | ALLOW_IN_SOCKET_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_VR_AND_SOCKET_PROCESS: 'ALLOW_IN_GPU_RDD_VR_AND_SOCKET_PROCESS', } # 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 # 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.external = data.get('external', not (self.headers or self.legacy_constructor)) self.singleton = data.get('singleton', False) self.overridable = data.get('overridable', False) 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.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 represengin 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(nullptr, aIID, aResult);\n' % self.legacy_constructor) return res if self.jsm: res += ( ' nsCOMPtr inst;\n' ' MOZ_TRY(ConstructJSMComponent(nsLiteralCString(%s),\n' ' %s,\n' ' getter_AddRefs(inst)));' '\n' % (json.dumps(self.jsm), json.dumps(self.constructor))) elif self.external: res += (' nsCOMPtr 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::Type; static_assert( std::is_same_v, 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::` 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 # 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)) # 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()): entries.sort() 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: ents.append(' { %s,\n' ' %s,\n' ' %s },\n' % (strings.entry_to_cxx(entry), strings.entry_to_cxx(value), 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) 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_list(val): if isinstance(val, (list, tuple)): return val return 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((key, value, process)) cids = set() contracts = [] contract_map = {} js_services = {} jsms = 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_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.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 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) 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) 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['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['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(&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()') # 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} exec(open(filename).read(), 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 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; } // namespace xpcom } // namespace mozilla #endif """ % 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 mozCreateComponent() template already_AddRefed mozCreateComponent(); namespace mozilla { namespace xpcom { enum class ModuleID : uint16_t { %(module_ids)s }; 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) return deps