Bug 1478124: Part 6 - Add helpers for creating/inspecting static modules. r=froydnj

The static XPCOM manifest format makes it easy to define a component in a
single place, without separate contract ID and CID macro definitions in
headers, and variable constants in module files. Without any other changes,
however, those macros are still required in order to create instances of or
retrieve services for the component.

This patch solves that problem by allowing component definitions to include an
explicit component name, and adding helpers for each named component to
Components.h:

  mozilla::components::<Name>::CID() to retrieve its class ID.

  mozilla::components::<Name>::Create() to create a new instance.

  mozilla::components::<Name>::Service() to retrieve its service instance.

These getters have the benefit of doing full compile-time sanity checking,
with no possibilty of using a mistyped contract ID string, or a macro constant
which has gotten out of sync with the component entry.

Moreover, when possible, these getters are optimized to operate on module
entries directly, without going through expensive hash lookups or virtual
calls.

Differential Revision: https://phabricator.services.mozilla.com/D15037

--HG--
extra : rebase_source : ab07ef6a7ad8b26cd4e1901d3365beeb8c22ec3b
extra : source : 929fd654c9dfc3222e1972faadea3cc066e51654
This commit is contained in:
Kris Maglione 2018-12-15 14:17:27 -08:00
parent 9496ee68de
commit 476dc527b7
5 changed files with 197 additions and 0 deletions

View File

@ -29,6 +29,8 @@
namespace mozilla {
namespace xpcom {
static constexpr uint32_t kNoContractID = 0xffffffff;
namespace {
// Template helpers for constructor function sanity checks.
template <typename T>
@ -165,6 +167,15 @@ bool StaticModule::Active() const {
return FastProcessSelectorMatches(mProcessSelector);
}
bool StaticModule::Overridable() const {
return mContractID.mOffset != kNoContractID;
}
nsCString StaticModule::ContractID() const {
MOZ_ASSERT(Overridable());
return GetString(mContractID);
}
nsresult StaticModule::CreateInstance(nsISupports* aOuter, const nsIID& aIID,
void** aResult) const {
return CreateInstanceImpl(ID(), aOuter, aIID, aResult);
@ -224,5 +235,26 @@ nsCString StaticCategory::Name() const {
CallUnloadFuncs();
}
/* static */ const nsID& Components::GetCID(ModuleID aID) {
return gStaticModules[size_t(aID)].CID();
}
nsresult GetServiceHelper::operator()(const nsIID& aIID, void** aResult) const {
nsresult rv =
nsComponentManagerImpl::gComponentManager->GetService(mId, aIID, aResult);
return SetResult(rv);
}
nsresult CreateInstanceHelper::operator()(const nsIID& aIID,
void** aResult) const {
const auto& entry = gStaticModules[size_t(mId)];
if (!entry.Active()) {
return SetResult(NS_ERROR_FACTORY_NOT_REGISTERED);
}
nsresult rv = entry.CreateInstance(nullptr, aIID, aResult);
return SetResult(rv);
}
} // namespace xpcom
} // namespace mozilla

View File

@ -78,6 +78,7 @@ struct StringOffset final {
*/
struct StaticModule {
nsID mCID;
StringOffset mContractID;
Module::ProcessSelector mProcessSelector;
const nsID& CID() const { return mCID; }
@ -89,6 +90,22 @@ struct StaticModule {
*/
size_t Idx() const { return size_t(ID()); }
/**
* Returns true if this component's corresponding contract ID is expected to
* be overridden at runtime. If so, it should always be looked up by its
* ContractID() when retrieving its service instance.
*/
bool Overridable() const;
/**
* If this entry is overridable, returns its associated contract ID string.
* The component should always be looked up by this contract ID when
* retrieving its service instance.
*
* Note: This may *only* be called if Overridable() returns true.
*/
nsCString ContractID() const;
/**
* Returns true if this entry is active. Typically this will only return false
* if the entry's process selector does not match this process.

View File

@ -11,6 +11,8 @@ from perfecthash import PerfectHash
import buildconfig
NO_CONTRACT_ID = 0xffffffff
PHF_SIZE = 512
ENDIAN = '<' if buildconfig.substs['TARGET_ENDIANNESS'] == 'little' else '>'
@ -219,6 +221,7 @@ class ModuleEntry(object):
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
@ -248,6 +251,10 @@ class ModuleEntry(object):
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]
@ -255,13 +262,19 @@ class ModuleEntry(object):
# 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
@ -322,6 +335,42 @@ class ModuleEntry(object):
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
@ -442,6 +491,17 @@ def gen_constructors(entries):
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()
@ -535,6 +595,8 @@ def gen_substs(manifests):
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',
@ -666,8 +728,52 @@ 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)

View File

@ -1446,6 +1446,46 @@ nsComponentManagerImpl::GetService(const nsCID& aClass, const nsIID& aIID,
return GetServiceLocked(lock, *entry, aIID, aResult);
}
nsresult
nsComponentManagerImpl::GetService(ModuleID aId, const nsIID& aIID,
void** aResult) {
const auto& entry = gStaticModules[size_t(aId)];
// test this first, since there's no point in returning a service during
// shutdown -- whether it's available or not would depend on the order it
// occurs in the list
if (gXPCOMShuttingDown) {
// When processing shutdown, don't process new GetService() requests
#ifdef SHOW_DENIED_ON_SHUTDOWN
fprintf(stderr,
"Getting service on shutdown. Denied.\n"
" CID: %s\n IID: %s\n",
AutoIDString(entry.CID()).get(),
AutoIDString(aIID).get());
#endif /* SHOW_DENIED_ON_SHUTDOWN */
return NS_ERROR_UNEXPECTED;
}
MutexLock lock(mLock);
if (!entry.Active()) {
return NS_ERROR_FACTORY_NOT_REGISTERED;
}
Maybe<EntryWrapper> wrapper;
if (entry.Overridable()) {
// If we expect this service to be overridden by test code, we need to look
// it up by contract ID every time.
wrapper = LookupByContractID(lock, entry.ContractID());
if (!wrapper) {
return NS_ERROR_FACTORY_NOT_REGISTERED;
}
} else {
wrapper.emplace(&entry);
}
return GetServiceLocked(lock, *wrapper, aIID, aResult);
}
NS_IMETHODIMP
nsComponentManagerImpl::IsServiceInstantiated(const nsCID& aClass,
const nsIID& aIID,

View File

@ -163,6 +163,8 @@ class nsComponentManagerImpl final : public nsIComponentManager,
mozilla::Maybe<EntryWrapper> LookupByContractID(
const MutexLock&, const nsACString& aContractID);
nsresult GetService(mozilla::xpcom::ModuleID, const nsIID& aIID, void** aResult);
static void InitializeStaticModules();
static void InitializeModuleLocations();