Bug 959405 - Please update the Buri Moz-central, 1.3, 1.2 with the latest blobs from...
[gecko.git] / xpcom / ds / nsAtomTable.cpp
blob3da66602f967e3650a78d79883b64f0395b41eaa
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 // vim:cindent:ts=2:et:sw=2:
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/. */
7 #include "mozilla/Assertions.h"
8 #include "mozilla/Attributes.h"
9 #include "mozilla/Compiler.h"
10 #include "mozilla/HashFunctions.h"
11 #include "mozilla/MemoryReporting.h"
12 #include "mozilla/DebugOnly.h"
14 #include "nsAtomTable.h"
15 #include "nsStaticAtom.h"
16 #include "nsString.h"
17 #include "nsCRT.h"
18 #include "pldhash.h"
19 #include "prenv.h"
20 #include "nsThreadUtils.h"
21 #include "nsDataHashtable.h"
22 #include "nsHashKeys.h"
23 #include "nsAutoPtr.h"
24 #include "nsUnicharUtils.h"
26 using namespace mozilla;
28 #if defined(__clang__)
29 # pragma GCC diagnostic ignored "-Wdelete-non-virtual-dtor"
30 #elif MOZ_IS_GCC
31 # if MOZ_GCC_VERSION_AT_LEAST(4, 7, 0)
32 # pragma GCC diagnostic ignored "-Wdelete-non-virtual-dtor"
33 # endif
34 #endif
36 /**
37 * The shared hash table for atom lookups.
39 * XXX This should be manipulated in a threadsafe way or we should make
40 * sure it's only manipulated from the main thread. Probably the latter
41 * is better, since the former would hurt performance.
43 * If |gAtomTable.ops| is 0, then the table is uninitialized.
45 static PLDHashTable gAtomTable;
47 /**
48 * A hashtable of static atoms that existed at app startup. This hashtable helps
49 * nsHtml5AtomTable.
51 static nsDataHashtable<nsStringHashKey, nsIAtom*>* gStaticAtomTable = 0;
53 /**
54 * Whether it is still OK to add atoms to gStaticAtomTable.
56 static bool gStaticAtomTableSealed = false;
58 //----------------------------------------------------------------------
60 /**
61 * Note that AtomImpl objects are sometimes converted into PermanentAtomImpl
62 * objects using placement new and just overwriting the vtable pointer.
65 class AtomImpl : public nsIAtom {
66 public:
67 AtomImpl(const nsAString& aString, uint32_t aHash);
69 // This is currently only used during startup when creating a permanent atom
70 // from NS_RegisterStaticAtoms
71 AtomImpl(nsStringBuffer* aData, uint32_t aLength, uint32_t aHash);
73 protected:
74 // This is only intended to be used when a normal atom is turned into a
75 // permanent one.
76 AtomImpl() {
77 // We can't really assert that mString is a valid nsStringBuffer string,
78 // so do the best we can do and check for some consistencies.
79 NS_ASSERTION((mLength + 1) * sizeof(char16_t) <=
80 nsStringBuffer::FromData(mString)->StorageSize() &&
81 mString[mLength] == 0,
82 "Not initialized atom");
85 // We don't need a virtual destructor here because PermanentAtomImpl
86 // deletions aren't handled through Release().
87 ~AtomImpl();
89 public:
90 NS_DECL_ISUPPORTS
91 NS_DECL_NSIATOM
93 enum { REFCNT_PERMANENT_SENTINEL = UINT32_MAX };
95 virtual bool IsPermanent();
97 // We can't use the virtual function in the base class destructor.
98 bool IsPermanentInDestructor() {
99 return mRefCnt == REFCNT_PERMANENT_SENTINEL;
102 // for |#ifdef NS_BUILD_REFCNT_LOGGING| access to reference count
103 nsrefcnt GetRefCount() { return mRefCnt; }
105 size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const;
109 * A non-refcounted implementation of nsIAtom.
112 class PermanentAtomImpl MOZ_FINAL : public AtomImpl {
113 public:
114 PermanentAtomImpl(const nsAString& aString, PLDHashNumber aKeyHash)
115 : AtomImpl(aString, aKeyHash)
117 PermanentAtomImpl(nsStringBuffer* aData, uint32_t aLength,
118 PLDHashNumber aKeyHash)
119 : AtomImpl(aData, aLength, aKeyHash)
121 PermanentAtomImpl()
124 ~PermanentAtomImpl();
125 NS_IMETHOD_(nsrefcnt) AddRef();
126 NS_IMETHOD_(nsrefcnt) Release();
128 virtual bool IsPermanent();
130 // SizeOfIncludingThis() isn't needed -- the one inherited from AtomImpl is
131 // good enough, because PermanentAtomImpl doesn't add any new data members.
133 void* operator new(size_t size, AtomImpl* aAtom) CPP_THROW_NEW;
134 void* operator new(size_t size) CPP_THROW_NEW
136 return ::operator new(size);
140 //----------------------------------------------------------------------
142 struct AtomTableEntry : public PLDHashEntryHdr {
143 AtomImpl* mAtom;
146 struct AtomTableKey
148 AtomTableKey(const char16_t* aUTF16String, uint32_t aLength,
149 /*inout*/ uint32_t& aHash)
150 : mUTF16String(aUTF16String),
151 mUTF8String(nullptr),
152 mLength(aLength)
154 if (aHash) {
155 MOZ_ASSERT(aHash == HashString(mUTF16String, mLength));
156 mHash = aHash;
157 } else {
158 UpdateHashKey();
159 aHash = mHash;
163 AtomTableKey(const char* aUTF8String, uint32_t aLength,
164 /*inout*/ uint32_t& aHash)
165 : mUTF16String(nullptr),
166 mUTF8String(aUTF8String),
167 mLength(aLength)
169 if (aHash) {
170 mozilla::DebugOnly<bool> err;
171 MOZ_ASSERT(aHash == HashUTF8AsUTF16(mUTF8String, mLength, &err));
172 mHash = aHash;
173 } else {
174 UpdateHashKey();
175 aHash = mHash;
179 const char16_t* mUTF16String;
180 const char* mUTF8String;
181 uint32_t mLength;
182 uint32_t mHash;
184 void UpdateHashKey()
186 if (mUTF8String) {
187 bool err;
188 mHash = HashUTF8AsUTF16(mUTF8String, mLength, &err);
189 if (err) {
190 mUTF8String = nullptr;
191 mLength = 0;
192 mHash = 0;
194 } else {
195 mHash = HashString(mUTF16String, mLength);
200 static PLDHashNumber
201 AtomTableGetHash(PLDHashTable *table, const void *key)
203 const AtomTableKey *k = static_cast<const AtomTableKey*>(key);
204 return k->mHash;
207 static bool
208 AtomTableMatchKey(PLDHashTable *table, const PLDHashEntryHdr *entry,
209 const void *key)
211 const AtomTableEntry *he = static_cast<const AtomTableEntry*>(entry);
212 const AtomTableKey *k = static_cast<const AtomTableKey*>(key);
214 if (k->mUTF8String) {
215 return
216 CompareUTF8toUTF16(nsDependentCSubstring(k->mUTF8String,
217 k->mUTF8String + k->mLength),
218 nsDependentAtomString(he->mAtom)) == 0;
221 uint32_t length = he->mAtom->GetLength();
222 if (length != k->mLength) {
223 return false;
226 return memcmp(he->mAtom->GetUTF16String(),
227 k->mUTF16String, length * sizeof(char16_t)) == 0;
230 static void
231 AtomTableClearEntry(PLDHashTable *table, PLDHashEntryHdr *entry)
233 // Normal |AtomImpl| atoms are deleted when their refcount hits 0, and
234 // they then remove themselves from the table. In other words, they
235 // are owned by the callers who own references to them.
236 // |PermanentAtomImpl| permanent atoms ignore their refcount and are
237 // deleted when they are removed from the table at table destruction.
238 // In other words, they are owned by the atom table.
240 AtomImpl *atom = static_cast<AtomTableEntry*>(entry)->mAtom;
241 if (atom->IsPermanent()) {
242 // Note that the cast here is important since AtomImpls doesn't have a
243 // virtual dtor.
244 delete static_cast<PermanentAtomImpl*>(atom);
248 static bool
249 AtomTableInitEntry(PLDHashTable *table, PLDHashEntryHdr *entry,
250 const void *key)
252 static_cast<AtomTableEntry*>(entry)->mAtom = nullptr;
254 return true;
258 static const PLDHashTableOps AtomTableOps = {
259 PL_DHashAllocTable,
260 PL_DHashFreeTable,
261 AtomTableGetHash,
262 AtomTableMatchKey,
263 PL_DHashMoveEntryStub,
264 AtomTableClearEntry,
265 PL_DHashFinalizeStub,
266 AtomTableInitEntry
270 #ifdef DEBUG
271 static PLDHashOperator
272 DumpAtomLeaks(PLDHashTable *table, PLDHashEntryHdr *he,
273 uint32_t index, void *arg)
275 AtomTableEntry *entry = static_cast<AtomTableEntry*>(he);
277 AtomImpl* atom = entry->mAtom;
278 if (!atom->IsPermanent()) {
279 ++*static_cast<uint32_t*>(arg);
280 nsAutoCString str;
281 atom->ToUTF8String(str);
282 fputs(str.get(), stdout);
283 fputs("\n", stdout);
285 return PL_DHASH_NEXT;
287 #endif
289 static inline
290 void PromoteToPermanent(AtomImpl* aAtom)
292 #ifdef NS_BUILD_REFCNT_LOGGING
294 nsrefcnt refcount = aAtom->GetRefCount();
295 do {
296 NS_LOG_RELEASE(aAtom, --refcount, "AtomImpl");
297 } while (refcount);
299 #endif
300 aAtom = new (aAtom) PermanentAtomImpl();
303 void
304 NS_PurgeAtomTable()
306 delete gStaticAtomTable;
308 if (gAtomTable.ops) {
309 #ifdef DEBUG
310 const char *dumpAtomLeaks = PR_GetEnv("MOZ_DUMP_ATOM_LEAKS");
311 if (dumpAtomLeaks && *dumpAtomLeaks) {
312 uint32_t leaked = 0;
313 printf("*** %d atoms still exist (including permanent):\n",
314 gAtomTable.entryCount);
315 PL_DHashTableEnumerate(&gAtomTable, DumpAtomLeaks, &leaked);
316 printf("*** %u non-permanent atoms leaked\n", leaked);
318 #endif
319 PL_DHashTableFinish(&gAtomTable);
320 gAtomTable.entryCount = 0;
321 gAtomTable.ops = nullptr;
325 AtomImpl::AtomImpl(const nsAString& aString, uint32_t aHash)
327 mLength = aString.Length();
328 nsRefPtr<nsStringBuffer> buf = nsStringBuffer::FromString(aString);
329 if (buf) {
330 mString = static_cast<char16_t*>(buf->Data());
331 } else {
332 buf = nsStringBuffer::Alloc((mLength + 1) * sizeof(char16_t));
333 mString = static_cast<char16_t*>(buf->Data());
334 CopyUnicodeTo(aString, 0, mString, mLength);
335 mString[mLength] = char16_t(0);
338 mHash = aHash;
339 MOZ_ASSERT(mHash == HashString(mString, mLength));
341 NS_ASSERTION(mString[mLength] == char16_t(0), "null terminated");
342 NS_ASSERTION(buf && buf->StorageSize() >= (mLength+1) * sizeof(char16_t),
343 "enough storage");
344 NS_ASSERTION(Equals(aString), "correct data");
346 // Take ownership of buffer
347 buf.forget();
350 AtomImpl::AtomImpl(nsStringBuffer* aStringBuffer, uint32_t aLength,
351 uint32_t aHash)
353 mLength = aLength;
354 mString = static_cast<char16_t*>(aStringBuffer->Data());
355 // Technically we could currently avoid doing this addref by instead making
356 // the static atom buffers have an initial refcount of 2.
357 aStringBuffer->AddRef();
359 mHash = aHash;
360 MOZ_ASSERT(mHash == HashString(mString, mLength));
362 NS_ASSERTION(mString[mLength] == char16_t(0), "null terminated");
363 NS_ASSERTION(aStringBuffer &&
364 aStringBuffer->StorageSize() == (mLength+1) * sizeof(char16_t),
365 "correct storage");
368 AtomImpl::~AtomImpl()
370 NS_PRECONDITION(gAtomTable.ops, "uninitialized atom hashtable");
371 // Permanent atoms are removed from the hashtable at shutdown, and we
372 // don't want to remove them twice. See comment above in
373 // |AtomTableClearEntry|.
374 if (!IsPermanentInDestructor()) {
375 AtomTableKey key(mString, mLength, mHash);
376 PL_DHashTableOperate(&gAtomTable, &key, PL_DHASH_REMOVE);
377 if (gAtomTable.entryCount == 0) {
378 PL_DHashTableFinish(&gAtomTable);
379 NS_ASSERTION(gAtomTable.entryCount == 0,
380 "PL_DHashTableFinish changed the entry count");
384 nsStringBuffer::FromData(mString)->Release();
387 NS_IMPL_ISUPPORTS1(AtomImpl, nsIAtom)
389 PermanentAtomImpl::~PermanentAtomImpl()
391 // So we can tell if we were permanent while running the base class dtor.
392 mRefCnt = REFCNT_PERMANENT_SENTINEL;
395 NS_IMETHODIMP_(nsrefcnt) PermanentAtomImpl::AddRef()
397 MOZ_ASSERT(NS_IsMainThread(), "wrong thread");
398 return 2;
401 NS_IMETHODIMP_(nsrefcnt) PermanentAtomImpl::Release()
403 MOZ_ASSERT(NS_IsMainThread(), "wrong thread");
404 return 1;
407 /* virtual */ bool
408 AtomImpl::IsPermanent()
410 return false;
413 /* virtual */ bool
414 PermanentAtomImpl::IsPermanent()
416 return true;
419 void* PermanentAtomImpl::operator new ( size_t size, AtomImpl* aAtom ) CPP_THROW_NEW {
420 MOZ_ASSERT(!aAtom->IsPermanent(),
421 "converting atom that's already permanent");
423 // Just let the constructor overwrite the vtable pointer.
424 return aAtom;
427 NS_IMETHODIMP
428 AtomImpl::ScriptableToString(nsAString& aBuf)
430 nsStringBuffer::FromData(mString)->ToString(mLength, aBuf);
431 return NS_OK;
434 NS_IMETHODIMP
435 AtomImpl::ToUTF8String(nsACString& aBuf)
437 CopyUTF16toUTF8(nsDependentString(mString, mLength), aBuf);
438 return NS_OK;
441 NS_IMETHODIMP_(bool)
442 AtomImpl::EqualsUTF8(const nsACString& aString)
444 return CompareUTF8toUTF16(aString,
445 nsDependentString(mString, mLength)) == 0;
448 NS_IMETHODIMP
449 AtomImpl::ScriptableEquals(const nsAString& aString, bool* aResult)
451 *aResult = aString.Equals(nsDependentString(mString, mLength));
452 return NS_OK;
455 NS_IMETHODIMP_(bool)
456 AtomImpl::IsStaticAtom()
458 return IsPermanent();
461 size_t
462 AtomImpl::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
464 return aMallocSizeOf(this) +
465 nsStringBuffer::FromData(mString)->
466 SizeOfIncludingThisIfUnshared(aMallocSizeOf);
469 //----------------------------------------------------------------------
471 static size_t
472 SizeOfAtomTableEntryExcludingThis(PLDHashEntryHdr *aHdr,
473 MallocSizeOf aMallocSizeOf,
474 void *aArg)
476 AtomTableEntry* entry = static_cast<AtomTableEntry*>(aHdr);
477 return entry->mAtom->SizeOfIncludingThis(aMallocSizeOf);
480 static size_t
481 SizeOfStaticAtomTableEntryExcludingThis(const nsAString& aKey,
482 nsIAtom* const& aData,
483 MallocSizeOf aMallocSizeOf,
484 void* aArg)
486 return aKey.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
489 size_t
490 NS_SizeOfAtomTablesIncludingThis(MallocSizeOf aMallocSizeOf) {
491 size_t n = 0;
492 if (gAtomTable.ops) {
493 n += PL_DHashTableSizeOfExcludingThis(&gAtomTable,
494 SizeOfAtomTableEntryExcludingThis,
495 aMallocSizeOf);
497 if (gStaticAtomTable) {
498 n += gStaticAtomTable->SizeOfIncludingThis(SizeOfStaticAtomTableEntryExcludingThis,
499 aMallocSizeOf);
501 return n;
504 #define ATOM_HASHTABLE_INITIAL_SIZE 4096
506 static inline void
507 EnsureTableExists()
509 if (!gAtomTable.ops &&
510 !PL_DHashTableInit(&gAtomTable, &AtomTableOps, 0,
511 sizeof(AtomTableEntry), ATOM_HASHTABLE_INITIAL_SIZE)) {
512 // Initialization failed.
513 NS_ABORT_OOM(ATOM_HASHTABLE_INITIAL_SIZE * sizeof(AtomTableEntry));
517 static inline AtomTableEntry*
518 GetAtomHashEntry(const char* aString, uint32_t aLength, uint32_t& aHash)
520 MOZ_ASSERT(NS_IsMainThread(), "wrong thread");
521 EnsureTableExists();
522 AtomTableKey key(aString, aLength, aHash);
523 AtomTableEntry* e =
524 static_cast<AtomTableEntry*>
525 (PL_DHashTableOperate(&gAtomTable, &key, PL_DHASH_ADD));
526 if (!e) {
527 NS_ABORT_OOM(gAtomTable.entryCount * gAtomTable.entrySize);
529 return e;
532 static inline AtomTableEntry*
533 GetAtomHashEntry(const char16_t* aString, uint32_t aLength, uint32_t& aHash)
535 MOZ_ASSERT(NS_IsMainThread(), "wrong thread");
536 EnsureTableExists();
537 AtomTableKey key(aString, aLength, aHash);
538 AtomTableEntry* e =
539 static_cast<AtomTableEntry*>
540 (PL_DHashTableOperate(&gAtomTable, &key, PL_DHASH_ADD));
541 if (!e) {
542 NS_ABORT_OOM(gAtomTable.entryCount * gAtomTable.entrySize);
544 return e;
547 class CheckStaticAtomSizes
549 CheckStaticAtomSizes() {
550 static_assert((sizeof(nsFakeStringBuffer<1>().mRefCnt) ==
551 sizeof(nsStringBuffer().mRefCount)) &&
552 (sizeof(nsFakeStringBuffer<1>().mSize) ==
553 sizeof(nsStringBuffer().mStorageSize)) &&
554 (offsetof(nsFakeStringBuffer<1>, mRefCnt) ==
555 offsetof(nsStringBuffer, mRefCount)) &&
556 (offsetof(nsFakeStringBuffer<1>, mSize) ==
557 offsetof(nsStringBuffer, mStorageSize)) &&
558 (offsetof(nsFakeStringBuffer<1>, mStringData) ==
559 sizeof(nsStringBuffer)),
560 "mocked-up strings' representations should be compatible");
564 nsresult
565 RegisterStaticAtoms(const nsStaticAtom* aAtoms, uint32_t aAtomCount)
567 // this does three things:
568 // 1) wraps each static atom in a wrapper, if necessary
569 // 2) initializes the address pointed to by each mBits slot
570 // 3) puts the atom into the static atom table as well
572 if (!gStaticAtomTable && !gStaticAtomTableSealed) {
573 gStaticAtomTable = new nsDataHashtable<nsStringHashKey, nsIAtom*>();
576 for (uint32_t i=0; i<aAtomCount; i++) {
577 NS_ASSERTION(nsCRT::IsAscii((char16_t*)aAtoms[i].mStringBuffer->Data()),
578 "Static atoms must be ASCII!");
580 uint32_t stringLen =
581 aAtoms[i].mStringBuffer->StorageSize() / sizeof(char16_t) - 1;
583 uint32_t hash = 0;
584 AtomTableEntry *he =
585 GetAtomHashEntry((char16_t*)aAtoms[i].mStringBuffer->Data(),
586 stringLen, hash);
588 if (he->mAtom) {
589 // there already is an atom with this name in the table.. but we
590 // still have to update mBits
591 if (!he->mAtom->IsPermanent()) {
592 // since we wanted to create a static atom but there is
593 // already one there, we convert it to a non-refcounting
594 // permanent atom
595 PromoteToPermanent(he->mAtom);
598 *aAtoms[i].mAtom = he->mAtom;
600 else {
601 AtomImpl* atom = new PermanentAtomImpl(aAtoms[i].mStringBuffer,
602 stringLen,
603 hash);
604 he->mAtom = atom;
605 *aAtoms[i].mAtom = atom;
607 if (!gStaticAtomTableSealed) {
608 gStaticAtomTable->Put(nsAtomString(atom), atom);
612 return NS_OK;
615 already_AddRefed<nsIAtom>
616 NS_NewAtom(const char* aUTF8String)
618 return NS_NewAtom(nsDependentCString(aUTF8String));
621 already_AddRefed<nsIAtom>
622 NS_NewAtom(const nsACString& aUTF8String)
624 uint32_t hash = 0;
625 AtomTableEntry *he = GetAtomHashEntry(aUTF8String.Data(),
626 aUTF8String.Length(),
627 hash);
629 if (he->mAtom) {
630 nsCOMPtr<nsIAtom> atom = he->mAtom;
632 return atom.forget();
635 // This results in an extra addref/release of the nsStringBuffer.
636 // Unfortunately there doesn't seem to be any APIs to avoid that.
637 // Actually, now there is, sort of: ForgetSharedBuffer.
638 nsString str;
639 CopyUTF8toUTF16(aUTF8String, str);
640 nsRefPtr<AtomImpl> atom = new AtomImpl(str, hash);
642 he->mAtom = atom;
644 return atom.forget();
647 already_AddRefed<nsIAtom>
648 NS_NewAtom(const char16_t* aUTF16String)
650 return NS_NewAtom(nsDependentString(aUTF16String));
653 already_AddRefed<nsIAtom>
654 NS_NewAtom(const nsAString& aUTF16String)
656 uint32_t hash = 0;
657 AtomTableEntry *he = GetAtomHashEntry(aUTF16String.Data(),
658 aUTF16String.Length(),
659 hash);
661 if (he->mAtom) {
662 nsCOMPtr<nsIAtom> atom = he->mAtom;
664 return atom.forget();
667 nsRefPtr<AtomImpl> atom = new AtomImpl(aUTF16String, hash);
668 he->mAtom = atom;
670 return atom.forget();
673 nsIAtom*
674 NS_NewPermanentAtom(const nsAString& aUTF16String)
676 uint32_t hash = 0;
677 AtomTableEntry *he = GetAtomHashEntry(aUTF16String.Data(),
678 aUTF16String.Length(),
679 hash);
681 AtomImpl* atom = he->mAtom;
682 if (atom) {
683 if (!atom->IsPermanent()) {
684 PromoteToPermanent(atom);
687 else {
688 atom = new PermanentAtomImpl(aUTF16String, hash);
689 he->mAtom = atom;
692 // No need to addref since permanent atoms aren't refcounted anyway
693 return atom;
696 nsrefcnt
697 NS_GetNumberOfAtoms(void)
699 return gAtomTable.entryCount;
702 nsIAtom*
703 NS_GetStaticAtom(const nsAString& aUTF16String)
705 NS_PRECONDITION(gStaticAtomTable, "Static atom table not created yet.");
706 NS_PRECONDITION(gStaticAtomTableSealed, "Static atom table not sealed yet.");
707 nsIAtom* atom;
708 if (!gStaticAtomTable->Get(aUTF16String, &atom)) {
709 atom = nullptr;
711 return atom;
714 void
715 NS_SealStaticAtomTable()
717 gStaticAtomTableSealed = true;