Revision control

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_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)
# 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_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',
}
# 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()
# 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', [])
# 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.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.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 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.external:
res += (' nsCOMPtr<nsISupports> inst = '
'mozCreateComponent<%s>();\n' % self.type)
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(
mozilla::IsSame<already_AddRefed<T>, decltype(%(constructor)s())>::value,
"Singleton constructor must return already_AddRefed");
static_assert(
mozilla::IsBaseOf<%(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
# 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)
# 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 = []
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))
contracts = []
contract_map = {}
categories = defaultdict(list)
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)
cid_phf = PerfectHash(modules, PHF_SIZE,
key=lambda module: module.cid.bytes)
contract_phf = PerfectHash(contracts, PHF_SIZE,
key=lambda entry: entry.contract)
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['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<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()')
# 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}
execfile(filename, 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<nsISupports> mozCreateComponent<iface>()
template <typename T>
already_AddRefed<nsISupports> 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