diff options
author | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
---|---|---|
committer | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
commit | 5f8de423f190bbb79a62f804151bc24824fa32d8 (patch) | |
tree | 10027f336435511475e392454359edea8e25895d /xpcom/glue | |
parent | 49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff) | |
download | uxp-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz |
Add m-esr52 at 52.6.0
Diffstat (limited to 'xpcom/glue')
106 files changed, 30515 insertions, 0 deletions
diff --git a/xpcom/glue/AppData.cpp b/xpcom/glue/AppData.cpp new file mode 100644 index 0000000000..845267e60d --- /dev/null +++ b/xpcom/glue/AppData.cpp @@ -0,0 +1,95 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/AppData.h" +#include "nsXULAppAPI.h" +#include "nsINIParser.h" +#include "nsIFile.h" +#include "nsCRTGlue.h" +#include "nsAutoPtr.h" + +namespace mozilla { + +void +SetAllocatedString(const char*& aStr, const char* aNewValue) +{ + NS_Free(const_cast<char*>(aStr)); + if (aNewValue) { + aStr = NS_strdup(aNewValue); + } else { + aStr = nullptr; + } +} + +void +SetAllocatedString(const char*& aStr, const nsACString& aNewValue) +{ + NS_Free(const_cast<char*>(aStr)); + if (aNewValue.IsEmpty()) { + aStr = nullptr; + } else { + aStr = ToNewCString(aNewValue); + } +} + +ScopedAppData::ScopedAppData(const nsXREAppData* aAppData) +{ + Zero(); + + this->size = aAppData->size; + + SetAllocatedString(this->vendor, aAppData->vendor); + SetAllocatedString(this->name, aAppData->name); + SetAllocatedString(this->remotingName, aAppData->remotingName); + SetAllocatedString(this->version, aAppData->version); + SetAllocatedString(this->buildID, aAppData->buildID); + SetAllocatedString(this->ID, aAppData->ID); + SetAllocatedString(this->copyright, aAppData->copyright); + SetAllocatedString(this->profile, aAppData->profile); + SetStrongPtr(this->directory, aAppData->directory); + this->flags = aAppData->flags; + + if (aAppData->size > offsetof(nsXREAppData, xreDirectory)) { + SetStrongPtr(this->xreDirectory, aAppData->xreDirectory); + SetAllocatedString(this->minVersion, aAppData->minVersion); + SetAllocatedString(this->maxVersion, aAppData->maxVersion); + } + + if (aAppData->size > offsetof(nsXREAppData, crashReporterURL)) { + SetAllocatedString(this->crashReporterURL, aAppData->crashReporterURL); + } + + if (aAppData->size > offsetof(nsXREAppData, UAName)) { + SetAllocatedString(this->UAName, aAppData->UAName); + } + +#if defined(XP_WIN) && defined(MOZ_SANDBOX) + sandboxBrokerServices = aAppData->sandboxBrokerServices; +#endif +} + +ScopedAppData::~ScopedAppData() +{ + SetAllocatedString(this->vendor, nullptr); + SetAllocatedString(this->name, nullptr); + SetAllocatedString(this->remotingName, nullptr); + SetAllocatedString(this->version, nullptr); + SetAllocatedString(this->buildID, nullptr); + SetAllocatedString(this->ID, nullptr); + SetAllocatedString(this->copyright, nullptr); + SetAllocatedString(this->profile, nullptr); + + NS_IF_RELEASE(this->directory); + + SetStrongPtr(this->xreDirectory, (nsIFile*)nullptr); + SetAllocatedString(this->minVersion, nullptr); + SetAllocatedString(this->maxVersion, nullptr); + + SetAllocatedString(this->crashReporterURL, nullptr); + SetAllocatedString(this->UAName, nullptr); +} + +} // namespace mozilla diff --git a/xpcom/glue/AppData.h b/xpcom/glue/AppData.h new file mode 100644 index 0000000000..0134df32ca --- /dev/null +++ b/xpcom/glue/AppData.h @@ -0,0 +1,63 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_AppData_h +#define mozilla_AppData_h + +#include "nsXREAppData.h" +#include "nscore.h" +#include "nsStringGlue.h" +#include "nsISupportsUtils.h" + +namespace mozilla { + +// Like nsXREAppData, but releases all strong refs/allocated memory +// in the destructor. +class ScopedAppData : public nsXREAppData +{ +public: + ScopedAppData() + { + Zero(); + this->size = sizeof(*this); + } + + explicit ScopedAppData(const nsXREAppData* aAppData); + + void Zero() { memset(this, 0, sizeof(*this)); } + + ~ScopedAppData(); +}; + +/** + * Given |aStr| is holding a string allocated with NS_Alloc, or null: + * replace the value in |aStr| with a new value. + * + * @param aNewValue Null is permitted. The string is cloned with NS_strdup. + */ +void SetAllocatedString(const char*& aStr, const char* aNewValue); + +/** + * Given "str" is holding a string allocated with NS_Alloc, or null: + * replace the value in "str" with a new value. + * + * @param aNewValue If |aNewValue| is the empty string, |aStr| will be set + * to null. + */ +void SetAllocatedString(const char*& aStr, const nsACString& aNewValue); + +template<class T> +void +SetStrongPtr(T*& aPtr, T* aNewValue) +{ + NS_IF_RELEASE(aPtr); + aPtr = aNewValue; + NS_IF_ADDREF(aPtr); +} + +} // namespace mozilla + +#endif diff --git a/xpcom/glue/AutoRestore.h b/xpcom/glue/AutoRestore.h new file mode 100644 index 0000000000..20909c92c3 --- /dev/null +++ b/xpcom/glue/AutoRestore.h @@ -0,0 +1,55 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* functions for restoring saved values at the end of a C++ scope */ + +#ifndef mozilla_AutoRestore_h_ +#define mozilla_AutoRestore_h_ + +#include "mozilla/Attributes.h" // MOZ_STACK_CLASS +#include "mozilla/GuardObjects.h" + +namespace mozilla { + +/** + * Save the current value of a variable and restore it when the object + * goes out of scope. For example: + * { + * AutoRestore<bool> savePainting(mIsPainting); + * mIsPainting = true; + * + * // ... your code here ... + * + * // mIsPainting is reset to its old value at the end of this block + * } + */ +template<class T> +class MOZ_RAII AutoRestore +{ +private: + T& mLocation; + T mValue; + MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER +public: + explicit AutoRestore(T& aValue MOZ_GUARD_OBJECT_NOTIFIER_PARAM) + : mLocation(aValue) + , mValue(aValue) + { + MOZ_GUARD_OBJECT_NOTIFIER_INIT; + } + ~AutoRestore() + { + mLocation = mValue; + } + T SavedValue() const + { + return mValue; + } +}; + +} // namespace mozilla + +#endif /* !defined(mozilla_AutoRestore_h_) */ diff --git a/xpcom/glue/BlockingResourceBase.cpp b/xpcom/glue/BlockingResourceBase.cpp new file mode 100644 index 0000000000..ada02c72ce --- /dev/null +++ b/xpcom/glue/BlockingResourceBase.cpp @@ -0,0 +1,511 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/BlockingResourceBase.h" + +#ifdef DEBUG +#include "prthread.h" + +#include "nsAutoPtr.h" + +#ifndef MOZ_CALLSTACK_DISABLED +#include "CodeAddressService.h" +#include "nsHashKeys.h" +#include "mozilla/StackWalk.h" +#include "nsTHashtable.h" +#endif + +#include "mozilla/CondVar.h" +#include "mozilla/DeadlockDetector.h" +#include "mozilla/ReentrantMonitor.h" +#include "mozilla/Mutex.h" + +#if defined(MOZILLA_INTERNAL_API) +#include "GeckoProfiler.h" +#endif //MOZILLA_INTERNAL_API + +#endif // ifdef DEBUG + +namespace mozilla { +// +// BlockingResourceBase implementation +// + +// static members +const char* const BlockingResourceBase::kResourceTypeName[] = { + // needs to be kept in sync with BlockingResourceType + "Mutex", "ReentrantMonitor", "CondVar" +}; + +#ifdef DEBUG + +PRCallOnceType BlockingResourceBase::sCallOnce; +unsigned BlockingResourceBase::sResourceAcqnChainFrontTPI = (unsigned)-1; +BlockingResourceBase::DDT* BlockingResourceBase::sDeadlockDetector; + + +void +BlockingResourceBase::StackWalkCallback(uint32_t aFrameNumber, void* aPc, + void* aSp, void* aClosure) +{ +#ifndef MOZ_CALLSTACK_DISABLED + AcquisitionState* state = (AcquisitionState*)aClosure; + state->AppendElement(aPc); +#endif +} + +void +BlockingResourceBase::GetStackTrace(AcquisitionState& aState) +{ +#ifndef MOZ_CALLSTACK_DISABLED + // Skip this function and the calling function. + const uint32_t kSkipFrames = 2; + + aState.Clear(); + + // NB: Ignore the return value, there's nothing useful we can do if this + // this fails. + MozStackWalk(StackWalkCallback, kSkipFrames, 24, &aState, 0, nullptr); +#endif +} + +/** + * PrintCycle + * Append to |aOut| detailed information about the circular + * dependency in |aCycle|. Returns true if it *appears* that this + * cycle may represent an imminent deadlock, but this is merely a + * heuristic; the value returned may be a false positive or false + * negative. + * + * *NOT* thread safe. Calls |Print()|. + * + * FIXME bug 456272 hack alert: because we can't write call + * contexts into strings, all info is written to stderr, but only + * some info is written into |aOut| + */ +bool +PrintCycle(const BlockingResourceBase::DDT::ResourceAcquisitionArray* aCycle, + nsACString& aOut) +{ + NS_ASSERTION(aCycle->Length() > 1, "need > 1 element for cycle!"); + + bool maybeImminent = true; + + fputs("=== Cyclical dependency starts at\n", stderr); + aOut += "Cyclical dependency starts at\n"; + + const BlockingResourceBase::DDT::ResourceAcquisitionArray::elem_type res = + aCycle->ElementAt(0); + maybeImminent &= res->Print(aOut); + + BlockingResourceBase::DDT::ResourceAcquisitionArray::index_type i; + BlockingResourceBase::DDT::ResourceAcquisitionArray::size_type len = + aCycle->Length(); + const BlockingResourceBase::DDT::ResourceAcquisitionArray::elem_type* it = + 1 + aCycle->Elements(); + for (i = 1; i < len - 1; ++i, ++it) { + fputs("\n--- Next dependency:\n", stderr); + aOut += "\nNext dependency:\n"; + + maybeImminent &= (*it)->Print(aOut); + } + + fputs("\n=== Cycle completed at\n", stderr); + aOut += "Cycle completed at\n"; + (*it)->Print(aOut); + + return maybeImminent; +} + +#ifndef MOZ_CALLSTACK_DISABLED +struct CodeAddressServiceLock final +{ + static void Unlock() { } + static void Lock() { } + static bool IsLocked() { return true; } +}; + +struct CodeAddressServiceStringAlloc final +{ + static char* copy(const char* aString) { return ::strdup(aString); } + static void free(char* aString) { ::free(aString); } +}; + +class CodeAddressServiceStringTable final +{ +public: + CodeAddressServiceStringTable() : mSet(32) {} + + const char* Intern(const char* aString) + { + nsCharPtrHashKey* e = mSet.PutEntry(aString); + return e->GetKey(); + } + + size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const + { + return mSet.SizeOfExcludingThis(aMallocSizeOf); + } + +private: + typedef nsTHashtable<nsCharPtrHashKey> StringSet; + StringSet mSet; +}; + +typedef CodeAddressService<CodeAddressServiceStringTable, + CodeAddressServiceStringAlloc, + CodeAddressServiceLock> WalkTheStackCodeAddressService; +#endif + +bool +BlockingResourceBase::Print(nsACString& aOut) const +{ + fprintf(stderr, "--- %s : %s", + kResourceTypeName[mType], mName); + aOut += BlockingResourceBase::kResourceTypeName[mType]; + aOut += " : "; + aOut += mName; + + bool acquired = IsAcquired(); + + if (acquired) { + fputs(" (currently acquired)\n", stderr); + aOut += " (currently acquired)\n"; + } + + fputs(" calling context\n", stderr); +#ifdef MOZ_CALLSTACK_DISABLED + fputs(" [stack trace unavailable]\n", stderr); +#else + const AcquisitionState& state = acquired ? mAcquired : mFirstSeen; + + WalkTheStackCodeAddressService addressService; + + for (uint32_t i = 0; i < state.Length(); i++) { + const size_t kMaxLength = 1024; + char buffer[kMaxLength]; + addressService.GetLocation(i + 1, state[i], buffer, kMaxLength); + const char* fmt = " %s\n"; + aOut.AppendLiteral(" "); + aOut.Append(buffer); + aOut.AppendLiteral("\n"); + fprintf(stderr, fmt, buffer); + } + +#endif + + return acquired; +} + + +BlockingResourceBase::BlockingResourceBase( + const char* aName, + BlockingResourceBase::BlockingResourceType aType) + : mName(aName) + , mType(aType) +#ifdef MOZ_CALLSTACK_DISABLED + , mAcquired(false) +#else + , mAcquired() +#endif +{ + MOZ_ASSERT(mName, "Name must be nonnull"); + // PR_CallOnce guaranatees that InitStatics is called in a + // thread-safe way + if (PR_SUCCESS != PR_CallOnce(&sCallOnce, InitStatics)) { + NS_RUNTIMEABORT("can't initialize blocking resource static members"); + } + + mChainPrev = 0; + sDeadlockDetector->Add(this); +} + + +BlockingResourceBase::~BlockingResourceBase() +{ + // we don't check for really obviously bad things like freeing + // Mutexes while they're still locked. it is assumed that the + // base class, or its underlying primitive, will check for such + // stupid mistakes. + mChainPrev = 0; // racy only for stupidly buggy client code + if (sDeadlockDetector) { + sDeadlockDetector->Remove(this); + } +} + + +size_t +BlockingResourceBase::SizeOfDeadlockDetector(MallocSizeOf aMallocSizeOf) +{ + return sDeadlockDetector ? + sDeadlockDetector->SizeOfIncludingThis(aMallocSizeOf) : 0; +} + + +PRStatus +BlockingResourceBase::InitStatics() +{ + PR_NewThreadPrivateIndex(&sResourceAcqnChainFrontTPI, 0); + sDeadlockDetector = new DDT(); + if (!sDeadlockDetector) { + NS_RUNTIMEABORT("can't allocate deadlock detector"); + } + return PR_SUCCESS; +} + + +void +BlockingResourceBase::Shutdown() +{ + delete sDeadlockDetector; + sDeadlockDetector = 0; +} + + +void +BlockingResourceBase::CheckAcquire() +{ + if (mType == eCondVar) { + NS_NOTYETIMPLEMENTED( + "FIXME bug 456272: annots. to allow CheckAcquire()ing condvars"); + return; + } + + BlockingResourceBase* chainFront = ResourceChainFront(); + nsAutoPtr<DDT::ResourceAcquisitionArray> cycle( + sDeadlockDetector->CheckAcquisition( + chainFront ? chainFront : 0, this)); + if (!cycle) { + return; + } + +#ifndef MOZ_CALLSTACK_DISABLED + // Update the current stack before printing. + GetStackTrace(mAcquired); +#endif + + fputs("###!!! ERROR: Potential deadlock detected:\n", stderr); + nsAutoCString out("Potential deadlock detected:\n"); + bool maybeImminent = PrintCycle(cycle, out); + + if (maybeImminent) { + fputs("\n###!!! Deadlock may happen NOW!\n\n", stderr); + out.AppendLiteral("\n###!!! Deadlock may happen NOW!\n\n"); + } else { + fputs("\nDeadlock may happen for some other execution\n\n", + stderr); + out.AppendLiteral("\nDeadlock may happen for some other execution\n\n"); + } + + // XXX can customize behavior on whether we /think/ deadlock is + // XXX about to happen. for example: + // XXX if (maybeImminent) + // NS_RUNTIMEABORT(out.get()); + NS_ERROR(out.get()); +} + + +void +BlockingResourceBase::Acquire() +{ + if (mType == eCondVar) { + NS_NOTYETIMPLEMENTED( + "FIXME bug 456272: annots. to allow Acquire()ing condvars"); + return; + } + NS_ASSERTION(!IsAcquired(), + "reacquiring already acquired resource"); + + ResourceChainAppend(ResourceChainFront()); + +#ifdef MOZ_CALLSTACK_DISABLED + mAcquired = true; +#else + // Take a stack snapshot. + GetStackTrace(mAcquired); + if (mFirstSeen.IsEmpty()) { + mFirstSeen = mAcquired; + } +#endif +} + + +void +BlockingResourceBase::Release() +{ + if (mType == eCondVar) { + NS_NOTYETIMPLEMENTED( + "FIXME bug 456272: annots. to allow Release()ing condvars"); + return; + } + + BlockingResourceBase* chainFront = ResourceChainFront(); + NS_ASSERTION(chainFront && IsAcquired(), + "Release()ing something that hasn't been Acquire()ed"); + + if (chainFront == this) { + ResourceChainRemove(); + } else { + // not an error, but makes code hard to reason about. + NS_WARNING("Resource acquired is being released in non-LIFO order; why?\n"); + nsCString tmp; + Print(tmp); + + // remove this resource from wherever it lives in the chain + // we walk backwards in order of acquisition: + // (1) ...node<-prev<-curr... + // / / + // (2) ...prev<-curr... + BlockingResourceBase* curr = chainFront; + BlockingResourceBase* prev = nullptr; + while (curr && (prev = curr->mChainPrev) && (prev != this)) { + curr = prev; + } + if (prev == this) { + curr->mChainPrev = prev->mChainPrev; + } + } + + ClearAcquisitionState(); +} + + +// +// Debug implementation of (OffTheBooks)Mutex +void +OffTheBooksMutex::Lock() +{ + CheckAcquire(); + PR_Lock(mLock); + Acquire(); // protected by mLock +} + +void +OffTheBooksMutex::Unlock() +{ + Release(); // protected by mLock + PRStatus status = PR_Unlock(mLock); + NS_ASSERTION(PR_SUCCESS == status, "bad Mutex::Unlock()"); +} + + +// +// Debug implementation of ReentrantMonitor +void +ReentrantMonitor::Enter() +{ + BlockingResourceBase* chainFront = ResourceChainFront(); + + // the code below implements monitor reentrancy semantics + + if (this == chainFront) { + // immediately re-entered the monitor: acceptable + PR_EnterMonitor(mReentrantMonitor); + ++mEntryCount; + return; + } + + // this is sort of a hack around not recording the thread that + // owns this monitor + if (chainFront) { + for (BlockingResourceBase* br = ResourceChainPrev(chainFront); + br; + br = ResourceChainPrev(br)) { + if (br == this) { + NS_WARNING( + "Re-entering ReentrantMonitor after acquiring other resources."); + + // show the caller why this is potentially bad + CheckAcquire(); + + PR_EnterMonitor(mReentrantMonitor); + ++mEntryCount; + return; + } + } + } + + CheckAcquire(); + PR_EnterMonitor(mReentrantMonitor); + NS_ASSERTION(mEntryCount == 0, "ReentrantMonitor isn't free!"); + Acquire(); // protected by mReentrantMonitor + mEntryCount = 1; +} + +void +ReentrantMonitor::Exit() +{ + if (--mEntryCount == 0) { + Release(); // protected by mReentrantMonitor + } + PRStatus status = PR_ExitMonitor(mReentrantMonitor); + NS_ASSERTION(PR_SUCCESS == status, "bad ReentrantMonitor::Exit()"); +} + +nsresult +ReentrantMonitor::Wait(PRIntervalTime aInterval) +{ + AssertCurrentThreadIn(); + + // save monitor state and reset it to empty + int32_t savedEntryCount = mEntryCount; + AcquisitionState savedAcquisitionState = GetAcquisitionState(); + BlockingResourceBase* savedChainPrev = mChainPrev; + mEntryCount = 0; + ClearAcquisitionState(); + mChainPrev = 0; + + nsresult rv; +#if defined(MOZILLA_INTERNAL_API) + { + GeckoProfilerSleepRAII profiler_sleep; +#endif //MOZILLA_INTERNAL_API + + // give up the monitor until we're back from Wait() + rv = PR_Wait(mReentrantMonitor, aInterval) == PR_SUCCESS ? NS_OK : + NS_ERROR_FAILURE; + +#if defined(MOZILLA_INTERNAL_API) + } +#endif //MOZILLA_INTERNAL_API + + // restore saved state + mEntryCount = savedEntryCount; + SetAcquisitionState(savedAcquisitionState); + mChainPrev = savedChainPrev; + + return rv; +} + + +// +// Debug implementation of CondVar +nsresult +CondVar::Wait(PRIntervalTime aInterval) +{ + AssertCurrentThreadOwnsMutex(); + + // save mutex state and reset to empty + AcquisitionState savedAcquisitionState = mLock->GetAcquisitionState(); + BlockingResourceBase* savedChainPrev = mLock->mChainPrev; + mLock->ClearAcquisitionState(); + mLock->mChainPrev = 0; + + // give up mutex until we're back from Wait() + nsresult rv = + PR_WaitCondVar(mCvar, aInterval) == PR_SUCCESS ? NS_OK : NS_ERROR_FAILURE; + + // restore saved state + mLock->SetAcquisitionState(savedAcquisitionState); + mLock->mChainPrev = savedChainPrev; + + return rv; +} + +#endif // ifdef DEBUG + + +} // namespace mozilla diff --git a/xpcom/glue/BlockingResourceBase.h b/xpcom/glue/BlockingResourceBase.h new file mode 100644 index 0000000000..d70fbcbbf3 --- /dev/null +++ b/xpcom/glue/BlockingResourceBase.h @@ -0,0 +1,344 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + + +#ifndef mozilla_BlockingResourceBase_h +#define mozilla_BlockingResourceBase_h + +#include "mozilla/Logging.h" + +#include "nscore.h" +#include "nsDebug.h" +#include "nsError.h" +#include "nsISupportsImpl.h" + +#ifdef DEBUG + +// NB: Comment this out to enable callstack tracking. +#define MOZ_CALLSTACK_DISABLED + +#include "prinit.h" + +#include "nsStringGlue.h" + +#ifndef MOZ_CALLSTACK_DISABLED +#include "nsTArray.h" +#endif + +#include "nsXPCOM.h" +#endif + +// +// This header is not meant to be included by client code. +// + +namespace mozilla { + +#ifdef DEBUG +template <class T> class DeadlockDetector; +#endif + +/** + * BlockingResourceBase + * Base class of resources that might block clients trying to acquire them. + * Does debugging and deadlock detection in DEBUG builds. + **/ +class BlockingResourceBase +{ +public: + // Needs to be kept in sync with kResourceTypeNames. + enum BlockingResourceType { eMutex, eReentrantMonitor, eCondVar }; + + /** + * kResourceTypeName + * Human-readable version of BlockingResourceType enum. + */ + static const char* const kResourceTypeName[]; + + +#ifdef DEBUG + + static size_t + SizeOfDeadlockDetector(MallocSizeOf aMallocSizeOf); + + /** + * Print + * Write a description of this blocking resource to |aOut|. If + * the resource appears to be currently acquired, the current + * acquisition context is printed and true is returned. + * Otherwise, we print the context from |aFirstSeen|, the + * first acquisition from which the code calling |Print()| + * became interested in us, and return false. + * + * *NOT* thread safe. Reads |mAcquisitionContext| without + * synchronization, but this will not cause correctness + * problems. + * + * FIXME bug 456272: hack alert: because we can't write call + * contexts into strings, all info is written to stderr, but + * only some info is written into |aOut| + */ + bool Print(nsACString& aOut) const; + + size_t + SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const + { + // NB: |mName| is not reported as it's expected to be a static string. + // If we switch to a nsString it should be added to the tally. + // |mChainPrev| is not reported because its memory is not owned. + size_t n = aMallocSizeOf(this); + return n; + } + + // ``DDT'' = ``Deadlock Detector Type'' + typedef DeadlockDetector<BlockingResourceBase> DDT; + +protected: +#ifdef MOZ_CALLSTACK_DISABLED + typedef bool AcquisitionState; +#else + typedef AutoTArray<void*, 24> AcquisitionState; +#endif + + /** + * BlockingResourceBase + * Initialize this blocking resource. Also hooks the resource into + * instrumentation code. + * + * Thread safe. + * + * @param aName A meaningful, unique name that can be used in + * error messages, et al. + * @param aType The specific type of |this|, if any. + **/ + BlockingResourceBase(const char* aName, BlockingResourceType aType); + + ~BlockingResourceBase(); + + /** + * CheckAcquire + * + * Thread safe. + **/ + void CheckAcquire(); + + /** + * Acquire + * + * *NOT* thread safe. Requires ownership of underlying resource. + **/ + void Acquire(); //NS_NEEDS_RESOURCE(this) + + /** + * Release + * Remove this resource from the current thread's acquisition chain. + * The resource does not have to be at the front of the chain, although + * it is confusing to release resources in a different order than they + * are acquired. This generates a warning. + * + * *NOT* thread safe. Requires ownership of underlying resource. + **/ + void Release(); //NS_NEEDS_RESOURCE(this) + + /** + * ResourceChainFront + * + * Thread safe. + * + * @return the front of the resource acquisition chain, i.e., the last + * resource acquired. + */ + static BlockingResourceBase* ResourceChainFront() + { + return + (BlockingResourceBase*)PR_GetThreadPrivate(sResourceAcqnChainFrontTPI); + } + + /** + * ResourceChainPrev + * + * *NOT* thread safe. Requires ownership of underlying resource. + */ + static BlockingResourceBase* ResourceChainPrev( + const BlockingResourceBase* aResource) + { + return aResource->mChainPrev; + } //NS_NEEDS_RESOURCE(this) + + /** + * ResourceChainAppend + * Set |this| to the front of the resource acquisition chain, and link + * |this| to |aPrev|. + * + * *NOT* thread safe. Requires ownership of underlying resource. + */ + void ResourceChainAppend(BlockingResourceBase* aPrev) + { + mChainPrev = aPrev; + PR_SetThreadPrivate(sResourceAcqnChainFrontTPI, this); + } //NS_NEEDS_RESOURCE(this) + + /** + * ResourceChainRemove + * Remove |this| from the front of the resource acquisition chain. + * + * *NOT* thread safe. Requires ownership of underlying resource. + */ + void ResourceChainRemove() + { + NS_ASSERTION(this == ResourceChainFront(), "not at chain front"); + PR_SetThreadPrivate(sResourceAcqnChainFrontTPI, mChainPrev); + } //NS_NEEDS_RESOURCE(this) + + /** + * GetAcquisitionState + * Return whether or not this resource was acquired. + * + * *NOT* thread safe. Requires ownership of underlying resource. + */ + AcquisitionState GetAcquisitionState() + { + return mAcquired; + } + + /** + * SetAcquisitionState + * Set whether or not this resource was acquired. + * + * *NOT* thread safe. Requires ownership of underlying resource. + */ + void SetAcquisitionState(const AcquisitionState& aAcquisitionState) + { + mAcquired = aAcquisitionState; + } + + /** + * ClearAcquisitionState + * Indicate this resource is not acquired. + * + * *NOT* thread safe. Requires ownership of underlying resource. + */ + void ClearAcquisitionState() + { +#ifdef MOZ_CALLSTACK_DISABLED + mAcquired = false; +#else + mAcquired.Clear(); +#endif + } + + /** + * IsAcquired + * Indicates if this resource is acquired. + * + * *NOT* thread safe. Requires ownership of underlying resource. + */ + bool IsAcquired() const + { +#ifdef MOZ_CALLSTACK_DISABLED + return mAcquired; +#else + return !mAcquired.IsEmpty(); +#endif + } + + /** + * mChainPrev + * A series of resource acquisitions creates a chain of orders. This + * chain is implemented as a linked list; |mChainPrev| points to the + * resource most recently Acquire()'d before this one. + **/ + BlockingResourceBase* mChainPrev; + +private: + /** + * mName + * A descriptive name for this resource. Used in error + * messages etc. + */ + const char* mName; + + /** + * mType + * The more specific type of this resource. Used to implement + * special semantics (e.g., reentrancy of monitors). + **/ + BlockingResourceType mType; + + /** + * mAcquired + * Indicates if this resource is currently acquired. + */ + AcquisitionState mAcquired; + +#ifndef MOZ_CALLSTACK_DISABLED + /** + * mFirstSeen + * Inidicates where this resource was first acquired. + */ + AcquisitionState mFirstSeen; +#endif + + /** + * sCallOnce + * Ensures static members are initialized only once, and in a + * thread-safe way. + */ + static PRCallOnceType sCallOnce; + + /** + * sResourceAcqnChainFrontTPI + * Thread-private index to the front of each thread's resource + * acquisition chain. + */ + static unsigned sResourceAcqnChainFrontTPI; + + /** + * sDeadlockDetector + * Does as named. + */ + static DDT* sDeadlockDetector; + + /** + * InitStatics + * Inititialize static members of BlockingResourceBase that can't + * be statically initialized. + * + * *NOT* thread safe. + */ + static PRStatus InitStatics(); + + /** + * Shutdown + * Free static members. + * + * *NOT* thread safe. + */ + static void Shutdown(); + + static void StackWalkCallback(uint32_t aFrameNumber, void* aPc, + void* aSp, void* aClosure); + static void GetStackTrace(AcquisitionState& aState); + +# ifdef MOZILLA_INTERNAL_API + // so it can call BlockingResourceBase::Shutdown() + friend void LogTerm(); +# endif // ifdef MOZILLA_INTERNAL_API + +#else // non-DEBUG implementation + + BlockingResourceBase(const char* aName, BlockingResourceType aType) {} + + ~BlockingResourceBase() {} + +#endif +}; + + +} // namespace mozilla + + +#endif // mozilla_BlockingResourceBase_h diff --git a/xpcom/glue/CondVar.h b/xpcom/glue/CondVar.h new file mode 100644 index 0000000000..5d05464eca --- /dev/null +++ b/xpcom/glue/CondVar.h @@ -0,0 +1,144 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_CondVar_h +#define mozilla_CondVar_h + +#include "prcvar.h" + +#include "mozilla/BlockingResourceBase.h" +#include "mozilla/Mutex.h" + +#ifdef MOZILLA_INTERNAL_API +#include "GeckoProfiler.h" +#endif //MOZILLA_INTERNAL_API + +namespace mozilla { + + +/** + * CondVar + * Vanilla condition variable. Please don't use this unless you have a + * compelling reason --- Monitor provides a simpler API. + */ +class CondVar : BlockingResourceBase +{ +public: + /** + * CondVar + * + * The CALLER owns |aLock|. + * + * @param aLock A Mutex to associate with this condition variable. + * @param aName A name which can reference this monitor + * @returns If failure, nullptr. + * If success, a valid Monitor* which must be destroyed + * by Monitor::DestroyMonitor() + **/ + CondVar(Mutex& aLock, const char* aName) + : BlockingResourceBase(aName, eCondVar) + , mLock(&aLock) + { + MOZ_COUNT_CTOR(CondVar); + // |aLock| must necessarily already be known to the deadlock detector + mCvar = PR_NewCondVar(mLock->mLock); + if (!mCvar) { + NS_RUNTIMEABORT("Can't allocate mozilla::CondVar"); + } + } + + /** + * ~CondVar + * Clean up after this CondVar, but NOT its associated Mutex. + **/ + ~CondVar() + { + NS_ASSERTION(mCvar && mLock, + "improperly constructed CondVar or double free"); + PR_DestroyCondVar(mCvar); + mCvar = 0; + mLock = 0; + MOZ_COUNT_DTOR(CondVar); + } + +#ifndef DEBUG + /** + * Wait + * @see prcvar.h + **/ + nsresult Wait(PRIntervalTime aInterval = PR_INTERVAL_NO_TIMEOUT) + { + +#ifdef MOZILLA_INTERNAL_API + GeckoProfilerSleepRAII profiler_sleep; +#endif //MOZILLA_INTERNAL_API + // NSPR checks for lock ownership + return PR_WaitCondVar(mCvar, aInterval) == PR_SUCCESS ? NS_OK : + NS_ERROR_FAILURE; + } +#else + nsresult Wait(PRIntervalTime aInterval = PR_INTERVAL_NO_TIMEOUT); +#endif // ifndef DEBUG + + /** + * Notify + * @see prcvar.h + **/ + nsresult Notify() + { + // NSPR checks for lock ownership + return PR_NotifyCondVar(mCvar) == PR_SUCCESS ? NS_OK : NS_ERROR_FAILURE; + } + + /** + * NotifyAll + * @see prcvar.h + **/ + nsresult NotifyAll() + { + // NSPR checks for lock ownership + return PR_NotifyAllCondVar(mCvar) == PR_SUCCESS ? NS_OK : NS_ERROR_FAILURE; + } + +#ifdef DEBUG + /** + * AssertCurrentThreadOwnsMutex + * @see Mutex::AssertCurrentThreadOwns + **/ + void AssertCurrentThreadOwnsMutex() + { + mLock->AssertCurrentThreadOwns(); + } + + /** + * AssertNotCurrentThreadOwnsMutex + * @see Mutex::AssertNotCurrentThreadOwns + **/ + void AssertNotCurrentThreadOwnsMutex() + { + mLock->AssertNotCurrentThreadOwns(); + } + +#else + void AssertCurrentThreadOwnsMutex() {} + void AssertNotCurrentThreadOwnsMutex() {} + +#endif // ifdef DEBUG + +private: + CondVar(); + CondVar(CondVar&); + CondVar& operator=(CondVar&); + + Mutex* mLock; + PRCondVar* mCvar; +}; + + +} // namespace mozilla + + +#endif // ifndef mozilla_CondVar_h diff --git a/xpcom/glue/DeadlockDetector.h b/xpcom/glue/DeadlockDetector.h new file mode 100644 index 0000000000..382e4ef4a7 --- /dev/null +++ b/xpcom/glue/DeadlockDetector.h @@ -0,0 +1,382 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef mozilla_DeadlockDetector_h +#define mozilla_DeadlockDetector_h + +#include "mozilla/Attributes.h" + +#include <stdlib.h> + +#include "prlock.h" + +#include "nsClassHashtable.h" +#include "nsTArray.h" + +namespace mozilla { + +/** + * DeadlockDetector + * + * The following is an approximate description of how the deadlock detector + * works. + * + * The deadlock detector ensures that all blocking resources are + * acquired according to a partial order P. One type of blocking + * resource is a lock. If a lock l1 is acquired (locked) before l2, + * then we say that |l1 <_P l2|. The detector flags an error if two + * locks l1 and l2 have an inconsistent ordering in P; that is, if + * both |l1 <_P l2| and |l2 <_P l1|. This is a potential error + * because a thread acquiring l1,l2 according to the first order might + * race with a thread acquiring them according to the second order. + * If this happens under the right conditions, then the acquisitions + * will deadlock. + * + * This deadlock detector doesn't know at compile-time what P is. So, + * it tries to discover the order at run time. More precisely, it + * finds <i>some</i> order P, then tries to find chains of resource + * acquisitions that violate P. An example acquisition sequence, and + * the orders they impose, is + * l1.lock() // current chain: [ l1 ] + * // order: { } + * + * l2.lock() // current chain: [ l1, l2 ] + * // order: { l1 <_P l2 } + * + * l3.lock() // current chain: [ l1, l2, l3 ] + * // order: { l1 <_P l2, l2 <_P l3, l1 <_P l3 } + * // (note: <_P is transitive, so also |l1 <_P l3|) + * + * l2.unlock() // current chain: [ l1, l3 ] + * // order: { l1 <_P l2, l2 <_P l3, l1 <_P l3 } + * // (note: it's OK, but weird, that l2 was unlocked out + * // of order. we still have l1 <_P l3). + * + * l2.lock() // current chain: [ l1, l3, l2 ] + * // order: { l1 <_P l2, l2 <_P l3, l1 <_P l3, + * l3 <_P l2 (!!!) } + * BEEP BEEP! Here the detector will flag a potential error, since + * l2 and l3 were used inconsistently (and potentially in ways that + * would deadlock). + */ +template<typename T> +class DeadlockDetector +{ +public: + typedef nsTArray<const T*> ResourceAcquisitionArray; + +private: + struct OrderingEntry; + typedef nsTArray<OrderingEntry*> HashEntryArray; + typedef typename HashEntryArray::index_type index_type; + typedef typename HashEntryArray::size_type size_type; + static const index_type NoIndex = HashEntryArray::NoIndex; + + /** + * Value type for the ordering table. Contains the other + * resources on which an ordering constraint |key < other| + * exists. The catch is that we also store the calling context at + * which the other resource was acquired; this improves the + * quality of error messages when potential deadlock is detected. + */ + struct OrderingEntry + { + explicit OrderingEntry(const T* aResource) + : mOrderedLT() // FIXME bug 456272: set to empirical dep size? + , mExternalRefs() + , mResource(aResource) + { + } + ~OrderingEntry() + { + } + + size_t + SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const + { + size_t n = aMallocSizeOf(this); + n += mOrderedLT.ShallowSizeOfExcludingThis(aMallocSizeOf); + n += mExternalRefs.ShallowSizeOfExcludingThis(aMallocSizeOf); + return n; + } + + HashEntryArray mOrderedLT; // this <_o Other + HashEntryArray mExternalRefs; // hash entries that reference this + const T* mResource; + }; + + // Throwaway RAII lock to make the following code safer. + struct PRAutoLock + { + explicit PRAutoLock(PRLock* aLock) : mLock(aLock) { PR_Lock(mLock); } + ~PRAutoLock() { PR_Unlock(mLock); } + PRLock* mLock; + }; + +public: + static const uint32_t kDefaultNumBuckets; + + /** + * DeadlockDetector + * Create a new deadlock detector. + * + * @param aNumResourcesGuess Guess at approximate number of resources + * that will be checked. + */ + explicit DeadlockDetector(uint32_t aNumResourcesGuess = kDefaultNumBuckets) + : mOrdering(aNumResourcesGuess) + { + mLock = PR_NewLock(); + if (!mLock) { + NS_RUNTIMEABORT("couldn't allocate deadlock detector lock"); + } + } + + /** + * ~DeadlockDetector + * + * *NOT* thread safe. + */ + ~DeadlockDetector() + { + PR_DestroyLock(mLock); + } + + size_t + SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const + { + size_t n = aMallocSizeOf(this); + + { + PRAutoLock _(mLock); + n += mOrdering.ShallowSizeOfExcludingThis(aMallocSizeOf); + for (auto iter = mOrdering.ConstIter(); !iter.Done(); iter.Next()) { + // NB: Key is accounted for in the entry. + n += iter.Data()->SizeOfIncludingThis(aMallocSizeOf); + } + } + + return n; + } + + /** + * Add + * Make the deadlock detector aware of |aResource|. + * + * WARNING: The deadlock detector owns |aResource|. + * + * Thread safe. + * + * @param aResource Resource to make deadlock detector aware of. + */ + void Add(const T* aResource) + { + PRAutoLock _(mLock); + mOrdering.Put(aResource, new OrderingEntry(aResource)); + } + + void Remove(const T* aResource) + { + PRAutoLock _(mLock); + + OrderingEntry* entry = mOrdering.Get(aResource); + + // Iterate the external refs and remove the entry from them. + HashEntryArray& refs = entry->mExternalRefs; + for (index_type i = 0; i < refs.Length(); i++) { + refs[i]->mOrderedLT.RemoveElementSorted(entry); + } + + // Iterate orders and remove this entry from their refs. + HashEntryArray& orders = entry->mOrderedLT; + for (index_type i = 0; i < orders.Length(); i++) { + orders[i]->mExternalRefs.RemoveElementSorted(entry); + } + + // Now the entry can be safely removed. + mOrdering.Remove(aResource); + } + + /** + * CheckAcquisition This method is called after acquiring |aLast|, + * but before trying to acquire |aProposed|. + * It determines whether actually trying to acquire |aProposed| + * will create problems. It is OK if |aLast| is nullptr; this is + * interpreted as |aProposed| being the thread's first acquisition + * of its current chain. + * + * Iff acquiring |aProposed| may lead to deadlock for some thread + * interleaving (including the current one!), the cyclical + * dependency from which this was deduced is returned. Otherwise, + * 0 is returned. + * + * If a potential deadlock is detected and a resource cycle is + * returned, it is the *caller's* responsibility to free it. + * + * Thread safe. + * + * @param aLast Last resource acquired by calling thread (or 0). + * @param aProposed Resource calling thread proposes to acquire. + */ + ResourceAcquisitionArray* CheckAcquisition(const T* aLast, + const T* aProposed) + { + if (!aLast) { + // don't check if |0 < aProposed|; just vamoose + return 0; + } + + NS_ASSERTION(aProposed, "null resource"); + PRAutoLock _(mLock); + + OrderingEntry* proposed = mOrdering.Get(aProposed); + NS_ASSERTION(proposed, "missing ordering entry"); + + OrderingEntry* current = mOrdering.Get(aLast); + NS_ASSERTION(current, "missing ordering entry"); + + // this is the crux of the deadlock detector algorithm + + if (current == proposed) { + // reflexive deadlock. fastpath b/c InTransitiveClosure is + // not applicable here. + ResourceAcquisitionArray* cycle = new ResourceAcquisitionArray(); + if (!cycle) { + NS_RUNTIMEABORT("can't allocate dep. cycle array"); + } + cycle->AppendElement(current->mResource); + cycle->AppendElement(aProposed); + return cycle; + } + if (InTransitiveClosure(current, proposed)) { + // we've already established |aLast < aProposed|. all is well. + return 0; + } + if (InTransitiveClosure(proposed, current)) { + // the order |aProposed < aLast| has been deduced, perhaps + // transitively. we're attempting to violate that + // constraint by acquiring resources in the order + // |aLast < aProposed|, and thus we may deadlock under the + // right conditions. + ResourceAcquisitionArray* cycle = GetDeductionChain(proposed, current); + // show how acquiring |aProposed| would complete the cycle + cycle->AppendElement(aProposed); + return cycle; + } + // |aLast|, |aProposed| are unordered according to our + // poset. this is fine, but we now need to add this + // ordering constraint. + current->mOrderedLT.InsertElementSorted(proposed); + proposed->mExternalRefs.InsertElementSorted(current); + return 0; + } + + /** + * Return true iff |aTarget| is in the transitive closure of |aStart| + * over the ordering relation `<_this'. + * + * @precondition |aStart != aTarget| + */ + bool InTransitiveClosure(const OrderingEntry* aStart, + const OrderingEntry* aTarget) const + { + // NB: Using a static comparator rather than default constructing one shows + // a 9% improvement in scalability tests on some systems. + static nsDefaultComparator<const OrderingEntry*, const OrderingEntry*> comp; + if (aStart->mOrderedLT.BinaryIndexOf(aTarget, comp) != NoIndex) { + return true; + } + + index_type i = 0; + size_type len = aStart->mOrderedLT.Length(); + for (auto it = aStart->mOrderedLT.Elements(); i < len; ++i, ++it) { + if (InTransitiveClosure(*it, aTarget)) { + return true; + } + } + return false; + } + + /** + * Return an array of all resource acquisitions + * aStart <_this r1 <_this r2 <_ ... <_ aTarget + * from which |aStart <_this aTarget| was deduced, including + * |aStart| and |aTarget|. + * + * Nb: there may be multiple deductions of |aStart <_this + * aTarget|. This function returns the first ordering found by + * depth-first search. + * + * Nb: |InTransitiveClosure| could be replaced by this function. + * However, this one is more expensive because we record the DFS + * search stack on the heap whereas the other doesn't. + * + * @precondition |aStart != aTarget| + */ + ResourceAcquisitionArray* GetDeductionChain(const OrderingEntry* aStart, + const OrderingEntry* aTarget) + { + ResourceAcquisitionArray* chain = new ResourceAcquisitionArray(); + if (!chain) { + NS_RUNTIMEABORT("can't allocate dep. cycle array"); + } + chain->AppendElement(aStart->mResource); + + NS_ASSERTION(GetDeductionChain_Helper(aStart, aTarget, chain), + "GetDeductionChain called when there's no deadlock"); + return chain; + } + + // precondition: |aStart != aTarget| + // invariant: |aStart| is the last element in |aChain| + bool GetDeductionChain_Helper(const OrderingEntry* aStart, + const OrderingEntry* aTarget, + ResourceAcquisitionArray* aChain) + { + if (aStart->mOrderedLT.BinaryIndexOf(aTarget) != NoIndex) { + aChain->AppendElement(aTarget->mResource); + return true; + } + + index_type i = 0; + size_type len = aStart->mOrderedLT.Length(); + for (auto it = aStart->mOrderedLT.Elements(); i < len; ++i, ++it) { + aChain->AppendElement((*it)->mResource); + if (GetDeductionChain_Helper(*it, aTarget, aChain)) { + return true; + } + aChain->RemoveElementAt(aChain->Length() - 1); + } + return false; + } + + /** + * The partial order on resource acquisitions used by the deadlock + * detector. + */ + nsClassHashtable<nsPtrHashKey<const T>, OrderingEntry> mOrdering; + + + /** + * Protects contentious methods. + * Nb: can't use mozilla::Mutex since we are used as its deadlock + * detector. + */ + PRLock* mLock; + +private: + DeadlockDetector(const DeadlockDetector& aDD) = delete; + DeadlockDetector& operator=(const DeadlockDetector& aDD) = delete; +}; + + +template<typename T> +// FIXME bug 456272: tune based on average workload +const uint32_t DeadlockDetector<T>::kDefaultNumBuckets = 32; + + +} // namespace mozilla + +#endif // ifndef mozilla_DeadlockDetector_h diff --git a/xpcom/glue/EnumeratedArrayCycleCollection.h b/xpcom/glue/EnumeratedArrayCycleCollection.h new file mode 100644 index 0000000000..faabb5a529 --- /dev/null +++ b/xpcom/glue/EnumeratedArrayCycleCollection.h @@ -0,0 +1,43 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef EnumeratedArrayCycleCollection_h_ +#define EnumeratedArrayCycleCollection_h_ + +#include "mozilla/EnumeratedArray.h" +#include "nsCycleCollectionTraversalCallback.h" + +template<typename IndexType, + IndexType SizeAsEnumValue, + typename ValueType> +inline void +ImplCycleCollectionUnlink(mozilla::EnumeratedArray<IndexType, + SizeAsEnumValue, + ValueType>& aField) +{ + for (size_t i = 0; i < size_t(SizeAsEnumValue); ++i) { + aField[IndexType(i)] = nullptr; + } +} + +template<typename IndexType, + IndexType SizeAsEnumValue, + typename ValueType> +inline void +ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback& aCallback, + mozilla::EnumeratedArray<IndexType, + SizeAsEnumValue, + ValueType>& aField, + const char* aName, + uint32_t aFlags = 0) +{ + aFlags |= CycleCollectionEdgeNameArrayFlag; + for (size_t i = 0; i < size_t(SizeAsEnumValue); ++i) { + ImplCycleCollectionTraverse(aCallback, aField[IndexType(i)], aName, aFlags); + } +} + +#endif // EnumeratedArrayCycleCollection_h_ diff --git a/xpcom/glue/FileUtils.cpp b/xpcom/glue/FileUtils.cpp new file mode 100644 index 0000000000..699812461f --- /dev/null +++ b/xpcom/glue/FileUtils.cpp @@ -0,0 +1,568 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include <errno.h> +#include <stdio.h> + +#include "nscore.h" +#include "nsStringGlue.h" +#include "private/pprio.h" +#include "mozilla/Assertions.h" +#include "mozilla/FileUtils.h" + +#if defined(XP_MACOSX) +#include <fcntl.h> +#include <unistd.h> +#include <mach/machine.h> +#include <mach-o/fat.h> +#include <mach-o/loader.h> +#include <sys/mman.h> +#include <sys/stat.h> +#include <limits.h> +#elif defined(XP_UNIX) +#include <fcntl.h> +#include <unistd.h> +#if defined(LINUX) +#include <elf.h> +#endif +#include <sys/types.h> +#include <sys/stat.h> +#elif defined(XP_WIN) +#include <windows.h> +#endif + +// Functions that are not to be used in standalone glue must be implemented +// within this #if block +#if !defined(XPCOM_GLUE) + +bool +mozilla::fallocate(PRFileDesc* aFD, int64_t aLength) +{ +#if defined(HAVE_POSIX_FALLOCATE) + return posix_fallocate(PR_FileDesc2NativeHandle(aFD), 0, aLength) == 0; +#elif defined(XP_WIN) + int64_t oldpos = PR_Seek64(aFD, 0, PR_SEEK_CUR); + if (oldpos == -1) { + return false; + } + + if (PR_Seek64(aFD, aLength, PR_SEEK_SET) != aLength) { + return false; + } + + bool retval = (0 != SetEndOfFile((HANDLE)PR_FileDesc2NativeHandle(aFD))); + + PR_Seek64(aFD, oldpos, PR_SEEK_SET); + return retval; +#elif defined(XP_MACOSX) + int fd = PR_FileDesc2NativeHandle(aFD); + fstore_t store = {F_ALLOCATECONTIG, F_PEOFPOSMODE, 0, aLength}; + // Try to get a continous chunk of disk space + int ret = fcntl(fd, F_PREALLOCATE, &store); + if (ret == -1) { + // OK, perhaps we are too fragmented, allocate non-continuous + store.fst_flags = F_ALLOCATEALL; + ret = fcntl(fd, F_PREALLOCATE, &store); + if (ret == -1) { + return false; + } + } + return ftruncate(fd, aLength) == 0; +#elif defined(XP_UNIX) + // The following is copied from fcntlSizeHint in sqlite + /* If the OS does not have posix_fallocate(), fake it. First use + ** ftruncate() to set the file size, then write a single byte to + ** the last byte in each block within the extended region. This + ** is the same technique used by glibc to implement posix_fallocate() + ** on systems that do not have a real fallocate() system call. + */ + int64_t oldpos = PR_Seek64(aFD, 0, PR_SEEK_CUR); + if (oldpos == -1) { + return false; + } + + struct stat buf; + int fd = PR_FileDesc2NativeHandle(aFD); + if (fstat(fd, &buf)) { + return false; + } + + if (buf.st_size >= aLength) { + return false; + } + + const int nBlk = buf.st_blksize; + + if (!nBlk) { + return false; + } + + if (ftruncate(fd, aLength)) { + return false; + } + + int nWrite; // Return value from write() + int64_t iWrite = ((buf.st_size + 2 * nBlk - 1) / nBlk) * nBlk - 1; // Next offset to write to + while (iWrite < aLength) { + nWrite = 0; + if (PR_Seek64(aFD, iWrite, PR_SEEK_SET) == iWrite) { + nWrite = PR_Write(aFD, "", 1); + } + if (nWrite != 1) { + break; + } + iWrite += nBlk; + } + + PR_Seek64(aFD, oldpos, PR_SEEK_SET); + return nWrite == 1; +#endif + return false; +} + +#ifdef ReadSysFile_PRESENT + +bool +mozilla::ReadSysFile( + const char* aFilename, + char* aBuf, + size_t aBufSize) +{ + int fd = MOZ_TEMP_FAILURE_RETRY(open(aFilename, O_RDONLY)); + if (fd < 0) { + return false; + } + ScopedClose autoClose(fd); + if (aBufSize == 0) { + return true; + } + ssize_t bytesRead; + size_t offset = 0; + do { + bytesRead = MOZ_TEMP_FAILURE_RETRY(read(fd, aBuf + offset, + aBufSize - offset)); + if (bytesRead == -1) { + return false; + } + offset += bytesRead; + } while (bytesRead > 0 && offset < aBufSize); + MOZ_ASSERT(offset <= aBufSize); + if (offset > 0 && aBuf[offset - 1] == '\n') { + offset--; + } + if (offset == aBufSize) { + MOZ_ASSERT(offset > 0); + offset--; + } + aBuf[offset] = '\0'; + return true; +} + +bool +mozilla::ReadSysFile( + const char* aFilename, + int* aVal) +{ + char valBuf[32]; + if (!ReadSysFile(aFilename, valBuf, sizeof(valBuf))) { + return false; + } + return sscanf(valBuf, "%d", aVal) == 1; +} + +bool +mozilla::ReadSysFile( + const char* aFilename, + bool* aVal) +{ + int v; + if (!ReadSysFile(aFilename, &v)) { + return false; + } + *aVal = (v != 0); + return true; +} + +#endif /* ReadSysFile_PRESENT */ + +#ifdef WriteSysFile_PRESENT + +bool +mozilla::WriteSysFile( + const char* aFilename, + const char* aBuf) +{ + size_t aBufSize = strlen(aBuf); + int fd = MOZ_TEMP_FAILURE_RETRY(open(aFilename, O_WRONLY)); + if (fd < 0) { + return false; + } + ScopedClose autoClose(fd); + ssize_t bytesWritten; + size_t offset = 0; + do { + bytesWritten = MOZ_TEMP_FAILURE_RETRY(write(fd, aBuf + offset, + aBufSize - offset)); + if (bytesWritten == -1) { + return false; + } + offset += bytesWritten; + } while (bytesWritten > 0 && offset < aBufSize); + MOZ_ASSERT(offset == aBufSize); + return true; +} + +#endif /* WriteSysFile_PRESENT */ + +void +mozilla::ReadAheadLib(nsIFile* aFile) +{ +#if defined(XP_WIN) + nsAutoString path; + if (!aFile || NS_FAILED(aFile->GetPath(path))) { + return; + } + ReadAheadLib(path.get()); +#elif defined(LINUX) && !defined(ANDROID) || defined(XP_MACOSX) + nsAutoCString nativePath; + if (!aFile || NS_FAILED(aFile->GetNativePath(nativePath))) { + return; + } + ReadAheadLib(nativePath.get()); +#endif +} + +void +mozilla::ReadAheadFile(nsIFile* aFile, const size_t aOffset, + const size_t aCount, mozilla::filedesc_t* aOutFd) +{ +#if defined(XP_WIN) + nsAutoString path; + if (!aFile || NS_FAILED(aFile->GetPath(path))) { + return; + } + ReadAheadFile(path.get(), aOffset, aCount, aOutFd); +#elif defined(LINUX) && !defined(ANDROID) || defined(XP_MACOSX) + nsAutoCString nativePath; + if (!aFile || NS_FAILED(aFile->GetNativePath(nativePath))) { + return; + } + ReadAheadFile(nativePath.get(), aOffset, aCount, aOutFd); +#endif +} + +#endif // !defined(XPCOM_GLUE) + +#if defined(LINUX) && !defined(ANDROID) + +static const unsigned int bufsize = 4096; + +#ifdef __LP64__ +typedef Elf64_Ehdr Elf_Ehdr; +typedef Elf64_Phdr Elf_Phdr; +static const unsigned char ELFCLASS = ELFCLASS64; +typedef Elf64_Off Elf_Off; +#else +typedef Elf32_Ehdr Elf_Ehdr; +typedef Elf32_Phdr Elf_Phdr; +static const unsigned char ELFCLASS = ELFCLASS32; +typedef Elf32_Off Elf_Off; +#endif + +#elif defined(XP_MACOSX) + +#if defined(__i386__) +static const uint32_t CPU_TYPE = CPU_TYPE_X86; +#elif defined(__x86_64__) +static const uint32_t CPU_TYPE = CPU_TYPE_X86_64; +#elif defined(__ppc__) +static const uint32_t CPU_TYPE = CPU_TYPE_POWERPC; +#elif defined(__ppc64__) +static const uint32_t CPU_TYPE = CPU_TYPE_POWERPC64; +#else +#error Unsupported CPU type +#endif + +#ifdef __LP64__ +#undef LC_SEGMENT +#define LC_SEGMENT LC_SEGMENT_64 +#undef MH_MAGIC +#define MH_MAGIC MH_MAGIC_64 +#define cpu_mach_header mach_header_64 +#define segment_command segment_command_64 +#else +#define cpu_mach_header mach_header +#endif + +class ScopedMMap +{ +public: + explicit ScopedMMap(const char* aFilePath) + : buf(nullptr) + { + fd = open(aFilePath, O_RDONLY); + if (fd < 0) { + return; + } + struct stat st; + if (fstat(fd, &st) < 0) { + return; + } + size = st.st_size; + buf = (char*)mmap(nullptr, size, PROT_READ, MAP_PRIVATE, fd, 0); + } + ~ScopedMMap() + { + if (buf) { + munmap(buf, size); + } + if (fd >= 0) { + close(fd); + } + } + operator char*() { return buf; } + int getFd() { return fd; } +private: + int fd; + char* buf; + size_t size; +}; +#endif + +void +mozilla::ReadAhead(mozilla::filedesc_t aFd, const size_t aOffset, + const size_t aCount) +{ +#if defined(XP_WIN) + + LARGE_INTEGER fpOriginal; + LARGE_INTEGER fpOffset; +#if defined(HAVE_LONG_LONG) + fpOffset.QuadPart = 0; +#else + fpOffset.u.LowPart = 0; + fpOffset.u.HighPart = 0; +#endif + + // Get the current file pointer so that we can restore it. This isn't + // really necessary other than to provide the same semantics regarding the + // file pointer that other platforms do + if (!SetFilePointerEx(aFd, fpOffset, &fpOriginal, FILE_CURRENT)) { + return; + } + + if (aOffset) { +#if defined(HAVE_LONG_LONG) + fpOffset.QuadPart = static_cast<LONGLONG>(aOffset); +#else + fpOffset.u.LowPart = aOffset; + fpOffset.u.HighPart = 0; +#endif + + if (!SetFilePointerEx(aFd, fpOffset, nullptr, FILE_BEGIN)) { + return; + } + } + + char buf[64 * 1024]; + size_t totalBytesRead = 0; + DWORD dwBytesRead; + // Do dummy reads to trigger kernel-side readhead via FILE_FLAG_SEQUENTIAL_SCAN. + // Abort when underfilling because during testing the buffers are read fully + // A buffer that's not keeping up would imply that readahead isn't working right + while (totalBytesRead < aCount && + ReadFile(aFd, buf, sizeof(buf), &dwBytesRead, nullptr) && + dwBytesRead == sizeof(buf)) { + totalBytesRead += dwBytesRead; + } + + // Restore the file pointer + SetFilePointerEx(aFd, fpOriginal, nullptr, FILE_BEGIN); + +#elif defined(LINUX) && !defined(ANDROID) + + readahead(aFd, aOffset, aCount); + +#elif defined(XP_MACOSX) + + struct radvisory ra; + ra.ra_offset = aOffset; + ra.ra_count = aCount; + // The F_RDADVISE fcntl is equivalent to Linux' readahead() system call. + fcntl(aFd, F_RDADVISE, &ra); + +#endif +} + +void +mozilla::ReadAheadLib(mozilla::pathstr_t aFilePath) +{ + if (!aFilePath) { + return; + } +#if defined(XP_WIN) + ReadAheadFile(aFilePath); +#elif defined(LINUX) && !defined(ANDROID) + int fd = open(aFilePath, O_RDONLY); + if (fd < 0) { + return; + } + + union + { + char buf[bufsize]; + Elf_Ehdr ehdr; + } elf; + // Read ELF header (ehdr) and program header table (phdr). + // We check that the ELF magic is found, that the ELF class matches + // our own, and that the program header table as defined in the ELF + // headers fits in the buffer we read. + if ((read(fd, elf.buf, bufsize) <= 0) || + (memcmp(elf.buf, ELFMAG, 4)) || + (elf.ehdr.e_ident[EI_CLASS] != ELFCLASS) || + // Upcast e_phentsize so the multiplication is done in the same precision + // as the subsequent addition, to satisfy static analyzers and avoid + // issues with abnormally large program header tables. + (elf.ehdr.e_phoff + (static_cast<Elf_Off>(elf.ehdr.e_phentsize) * + elf.ehdr.e_phnum) >= bufsize)) { + close(fd); + return; + } + // The program header table contains segment definitions. One such + // segment type is PT_LOAD, which describes how the dynamic loader + // is going to map the file in memory. We use that information to + // find the biggest offset from the library that will be mapped in + // memory. + Elf_Phdr* phdr = (Elf_Phdr*)&elf.buf[elf.ehdr.e_phoff]; + Elf_Off end = 0; + for (int phnum = elf.ehdr.e_phnum; phnum; phdr++, phnum--) { + if ((phdr->p_type == PT_LOAD) && + (end < phdr->p_offset + phdr->p_filesz)) { + end = phdr->p_offset + phdr->p_filesz; + } + } + // Let the kernel read ahead what the dynamic loader is going to + // map in memory soon after. + if (end > 0) { + ReadAhead(fd, 0, end); + } + close(fd); +#elif defined(XP_MACOSX) + ScopedMMap buf(aFilePath); + char* base = buf; + if (!base) { + return; + } + + // An OSX binary might either be a fat (universal) binary or a + // Mach-O binary. A fat binary actually embeds several Mach-O + // binaries. If we have a fat binary, find the offset where the + // Mach-O binary for our CPU type can be found. + struct fat_header* fh = (struct fat_header*)base; + + if (OSSwapBigToHostInt32(fh->magic) == FAT_MAGIC) { + uint32_t nfat_arch = OSSwapBigToHostInt32(fh->nfat_arch); + struct fat_arch* arch = (struct fat_arch*)&buf[sizeof(struct fat_header)]; + for (; nfat_arch; arch++, nfat_arch--) { + if (OSSwapBigToHostInt32(arch->cputype) == CPU_TYPE) { + base += OSSwapBigToHostInt32(arch->offset); + break; + } + } + if (base == buf) { + return; + } + } + + // Check Mach-O magic in the Mach header + struct cpu_mach_header* mh = (struct cpu_mach_header*)base; + if (mh->magic != MH_MAGIC) { + return; + } + + // The Mach header is followed by a sequence of load commands. + // Each command has a header containing the command type and the + // command size. LD_SEGMENT commands describes how the dynamic + // loader is going to map the file in memory. We use that + // information to find the biggest offset from the library that + // will be mapped in memory. + char* cmd = &base[sizeof(struct cpu_mach_header)]; + uint32_t end = 0; + for (uint32_t ncmds = mh->ncmds; ncmds; ncmds--) { + struct segment_command* sh = (struct segment_command*)cmd; + if (sh->cmd != LC_SEGMENT) { + continue; + } + if (end < sh->fileoff + sh->filesize) { + end = sh->fileoff + sh->filesize; + } + cmd += sh->cmdsize; + } + // Let the kernel read ahead what the dynamic loader is going to + // map in memory soon after. + if (end > 0) { + ReadAhead(buf.getFd(), base - buf, end); + } +#endif +} + +void +mozilla::ReadAheadFile(mozilla::pathstr_t aFilePath, const size_t aOffset, + const size_t aCount, mozilla::filedesc_t* aOutFd) +{ +#if defined(XP_WIN) + if (!aFilePath) { + if (aOutFd) { + *aOutFd = INVALID_HANDLE_VALUE; + } + return; + } + HANDLE fd = CreateFileW(aFilePath, GENERIC_READ, FILE_SHARE_READ, nullptr, + OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, nullptr); + if (aOutFd) { + *aOutFd = fd; + } + if (fd == INVALID_HANDLE_VALUE) { + return; + } + ReadAhead(fd, aOffset, aCount); + if (!aOutFd) { + CloseHandle(fd); + } +#elif defined(LINUX) && !defined(ANDROID) || defined(XP_MACOSX) + if (!aFilePath) { + if (aOutFd) { + *aOutFd = -1; + } + return; + } + int fd = open(aFilePath, O_RDONLY); + if (aOutFd) { + *aOutFd = fd; + } + if (fd < 0) { + return; + } + size_t count; + if (aCount == SIZE_MAX) { + struct stat st; + if (fstat(fd, &st) < 0) { + if (!aOutFd) { + close(fd); + } + return; + } + count = st.st_size; + } else { + count = aCount; + } + ReadAhead(fd, aOffset, count); + if (!aOutFd) { + close(fd); + } +#endif +} + diff --git a/xpcom/glue/FileUtils.h b/xpcom/glue/FileUtils.h new file mode 100644 index 0000000000..aaf912b21b --- /dev/null +++ b/xpcom/glue/FileUtils.h @@ -0,0 +1,220 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_FileUtils_h +#define mozilla_FileUtils_h + +#include "nscore.h" // nullptr + +#if defined(XP_UNIX) +# include <unistd.h> +#elif defined(XP_WIN) +# include <io.h> +#endif +#include "prio.h" + +#include "mozilla/Scoped.h" +#include "nsIFile.h" +#include <errno.h> +#include <limits.h> + +namespace mozilla { + +#if defined(XP_WIN) +typedef void* filedesc_t; +typedef const wchar_t* pathstr_t; +#else +typedef int filedesc_t; +typedef const char* pathstr_t; +#endif + +/** + * ScopedCloseFD is a RAII wrapper for POSIX file descriptors + * + * Instances |close()| their fds when they go out of scope. + */ +struct ScopedCloseFDTraits +{ + typedef int type; + static type empty() { return -1; } + static void release(type aFd) + { + if (aFd != -1) { + while (close(aFd) == -1 && errno == EINTR) { + } + } + } +}; +typedef Scoped<ScopedCloseFDTraits> ScopedClose; + +#if !defined(XPCOM_GLUE) + +/** + * AutoFDClose is a RAII wrapper for PRFileDesc. + * + * Instances |PR_Close| their fds when they go out of scope. + **/ +struct ScopedClosePRFDTraits +{ + typedef PRFileDesc* type; + static type empty() { return nullptr; } + static void release(type aFd) + { + if (aFd) { + PR_Close(aFd); + } + } +}; +typedef Scoped<ScopedClosePRFDTraits> AutoFDClose; + +/* RAII wrapper for FILE descriptors */ +struct ScopedCloseFileTraits +{ + typedef FILE* type; + static type empty() { return nullptr; } + static void release(type aFile) + { + if (aFile) { + fclose(aFile); + } + } +}; +typedef Scoped<ScopedCloseFileTraits> ScopedCloseFile; + +/** + * Fallocate efficiently and continuously allocates files via fallocate-type APIs. + * This is useful for avoiding fragmentation. + * On sucess the file be padded with zeros to grow to aLength. + * + * @param aFD file descriptor. + * @param aLength length of file to grow to. + * @return true on success. + */ +bool fallocate(PRFileDesc* aFD, int64_t aLength); + +/** + * Use readahead to preload shared libraries into the file cache before loading. + * WARNING: This function should not be used without a telemetry field trial + * demonstrating a clear performance improvement! + * + * @param aFile nsIFile representing path to shared library + */ +void ReadAheadLib(nsIFile* aFile); + +/** + * Use readahead to preload a file into the file cache before reading. + * WARNING: This function should not be used without a telemetry field trial + * demonstrating a clear performance improvement! + * + * @param aFile nsIFile representing path to shared library + * @param aOffset Offset into the file to begin preloading + * @param aCount Number of bytes to preload (SIZE_MAX implies file size) + * @param aOutFd Pointer to file descriptor. If specified, ReadAheadFile will + * return its internal, opened file descriptor instead of closing it. + */ +void ReadAheadFile(nsIFile* aFile, const size_t aOffset = 0, + const size_t aCount = SIZE_MAX, + filedesc_t* aOutFd = nullptr); + +#endif // !defined(XPCOM_GLUE) + +/** + * Use readahead to preload shared libraries into the file cache before loading. + * WARNING: This function should not be used without a telemetry field trial + * demonstrating a clear performance improvement! + * + * @param aFilePath path to shared library + */ +void ReadAheadLib(pathstr_t aFilePath); + +/** + * Use readahead to preload a file into the file cache before loading. + * WARNING: This function should not be used without a telemetry field trial + * demonstrating a clear performance improvement! + * + * @param aFilePath path to shared library + * @param aOffset Offset into the file to begin preloading + * @param aCount Number of bytes to preload (SIZE_MAX implies file size) + * @param aOutFd Pointer to file descriptor. If specified, ReadAheadFile will + * return its internal, opened file descriptor instead of closing it. + */ +void ReadAheadFile(pathstr_t aFilePath, const size_t aOffset = 0, + const size_t aCount = SIZE_MAX, + filedesc_t* aOutFd = nullptr); + +/** + * Use readahead to preload a file into the file cache before reading. + * When this function exits, the file pointer is guaranteed to be in the same + * position it was in before this function was called. + * WARNING: This function should not be used without a telemetry field trial + * demonstrating a clear performance improvement! + * + * @param aFd file descriptor opened for read access + * (on Windows, file must be opened with FILE_FLAG_SEQUENTIAL_SCAN) + * @param aOffset Offset into the file to begin preloading + * @param aCount Number of bytes to preload (SIZE_MAX implies file size) + */ +void ReadAhead(filedesc_t aFd, const size_t aOffset = 0, + const size_t aCount = SIZE_MAX); + + +#if defined(MOZ_WIDGET_GONK) || defined(XP_UNIX) +#define MOZ_TEMP_FAILURE_RETRY(exp) (__extension__({ \ + typeof (exp) _rc; \ + do { \ + _rc = (exp); \ + } while (_rc == -1 && errno == EINTR); \ + _rc; \ +})) +#endif + +/* Define ReadSysFile() and WriteSysFile() only on GONK to avoid unnecessary + * libxul bloat. Also define it in debug builds, so that unit tests for it can + * be written and run in non-GONK builds. */ +#if (defined(MOZ_WIDGET_GONK) || defined(DEBUG)) && defined(XP_UNIX) + +#ifndef ReadSysFile_PRESENT +#define ReadSysFile_PRESENT +#endif /* ReadSysFile_PRESENT */ + +#ifndef WriteSysFile_PRESENT +#define WriteSysFile_PRESENT +#endif /* WriteSysFile_PRESENT */ + +/** + * Read the contents of a file. + * This function is intended for reading a single-lined text files from + * /sys/. If the file ends with a newline ('\n') then it will be discarded. + * The output buffer will always be '\0'-terminated on successful completion. + * If aBufSize == 0, then this function will return true if the file exists + * and is readable (it will not attempt to read anything from it). + * On failure the contents of aBuf after this call will be undefined and the + * value of the global variable errno will be set accordingly. + * @return true on success, notice that less than requested bytes could have + * been read if the file was smaller + */ +bool ReadSysFile(const char* aFilename, char* aBuf, size_t aBufSize); + +/** + * Parse the contents of a file, assuming it contains a decimal integer. + * @return true on success + */ +bool ReadSysFile(const char* aFilename, int* aVal); + +/** + * Parse the contents of a file, assuming it contains a boolean value + * (either 0 or 1). + * @return true on success + */ +bool ReadSysFile(const char* aFilename, bool* aVal); + +bool WriteSysFile(const char* aFilename, const char* aBuf); + +#endif /* (MOZ_WIDGET_GONK || DEBUG) && XP_UNIX */ + +} // namespace mozilla + +#endif diff --git a/xpcom/glue/GenericFactory.cpp b/xpcom/glue/GenericFactory.cpp new file mode 100644 index 0000000000..5bf0b97a4f --- /dev/null +++ b/xpcom/glue/GenericFactory.cpp @@ -0,0 +1,27 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/GenericFactory.h" + +namespace mozilla { + +NS_IMPL_ISUPPORTS(GenericFactory, nsIFactory) + +NS_IMETHODIMP +GenericFactory::CreateInstance(nsISupports* aOuter, REFNSIID aIID, + void** aResult) +{ + return mCtor(aOuter, aIID, aResult); +} + +NS_IMETHODIMP +GenericFactory::LockFactory(bool aLock) +{ + NS_ERROR("Vestigial method, never called!"); + return NS_ERROR_FAILURE; +} + +} // namespace mozilla diff --git a/xpcom/glue/GenericFactory.h b/xpcom/glue/GenericFactory.h new file mode 100644 index 0000000000..4561f2a2d0 --- /dev/null +++ b/xpcom/glue/GenericFactory.h @@ -0,0 +1,43 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_GenericFactory_h +#define mozilla_GenericFactory_h + +#include "mozilla/Attributes.h" + +#include "mozilla/Module.h" + +namespace mozilla { + +/** + * A generic factory which uses a constructor function to create instances. + * This class is intended for use by the component manager and the generic + * module. + */ +class GenericFactory final : public nsIFactory +{ + ~GenericFactory() {} + +public: + typedef Module::ConstructorProcPtr ConstructorProcPtr; + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIFACTORY + + explicit GenericFactory(ConstructorProcPtr aCtor) + : mCtor(aCtor) + { + NS_ASSERTION(mCtor, "GenericFactory with no constructor"); + } + +private: + ConstructorProcPtr mCtor; +}; + +} // namespace mozilla + +#endif // mozilla_GenericFactory_h diff --git a/xpcom/glue/GenericModule.cpp b/xpcom/glue/GenericModule.cpp new file mode 100644 index 0000000000..0b5cd3a7c2 --- /dev/null +++ b/xpcom/glue/GenericModule.cpp @@ -0,0 +1,98 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/ModuleUtils.h" +#include "mozilla/GenericFactory.h" + +#include "nsICategoryManager.h" +#include "nsIComponentManager.h" +#include "nsIComponentRegistrar.h" +#include "nsServiceManagerUtils.h" +#include "nsXPCOMCID.h" +#include "nsStringAPI.h" + +namespace mozilla { + +NS_IMPL_ISUPPORTS(GenericModule, nsIModule) + +NS_IMETHODIMP +GenericModule::GetClassObject(nsIComponentManager* aCompMgr, + const nsCID& aCID, + const nsIID& aIID, + void** aResult) +{ + for (const Module::CIDEntry* e = mData->mCIDs; e->cid; ++e) { + if (e->cid->Equals(aCID)) { + nsCOMPtr<nsIFactory> f; + if (e->getFactoryProc) { + f = e->getFactoryProc(*mData, *e); + } else { + NS_ASSERTION(e->constructorProc, "No constructor proc?"); + f = new GenericFactory(e->constructorProc); + } + if (!f) { + return NS_ERROR_FAILURE; + } + + return f->QueryInterface(aIID, aResult); + } + } + NS_ERROR("Asking a module for a CID it doesn't implement."); + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +GenericModule::RegisterSelf(nsIComponentManager* aCompMgr, + nsIFile* aLocation, + const char* aLoaderStr, + const char* aType) +{ + nsCOMPtr<nsIComponentRegistrar> registrar = do_QueryInterface(aCompMgr); + for (const Module::CIDEntry* e = mData->mCIDs; e->cid; ++e) { + registrar->RegisterFactoryLocation(*e->cid, "", nullptr, aLocation, + aLoaderStr, aType); + } + + for (const Module::ContractIDEntry* e = mData->mContractIDs; + e && e->contractid; + ++e) { + registrar->RegisterFactoryLocation(*e->cid, "", e->contractid, aLocation, + aLoaderStr, aType); + } + + nsCOMPtr<nsICategoryManager> catman; + for (const Module::CategoryEntry* e = mData->mCategoryEntries; + e && e->category; + ++e) { + if (!catman) { + catman = do_GetService(NS_CATEGORYMANAGER_CONTRACTID); + } + + nsAutoCString prevValue; + catman->AddCategoryEntry(e->category, e->entry, e->value, true, true, + getter_Copies(prevValue)); + } + return NS_OK; +} + +NS_IMETHODIMP +GenericModule::UnregisterSelf(nsIComponentManager* aCompMgr, + nsIFile* aFile, + const char* aLoaderStr) +{ + NS_ERROR("Nobody should ever call UnregisterSelf!"); + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +GenericModule::CanUnload(nsIComponentManager* aCompMgr, bool* aResult) +{ + NS_ERROR("Nobody should ever call CanUnload!"); + *aResult = false; + return NS_OK; +} + +} // namespace mozilla diff --git a/xpcom/glue/IntentionalCrash.h b/xpcom/glue/IntentionalCrash.h new file mode 100644 index 0000000000..d01006dff1 --- /dev/null +++ b/xpcom/glue/IntentionalCrash.h @@ -0,0 +1,58 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include <string> +#include <sstream> +#include <stdlib.h> +#include <stdio.h> + +#ifdef XP_WIN +#include <process.h> +#define getpid _getpid +#else +#include <unistd.h> +#endif + +#ifndef mozilla_IntentionalCrash_h +#define mozilla_IntentionalCrash_h + +namespace mozilla { + +inline void +NoteIntentionalCrash(const char* aProcessType) +{ + char* f = getenv("XPCOM_MEM_BLOAT_LOG"); + if (!f) { + return; + } + + fprintf(stderr, "XPCOM_MEM_BLOAT_LOG: %s\n", f); + + std::string bloatLog(f); + + bool hasExt = false; + if (bloatLog.size() >= 4 && + bloatLog.compare(bloatLog.size() - 4, 4, ".log", 4) == 0) { + hasExt = true; + bloatLog.erase(bloatLog.size() - 4, 4); + } + + std::ostringstream bloatName; + bloatName << bloatLog << "_" << aProcessType << "_pid" << getpid(); + if (hasExt) { + bloatName << ".log"; + } + + fprintf(stderr, "Writing to log: %s\n", bloatName.str().c_str()); + + FILE* processfd = fopen(bloatName.str().c_str(), "a"); + fprintf(processfd, "==> process %d will purposefully crash\n", getpid()); + fclose(processfd); +} + +} // namespace mozilla + +#endif // mozilla_IntentionalCrash_h diff --git a/xpcom/glue/MainThreadUtils.h b/xpcom/glue/MainThreadUtils.h new file mode 100644 index 0000000000..8d76f84ec5 --- /dev/null +++ b/xpcom/glue/MainThreadUtils.h @@ -0,0 +1,42 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MainThreadUtils_h_ +#define MainThreadUtils_h_ + +#include "nscore.h" + +class nsIThread; + +/** + * Get a reference to the main thread. + * + * @param aResult + * The resulting nsIThread object. + */ +extern nsresult NS_GetMainThread(nsIThread** aResult); + +#ifdef MOZILLA_INTERNAL_API +// Fast access to the current thread. Do not release the returned pointer! If +// you want to use this pointer from some other thread, then you will need to +// AddRef it. Otherwise, you should only consider this pointer valid from code +// running on the current thread. +extern nsIThread* NS_GetCurrentThread(); +#endif + +#ifdef MOZILLA_INTERNAL_API +bool NS_IsMainThread(); +#else +/** + * Test to see if the current thread is the main thread. + * + * @returns true if the current thread is the main thread, and false + * otherwise. + */ +extern bool NS_IsMainThread(); +#endif + +#endif // MainThreadUtils_h_ diff --git a/xpcom/glue/Monitor.h b/xpcom/glue/Monitor.h new file mode 100644 index 0000000000..60750acb33 --- /dev/null +++ b/xpcom/glue/Monitor.h @@ -0,0 +1,135 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_Monitor_h +#define mozilla_Monitor_h + +#include "mozilla/CondVar.h" +#include "mozilla/Mutex.h" + +namespace mozilla { + +/** + * Monitor provides a *non*-reentrant monitor: *not* a Java-style + * monitor. If your code needs support for reentrancy, use + * ReentrantMonitor instead. (Rarely should reentrancy be needed.) + * + * Instead of directly calling Monitor methods, it's safer and simpler + * to instead use the RAII wrappers MonitorAutoLock and + * MonitorAutoUnlock. + */ +class Monitor +{ +public: + explicit Monitor(const char* aName) + : mMutex(aName) + , mCondVar(mMutex, "[Monitor.mCondVar]") + { + } + + ~Monitor() {} + + void Lock() { mMutex.Lock(); } + void Unlock() { mMutex.Unlock(); } + + nsresult Wait(PRIntervalTime aInterval = PR_INTERVAL_NO_TIMEOUT) + { + return mCondVar.Wait(aInterval); + } + + nsresult Notify() { return mCondVar.Notify(); } + nsresult NotifyAll() { return mCondVar.NotifyAll(); } + + void AssertCurrentThreadOwns() const + { + mMutex.AssertCurrentThreadOwns(); + } + + void AssertNotCurrentThreadOwns() const + { + mMutex.AssertNotCurrentThreadOwns(); + } + +private: + Monitor(); + Monitor(const Monitor&); + Monitor& operator=(const Monitor&); + + Mutex mMutex; + CondVar mCondVar; +}; + +/** + * Lock the monitor for the lexical scope instances of this class are + * bound to (except for MonitorAutoUnlock in nested scopes). + * + * The monitor must be unlocked when instances of this class are + * created. + */ +class MOZ_STACK_CLASS MonitorAutoLock +{ +public: + explicit MonitorAutoLock(Monitor& aMonitor) + : mMonitor(&aMonitor) + { + mMonitor->Lock(); + } + + ~MonitorAutoLock() + { + mMonitor->Unlock(); + } + + nsresult Wait(PRIntervalTime aInterval = PR_INTERVAL_NO_TIMEOUT) + { + return mMonitor->Wait(aInterval); + } + + nsresult Notify() { return mMonitor->Notify(); } + nsresult NotifyAll() { return mMonitor->NotifyAll(); } + +private: + MonitorAutoLock(); + MonitorAutoLock(const MonitorAutoLock&); + MonitorAutoLock& operator=(const MonitorAutoLock&); + static void* operator new(size_t) CPP_THROW_NEW; + + Monitor* mMonitor; +}; + +/** + * Unlock the monitor for the lexical scope instances of this class + * are bound to (except for MonitorAutoLock in nested scopes). + * + * The monitor must be locked by the current thread when instances of + * this class are created. + */ +class MOZ_STACK_CLASS MonitorAutoUnlock +{ +public: + explicit MonitorAutoUnlock(Monitor& aMonitor) + : mMonitor(&aMonitor) + { + mMonitor->Unlock(); + } + + ~MonitorAutoUnlock() + { + mMonitor->Lock(); + } + +private: + MonitorAutoUnlock(); + MonitorAutoUnlock(const MonitorAutoUnlock&); + MonitorAutoUnlock& operator=(const MonitorAutoUnlock&); + static void* operator new(size_t) CPP_THROW_NEW; + + Monitor* mMonitor; +}; + +} // namespace mozilla + +#endif // mozilla_Monitor_h diff --git a/xpcom/glue/Mutex.h b/xpcom/glue/Mutex.h new file mode 100644 index 0000000000..16ad44f4c1 --- /dev/null +++ b/xpcom/glue/Mutex.h @@ -0,0 +1,229 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_Mutex_h +#define mozilla_Mutex_h + +#include "prlock.h" + +#include "mozilla/BlockingResourceBase.h" +#include "mozilla/GuardObjects.h" + +// +// Provides: +// +// - Mutex, a non-recursive mutex +// - MutexAutoLock, an RAII class for ensuring that Mutexes are properly +// locked and unlocked +// - MutexAutoUnlock, complementary sibling to MutexAutoLock +// +// - OffTheBooksMutex, a non-recursive mutex that doesn't do leak checking +// - OffTheBooksMutexAuto{Lock,Unlock} - Like MutexAuto{Lock,Unlock}, but for +// an OffTheBooksMutex. +// +// Using MutexAutoLock/MutexAutoUnlock etc. is MUCH preferred to making bare +// calls to Lock and Unlock. +// +namespace mozilla { + +/** + * OffTheBooksMutex is identical to Mutex, except that OffTheBooksMutex doesn't + * include leak checking. Sometimes you want to intentionally "leak" a mutex + * until shutdown; in these cases, OffTheBooksMutex is for you. + */ +class OffTheBooksMutex : BlockingResourceBase +{ +public: + /** + * @param aName A name which can reference this lock + * @returns If failure, nullptr + * If success, a valid Mutex* which must be destroyed + * by Mutex::DestroyMutex() + **/ + explicit OffTheBooksMutex(const char* aName) + : BlockingResourceBase(aName, eMutex) + { + mLock = PR_NewLock(); + if (!mLock) { + NS_RUNTIMEABORT("Can't allocate mozilla::Mutex"); + } + } + + ~OffTheBooksMutex() + { + NS_ASSERTION(mLock, + "improperly constructed Lock or double free"); + // NSPR does consistency checks for us + PR_DestroyLock(mLock); + mLock = 0; + } + +#ifndef DEBUG + /** + * Lock + * @see prlock.h + **/ + void Lock() { PR_Lock(mLock); } + + /** + * Unlock + * @see prlock.h + **/ + void Unlock() { PR_Unlock(mLock); } + + /** + * AssertCurrentThreadOwns + * @see prlock.h + **/ + void AssertCurrentThreadOwns() const {} + + /** + * AssertNotCurrentThreadOwns + * @see prlock.h + **/ + void AssertNotCurrentThreadOwns() const {} + +#else + void Lock(); + void Unlock(); + + void AssertCurrentThreadOwns() const + { + PR_ASSERT_CURRENT_THREAD_OWNS_LOCK(mLock); + } + + void AssertNotCurrentThreadOwns() const + { + // FIXME bug 476536 + } + +#endif // ifndef DEBUG + +private: + OffTheBooksMutex(); + OffTheBooksMutex(const OffTheBooksMutex&); + OffTheBooksMutex& operator=(const OffTheBooksMutex&); + + PRLock* mLock; + + friend class CondVar; + + // MozPromise needs to access mLock for debugging purpose. + template<typename, typename, bool> + friend class MozPromise; +}; + +/** + * Mutex + * When possible, use MutexAutoLock/MutexAutoUnlock to lock/unlock this + * mutex within a scope, instead of calling Lock/Unlock directly. + */ +class Mutex : public OffTheBooksMutex +{ +public: + explicit Mutex(const char* aName) + : OffTheBooksMutex(aName) + { + MOZ_COUNT_CTOR(Mutex); + } + + ~Mutex() + { + MOZ_COUNT_DTOR(Mutex); + } + +private: + Mutex(); + Mutex(const Mutex&); + Mutex& operator=(const Mutex&); +}; + +/** + * MutexAutoLock + * Acquires the Mutex when it enters scope, and releases it when it leaves + * scope. + * + * MUCH PREFERRED to bare calls to Mutex.Lock and Unlock. + */ +template<typename T> +class MOZ_RAII BaseAutoLock +{ +public: + /** + * Constructor + * The constructor aquires the given lock. The destructor + * releases the lock. + * + * @param aLock A valid mozilla::Mutex* returned by + * mozilla::Mutex::NewMutex. + **/ + explicit BaseAutoLock(T& aLock MOZ_GUARD_OBJECT_NOTIFIER_PARAM) + : mLock(&aLock) + { + MOZ_GUARD_OBJECT_NOTIFIER_INIT; + NS_ASSERTION(mLock, "null mutex"); + mLock->Lock(); + } + + ~BaseAutoLock(void) + { + mLock->Unlock(); + } + +private: + BaseAutoLock(); + BaseAutoLock(BaseAutoLock&); + BaseAutoLock& operator=(BaseAutoLock&); + static void* operator new(size_t) CPP_THROW_NEW; + + T* mLock; + MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER +}; + +typedef BaseAutoLock<Mutex> MutexAutoLock; +typedef BaseAutoLock<OffTheBooksMutex> OffTheBooksMutexAutoLock; + +/** + * MutexAutoUnlock + * Releases the Mutex when it enters scope, and re-acquires it when it leaves + * scope. + * + * MUCH PREFERRED to bare calls to Mutex.Unlock and Lock. + */ +template<typename T> +class MOZ_RAII BaseAutoUnlock +{ +public: + explicit BaseAutoUnlock(T& aLock MOZ_GUARD_OBJECT_NOTIFIER_PARAM) + : mLock(&aLock) + { + MOZ_GUARD_OBJECT_NOTIFIER_INIT; + NS_ASSERTION(mLock, "null lock"); + mLock->Unlock(); + } + + ~BaseAutoUnlock() + { + mLock->Lock(); + } + +private: + BaseAutoUnlock(); + BaseAutoUnlock(BaseAutoUnlock&); + BaseAutoUnlock& operator=(BaseAutoUnlock&); + static void* operator new(size_t) CPP_THROW_NEW; + + T* mLock; + MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER +}; + +typedef BaseAutoUnlock<Mutex> MutexAutoUnlock; +typedef BaseAutoUnlock<OffTheBooksMutex> OffTheBooksMutexAutoUnlock; + +} // namespace mozilla + + +#endif // ifndef mozilla_Mutex_h diff --git a/xpcom/glue/Observer.h b/xpcom/glue/Observer.h new file mode 100644 index 0000000000..958e5e4a96 --- /dev/null +++ b/xpcom/glue/Observer.h @@ -0,0 +1,83 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_Observer_h +#define mozilla_Observer_h + +#include "nsTArray.h" + +namespace mozilla { + +/** + * Observer<T> provides a way for a class to observe something. + * When an event has to be broadcasted to all Observer<T>, Notify() method + * is called. + * T represents the type of the object passed in argument to Notify(). + * + * @see ObserverList. + */ +template<class T> +class Observer +{ +public: + virtual ~Observer() {} + virtual void Notify(const T& aParam) = 0; +}; + +/** + * ObserverList<T> tracks Observer<T> and can notify them when Broadcast() is + * called. + * T represents the type of the object passed in argument to Broadcast() and + * sent to Observer<T> objects through Notify(). + * + * @see Observer. + */ +template<class T> +class ObserverList +{ +public: + /** + * Note: When calling AddObserver, it's up to the caller to make sure the + * object isn't going to be release as long as RemoveObserver hasn't been + * called. + * + * @see RemoveObserver() + */ + void AddObserver(Observer<T>* aObserver) + { + mObservers.AppendElement(aObserver); + } + + /** + * Remove the observer from the observer list. + * @return Whether the observer has been found in the list. + */ + bool RemoveObserver(Observer<T>* aObserver) + { + return mObservers.RemoveElement(aObserver); + } + + uint32_t Length() + { + return mObservers.Length(); + } + + void Broadcast(const T& aParam) + { + nsTArray<Observer<T>*> observersCopy(mObservers); + uint32_t size = observersCopy.Length(); + for (uint32_t i = 0; i < size; ++i) { + observersCopy[i]->Notify(aParam); + } + } + +protected: + nsTArray<Observer<T>*> mObservers; +}; + +} // namespace mozilla + +#endif // mozilla_Observer_h diff --git a/xpcom/glue/PLDHashTable.cpp b/xpcom/glue/PLDHashTable.cpp new file mode 100644 index 0000000000..6152e90003 --- /dev/null +++ b/xpcom/glue/PLDHashTable.cpp @@ -0,0 +1,801 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include <new> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include "PLDHashTable.h" +#include "mozilla/HashFunctions.h" +#include "mozilla/MathAlgorithms.h" +#include "mozilla/OperatorNewExtensions.h" +#include "nsAlgorithm.h" +#include "mozilla/Likely.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/ChaosMode.h" + +using namespace mozilla; + +#ifdef DEBUG + +class AutoReadOp +{ + Checker& mChk; +public: + explicit AutoReadOp(Checker& aChk) : mChk(aChk) { mChk.StartReadOp(); } + ~AutoReadOp() { mChk.EndReadOp(); } +}; + +class AutoWriteOp +{ + Checker& mChk; +public: + explicit AutoWriteOp(Checker& aChk) : mChk(aChk) { mChk.StartWriteOp(); } + ~AutoWriteOp() { mChk.EndWriteOp(); } +}; + +class AutoIteratorRemovalOp +{ + Checker& mChk; +public: + explicit AutoIteratorRemovalOp(Checker& aChk) + : mChk(aChk) + { + mChk.StartIteratorRemovalOp(); + } + ~AutoIteratorRemovalOp() { mChk.EndIteratorRemovalOp(); } +}; + +class AutoDestructorOp +{ + Checker& mChk; +public: + explicit AutoDestructorOp(Checker& aChk) + : mChk(aChk) + { + mChk.StartDestructorOp(); + } + ~AutoDestructorOp() { mChk.EndDestructorOp(); } +}; + +#endif + +/* static */ PLDHashNumber +PLDHashTable::HashStringKey(const void* aKey) +{ + return HashString(static_cast<const char*>(aKey)); +} + +/* static */ PLDHashNumber +PLDHashTable::HashVoidPtrKeyStub(const void* aKey) +{ + return (PLDHashNumber)(ptrdiff_t)aKey >> 2; +} + +/* static */ bool +PLDHashTable::MatchEntryStub(const PLDHashEntryHdr* aEntry, const void* aKey) +{ + const PLDHashEntryStub* stub = (const PLDHashEntryStub*)aEntry; + + return stub->key == aKey; +} + +/* static */ bool +PLDHashTable::MatchStringKey(const PLDHashEntryHdr* aEntry, const void* aKey) +{ + const PLDHashEntryStub* stub = (const PLDHashEntryStub*)aEntry; + + // XXX tolerate null keys on account of sloppy Mozilla callers. + return stub->key == aKey || + (stub->key && aKey && + strcmp((const char*)stub->key, (const char*)aKey) == 0); +} + +/* static */ void +PLDHashTable::MoveEntryStub(PLDHashTable* aTable, + const PLDHashEntryHdr* aFrom, + PLDHashEntryHdr* aTo) +{ + memcpy(aTo, aFrom, aTable->mEntrySize); +} + +/* static */ void +PLDHashTable::ClearEntryStub(PLDHashTable* aTable, PLDHashEntryHdr* aEntry) +{ + memset(aEntry, 0, aTable->mEntrySize); +} + +static const PLDHashTableOps gStubOps = { + PLDHashTable::HashVoidPtrKeyStub, + PLDHashTable::MatchEntryStub, + PLDHashTable::MoveEntryStub, + PLDHashTable::ClearEntryStub, + nullptr +}; + +/* static */ const PLDHashTableOps* +PLDHashTable::StubOps() +{ + return &gStubOps; +} + +static bool +SizeOfEntryStore(uint32_t aCapacity, uint32_t aEntrySize, uint32_t* aNbytes) +{ + uint64_t nbytes64 = uint64_t(aCapacity) * uint64_t(aEntrySize); + *aNbytes = aCapacity * aEntrySize; + return uint64_t(*aNbytes) == nbytes64; // returns false on overflow +} + +// Compute max and min load numbers (entry counts). We have a secondary max +// that allows us to overload a table reasonably if it cannot be grown further +// (i.e. if ChangeTable() fails). The table slows down drastically if the +// secondary max is too close to 1, but 0.96875 gives only a slight slowdown +// while allowing 1.3x more elements. +static inline uint32_t +MaxLoad(uint32_t aCapacity) +{ + return aCapacity - (aCapacity >> 2); // == aCapacity * 0.75 +} +static inline uint32_t +MaxLoadOnGrowthFailure(uint32_t aCapacity) +{ + return aCapacity - (aCapacity >> 5); // == aCapacity * 0.96875 +} +static inline uint32_t +MinLoad(uint32_t aCapacity) +{ + return aCapacity >> 2; // == aCapacity * 0.25 +} + +// Compute the minimum capacity (and the Log2 of that capacity) for a table +// containing |aLength| elements while respecting the following contraints: +// - table must be at most 75% full; +// - capacity must be a power of two; +// - capacity cannot be too small. +static inline void +BestCapacity(uint32_t aLength, uint32_t* aCapacityOut, + uint32_t* aLog2CapacityOut) +{ + // Compute the smallest capacity allowing |aLength| elements to be inserted + // without rehashing. + uint32_t capacity = (aLength * 4 + (3 - 1)) / 3; // == ceil(aLength * 4 / 3) + if (capacity < PLDHashTable::kMinCapacity) { + capacity = PLDHashTable::kMinCapacity; + } + + // Round up capacity to next power-of-two. + uint32_t log2 = CeilingLog2(capacity); + capacity = 1u << log2; + MOZ_ASSERT(capacity <= PLDHashTable::kMaxCapacity); + + *aCapacityOut = capacity; + *aLog2CapacityOut = log2; +} + +/* static */ MOZ_ALWAYS_INLINE uint32_t +PLDHashTable::HashShift(uint32_t aEntrySize, uint32_t aLength) +{ + if (aLength > kMaxInitialLength) { + MOZ_CRASH("Initial length is too large"); + } + + uint32_t capacity, log2; + BestCapacity(aLength, &capacity, &log2); + + uint32_t nbytes; + if (!SizeOfEntryStore(capacity, aEntrySize, &nbytes)) { + MOZ_CRASH("Initial entry store size is too large"); + } + + // Compute the hashShift value. + return kHashBits - log2; +} + +PLDHashTable::PLDHashTable(const PLDHashTableOps* aOps, uint32_t aEntrySize, + uint32_t aLength) + : mOps(aOps) + , mHashShift(HashShift(aEntrySize, aLength)) + , mEntrySize(aEntrySize) + , mEntryCount(0) + , mRemovedCount(0) + , mEntryStore() +#ifdef DEBUG + , mChecker() +#endif +{ +} + +PLDHashTable& +PLDHashTable::operator=(PLDHashTable&& aOther) +{ + if (this == &aOther) { + return *this; + } + + // Destruct |this|. + this->~PLDHashTable(); + + // |mOps| and |mEntrySize| are const so we can't assign them. Instead, we + // require that they are equal. The justification for this is that they're + // conceptually part of the type -- indeed, if PLDHashTable was a templated + // type like nsTHashtable, they *would* be part of the type -- so it only + // makes sense to assign in cases where they match. + MOZ_RELEASE_ASSERT(mOps == aOther.mOps); + MOZ_RELEASE_ASSERT(mEntrySize == aOther.mEntrySize); + + // Move non-const pieces over. + mHashShift = Move(aOther.mHashShift); + mEntryCount = Move(aOther.mEntryCount); + mRemovedCount = Move(aOther.mRemovedCount); + mEntryStore = Move(aOther.mEntryStore); +#ifdef DEBUG + mChecker = Move(aOther.mChecker); +#endif + + // Clear up |aOther| so its destruction will be a no-op. + { +#ifdef DEBUG + AutoDestructorOp op(mChecker); +#endif + aOther.mEntryStore.Set(nullptr); + } + + return *this; +} + +PLDHashNumber +PLDHashTable::Hash1(PLDHashNumber aHash0) +{ + return aHash0 >> mHashShift; +} + +// Double hashing needs the second hash code to be relatively prime to table +// size, so we simply make hash2 odd. +void +PLDHashTable::Hash2(PLDHashNumber aHash, + uint32_t& aHash2Out, uint32_t& aSizeMaskOut) +{ + uint32_t sizeLog2 = kHashBits - mHashShift; + aHash2Out = ((aHash << sizeLog2) >> mHashShift) | 1; + aSizeMaskOut = (PLDHashNumber(1) << sizeLog2) - 1; +} + +// Reserve mKeyHash 0 for free entries and 1 for removed-entry sentinels. Note +// that a removed-entry sentinel need be stored only if the removed entry had +// a colliding entry added after it. Therefore we can use 1 as the collision +// flag in addition to the removed-entry sentinel value. Multiplicative hash +// uses the high order bits of mKeyHash, so this least-significant reservation +// should not hurt the hash function's effectiveness much. + +// Match an entry's mKeyHash against an unstored one computed from a key. +/* static */ bool +PLDHashTable::MatchEntryKeyhash(PLDHashEntryHdr* aEntry, PLDHashNumber aKeyHash) +{ + return (aEntry->mKeyHash & ~kCollisionFlag) == aKeyHash; +} + +// Compute the address of the indexed entry in table. +PLDHashEntryHdr* +PLDHashTable::AddressEntry(uint32_t aIndex) +{ + return reinterpret_cast<PLDHashEntryHdr*>( + mEntryStore.Get() + aIndex * mEntrySize); +} + +PLDHashTable::~PLDHashTable() +{ +#ifdef DEBUG + AutoDestructorOp op(mChecker); +#endif + + if (!mEntryStore.Get()) { + return; + } + + // Clear any remaining live entries. + char* entryAddr = mEntryStore.Get(); + char* entryLimit = entryAddr + Capacity() * mEntrySize; + while (entryAddr < entryLimit) { + PLDHashEntryHdr* entry = (PLDHashEntryHdr*)entryAddr; + if (EntryIsLive(entry)) { + mOps->clearEntry(this, entry); + } + entryAddr += mEntrySize; + } + + // Entry storage is freed last, by ~EntryStore(). +} + +void +PLDHashTable::ClearAndPrepareForLength(uint32_t aLength) +{ + // Get these values before the destructor clobbers them. + const PLDHashTableOps* ops = mOps; + uint32_t entrySize = mEntrySize; + + this->~PLDHashTable(); + new (KnownNotNull, this) PLDHashTable(ops, entrySize, aLength); +} + +void +PLDHashTable::Clear() +{ + ClearAndPrepareForLength(kDefaultInitialLength); +} + +// If |Reason| is |ForAdd|, the return value is always non-null and it may be +// a previously-removed entry. If |Reason| is |ForSearchOrRemove|, the return +// value is null on a miss, and will never be a previously-removed entry on a +// hit. This distinction is a bit grotty but this function is hot enough that +// these differences are worthwhile. +template <PLDHashTable::SearchReason Reason> +PLDHashEntryHdr* NS_FASTCALL +PLDHashTable::SearchTable(const void* aKey, PLDHashNumber aKeyHash) +{ + MOZ_ASSERT(mEntryStore.Get()); + NS_ASSERTION(!(aKeyHash & kCollisionFlag), + "!(aKeyHash & kCollisionFlag)"); + + // Compute the primary hash address. + PLDHashNumber hash1 = Hash1(aKeyHash); + PLDHashEntryHdr* entry = AddressEntry(hash1); + + // Miss: return space for a new entry. + if (EntryIsFree(entry)) { + return (Reason == ForAdd) ? entry : nullptr; + } + + // Hit: return entry. + PLDHashMatchEntry matchEntry = mOps->matchEntry; + if (MatchEntryKeyhash(entry, aKeyHash) && + matchEntry(entry, aKey)) { + return entry; + } + + // Collision: double hash. + PLDHashNumber hash2; + uint32_t sizeMask; + Hash2(aKeyHash, hash2, sizeMask); + + // Save the first removed entry pointer so Add() can recycle it. (Only used + // if Reason==ForAdd.) + PLDHashEntryHdr* firstRemoved = nullptr; + + for (;;) { + if (Reason == ForAdd) { + if (MOZ_UNLIKELY(EntryIsRemoved(entry))) { + if (!firstRemoved) { + firstRemoved = entry; + } + } else { + entry->mKeyHash |= kCollisionFlag; + } + } + + hash1 -= hash2; + hash1 &= sizeMask; + + entry = AddressEntry(hash1); + if (EntryIsFree(entry)) { + return (Reason == ForAdd) ? (firstRemoved ? firstRemoved : entry) + : nullptr; + } + + if (MatchEntryKeyhash(entry, aKeyHash) && + matchEntry(entry, aKey)) { + return entry; + } + } + + // NOTREACHED + return nullptr; +} + +// This is a copy of SearchTable(), used by ChangeTable(), hardcoded to +// 1. assume |Reason| is |ForAdd|, +// 2. assume that |aKey| will never match an existing entry, and +// 3. assume that no entries have been removed from the current table +// structure. +// Avoiding the need for |aKey| means we can avoid needing a way to map entries +// to keys, which means callers can use complex key types more easily. +MOZ_ALWAYS_INLINE PLDHashEntryHdr* +PLDHashTable::FindFreeEntry(PLDHashNumber aKeyHash) +{ + MOZ_ASSERT(mEntryStore.Get()); + NS_ASSERTION(!(aKeyHash & kCollisionFlag), + "!(aKeyHash & kCollisionFlag)"); + + // Compute the primary hash address. + PLDHashNumber hash1 = Hash1(aKeyHash); + PLDHashEntryHdr* entry = AddressEntry(hash1); + + // Miss: return space for a new entry. + if (EntryIsFree(entry)) { + return entry; + } + + // Collision: double hash. + PLDHashNumber hash2; + uint32_t sizeMask; + Hash2(aKeyHash, hash2, sizeMask); + + for (;;) { + NS_ASSERTION(!EntryIsRemoved(entry), + "!EntryIsRemoved(entry)"); + entry->mKeyHash |= kCollisionFlag; + + hash1 -= hash2; + hash1 &= sizeMask; + + entry = AddressEntry(hash1); + if (EntryIsFree(entry)) { + return entry; + } + } + + // NOTREACHED +} + +bool +PLDHashTable::ChangeTable(int32_t aDeltaLog2) +{ + MOZ_ASSERT(mEntryStore.Get()); + + // Look, but don't touch, until we succeed in getting new entry store. + int32_t oldLog2 = kHashBits - mHashShift; + int32_t newLog2 = oldLog2 + aDeltaLog2; + uint32_t newCapacity = 1u << newLog2; + if (newCapacity > kMaxCapacity) { + return false; + } + + uint32_t nbytes; + if (!SizeOfEntryStore(newCapacity, mEntrySize, &nbytes)) { + return false; // overflowed + } + + char* newEntryStore = (char*)malloc(nbytes); + if (!newEntryStore) { + return false; + } + + // We can't fail from here on, so update table parameters. + mHashShift = kHashBits - newLog2; + mRemovedCount = 0; + + // Assign the new entry store to table. + memset(newEntryStore, 0, nbytes); + char* oldEntryStore; + char* oldEntryAddr; + oldEntryAddr = oldEntryStore = mEntryStore.Get(); + mEntryStore.Set(newEntryStore); + PLDHashMoveEntry moveEntry = mOps->moveEntry; + + // Copy only live entries, leaving removed ones behind. + uint32_t oldCapacity = 1u << oldLog2; + for (uint32_t i = 0; i < oldCapacity; ++i) { + PLDHashEntryHdr* oldEntry = (PLDHashEntryHdr*)oldEntryAddr; + if (EntryIsLive(oldEntry)) { + oldEntry->mKeyHash &= ~kCollisionFlag; + PLDHashEntryHdr* newEntry = FindFreeEntry(oldEntry->mKeyHash); + NS_ASSERTION(EntryIsFree(newEntry), "EntryIsFree(newEntry)"); + moveEntry(this, oldEntry, newEntry); + newEntry->mKeyHash = oldEntry->mKeyHash; + } + oldEntryAddr += mEntrySize; + } + + free(oldEntryStore); + return true; +} + +MOZ_ALWAYS_INLINE PLDHashNumber +PLDHashTable::ComputeKeyHash(const void* aKey) +{ + MOZ_ASSERT(mEntryStore.Get()); + + PLDHashNumber keyHash = mOps->hashKey(aKey); + keyHash *= kGoldenRatio; + + // Avoid 0 and 1 hash codes, they indicate free and removed entries. + if (keyHash < 2) { + keyHash -= 2; + } + keyHash &= ~kCollisionFlag; + + return keyHash; +} + +PLDHashEntryHdr* +PLDHashTable::Search(const void* aKey) +{ +#ifdef DEBUG + AutoReadOp op(mChecker); +#endif + + PLDHashEntryHdr* entry = mEntryStore.Get() + ? SearchTable<ForSearchOrRemove>(aKey, + ComputeKeyHash(aKey)) + : nullptr; + return entry; +} + +PLDHashEntryHdr* +PLDHashTable::Add(const void* aKey, const mozilla::fallible_t&) +{ +#ifdef DEBUG + AutoWriteOp op(mChecker); +#endif + + // Allocate the entry storage if it hasn't already been allocated. + if (!mEntryStore.Get()) { + uint32_t nbytes; + // We already checked this in the constructor, so it must still be true. + MOZ_RELEASE_ASSERT(SizeOfEntryStore(CapacityFromHashShift(), mEntrySize, + &nbytes)); + mEntryStore.Set((char*)malloc(nbytes)); + if (!mEntryStore.Get()) { + return nullptr; + } + memset(mEntryStore.Get(), 0, nbytes); + } + + // If alpha is >= .75, grow or compress the table. If aKey is already in the + // table, we may grow once more than necessary, but only if we are on the + // edge of being overloaded. + uint32_t capacity = Capacity(); + if (mEntryCount + mRemovedCount >= MaxLoad(capacity)) { + // Compress if a quarter or more of all entries are removed. + int deltaLog2; + if (mRemovedCount >= capacity >> 2) { + deltaLog2 = 0; + } else { + deltaLog2 = 1; + } + + // Grow or compress the table. If ChangeTable() fails, allow overloading up + // to the secondary max. Once we hit the secondary max, return null. + if (!ChangeTable(deltaLog2) && + mEntryCount + mRemovedCount >= MaxLoadOnGrowthFailure(capacity)) { + return nullptr; + } + } + + // Look for entry after possibly growing, so we don't have to add it, + // then skip it while growing the table and re-add it after. + PLDHashNumber keyHash = ComputeKeyHash(aKey); + PLDHashEntryHdr* entry = SearchTable<ForAdd>(aKey, keyHash); + if (!EntryIsLive(entry)) { + // Initialize the entry, indicating that it's no longer free. + if (EntryIsRemoved(entry)) { + mRemovedCount--; + keyHash |= kCollisionFlag; + } + if (mOps->initEntry) { + mOps->initEntry(entry, aKey); + } + entry->mKeyHash = keyHash; + mEntryCount++; + } + + return entry; +} + +PLDHashEntryHdr* +PLDHashTable::Add(const void* aKey) +{ + PLDHashEntryHdr* entry = Add(aKey, fallible); + if (!entry) { + if (!mEntryStore.Get()) { + // We OOM'd while allocating the initial entry storage. + uint32_t nbytes; + (void) SizeOfEntryStore(CapacityFromHashShift(), mEntrySize, &nbytes); + NS_ABORT_OOM(nbytes); + } else { + // We failed to resize the existing entry storage, either due to OOM or + // because we exceeded the maximum table capacity or size; report it as + // an OOM. The multiplication by 2 gets us the size we tried to allocate, + // which is double the current size. + NS_ABORT_OOM(2 * EntrySize() * EntryCount()); + } + } + return entry; +} + +void +PLDHashTable::Remove(const void* aKey) +{ +#ifdef DEBUG + AutoWriteOp op(mChecker); +#endif + + PLDHashEntryHdr* entry = mEntryStore.Get() + ? SearchTable<ForSearchOrRemove>(aKey, + ComputeKeyHash(aKey)) + : nullptr; + if (entry) { + RawRemove(entry); + ShrinkIfAppropriate(); + } +} + +void +PLDHashTable::RemoveEntry(PLDHashEntryHdr* aEntry) +{ +#ifdef DEBUG + AutoWriteOp op(mChecker); +#endif + + RawRemove(aEntry); + ShrinkIfAppropriate(); +} + +void +PLDHashTable::RawRemove(PLDHashEntryHdr* aEntry) +{ + // Unfortunately, we can only do weak checking here. That's because + // RawRemove() can be called legitimately while an Enumerate() call is + // active, which doesn't fit well into how Checker's mState variable works. + MOZ_ASSERT(mChecker.IsWritable()); + + MOZ_ASSERT(mEntryStore.Get()); + + MOZ_ASSERT(EntryIsLive(aEntry), "EntryIsLive(aEntry)"); + + // Load keyHash first in case clearEntry() goofs it. + PLDHashNumber keyHash = aEntry->mKeyHash; + mOps->clearEntry(this, aEntry); + if (keyHash & kCollisionFlag) { + MarkEntryRemoved(aEntry); + mRemovedCount++; + } else { + MarkEntryFree(aEntry); + } + mEntryCount--; +} + +// Shrink or compress if a quarter or more of all entries are removed, or if the +// table is underloaded according to the minimum alpha, and is not minimal-size +// already. +void +PLDHashTable::ShrinkIfAppropriate() +{ + uint32_t capacity = Capacity(); + if (mRemovedCount >= capacity >> 2 || + (capacity > kMinCapacity && mEntryCount <= MinLoad(capacity))) { + uint32_t log2; + BestCapacity(mEntryCount, &capacity, &log2); + + int32_t deltaLog2 = log2 - (kHashBits - mHashShift); + MOZ_ASSERT(deltaLog2 <= 0); + + (void) ChangeTable(deltaLog2); + } +} + +size_t +PLDHashTable::ShallowSizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const +{ +#ifdef DEBUG + AutoReadOp op(mChecker); +#endif + + return aMallocSizeOf(mEntryStore.Get()); +} + +size_t +PLDHashTable::ShallowSizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const +{ + return aMallocSizeOf(this) + ShallowSizeOfExcludingThis(aMallocSizeOf); +} + +PLDHashTable::Iterator::Iterator(Iterator&& aOther) + : mTable(aOther.mTable) + , mStart(aOther.mStart) + , mLimit(aOther.mLimit) + , mCurrent(aOther.mCurrent) + , mNexts(aOther.mNexts) + , mNextsLimit(aOther.mNextsLimit) + , mHaveRemoved(aOther.mHaveRemoved) +{ + // No need to change |mChecker| here. + aOther.mTable = nullptr; + aOther.mStart = nullptr; + aOther.mLimit = nullptr; + aOther.mCurrent = nullptr; + aOther.mNexts = 0; + aOther.mNextsLimit = 0; + aOther.mHaveRemoved = false; +} + +PLDHashTable::Iterator::Iterator(PLDHashTable* aTable) + : mTable(aTable) + , mStart(mTable->mEntryStore.Get()) + , mLimit(mTable->mEntryStore.Get() + mTable->Capacity() * mTable->mEntrySize) + , mCurrent(mTable->mEntryStore.Get()) + , mNexts(0) + , mNextsLimit(mTable->EntryCount()) + , mHaveRemoved(false) +{ +#ifdef DEBUG + mTable->mChecker.StartReadOp(); +#endif + + if (ChaosMode::isActive(ChaosFeature::HashTableIteration) && + mTable->Capacity() > 0) { + // Start iterating at a random entry. It would be even more chaotic to + // iterate in fully random order, but that's harder. + mCurrent += ChaosMode::randomUint32LessThan(mTable->Capacity()) * + mTable->mEntrySize; + } + + // Advance to the first live entry, if there is one. + if (!Done()) { + while (IsOnNonLiveEntry()) { + MoveToNextEntry(); + } + } +} + +PLDHashTable::Iterator::~Iterator() +{ + if (mTable) { + if (mHaveRemoved) { + mTable->ShrinkIfAppropriate(); + } +#ifdef DEBUG + mTable->mChecker.EndReadOp(); +#endif + } +} + +MOZ_ALWAYS_INLINE bool +PLDHashTable::Iterator::IsOnNonLiveEntry() const +{ + MOZ_ASSERT(!Done()); + return !EntryIsLive(reinterpret_cast<PLDHashEntryHdr*>(mCurrent)); +} + +MOZ_ALWAYS_INLINE void +PLDHashTable::Iterator::MoveToNextEntry() +{ + mCurrent += mTable->mEntrySize; + if (mCurrent == mLimit) { + mCurrent = mStart; // Wrap-around. Possible due to Chaos Mode. + } +} + +void +PLDHashTable::Iterator::Next() +{ + MOZ_ASSERT(!Done()); + + mNexts++; + + // Advance to the next live entry, if there is one. + if (!Done()) { + do { + MoveToNextEntry(); + } while (IsOnNonLiveEntry()); + } +} + +void +PLDHashTable::Iterator::Remove() +{ + // This cast is needed for the same reason as the one in the destructor. + mTable->RawRemove(Get()); + mHaveRemoved = true; +} + +#ifdef DEBUG +void +PLDHashTable::MarkImmutable() +{ + mChecker.SetNonWritable(); +} +#endif diff --git a/xpcom/glue/PLDHashTable.h b/xpcom/glue/PLDHashTable.h new file mode 100644 index 0000000000..cd1323dbe7 --- /dev/null +++ b/xpcom/glue/PLDHashTable.h @@ -0,0 +1,621 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef PLDHashTable_h +#define PLDHashTable_h + +#include "mozilla/Atomics.h" +#include "mozilla/Attributes.h" // for MOZ_ALWAYS_INLINE +#include "mozilla/fallible.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/Move.h" +#include "mozilla/Types.h" +#include "nscore.h" + +typedef uint32_t PLDHashNumber; + +class PLDHashTable; +struct PLDHashTableOps; + +// Table entry header structure. +// +// In order to allow in-line allocation of key and value, we do not declare +// either here. Instead, the API uses const void *key as a formal parameter. +// The key need not be stored in the entry; it may be part of the value, but +// need not be stored at all. +// +// Callback types are defined below and grouped into the PLDHashTableOps +// structure, for single static initialization per hash table sub-type. +// +// Each hash table sub-type should make its entry type a subclass of +// PLDHashEntryHdr. The mKeyHash member contains the result of multiplying the +// hash code returned from the hashKey callback (see below) by kGoldenRatio, +// then constraining the result to avoid the magic 0 and 1 values. The stored +// mKeyHash value is table size invariant, and it is maintained automatically +// -- users need never access it. +struct PLDHashEntryHdr +{ +private: + friend class PLDHashTable; + + PLDHashNumber mKeyHash; +}; + +#ifdef DEBUG + +// This class does three kinds of checking: +// +// - that calls to one of |mOps| or to an enumerator do not cause re-entry into +// the table in an unsafe way; +// +// - that multiple threads do not access the table in an unsafe way; +// +// - that a table marked as immutable is not modified. +// +// "Safe" here means that multiple concurrent read operations are ok, but a +// write operation (i.e. one that can cause the entry storage to be reallocated +// or destroyed) cannot safely run concurrently with another read or write +// operation. This meaning of "safe" is only partial; for example, it does not +// cover whether a single entry in the table is modified by two separate +// threads. (Doing such checking would be much harder.) +// +// It does this with two variables: +// +// - mState, which embodies a tri-stage tagged union with the following +// variants: +// - Idle +// - Read(n), where 'n' is the number of concurrent read operations +// - Write +// +// - mIsWritable, which indicates if the table is mutable. +// +class Checker +{ +public: + constexpr Checker() : mState(kIdle), mIsWritable(1) {} + + Checker& operator=(Checker&& aOther) { + // Atomic<> doesn't have an |operator=(Atomic<>&&)|. + mState = uint32_t(aOther.mState); + mIsWritable = uint32_t(aOther.mIsWritable); + + aOther.mState = kIdle; + + return *this; + } + + static bool IsIdle(uint32_t aState) { return aState == kIdle; } + static bool IsRead(uint32_t aState) { return kRead1 <= aState && + aState <= kReadMax; } + static bool IsRead1(uint32_t aState) { return aState == kRead1; } + static bool IsWrite(uint32_t aState) { return aState == kWrite; } + + bool IsIdle() const { return mState == kIdle; } + + bool IsWritable() const { return !!mIsWritable; } + + void SetNonWritable() { mIsWritable = 0; } + + // NOTE: the obvious way to implement these functions is to (a) check + // |mState| is reasonable, and then (b) update |mState|. But the lack of + // atomicity in such an implementation can cause problems if we get unlucky + // thread interleaving between (a) and (b). + // + // So instead for |mState| we are careful to (a) first get |mState|'s old + // value and assign it a new value in single atomic operation, and only then + // (b) check the old value was reasonable. This ensures we don't have + // interleaving problems. + // + // For |mIsWritable| we don't need to be as careful because it can only in + // transition in one direction (from writable to non-writable). + + void StartReadOp() + { + uint32_t oldState = mState++; // this is an atomic increment + MOZ_ASSERT(IsIdle(oldState) || IsRead(oldState)); + MOZ_ASSERT(oldState < kReadMax); // check for overflow + } + + void EndReadOp() + { + uint32_t oldState = mState--; // this is an atomic decrement + MOZ_ASSERT(IsRead(oldState)); + } + + void StartWriteOp() + { + MOZ_ASSERT(IsWritable()); + uint32_t oldState = mState.exchange(kWrite); + MOZ_ASSERT(IsIdle(oldState)); + } + + void EndWriteOp() + { + // Check again that the table is writable, in case it was marked as + // non-writable just after the IsWritable() assertion in StartWriteOp() + // occurred. + MOZ_ASSERT(IsWritable()); + uint32_t oldState = mState.exchange(kIdle); + MOZ_ASSERT(IsWrite(oldState)); + } + + void StartIteratorRemovalOp() + { + // When doing removals at the end of iteration, we go from Read1 state to + // Write and then back. + MOZ_ASSERT(IsWritable()); + uint32_t oldState = mState.exchange(kWrite); + MOZ_ASSERT(IsRead1(oldState)); + } + + void EndIteratorRemovalOp() + { + // Check again that the table is writable, in case it was marked as + // non-writable just after the IsWritable() assertion in + // StartIteratorRemovalOp() occurred. + MOZ_ASSERT(IsWritable()); + uint32_t oldState = mState.exchange(kRead1); + MOZ_ASSERT(IsWrite(oldState)); + } + + void StartDestructorOp() + { + // A destructor op is like a write, but the table doesn't need to be + // writable. + uint32_t oldState = mState.exchange(kWrite); + MOZ_ASSERT(IsIdle(oldState)); + } + + void EndDestructorOp() + { + uint32_t oldState = mState.exchange(kIdle); + MOZ_ASSERT(IsWrite(oldState)); + } + +private: + // Things of note about the representation of |mState|. + // - The values between kRead1..kReadMax represent valid Read(n) values. + // - kIdle and kRead1 are deliberately chosen so that incrementing the - + // former gives the latter. + // - 9999 concurrent readers should be enough for anybody. + static const uint32_t kIdle = 0; + static const uint32_t kRead1 = 1; + static const uint32_t kReadMax = 9999; + static const uint32_t kWrite = 10000; + + mutable mozilla::Atomic<uint32_t> mState; + mutable mozilla::Atomic<uint32_t> mIsWritable; +}; +#endif + +// A PLDHashTable may be allocated on the stack or within another structure or +// class. No entry storage is allocated until the first element is added. This +// means that empty hash tables are cheap, which is good because they are +// common. +// +// There used to be a long, math-heavy comment here about the merits of +// double hashing vs. chaining; it was removed in bug 1058335. In short, double +// hashing is more space-efficient unless the element size gets large (in which +// case you should keep using double hashing but switch to using pointer +// elements). Also, with double hashing, you can't safely hold an entry pointer +// and use it after an add or remove operation, unless you sample Generation() +// before adding or removing, and compare the sample after, dereferencing the +// entry pointer only if Generation() has not changed. +class PLDHashTable +{ +private: + // This class maintains the invariant that every time the entry store is + // changed, the generation is updated. + class EntryStore + { + private: + char* mEntryStore; + uint32_t mGeneration; + + public: + EntryStore() : mEntryStore(nullptr), mGeneration(0) {} + + ~EntryStore() + { + free(mEntryStore); + mEntryStore = nullptr; + mGeneration++; // a little paranoid, but why not be extra safe? + } + + char* Get() { return mEntryStore; } + const char* Get() const { return mEntryStore; } + + void Set(char* aEntryStore) + { + mEntryStore = aEntryStore; + mGeneration++; + } + + uint32_t Generation() const { return mGeneration; } + }; + + const PLDHashTableOps* const mOps; // Virtual operations; see below. + int16_t mHashShift; // Multiplicative hash shift. + const uint32_t mEntrySize; // Number of bytes in an entry. + uint32_t mEntryCount; // Number of entries in table. + uint32_t mRemovedCount; // Removed entry sentinels in table. + EntryStore mEntryStore; // (Lazy) entry storage and generation. + +#ifdef DEBUG + mutable Checker mChecker; +#endif + +public: + // Table capacity limit; do not exceed. The max capacity used to be 1<<23 but + // that occasionally that wasn't enough. Making it much bigger than 1<<26 + // probably isn't worthwhile -- tables that big are kind of ridiculous. + // Also, the growth operation will (deliberately) fail if |capacity * + // mEntrySize| overflows a uint32_t, and mEntrySize is always at least 8 + // bytes. + static const uint32_t kMaxCapacity = ((uint32_t)1 << 26); + + static const uint32_t kMinCapacity = 8; + + // Making this half of kMaxCapacity ensures it'll fit. Nobody should need an + // initial length anywhere nearly this large, anyway. + static const uint32_t kMaxInitialLength = kMaxCapacity / 2; + + // This gives a default initial capacity of 8. + static const uint32_t kDefaultInitialLength = 4; + + // Initialize the table with |aOps| and |aEntrySize|. The table's initial + // capacity is chosen such that |aLength| elements can be inserted without + // rehashing; if |aLength| is a power-of-two, this capacity will be + // |2*length|. However, because entry storage is allocated lazily, this + // initial capacity won't be relevant until the first element is added; prior + // to that the capacity will be zero. + // + // This will crash if |aEntrySize| and/or |aLength| are too large. + PLDHashTable(const PLDHashTableOps* aOps, uint32_t aEntrySize, + uint32_t aLength = kDefaultInitialLength); + + PLDHashTable(PLDHashTable&& aOther) + // These two fields are |const|. Initialize them here because the + // move assignment operator cannot modify them. + : mOps(aOther.mOps) + , mEntrySize(aOther.mEntrySize) + // Initialize this field because it is required for a safe call to the + // destructor, which the move assignment operator does. + , mEntryStore() +#ifdef DEBUG + , mChecker() +#endif + { + *this = mozilla::Move(aOther); + } + + PLDHashTable& operator=(PLDHashTable&& aOther); + + ~PLDHashTable(); + + // This should be used rarely. + const PLDHashTableOps* Ops() const { return mOps; } + + // Size in entries (gross, not net of free and removed sentinels) for table. + // This can be zero if no elements have been added yet, in which case the + // entry storage will not have yet been allocated. + uint32_t Capacity() const + { + return mEntryStore.Get() ? CapacityFromHashShift() : 0; + } + + uint32_t EntrySize() const { return mEntrySize; } + uint32_t EntryCount() const { return mEntryCount; } + uint32_t Generation() const { return mEntryStore.Generation(); } + + // To search for a |key| in |table|, call: + // + // entry = table.Search(key); + // + // If |entry| is non-null, |key| was found. If |entry| is null, key was not + // found. + PLDHashEntryHdr* Search(const void* aKey); + + // To add an entry identified by |key| to table, call: + // + // entry = table.Add(key, mozilla::fallible); + // + // If |entry| is null upon return, then the table is severely overloaded and + // memory can't be allocated for entry storage. + // + // Otherwise, |aEntry->mKeyHash| has been set so that + // PLDHashTable::EntryIsFree(entry) is false, and it is up to the caller to + // initialize the key and value parts of the entry sub-type, if they have not + // been set already (i.e. if entry was not already in the table, and if the + // optional initEntry hook was not used). + PLDHashEntryHdr* Add(const void* aKey, const mozilla::fallible_t&); + + // This is like the other Add() function, but infallible, and so never + // returns null. + PLDHashEntryHdr* Add(const void* aKey); + + // To remove an entry identified by |key| from table, call: + // + // table.Remove(key); + // + // If |key|'s entry is found, it is cleared (via table->mOps->clearEntry). + // The table's capacity may be reduced afterwards. + void Remove(const void* aKey); + + // To remove an entry found by a prior search, call: + // + // table.RemoveEntry(entry); + // + // The entry, which must be present and in use, is cleared (via + // table->mOps->clearEntry). The table's capacity may be reduced afterwards. + void RemoveEntry(PLDHashEntryHdr* aEntry); + + // Remove an entry already accessed via Search() or Add(). + // + // NB: this is a "raw" or low-level method. It does not shrink the table if + // it is underloaded. Don't use it unless necessary and you know what you are + // doing, and if so, please explain in a comment why it is necessary instead + // of RemoveEntry(). + void RawRemove(PLDHashEntryHdr* aEntry); + + // This function is equivalent to + // ClearAndPrepareForLength(kDefaultInitialLength). + void Clear(); + + // This function clears the table's contents and frees its entry storage, + // leaving it in a empty state ready to be used again. Afterwards, when the + // first element is added the entry storage that gets allocated will have a + // capacity large enough to fit |aLength| elements without rehashing. + // + // It's conceptually the same as calling the destructor and then re-calling + // the constructor with the original |aOps| and |aEntrySize| arguments, and + // a new |aLength| argument. + void ClearAndPrepareForLength(uint32_t aLength); + + // Measure the size of the table's entry storage. If the entries contain + // pointers to other heap blocks, you have to iterate over the table and + // measure those separately; hence the "Shallow" prefix. + size_t ShallowSizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const; + + // Like ShallowSizeOfExcludingThis(), but includes sizeof(*this). + size_t ShallowSizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const; + +#ifdef DEBUG + // Mark a table as immutable for the remainder of its lifetime. This + // changes the implementation from asserting one set of invariants to + // asserting a different set. + void MarkImmutable(); +#endif + + // If you use PLDHashEntryStub or a subclass of it as your entry struct, and + // if your entries move via memcpy and clear via memset(0), you can use these + // stub operations. + static const PLDHashTableOps* StubOps(); + + // The individual stub operations in StubOps(). + static PLDHashNumber HashVoidPtrKeyStub(const void* aKey); + static bool MatchEntryStub(const PLDHashEntryHdr* aEntry, const void* aKey); + static void MoveEntryStub(PLDHashTable* aTable, const PLDHashEntryHdr* aFrom, + PLDHashEntryHdr* aTo); + static void ClearEntryStub(PLDHashTable* aTable, PLDHashEntryHdr* aEntry); + + // Hash/match operations for tables holding C strings. + static PLDHashNumber HashStringKey(const void* aKey); + static bool MatchStringKey(const PLDHashEntryHdr* aEntry, const void* aKey); + + // This is an iterator for PLDHashtable. Assertions will detect some, but not + // all, mid-iteration table modifications that might invalidate (e.g. + // reallocate) the entry storage. + // + // Any element can be removed during iteration using Remove(). If any + // elements are removed, the table may be resized once iteration ends. + // + // Example usage: + // + // for (auto iter = table.Iter(); !iter.Done(); iter.Next()) { + // auto entry = static_cast<FooEntry*>(iter.Get()); + // // ... do stuff with |entry| ... + // // ... possibly call iter.Remove() once ... + // } + // + // or: + // + // for (PLDHashTable::Iterator iter(&table); !iter.Done(); iter.Next()) { + // auto entry = static_cast<FooEntry*>(iter.Get()); + // // ... do stuff with |entry| ... + // // ... possibly call iter.Remove() once ... + // } + // + // The latter form is more verbose but is easier to work with when + // making subclasses of Iterator. + // + class Iterator + { + public: + explicit Iterator(PLDHashTable* aTable); + Iterator(Iterator&& aOther); + ~Iterator(); + + // Have we finished? + bool Done() const { return mNexts == mNextsLimit; } + + // Get the current entry. + PLDHashEntryHdr* Get() const + { + MOZ_ASSERT(!Done()); + + PLDHashEntryHdr* entry = reinterpret_cast<PLDHashEntryHdr*>(mCurrent); + MOZ_ASSERT(EntryIsLive(entry)); + return entry; + } + + // Advance to the next entry. + void Next(); + + // Remove the current entry. Must only be called once per entry, and Get() + // must not be called on that entry afterwards. + void Remove(); + + protected: + PLDHashTable* mTable; // Main table pointer. + + private: + char* mStart; // The first entry. + char* mLimit; // One past the last entry. + char* mCurrent; // Pointer to the current entry. + uint32_t mNexts; // Number of Next() calls. + uint32_t mNextsLimit; // Next() call limit. + + bool mHaveRemoved; // Have any elements been removed? + + bool IsOnNonLiveEntry() const; + void MoveToNextEntry(); + + Iterator() = delete; + Iterator(const Iterator&) = delete; + Iterator& operator=(const Iterator&) = delete; + Iterator& operator=(const Iterator&&) = delete; + }; + + Iterator Iter() { return Iterator(this); } + + // Use this if you need to initialize an Iterator in a const method. If you + // use this case, you should not call Remove() on the iterator. + Iterator ConstIter() const + { + return Iterator(const_cast<PLDHashTable*>(this)); + } + +private: + // Multiplicative hash uses an unsigned 32 bit integer and the golden ratio, + // expressed as a fixed-point 32-bit fraction. + static const uint32_t kHashBits = 32; + static const uint32_t kGoldenRatio = 0x9E3779B9U; + + static uint32_t HashShift(uint32_t aEntrySize, uint32_t aLength); + + static const PLDHashNumber kCollisionFlag = 1; + + static bool EntryIsFree(PLDHashEntryHdr* aEntry) + { + return aEntry->mKeyHash == 0; + } + static bool EntryIsRemoved(PLDHashEntryHdr* aEntry) + { + return aEntry->mKeyHash == 1; + } + static bool EntryIsLive(PLDHashEntryHdr* aEntry) + { + return aEntry->mKeyHash >= 2; + } + + static void MarkEntryFree(PLDHashEntryHdr* aEntry) + { + aEntry->mKeyHash = 0; + } + static void MarkEntryRemoved(PLDHashEntryHdr* aEntry) + { + aEntry->mKeyHash = 1; + } + + PLDHashNumber Hash1(PLDHashNumber aHash0); + void Hash2(PLDHashNumber aHash, uint32_t& aHash2Out, uint32_t& aSizeMaskOut); + + static bool MatchEntryKeyhash(PLDHashEntryHdr* aEntry, PLDHashNumber aHash); + PLDHashEntryHdr* AddressEntry(uint32_t aIndex); + + // We store mHashShift rather than sizeLog2 to optimize the collision-free + // case in SearchTable. + uint32_t CapacityFromHashShift() const + { + return ((uint32_t)1 << (kHashBits - mHashShift)); + } + + PLDHashNumber ComputeKeyHash(const void* aKey); + + enum SearchReason { ForSearchOrRemove, ForAdd }; + + template <SearchReason Reason> + PLDHashEntryHdr* NS_FASTCALL + SearchTable(const void* aKey, PLDHashNumber aKeyHash); + + PLDHashEntryHdr* FindFreeEntry(PLDHashNumber aKeyHash); + + bool ChangeTable(int aDeltaLog2); + + void ShrinkIfAppropriate(); + + PLDHashTable(const PLDHashTable& aOther) = delete; + PLDHashTable& operator=(const PLDHashTable& aOther) = delete; +}; + +// Compute the hash code for a given key to be looked up, added, or removed. +// A hash code may have any PLDHashNumber value. +typedef PLDHashNumber (*PLDHashHashKey)(const void* aKey); + +// Compare the key identifying aEntry with the provided key parameter. Return +// true if keys match, false otherwise. +typedef bool (*PLDHashMatchEntry)(const PLDHashEntryHdr* aEntry, + const void* aKey); + +// Copy the data starting at aFrom to the new entry storage at aTo. Do not add +// reference counts for any strong references in the entry, however, as this +// is a "move" operation: the old entry storage at from will be freed without +// any reference-decrementing callback shortly. +typedef void (*PLDHashMoveEntry)(PLDHashTable* aTable, + const PLDHashEntryHdr* aFrom, + PLDHashEntryHdr* aTo); + +// Clear the entry and drop any strong references it holds. This callback is +// invoked by Remove(), but only if the given key is found in the table. +typedef void (*PLDHashClearEntry)(PLDHashTable* aTable, + PLDHashEntryHdr* aEntry); + +// Initialize a new entry, apart from mKeyHash. This function is called when +// Add() finds no existing entry for the given key, and must add a new one. At +// that point, |aEntry->mKeyHash| is not set yet, to avoid claiming the last +// free entry in a severely overloaded table. +typedef void (*PLDHashInitEntry)(PLDHashEntryHdr* aEntry, const void* aKey); + +// Finally, the "vtable" structure for PLDHashTable. The first four hooks +// must be provided by implementations; they're called unconditionally by the +// generic PLDHashTable.cpp code. Hooks after these may be null. +// +// Summary of allocation-related hook usage with C++ placement new emphasis: +// initEntry Call placement new using default key-based ctor. +// moveEntry Call placement new using copy ctor, run dtor on old +// entry storage. +// clearEntry Run dtor on entry. +// +// Note the reason why initEntry is optional: the default hooks (stubs) clear +// entry storage: On successful Add(tbl, key), the returned entry pointer +// addresses an entry struct whose mKeyHash member has been set non-zero, but +// all other entry members are still clear (null). Add() callers can test such +// members to see whether the entry was newly created by the Add() call that +// just succeeded. If placement new or similar initialization is required, +// define an |initEntry| hook. Of course, the |clearEntry| hook must zero or +// null appropriately. +// +// XXX assumes 0 is null for pointer types. +struct PLDHashTableOps +{ + // Mandatory hooks. All implementations must provide these. + PLDHashHashKey hashKey; + PLDHashMatchEntry matchEntry; + PLDHashMoveEntry moveEntry; + PLDHashClearEntry clearEntry; + + // Optional hooks start here. If null, these are not called. + PLDHashInitEntry initEntry; +}; + +// A minimal entry is a subclass of PLDHashEntryHdr and has a void* key pointer. +struct PLDHashEntryStub : public PLDHashEntryHdr +{ + const void* key; +}; + +#endif /* PLDHashTable_h */ diff --git a/xpcom/glue/ReentrantMonitor.h b/xpcom/glue/ReentrantMonitor.h new file mode 100644 index 0000000000..0798fe2af3 --- /dev/null +++ b/xpcom/glue/ReentrantMonitor.h @@ -0,0 +1,249 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_ReentrantMonitor_h +#define mozilla_ReentrantMonitor_h + +#include "prmon.h" + +#ifdef MOZILLA_INTERNAL_API +#include "GeckoProfiler.h" +#endif //MOZILLA_INTERNAL_API + +#include "mozilla/BlockingResourceBase.h" + +// +// Provides: +// +// - ReentrantMonitor, a Java-like monitor +// - ReentrantMonitorAutoEnter, an RAII class for ensuring that +// ReentrantMonitors are properly entered and exited +// +// Using ReentrantMonitorAutoEnter is MUCH preferred to making bare calls to +// ReentrantMonitor.Enter and Exit. +// +namespace mozilla { + + +/** + * ReentrantMonitor + * Java-like monitor. + * When possible, use ReentrantMonitorAutoEnter to hold this monitor within a + * scope, instead of calling Enter/Exit directly. + **/ +class ReentrantMonitor : BlockingResourceBase +{ +public: + /** + * ReentrantMonitor + * @param aName A name which can reference this monitor + */ + explicit ReentrantMonitor(const char* aName) + : BlockingResourceBase(aName, eReentrantMonitor) +#ifdef DEBUG + , mEntryCount(0) +#endif + { + MOZ_COUNT_CTOR(ReentrantMonitor); + mReentrantMonitor = PR_NewMonitor(); + if (!mReentrantMonitor) { + NS_RUNTIMEABORT("Can't allocate mozilla::ReentrantMonitor"); + } + } + + /** + * ~ReentrantMonitor + **/ + ~ReentrantMonitor() + { + NS_ASSERTION(mReentrantMonitor, + "improperly constructed ReentrantMonitor or double free"); + PR_DestroyMonitor(mReentrantMonitor); + mReentrantMonitor = 0; + MOZ_COUNT_DTOR(ReentrantMonitor); + } + +#ifndef DEBUG + /** + * Enter + * @see prmon.h + **/ + void Enter() { PR_EnterMonitor(mReentrantMonitor); } + + /** + * Exit + * @see prmon.h + **/ + void Exit() { PR_ExitMonitor(mReentrantMonitor); } + + /** + * Wait + * @see prmon.h + **/ + nsresult Wait(PRIntervalTime aInterval = PR_INTERVAL_NO_TIMEOUT) + { +#ifdef MOZILLA_INTERNAL_API + GeckoProfilerSleepRAII profiler_sleep; +#endif //MOZILLA_INTERNAL_API + return PR_Wait(mReentrantMonitor, aInterval) == PR_SUCCESS ? + NS_OK : NS_ERROR_FAILURE; + } + +#else // ifndef DEBUG + void Enter(); + void Exit(); + nsresult Wait(PRIntervalTime aInterval = PR_INTERVAL_NO_TIMEOUT); + +#endif // ifndef DEBUG + + /** + * Notify + * @see prmon.h + **/ + nsresult Notify() + { + return PR_Notify(mReentrantMonitor) == PR_SUCCESS ? NS_OK : + NS_ERROR_FAILURE; + } + + /** + * NotifyAll + * @see prmon.h + **/ + nsresult NotifyAll() + { + return PR_NotifyAll(mReentrantMonitor) == PR_SUCCESS ? NS_OK : + NS_ERROR_FAILURE; + } + +#ifdef DEBUG + /** + * AssertCurrentThreadIn + * @see prmon.h + **/ + void AssertCurrentThreadIn() + { + PR_ASSERT_CURRENT_THREAD_IN_MONITOR(mReentrantMonitor); + } + + /** + * AssertNotCurrentThreadIn + * @see prmon.h + **/ + void AssertNotCurrentThreadIn() + { + // FIXME bug 476536 + } + +#else + void AssertCurrentThreadIn() {} + void AssertNotCurrentThreadIn() {} + +#endif // ifdef DEBUG + +private: + ReentrantMonitor(); + ReentrantMonitor(const ReentrantMonitor&); + ReentrantMonitor& operator=(const ReentrantMonitor&); + + PRMonitor* mReentrantMonitor; +#ifdef DEBUG + int32_t mEntryCount; +#endif +}; + + +/** + * ReentrantMonitorAutoEnter + * Enters the ReentrantMonitor when it enters scope, and exits it when + * it leaves scope. + * + * MUCH PREFERRED to bare calls to ReentrantMonitor.Enter and Exit. + */ +class MOZ_STACK_CLASS ReentrantMonitorAutoEnter +{ +public: + /** + * Constructor + * The constructor aquires the given lock. The destructor + * releases the lock. + * + * @param aReentrantMonitor A valid mozilla::ReentrantMonitor*. + **/ + explicit ReentrantMonitorAutoEnter(mozilla::ReentrantMonitor& aReentrantMonitor) + : mReentrantMonitor(&aReentrantMonitor) + { + NS_ASSERTION(mReentrantMonitor, "null monitor"); + mReentrantMonitor->Enter(); + } + + ~ReentrantMonitorAutoEnter(void) + { + mReentrantMonitor->Exit(); + } + + nsresult Wait(PRIntervalTime aInterval = PR_INTERVAL_NO_TIMEOUT) + { + return mReentrantMonitor->Wait(aInterval); + } + + nsresult Notify() { return mReentrantMonitor->Notify(); } + nsresult NotifyAll() { return mReentrantMonitor->NotifyAll(); } + +private: + ReentrantMonitorAutoEnter(); + ReentrantMonitorAutoEnter(const ReentrantMonitorAutoEnter&); + ReentrantMonitorAutoEnter& operator=(const ReentrantMonitorAutoEnter&); + static void* operator new(size_t) CPP_THROW_NEW; + + mozilla::ReentrantMonitor* mReentrantMonitor; +}; + +/** + * ReentrantMonitorAutoExit + * Exit the ReentrantMonitor when it enters scope, and enters it when it leaves + * scope. + * + * MUCH PREFERRED to bare calls to ReentrantMonitor.Exit and Enter. + */ +class MOZ_STACK_CLASS ReentrantMonitorAutoExit +{ +public: + /** + * Constructor + * The constructor releases the given lock. The destructor + * acquires the lock. The lock must be held before constructing + * this object! + * + * @param aReentrantMonitor A valid mozilla::ReentrantMonitor*. It + * must be already locked. + **/ + explicit ReentrantMonitorAutoExit(ReentrantMonitor& aReentrantMonitor) + : mReentrantMonitor(&aReentrantMonitor) + { + NS_ASSERTION(mReentrantMonitor, "null monitor"); + mReentrantMonitor->AssertCurrentThreadIn(); + mReentrantMonitor->Exit(); + } + + ~ReentrantMonitorAutoExit(void) + { + mReentrantMonitor->Enter(); + } + +private: + ReentrantMonitorAutoExit(); + ReentrantMonitorAutoExit(const ReentrantMonitorAutoExit&); + ReentrantMonitorAutoExit& operator=(const ReentrantMonitorAutoExit&); + static void* operator new(size_t) CPP_THROW_NEW; + + ReentrantMonitor* mReentrantMonitor; +}; + +} // namespace mozilla + + +#endif // ifndef mozilla_ReentrantMonitor_h diff --git a/xpcom/glue/moz.build b/xpcom/glue/moz.build new file mode 100644 index 0000000000..95c18b273e --- /dev/null +++ b/xpcom/glue/moz.build @@ -0,0 +1,123 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +with Files('nsString*'): + BUG_COMPONENT = ('Core', 'String') + +DIRS += ['standalone'] + +# On win we build two glue libs - glue linked to crt dlls here and in staticruntime we build +# a statically linked glue lib. +if CONFIG['OS_ARCH'] == 'WINNT': + DIRS += ['staticruntime'] + +EXPORTS += [ + 'MainThreadUtils.h', + 'nsArrayEnumerator.h', + 'nsArrayUtils.h', + 'nsBaseHashtable.h', + 'nsCategoryCache.h', + 'nsClassHashtable.h', + 'nsCOMArray.h', + 'nsComponentManagerUtils.h', + 'nsCOMPtr.h', + 'nsCRTGlue.h', + 'nsCycleCollectionNoteChild.h', + 'nsCycleCollectionNoteRootCallback.h', + 'nsCycleCollectionParticipant.h', + 'nsCycleCollectionTraversalCallback.h', + 'nsDataHashtable.h', + 'nsDebug.h', + 'nsDeque.h', + 'nsEnumeratorUtils.h', + 'nsHashKeys.h', + 'nsIClassInfoImpl.h', + 'nsID.h', + 'nsIInterfaceRequestorUtils.h', + 'nsINIParser.h', + 'nsInterfaceHashtable.h', + 'nsISupportsImpl.h', + 'nsISupportsUtils.h', + 'nsIWeakReferenceUtils.h', + 'nsJSThingHashtable.h', + 'nsMemory.h', + 'nsPointerHashKeys.h', + 'nsProxyRelease.h', + 'nsQuickSort.h', + 'nsRefPtrHashtable.h', + 'nsServiceManagerUtils.h', + 'nsStringAPI.h', + 'nsStringGlue.h', + 'nsTArray-inl.h', + 'nsTArray.h', + 'nsTArrayForwardDeclare.h', + 'nsTextFormatter.h', + 'nsTHashtable.h', + 'nsThreadUtils.h', + 'nsTObserverArray.h', + 'nsTPriorityQueue.h', + 'nsTWeakRef.h', + 'nsVersionComparator.h', + 'nsWeakReference.h', + 'nsXPTCUtils.h', + 'PLDHashTable.h', +] + +EXPORTS.mozilla += [ + 'AppData.h', + 'AutoRestore.h', + 'BlockingResourceBase.h', + 'CondVar.h', + 'DeadlockDetector.h', + 'EnumeratedArrayCycleCollection.h', + 'FileUtils.h', + 'GenericFactory.h', + 'IntentionalCrash.h', + 'Monitor.h', + 'Mutex.h', + 'Observer.h', + 'ReentrantMonitor.h', +] + +include('objs.mozbuild') + +UNIFIED_SOURCES += xpcom_gluens_src_cppsrcs +UNIFIED_SOURCES += xpcom_glue_src_cppsrcs + +UNIFIED_SOURCES += [ + 'GenericModule.cpp', + 'nsStringAPI.cpp', +] + +Library('xpcomglue_s') + +SDK_LIBRARY = True + +FORCE_STATIC_LIB = True + +if CONFIG['_MSC_VER']: + DEFINES['_USE_ANSI_CPP'] = True + # Don't include directives about which CRT to use + CFLAGS += ['-Zl'] + CXXFLAGS += ['-Zl'] + +LOCAL_INCLUDES += [ + '../build', + '../threads', +] + +if CONFIG['ENABLE_TESTS']: + DIRS += ['tests/gtest'] + +# Include fallible for third party code using the xpcom glue +USE_LIBS += [ + 'fallible', +] + +# Force to build a static library only +NO_EXPAND_LIBS = True + +DIST_INSTALL = True diff --git a/xpcom/glue/nsArrayEnumerator.cpp b/xpcom/glue/nsArrayEnumerator.cpp new file mode 100644 index 0000000000..2d2ef6da78 --- /dev/null +++ b/xpcom/glue/nsArrayEnumerator.cpp @@ -0,0 +1,213 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/Attributes.h" + +#include "nsArrayEnumerator.h" + +#include "nsIArray.h" +#include "nsISimpleEnumerator.h" + +#include "nsCOMArray.h" +#include "nsCOMPtr.h" +#include "mozilla/RefPtr.h" + +class nsSimpleArrayEnumerator final : public nsISimpleEnumerator +{ +public: + // nsISupports interface + NS_DECL_ISUPPORTS + + // nsISimpleEnumerator interface + NS_DECL_NSISIMPLEENUMERATOR + + // nsSimpleArrayEnumerator methods + explicit nsSimpleArrayEnumerator(nsIArray* aValueArray) + : mValueArray(aValueArray) + , mIndex(0) + { + } + +private: + ~nsSimpleArrayEnumerator() {} + +protected: + nsCOMPtr<nsIArray> mValueArray; + uint32_t mIndex; +}; + +NS_IMPL_ISUPPORTS(nsSimpleArrayEnumerator, nsISimpleEnumerator) + +NS_IMETHODIMP +nsSimpleArrayEnumerator::HasMoreElements(bool* aResult) +{ + NS_PRECONDITION(aResult != 0, "null ptr"); + if (!aResult) { + return NS_ERROR_NULL_POINTER; + } + + if (!mValueArray) { + *aResult = false; + return NS_OK; + } + + uint32_t cnt; + nsresult rv = mValueArray->GetLength(&cnt); + if (NS_FAILED(rv)) { + return rv; + } + *aResult = (mIndex < cnt); + return NS_OK; +} + +NS_IMETHODIMP +nsSimpleArrayEnumerator::GetNext(nsISupports** aResult) +{ + NS_PRECONDITION(aResult != 0, "null ptr"); + if (!aResult) { + return NS_ERROR_NULL_POINTER; + } + + if (!mValueArray) { + *aResult = nullptr; + return NS_OK; + } + + uint32_t cnt; + nsresult rv = mValueArray->GetLength(&cnt); + if (NS_FAILED(rv)) { + return rv; + } + if (mIndex >= cnt) { + return NS_ERROR_UNEXPECTED; + } + + return mValueArray->QueryElementAt(mIndex++, NS_GET_IID(nsISupports), + (void**)aResult); +} + +nsresult +NS_NewArrayEnumerator(nsISimpleEnumerator** aResult, nsIArray* aArray) +{ + RefPtr<nsSimpleArrayEnumerator> enumer = new nsSimpleArrayEnumerator(aArray); + enumer.forget(aResult); + return NS_OK; +} + +//////////////////////////////////////////////////////////////////////////////// + +// enumerator implementation for nsCOMArray +// creates a snapshot of the array in question +// you MUST use NS_NewArrayEnumerator to create this, so that +// allocation is done correctly +class nsCOMArrayEnumerator final : public nsISimpleEnumerator +{ +public: + // nsISupports interface + NS_DECL_ISUPPORTS + + // nsISimpleEnumerator interface + NS_DECL_NSISIMPLEENUMERATOR + + // nsSimpleArrayEnumerator methods + nsCOMArrayEnumerator() : mIndex(0) {} + + // specialized operator to make sure we make room for mValues + void* operator new(size_t aSize, const nsCOMArray_base& aArray) CPP_THROW_NEW; + void operator delete(void* aPtr) { ::operator delete(aPtr); } + +private: + ~nsCOMArrayEnumerator(void); + +protected: + uint32_t mIndex; // current position + uint32_t mArraySize; // size of the array + + // this is actually bigger + nsISupports* mValueArray[1]; +}; + +NS_IMPL_ISUPPORTS(nsCOMArrayEnumerator, nsISimpleEnumerator) + +nsCOMArrayEnumerator::~nsCOMArrayEnumerator() +{ + // only release the entries that we haven't visited yet + for (; mIndex < mArraySize; ++mIndex) { + NS_IF_RELEASE(mValueArray[mIndex]); + } +} + +NS_IMETHODIMP +nsCOMArrayEnumerator::HasMoreElements(bool* aResult) +{ + NS_PRECONDITION(aResult != 0, "null ptr"); + if (!aResult) { + return NS_ERROR_NULL_POINTER; + } + + *aResult = (mIndex < mArraySize); + return NS_OK; +} + +NS_IMETHODIMP +nsCOMArrayEnumerator::GetNext(nsISupports** aResult) +{ + NS_PRECONDITION(aResult != 0, "null ptr"); + if (!aResult) { + return NS_ERROR_NULL_POINTER; + } + + if (mIndex >= mArraySize) { + return NS_ERROR_UNEXPECTED; + } + + // pass the ownership of the reference to the caller. Since + // we AddRef'ed during creation of |this|, there is no need + // to AddRef here + *aResult = mValueArray[mIndex++]; + + // this really isn't necessary. just pretend this happens, since + // we'll never visit this value again! + // mValueArray[(mIndex-1)] = nullptr; + + return NS_OK; +} + +void* +nsCOMArrayEnumerator::operator new(size_t aSize, + const nsCOMArray_base& aArray) CPP_THROW_NEW +{ + // create enough space such that mValueArray points to a large + // enough value. Note that the initial value of aSize gives us + // space for mValueArray[0], so we must subtract + aSize += (aArray.Count() - 1) * sizeof(aArray[0]); + + // do the actual allocation + nsCOMArrayEnumerator* result = + static_cast<nsCOMArrayEnumerator*>(::operator new(aSize)); + + // now need to copy over the values, and addref each one + // now this might seem like a lot of work, but we're actually just + // doing all our AddRef's ahead of time since GetNext() doesn't + // need to AddRef() on the way out + uint32_t i; + uint32_t max = result->mArraySize = aArray.Count(); + for (i = 0; i < max; ++i) { + result->mValueArray[i] = aArray[i]; + NS_IF_ADDREF(result->mValueArray[i]); + } + + return result; +} + +nsresult +NS_NewArrayEnumerator(nsISimpleEnumerator** aResult, + const nsCOMArray_base& aArray) +{ + RefPtr<nsCOMArrayEnumerator> enumerator = new (aArray) nsCOMArrayEnumerator(); + enumerator.forget(aResult); + return NS_OK; +} diff --git a/xpcom/glue/nsArrayEnumerator.h b/xpcom/glue/nsArrayEnumerator.h new file mode 100644 index 0000000000..341ae86eda --- /dev/null +++ b/xpcom/glue/nsArrayEnumerator.h @@ -0,0 +1,32 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsArrayEnumerator_h__ +#define nsArrayEnumerator_h__ + +// enumerator implementation for nsIArray + +#include "nscore.h" + +class nsISimpleEnumerator; +class nsIArray; +class nsCOMArray_base; + +// Create an enumerator for an existing nsIArray implementation +// The enumerator holds an owning reference to the array. +nsresult +NS_NewArrayEnumerator(nsISimpleEnumerator** aResult, + nsIArray* aArray); + +// create an enumerator for an existing nsCOMArray<T> implementation +// The enumerator will hold an owning reference to each ELEMENT in +// the array. This means that the nsCOMArray<T> can safely go away +// without its objects going away. +nsresult +NS_NewArrayEnumerator(nsISimpleEnumerator** aResult, + const nsCOMArray_base& aArray); + +#endif diff --git a/xpcom/glue/nsArrayUtils.cpp b/xpcom/glue/nsArrayUtils.cpp new file mode 100644 index 0000000000..4808637376 --- /dev/null +++ b/xpcom/glue/nsArrayUtils.cpp @@ -0,0 +1,23 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsArrayUtils.h" + +// +// do_QueryElementAt helper stuff +// +nsresult +nsQueryArrayElementAt::operator()(const nsIID& aIID, void** aResult) const +{ + nsresult status = mArray ? mArray->QueryElementAt(mIndex, aIID, aResult) : + NS_ERROR_NULL_POINTER; + + if (mErrorPtr) { + *mErrorPtr = status; + } + + return status; +} diff --git a/xpcom/glue/nsArrayUtils.h b/xpcom/glue/nsArrayUtils.h new file mode 100644 index 0000000000..68032e8c0a --- /dev/null +++ b/xpcom/glue/nsArrayUtils.h @@ -0,0 +1,40 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsArrayUtils_h__ +#define nsArrayUtils_h__ + +#include "nsCOMPtr.h" +#include "nsIArray.h" + +// helper class for do_QueryElementAt +class MOZ_STACK_CLASS nsQueryArrayElementAt final : public nsCOMPtr_helper +{ +public: + nsQueryArrayElementAt(nsIArray* aArray, uint32_t aIndex, + nsresult* aErrorPtr) + : mArray(aArray) + , mIndex(aIndex) + , mErrorPtr(aErrorPtr) + { + } + + virtual nsresult NS_FASTCALL operator()(const nsIID& aIID, void**) const + override; + +private: + nsIArray* MOZ_NON_OWNING_REF mArray; + uint32_t mIndex; + nsresult* mErrorPtr; +}; + +inline const nsQueryArrayElementAt +do_QueryElementAt(nsIArray* aArray, uint32_t aIndex, nsresult* aErrorPtr = 0) +{ + return nsQueryArrayElementAt(aArray, aIndex, aErrorPtr); +} + +#endif // nsArrayUtils_h__ diff --git a/xpcom/glue/nsBaseHashtable.h b/xpcom/glue/nsBaseHashtable.h new file mode 100644 index 0000000000..f52df3dd18 --- /dev/null +++ b/xpcom/glue/nsBaseHashtable.h @@ -0,0 +1,270 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsBaseHashtable_h__ +#define nsBaseHashtable_h__ + +#include "mozilla/MemoryReporting.h" +#include "mozilla/Move.h" +#include "nsTHashtable.h" +#include "nsDebug.h" + +template<class KeyClass, class DataType, class UserDataType> +class nsBaseHashtable; // forward declaration + +/** + * the private nsTHashtable::EntryType class used by nsBaseHashtable + * @see nsTHashtable for the specification of this class + * @see nsBaseHashtable for template parameters + */ +template<class KeyClass, class DataType> +class nsBaseHashtableET : public KeyClass +{ +public: + DataType mData; + friend class nsTHashtable<nsBaseHashtableET<KeyClass, DataType>>; + +private: + typedef typename KeyClass::KeyType KeyType; + typedef typename KeyClass::KeyTypePointer KeyTypePointer; + + explicit nsBaseHashtableET(KeyTypePointer aKey); + nsBaseHashtableET(nsBaseHashtableET<KeyClass, DataType>&& aToMove); + ~nsBaseHashtableET(); +}; + +/** + * templated hashtable for simple data types + * This class manages simple data types that do not need construction or + * destruction. + * + * @param KeyClass a wrapper-class for the hashtable key, see nsHashKeys.h + * for a complete specification. + * @param DataType the datatype stored in the hashtable, + * for example, uint32_t or nsCOMPtr. If UserDataType is not the same, + * DataType must implicitly cast to UserDataType + * @param UserDataType the user sees, for example uint32_t or nsISupports* + */ +template<class KeyClass, class DataType, class UserDataType> +class nsBaseHashtable + : protected nsTHashtable<nsBaseHashtableET<KeyClass, DataType>> +{ + typedef mozilla::fallible_t fallible_t; + +public: + typedef typename KeyClass::KeyType KeyType; + typedef nsBaseHashtableET<KeyClass, DataType> EntryType; + + using nsTHashtable<EntryType>::Contains; + + nsBaseHashtable() {} + explicit nsBaseHashtable(uint32_t aInitLength) + : nsTHashtable<EntryType>(aInitLength) + { + } + + /** + * Return the number of entries in the table. + * @return number of entries + */ + uint32_t Count() const { return nsTHashtable<EntryType>::Count(); } + + /** + * retrieve the value for a key. + * @param aKey the key to retreive + * @param aData data associated with this key will be placed at this + * pointer. If you only need to check if the key exists, aData + * may be null. + * @return true if the key exists. If key does not exist, aData is not + * modified. + */ + bool Get(KeyType aKey, UserDataType* aData) const + { + EntryType* ent = this->GetEntry(aKey); + if (!ent) { + return false; + } + + if (aData) { + *aData = ent->mData; + } + + return true; + } + + /** + * Get the value, returning a zero-initialized POD or a default-initialized + * object if the entry is not present in the table. + * + * @param aKey the key to retrieve + * @return The found value, or UserDataType{} if no entry was found with the + * given key. + * @note If zero/default-initialized values are stored in the table, it is + * not possible to distinguish between such a value and a missing entry. + */ + UserDataType Get(KeyType aKey) const + { + EntryType* ent = this->GetEntry(aKey); + if (!ent) { + return UserDataType{}; + } + + return ent->mData; + } + + /** + * Add key to the table if not already present, and return a reference to its + * value. If key is not already in the table then the value is default + * constructed. + */ + DataType& GetOrInsert(const KeyType& aKey) + { + EntryType* ent = this->GetEntry(aKey); + if (ent) { + return ent->mData; + } + + ent = this->PutEntry(aKey); + return ent->mData; + } + + /** + * put a new value for the associated key + * @param aKey the key to put + * @param aData the new data + * @return always true, unless memory allocation failed + */ + void Put(KeyType aKey, const UserDataType& aData) + { + if (!Put(aKey, aData, mozilla::fallible)) { + NS_ABORT_OOM(this->mTable.EntrySize() * this->mTable.EntryCount()); + } + } + + MOZ_MUST_USE bool Put(KeyType aKey, const UserDataType& aData, + const fallible_t&) + { + EntryType* ent = this->PutEntry(aKey, mozilla::fallible); + if (!ent) { + return false; + } + + ent->mData = aData; + + return true; + } + + /** + * remove the data for the associated key + * @param aKey the key to remove from the hashtable + */ + void Remove(KeyType aKey) { this->RemoveEntry(aKey); } + + // This is an iterator that also allows entry removal. Example usage: + // + // for (auto iter = table.Iter(); !iter.Done(); iter.Next()) { + // const KeyType key = iter.Key(); + // const UserDataType data = iter.UserData(); + // // or + // const DataType& data = iter.Data(); + // // ... do stuff with |key| and/or |data| ... + // // ... possibly call iter.Remove() once ... + // } + // + class Iterator : public PLDHashTable::Iterator + { + public: + typedef PLDHashTable::Iterator Base; + + explicit Iterator(nsBaseHashtable* aTable) : Base(&aTable->mTable) {} + Iterator(Iterator&& aOther) : Base(aOther.mTable) {} + ~Iterator() {} + + KeyType Key() const { return static_cast<EntryType*>(Get())->GetKey(); } + UserDataType UserData() const + { + return static_cast<EntryType*>(Get())->mData; + } + DataType& Data() const { return static_cast<EntryType*>(Get())->mData; } + + private: + Iterator() = delete; + Iterator(const Iterator&) = delete; + Iterator& operator=(const Iterator&) = delete; + Iterator& operator=(const Iterator&&) = delete; + }; + + Iterator Iter() { return Iterator(this); } + + Iterator ConstIter() const + { + return Iterator(const_cast<nsBaseHashtable*>(this)); + } + + /** + * reset the hashtable, removing all entries + */ + void Clear() { nsTHashtable<EntryType>::Clear(); } + + /** + * Measure the size of the table's entry storage. The size of things pointed + * to by entries must be measured separately; hence the "Shallow" prefix. + * + * @param aMallocSizeOf the function used to measure heap-allocated blocks + * @return the summed size of the table's storage + */ + size_t ShallowSizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const + { + return this->mTable.ShallowSizeOfExcludingThis(aMallocSizeOf); + } + + /** + * Like ShallowSizeOfExcludingThis, but includes sizeof(*this). + */ + size_t ShallowSizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const + { + return aMallocSizeOf(this) + ShallowSizeOfExcludingThis(aMallocSizeOf); + } + + /** + * Swap the elements in this hashtable with the elements in aOther. + */ + void SwapElements(nsBaseHashtable& aOther) + { + nsTHashtable<EntryType>::SwapElements(aOther); + } + + +#ifdef DEBUG + using nsTHashtable<EntryType>::MarkImmutable; +#endif +}; + +// +// nsBaseHashtableET definitions +// + +template<class KeyClass, class DataType> +nsBaseHashtableET<KeyClass, DataType>::nsBaseHashtableET(KeyTypePointer aKey) + : KeyClass(aKey) + , mData() +{ +} + +template<class KeyClass, class DataType> +nsBaseHashtableET<KeyClass, DataType>::nsBaseHashtableET( + nsBaseHashtableET<KeyClass, DataType>&& aToMove) + : KeyClass(mozilla::Move(aToMove)) + , mData(mozilla::Move(aToMove.mData)) +{ +} + +template<class KeyClass, class DataType> +nsBaseHashtableET<KeyClass, DataType>::~nsBaseHashtableET() +{ +} + +#endif // nsBaseHashtable_h__ diff --git a/xpcom/glue/nsCOMArray.cpp b/xpcom/glue/nsCOMArray.cpp new file mode 100644 index 0000000000..3522f7b0d1 --- /dev/null +++ b/xpcom/glue/nsCOMArray.cpp @@ -0,0 +1,323 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsCOMArray.h" + +#include "mozilla/MemoryReporting.h" +#include "mozilla/OperatorNewExtensions.h" + +#include "nsCOMPtr.h" + +// This specialization is private to nsCOMArray. +// It exists solely to automatically zero-out newly created array elements. +template<> +class nsTArrayElementTraits<nsISupports*> +{ + typedef nsISupports* E; +public: + // Zero out the value + static inline void Construct(E* aE) + { + new (mozilla::KnownNotNull, static_cast<void*>(aE)) E(); + } + // Invoke the copy-constructor in place. + template<class A> + static inline void Construct(E* aE, const A& aArg) + { + new (mozilla::KnownNotNull, static_cast<void*>(aE)) E(aArg); + } + // Invoke the destructor in place. + static inline void Destruct(E* aE) + { + aE->~E(); + } +}; + +static void ReleaseObjects(nsTArray<nsISupports*>& aArray); + +// implementations of non-trivial methods in nsCOMArray_base + +nsCOMArray_base::nsCOMArray_base(const nsCOMArray_base& aOther) +{ + // make sure we do only one allocation + mArray.SetCapacity(aOther.Count()); + AppendObjects(aOther); +} + +nsCOMArray_base::~nsCOMArray_base() +{ + Clear(); +} + +int32_t +nsCOMArray_base::IndexOf(nsISupports* aObject, uint32_t aStartIndex) const +{ + return mArray.IndexOf(aObject, aStartIndex); +} + +int32_t +nsCOMArray_base::IndexOfObject(nsISupports* aObject) const +{ + nsCOMPtr<nsISupports> supports = do_QueryInterface(aObject); + if (NS_WARN_IF(!supports)) { + return -1; + } + + uint32_t i, count; + int32_t retval = -1; + count = mArray.Length(); + for (i = 0; i < count; ++i) { + nsCOMPtr<nsISupports> arrayItem = do_QueryInterface(mArray[i]); + if (arrayItem == supports) { + retval = i; + break; + } + } + return retval; +} + +bool +nsCOMArray_base::EnumerateForwards(nsBaseArrayEnumFunc aFunc, void* aData) const +{ + for (uint32_t index = 0; index < mArray.Length(); ++index) { + if (!(*aFunc)(mArray[index], aData)) { + return false; + } + } + + return true; +} + +bool +nsCOMArray_base::EnumerateBackwards(nsBaseArrayEnumFunc aFunc, void* aData) const +{ + for (uint32_t index = mArray.Length(); index--; ) { + if (!(*aFunc)(mArray[index], aData)) { + return false; + } + } + + return true; +} + +int +nsCOMArray_base::nsCOMArrayComparator(const void* aElement1, + const void* aElement2, + void* aData) +{ + nsCOMArrayComparatorContext* ctx = + static_cast<nsCOMArrayComparatorContext*>(aData); + return (*ctx->mComparatorFunc)(*static_cast<nsISupports* const*>(aElement1), + *static_cast<nsISupports* const*>(aElement2), + ctx->mData); +} + +void +nsCOMArray_base::Sort(nsBaseArrayComparatorFunc aFunc, void* aData) +{ + if (mArray.Length() > 1) { + nsCOMArrayComparatorContext ctx = {aFunc, aData}; + NS_QuickSort(mArray.Elements(), mArray.Length(), sizeof(nsISupports*), + nsCOMArrayComparator, &ctx); + } +} + +bool +nsCOMArray_base::InsertObjectAt(nsISupports* aObject, int32_t aIndex) +{ + if ((uint32_t)aIndex > mArray.Length()) { + return false; + } + + if (!mArray.InsertElementAt(aIndex, aObject)) { + return false; + } + + NS_IF_ADDREF(aObject); + return true; +} + +void +nsCOMArray_base::InsertElementAt(uint32_t aIndex, nsISupports* aElement) +{ + mArray.InsertElementAt(aIndex, aElement); + NS_IF_ADDREF(aElement); +} + +void +nsCOMArray_base::InsertElementAt(uint32_t aIndex, already_AddRefed<nsISupports> aElement) +{ + mArray.InsertElementAt(aIndex, aElement.take()); +} + +bool +nsCOMArray_base::InsertObjectsAt(const nsCOMArray_base& aObjects, int32_t aIndex) +{ + if ((uint32_t)aIndex > mArray.Length()) { + return false; + } + + if (!mArray.InsertElementsAt(aIndex, aObjects.mArray)) { + return false; + } + + // need to addref all these + uint32_t count = aObjects.Length(); + for (uint32_t i = 0; i < count; ++i) { + NS_IF_ADDREF(aObjects[i]); + } + + return true; +} + +void +nsCOMArray_base::InsertElementsAt(uint32_t aIndex, + const nsCOMArray_base& aElements) +{ + mArray.InsertElementsAt(aIndex, aElements.mArray); + + // need to addref all these + uint32_t count = aElements.Length(); + for (uint32_t i = 0; i < count; ++i) { + NS_IF_ADDREF(aElements[i]); + } +} + +void +nsCOMArray_base::InsertElementsAt(uint32_t aIndex, + nsISupports* const* aElements, + uint32_t aCount) +{ + mArray.InsertElementsAt(aIndex, aElements, aCount); + + // need to addref all these + for (uint32_t i = 0; i < aCount; ++i) { + NS_IF_ADDREF(aElements[i]); + } +} + +void +nsCOMArray_base::ReplaceObjectAt(nsISupports* aObject, int32_t aIndex) +{ + mArray.EnsureLengthAtLeast(aIndex + 1); + nsISupports* oldObject = mArray[aIndex]; + // Make sure to addref first, in case aObject == oldObject + NS_IF_ADDREF(mArray[aIndex] = aObject); + NS_IF_RELEASE(oldObject); +} + +bool +nsCOMArray_base::RemoveObject(nsISupports* aObject) +{ + bool result = mArray.RemoveElement(aObject); + if (result) { + NS_IF_RELEASE(aObject); + } + return result; +} + +bool +nsCOMArray_base::RemoveObjectAt(int32_t aIndex) +{ + if (uint32_t(aIndex) < mArray.Length()) { + nsISupports* element = mArray[aIndex]; + + mArray.RemoveElementAt(aIndex); + NS_IF_RELEASE(element); + return true; + } + + return false; +} + +void +nsCOMArray_base::RemoveElementAt(uint32_t aIndex) +{ + nsISupports* element = mArray[aIndex]; + mArray.RemoveElementAt(aIndex); + NS_IF_RELEASE(element); +} + +bool +nsCOMArray_base::RemoveObjectsAt(int32_t aIndex, int32_t aCount) +{ + if (uint32_t(aIndex) + uint32_t(aCount) <= mArray.Length()) { + nsTArray<nsISupports*> elementsToDestroy(aCount); + elementsToDestroy.AppendElements(mArray.Elements() + aIndex, aCount); + mArray.RemoveElementsAt(aIndex, aCount); + ReleaseObjects(elementsToDestroy); + return true; + } + + return false; +} + +void +nsCOMArray_base::RemoveElementsAt(uint32_t aIndex, uint32_t aCount) +{ + nsTArray<nsISupports*> elementsToDestroy(aCount); + elementsToDestroy.AppendElements(mArray.Elements() + aIndex, aCount); + mArray.RemoveElementsAt(aIndex, aCount); + ReleaseObjects(elementsToDestroy); +} + +// useful for destructors +void +ReleaseObjects(nsTArray<nsISupports*>& aArray) +{ + for (uint32_t i = 0; i < aArray.Length(); ++i) { + NS_IF_RELEASE(aArray[i]); + } +} + +void +nsCOMArray_base::Clear() +{ + nsTArray<nsISupports*> objects; + objects.SwapElements(mArray); + ReleaseObjects(objects); +} + +bool +nsCOMArray_base::SetCount(int32_t aNewCount) +{ + NS_ASSERTION(aNewCount >= 0, "SetCount(negative index)"); + if (aNewCount < 0) { + return false; + } + + int32_t count = mArray.Length(); + if (count > aNewCount) { + RemoveObjectsAt(aNewCount, mArray.Length() - aNewCount); + } + mArray.SetLength(aNewCount); + return true; +} + +void +nsCOMArray_base::Adopt(nsISupports** aElements, uint32_t aSize) +{ + Clear(); + mArray.AppendElements(aElements, aSize); + + // Free the allocated array as well. + NS_Free(aElements); +} + +uint32_t +nsCOMArray_base::Forget(nsISupports*** aElements) +{ + uint32_t length = Length(); + size_t array_size = sizeof(nsISupports*) * length; + nsISupports** array = static_cast<nsISupports**>(NS_Alloc(array_size)); + memmove(array, Elements(), array_size); + *aElements = array; + // Don't Release the contained pointers; the caller of the method will + // do this eventually. + mArray.Clear(); + + return length; +} diff --git a/xpcom/glue/nsCOMArray.h b/xpcom/glue/nsCOMArray.h new file mode 100644 index 0000000000..56d077a2fd --- /dev/null +++ b/xpcom/glue/nsCOMArray.h @@ -0,0 +1,473 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsCOMArray_h__ +#define nsCOMArray_h__ + +#include "mozilla/Attributes.h" +#include "mozilla/MemoryReporting.h" + +#include "nsCycleCollectionNoteChild.h" +#include "nsTArray.h" +#include "nsISupports.h" + +// See below for the definition of nsCOMArray<T> + +// a class that's nsISupports-specific, so that we can contain the +// work of this class in the XPCOM dll +class nsCOMArray_base +{ + friend class nsArrayBase; +protected: + nsCOMArray_base() {} + explicit nsCOMArray_base(int32_t aCount) : mArray(aCount) {} + nsCOMArray_base(const nsCOMArray_base& aOther); + ~nsCOMArray_base(); + + int32_t IndexOf(nsISupports* aObject, uint32_t aStartIndex = 0) const; + bool Contains(nsISupports* aObject) const + { + return IndexOf(aObject) != -1; + } + + int32_t IndexOfObject(nsISupports* aObject) const; + bool ContainsObject(nsISupports* aObject) const + { + return IndexOfObject(aObject) != -1; + } + + typedef bool (*nsBaseArrayEnumFunc)(void* aElement, void* aData); + + // enumerate through the array with a callback. + bool EnumerateForwards(nsBaseArrayEnumFunc aFunc, void* aData) const; + + bool EnumerateBackwards(nsBaseArrayEnumFunc aFunc, void* aData) const; + + typedef int (*nsBaseArrayComparatorFunc)(nsISupports* aElement1, + nsISupports* aElement2, + void* aData); + + struct nsCOMArrayComparatorContext + { + nsBaseArrayComparatorFunc mComparatorFunc; + void* mData; + }; + + static int nsCOMArrayComparator(const void* aElement1, const void* aElement2, + void* aData); + void Sort(nsBaseArrayComparatorFunc aFunc, void* aData); + + bool InsertObjectAt(nsISupports* aObject, int32_t aIndex); + void InsertElementAt(uint32_t aIndex, nsISupports* aElement); + void InsertElementAt(uint32_t aIndex, already_AddRefed<nsISupports> aElement); + bool InsertObjectsAt(const nsCOMArray_base& aObjects, int32_t aIndex); + void InsertElementsAt(uint32_t aIndex, const nsCOMArray_base& aElements); + void InsertElementsAt(uint32_t aIndex, nsISupports* const* aElements, + uint32_t aCount); + void ReplaceObjectAt(nsISupports* aObject, int32_t aIndex); + void ReplaceElementAt(uint32_t aIndex, nsISupports* aElement) + { + nsISupports* oldElement = mArray[aIndex]; + NS_IF_ADDREF(mArray[aIndex] = aElement); + NS_IF_RELEASE(oldElement); + } + bool AppendObject(nsISupports* aObject) + { + return InsertObjectAt(aObject, Count()); + } + void AppendElement(nsISupports* aElement) + { + InsertElementAt(Length(), aElement); + } + void AppendElement(already_AddRefed<nsISupports> aElement) + { + InsertElementAt(Length(), mozilla::Move(aElement)); + } + + bool AppendObjects(const nsCOMArray_base& aObjects) + { + return InsertObjectsAt(aObjects, Count()); + } + void AppendElements(const nsCOMArray_base& aElements) + { + return InsertElementsAt(Length(), aElements); + } + void AppendElements(nsISupports* const* aElements, uint32_t aCount) + { + return InsertElementsAt(Length(), aElements, aCount); + } + bool RemoveObject(nsISupports* aObject); + nsISupports** Elements() { return mArray.Elements(); } + void SwapElements(nsCOMArray_base& aOther) + { + mArray.SwapElements(aOther.mArray); + } + + void Adopt(nsISupports** aElements, uint32_t aCount); + uint32_t Forget(nsISupports*** aElements); +public: + // elements in the array (including null elements!) + int32_t Count() const { return mArray.Length(); } + // nsTArray-compatible version + uint32_t Length() const { return mArray.Length(); } + bool IsEmpty() const { return mArray.IsEmpty(); } + + // If the array grows, the newly created entries will all be null; + // if the array shrinks, the excess entries will all be released. + bool SetCount(int32_t aNewCount); + // nsTArray-compatible version + void TruncateLength(uint32_t aNewLength) + { + if (mArray.Length() > aNewLength) { + RemoveElementsAt(aNewLength, mArray.Length() - aNewLength); + } + } + + // remove all elements in the array, and call NS_RELEASE on each one + void Clear(); + + nsISupports* ObjectAt(int32_t aIndex) const { return mArray[aIndex]; } + // nsTArray-compatible version + nsISupports* ElementAt(uint32_t aIndex) const { return mArray[aIndex]; } + + nsISupports* SafeObjectAt(int32_t aIndex) const + { + return mArray.SafeElementAt(aIndex, nullptr); + } + // nsTArray-compatible version + nsISupports* SafeElementAt(uint32_t aIndex) const + { + return mArray.SafeElementAt(aIndex, nullptr); + } + + nsISupports* operator[](int32_t aIndex) const { return mArray[aIndex]; } + + // remove an element at a specific position, shrinking the array + // as necessary + bool RemoveObjectAt(int32_t aIndex); + // nsTArray-compatible version + void RemoveElementAt(uint32_t aIndex); + + // remove a range of elements at a specific position, shrinking the array + // as necessary + bool RemoveObjectsAt(int32_t aIndex, int32_t aCount); + // nsTArray-compatible version + void RemoveElementsAt(uint32_t aIndex, uint32_t aCount); + + void SwapElementsAt(uint32_t aIndex1, uint32_t aIndex2) + { + nsISupports* tmp = mArray[aIndex1]; + mArray[aIndex1] = mArray[aIndex2]; + mArray[aIndex2] = tmp; + } + + // Ensures there is enough space to store a total of aCapacity objects. + // This method never deletes any objects. + void SetCapacity(uint32_t aCapacity) { mArray.SetCapacity(aCapacity); } + uint32_t Capacity() { return mArray.Capacity(); } + + // Measures the size of the array's element storage. If you want to measure + // anything hanging off the array, you must iterate over the elements and + // measure them individually; hence the "Shallow" prefix. Note that because + // each element in an nsCOMArray<T> is actually a T* any such iteration + // should use a SizeOfIncludingThis() function on each element rather than a + // SizeOfExcludingThis() function, so that the memory taken by the T itself + // is included as well as anything it points to. + size_t ShallowSizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const + { + return mArray.ShallowSizeOfExcludingThis(aMallocSizeOf); + } + +private: + + // the actual storage + nsTArray<nsISupports*> mArray; + + // don't implement these, defaults will muck with refcounts! + nsCOMArray_base& operator=(const nsCOMArray_base& aOther) = delete; +}; + +inline void +ImplCycleCollectionUnlink(nsCOMArray_base& aField) +{ + aField.Clear(); +} + +inline void +ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback& aCallback, + nsCOMArray_base& aField, + const char* aName, + uint32_t aFlags = 0) +{ + aFlags |= CycleCollectionEdgeNameArrayFlag; + int32_t length = aField.Count(); + for (int32_t i = 0; i < length; ++i) { + CycleCollectionNoteChild(aCallback, aField[i], aName, aFlags); + } +} + + +// a non-XPCOM, refcounting array of XPCOM objects +// used as a member variable or stack variable - this object is NOT +// refcounted, but the objects that it holds are +// +// most of the read-only accessors like ObjectAt()/etc do NOT refcount +// on the way out. This means that you can do one of two things: +// +// * does an addref, but holds onto a reference +// nsCOMPtr<T> foo = array[i]; +// +// * avoids the refcount, but foo might go stale if array[i] is ever +// * modified/removed. Be careful not to NS_RELEASE(foo)! +// T* foo = array[i]; +// +// This array will accept null as an argument for any object, and will store +// null in the array. But that also means that methods like ObjectAt() may +// return null when referring to an existing, but null entry in the array. +template<class T> +class nsCOMArray : public nsCOMArray_base +{ +public: + nsCOMArray() {} + explicit nsCOMArray(int32_t aCount) : nsCOMArray_base(aCount) {} + explicit nsCOMArray(const nsCOMArray<T>& aOther) : nsCOMArray_base(aOther) {} + nsCOMArray(nsCOMArray<T>&& aOther) { SwapElements(aOther); } + ~nsCOMArray() {} + + // We have a move assignment operator, but no copy assignment operator. + nsCOMArray<T>& operator=(nsCOMArray<T> && aOther) + { + SwapElements(aOther); + return *this; + } + + // these do NOT refcount on the way out, for speed + T* ObjectAt(int32_t aIndex) const + { + return static_cast<T*>(nsCOMArray_base::ObjectAt(aIndex)); + } + // nsTArray-compatible version + T* ElementAt(uint32_t aIndex) const + { + return static_cast<T*>(nsCOMArray_base::ElementAt(aIndex)); + } + + // these do NOT refcount on the way out, for speed + T* SafeObjectAt(int32_t aIndex) const + { + return static_cast<T*>(nsCOMArray_base::SafeObjectAt(aIndex)); + } + // nsTArray-compatible version + T* SafeElementAt(uint32_t aIndex) const + { + return static_cast<T*>(nsCOMArray_base::SafeElementAt(aIndex)); + } + + // indexing operator for syntactic sugar + T* operator[](int32_t aIndex) const { return ObjectAt(aIndex); } + + // index of the element in question.. does NOT refcount + // note: this does not check COM object identity. Use + // IndexOfObject() for that purpose + int32_t IndexOf(T* aObject, uint32_t aStartIndex = 0) const + { + return nsCOMArray_base::IndexOf(aObject, aStartIndex); + } + bool Contains(T* aObject) const + { + return nsCOMArray_base::Contains(aObject); + } + + // index of the element in question.. be careful! + // this is much slower than IndexOf() because it uses + // QueryInterface to determine actual COM identity of the object + // if you need to do this frequently then consider enforcing + // COM object identity before adding/comparing elements + int32_t IndexOfObject(T* aObject) const + { + return nsCOMArray_base::IndexOfObject(aObject); + } + bool ContainsObject(nsISupports* aObject) const + { + return nsCOMArray_base::ContainsObject(aObject); + } + + // inserts aObject at aIndex, shifting the objects at aIndex and + // later to make space + bool InsertObjectAt(T* aObject, int32_t aIndex) + { + return nsCOMArray_base::InsertObjectAt(aObject, aIndex); + } + // nsTArray-compatible version + void InsertElementAt(uint32_t aIndex, T* aElement) + { + nsCOMArray_base::InsertElementAt(aIndex, aElement); + } + + // inserts the objects from aObject at aIndex, shifting the + // objects at aIndex and later to make space + bool InsertObjectsAt(const nsCOMArray<T>& aObjects, int32_t aIndex) + { + return nsCOMArray_base::InsertObjectsAt(aObjects, aIndex); + } + // nsTArray-compatible version + void InsertElementsAt(uint32_t aIndex, const nsCOMArray<T>& aElements) + { + nsCOMArray_base::InsertElementsAt(aIndex, aElements); + } + void InsertElementsAt(uint32_t aIndex, T* const* aElements, uint32_t aCount) + { + nsCOMArray_base::InsertElementsAt( + aIndex, reinterpret_cast<nsISupports* const*>(aElements), aCount); + } + + // replaces an existing element. Warning: if the array grows, + // the newly created entries will all be null + void ReplaceObjectAt(T* aObject, int32_t aIndex) + { + nsCOMArray_base::ReplaceObjectAt(aObject, aIndex); + } + // nsTArray-compatible version + void ReplaceElementAt(uint32_t aIndex, T* aElement) + { + nsCOMArray_base::ReplaceElementAt(aIndex, aElement); + } + + // Enumerator callback function. Return false to stop + // Here's a more readable form: + // bool enumerate(T* aElement, void* aData) + typedef bool (*nsCOMArrayEnumFunc)(T* aElement, void* aData); + + // enumerate through the array with a callback. + bool EnumerateForwards(nsCOMArrayEnumFunc aFunc, void* aData) + { + return nsCOMArray_base::EnumerateForwards(nsBaseArrayEnumFunc(aFunc), + aData); + } + + bool EnumerateBackwards(nsCOMArrayEnumFunc aFunc, void* aData) + { + return nsCOMArray_base::EnumerateBackwards(nsBaseArrayEnumFunc(aFunc), + aData); + } + + typedef int (*nsCOMArrayComparatorFunc)(T* aElement1, T* aElement2, + void* aData); + + void Sort(nsCOMArrayComparatorFunc aFunc, void* aData) + { + nsCOMArray_base::Sort(nsBaseArrayComparatorFunc(aFunc), aData); + } + + // append an object, growing the array as necessary + bool AppendObject(T* aObject) + { + return nsCOMArray_base::AppendObject(aObject); + } + // nsTArray-compatible version + void AppendElement(T* aElement) + { + nsCOMArray_base::AppendElement(aElement); + } + void AppendElement(already_AddRefed<T> aElement) + { + nsCOMArray_base::AppendElement(mozilla::Move(aElement)); + } + + // append objects, growing the array as necessary + bool AppendObjects(const nsCOMArray<T>& aObjects) + { + return nsCOMArray_base::AppendObjects(aObjects); + } + // nsTArray-compatible version + void AppendElements(const nsCOMArray<T>& aElements) + { + return nsCOMArray_base::AppendElements(aElements); + } + void AppendElements(T* const* aElements, uint32_t aCount) + { + InsertElementsAt(Length(), aElements, aCount); + } + + // remove the first instance of the given object and shrink the + // array as necessary + // Warning: if you pass null here, it will remove the first null element + bool RemoveObject(T* aObject) + { + return nsCOMArray_base::RemoveObject(aObject); + } + // nsTArray-compatible version + bool RemoveElement(T* aElement) + { + return nsCOMArray_base::RemoveObject(aElement); + } + + T** Elements() + { + return reinterpret_cast<T**>(nsCOMArray_base::Elements()); + } + void SwapElements(nsCOMArray<T>& aOther) + { + nsCOMArray_base::SwapElements(aOther); + } + + /** + * Adopt parameters that resulted from an XPIDL outparam. The aElements + * parameter will be freed as a result of the call. + * + * Example usage: + * nsCOMArray<nsISomeInterface> array; + * nsISomeInterface** elements; + * uint32_t length; + * ptr->GetSomeArray(&elements, &length); + * array.Adopt(elements, length); + */ + void Adopt(T** aElements, uint32_t aSize) + { + nsCOMArray_base::Adopt(reinterpret_cast<nsISupports**>(aElements), aSize); + } + + /** + * Export the contents of this array to an XPIDL outparam. The array will be + * Clear()'d after this operation. + * + * Example usage: + * nsCOMArray<nsISomeInterface> array; + * *length = array.Forget(retval); + */ + uint32_t Forget(T*** aElements) + { + return nsCOMArray_base::Forget(reinterpret_cast<nsISupports***>(aElements)); + } + +private: + + // don't implement these! + nsCOMArray<T>& operator=(const nsCOMArray<T>& aOther) = delete; +}; + +template<typename T> +inline void +ImplCycleCollectionUnlink(nsCOMArray<T>& aField) +{ + aField.Clear(); +} + +template<typename E> +inline void +ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback& aCallback, + nsCOMArray<E>& aField, + const char* aName, + uint32_t aFlags = 0) +{ + aFlags |= CycleCollectionEdgeNameArrayFlag; + int32_t length = aField.Count(); + for (int32_t i = 0; i < length; ++i) { + CycleCollectionNoteChild(aCallback, aField[i], aName, aFlags); + } +} + +#endif diff --git a/xpcom/glue/nsCOMPtr.cpp b/xpcom/glue/nsCOMPtr.cpp new file mode 100644 index 0000000000..263fa62c2d --- /dev/null +++ b/xpcom/glue/nsCOMPtr.cpp @@ -0,0 +1,128 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsCOMPtr.h" + +nsresult +nsQueryInterface::operator()(const nsIID& aIID, void** aAnswer) const +{ + nsresult status; + if (mRawPtr) { + status = mRawPtr->QueryInterface(aIID, aAnswer); + } else { + status = NS_ERROR_NULL_POINTER; + } + + return status; +} + +nsresult +nsQueryInterfaceWithError::operator()(const nsIID& aIID, void** aAnswer) const +{ + nsresult status; + if (mRawPtr) { + status = mRawPtr->QueryInterface(aIID, aAnswer); + } else { + status = NS_ERROR_NULL_POINTER; + } + + if (mErrorPtr) { + *mErrorPtr = status; + } + return status; +} + +void +nsCOMPtr_base::assign_with_AddRef(nsISupports* aRawPtr) +{ + if (aRawPtr) { + NSCAP_ADDREF(this, aRawPtr); + } + assign_assuming_AddRef(aRawPtr); +} + +void +nsCOMPtr_base::assign_from_qi(const nsQueryInterface aQI, const nsIID& aIID) +{ + void* newRawPtr; + if (NS_FAILED(aQI(aIID, &newRawPtr))) { + newRawPtr = nullptr; + } + assign_assuming_AddRef(static_cast<nsISupports*>(newRawPtr)); +} + +void +nsCOMPtr_base::assign_from_qi_with_error(const nsQueryInterfaceWithError& aQI, + const nsIID& aIID) +{ + void* newRawPtr; + if (NS_FAILED(aQI(aIID, &newRawPtr))) { + newRawPtr = nullptr; + } + assign_assuming_AddRef(static_cast<nsISupports*>(newRawPtr)); +} + +void +nsCOMPtr_base::assign_from_gs_cid(const nsGetServiceByCID aGS, + const nsIID& aIID) +{ + void* newRawPtr; + if (NS_FAILED(aGS(aIID, &newRawPtr))) { + newRawPtr = nullptr; + } + assign_assuming_AddRef(static_cast<nsISupports*>(newRawPtr)); +} + +void +nsCOMPtr_base::assign_from_gs_cid_with_error( + const nsGetServiceByCIDWithError& aGS, const nsIID& aIID) +{ + void* newRawPtr; + if (NS_FAILED(aGS(aIID, &newRawPtr))) { + newRawPtr = nullptr; + } + assign_assuming_AddRef(static_cast<nsISupports*>(newRawPtr)); +} + +void +nsCOMPtr_base::assign_from_gs_contractid(const nsGetServiceByContractID aGS, + const nsIID& aIID) +{ + void* newRawPtr; + if (NS_FAILED(aGS(aIID, &newRawPtr))) { + newRawPtr = nullptr; + } + assign_assuming_AddRef(static_cast<nsISupports*>(newRawPtr)); +} + +void +nsCOMPtr_base::assign_from_gs_contractid_with_error( + const nsGetServiceByContractIDWithError& aGS, const nsIID& aIID) +{ + void* newRawPtr; + if (NS_FAILED(aGS(aIID, &newRawPtr))) { + newRawPtr = nullptr; + } + assign_assuming_AddRef(static_cast<nsISupports*>(newRawPtr)); +} + +void +nsCOMPtr_base::assign_from_helper(const nsCOMPtr_helper& aHelper, + const nsIID& aIID) +{ + void* newRawPtr; + if (NS_FAILED(aHelper(aIID, &newRawPtr))) { + newRawPtr = nullptr; + } + assign_assuming_AddRef(static_cast<nsISupports*>(newRawPtr)); +} + +void** +nsCOMPtr_base::begin_assignment() +{ + assign_assuming_AddRef(nullptr); + return reinterpret_cast<void**>(&mRawPtr); +} diff --git a/xpcom/glue/nsCOMPtr.h b/xpcom/glue/nsCOMPtr.h new file mode 100644 index 0000000000..373f55cbb9 --- /dev/null +++ b/xpcom/glue/nsCOMPtr.h @@ -0,0 +1,1472 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsCOMPtr_h___ +#define nsCOMPtr_h___ + +/* + * Having problems? + * + * See the User Manual at: + * http://www.mozilla.org/projects/xpcom/nsCOMPtr.html + * + * + * nsCOMPtr + * better than a raw pointer + * for owning objects + * -- scc + */ + +#include "mozilla/AlreadyAddRefed.h" +#include "mozilla/Assertions.h" +#include "mozilla/Attributes.h" +#include "mozilla/Move.h" +#include "mozilla/TypeTraits.h" + +#include "nsDebug.h" // for |NS_ASSERTION| +#include "nsISupportsUtils.h" // for |nsresult|, |NS_ADDREF|, |NS_GET_TEMPLATE_IID| et al +#include "mozilla/RefPtr.h" + +#include "nsCycleCollectionNoteChild.h" + + +/* + * WARNING: This file defines several macros for internal use only. These + * macros begin with the prefix |NSCAP_|. Do not use these macros in your own + * code. They are for internal use only for cross-platform compatibility, and + * are subject to change without notice. + */ + + +#ifdef _MSC_VER + // Under VC++, we win by inlining StartAssignment. + #define NSCAP_FEATURE_INLINE_STARTASSIGNMENT + + // Also under VC++, at the highest warning level, we are overwhelmed with + // warnings about (unused) inline functions being removed. This is to be + // expected with templates, so we disable the warning. + #pragma warning( disable: 4514 ) +#endif + +#define NSCAP_FEATURE_USE_BASE + +#ifdef DEBUG + #define NSCAP_FEATURE_TEST_DONTQUERY_CASES + #undef NSCAP_FEATURE_USE_BASE +#endif + +#ifdef __GNUC__ + // Our use of nsCOMPtr_base::mRawPtr violates the C++ standard's aliasing + // rules. Mark it with the may_alias attribute so that gcc 3.3 and higher + // don't reorder instructions based on aliasing assumptions for + // this variable. Fortunately, gcc versions < 3.3 do not do any + // optimizations that break nsCOMPtr. + + #define NS_MAY_ALIAS_PTR(t) t* __attribute__((__may_alias__)) +#else + #define NS_MAY_ALIAS_PTR(t) t* +#endif + +#if defined(NSCAP_DISABLE_DEBUG_PTR_TYPES) + #define NSCAP_FEATURE_USE_BASE +#endif + +/* + * The following three macros (NSCAP_ADDREF, NSCAP_RELEASE, and + * NSCAP_LOG_ASSIGNMENT) allow external clients the ability to add logging or + * other interesting debug facilities. In fact, if you want |nsCOMPtr| to + * participate in the standard logging facility, you provide + * (e.g., in "nsISupportsImpl.h") suitable definitions + * + * #define NSCAP_ADDREF(this, ptr) NS_ADDREF(ptr) + * #define NSCAP_RELEASE(this, ptr) NS_RELEASE(ptr) + */ + +#ifndef NSCAP_ADDREF + #define NSCAP_ADDREF(this, ptr) (ptr)->AddRef() +#endif + +#ifndef NSCAP_RELEASE + #define NSCAP_RELEASE(this, ptr) (ptr)->Release() +#endif + +// Clients can define |NSCAP_LOG_ASSIGNMENT| to perform logging. +#ifdef NSCAP_LOG_ASSIGNMENT + // Remember that |NSCAP_LOG_ASSIGNMENT| was defined by some client so that we + // know to instantiate |~nsGetterAddRefs| in turn to note the external + // assignment into the |nsCOMPtr|. + #define NSCAP_LOG_EXTERNAL_ASSIGNMENT +#else + // ...otherwise, just strip it out of the code + #define NSCAP_LOG_ASSIGNMENT(this, ptr) +#endif + +#ifndef NSCAP_LOG_RELEASE + #define NSCAP_LOG_RELEASE(this, ptr) +#endif + +namespace mozilla { +template<class T> class OwningNonNull; +} // namespace mozilla + +template<class T> +inline already_AddRefed<T> +dont_AddRef(T* aRawPtr) +{ + return already_AddRefed<T>(aRawPtr); +} + +template<class T> +inline already_AddRefed<T>&& +dont_AddRef(already_AddRefed<T>&& aAlreadyAddRefedPtr) +{ + return mozilla::Move(aAlreadyAddRefedPtr); +} + + +/* + * An nsCOMPtr_helper transforms commonly called getters into typesafe forms + * that are more convenient to call, and more efficient to use with |nsCOMPtr|s. + * Good candidates for helpers are |QueryInterface()|, |CreateInstance()|, etc. + * + * Here are the rules for a helper: + * - it implements |operator()| to produce an interface pointer + * - (except for its name) |operator()| is a valid [XP]COM `getter' + * - the interface pointer that it returns is already |AddRef()|ed (as from + * any good getter) + * - it matches the type requested with the supplied |nsIID| argument + * - its constructor provides an optional |nsresult*| that |operator()| can + * fill in with an error when it is executed + * + * See |class nsGetInterface| for an example. + */ +class MOZ_STACK_CLASS nsCOMPtr_helper +{ +public: + virtual nsresult NS_FASTCALL operator()(const nsIID&, void**) const = 0; +}; + +/* + * nsQueryInterface could have been implemented as an nsCOMPtr_helper to avoid + * adding specialized machinery in nsCOMPtr, but do_QueryInterface is called + * often enough that the codesize savings are big enough to warrant the + * specialcasing. + */ +class MOZ_STACK_CLASS nsQueryInterface final +{ +public: + explicit + nsQueryInterface(nsISupports* aRawPtr) : mRawPtr(aRawPtr) {} + + nsresult NS_FASTCALL operator()(const nsIID& aIID, void**) const; + +private: + nsISupports* MOZ_OWNING_REF mRawPtr; +}; + +class nsQueryInterfaceWithError final +{ +public: + nsQueryInterfaceWithError(nsISupports* aRawPtr, nsresult* aError) + : mRawPtr(aRawPtr) + , mErrorPtr(aError) + { + } + + nsresult NS_FASTCALL operator()(const nsIID& aIID, void**) const; + +private: + nsISupports* MOZ_OWNING_REF mRawPtr; + nsresult* mErrorPtr; +}; + +inline nsQueryInterface +do_QueryInterface(nsISupports* aRawPtr) +{ + return nsQueryInterface(aRawPtr); +} + +inline nsQueryInterfaceWithError +do_QueryInterface(nsISupports* aRawPtr, nsresult* aError) +{ + return nsQueryInterfaceWithError(aRawPtr, aError); +} + +template<class T> +inline void +do_QueryInterface(already_AddRefed<T>&) +{ + // This signature exists solely to _stop_ you from doing the bad thing. + // Saying |do_QueryInterface()| on a pointer that is not otherwise owned by + // someone else is an automatic leak. See bug 8221. +} + +template<class T> +inline void +do_QueryInterface(already_AddRefed<T>&, nsresult*) +{ + // This signature exists solely to _stop_ you from doing the bad thing. + // Saying |do_QueryInterface()| on a pointer that is not otherwise owned by + // someone else is an automatic leak. See bug 8221. +} + + +//////////////////////////////////////////////////////////////////////////// +// Using servicemanager with COMPtrs +class nsGetServiceByCID final +{ +public: + explicit nsGetServiceByCID(const nsCID& aCID) : mCID(aCID) {} + + nsresult NS_FASTCALL operator()(const nsIID&, void**) const; + +private: + const nsCID& mCID; +}; + +class nsGetServiceByCIDWithError final +{ +public: + nsGetServiceByCIDWithError(const nsCID& aCID, nsresult* aErrorPtr) + : mCID(aCID) + , mErrorPtr(aErrorPtr) + { + } + + nsresult NS_FASTCALL operator()(const nsIID&, void**) const; + +private: + const nsCID& mCID; + nsresult* mErrorPtr; +}; + +class nsGetServiceByContractID final +{ +public: + explicit nsGetServiceByContractID(const char* aContractID) + : mContractID(aContractID) + { + } + + nsresult NS_FASTCALL operator()(const nsIID&, void**) const; + +private: + const char* mContractID; +}; + +class nsGetServiceByContractIDWithError final +{ +public: + nsGetServiceByContractIDWithError(const char* aContractID, nsresult* aErrorPtr) + : mContractID(aContractID) + , mErrorPtr(aErrorPtr) + { + } + + nsresult NS_FASTCALL operator()(const nsIID&, void**) const; + +private: + const char* mContractID; + nsresult* mErrorPtr; +}; + +/** + * Factors implementation for all template versions of nsCOMPtr. + * + * Here's the way people normally do things like this: + * + * template<class T> class Foo { ... }; + * template<> class Foo<void*> { ... }; + * template<class T> class Foo<T*> : private Foo<void*> { ... }; + */ +class nsCOMPtr_base +{ +public: + explicit nsCOMPtr_base(nsISupports* aRawPtr = nullptr) : mRawPtr(aRawPtr) {} + + NS_CONSTRUCTOR_FASTCALL ~nsCOMPtr_base() + { + NSCAP_LOG_RELEASE(this, mRawPtr); + if (mRawPtr) { + NSCAP_RELEASE(this, mRawPtr); + } + } + + void NS_FASTCALL + assign_with_AddRef(nsISupports*); + void NS_FASTCALL + assign_from_qi(const nsQueryInterface, const nsIID&); + void NS_FASTCALL + assign_from_qi_with_error(const nsQueryInterfaceWithError&, const nsIID&); + void NS_FASTCALL + assign_from_gs_cid(const nsGetServiceByCID, const nsIID&); + void NS_FASTCALL + assign_from_gs_cid_with_error(const nsGetServiceByCIDWithError&, const nsIID&); + void NS_FASTCALL + assign_from_gs_contractid(const nsGetServiceByContractID, const nsIID&); + void NS_FASTCALL + assign_from_gs_contractid_with_error(const nsGetServiceByContractIDWithError&, + const nsIID&); + void NS_FASTCALL + assign_from_helper(const nsCOMPtr_helper&, const nsIID&); + void** NS_FASTCALL + begin_assignment(); + +protected: + NS_MAY_ALIAS_PTR(nsISupports) MOZ_OWNING_REF mRawPtr; + + void assign_assuming_AddRef(nsISupports* aNewPtr) + { + // |AddRef()|ing the new value (before entering this function) before + // |Release()|ing the old lets us safely ignore the self-assignment case. + // We must, however, be careful only to |Release()| _after_ doing the + // assignment, in case the |Release()| leads to our _own_ destruction, + // which would, in turn, cause an incorrect second |Release()| of our old + // pointer. Thank <waterson@netscape.com> for discovering this. + nsISupports* oldPtr = mRawPtr; + mRawPtr = aNewPtr; + NSCAP_LOG_ASSIGNMENT(this, aNewPtr); + NSCAP_LOG_RELEASE(this, oldPtr); + if (oldPtr) { + NSCAP_RELEASE(this, oldPtr); + } + } +}; + +// template<class T> class nsGetterAddRefs; + +// Helper for assert_validity method +template<class T> +char (&TestForIID(decltype(&NS_GET_TEMPLATE_IID(T))))[2]; +template<class T> +char TestForIID(...); + +template<class T> +class nsCOMPtr final +#ifdef NSCAP_FEATURE_USE_BASE + : private nsCOMPtr_base +#endif +{ + +#ifdef NSCAP_FEATURE_USE_BASE + #define NSCAP_CTOR_BASE(x) nsCOMPtr_base(x) +#else + #define NSCAP_CTOR_BASE(x) mRawPtr(x) + +private: + void assign_with_AddRef(nsISupports*); + void assign_from_qi(const nsQueryInterface, const nsIID&); + void assign_from_qi_with_error(const nsQueryInterfaceWithError&, const nsIID&); + void assign_from_gs_cid(const nsGetServiceByCID, const nsIID&); + void assign_from_gs_cid_with_error(const nsGetServiceByCIDWithError&, + const nsIID&); + void assign_from_gs_contractid(const nsGetServiceByContractID, const nsIID&); + void assign_from_gs_contractid_with_error( + const nsGetServiceByContractIDWithError&, const nsIID&); + void assign_from_helper(const nsCOMPtr_helper&, const nsIID&); + void** begin_assignment(); + + void assign_assuming_AddRef(T* aNewPtr) + { + T* oldPtr = mRawPtr; + mRawPtr = aNewPtr; + NSCAP_LOG_ASSIGNMENT(this, aNewPtr); + NSCAP_LOG_RELEASE(this, oldPtr); + if (oldPtr) { + NSCAP_RELEASE(this, oldPtr); + } + } + +private: + T* MOZ_OWNING_REF mRawPtr; +#endif + + void assert_validity() + { + static_assert(1 < sizeof(TestForIID<T>(nullptr)), "nsCOMPtr only works " + "for types with IIDs. Either use RefPtr; add an IID to " + "your type with NS_DECLARE_STATIC_IID_ACCESSOR/" + "NS_DEFINE_STATIC_IID_ACCESSOR; or make the nsCOMPtr point " + "to a base class with an IID."); + } + +public: + typedef T element_type; + +#ifndef NSCAP_FEATURE_USE_BASE + ~nsCOMPtr() + { + NSCAP_LOG_RELEASE(this, mRawPtr); + if (mRawPtr) { + NSCAP_RELEASE(this, mRawPtr); + } + } +#endif + +#ifdef NSCAP_FEATURE_TEST_DONTQUERY_CASES + void Assert_NoQueryNeeded() + { + if (mRawPtr) { + nsCOMPtr<T> query_result(do_QueryInterface(mRawPtr)); + NS_ASSERTION(query_result.get() == mRawPtr, "QueryInterface needed"); + } + } + + #define NSCAP_ASSERT_NO_QUERY_NEEDED() Assert_NoQueryNeeded(); +#else + #define NSCAP_ASSERT_NO_QUERY_NEEDED() +#endif + + + // Constructors + + nsCOMPtr() + : NSCAP_CTOR_BASE(nullptr) + { + assert_validity(); + NSCAP_LOG_ASSIGNMENT(this, nullptr); + } + + MOZ_IMPLICIT nsCOMPtr(decltype(nullptr)) + : NSCAP_CTOR_BASE(nullptr) + { + assert_validity(); + NSCAP_LOG_ASSIGNMENT(this, nullptr); + } + + nsCOMPtr(const nsCOMPtr<T>& aSmartPtr) + : NSCAP_CTOR_BASE(aSmartPtr.mRawPtr) + { + assert_validity(); + if (mRawPtr) { + NSCAP_ADDREF(this, mRawPtr); + } + NSCAP_LOG_ASSIGNMENT(this, aSmartPtr.mRawPtr); + } + + nsCOMPtr(nsCOMPtr<T>&& aSmartPtr) + : NSCAP_CTOR_BASE(aSmartPtr.mRawPtr) + { + assert_validity(); + aSmartPtr.mRawPtr = nullptr; + NSCAP_LOG_ASSIGNMENT(this, mRawPtr); + NSCAP_ASSERT_NO_QUERY_NEEDED(); + } + + MOZ_IMPLICIT nsCOMPtr(T* aRawPtr) + : NSCAP_CTOR_BASE(aRawPtr) + { + assert_validity(); + if (mRawPtr) { + NSCAP_ADDREF(this, mRawPtr); + } + NSCAP_LOG_ASSIGNMENT(this, aRawPtr); + NSCAP_ASSERT_NO_QUERY_NEEDED(); + } + + MOZ_IMPLICIT nsCOMPtr(already_AddRefed<T>& aSmartPtr) + : NSCAP_CTOR_BASE(aSmartPtr.take()) + { + assert_validity(); + NSCAP_LOG_ASSIGNMENT(this, mRawPtr); + NSCAP_ASSERT_NO_QUERY_NEEDED(); + } + + // Construct from |otherComPtr.forget()|. + MOZ_IMPLICIT nsCOMPtr(already_AddRefed<T>&& aSmartPtr) + : NSCAP_CTOR_BASE(aSmartPtr.take()) + { + assert_validity(); + NSCAP_LOG_ASSIGNMENT(this, mRawPtr); + NSCAP_ASSERT_NO_QUERY_NEEDED(); + } + + // Construct from |already_AddRefed|. + template<typename U> + MOZ_IMPLICIT nsCOMPtr(already_AddRefed<U>& aSmartPtr) + : NSCAP_CTOR_BASE(static_cast<T*>(aSmartPtr.take())) + { + assert_validity(); + // But make sure that U actually inherits from T. + static_assert(mozilla::IsBaseOf<T, U>::value, + "U is not a subclass of T"); + NSCAP_LOG_ASSIGNMENT(this, static_cast<T*>(mRawPtr)); + NSCAP_ASSERT_NO_QUERY_NEEDED(); + } + + // Construct from |otherComPtr.forget()|. + template<typename U> + MOZ_IMPLICIT nsCOMPtr(already_AddRefed<U>&& aSmartPtr) + : NSCAP_CTOR_BASE(static_cast<T*>(aSmartPtr.take())) + { + assert_validity(); + // But make sure that U actually inherits from T. + static_assert(mozilla::IsBaseOf<T, U>::value, + "U is not a subclass of T"); + NSCAP_LOG_ASSIGNMENT(this, static_cast<T*>(mRawPtr)); + NSCAP_ASSERT_NO_QUERY_NEEDED(); + } + + // Construct from |do_QueryInterface(expr)|. + MOZ_IMPLICIT nsCOMPtr(const nsQueryInterface aQI) + : NSCAP_CTOR_BASE(nullptr) + { + assert_validity(); + NSCAP_LOG_ASSIGNMENT(this, nullptr); + assign_from_qi(aQI, NS_GET_TEMPLATE_IID(T)); + } + + // Construct from |do_QueryInterface(expr, &rv)|. + MOZ_IMPLICIT nsCOMPtr(const nsQueryInterfaceWithError& aQI) + : NSCAP_CTOR_BASE(nullptr) + { + assert_validity(); + NSCAP_LOG_ASSIGNMENT(this, nullptr); + assign_from_qi_with_error(aQI, NS_GET_TEMPLATE_IID(T)); + } + + // Construct from |do_GetService(cid_expr)|. + MOZ_IMPLICIT nsCOMPtr(const nsGetServiceByCID aGS) + : NSCAP_CTOR_BASE(nullptr) + { + assert_validity(); + NSCAP_LOG_ASSIGNMENT(this, nullptr); + assign_from_gs_cid(aGS, NS_GET_TEMPLATE_IID(T)); + } + + // Construct from |do_GetService(cid_expr, &rv)|. + MOZ_IMPLICIT nsCOMPtr(const nsGetServiceByCIDWithError& aGS) + : NSCAP_CTOR_BASE(nullptr) + { + assert_validity(); + NSCAP_LOG_ASSIGNMENT(this, nullptr); + assign_from_gs_cid_with_error(aGS, NS_GET_TEMPLATE_IID(T)); + } + + // Construct from |do_GetService(contractid_expr)|. + MOZ_IMPLICIT nsCOMPtr(const nsGetServiceByContractID aGS) + : NSCAP_CTOR_BASE(nullptr) + { + assert_validity(); + NSCAP_LOG_ASSIGNMENT(this, nullptr); + assign_from_gs_contractid(aGS, NS_GET_TEMPLATE_IID(T)); + } + + // Construct from |do_GetService(contractid_expr, &rv)|. + MOZ_IMPLICIT nsCOMPtr(const nsGetServiceByContractIDWithError& aGS) + : NSCAP_CTOR_BASE(nullptr) + { + assert_validity(); + NSCAP_LOG_ASSIGNMENT(this, nullptr); + assign_from_gs_contractid_with_error(aGS, NS_GET_TEMPLATE_IID(T)); + } + + // And finally, anything else we might need to construct from can exploit the + // nsCOMPtr_helper facility. + MOZ_IMPLICIT nsCOMPtr(const nsCOMPtr_helper& aHelper) + : NSCAP_CTOR_BASE(nullptr) + { + assert_validity(); + NSCAP_LOG_ASSIGNMENT(this, nullptr); + assign_from_helper(aHelper, NS_GET_TEMPLATE_IID(T)); + NSCAP_ASSERT_NO_QUERY_NEEDED(); + } + + // Defined in OwningNonNull.h + template<class U> + MOZ_IMPLICIT nsCOMPtr(const mozilla::OwningNonNull<U>& aOther); + + + // Assignment operators + + nsCOMPtr<T>& operator=(const nsCOMPtr<T>& aRhs) + { + assign_with_AddRef(aRhs.mRawPtr); + return *this; + } + + nsCOMPtr<T>& operator=(T* aRhs) + { + assign_with_AddRef(aRhs); + NSCAP_ASSERT_NO_QUERY_NEEDED(); + return *this; + } + + nsCOMPtr<T>& operator=(decltype(nullptr)) + { + assign_assuming_AddRef(nullptr); + return *this; + } + + // Assign from |already_AddRefed|. + template<typename U> + nsCOMPtr<T>& operator=(already_AddRefed<U>& aRhs) + { + // Make sure that U actually inherits from T + static_assert(mozilla::IsBaseOf<T, U>::value, + "U is not a subclass of T"); + assign_assuming_AddRef(static_cast<T*>(aRhs.take())); + NSCAP_ASSERT_NO_QUERY_NEEDED(); + return *this; + } + + // Assign from |otherComPtr.forget()|. + template<typename U> + nsCOMPtr<T>& operator=(already_AddRefed<U>&& aRhs) + { + // Make sure that U actually inherits from T + static_assert(mozilla::IsBaseOf<T, U>::value, + "U is not a subclass of T"); + assign_assuming_AddRef(static_cast<T*>(aRhs.take())); + NSCAP_ASSERT_NO_QUERY_NEEDED(); + return *this; + } + + // Assign from |do_QueryInterface(expr)|. + nsCOMPtr<T>& operator=(const nsQueryInterface aRhs) + { + assign_from_qi(aRhs, NS_GET_TEMPLATE_IID(T)); + return *this; + } + + // Assign from |do_QueryInterface(expr, &rv)|. + nsCOMPtr<T>& operator=(const nsQueryInterfaceWithError& aRhs) + { + assign_from_qi_with_error(aRhs, NS_GET_TEMPLATE_IID(T)); + return *this; + } + + // Assign from |do_GetService(cid_expr)|. + nsCOMPtr<T>& operator=(const nsGetServiceByCID aRhs) + { + assign_from_gs_cid(aRhs, NS_GET_TEMPLATE_IID(T)); + return *this; + } + + // Assign from |do_GetService(cid_expr, &rv)|. + nsCOMPtr<T>& operator=(const nsGetServiceByCIDWithError& aRhs) + { + assign_from_gs_cid_with_error(aRhs, NS_GET_TEMPLATE_IID(T)); + return *this; + } + + // Assign from |do_GetService(contractid_expr)|. + nsCOMPtr<T>& operator=(const nsGetServiceByContractID aRhs) + { + assign_from_gs_contractid(aRhs, NS_GET_TEMPLATE_IID(T)); + return *this; + } + + // Assign from |do_GetService(contractid_expr, &rv)|. + nsCOMPtr<T>& operator=(const nsGetServiceByContractIDWithError& aRhs) + { + assign_from_gs_contractid_with_error(aRhs, NS_GET_TEMPLATE_IID(T)); + return *this; + } + + // And finally, anything else we might need to assign from can exploit the + // nsCOMPtr_helper facility. + nsCOMPtr<T>& operator=(const nsCOMPtr_helper& aRhs) + { + assign_from_helper(aRhs, NS_GET_TEMPLATE_IID(T)); + NSCAP_ASSERT_NO_QUERY_NEEDED(); + return *this; + } + + // Defined in OwningNonNull.h + template<class U> + nsCOMPtr<T>& operator=(const mozilla::OwningNonNull<U>& aOther); + + // Exchange ownership with |aRhs|; can save a pair of refcount operations. + void swap(nsCOMPtr<T>& aRhs) + { +#ifdef NSCAP_FEATURE_USE_BASE + nsISupports* temp = aRhs.mRawPtr; +#else + T* temp = aRhs.mRawPtr; +#endif + NSCAP_LOG_ASSIGNMENT(&aRhs, mRawPtr); + NSCAP_LOG_ASSIGNMENT(this, temp); + NSCAP_LOG_RELEASE(this, mRawPtr); + NSCAP_LOG_RELEASE(&aRhs, temp); + aRhs.mRawPtr = mRawPtr; + mRawPtr = temp; + // |aRhs| maintains the same invariants, so we don't need to |NSCAP_ASSERT_NO_QUERY_NEEDED| + } + + // Exchange ownership with |aRhs|; can save a pair of refcount operations. + void swap(T*& aRhs) + { +#ifdef NSCAP_FEATURE_USE_BASE + nsISupports* temp = aRhs; +#else + T* temp = aRhs; +#endif + NSCAP_LOG_ASSIGNMENT(this, temp); + NSCAP_LOG_RELEASE(this, mRawPtr); + aRhs = reinterpret_cast<T*>(mRawPtr); + mRawPtr = temp; + NSCAP_ASSERT_NO_QUERY_NEEDED(); + } + + + // Other pointer operators + + // Return the value of mRawPtr and null out mRawPtr. Useful for + // already_AddRefed return values. + already_AddRefed<T> forget() + { + T* temp = nullptr; + swap(temp); + return already_AddRefed<T>(temp); + } + + // Set the target of aRhs to the value of mRawPtr and null out mRawPtr. + // Useful to avoid unnecessary AddRef/Release pairs with "out" parameters + // where aRhs bay be a T** or an I** where I is a base class of T. + template<typename I> + void forget(I** aRhs) + { + NS_ASSERTION(aRhs, "Null pointer passed to forget!"); + NSCAP_LOG_RELEASE(this, mRawPtr); + *aRhs = get(); + mRawPtr = nullptr; + } + + // Prefer the implicit conversion provided automatically by + // |operator T*() const|. Use |get()| to resolve ambiguity or to get a + // castable pointer. + T* get() const { return reinterpret_cast<T*>(mRawPtr); } + + // Makes an nsCOMPtr act like its underlying raw pointer type whenever it is + // used in a context where a raw pointer is expected. It is this operator + // that makes an nsCOMPtr substitutable for a raw pointer. + // + // Prefer the implicit use of this operator to calling |get()|, except where + // necessary to resolve ambiguity. + operator T*() const +#ifdef MOZ_HAVE_REF_QUALIFIERS + & +#endif + { return get(); } + +#ifdef MOZ_HAVE_REF_QUALIFIERS + // Don't allow implicit conversion of temporary nsCOMPtr to raw pointer, + // because the refcount might be one and the pointer will immediately become + // invalid. + operator T*() const && = delete; + + // Needed to avoid the deleted operator above + explicit operator bool() const { return !!mRawPtr; } +#endif + + T* operator->() const MOZ_NO_ADDREF_RELEASE_ON_RETURN + { + MOZ_ASSERT(mRawPtr != nullptr, + "You can't dereference a NULL nsCOMPtr with operator->()."); + return get(); + } + + // These are not intended to be used by clients. See |address_of| below. + nsCOMPtr<T>* get_address() { return this; } + const nsCOMPtr<T>* get_address() const { return this; } + +public: + T& operator*() const + { + MOZ_ASSERT(mRawPtr != nullptr, + "You can't dereference a NULL nsCOMPtr with operator*()."); + return *get(); + } + + T** StartAssignment() + { +#ifndef NSCAP_FEATURE_INLINE_STARTASSIGNMENT + return reinterpret_cast<T**>(begin_assignment()); +#else + assign_assuming_AddRef(nullptr); + return reinterpret_cast<T**>(&mRawPtr); +#endif + } +}; + + +/* + * Specializing nsCOMPtr for nsISupports allows us to use nsCOMPtr<nsISupports> + * the same way people use nsISupports* and void*, i.e., as a `catch-all' + * pointing to any valid [XP]COM interface. Otherwise, an nsCOMPtr<nsISupports> + * would only be able to point to the single [XP]COM-correct nsISupports + * instance within an object; extra querying ensues. Clients need to be able to + * pass around arbitrary interface pointers, without hassles, through + * intermediary code that doesn't know the exact type. + */ +template<> +class nsCOMPtr<nsISupports> + : private nsCOMPtr_base +{ +public: + typedef nsISupports element_type; + + // Constructors + + nsCOMPtr() + : nsCOMPtr_base(nullptr) + { + NSCAP_LOG_ASSIGNMENT(this, nullptr); + } + + MOZ_IMPLICIT nsCOMPtr(decltype(nullptr)) + : nsCOMPtr_base(nullptr) + { + NSCAP_LOG_ASSIGNMENT(this, nullptr); + } + + nsCOMPtr(const nsCOMPtr<nsISupports>& aSmartPtr) + : nsCOMPtr_base(aSmartPtr.mRawPtr) + { + if (mRawPtr) { + NSCAP_ADDREF(this, mRawPtr); + } + NSCAP_LOG_ASSIGNMENT(this, aSmartPtr.mRawPtr); + } + + MOZ_IMPLICIT nsCOMPtr(nsISupports* aRawPtr) + : nsCOMPtr_base(aRawPtr) + { + if (mRawPtr) { + NSCAP_ADDREF(this, mRawPtr); + } + NSCAP_LOG_ASSIGNMENT(this, aRawPtr); + } + + // Construct from |already_AddRefed|. + MOZ_IMPLICIT nsCOMPtr(already_AddRefed<nsISupports>& aSmartPtr) + : nsCOMPtr_base(aSmartPtr.take()) + { + NSCAP_LOG_ASSIGNMENT(this, mRawPtr); + } + + // Construct from |otherComPtr.forget()|. + MOZ_IMPLICIT nsCOMPtr(already_AddRefed<nsISupports>&& aSmartPtr) + : nsCOMPtr_base(aSmartPtr.take()) + { + NSCAP_LOG_ASSIGNMENT(this, mRawPtr); + } + + // Construct from |do_QueryInterface(expr)|. + MOZ_IMPLICIT nsCOMPtr(const nsQueryInterface aQI) + : nsCOMPtr_base(nullptr) + { + NSCAP_LOG_ASSIGNMENT(this, nullptr); + assign_from_qi(aQI, NS_GET_IID(nsISupports)); + } + + // Construct from |do_QueryInterface(expr, &rv)|. + MOZ_IMPLICIT nsCOMPtr(const nsQueryInterfaceWithError& aQI) + : nsCOMPtr_base(nullptr) + { + NSCAP_LOG_ASSIGNMENT(this, nullptr); + assign_from_qi_with_error(aQI, NS_GET_IID(nsISupports)); + } + + // Construct from |do_GetService(cid_expr)|. + MOZ_IMPLICIT nsCOMPtr(const nsGetServiceByCID aGS) + : nsCOMPtr_base(nullptr) + { + NSCAP_LOG_ASSIGNMENT(this, nullptr); + assign_from_gs_cid(aGS, NS_GET_IID(nsISupports)); + } + + // Construct from |do_GetService(cid_expr, &rv)|. + MOZ_IMPLICIT nsCOMPtr(const nsGetServiceByCIDWithError& aGS) + : nsCOMPtr_base(nullptr) + { + NSCAP_LOG_ASSIGNMENT(this, nullptr); + assign_from_gs_cid_with_error(aGS, NS_GET_IID(nsISupports)); + } + + // Construct from |do_GetService(contractid_expr)|. + MOZ_IMPLICIT nsCOMPtr(const nsGetServiceByContractID aGS) + : nsCOMPtr_base(nullptr) + { + NSCAP_LOG_ASSIGNMENT(this, nullptr); + assign_from_gs_contractid(aGS, NS_GET_IID(nsISupports)); + } + + // Construct from |do_GetService(contractid_expr, &rv)|. + MOZ_IMPLICIT nsCOMPtr(const nsGetServiceByContractIDWithError& aGS) + : nsCOMPtr_base(nullptr) + { + NSCAP_LOG_ASSIGNMENT(this, nullptr); + assign_from_gs_contractid_with_error(aGS, NS_GET_IID(nsISupports)); + } + + // And finally, anything else we might need to construct from can exploit + // the |nsCOMPtr_helper| facility + MOZ_IMPLICIT nsCOMPtr(const nsCOMPtr_helper& aHelper) + : nsCOMPtr_base(nullptr) + { + NSCAP_LOG_ASSIGNMENT(this, nullptr); + assign_from_helper(aHelper, NS_GET_IID(nsISupports)); + } + + + // Assignment operators + + nsCOMPtr<nsISupports>& operator=(const nsCOMPtr<nsISupports>& aRhs) + { + assign_with_AddRef(aRhs.mRawPtr); + return *this; + } + + nsCOMPtr<nsISupports>& operator=(nsISupports* aRhs) + { + assign_with_AddRef(aRhs); + return *this; + } + + nsCOMPtr<nsISupports>& operator=(decltype(nullptr)) + { + assign_assuming_AddRef(nullptr); + return *this; + } + + // Assign from |already_AddRefed|. + nsCOMPtr<nsISupports>& operator=(already_AddRefed<nsISupports>& aRhs) + { + assign_assuming_AddRef(aRhs.take()); + return *this; + } + + // Assign from |otherComPtr.forget()|. + nsCOMPtr<nsISupports>& operator=(already_AddRefed<nsISupports>&& aRhs) + { + assign_assuming_AddRef(aRhs.take()); + return *this; + } + + // Assign from |do_QueryInterface(expr)|. + nsCOMPtr<nsISupports>& operator=(const nsQueryInterface aRhs) + { + assign_from_qi(aRhs, NS_GET_IID(nsISupports)); + return *this; + } + + // Assign from |do_QueryInterface(expr, &rv)|. + nsCOMPtr<nsISupports>& operator=(const nsQueryInterfaceWithError& aRhs) + { + assign_from_qi_with_error(aRhs, NS_GET_IID(nsISupports)); + return *this; + } + + // Assign from |do_GetService(cid_expr)|. + nsCOMPtr<nsISupports>& operator=(const nsGetServiceByCID aRhs) + { + assign_from_gs_cid(aRhs, NS_GET_IID(nsISupports)); + return *this; + } + + // Assign from |do_GetService(cid_expr, &rv)|. + nsCOMPtr<nsISupports>& operator=(const nsGetServiceByCIDWithError& aRhs) + { + assign_from_gs_cid_with_error(aRhs, NS_GET_IID(nsISupports)); + return *this; + } + + // Assign from |do_GetService(contractid_expr)|. + nsCOMPtr<nsISupports>& operator=(const nsGetServiceByContractID aRhs) + { + assign_from_gs_contractid(aRhs, NS_GET_IID(nsISupports)); + return *this; + } + + // Assign from |do_GetService(contractid_expr, &rv)|. + nsCOMPtr<nsISupports>& operator=(const nsGetServiceByContractIDWithError& aRhs) + { + assign_from_gs_contractid_with_error(aRhs, NS_GET_IID(nsISupports)); + return *this; + } + + // And finally, anything else we might need to assign from can exploit the + // nsCOMPtr_helper facility + nsCOMPtr<nsISupports>& operator=(const nsCOMPtr_helper& aRhs) + { + assign_from_helper(aRhs, NS_GET_IID(nsISupports)); + return *this; + } + + // Exchange ownership with |aRhs|; can save a pair of refcount operations. + void swap(nsCOMPtr<nsISupports>& aRhs) + { + nsISupports* temp = aRhs.mRawPtr; + NSCAP_LOG_ASSIGNMENT(&aRhs, mRawPtr); + NSCAP_LOG_ASSIGNMENT(this, temp); + NSCAP_LOG_RELEASE(this, mRawPtr); + NSCAP_LOG_RELEASE(&aRhs, temp); + aRhs.mRawPtr = mRawPtr; + mRawPtr = temp; + } + + // Exchange ownership with |aRhs|; can save a pair of refcount operations. + void swap(nsISupports*& aRhs) + { + nsISupports* temp = aRhs; + NSCAP_LOG_ASSIGNMENT(this, temp); + NSCAP_LOG_RELEASE(this, mRawPtr); + aRhs = mRawPtr; + mRawPtr = temp; + } + + // Return the value of mRawPtr and null out mRawPtr. Useful for + // already_AddRefed return values. + already_AddRefed<nsISupports> forget() + { + nsISupports* temp = nullptr; + swap(temp); + return already_AddRefed<nsISupports>(temp); + } + + // Set the target of aRhs to the value of mRawPtr and null out mRawPtr. + // Useful to avoid unnecessary AddRef/Release pairs with "out" + // parameters. + void forget(nsISupports** aRhs) + { + NS_ASSERTION(aRhs, "Null pointer passed to forget!"); + *aRhs = nullptr; + swap(*aRhs); + } + + // Other pointer operators + + // Prefer the implicit conversion provided automatically by + // |operator nsISupports*() const|. Use |get()| to resolve ambiguity or to + // get a castable pointer. + nsISupports* get() const { return reinterpret_cast<nsISupports*>(mRawPtr); } + + // Makes an nsCOMPtr act like its underlying raw pointer type whenever it is + // used in a context where a raw pointer is expected. It is this operator + // that makes an nsCOMPtr substitutable for a raw pointer. + // + // Prefer the implicit use of this operator to calling |get()|, except where + // necessary to resolve ambiguity/ + operator nsISupports* () const { return get(); } + + nsISupports* operator->() const MOZ_NO_ADDREF_RELEASE_ON_RETURN + { + MOZ_ASSERT(mRawPtr != nullptr, + "You can't dereference a NULL nsCOMPtr with operator->()."); + return get(); + } + + // These are not intended to be used by clients. See |address_of| below. + nsCOMPtr<nsISupports>* get_address() { return this; } + const nsCOMPtr<nsISupports>* get_address() const { return this; } + +public: + + nsISupports& operator*() const + { + MOZ_ASSERT(mRawPtr != nullptr, + "You can't dereference a NULL nsCOMPtr with operator*()."); + return *get(); + } + + nsISupports** StartAssignment() + { +#ifndef NSCAP_FEATURE_INLINE_STARTASSIGNMENT + return reinterpret_cast<nsISupports**>(begin_assignment()); +#else + assign_assuming_AddRef(nullptr); + return reinterpret_cast<nsISupports**>(&mRawPtr); +#endif + } +}; + +template<typename T> +inline void +ImplCycleCollectionUnlink(nsCOMPtr<T>& aField) +{ + aField = nullptr; +} + +template<typename T> +inline void +ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback& aCallback, + nsCOMPtr<T>& aField, + const char* aName, + uint32_t aFlags = 0) +{ + CycleCollectionNoteChild(aCallback, aField.get(), aName, aFlags); +} + +#ifndef NSCAP_FEATURE_USE_BASE +template<class T> +void +nsCOMPtr<T>::assign_with_AddRef(nsISupports* aRawPtr) +{ + if (aRawPtr) { + NSCAP_ADDREF(this, aRawPtr); + } + assign_assuming_AddRef(reinterpret_cast<T*>(aRawPtr)); +} + +template<class T> +void +nsCOMPtr<T>::assign_from_qi(const nsQueryInterface aQI, const nsIID& aIID) +{ + void* newRawPtr; + if (NS_FAILED(aQI(aIID, &newRawPtr))) { + newRawPtr = nullptr; + } + assign_assuming_AddRef(static_cast<T*>(newRawPtr)); +} + +template<class T> +void +nsCOMPtr<T>::assign_from_qi_with_error(const nsQueryInterfaceWithError& aQI, + const nsIID& aIID) +{ + void* newRawPtr; + if (NS_FAILED(aQI(aIID, &newRawPtr))) { + newRawPtr = nullptr; + } + assign_assuming_AddRef(static_cast<T*>(newRawPtr)); +} + +template<class T> +void +nsCOMPtr<T>::assign_from_gs_cid(const nsGetServiceByCID aGS, const nsIID& aIID) +{ + void* newRawPtr; + if (NS_FAILED(aGS(aIID, &newRawPtr))) { + newRawPtr = nullptr; + } + assign_assuming_AddRef(static_cast<T*>(newRawPtr)); +} + +template<class T> +void +nsCOMPtr<T>::assign_from_gs_cid_with_error(const nsGetServiceByCIDWithError& aGS, + const nsIID& aIID) +{ + void* newRawPtr; + if (NS_FAILED(aGS(aIID, &newRawPtr))) { + newRawPtr = nullptr; + } + assign_assuming_AddRef(static_cast<T*>(newRawPtr)); +} + +template<class T> +void +nsCOMPtr<T>::assign_from_gs_contractid(const nsGetServiceByContractID aGS, + const nsIID& aIID) +{ + void* newRawPtr; + if (NS_FAILED(aGS(aIID, &newRawPtr))) { + newRawPtr = nullptr; + } + assign_assuming_AddRef(static_cast<T*>(newRawPtr)); +} + +template<class T> +void +nsCOMPtr<T>::assign_from_gs_contractid_with_error( + const nsGetServiceByContractIDWithError& aGS, const nsIID& aIID) +{ + void* newRawPtr; + if (NS_FAILED(aGS(aIID, &newRawPtr))) { + newRawPtr = nullptr; + } + assign_assuming_AddRef(static_cast<T*>(newRawPtr)); +} + +template<class T> +void +nsCOMPtr<T>::assign_from_helper(const nsCOMPtr_helper& helper, const nsIID& aIID) +{ + void* newRawPtr; + if (NS_FAILED(helper(aIID, &newRawPtr))) { + newRawPtr = nullptr; + } + assign_assuming_AddRef(static_cast<T*>(newRawPtr)); +} + +template<class T> +void** +nsCOMPtr<T>::begin_assignment() +{ + assign_assuming_AddRef(nullptr); + union + { + T** mT; + void** mVoid; + } result; + result.mT = &mRawPtr; + return result.mVoid; +} +#endif + +template<class T> +inline nsCOMPtr<T>* +address_of(nsCOMPtr<T>& aPtr) +{ + return aPtr.get_address(); +} + +template<class T> +inline const nsCOMPtr<T>* +address_of(const nsCOMPtr<T>& aPtr) +{ + return aPtr.get_address(); +} + +/** + * This class is designed to be used for anonymous temporary objects in the + * argument list of calls that return COM interface pointers, e.g., + * + * nsCOMPtr<IFoo> fooP; + * ...->QueryInterface(iid, getter_AddRefs(fooP)) + * + * DO NOT USE THIS TYPE DIRECTLY IN YOUR CODE. Use |getter_AddRefs()| instead. + * + * When initialized with a |nsCOMPtr|, as in the example above, it returns + * a |void**|, a |T**|, or an |nsISupports**| as needed, that the outer call + * (|QueryInterface| in this case) can fill in. + * + * This type should be a nested class inside |nsCOMPtr<T>|. + */ +template<class T> +class nsGetterAddRefs +{ +public: + explicit nsGetterAddRefs(nsCOMPtr<T>& aSmartPtr) + : mTargetSmartPtr(aSmartPtr) + { + } + +#if defined(NSCAP_FEATURE_TEST_DONTQUERY_CASES) || defined(NSCAP_LOG_EXTERNAL_ASSIGNMENT) + ~nsGetterAddRefs() + { +#ifdef NSCAP_LOG_EXTERNAL_ASSIGNMENT + NSCAP_LOG_ASSIGNMENT(reinterpret_cast<void*>(address_of(mTargetSmartPtr)), + mTargetSmartPtr.get()); +#endif + +#ifdef NSCAP_FEATURE_TEST_DONTQUERY_CASES + mTargetSmartPtr.Assert_NoQueryNeeded(); +#endif + } +#endif + + operator void**() + { + return reinterpret_cast<void**>(mTargetSmartPtr.StartAssignment()); + } + + operator T**() { return mTargetSmartPtr.StartAssignment(); } + T*& operator*() { return *(mTargetSmartPtr.StartAssignment()); } + +private: + nsCOMPtr<T>& mTargetSmartPtr; +}; + + +template<> +class nsGetterAddRefs<nsISupports> +{ +public: + explicit nsGetterAddRefs(nsCOMPtr<nsISupports>& aSmartPtr) + : mTargetSmartPtr(aSmartPtr) + { + } + +#ifdef NSCAP_LOG_EXTERNAL_ASSIGNMENT + ~nsGetterAddRefs() + { + NSCAP_LOG_ASSIGNMENT(reinterpret_cast<void*>(address_of(mTargetSmartPtr)), + mTargetSmartPtr.get()); + } +#endif + + operator void**() + { + return reinterpret_cast<void**>(mTargetSmartPtr.StartAssignment()); + } + + operator nsISupports**() { return mTargetSmartPtr.StartAssignment(); } + nsISupports*& operator*() { return *(mTargetSmartPtr.StartAssignment()); } + +private: + nsCOMPtr<nsISupports>& mTargetSmartPtr; +}; + +template<class T> +inline nsGetterAddRefs<T> +getter_AddRefs(nsCOMPtr<T>& aSmartPtr) +{ + return nsGetterAddRefs<T>(aSmartPtr); +} + +template<class T, class DestinationType> +inline nsresult +CallQueryInterface(T* aSource, nsGetterAddRefs<DestinationType> aDestination) +{ + return CallQueryInterface(aSource, + static_cast<DestinationType**>(aDestination)); +} + + +// Comparing two |nsCOMPtr|s + +template<class T, class U> +inline bool +operator==(const nsCOMPtr<T>& aLhs, const nsCOMPtr<U>& aRhs) +{ + return static_cast<const T*>(aLhs.get()) == static_cast<const U*>(aRhs.get()); +} + + +template<class T, class U> +inline bool +operator!=(const nsCOMPtr<T>& aLhs, const nsCOMPtr<U>& aRhs) +{ + return static_cast<const T*>(aLhs.get()) != static_cast<const U*>(aRhs.get()); +} + + +// Comparing an |nsCOMPtr| to a raw pointer + +template<class T, class U> +inline bool +operator==(const nsCOMPtr<T>& aLhs, const U* aRhs) +{ + return static_cast<const T*>(aLhs.get()) == aRhs; +} + +template<class T, class U> +inline bool +operator==(const U* aLhs, const nsCOMPtr<T>& aRhs) +{ + return aLhs == static_cast<const T*>(aRhs.get()); +} + +template<class T, class U> +inline bool +operator!=(const nsCOMPtr<T>& aLhs, const U* aRhs) +{ + return static_cast<const T*>(aLhs.get()) != aRhs; +} + +template<class T, class U> +inline bool +operator!=(const U* aLhs, const nsCOMPtr<T>& aRhs) +{ + return aLhs != static_cast<const T*>(aRhs.get()); +} + +template<class T, class U> +inline bool +operator==(const nsCOMPtr<T>& aLhs, U* aRhs) +{ + return static_cast<const T*>(aLhs.get()) == const_cast<const U*>(aRhs); +} + +template<class T, class U> +inline bool +operator==(U* aLhs, const nsCOMPtr<T>& aRhs) +{ + return const_cast<const U*>(aLhs) == static_cast<const T*>(aRhs.get()); +} + +template<class T, class U> +inline bool +operator!=(const nsCOMPtr<T>& aLhs, U* aRhs) +{ + return static_cast<const T*>(aLhs.get()) != const_cast<const U*>(aRhs); +} + +template<class T, class U> +inline bool +operator!=(U* aLhs, const nsCOMPtr<T>& aRhs) +{ + return const_cast<const U*>(aLhs) != static_cast<const T*>(aRhs.get()); +} + + + +// Comparing an |nsCOMPtr| to |nullptr| + +template<class T> +inline bool +operator==(const nsCOMPtr<T>& aLhs, decltype(nullptr)) +{ + return aLhs.get() == nullptr; +} + +template<class T> +inline bool +operator==(decltype(nullptr), const nsCOMPtr<T>& aRhs) +{ + return nullptr == aRhs.get(); +} + +template<class T> +inline bool +operator!=(const nsCOMPtr<T>& aLhs, decltype(nullptr)) +{ + return aLhs.get() != nullptr; +} + +template<class T> +inline bool +operator!=(decltype(nullptr), const nsCOMPtr<T>& aRhs) +{ + return nullptr != aRhs.get(); +} + + +// Comparing any two [XP]COM objects for identity + +inline bool +SameCOMIdentity(nsISupports* aLhs, nsISupports* aRhs) +{ + return nsCOMPtr<nsISupports>(do_QueryInterface(aLhs)) == + nsCOMPtr<nsISupports>(do_QueryInterface(aRhs)); +} + + + +template<class SourceType, class DestinationType> +inline nsresult +CallQueryInterface(nsCOMPtr<SourceType>& aSourcePtr, DestinationType** aDestPtr) +{ + return CallQueryInterface(aSourcePtr.get(), aDestPtr); +} + +template <class T> +RefPtr<T>::RefPtr(const nsCOMPtr_helper& aHelper) +{ + void* newRawPtr; + if (NS_FAILED(aHelper(NS_GET_TEMPLATE_IID(T), &newRawPtr))) { + newRawPtr = nullptr; + } + mRawPtr = static_cast<T*>(newRawPtr); +} + +template <class T> +RefPtr<T>& +RefPtr<T>::operator=(const nsCOMPtr_helper& aHelper) +{ + void* newRawPtr; + if (NS_FAILED(aHelper(NS_GET_TEMPLATE_IID(T), &newRawPtr))) { + newRawPtr = nullptr; + } + assign_assuming_AddRef(static_cast<T*>(newRawPtr)); + return *this; +} + + +#endif // !defined(nsCOMPtr_h___) diff --git a/xpcom/glue/nsCRTGlue.cpp b/xpcom/glue/nsCRTGlue.cpp new file mode 100644 index 0000000000..7a9f6db030 --- /dev/null +++ b/xpcom/glue/nsCRTGlue.cpp @@ -0,0 +1,441 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsCRTGlue.h" +#include "nsXPCOM.h" +#include "nsDebug.h" +#include "prtime.h" + +#include <stdlib.h> +#include <string.h> +#include <stdio.h> +#include <stdarg.h> + +#include "mozilla/Sprintf.h" + +#ifdef XP_WIN +#include <io.h> +#include <windows.h> +#include "mozilla/UniquePtr.h" +#endif + +#ifdef ANDROID +#include <android/log.h> +#include <unistd.h> +#endif + +using namespace mozilla; + +const char* +NS_strspnp(const char* aDelims, const char* aStr) +{ + const char* d; + do { + for (d = aDelims; *d != '\0'; ++d) { + if (*aStr == *d) { + ++aStr; + break; + } + } + } while (*d); + + return aStr; +} + +char* +NS_strtok(const char* aDelims, char** aStr) +{ + if (!*aStr) { + return nullptr; + } + + char* ret = (char*)NS_strspnp(aDelims, *aStr); + + if (!*ret) { + *aStr = ret; + return nullptr; + } + + char* i = ret; + do { + for (const char* d = aDelims; *d != '\0'; ++d) { + if (*i == *d) { + *i = '\0'; + *aStr = ++i; + return ret; + } + } + ++i; + } while (*i); + + *aStr = nullptr; + return ret; +} + +uint32_t +NS_strlen(const char16_t* aString) +{ + MOZ_ASSERT(aString); + const char16_t* end; + + for (end = aString; *end; ++end) { + // empty loop + } + + return end - aString; +} + +int +NS_strcmp(const char16_t* aStrA, const char16_t* aStrB) +{ + while (*aStrB) { + int r = *aStrA - *aStrB; + if (r) { + return r; + } + + ++aStrA; + ++aStrB; + } + + return *aStrA != '\0'; +} + +int +NS_strncmp(const char16_t* aStrA, const char16_t* aStrB, size_t aLen) +{ + while (aLen && *aStrB) { + int r = *aStrA - *aStrB; + if (r) { + return r; + } + + ++aStrA; + ++aStrB; + --aLen; + } + + return aLen ? *aStrA != '\0' : *aStrA - *aStrB; +} + +char16_t* +NS_strdup(const char16_t* aString) +{ + uint32_t len = NS_strlen(aString); + return NS_strndup(aString, len); +} + +template<typename CharT> +CharT* +NS_strndup(const CharT* aString, uint32_t aLen) +{ + auto newBuf = (CharT*)NS_Alloc((aLen + 1) * sizeof(CharT)); + if (newBuf) { + memcpy(newBuf, aString, aLen * sizeof(CharT)); + newBuf[aLen] = '\0'; + } + return newBuf; +} + +template char16_t* NS_strndup<char16_t>(const char16_t* aString, uint32_t aLen); +template char* NS_strndup<char>(const char* aString, uint32_t aLen); + +char* +NS_strdup(const char* aString) +{ + uint32_t len = strlen(aString); + char* str = (char*)NS_Alloc(len + 1); + if (str) { + memcpy(str, aString, len); + str[len] = '\0'; + } + return str; +} + +// This table maps uppercase characters to lower case characters; +// characters that are neither upper nor lower case are unaffected. +const unsigned char nsLowerUpperUtils::kUpper2Lower[256] = { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, + 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, + 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, + 64, + + // upper band mapped to lower [A-Z] => [a-z] + 97, 98, 99,100,101,102,103,104,105,106,107,108,109,110,111, + 112,113,114,115,116,117,118,119,120,121,122, + + 91, 92, 93, 94, 95, + 96, 97, 98, 99,100,101,102,103,104,105,106,107,108,109,110,111, + 112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127, + 128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143, + 144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159, + 160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175, + 176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191, + 192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207, + 208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223, + 224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239, + 240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255 +}; + +const unsigned char nsLowerUpperUtils::kLower2Upper[256] = { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, + 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, + 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, + 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, + 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, + 96, + + // lower band mapped to upper [a-z] => [A-Z] + 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, + 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, + + 123,124,125,126,127, + 128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143, + 144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159, + 160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175, + 176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191, + 192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207, + 208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223, + 224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239, + 240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255 +}; + +bool +NS_IsUpper(char aChar) +{ + return aChar != (char)nsLowerUpperUtils::kUpper2Lower[(unsigned char)aChar]; +} + +bool +NS_IsLower(char aChar) +{ + return aChar != (char)nsLowerUpperUtils::kLower2Upper[(unsigned char)aChar]; +} + +bool +NS_IsAscii(char16_t aChar) +{ + return (0x0080 > aChar); +} + +bool +NS_IsAscii(const char16_t* aString) +{ + while (*aString) { + if (0x0080 <= *aString) { + return false; + } + aString++; + } + return true; +} + +bool +NS_IsAscii(const char* aString) +{ + while (*aString) { + if (0x80 & *aString) { + return false; + } + aString++; + } + return true; +} + +bool +NS_IsAscii(const char* aString, uint32_t aLength) +{ + const char* end = aString + aLength; + while (aString < end) { + if (0x80 & *aString) { + return false; + } + ++aString; + } + return true; +} + +bool +NS_IsAsciiAlpha(char16_t aChar) +{ + return (aChar >= 'A' && aChar <= 'Z') || + (aChar >= 'a' && aChar <= 'z'); +} + +bool +NS_IsAsciiWhitespace(char16_t aChar) +{ + return aChar == ' ' || + aChar == '\r' || + aChar == '\n' || + aChar == '\t'; +} + +bool +NS_IsAsciiDigit(char16_t aChar) +{ + return aChar >= '0' && aChar <= '9'; +} + +#ifndef XPCOM_GLUE_AVOID_NSPR + +void +NS_MakeRandomString(char* aBuf, int32_t aBufLen) +{ +#define TABLE_SIZE 36 + static const char table[] = { + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', + 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', + 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', + '4', '5', '6', '7', '8', '9' + }; + + // turn PR_Now() into milliseconds since epoch + // and salt rand with that. + static unsigned int seed = 0; + if (seed == 0) { + double fpTime = double(PR_Now()); + seed = (unsigned int)(fpTime * 1e-6 + 0.5); // use 1e-6, granularity of PR_Now() on the mac is seconds + srand(seed); + } + + int32_t i; + for (i = 0; i < aBufLen; ++i) { + *aBuf++ = table[rand() % TABLE_SIZE]; + } + *aBuf = 0; +} + +#endif + +static StderrCallback sStderrCallback = nullptr; + +void +set_stderr_callback(StderrCallback aCallback) +{ + sStderrCallback = aCallback; +} + +#if defined(ANDROID) && !defined(RELEASE_OR_BETA) +static FILE* sStderrCopy = nullptr; + +void +stderr_to_file(const char* aFmt, va_list aArgs) +{ + vfprintf(sStderrCopy, aFmt, aArgs); +} + +void +copy_stderr_to_file(const char* aFile) +{ + if (sStderrCopy) { + return; + } + size_t buflen = strlen(aFile) + 16; + char* buf = (char*)malloc(buflen); + snprintf(buf, buflen, "%s.%u", aFile, (uint32_t)getpid()); + sStderrCopy = fopen(buf, "w"); + free(buf); + set_stderr_callback(stderr_to_file); +} +#endif + +#ifdef HAVE_VA_COPY +#define VARARGS_ASSIGN(foo, bar) VA_COPY(foo,bar) +#elif defined(HAVE_VA_LIST_AS_ARRAY) +#define VARARGS_ASSIGN(foo, bar) foo[0] = bar[0] +#else +#define VARARGS_ASSIGN(foo, bar) (foo) = (bar) +#endif + +#if defined(XP_WIN) +void +vprintf_stderr(const char* aFmt, va_list aArgs) +{ + if (sStderrCallback) { + va_list argsCpy; + VARARGS_ASSIGN(argsCpy, aArgs); + sStderrCallback(aFmt, aArgs); + va_end(argsCpy); + } + + if (IsDebuggerPresent()) { + int lengthNeeded = _vscprintf(aFmt, aArgs); + if (lengthNeeded) { + lengthNeeded++; + auto buf = MakeUnique<char[]>(lengthNeeded); + if (buf) { + va_list argsCpy; + VARARGS_ASSIGN(argsCpy, aArgs); + vsnprintf(buf.get(), lengthNeeded, aFmt, argsCpy); + buf[lengthNeeded - 1] = '\0'; + va_end(argsCpy); + OutputDebugStringA(buf.get()); + } + } + } + + FILE* fp = _fdopen(_dup(2), "a"); + if (!fp) { + return; + } + + vfprintf(fp, aFmt, aArgs); + + fclose(fp); +} + +#elif defined(ANDROID) +void +vprintf_stderr(const char* aFmt, va_list aArgs) +{ + if (sStderrCallback) { + va_list argsCpy; + VARARGS_ASSIGN(argsCpy, aArgs); + sStderrCallback(aFmt, aArgs); + va_end(argsCpy); + } + + __android_log_vprint(ANDROID_LOG_INFO, "Gecko", aFmt, aArgs); +} +#else +void +vprintf_stderr(const char* aFmt, va_list aArgs) +{ + if (sStderrCallback) { + va_list argsCpy; + VARARGS_ASSIGN(argsCpy, aArgs); + sStderrCallback(aFmt, aArgs); + va_end(argsCpy); + } + + vfprintf(stderr, aFmt, aArgs); +} +#endif + +void +printf_stderr(const char* aFmt, ...) +{ + va_list args; + va_start(args, aFmt); + vprintf_stderr(aFmt, args); + va_end(args); +} + +void +fprintf_stderr(FILE* aFile, const char* aFmt, ...) +{ + va_list args; + va_start(args, aFmt); + if (aFile == stderr) { + vprintf_stderr(aFmt, args); + } else { + vfprintf(aFile, aFmt, args); + } + va_end(args); +} diff --git a/xpcom/glue/nsCRTGlue.h b/xpcom/glue/nsCRTGlue.h new file mode 100644 index 0000000000..8caa1ae272 --- /dev/null +++ b/xpcom/glue/nsCRTGlue.h @@ -0,0 +1,147 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsCRTGlue_h__ +#define nsCRTGlue_h__ + +#include "nscore.h" + +/** + * Scan a string for the first character that is *not* in a set of + * delimiters. If the string is only delimiter characters, the end of the + * string is returned. + * + * @param aDelims The set of delimiters (null-terminated) + * @param aStr The string to search (null-terminated) + */ +const char* NS_strspnp(const char* aDelims, const char* aStr); + +/** + * Tokenize a string. This function is similar to the strtok function in the + * C standard library, but it does not use static variables to maintain state + * and is therefore thread and reentrancy-safe. + * + * Any leading delimiters in str are skipped. Then the string is scanned + * until an additional delimiter or end-of-string is found. The final + * delimiter is set to '\0'. + * + * @param aDelims The set of delimiters. + * @param aStr The string to search. This is an in-out parameter; it is + * reset to the end of the found token + 1, or to the + * end-of-string if there are no more tokens. + * @return The token. If no token is found (the string is only + * delimiter characters), nullptr is returned. + */ +char* NS_strtok(const char* aDelims, char** aStr); + +/** + * "strlen" for char16_t strings + */ +uint32_t NS_strlen(const char16_t* aString); + +/** + * "strcmp" for char16_t strings + */ +int NS_strcmp(const char16_t* aStrA, const char16_t* aStrB); + +/** + * "strncmp" for char16_t strings + */ +int NS_strncmp(const char16_t* aStrA, const char16_t* aStrB, size_t aLen); + +/** + * "strdup" for char16_t strings, uses the NS_Alloc allocator. + */ +char16_t* NS_strdup(const char16_t* aString); + +/** + * "strdup", but using the NS_Alloc allocator. + */ +char* NS_strdup(const char* aString); + +/** + * strndup for char16_t or char strings (normal strndup is not available on + * windows). This function will ensure that the new string is + * null-terminated. Uses the NS_Alloc allocator. + * + * CharT may be either char16_t or char. + */ +template<typename CharT> +CharT* NS_strndup(const CharT* aString, uint32_t aLen); + +// The following case-conversion methods only deal in the ascii repertoire +// A-Z and a-z + +// semi-private data declarations... don't use these directly. +class nsLowerUpperUtils +{ +public: + static const unsigned char kLower2Upper[256]; + static const unsigned char kUpper2Lower[256]; +}; + +inline char +NS_ToUpper(char aChar) +{ + return (char)nsLowerUpperUtils::kLower2Upper[(unsigned char)aChar]; +} + +inline char +NS_ToLower(char aChar) +{ + return (char)nsLowerUpperUtils::kUpper2Lower[(unsigned char)aChar]; +} + +bool NS_IsUpper(char aChar); +bool NS_IsLower(char aChar); + +bool NS_IsAscii(char16_t aChar); +bool NS_IsAscii(const char16_t* aString); +bool NS_IsAsciiAlpha(char16_t aChar); +bool NS_IsAsciiDigit(char16_t aChar); +bool NS_IsAsciiWhitespace(char16_t aChar); +bool NS_IsAscii(const char* aString); +bool NS_IsAscii(const char* aString, uint32_t aLength); + +#ifndef XPCOM_GLUE_AVOID_NSPR +void NS_MakeRandomString(char* aBuf, int32_t aBufLen); +#endif + +#define FF '\f' +#define TAB '\t' + +#define CRSTR "\015" +#define LFSTR "\012" +#define CRLF "\015\012" /* A CR LF equivalent string */ + +// We use the most restrictive filesystem as our default set of illegal filename +// characters. This is currently Windows. +#define OS_FILE_ILLEGAL_CHARACTERS "/:*?\"<>|" +// We also provide a list of all known file path separators for all filesystems. +// This can be used in replacement of FILE_PATH_SEPARATOR when you need to +// identify or replace all known path separators. +#define KNOWN_PATH_SEPARATORS "\\/" + +#if defined(XP_MACOSX) + #define FILE_PATH_SEPARATOR "/" +#elif defined(XP_WIN) + #define FILE_PATH_SEPARATOR "\\" +#elif defined(XP_UNIX) + #define FILE_PATH_SEPARATOR "/" +#else + #error need_to_define_your_file_path_separator_and_maybe_illegal_characters +#endif + +// Not all these control characters are illegal in all OSs, but we don't really +// want them appearing in filenames +#define CONTROL_CHARACTERS "\001\002\003\004\005\006\007" \ + "\010\011\012\013\014\015\016\017" \ + "\020\021\022\023\024\025\026\027" \ + "\030\031\032\033\034\035\036\037" + +#define FILE_ILLEGAL_CHARACTERS CONTROL_CHARACTERS OS_FILE_ILLEGAL_CHARACTERS + +#endif // nsCRTGlue_h__ diff --git a/xpcom/glue/nsCategoryCache.cpp b/xpcom/glue/nsCategoryCache.cpp new file mode 100644 index 0000000000..30501b8e39 --- /dev/null +++ b/xpcom/glue/nsCategoryCache.cpp @@ -0,0 +1,149 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsIObserverService.h" +#include "mozilla/Services.h" +#include "nsISupportsPrimitives.h" +#include "nsIStringEnumerator.h" + +#include "nsXPCOMCID.h" + +#include "nsCategoryCache.h" + +nsCategoryObserver::nsCategoryObserver(const char* aCategory) + : mCategory(aCategory) + , mObserversRemoved(false) +{ + // First, enumerate the currently existing entries + nsCOMPtr<nsICategoryManager> catMan = + do_GetService(NS_CATEGORYMANAGER_CONTRACTID); + if (!catMan) { + return; + } + + nsCOMPtr<nsISimpleEnumerator> enumerator; + nsresult rv = catMan->EnumerateCategory(aCategory, + getter_AddRefs(enumerator)); + if (NS_FAILED(rv)) { + return; + } + + nsCOMPtr<nsIUTF8StringEnumerator> strings = do_QueryInterface(enumerator); + MOZ_ASSERT(strings); + + bool more; + while (NS_SUCCEEDED(strings->HasMore(&more)) && more) { + nsAutoCString entryName; + strings->GetNext(entryName); + + nsCString entryValue; + rv = catMan->GetCategoryEntry(aCategory, + entryName.get(), + getter_Copies(entryValue)); + if (NS_SUCCEEDED(rv)) { + nsCOMPtr<nsISupports> service = do_GetService(entryValue.get()); + if (service) { + mHash.Put(entryName, service); + } + } + } + + // Now, listen for changes + nsCOMPtr<nsIObserverService> serv = + mozilla::services::GetObserverService(); + if (serv) { + serv->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false); + serv->AddObserver(this, NS_XPCOM_CATEGORY_ENTRY_ADDED_OBSERVER_ID, false); + serv->AddObserver(this, NS_XPCOM_CATEGORY_ENTRY_REMOVED_OBSERVER_ID, false); + serv->AddObserver(this, NS_XPCOM_CATEGORY_CLEARED_OBSERVER_ID, false); + } +} + +nsCategoryObserver::~nsCategoryObserver() +{ +} + +NS_IMPL_ISUPPORTS(nsCategoryObserver, nsIObserver) + +void +nsCategoryObserver::ListenerDied() +{ + RemoveObservers(); +} + +void +nsCategoryObserver::RemoveObservers() +{ + if (mObserversRemoved) { + return; + } + + mObserversRemoved = true; + nsCOMPtr<nsIObserverService> obsSvc = + mozilla::services::GetObserverService(); + if (obsSvc) { + obsSvc->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID); + obsSvc->RemoveObserver(this, NS_XPCOM_CATEGORY_ENTRY_ADDED_OBSERVER_ID); + obsSvc->RemoveObserver(this, NS_XPCOM_CATEGORY_ENTRY_REMOVED_OBSERVER_ID); + obsSvc->RemoveObserver(this, NS_XPCOM_CATEGORY_CLEARED_OBSERVER_ID); + } +} + +NS_IMETHODIMP +nsCategoryObserver::Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* aData) +{ + if (strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0) { + mHash.Clear(); + RemoveObservers(); + + return NS_OK; + } + + if (!aData || + !nsDependentString(aData).Equals(NS_ConvertASCIItoUTF16(mCategory))) { + return NS_OK; + } + + nsAutoCString str; + nsCOMPtr<nsISupportsCString> strWrapper(do_QueryInterface(aSubject)); + if (strWrapper) { + strWrapper->GetData(str); + } + + if (strcmp(aTopic, NS_XPCOM_CATEGORY_ENTRY_ADDED_OBSERVER_ID) == 0) { + // We may get an add notification even when we already have an entry. This + // is due to the notification happening asynchronously, so if the entry gets + // added and an nsCategoryObserver gets instantiated before events get + // processed, we'd get the notification for an existing entry. + // Do nothing in that case. + if (mHash.GetWeak(str)) { + return NS_OK; + } + + nsCOMPtr<nsICategoryManager> catMan = + do_GetService(NS_CATEGORYMANAGER_CONTRACTID); + if (!catMan) { + return NS_OK; + } + + nsCString entryValue; + catMan->GetCategoryEntry(mCategory.get(), + str.get(), + getter_Copies(entryValue)); + + nsCOMPtr<nsISupports> service = do_GetService(entryValue.get()); + + if (service) { + mHash.Put(str, service); + } + } else if (strcmp(aTopic, NS_XPCOM_CATEGORY_ENTRY_REMOVED_OBSERVER_ID) == 0) { + mHash.Remove(str); + } else if (strcmp(aTopic, NS_XPCOM_CATEGORY_CLEARED_OBSERVER_ID) == 0) { + mHash.Clear(); + } + return NS_OK; +} diff --git a/xpcom/glue/nsCategoryCache.h b/xpcom/glue/nsCategoryCache.h new file mode 100644 index 0000000000..023aa7a75e --- /dev/null +++ b/xpcom/glue/nsCategoryCache.h @@ -0,0 +1,95 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsCategoryCache_h_ +#define nsCategoryCache_h_ + +#include "mozilla/Attributes.h" + +#include "nsICategoryManager.h" +#include "nsIObserver.h" +#include "nsISimpleEnumerator.h" +#include "nsISupportsPrimitives.h" + +#include "nsServiceManagerUtils.h" + +#include "nsAutoPtr.h" +#include "nsCOMArray.h" +#include "nsInterfaceHashtable.h" + +#include "nsXPCOM.h" + +class nsCategoryObserver final : public nsIObserver +{ + ~nsCategoryObserver(); + +public: + explicit nsCategoryObserver(const char* aCategory); + + void ListenerDied(); + nsInterfaceHashtable<nsCStringHashKey, nsISupports>& GetHash() + { + return mHash; + } + + NS_DECL_ISUPPORTS + NS_DECL_NSIOBSERVER +private: + void RemoveObservers(); + + nsInterfaceHashtable<nsCStringHashKey, nsISupports> mHash; + nsCString mCategory; + bool mObserversRemoved; +}; + +/** + * This is a helper class that caches services that are registered in a certain + * category. The intended usage is that a service stores a variable of type + * nsCategoryCache<nsIFoo> in a member variable, where nsIFoo is the interface + * that these services should implement. The constructor of this class should + * then get the name of the category. + */ +template<class T> +class nsCategoryCache final +{ +public: + explicit nsCategoryCache(const char* aCategory) + : mCategoryName(aCategory) + { + } + ~nsCategoryCache() + { + if (mObserver) { + mObserver->ListenerDied(); + } + } + + void GetEntries(nsCOMArray<T>& aResult) + { + // Lazy initialization, so that services in this category can't + // cause reentrant getService (bug 386376) + if (!mObserver) { + mObserver = new nsCategoryObserver(mCategoryName.get()); + } + + for (auto iter = mObserver->GetHash().Iter(); !iter.Done(); iter.Next()) { + nsISupports* entry = iter.UserData(); + nsCOMPtr<T> service = do_QueryInterface(entry); + if (service) { + aResult.AppendElement(service.forget()); + } + } + } + +private: + // Not to be implemented + nsCategoryCache(const nsCategoryCache<T>&); + + nsCString mCategoryName; + RefPtr<nsCategoryObserver> mObserver; +}; + +#endif diff --git a/xpcom/glue/nsClassHashtable.h b/xpcom/glue/nsClassHashtable.h new file mode 100644 index 0000000000..53ca5676b8 --- /dev/null +++ b/xpcom/glue/nsClassHashtable.h @@ -0,0 +1,140 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsClassHashtable_h__ +#define nsClassHashtable_h__ + +#include "mozilla/Move.h" +#include "nsBaseHashtable.h" +#include "nsHashKeys.h" +#include "nsAutoPtr.h" + +/** + * templated hashtable class maps keys to C++ object pointers. + * See nsBaseHashtable for complete declaration. + * @param KeyClass a wrapper-class for the hashtable key, see nsHashKeys.h + * for a complete specification. + * @param Class the class-type being wrapped + * @see nsInterfaceHashtable, nsClassHashtable + */ +template<class KeyClass, class T> +class nsClassHashtable + : public nsBaseHashtable<KeyClass, nsAutoPtr<T>, T*> +{ +public: + typedef typename KeyClass::KeyType KeyType; + typedef T* UserDataType; + typedef nsBaseHashtable<KeyClass, nsAutoPtr<T>, T*> base_type; + + using base_type::IsEmpty; + + nsClassHashtable() {} + explicit nsClassHashtable(uint32_t aInitLength) + : nsBaseHashtable<KeyClass, nsAutoPtr<T>, T*>(aInitLength) + { + } + + /** + * Looks up aKey in the hash table. If it doesn't exist a new object of + * KeyClass will be created (using the arguments provided) and then returned. + */ + template<typename... Args> + UserDataType LookupOrAdd(KeyType aKey, Args&&... aConstructionArgs); + + /** + * @copydoc nsBaseHashtable::Get + * @param aData if the key doesn't exist, pData will be set to nullptr. + */ + bool Get(KeyType aKey, UserDataType* aData) const; + + /** + * @copydoc nsBaseHashtable::Get + * @returns nullptr if the key is not present. + */ + UserDataType Get(KeyType aKey) const; + + /** + * Remove the entry for the given key from the hashtable and return it in + * aOut. If the key is not in the hashtable, aOut's pointer is set to + * nullptr. + * + * Normally, an entry is deleted when it's removed from an nsClassHashtable, + * but this function transfers ownership of the entry back to the caller + * through aOut -- the entry will be deleted when aOut goes out of scope. + * + * @param aKey the key to get and remove from the hashtable + */ + void RemoveAndForget(KeyType aKey, nsAutoPtr<T>& aOut); +}; + +// +// nsClassHashtable definitions +// + +template<class KeyClass, class T> +template<typename... Args> +T* +nsClassHashtable<KeyClass, T>::LookupOrAdd(KeyType aKey, + Args&&... aConstructionArgs) +{ + typename base_type::EntryType* ent = this->PutEntry(aKey); + if (!ent->mData) { + ent->mData = new T(mozilla::Forward<Args>(aConstructionArgs)...); + } + return ent->mData; +} + +template<class KeyClass, class T> +bool +nsClassHashtable<KeyClass, T>::Get(KeyType aKey, T** aRetVal) const +{ + typename base_type::EntryType* ent = this->GetEntry(aKey); + + if (ent) { + if (aRetVal) { + *aRetVal = ent->mData; + } + + return true; + } + + if (aRetVal) { + *aRetVal = nullptr; + } + + return false; +} + +template<class KeyClass, class T> +T* +nsClassHashtable<KeyClass, T>::Get(KeyType aKey) const +{ + typename base_type::EntryType* ent = this->GetEntry(aKey); + if (!ent) { + return nullptr; + } + + return ent->mData; +} + +template<class KeyClass, class T> +void +nsClassHashtable<KeyClass, T>::RemoveAndForget(KeyType aKey, nsAutoPtr<T>& aOut) +{ + aOut = nullptr; + + typename base_type::EntryType* ent = this->GetEntry(aKey); + if (!ent) { + return; + } + + // Transfer ownership from ent->mData into aOut. + aOut = mozilla::Move(ent->mData); + + this->Remove(aKey); +} + +#endif // nsClassHashtable_h__ diff --git a/xpcom/glue/nsClassInfoImpl.cpp b/xpcom/glue/nsClassInfoImpl.cpp new file mode 100644 index 0000000000..6eb34f9f7c --- /dev/null +++ b/xpcom/glue/nsClassInfoImpl.cpp @@ -0,0 +1,73 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsIClassInfoImpl.h" + +NS_IMETHODIMP_(MozExternalRefCountType) +GenericClassInfo::AddRef() +{ + return 2; +} + +NS_IMETHODIMP_(MozExternalRefCountType) +GenericClassInfo::Release() +{ + return 1; +} + +NS_IMPL_QUERY_INTERFACE(GenericClassInfo, nsIClassInfo) + +NS_IMETHODIMP +GenericClassInfo::GetInterfaces(uint32_t* aCount, nsIID*** aArray) +{ + return mData->getinterfaces(aCount, aArray); +} + +NS_IMETHODIMP +GenericClassInfo::GetScriptableHelper(nsIXPCScriptable** aHelper) +{ + if (mData->getscriptablehelper) { + return mData->getscriptablehelper(aHelper); + } + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +GenericClassInfo::GetContractID(char** aContractID) +{ + NS_ERROR("GetContractID not implemented"); + *aContractID = nullptr; + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +GenericClassInfo::GetClassDescription(char** aDescription) +{ + *aDescription = nullptr; + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +GenericClassInfo::GetClassID(nsCID** aClassID) +{ + NS_ERROR("GetClassID not implemented"); + *aClassID = nullptr; + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +GenericClassInfo::GetFlags(uint32_t* aFlags) +{ + *aFlags = mData->flags; + return NS_OK; +} + +NS_IMETHODIMP +GenericClassInfo::GetClassIDNoAlloc(nsCID* aClassIDNoAlloc) +{ + *aClassIDNoAlloc = mData->cid; + return NS_OK; +} diff --git a/xpcom/glue/nsComponentManagerUtils.cpp b/xpcom/glue/nsComponentManagerUtils.cpp new file mode 100644 index 0000000000..d8a590fa72 --- /dev/null +++ b/xpcom/glue/nsComponentManagerUtils.cpp @@ -0,0 +1,301 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsXPCOM_h__ +#include "nsXPCOM.h" +#endif + +#ifndef nsCOMPtr_h__ +#include "nsCOMPtr.h" +#endif + +#include "nsComponentManagerUtils.h" +#include "nsServiceManagerUtils.h" + +#include "nsIComponentManager.h" + +#ifndef MOZILLA_INTERNAL_API + +nsresult +CallGetService(const nsCID& aCID, const nsIID& aIID, void** aResult) +{ + nsCOMPtr<nsIServiceManager> servMgr; + nsresult status = NS_GetServiceManager(getter_AddRefs(servMgr)); + if (servMgr) { + status = servMgr->GetService(aCID, aIID, aResult); + } + return status; +} + +nsresult +CallGetService(const char* aContractID, const nsIID& aIID, void** aResult) +{ + nsCOMPtr<nsIServiceManager> servMgr; + nsresult status = NS_GetServiceManager(getter_AddRefs(servMgr)); + if (servMgr) { + status = servMgr->GetServiceByContractID(aContractID, aIID, aResult); + } + return status; +} + +#else + +#include "nsComponentManager.h" + +nsresult +CallGetService(const nsCID& aCID, const nsIID& aIID, void** aResult) +{ + nsComponentManagerImpl* compMgr = nsComponentManagerImpl::gComponentManager; + if (NS_WARN_IF(!compMgr)) { + return NS_ERROR_NOT_INITIALIZED; + } + + return compMgr->nsComponentManagerImpl::GetService(aCID, aIID, aResult); +} + +nsresult +CallGetService(const char* aContractID, const nsIID& aIID, void** aResult) +{ + nsComponentManagerImpl* compMgr = nsComponentManagerImpl::gComponentManager; + if (NS_WARN_IF(!compMgr)) { + return NS_ERROR_NOT_INITIALIZED; + } + + return compMgr->nsComponentManagerImpl::GetServiceByContractID(aContractID, + aIID, + aResult); +} + +#endif + +#ifndef MOZILLA_INTERNAL_API + +nsresult +CallCreateInstance(const nsCID& aCID, nsISupports* aDelegate, + const nsIID& aIID, void** aResult) +{ + nsCOMPtr<nsIComponentManager> compMgr; + nsresult status = NS_GetComponentManager(getter_AddRefs(compMgr)); + if (compMgr) { + status = compMgr->CreateInstance(aCID, aDelegate, aIID, aResult); + } + return status; +} + +nsresult +CallCreateInstance(const char* aContractID, nsISupports* aDelegate, + const nsIID& aIID, void** aResult) +{ + nsCOMPtr<nsIComponentManager> compMgr; + nsresult status = NS_GetComponentManager(getter_AddRefs(compMgr)); + if (compMgr) + status = compMgr->CreateInstanceByContractID(aContractID, aDelegate, + aIID, aResult); + return status; +} + +nsresult +CallGetClassObject(const nsCID& aCID, const nsIID& aIID, void** aResult) +{ + nsCOMPtr<nsIComponentManager> compMgr; + nsresult status = NS_GetComponentManager(getter_AddRefs(compMgr)); + if (compMgr) { + status = compMgr->GetClassObject(aCID, aIID, aResult); + } + return status; +} + +nsresult +CallGetClassObject(const char* aContractID, const nsIID& aIID, void** aResult) +{ + nsCOMPtr<nsIComponentManager> compMgr; + nsresult status = NS_GetComponentManager(getter_AddRefs(compMgr)); + if (compMgr) + status = compMgr->GetClassObjectByContractID(aContractID, aIID, + aResult); + return status; +} + +#else + +#include "nsComponentManager.h" + +nsresult +CallCreateInstance(const nsCID& aCID, nsISupports* aDelegate, + const nsIID& aIID, void** aResult) +{ + nsComponentManagerImpl* compMgr = nsComponentManagerImpl::gComponentManager; + if (NS_WARN_IF(!compMgr)) { + return NS_ERROR_NOT_INITIALIZED; + } + + return compMgr->nsComponentManagerImpl::CreateInstance(aCID, aDelegate, aIID, + aResult); +} + +nsresult +CallCreateInstance(const char* aContractID, nsISupports* aDelegate, + const nsIID& aIID, void** aResult) +{ + nsComponentManagerImpl* compMgr = nsComponentManagerImpl::gComponentManager; + if (NS_WARN_IF(!compMgr)) { + return NS_ERROR_NOT_INITIALIZED; + } + + return + compMgr->nsComponentManagerImpl::CreateInstanceByContractID(aContractID, + aDelegate, aIID, + aResult); +} + +nsresult +CallGetClassObject(const nsCID& aCID, const nsIID& aIID, void** aResult) +{ + nsComponentManagerImpl* compMgr = nsComponentManagerImpl::gComponentManager; + if (NS_WARN_IF(!compMgr)) { + return NS_ERROR_NOT_INITIALIZED; + } + + return compMgr->nsComponentManagerImpl::GetClassObject(aCID, aIID, aResult); +} + +nsresult +CallGetClassObject(const char* aContractID, const nsIID& aIID, void** aResult) +{ + nsComponentManagerImpl* compMgr = nsComponentManagerImpl::gComponentManager; + if (NS_WARN_IF(!compMgr)) { + return NS_ERROR_NOT_INITIALIZED; + } + + return + compMgr->nsComponentManagerImpl::GetClassObjectByContractID(aContractID, + aIID, aResult); +} + +#endif + +nsresult +nsCreateInstanceByCID::operator()(const nsIID& aIID, void** aInstancePtr) const +{ + nsresult status = CallCreateInstance(mCID, nullptr, aIID, aInstancePtr); + if (NS_FAILED(status)) { + *aInstancePtr = 0; + } + if (mErrorPtr) { + *mErrorPtr = status; + } + return status; +} + +nsresult +nsCreateInstanceByContractID::operator()(const nsIID& aIID, + void** aInstancePtr) const +{ + nsresult status = CallCreateInstance(mContractID, nullptr, aIID, aInstancePtr); + if (NS_FAILED(status)) { + *aInstancePtr = 0; + } + if (mErrorPtr) { + *mErrorPtr = status; + } + return status; +} + +nsresult +nsCreateInstanceFromFactory::operator()(const nsIID& aIID, + void** aInstancePtr) const +{ + nsresult status = mFactory->CreateInstance(nullptr, aIID, aInstancePtr); + if (NS_FAILED(status)) { + *aInstancePtr = 0; + } + if (mErrorPtr) { + *mErrorPtr = status; + } + return status; +} + + +nsresult +nsGetClassObjectByCID::operator()(const nsIID& aIID, void** aInstancePtr) const +{ + nsresult status = CallGetClassObject(mCID, aIID, aInstancePtr); + if (NS_FAILED(status)) { + *aInstancePtr = 0; + } + if (mErrorPtr) { + *mErrorPtr = status; + } + return status; +} + +nsresult +nsGetClassObjectByContractID::operator()(const nsIID& aIID, + void** aInstancePtr) const +{ + nsresult status = CallGetClassObject(mContractID, aIID, aInstancePtr); + if (NS_FAILED(status)) { + *aInstancePtr = 0; + } + if (mErrorPtr) { + *mErrorPtr = status; + } + return status; +} + + +nsresult +nsGetServiceByCID::operator()(const nsIID& aIID, void** aInstancePtr) const +{ + nsresult status = CallGetService(mCID, aIID, aInstancePtr); + if (NS_FAILED(status)) { + *aInstancePtr = 0; + } + + return status; +} + +nsresult +nsGetServiceByCIDWithError::operator()(const nsIID& aIID, + void** aInstancePtr) const +{ + nsresult status = CallGetService(mCID, aIID, aInstancePtr); + if (NS_FAILED(status)) { + *aInstancePtr = 0; + } + + if (mErrorPtr) { + *mErrorPtr = status; + } + return status; +} + +nsresult +nsGetServiceByContractID::operator()(const nsIID& aIID, + void** aInstancePtr) const +{ + nsresult status = CallGetService(mContractID, aIID, aInstancePtr); + if (NS_FAILED(status)) { + *aInstancePtr = 0; + } + + return status; +} + +nsresult +nsGetServiceByContractIDWithError::operator()(const nsIID& aIID, + void** aInstancePtr) const +{ + nsresult status = CallGetService(mContractID, aIID, aInstancePtr); + if (NS_FAILED(status)) { + *aInstancePtr = 0; + } + + if (mErrorPtr) { + *mErrorPtr = status; + } + return status; +} diff --git a/xpcom/glue/nsComponentManagerUtils.h b/xpcom/glue/nsComponentManagerUtils.h new file mode 100644 index 0000000000..2a7a4fbd7c --- /dev/null +++ b/xpcom/glue/nsComponentManagerUtils.h @@ -0,0 +1,247 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsComponentManagerUtils_h__ +#define nsComponentManagerUtils_h__ + +#include "nscore.h" +#include "nsCOMPtr.h" + +#include "nsIFactory.h" + + +nsresult CallCreateInstance(const nsCID& aClass, nsISupports* aDelegate, + const nsIID& aIID, void** aResult); + +nsresult CallCreateInstance(const char* aContractID, nsISupports* aDelegate, + const nsIID& aIID, void** aResult); + +nsresult CallGetClassObject(const nsCID& aClass, const nsIID& aIID, + void** aResult); + +nsresult CallGetClassObject(const char* aContractID, const nsIID& aIID, + void** aResult); + + +class MOZ_STACK_CLASS nsCreateInstanceByCID final : public nsCOMPtr_helper +{ +public: + nsCreateInstanceByCID(const nsCID& aCID, nsresult* aErrorPtr) + : mCID(aCID) + , mErrorPtr(aErrorPtr) + { + } + + virtual nsresult NS_FASTCALL operator()(const nsIID&, void**) const + override; + +private: + const nsCID& mCID; + nsresult* mErrorPtr; +}; + +class MOZ_STACK_CLASS nsCreateInstanceByContractID final : public nsCOMPtr_helper +{ +public: + nsCreateInstanceByContractID(const char* aContractID, nsresult* aErrorPtr) + : mContractID(aContractID) + , mErrorPtr(aErrorPtr) + { + } + + virtual nsresult NS_FASTCALL operator()(const nsIID&, void**) const override; + +private: + const char* mContractID; + nsresult* mErrorPtr; +}; + +class MOZ_STACK_CLASS nsCreateInstanceFromFactory final : public nsCOMPtr_helper +{ +public: + nsCreateInstanceFromFactory(nsIFactory* aFactory, nsresult* aErrorPtr) + : mFactory(aFactory) + , mErrorPtr(aErrorPtr) + { + } + + virtual nsresult NS_FASTCALL operator()(const nsIID&, void**) const override; + +private: + nsIFactory* MOZ_NON_OWNING_REF mFactory; + nsresult* mErrorPtr; +}; + + +inline const nsCreateInstanceByCID +do_CreateInstance(const nsCID& aCID, nsresult* aError = 0) +{ + return nsCreateInstanceByCID(aCID, aError); +} + +inline const nsCreateInstanceByContractID +do_CreateInstance(const char* aContractID, nsresult* aError = 0) +{ + return nsCreateInstanceByContractID(aContractID, aError); +} + +inline const nsCreateInstanceFromFactory +do_CreateInstance(nsIFactory* aFactory, nsresult* aError = 0) +{ + return nsCreateInstanceFromFactory(aFactory, aError); +} + + +class MOZ_STACK_CLASS nsGetClassObjectByCID final : public nsCOMPtr_helper +{ +public: + nsGetClassObjectByCID(const nsCID& aCID, nsresult* aErrorPtr) + : mCID(aCID) + , mErrorPtr(aErrorPtr) + { + } + + virtual nsresult NS_FASTCALL operator()(const nsIID&, void**) const override; + +private: + const nsCID& mCID; + nsresult* mErrorPtr; +}; + +class MOZ_STACK_CLASS nsGetClassObjectByContractID final : public nsCOMPtr_helper +{ +public: + nsGetClassObjectByContractID(const char* aContractID, nsresult* aErrorPtr) + : mContractID(aContractID) + , mErrorPtr(aErrorPtr) + { + } + + virtual nsresult NS_FASTCALL operator()(const nsIID&, void**) const override; + +private: + const char* mContractID; + nsresult* mErrorPtr; +}; + +/** + * do_GetClassObject can be used to improve performance of callers + * that call |CreateInstance| many times. They can cache the factory + * and call do_CreateInstance or CallCreateInstance with the cached + * factory rather than having the component manager retrieve it every + * time. + */ +inline const nsGetClassObjectByCID +do_GetClassObject(const nsCID& aCID, nsresult* aError = 0) +{ + return nsGetClassObjectByCID(aCID, aError); +} + +inline const nsGetClassObjectByContractID +do_GetClassObject(const char* aContractID, nsresult* aError = 0) +{ + return nsGetClassObjectByContractID(aContractID, aError); +} + +// type-safe shortcuts for calling |CreateInstance| +template<class DestinationType> +inline nsresult +CallCreateInstance(const nsCID& aClass, + nsISupports* aDelegate, + DestinationType** aDestination) +{ + NS_PRECONDITION(aDestination, "null parameter"); + + return CallCreateInstance(aClass, aDelegate, + NS_GET_TEMPLATE_IID(DestinationType), + reinterpret_cast<void**>(aDestination)); +} + +template<class DestinationType> +inline nsresult +CallCreateInstance(const nsCID& aClass, DestinationType** aDestination) +{ + NS_PRECONDITION(aDestination, "null parameter"); + + return CallCreateInstance(aClass, nullptr, + NS_GET_TEMPLATE_IID(DestinationType), + reinterpret_cast<void**>(aDestination)); +} + +template<class DestinationType> +inline nsresult +CallCreateInstance(const char* aContractID, + nsISupports* aDelegate, + DestinationType** aDestination) +{ + NS_PRECONDITION(aContractID, "null parameter"); + NS_PRECONDITION(aDestination, "null parameter"); + + return CallCreateInstance(aContractID, + aDelegate, + NS_GET_TEMPLATE_IID(DestinationType), + reinterpret_cast<void**>(aDestination)); +} + +template<class DestinationType> +inline nsresult +CallCreateInstance(const char* aContractID, DestinationType** aDestination) +{ + NS_PRECONDITION(aContractID, "null parameter"); + NS_PRECONDITION(aDestination, "null parameter"); + + return CallCreateInstance(aContractID, nullptr, + NS_GET_TEMPLATE_IID(DestinationType), + reinterpret_cast<void**>(aDestination)); +} + +template<class DestinationType> +inline nsresult +CallCreateInstance(nsIFactory* aFactory, + nsISupports* aDelegate, + DestinationType** aDestination) +{ + NS_PRECONDITION(aFactory, "null parameter"); + NS_PRECONDITION(aDestination, "null parameter"); + + return aFactory->CreateInstance(aDelegate, + NS_GET_TEMPLATE_IID(DestinationType), + reinterpret_cast<void**>(aDestination)); +} + +template<class DestinationType> +inline nsresult +CallCreateInstance(nsIFactory* aFactory, DestinationType** aDestination) +{ + NS_PRECONDITION(aFactory, "null parameter"); + NS_PRECONDITION(aDestination, "null parameter"); + + return aFactory->CreateInstance(nullptr, + NS_GET_TEMPLATE_IID(DestinationType), + reinterpret_cast<void**>(aDestination)); +} + +template<class DestinationType> +inline nsresult +CallGetClassObject(const nsCID& aClass, DestinationType** aDestination) +{ + NS_PRECONDITION(aDestination, "null parameter"); + + return CallGetClassObject(aClass, NS_GET_TEMPLATE_IID(DestinationType), + reinterpret_cast<void**>(aDestination)); +} + +template<class DestinationType> +inline nsresult +CallGetClassObject(const char* aContractID, DestinationType** aDestination) +{ + NS_PRECONDITION(aDestination, "null parameter"); + + return CallGetClassObject(aContractID, NS_GET_TEMPLATE_IID(DestinationType), + reinterpret_cast<void**>(aDestination)); +} + +#endif /* nsComponentManagerUtils_h__ */ diff --git a/xpcom/glue/nsCycleCollectionNoteChild.h b/xpcom/glue/nsCycleCollectionNoteChild.h new file mode 100644 index 0000000000..5d47caefd0 --- /dev/null +++ b/xpcom/glue/nsCycleCollectionNoteChild.h @@ -0,0 +1,101 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// This header will be included by headers that define refpointer and array classes +// in order to specialize CC helpers such as ImplCycleCollectionTraverse for them. + +#ifndef nsCycleCollectionNoteChild_h__ +#define nsCycleCollectionNoteChild_h__ + +#include "nsCycleCollectionTraversalCallback.h" +#include "mozilla/Likely.h" +#include "mozilla/TypeTraits.h" + +enum +{ + CycleCollectionEdgeNameArrayFlag = 1 +}; + +// Just a helper for appending "[i]". Didn't want to pull in string headers here. +void +CycleCollectionNoteEdgeNameImpl(nsCycleCollectionTraversalCallback& aCallback, + const char* aName, + uint32_t aFlags = 0); + +// Should be inlined so that in the no-debug-info case this is just a simple if(). +MOZ_ALWAYS_INLINE void +CycleCollectionNoteEdgeName(nsCycleCollectionTraversalCallback& aCallback, + const char* aName, + uint32_t aFlags = 0) +{ + if (MOZ_UNLIKELY(aCallback.WantDebugInfo())) { + CycleCollectionNoteEdgeNameImpl(aCallback, aName, aFlags); + } +} + +#define NS_CYCLE_COLLECTION_INNERCLASS \ + cycleCollection + +#define NS_CYCLE_COLLECTION_INNERNAME \ + _cycleCollectorGlobal + +#define NS_CYCLE_COLLECTION_PARTICIPANT(_class) \ + _class::NS_CYCLE_COLLECTION_INNERCLASS::GetParticipant() + +template<typename T> +nsISupports* +ToSupports(T* aPtr, typename T::NS_CYCLE_COLLECTION_INNERCLASS* aDummy = 0) +{ + return T::NS_CYCLE_COLLECTION_INNERCLASS::Upcast(aPtr); +} + +// The default implementation of this class template is empty, because it +// should never be used: see the partial specializations below. +template<typename T, + bool IsXPCOM = mozilla::IsBaseOf<nsISupports, T>::value> +struct CycleCollectionNoteChildImpl +{ +}; + +template<typename T> +struct CycleCollectionNoteChildImpl<T, true> +{ + static void Run(nsCycleCollectionTraversalCallback& aCallback, T* aChild) + { + aCallback.NoteXPCOMChild(ToSupports(aChild)); + } +}; + +template<typename T> +struct CycleCollectionNoteChildImpl<T, false> +{ + static void Run(nsCycleCollectionTraversalCallback& aCallback, T* aChild) + { + aCallback.NoteNativeChild(aChild, NS_CYCLE_COLLECTION_PARTICIPANT(T)); + } +}; + +// We declare CycleCollectionNoteChild in 3-argument and 4-argument variants, +// rather than using default arguments, so that forward declarations work +// regardless of header inclusion order. +template<typename T> +inline void +CycleCollectionNoteChild(nsCycleCollectionTraversalCallback& aCallback, + T* aChild, const char* aName, uint32_t aFlags) +{ + CycleCollectionNoteEdgeName(aCallback, aName, aFlags); + CycleCollectionNoteChildImpl<T>::Run(aCallback, aChild); +} + +template<typename T> +inline void +CycleCollectionNoteChild(nsCycleCollectionTraversalCallback& aCallback, + T* aChild, const char* aName) +{ + CycleCollectionNoteChild(aCallback, aChild, aName, 0); +} + +#endif // nsCycleCollectionNoteChild_h__ diff --git a/xpcom/glue/nsCycleCollectionNoteRootCallback.h b/xpcom/glue/nsCycleCollectionNoteRootCallback.h new file mode 100644 index 0000000000..42c43f301f --- /dev/null +++ b/xpcom/glue/nsCycleCollectionNoteRootCallback.h @@ -0,0 +1,31 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsCycleCollectionNoteRootCallback_h__ +#define nsCycleCollectionNoteRootCallback_h__ + +class nsCycleCollectionParticipant; +class nsISupports; + +class nsCycleCollectionNoteRootCallback +{ +public: + NS_IMETHOD_(void) NoteXPCOMRoot(nsISupports* aRoot) = 0; + NS_IMETHOD_(void) NoteJSRoot(JSObject* aRoot) = 0; + NS_IMETHOD_(void) NoteNativeRoot(void* aRoot, + nsCycleCollectionParticipant* aParticipant) = 0; + + NS_IMETHOD_(void) NoteWeakMapping(JSObject* aMap, JS::GCCellPtr aKey, + JSObject* aKeyDelegate, JS::GCCellPtr aVal) = 0; + + bool WantAllTraces() const { return mWantAllTraces; } +protected: + nsCycleCollectionNoteRootCallback() : mWantAllTraces(false) {} + + bool mWantAllTraces; +}; + +#endif // nsCycleCollectionNoteRootCallback_h__ diff --git a/xpcom/glue/nsCycleCollectionParticipant.cpp b/xpcom/glue/nsCycleCollectionParticipant.cpp new file mode 100644 index 0000000000..973ef2ff59 --- /dev/null +++ b/xpcom/glue/nsCycleCollectionParticipant.cpp @@ -0,0 +1,39 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsCycleCollectionParticipant.h" +#include "nsCOMPtr.h" + +NS_IMETHODIMP_(void) +nsXPCOMCycleCollectionParticipant::Root(void* aPtr) +{ + nsISupports* s = static_cast<nsISupports*>(aPtr); + NS_ADDREF(s); +} + +NS_IMETHODIMP_(void) +nsXPCOMCycleCollectionParticipant::Unroot(void* aPtr) +{ + nsISupports* s = static_cast<nsISupports*>(aPtr); + NS_RELEASE(s); +} + +// We define a default trace function because some participants don't need +// to trace anything, so it is okay for them not to define one. +NS_IMETHODIMP_(void) +nsXPCOMCycleCollectionParticipant::Trace(void* aPtr, const TraceCallbacks& aCb, + void* aClosure) +{ +} + +bool +nsXPCOMCycleCollectionParticipant::CheckForRightISupports(nsISupports* aSupports) +{ + nsISupports* foo; + aSupports->QueryInterface(NS_GET_IID(nsCycleCollectionISupports), + reinterpret_cast<void**>(&foo)); + return aSupports == foo; +} diff --git a/xpcom/glue/nsCycleCollectionParticipant.h b/xpcom/glue/nsCycleCollectionParticipant.h new file mode 100644 index 0000000000..2dfbb6750e --- /dev/null +++ b/xpcom/glue/nsCycleCollectionParticipant.h @@ -0,0 +1,852 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsCycleCollectionParticipant_h__ +#define nsCycleCollectionParticipant_h__ + +#include "mozilla/MacroArgs.h" +#include "mozilla/MacroForEach.h" +#include "nsCycleCollectionNoteChild.h" +#include "js/RootingAPI.h" + +#define NS_XPCOMCYCLECOLLECTIONPARTICIPANT_IID \ +{ \ + 0x9674489b, \ + 0x1f6f, \ + 0x4550, \ + { 0xa7, 0x30, 0xcc, 0xae, 0xdd, 0x10, 0x4c, 0xf9 } \ +} + +/** + * Special IID to get at the base nsISupports for a class. Usually this is the + * canonical nsISupports pointer, but in the case of tearoffs for example it is + * the base nsISupports pointer of the tearoff. This allow the cycle collector + * to have separate nsCycleCollectionParticipant's for tearoffs or aggregated + * classes. + */ +#define NS_CYCLECOLLECTIONISUPPORTS_IID \ +{ \ + 0xc61eac14, \ + 0x5f7a, \ + 0x4481, \ + { 0x96, 0x5e, 0x7e, 0xaa, 0x6e, 0xff, 0xa8, 0x5f } \ +} + +/** + * Just holds the IID so NS_GET_IID works. + */ +class nsCycleCollectionISupports +{ +public: + NS_DECLARE_STATIC_IID_ACCESSOR(NS_CYCLECOLLECTIONISUPPORTS_IID) +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(nsCycleCollectionISupports, + NS_CYCLECOLLECTIONISUPPORTS_IID) + +namespace JS { +template<class T> class Heap; +} /* namespace JS */ + +/* + * A struct defining pure virtual methods which are called when tracing cycle + * collection paticipants. The appropriate method is called depending on the + * type of JS GC thing. + */ +struct TraceCallbacks +{ + virtual void Trace(JS::Heap<JS::Value>* aPtr, const char* aName, + void* aClosure) const = 0; + virtual void Trace(JS::Heap<jsid>* aPtr, const char* aName, + void* aClosure) const = 0; + virtual void Trace(JS::Heap<JSObject*>* aPtr, const char* aName, + void* aClosure) const = 0; + virtual void Trace(JSObject** aPtr, const char* aName, + void* aClosure) const = 0; + virtual void Trace(JS::TenuredHeap<JSObject*>* aPtr, const char* aName, + void* aClosure) const = 0; + virtual void Trace(JS::Heap<JSString*>* aPtr, const char* aName, + void* aClosure) const = 0; + virtual void Trace(JS::Heap<JSScript*>* aPtr, const char* aName, + void* aClosure) const = 0; + virtual void Trace(JS::Heap<JSFunction*>* aPtr, const char* aName, + void* aClosure) const = 0; +}; + +/* + * An implementation of TraceCallbacks that calls a single function for all JS + * GC thing types encountered. Implemented in nsCycleCollectorTraceJSHelpers.cpp. + */ +struct TraceCallbackFunc : public TraceCallbacks +{ + typedef void (*Func)(JS::GCCellPtr aPtr, const char* aName, void* aClosure); + + explicit TraceCallbackFunc(Func aCb) : mCallback(aCb) {} + + virtual void Trace(JS::Heap<JS::Value>* aPtr, const char* aName, + void* aClosure) const override; + virtual void Trace(JS::Heap<jsid>* aPtr, const char* aName, + void* aClosure) const override; + virtual void Trace(JS::Heap<JSObject*>* aPtr, const char* aName, + void* aClosure) const override; + virtual void Trace(JSObject** aPtr, const char* aName, + void* aClosure) const override; + virtual void Trace(JS::TenuredHeap<JSObject*>* aPtr, const char* aName, + void* aClosure) const override; + virtual void Trace(JS::Heap<JSString*>* aPtr, const char* aName, + void* aClosure) const override; + virtual void Trace(JS::Heap<JSScript*>* aPtr, const char* aName, + void* aClosure) const override; + virtual void Trace(JS::Heap<JSFunction*>* aPtr, const char* aName, + void* aClosure) const override; + +private: + Func mCallback; +}; + +/** + * Participant implementation classes + */ +class NS_NO_VTABLE nsCycleCollectionParticipant +{ +public: + constexpr nsCycleCollectionParticipant() : mMightSkip(false) {} + constexpr explicit nsCycleCollectionParticipant(bool aSkip) : mMightSkip(aSkip) {} + + NS_IMETHOD Traverse(void* aPtr, nsCycleCollectionTraversalCallback& aCb) = 0; + + NS_IMETHOD_(void) Root(void* aPtr) = 0; + NS_IMETHOD_(void) Unlink(void* aPtr) = 0; + NS_IMETHOD_(void) Unroot(void* aPtr) = 0; + NS_IMETHOD_(const char*) ClassName() = 0; + + NS_IMETHOD_(void) Trace(void* aPtr, const TraceCallbacks& aCb, + void* aClosure) {} + + // If CanSkip returns true, p is removed from the purple buffer during + // a call to nsCycleCollector_forgetSkippable(). + // Note, calling CanSkip may remove objects from the purple buffer! + // If aRemovingAllowed is true, p can be removed from the purple buffer. + bool CanSkip(void* aPtr, bool aRemovingAllowed) + { + return mMightSkip ? CanSkipReal(aPtr, aRemovingAllowed) : false; + } + + // If CanSkipInCC returns true, p is skipped when selecting roots for the + // cycle collector graph. + // Note, calling CanSkipInCC may remove other objects from the purple buffer! + bool CanSkipInCC(void* aPtr) + { + return mMightSkip ? CanSkipInCCReal(aPtr) : false; + } + + // If CanSkipThis returns true, p is not added to the graph. + // This method is called during cycle collection, so don't + // change the state of any objects! + bool CanSkipThis(void* aPtr) + { + return mMightSkip ? CanSkipThisReal(aPtr) : false; + } + + NS_IMETHOD_(void) DeleteCycleCollectable(void* aPtr) = 0; + +protected: + NS_IMETHOD_(bool) CanSkipReal(void* aPtr, bool aRemovingAllowed) + { + NS_ASSERTION(false, "Forgot to implement CanSkipReal?"); + return false; + } + NS_IMETHOD_(bool) CanSkipInCCReal(void* aPtr) + { + NS_ASSERTION(false, "Forgot to implement CanSkipInCCReal?"); + return false; + } + NS_IMETHOD_(bool) CanSkipThisReal(void* aPtr) + { + NS_ASSERTION(false, "Forgot to implement CanSkipThisReal?"); + return false; + } + +private: + const bool mMightSkip; +}; + +class NS_NO_VTABLE nsScriptObjectTracer : public nsCycleCollectionParticipant +{ +public: + constexpr nsScriptObjectTracer() + : nsCycleCollectionParticipant(false) + { + } + constexpr explicit nsScriptObjectTracer(bool aSkip) + : nsCycleCollectionParticipant(aSkip) + { + } + + NS_IMETHOD_(void) Trace(void* aPtr, const TraceCallbacks& aCb, + void* aClosure) override = 0; + + // Implemented in nsCycleCollectorTraceJSHelpers.cpp. + static void NoteJSChild(JS::GCCellPtr aGCThing, const char* aName, + void* aClosure); +}; + +class NS_NO_VTABLE nsXPCOMCycleCollectionParticipant : public nsScriptObjectTracer +{ +public: + constexpr nsXPCOMCycleCollectionParticipant() + : nsScriptObjectTracer(false) + { + } + constexpr explicit nsXPCOMCycleCollectionParticipant(bool aSkip) + : nsScriptObjectTracer(aSkip) + { + } + + NS_DECLARE_STATIC_IID_ACCESSOR(NS_XPCOMCYCLECOLLECTIONPARTICIPANT_IID) + + NS_IMETHOD_(void) Root(void* aPtr) override; + NS_IMETHOD_(void) Unroot(void* aPtr) override; + + NS_IMETHOD_(void) Trace(void* aPtr, const TraceCallbacks& aCb, + void* aClosure) override; + + static bool CheckForRightISupports(nsISupports* aSupports); +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(nsXPCOMCycleCollectionParticipant, + NS_XPCOMCYCLECOLLECTIONPARTICIPANT_IID) + +/////////////////////////////////////////////////////////////////////////////// +// Helpers for implementing a QI to nsXPCOMCycleCollectionParticipant +/////////////////////////////////////////////////////////////////////////////// + +#define NS_CYCLE_COLLECTION_CLASSNAME(_class) \ + _class::NS_CYCLE_COLLECTION_INNERCLASS + +#define NS_IMPL_QUERY_CYCLE_COLLECTION(_class) \ + if ( aIID.Equals(NS_GET_IID(nsXPCOMCycleCollectionParticipant)) ) { \ + *aInstancePtr = NS_CYCLE_COLLECTION_PARTICIPANT(_class); \ + return NS_OK; \ + } else + +#define NS_IMPL_QUERY_CYCLE_COLLECTION_ISUPPORTS(_class) \ + if ( aIID.Equals(NS_GET_IID(nsCycleCollectionISupports)) ) { \ + *aInstancePtr = NS_CYCLE_COLLECTION_CLASSNAME(_class)::Upcast(this); \ + return NS_OK; \ + } else + +#define NS_INTERFACE_MAP_ENTRY_CYCLE_COLLECTION(_class) \ + NS_IMPL_QUERY_CYCLE_COLLECTION(_class) + +#define NS_INTERFACE_MAP_ENTRY_CYCLE_COLLECTION_ISUPPORTS(_class) \ + NS_IMPL_QUERY_CYCLE_COLLECTION_ISUPPORTS(_class) + +#define NS_INTERFACE_MAP_ENTRIES_CYCLE_COLLECTION(_class) \ + NS_INTERFACE_MAP_ENTRY_CYCLE_COLLECTION(_class) \ + NS_INTERFACE_MAP_ENTRY_CYCLE_COLLECTION_ISUPPORTS(_class) + +#define NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(_class) \ + NS_INTERFACE_MAP_BEGIN(_class) \ + NS_INTERFACE_MAP_ENTRIES_CYCLE_COLLECTION(_class) + +#define NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(_class) \ + NS_INTERFACE_MAP_BEGIN(_class) \ + NS_INTERFACE_MAP_ENTRY_CYCLE_COLLECTION(_class) + +#define NS_INTERFACE_TABLE_TO_MAP_SEGUE_CYCLE_COLLECTION(_class) \ + if (rv == NS_OK) return rv; \ + nsISupports* foundInterface; \ + NS_INTERFACE_MAP_ENTRIES_CYCLE_COLLECTION(_class) + +#define NS_INTERFACE_TABLE_HEAD_CYCLE_COLLECTION_INHERITED(_class) \ + NS_IMETHODIMP _class::QueryInterface(REFNSIID aIID, void** aInstancePtr) \ + { \ + NS_PRECONDITION(aInstancePtr, "null out param"); \ + \ + if ( aIID.Equals(NS_GET_IID(nsXPCOMCycleCollectionParticipant)) ) { \ + *aInstancePtr = NS_CYCLE_COLLECTION_PARTICIPANT(_class); \ + return NS_OK; \ + } \ + nsresult rv; + +#define NS_CYCLE_COLLECTION_UPCAST(obj, clazz) \ + NS_CYCLE_COLLECTION_CLASSNAME(clazz)::Upcast(obj) + +#ifdef DEBUG +#define NS_CHECK_FOR_RIGHT_PARTICIPANT(_ptr) _ptr->CheckForRightParticipant() +#else +#define NS_CHECK_FOR_RIGHT_PARTICIPANT(_ptr) +#endif + +// The default implementation of this class template is empty, because it +// should never be used: see the partial specializations below. +template<typename T, + bool IsXPCOM = mozilla::IsBaseOf<nsISupports, T>::value> +struct DowncastCCParticipantImpl +{ +}; + +// Specialization for XPCOM CC participants +template<typename T> +struct DowncastCCParticipantImpl<T, true> +{ + static T* Run(void* aPtr) + { + nsISupports* s = static_cast<nsISupports*>(aPtr); + MOZ_ASSERT(NS_CYCLE_COLLECTION_CLASSNAME(T)::CheckForRightISupports(s), + "not the nsISupports pointer we expect"); + T* rval = NS_CYCLE_COLLECTION_CLASSNAME(T)::Downcast(s); + NS_CHECK_FOR_RIGHT_PARTICIPANT(rval); + return rval; + } +}; + +// Specialization for native CC participants +template<typename T> +struct DowncastCCParticipantImpl<T, false> +{ + static T* Run(void* aPtr) { return static_cast<T*>(aPtr); } +}; + +template<typename T> +T* +DowncastCCParticipant(void* aPtr) +{ + return DowncastCCParticipantImpl<T>::Run(aPtr); +} + +/////////////////////////////////////////////////////////////////////////////// +// Helpers for implementing CanSkip methods +/////////////////////////////////////////////////////////////////////////////// + +#define NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(_class) \ + NS_IMETHODIMP_(bool) \ + NS_CYCLE_COLLECTION_CLASSNAME(_class)::CanSkipReal(void *p, \ + bool aRemovingAllowed) \ + { \ + _class *tmp = DowncastCCParticipant<_class >(p); + +#define NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_END \ + (void)tmp; \ + return false; \ + } + +#define NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_BEGIN(_class) \ + NS_IMETHODIMP_(bool) \ + NS_CYCLE_COLLECTION_CLASSNAME(_class)::CanSkipInCCReal(void *p) \ + { \ + _class *tmp = DowncastCCParticipant<_class >(p); + +#define NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END \ + (void)tmp; \ + return false; \ + } + +#define NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(_class) \ + NS_IMETHODIMP_(bool) \ + NS_CYCLE_COLLECTION_CLASSNAME(_class)::CanSkipThisReal(void *p) \ + { \ + _class *tmp = DowncastCCParticipant<_class >(p); + +#define NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END \ + (void)tmp; \ + return false; \ + } + +/////////////////////////////////////////////////////////////////////////////// +// Helpers for implementing nsCycleCollectionParticipant::Unlink +// +// You need to use NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED if you want +// the base class Unlink version to be called before your own implementation. +// You can use NS_IMPL_CYCLE_COLLECTION_UNLINK_END_INHERITED if you want the +// base class Unlink to get called after your own implementation. You should +// never use them together. +/////////////////////////////////////////////////////////////////////////////// + +#define NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(_class) \ + NS_IMETHODIMP_(void) \ + NS_CYCLE_COLLECTION_CLASSNAME(_class)::Unlink(void *p) \ + { \ + _class *tmp = DowncastCCParticipant<_class >(p); + +#define NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(_class, _base_class) \ + NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(_class) \ + nsISupports *s = static_cast<nsISupports*>(p); \ + NS_CYCLE_COLLECTION_CLASSNAME(_base_class)::Unlink(s); + +#define NS_IMPL_CYCLE_COLLECTION_UNLINK_HELPER(_field) \ + ImplCycleCollectionUnlink(tmp->_field); + +#define NS_IMPL_CYCLE_COLLECTION_UNLINK(...) \ + MOZ_STATIC_ASSERT_VALID_ARG_COUNT(__VA_ARGS__); \ + MOZ_FOR_EACH(NS_IMPL_CYCLE_COLLECTION_UNLINK_HELPER, (), (__VA_ARGS__)) + +#define NS_IMPL_CYCLE_COLLECTION_UNLINK_END \ + (void)tmp; \ + } + +#define NS_IMPL_CYCLE_COLLECTION_UNLINK_END_INHERITED(_base_class) \ + nsISupports *s = static_cast<nsISupports*>(p); \ + NS_CYCLE_COLLECTION_CLASSNAME(_base_class)::Unlink(s); \ + (void)tmp; \ + } + +#define NS_IMPL_CYCLE_COLLECTION_UNLINK_0(_class) \ + NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(_class) \ + NS_IMPL_CYCLE_COLLECTION_UNLINK_END + + +/////////////////////////////////////////////////////////////////////////////// +// Helpers for implementing nsCycleCollectionParticipant::Traverse +/////////////////////////////////////////////////////////////////////////////// + +#define NS_IMPL_CYCLE_COLLECTION_DESCRIBE(_class, _refcnt) \ + cb.DescribeRefCountedNode(_refcnt, #_class); + +#define NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INTERNAL(_class) \ + NS_IMETHODIMP \ + NS_CYCLE_COLLECTION_CLASSNAME(_class)::Traverse \ + (void *p, nsCycleCollectionTraversalCallback &cb) \ + { \ + _class *tmp = DowncastCCParticipant<_class >(p); + +#define NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(_class) \ + NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INTERNAL(_class) \ + NS_IMPL_CYCLE_COLLECTION_DESCRIBE(_class, tmp->mRefCnt.get()) + +// Base class' CC participant should return NS_SUCCESS_INTERRUPTED_TRAVERSE +// from Traverse if it wants derived classes to not traverse anything from +// their CC participant. + +#define NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(_class, _base_class) \ + NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INTERNAL(_class) \ + nsISupports *s = static_cast<nsISupports*>(p); \ + if (NS_CYCLE_COLLECTION_CLASSNAME(_base_class)::Traverse(s, cb) \ + == NS_SUCCESS_INTERRUPTED_TRAVERSE) { \ + return NS_SUCCESS_INTERRUPTED_TRAVERSE; \ + } + +#define NS_IMPL_CYCLE_COLLECTION_TRAVERSE_HELPER(_field) \ + ImplCycleCollectionTraverse(cb, tmp->_field, #_field, 0); + +#define NS_IMPL_CYCLE_COLLECTION_TRAVERSE(...) \ + MOZ_STATIC_ASSERT_VALID_ARG_COUNT(__VA_ARGS__); \ + MOZ_FOR_EACH(NS_IMPL_CYCLE_COLLECTION_TRAVERSE_HELPER, (), (__VA_ARGS__)) + +#define NS_IMPL_CYCLE_COLLECTION_TRAVERSE_RAWPTR(_field) \ + CycleCollectionNoteChild(cb, tmp->_field, #_field); + +#define NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS \ + { \ + TraceCallbackFunc noteJsChild(&nsScriptObjectTracer::NoteJSChild); \ + Trace(p, noteJsChild, &cb); \ + } + +#define NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END \ + (void)tmp; \ + return NS_OK; \ + } + +/////////////////////////////////////////////////////////////////////////////// +// Helpers for implementing nsScriptObjectTracer::Trace +/////////////////////////////////////////////////////////////////////////////// + +#define NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(_class) \ + void \ + NS_CYCLE_COLLECTION_CLASSNAME(_class)::Trace(void *p, \ + const TraceCallbacks &aCallbacks, \ + void *aClosure) \ + { \ + _class *tmp = DowncastCCParticipant<_class >(p); + +#define NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(_class, _base_class) \ + NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(_class) \ + nsISupports *s = static_cast<nsISupports*>(p); \ + NS_CYCLE_COLLECTION_CLASSNAME(_base_class)::Trace(s, aCallbacks, aClosure); + +#define NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(_field) \ + aCallbacks.Trace(&tmp->_field, #_field, aClosure); + +// NB: The (void)tmp; hack in the TRACE_END macro exists to support +// implementations that don't need to do anything in their Trace method. +// Without this hack, some compilers warn about the unused tmp local. +#define NS_IMPL_CYCLE_COLLECTION_TRACE_END \ + (void)tmp; \ + } + +/////////////////////////////////////////////////////////////////////////////// +// Helpers for implementing a concrete nsCycleCollectionParticipant +/////////////////////////////////////////////////////////////////////////////// + +// If a class defines a participant, then QIing an instance of that class to +// nsXPCOMCycleCollectionParticipant should produce that participant. +#ifdef DEBUG +#define NS_CHECK_FOR_RIGHT_PARTICIPANT_BASE \ + virtual void CheckForRightParticipant() +#define NS_CHECK_FOR_RIGHT_PARTICIPANT_DERIVED \ + virtual void CheckForRightParticipant() override +#define NS_CHECK_FOR_RIGHT_PARTICIPANT_BODY(_class) \ + { \ + nsXPCOMCycleCollectionParticipant *p; \ + CallQueryInterface(this, &p); \ + MOZ_ASSERT(p == &NS_CYCLE_COLLECTION_INNERNAME, \ + #_class " should QI to its own CC participant"); \ + } +#define NS_CHECK_FOR_RIGHT_PARTICIPANT_IMPL(_class) \ + NS_CHECK_FOR_RIGHT_PARTICIPANT_BASE \ + NS_CHECK_FOR_RIGHT_PARTICIPANT_BODY(_class) +#define NS_CHECK_FOR_RIGHT_PARTICIPANT_IMPL_INHERITED(_class) \ + NS_CHECK_FOR_RIGHT_PARTICIPANT_DERIVED \ + NS_CHECK_FOR_RIGHT_PARTICIPANT_BODY(_class) +#else +#define NS_CHECK_FOR_RIGHT_PARTICIPANT_IMPL(_class) +#define NS_CHECK_FOR_RIGHT_PARTICIPANT_IMPL_INHERITED(_class) +#endif + +#define NS_DECL_CYCLE_COLLECTION_CLASS_NAME_METHOD(_class) \ + NS_IMETHOD_(const char*) ClassName() override { return #_class; }; + + +#define NS_DECL_CYCLE_COLLECTION_CLASS_BODY_NO_UNLINK(_class, _base) \ +public: \ + NS_IMETHOD Traverse(void *p, nsCycleCollectionTraversalCallback &cb) \ + override; \ + NS_DECL_CYCLE_COLLECTION_CLASS_NAME_METHOD(_class) \ + NS_IMETHOD_(void) DeleteCycleCollectable(void *p) override \ + { \ + DowncastCCParticipant<_class>(p)->DeleteCycleCollectable(); \ + } \ + static _class* Downcast(nsISupports* s) \ + { \ + return static_cast<_class*>(static_cast<_base*>(s)); \ + } \ + static nsISupports* Upcast(_class *p) \ + { \ + return NS_ISUPPORTS_CAST(_base*, p); \ + } \ + template<typename T> \ + friend nsISupports* \ + ToSupports(T* p, NS_CYCLE_COLLECTION_INNERCLASS* dummy); + +#define NS_DECL_CYCLE_COLLECTION_CLASS_BODY(_class, _base) \ + NS_DECL_CYCLE_COLLECTION_CLASS_BODY_NO_UNLINK(_class, _base) \ + NS_IMETHOD_(void) Unlink(void *p) override; + +#define NS_PARTICIPANT_AS(type, participant) \ + const_cast<type*>(reinterpret_cast<const type*>(participant)) + +#define NS_IMPL_GET_XPCOM_CYCLE_COLLECTION_PARTICIPANT(_class) \ + static constexpr nsXPCOMCycleCollectionParticipant* GetParticipant() \ + { \ + return &_class::NS_CYCLE_COLLECTION_INNERNAME; \ + } + +/** + * We use this macro to force that classes that inherit from a ccable class and + * declare their own participant declare themselves as inherited cc classes. + * To avoid possibly unnecessary vtables we only do this checking in debug + * builds. + */ +#ifdef DEBUG +#define NOT_INHERITED_CANT_OVERRIDE virtual void BaseCycleCollectable() final {} +#else +#define NOT_INHERITED_CANT_OVERRIDE +#endif + +#define NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(_class, _base) \ +class NS_CYCLE_COLLECTION_INNERCLASS \ + : public nsXPCOMCycleCollectionParticipant \ +{ \ + NS_DECL_CYCLE_COLLECTION_CLASS_BODY(_class, _base) \ + NS_IMPL_GET_XPCOM_CYCLE_COLLECTION_PARTICIPANT(_class) \ +}; \ +NS_CHECK_FOR_RIGHT_PARTICIPANT_IMPL(_class) \ +static NS_CYCLE_COLLECTION_INNERCLASS NS_CYCLE_COLLECTION_INNERNAME; \ + NOT_INHERITED_CANT_OVERRIDE + +#define NS_DECL_CYCLE_COLLECTION_CLASS(_class) \ + NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(_class, _class) + +// Cycle collector helper for ambiguous classes that can sometimes be skipped. +#define NS_DECL_CYCLE_COLLECTION_SKIPPABLE_CLASS_AMBIGUOUS(_class, _base) \ +class NS_CYCLE_COLLECTION_INNERCLASS \ + : public nsXPCOMCycleCollectionParticipant \ +{ \ +public: \ + constexpr NS_CYCLE_COLLECTION_INNERCLASS () \ + : nsXPCOMCycleCollectionParticipant(true) {} \ +private: \ + NS_DECL_CYCLE_COLLECTION_CLASS_BODY(_class, _base) \ + NS_IMETHOD_(bool) CanSkipReal(void *p, bool aRemovingAllowed) override; \ + NS_IMETHOD_(bool) CanSkipInCCReal(void *p) override; \ + NS_IMETHOD_(bool) CanSkipThisReal(void *p) override; \ + NS_IMPL_GET_XPCOM_CYCLE_COLLECTION_PARTICIPANT(_class) \ +}; \ +NS_CHECK_FOR_RIGHT_PARTICIPANT_IMPL(_class) \ +static NS_CYCLE_COLLECTION_INNERCLASS NS_CYCLE_COLLECTION_INNERNAME; \ +NOT_INHERITED_CANT_OVERRIDE + +#define NS_DECL_CYCLE_COLLECTION_SKIPPABLE_CLASS(_class) \ + NS_DECL_CYCLE_COLLECTION_SKIPPABLE_CLASS_AMBIGUOUS(_class, _class) + +#define NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_AMBIGUOUS(_class, _base) \ +class NS_CYCLE_COLLECTION_INNERCLASS \ + : public nsXPCOMCycleCollectionParticipant \ +{ \ + NS_DECL_CYCLE_COLLECTION_CLASS_BODY(_class, _base) \ + NS_IMETHOD_(void) Trace(void *p, const TraceCallbacks &cb, void *closure) override; \ + NS_IMPL_GET_XPCOM_CYCLE_COLLECTION_PARTICIPANT(_class) \ +}; \ +NS_CHECK_FOR_RIGHT_PARTICIPANT_IMPL(_class) \ +static NS_CYCLE_COLLECTION_INNERCLASS NS_CYCLE_COLLECTION_INNERNAME; \ +NOT_INHERITED_CANT_OVERRIDE + +#define NS_DECL_CYCLE_COLLECTION_SKIPPABLE_SCRIPT_HOLDER_CLASS_AMBIGUOUS(_class, _base) \ +class NS_CYCLE_COLLECTION_INNERCLASS \ + : public nsXPCOMCycleCollectionParticipant \ +{ \ +public: \ + constexpr NS_CYCLE_COLLECTION_INNERCLASS () \ + : nsXPCOMCycleCollectionParticipant(true) {} \ +private: \ + NS_DECL_CYCLE_COLLECTION_CLASS_BODY(_class, _base) \ + NS_IMETHOD_(void) Trace(void *p, const TraceCallbacks &cb, void *closure) override; \ + NS_IMETHOD_(bool) CanSkipReal(void *p, bool aRemovingAllowed) override; \ + NS_IMETHOD_(bool) CanSkipInCCReal(void *p) override; \ + NS_IMETHOD_(bool) CanSkipThisReal(void *p) override; \ + NS_IMPL_GET_XPCOM_CYCLE_COLLECTION_PARTICIPANT(_class) \ +}; \ +NS_CHECK_FOR_RIGHT_PARTICIPANT_IMPL(_class) \ +static NS_CYCLE_COLLECTION_INNERCLASS NS_CYCLE_COLLECTION_INNERNAME; \ +NOT_INHERITED_CANT_OVERRIDE + +#define NS_DECL_CYCLE_COLLECTION_SKIPPABLE_SCRIPT_HOLDER_CLASS(_class) \ + NS_DECL_CYCLE_COLLECTION_SKIPPABLE_SCRIPT_HOLDER_CLASS_AMBIGUOUS(_class, _class) + +#define NS_DECL_CYCLE_COLLECTION_SKIPPABLE_SCRIPT_HOLDER_CLASS_INHERITED(_class, \ + _base_class) \ +class NS_CYCLE_COLLECTION_INNERCLASS \ + : public NS_CYCLE_COLLECTION_CLASSNAME(_base_class) \ +{ \ + NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED_BODY(_class, _base_class) \ + NS_IMETHOD_(void) Trace(void *p, const TraceCallbacks &cb, void *closure) override; \ + NS_IMETHOD_(bool) CanSkipReal(void *p, bool aRemovingAllowed) override; \ + NS_IMETHOD_(bool) CanSkipInCCReal(void *p) override; \ + NS_IMETHOD_(bool) CanSkipThisReal(void *p) override; \ + NS_IMPL_GET_XPCOM_CYCLE_COLLECTION_PARTICIPANT(_class) \ +}; \ +NS_CHECK_FOR_RIGHT_PARTICIPANT_IMPL_INHERITED(_class) \ +static NS_CYCLE_COLLECTION_INNERCLASS NS_CYCLE_COLLECTION_INNERNAME; + +#define NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(_class) \ + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_AMBIGUOUS(_class, _class) + +#define NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED_BODY_NO_UNLINK(_class, \ + _base_class) \ +public: \ + NS_IMETHOD Traverse(void *p, nsCycleCollectionTraversalCallback &cb) \ + override; \ + NS_DECL_CYCLE_COLLECTION_CLASS_NAME_METHOD(_class) \ + static _class* Downcast(nsISupports* s) \ + { \ + return static_cast<_class*>(static_cast<_base_class*>( \ + NS_CYCLE_COLLECTION_CLASSNAME(_base_class)::Downcast(s))); \ + } + +#define NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED_BODY(_class, _base_class) \ + NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED_BODY_NO_UNLINK(_class, _base_class) \ + NS_IMETHOD_(void) Unlink(void *p) override; + +#define NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(_class, _base_class) \ +class NS_CYCLE_COLLECTION_INNERCLASS \ + : public NS_CYCLE_COLLECTION_CLASSNAME(_base_class) \ +{ \ +public: \ + NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED_BODY(_class, _base_class) \ + NS_IMPL_GET_XPCOM_CYCLE_COLLECTION_PARTICIPANT(_class) \ +}; \ +NS_CHECK_FOR_RIGHT_PARTICIPANT_IMPL_INHERITED(_class) \ +static NS_CYCLE_COLLECTION_INNERCLASS NS_CYCLE_COLLECTION_INNERNAME; + +#define NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED_NO_UNLINK(_class, \ + _base_class) \ +class NS_CYCLE_COLLECTION_INNERCLASS \ + : public NS_CYCLE_COLLECTION_CLASSNAME(_base_class) \ +{ \ +public: \ + NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED_BODY_NO_UNLINK(_class, _base_class) \ + NS_IMPL_GET_XPCOM_CYCLE_COLLECTION_PARTICIPANT(_class) \ +}; \ +NS_CHECK_FOR_RIGHT_PARTICIPANT_IMPL_INHERITED(_class) \ +static NS_CYCLE_COLLECTION_INNERCLASS NS_CYCLE_COLLECTION_INNERNAME; + +#define NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(_class, \ + _base_class) \ +class NS_CYCLE_COLLECTION_INNERCLASS \ + : public NS_CYCLE_COLLECTION_CLASSNAME(_base_class) \ +{ \ + NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED_BODY(_class, _base_class) \ + NS_IMETHOD_(void) Trace(void *p, const TraceCallbacks &cb, void *closure) \ + override; \ + NS_IMPL_GET_XPCOM_CYCLE_COLLECTION_PARTICIPANT(_class) \ +}; \ +NS_CHECK_FOR_RIGHT_PARTICIPANT_IMPL_INHERITED(_class) \ +static NS_CYCLE_COLLECTION_INNERCLASS NS_CYCLE_COLLECTION_INNERNAME; + +// Cycle collector participant declarations. + +#define NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS_BODY(_class) \ + public: \ + NS_IMETHOD_(void) Root(void *n) override; \ + NS_IMETHOD_(void) Unlink(void *n) override; \ + NS_IMETHOD_(void) Unroot(void *n) override; \ + NS_IMETHOD Traverse(void *n, nsCycleCollectionTraversalCallback &cb) \ + override; \ + NS_DECL_CYCLE_COLLECTION_CLASS_NAME_METHOD(_class) \ + NS_IMETHOD_(void) DeleteCycleCollectable(void *n) override \ + { \ + DowncastCCParticipant<_class>(n)->DeleteCycleCollectable(); \ + } \ + static _class* Downcast(void* s) \ + { \ + return DowncastCCParticipant<_class>(s); \ + } \ + static void* Upcast(_class *p) \ + { \ + return static_cast<void*>(p); \ + } + +#define NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS(_class) \ + void DeleteCycleCollectable(void) \ + { \ + delete this; \ + } \ + class NS_CYCLE_COLLECTION_INNERCLASS \ + : public nsCycleCollectionParticipant \ + { \ + NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS_BODY(_class) \ + static constexpr nsCycleCollectionParticipant* GetParticipant() \ + { \ + return &_class::NS_CYCLE_COLLECTION_INNERNAME; \ + } \ + }; \ + static NS_CYCLE_COLLECTION_INNERCLASS NS_CYCLE_COLLECTION_INNERNAME; + +#define NS_DECL_CYCLE_COLLECTION_SKIPPABLE_NATIVE_CLASS(_class) \ + void DeleteCycleCollectable(void) \ + { \ + delete this; \ + } \ + class NS_CYCLE_COLLECTION_INNERCLASS \ + : public nsCycleCollectionParticipant \ + { \ + public: \ + constexpr NS_CYCLE_COLLECTION_INNERCLASS () \ + : nsCycleCollectionParticipant(true) {} \ + private: \ + NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS_BODY(_class) \ + NS_IMETHOD_(bool) CanSkipReal(void *p, bool aRemovingAllowed) override; \ + NS_IMETHOD_(bool) CanSkipInCCReal(void *p) override; \ + NS_IMETHOD_(bool) CanSkipThisReal(void *p) override; \ + static nsCycleCollectionParticipant* GetParticipant() \ + { \ + return &_class::NS_CYCLE_COLLECTION_INNERNAME; \ + } \ + }; \ + static NS_CYCLE_COLLECTION_INNERCLASS NS_CYCLE_COLLECTION_INNERNAME; + +#define NS_DECL_CYCLE_COLLECTION_SKIPPABLE_NATIVE_CLASS_WITH_CUSTOM_DELETE(_class) \ +class NS_CYCLE_COLLECTION_INNERCLASS \ + : public nsCycleCollectionParticipant \ +{ \ +public: \ + constexpr NS_CYCLE_COLLECTION_INNERCLASS () \ + : nsCycleCollectionParticipant(true) {} \ +private: \ + NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS_BODY(_class) \ + NS_IMETHOD_(bool) CanSkipReal(void *p, bool aRemovingAllowed) override; \ + NS_IMETHOD_(bool) CanSkipInCCReal(void *p) override; \ + NS_IMETHOD_(bool) CanSkipThisReal(void *p) override; \ + static nsCycleCollectionParticipant* GetParticipant() \ + { \ + return &_class::NS_CYCLE_COLLECTION_INNERNAME; \ + } \ +}; \ +static NS_CYCLE_COLLECTION_INNERCLASS NS_CYCLE_COLLECTION_INNERNAME; + +#define NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(_class) \ + void DeleteCycleCollectable(void) \ + { \ + delete this; \ + } \ + class NS_CYCLE_COLLECTION_INNERCLASS \ + : public nsScriptObjectTracer \ + { \ + NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS_BODY(_class) \ + NS_IMETHOD_(void) Trace(void *p, const TraceCallbacks &cb, void *closure) \ + override; \ + static constexpr nsScriptObjectTracer* GetParticipant() \ + { \ + return &_class::NS_CYCLE_COLLECTION_INNERNAME; \ + } \ + }; \ + static NS_CYCLE_COLLECTION_INNERCLASS NS_CYCLE_COLLECTION_INNERNAME; + +#define NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(_class, _root_function) \ + NS_IMETHODIMP_(void) \ + NS_CYCLE_COLLECTION_CLASSNAME(_class)::Root(void *p) \ + { \ + _class *tmp = static_cast<_class*>(p); \ + tmp->_root_function(); \ + } + +#define NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(_class, _unroot_function) \ + NS_IMETHODIMP_(void) \ + NS_CYCLE_COLLECTION_CLASSNAME(_class)::Unroot(void *p) \ + { \ + _class *tmp = static_cast<_class*>(p); \ + tmp->_unroot_function(); \ + } + +#define NS_IMPL_CYCLE_COLLECTION_CLASS(_class) \ + _class::NS_CYCLE_COLLECTION_INNERCLASS _class::NS_CYCLE_COLLECTION_INNERNAME; + +// NB: This is not something you usually want to use. It is here to allow +// adding things to the CC graph to help debugging via CC logs, but it does not +// traverse or unlink anything, so it is useless for anything else. +#define NS_IMPL_CYCLE_COLLECTION_0(_class) \ + NS_IMPL_CYCLE_COLLECTION_CLASS(_class) \ + NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(_class) \ + NS_IMPL_CYCLE_COLLECTION_UNLINK_END \ + NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(_class) \ + NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +#define NS_IMPL_CYCLE_COLLECTION(_class, ...) \ + NS_IMPL_CYCLE_COLLECTION_CLASS(_class) \ + NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(_class) \ + NS_IMPL_CYCLE_COLLECTION_UNLINK(__VA_ARGS__) \ + NS_IMPL_CYCLE_COLLECTION_UNLINK_END \ + NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(_class) \ + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(__VA_ARGS__) \ + NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +// If you are looking for NS_IMPL_CYCLE_COLLECTION_INHERITED_0(_class, _base) +// you should instead not declare any cycle collected stuff in _class, so it +// will just inherit the CC declarations from _base. + +#define NS_IMPL_CYCLE_COLLECTION_INHERITED(_class, _base, ...) \ + NS_IMPL_CYCLE_COLLECTION_CLASS(_class) \ + NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(_class, _base) \ + NS_IMPL_CYCLE_COLLECTION_UNLINK(__VA_ARGS__) \ + NS_IMPL_CYCLE_COLLECTION_UNLINK_END \ + NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(_class, _base) \ + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(__VA_ARGS__) \ + NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +#define NS_CYCLE_COLLECTION_NOTE_EDGE_NAME CycleCollectionNoteEdgeName + +#endif // nsCycleCollectionParticipant_h__ diff --git a/xpcom/glue/nsCycleCollectionTraversalCallback.h b/xpcom/glue/nsCycleCollectionTraversalCallback.h new file mode 100644 index 0000000000..9e314af9b2 --- /dev/null +++ b/xpcom/glue/nsCycleCollectionTraversalCallback.h @@ -0,0 +1,62 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsCycleCollectionTraversalCallback_h__ +#define nsCycleCollectionTraversalCallback_h__ + +#include "jspubtd.h" +#include "js/HeapAPI.h" +#include "nsISupports.h" + +class nsCycleCollectionParticipant; + +class NS_NO_VTABLE nsCycleCollectionTraversalCallback +{ +public: + // You must call DescribeRefCountedNode() with an accurate + // refcount, otherwise cycle collection will fail, and probably crash. + // If the callback cares about objname, it should put + // WANT_DEBUG_INFO in mFlags. + NS_IMETHOD_(void) DescribeRefCountedNode(nsrefcnt aRefcount, + const char* aObjName) = 0; + // Note, aCompartmentAddress is 0 if it is unknown. + NS_IMETHOD_(void) DescribeGCedNode(bool aIsMarked, + const char* aObjName, + uint64_t aCompartmentAddress = 0) = 0; + + NS_IMETHOD_(void) NoteXPCOMChild(nsISupports* aChild) = 0; + NS_IMETHOD_(void) NoteJSChild(const JS::GCCellPtr& aThing) = 0; + NS_IMETHOD_(void) NoteNativeChild(void* aChild, + nsCycleCollectionParticipant* aHelper) = 0; + + // Give a name to the edge associated with the next call to + // NoteXPCOMChild, NoteJSObject, NoteJSScript, or NoteNativeChild. + // Callbacks who care about this should set WANT_DEBUG_INFO in the + // flags. + NS_IMETHOD_(void) NoteNextEdgeName(const char* aName) = 0; + + enum + { + // Values for flags: + + // Caller should call NoteNextEdgeName and pass useful objName + // to DescribeRefCountedNode and DescribeGCedNode. + WANT_DEBUG_INFO = (1 << 0), + + // Caller should not skip objects that we know will be + // uncollectable. + WANT_ALL_TRACES = (1 << 1) + }; + uint32_t Flags() const { return mFlags; } + bool WantDebugInfo() const { return (mFlags & WANT_DEBUG_INFO) != 0; } + bool WantAllTraces() const { return (mFlags & WANT_ALL_TRACES) != 0; } +protected: + nsCycleCollectionTraversalCallback() : mFlags(0) {} + + uint32_t mFlags; +}; + +#endif // nsCycleCollectionTraversalCallback_h__ diff --git a/xpcom/glue/nsDataHashtable.h b/xpcom/glue/nsDataHashtable.h new file mode 100644 index 0000000000..19c0728b4a --- /dev/null +++ b/xpcom/glue/nsDataHashtable.h @@ -0,0 +1,58 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsDataHashtable_h__ +#define nsDataHashtable_h__ + +#include "nsHashKeys.h" +#include "nsBaseHashtable.h" +#include "mozilla/Maybe.h" + +/** + * templated hashtable class maps keys to simple datatypes. + * See nsBaseHashtable for complete declaration + * @param KeyClass a wrapper-class for the hashtable key, see nsHashKeys.h + * for a complete specification. + * @param DataType the simple datatype being wrapped + * @see nsInterfaceHashtable, nsClassHashtable + */ +template<class KeyClass, class DataType> +class nsDataHashtable + : public nsBaseHashtable<KeyClass, DataType, DataType> +{ +private: + typedef nsBaseHashtable<KeyClass, DataType, DataType> BaseClass; + +public: + using typename BaseClass::KeyType; + using typename BaseClass::EntryType; + + nsDataHashtable() {} + explicit nsDataHashtable(uint32_t aInitLength) + : BaseClass(aInitLength) + { + } + + /** + * Retrieve the value for a key and remove the corresponding entry at + * the same time. + * + * @param aKey the key to retrieve and remove + * @return the found value, or Nothing if no entry was found with the + * given key. + */ + mozilla::Maybe<DataType> GetAndRemove(KeyType aKey) + { + mozilla::Maybe<DataType> value; + if (EntryType* ent = this->GetEntry(aKey)) { + value.emplace(mozilla::Move(ent->mData)); + this->RemoveEntry(ent); + } + return value; + } +}; + +#endif // nsDataHashtable_h__ diff --git a/xpcom/glue/nsDebug.h b/xpcom/glue/nsDebug.h new file mode 100644 index 0000000000..7365f9ce34 --- /dev/null +++ b/xpcom/glue/nsDebug.h @@ -0,0 +1,460 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsDebug_h___ +#define nsDebug_h___ + +#include "nscore.h" +#include "nsError.h" + +#include "nsXPCOM.h" +#include "mozilla/Assertions.h" +#include "mozilla/Likely.h" +#include <stdarg.h> + +#ifdef DEBUG +#include "prprf.h" +#endif + +/** + * Warn if the given condition is true. The condition is evaluated in both + * release and debug builds, and the result is an expression which can be + * used in subsequent expressions, such as: + * + * if (NS_WARN_IF(NS_FAILED(rv)) { + * return rv; + * } + * + * This explicit warning and return is preferred to the NS_ENSURE_* macros + * which hide the warning and the return control flow. + * + * This macro can also be used outside of conditions just to issue a warning, + * like so: + * + * Unused << NS_WARN_IF(NS_FAILED(FnWithSideEffects()); + * + * (The |Unused <<| is necessary because of the MOZ_MUST_USE annotation.) + * + * However, note that the argument to this macro is evaluated in all builds. If + * you just want a warning assertion, it is better to use NS_WARNING_ASSERTION + * (which evaluates the condition only in debug builds) like so: + * + * NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "operation failed"); + * + * @note This is C++-only + */ +#ifdef __cplusplus +#ifdef DEBUG +inline MOZ_MUST_USE bool NS_warn_if_impl(bool aCondition, const char* aExpr, + const char* aFile, int32_t aLine) +{ + if (MOZ_UNLIKELY(aCondition)) { + NS_DebugBreak(NS_DEBUG_WARNING, nullptr, aExpr, aFile, aLine); + } + return aCondition; +} +#define NS_WARN_IF(condition) \ + NS_warn_if_impl(condition, #condition, __FILE__, __LINE__) +#else +#define NS_WARN_IF(condition) (bool)(condition) +#endif +#endif + +/** + * Test an assertion for truth. If the expression is not true then + * emit a warning. + * + * Program execution continues past the usage of this macro. + * + * Note also that the non-debug version of this macro does <b>not</b> + * evaluate the message argument. + */ +#ifdef DEBUG +#define NS_WARNING_ASSERTION(_expr, _msg) \ + do { \ + if (!(_expr)) { \ + NS_DebugBreak(NS_DEBUG_WARNING, _msg, #_expr, __FILE__, __LINE__); \ + } \ + } while(0) +#else +#define NS_WARNING_ASSERTION(_expr, _msg) do { /* nothing */ } while(0) +#endif + +/** + * Test an assertion for truth. If the expression is not true then + * trigger a program failure. + * + * Note that the non-debug version of this macro does <b>not</b> + * evaluate the message argument. + */ +#ifdef DEBUG +inline void MOZ_PretendNoReturn() + MOZ_PRETEND_NORETURN_FOR_STATIC_ANALYSIS {} +#define NS_ASSERTION(expr, str) \ + do { \ + if (!(expr)) { \ + NS_DebugBreak(NS_DEBUG_ASSERTION, str, #expr, __FILE__, __LINE__); \ + MOZ_PretendNoReturn(); \ + } \ + } while(0) +#else +#define NS_ASSERTION(expr, str) do { /* nothing */ } while(0) +#endif + +/** + * NS_PRECONDITION/POSTCONDITION are synonyms for NS_ASSERTION. + */ +#define NS_PRECONDITION(expr, str) NS_ASSERTION(expr, str) +#define NS_POSTCONDITION(expr, str) NS_ASSERTION(expr, str) + +/** + * This macros triggers a program failure if executed. It indicates that + * an attempt was made to execute some unimplemented functionality. + */ +#ifdef DEBUG +#define NS_NOTYETIMPLEMENTED(str) \ + do { \ + NS_DebugBreak(NS_DEBUG_ASSERTION, str, "NotYetImplemented", __FILE__, __LINE__); \ + MOZ_PretendNoReturn(); \ + } while(0) +#else +#define NS_NOTYETIMPLEMENTED(str) do { /* nothing */ } while(0) +#endif + +/** + * This macros triggers a program failure if executed. It indicates that + * an attempt was made to execute a codepath which should not be reachable. + */ +#ifdef DEBUG +#define NS_NOTREACHED(str) \ + do { \ + NS_DebugBreak(NS_DEBUG_ASSERTION, str, "Not Reached", __FILE__, __LINE__); \ + MOZ_PretendNoReturn(); \ + } while(0) +#else +#define NS_NOTREACHED(str) do { /* nothing */ } while(0) +#endif + +/** + * Log an error message. + */ +#ifdef DEBUG +#define NS_ERROR(str) \ + do { \ + NS_DebugBreak(NS_DEBUG_ASSERTION, str, "Error", __FILE__, __LINE__); \ + MOZ_PretendNoReturn(); \ + } while(0) +#else +#define NS_ERROR(str) do { /* nothing */ } while(0) +#endif + +/** + * Log a warning message. + */ +#ifdef DEBUG +#define NS_WARNING(str) \ + NS_DebugBreak(NS_DEBUG_WARNING, str, nullptr, __FILE__, __LINE__) +#else +#define NS_WARNING(str) do { /* nothing */ } while(0) +#endif + +/** + * Trigger an debug-only abort. + * + * @see NS_RUNTIMEABORT for release-mode asserts. + */ +#ifdef DEBUG +#define NS_ABORT() \ + do { \ + NS_DebugBreak(NS_DEBUG_ABORT, nullptr, nullptr, __FILE__, __LINE__); \ + MOZ_PretendNoReturn(); \ + } while(0) +#else +#define NS_ABORT() do { /* nothing */ } while(0) +#endif + +/** + * Trigger a debugger breakpoint, only in debug builds. + */ +#ifdef DEBUG +#define NS_BREAK() \ + do { \ + NS_DebugBreak(NS_DEBUG_BREAK, nullptr, nullptr, __FILE__, __LINE__); \ + MOZ_PretendNoReturn(); \ + } while(0) +#else +#define NS_BREAK() do { /* nothing */ } while(0) +#endif + +/****************************************************************************** +** Macros for static assertions. These are used by the sixgill tool. +** When the tool is not running these macros are no-ops. +******************************************************************************/ + +/* Avoid name collision if included with other headers defining annotations. */ +#ifndef HAVE_STATIC_ANNOTATIONS +#define HAVE_STATIC_ANNOTATIONS + +#ifdef XGILL_PLUGIN + +#define STATIC_PRECONDITION(COND) __attribute__((precondition(#COND))) +#define STATIC_PRECONDITION_ASSUME(COND) __attribute__((precondition_assume(#COND))) +#define STATIC_POSTCONDITION(COND) __attribute__((postcondition(#COND))) +#define STATIC_POSTCONDITION_ASSUME(COND) __attribute__((postcondition_assume(#COND))) +#define STATIC_INVARIANT(COND) __attribute__((invariant(#COND))) +#define STATIC_INVARIANT_ASSUME(COND) __attribute__((invariant_assume(#COND))) + +/* Used to make identifiers for assert/assume annotations in a function. */ +#define STATIC_PASTE2(X,Y) X ## Y +#define STATIC_PASTE1(X,Y) STATIC_PASTE2(X,Y) + +#define STATIC_ASSUME(COND) \ + do { \ + __attribute__((assume_static(#COND), unused)) \ + int STATIC_PASTE1(assume_static_, __COUNTER__); \ + } while(0) + +#define STATIC_ASSERT_RUNTIME(COND) \ + do { \ + __attribute__((assert_static_runtime(#COND), unused)) \ + int STATIC_PASTE1(assert_static_runtime_, __COUNTER__); \ + } while(0) + +#else /* XGILL_PLUGIN */ + +#define STATIC_PRECONDITION(COND) /* nothing */ +#define STATIC_PRECONDITION_ASSUME(COND) /* nothing */ +#define STATIC_POSTCONDITION(COND) /* nothing */ +#define STATIC_POSTCONDITION_ASSUME(COND) /* nothing */ +#define STATIC_INVARIANT(COND) /* nothing */ +#define STATIC_INVARIANT_ASSUME(COND) /* nothing */ + +#define STATIC_ASSUME(COND) do { /* nothing */ } while(0) +#define STATIC_ASSERT_RUNTIME(COND) do { /* nothing */ } while(0) + +#endif /* XGILL_PLUGIN */ + +#define STATIC_SKIP_INFERENCE STATIC_INVARIANT(skip_inference()) + +#endif /* HAVE_STATIC_ANNOTATIONS */ + +/****************************************************************************** +** Macros for terminating execution when an unrecoverable condition is +** reached. These need to be compiled regardless of the DEBUG flag. +******************************************************************************/ + +/** + * Terminate execution <i>immediately</i>, and if possible on the current + * platform, in such a way that execution can't be continued by other + * code (e.g., by intercepting a signal). + */ +#define NS_RUNTIMEABORT(msg) \ + NS_DebugBreak(NS_DEBUG_ABORT, msg, nullptr, __FILE__, __LINE__) + + +/* Macros for checking the trueness of an expression passed in within an + * interface implementation. These need to be compiled regardless of the + * DEBUG flag. New code should use NS_WARN_IF(condition) instead! + * @status deprecated + */ + +#define NS_ENSURE_TRUE(x, ret) \ + do { \ + if (MOZ_UNLIKELY(!(x))) { \ + NS_WARNING("NS_ENSURE_TRUE(" #x ") failed"); \ + return ret; \ + } \ + } while(0) + +#define NS_ENSURE_FALSE(x, ret) \ + NS_ENSURE_TRUE(!(x), ret) + +#define NS_ENSURE_TRUE_VOID(x) \ + do { \ + if (MOZ_UNLIKELY(!(x))) { \ + NS_WARNING("NS_ENSURE_TRUE(" #x ") failed"); \ + return; \ + } \ + } while(0) + +#define NS_ENSURE_FALSE_VOID(x) \ + NS_ENSURE_TRUE_VOID(!(x)) + +/****************************************************************************** +** Macros for checking results +******************************************************************************/ + +#if defined(DEBUG) && !defined(XPCOM_GLUE_AVOID_NSPR) + +#define NS_ENSURE_SUCCESS_BODY(res, ret) \ + char *msg = PR_smprintf("NS_ENSURE_SUCCESS(%s, %s) failed with " \ + "result 0x%X", #res, #ret, __rv); \ + NS_WARNING(msg); \ + PR_smprintf_free(msg); + +#define NS_ENSURE_SUCCESS_BODY_VOID(res) \ + char *msg = PR_smprintf("NS_ENSURE_SUCCESS_VOID(%s) failed with " \ + "result 0x%X", #res, __rv); \ + NS_WARNING(msg); \ + PR_smprintf_free(msg); + +#else + +#define NS_ENSURE_SUCCESS_BODY(res, ret) \ + NS_WARNING("NS_ENSURE_SUCCESS(" #res ", " #ret ") failed"); + +#define NS_ENSURE_SUCCESS_BODY_VOID(res) \ + NS_WARNING("NS_ENSURE_SUCCESS_VOID(" #res ") failed"); + +#endif + +#define NS_ENSURE_SUCCESS(res, ret) \ + do { \ + nsresult __rv = res; /* Don't evaluate |res| more than once */ \ + if (NS_FAILED(__rv)) { \ + NS_ENSURE_SUCCESS_BODY(res, ret) \ + return ret; \ + } \ + } while(0) + +#define NS_ENSURE_SUCCESS_VOID(res) \ + do { \ + nsresult __rv = res; \ + if (NS_FAILED(__rv)) { \ + NS_ENSURE_SUCCESS_BODY_VOID(res) \ + return; \ + } \ + } while(0) + +/****************************************************************************** +** Macros for checking state and arguments upon entering interface boundaries +******************************************************************************/ + +#define NS_ENSURE_ARG(arg) \ + NS_ENSURE_TRUE(arg, NS_ERROR_INVALID_ARG) + +#define NS_ENSURE_ARG_POINTER(arg) \ + NS_ENSURE_TRUE(arg, NS_ERROR_INVALID_POINTER) + +#define NS_ENSURE_ARG_MIN(arg, min) \ + NS_ENSURE_TRUE((arg) >= min, NS_ERROR_INVALID_ARG) + +#define NS_ENSURE_ARG_MAX(arg, max) \ + NS_ENSURE_TRUE((arg) <= max, NS_ERROR_INVALID_ARG) + +#define NS_ENSURE_ARG_RANGE(arg, min, max) \ + NS_ENSURE_TRUE(((arg) >= min) && ((arg) <= max), NS_ERROR_INVALID_ARG) + +#define NS_ENSURE_STATE(state) \ + NS_ENSURE_TRUE(state, NS_ERROR_UNEXPECTED) + +#define NS_ENSURE_NO_AGGREGATION(outer) \ + NS_ENSURE_FALSE(outer, NS_ERROR_NO_AGGREGATION) + +/*****************************************************************************/ + +#if (defined(DEBUG) || (defined(NIGHTLY_BUILD) && !defined(MOZ_PROFILING))) && !defined(XPCOM_GLUE_AVOID_NSPR) + #define MOZ_THREAD_SAFETY_OWNERSHIP_CHECKS_SUPPORTED 1 +#endif + +#ifdef XPCOM_GLUE + #define NS_CheckThreadSafe(owningThread, msg) +#else + #define NS_CheckThreadSafe(owningThread, msg) \ + if (MOZ_UNLIKELY(owningThread != PR_GetCurrentThread())) { \ + MOZ_CRASH(msg); \ + } +#endif + +#ifdef MOZILLA_INTERNAL_API +void NS_ABORT_OOM(size_t aSize); +#else +inline void NS_ABORT_OOM(size_t) +{ + MOZ_CRASH(); +} +#endif + +typedef void (*StderrCallback)(const char* aFmt, va_list aArgs); +/* When compiling the XPCOM Glue on Windows, we pretend that it's going to + * be linked with a static CRT (-MT) even when it's not. This means that we + * cannot link to data exports from the CRT, only function exports. So, + * instead of referencing "stderr" directly, use fdopen. + */ +#ifdef __cplusplus +extern "C" { +#endif + +/** + * printf_stderr(...) is much like fprintf(stderr, ...), except that: + * - it calls the callback set through set_stderr_callback + * - on Android and Firefox OS, *instead* of printing to stderr, it + * prints to logcat. (Newlines in the string lead to multiple lines + * of logcat, but each function call implicitly completes a line even + * if the string does not end with a newline.) + * - on Windows, if a debugger is present, it calls OutputDebugString + * in *addition* to writing to stderr + */ +void printf_stderr(const char* aFmt, ...) MOZ_FORMAT_PRINTF(1, 2); + +/** + * Same as printf_stderr, but taking va_list instead of varargs + */ +void vprintf_stderr(const char* aFmt, va_list aArgs); + +/** + * fprintf_stderr is like fprintf, except that if its file argument + * is stderr, it invokes printf_stderr instead. + * + * This is useful for general debugging code that logs information to a + * file, but that you would like to be useful on Android and Firefox OS. + * If you use fprintf_stderr instead of fprintf in such debugging code, + * then callers can pass stderr to get logging that works on Android and + * Firefox OS (and also the other side-effects of using printf_stderr). + * + * Code that is structured this way needs to be careful not to split a + * line of output across multiple calls to fprintf_stderr, since doing + * so will cause it to appear in multiple lines in logcat output. + * (Producing multiple lines at once is fine.) + */ +void fprintf_stderr(FILE* aFile, const char* aFmt, ...) MOZ_FORMAT_PRINTF(2, 3); + +// used by the profiler to log stderr in the profiler for more +// advanced performance debugging and display/layers visualization. +void set_stderr_callback(StderrCallback aCallback); + +#if defined(ANDROID) && !defined(RELEASE_OR_BETA) +// Call this if you want a copy of stderr logging sent to a file. This is +// useful to get around logcat overflow problems on android devices, which use +// a circular logcat buffer and can intermittently drop messages if there's too +// much spew. +// +// This is intended for local debugging only, DO NOT USE IN PRODUCTION CODE. +// (This is ifndef RELEASE_OR_BETA to catch uses of it that accidentally get +// checked in). Using this will also prevent the profiler from getting a copy of +// the stderr messages which it uses for various visualization features. +// +// This function can be called from any thread, but if it is called multiple +// times all invocations must be on the same thread. Invocations after the +// first one are ignored, so you can safely put it inside a loop, for example. +// Once this is called there is no way to turn it off; all stderr output from +// that point forward will go to the file. Note that the output is subject to +// buffering so make sure you have enough output to flush the messages you care +// about before you terminate the process. +// +// The file passed in should be writable, so on Android devices a path like +// "/data/local/tmp/blah" is a good one to use as it is world-writable and will +// work even in B2G child processes which have reduced privileges. Note that the +// actual file created will have the PID appended to the path you pass in, so +// that on B2G the output from each process goes to a separate file. +void copy_stderr_to_file(const char* aFile); +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* nsDebug_h___ */ diff --git a/xpcom/glue/nsDeque.cpp b/xpcom/glue/nsDeque.cpp new file mode 100644 index 0000000000..f9eb18b405 --- /dev/null +++ b/xpcom/glue/nsDeque.cpp @@ -0,0 +1,361 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsDeque.h" +#include "nsISupportsImpl.h" +#include <string.h> +#ifdef DEBUG_rickg +#include <stdio.h> +#endif + +#include "mozilla/CheckedInt.h" + +#define modulus(x,y) ((x)%(y)) + +/** + * Standard constructor + * @param deallocator, called by Erase and ~nsDeque + */ +nsDeque::nsDeque(nsDequeFunctor* aDeallocator) +{ + MOZ_COUNT_CTOR(nsDeque); + mDeallocator = aDeallocator; + mOrigin = mSize = 0; + mData = mBuffer; // don't allocate space until you must + mCapacity = sizeof(mBuffer) / sizeof(mBuffer[0]); + memset(mData, 0, mCapacity * sizeof(mBuffer[0])); +} + +/** + * Destructor + */ +nsDeque::~nsDeque() +{ + MOZ_COUNT_DTOR(nsDeque); + +#ifdef DEBUG_rickg + char buffer[30]; + printf("Capacity: %i\n", mCapacity); + + static int mCaps[15] = {0}; + switch (mCapacity) { + case 4: mCaps[0]++; break; + case 8: mCaps[1]++; break; + case 16: mCaps[2]++; break; + case 32: mCaps[3]++; break; + case 64: mCaps[4]++; break; + case 128: mCaps[5]++; break; + case 256: mCaps[6]++; break; + case 512: mCaps[7]++; break; + case 1024: mCaps[8]++; break; + case 2048: mCaps[9]++; break; + case 4096: mCaps[10]++; break; + default: + break; + } +#endif + + Erase(); + if (mData && mData != mBuffer) { + free(mData); + } + mData = 0; + SetDeallocator(0); +} + +size_t +nsDeque::SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const +{ + size_t size = 0; + if (mData != mBuffer) { + size += aMallocSizeOf(mData); + } + + if (mDeallocator) { + size += aMallocSizeOf(mDeallocator); + } + + return size; +} + +size_t +nsDeque::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const +{ + return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); +} + +/** + * Set the functor to be called by Erase() + * The deque owns the functor. + * + * @param aDeallocator functor object for use by Erase() + */ +void +nsDeque::SetDeallocator(nsDequeFunctor* aDeallocator) +{ + delete mDeallocator; + mDeallocator = aDeallocator; +} + +/** + * Remove all items from container without destroying them. + */ +void +nsDeque::Empty() +{ + if (mSize && mData) { + memset(mData, 0, mCapacity * sizeof(*mData)); + } + mSize = 0; + mOrigin = 0; +} + +/** + * Remove and delete all items from container + */ +void +nsDeque::Erase() +{ + if (mDeallocator && mSize) { + ForEach(*mDeallocator); + } + Empty(); +} + +/** + * This method quadruples the size of the deque + * Elements in the deque are resequenced so that elements + * in the deque are stored sequentially + * + * @return whether growing succeeded + */ +bool +nsDeque::GrowCapacity() +{ + mozilla::CheckedInt<size_t> newCapacity = mCapacity; + newCapacity *= 4; + + NS_ASSERTION(newCapacity.isValid(), "Overflow"); + if (!newCapacity.isValid()) { + return false; + } + + // Sanity check the new byte size. + mozilla::CheckedInt<size_t> newByteSize = newCapacity; + newByteSize *= sizeof(void*); + + NS_ASSERTION(newByteSize.isValid(), "Overflow"); + if (!newByteSize.isValid()) { + return false; + } + + void** temp = (void**)malloc(newByteSize.value()); + if (!temp) { + return false; + } + + //Here's the interesting part: You can't just move the elements + //directly (in situ) from the old buffer to the new one. + //Since capacity has changed, the old origin doesn't make + //sense anymore. It's better to resequence the elements now. + + memcpy(temp, mData + mOrigin, sizeof(void*) * (mCapacity - mOrigin)); + memcpy(temp + (mCapacity - mOrigin), mData, sizeof(void*) * mOrigin); + + if (mData != mBuffer) { + free(mData); + } + + mCapacity = newCapacity.value(); + mOrigin = 0; //now realign the origin... + mData = temp; + + return true; +} + +/** + * This method adds an item to the end of the deque. + * This operation has the potential to cause the + * underlying buffer to resize. + * + * @param aItem: new item to be added to deque + */ +bool +nsDeque::Push(void* aItem, const fallible_t&) +{ + if (mSize == mCapacity && !GrowCapacity()) { + return false; + } + mData[modulus(mOrigin + mSize, mCapacity)] = aItem; + mSize++; + return true; +} + +/** + * This method adds an item to the front of the deque. + * This operation has the potential to cause the + * underlying buffer to resize. + * + * --Commments for GrowCapacity() case + * We've grown and shifted which means that the old + * final element in the deque is now the first element + * in the deque. This is temporary. + * We haven't inserted the new element at the front. + * + * To continue with the idea of having the front at zero + * after a grow, we move the old final item (which through + * the voodoo of mOrigin-- is now the first) to its final + * position which is conveniently the old length. + * + * Note that this case only happens when the deque is full. + * [And that pieces of this magic only work if the deque is full.] + * picture: + * [ABCDEFGH] @[mOrigin:3]:D. + * Task: PushFront("Z") + * shift mOrigin so, @[mOrigin:2]:C + * stretch and rearrange: (mOrigin:0) + * [CDEFGHAB ________ ________ ________] + * copy: (The second C is currently out of bounds) + * [CDEFGHAB C_______ ________ ________] + * later we will insert Z: + * [ZDEFGHAB C_______ ________ ________] + * and increment size: 9. (C is no longer out of bounds) + * -- + * @param aItem: new item to be added to deque + */ +bool +nsDeque::PushFront(void* aItem, const fallible_t&) +{ + + if (mOrigin == 0) { + mOrigin = mCapacity - 1; + } else { + mOrigin--; + } + + if (mSize == mCapacity) { + if (!GrowCapacity()) { + return false; + } + /* Comments explaining this are above*/ + mData[mSize] = mData[mOrigin]; + } + mData[mOrigin] = aItem; + mSize++; + return true; +} + +/** + * Remove and return the last item in the container. + * + * @return ptr to last item in container + */ +void* +nsDeque::Pop() +{ + void* result = 0; + if (mSize > 0) { + --mSize; + size_t offset = modulus(mSize + mOrigin, mCapacity); + result = mData[offset]; + mData[offset] = 0; + if (!mSize) { + mOrigin = 0; + } + } + return result; +} + +/** + * This method gets called you want to remove and return + * the first member in the container. + * + * @return last item in container + */ +void* +nsDeque::PopFront() +{ + void* result = 0; + if (mSize > 0) { + NS_ASSERTION(mOrigin < mCapacity, "Error: Bad origin"); + result = mData[mOrigin]; + mData[mOrigin++] = 0; //zero it out for debugging purposes. + mSize--; + // Cycle around if we pop off the end + // and reset origin if when we pop the last element + if (mCapacity == mOrigin || !mSize) { + mOrigin = 0; + } + } + return result; +} + +/** + * This method gets called you want to peek at the bottom + * member without removing it. + * + * @return last item in container + */ +void* +nsDeque::Peek() const +{ + void* result = 0; + if (mSize > 0) { + result = mData[modulus(mSize - 1 + mOrigin, mCapacity)]; + } + return result; +} + +/** + * This method gets called you want to peek at the topmost + * member without removing it. + * + * @return last item in container + */ +void* +nsDeque::PeekFront() const +{ + void* result = 0; + if (mSize > 0) { + result = mData[mOrigin]; + } + return result; +} + +/** + * Call this to retrieve the ith element from this container. + * Keep in mind that accessing the underlying elements is + * done in a relative fashion. Object 0 is not necessarily + * the first element (the first element is at mOrigin). + * + * @param aIndex : 0 relative offset of item you want + * @return void* or null + */ +void* +nsDeque::ObjectAt(size_t aIndex) const +{ + void* result = 0; + if (aIndex < mSize) { + result = mData[modulus(mOrigin + aIndex, mCapacity)]; + } + return result; +} + +/** + * Call this method when you want to iterate all the + * members of the container, passing a functor along + * to call your code. + * + * @param aFunctor object to call for each member + * @return *this + */ +void +nsDeque::ForEach(nsDequeFunctor& aFunctor) const +{ + for (size_t i = 0; i < mSize; ++i) { + aFunctor(ObjectAt(i)); + } +} diff --git a/xpcom/glue/nsDeque.h b/xpcom/glue/nsDeque.h new file mode 100644 index 0000000000..ace7607d39 --- /dev/null +++ b/xpcom/glue/nsDeque.h @@ -0,0 +1,195 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * MODULE NOTES: + * + * The Deque is a very small, very efficient container object + * than can hold elements of type void*, offering the following features: + * Its interface supports pushing and popping of elements. + * It can iterate (via an interator class) its elements. + * When full, it can efficiently resize dynamically. + * + * + * NOTE: The only bit of trickery here is that this deque is + * built upon a ring-buffer. Like all ring buffers, the first + * element may not be at index[0]. The mOrigin member determines + * where the first child is. This point is quietly hidden from + * customers of this class. + * + */ + +#ifndef _NSDEQUE +#define _NSDEQUE + +#include "nscore.h" +#include "nsDebug.h" +#include "mozilla/Attributes.h" +#include "mozilla/fallible.h" +#include "mozilla/MemoryReporting.h" + +/** + * The nsDequeFunctor class is used when you want to create + * callbacks between the deque and your generic code. + * Use these objects in a call to ForEach(); + * + */ + +class nsDequeFunctor +{ +public: + virtual void* operator()(void* aObject) = 0; + virtual ~nsDequeFunctor() {} +}; + +/****************************************************** + * Here comes the nsDeque class itself... + ******************************************************/ + +/** + * The deque (double-ended queue) class is a common container type, + * whose behavior mimics a line in your favorite checkout stand. + * Classic CS describes the common behavior of a queue as FIFO. + * A deque allows insertion and removal at both ends of + * the container. + * + * The deque stores pointers to items. + */ + +class nsDequeIterator; + +class nsDeque +{ + typedef mozilla::fallible_t fallible_t; +public: + explicit nsDeque(nsDequeFunctor* aDeallocator = nullptr); + ~nsDeque(); + + /** + * Returns the number of elements currently stored in + * this deque. + * + * @return number of elements currently in the deque + */ + inline size_t GetSize() const { return mSize; } + + /** + * Appends new member at the end of the deque. + * + * @param item to store in deque + */ + void Push(void* aItem) + { + if (!Push(aItem, mozilla::fallible)) { + NS_ABORT_OOM(mSize * sizeof(void*)); + } + } + + MOZ_MUST_USE bool Push(void* aItem, const fallible_t&); + + /** + * Inserts new member at the front of the deque. + * + * @param item to store in deque + */ + void PushFront(void* aItem) + { + if (!PushFront(aItem, mozilla::fallible)) { + NS_ABORT_OOM(mSize * sizeof(void*)); + } + } + + MOZ_MUST_USE bool PushFront(void* aItem, const fallible_t&); + + /** + * Remove and return the last item in the container. + * + * @return the item that was the last item in container + */ + void* Pop(); + + /** + * Remove and return the first item in the container. + * + * @return the item that was first item in container + */ + void* PopFront(); + + /** + * Retrieve the bottom item without removing it. + * + * @return the first item in container + */ + + void* Peek() const; + /** + * Return topmost item without removing it. + * + * @return the first item in container + */ + void* PeekFront() const; + + /** + * Retrieve a member from the deque without removing it. + * + * @param index of desired item + * @return element in list + */ + void* ObjectAt(size_t aIndex) const; + + /** + * Remove and delete all items from container. + * Deletes are handled by the deallocator nsDequeFunctor + * which is specified at deque construction. + */ + void Erase(); + + /** + * Call this method when you want to iterate all the + * members of the container, passing a functor along + * to call your code. + * + * @param aFunctor object to call for each member + */ + void ForEach(nsDequeFunctor& aFunctor) const; + + size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const; + size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const; + +protected: + size_t mSize; + size_t mCapacity; + size_t mOrigin; + nsDequeFunctor* mDeallocator; + void* mBuffer[8]; + void** mData; + +private: + + /** + * Copy constructor (PRIVATE) + * + * @param aOther another deque + */ + nsDeque(const nsDeque& aOther); + + /** + * Deque assignment operator (PRIVATE) + * + * @param aOther another deque + * @return *this + */ + nsDeque& operator=(const nsDeque& aOther); + + bool GrowCapacity(); + void SetDeallocator(nsDequeFunctor* aDeallocator); + + /** + * Remove all items from container without destroying them. + */ + void Empty(); +}; +#endif diff --git a/xpcom/glue/nsEnumeratorUtils.cpp b/xpcom/glue/nsEnumeratorUtils.cpp new file mode 100644 index 0000000000..d1843a78ad --- /dev/null +++ b/xpcom/glue/nsEnumeratorUtils.cpp @@ -0,0 +1,291 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/Attributes.h" + +#include "nsEnumeratorUtils.h" + +#include "nsISimpleEnumerator.h" +#include "nsIStringEnumerator.h" + +#include "nsCOMPtr.h" +#include "mozilla/RefPtr.h" + +class EmptyEnumeratorImpl + : public nsISimpleEnumerator + , public nsIUTF8StringEnumerator + , public nsIStringEnumerator +{ +public: + EmptyEnumeratorImpl() {} + + // nsISupports interface + NS_DECL_ISUPPORTS_INHERITED // not really inherited, but no mRefCnt + + // nsISimpleEnumerator + NS_DECL_NSISIMPLEENUMERATOR + NS_DECL_NSIUTF8STRINGENUMERATOR + // can't use NS_DECL_NSISTRINGENUMERATOR because they share the + // HasMore() signature + NS_IMETHOD GetNext(nsAString& aResult) override; + + static EmptyEnumeratorImpl* GetInstance() + { + static const EmptyEnumeratorImpl kInstance; + return const_cast<EmptyEnumeratorImpl*>(&kInstance); + } +}; + +// nsISupports interface +NS_IMETHODIMP_(MozExternalRefCountType) +EmptyEnumeratorImpl::AddRef(void) +{ + return 2; +} + +NS_IMETHODIMP_(MozExternalRefCountType) +EmptyEnumeratorImpl::Release(void) +{ + return 1; +} + +NS_IMPL_QUERY_INTERFACE(EmptyEnumeratorImpl, nsISimpleEnumerator, + nsIUTF8StringEnumerator, nsIStringEnumerator) + +// nsISimpleEnumerator interface +NS_IMETHODIMP +EmptyEnumeratorImpl::HasMoreElements(bool* aResult) +{ + *aResult = false; + return NS_OK; +} + +NS_IMETHODIMP +EmptyEnumeratorImpl::HasMore(bool* aResult) +{ + *aResult = false; + return NS_OK; +} + +NS_IMETHODIMP +EmptyEnumeratorImpl::GetNext(nsISupports** aResult) +{ + return NS_ERROR_UNEXPECTED; +} + +NS_IMETHODIMP +EmptyEnumeratorImpl::GetNext(nsACString& aResult) +{ + return NS_ERROR_UNEXPECTED; +} + +NS_IMETHODIMP +EmptyEnumeratorImpl::GetNext(nsAString& aResult) +{ + return NS_ERROR_UNEXPECTED; +} + +nsresult +NS_NewEmptyEnumerator(nsISimpleEnumerator** aResult) +{ + *aResult = EmptyEnumeratorImpl::GetInstance(); + return NS_OK; +} + +//////////////////////////////////////////////////////////////////////////////// + +class nsSingletonEnumerator final : public nsISimpleEnumerator +{ +public: + NS_DECL_ISUPPORTS + + // nsISimpleEnumerator methods + NS_IMETHOD HasMoreElements(bool* aResult) override; + NS_IMETHOD GetNext(nsISupports** aResult) override; + + explicit nsSingletonEnumerator(nsISupports* aValue); + +private: + ~nsSingletonEnumerator(); + +protected: + nsCOMPtr<nsISupports> mValue; + bool mConsumed; +}; + +nsSingletonEnumerator::nsSingletonEnumerator(nsISupports* aValue) + : mValue(aValue) +{ + mConsumed = (mValue ? false : true); +} + +nsSingletonEnumerator::~nsSingletonEnumerator() +{ +} + +NS_IMPL_ISUPPORTS(nsSingletonEnumerator, nsISimpleEnumerator) + +NS_IMETHODIMP +nsSingletonEnumerator::HasMoreElements(bool* aResult) +{ + NS_PRECONDITION(aResult != 0, "null ptr"); + if (!aResult) { + return NS_ERROR_NULL_POINTER; + } + + *aResult = !mConsumed; + return NS_OK; +} + + +NS_IMETHODIMP +nsSingletonEnumerator::GetNext(nsISupports** aResult) +{ + NS_PRECONDITION(aResult != 0, "null ptr"); + if (!aResult) { + return NS_ERROR_NULL_POINTER; + } + + if (mConsumed) { + return NS_ERROR_UNEXPECTED; + } + + mConsumed = true; + + *aResult = mValue; + NS_ADDREF(*aResult); + return NS_OK; +} + +nsresult +NS_NewSingletonEnumerator(nsISimpleEnumerator** aResult, + nsISupports* aSingleton) +{ + RefPtr<nsSingletonEnumerator> enumer = new nsSingletonEnumerator(aSingleton); + enumer.forget(aResult); + return NS_OK; +} + +//////////////////////////////////////////////////////////////////////////////// + +class nsUnionEnumerator final : public nsISimpleEnumerator +{ +public: + NS_DECL_ISUPPORTS + + // nsISimpleEnumerator methods + NS_IMETHOD HasMoreElements(bool* aResult) override; + NS_IMETHOD GetNext(nsISupports** aResult) override; + + nsUnionEnumerator(nsISimpleEnumerator* aFirstEnumerator, + nsISimpleEnumerator* aSecondEnumerator); + +private: + ~nsUnionEnumerator(); + +protected: + nsCOMPtr<nsISimpleEnumerator> mFirstEnumerator, mSecondEnumerator; + bool mConsumed; + bool mAtSecond; +}; + +nsUnionEnumerator::nsUnionEnumerator(nsISimpleEnumerator* aFirstEnumerator, + nsISimpleEnumerator* aSecondEnumerator) + : mFirstEnumerator(aFirstEnumerator) + , mSecondEnumerator(aSecondEnumerator) + , mConsumed(false) + , mAtSecond(false) +{ +} + +nsUnionEnumerator::~nsUnionEnumerator() +{ +} + +NS_IMPL_ISUPPORTS(nsUnionEnumerator, nsISimpleEnumerator) + +NS_IMETHODIMP +nsUnionEnumerator::HasMoreElements(bool* aResult) +{ + NS_PRECONDITION(aResult != 0, "null ptr"); + if (!aResult) { + return NS_ERROR_NULL_POINTER; + } + + nsresult rv; + + if (mConsumed) { + *aResult = false; + return NS_OK; + } + + if (!mAtSecond) { + rv = mFirstEnumerator->HasMoreElements(aResult); + if (NS_FAILED(rv)) { + return rv; + } + + if (*aResult) { + return NS_OK; + } + + mAtSecond = true; + } + + rv = mSecondEnumerator->HasMoreElements(aResult); + if (NS_FAILED(rv)) { + return rv; + } + + if (*aResult) { + return NS_OK; + } + + *aResult = false; + mConsumed = true; + return NS_OK; +} + +NS_IMETHODIMP +nsUnionEnumerator::GetNext(nsISupports** aResult) +{ + NS_PRECONDITION(aResult != 0, "null ptr"); + if (!aResult) { + return NS_ERROR_NULL_POINTER; + } + + if (mConsumed) { + return NS_ERROR_UNEXPECTED; + } + + if (!mAtSecond) { + return mFirstEnumerator->GetNext(aResult); + } + + return mSecondEnumerator->GetNext(aResult); +} + +nsresult +NS_NewUnionEnumerator(nsISimpleEnumerator** aResult, + nsISimpleEnumerator* aFirstEnumerator, + nsISimpleEnumerator* aSecondEnumerator) +{ + *aResult = nullptr; + if (!aFirstEnumerator) { + *aResult = aSecondEnumerator; + } else if (!aSecondEnumerator) { + *aResult = aFirstEnumerator; + } else { + nsUnionEnumerator* enumer = new nsUnionEnumerator(aFirstEnumerator, + aSecondEnumerator); + if (!enumer) { + return NS_ERROR_OUT_OF_MEMORY; + } + *aResult = enumer; + } + NS_ADDREF(*aResult); + return NS_OK; +} diff --git a/xpcom/glue/nsEnumeratorUtils.h b/xpcom/glue/nsEnumeratorUtils.h new file mode 100644 index 0000000000..f7a0db099e --- /dev/null +++ b/xpcom/glue/nsEnumeratorUtils.h @@ -0,0 +1,24 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsEnumeratorUtils_h__ +#define nsEnumeratorUtils_h__ + +#include "nscore.h" + +class nsISupports; +class nsISimpleEnumerator; + +nsresult NS_NewEmptyEnumerator(nsISimpleEnumerator** aResult); + +nsresult NS_NewSingletonEnumerator(nsISimpleEnumerator** aResult, + nsISupports* aSingleton); + +nsresult NS_NewUnionEnumerator(nsISimpleEnumerator** aResult, + nsISimpleEnumerator* aFirstEnumerator, + nsISimpleEnumerator* aSecondEnumerator); + +#endif /* nsEnumeratorUtils_h__ */ diff --git a/xpcom/glue/nsHashKeys.h b/xpcom/glue/nsHashKeys.h new file mode 100644 index 0000000000..9c688691fc --- /dev/null +++ b/xpcom/glue/nsHashKeys.h @@ -0,0 +1,660 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsTHashKeys_h__ +#define nsTHashKeys_h__ + +#include "nsID.h" +#include "nsISupports.h" +#include "nsIHashable.h" +#include "nsAutoPtr.h" +#include "nsCOMPtr.h" +#include "PLDHashTable.h" +#include <new> + +#include "nsStringGlue.h" +#include "nsCRTGlue.h" +#include "nsUnicharUtils.h" +#include "nsPointerHashKeys.h" + +#include <stdlib.h> +#include <string.h> + +#include "mozilla/HashFunctions.h" +#include "mozilla/Move.h" + +namespace mozilla { + +// These are defined analogously to the HashString overloads in mfbt. + +inline uint32_t +HashString(const nsAString& aStr) +{ + return HashString(aStr.BeginReading(), aStr.Length()); +} + +inline uint32_t +HashString(const nsACString& aStr) +{ + return HashString(aStr.BeginReading(), aStr.Length()); +} + +} // namespace mozilla + +/** @file nsHashKeys.h + * standard HashKey classes for nsBaseHashtable and relatives. Each of these + * classes follows the nsTHashtable::EntryType specification + * + * Lightweight keytypes provided here: + * nsStringHashKey + * nsCStringHashKey + * nsUint32HashKey + * nsUint64HashKey + * nsFloatHashKey + * nsPtrHashKey + * nsClearingPtrHashKey + * nsVoidPtrHashKey + * nsClearingVoidPtrHashKey + * nsISupportsHashKey + * nsIDHashKey + * nsDepCharHashKey + * nsCharPtrHashKey + * nsUnicharPtrHashKey + * nsHashableHashKey + * nsGenericHashKey + */ + +/** + * hashkey wrapper using nsAString KeyType + * + * @see nsTHashtable::EntryType for specification + */ +class nsStringHashKey : public PLDHashEntryHdr +{ +public: + typedef const nsAString& KeyType; + typedef const nsAString* KeyTypePointer; + + explicit nsStringHashKey(KeyTypePointer aStr) : mStr(*aStr) {} + nsStringHashKey(const nsStringHashKey& aToCopy) : mStr(aToCopy.mStr) {} + ~nsStringHashKey() {} + + KeyType GetKey() const { return mStr; } + bool KeyEquals(const KeyTypePointer aKey) const + { + return mStr.Equals(*aKey); + } + + static KeyTypePointer KeyToPointer(KeyType aKey) { return &aKey; } + static PLDHashNumber HashKey(const KeyTypePointer aKey) + { + return mozilla::HashString(*aKey); + } + +#ifdef MOZILLA_INTERNAL_API + // To avoid double-counting, only measure the string if it is unshared. + size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const + { + return GetKey().SizeOfExcludingThisIfUnshared(aMallocSizeOf); + } +#endif + + enum { ALLOW_MEMMOVE = true }; + +private: + const nsString mStr; +}; + +#ifdef MOZILLA_INTERNAL_API + +/** + * hashkey wrapper using nsAString KeyType + * + * This is internal-API only because nsCaseInsensitiveStringComparator is + * internal-only. + * + * @see nsTHashtable::EntryType for specification + */ +class nsStringCaseInsensitiveHashKey : public PLDHashEntryHdr +{ +public: + typedef const nsAString& KeyType; + typedef const nsAString* KeyTypePointer; + + explicit nsStringCaseInsensitiveHashKey(KeyTypePointer aStr) + : mStr(*aStr) + { + // take it easy just deal HashKey + } + nsStringCaseInsensitiveHashKey(const nsStringCaseInsensitiveHashKey& aToCopy) + : mStr(aToCopy.mStr) + { + } + ~nsStringCaseInsensitiveHashKey() {} + + KeyType GetKey() const { return mStr; } + bool KeyEquals(const KeyTypePointer aKey) const + { + return mStr.Equals(*aKey, nsCaseInsensitiveStringComparator()); + } + + static KeyTypePointer KeyToPointer(KeyType aKey) { return &aKey; } + static PLDHashNumber HashKey(const KeyTypePointer aKey) + { + nsAutoString tmKey(*aKey); + ToLowerCase(tmKey); + return mozilla::HashString(tmKey); + } + enum { ALLOW_MEMMOVE = true }; + + // To avoid double-counting, only measure the string if it is unshared. + size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const + { + return GetKey().SizeOfExcludingThisIfUnshared(aMallocSizeOf); + } + +private: + const nsString mStr; +}; + +#endif + +/** + * hashkey wrapper using nsACString KeyType + * + * @see nsTHashtable::EntryType for specification + */ +class nsCStringHashKey : public PLDHashEntryHdr +{ +public: + typedef const nsACString& KeyType; + typedef const nsACString* KeyTypePointer; + + explicit nsCStringHashKey(const nsACString* aStr) : mStr(*aStr) {} + nsCStringHashKey(const nsCStringHashKey& aToCopy) : mStr(aToCopy.mStr) {} + ~nsCStringHashKey() {} + + KeyType GetKey() const { return mStr; } + bool KeyEquals(KeyTypePointer aKey) const { return mStr.Equals(*aKey); } + + static KeyTypePointer KeyToPointer(KeyType aKey) { return &aKey; } + static PLDHashNumber HashKey(KeyTypePointer aKey) + { + return mozilla::HashString(*aKey); + } + +#ifdef MOZILLA_INTERNAL_API + // To avoid double-counting, only measure the string if it is unshared. + size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const + { + return GetKey().SizeOfExcludingThisIfUnshared(aMallocSizeOf); + } +#endif + + enum { ALLOW_MEMMOVE = true }; + +private: + const nsCString mStr; +}; + +/** + * hashkey wrapper using uint32_t KeyType + * + * @see nsTHashtable::EntryType for specification + */ +class nsUint32HashKey : public PLDHashEntryHdr +{ +public: + typedef const uint32_t& KeyType; + typedef const uint32_t* KeyTypePointer; + + explicit nsUint32HashKey(KeyTypePointer aKey) : mValue(*aKey) {} + nsUint32HashKey(const nsUint32HashKey& aToCopy) : mValue(aToCopy.mValue) {} + ~nsUint32HashKey() {} + + KeyType GetKey() const { return mValue; } + bool KeyEquals(KeyTypePointer aKey) const { return *aKey == mValue; } + + static KeyTypePointer KeyToPointer(KeyType aKey) { return &aKey; } + static PLDHashNumber HashKey(KeyTypePointer aKey) { return *aKey; } + enum { ALLOW_MEMMOVE = true }; + +private: + const uint32_t mValue; +}; + +/** + * hashkey wrapper using uint64_t KeyType + * + * @see nsTHashtable::EntryType for specification + */ +class nsUint64HashKey : public PLDHashEntryHdr +{ +public: + typedef const uint64_t& KeyType; + typedef const uint64_t* KeyTypePointer; + + explicit nsUint64HashKey(KeyTypePointer aKey) : mValue(*aKey) {} + nsUint64HashKey(const nsUint64HashKey& aToCopy) : mValue(aToCopy.mValue) {} + ~nsUint64HashKey() {} + + KeyType GetKey() const { return mValue; } + bool KeyEquals(KeyTypePointer aKey) const { return *aKey == mValue; } + + static KeyTypePointer KeyToPointer(KeyType aKey) { return &aKey; } + static PLDHashNumber HashKey(KeyTypePointer aKey) + { + return PLDHashNumber(*aKey); + } + enum { ALLOW_MEMMOVE = true }; + +private: + const uint64_t mValue; +}; + +/** + * hashkey wrapper using float KeyType + * + * @see nsTHashtable::EntryType for specification + */ +class nsFloatHashKey : public PLDHashEntryHdr +{ +public: + typedef const float& KeyType; + typedef const float* KeyTypePointer; + + explicit nsFloatHashKey(KeyTypePointer aKey) : mValue(*aKey) {} + nsFloatHashKey(const nsFloatHashKey& aToCopy) : mValue(aToCopy.mValue) {} + ~nsFloatHashKey() {} + + KeyType GetKey() const { return mValue; } + bool KeyEquals(KeyTypePointer aKey) const { return *aKey == mValue; } + + static KeyTypePointer KeyToPointer(KeyType aKey) { return &aKey; } + static PLDHashNumber HashKey(KeyTypePointer aKey) + { + return *reinterpret_cast<const uint32_t*>(aKey); + } + enum { ALLOW_MEMMOVE = true }; + +private: + const float mValue; +}; + +/** + * hashkey wrapper using nsISupports* KeyType + * + * @see nsTHashtable::EntryType for specification + */ +class nsISupportsHashKey : public PLDHashEntryHdr +{ +public: + typedef nsISupports* KeyType; + typedef const nsISupports* KeyTypePointer; + + explicit nsISupportsHashKey(const nsISupports* aKey) + : mSupports(const_cast<nsISupports*>(aKey)) + { + } + nsISupportsHashKey(const nsISupportsHashKey& aToCopy) + : mSupports(aToCopy.mSupports) + { + } + ~nsISupportsHashKey() {} + + KeyType GetKey() const { return mSupports; } + bool KeyEquals(KeyTypePointer aKey) const { return aKey == mSupports; } + + static KeyTypePointer KeyToPointer(KeyType aKey) { return aKey; } + static PLDHashNumber HashKey(KeyTypePointer aKey) + { + return NS_PTR_TO_UINT32(aKey) >> 2; + } + enum { ALLOW_MEMMOVE = true }; + +private: + nsCOMPtr<nsISupports> mSupports; +}; + +/** + * hashkey wrapper using refcounted * KeyType + * + * @see nsTHashtable::EntryType for specification + */ +template<class T> +class nsRefPtrHashKey : public PLDHashEntryHdr +{ +public: + typedef T* KeyType; + typedef const T* KeyTypePointer; + + explicit nsRefPtrHashKey(const T* aKey) : mKey(const_cast<T*>(aKey)) {} + nsRefPtrHashKey(const nsRefPtrHashKey& aToCopy) : mKey(aToCopy.mKey) {} + ~nsRefPtrHashKey() {} + + KeyType GetKey() const { return mKey; } + bool KeyEquals(KeyTypePointer aKey) const { return aKey == mKey; } + + static KeyTypePointer KeyToPointer(KeyType aKey) { return aKey; } + static PLDHashNumber HashKey(KeyTypePointer aKey) + { + return NS_PTR_TO_UINT32(aKey) >> 2; + } + enum { ALLOW_MEMMOVE = true }; + +private: + RefPtr<T> mKey; +}; + +template<class T> +inline void +ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback& aCallback, + nsRefPtrHashKey<T>& aField, + const char* aName, + uint32_t aFlags = 0) +{ + CycleCollectionNoteChild(aCallback, aField.GetKey(), aName, aFlags); +} + +/** + * hashkey wrapper using T* KeyType that sets key to nullptr upon + * destruction. Relevant only in cases where a memory pointer-scanner + * like valgrind might get confused about stale references. + * + * @see nsTHashtable::EntryType for specification + */ + +template<class T> +class nsClearingPtrHashKey : public nsPtrHashKey<T> +{ +public: + explicit nsClearingPtrHashKey(const T* aKey) : nsPtrHashKey<T>(aKey) {} + nsClearingPtrHashKey(const nsClearingPtrHashKey<T>& aToCopy) + : nsPtrHashKey<T>(aToCopy) + { + } + ~nsClearingPtrHashKey() { nsPtrHashKey<T>::mKey = nullptr; } +}; + +typedef nsClearingPtrHashKey<const void> nsClearingVoidPtrHashKey; + +/** + * hashkey wrapper using a function pointer KeyType + * + * @see nsTHashtable::EntryType for specification + */ +template<class T> +class nsFuncPtrHashKey : public PLDHashEntryHdr +{ +public: + typedef T& KeyType; + typedef const T* KeyTypePointer; + + explicit nsFuncPtrHashKey(const T* aKey) : mKey(*const_cast<T*>(aKey)) {} + nsFuncPtrHashKey(const nsFuncPtrHashKey<T>& aToCopy) : mKey(aToCopy.mKey) {} + ~nsFuncPtrHashKey() {} + + KeyType GetKey() const { return const_cast<T&>(mKey); } + bool KeyEquals(KeyTypePointer aKey) const { return *aKey == mKey; } + + static KeyTypePointer KeyToPointer(KeyType aKey) { return &aKey; } + static PLDHashNumber HashKey(KeyTypePointer aKey) + { + return NS_PTR_TO_UINT32(*aKey) >> 2; + } + enum { ALLOW_MEMMOVE = true }; + +protected: + T mKey; +}; + +/** + * hashkey wrapper using nsID KeyType + * + * @see nsTHashtable::EntryType for specification + */ +class nsIDHashKey : public PLDHashEntryHdr +{ +public: + typedef const nsID& KeyType; + typedef const nsID* KeyTypePointer; + + explicit nsIDHashKey(const nsID* aInID) : mID(*aInID) {} + nsIDHashKey(const nsIDHashKey& aToCopy) : mID(aToCopy.mID) {} + ~nsIDHashKey() {} + + KeyType GetKey() const { return mID; } + bool KeyEquals(KeyTypePointer aKey) const { return aKey->Equals(mID); } + + static KeyTypePointer KeyToPointer(KeyType aKey) { return &aKey; } + static PLDHashNumber HashKey(KeyTypePointer aKey) + { + // Hash the nsID object's raw bytes. + return mozilla::HashBytes(aKey, sizeof(KeyType)); + } + + enum { ALLOW_MEMMOVE = true }; + +private: + const nsID mID; +}; + +/** + * hashkey wrapper for "dependent" const char*; this class does not "own" + * its string pointer. + * + * This class must only be used if the strings have a lifetime longer than + * the hashtable they occupy. This normally occurs only for static + * strings or strings that have been arena-allocated. + * + * @see nsTHashtable::EntryType for specification + */ +class nsDepCharHashKey : public PLDHashEntryHdr +{ +public: + typedef const char* KeyType; + typedef const char* KeyTypePointer; + + explicit nsDepCharHashKey(const char* aKey) : mKey(aKey) {} + nsDepCharHashKey(const nsDepCharHashKey& aToCopy) : mKey(aToCopy.mKey) {} + ~nsDepCharHashKey() {} + + const char* GetKey() const { return mKey; } + bool KeyEquals(const char* aKey) const { return !strcmp(mKey, aKey); } + + static const char* KeyToPointer(const char* aKey) { return aKey; } + static PLDHashNumber HashKey(const char* aKey) + { + return mozilla::HashString(aKey); + } + enum { ALLOW_MEMMOVE = true }; + +private: + const char* mKey; +}; + +/** + * hashkey wrapper for const char*; at construction, this class duplicates + * a string pointed to by the pointer so that it doesn't matter whether or not + * the string lives longer than the hash table. + */ +class nsCharPtrHashKey : public PLDHashEntryHdr +{ +public: + typedef const char* KeyType; + typedef const char* KeyTypePointer; + + explicit nsCharPtrHashKey(const char* aKey) : mKey(strdup(aKey)) {} + nsCharPtrHashKey(const nsCharPtrHashKey& aToCopy) + : mKey(strdup(aToCopy.mKey)) + { + } + + nsCharPtrHashKey(nsCharPtrHashKey&& aOther) + : mKey(aOther.mKey) + { + aOther.mKey = nullptr; + } + + ~nsCharPtrHashKey() + { + if (mKey) { + free(const_cast<char*>(mKey)); + } + } + + const char* GetKey() const { return mKey; } + bool KeyEquals(KeyTypePointer aKey) const { return !strcmp(mKey, aKey); } + + static KeyTypePointer KeyToPointer(KeyType aKey) { return aKey; } + static PLDHashNumber HashKey(KeyTypePointer aKey) + { + return mozilla::HashString(aKey); + } + + size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const + { + return aMallocSizeOf(mKey); + } + + enum { ALLOW_MEMMOVE = true }; + +private: + const char* mKey; +}; + +/** + * hashkey wrapper for const char16_t*; at construction, this class duplicates + * a string pointed to by the pointer so that it doesn't matter whether or not + * the string lives longer than the hash table. + */ +class nsUnicharPtrHashKey : public PLDHashEntryHdr +{ +public: + typedef const char16_t* KeyType; + typedef const char16_t* KeyTypePointer; + + explicit nsUnicharPtrHashKey(const char16_t* aKey) : mKey(NS_strdup(aKey)) {} + nsUnicharPtrHashKey(const nsUnicharPtrHashKey& aToCopy) + : mKey(NS_strdup(aToCopy.mKey)) + { + } + + nsUnicharPtrHashKey(nsUnicharPtrHashKey&& aOther) + : mKey(aOther.mKey) + { + aOther.mKey = nullptr; + } + + ~nsUnicharPtrHashKey() + { + if (mKey) { + NS_Free(const_cast<char16_t*>(mKey)); + } + } + + const char16_t* GetKey() const { return mKey; } + bool KeyEquals(KeyTypePointer aKey) const { return !NS_strcmp(mKey, aKey); } + + static KeyTypePointer KeyToPointer(KeyType aKey) { return aKey; } + static PLDHashNumber HashKey(KeyTypePointer aKey) + { + return mozilla::HashString(aKey); + } + + size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const + { + return aMallocSizeOf(mKey); + } + + enum { ALLOW_MEMMOVE = true }; + +private: + const char16_t* mKey; +}; + +/** + * Hashtable key class to use with objects that support nsIHashable + */ +class nsHashableHashKey : public PLDHashEntryHdr +{ +public: + typedef nsIHashable* KeyType; + typedef const nsIHashable* KeyTypePointer; + + explicit nsHashableHashKey(const nsIHashable* aKey) + : mKey(const_cast<nsIHashable*>(aKey)) + { + } + nsHashableHashKey(const nsHashableHashKey& aToCopy) : mKey(aToCopy.mKey) {} + ~nsHashableHashKey() {} + + nsIHashable* GetKey() const { return mKey; } + + bool KeyEquals(const nsIHashable* aKey) const + { + bool eq; + if (NS_SUCCEEDED(mKey->Equals(const_cast<nsIHashable*>(aKey), &eq))) { + return eq; + } + return false; + } + + static const nsIHashable* KeyToPointer(nsIHashable* aKey) { return aKey; } + static PLDHashNumber HashKey(const nsIHashable* aKey) + { + uint32_t code = 8888; // magic number if GetHashCode fails :-( +#ifdef DEBUG + nsresult rv = +#endif + const_cast<nsIHashable*>(aKey)->GetHashCode(&code); + NS_ASSERTION(NS_SUCCEEDED(rv), "GetHashCode should not throw!"); + return code; + } + + enum { ALLOW_MEMMOVE = true }; + +private: + nsCOMPtr<nsIHashable> mKey; +}; + +namespace mozilla { + +template <typename T> +PLDHashNumber +Hash(const T& aValue) +{ + return aValue.Hash(); +} + +} // namespace mozilla + +/** + * Hashtable key class to use with objects for which Hash() and operator==() + * are defined. + */ +template<typename T> +class nsGenericHashKey : public PLDHashEntryHdr +{ +public: + typedef const T& KeyType; + typedef const T* KeyTypePointer; + + explicit nsGenericHashKey(KeyTypePointer aKey) : mKey(*aKey) {} + nsGenericHashKey(const nsGenericHashKey<T>& aOther) : mKey(aOther.mKey) {} + + KeyType GetKey() const { return mKey; } + bool KeyEquals(KeyTypePointer aKey) const { return *aKey == mKey; } + + static KeyTypePointer KeyToPointer(KeyType aKey) { return &aKey; } + static PLDHashNumber HashKey(KeyTypePointer aKey) { return ::mozilla::Hash(*aKey); } + enum { ALLOW_MEMMOVE = true }; + +private: + T mKey; +}; + +#endif // nsTHashKeys_h__ diff --git a/xpcom/glue/nsIClassInfoImpl.h b/xpcom/glue/nsIClassInfoImpl.h new file mode 100644 index 0000000000..44303f2be9 --- /dev/null +++ b/xpcom/glue/nsIClassInfoImpl.h @@ -0,0 +1,179 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsIClassInfoImpl_h__ +#define nsIClassInfoImpl_h__ + +#include "mozilla/Alignment.h" +#include "mozilla/Assertions.h" +#include "mozilla/MacroArgs.h" +#include "mozilla/MacroForEach.h" +#include "nsIClassInfo.h" +#include "nsISupportsImpl.h" + +#include <new> + +/** + * This header file provides macros which help you make your class implement + * nsIClassInfo. Implementing nsIClassInfo is particularly helpful if you have + * a C++ class which implements multiple interfaces and which you access from + * JavaScript. If that class implements nsIClassInfo, the JavaScript code + * won't have to call QueryInterface on instances of the class; all methods + * from all interfaces returned by GetInterfaces() will be available + * automagically. + * + * Here's all you need to do. Given a class + * + * class nsFooBar : public nsIFoo, public nsIBar { }; + * + * you should already have the following nsISupports implementation in its cpp + * file: + * + * NS_IMPL_ISUPPORTS(nsFooBar, nsIFoo, nsIBar). + * + * Change this to + * + * NS_IMPL_CLASSINFO(nsFooBar, nullptr, 0, NS_FOOBAR_CID) + * NS_IMPL_ISUPPORTS_CI(nsFooBar, nsIFoo, nsIBar) + * + * If nsFooBar is threadsafe, change the 0 above to nsIClassInfo::THREADSAFE. + * If it's a singleton, use nsIClassInfo::SINGLETON. The full list of flags is + * in nsIClassInfo.idl. + * + * The nullptr parameter is there so you can pass a function for converting + * from an XPCOM object to a scriptable helper. Unless you're doing + * specialized JS work, you can probably leave this as nullptr. + * + * This file also defines the NS_IMPL_QUERY_INTERFACE_CI macro, which you can + * use to replace NS_IMPL_QUERY_INTERFACE, if you use that instead of + * NS_IMPL_ISUPPORTS. + * + * That's it! The rest is gory details. + * + * + * Notice that nsFooBar didn't need to inherit from nsIClassInfo in order to + * "implement" it. However, after adding these macros to nsFooBar, you you can + * QueryInterface an instance of nsFooBar to nsIClassInfo. How can this be? + * + * The answer lies in the NS_IMPL_ISUPPORTS_CI macro. It modifies nsFooBar's + * QueryInterface implementation such that, if we ask to QI to nsIClassInfo, it + * returns a singleton object associated with the class. (That singleton is + * defined by NS_IMPL_CLASSINFO.) So all nsFooBar instances will return the + * same object when QI'ed to nsIClassInfo. (You can see this in + * NS_IMPL_QUERY_CLASSINFO below.) + * + * This hack breaks XPCOM's rules, since if you take an instance of nsFooBar, + * QI it to nsIClassInfo, and then try to QI to nsIFoo, that will fail. On the + * upside, implementing nsIClassInfo doesn't add a vtable pointer to instances + * of your class. + * + * In principal, you can also implement nsIClassInfo by inheriting from the + * interface. But some code expects that when it QI's an object to + * nsIClassInfo, it gets back a singleton which isn't attached to any + * particular object. If a class were to implement nsIClassInfo through + * inheritance, that code might QI to nsIClassInfo and keep the resulting + * object alive, thinking it was only keeping alive the classinfo singleton, + * but in fact keeping a whole instance of the class alive. See, e.g., bug + * 658632. + * + * Unless you specifically need to have a different nsIClassInfo instance for + * each instance of your class, you should probably just implement nsIClassInfo + * as a singleton. + */ + +class GenericClassInfo : public nsIClassInfo +{ +public: + struct ClassInfoData + { + // This function pointer uses NS_CALLBACK because it's always set to an + // NS_IMETHOD function, which uses __stdcall on Win32. + typedef NS_CALLBACK(GetInterfacesProc)(uint32_t* aCountP, nsIID*** aArray); + GetInterfacesProc getinterfaces; + + // This function pointer doesn't use NS_CALLBACK because it's always set to + // a vanilla function. + typedef nsresult (*GetScriptableHelperProc)(nsIXPCScriptable** aHelper); + GetScriptableHelperProc getscriptablehelper; + + uint32_t flags; + nsCID cid; + }; + + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSICLASSINFO + + explicit GenericClassInfo(const ClassInfoData* aData) : mData(aData) {} + +private: + const ClassInfoData* mData; +}; + +#define NS_CLASSINFO_NAME(_class) g##_class##_classInfoGlobal +#define NS_CI_INTERFACE_GETTER_NAME(_class) _class##_GetInterfacesHelper +#define NS_DECL_CI_INTERFACE_GETTER(_class) \ + extern NS_IMETHODIMP NS_CI_INTERFACE_GETTER_NAME(_class) \ + (uint32_t *, nsIID ***); + +#define NS_IMPL_CLASSINFO(_class, _getscriptablehelper, _flags, _cid) \ + NS_DECL_CI_INTERFACE_GETTER(_class) \ + static const GenericClassInfo::ClassInfoData k##_class##ClassInfoData = { \ + NS_CI_INTERFACE_GETTER_NAME(_class), \ + _getscriptablehelper, \ + _flags | nsIClassInfo::SINGLETON_CLASSINFO, \ + _cid, \ + }; \ + mozilla::AlignedStorage2<GenericClassInfo> k##_class##ClassInfoDataPlace; \ + nsIClassInfo* NS_CLASSINFO_NAME(_class) = nullptr; + +#define NS_IMPL_QUERY_CLASSINFO(_class) \ + if ( aIID.Equals(NS_GET_IID(nsIClassInfo)) ) { \ + if (!NS_CLASSINFO_NAME(_class)) \ + NS_CLASSINFO_NAME(_class) = new (k##_class##ClassInfoDataPlace.addr()) \ + GenericClassInfo(&k##_class##ClassInfoData); \ + foundInterface = NS_CLASSINFO_NAME(_class); \ + } else + +#define NS_CLASSINFO_HELPER_BEGIN(_class, _c) \ +NS_IMETHODIMP \ +NS_CI_INTERFACE_GETTER_NAME(_class)(uint32_t *count, nsIID ***array) \ +{ \ + *count = _c; \ + *array = (nsIID **)moz_xmalloc(sizeof (nsIID *) * _c); \ + uint32_t i = 0; + +#define NS_CLASSINFO_HELPER_ENTRY(_interface) \ + (*array)[i++] = (nsIID*)nsMemory::Clone(&NS_GET_IID(_interface), \ + sizeof(nsIID)); + +#define NS_CLASSINFO_HELPER_END \ + MOZ_ASSERT(i == *count, "Incorrent number of entries"); \ + return NS_OK; \ +} + +#define NS_IMPL_CI_INTERFACE_GETTER(aClass, ...) \ + MOZ_STATIC_ASSERT_VALID_ARG_COUNT(__VA_ARGS__); \ + NS_CLASSINFO_HELPER_BEGIN(aClass, \ + MOZ_PASTE_PREFIX_AND_ARG_COUNT(/* No prefix */, \ + __VA_ARGS__)) \ + MOZ_FOR_EACH(NS_CLASSINFO_HELPER_ENTRY, (), (__VA_ARGS__)) \ + NS_CLASSINFO_HELPER_END + +#define NS_IMPL_QUERY_INTERFACE_CI(aClass, ...) \ + MOZ_STATIC_ASSERT_VALID_ARG_COUNT(__VA_ARGS__); \ + NS_INTERFACE_MAP_BEGIN(aClass) \ + MOZ_FOR_EACH(NS_INTERFACE_MAP_ENTRY, (), (__VA_ARGS__)) \ + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, MOZ_ARG_1(__VA_ARGS__)) \ + NS_IMPL_QUERY_CLASSINFO(aClass) \ + NS_INTERFACE_MAP_END + +#define NS_IMPL_ISUPPORTS_CI(aClass, ...) \ + NS_IMPL_ADDREF(aClass) \ + NS_IMPL_RELEASE(aClass) \ + NS_IMPL_QUERY_INTERFACE_CI(aClass, __VA_ARGS__) \ + NS_IMPL_CI_INTERFACE_GETTER(aClass, __VA_ARGS__) + +#endif // nsIClassInfoImpl_h__ diff --git a/xpcom/glue/nsID.cpp b/xpcom/glue/nsID.cpp new file mode 100644 index 0000000000..0a73bf6c3f --- /dev/null +++ b/xpcom/glue/nsID.cpp @@ -0,0 +1,133 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsID.h" +#include "nsMemory.h" +#include "mozilla/Sprintf.h" + +void nsID::Clear() +{ + m0 = 0; + m1 = 0; + m2 = 0; + for (int i = 0; i < 8; ++i) { + m3[i] = 0; + } +} + +/** + * Multiplies the_int_var with 16 (0x10) and adds the value of the + * hexadecimal digit the_char. If it fails it returns false from + * the function it's used in. + */ + +#define ADD_HEX_CHAR_TO_INT_OR_RETURN_FALSE(the_char, the_int_var) \ + the_int_var = (the_int_var << 4) + the_char; \ + if(the_char >= '0' && the_char <= '9') the_int_var -= '0'; \ + else if(the_char >= 'a' && the_char <= 'f') the_int_var -= 'a'-10; \ + else if(the_char >= 'A' && the_char <= 'F') the_int_var -= 'A'-10; \ + else return false + + +/** + * Parses number_of_chars characters from the char_pointer pointer and + * puts the number in the dest_variable. The pointer is moved to point + * at the first character after the parsed ones. If it fails it returns + * false from the function the macro is used in. + */ + +#define PARSE_CHARS_TO_NUM(char_pointer, dest_variable, number_of_chars) \ + do { int32_t _i=number_of_chars; \ + dest_variable = 0; \ + while(_i) { \ + ADD_HEX_CHAR_TO_INT_OR_RETURN_FALSE(*char_pointer, dest_variable); \ + char_pointer++; \ + _i--; \ + } } while(0) + + +/** + * Parses a hyphen from the char_pointer string. If there is no hyphen there + * the function returns false from the function it's used in. The + * char_pointer is advanced one step. + */ + +#define PARSE_HYPHEN(char_pointer) if (*(char_pointer++) != '-') return false + +/* + * Turns a {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx} string into + * an nsID. It can also handle the old format without the { and }. + */ + +bool +nsID::Parse(const char* aIDStr) +{ + /* Optimized for speed */ + if (!aIDStr) { + return false; + } + + bool expectFormat1 = (aIDStr[0] == '{'); + if (expectFormat1) { + ++aIDStr; + } + + PARSE_CHARS_TO_NUM(aIDStr, m0, 8); + PARSE_HYPHEN(aIDStr); + PARSE_CHARS_TO_NUM(aIDStr, m1, 4); + PARSE_HYPHEN(aIDStr); + PARSE_CHARS_TO_NUM(aIDStr, m2, 4); + PARSE_HYPHEN(aIDStr); + int i; + for (i = 0; i < 2; ++i) { + PARSE_CHARS_TO_NUM(aIDStr, m3[i], 2); + } + PARSE_HYPHEN(aIDStr); + while (i < 8) { + PARSE_CHARS_TO_NUM(aIDStr, m3[i], 2); + i++; + } + + return expectFormat1 ? *aIDStr == '}' : true; +} + +#ifndef XPCOM_GLUE_AVOID_NSPR + +static const char gIDFormat[] = + "{%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x}"; + +/* + * Returns an allocated string in {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx} + * format. The string is allocated with NS_Alloc and should be freed by + * the caller. + */ + +char* +nsID::ToString() const +{ + char* res = (char*)NS_Alloc(NSID_LENGTH); + + if (res) { + snprintf(res, NSID_LENGTH, gIDFormat, + m0, (uint32_t)m1, (uint32_t)m2, + (uint32_t)m3[0], (uint32_t)m3[1], (uint32_t)m3[2], + (uint32_t)m3[3], (uint32_t)m3[4], (uint32_t)m3[5], + (uint32_t)m3[6], (uint32_t)m3[7]); + } + return res; +} + +void +nsID::ToProvidedString(char (&aDest)[NSID_LENGTH]) const +{ + SprintfLiteral(aDest, gIDFormat, + m0, (uint32_t)m1, (uint32_t)m2, + (uint32_t)m3[0], (uint32_t)m3[1], (uint32_t)m3[2], + (uint32_t)m3[3], (uint32_t)m3[4], (uint32_t)m3[5], + (uint32_t)m3[6], (uint32_t)m3[7]); +} + +#endif // XPCOM_GLUE_AVOID_NSPR diff --git a/xpcom/glue/nsID.h b/xpcom/glue/nsID.h new file mode 100644 index 0000000000..c04007d4e9 --- /dev/null +++ b/xpcom/glue/nsID.h @@ -0,0 +1,179 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsID_h__ +#define nsID_h__ + +#include <string.h> + +#include "nscore.h" + +#define NSID_LENGTH 39 + +/** + * A "unique identifier". This is modeled after OSF DCE UUIDs. + */ + +struct nsID +{ + /** + * @name Identifier values + */ + + //@{ + uint32_t m0; + uint16_t m1; + uint16_t m2; + uint8_t m3[8]; + //@} + + /** + * @name Methods + */ + + //@{ + /** + * Ensures everything is zeroed out. + */ + void Clear(); + + /** + * Equivalency method. Compares this nsID with another. + * @return <b>true</b> if they are the same, <b>false</b> if not. + */ + + inline bool Equals(const nsID& aOther) const + { + // Unfortunately memcmp isn't faster than this. + return + (((uint32_t*)&m0)[0] == ((uint32_t*)&aOther.m0)[0]) && + (((uint32_t*)&m0)[1] == ((uint32_t*)&aOther.m0)[1]) && + (((uint32_t*)&m0)[2] == ((uint32_t*)&aOther.m0)[2]) && + (((uint32_t*)&m0)[3] == ((uint32_t*)&aOther.m0)[3]); + } + + inline bool operator==(const nsID& aOther) const + { + return Equals(aOther); + } + + /** + * nsID Parsing method. Turns a {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx} + * string into an nsID + */ + bool Parse(const char* aIDStr); + +#ifndef XPCOM_GLUE_AVOID_NSPR + /** + * nsID string encoder. Returns an allocated string in + * {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx} format. Caller should free string. + * YOU SHOULD ONLY USE THIS IF YOU CANNOT USE ToProvidedString() BELOW. + */ + char* ToString() const; + + /** + * nsID string encoder. Builds a string in + * {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx} format, into a char[NSID_LENGTH] + * buffer provided by the caller (for instance, on the stack). + */ + void ToProvidedString(char (&aDest)[NSID_LENGTH]) const; + +#endif // XPCOM_GLUE_AVOID_NSPR + + //@} +}; + +#ifndef XPCOM_GLUE_AVOID_NSPR +/** + * A stack helper class to convert a nsID to a string. Useful + * for printing nsIDs. For example: + * nsID aID = ...; + * printf("%s", nsIDToCString(aID).get()); + */ +class nsIDToCString +{ +public: + explicit nsIDToCString(const nsID& aID) + { + aID.ToProvidedString(mStringBytes); + } + + const char *get() const + { + return mStringBytes; + } + +protected: + char mStringBytes[NSID_LENGTH]; +}; +#endif + +/* + * Class IDs + */ + +typedef nsID nsCID; + +// Define an CID +#define NS_DEFINE_CID(_name, _cidspec) \ + const nsCID _name = _cidspec + +#define NS_DEFINE_NAMED_CID(_name) \ + static const nsCID k##_name = _name + +#define REFNSCID const nsCID& + +/** + * An "interface id" which can be used to uniquely identify a given + * interface. + */ + +typedef nsID nsIID; + +/** + * A macro shorthand for <tt>const nsIID&<tt> + */ + +#define REFNSIID const nsIID& + +/** + * Define an IID + * obsolete - do not use this macro + */ + +#define NS_DEFINE_IID(_name, _iidspec) \ + const nsIID _name = _iidspec + +/** + * A macro to build the static const IID accessor method. The Dummy + * template parameter only exists so that the kIID symbol will be linked + * properly (weak symbol on linux, gnu_linkonce on mac, multiple-definitions + * merged on windows). Dummy should always be instantiated as "void". + */ + +#define NS_DECLARE_STATIC_IID_ACCESSOR(the_iid) \ + template<typename T, typename U> \ + struct COMTypeInfo; + +#define NS_DEFINE_STATIC_IID_ACCESSOR(the_interface, the_iid) \ + template<typename T> \ + struct the_interface::COMTypeInfo<the_interface, T> { \ + static const nsIID kIID NS_HIDDEN; \ + }; \ + template<typename T> \ + const nsIID the_interface::COMTypeInfo<the_interface, T>::kIID NS_HIDDEN = the_iid; + +/** + * A macro to build the static const CID accessor method + */ + +#define NS_DEFINE_STATIC_CID_ACCESSOR(the_cid) \ + static const nsID& GetCID() {static const nsID cid = the_cid; return cid;} + +#define NS_GET_IID(T) (T::COMTypeInfo<T, void>::kIID) +#define NS_GET_TEMPLATE_IID(T) (T::template COMTypeInfo<T, void>::kIID) + +#endif diff --git a/xpcom/glue/nsIInterfaceRequestorUtils.cpp b/xpcom/glue/nsIInterfaceRequestorUtils.cpp new file mode 100644 index 0000000000..8a2dfe3f85 --- /dev/null +++ b/xpcom/glue/nsIInterfaceRequestorUtils.cpp @@ -0,0 +1,33 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsIInterfaceRequestor.h" +#include "nsIInterfaceRequestorUtils.h" + +nsresult +nsGetInterface::operator()(const nsIID& aIID, void** aInstancePtr) const +{ + nsresult status; + + if (mSource) { + nsCOMPtr<nsIInterfaceRequestor> factoryPtr = do_QueryInterface(mSource); + if (factoryPtr) { + status = factoryPtr->GetInterface(aIID, aInstancePtr); + } else { + status = NS_ERROR_NO_INTERFACE; + } + } else { + status = NS_ERROR_NULL_POINTER; + } + + if (NS_FAILED(status)) { + *aInstancePtr = 0; + } + if (mErrorPtr) { + *mErrorPtr = status; + } + return status; +} diff --git a/xpcom/glue/nsIInterfaceRequestorUtils.h b/xpcom/glue/nsIInterfaceRequestorUtils.h new file mode 100644 index 0000000000..718cf387b5 --- /dev/null +++ b/xpcom/glue/nsIInterfaceRequestorUtils.h @@ -0,0 +1,49 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef __nsInterfaceRequestorUtils_h +#define __nsInterfaceRequestorUtils_h + +#include "nsCOMPtr.h" + +// a type-safe shortcut for calling the |GetInterface()| member function +// T must inherit from nsIInterfaceRequestor, but the cast may be ambiguous. +template<class T, class DestinationType> +inline nsresult +CallGetInterface(T* aSource, DestinationType** aDestination) +{ + NS_PRECONDITION(aSource, "null parameter"); + NS_PRECONDITION(aDestination, "null parameter"); + + return aSource->GetInterface(NS_GET_TEMPLATE_IID(DestinationType), + reinterpret_cast<void**>(aDestination)); +} + +class MOZ_STACK_CLASS nsGetInterface final : public nsCOMPtr_helper +{ +public: + nsGetInterface(nsISupports* aSource, nsresult* aError) + : mSource(aSource) + , mErrorPtr(aError) + { + } + + virtual nsresult NS_FASTCALL operator()(const nsIID&, void**) const + override; + +private: + nsISupports* MOZ_NON_OWNING_REF mSource; + nsresult* mErrorPtr; +}; + +inline const nsGetInterface +do_GetInterface(nsISupports* aSource, nsresult* aError = 0) +{ + return nsGetInterface(aSource, aError); +} + +#endif // __nsInterfaceRequestorUtils_h + diff --git a/xpcom/glue/nsINIParser.cpp b/xpcom/glue/nsINIParser.cpp new file mode 100644 index 0000000000..53eae72929 --- /dev/null +++ b/xpcom/glue/nsINIParser.cpp @@ -0,0 +1,331 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// Moz headers (alphabetical) +#include "nsCRTGlue.h" +#include "nsError.h" +#include "nsIFile.h" +#include "nsINIParser.h" +#include "mozilla/FileUtils.h" // AutoFILE + +// System headers (alphabetical) +#include <stdio.h> +#include <stdlib.h> +#ifdef XP_WIN +#include <windows.h> +#endif + +#if defined(XP_WIN) +#define READ_BINARYMODE L"rb" +#else +#define READ_BINARYMODE "r" +#endif + +using namespace mozilla; + +#ifdef XP_WIN +inline FILE* +TS_tfopen(const char* aPath, const wchar_t* aMode) +{ + wchar_t wPath[MAX_PATH]; + MultiByteToWideChar(CP_UTF8, 0, aPath, -1, wPath, MAX_PATH); + return _wfopen(wPath, aMode); +} +#else +inline FILE* +TS_tfopen(const char* aPath, const char* aMode) +{ + return fopen(aPath, aMode); +} +#endif + +// Stack based FILE wrapper to ensure that fclose is called, copied from +// toolkit/mozapps/update/updater/readstrings.cpp + +class AutoFILE +{ +public: + explicit AutoFILE(FILE* aFp = nullptr) : fp_(aFp) {} + ~AutoFILE() + { + if (fp_) { + fclose(fp_); + } + } + operator FILE*() { return fp_; } + FILE** operator&() { return &fp_; } + void operator=(FILE* aFp) { fp_ = aFp; } +private: + FILE* fp_; +}; + +nsresult +nsINIParser::Init(nsIFile* aFile) +{ + /* open the file. Don't use OpenANSIFileDesc, because you mustn't + pass FILE* across shared library boundaries, which may be using + different CRTs */ + + AutoFILE fd; + +#ifdef XP_WIN + nsAutoString path; + nsresult rv = aFile->GetPath(path); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + fd = _wfopen(path.get(), READ_BINARYMODE); +#else + nsAutoCString path; + aFile->GetNativePath(path); + + fd = fopen(path.get(), READ_BINARYMODE); +#endif + + if (!fd) { + return NS_ERROR_FAILURE; + } + + return InitFromFILE(fd); +} + +nsresult +nsINIParser::Init(const char* aPath) +{ + /* open the file */ + AutoFILE fd(TS_tfopen(aPath, READ_BINARYMODE)); + if (!fd) { + return NS_ERROR_FAILURE; + } + + return InitFromFILE(fd); +} + +static const char kNL[] = "\r\n"; +static const char kEquals[] = "="; +static const char kWhitespace[] = " \t"; +static const char kRBracket[] = "]"; + +nsresult +nsINIParser::InitFromFILE(FILE* aFd) +{ + /* get file size */ + if (fseek(aFd, 0, SEEK_END) != 0) { + return NS_ERROR_FAILURE; + } + + long flen = ftell(aFd); + /* zero-sized file, or an error */ + if (flen <= 0) { + return NS_ERROR_FAILURE; + } + + /* malloc an internal buf the size of the file */ + mFileContents = MakeUnique<char[]>(flen + 2); + if (!mFileContents) { + return NS_ERROR_OUT_OF_MEMORY; + } + + /* read the file in one swoop */ + if (fseek(aFd, 0, SEEK_SET) != 0) { + return NS_BASE_STREAM_OSERROR; + } + + int rd = fread(mFileContents.get(), sizeof(char), flen, aFd); + if (rd != flen) { + return NS_BASE_STREAM_OSERROR; + } + + // We write a UTF16 null so that the file is easier to convert to UTF8 + mFileContents[flen] = mFileContents[flen + 1] = '\0'; + + char* buffer = &mFileContents[0]; + + if (flen >= 3 && + mFileContents[0] == '\xEF' && + mFileContents[1] == '\xBB' && + mFileContents[2] == '\xBF') { + // Someone set us up the Utf-8 BOM + // This case is easy, since we assume that BOM-less + // files are Utf-8 anyway. Just skip the BOM and process as usual. + buffer = &mFileContents[3]; + } + +#ifdef XP_WIN + if (flen >= 2 && + mFileContents[0] == '\xFF' && + mFileContents[1] == '\xFE') { + // Someone set us up the Utf-16LE BOM + buffer = &mFileContents[2]; + // Get the size required for our Utf8 buffer + flen = WideCharToMultiByte(CP_UTF8, + 0, + reinterpret_cast<LPWSTR>(buffer), + -1, + nullptr, + 0, + nullptr, + nullptr); + if (flen == 0) { + return NS_ERROR_FAILURE; + } + + UniquePtr<char[]> utf8Buffer(new char[flen]); + if (WideCharToMultiByte(CP_UTF8, 0, reinterpret_cast<LPWSTR>(buffer), -1, + utf8Buffer.get(), flen, nullptr, nullptr) == 0) { + return NS_ERROR_FAILURE; + } + mFileContents = Move(utf8Buffer); + buffer = mFileContents.get(); + } +#endif + + char* currSection = nullptr; + + // outer loop tokenizes into lines + while (char* token = NS_strtok(kNL, &buffer)) { + if (token[0] == '#' || token[0] == ';') { // it's a comment + continue; + } + + token = (char*)NS_strspnp(kWhitespace, token); + if (!*token) { // empty line + continue; + } + + if (token[0] == '[') { // section header! + ++token; + currSection = token; + + char* rb = NS_strtok(kRBracket, &token); + if (!rb || NS_strtok(kWhitespace, &token)) { + // there's either an unclosed [Section or a [Section]Moretext! + // we could frankly decide that this INI file is malformed right + // here and stop, but we won't... keep going, looking for + // a well-formed [section] to continue working with + currSection = nullptr; + } + + continue; + } + + if (!currSection) { + // If we haven't found a section header (or we found a malformed + // section header), don't bother parsing this line. + continue; + } + + char* key = token; + char* e = NS_strtok(kEquals, &token); + if (!e || !token) { + continue; + } + + INIValue* v; + if (!mSections.Get(currSection, &v)) { + v = new INIValue(key, token); + if (!v) { + return NS_ERROR_OUT_OF_MEMORY; + } + + mSections.Put(currSection, v); + continue; + } + + // Check whether this key has already been specified; overwrite + // if so, or append if not. + while (v) { + if (!strcmp(key, v->key)) { + v->value = token; + break; + } + if (!v->next) { + v->next = MakeUnique<INIValue>(key, token); + if (!v->next) { + return NS_ERROR_OUT_OF_MEMORY; + } + break; + } + v = v->next.get(); + } + NS_ASSERTION(v, "v should never be null coming out of this loop"); + } + + return NS_OK; +} + +nsresult +nsINIParser::GetString(const char* aSection, const char* aKey, + nsACString& aResult) +{ + INIValue* val; + mSections.Get(aSection, &val); + + while (val) { + if (strcmp(val->key, aKey) == 0) { + aResult.Assign(val->value); + return NS_OK; + } + + val = val->next.get(); + } + + return NS_ERROR_FAILURE; +} + +nsresult +nsINIParser::GetString(const char* aSection, const char* aKey, + char* aResult, uint32_t aResultLen) +{ + INIValue* val; + mSections.Get(aSection, &val); + + while (val) { + if (strcmp(val->key, aKey) == 0) { + strncpy(aResult, val->value, aResultLen); + aResult[aResultLen - 1] = '\0'; + if (strlen(val->value) >= aResultLen) { + return NS_ERROR_LOSS_OF_SIGNIFICANT_DATA; + } + + return NS_OK; + } + + val = val->next.get(); + } + + return NS_ERROR_FAILURE; +} + +nsresult +nsINIParser::GetSections(INISectionCallback aCB, void* aClosure) +{ + for (auto iter = mSections.Iter(); !iter.Done(); iter.Next()) { + if (!aCB(iter.Key(), aClosure)) { + break; + } + } + return NS_OK; +} + +nsresult +nsINIParser::GetStrings(const char* aSection, + INIStringCallback aCB, void* aClosure) +{ + INIValue* val; + + for (mSections.Get(aSection, &val); + val; + val = val->next.get()) { + + if (!aCB(val->key, val->value, aClosure)) { + return NS_OK; + } + } + + return NS_OK; +} diff --git a/xpcom/glue/nsINIParser.h b/xpcom/glue/nsINIParser.h new file mode 100644 index 0000000000..d0f553d49d --- /dev/null +++ b/xpcom/glue/nsINIParser.h @@ -0,0 +1,118 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// This file was shamelessly copied from mozilla/xpinstall/wizard/unix/src2 + +#ifndef nsINIParser_h__ +#define nsINIParser_h__ + +#ifdef MOZILLA_INTERNAL_API +#define nsINIParser nsINIParser_internal +#endif + +#include "nscore.h" +#include "nsClassHashtable.h" +#include "mozilla/UniquePtr.h" + +#include <stdio.h> + +class nsIFile; + +class nsINIParser +{ +public: + nsINIParser() {} + ~nsINIParser() {} + + /** + * Initialize the INIParser with a nsIFile. If this method fails, no + * other methods should be called. This method reads and parses the file, + * the class does not hold a file handle open. An instance must only be + * initialized once. + */ + nsresult Init(nsIFile* aFile); + + /** + * Initialize the INIParser with a file path. If this method fails, no + * other methods should be called. This method reads and parses the file, + * the class does not hold a file handle open. An instance must only + * be initialized once. + */ + nsresult Init(const char* aPath); + + /** + * Callback for GetSections + * @return false to stop enumeration, or true to continue. + */ + typedef bool (*INISectionCallback)(const char* aSection, void* aClosure); + + /** + * Enumerate the sections within the INI file. + */ + nsresult GetSections(INISectionCallback aCB, void* aClosure); + + /** + * Callback for GetStrings + * @return false to stop enumeration, or true to continue + */ + typedef bool (*INIStringCallback)(const char* aString, const char* aValue, + void* aClosure); + + /** + * Enumerate the strings within a section. If the section does + * not exist, this function will silently return. + */ + nsresult GetStrings(const char* aSection, + INIStringCallback aCB, void* aClosure); + + /** + * Get the value of the specified key in the specified section + * of the INI file represented by this instance. + * + * @param aSection section name + * @param aKey key name + * @param aResult the value found + * @throws NS_ERROR_FAILURE if the specified section/key could not be + * found. + */ + nsresult GetString(const char* aSection, const char* aKey, + nsACString& aResult); + + /** + * Alternate signature of GetString that uses a pre-allocated buffer + * instead of a nsACString (for use in the standalone glue before + * the glue is initialized). + * + * @throws NS_ERROR_LOSS_OF_SIGNIFICANT_DATA if the aResult buffer is not + * large enough for the data. aResult will be filled with as + * much data as possible. + * + * @see GetString [1] + */ + nsresult GetString(const char* aSection, const char* aKey, + char* aResult, uint32_t aResultLen); + +private: + struct INIValue + { + INIValue(const char* aKey, const char* aValue) + : key(aKey) + , value(aValue) + { + } + + const char* key; + const char* value; + mozilla::UniquePtr<INIValue> next; + }; + + nsClassHashtable<nsDepCharHashKey, INIValue> mSections; + mozilla::UniquePtr<char[]> mFileContents; + + nsresult InitFromFILE(FILE* aFd); +}; + +#endif /* nsINIParser_h__ */ diff --git a/xpcom/glue/nsISupportsImpl.cpp b/xpcom/glue/nsISupportsImpl.cpp new file mode 100644 index 0000000000..c60c0bfa73 --- /dev/null +++ b/xpcom/glue/nsISupportsImpl.cpp @@ -0,0 +1,27 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupportsImpl.h" + +nsresult NS_FASTCALL +NS_TableDrivenQI(void* aThis, REFNSIID aIID, void** aInstancePtr, + const QITableEntry* aEntries) +{ + do { + if (aIID.Equals(*aEntries->iid)) { + nsISupports* r = reinterpret_cast<nsISupports*>( + reinterpret_cast<char*>(aThis) + aEntries->offset); + NS_ADDREF(r); + *aInstancePtr = r; + return NS_OK; + } + + ++aEntries; + } while (aEntries->iid); + + *aInstancePtr = nullptr; + return NS_ERROR_NO_INTERFACE; +} diff --git a/xpcom/glue/nsISupportsImpl.h b/xpcom/glue/nsISupportsImpl.h new file mode 100644 index 0000000000..26db3c525d --- /dev/null +++ b/xpcom/glue/nsISupportsImpl.h @@ -0,0 +1,1090 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +// IWYU pragma: private, include "nsISupports.h" + + +#ifndef nsISupportsImpl_h__ +#define nsISupportsImpl_h__ + +#include "nscore.h" +#include "nsISupportsBase.h" +#include "nsISupportsUtils.h" + + +#if !defined(XPCOM_GLUE_AVOID_NSPR) +#include "prthread.h" /* needed for thread-safety checks */ +#endif // !XPCOM_GLUE_AVOID_NSPR + +#include "nsDebug.h" +#include "nsXPCOM.h" +#include "mozilla/Atomics.h" +#include "mozilla/Attributes.h" +#include "mozilla/Assertions.h" +#include "mozilla/Compiler.h" +#include "mozilla/Likely.h" +#include "mozilla/MacroArgs.h" +#include "mozilla/MacroForEach.h" +#include "mozilla/TypeTraits.h" + +#define MOZ_ASSERT_TYPE_OK_FOR_REFCOUNTING(X) \ + static_assert(!mozilla::IsDestructible<X>::value, \ + "Reference-counted class " #X " should not have a public destructor. " \ + "Make this class's destructor non-public"); + +inline nsISupports* +ToSupports(nsISupports* aSupports) +{ + return aSupports; +} + +inline nsISupports* +ToCanonicalSupports(nsISupports* aSupports) +{ + return nullptr; +} + +//////////////////////////////////////////////////////////////////////////////// +// Macros to help detect thread-safety: + +#ifdef MOZ_THREAD_SAFETY_OWNERSHIP_CHECKS_SUPPORTED + +class nsAutoOwningThread +{ +public: + nsAutoOwningThread() { mThread = PR_GetCurrentThread(); } + void* GetThread() const { return mThread; } + +private: + void* mThread; +}; + +#define NS_DECL_OWNINGTHREAD nsAutoOwningThread _mOwningThread; +#define NS_ASSERT_OWNINGTHREAD_AGGREGATE(agg, _class) \ + NS_CheckThreadSafe(agg->_mOwningThread.GetThread(), #_class " not thread-safe") +#define NS_ASSERT_OWNINGTHREAD(_class) NS_ASSERT_OWNINGTHREAD_AGGREGATE(this, _class) +#else // !DEBUG && !(NIGHTLY_BUILD && !MOZ_PROFILING) + +#define NS_DECL_OWNINGTHREAD /* nothing */ +#define NS_ASSERT_OWNINGTHREAD_AGGREGATE(agg, _class) ((void)0) +#define NS_ASSERT_OWNINGTHREAD(_class) ((void)0) + +#endif // DEBUG || (NIGHTLY_BUILD && !MOZ_PROFILING) + + +// Macros for reference-count and constructor logging + +#if defined(NS_BUILD_REFCNT_LOGGING) + +#define NS_LOG_ADDREF(_p, _rc, _type, _size) \ + NS_LogAddRef((_p), (_rc), (_type), (uint32_t) (_size)) + +#define NS_LOG_RELEASE(_p, _rc, _type) \ + NS_LogRelease((_p), (_rc), (_type)) + +#include "mozilla/TypeTraits.h" +#define MOZ_ASSERT_CLASSNAME(_type) \ + static_assert(mozilla::IsClass<_type>::value, \ + "Token '" #_type "' is not a class type.") + +// Note that the following constructor/destructor logging macros are redundant +// for refcounted objects that log via the NS_LOG_ADDREF/NS_LOG_RELEASE macros. +// Refcount logging is preferred. +#define MOZ_COUNT_CTOR(_type) \ +do { \ + MOZ_ASSERT_CLASSNAME(_type); \ + NS_LogCtor((void*)this, #_type, sizeof(*this)); \ +} while (0) + +#define MOZ_COUNT_CTOR_INHERITED(_type, _base) \ +do { \ + MOZ_ASSERT_CLASSNAME(_type); \ + MOZ_ASSERT_CLASSNAME(_base); \ + NS_LogCtor((void*)this, #_type, sizeof(*this) - sizeof(_base)); \ +} while (0) + +#define MOZ_LOG_CTOR(_ptr, _name, _size) \ +do { \ + NS_LogCtor((void*)_ptr, _name, _size); \ +} while (0) + +#define MOZ_COUNT_DTOR(_type) \ +do { \ + MOZ_ASSERT_CLASSNAME(_type); \ + NS_LogDtor((void*)this, #_type, sizeof(*this)); \ +} while (0) + +#define MOZ_COUNT_DTOR_INHERITED(_type, _base) \ +do { \ + MOZ_ASSERT_CLASSNAME(_type); \ + MOZ_ASSERT_CLASSNAME(_base); \ + NS_LogDtor((void*)this, #_type, sizeof(*this) - sizeof(_base)); \ +} while (0) + +#define MOZ_LOG_DTOR(_ptr, _name, _size) \ +do { \ + NS_LogDtor((void*)_ptr, _name, _size); \ +} while (0) + +/* nsCOMPtr.h allows these macros to be defined by clients + * These logging functions require dynamic_cast<void*>, so they don't + * do anything useful if we don't have dynamic_cast<void*>. + * Note: The explicit comparison to nullptr is needed to avoid warnings + * when _p is a nullptr itself. */ +#define NSCAP_LOG_ASSIGNMENT(_c, _p) \ + if (_p != nullptr) \ + NS_LogCOMPtrAddRef((_c),static_cast<nsISupports*>(_p)) + +#define NSCAP_LOG_RELEASE(_c, _p) \ + if (_p) \ + NS_LogCOMPtrRelease((_c), static_cast<nsISupports*>(_p)) + +#else /* !NS_BUILD_REFCNT_LOGGING */ + +#define NS_LOG_ADDREF(_p, _rc, _type, _size) +#define NS_LOG_RELEASE(_p, _rc, _type) +#define MOZ_COUNT_CTOR(_type) +#define MOZ_COUNT_CTOR_INHERITED(_type, _base) +#define MOZ_LOG_CTOR(_ptr, _name, _size) +#define MOZ_COUNT_DTOR(_type) +#define MOZ_COUNT_DTOR_INHERITED(_type, _base) +#define MOZ_LOG_DTOR(_ptr, _name, _size) + +#endif /* NS_BUILD_REFCNT_LOGGING */ + + +// Support for ISupports classes which interact with cycle collector. + +#define NS_NUMBER_OF_FLAGS_IN_REFCNT 2 +#define NS_IN_PURPLE_BUFFER (1 << 0) +#define NS_IS_PURPLE (1 << 1) +#define NS_REFCOUNT_CHANGE (1 << NS_NUMBER_OF_FLAGS_IN_REFCNT) +#define NS_REFCOUNT_VALUE(_val) (_val >> NS_NUMBER_OF_FLAGS_IN_REFCNT) + +class nsCycleCollectingAutoRefCnt +{ +public: + nsCycleCollectingAutoRefCnt() : mRefCntAndFlags(0) {} + + explicit nsCycleCollectingAutoRefCnt(uintptr_t aValue) + : mRefCntAndFlags(aValue << NS_NUMBER_OF_FLAGS_IN_REFCNT) + { + } + + nsCycleCollectingAutoRefCnt(const nsCycleCollectingAutoRefCnt&) = delete; + void operator=(const nsCycleCollectingAutoRefCnt&) = delete; + + MOZ_ALWAYS_INLINE uintptr_t incr(nsISupports* aOwner) + { + return incr(aOwner, nullptr); + } + + MOZ_ALWAYS_INLINE uintptr_t incr(void* aOwner, + nsCycleCollectionParticipant* aCp) + { + mRefCntAndFlags += NS_REFCOUNT_CHANGE; + mRefCntAndFlags &= ~NS_IS_PURPLE; + // For incremental cycle collection, use the purple buffer to track objects + // that have been AddRef'd. + if (!IsInPurpleBuffer()) { + mRefCntAndFlags |= NS_IN_PURPLE_BUFFER; + // Refcount isn't zero, so Suspect won't delete anything. + MOZ_ASSERT(get() > 0); + NS_CycleCollectorSuspect3(aOwner, aCp, this, nullptr); + } + return NS_REFCOUNT_VALUE(mRefCntAndFlags); + } + + MOZ_ALWAYS_INLINE void stabilizeForDeletion() + { + // Set refcnt to 1 and mark us to be in the purple buffer. + // This way decr won't call suspect again. + mRefCntAndFlags = NS_REFCOUNT_CHANGE | NS_IN_PURPLE_BUFFER; + } + + MOZ_ALWAYS_INLINE uintptr_t decr(nsISupports* aOwner, + bool* aShouldDelete = nullptr) + { + return decr(aOwner, nullptr, aShouldDelete); + } + + MOZ_ALWAYS_INLINE uintptr_t decr(void* aOwner, + nsCycleCollectionParticipant* aCp, + bool* aShouldDelete = nullptr) + { + MOZ_ASSERT(get() > 0); + if (!IsInPurpleBuffer()) { + mRefCntAndFlags -= NS_REFCOUNT_CHANGE; + mRefCntAndFlags |= (NS_IN_PURPLE_BUFFER | NS_IS_PURPLE); + uintptr_t retval = NS_REFCOUNT_VALUE(mRefCntAndFlags); + // Suspect may delete 'aOwner' and 'this'! + NS_CycleCollectorSuspect3(aOwner, aCp, this, aShouldDelete); + return retval; + } + mRefCntAndFlags -= NS_REFCOUNT_CHANGE; + mRefCntAndFlags |= (NS_IN_PURPLE_BUFFER | NS_IS_PURPLE); + return NS_REFCOUNT_VALUE(mRefCntAndFlags); + } + + MOZ_ALWAYS_INLINE void RemovePurple() + { + MOZ_ASSERT(IsPurple(), "must be purple"); + mRefCntAndFlags &= ~NS_IS_PURPLE; + } + + MOZ_ALWAYS_INLINE void RemoveFromPurpleBuffer() + { + MOZ_ASSERT(IsInPurpleBuffer()); + mRefCntAndFlags &= ~(NS_IS_PURPLE | NS_IN_PURPLE_BUFFER); + } + + MOZ_ALWAYS_INLINE bool IsPurple() const + { + return !!(mRefCntAndFlags & NS_IS_PURPLE); + } + + MOZ_ALWAYS_INLINE bool IsInPurpleBuffer() const + { + return !!(mRefCntAndFlags & NS_IN_PURPLE_BUFFER); + } + + MOZ_ALWAYS_INLINE nsrefcnt get() const + { + return NS_REFCOUNT_VALUE(mRefCntAndFlags); + } + + MOZ_ALWAYS_INLINE operator nsrefcnt() const + { + return get(); + } + +private: + uintptr_t mRefCntAndFlags; +}; + +class nsAutoRefCnt +{ +public: + nsAutoRefCnt() : mValue(0) {} + explicit nsAutoRefCnt(nsrefcnt aValue) : mValue(aValue) {} + + nsAutoRefCnt(const nsAutoRefCnt&) = delete; + void operator=(const nsAutoRefCnt&) = delete; + + // only support prefix increment/decrement + nsrefcnt operator++() { return ++mValue; } + nsrefcnt operator--() { return --mValue; } + + nsrefcnt operator=(nsrefcnt aValue) { return (mValue = aValue); } + operator nsrefcnt() const { return mValue; } + nsrefcnt get() const { return mValue; } + + static const bool isThreadSafe = false; +private: + nsrefcnt operator++(int) = delete; + nsrefcnt operator--(int) = delete; + nsrefcnt mValue; +}; + +namespace mozilla { +class ThreadSafeAutoRefCnt +{ +public: + ThreadSafeAutoRefCnt() : mValue(0) {} + explicit ThreadSafeAutoRefCnt(nsrefcnt aValue) : mValue(aValue) {} + + ThreadSafeAutoRefCnt(const ThreadSafeAutoRefCnt&) = delete; + void operator=(const ThreadSafeAutoRefCnt&) = delete; + + // only support prefix increment/decrement + MOZ_ALWAYS_INLINE nsrefcnt operator++() { return ++mValue; } + MOZ_ALWAYS_INLINE nsrefcnt operator--() { return --mValue; } + + MOZ_ALWAYS_INLINE nsrefcnt operator=(nsrefcnt aValue) + { + return (mValue = aValue); + } + MOZ_ALWAYS_INLINE operator nsrefcnt() const { return mValue; } + MOZ_ALWAYS_INLINE nsrefcnt get() const { return mValue; } + + static const bool isThreadSafe = true; +private: + nsrefcnt operator++(int) = delete; + nsrefcnt operator--(int) = delete; + // In theory, RelaseAcquire consistency (but no weaker) is sufficient for + // the counter. Making it weaker could speed up builds on ARM (but not x86), + // but could break pre-existing code that assumes sequential consistency. + Atomic<nsrefcnt> mValue; +}; +} // namespace mozilla + +/////////////////////////////////////////////////////////////////////////////// + +/** + * Declare the reference count variable and the implementations of the + * AddRef and QueryInterface methods. + */ + +#define NS_DECL_ISUPPORTS \ +public: \ + NS_IMETHOD QueryInterface(REFNSIID aIID, \ + void** aInstancePtr) override; \ + NS_IMETHOD_(MozExternalRefCountType) AddRef(void) override; \ + NS_IMETHOD_(MozExternalRefCountType) Release(void) override; \ + typedef mozilla::FalseType HasThreadSafeRefCnt; \ +protected: \ + nsAutoRefCnt mRefCnt; \ + NS_DECL_OWNINGTHREAD \ +public: + +#define NS_DECL_THREADSAFE_ISUPPORTS \ +public: \ + NS_IMETHOD QueryInterface(REFNSIID aIID, \ + void** aInstancePtr) override; \ + NS_IMETHOD_(MozExternalRefCountType) AddRef(void) override; \ + NS_IMETHOD_(MozExternalRefCountType) Release(void) override; \ + typedef mozilla::TrueType HasThreadSafeRefCnt; \ +protected: \ + ::mozilla::ThreadSafeAutoRefCnt mRefCnt; \ + NS_DECL_OWNINGTHREAD \ +public: + +#define NS_DECL_CYCLE_COLLECTING_ISUPPORTS \ +public: \ + NS_IMETHOD QueryInterface(REFNSIID aIID, \ + void** aInstancePtr) override; \ + NS_IMETHOD_(MozExternalRefCountType) AddRef(void) override; \ + NS_IMETHOD_(MozExternalRefCountType) Release(void) override; \ + NS_IMETHOD_(void) DeleteCycleCollectable(void); \ + typedef mozilla::FalseType HasThreadSafeRefCnt; \ +protected: \ + nsCycleCollectingAutoRefCnt mRefCnt; \ + NS_DECL_OWNINGTHREAD \ +public: + + +/////////////////////////////////////////////////////////////////////////////// + +/* + * Implementation of AddRef and Release for non-nsISupports (ie "native") + * cycle-collected classes that use the purple buffer to avoid leaks. + */ + +#define NS_IMPL_CC_NATIVE_ADDREF_BODY(_class) \ + MOZ_ASSERT_TYPE_OK_FOR_REFCOUNTING(_class) \ + MOZ_ASSERT(int32_t(mRefCnt) >= 0, "illegal refcnt"); \ + NS_ASSERT_OWNINGTHREAD(_class); \ + nsrefcnt count = \ + mRefCnt.incr(static_cast<void*>(this), \ + _class::NS_CYCLE_COLLECTION_INNERCLASS::GetParticipant()); \ + NS_LOG_ADDREF(this, count, #_class, sizeof(*this)); \ + return count; + +#define NS_IMPL_CC_NATIVE_RELEASE_BODY(_class) \ + MOZ_ASSERT(int32_t(mRefCnt) > 0, "dup release"); \ + NS_ASSERT_OWNINGTHREAD(_class); \ + nsrefcnt count = \ + mRefCnt.decr(static_cast<void*>(this), \ + _class::NS_CYCLE_COLLECTION_INNERCLASS::GetParticipant()); \ + NS_LOG_RELEASE(this, count, #_class); \ + return count; + +#define NS_IMPL_CYCLE_COLLECTING_NATIVE_ADDREF(_class) \ +NS_METHOD_(MozExternalRefCountType) _class::AddRef(void) \ +{ \ + NS_IMPL_CC_NATIVE_ADDREF_BODY(_class) \ +} + +#define NS_IMPL_CYCLE_COLLECTING_NATIVE_RELEASE_WITH_LAST_RELEASE(_class, _last) \ +NS_METHOD_(MozExternalRefCountType) _class::Release(void) \ +{ \ + MOZ_ASSERT(int32_t(mRefCnt) > 0, "dup release"); \ + NS_ASSERT_OWNINGTHREAD(_class); \ + bool shouldDelete = false; \ + nsrefcnt count = \ + mRefCnt.decr(static_cast<void*>(this), \ + _class::NS_CYCLE_COLLECTION_INNERCLASS::GetParticipant(), \ + &shouldDelete); \ + NS_LOG_RELEASE(this, count, #_class); \ + if (count == 0) { \ + mRefCnt.incr(static_cast<void*>(this), \ + _class::NS_CYCLE_COLLECTION_INNERCLASS::GetParticipant()); \ + _last; \ + mRefCnt.decr(static_cast<void*>(this), \ + _class::NS_CYCLE_COLLECTION_INNERCLASS::GetParticipant()); \ + if (shouldDelete) { \ + mRefCnt.stabilizeForDeletion(); \ + DeleteCycleCollectable(); \ + } \ + } \ + return count; \ +} + +#define NS_IMPL_CYCLE_COLLECTING_NATIVE_RELEASE(_class) \ +NS_METHOD_(MozExternalRefCountType) _class::Release(void) \ +{ \ + NS_IMPL_CC_NATIVE_RELEASE_BODY(_class) \ +} + +#define NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(_class) \ +public: \ + NS_METHOD_(MozExternalRefCountType) AddRef(void) { \ + NS_IMPL_CC_NATIVE_ADDREF_BODY(_class) \ + } \ + NS_METHOD_(MozExternalRefCountType) Release(void) { \ + NS_IMPL_CC_NATIVE_RELEASE_BODY(_class) \ + } \ + typedef mozilla::FalseType HasThreadSafeRefCnt; \ +protected: \ + nsCycleCollectingAutoRefCnt mRefCnt; \ + NS_DECL_OWNINGTHREAD \ +public: + + +/////////////////////////////////////////////////////////////////////////////// + +/** + * Use this macro to declare and implement the AddRef & Release methods for a + * given non-XPCOM <i>_class</i>. + * + * @param _class The name of the class implementing the method + * @param _destroy A statement that is executed when the object's + * refcount drops to zero. + * @param optional override Mark the AddRef & Release methods as overrides. + */ +#define NS_INLINE_DECL_REFCOUNTING_WITH_DESTROY(_class, _destroy, ...) \ +public: \ + NS_METHOD_(MozExternalRefCountType) AddRef(void) __VA_ARGS__ { \ + MOZ_ASSERT_TYPE_OK_FOR_REFCOUNTING(_class) \ + MOZ_ASSERT(int32_t(mRefCnt) >= 0, "illegal refcnt"); \ + NS_ASSERT_OWNINGTHREAD(_class); \ + ++mRefCnt; \ + NS_LOG_ADDREF(this, mRefCnt, #_class, sizeof(*this)); \ + return mRefCnt; \ + } \ + NS_METHOD_(MozExternalRefCountType) Release(void) __VA_ARGS__ { \ + MOZ_ASSERT(int32_t(mRefCnt) > 0, "dup release"); \ + NS_ASSERT_OWNINGTHREAD(_class); \ + --mRefCnt; \ + NS_LOG_RELEASE(this, mRefCnt, #_class); \ + if (mRefCnt == 0) { \ + mRefCnt = 1; /* stabilize */ \ + _destroy; \ + return 0; \ + } \ + return mRefCnt; \ + } \ + typedef mozilla::FalseType HasThreadSafeRefCnt; \ +protected: \ + nsAutoRefCnt mRefCnt; \ + NS_DECL_OWNINGTHREAD \ +public: + +/** + * Use this macro to declare and implement the AddRef & Release methods for a + * given non-XPCOM <i>_class</i>. + * + * @param _class The name of the class implementing the method + * @param optional override Mark the AddRef & Release methods as overrides. + */ +#define NS_INLINE_DECL_REFCOUNTING(_class, ...) \ + NS_INLINE_DECL_REFCOUNTING_WITH_DESTROY(_class, delete(this), __VA_ARGS__) + +#define NS_INLINE_DECL_THREADSAFE_REFCOUNTING_META(_class, _decl, ...) \ +public: \ + _decl(MozExternalRefCountType) AddRef(void) __VA_ARGS__ { \ + MOZ_ASSERT_TYPE_OK_FOR_REFCOUNTING(_class) \ + MOZ_ASSERT(int32_t(mRefCnt) >= 0, "illegal refcnt"); \ + nsrefcnt count = ++mRefCnt; \ + NS_LOG_ADDREF(this, count, #_class, sizeof(*this)); \ + return (nsrefcnt) count; \ + } \ + _decl(MozExternalRefCountType) Release(void) __VA_ARGS__ { \ + MOZ_ASSERT(int32_t(mRefCnt) > 0, "dup release"); \ + nsrefcnt count = --mRefCnt; \ + NS_LOG_RELEASE(this, count, #_class); \ + if (count == 0) { \ + delete (this); \ + return 0; \ + } \ + return count; \ + } \ + typedef mozilla::TrueType HasThreadSafeRefCnt; \ +protected: \ + ::mozilla::ThreadSafeAutoRefCnt mRefCnt; \ +public: + +/** + * Use this macro to declare and implement the AddRef & Release methods for a + * given non-XPCOM <i>_class</i> in a threadsafe manner. + * + * DOES NOT DO REFCOUNT STABILIZATION! + * + * @param _class The name of the class implementing the method + */ +#define NS_INLINE_DECL_THREADSAFE_REFCOUNTING(_class, ...) \ +NS_INLINE_DECL_THREADSAFE_REFCOUNTING_META(_class, NS_METHOD_, __VA_ARGS__) + +/** + * Like NS_INLINE_DECL_THREADSAFE_REFCOUNTING with AddRef & Release declared + * virtual. + */ +#define NS_INLINE_DECL_THREADSAFE_VIRTUAL_REFCOUNTING(_class, ...) \ +NS_INLINE_DECL_THREADSAFE_REFCOUNTING_META(_class, NS_IMETHOD_, __VA_ARGS__) + +/** + * Use this macro to implement the AddRef method for a given <i>_class</i> + * @param _class The name of the class implementing the method + */ +#define NS_IMPL_ADDREF(_class) \ +NS_IMETHODIMP_(MozExternalRefCountType) _class::AddRef(void) \ +{ \ + MOZ_ASSERT_TYPE_OK_FOR_REFCOUNTING(_class) \ + MOZ_ASSERT(int32_t(mRefCnt) >= 0, "illegal refcnt"); \ + if (!mRefCnt.isThreadSafe) \ + NS_ASSERT_OWNINGTHREAD(_class); \ + nsrefcnt count = ++mRefCnt; \ + NS_LOG_ADDREF(this, count, #_class, sizeof(*this)); \ + return count; \ +} + +/** + * Use this macro to implement the AddRef method for a given <i>_class</i> + * implemented as a wholly owned aggregated object intended to implement + * interface(s) for its owner + * @param _class The name of the class implementing the method + * @param _aggregator the owning/containing object + */ +#define NS_IMPL_ADDREF_USING_AGGREGATOR(_class, _aggregator) \ +NS_IMETHODIMP_(MozExternalRefCountType) _class::AddRef(void) \ +{ \ + MOZ_ASSERT_TYPE_OK_FOR_REFCOUNTING(_class) \ + NS_PRECONDITION(_aggregator, "null aggregator"); \ + return (_aggregator)->AddRef(); \ +} + +/** + * Use this macro to implement the Release method for a given + * <i>_class</i>. + * @param _class The name of the class implementing the method + * @param _destroy A statement that is executed when the object's + * refcount drops to zero. + * + * For example, + * + * NS_IMPL_RELEASE_WITH_DESTROY(Foo, Destroy(this)) + * + * will cause + * + * Destroy(this); + * + * to be invoked when the object's refcount drops to zero. This + * allows for arbitrary teardown activity to occur (e.g., deallocation + * of object allocated with placement new). + */ +#define NS_IMPL_RELEASE_WITH_DESTROY(_class, _destroy) \ +NS_IMETHODIMP_(MozExternalRefCountType) _class::Release(void) \ +{ \ + MOZ_ASSERT(int32_t(mRefCnt) > 0, "dup release"); \ + if (!mRefCnt.isThreadSafe) \ + NS_ASSERT_OWNINGTHREAD(_class); \ + nsrefcnt count = --mRefCnt; \ + NS_LOG_RELEASE(this, count, #_class); \ + if (count == 0) { \ + mRefCnt = 1; /* stabilize */ \ + _destroy; \ + return 0; \ + } \ + return count; \ +} + +/** + * Use this macro to implement the Release method for a given <i>_class</i> + * @param _class The name of the class implementing the method + * + * A note on the 'stabilization' of the refcnt to one. At that point, + * the object's refcount will have gone to zero. The object's + * destructor may trigger code that attempts to QueryInterface() and + * Release() 'this' again. Doing so will temporarily increment and + * decrement the refcount. (Only a logic error would make one try to + * keep a permanent hold on 'this'.) To prevent re-entering the + * destructor, we make sure that no balanced refcounting can return + * the refcount to |0|. + */ +#define NS_IMPL_RELEASE(_class) \ + NS_IMPL_RELEASE_WITH_DESTROY(_class, delete (this)) + +/** + * Use this macro to implement the Release method for a given <i>_class</i> + * implemented as a wholly owned aggregated object intended to implement + * interface(s) for its owner + * @param _class The name of the class implementing the method + * @param _aggregator the owning/containing object + */ +#define NS_IMPL_RELEASE_USING_AGGREGATOR(_class, _aggregator) \ +NS_IMETHODIMP_(MozExternalRefCountType) _class::Release(void) \ +{ \ + NS_PRECONDITION(_aggregator, "null aggregator"); \ + return (_aggregator)->Release(); \ +} + + +#define NS_IMPL_CYCLE_COLLECTING_ADDREF(_class) \ +NS_IMETHODIMP_(MozExternalRefCountType) _class::AddRef(void) \ +{ \ + MOZ_ASSERT_TYPE_OK_FOR_REFCOUNTING(_class) \ + MOZ_ASSERT(int32_t(mRefCnt) >= 0, "illegal refcnt"); \ + NS_ASSERT_OWNINGTHREAD(_class); \ + nsISupports *base = NS_CYCLE_COLLECTION_CLASSNAME(_class)::Upcast(this); \ + nsrefcnt count = mRefCnt.incr(base); \ + NS_LOG_ADDREF(this, count, #_class, sizeof(*this)); \ + return count; \ +} + +#define NS_IMPL_CYCLE_COLLECTING_RELEASE_WITH_DESTROY(_class, _destroy) \ +NS_IMETHODIMP_(MozExternalRefCountType) _class::Release(void) \ +{ \ + MOZ_ASSERT(int32_t(mRefCnt) > 0, "dup release"); \ + NS_ASSERT_OWNINGTHREAD(_class); \ + nsISupports *base = NS_CYCLE_COLLECTION_CLASSNAME(_class)::Upcast(this); \ + nsrefcnt count = mRefCnt.decr(base); \ + NS_LOG_RELEASE(this, count, #_class); \ + return count; \ +} \ +NS_IMETHODIMP_(void) _class::DeleteCycleCollectable(void) \ +{ \ + _destroy; \ +} + +#define NS_IMPL_CYCLE_COLLECTING_RELEASE(_class) \ + NS_IMPL_CYCLE_COLLECTING_RELEASE_WITH_DESTROY(_class, delete (this)) + +// _LAST_RELEASE can be useful when certain resources should be released +// as soon as we know the object will be deleted. +#define NS_IMPL_CYCLE_COLLECTING_RELEASE_WITH_LAST_RELEASE(_class, _last) \ +NS_IMETHODIMP_(MozExternalRefCountType) _class::Release(void) \ +{ \ + MOZ_ASSERT(int32_t(mRefCnt) > 0, "dup release"); \ + NS_ASSERT_OWNINGTHREAD(_class); \ + bool shouldDelete = false; \ + nsISupports *base = NS_CYCLE_COLLECTION_CLASSNAME(_class)::Upcast(this); \ + nsrefcnt count = mRefCnt.decr(base, &shouldDelete); \ + NS_LOG_RELEASE(this, count, #_class); \ + if (count == 0) { \ + mRefCnt.incr(base); \ + _last; \ + mRefCnt.decr(base); \ + if (shouldDelete) { \ + mRefCnt.stabilizeForDeletion(); \ + DeleteCycleCollectable(); \ + } \ + } \ + return count; \ +} \ +NS_IMETHODIMP_(void) _class::DeleteCycleCollectable(void) \ +{ \ + delete this; \ +} + +/////////////////////////////////////////////////////////////////////////////// + +/** + * There are two ways of implementing QueryInterface, and we use both: + * + * Table-driven QueryInterface uses a static table of IID->offset mappings + * and a shared helper function. Using it tends to reduce codesize and improve + * runtime performance (due to processor cache hits). + * + * Macro-driven QueryInterface generates a QueryInterface function directly + * using common macros. This is necessary if special QueryInterface features + * are being used (such as tearoffs and conditional interfaces). + * + * These methods can be combined into a table-driven function call followed + * by custom code for tearoffs and conditionals. + */ + +struct QITableEntry +{ + const nsIID* iid; // null indicates end of the QITableEntry array + int32_t offset; +}; + +nsresult NS_FASTCALL +NS_TableDrivenQI(void* aThis, REFNSIID aIID, + void** aInstancePtr, const QITableEntry* aEntries); + +/** + * Implement table-driven queryinterface + */ + +#define NS_INTERFACE_TABLE_HEAD(_class) \ +NS_IMETHODIMP _class::QueryInterface(REFNSIID aIID, void** aInstancePtr) \ +{ \ + NS_ASSERTION(aInstancePtr, \ + "QueryInterface requires a non-NULL destination!"); \ + nsresult rv = NS_ERROR_FAILURE; + +#define NS_INTERFACE_TABLE_BEGIN \ + static const QITableEntry table[] = { + +#define NS_INTERFACE_TABLE_ENTRY(_class, _interface) \ + { &NS_GET_IID(_interface), \ + int32_t(reinterpret_cast<char*>( \ + static_cast<_interface*>((_class*) 0x1000)) - \ + reinterpret_cast<char*>((_class*) 0x1000)) \ + }, + +#define NS_INTERFACE_TABLE_ENTRY_AMBIGUOUS(_class, _interface, _implClass) \ + { &NS_GET_IID(_interface), \ + int32_t(reinterpret_cast<char*>( \ + static_cast<_interface*>( \ + static_cast<_implClass*>( \ + (_class*) 0x1000))) - \ + reinterpret_cast<char*>((_class*) 0x1000)) \ + }, + +/* + * XXX: we want to use mozilla::ArrayLength (or equivalent, + * MOZ_ARRAY_LENGTH) in this condition, but some versions of GCC don't + * see that the static_assert condition is actually constant in those + * cases, even with constexpr support (?). + */ +#define NS_INTERFACE_TABLE_END_WITH_PTR(_ptr) \ + { nullptr, 0 } }; \ + static_assert((sizeof(table)/sizeof(table[0])) > 1, "need at least 1 interface"); \ + rv = NS_TableDrivenQI(static_cast<void*>(_ptr), \ + aIID, aInstancePtr, table); + +#define NS_INTERFACE_TABLE_END \ + NS_INTERFACE_TABLE_END_WITH_PTR(this) + +#define NS_INTERFACE_TABLE_TAIL \ + return rv; \ +} + +#define NS_INTERFACE_TABLE_TAIL_INHERITING(_baseclass) \ + if (NS_SUCCEEDED(rv)) \ + return rv; \ + return _baseclass::QueryInterface(aIID, aInstancePtr); \ +} + +#define NS_INTERFACE_TABLE_TAIL_USING_AGGREGATOR(_aggregator) \ + if (NS_SUCCEEDED(rv)) \ + return rv; \ + NS_ASSERTION(_aggregator, "null aggregator"); \ + return _aggregator->QueryInterface(aIID, aInstancePtr) \ +} + +/** + * This implements query interface with two assumptions: First, the + * class in question implements nsISupports and its own interface and + * nothing else. Second, the implementation of the class's primary + * inheritance chain leads to its own interface. + * + * @param _class The name of the class implementing the method + * @param _classiiddef The name of the #define symbol that defines the IID + * for the class (e.g. NS_ISUPPORTS_IID) + */ + +#define NS_IMPL_QUERY_HEAD(_class) \ +NS_IMETHODIMP _class::QueryInterface(REFNSIID aIID, void** aInstancePtr) \ +{ \ + NS_ASSERTION(aInstancePtr, \ + "QueryInterface requires a non-NULL destination!"); \ + nsISupports* foundInterface; + +#define NS_IMPL_QUERY_BODY(_interface) \ + if ( aIID.Equals(NS_GET_IID(_interface)) ) \ + foundInterface = static_cast<_interface*>(this); \ + else + +#define NS_IMPL_QUERY_BODY_CONDITIONAL(_interface, condition) \ + if ( (condition) && aIID.Equals(NS_GET_IID(_interface))) \ + foundInterface = static_cast<_interface*>(this); \ + else + +#define NS_IMPL_QUERY_BODY_AMBIGUOUS(_interface, _implClass) \ + if ( aIID.Equals(NS_GET_IID(_interface)) ) \ + foundInterface = static_cast<_interface*>( \ + static_cast<_implClass*>(this)); \ + else + +#define NS_IMPL_QUERY_BODY_AGGREGATED(_interface, _aggregate) \ + if ( aIID.Equals(NS_GET_IID(_interface)) ) \ + foundInterface = static_cast<_interface*>(_aggregate); \ + else + +#define NS_IMPL_QUERY_TAIL_GUTS \ + foundInterface = 0; \ + nsresult status; \ + if ( !foundInterface ) \ + { \ + /* nsISupports should be handled by this point. If not, fail. */ \ + MOZ_ASSERT(!aIID.Equals(NS_GET_IID(nsISupports))); \ + status = NS_NOINTERFACE; \ + } \ + else \ + { \ + NS_ADDREF(foundInterface); \ + status = NS_OK; \ + } \ + *aInstancePtr = foundInterface; \ + return status; \ +} + +#define NS_IMPL_QUERY_TAIL_INHERITING(_baseclass) \ + foundInterface = 0; \ + nsresult status; \ + if ( !foundInterface ) \ + status = _baseclass::QueryInterface(aIID, (void**)&foundInterface); \ + else \ + { \ + NS_ADDREF(foundInterface); \ + status = NS_OK; \ + } \ + *aInstancePtr = foundInterface; \ + return status; \ +} + +#define NS_IMPL_QUERY_TAIL_USING_AGGREGATOR(_aggregator) \ + foundInterface = 0; \ + nsresult status; \ + if ( !foundInterface ) { \ + NS_ASSERTION(_aggregator, "null aggregator"); \ + status = _aggregator->QueryInterface(aIID, (void**)&foundInterface); \ + } else \ + { \ + NS_ADDREF(foundInterface); \ + status = NS_OK; \ + } \ + *aInstancePtr = foundInterface; \ + return status; \ +} + +#define NS_IMPL_QUERY_TAIL(_supports_interface) \ + NS_IMPL_QUERY_BODY_AMBIGUOUS(nsISupports, _supports_interface) \ + NS_IMPL_QUERY_TAIL_GUTS + + +/* + This is the new scheme. Using this notation now will allow us to switch to + a table driven mechanism when it's ready. Note the difference between this + and the (currently) underlying NS_IMPL_QUERY_INTERFACE mechanism. You must + explicitly mention |nsISupports| when using the interface maps. +*/ +#define NS_INTERFACE_MAP_BEGIN(_implClass) NS_IMPL_QUERY_HEAD(_implClass) +#define NS_INTERFACE_MAP_ENTRY(_interface) NS_IMPL_QUERY_BODY(_interface) +#define NS_INTERFACE_MAP_ENTRY_CONDITIONAL(_interface, condition) \ + NS_IMPL_QUERY_BODY_CONDITIONAL(_interface, condition) +#define NS_INTERFACE_MAP_ENTRY_AGGREGATED(_interface,_aggregate) \ + NS_IMPL_QUERY_BODY_AGGREGATED(_interface,_aggregate) + +#define NS_INTERFACE_MAP_END NS_IMPL_QUERY_TAIL_GUTS +#define NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(_interface, _implClass) \ + NS_IMPL_QUERY_BODY_AMBIGUOUS(_interface, _implClass) +#define NS_INTERFACE_MAP_END_INHERITING(_baseClass) \ + NS_IMPL_QUERY_TAIL_INHERITING(_baseClass) +#define NS_INTERFACE_MAP_END_AGGREGATED(_aggregator) \ + NS_IMPL_QUERY_TAIL_USING_AGGREGATOR(_aggregator) + +#define NS_INTERFACE_TABLE0(_class) \ + NS_INTERFACE_TABLE_BEGIN \ + NS_INTERFACE_TABLE_ENTRY(_class, nsISupports) \ + NS_INTERFACE_TABLE_END + +#define NS_INTERFACE_TABLE(aClass, ...) \ + MOZ_STATIC_ASSERT_VALID_ARG_COUNT(__VA_ARGS__); \ + NS_INTERFACE_TABLE_BEGIN \ + MOZ_FOR_EACH(NS_INTERFACE_TABLE_ENTRY, (aClass,), (__VA_ARGS__)) \ + NS_INTERFACE_TABLE_ENTRY_AMBIGUOUS(aClass, nsISupports, \ + MOZ_ARG_1(__VA_ARGS__)) \ + NS_INTERFACE_TABLE_END + +#define NS_IMPL_QUERY_INTERFACE0(_class) \ + NS_INTERFACE_TABLE_HEAD(_class) \ + NS_INTERFACE_TABLE0(_class) \ + NS_INTERFACE_TABLE_TAIL + +#define NS_IMPL_QUERY_INTERFACE(aClass, ...) \ + NS_INTERFACE_TABLE_HEAD(aClass) \ + NS_INTERFACE_TABLE(aClass, __VA_ARGS__) \ + NS_INTERFACE_TABLE_TAIL + +/** + * Declare that you're going to inherit from something that already + * implements nsISupports, but also implements an additional interface, thus + * causing an ambiguity. In this case you don't need another mRefCnt, you + * just need to forward the definitions to the appropriate superclass. E.g. + * + * class Bar : public Foo, public nsIBar { // both provide nsISupports + * public: + * NS_DECL_ISUPPORTS_INHERITED + * ...other nsIBar and Bar methods... + * }; + */ +#define NS_DECL_ISUPPORTS_INHERITED \ +public: \ + NS_IMETHOD QueryInterface(REFNSIID aIID, \ + void** aInstancePtr) override; \ + NS_IMETHOD_(MozExternalRefCountType) AddRef(void) override; \ + NS_IMETHOD_(MozExternalRefCountType) Release(void) override; \ + +/** + * These macros can be used in conjunction with NS_DECL_ISUPPORTS_INHERITED + * to implement the nsISupports methods, forwarding the invocations to a + * superclass that already implements nsISupports. + * + * Note that I didn't make these inlined because they're virtual methods. + */ + +#define NS_IMPL_ADDREF_INHERITED(Class, Super) \ +NS_IMETHODIMP_(MozExternalRefCountType) Class::AddRef(void) \ +{ \ + MOZ_ASSERT_TYPE_OK_FOR_REFCOUNTING(Class) \ + nsrefcnt r = Super::AddRef(); \ + NS_LOG_ADDREF(this, r, #Class, sizeof(*this)); \ + return r; \ +} + +#define NS_IMPL_RELEASE_INHERITED(Class, Super) \ +NS_IMETHODIMP_(MozExternalRefCountType) Class::Release(void) \ +{ \ + nsrefcnt r = Super::Release(); \ + NS_LOG_RELEASE(this, r, #Class); \ + return r; \ +} + +/** + * As above but not logging the addref/release; needed if the base + * class might be aggregated. + */ +#define NS_IMPL_NONLOGGING_ADDREF_INHERITED(Class, Super) \ +NS_IMETHODIMP_(MozExternalRefCountType) Class::AddRef(void) \ +{ \ + MOZ_ASSERT_TYPE_OK_FOR_REFCOUNTING(Class) \ + return Super::AddRef(); \ +} + +#define NS_IMPL_NONLOGGING_RELEASE_INHERITED(Class, Super) \ +NS_IMETHODIMP_(MozExternalRefCountType) Class::Release(void) \ +{ \ + return Super::Release(); \ +} + +#define NS_INTERFACE_TABLE_INHERITED0(Class) /* Nothing to do here */ + +#define NS_INTERFACE_TABLE_INHERITED(aClass, ...) \ + MOZ_STATIC_ASSERT_VALID_ARG_COUNT(__VA_ARGS__); \ + NS_INTERFACE_TABLE_BEGIN \ + MOZ_FOR_EACH(NS_INTERFACE_TABLE_ENTRY, (aClass,), (__VA_ARGS__)) \ + NS_INTERFACE_TABLE_END + +#define NS_IMPL_QUERY_INTERFACE_INHERITED(aClass, aSuper, ...) \ + NS_INTERFACE_TABLE_HEAD(aClass) \ + NS_INTERFACE_TABLE_INHERITED(aClass, __VA_ARGS__) \ + NS_INTERFACE_TABLE_TAIL_INHERITING(aSuper) + +/** + * Convenience macros for implementing all nsISupports methods for + * a simple class. + * @param _class The name of the class implementing the method + * @param _classiiddef The name of the #define symbol that defines the IID + * for the class (e.g. NS_ISUPPORTS_IID) + */ + +#define NS_IMPL_ISUPPORTS0(_class) \ + NS_IMPL_ADDREF(_class) \ + NS_IMPL_RELEASE(_class) \ + NS_IMPL_QUERY_INTERFACE0(_class) + +#define NS_IMPL_ISUPPORTS(aClass, ...) \ + NS_IMPL_ADDREF(aClass) \ + NS_IMPL_RELEASE(aClass) \ + NS_IMPL_QUERY_INTERFACE(aClass, __VA_ARGS__) + +#define NS_IMPL_ISUPPORTS_INHERITED0(aClass, aSuper) \ + NS_INTERFACE_TABLE_HEAD(aClass) \ + NS_INTERFACE_TABLE_TAIL_INHERITING(aSuper) \ + NS_IMPL_ADDREF_INHERITED(aClass, aSuper) \ + NS_IMPL_RELEASE_INHERITED(aClass, aSuper) \ + +#define NS_IMPL_ISUPPORTS_INHERITED(aClass, aSuper, ...) \ + NS_IMPL_QUERY_INTERFACE_INHERITED(aClass, aSuper, __VA_ARGS__) \ + NS_IMPL_ADDREF_INHERITED(aClass, aSuper) \ + NS_IMPL_RELEASE_INHERITED(aClass, aSuper) + +/* + * Macro to glue together a QI that starts with an interface table + * and segues into an interface map (e.g. it uses singleton classinfo + * or tearoffs). + */ +#define NS_INTERFACE_TABLE_TO_MAP_SEGUE \ + if (rv == NS_OK) return rv; \ + nsISupports* foundInterface; + + +/////////////////////////////////////////////////////////////////////////////// +/** + * + * Threadsafe implementations of the ISupports convenience macros. + * + * @note These are not available when linking against the standalone glue, + * because the implementation requires PR_ symbols. + */ +#define NS_INTERFACE_MAP_END_THREADSAFE NS_IMPL_QUERY_TAIL_GUTS + +/** + * Macro to generate nsIClassInfo methods for classes which do not have + * corresponding nsIFactory implementations. + */ +#define NS_IMPL_THREADSAFE_CI(_class) \ +NS_IMETHODIMP \ +_class::GetInterfaces(uint32_t* _count, nsIID*** _array) \ +{ \ + return NS_CI_INTERFACE_GETTER_NAME(_class)(_count, _array); \ +} \ + \ +NS_IMETHODIMP \ +_class::GetScriptableHelper(nsIXPCScriptable** _retval) \ +{ \ + *_retval = nullptr; \ + return NS_OK; \ +} \ + \ +NS_IMETHODIMP \ +_class::GetContractID(char** _contractID) \ +{ \ + *_contractID = nullptr; \ + return NS_OK; \ +} \ + \ +NS_IMETHODIMP \ +_class::GetClassDescription(char** _classDescription) \ +{ \ + *_classDescription = nullptr; \ + return NS_OK; \ +} \ + \ +NS_IMETHODIMP \ +_class::GetClassID(nsCID** _classID) \ +{ \ + *_classID = nullptr; \ + return NS_OK; \ +} \ + \ +NS_IMETHODIMP \ +_class::GetFlags(uint32_t* _flags) \ +{ \ + *_flags = nsIClassInfo::THREADSAFE; \ + return NS_OK; \ +} \ + \ +NS_IMETHODIMP \ +_class::GetClassIDNoAlloc(nsCID* _classIDNoAlloc) \ +{ \ + return NS_ERROR_NOT_AVAILABLE; \ +} + +#endif diff --git a/xpcom/glue/nsISupportsUtils.h b/xpcom/glue/nsISupportsUtils.h new file mode 100644 index 0000000000..4d306d3d1a --- /dev/null +++ b/xpcom/glue/nsISupportsUtils.h @@ -0,0 +1,145 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsISupportsUtils_h__ +#define nsISupportsUtils_h__ + +#include "nscore.h" +#include "nsISupportsBase.h" +#include "nsError.h" +#include "nsDebug.h" +#include "nsISupportsImpl.h" +#include "mozilla/RefPtr.h" +#include "mozilla/TypeTraits.h" + +/** + * Macro for adding a reference to an interface. + * @param _ptr The interface pointer. + */ +#define NS_ADDREF(_ptr) \ + (_ptr)->AddRef() + +/** + * Macro for adding a reference to this. This macro should be used + * because NS_ADDREF (when tracing) may require an ambiguous cast + * from the pointers primary type to nsISupports. This macro sidesteps + * that entire problem. + */ +#define NS_ADDREF_THIS() \ + AddRef() + + +// Making this a |inline| |template| allows |aExpr| to be evaluated only once, +// yet still denies you the ability to |AddRef()| an |nsCOMPtr|. +template<class T> +inline void +ns_if_addref(T aExpr) +{ + if (aExpr) { + aExpr->AddRef(); + } +} + +/** + * Macro for adding a reference to an interface that checks for nullptr. + * @param _expr The interface pointer. + */ +#define NS_IF_ADDREF(_expr) ns_if_addref(_expr) + +/* + * Given these declarations, it explicitly OK and efficient to end a `getter' with: + * + * NS_IF_ADDREF(*result = mThing); + * + * even if |mThing| is an |nsCOMPtr|. If |mThing| is an |nsCOMPtr|, however, it is still + * _illegal_ to say |NS_IF_ADDREF(mThing)|. + */ + +/** + * Macro for releasing a reference to an interface. + * @param _ptr The interface pointer. + */ +#define NS_RELEASE(_ptr) \ + do { \ + (_ptr)->Release(); \ + (_ptr) = 0; \ + } while (0) + +/** + * Macro for releasing a reference to this interface. + */ +#define NS_RELEASE_THIS() \ + Release() + +/** + * Macro for releasing a reference to an interface, except that this + * macro preserves the return value from the underlying Release call. + * The interface pointer argument will only be NULLed if the reference count + * goes to zero. + * + * @param _ptr The interface pointer. + * @param _rc The reference count. + */ +#define NS_RELEASE2(_ptr, _rc) \ + do { \ + _rc = (_ptr)->Release(); \ + if (0 == (_rc)) (_ptr) = 0; \ + } while (0) + +/** + * Macro for releasing a reference to an interface that checks for nullptr; + * @param _ptr The interface pointer. + */ +#define NS_IF_RELEASE(_ptr) \ + do { \ + if (_ptr) { \ + (_ptr)->Release(); \ + (_ptr) = 0; \ + } \ + } while (0) + +/* + * Often you have to cast an implementation pointer, e.g., |this|, to an + * |nsISupports*|, but because you have multiple inheritance, a simple cast + * is ambiguous. One could simply say, e.g., (given a base |nsIBase|), + * |static_cast<nsIBase*>(this)|; but that disguises the fact that what + * you are really doing is disambiguating the |nsISupports|. You could make + * that more obvious with a double cast, e.g., |static_cast<nsISupports*> + (* static_cast<nsIBase*>(this))|, but that is bulky and harder to read... + * + * The following macro is clean, short, and obvious. In the example above, + * you would use it like this: |NS_ISUPPORTS_CAST(nsIBase*, this)|. + */ + +#define NS_ISUPPORTS_CAST(__unambiguousBase, __expr) \ + static_cast<nsISupports*>(static_cast<__unambiguousBase>(__expr)) + +// a type-safe shortcut for calling the |QueryInterface()| member function +template<class T, class DestinationType> +inline nsresult +CallQueryInterface(T* aSource, DestinationType** aDestination) +{ + // We permit nsISupports-to-nsISupports here so that one can still obtain + // the canonical nsISupports pointer with CallQueryInterface. + static_assert(!mozilla::IsSame<T, DestinationType>::value || + mozilla::IsSame<DestinationType, nsISupports>::value, + "don't use CallQueryInterface for compile-time-determinable casts"); + + NS_PRECONDITION(aSource, "null parameter"); + NS_PRECONDITION(aDestination, "null parameter"); + + return aSource->QueryInterface(NS_GET_TEMPLATE_IID(DestinationType), + reinterpret_cast<void**>(aDestination)); +} + +template <class SourceType, class DestinationType> +inline nsresult +CallQueryInterface(RefPtr<SourceType>& aSourcePtr, DestinationType** aDestPtr) +{ + return CallQueryInterface(aSourcePtr.get(), aDestPtr); +} + +#endif /* __nsISupportsUtils_h */ diff --git a/xpcom/glue/nsIWeakReferenceUtils.h b/xpcom/glue/nsIWeakReferenceUtils.h new file mode 100644 index 0000000000..1c84e00dfb --- /dev/null +++ b/xpcom/glue/nsIWeakReferenceUtils.h @@ -0,0 +1,102 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsIWeakReferenceUtils_h__ +#define nsIWeakReferenceUtils_h__ + +#include "nsCOMPtr.h" +#include "nsIWeakReference.h" + +typedef nsCOMPtr<nsIWeakReference> nsWeakPtr; + +/** + * + */ + +// a type-safe shortcut for calling the |QueryReferent()| member function +// T must inherit from nsIWeakReference, but the cast may be ambiguous. +template<class T, class DestinationType> +inline nsresult +CallQueryReferent(T* aSource, DestinationType** aDestination) +{ + NS_PRECONDITION(aSource, "null parameter"); + NS_PRECONDITION(aDestination, "null parameter"); + + return aSource->QueryReferent(NS_GET_TEMPLATE_IID(DestinationType), + reinterpret_cast<void**>(aDestination)); +} + + +class MOZ_STACK_CLASS nsQueryReferent final : public nsCOMPtr_helper +{ +public: + nsQueryReferent(nsIWeakReference* aWeakPtr, nsresult* aError) + : mWeakPtr(aWeakPtr) + , mErrorPtr(aError) + { + } + + virtual nsresult NS_FASTCALL operator()(const nsIID& aIID, void**) const + override; + +private: + nsIWeakReference* MOZ_NON_OWNING_REF mWeakPtr; + nsresult* mErrorPtr; +}; + +inline const nsQueryReferent +do_QueryReferent(nsIWeakReference* aRawPtr, nsresult* aError = 0) +{ + return nsQueryReferent(aRawPtr, aError); +} + + +/** + * Deprecated, use |do_GetWeakReference| instead. + */ +extern nsIWeakReference* NS_GetWeakReference(nsISupports*, + nsresult* aResult = 0); + +/** + * |do_GetWeakReference| is a convenience function that bundles up all the work needed + * to get a weak reference to an arbitrary object, i.e., the |QueryInterface|, test, and + * call through to |GetWeakReference|, and put it into your |nsCOMPtr|. + * It is specifically designed to cooperate with |nsCOMPtr| (or |nsWeakPtr|) like so: + * |nsWeakPtr myWeakPtr = do_GetWeakReference(aPtr);|. + */ +inline already_AddRefed<nsIWeakReference> +do_GetWeakReference(nsISupports* aRawPtr, nsresult* aError = 0) +{ + return dont_AddRef(NS_GetWeakReference(aRawPtr, aError)); +} + +inline void +do_GetWeakReference(nsIWeakReference* aRawPtr, nsresult* aError = 0) +{ + // This signature exists solely to _stop_ you from doing a bad thing. + // Saying |do_GetWeakReference()| on a weak reference itself, + // is very likely to be a programmer error. +} + +template<class T> +inline void +do_GetWeakReference(already_AddRefed<T>&) +{ + // This signature exists solely to _stop_ you from doing the bad thing. + // Saying |do_GetWeakReference()| on a pointer that is not otherwise owned by + // someone else is an automatic leak. See <http://bugzilla.mozilla.org/show_bug.cgi?id=8221>. +} + +template<class T> +inline void +do_GetWeakReference(already_AddRefed<T>&, nsresult*) +{ + // This signature exists solely to _stop_ you from doing the bad thing. + // Saying |do_GetWeakReference()| on a pointer that is not otherwise owned by + // someone else is an automatic leak. See <http://bugzilla.mozilla.org/show_bug.cgi?id=8221>. +} + +#endif diff --git a/xpcom/glue/nsInterfaceHashtable.h b/xpcom/glue/nsInterfaceHashtable.h new file mode 100644 index 0000000000..14368af637 --- /dev/null +++ b/xpcom/glue/nsInterfaceHashtable.h @@ -0,0 +1,142 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsInterfaceHashtable_h__ +#define nsInterfaceHashtable_h__ + +#include "nsBaseHashtable.h" +#include "nsHashKeys.h" +#include "nsCOMPtr.h" + +/** + * templated hashtable class maps keys to interface pointers. + * See nsBaseHashtable for complete declaration. + * @param KeyClass a wrapper-class for the hashtable key, see nsHashKeys.h + * for a complete specification. + * @param Interface the interface-type being wrapped + * @see nsDataHashtable, nsClassHashtable + */ +template<class KeyClass, class Interface> +class nsInterfaceHashtable + : public nsBaseHashtable<KeyClass, nsCOMPtr<Interface>, Interface*> +{ +public: + typedef typename KeyClass::KeyType KeyType; + typedef Interface* UserDataType; + typedef nsBaseHashtable<KeyClass, nsCOMPtr<Interface>, Interface*> base_type; + + nsInterfaceHashtable() {} + explicit nsInterfaceHashtable(uint32_t aInitLength) + : nsBaseHashtable<KeyClass, nsCOMPtr<Interface>, Interface*>(aInitLength) + { + } + + /** + * @copydoc nsBaseHashtable::Get + * @param aData This is an XPCOM getter, so aData is already_addrefed. + * If the key doesn't exist, aData will be set to nullptr. + */ + bool Get(KeyType aKey, UserDataType* aData) const; + + /** + * @copydoc nsBaseHashtable::Get + */ + already_AddRefed<Interface> Get(KeyType aKey) const; + + /** + * Gets a weak reference to the hashtable entry. + * @param aFound If not nullptr, will be set to true if the entry is found, + * to false otherwise. + * @return The entry, or nullptr if not found. Do not release this pointer! + */ + Interface* GetWeak(KeyType aKey, bool* aFound = nullptr) const; +}; + +template<typename K, typename T> +inline void +ImplCycleCollectionUnlink(nsInterfaceHashtable<K, T>& aField) +{ + aField.Clear(); +} + +template<typename K, typename T> +inline void +ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback& aCallback, + const nsInterfaceHashtable<K, T>& aField, + const char* aName, + uint32_t aFlags = 0) +{ + for (auto iter = aField.ConstIter(); !iter.Done(); iter.Next()) { + CycleCollectionNoteChild(aCallback, iter.UserData(), aName, aFlags); + } +} + +// +// nsInterfaceHashtable definitions +// + +template<class KeyClass, class Interface> +bool +nsInterfaceHashtable<KeyClass, Interface>::Get(KeyType aKey, + UserDataType* aInterface) const +{ + typename base_type::EntryType* ent = this->GetEntry(aKey); + + if (ent) { + if (aInterface) { + *aInterface = ent->mData; + + NS_IF_ADDREF(*aInterface); + } + + return true; + } + + // if the key doesn't exist, set *aInterface to null + // so that it is a valid XPCOM getter + if (aInterface) { + *aInterface = nullptr; + } + + return false; +} + +template<class KeyClass, class Interface> +already_AddRefed<Interface> +nsInterfaceHashtable<KeyClass, Interface>::Get(KeyType aKey) const +{ + typename base_type::EntryType* ent = this->GetEntry(aKey); + if (!ent) { + return nullptr; + } + + nsCOMPtr<Interface> copy = ent->mData; + return copy.forget(); +} + +template<class KeyClass, class Interface> +Interface* +nsInterfaceHashtable<KeyClass, Interface>::GetWeak(KeyType aKey, + bool* aFound) const +{ + typename base_type::EntryType* ent = this->GetEntry(aKey); + + if (ent) { + if (aFound) { + *aFound = true; + } + + return ent->mData; + } + + // Key does not exist, return nullptr and set aFound to false + if (aFound) { + *aFound = false; + } + return nullptr; +} + +#endif // nsInterfaceHashtable_h__ diff --git a/xpcom/glue/nsJSThingHashtable.h b/xpcom/glue/nsJSThingHashtable.h new file mode 100644 index 0000000000..8c1b1447d2 --- /dev/null +++ b/xpcom/glue/nsJSThingHashtable.h @@ -0,0 +1,61 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsJSThingHashtable_h__ +#define nsJSThingHashtable_h__ + +#include "nsHashKeys.h" +#include "nsBaseHashtable.h" + +namespace JS { +template<class T> +class Heap; +} /* namespace JS */ + +/** + * A wrapper for hash keys that sets ALLOW_MEMMOVE to false. + * + * This is used in the implementation of nsJSThingHashtable and is not intended + * to be used directly. + * + * It is necessary for hash tables containing JS::Heap<T> values as these must + * be copied rather than memmoved. + */ +template<class T> +class nsHashKeyDisallowMemmove : public T +{ +public: + explicit nsHashKeyDisallowMemmove(const typename T::KeyTypePointer aKey) : T(aKey) {} + enum { ALLOW_MEMMOVE = false }; +}; + + +/** + * Templated hashtable class for use on the heap where the values are JS GC things. + * + * Storing JS GC thing pointers on the heap requires wrapping them in a + * JS::Heap<T>, and this class takes care of that while presenting an interface + * in terms of the wrapped type T. + * + * For example, to store a hashtable mapping strings to JSObject pointers, you + * can declare a data member like this: + * + * nsJSThingHashtable<nsStringHashKey, JSObject*> mStringToObjectMap; + * + * See nsBaseHashtable for complete declaration + * @param KeyClass a wrapper-class for the hashtable key, see nsHashKeys.h + * for a complete specification. + * @param DataType the datatype being wrapped, must be a JS GC thing. + * @see nsInterfaceHashtable, nsClassHashtable + */ +template<class KeyClass, class DataType> +class nsJSThingHashtable + : public nsBaseHashtable<nsHashKeyDisallowMemmove<KeyClass>, + JS::Heap<DataType>, DataType> +{ +}; + +#endif // nsJSThingHashtable_h__ diff --git a/xpcom/glue/nsMemory.cpp b/xpcom/glue/nsMemory.cpp new file mode 100644 index 0000000000..3bf7c1f0fe --- /dev/null +++ b/xpcom/glue/nsMemory.cpp @@ -0,0 +1,53 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsXPCOM.h" +#include "nsMemory.h" +#include "nsIMemory.h" +#include "nsXPCOMPrivate.h" +#include "nsDebug.h" +#include "nsISupportsUtils.h" +#include "nsCOMPtr.h" + +//////////////////////////////////////////////////////////////////////////////// +// nsMemory static helper routines + +nsresult +nsMemory::HeapMinimize(bool aImmediate) +{ + nsCOMPtr<nsIMemory> mem; + nsresult rv = NS_GetMemoryManager(getter_AddRefs(mem)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + return mem->HeapMinimize(aImmediate); +} + +void* +nsMemory::Clone(const void* aPtr, size_t aSize) +{ + void* newPtr = NS_Alloc(aSize); + if (newPtr) { + memcpy(newPtr, aPtr, aSize); + } + return newPtr; +} + +nsIMemory* +nsMemory::GetGlobalMemoryService() +{ + nsIMemory* mem; + nsresult rv = NS_GetMemoryManager(&mem); + if (NS_FAILED(rv)) { + return nullptr; + } + + return mem; +} + +//---------------------------------------------------------------------- + diff --git a/xpcom/glue/nsMemory.h b/xpcom/glue/nsMemory.h new file mode 100644 index 0000000000..6f19b81171 --- /dev/null +++ b/xpcom/glue/nsMemory.h @@ -0,0 +1,136 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsMemory_h__ +#define nsMemory_h__ + +#include "nsXPCOM.h" + +class nsIMemory; + +#define NS_MEMORY_CONTRACTID "@mozilla.org/xpcom/memory-service;1" +#define NS_MEMORY_CID \ +{ /* 30a04e40-38e7-11d4-8cf5-0060b0fc14a3 */ \ + 0x30a04e40, \ + 0x38e7, \ + 0x11d4, \ + {0x8c, 0xf5, 0x00, 0x60, 0xb0, 0xfc, 0x14, 0xa3} \ +} + + +/** + * Static helper routines to manage memory. These routines allow easy access + * to xpcom's built-in (global) nsIMemory implementation, without needing + * to go through the service manager to get it. However this requires clients + * to link with the xpcom DLL. + * + * This class is not threadsafe and is intented for use only on the main + * thread. + */ +class nsMemory +{ +public: + static nsresult HeapMinimize(bool aImmediate); + static void* Clone(const void* aPtr, size_t aSize); + static nsIMemory* GetGlobalMemoryService(); // AddRefs +}; + +/** + * Macro to free all elements of an XPCOM array of a given size using + * freeFunc, then frees the array itself using free(). + * + * Note that this macro (and its wrappers) can be used to deallocate a + * partially- or completely-built array while unwinding an error + * condition inside the XPCOM routine that was going to return the + * array. For this to work on a partially-built array, your code + * needs to be building the array from index 0 upwards, and simply + * pass the number of elements that have already been built (and thus + * need to be freed) as |size|. + * + * Thanks to <alecf@netscape.com> for suggesting this form, which + * allows the macro to be used with NS_RELEASE / NS_RELEASE_IF in + * addition to free. + * + * @param size Number of elements in the array. If not a constant, this + * should be a int32_t. Note that this means this macro + * will not work if size >= 2^31. + * @param array The array to be freed. + * @param freeFunc The function or macro to be used to free it. + * For arrays of nsISupports (or any class derived + * from it), NS_IF_RELEASE (or NS_RELEASE) should be + * passed as freeFunc. For most (all?) other pointer + * types (including XPCOM strings and wstrings), + * free should be used. + */ +#define NS_FREE_XPCOM_POINTER_ARRAY(size, array, freeFunc) \ + PR_BEGIN_MACRO \ + int32_t iter_ = int32_t(size); \ + while (--iter_ >= 0) \ + freeFunc((array)[iter_]); \ + NS_Free((array)); \ + PR_END_MACRO + +// convenience macros for commonly used calls. mmmmm. syntactic sugar. + +/** + * Macro to free arrays of non-refcounted objects allocated by the + * shared allocator (nsMemory) such as strings and wstrings. A + * convenience wrapper around NS_FREE_XPCOM_POINTER_ARRAY. + * + * @param size Number of elements in the array. If not a constant, this + * should be a int32_t. Note that this means this macro + * will not work if size >= 2^31. + * @param array The array to be freed. + */ +#define NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(size, array) \ + NS_FREE_XPCOM_POINTER_ARRAY((size), (array), NS_Free) + +/** + * Macro to free an array of pointers to nsISupports (or classes + * derived from it). A convenience wrapper around + * NS_FREE_XPCOM_POINTER_ARRAY. + * + * Note that if you know that none of your nsISupports pointers are + * going to be 0, you can gain a bit of speed by calling + * NS_FREE_XPCOM_POINTER_ARRAY directly and using NS_RELEASE as your + * free function. + * + * @param size Number of elements in the array. If not a constant, this + * should be a int32_t. Note that this means this macro + * will not work if size >= 2^31. + * @param array The array to be freed. + */ +#define NS_FREE_XPCOM_ISUPPORTS_POINTER_ARRAY(size, array) \ + NS_FREE_XPCOM_POINTER_ARRAY((size), (array), NS_IF_RELEASE) + +/** + * A macro, NS_ALIGNMENT_OF(t_) that determines the alignment + * requirements of a type. + */ +namespace mozilla { +template<class T> +struct AlignmentTestStruct +{ + char c; + T t; +}; +} // namespace mozilla + +#define NS_ALIGNMENT_OF(t_) \ + (sizeof(mozilla::AlignmentTestStruct<t_>) - sizeof(t_)) + +/** + * An enumeration type used to represent a method of assignment. + */ +enum nsAssignmentType +{ + NS_ASSIGNMENT_COPY, // copy by value + NS_ASSIGNMENT_DEPEND, // copy by reference + NS_ASSIGNMENT_ADOPT // copy by reference (take ownership of resource) +}; + +#endif // nsMemory_h__ + diff --git a/xpcom/glue/nsPointerHashKeys.h b/xpcom/glue/nsPointerHashKeys.h new file mode 100644 index 0000000000..a89843101c --- /dev/null +++ b/xpcom/glue/nsPointerHashKeys.h @@ -0,0 +1,48 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* Definitions for nsPtrHashKey<T> and nsVoidPtrHashKey. */ + +#ifndef nsPointerHashKeys_h +#define nsPointerHashKeys_h + +#include "nscore.h" + +#include "mozilla/Attributes.h" + +/** + * hashkey wrapper using T* KeyType + * + * @see nsTHashtable::EntryType for specification + */ +template<class T> +class nsPtrHashKey : public PLDHashEntryHdr +{ +public: + typedef T* KeyType; + typedef const T* KeyTypePointer; + + explicit nsPtrHashKey(const T* aKey) : mKey(const_cast<T*>(aKey)) {} + nsPtrHashKey(const nsPtrHashKey<T>& aToCopy) : mKey(aToCopy.mKey) {} + ~nsPtrHashKey() {} + + KeyType GetKey() const { return mKey; } + bool KeyEquals(KeyTypePointer aKey) const { return aKey == mKey; } + + static KeyTypePointer KeyToPointer(KeyType aKey) { return aKey; } + static PLDHashNumber HashKey(KeyTypePointer aKey) + { + return NS_PTR_TO_UINT32(aKey) >> 2; + } + enum { ALLOW_MEMMOVE = true }; + +protected: + T* MOZ_NON_OWNING_REF mKey; +}; + +typedef nsPtrHashKey<const void> nsVoidPtrHashKey; + +#endif // nsPointerHashKeys_h diff --git a/xpcom/glue/nsProxyRelease.cpp b/xpcom/glue/nsProxyRelease.cpp new file mode 100644 index 0000000000..1a8150cc6f --- /dev/null +++ b/xpcom/glue/nsProxyRelease.cpp @@ -0,0 +1,21 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsProxyRelease.h" +#include "nsThreadUtils.h" + +namespace detail { + +/* static */ void +ProxyReleaseChooser<true>::ProxyReleaseISupports(nsIEventTarget* aTarget, + nsISupports* aDoomed, + bool aAlwaysProxy) +{ + ::detail::ProxyRelease<nsISupports>(aTarget, dont_AddRef(aDoomed), + aAlwaysProxy); +} + +} // namespace detail diff --git a/xpcom/glue/nsProxyRelease.h b/xpcom/glue/nsProxyRelease.h new file mode 100644 index 0000000000..d99f970b9f --- /dev/null +++ b/xpcom/glue/nsProxyRelease.h @@ -0,0 +1,353 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsProxyRelease_h__ +#define nsProxyRelease_h__ + +#include "nsIEventTarget.h" +#include "nsIThread.h" +#include "nsCOMPtr.h" +#include "nsAutoPtr.h" +#include "MainThreadUtils.h" +#include "nsThreadUtils.h" +#include "mozilla/Likely.h" +#include "mozilla/Move.h" +#include "mozilla/TypeTraits.h" +#include "mozilla/Unused.h" + +#ifdef XPCOM_GLUE_AVOID_NSPR +#error NS_ProxyRelease implementation depends on NSPR. +#endif + +namespace detail { + +template<typename T> +class ProxyReleaseEvent : public mozilla::Runnable +{ +public: + explicit ProxyReleaseEvent(already_AddRefed<T> aDoomed) + : mDoomed(aDoomed.take()) {} + + NS_IMETHOD Run() override + { + NS_IF_RELEASE(mDoomed); + return NS_OK; + } + +private: + T* MOZ_OWNING_REF mDoomed; +}; + +template<typename T> +void +ProxyRelease(nsIEventTarget* aTarget, already_AddRefed<T> aDoomed, bool aAlwaysProxy) +{ + // Auto-managing release of the pointer. + RefPtr<T> doomed = aDoomed; + nsresult rv; + + if (!doomed || !aTarget) { + return; + } + + if (!aAlwaysProxy) { + bool onCurrentThread = false; + rv = aTarget->IsOnCurrentThread(&onCurrentThread); + if (NS_SUCCEEDED(rv) && onCurrentThread) { + return; + } + } + + nsCOMPtr<nsIRunnable> ev = new ProxyReleaseEvent<T>(doomed.forget()); + + rv = aTarget->Dispatch(ev, NS_DISPATCH_NORMAL); + if (NS_FAILED(rv)) { + NS_WARNING("failed to post proxy release event, leaking!"); + // It is better to leak the aDoomed object than risk crashing as + // a result of deleting it on the wrong thread. + } +} + +template<bool nsISupportsBased> +struct ProxyReleaseChooser +{ + template<typename T> + static void ProxyRelease(nsIEventTarget* aTarget, + already_AddRefed<T> aDoomed, + bool aAlwaysProxy) + { + ::detail::ProxyRelease(aTarget, mozilla::Move(aDoomed), aAlwaysProxy); + } +}; + +template<> +struct ProxyReleaseChooser<true> +{ + // We need an intermediate step for handling classes with ambiguous + // inheritance to nsISupports. + template<typename T> + static void ProxyRelease(nsIEventTarget* aTarget, + already_AddRefed<T> aDoomed, + bool aAlwaysProxy) + { + ProxyReleaseISupports(aTarget, ToSupports(aDoomed.take()), aAlwaysProxy); + } + + static void ProxyReleaseISupports(nsIEventTarget* aTarget, + nsISupports* aDoomed, + bool aAlwaysProxy); +}; + +} // namespace detail + +/** + * Ensures that the delete of a smart pointer occurs on the target thread. + * + * @param aTarget + * the target thread where the doomed object should be released. + * @param aDoomed + * the doomed object; the object to be released on the target thread. + * @param aAlwaysProxy + * normally, if NS_ProxyRelease is called on the target thread, then the + * doomed object will be released directly. However, if this parameter is + * true, then an event will always be posted to the target thread for + * asynchronous release. + */ +template<class T> +inline NS_HIDDEN_(void) +NS_ProxyRelease(nsIEventTarget* aTarget, already_AddRefed<T> aDoomed, + bool aAlwaysProxy = false) +{ + ::detail::ProxyReleaseChooser<mozilla::IsBaseOf<nsISupports, T>::value> + ::ProxyRelease(aTarget, mozilla::Move(aDoomed), aAlwaysProxy); +} + +/** + * Ensures that the delete of a smart pointer occurs on the main thread. + * + * @param aDoomed + * the doomed object; the object to be released on the main thread. + * @param aAlwaysProxy + * normally, if NS_ReleaseOnMainThread is called on the main thread, + * then the doomed object will be released directly. However, if this + * parameter is true, then an event will always be posted to the main + * thread for asynchronous release. + */ +template<class T> +inline NS_HIDDEN_(void) +NS_ReleaseOnMainThread(already_AddRefed<T> aDoomed, + bool aAlwaysProxy = false) +{ + // NS_ProxyRelease treats a null event target as "the current thread". So a + // handle on the main thread is only necessary when we're not already on the + // main thread or the release must happen asynchronously. + nsCOMPtr<nsIThread> mainThread; + if (!NS_IsMainThread() || aAlwaysProxy) { + nsresult rv = NS_GetMainThread(getter_AddRefs(mainThread)); + + if (NS_FAILED(rv)) { + MOZ_ASSERT_UNREACHABLE("Could not get main thread; leaking an object!"); + mozilla::Unused << aDoomed.take(); + return; + } + } + + NS_ProxyRelease(mainThread, mozilla::Move(aDoomed), aAlwaysProxy); +} + +/** + * Class to safely handle main-thread-only pointers off the main thread. + * + * Classes like XPCWrappedJS are main-thread-only, which means that it is + * forbidden to call methods on instances of these classes off the main thread. + * For various reasons (see bug 771074), this restriction recently began to + * apply to AddRef/Release as well. + * + * This presents a problem for consumers that wish to hold a callback alive + * on non-main-thread code. A common example of this is the proxy callback + * pattern, where non-main-thread code holds a strong-reference to the callback + * object, and dispatches new Runnables (also with a strong reference) to the + * main thread in order to execute the callback. This involves several AddRef + * and Release calls on the other thread, which is (now) verboten. + * + * The basic idea of this class is to introduce a layer of indirection. + * nsMainThreadPtrHolder is a threadsafe reference-counted class that internally + * maintains one strong reference to the main-thread-only object. It must be + * instantiated on the main thread (so that the AddRef of the underlying object + * happens on the main thread), but consumers may subsequently pass references + * to the holder anywhere they please. These references are meant to be opaque + * when accessed off-main-thread (assertions enforce this). + * + * The semantics of RefPtr<nsMainThreadPtrHolder<T> > would be cumbersome, so + * we also introduce nsMainThreadPtrHandle<T>, which is conceptually identical + * to the above (though it includes various convenience methods). The basic + * pattern is as follows. + * + * // On the main thread: + * nsCOMPtr<nsIFooCallback> callback = ...; + * nsMainThreadPtrHandle<nsIFooCallback> callbackHandle = + * new nsMainThreadPtrHolder<nsIFooCallback>(callback); + * // Pass callbackHandle to structs/classes that might be accessed on other + * // threads. + * + * All structs and classes that might be accessed on other threads should store + * an nsMainThreadPtrHandle<T> rather than an nsCOMPtr<T>. + */ +template<class T> +class nsMainThreadPtrHolder final +{ +public: + // We can only acquire a pointer on the main thread. We to fail fast for + // threading bugs, so by default we assert if our pointer is used or acquired + // off-main-thread. But some consumers need to use the same pointer for + // multiple classes, some of which are main-thread-only and some of which + // aren't. So we allow them to explicitly disable this strict checking. + explicit nsMainThreadPtrHolder(T* aPtr, bool aStrict = true) + : mRawPtr(nullptr) + , mStrict(aStrict) + { + // We can only AddRef our pointer on the main thread, which means that the + // holder must be constructed on the main thread. + MOZ_ASSERT(!mStrict || NS_IsMainThread()); + NS_IF_ADDREF(mRawPtr = aPtr); + } + explicit nsMainThreadPtrHolder(already_AddRefed<T> aPtr, bool aString = true) + : mRawPtr(aPtr.take()) + , mStrict(aString) + { + // Since we don't need to AddRef the pointer, this constructor is safe to + // call on any thread. + } + +private: + // We can be released on any thread. + ~nsMainThreadPtrHolder() + { + if (NS_IsMainThread()) { + NS_IF_RELEASE(mRawPtr); + } else if (mRawPtr) { + NS_ReleaseOnMainThread(dont_AddRef(mRawPtr)); + } + } + +public: + T* get() + { + // Nobody should be touching the raw pointer off-main-thread. + if (mStrict && MOZ_UNLIKELY(!NS_IsMainThread())) { + NS_ERROR("Can't dereference nsMainThreadPtrHolder off main thread"); + MOZ_CRASH(); + } + return mRawPtr; + } + + bool operator==(const nsMainThreadPtrHolder<T>& aOther) const + { + return mRawPtr == aOther.mRawPtr; + } + bool operator!() const + { + return !mRawPtr; + } + + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(nsMainThreadPtrHolder<T>) + +private: + // Our wrapped pointer. + T* mRawPtr; + + // Whether to strictly enforce thread invariants in this class. + bool mStrict; + + // Copy constructor and operator= not implemented. Once constructed, the + // holder is immutable. + T& operator=(nsMainThreadPtrHolder& aOther); + nsMainThreadPtrHolder(const nsMainThreadPtrHolder& aOther); +}; + +template<class T> +class nsMainThreadPtrHandle +{ + RefPtr<nsMainThreadPtrHolder<T>> mPtr; + +public: + nsMainThreadPtrHandle() : mPtr(nullptr) {} + MOZ_IMPLICIT nsMainThreadPtrHandle(decltype(nullptr)) : mPtr(nullptr) {} + explicit nsMainThreadPtrHandle(nsMainThreadPtrHolder<T>* aHolder) + : mPtr(aHolder) + { + } + explicit nsMainThreadPtrHandle( + already_AddRefed<nsMainThreadPtrHolder<T>> aHolder) + : mPtr(aHolder) + { + } + nsMainThreadPtrHandle(const nsMainThreadPtrHandle& aOther) + : mPtr(aOther.mPtr) + { + } + nsMainThreadPtrHandle& operator=(const nsMainThreadPtrHandle& aOther) + { + mPtr = aOther.mPtr; + return *this; + } + nsMainThreadPtrHandle& operator=(nsMainThreadPtrHolder<T>* aHolder) + { + mPtr = aHolder; + return *this; + } + + // These all call through to nsMainThreadPtrHolder, and thus implicitly + // assert that we're on the main thread. Off-main-thread consumers must treat + // these handles as opaque. + T* get() + { + if (mPtr) { + return mPtr.get()->get(); + } + return nullptr; + } + const T* get() const + { + if (mPtr) { + return mPtr.get()->get(); + } + return nullptr; + } + + operator T*() { return get(); } + T* operator->() MOZ_NO_ADDREF_RELEASE_ON_RETURN { return get(); } + + // These are safe to call on other threads with appropriate external locking. + bool operator==(const nsMainThreadPtrHandle<T>& aOther) const + { + if (!mPtr || !aOther.mPtr) { + return mPtr == aOther.mPtr; + } + return *mPtr == *aOther.mPtr; + } + bool operator!=(const nsMainThreadPtrHandle<T>& aOther) const + { + return !operator==(aOther); + } + bool operator==(decltype(nullptr)) const { return mPtr == nullptr; } + bool operator!=(decltype(nullptr)) const { return mPtr != nullptr; } + bool operator!() const { + return !mPtr || !*mPtr; + } +}; + +namespace mozilla { + +template<typename T> +using PtrHolder = nsMainThreadPtrHolder<T>; + +template<typename T> +using PtrHandle = nsMainThreadPtrHandle<T>; + +} // namespace mozilla + +#endif diff --git a/xpcom/glue/nsQuickSort.cpp b/xpcom/glue/nsQuickSort.cpp new file mode 100644 index 0000000000..f409b875e4 --- /dev/null +++ b/xpcom/glue/nsQuickSort.cpp @@ -0,0 +1,187 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ + +/*- + * Copyright (c) 1992, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* We need this because Solaris' version of qsort is broken and + * causes array bounds reads. + */ + +#include <stdlib.h> +#include "nsAlgorithm.h" +#include "nsQuickSort.h" + +extern "C" { + +#if !defined(DEBUG) && (defined(__cplusplus) || defined(__gcc)) +# ifndef INLINE +# define INLINE inline +# endif +#else +# define INLINE +#endif + +typedef int cmp_t(const void *, const void *, void *); +static INLINE char *med3(char *, char *, char *, cmp_t *, void *); +static INLINE void swapfunc(char *, char *, int, int); + +/* + * Qsort routine from Bentley & McIlroy's "Engineering a Sort Function". + */ +#define swapcode(TYPE, parmi, parmj, n) { \ + long i = (n) / sizeof (TYPE); \ + TYPE *pi = (TYPE *) (parmi); \ + TYPE *pj = (TYPE *) (parmj); \ + do { \ + TYPE t = *pi; \ + *pi++ = *pj; \ + *pj++ = t; \ + } while (--i > 0); \ +} + +#define SWAPINIT(a, es) swaptype = ((char *)a - (char *)0) % sizeof(long) || \ + es % sizeof(long) ? 2 : es == sizeof(long)? 0 : 1; + +static INLINE void +swapfunc(char *a, char *b, int n, int swaptype) +{ + if(swaptype <= 1) + swapcode(long, a, b, n) + else + swapcode(char, a, b, n) +} + +#define swap(a, b) \ + if (swaptype == 0) { \ + long t = *(long *)(a); \ + *(long *)(a) = *(long *)(b); \ + *(long *)(b) = t; \ + } else \ + swapfunc((char *)a, (char*)b, (int)es, swaptype) + +#define vecswap(a, b, n) if ((n) > 0) swapfunc((char *)a, (char *)b, (int)n, swaptype) + +static INLINE char * +med3(char *a, char *b, char *c, cmp_t* cmp, void *data) +{ + return cmp(a, b, data) < 0 ? + (cmp(b, c, data) < 0 ? b : (cmp(a, c, data) < 0 ? c : a )) + :(cmp(b, c, data) > 0 ? b : (cmp(a, c, data) < 0 ? a : c )); +} + +void NS_QuickSort ( + void *a, + unsigned int n, + unsigned int es, + cmp_t *cmp, + void *data + ) +{ + char *pa, *pb, *pc, *pd, *pl, *pm, *pn; + int d, r, swaptype; + +loop: SWAPINIT(a, es); + /* Use insertion sort when input is small */ + if (n < 7) { + for (pm = (char *)a + es; pm < (char *)a + n * es; pm += es) + for (pl = pm; pl > (char *)a && cmp(pl - es, pl, data) > 0; + pl -= es) + swap(pl, pl - es); + return; + } + /* Choose pivot */ + pm = (char *)a + (n / 2) * es; + if (n > 7) { + pl = (char *)a; + pn = (char *)a + (n - 1) * es; + if (n > 40) { + d = (n / 8) * es; + pl = med3(pl, pl + d, pl + 2 * d, cmp, data); + pm = med3(pm - d, pm, pm + d, cmp, data); + pn = med3(pn - 2 * d, pn - d, pn, cmp, data); + } + pm = med3(pl, pm, pn, cmp, data); + } + swap(a, pm); + pa = pb = (char *)a + es; + + pc = pd = (char *)a + (n - 1) * es; + /* loop invariants: + * [a, pa) = pivot + * [pa, pb) < pivot + * [pb, pc + es) unprocessed + * [pc + es, pd + es) > pivot + * [pd + es, pn) = pivot + */ + for (;;) { + while (pb <= pc && (r = cmp(pb, a, data)) <= 0) { + if (r == 0) { + swap(pa, pb); + pa += es; + } + pb += es; + } + while (pb <= pc && (r = cmp(pc, a, data)) >= 0) { + if (r == 0) { + swap(pc, pd); + pd -= es; + } + pc -= es; + } + if (pb > pc) + break; + swap(pb, pc); + pb += es; + pc -= es; + } + /* Move pivot values */ + pn = (char *)a + n * es; + r = XPCOM_MIN(pa - (char *)a, pb - pa); + vecswap(a, pb - r, r); + r = XPCOM_MIN<size_t>(pd - pc, pn - pd - es); + vecswap(pb, pn - r, r); + /* Recursively process partitioned items */ + if ((r = pb - pa) > (int)es) + NS_QuickSort(a, r / es, es, cmp, data); + if ((r = pd - pc) > (int)es) { + /* Iterate rather than recurse to save stack space */ + a = pn - r; + n = r / es; + goto loop; + } +/* NS_QuickSort(pn - r, r / es, es, cmp, data);*/ +} + +} + +#undef INLINE +#undef swapcode +#undef SWAPINIT +#undef swap +#undef vecswap diff --git a/xpcom/glue/nsQuickSort.h b/xpcom/glue/nsQuickSort.h new file mode 100644 index 0000000000..e8d8ed8709 --- /dev/null +++ b/xpcom/glue/nsQuickSort.h @@ -0,0 +1,41 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + + +/* We need this because Solaris' version of qsort is broken and + * causes array bounds reads. + */ + +#ifndef nsQuickSort_h___ +#define nsQuickSort_h___ + +#include "nscore.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Parameters: + * 1. the array to sort + * 2. the number of elements in the array + * 3. the size of each array element + * 4. comparison function taking two elements and parameter #5 and + * returning an integer: + * + less than zero if the first element should be before the second + * + 0 if the order of the elements does not matter + * + greater than zero if the second element should be before the first + * 5. extra data to pass to comparison function + */ +void NS_QuickSort(void*, unsigned int, unsigned int, + int (*)(const void*, const void*, void*), + void*); + +#ifdef __cplusplus +} +#endif + +#endif /* nsQuickSort_h___ */ diff --git a/xpcom/glue/nsRefPtrHashtable.h b/xpcom/glue/nsRefPtrHashtable.h new file mode 100644 index 0000000000..0ff1e0181d --- /dev/null +++ b/xpcom/glue/nsRefPtrHashtable.h @@ -0,0 +1,191 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsRefPtrHashtable_h__ +#define nsRefPtrHashtable_h__ + +#include "nsBaseHashtable.h" +#include "nsHashKeys.h" +#include "nsAutoPtr.h" + +/** + * templated hashtable class maps keys to reference pointers. + * See nsBaseHashtable for complete declaration. + * @param KeyClass a wrapper-class for the hashtable key, see nsHashKeys.h + * for a complete specification. + * @param PtrType the reference-type being wrapped + * @see nsDataHashtable, nsClassHashtable + */ +template<class KeyClass, class PtrType> +class nsRefPtrHashtable + : public nsBaseHashtable<KeyClass, RefPtr<PtrType>, PtrType*> +{ +public: + typedef typename KeyClass::KeyType KeyType; + typedef PtrType* UserDataType; + typedef nsBaseHashtable<KeyClass, RefPtr<PtrType>, PtrType*> base_type; + + nsRefPtrHashtable() {} + explicit nsRefPtrHashtable(uint32_t aInitLength) + : nsBaseHashtable<KeyClass, RefPtr<PtrType>, PtrType*>(aInitLength) + { + } + + /** + * @copydoc nsBaseHashtable::Get + * @param aData This is an XPCOM getter, so aData is already_addrefed. + * If the key doesn't exist, aData will be set to nullptr. + */ + bool Get(KeyType aKey, UserDataType* aData) const; + + /** + * Gets a weak reference to the hashtable entry. + * @param aFound If not nullptr, will be set to true if the entry is found, + * to false otherwise. + * @return The entry, or nullptr if not found. Do not release this pointer! + */ + PtrType* GetWeak(KeyType aKey, bool* aFound = nullptr) const; + + // Overload Put, rather than overriding it. + using base_type::Put; + + void Put(KeyType aKey, already_AddRefed<PtrType> aData); + + MOZ_MUST_USE bool Put(KeyType aKey, already_AddRefed<PtrType> aData, + const mozilla::fallible_t&); + + // Overload Remove, rather than overriding it. + using base_type::Remove; + + /** + * Remove the data for the associated key, swapping the current value into + * pData, thereby avoiding calls to AddRef and Release. + * @param aKey the key to remove from the hashtable + * @param aData This is an XPCOM getter, so aData is already_addrefed. + * If the key doesn't exist, aData will be set to nullptr. Must be non-null. + */ + bool Remove(KeyType aKey, UserDataType* aData); +}; + +template<typename K, typename T> +inline void +ImplCycleCollectionUnlink(nsRefPtrHashtable<K, T>& aField) +{ + aField.Clear(); +} + +template<typename K, typename T> +inline void +ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback& aCallback, + nsRefPtrHashtable<K, T>& aField, + const char* aName, + uint32_t aFlags = 0) +{ + for (auto iter = aField.ConstIter(); !iter.Done(); iter.Next()) { + CycleCollectionNoteChild(aCallback, iter.UserData(), aName, aFlags); + } +} + +// +// nsRefPtrHashtable definitions +// + +template<class KeyClass, class PtrType> +bool +nsRefPtrHashtable<KeyClass, PtrType>::Get(KeyType aKey, + UserDataType* aRefPtr) const +{ + typename base_type::EntryType* ent = this->GetEntry(aKey); + + if (ent) { + if (aRefPtr) { + *aRefPtr = ent->mData; + + NS_IF_ADDREF(*aRefPtr); + } + + return true; + } + + // if the key doesn't exist, set *aRefPtr to null + // so that it is a valid XPCOM getter + if (aRefPtr) { + *aRefPtr = nullptr; + } + + return false; +} + +template<class KeyClass, class PtrType> +PtrType* +nsRefPtrHashtable<KeyClass, PtrType>::GetWeak(KeyType aKey, bool* aFound) const +{ + typename base_type::EntryType* ent = this->GetEntry(aKey); + + if (ent) { + if (aFound) { + *aFound = true; + } + + return ent->mData; + } + + // Key does not exist, return nullptr and set aFound to false + if (aFound) { + *aFound = false; + } + + return nullptr; +} + +template<class KeyClass, class PtrType> +void +nsRefPtrHashtable<KeyClass, PtrType>::Put(KeyType aKey, + already_AddRefed<PtrType> aData) +{ + if (!Put(aKey, mozilla::Move(aData), mozilla::fallible)) { + NS_ABORT_OOM(this->mTable.EntrySize() * this->mTable.EntryCount()); + } +} + +template<class KeyClass, class PtrType> +bool +nsRefPtrHashtable<KeyClass, PtrType>::Put(KeyType aKey, + already_AddRefed<PtrType> aData, + const mozilla::fallible_t&) +{ + typename base_type::EntryType* ent = this->PutEntry(aKey); + + if (!ent) { + return false; + } + + ent->mData = aData; + + return true; +} + +template<class KeyClass, class PtrType> +bool +nsRefPtrHashtable<KeyClass, PtrType>::Remove(KeyType aKey, + UserDataType* aRefPtr) +{ + MOZ_ASSERT(aRefPtr); + typename base_type::EntryType* ent = this->GetEntry(aKey); + + if (ent) { + ent->mData.forget(aRefPtr); + this->Remove(aKey); + return true; + } + + // If the key doesn't exist, set *aRefPtr to null + // so that it is a valid XPCOM getter. + *aRefPtr = nullptr; + return false; +} + +#endif // nsRefPtrHashtable_h__ diff --git a/xpcom/glue/nsServiceManagerUtils.h b/xpcom/glue/nsServiceManagerUtils.h new file mode 100644 index 0000000000..d1ea408a2a --- /dev/null +++ b/xpcom/glue/nsServiceManagerUtils.h @@ -0,0 +1,94 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsServiceManagerUtils_h__ +#define nsServiceManagerUtils_h__ + +#include "nsIServiceManager.h" +#include "nsCOMPtr.h" + +inline const nsGetServiceByCID +do_GetService(const nsCID& aCID) +{ + return nsGetServiceByCID(aCID); +} + +inline const nsGetServiceByCIDWithError +do_GetService(const nsCID& aCID, nsresult* aError) +{ + return nsGetServiceByCIDWithError(aCID, aError); +} + +inline const nsGetServiceByContractID +do_GetService(const char* aContractID) +{ + return nsGetServiceByContractID(aContractID); +} + +inline const nsGetServiceByContractIDWithError +do_GetService(const char* aContractID, nsresult* aError) +{ + return nsGetServiceByContractIDWithError(aContractID, aError); +} + +class MOZ_STACK_CLASS nsGetServiceFromCategory final : public nsCOMPtr_helper +{ +public: + nsGetServiceFromCategory(const char* aCategory, const char* aEntry, + nsresult* aErrorPtr) + : mCategory(aCategory) + , mEntry(aEntry) + , mErrorPtr(aErrorPtr) + { + } + + virtual nsresult NS_FASTCALL operator()(const nsIID&, void**) const + override; +protected: + const char* mCategory; + const char* mEntry; + nsresult* mErrorPtr; +}; + +inline const nsGetServiceFromCategory +do_GetServiceFromCategory(const char* aCategory, const char* aEntry, + nsresult* aError = 0) +{ + return nsGetServiceFromCategory(aCategory, aEntry, aError); +} + +nsresult CallGetService(const nsCID& aClass, const nsIID& aIID, void** aResult); + +nsresult CallGetService(const char* aContractID, const nsIID& aIID, + void** aResult); + +// type-safe shortcuts for calling |GetService| +template<class DestinationType> +inline nsresult +CallGetService(const nsCID& aClass, + DestinationType** aDestination) +{ + NS_PRECONDITION(aDestination, "null parameter"); + + return CallGetService(aClass, + NS_GET_TEMPLATE_IID(DestinationType), + reinterpret_cast<void**>(aDestination)); +} + +template<class DestinationType> +inline nsresult +CallGetService(const char* aContractID, + DestinationType** aDestination) +{ + NS_PRECONDITION(aContractID, "null parameter"); + NS_PRECONDITION(aDestination, "null parameter"); + + return CallGetService(aContractID, + NS_GET_TEMPLATE_IID(DestinationType), + reinterpret_cast<void**>(aDestination)); +} + +#endif diff --git a/xpcom/glue/nsStringAPI.cpp b/xpcom/glue/nsStringAPI.cpp new file mode 100644 index 0000000000..e5114a149a --- /dev/null +++ b/xpcom/glue/nsStringAPI.cpp @@ -0,0 +1,1304 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nscore.h" +#include "nsCRTGlue.h" +#include "prprf.h" +#include "nsStringAPI.h" +#include "nsXPCOMStrings.h" +#include "nsDebug.h" + +#include "mozilla/Sprintf.h" + +#include <stdio.h> + +// nsAString + +uint32_t +nsAString::BeginReading(const char_type** aBegin, const char_type** aEnd) const +{ + uint32_t len = NS_StringGetData(*this, aBegin); + if (aEnd) { + *aEnd = *aBegin + len; + } + + return len; +} + +const nsAString::char_type* +nsAString::BeginReading() const +{ + const char_type* data; + NS_StringGetData(*this, &data); + return data; +} + +const nsAString::char_type* +nsAString::EndReading() const +{ + const char_type* data; + uint32_t len = NS_StringGetData(*this, &data); + return data + len; +} + +uint32_t +nsAString::BeginWriting(char_type** aBegin, char_type** aEnd, uint32_t aNewSize) +{ + uint32_t len = NS_StringGetMutableData(*this, aNewSize, aBegin); + if (aEnd) { + *aEnd = *aBegin + len; + } + + return len; +} + +nsAString::char_type* +nsAString::BeginWriting(uint32_t aLen) +{ + char_type* data; + NS_StringGetMutableData(*this, aLen, &data); + return data; +} + +nsAString::char_type* +nsAString::EndWriting() +{ + char_type* data; + uint32_t len = NS_StringGetMutableData(*this, UINT32_MAX, &data); + return data + len; +} + +bool +nsAString::SetLength(uint32_t aLen) +{ + char_type* data; + NS_StringGetMutableData(*this, aLen, &data); + return data != nullptr; +} + +void +nsAString::AssignLiteral(const char* aStr) +{ + uint32_t len = strlen(aStr); + char16_t* buf = BeginWriting(len); + if (!buf) { + return; + } + + for (; *aStr; ++aStr, ++buf) { + *buf = *aStr; + } +} + +void +nsAString::AppendLiteral(const char* aASCIIStr) +{ + uint32_t appendLen = strlen(aASCIIStr); + + uint32_t thisLen = Length(); + char16_t* begin; + char16_t* end; + BeginWriting(&begin, &end, appendLen + thisLen); + if (!begin) { + return; + } + + for (begin += thisLen; begin < end; ++begin, ++aASCIIStr) { + *begin = *aASCIIStr; + } +} + +void +nsAString::StripChars(const char* aSet) +{ + nsString copy(*this); + + const char_type* source; + const char_type* sourceEnd; + copy.BeginReading(&source, &sourceEnd); + + char_type* dest; + BeginWriting(&dest); + if (!dest) { + return; + } + + char_type* curDest = dest; + + for (; source < sourceEnd; ++source) { + const char* test; + for (test = aSet; *test; ++test) { + if (*source == char_type(*test)) { + break; + } + } + + if (!*test) { + // not stripped, copy this char + *curDest = *source; + ++curDest; + } + } + + SetLength(curDest - dest); +} + +void +nsAString::Trim(const char* aSet, bool aLeading, bool aTrailing) +{ + NS_ASSERTION(aLeading || aTrailing, "Ineffective Trim"); + + const char16_t* start; + const char16_t* end; + uint32_t cutLen; + + if (aLeading) { + BeginReading(&start, &end); + for (cutLen = 0; start < end; ++start, ++cutLen) { + const char* test; + for (test = aSet; *test; ++test) { + if (*test == *start) { + break; + } + } + if (!*test) { + break; + } + } + if (cutLen) { + NS_StringCutData(*this, 0, cutLen); + } + } + if (aTrailing) { + uint32_t len = BeginReading(&start, &end); + --end; + for (cutLen = 0; end >= start; --end, ++cutLen) { + const char* test; + for (test = aSet; *test; ++test) { + if (*test == *end) { + break; + } + } + if (!*test) { + break; + } + } + if (cutLen) { + NS_StringCutData(*this, len - cutLen, cutLen); + } + } +} + +int32_t +nsAString::DefaultComparator(const char_type* aStrA, const char_type* aStrB, + uint32_t aLen) +{ + for (const char_type* end = aStrA + aLen; aStrA < end; ++aStrA, ++aStrB) { + if (*aStrA == *aStrB) { + continue; + } + + return *aStrA < *aStrB ? -1 : 1; + } + + return 0; +} + +int32_t +nsAString::Compare(const char_type* aOther, ComparatorFunc aComparator) const +{ + const char_type* cself; + uint32_t selflen = NS_StringGetData(*this, &cself); + uint32_t otherlen = NS_strlen(aOther); + uint32_t comparelen = selflen <= otherlen ? selflen : otherlen; + + int32_t result = aComparator(cself, aOther, comparelen); + if (result == 0) { + if (selflen < otherlen) { + return -1; + } else if (selflen > otherlen) { + return 1; + } + } + return result; +} + +int32_t +nsAString::Compare(const self_type& aOther, ComparatorFunc aComparator) const +{ + const char_type* cself; + const char_type* cother; + uint32_t selflen = NS_StringGetData(*this, &cself); + uint32_t otherlen = NS_StringGetData(aOther, &cother); + uint32_t comparelen = selflen <= otherlen ? selflen : otherlen; + + int32_t result = aComparator(cself, cother, comparelen); + if (result == 0) { + if (selflen < otherlen) { + return -1; + } else if (selflen > otherlen) { + return 1; + } + } + return result; +} + +bool +nsAString::Equals(const char_type* aOther, ComparatorFunc aComparator) const +{ + const char_type* cself; + uint32_t selflen = NS_StringGetData(*this, &cself); + uint32_t otherlen = NS_strlen(aOther); + + if (selflen != otherlen) { + return false; + } + + return aComparator(cself, aOther, selflen) == 0; +} + +bool +nsAString::Equals(const self_type& aOther, ComparatorFunc aComparator) const +{ + const char_type* cself; + const char_type* cother; + uint32_t selflen = NS_StringGetData(*this, &cself); + uint32_t otherlen = NS_StringGetData(aOther, &cother); + + if (selflen != otherlen) { + return false; + } + + return aComparator(cself, cother, selflen) == 0; +} + +bool +nsAString::EqualsLiteral(const char* aASCIIString) const +{ + const char16_t* begin; + const char16_t* end; + BeginReading(&begin, &end); + + for (; begin < end; ++begin, ++aASCIIString) { + if (!*aASCIIString || !NS_IsAscii(*begin) || + (char)*begin != *aASCIIString) { + return false; + } + } + + return *aASCIIString == '\0'; +} + +bool +nsAString::LowerCaseEqualsLiteral(const char* aASCIIString) const +{ + const char16_t* begin; + const char16_t* end; + BeginReading(&begin, &end); + + for (; begin < end; ++begin, ++aASCIIString) { + if (!*aASCIIString || !NS_IsAscii(*begin) || + NS_ToLower((char)*begin) != *aASCIIString) { + return false; + } + } + + return *aASCIIString == '\0'; +} + +int32_t +nsAString::Find(const self_type& aStr, uint32_t aOffset, + ComparatorFunc aComparator) const +{ + const char_type* begin; + const char_type* end; + uint32_t selflen = BeginReading(&begin, &end); + + if (aOffset > selflen) { + return -1; + } + + const char_type* other; + uint32_t otherlen = aStr.BeginReading(&other); + + if (otherlen > selflen - aOffset) { + return -1; + } + + // We want to stop searching otherlen characters before the end of the string + end -= otherlen; + + for (const char_type* cur = begin + aOffset; cur <= end; ++cur) { + if (!aComparator(cur, other, otherlen)) { + return cur - begin; + } + } + return -1; +} + +static bool +ns_strnmatch(const char16_t* aStr, const char* aSubstring, uint32_t aLen) +{ + for (; aLen; ++aStr, ++aSubstring, --aLen) { + if (!NS_IsAscii(*aStr)) { + return false; + } + + if ((char)*aStr != *aSubstring) { + return false; + } + } + + return true; +} + +static bool +ns_strnimatch(const char16_t* aStr, const char* aSubstring, uint32_t aLen) +{ + for (; aLen; ++aStr, ++aSubstring, --aLen) { + if (!NS_IsAscii(*aStr)) { + return false; + } + + if (NS_ToLower((char)*aStr) != NS_ToLower(*aSubstring)) { + return false; + } + } + + return true; +} + +int32_t +nsAString::Find(const char* aStr, uint32_t aOffset, bool aIgnoreCase) const +{ + bool (*match)(const char16_t*, const char*, uint32_t) = + aIgnoreCase ? ns_strnimatch : ns_strnmatch; + + const char_type* begin; + const char_type* end; + uint32_t selflen = BeginReading(&begin, &end); + + if (aOffset > selflen) { + return -1; + } + + uint32_t otherlen = strlen(aStr); + + if (otherlen > selflen - aOffset) { + return -1; + } + + // We want to stop searching otherlen characters before the end of the string + end -= otherlen; + + for (const char_type* cur = begin + aOffset; cur <= end; ++cur) { + if (match(cur, aStr, otherlen)) { + return cur - begin; + } + } + return -1; +} + +int32_t +nsAString::RFind(const self_type& aStr, int32_t aOffset, + ComparatorFunc aComparator) const +{ + const char_type* begin; + const char_type* end; + uint32_t selflen = BeginReading(&begin, &end); + + const char_type* other; + uint32_t otherlen = aStr.BeginReading(&other); + + if (selflen < otherlen) { + return -1; + } + + if (aOffset < 0 || uint32_t(aOffset) > (selflen - otherlen)) { + end -= otherlen; + } else { + end = begin + aOffset; + } + + for (const char_type* cur = end; cur >= begin; --cur) { + if (!aComparator(cur, other, otherlen)) { + return cur - begin; + } + } + return -1; +} + +int32_t +nsAString::RFind(const char* aStr, int32_t aOffset, bool aIgnoreCase) const +{ + bool (*match)(const char16_t*, const char*, uint32_t) = + aIgnoreCase ? ns_strnimatch : ns_strnmatch; + + const char_type* begin; + const char_type* end; + uint32_t selflen = BeginReading(&begin, &end); + uint32_t otherlen = strlen(aStr); + + if (selflen < otherlen) { + return -1; + } + + if (aOffset < 0 || uint32_t(aOffset) > (selflen - otherlen)) { + end -= otherlen; + } else { + end = begin + aOffset; + } + + for (const char_type* cur = end; cur >= begin; --cur) { + if (match(cur, aStr, otherlen)) { + return cur - begin; + } + } + return -1; +} + +int32_t +nsAString::FindChar(char_type aChar, uint32_t aOffset) const +{ + const char_type* start; + const char_type* end; + uint32_t len = BeginReading(&start, &end); + if (aOffset > len) { + return -1; + } + + const char_type* cur; + + for (cur = start + aOffset; cur < end; ++cur) { + if (*cur == aChar) { + return cur - start; + } + } + + return -1; +} + +int32_t +nsAString::RFindChar(char_type aChar) const +{ + const char16_t* start; + const char16_t* end; + BeginReading(&start, &end); + + do { + --end; + + if (*end == aChar) { + return end - start; + } + + } while (end >= start); + + return -1; +} + +void +nsAString::AppendInt(int aInt, int32_t aRadix) +{ + const char* fmt; + switch (aRadix) { + case 8: + fmt = "%o"; + break; + + case 10: + fmt = "%d"; + break; + + case 16: + fmt = "%x"; + break; + + default: + NS_ERROR("Unrecognized radix"); + fmt = ""; + } + + char buf[20]; + int len = SprintfLiteral(buf, fmt, aInt); + Append(NS_ConvertASCIItoUTF16(buf, len)); +} + +// Strings + +#ifndef XPCOM_GLUE_AVOID_NSPR +int32_t +nsAString::ToInteger(nsresult* aErrorCode, uint32_t aRadix) const +{ + NS_ConvertUTF16toUTF8 narrow(*this); + + const char* fmt; + switch (aRadix) { + case 10: + fmt = "%i"; + break; + + case 16: + fmt = "%x"; + break; + + default: + NS_ERROR("Unrecognized radix!"); + *aErrorCode = NS_ERROR_INVALID_ARG; + return 0; + } + + int32_t result = 0; + if (PR_sscanf(narrow.get(), fmt, &result) == 1) { + *aErrorCode = NS_OK; + } else { + *aErrorCode = NS_ERROR_FAILURE; + } + + return result; +} + +int64_t +nsAString::ToInteger64(nsresult* aErrorCode, uint32_t aRadix) const +{ + NS_ConvertUTF16toUTF8 narrow(*this); + + const char* fmt; + switch (aRadix) { + case 10: + fmt = "%lli"; + break; + + case 16: + fmt = "%llx"; + break; + + default: + NS_ERROR("Unrecognized radix!"); + *aErrorCode = NS_ERROR_INVALID_ARG; + return 0; + } + + int64_t result = 0; + if (PR_sscanf(narrow.get(), fmt, &result) == 1) { + *aErrorCode = NS_OK; + } else { + *aErrorCode = NS_ERROR_FAILURE; + } + + return result; +} +#endif // XPCOM_GLUE_AVOID_NSPR + +// nsACString + +uint32_t +nsACString::BeginReading(const char_type** aBegin, const char_type** aEnd) const +{ + uint32_t len = NS_CStringGetData(*this, aBegin); + if (aEnd) { + *aEnd = *aBegin + len; + } + + return len; +} + +const nsACString::char_type* +nsACString::BeginReading() const +{ + const char_type* data; + NS_CStringGetData(*this, &data); + return data; +} + +const nsACString::char_type* +nsACString::EndReading() const +{ + const char_type* data; + uint32_t len = NS_CStringGetData(*this, &data); + return data + len; +} + +uint32_t +nsACString::BeginWriting(char_type** aBegin, char_type** aEnd, + uint32_t aNewSize) +{ + uint32_t len = NS_CStringGetMutableData(*this, aNewSize, aBegin); + if (aEnd) { + *aEnd = *aBegin + len; + } + + return len; +} + +nsACString::char_type* +nsACString::BeginWriting(uint32_t aLen) +{ + char_type* data; + NS_CStringGetMutableData(*this, aLen, &data); + return data; +} + +nsACString::char_type* +nsACString::EndWriting() +{ + char_type* data; + uint32_t len = NS_CStringGetMutableData(*this, UINT32_MAX, &data); + return data + len; +} + +bool +nsACString::SetLength(uint32_t aLen) +{ + char_type* data; + NS_CStringGetMutableData(*this, aLen, &data); + return data != nullptr; +} + +void +nsACString::StripChars(const char* aSet) +{ + nsCString copy(*this); + + const char_type* source; + const char_type* sourceEnd; + copy.BeginReading(&source, &sourceEnd); + + char_type* dest; + BeginWriting(&dest); + if (!dest) { + return; + } + + char_type* curDest = dest; + + for (; source < sourceEnd; ++source) { + const char* test; + for (test = aSet; *test; ++test) { + if (*source == char_type(*test)) { + break; + } + } + + if (!*test) { + // not stripped, copy this char + *curDest = *source; + ++curDest; + } + } + + SetLength(curDest - dest); +} + +void +nsACString::Trim(const char* aSet, bool aLeading, bool aTrailing) +{ + NS_ASSERTION(aLeading || aTrailing, "Ineffective Trim"); + + const char* start; + const char* end; + uint32_t cutLen; + + if (aLeading) { + BeginReading(&start, &end); + for (cutLen = 0; start < end; ++start, ++cutLen) { + const char* test; + for (test = aSet; *test; ++test) { + if (*test == *start) { + break; + } + } + if (!*test) { + break; + } + } + if (cutLen) { + NS_CStringCutData(*this, 0, cutLen); + } + } + if (aTrailing) { + uint32_t len = BeginReading(&start, &end); + --end; + for (cutLen = 0; end >= start; --end, ++cutLen) { + const char* test; + for (test = aSet; *test; ++test) { + if (*test == *end) { + break; + } + } + if (!*test) { + break; + } + } + if (cutLen) { + NS_CStringCutData(*this, len - cutLen, cutLen); + } + } +} + +int32_t +nsACString::DefaultComparator(const char_type* aStrA, const char_type* aStrB, + uint32_t aLen) +{ + return memcmp(aStrA, aStrB, aLen); +} + +int32_t +nsACString::Compare(const char_type* aOther, ComparatorFunc aComparator) const +{ + const char_type* cself; + uint32_t selflen = NS_CStringGetData(*this, &cself); + uint32_t otherlen = strlen(aOther); + uint32_t comparelen = selflen <= otherlen ? selflen : otherlen; + + int32_t result = aComparator(cself, aOther, comparelen); + if (result == 0) { + if (selflen < otherlen) { + return -1; + } else if (selflen > otherlen) { + return 1; + } + } + return result; +} + +int32_t +nsACString::Compare(const self_type& aOther, ComparatorFunc aComparator) const +{ + const char_type* cself; + const char_type* cother; + uint32_t selflen = NS_CStringGetData(*this, &cself); + uint32_t otherlen = NS_CStringGetData(aOther, &cother); + uint32_t comparelen = selflen <= otherlen ? selflen : otherlen; + + int32_t result = aComparator(cself, cother, comparelen); + if (result == 0) { + if (selflen < otherlen) { + return -1; + } else if (selflen > otherlen) { + return 1; + } + } + return result; +} + +bool +nsACString::Equals(const char_type* aOther, ComparatorFunc aComparator) const +{ + const char_type* cself; + uint32_t selflen = NS_CStringGetData(*this, &cself); + uint32_t otherlen = strlen(aOther); + + if (selflen != otherlen) { + return false; + } + + return aComparator(cself, aOther, selflen) == 0; +} + +bool +nsACString::Equals(const self_type& aOther, ComparatorFunc aComparator) const +{ + const char_type* cself; + const char_type* cother; + uint32_t selflen = NS_CStringGetData(*this, &cself); + uint32_t otherlen = NS_CStringGetData(aOther, &cother); + + if (selflen != otherlen) { + return false; + } + + return aComparator(cself, cother, selflen) == 0; +} + +int32_t +nsACString::Find(const self_type& aStr, uint32_t aOffset, + ComparatorFunc aComparator) const +{ + const char_type* begin; + const char_type* end; + uint32_t selflen = BeginReading(&begin, &end); + + if (aOffset > selflen) { + return -1; + } + + const char_type* other; + uint32_t otherlen = aStr.BeginReading(&other); + + if (otherlen > selflen - aOffset) { + return -1; + } + + // We want to stop searching otherlen characters before the end of the string + end -= otherlen; + + for (const char_type* cur = begin + aOffset; cur <= end; ++cur) { + if (!aComparator(cur, other, otherlen)) { + return cur - begin; + } + } + return -1; +} + +int32_t +nsACString::Find(const char_type* aStr, ComparatorFunc aComparator) const +{ + return Find(aStr, strlen(aStr), aComparator); +} + +int32_t +nsACString::Find(const char_type* aStr, uint32_t aLen, + ComparatorFunc aComparator) const +{ + const char_type* begin; + const char_type* end; + uint32_t selflen = BeginReading(&begin, &end); + + if (aLen == 0) { + NS_WARNING("Searching for zero-length string."); + return -1; + } + + if (aLen > selflen) { + return -1; + } + + // We want to stop searching otherlen characters before the end of the string + end -= aLen; + + for (const char_type* cur = begin; cur <= end; ++cur) { + if (!aComparator(cur, aStr, aLen)) { + return cur - begin; + } + } + return -1; +} + +int32_t +nsACString::RFind(const self_type& aStr, int32_t aOffset, + ComparatorFunc aComparator) const +{ + const char_type* begin; + const char_type* end; + uint32_t selflen = BeginReading(&begin, &end); + + const char_type* other; + uint32_t otherlen = aStr.BeginReading(&other); + + if (selflen < otherlen) { + return -1; + } + + if (aOffset < 0 || uint32_t(aOffset) > (selflen - otherlen)) { + end -= otherlen; + } else { + end = begin + aOffset; + } + + for (const char_type* cur = end; cur >= begin; --cur) { + if (!aComparator(cur, other, otherlen)) { + return cur - begin; + } + } + return -1; +} + +int32_t +nsACString::RFind(const char_type* aStr, ComparatorFunc aComparator) const +{ + return RFind(aStr, strlen(aStr), aComparator); +} + +int32_t +nsACString::RFind(const char_type* aStr, int32_t aLen, + ComparatorFunc aComparator) const +{ + const char_type* begin; + const char_type* end; + uint32_t selflen = BeginReading(&begin, &end); + + if (aLen <= 0) { + NS_WARNING("Searching for zero-length string."); + return -1; + } + + if (uint32_t(aLen) > selflen) { + return -1; + } + + // We want to start searching otherlen characters before the end of the string + end -= aLen; + + for (const char_type* cur = end; cur >= begin; --cur) { + if (!aComparator(cur, aStr, aLen)) { + return cur - begin; + } + } + return -1; +} + +int32_t +nsACString::FindChar(char_type aChar, uint32_t aOffset) const +{ + const char_type* start; + const char_type* end; + uint32_t len = BeginReading(&start, &end); + if (aOffset > len) { + return -1; + } + + const char_type* cur; + + for (cur = start + aOffset; cur < end; ++cur) { + if (*cur == aChar) { + return cur - start; + } + } + + return -1; +} + +int32_t +nsACString::RFindChar(char_type aChar) const +{ + const char* start; + const char* end; + BeginReading(&start, &end); + + for (; end >= start; --end) { + if (*end == aChar) { + return end - start; + } + } + + return -1; +} + +void +nsACString::AppendInt(int aInt, int32_t aRadix) +{ + const char* fmt; + switch (aRadix) { + case 8: + fmt = "%o"; + break; + + case 10: + fmt = "%d"; + break; + + case 16: + fmt = "%x"; + break; + + default: + NS_ERROR("Unrecognized radix"); + fmt = ""; + } + + char buf[20]; + int len = SprintfLiteral(buf, fmt, aInt); + Append(buf, len); +} + +#ifndef XPCOM_GLUE_AVOID_NSPR +int32_t +nsACString::ToInteger(nsresult* aErrorCode, uint32_t aRadix) const +{ + const char* fmt; + switch (aRadix) { + case 10: + fmt = "%i"; + break; + + case 16: + fmt = "%x"; + break; + + default: + NS_ERROR("Unrecognized radix!"); + *aErrorCode = NS_ERROR_INVALID_ARG; + return 0; + } + + int32_t result = 0; + if (PR_sscanf(nsCString(*this).get(), fmt, &result) == 1) { + *aErrorCode = NS_OK; + } else { + *aErrorCode = NS_ERROR_FAILURE; + } + + return result; +} + +int64_t +nsACString::ToInteger64(nsresult* aErrorCode, uint32_t aRadix) const +{ + const char* fmt; + switch (aRadix) { + case 10: + fmt = "%lli"; + break; + + case 16: + fmt = "%llx"; + break; + + default: + NS_ERROR("Unrecognized radix!"); + *aErrorCode = NS_ERROR_INVALID_ARG; + return 0; + } + + int64_t result = 0; + if (PR_sscanf(nsCString(*this).get(), fmt, &result) == 1) { + *aErrorCode = NS_OK; + } else { + *aErrorCode = NS_ERROR_FAILURE; + } + + return result; +} +#endif // XPCOM_GLUE_AVOID_NSPR + +// Substrings + +nsDependentSubstring::nsDependentSubstring(const abstract_string_type& aStr, + uint32_t aStartPos) +{ + const char16_t* data; + uint32_t len = NS_StringGetData(aStr, &data); + + if (aStartPos > len) { + aStartPos = len; + } + + NS_StringContainerInit2(*this, data + aStartPos, len - aStartPos, + NS_STRING_CONTAINER_INIT_DEPEND | + NS_STRING_CONTAINER_INIT_SUBSTRING); +} + +nsDependentSubstring::nsDependentSubstring(const abstract_string_type& aStr, + uint32_t aStartPos, + uint32_t aLength) +{ + const char16_t* data; + uint32_t len = NS_StringGetData(aStr, &data); + + if (aStartPos > len) { + aStartPos = len; + } + + if (aStartPos + aLength > len) { + aLength = len - aStartPos; + } + + NS_StringContainerInit2(*this, data + aStartPos, aLength, + NS_STRING_CONTAINER_INIT_DEPEND | + NS_STRING_CONTAINER_INIT_SUBSTRING); +} + +nsDependentCSubstring::nsDependentCSubstring(const abstract_string_type& aStr, + uint32_t aStartPos) +{ + const char* data; + uint32_t len = NS_CStringGetData(aStr, &data); + + if (aStartPos > len) { + aStartPos = len; + } + + NS_CStringContainerInit2(*this, data + aStartPos, len - aStartPos, + NS_CSTRING_CONTAINER_INIT_DEPEND | + NS_CSTRING_CONTAINER_INIT_SUBSTRING); +} + +nsDependentCSubstring::nsDependentCSubstring(const abstract_string_type& aStr, + uint32_t aStartPos, + uint32_t aLength) +{ + const char* data; + uint32_t len = NS_CStringGetData(aStr, &data); + + if (aStartPos > len) { + aStartPos = len; + } + + if (aStartPos + aLength > len) { + aLength = len - aStartPos; + } + + NS_CStringContainerInit2(*this, data + aStartPos, aLength, + NS_CSTRING_CONTAINER_INIT_DEPEND | + NS_CSTRING_CONTAINER_INIT_SUBSTRING); +} + +// Utils + +char* +ToNewUTF8String(const nsAString& aSource) +{ + nsCString temp; + CopyUTF16toUTF8(aSource, temp); + return NS_CStringCloneData(temp); +} + +void +CompressWhitespace(nsAString& aString) +{ + char16_t* start; + uint32_t len = NS_StringGetMutableData(aString, UINT32_MAX, &start); + char16_t* end = start + len; + char16_t* from = start; + char16_t* to = start; + + // Skip any leading whitespace + while (from < end && NS_IsAsciiWhitespace(*from)) { + from++; + } + + while (from < end) { + char16_t theChar = *from++; + + if (NS_IsAsciiWhitespace(theChar)) { + // We found a whitespace char, so skip over any more + while (from < end && NS_IsAsciiWhitespace(*from)) { + from++; + } + + // Turn all whitespace into spaces + theChar = ' '; + } + + *to++ = theChar; + } + + // Drop any trailing space + if (to > start && to[-1] == ' ') { + to--; + } + + // Re-terminate the string + *to = '\0'; + + // Set the new length + aString.SetLength(to - start); +} + +uint32_t +ToLowerCase(nsACString& aStr) +{ + char* begin; + char* end; + uint32_t len = aStr.BeginWriting(&begin, &end); + + for (; begin < end; ++begin) { + *begin = NS_ToLower(*begin); + } + + return len; +} + +uint32_t +ToUpperCase(nsACString& aStr) +{ + char* begin; + char* end; + uint32_t len = aStr.BeginWriting(&begin, &end); + + for (; begin < end; ++begin) { + *begin = NS_ToUpper(*begin); + } + + return len; +} + +uint32_t +ToLowerCase(const nsACString& aSrc, nsACString& aDest) +{ + const char* begin; + const char* end; + uint32_t len = aSrc.BeginReading(&begin, &end); + + char* dest; + NS_CStringGetMutableData(aDest, len, &dest); + + for (; begin < end; ++begin, ++dest) { + *dest = NS_ToLower(*begin); + } + + return len; +} + +uint32_t +ToUpperCase(const nsACString& aSrc, nsACString& aDest) +{ + const char* begin; + const char* end; + uint32_t len = aSrc.BeginReading(&begin, &end); + + char* dest; + NS_CStringGetMutableData(aDest, len, &dest); + + for (; begin < end; ++begin, ++dest) { + *dest = NS_ToUpper(*begin); + } + + return len; +} + +int32_t +CaseInsensitiveCompare(const char* aStrA, const char* aStrB, + uint32_t aLen) +{ + for (const char* aend = aStrA + aLen; aStrA < aend; ++aStrA, ++aStrB) { + char la = NS_ToLower(*aStrA); + char lb = NS_ToLower(*aStrB); + + if (la == lb) { + continue; + } + + return la < lb ? -1 : 1; + } + + return 0; +} + +bool +ParseString(const nsACString& aSource, char aDelimiter, + nsTArray<nsCString>& aArray) +{ + int32_t start = 0; + int32_t end = aSource.Length(); + + uint32_t oldLength = aArray.Length(); + + for (;;) { + int32_t delimiter = aSource.FindChar(aDelimiter, start); + if (delimiter < 0) { + delimiter = end; + } + + if (delimiter != start) { + if (!aArray.AppendElement(Substring(aSource, start, delimiter - start))) { + aArray.RemoveElementsAt(oldLength, aArray.Length() - oldLength); + return false; + } + } + + if (delimiter == end) { + break; + } + start = ++delimiter; + if (start == end) { + break; + } + } + + return true; +} diff --git a/xpcom/glue/nsStringAPI.h b/xpcom/glue/nsStringAPI.h new file mode 100644 index 0000000000..d5e3686957 --- /dev/null +++ b/xpcom/glue/nsStringAPI.h @@ -0,0 +1,1596 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * This header provides wrapper classes around the frozen string API + * which are roughly equivalent to the internal string classes. + */ + +#ifdef MOZILLA_INTERNAL_API +#error nsStringAPI.h is only usable from non-MOZILLA_INTERNAL_API code! +#endif + +#ifndef nsStringAPI_h__ +#define nsStringAPI_h__ + +#include "mozilla/Attributes.h" +#include "mozilla/Char16.h" + +#include "nsXPCOMStrings.h" +#include "nsISupportsImpl.h" +#include "mozilla/Logging.h" +#include "nsTArray.h" + +/** + * Comparison function for use with nsACString::Equals + */ +NS_HIDDEN_(int32_t) CaseInsensitiveCompare(const char* aStrA, const char* aStrB, + uint32_t aLength); + +class nsAString +{ +public: + typedef char16_t char_type; + typedef nsAString self_type; + typedef uint32_t size_type; + typedef uint32_t index_type; + + /** + * Returns the length, beginning, and end of a string in one operation. + */ + NS_HIDDEN_(uint32_t) BeginReading(const char_type** aBegin, + const char_type** aEnd = nullptr) const; + + NS_HIDDEN_(const char_type*) BeginReading() const; + NS_HIDDEN_(const char_type*) EndReading() const; + + NS_HIDDEN_(char_type) CharAt(uint32_t aPos) const + { + NS_ASSERTION(aPos < Length(), "Out of bounds"); + return BeginReading()[aPos]; + } + NS_HIDDEN_(char_type) operator [](uint32_t aPos) const + { + return CharAt(aPos); + } + NS_HIDDEN_(char_type) First() const + { + return CharAt(0); + } + NS_HIDDEN_(char_type) Last() const + { + const char_type* data; + uint32_t dataLen = NS_StringGetData(*this, &data); + return data[dataLen - 1]; + } + + /** + * Get the length, begin writing, and optionally set the length of a + * string all in one operation. + * + * @param newSize Size the string to this length. Pass UINT32_MAX + * to leave the length unchanged. + * @return The new length of the string, or 0 if resizing failed. + */ + NS_HIDDEN_(uint32_t) BeginWriting(char_type** aBegin, + char_type** aEnd = nullptr, + uint32_t aNewSize = UINT32_MAX); + + NS_HIDDEN_(char_type*) BeginWriting(uint32_t = UINT32_MAX); + NS_HIDDEN_(char_type*) EndWriting(); + + NS_HIDDEN_(bool) SetLength(uint32_t aLen); + + NS_HIDDEN_(size_type) Length() const + { + const char_type* data; + return NS_StringGetData(*this, &data); + } + + NS_HIDDEN_(bool) IsEmpty() const { return Length() == 0; } + + NS_HIDDEN_(void) SetIsVoid(bool aVal) { NS_StringSetIsVoid(*this, aVal); } + NS_HIDDEN_(bool) IsVoid() const { return NS_StringGetIsVoid(*this); } + + NS_HIDDEN_(void) Assign(const self_type& aString) + { + NS_StringCopy(*this, aString); + } + NS_HIDDEN_(void) Assign(const char_type* aData, size_type aLength = UINT32_MAX) + { + NS_StringSetData(*this, aData, aLength); + } + NS_HIDDEN_(void) Assign(char_type aChar) + { + NS_StringSetData(*this, &aChar, 1); + } +#ifdef MOZ_USE_CHAR16_WRAPPER + NS_HIDDEN_(void) Assign(char16ptr_t aData, size_type aLength = UINT32_MAX) + { + NS_StringSetData(*this, aData, aLength); + } +#endif + + NS_HIDDEN_(void) AssignLiteral(const char* aStr); + NS_HIDDEN_(void) AssignASCII(const char* aStr) + { + AssignLiteral(aStr); + } + + NS_HIDDEN_(self_type&) operator=(const self_type& aString) + { + Assign(aString); + return *this; + } + NS_HIDDEN_(self_type&) operator=(const char_type* aPtr) + { + Assign(aPtr); + return *this; + } + NS_HIDDEN_(self_type&) operator=(char_type aChar) + { + Assign(aChar); + return *this; + } +#ifdef MOZ_USE_CHAR16_WRAPPER + NS_HIDDEN_(self_type&) operator=(char16ptr_t aPtr) + { + Assign(aPtr); + return *this; + } +#endif + + NS_HIDDEN_(void) Replace(index_type aCutStart, size_type aCutLength, + const char_type* aData, + size_type aLength = size_type(-1)) + { + NS_StringSetDataRange(*this, aCutStart, aCutLength, aData, aLength); + } + NS_HIDDEN_(void) Replace(index_type aCutStart, size_type aCutLength, + char_type aChar) + { + Replace(aCutStart, aCutLength, &aChar, 1); + } + NS_HIDDEN_(void) Replace(index_type aCutStart, size_type aCutLength, + const self_type& aReadable) + { + const char_type* data; + uint32_t dataLen = NS_StringGetData(aReadable, &data); + NS_StringSetDataRange(*this, aCutStart, aCutLength, data, dataLen); + } + NS_HIDDEN_(void) SetCharAt(char_type aChar, index_type aPos) + { + Replace(aPos, 1, &aChar, 1); + } + + NS_HIDDEN_(void) Append(char_type aChar) + { + Replace(size_type(-1), 0, aChar); + } + NS_HIDDEN_(void) Append(const char_type* aData, + size_type aLength = size_type(-1)) + { + Replace(size_type(-1), 0, aData, aLength); + } +#ifdef MOZ_USE_CHAR16_WRAPPER + NS_HIDDEN_(void) Append(char16ptr_t aData, size_type aLength = size_type(-1)) + { + Append(static_cast<const char16_t*>(aData), aLength); + } +#endif + NS_HIDDEN_(void) Append(const self_type& aReadable) + { + Replace(size_type(-1), 0, aReadable); + } + NS_HIDDEN_(void) AppendLiteral(const char* aASCIIStr); + NS_HIDDEN_(void) AppendASCII(const char* aASCIIStr) + { + AppendLiteral(aASCIIStr); + } + + NS_HIDDEN_(self_type&) operator+=(char_type aChar) + { + Append(aChar); + return *this; + } + NS_HIDDEN_(self_type&) operator+=(const char_type* aData) + { + Append(aData); + return *this; + } + NS_HIDDEN_(self_type&) operator+=(const self_type& aReadable) + { + Append(aReadable); + return *this; + } + + NS_HIDDEN_(void) Insert(char_type aChar, index_type aPos) + { + Replace(aPos, 0, aChar); + } + NS_HIDDEN_(void) Insert(const char_type* aData, index_type aPos, + size_type aLength = size_type(-1)) + { + Replace(aPos, 0, aData, aLength); + } + NS_HIDDEN_(void) Insert(const self_type& aReadable, index_type aPos) + { + Replace(aPos, 0, aReadable); + } + + NS_HIDDEN_(void) Cut(index_type aCutStart, size_type aCutLength) + { + Replace(aCutStart, aCutLength, nullptr, 0); + } + + NS_HIDDEN_(void) Truncate(size_type aNewLength = 0) + { + NS_ASSERTION(aNewLength <= Length(), "Truncate cannot make string longer"); + SetLength(aNewLength); + } + + /** + * Remove all occurences of characters in aSet from the string. + */ + NS_HIDDEN_(void) StripChars(const char* aSet); + + /** + * Strip whitespace characters from the string. + */ + NS_HIDDEN_(void) StripWhitespace() { StripChars("\b\t\r\n "); } + + NS_HIDDEN_(void) Trim(const char* aSet, bool aLeading = true, + bool aTrailing = true); + + /** + * Compare strings of characters. Return 0 if the characters are equal, + */ + typedef int32_t (*ComparatorFunc)(const char_type* aStrA, + const char_type* aStrB, + uint32_t aLength); + + static NS_HIDDEN_(int32_t) DefaultComparator(const char_type* aStrA, + const char_type* aStrB, + uint32_t aLength); + + NS_HIDDEN_(int32_t) Compare(const char_type* aOther, + ComparatorFunc aComparator = DefaultComparator) const; + + NS_HIDDEN_(int32_t) Compare(const self_type& aOther, + ComparatorFunc aComparator = DefaultComparator) const; + + NS_HIDDEN_(bool) Equals(const char_type* aOther, + ComparatorFunc aComparator = DefaultComparator) const; + + NS_HIDDEN_(bool) Equals(const self_type& aOther, + ComparatorFunc aComparator = DefaultComparator) const; + + NS_HIDDEN_(bool) operator<(const self_type& aOther) const + { + return Compare(aOther) < 0; + } + NS_HIDDEN_(bool) operator<(const char_type* aOther) const + { + return Compare(aOther) < 0; + } + + NS_HIDDEN_(bool) operator<=(const self_type& aOther) const + { + return Compare(aOther) <= 0; + } + NS_HIDDEN_(bool) operator<=(const char_type* aOther) const + { + return Compare(aOther) <= 0; + } + + NS_HIDDEN_(bool) operator==(const self_type& aOther) const + { + return Equals(aOther); + } + NS_HIDDEN_(bool) operator==(const char_type* aOther) const + { + return Equals(aOther); + } +#ifdef MOZ_USE_CHAR16_WRAPPER + NS_HIDDEN_(bool) operator==(char16ptr_t aOther) const + { + return Equals(aOther); + } +#endif + + NS_HIDDEN_(bool) operator>=(const self_type& aOther) const + { + return Compare(aOther) >= 0; + } + NS_HIDDEN_(bool) operator>=(const char_type* aOther) const + { + return Compare(aOther) >= 0; + } + + NS_HIDDEN_(bool) operator>(const self_type& aOther) const + { + return Compare(aOther) > 0; + } + NS_HIDDEN_(bool) operator>(const char_type* aOther) const + { + return Compare(aOther) > 0; + } + + NS_HIDDEN_(bool) operator!=(const self_type& aOther) const + { + return !Equals(aOther); + } + NS_HIDDEN_(bool) operator!=(const char_type* aOther) const + { + return !Equals(aOther); + } + + NS_HIDDEN_(bool) EqualsLiteral(const char* aASCIIString) const; + NS_HIDDEN_(bool) EqualsASCII(const char* aASCIIString) const + { + return EqualsLiteral(aASCIIString); + } + + /** + * Case-insensitive match this string to a lowercase ASCII string. + */ + NS_HIDDEN_(bool) LowerCaseEqualsLiteral(const char* aASCIIString) const; + + /** + * Find the first occurrence of aStr in this string. + * + * @return the offset of aStr, or -1 if not found + */ + NS_HIDDEN_(int32_t) Find(const self_type& aStr, + ComparatorFunc aComparator = DefaultComparator) const + { + return Find(aStr, 0, aComparator); + } + + /** + * Find the first occurrence of aStr in this string, beginning at aOffset. + * + * @return the offset of aStr, or -1 if not found + */ + NS_HIDDEN_(int32_t) Find(const self_type& aStr, uint32_t aOffset, + ComparatorFunc aComparator = DefaultComparator) const; + + /** + * Find an ASCII string within this string. + * + * @return the offset of aStr, or -1 if not found. + */ + NS_HIDDEN_(int32_t) Find(const char* aStr, bool aIgnoreCase = false) const + { + return Find(aStr, 0, aIgnoreCase); + } + + NS_HIDDEN_(int32_t) Find(const char* aStr, uint32_t aOffset, + bool aIgnoreCase = false) const; + + /** + * Find the last occurrence of aStr in this string. + * + * @return The offset of aStr from the beginning of the string, + * or -1 if not found. + */ + NS_HIDDEN_(int32_t) RFind(const self_type& aStr, + ComparatorFunc aComparator = DefaultComparator) const + { + return RFind(aStr, -1, aComparator); + } + + /** + * Find the last occurrence of aStr in this string, beginning at aOffset. + * + * @param aOffset the offset from the beginning of the string to begin + * searching. If aOffset < 0, search from end of this string. + * @return The offset of aStr from the beginning of the string, + * or -1 if not found. + */ + NS_HIDDEN_(int32_t) RFind(const self_type& aStr, int32_t aOffset, + ComparatorFunc aComparator = DefaultComparator) const; + + /** + * Find the last occurrence of an ASCII string within this string. + * + * @return The offset of aStr from the beginning of the string, + * or -1 if not found. + */ + NS_HIDDEN_(int32_t) RFind(const char* aStr, bool aIgnoreCase = false) const + { + return RFind(aStr, -1, aIgnoreCase); + } + + /** + * Find the last occurrence of an ASCII string beginning at aOffset. + * + * @param aOffset the offset from the beginning of the string to begin + * searching. If aOffset < 0, search from end of this string. + * @return The offset of aStr from the beginning of the string, + * or -1 if not found. + */ + NS_HIDDEN_(int32_t) RFind(const char* aStr, int32_t aOffset, + bool aIgnoreCase) const; + + /** + * Search for the offset of the first occurrence of a character in a + * string. + * + * @param aOffset the offset from the beginning of the string to begin + * searching + * @return The offset of the character from the beginning of the string, + * or -1 if not found. + */ + NS_HIDDEN_(int32_t) FindChar(char_type aChar, uint32_t aOffset = 0) const; + + /** + * Search for the offset of the last occurrence of a character in a + * string. + * + * @return The offset of the character from the beginning of the string, + * or -1 if not found. + */ + NS_HIDDEN_(int32_t) RFindChar(char_type aChar) const; + + /** + * Append a string representation of a number. + */ + NS_HIDDEN_(void) AppendInt(int aInt, int32_t aRadix = 10); + +#ifndef XPCOM_GLUE_AVOID_NSPR + /** + * Convert this string to an integer. + * + * @param aErrorCode pointer to contain result code. + * @param aRadix must be 10 or 16 + */ + NS_HIDDEN_(int32_t) ToInteger(nsresult* aErrorCode, + uint32_t aRadix = 10) const; + /** + * Convert this string to a 64-bit integer. + * + * @param aErrorCode pointer to contain result code. + * @param aRadix must be 10 or 16 + */ + NS_HIDDEN_(int64_t) ToInteger64(nsresult* aErrorCode, + uint32_t aRadix = 10) const; +#endif // XPCOM_GLUE_AVOID_NSPR + +protected: + // Prevent people from allocating a nsAString directly. + ~nsAString() {} +}; + +class nsACString +{ +public: + typedef char char_type; + typedef nsACString self_type; + typedef uint32_t size_type; + typedef uint32_t index_type; + + /** + * Returns the length, beginning, and end of a string in one operation. + */ + NS_HIDDEN_(uint32_t) BeginReading(const char_type** aBegin, + const char_type** aEnd = nullptr) const; + + NS_HIDDEN_(const char_type*) BeginReading() const; + NS_HIDDEN_(const char_type*) EndReading() const; + + NS_HIDDEN_(char_type) CharAt(uint32_t aPos) const + { + NS_ASSERTION(aPos < Length(), "Out of bounds"); + return BeginReading()[aPos]; + } + NS_HIDDEN_(char_type) operator [](uint32_t aPos) const + { + return CharAt(aPos); + } + NS_HIDDEN_(char_type) First() const + { + return CharAt(0); + } + NS_HIDDEN_(char_type) Last() const + { + const char_type* data; + uint32_t dataLen = NS_CStringGetData(*this, &data); + return data[dataLen - 1]; + } + + /** + * Get the length, begin writing, and optionally set the length of a + * string all in one operation. + * + * @param newSize Size the string to this length. Pass UINT32_MAX + * to leave the length unchanged. + * @return The new length of the string, or 0 if resizing failed. + */ + NS_HIDDEN_(uint32_t) BeginWriting(char_type** aBegin, + char_type** aEnd = nullptr, + uint32_t aNewSize = UINT32_MAX); + + NS_HIDDEN_(char_type*) BeginWriting(uint32_t aLen = UINT32_MAX); + NS_HIDDEN_(char_type*) EndWriting(); + + NS_HIDDEN_(bool) SetLength(uint32_t aLen); + + NS_HIDDEN_(size_type) Length() const + { + const char_type* data; + return NS_CStringGetData(*this, &data); + } + + NS_HIDDEN_(bool) IsEmpty() const { return Length() == 0; } + + NS_HIDDEN_(void) SetIsVoid(bool aVal) { NS_CStringSetIsVoid(*this, aVal); } + NS_HIDDEN_(bool) IsVoid() const { return NS_CStringGetIsVoid(*this); } + + NS_HIDDEN_(void) Assign(const self_type& aString) + { + NS_CStringCopy(*this, aString); + } + NS_HIDDEN_(void) Assign(const char_type* aData, size_type aLength = UINT32_MAX) + { + NS_CStringSetData(*this, aData, aLength); + } + NS_HIDDEN_(void) Assign(char_type aChar) + { + NS_CStringSetData(*this, &aChar, 1); + } + NS_HIDDEN_(void) AssignLiteral(const char_type* aData) + { + Assign(aData); + } + NS_HIDDEN_(void) AssignASCII(const char_type* aData) + { + Assign(aData); + } + + NS_HIDDEN_(self_type&) operator=(const self_type& aString) + { + Assign(aString); + return *this; + } + NS_HIDDEN_(self_type&) operator=(const char_type* aPtr) + { + Assign(aPtr); + return *this; + } + NS_HIDDEN_(self_type&) operator=(char_type aChar) + { + Assign(aChar); + return *this; + } + + NS_HIDDEN_(void) Replace(index_type aCutStart, size_type aCutLength, + const char_type* aData, + size_type aLength = size_type(-1)) + { + NS_CStringSetDataRange(*this, aCutStart, aCutLength, aData, aLength); + } + NS_HIDDEN_(void) Replace(index_type aCutStart, size_type aCutLength, + char_type aChar) + { + Replace(aCutStart, aCutLength, &aChar, 1); + } + NS_HIDDEN_(void) Replace(index_type aCutStart, size_type aCutLength, + const self_type& aReadable) + { + const char_type* data; + uint32_t dataLen = NS_CStringGetData(aReadable, &data); + NS_CStringSetDataRange(*this, aCutStart, aCutLength, data, dataLen); + } + NS_HIDDEN_(void) SetCharAt(char_type aChar, index_type aPos) + { + Replace(aPos, 1, &aChar, 1); + } + + NS_HIDDEN_(void) Append(char_type aChar) + { + Replace(size_type(-1), 0, aChar); + } + NS_HIDDEN_(void) Append(const char_type* aData, + size_type aLength = size_type(-1)) + { + Replace(size_type(-1), 0, aData, aLength); + } + NS_HIDDEN_(void) Append(const self_type& aReadable) + { + Replace(size_type(-1), 0, aReadable); + } + NS_HIDDEN_(void) AppendLiteral(const char* aASCIIStr) + { + Append(aASCIIStr); + } + NS_HIDDEN_(void) AppendASCII(const char* aASCIIStr) + { + Append(aASCIIStr); + } + + NS_HIDDEN_(self_type&) operator+=(char_type aChar) + { + Append(aChar); + return *this; + } + NS_HIDDEN_(self_type&) operator+=(const char_type* aData) + { + Append(aData); + return *this; + } + NS_HIDDEN_(self_type&) operator+=(const self_type& aReadable) + { + Append(aReadable); + return *this; + } + + NS_HIDDEN_(void) Insert(char_type aChar, index_type aPos) + { + Replace(aPos, 0, aChar); + } + NS_HIDDEN_(void) Insert(const char_type* aData, index_type aPos, + size_type aLength = size_type(-1)) + { + Replace(aPos, 0, aData, aLength); + } + NS_HIDDEN_(void) Insert(const self_type& aReadable, index_type aPos) + { + Replace(aPos, 0, aReadable); + } + + NS_HIDDEN_(void) Cut(index_type aCutStart, size_type aCutLength) + { + Replace(aCutStart, aCutLength, nullptr, 0); + } + + NS_HIDDEN_(void) Truncate(size_type aNewLength = 0) + { + NS_ASSERTION(aNewLength <= Length(), "Truncate cannot make string longer"); + SetLength(aNewLength); + } + + /** + * Remove all occurences of characters in aSet from the string. + */ + NS_HIDDEN_(void) StripChars(const char* aSet); + + /** + * Strip whitespace characters from the string. + */ + NS_HIDDEN_(void) StripWhitespace() { StripChars("\b\t\r\n "); } + + NS_HIDDEN_(void) Trim(const char* aSet, bool aLeading = true, + bool aTrailing = true); + + /** + * Compare strings of characters. Return 0 if the characters are equal, + */ + typedef int32_t (*ComparatorFunc)(const char_type* a, + const char_type* b, + uint32_t length); + + static NS_HIDDEN_(int32_t) DefaultComparator(const char_type* aStrA, + const char_type* aStrB, + uint32_t aLength); + + NS_HIDDEN_(int32_t) Compare(const char_type* aOther, + ComparatorFunc aComparator = DefaultComparator) const; + + NS_HIDDEN_(int32_t) Compare(const self_type& aOther, + ComparatorFunc aComparator = DefaultComparator) const; + + NS_HIDDEN_(bool) Equals(const char_type* aOther, + ComparatorFunc aComparator = DefaultComparator) const; + + NS_HIDDEN_(bool) Equals(const self_type& aOther, + ComparatorFunc aComparator = DefaultComparator) const; + + NS_HIDDEN_(bool) operator<(const self_type& aOther) const + { + return Compare(aOther) < 0; + } + NS_HIDDEN_(bool) operator<(const char_type* aOther) const + { + return Compare(aOther) < 0; + } + + NS_HIDDEN_(bool) operator<=(const self_type& aOther) const + { + return Compare(aOther) <= 0; + } + NS_HIDDEN_(bool) operator<=(const char_type* aOther) const + { + return Compare(aOther) <= 0; + } + + NS_HIDDEN_(bool) operator==(const self_type& aOther) const + { + return Equals(aOther); + } + NS_HIDDEN_(bool) operator==(const char_type* aOther) const + { + return Equals(aOther); + } + + NS_HIDDEN_(bool) operator>=(const self_type& aOther) const + { + return Compare(aOther) >= 0; + } + NS_HIDDEN_(bool) operator>=(const char_type* aOther) const + { + return Compare(aOther) >= 0; + } + + NS_HIDDEN_(bool) operator>(const self_type& aOther) const + { + return Compare(aOther) > 0; + } + NS_HIDDEN_(bool) operator>(const char_type* aOther) const + { + return Compare(aOther) > 0; + } + + NS_HIDDEN_(bool) operator!=(const self_type& aOther) const + { + return !Equals(aOther); + } + NS_HIDDEN_(bool) operator!=(const char_type* aOther) const + { + return !Equals(aOther); + } + + NS_HIDDEN_(bool) EqualsLiteral(const char_type* aOther) const + { + return Equals(aOther); + } + NS_HIDDEN_(bool) EqualsASCII(const char_type* aOther) const + { + return Equals(aOther); + } + + /** + * Case-insensitive match this string to a lowercase ASCII string. + */ + NS_HIDDEN_(bool) LowerCaseEqualsLiteral(const char* aASCIIString) const + { + return Equals(aASCIIString, CaseInsensitiveCompare); + } + + /** + * Find the first occurrence of aStr in this string. + * + * @return the offset of aStr, or -1 if not found + */ + NS_HIDDEN_(int32_t) Find(const self_type& aStr, + ComparatorFunc aComparator = DefaultComparator) const + { + return Find(aStr, 0, aComparator); + } + + /** + * Find the first occurrence of aStr in this string, beginning at aOffset. + * + * @return the offset of aStr, or -1 if not found + */ + NS_HIDDEN_(int32_t) Find(const self_type& aStr, uint32_t aOffset, + ComparatorFunc aComparator = DefaultComparator) const; + + /** + * Find the first occurrence of aStr in this string. + * + * @return the offset of aStr, or -1 if not found + */ + NS_HIDDEN_(int32_t) Find(const char_type* aStr, + ComparatorFunc aComparator = DefaultComparator) const; + + NS_HIDDEN_(int32_t) Find(const char_type* aStr, uint32_t aLen, + ComparatorFunc aComparator = DefaultComparator) const; + + /** + * Find the last occurrence of aStr in this string. + * + * @return The offset of the character from the beginning of the string, + * or -1 if not found. + */ + NS_HIDDEN_(int32_t) RFind(const self_type& aStr, + ComparatorFunc aComparator = DefaultComparator) const + { + return RFind(aStr, -1, aComparator); + } + + /** + * Find the last occurrence of aStr in this string, beginning at aOffset. + * + * @param aOffset the offset from the beginning of the string to begin + * searching. If aOffset < 0, search from end of this string. + * @return The offset of aStr from the beginning of the string, + * or -1 if not found. + */ + NS_HIDDEN_(int32_t) RFind(const self_type& aStr, int32_t aOffset, + ComparatorFunc aComparator = DefaultComparator) const; + + /** + * Find the last occurrence of aStr in this string. + * + * @return The offset of aStr from the beginning of the string, + * or -1 if not found. + */ + NS_HIDDEN_(int32_t) RFind(const char_type* aStr, + ComparatorFunc aComparator = DefaultComparator) const; + + /** + * Find the last occurrence of an ASCII string in this string, + * beginning at aOffset. + * + * @param aLen is the length of aStr + * @return The offset of aStr from the beginning of the string, + * or -1 if not found. + */ + NS_HIDDEN_(int32_t) RFind(const char_type* aStr, int32_t aLen, + ComparatorFunc aComparator = DefaultComparator) const; + + /** + * Search for the offset of the first occurrence of a character in a + * string. + * + * @param aOffset the offset from the beginning of the string to begin + * searching + * @return The offset of the character from the beginning of the string, + * or -1 if not found. + */ + NS_HIDDEN_(int32_t) FindChar(char_type aChar, uint32_t aOffset = 0) const; + + /** + * Search for the offset of the last occurrence of a character in a + * string. + * + * @return The offset of the character from the beginning of the string, + * or -1 if not found. + */ + NS_HIDDEN_(int32_t) RFindChar(char_type aChar) const; + + /** + * Append a string representation of a number. + */ + NS_HIDDEN_(void) AppendInt(int aInt, int32_t aRadix = 10); + +#ifndef XPCOM_GLUE_AVOID_NSPR + /** + * Convert this string to an integer. + * + * @param aErrorCode pointer to contain result code. + * @param aRadix must be 10 or 16 + */ + NS_HIDDEN_(int32_t) ToInteger(nsresult* aErrorCode, + uint32_t aRadix = 10) const; + /** + * Convert this string to a 64-bit integer. + * + * @param aErrorCode pointer to contain result code. + * @param aRadix must be 10 or 16 + */ + NS_HIDDEN_(int64_t) ToInteger64(nsresult* aErrorCode, + uint32_t aRadix = 10) const; +#endif // XPCOM_GLUE_AVOID_NSPR + +protected: + // Prevent people from allocating a nsAString directly. + ~nsACString() {} +}; + +/* ------------------------------------------------------------------------- */ + +/** + * Below we define nsStringContainer and nsCStringContainer. These classes + * have unspecified structure. In most cases, your code should use + * nsString/nsCString instead of these classes; if you prefer C-style + * programming, then look no further. + */ + +class nsStringContainer + : public nsAString + , private nsStringContainer_base +{ +}; + +class nsCStringContainer + : public nsACString + , private nsStringContainer_base +{ +}; + +/** + * The following classes are C++ helper classes that make the frozen string + * API easier to use. + */ + +/** + * Rename symbols to avoid conflicting with internal versions. + */ +#define nsString nsString_external +#define nsCString nsCString_external +#define nsDependentString nsDependentString_external +#define nsDependentCString nsDependentCString_external +#define NS_ConvertASCIItoUTF16 NS_ConvertASCIItoUTF16_external +#define NS_ConvertUTF8toUTF16 NS_ConvertUTF8toUTF16_external +#define NS_ConvertUTF16toUTF8 NS_ConvertUTF16toUTF8_external +#define NS_LossyConvertUTF16toASCII NS_LossyConvertUTF16toASCII_external +#define nsGetterCopies nsGetterCopies_external +#define nsCGetterCopies nsCGetterCopies_external +#define nsDependentSubstring nsDependentSubstring_external +#define nsDependentCSubstring nsDependentCSubstring_external + +/** + * basic strings + */ + +class nsString : public nsStringContainer +{ +public: + typedef nsString self_type; + typedef nsAString abstract_string_type; + + nsString() + { + NS_StringContainerInit(*this); + } + + nsString(const self_type& aString) + { + NS_StringContainerInit(*this); + NS_StringCopy(*this, aString); + } + + explicit nsString(const abstract_string_type& aReadable) + { + NS_StringContainerInit(*this); + NS_StringCopy(*this, aReadable); + } + + explicit nsString(const char_type* aData, size_type aLength = UINT32_MAX) + { + NS_StringContainerInit2(*this, aData, aLength, 0); + } + +#ifdef MOZ_USE_CHAR16_WRAPPER + explicit nsString(char16ptr_t aData, size_type aLength = UINT32_MAX) + : nsString(static_cast<const char16_t*>(aData), aLength) + { + } +#endif + + ~nsString() + { + NS_StringContainerFinish(*this); + } + + char16ptr_t get() const + { + return char16ptr_t(BeginReading()); + } + + self_type& operator=(const self_type& aString) + { + Assign(aString); + return *this; + } + self_type& operator=(const abstract_string_type& aReadable) + { + Assign(aReadable); + return *this; + } + self_type& operator=(const char_type* aPtr) + { + Assign(aPtr); + return *this; + } + self_type& operator=(char_type aChar) + { + Assign(aChar); + return *this; + } + + void Adopt(const char_type* aData, size_type aLength = UINT32_MAX) + { + NS_StringContainerFinish(*this); + NS_StringContainerInit2(*this, aData, aLength, + NS_STRING_CONTAINER_INIT_ADOPT); + } + +protected: + nsString(const char_type* aData, size_type aLength, uint32_t aFlags) + { + NS_StringContainerInit2(*this, aData, aLength, aFlags); + } +}; + +class nsCString : public nsCStringContainer +{ +public: + typedef nsCString self_type; + typedef nsACString abstract_string_type; + + nsCString() + { + NS_CStringContainerInit(*this); + } + + nsCString(const self_type& aString) + { + NS_CStringContainerInit(*this); + NS_CStringCopy(*this, aString); + } + + explicit nsCString(const abstract_string_type& aReadable) + { + NS_CStringContainerInit(*this); + NS_CStringCopy(*this, aReadable); + } + + explicit nsCString(const char_type* aData, size_type aLength = UINT32_MAX) + { + NS_CStringContainerInit(*this); + NS_CStringSetData(*this, aData, aLength); + } + + ~nsCString() + { + NS_CStringContainerFinish(*this); + } + + const char_type* get() const + { + return BeginReading(); + } + + self_type& operator=(const self_type& aString) + { + Assign(aString); + return *this; + } + self_type& operator=(const abstract_string_type& aReadable) + { + Assign(aReadable); + return *this; + } + self_type& operator=(const char_type* aPtr) + { + Assign(aPtr); + return *this; + } + self_type& operator=(char_type aChar) + { + Assign(aChar); + return *this; + } + + void Adopt(const char_type* aData, size_type aLength = UINT32_MAX) + { + NS_CStringContainerFinish(*this); + NS_CStringContainerInit2(*this, aData, aLength, + NS_CSTRING_CONTAINER_INIT_ADOPT); + } + +protected: + nsCString(const char_type* aData, size_type aLength, uint32_t aFlags) + { + NS_CStringContainerInit2(*this, aData, aLength, aFlags); + } +}; + + +/** + * dependent strings + */ + +class nsDependentString : public nsString +{ +public: + typedef nsDependentString self_type; + + nsDependentString() {} + + explicit nsDependentString(const char_type* aData, + size_type aLength = UINT32_MAX) + : nsString(aData, aLength, NS_CSTRING_CONTAINER_INIT_DEPEND) + { + } + +#ifdef MOZ_USE_CHAR16_WRAPPER + explicit nsDependentString(char16ptr_t aData, size_type aLength = UINT32_MAX) + : nsDependentString(static_cast<const char16_t*>(aData), aLength) + { + } +#endif + + void Rebind(const char_type* aData, size_type aLength = UINT32_MAX) + { + NS_StringContainerFinish(*this); + NS_StringContainerInit2(*this, aData, aLength, + NS_STRING_CONTAINER_INIT_DEPEND); + } + +private: + self_type& operator=(const self_type& aString) = delete; +}; + +class nsDependentCString : public nsCString +{ +public: + typedef nsDependentCString self_type; + + nsDependentCString() {} + + explicit nsDependentCString(const char_type* aData, + size_type aLength = UINT32_MAX) + : nsCString(aData, aLength, NS_CSTRING_CONTAINER_INIT_DEPEND) + { + } + + void Rebind(const char_type* aData, size_type aLength = UINT32_MAX) + { + NS_CStringContainerFinish(*this); + NS_CStringContainerInit2(*this, aData, aLength, + NS_CSTRING_CONTAINER_INIT_DEPEND); + } + +private: + self_type& operator=(const self_type& aString) = delete; +}; + + +/** + * conversion classes + */ + +inline void +CopyUTF16toUTF8(const nsAString& aSource, nsACString& aDest) +{ + NS_UTF16ToCString(aSource, NS_CSTRING_ENCODING_UTF8, aDest); +} + +inline void +CopyUTF8toUTF16(const nsACString& aSource, nsAString& aDest) +{ + NS_CStringToUTF16(aSource, NS_CSTRING_ENCODING_UTF8, aDest); +} + +inline void +LossyCopyUTF16toASCII(const nsAString& aSource, nsACString& aDest) +{ + NS_UTF16ToCString(aSource, NS_CSTRING_ENCODING_ASCII, aDest); +} + +inline void +CopyASCIItoUTF16(const nsACString& aSource, nsAString& aDest) +{ + NS_CStringToUTF16(aSource, NS_CSTRING_ENCODING_ASCII, aDest); +} + +char* +ToNewUTF8String(const nsAString& aSource); + +class NS_ConvertASCIItoUTF16 : public nsString +{ +public: + typedef NS_ConvertASCIItoUTF16 self_type; + + explicit NS_ConvertASCIItoUTF16(const nsACString& aStr) + { + NS_CStringToUTF16(aStr, NS_CSTRING_ENCODING_ASCII, *this); + } + + explicit NS_ConvertASCIItoUTF16(const char* aData, + uint32_t aLength = UINT32_MAX) + { + NS_CStringToUTF16(nsDependentCString(aData, aLength), + NS_CSTRING_ENCODING_ASCII, *this); + } + +private: + self_type& operator=(const self_type& aString) = delete; +}; + +class NS_ConvertUTF8toUTF16 : public nsString +{ +public: + typedef NS_ConvertUTF8toUTF16 self_type; + + explicit NS_ConvertUTF8toUTF16(const nsACString& aStr) + { + NS_CStringToUTF16(aStr, NS_CSTRING_ENCODING_UTF8, *this); + } + + explicit NS_ConvertUTF8toUTF16(const char* aData, + uint32_t aLength = UINT32_MAX) + { + NS_CStringToUTF16(nsDependentCString(aData, aLength), + NS_CSTRING_ENCODING_UTF8, *this); + } + +private: + self_type& operator=(const self_type& aString) = delete; +}; + +class NS_ConvertUTF16toUTF8 : public nsCString +{ +public: + typedef NS_ConvertUTF16toUTF8 self_type; + + explicit NS_ConvertUTF16toUTF8(const nsAString& aStr) + { + NS_UTF16ToCString(aStr, NS_CSTRING_ENCODING_UTF8, *this); + } + + explicit NS_ConvertUTF16toUTF8(const char16ptr_t aData, + uint32_t aLength = UINT32_MAX) + { + NS_UTF16ToCString(nsDependentString(aData, aLength), + NS_CSTRING_ENCODING_UTF8, *this); + } + +private: + self_type& operator=(const self_type& aString) = delete; +}; + +class NS_LossyConvertUTF16toASCII : public nsCString +{ +public: + typedef NS_LossyConvertUTF16toASCII self_type; + + explicit NS_LossyConvertUTF16toASCII(const nsAString& aStr) + { + NS_UTF16ToCString(aStr, NS_CSTRING_ENCODING_ASCII, *this); + } + + explicit NS_LossyConvertUTF16toASCII(const char16ptr_t aData, + uint32_t aLength = UINT32_MAX) + { + NS_UTF16ToCString(nsDependentString(aData, aLength), + NS_CSTRING_ENCODING_ASCII, *this); + } + +private: + self_type& operator=(const self_type& aString) = delete; +}; + + +/** + * literal strings + */ +static_assert(sizeof(char16_t) == 2, "size of char16_t must be 2"); +static_assert(char16_t(-1) > char16_t(0), "char16_t must be unsigned"); + +#define NS_MULTILINE_LITERAL_STRING(s) \ + nsDependentString(reinterpret_cast<const nsAString::char_type*>(s), \ + uint32_t((sizeof(s) / 2) - 1)) +#define NS_MULTILINE_LITERAL_STRING_INIT(n, s) \ + n(reinterpret_cast<const nsAString::char_type*>(s), \ + uint32_t((sizeof(s) / 2) - 1)) +#define NS_NAMED_MULTILINE_LITERAL_STRING(n,s) \ + const nsDependentString n(reinterpret_cast<const nsAString::char_type*>(s), \ + uint32_t((sizeof(s) / 2) - 1)) +typedef nsDependentString nsLiteralString; + +#define NS_LITERAL_STRING(s) \ + static_cast<const nsString&>(NS_MULTILINE_LITERAL_STRING(u"" s)) +#define NS_LITERAL_STRING_INIT(n, s) \ + NS_MULTILINE_LITERAL_STRING_INIT(n, (u"" s)) +#define NS_NAMED_LITERAL_STRING(n, s) \ + NS_NAMED_MULTILINE_LITERAL_STRING(n, (u"" s)) + +#define NS_LITERAL_CSTRING(s) \ + static_cast<const nsDependentCString&>(nsDependentCString(s, uint32_t(sizeof(s) - 1))) +#define NS_LITERAL_CSTRING_INIT(n, s) \ + n(s, uint32_t(sizeof(s)-1)) +#define NS_NAMED_LITERAL_CSTRING(n, s) \ + const nsDependentCString n(s, uint32_t(sizeof(s)-1)) + +typedef nsDependentCString nsLiteralCString; + + +/** + * getter_Copies support + * + * NS_IMETHOD GetBlah(char16_t**); + * + * void some_function() + * { + * nsString blah; + * GetBlah(getter_Copies(blah)); + * // ... + * } + */ + +class nsGetterCopies +{ +public: + typedef char16_t char_type; + + explicit nsGetterCopies(nsString& aStr) + : mString(aStr) + , mData(nullptr) + { + } + + ~nsGetterCopies() { mString.Adopt(mData); } + + operator char_type**() { return &mData; } + +private: + nsString& mString; + char_type* mData; +}; + +inline nsGetterCopies +getter_Copies(nsString& aString) +{ + return nsGetterCopies(aString); +} + +class nsCGetterCopies +{ +public: + typedef char char_type; + + explicit nsCGetterCopies(nsCString& aStr) + : mString(aStr) + , mData(nullptr) + { + } + + ~nsCGetterCopies() { mString.Adopt(mData); } + + operator char_type**() { return &mData; } + +private: + nsCString& mString; + char_type* mData; +}; + +inline nsCGetterCopies +getter_Copies(nsCString& aString) +{ + return nsCGetterCopies(aString); +} + + +/** +* substrings +*/ + +class nsDependentSubstring : public nsStringContainer +{ +public: + typedef nsDependentSubstring self_type; + typedef nsAString abstract_string_type; + + ~nsDependentSubstring() { NS_StringContainerFinish(*this); } + nsDependentSubstring() { NS_StringContainerInit(*this); } + + nsDependentSubstring(const char_type* aStart, uint32_t aLength) + { + NS_StringContainerInit2(*this, aStart, aLength, + NS_STRING_CONTAINER_INIT_DEPEND | + NS_STRING_CONTAINER_INIT_SUBSTRING); + } + + nsDependentSubstring(const abstract_string_type& aStr, + uint32_t aStartPos); + nsDependentSubstring(const abstract_string_type& aStr, + uint32_t aStartPos, uint32_t aLength); + + void Rebind(const char_type* aStart, uint32_t aLength) + { + NS_StringContainerFinish(*this); + NS_StringContainerInit2(*this, aStart, aLength, + NS_STRING_CONTAINER_INIT_DEPEND | + NS_STRING_CONTAINER_INIT_SUBSTRING); + } + +private: + self_type& operator=(const self_type& aString) = delete; +}; + +class nsDependentCSubstring : public nsCStringContainer +{ +public: + typedef nsDependentCSubstring self_type; + typedef nsACString abstract_string_type; + + ~nsDependentCSubstring() { NS_CStringContainerFinish(*this); } + nsDependentCSubstring() { NS_CStringContainerInit(*this); } + + nsDependentCSubstring(const char_type* aStart, uint32_t aLength) + { + NS_CStringContainerInit2(*this, aStart, aLength, + NS_CSTRING_CONTAINER_INIT_DEPEND | + NS_CSTRING_CONTAINER_INIT_SUBSTRING); + } + + nsDependentCSubstring(const abstract_string_type& aStr, + uint32_t aStartPos); + nsDependentCSubstring(const abstract_string_type& aStr, + uint32_t aStartPos, uint32_t aLength); + + void Rebind(const char_type* aStart, uint32_t aLength) + { + NS_CStringContainerFinish(*this); + NS_CStringContainerInit2(*this, aStart, aLength, + NS_CSTRING_CONTAINER_INIT_DEPEND | + NS_CSTRING_CONTAINER_INIT_SUBSTRING); + } + +private: + self_type& operator=(const self_type& aString) = delete; +}; + + +/** + * Various nsDependentC?Substring constructor functions + */ + +// char16_t +inline const nsDependentSubstring +Substring(const nsAString& aStr, uint32_t aStartPos) +{ + return nsDependentSubstring(aStr, aStartPos); +} + +inline const nsDependentSubstring +Substring(const nsAString& aStr, uint32_t aStartPos, uint32_t aLength) +{ + return nsDependentSubstring(aStr, aStartPos, aLength); +} + +inline const nsDependentSubstring +Substring(const char16_t* aStart, const char16_t* aEnd) +{ + MOZ_ASSERT(uint32_t(aEnd - aStart) == uintptr_t(aEnd - aStart), + "string too long"); + return nsDependentSubstring(aStart, uint32_t(aEnd - aStart)); +} + +inline const nsDependentSubstring +Substring(const char16_t* aStart, uint32_t aLength) +{ + return nsDependentSubstring(aStart, aLength); +} + +inline const nsDependentSubstring +StringHead(const nsAString& aStr, uint32_t aCount) +{ + return nsDependentSubstring(aStr, 0, aCount); +} + +inline const nsDependentSubstring +StringTail(const nsAString& aStr, uint32_t aCount) +{ + return nsDependentSubstring(aStr, aStr.Length() - aCount, aCount); +} + +// char +inline const nsDependentCSubstring +Substring(const nsACString& aStr, uint32_t aStartPos) +{ + return nsDependentCSubstring(aStr, aStartPos); +} + +inline const nsDependentCSubstring +Substring(const nsACString& aStr, uint32_t aStartPos, uint32_t aLength) +{ + return nsDependentCSubstring(aStr, aStartPos, aLength); +} + +inline const nsDependentCSubstring +Substring(const char* aStart, const char* aEnd) +{ + MOZ_ASSERT(uint32_t(aEnd - aStart) == uintptr_t(aEnd - aStart), + "string too long"); + return nsDependentCSubstring(aStart, uint32_t(aEnd - aStart)); +} + +inline const nsDependentCSubstring +Substring(const char* aStart, uint32_t aLength) +{ + return nsDependentCSubstring(aStart, aLength); +} + +inline const nsDependentCSubstring +StringHead(const nsACString& aStr, uint32_t aCount) +{ + return nsDependentCSubstring(aStr, 0, aCount); +} + +inline const nsDependentCSubstring +StringTail(const nsACString& aStr, uint32_t aCount) +{ + return nsDependentCSubstring(aStr, aStr.Length() - aCount, aCount); +} + + +inline bool +StringBeginsWith(const nsAString& aSource, const nsAString& aSubstring, + nsAString::ComparatorFunc aComparator = nsAString::DefaultComparator) +{ + return aSubstring.Length() <= aSource.Length() && + StringHead(aSource, aSubstring.Length()).Equals(aSubstring, aComparator); +} + +inline bool +StringEndsWith(const nsAString& aSource, const nsAString& aSubstring, + nsAString::ComparatorFunc aComparator = nsAString::DefaultComparator) +{ + return aSubstring.Length() <= aSource.Length() && + StringTail(aSource, aSubstring.Length()).Equals(aSubstring, aComparator); +} + +inline bool +StringBeginsWith(const nsACString& aSource, const nsACString& aSubstring, + nsACString::ComparatorFunc aComparator = nsACString::DefaultComparator) +{ + return aSubstring.Length() <= aSource.Length() && + StringHead(aSource, aSubstring.Length()).Equals(aSubstring, aComparator); +} + +inline bool +StringEndsWith(const nsACString& aSource, const nsACString& aSubstring, + nsACString::ComparatorFunc aComparator = nsACString::DefaultComparator) +{ + return aSubstring.Length() <= aSource.Length() && + StringTail(aSource, aSubstring.Length()).Equals(aSubstring, aComparator); +} + +/** + * Trim whitespace from the beginning and end of a string; then compress + * remaining runs of whitespace characters to a single space. + */ +NS_HIDDEN_(void) CompressWhitespace(nsAString& aString); + +#define EmptyCString() nsCString() +#define EmptyString() nsString() + +/** + * Convert an ASCII string to all upper/lowercase (a-z,A-Z only). As a bonus, + * returns the string length. + */ +NS_HIDDEN_(uint32_t) ToLowerCase(nsACString& aStr); + +NS_HIDDEN_(uint32_t) ToUpperCase(nsACString& aStr); + +NS_HIDDEN_(uint32_t) ToLowerCase(const nsACString& aSrc, nsACString& aDest); + +NS_HIDDEN_(uint32_t) ToUpperCase(const nsACString& aSrc, nsACString& aDest); + +/** + * The following declarations are *deprecated*, and are included here only + * to make porting from existing code that doesn't use the frozen string API + * easier. They may disappear in the future. + */ + +inline char* +ToNewCString(const nsACString& aStr) +{ + return NS_CStringCloneData(aStr); +} + +inline char16_t* +ToNewUnicode(const nsAString& aStr) +{ + return NS_StringCloneData(aStr); +} + +typedef nsString PromiseFlatString; +typedef nsCString PromiseFlatCString; + +typedef nsCString nsAutoCString; +typedef nsString nsAutoString; + +NS_HIDDEN_(bool) ParseString(const nsACString& aAstring, char aDelimiter, + nsTArray<nsCString>& aArray); + +#endif // nsStringAPI_h__ diff --git a/xpcom/glue/nsStringGlue.h b/xpcom/glue/nsStringGlue.h new file mode 100644 index 0000000000..a444eb8a19 --- /dev/null +++ b/xpcom/glue/nsStringGlue.h @@ -0,0 +1,24 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +// IWYU pragma: private, include "nsString.h" + +/** + * @file nsStringGlue.h + * This header exists solely to #include the proper internal/frozen string + * headers, depending on whether MOZILLA_INTERNAL_API is defined. + */ + +#ifndef nsStringGlue_h__ +#define nsStringGlue_h__ + +#ifdef MOZILLA_INTERNAL_API +#include "nsString.h" +#include "nsReadableUtils.h" +#else +#include "nsStringAPI.h" +#endif + +#endif // nsStringGlue_h__ diff --git a/xpcom/glue/nsTArray-inl.h b/xpcom/glue/nsTArray-inl.h new file mode 100644 index 0000000000..af57c9866f --- /dev/null +++ b/xpcom/glue/nsTArray-inl.h @@ -0,0 +1,463 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsTArray_h__ +# error "Don't include this file directly" +#endif + +template<class Alloc, class Copy> +nsTArray_base<Alloc, Copy>::nsTArray_base() + : mHdr(EmptyHdr()) +{ + MOZ_COUNT_CTOR(nsTArray_base); +} + +template<class Alloc, class Copy> +nsTArray_base<Alloc, Copy>::~nsTArray_base() +{ + if (mHdr != EmptyHdr() && !UsesAutoArrayBuffer()) { + Alloc::Free(mHdr); + } + MOZ_COUNT_DTOR(nsTArray_base); +} + +template<class Alloc, class Copy> +const nsTArrayHeader* +nsTArray_base<Alloc, Copy>::GetAutoArrayBufferUnsafe(size_t aElemAlign) const +{ + // Assuming |this| points to an nsAutoArray, we want to get a pointer to + // mAutoBuf. So just cast |this| to nsAutoArray* and read &mAutoBuf! + + const void* autoBuf = + &reinterpret_cast<const AutoTArray<nsTArray<uint32_t>, 1>*>(this)->mAutoBuf; + + // If we're on a 32-bit system and aElemAlign is 8, we need to adjust our + // pointer to take into account the extra alignment in the auto array. + + static_assert(sizeof(void*) != 4 || + (MOZ_ALIGNOF(mozilla::AlignedElem<8>) == 8 && + sizeof(AutoTArray<mozilla::AlignedElem<8>, 1>) == + sizeof(void*) + sizeof(nsTArrayHeader) + + 4 + sizeof(mozilla::AlignedElem<8>)), + "auto array padding wasn't what we expected"); + + // We don't support alignments greater than 8 bytes. + MOZ_ASSERT(aElemAlign <= 4 || aElemAlign == 8, + "unsupported alignment."); + if (sizeof(void*) == 4 && aElemAlign == 8) { + autoBuf = reinterpret_cast<const char*>(autoBuf) + 4; + } + + return reinterpret_cast<const Header*>(autoBuf); +} + +template<class Alloc, class Copy> +bool +nsTArray_base<Alloc, Copy>::UsesAutoArrayBuffer() const +{ + if (!mHdr->mIsAutoArray) { + return false; + } + + // This is nuts. If we were sane, we'd pass aElemAlign as a parameter to + // this function. Unfortunately this function is called in nsTArray_base's + // destructor, at which point we don't know elem_type's alignment. + // + // We'll fall on our face and return true when we should say false if + // + // * we're not using our auto buffer, + // * aElemAlign == 4, and + // * mHdr == GetAutoArrayBuffer(8). + // + // This could happen if |*this| lives on the heap and malloc allocated our + // buffer on the heap adjacent to |*this|. + // + // However, we can show that this can't happen. If |this| is an auto array + // (as we ensured at the beginning of the method), GetAutoArrayBuffer(8) + // always points to memory owned by |*this|, because (as we assert below) + // + // * GetAutoArrayBuffer(8) is at most 4 bytes past GetAutoArrayBuffer(4), and + // * sizeof(nsTArrayHeader) > 4. + // + // Since AutoTArray always contains an nsTArrayHeader, + // GetAutoArrayBuffer(8) will always point inside the auto array object, + // even if it doesn't point at the beginning of the header. + // + // Note that this means that we can't store elements with alignment 16 in an + // nsTArray, because GetAutoArrayBuffer(16) could lie outside the memory + // owned by this AutoTArray. We statically assert that elem_type's + // alignment is 8 bytes or less in AutoTArray. + + static_assert(sizeof(nsTArrayHeader) > 4, + "see comment above"); + +#ifdef DEBUG + ptrdiff_t diff = reinterpret_cast<const char*>(GetAutoArrayBuffer(8)) - + reinterpret_cast<const char*>(GetAutoArrayBuffer(4)); + MOZ_ASSERT(diff >= 0 && diff <= 4, + "GetAutoArrayBuffer doesn't do what we expect."); +#endif + + return mHdr == GetAutoArrayBuffer(4) || mHdr == GetAutoArrayBuffer(8); +} + +// defined in nsTArray.cpp +bool IsTwiceTheRequiredBytesRepresentableAsUint32(size_t aCapacity, + size_t aElemSize); + +template<class Alloc, class Copy> +template<typename ActualAlloc> +typename ActualAlloc::ResultTypeProxy +nsTArray_base<Alloc, Copy>::EnsureCapacity(size_type aCapacity, + size_type aElemSize) +{ + // This should be the most common case so test this first + if (aCapacity <= mHdr->mCapacity) { + return ActualAlloc::SuccessResult(); + } + + // If the requested memory allocation exceeds size_type(-1)/2, then + // our doubling algorithm may not be able to allocate it. + // Additionally, if it exceeds uint32_t(-1) then we couldn't fit in the + // Header::mCapacity member. Just bail out in cases like that. We don't want + // to be allocating 2 GB+ arrays anyway. + if (!IsTwiceTheRequiredBytesRepresentableAsUint32(aCapacity, aElemSize)) { + ActualAlloc::SizeTooBig((size_t)aCapacity * aElemSize); + return ActualAlloc::FailureResult(); + } + + size_t reqSize = sizeof(Header) + aCapacity * aElemSize; + + if (mHdr == EmptyHdr()) { + // Malloc() new data + Header* header = static_cast<Header*>(ActualAlloc::Malloc(reqSize)); + if (!header) { + return ActualAlloc::FailureResult(); + } + header->mLength = 0; + header->mCapacity = aCapacity; + header->mIsAutoArray = 0; + mHdr = header; + + return ActualAlloc::SuccessResult(); + } + + // We increase our capacity so that the allocated buffer grows exponentially, + // which gives us amortized O(1) appending. Below the threshold, we use + // powers-of-two. Above the threshold, we grow by at least 1.125, rounding up + // to the nearest MiB. + const size_t slowGrowthThreshold = 8 * 1024 * 1024; + + size_t bytesToAlloc; + if (reqSize >= slowGrowthThreshold) { + size_t currSize = sizeof(Header) + Capacity() * aElemSize; + size_t minNewSize = currSize + (currSize >> 3); // multiply by 1.125 + bytesToAlloc = reqSize > minNewSize ? reqSize : minNewSize; + + // Round up to the next multiple of MiB. + const size_t MiB = 1 << 20; + bytesToAlloc = MiB * ((bytesToAlloc + MiB - 1) / MiB); + } else { + // Round up to the next power of two. + bytesToAlloc = mozilla::RoundUpPow2(reqSize); + } + + Header* header; + if (UsesAutoArrayBuffer() || !Copy::allowRealloc) { + // Malloc() and copy + header = static_cast<Header*>(ActualAlloc::Malloc(bytesToAlloc)); + if (!header) { + return ActualAlloc::FailureResult(); + } + + Copy::MoveNonOverlappingRegionWithHeader(header, mHdr, Length(), aElemSize); + + if (!UsesAutoArrayBuffer()) { + ActualAlloc::Free(mHdr); + } + } else { + // Realloc() existing data + header = static_cast<Header*>(ActualAlloc::Realloc(mHdr, bytesToAlloc)); + if (!header) { + return ActualAlloc::FailureResult(); + } + } + + // How many elements can we fit in bytesToAlloc? + size_t newCapacity = (bytesToAlloc - sizeof(Header)) / aElemSize; + MOZ_ASSERT(newCapacity >= aCapacity, "Didn't enlarge the array enough!"); + header->mCapacity = newCapacity; + + mHdr = header; + + return ActualAlloc::SuccessResult(); +} + +// We don't need use Alloc template parameter specified here because failure to +// shrink the capacity will leave the array unchanged. +template<class Alloc, class Copy> +void +nsTArray_base<Alloc, Copy>::ShrinkCapacity(size_type aElemSize, + size_t aElemAlign) +{ + if (mHdr == EmptyHdr() || UsesAutoArrayBuffer()) { + return; + } + + if (mHdr->mLength >= mHdr->mCapacity) { // should never be greater than... + return; + } + + size_type length = Length(); + + if (IsAutoArray() && GetAutoArrayBuffer(aElemAlign)->mCapacity >= length) { + Header* header = GetAutoArrayBuffer(aElemAlign); + + // Move the data, but don't copy the header to avoid overwriting mCapacity. + header->mLength = length; + Copy::MoveNonOverlappingRegion(header + 1, mHdr + 1, length, aElemSize); + + nsTArrayFallibleAllocator::Free(mHdr); + mHdr = header; + return; + } + + if (length == 0) { + MOZ_ASSERT(!IsAutoArray(), "autoarray should have fit 0 elements"); + nsTArrayFallibleAllocator::Free(mHdr); + mHdr = EmptyHdr(); + return; + } + + size_type size = sizeof(Header) + length * aElemSize; + void* ptr = nsTArrayFallibleAllocator::Realloc(mHdr, size); + if (!ptr) { + return; + } + mHdr = static_cast<Header*>(ptr); + mHdr->mCapacity = length; +} + +template<class Alloc, class Copy> +template<typename ActualAlloc> +void +nsTArray_base<Alloc, Copy>::ShiftData(index_type aStart, + size_type aOldLen, size_type aNewLen, + size_type aElemSize, size_t aElemAlign) +{ + if (aOldLen == aNewLen) { + return; + } + + // Determine how many elements need to be shifted + size_type num = mHdr->mLength - (aStart + aOldLen); + + // Compute the resulting length of the array + mHdr->mLength += aNewLen - aOldLen; + if (mHdr->mLength == 0) { + ShrinkCapacity(aElemSize, aElemAlign); + } else { + // Maybe nothing needs to be shifted + if (num == 0) { + return; + } + // Perform shift (change units to bytes first) + aStart *= aElemSize; + aNewLen *= aElemSize; + aOldLen *= aElemSize; + char* baseAddr = reinterpret_cast<char*>(mHdr + 1) + aStart; + Copy::MoveOverlappingRegion(baseAddr + aNewLen, baseAddr + aOldLen, num, aElemSize); + } +} + +template<class Alloc, class Copy> +template<typename ActualAlloc> +bool +nsTArray_base<Alloc, Copy>::InsertSlotsAt(index_type aIndex, size_type aCount, + size_type aElemSize, + size_t aElemAlign) +{ + MOZ_ASSERT(aIndex <= Length(), "Bogus insertion index"); + size_type newLen = Length() + aCount; + + EnsureCapacity<ActualAlloc>(newLen, aElemSize); + + // Check for out of memory conditions + if (Capacity() < newLen) { + return false; + } + + // Move the existing elements as needed. Note that this will + // change our mLength, so no need to call IncrementLength. + ShiftData<ActualAlloc>(aIndex, 0, aCount, aElemSize, aElemAlign); + + return true; +} + +// nsTArray_base::IsAutoArrayRestorer is an RAII class which takes +// |nsTArray_base &array| in its constructor. When it's destructed, it ensures +// that +// +// * array.mIsAutoArray has the same value as it did when we started, and +// * if array has an auto buffer and mHdr would otherwise point to sEmptyHdr, +// array.mHdr points to array's auto buffer. + +template<class Alloc, class Copy> +nsTArray_base<Alloc, Copy>::IsAutoArrayRestorer::IsAutoArrayRestorer( + nsTArray_base<Alloc, Copy>& aArray, + size_t aElemAlign) + : mArray(aArray) + , mElemAlign(aElemAlign) + , mIsAuto(aArray.IsAutoArray()) +{ +} + +template<class Alloc, class Copy> +nsTArray_base<Alloc, Copy>::IsAutoArrayRestorer::~IsAutoArrayRestorer() +{ + // Careful: We don't want to set mIsAutoArray = 1 on sEmptyHdr. + if (mIsAuto && mArray.mHdr == mArray.EmptyHdr()) { + // Call GetAutoArrayBufferUnsafe() because GetAutoArrayBuffer() asserts + // that mHdr->mIsAutoArray is true, which surely isn't the case here. + mArray.mHdr = mArray.GetAutoArrayBufferUnsafe(mElemAlign); + mArray.mHdr->mLength = 0; + } else if (mArray.mHdr != mArray.EmptyHdr()) { + mArray.mHdr->mIsAutoArray = mIsAuto; + } +} + +template<class Alloc, class Copy> +template<typename ActualAlloc, class Allocator> +typename ActualAlloc::ResultTypeProxy +nsTArray_base<Alloc, Copy>::SwapArrayElements(nsTArray_base<Allocator, + Copy>& aOther, + size_type aElemSize, + size_t aElemAlign) +{ + + // EnsureNotUsingAutoArrayBuffer will set mHdr = sEmptyHdr even if we have an + // auto buffer. We need to point mHdr back to our auto buffer before we + // return, otherwise we'll forget that we have an auto buffer at all! + // IsAutoArrayRestorer takes care of this for us. + + IsAutoArrayRestorer ourAutoRestorer(*this, aElemAlign); + typename nsTArray_base<Allocator, Copy>::IsAutoArrayRestorer + otherAutoRestorer(aOther, aElemAlign); + + // If neither array uses an auto buffer which is big enough to store the + // other array's elements, then ensure that both arrays use malloc'ed storage + // and swap their mHdr pointers. + if ((!UsesAutoArrayBuffer() || Capacity() < aOther.Length()) && + (!aOther.UsesAutoArrayBuffer() || aOther.Capacity() < Length())) { + + if (!EnsureNotUsingAutoArrayBuffer<ActualAlloc>(aElemSize) || + !aOther.template EnsureNotUsingAutoArrayBuffer<ActualAlloc>(aElemSize)) { + return ActualAlloc::FailureResult(); + } + + Header* temp = mHdr; + mHdr = aOther.mHdr; + aOther.mHdr = temp; + + return ActualAlloc::SuccessResult(); + } + + // Swap the two arrays by copying, since at least one is using an auto + // buffer which is large enough to hold all of the aOther's elements. We'll + // copy the shorter array into temporary storage. + // + // (We could do better than this in some circumstances. Suppose we're + // swapping arrays X and Y. X has space for 2 elements in its auto buffer, + // but currently has length 4, so it's using malloc'ed storage. Y has length + // 2. When we swap X and Y, we don't need to use a temporary buffer; we can + // write Y straight into X's auto buffer, write X's malloc'ed buffer on top + // of Y, and then switch X to using its auto buffer.) + + if (!ActualAlloc::Successful(EnsureCapacity<ActualAlloc>(aOther.Length(), aElemSize)) || + !Allocator::Successful(aOther.template EnsureCapacity<Allocator>(Length(), aElemSize))) { + return ActualAlloc::FailureResult(); + } + + // The EnsureCapacity calls above shouldn't have caused *both* arrays to + // switch from their auto buffers to malloc'ed space. + MOZ_ASSERT(UsesAutoArrayBuffer() || aOther.UsesAutoArrayBuffer(), + "One of the arrays should be using its auto buffer."); + + size_type smallerLength = XPCOM_MIN(Length(), aOther.Length()); + size_type largerLength = XPCOM_MAX(Length(), aOther.Length()); + void* smallerElements; + void* largerElements; + if (Length() <= aOther.Length()) { + smallerElements = Hdr() + 1; + largerElements = aOther.Hdr() + 1; + } else { + smallerElements = aOther.Hdr() + 1; + largerElements = Hdr() + 1; + } + + // Allocate temporary storage for the smaller of the two arrays. We want to + // allocate this space on the stack, if it's not too large. Sounds like a + // job for AutoTArray! (One of the two arrays we're swapping is using an + // auto buffer, so we're likely not allocating a lot of space here. But one + // could, in theory, allocate a huge AutoTArray on the heap.) + AutoTArray<nsTArray_Impl<uint8_t, ActualAlloc>, 64> temp; + if (!ActualAlloc::Successful(temp.template EnsureCapacity<ActualAlloc>(smallerLength, + aElemSize))) { + return ActualAlloc::FailureResult(); + } + + Copy::MoveNonOverlappingRegion(temp.Elements(), smallerElements, smallerLength, aElemSize); + Copy::MoveNonOverlappingRegion(smallerElements, largerElements, largerLength, aElemSize); + Copy::MoveNonOverlappingRegion(largerElements, temp.Elements(), smallerLength, aElemSize); + + // Swap the arrays' lengths. + MOZ_ASSERT((aOther.Length() == 0 || mHdr != EmptyHdr()) && + (Length() == 0 || aOther.mHdr != EmptyHdr()), + "Don't set sEmptyHdr's length."); + size_type tempLength = Length(); + + // Avoid writing to EmptyHdr, since it can trigger false + // positives with TSan. + if (mHdr != EmptyHdr()) { + mHdr->mLength = aOther.Length(); + } + if (aOther.mHdr != EmptyHdr()) { + aOther.mHdr->mLength = tempLength; + } + + return ActualAlloc::SuccessResult(); +} + +template<class Alloc, class Copy> +template<typename ActualAlloc> +bool +nsTArray_base<Alloc, Copy>::EnsureNotUsingAutoArrayBuffer(size_type aElemSize) +{ + if (UsesAutoArrayBuffer()) { + + // If you call this on a 0-length array, we'll set that array's mHdr to + // sEmptyHdr, in flagrant violation of the AutoTArray invariants. It's + // up to you to set it back! (If you don't, the AutoTArray will forget + // that it has an auto buffer.) + if (Length() == 0) { + mHdr = EmptyHdr(); + return true; + } + + size_type size = sizeof(Header) + Length() * aElemSize; + + Header* header = static_cast<Header*>(ActualAlloc::Malloc(size)); + if (!header) { + return false; + } + + Copy::MoveNonOverlappingRegionWithHeader(header, mHdr, Length(), aElemSize); + header->mCapacity = Length(); + mHdr = header; + } + + return true; +} diff --git a/xpcom/glue/nsTArray.cpp b/xpcom/glue/nsTArray.cpp new file mode 100644 index 0000000000..fd8422ec7e --- /dev/null +++ b/xpcom/glue/nsTArray.cpp @@ -0,0 +1,29 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include <string.h> +#include "nsTArray.h" +#include "nsXPCOM.h" +#include "nsDebug.h" +#include "mozilla/CheckedInt.h" +#include "mozilla/IntegerPrintfMacros.h" + +nsTArrayHeader nsTArrayHeader::sEmptyHdr = { 0, 0, 0 }; + +bool +IsTwiceTheRequiredBytesRepresentableAsUint32(size_t aCapacity, size_t aElemSize) +{ + using mozilla::CheckedUint32; + return ((CheckedUint32(aCapacity) * aElemSize) * 2).isValid(); +} + +MOZ_NORETURN MOZ_COLD void +InvalidArrayIndex_CRASH(size_t aIndex, size_t aLength) +{ + MOZ_CRASH_UNSAFE_PRINTF( + "ElementAt(aIndex = %" PRIu64 ", aLength = %" PRIu64 ")", + static_cast<uint64_t>(aIndex), static_cast<uint64_t>(aLength)); +} diff --git a/xpcom/glue/nsTArray.h b/xpcom/glue/nsTArray.h new file mode 100644 index 0000000000..ca74a41f76 --- /dev/null +++ b/xpcom/glue/nsTArray.h @@ -0,0 +1,2371 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsTArray_h__ +#define nsTArray_h__ + +#include "nsTArrayForwardDeclare.h" +#include "mozilla/Alignment.h" +#include "mozilla/Assertions.h" +#include "mozilla/Attributes.h" +#include "mozilla/BinarySearch.h" +#include "mozilla/fallible.h" +#include "mozilla/Function.h" +#include "mozilla/MathAlgorithms.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/Move.h" +#include "mozilla/ReverseIterator.h" +#include "mozilla/TypeTraits.h" + +#include <string.h> + +#include "nsCycleCollectionNoteChild.h" +#include "nsAlgorithm.h" +#include "nscore.h" +#include "nsQuickSort.h" +#include "nsDebug.h" +#include "nsISupportsImpl.h" +#include "nsRegionFwd.h" +#include <initializer_list> +#include <new> + +namespace JS { +template<class T> +class Heap; +class ObjectPtr; +} /* namespace JS */ + +class nsRegion; +namespace mozilla { +namespace layers { +struct TileClient; +} // namespace layers +} // namespace mozilla + +namespace mozilla { +struct SerializedStructuredCloneBuffer; +} // namespace mozilla + +namespace mozilla { +namespace dom { +namespace ipc { +class StructuredCloneData; +} // namespace ipc +} // namespace dom +} // namespace mozilla + +namespace mozilla { +namespace dom { +class ClonedMessageData; +class MessagePortMessage; +namespace indexedDB { +struct StructuredCloneReadInfo; +class SerializedStructuredCloneReadInfo; +class ObjectStoreCursorResponse; +} // namespace indexedDB +} // namespace dom +} // namespace mozilla + +class JSStructuredCloneData; + +// +// nsTArray is a resizable array class, like std::vector. +// +// Unlike std::vector, which follows C++'s construction/destruction rules, +// nsTArray assumes that your "T" can be memmoved()'ed safely. +// +// The public classes defined in this header are +// +// nsTArray<T>, +// FallibleTArray<T>, +// AutoTArray<T, N>, and +// +// nsTArray and AutoTArray are infallible by default. To opt-in to fallible +// behaviour, use the `mozilla::fallible` parameter and check the return value. +// +// If you just want to declare the nsTArray types (e.g., if you're in a header +// file and don't need the full nsTArray definitions) consider including +// nsTArrayForwardDeclare.h instead of nsTArray.h. +// +// The template parameter (i.e., T in nsTArray<T>) specifies the type of the +// elements and has the following requirements: +// +// T MUST be safely memmove()'able. +// T MUST define a copy-constructor. +// T MAY define operator< for sorting. +// T MAY define operator== for searching. +// +// (Note that the memmove requirement may be relaxed for certain types - see +// nsTArray_CopyChooser below.) +// +// For methods taking a Comparator instance, the Comparator must be a class +// defining the following methods: +// +// class Comparator { +// public: +// /** @return True if the elements are equals; false otherwise. */ +// bool Equals(const elem_type& a, const Item& b) const; +// +// /** @return True if (a < b); false otherwise. */ +// bool LessThan(const elem_type& a, const Item& b) const; +// }; +// +// The Equals method is used for searching, and the LessThan method is used for +// searching and sorting. The |Item| type above can be arbitrary, but must +// match the Item type passed to the sort or search function. +// + + +// +// nsTArrayFallibleResult and nsTArrayInfallibleResult types are proxy types +// which are used because you cannot use a templated type which is bound to +// void as an argument to a void function. In order to work around that, we +// encode either a void or a boolean inside these proxy objects, and pass them +// to the aforementioned function instead, and then use the type information to +// decide what to do in the function. +// +// Note that public nsTArray methods should never return a proxy type. Such +// types are only meant to be used in the internal nsTArray helper methods. +// Public methods returning non-proxy types cannot be called from other +// nsTArray members. +// +struct nsTArrayFallibleResult +{ + // Note: allows implicit conversions from and to bool + MOZ_IMPLICIT nsTArrayFallibleResult(bool aResult) : mResult(aResult) {} + + MOZ_IMPLICIT operator bool() { return mResult; } + +private: + bool mResult; +}; + +struct nsTArrayInfallibleResult +{ +}; + +// +// nsTArray*Allocators must all use the same |free()|, to allow swap()'ing +// between fallible and infallible variants. +// + +struct nsTArrayFallibleAllocatorBase +{ + typedef bool ResultType; + typedef nsTArrayFallibleResult ResultTypeProxy; + + static ResultType Result(ResultTypeProxy aResult) { return aResult; } + static bool Successful(ResultTypeProxy aResult) { return aResult; } + static ResultTypeProxy SuccessResult() { return true; } + static ResultTypeProxy FailureResult() { return false; } + static ResultType ConvertBoolToResultType(bool aValue) { return aValue; } +}; + +struct nsTArrayInfallibleAllocatorBase +{ + typedef void ResultType; + typedef nsTArrayInfallibleResult ResultTypeProxy; + + static ResultType Result(ResultTypeProxy aResult) {} + static bool Successful(ResultTypeProxy) { return true; } + static ResultTypeProxy SuccessResult() { return ResultTypeProxy(); } + + static ResultTypeProxy FailureResult() + { + NS_RUNTIMEABORT("Infallible nsTArray should never fail"); + return ResultTypeProxy(); + } + + static ResultType ConvertBoolToResultType(bool aValue) + { + if (!aValue) { + NS_RUNTIMEABORT("infallible nsTArray should never convert false to ResultType"); + } + } +}; + +struct nsTArrayFallibleAllocator : nsTArrayFallibleAllocatorBase +{ + static void* Malloc(size_t aSize) { return malloc(aSize); } + static void* Realloc(void* aPtr, size_t aSize) + { + return realloc(aPtr, aSize); + } + + static void Free(void* aPtr) { free(aPtr); } + static void SizeTooBig(size_t) {} +}; + +#if defined(MOZALLOC_HAVE_XMALLOC) +#include "mozilla/mozalloc_abort.h" + +struct nsTArrayInfallibleAllocator : nsTArrayInfallibleAllocatorBase +{ + static void* Malloc(size_t aSize) { return moz_xmalloc(aSize); } + static void* Realloc(void* aPtr, size_t aSize) + { + return moz_xrealloc(aPtr, aSize); + } + + static void Free(void* aPtr) { free(aPtr); } + static void SizeTooBig(size_t aSize) { NS_ABORT_OOM(aSize); } +}; + +#else +#include <stdlib.h> + +struct nsTArrayInfallibleAllocator : nsTArrayInfallibleAllocatorBase +{ + static void* Malloc(size_t aSize) + { + void* ptr = malloc(aSize); + if (MOZ_UNLIKELY(!ptr)) { + NS_ABORT_OOM(aSize); + } + return ptr; + } + + static void* Realloc(void* aPtr, size_t aSize) + { + void* newptr = realloc(aPtr, aSize); + if (MOZ_UNLIKELY(!newptr && aSize)) { + NS_ABORT_OOM(aSize); + } + return newptr; + } + + static void Free(void* aPtr) { free(aPtr); } + static void SizeTooBig(size_t aSize) { NS_ABORT_OOM(aSize); } +}; + +#endif + +// nsTArray_base stores elements into the space allocated beyond +// sizeof(*this). This is done to minimize the size of the nsTArray +// object when it is empty. +struct nsTArrayHeader +{ + static nsTArrayHeader sEmptyHdr; + + uint32_t mLength; + uint32_t mCapacity : 31; + uint32_t mIsAutoArray : 1; +}; + +// This class provides a SafeElementAt method to nsTArray<T*> which does +// not take a second default value parameter. +template<class E, class Derived> +struct nsTArray_SafeElementAtHelper +{ + typedef E* elem_type; + typedef size_t index_type; + + // No implementation is provided for these two methods, and that is on + // purpose, since we don't support these functions on non-pointer type + // instantiations. + elem_type& SafeElementAt(index_type aIndex); + const elem_type& SafeElementAt(index_type aIndex) const; +}; + +template<class E, class Derived> +struct nsTArray_SafeElementAtHelper<E*, Derived> +{ + typedef E* elem_type; + //typedef const E* const_elem_type; XXX: see below + typedef size_t index_type; + + elem_type SafeElementAt(index_type aIndex) + { + return static_cast<Derived*>(this)->SafeElementAt(aIndex, nullptr); + } + + // XXX: Probably should return const_elem_type, but callsites must be fixed. + // Also, the use of const_elem_type for nsTArray<xpcGCCallback> in + // xpcprivate.h causes build failures on Windows because xpcGCCallback is a + // function pointer and MSVC doesn't like qualifying it with |const|. + elem_type SafeElementAt(index_type aIndex) const + { + return static_cast<const Derived*>(this)->SafeElementAt(aIndex, nullptr); + } +}; + +// E is the base type that the smart pointer is templated over; the +// smart pointer can act as E*. +template<class E, class Derived> +struct nsTArray_SafeElementAtSmartPtrHelper +{ + typedef E* elem_type; + typedef const E* const_elem_type; + typedef size_t index_type; + + elem_type SafeElementAt(index_type aIndex) + { + return static_cast<Derived*>(this)->SafeElementAt(aIndex, nullptr); + } + + // XXX: Probably should return const_elem_type, but callsites must be fixed. + elem_type SafeElementAt(index_type aIndex) const + { + return static_cast<const Derived*>(this)->SafeElementAt(aIndex, nullptr); + } +}; + +template<class T> class nsCOMPtr; + +template<class E, class Derived> +struct nsTArray_SafeElementAtHelper<nsCOMPtr<E>, Derived> + : public nsTArray_SafeElementAtSmartPtrHelper<E, Derived> +{ +}; + +template<class E, class Derived> +struct nsTArray_SafeElementAtHelper<RefPtr<E>, Derived> + : public nsTArray_SafeElementAtSmartPtrHelper<E, Derived> +{ +}; + +namespace mozilla { +template<class T> class OwningNonNull; +} // namespace mozilla + +template<class E, class Derived> +struct nsTArray_SafeElementAtHelper<mozilla::OwningNonNull<E>, Derived> +{ + typedef E* elem_type; + typedef const E* const_elem_type; + typedef size_t index_type; + + elem_type SafeElementAt(index_type aIndex) + { + if (aIndex < static_cast<Derived*>(this)->Length()) { + return static_cast<Derived*>(this)->ElementAt(aIndex); + } + return nullptr; + } + + // XXX: Probably should return const_elem_type, but callsites must be fixed. + elem_type SafeElementAt(index_type aIndex) const + { + if (aIndex < static_cast<const Derived*>(this)->Length()) { + return static_cast<const Derived*>(this)->ElementAt(aIndex); + } + return nullptr; + } +}; + +// Servo bindings. +extern "C" void Gecko_EnsureTArrayCapacity(void* aArray, + size_t aCapacity, + size_t aElementSize); +extern "C" void Gecko_ClearPODTArray(void* aArray, + size_t aElementSize, + size_t aElementAlign); + +MOZ_NORETURN MOZ_COLD void +InvalidArrayIndex_CRASH(size_t aIndex, size_t aLength); + +// +// This class serves as a base class for nsTArray. It shouldn't be used +// directly. It holds common implementation code that does not depend on the +// element type of the nsTArray. +// +template<class Alloc, class Copy> +class nsTArray_base +{ + // Allow swapping elements with |nsTArray_base|s created using a + // different allocator. This is kosher because all allocators use + // the same free(). + template<class Allocator, class Copier> + friend class nsTArray_base; + friend void Gecko_EnsureTArrayCapacity(void* aArray, size_t aCapacity, + size_t aElemSize); + friend void Gecko_ClearPODTArray(void* aTArray, size_t aElementSize, + size_t aElementAlign); + +protected: + typedef nsTArrayHeader Header; + +public: + typedef size_t size_type; + typedef size_t index_type; + + // @return The number of elements in the array. + size_type Length() const { return mHdr->mLength; } + + // @return True if the array is empty or false otherwise. + bool IsEmpty() const { return Length() == 0; } + + // @return The number of elements that can fit in the array without forcing + // the array to be re-allocated. The length of an array is always less + // than or equal to its capacity. + size_type Capacity() const { return mHdr->mCapacity; } + +#ifdef DEBUG + void* DebugGetHeader() const { return mHdr; } +#endif + +protected: + nsTArray_base(); + + ~nsTArray_base(); + + // Resize the storage if necessary to achieve the requested capacity. + // @param aCapacity The requested number of array elements. + // @param aElemSize The size of an array element. + // @return False if insufficient memory is available; true otherwise. + template<typename ActualAlloc> + typename ActualAlloc::ResultTypeProxy EnsureCapacity(size_type aCapacity, + size_type aElemSize); + + // Tries to resize the storage to the minimum required amount. If this fails, + // the array is left as-is. + // @param aElemSize The size of an array element. + // @param aElemAlign The alignment in bytes of an array element. + void ShrinkCapacity(size_type aElemSize, size_t aElemAlign); + + // This method may be called to resize a "gap" in the array by shifting + // elements around. It updates mLength appropriately. If the resulting + // array has zero elements, then the array's memory is free'd. + // @param aStart The starting index of the gap. + // @param aOldLen The current length of the gap. + // @param aNewLen The desired length of the gap. + // @param aElemSize The size of an array element. + // @param aElemAlign The alignment in bytes of an array element. + template<typename ActualAlloc> + void ShiftData(index_type aStart, size_type aOldLen, size_type aNewLen, + size_type aElemSize, size_t aElemAlign); + + // This method increments the length member of the array's header. + // Note that mHdr may actually be sEmptyHdr in the case where a + // zero-length array is inserted into our array. But then aNum should + // always be 0. + void IncrementLength(size_t aNum) + { + if (mHdr == EmptyHdr()) { + if (MOZ_UNLIKELY(aNum != 0)) { + // Writing a non-zero length to the empty header would be extremely bad. + MOZ_CRASH(); + } + } else { + mHdr->mLength += aNum; + } + } + + // This method inserts blank slots into the array. + // @param aIndex the place to insert the new elements. This must be no + // greater than the current length of the array. + // @param aCount the number of slots to insert + // @param aElementSize the size of an array element. + // @param aElemAlign the alignment in bytes of an array element. + template<typename ActualAlloc> + bool InsertSlotsAt(index_type aIndex, size_type aCount, + size_type aElementSize, size_t aElemAlign); + + template<typename ActualAlloc, class Allocator> + typename ActualAlloc::ResultTypeProxy + SwapArrayElements(nsTArray_base<Allocator, Copy>& aOther, + size_type aElemSize, + size_t aElemAlign); + + // This is an RAII class used in SwapArrayElements. + class IsAutoArrayRestorer + { + public: + IsAutoArrayRestorer(nsTArray_base<Alloc, Copy>& aArray, size_t aElemAlign); + ~IsAutoArrayRestorer(); + + private: + nsTArray_base<Alloc, Copy>& mArray; + size_t mElemAlign; + bool mIsAuto; + }; + + // Helper function for SwapArrayElements. Ensures that if the array + // is an AutoTArray that it doesn't use the built-in buffer. + template<typename ActualAlloc> + bool EnsureNotUsingAutoArrayBuffer(size_type aElemSize); + + // Returns true if this nsTArray is an AutoTArray with a built-in buffer. + bool IsAutoArray() const { return mHdr->mIsAutoArray; } + + // Returns a Header for the built-in buffer of this AutoTArray. + Header* GetAutoArrayBuffer(size_t aElemAlign) + { + MOZ_ASSERT(IsAutoArray(), "Should be an auto array to call this"); + return GetAutoArrayBufferUnsafe(aElemAlign); + } + const Header* GetAutoArrayBuffer(size_t aElemAlign) const + { + MOZ_ASSERT(IsAutoArray(), "Should be an auto array to call this"); + return GetAutoArrayBufferUnsafe(aElemAlign); + } + + // Returns a Header for the built-in buffer of this AutoTArray, but doesn't + // assert that we are an AutoTArray. + Header* GetAutoArrayBufferUnsafe(size_t aElemAlign) + { + return const_cast<Header*>(static_cast<const nsTArray_base<Alloc, Copy>*>( + this)->GetAutoArrayBufferUnsafe(aElemAlign)); + } + const Header* GetAutoArrayBufferUnsafe(size_t aElemAlign) const; + + // Returns true if this is an AutoTArray and it currently uses the + // built-in buffer to store its elements. + bool UsesAutoArrayBuffer() const; + + // The array's elements (prefixed with a Header). This pointer is never + // null. If the array is empty, then this will point to sEmptyHdr. + Header* mHdr; + + Header* Hdr() const { return mHdr; } + Header** PtrToHdr() { return &mHdr; } + static Header* EmptyHdr() { return &Header::sEmptyHdr; } +}; + +// +// This class defines convenience functions for element specific operations. +// Specialize this template if necessary. +// +template<class E> +class nsTArrayElementTraits +{ +public: + // Invoke the default constructor in place. + static inline void Construct(E* aE) + { + // Do NOT call "E()"! That triggers C++ "default initialization" + // which zeroes out POD ("plain old data") types such as regular + // ints. We don't want that because it can be a performance issue + // and people don't expect it; nsTArray should work like a regular + // C/C++ array in this respect. + new (static_cast<void*>(aE)) E; + } + // Invoke the copy-constructor in place. + template<class A> + static inline void Construct(E* aE, A&& aArg) + { + typedef typename mozilla::RemoveCV<E>::Type E_NoCV; + typedef typename mozilla::RemoveCV<A>::Type A_NoCV; + static_assert(!mozilla::IsSame<E_NoCV*, A_NoCV>::value, + "For safety, we disallow constructing nsTArray<E> elements " + "from E* pointers. See bug 960591."); + new (static_cast<void*>(aE)) E(mozilla::Forward<A>(aArg)); + } + // Invoke the destructor in place. + static inline void Destruct(E* aE) { aE->~E(); } +}; + +// The default comparator used by nsTArray +template<class A, class B> +class nsDefaultComparator +{ +public: + bool Equals(const A& aA, const B& aB) const { return aA == aB; } + bool LessThan(const A& aA, const B& aB) const { return aA < aB; } +}; + +template<bool IsPod, bool IsSameType> +struct AssignRangeAlgorithm +{ + template<class Item, class ElemType, class IndexType, class SizeType> + static void implementation(ElemType* aElements, IndexType aStart, + SizeType aCount, const Item* aValues) + { + ElemType* iter = aElements + aStart; + ElemType* end = iter + aCount; + for (; iter != end; ++iter, ++aValues) { + nsTArrayElementTraits<ElemType>::Construct(iter, *aValues); + } + } +}; + +template<> +struct AssignRangeAlgorithm<true, true> +{ + template<class Item, class ElemType, class IndexType, class SizeType> + static void implementation(ElemType* aElements, IndexType aStart, + SizeType aCount, const Item* aValues) + { + memcpy(aElements + aStart, aValues, aCount * sizeof(ElemType)); + } +}; + +// +// Normally elements are copied with memcpy and memmove, but for some element +// types that is problematic. The nsTArray_CopyChooser template class can be +// specialized to ensure that copying calls constructors and destructors +// instead, as is done below for JS::Heap<E> elements. +// + +// +// A class that defines how to copy elements using memcpy/memmove. +// +struct nsTArray_CopyWithMemutils +{ + const static bool allowRealloc = true; + + static void MoveNonOverlappingRegionWithHeader(void* aDest, const void* aSrc, + size_t aCount, size_t aElemSize) + { + memcpy(aDest, aSrc, sizeof(nsTArrayHeader) + aCount * aElemSize); + } + + static void MoveOverlappingRegion(void* aDest, void* aSrc, size_t aCount, + size_t aElemSize) + { + memmove(aDest, aSrc, aCount * aElemSize); + } + + static void MoveNonOverlappingRegion(void* aDest, void* aSrc, size_t aCount, + size_t aElemSize) + { + memcpy(aDest, aSrc, aCount * aElemSize); + } +}; + +// +// A template class that defines how to copy elements calling their constructors +// and destructors appropriately. +// +template<class ElemType> +struct nsTArray_CopyWithConstructors +{ + typedef nsTArrayElementTraits<ElemType> traits; + + const static bool allowRealloc = false; + + static void MoveNonOverlappingRegionWithHeader(void* aDest, void* aSrc, size_t aCount, + size_t aElemSize) + { + nsTArrayHeader* destHeader = static_cast<nsTArrayHeader*>(aDest); + nsTArrayHeader* srcHeader = static_cast<nsTArrayHeader*>(aSrc); + *destHeader = *srcHeader; + MoveNonOverlappingRegion(static_cast<uint8_t*>(aDest) + sizeof(nsTArrayHeader), + static_cast<uint8_t*>(aSrc) + sizeof(nsTArrayHeader), + aCount, aElemSize); + } + + // These functions are defined by analogy with memmove and memcpy. + // What they actually do is slightly different: MoveOverlappingRegion + // checks to see which direction the movement needs to take place, + // whether from back-to-front of the range to be moved or from + // front-to-back. MoveNonOverlappingRegion assumes that moving + // front-to-back is always valid. So they're really more like + // std::move{_backward,} in that respect. We keep these names because + // we think they read slightly better, and MoveNonOverlappingRegion is + // only ever called on overlapping regions from MoveOverlappingRegion. + static void MoveOverlappingRegion(void* aDest, void* aSrc, size_t aCount, + size_t aElemSize) + { + ElemType* destElem = static_cast<ElemType*>(aDest); + ElemType* srcElem = static_cast<ElemType*>(aSrc); + ElemType* destElemEnd = destElem + aCount; + ElemType* srcElemEnd = srcElem + aCount; + if (destElem == srcElem) { + return; // In practice, we don't do this. + } + + // Figure out whether to copy back-to-front or front-to-back. + if (srcElemEnd > destElem && srcElemEnd < destElemEnd) { + while (destElemEnd != destElem) { + --destElemEnd; + --srcElemEnd; + traits::Construct(destElemEnd, mozilla::Move(*srcElemEnd)); + traits::Destruct(srcElemEnd); + } + } else { + MoveNonOverlappingRegion(aDest, aSrc, aCount, aElemSize); + } + } + + static void MoveNonOverlappingRegion(void* aDest, void* aSrc, size_t aCount, + size_t aElemSize) + { + ElemType* destElem = static_cast<ElemType*>(aDest); + ElemType* srcElem = static_cast<ElemType*>(aSrc); + ElemType* destElemEnd = destElem + aCount; +#ifdef DEBUG + ElemType* srcElemEnd = srcElem + aCount; + MOZ_ASSERT(srcElemEnd <= destElem || srcElemEnd > destElemEnd); +#endif + while (destElem != destElemEnd) { + traits::Construct(destElem, mozilla::Move(*srcElem)); + traits::Destruct(srcElem); + ++destElem; + ++srcElem; + } + } +}; + +// +// The default behaviour is to use memcpy/memmove for everything. +// +template<class E> +struct MOZ_NEEDS_MEMMOVABLE_TYPE nsTArray_CopyChooser +{ + using Type = nsTArray_CopyWithMemutils; +}; + +// +// Some classes require constructors/destructors to be called, so they are +// specialized here. +// +#define DECLARE_USE_COPY_CONSTRUCTORS(T) \ + template<> \ + struct nsTArray_CopyChooser<T> \ + { \ + using Type = nsTArray_CopyWithConstructors<T>; \ + }; + +#define DECLARE_USE_COPY_CONSTRUCTORS_FOR_TEMPLATE(T) \ + template<typename S> \ + struct nsTArray_CopyChooser<T<S>> \ + { \ + using Type = nsTArray_CopyWithConstructors<T<S>>; \ + }; + +DECLARE_USE_COPY_CONSTRUCTORS_FOR_TEMPLATE(JS::Heap) + +DECLARE_USE_COPY_CONSTRUCTORS(nsRegion) +DECLARE_USE_COPY_CONSTRUCTORS(nsIntRegion) +DECLARE_USE_COPY_CONSTRUCTORS(mozilla::layers::TileClient) +DECLARE_USE_COPY_CONSTRUCTORS(mozilla::SerializedStructuredCloneBuffer) +DECLARE_USE_COPY_CONSTRUCTORS(mozilla::dom::ipc::StructuredCloneData) +DECLARE_USE_COPY_CONSTRUCTORS(mozilla::dom::ClonedMessageData) +DECLARE_USE_COPY_CONSTRUCTORS(mozilla::dom::indexedDB::StructuredCloneReadInfo); +DECLARE_USE_COPY_CONSTRUCTORS(mozilla::dom::indexedDB::ObjectStoreCursorResponse) +DECLARE_USE_COPY_CONSTRUCTORS(mozilla::dom::indexedDB::SerializedStructuredCloneReadInfo); +DECLARE_USE_COPY_CONSTRUCTORS(JSStructuredCloneData) +DECLARE_USE_COPY_CONSTRUCTORS(mozilla::dom::MessagePortMessage) +DECLARE_USE_COPY_CONSTRUCTORS(JS::ObjectPtr) + + +// +// Base class for nsTArray_Impl that is templated on element type and derived +// nsTArray_Impl class, to allow extra conversions to be added for specific +// types. +// +template<class E, class Derived> +struct nsTArray_TypedBase : public nsTArray_SafeElementAtHelper<E, Derived> +{ +}; + +// +// Specialization of nsTArray_TypedBase for arrays containing JS::Heap<E> +// elements. +// +// These conversions are safe because JS::Heap<E> and E share the same +// representation, and since the result of the conversions are const references +// we won't miss any barriers. +// +// The static_cast is necessary to obtain the correct address for the derived +// class since we are a base class used in multiple inheritance. +// +template<class E, class Derived> +struct nsTArray_TypedBase<JS::Heap<E>, Derived> + : public nsTArray_SafeElementAtHelper<JS::Heap<E>, Derived> +{ + operator const nsTArray<E>&() + { + static_assert(sizeof(E) == sizeof(JS::Heap<E>), + "JS::Heap<E> must be binary compatible with E."); + Derived* self = static_cast<Derived*>(this); + return *reinterpret_cast<nsTArray<E> *>(self); + } + + operator const FallibleTArray<E>&() + { + Derived* self = static_cast<Derived*>(this); + return *reinterpret_cast<FallibleTArray<E> *>(self); + } +}; + +namespace detail { + +template<class Item, class Comparator> +struct ItemComparatorEq +{ + const Item& mItem; + const Comparator& mComp; + ItemComparatorEq(const Item& aItem, const Comparator& aComp) + : mItem(aItem) + , mComp(aComp) + {} + template<class T> + int operator()(const T& aElement) const { + if (mComp.Equals(aElement, mItem)) { + return 0; + } + + return mComp.LessThan(aElement, mItem) ? 1 : -1; + } +}; + +template<class Item, class Comparator> +struct ItemComparatorFirstElementGT +{ + const Item& mItem; + const Comparator& mComp; + ItemComparatorFirstElementGT(const Item& aItem, const Comparator& aComp) + : mItem(aItem) + , mComp(aComp) + {} + template<class T> + int operator()(const T& aElement) const { + if (mComp.LessThan(aElement, mItem) || + mComp.Equals(aElement, mItem)) { + return 1; + } else { + return -1; + } + } +}; + +} // namespace detail + +// +// nsTArray_Impl contains most of the guts supporting nsTArray, FallibleTArray, +// AutoTArray. +// +// The only situation in which you might need to use nsTArray_Impl in your code +// is if you're writing code which mutates a TArray which may or may not be +// infallible. +// +// Code which merely reads from a TArray which may or may not be infallible can +// simply cast the TArray to |const nsTArray&|; both fallible and infallible +// TArrays can be cast to |const nsTArray&|. +// +template<class E, class Alloc> +class nsTArray_Impl + : public nsTArray_base<Alloc, typename nsTArray_CopyChooser<E>::Type> + , public nsTArray_TypedBase<E, nsTArray_Impl<E, Alloc>> +{ +private: + typedef nsTArrayFallibleAllocator FallibleAlloc; + typedef nsTArrayInfallibleAllocator InfallibleAlloc; + +public: + typedef typename nsTArray_CopyChooser<E>::Type copy_type; + typedef nsTArray_base<Alloc, copy_type> base_type; + typedef typename base_type::size_type size_type; + typedef typename base_type::index_type index_type; + typedef E elem_type; + typedef nsTArray_Impl<E, Alloc> self_type; + typedef nsTArrayElementTraits<E> elem_traits; + typedef nsTArray_SafeElementAtHelper<E, self_type> safeelementat_helper_type; + typedef elem_type* iterator; + typedef const elem_type* const_iterator; + typedef mozilla::ReverseIterator<elem_type*> reverse_iterator; + typedef mozilla::ReverseIterator<const elem_type*> const_reverse_iterator; + + using safeelementat_helper_type::SafeElementAt; + using base_type::EmptyHdr; + + // A special value that is used to indicate an invalid or unknown index + // into the array. + static const index_type NoIndex = index_type(-1); + + using base_type::Length; + + // + // Finalization method + // + + ~nsTArray_Impl() { Clear(); } + + // + // Initialization methods + // + + nsTArray_Impl() {} + + // Initialize this array and pre-allocate some number of elements. + explicit nsTArray_Impl(size_type aCapacity) { SetCapacity(aCapacity); } + + // Initialize this array with an r-value. + // Allow different types of allocators, since the allocator doesn't matter. + template<typename Allocator> + explicit nsTArray_Impl(nsTArray_Impl<E, Allocator>&& aOther) + { + SwapElements(aOther); + } + + // The array's copy-constructor performs a 'deep' copy of the given array. + // @param aOther The array object to copy. + // + // It's very important that we declare this method as taking |const + // self_type&| as opposed to taking |const nsTArray_Impl<E, OtherAlloc>| for + // an arbitrary OtherAlloc. + // + // If we don't declare a constructor taking |const self_type&|, C++ generates + // a copy-constructor for this class which merely copies the object's + // members, which is obviously wrong. + // + // You can pass an nsTArray_Impl<E, OtherAlloc> to this method because + // nsTArray_Impl<E, X> can be cast to const nsTArray_Impl<E, Y>&. So the + // effect on the API is the same as if we'd declared this method as taking + // |const nsTArray_Impl<E, OtherAlloc>&|. + explicit nsTArray_Impl(const self_type& aOther) { AppendElements(aOther); } + + explicit nsTArray_Impl(std::initializer_list<E> aIL) { AppendElements(aIL.begin(), aIL.size()); } + // Allow converting to a const array with a different kind of allocator, + // Since the allocator doesn't matter for const arrays + template<typename Allocator> + operator const nsTArray_Impl<E, Allocator>&() const + { + return *reinterpret_cast<const nsTArray_Impl<E, Allocator>*>(this); + } + // And we have to do this for our subclasses too + operator const nsTArray<E>&() const + { + return *reinterpret_cast<const InfallibleTArray<E>*>(this); + } + operator const FallibleTArray<E>&() const + { + return *reinterpret_cast<const FallibleTArray<E>*>(this); + } + + // The array's assignment operator performs a 'deep' copy of the given + // array. It is optimized to reuse existing storage if possible. + // @param aOther The array object to copy. + self_type& operator=(const self_type& aOther) + { + if (this != &aOther) { + ReplaceElementsAt(0, Length(), aOther.Elements(), aOther.Length()); + } + return *this; + } + + // The array's move assignment operator steals the underlying data from + // the other array. + // @param other The array object to move from. + self_type& operator=(self_type&& aOther) + { + if (this != &aOther) { + Clear(); + SwapElements(aOther); + } + return *this; + } + + // Return true if this array has the same length and the same + // elements as |aOther|. + template<typename Allocator> + bool operator==(const nsTArray_Impl<E, Allocator>& aOther) const + { + size_type len = Length(); + if (len != aOther.Length()) { + return false; + } + + // XXX std::equal would be as fast or faster here + for (index_type i = 0; i < len; ++i) { + if (!(operator[](i) == aOther[i])) { + return false; + } + } + + return true; + } + + // Return true if this array does not have the same length and the same + // elements as |aOther|. + bool operator!=(const self_type& aOther) const { return !operator==(aOther); } + + template<typename Allocator> + self_type& operator=(const nsTArray_Impl<E, Allocator>& aOther) + { + ReplaceElementsAt(0, Length(), aOther.Elements(), aOther.Length()); + return *this; + } + + template<typename Allocator> + self_type& operator=(nsTArray_Impl<E, Allocator>&& aOther) + { + Clear(); + SwapElements(aOther); + return *this; + } + + // @return The amount of memory used by this nsTArray_Impl, excluding + // sizeof(*this). If you want to measure anything hanging off the array, you + // must iterate over the elements and measure them individually; hence the + // "Shallow" prefix. + size_t ShallowSizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const + { + if (this->UsesAutoArrayBuffer() || Hdr() == EmptyHdr()) { + return 0; + } + return aMallocSizeOf(this->Hdr()); + } + + // @return The amount of memory used by this nsTArray_Impl, including + // sizeof(*this). If you want to measure anything hanging off the array, you + // must iterate over the elements and measure them individually; hence the + // "Shallow" prefix. + size_t ShallowSizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const + { + return aMallocSizeOf(this) + ShallowSizeOfExcludingThis(aMallocSizeOf); + } + + // + // Accessor methods + // + + // This method provides direct access to the array elements. + // @return A pointer to the first element of the array. If the array is + // empty, then this pointer must not be dereferenced. + elem_type* Elements() { return reinterpret_cast<elem_type*>(Hdr() + 1); } + + // This method provides direct, readonly access to the array elements. + // @return A pointer to the first element of the array. If the array is + // empty, then this pointer must not be dereferenced. + const elem_type* Elements() const + { + return reinterpret_cast<const elem_type*>(Hdr() + 1); + } + + // This method provides direct access to an element of the array. The given + // index must be within the array bounds. + // @param aIndex The index of an element in the array. + // @return A reference to the i'th element of the array. + elem_type& ElementAt(index_type aIndex) + { + if (MOZ_UNLIKELY(aIndex >= Length())) { + InvalidArrayIndex_CRASH(aIndex, Length()); + } + return Elements()[aIndex]; + } + + // This method provides direct, readonly access to an element of the array + // The given index must be within the array bounds. + // @param aIndex The index of an element in the array. + // @return A const reference to the i'th element of the array. + const elem_type& ElementAt(index_type aIndex) const + { + if (MOZ_UNLIKELY(aIndex >= Length())) { + InvalidArrayIndex_CRASH(aIndex, Length()); + } + return Elements()[aIndex]; + } + + // This method provides direct access to an element of the array in a bounds + // safe manner. If the requested index is out of bounds the provided default + // value is returned. + // @param aIndex The index of an element in the array. + // @param aDef The value to return if the index is out of bounds. + elem_type& SafeElementAt(index_type aIndex, elem_type& aDef) + { + return aIndex < Length() ? Elements()[aIndex] : aDef; + } + + // This method provides direct access to an element of the array in a bounds + // safe manner. If the requested index is out of bounds the provided default + // value is returned. + // @param aIndex The index of an element in the array. + // @param aDef The value to return if the index is out of bounds. + const elem_type& SafeElementAt(index_type aIndex, const elem_type& aDef) const + { + return aIndex < Length() ? Elements()[aIndex] : aDef; + } + + // Shorthand for ElementAt(aIndex) + elem_type& operator[](index_type aIndex) { return ElementAt(aIndex); } + + // Shorthand for ElementAt(aIndex) + const elem_type& operator[](index_type aIndex) const { return ElementAt(aIndex); } + + // Shorthand for ElementAt(length - 1) + elem_type& LastElement() { return ElementAt(Length() - 1); } + + // Shorthand for ElementAt(length - 1) + const elem_type& LastElement() const { return ElementAt(Length() - 1); } + + // Shorthand for SafeElementAt(length - 1, def) + elem_type& SafeLastElement(elem_type& aDef) + { + return SafeElementAt(Length() - 1, aDef); + } + + // Shorthand for SafeElementAt(length - 1, def) + const elem_type& SafeLastElement(const elem_type& aDef) const + { + return SafeElementAt(Length() - 1, aDef); + } + + // Methods for range-based for loops. + iterator begin() { return Elements(); } + const_iterator begin() const { return Elements(); } + const_iterator cbegin() const { return begin(); } + iterator end() { return Elements() + Length(); } + const_iterator end() const { return Elements() + Length(); } + const_iterator cend() const { return end(); } + + // Methods for reverse iterating. + reverse_iterator rbegin() { return reverse_iterator(end()); } + const_reverse_iterator rbegin() const { return const_reverse_iterator(end()); } + const_reverse_iterator crbegin() const { return rbegin(); } + reverse_iterator rend() { return reverse_iterator(begin()); } + const_reverse_iterator rend() const { return const_reverse_iterator(begin()); } + const_reverse_iterator crend() const { return rend(); } + + // + // Search methods + // + + // This method searches for the first element in this array that is equal + // to the given element. + // @param aItem The item to search for. + // @param aComp The Comparator used to determine element equality. + // @return true if the element was found. + template<class Item, class Comparator> + bool Contains(const Item& aItem, const Comparator& aComp) const + { + return IndexOf(aItem, 0, aComp) != NoIndex; + } + + // This method searches for the first element in this array that is equal + // to the given element. This method assumes that 'operator==' is defined + // for elem_type. + // @param aItem The item to search for. + // @return true if the element was found. + template<class Item> + bool Contains(const Item& aItem) const + { + return IndexOf(aItem) != NoIndex; + } + + // This method searches for the offset of the first element in this + // array that is equal to the given element. + // @param aItem The item to search for. + // @param aStart The index to start from. + // @param aComp The Comparator used to determine element equality. + // @return The index of the found element or NoIndex if not found. + template<class Item, class Comparator> + index_type IndexOf(const Item& aItem, index_type aStart, + const Comparator& aComp) const + { + const elem_type* iter = Elements() + aStart; + const elem_type* iend = Elements() + Length(); + for (; iter != iend; ++iter) { + if (aComp.Equals(*iter, aItem)) { + return index_type(iter - Elements()); + } + } + return NoIndex; + } + + // This method searches for the offset of the first element in this + // array that is equal to the given element. This method assumes + // that 'operator==' is defined for elem_type. + // @param aItem The item to search for. + // @param aStart The index to start from. + // @return The index of the found element or NoIndex if not found. + template<class Item> + index_type IndexOf(const Item& aItem, index_type aStart = 0) const + { + return IndexOf(aItem, aStart, nsDefaultComparator<elem_type, Item>()); + } + + // This method searches for the offset of the last element in this + // array that is equal to the given element. + // @param aItem The item to search for. + // @param aStart The index to start from. If greater than or equal to the + // length of the array, then the entire array is searched. + // @param aComp The Comparator used to determine element equality. + // @return The index of the found element or NoIndex if not found. + template<class Item, class Comparator> + index_type LastIndexOf(const Item& aItem, index_type aStart, + const Comparator& aComp) const + { + size_type endOffset = aStart >= Length() ? Length() : aStart + 1; + const elem_type* iend = Elements() - 1; + const elem_type* iter = iend + endOffset; + for (; iter != iend; --iter) { + if (aComp.Equals(*iter, aItem)) { + return index_type(iter - Elements()); + } + } + return NoIndex; + } + + // This method searches for the offset of the last element in this + // array that is equal to the given element. This method assumes + // that 'operator==' is defined for elem_type. + // @param aItem The item to search for. + // @param aStart The index to start from. If greater than or equal to the + // length of the array, then the entire array is searched. + // @return The index of the found element or NoIndex if not found. + template<class Item> + index_type LastIndexOf(const Item& aItem, + index_type aStart = NoIndex) const + { + return LastIndexOf(aItem, aStart, nsDefaultComparator<elem_type, Item>()); + } + + // This method searches for the offset for the element in this array + // that is equal to the given element. The array is assumed to be sorted. + // If there is more than one equivalent element, there is no guarantee + // on which one will be returned. + // @param aItem The item to search for. + // @param aComp The Comparator used. + // @return The index of the found element or NoIndex if not found. + template<class Item, class Comparator> + index_type BinaryIndexOf(const Item& aItem, const Comparator& aComp) const + { + using mozilla::BinarySearchIf; + typedef ::detail::ItemComparatorEq<Item, Comparator> Cmp; + + size_t index; + bool found = BinarySearchIf(*this, 0, Length(), Cmp(aItem, aComp), &index); + return found ? index : NoIndex; + } + + // This method searches for the offset for the element in this array + // that is equal to the given element. The array is assumed to be sorted. + // This method assumes that 'operator==' and 'operator<' are defined. + // @param aItem The item to search for. + // @return The index of the found element or NoIndex if not found. + template<class Item> + index_type BinaryIndexOf(const Item& aItem) const + { + return BinaryIndexOf(aItem, nsDefaultComparator<elem_type, Item>()); + } + + // + // Mutation methods + // + + template<class Allocator, typename ActualAlloc = Alloc> + typename ActualAlloc::ResultType Assign( + const nsTArray_Impl<E, Allocator>& aOther) + { + return ActualAlloc::ConvertBoolToResultType( + !!ReplaceElementsAt<E, ActualAlloc>(0, Length(), + aOther.Elements(), aOther.Length())); + } + + template<class Allocator> + MOZ_MUST_USE + bool Assign(const nsTArray_Impl<E, Allocator>& aOther, + const mozilla::fallible_t&) + { + return Assign<Allocator, FallibleAlloc>(aOther); + } + + template<class Allocator> + void Assign(nsTArray_Impl<E, Allocator>&& aOther) + { + Clear(); + SwapElements(aOther); + } + + // This method call the destructor on each element of the array, empties it, + // but does not shrink the array's capacity. + // See also SetLengthAndRetainStorage. + // Make sure to call Compact() if needed to avoid keeping a huge array + // around. + void ClearAndRetainStorage() + { + if (base_type::mHdr == EmptyHdr()) { + return; + } + + DestructRange(0, Length()); + base_type::mHdr->mLength = 0; + } + + // This method modifies the length of the array, but unlike SetLength + // it doesn't deallocate/reallocate the current internal storage. + // The new length MUST be shorter than or equal to the current capacity. + // If the new length is larger than the existing length of the array, + // then new elements will be constructed using elem_type's default + // constructor. If shorter, elements will be destructed and removed. + // See also ClearAndRetainStorage. + // @param aNewLen The desired length of this array. + void SetLengthAndRetainStorage(size_type aNewLen) + { + MOZ_ASSERT(aNewLen <= base_type::Capacity()); + size_type oldLen = Length(); + if (aNewLen > oldLen) { + InsertElementsAt(oldLen, aNewLen - oldLen); + return; + } + if (aNewLen < oldLen) { + DestructRange(aNewLen, oldLen - aNewLen); + base_type::mHdr->mLength = aNewLen; + } + } + + // This method replaces a range of elements in this array. + // @param aStart The starting index of the elements to replace. + // @param aCount The number of elements to replace. This may be zero to + // insert elements without removing any existing elements. + // @param aArray The values to copy into this array. Must be non-null, + // and these elements must not already exist in the array + // being modified. + // @param aArrayLen The number of values to copy into this array. + // @return A pointer to the new elements in the array, or null if + // the operation failed due to insufficient memory. +protected: + template<class Item, typename ActualAlloc = Alloc> + elem_type* ReplaceElementsAt(index_type aStart, size_type aCount, + const Item* aArray, size_type aArrayLen); + +public: + + template<class Item> + MOZ_MUST_USE + elem_type* ReplaceElementsAt(index_type aStart, size_type aCount, + const Item* aArray, size_type aArrayLen, + const mozilla::fallible_t&) + { + return ReplaceElementsAt<Item, FallibleAlloc>(aStart, aCount, + aArray, aArrayLen); + } + + // A variation on the ReplaceElementsAt method defined above. +protected: + template<class Item, typename ActualAlloc = Alloc> + elem_type* ReplaceElementsAt(index_type aStart, size_type aCount, + const nsTArray<Item>& aArray) + { + return ReplaceElementsAt<Item, ActualAlloc>( + aStart, aCount, aArray.Elements(), aArray.Length()); + } +public: + + template<class Item> + MOZ_MUST_USE + elem_type* ReplaceElementsAt(index_type aStart, size_type aCount, + const nsTArray<Item>& aArray, + const mozilla::fallible_t&) + { + return ReplaceElementsAt<Item, FallibleAlloc>(aStart, aCount, aArray); + } + + // A variation on the ReplaceElementsAt method defined above. +protected: + template<class Item, typename ActualAlloc = Alloc> + elem_type* ReplaceElementsAt(index_type aStart, size_type aCount, + const Item& aItem) + { + return ReplaceElementsAt<Item, ActualAlloc>(aStart, aCount, &aItem, 1); + } +public: + + template<class Item> + MOZ_MUST_USE + elem_type* ReplaceElementsAt(index_type aStart, size_type aCount, + const Item& aItem, const mozilla::fallible_t&) + { + return ReplaceElementsAt<Item, FallibleAlloc>(aStart, aCount, aItem); + } + + // A variation on the ReplaceElementsAt method defined above. + template<class Item> + elem_type* ReplaceElementAt(index_type aIndex, const Item& aItem) + { + return ReplaceElementsAt(aIndex, 1, &aItem, 1); + } + + // A variation on the ReplaceElementsAt method defined above. +protected: + template<class Item, typename ActualAlloc = Alloc> + elem_type* InsertElementsAt(index_type aIndex, const Item* aArray, + size_type aArrayLen) + { + return ReplaceElementsAt<Item, ActualAlloc>(aIndex, 0, aArray, aArrayLen); + } +public: + + template<class Item> + MOZ_MUST_USE + elem_type* InsertElementsAt(index_type aIndex, const Item* aArray, + size_type aArrayLen, const mozilla::fallible_t&) + { + return InsertElementsAt<Item, FallibleAlloc>(aIndex, aArray, aArrayLen); + } + + // A variation on the ReplaceElementsAt method defined above. +protected: + template<class Item, class Allocator, typename ActualAlloc = Alloc> + elem_type* InsertElementsAt(index_type aIndex, + const nsTArray_Impl<Item, Allocator>& aArray) + { + return ReplaceElementsAt<Item, ActualAlloc>( + aIndex, 0, aArray.Elements(), aArray.Length()); + } +public: + + template<class Item, class Allocator> + MOZ_MUST_USE + elem_type* InsertElementsAt(index_type aIndex, + const nsTArray_Impl<Item, Allocator>& aArray, + const mozilla::fallible_t&) + { + return InsertElementsAt<Item, Allocator, FallibleAlloc>(aIndex, aArray); + } + + // Insert a new element without copy-constructing. This is useful to avoid + // temporaries. + // @return A pointer to the newly inserted element, or null on OOM. +protected: + template<typename ActualAlloc = Alloc> + elem_type* InsertElementAt(index_type aIndex); + +public: + + MOZ_MUST_USE + elem_type* InsertElementAt(index_type aIndex, const mozilla::fallible_t&) + { + return InsertElementAt<FallibleAlloc>(aIndex); + } + + // Insert a new element, move constructing if possible. +protected: + template<class Item, typename ActualAlloc = Alloc> + elem_type* InsertElementAt(index_type aIndex, Item&& aItem); + +public: + + template<class Item> + MOZ_MUST_USE + elem_type* InsertElementAt(index_type aIndex, Item&& aItem, + const mozilla::fallible_t&) + { + return InsertElementAt<Item, FallibleAlloc>(aIndex, + mozilla::Forward<Item>(aItem)); + } + + // This method searches for the smallest index of an element that is strictly + // greater than |aItem|. If |aItem| is inserted at this index, the array will + // remain sorted and |aItem| would come after all elements that are equal to + // it. If |aItem| is greater than or equal to all elements in the array, the + // array length is returned. + // + // Note that consumers who want to know whether there are existing items equal + // to |aItem| in the array can just check that the return value here is > 0 + // and indexing into the previous slot gives something equal to |aItem|. + // + // + // @param aItem The item to search for. + // @param aComp The Comparator used. + // @return The index of greatest element <= to |aItem| + // @precondition The array is sorted + template<class Item, class Comparator> + index_type IndexOfFirstElementGt(const Item& aItem, + const Comparator& aComp) const + { + using mozilla::BinarySearchIf; + typedef ::detail::ItemComparatorFirstElementGT<Item, Comparator> Cmp; + + size_t index; + BinarySearchIf(*this, 0, Length(), Cmp(aItem, aComp), &index); + return index; + } + + // A variation on the IndexOfFirstElementGt method defined above. + template<class Item> + index_type + IndexOfFirstElementGt(const Item& aItem) const + { + return IndexOfFirstElementGt(aItem, nsDefaultComparator<elem_type, Item>()); + } + + // Inserts |aItem| at such an index to guarantee that if the array + // was previously sorted, it will remain sorted after this + // insertion. +protected: + template<class Item, class Comparator, typename ActualAlloc = Alloc> + elem_type* InsertElementSorted(Item&& aItem, const Comparator& aComp) + { + index_type index = IndexOfFirstElementGt<Item, Comparator>(aItem, aComp); + return InsertElementAt<Item, ActualAlloc>( + index, mozilla::Forward<Item>(aItem)); + } +public: + + template<class Item, class Comparator> + MOZ_MUST_USE + elem_type* InsertElementSorted(Item&& aItem, const Comparator& aComp, + const mozilla::fallible_t&) + { + return InsertElementSorted<Item, Comparator, FallibleAlloc>( + mozilla::Forward<Item>(aItem), aComp); + } + + // A variation on the InsertElementSorted method defined above. +protected: + template<class Item, typename ActualAlloc = Alloc> + elem_type* InsertElementSorted(Item&& aItem) + { + nsDefaultComparator<elem_type, Item> comp; + return InsertElementSorted<Item, decltype(comp), ActualAlloc>( + mozilla::Forward<Item>(aItem), comp); + } +public: + + template<class Item> + MOZ_MUST_USE + elem_type* InsertElementSorted(Item&& aItem, const mozilla::fallible_t&) + { + return InsertElementSorted<Item, FallibleAlloc>( + mozilla::Forward<Item>(aItem)); + } + + // This method appends elements to the end of this array. + // @param aArray The elements to append to this array. + // @param aArrayLen The number of elements to append to this array. + // @return A pointer to the new elements in the array, or null if + // the operation failed due to insufficient memory. +protected: + template<class Item, typename ActualAlloc = Alloc> + elem_type* AppendElements(const Item* aArray, size_type aArrayLen); + +public: + + template<class Item> + /* MOZ_MUST_USE */ + elem_type* AppendElements(const Item* aArray, size_type aArrayLen, + const mozilla::fallible_t&) + { + return AppendElements<Item, FallibleAlloc>(aArray, aArrayLen); + } + + // A variation on the AppendElements method defined above. +protected: + template<class Item, class Allocator, typename ActualAlloc = Alloc> + elem_type* AppendElements(const nsTArray_Impl<Item, Allocator>& aArray) + { + return AppendElements<Item, ActualAlloc>(aArray.Elements(), aArray.Length()); + } +public: + + template<class Item, class Allocator> + /* MOZ_MUST_USE */ + elem_type* AppendElements(const nsTArray_Impl<Item, Allocator>& aArray, + const mozilla::fallible_t&) + { + return AppendElements<Item, Allocator, FallibleAlloc>(aArray); + } + + // Move all elements from another array to the end of this array. + // @return A pointer to the newly appended elements, or null on OOM. +protected: + template<class Item, class Allocator, typename ActualAlloc = Alloc> + elem_type* AppendElements(nsTArray_Impl<Item, Allocator>&& aArray); + +public: + + template<class Item, class Allocator, typename ActualAlloc = Alloc> + /* MOZ_MUST_USE */ + elem_type* AppendElements(nsTArray_Impl<Item, Allocator>&& aArray, + const mozilla::fallible_t&) + { + return AppendElements<Item, Allocator>(mozilla::Move(aArray)); + } + + // Append a new element, move constructing if possible. +protected: + template<class Item, typename ActualAlloc = Alloc> + elem_type* AppendElement(Item&& aItem); + +public: + + template<class Item> + /* MOZ_MUST_USE */ + elem_type* AppendElement(Item&& aItem, + const mozilla::fallible_t&) + { + return AppendElement<Item, FallibleAlloc>(mozilla::Forward<Item>(aItem)); + } + + // Append new elements without copy-constructing. This is useful to avoid + // temporaries. + // @return A pointer to the newly appended elements, or null on OOM. +protected: + template<typename ActualAlloc = Alloc> + elem_type* AppendElements(size_type aCount) { + if (!ActualAlloc::Successful(this->template EnsureCapacity<ActualAlloc>( + Length() + aCount, sizeof(elem_type)))) { + return nullptr; + } + elem_type* elems = Elements() + Length(); + size_type i; + for (i = 0; i < aCount; ++i) { + elem_traits::Construct(elems + i); + } + this->IncrementLength(aCount); + return elems; + } +public: + + /* MOZ_MUST_USE */ + elem_type* AppendElements(size_type aCount, + const mozilla::fallible_t&) + { + return AppendElements<FallibleAlloc>(aCount); + } + + // Append a new element without copy-constructing. This is useful to avoid + // temporaries. + // @return A pointer to the newly appended element, or null on OOM. +protected: + template<typename ActualAlloc = Alloc> + elem_type* AppendElement() + { + return AppendElements<ActualAlloc>(1); + } +public: + + /* MOZ_MUST_USE */ + elem_type* AppendElement(const mozilla::fallible_t&) + { + return AppendElement<FallibleAlloc>(); + } + + // This method removes a range of elements from this array. + // @param aStart The starting index of the elements to remove. + // @param aCount The number of elements to remove. + void RemoveElementsAt(index_type aStart, size_type aCount); + + // A variation on the RemoveElementsAt method defined above. + void RemoveElementAt(index_type aIndex) { RemoveElementsAt(aIndex, 1); } + + // A variation on the RemoveElementsAt method defined above. + void Clear() { RemoveElementsAt(0, Length()); } + + // This method removes elements based on the return value of the + // callback function aPredicate. If the function returns true for + // an element, the element is removed. aPredicate will be called + // for each element in order. It is not safe to access the array + // inside aPredicate. + template<typename Predicate> + void RemoveElementsBy(Predicate aPredicate); + + // This helper function combines IndexOf with RemoveElementAt to "search + // and destroy" the first element that is equal to the given element. + // @param aItem The item to search for. + // @param aComp The Comparator used to determine element equality. + // @return true if the element was found + template<class Item, class Comparator> + bool RemoveElement(const Item& aItem, const Comparator& aComp) + { + index_type i = IndexOf(aItem, 0, aComp); + if (i == NoIndex) { + return false; + } + + RemoveElementAt(i); + return true; + } + + // A variation on the RemoveElement method defined above that assumes + // that 'operator==' is defined for elem_type. + template<class Item> + bool RemoveElement(const Item& aItem) + { + return RemoveElement(aItem, nsDefaultComparator<elem_type, Item>()); + } + + // This helper function combines IndexOfFirstElementGt with + // RemoveElementAt to "search and destroy" the last element that + // is equal to the given element. + // @param aItem The item to search for. + // @param aComp The Comparator used to determine element equality. + // @return true if the element was found + template<class Item, class Comparator> + bool RemoveElementSorted(const Item& aItem, const Comparator& aComp) + { + index_type index = IndexOfFirstElementGt(aItem, aComp); + if (index > 0 && aComp.Equals(ElementAt(index - 1), aItem)) { + RemoveElementAt(index - 1); + return true; + } + return false; + } + + // A variation on the RemoveElementSorted method defined above. + template<class Item> + bool RemoveElementSorted(const Item& aItem) + { + return RemoveElementSorted(aItem, nsDefaultComparator<elem_type, Item>()); + } + + // This method causes the elements contained in this array and the given + // array to be swapped. + template<class Allocator> + typename Alloc::ResultType SwapElements(nsTArray_Impl<E, Allocator>& aOther) + { + return Alloc::Result(this->template SwapArrayElements<Alloc>( + aOther, sizeof(elem_type), MOZ_ALIGNOF(elem_type))); + } + + // + // Allocation + // + + // This method may increase the capacity of this array object by the + // specified amount. This method may be called in advance of several + // AppendElement operations to minimize heap re-allocations. This method + // will not reduce the number of elements in this array. + // @param aCapacity The desired capacity of this array. + // @return True if the operation succeeded; false if we ran out of memory +protected: + template<typename ActualAlloc = Alloc> + typename ActualAlloc::ResultType SetCapacity(size_type aCapacity) + { + return ActualAlloc::Result(this->template EnsureCapacity<ActualAlloc>( + aCapacity, sizeof(elem_type))); + } +public: + + MOZ_MUST_USE + bool SetCapacity(size_type aCapacity, const mozilla::fallible_t&) + { + return SetCapacity<FallibleAlloc>(aCapacity); + } + + // This method modifies the length of the array. If the new length is + // larger than the existing length of the array, then new elements will be + // constructed using elem_type's default constructor. Otherwise, this call + // removes elements from the array (see also RemoveElementsAt). + // @param aNewLen The desired length of this array. + // @return True if the operation succeeded; false otherwise. + // See also TruncateLength if the new length is guaranteed to be smaller than + // the old. +protected: + template<typename ActualAlloc = Alloc> + typename ActualAlloc::ResultType SetLength(size_type aNewLen) + { + size_type oldLen = Length(); + if (aNewLen > oldLen) { + return ActualAlloc::ConvertBoolToResultType( + InsertElementsAt<ActualAlloc>(oldLen, aNewLen - oldLen) != nullptr); + } + + TruncateLength(aNewLen); + return ActualAlloc::ConvertBoolToResultType(true); + } +public: + + MOZ_MUST_USE + bool SetLength(size_type aNewLen, const mozilla::fallible_t&) + { + return SetLength<FallibleAlloc>(aNewLen); + } + + // This method modifies the length of the array, but may only be + // called when the new length is shorter than the old. It can + // therefore be called when elem_type has no default constructor, + // unlike SetLength. It removes elements from the array (see also + // RemoveElementsAt). + // @param aNewLen The desired length of this array. + void TruncateLength(size_type aNewLen) + { + size_type oldLen = Length(); + MOZ_ASSERT(aNewLen <= oldLen, + "caller should use SetLength instead"); + RemoveElementsAt(aNewLen, oldLen - aNewLen); + } + + // This method ensures that the array has length at least the given + // length. If the current length is shorter than the given length, + // then new elements will be constructed using elem_type's default + // constructor. + // @param aMinLen The desired minimum length of this array. + // @return True if the operation succeeded; false otherwise. +protected: + template<typename ActualAlloc = Alloc> + typename ActualAlloc::ResultType EnsureLengthAtLeast(size_type aMinLen) + { + size_type oldLen = Length(); + if (aMinLen > oldLen) { + return ActualAlloc::ConvertBoolToResultType( + !!InsertElementsAt<ActualAlloc>(oldLen, aMinLen - oldLen)); + } + return ActualAlloc::ConvertBoolToResultType(true); + } +public: + + MOZ_MUST_USE + bool EnsureLengthAtLeast(size_type aMinLen, const mozilla::fallible_t&) + { + return EnsureLengthAtLeast<FallibleAlloc>(aMinLen); + } + + // This method inserts elements into the array, constructing + // them using elem_type's default constructor. + // @param aIndex the place to insert the new elements. This must be no + // greater than the current length of the array. + // @param aCount the number of elements to insert +protected: + template<typename ActualAlloc = Alloc> + elem_type* InsertElementsAt(index_type aIndex, size_type aCount) + { + if (!base_type::template InsertSlotsAt<ActualAlloc>(aIndex, aCount, + sizeof(elem_type), + MOZ_ALIGNOF(elem_type))) { + return nullptr; + } + + // Initialize the extra array elements + elem_type* iter = Elements() + aIndex; + elem_type* iend = iter + aCount; + for (; iter != iend; ++iter) { + elem_traits::Construct(iter); + } + + return Elements() + aIndex; + } +public: + + MOZ_MUST_USE + elem_type* InsertElementsAt(index_type aIndex, size_type aCount, + const mozilla::fallible_t&) + { + return InsertElementsAt<FallibleAlloc>(aIndex, aCount); + } + + // This method inserts elements into the array, constructing them + // elem_type's copy constructor (or whatever one-arg constructor + // happens to match the Item type). + // @param aIndex the place to insert the new elements. This must be no + // greater than the current length of the array. + // @param aCount the number of elements to insert. + // @param aItem the value to use when constructing the new elements. +protected: + template<class Item, typename ActualAlloc = Alloc> + elem_type* InsertElementsAt(index_type aIndex, size_type aCount, + const Item& aItem); + +public: + + template<class Item> + MOZ_MUST_USE + elem_type* InsertElementsAt(index_type aIndex, size_type aCount, + const Item& aItem, const mozilla::fallible_t&) + { + return InsertElementsAt<Item, FallibleAlloc>(aIndex, aCount, aItem); + } + + // This method may be called to minimize the memory used by this array. + void Compact() + { + ShrinkCapacity(sizeof(elem_type), MOZ_ALIGNOF(elem_type)); + } + + // + // Sorting + // + + // This function is meant to be used with the NS_QuickSort function. It + // maps the callback API expected by NS_QuickSort to the Comparator API + // used by nsTArray_Impl. See nsTArray_Impl::Sort. + template<class Comparator> + static int Compare(const void* aE1, const void* aE2, void* aData) + { + const Comparator* c = reinterpret_cast<const Comparator*>(aData); + const elem_type* a = static_cast<const elem_type*>(aE1); + const elem_type* b = static_cast<const elem_type*>(aE2); + return c->LessThan(*a, *b) ? -1 : (c->Equals(*a, *b) ? 0 : 1); + } + + // This method sorts the elements of the array. It uses the LessThan + // method defined on the given Comparator object to collate elements. + // @param aComp The Comparator used to collate elements. + template<class Comparator> + void Sort(const Comparator& aComp) + { + NS_QuickSort(Elements(), Length(), sizeof(elem_type), + Compare<Comparator>, const_cast<Comparator*>(&aComp)); + } + + // A variation on the Sort method defined above that assumes that + // 'operator<' is defined for elem_type. + void Sort() { Sort(nsDefaultComparator<elem_type, elem_type>()); } + +protected: + using base_type::Hdr; + using base_type::ShrinkCapacity; + + // This method invokes elem_type's destructor on a range of elements. + // @param aStart The index of the first element to destroy. + // @param aCount The number of elements to destroy. + void DestructRange(index_type aStart, size_type aCount) + { + elem_type* iter = Elements() + aStart; + elem_type *iend = iter + aCount; + for (; iter != iend; ++iter) { + elem_traits::Destruct(iter); + } + } + + // This method invokes elem_type's copy-constructor on a range of elements. + // @param aStart The index of the first element to construct. + // @param aCount The number of elements to construct. + // @param aValues The array of elements to copy. + template<class Item> + void AssignRange(index_type aStart, size_type aCount, const Item* aValues) + { + AssignRangeAlgorithm<mozilla::IsPod<Item>::value, + mozilla::IsSame<Item, elem_type>::value> + ::implementation(Elements(), aStart, aCount, aValues); + } +}; + +template<typename E, class Alloc> +template<class Item, typename ActualAlloc> +auto +nsTArray_Impl<E, Alloc>::ReplaceElementsAt(index_type aStart, size_type aCount, + const Item* aArray, size_type aArrayLen) -> elem_type* +{ + // Adjust memory allocation up-front to catch errors. + if (!ActualAlloc::Successful(this->template EnsureCapacity<ActualAlloc>( + Length() + aArrayLen - aCount, sizeof(elem_type)))) { + return nullptr; + } + DestructRange(aStart, aCount); + this->template ShiftData<ActualAlloc>(aStart, aCount, aArrayLen, + sizeof(elem_type), + MOZ_ALIGNOF(elem_type)); + AssignRange(aStart, aArrayLen, aArray); + return Elements() + aStart; +} + +template<typename E, class Alloc> +void +nsTArray_Impl<E, Alloc>::RemoveElementsAt(index_type aStart, size_type aCount) +{ + MOZ_ASSERT(aCount == 0 || aStart < Length(), "Invalid aStart index"); + MOZ_ASSERT(aStart + aCount <= Length(), "Invalid length"); + // Check that the previous assert didn't overflow + MOZ_ASSERT(aStart <= aStart + aCount, "Start index plus length overflows"); + DestructRange(aStart, aCount); + this->template ShiftData<InfallibleAlloc>(aStart, aCount, 0, + sizeof(elem_type), + MOZ_ALIGNOF(elem_type)); +} + +template<typename E, class Alloc> +template<typename Predicate> +void +nsTArray_Impl<E, Alloc>::RemoveElementsBy(Predicate aPredicate) +{ + if (base_type::mHdr == EmptyHdr()) { + return; + } + + index_type j = 0; + index_type len = Length(); + for (index_type i = 0; i < len; ++i) { + if (aPredicate(Elements()[i])) { + elem_traits::Destruct(Elements() + i); + } else { + if (j < i) { + copy_type::MoveNonOverlappingRegion(Elements() + j, Elements() + i, + 1, sizeof(elem_type)); + } + ++j; + } + } + base_type::mHdr->mLength = j; +} + +template<typename E, class Alloc> +template<class Item, typename ActualAlloc> +auto +nsTArray_Impl<E, Alloc>::InsertElementsAt(index_type aIndex, size_type aCount, + const Item& aItem) -> elem_type* +{ + if (!base_type::template InsertSlotsAt<ActualAlloc>(aIndex, aCount, + sizeof(elem_type), + MOZ_ALIGNOF(elem_type))) { + return nullptr; + } + + // Initialize the extra array elements + elem_type* iter = Elements() + aIndex; + elem_type* iend = iter + aCount; + for (; iter != iend; ++iter) { + elem_traits::Construct(iter, aItem); + } + + return Elements() + aIndex; +} + +template<typename E, class Alloc> +template<typename ActualAlloc> +auto +nsTArray_Impl<E, Alloc>::InsertElementAt(index_type aIndex) -> elem_type* +{ + if (!ActualAlloc::Successful(this->template EnsureCapacity<ActualAlloc>( + Length() + 1, sizeof(elem_type)))) { + return nullptr; + } + this->template ShiftData<ActualAlloc>(aIndex, 0, 1, sizeof(elem_type), + MOZ_ALIGNOF(elem_type)); + elem_type* elem = Elements() + aIndex; + elem_traits::Construct(elem); + return elem; +} + +template<typename E, class Alloc> +template<class Item, typename ActualAlloc> +auto +nsTArray_Impl<E, Alloc>::InsertElementAt(index_type aIndex, Item&& aItem) -> elem_type* +{ + if (!ActualAlloc::Successful(this->template EnsureCapacity<ActualAlloc>( + Length() + 1, sizeof(elem_type)))) { + return nullptr; + } + this->template ShiftData<ActualAlloc>(aIndex, 0, 1, sizeof(elem_type), + MOZ_ALIGNOF(elem_type)); + elem_type* elem = Elements() + aIndex; + elem_traits::Construct(elem, mozilla::Forward<Item>(aItem)); + return elem; +} + +template<typename E, class Alloc> +template<class Item, typename ActualAlloc> +auto +nsTArray_Impl<E, Alloc>::AppendElements(const Item* aArray, size_type aArrayLen) -> elem_type* +{ + if (!ActualAlloc::Successful(this->template EnsureCapacity<ActualAlloc>( + Length() + aArrayLen, sizeof(elem_type)))) { + return nullptr; + } + index_type len = Length(); + AssignRange(len, aArrayLen, aArray); + this->IncrementLength(aArrayLen); + return Elements() + len; +} + +template<typename E, class Alloc> +template<class Item, class Allocator, typename ActualAlloc> +auto +nsTArray_Impl<E, Alloc>::AppendElements(nsTArray_Impl<Item, Allocator>&& aArray) -> elem_type* +{ + MOZ_ASSERT(&aArray != this, "argument must be different aArray"); + if (Length() == 0) { + SwapElements<ActualAlloc>(aArray); + return Elements(); + } + + index_type len = Length(); + index_type otherLen = aArray.Length(); + if (!Alloc::Successful(this->template EnsureCapacity<Alloc>( + len + otherLen, sizeof(elem_type)))) { + return nullptr; + } + copy_type::MoveNonOverlappingRegion(Elements() + len, aArray.Elements(), otherLen, + sizeof(elem_type)); + this->IncrementLength(otherLen); + aArray.template ShiftData<Alloc>(0, otherLen, 0, sizeof(elem_type), + MOZ_ALIGNOF(elem_type)); + return Elements() + len; +} + +template<typename E, class Alloc> +template<class Item, typename ActualAlloc> +auto +nsTArray_Impl<E, Alloc>::AppendElement(Item&& aItem) -> elem_type* +{ + if (!ActualAlloc::Successful(this->template EnsureCapacity<ActualAlloc>( + Length() + 1, sizeof(elem_type)))) { + return nullptr; + } + elem_type* elem = Elements() + Length(); + elem_traits::Construct(elem, mozilla::Forward<Item>(aItem)); + this->IncrementLength(1); + return elem; +} + +template<typename E, typename Alloc> +inline void +ImplCycleCollectionUnlink(nsTArray_Impl<E, Alloc>& aField) +{ + aField.Clear(); +} + +template<typename E, typename Alloc> +inline void +ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback& aCallback, + nsTArray_Impl<E, Alloc>& aField, + const char* aName, + uint32_t aFlags = 0) +{ + aFlags |= CycleCollectionEdgeNameArrayFlag; + size_t length = aField.Length(); + for (size_t i = 0; i < length; ++i) { + ImplCycleCollectionTraverse(aCallback, aField[i], aName, aFlags); + } +} + +// +// nsTArray is an infallible vector class. See the comment at the top of this +// file for more details. +// +template<class E> +class nsTArray : public nsTArray_Impl<E, nsTArrayInfallibleAllocator> +{ +public: + typedef nsTArray_Impl<E, nsTArrayInfallibleAllocator> base_type; + typedef nsTArray<E> self_type; + typedef typename base_type::size_type size_type; + + nsTArray() {} + explicit nsTArray(size_type aCapacity) : base_type(aCapacity) {} + explicit nsTArray(const nsTArray& aOther) : base_type(aOther) {} + MOZ_IMPLICIT nsTArray(nsTArray&& aOther) : base_type(mozilla::Move(aOther)) {} + MOZ_IMPLICIT nsTArray(std::initializer_list<E> aIL) : base_type(aIL) {} + + template<class Allocator> + explicit nsTArray(const nsTArray_Impl<E, Allocator>& aOther) + : base_type(aOther) + { + } + template<class Allocator> + MOZ_IMPLICIT nsTArray(nsTArray_Impl<E, Allocator>&& aOther) + : base_type(mozilla::Move(aOther)) + { + } + + self_type& operator=(const self_type& aOther) + { + base_type::operator=(aOther); + return *this; + } + template<class Allocator> + self_type& operator=(const nsTArray_Impl<E, Allocator>& aOther) + { + base_type::operator=(aOther); + return *this; + } + self_type& operator=(self_type&& aOther) + { + base_type::operator=(mozilla::Move(aOther)); + return *this; + } + template<class Allocator> + self_type& operator=(nsTArray_Impl<E, Allocator>&& aOther) + { + base_type::operator=(mozilla::Move(aOther)); + return *this; + } + + using base_type::AppendElement; + using base_type::AppendElements; + using base_type::EnsureLengthAtLeast; + using base_type::InsertElementAt; + using base_type::InsertElementsAt; + using base_type::InsertElementSorted; + using base_type::ReplaceElementsAt; + using base_type::SetCapacity; + using base_type::SetLength; +}; + +// +// FallibleTArray is a fallible vector class. +// +template<class E> +class FallibleTArray : public nsTArray_Impl<E, nsTArrayFallibleAllocator> +{ +public: + typedef nsTArray_Impl<E, nsTArrayFallibleAllocator> base_type; + typedef FallibleTArray<E> self_type; + typedef typename base_type::size_type size_type; + + FallibleTArray() {} + explicit FallibleTArray(size_type aCapacity) : base_type(aCapacity) {} + explicit FallibleTArray(const FallibleTArray<E>& aOther) : base_type(aOther) {} + FallibleTArray(FallibleTArray<E>&& aOther) + : base_type(mozilla::Move(aOther)) + { + } + + template<class Allocator> + explicit FallibleTArray(const nsTArray_Impl<E, Allocator>& aOther) + : base_type(aOther) + { + } + template<class Allocator> + explicit FallibleTArray(nsTArray_Impl<E, Allocator>&& aOther) + : base_type(mozilla::Move(aOther)) + { + } + + self_type& operator=(const self_type& aOther) + { + base_type::operator=(aOther); + return *this; + } + template<class Allocator> + self_type& operator=(const nsTArray_Impl<E, Allocator>& aOther) + { + base_type::operator=(aOther); + return *this; + } + self_type& operator=(self_type&& aOther) + { + base_type::operator=(mozilla::Move(aOther)); + return *this; + } + template<class Allocator> + self_type& operator=(nsTArray_Impl<E, Allocator>&& aOther) + { + base_type::operator=(mozilla::Move(aOther)); + return *this; + } +}; + +// +// AutoTArray<E, N> is like nsTArray<E>, but with N elements of inline storage. +// Storing more than N elements is fine, but it will cause a heap allocation. +// +template<class E, size_t N> +class MOZ_NON_MEMMOVABLE AutoTArray : public nsTArray<E> +{ + static_assert(N != 0, "AutoTArray<E, 0> should be specialized"); +public: + typedef AutoTArray<E, N> self_type; + typedef nsTArray<E> base_type; + typedef typename base_type::Header Header; + typedef typename base_type::elem_type elem_type; + + AutoTArray() + { + Init(); + } + + AutoTArray(const self_type& aOther) + { + Init(); + this->AppendElements(aOther); + } + + explicit AutoTArray(const base_type& aOther) + { + Init(); + this->AppendElements(aOther); + } + + explicit AutoTArray(base_type&& aOther) + { + Init(); + this->SwapElements(aOther); + } + + template<typename Allocator> + explicit AutoTArray(nsTArray_Impl<elem_type, Allocator>&& aOther) + { + Init(); + this->SwapElements(aOther); + } + + MOZ_IMPLICIT AutoTArray(std::initializer_list<E> aIL) + { + Init(); + this->AppendElements(aIL.begin(), aIL.size()); + } + + self_type& operator=(const self_type& aOther) + { + base_type::operator=(aOther); + return *this; + } + + template<typename Allocator> + self_type& operator=(const nsTArray_Impl<elem_type, Allocator>& aOther) + { + base_type::operator=(aOther); + return *this; + } + +private: + // nsTArray_base casts itself as an nsAutoArrayBase in order to get a pointer + // to mAutoBuf. + template<class Allocator, class Copier> + friend class nsTArray_base; + + void Init() + { + static_assert(MOZ_ALIGNOF(elem_type) <= 8, + "can't handle alignments greater than 8, " + "see nsTArray_base::UsesAutoArrayBuffer()"); + // Temporary work around for VS2012 RC compiler crash + Header** phdr = base_type::PtrToHdr(); + *phdr = reinterpret_cast<Header*>(&mAutoBuf); + (*phdr)->mLength = 0; + (*phdr)->mCapacity = N; + (*phdr)->mIsAutoArray = 1; + + MOZ_ASSERT(base_type::GetAutoArrayBuffer(MOZ_ALIGNOF(elem_type)) == + reinterpret_cast<Header*>(&mAutoBuf), + "GetAutoArrayBuffer needs to be fixed"); + } + + // Declare mAutoBuf aligned to the maximum of the header's alignment and + // elem_type's alignment. We need to use a union rather than + // MOZ_ALIGNED_DECL because GCC is picky about what goes into + // __attribute__((aligned(foo))). + union + { + char mAutoBuf[sizeof(nsTArrayHeader) + N * sizeof(elem_type)]; + // Do the max operation inline to ensure that it is a compile-time constant. + mozilla::AlignedElem<(MOZ_ALIGNOF(Header) > MOZ_ALIGNOF(elem_type)) ? + MOZ_ALIGNOF(Header) : MOZ_ALIGNOF(elem_type)> mAlign; + }; +}; + +// +// Specialization of AutoTArray<E, N> for the case where N == 0. +// AutoTArray<E, 0> behaves exactly like nsTArray<E>, but without this +// specialization, it stores a useless inline header. +// +// We do have many AutoTArray<E, 0> objects in memory: about 2,000 per tab as +// of May 2014. These are typically not explicitly AutoTArray<E, 0> but rather +// AutoTArray<E, N> for some value N depending on template parameters, in +// generic code. +// +// For that reason, we optimize this case with the below partial specialization, +// which ensures that AutoTArray<E, 0> is just like nsTArray<E>, without any +// inline header overhead. +// +template<class E> +class AutoTArray<E, 0> : public nsTArray<E> +{ +}; + +template<class E, size_t N> +struct nsTArray_CopyChooser<AutoTArray<E, N>> +{ + typedef nsTArray_CopyWithConstructors<AutoTArray<E, N>> Type; +}; + +// Assert that AutoTArray doesn't have any extra padding inside. +// +// It's important that the data stored in this auto array takes up a multiple of +// 8 bytes; e.g. AutoTArray<uint32_t, 1> wouldn't work. Since AutoTArray +// contains a pointer, its size must be a multiple of alignof(void*). (This is +// because any type may be placed into an array, and there's no padding between +// elements of an array.) The compiler pads the end of the structure to +// enforce this rule. +// +// If we used AutoTArray<uint32_t, 1> below, this assertion would fail on a +// 64-bit system, where the compiler inserts 4 bytes of padding at the end of +// the auto array to make its size a multiple of alignof(void*) == 8 bytes. + +static_assert(sizeof(AutoTArray<uint32_t, 2>) == + sizeof(void*) + sizeof(nsTArrayHeader) + sizeof(uint32_t) * 2, + "AutoTArray shouldn't contain any extra padding, " + "see the comment"); + +// Definitions of nsTArray_Impl methods +#include "nsTArray-inl.h" + +#endif // nsTArray_h__ diff --git a/xpcom/glue/nsTArrayForwardDeclare.h b/xpcom/glue/nsTArrayForwardDeclare.h new file mode 100644 index 0000000000..f63ef5f59c --- /dev/null +++ b/xpcom/glue/nsTArrayForwardDeclare.h @@ -0,0 +1,36 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsTArrayForwardDeclare_h__ +#define nsTArrayForwardDeclare_h__ + +// +// This simple header file contains forward declarations for the TArray family +// of classes. +// +// Including this header is preferable to writing +// +// template<class E> class nsTArray; +// +// yourself, since this header gives us flexibility to e.g. change the default +// template parameters. +// + +#include <stddef.h> + +template<class E> +class nsTArray; + +template<class E> +class FallibleTArray; + +template<class E, size_t N> +class AutoTArray; + +template<class E> +using InfallibleTArray = nsTArray<E>; + +#endif diff --git a/xpcom/glue/nsTHashtable.h b/xpcom/glue/nsTHashtable.h new file mode 100644 index 0000000000..705b0294e2 --- /dev/null +++ b/xpcom/glue/nsTHashtable.h @@ -0,0 +1,577 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsTHashtable_h__ +#define nsTHashtable_h__ + +#include "PLDHashTable.h" +#include "nsPointerHashKeys.h" +#include "mozilla/Assertions.h" +#include "mozilla/Attributes.h" +#include "mozilla/fallible.h" +#include "mozilla/MemoryChecking.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/Move.h" +#include "mozilla/OperatorNewExtensions.h" +#include "mozilla/PodOperations.h" +#include "mozilla/TypeTraits.h" + +#include <new> + +/** + * a base class for templated hashtables. + * + * Clients will rarely need to use this class directly. Check the derived + * classes first, to see if they will meet your needs. + * + * @param EntryType the templated entry-type class that is managed by the + * hashtable. <code>EntryType</code> must extend the following declaration, + * and <strong>must not declare any virtual functions or derive from classes + * with virtual functions.</strong> Any vtable pointer would break the + * PLDHashTable code. + *<pre> class EntryType : public PLDHashEntryHdr + * { + * public: or friend nsTHashtable<EntryType>; + * // KeyType is what we use when Get()ing or Put()ing this entry + * // this should either be a simple datatype (uint32_t, nsISupports*) or + * // a const reference (const nsAString&) + * typedef something KeyType; + * // KeyTypePointer is the pointer-version of KeyType, because + * // PLDHashTable.h requires keys to cast to <code>const void*</code> + * typedef const something* KeyTypePointer; + * + * EntryType(KeyTypePointer aKey); + * + * // A copy or C++11 Move constructor must be defined, even if + * // AllowMemMove() == true, otherwise you will cause link errors. + * EntryType(const EntryType& aEnt); // Either this... + * EntryType(EntryType&& aEnt); // ...or this + * + * // the destructor must be defined... or you will cause link errors! + * ~EntryType(); + * + * // KeyEquals(): does this entry match this key? + * bool KeyEquals(KeyTypePointer aKey) const; + * + * // KeyToPointer(): Convert KeyType to KeyTypePointer + * static KeyTypePointer KeyToPointer(KeyType aKey); + * + * // HashKey(): calculate the hash number + * static PLDHashNumber HashKey(KeyTypePointer aKey); + * + * // ALLOW_MEMMOVE can we move this class with memmove(), or do we have + * // to use the copy constructor? + * enum { ALLOW_MEMMOVE = true/false }; + * }</pre> + * + * @see nsInterfaceHashtable + * @see nsDataHashtable + * @see nsClassHashtable + * @author "Benjamin Smedberg <bsmedberg@covad.net>" + */ + +template<class EntryType> +class MOZ_NEEDS_NO_VTABLE_TYPE nsTHashtable +{ + typedef mozilla::fallible_t fallible_t; + static_assert(mozilla::IsPointer<typename EntryType::KeyTypePointer>::value, + "KeyTypePointer should be a pointer"); + +public: + // Separate constructors instead of default aInitLength parameter since + // otherwise the default no-arg constructor isn't found. + nsTHashtable() + : mTable(Ops(), sizeof(EntryType), PLDHashTable::kDefaultInitialLength) + {} + explicit nsTHashtable(uint32_t aInitLength) + : mTable(Ops(), sizeof(EntryType), aInitLength) + {} + + /** + * destructor, cleans up and deallocates + */ + ~nsTHashtable(); + + nsTHashtable(nsTHashtable<EntryType>&& aOther); + + /** + * Return the generation number for the table. This increments whenever + * the table data items are moved. + */ + uint32_t GetGeneration() const { return mTable.Generation(); } + + /** + * KeyType is typedef'ed for ease of use. + */ + typedef typename EntryType::KeyType KeyType; + + /** + * KeyTypePointer is typedef'ed for ease of use. + */ + typedef typename EntryType::KeyTypePointer KeyTypePointer; + + /** + * Return the number of entries in the table. + * @return number of entries + */ + uint32_t Count() const { return mTable.EntryCount(); } + + /** + * Return true if the hashtable is empty. + */ + bool IsEmpty() const { return Count() == 0; } + + /** + * Get the entry associated with a key. + * @param aKey the key to retrieve + * @return pointer to the entry class, if the key exists; nullptr if the + * key doesn't exist + */ + EntryType* GetEntry(KeyType aKey) const + { + return static_cast<EntryType*>( + const_cast<PLDHashTable*>(&mTable)->Search(EntryType::KeyToPointer(aKey))); + } + + /** + * Return true if an entry for the given key exists, false otherwise. + * @param aKey the key to retrieve + * @return true if the key exists, false if the key doesn't exist + */ + bool Contains(KeyType aKey) const { return !!GetEntry(aKey); } + + /** + * Get the entry associated with a key, or create a new entry, + * @param aKey the key to retrieve + * @return pointer to the entry class retreived; nullptr only if memory + can't be allocated + */ + EntryType* PutEntry(KeyType aKey) + { + // infallible add + return static_cast<EntryType*>(mTable.Add(EntryType::KeyToPointer(aKey))); + } + + MOZ_MUST_USE + EntryType* PutEntry(KeyType aKey, const fallible_t&) + { + return static_cast<EntryType*>(mTable.Add(EntryType::KeyToPointer(aKey), + mozilla::fallible)); + } + + /** + * Remove the entry associated with a key. + * @param aKey of the entry to remove + */ + void RemoveEntry(KeyType aKey) + { + mTable.Remove(EntryType::KeyToPointer(aKey)); + } + + /** + * Remove the entry associated with a key. + * @param aEntry the entry-pointer to remove (obtained from GetEntry) + */ + void RemoveEntry(EntryType* aEntry) + { + mTable.RemoveEntry(aEntry); + } + + /** + * Remove the entry associated with a key, but don't resize the hashtable. + * This is a low-level method, and is not recommended unless you know what + * you're doing. If you use it, please add a comment explaining why you + * didn't use RemoveEntry(). + * @param aEntry the entry-pointer to remove (obtained from GetEntry) + */ + void RawRemoveEntry(EntryType* aEntry) + { + mTable.RawRemove(aEntry); + } + + // This is an iterator that also allows entry removal. Example usage: + // + // for (auto iter = table.Iter(); !iter.Done(); iter.Next()) { + // Entry* entry = iter.Get(); + // // ... do stuff with |entry| ... + // // ... possibly call iter.Remove() once ... + // } + // + class Iterator : public PLDHashTable::Iterator + { + public: + typedef PLDHashTable::Iterator Base; + + explicit Iterator(nsTHashtable* aTable) : Base(&aTable->mTable) {} + Iterator(Iterator&& aOther) : Base(aOther.mTable) {} + ~Iterator() {} + + EntryType* Get() const { return static_cast<EntryType*>(Base::Get()); } + + private: + Iterator() = delete; + Iterator(const Iterator&) = delete; + Iterator& operator=(const Iterator&) = delete; + Iterator& operator=(const Iterator&&) = delete; + }; + + Iterator Iter() { return Iterator(this); } + + Iterator ConstIter() const + { + return Iterator(const_cast<nsTHashtable*>(this)); + } + + /** + * Remove all entries, return hashtable to "pristine" state. It's + * conceptually the same as calling the destructor and then re-calling the + * constructor. + */ + void Clear() + { + mTable.Clear(); + } + + /** + * Measure the size of the table's entry storage. Does *not* measure anything + * hanging off table entries; hence the "Shallow" prefix. To measure that, + * either use SizeOfExcludingThis() or iterate manually over the entries, + * calling SizeOfExcludingThis() on each one. + * + * @param aMallocSizeOf the function used to measure heap-allocated blocks + * @return the measured shallow size of the table + */ + size_t ShallowSizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const + { + return mTable.ShallowSizeOfExcludingThis(aMallocSizeOf); + } + + /** + * Like ShallowSizeOfExcludingThis, but includes sizeof(*this). + */ + size_t ShallowSizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const + { + return aMallocSizeOf(this) + ShallowSizeOfExcludingThis(aMallocSizeOf); + } + + /** + * This is a "deep" measurement of the table. To use it, |EntryType| must + * define SizeOfExcludingThis, and that method will be called on all live + * entries. + */ + size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const + { + size_t n = ShallowSizeOfExcludingThis(aMallocSizeOf); + for (auto iter = ConstIter(); !iter.Done(); iter.Next()) { + n += (*iter.Get()).SizeOfExcludingThis(aMallocSizeOf); + } + return n; + } + + /** + * Like SizeOfExcludingThis, but includes sizeof(*this). + */ + size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const + { + return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); + } + + /** + * Swap the elements in this hashtable with the elements in aOther. + */ + void SwapElements(nsTHashtable<EntryType>& aOther) + { + MOZ_ASSERT_IF(this->mTable.Ops() && aOther.mTable.Ops(), + this->mTable.Ops() == aOther.mTable.Ops()); + mozilla::Swap(this->mTable, aOther.mTable); + } + +#ifdef DEBUG + /** + * Mark the table as constant after initialization. + * + * This will prevent assertions when a read-only hash is accessed on multiple + * threads without synchronization. + */ + void MarkImmutable() + { + mTable.MarkImmutable(); + } +#endif + +protected: + PLDHashTable mTable; + + static PLDHashNumber s_HashKey(const void* aKey); + + static bool s_MatchEntry(const PLDHashEntryHdr* aEntry, + const void* aKey); + + static void s_CopyEntry(PLDHashTable* aTable, const PLDHashEntryHdr* aFrom, + PLDHashEntryHdr* aTo); + + static void s_ClearEntry(PLDHashTable* aTable, PLDHashEntryHdr* aEntry); + + static void s_InitEntry(PLDHashEntryHdr* aEntry, const void* aKey); + +private: + // copy constructor, not implemented + nsTHashtable(nsTHashtable<EntryType>& aToCopy) = delete; + + /** + * Gets the table's ops. + */ + static const PLDHashTableOps* Ops(); + + // assignment operator, not implemented + nsTHashtable<EntryType>& operator=(nsTHashtable<EntryType>& aToEqual) = delete; +}; + +// +// template definitions +// + +template<class EntryType> +nsTHashtable<EntryType>::nsTHashtable(nsTHashtable<EntryType>&& aOther) + : mTable(mozilla::Move(aOther.mTable)) +{ + // aOther shouldn't touch mTable after this, because we've stolen the table's + // pointers but not overwitten them. + MOZ_MAKE_MEM_UNDEFINED(&aOther.mTable, sizeof(aOther.mTable)); +} + +template<class EntryType> +nsTHashtable<EntryType>::~nsTHashtable() +{ +} + +template<class EntryType> +/* static */ const PLDHashTableOps* +nsTHashtable<EntryType>::Ops() +{ + // If this variable is a global variable, we get strange start-up failures on + // WindowsCrtPatch.h (see bug 1166598 comment 20). But putting it inside a + // function avoids that problem. + static const PLDHashTableOps sOps = + { + s_HashKey, + s_MatchEntry, + EntryType::ALLOW_MEMMOVE ? PLDHashTable::MoveEntryStub : s_CopyEntry, + s_ClearEntry, + s_InitEntry + }; + return &sOps; +} + +// static definitions + +template<class EntryType> +PLDHashNumber +nsTHashtable<EntryType>::s_HashKey(const void* aKey) +{ + return EntryType::HashKey(static_cast<const KeyTypePointer>(aKey)); +} + +template<class EntryType> +bool +nsTHashtable<EntryType>::s_MatchEntry(const PLDHashEntryHdr* aEntry, + const void* aKey) +{ + return ((const EntryType*)aEntry)->KeyEquals( + static_cast<const KeyTypePointer>(aKey)); +} + +template<class EntryType> +void +nsTHashtable<EntryType>::s_CopyEntry(PLDHashTable* aTable, + const PLDHashEntryHdr* aFrom, + PLDHashEntryHdr* aTo) +{ + EntryType* fromEntry = + const_cast<EntryType*>(static_cast<const EntryType*>(aFrom)); + + new (mozilla::KnownNotNull, aTo) EntryType(mozilla::Move(*fromEntry)); + + fromEntry->~EntryType(); +} + +template<class EntryType> +void +nsTHashtable<EntryType>::s_ClearEntry(PLDHashTable* aTable, + PLDHashEntryHdr* aEntry) +{ + static_cast<EntryType*>(aEntry)->~EntryType(); +} + +template<class EntryType> +void +nsTHashtable<EntryType>::s_InitEntry(PLDHashEntryHdr* aEntry, + const void* aKey) +{ + new (mozilla::KnownNotNull, aEntry) EntryType(static_cast<KeyTypePointer>(aKey)); +} + +class nsCycleCollectionTraversalCallback; + +template<class EntryType> +inline void +ImplCycleCollectionUnlink(nsTHashtable<EntryType>& aField) +{ + aField.Clear(); +} + +template<class EntryType> +inline void +ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback& aCallback, + nsTHashtable<EntryType>& aField, + const char* aName, + uint32_t aFlags = 0) +{ + for (auto iter = aField.Iter(); !iter.Done(); iter.Next()) { + EntryType* entry = iter.Get(); + ImplCycleCollectionTraverse(aCallback, *entry, aName, aFlags); + } +} + +/** + * For nsTHashtable with pointer entries, we can have a template specialization + * that layers a typed T* interface on top of a common implementation that + * works internally with void pointers. This arrangement saves code size and + * might slightly improve performance as well. + */ + +/** + * We need a separate entry type class for the inheritance structure of the + * nsTHashtable specialization below; nsVoidPtrHashKey is simply typedefed to a + * specialization of nsPtrHashKey, and the formulation: + * + * class nsTHashtable<nsPtrHashKey<T>> : protected nsTHashtable<nsPtrHashKey<const void> + * + * is not going to turn out very well, since we'd wind up with an nsTHashtable + * instantiation that is its own base class. + */ +namespace detail { + +class VoidPtrHashKey : public nsPtrHashKey<const void> +{ + typedef nsPtrHashKey<const void> Base; + +public: + explicit VoidPtrHashKey(const void* aKey) : Base(aKey) {} +}; + +} // namespace detail + +/** + * See the main nsTHashtable documentation for descriptions of this class's + * methods. + */ +template<typename T> +class nsTHashtable<nsPtrHashKey<T>> : protected nsTHashtable<::detail::VoidPtrHashKey> +{ + typedef nsTHashtable<::detail::VoidPtrHashKey> Base; + typedef nsPtrHashKey<T> EntryType; + + // We play games with reinterpret_cast'ing between these two classes, so + // try to ensure that playing said games is reasonable. + static_assert(sizeof(nsPtrHashKey<T>) == sizeof(::detail::VoidPtrHashKey), + "hash keys must be the same size"); + + nsTHashtable(const nsTHashtable& aOther) = delete; + nsTHashtable& operator=(const nsTHashtable& aOther) = delete; + +public: + nsTHashtable() = default; + explicit nsTHashtable(uint32_t aInitLength) + : Base(aInitLength) + {} + + ~nsTHashtable() = default; + + nsTHashtable(nsTHashtable&&) = default; + + /* Wrapper functions */ + using Base::GetGeneration; + using Base::Count; + using Base::IsEmpty; + using Base::Clear; + + using Base::ShallowSizeOfExcludingThis; + using Base::ShallowSizeOfIncludingThis; + +#ifdef DEBUG + using Base::MarkImmutable; +#endif + + EntryType* GetEntry(T* aKey) const + { + return reinterpret_cast<EntryType*>(Base::GetEntry(aKey)); + } + + bool Contains(T* aKey) const + { + return Base::Contains(aKey); + } + + EntryType* PutEntry(T* aKey) + { + return reinterpret_cast<EntryType*>(Base::PutEntry(aKey)); + } + + MOZ_MUST_USE + EntryType* PutEntry(T* aKey, const mozilla::fallible_t&) + { + return reinterpret_cast<EntryType*>( + Base::PutEntry(aKey, mozilla::fallible)); + } + + void RemoveEntry(T* aKey) + { + Base::RemoveEntry(aKey); + } + + void RemoveEntry(EntryType* aEntry) + { + Base::RemoveEntry(reinterpret_cast<::detail::VoidPtrHashKey*>(aEntry)); + } + + void RawRemoveEntry(EntryType* aEntry) + { + Base::RawRemoveEntry(reinterpret_cast<::detail::VoidPtrHashKey*>(aEntry)); + } + + class Iterator : public Base::Iterator + { + public: + typedef nsTHashtable::Base::Iterator Base; + + explicit Iterator(nsTHashtable* aTable) : Base(aTable) {} + Iterator(Iterator&& aOther) : Base(mozilla::Move(aOther)) {} + ~Iterator() = default; + + EntryType* Get() const { return reinterpret_cast<EntryType*>(Base::Get()); } + + private: + Iterator() = delete; + Iterator(const Iterator&) = delete; + Iterator& operator=(const Iterator&) = delete; + Iterator& operator=(Iterator&&) = delete; + }; + + Iterator Iter() { return Iterator(this); } + + Iterator ConstIter() const + { + return Iterator(const_cast<nsTHashtable*>(this)); + } + + void SwapElements(nsTHashtable& aOther) + { + Base::SwapElements(aOther); + } +}; + +#endif // nsTHashtable_h__ diff --git a/xpcom/glue/nsTObserverArray.cpp b/xpcom/glue/nsTObserverArray.cpp new file mode 100644 index 0000000000..dd68631f49 --- /dev/null +++ b/xpcom/glue/nsTObserverArray.cpp @@ -0,0 +1,31 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsTObserverArray.h" + +void +nsTObserverArray_base::AdjustIterators(index_type aModPos, + diff_type aAdjustment) +{ + NS_PRECONDITION(aAdjustment == -1 || aAdjustment == 1, "invalid adjustment"); + Iterator_base* iter = mIterators; + while (iter) { + if (iter->mPosition > aModPos) { + iter->mPosition += aAdjustment; + } + iter = iter->mNext; + } +} + +void +nsTObserverArray_base::ClearIterators() +{ + Iterator_base* iter = mIterators; + while (iter) { + iter->mPosition = 0; + iter = iter->mNext; + } +} diff --git a/xpcom/glue/nsTObserverArray.h b/xpcom/glue/nsTObserverArray.h new file mode 100644 index 0000000000..e9966d0807 --- /dev/null +++ b/xpcom/glue/nsTObserverArray.h @@ -0,0 +1,520 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsTObserverArray_h___ +#define nsTObserverArray_h___ + +#include "mozilla/MemoryReporting.h" +#include "nsTArray.h" +#include "nsCycleCollectionNoteChild.h" + +/** + * An array of observers. Like a normal array, but supports iterators that are + * stable even if the array is modified during iteration. + * The template parameter T is the observer type the array will hold; + * N is the number of built-in storage slots that come with the array. + * NOTE: You probably want to use nsTObserverArray, unless you specifically + * want built-in storage. See below. + * @see nsTObserverArray, nsTArray + */ + +class nsTObserverArray_base +{ +public: + typedef size_t index_type; + typedef size_t size_type; + typedef ptrdiff_t diff_type; + +protected: + class Iterator_base + { + protected: + friend class nsTObserverArray_base; + + Iterator_base(index_type aPosition, Iterator_base* aNext) + : mPosition(aPosition) + , mNext(aNext) + { + } + + // The current position of the iterator. Its exact meaning differs + // depending on iterator. See nsTObserverArray<T>::ForwardIterator. + index_type mPosition; + + // The next iterator currently iterating the same array + Iterator_base* mNext; + }; + + nsTObserverArray_base() : mIterators(nullptr) {} + + ~nsTObserverArray_base() + { + NS_ASSERTION(mIterators == nullptr, "iterators outlasting array"); + } + + /** + * Adjusts iterators after an element has been inserted or removed + * from the array. + * @param aModPos Position where elements were added or removed. + * @param aAdjustment -1 if an element was removed, 1 if an element was + * added. + */ + void AdjustIterators(index_type aModPos, diff_type aAdjustment); + + /** + * Clears iterators when the array is destroyed. + */ + void ClearIterators(); + + mutable Iterator_base* mIterators; +}; + +template<class T, size_t N> +class nsAutoTObserverArray : protected nsTObserverArray_base +{ +public: + typedef T elem_type; + typedef nsTArray<T> array_type; + + nsAutoTObserverArray() {} + + // + // Accessor methods + // + + // @return The number of elements in the array. + size_type Length() const { return mArray.Length(); } + + // @return True if the array is empty or false otherwise. + bool IsEmpty() const { return mArray.IsEmpty(); } + + // This method provides direct access to an element of the array. The given + // index must be within the array bounds. If the underlying array may change + // during iteration, use an iterator instead of this function. + // @param aIndex The index of an element in the array. + // @return A reference to the i'th element of the array. + elem_type& ElementAt(index_type aIndex) + { + return mArray.ElementAt(aIndex); + } + + // Same as above, but readonly. + const elem_type& ElementAt(index_type aIndex) const + { + return mArray.ElementAt(aIndex); + } + + // This method provides direct access to an element of the array in a bounds + // safe manner. If the requested index is out of bounds the provided default + // value is returned. + // @param aIndex The index of an element in the array. + // @param aDef The value to return if the index is out of bounds. + elem_type& SafeElementAt(index_type aIndex, elem_type& aDef) + { + return mArray.SafeElementAt(aIndex, aDef); + } + + // Same as above, but readonly. + const elem_type& SafeElementAt(index_type aIndex, const elem_type& aDef) const + { + return mArray.SafeElementAt(aIndex, aDef); + } + + // No operator[] is provided because the point of this class is to support + // allow modifying the array during iteration, and ElementAt() is not safe + // in those conditions. + + // + // Search methods + // + + // This method searches, starting from the beginning of the array, + // for the first element in this array that is equal to the given element. + // 'operator==' must be defined for elem_type. + // @param aItem The item to search for. + // @return true if the element was found. + template<class Item> + bool Contains(const Item& aItem) const + { + return IndexOf(aItem) != array_type::NoIndex; + } + + // This method searches for the offset of the first element in this + // array that is equal to the given element. + // 'operator==' must be defined for elem_type. + // @param aItem The item to search for. + // @param aStart The index to start from. + // @return The index of the found element or NoIndex if not found. + template<class Item> + index_type IndexOf(const Item& aItem, index_type aStart = 0) const + { + return mArray.IndexOf(aItem, aStart); + } + + // + // Mutation methods + // + + // Insert a given element at the given index. + // @param aIndex The index at which to insert item. + // @param aItem The item to insert, + // @return A pointer to the newly inserted element, or a null on DOM + template<class Item> + elem_type* InsertElementAt(index_type aIndex, const Item& aItem) + { + elem_type* item = mArray.InsertElementAt(aIndex, aItem); + AdjustIterators(aIndex, 1); + return item; + } + + // Same as above but without copy constructing. + // This is useful to avoid temporaries. + elem_type* InsertElementAt(index_type aIndex) + { + elem_type* item = mArray.InsertElementAt(aIndex); + AdjustIterators(aIndex, 1); + return item; + } + + // Prepend an element to the array unless it already exists in the array. + // 'operator==' must be defined for elem_type. + // @param aItem The item to prepend. + // @return true if the element was found, or inserted successfully. + template<class Item> + bool PrependElementUnlessExists(const Item& aItem) + { + if (Contains(aItem)) { + return true; + } + + bool inserted = mArray.InsertElementAt(0, aItem) != nullptr; + AdjustIterators(0, 1); + return inserted; + } + + // Append an element to the array. + // @param aItem The item to append. + // @return A pointer to the newly appended element, or null on OOM. + template<class Item> + elem_type* AppendElement(const Item& aItem) + { + return mArray.AppendElement(aItem); + } + + // Same as above, but without copy-constructing. This is useful to avoid + // temporaries. + elem_type* AppendElement() + { + return mArray.AppendElement(); + } + + // Append an element to the array unless it already exists in the array. + // 'operator==' must be defined for elem_type. + // @param aItem The item to append. + // @return true if the element was found, or inserted successfully. + template<class Item> + bool AppendElementUnlessExists(const Item& aItem) + { + return Contains(aItem) || AppendElement(aItem) != nullptr; + } + + // Remove an element from the array. + // @param aIndex The index of the item to remove. + void RemoveElementAt(index_type aIndex) + { + NS_ASSERTION(aIndex < mArray.Length(), "invalid index"); + mArray.RemoveElementAt(aIndex); + AdjustIterators(aIndex, -1); + } + + // This helper function combines IndexOf with RemoveElementAt to "search + // and destroy" the first element that is equal to the given element. + // 'operator==' must be defined for elem_type. + // @param aItem The item to search for. + // @return true if the element was found and removed. + template<class Item> + bool RemoveElement(const Item& aItem) + { + index_type index = mArray.IndexOf(aItem, 0); + if (index == array_type::NoIndex) { + return false; + } + + mArray.RemoveElementAt(index); + AdjustIterators(index, -1); + return true; + } + + // See nsTArray::RemoveElementsBy. + void RemoveElementsBy(mozilla::function<bool(const elem_type&)> aPredicate) + { + index_type i = 0; + mArray.RemoveElementsBy([&](const elem_type& aItem) { + if (aPredicate(aItem)) { + // This element is going to be removed. + AdjustIterators(i, -1); + return true; + } + ++i; + return false; + }); + } + + // Removes all observers and collapses all iterators to the beginning of + // the array. The result is that forward iterators will see all elements + // in the array. + void Clear() + { + mArray.Clear(); + ClearIterators(); + } + + // Compact the array to minimize the memory it uses + void Compact() { mArray.Compact(); } + + // Returns the number of bytes on the heap taken up by this object, not + // including sizeof(*this). If you want to measure anything hanging off the + // array, you must iterate over the elements and measure them individually; + // hence the "Shallow" prefix. + size_t ShallowSizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const + { + return mArray.ShallowSizeOfExcludingThis(aMallocSizeOf); + } + + // + // Iterators + // + + // Base class for iterators. Do not use this directly. + class Iterator : public Iterator_base + { + protected: + friend class nsAutoTObserverArray; + typedef nsAutoTObserverArray<T, N> array_type; + + Iterator(index_type aPosition, const array_type& aArray) + : Iterator_base(aPosition, aArray.mIterators) + , mArray(const_cast<array_type&>(aArray)) + { + aArray.mIterators = this; + } + + ~Iterator() + { + NS_ASSERTION(mArray.mIterators == this, + "Iterators must currently be destroyed in opposite order " + "from the construction order. It is suggested that you " + "simply put them on the stack"); + mArray.mIterators = mNext; + } + + // The array we're iterating + array_type& mArray; + }; + + // Iterates the array forward from beginning to end. mPosition points + // to the element that will be returned on next call to GetNext. + // Elements: + // - prepended to the array during iteration *will not* be traversed + // - appended during iteration *will* be traversed + // - removed during iteration *will not* be traversed. + // @see EndLimitedIterator + class ForwardIterator : protected Iterator + { + public: + typedef nsAutoTObserverArray<T, N> array_type; + typedef Iterator base_type; + + explicit ForwardIterator(const array_type& aArray) + : Iterator(0, aArray) + { + } + + ForwardIterator(const array_type& aArray, index_type aPos) + : Iterator(aPos, aArray) + { + } + + bool operator<(const ForwardIterator& aOther) const + { + NS_ASSERTION(&this->mArray == &aOther.mArray, + "not iterating the same array"); + return base_type::mPosition < aOther.mPosition; + } + + // Returns true if there are more elements to iterate. + // This must precede a call to GetNext(). If false is + // returned, GetNext() must not be called. + bool HasMore() const + { + return base_type::mPosition < base_type::mArray.Length(); + } + + // Returns the next element and steps one step. This must + // be preceded by a call to HasMore(). + // @return The next observer. + elem_type& GetNext() + { + NS_ASSERTION(HasMore(), "iterating beyond end of array"); + return base_type::mArray.ElementAt(base_type::mPosition++); + } + }; + + // EndLimitedIterator works like ForwardIterator, but will not iterate new + // observers appended to the array after the iterator was created. + class EndLimitedIterator : protected ForwardIterator + { + public: + typedef nsAutoTObserverArray<T, N> array_type; + typedef Iterator base_type; + + explicit EndLimitedIterator(const array_type& aArray) + : ForwardIterator(aArray) + , mEnd(aArray, aArray.Length()) + { + } + + // Returns true if there are more elements to iterate. + // This must precede a call to GetNext(). If false is + // returned, GetNext() must not be called. + bool HasMore() const { return *this < mEnd; } + + // Returns the next element and steps one step. This must + // be preceded by a call to HasMore(). + // @return The next observer. + elem_type& GetNext() + { + NS_ASSERTION(HasMore(), "iterating beyond end of array"); + return base_type::mArray.ElementAt(base_type::mPosition++); + } + + private: + ForwardIterator mEnd; + }; + + // Iterates the array backward from end to start. mPosition points + // to the element that was returned last. + // Elements: + // - prepended to the array during iteration *will* be traversed, + // unless the iteration already arrived at the first element + // - appended during iteration *will not* be traversed + // - removed during iteration *will not* be traversed. + class BackwardIterator : protected Iterator + { + public: + typedef nsAutoTObserverArray<T, N> array_type; + typedef Iterator base_type; + + explicit BackwardIterator(const array_type& aArray) + : Iterator(aArray.Length(), aArray) + { + } + + // Returns true if there are more elements to iterate. + // This must precede a call to GetNext(). If false is + // returned, GetNext() must not be called. + bool HasMore() const { return base_type::mPosition > 0; } + + // Returns the next element and steps one step. This must + // be preceded by a call to HasMore(). + // @return The next observer. + elem_type& GetNext() + { + NS_ASSERTION(HasMore(), "iterating beyond start of array"); + return base_type::mArray.ElementAt(--base_type::mPosition); + } + + // Removes the element at the current iterator position. + // (the last element returned from |GetNext()|) + // This will not affect the next call to |GetNext()| + void Remove() + { + return base_type::mArray.RemoveElementAt(base_type::mPosition); + } + }; + +protected: + AutoTArray<T, N> mArray; +}; + +template<class T> +class nsTObserverArray : public nsAutoTObserverArray<T, 0> +{ +public: + typedef nsAutoTObserverArray<T, 0> base_type; + typedef nsTObserverArray_base::size_type size_type; + + // + // Initialization methods + // + + nsTObserverArray() {} + + // Initialize this array and pre-allocate some number of elements. + explicit nsTObserverArray(size_type aCapacity) + { + base_type::mArray.SetCapacity(aCapacity); + } +}; + +template<typename T, size_t N> +inline void +ImplCycleCollectionUnlink(nsAutoTObserverArray<T, N>& aField) +{ + aField.Clear(); +} + +template<typename T, size_t N> +inline void +ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback& aCallback, + nsAutoTObserverArray<T, N>& aField, + const char* aName, + uint32_t aFlags = 0) +{ + aFlags |= CycleCollectionEdgeNameArrayFlag; + size_t length = aField.Length(); + for (size_t i = 0; i < length; ++i) { + ImplCycleCollectionTraverse(aCallback, aField.ElementAt(i), aName, aFlags); + } +} + +// XXXbz I wish I didn't have to pass in the observer type, but I +// don't see a way to get it out of array_. +// Note that this macro only works if the array holds pointers to XPCOM objects. +#define NS_OBSERVER_ARRAY_NOTIFY_XPCOM_OBSERVERS(array_, obstype_, func_, params_) \ + PR_BEGIN_MACRO \ + nsTObserverArray<obstype_ *>::ForwardIterator iter_(array_); \ + RefPtr<obstype_> obs_; \ + while (iter_.HasMore()) { \ + obs_ = iter_.GetNext(); \ + obs_ -> func_ params_ ; \ + } \ + PR_END_MACRO + +// Note that this macro only works if the array holds pointers to XPCOM objects. +#define NS_OBSERVER_ARRAY_NOTIFY_OBSERVERS(array_, obstype_, func_, params_) \ + PR_BEGIN_MACRO \ + nsTObserverArray<obstype_ *>::ForwardIterator iter_(array_); \ + obstype_* obs_; \ + while (iter_.HasMore()) { \ + obs_ = iter_.GetNext(); \ + obs_ -> func_ params_ ; \ + } \ + PR_END_MACRO + +#define NS_OBSERVER_ARRAY_NOTIFY_OBSERVERS_WITH_QI(array_, basetype_, obstype_, func_, params_) \ + PR_BEGIN_MACRO \ + nsTObserverArray<basetype_ *>::ForwardIterator iter_(array_); \ + basetype_* obsbase_; \ + while (iter_.HasMore()) { \ + obsbase_ = iter_.GetNext(); \ + nsCOMPtr<obstype_> obs_ = do_QueryInterface(obsbase_); \ + if (obs_) { \ + obs_ -> func_ params_ ; \ + } \ + } \ + PR_END_MACRO +#endif // nsTObserverArray_h___ diff --git a/xpcom/glue/nsTPriorityQueue.h b/xpcom/glue/nsTPriorityQueue.h new file mode 100644 index 0000000000..20a0fc8a55 --- /dev/null +++ b/xpcom/glue/nsTPriorityQueue.h @@ -0,0 +1,161 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef NS_TPRIORITY_QUEUE_H_ +#define NS_TPRIORITY_QUEUE_H_ + +#include "nsTArray.h" +#include "nsDebug.h" + +/** + * A templatized priority queue data structure that uses an nsTArray to serve as + * a binary heap. The default comparator causes this to act like a min-heap. + * Only the LessThan method of the comparator is used. + */ +template<class T, class Compare = nsDefaultComparator<T, T>> +class nsTPriorityQueue +{ +public: + typedef typename nsTArray<T>::size_type size_type; + + /** + * Default constructor also creates a comparator object using the default + * constructor for type Compare. + */ + nsTPriorityQueue() : mCompare(Compare()) {} + + /** + * Constructor to allow a specific instance of a comparator object to be + * used. + */ + explicit nsTPriorityQueue(const Compare& aComp) : mCompare(aComp) {} + + /** + * Copy constructor + */ + nsTPriorityQueue(const nsTPriorityQueue& aOther) + : mElements(aOther.mElements) + , mCompare(aOther.mCompare) + { + } + + /** + * @return True if the queue is empty or false otherwise. + */ + bool IsEmpty() const { return mElements.IsEmpty(); } + + /** + * @return The number of elements in the queue. + */ + size_type Length() const { return mElements.Length(); } + + /** + * @return The topmost element in the queue without changing the queue. This + * is the element 'a' such that there is no other element 'b' in the queue for + * which Compare(b, a) returns true. (Since this container does not check + * for duplicate entries there may exist 'b' for which Compare(a, b) returns + * false.) + */ + const T& Top() const + { + MOZ_ASSERT(!mElements.IsEmpty(), "Empty queue"); + return mElements[0]; + } + + /** + * Adds an element to the queue + * @param aElement The element to add + * @return true on success, false on out of memory. + */ + bool Push(const T& aElement) + { + T* elem = mElements.AppendElement(aElement); + if (!elem) { + return false; // Out of memory + } + + // Sift up + size_type i = mElements.Length() - 1; + while (i) { + size_type parent = (size_type)((i - 1) / 2); + if (mCompare.LessThan(mElements[parent], mElements[i])) { + break; + } + Swap(i, parent); + i = parent; + } + + return true; + } + + /** + * Removes and returns the top-most element from the queue. + * @return The topmost element, that is, the element 'a' such that there is no + * other element 'b' in the queue for which Compare(b, a) returns true. + * @see Top() + */ + T Pop() + { + MOZ_ASSERT(!mElements.IsEmpty(), "Empty queue"); + T pop = mElements[0]; + + // Move last to front + mElements[0] = mElements[mElements.Length() - 1]; + mElements.TruncateLength(mElements.Length() - 1); + + // Sift down + size_type i = 0; + while (2 * i + 1 < mElements.Length()) { + size_type swap = i; + size_type l_child = 2 * i + 1; + if (mCompare.LessThan(mElements[l_child], mElements[i])) { + swap = l_child; + } + size_type r_child = l_child + 1; + if (r_child < mElements.Length() && + mCompare.LessThan(mElements[r_child], mElements[swap])) { + swap = r_child; + } + if (swap == i) { + break; + } + Swap(i, swap); + i = swap; + } + + return pop; + } + + /** + * Removes all elements from the queue. + */ + void Clear() { mElements.Clear(); } + + /** + * Provides readonly access to the queue elements as an array. Generally this + * should be avoided but may be needed in some situations such as when the + * elements contained in the queue need to be enumerated for cycle-collection. + * @return A pointer to the first element of the array. If the array is + * empty, then this pointer must not be dereferenced. + */ + const T* Elements() const { return mElements.Elements(); } + +protected: + /** + * Swaps the elements at the specified indices. + */ + void Swap(size_type aIndexA, size_type aIndexB) + { + T temp = mElements[aIndexA]; + mElements[aIndexA] = mElements[aIndexB]; + mElements[aIndexB] = temp; + } + + nsTArray<T> mElements; + Compare mCompare; // Comparator object +}; + +#endif // NS_TPRIORITY_QUEUE_H_ diff --git a/xpcom/glue/nsTWeakRef.h b/xpcom/glue/nsTWeakRef.h new file mode 100644 index 0000000000..6c9a5e8ebf --- /dev/null +++ b/xpcom/glue/nsTWeakRef.h @@ -0,0 +1,176 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsTWeakRef_h__ +#define nsTWeakRef_h__ + +#ifndef nsDebug_h___ +#include "nsDebug.h" +#endif + +/** + * A weak reference class for use with generic C++ objects. NOT THREADSAFE! + * + * Example usage: + * + * class A { + * public: + * A() : mWeakSelf(this) { + * } + * ~A() { + * mWeakSelf.forget(); + * } + * void Bar() { printf("Bar!\n"); } + * const nsTWeakRef<A> &AsWeakRef() const { return mWeakSelf; } + * private: + * nsTWeakRef<A> mWeakSelf; + * }; + * + * class B { + * public: + * void SetA(const nsTWeakRef<A> &a) { + * mA = a; + * } + * void Foo() { + * if (mA) + * mA->Bar(); + * } + * private: + * nsTWeakRef<A> mA; + * }; + * + * void Test() { + * B b; + * { + * A a; + * b.SetA(a.AsWeakRef()); + * b.Foo(); // prints "Bar!" + * } + * b.Foo(); // prints nothing because |a| has already been destroyed + * } + * + * One can imagine much more complex examples, especially when asynchronous + * event processing is involved. + * + * Keep in mind that you should only ever need a class like this when you have + * multiple instances of B, such that it is not possible for A and B to simply + * have pointers to one another. + */ +template<class Type> +class nsTWeakRef +{ +public: + ~nsTWeakRef() + {} + + /** + * Construct from an object pointer (may be null). + */ + explicit nsTWeakRef(Type* aObj = nullptr) + { + if (aObj) { + mRef = new Inner(aObj); + } else { + mRef = nullptr; + } + } + + /** + * Construct from another weak reference object. + */ + explicit nsTWeakRef(const nsTWeakRef<Type>& aOther) : mRef(aOther.mRef) + {} + + /** + * Assign from an object pointer. + */ + nsTWeakRef<Type>& operator=(Type* aObj) + { + if (aObj) { + mRef = new Inner(aObj); + } else { + mRef = nullptr; + } + return *this; + } + + /** + * Assign from another weak reference object. + */ + nsTWeakRef<Type>& operator=(const nsTWeakRef<Type>& aOther) + { + mRef = aOther.mRef; + return *this; + } + + /** + * Get the referenced object. This method may return null if the reference + * has been cleared or if an out-of-memory error occurred at assignment. + */ + Type* get() const { return mRef ? mRef->mObj : nullptr; } + + /** + * Called to "null out" the weak reference. Typically, the object referenced + * by this weak reference calls this method when it is being destroyed. + * @returns The former referenced object. + */ + Type* forget() + { + Type* obj; + if (mRef) { + obj = mRef->mObj; + mRef->mObj = nullptr; + mRef = nullptr; + } else { + obj = nullptr; + } + return obj; + } + + /** + * Allow |*this| to be treated as a |Type*| for convenience. + */ + operator Type*() const { return get(); } + + /** + * Allow |*this| to be treated as a |Type*| for convenience. Use with + * caution since this method will crash if the referenced object is null. + */ + Type* operator->() const MOZ_NO_ADDREF_RELEASE_ON_RETURN + { + NS_ASSERTION(mRef && mRef->mObj, + "You can't dereference a null weak reference with operator->()."); + return get(); + } + +private: + + struct Inner + { + int mCnt; + Type* mObj; + + explicit Inner(Type* aObj) + : mCnt(1) + , mObj(aObj) + { + } + void AddRef() + { + ++mCnt; + } + void Release() + { + if (--mCnt == 0) { + delete this; + } + } + }; + + RefPtr<Inner> mRef; +}; + +#endif // nsTWeakRef_h__ diff --git a/xpcom/glue/nsTextFormatter.cpp b/xpcom/glue/nsTextFormatter.cpp new file mode 100644 index 0000000000..ab9941d156 --- /dev/null +++ b/xpcom/glue/nsTextFormatter.cpp @@ -0,0 +1,1394 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * Portable safe sprintf code. + * + * Code based on mozilla/nsprpub/src/io/prprf.c rev 3.7 + * + * Contributor(s): + * Kipp E.B. Hickman <kipp@netscape.com> (original author) + * Frank Yung-Fong Tang <ftang@netscape.com> + * Daniele Nicolodi <daniele@grinta.net> + */ + +/* + * Copied from xpcom/ds/nsTextFormatter.cpp r1.22 + * Changed to use nsMemory and Frozen linkage + * -- Prasad <prasad@medhas.org> + */ + +#include <stdarg.h> +#include <stddef.h> +#include <stdio.h> +#include <string.h> +#include "prdtoa.h" +#include "mozilla/Logging.h" +#include "mozilla/Sprintf.h" +#include "prmem.h" +#include "nsCRTGlue.h" +#include "nsTextFormatter.h" +#include "nsMemory.h" + +/* +** Note: on some platforms va_list is defined as an array, +** and requires array notation. +*/ + +#ifdef HAVE_VA_COPY +#define VARARGS_ASSIGN(foo, bar) VA_COPY(foo,bar) +#elif defined(HAVE_VA_LIST_AS_ARRAY) +#define VARARGS_ASSIGN(foo, bar) foo[0] = bar[0] +#else +#define VARARGS_ASSIGN(foo, bar) (foo) = (bar) +#endif + +typedef struct SprintfStateStr SprintfState; + +struct SprintfStateStr +{ + int (*stuff)(SprintfState* aState, const char16_t* aStr, uint32_t aLen); + + char16_t* base; + char16_t* cur; + uint32_t maxlen; + + void* stuffclosure; +}; + +/* +** Numbered Arguement State +*/ +struct NumArgState +{ + int type; /* type of the current ap */ + va_list ap; /* point to the corresponding position on ap */ + + enum Type + { + INT16, + UINT16, + INTN, + UINTN, + INT32, + UINT32, + INT64, + UINT64, + STRING, + DOUBLE, + INTSTR, + UNISTRING, + UNKNOWN + }; +}; + +#define NAS_DEFAULT_NUM 20 /* default number of NumberedArgumentState array */ + +#define _LEFT 0x1 +#define _SIGNED 0x2 +#define _SPACED 0x4 +#define _ZEROS 0x8 +#define _NEG 0x10 + +#define ELEMENTS_OF(array_) (sizeof(array_) / sizeof(array_[0])) + +#define PR_CHECK_DELETE(nas) if (nas && (nas != nasArray)) { PR_DELETE(nas); } + +/* +** Fill into the buffer using the data in src +*/ +static int +fill2(SprintfState* aState, const char16_t* aSrc, int aSrcLen, int aWidth, + int aFlags) +{ + char16_t space = ' '; + int rv; + + aWidth -= aSrcLen; + /* Right adjusting */ + if ((aWidth > 0) && ((aFlags & _LEFT) == 0)) { + if (aFlags & _ZEROS) { + space = '0'; + } + while (--aWidth >= 0) { + rv = (*aState->stuff)(aState, &space, 1); + if (rv < 0) { + return rv; + } + } + } + + /* Copy out the source data */ + rv = (*aState->stuff)(aState, aSrc, aSrcLen); + if (rv < 0) { + return rv; + } + + /* Left adjusting */ + if ((aWidth > 0) && ((aFlags & _LEFT) != 0)) { + while (--aWidth >= 0) { + rv = (*aState->stuff)(aState, &space, 1); + if (rv < 0) { + return rv; + } + } + } + return 0; +} + +/* +** Fill a number. The order is: optional-sign zero-filling conversion-digits +*/ +static int +fill_n(SprintfState* aState, const char16_t* aSrc, int aSrcLen, int aWidth, + int aPrec, int aType, int aFlags) +{ + int zerowidth = 0; + int precwidth = 0; + int signwidth = 0; + int leftspaces = 0; + int rightspaces = 0; + int cvtwidth; + int rv; + char16_t sign; + char16_t space = ' '; + char16_t zero = '0'; + + if ((aType & 1) == 0) { + if (aFlags & _NEG) { + sign = '-'; + signwidth = 1; + } else if (aFlags & _SIGNED) { + sign = '+'; + signwidth = 1; + } else if (aFlags & _SPACED) { + sign = ' '; + signwidth = 1; + } + } + cvtwidth = signwidth + aSrcLen; + + if (aPrec > 0) { + if (aPrec > aSrcLen) { + /* Need zero filling */ + precwidth = aPrec - aSrcLen; + cvtwidth += precwidth; + } + } + + if ((aFlags & _ZEROS) && (aPrec < 0)) { + if (aWidth > cvtwidth) { + /* Zero filling */ + zerowidth = aWidth - cvtwidth; + cvtwidth += zerowidth; + } + } + + if (aFlags & _LEFT) { + if (aWidth > cvtwidth) { + /* Space filling on the right (i.e. left adjusting) */ + rightspaces = aWidth - cvtwidth; + } + } else { + if (aWidth > cvtwidth) { + /* Space filling on the left (i.e. right adjusting) */ + leftspaces = aWidth - cvtwidth; + } + } + while (--leftspaces >= 0) { + rv = (*aState->stuff)(aState, &space, 1); + if (rv < 0) { + return rv; + } + } + if (signwidth) { + rv = (*aState->stuff)(aState, &sign, 1); + if (rv < 0) { + return rv; + } + } + while (--precwidth >= 0) { + rv = (*aState->stuff)(aState, &space, 1); + if (rv < 0) { + return rv; + } + } + while (--zerowidth >= 0) { + rv = (*aState->stuff)(aState, &zero, 1); + if (rv < 0) { + return rv; + } + } + rv = (*aState->stuff)(aState, aSrc, aSrcLen); + if (rv < 0) { + return rv; + } + while (--rightspaces >= 0) { + rv = (*aState->stuff)(aState, &space, 1); + if (rv < 0) { + return rv; + } + } + return 0; +} + +/* +** Convert a long into its printable form +*/ +static int +cvt_l(SprintfState* aState, long aNum, int aWidth, int aPrec, int aRadix, + int aType, int aFlags, const char16_t* aHexStr) +{ + char16_t cvtbuf[100]; + char16_t* cvt; + int digits; + + /* according to the man page this needs to happen */ + if ((aPrec == 0) && (aNum == 0)) { + return 0; + } + + /* + ** Converting decimal is a little tricky. In the unsigned case we + ** need to stop when we hit 10 digits. In the signed case, we can + ** stop when the number is zero. + */ + cvt = &cvtbuf[0] + ELEMENTS_OF(cvtbuf); + digits = 0; + while (aNum) { + int digit = (((unsigned long)aNum) % aRadix) & 0xF; + *--cvt = aHexStr[digit]; + digits++; + aNum = (long)(((unsigned long)aNum) / aRadix); + } + if (digits == 0) { + *--cvt = '0'; + digits++; + } + + /* + ** Now that we have the number converted without its sign, deal with + ** the sign and zero padding. + */ + return fill_n(aState, cvt, digits, aWidth, aPrec, aType, aFlags); +} + +/* +** Convert a 64-bit integer into its printable form +*/ +static int +cvt_ll(SprintfState* aState, int64_t aNum, int aWidth, int aPrec, int aRadix, + int aType, int aFlags, const char16_t* aHexStr) +{ + char16_t cvtbuf[100]; + char16_t* cvt; + int digits; + int64_t rad; + + /* according to the man page this needs to happen */ + if (aPrec == 0 && aNum == 0) { + return 0; + } + + /* + ** Converting decimal is a little tricky. In the unsigned case we + ** need to stop when we hit 10 digits. In the signed case, we can + ** stop when the number is zero. + */ + rad = aRadix; + cvt = &cvtbuf[0] + ELEMENTS_OF(cvtbuf); + digits = 0; + while (aNum != 0) { + *--cvt = aHexStr[int32_t(aNum % rad) & 0xf]; + digits++; + aNum /= rad; + } + if (digits == 0) { + *--cvt = '0'; + digits++; + } + + /* + ** Now that we have the number converted without its sign, deal with + ** the sign and zero padding. + */ + return fill_n(aState, cvt, digits, aWidth, aPrec, aType, aFlags); +} + +/* +** Convert a double precision floating point number into its printable +** form. +*/ +static int +cvt_f(SprintfState* aState, double aDouble, int aWidth, int aPrec, + const char16_t aType, int aFlags) +{ + int mode = 2; + int decpt; + int sign; + char buf[256]; + char* bufp = buf; + int bufsz = 256; + char num[256]; + char* nump; + char* endnum; + int numdigits = 0; + char exp = 'e'; + + if (aPrec == -1) { + aPrec = 6; + } else if (aPrec > 50) { + // limit precision to avoid PR_dtoa bug 108335 + // and to prevent buffers overflows + aPrec = 50; + } + + switch (aType) { + case 'f': + numdigits = aPrec; + mode = 3; + break; + case 'E': + exp = 'E'; + MOZ_FALLTHROUGH; + case 'e': + numdigits = aPrec + 1; + mode = 2; + break; + case 'G': + exp = 'E'; + MOZ_FALLTHROUGH; + case 'g': + if (aPrec == 0) { + aPrec = 1; + } + numdigits = aPrec; + mode = 2; + break; + default: + NS_ERROR("invalid aType passed to cvt_f"); + } + + if (PR_dtoa(aDouble, mode, numdigits, &decpt, &sign, + &endnum, num, bufsz) == PR_FAILURE) { + buf[0] = '\0'; + return -1; + } + numdigits = endnum - num; + nump = num; + + if (sign) { + *bufp++ = '-'; + } else if (aFlags & _SIGNED) { + *bufp++ = '+'; + } + + if (decpt == 9999) { + while ((*bufp++ = *nump++)) { + } + } else { + + switch (aType) { + + case 'E': + case 'e': + + *bufp++ = *nump++; + if (aPrec > 0) { + *bufp++ = '.'; + while (*nump) { + *bufp++ = *nump++; + aPrec--; + } + while (aPrec-- > 0) { + *bufp++ = '0'; + } + } + *bufp++ = exp; + + snprintf(bufp, bufsz - (bufp - buf), "%+03d", decpt - 1); + break; + + case 'f': + + if (decpt < 1) { + *bufp++ = '0'; + if (aPrec > 0) { + *bufp++ = '.'; + while (decpt++ && aPrec-- > 0) { + *bufp++ = '0'; + } + while (*nump && aPrec-- > 0) { + *bufp++ = *nump++; + } + while (aPrec-- > 0) { + *bufp++ = '0'; + } + } + } else { + while (*nump && decpt-- > 0) { + *bufp++ = *nump++; + } + while (decpt-- > 0) { + *bufp++ = '0'; + } + if (aPrec > 0) { + *bufp++ = '.'; + while (*nump && aPrec-- > 0) { + *bufp++ = *nump++; + } + while (aPrec-- > 0) { + *bufp++ = '0'; + } + } + } + *bufp = '\0'; + break; + + case 'G': + case 'g': + + if ((decpt < -3) || ((decpt - 1) >= aPrec)) { + *bufp++ = *nump++; + numdigits--; + if (numdigits > 0) { + *bufp++ = '.'; + while (*nump) { + *bufp++ = *nump++; + } + } + *bufp++ = exp; + snprintf(bufp, bufsz - (bufp - buf), "%+03d", decpt - 1); + } else { + if (decpt < 1) { + *bufp++ = '0'; + if (aPrec > 0) { + *bufp++ = '.'; + while (decpt++) { + *bufp++ = '0'; + } + while (*nump) { + *bufp++ = *nump++; + } + } + } else { + while (*nump && decpt-- > 0) { + *bufp++ = *nump++; + numdigits--; + } + while (decpt-- > 0) { + *bufp++ = '0'; + } + if (numdigits > 0) { + *bufp++ = '.'; + while (*nump) { + *bufp++ = *nump++; + } + } + } + *bufp = '\0'; + } + } + } + + char16_t rbuf[256]; + char16_t* rbufp = rbuf; + bufp = buf; + // cast to char16_t + while ((*rbufp++ = *bufp++)) { + } + *rbufp = '\0'; + + return fill2(aState, rbuf, NS_strlen(rbuf), aWidth, aFlags); +} + +/* +** Convert a string into its printable form. |aWidth| is the output +** width. |aPrec| is the maximum number of characters of |aStr| to output, +** where -1 means until NUL. +*/ +static int +cvt_S(SprintfState* aState, const char16_t* aStr, int aWidth, int aPrec, + int aFlags) +{ + int slen; + + if (aPrec == 0) { + return 0; + } + + /* Limit string length by precision value */ + slen = aStr ? NS_strlen(aStr) : 6; + if (aPrec > 0) { + if (aPrec < slen) { + slen = aPrec; + } + } + + /* and away we go */ + return fill2(aState, aStr ? aStr : u"(null)", slen, aWidth, aFlags); +} + +/* +** Convert a string into its printable form. |aWidth| is the output +** width. |aPrec| is the maximum number of characters of |aStr| to output, +** where -1 means until NUL. +*/ +static int +cvt_s(SprintfState* aState, const char* aStr, int aWidth, int aPrec, int aFlags) +{ + NS_ConvertUTF8toUTF16 utf16Val(aStr); + return cvt_S(aState, utf16Val.get(), aWidth, aPrec, aFlags); +} + +/* +** BuildArgArray stands for Numbered Argument list Sprintf +** for example, +** fmp = "%4$i, %2$d, %3s, %1d"; +** the number must start from 1, and no gap among them +*/ + +static struct NumArgState* +BuildArgArray(const char16_t* aFmt, va_list aAp, int* aRv, + struct NumArgState* aNasArray) +{ + int number = 0, cn = 0, i; + const char16_t* p; + char16_t c; + struct NumArgState* nas; + + /* + ** first pass: + ** detemine how many legal % I have got, then allocate space + */ + p = aFmt; + *aRv = 0; + i = 0; + while ((c = *p++) != 0) { + if (c != '%') { + continue; + } + /* skip %% case */ + if ((c = *p++) == '%') { + continue; + } + + while (c != 0) { + if (c > '9' || c < '0') { + /* numbered argument csae */ + if (c == '$') { + if (i > 0) { + *aRv = -1; + return nullptr; + } + number++; + break; + + } else { + /* non-numbered argument case */ + if (number > 0) { + *aRv = -1; + return nullptr; + } + i = 1; + break; + } + } + c = *p++; + } + } + + if (number == 0) { + return nullptr; + } + + if (number > NAS_DEFAULT_NUM) { + nas = (struct NumArgState*)moz_xmalloc(number * sizeof(struct NumArgState)); + if (!nas) { + *aRv = -1; + return nullptr; + } + } else { + nas = aNasArray; + } + + for (i = 0; i < number; i++) { + nas[i].type = NumArgState::UNKNOWN; + } + + /* + ** second pass: + ** set nas[].type + */ + p = aFmt; + while ((c = *p++) != 0) { + if (c != '%') { + continue; + } + c = *p++; + if (c == '%') { + continue; + } + cn = 0; + /* should improve error check later */ + while (c && c != '$') { + cn = cn * 10 + c - '0'; + c = *p++; + } + + if (!c || cn < 1 || cn > number) { + *aRv = -1; + break; + } + + /* nas[cn] starts from 0, and make sure + nas[cn].type is not assigned */ + cn--; + if (nas[cn].type != NumArgState::UNKNOWN) { + continue; + } + + c = *p++; + + /* width */ + if (c == '*') { + /* not supported feature, for the argument is not numbered */ + *aRv = -1; + break; + } else { + while ((c >= '0') && (c <= '9')) { + c = *p++; + } + } + + /* precision */ + if (c == '.') { + c = *p++; + if (c == '*') { + /* not supported feature, for the argument is not numbered */ + *aRv = -1; + break; + } else { + while ((c >= '0') && (c <= '9')) { + c = *p++; + } + } + } + + /* size */ + nas[cn].type = NumArgState::INTN; + if (c == 'h') { + nas[cn].type = NumArgState::INT16; + c = *p++; + } else if (c == 'L') { + /* XXX not quite sure here */ + nas[cn].type = NumArgState::INT64; + c = *p++; + } else if (c == 'l') { + nas[cn].type = NumArgState::INT32; + c = *p++; + if (c == 'l') { + nas[cn].type = NumArgState::INT64; + c = *p++; + } + } + + /* format */ + switch (c) { + case 'd': + case 'c': + case 'i': + case 'o': + case 'u': + case 'x': + case 'X': + break; + + case 'e': + case 'f': + case 'g': + nas[cn].type = NumArgState::DOUBLE; + break; + + case 'p': + /* XXX should use cpp */ + if (sizeof(void*) == sizeof(int32_t)) { + nas[cn].type = NumArgState::UINT32; + } else if (sizeof(void*) == sizeof(int64_t)) { + nas[cn].type = NumArgState::UINT64; + } else if (sizeof(void*) == sizeof(int)) { + nas[cn].type = NumArgState::UINTN; + } else { + nas[cn].type = NumArgState::UNKNOWN; + } + break; + + case 'C': + /* XXX not supported I suppose */ + PR_ASSERT(0); + nas[cn].type = NumArgState::UNKNOWN; + break; + + case 'S': + nas[cn].type = NumArgState::UNISTRING; + break; + + case 's': + nas[cn].type = NumArgState::STRING; + break; + + case 'n': + nas[cn].type = NumArgState::INTSTR; + break; + + default: + PR_ASSERT(0); + nas[cn].type = NumArgState::UNKNOWN; + break; + } + + /* get a legal para. */ + if (nas[cn].type == NumArgState::UNKNOWN) { + *aRv = -1; + break; + } + } + + + /* + ** third pass + ** fill the nas[cn].ap + */ + if (*aRv < 0) { + if (nas != aNasArray) { + PR_DELETE(nas); + } + return nullptr; + } + + cn = 0; + while (cn < number) { + if (nas[cn].type == NumArgState::UNKNOWN) { + cn++; + continue; + } + + VARARGS_ASSIGN(nas[cn].ap, aAp); + + switch (nas[cn].type) { + case NumArgState::INT16: + case NumArgState::UINT16: + case NumArgState::INTN: + case NumArgState::UINTN: (void)va_arg(aAp, int); break; + + case NumArgState::INT32: (void)va_arg(aAp, int32_t); break; + + case NumArgState::UINT32: (void)va_arg(aAp, uint32_t); break; + + case NumArgState::INT64: (void)va_arg(aAp, int64_t); break; + + case NumArgState::UINT64: (void)va_arg(aAp, uint64_t); break; + + case NumArgState::STRING: (void)va_arg(aAp, char*); break; + + case NumArgState::INTSTR: (void)va_arg(aAp, int*); break; + + case NumArgState::DOUBLE: (void)va_arg(aAp, double); break; + + case NumArgState::UNISTRING: (void)va_arg(aAp, char16_t*); break; + + default: + if (nas != aNasArray) { + PR_DELETE(nas); + } + *aRv = -1; + va_end(aAp); + return nullptr; + } + cn++; + } + va_end(aAp); + return nas; +} + + +/* +** The workhorse sprintf code. +*/ +static int +dosprintf(SprintfState* aState, const char16_t* aFmt, va_list aAp) +{ + char16_t c; + int flags, width, prec, radix, type; + union + { + char16_t ch; + int i; + long l; + int64_t ll; + double d; + const char* s; + const char16_t* S; + int* ip; + } u; + char16_t space = ' '; + + nsAutoString hex; + hex.AssignLiteral("0123456789abcdef"); + + nsAutoString HEX; + HEX.AssignLiteral("0123456789ABCDEF"); + + const char16_t* hexp; + int rv, i; + struct NumArgState* nas = nullptr; + struct NumArgState nasArray[NAS_DEFAULT_NUM]; + + + /* + ** build an argument array, IF the aFmt is numbered argument + ** list style, to contain the Numbered Argument list pointers + */ + nas = BuildArgArray(aFmt, aAp, &rv, nasArray); + if (rv < 0) { + /* the aFmt contains error Numbered Argument format, jliu@netscape.com */ + PR_ASSERT(0); + return rv; + } + + while ((c = *aFmt++) != 0) { + if (c != '%') { + rv = (*aState->stuff)(aState, aFmt - 1, 1); + if (rv < 0) { + va_end(aAp); + PR_CHECK_DELETE(nas); + return rv; + } + continue; + } + + /* + ** Gobble up the % format string. Hopefully we have handled all + ** of the strange cases! + */ + flags = 0; + c = *aFmt++; + if (c == '%') { + /* quoting a % with %% */ + rv = (*aState->stuff)(aState, aFmt - 1, 1); + if (rv < 0) { + va_end(aAp); + PR_CHECK_DELETE(nas); + return rv; + } + continue; + } + + if (nas) { + /* the aFmt contains the Numbered Arguments feature */ + i = 0; + /* should improve error check later */ + while (c && c != '$') { + i = (i * 10) + (c - '0'); + c = *aFmt++; + } + + if (nas[i - 1].type == NumArgState::UNKNOWN) { + if (nas != nasArray) { + PR_DELETE(nas); + } + va_end(aAp); + return -1; + } + + VARARGS_ASSIGN(aAp, nas[i - 1].ap); + c = *aFmt++; + } + + /* + * Examine optional flags. Note that we do not implement the + * '#' flag of sprintf(). The ANSI C spec. of the '#' flag is + * somewhat ambiguous and not ideal, which is perhaps why + * the various sprintf() implementations are inconsistent + * on this feature. + */ + while ((c == '-') || (c == '+') || (c == ' ') || (c == '0')) { + if (c == '-') { + flags |= _LEFT; + } + if (c == '+') { + flags |= _SIGNED; + } + if (c == ' ') { + flags |= _SPACED; + } + if (c == '0') { + flags |= _ZEROS; + } + c = *aFmt++; + } + if (flags & _SIGNED) { + flags &= ~_SPACED; + } + if (flags & _LEFT) { + flags &= ~_ZEROS; + } + + /* width */ + if (c == '*') { + c = *aFmt++; + width = va_arg(aAp, int); + } else { + width = 0; + while ((c >= '0') && (c <= '9')) { + width = (width * 10) + (c - '0'); + c = *aFmt++; + } + } + + /* precision */ + prec = -1; + if (c == '.') { + c = *aFmt++; + if (c == '*') { + c = *aFmt++; + prec = va_arg(aAp, int); + } else { + prec = 0; + while ((c >= '0') && (c <= '9')) { + prec = (prec * 10) + (c - '0'); + c = *aFmt++; + } + } + } + + /* size */ + type = NumArgState::INTN; + if (c == 'h') { + type = NumArgState::INT16; + c = *aFmt++; + } else if (c == 'L') { + /* XXX not quite sure here */ + type = NumArgState::INT64; + c = *aFmt++; + } else if (c == 'l') { + type = NumArgState::INT32; + c = *aFmt++; + if (c == 'l') { + type = NumArgState::INT64; + c = *aFmt++; + } + } + + /* format */ + hexp = hex.get(); + switch (c) { + case 'd': + case 'i': /* decimal/integer */ + radix = 10; + goto fetch_and_convert; + + case 'o': /* octal */ + radix = 8; + type |= 1; + goto fetch_and_convert; + + case 'u': /* unsigned decimal */ + radix = 10; + type |= 1; + goto fetch_and_convert; + + case 'x': /* unsigned hex */ + radix = 16; + type |= 1; + goto fetch_and_convert; + + case 'X': /* unsigned HEX */ + radix = 16; + hexp = HEX.get(); + type |= 1; + goto fetch_and_convert; + + fetch_and_convert: + switch (type) { + case NumArgState::INT16: + u.l = va_arg(aAp, int); + if (u.l < 0) { + u.l = -u.l; + flags |= _NEG; + } + goto do_long; + case NumArgState::UINT16: + u.l = va_arg(aAp, int) & 0xffff; + goto do_long; + case NumArgState::INTN: + u.l = va_arg(aAp, int); + if (u.l < 0) { + u.l = -u.l; + flags |= _NEG; + } + goto do_long; + case NumArgState::UINTN: + u.l = (long)va_arg(aAp, unsigned int); + goto do_long; + + case NumArgState::INT32: + u.l = va_arg(aAp, int32_t); + if (u.l < 0) { + u.l = -u.l; + flags |= _NEG; + } + goto do_long; + case NumArgState::UINT32: + u.l = (long)va_arg(aAp, uint32_t); + do_long: + rv = cvt_l(aState, u.l, width, prec, radix, type, flags, hexp); + if (rv < 0) { + va_end(aAp); + PR_CHECK_DELETE(nas); + return rv; + } + break; + + case NumArgState::INT64: + u.ll = va_arg(aAp, int64_t); + if (u.ll < 0) { + u.ll = -u.ll; + flags |= _NEG; + } + goto do_longlong; + case NumArgState::UINT64: + u.ll = va_arg(aAp, uint64_t); + do_longlong: + rv = cvt_ll(aState, u.ll, width, prec, radix, type, flags, hexp); + if (rv < 0) { + va_end(aAp); + PR_CHECK_DELETE(nas); + return rv; + } + break; + } + break; + + case 'e': + case 'E': + case 'f': + case 'g': + case 'G': + u.d = va_arg(aAp, double); + rv = cvt_f(aState, u.d, width, prec, c, flags); + if (rv < 0) { + return rv; + } + break; + + case 'c': + u.ch = va_arg(aAp, int); + if ((flags & _LEFT) == 0) { + while (width-- > 1) { + rv = (*aState->stuff)(aState, &space, 1); + if (rv < 0) { + va_end(aAp); + PR_CHECK_DELETE(nas); + return rv; + } + } + } + rv = (*aState->stuff)(aState, &u.ch, 1); + if (rv < 0) { + va_end(aAp); + PR_CHECK_DELETE(nas); + return rv; + } + if (flags & _LEFT) { + while (width-- > 1) { + rv = (*aState->stuff)(aState, &space, 1); + if (rv < 0) { + va_end(aAp); + PR_CHECK_DELETE(nas); + return rv; + } + } + } + break; + + case 'p': + if (sizeof(void*) == sizeof(int32_t)) { + type = NumArgState::UINT32; + } else if (sizeof(void*) == sizeof(int64_t)) { + type = NumArgState::UINT64; + } else if (sizeof(void*) == sizeof(int)) { + type = NumArgState::UINTN; + } else { + PR_ASSERT(0); + break; + } + radix = 16; + goto fetch_and_convert; + +#if 0 + case 'C': + /* XXX not supported I suppose */ + PR_ASSERT(0); + break; +#endif + + case 'S': + u.S = va_arg(aAp, const char16_t*); + rv = cvt_S(aState, u.S, width, prec, flags); + if (rv < 0) { + va_end(aAp); + PR_CHECK_DELETE(nas); + return rv; + } + break; + + case 's': + u.s = va_arg(aAp, const char*); + rv = cvt_s(aState, u.s, width, prec, flags); + if (rv < 0) { + va_end(aAp); + PR_CHECK_DELETE(nas); + return rv; + } + break; + + case 'n': + u.ip = va_arg(aAp, int*); + if (u.ip) { + *u.ip = aState->cur - aState->base; + } + break; + + default: + /* Not a % token after all... skip it */ +#if 0 + PR_ASSERT(0); +#endif + char16_t perct = '%'; + rv = (*aState->stuff)(aState, &perct, 1); + if (rv < 0) { + va_end(aAp); + PR_CHECK_DELETE(nas); + return rv; + } + rv = (*aState->stuff)(aState, aFmt - 1, 1); + if (rv < 0) { + va_end(aAp); + PR_CHECK_DELETE(nas); + return rv; + } + } + } + + /* Stuff trailing NUL */ + char16_t null = '\0'; + + rv = (*aState->stuff)(aState, &null, 1); + + va_end(aAp); + PR_CHECK_DELETE(nas); + + return rv; +} + +/************************************************************************/ + +static int +StringStuff(SprintfState* aState, const char16_t* aStr, uint32_t aLen) +{ + if (*aStr == '\0') { + return 0; + } + + ptrdiff_t off = aState->cur - aState->base; + + nsAString* str = static_cast<nsAString*>(aState->stuffclosure); + str->Append(aStr, aLen); + + aState->base = str->BeginWriting(); + aState->cur = aState->base + off; + + return 0; +} + +/* +** Stuff routine that automatically grows the malloc'd output buffer +** before it overflows. +*/ +static int +GrowStuff(SprintfState* aState, const char16_t* aStr, uint32_t aLen) +{ + ptrdiff_t off; + char16_t* newbase; + uint32_t newlen; + + off = aState->cur - aState->base; + if (off + aLen >= aState->maxlen) { + /* Grow the buffer */ + newlen = aState->maxlen + ((aLen > 32) ? aLen : 32); + if (aState->base) { + newbase = (char16_t*)moz_xrealloc(aState->base, + newlen * sizeof(char16_t)); + } else { + newbase = (char16_t*)moz_xmalloc(newlen * sizeof(char16_t)); + } + if (!newbase) { + /* Ran out of memory */ + return -1; + } + aState->base = newbase; + aState->maxlen = newlen; + aState->cur = aState->base + off; + } + + /* Copy data */ + while (aLen) { + --aLen; + *aState->cur++ = *aStr++; + } + MOZ_ASSERT((uint32_t)(aState->cur - aState->base) <= aState->maxlen); + return 0; +} + +/* +** sprintf into a malloc'd buffer +*/ +char16_t* +nsTextFormatter::smprintf(const char16_t* aFmt, ...) +{ + va_list ap; + char16_t* rv; + + va_start(ap, aFmt); + rv = nsTextFormatter::vsmprintf(aFmt, ap); + va_end(ap); + return rv; +} + +uint32_t +nsTextFormatter::ssprintf(nsAString& aOut, const char16_t* aFmt, ...) +{ + va_list ap; + uint32_t rv; + + va_start(ap, aFmt); + rv = nsTextFormatter::vssprintf(aOut, aFmt, ap); + va_end(ap); + return rv; +} + +uint32_t +nsTextFormatter::vssprintf(nsAString& aOut, const char16_t* aFmt, va_list aAp) +{ + SprintfState ss; + ss.stuff = StringStuff; + ss.base = 0; + ss.cur = 0; + ss.maxlen = 0; + ss.stuffclosure = &aOut; + + aOut.Truncate(); + int n = dosprintf(&ss, aFmt, aAp); + return n ? n - 1 : n; +} + +char16_t* +nsTextFormatter::vsmprintf(const char16_t* aFmt, va_list aAp) +{ + SprintfState ss; + int rv; + + ss.stuff = GrowStuff; + ss.base = 0; + ss.cur = 0; + ss.maxlen = 0; + rv = dosprintf(&ss, aFmt, aAp); + if (rv < 0) { + if (ss.base) { + PR_DELETE(ss.base); + } + return 0; + } + return ss.base; +} + +/* +** Stuff routine that discards overflow data +*/ +static int +LimitStuff(SprintfState* aState, const char16_t* aStr, uint32_t aLen) +{ + uint32_t limit = aState->maxlen - (aState->cur - aState->base); + + if (aLen > limit) { + aLen = limit; + } + while (aLen) { + --aLen; + *aState->cur++ = *aStr++; + } + return 0; +} + +/* +** sprintf into a fixed size buffer. Make sure there is a NUL at the end +** when finished. +*/ +uint32_t +nsTextFormatter::snprintf(char16_t* aOut, uint32_t aOutLen, + const char16_t* aFmt, ...) +{ + va_list ap; + uint32_t rv; + + MOZ_ASSERT((int32_t)aOutLen > 0); + if ((int32_t)aOutLen <= 0) { + return 0; + } + + va_start(ap, aFmt); + rv = nsTextFormatter::vsnprintf(aOut, aOutLen, aFmt, ap); + va_end(ap); + return rv; +} + +uint32_t +nsTextFormatter::vsnprintf(char16_t* aOut, uint32_t aOutLen, + const char16_t* aFmt, va_list aAp) +{ + SprintfState ss; + uint32_t n; + + MOZ_ASSERT((int32_t)aOutLen > 0); + if ((int32_t)aOutLen <= 0) { + return 0; + } + + ss.stuff = LimitStuff; + ss.base = aOut; + ss.cur = aOut; + ss.maxlen = aOutLen; + (void) dosprintf(&ss, aFmt, aAp); + + /* If we added chars, and we didn't append a null, do it now. */ + if ((ss.cur != ss.base) && (*(ss.cur - 1) != '\0')) { + *(--ss.cur) = '\0'; + } + + n = ss.cur - ss.base; + return n ? n - 1 : n; +} + +/* + * Free memory allocated, for the caller, by smprintf + */ +void +nsTextFormatter::smprintf_free(char16_t* aMem) +{ + free(aMem); +} + diff --git a/xpcom/glue/nsTextFormatter.h b/xpcom/glue/nsTextFormatter.h new file mode 100644 index 0000000000..4cd44be320 --- /dev/null +++ b/xpcom/glue/nsTextFormatter.h @@ -0,0 +1,80 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * This code was copied from xpcom/ds/nsTextFormatter r1.3 + * Memory model and Frozen linkage changes only. + * -- Prasad <prasad@medhas.org> + */ + +#ifndef nsTextFormatter_h___ +#define nsTextFormatter_h___ + +/* + ** API for PR printf like routines. Supports the following formats + ** %d - decimal + ** %u - unsigned decimal + ** %x - unsigned hex + ** %X - unsigned uppercase hex + ** %o - unsigned octal + ** %hd, %hu, %hx, %hX, %ho - 16-bit versions of above + ** %ld, %lu, %lx, %lX, %lo - 32-bit versions of above + ** %lld, %llu, %llx, %llX, %llo - 64 bit versions of above + ** %s - utf8 string + ** %S - char16_t string + ** %c - character + ** %p - pointer (deals with machine dependent pointer size) + ** %f - float + ** %g - float + */ +#include "prio.h" +#include <stdio.h> +#include <stdarg.h> +#include "nscore.h" +#include "nsStringGlue.h" + +#ifdef XPCOM_GLUE +#error "nsTextFormatter is not available in the standalone glue due to NSPR dependencies." +#endif + +class nsTextFormatter +{ +public: + + /* + * sprintf into a fixed size buffer. Guarantees that the buffer is null + * terminated. Returns the length of the written output, NOT including the + * null terminator, or (uint32_t)-1 if an error occurs. + */ + static uint32_t snprintf(char16_t* aOut, uint32_t aOutLen, + const char16_t* aFmt, ...); + + /* + * sprintf into a moz_xmalloc'd buffer. Return a pointer to + * buffer on success, nullptr on failure. + */ + static char16_t* smprintf(const char16_t* aFmt, ...); + + static uint32_t ssprintf(nsAString& aOut, const char16_t* aFmt, ...); + + /* + * va_list forms of the above. + */ + static uint32_t vsnprintf(char16_t* aOut, uint32_t aOutLen, const char16_t* aFmt, + va_list aAp); + static char16_t* vsmprintf(const char16_t* aFmt, va_list aAp); + static uint32_t vssprintf(nsAString& aOut, const char16_t* aFmt, va_list aAp); + + /* + * Free the memory allocated, for the caller, by smprintf. + * -- Deprecated -- + * Callers can substitute calling smprintf_free with free + */ + static void smprintf_free(char16_t* aMem); + +}; + +#endif /* nsTextFormatter_h___ */ diff --git a/xpcom/glue/nsThreadUtils.cpp b/xpcom/glue/nsThreadUtils.cpp new file mode 100644 index 0000000000..287ada7bef --- /dev/null +++ b/xpcom/glue/nsThreadUtils.cpp @@ -0,0 +1,472 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsThreadUtils.h" +#include "mozilla/Attributes.h" +#include "mozilla/Likely.h" +#include "mozilla/TimeStamp.h" +#include "LeakRefPtr.h" + +#ifdef MOZILLA_INTERNAL_API +# include "nsThreadManager.h" +#else +# include "nsXPCOMCIDInternal.h" +# include "nsIThreadManager.h" +# include "nsServiceManagerUtils.h" +#endif + +#ifdef XP_WIN +#include <windows.h> +#include "mozilla/WindowsVersion.h" +using mozilla::IsVistaOrLater; +#elif defined(XP_MACOSX) +#include <sys/resource.h> +#endif + +#include <pratom.h> +#include <prthread.h> + +using namespace mozilla; + +#ifndef XPCOM_GLUE_AVOID_NSPR + +NS_IMPL_ISUPPORTS(IdlePeriod, nsIIdlePeriod) + +NS_IMETHODIMP +IdlePeriod::GetIdlePeriodHint(TimeStamp* aIdleDeadline) +{ + *aIdleDeadline = TimeStamp(); + return NS_OK; +} + +NS_IMPL_ISUPPORTS(Runnable, nsIRunnable) + +NS_IMETHODIMP +Runnable::Run() +{ + // Do nothing + return NS_OK; +} + +NS_IMPL_ISUPPORTS_INHERITED(CancelableRunnable, Runnable, + nsICancelableRunnable) + +nsresult +CancelableRunnable::Cancel() +{ + // Do nothing + return NS_OK; +} + +NS_IMPL_ISUPPORTS_INHERITED(IncrementalRunnable, CancelableRunnable, + nsIIncrementalRunnable) + +void +IncrementalRunnable::SetDeadline(TimeStamp aDeadline) +{ + // Do nothing +} + +#endif // XPCOM_GLUE_AVOID_NSPR + +//----------------------------------------------------------------------------- + +nsresult +NS_NewThread(nsIThread** aResult, nsIRunnable* aEvent, uint32_t aStackSize) +{ + nsCOMPtr<nsIThread> thread; +#ifdef MOZILLA_INTERNAL_API + nsresult rv = + nsThreadManager::get().nsThreadManager::NewThread(0, aStackSize, + getter_AddRefs(thread)); +#else + nsresult rv; + nsCOMPtr<nsIThreadManager> mgr = + do_GetService(NS_THREADMANAGER_CONTRACTID, &rv); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = mgr->NewThread(0, aStackSize, getter_AddRefs(thread)); +#endif + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + if (aEvent) { + rv = thread->Dispatch(aEvent, NS_DISPATCH_NORMAL); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } + + *aResult = nullptr; + thread.swap(*aResult); + return NS_OK; +} + +nsresult +NS_GetCurrentThread(nsIThread** aResult) +{ +#ifdef MOZILLA_INTERNAL_API + return nsThreadManager::get().nsThreadManager::GetCurrentThread(aResult); +#else + nsresult rv; + nsCOMPtr<nsIThreadManager> mgr = + do_GetService(NS_THREADMANAGER_CONTRACTID, &rv); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + return mgr->GetCurrentThread(aResult); +#endif +} + +nsresult +NS_GetMainThread(nsIThread** aResult) +{ +#ifdef MOZILLA_INTERNAL_API + return nsThreadManager::get().nsThreadManager::GetMainThread(aResult); +#else + nsresult rv; + nsCOMPtr<nsIThreadManager> mgr = + do_GetService(NS_THREADMANAGER_CONTRACTID, &rv); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + return mgr->GetMainThread(aResult); +#endif +} + +#ifndef MOZILLA_INTERNAL_API +bool +NS_IsMainThread() +{ + bool result = false; + nsCOMPtr<nsIThreadManager> mgr = + do_GetService(NS_THREADMANAGER_CONTRACTID); + if (mgr) { + mgr->GetIsMainThread(&result); + } + return bool(result); +} +#endif + +nsresult +NS_DispatchToCurrentThread(already_AddRefed<nsIRunnable>&& aEvent) +{ + nsresult rv; + nsCOMPtr<nsIRunnable> event(aEvent); +#ifdef MOZILLA_INTERNAL_API + nsIThread* thread = NS_GetCurrentThread(); + if (!thread) { + return NS_ERROR_UNEXPECTED; + } +#else + nsCOMPtr<nsIThread> thread; + rv = NS_GetCurrentThread(getter_AddRefs(thread)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } +#endif + // To keep us from leaking the runnable if dispatch method fails, + // we grab the reference on failures and release it. + nsIRunnable* temp = event.get(); + rv = thread->Dispatch(event.forget(), NS_DISPATCH_NORMAL); + if (NS_WARN_IF(NS_FAILED(rv))) { + // Dispatch() leaked the reference to the event, but due to caller's + // assumptions, we shouldn't leak here. And given we are on the same + // thread as the dispatch target, it's mostly safe to do it here. + NS_RELEASE(temp); + } + return rv; +} + +// It is common to call NS_DispatchToCurrentThread with a newly +// allocated runnable with a refcount of zero. To keep us from leaking +// the runnable if the dispatch method fails, we take a death grip. +nsresult +NS_DispatchToCurrentThread(nsIRunnable* aEvent) +{ + nsCOMPtr<nsIRunnable> event(aEvent); + return NS_DispatchToCurrentThread(event.forget()); +} + +nsresult +NS_DispatchToMainThread(already_AddRefed<nsIRunnable>&& aEvent, uint32_t aDispatchFlags) +{ + LeakRefPtr<nsIRunnable> event(Move(aEvent)); + nsCOMPtr<nsIThread> thread; + nsresult rv = NS_GetMainThread(getter_AddRefs(thread)); + if (NS_WARN_IF(NS_FAILED(rv))) { + NS_ASSERTION(false, "Failed NS_DispatchToMainThread() in shutdown; leaking"); + // NOTE: if you stop leaking here, adjust Promise::MaybeReportRejected(), + // which assumes a leak here, or split into leaks and no-leaks versions + return rv; + } + return thread->Dispatch(event.take(), aDispatchFlags); +} + +// In the case of failure with a newly allocated runnable with a +// refcount of zero, we intentionally leak the runnable, because it is +// likely that the runnable is being dispatched to the main thread +// because it owns main thread only objects, so it is not safe to +// release them here. +nsresult +NS_DispatchToMainThread(nsIRunnable* aEvent, uint32_t aDispatchFlags) +{ + nsCOMPtr<nsIRunnable> event(aEvent); + return NS_DispatchToMainThread(event.forget(), aDispatchFlags); +} + +nsresult +NS_DelayedDispatchToCurrentThread(already_AddRefed<nsIRunnable>&& aEvent, uint32_t aDelayMs) +{ + nsCOMPtr<nsIRunnable> event(aEvent); +#ifdef MOZILLA_INTERNAL_API + nsIThread* thread = NS_GetCurrentThread(); + if (!thread) { + return NS_ERROR_UNEXPECTED; + } +#else + nsresult rv; + nsCOMPtr<nsIThread> thread; + rv = NS_GetCurrentThread(getter_AddRefs(thread)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } +#endif + + return thread->DelayedDispatch(event.forget(), aDelayMs); +} + +nsresult +NS_IdleDispatchToCurrentThread(already_AddRefed<nsIRunnable>&& aEvent) +{ + nsresult rv; + nsCOMPtr<nsIRunnable> event(aEvent); +#ifdef MOZILLA_INTERNAL_API + nsIThread* thread = NS_GetCurrentThread(); + if (!thread) { + return NS_ERROR_UNEXPECTED; + } +#else + nsCOMPtr<nsIThread> thread; + rv = NS_GetCurrentThread(getter_AddRefs(thread)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } +#endif + // To keep us from leaking the runnable if dispatch method fails, + // we grab the reference on failures and release it. + nsIRunnable* temp = event.get(); + rv = thread->IdleDispatch(event.forget()); + if (NS_WARN_IF(NS_FAILED(rv))) { + // Dispatch() leaked the reference to the event, but due to caller's + // assumptions, we shouldn't leak here. And given we are on the same + // thread as the dispatch target, it's mostly safe to do it here. + NS_RELEASE(temp); + } + + return rv; +} + +#ifndef XPCOM_GLUE_AVOID_NSPR +nsresult +NS_ProcessPendingEvents(nsIThread* aThread, PRIntervalTime aTimeout) +{ + nsresult rv = NS_OK; + +#ifdef MOZILLA_INTERNAL_API + if (!aThread) { + aThread = NS_GetCurrentThread(); + if (NS_WARN_IF(!aThread)) { + return NS_ERROR_UNEXPECTED; + } + } +#else + nsCOMPtr<nsIThread> current; + if (!aThread) { + rv = NS_GetCurrentThread(getter_AddRefs(current)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + aThread = current.get(); + } +#endif + + PRIntervalTime start = PR_IntervalNow(); + for (;;) { + bool processedEvent; + rv = aThread->ProcessNextEvent(false, &processedEvent); + if (NS_FAILED(rv) || !processedEvent) { + break; + } + if (PR_IntervalNow() - start > aTimeout) { + break; + } + } + return rv; +} +#endif // XPCOM_GLUE_AVOID_NSPR + +inline bool +hasPendingEvents(nsIThread* aThread) +{ + bool val; + return NS_SUCCEEDED(aThread->HasPendingEvents(&val)) && val; +} + +bool +NS_HasPendingEvents(nsIThread* aThread) +{ + if (!aThread) { +#ifndef MOZILLA_INTERNAL_API + nsCOMPtr<nsIThread> current; + NS_GetCurrentThread(getter_AddRefs(current)); + return hasPendingEvents(current); +#else + aThread = NS_GetCurrentThread(); + if (NS_WARN_IF(!aThread)) { + return false; + } +#endif + } + return hasPendingEvents(aThread); +} + +bool +NS_ProcessNextEvent(nsIThread* aThread, bool aMayWait) +{ +#ifdef MOZILLA_INTERNAL_API + if (!aThread) { + aThread = NS_GetCurrentThread(); + if (NS_WARN_IF(!aThread)) { + return false; + } + } +#else + nsCOMPtr<nsIThread> current; + if (!aThread) { + NS_GetCurrentThread(getter_AddRefs(current)); + if (NS_WARN_IF(!current)) { + return false; + } + aThread = current.get(); + } +#endif + bool val; + return NS_SUCCEEDED(aThread->ProcessNextEvent(aMayWait, &val)) && val; +} + +#ifndef XPCOM_GLUE_AVOID_NSPR + +namespace { + +class nsNameThreadRunnable final : public nsIRunnable +{ + ~nsNameThreadRunnable() {} + +public: + explicit nsNameThreadRunnable(const nsACString& aName) : mName(aName) {} + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIRUNNABLE + +protected: + const nsCString mName; +}; + +NS_IMPL_ISUPPORTS(nsNameThreadRunnable, nsIRunnable) + +NS_IMETHODIMP +nsNameThreadRunnable::Run() +{ + PR_SetCurrentThreadName(mName.BeginReading()); + return NS_OK; +} + +} // namespace + +void +NS_SetThreadName(nsIThread* aThread, const nsACString& aName) +{ + if (!aThread) { + return; + } + + aThread->Dispatch(new nsNameThreadRunnable(aName), + nsIEventTarget::DISPATCH_NORMAL); +} + +#else // !XPCOM_GLUE_AVOID_NSPR + +void +NS_SetThreadName(nsIThread* aThread, const nsACString& aName) +{ + // No NSPR, no love. +} + +#endif + +#ifdef MOZILLA_INTERNAL_API +nsIThread* +NS_GetCurrentThread() +{ + return nsThreadManager::get().GetCurrentThread(); +} +#endif + +// nsThreadPoolNaming +void +nsThreadPoolNaming::SetThreadPoolName(const nsACString& aPoolName, + nsIThread* aThread) +{ + nsCString name(aPoolName); + name.AppendLiteral(" #"); + name.AppendInt(++mCounter, 10); // The counter is declared as volatile + + if (aThread) { + // Set on the target thread + NS_SetThreadName(aThread, name); + } else { + // Set on the current thread +#ifndef XPCOM_GLUE_AVOID_NSPR + PR_SetCurrentThreadName(name.BeginReading()); +#endif + } +} + +// nsAutoLowPriorityIO +nsAutoLowPriorityIO::nsAutoLowPriorityIO() +{ +#if defined(XP_WIN) + lowIOPrioritySet = IsVistaOrLater() && + SetThreadPriority(GetCurrentThread(), + THREAD_MODE_BACKGROUND_BEGIN); +#elif defined(XP_MACOSX) + oldPriority = getiopolicy_np(IOPOL_TYPE_DISK, IOPOL_SCOPE_THREAD); + lowIOPrioritySet = oldPriority != -1 && + setiopolicy_np(IOPOL_TYPE_DISK, + IOPOL_SCOPE_THREAD, + IOPOL_THROTTLE) != -1; +#else + lowIOPrioritySet = false; +#endif +} + +nsAutoLowPriorityIO::~nsAutoLowPriorityIO() +{ +#if defined(XP_WIN) + if (MOZ_LIKELY(lowIOPrioritySet)) { + // On Windows the old thread priority is automatically restored + SetThreadPriority(GetCurrentThread(), THREAD_MODE_BACKGROUND_END); + } +#elif defined(XP_MACOSX) + if (MOZ_LIKELY(lowIOPrioritySet)) { + setiopolicy_np(IOPOL_TYPE_DISK, IOPOL_SCOPE_THREAD, oldPriority); + } +#endif +} diff --git a/xpcom/glue/nsThreadUtils.h b/xpcom/glue/nsThreadUtils.h new file mode 100644 index 0000000000..cbd7f78421 --- /dev/null +++ b/xpcom/glue/nsThreadUtils.h @@ -0,0 +1,1049 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsThreadUtils_h__ +#define nsThreadUtils_h__ + +#include "prthread.h" +#include "prinrval.h" +#include "MainThreadUtils.h" +#include "nsIThreadManager.h" +#include "nsIThread.h" +#include "nsIRunnable.h" +#include "nsICancelableRunnable.h" +#include "nsIIdlePeriod.h" +#include "nsIIncrementalRunnable.h" +#include "nsStringGlue.h" +#include "nsCOMPtr.h" +#include "nsAutoPtr.h" +#include "mozilla/Atomics.h" +#include "mozilla/IndexSequence.h" +#include "mozilla/Likely.h" +#include "mozilla/Move.h" +#include "mozilla/TimeStamp.h" +#include "mozilla/Tuple.h" +#include "mozilla/TypeTraits.h" + +//----------------------------------------------------------------------------- +// These methods are alternatives to the methods on nsIThreadManager, provided +// for convenience. + +/** + * Set name of the target thread. This operation is asynchronous. + */ +extern void NS_SetThreadName(nsIThread* aThread, const nsACString& aName); + +/** + * Static length version of the above function checking length of the + * name at compile time. + */ +template<size_t LEN> +inline void +NS_SetThreadName(nsIThread* aThread, const char (&aName)[LEN]) +{ + static_assert(LEN <= 16, + "Thread name must be no more than 16 characters"); + NS_SetThreadName(aThread, nsDependentCString(aName)); +} + +/** + * Create a new thread, and optionally provide an initial event for the thread. + * + * @param aResult + * The resulting nsIThread object. + * @param aInitialEvent + * The initial event to run on this thread. This parameter may be null. + * @param aStackSize + * The size in bytes to reserve for the thread's stack. + * + * @returns NS_ERROR_INVALID_ARG + * Indicates that the given name is not unique. + */ +extern nsresult +NS_NewThread(nsIThread** aResult, + nsIRunnable* aInitialEvent = nullptr, + uint32_t aStackSize = nsIThreadManager::DEFAULT_STACK_SIZE); + +/** + * Creates a named thread, otherwise the same as NS_NewThread + */ +template<size_t LEN> +inline nsresult +NS_NewNamedThread(const char (&aName)[LEN], + nsIThread** aResult, + nsIRunnable* aInitialEvent = nullptr, + uint32_t aStackSize = nsIThreadManager::DEFAULT_STACK_SIZE) +{ + // Hold a ref while dispatching the initial event to match NS_NewThread() + nsCOMPtr<nsIThread> thread; + nsresult rv = NS_NewThread(getter_AddRefs(thread), nullptr, aStackSize); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + NS_SetThreadName<LEN>(thread, aName); + if (aInitialEvent) { + rv = thread->Dispatch(aInitialEvent, NS_DISPATCH_NORMAL); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Initial event dispatch failed"); + } + + *aResult = nullptr; + thread.swap(*aResult); + return rv; +} + +/** + * Get a reference to the current thread. + * + * @param aResult + * The resulting nsIThread object. + */ +extern nsresult NS_GetCurrentThread(nsIThread** aResult); + +/** + * Dispatch the given event to the current thread. + * + * @param aEvent + * The event to dispatch. + * + * @returns NS_ERROR_INVALID_ARG + * If event is null. + */ +extern nsresult NS_DispatchToCurrentThread(nsIRunnable* aEvent); +extern nsresult +NS_DispatchToCurrentThread(already_AddRefed<nsIRunnable>&& aEvent); + +/** + * Dispatch the given event to the main thread. + * + * @param aEvent + * The event to dispatch. + * @param aDispatchFlags + * The flags to pass to the main thread's dispatch method. + * + * @returns NS_ERROR_INVALID_ARG + * If event is null. + */ +extern nsresult +NS_DispatchToMainThread(nsIRunnable* aEvent, + uint32_t aDispatchFlags = NS_DISPATCH_NORMAL); +extern nsresult +NS_DispatchToMainThread(already_AddRefed<nsIRunnable>&& aEvent, + uint32_t aDispatchFlags = NS_DISPATCH_NORMAL); + +extern nsresult +NS_DelayedDispatchToCurrentThread( + already_AddRefed<nsIRunnable>&& aEvent, uint32_t aDelayMs); + +extern nsresult +NS_IdleDispatchToCurrentThread(already_AddRefed<nsIRunnable>&& aEvent); + +#ifndef XPCOM_GLUE_AVOID_NSPR +/** + * Process all pending events for the given thread before returning. This + * method simply calls ProcessNextEvent on the thread while HasPendingEvents + * continues to return true and the time spent in NS_ProcessPendingEvents + * does not exceed the given timeout value. + * + * @param aThread + * The thread object for which to process pending events. If null, then + * events will be processed for the current thread. + * @param aTimeout + * The maximum number of milliseconds to spend processing pending events. + * Events are not pre-empted to honor this timeout. Rather, the timeout + * value is simply used to determine whether or not to process another event. + * Pass PR_INTERVAL_NO_TIMEOUT to specify no timeout. + */ +extern nsresult +NS_ProcessPendingEvents(nsIThread* aThread, + PRIntervalTime aTimeout = PR_INTERVAL_NO_TIMEOUT); +#endif + +/** + * Shortcut for nsIThread::HasPendingEvents. + * + * It is an error to call this function when the given thread is not the + * current thread. This function will return false if called from some + * other thread. + * + * @param aThread + * The current thread or null. + * + * @returns + * A boolean value that if "true" indicates that there are pending events + * in the current thread's event queue. + */ +extern bool NS_HasPendingEvents(nsIThread* aThread = nullptr); + +/** + * Shortcut for nsIThread::ProcessNextEvent. + * + * It is an error to call this function when the given thread is not the + * current thread. This function will simply return false if called + * from some other thread. + * + * @param aThread + * The current thread or null. + * @param aMayWait + * A boolean parameter that if "true" indicates that the method may block + * the calling thread to wait for a pending event. + * + * @returns + * A boolean value that if "true" indicates that an event from the current + * thread's event queue was processed. + */ +extern bool NS_ProcessNextEvent(nsIThread* aThread = nullptr, + bool aMayWait = true); + +//----------------------------------------------------------------------------- +// Helpers that work with nsCOMPtr: + +inline already_AddRefed<nsIThread> +do_GetCurrentThread() +{ + nsIThread* thread = nullptr; + NS_GetCurrentThread(&thread); + return already_AddRefed<nsIThread>(thread); +} + +inline already_AddRefed<nsIThread> +do_GetMainThread() +{ + nsIThread* thread = nullptr; + NS_GetMainThread(&thread); + return already_AddRefed<nsIThread>(thread); +} + +//----------------------------------------------------------------------------- + +#ifdef MOZILLA_INTERNAL_API +// Fast access to the current thread. Do not release the returned pointer! If +// you want to use this pointer from some other thread, then you will need to +// AddRef it. Otherwise, you should only consider this pointer valid from code +// running on the current thread. +extern nsIThread* NS_GetCurrentThread(); +#endif + +//----------------------------------------------------------------------------- + +#ifndef XPCOM_GLUE_AVOID_NSPR + +namespace mozilla { + +// This class is designed to be subclassed. +class IdlePeriod : public nsIIdlePeriod +{ +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIIDLEPERIOD + + IdlePeriod() {} + +protected: + virtual ~IdlePeriod() {} +private: + IdlePeriod(const IdlePeriod&) = delete; + IdlePeriod& operator=(const IdlePeriod&) = delete; + IdlePeriod& operator=(const IdlePeriod&&) = delete; +}; + +// This class is designed to be subclassed. +class Runnable : public nsIRunnable +{ +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIRUNNABLE + + Runnable() {} + +protected: + virtual ~Runnable() {} +private: + Runnable(const Runnable&) = delete; + Runnable& operator=(const Runnable&) = delete; + Runnable& operator=(const Runnable&&) = delete; +}; + +// This class is designed to be subclassed. +class CancelableRunnable : public Runnable, + public nsICancelableRunnable +{ +public: + NS_DECL_ISUPPORTS_INHERITED + // nsICancelableRunnable + virtual nsresult Cancel() override; + + CancelableRunnable() {} + +protected: + virtual ~CancelableRunnable() {} +private: + CancelableRunnable(const CancelableRunnable&) = delete; + CancelableRunnable& operator=(const CancelableRunnable&) = delete; + CancelableRunnable& operator=(const CancelableRunnable&&) = delete; +}; + +// This class is designed to be subclassed. +class IncrementalRunnable : public CancelableRunnable, + public nsIIncrementalRunnable +{ +public: + NS_DECL_ISUPPORTS_INHERITED + // nsIIncrementalRunnable + virtual void SetDeadline(TimeStamp aDeadline) override; + + IncrementalRunnable() {} + +protected: + virtual ~IncrementalRunnable() {} +private: + IncrementalRunnable(const IncrementalRunnable&) = delete; + IncrementalRunnable& operator=(const IncrementalRunnable&) = delete; + IncrementalRunnable& operator=(const IncrementalRunnable&&) = delete; +}; + +namespace detail { + +// An event that can be used to call a C++11 functions or function objects, +// including lambdas. The function must have no required arguments, and must +// return void. +template<typename StoredFunction> +class RunnableFunction : public Runnable +{ +public: + template <typename F> + explicit RunnableFunction(F&& aFunction) + : mFunction(Forward<F>(aFunction)) + { } + + NS_IMETHOD Run() override { + static_assert(IsVoid<decltype(mFunction())>::value, + "The lambda must return void!"); + mFunction(); + return NS_OK; + } +private: + StoredFunction mFunction; +}; + +} // namespace detail + +} // namespace mozilla + +template<typename Function> +already_AddRefed<mozilla::Runnable> +NS_NewRunnableFunction(Function&& aFunction) +{ + return do_AddRef(new mozilla::detail::RunnableFunction + // Make sure we store a non-reference in nsRunnableFunction. + <typename mozilla::RemoveReference<Function>::Type> + // But still forward aFunction to move if possible. + (mozilla::Forward<Function>(aFunction))); +} + +// An event that can be used to call a method on a class. The class type must +// support reference counting. This event supports Revoke for use +// with nsRevocableEventPtr. +template<class ClassType, + typename ReturnType = void, + bool Owning = true, + bool Cancelable = false> +class nsRunnableMethod : public mozilla::Conditional<!Cancelable, + mozilla::Runnable, + mozilla::CancelableRunnable>::Type +{ +public: + virtual void Revoke() = 0; + + // These ReturnTypeEnforcer classes set up a blacklist for return types that + // we know are not safe. The default ReturnTypeEnforcer compiles just fine but + // already_AddRefed will not. + template<typename OtherReturnType> + class ReturnTypeEnforcer + { + public: + typedef int ReturnTypeIsSafe; + }; + + template<class T> + class ReturnTypeEnforcer<already_AddRefed<T>> + { + // No ReturnTypeIsSafe makes this illegal! + }; + + // Make sure this return type is safe. + typedef typename ReturnTypeEnforcer<ReturnType>::ReturnTypeIsSafe check; +}; + +template<class ClassType, bool Owning> +struct nsRunnableMethodReceiver +{ + RefPtr<ClassType> mObj; + explicit nsRunnableMethodReceiver(ClassType* aObj) : mObj(aObj) {} + ~nsRunnableMethodReceiver() { Revoke(); } + ClassType* Get() const { return mObj.get(); } + void Revoke() { mObj = nullptr; } +}; + +template<class ClassType> +struct nsRunnableMethodReceiver<ClassType, false> +{ + ClassType* MOZ_NON_OWNING_REF mObj; + explicit nsRunnableMethodReceiver(ClassType* aObj) : mObj(aObj) {} + ClassType* Get() const { return mObj; } + void Revoke() { mObj = nullptr; } +}; + +template<typename Method, bool Owning, bool Cancelable> struct nsRunnableMethodTraits; + +template<class C, typename R, bool Owning, bool Cancelable, typename... As> +struct nsRunnableMethodTraits<R(C::*)(As...), Owning, Cancelable> +{ + typedef C class_type; + typedef R return_type; + typedef nsRunnableMethod<C, R, Owning, Cancelable> base_type; + static const bool can_cancel = Cancelable; +}; + +template<class C, typename R, bool Owning, bool Cancelable, typename... As> +struct nsRunnableMethodTraits<R(C::*)(As...) const, Owning, Cancelable> +{ + typedef const C class_type; + typedef R return_type; + typedef nsRunnableMethod<C, R, Owning, Cancelable> base_type; + static const bool can_cancel = Cancelable; +}; + +#ifdef NS_HAVE_STDCALL +template<class C, typename R, bool Owning, bool Cancelable, typename... As> +struct nsRunnableMethodTraits<R(__stdcall C::*)(As...), Owning, Cancelable> +{ + typedef C class_type; + typedef R return_type; + typedef nsRunnableMethod<C, R, Owning, Cancelable> base_type; + static const bool can_cancel = Cancelable; +}; + +template<class C, typename R, bool Owning, bool Cancelable> +struct nsRunnableMethodTraits<R(NS_STDCALL C::*)(), Owning, Cancelable> +{ + typedef C class_type; + typedef R return_type; + typedef nsRunnableMethod<C, R, Owning, Cancelable> base_type; + static const bool can_cancel = Cancelable; +}; +template<class C, typename R, bool Owning, bool Cancelable, typename... As> +struct nsRunnableMethodTraits<R(__stdcall C::*)(As...) const, Owning, Cancelable> +{ + typedef const C class_type; + typedef R return_type; + typedef nsRunnableMethod<C, R, Owning, Cancelable> base_type; + static const bool can_cancel = Cancelable; +}; + +template<class C, typename R, bool Owning, bool Cancelable> +struct nsRunnableMethodTraits<R(NS_STDCALL C::*)() const, Owning, Cancelable> +{ + typedef const C class_type; + typedef R return_type; + typedef nsRunnableMethod<C, R, Owning, Cancelable> base_type; + static const bool can_cancel = Cancelable; +}; +#endif + + +// IsParameterStorageClass<T>::value is true if T is a parameter-storage class +// that will be recognized by NS_New[NonOwning]RunnableMethodWithArg[s] to +// force a specific storage&passing strategy (instead of inferring one, +// see ParameterStorage). +// When creating a new storage class, add a specialization for it to be +// recognized. +template<typename T> +struct IsParameterStorageClass : public mozilla::FalseType {}; + +// StoreXPassByY structs used to inform nsRunnableMethodArguments how to +// store arguments, and how to pass them to the target method. + +template<typename T> +struct StoreCopyPassByValue +{ + typedef T stored_type; + typedef T passed_type; + stored_type m; + template <typename A> + MOZ_IMPLICIT StoreCopyPassByValue(A&& a) : m(mozilla::Forward<A>(a)) {} + passed_type PassAsParameter() { return m; } +}; +template<typename S> +struct IsParameterStorageClass<StoreCopyPassByValue<S>> + : public mozilla::TrueType {}; + +template<typename T> +struct StoreCopyPassByConstLRef +{ + typedef T stored_type; + typedef const T& passed_type; + stored_type m; + template <typename A> + MOZ_IMPLICIT StoreCopyPassByConstLRef(A&& a) : m(mozilla::Forward<A>(a)) {} + passed_type PassAsParameter() { return m; } +}; +template<typename S> +struct IsParameterStorageClass<StoreCopyPassByConstLRef<S>> + : public mozilla::TrueType {}; + +template<typename T> +struct StoreCopyPassByLRef +{ + typedef T stored_type; + typedef T& passed_type; + stored_type m; + template <typename A> + MOZ_IMPLICIT StoreCopyPassByLRef(A&& a) : m(mozilla::Forward<A>(a)) {} + passed_type PassAsParameter() { return m; } +}; +template<typename S> +struct IsParameterStorageClass<StoreCopyPassByLRef<S>> + : public mozilla::TrueType {}; + +template<typename T> +struct StoreCopyPassByRRef +{ + typedef T stored_type; + typedef T&& passed_type; + stored_type m; + template <typename A> + MOZ_IMPLICIT StoreCopyPassByRRef(A&& a) : m(mozilla::Forward<A>(a)) {} + passed_type PassAsParameter() { return mozilla::Move(m); } +}; +template<typename S> +struct IsParameterStorageClass<StoreCopyPassByRRef<S>> + : public mozilla::TrueType {}; + +template<typename T> +struct StoreRefPassByLRef +{ + typedef T& stored_type; + typedef T& passed_type; + stored_type m; + template <typename A> + MOZ_IMPLICIT StoreRefPassByLRef(A& a) : m(a) {} + passed_type PassAsParameter() { return m; } +}; +template<typename S> +struct IsParameterStorageClass<StoreRefPassByLRef<S>> + : public mozilla::TrueType {}; + +template<typename T> +struct StoreConstRefPassByConstLRef +{ + typedef const T& stored_type; + typedef const T& passed_type; + stored_type m; + template <typename A> + MOZ_IMPLICIT StoreConstRefPassByConstLRef(const A& a) : m(a) {} + passed_type PassAsParameter() { return m; } +}; +template<typename S> +struct IsParameterStorageClass<StoreConstRefPassByConstLRef<S>> + : public mozilla::TrueType {}; + +template<typename T> +struct StorensRefPtrPassByPtr +{ + typedef RefPtr<T> stored_type; + typedef T* passed_type; + stored_type m; + template <typename A> + MOZ_IMPLICIT StorensRefPtrPassByPtr(A&& a) : m(mozilla::Forward<A>(a)) {} + passed_type PassAsParameter() { return m.get(); } +}; +template<typename S> +struct IsParameterStorageClass<StorensRefPtrPassByPtr<S>> + : public mozilla::TrueType {}; + +template<typename T> +struct StorePtrPassByPtr +{ + typedef T* stored_type; + typedef T* passed_type; + stored_type m; + template <typename A> + MOZ_IMPLICIT StorePtrPassByPtr(A a) : m(a) {} + passed_type PassAsParameter() { return m; } +}; +template<typename S> +struct IsParameterStorageClass<StorePtrPassByPtr<S>> + : public mozilla::TrueType {}; + +template<typename T> +struct StoreConstPtrPassByConstPtr +{ + typedef const T* stored_type; + typedef const T* passed_type; + stored_type m; + template <typename A> + MOZ_IMPLICIT StoreConstPtrPassByConstPtr(A a) : m(a) {} + passed_type PassAsParameter() { return m; } +}; +template<typename S> +struct IsParameterStorageClass<StoreConstPtrPassByConstPtr<S>> + : public mozilla::TrueType {}; + +template<typename T> +struct StoreCopyPassByConstPtr +{ + typedef T stored_type; + typedef const T* passed_type; + stored_type m; + template <typename A> + MOZ_IMPLICIT StoreCopyPassByConstPtr(A&& a) : m(mozilla::Forward<A>(a)) {} + passed_type PassAsParameter() { return &m; } +}; +template<typename S> +struct IsParameterStorageClass<StoreCopyPassByConstPtr<S>> + : public mozilla::TrueType {}; + +template<typename T> +struct StoreCopyPassByPtr +{ + typedef T stored_type; + typedef T* passed_type; + stored_type m; + template <typename A> + MOZ_IMPLICIT StoreCopyPassByPtr(A&& a) : m(mozilla::Forward<A>(a)) {} + passed_type PassAsParameter() { return &m; } +}; +template<typename S> +struct IsParameterStorageClass<StoreCopyPassByPtr<S>> + : public mozilla::TrueType {}; + +namespace detail { + +template<typename TWithoutPointer> +struct NonnsISupportsPointerStorageClass + : mozilla::Conditional<mozilla::IsConst<TWithoutPointer>::value, + StoreConstPtrPassByConstPtr< + typename mozilla::RemoveConst<TWithoutPointer>::Type>, + StorePtrPassByPtr<TWithoutPointer>> +{}; + +template<typename> +struct SFINAE1True : mozilla::TrueType +{}; + +template<class T> +static auto HasRefCountMethodsTest(int) + -> SFINAE1True<decltype(mozilla::DeclVal<T>().AddRef(), + mozilla::DeclVal<T>().Release())>; +template<class> +static auto HasRefCountMethodsTest(long) -> mozilla::FalseType; + +template<class T> +struct HasRefCountMethods : decltype(HasRefCountMethodsTest<T>(0)) +{}; + +template<typename T> +struct IsRefcountedSmartPointer : public mozilla::FalseType +{}; + +template<typename T> +struct IsRefcountedSmartPointer<RefPtr<T>> : public mozilla::TrueType +{}; + +template<typename T> +struct IsRefcountedSmartPointer<nsCOMPtr<T>> : public mozilla::TrueType +{}; + +template<typename T> +struct StripSmartPointer +{ + typedef void Type; +}; + +template<typename T> +struct StripSmartPointer<RefPtr<T>> +{ + typedef T Type; +}; + +template<typename T> +struct StripSmartPointer<nsCOMPtr<T>> +{ + typedef T Type; +}; + +template<typename TWithoutPointer> +struct PointerStorageClass + : mozilla::Conditional<HasRefCountMethods<TWithoutPointer>::value, + StorensRefPtrPassByPtr<TWithoutPointer>, + typename NonnsISupportsPointerStorageClass< + TWithoutPointer + >::Type> +{}; + +template<typename TWithoutRef> +struct LValueReferenceStorageClass + : mozilla::Conditional<mozilla::IsConst<TWithoutRef>::value, + StoreConstRefPassByConstLRef< + typename mozilla::RemoveConst<TWithoutRef>::Type>, + StoreRefPassByLRef<TWithoutRef>> +{}; + +template<typename T> +struct SmartPointerStorageClass + : mozilla::Conditional<IsRefcountedSmartPointer<T>::value, + StorensRefPtrPassByPtr< + typename StripSmartPointer<T>::Type>, + StoreCopyPassByValue<T>> +{}; + +template<typename T> +struct NonLValueReferenceStorageClass + : mozilla::Conditional<mozilla::IsRvalueReference<T>::value, + StoreCopyPassByRRef< + typename mozilla::RemoveReference<T>::Type>, + typename SmartPointerStorageClass<T>::Type> +{}; + +template<typename T> +struct NonPointerStorageClass + : mozilla::Conditional<mozilla::IsLvalueReference<T>::value, + typename LValueReferenceStorageClass< + typename mozilla::RemoveReference<T>::Type + >::Type, + typename NonLValueReferenceStorageClass<T>::Type> +{}; + +template<typename T> +struct NonParameterStorageClass + : mozilla::Conditional<mozilla::IsPointer<T>::value, + typename PointerStorageClass< + typename mozilla::RemovePointer<T>::Type + >::Type, + typename NonPointerStorageClass<T>::Type> +{}; + +// Choose storage&passing strategy based on preferred storage type: +// - If IsParameterStorageClass<T>::value is true, use as-is. +// - RC* -> StorensRefPtrPassByPtr<RC> : Store RefPtr<RC>, pass RC* +// ^^ RC quacks like a ref-counted type (i.e., has AddRef and Release methods) +// - const T* -> StoreConstPtrPassByConstPtr<T> : Store const T*, pass const T* +// - T* -> StorePtrPassByPtr<T> : Store T*, pass T*. +// - const T& -> StoreConstRefPassByConstLRef<T>: Store const T&, pass const T&. +// - T& -> StoreRefPassByLRef<T> : Store T&, pass T&. +// - T&& -> StoreCopyPassByRRef<T> : Store T, pass Move(T). +// - RefPtr<T>, nsCOMPtr<T> +// -> StorensRefPtrPassByPtr<T> : Store RefPtr<T>, pass T* +// - Other T -> StoreCopyPassByValue<T> : Store T, pass T. +// Other available explicit options: +// - StoreCopyPassByConstLRef<T> : Store T, pass const T&. +// - StoreCopyPassByLRef<T> : Store T, pass T& (of copy!) +// - StoreCopyPassByConstPtr<T> : Store T, pass const T* +// - StoreCopyPassByPtr<T> : Store T, pass T* (of copy!) +// Or create your own class with PassAsParameter() method, optional +// clean-up in destructor, and with associated IsParameterStorageClass<>. +template<typename T> +struct ParameterStorage + : mozilla::Conditional<IsParameterStorageClass<T>::value, + T, + typename NonParameterStorageClass<T>::Type> +{}; + +} /* namespace detail */ + +namespace mozilla { + +namespace detail { + +// struct used to store arguments and later apply them to a method. +template <typename... Ts> +struct RunnableMethodArguments final +{ + Tuple<typename ::detail::ParameterStorage<Ts>::Type...> mArguments; + template <typename... As> + explicit RunnableMethodArguments(As&&... aArguments) + : mArguments(Forward<As>(aArguments)...) + {} + template<typename C, typename M, typename... Args, size_t... Indices> + static auto + applyImpl(C* o, M m, Tuple<Args...>& args, IndexSequence<Indices...>) + -> decltype(((*o).*m)(Get<Indices>(args).PassAsParameter()...)) + { + return ((*o).*m)(Get<Indices>(args).PassAsParameter()...); + } + template<class C, typename M> auto apply(C* o, M m) + -> decltype(applyImpl(o, m, mArguments, + typename IndexSequenceFor<Ts...>::Type())) + { + return applyImpl(o, m, mArguments, + typename IndexSequenceFor<Ts...>::Type()); + } +}; + +template<typename Method, bool Owning, bool Cancelable, typename... Storages> +class RunnableMethodImpl final + : public ::nsRunnableMethodTraits<Method, Owning, Cancelable>::base_type +{ + typedef typename ::nsRunnableMethodTraits<Method, Owning, Cancelable>::class_type + ClassType; + ::nsRunnableMethodReceiver<ClassType, Owning> mReceiver; + Method mMethod; + RunnableMethodArguments<Storages...> mArgs; +private: + virtual ~RunnableMethodImpl() { Revoke(); }; +public: + template<typename... Args> + explicit RunnableMethodImpl(ClassType* aObj, Method aMethod, + Args&&... aArgs) + : mReceiver(aObj) + , mMethod(aMethod) + , mArgs(Forward<Args>(aArgs)...) + { + static_assert(sizeof...(Storages) == sizeof...(Args), "Storages and Args should have equal sizes"); + } + NS_IMETHOD Run() + { + if (MOZ_LIKELY(mReceiver.Get())) { + mArgs.apply(mReceiver.Get(), mMethod); + } + return NS_OK; + } + nsresult Cancel() { + static_assert(Cancelable, "Don't use me!"); + Revoke(); + return NS_OK; + } + void Revoke() { mReceiver.Revoke(); } +}; + +} // namespace detail + +// Use this template function like so: +// +// nsCOMPtr<nsIRunnable> event = +// mozilla::NewRunnableMethod(myObject, &MyClass::HandleEvent); +// NS_DispatchToCurrentThread(event); +// +// Statically enforced constraints: +// - myObject must be of (or implicitly convertible to) type MyClass +// - MyClass must define AddRef and Release methods +// + +template<typename PtrType, typename Method> +already_AddRefed<typename ::nsRunnableMethodTraits<Method, true, false>::base_type> +NewRunnableMethod(PtrType aPtr, Method aMethod) +{ + return do_AddRef(new detail::RunnableMethodImpl<Method, true, false>(aPtr, aMethod)); +} + +template<typename PtrType, typename Method> +already_AddRefed<typename ::nsRunnableMethodTraits<Method, true, true>::base_type> +NewCancelableRunnableMethod(PtrType aPtr, Method aMethod) +{ + return do_AddRef(new detail::RunnableMethodImpl<Method, true, true>(aPtr, aMethod)); +} + +template<typename PtrType, typename Method> +already_AddRefed<typename ::nsRunnableMethodTraits<Method, false, false>::base_type> +NewNonOwningRunnableMethod(PtrType&& aPtr, Method aMethod) +{ + return do_AddRef(new detail::RunnableMethodImpl<Method, false, false>(aPtr, aMethod)); +} + +template<typename PtrType, typename Method> +already_AddRefed<typename ::nsRunnableMethodTraits<Method, false, true>::base_type> +NewNonOwningCancelableRunnableMethod(PtrType&& aPtr, Method aMethod) +{ + return do_AddRef(new detail::RunnableMethodImpl<Method, false, true>(aPtr, aMethod)); +} + +// Similar to NewRunnableMethod. Call like so: +// nsCOMPtr<nsIRunnable> event = +// NewRunnableMethod<Types,...>(myObject, &MyClass::HandleEvent, myArg1,...); +// 'Types' are the stored type for each argument, see ParameterStorage for details. +template<typename... Storages, typename Method, typename PtrType, typename... Args> +already_AddRefed<typename ::nsRunnableMethodTraits<Method, true, false>::base_type> +NewRunnableMethod(PtrType&& aPtr, Method aMethod, Args&&... aArgs) +{ + static_assert(sizeof...(Storages) == sizeof...(Args), + "<Storages...> size should be equal to number of arguments"); + return do_AddRef(new detail::RunnableMethodImpl<Method, true, false, Storages...>( + aPtr, aMethod, mozilla::Forward<Args>(aArgs)...)); +} + +template<typename... Storages, typename Method, typename PtrType, typename... Args> +already_AddRefed<typename ::nsRunnableMethodTraits<Method, false, false>::base_type> +NewNonOwningRunnableMethod(PtrType&& aPtr, Method aMethod, Args&&... aArgs) +{ + static_assert(sizeof...(Storages) == sizeof...(Args), + "<Storages...> size should be equal to number of arguments"); + return do_AddRef(new detail::RunnableMethodImpl<Method, false, false, Storages...>( + aPtr, aMethod, mozilla::Forward<Args>(aArgs)...)); +} + +template<typename... Storages, typename Method, typename PtrType, typename... Args> +already_AddRefed<typename ::nsRunnableMethodTraits<Method, true, true>::base_type> +NewCancelableRunnableMethod(PtrType&& aPtr, Method aMethod, Args&&... aArgs) +{ + static_assert(sizeof...(Storages) == sizeof...(Args), + "<Storages...> size should be equal to number of arguments"); + return do_AddRef(new detail::RunnableMethodImpl<Method, true, true, Storages...>( + aPtr, aMethod, mozilla::Forward<Args>(aArgs)...)); +} + +template<typename... Storages, typename Method, typename PtrType, typename... Args> +already_AddRefed<typename ::nsRunnableMethodTraits<Method, false, true>::base_type> +NewNonOwningCancelableRunnableMethod(PtrType&& aPtr, Method aMethod, + Args&&... aArgs) +{ + static_assert(sizeof...(Storages) == sizeof...(Args), + "<Storages...> size should be equal to number of arguments"); + return do_AddRef(new detail::RunnableMethodImpl<Method, false, true, Storages...>( + aPtr, aMethod, mozilla::Forward<Args>(aArgs)...)); +} + +} // namespace mozilla + +#endif // XPCOM_GLUE_AVOID_NSPR + +// This class is designed to be used when you have an event class E that has a +// pointer back to resource class R. If R goes away while E is still pending, +// then it is important to "revoke" E so that it does not try use R after R has +// been destroyed. nsRevocableEventPtr makes it easy for R to manage such +// situations: +// +// class R; +// +// class E : public mozilla::Runnable { +// public: +// void Revoke() { +// mResource = nullptr; +// } +// private: +// R *mResource; +// }; +// +// class R { +// public: +// void EventHandled() { +// mEvent.Forget(); +// } +// private: +// nsRevocableEventPtr<E> mEvent; +// }; +// +// void R::PostEvent() { +// // Make sure any pending event is revoked. +// mEvent->Revoke(); +// +// nsCOMPtr<nsIRunnable> event = new E(); +// if (NS_SUCCEEDED(NS_DispatchToCurrentThread(event))) { +// // Keep pointer to event so we can revoke it. +// mEvent = event; +// } +// } +// +// NS_IMETHODIMP E::Run() { +// if (!mResource) +// return NS_OK; +// ... +// mResource->EventHandled(); +// return NS_OK; +// } +// +template<class T> +class nsRevocableEventPtr +{ +public: + nsRevocableEventPtr() : mEvent(nullptr) {} + ~nsRevocableEventPtr() { Revoke(); } + + const nsRevocableEventPtr& operator=(T* aEvent) + { + if (mEvent != aEvent) { + Revoke(); + mEvent = aEvent; + } + return *this; + } + + const nsRevocableEventPtr& operator=(already_AddRefed<T> aEvent) + { + RefPtr<T> event = aEvent; + if (mEvent != event) { + Revoke(); + mEvent = event.forget(); + } + return *this; + } + + void Revoke() + { + if (mEvent) { + mEvent->Revoke(); + mEvent = nullptr; + } + } + + void Forget() { mEvent = nullptr; } + bool IsPending() { return mEvent != nullptr; } + T* get() { return mEvent; } + +private: + // Not implemented + nsRevocableEventPtr(const nsRevocableEventPtr&); + nsRevocableEventPtr& operator=(const nsRevocableEventPtr&); + + RefPtr<T> mEvent; +}; + +/** + * A simple helper to suffix thread pool name + * with incremental numbers. + */ +class nsThreadPoolNaming +{ +public: + nsThreadPoolNaming() : mCounter(0) {} + + /** + * Creates and sets next thread name as "<aPoolName> #<n>" + * on the specified thread. If no thread is specified (aThread + * is null) then the name is synchronously set on the current thread. + */ + void SetThreadPoolName(const nsACString& aPoolName, + nsIThread* aThread = nullptr); + +private: + mozilla::Atomic<uint32_t> mCounter; + + nsThreadPoolNaming(const nsThreadPoolNaming&) = delete; + void operator=(const nsThreadPoolNaming&) = delete; +}; + +/** + * Thread priority in most operating systems affect scheduling, not IO. This + * helper is used to set the current thread to low IO priority for the lifetime + * of the created object. You can only use this low priority IO setting within + * the context of the current thread. + */ +class MOZ_STACK_CLASS nsAutoLowPriorityIO +{ +public: + nsAutoLowPriorityIO(); + ~nsAutoLowPriorityIO(); + +private: + bool lowIOPrioritySet; +#if defined(XP_MACOSX) + int oldPriority; +#endif +}; + +void +NS_SetMainThread(); + +#endif // nsThreadUtils_h__ diff --git a/xpcom/glue/nsVersionComparator.cpp b/xpcom/glue/nsVersionComparator.cpp new file mode 100644 index 0000000000..6be32b9744 --- /dev/null +++ b/xpcom/glue/nsVersionComparator.cpp @@ -0,0 +1,379 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsVersionComparator.h" + +#include <stdlib.h> +#include <string.h> +#include <stdint.h> +#if defined(XP_WIN) && !defined(UPDATER_NO_STRING_GLUE_STL) +#include <wchar.h> +#include "nsStringGlue.h" +#endif + +struct VersionPart +{ + int32_t numA; + + const char* strB; // NOT null-terminated, can be a null pointer + uint32_t strBlen; + + int32_t numC; + + char* extraD; // null-terminated +}; + +#ifdef XP_WIN +struct VersionPartW +{ + int32_t numA; + + wchar_t* strB; // NOT null-terminated, can be a null pointer + uint32_t strBlen; + + int32_t numC; + + wchar_t* extraD; // null-terminated + +}; +#endif + +/** + * Parse a version part into a number and "extra text". + * + * @returns A pointer to the next versionpart, or null if none. + */ +static char* +ParseVP(char* aPart, VersionPart& aResult) +{ + char* dot; + + aResult.numA = 0; + aResult.strB = nullptr; + aResult.strBlen = 0; + aResult.numC = 0; + aResult.extraD = nullptr; + + if (!aPart) { + return aPart; + } + + dot = strchr(aPart, '.'); + if (dot) { + *dot = '\0'; + } + + if (aPart[0] == '*' && aPart[1] == '\0') { + aResult.numA = INT32_MAX; + aResult.strB = ""; + } else { + aResult.numA = strtol(aPart, const_cast<char**>(&aResult.strB), 10); + } + + if (!*aResult.strB) { + aResult.strB = nullptr; + aResult.strBlen = 0; + } else { + if (aResult.strB[0] == '+') { + static const char kPre[] = "pre"; + + ++aResult.numA; + aResult.strB = kPre; + aResult.strBlen = sizeof(kPre) - 1; + } else { + const char* numstart = strpbrk(aResult.strB, "0123456789+-"); + if (!numstart) { + aResult.strBlen = strlen(aResult.strB); + } else { + aResult.strBlen = numstart - aResult.strB; + + aResult.numC = strtol(numstart, &aResult.extraD, 10); + if (!*aResult.extraD) { + aResult.extraD = nullptr; + } + } + } + } + + if (dot) { + ++dot; + + if (!*dot) { + dot = nullptr; + } + } + + return dot; +} + + +/** + * Parse a version part into a number and "extra text". + * + * @returns A pointer to the next versionpart, or null if none. + */ +#ifdef XP_WIN +static wchar_t* +ParseVP(wchar_t* aPart, VersionPartW& aResult) +{ + + wchar_t* dot; + + aResult.numA = 0; + aResult.strB = nullptr; + aResult.strBlen = 0; + aResult.numC = 0; + aResult.extraD = nullptr; + + if (!aPart) { + return aPart; + } + + dot = wcschr(aPart, '.'); + if (dot) { + *dot = '\0'; + } + + if (aPart[0] == '*' && aPart[1] == '\0') { + aResult.numA = INT32_MAX; + aResult.strB = L""; + } else { + aResult.numA = wcstol(aPart, const_cast<wchar_t**>(&aResult.strB), 10); + } + + if (!*aResult.strB) { + aResult.strB = nullptr; + aResult.strBlen = 0; + } else { + if (aResult.strB[0] == '+') { + static wchar_t kPre[] = L"pre"; + + ++aResult.numA; + aResult.strB = kPre; + aResult.strBlen = sizeof(kPre) - 1; + } else { + const wchar_t* numstart = wcspbrk(aResult.strB, L"0123456789+-"); + if (!numstart) { + aResult.strBlen = wcslen(aResult.strB); + } else { + aResult.strBlen = numstart - aResult.strB; + + aResult.numC = wcstol(numstart, &aResult.extraD, 10); + if (!*aResult.extraD) { + aResult.extraD = nullptr; + } + } + } + } + + if (dot) { + ++dot; + + if (!*dot) { + dot = nullptr; + } + } + + return dot; +} +#endif + +// compare two null-terminated strings, which may be null pointers +static int32_t +ns_strcmp(const char* aStr1, const char* aStr2) +{ + // any string is *before* no string + if (!aStr1) { + return aStr2 != 0; + } + + if (!aStr2) { + return -1; + } + + return strcmp(aStr1, aStr2); +} + +// compare two length-specified string, which may be null pointers +static int32_t +ns_strnncmp(const char* aStr1, uint32_t aLen1, + const char* aStr2, uint32_t aLen2) +{ + // any string is *before* no string + if (!aStr1) { + return aStr2 != 0; + } + + if (!aStr2) { + return -1; + } + + for (; aLen1 && aLen2; --aLen1, --aLen2, ++aStr1, ++aStr2) { + if (*aStr1 < *aStr2) { + return -1; + } + + if (*aStr1 > *aStr2) { + return 1; + } + } + + if (aLen1 == 0) { + return aLen2 == 0 ? 0 : -1; + } + + return 1; +} + +// compare two int32_t +static int32_t +ns_cmp(int32_t aNum1, int32_t aNum2) +{ + if (aNum1 < aNum2) { + return -1; + } + + return aNum1 != aNum2; +} + +/** + * Compares two VersionParts + */ +static int32_t +CompareVP(VersionPart& aVer1, VersionPart& aVer2) +{ + int32_t r = ns_cmp(aVer1.numA, aVer2.numA); + if (r) { + return r; + } + + r = ns_strnncmp(aVer1.strB, aVer1.strBlen, aVer2.strB, aVer2.strBlen); + if (r) { + return r; + } + + r = ns_cmp(aVer1.numC, aVer2.numC); + if (r) { + return r; + } + + return ns_strcmp(aVer1.extraD, aVer2.extraD); +} + +/** + * Compares two VersionParts + */ +#ifdef XP_WIN +static int32_t +CompareVP(VersionPartW& aVer1, VersionPartW& aVer2) +{ + int32_t r = ns_cmp(aVer1.numA, aVer2.numA); + if (r) { + return r; + } + + r = wcsncmp(aVer1.strB, aVer2.strB, XPCOM_MIN(aVer1.strBlen, aVer2.strBlen)); + if (r) { + return r; + } + + r = ns_cmp(aVer1.numC, aVer2.numC); + if (r) { + return r; + } + + if (!aVer1.extraD) { + return aVer2.extraD != 0; + } + + if (!aVer2.extraD) { + return -1; + } + + return wcscmp(aVer1.extraD, aVer2.extraD); +} +#endif + +namespace mozilla { + +#ifdef XP_WIN +int32_t +CompareVersions(const char16_t* aStrA, const char16_t* aStrB) +{ + wchar_t* A2 = wcsdup(char16ptr_t(aStrA)); + if (!A2) { + return 1; + } + + wchar_t* B2 = wcsdup(char16ptr_t(aStrB)); + if (!B2) { + free(A2); + return 1; + } + + int32_t result; + wchar_t* a = A2; + wchar_t* b = B2; + + do { + VersionPartW va, vb; + + a = ParseVP(a, va); + b = ParseVP(b, vb); + + result = CompareVP(va, vb); + if (result) { + break; + } + + } while (a || b); + + free(A2); + free(B2); + + return result; +} +#endif + +int32_t +CompareVersions(const char* aStrA, const char* aStrB) +{ + char* A2 = strdup(aStrA); + if (!A2) { + return 1; + } + + char* B2 = strdup(aStrB); + if (!B2) { + free(A2); + return 1; + } + + int32_t result; + char* a = A2; + char* b = B2; + + do { + VersionPart va, vb; + + a = ParseVP(a, va); + b = ParseVP(b, vb); + + result = CompareVP(va, vb); + if (result) { + break; + } + + } while (a || b); + + free(A2); + free(B2); + + return result; +} + +} // namespace mozilla + diff --git a/xpcom/glue/nsVersionComparator.h b/xpcom/glue/nsVersionComparator.h new file mode 100644 index 0000000000..0dbf8532b0 --- /dev/null +++ b/xpcom/glue/nsVersionComparator.h @@ -0,0 +1,174 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsVersionComparator_h__ +#define nsVersionComparator_h__ + +#include "nscore.h" +#include <stdlib.h> +#include <string.h> +#include <assert.h> +#if defined(XP_WIN) && !defined(UPDATER_NO_STRING_GLUE_STL) +#include <wchar.h> +#include "nsStringGlue.h" +#endif + +/** + * In order to compare version numbers in Mozilla, you need to use the + * mozilla::Version class. You can construct an object of this type by passing + * in a string version number to the constructor. Objects of this type can be + * compared using the standard comparison operators. + * + * For example, let's say that you want to make sure that a given version + * number is not older than 15.a2. Here's how you would write a function to + * do that. + * + * bool IsVersionValid(const char* version) { + * return mozilla::Version("15.a2") <= mozilla::Version(version); + * } + * + * Or, since Version's constructor is implicit, you can simplify this code: + * + * bool IsVersionValid(const char* version) { + * return mozilla::Version("15.a2") <= version; + * } + * + * On Windows, if your version strings are wide characters, you should use the + * mozilla::VersionW variant instead. The semantics of that class is the same + * as Version. + */ + +namespace mozilla { + +int32_t CompareVersions(const char* aStrA, const char* aStrB); + +#ifdef XP_WIN +int32_t CompareVersions(const char16_t* aStrA, const char16_t* aStrB); +#endif + +struct Version +{ + explicit Version(const char* aVersionString) + { + versionContent = strdup(aVersionString); + } + + const char* ReadContent() const + { + return versionContent; + } + + ~Version() + { + free(versionContent); + } + + bool operator<(const Version& aRhs) const + { + return CompareVersions(versionContent, aRhs.ReadContent()) == -1; + } + bool operator<=(const Version& aRhs) const + { + return CompareVersions(versionContent, aRhs.ReadContent()) < 1; + } + bool operator>(const Version& aRhs) const + { + return CompareVersions(versionContent, aRhs.ReadContent()) == 1; + } + bool operator>=(const Version& aRhs) const + { + return CompareVersions(versionContent, aRhs.ReadContent()) > -1; + } + bool operator==(const Version& aRhs) const + { + return CompareVersions(versionContent, aRhs.ReadContent()) == 0; + } + bool operator!=(const Version& aRhs) const + { + return CompareVersions(versionContent, aRhs.ReadContent()) != 0; + } + bool operator<(const char* aRhs) const + { + return CompareVersions(versionContent, aRhs) == -1; + } + bool operator<=(const char* aRhs) const + { + return CompareVersions(versionContent, aRhs) < 1; + } + bool operator>(const char* aRhs) const + { + return CompareVersions(versionContent, aRhs) == 1; + } + bool operator>=(const char* aRhs) const + { + return CompareVersions(versionContent, aRhs) > -1; + } + bool operator==(const char* aRhs) const + { + return CompareVersions(versionContent, aRhs) == 0; + } + bool operator!=(const char* aRhs) const + { + return CompareVersions(versionContent, aRhs) != 0; + } + +private: + char* versionContent; +}; + +#ifdef XP_WIN +struct VersionW +{ + VersionW(const char16_t* aVersionStringW) + { + versionContentW = + reinterpret_cast<char16_t*>(wcsdup(char16ptr_t(aVersionStringW))); + } + + const char16_t* ReadContentW() const + { + return versionContentW; + } + + ~VersionW() + { + free(versionContentW); + } + + bool operator<(const VersionW& aRhs) const + { + return CompareVersions(versionContentW, aRhs.ReadContentW()) == -1; + } + bool operator<=(const VersionW& aRhs) const + { + return CompareVersions(versionContentW, aRhs.ReadContentW()) < 1; + } + bool operator>(const VersionW& aRhs) const + { + return CompareVersions(versionContentW, aRhs.ReadContentW()) == 1; + } + bool operator>=(const VersionW& aRhs) const + { + return CompareVersions(versionContentW, aRhs.ReadContentW()) > -1; + } + bool operator==(const VersionW& aRhs) const + { + return CompareVersions(versionContentW, aRhs.ReadContentW()) == 0; + } + bool operator!=(const VersionW& aRhs) const + { + return CompareVersions(versionContentW, aRhs.ReadContentW()) != 0; + } + +private: + char16_t* versionContentW; +}; +#endif + +} // namespace mozilla + +#endif // nsVersionComparator_h__ + diff --git a/xpcom/glue/nsWeakReference.cpp b/xpcom/glue/nsWeakReference.cpp new file mode 100644 index 0000000000..57f372641e --- /dev/null +++ b/xpcom/glue/nsWeakReference.cpp @@ -0,0 +1,164 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// nsWeakReference.cpp + +#include "mozilla/Attributes.h" + +#include "nsWeakReference.h" +#include "nsCOMPtr.h" +#include "nsDebug.h" + +#ifdef MOZ_THREAD_SAFETY_OWNERSHIP_CHECKS_SUPPORTED + +#define MOZ_WEAKREF_DECL_OWNINGTHREAD nsAutoOwningThread _mWeakRefOwningThread; +#define MOZ_WEAKREF_ASSERT_OWNINGTHREAD \ + NS_CheckThreadSafe(_mWeakRefOwningThread.GetThread(), "nsWeakReference not thread-safe") +#define MOZ_WEAKREF_ASSERT_OWNINGTHREAD_DELEGATED(that) \ + NS_CheckThreadSafe((that)->_mWeakRefOwningThread.GetThread(), "nsWeakReference not thread-safe") + +#else + +#define MOZ_WEAKREF_DECL_OWNINGTHREAD +#define MOZ_WEAKREF_ASSERT_OWNINGTHREAD do { } while (false) +#define MOZ_WEAKREF_ASSERT_OWNINGTHREAD_DELEGATED(that) do { } while (false) + +#endif + +class nsWeakReference final : public nsIWeakReference +{ +public: + // nsISupports... + NS_DECL_ISUPPORTS + + // nsIWeakReference... + NS_DECL_NSIWEAKREFERENCE + virtual size_t SizeOfOnlyThis(mozilla::MallocSizeOf aMallocSizeOf) const override; + +private: + MOZ_WEAKREF_DECL_OWNINGTHREAD + + friend class nsSupportsWeakReference; + + explicit nsWeakReference(nsSupportsWeakReference* aReferent) + : mReferent(aReferent) + // ...I can only be constructed by an |nsSupportsWeakReference| + { + } + + ~nsWeakReference() + // ...I will only be destroyed by calling |delete| myself. + { + MOZ_WEAKREF_ASSERT_OWNINGTHREAD; + if (mReferent) { + mReferent->NoticeProxyDestruction(); + } + } + + void + NoticeReferentDestruction() + // ...called (only) by an |nsSupportsWeakReference| from _its_ dtor. + { + MOZ_WEAKREF_ASSERT_OWNINGTHREAD; + mReferent = nullptr; + } + + nsSupportsWeakReference* MOZ_NON_OWNING_REF mReferent; +}; + +nsresult +nsQueryReferent::operator()(const nsIID& aIID, void** aAnswer) const +{ + nsresult status; + if (mWeakPtr) { + if (NS_FAILED(status = mWeakPtr->QueryReferent(aIID, aAnswer))) { + *aAnswer = 0; + } + } else { + status = NS_ERROR_NULL_POINTER; + } + + if (mErrorPtr) { + *mErrorPtr = status; + } + return status; +} + +nsIWeakReference* // or else |already_AddRefed<nsIWeakReference>| +NS_GetWeakReference(nsISupports* aInstancePtr, nsresult* aErrorPtr) +{ + nsresult status; + + nsIWeakReference* result = nullptr; + + if (aInstancePtr) { + nsCOMPtr<nsISupportsWeakReference> factoryPtr = + do_QueryInterface(aInstancePtr, &status); + if (factoryPtr) { + status = factoryPtr->GetWeakReference(&result); + } + // else, |status| has already been set by |do_QueryInterface| + } else { + status = NS_ERROR_NULL_POINTER; + } + + if (aErrorPtr) { + *aErrorPtr = status; + } + return result; +} + +nsresult +nsSupportsWeakReference::GetWeakReference(nsIWeakReference** aInstancePtr) +{ + if (!aInstancePtr) { + return NS_ERROR_NULL_POINTER; + } + + if (!mProxy) { + mProxy = new nsWeakReference(this); + } else { + MOZ_WEAKREF_ASSERT_OWNINGTHREAD_DELEGATED(mProxy); + } + *aInstancePtr = mProxy; + + nsresult status; + if (!*aInstancePtr) { + status = NS_ERROR_OUT_OF_MEMORY; + } else { + NS_ADDREF(*aInstancePtr); + status = NS_OK; + } + + return status; +} + +NS_IMPL_ISUPPORTS(nsWeakReference, nsIWeakReference) + +NS_IMETHODIMP +nsWeakReference::QueryReferent(const nsIID& aIID, void** aInstancePtr) +{ + MOZ_WEAKREF_ASSERT_OWNINGTHREAD; + + return mReferent ? mReferent->QueryInterface(aIID, aInstancePtr) : + NS_ERROR_NULL_POINTER; +} + +size_t +nsWeakReference::SizeOfOnlyThis(mozilla::MallocSizeOf aMallocSizeOf) const +{ + return aMallocSizeOf(this); +} + +void +nsSupportsWeakReference::ClearWeakReferences() +{ + if (mProxy) { + mProxy->NoticeReferentDestruction(); + mProxy = nullptr; + } +} + diff --git a/xpcom/glue/nsWeakReference.h b/xpcom/glue/nsWeakReference.h new file mode 100644 index 0000000000..fc875ba968 --- /dev/null +++ b/xpcom/glue/nsWeakReference.h @@ -0,0 +1,49 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsWeakReference_h__ +#define nsWeakReference_h__ + +// nsWeakReference.h + +// See mfbt/WeakPtr.h for a more typesafe C++ implementation of weak references + +#include "nsIWeakReferenceUtils.h" + +class nsWeakReference; + +class nsSupportsWeakReference : public nsISupportsWeakReference +{ +public: + nsSupportsWeakReference() : mProxy(0) {} + + NS_DECL_NSISUPPORTSWEAKREFERENCE + +protected: + inline ~nsSupportsWeakReference(); + +private: + friend class nsWeakReference; + + // Called (only) by an |nsWeakReference| from _its_ dtor. + // The thread safety check is made by the caller. + void NoticeProxyDestruction() { mProxy = nullptr; } + + nsWeakReference* MOZ_NON_OWNING_REF mProxy; + +protected: + + void ClearWeakReferences(); + bool HasWeakReferences() const { return !!mProxy; } +}; + +inline +nsSupportsWeakReference::~nsSupportsWeakReference() +{ + ClearWeakReferences(); +} + +#endif diff --git a/xpcom/glue/nsXPTCUtils.h b/xpcom/glue/nsXPTCUtils.h new file mode 100644 index 0000000000..8922aca162 --- /dev/null +++ b/xpcom/glue/nsXPTCUtils.h @@ -0,0 +1,45 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsXPTCUtils_h__ +#define nsXPTCUtils_h__ + +#include "xptcall.h" +#include "mozilla/MemoryReporting.h" + +/** + * A helper class that initializes an xptcall helper at construction + * and releases it at destruction. + */ +class nsAutoXPTCStub : protected nsIXPTCProxy +{ +public: + nsISomeInterface* mXPTCStub; + +protected: + nsAutoXPTCStub() : mXPTCStub(nullptr) {} + + nsresult + InitStub(const nsIID& aIID) + { + return NS_GetXPTCallStub(aIID, this, &mXPTCStub); + } + + ~nsAutoXPTCStub() + { + if (mXPTCStub) { + NS_DestroyXPTCallStub(mXPTCStub); + } + } + + size_t + SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const + { + return mXPTCStub ? NS_SizeOfIncludingThisXPTCallStub(mXPTCStub, aMallocSizeOf) : 0; + } +}; + +#endif // nsXPTCUtils_h__ diff --git a/xpcom/glue/objs.mozbuild b/xpcom/glue/objs.mozbuild new file mode 100644 index 0000000000..8161e1ebce --- /dev/null +++ b/xpcom/glue/objs.mozbuild @@ -0,0 +1,48 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +xpcom_glue_src_lcppsrcs = [ + 'AppData.cpp', + 'FileUtils.cpp', + 'nsArrayEnumerator.cpp', + 'nsArrayUtils.cpp', + 'nsCategoryCache.cpp', + 'nsClassInfoImpl.cpp', + 'nsCOMArray.cpp', + 'nsComponentManagerUtils.cpp', + 'nsCOMPtr.cpp', + 'nsCRTGlue.cpp', + 'nsCycleCollectionParticipant.cpp', + 'nsDeque.cpp', + 'nsEnumeratorUtils.cpp', + 'nsID.cpp', + 'nsIInterfaceRequestorUtils.cpp', + 'nsINIParser.cpp', + 'nsISupportsImpl.cpp', + 'nsMemory.cpp', + 'nsQuickSort.cpp', + 'nsTArray.cpp', + 'nsThreadUtils.cpp', + 'nsTObserverArray.cpp', + 'nsVersionComparator.cpp', + 'nsWeakReference.cpp', + 'PLDHashTable.cpp', +] + +xpcom_glue_src_cppsrcs = [ + '/xpcom/glue/%s' % s for s in xpcom_glue_src_lcppsrcs +] + +xpcom_gluens_src_lcppsrcs = [ + 'BlockingResourceBase.cpp', + 'GenericFactory.cpp', + 'nsProxyRelease.cpp', + 'nsTextFormatter.cpp', +] + +xpcom_gluens_src_cppsrcs = [ + '/xpcom/glue/%s' % s for s in xpcom_gluens_src_lcppsrcs +] diff --git a/xpcom/glue/standalone/moz.build b/xpcom/glue/standalone/moz.build new file mode 100644 index 0000000000..fd91fa0bb0 --- /dev/null +++ b/xpcom/glue/standalone/moz.build @@ -0,0 +1,58 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +# On win we build two glue libs - glue linked to crt dlls here and in staticruntime we build +# a statically linked glue lib. +if CONFIG['OS_ARCH'] == 'WINNT': + DIRS += ['staticruntime'] + +include('../objs.mozbuild') + +SOURCES += xpcom_glue_src_cppsrcs + +SOURCES += [ + '../nsStringAPI.cpp', + 'nsXPCOMGlue.cpp', +] + +Library('xpcomglue') + +EXPORTS += [ + 'nsXPCOMGlue.h', +] + +SDK_LIBRARY = True + +FORCE_STATIC_LIB = True + +if CONFIG['_MSC_VER']: + DEFINES['_USE_ANSI_CPP'] = True + # Don't include directives about which CRT to use + CFLAGS += ['-Zl'] + CXXFLAGS += ['-Zl'] + +DEFINES['XPCOM_GLUE'] = True + +LOCAL_INCLUDES += [ + '../../build', + '../../threads', +] + +# Don't use STL wrappers here (i.e. wrapped <new>); they require mozalloc +DISABLE_STL_WRAPPING = True + +# Include fallible for third party code using the xpcom glue +USE_LIBS += [ + 'fallible', +] + +# Force to build a static library only +NO_EXPAND_LIBS = True + +DIST_INSTALL = True + +if CONFIG['MOZ_WIDGET_TOOLKIT'] in ('gtk2', 'gtk3'): + CXXFLAGS += CONFIG['GLIB_CFLAGS'] diff --git a/xpcom/glue/standalone/nsXPCOMGlue.cpp b/xpcom/glue/standalone/nsXPCOMGlue.cpp new file mode 100644 index 0000000000..b657d7050e --- /dev/null +++ b/xpcom/glue/standalone/nsXPCOMGlue.cpp @@ -0,0 +1,927 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsXPCOMGlue.h" + +#include "nspr.h" +#include "nsDebug.h" +#include "nsIServiceManager.h" +#include "nsXPCOMPrivate.h" +#include "nsCOMPtr.h" +#include <stdlib.h> +#include <stdio.h> + +#include "mozilla/FileUtils.h" +#include "mozilla/Sprintf.h" + +using namespace mozilla; + +#define XPCOM_DEPENDENT_LIBS_LIST "dependentlibs.list" + +static XPCOMFunctions xpcomFunctions; +static bool do_preload = false; + +#if defined(XP_WIN) +#define READ_TEXTMODE L"rt" +#else +#define READ_TEXTMODE "r" +#endif + +#if defined(XP_WIN) +#include <windows.h> +#include <mbstring.h> + +typedef HINSTANCE LibHandleType; + +static LibHandleType +GetLibHandle(pathstr_t aDependentLib) +{ + LibHandleType libHandle = + LoadLibraryExW(aDependentLib, nullptr, LOAD_WITH_ALTERED_SEARCH_PATH); + +#ifdef DEBUG + if (!libHandle) { + DWORD err = GetLastError(); + LPWSTR lpMsgBuf; + FormatMessageW( + FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + nullptr, + err, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPWSTR)&lpMsgBuf, + 0, + nullptr + ); + wprintf(L"Error loading %ls: %s\n", aDependentLib, lpMsgBuf); + LocalFree(lpMsgBuf); + } +#endif + + return libHandle; +} + +static NSFuncPtr +GetSymbol(LibHandleType aLibHandle, const char* aSymbol) +{ + return (NSFuncPtr)GetProcAddress(aLibHandle, aSymbol); +} + +static void +CloseLibHandle(LibHandleType aLibHandle) +{ + FreeLibrary(aLibHandle); +} + +#else +#include <dlfcn.h> + +#if defined(MOZ_LINKER) && !defined(ANDROID) +extern "C" { +NS_HIDDEN __typeof(dlopen) __wrap_dlopen; +NS_HIDDEN __typeof(dlsym) __wrap_dlsym; +NS_HIDDEN __typeof(dlclose) __wrap_dlclose; +} + +#define dlopen __wrap_dlopen +#define dlsym __wrap_dlsym +#define dlclose __wrap_dlclose +#endif + +typedef void* LibHandleType; + +static LibHandleType +GetLibHandle(pathstr_t aDependentLib) +{ + LibHandleType libHandle = dlopen(aDependentLib, + RTLD_GLOBAL | RTLD_LAZY +#ifdef XP_MACOSX + | RTLD_FIRST +#endif + ); + if (!libHandle) { + fprintf(stderr, "XPCOMGlueLoad error for file %s:\n%s\n", aDependentLib, + dlerror()); + } + return libHandle; +} + +static NSFuncPtr +GetSymbol(LibHandleType aLibHandle, const char* aSymbol) +{ + return (NSFuncPtr)dlsym(aLibHandle, aSymbol); +} + +static void +CloseLibHandle(LibHandleType aLibHandle) +{ + dlclose(aLibHandle); +} +#endif + +struct DependentLib +{ + LibHandleType libHandle; + DependentLib* next; +}; + +static DependentLib* sTop; + +static void +AppendDependentLib(LibHandleType aLibHandle) +{ + DependentLib* d = new DependentLib; + if (!d) { + return; + } + + d->next = sTop; + d->libHandle = aLibHandle; + + sTop = d; +} + +static bool +ReadDependentCB(pathstr_t aDependentLib, bool aDoPreload) +{ + if (aDoPreload) { + ReadAheadLib(aDependentLib); + } + LibHandleType libHandle = GetLibHandle(aDependentLib); + if (libHandle) { + AppendDependentLib(libHandle); + } + + return libHandle; +} + +#ifdef XP_WIN +static bool +ReadDependentCB(const char* aDependentLib, bool do_preload) +{ + wchar_t wideDependentLib[MAX_PATH]; + MultiByteToWideChar(CP_UTF8, 0, aDependentLib, -1, wideDependentLib, MAX_PATH); + return ReadDependentCB(wideDependentLib, do_preload); +} + +inline FILE* +TS_tfopen(const char* path, const wchar_t* mode) +{ + wchar_t wPath[MAX_PATH]; + MultiByteToWideChar(CP_UTF8, 0, path, -1, wPath, MAX_PATH); + return _wfopen(wPath, mode); +} +#else +inline FILE* +TS_tfopen(const char* aPath, const char* aMode) +{ + return fopen(aPath, aMode); +} +#endif + +/* RAII wrapper for FILE descriptors */ +struct ScopedCloseFileTraits +{ + typedef FILE* type; + static type empty() { return nullptr; } + static void release(type aFile) + { + if (aFile) { + fclose(aFile); + } + } +}; +typedef Scoped<ScopedCloseFileTraits> ScopedCloseFile; + +static void +XPCOMGlueUnload() +{ + while (sTop) { + CloseLibHandle(sTop->libHandle); + + DependentLib* temp = sTop; + sTop = sTop->next; + + delete temp; + } +} + +#if defined(XP_WIN) +// like strpbrk but finds the *last* char, not the first +static const char* +ns_strrpbrk(const char* string, const char* strCharSet) +{ + const char* found = nullptr; + for (; *string; ++string) { + for (const char* search = strCharSet; *search; ++search) { + if (*search == *string) { + found = string; + // Since we're looking for the last char, we save "found" + // until we're at the end of the string. + } + } + } + + return found; +} +#endif + +static GetFrozenFunctionsFunc +XPCOMGlueLoad(const char* aXPCOMFile) +{ + char xpcomDir[MAXPATHLEN]; +#ifdef XP_WIN + const char* lastSlash = ns_strrpbrk(aXPCOMFile, "/\\"); +#elif XP_MACOSX + // On OSX, the dependentlibs.list file lives under Contents/Resources. + // However, the actual libraries listed in dependentlibs.list live under + // Contents/MacOS. We want to read the list from Contents/Resources, then + // load the libraries from Contents/MacOS. + const char *tempSlash = strrchr(aXPCOMFile, '/'); + size_t tempLen = size_t(tempSlash - aXPCOMFile); + if (tempLen > MAXPATHLEN) { + return nullptr; + } + char tempBuffer[MAXPATHLEN]; + memcpy(tempBuffer, aXPCOMFile, tempLen); + tempBuffer[tempLen] = '\0'; + const char *slash = strrchr(tempBuffer, '/'); + tempLen = size_t(slash - tempBuffer); + const char *lastSlash = aXPCOMFile + tempLen; +#else + const char* lastSlash = strrchr(aXPCOMFile, '/'); +#endif + char* cursor; + if (lastSlash) { + size_t len = size_t(lastSlash - aXPCOMFile); + + if (len > MAXPATHLEN - sizeof(XPCOM_FILE_PATH_SEPARATOR +#ifdef XP_MACOSX + "Resources" + XPCOM_FILE_PATH_SEPARATOR +#endif + XPCOM_DEPENDENT_LIBS_LIST)) { + return nullptr; + } + memcpy(xpcomDir, aXPCOMFile, len); + strcpy(xpcomDir + len, XPCOM_FILE_PATH_SEPARATOR +#ifdef XP_MACOSX + "Resources" + XPCOM_FILE_PATH_SEPARATOR +#endif + XPCOM_DEPENDENT_LIBS_LIST); + cursor = xpcomDir + len + 1; + } else { + strcpy(xpcomDir, XPCOM_DEPENDENT_LIBS_LIST); + cursor = xpcomDir; + } + + if (getenv("MOZ_RUN_GTEST")) { + strcat(xpcomDir, ".gtest"); + } + + ScopedCloseFile flist; + flist = TS_tfopen(xpcomDir, READ_TEXTMODE); + if (!flist) { + return nullptr; + } + +#ifdef XP_MACOSX + tempLen = size_t(cursor - xpcomDir); + if (tempLen > MAXPATHLEN - sizeof("MacOS" XPCOM_FILE_PATH_SEPARATOR) - 1) { + return nullptr; + } + strcpy(cursor, "MacOS" XPCOM_FILE_PATH_SEPARATOR); + cursor += strlen(cursor); +#endif + *cursor = '\0'; + + char buffer[MAXPATHLEN]; + + while (fgets(buffer, sizeof(buffer), flist)) { + int l = strlen(buffer); + + // ignore empty lines and comments + if (l == 0 || *buffer == '#') { + continue; + } + + // cut the trailing newline, if present + if (buffer[l - 1] == '\n') { + buffer[l - 1] = '\0'; + } + + if (l + size_t(cursor - xpcomDir) > MAXPATHLEN) { + return nullptr; + } + + strcpy(cursor, buffer); + if (!ReadDependentCB(xpcomDir, do_preload)) { + XPCOMGlueUnload(); + return nullptr; + } + } + + GetFrozenFunctionsFunc sym = + (GetFrozenFunctionsFunc)GetSymbol(sTop->libHandle, + "NS_GetFrozenFunctions"); + + if (!sym) { // No symbol found. + XPCOMGlueUnload(); + return nullptr; + } + + return sym; +} + +nsresult +XPCOMGlueLoadXULFunctions(const nsDynamicFunctionLoad* aSymbols) +{ + // We don't null-check sXULLibHandle because this might work even + // if it is null (same as RTLD_DEFAULT) + + nsresult rv = NS_OK; + while (aSymbols->functionName) { + char buffer[512]; + SprintfLiteral(buffer, "%s", aSymbols->functionName); + + *aSymbols->function = (NSFuncPtr)GetSymbol(sTop->libHandle, buffer); + if (!*aSymbols->function) { + rv = NS_ERROR_LOSS_OF_SIGNIFICANT_DATA; + } + + ++aSymbols; + } + return rv; +} + +void +XPCOMGlueEnablePreload() +{ + do_preload = true; +} + +#if defined(MOZ_WIDGET_GTK) && (defined(MOZ_MEMORY) || defined(__FreeBSD__) || defined(__NetBSD__)) +#define MOZ_GSLICE_INIT +#endif + +#ifdef MOZ_GSLICE_INIT +#include <glib.h> + +class GSliceInit { +public: + GSliceInit() { + mHadGSlice = bool(getenv("G_SLICE")); + if (!mHadGSlice) { + // Disable the slice allocator, since jemalloc already uses similar layout + // algorithms, and using a sub-allocator tends to increase fragmentation. + // This must be done before g_thread_init() is called. + // glib >= 2.36 initializes g_slice as a side effect of its various static + // initializers, so this needs to happen before glib is loaded, which is + // this is hooked in XPCOMGlueStartup before libxul is loaded. This + // relies on the main executable not depending on glib. + setenv("G_SLICE", "always-malloc", 1); + } + } + + ~GSliceInit() { +#if MOZ_WIDGET_GTK == 2 + if (sTop) { + auto XRE_GlibInit = (void (*)(void)) GetSymbol(sTop->libHandle, + "XRE_GlibInit"); + // Initialize glib enough for G_SLICE to have an effect before it is unset. + // unset. + XRE_GlibInit(); + } +#endif + if (!mHadGSlice) { + unsetenv("G_SLICE"); + } + } + +private: + bool mHadGSlice; +}; +#endif + +nsresult +XPCOMGlueStartup(const char* aXPCOMFile) +{ +#ifdef MOZ_GSLICE_INIT + GSliceInit gSliceInit; +#endif + xpcomFunctions.version = XPCOM_GLUE_VERSION; + xpcomFunctions.size = sizeof(XPCOMFunctions); + + if (!aXPCOMFile) { + aXPCOMFile = XPCOM_DLL; + } + + GetFrozenFunctionsFunc func = XPCOMGlueLoad(aXPCOMFile); + if (!func) { + return NS_ERROR_NOT_AVAILABLE; + } + + nsresult rv = (*func)(&xpcomFunctions, nullptr); + if (NS_FAILED(rv)) { + XPCOMGlueUnload(); + return rv; + } + + return NS_OK; +} + +XPCOM_API(nsresult) +NS_InitXPCOM2(nsIServiceManager** aResult, + nsIFile* aBinDirectory, + nsIDirectoryServiceProvider* aAppFileLocationProvider) +{ + if (!xpcomFunctions.init) { + return NS_ERROR_NOT_INITIALIZED; + } + return xpcomFunctions.init(aResult, aBinDirectory, aAppFileLocationProvider); +} + +XPCOM_API(nsresult) +NS_ShutdownXPCOM(nsIServiceManager* aServMgr) +{ + if (!xpcomFunctions.shutdown) { + return NS_ERROR_NOT_INITIALIZED; + } + return xpcomFunctions.shutdown(aServMgr); +} + +XPCOM_API(nsresult) +NS_GetServiceManager(nsIServiceManager** aResult) +{ + if (!xpcomFunctions.getServiceManager) { + return NS_ERROR_NOT_INITIALIZED; + } + return xpcomFunctions.getServiceManager(aResult); +} + +XPCOM_API(nsresult) +NS_GetComponentManager(nsIComponentManager** aResult) +{ + if (!xpcomFunctions.getComponentManager) { + return NS_ERROR_NOT_INITIALIZED; + } + return xpcomFunctions.getComponentManager(aResult); +} + +XPCOM_API(nsresult) +NS_GetComponentRegistrar(nsIComponentRegistrar** aResult) +{ + if (!xpcomFunctions.getComponentRegistrar) { + return NS_ERROR_NOT_INITIALIZED; + } + return xpcomFunctions.getComponentRegistrar(aResult); +} + +XPCOM_API(nsresult) +NS_GetMemoryManager(nsIMemory** aResult) +{ + if (!xpcomFunctions.getMemoryManager) { + return NS_ERROR_NOT_INITIALIZED; + } + return xpcomFunctions.getMemoryManager(aResult); +} + +XPCOM_API(nsresult) +NS_NewLocalFile(const nsAString& aPath, bool aFollowLinks, nsIFile** aResult) +{ + if (!xpcomFunctions.newLocalFile) { + return NS_ERROR_NOT_INITIALIZED; + } + return xpcomFunctions.newLocalFile(aPath, aFollowLinks, aResult); +} + +XPCOM_API(nsresult) +NS_NewNativeLocalFile(const nsACString& aPath, bool aFollowLinks, + nsIFile** aResult) +{ + if (!xpcomFunctions.newNativeLocalFile) { + return NS_ERROR_NOT_INITIALIZED; + } + return xpcomFunctions.newNativeLocalFile(aPath, aFollowLinks, aResult); +} + +XPCOM_API(nsresult) +NS_GetDebug(nsIDebug2** aResult) +{ + if (!xpcomFunctions.getDebug) { + return NS_ERROR_NOT_INITIALIZED; + } + return xpcomFunctions.getDebug(aResult); +} + + +XPCOM_API(nsresult) +NS_StringContainerInit(nsStringContainer& aStr) +{ + if (!xpcomFunctions.stringContainerInit) { + return NS_ERROR_NOT_INITIALIZED; + } + return xpcomFunctions.stringContainerInit(aStr); +} + +XPCOM_API(nsresult) +NS_StringContainerInit2(nsStringContainer& aStr, const char16_t* aData, + uint32_t aDataLength, uint32_t aFlags) +{ + if (!xpcomFunctions.stringContainerInit2) { + return NS_ERROR_NOT_INITIALIZED; + } + return xpcomFunctions.stringContainerInit2(aStr, aData, aDataLength, aFlags); +} + +XPCOM_API(void) +NS_StringContainerFinish(nsStringContainer& aStr) +{ + if (xpcomFunctions.stringContainerFinish) { + xpcomFunctions.stringContainerFinish(aStr); + } +} + +XPCOM_API(uint32_t) +NS_StringGetData(const nsAString& aStr, const char16_t** aBuf, bool* aTerm) +{ + if (!xpcomFunctions.stringGetData) { + *aBuf = nullptr; + return 0; + } + return xpcomFunctions.stringGetData(aStr, aBuf, aTerm); +} + +XPCOM_API(uint32_t) +NS_StringGetMutableData(nsAString& aStr, uint32_t aLen, char16_t** aBuf) +{ + if (!xpcomFunctions.stringGetMutableData) { + *aBuf = nullptr; + return 0; + } + return xpcomFunctions.stringGetMutableData(aStr, aLen, aBuf); +} + +XPCOM_API(char16_t*) +NS_StringCloneData(const nsAString& aStr) +{ + if (!xpcomFunctions.stringCloneData) { + return nullptr; + } + return xpcomFunctions.stringCloneData(aStr); +} + +XPCOM_API(nsresult) +NS_StringSetData(nsAString& aStr, const char16_t* aBuf, uint32_t aCount) +{ + if (!xpcomFunctions.stringSetData) { + return NS_ERROR_NOT_INITIALIZED; + } + + return xpcomFunctions.stringSetData(aStr, aBuf, aCount); +} + +XPCOM_API(nsresult) +NS_StringSetDataRange(nsAString& aStr, uint32_t aCutStart, uint32_t aCutLength, + const char16_t* aBuf, uint32_t aCount) +{ + if (!xpcomFunctions.stringSetDataRange) { + return NS_ERROR_NOT_INITIALIZED; + } + return xpcomFunctions.stringSetDataRange(aStr, aCutStart, aCutLength, aBuf, + aCount); +} + +XPCOM_API(nsresult) +NS_StringCopy(nsAString& aDest, const nsAString& aSrc) +{ + if (!xpcomFunctions.stringCopy) { + return NS_ERROR_NOT_INITIALIZED; + } + return xpcomFunctions.stringCopy(aDest, aSrc); +} + +XPCOM_API(void) +NS_StringSetIsVoid(nsAString& aStr, const bool aIsVoid) +{ + if (xpcomFunctions.stringSetIsVoid) { + xpcomFunctions.stringSetIsVoid(aStr, aIsVoid); + } +} + +XPCOM_API(bool) +NS_StringGetIsVoid(const nsAString& aStr) +{ + if (!xpcomFunctions.stringGetIsVoid) { + return false; + } + return xpcomFunctions.stringGetIsVoid(aStr); +} + +XPCOM_API(nsresult) +NS_CStringContainerInit(nsCStringContainer& aStr) +{ + if (!xpcomFunctions.cstringContainerInit) { + return NS_ERROR_NOT_INITIALIZED; + } + return xpcomFunctions.cstringContainerInit(aStr); +} + +XPCOM_API(nsresult) +NS_CStringContainerInit2(nsCStringContainer& aStr, const char* aData, + uint32_t aDataLength, uint32_t aFlags) +{ + if (!xpcomFunctions.cstringContainerInit2) { + return NS_ERROR_NOT_INITIALIZED; + } + return xpcomFunctions.cstringContainerInit2(aStr, aData, aDataLength, aFlags); +} + +XPCOM_API(void) +NS_CStringContainerFinish(nsCStringContainer& aStr) +{ + if (xpcomFunctions.cstringContainerFinish) { + xpcomFunctions.cstringContainerFinish(aStr); + } +} + +XPCOM_API(uint32_t) +NS_CStringGetData(const nsACString& aStr, const char** aBuf, bool* aTerm) +{ + if (!xpcomFunctions.cstringGetData) { + *aBuf = nullptr; + return 0; + } + return xpcomFunctions.cstringGetData(aStr, aBuf, aTerm); +} + +XPCOM_API(uint32_t) +NS_CStringGetMutableData(nsACString& aStr, uint32_t aLen, char** aBuf) +{ + if (!xpcomFunctions.cstringGetMutableData) { + *aBuf = nullptr; + return 0; + } + return xpcomFunctions.cstringGetMutableData(aStr, aLen, aBuf); +} + +XPCOM_API(char*) +NS_CStringCloneData(const nsACString& aStr) +{ + if (!xpcomFunctions.cstringCloneData) { + return nullptr; + } + return xpcomFunctions.cstringCloneData(aStr); +} + +XPCOM_API(nsresult) +NS_CStringSetData(nsACString& aStr, const char* aBuf, uint32_t aCount) +{ + if (!xpcomFunctions.cstringSetData) { + return NS_ERROR_NOT_INITIALIZED; + } + return xpcomFunctions.cstringSetData(aStr, aBuf, aCount); +} + +XPCOM_API(nsresult) +NS_CStringSetDataRange(nsACString& aStr, uint32_t aCutStart, + uint32_t aCutLength, const char* aBuf, uint32_t aCount) +{ + if (!xpcomFunctions.cstringSetDataRange) { + return NS_ERROR_NOT_INITIALIZED; + } + return xpcomFunctions.cstringSetDataRange(aStr, aCutStart, aCutLength, aBuf, + aCount); +} + +XPCOM_API(nsresult) +NS_CStringCopy(nsACString& aDest, const nsACString& aSrc) +{ + if (!xpcomFunctions.cstringCopy) { + return NS_ERROR_NOT_INITIALIZED; + } + return xpcomFunctions.cstringCopy(aDest, aSrc); +} + +XPCOM_API(void) +NS_CStringSetIsVoid(nsACString& aStr, const bool aIsVoid) +{ + if (xpcomFunctions.cstringSetIsVoid) { + xpcomFunctions.cstringSetIsVoid(aStr, aIsVoid); + } +} + +XPCOM_API(bool) +NS_CStringGetIsVoid(const nsACString& aStr) +{ + if (!xpcomFunctions.cstringGetIsVoid) { + return false; + } + return xpcomFunctions.cstringGetIsVoid(aStr); +} + +XPCOM_API(nsresult) +NS_CStringToUTF16(const nsACString& aSrc, nsCStringEncoding aSrcEncoding, + nsAString& aDest) +{ + if (!xpcomFunctions.cstringToUTF16) { + return NS_ERROR_NOT_INITIALIZED; + } + return xpcomFunctions.cstringToUTF16(aSrc, aSrcEncoding, aDest); +} + +XPCOM_API(nsresult) +NS_UTF16ToCString(const nsAString& aSrc, nsCStringEncoding aDestEncoding, + nsACString& aDest) +{ + if (!xpcomFunctions.utf16ToCString) { + return NS_ERROR_NOT_INITIALIZED; + } + return xpcomFunctions.utf16ToCString(aSrc, aDestEncoding, aDest); +} + +XPCOM_API(void*) +NS_Alloc(size_t aSize) +{ + if (!xpcomFunctions.allocFunc) { + return nullptr; + } + return xpcomFunctions.allocFunc(aSize); +} + +XPCOM_API(void*) +NS_Realloc(void* aPtr, size_t aSize) +{ + if (!xpcomFunctions.reallocFunc) { + return nullptr; + } + return xpcomFunctions.reallocFunc(aPtr, aSize); +} + +XPCOM_API(void) +NS_Free(void* aPtr) +{ + if (xpcomFunctions.freeFunc) { + xpcomFunctions.freeFunc(aPtr); + } +} + +XPCOM_API(void) +NS_DebugBreak(uint32_t aSeverity, const char* aStr, const char* aExpr, + const char* aFile, int32_t aLine) +{ + if (xpcomFunctions.debugBreakFunc) { + xpcomFunctions.debugBreakFunc(aSeverity, aStr, aExpr, aFile, aLine); + } +} + +XPCOM_API(void) +NS_LogInit() +{ + if (xpcomFunctions.logInitFunc) { + xpcomFunctions.logInitFunc(); + } +} + +XPCOM_API(void) +NS_LogTerm() +{ + if (xpcomFunctions.logTermFunc) { + xpcomFunctions.logTermFunc(); + } +} + +XPCOM_API(void) +NS_LogAddRef(void* aPtr, nsrefcnt aNewRefCnt, + const char* aTypeName, uint32_t aInstanceSize) +{ + if (xpcomFunctions.logAddRefFunc) + xpcomFunctions.logAddRefFunc(aPtr, aNewRefCnt, + aTypeName, aInstanceSize); +} + +XPCOM_API(void) +NS_LogRelease(void* aPtr, nsrefcnt aNewRefCnt, const char* aTypeName) +{ + if (xpcomFunctions.logReleaseFunc) { + xpcomFunctions.logReleaseFunc(aPtr, aNewRefCnt, aTypeName); + } +} + +XPCOM_API(void) +NS_LogCtor(void* aPtr, const char* aTypeName, uint32_t aInstanceSize) +{ + if (xpcomFunctions.logCtorFunc) { + xpcomFunctions.logCtorFunc(aPtr, aTypeName, aInstanceSize); + } +} + +XPCOM_API(void) +NS_LogDtor(void* aPtr, const char* aTypeName, uint32_t aInstanceSize) +{ + if (xpcomFunctions.logDtorFunc) { + xpcomFunctions.logDtorFunc(aPtr, aTypeName, aInstanceSize); + } +} + +XPCOM_API(void) +NS_LogCOMPtrAddRef(void* aCOMPtr, nsISupports* aObject) +{ + if (xpcomFunctions.logCOMPtrAddRefFunc) { + xpcomFunctions.logCOMPtrAddRefFunc(aCOMPtr, aObject); + } +} + +XPCOM_API(void) +NS_LogCOMPtrRelease(void* aCOMPtr, nsISupports* aObject) +{ + if (xpcomFunctions.logCOMPtrReleaseFunc) { + xpcomFunctions.logCOMPtrReleaseFunc(aCOMPtr, aObject); + } +} + +XPCOM_API(nsresult) +NS_GetXPTCallStub(REFNSIID aIID, nsIXPTCProxy* aOuter, + nsISomeInterface** aStub) +{ + if (!xpcomFunctions.getXPTCallStubFunc) { + return NS_ERROR_NOT_INITIALIZED; + } + + return xpcomFunctions.getXPTCallStubFunc(aIID, aOuter, aStub); +} + +XPCOM_API(void) +NS_DestroyXPTCallStub(nsISomeInterface* aStub) +{ + if (xpcomFunctions.destroyXPTCallStubFunc) { + xpcomFunctions.destroyXPTCallStubFunc(aStub); + } +} + +XPCOM_API(nsresult) +NS_InvokeByIndex(nsISupports* aThat, uint32_t aMethodIndex, + uint32_t aParamCount, nsXPTCVariant* aParams) +{ + if (!xpcomFunctions.invokeByIndexFunc) { + return NS_ERROR_NOT_INITIALIZED; + } + + return xpcomFunctions.invokeByIndexFunc(aThat, aMethodIndex, + aParamCount, aParams); +} + +XPCOM_API(bool) +NS_CycleCollectorSuspect(nsISupports* aObj) +{ + if (!xpcomFunctions.cycleSuspectFunc) { + return false; + } + + return xpcomFunctions.cycleSuspectFunc(aObj); +} + +XPCOM_API(bool) +NS_CycleCollectorForget(nsISupports* aObj) +{ + if (!xpcomFunctions.cycleForgetFunc) { + return false; + } + + return xpcomFunctions.cycleForgetFunc(aObj); +} + +XPCOM_API(nsPurpleBufferEntry*) +NS_CycleCollectorSuspect2(void* aObj, nsCycleCollectionParticipant* aCp) +{ + if (!xpcomFunctions.cycleSuspect2Func) { + return nullptr; + } + + return xpcomFunctions.cycleSuspect2Func(aObj, aCp); +} + +XPCOM_API(void) +NS_CycleCollectorSuspect3(void* aObj, nsCycleCollectionParticipant* aCp, + nsCycleCollectingAutoRefCnt* aRefCnt, + bool* aShouldDelete) +{ + if (xpcomFunctions.cycleSuspect3Func) { + xpcomFunctions.cycleSuspect3Func(aObj, aCp, aRefCnt, aShouldDelete); + } +} + +XPCOM_API(bool) +NS_CycleCollectorForget2(nsPurpleBufferEntry* aEntry) +{ + if (!xpcomFunctions.cycleForget2Func) { + return false; + } + + return xpcomFunctions.cycleForget2Func(aEntry); +} diff --git a/xpcom/glue/standalone/nsXPCOMGlue.h b/xpcom/glue/standalone/nsXPCOMGlue.h new file mode 100644 index 0000000000..e23bfa4981 --- /dev/null +++ b/xpcom/glue/standalone/nsXPCOMGlue.h @@ -0,0 +1,49 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsXPCOMGlue_h__ +#define nsXPCOMGlue_h__ + +#include "nscore.h" + +#ifdef XPCOM_GLUE + +/** + * The following functions are only available in the standalone glue. + */ + +/** + * Enabled preloading of dynamically loaded libraries + */ +extern "C" NS_HIDDEN_(void) XPCOMGlueEnablePreload(); + +/** + * Initialize the XPCOM glue by dynamically linking against the XPCOM + * shared library indicated by xpcomFile. + */ +extern "C" NS_HIDDEN_(nsresult) XPCOMGlueStartup(const char* aXPCOMFile); + +typedef void (*NSFuncPtr)(); + +struct nsDynamicFunctionLoad +{ + const char* functionName; + NSFuncPtr* function; +}; + +/** + * Dynamically load functions from libxul. + * + * @throws NS_ERROR_NOT_INITIALIZED if XPCOMGlueStartup() was not called or + * if the libxul DLL was not found. + * @throws NS_ERROR_LOSS_OF_SIGNIFICANT_DATA if only some of the required + * functions were found. + */ +extern "C" NS_HIDDEN_(nsresult) +XPCOMGlueLoadXULFunctions(const nsDynamicFunctionLoad* aSymbols); + +#endif // XPCOM_GLUE +#endif // nsXPCOMGlue_h__ diff --git a/xpcom/glue/standalone/staticruntime/moz.build b/xpcom/glue/standalone/staticruntime/moz.build new file mode 100644 index 0000000000..735086ab0b --- /dev/null +++ b/xpcom/glue/standalone/staticruntime/moz.build @@ -0,0 +1,50 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +include('../../objs.mozbuild') + +SOURCES += xpcom_glue_src_cppsrcs + +SOURCES += [ + '../../nsStringAPI.cpp', + '../nsXPCOMGlue.cpp', +] + +Library('xpcomglue_staticruntime') + +SDK_LIBRARY = True + +# create a static lib +FORCE_STATIC_LIB = True + +if CONFIG['_MSC_VER']: + DEFINES['_USE_ANSI_CPP'] = True + # Don't include directives about which CRT to use + CFLAGS += ['-Zl'] + CXXFLAGS += ['-Zl'] + +DEFINES['XPCOM_GLUE'] = True + +LOCAL_INCLUDES += [ + '../../../build', + '../../../threads', +] + +# Statically link to the CRT on Windows +USE_STATIC_LIBS = True + +# Don't use STL wrappers here (i.e. wrapped <new>); they require mozalloc +DISABLE_STL_WRAPPING = True + +# Include fallible for third party code using the xpcom glue +USE_LIBS += [ + 'fallible', +] + +# Force to build a static library only +NO_EXPAND_LIBS = True + +DIST_INSTALL = True diff --git a/xpcom/glue/staticruntime/moz.build b/xpcom/glue/staticruntime/moz.build new file mode 100644 index 0000000000..384bc6878f --- /dev/null +++ b/xpcom/glue/staticruntime/moz.build @@ -0,0 +1,48 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +include('../objs.mozbuild') + +UNIFIED_SOURCES += xpcom_gluens_src_cppsrcs +UNIFIED_SOURCES += xpcom_glue_src_cppsrcs + +UNIFIED_SOURCES += [ + '../GenericModule.cpp', + '../nsStringAPI.cpp', +] + +Library('xpcomglue_staticruntime_s') + +SDK_LIBRARY = True + +FORCE_STATIC_LIB = True + +if CONFIG['_MSC_VER']: + DEFINES['_USE_ANSI_CPP'] = True + # Don't include directives about which CRT to use + CFLAGS += ['-Zl'] + CXXFLAGS += ['-Zl'] + +LOCAL_INCLUDES += [ + '../../build', + '../../threads', +] + +# Statically link to the CRT on Windows +USE_STATIC_LIBS = True + +# Don't use STL wrappers here (i.e. wrapped <new>); they require mozalloc +DISABLE_STL_WRAPPING = True + +# Include fallible for third party code using the xpcom glue +USE_LIBS += [ + 'fallible', +] + +# Force to build a static library only +NO_EXPAND_LIBS = True + +DIST_INSTALL = True diff --git a/xpcom/glue/tests/gtest/TestArray.cpp b/xpcom/glue/tests/gtest/TestArray.cpp new file mode 100644 index 0000000000..72d28b4dff --- /dev/null +++ b/xpcom/glue/tests/gtest/TestArray.cpp @@ -0,0 +1,169 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include <stdio.h> +#include <stdlib.h> +#include "gtest/gtest.h" + +// Disable deprecation warnings generated by nsISupportsArray and associated +// classes. +#if defined(__GNUC__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#elif defined(_MSC_VER) +#pragma warning (push) +#pragma warning (disable : 4996) +#endif + +#include "nsISupportsArray.h" + +// {9e70a320-be02-11d1-8031-006008159b5a} +#define NS_IFOO_IID \ + {0x9e70a320, 0xbe02, 0x11d1, \ + {0x80, 0x31, 0x00, 0x60, 0x08, 0x15, 0x9b, 0x5a}} + +namespace TestArray { + +class IFoo : public nsISupports { +public: + + NS_DECLARE_STATIC_IID_ACCESSOR(NS_IFOO_IID) + + NS_IMETHOD_(nsrefcnt) RefCnt() = 0; + NS_IMETHOD_(int32_t) ID() = 0; +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(IFoo, NS_IFOO_IID) + +class Foo final : public IFoo { +public: + + explicit Foo(int32_t aID); + + // nsISupports implementation + NS_DECL_ISUPPORTS + + // IFoo implementation + NS_IMETHOD_(nsrefcnt) RefCnt() override { return mRefCnt; } + NS_IMETHOD_(int32_t) ID() override { return mID; } + + static int32_t gCount; + + int32_t mID; + +private: + ~Foo(); +}; + +int32_t Foo::gCount; + +Foo::Foo(int32_t aID) +{ + mID = aID; + ++gCount; +} + +Foo::~Foo() +{ + --gCount; +} + +NS_IMPL_ISUPPORTS(Foo, IFoo) + +void CheckArray(nsISupportsArray* aArray, int32_t aExpectedCount, int32_t aElementIDs[], int32_t aExpectedTotal) +{ + uint32_t cnt = 0; +#ifdef DEBUG + nsresult rv = +#endif + aArray->Count(&cnt); + NS_ASSERTION(NS_SUCCEEDED(rv), "Count failed"); + int32_t count = cnt; + int32_t index; + + EXPECT_EQ(Foo::gCount, aExpectedTotal); + EXPECT_EQ(count, aExpectedCount); + + for (index = 0; (index < count) && (index < aExpectedCount); index++) { + nsCOMPtr<IFoo> foo = do_QueryElementAt(aArray, index); + EXPECT_EQ(foo->ID(), aElementIDs[index]); + } +} + +void FillArray(nsISupportsArray* aArray, int32_t aCount) +{ + int32_t index; + for (index = 0; index < aCount; index++) { + nsCOMPtr<IFoo> foo = new Foo(index); + aArray->AppendElement(foo); + } +} + +} // namespace TestArray + +using namespace TestArray; + +TEST(Array, main) +{ + nsISupportsArray* array; + nsresult rv; + + if (NS_OK == (rv = NS_NewISupportsArray(&array))) { + FillArray(array, 10); + int32_t fillResult[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; + CheckArray(array, 10, fillResult, 10); + + // test insert + nsCOMPtr<IFoo> foo = do_QueryElementAt(array, 3); + array->InsertElementAt(foo, 5); + int32_t insertResult[11] = {0, 1, 2, 3, 4, 3, 5, 6, 7, 8, 9}; + CheckArray(array, 11, insertResult, 10); + array->InsertElementAt(foo, 0); + int32_t insertResult2[12] = {3, 0, 1, 2, 3, 4, 3, 5, 6, 7, 8, 9}; + CheckArray(array, 12, insertResult2, 10); + array->AppendElement(foo); + int32_t appendResult[13] = {3, 0, 1, 2, 3, 4, 3, 5, 6, 7, 8, 9, 3}; + CheckArray(array, 13, appendResult, 10); + + + // test IndexOf + int32_t expectedIndex = 0; + int32_t index = array->IndexOf(foo); + EXPECT_EQ(index, expectedIndex); + + // test ReplaceElementAt + array->ReplaceElementAt(foo, 8); + int32_t replaceResult[13] = {3, 0, 1, 2, 3, 4, 3, 5, 3, 7, 8, 9, 3}; + CheckArray(array, 13, replaceResult, 9); + + // test RemoveElementAt, RemoveElement + array->RemoveElementAt(0); + int32_t removeResult[12] = {0, 1, 2, 3, 4, 3, 5, 3, 7, 8, 9, 3}; + CheckArray(array, 12, removeResult, 9); + array->RemoveElementAt(7); + int32_t removeResult2[11] = {0, 1, 2, 3, 4, 3, 5, 7, 8, 9, 3}; + CheckArray(array, 11, removeResult2, 9); + array->RemoveElement(foo); + int32_t removeResult3[10] = {0, 1, 2, 4, 3, 5, 7, 8, 9, 3}; + CheckArray(array, 10, removeResult3, 9); + + foo = nullptr; + + // test clear + array->Clear(); + FillArray(array, 4); + CheckArray(array, 4, fillResult, 4); + + // test delete + NS_RELEASE(array); + } +} + +#if defined(__GNUC__) +#pragma GCC diagnostic pop +#elif defined(_MSC_VER) +#pragma warning (pop) +#endif diff --git a/xpcom/glue/tests/gtest/TestFileUtils.cpp b/xpcom/glue/tests/gtest/TestFileUtils.cpp new file mode 100644 index 0000000000..55106c6c5f --- /dev/null +++ b/xpcom/glue/tests/gtest/TestFileUtils.cpp @@ -0,0 +1,283 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +/* Test ReadSysFile() */ + +#include <sys/types.h> +#include <sys/stat.h> + +#include <errno.h> +#include <fcntl.h> +#include <stdio.h> +#include <string.h> + +#include "FileUtils.h" + +#include "gtest/gtest.h" + +namespace mozilla { + +#ifdef ReadSysFile_PRESENT + +/** + * Create a file with the specified contents. + */ +static bool +WriteFile( + const char* aFilename, + const void* aContents, + size_t aContentsLen) +{ + int fd; + ssize_t ret; + size_t offt; + + fd = MOZ_TEMP_FAILURE_RETRY( + open(aFilename, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR)); + if (fd == -1) { + fprintf(stderr, "open(): %s: %s\n", aFilename, strerror(errno)); + return false; + } + + offt = 0; + do { + ret = MOZ_TEMP_FAILURE_RETRY( + write(fd, (char*)aContents + offt, aContentsLen - offt)); + if (ret == -1) { + fprintf(stderr, "write(): %s: %s\n", aFilename, strerror(errno)); + close(fd); + return false; + } + offt += ret; + } while (offt < aContentsLen); + + ret = MOZ_TEMP_FAILURE_RETRY(close(fd)); + if (ret == -1) { + fprintf(stderr, "close(): %s: %s\n", aFilename, strerror(errno)); + return false; + } + return true; +} + +TEST(ReadSysFile, Nonexistent) { + bool ret; + int errno_saved; + + ret = ReadSysFile("/nonexistent", nullptr, 0); + errno_saved = errno; + + ASSERT_FALSE(ret); + ASSERT_EQ(errno_saved, ENOENT); +} + +TEST(ReadSysFile, Main) { + /* Use a different file name for each test since different tests could be + executed concurrently. */ + static const char* fn = "TestReadSysFileMain"; + /* If we have a file which contains "abcd" and we read it with ReadSysFile(), + providing a buffer of size 10 bytes, we would expect 5 bytes to be written + to that buffer: "abcd\0". */ + struct + { + /* input (file contents), e.g. "abcd" */ + const char* input; + /* pretended output buffer size, e.g. 10; the actual buffer is larger + and we check if anything was written past the end of the allowed length */ + size_t output_size; + /* expected number of bytes written to the output buffer, including the + terminating '\0', e.g. 5 */ + size_t output_len; + /* expected output buffer contents, e.g. "abcd\0", the first output_len + bytes of the output buffer should match the first 'output_len' bytes from + 'output', the rest of the output buffer should be untouched. */ + const char* output; + } tests[] = { + /* No new lines */ + {"", 0, 0, ""}, + {"", 1, 1, "\0"}, /* \0 is redundant, but we write it for clarity */ + {"", 9, 1, "\0"}, + + {"a", 0, 0, ""}, + {"a", 1, 1, "\0"}, + {"a", 2, 2, "a\0"}, + {"a", 9, 2, "a\0"}, + + {"abcd", 0, 0, ""}, + {"abcd", 1, 1, "\0"}, + {"abcd", 2, 2, "a\0"}, + {"abcd", 3, 3, "ab\0"}, + {"abcd", 4, 4, "abc\0"}, + {"abcd", 5, 5, "abcd\0"}, + {"abcd", 9, 5, "abcd\0"}, + + /* A single trailing new line */ + {"\n", 0, 0, ""}, + {"\n", 1, 1, "\0"}, + {"\n", 2, 1, "\0"}, + {"\n", 9, 1, "\0"}, + + {"a\n", 0, 0, ""}, + {"a\n", 1, 1, "\0"}, + {"a\n", 2, 2, "a\0"}, + {"a\n", 3, 2, "a\0"}, + {"a\n", 9, 2, "a\0"}, + + {"abcd\n", 0, 0, ""}, + {"abcd\n", 1, 1, "\0"}, + {"abcd\n", 2, 2, "a\0"}, + {"abcd\n", 3, 3, "ab\0"}, + {"abcd\n", 4, 4, "abc\0"}, + {"abcd\n", 5, 5, "abcd\0"}, + {"abcd\n", 6, 5, "abcd\0"}, + {"abcd\n", 9, 5, "abcd\0"}, + + /* Multiple trailing new lines */ + {"\n\n", 0, 0, ""}, + {"\n\n", 1, 1, "\0"}, + {"\n\n", 2, 2, "\n\0"}, + {"\n\n", 3, 2, "\n\0"}, + {"\n\n", 9, 2, "\n\0"}, + + {"a\n\n", 0, 0, ""}, + {"a\n\n", 1, 1, "\0"}, + {"a\n\n", 2, 2, "a\0"}, + {"a\n\n", 3, 3, "a\n\0"}, + {"a\n\n", 4, 3, "a\n\0"}, + {"a\n\n", 9, 3, "a\n\0"}, + + {"abcd\n\n", 0, 0, ""}, + {"abcd\n\n", 1, 1, "\0"}, + {"abcd\n\n", 2, 2, "a\0"}, + {"abcd\n\n", 3, 3, "ab\0"}, + {"abcd\n\n", 4, 4, "abc\0"}, + {"abcd\n\n", 5, 5, "abcd\0"}, + {"abcd\n\n", 6, 6, "abcd\n\0"}, + {"abcd\n\n", 7, 6, "abcd\n\0"}, + {"abcd\n\n", 9, 6, "abcd\n\0"}, + + /* New line in the middle */ + {"ab\ncd", 9, 6, "ab\ncd\0"}, + }; + + for (size_t i = 0; i < sizeof(tests) / sizeof(tests[0]); i++) { + ASSERT_TRUE(WriteFile(fn, tests[i].input, strlen(tests[i].input))); + /* Leave the file to exist if some of the assertions fail. */ + + char buf[128]; + static const char unmodified = 'X'; + + memset(buf, unmodified, sizeof(buf)); + + ASSERT_TRUE(ReadSysFile(fn, buf, tests[i].output_size)); + + if (tests[i].output_size == 0) { + /* The buffer must be unmodified. We check only the first byte. */ + ASSERT_EQ(unmodified, buf[0]); + } else { + ASSERT_EQ(tests[i].output_len, strlen(buf) + 1); + ASSERT_STREQ(tests[i].output, buf); + /* Check that the first byte after the trailing '\0' has not been + modified. */ + ASSERT_EQ(unmodified, buf[tests[i].output_len]); + } + } + + unlink(fn); +} + +TEST(ReadSysFile, Int) { + static const char* fn = "TestReadSysFileInt"; + struct + { + /* input (file contents), e.g. "5" */ + const char* input; + /* expected return value, if false, then the output is not checked */ + bool ret; + /* expected result */ + int output; + } tests[] = { + {"0", true, 0}, + {"00", true, 0}, + {"1", true, 1}, + {"5", true, 5}, + {"55", true, 55}, + + {" 123", true, 123}, + {"123 ", true, 123}, + {" 123 ", true, 123}, + {"123\n", true, 123}, + + {"", false, 0}, + {" ", false, 0}, + {"a", false, 0}, + + {"-1", true, -1}, + {" -456 ", true, -456}, + {" -78.9 ", true, -78}, + }; + + for (size_t i = 0; i < sizeof(tests) / sizeof(tests[0]); i++) { + ASSERT_TRUE(WriteFile(fn, tests[i].input, strlen(tests[i].input))); + /* Leave the file to exist if some of the assertions fail. */ + + bool ret; + int output = 424242; + + ret = ReadSysFile(fn, &output); + + ASSERT_EQ(tests[i].ret, ret); + + if (ret) { + ASSERT_EQ(tests[i].output, output); + } + } + + unlink(fn); +} + +TEST(ReadSysFile, Bool) { + static const char* fn = "TestReadSysFileBool"; + struct + { + /* input (file contents), e.g. "1" */ + const char* input; + /* expected return value */ + bool ret; + /* expected result */ + bool output; + } tests[] = { + {"0", true, false}, + {"00", true, false}, + {"1", true, true}, + {"5", true, true}, + {"23", true, true}, + {"-1", true, true}, + + {"", false, true /* unused */}, + }; + + for (size_t i = 0; i < sizeof(tests) / sizeof(tests[0]); i++) { + ASSERT_TRUE(WriteFile(fn, tests[i].input, strlen(tests[i].input))); + /* Leave the file to exist if some of the assertions fail. */ + + bool ret; + bool output; + + ret = ReadSysFile(fn, &output); + + ASSERT_EQ(tests[i].ret, ret); + + if (ret) { + ASSERT_EQ(tests[i].output, output); + } + } + + unlink(fn); +} + +#endif /* ReadSysFile_PRESENT */ + +} // namespace mozilla diff --git a/xpcom/glue/tests/gtest/TestGCPostBarriers.cpp b/xpcom/glue/tests/gtest/TestGCPostBarriers.cpp new file mode 100644 index 0000000000..5bf10ab059 --- /dev/null +++ b/xpcom/glue/tests/gtest/TestGCPostBarriers.cpp @@ -0,0 +1,140 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * Tests that generational garbage collection post-barriers are correctly + * implemented for nsTArrays that contain JavaScript Values. + */ + +#include "jsapi.h" +#include "nsTArray.h" + +#include "gtest/gtest.h" + +#include "js/TracingAPI.h" +#include "js/HeapAPI.h" + +#include "mozilla/CycleCollectedJSContext.h" + +using namespace JS; +using namespace mozilla; + +template<class ArrayT> +static void +TraceArray(JSTracer* trc, void* data) +{ + ArrayT* array = static_cast<ArrayT *>(data); + for (unsigned i = 0; i < array->Length(); ++i) + JS::TraceEdge(trc, &array->ElementAt(i), "array-element"); +} + +/* + * Use arrays with initial size much smaller than the final number of elements + * to test that moving Heap<T> elements works correctly. + */ +const size_t ElementCount = 100; +const size_t InitialElements = ElementCount / 10; + +template<class ArrayT> +static void +RunTest(JSContext* cx, ArrayT* array) +{ + JS_GC(cx); + + ASSERT_TRUE(array != nullptr); + JS_AddExtraGCRootsTracer(cx, TraceArray<ArrayT>, array); + + /* + * Create the array and fill it with new JS objects. With GGC these will be + * allocated in the nursery. + */ + RootedValue value(cx); + const char* property = "foo"; + for (size_t i = 0; i < ElementCount; ++i) { + RootedObject obj(cx, JS_NewPlainObject(cx)); + ASSERT_FALSE(JS::ObjectIsTenured(obj)); + value = Int32Value(i); + ASSERT_TRUE(JS_SetProperty(cx, obj, property, value)); + ASSERT_TRUE(array->AppendElement(obj, fallible)); + } + + /* + * If postbarriers are not working, we will crash here when we try to mark + * objects that have been moved to the tenured heap. + */ + JS_GC(cx); + + /* + * Sanity check that our array contains what we expect. + */ + for (size_t i = 0; i < ElementCount; ++i) { + RootedObject obj(cx, array->ElementAt(i)); + ASSERT_TRUE(JS::ObjectIsTenured(obj)); + ASSERT_TRUE(JS_GetProperty(cx, obj, property, &value)); + ASSERT_TRUE(value.isInt32()); + ASSERT_EQ(static_cast<int32_t>(i), value.toInt32()); + } + + JS_RemoveExtraGCRootsTracer(cx, TraceArray<ArrayT>, array); +} + +static void +CreateGlobalAndRunTest(JSContext* cx) +{ + static const JSClassOps GlobalClassOps = { + nullptr, nullptr, nullptr, nullptr, + nullptr, nullptr, nullptr, nullptr, + nullptr, nullptr, nullptr, + JS_GlobalObjectTraceHook + }; + + static const JSClass GlobalClass = { + "global", JSCLASS_GLOBAL_FLAGS, + &GlobalClassOps + }; + + JS::CompartmentOptions options; + options.behaviors().setVersion(JSVERSION_LATEST); + JS::PersistentRootedObject global(cx); + global = JS_NewGlobalObject(cx, &GlobalClass, nullptr, JS::FireOnNewGlobalHook, options); + ASSERT_TRUE(global != nullptr); + + JSCompartment *oldCompartment = JS_EnterCompartment(cx, global); + + typedef Heap<JSObject*> ElementT; + + { + nsTArray<ElementT>* array = new nsTArray<ElementT>(InitialElements); + RunTest(cx, array); + delete array; + } + + { + FallibleTArray<ElementT>* array = new FallibleTArray<ElementT>(InitialElements); + RunTest(cx, array); + delete array; + } + + { + AutoTArray<ElementT, InitialElements> array; + RunTest(cx, &array); + } + + JS_LeaveCompartment(cx, oldCompartment); +} + +TEST(GCPostBarriers, nsTArray) { + CycleCollectedJSContext* ccjscx = CycleCollectedJSContext::Get(); + ASSERT_TRUE(ccjscx != nullptr); + JSContext* cx = ccjscx->Context(); + ASSERT_TRUE(cx != nullptr); + + JS_BeginRequest(cx); + + CreateGlobalAndRunTest(cx); + + JS_EndRequest(cx); +} diff --git a/xpcom/glue/tests/gtest/TestNsDeque.cpp b/xpcom/glue/tests/gtest/TestNsDeque.cpp new file mode 100644 index 0000000000..b84e1b781c --- /dev/null +++ b/xpcom/glue/tests/gtest/TestNsDeque.cpp @@ -0,0 +1,342 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "gtest/gtest.h" +#include "nsDeque.h" +#include "nsCRT.h" +#include <stdio.h> + +/************************************************************** + Now define the token deallocator class... + **************************************************************/ +namespace TestNsDeque { + + class _Dealloc: public nsDequeFunctor + { + virtual void* operator()(void* aObject) { + return 0; + } + }; + + static bool VerifyContents(const nsDeque& aDeque, const int* aContents, size_t aLength) + { + for (size_t i=0; i<aLength; ++i) { + if (*(int*)aDeque.ObjectAt(i) != aContents[i]) { + return false; + } + } + return true; + } + + class Deallocator: public nsDequeFunctor + { + virtual void* operator()(void* aObject) + { + if (aObject) + { + // Set value to -1, to use in test function. + *((int*)aObject) = -1; + } + + return nullptr; + } + }; + + class ForEachAdder: public nsDequeFunctor + { + virtual void* operator()(void* aObject) + { + if (aObject) + { + sum += *(int*)aObject; + } + + return aObject; + } + + private: + int sum = 0; + + public: + int GetSum() { return sum; } + + }; +} + +using namespace TestNsDeque; + +TEST(NsDeque, OriginalTest) +{ + const size_t size = 200; + int ints[size]; + size_t i=0; + int temp; + nsDeque theDeque(new _Dealloc); //construct a simple one... + + // ints = [0...199] + for (i=0;i<size;i++) { //initialize'em + ints[i]=static_cast<int>(i); + } + // queue = [0...69] + for (i=0;i<70;i++) { + theDeque.Push(&ints[i]); + temp=*(int*)theDeque.Peek(); + EXPECT_EQ(static_cast<int>(i), temp) << "Verify end after push #1"; + EXPECT_EQ(i + 1, theDeque.GetSize()) << "Verify size after push #1"; + } + + EXPECT_EQ(70u,theDeque.GetSize()) << "Verify overall size after pushes #1"; + + // queue = [0...14] + for (i=1;i<=55;i++) { + temp=*(int*)theDeque.Pop(); + EXPECT_EQ(70-static_cast<int>(i),temp) << "Verify end after pop # 1"; + EXPECT_EQ(70u - i,theDeque.GetSize()) << "Verify size after pop # 1"; + } + EXPECT_EQ(15u,theDeque.GetSize()) << "Verify overall size after pops"; + + // queue = [0...14,0...54] + for (i=0;i<55;i++) { + theDeque.Push(&ints[i]); + temp=*(int*)theDeque.Peek(); + EXPECT_EQ(static_cast<int>(i),temp) << "Verify end after push #2"; + EXPECT_EQ(i + 15u + 1,theDeque.GetSize()) << "Verify size after push # 2"; + } + EXPECT_EQ(70u,theDeque.GetSize()) << "Verify size after end of all pushes #2"; + + // queue = [0...14,0...19] + for (i=1;i<=35;i++) { + temp=*(int*)theDeque.Pop(); + EXPECT_EQ(55-static_cast<int>(i),temp ) << "Verify end after pop # 2"; + EXPECT_EQ(70u - i,theDeque.GetSize()) << "Verify size after pop #2"; + } + EXPECT_EQ(35u,theDeque.GetSize()) << "Verify overall size after end of all pops #2"; + + // queue = [0...14,0...19,0...34] + for (i=0;i<35;i++) { + theDeque.Push(&ints[i]); + temp = *(int*)theDeque.Peek(); + EXPECT_EQ(static_cast<int>(i),temp) << "Verify end after push # 3"; + EXPECT_EQ(35u + 1u + i,theDeque.GetSize()) << "Verify size after push #3"; + } + + // queue = [0...14,0...19] + for (i=0;i<35;i++) { + temp=*(int*)theDeque.Pop(); + EXPECT_EQ(34 - static_cast<int>(i), temp) << "Verify end after pop # 3"; + } + + // queue = [0...14] + for (i=0;i<20;i++) { + temp=*(int*)theDeque.Pop(); + EXPECT_EQ(19 - static_cast<int>(i),temp) << "Verify end after pop # 4"; + } + + // queue = [] + for (i=0;i<15;i++) { + temp=*(int*)theDeque.Pop(); + EXPECT_EQ(14 - static_cast<int>(i),temp) << "Verify end after pop # 5"; + } + + EXPECT_EQ(0u,theDeque.GetSize()) << "Deque should finish empty."; +} + +TEST(NsDeque, OriginalFlaw) +{ + int ints[200]; + int i=0; + int temp; + nsDeque d(new _Dealloc); + /** + * Test 1. Origin near end, semi full, call Peek(). + * you start, mCapacity is 8 + */ + for (i=0; i<30; i++) + ints[i]=i; + + for (i=0; i<6; i++) { + d.Push(&ints[i]); + temp = *(int*)d.Peek(); + EXPECT_EQ(i, temp) << "OriginalFlaw push #1"; + } + EXPECT_EQ(6u, d.GetSize()) << "OriginalFlaw size check #1"; + + for (i=0; i<4; i++) { + temp=*(int*)d.PopFront(); + EXPECT_EQ(i, temp) << "PopFront test"; + } + // d = [4,5] + EXPECT_EQ(2u, d.GetSize()) << "OriginalFlaw size check #2"; + + for (i=0; i<4; i++) { + d.Push(&ints[6 + i]); + } + + // d = [4...9] + for (i=4; i<=9; i++) { + temp=*(int*)d.PopFront(); + EXPECT_EQ(i, temp) << "OriginalFlaw empty check"; + } +} + +TEST(NsDeque, TestObjectAt) +{ + nsDeque d; + const int count = 10; + int ints[count]; + for (int i=0; i<count; i++) { + ints[i] = i; + } + + for (int i=0; i<6; i++) { + d.Push(&ints[i]); + } + // d = [0...5] + d.PopFront(); + d.PopFront(); + + // d = [2..5] + for (size_t i=2; i<=5; i++) { + int t = *(int*)d.ObjectAt(i-2); + EXPECT_EQ(static_cast<int>(i),t) << "Verify ObjectAt()"; + } +} + +TEST(NsDeque, TestPushFront) +{ + // PushFront has some interesting corner cases, primarily we're interested in whether: + // - wrapping around works properly + // - growing works properly + + nsDeque d; + + const int kPoolSize = 10; + const size_t kMaxSizeBeforeGrowth = 8; + + int pool[kPoolSize]; + for (int i = 0; i < kPoolSize; i++) { + pool[i] = i; + } + + for (size_t i = 0; i < kMaxSizeBeforeGrowth; i++) { + d.PushFront(pool + i); + } + + EXPECT_EQ(kMaxSizeBeforeGrowth, d.GetSize()) << "verify size"; + + static const int t1[] = {7,6,5,4,3,2,1,0}; + EXPECT_TRUE(VerifyContents(d, t1, kMaxSizeBeforeGrowth)) << "verify pushfront 1"; + + // Now push one more so it grows + d.PushFront(pool + kMaxSizeBeforeGrowth); + EXPECT_EQ(kMaxSizeBeforeGrowth + 1, d.GetSize()) << "verify size"; + + static const int t2[] = {8,7,6,5,4,3,2,1,0}; + EXPECT_TRUE(VerifyContents(d, t2, kMaxSizeBeforeGrowth + 1)) << "verify pushfront 2"; + + // And one more so that it wraps again + d.PushFront(pool + kMaxSizeBeforeGrowth + 1); + EXPECT_EQ(kMaxSizeBeforeGrowth + 2, d.GetSize()) << "verify size"; + + static const int t3[] = {9,8,7,6,5,4,3,2,1,0}; + EXPECT_TRUE(VerifyContents(d, t3, kMaxSizeBeforeGrowth + 2)) <<"verify pushfront 3"; +} + +void CheckIfQueueEmpty(nsDeque& d) +{ + EXPECT_EQ(0u, d.GetSize()) << "Size should be 0"; + EXPECT_EQ(nullptr, d.Pop()) << "Invalid operation should return nullptr"; + EXPECT_EQ(nullptr, d.PopFront()) << "Invalid operation should return nullptr"; + EXPECT_EQ(nullptr, d.Peek()) << "Invalid operation should return nullptr"; + EXPECT_EQ(nullptr, d.PeekFront()) << "Invalid operation should return nullptr"; + EXPECT_EQ(nullptr, d.ObjectAt(0u)) << "Invalid operation should return nullptr"; +} + +TEST(NsDeque,TestEmpty) +{ + // Make sure nsDeque gives sane results if it's empty. + nsDeque d; + size_t numberOfEntries = 8; + + CheckIfQueueEmpty(d); + + // Fill it up and drain it. + for (size_t i = 0; i < numberOfEntries; i++) { + d.Push((void*)0xAA); + } + + EXPECT_EQ(numberOfEntries, d.GetSize()); + + for (size_t i = 0; i < numberOfEntries; i++) { + (void)d.Pop(); + } + + // Now check it again. + CheckIfQueueEmpty(d); +} + +TEST(NsDeque,TestEraseMethod) +{ + nsDeque d; + const size_t numberOfEntries = 8; + + // Fill it up before calling Erase + for (size_t i = 0; i < numberOfEntries; i++) { + d.Push((void*)0xAA); + } + + // Call Erase + d.Erase(); + + // Now check it again. + CheckIfQueueEmpty(d); +} + +TEST(NsDeque,TestEraseShouldCallDeallocator) +{ + nsDeque d(new Deallocator()); + const size_t NumTestValues = 8; + + int* testArray[NumTestValues]; + for (size_t i=0; i < NumTestValues; i++) + { + testArray[i] = new int(); + *(testArray[i]) = i; + d.Push((void*)testArray[i]); + } + + d.Erase(); + + // Now check it again. + CheckIfQueueEmpty(d); + + for (size_t i=0; i < NumTestValues; i++) + { + EXPECT_EQ(-1, *(testArray[i])) << "Erase should call deallocator: " << *(testArray[i]); + } +} + +TEST(NsDeque, TestForEach) +{ + nsDeque d(new Deallocator()); + const size_t NumTestValues = 8; + int sum = 0; + + int* testArray[NumTestValues]; + for (size_t i=0; i < NumTestValues; i++) + { + testArray[i] = new int(); + *(testArray[i]) = i; + sum += i; + d.Push((void*)testArray[i]); + } + + ForEachAdder adder; + d.ForEach(adder); + EXPECT_EQ(sum, adder.GetSum()) << "For each should iterate over values"; + + d.Erase(); +} diff --git a/xpcom/glue/tests/gtest/TestThreadUtils.cpp b/xpcom/glue/tests/gtest/TestThreadUtils.cpp new file mode 100644 index 0000000000..728bae612a --- /dev/null +++ b/xpcom/glue/tests/gtest/TestThreadUtils.cpp @@ -0,0 +1,937 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include <stdio.h> +#include <stdlib.h> +#include "nsThreadUtils.h" +#include "gtest/gtest.h" + +// {9e70a320-be02-11d1-8031-006008159b5a} +#define NS_IFOO_IID \ + {0x9e70a320, 0xbe02, 0x11d1, \ + {0x80, 0x31, 0x00, 0x60, 0x08, 0x15, 0x9b, 0x5a}} + +namespace TestThreadUtils { + +static bool gDebug = false; +static int gAlive, gZombies; +static int gAllConstructions, gConstructions, gCopyConstructions, + gMoveConstructions, gDestructions, gAssignments, gMoves; +struct Spy +{ + static void ClearActions() + { + gAllConstructions = gConstructions = gCopyConstructions + = gMoveConstructions = gDestructions = gAssignments = gMoves = 0; + } + static void ClearAll() + { + ClearActions(); + gAlive = 0; + } + + explicit Spy(int aID) : mID(aID) + { + ++gAlive; ++gAllConstructions; ++gConstructions; + if (gDebug) { printf("Spy[%d@%p]()\n", mID, this); } + } + Spy(const Spy& o) : mID(o.mID) + { + ++gAlive; ++gAllConstructions; ++gCopyConstructions; + if (gDebug) { printf("Spy[%d@%p](&[%d@%p])\n", mID, this, o.mID, &o); } + } + Spy(Spy&& o) : mID(o.mID) + { + o.mID = -o.mID; + ++gZombies; ++gAllConstructions; ++gMoveConstructions; + if (gDebug) { printf("Spy[%d@%p](&&[%d->%d@%p])\n", mID, this, -o.mID, o.mID, &o); } + } + ~Spy() + { + if (mID >= 0) { --gAlive; } else { --gZombies; } ++gDestructions; + if (gDebug) { printf("~Spy[%d@%p]()\n", mID, this); } + mID = 0; + } + Spy& operator=(const Spy& o) + { + ++gAssignments; + if (gDebug) { printf("Spy[%d->%d@%p] = &[%d@%p]\n", mID, o.mID, this, o.mID, &o); } + mID = o.mID; + return *this; + }; + Spy& operator=(Spy&& o) + { + --gAlive; ++gZombies; + ++gMoves; + if (gDebug) { printf("Spy[%d->%d@%p] = &&[%d->%d@%p]\n", mID, o.mID, this, o.mID, -o.mID, &o); } + mID = o.mID; o.mID = -o.mID; + return *this; + }; + + int mID; // ID given at construction, or negation if was moved from; 0 when destroyed. +}; + +struct ISpyWithISupports : public nsISupports +{ + NS_DECLARE_STATIC_IID_ACCESSOR(NS_IFOO_IID) + NS_IMETHOD_(nsrefcnt) RefCnt() = 0; + NS_IMETHOD_(int32_t) ID() = 0; +}; +NS_DEFINE_STATIC_IID_ACCESSOR(ISpyWithISupports, NS_IFOO_IID) +struct SpyWithISupports : public ISpyWithISupports, public Spy +{ +private: + virtual ~SpyWithISupports() = default; +public: + explicit SpyWithISupports(int aID) : Spy(aID) {}; + NS_DECL_ISUPPORTS + NS_IMETHOD_(nsrefcnt) RefCnt() override { return mRefCnt; } + NS_IMETHOD_(int32_t) ID() override { return mID; } +}; +NS_IMPL_ISUPPORTS(SpyWithISupports, ISpyWithISupports) + + +class IThreadUtilsObject : public nsISupports +{ +public: + NS_DECLARE_STATIC_IID_ACCESSOR(NS_IFOO_IID) + + NS_IMETHOD_(nsrefcnt) RefCnt() = 0; + NS_IMETHOD_(int32_t) ID() = 0; +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(IThreadUtilsObject, NS_IFOO_IID) + +struct ThreadUtilsObject : public IThreadUtilsObject +{ + // nsISupports implementation + NS_DECL_ISUPPORTS + + // IThreadUtilsObject implementation + NS_IMETHOD_(nsrefcnt) RefCnt() override { return mRefCnt; } + NS_IMETHOD_(int32_t) ID() override { return 0; } + + int mCount; // Number of calls + arguments processed. + int mA0, mA1, mA2, mA3; + Spy mSpy; const Spy* mSpyPtr; + ThreadUtilsObject() + : mCount(0) + , mA0(0), mA1(0), mA2(0), mA3(0) + , mSpy(1), mSpyPtr(nullptr) + {} +private: + virtual ~ThreadUtilsObject() = default; +public: + void Test0() { mCount += 1; } + void Test1i(int a0) { mCount += 2; mA0 = a0; } + void Test2i(int a0, int a1) { mCount += 3; mA0 = a0; mA1 = a1; } + void Test3i(int a0, int a1, int a2) + { + mCount += 4; mA0 = a0; mA1 = a1; mA2 = a2; + } + void Test4i(int a0, int a1, int a2, int a3) + { + mCount += 5; mA0 = a0; mA1 = a1; mA2 = a2; mA3 = a3; + } + void Test1pi(int* ap) + { + mCount += 2; mA0 = ap ? *ap : -1; + } + void Test1pci(const int* ap) + { + mCount += 2; mA0 = ap ? *ap : -1; + } + void Test1ri(int& ar) + { + mCount += 2; mA0 = ar; + } + void Test1rri(int&& arr) + { + mCount += 2; mA0 = arr; + } + void Test1upi(mozilla::UniquePtr<int> aup) + { + mCount += 2; mA0 = aup ? *aup : -1; + } + void Test1rupi(mozilla::UniquePtr<int>& aup) + { + mCount += 2; mA0 = aup ? *aup : -1; + } + void Test1rrupi(mozilla::UniquePtr<int>&& aup) + { + mCount += 2; mA0 = aup ? *aup : -1; + } + + void Test1s(Spy) { mCount += 2; } + void Test1ps(Spy*) { mCount += 2; } + void Test1rs(Spy&) { mCount += 2; } + void Test1rrs(Spy&&) { mCount += 2; } + void Test1ups(mozilla::UniquePtr<Spy>) { mCount += 2; } + void Test1rups(mozilla::UniquePtr<Spy>&) { mCount += 2; } + void Test1rrups(mozilla::UniquePtr<Spy>&&) { mCount += 2; } + + // Possible parameter passing styles: + void TestByValue(Spy s) + { + if (gDebug) { printf("TestByValue(Spy[%d@%p])\n", s.mID, &s); } + mSpy = s; + }; + void TestByConstLRef(const Spy& s) + { + if (gDebug) { printf("TestByConstLRef(Spy[%d@%p]&)\n", s.mID, &s); } + mSpy = s; + }; + void TestByRRef(Spy&& s) + { + if (gDebug) { printf("TestByRRef(Spy[%d@%p]&&)\n", s.mID, &s); } + mSpy = mozilla::Move(s); + }; + void TestByLRef(Spy& s) + { + if (gDebug) { printf("TestByLRef(Spy[%d@%p]&)\n", s.mID, &s); } + mSpy = s; + mSpyPtr = &s; + }; + void TestByPointer(Spy* p) + { + if (p) { + if (gDebug) { printf("TestByPointer(&Spy[%d@%p])\n", p->mID, p); } + mSpy = *p; + } else { + if (gDebug) { printf("TestByPointer(nullptr)\n"); } + } + mSpyPtr = p; + }; + void TestByPointerToConst(const Spy* p) + { + if (p) { + if (gDebug) { printf("TestByPointerToConst(&Spy[%d@%p])\n", p->mID, p); } + mSpy = *p; + } else { + if (gDebug) { printf("TestByPointerToConst(nullptr)\n"); } + } + mSpyPtr = p; + }; +}; + +NS_IMPL_ISUPPORTS(ThreadUtilsObject, IThreadUtilsObject) + +class ThreadUtilsRefCountedFinal final +{ +public: + ThreadUtilsRefCountedFinal() : m_refCount(0) {} + ~ThreadUtilsRefCountedFinal() {} + // 'AddRef' and 'Release' methods with different return types, to verify + // that the return type doesn't influence storage selection. + long AddRef(void) { return ++m_refCount; } + void Release(void) { --m_refCount; } +private: + long m_refCount; +}; + +class ThreadUtilsRefCountedBase +{ +public: + ThreadUtilsRefCountedBase() : m_refCount(0) {} + virtual ~ThreadUtilsRefCountedBase() {} + // 'AddRef' and 'Release' methods with different return types, to verify + // that the return type doesn't influence storage selection. + virtual void AddRef(void) { ++m_refCount; } + virtual MozExternalRefCountType Release(void) { return --m_refCount; } +private: + MozExternalRefCountType m_refCount; +}; + +class ThreadUtilsRefCountedDerived + : public ThreadUtilsRefCountedBase +{}; + +class ThreadUtilsNonRefCounted +{}; + +} // namespace TestThreadUtils + +TEST(ThreadUtils, main) +{ +#ifndef XPCOM_GLUE_AVOID_NSPR + using namespace TestThreadUtils; + + static_assert(!IsParameterStorageClass<int>::value, + "'int' should not be recognized as Storage Class"); + static_assert(IsParameterStorageClass<StoreCopyPassByValue<int>>::value, + "StoreCopyPassByValue<int> should be recognized as Storage Class"); + static_assert(IsParameterStorageClass<StoreCopyPassByConstLRef<int>>::value, + "StoreCopyPassByConstLRef<int> should be recognized as Storage Class"); + static_assert(IsParameterStorageClass<StoreCopyPassByLRef<int>>::value, + "StoreCopyPassByLRef<int> should be recognized as Storage Class"); + static_assert(IsParameterStorageClass<StoreCopyPassByRRef<int>>::value, + "StoreCopyPassByRRef<int> should be recognized as Storage Class"); + static_assert(IsParameterStorageClass<StoreRefPassByLRef<int>>::value, + "StoreRefPassByLRef<int> should be recognized as Storage Class"); + static_assert(IsParameterStorageClass<StoreConstRefPassByConstLRef<int>>::value, + "StoreConstRefPassByConstLRef<int> should be recognized as Storage Class"); + static_assert(IsParameterStorageClass<StorensRefPtrPassByPtr<int>>::value, + "StorensRefPtrPassByPtr<int> should be recognized as Storage Class"); + static_assert(IsParameterStorageClass<StorePtrPassByPtr<int>>::value, + "StorePtrPassByPtr<int> should be recognized as Storage Class"); + static_assert(IsParameterStorageClass<StoreConstPtrPassByConstPtr<int>>::value, + "StoreConstPtrPassByConstPtr<int> should be recognized as Storage Class"); + static_assert(IsParameterStorageClass<StoreCopyPassByConstPtr<int>>::value, + "StoreCopyPassByConstPtr<int> should be recognized as Storage Class"); + static_assert(IsParameterStorageClass<StoreCopyPassByPtr<int>>::value, + "StoreCopyPassByPtr<int> should be recognized as Storage Class"); + + RefPtr<ThreadUtilsObject> rpt(new ThreadUtilsObject); + int count = 0; + + // Test legacy functions. + + nsCOMPtr<nsIRunnable> r1 = + NewRunnableMethod(rpt, &ThreadUtilsObject::Test0); + r1->Run(); + EXPECT_EQ(count += 1, rpt->mCount); + + r1 = NewRunnableMethod<int>(rpt, &ThreadUtilsObject::Test1i, 11); + r1->Run(); + EXPECT_EQ(count += 2, rpt->mCount); + EXPECT_EQ(11, rpt->mA0); + + // Test variadic function with simple POD arguments. + + r1 = NewRunnableMethod(rpt, &ThreadUtilsObject::Test0); + r1->Run(); + EXPECT_EQ(count += 1, rpt->mCount); + + static_assert( + mozilla::IsSame< ::detail::ParameterStorage<int>::Type, + StoreCopyPassByValue<int>>::value, + "detail::ParameterStorage<int>::Type should be StoreCopyPassByValue<int>"); + static_assert( + mozilla::IsSame< ::detail::ParameterStorage<StoreCopyPassByValue<int>>::Type, + StoreCopyPassByValue<int>>::value, + "detail::ParameterStorage<StoreCopyPassByValue<int>>::Type should be StoreCopyPassByValue<int>"); + + r1 = NewRunnableMethod<int>(rpt, &ThreadUtilsObject::Test1i, 12); + r1->Run(); + EXPECT_EQ(count += 2, rpt->mCount); + EXPECT_EQ(12, rpt->mA0); + + r1 = NewRunnableMethod<int, int>( + rpt, &ThreadUtilsObject::Test2i, 21, 22); + r1->Run(); + EXPECT_EQ(count += 3, rpt->mCount); + EXPECT_EQ(21, rpt->mA0); + EXPECT_EQ(22, rpt->mA1); + + r1 = NewRunnableMethod<int, int, int>( + rpt, &ThreadUtilsObject::Test3i, 31, 32, 33); + r1->Run(); + EXPECT_EQ(count += 4, rpt->mCount); + EXPECT_EQ(31, rpt->mA0); + EXPECT_EQ(32, rpt->mA1); + EXPECT_EQ(33, rpt->mA2); + + r1 = NewRunnableMethod<int, int, int, int>( + rpt, &ThreadUtilsObject::Test4i, 41, 42, 43, 44); + r1->Run(); + EXPECT_EQ(count += 5, rpt->mCount); + EXPECT_EQ(41, rpt->mA0); + EXPECT_EQ(42, rpt->mA1); + EXPECT_EQ(43, rpt->mA2); + EXPECT_EQ(44, rpt->mA3); + + // More interesting types of arguments. + + // Passing a short to make sure forwarding works with an inexact type match. + short int si = 11; + r1 = NewRunnableMethod<int>(rpt, &ThreadUtilsObject::Test1i, si); + r1->Run(); + EXPECT_EQ(count += 2, rpt->mCount); + EXPECT_EQ(si, rpt->mA0); + + // Raw pointer, possible cv-qualified. + static_assert(mozilla::IsSame< ::detail::ParameterStorage<int*>::Type, + StorePtrPassByPtr<int>>::value, + "detail::ParameterStorage<int*>::Type should be StorePtrPassByPtr<int>"); + static_assert(mozilla::IsSame< ::detail::ParameterStorage<int* const>::Type, + StorePtrPassByPtr<int>>::value, + "detail::ParameterStorage<int* const>::Type should be StorePtrPassByPtr<int>"); + static_assert(mozilla::IsSame< ::detail::ParameterStorage<int* volatile>::Type, + StorePtrPassByPtr<int>>::value, + "detail::ParameterStorage<int* volatile>::Type should be StorePtrPassByPtr<int>"); + static_assert(mozilla::IsSame< ::detail::ParameterStorage<int* const volatile>::Type, + StorePtrPassByPtr<int>>::value, + "detail::ParameterStorage<int* const volatile>::Type should be StorePtrPassByPtr<int>"); + static_assert(mozilla::IsSame< ::detail::ParameterStorage<int*>::Type::stored_type, + int*>::value, + "detail::ParameterStorage<int*>::Type::stored_type should be int*"); + static_assert(mozilla::IsSame< ::detail::ParameterStorage<int*>::Type::passed_type, + int*>::value, + "detail::ParameterStorage<int*>::Type::passed_type should be int*"); + { + int i = 12; + r1 = NewRunnableMethod<int*>(rpt, &ThreadUtilsObject::Test1pi, &i); + r1->Run(); + EXPECT_EQ(count += 2, rpt->mCount); + EXPECT_EQ(i, rpt->mA0); + } + + // Raw pointer to const. + static_assert(mozilla::IsSame< ::detail::ParameterStorage<const int*>::Type, + StoreConstPtrPassByConstPtr<int>>::value, + "detail::ParameterStorage<const int*>::Type should be StoreConstPtrPassByConstPtr<int>"); + static_assert(mozilla::IsSame< ::detail::ParameterStorage<const int* const>::Type, + StoreConstPtrPassByConstPtr<int>>::value, + "detail::ParameterStorage<const int* const>::Type should be StoreConstPtrPassByConstPtr<int>"); + static_assert(mozilla::IsSame< ::detail::ParameterStorage<const int* volatile>::Type, + StoreConstPtrPassByConstPtr<int>>::value, + "detail::ParameterStorage<const int* volatile>::Type should be StoreConstPtrPassByConstPtr<int>"); + static_assert(mozilla::IsSame< ::detail::ParameterStorage<const int* const volatile>::Type, + StoreConstPtrPassByConstPtr<int>>::value, + "detail::ParameterStorage<const int* const volatile>::Type should be StoreConstPtrPassByConstPtr<int>"); + static_assert(mozilla::IsSame< ::detail::ParameterStorage<const int*>::Type::stored_type, + const int*>::value, + "detail::ParameterStorage<const int*>::Type::stored_type should be const int*"); + static_assert(mozilla::IsSame< ::detail::ParameterStorage<const int*>::Type::passed_type, + const int*>::value, + "detail::ParameterStorage<const int*>::Type::passed_type should be const int*"); + { + int i = 1201; + r1 = NewRunnableMethod<const int*>(rpt, &ThreadUtilsObject::Test1pci, &i); + r1->Run(); + EXPECT_EQ(count += 2, rpt->mCount); + EXPECT_EQ(i, rpt->mA0); + } + + // Raw pointer to copy. + static_assert(mozilla::IsSame<StoreCopyPassByPtr<int>::stored_type, + int>::value, + "StoreCopyPassByPtr<int>::stored_type should be int"); + static_assert(mozilla::IsSame<StoreCopyPassByPtr<int>::passed_type, + int*>::value, + "StoreCopyPassByPtr<int>::passed_type should be int*"); + { + int i = 1202; + r1 = NewRunnableMethod<StoreCopyPassByPtr<int>>( + rpt, &ThreadUtilsObject::Test1pi, i); + r1->Run(); + EXPECT_EQ(count += 2, rpt->mCount); + EXPECT_EQ(i, rpt->mA0); + } + + // Raw pointer to const copy. + static_assert(mozilla::IsSame<StoreCopyPassByConstPtr<int>::stored_type, + int>::value, + "StoreCopyPassByConstPtr<int>::stored_type should be int"); + static_assert(mozilla::IsSame<StoreCopyPassByConstPtr<int>::passed_type, + const int*>::value, + "StoreCopyPassByConstPtr<int>::passed_type should be const int*"); + { + int i = 1203; + r1 = NewRunnableMethod<StoreCopyPassByConstPtr<int>>( + rpt, &ThreadUtilsObject::Test1pci, i); + r1->Run(); + EXPECT_EQ(count += 2, rpt->mCount); + EXPECT_EQ(i, rpt->mA0); + } + + // nsRefPtr to pointer. + static_assert(mozilla::IsSame< ::detail::ParameterStorage<StorensRefPtrPassByPtr<SpyWithISupports>>::Type, + StorensRefPtrPassByPtr<SpyWithISupports>>::value, + "ParameterStorage<StorensRefPtrPassByPtr<SpyWithISupports>>::Type should be StorensRefPtrPassByPtr<SpyWithISupports>"); + static_assert(mozilla::IsSame< ::detail::ParameterStorage<SpyWithISupports*>::Type, + StorensRefPtrPassByPtr<SpyWithISupports>>::value, + "ParameterStorage<SpyWithISupports*>::Type should be StorensRefPtrPassByPtr<SpyWithISupports>"); + static_assert(mozilla::IsSame<StorensRefPtrPassByPtr<SpyWithISupports>::stored_type, + RefPtr<SpyWithISupports>>::value, + "StorensRefPtrPassByPtr<SpyWithISupports>::stored_type should be RefPtr<SpyWithISupports>"); + static_assert(mozilla::IsSame<StorensRefPtrPassByPtr<SpyWithISupports>::passed_type, + SpyWithISupports*>::value, + "StorensRefPtrPassByPtr<SpyWithISupports>::passed_type should be SpyWithISupports*"); + // (more nsRefPtr tests below) + + // nsRefPtr for ref-countable classes that do not derive from ISupports. + static_assert(::detail::HasRefCountMethods<ThreadUtilsRefCountedFinal>::value, + "ThreadUtilsRefCountedFinal has AddRef() and Release()"); + static_assert(mozilla::IsSame< ::detail::ParameterStorage<ThreadUtilsRefCountedFinal*>::Type, + StorensRefPtrPassByPtr<ThreadUtilsRefCountedFinal>>::value, + "ParameterStorage<ThreadUtilsRefCountedFinal*>::Type should be StorensRefPtrPassByPtr<ThreadUtilsRefCountedFinal>"); + static_assert(::detail::HasRefCountMethods<ThreadUtilsRefCountedBase>::value, + "ThreadUtilsRefCountedBase has AddRef() and Release()"); + static_assert(mozilla::IsSame< ::detail::ParameterStorage<ThreadUtilsRefCountedBase*>::Type, + StorensRefPtrPassByPtr<ThreadUtilsRefCountedBase>>::value, + "ParameterStorage<ThreadUtilsRefCountedBase*>::Type should be StorensRefPtrPassByPtr<ThreadUtilsRefCountedBase>"); + static_assert(::detail::HasRefCountMethods<ThreadUtilsRefCountedDerived>::value, + "ThreadUtilsRefCountedDerived has AddRef() and Release()"); + static_assert(mozilla::IsSame< ::detail::ParameterStorage<ThreadUtilsRefCountedDerived*>::Type, + StorensRefPtrPassByPtr<ThreadUtilsRefCountedDerived>>::value, + "ParameterStorage<ThreadUtilsRefCountedDerived*>::Type should be StorensRefPtrPassByPtr<ThreadUtilsRefCountedDerived>"); + + static_assert(!::detail::HasRefCountMethods<ThreadUtilsNonRefCounted>::value, + "ThreadUtilsNonRefCounted doesn't have AddRef() and Release()"); + static_assert(!mozilla::IsSame< ::detail::ParameterStorage<ThreadUtilsNonRefCounted*>::Type, + StorensRefPtrPassByPtr<ThreadUtilsNonRefCounted>>::value, + "ParameterStorage<ThreadUtilsNonRefCounted*>::Type should NOT be StorensRefPtrPassByPtr<ThreadUtilsNonRefCounted>"); + + // Lvalue reference. + static_assert(mozilla::IsSame< ::detail::ParameterStorage<int&>::Type, + StoreRefPassByLRef<int>>::value, + "ParameterStorage<int&>::Type should be StoreRefPassByLRef<int>"); + static_assert(mozilla::IsSame< ::detail::ParameterStorage<int&>::Type::stored_type, + StoreRefPassByLRef<int>::stored_type>::value, + "ParameterStorage<int&>::Type::stored_type should be StoreRefPassByLRef<int>::stored_type"); + static_assert(mozilla::IsSame< ::detail::ParameterStorage<int&>::Type::stored_type, + int&>::value, + "ParameterStorage<int&>::Type::stored_type should be int&"); + static_assert(mozilla::IsSame< ::detail::ParameterStorage<int&>::Type::passed_type, + int&>::value, + "ParameterStorage<int&>::Type::passed_type should be int&"); + { + int i = 13; + r1 = NewRunnableMethod<int&>(rpt, &ThreadUtilsObject::Test1ri, i); + r1->Run(); + EXPECT_EQ(count += 2, rpt->mCount); + EXPECT_EQ(i, rpt->mA0); + } + + // Rvalue reference -- Actually storing a copy and then moving it. + static_assert(mozilla::IsSame< ::detail::ParameterStorage<int&&>::Type, + StoreCopyPassByRRef<int>>::value, + "ParameterStorage<int&&>::Type should be StoreCopyPassByRRef<int>"); + static_assert(mozilla::IsSame< ::detail::ParameterStorage<int&&>::Type::stored_type, + StoreCopyPassByRRef<int>::stored_type>::value, + "ParameterStorage<int&&>::Type::stored_type should be StoreCopyPassByRRef<int>::stored_type"); + static_assert(mozilla::IsSame< ::detail::ParameterStorage<int&&>::Type::stored_type, + int>::value, + "ParameterStorage<int&&>::Type::stored_type should be int"); + static_assert(mozilla::IsSame< ::detail::ParameterStorage<int&&>::Type::passed_type, + int&&>::value, + "ParameterStorage<int&&>::Type::passed_type should be int&&"); + { + int i = 14; + r1 = NewRunnableMethod<int&&>( + rpt, &ThreadUtilsObject::Test1rri, mozilla::Move(i)); + } + r1->Run(); + EXPECT_EQ(count += 2, rpt->mCount); + EXPECT_EQ(14, rpt->mA0); + + // Null unique pointer, by semi-implicit store&move with "T&&" syntax. + static_assert(mozilla::IsSame< ::detail::ParameterStorage<mozilla::UniquePtr<int>&&>::Type, + StoreCopyPassByRRef<mozilla::UniquePtr<int>>>::value, + "ParameterStorage<UniquePtr<int>&&>::Type should be StoreCopyPassByRRef<UniquePtr<int>>"); + static_assert(mozilla::IsSame< ::detail::ParameterStorage<mozilla::UniquePtr<int>&&>::Type::stored_type, + StoreCopyPassByRRef<mozilla::UniquePtr<int>>::stored_type>::value, + "ParameterStorage<UniquePtr<int>&&>::Type::stored_type should be StoreCopyPassByRRef<UniquePtr<int>>::stored_type"); + static_assert(mozilla::IsSame< ::detail::ParameterStorage<mozilla::UniquePtr<int>&&>::Type::stored_type, + mozilla::UniquePtr<int>>::value, + "ParameterStorage<UniquePtr<int>&&>::Type::stored_type should be UniquePtr<int>"); + static_assert(mozilla::IsSame< ::detail::ParameterStorage<mozilla::UniquePtr<int>&&>::Type::passed_type, + mozilla::UniquePtr<int>&&>::value, + "ParameterStorage<UniquePtr<int>&&>::Type::passed_type should be UniquePtr<int>&&"); + { + mozilla::UniquePtr<int> upi; + r1 = NewRunnableMethod<mozilla::UniquePtr<int>&&>( + rpt, &ThreadUtilsObject::Test1upi, mozilla::Move(upi)); + } + r1->Run(); + EXPECT_EQ(count += 2, rpt->mCount); + EXPECT_EQ(-1, rpt->mA0); + rpt->mA0 = 0; + + // Null unique pointer, by explicit store&move with "StoreCopyPassByRRef<T>" syntax. + static_assert(mozilla::IsSame< ::detail::ParameterStorage<StoreCopyPassByRRef<mozilla::UniquePtr<int>>>::Type::stored_type, + StoreCopyPassByRRef<mozilla::UniquePtr<int>>::stored_type>::value, + "ParameterStorage<StoreCopyPassByRRef<UniquePtr<int>>>::Type::stored_type should be StoreCopyPassByRRef<UniquePtr<int>>::stored_type"); + static_assert(mozilla::IsSame< ::detail::ParameterStorage<StoreCopyPassByRRef<mozilla::UniquePtr<int>>>::Type::stored_type, + StoreCopyPassByRRef<mozilla::UniquePtr<int>>::stored_type>::value, + "ParameterStorage<StoreCopyPassByRRef<UniquePtr<int>>>::Type::stored_type should be StoreCopyPassByRRef<UniquePtr<int>>::stored_type"); + static_assert(mozilla::IsSame< ::detail::ParameterStorage<StoreCopyPassByRRef<mozilla::UniquePtr<int>>>::Type::stored_type, + mozilla::UniquePtr<int>>::value, + "ParameterStorage<StoreCopyPassByRRef<UniquePtr<int>>>::Type::stored_type should be UniquePtr<int>"); + static_assert(mozilla::IsSame< ::detail::ParameterStorage<StoreCopyPassByRRef<mozilla::UniquePtr<int>>>::Type::passed_type, + mozilla::UniquePtr<int>&&>::value, + "ParameterStorage<StoreCopyPassByRRef<UniquePtr<int>>>::Type::passed_type should be UniquePtr<int>&&"); + { + mozilla::UniquePtr<int> upi; + r1 = NewRunnableMethod + <StoreCopyPassByRRef<mozilla::UniquePtr<int>>>( + rpt, &ThreadUtilsObject::Test1upi, mozilla::Move(upi)); + } + r1->Run(); + EXPECT_EQ(count += 2, rpt->mCount); + EXPECT_EQ(-1, rpt->mA0); + + // Unique pointer as xvalue. + { + mozilla::UniquePtr<int> upi = mozilla::MakeUnique<int>(1); + r1 = NewRunnableMethod<mozilla::UniquePtr<int>&&>( + rpt, &ThreadUtilsObject::Test1upi, mozilla::Move(upi)); + } + r1->Run(); + EXPECT_EQ(count += 2, rpt->mCount); + EXPECT_EQ(1, rpt->mA0); + + { + mozilla::UniquePtr<int> upi = mozilla::MakeUnique<int>(1); + r1 = NewRunnableMethod + <StoreCopyPassByRRef<mozilla::UniquePtr<int>>> + (rpt, &ThreadUtilsObject::Test1upi, mozilla::Move(upi)); + } + r1->Run(); + EXPECT_EQ(count += 2, rpt->mCount); + EXPECT_EQ(1, rpt->mA0); + + // Unique pointer as prvalue. + r1 = NewRunnableMethod<mozilla::UniquePtr<int>&&>( + rpt, &ThreadUtilsObject::Test1upi, mozilla::MakeUnique<int>(2)); + r1->Run(); + EXPECT_EQ(count += 2, rpt->mCount); + EXPECT_EQ(2, rpt->mA0); + + // Unique pointer as lvalue to lref. + { + mozilla::UniquePtr<int> upi; + r1 = NewRunnableMethod<mozilla::UniquePtr<int>&>( + rpt, &ThreadUtilsObject::Test1rupi, upi); + // Passed as lref, so Run() must be called while local upi is still alive! + r1->Run(); + } + EXPECT_EQ(count += 2, rpt->mCount); + EXPECT_EQ(-1, rpt->mA0); + + // Verify copy/move assumptions. + + Spy::ClearAll(); + if (gDebug) { printf("%d - Test: Store copy from lvalue, pass by value\n", __LINE__); } + { // Block around nsCOMPtr lifetime. + nsCOMPtr<nsIRunnable> r2; + { // Block around Spy lifetime. + if (gDebug) { printf("%d - Spy s(10)\n", __LINE__); } + Spy s(10); + EXPECT_EQ(1, gConstructions); + EXPECT_EQ(1, gAlive); + if (gDebug) { printf("%d - r2 = NewRunnableMethod<StoreCopyPassByValue<Spy>>(&TestByValue, s)\n", __LINE__); } + r2 = NewRunnableMethod<StoreCopyPassByValue<Spy>>( + rpt, &ThreadUtilsObject::TestByValue, s); + EXPECT_EQ(2, gAlive); + EXPECT_LE(1, gCopyConstructions); // At least 1 copy-construction. + Spy::ClearActions(); + if (gDebug) { printf("%d - End block with Spy s(10)\n", __LINE__); } + } + EXPECT_EQ(1, gDestructions); + EXPECT_EQ(1, gAlive); + Spy::ClearActions(); + if (gDebug) { printf("%d - Run()\n", __LINE__); } + r2->Run(); + EXPECT_LE(1, gCopyConstructions); // Another copy-construction in call. + EXPECT_EQ(10, rpt->mSpy.mID); + EXPECT_LE(1, gDestructions); + EXPECT_EQ(1, gAlive); + Spy::ClearActions(); + if (gDebug) { printf("%d - End block with r\n", __LINE__); } + } + if (gDebug) { printf("%d - After end block with r\n", __LINE__); } + EXPECT_EQ(1, gDestructions); + EXPECT_EQ(0, gAlive); + + Spy::ClearAll(); + if (gDebug) { printf("%d - Test: Store copy from prvalue, pass by value\n", __LINE__); } + { + if (gDebug) { printf("%d - r3 = NewRunnableMethod<StoreCopyPassByValue<Spy>>(&TestByValue, Spy(11))\n", __LINE__); } + nsCOMPtr<nsIRunnable> r3 = + NewRunnableMethod<StoreCopyPassByValue<Spy>>( + rpt, &ThreadUtilsObject::TestByValue, Spy(11)); + EXPECT_EQ(1, gAlive); + EXPECT_EQ(1, gConstructions); + EXPECT_LE(1, gMoveConstructions); + Spy::ClearActions(); + if (gDebug) { printf("%d - Run()\n", __LINE__); } + r3->Run(); + EXPECT_LE(1, gCopyConstructions); // Another copy-construction in call. + EXPECT_EQ(11, rpt->mSpy.mID); + EXPECT_LE(1, gDestructions); + EXPECT_EQ(1, gAlive); + Spy::ClearActions(); + if (gDebug) { printf("%d - End block with r\n", __LINE__); } + } + if (gDebug) { printf("%d - After end block with r\n", __LINE__); } + EXPECT_EQ(1, gDestructions); + EXPECT_EQ(0, gAlive); + + Spy::ClearAll(); + { // Store copy from xvalue, pass by value. + nsCOMPtr<nsIRunnable> r4; + { + Spy s(12); + EXPECT_EQ(1, gConstructions); + EXPECT_EQ(1, gAlive); + Spy::ClearActions(); + r4 = NewRunnableMethod<StoreCopyPassByValue<Spy>>( + rpt, &ThreadUtilsObject::TestByValue, mozilla::Move(s)); + EXPECT_LE(1, gMoveConstructions); + EXPECT_EQ(1, gAlive); + EXPECT_EQ(1, gZombies); + Spy::ClearActions(); + } + EXPECT_EQ(1, gDestructions); + EXPECT_EQ(1, gAlive); + EXPECT_EQ(0, gZombies); + Spy::ClearActions(); + r4->Run(); + EXPECT_LE(1, gCopyConstructions); // Another copy-construction in call. + EXPECT_EQ(12, rpt->mSpy.mID); + EXPECT_LE(1, gDestructions); + EXPECT_EQ(1, gAlive); + Spy::ClearActions(); + } + EXPECT_EQ(1, gDestructions); + EXPECT_EQ(0, gAlive); + // Won't test xvalues anymore, prvalues are enough to verify all rvalues. + + Spy::ClearAll(); + if (gDebug) { printf("%d - Test: Store copy from lvalue, pass by const lvalue ref\n", __LINE__); } + { // Block around nsCOMPtr lifetime. + nsCOMPtr<nsIRunnable> r5; + { // Block around Spy lifetime. + if (gDebug) { printf("%d - Spy s(20)\n", __LINE__); } + Spy s(20); + EXPECT_EQ(1, gConstructions); + EXPECT_EQ(1, gAlive); + if (gDebug) { printf("%d - r5 = NewRunnableMethod<StoreCopyPassByConstLRef<Spy>>(&TestByConstLRef, s)\n", __LINE__); } + r5 = NewRunnableMethod<StoreCopyPassByConstLRef<Spy>>( + rpt, &ThreadUtilsObject::TestByConstLRef, s); + EXPECT_EQ(2, gAlive); + EXPECT_LE(1, gCopyConstructions); // At least 1 copy-construction. + Spy::ClearActions(); + if (gDebug) { printf("%d - End block with Spy s(20)\n", __LINE__); } + } + EXPECT_EQ(1, gDestructions); + EXPECT_EQ(1, gAlive); + Spy::ClearActions(); + if (gDebug) { printf("%d - Run()\n", __LINE__); } + r5->Run(); + EXPECT_EQ(0, gCopyConstructions); // No copies in call. + EXPECT_EQ(20, rpt->mSpy.mID); + EXPECT_EQ(0, gDestructions); + EXPECT_EQ(1, gAlive); + Spy::ClearActions(); + if (gDebug) { printf("%d - End block with r\n", __LINE__); } + } + if (gDebug) { printf("%d - After end block with r\n", __LINE__); } + EXPECT_EQ(1, gDestructions); + EXPECT_EQ(0, gAlive); + + Spy::ClearAll(); + if (gDebug) { printf("%d - Test: Store copy from prvalue, pass by const lvalue ref\n", __LINE__); } + { + if (gDebug) { printf("%d - r6 = NewRunnableMethod<StoreCopyPassByConstLRef<Spy>>(&TestByConstLRef, Spy(21))\n", __LINE__); } + nsCOMPtr<nsIRunnable> r6 = + NewRunnableMethod<StoreCopyPassByConstLRef<Spy>>( + rpt, &ThreadUtilsObject::TestByConstLRef, Spy(21)); + EXPECT_EQ(1, gAlive); + EXPECT_EQ(1, gConstructions); + EXPECT_LE(1, gMoveConstructions); + Spy::ClearActions(); + if (gDebug) { printf("%d - Run()\n", __LINE__); } + r6->Run(); + EXPECT_EQ(0, gCopyConstructions); // No copies in call. + EXPECT_EQ(21, rpt->mSpy.mID); + EXPECT_EQ(0, gDestructions); + EXPECT_EQ(1, gAlive); + Spy::ClearActions(); + if (gDebug) { printf("%d - End block with r\n", __LINE__); } + } + if (gDebug) { printf("%d - After end block with r\n", __LINE__); } + EXPECT_EQ(1, gDestructions); + EXPECT_EQ(0, gAlive); + + Spy::ClearAll(); + if (gDebug) { printf("%d - Test: Store copy from lvalue, pass by rvalue ref\n", __LINE__); } + { // Block around nsCOMPtr lifetime. + nsCOMPtr<nsIRunnable> r7; + { // Block around Spy lifetime. + if (gDebug) { printf("%d - Spy s(30)\n", __LINE__); } + Spy s(30); + EXPECT_EQ(1, gConstructions); + EXPECT_EQ(1, gAlive); + if (gDebug) { printf("%d - r7 = NewRunnableMethod<StoreCopyPassByRRef<Spy>>(&TestByRRef, s)\n", __LINE__); } + r7 = NewRunnableMethod<StoreCopyPassByRRef<Spy>>( + rpt, &ThreadUtilsObject::TestByRRef, s); + EXPECT_EQ(2, gAlive); + EXPECT_LE(1, gCopyConstructions); // At least 1 copy-construction. + Spy::ClearActions(); + if (gDebug) { printf("%d - End block with Spy s(30)\n", __LINE__); } + } + EXPECT_EQ(1, gDestructions); + EXPECT_EQ(1, gAlive); + Spy::ClearActions(); + if (gDebug) { printf("%d - Run()\n", __LINE__); } + r7->Run(); + EXPECT_LE(1, gMoves); // Move in call. + EXPECT_EQ(30, rpt->mSpy.mID); + EXPECT_EQ(0, gDestructions); + EXPECT_EQ(0, gAlive); // Spy inside Test is not counted. + EXPECT_EQ(1, gZombies); // Our local spy should now be a zombie. + Spy::ClearActions(); + if (gDebug) { printf("%d - End block with r\n", __LINE__); } + } + if (gDebug) { printf("%d - After end block with r\n", __LINE__); } + EXPECT_EQ(1, gDestructions); + EXPECT_EQ(0, gAlive); + + Spy::ClearAll(); + if (gDebug) { printf("%d - Test: Store copy from prvalue, pass by rvalue ref\n", __LINE__); } + { + if (gDebug) { printf("%d - r8 = NewRunnableMethod<StoreCopyPassByRRef<Spy>>(&TestByRRef, Spy(31))\n", __LINE__); } + nsCOMPtr<nsIRunnable> r8 = + NewRunnableMethod<StoreCopyPassByRRef<Spy>>( + rpt, &ThreadUtilsObject::TestByRRef, Spy(31)); + EXPECT_EQ(1, gAlive); + EXPECT_EQ(1, gConstructions); + EXPECT_LE(1, gMoveConstructions); + Spy::ClearActions(); + if (gDebug) { printf("%d - Run()\n", __LINE__); } + r8->Run(); + EXPECT_LE(1, gMoves); // Move in call. + EXPECT_EQ(31, rpt->mSpy.mID); + EXPECT_EQ(0, gDestructions); + EXPECT_EQ(0, gAlive); // Spy inside Test is not counted. + EXPECT_EQ(1, gZombies); // Our local spy should now be a zombie. + Spy::ClearActions(); + if (gDebug) { printf("%d - End block with r\n", __LINE__); } + } + if (gDebug) { printf("%d - After end block with r\n", __LINE__); } + EXPECT_EQ(1, gDestructions); + EXPECT_EQ(0, gAlive); + + Spy::ClearAll(); + if (gDebug) { printf("%d - Test: Store lvalue ref, pass lvalue ref\n", __LINE__); } + { + if (gDebug) { printf("%d - Spy s(40)\n", __LINE__); } + Spy s(40); + EXPECT_EQ(1, gConstructions); + EXPECT_EQ(1, gAlive); + Spy::ClearActions(); + if (gDebug) { printf("%d - r9 = NewRunnableMethod<Spy&>(&TestByLRef, s)\n", __LINE__); } + nsCOMPtr<nsIRunnable> r9 = + NewRunnableMethod<Spy&>( + rpt, &ThreadUtilsObject::TestByLRef, s); + EXPECT_EQ(0, gAllConstructions); + EXPECT_EQ(0, gDestructions); + EXPECT_EQ(1, gAlive); + Spy::ClearActions(); + if (gDebug) { printf("%d - Run()\n", __LINE__); } + r9->Run(); + EXPECT_LE(1, gAssignments); // Assignment from reference in call. + EXPECT_EQ(40, rpt->mSpy.mID); + EXPECT_EQ(&s, rpt->mSpyPtr); + EXPECT_EQ(0, gDestructions); + EXPECT_EQ(1, gAlive); // Spy inside Test is not counted. + Spy::ClearActions(); + if (gDebug) { printf("%d - End block with r\n", __LINE__); } + } + if (gDebug) { printf("%d - After end block with r\n", __LINE__); } + EXPECT_EQ(1, gDestructions); + EXPECT_EQ(0, gAlive); + + Spy::ClearAll(); + if (gDebug) { printf("%d - Test: Store nsRefPtr, pass by pointer\n", __LINE__); } + { // Block around nsCOMPtr lifetime. + nsCOMPtr<nsIRunnable> r10; + SpyWithISupports* ptr = 0; + { // Block around RefPtr<Spy> lifetime. + if (gDebug) { printf("%d - RefPtr<SpyWithISupports> s(new SpyWithISupports(45))\n", __LINE__); } + RefPtr<SpyWithISupports> s(new SpyWithISupports(45)); + ptr = s.get(); + EXPECT_EQ(1, gConstructions); + EXPECT_EQ(1, gAlive); + if (gDebug) { printf("%d - r10 = NewRunnableMethod<StorensRefPtrPassByPtr<Spy>>(&TestByRRef, s.get())\n", __LINE__); } + r10 = NewRunnableMethod<StorensRefPtrPassByPtr<SpyWithISupports>>( + rpt, &ThreadUtilsObject::TestByPointer, s.get()); + EXPECT_LE(0, gAllConstructions); + EXPECT_EQ(1, gAlive); + Spy::ClearActions(); + if (gDebug) { printf("%d - End block with RefPtr<Spy> s\n", __LINE__); } + } + EXPECT_EQ(0, gDestructions); + EXPECT_EQ(1, gAlive); + Spy::ClearActions(); + if (gDebug) { printf("%d - Run()\n", __LINE__); } + r10->Run(); + EXPECT_LE(1, gAssignments); // Assignment from pointee in call. + EXPECT_EQ(45, rpt->mSpy.mID); + EXPECT_EQ(ptr, rpt->mSpyPtr); + EXPECT_EQ(0, gDestructions); + EXPECT_EQ(1, gAlive); // Spy inside Test is not counted. + Spy::ClearActions(); + if (gDebug) { printf("%d - End block with r\n", __LINE__); } + } + if (gDebug) { printf("%d - After end block with r\n", __LINE__); } + EXPECT_EQ(1, gDestructions); + EXPECT_EQ(0, gAlive); + + Spy::ClearAll(); + if (gDebug) { printf("%d - Test: Store pointer to lvalue, pass by pointer\n", __LINE__); } + { + if (gDebug) { printf("%d - Spy s(55)\n", __LINE__); } + Spy s(55); + EXPECT_EQ(1, gConstructions); + EXPECT_EQ(1, gAlive); + Spy::ClearActions(); + if (gDebug) { printf("%d - r11 = NewRunnableMethod<Spy*>(&TestByPointer, s)\n", __LINE__); } + nsCOMPtr<nsIRunnable> r11 = + NewRunnableMethod<Spy*>( + rpt, &ThreadUtilsObject::TestByPointer, &s); + EXPECT_EQ(0, gAllConstructions); + EXPECT_EQ(0, gDestructions); + EXPECT_EQ(1, gAlive); + Spy::ClearActions(); + if (gDebug) { printf("%d - Run()\n", __LINE__); } + r11->Run(); + EXPECT_LE(1, gAssignments); // Assignment from pointee in call. + EXPECT_EQ(55, rpt->mSpy.mID); + EXPECT_EQ(&s, rpt->mSpyPtr); + EXPECT_EQ(0, gDestructions); + EXPECT_EQ(1, gAlive); // Spy inside Test is not counted. + Spy::ClearActions(); + if (gDebug) { printf("%d - End block with r\n", __LINE__); } + } + if (gDebug) { printf("%d - After end block with r\n", __LINE__); } + EXPECT_EQ(1, gDestructions); + EXPECT_EQ(0, gAlive); + + Spy::ClearAll(); + if (gDebug) { printf("%d - Test: Store pointer to const lvalue, pass by pointer\n", __LINE__); } + { + if (gDebug) { printf("%d - Spy s(60)\n", __LINE__); } + Spy s(60); + EXPECT_EQ(1, gConstructions); + EXPECT_EQ(1, gAlive); + Spy::ClearActions(); + if (gDebug) { printf("%d - r12 = NewRunnableMethod<Spy*>(&TestByPointer, s)\n", __LINE__); } + nsCOMPtr<nsIRunnable> r12 = + NewRunnableMethod<const Spy*>( + rpt, &ThreadUtilsObject::TestByPointerToConst, &s); + EXPECT_EQ(0, gAllConstructions); + EXPECT_EQ(0, gDestructions); + EXPECT_EQ(1, gAlive); + Spy::ClearActions(); + if (gDebug) { printf("%d - Run()\n", __LINE__); } + r12->Run(); + EXPECT_LE(1, gAssignments); // Assignment from pointee in call. + EXPECT_EQ(60, rpt->mSpy.mID); + EXPECT_EQ(&s, rpt->mSpyPtr); + EXPECT_EQ(0, gDestructions); + EXPECT_EQ(1, gAlive); // Spy inside Test is not counted. + Spy::ClearActions(); + if (gDebug) { printf("%d - End block with r\n", __LINE__); } + } + if (gDebug) { printf("%d - After end block with r\n", __LINE__); } + EXPECT_EQ(1, gDestructions); + EXPECT_EQ(0, gAlive); +#endif // XPCOM_GLUE_AVOID_NSPR +} diff --git a/xpcom/glue/tests/gtest/moz.build b/xpcom/glue/tests/gtest/moz.build new file mode 100644 index 0000000000..9f4d83a3e9 --- /dev/null +++ b/xpcom/glue/tests/gtest/moz.build @@ -0,0 +1,22 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +UNIFIED_SOURCES += [ + 'TestArray.cpp', + 'TestFileUtils.cpp', + 'TestGCPostBarriers.cpp', + 'TestNsDeque.cpp', + 'TestThreadUtils.cpp', +] + +LOCAL_INCLUDES = [ + '../..', +] + +FINAL_LIBRARY = 'xul-gtest' + +if CONFIG['GNU_CXX']: + CXXFLAGS += ['-Wno-error=shadow'] |