/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ // vim:cindent:ts=2:et:sw=2: /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is mozilla.org code. * * The Initial Developer of the Original Code is * Netscape Communications Corporation. * Portions created by the Initial Developer are Copyright (C) 1998 * the Initial Developer. All Rights Reserved. * * Contributor(s): * * Alternatively, the contents of this file may be used under the terms of * either of the GNU General Public License Version 2 or later (the "GPL"), * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ #include "nsAtomTable.h" #include "nsStaticAtom.h" #include "nsString.h" #include "nsReadableUtils.h" #include "nsUTF8Utils.h" #include "nsCRT.h" #include "pldhash.h" #include "prenv.h" #include "nsThreadUtils.h" #include "nsDataHashtable.h" #include "nsHashKeys.h" #include "nsAutoPtr.h" #define PL_ARENA_CONST_ALIGN_MASK 3 #include "plarena.h" /** * The shared hash table for atom lookups. * * XXX This should be manipulated in a threadsafe way or we should make * sure it's only manipulated from the main thread. Probably the latter * is better, since the former would hurt performance. * * If |gAtomTable.ops| is 0, then the table is uninitialized. */ static PLDHashTable gAtomTable; /** * A hashtable of static atoms that existed at app startup. This hashtable helps * nsHtml5AtomTable. */ static nsDataHashtable* gStaticAtomTable = 0; /** * Whether it is still OK to add atoms to gStaticAtomTable. */ static PRBool gStaticAtomTableSealed = PR_FALSE; // The |key| pointer in the various PLDHashTable callbacks we use is an // AtomTableClearEntry*. These pointers can come from two places: either a // (probably stack-allocated) string key being passed to PL_DHashTableOperate, // or an actual entry in the atom table. PLDHashTable reseves the keyHash // values 0 and 1 for internal use, which means that the *PLDHashTable code* // will never pass an entry whose keyhash is 0 or 1 to our hooks. That means we // can use those values to tell whether an AtomTableEntry is a string key // created by a PLDHashTable code caller or an actual live AtomTableEntry used // by our PLDHashTable. // // Evil? Yes, but kinda neat too :-) // // An AtomTableEntry is a UTF-8 string key if keyHash is 0, in that // case mBits points to a UTF-8 encoded char *. If keyHash is 1 the // AtomTableEntry is a UTF-16 encoded string key and mBits points to a // UTF-16 encoded PRUnichar *. // // If keyHash is any other value (> 1), the AtomTableEntry is an // actual live entry in the table, and then mBits & ~0x1 in the // AtomTableEntry points to an AtomImpl or a nsStaticAtomWrapper, // indicated by the first bit of PtrBits. // XXX This whole mess could be vastly simplified now that pldhash // no longer has a getKey callback. typedef PRUword PtrBits; struct AtomTableEntry : public PLDHashEntryHdr { // If keyHash > 1, mBits & 0x1 means (mBits & ~0x1) points to an // nsStaticAtomWrapper else it points to an nsAtomImpl PtrBits mBits; PRUint32 mLength; inline AtomTableEntry(const char *aString, PRUint32 aLength) : mBits(PtrBits(aString)), mLength(aLength) { keyHash = 0; } inline AtomTableEntry(const PRUnichar *aString, PRUint32 aLength) : mBits(PtrBits(aString)), mLength(aLength) { keyHash = 1; } inline PRBool IsUTF8String() const { return keyHash == 0; } inline PRBool IsUTF16String() const { return keyHash == 1; } inline void SetAtomImpl(AtomImpl* aAtom) { NS_ASSERTION(keyHash > 1, "SetAtomImpl() called on non-atom AtomTableEntry!"); NS_ASSERTION(aAtom, "Setting null atom"); mBits = PtrBits(aAtom); mLength = aAtom->GetLength(); } inline void ClearAtom() { mBits = nsnull; } inline PRBool HasValue() const { NS_ASSERTION(keyHash > 1, "HasValue() called on non-atom AtomTableEntry!"); return (mBits & ~0x1) != 0; } // these accessors assume that you already know the type inline AtomImpl *GetAtomImpl() const { NS_ASSERTION(keyHash > 1, "GetAtomImpl() called on non-atom AtomTableEntry!"); return (AtomImpl*) (mBits & ~0x1); } // type-agnostic accessors // get the string buffer inline const PRUnichar* getAtomString() const { NS_ASSERTION(keyHash > 1, "getAtomString() called on non-atom AtomTableEntry!"); return GetAtomImpl()->GetUTF16String(); } // get the string buffer inline const char* getUTF8String() const { NS_ASSERTION(keyHash == 0, "getUTF8String() called on non-UTF8 AtomTableEntry!"); return (char *)mBits; } // get the string buffer inline const PRUnichar* getUTF16String() const { NS_ASSERTION(keyHash == 1, "getUTF16String() called on non-UTF16 AtomTableEntry!"); return (PRUnichar *)mBits; } // get the string length inline PRUint32 getLength() const { return mLength; } // get an addreffed nsIAtom - not using already_AddRef'ed atom // because the callers are not (and should not be) using nsCOMPtr inline nsIAtom* GetAtom() const { NS_ASSERTION(keyHash > 1, "GetAtom() called on non-atom AtomTableEntry!"); nsIAtom* result; NS_ADDREF(result = GetAtomImpl()); return result; } }; static PLDHashNumber AtomTableGetHash(PLDHashTable *table, const void *key) { const AtomTableEntry *e = static_cast(key); if (e->IsUTF8String()) { return nsCRT::HashCodeAsUTF16(e->getUTF8String(), e->getLength());; } NS_ASSERTION(e->IsUTF16String(), "AtomTableGetHash() called on non-string-key AtomTableEntry!"); return nsCRT::HashCode(e->getUTF16String(), e->getLength()); } static PRBool AtomTableMatchKey(PLDHashTable *table, const PLDHashEntryHdr *entry, const void *key) { const AtomTableEntry *he = static_cast(entry); const AtomTableEntry *strKey = static_cast(key); const PRUnichar *atomString = he->getAtomString(); if (strKey->IsUTF8String()) { return CompareUTF8toUTF16(nsDependentCSubstring(strKey->getUTF8String(), strKey->getUTF8String() + strKey->getLength()), nsDependentSubstring(atomString, atomString + he->getLength())) == 0; } PRUint32 length = he->getLength(); if (length != strKey->getLength()) { return PR_FALSE; } const PRUnichar *str; if (strKey->IsUTF16String()) { str = strKey->getUTF16String(); } else { str = strKey->getAtomString(); } return memcmp(atomString, str, length * sizeof(PRUnichar)) == 0; } static void AtomTableClearEntry(PLDHashTable *table, PLDHashEntryHdr *entry) { AtomTableEntry *he = static_cast(entry); AtomImpl *atom = he->GetAtomImpl(); // Normal |AtomImpl| atoms are deleted when their refcount hits 0, and // they then remove themselves from the table. In other words, they // are owned by the callers who own references to them. // |PermanentAtomImpl| permanent atoms ignore their refcount and are // deleted when they are removed from the table at table destruction. // In other words, they are owned by the atom table. if (atom->IsPermanent()) { he->keyHash = 0; delete static_cast(atom); } he->ClearAtom(); } static PRBool AtomTableInitEntry(PLDHashTable *table, PLDHashEntryHdr *entry, const void *key) { AtomTableEntry *he = static_cast(entry); const AtomTableEntry *strKey = static_cast(key); he->mLength = strKey->getLength(); return PR_TRUE; } static const PLDHashTableOps AtomTableOps = { PL_DHashAllocTable, PL_DHashFreeTable, AtomTableGetHash, AtomTableMatchKey, PL_DHashMoveEntryStub, AtomTableClearEntry, PL_DHashFinalizeStub, AtomTableInitEntry }; #ifdef DEBUG static PLDHashOperator DumpAtomLeaks(PLDHashTable *table, PLDHashEntryHdr *he, PRUint32 index, void *arg) { AtomTableEntry *entry = static_cast(he); AtomImpl* atom = entry->GetAtomImpl(); if (!atom->IsPermanent()) { ++*static_cast(arg); nsCAutoString str; atom->ToUTF8String(str); fputs(str.get(), stdout); fputs("\n", stdout); } return PL_DHASH_NEXT; } #endif static inline void PromoteToPermanent(AtomImpl* aAtom) { #ifdef NS_BUILD_REFCNT_LOGGING { nsrefcnt refcount = aAtom->GetRefCount(); do { NS_LOG_RELEASE(aAtom, --refcount, "AtomImpl"); } while (refcount); } #endif aAtom = new (aAtom) PermanentAtomImpl(); } void NS_PurgeAtomTable() { delete gStaticAtomTable; if (gAtomTable.ops) { #ifdef DEBUG const char *dumpAtomLeaks = PR_GetEnv("MOZ_DUMP_ATOM_LEAKS"); if (dumpAtomLeaks && *dumpAtomLeaks) { PRUint32 leaked = 0; printf("*** %d atoms still exist (including permanent):\n", gAtomTable.entryCount); PL_DHashTableEnumerate(&gAtomTable, DumpAtomLeaks, &leaked); printf("*** %u non-permanent atoms leaked\n", leaked); } #endif PL_DHashTableFinish(&gAtomTable); gAtomTable.entryCount = 0; gAtomTable.ops = nsnull; } } AtomImpl::AtomImpl(const nsAString& aString) { mLength = aString.Length(); nsStringBuffer* buf = nsStringBuffer::FromString(aString); if (buf) { buf->AddRef(); mString = static_cast(buf->Data()); } else { buf = nsStringBuffer::Alloc((mLength + 1) * sizeof(PRUnichar)); mString = static_cast(buf->Data()); CopyUnicodeTo(aString, 0, mString, mLength); mString[mLength] = PRUnichar(0); } NS_ASSERTION(mString[mLength] == PRUnichar(0), "null terminated"); NS_ASSERTION(buf && buf->StorageSize() >= (mLength+1) * sizeof(PRUnichar), "enough storage"); NS_ASSERTION(Equals(aString), "correct data"); } AtomImpl::AtomImpl(nsStringBuffer* aStringBuffer, PRUint32 aLength) { mLength = aLength; mString = static_cast(aStringBuffer->Data()); // Technically we could currently avoid doing this addref by instead making // the static atom buffers have an initial refcount of 2. aStringBuffer->AddRef(); NS_ASSERTION(mString[mLength] == PRUnichar(0), "null terminated"); NS_ASSERTION(aStringBuffer && aStringBuffer->StorageSize() == (mLength+1) * 2, "correct storage"); } AtomImpl::~AtomImpl() { NS_PRECONDITION(gAtomTable.ops, "uninitialized atom hashtable"); // Permanent atoms are removed from the hashtable at shutdown, and we // don't want to remove them twice. See comment above in // |AtomTableClearEntry|. if (!IsPermanentInDestructor()) { AtomTableEntry key(mString, mLength); PL_DHashTableOperate(&gAtomTable, &key, PL_DHASH_REMOVE); if (gAtomTable.entryCount == 0) { PL_DHashTableFinish(&gAtomTable); NS_ASSERTION(gAtomTable.entryCount == 0, "PL_DHashTableFinish changed the entry count"); } } nsStringBuffer::FromData(mString)->Release(); } NS_IMPL_ISUPPORTS1(AtomImpl, nsIAtom) PermanentAtomImpl::~PermanentAtomImpl() { // So we can tell if we were permanent while running the base class dtor. mRefCnt = REFCNT_PERMANENT_SENTINEL; } NS_IMETHODIMP_(nsrefcnt) PermanentAtomImpl::AddRef() { NS_ASSERTION(NS_IsMainThread(), "wrong thread"); return 2; } NS_IMETHODIMP_(nsrefcnt) PermanentAtomImpl::Release() { NS_ASSERTION(NS_IsMainThread(), "wrong thread"); return 1; } /* virtual */ PRBool AtomImpl::IsPermanent() { return PR_FALSE; } /* virtual */ PRBool PermanentAtomImpl::IsPermanent() { return PR_TRUE; } void* PermanentAtomImpl::operator new ( size_t size, AtomImpl* aAtom ) CPP_THROW_NEW { NS_ASSERTION(!aAtom->IsPermanent(), "converting atom that's already permanent"); // Just let the constructor overwrite the vtable pointer. return aAtom; } NS_IMETHODIMP AtomImpl::ScriptableToString(nsAString& aBuf) { nsStringBuffer::FromData(mString)->ToString(mLength, aBuf); return NS_OK; } NS_IMETHODIMP AtomImpl::ToUTF8String(nsACString& aBuf) { CopyUTF16toUTF8(nsDependentString(mString, mLength), aBuf); return NS_OK; } NS_IMETHODIMP_(PRBool) AtomImpl::EqualsUTF8(const nsACString& aString) { return CompareUTF8toUTF16(aString, nsDependentString(mString, mLength)) == 0; } NS_IMETHODIMP AtomImpl::ScriptableEquals(const nsAString& aString, PRBool* aResult) { *aResult = aString.Equals(nsDependentString(mString, mLength)); return NS_OK; } NS_IMETHODIMP_(PRBool) AtomImpl::IsStaticAtom() { return IsPermanent(); } //---------------------------------------------------------------------- #define ATOM_HASHTABLE_INITIAL_SIZE 4096 static inline AtomTableEntry* GetAtomHashEntry(const char* aString, PRUint32 aLength) { NS_ASSERTION(NS_IsMainThread(), "wrong thread"); if (!gAtomTable.ops && !PL_DHashTableInit(&gAtomTable, &AtomTableOps, 0, sizeof(AtomTableEntry), ATOM_HASHTABLE_INITIAL_SIZE)) { gAtomTable.ops = nsnull; return nsnull; } AtomTableEntry key(aString, aLength); return static_cast (PL_DHashTableOperate(&gAtomTable, &key, PL_DHASH_ADD)); } static inline AtomTableEntry* GetAtomHashEntry(const PRUnichar* aString, PRUint32 aLength) { NS_ASSERTION(NS_IsMainThread(), "wrong thread"); if (!gAtomTable.ops && !PL_DHashTableInit(&gAtomTable, &AtomTableOps, 0, sizeof(AtomTableEntry), ATOM_HASHTABLE_INITIAL_SIZE)) { gAtomTable.ops = nsnull; return nsnull; } AtomTableEntry key(aString, aLength); return static_cast (PL_DHashTableOperate(&gAtomTable, &key, PL_DHASH_ADD)); } class CheckStaticAtomSizes { CheckStaticAtomSizes() { PR_STATIC_ASSERT((sizeof(nsFakeStringBuffer<1>().mRefCnt) == sizeof(nsStringBuffer().mRefCount)) && (sizeof(nsFakeStringBuffer<1>().mSize) == sizeof(nsStringBuffer().mStorageSize)) && (offsetof(nsFakeStringBuffer<1>, mRefCnt) == offsetof(nsStringBuffer, mRefCount)) && (offsetof(nsFakeStringBuffer<1>, mSize) == offsetof(nsStringBuffer, mStorageSize)) && (offsetof(nsFakeStringBuffer<1>, mStringData) == sizeof(nsStringBuffer))); } }; NS_COM nsresult NS_RegisterStaticAtoms(const nsStaticAtom* aAtoms, PRUint32 aAtomCount) { // this does three things: // 1) wraps each static atom in a wrapper, if necessary // 2) initializes the address pointed to by each mBits slot // 3) puts the atom into the static atom table as well if (!gStaticAtomTable && !gStaticAtomTableSealed) { gStaticAtomTable = new nsDataHashtable(); if (!gStaticAtomTable || !gStaticAtomTable->Init()) { delete gStaticAtomTable; gStaticAtomTable = nsnull; return NS_ERROR_OUT_OF_MEMORY; } } for (PRUint32 i=0; iData()), "Static atoms must be ASCII!"); PRUint32 stringLen = aAtoms[i].mStringBuffer->StorageSize() / sizeof(PRUnichar) - 1; AtomTableEntry *he = GetAtomHashEntry((PRUnichar*)aAtoms[i].mStringBuffer->Data(), stringLen); if (he->HasValue()) { // there already is an atom with this name in the table.. but we // still have to update mBits if (!he->GetAtomImpl()->IsPermanent()) { // since we wanted to create a static atom but there is // already one there, we convert it to a non-refcounting // permanent atom PromoteToPermanent(he->GetAtomImpl()); } *aAtoms[i].mAtom = he->GetAtom(); } else { AtomImpl* atom = new PermanentAtomImpl(aAtoms[i].mStringBuffer, stringLen); he->SetAtomImpl(atom); *aAtoms[i].mAtom = atom; if (!gStaticAtomTableSealed) { gStaticAtomTable->Put(nsAtomString(atom), atom); } } #else // NS_STATIC_ATOM_USE_WIDE_STRINGS NS_ASSERTION(nsCRT::IsAscii((char*)aAtoms[i].mStringBuffer->Data()), "Static atoms must be ASCII!"); PRUint32 stringLen = aAtoms[i].mStringBuffer->StorageSize() - 1; NS_ConvertASCIItoUTF16 str((char*)aAtoms[i].mStringBuffer->Data(), stringLen); nsIAtom* atom = NS_NewPermanentAtom(str); *aAtoms[i].mAtom = atom; if (!gStaticAtomTableSealed) { gStaticAtomTable->Put(str, atom); } #endif } return NS_OK; } NS_COM nsIAtom* NS_NewAtom(const char* aUTF8String) { return NS_NewAtom(nsDependentCString(aUTF8String)); } NS_COM nsIAtom* NS_NewAtom(const nsACString& aUTF8String) { AtomTableEntry *he = GetAtomHashEntry(aUTF8String.Data(), aUTF8String.Length()); if (!he) { return nsnull; } if (he->HasValue()) return he->GetAtom(); // This results in an extra addref/release of the nsStringBuffer. // Unfortunately there doesn't seem to be any APIs to avoid that. nsString str; CopyUTF8toUTF16(aUTF8String, str); AtomImpl* atom = new AtomImpl(str); he->SetAtomImpl(atom); NS_ADDREF(atom); return atom; } NS_COM nsIAtom* NS_NewAtom(const PRUnichar* aUTF16String) { return NS_NewAtom(nsDependentString(aUTF16String)); } NS_COM nsIAtom* NS_NewAtom(const nsAString& aUTF16String) { AtomTableEntry *he = GetAtomHashEntry(aUTF16String.Data(), aUTF16String.Length()); if (he->HasValue()) return he->GetAtom(); AtomImpl* atom = new AtomImpl(aUTF16String); he->SetAtomImpl(atom); NS_ADDREF(atom); return atom; } NS_COM nsIAtom* NS_NewPermanentAtom(const nsAString& aUTF16String) { AtomTableEntry *he = GetAtomHashEntry(aUTF16String.Data(), aUTF16String.Length()); if (he->HasValue()) { AtomImpl* atom = he->GetAtomImpl(); if (!atom->IsPermanent()) { PromoteToPermanent(atom); } return atom; } AtomImpl* atom = new PermanentAtomImpl(aUTF16String); he->SetAtomImpl(atom); // No need to addref since permanent atoms aren't refcounted anyway return atom; } NS_COM nsrefcnt NS_GetNumberOfAtoms(void) { return gAtomTable.entryCount; } NS_COM nsIAtom* NS_GetStaticAtom(const nsAString& aUTF16String) { NS_PRECONDITION(gStaticAtomTable, "Static atom table not created yet."); NS_PRECONDITION(gStaticAtomTableSealed, "Static atom table not sealed yet."); nsIAtom* atom; if (!gStaticAtomTable->Get(aUTF16String, &atom)) { atom = nsnull; } return atom; } NS_COM void NS_SealStaticAtomTable() { gStaticAtomTableSealed = PR_TRUE; }