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"
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"
31 # if MOZ_GCC_VERSION_AT_LEAST(4, 7, 0)
32 # pragma GCC diagnostic ignored "-Wdelete-non-virtual-dtor"
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
;
48 * A hashtable of static atoms that existed at app startup. This hashtable helps
51 static nsDataHashtable
<nsStringHashKey
, nsIAtom
*>* gStaticAtomTable
= 0;
54 * Whether it is still OK to add atoms to gStaticAtomTable.
56 static bool gStaticAtomTableSealed
= false;
58 //----------------------------------------------------------------------
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
{
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
);
74 // This is only intended to be used when a normal atom is turned into a
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().
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
{
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
)
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
{
148 AtomTableKey(const char16_t
* aUTF16String
, uint32_t aLength
,
149 /*inout*/ uint32_t& aHash
)
150 : mUTF16String(aUTF16String
),
151 mUTF8String(nullptr),
155 MOZ_ASSERT(aHash
== HashString(mUTF16String
, mLength
));
163 AtomTableKey(const char* aUTF8String
, uint32_t aLength
,
164 /*inout*/ uint32_t& aHash
)
165 : mUTF16String(nullptr),
166 mUTF8String(aUTF8String
),
170 mozilla::DebugOnly
<bool> err
;
171 MOZ_ASSERT(aHash
== HashUTF8AsUTF16(mUTF8String
, mLength
, &err
));
179 const char16_t
* mUTF16String
;
180 const char* mUTF8String
;
188 mHash
= HashUTF8AsUTF16(mUTF8String
, mLength
, &err
);
190 mUTF8String
= nullptr;
195 mHash
= HashString(mUTF16String
, mLength
);
201 AtomTableGetHash(PLDHashTable
*table
, const void *key
)
203 const AtomTableKey
*k
= static_cast<const AtomTableKey
*>(key
);
208 AtomTableMatchKey(PLDHashTable
*table
, const PLDHashEntryHdr
*entry
,
211 const AtomTableEntry
*he
= static_cast<const AtomTableEntry
*>(entry
);
212 const AtomTableKey
*k
= static_cast<const AtomTableKey
*>(key
);
214 if (k
->mUTF8String
) {
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
) {
226 return memcmp(he
->mAtom
->GetUTF16String(),
227 k
->mUTF16String
, length
* sizeof(char16_t
)) == 0;
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
244 delete static_cast<PermanentAtomImpl
*>(atom
);
249 AtomTableInitEntry(PLDHashTable
*table
, PLDHashEntryHdr
*entry
,
252 static_cast<AtomTableEntry
*>(entry
)->mAtom
= nullptr;
258 static const PLDHashTableOps AtomTableOps
= {
263 PL_DHashMoveEntryStub
,
265 PL_DHashFinalizeStub
,
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
);
281 atom
->ToUTF8String(str
);
282 fputs(str
.get(), stdout
);
285 return PL_DHASH_NEXT
;
290 void PromoteToPermanent(AtomImpl
* aAtom
)
292 #ifdef NS_BUILD_REFCNT_LOGGING
294 nsrefcnt refcount
= aAtom
->GetRefCount();
296 NS_LOG_RELEASE(aAtom
, --refcount
, "AtomImpl");
300 aAtom
= new (aAtom
) PermanentAtomImpl();
306 delete gStaticAtomTable
;
308 if (gAtomTable
.ops
) {
310 const char *dumpAtomLeaks
= PR_GetEnv("MOZ_DUMP_ATOM_LEAKS");
311 if (dumpAtomLeaks
&& *dumpAtomLeaks
) {
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
);
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
);
330 mString
= static_cast<char16_t
*>(buf
->Data());
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);
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
),
344 NS_ASSERTION(Equals(aString
), "correct data");
346 // Take ownership of buffer
350 AtomImpl::AtomImpl(nsStringBuffer
* aStringBuffer
, uint32_t 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();
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
),
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");
401 NS_IMETHODIMP_(nsrefcnt
) PermanentAtomImpl::Release()
403 MOZ_ASSERT(NS_IsMainThread(), "wrong thread");
408 AtomImpl::IsPermanent()
414 PermanentAtomImpl::IsPermanent()
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.
428 AtomImpl::ScriptableToString(nsAString
& aBuf
)
430 nsStringBuffer::FromData(mString
)->ToString(mLength
, aBuf
);
435 AtomImpl::ToUTF8String(nsACString
& aBuf
)
437 CopyUTF16toUTF8(nsDependentString(mString
, mLength
), aBuf
);
442 AtomImpl::EqualsUTF8(const nsACString
& aString
)
444 return CompareUTF8toUTF16(aString
,
445 nsDependentString(mString
, mLength
)) == 0;
449 AtomImpl::ScriptableEquals(const nsAString
& aString
, bool* aResult
)
451 *aResult
= aString
.Equals(nsDependentString(mString
, mLength
));
456 AtomImpl::IsStaticAtom()
458 return IsPermanent();
462 AtomImpl::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf
) const
464 return aMallocSizeOf(this) +
465 nsStringBuffer::FromData(mString
)->
466 SizeOfIncludingThisIfUnshared(aMallocSizeOf
);
469 //----------------------------------------------------------------------
472 SizeOfAtomTableEntryExcludingThis(PLDHashEntryHdr
*aHdr
,
473 MallocSizeOf aMallocSizeOf
,
476 AtomTableEntry
* entry
= static_cast<AtomTableEntry
*>(aHdr
);
477 return entry
->mAtom
->SizeOfIncludingThis(aMallocSizeOf
);
481 SizeOfStaticAtomTableEntryExcludingThis(const nsAString
& aKey
,
482 nsIAtom
* const& aData
,
483 MallocSizeOf aMallocSizeOf
,
486 return aKey
.SizeOfExcludingThisIfUnshared(aMallocSizeOf
);
490 NS_SizeOfAtomTablesIncludingThis(MallocSizeOf aMallocSizeOf
) {
492 if (gAtomTable
.ops
) {
493 n
+= PL_DHashTableSizeOfExcludingThis(&gAtomTable
,
494 SizeOfAtomTableEntryExcludingThis
,
497 if (gStaticAtomTable
) {
498 n
+= gStaticAtomTable
->SizeOfIncludingThis(SizeOfStaticAtomTableEntryExcludingThis
,
504 #define ATOM_HASHTABLE_INITIAL_SIZE 4096
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");
522 AtomTableKey
key(aString
, aLength
, aHash
);
524 static_cast<AtomTableEntry
*>
525 (PL_DHashTableOperate(&gAtomTable
, &key
, PL_DHASH_ADD
));
527 NS_ABORT_OOM(gAtomTable
.entryCount
* gAtomTable
.entrySize
);
532 static inline AtomTableEntry
*
533 GetAtomHashEntry(const char16_t
* aString
, uint32_t aLength
, uint32_t& aHash
)
535 MOZ_ASSERT(NS_IsMainThread(), "wrong thread");
537 AtomTableKey
key(aString
, aLength
, aHash
);
539 static_cast<AtomTableEntry
*>
540 (PL_DHashTableOperate(&gAtomTable
, &key
, PL_DHASH_ADD
));
542 NS_ABORT_OOM(gAtomTable
.entryCount
* gAtomTable
.entrySize
);
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");
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!");
581 aAtoms
[i
].mStringBuffer
->StorageSize() / sizeof(char16_t
) - 1;
585 GetAtomHashEntry((char16_t
*)aAtoms
[i
].mStringBuffer
->Data(),
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
595 PromoteToPermanent(he
->mAtom
);
598 *aAtoms
[i
].mAtom
= he
->mAtom
;
601 AtomImpl
* atom
= new PermanentAtomImpl(aAtoms
[i
].mStringBuffer
,
605 *aAtoms
[i
].mAtom
= atom
;
607 if (!gStaticAtomTableSealed
) {
608 gStaticAtomTable
->Put(nsAtomString(atom
), atom
);
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
)
625 AtomTableEntry
*he
= GetAtomHashEntry(aUTF8String
.Data(),
626 aUTF8String
.Length(),
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.
639 CopyUTF8toUTF16(aUTF8String
, str
);
640 nsRefPtr
<AtomImpl
> atom
= new AtomImpl(str
, hash
);
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
)
657 AtomTableEntry
*he
= GetAtomHashEntry(aUTF16String
.Data(),
658 aUTF16String
.Length(),
662 nsCOMPtr
<nsIAtom
> atom
= he
->mAtom
;
664 return atom
.forget();
667 nsRefPtr
<AtomImpl
> atom
= new AtomImpl(aUTF16String
, hash
);
670 return atom
.forget();
674 NS_NewPermanentAtom(const nsAString
& aUTF16String
)
677 AtomTableEntry
*he
= GetAtomHashEntry(aUTF16String
.Data(),
678 aUTF16String
.Length(),
681 AtomImpl
* atom
= he
->mAtom
;
683 if (!atom
->IsPermanent()) {
684 PromoteToPermanent(atom
);
688 atom
= new PermanentAtomImpl(aUTF16String
, hash
);
692 // No need to addref since permanent atoms aren't refcounted anyway
697 NS_GetNumberOfAtoms(void)
699 return gAtomTable
.entryCount
;
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.");
708 if (!gStaticAtomTable
->Get(aUTF16String
, &atom
)) {
715 NS_SealStaticAtomTable()
717 gStaticAtomTableSealed
= true;