LCOV - code coverage report
Current view: top level - xpcom/ds - nsAtomTable.cpp (source / functions) Hit Total Coverage
Test: output.info Lines: 219 285 76.8 %
Date: 2017-07-14 16:53:18 Functions: 35 46 76.1 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
       2             : /* vim: set ts=8 sts=2 et sw=2 tw=80: */
       3             : /* This Source Code Form is subject to the terms of the Mozilla Public
       4             :  * License, v. 2.0. If a copy of the MPL was not distributed with this
       5             :  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
       6             : 
       7             : #include "mozilla/Assertions.h"
       8             : #include "mozilla/Attributes.h"
       9             : #include "mozilla/HashFunctions.h"
      10             : #include "mozilla/MemoryReporting.h"
      11             : #include "mozilla/Mutex.h"
      12             : #include "mozilla/DebugOnly.h"
      13             : #include "mozilla/Sprintf.h"
      14             : #include "mozilla/Unused.h"
      15             : 
      16             : #include "nsAtomTable.h"
      17             : #include "nsStaticAtom.h"
      18             : #include "nsString.h"
      19             : #include "nsCRT.h"
      20             : #include "PLDHashTable.h"
      21             : #include "prenv.h"
      22             : #include "nsThreadUtils.h"
      23             : #include "nsDataHashtable.h"
      24             : #include "nsHashKeys.h"
      25             : #include "nsAutoPtr.h"
      26             : #include "nsUnicharUtils.h"
      27             : #include "nsPrintfCString.h"
      28             : 
      29             : // There are two kinds of atoms handled by this module.
      30             : //
      31             : // - DynamicAtom: the atom itself is heap allocated, as is the nsStringBuffer it
      32             : //   points to. |gAtomTable| holds weak references to them DynamicAtoms. When
      33             : //   the refcount of a DynamicAtom drops to zero, we increment a static counter.
      34             : //   When that counter reaches a certain threshold, we iterate over the atom
      35             : //   table, removing and deleting DynamicAtoms with refcount zero. This allows
      36             : //   us to avoid acquiring the atom table lock during normal refcounting.
      37             : //
      38             : // - StaticAtom: the atom itself is heap allocated, but it points to a static
      39             : //   nsStringBuffer. |gAtomTable| effectively owns StaticAtoms, because such
      40             : //   atoms ignore all AddRef/Release calls, which ensures they stay alive until
      41             : //   |gAtomTable| itself is destroyed whereupon they are explicitly deleted.
      42             : //
      43             : //   Note that gAtomTable is used on multiple threads, and callers must
      44             : //   acquire gAtomTableLock before touching it.
      45             : 
      46             : using namespace mozilla;
      47             : 
      48             : //----------------------------------------------------------------------
      49             : 
      50             : class CheckStaticAtomSizes
      51             : {
      52             :   CheckStaticAtomSizes()
      53             :   {
      54             :     static_assert((sizeof(nsFakeStringBuffer<1>().mRefCnt) ==
      55             :                    sizeof(nsStringBuffer().mRefCount)) &&
      56             :                   (sizeof(nsFakeStringBuffer<1>().mSize) ==
      57             :                    sizeof(nsStringBuffer().mStorageSize)) &&
      58             :                   (offsetof(nsFakeStringBuffer<1>, mRefCnt) ==
      59             :                    offsetof(nsStringBuffer, mRefCount)) &&
      60             :                   (offsetof(nsFakeStringBuffer<1>, mSize) ==
      61             :                    offsetof(nsStringBuffer, mStorageSize)) &&
      62             :                   (offsetof(nsFakeStringBuffer<1>, mStringData) ==
      63             :                    sizeof(nsStringBuffer)),
      64             :                   "mocked-up strings' representations should be compatible");
      65             :   }
      66             : };
      67             : 
      68             : //----------------------------------------------------------------------
      69             : 
      70             : static Atomic<uint32_t, ReleaseAcquire> gUnusedAtomCount(0);
      71             : 
      72             : class DynamicAtom final : public nsIAtom
      73             : {
      74             : public:
      75        2854 :   static already_AddRefed<DynamicAtom> Create(const nsAString& aString, uint32_t aHash)
      76             :   {
      77             :     // The refcount is appropriately initialized in the constructor.
      78        2854 :     return dont_AddRef(new DynamicAtom(aString, aHash));
      79             :   }
      80             : 
      81             :   static void GCAtomTable();
      82             : 
      83             :   enum class GCKind {
      84             :     RegularOperation,
      85             :     Shutdown,
      86             :   };
      87             : 
      88             :   static void GCAtomTableLocked(const MutexAutoLock& aProofOfLock,
      89             :                                 GCKind aKind);
      90             : 
      91             : private:
      92        2854 :   DynamicAtom(const nsAString& aString, uint32_t aHash)
      93        2854 :     : mRefCnt(1)
      94             :   {
      95        2854 :     mLength = aString.Length();
      96        2854 :     mIsStatic = false;
      97        5708 :     RefPtr<nsStringBuffer> buf = nsStringBuffer::FromString(aString);
      98        2854 :     if (buf) {
      99         549 :       mString = static_cast<char16_t*>(buf->Data());
     100             :     } else {
     101        2305 :       const size_t size = (mLength + 1) * sizeof(char16_t);
     102        2305 :       buf = nsStringBuffer::Alloc(size);
     103        2305 :       if (MOZ_UNLIKELY(!buf)) {
     104             :         // We OOM because atom allocations should be small and it's hard to
     105             :         // handle them more gracefully in a constructor.
     106           0 :         NS_ABORT_OOM(size);
     107             :       }
     108        2305 :       mString = static_cast<char16_t*>(buf->Data());
     109        2305 :       CopyUnicodeTo(aString, 0, mString, mLength);
     110        2305 :       mString[mLength] = char16_t(0);
     111             :     }
     112             : 
     113        2854 :     mHash = aHash;
     114        2854 :     MOZ_ASSERT(mHash == HashString(mString, mLength));
     115             : 
     116        2854 :     NS_ASSERTION(mString[mLength] == char16_t(0), "null terminated");
     117        2854 :     NS_ASSERTION(buf && buf->StorageSize() >= (mLength + 1) * sizeof(char16_t),
     118             :                  "enough storage");
     119        2854 :     NS_ASSERTION(Equals(aString), "correct data");
     120             : 
     121             :     // Take ownership of buffer
     122        2854 :     mozilla::Unused << buf.forget();
     123        2854 :   }
     124             : 
     125             : private:
     126             :   // We don't need a virtual destructor because we always delete via a
     127             :   // DynamicAtom* pointer (in GCAtomTable()), not an nsIAtom* pointer.
     128             :   ~DynamicAtom();
     129             : 
     130             : public:
     131             :   NS_DECL_THREADSAFE_ISUPPORTS
     132             :   NS_DECL_NSIATOM
     133             : };
     134             : 
     135             : class StaticAtom final : public nsIAtom
     136             : {
     137             : public:
     138        8013 :   StaticAtom(nsStringBuffer* aStringBuffer, uint32_t aLength, uint32_t aHash)
     139        8013 :   {
     140        8013 :     mLength = aLength;
     141        8013 :     mIsStatic = true;
     142        8013 :     mString = static_cast<char16_t*>(aStringBuffer->Data());
     143             :     // Technically we could currently avoid doing this addref by instead making
     144             :     // the static atom buffers have an initial refcount of 2.
     145        8013 :     aStringBuffer->AddRef();
     146             : 
     147        8013 :     mHash = aHash;
     148        8013 :     MOZ_ASSERT(mHash == HashString(mString, mLength));
     149             : 
     150        8013 :     MOZ_ASSERT(mString[mLength] == char16_t(0), "null terminated");
     151        8013 :     MOZ_ASSERT(aStringBuffer &&
     152             :                aStringBuffer->StorageSize() == (mLength + 1) * sizeof(char16_t),
     153             :                "correct storage");
     154        8013 :   }
     155             : 
     156             :   // We don't need a virtual destructor because we always delete via a
     157             :   // StaticAtom* pointer (in AtomTableClearEntry()), not an nsIAtom* pointer.
     158           0 :   ~StaticAtom() {}
     159             : 
     160             :   NS_DECL_ISUPPORTS
     161             :   NS_DECL_NSIATOM
     162             : };
     163             : 
     164       56600 : NS_IMPL_QUERY_INTERFACE(StaticAtom, nsIAtom)
     165             : 
     166             : NS_IMETHODIMP_(MozExternalRefCountType)
     167      111207 : StaticAtom::AddRef()
     168             : {
     169      111207 :   return 2;
     170             : }
     171             : 
     172             : NS_IMETHODIMP_(MozExternalRefCountType)
     173       80134 : StaticAtom::Release()
     174             : {
     175       80134 :   return 1;
     176             : }
     177             : 
     178             : NS_IMETHODIMP
     179           0 : DynamicAtom::ScriptableToString(nsAString& aBuf)
     180             : {
     181           0 :   nsStringBuffer::FromData(mString)->ToString(mLength, aBuf);
     182           0 :   return NS_OK;
     183             : }
     184             : 
     185             : NS_IMETHODIMP
     186           0 : StaticAtom::ScriptableToString(nsAString& aBuf)
     187             : {
     188           0 :   nsStringBuffer::FromData(mString)->ToString(mLength, aBuf);
     189           0 :   return NS_OK;
     190             : }
     191             : 
     192             : NS_IMETHODIMP
     193         100 : DynamicAtom::ToUTF8String(nsACString& aBuf)
     194             : {
     195         100 :   CopyUTF16toUTF8(nsDependentString(mString, mLength), aBuf);
     196         100 :   return NS_OK;
     197             : }
     198             : 
     199             : NS_IMETHODIMP
     200          86 : StaticAtom::ToUTF8String(nsACString& aBuf)
     201             : {
     202          86 :   CopyUTF16toUTF8(nsDependentString(mString, mLength), aBuf);
     203          86 :   return NS_OK;
     204             : }
     205             : 
     206             : NS_IMETHODIMP
     207           0 : DynamicAtom::ScriptableEquals(const nsAString& aString, bool* aResult)
     208             : {
     209           0 :   *aResult = aString.Equals(nsDependentString(mString, mLength));
     210           0 :   return NS_OK;
     211             : }
     212             : 
     213             : NS_IMETHODIMP
     214           0 : StaticAtom::ScriptableEquals(const nsAString& aString, bool* aResult)
     215             : {
     216           0 :   *aResult = aString.Equals(nsDependentString(mString, mLength));
     217           0 :   return NS_OK;
     218             : }
     219             : 
     220             : NS_IMETHODIMP_(size_t)
     221           0 : DynamicAtom::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf)
     222             : {
     223           0 :   size_t n = aMallocSizeOf(this);
     224           0 :   n += nsStringBuffer::FromData(mString)->SizeOfIncludingThisIfUnshared(
     225             :          aMallocSizeOf);
     226           0 :   return n;
     227             : }
     228             : 
     229             : NS_IMETHODIMP_(size_t)
     230           0 : StaticAtom::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf)
     231             : {
     232           0 :   size_t n = aMallocSizeOf(this);
     233             :   // Don't measure the string buffer pointed to by the StaticAtom because it's
     234             :   // in static memory.
     235           0 :   return n;
     236             : }
     237             : 
     238             : //----------------------------------------------------------------------
     239             : 
     240             : /**
     241             :  * The shared hash table for atom lookups.
     242             :  *
     243             :  * Callers must hold gAtomTableLock before manipulating the table.
     244             :  */
     245             : static PLDHashTable* gAtomTable;
     246             : static Mutex* gAtomTableLock;
     247             : 
     248             : struct AtomTableKey
     249             : {
     250             :   AtomTableKey(const char16_t* aUTF16String, uint32_t aLength, uint32_t aHash)
     251             :     : mUTF16String(aUTF16String)
     252             :     , mUTF8String(nullptr)
     253             :     , mLength(aLength)
     254             :     , mHash(aHash)
     255             :   {
     256             :     MOZ_ASSERT(mHash == HashString(mUTF16String, mLength));
     257             :   }
     258             : 
     259             :   AtomTableKey(const char* aUTF8String, uint32_t aLength, uint32_t aHash)
     260             :     : mUTF16String(nullptr)
     261             :     , mUTF8String(aUTF8String)
     262             :     , mLength(aLength)
     263             :     , mHash(aHash)
     264             :   {
     265             :     mozilla::DebugOnly<bool> err;
     266             :     MOZ_ASSERT(aHash == HashUTF8AsUTF16(mUTF8String, mLength, &err));
     267             :   }
     268             : 
     269       40920 :   AtomTableKey(const char16_t* aUTF16String, uint32_t aLength,
     270             :                uint32_t* aHashOut)
     271       40920 :     : mUTF16String(aUTF16String)
     272             :     , mUTF8String(nullptr)
     273       40920 :     , mLength(aLength)
     274             :   {
     275       40920 :     mHash = HashString(mUTF16String, mLength);
     276       40920 :     *aHashOut = mHash;
     277       40920 :   }
     278             : 
     279         844 :   AtomTableKey(const char* aUTF8String, uint32_t aLength, uint32_t* aHashOut)
     280         844 :     : mUTF16String(nullptr)
     281             :     , mUTF8String(aUTF8String)
     282         844 :     , mLength(aLength)
     283             :   {
     284             :     bool err;
     285         844 :     mHash = HashUTF8AsUTF16(mUTF8String, mLength, &err);
     286         844 :     if (err) {
     287           0 :       mUTF8String = nullptr;
     288           0 :       mLength = 0;
     289           0 :       mHash = 0;
     290             :     }
     291         844 :     *aHashOut = mHash;
     292         844 :   }
     293             : 
     294             :   const char16_t* mUTF16String;
     295             :   const char* mUTF8String;
     296             :   uint32_t mLength;
     297             :   uint32_t mHash;
     298             : };
     299             : 
     300             : struct AtomTableEntry : public PLDHashEntryHdr
     301             : {
     302             :   // These references are either to DynamicAtoms, in which case they are
     303             :   // non-owning, or they are to StaticAtoms, which aren't really refcounted.
     304             :   // See the comment at the top of this file for more details.
     305             :   nsIAtom* MOZ_NON_OWNING_REF mAtom;
     306             : };
     307             : 
     308             : static PLDHashNumber
     309       40558 : AtomTableGetHash(const void* aKey)
     310             : {
     311       40558 :   const AtomTableKey* k = static_cast<const AtomTableKey*>(aKey);
     312       40558 :   return k->mHash;
     313             : }
     314             : 
     315             : static bool
     316       29691 : AtomTableMatchKey(const PLDHashEntryHdr* aEntry, const void* aKey)
     317             : {
     318       29691 :   const AtomTableEntry* he = static_cast<const AtomTableEntry*>(aEntry);
     319       29691 :   const AtomTableKey* k = static_cast<const AtomTableKey*>(aKey);
     320             : 
     321       29691 :   if (k->mUTF8String) {
     322             :     return
     323        1200 :       CompareUTF8toUTF16(nsDependentCSubstring(k->mUTF8String,
     324         600 :                                                k->mUTF8String + k->mLength),
     325        1800 :                          nsDependentAtomString(he->mAtom)) == 0;
     326             :   }
     327             : 
     328       29091 :   return he->mAtom->Equals(k->mUTF16String, k->mLength);
     329             : }
     330             : 
     331             : static void
     332          80 : AtomTableClearEntry(PLDHashTable* aTable, PLDHashEntryHdr* aEntry)
     333             : {
     334          80 :   auto entry = static_cast<AtomTableEntry*>(aEntry);
     335          80 :   nsIAtom* atom = entry->mAtom;
     336          80 :   if (atom->IsStaticAtom()) {
     337             :     // This case -- when the entry being cleared holds a StaticAtom -- only
     338             :     // occurs when gAtomTable is destroyed, whereupon all StaticAtoms within it
     339             :     // must be explicitly deleted. The cast is required because StaticAtom
     340             :     // doesn't have a virtual destructor.
     341           0 :     delete static_cast<StaticAtom*>(atom);
     342             :   }
     343          80 : }
     344             : 
     345             : static void
     346       10867 : AtomTableInitEntry(PLDHashEntryHdr* aEntry, const void* aKey)
     347             : {
     348       10867 :   static_cast<AtomTableEntry*>(aEntry)->mAtom = nullptr;
     349       10867 : }
     350             : 
     351             : static const PLDHashTableOps AtomTableOps = {
     352             :   AtomTableGetHash,
     353             :   AtomTableMatchKey,
     354             :   PLDHashTable::MoveEntryStub,
     355             :   AtomTableClearEntry,
     356             :   AtomTableInitEntry
     357             : };
     358             : 
     359             : //----------------------------------------------------------------------
     360             : 
     361             : #define RECENTLY_USED_MAIN_THREAD_ATOM_CACHE_SIZE 31
     362             : static nsIAtom*
     363             :   sRecentlyUsedMainThreadAtoms[RECENTLY_USED_MAIN_THREAD_ATOM_CACHE_SIZE] = {};
     364             : 
     365             : void
     366           4 : DynamicAtom::GCAtomTable()
     367             : {
     368           4 :   if (NS_IsMainThread()) {
     369           8 :     MutexAutoLock lock(*gAtomTableLock);
     370           4 :     GCAtomTableLocked(lock, GCKind::RegularOperation);
     371             :   }
     372           4 : }
     373             : 
     374             : void
     375           4 : DynamicAtom::GCAtomTableLocked(const MutexAutoLock& aProofOfLock,
     376             :                                GCKind aKind)
     377             : {
     378           4 :   MOZ_ASSERT(NS_IsMainThread());
     379         128 :   for (uint32_t i = 0; i < RECENTLY_USED_MAIN_THREAD_ATOM_CACHE_SIZE; ++i) {
     380         124 :     sRecentlyUsedMainThreadAtoms[i] = nullptr;
     381             :   }
     382             : 
     383           4 :   uint32_t removedCount = 0; // Use a non-atomic temporary for cheaper increments.
     384           8 :   nsAutoCString nonZeroRefcountAtoms;
     385           4 :   uint32_t nonZeroRefcountAtomsCount = 0;
     386       15456 :   for (auto i = gAtomTable->Iter(); !i.Done(); i.Next()) {
     387       15452 :     auto entry = static_cast<AtomTableEntry*>(i.Get());
     388       15452 :     if (entry->mAtom->IsStaticAtom()) {
     389       10684 :       continue;
     390             :     }
     391             : 
     392        4768 :     auto atom = static_cast<DynamicAtom*>(entry->mAtom);
     393        4768 :     if (atom->mRefCnt == 0) {
     394          80 :       i.Remove();
     395          80 :       delete atom;
     396          80 :       ++removedCount;
     397             :     }
     398             : #ifdef NS_FREE_PERMANENT_DATA
     399        4688 :     else if (aKind == GCKind::Shutdown && PR_GetEnv("XPCOM_MEM_BLOAT_LOG")) {
     400             :       // Only report leaking atoms in leak-checking builds in a run
     401             :       // where we are checking for leaks, during shutdown. If
     402             :       // something is anomalous, then we'll assert later in this
     403             :       // function.
     404           0 :       nsAutoCString name;
     405           0 :       atom->ToUTF8String(name);
     406           0 :       if (nonZeroRefcountAtomsCount == 0) {
     407           0 :         nonZeroRefcountAtoms = name;
     408           0 :       } else if (nonZeroRefcountAtomsCount < 20) {
     409           0 :         nonZeroRefcountAtoms += NS_LITERAL_CSTRING(",") + name;
     410           0 :       } else if (nonZeroRefcountAtomsCount == 20) {
     411           0 :         nonZeroRefcountAtoms += NS_LITERAL_CSTRING(",...");
     412             :       }
     413           0 :       nonZeroRefcountAtomsCount++;
     414             :     }
     415             : #endif
     416             : 
     417             :   }
     418           4 :   if (nonZeroRefcountAtomsCount) {
     419             :     nsPrintfCString msg("%d dynamic atom(s) with non-zero refcount: %s",
     420           0 :                         nonZeroRefcountAtomsCount, nonZeroRefcountAtoms.get());
     421           0 :     NS_ASSERTION(nonZeroRefcountAtomsCount == 0, msg.get());
     422             :   }
     423             : 
     424             :   // During the course of this function, the atom table is locked. This means
     425             :   // that, barring refcounting bugs in consumers, an atom can never go from
     426             :   // refcount == 0 to refcount != 0 during a GC. However, an atom _can_ go from
     427             :   // refcount != 0 to refcount == 0 if a Release() occurs in parallel with GC.
     428             :   // This means that we cannot assert that gUnusedAtomCount == removedCount, and
     429             :   // thus that there are no unused atoms at the end of a GC. We can and do,
     430             :   // however, assert this after the last GC at shutdown.
     431           4 :   if (aKind == GCKind::RegularOperation) {
     432           4 :     MOZ_ASSERT(removedCount <= gUnusedAtomCount);
     433             :   } else {
     434             :     // Complain if somebody adds new GCKind enums.
     435           0 :     MOZ_ASSERT(aKind == GCKind::Shutdown);
     436             :     // Our unused atom count should be accurate.
     437           0 :     MOZ_ASSERT(removedCount == gUnusedAtomCount);
     438             :   }
     439             : 
     440           4 :   gUnusedAtomCount -= removedCount;
     441           4 : }
     442             : 
     443       28769 : NS_IMPL_QUERY_INTERFACE(DynamicAtom, nsIAtom)
     444             : 
     445             : NS_IMETHODIMP_(MozExternalRefCountType)
     446       52664 : DynamicAtom::AddRef(void)
     447             : {
     448       52664 :   nsrefcnt count = ++mRefCnt;
     449       52664 :   if (count == 1) {
     450         164 :     MOZ_ASSERT(gUnusedAtomCount > 0);
     451         164 :     gUnusedAtomCount--;
     452             :   }
     453       52664 :   return count;
     454             : }
     455             : 
     456             : #ifdef DEBUG
     457             : // We set a lower GC threshold for atoms in debug builds so that we exercise
     458             : // the GC machinery more often.
     459             : static const uint32_t kAtomGCThreshold = 20;
     460             : #else
     461             : static const uint32_t kAtomGCThreshold = 10000;
     462             : #endif
     463             : 
     464             : NS_IMETHODIMP_(MozExternalRefCountType)
     465       38841 : DynamicAtom::Release(void)
     466             : {
     467       38841 :   MOZ_ASSERT(mRefCnt > 0);
     468       38841 :   nsrefcnt count = --mRefCnt;
     469       38841 :   if (count == 0) {
     470         268 :     if (++gUnusedAtomCount >= kAtomGCThreshold) {
     471           4 :       GCAtomTable();
     472             :     }
     473             :   }
     474             : 
     475       38841 :   return count;
     476             : }
     477             : 
     478         160 : DynamicAtom::~DynamicAtom()
     479             : {
     480          80 :   nsStringBuffer::FromData(mString)->Release();
     481          80 : }
     482             : 
     483             : //----------------------------------------------------------------------
     484             : 
     485             : class StaticAtomEntry : public PLDHashEntryHdr
     486             : {
     487             : public:
     488             :   typedef const nsAString& KeyType;
     489             :   typedef const nsAString* KeyTypePointer;
     490             : 
     491        8013 :   explicit StaticAtomEntry(KeyTypePointer aKey) {}
     492             :   StaticAtomEntry(const StaticAtomEntry& aOther) : mAtom(aOther.mAtom) {}
     493             : 
     494             :   // We do not delete the atom because that's done when gAtomTable is
     495             :   // destroyed -- which happens immediately after gStaticAtomTable is destroyed
     496             :   // -- in NS_PurgeAtomTable().
     497           0 :   ~StaticAtomEntry() {}
     498             : 
     499         542 :   bool KeyEquals(KeyTypePointer aKey) const
     500             :   {
     501         542 :     return mAtom->Equals(*aKey);
     502             :   }
     503             : 
     504        8555 :   static KeyTypePointer KeyToPointer(KeyType aKey) { return &aKey; }
     505        8555 :   static PLDHashNumber HashKey(KeyTypePointer aKey)
     506             :   {
     507        8555 :     return HashString(*aKey);
     508             :   }
     509             : 
     510             :   enum { ALLOW_MEMMOVE = true };
     511             : 
     512             :   // StaticAtoms aren't really refcounted. Because these entries live in a
     513             :   // global hashtable, this reference is essentially owning.
     514             :   StaticAtom* MOZ_OWNING_REF mAtom;
     515             : };
     516             : 
     517             : /**
     518             :  * A hashtable of static atoms that existed at app startup. This hashtable
     519             :  * helps nsHtml5AtomTable.
     520             :  */
     521             : typedef nsTHashtable<StaticAtomEntry> StaticAtomTable;
     522             : static StaticAtomTable* gStaticAtomTable = nullptr;
     523             : 
     524             : /**
     525             :  * Whether it is still OK to add atoms to gStaticAtomTable.
     526             :  */
     527             : static bool gStaticAtomTableSealed = false;
     528             : 
     529             : // The atom table very quickly gets 10,000+ entries in it (or even 100,000+).
     530             : // But choosing the best initial length has some subtleties: we add ~2700
     531             : // static atoms to the table at start-up, and then we start adding and removing
     532             : // dynamic atoms. If we make the table too big to start with, when the first
     533             : // dynamic atom gets removed the load factor will be < 25% and so we will
     534             : // shrink it to 4096 entries.
     535             : //
     536             : // By choosing an initial length of 4096, we get an initial capacity of 8192.
     537             : // That's the biggest initial capacity that will let us be > 25% full when the
     538             : // first dynamic atom is removed (when the count is ~2700), thus avoiding any
     539             : // shrinking.
     540             : #define ATOM_HASHTABLE_INITIAL_LENGTH  4096
     541             : 
     542             : void
     543           3 : NS_InitAtomTable()
     544             : {
     545           3 :   MOZ_ASSERT(!gAtomTable);
     546           3 :   gAtomTable = new PLDHashTable(&AtomTableOps, sizeof(AtomTableEntry),
     547           3 :                                 ATOM_HASHTABLE_INITIAL_LENGTH);
     548           3 :   gAtomTableLock = new Mutex("Atom Table Lock");
     549             : 
     550             :   // Bug 1340710 has caused us to generate an empty atom at arbitrary times
     551             :   // after startup.  If we end up creating one before nsGkAtoms::_empty is
     552             :   // registered, we get an assertion about transmuting a dynamic atom into a
     553             :   // static atom.  In order to avoid that, we register an empty string static
     554             :   // atom as soon as we initialize the atom table to guarantee that the empty
     555             :   // string atom will always be static.
     556             :   NS_STATIC_ATOM_BUFFER(empty, "");
     557             :   static nsIAtom* empty_atom = nullptr;
     558             :   static const nsStaticAtom default_atoms[] = {
     559             :     NS_STATIC_ATOM(empty, &empty_atom)
     560             :   };
     561           3 :   NS_RegisterStaticAtoms(default_atoms);
     562           3 : }
     563             : 
     564             : void
     565           0 : NS_ShutdownAtomTable()
     566             : {
     567           0 :   delete gStaticAtomTable;
     568           0 :   gStaticAtomTable = nullptr;
     569             : 
     570             : #ifdef NS_FREE_PERMANENT_DATA
     571             :   // Do a final GC to satisfy leak checking. We skip this step in release
     572             :   // builds.
     573             :   {
     574           0 :     MutexAutoLock lock(*gAtomTableLock);
     575           0 :     DynamicAtom::GCAtomTableLocked(lock, DynamicAtom::GCKind::Shutdown);
     576             :   }
     577             : #endif
     578             : 
     579           0 :   delete gAtomTable;
     580           0 :   gAtomTable = nullptr;
     581           0 :   delete gAtomTableLock;
     582           0 :   gAtomTableLock = nullptr;
     583           0 : }
     584             : 
     585             : void
     586           0 : NS_SizeOfAtomTablesIncludingThis(MallocSizeOf aMallocSizeOf,
     587             :                                  size_t* aMain, size_t* aStatic)
     588             : {
     589           0 :   MutexAutoLock lock(*gAtomTableLock);
     590           0 :   *aMain = gAtomTable->ShallowSizeOfIncludingThis(aMallocSizeOf);
     591           0 :   for (auto iter = gAtomTable->Iter(); !iter.Done(); iter.Next()) {
     592           0 :     auto entry = static_cast<AtomTableEntry*>(iter.Get());
     593           0 :     *aMain += entry->mAtom->SizeOfIncludingThis(aMallocSizeOf);
     594             :   }
     595             : 
     596             :   // The atoms pointed to by gStaticAtomTable are also pointed to by gAtomTable,
     597             :   // and they're measured by the loop above. So no need to measure them here.
     598           0 :   *aStatic = gStaticAtomTable
     599           0 :            ? gStaticAtomTable->ShallowSizeOfIncludingThis(aMallocSizeOf)
     600           0 :            : 0;
     601           0 : }
     602             : 
     603             : static inline AtomTableEntry*
     604         844 : GetAtomHashEntry(const char* aString, uint32_t aLength, uint32_t* aHashOut)
     605             : {
     606         844 :   gAtomTableLock->AssertCurrentThreadOwns();
     607         844 :   AtomTableKey key(aString, aLength, aHashOut);
     608             :   // This is an infallible add.
     609         844 :   return static_cast<AtomTableEntry*>(gAtomTable->Add(&key));
     610             : }
     611             : 
     612             : static inline AtomTableEntry*
     613       38711 : GetAtomHashEntry(const char16_t* aString, uint32_t aLength, uint32_t* aHashOut)
     614             : {
     615       38711 :   gAtomTableLock->AssertCurrentThreadOwns();
     616       38711 :   AtomTableKey key(aString, aLength, aHashOut);
     617             :   // This is an infallible add.
     618       38711 :   return static_cast<AtomTableEntry*>(gAtomTable->Add(&key));
     619             : }
     620             : 
     621             : void
     622          27 : RegisterStaticAtoms(const nsStaticAtom* aAtoms, uint32_t aAtomCount)
     623             : {
     624          54 :   MutexAutoLock lock(*gAtomTableLock);
     625             : 
     626          27 :   MOZ_RELEASE_ASSERT(!gStaticAtomTableSealed,
     627             :                      "Atom table has already been sealed!");
     628             : 
     629          27 :   if (!gStaticAtomTable) {
     630           3 :     gStaticAtomTable = new StaticAtomTable();
     631             :   }
     632             : 
     633        8568 :   for (uint32_t i = 0; i < aAtomCount; ++i) {
     634        8541 :     nsStringBuffer* stringBuffer = aAtoms[i].mStringBuffer;
     635        8541 :     nsIAtom** atomp = aAtoms[i].mAtom;
     636             : 
     637        8541 :     MOZ_ASSERT(nsCRT::IsAscii(static_cast<char16_t*>(stringBuffer->Data())));
     638             : 
     639        8541 :     uint32_t stringLen = stringBuffer->StorageSize() / sizeof(char16_t) - 1;
     640             : 
     641             :     uint32_t hash;
     642             :     AtomTableEntry* he =
     643        8541 :       GetAtomHashEntry(static_cast<char16_t*>(stringBuffer->Data()),
     644        8541 :                        stringLen, &hash);
     645             : 
     646        8541 :     nsIAtom* atom = he->mAtom;
     647        8541 :     if (atom) {
     648             :       // Disallow creating a dynamic atom, and then later, while the
     649             :       // dynamic atom is still alive, registering that same atom as a
     650             :       // static atom.  It causes subtle bugs, and we're programming in
     651             :       // C++ here, not Smalltalk.
     652         528 :       if (!atom->IsStaticAtom()) {
     653           0 :         nsAutoCString name;
     654           0 :         atom->ToUTF8String(name);
     655           0 :         MOZ_CRASH_UNSAFE_PRINTF(
     656             :           "Static atom registration for %s should be pushed back", name.get());
     657             :       }
     658             :     } else {
     659        8013 :       atom = new StaticAtom(stringBuffer, stringLen, hash);
     660        8013 :       he->mAtom = atom;
     661             :     }
     662        8541 :     *atomp = atom;
     663             : 
     664        8541 :     if (!gStaticAtomTableSealed) {
     665             :       StaticAtomEntry* entry =
     666        8541 :         gStaticAtomTable->PutEntry(nsDependentAtomString(atom));
     667        8541 :       MOZ_ASSERT(atom->IsStaticAtom());
     668        8541 :       entry->mAtom = static_cast<StaticAtom*>(atom);
     669             :     }
     670             :   }
     671          27 : }
     672             : 
     673             : already_AddRefed<nsIAtom>
     674         145 : NS_Atomize(const char* aUTF8String)
     675             : {
     676         145 :   return NS_Atomize(nsDependentCString(aUTF8String));
     677             : }
     678             : 
     679             : already_AddRefed<nsIAtom>
     680         844 : NS_Atomize(const nsACString& aUTF8String)
     681             : {
     682        1688 :   MutexAutoLock lock(*gAtomTableLock);
     683             :   uint32_t hash;
     684         844 :   AtomTableEntry* he = GetAtomHashEntry(aUTF8String.Data(),
     685             :                                         aUTF8String.Length(),
     686         844 :                                         &hash);
     687             : 
     688         844 :   if (he->mAtom) {
     689        1200 :     nsCOMPtr<nsIAtom> atom = he->mAtom;
     690             : 
     691         600 :     return atom.forget();
     692             :   }
     693             : 
     694             :   // This results in an extra addref/release of the nsStringBuffer.
     695             :   // Unfortunately there doesn't seem to be any APIs to avoid that.
     696             :   // Actually, now there is, sort of: ForgetSharedBuffer.
     697         488 :   nsString str;
     698         244 :   CopyUTF8toUTF16(aUTF8String, str);
     699         488 :   RefPtr<DynamicAtom> atom = DynamicAtom::Create(str, hash);
     700             : 
     701         244 :   he->mAtom = atom;
     702             : 
     703         244 :   return atom.forget();
     704             : }
     705             : 
     706             : already_AddRefed<nsIAtom>
     707         428 : NS_Atomize(const char16_t* aUTF16String)
     708             : {
     709         428 :   return NS_Atomize(nsDependentString(aUTF16String));
     710             : }
     711             : 
     712             : already_AddRefed<nsIAtom>
     713       30170 : NS_Atomize(const nsAString& aUTF16String)
     714             : {
     715       60340 :   MutexAutoLock lock(*gAtomTableLock);
     716             :   uint32_t hash;
     717       30170 :   AtomTableEntry* he = GetAtomHashEntry(aUTF16String.Data(),
     718             :                                         aUTF16String.Length(),
     719       30170 :                                         &hash);
     720             : 
     721       30170 :   if (he->mAtom) {
     722       55848 :     nsCOMPtr<nsIAtom> atom = he->mAtom;
     723             : 
     724       27924 :     return atom.forget();
     725             :   }
     726             : 
     727        4492 :   RefPtr<DynamicAtom> atom = DynamicAtom::Create(aUTF16String, hash);
     728        2246 :   he->mAtom = atom;
     729             : 
     730        2246 :   return atom.forget();
     731             : }
     732             : 
     733             : already_AddRefed<nsIAtom>
     734        2209 : NS_AtomizeMainThread(const nsAString& aUTF16String)
     735             : {
     736        2209 :   MOZ_ASSERT(NS_IsMainThread());
     737        4418 :   nsCOMPtr<nsIAtom> retVal;
     738             :   uint32_t hash;
     739        2209 :   AtomTableKey key(aUTF16String.Data(), aUTF16String.Length(), &hash);
     740        2209 :   uint32_t index = hash % RECENTLY_USED_MAIN_THREAD_ATOM_CACHE_SIZE;
     741             :   nsIAtom* atom =
     742        2209 :     sRecentlyUsedMainThreadAtoms[index];
     743        2209 :   if (atom) {
     744        2068 :     uint32_t length = atom->GetLength();
     745        3305 :     if (length == key.mLength &&
     746        2474 :         (memcmp(atom->GetUTF16String(),
     747        1237 :                 key.mUTF16String, length * sizeof(char16_t)) == 0)) {
     748        1206 :       retVal = atom;
     749        1206 :       return retVal.forget();
     750             :     }
     751             :   }
     752             : 
     753        2006 :   MutexAutoLock lock(*gAtomTableLock);
     754        1003 :   AtomTableEntry* he = static_cast<AtomTableEntry*>(gAtomTable->Add(&key));
     755             : 
     756        1003 :   if (he->mAtom) {
     757         639 :     retVal = he->mAtom;
     758             :   } else {
     759         364 :     retVal = DynamicAtom::Create(aUTF16String, hash);
     760         364 :     he->mAtom = retVal;
     761             :   }
     762             : 
     763        1003 :   sRecentlyUsedMainThreadAtoms[index] = retVal;
     764        1003 :   return retVal.forget();
     765             : }
     766             : 
     767             : nsrefcnt
     768           0 : NS_GetNumberOfAtoms(void)
     769             : {
     770           0 :   DynamicAtom::GCAtomTable(); // Trigger a GC so that we return a deterministic result.
     771           0 :   MutexAutoLock lock(*gAtomTableLock);
     772           0 :   return gAtomTable->EntryCount();
     773             : }
     774             : 
     775             : nsIAtom*
     776          14 : NS_GetStaticAtom(const nsAString& aUTF16String)
     777             : {
     778          14 :   NS_PRECONDITION(gStaticAtomTable, "Static atom table not created yet.");
     779          14 :   NS_PRECONDITION(gStaticAtomTableSealed, "Static atom table not sealed yet.");
     780          14 :   StaticAtomEntry* entry = gStaticAtomTable->GetEntry(aUTF16String);
     781          14 :   return entry ? entry->mAtom : nullptr;
     782             : }
     783             : 
     784             : void
     785           3 : NS_SealStaticAtomTable()
     786             : {
     787           3 :   gStaticAtomTableSealed = true;
     788           3 : }

Generated by: LCOV version 1.13