Bug 1478124: Part 6 - Add helpers for creating/inspecting static modules. r=froydnj
☠☠ backed out by 1e042fc7de3d ☠ ☠
authorKris Maglione <maglione.k@gmail.com>
Sat, 15 Dec 2018 14:17:27 -0800
changeset 455926 929fd654c9dfc3222e1972faadea3cc066e51654
parent 455925 1ddd80d9e91a17c01f0a8a73036810042a0ab080
child 455927 5d85deac61c2ee54a69525de8bdfff4be72d224c
push id35463
push usershindli@mozilla.com
push dateTue, 29 Jan 2019 21:38:17 +0000
treeherdermozilla-central@4440fbf71c72 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersfroydnj
bugs1478124
milestone67.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
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
xpcom/components/StaticComponents.cpp.in
xpcom/components/StaticComponents.h
xpcom/components/gen_static_components.py
xpcom/components/nsComponentManager.cpp
xpcom/components/nsComponentManager.h
--- a/xpcom/components/StaticComponents.cpp.in
+++ b/xpcom/components/StaticComponents.cpp.in
@@ -24,16 +24,18 @@
 // Relative includes
 //# @relative_includes@
 
 //# @decls@
 
 namespace mozilla {
 namespace xpcom {
 
+static constexpr uint32_t kNoContractID = 0xffffffff;
+
 namespace {
 // Template helpers for constructor function sanity checks.
 template <typename T>
 struct RemoveAlreadyAddRefed {
   using Type = T;
 };
 
 template <typename T>
@@ -160,16 +162,25 @@ NS_IMETHODIMP StaticModuleFactory::LockF
 already_AddRefed<nsIFactory> StaticModule::GetFactory() const {
   return do_AddRef(new StaticModuleFactory(ID()));
 }
 
 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);
 }
 
 
 nsISupports* StaticModule::ServiceInstance() const {
   return gServiceInstances[Idx()];
@@ -219,10 +230,31 @@ nsCString StaticCategory::Name() const {
   }
   return false;
 }
 
 /* static */ void StaticComponents::Shutdown() {
   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
--- a/xpcom/components/StaticComponents.h
+++ b/xpcom/components/StaticComponents.h
@@ -73,28 +73,45 @@ struct StringOffset final {
 
 /**
  * Represents a static component entry defined in a `Classes` list in an XPCOM
  * manifest. Handles creating instances of and caching service instances for
  * that class.
  */
 struct StaticModule {
   nsID mCID;
+  StringOffset mContractID;
   Module::ProcessSelector mProcessSelector;
 
   const nsID& CID() const { return mCID; }
 
   ModuleID ID() const { return ModuleID(this - gStaticModules); }
 
   /**
    * Returns this entry's index in the gStaticModules array.
    */
   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.
    */
   bool Active() const;
 
   already_AddRefed<nsIFactory> GetFactory() const;
 
   nsresult CreateInstance(nsISupports* aOuter, const nsIID& aIID,
--- a/xpcom/components/gen_static_components.py
+++ b/xpcom/components/gen_static_components.py
@@ -6,16 +6,18 @@ 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):
@@ -214,16 +216,17 @@ class ModuleEntry(object):
 
         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
@@ -243,30 +246,40 @@ class ModuleEntry(object):
             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
@@ -317,16 +330,52 @@ class ModuleEntry(object):
 
             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'/\*'))
 
@@ -437,16 +486,27 @@ def gen_constructors(entries):
 {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:
@@ -530,16 +590,18 @@ def gen_substs(manifests):
     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()'
@@ -661,14 +723,58 @@ already_AddRefed<nsISupports> mozCreateC
 
 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
--- a/xpcom/components/nsComponentManager.cpp
+++ b/xpcom/components/nsComponentManager.cpp
@@ -1439,16 +1439,56 @@ nsComponentManagerImpl::GetService(const
   Maybe<EntryWrapper> entry = LookupByCID(lock, aClass);
   if (!entry) {
     return NS_ERROR_FACTORY_NOT_REGISTERED;
   }
 
   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,
                                               bool* aResult) {
   // Now we want to get the service if we already got it. If not, we don't want
   // to create an instance of it. mmh!
 
   // test this first, since there's no point in returning a service during
--- a/xpcom/components/nsComponentManager.h
+++ b/xpcom/components/nsComponentManager.h
@@ -158,16 +158,18 @@ class nsComponentManagerImpl final : pub
   mozilla::Maybe<EntryWrapper> LookupByCID(const nsID& aCID);
   mozilla::Maybe<EntryWrapper> LookupByCID(const MutexLock&, const nsID& aCID);
 
   mozilla::Maybe<EntryWrapper> LookupByContractID(
       const nsACString& aContractID);
   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();
 
   struct ComponentLocation {
     NSLocationType type;
     mozilla::FileLocation location;
   };