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 : }
|